mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Some mod movement.
This commit is contained in:
parent
c12dbf3f8a
commit
577669b21f
26 changed files with 726 additions and 732 deletions
|
|
@ -828,7 +828,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
public PenumbraApiEc CreateNamedTemporaryCollection(string name)
|
public PenumbraApiEc CreateNamedTemporaryCollection(string name)
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (name.Length == 0 || Mod.Creator.ReplaceBadXivSymbols(name) != name)
|
if (name.Length == 0 || ModCreator.ReplaceBadXivSymbols(name) != name)
|
||||||
return PenumbraApiEc.InvalidArgument;
|
return PenumbraApiEc.InvalidArgument;
|
||||||
|
|
||||||
return _tempCollections.CreateTemporaryCollection(name).Length > 0
|
return _tempCollections.CreateTemporaryCollection(name).Length > 0
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using OtterGui.Widgets;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Import.Structs;
|
using Penumbra.Import.Structs;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Mods.Manager;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ public partial class TexToolsImporter
|
||||||
};
|
};
|
||||||
Penumbra.Log.Information( $" -> Importing {archive.Type} Archive." );
|
Penumbra.Log.Information( $" -> Importing {archive.Type} Archive." );
|
||||||
|
|
||||||
_currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, Path.GetRandomFileName() );
|
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, Path.GetRandomFileName() );
|
||||||
var options = new ExtractionOptions()
|
var options = new ExtractionOptions()
|
||||||
{
|
{
|
||||||
ExtractFullPath = true,
|
ExtractFullPath = true,
|
||||||
|
|
@ -100,18 +100,18 @@ public partial class TexToolsImporter
|
||||||
// Use either the top-level directory as the mods base name, or the (fixed for path) name in the json.
|
// Use either the top-level directory as the mods base name, or the (fixed for path) name in the json.
|
||||||
if( leadDir )
|
if( leadDir )
|
||||||
{
|
{
|
||||||
_currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, baseName, false );
|
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, baseName, false );
|
||||||
Directory.Move( Path.Combine( oldName, baseName ), _currentModDirectory.FullName );
|
Directory.Move( Path.Combine( oldName, baseName ), _currentModDirectory.FullName );
|
||||||
Directory.Delete( oldName );
|
Directory.Delete( oldName );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, name, false );
|
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, name, false );
|
||||||
Directory.Move( oldName, _currentModDirectory.FullName );
|
Directory.Move( oldName, _currentModDirectory.FullName );
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentModDirectory.Refresh();
|
_currentModDirectory.Refresh();
|
||||||
Mod.Creator.SplitMultiGroups( _currentModDirectory );
|
ModCreator.SplitMultiGroups( _currentModDirectory );
|
||||||
|
|
||||||
return _currentModDirectory;
|
return _currentModDirectory;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,14 +33,14 @@ public partial class TexToolsImporter
|
||||||
|
|
||||||
var modList = modListRaw.Select( m => JsonConvert.DeserializeObject< SimpleMod >( m, JsonSettings )! ).ToList();
|
var modList = modListRaw.Select( m => JsonConvert.DeserializeObject< SimpleMod >( m, JsonSettings )! ).ToList();
|
||||||
|
|
||||||
_currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, Path.GetFileNameWithoutExtension( modPackFile.Name ) );
|
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, Path.GetFileNameWithoutExtension( modPackFile.Name ) );
|
||||||
// Create a new ModMeta from the TTMP mod list info
|
// Create a new ModMeta from the TTMP mod list info
|
||||||
_modManager.DataEditor.CreateMeta( _currentModDirectory, _currentModName, DefaultTexToolsData.Author, DefaultTexToolsData.Description, null, null );
|
_modManager.DataEditor.CreateMeta( _currentModDirectory, _currentModName, DefaultTexToolsData.Author, DefaultTexToolsData.Description, null, null );
|
||||||
|
|
||||||
// Open the mod data file from the mod pack as a SqPackStream
|
// Open the mod data file from the mod pack as a SqPackStream
|
||||||
_streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" );
|
_streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" );
|
||||||
ExtractSimpleModList( _currentModDirectory, modList );
|
ExtractSimpleModList( _currentModDirectory, modList );
|
||||||
Mod.Creator.CreateDefaultFiles( _currentModDirectory );
|
ModCreator.CreateDefaultFiles( _currentModDirectory );
|
||||||
ResetStreamDisposer();
|
ResetStreamDisposer();
|
||||||
return _currentModDirectory;
|
return _currentModDirectory;
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +89,7 @@ public partial class TexToolsImporter
|
||||||
_currentOptionName = DefaultTexToolsData.DefaultOption;
|
_currentOptionName = DefaultTexToolsData.DefaultOption;
|
||||||
Penumbra.Log.Information( " -> Importing Simple V2 ModPack" );
|
Penumbra.Log.Information( " -> Importing Simple V2 ModPack" );
|
||||||
|
|
||||||
_currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, _currentModName );
|
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, _currentModName );
|
||||||
_modManager.DataEditor.CreateMeta( _currentModDirectory, _currentModName, modList.Author, string.IsNullOrEmpty( modList.Description )
|
_modManager.DataEditor.CreateMeta( _currentModDirectory, _currentModName, modList.Author, string.IsNullOrEmpty( modList.Description )
|
||||||
? "Mod imported from TexTools mod pack"
|
? "Mod imported from TexTools mod pack"
|
||||||
: modList.Description, modList.Version, modList.Url );
|
: modList.Description, modList.Version, modList.Url );
|
||||||
|
|
@ -97,7 +97,7 @@ public partial class TexToolsImporter
|
||||||
// Open the mod data file from the mod pack as a SqPackStream
|
// Open the mod data file from the mod pack as a SqPackStream
|
||||||
_streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" );
|
_streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" );
|
||||||
ExtractSimpleModList( _currentModDirectory, modList.SimpleModsList );
|
ExtractSimpleModList( _currentModDirectory, modList.SimpleModsList );
|
||||||
Mod.Creator.CreateDefaultFiles( _currentModDirectory );
|
ModCreator.CreateDefaultFiles( _currentModDirectory );
|
||||||
ResetStreamDisposer();
|
ResetStreamDisposer();
|
||||||
return _currentModDirectory;
|
return _currentModDirectory;
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +134,7 @@ public partial class TexToolsImporter
|
||||||
_currentNumOptions = GetOptionCount( modList );
|
_currentNumOptions = GetOptionCount( modList );
|
||||||
_currentModName = modList.Name;
|
_currentModName = modList.Name;
|
||||||
|
|
||||||
_currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, _currentModName );
|
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, _currentModName );
|
||||||
_modManager.DataEditor.CreateMeta( _currentModDirectory, _currentModName, modList.Author, modList.Description, modList.Version, modList.Url );
|
_modManager.DataEditor.CreateMeta( _currentModDirectory, _currentModName, modList.Author, modList.Description, modList.Version, modList.Url );
|
||||||
|
|
||||||
if( _currentNumOptions == 0 )
|
if( _currentNumOptions == 0 )
|
||||||
|
|
@ -172,7 +172,7 @@ public partial class TexToolsImporter
|
||||||
{
|
{
|
||||||
var name = numGroups == 1 ? _currentGroupName : $"{_currentGroupName}, Part {groupId + 1}";
|
var name = numGroups == 1 ? _currentGroupName : $"{_currentGroupName}, Part {groupId + 1}";
|
||||||
options.Clear();
|
options.Clear();
|
||||||
var groupFolder = Mod.Creator.NewSubFolderName( _currentModDirectory, name )
|
var groupFolder = ModCreator.NewSubFolderName( _currentModDirectory, name )
|
||||||
?? new DirectoryInfo( Path.Combine( _currentModDirectory.FullName,
|
?? new DirectoryInfo( Path.Combine( _currentModDirectory.FullName,
|
||||||
numGroups == 1 ? $"Group {groupPriority + 1}" : $"Group {groupPriority + 1}, Part {groupId + 1}" ) );
|
numGroups == 1 ? $"Group {groupPriority + 1}" : $"Group {groupPriority + 1}, Part {groupId + 1}" ) );
|
||||||
|
|
||||||
|
|
@ -182,10 +182,10 @@ public partial class TexToolsImporter
|
||||||
var option = allOptions[ i + optionIdx ];
|
var option = allOptions[ i + optionIdx ];
|
||||||
_token.ThrowIfCancellationRequested();
|
_token.ThrowIfCancellationRequested();
|
||||||
_currentOptionName = option.Name;
|
_currentOptionName = option.Name;
|
||||||
var optionFolder = Mod.Creator.NewSubFolderName( groupFolder, option.Name )
|
var optionFolder = ModCreator.NewSubFolderName( groupFolder, option.Name )
|
||||||
?? new DirectoryInfo( Path.Combine( groupFolder.FullName, $"Option {i + optionIdx + 1}" ) );
|
?? new DirectoryInfo( Path.Combine( groupFolder.FullName, $"Option {i + optionIdx + 1}" ) );
|
||||||
ExtractSimpleModList( optionFolder, option.ModsJsons );
|
ExtractSimpleModList( optionFolder, option.ModsJsons );
|
||||||
options.Add( Mod.Creator.CreateSubMod( _currentModDirectory, optionFolder, option ) );
|
options.Add( ModCreator.CreateSubMod( _currentModDirectory, optionFolder, option ) );
|
||||||
if( option.IsChecked )
|
if( option.IsChecked )
|
||||||
{
|
{
|
||||||
defaultSettings = group.SelectionType == GroupType.Multi
|
defaultSettings = group.SelectionType == GroupType.Multi
|
||||||
|
|
@ -206,12 +206,12 @@ public partial class TexToolsImporter
|
||||||
if( empty != null )
|
if( empty != null )
|
||||||
{
|
{
|
||||||
_currentOptionName = empty.Name;
|
_currentOptionName = empty.Name;
|
||||||
options.Insert( 0, Mod.Creator.CreateEmptySubMod( empty.Name ) );
|
options.Insert( 0, ModCreator.CreateEmptySubMod( empty.Name ) );
|
||||||
defaultSettings = defaultSettings == null ? 0 : defaultSettings.Value + 1;
|
defaultSettings = defaultSettings == null ? 0 : defaultSettings.Value + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Mod.Creator.CreateOptionGroup( _currentModDirectory, group.SelectionType, name, groupPriority, groupPriority,
|
ModCreator.CreateOptionGroup( _currentModDirectory, group.SelectionType, name, groupPriority, groupPriority,
|
||||||
defaultSettings ?? 0, group.Description, options );
|
defaultSettings ?? 0, group.Description, options );
|
||||||
++groupPriority;
|
++groupPriority;
|
||||||
}
|
}
|
||||||
|
|
@ -219,7 +219,7 @@ public partial class TexToolsImporter
|
||||||
}
|
}
|
||||||
|
|
||||||
ResetStreamDisposer();
|
ResetStreamDisposer();
|
||||||
Mod.Creator.CreateDefaultFiles( _currentModDirectory );
|
ModCreator.CreateDefaultFiles( _currentModDirectory );
|
||||||
return _currentModDirectory;
|
return _currentModDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Penumbra.Mods.Manager;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
|
||||||
|
|
@ -181,10 +181,10 @@ public class ModNormalizer
|
||||||
for (var i = _redirections[groupIdx + 1].Count; i < group.Count; ++i)
|
for (var i = _redirections[groupIdx + 1].Count; i < group.Count; ++i)
|
||||||
_redirections[groupIdx + 1].Add(new Dictionary<Utf8GamePath, FullPath>());
|
_redirections[groupIdx + 1].Add(new Dictionary<Utf8GamePath, FullPath>());
|
||||||
|
|
||||||
var groupDir = Mod.Creator.CreateModFolder(directory, group.Name);
|
var groupDir = ModCreator.CreateModFolder(directory, group.Name);
|
||||||
foreach (var option in group.OfType<SubMod>())
|
foreach (var option in group.OfType<SubMod>())
|
||||||
{
|
{
|
||||||
var optionDir = Mod.Creator.CreateModFolder(groupDir, option.Name);
|
var optionDir = ModCreator.CreateModFolder(groupDir, option.Name);
|
||||||
|
|
||||||
newDict = _redirections[groupIdx + 1][option.OptionIdx];
|
newDict = _redirections[groupIdx + 1][option.OptionIdx];
|
||||||
newDict.Clear();
|
newDict.Clear();
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods.Manager;
|
||||||
|
|
||||||
public class ModCacheManager : IDisposable, IReadOnlyList<ModCache>
|
public class ModCacheManager : IDisposable, IReadOnlyList<ModCache>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ using OtterGui.Classes;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods.Manager;
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum ModDataChangeType : ushort
|
public enum ModDataChangeType : ushort
|
||||||
|
|
@ -29,22 +29,20 @@ public enum ModDataChangeType : ushort
|
||||||
|
|
||||||
public class ModDataEditor
|
public class ModDataEditor
|
||||||
{
|
{
|
||||||
private readonly FilenameService _filenameService;
|
|
||||||
private readonly SaveService _saveService;
|
private readonly SaveService _saveService;
|
||||||
private readonly CommunicatorService _communicatorService;
|
private readonly CommunicatorService _communicatorService;
|
||||||
|
|
||||||
public ModDataEditor(FilenameService filenameService, SaveService saveService, CommunicatorService communicatorService)
|
public ModDataEditor(SaveService saveService, CommunicatorService communicatorService)
|
||||||
{
|
{
|
||||||
_filenameService = filenameService;
|
|
||||||
_saveService = saveService;
|
_saveService = saveService;
|
||||||
_communicatorService = communicatorService;
|
_communicatorService = communicatorService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string MetaFile(Mod mod)
|
public string MetaFile(Mod mod)
|
||||||
=> _filenameService.ModMetaPath(mod);
|
=> _saveService.FileNames.ModMetaPath(mod);
|
||||||
|
|
||||||
public string DataFile(Mod mod)
|
public string DataFile(Mod mod)
|
||||||
=> _filenameService.LocalDataFile(mod);
|
=> _saveService.FileNames.LocalDataFile(mod);
|
||||||
|
|
||||||
/// <summary> Create the file containing the meta information about a mod from scratch. </summary>
|
/// <summary> Create the file containing the meta information about a mod from scratch. </summary>
|
||||||
public void CreateMeta(DirectoryInfo directory, string? name, string? author, string? description, string? version,
|
public void CreateMeta(DirectoryInfo directory, string? name, string? author, string? description, string? version,
|
||||||
|
|
@ -56,12 +54,12 @@ public class ModDataEditor
|
||||||
mod.Description = description ?? mod.Description;
|
mod.Description = description ?? mod.Description;
|
||||||
mod.Version = version ?? mod.Version;
|
mod.Version = version ?? mod.Version;
|
||||||
mod.Website = website ?? mod.Website;
|
mod.Website = website ?? mod.Website;
|
||||||
_saveService.ImmediateSave(new Mod.ModMeta(mod));
|
_saveService.ImmediateSave(new ModMeta(mod));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModDataChangeType LoadLocalData(Mod mod)
|
public ModDataChangeType LoadLocalData(Mod mod)
|
||||||
{
|
{
|
||||||
var dataFile = _filenameService.LocalDataFile(mod);
|
var dataFile = _saveService.FileNames.LocalDataFile(mod);
|
||||||
|
|
||||||
var importDate = 0L;
|
var importDate = 0L;
|
||||||
var localTags = Enumerable.Empty<string>();
|
var localTags = Enumerable.Empty<string>();
|
||||||
|
|
@ -98,7 +96,7 @@ public class ModDataEditor
|
||||||
changes |= ModDataChangeType.ImportDate;
|
changes |= ModDataChangeType.ImportDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
changes |= mod.UpdateTags(null, localTags);
|
changes |= ModLocalData.UpdateTags(mod, null, localTags);
|
||||||
|
|
||||||
if (mod.Favorite != favorite)
|
if (mod.Favorite != favorite)
|
||||||
{
|
{
|
||||||
|
|
@ -113,14 +111,14 @@ public class ModDataEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
if (save)
|
if (save)
|
||||||
_saveService.QueueSave(new Mod.ModData(mod));
|
_saveService.QueueSave(new ModLocalData(mod));
|
||||||
|
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModDataChangeType LoadMeta(Mod mod)
|
public ModDataChangeType LoadMeta(Mod mod)
|
||||||
{
|
{
|
||||||
var metaFile = _filenameService.ModMetaPath(mod);
|
var metaFile = _saveService.FileNames.ModMetaPath(mod);
|
||||||
if (!File.Exists(metaFile))
|
if (!File.Exists(metaFile))
|
||||||
{
|
{
|
||||||
Penumbra.Log.Debug($"No mod meta found for {mod.ModPath.Name}.");
|
Penumbra.Log.Debug($"No mod meta found for {mod.ModPath.Name}.");
|
||||||
|
|
@ -137,7 +135,7 @@ public class ModDataEditor
|
||||||
var newDescription = json[nameof(Mod.Description)]?.Value<string>() ?? string.Empty;
|
var newDescription = json[nameof(Mod.Description)]?.Value<string>() ?? string.Empty;
|
||||||
var newVersion = json[nameof(Mod.Version)]?.Value<string>() ?? string.Empty;
|
var newVersion = json[nameof(Mod.Version)]?.Value<string>() ?? string.Empty;
|
||||||
var newWebsite = json[nameof(Mod.Website)]?.Value<string>() ?? string.Empty;
|
var newWebsite = json[nameof(Mod.Website)]?.Value<string>() ?? string.Empty;
|
||||||
var newFileVersion = json[nameof(Mod.ModMeta.FileVersion)]?.Value<uint>() ?? 0;
|
var newFileVersion = json[nameof(ModMeta.FileVersion)]?.Value<uint>() ?? 0;
|
||||||
var importDate = json[nameof(Mod.ImportDate)]?.Value<long>();
|
var importDate = json[nameof(Mod.ImportDate)]?.Value<long>();
|
||||||
var modTags = json[nameof(Mod.ModTags)]?.Values<string>().OfType<string>();
|
var modTags = json[nameof(Mod.ModTags)]?.Values<string>().OfType<string>();
|
||||||
|
|
||||||
|
|
@ -172,13 +170,11 @@ public class ModDataEditor
|
||||||
mod.Website = newWebsite;
|
mod.Website = newWebsite;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newFileVersion != Mod.ModMeta.FileVersion)
|
if (newFileVersion != ModMeta.FileVersion)
|
||||||
{
|
if (ModMigration.Migrate(_saveService, mod, json, ref newFileVersion))
|
||||||
if (Mod.Migration.Migrate(mod, json, ref newFileVersion))
|
|
||||||
{
|
{
|
||||||
changes |= ModDataChangeType.Migration;
|
changes |= ModDataChangeType.Migration;
|
||||||
_saveService.ImmediateSave(new Mod.ModMeta(mod));
|
_saveService.ImmediateSave(new ModMeta(mod));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (importDate != null && mod.ImportDate != importDate.Value)
|
if (importDate != null && mod.ImportDate != importDate.Value)
|
||||||
|
|
@ -187,7 +183,7 @@ public class ModDataEditor
|
||||||
changes |= ModDataChangeType.ImportDate;
|
changes |= ModDataChangeType.ImportDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
changes |= mod.UpdateTags(modTags, null);
|
changes |= ModLocalData.UpdateTags(mod, modTags, null);
|
||||||
|
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +201,7 @@ public class ModDataEditor
|
||||||
|
|
||||||
var oldName = mod.Name;
|
var oldName = mod.Name;
|
||||||
mod.Name = newName;
|
mod.Name = newName;
|
||||||
_saveService.QueueSave(new Mod.ModMeta(mod));
|
_saveService.QueueSave(new ModMeta(mod));
|
||||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Name, mod, oldName.Text);
|
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Name, mod, oldName.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,7 +211,7 @@ public class ModDataEditor
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mod.Author = newAuthor;
|
mod.Author = newAuthor;
|
||||||
_saveService.QueueSave(new Mod.ModMeta(mod));
|
_saveService.QueueSave(new ModMeta(mod));
|
||||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Author, mod, null);
|
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Author, mod, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,7 +221,7 @@ public class ModDataEditor
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mod.Description = newDescription;
|
mod.Description = newDescription;
|
||||||
_saveService.QueueSave(new Mod.ModMeta(mod));
|
_saveService.QueueSave(new ModMeta(mod));
|
||||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Description, mod, null);
|
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Description, mod, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,7 +231,7 @@ public class ModDataEditor
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mod.Version = newVersion;
|
mod.Version = newVersion;
|
||||||
_saveService.QueueSave(new Mod.ModMeta(mod));
|
_saveService.QueueSave(new ModMeta(mod));
|
||||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Version, mod, null);
|
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Version, mod, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -245,7 +241,7 @@ public class ModDataEditor
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mod.Website = newWebsite;
|
mod.Website = newWebsite;
|
||||||
_saveService.QueueSave(new Mod.ModMeta(mod));
|
_saveService.QueueSave(new ModMeta(mod));
|
||||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Website, mod, null);
|
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Website, mod, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,7 +257,7 @@ public class ModDataEditor
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mod.Favorite = state;
|
mod.Favorite = state;
|
||||||
_saveService.QueueSave(new Mod.ModData(mod));
|
_saveService.QueueSave(new ModLocalData(mod));
|
||||||
;
|
;
|
||||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Favorite, mod, null);
|
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Favorite, mod, null);
|
||||||
}
|
}
|
||||||
|
|
@ -272,7 +268,7 @@ public class ModDataEditor
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mod.Note = newNote;
|
mod.Note = newNote;
|
||||||
_saveService.QueueSave(new Mod.ModData(mod));
|
_saveService.QueueSave(new ModLocalData(mod));
|
||||||
;
|
;
|
||||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Favorite, mod, null);
|
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Favorite, mod, null);
|
||||||
}
|
}
|
||||||
|
|
@ -287,20 +283,20 @@ public class ModDataEditor
|
||||||
ModDataChangeType flags = 0;
|
ModDataChangeType flags = 0;
|
||||||
if (tagIdx == which.Count)
|
if (tagIdx == which.Count)
|
||||||
{
|
{
|
||||||
flags = mod.UpdateTags(local ? null : which.Append(newTag), local ? which.Append(newTag) : null);
|
flags = ModLocalData.UpdateTags(mod, local ? null : which.Append(newTag), local ? which.Append(newTag) : null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var tmp = which.ToArray();
|
var tmp = which.ToArray();
|
||||||
tmp[tagIdx] = newTag;
|
tmp[tagIdx] = newTag;
|
||||||
flags = mod.UpdateTags(local ? null : tmp, local ? tmp : null);
|
flags = ModLocalData.UpdateTags(mod, local ? null : tmp, local ? tmp : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags.HasFlag(ModDataChangeType.ModTags))
|
if (flags.HasFlag(ModDataChangeType.ModTags))
|
||||||
_saveService.QueueSave(new Mod.ModMeta(mod));
|
_saveService.QueueSave(new ModMeta(mod));
|
||||||
|
|
||||||
if (flags.HasFlag(ModDataChangeType.LocalTags))
|
if (flags.HasFlag(ModDataChangeType.LocalTags))
|
||||||
_saveService.QueueSave(new Mod.ModData(mod));
|
_saveService.QueueSave(new ModLocalData(mod));
|
||||||
|
|
||||||
if (flags != 0)
|
if (flags != 0)
|
||||||
_communicatorService.ModDataChanged.Invoke(flags, mod, null);
|
_communicatorService.ModDataChanged.Invoke(flags, mod, null);
|
||||||
|
|
@ -308,8 +304,8 @@ public class ModDataEditor
|
||||||
|
|
||||||
public void MoveDataFile(DirectoryInfo oldMod, DirectoryInfo newMod)
|
public void MoveDataFile(DirectoryInfo oldMod, DirectoryInfo newMod)
|
||||||
{
|
{
|
||||||
var oldFile = _filenameService.LocalDataFile(oldMod.Name);
|
var oldFile = _saveService.FileNames.LocalDataFile(oldMod.Name);
|
||||||
var newFile = _filenameService.LocalDataFile(newMod.Name);
|
var newFile = _saveService.FileNames.LocalDataFile(newMod.Name);
|
||||||
if (!File.Exists(oldFile))
|
if (!File.Exists(oldFile))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,10 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.Mods.Manager;
|
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods.Manager;
|
||||||
|
|
||||||
public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable
|
public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable
|
||||||
{
|
{
|
||||||
|
|
@ -5,7 +5,7 @@ using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods.Manager;
|
||||||
|
|
||||||
/// <summary> Describes the state of a potential move-target for a mod. </summary>
|
/// <summary> Describes the state of a potential move-target for a mod. </summary>
|
||||||
public enum NewDirectoryState
|
public enum NewDirectoryState
|
||||||
|
|
@ -73,7 +73,7 @@ public sealed class ModManager : ModStorage
|
||||||
if (this.Any(m => m.ModPath.Name == modFolder.Name))
|
if (this.Any(m => m.ModPath.Name == modFolder.Name))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Mod.Creator.SplitMultiGroups(modFolder);
|
ModCreator.SplitMultiGroups(modFolder);
|
||||||
var mod = Mod.LoadMod(this, modFolder, true);
|
var mod = Mod.LoadMod(this, modFolder, true);
|
||||||
if (mod == null)
|
if (mod == null)
|
||||||
return;
|
return;
|
||||||
|
|
@ -206,7 +206,7 @@ public sealed class ModManager : ModStorage
|
||||||
if (oldName == newName)
|
if (oldName == newName)
|
||||||
return NewDirectoryState.Identical;
|
return NewDirectoryState.Identical;
|
||||||
|
|
||||||
var fixedNewName = Mod.Creator.ReplaceBadXivSymbols(newName);
|
var fixedNewName = ModCreator.ReplaceBadXivSymbols(newName);
|
||||||
if (fixedNewName != newName)
|
if (fixedNewName != newName)
|
||||||
return NewDirectoryState.ContainsInvalidSymbols;
|
return NewDirectoryState.ContainsInvalidSymbols;
|
||||||
|
|
||||||
|
|
|
||||||
244
Penumbra/Mods/Manager/ModMigration.cs
Normal file
244
Penumbra/Mods/Manager/ModMigration.cs
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using OtterGui;
|
||||||
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Mods.Manager;
|
||||||
|
|
||||||
|
public static partial class ModMigration
|
||||||
|
{
|
||||||
|
[GeneratedRegex(@"group_\d{3}_", RegexOptions.Compiled | RegexOptions.NonBacktracking | RegexOptions.ExplicitCapture)]
|
||||||
|
private static partial Regex GroupRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex("^group_", RegexOptions.Compiled)]
|
||||||
|
private static partial Regex GroupStartRegex();
|
||||||
|
|
||||||
|
public static bool Migrate(SaveService saveService, Mod mod, JObject json, ref uint fileVersion)
|
||||||
|
=> MigrateV0ToV1(saveService, mod, json, ref fileVersion) || MigrateV1ToV2(mod, ref fileVersion) || MigrateV2ToV3(mod, ref fileVersion);
|
||||||
|
|
||||||
|
private static bool MigrateV2ToV3(Mod _, ref uint fileVersion)
|
||||||
|
{
|
||||||
|
if (fileVersion > 2)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Remove import time.
|
||||||
|
fileVersion = 3;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MigrateV1ToV2(Mod mod, ref uint fileVersion)
|
||||||
|
{
|
||||||
|
if (fileVersion > 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!mod.GroupFiles.All(g => GroupRegex().IsMatch(g.Name)))
|
||||||
|
foreach (var (group, index) in mod.GroupFiles.WithIndex().ToArray())
|
||||||
|
{
|
||||||
|
var newName = GroupStartRegex().Replace(group.Name, $"group_{index + 1:D3}_");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (newName != group.Name)
|
||||||
|
group.MoveTo(Path.Combine(group.DirectoryName ?? string.Empty, newName), false);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"Could not rename group file {group.Name} to {newName} during migration:\n{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileVersion = 2;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MigrateV0ToV1(SaveService saveService, Mod mod, JObject json, ref uint fileVersion)
|
||||||
|
{
|
||||||
|
if (fileVersion > 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var swaps = json["FileSwaps"]?.ToObject<Dictionary<Utf8GamePath, FullPath>>()
|
||||||
|
?? new Dictionary<Utf8GamePath, FullPath>();
|
||||||
|
var groups = json["Groups"]?.ToObject<Dictionary<string, OptionGroupV0>>() ?? new Dictionary<string, OptionGroupV0>();
|
||||||
|
var priority = 1;
|
||||||
|
var seenMetaFiles = new HashSet<FullPath>();
|
||||||
|
foreach (var group in groups.Values)
|
||||||
|
ConvertGroup(mod, group, ref priority, seenMetaFiles);
|
||||||
|
|
||||||
|
foreach (var unusedFile in mod.FindUnusedFiles().Where(f => !seenMetaFiles.Contains(f)))
|
||||||
|
{
|
||||||
|
if (unusedFile.ToGamePath(mod.ModPath, out var gamePath)
|
||||||
|
&& !mod._default.FileData.TryAdd(gamePath, unusedFile))
|
||||||
|
Penumbra.Log.Error($"Could not add {gamePath} because it already points to {mod._default.FileData[gamePath]}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
mod._default.FileSwapData.Clear();
|
||||||
|
mod._default.FileSwapData.EnsureCapacity(swaps.Count);
|
||||||
|
foreach (var (gamePath, swapPath) in swaps)
|
||||||
|
mod._default.FileSwapData.Add(gamePath, swapPath);
|
||||||
|
|
||||||
|
mod._default.IncorporateMetaChanges(mod.ModPath, true);
|
||||||
|
foreach (var (_, index) in mod.Groups.WithIndex())
|
||||||
|
saveService.ImmediateSave(new ModSaveGroup(mod, index));
|
||||||
|
|
||||||
|
// Delete meta files.
|
||||||
|
foreach (var file in seenMetaFiles.Where(f => f.Exists))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(file.FullName);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Warning($"Could not delete meta file {file.FullName} during migration:\n{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete old meta files.
|
||||||
|
var oldMetaFile = Path.Combine(mod.ModPath.FullName, "metadata_manipulations.json");
|
||||||
|
if (File.Exists(oldMetaFile))
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(oldMetaFile);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Warning($"Could not delete old meta file {oldMetaFile} during migration:\n{e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
fileVersion = 1;
|
||||||
|
saveService.ImmediateSave(new ModSaveGroup(mod, -1));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConvertGroup(Mod mod, OptionGroupV0 group, ref int priority, HashSet<FullPath> seenMetaFiles)
|
||||||
|
{
|
||||||
|
if (group.Options.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (group.SelectionType)
|
||||||
|
{
|
||||||
|
case GroupType.Multi:
|
||||||
|
|
||||||
|
var optionPriority = 0;
|
||||||
|
var newMultiGroup = new MultiModGroup()
|
||||||
|
{
|
||||||
|
Name = group.GroupName,
|
||||||
|
Priority = priority++,
|
||||||
|
Description = string.Empty,
|
||||||
|
};
|
||||||
|
mod._groups.Add(newMultiGroup);
|
||||||
|
foreach (var option in group.Options)
|
||||||
|
newMultiGroup.PrioritizedOptions.Add((SubModFromOption(mod, option, seenMetaFiles), optionPriority++));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case GroupType.Single:
|
||||||
|
if (group.Options.Count == 1)
|
||||||
|
{
|
||||||
|
AddFilesToSubMod(mod._default, mod.ModPath, group.Options[0], seenMetaFiles);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newSingleGroup = new SingleModGroup()
|
||||||
|
{
|
||||||
|
Name = group.GroupName,
|
||||||
|
Priority = priority++,
|
||||||
|
Description = string.Empty,
|
||||||
|
};
|
||||||
|
mod._groups.Add(newSingleGroup);
|
||||||
|
foreach (var option in group.Options)
|
||||||
|
newSingleGroup.OptionData.Add(SubModFromOption(mod, option, seenMetaFiles));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddFilesToSubMod(SubMod mod, DirectoryInfo basePath, OptionV0 option, HashSet<FullPath> seenMetaFiles)
|
||||||
|
{
|
||||||
|
foreach (var (relPath, gamePaths) in option.OptionFiles)
|
||||||
|
{
|
||||||
|
var fullPath = new FullPath(basePath, relPath);
|
||||||
|
foreach (var gamePath in gamePaths)
|
||||||
|
mod.FileData.TryAdd(gamePath, fullPath);
|
||||||
|
|
||||||
|
if (fullPath.Extension is ".meta" or ".rgsp")
|
||||||
|
seenMetaFiles.Add(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SubMod SubModFromOption(Mod mod, OptionV0 option, HashSet<FullPath> seenMetaFiles)
|
||||||
|
{
|
||||||
|
var subMod = new SubMod(mod) { Name = option.OptionName };
|
||||||
|
AddFilesToSubMod(subMod, mod.ModPath, option, seenMetaFiles);
|
||||||
|
subMod.IncorporateMetaChanges(mod.ModPath, false);
|
||||||
|
return subMod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct OptionV0
|
||||||
|
{
|
||||||
|
public string OptionName = string.Empty;
|
||||||
|
public string OptionDesc = string.Empty;
|
||||||
|
|
||||||
|
[JsonProperty(ItemConverterType = typeof(SingleOrArrayConverter<Utf8GamePath>))]
|
||||||
|
public Dictionary<Utf8RelPath, HashSet<Utf8GamePath>> OptionFiles = new();
|
||||||
|
|
||||||
|
public OptionV0()
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct OptionGroupV0
|
||||||
|
{
|
||||||
|
public string GroupName = string.Empty;
|
||||||
|
|
||||||
|
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||||
|
public GroupType SelectionType = GroupType.Single;
|
||||||
|
|
||||||
|
public List<OptionV0> Options = new();
|
||||||
|
|
||||||
|
public OptionGroupV0()
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not used anymore, but required for migration.
|
||||||
|
private class SingleOrArrayConverter<T> : JsonConverter
|
||||||
|
{
|
||||||
|
public override bool CanConvert(Type objectType)
|
||||||
|
=> objectType == typeof(HashSet<T>);
|
||||||
|
|
||||||
|
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
var token = JToken.Load(reader);
|
||||||
|
|
||||||
|
if (token.Type == JTokenType.Array)
|
||||||
|
return token.ToObject<HashSet<T>>() ?? new HashSet<T>();
|
||||||
|
|
||||||
|
var tmp = token.ToObject<T>();
|
||||||
|
return tmp != null
|
||||||
|
? new HashSet<T> { tmp }
|
||||||
|
: new HashSet<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanWrite
|
||||||
|
=> true;
|
||||||
|
|
||||||
|
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
writer.WriteStartArray();
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
var v = (HashSet<T>)value;
|
||||||
|
foreach (var val in v)
|
||||||
|
serializer.Serialize(writer, val?.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteEndArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,9 +13,7 @@ using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
public partial class Mod
|
internal static partial class ModCreator
|
||||||
{
|
|
||||||
internal static partial class Creator
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create and return a new directory based on the given directory and name, that is <br/>
|
/// Create and return a new directory based on the given directory and name, that is <br/>
|
||||||
|
|
@ -282,4 +280,3 @@ public partial class Mod
|
||||||
[GeneratedRegex(@", Part (\d+)$", RegexOptions.NonBacktracking )]
|
[GeneratedRegex(@", Part (\d+)$", RegexOptions.NonBacktracking )]
|
||||||
private static partial Regex DuplicateNumber();
|
private static partial Regex DuplicateNumber();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -114,13 +114,13 @@ public partial class Mod
|
||||||
_default.WriteTexToolsMeta(ModPath);
|
_default.WriteTexToolsMeta(ModPath);
|
||||||
foreach (var group in Groups)
|
foreach (var group in Groups)
|
||||||
{
|
{
|
||||||
var dir = Creator.NewOptionDirectory(ModPath, group.Name);
|
var dir = ModCreator.NewOptionDirectory(ModPath, group.Name);
|
||||||
if (!dir.Exists)
|
if (!dir.Exists)
|
||||||
dir.Create();
|
dir.Create();
|
||||||
|
|
||||||
foreach (var option in group.OfType<SubMod>())
|
foreach (var option in group.OfType<SubMod>())
|
||||||
{
|
{
|
||||||
var optionDir = Creator.NewOptionDirectory(dir, option.Name);
|
var optionDir = ModCreator.NewOptionDirectory(dir, option.Name);
|
||||||
if (!optionDir.Exists)
|
if (!optionDir.Exists)
|
||||||
optionDir.Create();
|
optionDir.Create();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Penumbra.Services;
|
|
||||||
using Penumbra.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
|
||||||
|
|
||||||
public sealed partial class Mod
|
|
||||||
{
|
|
||||||
public long ImportDate { get; internal set; } = DateTimeOffset.UnixEpoch.ToUnixTimeMilliseconds();
|
|
||||||
|
|
||||||
public IReadOnlyList<string> LocalTags { get; private set; } = Array.Empty<string>();
|
|
||||||
|
|
||||||
public string Note { get; internal set; } = string.Empty;
|
|
||||||
public bool Favorite { get; internal set; } = false;
|
|
||||||
|
|
||||||
internal ModDataChangeType UpdateTags(IEnumerable<string>? newModTags, IEnumerable<string>? newLocalTags)
|
|
||||||
{
|
|
||||||
if (newModTags == null && newLocalTags == null)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
ModDataChangeType type = 0;
|
|
||||||
if (newModTags != null)
|
|
||||||
{
|
|
||||||
var modTags = newModTags.Where(t => t.Length > 0).Distinct().ToArray();
|
|
||||||
if (!modTags.SequenceEqual(ModTags))
|
|
||||||
{
|
|
||||||
newLocalTags ??= LocalTags;
|
|
||||||
ModTags = modTags;
|
|
||||||
type |= ModDataChangeType.ModTags;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newLocalTags != null)
|
|
||||||
{
|
|
||||||
var localTags = newLocalTags!.Where(t => t.Length > 0 && !ModTags.Contains(t)).Distinct().ToArray();
|
|
||||||
if (!localTags.SequenceEqual(LocalTags))
|
|
||||||
{
|
|
||||||
LocalTags = localTags;
|
|
||||||
type |= ModDataChangeType.LocalTags;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal readonly struct ModData : ISavable
|
|
||||||
{
|
|
||||||
public const int FileVersion = 3;
|
|
||||||
|
|
||||||
private readonly Mod _mod;
|
|
||||||
|
|
||||||
public ModData(Mod mod)
|
|
||||||
=> _mod = mod;
|
|
||||||
|
|
||||||
public string ToFilename(FilenameService fileNames)
|
|
||||||
=> fileNames.LocalDataFile(_mod);
|
|
||||||
|
|
||||||
public void Save(StreamWriter writer)
|
|
||||||
{
|
|
||||||
var jObject = new JObject
|
|
||||||
{
|
|
||||||
{ nameof(FileVersion), JToken.FromObject(FileVersion) },
|
|
||||||
{ nameof(ImportDate), JToken.FromObject(_mod.ImportDate) },
|
|
||||||
{ nameof(LocalTags), JToken.FromObject(_mod.LocalTags) },
|
|
||||||
{ nameof(Note), JToken.FromObject(_mod.Note) },
|
|
||||||
{ nameof(Favorite), JToken.FromObject(_mod.Favorite) },
|
|
||||||
};
|
|
||||||
using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
|
|
||||||
jObject.WriteTo(jWriter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,246 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using OtterGui;
|
|
||||||
using Penumbra.Api.Enums;
|
|
||||||
using Penumbra.String.Classes;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
|
||||||
|
|
||||||
public sealed partial class Mod
|
|
||||||
{
|
|
||||||
public static partial class Migration
|
|
||||||
{
|
|
||||||
[GeneratedRegex(@"group_\d{3}_", RegexOptions.Compiled | RegexOptions.NonBacktracking | RegexOptions.ExplicitCapture)]
|
|
||||||
private static partial Regex GroupRegex();
|
|
||||||
|
|
||||||
[GeneratedRegex("^group_", RegexOptions.Compiled)]
|
|
||||||
private static partial Regex GroupStartRegex();
|
|
||||||
|
|
||||||
public static bool Migrate(Mod mod, JObject json, ref uint fileVersion)
|
|
||||||
=> MigrateV0ToV1(mod, json, ref fileVersion) || MigrateV1ToV2(mod, ref fileVersion) || MigrateV2ToV3(mod, ref fileVersion);
|
|
||||||
|
|
||||||
private static bool MigrateV2ToV3(Mod _, ref uint fileVersion)
|
|
||||||
{
|
|
||||||
if (fileVersion > 2)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Remove import time.
|
|
||||||
fileVersion = 3;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool MigrateV1ToV2(Mod mod, ref uint fileVersion)
|
|
||||||
{
|
|
||||||
if (fileVersion > 1)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!mod.GroupFiles.All(g => GroupRegex().IsMatch(g.Name)))
|
|
||||||
foreach (var (group, index) in mod.GroupFiles.WithIndex().ToArray())
|
|
||||||
{
|
|
||||||
var newName = GroupStartRegex().Replace(group.Name, $"group_{index + 1:D3}_");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (newName != group.Name)
|
|
||||||
group.MoveTo(Path.Combine(group.DirectoryName ?? string.Empty, newName), false);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Penumbra.Log.Error($"Could not rename group file {group.Name} to {newName} during migration:\n{e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileVersion = 2;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool MigrateV0ToV1(Mod mod, JObject json, ref uint fileVersion)
|
|
||||||
{
|
|
||||||
if (fileVersion > 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var swaps = json["FileSwaps"]?.ToObject<Dictionary<Utf8GamePath, FullPath>>()
|
|
||||||
?? new Dictionary<Utf8GamePath, FullPath>();
|
|
||||||
var groups = json["Groups"]?.ToObject<Dictionary<string, OptionGroupV0>>() ?? new Dictionary<string, OptionGroupV0>();
|
|
||||||
var priority = 1;
|
|
||||||
var seenMetaFiles = new HashSet<FullPath>();
|
|
||||||
foreach (var group in groups.Values)
|
|
||||||
ConvertGroup(mod, group, ref priority, seenMetaFiles);
|
|
||||||
|
|
||||||
foreach (var unusedFile in mod.FindUnusedFiles().Where(f => !seenMetaFiles.Contains(f)))
|
|
||||||
{
|
|
||||||
if (unusedFile.ToGamePath(mod.ModPath, out var gamePath)
|
|
||||||
&& !mod._default.FileData.TryAdd(gamePath, unusedFile))
|
|
||||||
Penumbra.Log.Error($"Could not add {gamePath} because it already points to {mod._default.FileData[gamePath]}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
mod._default.FileSwapData.Clear();
|
|
||||||
mod._default.FileSwapData.EnsureCapacity(swaps.Count);
|
|
||||||
foreach (var (gamePath, swapPath) in swaps)
|
|
||||||
mod._default.FileSwapData.Add(gamePath, swapPath);
|
|
||||||
|
|
||||||
mod._default.IncorporateMetaChanges(mod.ModPath, true);
|
|
||||||
foreach (var (_, index) in mod.Groups.WithIndex())
|
|
||||||
Penumbra.SaveService.ImmediateSave(new ModSaveGroup(mod, index));
|
|
||||||
|
|
||||||
// Delete meta files.
|
|
||||||
foreach (var file in seenMetaFiles.Where(f => f.Exists))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File.Delete(file.FullName);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Penumbra.Log.Warning($"Could not delete meta file {file.FullName} during migration:\n{e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete old meta files.
|
|
||||||
var oldMetaFile = Path.Combine(mod.ModPath.FullName, "metadata_manipulations.json");
|
|
||||||
if (File.Exists(oldMetaFile))
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File.Delete(oldMetaFile);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Penumbra.Log.Warning($"Could not delete old meta file {oldMetaFile} during migration:\n{e}");
|
|
||||||
}
|
|
||||||
|
|
||||||
fileVersion = 1;
|
|
||||||
Penumbra.SaveService.ImmediateSave(new ModSaveGroup(mod, -1));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConvertGroup(Mod mod, OptionGroupV0 group, ref int priority, HashSet<FullPath> seenMetaFiles)
|
|
||||||
{
|
|
||||||
if (group.Options.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (group.SelectionType)
|
|
||||||
{
|
|
||||||
case GroupType.Multi:
|
|
||||||
|
|
||||||
var optionPriority = 0;
|
|
||||||
var newMultiGroup = new MultiModGroup()
|
|
||||||
{
|
|
||||||
Name = group.GroupName,
|
|
||||||
Priority = priority++,
|
|
||||||
Description = string.Empty,
|
|
||||||
};
|
|
||||||
mod._groups.Add(newMultiGroup);
|
|
||||||
foreach (var option in group.Options)
|
|
||||||
newMultiGroup.PrioritizedOptions.Add((SubModFromOption(mod, option, seenMetaFiles), optionPriority++));
|
|
||||||
|
|
||||||
break;
|
|
||||||
case GroupType.Single:
|
|
||||||
if (group.Options.Count == 1)
|
|
||||||
{
|
|
||||||
AddFilesToSubMod(mod._default, mod.ModPath, group.Options[0], seenMetaFiles);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newSingleGroup = new SingleModGroup()
|
|
||||||
{
|
|
||||||
Name = group.GroupName,
|
|
||||||
Priority = priority++,
|
|
||||||
Description = string.Empty,
|
|
||||||
};
|
|
||||||
mod._groups.Add(newSingleGroup);
|
|
||||||
foreach (var option in group.Options)
|
|
||||||
newSingleGroup.OptionData.Add(SubModFromOption(mod, option, seenMetaFiles));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AddFilesToSubMod(SubMod mod, DirectoryInfo basePath, OptionV0 option, HashSet<FullPath> seenMetaFiles)
|
|
||||||
{
|
|
||||||
foreach (var (relPath, gamePaths) in option.OptionFiles)
|
|
||||||
{
|
|
||||||
var fullPath = new FullPath(basePath, relPath);
|
|
||||||
foreach (var gamePath in gamePaths)
|
|
||||||
mod.FileData.TryAdd(gamePath, fullPath);
|
|
||||||
|
|
||||||
if (fullPath.Extension is ".meta" or ".rgsp")
|
|
||||||
seenMetaFiles.Add(fullPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SubMod SubModFromOption(Mod mod, OptionV0 option, HashSet<FullPath> seenMetaFiles)
|
|
||||||
{
|
|
||||||
var subMod = new SubMod(mod) { Name = option.OptionName };
|
|
||||||
AddFilesToSubMod(subMod, mod.ModPath, option, seenMetaFiles);
|
|
||||||
subMod.IncorporateMetaChanges(mod.ModPath, false);
|
|
||||||
return subMod;
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct OptionV0
|
|
||||||
{
|
|
||||||
public string OptionName = string.Empty;
|
|
||||||
public string OptionDesc = string.Empty;
|
|
||||||
|
|
||||||
[JsonProperty(ItemConverterType = typeof(SingleOrArrayConverter<Utf8GamePath>))]
|
|
||||||
public Dictionary<Utf8RelPath, HashSet<Utf8GamePath>> OptionFiles = new();
|
|
||||||
|
|
||||||
public OptionV0()
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct OptionGroupV0
|
|
||||||
{
|
|
||||||
public string GroupName = string.Empty;
|
|
||||||
|
|
||||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
|
||||||
public GroupType SelectionType = GroupType.Single;
|
|
||||||
|
|
||||||
public List<OptionV0> Options = new();
|
|
||||||
|
|
||||||
public OptionGroupV0()
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not used anymore, but required for migration.
|
|
||||||
private class SingleOrArrayConverter<T> : JsonConverter
|
|
||||||
{
|
|
||||||
public override bool CanConvert(Type objectType)
|
|
||||||
=> objectType == typeof(HashSet<T>);
|
|
||||||
|
|
||||||
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
|
||||||
{
|
|
||||||
var token = JToken.Load(reader);
|
|
||||||
|
|
||||||
if (token.Type == JTokenType.Array)
|
|
||||||
return token.ToObject<HashSet<T>>() ?? new HashSet<T>();
|
|
||||||
|
|
||||||
var tmp = token.ToObject<T>();
|
|
||||||
return tmp != null
|
|
||||||
? new HashSet<T> { tmp }
|
|
||||||
: new HashSet<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanWrite
|
|
||||||
=> true;
|
|
||||||
|
|
||||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
|
||||||
{
|
|
||||||
writer.WriteStartArray();
|
|
||||||
if (value != null)
|
|
||||||
{
|
|
||||||
var v = (HashSet<T>)value;
|
|
||||||
foreach (var val in v)
|
|
||||||
serializer.Serialize(writer, val?.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.WriteEndArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using OtterGui.Classes;
|
|
||||||
using Penumbra.Services;
|
|
||||||
using Penumbra.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
|
||||||
|
|
||||||
public sealed partial class Mod : IMod
|
|
||||||
{
|
|
||||||
public static readonly TemporaryMod ForcedFiles = new()
|
|
||||||
{
|
|
||||||
Name = "Forced Files",
|
|
||||||
Index = -1,
|
|
||||||
Priority = int.MaxValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
public LowerString Name { get; internal set; } = "New Mod";
|
|
||||||
public LowerString Author { get; internal set; } = LowerString.Empty;
|
|
||||||
public string Description { get; internal set; } = string.Empty;
|
|
||||||
public string Version { get; internal set; } = string.Empty;
|
|
||||||
public string Website { get; internal set; } = string.Empty;
|
|
||||||
public IReadOnlyList<string> ModTags { get; internal set; } = Array.Empty<string>();
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
=> Name.Text;
|
|
||||||
|
|
||||||
internal readonly struct ModMeta : ISavable
|
|
||||||
{
|
|
||||||
public const uint FileVersion = 3;
|
|
||||||
|
|
||||||
private readonly Mod _mod;
|
|
||||||
|
|
||||||
public ModMeta(Mod mod)
|
|
||||||
=> _mod = mod;
|
|
||||||
|
|
||||||
public string ToFilename(FilenameService fileNames)
|
|
||||||
=> fileNames.ModMetaPath(_mod);
|
|
||||||
|
|
||||||
public void Save(StreamWriter writer)
|
|
||||||
{
|
|
||||||
var jObject = new JObject
|
|
||||||
{
|
|
||||||
{ nameof(FileVersion), JToken.FromObject(FileVersion) },
|
|
||||||
{ nameof(Name), JToken.FromObject(_mod.Name) },
|
|
||||||
{ nameof(Author), JToken.FromObject(_mod.Author) },
|
|
||||||
{ nameof(Description), JToken.FromObject(_mod.Description) },
|
|
||||||
{ nameof(Version), JToken.FromObject(_mod.Version) },
|
|
||||||
{ nameof(Website), JToken.FromObject(_mod.Website) },
|
|
||||||
{ nameof(ModTags), JToken.FromObject(_mod.ModTags) },
|
|
||||||
};
|
|
||||||
using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
|
|
||||||
jObject.WriteTo(jWriter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
35
Penumbra/Mods/Mod.cs
Normal file
35
Penumbra/Mods/Mod.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
|
||||||
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
public sealed partial class Mod : IMod
|
||||||
|
{
|
||||||
|
public static readonly TemporaryMod ForcedFiles = new()
|
||||||
|
{
|
||||||
|
Name = "Forced Files",
|
||||||
|
Index = -1,
|
||||||
|
Priority = int.MaxValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Meta Data
|
||||||
|
public LowerString Name { get; internal set; } = "New Mod";
|
||||||
|
public LowerString Author { get; internal set; } = LowerString.Empty;
|
||||||
|
public string Description { get; internal set; } = string.Empty;
|
||||||
|
public string Version { get; internal set; } = string.Empty;
|
||||||
|
public string Website { get; internal set; } = string.Empty;
|
||||||
|
public IReadOnlyList<string> ModTags { get; internal set; } = Array.Empty<string>();
|
||||||
|
|
||||||
|
|
||||||
|
// Local Data
|
||||||
|
public long ImportDate { get; internal set; } = DateTimeOffset.UnixEpoch.ToUnixTimeMilliseconds();
|
||||||
|
public IReadOnlyList<string> LocalTags { get; internal set; } = Array.Empty<string>();
|
||||||
|
public string Note { get; internal set; } = string.Empty;
|
||||||
|
public bool Favorite { get; internal set; } = false;
|
||||||
|
|
||||||
|
|
||||||
|
// Access
|
||||||
|
public override string ToString()
|
||||||
|
=> Name.Text;
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Penumbra.Mods.Manager;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
public class ModCache
|
public class ModCache
|
||||||
{
|
{
|
||||||
|
|
|
||||||
67
Penumbra/Mods/ModLocalData.cs
Normal file
67
Penumbra/Mods/ModLocalData.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.Mods.Manager;
|
||||||
|
using Penumbra.Services;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
public readonly struct ModLocalData : ISavable
|
||||||
|
{
|
||||||
|
public const int FileVersion = 3;
|
||||||
|
|
||||||
|
private readonly Mod _mod;
|
||||||
|
|
||||||
|
public ModLocalData(Mod mod)
|
||||||
|
=> _mod = mod;
|
||||||
|
|
||||||
|
public string ToFilename(FilenameService fileNames)
|
||||||
|
=> fileNames.LocalDataFile(_mod);
|
||||||
|
|
||||||
|
public void Save(StreamWriter writer)
|
||||||
|
{
|
||||||
|
var jObject = new JObject
|
||||||
|
{
|
||||||
|
{ nameof(FileVersion), JToken.FromObject(FileVersion) },
|
||||||
|
{ nameof(Mod.ImportDate), JToken.FromObject(_mod.ImportDate) },
|
||||||
|
{ nameof(Mod.LocalTags), JToken.FromObject(_mod.LocalTags) },
|
||||||
|
{ nameof(Mod.Note), JToken.FromObject(_mod.Note) },
|
||||||
|
{ nameof(Mod.Favorite), JToken.FromObject(_mod.Favorite) },
|
||||||
|
};
|
||||||
|
using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
|
||||||
|
jObject.WriteTo(jWriter);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static ModDataChangeType UpdateTags(Mod mod, IEnumerable<string>? newModTags, IEnumerable<string>? newLocalTags)
|
||||||
|
{
|
||||||
|
if (newModTags == null && newLocalTags == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
ModDataChangeType type = 0;
|
||||||
|
if (newModTags != null)
|
||||||
|
{
|
||||||
|
var modTags = newModTags.Where(t => t.Length > 0).Distinct().ToArray();
|
||||||
|
if (!modTags.SequenceEqual(mod.ModTags))
|
||||||
|
{
|
||||||
|
newLocalTags ??= mod.LocalTags;
|
||||||
|
mod.ModTags = modTags;
|
||||||
|
type |= ModDataChangeType.ModTags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newLocalTags != null)
|
||||||
|
{
|
||||||
|
var localTags = newLocalTags!.Where(t => t.Length > 0 && !mod.ModTags.Contains(t)).Distinct().ToArray();
|
||||||
|
if (!localTags.SequenceEqual(mod.LocalTags))
|
||||||
|
{
|
||||||
|
mod.LocalTags = localTags;
|
||||||
|
type |= ModDataChangeType.LocalTags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Penumbra/Mods/ModMeta.cs
Normal file
36
Penumbra/Mods/ModMeta.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System.IO;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.Services;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
public readonly struct ModMeta : ISavable
|
||||||
|
{
|
||||||
|
public const uint FileVersion = 3;
|
||||||
|
|
||||||
|
private readonly Mod _mod;
|
||||||
|
|
||||||
|
public ModMeta(Mod mod)
|
||||||
|
=> _mod = mod;
|
||||||
|
|
||||||
|
public string ToFilename(FilenameService fileNames)
|
||||||
|
=> fileNames.ModMetaPath(_mod);
|
||||||
|
|
||||||
|
public void Save(StreamWriter writer)
|
||||||
|
{
|
||||||
|
var jObject = new JObject
|
||||||
|
{
|
||||||
|
{ nameof(FileVersion), JToken.FromObject(FileVersion) },
|
||||||
|
{ nameof(Mod.Name), JToken.FromObject(_mod.Name) },
|
||||||
|
{ nameof(Mod.Author), JToken.FromObject(_mod.Author) },
|
||||||
|
{ nameof(Mod.Description), JToken.FromObject(_mod.Description) },
|
||||||
|
{ nameof(Mod.Version), JToken.FromObject(_mod.Version) },
|
||||||
|
{ nameof(Mod.Website), JToken.FromObject(_mod.Website) },
|
||||||
|
{ nameof(Mod.ModTags), JToken.FromObject(_mod.ModTags) },
|
||||||
|
};
|
||||||
|
using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
|
||||||
|
jObject.WriteTo(jWriter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -50,7 +50,7 @@ public class TemporaryMod : IMod
|
||||||
DirectoryInfo? dir = null;
|
DirectoryInfo? dir = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
dir = Mod.Creator.CreateModFolder( Penumbra.ModManager.BasePath, collection.Name );
|
dir = ModCreator.CreateModFolder( Penumbra.ModManager.BasePath, collection.Name );
|
||||||
var fileDir = Directory.CreateDirectory( Path.Combine( dir.FullName, "files" ) );
|
var fileDir = Directory.CreateDirectory( Path.Combine( dir.FullName, "files" ) );
|
||||||
modManager.DataEditor.CreateMeta( dir, collection.Name, character ?? Penumbra.Config.DefaultModAuthor,
|
modManager.DataEditor.CreateMeta( dir, collection.Name, character ?? Penumbra.Config.DefaultModAuthor,
|
||||||
$"Mod generated from temporary collection {collection.Name} for {character ?? "Unknown Character"}.", null, null );
|
$"Mod generated from temporary collection {collection.Name} for {character ?? "Unknown Character"}.", null, null );
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ using Penumbra.GameData.Actors;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
using Penumbra.Interop.ResourceLoading;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
using Penumbra.Mods;
|
|
||||||
using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
|
using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
|
||||||
using DalamudUtil = Dalamud.Utility.Util;
|
using DalamudUtil = Dalamud.Utility.Util;
|
||||||
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
||||||
|
|
|
||||||
|
|
@ -269,9 +269,9 @@ public class ItemSwapTab : IDisposable, ITab
|
||||||
|
|
||||||
private void CreateMod()
|
private void CreateMod()
|
||||||
{
|
{
|
||||||
var newDir = Mod.Creator.CreateModFolder(_modManager.BasePath, _newModName);
|
var newDir = ModCreator.CreateModFolder(_modManager.BasePath, _newModName);
|
||||||
_modManager.DataEditor.CreateMeta(newDir, _newModName, _config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty);
|
_modManager.DataEditor.CreateMeta(newDir, _newModName, _config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty);
|
||||||
Mod.Creator.CreateDefaultFiles(newDir);
|
ModCreator.CreateDefaultFiles(newDir);
|
||||||
_modManager.AddMod(newDir);
|
_modManager.AddMod(newDir);
|
||||||
if (!_swapData.WriteMod(_modManager, _modManager[^1],
|
if (!_swapData.WriteMod(_modManager, _modManager[^1],
|
||||||
_useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps))
|
_useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps))
|
||||||
|
|
@ -290,7 +290,7 @@ public class ItemSwapTab : IDisposable, ITab
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
optionFolderName =
|
optionFolderName =
|
||||||
Mod.Creator.NewSubFolderName(new DirectoryInfo(Path.Combine(_mod.ModPath.FullName, _selectedGroup?.Name ?? _newGroupName)),
|
ModCreator.NewSubFolderName(new DirectoryInfo(Path.Combine(_mod.ModPath.FullName, _selectedGroup?.Name ?? _newGroupName)),
|
||||||
_newOptionName);
|
_newOptionName);
|
||||||
if (optionFolderName?.Exists == true)
|
if (optionFolderName?.Exists == true)
|
||||||
throw new Exception($"The folder {optionFolderName.FullName} for the option already exists.");
|
throw new Exception($"The folder {optionFolderName.FullName} for the option already exists.");
|
||||||
|
|
|
||||||
|
|
@ -217,13 +217,13 @@ public partial class ModEditWindow
|
||||||
var fullName = subMod.FullName;
|
var fullName = subMod.FullName;
|
||||||
if (fullName.EndsWith(": " + name))
|
if (fullName.EndsWith(": " + name))
|
||||||
{
|
{
|
||||||
path = Mod.Creator.NewOptionDirectory(path, fullName[..^(name.Length + 2)]);
|
path = ModCreator.NewOptionDirectory(path, fullName[..^(name.Length + 2)]);
|
||||||
path = Mod.Creator.NewOptionDirectory(path, name);
|
path = ModCreator.NewOptionDirectory(path, name);
|
||||||
subDirs = 2;
|
subDirs = 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
path = Mod.Creator.NewOptionDirectory(path, fullName);
|
path = ModCreator.NewOptionDirectory(path, fullName);
|
||||||
subDirs = 1;
|
subDirs = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,9 +131,9 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
|
||||||
if (ImGuiUtil.OpenNameField("Create New Mod", ref _newModName))
|
if (ImGuiUtil.OpenNameField("Create New Mod", ref _newModName))
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var newDir = Mod.Creator.CreateModFolder(Penumbra.ModManager.BasePath, _newModName);
|
var newDir = ModCreator.CreateModFolder(Penumbra.ModManager.BasePath, _newModName);
|
||||||
_modManager.DataEditor.CreateMeta(newDir, _newModName, Penumbra.Config.DefaultModAuthor, string.Empty, "1.0", string.Empty);
|
_modManager.DataEditor.CreateMeta(newDir, _newModName, Penumbra.Config.DefaultModAuthor, string.Empty, "1.0", string.Empty);
|
||||||
Mod.Creator.CreateDefaultFiles(newDir);
|
ModCreator.CreateDefaultFiles(newDir);
|
||||||
_modManager.AddMod(newDir);
|
_modManager.AddMod(newDir);
|
||||||
_newModName = string.Empty;
|
_newModName = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,20 +30,21 @@ public interface ISavable
|
||||||
public class SaveService
|
public class SaveService
|
||||||
{
|
{
|
||||||
private readonly Logger _log;
|
private readonly Logger _log;
|
||||||
private readonly FilenameService _fileNames;
|
|
||||||
private readonly FrameworkManager _framework;
|
private readonly FrameworkManager _framework;
|
||||||
|
|
||||||
public SaveService(Logger log, FilenameService fileNames, FrameworkManager framework)
|
public readonly FilenameService FileNames;
|
||||||
|
|
||||||
|
public SaveService(Logger log, FrameworkManager framework, FilenameService fileNames)
|
||||||
{
|
{
|
||||||
_log = log;
|
_log = log;
|
||||||
_fileNames = fileNames;
|
|
||||||
_framework = framework;
|
_framework = framework;
|
||||||
|
FileNames = fileNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Queue a save for the next framework tick. </summary>
|
/// <summary> Queue a save for the next framework tick. </summary>
|
||||||
public void QueueSave(ISavable value)
|
public void QueueSave(ISavable value)
|
||||||
{
|
{
|
||||||
var file = value.ToFilename(_fileNames);
|
var file = value.ToFilename(FileNames);
|
||||||
_framework.RegisterDelayed(value.GetType().Name + file, () =>
|
_framework.RegisterDelayed(value.GetType().Name + file, () =>
|
||||||
{
|
{
|
||||||
ImmediateSave(value);
|
ImmediateSave(value);
|
||||||
|
|
@ -53,7 +54,7 @@ public class SaveService
|
||||||
/// <summary> Immediately trigger a save. </summary>
|
/// <summary> Immediately trigger a save. </summary>
|
||||||
public void ImmediateSave(ISavable value)
|
public void ImmediateSave(ISavable value)
|
||||||
{
|
{
|
||||||
var name = value.ToFilename(_fileNames);
|
var name = value.ToFilename(FileNames);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (name.Length == 0)
|
if (name.Length == 0)
|
||||||
|
|
@ -76,7 +77,7 @@ public class SaveService
|
||||||
|
|
||||||
public void ImmediateDelete(ISavable value)
|
public void ImmediateDelete(ISavable value)
|
||||||
{
|
{
|
||||||
var name = value.ToFilename(_fileNames);
|
var name = value.ToFilename(FileNames);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (name.Length == 0)
|
if (name.Length == 0)
|
||||||
|
|
@ -99,7 +100,7 @@ public class SaveService
|
||||||
/// <summary> Immediately delete all existing option group files for a mod and save them anew. </summary>
|
/// <summary> Immediately delete all existing option group files for a mod and save them anew. </summary>
|
||||||
public void SaveAllOptionGroups(Mod mod)
|
public void SaveAllOptionGroups(Mod mod)
|
||||||
{
|
{
|
||||||
foreach (var file in _fileNames.GetOptionGroupFiles(mod))
|
foreach (var file in FileNames.GetOptionGroupFiles(mod))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue