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

@ -1,67 +1,85 @@
[*]
charset=utf-8
end_of_line=lf
trim_trailing_whitespace=false
insert_final_newline=false
indent_style=space
indent_size=4
# Microsoft .NET properties
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_space_after_cast=false
csharp_space_after_keywords_in_control_flow_statements=false
csharp_space_between_method_call_parameter_list_parentheses=true
csharp_space_between_method_declaration_parameter_list_parentheses=true
csharp_space_between_parentheses=control_flow_statements,expressions,type_casts
csharp_style_var_elsewhere=true:suggestion
csharp_style_var_for_built_in_types=true:suggestion
csharp_style_var_when_type_is_apparent=true:suggestion
dotnet_style_parentheses_in_arithmetic_binary_operators=never_if_unnecessary:none
dotnet_style_parentheses_in_other_binary_operators=never_if_unnecessary:none
dotnet_style_parentheses_in_relational_binary_operators=never_if_unnecessary:none
dotnet_style_predefined_type_for_locals_parameters_members=true:suggestion
dotnet_style_predefined_type_for_member_access=true:suggestion
dotnet_style_qualification_for_event=false:suggestion
dotnet_style_qualification_for_field=false:suggestion
dotnet_style_qualification_for_method=false:suggestion
dotnet_style_qualification_for_property=false:suggestion
dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion
# ReSharper properties
resharper_autodetect_indent_settings=true
resharper_csharp_space_within_array_access_brackets=true
resharper_enforce_line_ending_style=true
resharper_place_attribute_on_same_line=false
resharper_space_after_cast=false
resharper_space_within_checked_parentheses=true
resharper_space_within_default_parentheses=true
resharper_space_within_nameof_parentheses=true
resharper_space_within_single_line_array_initializer_braces=true
resharper_space_within_sizeof_parentheses=true
resharper_space_within_typeof_parentheses=true
resharper_space_within_type_argument_angles=true
resharper_space_within_type_parameter_angles=true
resharper_use_indent_from_vs=false
resharper_wrap_lines=true
# ReSharper inspection severities
resharper_arrange_redundant_parentheses_highlighting=hint
resharper_arrange_this_qualifier_highlighting=hint
resharper_arrange_type_member_modifiers_highlighting=hint
resharper_arrange_type_modifiers_highlighting=hint
resharper_built_in_type_reference_style_for_member_access_highlighting=hint
resharper_built_in_type_reference_style_highlighting=hint
resharper_redundant_base_qualifier_highlighting=warning
resharper_suggest_var_or_type_built_in_types_highlighting=hint
resharper_suggest_var_or_type_elsewhere_highlighting=hint
resharper_suggest_var_or_type_simple_types_highlighting=hint
resharper_web_config_module_not_resolved_highlighting=warning
resharper_web_config_type_not_resolved_highlighting=warning
resharper_web_config_wrong_module_highlighting=warning
[*.{appxmanifest,asax,ascx,aspx,build,cg,cginc,compute,cs,cshtml,dtd,hlsl,hlsli,hlslinc,master,nuspec,razor,resw,resx,shader,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}]
indent_style=space
indent_size=4
tab_width=4
[*]
charset=utf-8
end_of_line=lf
trim_trailing_whitespace=true
insert_final_newline=false
indent_style=space
indent_size=4
# Microsoft .NET properties
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_prefer_braces=true:none
csharp_space_after_cast=false
csharp_space_after_keywords_in_control_flow_statements=false
csharp_space_between_method_call_parameter_list_parentheses=true
csharp_space_between_method_declaration_parameter_list_parentheses=true
csharp_space_between_parentheses=control_flow_statements,expressions,type_casts
csharp_style_var_elsewhere=true:suggestion
csharp_style_var_for_built_in_types=true:suggestion
csharp_style_var_when_type_is_apparent=true:suggestion
dotnet_style_parentheses_in_arithmetic_binary_operators=never_if_unnecessary:none
dotnet_style_parentheses_in_other_binary_operators=never_if_unnecessary:none
dotnet_style_parentheses_in_relational_binary_operators=never_if_unnecessary:none
dotnet_style_predefined_type_for_locals_parameters_members=true:suggestion
dotnet_style_predefined_type_for_member_access=true:suggestion
dotnet_style_qualification_for_event=false:suggestion
dotnet_style_qualification_for_field=false:suggestion
dotnet_style_qualification_for_method=false:suggestion
dotnet_style_qualification_for_property=false:suggestion
dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion
# ReSharper properties
resharper_align_multiline_binary_expressions_chain=false
resharper_align_multiline_calls_chain=false
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_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_space_after_cast=false
resharper_space_within_checked_parentheses=true
resharper_space_within_default_parentheses=true
resharper_space_within_nameof_parentheses=true
resharper_space_within_single_line_array_initializer_braces=true
resharper_space_within_sizeof_parentheses=true
resharper_space_within_typeof_parentheses=true
resharper_space_within_type_argument_angles=true
resharper_space_within_type_parameter_angles=true
resharper_use_indent_from_vs=false
resharper_wrap_lines=true
# ReSharper inspection severities
resharper_arrange_redundant_parentheses_highlighting=hint
resharper_arrange_this_qualifier_highlighting=hint
resharper_arrange_type_member_modifiers_highlighting=hint
resharper_arrange_type_modifiers_highlighting=hint
resharper_built_in_type_reference_style_for_member_access_highlighting=hint
resharper_built_in_type_reference_style_highlighting=hint
resharper_redundant_base_qualifier_highlighting=warning
resharper_suggest_var_or_type_built_in_types_highlighting=hint
resharper_suggest_var_or_type_elsewhere_highlighting=hint
resharper_suggest_var_or_type_simple_types_highlighting=hint
resharper_web_config_module_not_resolved_highlighting=warning
resharper_web_config_type_not_resolved_highlighting=warning
resharper_web_config_wrong_module_highlighting=warning
[*.{appxmanifest,asax,ascx,aspx,build,cg,cginc,compute,cs,cshtml,dtd,hlsl,hlsli,hlslinc,master,nuspec,razor,resw,resx,shader,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}]
indent_style=space
indent_size=4
tab_width=4

View file

@ -9,10 +9,7 @@ namespace Penumbra.API
{
private readonly Plugin _plugin;
public ModsController( Plugin plugin )
{
_plugin = plugin;
}
public ModsController( Plugin plugin ) => _plugin = plugin;
[Route( HttpVerbs.Get, "/mods" )]
public object GetMods()
@ -24,7 +21,7 @@ namespace Penumbra.API
x.FolderName,
x.Mod.Meta,
BasePath = x.Mod.ModBasePath.FullName,
Files = x.Mod.ModFiles.Select( fi => fi.FullName )
Files = x.Mod.ModFiles.Select( fi => fi.FullName )
} );
}

View file

