Merge pull request #17 from Ottermandias/SmallFixes

Small fixes
This commit is contained in:
Adam 2021-02-17 10:19:51 +11:00 committed by GitHub
commit 7d23505bfd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 2709 additions and 2195 deletions

View file

@ -2,7 +2,7 @@
[*] [*]
charset=utf-8 charset=utf-8
end_of_line=lf end_of_line=lf
trim_trailing_whitespace=false trim_trailing_whitespace=true
insert_final_newline=false insert_final_newline=false
indent_style=space indent_style=space
indent_size=4 indent_size=4
@ -10,6 +10,7 @@ indent_size=4
# Microsoft .NET properties # Microsoft .NET properties
csharp_new_line_before_members_in_object_initializers=false csharp_new_line_before_members_in_object_initializers=false
csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
csharp_prefer_braces=true:none
csharp_space_after_cast=false csharp_space_after_cast=false
csharp_space_after_keywords_in_control_flow_statements=false csharp_space_after_keywords_in_control_flow_statements=false
csharp_space_between_method_call_parameter_list_parentheses=true csharp_space_between_method_call_parameter_list_parentheses=true
@ -30,9 +31,26 @@ dotnet_style_qualification_for_property=false:suggestion
dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion
# ReSharper properties # ReSharper properties
resharper_align_multiline_binary_expressions_chain=false
resharper_align_multiline_calls_chain=false
resharper_autodetect_indent_settings=true resharper_autodetect_indent_settings=true
resharper_braces_redundant=true
resharper_constructor_or_destructor_body=expression_body
resharper_csharp_empty_block_style=together
resharper_csharp_max_line_length=144
resharper_csharp_space_within_array_access_brackets=true resharper_csharp_space_within_array_access_brackets=true
resharper_enforce_line_ending_style=true resharper_enforce_line_ending_style=true
resharper_int_align_assignments=true
resharper_int_align_comments=true
resharper_int_align_fields=true
resharper_int_align_invocations=false
resharper_int_align_nested_ternary=true
resharper_int_align_properties=false
resharper_int_align_switch_expressions=true
resharper_int_align_switch_sections=true
resharper_int_align_variables=true
resharper_local_function_body=expression_body
resharper_method_or_operator_body=expression_body
resharper_place_attribute_on_same_line=false resharper_place_attribute_on_same_line=false
resharper_space_after_cast=false resharper_space_after_cast=false
resharper_space_within_checked_parentheses=true resharper_space_within_checked_parentheses=true

View file

