mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-22 16:39:27 +01:00
261 lines
No EOL
8.9 KiB
C#
261 lines
No EOL
8.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using Dalamud.Logging;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
using OtterGui;
|
|
using Penumbra.GameData.ByteString;
|
|
|
|
namespace Penumbra.Mods;
|
|
|
|
public sealed partial class Mod
|
|
{
|
|
private static class Migration
|
|
{
|
|
public static bool Migrate( Mod mod, JObject json )
|
|
=> MigrateV0ToV1( mod, json ) || MigrateV1ToV2( mod );
|
|
|
|
private static bool MigrateV1ToV2( Mod mod )
|
|
{
|
|
if( mod.FileVersion > 1 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach( var (group, index) in mod.GroupFiles.WithIndex().ToArray() )
|
|
{
|
|
var newName = Regex.Replace( group.Name, "^group_", $"group_{index + 1:D3}_", RegexOptions.Compiled );
|
|
try
|
|
{
|
|
if( newName != group.Name )
|
|
{
|
|
group.MoveTo( Path.Combine( group.DirectoryName ?? string.Empty, newName ), false );
|
|
}
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
PluginLog.Error( $"Could not rename group file {group.Name} to {newName} during migration:\n{e}" );
|
|
}
|
|
}
|
|
|
|
mod.FileVersion = 2;
|
|
mod.SaveMeta();
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool MigrateV0ToV1( Mod mod, JObject json )
|
|
{
|
|
if( mod.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 ) )
|
|
{
|
|
PluginLog.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 (group, index) in mod.Groups.WithIndex() )
|
|
{
|
|
IModGroup.Save( group, mod.ModPath, index );
|
|
}
|
|
|
|
// Delete meta files.
|
|
foreach( var file in seenMetaFiles.Where( f => f.Exists ) )
|
|
{
|
|
try
|
|
{
|
|
File.Delete( file.FullName );
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
PluginLog.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 )
|
|
{
|
|
PluginLog.Warning( $"Could not delete old meta file {oldMetaFile} during migration:\n{e}" );
|
|
}
|
|
}
|
|
|
|
mod.FileVersion = 1;
|
|
mod.SaveDefaultMod();
|
|
mod.SaveMeta();
|
|
|
|
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 SelectType.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 SelectType.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 SelectType SelectionType = SelectType.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();
|
|
}
|
|
}
|
|
}
|
|
} |