@ -1,7 +1,7 @@
using Dalamud.Configuration;
using Dalamud.Plugin;
using System;
using System.Collections.Generic;
using Dalamud.Configuration;
using Dalamud.Plugin;
namespace Penumbra
{
@ -13,7 +13,7 @@ namespace Penumbra
public bool IsEnabled { get; set; } = true;
public bool ShowAdvanced { get; set; }
public bool DisableFileSystemNotifications { get; set; }
public bool EnableHttpApi { get; set; }

View file

@ -18,7 +18,7 @@ namespace Penumbra
public static Task< DialogResult > ShowDialogAsync( this CommonDialog form, IWin32Window owner )
{
var taskSource = new TaskCompletionSource< DialogResult >();
var th = new Thread( () => DialogThread( form, owner, taskSource ) );
var th = new Thread( () => DialogThread( form, owner, taskSource ) );
th.Start();
return taskSource.Task;
}
@ -38,28 +38,25 @@ namespace Penumbra
{
public IntPtr Handle { get; set; }
public DialogHandle( IntPtr handle )
{
Handle = handle;
}
public DialogHandle( IntPtr handle ) => Handle = handle;
}
public class HiddenForm : Form
{
private readonly CommonDialog _form;
private readonly IWin32Window _owner;
private readonly CommonDialog _form;
private readonly IWin32Window _owner;
private readonly TaskCompletionSource< DialogResult > _taskSource;
public HiddenForm( CommonDialog form, IWin32Window owner, TaskCompletionSource< DialogResult > taskSource )
{
this._form = form;
this._owner = owner;
this._taskSource = taskSource;
_form = form;
_owner = owner;
_taskSource = taskSource;
Opacity = 0;
Opacity = 0;
FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = false;
Size = new Size( 0, 0 );
ShowInTaskbar = false;
Size = new Size( 0, 0 );
Shown += HiddenForm_Shown;
}

View file

@ -25,7 +25,8 @@ namespace Penumbra.Extensions
/// <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>
/// <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;

View file

@ -14,11 +14,11 @@ namespace Penumbra.Game
public unsafe delegate void* UnloadPlayerResourcesPrototype( IntPtr pResourceManager );
public LoadPlayerResourcesPrototype LoadPlayerResources { get; private set; }
public UnloadPlayerResourcesPrototype UnloadPlayerResources { get; private set; }
public LoadPlayerResourcesPrototype LoadPlayerResources { get; }
public UnloadPlayerResourcesPrototype UnloadPlayerResources { get; }
// Object addresses
private IntPtr _playerResourceManagerAddress;
private readonly IntPtr _playerResourceManagerAddress;
public IntPtr PlayerResourceManagerPtr => Marshal.ReadIntPtr( _playerResourceManagerAddress );
public GameUtils( DalamudPluginInterface pluginInterface )
@ -33,7 +33,7 @@ namespace Penumbra.Game
_playerResourceManagerAddress = scanner.GetStaticAddressFromSig( "0F 44 FE 48 8B 0D ?? ?? ?? ?? 48 85 C9 74 05" );
LoadPlayerResources = Marshal.GetDelegateForFunctionPointer< LoadPlayerResourcesPrototype >( loadPlayerResourcesAddress );
LoadPlayerResources = Marshal.GetDelegateForFunctionPointer< LoadPlayerResourcesPrototype >( loadPlayerResourcesAddress );
UnloadPlayerResources = Marshal.GetDelegateForFunctionPointer< UnloadPlayerResourcesPrototype >( unloadPlayerResourcesAddress );
}

View file

@ -1,54 +1,61 @@
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types;
namespace Penumbra
namespace Penumbra.Game
{
public static class RefreshActors
{
private const int RenderModeOffset = 0x0104;
private const int RenderTaskPlayerDelay = 75;
private const int RenderTaskOtherDelay = 25;
private const int ModelInvisibilityFlag = 0b10;
private static async void Redraw(Actor actor)
{
var ptr = actor.Address;
var renderModePtr = ptr + RenderModeOffset;
var renderStatus = Marshal.ReadInt32(renderModePtr);
async void DrawObject(int delay)
{
Marshal.WriteInt32(renderModePtr, renderStatus | ModelInvisibilityFlag);
await Task.Delay(delay);
Marshal.WriteInt32(renderModePtr, renderStatus & ~ModelInvisibilityFlag);
}
if (actor.ObjectKind == Dalamud.Game.ClientState.Actors.ObjectKind.Player)
{
DrawObject(RenderTaskPlayerDelay);
await Task.Delay(RenderTaskPlayerDelay);
}
else
DrawObject(RenderTaskOtherDelay);
}
public static void RedrawSpecific(ActorTable actors, string name)
{
if (name?.Length == 0)
RedrawAll(actors);
foreach (var actor in actors)
if (actor.Name == name)
Redraw(actor);
}
public static void RedrawAll(ActorTable actors)
{
foreach (var actor in actors)
Redraw(actor);
}
public static class RefreshActors
{
private const int RenderModeOffset = 0x0104;
private const int RenderTaskPlayerDelay = 75;
private const int RenderTaskOtherDelay = 25;
private const int ModelInvisibilityFlag = 0b10;
private static async void Redraw( Actor actor )
{
var ptr = actor.Address;
var renderModePtr = ptr + RenderModeOffset;
var renderStatus = Marshal.ReadInt32( renderModePtr );
async void DrawObject( int delay )
{
Marshal.WriteInt32( renderModePtr, renderStatus | ModelInvisibilityFlag );
await Task.Delay( delay );
Marshal.WriteInt32( renderModePtr, renderStatus & ~ModelInvisibilityFlag );
}
if( actor.ObjectKind == ObjectKind.Player )
{
DrawObject( RenderTaskPlayerDelay );
await Task.Delay( RenderTaskPlayerDelay );
}
else
{
DrawObject( RenderTaskOtherDelay );
}
}
public static void RedrawSpecific( ActorTable actors, string name )
{
if( name?.Length == 0 )
{
RedrawAll( actors );
}
foreach( var actor in actors.Where( A => A.Name == name ) )
{
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;
public MagicTempFileStreamManagerAndDeleterFuckery( FileStream stream ) : base( stream )
{
_fileStream = stream;
}
public MagicTempFileStreamManagerAndDeleterFuckery( FileStream stream ) : base( stream ) => _fileStream = stream;
public new void Dispose()
{

View file

@ -16,12 +16,12 @@ namespace Penumbra.Importer
{
private readonly DirectoryInfo _outDirectory;
private const string TempFileName = "textools-import";
private const string TempFileName = "textools-import";
private readonly string _resolvedTempFilePath;
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 float Progress
@ -42,7 +42,7 @@ namespace Penumbra.Importer
public TexToolsImport( DirectoryInfo outDirectory )
{
_outDirectory = outDirectory;
_outDirectory = outDirectory;
_resolvedTempFilePath = Path.Combine( _outDirectory.FullName, TempFileName );
}
@ -50,19 +50,7 @@ namespace Penumbra.Importer
{
CurrentModPack = modPackFile.Name;
switch( modPackFile.Extension )
{
case ".ttmp":
ImportV1ModPack( modPackFile );
break;
case ".ttmp2":
ImportV2ModPack( modPackFile );
break;
default:
throw new ArgumentException( $"Unrecognized modpack format: {modPackFile.Extension}", nameof(modPackFile) );
}
VerifyVersionAndImport( modPackFile );
State = ImporterState.Done;
}
@ -79,8 +67,8 @@ namespace Penumbra.Importer
State = ImporterState.WritingPackToDisk;
// write shitty zip garbage to disk
var entry = file.GetEntry( entryName );
using var s = file.GetInputStream( entry );
var entry = file.GetEntry( entryName );
using var s = file.GetInputStream( entry );
WriteZipEntryToTempFile( s );
@ -88,15 +76,39 @@ namespace Penumbra.Importer
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" );
using var zfs = modPackFile.OpenRead();
using var extractedModPack = new ZipFile( zfs );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modListRaw = GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ).Split(
var modListRaw = modRaw.Split(
new[] { "\r\n", "\r", "\n" },
StringSplitOptions.None
);
@ -106,8 +118,8 @@ namespace Penumbra.Importer
// Create a new ModMeta from the TTMP modlist info
var modMeta = new ModMeta
{
Author = "Unknown",
Name = modPackFile.Name,
Author = "Unknown",
Name = modPackFile.Name,
Description = "Mod imported from TexTools mod pack"
};
@ -129,38 +141,31 @@ namespace Penumbra.Importer
ExtractSimpleModList( newModFolder, modList, modData );
}
private void ImportV2ModPack( FileInfo modPackFile )
private void ImportV2ModPack( FileInfo modPackFile, ZipFile extractedModPack, string modRaw )
{
using var zfs = modPackFile.OpenRead();
using var extractedModPack = new ZipFile( zfs );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modList = JsonConvert.DeserializeObject< SimpleModPack >( GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ) );
var modList = JsonConvert.DeserializeObject< SimpleModPack >( modRaw );
if( modList.TTMPVersion.EndsWith( "s" ) )
{
ImportSimpleV2ModPack( extractedModPack );
ImportSimpleV2ModPack( extractedModPack, modList );
return;
}
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" );
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
var modMeta = new ModMeta
{
Author = modList.Author,
Name = modList.Name,
Name = modList.Name,
Description = string.IsNullOrEmpty( modList.Description )
? "Mod imported from TexTools mod pack"
: modList.Description
@ -179,18 +184,17 @@ namespace Penumbra.Importer
ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData );
}
private void ImportExtendedV2ModPack( ZipFile extractedModPack )
private void ImportExtendedV2ModPack( ZipFile extractedModPack, string modRaw )
{
PluginLog.Log( " -> Importing Extended V2 ModPack" );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modList = JsonConvert.DeserializeObject< ExtendedModPack >( GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ) );
var modList = JsonConvert.DeserializeObject< ExtendedModPack >( modRaw );
// Create a new ModMeta from the TTMP modlist info
var modMeta = new ModMeta
{
Author = modList.Author,
Name = modList.Name,
Name = modList.Name,
Description = string.IsNullOrEmpty( modList.Description )
? "Mod imported from TexTools mod pack"
: modList.Description,
@ -202,28 +206,32 @@ namespace Penumbra.Importer
var newModFolder = new DirectoryInfo(
Path.Combine( _outDirectory.FullName,
Path.GetFileNameWithoutExtension( modList.Name )
Path.GetFileNameWithoutExtension( modList.Name ).ReplaceInvalidPathSymbols()
)
);
newModFolder.Create();
if( modList.SimpleModsList != null )
{
ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData );
}
if( modList.ModPackPages == null )
{
return;
}
// 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));
foreach(var option in group.OptionList) {
var optionFolder = new DirectoryInfo( Path.Combine( groupFolder.FullName, option.Name ) );
ExtractSimpleModList( optionFolder, option.ModsJsons, modData );
}
AddMeta(newModFolder, groupFolder, group, modMeta);
var groupFolder = new DirectoryInfo( Path.Combine( newModFolder.FullName, group.GroupName.ReplaceInvalidPathSymbols() ) );
foreach( var option in group.OptionList )
{
var optionFolder = new DirectoryInfo( Path.Combine( groupFolder.FullName, option.Name.ReplaceInvalidPathSymbols() ) );
ExtractSimpleModList( optionFolder, option.ModsJsons, modData );
}
AddMeta( newModFolder, groupFolder, group, modMeta );
}
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
{
SelectionType = group.SelectionType,
GroupName = group.GroupName,
Options = new List<Option>(),
GroupName = group.GroupName,
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,
OptionDesc = String.IsNullOrEmpty(opt.Description) ? "" : opt.Description,
OptionFiles = new Dictionary<string, HashSet<string>>()
OptionName = opt.Name,
OptionDesc = string.IsNullOrEmpty( opt.Description ) ? "" : opt.Description,
OptionFiles = new Dictionary< string, HashSet< string > >()
};
var optDir = new DirectoryInfo(Path.Combine( groupFolder.FullName, opt.Name));
if (optDir.Exists)
var optDir = new DirectoryInfo( Path.Combine( groupFolder.FullName, opt.Name.ReplaceInvalidPathSymbols() ) );
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 );
}
@ -273,17 +284,11 @@ namespace Penumbra.Importer
// haha allocation go brr
var wtf = mods.ToList();
TotalProgress = wtf.LongCount();
TotalProgress += wtf.LongCount();
// 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 );
CurrentProgress++;
}
@ -301,13 +306,15 @@ namespace Penumbra.Importer
extractedFile.Directory?.Create();
if( extractedFile.FullName.EndsWith( "mdl" ) )
{
ProcessMdl( data.Data );
}
File.WriteAllBytes( extractedFile.FullName, data.Data );
}
catch( Exception ex )
{
PluginLog.LogError( ex, "Could not export mod." );
PluginLog.LogError( ex, "Could not extract mod." );
}
}
@ -317,26 +324,23 @@ namespace Penumbra.Importer
mdl[ 64 ] = 1;
// Model header LOD num
var stackSize = BitConverter.ToUInt32( mdl, 4 );
var runtimeBegin = stackSize + 0x44;
var stringsLengthOffset = runtimeBegin + 4;
var stringsLength = BitConverter.ToUInt32( mdl, ( int )stringsLengthOffset );
var modelHeaderStart = stringsLengthOffset + stringsLength + 4;
var stackSize = BitConverter.ToUInt32( mdl, 4 );
var runtimeBegin = stackSize + 0x44;
var stringsLengthOffset = runtimeBegin + 4;
var stringsLength = BitConverter.ToUInt32( mdl, ( int )stringsLengthOffset );
var modelHeaderStart = stringsLengthOffset + stringsLength + 4;
var modelHeaderLodOffset = 22;
mdl[ modelHeaderStart + modelHeaderLodOffset ] = 1;
}
private static Stream GetStreamFromZipEntry( ZipFile file, ZipEntry entry )
{
return file.GetInputStream( entry );
}
private static Stream GetStreamFromZipEntry( ZipFile file, ZipEntry entry ) => file.GetInputStream( entry );
private static string GetStringFromZipEntry( ZipFile file, ZipEntry entry, Encoding encoding )
{
using var ms = new MemoryStream();
using var s = GetStreamFromZipEntry( file, entry );
using var s = GetStreamFromZipEntry( file, entry );
s.CopyTo( ms );
return encoding.GetString( ms.ToArray() );
}
}
}
}

View file

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

View file