@ -9,10 +9,7 @@ namespace Penumbra.API
{ {
private readonly Plugin _plugin; private readonly Plugin _plugin;
public ModsController( Plugin plugin ) public ModsController( Plugin plugin ) => _plugin = plugin;
{
_plugin = plugin;
}
[Route( HttpVerbs.Get, "/mods" )] [Route( HttpVerbs.Get, "/mods" )]
public object GetMods() public object GetMods()

View file

@ -1,7 +1,7 @@
using Dalamud.Configuration;
using Dalamud.Plugin;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Dalamud.Configuration;
using Dalamud.Plugin;
namespace Penumbra namespace Penumbra
{ {

View file

@ -38,10 +38,7 @@ namespace Penumbra
{ {
public IntPtr Handle { get; set; } public IntPtr Handle { get; set; }
public DialogHandle( IntPtr handle ) public DialogHandle( IntPtr handle ) => Handle = handle;
{
Handle = handle;
}
} }
public class HiddenForm : Form public class HiddenForm : Form
@ -52,9 +49,9 @@ namespace Penumbra
public HiddenForm( CommonDialog form, IWin32Window owner, TaskCompletionSource< DialogResult > taskSource ) public HiddenForm( CommonDialog form, IWin32Window owner, TaskCompletionSource< DialogResult > taskSource )
{ {
this._form = form; _form = form;
this._owner = owner; _owner = owner;
this._taskSource = taskSource; _taskSource = taskSource;
Opacity = 0; Opacity = 0;
FormBorderStyle = FormBorderStyle.None; FormBorderStyle = FormBorderStyle.None;

View file

@ -25,7 +25,8 @@ namespace Penumbra.Extensions
/// <typeparam name="TField">The type of the underlying field</typeparam> /// <typeparam name="TField">The type of the underlying field</typeparam>
/// <returns>A delegate that will return a reference to a particular field - zero copy</returns> /// <returns>A delegate that will return a reference to a particular field - zero copy</returns>
/// <exception cref="MissingFieldException"></exception> /// <exception cref="MissingFieldException"></exception>
private static RefGet< TObject, TField > CreateRefGetter< TObject, TField >( string fieldName ) where TField : unmanaged private static RefGet< TObject, TField > CreateRefGetter< TObject, TField >( string fieldName )
where TField : unmanaged
{ {
const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;

View file

@ -14,11 +14,11 @@ namespace Penumbra.Game
public unsafe delegate void* UnloadPlayerResourcesPrototype( IntPtr pResourceManager ); public unsafe delegate void* UnloadPlayerResourcesPrototype( IntPtr pResourceManager );
public LoadPlayerResourcesPrototype LoadPlayerResources { get; private set; } public LoadPlayerResourcesPrototype LoadPlayerResources { get; }
public UnloadPlayerResourcesPrototype UnloadPlayerResources { get; private set; } public UnloadPlayerResourcesPrototype UnloadPlayerResources { get; }
// Object addresses // Object addresses
private IntPtr _playerResourceManagerAddress; private readonly IntPtr _playerResourceManagerAddress;
public IntPtr PlayerResourceManagerPtr => Marshal.ReadIntPtr( _playerResourceManagerAddress ); public IntPtr PlayerResourceManagerPtr => Marshal.ReadIntPtr( _playerResourceManagerAddress );
public GameUtils( DalamudPluginInterface pluginInterface ) public GameUtils( DalamudPluginInterface pluginInterface )

View file

@ -1,9 +1,10 @@
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Dalamud.Game.ClientState.Actors; using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types; using Dalamud.Game.ClientState.Actors.Types;
using System.Threading.Tasks;
namespace Penumbra namespace Penumbra.Game
{ {
public static class RefreshActors public static class RefreshActors
{ {
@ -12,43 +13,49 @@ namespace Penumbra
private const int RenderTaskOtherDelay = 25; private const int RenderTaskOtherDelay = 25;
private const int ModelInvisibilityFlag = 0b10; private const int ModelInvisibilityFlag = 0b10;
private static async void Redraw(Actor actor) private static async void Redraw( Actor actor )
{ {
var ptr = actor.Address; var ptr = actor.Address;
var renderModePtr = ptr + RenderModeOffset; var renderModePtr = ptr + RenderModeOffset;
var renderStatus = Marshal.ReadInt32(renderModePtr); var renderStatus = Marshal.ReadInt32( renderModePtr );
async void DrawObject(int delay) async void DrawObject( int delay )
{ {
Marshal.WriteInt32(renderModePtr, renderStatus | ModelInvisibilityFlag); Marshal.WriteInt32( renderModePtr, renderStatus | ModelInvisibilityFlag );
await Task.Delay(delay); await Task.Delay( delay );
Marshal.WriteInt32(renderModePtr, renderStatus & ~ModelInvisibilityFlag); Marshal.WriteInt32( renderModePtr, renderStatus & ~ModelInvisibilityFlag );
} }
if (actor.ObjectKind == Dalamud.Game.ClientState.Actors.ObjectKind.Player) if( actor.ObjectKind == ObjectKind.Player )
{ {
DrawObject(RenderTaskPlayerDelay); DrawObject( RenderTaskPlayerDelay );
await Task.Delay(RenderTaskPlayerDelay); await Task.Delay( RenderTaskPlayerDelay );
} }
else else
DrawObject(RenderTaskOtherDelay); {
DrawObject( RenderTaskOtherDelay );
}
} }
public static void RedrawSpecific(ActorTable actors, string name) public static void RedrawSpecific( ActorTable actors, string name )
{ {
if (name?.Length == 0) if( name?.Length == 0 )
RedrawAll(actors); {
RedrawAll( actors );
foreach (var actor in actors)
if (actor.Name == name)
Redraw(actor);
} }
public static void RedrawAll(ActorTable actors) foreach( var actor in actors.Where( A => A.Name == name ) )
{ {
foreach (var actor in actors) Redraw( actor );
Redraw(actor); }
}
public static void RedrawAll( ActorTable actors )
{
foreach( var actor in actors )
{
Redraw( actor );
}
} }
} }
} }

View file

@ -8,10 +8,7 @@ namespace Penumbra.Importer
{ {
private readonly FileStream _fileStream; private readonly FileStream _fileStream;
public MagicTempFileStreamManagerAndDeleterFuckery( FileStream stream ) : base( stream ) public MagicTempFileStreamManagerAndDeleterFuckery( FileStream stream ) : base( stream ) => _fileStream = stream;
{
_fileStream = stream;
}
public new void Dispose() public new void Dispose()
{ {

View file

@ -21,7 +21,7 @@ namespace Penumbra.Importer
public ImporterState State { get; private set; } public ImporterState State { get; private set; }
public long TotalProgress { get; private set; } public long TotalProgress { get; private set; } = 0;
public long CurrentProgress { get; private set; } public long CurrentProgress { get; private set; }
public float Progress public float Progress
@ -50,19 +50,7 @@ namespace Penumbra.Importer
{ {
CurrentModPack = modPackFile.Name; CurrentModPack = modPackFile.Name;
switch( modPackFile.Extension ) VerifyVersionAndImport( modPackFile );
{
case ".ttmp":
ImportV1ModPack( modPackFile );
break;
case ".ttmp2":
ImportV2ModPack( modPackFile );
break;
default:
throw new ArgumentException( $"Unrecognized modpack format: {modPackFile.Extension}", nameof(modPackFile) );
}
State = ImporterState.Done; State = ImporterState.Done;
} }
@ -88,15 +76,39 @@ namespace Penumbra.Importer
return new MagicTempFileStreamManagerAndDeleterFuckery( fs ); return new MagicTempFileStreamManagerAndDeleterFuckery( fs );
} }
private void ImportV1ModPack( FileInfo modPackFile ) private void VerifyVersionAndImport( FileInfo modPackFile )
{
using var zfs = modPackFile.OpenRead();
using var extractedModPack = new ZipFile( zfs );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modRaw = GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 );
// At least a better validation than going by the extension.
if( modRaw.Contains( "\"TTMPVersion\":" ) )
{
if( modPackFile.Extension != ".ttmp2" )
{
PluginLog.Warning( $"File {modPackFile.FullName} seems to be a V2 TTMP, but has the wrong extension." );
}
ImportV2ModPack( modPackFile, extractedModPack, modRaw );
}
else
{
if( modPackFile.Extension != ".ttmp" )
{
PluginLog.Warning( $"File {modPackFile.FullName} seems to be a V1 TTMP, but has the wrong extension." );
}
ImportV1ModPack( modPackFile, extractedModPack, modRaw );
}
}
private void ImportV1ModPack( FileInfo modPackFile, ZipFile extractedModPack, string modRaw )
{ {
PluginLog.Log( " -> Importing V1 ModPack" ); PluginLog.Log( " -> Importing V1 ModPack" );
using var zfs = modPackFile.OpenRead(); var modListRaw = modRaw.Split(
using var extractedModPack = new ZipFile( zfs );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modListRaw = GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ).Split(
new[] { "\r\n", "\r", "\n" }, new[] { "\r\n", "\r", "\n" },
StringSplitOptions.None StringSplitOptions.None
); );
@ -129,33 +141,26 @@ namespace Penumbra.Importer
ExtractSimpleModList( newModFolder, modList, modData ); ExtractSimpleModList( newModFolder, modList, modData );
} }
private void ImportV2ModPack( FileInfo modPackFile ) private void ImportV2ModPack( FileInfo modPackFile, ZipFile extractedModPack, string modRaw )
{ {
using var zfs = modPackFile.OpenRead(); var modList = JsonConvert.DeserializeObject< SimpleModPack >( modRaw );
using var extractedModPack = new ZipFile( zfs );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modList = JsonConvert.DeserializeObject< SimpleModPack >( GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ) );
if( modList.TTMPVersion.EndsWith( "s" ) ) if( modList.TTMPVersion.EndsWith( "s" ) )
{ {
ImportSimpleV2ModPack( extractedModPack ); ImportSimpleV2ModPack( extractedModPack, modList );
return; return;
} }
if( modList.TTMPVersion.EndsWith( "w" ) ) if( modList.TTMPVersion.EndsWith( "w" ) )
{ {
ImportExtendedV2ModPack( extractedModPack ); ImportExtendedV2ModPack( extractedModPack, modRaw );
} }
} }
private void ImportSimpleV2ModPack( ZipFile extractedModPack ) private void ImportSimpleV2ModPack( ZipFile extractedModPack, SimpleModPack modList )
{ {
PluginLog.Log( " -> Importing Simple V2 ModPack" ); PluginLog.Log( " -> Importing Simple V2 ModPack" );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modList = JsonConvert.DeserializeObject< SimpleModPack >( GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ) );
// Create a new ModMeta from the TTMP modlist info // Create a new ModMeta from the TTMP modlist info
var modMeta = new ModMeta var modMeta = new ModMeta
{ {
@ -179,12 +184,11 @@ namespace Penumbra.Importer
ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData ); ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData );
} }
private void ImportExtendedV2ModPack( ZipFile extractedModPack ) private void ImportExtendedV2ModPack( ZipFile extractedModPack, string modRaw )
{ {
PluginLog.Log( " -> Importing Extended V2 ModPack" ); PluginLog.Log( " -> Importing Extended V2 ModPack" );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" ); var modList = JsonConvert.DeserializeObject< ExtendedModPack >( modRaw );
var modList = JsonConvert.DeserializeObject< ExtendedModPack >( GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ) );
// Create a new ModMeta from the TTMP modlist info // Create a new ModMeta from the TTMP modlist info
var modMeta = new ModMeta var modMeta = new ModMeta
@ -202,28 +206,32 @@ namespace Penumbra.Importer
var newModFolder = new DirectoryInfo( var newModFolder = new DirectoryInfo(
Path.Combine( _outDirectory.FullName, Path.Combine( _outDirectory.FullName,
Path.GetFileNameWithoutExtension( modList.Name ) Path.GetFileNameWithoutExtension( modList.Name ).ReplaceInvalidPathSymbols()
) )
); );
newModFolder.Create(); newModFolder.Create();
if( modList.SimpleModsList != null ) if( modList.SimpleModsList != null )
{
ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData ); ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData );
}
if( modList.ModPackPages == null ) if( modList.ModPackPages == null )
{
return; return;
}
// Iterate through all pages // Iterate through all pages
foreach( var page in modList.ModPackPages) foreach( var group in modList.ModPackPages.SelectMany( page => page.ModGroups ) )
{ {
foreach(var group in page.ModGroups) { var groupFolder = new DirectoryInfo( Path.Combine( newModFolder.FullName, group.GroupName.ReplaceInvalidPathSymbols() ) );
var groupFolder = new DirectoryInfo(Path.Combine(newModFolder.FullName, group.GroupName)); foreach( var option in group.OptionList )
foreach(var option in group.OptionList) { {
var optionFolder = new DirectoryInfo( Path.Combine( groupFolder.FullName, option.Name ) ); var optionFolder = new DirectoryInfo( Path.Combine( groupFolder.FullName, option.Name.ReplaceInvalidPathSymbols() ) );
ExtractSimpleModList( optionFolder, option.ModsJsons, modData ); ExtractSimpleModList( optionFolder, option.ModsJsons, modData );
} }
AddMeta(newModFolder, groupFolder, group, modMeta);
} AddMeta( newModFolder, groupFolder, group, modMeta );
} }
File.WriteAllText( File.WriteAllText(
@ -232,32 +240,35 @@ namespace Penumbra.Importer
); );
} }
private void AddMeta( DirectoryInfo baseFolder, DirectoryInfo groupFolder,ModGroup group, ModMeta meta) private void AddMeta( DirectoryInfo baseFolder, DirectoryInfo groupFolder, ModGroup group, ModMeta meta )
{ {
var Inf = new InstallerInfo var Inf = new InstallerInfo
{ {
SelectionType = group.SelectionType, SelectionType = group.SelectionType,
GroupName = group.GroupName, GroupName = group.GroupName,
Options = new List<Option>(), Options = new List< Option >()
}; };
foreach( var opt in group.OptionList ) foreach( var opt in group.OptionList )
{ {
var optio = new Option var option = new Option
{ {
OptionName = opt.Name, OptionName = opt.Name,
OptionDesc = String.IsNullOrEmpty(opt.Description) ? "" : opt.Description, OptionDesc = string.IsNullOrEmpty( opt.Description ) ? "" : opt.Description,
OptionFiles = new Dictionary<string, HashSet<string>>() OptionFiles = new Dictionary< string, HashSet< string > >()
}; };
var optDir = new DirectoryInfo(Path.Combine( groupFolder.FullName, opt.Name)); var optDir = new DirectoryInfo( Path.Combine( groupFolder.FullName, opt.Name.ReplaceInvalidPathSymbols() ) );
if (optDir.Exists) if( !optDir.Exists )
{ {
foreach ( var file in optDir.EnumerateFiles("*.*", SearchOption.AllDirectories) ) foreach( var file in optDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
{ {
optio.AddFile(file.FullName.Substring(baseFolder.FullName.Length).TrimStart('\\'), file.FullName.Substring(optDir.FullName.Length).TrimStart('\\').Replace('\\','/')); option.AddFile( file.FullName.Substring( baseFolder.FullName.Length ).TrimStart( '\\' ),
file.FullName.Substring( optDir.FullName.Length ).TrimStart( '\\' ).Replace( '\\', '/' ) );
} }
} }
Inf.Options.Add( optio );
Inf.Options.Add( option );
} }
meta.Groups.Add( group.GroupName, Inf ); meta.Groups.Add( group.GroupName, Inf );
} }
@ -273,17 +284,11 @@ namespace Penumbra.Importer
// haha allocation go brr // haha allocation go brr
var wtf = mods.ToList(); var wtf = mods.ToList();
TotalProgress = wtf.LongCount(); TotalProgress += wtf.LongCount();
// Extract each SimpleMod into the new mod folder // Extract each SimpleMod into the new mod folder
foreach( var simpleMod in wtf ) foreach( var simpleMod in wtf.Where( M => M != null ) )
{ {
if( simpleMod == null )
{
// do we increment here too???? can this even happen?????
continue;
}
ExtractMod( outDirectory, simpleMod, dataStream ); ExtractMod( outDirectory, simpleMod, dataStream );
CurrentProgress++; CurrentProgress++;
} }
@ -301,13 +306,15 @@ namespace Penumbra.Importer
extractedFile.Directory?.Create(); extractedFile.Directory?.Create();
if( extractedFile.FullName.EndsWith( "mdl" ) ) if( extractedFile.FullName.EndsWith( "mdl" ) )
{
ProcessMdl( data.Data ); ProcessMdl( data.Data );
}
File.WriteAllBytes( extractedFile.FullName, data.Data ); File.WriteAllBytes( extractedFile.FullName, data.Data );
} }
catch( Exception ex ) catch( Exception ex )
{ {
PluginLog.LogError( ex, "Could not export mod." ); PluginLog.LogError( ex, "Could not extract mod." );
} }
} }
@ -326,10 +333,7 @@ namespace Penumbra.Importer
mdl[ modelHeaderStart + modelHeaderLodOffset ] = 1; mdl[ modelHeaderStart + modelHeaderLodOffset ] = 1;
} }
private static Stream GetStreamFromZipEntry( ZipFile file, ZipEntry entry ) private static Stream GetStreamFromZipEntry( ZipFile file, ZipEntry entry ) => file.GetInputStream( entry );
{
return file.GetInputStream( entry );
}
private static string GetStringFromZipEntry( ZipFile file, ZipEntry entry, Encoding encoding ) private static string GetStringFromZipEntry( ZipFile file, ZipEntry entry, Encoding encoding )
{ {

View file

@ -1,163 +1,170 @@
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Cryptography;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Collections; using System.Security.Cryptography;
using Dalamud.Plugin; using Dalamud.Plugin;
namespace Penumbra.Models namespace Penumbra.Models
{ {
public class Deduplicator public class Deduplicator
{ {
private DirectoryInfo baseDir; private readonly DirectoryInfo _baseDir;
private int baseDirLength; private readonly int _baseDirLength;
private ModMeta mod; private readonly ModMeta _mod;
private SHA256 hasher = null; private SHA256 _hasher;
private Dictionary<long, List<FileInfo>> filesBySize; private readonly Dictionary< long, List< FileInfo > > _filesBySize = new();
private ref SHA256 Sha() private ref SHA256 Sha()
{ {
if (hasher == null) _hasher ??= SHA256.Create();
hasher = SHA256.Create(); return ref _hasher;
return ref hasher;
} }
public Deduplicator(DirectoryInfo baseDir, ModMeta mod) public Deduplicator( DirectoryInfo baseDir, ModMeta mod )
{ {
this.baseDir = baseDir; _baseDir = baseDir;
this.baseDirLength = baseDir.FullName.Length; _baseDirLength = baseDir.FullName.Length;
this.mod = mod; _mod = mod;
filesBySize = new();
BuildDict(); BuildDict();
} }
private void BuildDict() private void BuildDict()
{ {
foreach( var file in baseDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) ) foreach( var file in _baseDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
{ {
var fileLength = file.Length; var fileLength = file.Length;
if (filesBySize.TryGetValue(fileLength, out var files)) if( _filesBySize.TryGetValue( fileLength, out var files ) )
files.Add(file); {
files.Add( file );
}
else else
filesBySize[fileLength] = new(){ file }; {
_filesBySize[ fileLength ] = new List< FileInfo >() { file };
}
} }
} }
public void Run() public void Run()
{ {
foreach (var pair in filesBySize) foreach( var pair in _filesBySize.Where( pair => pair.Value.Count >= 2 ) )
{ {
if (pair.Value.Count < 2) if( pair.Value.Count == 2 )
continue;
if (pair.Value.Count == 2)
{ {
if (CompareFilesDirectly(pair.Value[0], pair.Value[1])) if( CompareFilesDirectly( pair.Value[ 0 ], pair.Value[ 1 ] ) )
ReplaceFile(pair.Value[0], pair.Value[1]); {
ReplaceFile( pair.Value[ 0 ], pair.Value[ 1 ] );
}
} }
else else
{ {
var deleted = Enumerable.Repeat(false, pair.Value.Count).ToArray(); var deleted = Enumerable.Repeat( false, pair.Value.Count ).ToArray();
var hashes = pair.Value.Select( F => ComputeHash(F)).ToArray(); var hashes = pair.Value.Select( ComputeHash ).ToArray();
for (var i = 0; i < pair.Value.Count; ++i) for( var i = 0; i < pair.Value.Count; ++i )
{ {
if (deleted[i]) if( deleted[ i ] )
continue;
for (var j = i + 1; j < pair.Value.Count; ++j)
{ {
if (deleted[j])
continue; continue;
if (!CompareHashes(hashes[i], hashes[j]))
continue;
ReplaceFile(pair.Value[i], pair.Value[j]);
deleted[j] = true;
}
}
}
}
ClearEmptySubDirectories(baseDir);
} }
private void ReplaceFile(FileInfo f1, FileInfo f2) for( var j = i + 1; j < pair.Value.Count; ++j )
{ {
var relName1 = f1.FullName.Substring(baseDirLength).TrimStart('\\'); if( deleted[ j ] || !CompareHashes( hashes[ i ], hashes[ j ] ) )
var relName2 = f2.FullName.Substring(baseDirLength).TrimStart('\\'); {
continue;
}
ReplaceFile( pair.Value[ i ], pair.Value[ j ] );
deleted[ j ] = true;
}
}
}
}
ClearEmptySubDirectories( _baseDir );
}
private void ReplaceFile( FileInfo f1, FileInfo f2 )
{
var relName1 = f1.FullName.Substring( _baseDirLength ).TrimStart( '\\' );
var relName2 = f2.FullName.Substring( _baseDirLength ).TrimStart( '\\' );
var inOption = false; var inOption = false;
foreach (var group in mod.Groups.Select( g => g.Value.Options)) foreach( var group in _mod.Groups.Select( g => g.Value.Options ) )
{ {
foreach (var option in group) foreach( var option in group )
{ {
if (option.OptionFiles.TryGetValue(relName2, out var values)) if( option.OptionFiles.TryGetValue( relName2, out var values ) )
{ {
inOption = true; inOption = true;
foreach (var value in values) foreach( var value in values )
option.AddFile(relName1, value); {
option.OptionFiles.Remove(relName2); option.AddFile( relName1, value );
}
option.OptionFiles.Remove( relName2 );
} }
} }
} }
if (!inOption)
if( !inOption )
{ {
const string duplicates = "Duplicates"; const string duplicates = "Duplicates";
if (!mod.Groups.ContainsKey(duplicates)) if( !_mod.Groups.ContainsKey( duplicates ) )
{ {
InstallerInfo info = new() InstallerInfo info = new()
{ {
GroupName = duplicates, GroupName = duplicates,
SelectionType = SelectType.Single, SelectionType = SelectType.Single,
Options = new() Options = new List< Option >()
{ {
new() new()
{ {
OptionName = "Required", OptionName = "Required",
OptionDesc = "", OptionDesc = "",
OptionFiles = new() OptionFiles = new Dictionary< string, HashSet< string > >()
} }
} }
}; };
mod.Groups.Add(duplicates, info); _mod.Groups.Add( duplicates, info );
} }
mod.Groups[duplicates].Options[0].AddFile(relName1, relName2.Replace('\\', '/'));
mod.Groups[duplicates].Options[0].AddFile(relName1, relName1.Replace('\\', '/')); _mod.Groups[ duplicates ].Options[ 0 ].AddFile( relName1, relName2.Replace( '\\', '/' ) );
_mod.Groups[ duplicates ].Options[ 0 ].AddFile( relName1, relName1.Replace( '\\', '/' ) );
} }
PluginLog.Information($"File {relName1} and {relName2} are identical. Deleting the second.");
PluginLog.Information( $"File {relName1} and {relName2} are identical. Deleting the second." );
f2.Delete(); f2.Delete();
} }
public static bool CompareFilesDirectly(FileInfo f1, FileInfo f2) public static bool CompareFilesDirectly( FileInfo f1, FileInfo f2 )
{ => File.ReadAllBytes( f1.FullName ).SequenceEqual( File.ReadAllBytes( f2.FullName ) );
return File.ReadAllBytes(f1.FullName).SequenceEqual(File.ReadAllBytes(f2.FullName));
}
public static bool CompareHashes(byte[] f1, byte[] f2) public static bool CompareHashes( byte[] f1, byte[] f2 )
{ => StructuralComparisons.StructuralEqualityComparer.Equals( f1, f2 );
return StructuralComparisons.StructuralEqualityComparer.Equals(f1, f2);
}
public byte[] ComputeHash(FileInfo f) public byte[] ComputeHash( FileInfo f )
{ {
var stream = File.OpenRead( f.FullName ); var stream = File.OpenRead( f.FullName );
var ret = Sha().ComputeHash(stream); var ret = Sha().ComputeHash( stream );
stream.Dispose(); stream.Dispose();
return ret; return ret;
} }
// Does not delete the base directory itself even if it is completely empty at the end. // Does not delete the base directory itself even if it is completely empty at the end.
public static void ClearEmptySubDirectories(DirectoryInfo baseDir) public static void ClearEmptySubDirectories( DirectoryInfo baseDir )
{ {
foreach (var subDir in baseDir.GetDirectories()) foreach( var subDir in baseDir.GetDirectories() )
{
ClearEmptySubDirectories( subDir );
if( subDir.GetFiles().Length == 0 && subDir.GetDirectories().Length == 0 )
{ {
ClearEmptySubDirectories(subDir);
if (subDir.GetFiles().Length == 0 && subDir.GetDirectories().Length == 0)
subDir.Delete(); subDir.Delete();
} }
} }
} }
}
} }

View file

@ -5,30 +5,37 @@ namespace Penumbra.Models
{ {
public enum SelectType public enum SelectType
{ {
Single, Multi Single,
Multi
} }
public struct Option public struct Option
{ {
public string OptionName; public string OptionName;
public string OptionDesc; public string OptionDesc;
[JsonProperty(ItemConverterType = typeof(SingleOrArrayConverter<string>))] [JsonProperty( ItemConverterType = typeof( SingleOrArrayConverter< string > ) )]
public Dictionary<string, HashSet<string>> OptionFiles; public Dictionary< string, HashSet< string > > OptionFiles;
public bool AddFile(string filePath, string gamePath) public bool AddFile( string filePath, string gamePath )
{ {
if (OptionFiles.TryGetValue(filePath, out var set)) if( OptionFiles.TryGetValue( filePath, out var set ) )
return set.Add(gamePath); {
else return set.Add( gamePath );
OptionFiles[filePath] = new(){ gamePath }; }
OptionFiles[ filePath ] = new HashSet< string >() { gamePath };
return true; return true;
} }
} }
public struct InstallerInfo { public struct InstallerInfo
{
public string GroupName; public string GroupName;
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
[JsonConverter( typeof( Newtonsoft.Json.Converters.StringEnumConverter ) )]
public SelectType SelectionType; public SelectType SelectionType;
public List<Option> Options;
public List< Option > Options;
} }
} }

View file

@ -1,5 +1,5 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
using Penumbra.Mods; using Penumbra.Mods;
namespace Penumbra.Models namespace Penumbra.Models
@ -9,7 +9,7 @@ namespace Penumbra.Models
public string FolderName { get; set; } public string FolderName { get; set; }
public bool Enabled { get; set; } public bool Enabled { get; set; }
public int Priority { get; set; } public int Priority { get; set; }
public Dictionary<string, int> Conf {get;set;} public Dictionary< string, int > Conf { get; set; }
[JsonIgnore] [JsonIgnore]
public ResourceMod Mod { get; set; } public ResourceMod Mod { get; set; }

View file

@ -1,8 +1,8 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Linq;
using System.IO;
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
namespace Penumbra.Models namespace Penumbra.Models
{ {
@ -21,21 +21,24 @@ namespace Penumbra.Models
public Dictionary< string, string > FileSwaps { get; } = new(); public Dictionary< string, string > FileSwaps { get; } = new();
public Dictionary<string, InstallerInfo> Groups { get; set; } = new(); public Dictionary< string, InstallerInfo > Groups { get; set; } = new();
[JsonIgnore] [JsonIgnore]
public bool HasGroupWithConfig { get; set; } = false; public bool HasGroupWithConfig { get; set; } = false;
public static ModMeta LoadFromFile(string filePath) public static ModMeta LoadFromFile( string filePath )
{ {
try try
{ {
var meta = JsonConvert.DeserializeObject< ModMeta >( File.ReadAllText( filePath ) ); var meta = JsonConvert.DeserializeObject< ModMeta >( File.ReadAllText( filePath ) );
meta.HasGroupWithConfig = meta.Groups != null && meta.Groups.Count > 0 meta.HasGroupWithConfig =
&& meta.Groups.Values.Any( G => G.SelectionType == SelectType.Multi || G.Options.Count > 1); meta.Groups != null
&& meta.Groups.Count > 0
&& meta.Groups.Values.Any( G => G.SelectionType == SelectType.Multi || G.Options.Count > 1 );
return meta; return meta;
} }
catch( Exception) catch( Exception )
{ {
return null; return null;
// todo: handle broken mods properly // todo: handle broken mods properly

View file

@ -16,10 +16,7 @@ namespace Penumbra.Mods
public ResourceMod[] EnabledMods { get; set; } public ResourceMod[] EnabledMods { get; set; }
public ModCollection( DirectoryInfo basePath ) public ModCollection( DirectoryInfo basePath ) => _basePath = basePath;
{
_basePath = basePath;
}
public void Load( bool invertOrder = false ) public void Load( bool invertOrder = false )
{ {
@ -51,7 +48,7 @@ namespace Penumbra.Mods
} }
#endif #endif
ModSettings ??= new(); ModSettings ??= new List< ModInfo >();
var foundMods = new List< string >(); var foundMods = new List< string >();
foreach( var modDir in _basePath.EnumerateDirectories() ) foreach( var modDir in _basePath.EnumerateDirectories() )

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Dalamud.Plugin;
using Penumbra.Models; using Penumbra.Models;
namespace Penumbra.Mods namespace Penumbra.Mods
@ -16,10 +17,7 @@ namespace Penumbra.Mods
private DirectoryInfo _basePath; private DirectoryInfo _basePath;
public ModManager( Plugin plugin ) public ModManager( Plugin plugin ) => _plugin = plugin;
{
_plugin = plugin;
}
public void DiscoverMods() public void DiscoverMods()
{ {
@ -113,7 +111,7 @@ namespace Penumbra.Mods
mod.FileConflicts?.Clear(); mod.FileConflicts?.Clear();
if( settings.Conf == null ) if( settings.Conf == null )
{ {
settings.Conf = new(); settings.Conf = new Dictionary< string, int >();
_plugin.ModManager.Mods.Save(); _plugin.ModManager.Mods.Save();
} }
@ -154,13 +152,14 @@ namespace Penumbra.Mods
{ {
var relativeFilePath = file.FullName.Substring( baseDir.Length ).TrimStart( '\\' ); var relativeFilePath = file.FullName.Substring( baseDir.Length ).TrimStart( '\\' );
bool doNotAdd = false; var doNotAdd = false;
HashSet< string > paths; HashSet< string > paths;
foreach( var group in mod.Meta.Groups.Select( G => G.Value ) ) foreach( var group in mod.Meta.Groups.Select( G => G.Value ) )
{ {
if( !settings.Conf.TryGetValue( group.GroupName, out var setting ) if( !settings.Conf.TryGetValue( group.GroupName, out var setting )
|| ( group.SelectionType == SelectType.Single && settings.Conf[ group.GroupName ] >= group.Options.Count ) ) || group.SelectionType == SelectType.Single
&& settings.Conf[ group.GroupName ] >= group.Options.Count )
{ {
settings.Conf[ group.GroupName ] = 0; settings.Conf[ group.GroupName ] = 0;
_plugin.ModManager.Mods.Save(); _plugin.ModManager.Mods.Save();
@ -168,10 +167,14 @@ namespace Penumbra.Mods
} }
if( group.Options.Count == 0 ) if( group.Options.Count == 0 )
{
continue; continue;
}
if( group.SelectionType == SelectType.Multi ) if( group.SelectionType == SelectType.Multi )
settings.Conf[ group.GroupName ] &= ( ( 1 << group.Options.Count ) - 1 ); {
settings.Conf[ group.GroupName ] &= ( 1 << group.Options.Count ) - 1;
}
switch( group.SelectionType ) switch( group.SelectionType )
{ {
@ -182,15 +185,10 @@ namespace Penumbra.Mods
} }
else else
{ {
for( var i = 0; i < group.Options.Count; ++i ) if( group.Options.Where( ( o, i ) => i != setting )
{ .Any( option => option.OptionFiles.ContainsKey( relativeFilePath ) ) )
if( i == setting )
continue;
if( group.Options[ i ].OptionFiles.ContainsKey( relativeFilePath ) )
{ {
doNotAdd = true; doNotAdd = true;
break;
}
} }
} }
@ -206,8 +204,10 @@ namespace Penumbra.Mods
} }
} }
else if( group.Options[ i ].OptionFiles.ContainsKey( relativeFilePath ) ) else if( group.Options[ i ].OptionFiles.ContainsKey( relativeFilePath ) )
{
doNotAdd = true; doNotAdd = true;
} }
}
break; break;
} }
@ -215,7 +215,7 @@ namespace Penumbra.Mods
if( !doNotAdd ) if( !doNotAdd )
{ {
AddFiles( new() { relativeFilePath.Replace( '\\', '/' ) }, out doNotAdd, file, registeredFiles, mod ); AddFiles( new HashSet< string > { relativeFilePath.Replace( '\\', '/' ) }, out doNotAdd, file, registeredFiles, mod );
} }
} }
} }
@ -245,8 +245,19 @@ namespace Penumbra.Mods
} }
public void DeleteMod( ResourceMod mod ) public void DeleteMod( ResourceMod mod )
{
if( mod?.ModBasePath?.Exists ?? false )
{
try
{ {
Directory.Delete( mod.ModBasePath.FullName, true ); Directory.Delete( mod.ModBasePath.FullName, true );
}
catch( Exception e )
{
PluginLog.Error( $"Could not delete the mod {mod.ModBasePath.Name}:\n{e}" );
}
}
DiscoverMods(); DiscoverMods();
} }
@ -267,9 +278,7 @@ namespace Penumbra.Mods
} }
public string GetSwappedFilePath( string gameResourcePath ) public string GetSwappedFilePath( string gameResourcePath )
{ => SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null;
return SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null;
}
public string ResolveSwappedOrReplacementFilePath( string gameResourcePath ) public string ResolveSwappedOrReplacementFilePath( string gameResourcePath )
{ {

View file

@ -21,6 +21,10 @@
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Dalamud"> <Reference Include="Dalamud">
<HintPath>$(DALAMUD_ROOT)\Dalamud.dll</HintPath> <HintPath>$(DALAMUD_ROOT)\Dalamud.dll</HintPath>

View file

@ -57,6 +57,7 @@ namespace Penumbra
GameUtils.ReloadPlayerResources(); GameUtils.ReloadPlayerResources();
SettingsInterface = new SettingsInterface( this ); SettingsInterface = new SettingsInterface( this );
PluginInterface.UiBuilder.OnBuildUi += SettingsInterface.Draw; PluginInterface.UiBuilder.OnBuildUi += SettingsInterface.Draw;
PluginDebugTitleStr = $"{Name} - Debug Build"; PluginDebugTitleStr = $"{Name} - Debug Build";
@ -122,10 +123,15 @@ namespace Penumbra
} }
case "redraw": case "redraw":
{ {
if (args.Length > 1) if( args.Length > 1 )
RefreshActors.RedrawSpecific(PluginInterface.ClientState.Actors, string.Join(" ", args.Skip(1))); {
RefreshActors.RedrawSpecific( PluginInterface.ClientState.Actors, string.Join( " ", args.Skip( 1 ) ) );
}
else else
RefreshActors.RedrawAll(PluginInterface.ClientState.Actors); {
RefreshActors.RedrawAll( PluginInterface.ClientState.Actors );
}
break; break;
} }
} }

