From 25ac5d1999d7690731e9e9fc6df087f473de7dd3 Mon Sep 17 00:00:00 2001 From: Adam <893184+NotAdam@users.noreply.github.com> Date: Sat, 16 Jan 2021 00:23:53 +1100 Subject: [PATCH] move desc to own tab, invert load order setting, normalise line endings --- Penumbra/API/ModsController.cs | 3 +- Penumbra/Configuration.cs | 8 +- Penumbra/DialogExtensions.cs | 21 +- Penumbra/Game/GameUtils.cs | 6 +- Penumbra/Importer/TexToolsImport.cs | 62 ++--- Penumbra/Models/GroupInformation.cs | 340 +++++++++++++++------------- Penumbra/Models/ModMeta.cs | 17 +- Penumbra/Mods/ModCollection.cs | 34 +-- Penumbra/Mods/ModManager.cs | 70 +++--- Penumbra/Mods/ResourceMod.cs | 6 +- Penumbra/Penumbra.csproj | 2 +- Penumbra/Plugin.cs | 1 - Penumbra/UI/SettingsInterface.cs | 312 +++++++++++++------------ Penumbra/Util/Crc32.cs | 8 +- 14 files changed, 476 insertions(+), 414 deletions(-) diff --git a/Penumbra/API/ModsController.cs b/Penumbra/API/ModsController.cs index 4d3d0cc2..a48bdd12 100644 --- a/Penumbra/API/ModsController.cs +++ b/Penumbra/API/ModsController.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Linq; using EmbedIO; using EmbedIO.Routing; @@ -25,7 +24,7 @@ namespace Penumbra.API x.FolderName, x.Mod.Meta, BasePath = x.Mod.ModBasePath.FullName, - Files = x.Mod.ModFiles.Select( x => x.FullName ) + Files = x.Mod.ModFiles.Select( fi => fi.FullName ) } ); } diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index 0641f286..b8eae639 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -12,16 +12,18 @@ namespace Penumbra public bool IsEnabled { get; set; } = true; - public bool ShowAdvanced { get; set; } = false; + public bool ShowAdvanced { get; set; } - public bool DisableFileSystemNotifications { get; set; } = false; + public bool DisableFileSystemNotifications { get; set; } - public bool EnableHttpApi { get; set; } = false; + public bool EnableHttpApi { get; set; } public string CurrentCollection { get; set; } = @"D:/ffxiv/fs_mods/"; public List< string > ModCollections { get; set; } = new(); + public bool InvertModListOrder { get; set; } + // the below exist just to make saving less cumbersome [NonSerialized] diff --git a/Penumbra/DialogExtensions.cs b/Penumbra/DialogExtensions.cs index f409bac8..ac48e9f2 100644 --- a/Penumbra/DialogExtensions.cs +++ b/Penumbra/DialogExtensions.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Drawing; -using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -49,15 +46,15 @@ namespace Penumbra public class HiddenForm : Form { - private readonly CommonDialog form; - private readonly IWin32Window owner; - private readonly TaskCompletionSource< DialogResult > taskSource; + 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; + this._form = form; + this._owner = owner; + this._taskSource = taskSource; Opacity = 0; FormBorderStyle = FormBorderStyle.None; @@ -72,12 +69,12 @@ namespace Penumbra Hide(); try { - var result = form.ShowDialog( owner ); - taskSource.SetResult( result ); + var result = _form.ShowDialog( _owner ); + _taskSource.SetResult( result ); } catch( Exception e ) { - taskSource.SetException( e ); + _taskSource.SetException( e ); } Close(); diff --git a/Penumbra/Game/GameUtils.cs b/Penumbra/Game/GameUtils.cs index 7d8ce358..628adc00 100644 --- a/Penumbra/Game/GameUtils.cs +++ b/Penumbra/Game/GameUtils.cs @@ -7,8 +7,6 @@ namespace Penumbra.Game { public class GameUtils { - private readonly DalamudPluginInterface _pluginInterface; - [Function( CallingConventions.Microsoft )] public unsafe delegate void* LoadPlayerResourcesPrototype( IntPtr pResourceManager ); @@ -25,9 +23,7 @@ namespace Penumbra.Game public GameUtils( DalamudPluginInterface pluginInterface ) { - _pluginInterface = pluginInterface; - - var scanner = _pluginInterface.TargetModuleScanner; + var scanner = pluginInterface.TargetModuleScanner; var loadPlayerResourcesAddress = scanner.ScanText( diff --git a/Penumbra/Importer/TexToolsImport.cs b/Penumbra/Importer/TexToolsImport.cs index 0173bcec..c33a5f43 100644 --- a/Penumbra/Importer/TexToolsImport.cs +++ b/Penumbra/Importer/TexToolsImport.cs @@ -17,10 +17,10 @@ namespace Penumbra.Importer private readonly DirectoryInfo _outDirectory; private const string TempFileName = "textools-import"; - private readonly string _resolvedTempFilePath = null; + private readonly string _resolvedTempFilePath; public ImporterState State { get; private set; } - + public long TotalProgress { get; private set; } public long CurrentProgress { get; private set; } @@ -49,7 +49,7 @@ namespace Penumbra.Importer public void ImportModPack( FileInfo modPackFile ) { CurrentModPack = modPackFile.Name; - + switch( modPackFile.Extension ) { case ".ttmp": @@ -74,7 +74,7 @@ namespace Penumbra.Importer private SqPackStream GetMagicSqPackDeleterStream( ZipFile file, string entryName ) { State = ImporterState.WritingPackToDisk; - + // write shitty zip garbage to disk var entry = file.GetEntry( entryName ); using var s = file.GetInputStream( entry ); @@ -215,32 +215,32 @@ namespace Penumbra.Importer from modGroup in modPackPage.ModGroups from option in modGroup.OptionList select option ) - { - var OptionFolder = new DirectoryInfo(Path.Combine(newModFolder.FullName, option.Name)); - ExtractSimpleModList(OptionFolder, option.ModsJsons, modData ); - AddMeta(OptionFolder, newModFolder, modMeta, option.Name); - } - + { + var optionFolder = new DirectoryInfo( Path.Combine( newModFolder.FullName, option.Name ) ); + ExtractSimpleModList( optionFolder, option.ModsJsons, modData ); + AddMeta( optionFolder, newModFolder, modMeta, option.Name ); + } + File.WriteAllText( Path.Combine( newModFolder.FullName, "meta.json" ), JsonConvert.SerializeObject( modMeta, Formatting.Indented ) ); - } - - void AddMeta(DirectoryInfo optionFolder, DirectoryInfo baseFolder, ModMeta meta, string optionName) - { - var optionFolderLength = optionFolder.FullName.Length; - var baseFolderLength = baseFolder.FullName.Length; - foreach( var dir in optionFolder.EnumerateDirectories() ) + } + + void AddMeta( DirectoryInfo optionFolder, DirectoryInfo baseFolder, ModMeta meta, string optionName ) + { + var optionFolderLength = optionFolder.FullName.Length; + var baseFolderLength = baseFolder.FullName.Length; + foreach( var dir in optionFolder.EnumerateDirectories() ) { - foreach( var file in dir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) ) - { - meta.Groups.AddFileToOtherGroups(optionName - , file.FullName.Substring(baseFolderLength).TrimStart('\\') - , file.FullName.Substring(optionFolderLength).TrimStart('\\').Replace('\\', '/')); - } - } - } + foreach( var file in dir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) ) + { + meta.Groups.AddFileToOtherGroups( optionName + , file.FullName.Substring( baseFolderLength ).TrimStart( '\\' ) + , file.FullName.Substring( optionFolderLength ).TrimStart( '\\' ).Replace( '\\', '/' ) ); + } + } + } private void ImportMetaModPack( FileInfo file ) { @@ -250,12 +250,12 @@ namespace Penumbra.Importer private void ExtractSimpleModList( DirectoryInfo outDirectory, IEnumerable< SimpleMod > mods, SqPackStream dataStream ) { State = ImporterState.ExtractingModFiles; - + // haha allocation go brr var wtf = mods.ToList(); - + TotalProgress = wtf.LongCount(); - + // Extract each SimpleMod into the new mod folder foreach( var simpleMod in wtf ) { @@ -264,7 +264,7 @@ namespace Penumbra.Importer // do we increment here too???? can this even happen????? continue; } - + ExtractMod( outDirectory, simpleMod, dataStream ); CurrentProgress++; } @@ -296,12 +296,12 @@ namespace Penumbra.Importer { // Model file header LOD num 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 stringsLength = BitConverter.ToUInt32( mdl, ( int )stringsLengthOffset ); var modelHeaderStart = stringsLengthOffset + stringsLength + 4; var modelHeaderLodOffset = 22; mdl[ modelHeaderStart + modelHeaderLodOffset ] = 1; diff --git a/Penumbra/Models/GroupInformation.cs b/Penumbra/Models/GroupInformation.cs index 50b6cad4..f3065573 100644 --- a/Penumbra/Models/GroupInformation.cs +++ b/Penumbra/Models/GroupInformation.cs @@ -1,157 +1,189 @@ -using System; +using System; using System.Collections.Generic; -using System.Runtime.Serialization; -using System.Linq; - +using System.Runtime.Serialization; +using System.Linq; + namespace Penumbra.Models -{ - [Serializable] - public class GroupInformation : ISerializable - { - - // This class is just used as a temp class while (de)-serializing. - // It converts the flags into lists and back. - [Serializable] - private class GroupDescription : ISerializable - { - public GroupDescription(GroupInformation info, (string, uint, uint, ulong) vars) - { - GamePath = vars.Item1; - - static List AddGroupTypes(ulong flags, ulong bound, List groupType) - { - List ret = null; - if (flags != uint.MaxValue) - { - ret = new(); - for (var i = 0; i < groupType.Count; ++i) - { - var flag = 1u << i; - if ((flags & flag) == flag) - ret.Add(groupType[i]); - } - } - return ret; - } - - // Tops and Bottoms are uint. - TopTypes = AddGroupTypes(vars.Item2, uint.MaxValue, info.TopTypes); - BottomTypes = AddGroupTypes(vars.Item3, uint.MaxValue, info.BottomTypes); - // Exclusions are the other way around and ulong. - GroupExclusions = AddGroupTypes(~vars.Item4, 0, info.OtherGroups); - } - - public (string, uint, uint, ulong) ToTuple(GroupInformation info) - { - static ulong TypesToFlags(List ownTypes, List globalTypes) - { - if (ownTypes == null) - return ulong.MaxValue; - - ulong flags = 0; - foreach (var x in ownTypes) - { - var index = globalTypes.IndexOf(x); - if (index >= 0) - flags |= (1u << index); - } - return flags; - } - var tops = (uint) TypesToFlags(TopTypes, info.TopTypes); - var bottoms = (uint) TypesToFlags(BottomTypes, info.BottomTypes); - // Exclusions are the other way around. - var groupEx = (GroupExclusions == null) ? ulong.MaxValue : ~TypesToFlags(GroupExclusions, info.OtherGroups); - return (GamePath, tops, bottoms, groupEx); - } - - public string GamePath { get; set; } - public List TopTypes { get; set; } = null; - public List BottomTypes { get; set; } = null; - public List GroupExclusions { get; set; } = null; - - // Customize (De)-Serialization to ignore nulls. - public GroupDescription(SerializationInfo info, StreamingContext context) - { - List readListOrNull(string name) - { - try - { - var ret = (List) info.GetValue(name, typeof(List)); - if (ret == null || ret.Count == 0) - return null; - return ret; - } - catch (Exception) { return null; } - } - GamePath = info.GetString("GamePath"); - TopTypes = readListOrNull("TopTypes"); - BottomTypes = readListOrNull("BottomTypes"); - GroupExclusions = readListOrNull("GroupExclusions"); - } - - public virtual void GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue( "GamePath", GamePath ); - if (TopTypes != null) info.AddValue("TopTypes", TopTypes); - if (BottomTypes != null) info.AddValue("BottomTypes", BottomTypes); - if (GroupExclusions != null) info.AddValue("GroupExclusions", GroupExclusions); - } - } - - public List TopTypes { get; set; } = new(); - public List BottomTypes { get; set; } = new(); - public List OtherGroups { get; set; } = new(); - - public void AddFileToOtherGroups(string optionName, string fileName, string gamePath) - { - var idx = OtherGroups.IndexOf(optionName); - if (idx < 0) - { - idx = OtherGroups.Count; - OtherGroups.Add(optionName); - } - - (string, uint, uint, ulong) tuple = (gamePath, uint.MaxValue, uint.MaxValue, (1ul << idx)); - - if (!FileToGameAndGroup.TryGetValue(fileName, out var tuple2)) - { - FileToGameAndGroup.Add(fileName, tuple); - } - else - { - tuple2.Item1 = tuple.Item1; - tuple2.Item4 |= tuple.Item4; - } - } - - public Dictionary FileToGameAndGroup { get; set; } = new(); - - public GroupInformation(){ } - - public GroupInformation(SerializationInfo info, StreamingContext context) - { - try { TopTypes = (List) info.GetValue( "TopTypes", TopTypes.GetType() ); } catch(Exception){ } - try { BottomTypes = (List) info.GetValue( "BottomTypes", BottomTypes.GetType() ); } catch(Exception){ } - try { OtherGroups = (List) info.GetValue( "Groups", OtherGroups.GetType() ); } catch(Exception){ } - try - { - Dictionary dict = new(); - dict = (Dictionary) info.GetValue( "FileToGameAndGroups", dict.GetType()); - foreach (var pair in dict) - FileToGameAndGroup.Add(pair.Key, pair.Value.ToTuple(this)); - } catch (Exception){ } - } - - public virtual void GetObjectData(SerializationInfo info, StreamingContext context) - { - if ((TopTypes?.Count ?? 0) > 0) info.AddValue("TopTypes", TopTypes); - if ((BottomTypes?.Count ?? 0) > 0) info.AddValue("BottomTypes", BottomTypes); - if ((OtherGroups?.Count ?? 0) > 0) info.AddValue("Groups", OtherGroups); - if ((FileToGameAndGroup?.Count ?? 0) > 0) - { - var dict = FileToGameAndGroup.ToDictionary( pair => pair.Key, pair => new GroupDescription( this, pair.Value ) ); - info.AddValue("FileToGameAndGroups", dict); - } - } - } +{ + [Serializable] + public class GroupInformation : ISerializable + { + // This class is just used as a temp class while (de)-serializing. + // It converts the flags into lists and back. + [Serializable] + private class GroupDescription : ISerializable + { + public GroupDescription( GroupInformation info, (string, uint, uint, ulong) vars ) + { + GamePath = vars.Item1; + + static List< string > AddGroupTypes( ulong flags, ulong bound, List< string > groupType ) + { + List< string > ret = null; + if( flags != uint.MaxValue ) + { + ret = new(); + for( var i = 0; i < groupType.Count; ++i ) + { + var flag = 1u << i; + if( ( flags & flag ) == flag ) + ret.Add( groupType[ i ] ); + } + } + + return ret; + } + + // Tops and Bottoms are uint. + TopTypes = AddGroupTypes( vars.Item2, uint.MaxValue, info.TopTypes ); + BottomTypes = AddGroupTypes( vars.Item3, uint.MaxValue, info.BottomTypes ); + // Exclusions are the other way around and ulong. + GroupExclusions = AddGroupTypes( ~vars.Item4, 0, info.OtherGroups ); + } + + public (string, uint, uint, ulong) ToTuple( GroupInformation info ) + { + static ulong TypesToFlags( List< string > ownTypes, List< string > globalTypes ) + { + if( ownTypes == null ) + return ulong.MaxValue; + + ulong flags = 0; + foreach( var x in ownTypes ) + { + var index = globalTypes.IndexOf( x ); + if( index >= 0 ) + flags |= ( 1u << index ); + } + + return flags; + } + + var tops = ( uint )TypesToFlags( TopTypes, info.TopTypes ); + var bottoms = ( uint )TypesToFlags( BottomTypes, info.BottomTypes ); + // Exclusions are the other way around. + var groupEx = ( GroupExclusions == null ) ? ulong.MaxValue : ~TypesToFlags( GroupExclusions, info.OtherGroups ); + return ( GamePath, tops, bottoms, groupEx ); + } + + public string GamePath { get; set; } + public List< string > TopTypes { get; set; } + public List< string > BottomTypes { get; set; } + public List< string > GroupExclusions { get; set; } + + // Customize (De)-Serialization to ignore nulls. + public GroupDescription( SerializationInfo info, StreamingContext context ) + { + List< string > readListOrNull( string name ) + { + try + { + var ret = ( List< string > )info.GetValue( name, typeof( List< string > ) ); + if( ret == null || ret.Count == 0 ) + return null; + return ret; + } + catch( Exception ) + { + return null; + } + } + + GamePath = info.GetString( "GamePath" ); + TopTypes = readListOrNull( "TopTypes" ); + BottomTypes = readListOrNull( "BottomTypes" ); + GroupExclusions = readListOrNull( "GroupExclusions" ); + } + + public virtual void GetObjectData( SerializationInfo info, StreamingContext context ) + { + info.AddValue( "GamePath", GamePath ); + if( TopTypes != null ) info.AddValue( "TopTypes", TopTypes ); + if( BottomTypes != null ) info.AddValue( "BottomTypes", BottomTypes ); + if( GroupExclusions != null ) info.AddValue( "GroupExclusions", GroupExclusions ); + } + } + + public List< string > TopTypes { get; set; } = new(); + public List< string > BottomTypes { get; set; } = new(); + public List< string > OtherGroups { get; set; } = new(); + + public void AddFileToOtherGroups( string optionName, string fileName, string gamePath ) + { + var idx = OtherGroups.IndexOf( optionName ); + if( idx < 0 ) + { + idx = OtherGroups.Count; + OtherGroups.Add( optionName ); + } + + (string, uint, uint, ulong) tuple = ( gamePath, uint.MaxValue, uint.MaxValue, ( 1ul << idx ) ); + + if( !FileToGameAndGroup.TryGetValue( fileName, out var tuple2 ) ) + { + FileToGameAndGroup.Add( fileName, tuple ); + } + else + { + tuple2.Item1 = tuple.Item1; + tuple2.Item4 |= tuple.Item4; + } + } + + public Dictionary< string, (string, uint, uint, ulong) > FileToGameAndGroup { get; set; } = new(); + + public GroupInformation() + { + } + + public GroupInformation( SerializationInfo info, StreamingContext context ) + { + try + { + TopTypes = ( List< string > )info.GetValue( "TopTypes", TopTypes.GetType() ); + } + catch( Exception ) + { + } + + try + { + BottomTypes = ( List< string > )info.GetValue( "BottomTypes", BottomTypes.GetType() ); + } + catch( Exception ) + { + } + + try + { + OtherGroups = ( List< string > )info.GetValue( "Groups", OtherGroups.GetType() ); + } + catch( Exception ) + { + } + + try + { + Dictionary< string, GroupDescription > dict = new(); + dict = ( Dictionary< string, GroupDescription > )info.GetValue( "FileToGameAndGroups", dict.GetType() ); + foreach( var pair in dict ) + FileToGameAndGroup.Add( pair.Key, pair.Value.ToTuple( this ) ); + } + catch( Exception ) + { + } + } + + public virtual void GetObjectData( SerializationInfo info, StreamingContext context ) + { + if( ( TopTypes?.Count ?? 0 ) > 0 ) info.AddValue( "TopTypes", TopTypes ); + if( ( BottomTypes?.Count ?? 0 ) > 0 ) info.AddValue( "BottomTypes", BottomTypes ); + if( ( OtherGroups?.Count ?? 0 ) > 0 ) info.AddValue( "Groups", OtherGroups ); + if( ( FileToGameAndGroup?.Count ?? 0 ) > 0 ) + { + var dict = FileToGameAndGroup.ToDictionary( pair => pair.Key, pair => new GroupDescription( this, pair.Value ) ); + info.AddValue( "FileToGameAndGroups", dict ); + } + } + } } \ No newline at end of file diff --git a/Penumbra/Models/ModMeta.cs b/Penumbra/Models/ModMeta.cs index b5bb3113..4f8576d9 100644 --- a/Penumbra/Models/ModMeta.cs +++ b/Penumbra/Models/ModMeta.cs @@ -4,18 +4,19 @@ namespace Penumbra.Models { public class ModMeta { + public uint FileVersion { get; set; } public string Name { get; set; } public string Author { get; set; } public string Description { get; set; } - + public string Version { get; set; } - - public string Website { get; set; } - - public List ChangedItems { get; set; } = new(); - - public Dictionary< string, string > FileSwaps { get; } = new(); - + + public string Website { get; set; } + + public List< string > ChangedItems { get; set; } = new(); + + public Dictionary< string, string > FileSwaps { get; } = new(); + public GroupInformation Groups { get; set; } = new(); } } \ No newline at end of file diff --git a/Penumbra/Mods/ModCollection.cs b/Penumbra/Mods/ModCollection.cs index 7a9a853c..6669f414 100644 --- a/Penumbra/Mods/ModCollection.cs +++ b/Penumbra/Mods/ModCollection.cs @@ -21,7 +21,7 @@ namespace Penumbra.Mods _basePath = basePath; } - public void Load() + public void Load( bool invertOrder = false ) { // find the collection json var collectionPath = Path.Combine( _basePath.FullName, "collection.json" ); @@ -94,7 +94,7 @@ namespace Penumbra.Mods } // reorder the resourcemods list so we can just directly iterate - EnabledMods = GetOrderedAndEnabledModList().ToArray(); + EnabledMods = GetOrderedAndEnabledModList( invertOrder ).ToArray(); // write the collection metadata back to disk Save(); @@ -181,22 +181,28 @@ namespace Penumbra.Mods return AddModSettings( mod ); } - public IEnumerable GetOrderedAndEnabledModSettings() + public IEnumerable GetOrderedAndEnabledModSettings( bool invertOrder = false ) { - return ModSettings - .Where( x => x.Enabled ) - .OrderBy( x => x.Priority ); - } - - public IEnumerable GetOrderedAndEnabledModList() + var query = ModSettings + .Where( x => x.Enabled ); + + if( !invertOrder ) + { + return query.OrderBy( x => x.Priority ); + } + + return query.OrderByDescending( x => x.Priority ); + } + + public IEnumerable GetOrderedAndEnabledModList( bool invertOrder = false ) { - return GetOrderedAndEnabledModSettings() + return GetOrderedAndEnabledModSettings( invertOrder ) .Select( x => x.Mod ); - } - - public IEnumerable<(ResourceMod, ModInfo)> GetOrderedAndEnabledModListWithSettings() + } + + public IEnumerable<(ResourceMod, ModInfo)> GetOrderedAndEnabledModListWithSettings( bool invertOrder = false ) { - return GetOrderedAndEnabledModSettings() + return GetOrderedAndEnabledModSettings( invertOrder ) .Select( x => (x.Mod, x) ); } } diff --git a/Penumbra/Mods/ModManager.cs b/Penumbra/Mods/ModManager.cs index 4ab04660..95fa4905 100644 --- a/Penumbra/Mods/ModManager.cs +++ b/Penumbra/Mods/ModManager.cs @@ -1,14 +1,14 @@ using System; using System.Collections.Generic; -using System.IO; -using Penumbra.Models; - +using System.IO; +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, FileInfo > ResolvedFiles = new(); public readonly Dictionary< string, string > SwappedFiles = new(); public ModCollection Mods { get; set; } @@ -98,45 +98,45 @@ namespace Penumbra.Mods Mods.Save(); CalculateEffectiveFileList(); - + // Needed to reload body textures with mods _plugin.GameUtils.ReloadPlayerResources(); } public void CalculateEffectiveFileList() { - ResolvedFiles.Clear(); + ResolvedFiles.Clear(); SwappedFiles.Clear(); var registeredFiles = new Dictionary< string, string >(); - foreach( var (mod, settings) in Mods.GetOrderedAndEnabledModListWithSettings() ) - { + foreach( var (mod, settings) in Mods.GetOrderedAndEnabledModListWithSettings( _plugin.Configuration.InvertModListOrder ) ) + { mod.FileConflicts?.Clear(); // fixup path var baseDir = mod.ModBasePath.FullName; - foreach( var file in mod.ModFiles ) - { - var relativeFilePath = file.FullName.Substring( baseDir.Length ).TrimStart( '\\' ); - - string gamePath; - bool addFile = true; - (string, uint, uint, ulong) tuple; - if (mod.Meta.Groups.FileToGameAndGroup.TryGetValue(relativeFilePath, out tuple)) - { - gamePath = tuple.Item1; - var (_, tops, bottoms, excludes) = tuple; - var validTop = ((1u << settings.CurrentTop) & tops ) != 0; - var validBottom = ((1u << settings.CurrentBottom) & bottoms ) != 0; - var validGroup = ((1ul << settings.CurrentGroup) & excludes) != 0; - addFile = validTop && validBottom && validGroup; - } - else - gamePath = relativeFilePath.Replace( '\\', '/' ); - - if ( addFile ) + foreach( var file in mod.ModFiles ) + { + var relativeFilePath = file.FullName.Substring( baseDir.Length ).TrimStart( '\\' ); + + string gamePath; + bool addFile = true; + (string, uint, uint, ulong) tuple; + if( mod.Meta.Groups.FileToGameAndGroup.TryGetValue( relativeFilePath, out tuple ) ) + { + gamePath = tuple.Item1; + var (_, tops, bottoms, excludes) = tuple; + var validTop = ( ( 1u << settings.CurrentTop ) & tops ) != 0; + var validBottom = ( ( 1u << settings.CurrentBottom ) & bottoms ) != 0; + var validGroup = ( ( 1ul << settings.CurrentGroup ) & excludes ) != 0; + addFile = validTop && validBottom && validGroup; + } + else + gamePath = relativeFilePath.Replace( '\\', '/' ); + + if( addFile ) { if( !ResolvedFiles.ContainsKey( gamePath ) ) { @@ -146,10 +146,10 @@ namespace Penumbra.Mods else if( registeredFiles.TryGetValue( gamePath, out var modName ) ) { mod.AddConflict( modName, gamePath ); - } + } } - } - + } + foreach( var swap in mod.Meta.FileSwaps ) { // just assume people put not fucked paths in here lol @@ -160,8 +160,8 @@ namespace Penumbra.Mods } else if( registeredFiles.TryGetValue( swap.Key, out var modName ) ) { - mod.AddConflict( modName, swap.Key ); - } + mod.AddConflict( modName, swap.Key ); + } } } } @@ -202,10 +202,10 @@ namespace Penumbra.Mods public string ResolveSwappedOrReplacementFilePath( string gameResourcePath ) { gameResourcePath = gameResourcePath.ToLowerInvariant(); - + return GetCandidateForGameFile( gameResourcePath )?.FullName ?? GetSwappedFilePath( gameResourcePath ); } - + public void Dispose() { diff --git a/Penumbra/Mods/ResourceMod.cs b/Penumbra/Mods/ResourceMod.cs index 4800841d..f71ddfcd 100644 --- a/Penumbra/Mods/ResourceMod.cs +++ b/Penumbra/Mods/ResourceMod.cs @@ -31,10 +31,10 @@ namespace Penumbra.Mods ModFiles.Add( file ); } } - + // 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 char[]{'/', '\\'}) < 0) + foreach( var pair in Meta.Groups.FileToGameAndGroup ) + if (pair.Key.IndexOfAny(new[]{'/', '\\'}) < 0) ModFiles.Add( new FileInfo(Path.Combine(ModBasePath.FullName, pair.Key)) ); } diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index c1d746af..865d9299 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -1,7 +1,7 @@ net472 - latest + preview Penumbra absolute gangstas Penumbra diff --git a/Penumbra/Plugin.cs b/Penumbra/Plugin.cs index f3e7973a..b158e458 100644 --- a/Penumbra/Plugin.cs +++ b/Penumbra/Plugin.cs @@ -1,7 +1,6 @@ using Dalamud.Game.Command; using Dalamud.Plugin; using EmbedIO; -using EmbedIO.Actions; using EmbedIO.WebApi; using Penumbra.API; using Penumbra.Game; diff --git a/Penumbra/UI/SettingsInterface.cs b/Penumbra/UI/SettingsInterface.cs index 7824e2b1..7a7a0c28 100644 --- a/Penumbra/UI/SettingsInterface.cs +++ b/Penumbra/UI/SettingsInterface.cs @@ -6,8 +6,8 @@ using System.Numerics; using System.Threading.Tasks; using System.Windows.Forms; using Dalamud.Interface; -using Dalamud.Plugin; -using ImGuiNET; +using Dalamud.Plugin; +using ImGuiNET; using Newtonsoft.Json; using Penumbra.Importer; using Penumbra.Models; @@ -18,8 +18,8 @@ namespace Penumbra.UI { private readonly Plugin _plugin; - public bool Visible = false; - public bool ShowDebugBar = false; + public bool Visible; + public bool ShowDebugBar; private static readonly Vector2 AutoFillSize = new Vector2( -1, -1 ); private static readonly Vector2 ModListSize = new Vector2( 200, -1 ); @@ -33,7 +33,7 @@ namespace Penumbra.UI private int? _selectedModDeleteIndex; private ModInfo _selectedMod; - public bool IsImportRunning = false; + private bool _isImportRunning; private TexToolsImport _texToolsImport = null!; public SettingsInterface( Plugin plugin ) @@ -129,7 +129,7 @@ namespace Penumbra.UI DrawImportTab(); - if( !IsImportRunning ) + if( !_isImportRunning ) { DrawModBrowser(); @@ -156,11 +156,11 @@ namespace Penumbra.UI return; } - if( !IsImportRunning ) + if( !_isImportRunning ) { if( ImGui.Button( "Import TexTools Modpacks" ) ) { - IsImportRunning = true; + _isImportRunning = true; Task.Run( async () => { @@ -197,7 +197,7 @@ namespace Penumbra.UI ReloadMods(); } - IsImportRunning = false; + _isImportRunning = false; } ); } } @@ -267,9 +267,9 @@ namespace Penumbra.UI if( ImGui.Button( "Rediscover Mods" ) ) { - ReloadMods(); - _selectedModIndex = 0; - _selectedMod = null; + ReloadMods(); + _selectedModIndex = 0; + _selectedMod = null; } ImGui.SameLine(); @@ -279,7 +279,18 @@ namespace Penumbra.UI Process.Start( _plugin.Configuration.CurrentCollection ); } - ImGui.SetCursorPosY( ImGui.GetCursorPosY() + 15 ); + ImGui.SetCursorPosY( ImGui.GetCursorPosY() + 20 ); + + var invertOrder = _plugin.Configuration.InvertModListOrder; + if( ImGui.Checkbox( "Invert mod load order (mods are loaded bottom up)", ref invertOrder ) ) + { + _plugin.Configuration.InvertModListOrder = invertOrder; + dirty = true; + + ReloadMods(); + } + + ImGui.SetCursorPosY( ImGui.GetCursorPosY() + 20 ); var showAdvanced = _plugin.Configuration.ShowAdvanced; if( ImGui.Checkbox( "Show Advanced Settings", ref showAdvanced ) ) @@ -405,7 +416,13 @@ namespace Penumbra.UI ImGui.PopFont(); if( ImGui.IsItemHovered() ) - ImGui.SetTooltip( "Move the selected mod up in priority" ); + { + ImGui.SetTooltip( + _plugin.Configuration.InvertModListOrder + ? "Move the selected mod down in priority" + : "Move the selected mod up in priority" + ); + } ImGui.PushFont( UiBuilder.IconFont ); @@ -430,7 +447,13 @@ namespace Penumbra.UI ImGui.PopFont(); if( ImGui.IsItemHovered() ) - ImGui.SetTooltip( "Move the selected mod down in priority" ); + { + ImGui.SetTooltip( + _plugin.Configuration.InvertModListOrder + ? "Move the selected mod up in priority" + : "Move the selected mod down in priority" + ); + } ImGui.PushFont( UiBuilder.IconFont ); @@ -503,106 +526,109 @@ namespace Penumbra.UI } ImGui.EndPopup(); - } - - // Website button with On-Hover address if valid http(s), otherwise text. - private void DrawWebsiteText() - { - if ((_selectedMod.Mod.Meta.Website?.Length ?? 0) > 0) - { - var validUrl = Uri.TryCreate(_selectedMod.Mod.Meta.Website, UriKind.Absolute, out Uri uriResult) - && (uriResult.Scheme == Uri.UriSchemeHttps ||uriResult.Scheme == Uri.UriSchemeHttp); - ImGui.SameLine(); - if (validUrl) - { - if (ImGui.SmallButton("Open Website")) - { - Process.Start( _selectedMod.Mod.Meta.Website ); - } - if (ImGui.IsItemHovered()) - { - ImGui.BeginTooltip(); - ImGui.Text( _selectedMod.Mod.Meta.Website ); - ImGui.EndTooltip(); - } - } - else - { - ImGui.TextColored( new Vector4( 1f, 1f, 1f, 0.66f ), "from" ); - ImGui.SameLine(); - ImGui.Text(_selectedMod.Mod.Meta.Website); - } - } } - - // Create Mod-Handling buttons. - private void DrawEditButtons() - { - ImGui.SameLine(); + + // Website button with On-Hover address if valid http(s), otherwise text. + private void DrawWebsiteText() + { + if( ( _selectedMod.Mod.Meta.Website?.Length ?? 0 ) <= 0 ) + { + return; + } + + var validUrl = Uri.TryCreate( _selectedMod.Mod.Meta.Website, UriKind.Absolute, out Uri uriResult ) + && ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp ); + ImGui.SameLine(); + if( validUrl ) + { + if( ImGui.SmallButton( "Open Website" ) ) + { + Process.Start( _selectedMod.Mod.Meta.Website ); + } + + if( ImGui.IsItemHovered() ) + { + ImGui.BeginTooltip(); + ImGui.Text( _selectedMod.Mod.Meta.Website ); + ImGui.EndTooltip(); + } + } + else + { + ImGui.TextColored( new Vector4( 1f, 1f, 1f, 0.66f ), "from" ); + ImGui.SameLine(); + ImGui.Text( _selectedMod.Mod.Meta.Website ); + } + } + + // Create Mod-Handling buttons. + private void DrawEditButtons() + { + ImGui.SameLine(); if( ImGui.Button( "Open Mod Folder" ) ) { Process.Start( _selectedMod.Mod.ModBasePath.FullName ); - } - - ImGui.SameLine(); + } + + ImGui.SameLine(); if( ImGui.Button( "Edit JSON" ) ) - { - var metaPath = Path.Combine( _selectedMod.Mod.ModBasePath.FullName, "meta.json"); + { + var metaPath = Path.Combine( _selectedMod.Mod.ModBasePath.FullName, "meta.json" ); File.WriteAllText( metaPath, JsonConvert.SerializeObject( _selectedMod.Mod.Meta, Formatting.Indented ) ); Process.Start( metaPath ); - } - - ImGui.SameLine(); + } + + ImGui.SameLine(); if( ImGui.Button( "Reload JSON" ) ) - { - ReloadMods(); - - // May select a different mod than before if mods were added or deleted, but will not crash. - if (_selectedModIndex < _plugin.ModManager.Mods.ModSettings.Count) - { - _selectedMod = _plugin.ModManager.Mods.ModSettings[_selectedModIndex]; - } - else - { - _selectedModIndex = 0; - _selectedMod = null; - } - } - } - - private void DrawGroupSelectors() - { - var hasTopTypes = (_selectedMod.Mod.Meta.Groups.TopTypes?.Count ?? 0) > 1; - var hasBottomTypes = (_selectedMod.Mod.Meta.Groups.BottomTypes?.Count ?? 0) > 1; - var hasGroups = (_selectedMod.Mod.Meta.Groups.OtherGroups?.Count ?? 0) > 1; - var numSelectors = (hasTopTypes ? 1 : 0) + (hasBottomTypes ? 1 : 0) + (hasGroups ? 1 : 0); - var selectorWidth = (ImGui.GetWindowWidth() - - (hasTopTypes ? ImGui.CalcTextSize("Top ").X : 0) - - (hasBottomTypes ? ImGui.CalcTextSize("Bottom ").X : 0) - - (hasGroups ? ImGui.CalcTextSize("Group ").X : 0)) / numSelectors; - - void DrawSelector(string label, string propertyName, System.Collections.Generic.List list, bool sameLine) - { - var current = (int) _selectedMod.GetType().GetProperty(propertyName).GetValue(_selectedMod); - ImGui.SetNextItemWidth( selectorWidth ); - if (sameLine) ImGui.SameLine(); - if ( ImGui.Combo(label, ref current, list.ToArray(), list.Count()) ) - { - _selectedMod.GetType().GetProperty(propertyName).SetValue(_selectedMod, current); + { + ReloadMods(); + + // May select a different mod than before if mods were added or deleted, but will not crash. + if( _selectedModIndex < _plugin.ModManager.Mods.ModSettings.Count ) + { + _selectedMod = _plugin.ModManager.Mods.ModSettings[ _selectedModIndex ]; + } + else + { + _selectedModIndex = 0; + _selectedMod = null; + } + } + } + + private void DrawGroupSelectors() + { + var hasTopTypes = ( _selectedMod.Mod.Meta.Groups.TopTypes?.Count ?? 0 ) > 1; + var hasBottomTypes = ( _selectedMod.Mod.Meta.Groups.BottomTypes?.Count ?? 0 ) > 1; + var hasGroups = ( _selectedMod.Mod.Meta.Groups.OtherGroups?.Count ?? 0 ) > 1; + var numSelectors = ( hasTopTypes ? 1 : 0 ) + ( hasBottomTypes ? 1 : 0 ) + ( hasGroups ? 1 : 0 ); + var selectorWidth = ( ImGui.GetWindowWidth() + - ( hasTopTypes ? ImGui.CalcTextSize( "Top " ).X : 0 ) + - ( hasBottomTypes ? ImGui.CalcTextSize( "Bottom " ).X : 0 ) + - ( hasGroups ? ImGui.CalcTextSize( "Group " ).X : 0 ) ) / numSelectors; + + void DrawSelector( string label, string propertyName, System.Collections.Generic.List< string > list, bool sameLine ) + { + var current = ( int )_selectedMod.GetType().GetProperty( propertyName ).GetValue( _selectedMod ); + ImGui.SetNextItemWidth( selectorWidth ); + if( sameLine ) ImGui.SameLine(); + if( ImGui.Combo( label, ref current, list.ToArray(), list.Count() ) ) + { + _selectedMod.GetType().GetProperty( propertyName ).SetValue( _selectedMod, current ); _plugin.ModManager.Mods.Save(); - _plugin.ModManager.CalculateEffectiveFileList(); - } - } - - if ( hasTopTypes ) - DrawSelector("Top", "CurrentTop", _selectedMod.Mod.Meta.Groups.TopTypes, false); - - if ( hasBottomTypes ) - DrawSelector("Bottom", "CurrentBottom", _selectedMod.Mod.Meta.Groups.BottomTypes, hasTopTypes); - - if ( hasGroups ) - DrawSelector("Group", "CurrentGroup", _selectedMod.Mod.Meta.Groups.OtherGroups, numSelectors > 1); - } + _plugin.ModManager.CalculateEffectiveFileList(); + } + } + + if( hasTopTypes ) + DrawSelector( "Top", "CurrentTop", _selectedMod.Mod.Meta.Groups.TopTypes, false ); + + if( hasBottomTypes ) + DrawSelector( "Bottom", "CurrentBottom", _selectedMod.Mod.Meta.Groups.BottomTypes, hasTopTypes ); + + if( hasGroups ) + DrawSelector( "Group", "CurrentGroup", _selectedMod.Mod.Meta.Groups.OtherGroups, numSelectors > 1 ); + } void DrawInstalledMods() { @@ -634,24 +660,24 @@ namespace Penumbra.UI { ImGui.BeginChild( "selectedModInfo", AutoFillSize, true ); - ImGui.Text( _selectedMod.Mod.Meta.Name ); - - // (Version ...) or nothing. - if ((_selectedMod.Mod.Meta.Version?.Length ?? 0) > 0) - { - ImGui.SameLine(); - ImGui.Text($"(Version {_selectedMod.Mod.Meta.Version})" ); - } - + ImGui.Text( _selectedMod.Mod.Meta.Name ); + + // (Version ...) or nothing. + if( ( _selectedMod.Mod.Meta.Version?.Length ?? 0 ) > 0 ) + { + ImGui.SameLine(); + ImGui.Text( $"(Version {_selectedMod.Mod.Meta.Version})" ); + } + // by Author or Unknown. ImGui.SameLine(); ImGui.TextColored( new Vector4( 1f, 1f, 1f, 0.66f ), "by" ); - ImGui.SameLine(); - if ((_selectedMod.Mod.Meta.Author?.Length ?? 0) > 0 ) - ImGui.Text( _selectedMod.Mod.Meta.Author ); - else + ImGui.SameLine(); + if( ( _selectedMod.Mod.Meta.Author?.Length ?? 0 ) > 0 ) + ImGui.Text( _selectedMod.Mod.Meta.Author ); + else ImGui.Text( "Unknown" ); - + DrawWebsiteText(); ImGui.SetCursorPosY( ImGui.GetCursorPosY() + 10 ); @@ -662,27 +688,32 @@ namespace Penumbra.UI _selectedMod.Enabled = enabled; _plugin.ModManager.Mods.Save(); _plugin.ModManager.CalculateEffectiveFileList(); - } - - DrawEditButtons(); - - DrawGroupSelectors(); - - ImGui.TextWrapped( _selectedMod.Mod.Meta.Description ?? "" ); - - ImGui.BeginTabBar( "PenumbraPluginDetails" ); - if ( (_selectedMod.Mod.Meta.ChangedItems?.Count ?? 0 ) > 0) - { - if( ImGui.BeginTabItem( "Changed Items" ) ) - { - ImGui.SetNextItemWidth( -1 ); - if( ImGui.ListBoxHeader( "###", AutoFillSize ) ) - foreach(var item in _selectedMod.Mod.Meta.ChangedItems) + } + + DrawEditButtons(); + + DrawGroupSelectors(); + + ImGui.BeginTabBar( "PenumbraPluginDetails" ); + + if( _selectedMod.Mod.Meta.Description?.Length > 0 && ImGui.BeginTabItem( "About" ) ) + { + ImGui.TextWrapped( _selectedMod.Mod.Meta.Description ); + ImGui.EndTabItem(); + } + + if( ( _selectedMod.Mod.Meta.ChangedItems?.Count ?? 0 ) > 0 ) + { + if( ImGui.BeginTabItem( "Changed Items" ) ) + { + ImGui.SetNextItemWidth( -1 ); + if( ImGui.ListBoxHeader( "###", AutoFillSize ) ) + foreach( var item in _selectedMod.Mod.Meta.ChangedItems ) ImGui.Selectable( item ); - ImGui.ListBoxFooter(); - ImGui.EndTabItem(); - } - } + ImGui.ListBoxFooter(); + ImGui.EndTabItem(); + } + } if( ImGui.BeginTabItem( "Files" ) ) { @@ -713,6 +744,7 @@ namespace Penumbra.UI ImGui.EndTabItem(); } } + if( _selectedMod.Mod.FileConflicts.Any() ) { if( ImGui.BeginTabItem( "File Conflicts" ) ) diff --git a/Penumbra/Util/Crc32.cs b/Penumbra/Util/Crc32.cs index 401afa25..a1ad3d4c 100644 --- a/Penumbra/Util/Crc32.cs +++ b/Penumbra/Util/Crc32.cs @@ -1,7 +1,5 @@ -using System; -using System.Linq; +using System.Linq; using System.Runtime.CompilerServices; -using Penumbra.Extensions; namespace Penumbra.Util { @@ -10,14 +8,14 @@ namespace Penumbra.Util /// public class Crc32 { - private const uint POLY = 0xedb88320; + private const uint Poly = 0xedb88320; private static readonly uint[] CrcArray = Enumerable.Range( 0, 256 ).Select( i => { var k = ( uint )i; for( var j = 0; j < 8; j++ ) - k = ( k & 1 ) != 0 ? ( k >> 1 ) ^ POLY : k >> 1; + k = ( k & 1 ) != 0 ? ( k >> 1 ) ^ Poly : k >> 1; return k; } ).ToArray();