@ -5,30 +5,37 @@ namespace Penumbra.Models
{
public enum SelectType
{
Single, Multi
Single,
Multi
}
public struct Option
{
public string OptionName;
public string OptionDesc;
[JsonProperty(ItemConverterType = typeof(SingleOrArrayConverter<string>))]
public Dictionary<string, HashSet<string>> OptionFiles;
[JsonProperty( ItemConverterType = typeof( SingleOrArrayConverter< string > ) )]
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))
return set.Add(gamePath);
else
OptionFiles[filePath] = new(){ gamePath };
if( OptionFiles.TryGetValue( filePath, out var set ) )
{
return set.Add( gamePath );
}
OptionFiles[ filePath ] = new HashSet< string >() { gamePath };
return true;
}
}
public struct InstallerInfo {
public struct InstallerInfo
{
public string GroupName;
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
[JsonConverter( typeof( Newtonsoft.Json.Converters.StringEnumConverter ) )]
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 Newtonsoft.Json;
using Penumbra.Mods;
namespace Penumbra.Models
@ -7,9 +7,9 @@ namespace Penumbra.Models
public class ModInfo
{
public string FolderName { get; set; }
public bool Enabled { get; set; }
public int Priority { get; set; }
public Dictionary<string, int> Conf {get;set;}
public bool Enabled { get; set; }
public int Priority { get; set; }
public Dictionary< string, int > Conf { get; set; }
[JsonIgnore]
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.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
namespace Penumbra.Models
{
@ -21,25 +21,28 @@ namespace Penumbra.Models
public Dictionary< string, string > FileSwaps { get; } = new();
public Dictionary<string, InstallerInfo> Groups { get; set; } = new();
[JsonIgnore]
public Dictionary< string, InstallerInfo > Groups { get; set; } = new();
[JsonIgnore]
public bool HasGroupWithConfig { get; set; } = false;
public static ModMeta LoadFromFile(string filePath)
{
public static ModMeta LoadFromFile( string filePath )
{
try
{
var meta = JsonConvert.DeserializeObject< ModMeta >( File.ReadAllText( filePath ) );
meta.HasGroupWithConfig = meta.Groups != null && meta.Groups.Count > 0
&& meta.Groups.Values.Any( G => G.SelectionType == SelectType.Multi || G.Options.Count > 1);
meta.HasGroupWithConfig =
meta.Groups != null
&& meta.Groups.Count > 0
&& meta.Groups.Values.Any( G => G.SelectionType == SelectType.Multi || G.Options.Count > 1 );
return meta;
}
catch( Exception)
catch( Exception )
{
return null;
// todo: handle broken mods properly
}
}
}
}
}

View file

@ -16,10 +16,7 @@ namespace Penumbra.Mods
public ResourceMod[] EnabledMods { get; set; }
public ModCollection( DirectoryInfo basePath )
{
_basePath = basePath;
}
public ModCollection( DirectoryInfo basePath ) => _basePath = basePath;
public void Load( bool invertOrder = false )
{
@ -51,7 +48,7 @@ namespace Penumbra.Mods
}
#endif
ModSettings ??= new();
ModSettings ??= new List< ModInfo >();
var foundMods = new List< string >();
foreach( var modDir in _basePath.EnumerateDirectories() )
@ -73,7 +70,7 @@ namespace Penumbra.Mods
var mod = new ResourceMod
{
Meta = meta,
Meta = meta,
ModBasePath = modDir
};
@ -124,7 +121,7 @@ namespace Penumbra.Mods
{
// todo: certified fucked tier
var prio = info.Priority;
var prio = info.Priority;
var swapPrio = up ? prio + 1 : prio - 1;
var swapMeta = ModSettings.FirstOrDefault( x => x.Priority == swapPrio );
@ -133,7 +130,7 @@ namespace Penumbra.Mods
return;
}
info.Priority = swapPrio;
info.Priority = swapPrio;
swapMeta.Priority = prio;
// reorder mods list
@ -160,10 +157,10 @@ namespace Penumbra.Mods
{
var entry = new ModInfo
{
Priority = ModSettings.Count,
Priority = ModSettings.Count,
FolderName = mod.ModBasePath.Name,
Enabled = true,
Mod = mod
Enabled = true,
Mod = mod
};
#if DEBUG

View file

@ -2,24 +2,22 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Plugin;
using Penumbra.Models;
namespace Penumbra.Mods
{
public class ModManager : IDisposable
{
private readonly Plugin _plugin;
public readonly Dictionary< string, FileInfo > ResolvedFiles = new();
public readonly Dictionary< string, string > SwappedFiles = new();
private readonly Plugin _plugin;
public readonly Dictionary< string, FileInfo > ResolvedFiles = new();
public readonly Dictionary< string, string > SwappedFiles = new();
public ModCollection Mods { get; set; }
private DirectoryInfo _basePath;
public ModManager( Plugin plugin )
{
_plugin = plugin;
}
public ModManager( Plugin plugin ) => _plugin = plugin;
public void DiscoverMods()
{
@ -113,7 +111,7 @@ namespace Penumbra.Mods
mod.FileConflicts?.Clear();
if( settings.Conf == null )
{
settings.Conf = new();
settings.Conf = new Dictionary< string, int >();
_plugin.ModManager.Mods.Save();
}
@ -137,7 +135,7 @@ namespace Penumbra.Mods
if( !SwappedFiles.ContainsKey( swap.Value ) )
{
SwappedFiles[ swap.Key.ToLowerInvariant() ] = swap.Value;
registeredFiles[ swap.Key ] = mod.Meta.Name;
registeredFiles[ swap.Key ] = mod.Meta.Name;
}
else if( registeredFiles.TryGetValue( swap.Key, out var modName ) )
{
@ -154,13 +152,14 @@ namespace Penumbra.Mods
{
var relativeFilePath = file.FullName.Substring( baseDir.Length ).TrimStart( '\\' );
bool doNotAdd = false;
var doNotAdd = false;
HashSet< string > paths;
foreach( var group in mod.Meta.Groups.Select( G => G.Value ) )
{
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;
_plugin.ModManager.Mods.Save();
@ -168,10 +167,14 @@ namespace Penumbra.Mods
}
if( group.Options.Count == 0 )
{
continue;
}
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 )
{
@ -182,15 +185,10 @@ namespace Penumbra.Mods
}
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;
break;
}
doNotAdd = true;
}
}
@ -206,7 +204,9 @@ namespace Penumbra.Mods
}
}
else if( group.Options[ i ].OptionFiles.ContainsKey( relativeFilePath ) )
{
doNotAdd = true;
}
}
break;
@ -215,7 +215,7 @@ namespace Penumbra.Mods
if( !doNotAdd )
{
AddFiles( new() { relativeFilePath.Replace( '\\', '/' ) }, out doNotAdd, file, registeredFiles, mod );
AddFiles( new HashSet< string > { relativeFilePath.Replace( '\\', '/' ) }, out doNotAdd, file, registeredFiles, mod );
}
}
}
@ -229,7 +229,7 @@ namespace Penumbra.Mods
if( !ResolvedFiles.ContainsKey( gamePath ) )
{
ResolvedFiles[ gamePath.ToLowerInvariant() ] = file;
registeredFiles[ gamePath ] = mod.Meta.Name;
registeredFiles[ gamePath ] = mod.Meta.Name;
}
else if( registeredFiles.TryGetValue( gamePath, out var modName ) )
{
@ -246,7 +246,18 @@ namespace Penumbra.Mods
public void DeleteMod( ResourceMod mod )
{
Directory.Delete( mod.ModBasePath.FullName, true );
if( mod?.ModBasePath?.Exists ?? false )
{
try
{
Directory.Delete( mod.ModBasePath.FullName, true );
}
catch( Exception e )
{
PluginLog.Error( $"Could not delete the mod {mod.ModBasePath.Name}:\n{e}" );
}
}
DiscoverMods();
}
@ -267,9 +278,7 @@ namespace Penumbra.Mods
}
public string GetSwappedFilePath( string gameResourcePath )
{
return SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null;
}
=> SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null;
public string ResolveSwappedOrReplacementFilePath( string gameResourcePath )
{

View file

@ -22,7 +22,7 @@ namespace Penumbra.Mods
PluginLog.LogError( "no basepath has been set on {ResourceModName}", Meta.Name );
return;
}
ModFiles.Clear();
// we don't care about any _files_ in the root dir, but any folders should be a game folder/file combo
foreach( var dir in ModBasePath.EnumerateDirectories() )
@ -35,7 +35,7 @@ namespace Penumbra.Mods
// Only add if not in a sub-folder, otherwise it was already added.
//foreach( var pair in Meta.Groups.FileToGameAndGroup )
// if (pair.Key.IndexOfAny(new[]{'/', '\\'}) < 0)
// if (pair.Key.IndexOfAny(new[]{'/', '\\'}) < 0)
// ModFiles.Add( new FileInfo(Path.Combine(ModBasePath.FullName, pair.Key)) );
}

View file

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

View file

@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using Dalamud.Game.Command;
using Dalamud.Plugin;
using EmbedIO;
@ -57,6 +57,7 @@ namespace Penumbra
GameUtils.ReloadPlayerResources();
SettingsInterface = new SettingsInterface( this );
PluginInterface.UiBuilder.OnBuildUi += SettingsInterface.Draw;
PluginDebugTitleStr = $"{Name} - Debug Build";
@ -70,7 +71,7 @@ namespace Penumbra
public void CreateWebServer()
{
var prefix = "http://localhost:42069/";
ShutdownWebServer();
_webServer = new WebServer( o => o
@ -119,14 +120,19 @@ namespace Penumbra
$"Reloaded Penumbra mods. You have {ModManager.Mods.ModSettings.Count} mods, {ModManager.Mods.EnabledMods.Length} of which are enabled."
);
break;
}
case "redraw":
{
if (args.Length > 1)
RefreshActors.RedrawSpecific(PluginInterface.ClientState.Actors, string.Join(" ", args.Skip(1)));
else
RefreshActors.RedrawAll(PluginInterface.ClientState.Actors);
break;
}
case "redraw":
{
if( args.Length > 1 )
{
RefreshActors.RedrawSpecific( PluginInterface.ClientState.Actors, string.Join( " ", args.Skip( 1 ) ) );
}
else
{
RefreshActors.RedrawAll( PluginInterface.ClientState.Actors );
}
break;
}
}
@ -136,4 +142,4 @@ namespace Penumbra
SettingsInterface.FlipVisibility();
}
}
}
}

View file