View file

@ -1,14 +1,14 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using Dalamud.Plugin; using Dalamud.Plugin;
using Penumbra.Structs; using Penumbra.Structs;
using Penumbra.Util; using Penumbra.Util;
using FileMode = Penumbra.Structs.FileMode;
using Reloaded.Hooks; using Reloaded.Hooks;
using Reloaded.Hooks.Definitions; using Reloaded.Hooks.Definitions;
using Reloaded.Hooks.Definitions.X64; using Reloaded.Hooks.Definitions.X64;
using FileMode = Penumbra.Structs.FileMode;
namespace Penumbra namespace Penumbra
{ {
@ -129,7 +129,7 @@ namespace Penumbra
PluginLog.Log( "[GetResourceHandler] {0}", gameFsPath ); PluginLog.Log( "[GetResourceHandler] {0}", gameFsPath );
} }
if (Plugin.Configuration.IsEnabled) if( Plugin.Configuration.IsEnabled )
{ {
var replacementPath = Plugin.ModManager.ResolveSwappedOrReplacementFilePath( gameFsPath ); var replacementPath = Plugin.ModManager.ResolveSwappedOrReplacementFilePath( gameFsPath );
@ -192,7 +192,9 @@ namespace Penumbra
public void Enable() public void Enable()
{ {
if( IsEnabled ) if( IsEnabled )
{
return; return;
}
ReadSqpackHook.Activate(); ReadSqpackHook.Activate();
GetResourceSyncHook.Activate(); GetResourceSyncHook.Activate();
@ -208,7 +210,9 @@ namespace Penumbra
public void Disable() public void Disable()
{ {
if( !IsEnabled ) if( !IsEnabled )
{
return; return;
}
ReadSqpackHook.Disable(); ReadSqpackHook.Disable();
GetResourceSyncHook.Disable(); GetResourceSyncHook.Disable();
@ -220,7 +224,9 @@ namespace Penumbra
public void Dispose() public void Dispose()
{ {
if( IsEnabled ) if( IsEnabled )
{
Disable(); Disable();
}
// ReadSqpackHook.Disable(); // ReadSqpackHook.Disable();
// GetResourceSyncHook.Disable(); // GetResourceSyncHook.Disable();

View file

@ -1,9 +1,10 @@
namespace Penumbra.Structs namespace Penumbra.Structs
{ {
public enum FileMode : uint public enum FileMode : uint
{ {
LoadUnpackedResource = 0, LoadUnpackedResource = 0,
LoadFileResource = 1, // Shit in My Games uses this LoadFileResource = 1, // Shit in My Games uses this
// some shit here, the game does some jump if its < 0xA for other files for some reason but there's no impl, probs debug? // some shit here, the game does some jump if its < 0xA for other files for some reason but there's no impl, probs debug?
LoadIndexResource = 0xA, // load index/index2 LoadIndexResource = 0xA, // load index/index2
LoadSqPackResource = 0xB LoadSqPackResource = 0xB

View file

@ -1,4 +1,4 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Penumbra.Structs namespace Penumbra.Structs
{ {

View file

@ -1,4 +1,4 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Penumbra.Structs namespace Penumbra.Structs
{ {

View file

@ -7,124 +7,134 @@ namespace Penumbra.UI
{ {
public static partial class ImGuiCustom public static partial class ImGuiCustom
{ {
public static void BeginFramedGroup(string label) => BeginFramedGroupInternal(ref label, ZeroVector, false); public static void BeginFramedGroup( string label ) => BeginFramedGroupInternal( ref label, ZeroVector, false );
public static void BeginFramedGroup(string label, Vector2 minSize) => BeginFramedGroupInternal(ref label, minSize, false); public static void BeginFramedGroup( string label, Vector2 minSize ) => BeginFramedGroupInternal( ref label, minSize, false );
public static bool BeginFramedGroupEdit(ref string label) => BeginFramedGroupInternal(ref label, ZeroVector, true); public static bool BeginFramedGroupEdit( ref string label ) => BeginFramedGroupInternal( ref label, ZeroVector, true );
public static bool BeginFramedGroupEdit(ref string label, Vector2 minSize) => BeginFramedGroupInternal(ref label, minSize, true); public static bool BeginFramedGroupEdit( ref string label, Vector2 minSize ) => BeginFramedGroupInternal( ref label, minSize, true );
private static bool BeginFramedGroupInternal(ref string label, Vector2 minSize, bool edit) private static bool BeginFramedGroupInternal( ref string label, Vector2 minSize, bool edit )
{ {
var itemSpacing = ImGui.GetStyle().ItemSpacing; var itemSpacing = ImGui.GetStyle().ItemSpacing;
var frameHeight = ImGui.GetFrameHeight(); var frameHeight = ImGui.GetFrameHeight();
var halfFrameHeight = new Vector2(ImGui.GetFrameHeight() / 2, 0); var halfFrameHeight = new Vector2( ImGui.GetFrameHeight() / 2, 0 );
ImGui.BeginGroup(); // First group ImGui.BeginGroup(); // First group
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, ZeroVector); ImGui.PushStyleVar( ImGuiStyleVar.FramePadding, ZeroVector );
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ZeroVector); ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
ImGui.BeginGroup(); // Second group ImGui.BeginGroup(); // Second group
var effectiveSize = minSize; var effectiveSize = minSize;
if (effectiveSize.X < 0) if( effectiveSize.X < 0 )
{
effectiveSize.X = ImGui.GetContentRegionAvail().X; effectiveSize.X = ImGui.GetContentRegionAvail().X;
}
// Ensure width. // Ensure width.
ImGui.Dummy(new(effectiveSize.X, 0)); ImGui.Dummy( new Vector2( effectiveSize.X, 0 ) );
// Ensure left half boundary width/distance. // Ensure left half boundary width/distance.
ImGui.Dummy(halfFrameHeight); ImGui.Dummy( halfFrameHeight );
ImGui.SameLine(); ImGui.SameLine();
ImGui.BeginGroup(); // Third group. ImGui.BeginGroup(); // Third group.
// Ensure right half of boundary width/distance // Ensure right half of boundary width/distance
ImGui.Dummy(halfFrameHeight); ImGui.Dummy( halfFrameHeight );
// Label block // Label block
ImGui.SameLine(); ImGui.SameLine();
var ret = false; var ret = false;
if (edit) if( edit )
ret = ImGuiCustom.ResizingTextInput(ref label, 1024); {
ret = ResizingTextInput( ref label, 1024 );
}
else else
ImGui.TextUnformatted(label); {
ImGui.TextUnformatted( label );
}
var labelMin = ImGui.GetItemRectMin(); var labelMin = ImGui.GetItemRectMin();
var labelMax = ImGui.GetItemRectMax(); var labelMax = ImGui.GetItemRectMax();
ImGui.SameLine(); ImGui.SameLine();
// Ensure height and distance to label. // Ensure height and distance to label.
ImGui.Dummy(new Vector2(0, frameHeight + itemSpacing.Y)); ImGui.Dummy( new Vector2( 0, frameHeight + itemSpacing.Y ) );
ImGui.BeginGroup(); // Fourth Group. ImGui.BeginGroup(); // Fourth Group.
ImGui.PopStyleVar(2); ImGui.PopStyleVar( 2 );
ImGui.SetWindowSize(new Vector2(ImGui.GetWindowSize().X - frameHeight, ImGui.GetWindowSize().Y)); ImGui.SetWindowSize( new Vector2( ImGui.GetWindowSize().X - frameHeight, ImGui.GetWindowSize().Y ) );
var itemWidth = ImGui.CalcItemWidth(); var itemWidth = ImGui.CalcItemWidth();
ImGui.PushItemWidth(Math.Max(0f, itemWidth - frameHeight)); ImGui.PushItemWidth( Math.Max( 0f, itemWidth - frameHeight ) );
labelStack.Add((labelMin, labelMax)); labelStack.Add( ( labelMin, labelMax ) );
return ret; return ret;
} }
private static void DrawClippedRect(Vector2 clipMin, Vector2 clipMax, Vector2 drawMin, Vector2 drawMax, uint color, float thickness) private static void DrawClippedRect( Vector2 clipMin, Vector2 clipMax, Vector2 drawMin, Vector2 drawMax, uint color, float thickness )
{ {
ImGui.PushClipRect(clipMin, clipMax, true); ImGui.PushClipRect( clipMin, clipMax, true );
ImGui.GetWindowDrawList().AddRect(drawMin, drawMax, color, thickness); ImGui.GetWindowDrawList().AddRect( drawMin, drawMax, color, thickness );
ImGui.PopClipRect(); ImGui.PopClipRect();
} }
public static void EndFramedGroup() public static void EndFramedGroup()
{ {
uint borderColor = ImGui.ColorConvertFloat4ToU32(ImGui.GetStyle().Colors[(int)ImGuiCol.Border]); var borderColor = ImGui.ColorConvertFloat4ToU32( ImGui.GetStyle().Colors[ ( int )ImGuiCol.Border ] );
Vector2 itemSpacing = ImGui.GetStyle().ItemSpacing; var itemSpacing = ImGui.GetStyle().ItemSpacing;
float frameHeight = ImGui.GetFrameHeight(); var frameHeight = ImGui.GetFrameHeight();
Vector2 halfFrameHeight = new(ImGui.GetFrameHeight() / 2, 0); var halfFrameHeight = new Vector2( ImGui.GetFrameHeight() / 2, 0 );
ImGui.PopItemWidth(); ImGui.PopItemWidth();
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, ZeroVector); ImGui.PushStyleVar( ImGuiStyleVar.FramePadding, ZeroVector );
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ZeroVector); ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
ImGui.EndGroup(); // Close fourth group ImGui.EndGroup(); // Close fourth group
ImGui.EndGroup(); // Close third group ImGui.EndGroup(); // Close third group
ImGui.SameLine(); ImGui.SameLine();
// Ensure right distance. // Ensure right distance.
ImGui.Dummy(halfFrameHeight); ImGui.Dummy( halfFrameHeight );
// Ensure bottom distance // Ensure bottom distance
ImGui.Dummy(new Vector2(0, frameHeight/2 - itemSpacing.Y)); ImGui.Dummy( new Vector2( 0, frameHeight / 2 - itemSpacing.Y ) );
ImGui.EndGroup(); // Close second group ImGui.EndGroup(); // Close second group
var itemMin = ImGui.GetItemRectMin(); var itemMin = ImGui.GetItemRectMin();
var itemMax = ImGui.GetItemRectMax(); var itemMax = ImGui.GetItemRectMax();
var (currentLabelMin, currentLabelMax) = labelStack[labelStack.Count - 1]; var (currentLabelMin, currentLabelMax) = labelStack[ labelStack.Count - 1 ];
labelStack.RemoveAt(labelStack.Count - 1); labelStack.RemoveAt( labelStack.Count - 1 );
var halfFrame = new Vector2(frameHeight / 8, frameHeight / 2); var halfFrame = new Vector2( frameHeight / 8, frameHeight / 2 );
currentLabelMin.X -= itemSpacing.X; currentLabelMin.X -= itemSpacing.X;
currentLabelMax.X += itemSpacing.X; currentLabelMax.X += itemSpacing.X;
var frameMin = itemMin + halfFrame; var frameMin = itemMin + halfFrame;
var frameMax = itemMax - new Vector2(halfFrame.X, 0); var frameMax = itemMax - new Vector2( halfFrame.X, 0 );
// Left // Left
DrawClippedRect(new(-float.MaxValue , -float.MaxValue ), new(currentLabelMin.X, float.MaxValue ), frameMin, frameMax, borderColor, halfFrame.X); DrawClippedRect( new Vector2( -float.MaxValue, -float.MaxValue ), new Vector2( currentLabelMin.X, float.MaxValue ), frameMin,
frameMax, borderColor, halfFrame.X );
// Right // Right
DrawClippedRect(new(currentLabelMax.X, -float.MaxValue ), new(float.MaxValue , float.MaxValue ), frameMin, frameMax, borderColor, halfFrame.X); DrawClippedRect( new Vector2( currentLabelMax.X, -float.MaxValue ), new Vector2( float.MaxValue, float.MaxValue ), frameMin,
frameMax, borderColor, halfFrame.X );
// Top // Top
DrawClippedRect(new(currentLabelMin.X, -float.MaxValue ), new(currentLabelMax.X, currentLabelMin.Y), frameMin, frameMax, borderColor, halfFrame.X); DrawClippedRect( new Vector2( currentLabelMin.X, -float.MaxValue ), new Vector2( currentLabelMax.X, currentLabelMin.Y ), frameMin,
frameMax, borderColor, halfFrame.X );
// Bottom // Bottom
DrawClippedRect(new(currentLabelMin.X, currentLabelMax.Y), new(currentLabelMax.X, float.MaxValue ), frameMin, frameMax, borderColor, halfFrame.X); DrawClippedRect( new Vector2( currentLabelMin.X, currentLabelMax.Y ), new Vector2( currentLabelMax.X, float.MaxValue ), frameMin,
frameMax, borderColor, halfFrame.X );
ImGui.PopStyleVar(2); ImGui.PopStyleVar( 2 );
ImGui.SetWindowSize(new Vector2(ImGui.GetWindowSize().X + frameHeight, ImGui.GetWindowSize().Y)); ImGui.SetWindowSize( new Vector2( ImGui.GetWindowSize().X + frameHeight, ImGui.GetWindowSize().Y ) );
ImGui.Dummy(ZeroVector); ImGui.Dummy( ZeroVector );
ImGui.EndGroup(); // Close first group ImGui.EndGroup(); // Close first group
} }
private static readonly Vector2 ZeroVector = new(0, 0); private static readonly Vector2 ZeroVector = new( 0, 0 );
private static List<(Vector2, Vector2)> labelStack = new(); private static readonly List< (Vector2, Vector2) > labelStack = new();
} }
} }

View file

@ -4,39 +4,50 @@ namespace Penumbra.UI
{ {
public static partial class ImGuiCustom public static partial class ImGuiCustom
{ {
public static bool RenameableCombo(string label, ref int currentItem, ref string newName, string[] items, int numItems) public static bool RenameableCombo( string label, ref int currentItem, out string newName, string[] items, int numItems )
{ {
var ret = false; var ret = false;
newName = ""; newName = "";
var newOption = ""; var newOption = "";
if (ImGui.BeginCombo(label, (numItems > 0) ? items[currentItem] : newOption)) if( !ImGui.BeginCombo( label, numItems > 0 ? items[ currentItem ] : newOption ) )
{ {
for (var i = 0; i < numItems; ++i) return false;
}
for( var i = 0; i < numItems; ++i )
{ {
var isSelected = i == currentItem; var isSelected = i == currentItem;
ImGui.SetNextItemWidth(-1); ImGui.SetNextItemWidth( -1 );
if (ImGui.InputText($"##{label}_{i}", ref items[i], 64, ImGuiInputTextFlags.EnterReturnsTrue)) if( ImGui.InputText( $"##{label}_{i}", ref items[ i ], 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{ {
currentItem = i; currentItem = i;
newName = items[i]; newName = items[ i ];
ret = true; ret = true;
ImGui.CloseCurrentPopup(); ImGui.CloseCurrentPopup();
} }
if (isSelected)
if( isSelected )
{
ImGui.SetItemDefaultFocus(); ImGui.SetItemDefaultFocus();
} }
ImGui.SetNextItemWidth(-1); }
if (ImGui.InputText($"##{label}_new", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue))
ImGui.SetNextItemWidth( -1 );
if( ImGui.InputText( $"##{label}_new", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{ {
currentItem = numItems; currentItem = numItems;
newName = newOption; newName = newOption;
ret = true; ret = true;
ImGui.CloseCurrentPopup(); ImGui.CloseCurrentPopup();
} }
if (numItems == 0)
if( numItems == 0 )
{
ImGui.SetItemDefaultFocus(); ImGui.SetItemDefaultFocus();
ImGui.EndCombo();
} }
ImGui.EndCombo();
return ret; return ret;
} }
} }

View file

@ -5,36 +5,45 @@ namespace Penumbra.UI
{ {
public static partial class ImGuiCustom public static partial class ImGuiCustom
{ {
public static bool InputOrText(bool editable, string label, ref string text, uint maxLength) public static bool InputOrText( bool editable, string label, ref string text, uint maxLength )
{ {
if (editable) if( editable )
return ResizingTextInput(label, ref text, maxLength); {
return ResizingTextInput( label, ref text, maxLength );
}
ImGui.Text(text); ImGui.Text( text );
return false; return false;
} }
public static bool ResizingTextInput(string label, ref string input, uint maxLength) => ResizingTextInputIntern(label, ref input, maxLength).Item1; public static bool ResizingTextInput( string label, ref string input, uint maxLength ) =>
public static bool ResizingTextInput(ref string input, uint maxLength) ResizingTextInputIntern( label, ref input, maxLength ).Item1;
public static bool ResizingTextInput( ref string input, uint maxLength )
{ {
var (ret, id) = ResizingTextInputIntern($"##{input}", ref input, maxLength); var (ret, id) = ResizingTextInputIntern( $"##{input}", ref input, maxLength );
if (ret) if( ret )
_textInputWidths.Remove(id); {
TextInputWidths.Remove( id );
}
return ret; return ret;
} }
private static (bool, uint) ResizingTextInputIntern(string label, ref string input, uint maxLength) private static (bool, uint) ResizingTextInputIntern( string label, ref string input, uint maxLength )
{ {
var id = ImGui.GetID(label); var id = ImGui.GetID( label );
if (!_textInputWidths.TryGetValue(id, out var width)) if( !TextInputWidths.TryGetValue( id, out var width ) )
width = ImGui.CalcTextSize(input).X + 10; {
width = ImGui.CalcTextSize( input ).X + 10;
ImGui.SetNextItemWidth(width);
var ret = ImGui.InputText(label, ref input, maxLength, ImGuiInputTextFlags.EnterReturnsTrue);
_textInputWidths[id] = ImGui.CalcTextSize(input).X + 10;
return (ret, id);
} }
private static readonly Dictionary<uint, float> _textInputWidths = new(); ImGui.SetNextItemWidth( width );
var ret = ImGui.InputText( label, ref input, maxLength, ImGuiInputTextFlags.EnterReturnsTrue );
TextInputWidths[ id ] = ImGui.CalcTextSize( input ).X + 10;
return ( ret, id );
}
private static readonly Dictionary< uint, float > TextInputWidths = new();
} }
} }

View file

@ -4,23 +4,22 @@ namespace Penumbra.UI
{ {
public static partial class ImGuiCustom public static partial class ImGuiCustom
{ {
public static void VerticalDistance(float distance) public static void VerticalDistance( float distance )
{ {
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + distance); ImGui.SetCursorPosY( ImGui.GetCursorPosY() + distance );
} }
public static void RightJustifiedText(float pos, string text) public static void RightJustifiedText( float pos, string text )
{ {
ImGui.SetCursorPosX(pos - ImGui.CalcTextSize(text).X - 2 * ImGui.GetStyle().ItemSpacing.X); ImGui.SetCursorPosX( pos - ImGui.CalcTextSize( text ).X - 2 * ImGui.GetStyle().ItemSpacing.X );
ImGui.Text(text); ImGui.Text( text );
} }
public static void RightJustifiedLabel(float pos, string text) public static void RightJustifiedLabel( float pos, string text )
{ {
ImGui.SetCursorPosX(pos - ImGui.CalcTextSize(text).X - ImGui.GetStyle().ItemSpacing.X / 2); ImGui.SetCursorPosX( pos - ImGui.CalcTextSize( text ).X - ImGui.GetStyle().ItemSpacing.X / 2 );
ImGui.Text(text); ImGui.Text( text );
ImGui.SameLine(pos); ImGui.SameLine( pos );
} }
} }
} }

View file

@ -14,10 +14,11 @@ namespace Penumbra.UI
private const string MenuButtonsName = "Penumbra Menu Buttons"; private const string MenuButtonsName = "Penumbra Menu Buttons";
private const string MenuButtonLabel = "Manage Mods"; private const string MenuButtonLabel = "Manage Mods";
private static readonly Vector2 WindowSize = new(Width, Height); private static readonly Vector2 WindowSize = new( Width, Height );
private static readonly Vector2 WindowPosOffset = new(Padding + Width, Padding + Height); private static readonly Vector2 WindowPosOffset = new( Padding + Width, Padding + Height );
private readonly ImGuiWindowFlags ButtonFlags = ImGuiWindowFlags.AlwaysAutoResize private const ImGuiWindowFlags ButtonFlags =
ImGuiWindowFlags.AlwaysAutoResize
| ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoBackground
| ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDecoration
| ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoMove
@ -28,7 +29,7 @@ namespace Penumbra.UI
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
private readonly Dalamud.Game.ClientState.Condition _condition; private readonly Dalamud.Game.ClientState.Condition _condition;
public LaunchButton(SettingsInterface ui) public LaunchButton( SettingsInterface ui )
{ {
_base = ui; _base = ui;
_condition = ui._plugin.PluginInterface.ClientState.Condition; _condition = ui._plugin.PluginInterface.ClientState.Condition;
@ -36,21 +37,27 @@ namespace Penumbra.UI
public void Draw() public void Draw()
{ {
if( !_condition.Any() && !_base._menu.Visible ) if( _condition.Any() || _base._menu.Visible )
{ {
return;
}
var ss = ImGui.GetIO().DisplaySize; var ss = ImGui.GetIO().DisplaySize;
ImGui.SetNextWindowPos( ss - WindowPosOffset, ImGuiCond.Always ); ImGui.SetNextWindowPos( ss - WindowPosOffset, ImGuiCond.Always );
if( ImGui.Begin(MenuButtonsName, ButtonFlags) ) if( !ImGui.Begin( MenuButtonsName, ButtonFlags ) )
{ {
return;
}
if( ImGui.Button( MenuButtonLabel, WindowSize ) ) if( ImGui.Button( MenuButtonLabel, WindowSize ) )
{
_base.FlipVisibility(); _base.FlipVisibility();
}
ImGui.End(); ImGui.End();
} }
} }
} }
}
}
} }

View file

@ -10,27 +10,41 @@ namespace Penumbra.UI
private const string MenuItemToggle = "Toggle UI"; private const string MenuItemToggle = "Toggle UI";
private const string SlashCommand = "/penumbra"; private const string SlashCommand = "/penumbra";
private const string MenuItemRediscover = "Rediscover Mods"; private const string MenuItemRediscover = "Rediscover Mods";
private const string MenuItemHide = "Hide Menu Bar";
#if DEBUG #if DEBUG
private const bool _showDebugBar = true; private bool _showDebugBar = true;
#else #else
private const bool _showDebugBar = false; private const bool _showDebugBar = false;
#endif #endif
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
public MenuBar(SettingsInterface ui) => _base = ui; public MenuBar( SettingsInterface ui ) => _base = ui;
public void Draw() public void Draw()
{ {
if( _showDebugBar && ImGui.BeginMainMenuBar() ) if( !_showDebugBar || !ImGui.BeginMainMenuBar() )
{ {
return;
}
if( ImGui.BeginMenu( MenuLabel ) ) if( ImGui.BeginMenu( MenuLabel ) )
{ {
if( ImGui.MenuItem( MenuItemToggle, SlashCommand, _base._menu.Visible ) ) if( ImGui.MenuItem( MenuItemToggle, SlashCommand, _base._menu.Visible ) )
{
_base.FlipVisibility(); _base.FlipVisibility();
}
if( ImGui.MenuItem( MenuItemRediscover ) ) if( ImGui.MenuItem( MenuItemRediscover ) )
{
_base.ReloadMods(); _base.ReloadMods();
}
#if DEBUG
if( ImGui.MenuItem( MenuItemHide ) )
{
_showDebugBar = false;
}
#endif
ImGui.EndMenu(); ImGui.EndMenu();
} }
@ -39,5 +53,4 @@ namespace Penumbra.UI
} }
} }
} }
}
} }

View file

@ -7,8 +7,8 @@ namespace Penumbra.UI
{ {
private const float DefaultVerticalSpace = 20f; private const float DefaultVerticalSpace = 20f;
private static readonly Vector2 AutoFillSize = new(-1, -1); private static readonly Vector2 AutoFillSize = new( -1, -1 );
private static readonly Vector2 ZeroVector = new( 0, 0); private static readonly Vector2 ZeroVector = new( 0, 0 );
private readonly Plugin _plugin; private readonly Plugin _plugin;
@ -19,9 +19,9 @@ namespace Penumbra.UI
public SettingsInterface( Plugin plugin ) public SettingsInterface( Plugin plugin )
{ {
_plugin = plugin; _plugin = plugin;
_launchButton = new(this); _launchButton = new LaunchButton( this );
_menuBar = new(this); _menuBar = new MenuBar( this );
_menu = new(this); _menu = new SettingsMenu( this );
} }
public void FlipVisibility() => _menu.Visible = !_menu.Visible; public void FlipVisibility() => _menu.Visible = !_menu.Visible;
@ -35,12 +35,14 @@ namespace Penumbra.UI
private void ReloadMods() private void ReloadMods()
{ {
_menu._installedTab._selector.ClearSelection(); _menu.InstalledTab.Selector.ResetModNamesLower();
_menu.InstalledTab.Selector.ClearSelection();
// create the directory if it doesn't exist // create the directory if it doesn't exist
Directory.CreateDirectory( _plugin.Configuration.CurrentCollection ); Directory.CreateDirectory( _plugin.Configuration.CurrentCollection );
_plugin.ModManager.DiscoverMods( _plugin.Configuration.CurrentCollection ); _plugin.ModManager.DiscoverMods( _plugin.Configuration.CurrentCollection );
_menu._effectiveTab.RebuildFileList(_plugin.Configuration.ShowAdvanced); _menu.EffectiveTab.RebuildFileList( _plugin.Configuration.ShowAdvanced );
_menu.InstalledTab.Selector.ResetModNamesLower();
} }
} }
} }

View file

@ -13,20 +13,20 @@ namespace Penumbra.UI
private static readonly Vector2 MaxSettingsSize = new( 69420, 42069 ); private static readonly Vector2 MaxSettingsSize = new( 69420, 42069 );
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
public readonly TabSettings _settingsTab; private readonly TabSettings _settingsTab;
public readonly TabImport _importTab; private readonly TabImport _importTab;
public readonly TabBrowser _browserTab; private readonly TabBrowser _browserTab;
public readonly TabInstalled _installedTab; public readonly TabInstalled InstalledTab;
public readonly TabEffective _effectiveTab; public readonly TabEffective EffectiveTab;
public SettingsMenu(SettingsInterface ui) public SettingsMenu( SettingsInterface ui )
{ {
_base = ui; _base = ui;
_settingsTab = new(_base); _settingsTab = new TabSettings( _base );
_importTab = new(_base); _importTab = new TabImport( _base );
_browserTab = new(); _browserTab = new TabBrowser();
_installedTab = new(_base); InstalledTab = new TabInstalled( _base );
_effectiveTab = new(_base); EffectiveTab = new TabEffective( _base );
} }
#if DEBUG #if DEBUG
@ -39,7 +39,9 @@ namespace Penumbra.UI
public void Draw() public void Draw()
{ {
if( !Visible ) if( !Visible )
{
return; return;
}
ImGui.SetNextWindowSizeConstraints( MinSettingsSize, MaxSettingsSize ); ImGui.SetNextWindowSizeConstraints( MinSettingsSize, MaxSettingsSize );
#if DEBUG #if DEBUG
@ -48,7 +50,9 @@ namespace Penumbra.UI
var ret = ImGui.Begin( _base._plugin.Name, ref Visible ); var ret = ImGui.Begin( _base._plugin.Name, ref Visible );
#endif #endif
if( !ret ) if( !ret )
{
return; return;
}
ImGui.BeginTabBar( PenumbraSettingsLabel ); ImGui.BeginTabBar( PenumbraSettingsLabel );
@ -58,10 +62,12 @@ namespace Penumbra.UI
if( !_importTab.IsImporting() ) if( !_importTab.IsImporting() )
{ {
_browserTab.Draw(); _browserTab.Draw();
_installedTab.Draw(); InstalledTab.Draw();
if( _base._plugin.Configuration.ShowAdvanced ) if( _base._plugin.Configuration.ShowAdvanced )
_effectiveTab.Draw(); {
EffectiveTab.Draw();
}
} }
ImGui.EndTabBar(); ImGui.EndTabBar();

View file

@ -12,7 +12,9 @@ namespace Penumbra.UI
{ {
var ret = ImGui.BeginTabItem( "Available Mods" ); var ret = ImGui.BeginTabItem( "Available Mods" );
if( !ret ) if( !ret )
{
return; return;
}
ImGui.Text( "woah" ); ImGui.Text( "woah" );
ImGui.EndTabItem(); ImGui.EndTabItem();

View file

@ -11,22 +11,22 @@ namespace Penumbra.UI
private const string LabelTab = "Effective File List"; private const string LabelTab = "Effective File List";
private const float TextSizePadding = 5f; private const float TextSizePadding = 5f;
private ModManager _mods; private readonly ModManager _mods;
private (string, string)[] _fileList = null; private (string, string)[] _fileList;
private float _maxGamePath = 0f; private float _maxGamePath;
public TabEffective(SettingsInterface ui) public TabEffective( SettingsInterface ui )
{ {
_mods = ui._plugin.ModManager; _mods = ui._plugin.ModManager;
RebuildFileList(ui._plugin.Configuration.ShowAdvanced); RebuildFileList( ui._plugin.Configuration.ShowAdvanced );
} }
public void RebuildFileList(bool advanced) public void RebuildFileList( bool advanced )
{ {
if (advanced) if( advanced )
{ {
_fileList = _mods.ResolvedFiles.Select( P => (P.Value.FullName, P.Key) ).ToArray(); _fileList = _mods.ResolvedFiles.Select( P => ( P.Value.FullName, P.Key ) ).ToArray();
_maxGamePath = ((_fileList.Length > 0) ? _fileList.Max( P => ImGui.CalcTextSize(P.Item2).X ) : 0f) + TextSizePadding; _maxGamePath = ( _fileList.Length > 0 ? _fileList.Max( P => ImGui.CalcTextSize( P.Item2 ).X ) : 0f ) + TextSizePadding;
} }
else else
{ {
@ -35,26 +35,30 @@ namespace Penumbra.UI
} }
} }
private void DrawFileLine((string, string) file) private void DrawFileLine( (string, string) file )
{ {
ImGui.Selectable(file.Item2); ImGui.Selectable( file.Item2 );
ImGui.SameLine(); ImGui.SameLine();
ImGui.SetCursorPosX(_maxGamePath); ImGui.SetCursorPosX( _maxGamePath );
ImGui.TextUnformatted(" <-- "); ImGui.TextUnformatted( " <-- " );
ImGui.SameLine(); ImGui.SameLine();
ImGui.Selectable(file.Item1); ImGui.Selectable( file.Item1 );
} }
public void Draw() public void Draw()
{ {
var ret = ImGui.BeginTabItem( LabelTab ); var ret = ImGui.BeginTabItem( LabelTab );
if( !ret ) if( !ret )
{
return; return;
}
if( ImGui.ListBoxHeader( "##effective_files", AutoFillSize ) ) if( ImGui.ListBoxHeader( "##effective_files", AutoFillSize ) )
{ {
foreach( var file in _fileList ) foreach( var file in _fileList )
DrawFileLine(file); {
DrawFileLine( file );
}
ImGui.ListBoxFooter(); ImGui.ListBoxFooter();
} }

View file

@ -1,11 +1,11 @@
using ImGuiNET; using System;
using System.IO;
using System.Numerics;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using System.IO;
using System;
using Penumbra.Importer;
using Dalamud.Plugin; using Dalamud.Plugin;
using System.Numerics; using ImGuiNET;
using Penumbra.Importer;
namespace Penumbra.UI namespace Penumbra.UI
{ {
@ -15,9 +15,9 @@ namespace Penumbra.UI
{ {
private const string LabelTab = "Import Mods"; private const string LabelTab = "Import Mods";
private const string LabelImportButton = "Import TexTools Modpacks"; private const string LabelImportButton = "Import TexTools Modpacks";
private const string FileTypeFilter = "TexTools TTMP Modpack (*.ttmp2)|*.ttmp*|All files (*.*)|*.*";
private const string LabelFileDialog = "Pick one or more modpacks."; private const string LabelFileDialog = "Pick one or more modpacks.";
private const string LabelFileImportRunning = "Import in progress..."; private const string LabelFileImportRunning = "Import in progress...";
private const string FileTypeFilter = "TexTools TTMP Modpack (*.ttmp2)|*.ttmp*|All files (*.*)|*.*";
private const string TooltipModpack1 = "Writing modpack to disk before extracting..."; private const string TooltipModpack1 = "Writing modpack to disk before extracting...";
private const string FailedImport = "One or more of your modpacks failed to import.\nPlease submit a bug report."; private const string FailedImport = "One or more of your modpacks failed to import.\nPlease submit a bug report.";
@ -30,7 +30,7 @@ namespace Penumbra.UI
private TexToolsImport _texToolsImport = null!; private TexToolsImport _texToolsImport = null!;
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
public TabImport(SettingsInterface ui) => _base = ui; public TabImport( SettingsInterface ui ) => _base = ui;
public bool IsImporting() => _isImportRunning; public bool IsImporting() => _isImportRunning;
@ -55,7 +55,7 @@ namespace Penumbra.UI
foreach( var fileName in picker.FileNames ) foreach( var fileName in picker.FileNames )
{ {
PluginLog.Log( $"-> {fileName} START"); PluginLog.Log( $"-> {fileName} START" );
try try
{ {
@ -74,6 +74,7 @@ namespace Penumbra.UI
_texToolsImport = null; _texToolsImport = null;
_base.ReloadMods(); _base.ReloadMods();
} }
_isImportRunning = false; _isImportRunning = false;
} ); } );
} }
@ -90,8 +91,11 @@ namespace Penumbra.UI
{ {
ImGui.Button( LabelFileImportRunning ); ImGui.Button( LabelFileImportRunning );
if( _texToolsImport != null ) if( _texToolsImport == null )
{ {
return;
}
switch( _texToolsImport.State ) switch( _texToolsImport.State )
{ {
case ImporterState.None: case ImporterState.None:
@ -113,9 +117,8 @@ namespace Penumbra.UI
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
} }
}
private void DrawFailedImportMessage() private static void DrawFailedImportMessage()
{ {
ImGui.PushStyleColor( ImGuiCol.Text, ColorRed ); ImGui.PushStyleColor( ImGuiCol.Text, ColorRed );
ImGui.Text( FailedImport ); ImGui.Text( FailedImport );
@ -126,15 +129,23 @@ namespace Penumbra.UI
{ {
var ret = ImGui.BeginTabItem( LabelTab ); var ret = ImGui.BeginTabItem( LabelTab );
if( !ret ) if( !ret )
{
return; return;
}
if( !_isImportRunning ) if( !_isImportRunning )
{
DrawImportButton(); DrawImportButton();
}
else else
{
DrawImportProgress(); DrawImportProgress();
}
if (_hasError) if( _hasError )
{
DrawFailedImportMessage(); DrawFailedImportMessage();
}
ImGui.EndTabItem(); ImGui.EndTabItem();
} }

View file

@ -4,25 +4,25 @@ namespace Penumbra.UI
{ {
public partial class SettingsInterface public partial class SettingsInterface
{ {
private partial class TabInstalled private class TabInstalled
{ {
private const string LabelTab = "Installed Mods"; private const string LabelTab = "Installed Mods";
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
public readonly Selector _selector; public readonly Selector Selector;
public readonly ModPanel _modPanel; public readonly ModPanel ModPanel;
public TabInstalled(SettingsInterface ui) public TabInstalled( SettingsInterface ui )
{ {
_base = ui; _base = ui;
_selector = new(_base); Selector = new Selector( _base );
_modPanel = new(_base, _selector); ModPanel = new ModPanel( _base, Selector );
} }
private void DrawNoModsAvailable() private static void DrawNoModsAvailable()
{ {
ImGui.Text( "You don't have any mods :(" ); ImGui.Text( "You don't have any mods :(" );
ImGuiCustom.VerticalDistance(20f); ImGuiCustom.VerticalDistance( 20f );
ImGui.Text( "You'll need to install them first by creating a folder close to the root of your drive (preferably an SSD)." ); ImGui.Text( "You'll need to install them first by creating a folder close to the root of your drive (preferably an SSD)." );
ImGui.Text( "For example: D:/ffxiv/mods/" ); ImGui.Text( "For example: D:/ffxiv/mods/" );
ImGui.Text( "And pasting that path into the settings tab and clicking the 'Rediscover Mods' button." ); ImGui.Text( "And pasting that path into the settings tab and clicking the 'Rediscover Mods' button." );
@ -33,20 +33,22 @@ namespace Penumbra.UI
{ {
var ret = ImGui.BeginTabItem( LabelTab ); var ret = ImGui.BeginTabItem( LabelTab );
if( !ret ) if( !ret )
return;
if (_base._plugin.ModManager.Mods != null)
{ {
_selector.Draw(); return;
}
if( _base._plugin.ModManager.Mods != null )
{
Selector.Draw();
ImGui.SameLine(); ImGui.SameLine();
_modPanel.Draw(); ModPanel.Draw();
} }
else else
{
DrawNoModsAvailable(); DrawNoModsAvailable();
}
ImGui.EndTabItem(); ImGui.EndTabItem();
return;
} }
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,355 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using ImGuiNET;
using Penumbra.Models;
namespace Penumbra.UI
{
public partial class SettingsInterface
{
private partial class PluginDetails
{
private const string LabelDescEdit = "##descedit";
private const string LabelNewSingleGroup = "New Single Group";
private const string LabelNewSingleGroupEdit = "##newSingleGroup";
private const string LabelNewMultiGroup = "New Multi Group";
private const string LabelGamePathsEdit = "Game Paths";
private const string LabelGamePathsEditBox = "##gamePathsEdit";
private const string ButtonAddToGroup = "Add to Group";
private const string ButtonRemoveFromGroup = "Remove from Group";
private const string TooltipAboutEdit = "Use Ctrl+Enter for newlines.";
private const string TextNoOptionAvailable = "[Not Available]";
private const string TextDefaultGamePath = "default";
private const char GamePathsSeparator = ';';
private static readonly string TooltipFilesTabEdit =
$"{TooltipFilesTab}\n" +
$"Red Files are replaced in another group or a different option in this group, but not contained in the current option.";
private static readonly string TooltipGamePathsEdit =
$"Enter all game paths to add or remove, separated by '{GamePathsSeparator}'.\n" +
$"Use '{TextDefaultGamePath}' to add the original file path.";
private const float MultiEditBoxWidth = 300f;
private bool DrawEditGroupSelector()
{
ImGui.SetNextItemWidth( OptionSelectionWidth );
if( Meta.Groups.Count == 0 )
{
ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, TextNoOptionAvailable, 1 );
return false;
}
if( ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex
, Meta.Groups.Values.Select( G => G.GroupName ).ToArray()
, Meta.Groups.Count ) )
{
SelectGroup();
SelectOption( 0 );
}
return true;
}
private bool DrawEditOptionSelector()
{
ImGui.SameLine();
ImGui.SetNextItemWidth( OptionSelectionWidth );
if( ( _selectedGroup?.Options.Count ?? 0 ) == 0 )
{
ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, TextNoOptionAvailable, 1 );
return false;
}
var group = ( InstallerInfo )_selectedGroup;
if( ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, group.Options.Select( O => O.OptionName ).ToArray(),
group.Options.Count ) )
{
SelectOption();
}
return true;
}
private void DrawFileListTabEdit()
{
if( ImGui.BeginTabItem( LabelFileListTab ) )
{
UpdateFilenameList();
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( _editMode ? TooltipFilesTabEdit : TooltipFilesTab );
}
ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelFileListHeader, AutoFillSize - new Vector2( 0, 1.5f * ImGui.GetTextLineHeight() ) ) )
{
for( var i = 0; i < Mod.Mod.ModFiles.Count; ++i )
{
DrawFileAndGamePaths( i );
}
}
ImGui.ListBoxFooter();
DrawGroupRow();
ImGui.EndTabItem();
}
else
{
_fullFilenameList = null;
}
}
private bool DrawMultiSelectorEditBegin( InstallerInfo group )
{
var groupName = group.GroupName;
if( ImGuiCustom.BeginFramedGroupEdit( ref groupName )
&& groupName != group.GroupName && !Meta.Groups.ContainsKey( groupName ) )
{
var oldConf = Mod.Conf[ group.GroupName ];
Meta.Groups.Remove( group.GroupName );
Mod.Conf.Remove( group.GroupName );
if( groupName.Length > 0 )
{
Meta.Groups[ groupName ] = new InstallerInfo()
{ GroupName = groupName, SelectionType = SelectType.Multi, Options = group.Options };
Mod.Conf[ groupName ] = oldConf;
}
return true;
}
return false;
}
private void DrawMultiSelectorEditAdd( InstallerInfo group, float nameBoxStart )
{
var newOption = "";
ImGui.SetCursorPosX( nameBoxStart );
ImGui.SetNextItemWidth( MultiEditBoxWidth );
if( ImGui.InputText( $"##new_{group.GroupName}_l", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue )
&& newOption.Length != 0 )
{
group.Options.Add( new Option()
{ OptionName = newOption, OptionDesc = "", OptionFiles = new Dictionary< string, HashSet< string > >() } );
_selector.SaveCurrentMod();
}
}
private void DrawMultiSelectorEdit( InstallerInfo group )
{
var nameBoxStart = CheckMarkSize;
var flag = Mod.Conf[ group.GroupName ];
var modChanged = DrawMultiSelectorEditBegin( group );
for( var i = 0; i < group.Options.Count; ++i )
{
var opt = group.Options[ i ];
var label = $"##{opt.OptionName}_{group.GroupName}";
DrawMultiSelectorCheckBox( group, i, flag, label );
ImGui.SameLine();
var newName = opt.OptionName;
if( nameBoxStart == CheckMarkSize )
{
nameBoxStart = ImGui.GetCursorPosX();
}
ImGui.SetNextItemWidth( MultiEditBoxWidth );
if( ImGui.InputText( $"{label}_l", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
if( newName.Length == 0 )
{
group.Options.RemoveAt( i );
var bitmaskFront = ( 1 << i ) - 1;
Mod.Conf[ group.GroupName ] = ( flag & bitmaskFront ) | ( ( flag & ~bitmaskFront ) >> 1 );
modChanged = true;
}
else if( newName != opt.OptionName )
{
group.Options[ i ] = new Option()
{ OptionName = newName, OptionDesc = opt.OptionDesc, OptionFiles = opt.OptionFiles };
_selector.SaveCurrentMod();
}
}
}
DrawMultiSelectorEditAdd( group, nameBoxStart );
if( modChanged )
{
_selector.SaveCurrentMod();
Save();
}
ImGuiCustom.EndFramedGroup();
}
private bool DrawSingleSelectorEditGroup( InstallerInfo group )
{
var groupName = group.GroupName;
if( ImGui.InputText( $"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue )
&& !Meta.Groups.ContainsKey( groupName ) )
{
var oldConf = Mod.Conf[ group.GroupName ];
if( groupName != group.GroupName )
{
Meta.Groups.Remove( group.GroupName );
Mod.Conf.Remove( group.GroupName );
}
if( groupName.Length > 0 )
{
Meta.Groups.Add( groupName,
new InstallerInfo() { GroupName = groupName, Options = group.Options, SelectionType = SelectType.Single } );
Mod.Conf[ groupName ] = oldConf;
}
return true;
}
return false;
}
private float DrawSingleSelectorEdit( InstallerInfo group )
{
var code = Mod.Conf[ group.GroupName ];
var selectionChanged = false;
var modChanged = false;
if( ImGuiCustom.RenameableCombo( $"##{group.GroupName}", ref code, out var newName,
group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) )
{
if( code == group.Options.Count )
{
if( newName.Length > 0 )
{
selectionChanged = true;
modChanged = true;
Mod.Conf[ group.GroupName ] = code;
group.Options.Add( new Option()
{ OptionName = newName, OptionDesc = "", OptionFiles = new Dictionary< string, HashSet< string > >() } );
}
}
else
{
if( newName.Length == 0 )
{
modChanged = true;
group.Options.RemoveAt( code );
if( code >= group.Options.Count )
{
code = 0;
}
}
else if( newName != group.Options[ code ].OptionName )
{
modChanged = true;
group.Options[ code ] = new Option()
{
OptionName = newName, OptionDesc = group.Options[ code ].OptionDesc,
OptionFiles = group.Options[ code ].OptionFiles
};
}
if( Mod.Conf[ group.GroupName ] != code )
{
selectionChanged = true;
Mod.Conf[ group.GroupName ] = code;
}
}
}
ImGui.SameLine();
var labelEditPos = ImGui.GetCursorPosX();
modChanged |= DrawSingleSelectorEditGroup( group );
if( modChanged )
{
_selector.SaveCurrentMod();
}
if( selectionChanged )
{
Save();
}
return labelEditPos;
}
private void AddNewGroup( string newGroup, SelectType selectType )
{
if( Meta.Groups.ContainsKey( newGroup ) || newGroup.Length <= 0 )
{
return;
}
Meta.Groups[ newGroup ] = new InstallerInfo()
{
GroupName = newGroup,
SelectionType = selectType,
Options = new List< Option >()
};
Mod.Conf[ newGroup ] = 0;
_selector.SaveCurrentMod();
Save();
}
private void DrawAddSingleGroupField( float labelEditPos )
{
var newGroup = "";
if( labelEditPos == CheckMarkSize )
{
ImGui.SetCursorPosX( CheckMarkSize );
ImGui.SetNextItemWidth( MultiEditBoxWidth );
if( ImGui.InputText( LabelNewSingleGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
AddNewGroup( newGroup, SelectType.Single );
}
}
else
{
ImGuiCustom.RightJustifiedLabel( labelEditPos, LabelNewSingleGroup );
if( ImGui.InputText( LabelNewSingleGroupEdit, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
AddNewGroup( newGroup, SelectType.Single );
}
}
}
private void DrawAddMultiGroupField()
{
var newGroup = "";
ImGui.SetCursorPosX( CheckMarkSize );
ImGui.SetNextItemWidth( MultiEditBoxWidth );
if( ImGui.InputText( LabelNewMultiGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
AddNewGroup( newGroup, SelectType.Multi );
}
}
private void DrawGroupSelectorsEdit()
{
var labelEditPos = CheckMarkSize;
foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Single ) )
{
labelEditPos = DrawSingleSelectorEdit( g );
}
DrawAddSingleGroupField( labelEditPos );
foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Multi ) )
{
DrawMultiSelectorEdit( g );
}
DrawAddMultiGroupField();
}
}
}
}

View file

@ -1,8 +1,8 @@
using ImGuiNET;
using Dalamud.Plugin;
using System; using System;
using System.Numerics;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics;
using Dalamud.Plugin;
using ImGuiNET;
using Penumbra.Models; using Penumbra.Models;
namespace Penumbra.UI namespace Penumbra.UI
@ -16,46 +16,48 @@ namespace Penumbra.UI
private const string LabelEditVersion = "##editVersion"; private const string LabelEditVersion = "##editVersion";
private const string LabelEditAuthor = "##editAuthor"; private const string LabelEditAuthor = "##editAuthor";
private const string LabelEditWebsite = "##editWebsite"; private const string LabelEditWebsite = "##editWebsite";
private const string ButtonOpenWebsite = "Open Website";
private const string LabelModEnabled = "Enabled"; private const string LabelModEnabled = "Enabled";
private const string LabelEditingEnabled = "Enable Editing"; private const string LabelEditingEnabled = "Enable Editing";
private const string ButtonOpenWebsite = "Open Website";
private const string ButtonOpenModFolder = "Open Mod Folder"; private const string ButtonOpenModFolder = "Open Mod Folder";
private const string TooltipOpenModFolder = "Open the directory containing this mod in your default file explorer.";
private const string ButtonEditJson = "Edit JSON"; private const string ButtonEditJson = "Edit JSON";
private const string TooltipEditJson = "Open the JSON configuration file in your default application for .json.";
private const string ButtonReloadJson = "Reload JSON"; private const string ButtonReloadJson = "Reload JSON";
private const string TooltipReloadJson = "Reload the configuration of all mods.";
private const string ButtonDeduplicate = "Deduplicate"; private const string ButtonDeduplicate = "Deduplicate";
private const string TooltipDeduplicate = "Try to find identical files and remove duplicate occurences to reduce the mods disk size. Introduces an invisible single-option Group \"Duplicates\"."; private const string TooltipOpenModFolder = "Open the directory containing this mod in your default file explorer.";
private const string TooltipEditJson = "Open the JSON configuration file in your default application for .json.";
private const string TooltipReloadJson = "Reload the configuration of all mods.";
private const string TooltipDeduplicate =
"Try to find identical files and remove duplicate occurences to reduce the mods disk size.\n" +
"Introduces an invisible single-option Group \"Duplicates\".";
private const float HeaderLineDistance = 10f; private const float HeaderLineDistance = 10f;
private static readonly Vector4 GreyColor = new( 1f, 1f, 1f, 0.66f ); private static readonly Vector4 GreyColor = new( 1f, 1f, 1f, 0.66f );
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
private readonly Selector _selector; private readonly Selector _selector;
public readonly PluginDetails _details; public readonly PluginDetails Details;
private bool _editMode = false; private bool _editMode;
private string _currentWebsite; private string _currentWebsite;
private bool _validWebsite; private bool _validWebsite;
public ModPanel(SettingsInterface ui, Selector s) public ModPanel( SettingsInterface ui, Selector s )
{ {
_base = ui; _base = ui;
_selector = s; _selector = s;
_details = new(_base, _selector); Details = new PluginDetails( _base, _selector );
_currentWebsite = Meta?.Website; _currentWebsite = Meta?.Website;
} }
private ModInfo Mod { get{ return _selector.Mod(); } } private ModInfo Mod => _selector.Mod();
private ModMeta Meta { get{ return Mod?.Mod.Meta; } } private ModMeta Meta => Mod?.Mod.Meta;
#region Header Line Functions
private void DrawName() private void DrawName()
{ {
var name = Meta.Name; var name = Meta.Name;
if (ImGuiCustom.InputOrText(_editMode, LabelEditName, ref name, 64) if( ImGuiCustom.InputOrText( _editMode, LabelEditName, ref name, 64 )
&& name.Length > 0 && name != Meta.Name) && name.Length > 0 && name != Meta.Name )
{ {
Meta.Name = name; Meta.Name = name;
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
@ -64,27 +66,27 @@ namespace Penumbra.UI
private void DrawVersion() private void DrawVersion()
{ {
if (_editMode) if( _editMode )
{ {
ImGui.BeginGroup(); ImGui.BeginGroup();
ImGui.Text("(Version "); ImGui.Text( "(Version " );
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ZeroVector); ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
ImGui.SameLine(); ImGui.SameLine();
var version = Meta.Version ?? ""; var version = Meta.Version ?? "";
if (ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16) if( ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16 )
&& version != Meta.Version) && version != Meta.Version )
{ {
Meta.Version = version.Length > 0 ? version : null; Meta.Version = version.Length > 0 ? version : null;
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
} }
ImGui.SameLine(); ImGui.SameLine();
ImGui.Text(")"); ImGui.Text( ")" );
ImGui.PopStyleVar(); ImGui.PopStyleVar();
ImGui.EndGroup(); ImGui.EndGroup();
} }
else if ((Meta.Version?.Length ?? 0) > 0) else if( ( Meta.Version?.Length ?? 0 ) > 0 )
{ {
ImGui.Text( $"(Version {Meta.Version})" ); ImGui.Text( $"(Version {Meta.Version})" );
} }
@ -97,59 +99,63 @@ namespace Penumbra.UI
ImGui.SameLine(); ImGui.SameLine();
var author = Meta.Author ?? ""; var author = Meta.Author ?? "";
if (ImGuiCustom.InputOrText(_editMode, LabelEditAuthor, ref author, 64) if( ImGuiCustom.InputOrText( _editMode, LabelEditAuthor, ref author, 64 )
&& author != Meta.Author) && author != Meta.Author )
{ {
Meta.Author = author.Length > 0 ? author : null; Meta.Author = author.Length > 0 ? author : null;
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
} }
ImGui.EndGroup(); ImGui.EndGroup();
} }
private void DrawWebsite() private void DrawWebsite()
{ {
ImGui.BeginGroup(); ImGui.BeginGroup();
if (_editMode) if( _editMode )
{ {
ImGui.TextColored( GreyColor, "from" ); ImGui.TextColored( GreyColor, "from" );
ImGui.SameLine(); ImGui.SameLine();
var website = Meta.Website ?? ""; var website = Meta.Website ?? "";
if (ImGuiCustom.ResizingTextInput(LabelEditWebsite, ref website, 512) if( ImGuiCustom.ResizingTextInput( LabelEditWebsite, ref website, 512 )
&& website != Meta.Website) && website != Meta.Website )
{ {
Meta.Website = website.Length > 0 ? website : null; Meta.Website = website.Length > 0 ? website : null;
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
} }
} }
else if (( Meta.Website?.Length ?? 0 ) > 0) else if( ( Meta.Website?.Length ?? 0 ) > 0 )
{ {
if (_currentWebsite != Meta.Website) if( _currentWebsite != Meta.Website )
{ {
_currentWebsite = Meta.Website; _currentWebsite = Meta.Website;
_validWebsite = Uri.TryCreate( Meta.Website, UriKind.Absolute, out var uriResult ) _validWebsite = Uri.TryCreate( Meta.Website, UriKind.Absolute, out var uriResult )
&& ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp ); && ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp );
} }
if( _validWebsite ) if( _validWebsite )
{ {
if( ImGui.SmallButton( ButtonOpenWebsite ) ) if( ImGui.SmallButton( ButtonOpenWebsite ) )
{ {
try try
{ {
var process = new ProcessStartInfo(Meta.Website) var process = new ProcessStartInfo( Meta.Website )
{ {
UseShellExecute = true UseShellExecute = true
}; };
Process.Start(process); Process.Start( process );
} }
catch(System.ComponentModel.Win32Exception) catch( System.ComponentModel.Win32Exception )
{ {
// Do nothing. // Do nothing.
} }
} }
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( Meta.Website ); ImGui.SetTooltip( Meta.Website );
} }
}
else else
{ {
ImGui.TextColored( GreyColor, "from" ); ImGui.TextColored( GreyColor, "from" );
@ -157,6 +163,7 @@ namespace Penumbra.UI
ImGui.Text( Meta.Website ); ImGui.Text( Meta.Website );
} }
} }
ImGui.EndGroup(); ImGui.EndGroup();
} }
@ -170,9 +177,7 @@ namespace Penumbra.UI
ImGui.SameLine(); ImGui.SameLine();
DrawWebsite(); DrawWebsite();
} }
#endregion
#region Enabled Checkmarks
private void DrawEnabledMark() private void DrawEnabledMark()
{ {
var enabled = Mod.Enabled; var enabled = Mod.Enabled;
@ -181,26 +186,27 @@ namespace Penumbra.UI
Mod.Enabled = enabled; Mod.Enabled = enabled;
_base._plugin.ModManager.Mods.Save(); _base._plugin.ModManager.Mods.Save();
_base._plugin.ModManager.CalculateEffectiveFileList(); _base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced); _base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
} }
} }
private void DrawEditableMark() private void DrawEditableMark()
{ {
ImGui.Checkbox( LabelEditingEnabled, ref _editMode); ImGui.Checkbox( LabelEditingEnabled, ref _editMode );
} }
#endregion
#region Edit Line Functions
private void DrawOpenModFolderButton() private void DrawOpenModFolderButton()
{ {
if( ImGui.Button( ButtonOpenModFolder ) ) if( ImGui.Button( ButtonOpenModFolder ) )
{ {
Process.Start( Mod.Mod.ModBasePath.FullName ); Process.Start( Mod.Mod.ModBasePath.FullName );
} }
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipOpenModFolder ); ImGui.SetTooltip( TooltipOpenModFolder );
} }
}
private void DrawEditJsonButton() private void DrawEditJsonButton()
{ {
@ -208,9 +214,12 @@ namespace Penumbra.UI
{ {
Process.Start( _selector.SaveCurrentMod() ); Process.Start( _selector.SaveCurrentMod() );
} }
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipEditJson ); ImGui.SetTooltip( TooltipEditJson );
} }
}
private void DrawReloadJsonButton() private void DrawReloadJsonButton()
{ {
@ -218,23 +227,29 @@ namespace Penumbra.UI
{ {
_selector.ReloadCurrentMod(); _selector.ReloadCurrentMod();
} }
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipReloadJson ); ImGui.SetTooltip( TooltipReloadJson );
} }
}
private void DrawDeduplicateButton() private void DrawDeduplicateButton()
{ {
if( ImGui.Button( ButtonDeduplicate ) ) if( ImGui.Button( ButtonDeduplicate ) )
{ {
new Deduplicator(Mod.Mod.ModBasePath, Meta).Run(); new Deduplicator( Mod.Mod.ModBasePath, Meta ).Run();
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
Mod.Mod.RefreshModFiles(); Mod.Mod.RefreshModFiles();
_base._plugin.ModManager.CalculateEffectiveFileList(); _base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced); _base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
} }
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipDeduplicate ); ImGui.SetTooltip( TooltipDeduplicate );
} }
}
private void DrawEditLine() private void DrawEditLine()
{ {
@ -246,35 +261,41 @@ namespace Penumbra.UI
ImGui.SameLine(); ImGui.SameLine();
DrawDeduplicateButton(); DrawDeduplicateButton();
} }
#endregion
public void Draw() public void Draw()
{ {
if( Mod != null ) if( Mod == null )
{ {
return;
}
try try
{ {
var ret = ImGui.BeginChild( LabelModPanel, AutoFillSize, true ); var ret = ImGui.BeginChild( LabelModPanel, AutoFillSize, true );
if (!ret) if( !ret )
{
return; return;
}
DrawHeaderLine(); DrawHeaderLine();
// Next line with fixed distance. // Next line with fixed distance.
ImGuiCustom.VerticalDistance(HeaderLineDistance); ImGuiCustom.VerticalDistance( HeaderLineDistance );
DrawEnabledMark(); DrawEnabledMark();
if (_base._plugin.Configuration.ShowAdvanced) if( _base._plugin.Configuration.ShowAdvanced )
{ {
ImGui.SameLine(); ImGui.SameLine();
DrawEditableMark(); DrawEditableMark();
} }
// Next line, if editable. // Next line, if editable.
if (_editMode) if( _editMode )
{
DrawEditLine(); DrawEditLine();
}
_details.Draw(_editMode); Details.Draw( _editMode );
ImGui.EndChild(); ImGui.EndChild();
} }
@ -285,5 +306,4 @@ namespace Penumbra.UI
} }
} }
} }
}
} }

