From 2f9402ae5fa845893e9dec701a9a719a349a7b11 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 15 Feb 2021 12:35:14 +0100 Subject: [PATCH 01/14] Make import of files with erroneous extension a bit more robust. --- Penumbra/Importer/TexToolsImport.cs | 85 +++++++++++++++------------ Penumbra/Util/StringPathExtensions.cs | 18 ++++++ 2 files changed, 65 insertions(+), 38 deletions(-) create mode 100644 Penumbra/Util/StringPathExtensions.cs diff --git a/Penumbra/Importer/TexToolsImport.cs b/Penumbra/Importer/TexToolsImport.cs index 7d587314..cd322d6b 100644 --- a/Penumbra/Importer/TexToolsImport.cs +++ b/Penumbra/Importer/TexToolsImport.cs @@ -53,14 +53,11 @@ namespace Penumbra.Importer switch( modPackFile.Extension ) { case ".ttmp": - ImportV1ModPack( modPackFile ); - break; - case ".ttmp2": - ImportV2ModPack( modPackFile ); - break; - - default: + VerifyVersionAndImport(modPackFile); + break; + + default: throw new ArgumentException( $"Unrecognized modpack format: {modPackFile.Extension}", nameof(modPackFile) ); } @@ -88,15 +85,33 @@ 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 ); @@ -129,33 +144,26 @@ 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 { @@ -179,12 +187,11 @@ 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 @@ -202,7 +209,7 @@ namespace Penumbra.Importer var newModFolder = new DirectoryInfo( Path.Combine( _outDirectory.FullName, - Path.GetFileNameWithoutExtension( modList.Name ) + Path.GetFileNameWithoutExtension( modList.Name ).ReplaceInvalidPathSymbols() ) ); newModFolder.Create(); @@ -216,10 +223,12 @@ namespace Penumbra.Importer // Iterate through all pages foreach( var page in modList.ModPackPages) { - 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 ) ); + foreach(var group in page.ModGroups) + { + 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); @@ -248,14 +257,14 @@ namespace Penumbra.Importer OptionDesc = String.IsNullOrEmpty(opt.Description) ? "" : opt.Description, OptionFiles = new Dictionary>() }; - 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) ) { optio.AddFile(file.FullName.Substring(baseFolder.FullName.Length).TrimStart('\\'), file.FullName.Substring(optDir.FullName.Length).TrimStart('\\').Replace('\\','/')); - } - } + } + } Inf.Options.Add( optio ); } meta.Groups.Add( group.GroupName, Inf ); @@ -307,7 +316,7 @@ namespace Penumbra.Importer } catch( Exception ex ) { - PluginLog.LogError( ex, "Could not export mod." ); + PluginLog.LogError( ex, "Could not extract mod." ); } } diff --git a/Penumbra/Util/StringPathExtensions.cs b/Penumbra/Util/StringPathExtensions.cs new file mode 100644 index 00000000..027715cd --- /dev/null +++ b/Penumbra/Util/StringPathExtensions.cs @@ -0,0 +1,18 @@ +using System.IO; + +namespace Penumbra +{ + public static class StringPathExtensions + { + private static char[] _invalid = Path.GetInvalidFileNameChars(); + public static string ReplaceInvalidPathSymbols( this string s, string replacement = "_" ) + { + return string.Join( replacement, s.Split( _invalid ) ); + } + + public static string RemoveInvalidPathSymbols( this string s ) + { + return string.Concat( s.Split( _invalid ) ); + } + } +} \ No newline at end of file From f3857e03bec5d853f1e0cf7b7f2a0af6c8d8c620 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 31 Jan 2021 12:57:06 +0100 Subject: [PATCH 02/14] Allow disabling the debug bar in debug mode. --- Penumbra/UI/MenuBar.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Penumbra/UI/MenuBar.cs b/Penumbra/UI/MenuBar.cs index aa6e929b..1193c7a1 100644 --- a/Penumbra/UI/MenuBar.cs +++ b/Penumbra/UI/MenuBar.cs @@ -10,9 +10,10 @@ namespace Penumbra.UI 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 const bool _showDebugBar = true; + private bool _showDebugBar = true; #else private const bool _showDebugBar = false; #endif @@ -30,7 +31,11 @@ namespace Penumbra.UI _base.FlipVisibility(); if( ImGui.MenuItem( MenuItemRediscover ) ) - _base.ReloadMods(); + _base.ReloadMods(); +#if DEBUG + if ( ImGui.MenuItem( MenuItemHide) ) + _showDebugBar = false; +#endif ImGui.EndMenu(); } From fc2a18e5e7c5af39ce92dc121dd49f3c0f1fba82 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 1 Feb 2021 14:02:22 +0100 Subject: [PATCH 03/14] Tiny improvement to importing progress bar. Not sane, but at least not 420 / 69 objects extracted. --- Penumbra/Importer/TexToolsImport.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Penumbra/Importer/TexToolsImport.cs b/Penumbra/Importer/TexToolsImport.cs index cd322d6b..91b5b02d 100644 --- a/Penumbra/Importer/TexToolsImport.cs +++ b/Penumbra/Importer/TexToolsImport.cs @@ -21,7 +21,7 @@ namespace Penumbra.Importer 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 @@ -282,7 +282,7 @@ 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 ) From 374b652f0d816925e5f356ce47e3565fb4df556b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 5 Feb 2021 11:25:57 +0100 Subject: [PATCH 04/14] Fix crash on Delete Mod Button with no mod selected or already deleted mod. --- Penumbra/Mods/ModManager.cs | 15 +++++++++++++-- Penumbra/UI/TabInstalledSelector.cs | 9 ++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Penumbra/Mods/ModManager.cs b/Penumbra/Mods/ModManager.cs index 17be24d8..28e48b45 100644 --- a/Penumbra/Mods/ModManager.cs +++ b/Penumbra/Mods/ModManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Dalamud.Plugin; using Penumbra.Models; namespace Penumbra.Mods @@ -245,8 +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(); } diff --git a/Penumbra/UI/TabInstalledSelector.cs b/Penumbra/UI/TabInstalledSelector.cs index 3ea959da..21797949 100644 --- a/Penumbra/UI/TabInstalledSelector.cs +++ b/Penumbra/UI/TabInstalledSelector.cs @@ -119,8 +119,10 @@ namespace Penumbra.UI void DrawDeleteModal() { - if( _deleteIndex != null ) - ImGui.OpenPopup( DialogDeleteMod ); + if( _deleteIndex == null ) + return; + + ImGui.OpenPopup( DialogDeleteMod ); var ret = ImGui.BeginPopupModal( DialogDeleteMod ); if( !ret ) @@ -129,7 +131,8 @@ namespace Penumbra.UI if( _mod?.Mod == null ) { ImGui.CloseCurrentPopup(); - ImGui.EndPopup(); + ImGui.EndPopup(); + return; } ImGui.Text( "Are you sure you want to delete the following mod:" ); From 817c3a2206dbdc3fdb5e5523206141d44dd3941a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 11 Feb 2021 19:02:05 +0100 Subject: [PATCH 05/14] Silence warning about different system dlls --- Penumbra/Penumbra.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index 865d9299..e07c8d05 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -21,6 +21,10 @@ pdbonly + + $(MSBuildWarningsAsMessages);MSB3277 + + $(DALAMUD_ROOT)\Dalamud.dll From 82dff6be258f76f84e601724f0167001389b2d8c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 15 Feb 2021 12:52:38 +0100 Subject: [PATCH 06/14] Removed Regions. --- Penumbra/UI/TabInstalledDetails.cs | 318 +------------------------ Penumbra/UI/TabInstalledDetailsEdit.cs | 299 +++++++++++++++++++++++ Penumbra/UI/TabInstalledModPanel.cs | 6 - 3 files changed, 308 insertions(+), 315 deletions(-) create mode 100644 Penumbra/UI/TabInstalledDetailsEdit.cs diff --git a/Penumbra/UI/TabInstalledDetails.cs b/Penumbra/UI/TabInstalledDetails.cs index 7dec91c2..44d31bc1 100644 --- a/Penumbra/UI/TabInstalledDetails.cs +++ b/Penumbra/UI/TabInstalledDetails.cs @@ -20,13 +20,10 @@ namespace Penumbra.UI public partial class SettingsInterface { - private class PluginDetails + private partial class PluginDetails { - #region ========== Literals =============== private const string LabelPluginDetails = "PenumbraPluginDetails"; private const string LabelAboutTab = "About"; - private const string TooltipAboutEdit = "Use Ctrl+Enter for newlines."; - private const string LabelDescEdit = "##descedit"; private const string LabelChangedItemsTab = "Changed Items"; private const string LabelChangedItemsHeader = "##changedItems"; private const string LabelChangedItemIdx = "##citem_"; @@ -38,38 +35,22 @@ namespace Penumbra.UI private const string LabelFileListTab = "Files"; private const string LabelFileListHeader = "##fileList"; private const string TooltipFilesTab = "Green files replace their standard game path counterpart (not in any option) or are in all options of a Single-Select option.\nYellow files are restricted to some options."; - private const string ButtonAddToGroup = "Add to Group"; - private const string ButtonRemoveFromGroup = "Remove from Group"; private const string LabelGroupSelect = "##groupSelect"; private const string LabelOptionSelect = "##optionSelect"; - private const string TextNoOptionAvailable = "[No Option Available]"; private const string LabelConfigurationTab = "Configuration"; - private const string LabelNewSingleGroup = "New Single Group"; - private const string LabelNewSingleGroupEdit = "##newSingleGroup"; - private const string LabelNewMultiGroup = "New Multi Group"; - private const string TextDefaultGamePath = "default"; - private const string LabelGamePathsEdit = "Game Paths"; - private const string LabelGamePathsEditBox = "##gamePathsEdit"; - private const string TooltipGamePathText = "Click to copy to clipboard."; - private static readonly string TooltipGamePathsEdit = $"Enter all game paths to add or remove, separated by '{GamePathsSeparator}'.\nUse '{TextDefaultGamePath}' to add the original file path."; - private static readonly string TooltipFilesTabEdit = $"{TooltipFilesTab}\nRed Files are replaced in another group or a different option in this group, but not contained in the current option."; - private const char GamePathsSeparator = ';'; private const float TextSizePadding = 5f; private const float OptionSelectionWidth = 140f; private const float CheckMarkSize = 50f; - private const float MultiEditBoxWidth = 300f; private const uint ColorGreen = 0xFF00C800; private const uint ColorYellow = 0xFF00C8C8; private const uint ColorRed = 0xFF0000C8; - #endregion - #region ========== State ================== private bool _editMode = false; private int _selectedGroupIndex = 0; private InstallerInfo? _selectedGroup = null; - private int _selectedOptionIndex = 0; - private Option? _selectedOption = null; + private int _selectedOptionIndex = 0; + private Option? _selectedOption = null; private (string label, string name)[] _changedItemsList = null; private float? _fileSwapOffset = null; private string _currentGamePaths = ""; @@ -129,9 +110,6 @@ namespace Penumbra.UI _base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced); } - #endregion - - #region ========== Tabs =================== private void DrawAboutTab() { if (!_editMode && Meta.Description?.Length == 0) @@ -140,7 +118,7 @@ namespace Penumbra.UI if(ImGui.BeginTabItem( LabelAboutTab ) ) { var desc = Meta.Description; - var flags = _editMode + var flags = _editMode ? ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CtrlEnterForNewLine : ImGuiInputTextFlags.ReadOnly; @@ -158,7 +136,7 @@ namespace Penumbra.UI { ImGui.TextWrapped( desc ); } - + ImGui.EndTabItem(); } } @@ -168,7 +146,7 @@ namespace Penumbra.UI if (!_editMode && Meta.ChangedItems?.Count == 0) return; - var flags = _editMode + var flags = _editMode ? ImGuiInputTextFlags.EnterReturnsTrue : ImGuiInputTextFlags.ReadOnly; @@ -267,9 +245,7 @@ namespace Penumbra.UI _fileSwapOffset = null; } } - #endregion - #region ========== FileList =============== private void UpdateFilenameList() { if (_fullFilenameList == null) @@ -385,38 +361,6 @@ namespace Penumbra.UI HandleSelectedFilesButton(true); } - private void DrawEditGroupSelector() - { - ImGui.SetNextItemWidth( OptionSelectionWidth ); - if (Meta.Groups.Count == 0) - { - ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, TextNoOptionAvailable, 1); - } - else - { - if (ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, Meta.Groups.Values.Select( G => G.GroupName ).ToArray(), Meta.Groups.Count)) - { - SelectGroup(); - SelectOption(0); - } - } - } - - private void DrawEditOptionSelector() - { - ImGui.SameLine(); - ImGui.SetNextItemWidth( OptionSelectionWidth ); - if (_selectedGroup?.Options.Count == 0) - { - ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, TextNoOptionAvailable, 1); - return; - } - - var group = (InstallerInfo) _selectedGroup; - if (ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, group.Options.Select(O => O.OptionName).ToArray(), group.Options.Count)) - SelectOption(); - } - private void DrawGamePathInput() { ImGui.TextUnformatted( LabelGamePathsEdit ); @@ -426,7 +370,7 @@ namespace Penumbra.UI if (ImGui.IsItemHovered()) ImGui.SetTooltip(TooltipGamePathsEdit); } - + private void DrawGroupRow() { if (_selectedGroup == null) @@ -484,247 +428,6 @@ namespace Penumbra.UI Selectable(ColorYellow, ColorRed); } - 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; - } - #endregion - - #region ========== Configuration ========== - #region ========== MultiSelectorEdit ========== - 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(){ 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)) - { - if (newOption.Length != 0) - { - group.Options.Add(new(){ OptionName = newOption, OptionDesc = "", OptionFiles = new() }); - _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(){ OptionName = newName, OptionDesc = opt.OptionDesc, OptionFiles = opt.OptionFiles }; - _selector.SaveCurrentMod(); - } - } - } - - DrawMultiSelectorEditAdd(group, nameBoxStart); - - if (modChanged) - { - _selector.SaveCurrentMod(); - Save(); - } - - ImGuiCustom.EndFramedGroup(); - } - #endregion - - #region ========== SingleSelectorEdit ========== - 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; - var newName = ""; - if (ImGuiCustom.RenameableCombo($"##{group.GroupName}", ref code, ref 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(){ OptionName = newName, OptionDesc = "", OptionFiles = new()}); - } - } - 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; - } - #endregion - private void AddNewGroup(string newGroup, SelectType selectType) - { - if (!Meta.Groups.ContainsKey(newGroup) && newGroup.Length > 0) - { - Meta.Groups[newGroup] = new () - { - GroupName = newGroup, - SelectionType = selectType, - Options = new() - } ; - - 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(); - } - - #region Non-Edit - private void DrawMultiSelectorCheckBox(InstallerInfo group, int idx, int flag, string label) { var opt = group.Options[idx]; @@ -772,15 +475,13 @@ namespace Penumbra.UI DrawMultiSelector(g); return; } - #endregion - private void DrawConfigurationTab() { if (!_editMode && !Meta.HasGroupWithConfig) return; - if(ImGui.BeginTabItem( LabelConfigurationTab ) ) + if(ImGui.BeginTabItem( LabelConfigurationTab ) ) { if (_editMode) DrawGroupSelectorsEdit(); @@ -789,7 +490,6 @@ namespace Penumbra.UI ImGui.EndTabItem(); } } - #endregion public void Draw(bool editMode) { @@ -810,4 +510,4 @@ namespace Penumbra.UI } } } -} \ No newline at end of file +} diff --git a/Penumbra/UI/TabInstalledDetailsEdit.cs b/Penumbra/UI/TabInstalledDetailsEdit.cs new file mode 100644 index 00000000..adad26a0 --- /dev/null +++ b/Penumbra/UI/TabInstalledDetailsEdit.cs @@ -0,0 +1,299 @@ +using Penumbra.Models; +using ImGuiNET; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +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 TextNoOptionAvailable = "[No Option Available]"; + private const string ButtonAddToGroup = "Add to Group"; + private const string ButtonRemoveFromGroup = "Remove from Group"; + private const string TooltipGamePathText = "Click to copy to clipboard."; + private const string TooltipAboutEdit = "Use Ctrl+Enter for newlines."; + + private const string TextDefaultGamePath = "default"; + private const char GamePathsSeparator = ';'; + private static readonly string TooltipFilesTabEdit = $"{TooltipFilesTab}\nRed 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}'.\nUse '{TextDefaultGamePath}' to add the original file path."; + + private const float MultiEditBoxWidth = 300f; + + private void DrawEditGroupSelector() + { + ImGui.SetNextItemWidth( OptionSelectionWidth ); + if (Meta.Groups.Count == 0) + { + ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, TextNoOptionAvailable, 1); + } + else + { + if (ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, Meta.Groups.Values.Select( G => G.GroupName ).ToArray(), Meta.Groups.Count)) + { + SelectGroup(); + SelectOption(0); + } + } + } + + private void DrawEditOptionSelector() + { + ImGui.SameLine(); + ImGui.SetNextItemWidth( OptionSelectionWidth ); + if (_selectedGroup?.Options.Count == 0) + { + ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, TextNoOptionAvailable, 1); + return; + } + + var group = (InstallerInfo) _selectedGroup; + if (ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, group.Options.Select(O => O.OptionName).ToArray(), group.Options.Count)) + SelectOption(); + } + + 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(){ 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)) + { + if (newOption.Length != 0) + { + group.Options.Add(new(){ OptionName = newOption, OptionDesc = "", OptionFiles = new() }); + _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(){ 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; + var newName = ""; + if (ImGuiCustom.RenameableCombo($"##{group.GroupName}", ref code, ref 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(){ OptionName = newName, OptionDesc = "", OptionFiles = new()}); + } + } + 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) + { + Meta.Groups[newGroup] = new () + { + GroupName = newGroup, + SelectionType = selectType, + Options = new() + } ; + + 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(); + } + } + } +} \ No newline at end of file diff --git a/Penumbra/UI/TabInstalledModPanel.cs b/Penumbra/UI/TabInstalledModPanel.cs index da38d5c8..715a25f6 100644 --- a/Penumbra/UI/TabInstalledModPanel.cs +++ b/Penumbra/UI/TabInstalledModPanel.cs @@ -50,7 +50,6 @@ namespace Penumbra.UI 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; @@ -170,9 +169,7 @@ namespace Penumbra.UI ImGui.SameLine(); DrawWebsite(); } - #endregion - #region Enabled Checkmarks private void DrawEnabledMark() { var enabled = Mod.Enabled; @@ -189,9 +186,7 @@ namespace Penumbra.UI { ImGui.Checkbox( LabelEditingEnabled, ref _editMode); } - #endregion - #region Edit Line Functions private void DrawOpenModFolderButton() { if( ImGui.Button( ButtonOpenModFolder ) ) @@ -246,7 +241,6 @@ namespace Penumbra.UI ImGui.SameLine(); DrawDeduplicateButton(); } - #endregion public void Draw() { From de8930c57486f894d262d8cfa830b2bd335fc3f7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 15 Feb 2021 12:55:04 +0100 Subject: [PATCH 07/14] All Linebreaks to LF. --- Penumbra/Configuration.cs | 4 +- Penumbra/Game/RefreshActors.cs | 96 ++--- Penumbra/Importer/TexToolsImport.cs | 26 +- Penumbra/Models/Deduplicator.cs | 320 ++++++++--------- Penumbra/Models/GroupInformation.cs | 2 +- Penumbra/Models/ModMeta.cs | 26 +- Penumbra/Mods/ModManager.cs | 22 +- Penumbra/Mods/ResourceMod.cs | 4 +- Penumbra/Plugin.cs | 20 +- Penumbra/ResourceLoader.cs | 14 +- Penumbra/Structs/FileMode.cs | 2 +- Penumbra/Structs/ResourceHandle.cs | 2 +- Penumbra/Structs/SeFileDescriptor.cs | 2 +- Penumbra/UI/ImGuiFramedGroup.cs | 254 +++++++------- Penumbra/UI/ImGuiRenameableCombo.cs | 82 ++--- Penumbra/UI/ImGuiResizingTextInput.cs | 76 ++-- Penumbra/UI/ImGuiUtil.cs | 48 +-- Penumbra/UI/LaunchButton.cs | 68 ++-- Penumbra/UI/MenuBar.cs | 60 ++-- Penumbra/UI/SettingsInterface.cs | 54 +-- Penumbra/UI/SettingsMenu.cs | 86 ++--- Penumbra/UI/TabBrowser.cs | 22 +- Penumbra/UI/TabEffective.cs | 104 +++--- Penumbra/UI/TabImport.cs | 114 +++--- Penumbra/UI/TabInstalled.cs | 90 ++--- Penumbra/UI/TabInstalledDetailsEdit.cs | 2 +- Penumbra/UI/TabInstalledModPanel.cs | 446 ++++++++++++------------ Penumbra/UI/TabInstalledSelector.cs | 300 ++++++++-------- Penumbra/UI/TabSettings.cs | 220 ++++++------ Penumbra/Util/Crc32.cs | 2 +- Penumbra/Util/SingleOrArrayConverter.cs | 110 +++--- Penumbra/Util/StringPathExtensions.cs | 34 +- 32 files changed, 1356 insertions(+), 1356 deletions(-) diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index b8eae639..e3d1654d 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -1,4 +1,4 @@ -using Dalamud.Configuration; +using Dalamud.Configuration; using Dalamud.Plugin; using System; using System.Collections.Generic; @@ -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; } diff --git a/Penumbra/Game/RefreshActors.cs b/Penumbra/Game/RefreshActors.cs index 16a8f934..2f294e28 100644 --- a/Penumbra/Game/RefreshActors.cs +++ b/Penumbra/Game/RefreshActors.cs @@ -1,54 +1,54 @@ -using System.Runtime.InteropServices; -using Dalamud.Game.ClientState.Actors; -using Dalamud.Game.ClientState.Actors.Types; +using System.Runtime.InteropServices; +using Dalamud.Game.ClientState.Actors; +using Dalamud.Game.ClientState.Actors.Types; using System.Threading.Tasks; namespace Penumbra { - 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 == 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); + } } } diff --git a/Penumbra/Importer/TexToolsImport.cs b/Penumbra/Importer/TexToolsImport.cs index 91b5b02d..3cf1131b 100644 --- a/Penumbra/Importer/TexToolsImport.cs +++ b/Penumbra/Importer/TexToolsImport.cs @@ -55,9 +55,9 @@ namespace Penumbra.Importer case ".ttmp": case ".ttmp2": VerifyVersionAndImport(modPackFile); - break; - - default: + break; + + default: throw new ArgumentException( $"Unrecognized modpack format: {modPackFile.Extension}", nameof(modPackFile) ); } @@ -100,7 +100,7 @@ namespace Penumbra.Importer 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); @@ -223,11 +223,11 @@ namespace Penumbra.Importer // Iterate through all pages foreach( var page in modList.ModPackPages) { - foreach(var group in page.ModGroups) - { + foreach(var group in page.ModGroups) + { var groupFolder = new DirectoryInfo(Path.Combine(newModFolder.FullName, group.GroupName.ReplaceInvalidPathSymbols())); - foreach(var option in group.OptionList) - { + foreach(var option in group.OptionList) + { var optionFolder = new DirectoryInfo( Path.Combine( groupFolder.FullName, option.Name.ReplaceInvalidPathSymbols()) ); ExtractSimpleModList( optionFolder, option.ModsJsons, modData ); } @@ -249,7 +249,7 @@ namespace Penumbra.Importer GroupName = group.GroupName, Options = new List