@ -1,14 +1,14 @@
using System;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Plugin;
using Penumbra.Structs;
using Penumbra.Util;
using FileMode = Penumbra.Structs.FileMode;
using Reloaded.Hooks;
using Reloaded.Hooks.Definitions;
using Reloaded.Hooks.Definitions.X64;
using FileMode = Penumbra.Structs.FileMode;
namespace Penumbra
{
@ -51,7 +51,7 @@ namespace Penumbra
public ResourceLoader( Plugin plugin )
{
Plugin = plugin;
Crc32 = new Crc32();
Crc32 = new Crc32();
}
public unsafe void Init()
@ -71,8 +71,8 @@ namespace Penumbra
scanner.ScanText( "E8 ?? ?? ?? 00 48 8B D8 EB ?? F0 FF 83 ?? ?? 00 00" );
ReadSqpackHook = new Hook< ReadSqpackPrototype >( ReadSqpackHandler, ( long )readSqpackAddress );
GetResourceSyncHook = new Hook< GetResourceSyncPrototype >( GetResourceSyncHandler, ( long )getResourceSyncAddress );
ReadSqpackHook = new Hook< ReadSqpackPrototype >( ReadSqpackHandler, ( long )readSqpackAddress );
GetResourceSyncHook = new Hook< GetResourceSyncPrototype >( GetResourceSyncHandler, ( long )getResourceSyncAddress );
GetResourceAsyncHook = new Hook< GetResourceAsyncPrototype >( GetResourceAsyncHandler, ( long )getResourceAsyncAddress );
ReadFile = Marshal.GetDelegateForFunctionPointer< ReadFilePrototype >( readFileAddress );
@ -127,10 +127,10 @@ namespace Penumbra
if( LogAllFiles )
{
PluginLog.Log( "[GetResourceHandler] {0}", gameFsPath );
}
if (Plugin.Configuration.IsEnabled)
{
}
if( Plugin.Configuration.IsEnabled )
{
var replacementPath = Plugin.ModManager.ResolveSwappedOrReplacementFilePath( gameFsPath );
// path must be < 260 because statically defined array length :(
@ -140,7 +140,7 @@ namespace Penumbra
}
var cleanPath = replacementPath.Replace( '\\', '/' );
var path = Encoding.ASCII.GetBytes( cleanPath );
var path = Encoding.ASCII.GetBytes( cleanPath );
var bPath = stackalloc byte[path.Length + 1];
Marshal.Copy( path, 0, new IntPtr( bPath ), path.Length );
@ -152,9 +152,9 @@ namespace Penumbra
#if DEBUG
PluginLog.Log( "[GetResourceHandler] resolved {GamePath} to {NewPath}", gameFsPath, replacementPath );
#endif
#endif
}
return CallOriginalHandler( isSync, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, isUnknown );
}
@ -192,7 +192,9 @@ namespace Penumbra
public void Enable()
{
if( IsEnabled )
{
return;
}
ReadSqpackHook.Activate();
GetResourceSyncHook.Activate();
@ -208,7 +210,9 @@ namespace Penumbra
public void Disable()
{
if( !IsEnabled )
{
return;
}
ReadSqpackHook.Disable();
GetResourceSyncHook.Disable();
@ -220,7 +224,9 @@ namespace Penumbra
public void Dispose()
{
if( IsEnabled )
{
Disable();
}
// ReadSqpackHook.Disable();
// GetResourceSyncHook.Disable();

View file

@ -1,11 +1,12 @@
namespace Penumbra.Structs
namespace Penumbra.Structs
{
public enum FileMode : uint
{
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?
LoadIndexResource = 0xA, // load index/index2
LoadIndexResource = 0xA, // load index/index2
LoadSqPackResource = 0xB
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,40 +1,49 @@
using System.Collections.Generic;
using ImGuiNET;
using System.Collections.Generic;
using ImGuiNET;
namespace Penumbra.UI
{
public static partial class ImGuiCustom
{
public static bool InputOrText(bool editable, string label, ref string text, uint maxLength)
{
if (editable)
return ResizingTextInput(label, ref text, maxLength);
ImGui.Text(text);
return false;
}
public static bool ResizingTextInput(string label, 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);
if (ret)
_textInputWidths.Remove(id);
return ret;
}
private static (bool, uint) ResizingTextInputIntern(string label, ref string input, uint maxLength)
{
var id = ImGui.GetID(label);
if (!_textInputWidths.TryGetValue(id, out var width))
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();
}
{
public static partial class ImGuiCustom
{
public static bool InputOrText( bool editable, string label, ref string text, uint maxLength )
{
if( editable )
{
return ResizingTextInput( label, ref text, maxLength );
}
ImGui.Text( text );
return false;
}
public static bool ResizingTextInput( string label, 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 );
if( ret )
{
TextInputWidths.Remove( id );
}
return ret;
}
private static (bool, uint) ResizingTextInputIntern( string label, ref string input, uint maxLength )
{
var id = ImGui.GetID( label );
if( !TextInputWidths.TryGetValue( id, out var width ) )
{
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();
}
}

View file

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

View file

@ -2,55 +2,62 @@ using System.Numerics;
using ImGuiNET;
namespace Penumbra.UI
{
public partial class SettingsInterface
{
private class LaunchButton
{
// magic numbers
private const int Padding = 50;
private const int Width = 200;
private const int Height = 45;
private const string MenuButtonsName = "Penumbra Menu Buttons";
private const string MenuButtonLabel = "Manage Mods";
private static readonly Vector2 WindowSize = new(Width, Height);
private static readonly Vector2 WindowPosOffset = new(Padding + Width, Padding + Height);
private readonly ImGuiWindowFlags ButtonFlags = ImGuiWindowFlags.AlwaysAutoResize
| ImGuiWindowFlags.NoBackground
{
public partial class SettingsInterface
{
private class LaunchButton
{
// magic numbers
private const int Padding = 50;
private const int Width = 200;
private const int Height = 45;
private const string MenuButtonsName = "Penumbra Menu Buttons";
private const string MenuButtonLabel = "Manage Mods";
private static readonly Vector2 WindowSize = new( Width, Height );
private static readonly Vector2 WindowPosOffset = new( Padding + Width, Padding + Height );
private const ImGuiWindowFlags ButtonFlags =
ImGuiWindowFlags.AlwaysAutoResize
| ImGuiWindowFlags.NoBackground
| ImGuiWindowFlags.NoDecoration
| ImGuiWindowFlags.NoMove
| ImGuiWindowFlags.NoScrollbar
| ImGuiWindowFlags.NoResize
| ImGuiWindowFlags.NoSavedSettings;
private readonly SettingsInterface _base;
private readonly Dalamud.Game.ClientState.Condition _condition;
public LaunchButton(SettingsInterface ui)
{
_base = ui;
_condition = ui._plugin.PluginInterface.ClientState.Condition;
}
public void Draw()
{
if( !_condition.Any() && !_base._menu.Visible )
| ImGuiWindowFlags.NoSavedSettings;
private readonly SettingsInterface _base;
private readonly Dalamud.Game.ClientState.Condition _condition;
public LaunchButton( SettingsInterface ui )
{
_base = ui;
_condition = ui._plugin.PluginInterface.ClientState.Condition;
}
public void Draw()
{
if( _condition.Any() || _base._menu.Visible )
{
var ss = ImGui.GetIO().DisplaySize;
return;
}
ImGui.SetNextWindowPos( ss - WindowPosOffset, ImGuiCond.Always );
var ss = ImGui.GetIO().DisplaySize;
if( ImGui.Begin(MenuButtonsName, ButtonFlags) )
{
if( ImGui.Button( MenuButtonLabel, WindowSize ) )
_base.FlipVisibility();
ImGui.SetNextWindowPos( ss - WindowPosOffset, ImGuiCond.Always );
ImGui.End();
}
}
}
}
}
if( !ImGui.Begin( MenuButtonsName, ButtonFlags ) )
{
return;
}
if( ImGui.Button( MenuButtonLabel, WindowSize ) )
{
_base.FlipVisibility();
}
ImGui.End();
}
}
}
}

View file

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

View file

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

View file

@ -2,45 +2,47 @@ using System.Numerics;
using ImGuiNET;
namespace Penumbra.UI
{
public partial class SettingsInterface
{
private partial class SettingsMenu
{
private const string PenumbraSettingsLabel = "PenumbraSettings";
{
public partial class SettingsInterface
{
private partial class SettingsMenu
{
private const string PenumbraSettingsLabel = "PenumbraSettings";
private static readonly Vector2 MinSettingsSize = new( 800, 450 );
private static readonly Vector2 MaxSettingsSize = new( 69420, 42069 );
private readonly SettingsInterface _base;
public readonly TabSettings _settingsTab;
public readonly TabImport _importTab;
public readonly TabBrowser _browserTab;
public readonly TabInstalled _installedTab;
public readonly TabEffective _effectiveTab;
public SettingsMenu(SettingsInterface ui)
{
_base = ui;
_settingsTab = new(_base);
_importTab = new(_base);
_browserTab = new();
_installedTab = new(_base);
_effectiveTab = new(_base);
}
private static readonly Vector2 MaxSettingsSize = new( 69420, 42069 );
private readonly SettingsInterface _base;
private readonly TabSettings _settingsTab;
private readonly TabImport _importTab;
private readonly TabBrowser _browserTab;
public readonly TabInstalled InstalledTab;
public readonly TabEffective EffectiveTab;
public SettingsMenu( SettingsInterface ui )
{
_base = ui;
_settingsTab = new TabSettings( _base );
_importTab = new TabImport( _base );
_browserTab = new TabBrowser();
InstalledTab = new TabInstalled( _base );
EffectiveTab = new TabEffective( _base );
}
#if DEBUG
private const bool DefaultVisibility = true;
private const bool DefaultVisibility = true;
#else
private const bool DefaultVisibility = false;
#endif
public bool Visible = DefaultVisibility;
public void Draw()
{
#endif
public bool Visible = DefaultVisibility;
public void Draw()
{
if( !Visible )
return;
{
return;
}
ImGui.SetNextWindowSizeConstraints( MinSettingsSize, MaxSettingsSize );
#if DEBUG
var ret = ImGui.Begin( _base._plugin.PluginDebugTitleStr, ref Visible );
@ -48,25 +50,29 @@ namespace Penumbra.UI
var ret = ImGui.Begin( _base._plugin.Name, ref Visible );
#endif
if( !ret )
{
return;
ImGui.BeginTabBar( PenumbraSettingsLabel );
}
ImGui.BeginTabBar( PenumbraSettingsLabel );
_settingsTab.Draw();
_importTab.Draw();
_importTab.Draw();
if( !_importTab.IsImporting() )
{
_browserTab.Draw();
_installedTab.Draw();
InstalledTab.Draw();
if( _base._plugin.Configuration.ShowAdvanced )
_effectiveTab.Draw();
{
EffectiveTab.Draw();
}
}
ImGui.EndTabBar();
ImGui.End();
}
}
}
ImGui.End();
}
}
}
}

View file

@ -1,22 +1,24 @@
using System.Diagnostics;
using ImGuiNET;
using System.Diagnostics;
using ImGuiNET;
namespace Penumbra.UI
{
public partial class SettingsInterface
{
private class TabBrowser
{
{
public partial class SettingsInterface
{
private class TabBrowser
{
[Conditional( "DEBUG" )]
public void Draw()
{
var ret = ImGui.BeginTabItem( "Available Mods" );
if( !ret )
{
return;
}
ImGui.Text( "woah" );
ImGui.EndTabItem();
}
}
}
}
}
}
}

View file

@ -1,66 +1,70 @@
using System.Linq;
using ImGuiNET;
using Penumbra.Mods;
using System.Linq;
using ImGuiNET;
using Penumbra.Mods;
namespace Penumbra.UI
{
public partial class SettingsInterface
{
private class TabEffective
{
private const string LabelTab = "Effective File List";
private const float TextSizePadding = 5f;
private ModManager _mods;
private (string, string)[] _fileList = null;
private float _maxGamePath = 0f;
public TabEffective(SettingsInterface ui)
{
_mods = ui._plugin.ModManager;
RebuildFileList(ui._plugin.Configuration.ShowAdvanced);
}
public void RebuildFileList(bool advanced)
{
if (advanced)
{
_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;
}
else
{
_fileList = null;
_maxGamePath = 0f;
}
}
private void DrawFileLine((string, string) file)
{
ImGui.Selectable(file.Item2);
ImGui.SameLine();
ImGui.SetCursorPosX(_maxGamePath);
ImGui.TextUnformatted(" <-- ");
ImGui.SameLine();
ImGui.Selectable(file.Item1);
}
{
public partial class SettingsInterface
{
private class TabEffective
{
private const string LabelTab = "Effective File List";
private const float TextSizePadding = 5f;
private readonly ModManager _mods;
private (string, string)[] _fileList;
private float _maxGamePath;
public TabEffective( SettingsInterface ui )
{
_mods = ui._plugin.ModManager;
RebuildFileList( ui._plugin.Configuration.ShowAdvanced );
}
public void RebuildFileList( bool advanced )
{
if( advanced )
{
_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;
}
else
{
_fileList = null;
_maxGamePath = 0f;
}
}
private void DrawFileLine( (string, string) file )
{
ImGui.Selectable( file.Item2 );
ImGui.SameLine();
ImGui.SetCursorPosX( _maxGamePath );
ImGui.TextUnformatted( " <-- " );
ImGui.SameLine();
ImGui.Selectable( file.Item1 );
}
public void Draw()
{
var ret = ImGui.BeginTabItem( LabelTab );
if( !ret )
{
return;
}
if( ImGui.ListBoxHeader( "##effective_files", AutoFillSize ) )
{
foreach( var file in _fileList )
DrawFileLine(file);
foreach( var file in _fileList )
{
DrawFileLine( file );
}
ImGui.ListBoxFooter();
}
ImGui.EndTabItem();
}
}
}
}
}
}
}

View file

@ -1,61 +1,61 @@
using ImGuiNET;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System;
using Penumbra.Importer;
using Dalamud.Plugin;
using System.Numerics;
using System;
using System.IO;
using System.Numerics;
using System.Threading.Tasks;
using System.Windows.Forms;
using Dalamud.Plugin;
using ImGuiNET;
using Penumbra.Importer;
namespace Penumbra.UI
{
public partial class SettingsInterface
{
private class TabImport
{
private const string LabelTab = "Import Mods";
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 LabelFileImportRunning = "Import in progress...";
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 uint ColorRed = 0xFF0000C8;
private static readonly Vector2 ImportBarSize = new( -1, 0 );
private bool _isImportRunning = false;
private bool _hasError = false;
private TexToolsImport _texToolsImport = null!;
private readonly SettingsInterface _base;
public TabImport(SettingsInterface ui) => _base = ui;
public bool IsImporting() => _isImportRunning;
private void RunImportTask()
{
_isImportRunning = true;
{
public partial class SettingsInterface
{
private class TabImport
{
private const string LabelTab = "Import Mods";
private const string LabelImportButton = "Import TexTools Modpacks";
private const string LabelFileDialog = "Pick one or more modpacks.";
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 FailedImport = "One or more of your modpacks failed to import.\nPlease submit a bug report.";
private const uint ColorRed = 0xFF0000C8;
private static readonly Vector2 ImportBarSize = new( -1, 0 );
private bool _isImportRunning = false;
private bool _hasError = false;
private TexToolsImport _texToolsImport = null!;
private readonly SettingsInterface _base;
public TabImport( SettingsInterface ui ) => _base = ui;
public bool IsImporting() => _isImportRunning;
private void RunImportTask()
{
_isImportRunning = true;
Task.Run( async () =>
{
var picker = new OpenFileDialog
{
Multiselect = true,
Filter = FileTypeFilter,
Multiselect = true,
Filter = FileTypeFilter,
CheckFileExists = true,
Title = LabelFileDialog
Title = LabelFileDialog
};
var result = await picker.ShowDialogAsync();
if( result == DialogResult.OK )
{
_hasError = false;
_hasError = false;
foreach( var fileName in picker.FileNames )
{
PluginLog.Log( $"-> {fileName} START");
PluginLog.Log( $"-> {fileName} START" );
try
{
@ -73,71 +73,82 @@ namespace Penumbra.UI
_texToolsImport = null;
_base.ReloadMods();
}
}
_isImportRunning = false;
} );
}
private void DrawImportButton()
{
} );
}
private void DrawImportButton()
{
if( ImGui.Button( LabelImportButton ) )
{
RunImportTask();
}
}
}
}
private void DrawImportProgress()
{
ImGui.Button( LabelFileImportRunning );
if( _texToolsImport != null )
if( _texToolsImport == null )
{
switch( _texToolsImport.State )
{
case ImporterState.None:
break;
case ImporterState.WritingPackToDisk:
ImGui.Text( TooltipModpack1 );
break;
case ImporterState.ExtractingModFiles:
{
var str =
$"{_texToolsImport.CurrentModPack} - {_texToolsImport.CurrentProgress} of {_texToolsImport.TotalProgress} files";
return;
}
ImGui.ProgressBar( _texToolsImport.Progress, ImportBarSize, str );
break;
}
case ImporterState.Done:
break;
default:
throw new ArgumentOutOfRangeException();
switch( _texToolsImport.State )
{
case ImporterState.None:
break;
case ImporterState.WritingPackToDisk:
ImGui.Text( TooltipModpack1 );
break;
case ImporterState.ExtractingModFiles:
{
var str =
$"{_texToolsImport.CurrentModPack} - {_texToolsImport.CurrentProgress} of {_texToolsImport.TotalProgress} files";
ImGui.ProgressBar( _texToolsImport.Progress, ImportBarSize, str );
break;
}
case ImporterState.Done:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void DrawFailedImportMessage()
{
private static void DrawFailedImportMessage()
{
ImGui.PushStyleColor( ImGuiCol.Text, ColorRed );
ImGui.Text( FailedImport );
ImGui.Text( FailedImport );
ImGui.PopStyleColor();
}
}
public void Draw()
{
var ret = ImGui.BeginTabItem( LabelTab );
if( !ret )
{
return;
}
if( !_isImportRunning )
{
DrawImportButton();
}
else
DrawImportProgress();
if (_hasError)
{
DrawImportProgress();
}
if( _hasError )
{
DrawFailedImportMessage();
}
ImGui.EndTabItem();
}
}
}
}
}
}
}

View file

@ -1,53 +1,55 @@
using ImGuiNET;
using ImGuiNET;
namespace Penumbra.UI
{
public partial class SettingsInterface
{
private partial class TabInstalled
{
private const string LabelTab = "Installed Mods";
private readonly SettingsInterface _base;
public readonly Selector _selector;
public readonly ModPanel _modPanel;
public TabInstalled(SettingsInterface ui)
{
_base = ui;
_selector = new(_base);
_modPanel = new(_base, _selector);
}
private void DrawNoModsAvailable()
{
ImGui.Text( "You don't have any mods :(" );
ImGuiCustom.VerticalDistance(20f);
{
public partial class SettingsInterface
{
private class TabInstalled
{
private const string LabelTab = "Installed Mods";
private readonly SettingsInterface _base;
public readonly Selector Selector;
public readonly ModPanel ModPanel;
public TabInstalled( SettingsInterface ui )
{
_base = ui;
Selector = new Selector( _base );
ModPanel = new ModPanel( _base, Selector );
}
private static void DrawNoModsAvailable()
{
ImGui.Text( "You don't have any mods :(" );
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( "For example: D:/ffxiv/mods/" );
ImGui.Text( "And pasting that path into the settings tab and clicking the 'Rediscover Mods' button." );
ImGui.Text( "You can return to this tab once you've done that." );
}
public void Draw()
{
var ret = ImGui.BeginTabItem( LabelTab );
}
public void Draw()
{
var ret = ImGui.BeginTabItem( LabelTab );
if( !ret )
return;
if (_base._plugin.ModManager.Mods != null)
{
_selector.Draw();
ImGui.SameLine();
_modPanel.Draw();
}
else
DrawNoModsAvailable();
ImGui.EndTabItem();
return;
}
}
}
{
return;
}
if( _base._plugin.ModManager.Mods != null )
{
Selector.Draw();
ImGui.SameLine();
ModPanel.Draw();
}
else
{
DrawNoModsAvailable();
}
ImGui.EndTabItem();
}
}
}
}

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,154 +1,160 @@
using ImGuiNET;
using Dalamud.Plugin;
using System;
using System.Numerics;
using System.Diagnostics;
using Penumbra.Models;
using System;
using System.Diagnostics;
using System.Numerics;
using Dalamud.Plugin;
using ImGuiNET;
using Penumbra.Models;
namespace Penumbra.UI
{
public partial class SettingsInterface
{
private class ModPanel
{
private const string LabelModPanel = "selectedModInfo";
private const string LabelEditName = "##editName";
private const string LabelEditVersion = "##editVersion";
private const string LabelEditAuthor = "##editAuthor";
private const string LabelEditWebsite = "##editWebsite";
private const string ButtonOpenWebsite = "Open Website";
private const string LabelModEnabled = "Enabled";
private const string LabelEditingEnabled = "Enable Editing";
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 TooltipEditJson = "Open the JSON configuration file in your default application for .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 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 float HeaderLineDistance = 10f;
private static readonly Vector4 GreyColor = new( 1f, 1f, 1f, 0.66f );
private readonly SettingsInterface _base;
private readonly Selector _selector;
public readonly PluginDetails _details;
private bool _editMode = false;
private string _currentWebsite;
private bool _validWebsite;
public ModPanel(SettingsInterface ui, Selector s)
{
_base = ui;
_selector = s;
_details = new(_base, _selector);
_currentWebsite = Meta?.Website;
}
private ModInfo Mod { get{ return _selector.Mod(); } }
private ModMeta Meta { get{ return Mod?.Mod.Meta; } }
#region Header Line Functions
private void DrawName()
{
var name = Meta.Name;
if (ImGuiCustom.InputOrText(_editMode, LabelEditName, ref name, 64)
&& name.Length > 0 && name != Meta.Name)
{
Meta.Name = name;
_selector.SaveCurrentMod();
}
}
private void DrawVersion()
{
if (_editMode)
{
ImGui.BeginGroup();
ImGui.Text("(Version ");
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ZeroVector);
ImGui.SameLine();
var version = Meta.Version ?? "";
if (ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16)
&& version != Meta.Version)
{
Meta.Version = version.Length > 0 ? version : null;
_selector.SaveCurrentMod();
}
ImGui.SameLine();
ImGui.Text(")");
ImGui.PopStyleVar();
ImGui.EndGroup();
}
else if ((Meta.Version?.Length ?? 0) > 0)
{
ImGui.Text( $"(Version {Meta.Version})" );
}
}
private void DrawAuthor()
{
ImGui.BeginGroup();
ImGui.TextColored( GreyColor, "by" );
ImGui.SameLine();
var author = Meta.Author ?? "";
if (ImGuiCustom.InputOrText(_editMode, LabelEditAuthor, ref author, 64)
&& author != Meta.Author)
{
Meta.Author = author.Length > 0 ? author : null;
_selector.SaveCurrentMod();
}
ImGui.EndGroup();
}
private void DrawWebsite()
{
ImGui.BeginGroup();
if (_editMode)
{
ImGui.TextColored( GreyColor, "from" );
ImGui.SameLine();
var website = Meta.Website ?? "";
if (ImGuiCustom.ResizingTextInput(LabelEditWebsite, ref website, 512)
&& website != Meta.Website)
{
Meta.Website = website.Length > 0 ? website : null;
_selector.SaveCurrentMod();
}
}
else if (( Meta.Website?.Length ?? 0 ) > 0)
{
if (_currentWebsite != Meta.Website)
{
_currentWebsite = Meta.Website;
_validWebsite = Uri.TryCreate( Meta.Website, UriKind.Absolute, out var uriResult )
&& ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp );
{
public partial class SettingsInterface
{
private class ModPanel
{
private const string LabelModPanel = "selectedModInfo";
private const string LabelEditName = "##editName";
private const string LabelEditVersion = "##editVersion";
private const string LabelEditAuthor = "##editAuthor";
private const string LabelEditWebsite = "##editWebsite";
private const string LabelModEnabled = "Enabled";
private const string LabelEditingEnabled = "Enable Editing";
private const string ButtonOpenWebsite = "Open Website";
private const string ButtonOpenModFolder = "Open Mod Folder";
private const string ButtonEditJson = "Edit JSON";
private const string ButtonReloadJson = "Reload JSON";
private const string ButtonDeduplicate = "Deduplicate";
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 static readonly Vector4 GreyColor = new( 1f, 1f, 1f, 0.66f );
private readonly SettingsInterface _base;
private readonly Selector _selector;
public readonly PluginDetails Details;
private bool _editMode;
private string _currentWebsite;
private bool _validWebsite;
public ModPanel( SettingsInterface ui, Selector s )
{
_base = ui;
_selector = s;
Details = new PluginDetails( _base, _selector );
_currentWebsite = Meta?.Website;
}
private ModInfo Mod => _selector.Mod();
private ModMeta Meta => Mod?.Mod.Meta;
private void DrawName()
{
var name = Meta.Name;
if( ImGuiCustom.InputOrText( _editMode, LabelEditName, ref name, 64 )
&& name.Length > 0 && name != Meta.Name )
{
Meta.Name = name;
_selector.SaveCurrentMod();
}
}
private void DrawVersion()
{
if( _editMode )
{
ImGui.BeginGroup();
ImGui.Text( "(Version " );
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
ImGui.SameLine();
var version = Meta.Version ?? "";
if( ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16 )
&& version != Meta.Version )
{
Meta.Version = version.Length > 0 ? version : null;
_selector.SaveCurrentMod();
}
ImGui.SameLine();
ImGui.Text( ")" );
ImGui.PopStyleVar();
ImGui.EndGroup();
}
else if( ( Meta.Version?.Length ?? 0 ) > 0 )
{
ImGui.Text( $"(Version {Meta.Version})" );
}
}
private void DrawAuthor()
{
ImGui.BeginGroup();
ImGui.TextColored( GreyColor, "by" );
ImGui.SameLine();
var author = Meta.Author ?? "";
if( ImGuiCustom.InputOrText( _editMode, LabelEditAuthor, ref author, 64 )
&& author != Meta.Author )
{
Meta.Author = author.Length > 0 ? author : null;
_selector.SaveCurrentMod();
}
ImGui.EndGroup();
}
private void DrawWebsite()
{
ImGui.BeginGroup();
if( _editMode )
{
ImGui.TextColored( GreyColor, "from" );
ImGui.SameLine();
var website = Meta.Website ?? "";
if( ImGuiCustom.ResizingTextInput( LabelEditWebsite, ref website, 512 )
&& website != Meta.Website )
{
Meta.Website = website.Length > 0 ? website : null;
_selector.SaveCurrentMod();
}
}
else if( ( Meta.Website?.Length ?? 0 ) > 0 )
{
if( _currentWebsite != Meta.Website )
{
_currentWebsite = Meta.Website;
_validWebsite = Uri.TryCreate( Meta.Website, UriKind.Absolute, out var uriResult )
&& ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp );
}
if( _validWebsite )
{
if( ImGui.SmallButton( ButtonOpenWebsite ) )
{
try
if( ImGui.SmallButton( ButtonOpenWebsite ) )
{
try
{
var process = new ProcessStartInfo(Meta.Website)
{
UseShellExecute = true
};
Process.Start(process);
}
catch(System.ComponentModel.Win32Exception)
{
// Do nothing.
}
var process = new ProcessStartInfo( Meta.Website )
{
UseShellExecute = true
};
Process.Start( process );
}
catch( System.ComponentModel.Win32Exception )
{
// Do nothing.
}
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( Meta.Website );
}
}
else
{
@ -156,134 +162,148 @@ namespace Penumbra.UI
ImGui.SameLine();
ImGui.Text( Meta.Website );
}
}
}
ImGui.EndGroup();
}
private void DrawHeaderLine()
{
DrawName();
ImGui.SameLine();
DrawVersion();
ImGui.SameLine();
DrawAuthor();
}
private void DrawHeaderLine()
{
DrawName();
ImGui.SameLine();
DrawWebsite();
}
#endregion
#region Enabled Checkmarks
private void DrawEnabledMark()
{
DrawVersion();
ImGui.SameLine();
DrawAuthor();
ImGui.SameLine();
DrawWebsite();
}
private void DrawEnabledMark()
{
var enabled = Mod.Enabled;
if( ImGui.Checkbox( LabelModEnabled, ref enabled ) )
{
Mod.Enabled = enabled;
_base._plugin.ModManager.Mods.Save();
_base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced);
}
}
private void DrawEditableMark()
{
ImGui.Checkbox( LabelEditingEnabled, ref _editMode);
}
#endregion
#region Edit Line Functions
private void DrawOpenModFolderButton()
{
_base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
}
}
private void DrawEditableMark()
{
ImGui.Checkbox( LabelEditingEnabled, ref _editMode );
}
private void DrawOpenModFolderButton()
{
if( ImGui.Button( ButtonOpenModFolder ) )
{
Process.Start( Mod.Mod.ModBasePath.FullName );
}
}
if( ImGui.IsItemHovered() )
ImGui.SetTooltip( TooltipOpenModFolder );
}
private void DrawEditJsonButton()
{
{
ImGui.SetTooltip( TooltipOpenModFolder );
}
}
private void DrawEditJsonButton()
{
if( ImGui.Button( ButtonEditJson ) )
{
Process.Start( _selector.SaveCurrentMod() );
}
}
if( ImGui.IsItemHovered() )
ImGui.SetTooltip( TooltipEditJson );
}
private void DrawReloadJsonButton()
{
{
ImGui.SetTooltip( TooltipEditJson );
}
}
private void DrawReloadJsonButton()
{
if( ImGui.Button( ButtonReloadJson ) )
{
_selector.ReloadCurrentMod();
}
if( ImGui.IsItemHovered() )
ImGui.SetTooltip( TooltipReloadJson );
}
private void DrawDeduplicateButton()
{
if( ImGui.Button( ButtonDeduplicate ) )
{
new Deduplicator(Mod.Mod.ModBasePath, Meta).Run();
_selector.SaveCurrentMod();
Mod.Mod.RefreshModFiles();
_base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced);
}
if( ImGui.IsItemHovered() )
ImGui.SetTooltip( TooltipDeduplicate );
}
private void DrawEditLine()
{
DrawOpenModFolderButton();
ImGui.SameLine();
DrawEditJsonButton();
ImGui.SameLine();
DrawReloadJsonButton();
ImGui.SameLine();
DrawDeduplicateButton();
}
#endregion
public void Draw()
{
if( Mod != null )
{
try
{
var ret = ImGui.BeginChild( LabelModPanel, AutoFillSize, true );
if (!ret)
return;
DrawHeaderLine();
// Next line with fixed distance.
ImGuiCustom.VerticalDistance(HeaderLineDistance);
DrawEnabledMark();
if (_base._plugin.Configuration.ShowAdvanced)
{
ImGui.SameLine();
DrawEditableMark();
}
// Next line, if editable.
if (_editMode)
DrawEditLine();
_details.Draw(_editMode);
ImGui.EndChild();
}
catch( Exception ex )
{
PluginLog.LogError( ex, "fuck" );
}
}
}
}
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipReloadJson );
}
}
private void DrawDeduplicateButton()
{
if( ImGui.Button( ButtonDeduplicate ) )
{
new Deduplicator( Mod.Mod.ModBasePath, Meta ).Run();
_selector.SaveCurrentMod();
Mod.Mod.RefreshModFiles();
_base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipDeduplicate );
}
}
private void DrawEditLine()
{
DrawOpenModFolderButton();
ImGui.SameLine();
DrawEditJsonButton();
ImGui.SameLine();
DrawReloadJsonButton();
ImGui.SameLine();
DrawDeduplicateButton();
}
public void Draw()
{
if( Mod == null )
{
return;
}
try
{
var ret = ImGui.BeginChild( LabelModPanel, AutoFillSize, true );
if( !ret )
{
return;
}
DrawHeaderLine();
// Next line with fixed distance.
ImGuiCustom.VerticalDistance( HeaderLineDistance );
DrawEnabledMark();
if( _base._plugin.Configuration.ShowAdvanced )
{
ImGui.SameLine();
DrawEditableMark();
}
// Next line, if editable.
if( _editMode )
{
DrawEditLine();
}
Details.Draw( _editMode );
ImGui.EndChild();
}
catch( Exception ex )
{
PluginLog.LogError( ex, "fuck" );
}
}
}
}
}