View file

@ -1,11 +1,11 @@
using System.Numerics;
using System.Linq;
using System.IO; using System.IO;
using Newtonsoft.Json; using System.Linq;
using ImGuiNET; using System.Numerics;
using Penumbra.Mods;
using Penumbra.Models;
using Dalamud.Interface; using Dalamud.Interface;
using ImGuiNET;
using Newtonsoft.Json;
using Penumbra.Models;
using Penumbra.Mods;
namespace Penumbra.UI namespace Penumbra.UI
{ {
@ -14,6 +14,8 @@ namespace Penumbra.UI
private class Selector private class Selector
{ {
private const string LabelSelectorList = "##availableModList"; private const string LabelSelectorList = "##availableModList";
private const string LabelModFilter = "##ModFilter";
private const string TooltipModFilter = "Filter mods for those containing the given substring.";
private const string TooltipMoveDown = "Move the selected mod down in priority"; private const string TooltipMoveDown = "Move the selected mod down in priority";
private const string TooltipMoveUp = "Move the selected mod up in priority"; private const string TooltipMoveUp = "Move the selected mod up in priority";
private const string TooltipDelete = "Delete the selected mod"; private const string TooltipDelete = "Delete the selected mod";
@ -25,31 +27,41 @@ namespace Penumbra.UI
private const uint DisabledModColor = 0xFF666666; private const uint DisabledModColor = 0xFF666666;
private const uint ConflictingModColor = 0xFFAAAAFF; private const uint ConflictingModColor = 0xFFAAAAFF;
private static readonly Vector2 SelectorButtonSizes = new(60, 0); private static readonly Vector2 SelectorButtonSizes = new( 60, 0 );
private static readonly string ArrowUpString = FontAwesomeIcon.ArrowUp.ToIconString(); private static readonly string ArrowUpString = FontAwesomeIcon.ArrowUp.ToIconString();
private static readonly string ArrowDownString = FontAwesomeIcon.ArrowDown.ToIconString(); private static readonly string ArrowDownString = FontAwesomeIcon.ArrowDown.ToIconString();
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
private ModCollection Mods{ get{ return _base._plugin.ModManager.Mods; } } private ModCollection Mods => _base._plugin.ModManager.Mods;
private ModInfo _mod = null; private ModInfo _mod;
private int _index = 0; private int _index;
private int? _deleteIndex = null; private int? _deleteIndex;
private string _modFilter = "";
private string[] _modNamesLower;
public Selector(SettingsInterface ui)
public Selector( SettingsInterface ui )
{ {
_base = ui; _base = ui;
ResetModNamesLower();
} }
private void DrawPriorityChangeButton(string iconString, bool up, int unavailableWhen) public void ResetModNamesLower()
{
_modNamesLower = Mods.ModSettings.Select( I => I.Mod.Meta.Name.ToLowerInvariant() ).ToArray();
}
private void DrawPriorityChangeButton( string iconString, bool up, int unavailableWhen )
{ {
ImGui.PushFont( UiBuilder.IconFont ); ImGui.PushFont( UiBuilder.IconFont );
if( _index != unavailableWhen ) if( _index != unavailableWhen )
{ {
if( ImGui.Button( iconString, SelectorButtonSizes ) ) if( ImGui.Button( iconString, SelectorButtonSizes ) )
{ {
SetSelection(_index); SetSelection( _index );
_base._plugin.ModManager.ChangeModPriority( _mod, up ); _base._plugin.ModManager.ChangeModPriority( _mod, up );
_modNamesLower.Swap( _index, _index + ( up ? 1 : -1 ) );
_index += up ? 1 : -1; _index += up ? 1 : -1;
} }
} }
@ -82,10 +94,12 @@ namespace Penumbra.UI
ImGui.PopFont(); ImGui.PopFont();
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipDelete ); ImGui.SetTooltip( TooltipDelete );
} }
}
private void DrawModAddButton() private static void DrawModAddButton()
{ {
ImGui.PushFont( UiBuilder.IconFont ); ImGui.PushFont( UiBuilder.IconFont );
@ -97,8 +111,25 @@ namespace Penumbra.UI
ImGui.PopFont(); ImGui.PopFont();
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipAdd ); ImGui.SetTooltip( TooltipAdd );
} }
}
private void DrawModsSelectorFilter()
{
ImGui.SetNextItemWidth( SelectorButtonSizes.X * 4 );
var tmp = _modFilter;
if( ImGui.InputText( LabelModFilter, ref tmp, 256 ) )
{
_modFilter = tmp.ToLowerInvariant();
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipModFilter );
}
}
private void DrawModsSelectorButtons() private void DrawModsSelectorButtons()
{ {
@ -106,9 +137,9 @@ namespace Penumbra.UI
ImGui.PushStyleVar( ImGuiStyleVar.WindowPadding, ZeroVector ); ImGui.PushStyleVar( ImGuiStyleVar.WindowPadding, ZeroVector );
ImGui.PushStyleVar( ImGuiStyleVar.FrameRounding, 0 ); ImGui.PushStyleVar( ImGuiStyleVar.FrameRounding, 0 );
DrawPriorityChangeButton(ArrowUpString, false, 0); DrawPriorityChangeButton( ArrowUpString, false, 0 );
ImGui.SameLine(); ImGui.SameLine();
DrawPriorityChangeButton(ArrowDownString, true, Mods?.ModSettings.Count - 1 ?? 0); DrawPriorityChangeButton( ArrowDownString, true, Mods?.ModSettings.Count - 1 ?? 0 );
ImGui.SameLine(); ImGui.SameLine();
DrawModTrashButton(); DrawModTrashButton();
ImGui.SameLine(); ImGui.SameLine();
@ -117,19 +148,26 @@ namespace Penumbra.UI
ImGui.PopStyleVar( 3 ); ImGui.PopStyleVar( 3 );
} }
void DrawDeleteModal() private void DrawDeleteModal()
{ {
if( _deleteIndex != null ) if( _deleteIndex == null )
{
return;
}
ImGui.OpenPopup( DialogDeleteMod ); ImGui.OpenPopup( DialogDeleteMod );
var ret = ImGui.BeginPopupModal( DialogDeleteMod ); var ret = ImGui.BeginPopupModal( DialogDeleteMod );
if( !ret ) if( !ret )
{
return; return;
}
if( _mod?.Mod == null ) if( _mod?.Mod == null )
{ {
ImGui.CloseCurrentPopup(); ImGui.CloseCurrentPopup();
ImGui.EndPopup(); ImGui.EndPopup();
return;
} }
ImGui.Text( "Are you sure you want to delete the following mod:" ); ImGui.Text( "Are you sure you want to delete the following mod:" );
@ -158,19 +196,28 @@ namespace Penumbra.UI
public void Draw() public void Draw()
{ {
if (Mods == null) if( Mods == null )
{
return; return;
}
// Selector pane // Selector pane
ImGui.BeginGroup(); ImGui.BeginGroup();
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector ); ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
DrawModsSelectorFilter();
// Inlay selector list // Inlay selector list
ImGui.BeginChild( LabelSelectorList, new Vector2(SelectorPanelWidth, -ImGui.GetFrameHeightWithSpacing() ), true ); ImGui.BeginChild( LabelSelectorList, new Vector2( SelectorPanelWidth, -ImGui.GetFrameHeightWithSpacing() ), true );
for( var modIndex = 0; modIndex < Mods.ModSettings.Count; modIndex++ ) for( var modIndex = 0; modIndex < Mods.ModSettings.Count; modIndex++ )
{ {
var settings = Mods.ModSettings[ modIndex ]; var settings = Mods.ModSettings[ modIndex ];
var modName = settings.Mod.Meta.Name;
if( _modFilter.Length > 0 && !_modNamesLower[ modIndex ].Contains( _modFilter ) )
{
continue;
}
var changedColour = false; var changedColour = false;
if( !settings.Enabled ) if( !settings.Enabled )
@ -186,18 +233,22 @@ namespace Penumbra.UI
#if DEBUG #if DEBUG
var selected = ImGui.Selectable( var selected = ImGui.Selectable(
$"id={modIndex} {settings.Mod.Meta.Name}", $"id={modIndex} {modName}",
modIndex == _index modIndex == _index
); );
#else #else
var selected = ImGui.Selectable( settings.Mod.Meta.Name, modIndex == _index ); var selected = ImGui.Selectable( modName, modIndex == _index );
#endif #endif
if( changedColour ) if( changedColour )
{
ImGui.PopStyleColor(); ImGui.PopStyleColor();
}
if( selected ) if( selected )
SetSelection(modIndex, settings); {
SetSelection( modIndex, settings );
}
} }
ImGui.EndChild(); ImGui.EndChild();
@ -210,26 +261,36 @@ namespace Penumbra.UI
public ModInfo Mod() => _mod; public ModInfo Mod() => _mod;
private void SetSelection(int idx, ModInfo info) private void SetSelection( int idx, ModInfo info )
{ {
_mod = info; _mod = info;
if (idx != _index) if( idx != _index )
_base._menu._installedTab._modPanel._details.ResetState(); {
_base._menu.InstalledTab.ModPanel.Details.ResetState();
}
_index = idx; _index = idx;
_deleteIndex = null; _deleteIndex = null;
} }
public void SetSelection(int idx) private void SetSelection( int idx )
{
if( idx >= ( Mods?.ModSettings?.Count ?? 0 ) )
{ {
if (idx >= (Mods?.ModSettings?.Count ?? 0))
idx = -1; idx = -1;
if (idx < 0)
SetSelection(0, null);
else
SetSelection(idx, Mods.ModSettings[idx]);
} }
public void ClearSelection() => SetSelection(-1); if( idx < 0 )
{
SetSelection( 0, null );
}
else
{
SetSelection( idx, Mods.ModSettings[ idx ] );
}
}
public void ClearSelection() => SetSelection( -1 );
public void SelectModByName( string name ) public void SelectModByName( string name )
{ {
@ -238,38 +299,42 @@ namespace Penumbra.UI
var mod = Mods.ModSettings[ modIndex ]; var mod = Mods.ModSettings[ modIndex ];
if( mod.Mod.Meta.Name != name ) if( mod.Mod.Meta.Name != name )
{
continue; continue;
}
SetSelection(modIndex, mod); SetSelection( modIndex, mod );
return; return;
} }
} }
private string GetCurrentModMetaFile() private string GetCurrentModMetaFile()
{ => _mod == null ? "" : Path.Combine( _mod.Mod.ModBasePath.FullName, "meta.json" );
if( _mod == null )
return "";
return Path.Combine( _mod.Mod.ModBasePath.FullName, "meta.json" );
}
public void ReloadCurrentMod() public void ReloadCurrentMod()
{ {
var metaPath = GetCurrentModMetaFile(); var metaPath = GetCurrentModMetaFile();
if (metaPath.Length > 0 && File.Exists(metaPath)) if( metaPath.Length > 0 && File.Exists( metaPath ) )
{ {
_mod.Mod.Meta = ModMeta.LoadFromFile(metaPath) ?? _mod.Mod.Meta; _mod.Mod.Meta = ModMeta.LoadFromFile( metaPath ) ?? _mod.Mod.Meta;
_base._menu._installedTab._modPanel._details.ResetState(); _base._menu.InstalledTab.ModPanel.Details.ResetState();
} }
_mod.Mod.RefreshModFiles(); _mod.Mod.RefreshModFiles();
_base._plugin.ModManager.CalculateEffectiveFileList(); _base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
ResetModNamesLower();
} }
public string SaveCurrentMod() public string SaveCurrentMod()
{ {
var metaPath = GetCurrentModMetaFile(); var metaPath = GetCurrentModMetaFile();
if (metaPath.Length > 0) if( metaPath.Length > 0 )
{
File.WriteAllText( metaPath, JsonConvert.SerializeObject( _mod.Mod.Meta, Formatting.Indented ) ); File.WriteAllText( metaPath, JsonConvert.SerializeObject( _mod.Mod.Meta, Formatting.Indented ) );
_base._menu._installedTab._modPanel._details.ResetState(); }
_base._menu.InstalledTab.ModPanel.Details.ResetState();
return metaPath; return metaPath;
} }
} }

