diff --git a/Penumbra/Game/Enums/Race.cs b/Penumbra/Game/Enums/Race.cs index aea6fcf6..d5ce82fd 100644 --- a/Penumbra/Game/Enums/Race.cs +++ b/Penumbra/Game/Enums/Race.cs @@ -27,6 +27,7 @@ namespace Penumbra.Game.Enums Viera, } + // The combined gender-race-npc numerical code as used by the game. public enum GenderRace : ushort { Unknown = 0, diff --git a/Penumbra/Hooks/GameResourceManagement.cs b/Penumbra/Hooks/GameResourceManagement.cs index 80038be1..0d5db16d 100644 --- a/Penumbra/Hooks/GameResourceManagement.cs +++ b/Penumbra/Hooks/GameResourceManagement.cs @@ -62,6 +62,7 @@ namespace Penumbra.Hooks Marshal.GetDelegateForFunctionPointer< UnloadCharacterResourcePrototype >( unloadCharacterResourceAddress ); } + // Forces the reload of a specific set of 85 files, notably containing the eqp, eqdp, gmp and est tables, by filename. public unsafe void ReloadPlayerResources() { ReloadCharacterResources(); diff --git a/Penumbra/Hooks/MusicManager.cs b/Penumbra/Hooks/MusicManager.cs index 82fc08a1..9778d27e 100644 --- a/Penumbra/Hooks/MusicManager.cs +++ b/Penumbra/Hooks/MusicManager.cs @@ -3,6 +3,8 @@ using Dalamud.Plugin; namespace Penumbra.Hooks { + // Use this to disable streaming of specific soundfiles, + // which will allow replacement of .scd files. public unsafe class MusicManager { private readonly IntPtr _musicManager; diff --git a/Penumbra/Importer/TexToolsImport.cs b/Penumbra/Importer/TexToolsImport.cs index 6588148a..0662c367 100644 --- a/Penumbra/Importer/TexToolsImport.cs +++ b/Penumbra/Importer/TexToolsImport.cs @@ -21,6 +21,8 @@ namespace Penumbra.Importer private const string TempFileName = "textools-import"; private readonly string _resolvedTempFilePath; + public DirectoryInfo? ExtractedDirectory { get; private set; } + public ImporterState State { get; private set; } public long TotalProgress { get; private set; } @@ -161,14 +163,14 @@ namespace Penumbra.Importer // Open the mod data file from the modpack as a SqPackStream using var modData = GetMagicSqPackDeleterStream( extractedModPack, "TTMPD.mpd" ); - var newModFolder = CreateModFolder( _outDirectory, Path.GetFileNameWithoutExtension( modPackFile.Name ) ); + ExtractedDirectory = CreateModFolder( _outDirectory, Path.GetFileNameWithoutExtension( modPackFile.Name ) ); File.WriteAllText( - Path.Combine( newModFolder.FullName, "meta.json" ), + Path.Combine( ExtractedDirectory.FullName, "meta.json" ), JsonConvert.SerializeObject( modMeta ) ); - ExtractSimpleModList( newModFolder, modList, modData ); + ExtractSimpleModList( ExtractedDirectory, modList, modData ); } private void ImportV2ModPack( FileInfo modPackFile, ZipFile extractedModPack, string modRaw ) @@ -228,12 +230,12 @@ namespace Penumbra.Importer // Open the mod data file from the modpack as a SqPackStream using var modData = GetMagicSqPackDeleterStream( extractedModPack, "TTMPD.mpd" ); - var newModFolder = CreateModFolder( _outDirectory, modList.Name ?? "New Mod" ); + ExtractedDirectory = CreateModFolder( _outDirectory, modList.Name ?? "New Mod" ); - File.WriteAllText( Path.Combine( newModFolder.FullName, "meta.json" ), + File.WriteAllText( Path.Combine( ExtractedDirectory.FullName, "meta.json" ), JsonConvert.SerializeObject( modMeta ) ); - ExtractSimpleModList( newModFolder, modList.SimpleModsList ?? Enumerable.Empty< SimpleMod >(), modData ); + ExtractSimpleModList( ExtractedDirectory, modList.SimpleModsList ?? Enumerable.Empty< SimpleMod >(), modData ); } private void ImportExtendedV2ModPack( ZipFile extractedModPack, string modRaw ) @@ -256,11 +258,11 @@ namespace Penumbra.Importer // Open the mod data file from the modpack as a SqPackStream using var modData = GetMagicSqPackDeleterStream( extractedModPack, "TTMPD.mpd" ); - var newModFolder = CreateModFolder( _outDirectory, modList.Name ?? "New Mod" ); + ExtractedDirectory = CreateModFolder( _outDirectory, modList.Name ?? "New Mod" ); if( modList.SimpleModsList != null ) { - ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData ); + ExtractSimpleModList( ExtractedDirectory, modList.SimpleModsList, modData ); } if( modList.ModPackPages == null ) @@ -278,7 +280,7 @@ namespace Penumbra.Importer foreach( var group in page.ModGroups.Where( group => group.GroupName != null && group.OptionList != null ) ) { - var groupFolder = NewOptionDirectory( newModFolder, group.GroupName! ); + var groupFolder = NewOptionDirectory( ExtractedDirectory, group.GroupName! ); if( groupFolder.Exists ) { groupFolder = new DirectoryInfo( groupFolder.FullName + $" ({page.PageIndex})" ); @@ -291,12 +293,12 @@ namespace Penumbra.Importer ExtractSimpleModList( optionFolder, option.ModsJsons!, modData ); } - AddMeta( newModFolder, groupFolder, group, modMeta ); + AddMeta( ExtractedDirectory, groupFolder, group, modMeta ); } } File.WriteAllText( - Path.Combine( newModFolder.FullName, "meta.json" ), + Path.Combine( ExtractedDirectory.FullName, "meta.json" ), JsonConvert.SerializeObject( modMeta, Formatting.Indented ) ); } diff --git a/Penumbra/Importer/TexToolsMeta.cs b/Penumbra/Importer/TexToolsMeta.cs index 8508e90a..b4cc86b4 100644 --- a/Penumbra/Importer/TexToolsMeta.cs +++ b/Penumbra/Importer/TexToolsMeta.cs @@ -13,8 +13,18 @@ using GameData = Penumbra.Game.Enums.GameData; namespace Penumbra.Importer { + // TexTools provices custom generated *.meta files for its modpacks, that contain changes to + // - imc files + // - eqp files + // - gmp files + // - est files + // - eqdp files + // made by the mod. The filename determines to what the changes are applied, and the binary file itself contains changes. + // We parse every *.meta file in a mod and combine all actual changes that do not keep data on default values and that can be applied to the game in a .json. + // TexTools may also generate files that contain non-existing changes, e.g. *.imc files for weapon offhands, which will be ignored. public class TexToolsMeta { + // The info class determines the files or table locations the changes need to apply to from the filename. public class Info { private const string Pt = @"(?'PrimaryType'[a-z]*)"; // language=regex @@ -28,6 +38,7 @@ namespace Penumbra.Importer private const string Slot = @"(_(?'Slot'[a-z]{3}))?"; // language=regex private const string Ext = @"\.meta"; + // These are the valid regexes for .meta files that we are able to support at the moment. private static readonly Regex HousingMeta = new( $"bgcommon/hou/{Pt}/general/{Pi}/{Pir}{Ext}" ); private static readonly Regex CharaMeta = new( $"chara/{Pt}/{Pp}{Pi}(/obj/{St}/{Sp}{Si})?/{File}{Slot}{Ext}" ); diff --git a/Penumbra/Meta/Files/ImcExtensions.cs b/Penumbra/Meta/Files/ImcExtensions.cs index f9d64090..9be5a673 100644 --- a/Penumbra/Meta/Files/ImcExtensions.cs +++ b/Penumbra/Meta/Files/ImcExtensions.cs @@ -14,6 +14,8 @@ namespace Penumbra.Meta.Files { } } + // Imc files are already supported in Lumina, but changing the provided data is not supported. + // We use reflection and extension methods to support changing the data of a given Imc file. public static class ImcExtensions { public static ulong ToInteger( this ImcFile.ImageChangeData imc ) diff --git a/Penumbra/Meta/Files/MetaDefaults.cs b/Penumbra/Meta/Files/MetaDefaults.cs index 4d484de2..df2d6cae 100644 --- a/Penumbra/Meta/Files/MetaDefaults.cs +++ b/Penumbra/Meta/Files/MetaDefaults.cs @@ -9,6 +9,8 @@ using Penumbra.Util; namespace Penumbra.Meta.Files { + // This class manages the default meta files obtained via lumina from the game files themselves. + // On first call, the default version of any supported file will be cached and can be returned without reparsing. public class MetaDefaults { private readonly DalamudPluginInterface _pi; @@ -107,6 +109,7 @@ namespace Penumbra.Meta.Files private FileResource FetchFile( string name ) => _pi.Data.GetFile( name ); + // Check that a given meta manipulation is an actual change to the default value. We don't need to keep changes to default. public bool CheckAgainstDefault( MetaManipulation m ) { return m.Type switch @@ -129,6 +132,7 @@ namespace Penumbra.Meta.Files }; } + // Create a deep copy of a default file as a new file. public object? CreateNewFile( MetaManipulation m ) { return m.Type switch diff --git a/Penumbra/Meta/Files/MetaFilenames.cs b/Penumbra/Meta/Files/MetaFilenames.cs index 1b6272dc..6f33e88f 100644 --- a/Penumbra/Meta/Files/MetaFilenames.cs +++ b/Penumbra/Meta/Files/MetaFilenames.cs @@ -4,6 +4,7 @@ using Penumbra.Util; namespace Penumbra.Meta.Files { + // Contains all filenames for meta changes depending on their parameters. public static class MetaFileNames { public static GamePath Eqp() diff --git a/Penumbra/Meta/Identifier.cs b/Penumbra/Meta/Identifier.cs index 041b5d03..8af19288 100644 --- a/Penumbra/Meta/Identifier.cs +++ b/Penumbra/Meta/Identifier.cs @@ -1,6 +1,10 @@ using System.Runtime.InteropServices; using Penumbra.Game.Enums; + +// A struct for each type of meta change that contains all relevant information, +// to uniquely identify the corresponding file and location for the change. +// The first byte is guaranteed to be the MetaType enum for each case. namespace Penumbra.Meta { public enum MetaType : byte diff --git a/Penumbra/Meta/MetaManipulation.cs b/Penumbra/Meta/MetaManipulation.cs index 01b25b66..d73c7d08 100644 --- a/Penumbra/Meta/MetaManipulation.cs +++ b/Penumbra/Meta/MetaManipulation.cs @@ -12,6 +12,7 @@ using ImcFile = Lumina.Data.Files.ImcFile; namespace Penumbra.Meta { + // Write a single meta manipulation as a Base64string of the 16 bytes defining it. public class MetaManipulationConverter : JsonConverter< MetaManipulation > { public override void WriteJson( JsonWriter writer, MetaManipulation manip, JsonSerializer serializer ) @@ -38,6 +39,10 @@ namespace Penumbra.Meta } } + // A MetaManipulation is a union of a type of Identifier (first 8 bytes, cf. Identifier.cs) + // and the appropriate Value to change the meta entry to (the other 8 bytes). + // Its comparison for sorting and hashes depends only on the identifier. + // The first byte is guaranteed to be a MetaType enum value in any case, so Type can always be read. [StructLayout( LayoutKind.Explicit )] [JsonConverter( typeof( MetaManipulationConverter ) )] public struct MetaManipulation : IComparable @@ -219,11 +224,11 @@ namespace Penumbra.Meta { return Type switch { - MetaType.Eqp => $"EQP - {EqpIdentifier}", - MetaType.Eqdp => $"EQDP - {EqdpIdentifier}", - MetaType.Est => $"EST - {EstIdentifier}", - MetaType.Gmp => $"GMP - {GmpIdentifier}", - MetaType.Imc => $"IMC - {ImcIdentifier}", + MetaType.Eqp => EqpIdentifier.ToString(), + MetaType.Eqdp => EqdpIdentifier.ToString(), + MetaType.Est => EstIdentifier.ToString(), + MetaType.Gmp => GmpIdentifier.ToString(), + MetaType.Imc => ImcIdentifier.ToString(), _ => throw new InvalidEnumArgumentException(), }; } diff --git a/Penumbra/Mod/Mod.cs b/Penumbra/Mod/Mod.cs index 75db53b7..8040dee7 100644 --- a/Penumbra/Mod/Mod.cs +++ b/Penumbra/Mod/Mod.cs @@ -4,6 +4,8 @@ using Penumbra.Util; namespace Penumbra.Mod { + // A complete Mod containing settings (i.e. dependent on a collection) + // and the resulting cache. public class Mod { public ModSettings Settings { get; } diff --git a/Penumbra/Mod/ModCache.cs b/Penumbra/Mod/ModCache.cs index ca6649e7..8ea4284d 100644 --- a/Penumbra/Mod/ModCache.cs +++ b/Penumbra/Mod/ModCache.cs @@ -5,6 +5,7 @@ using Penumbra.Util; namespace Penumbra.Mod { + // The ModCache contains volatile information dependent on all current settings in a collection. public class ModCache { public Dictionary< Mod, (List< GamePath > Files, List< MetaManipulation > Manipulations) > Conflicts { get; private set; } = new(); diff --git a/Penumbra/Mod/ModData.cs b/Penumbra/Mod/ModData.cs index a5fb3f46..94d12ae0 100644 --- a/Penumbra/Mod/ModData.cs +++ b/Penumbra/Mod/ModData.cs @@ -3,6 +3,9 @@ using Dalamud.Plugin; namespace Penumbra.Mod { + // ModData contains all permanent information about a mod, + // and is independent of collections or settings. + // It only changes when the user actively changes the mod or their filesystem. public class ModData { public DirectoryInfo BasePath; diff --git a/Penumbra/Mod/ModFunctions.cs b/Penumbra/Mod/ModFunctions.cs index 74a6d4b4..bb022e47 100644 --- a/Penumbra/Mod/ModFunctions.cs +++ b/Penumbra/Mod/ModFunctions.cs @@ -6,6 +6,7 @@ using Penumbra.Util; namespace Penumbra.Mod { + // Functions that do not really depend on only one component of a mod. public static class ModFunctions { public static bool CleanUpCollection( Dictionary< string, ModSettings > settings, IEnumerable< DirectoryInfo > modPaths ) diff --git a/Penumbra/Mod/ModMeta.cs b/Penumbra/Mod/ModMeta.cs index 49ae549c..117cd1a8 100644 --- a/Penumbra/Mod/ModMeta.cs +++ b/Penumbra/Mod/ModMeta.cs @@ -58,7 +58,6 @@ namespace Penumbra.Mod return true; } - public static ModMeta? LoadFromFile( FileInfo filePath ) { try diff --git a/Penumbra/Mod/ModSettings.cs b/Penumbra/Mod/ModSettings.cs index e40448ca..d24b6c0b 100644 --- a/Penumbra/Mod/ModSettings.cs +++ b/Penumbra/Mod/ModSettings.cs @@ -5,6 +5,7 @@ using Penumbra.Structs; namespace Penumbra.Mod { + // Contains the settings for a given mod. public class ModSettings { public bool Enabled { get; set; } diff --git a/Penumbra/Mod/NamedModSettings.cs b/Penumbra/Mod/NamedModSettings.cs index 34e3f883..9e812ef5 100644 --- a/Penumbra/Mod/NamedModSettings.cs +++ b/Penumbra/Mod/NamedModSettings.cs @@ -4,6 +4,9 @@ using Penumbra.Structs; namespace Penumbra.Mod { + // Contains settings with the option selections stored by names instead of index. + // This is meant to make them possibly more portable when we support importing collections from other users. + // Enabled does not exist, because disabled mods would not be exported in this way. public class NamedModSettings { public int Priority { get; set; } diff --git a/Penumbra/Mods/ModCollection.cs b/Penumbra/Mods/ModCollection.cs index 1aa0e390..df524d2a 100644 --- a/Penumbra/Mods/ModCollection.cs +++ b/Penumbra/Mods/ModCollection.cs @@ -9,6 +9,10 @@ using Penumbra.Util; namespace Penumbra.Mods { + // A ModCollection is a named set of ModSettings to all of the users' installed mods. + // It is meant to be local only, and thus should always contain settings for every mod, not just the enabled ones. + // Settings to mods that are not installed anymore are kept as long as no call to CleanUnavailableSettings is made. + // Active ModCollections build a cache of currently relevant data. public class ModCollection { public const string DefaultCollection = "Default"; diff --git a/Penumbra/Mods/ModCollectionCache.cs b/Penumbra/Mods/ModCollectionCache.cs index 217cf950..ea34db2a 100644 --- a/Penumbra/Mods/ModCollectionCache.cs +++ b/Penumbra/Mods/ModCollectionCache.cs @@ -8,6 +8,8 @@ using Penumbra.Util; namespace Penumbra.Mods { + // The ModCollectionCache contains all required temporary data to use a collection. + // It will only be setup if a collection gets activated in any way. public class ModCollectionCache { public readonly List< Mod.Mod > AvailableMods = new(); diff --git a/Penumbra/Mods/ModManager.cs b/Penumbra/Mods/ModManager.cs index 58da8881..0f7d14f7 100644 --- a/Penumbra/Mods/ModManager.cs +++ b/Penumbra/Mods/ModManager.cs @@ -9,6 +9,11 @@ using Penumbra.Util; namespace Penumbra.Mods { + // The ModManager handles the basic mods installed to the mod directory, + // as well as all saved collections. + // It also handles manual changes to mods that require changes in all collections, + // updating the state of a mod from the filesystem, + // and collection swapping. public class ModManager { private readonly Plugin _plugin; @@ -216,7 +221,6 @@ namespace Penumbra.Mods return true; } - public bool UpdateMod( ModData mod, bool recomputeMeta = false ) { var oldName = mod.Meta.Name; diff --git a/Penumbra/Mods/ModManagerEditExtensions.cs b/Penumbra/Mods/ModManagerEditExtensions.cs index 8ec686c8..d281d710 100644 --- a/Penumbra/Mods/ModManagerEditExtensions.cs +++ b/Penumbra/Mods/ModManagerEditExtensions.cs @@ -9,6 +9,8 @@ using Penumbra.Structs; namespace Penumbra.Mods { + // Extracted to keep the main file a bit more clean. + // Contains all change functions on a specific mod that also require corresponding changes to collections. public static class ModManagerEditExtensions { public static bool RenameMod( this ModManager manager, string newName, ModData mod ) diff --git a/Penumbra/UI/Custom/ImGuiFramedGroup.cs b/Penumbra/UI/Custom/ImGuiFramedGroup.cs index 6f0f903f..74e00b29 100644 --- a/Penumbra/UI/Custom/ImGuiFramedGroup.cs +++ b/Penumbra/UI/Custom/ImGuiFramedGroup.cs @@ -76,7 +76,7 @@ namespace Penumbra.UI.Custom var itemWidth = ImGui.CalcItemWidth(); ImGui.PushItemWidth( Math.Max( 0f, itemWidth - frameHeight ) ); - labelStack.Add( ( labelMin, labelMax ) ); + LabelStack.Add( ( labelMin, labelMax ) ); return ret; } @@ -111,8 +111,8 @@ namespace Penumbra.UI.Custom var itemMin = ImGui.GetItemRectMin(); var itemMax = ImGui.GetItemRectMax(); - var (currentLabelMin, currentLabelMax) = labelStack[ labelStack.Count - 1 ]; - labelStack.RemoveAt( labelStack.Count - 1 ); + var (currentLabelMin, currentLabelMax) = LabelStack[ LabelStack.Count - 1 ]; + LabelStack.RemoveAt( LabelStack.Count - 1 ); var halfFrame = new Vector2( frameHeight / 8, frameHeight / 2 ); currentLabelMin.X -= itemSpacing.X; @@ -143,6 +143,6 @@ namespace Penumbra.UI.Custom private static readonly Vector2 ZeroVector = new( 0, 0 ); - private static readonly List< (Vector2, Vector2) > labelStack = new(); + private static readonly List< (Vector2, Vector2) > LabelStack = new(); } } \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabImport.cs b/Penumbra/UI/MenuTabs/TabImport.cs index 79259731..20f5bb48 100644 --- a/Penumbra/UI/MenuTabs/TabImport.cs +++ b/Penumbra/UI/MenuTabs/TabImport.cs @@ -76,8 +76,13 @@ namespace Penumbra.UI } } + var directory = _texToolsImport?.ExtractedDirectory; _texToolsImport = null; _base.ReloadMods(); + if( directory != null ) + { + _base._menu.InstalledTab.Selector.SelectModByDir( directory.Name ); + } } } catch( Exception e ) diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs index 82c40a66..f18e3fed 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs @@ -88,7 +88,7 @@ namespace Penumbra.UI { _base = ui; _modNamesLower = Array.Empty< string >(); - _modManager = Service.Get(); + _modManager = Service< ModManager >.Get(); ResetModNamesLower(); }