View file

@ -1,55 +1,67 @@
using System.Numerics;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
using ImGuiNET;
using Penumbra.Mods;
using Penumbra.Models;
using Dalamud.Interface;
using System.IO;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using ImGuiNET;
using Newtonsoft.Json;
using Penumbra.Models;
using Penumbra.Mods;
namespace Penumbra.UI
{
public partial class SettingsInterface
{
private class Selector
{
private const string LabelSelectorList = "##availableModList";
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 TooltipDelete = "Delete the selected mod";
private const string TooltipAdd = "Add an empty mod";
private const string DialogDeleteMod = "PenumbraDeleteMod";
private const string ButtonYesDelete = "Yes, delete it";
private const string ButtonNoDelete = "No, keep it";
private const float SelectorPanelWidth = 240f;
private const uint DisabledModColor = 0xFF666666;
private const uint ConflictingModColor = 0xFFAAAAFF;
private static readonly Vector2 SelectorButtonSizes = new(60, 0);
private static readonly string ArrowUpString = FontAwesomeIcon.ArrowUp.ToIconString();
private static readonly string ArrowDownString = FontAwesomeIcon.ArrowDown.ToIconString();
private readonly SettingsInterface _base;
private ModCollection Mods{ get{ return _base._plugin.ModManager.Mods; } }
private ModInfo _mod = null;
private int _index = 0;
private int? _deleteIndex = null;
public Selector(SettingsInterface ui)
{
_base = ui;
}
private void DrawPriorityChangeButton(string iconString, bool up, int unavailableWhen)
{
{
public partial class SettingsInterface
{
private class Selector
{
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 TooltipMoveUp = "Move the selected mod up in priority";
private const string TooltipDelete = "Delete the selected mod";
private const string TooltipAdd = "Add an empty mod";
private const string DialogDeleteMod = "PenumbraDeleteMod";
private const string ButtonYesDelete = "Yes, delete it";
private const string ButtonNoDelete = "No, keep it";
private const float SelectorPanelWidth = 240f;
private const uint DisabledModColor = 0xFF666666;
private const uint ConflictingModColor = 0xFFAAAAFF;
private static readonly Vector2 SelectorButtonSizes = new( 60, 0 );
private static readonly string ArrowUpString = FontAwesomeIcon.ArrowUp.ToIconString();
private static readonly string ArrowDownString = FontAwesomeIcon.ArrowDown.ToIconString();
private readonly SettingsInterface _base;
private ModCollection Mods => _base._plugin.ModManager.Mods;
private ModInfo _mod;
private int _index;
private int? _deleteIndex;
private string _modFilter = "";
private string[] _modNamesLower;
public Selector( SettingsInterface ui )
{
_base = ui;
ResetModNamesLower();
}
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 );
if( _index != unavailableWhen )
{
{
if( ImGui.Button( iconString, SelectorButtonSizes ) )
{
SetSelection(_index);
{
SetSelection( _index );
_base._plugin.ModManager.ChangeModPriority( _mod, up );
_modNamesLower.Swap( _index, _index + ( up ? 1 : -1 ) );
_index += up ? 1 : -1;
}
}
@ -60,18 +72,18 @@ namespace Penumbra.UI
ImGui.PopStyleVar();
}
ImGui.PopFont();
ImGui.PopFont();
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip(
_base._plugin.Configuration.InvertModListOrder ^ up ? TooltipMoveDown : TooltipMoveUp
);
}
}
private void DrawModTrashButton()
{
}
}
private void DrawModTrashButton()
{
ImGui.PushFont( UiBuilder.IconFont );
if( ImGui.Button( FontAwesomeIcon.Trash.ToIconString(), SelectorButtonSizes ) )
@ -79,57 +91,83 @@ namespace Penumbra.UI
_deleteIndex = _index;
}
ImGui.PopFont();
ImGui.PopFont();
if( ImGui.IsItemHovered() )
ImGui.SetTooltip( TooltipDelete );
}
private void DrawModAddButton()
{
{
ImGui.SetTooltip( TooltipDelete );
}
}
private static void DrawModAddButton()
{
ImGui.PushFont( UiBuilder.IconFont );
if( ImGui.Button( FontAwesomeIcon.Plus.ToIconString(), SelectorButtonSizes ) )
{
{
// Do nothing. YEAH. #TODO.
}
ImGui.PopFont();
ImGui.PopFont();
if( ImGui.IsItemHovered() )
ImGui.SetTooltip( TooltipAdd );
}
private void DrawModsSelectorButtons()
{
{
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()
{
// Selector controls
ImGui.PushStyleVar( ImGuiStyleVar.WindowPadding, ZeroVector );
ImGui.PushStyleVar( ImGuiStyleVar.FrameRounding, 0 );
DrawPriorityChangeButton(ArrowUpString, false, 0);
ImGui.SameLine();
DrawPriorityChangeButton(ArrowDownString, true, Mods?.ModSettings.Count - 1 ?? 0);
ImGui.SameLine();
DrawModTrashButton();
DrawPriorityChangeButton( ArrowUpString, false, 0 );
ImGui.SameLine();
DrawPriorityChangeButton( ArrowDownString, true, Mods?.ModSettings.Count - 1 ?? 0 );
ImGui.SameLine();
DrawModTrashButton();
ImGui.SameLine();
DrawModAddButton();
ImGui.PopStyleVar( 3 );
}
void DrawDeleteModal()
}
private void DrawDeleteModal()
{
if( _deleteIndex != null )
ImGui.OpenPopup( DialogDeleteMod );
if( _deleteIndex == null )
{
return;
}
ImGui.OpenPopup( DialogDeleteMod );
var ret = ImGui.BeginPopupModal( DialogDeleteMod );
if( !ret )
{
return;
}
if( _mod?.Mod == null )
{
ImGui.CloseCurrentPopup();
ImGui.EndPopup();
return;
}
ImGui.Text( "Are you sure you want to delete the following mod:" );
@ -140,7 +178,7 @@ namespace Penumbra.UI
{
ImGui.CloseCurrentPopup();
_base._plugin.ModManager.DeleteMod( _mod.Mod );
ClearSelection();
ClearSelection();
_base.ReloadMods();
}
@ -153,24 +191,33 @@ namespace Penumbra.UI
}
ImGui.EndPopup();
}
}
public void Draw()
{
if (Mods == null)
return;
{
if( Mods == null )
{
return;
}
// Selector pane
ImGui.BeginGroup();
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
DrawModsSelectorFilter();
// 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++ )
{
var settings = Mods.ModSettings[ modIndex ];
var modName = settings.Mod.Meta.Name;
if( _modFilter.Length > 0 && !_modNamesLower[ modIndex ].Contains( _modFilter ) )
{
continue;
}
var changedColour = false;
if( !settings.Enabled )
@ -186,51 +233,65 @@ namespace Penumbra.UI
#if DEBUG
var selected = ImGui.Selectable(
$"id={modIndex} {settings.Mod.Meta.Name}",
$"id={modIndex} {modName}",
modIndex == _index
);
#else
var selected = ImGui.Selectable( settings.Mod.Meta.Name, modIndex == _index );
var selected = ImGui.Selectable( modName, modIndex == _index );
#endif
if( changedColour )
{
ImGui.PopStyleColor();
}
if( selected )
SetSelection(modIndex, settings);
{
SetSelection( modIndex, settings );
}
}
ImGui.EndChild();
DrawModsSelectorButtons();
ImGui.EndGroup();
DrawDeleteModal();
}
public ModInfo Mod() => _mod;
private void SetSelection(int idx, ModInfo info)
{
_mod = info;
if (idx != _index)
_base._menu._installedTab._modPanel._details.ResetState();
_index = idx;
_deleteIndex = null;
}
public void SetSelection(int idx)
{
if (idx >= (Mods?.ModSettings?.Count ?? 0))
idx = -1;
if (idx < 0)
SetSelection(0, null);
else
SetSelection(idx, Mods.ModSettings[idx]);
}
public void ClearSelection() => SetSelection(-1);
ImGui.EndChild();
DrawModsSelectorButtons();
ImGui.EndGroup();
DrawDeleteModal();
}
public ModInfo Mod() => _mod;
private void SetSelection( int idx, ModInfo info )
{
_mod = info;
if( idx != _index )
{
_base._menu.InstalledTab.ModPanel.Details.ResetState();
}
_index = idx;
_deleteIndex = null;
}
private void SetSelection( int idx )
{
if( idx >= ( Mods?.ModSettings?.Count ?? 0 ) )
{
idx = -1;
}
if( idx < 0 )
{
SetSelection( 0, null );
}
else
{
SetSelection( idx, Mods.ModSettings[ idx ] );
}
}
public void ClearSelection() => SetSelection( -1 );
public void SelectModByName( string name )
{
for( var modIndex = 0; modIndex < Mods.ModSettings.Count; modIndex++ )
@ -238,40 +299,44 @@ namespace Penumbra.UI
var mod = Mods.ModSettings[ modIndex ];
if( mod.Mod.Meta.Name != name )
{
continue;
SetSelection(modIndex, mod);
}
SetSelection( modIndex, mod );
return;
}
}
private string GetCurrentModMetaFile()
{
if( _mod == null )
return "";
return Path.Combine( _mod.Mod.ModBasePath.FullName, "meta.json" );
}
public void ReloadCurrentMod()
{
var metaPath = GetCurrentModMetaFile();
if (metaPath.Length > 0 && File.Exists(metaPath))
{
_mod.Mod.Meta = ModMeta.LoadFromFile(metaPath) ?? _mod.Mod.Meta;
_base._menu._installedTab._modPanel._details.ResetState();
}
_mod.Mod.RefreshModFiles();
_base._plugin.ModManager.CalculateEffectiveFileList();
}
public string SaveCurrentMod()
{
var metaPath = GetCurrentModMetaFile();
if (metaPath.Length > 0)
File.WriteAllText( metaPath, JsonConvert.SerializeObject( _mod.Mod.Meta, Formatting.Indented ) );
_base._menu._installedTab._modPanel._details.ResetState();
return metaPath;
}
}
}
}
private string GetCurrentModMetaFile()
=> _mod == null ? "" : Path.Combine( _mod.Mod.ModBasePath.FullName, "meta.json" );
public void ReloadCurrentMod()
{
var metaPath = GetCurrentModMetaFile();
if( metaPath.Length > 0 && File.Exists( metaPath ) )
{
_mod.Mod.Meta = ModMeta.LoadFromFile( metaPath ) ?? _mod.Mod.Meta;
_base._menu.InstalledTab.ModPanel.Details.ResetState();
}
_mod.Mod.RefreshModFiles();
_base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
ResetModNamesLower();
}
public string SaveCurrentMod()
{
var metaPath = GetCurrentModMetaFile();
if( metaPath.Length > 0 )
{
File.WriteAllText( metaPath, JsonConvert.SerializeObject( _mod.Mod.Meta, Formatting.Indented ) );
}
_base._menu.InstalledTab.ModPanel.Details.ResetState();
return metaPath;
}
}
}
}