View file

@ -23,7 +23,7 @@ namespace Penumbra.UI
private readonly Configuration _config; private readonly Configuration _config;
private bool _configChanged; private bool _configChanged;
public TabSettings(SettingsInterface ui) public TabSettings( SettingsInterface ui )
{ {
_base = ui; _base = ui;
_config = _base._plugin.Configuration; _config = _base._plugin.Configuration;
@ -45,7 +45,7 @@ namespace Penumbra.UI
if( ImGui.Button( LabelRediscoverButton ) ) if( ImGui.Button( LabelRediscoverButton ) )
{ {
_base.ReloadMods(); _base.ReloadMods();
_base._menu._installedTab._selector.ClearSelection(); _base._menu.InstalledTab.Selector.ClearSelection();
} }
} }
@ -64,7 +64,7 @@ namespace Penumbra.UI
{ {
_config.IsEnabled = enabled; _config.IsEnabled = enabled;
_configChanged = true; _configChanged = true;
RefreshActors.RedrawAll(_base._plugin.PluginInterface.ClientState.Actors); Game.RefreshActors.RedrawAll( _base._plugin.PluginInterface.ClientState.Actors );
} }
} }
@ -86,15 +86,17 @@ namespace Penumbra.UI
{ {
_config.ShowAdvanced = showAdvanced; _config.ShowAdvanced = showAdvanced;
_configChanged = true; _configChanged = true;
_base._menu._effectiveTab.RebuildFileList(showAdvanced); _base._menu.EffectiveTab.RebuildFileList( showAdvanced );
} }
} }
private void DrawLogLoadedFilesBox() private void DrawLogLoadedFilesBox()
{ {
if( _base._plugin.ResourceLoader != null ) if( _base._plugin.ResourceLoader != null )
{
ImGui.Checkbox( LabelLogLoadedFiles, ref _base._plugin.ResourceLoader.LogAllFiles ); ImGui.Checkbox( LabelLogLoadedFiles, ref _base._plugin.ResourceLoader.LogAllFiles );
} }
}
private void DrawDisableNotificationsBox() private void DrawDisableNotificationsBox()
{ {
@ -112,9 +114,13 @@ namespace Penumbra.UI
if( ImGui.Checkbox( LabelEnableHttpApi, ref http ) ) if( ImGui.Checkbox( LabelEnableHttpApi, ref http ) )
{ {
if( http ) if( http )
{
_base._plugin.CreateWebServer(); _base._plugin.CreateWebServer();
}
else else
{
_base._plugin.ShutdownWebServer(); _base._plugin.ShutdownWebServer();
}
_config.EnableHttpApi = http; _config.EnableHttpApi = http;
_configChanged = true; _configChanged = true;
@ -141,7 +147,9 @@ namespace Penumbra.UI
{ {
var ret = ImGui.BeginTabItem( LabelTab ); var ret = ImGui.BeginTabItem( LabelTab );
if( !ret ) if( !ret )
{
return; return;
}
DrawRootFolder(); DrawRootFolder();
@ -149,17 +157,19 @@ namespace Penumbra.UI
ImGui.SameLine(); ImGui.SameLine();
DrawOpenModsButton(); DrawOpenModsButton();
ImGuiCustom.VerticalDistance(DefaultVerticalSpace); ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawEnabledBox(); DrawEnabledBox();
ImGuiCustom.VerticalDistance(DefaultVerticalSpace); ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawInvertModOrderBox(); DrawInvertModOrderBox();
ImGuiCustom.VerticalDistance(DefaultVerticalSpace); ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawShowAdvancedBox(); DrawShowAdvancedBox();
if( _config.ShowAdvanced ) if( _config.ShowAdvanced )
{
DrawAdvancedSettings(); DrawAdvancedSettings();
}
if( _configChanged ) if( _configChanged )
{ {

View file

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
namespace Penumbra
{
public static class ArrayExtensions
{
public static void Swap< T >( this T[] array, int idx1, int idx2 )
{
var tmp = array[ idx1 ];
array[ idx1 ] = array[ idx2 ];
array[ idx2 ] = tmp;
}
public static void Swap< T >( this List< T > array, int idx1, int idx2 )
{
var tmp = array[ idx1 ];
array[ idx1 ] = array[ idx2 ];
array[ idx2 ] = tmp;
}
public static int IndexOf< T >( this T[] array, Predicate< T > match )
{
for( var i = 0; i < array.Length; ++i )
{
if( match( array[ i ] ) )
{
return i;
}
}
return -1;
}
public static void Swap< T >( this T[] array, T lhs, T rhs )
{
var idx1 = Array.IndexOf( array, lhs );
if( idx1 < 0 )
{
return;
}
var idx2 = Array.IndexOf( array, rhs );
if( idx2 < 0 )
{
return;
}
array.Swap( idx1, idx2 );
}
public static void Swap< T >( this List< T > array, T lhs, T rhs )
{
var idx1 = array.IndexOf( lhs );
if( idx1 < 0 )
{
return;
}
var idx2 = array.IndexOf( rhs );
if( idx2 < 0 )
{
return;
}
array.Swap( idx1, idx2 );
}
}
}

View file

@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Penumbra.Util namespace Penumbra.Util
@ -15,7 +15,9 @@ namespace Penumbra.Util
{ {
var k = ( uint )i; var k = ( uint )i;
for( var j = 0; j < 8; j++ ) for( var j = 0; j < 8; j++ )
{
k = ( k & 1 ) != 0 ? ( k >> 1 ) ^ Poly : k >> 1; k = ( k & 1 ) != 0 ? ( k >> 1 ) ^ Poly : k >> 1;
}
return k; return k;
} ).ToArray(); } ).ToArray();
@ -40,8 +42,10 @@ namespace Penumbra.Util
public void Update( byte[] data ) public void Update( byte[] data )
{ {
foreach( var b in data ) foreach( var b in data )
{
Update( b ); Update( b );
} }
}
[MethodImpl( MethodImplOptions.AggressiveInlining )] [MethodImpl( MethodImplOptions.AggressiveInlining )]
public void Update( byte b ) public void Update( byte b )

View file

@ -3,21 +3,16 @@ using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
public class SingleOrArrayConverter<T> : JsonConverter public class SingleOrArrayConverter< T > : JsonConverter
{ {
public override bool CanConvert( Type objectType ) public override bool CanConvert( Type objectType ) => objectType == typeof( HashSet< T > );
{
return (objectType == typeof(HashSet<T>));
}
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{ {
var token = JToken.Load(reader); var token = JToken.Load( reader );
if (token.Type == JTokenType.Array) return token.Type == JTokenType.Array
{ ? token.ToObject< HashSet< T > >()
return token.ToObject<HashSet<T>>(); : new HashSet< T > { token.ToObject< T >() };
}
return new HashSet<T>{ token.ToObject<T>() };
} }
public override bool CanWrite => false; public override bool CanWrite => false;
@ -28,22 +23,20 @@ public class SingleOrArrayConverter<T> : JsonConverter
} }
} }
public class DictSingleOrArrayConverter<T,U> : JsonConverter public class DictSingleOrArrayConverter< T, U > : JsonConverter
{ {
public override bool CanConvert( Type objectType ) public override bool CanConvert( Type objectType ) => objectType == typeof( Dictionary< T, HashSet< U > > );
{
return (objectType == typeof(Dictionary<T, HashSet<U>>));
}
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{ {
var token = JToken.Load(reader); var token = JToken.Load( reader );
if (token.Type == JTokenType.Array) if( token.Type == JTokenType.Array )
{ {
return token.ToObject<HashSet<T>>(); return token.ToObject< HashSet< T > >();
} }
return new HashSet<T>{ token.ToObject<T>() };
return new HashSet< T > { token.ToObject< T >() };
} }
public override bool CanWrite => false; public override bool CanWrite => false;

View file

@ -0,0 +1,15 @@
using System.IO;
namespace Penumbra
{
public static class StringPathExtensions
{
private static readonly char[] _invalid = Path.GetInvalidFileNameChars();
public static string ReplaceInvalidPathSymbols( this string s, string replacement = "_" )
=> string.Join( replacement, s.Split( _invalid ) );
public static string RemoveInvalidPathSymbols( this string s )
=> string.Concat( s.Split( _invalid ) );
}
}