View file

@ -1,174 +1,184 @@
using System.Diagnostics;
using ImGuiNET;
using System.Diagnostics;
using ImGuiNET;
namespace Penumbra.UI
{
public partial class SettingsInterface
{
private class TabSettings
{
private const string LabelTab = "Settings";
private const string LabelRootFolder = "Root Folder";
private const string LabelRediscoverButton = "Rediscover Mods";
private const string LabelOpenFolder = "Open Mods Folder";
private const string LabelEnabled = "Enable Mods";
private const string LabelInvertModOrder = "Invert mod load order (mods are loaded bottom up)";
private const string LabelShowAdvanced = "Show Advanced Settings";
private const string LabelLogLoadedFiles = "Log all loaded files";
private const string LabelDisableNotifications = "Disable filesystem change notifications";
private const string LabelEnableHttpApi = "Enable HTTP API";
private const string LabelReloadResource = "Reload Player Resource";
private readonly SettingsInterface _base;
private readonly Configuration _config;
private bool _configChanged;
public TabSettings(SettingsInterface ui)
{
_base = ui;
_config = _base._plugin.Configuration;
_configChanged = false;
}
private void DrawRootFolder()
{
var basePath = _config.CurrentCollection;
{
public partial class SettingsInterface
{
private class TabSettings
{
private const string LabelTab = "Settings";
private const string LabelRootFolder = "Root Folder";
private const string LabelRediscoverButton = "Rediscover Mods";
private const string LabelOpenFolder = "Open Mods Folder";
private const string LabelEnabled = "Enable Mods";
private const string LabelInvertModOrder = "Invert mod load order (mods are loaded bottom up)";
private const string LabelShowAdvanced = "Show Advanced Settings";
private const string LabelLogLoadedFiles = "Log all loaded files";
private const string LabelDisableNotifications = "Disable filesystem change notifications";
private const string LabelEnableHttpApi = "Enable HTTP API";
private const string LabelReloadResource = "Reload Player Resource";
private readonly SettingsInterface _base;
private readonly Configuration _config;
private bool _configChanged;
public TabSettings( SettingsInterface ui )
{
_base = ui;
_config = _base._plugin.Configuration;
_configChanged = false;
}
private void DrawRootFolder()
{
var basePath = _config.CurrentCollection;
if( ImGui.InputText( LabelRootFolder, ref basePath, 255 ) && _config.CurrentCollection != basePath )
{
_config.CurrentCollection = basePath;
_configChanged = true;
}
}
private void DrawRediscoverButton()
{
_config.CurrentCollection = basePath;
_configChanged = true;
}
}
private void DrawRediscoverButton()
{
if( ImGui.Button( LabelRediscoverButton ) )
{
_base.ReloadMods();
_base._menu._installedTab._selector.ClearSelection();
}
}
private void DrawOpenModsButton()
{
_base._menu.InstalledTab.Selector.ClearSelection();
}
}
private void DrawOpenModsButton()
{
if( ImGui.Button( LabelOpenFolder ) )
{
Process.Start( _config.CurrentCollection );
}
}
private void DrawEnabledBox()
{
var enabled = _config.IsEnabled;
}
}
private void DrawEnabledBox()
{
var enabled = _config.IsEnabled;
if( ImGui.Checkbox( LabelEnabled, ref enabled ) )
{
_config.IsEnabled = enabled;
_configChanged = true;
RefreshActors.RedrawAll(_base._plugin.PluginInterface.ClientState.Actors);
}
}
private void DrawInvertModOrderBox()
{
_configChanged = true;
Game.RefreshActors.RedrawAll( _base._plugin.PluginInterface.ClientState.Actors );
}
}
private void DrawInvertModOrderBox()
{
var invertOrder = _config.InvertModListOrder;
if( ImGui.Checkbox( LabelInvertModOrder, ref invertOrder ) )
{
_config.InvertModListOrder = invertOrder;
_base.ReloadMods();
_base.ReloadMods();
_configChanged = true;
}
}
private void DrawShowAdvancedBox()
{
}
}
private void DrawShowAdvancedBox()
{
var showAdvanced = _config.ShowAdvanced;
if( ImGui.Checkbox( LabelShowAdvanced, ref showAdvanced ) )
{
_config.ShowAdvanced = showAdvanced;
_configChanged = true;
_base._menu._effectiveTab.RebuildFileList(showAdvanced);
}
}
private void DrawLogLoadedFilesBox()
{
_config.ShowAdvanced = showAdvanced;
_configChanged = true;
_base._menu.EffectiveTab.RebuildFileList( showAdvanced );
}
}
private void DrawLogLoadedFilesBox()
{
if( _base._plugin.ResourceLoader != null )
ImGui.Checkbox( LabelLogLoadedFiles, ref _base._plugin.ResourceLoader.LogAllFiles );
}
private void DrawDisableNotificationsBox()
{
{
ImGui.Checkbox( LabelLogLoadedFiles, ref _base._plugin.ResourceLoader.LogAllFiles );
}
}
private void DrawDisableNotificationsBox()
{
var fswatch = _config.DisableFileSystemNotifications;
if( ImGui.Checkbox( LabelDisableNotifications, ref fswatch ) )
{
_config.DisableFileSystemNotifications = fswatch;
_configChanged = true;
}
}
private void DrawEnableHttpApiBox()
{
_configChanged = true;
}
}
private void DrawEnableHttpApiBox()
{
var http = _config.EnableHttpApi;
if( ImGui.Checkbox( LabelEnableHttpApi, ref http ) )
{
if( http )
{
_base._plugin.CreateWebServer();
}
else
{
_base._plugin.ShutdownWebServer();
}
_config.EnableHttpApi = http;
_configChanged = true;
}
}
private void DrawReloadResourceButton()
{
_configChanged = true;
}
}
private void DrawReloadResourceButton()
{
if( ImGui.Button( LabelReloadResource ) )
{
_base._plugin.GameUtils.ReloadPlayerResources();
}
}
private void DrawAdvancedSettings()
{
DrawLogLoadedFilesBox();
}
}
private void DrawAdvancedSettings()
{
DrawLogLoadedFilesBox();
DrawDisableNotificationsBox();
DrawEnableHttpApiBox();
DrawReloadResourceButton();
}
public void Draw()
{
DrawEnableHttpApiBox();
DrawReloadResourceButton();
}
public void Draw()
{
var ret = ImGui.BeginTabItem( LabelTab );
if( !ret )
return;
{
return;
}
DrawRootFolder();
DrawRediscoverButton();
ImGui.SameLine();
ImGui.SameLine();
DrawOpenModsButton();
ImGuiCustom.VerticalDistance(DefaultVerticalSpace);
DrawEnabledBox();
ImGuiCustom.VerticalDistance(DefaultVerticalSpace);
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawEnabledBox();
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawInvertModOrderBox();
ImGuiCustom.VerticalDistance(DefaultVerticalSpace);
DrawShowAdvancedBox();
if( _config.ShowAdvanced )
DrawAdvancedSettings();
if( _configChanged )
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawShowAdvancedBox();
if( _config.ShowAdvanced )
{
_config.Save();
_configChanged = false;
DrawAdvancedSettings();
}
ImGui.EndTabItem();
}
}
}
if( _configChanged )
{
_config.Save();
_configChanged = false;
}
ImGui.EndTabItem();
}
}
}
}

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;
namespace Penumbra.Util
@ -15,7 +15,9 @@ namespace Penumbra.Util
{
var k = ( uint )i;
for( var j = 0; j < 8; j++ )
{
k = ( k & 1 ) != 0 ? ( k >> 1 ) ^ Poly : k >> 1;
}
return k;
} ).ToArray();
@ -40,14 +42,16 @@ namespace Penumbra.Util
public void Update( byte[] data )
{
foreach( var b in data )
{
Update( b );
}
}
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public void Update( byte b )
{
_crc32 = CrcArray[ ( _crc32 ^ b ) & 0xFF ] ^
( ( _crc32 >> 8 ) & 0x00FFFFFF );
( ( _crc32 >> 8 ) & 0x00FFFFFF );
}
}
}

View file

@ -1,55 +1,48 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert( Type objectType )
{
return (objectType == typeof(HashSet<T>));
}
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
var token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<HashSet<T>>();
}
return new HashSet<T>{ token.ToObject<T>() };
}
public override bool CanWrite => false;
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
{
throw new NotImplementedException();
}
}
public class DictSingleOrArrayConverter<T,U> : JsonConverter
{
public override bool CanConvert( Type objectType )
{
return (objectType == typeof(Dictionary<T, HashSet<U>>));
}
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
var token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<HashSet<T>>();
}
return new HashSet<T>{ token.ToObject<T>() };
}
public override bool CanWrite => false;
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
{
throw new NotImplementedException();
}
}
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public class SingleOrArrayConverter< T > : JsonConverter
{
public override bool CanConvert( Type objectType ) => objectType == typeof( HashSet< T > );
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
var token = JToken.Load( reader );
return token.Type == JTokenType.Array
? token.ToObject< HashSet< T > >()
: new HashSet< T > { token.ToObject< T >() };
}
public override bool CanWrite => false;
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
{
throw new NotImplementedException();
}
}
public class DictSingleOrArrayConverter< T, U > : JsonConverter
{
public override bool CanConvert( Type objectType ) => objectType == typeof( Dictionary< T, HashSet< U > > );
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
var token = JToken.Load( reader );
if( token.Type == JTokenType.Array )
{
return token.ToObject< HashSet< T > >();
}
return new HashSet< T > { token.ToObject< T >() };
}
public override bool CanWrite => false;
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
{
throw new NotImplementedException();
}
}

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 ) );
}
}