mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-14 04:34:19 +01:00
Complete refactoring of most code, indiscriminate application of .editorconfig and general cleanup.
This commit is contained in:
parent
5332119a63
commit
a19ec226c5
84 changed files with 3168 additions and 1709 deletions
|
|
@ -1,8 +1,10 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using EmbedIO;
|
using EmbedIO;
|
||||||
using EmbedIO.Routing;
|
using EmbedIO.Routing;
|
||||||
using EmbedIO.WebApi;
|
using EmbedIO.WebApi;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.API
|
namespace Penumbra.API
|
||||||
{
|
{
|
||||||
|
|
@ -10,37 +12,38 @@ namespace Penumbra.API
|
||||||
{
|
{
|
||||||
private readonly Plugin _plugin;
|
private readonly Plugin _plugin;
|
||||||
|
|
||||||
public ModsController( Plugin plugin ) => _plugin = plugin;
|
public ModsController( Plugin plugin )
|
||||||
|
=> _plugin = plugin;
|
||||||
|
|
||||||
[Route( HttpVerbs.Get, "/mods" )]
|
[Route( HttpVerbs.Get, "/mods" )]
|
||||||
public object? GetMods()
|
public object? GetMods()
|
||||||
{
|
{
|
||||||
var modManager = Service< ModManager >.Get();
|
var modManager = Service< ModManager >.Get();
|
||||||
return modManager.Mods?.ModSettings.Select( x => new
|
return modManager.CurrentCollection.Cache?.AvailableMods.Select( x => new
|
||||||
{
|
{
|
||||||
x.Enabled,
|
x.Settings.Enabled,
|
||||||
x.Priority,
|
x.Settings.Priority,
|
||||||
x.FolderName,
|
x.Data.BasePath.Name,
|
||||||
x.Mod.Meta,
|
x.Data.Meta,
|
||||||
BasePath = x.Mod.ModBasePath.FullName,
|
BasePath = x.Data.BasePath.FullName,
|
||||||
Files = x.Mod.ModFiles.Select( fi => fi.FullName )
|
Files = x.Data.Resources.ModFiles.Select( fi => fi.FullName ),
|
||||||
} );
|
} )
|
||||||
|
?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route( HttpVerbs.Post, "/mods" )]
|
[Route( HttpVerbs.Post, "/mods" )]
|
||||||
public object CreateMod()
|
public object CreateMod()
|
||||||
{
|
=> new { };
|
||||||
return new { };
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route( HttpVerbs.Get, "/files" )]
|
[Route( HttpVerbs.Get, "/files" )]
|
||||||
public object GetFiles()
|
public object GetFiles()
|
||||||
{
|
{
|
||||||
var modManager = Service< ModManager >.Get();
|
var modManager = Service< ModManager >.Get();
|
||||||
return modManager.ResolvedFiles.ToDictionary(
|
return modManager.CurrentCollection.Cache?.ResolvedFiles.ToDictionary(
|
||||||
o => o.Key,
|
o => ( string )o.Key,
|
||||||
o => o.Value.FullName
|
o => o.Value.FullName
|
||||||
);
|
)
|
||||||
|
?? new Dictionary< string, string >();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Dalamud.Configuration;
|
using Dalamud.Configuration;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra
|
namespace Penumbra
|
||||||
{
|
{
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class Configuration : IPluginConfiguration
|
public class Configuration : IPluginConfiguration
|
||||||
{
|
{
|
||||||
public int Version { get; set; } = 0;
|
private const int CurrentVersion = 1;
|
||||||
|
|
||||||
|
public int Version { get; set; } = CurrentVersion;
|
||||||
|
|
||||||
public bool IsEnabled { get; set; } = true;
|
public bool IsEnabled { get; set; } = true;
|
||||||
|
|
||||||
|
|
@ -18,25 +20,39 @@ namespace Penumbra
|
||||||
|
|
||||||
public bool EnableHttpApi { get; set; }
|
public bool EnableHttpApi { get; set; }
|
||||||
|
|
||||||
public string CurrentCollection { get; set; } = @"D:/ffxiv/fs_mods/";
|
public string ModDirectory { get; set; } = @"D:/ffxiv/fs_mods/";
|
||||||
|
|
||||||
public List< string > ModCollections { get; set; } = new();
|
public string CurrentCollection { get; set; } = "Default";
|
||||||
|
|
||||||
public bool InvertModListOrder { get; set; }
|
public bool InvertModListOrder { internal get; set; }
|
||||||
|
|
||||||
// the below exist just to make saving less cumbersome
|
public static Configuration Load( DalamudPluginInterface pi )
|
||||||
|
|
||||||
[NonSerialized]
|
|
||||||
private DalamudPluginInterface? _pluginInterface;
|
|
||||||
|
|
||||||
public void Initialize( DalamudPluginInterface pluginInterface )
|
|
||||||
{
|
{
|
||||||
_pluginInterface = pluginInterface;
|
var configuration = pi.GetPluginConfig() as Configuration ?? new Configuration();
|
||||||
|
if( configuration.Version == CurrentVersion )
|
||||||
|
{
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
MigrateConfiguration.Version0To1( configuration );
|
||||||
|
configuration.Save( pi );
|
||||||
|
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save( DalamudPluginInterface pi )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pi.SavePluginConfig( this );
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not save plugin configuration:\n{e}" );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save()
|
||||||
{
|
=> Save( Service< DalamudPluginInterface >.Get() );
|
||||||
_pluginInterface?.SavePluginConfig( this );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace Penumbra.Game
|
namespace Penumbra.Game.Enums
|
||||||
{
|
{
|
||||||
public enum BodySlot : byte
|
public enum BodySlot : byte
|
||||||
{
|
{
|
||||||
|
|
@ -10,7 +10,7 @@ namespace Penumbra.Game
|
||||||
Face,
|
Face,
|
||||||
Tail,
|
Tail,
|
||||||
Body,
|
Body,
|
||||||
Zear
|
Zear,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class BodySlotEnumExtension
|
public static class BodySlotEnumExtension
|
||||||
|
|
@ -24,7 +24,7 @@ namespace Penumbra.Game
|
||||||
BodySlot.Hair => "hair",
|
BodySlot.Hair => "hair",
|
||||||
BodySlot.Body => "body",
|
BodySlot.Body => "body",
|
||||||
BodySlot.Tail => "tail",
|
BodySlot.Tail => "tail",
|
||||||
_ => throw new InvalidEnumArgumentException()
|
_ => throw new InvalidEnumArgumentException(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -37,7 +37,7 @@ namespace Penumbra.Game
|
||||||
{ BodySlot.Face.ToSuffix(), BodySlot.Face },
|
{ BodySlot.Face.ToSuffix(), BodySlot.Face },
|
||||||
{ BodySlot.Hair.ToSuffix(), BodySlot.Hair },
|
{ BodySlot.Hair.ToSuffix(), BodySlot.Hair },
|
||||||
{ BodySlot.Body.ToSuffix(), BodySlot.Body },
|
{ BodySlot.Body.ToSuffix(), BodySlot.Body },
|
||||||
{ BodySlot.Tail.ToSuffix(), BodySlot.Tail }
|
{ BodySlot.Tail.ToSuffix(), BodySlot.Tail },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace Penumbra.Game
|
namespace Penumbra.Game.Enums
|
||||||
{
|
{
|
||||||
public enum CustomizationType : byte
|
public enum CustomizationType : byte
|
||||||
{
|
{
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Penumbra.Game
|
||||||
DecalFace,
|
DecalFace,
|
||||||
DecalEquip,
|
DecalEquip,
|
||||||
Skin,
|
Skin,
|
||||||
Etc
|
Etc,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CustomizationTypeEnumExtension
|
public static class CustomizationTypeEnumExtension
|
||||||
|
|
@ -30,7 +30,7 @@ namespace Penumbra.Game
|
||||||
CustomizationType.Hair => "hir",
|
CustomizationType.Hair => "hir",
|
||||||
CustomizationType.Tail => "til",
|
CustomizationType.Tail => "til",
|
||||||
CustomizationType.Etc => "etc",
|
CustomizationType.Etc => "etc",
|
||||||
_ => throw new InvalidEnumArgumentException()
|
_ => throw new InvalidEnumArgumentException(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -44,7 +44,7 @@ namespace Penumbra.Game
|
||||||
{ CustomizationType.Accessory.ToSuffix(), CustomizationType.Accessory },
|
{ CustomizationType.Accessory.ToSuffix(), CustomizationType.Accessory },
|
||||||
{ CustomizationType.Hair.ToSuffix(), CustomizationType.Hair },
|
{ CustomizationType.Hair.ToSuffix(), CustomizationType.Hair },
|
||||||
{ CustomizationType.Tail.ToSuffix(), CustomizationType.Tail },
|
{ CustomizationType.Tail.ToSuffix(), CustomizationType.Tail },
|
||||||
{ CustomizationType.Etc.ToSuffix(), CustomizationType.Etc }
|
{ CustomizationType.Etc.ToSuffix(), CustomizationType.Etc },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace Penumbra.Game
|
namespace Penumbra.Game.Enums
|
||||||
{
|
{
|
||||||
public enum EquipSlot : byte
|
public enum EquipSlot : byte
|
||||||
{
|
{
|
||||||
|
|
@ -27,7 +27,7 @@ namespace Penumbra.Game
|
||||||
FullBody = 19,
|
FullBody = 19,
|
||||||
BodyHands = 20,
|
BodyHands = 20,
|
||||||
BodyLegsFeet = 21,
|
BodyLegsFeet = 21,
|
||||||
All = 22
|
All = 22,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EquipSlotEnumExtension
|
public static class EquipSlotEnumExtension
|
||||||
|
|
@ -46,7 +46,7 @@ namespace Penumbra.Game
|
||||||
EquipSlot.RingR => "rir",
|
EquipSlot.RingR => "rir",
|
||||||
EquipSlot.RingL => "ril",
|
EquipSlot.RingL => "ril",
|
||||||
EquipSlot.Wrists => "wrs",
|
EquipSlot.Wrists => "wrs",
|
||||||
_ => throw new InvalidEnumArgumentException()
|
_ => throw new InvalidEnumArgumentException(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ namespace Penumbra.Game
|
||||||
EquipSlot.Legs => true,
|
EquipSlot.Legs => true,
|
||||||
EquipSlot.Feet => true,
|
EquipSlot.Feet => true,
|
||||||
EquipSlot.Body => true,
|
EquipSlot.Body => true,
|
||||||
_ => false
|
_ => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ namespace Penumbra.Game
|
||||||
EquipSlot.RingR => true,
|
EquipSlot.RingR => true,
|
||||||
EquipSlot.RingL => true,
|
EquipSlot.RingL => true,
|
||||||
EquipSlot.Wrists => true,
|
EquipSlot.Wrists => true,
|
||||||
_ => false
|
_ => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +90,7 @@ namespace Penumbra.Game
|
||||||
{ EquipSlot.Neck.ToSuffix(), EquipSlot.Neck },
|
{ EquipSlot.Neck.ToSuffix(), EquipSlot.Neck },
|
||||||
{ EquipSlot.RingR.ToSuffix(), EquipSlot.RingR },
|
{ EquipSlot.RingR.ToSuffix(), EquipSlot.RingR },
|
||||||
{ EquipSlot.RingL.ToSuffix(), EquipSlot.RingL },
|
{ EquipSlot.RingL.ToSuffix(), EquipSlot.RingL },
|
||||||
{ EquipSlot.Wrists.ToSuffix(), EquipSlot.Wrists }
|
{ EquipSlot.Wrists.ToSuffix(), EquipSlot.Wrists },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Penumbra.Game
|
namespace Penumbra.Game.Enums
|
||||||
{
|
{
|
||||||
public enum FileType : byte
|
public enum FileType : byte
|
||||||
{
|
{
|
||||||
|
|
@ -16,7 +16,7 @@ namespace Penumbra.Game
|
||||||
Model,
|
Model,
|
||||||
Shader,
|
Shader,
|
||||||
Font,
|
Font,
|
||||||
Environment
|
Environment,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static partial class GameData
|
public static partial class GameData
|
||||||
|
|
@ -39,7 +39,7 @@ namespace Penumbra.Game
|
||||||
{ ".shpk", FileType.Shader },
|
{ ".shpk", FileType.Shader },
|
||||||
{ ".shcd", FileType.Shader },
|
{ ".shcd", FileType.Shader },
|
||||||
{ ".fdt", FileType.Font },
|
{ ".fdt", FileType.Font },
|
||||||
{ ".envb", FileType.Environment }
|
{ ".envb", FileType.Environment },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace Penumbra.Game
|
namespace Penumbra.Game.Enums
|
||||||
{
|
{
|
||||||
public enum ObjectType : byte
|
public enum ObjectType : byte
|
||||||
{
|
{
|
||||||
|
|
@ -16,6 +16,6 @@ namespace Penumbra.Game
|
||||||
Equipment,
|
Equipment,
|
||||||
Character,
|
Character,
|
||||||
Weapon,
|
Weapon,
|
||||||
Font
|
Font,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace Penumbra.Game
|
namespace Penumbra.Game.Enums
|
||||||
{
|
{
|
||||||
public enum Gender : byte
|
public enum Gender : byte
|
||||||
{
|
{
|
||||||
|
|
@ -10,7 +10,7 @@ namespace Penumbra.Game
|
||||||
Male,
|
Male,
|
||||||
Female,
|
Female,
|
||||||
MaleNpc,
|
MaleNpc,
|
||||||
FemaleNpc
|
FemaleNpc,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Race : byte
|
public enum Race : byte
|
||||||
|
|
@ -24,7 +24,7 @@ namespace Penumbra.Game
|
||||||
Roegadyn,
|
Roegadyn,
|
||||||
AuRa,
|
AuRa,
|
||||||
Hrothgar,
|
Hrothgar,
|
||||||
Viera
|
Viera,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum GenderRace : ushort
|
public enum GenderRace : ushort
|
||||||
|
|
@ -63,7 +63,7 @@ namespace Penumbra.Game
|
||||||
VieraFemale = 1801,
|
VieraFemale = 1801,
|
||||||
VieraFemaleNpc = 1804,
|
VieraFemaleNpc = 1804,
|
||||||
UnknownMaleNpc = 9104,
|
UnknownMaleNpc = 9104,
|
||||||
UnknownFemaleNpc = 9204
|
UnknownFemaleNpc = 9204,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RaceEnumExtensions
|
public static class RaceEnumExtensions
|
||||||
|
|
@ -118,7 +118,7 @@ namespace Penumbra.Game
|
||||||
GenderRace.VieraFemaleNpc => ( Gender.FemaleNpc, Race.Viera ),
|
GenderRace.VieraFemaleNpc => ( Gender.FemaleNpc, Race.Viera ),
|
||||||
GenderRace.UnknownMaleNpc => ( Gender.MaleNpc, Race.Unknown ),
|
GenderRace.UnknownMaleNpc => ( Gender.MaleNpc, Race.Unknown ),
|
||||||
GenderRace.UnknownFemaleNpc => ( Gender.FemaleNpc, Race.Unknown ),
|
GenderRace.UnknownFemaleNpc => ( Gender.FemaleNpc, Race.Unknown ),
|
||||||
_ => throw new InvalidEnumArgumentException()
|
_ => throw new InvalidEnumArgumentException(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,7 +163,7 @@ namespace Penumbra.Game
|
||||||
GenderRace.VieraFemaleNpc => "1804",
|
GenderRace.VieraFemaleNpc => "1804",
|
||||||
GenderRace.UnknownMaleNpc => "9104",
|
GenderRace.UnknownMaleNpc => "9104",
|
||||||
GenderRace.UnknownFemaleNpc => "9204",
|
GenderRace.UnknownFemaleNpc => "9204",
|
||||||
_ => throw new InvalidEnumArgumentException()
|
_ => throw new InvalidEnumArgumentException(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +208,7 @@ namespace Penumbra.Game
|
||||||
"1804" => GenderRace.VieraFemaleNpc,
|
"1804" => GenderRace.VieraFemaleNpc,
|
||||||
"9104" => GenderRace.UnknownMaleNpc,
|
"9104" => GenderRace.UnknownMaleNpc,
|
||||||
"9204" => GenderRace.UnknownFemaleNpc,
|
"9204" => GenderRace.UnknownFemaleNpc,
|
||||||
_ => throw new KeyNotFoundException()
|
_ => throw new KeyNotFoundException(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -233,7 +233,7 @@ namespace Penumbra.Game
|
||||||
Race.Roegadyn => GenderRace.RoegadynMale,
|
Race.Roegadyn => GenderRace.RoegadynMale,
|
||||||
Race.AuRa => GenderRace.AuRaMale,
|
Race.AuRa => GenderRace.AuRaMale,
|
||||||
Race.Hrothgar => GenderRace.HrothgarMale,
|
Race.Hrothgar => GenderRace.HrothgarMale,
|
||||||
_ => GenderRace.Unknown
|
_ => GenderRace.Unknown,
|
||||||
},
|
},
|
||||||
Gender.MaleNpc => race switch
|
Gender.MaleNpc => race switch
|
||||||
{
|
{
|
||||||
|
|
@ -245,7 +245,7 @@ namespace Penumbra.Game
|
||||||
Race.Roegadyn => GenderRace.RoegadynMaleNpc,
|
Race.Roegadyn => GenderRace.RoegadynMaleNpc,
|
||||||
Race.AuRa => GenderRace.AuRaMaleNpc,
|
Race.AuRa => GenderRace.AuRaMaleNpc,
|
||||||
Race.Hrothgar => GenderRace.HrothgarMaleNpc,
|
Race.Hrothgar => GenderRace.HrothgarMaleNpc,
|
||||||
_ => GenderRace.Unknown
|
_ => GenderRace.Unknown,
|
||||||
},
|
},
|
||||||
Gender.Female => race switch
|
Gender.Female => race switch
|
||||||
{
|
{
|
||||||
|
|
@ -257,7 +257,7 @@ namespace Penumbra.Game
|
||||||
Race.Roegadyn => GenderRace.RoegadynFemale,
|
Race.Roegadyn => GenderRace.RoegadynFemale,
|
||||||
Race.AuRa => GenderRace.AuRaFemale,
|
Race.AuRa => GenderRace.AuRaFemale,
|
||||||
Race.Viera => GenderRace.VieraFemale,
|
Race.Viera => GenderRace.VieraFemale,
|
||||||
_ => GenderRace.Unknown
|
_ => GenderRace.Unknown,
|
||||||
},
|
},
|
||||||
Gender.FemaleNpc => race switch
|
Gender.FemaleNpc => race switch
|
||||||
{
|
{
|
||||||
|
|
@ -269,9 +269,9 @@ namespace Penumbra.Game
|
||||||
Race.Roegadyn => GenderRace.RoegadynFemaleNpc,
|
Race.Roegadyn => GenderRace.RoegadynFemaleNpc,
|
||||||
Race.AuRa => GenderRace.AuRaFemaleNpc,
|
Race.AuRa => GenderRace.AuRaFemaleNpc,
|
||||||
Race.Viera => GenderRace.VieraFemaleNpc,
|
Race.Viera => GenderRace.VieraFemaleNpc,
|
||||||
_ => GenderRace.Unknown
|
_ => GenderRace.Unknown,
|
||||||
},
|
},
|
||||||
_ => GenderRace.Unknown
|
_ => GenderRace.Unknown,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Game.Enums;
|
||||||
|
using Penumbra.Meta;
|
||||||
|
|
||||||
namespace Penumbra.Game
|
namespace Penumbra.Game
|
||||||
{
|
{
|
||||||
|
|
@ -46,7 +47,7 @@ namespace Penumbra.Game
|
||||||
|
|
||||||
RingL1 = 0b0100000000,
|
RingL1 = 0b0100000000,
|
||||||
RingL2 = 0b1000000000,
|
RingL2 = 0b1000000000,
|
||||||
RingLMask = 0b1100000000
|
RingLMask = 0b1100000000,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Eqdp
|
public static class Eqdp
|
||||||
|
|
@ -65,7 +66,7 @@ namespace Penumbra.Game
|
||||||
EquipSlot.Wrists => 4,
|
EquipSlot.Wrists => 4,
|
||||||
EquipSlot.RingR => 6,
|
EquipSlot.RingR => 6,
|
||||||
EquipSlot.RingL => 8,
|
EquipSlot.RingL => 8,
|
||||||
_ => throw new InvalidEnumArgumentException()
|
_ => throw new InvalidEnumArgumentException(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,7 +101,7 @@ namespace Penumbra.Game
|
||||||
EquipSlot.Wrists => EqdpEntry.WristsMask,
|
EquipSlot.Wrists => EqdpEntry.WristsMask,
|
||||||
EquipSlot.RingR => EqdpEntry.RingRMask,
|
EquipSlot.RingR => EqdpEntry.RingRMask,
|
||||||
EquipSlot.RingL => EqdpEntry.RingLMask,
|
EquipSlot.RingL => EqdpEntry.RingLMask,
|
||||||
_ => 0
|
_ => 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Game.Enums;
|
||||||
|
using Penumbra.Meta;
|
||||||
|
|
||||||
namespace Penumbra.Game
|
namespace Penumbra.Game
|
||||||
{
|
{
|
||||||
|
|
@ -79,7 +80,7 @@ namespace Penumbra.Game
|
||||||
_61 = 0x20_00_00ul << 40,
|
_61 = 0x20_00_00ul << 40,
|
||||||
_62 = 0x40_00_00ul << 40,
|
_62 = 0x40_00_00ul << 40,
|
||||||
_63 = 0x80_00_00ul << 40,
|
_63 = 0x80_00_00ul << 40,
|
||||||
HeadMask = 0xFF_FF_FFul << 40
|
HeadMask = 0xFF_FF_FFul << 40,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Eqp
|
public static class Eqp
|
||||||
|
|
@ -93,7 +94,7 @@ namespace Penumbra.Game
|
||||||
EquipSlot.Hands => ( 1, 3 ),
|
EquipSlot.Hands => ( 1, 3 ),
|
||||||
EquipSlot.Feet => ( 1, 4 ),
|
EquipSlot.Feet => ( 1, 4 ),
|
||||||
EquipSlot.Head => ( 3, 5 ),
|
EquipSlot.Head => ( 3, 5 ),
|
||||||
_ => throw new InvalidEnumArgumentException()
|
_ => throw new InvalidEnumArgumentException(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,7 +124,7 @@ namespace Penumbra.Game
|
||||||
EquipSlot.Legs => EqpEntry.LegsMask,
|
EquipSlot.Legs => EqpEntry.LegsMask,
|
||||||
EquipSlot.Feet => EqpEntry.FeetMask,
|
EquipSlot.Feet => EqpEntry.FeetMask,
|
||||||
EquipSlot.Hands => EqpEntry.HandsMask,
|
EquipSlot.Hands => EqpEntry.HandsMask,
|
||||||
_ => 0
|
_ => 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Dalamud;
|
using Dalamud;
|
||||||
|
using Penumbra.Game.Enums;
|
||||||
|
|
||||||
namespace Penumbra.Game
|
namespace Penumbra.Game
|
||||||
{
|
{
|
||||||
|
|
@ -16,7 +17,7 @@ namespace Penumbra.Game
|
||||||
PrimaryId = setId,
|
PrimaryId = setId,
|
||||||
GenderRace = gr,
|
GenderRace = gr,
|
||||||
Variant = variant,
|
Variant = variant,
|
||||||
EquipSlot = slot
|
EquipSlot = slot,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static GameObjectInfo Weapon( FileType type, ushort setId, ushort weaponId, byte variant = 0 )
|
public static GameObjectInfo Weapon( FileType type, ushort setId, ushort weaponId, byte variant = 0 )
|
||||||
|
|
@ -26,7 +27,7 @@ namespace Penumbra.Game
|
||||||
ObjectType = ObjectType.Weapon,
|
ObjectType = ObjectType.Weapon,
|
||||||
PrimaryId = setId,
|
PrimaryId = setId,
|
||||||
SecondaryId = weaponId,
|
SecondaryId = weaponId,
|
||||||
Variant = variant
|
Variant = variant,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static GameObjectInfo Customization( FileType type, CustomizationType customizationType, ushort id = 0
|
public static GameObjectInfo Customization( FileType type, CustomizationType customizationType, ushort id = 0
|
||||||
|
|
@ -39,7 +40,7 @@ namespace Penumbra.Game
|
||||||
GenderRace = gr,
|
GenderRace = gr,
|
||||||
BodySlot = bodySlot,
|
BodySlot = bodySlot,
|
||||||
Variant = variant,
|
Variant = variant,
|
||||||
CustomizationType = customizationType
|
CustomizationType = customizationType,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static GameObjectInfo Monster( FileType type, ushort monsterId, ushort bodyId, byte variant = 0 )
|
public static GameObjectInfo Monster( FileType type, ushort monsterId, ushort bodyId, byte variant = 0 )
|
||||||
|
|
@ -49,7 +50,7 @@ namespace Penumbra.Game
|
||||||
ObjectType = ObjectType.Monster,
|
ObjectType = ObjectType.Monster,
|
||||||
PrimaryId = monsterId,
|
PrimaryId = monsterId,
|
||||||
SecondaryId = bodyId,
|
SecondaryId = bodyId,
|
||||||
Variant = variant
|
Variant = variant,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static GameObjectInfo DemiHuman( FileType type, ushort demiHumanId, ushort bodyId, byte variant = 0,
|
public static GameObjectInfo DemiHuman( FileType type, ushort demiHumanId, ushort bodyId, byte variant = 0,
|
||||||
|
|
@ -61,7 +62,7 @@ namespace Penumbra.Game
|
||||||
PrimaryId = demiHumanId,
|
PrimaryId = demiHumanId,
|
||||||
SecondaryId = bodyId,
|
SecondaryId = bodyId,
|
||||||
Variant = variant,
|
Variant = variant,
|
||||||
EquipSlot = slot
|
EquipSlot = slot,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static GameObjectInfo Map( FileType type, byte c1, byte c2, byte c3, byte c4, byte variant, byte suffix = 0 )
|
public static GameObjectInfo Map( FileType type, byte c1, byte c2, byte c3, byte c4, byte variant, byte suffix = 0 )
|
||||||
|
|
@ -74,7 +75,7 @@ namespace Penumbra.Game
|
||||||
MapC3 = c3,
|
MapC3 = c3,
|
||||||
MapC4 = c4,
|
MapC4 = c4,
|
||||||
MapSuffix = suffix,
|
MapSuffix = suffix,
|
||||||
Variant = variant
|
Variant = variant,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static GameObjectInfo Icon( FileType type, uint iconId, bool hq, ClientLanguage lang = ClientLanguage.English )
|
public static GameObjectInfo Icon( FileType type, uint iconId, bool hq, ClientLanguage lang = ClientLanguage.English )
|
||||||
|
|
@ -84,7 +85,7 @@ namespace Penumbra.Game
|
||||||
ObjectType = ObjectType.Map,
|
ObjectType = ObjectType.Map,
|
||||||
IconId = iconId,
|
IconId = iconId,
|
||||||
IconHq = hq,
|
IconHq = hq,
|
||||||
Language = lang
|
Language = lang,
|
||||||
};
|
};
|
||||||
|
|
||||||
[FieldOffset( 0 )]
|
[FieldOffset( 0 )]
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
|
using Penumbra.Game.Enums;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Game
|
namespace Penumbra.Game
|
||||||
|
|
@ -61,7 +62,7 @@ namespace Penumbra.Game
|
||||||
, { ObjectType.Monster, new Regex[]{ new(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/b\k'id'\.imc") } }
|
, { ObjectType.Monster, new Regex[]{ new(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/b\k'id'\.imc") } }
|
||||||
, { ObjectType.Equipment, new Regex[]{ new(@"chara/equipment/e(?'id'\d{4})/e\k'id'\.imc") } }
|
, { ObjectType.Equipment, new Regex[]{ new(@"chara/equipment/e(?'id'\d{4})/e\k'id'\.imc") } }
|
||||||
, { ObjectType.DemiHuman, new Regex[]{ new(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/e\k'equip'\.imc") } }
|
, { ObjectType.DemiHuman, new Regex[]{ new(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/e\k'equip'\.imc") } }
|
||||||
, { ObjectType.Accessory, new Regex[]{ new(@"chara/accessory/a(?'id'\d{4})/a\k'id'\.imc") } } } }
|
, { ObjectType.Accessory, new Regex[]{ new(@"chara/accessory/a(?'id'\d{4})/a\k'id'\.imc") } } } },
|
||||||
};
|
};
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
|
|
@ -90,7 +91,7 @@ namespace Penumbra.Game
|
||||||
DemiHumanFolder => ObjectType.DemiHuman,
|
DemiHumanFolder => ObjectType.DemiHuman,
|
||||||
MonsterFolder => ObjectType.Monster,
|
MonsterFolder => ObjectType.Monster,
|
||||||
CommonFolder => ObjectType.Character,
|
CommonFolder => ObjectType.Character,
|
||||||
_ => ObjectType.Unknown
|
_ => ObjectType.Unknown,
|
||||||
},
|
},
|
||||||
UiFolder => folders[ 1 ] switch
|
UiFolder => folders[ 1 ] switch
|
||||||
{
|
{
|
||||||
|
|
@ -98,22 +99,22 @@ namespace Penumbra.Game
|
||||||
LoadingFolder => ObjectType.LoadingScreen,
|
LoadingFolder => ObjectType.LoadingScreen,
|
||||||
MapFolder => ObjectType.Map,
|
MapFolder => ObjectType.Map,
|
||||||
InterfaceFolder => ObjectType.Interface,
|
InterfaceFolder => ObjectType.Interface,
|
||||||
_ => ObjectType.Unknown
|
_ => ObjectType.Unknown,
|
||||||
},
|
},
|
||||||
CommonFolder => folders[ 1 ] switch
|
CommonFolder => folders[ 1 ] switch
|
||||||
{
|
{
|
||||||
FontFolder => ObjectType.Font,
|
FontFolder => ObjectType.Font,
|
||||||
_ => ObjectType.Unknown
|
_ => ObjectType.Unknown,
|
||||||
},
|
},
|
||||||
HousingFolder => ObjectType.Housing,
|
HousingFolder => ObjectType.Housing,
|
||||||
WorldFolder1 => folders[ 1 ] switch
|
WorldFolder1 => folders[ 1 ] switch
|
||||||
{
|
{
|
||||||
HousingFolder => ObjectType.Housing,
|
HousingFolder => ObjectType.Housing,
|
||||||
_ => ObjectType.World
|
_ => ObjectType.World,
|
||||||
},
|
},
|
||||||
WorldFolder2 => ObjectType.World,
|
WorldFolder2 => ObjectType.World,
|
||||||
VfxFolder => ObjectType.Vfx,
|
VfxFolder => ObjectType.Vfx,
|
||||||
_ => ObjectType.Unknown
|
_ => ObjectType.Unknown,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -257,8 +258,8 @@ namespace Penumbra.Game
|
||||||
var id = ushort.Parse( groups[ "id" ].Value );
|
var id = ushort.Parse( groups[ "id" ].Value );
|
||||||
if( groups[ "location" ].Success )
|
if( groups[ "location" ].Success )
|
||||||
{
|
{
|
||||||
var tmpType = groups[ "location" ].Value == "face" ? CustomizationType.DecalFace
|
var tmpType = groups[ "location" ].Value == "face" ? CustomizationType.DecalFace
|
||||||
: groups[ "location" ].Value == "equip" ? CustomizationType.DecalEquip : CustomizationType.Unknown;
|
: groups[ "location" ].Value == "equip" ? CustomizationType.DecalEquip : CustomizationType.Unknown;
|
||||||
return GameObjectInfo.Customization( fileType, tmpType, id );
|
return GameObjectInfo.Customization( fileType, tmpType, id );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -297,7 +298,7 @@ namespace Penumbra.Game
|
||||||
"ja" => Dalamud.ClientLanguage.Japanese,
|
"ja" => Dalamud.ClientLanguage.Japanese,
|
||||||
"de" => Dalamud.ClientLanguage.German,
|
"de" => Dalamud.ClientLanguage.German,
|
||||||
"fr" => Dalamud.ClientLanguage.French,
|
"fr" => Dalamud.ClientLanguage.French,
|
||||||
_ => Dalamud.ClientLanguage.English
|
_ => Dalamud.ClientLanguage.English,
|
||||||
};
|
};
|
||||||
return GameObjectInfo.Icon( fileType, id, hq, language );
|
return GameObjectInfo.Icon( fileType, id, hq, language );
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Meta;
|
||||||
|
|
||||||
namespace Penumbra.Game
|
namespace Penumbra.Game
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ namespace Penumbra.Game
|
||||||
RedrawAll( actors );
|
RedrawAll( actors );
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach( var actor in actors.Where( A => A.Name == name ) )
|
foreach( var actor in actors.Where( a => a.Name == name ) )
|
||||||
{
|
{
|
||||||
Redraw( actor );
|
Redraw( actor );
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,14 @@ namespace Penumbra.Hooks
|
||||||
|
|
||||||
// Object addresses
|
// Object addresses
|
||||||
private readonly IntPtr _playerResourceManagerAddress;
|
private readonly IntPtr _playerResourceManagerAddress;
|
||||||
public IntPtr PlayerResourceManagerPtr => Marshal.ReadIntPtr( _playerResourceManagerAddress );
|
|
||||||
|
public IntPtr PlayerResourceManagerPtr
|
||||||
|
=> Marshal.ReadIntPtr( _playerResourceManagerAddress );
|
||||||
|
|
||||||
private readonly IntPtr _characterResourceManagerAddress;
|
private readonly IntPtr _characterResourceManagerAddress;
|
||||||
|
|
||||||
public unsafe CharacterResourceManager* CharacterResourceManagerPtr =>
|
public unsafe CharacterResourceManager* CharacterResourceManagerPtr
|
||||||
( CharacterResourceManager* )Marshal.ReadIntPtr( _characterResourceManagerAddress ).ToPointer();
|
=> ( CharacterResourceManager* )Marshal.ReadIntPtr( _characterResourceManagerAddress ).ToPointer();
|
||||||
|
|
||||||
public GameResourceManagement( DalamudPluginInterface pluginInterface )
|
public GameResourceManagement( DalamudPluginInterface pluginInterface )
|
||||||
{
|
{
|
||||||
|
|
@ -70,7 +73,7 @@ namespace Penumbra.Hooks
|
||||||
public unsafe string ResourceToPath( byte* resource )
|
public unsafe string ResourceToPath( byte* resource )
|
||||||
=> Marshal.PtrToStringAnsi( new IntPtr( *( char** )( resource + 9 * 8 ) ) )!;
|
=> Marshal.PtrToStringAnsi( new IntPtr( *( char** )( resource + 9 * 8 ) ) )!;
|
||||||
|
|
||||||
public unsafe void ReloadCharacterResources()
|
private unsafe void ReloadCharacterResources()
|
||||||
{
|
{
|
||||||
var oldResources = new IntPtr[NumResources];
|
var oldResources = new IntPtr[NumResources];
|
||||||
var resources = new IntPtr( &CharacterResourceManagerPtr->Resources );
|
var resources = new IntPtr( &CharacterResourceManagerPtr->Resources );
|
||||||
|
|
@ -88,9 +91,9 @@ namespace Penumbra.Hooks
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginLog.Debug( "Freeing " +
|
PluginLog.Debug( "Freeing "
|
||||||
$"{ResourceToPath( ( byte* )oldResources[ i ].ToPointer() )}, replaced with " +
|
+ $"{ResourceToPath( ( byte* )oldResources[ i ].ToPointer() )}, replaced with "
|
||||||
$"{ResourceToPath( ( byte* )pResources[ i ] )}" );
|
+ $"{ResourceToPath( ( byte* )pResources[ i ] )}" );
|
||||||
|
|
||||||
UnloadCharacterResource( oldResources[ i ] );
|
UnloadCharacterResource( oldResources[ i ] );
|
||||||
}
|
}
|
||||||
|
|
|
||||||
45
Penumbra/Hooks/MusicManager.cs
Normal file
45
Penumbra/Hooks/MusicManager.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
using System;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
|
||||||
|
namespace Penumbra.Hooks
|
||||||
|
{
|
||||||
|
public unsafe class MusicManager
|
||||||
|
{
|
||||||
|
private readonly IntPtr _musicManager;
|
||||||
|
|
||||||
|
public MusicManager( Plugin plugin )
|
||||||
|
{
|
||||||
|
var scanner = plugin!.PluginInterface!.TargetModuleScanner;
|
||||||
|
var framework = plugin.PluginInterface.Framework.Address.BaseAddress;
|
||||||
|
|
||||||
|
// the wildcard is basically the framework offset we want (lol)
|
||||||
|
// .text:000000000009051A 48 8B 8E 18 2A 00 00 mov rcx, [rsi+2A18h]
|
||||||
|
// .text:0000000000090521 39 78 20 cmp [rax+20h], edi
|
||||||
|
// .text:0000000000090524 0F 94 C2 setz dl
|
||||||
|
// .text:0000000000090527 45 33 C0 xor r8d, r8d
|
||||||
|
// .text:000000000009052A E8 41 1C 15 00 call musicInit
|
||||||
|
var musicInitCallLocation = scanner.ScanText( "48 8B 8E ?? ?? ?? ?? 39 78 20 0F 94 C2 45 33 C0" );
|
||||||
|
var musicManagerOffset = *( int* )( musicInitCallLocation + 3 );
|
||||||
|
PluginLog.Debug( "Found MusicInitCall location at 0x{Location:X16}. Framework offset for MusicManager is 0x{Offset:X8}",
|
||||||
|
musicInitCallLocation.ToInt64(), musicManagerOffset );
|
||||||
|
_musicManager = *( IntPtr* )( framework + musicManagerOffset );
|
||||||
|
PluginLog.Debug( "MusicManager found at 0x{Location:X16}", _musicManager );
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool StreamingEnabled
|
||||||
|
{
|
||||||
|
get => *( bool* )( _musicManager + 50 );
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
PluginLog.Debug( value ? "Music streaming enabled." : "Music streaming disabled." );
|
||||||
|
*( bool* )( _musicManager + 50 ) = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnableStreaming()
|
||||||
|
=> StreamingEnabled = true;
|
||||||
|
|
||||||
|
public void DisableStreaming()
|
||||||
|
=> StreamingEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Structs;
|
using Penumbra.Structs;
|
||||||
|
|
@ -30,12 +31,12 @@ namespace Penumbra.Hooks
|
||||||
public unsafe delegate byte ReadSqpackPrototype( IntPtr pFileHandler, SeFileDescriptor* pFileDesc, int priority, bool isSync );
|
public unsafe delegate byte ReadSqpackPrototype( IntPtr pFileHandler, SeFileDescriptor* pFileDesc, int priority, bool isSync );
|
||||||
|
|
||||||
[Function( CallingConventions.Microsoft )]
|
[Function( CallingConventions.Microsoft )]
|
||||||
public unsafe delegate void* GetResourceSyncPrototype( IntPtr pFileManager, uint* pCategoryId, char* pResourceType,
|
public unsafe delegate void* GetResourceSyncPrototype( IntPtr pFileManager, uint* pCategoryId, char* pResourceType
|
||||||
uint* pResourceHash, char* pPath, void* pUnknown );
|
, uint* pResourceHash, char* pPath, void* pUnknown );
|
||||||
|
|
||||||
[Function( CallingConventions.Microsoft )]
|
[Function( CallingConventions.Microsoft )]
|
||||||
public unsafe delegate void* GetResourceAsyncPrototype( IntPtr pFileManager, uint* pCategoryId, char* pResourceType,
|
public unsafe delegate void* GetResourceAsyncPrototype( IntPtr pFileManager, uint* pCategoryId, char* pResourceType
|
||||||
uint* pResourceHash, char* pPath, void* pUnknown, bool isUnknown );
|
, uint* pResourceHash, char* pPath, void* pUnknown, bool isUnknown );
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
public IHook< GetResourceSyncPrototype >? GetResourceSyncHook { get; private set; }
|
public IHook< GetResourceSyncPrototype >? GetResourceSyncHook { get; private set; }
|
||||||
|
|
@ -46,7 +47,8 @@ namespace Penumbra.Hooks
|
||||||
public ReadFilePrototype? ReadFile { get; private set; }
|
public ReadFilePrototype? ReadFile { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
public bool LogAllFiles = false;
|
public bool LogAllFiles = false;
|
||||||
|
public Regex? LogFileFilter = null;
|
||||||
|
|
||||||
|
|
||||||
public ResourceLoader( Plugin plugin )
|
public ResourceLoader( Plugin plugin )
|
||||||
|
|
@ -87,7 +89,8 @@ namespace Penumbra.Hooks
|
||||||
uint* pResourceHash,
|
uint* pResourceHash,
|
||||||
char* pPath,
|
char* pPath,
|
||||||
void* pUnknown
|
void* pUnknown
|
||||||
) => GetResourceHandler( true, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, false );
|
)
|
||||||
|
=> GetResourceHandler( true, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, false );
|
||||||
|
|
||||||
private unsafe void* GetResourceAsyncHandler(
|
private unsafe void* GetResourceAsyncHandler(
|
||||||
IntPtr pFileManager,
|
IntPtr pFileManager,
|
||||||
|
|
@ -97,7 +100,8 @@ namespace Penumbra.Hooks
|
||||||
char* pPath,
|
char* pPath,
|
||||||
void* pUnknown,
|
void* pUnknown,
|
||||||
bool isUnknown
|
bool isUnknown
|
||||||
) => GetResourceHandler( false, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, isUnknown );
|
)
|
||||||
|
=> GetResourceHandler( false, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, isUnknown );
|
||||||
|
|
||||||
private unsafe void* CallOriginalHandler(
|
private unsafe void* CallOriginalHandler(
|
||||||
bool isSync,
|
bool isSync,
|
||||||
|
|
@ -131,34 +135,39 @@ namespace Penumbra.Hooks
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void* GetResourceHandler(
|
private unsafe void* GetResourceHandler(
|
||||||
bool isSync,
|
bool isSync,
|
||||||
IntPtr pFileManager,
|
IntPtr pFileManager,
|
||||||
uint* pCategoryId,
|
uint* pCategoryId,
|
||||||
char* pResourceType,
|
char* pResourceType,
|
||||||
uint* pResourceHash,
|
uint* pResourceHash,
|
||||||
char* pPath,
|
char* pPath,
|
||||||
void* pUnknown,
|
void* pUnknown,
|
||||||
bool isUnknown
|
bool isUnknown
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var modManager = Service< ModManager >.Get();
|
string file;
|
||||||
|
var modManager = Service< ModManager >.Get();
|
||||||
|
|
||||||
if( !Plugin!.Configuration!.IsEnabled || modManager == null )
|
if( !Plugin!.Configuration!.IsEnabled || modManager == null )
|
||||||
{
|
{
|
||||||
if( LogAllFiles )
|
if( LogAllFiles )
|
||||||
{
|
{
|
||||||
PluginLog.Log( "[GetResourceHandler] {0}",
|
file = Marshal.PtrToStringAnsi( new IntPtr( pPath ) )!;
|
||||||
GamePath.GenerateUncheckedLower( Marshal.PtrToStringAnsi( new IntPtr( pPath ) )! ) );
|
if( LogFileFilter == null || LogFileFilter.IsMatch( file ) )
|
||||||
|
{
|
||||||
|
PluginLog.Log( "[GetResourceHandler] {0}", file );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CallOriginalHandler( isSync, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, isUnknown );
|
return CallOriginalHandler( isSync, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, isUnknown );
|
||||||
}
|
}
|
||||||
|
|
||||||
var gameFsPath = GamePath.GenerateUncheckedLower( Marshal.PtrToStringAnsi( new IntPtr( pPath ) )! );
|
file = Marshal.PtrToStringAnsi( new IntPtr( pPath ) )!;
|
||||||
var replacementPath = modManager.ResolveSwappedOrReplacementFilePath( gameFsPath );
|
var gameFsPath = GamePath.GenerateUncheckedLower( file );
|
||||||
if( LogAllFiles )
|
var replacementPath = modManager.CurrentCollection.ResolveSwappedOrReplacementPath( gameFsPath );
|
||||||
|
if( LogAllFiles && ( LogFileFilter == null || LogFileFilter.IsMatch( file ) ) )
|
||||||
{
|
{
|
||||||
PluginLog.Log( "[GetResourceHandler] {0}", gameFsPath );
|
PluginLog.Log( "[GetResourceHandler] {0}", file );
|
||||||
}
|
}
|
||||||
|
|
||||||
// path must be < 260 because statically defined array length :(
|
// path must be < 260 because statically defined array length :(
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Reloaded.Hooks;
|
|
||||||
using Reloaded.Hooks.Definitions;
|
|
||||||
using Reloaded.Hooks.Definitions.X64;
|
|
||||||
|
|
||||||
namespace Penumbra.Hooks
|
|
||||||
{
|
|
||||||
public unsafe class SoundShit
|
|
||||||
{
|
|
||||||
private readonly IntPtr _musicManager;
|
|
||||||
|
|
||||||
public SoundShit( Plugin plugin )
|
|
||||||
{
|
|
||||||
var scanner = plugin!.PluginInterface!.TargetModuleScanner;
|
|
||||||
|
|
||||||
var fw = plugin.PluginInterface.Framework.Address.BaseAddress;
|
|
||||||
|
|
||||||
// the wildcard is basically the framework offset we want (lol)
|
|
||||||
// .text:000000000009051A 48 8B 8E 18 2A 00 00 mov rcx, [rsi+2A18h]
|
|
||||||
// .text:0000000000090521 39 78 20 cmp [rax+20h], edi
|
|
||||||
// .text:0000000000090524 0F 94 C2 setz dl
|
|
||||||
// .text:0000000000090527 45 33 C0 xor r8d, r8d
|
|
||||||
// .text:000000000009052A E8 41 1C 15 00 call musicInit
|
|
||||||
var shit = scanner.ScanText( "48 8B 8E ?? ?? ?? ?? 39 78 20 0F 94 C2 45 33 C0" );
|
|
||||||
var fuckkk = *( int* )( shit + 3 );
|
|
||||||
_musicManager = *( IntPtr* )( fw + fuckkk );
|
|
||||||
StreamingEnabled = false;
|
|
||||||
|
|
||||||
// PluginLog.Information("disabled streaming: {addr}", _musicManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool StreamingEnabled
|
|
||||||
{
|
|
||||||
get => *( bool* )( _musicManager + 50 );
|
|
||||||
set => *( bool* )( _musicManager + 50 ) = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,6 +5,6 @@ namespace Penumbra.Importer
|
||||||
None,
|
None,
|
||||||
WritingPackToDisk,
|
WritingPackToDisk,
|
||||||
ExtractingModFiles,
|
ExtractingModFiles,
|
||||||
Done
|
Done,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Lumina.Data;
|
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Importer
|
namespace Penumbra.Importer
|
||||||
{
|
{
|
||||||
public class MagicTempFileStreamManagerAndDeleterFuckery : PenumbraSqPackStream, IDisposable
|
public class MagicTempFileStreamManagerAndDeleter : PenumbraSqPackStream, IDisposable
|
||||||
{
|
{
|
||||||
private readonly FileStream _fileStream;
|
private readonly FileStream _fileStream;
|
||||||
|
|
||||||
public MagicTempFileStreamManagerAndDeleterFuckery( FileStream stream ) : base( stream ) => _fileStream = stream;
|
public MagicTempFileStreamManagerAndDeleter( FileStream stream )
|
||||||
|
: base( stream )
|
||||||
|
=> _fileStream = stream;
|
||||||
|
|
||||||
public new void Dispose()
|
public new void Dispose()
|
||||||
{
|
{
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Penumbra.Models;
|
using Penumbra.Structs;
|
||||||
|
|
||||||
namespace Penumbra.Importer.Models
|
namespace Penumbra.Importer.Models
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@ using Dalamud.Plugin;
|
||||||
using ICSharpCode.SharpZipLib.Zip;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Penumbra.Importer.Models;
|
using Penumbra.Importer.Models;
|
||||||
using Penumbra.Models;
|
using Penumbra.Mod;
|
||||||
|
using Penumbra.Structs;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
using FileMode = System.IO.FileMode;
|
||||||
|
|
||||||
namespace Penumbra.Importer
|
namespace Penumbra.Importer
|
||||||
{
|
{
|
||||||
|
|
@ -46,6 +48,12 @@ namespace Penumbra.Importer
|
||||||
_resolvedTempFilePath = Path.Combine( _outDirectory.FullName, TempFileName );
|
_resolvedTempFilePath = Path.Combine( _outDirectory.FullName, TempFileName );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string ReplaceBadXivSymbols( string source )
|
||||||
|
=> source.ReplaceInvalidPathSymbols().RemoveNonAsciiSymbols();
|
||||||
|
|
||||||
|
private static DirectoryInfo NewOptionDirectory( DirectoryInfo baseDir, string optionName )
|
||||||
|
=> new( Path.Combine( baseDir.FullName, ReplaceBadXivSymbols( optionName ) ) );
|
||||||
|
|
||||||
public void ImportModPack( FileInfo modPackFile )
|
public void ImportModPack( FileInfo modPackFile )
|
||||||
{
|
{
|
||||||
CurrentModPack = modPackFile.Name;
|
CurrentModPack = modPackFile.Name;
|
||||||
|
|
@ -94,7 +102,7 @@ namespace Penumbra.Importer
|
||||||
WriteZipEntryToTempFile( s );
|
WriteZipEntryToTempFile( s );
|
||||||
|
|
||||||
var fs = new FileStream( _resolvedTempFilePath, FileMode.Open );
|
var fs = new FileStream( _resolvedTempFilePath, FileMode.Open );
|
||||||
return new MagicTempFileStreamManagerAndDeleterFuckery( fs );
|
return new MagicTempFileStreamManagerAndDeleter( fs );
|
||||||
}
|
}
|
||||||
|
|
||||||
private void VerifyVersionAndImport( FileInfo modPackFile )
|
private void VerifyVersionAndImport( FileInfo modPackFile )
|
||||||
|
|
@ -187,13 +195,11 @@ namespace Penumbra.Importer
|
||||||
|
|
||||||
public static DirectoryInfo CreateModFolder( DirectoryInfo outDirectory, string modListName )
|
public static DirectoryInfo CreateModFolder( DirectoryInfo outDirectory, string modListName )
|
||||||
{
|
{
|
||||||
var correctedPath = Path.Combine( outDirectory.FullName,
|
var newModFolder = NewOptionDirectory( outDirectory, Path.GetFileName( modListName ) );
|
||||||
Path.GetFileName( modListName ).RemoveInvalidPathSymbols().RemoveNonAsciiSymbols() );
|
|
||||||
var newModFolder = new DirectoryInfo( correctedPath );
|
|
||||||
var i = 2;
|
var i = 2;
|
||||||
while( newModFolder.Exists && i < 12 )
|
while( newModFolder.Exists && i < 12 )
|
||||||
{
|
{
|
||||||
newModFolder = new DirectoryInfo( correctedPath + $" ({i++})" );
|
newModFolder = new DirectoryInfo( newModFolder.FullName + $" ({i++})" );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( newModFolder.Exists )
|
if( newModFolder.Exists )
|
||||||
|
|
@ -272,7 +278,7 @@ namespace Penumbra.Importer
|
||||||
|
|
||||||
foreach( var group in page.ModGroups.Where( group => group.GroupName != null && group.OptionList != null ) )
|
foreach( var group in page.ModGroups.Where( group => group.GroupName != null && group.OptionList != null ) )
|
||||||
{
|
{
|
||||||
var groupFolder = new DirectoryInfo( Path.Combine( newModFolder.FullName, group.GroupName!.ReplaceInvalidPathSymbols().RemoveNonAsciiSymbols( ) ) );
|
var groupFolder = NewOptionDirectory( newModFolder, group.GroupName! );
|
||||||
if( groupFolder.Exists )
|
if( groupFolder.Exists )
|
||||||
{
|
{
|
||||||
groupFolder = new DirectoryInfo( groupFolder.FullName + $" ({page.PageIndex})" );
|
groupFolder = new DirectoryInfo( groupFolder.FullName + $" ({page.PageIndex})" );
|
||||||
|
|
@ -281,7 +287,7 @@ namespace Penumbra.Importer
|
||||||
|
|
||||||
foreach( var option in group.OptionList!.Where( option => option.Name != null && option.ModsJsons != null ) )
|
foreach( var option in group.OptionList!.Where( option => option.Name != null && option.ModsJsons != null ) )
|
||||||
{
|
{
|
||||||
var optionFolder = new DirectoryInfo( Path.Combine( groupFolder.FullName, option.Name!.ReplaceInvalidPathSymbols().RemoveNonAsciiSymbols() ) );
|
var optionFolder = NewOptionDirectory( groupFolder, option.Name! );
|
||||||
ExtractSimpleModList( optionFolder, option.ModsJsons!, modData );
|
ExtractSimpleModList( optionFolder, option.ModsJsons!, modData );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -311,7 +317,7 @@ namespace Penumbra.Importer
|
||||||
OptionDesc = string.IsNullOrEmpty( opt.Description ) ? "" : opt.Description!,
|
OptionDesc = string.IsNullOrEmpty( opt.Description ) ? "" : opt.Description!,
|
||||||
OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >(),
|
OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >(),
|
||||||
};
|
};
|
||||||
var optDir = new DirectoryInfo( Path.Combine( groupFolder.FullName, opt.Name!.ReplaceInvalidPathSymbols().RemoveNonAsciiSymbols() ) );
|
var optDir = NewOptionDirectory( groupFolder, opt.Name! );
|
||||||
if( optDir.Exists )
|
if( optDir.Exists )
|
||||||
{
|
{
|
||||||
foreach( var file in optDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
|
foreach( var file in optDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,11 @@ using System.Text.RegularExpressions;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Lumina.Data.Files;
|
using Lumina.Data.Files;
|
||||||
using Penumbra.Game;
|
using Penumbra.Game;
|
||||||
using Penumbra.MetaData;
|
using Penumbra.Game.Enums;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Meta;
|
||||||
|
using Penumbra.Meta.Files;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
using GameData = Penumbra.Game.Enums.GameData;
|
||||||
|
|
||||||
namespace Penumbra.Importer
|
namespace Penumbra.Importer
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ using System.Linq;
|
||||||
using Lumina.Data;
|
using Lumina.Data;
|
||||||
using Penumbra.Game;
|
using Penumbra.Game;
|
||||||
|
|
||||||
namespace Penumbra.MetaData
|
namespace Penumbra.Meta.Files
|
||||||
{
|
{
|
||||||
// EQDP file structure:
|
// EQDP file structure:
|
||||||
// [Identifier][BlockSize:ushort][BlockCount:ushort]
|
// [Identifier][BlockSize:ushort][BlockCount:ushort]
|
||||||
|
|
@ -37,7 +37,8 @@ namespace Penumbra.MetaData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref EqdpEntry this[ ushort setId ] => ref GetTrueEntry( setId );
|
public ref EqdpEntry this[ ushort setId ]
|
||||||
|
=> ref GetTrueEntry( setId );
|
||||||
|
|
||||||
|
|
||||||
public EqdpFile Clone()
|
public EqdpFile Clone()
|
||||||
|
|
@ -49,8 +50,11 @@ namespace Penumbra.MetaData
|
||||||
private ushort ExpandedBlockCount { get; set; }
|
private ushort ExpandedBlockCount { get; set; }
|
||||||
private EqdpEntry[]?[] Blocks { get; }
|
private EqdpEntry[]?[] Blocks { get; }
|
||||||
|
|
||||||
private int BlockIdx( ushort id ) => ( ushort )( id / BlockSize );
|
private int BlockIdx( ushort id )
|
||||||
private int SubIdx( ushort id ) => ( ushort )( id % BlockSize );
|
=> ( ushort )( id / BlockSize );
|
||||||
|
|
||||||
|
private int SubIdx( ushort id )
|
||||||
|
=> ( ushort )( id % BlockSize );
|
||||||
|
|
||||||
private bool ExpandBlock( int idx )
|
private bool ExpandBlock( int idx )
|
||||||
{
|
{
|
||||||
|
|
@ -156,7 +160,7 @@ namespace Penumbra.MetaData
|
||||||
private void WriteBlocks( BinaryWriter bw )
|
private void WriteBlocks( BinaryWriter bw )
|
||||||
{
|
{
|
||||||
foreach( var entry in Blocks.Where( block => block != null )
|
foreach( var entry in Blocks.Where( block => block != null )
|
||||||
.SelectMany( block => block ) )
|
.SelectMany( block => block ) )
|
||||||
{
|
{
|
||||||
bw.Write( ( ushort )entry );
|
bw.Write( ( ushort )entry );
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ using System.Linq;
|
||||||
using Lumina.Data;
|
using Lumina.Data;
|
||||||
using Penumbra.Game;
|
using Penumbra.Game;
|
||||||
|
|
||||||
namespace Penumbra.MetaData
|
namespace Penumbra.Meta.Files
|
||||||
{
|
{
|
||||||
// EQP Structure:
|
// EQP Structure:
|
||||||
// 64 x [Block collapsed or not bit]
|
// 64 x [Block collapsed or not bit]
|
||||||
|
|
@ -28,7 +28,7 @@ namespace Penumbra.MetaData
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] WriteBytes()
|
public byte[] WriteBytes()
|
||||||
=> WriteBytes( _entries, E => ( ulong )E );
|
=> WriteBytes( _entries, e => ( ulong )e );
|
||||||
|
|
||||||
public EqpFile Clone()
|
public EqpFile Clone()
|
||||||
=> new( this );
|
=> new( this );
|
||||||
|
|
@ -40,7 +40,7 @@ namespace Penumbra.MetaData
|
||||||
=> GetEntry( _entries, setId, ( EqpEntry )0 );
|
=> GetEntry( _entries, setId, ( EqpEntry )0 );
|
||||||
|
|
||||||
public bool SetEntry( ushort setId, EqpEntry entry )
|
public bool SetEntry( ushort setId, EqpEntry entry )
|
||||||
=> SetEntry( _entries, setId, entry, E => E == 0, ( E1, E2 ) => E1 == E2 );
|
=> SetEntry( _entries, setId, entry, e => e == 0, ( e1, e2 ) => e1 == e2 );
|
||||||
|
|
||||||
public ref EqpEntry this[ ushort setId ]
|
public ref EqpEntry this[ ushort setId ]
|
||||||
=> ref GetTrueEntry( _entries, setId );
|
=> ref GetTrueEntry( _entries, setId );
|
||||||
|
|
@ -2,9 +2,9 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Lumina.Data;
|
using Lumina.Data;
|
||||||
using Penumbra.Game;
|
using Penumbra.Game.Enums;
|
||||||
|
|
||||||
namespace Penumbra.MetaData
|
namespace Penumbra.Meta.Files
|
||||||
{
|
{
|
||||||
// EST Structure:
|
// EST Structure:
|
||||||
// 1x [NumEntries : UInt32]
|
// 1x [NumEntries : UInt32]
|
||||||
|
|
@ -101,7 +101,7 @@ namespace Penumbra.MetaData
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !setDict.TryGetValue( setId, out var entry ) ? (ushort) 0 : entry;
|
return !setDict.TryGetValue( setId, out var entry ) ? ( ushort )0 : entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] WriteBytes()
|
public byte[] WriteBytes()
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using Lumina.Data;
|
using Lumina.Data;
|
||||||
using Penumbra.Game;
|
using Penumbra.Game;
|
||||||
|
|
||||||
namespace Penumbra.MetaData
|
namespace Penumbra.Meta.Files
|
||||||
{
|
{
|
||||||
// GmpFiles use the same structure as Eqp Files.
|
// GmpFiles use the same structure as Eqp Files.
|
||||||
// Entries are also one ulong.
|
// Entries are also one ulong.
|
||||||
|
|
@ -22,19 +22,19 @@ namespace Penumbra.MetaData
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] WriteBytes()
|
public byte[] WriteBytes()
|
||||||
=> WriteBytes( _entries, E => ( ulong )E );
|
=> WriteBytes( _entries, e => ( ulong )e );
|
||||||
|
|
||||||
public GmpFile Clone()
|
public GmpFile Clone()
|
||||||
=> new( this );
|
=> new( this );
|
||||||
|
|
||||||
public GmpFile( FileResource file )
|
public GmpFile( FileResource file )
|
||||||
=> ReadFile( _entries, file, I => ( GmpEntry )I );
|
=> ReadFile( _entries, file, i => ( GmpEntry )i );
|
||||||
|
|
||||||
public GmpEntry GetEntry( ushort setId )
|
public GmpEntry GetEntry( ushort setId )
|
||||||
=> GetEntry( _entries, setId, ( GmpEntry )0 );
|
=> GetEntry( _entries, setId, ( GmpEntry )0 );
|
||||||
|
|
||||||
public bool SetEntry( ushort setId, GmpEntry entry )
|
public bool SetEntry( ushort setId, GmpEntry entry )
|
||||||
=> SetEntry( _entries, setId, entry, E => E == 0, ( E1, E2 ) => E1 == E2 );
|
=> SetEntry( _entries, setId, entry, e => e == 0, ( e1, e2 ) => e1 == e2 );
|
||||||
|
|
||||||
public ref GmpEntry this[ ushort setId ]
|
public ref GmpEntry this[ ushort setId ]
|
||||||
=> ref GetTrueEntry( _entries, setId );
|
=> ref GetTrueEntry( _entries, setId );
|
||||||
|
|
@ -2,22 +2,33 @@ using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Lumina.Data.Files;
|
using Lumina.Data.Files;
|
||||||
using Penumbra.Game;
|
using Penumbra.Game.Enums;
|
||||||
using Penumbra.Mods;
|
|
||||||
|
|
||||||
namespace Penumbra.MetaData
|
namespace Penumbra.Meta.Files
|
||||||
{
|
{
|
||||||
public class InvalidImcVariantException : ArgumentOutOfRangeException
|
public class InvalidImcVariantException : ArgumentOutOfRangeException
|
||||||
{
|
{
|
||||||
public InvalidImcVariantException()
|
public InvalidImcVariantException()
|
||||||
: base("Trying to manipulate invalid variant.")
|
: base( "Trying to manipulate invalid variant." )
|
||||||
{ }
|
{ }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ImcExtensions
|
public static class ImcExtensions
|
||||||
{
|
{
|
||||||
|
public static ulong ToInteger( this ImcFile.ImageChangeData imc )
|
||||||
|
{
|
||||||
|
ulong ret = imc.MaterialId;
|
||||||
|
ret |= ( ulong )imc.DecalId << 8;
|
||||||
|
ret |= ( ulong )imc.AttributeMask << 16;
|
||||||
|
ret |= ( ulong )imc.SoundId << 16;
|
||||||
|
ret |= ( ulong )imc.VfxId << 32;
|
||||||
|
var tmp = imc.GetType().GetField( "_MaterialAnimationIdMask",
|
||||||
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance );
|
||||||
|
ret |= ( ulong )( byte )tmp!.GetValue( imc ) << 40;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool Equal( this ImcFile.ImageChangeData lhs, ImcFile.ImageChangeData rhs )
|
public static bool Equal( this ImcFile.ImageChangeData lhs, ImcFile.ImageChangeData rhs )
|
||||||
=> lhs.MaterialId == rhs.MaterialId
|
=> lhs.MaterialId == rhs.MaterialId
|
||||||
&& lhs.DecalId == rhs.DecalId
|
&& lhs.DecalId == rhs.DecalId
|
||||||
|
|
@ -35,7 +46,6 @@ namespace Penumbra.MetaData
|
||||||
bw.Write( variant.MaterialAnimationId );
|
bw.Write( variant.MaterialAnimationId );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static byte[] WriteBytes( this ImcFile file )
|
public static byte[] WriteBytes( this ImcFile file )
|
||||||
{
|
{
|
||||||
var parts = file.PartMask == 31 ? 5 : 1;
|
var parts = file.PartMask == 31 ? 5 : 1;
|
||||||
|
|
@ -104,10 +114,10 @@ namespace Penumbra.MetaData
|
||||||
Count = file.Count,
|
Count = file.Count,
|
||||||
PartMask = file.PartMask,
|
PartMask = file.PartMask,
|
||||||
};
|
};
|
||||||
var parts = file.GetParts().Select( P => new ImcFile.ImageChangeParts()
|
var parts = file.GetParts().Select( p => new ImcFile.ImageChangeParts()
|
||||||
{
|
{
|
||||||
DefaultVariant = P.DefaultVariant,
|
DefaultVariant = p.DefaultVariant,
|
||||||
Variants = ( ImcFile.ImageChangeData[] )P.Variants.Clone(),
|
Variants = ( ImcFile.ImageChangeData[] )p.Variants.Clone(),
|
||||||
} ).ToArray();
|
} ).ToArray();
|
||||||
var prop = ret.GetType().GetField( "Parts", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance );
|
var prop = ret.GetType().GetField( "Parts", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance );
|
||||||
prop!.SetValue( ret, parts );
|
prop!.SetValue( ret, parts );
|
||||||
|
|
@ -4,10 +4,10 @@ using Dalamud.Plugin;
|
||||||
using Lumina.Data;
|
using Lumina.Data;
|
||||||
using Lumina.Data.Files;
|
using Lumina.Data.Files;
|
||||||
using Penumbra.Game;
|
using Penumbra.Game;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Game.Enums;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.MetaData
|
namespace Penumbra.Meta.Files
|
||||||
{
|
{
|
||||||
public class MetaDefaults
|
public class MetaDefaults
|
||||||
{
|
{
|
||||||
|
|
@ -112,14 +112,20 @@ namespace Penumbra.MetaData
|
||||||
return m.Type switch
|
return m.Type switch
|
||||||
{
|
{
|
||||||
MetaType.Imc => GetDefaultImcFile( m.ImcIdentifier.ObjectType, m.ImcIdentifier.PrimaryId, m.ImcIdentifier.SecondaryId )
|
MetaType.Imc => GetDefaultImcFile( m.ImcIdentifier.ObjectType, m.ImcIdentifier.PrimaryId, m.ImcIdentifier.SecondaryId )
|
||||||
?.GetValue( m ).Equal( m.ImcValue ) ?? false,
|
?.GetValue( m ).Equal( m.ImcValue )
|
||||||
MetaType.Gmp => GetDefaultGmpFile()?.GetEntry( m.GmpIdentifier.SetId ) == m.GmpValue,
|
?? true,
|
||||||
MetaType.Eqp => GetDefaultEqpFile()?.GetEntry( m.EqpIdentifier.SetId ).Reduce( m.EqpIdentifier.Slot ) == m.EqpValue,
|
MetaType.Gmp => GetDefaultGmpFile()?.GetEntry( m.GmpIdentifier.SetId )
|
||||||
|
== m.GmpValue,
|
||||||
|
MetaType.Eqp => GetDefaultEqpFile()?.GetEntry( m.EqpIdentifier.SetId )
|
||||||
|
.Reduce( m.EqpIdentifier.Slot )
|
||||||
|
== m.EqpValue,
|
||||||
MetaType.Eqdp => GetDefaultEqdpFile( m.EqdpIdentifier.Slot, m.EqdpIdentifier.GenderRace )?.GetEntry( m.EqdpIdentifier.SetId )
|
MetaType.Eqdp => GetDefaultEqdpFile( m.EqdpIdentifier.Slot, m.EqdpIdentifier.GenderRace )?.GetEntry( m.EqdpIdentifier.SetId )
|
||||||
.Reduce( m.EqdpIdentifier.Slot ) == m.EqdpValue,
|
.Reduce( m.EqdpIdentifier.Slot )
|
||||||
|
== m.EqdpValue,
|
||||||
MetaType.Est => GetDefaultEstFile( m.EstIdentifier.ObjectType, m.EstIdentifier.EquipSlot, m.EstIdentifier.BodySlot )
|
MetaType.Est => GetDefaultEstFile( m.EstIdentifier.ObjectType, m.EstIdentifier.EquipSlot, m.EstIdentifier.BodySlot )
|
||||||
?.GetEntry( m.EstIdentifier.GenderRace, m.EstIdentifier.PrimaryId ) == m.EstValue,
|
?.GetEntry( m.EstIdentifier.GenderRace, m.EstIdentifier.PrimaryId )
|
||||||
_ => throw new NotImplementedException()
|
== m.EstValue,
|
||||||
|
_ => throw new NotImplementedException(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,7 +138,7 @@ namespace Penumbra.MetaData
|
||||||
MetaType.Eqp => GetNewEqpFile(),
|
MetaType.Eqp => GetNewEqpFile(),
|
||||||
MetaType.Eqdp => GetNewEqdpFile( m.EqdpIdentifier.Slot, m.EqdpIdentifier.GenderRace ),
|
MetaType.Eqdp => GetNewEqdpFile( m.EqdpIdentifier.Slot, m.EqdpIdentifier.GenderRace ),
|
||||||
MetaType.Est => GetNewEstFile( m.EstIdentifier.ObjectType, m.EstIdentifier.EquipSlot, m.EstIdentifier.BodySlot ),
|
MetaType.Est => GetNewEstFile( m.EstIdentifier.ObjectType, m.EstIdentifier.EquipSlot, m.EstIdentifier.BodySlot ),
|
||||||
_ => throw new NotImplementedException()
|
_ => throw new NotImplementedException(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using Penumbra.Game;
|
using Penumbra.Game.Enums;
|
||||||
using Penumbra.Mods;
|
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.MetaData
|
namespace Penumbra.Meta.Files
|
||||||
{
|
{
|
||||||
public static class MetaFileNames
|
public static class MetaFileNames
|
||||||
{
|
{
|
||||||
149
Penumbra/Meta/Identifier.cs
Normal file
149
Penumbra/Meta/Identifier.cs
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Penumbra.Game.Enums;
|
||||||
|
|
||||||
|
namespace Penumbra.Meta
|
||||||
|
{
|
||||||
|
public enum MetaType : byte
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
Imc = 1,
|
||||||
|
Eqdp = 2,
|
||||||
|
Eqp = 3,
|
||||||
|
Est = 4,
|
||||||
|
Gmp = 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
|
public struct EqpIdentifier
|
||||||
|
{
|
||||||
|
[FieldOffset( 0 )]
|
||||||
|
public ulong Value;
|
||||||
|
|
||||||
|
[FieldOffset( 0 )]
|
||||||
|
public MetaType Type;
|
||||||
|
|
||||||
|
[FieldOffset( 1 )]
|
||||||
|
public EquipSlot Slot;
|
||||||
|
|
||||||
|
[FieldOffset( 2 )]
|
||||||
|
public ushort SetId;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> $"Eqp - {SetId} - {Slot}";
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
|
public struct EqdpIdentifier
|
||||||
|
{
|
||||||
|
[FieldOffset( 0 )]
|
||||||
|
public ulong Value;
|
||||||
|
|
||||||
|
[FieldOffset( 0 )]
|
||||||
|
public MetaType Type;
|
||||||
|
|
||||||
|
[FieldOffset( 1 )]
|
||||||
|
public EquipSlot Slot;
|
||||||
|
|
||||||
|
[FieldOffset( 2 )]
|
||||||
|
public GenderRace GenderRace;
|
||||||
|
|
||||||
|
[FieldOffset( 4 )]
|
||||||
|
public ushort SetId;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> $"Eqdp - {SetId} - {Slot} - {GenderRace.Split().Item2} {GenderRace.Split().Item1}";
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
|
public struct GmpIdentifier
|
||||||
|
{
|
||||||
|
[FieldOffset( 0 )]
|
||||||
|
public ulong Value;
|
||||||
|
|
||||||
|
[FieldOffset( 0 )]
|
||||||
|
public MetaType Type;
|
||||||
|
|
||||||
|
[FieldOffset( 1 )]
|
||||||
|
public ushort SetId;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> $"Gmp - {SetId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
|
public struct EstIdentifier
|
||||||
|
{
|
||||||
|
[FieldOffset( 0 )]
|
||||||
|
public ulong Value;
|
||||||
|
|
||||||
|
[FieldOffset( 0 )]
|
||||||
|
public MetaType Type;
|
||||||
|
|
||||||
|
[FieldOffset( 1 )]
|
||||||
|
public ObjectType ObjectType;
|
||||||
|
|
||||||
|
[FieldOffset( 2 )]
|
||||||
|
public EquipSlot EquipSlot;
|
||||||
|
|
||||||
|
[FieldOffset( 3 )]
|
||||||
|
public BodySlot BodySlot;
|
||||||
|
|
||||||
|
[FieldOffset( 4 )]
|
||||||
|
public GenderRace GenderRace;
|
||||||
|
|
||||||
|
[FieldOffset( 6 )]
|
||||||
|
public ushort PrimaryId;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> ObjectType == ObjectType.Equipment
|
||||||
|
? $"Est - {PrimaryId} - {EquipSlot} - {GenderRace.Split().Item2} {GenderRace.Split().Item1}"
|
||||||
|
: $"Est - {PrimaryId} - {BodySlot} - {GenderRace.Split().Item2} {GenderRace.Split().Item1}";
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
|
public struct ImcIdentifier
|
||||||
|
{
|
||||||
|
[FieldOffset( 0 )]
|
||||||
|
public ulong Value;
|
||||||
|
|
||||||
|
[FieldOffset( 0 )]
|
||||||
|
public MetaType Type;
|
||||||
|
|
||||||
|
[FieldOffset( 1 )]
|
||||||
|
public byte _objectAndBody;
|
||||||
|
|
||||||
|
public ObjectType ObjectType
|
||||||
|
{
|
||||||
|
get => ( ObjectType )( _objectAndBody & 0b00011111 );
|
||||||
|
set => _objectAndBody = ( byte )( ( _objectAndBody & 0b11100000 ) | ( byte )value );
|
||||||
|
}
|
||||||
|
|
||||||
|
public BodySlot BodySlot
|
||||||
|
{
|
||||||
|
get => ( BodySlot )( _objectAndBody >> 5 );
|
||||||
|
set => _objectAndBody = ( byte )( ( _objectAndBody & 0b00011111 ) | ( ( byte )value << 5 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
[FieldOffset( 2 )]
|
||||||
|
public ushort PrimaryId;
|
||||||
|
|
||||||
|
[FieldOffset( 4 )]
|
||||||
|
public ushort Variant;
|
||||||
|
|
||||||
|
[FieldOffset( 6 )]
|
||||||
|
public ushort SecondaryId;
|
||||||
|
|
||||||
|
[FieldOffset( 6 )]
|
||||||
|
public EquipSlot EquipSlot;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ObjectType switch
|
||||||
|
{
|
||||||
|
ObjectType.Accessory => $"Imc - {PrimaryId} - {EquipSlot} - {Variant}",
|
||||||
|
ObjectType.Equipment => $"Imc - {PrimaryId} - {EquipSlot} - {Variant}",
|
||||||
|
_ => $"Imc - {PrimaryId} - {ObjectType} - {SecondaryId} - {BodySlot} - {Variant}",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
221
Penumbra/Meta/MetaCollection.cs
Normal file
221
Penumbra/Meta/MetaCollection.cs
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Penumbra.Importer;
|
||||||
|
using Penumbra.Meta.Files;
|
||||||
|
using Penumbra.Mod;
|
||||||
|
using Penumbra.Structs;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Meta
|
||||||
|
{
|
||||||
|
// Corresponds meta manipulations of any kind with the settings for a mod.
|
||||||
|
// DefaultData contains all manipulations that are active regardless of option groups.
|
||||||
|
// GroupData contains a mapping of Group -> { Options -> {Manipulations} }.
|
||||||
|
public class MetaCollection
|
||||||
|
{
|
||||||
|
public List< MetaManipulation > DefaultData = new();
|
||||||
|
public Dictionary< string, Dictionary< string, List< MetaManipulation > > > GroupData = new();
|
||||||
|
|
||||||
|
|
||||||
|
// Store total number of manipulations for some ease of access.
|
||||||
|
[JsonProperty]
|
||||||
|
public int Count { get; private set; } = 0;
|
||||||
|
|
||||||
|
|
||||||
|
// Return an enumeration of all active meta manipulations for a given mod with given settings.
|
||||||
|
public IEnumerable< MetaManipulation > GetManipulationsForConfig( ModSettings settings, ModMeta modMeta )
|
||||||
|
{
|
||||||
|
if( Count == DefaultData.Count )
|
||||||
|
{
|
||||||
|
return DefaultData;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable< MetaManipulation > ret = DefaultData;
|
||||||
|
|
||||||
|
foreach( var group in modMeta.Groups )
|
||||||
|
{
|
||||||
|
if( !GroupData.TryGetValue( group.Key, out var metas ) || !settings.Settings.TryGetValue( group.Key, out var setting ) )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( group.Value.SelectionType == SelectType.Single )
|
||||||
|
{
|
||||||
|
var settingName = group.Value.Options[ setting ].OptionName;
|
||||||
|
if( metas.TryGetValue( settingName, out var meta ) )
|
||||||
|
{
|
||||||
|
ret = ret.Concat( meta );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for( var i = 0; i < group.Value.Options.Count; ++i )
|
||||||
|
{
|
||||||
|
var flag = 1 << i;
|
||||||
|
if( ( setting & flag ) == 0 )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var settingName = group.Value.Options[ i ].OptionName;
|
||||||
|
if( metas.TryGetValue( settingName, out var meta ) )
|
||||||
|
{
|
||||||
|
ret = ret.Concat( meta );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the collection is still basically valid,
|
||||||
|
// i.e. keep it sorted, and verify that the options stored by name are all still part of the mod,
|
||||||
|
// and that the contained manipulations are still valid and non-default manipulations.
|
||||||
|
public bool Validate( ModMeta modMeta )
|
||||||
|
{
|
||||||
|
var defaultFiles = Service< MetaDefaults >.Get();
|
||||||
|
SortLists();
|
||||||
|
foreach( var group in GroupData )
|
||||||
|
{
|
||||||
|
if( !modMeta.Groups.TryGetValue( group.Key, out var options ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var option in group.Value )
|
||||||
|
{
|
||||||
|
if( options.Options.All( o => o.OptionName != option.Key ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( option.Value.Any( manip => defaultFiles.CheckAgainstDefault( manip ) ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultData.All( manip => !defaultFiles.CheckAgainstDefault( manip ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-sort all manipulations.
|
||||||
|
private void SortLists()
|
||||||
|
{
|
||||||
|
DefaultData.Sort();
|
||||||
|
foreach( var list in GroupData.Values.SelectMany( g => g.Values ) )
|
||||||
|
{
|
||||||
|
list.Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a parsed TexTools .meta file to a given option group and option. If group is the empty string, add it to default.
|
||||||
|
// Creates the option group and the option if necessary.
|
||||||
|
private void AddMeta( string group, string option, TexToolsMeta meta )
|
||||||
|
{
|
||||||
|
if( meta.Manipulations.Count == 0 )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( group.Length == 0 )
|
||||||
|
{
|
||||||
|
DefaultData.AddRange( meta.Manipulations );
|
||||||
|
}
|
||||||
|
else if( option.Length == 0 )
|
||||||
|
{ }
|
||||||
|
else if( !GroupData.TryGetValue( group, out var options ) )
|
||||||
|
{
|
||||||
|
GroupData.Add( group, new Dictionary< string, List< MetaManipulation > >() { { option, meta.Manipulations.ToList() } } );
|
||||||
|
}
|
||||||
|
else if( !options.TryGetValue( option, out var list ) )
|
||||||
|
{
|
||||||
|
options.Add( option, meta.Manipulations.ToList() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list.AddRange( meta.Manipulations );
|
||||||
|
}
|
||||||
|
|
||||||
|
Count += meta.Manipulations.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the whole meta collection by reading all TexTools .meta files in a mod directory anew,
|
||||||
|
// combining them with the given ModMeta.
|
||||||
|
public void Update( IEnumerable< FileInfo > files, DirectoryInfo basePath, ModMeta modMeta )
|
||||||
|
{
|
||||||
|
DefaultData.Clear();
|
||||||
|
GroupData.Clear();
|
||||||
|
foreach( var file in files.Where( f => f.Extension == ".meta" ) )
|
||||||
|
{
|
||||||
|
var metaData = new TexToolsMeta( File.ReadAllBytes( file.FullName ) );
|
||||||
|
if( metaData.FilePath == string.Empty || metaData.Manipulations.Count == 0 )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = new RelPath( file, basePath );
|
||||||
|
var foundAny = false;
|
||||||
|
foreach( var group in modMeta.Groups )
|
||||||
|
{
|
||||||
|
foreach( var option in group.Value.Options.Where( o => o.OptionFiles.ContainsKey( path ) ) )
|
||||||
|
{
|
||||||
|
foundAny = true;
|
||||||
|
AddMeta( group.Key, option.OptionName, metaData );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !foundAny )
|
||||||
|
{
|
||||||
|
AddMeta( string.Empty, string.Empty, metaData );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SortLists();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FileInfo FileName( DirectoryInfo basePath )
|
||||||
|
=> new( Path.Combine( basePath.FullName, "metadata_manipulations.json" ) );
|
||||||
|
|
||||||
|
public void SaveToFile( FileInfo file )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var text = JsonConvert.SerializeObject( this, Formatting.Indented );
|
||||||
|
File.WriteAllText( file.FullName, text );
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not write metadata manipulations file to {file.FullName}:\n{e}" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MetaCollection? LoadFromFile( FileInfo file )
|
||||||
|
{
|
||||||
|
if( !file.Exists )
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var text = File.ReadAllText( file.FullName );
|
||||||
|
|
||||||
|
var collection = JsonConvert.DeserializeObject< MetaCollection >( text,
|
||||||
|
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore } );
|
||||||
|
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not load mod metadata manipulations from {file.FullName}:\n{e}" );
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,10 +5,10 @@ using System.Linq;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Lumina.Data.Files;
|
using Lumina.Data.Files;
|
||||||
using Penumbra.Hooks;
|
using Penumbra.Hooks;
|
||||||
|
using Penumbra.Meta.Files;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
using Penumbra.MetaData;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods
|
namespace Penumbra.Meta
|
||||||
{
|
{
|
||||||
public class MetaManager : IDisposable
|
public class MetaManager : IDisposable
|
||||||
{
|
{
|
||||||
|
|
@ -45,9 +45,15 @@ namespace Penumbra.Mods
|
||||||
private readonly GameResourceManagement _resourceManagement;
|
private readonly GameResourceManagement _resourceManagement;
|
||||||
private readonly Dictionary< GamePath, FileInfo > _resolvedFiles;
|
private readonly Dictionary< GamePath, FileInfo > _resolvedFiles;
|
||||||
|
|
||||||
private readonly HashSet< MetaManipulation > _currentManipulations = new();
|
private readonly Dictionary< MetaManipulation, Mod.Mod > _currentManipulations = new();
|
||||||
private readonly Dictionary< GamePath, FileInformation > _currentFiles = new();
|
private readonly Dictionary< GamePath, FileInformation > _currentFiles = new();
|
||||||
|
|
||||||
|
public IEnumerable< (MetaManipulation, Mod.Mod) > Manipulations
|
||||||
|
=> _currentManipulations.Select( kvp => ( kvp.Key, kvp.Value ) );
|
||||||
|
|
||||||
|
public bool TryGetValue( MetaManipulation manip, out Mod.Mod mod )
|
||||||
|
=> _currentManipulations.TryGetValue( manip, out mod );
|
||||||
|
|
||||||
private static void DisposeFile( FileInfo? file )
|
private static void DisposeFile( FileInfo? file )
|
||||||
{
|
{
|
||||||
if( !( file?.Exists ?? false ) )
|
if( !( file?.Exists ?? false ) )
|
||||||
|
|
@ -65,7 +71,7 @@ namespace Penumbra.Mods
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
private void Reset( bool reload )
|
||||||
{
|
{
|
||||||
foreach( var file in _currentFiles )
|
foreach( var file in _currentFiles )
|
||||||
{
|
{
|
||||||
|
|
@ -76,11 +82,26 @@ namespace Penumbra.Mods
|
||||||
_currentManipulations.Clear();
|
_currentManipulations.Clear();
|
||||||
_currentFiles.Clear();
|
_currentFiles.Clear();
|
||||||
ClearDirectory();
|
ClearDirectory();
|
||||||
_resourceManagement.ReloadPlayerResources();
|
if( reload )
|
||||||
|
{
|
||||||
|
_resourceManagement.ReloadPlayerResources();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
=> Reset( true );
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
=> Reset();
|
||||||
|
|
||||||
|
~MetaManager()
|
||||||
|
{
|
||||||
|
Reset( false );
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearDirectory()
|
private void ClearDirectory()
|
||||||
{
|
{
|
||||||
|
_dir.Refresh();
|
||||||
if( _dir.Exists )
|
if( _dir.Exists )
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -94,18 +115,18 @@ namespace Penumbra.Mods
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MetaManager( Dictionary< GamePath, FileInfo > resolvedFiles, DirectoryInfo modDir )
|
public MetaManager( string name, Dictionary< GamePath, FileInfo > resolvedFiles, DirectoryInfo modDir )
|
||||||
{
|
{
|
||||||
_resolvedFiles = resolvedFiles;
|
_resolvedFiles = resolvedFiles;
|
||||||
_default = Service< MetaDefaults >.Get();
|
_default = Service< MetaDefaults >.Get();
|
||||||
_resourceManagement = Service< GameResourceManagement >.Get();
|
_resourceManagement = Service< GameResourceManagement >.Get();
|
||||||
_dir = new DirectoryInfo( Path.Combine( modDir.FullName, TmpDirectory ) );
|
_dir = new DirectoryInfo( Path.Combine( modDir.FullName, TmpDirectory, name.ReplaceInvalidPathSymbols().RemoveNonAsciiSymbols() ) );
|
||||||
ClearDirectory();
|
ClearDirectory();
|
||||||
Directory.CreateDirectory( _dir.FullName );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteNewFiles()
|
public void WriteNewFiles()
|
||||||
{
|
{
|
||||||
|
Directory.CreateDirectory( _dir.FullName );
|
||||||
foreach( var kvp in _currentFiles.Where( kvp => kvp.Value.Changed ) )
|
foreach( var kvp in _currentFiles.Where( kvp => kvp.Value.Changed ) )
|
||||||
{
|
{
|
||||||
kvp.Value.Write( _dir );
|
kvp.Value.Write( _dir );
|
||||||
|
|
@ -115,13 +136,14 @@ namespace Penumbra.Mods
|
||||||
_resourceManagement.ReloadPlayerResources();
|
_resourceManagement.ReloadPlayerResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ApplyMod( MetaManipulation m )
|
public bool ApplyMod( MetaManipulation m, Mod.Mod mod )
|
||||||
{
|
{
|
||||||
if( !_currentManipulations.Add( m ) )
|
if( _currentManipulations.ContainsKey( m ) )
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_currentManipulations.Add( m, mod );
|
||||||
var gamePath = m.CorrespondingFilename();
|
var gamePath = m.CorrespondingFilename();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -1,134 +1,45 @@
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Penumbra.Game;
|
using Penumbra.Game;
|
||||||
using Penumbra.MetaData;
|
using Penumbra.Game.Enums;
|
||||||
|
using Penumbra.Meta.Files;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
using Swan;
|
||||||
using ImcFile = Lumina.Data.Files.ImcFile;
|
using ImcFile = Lumina.Data.Files.ImcFile;
|
||||||
|
|
||||||
namespace Penumbra.Mods
|
namespace Penumbra.Meta
|
||||||
{
|
{
|
||||||
public enum MetaType : byte
|
public class MetaManipulationConverter : JsonConverter< MetaManipulation >
|
||||||
{
|
{
|
||||||
Unknown = 0,
|
public override void WriteJson( JsonWriter writer, MetaManipulation manip, JsonSerializer serializer )
|
||||||
Imc = 1,
|
|
||||||
Eqdp = 2,
|
|
||||||
Eqp = 3,
|
|
||||||
Est = 4,
|
|
||||||
Gmp = 5
|
|
||||||
};
|
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
|
||||||
public struct EqpIdentifier
|
|
||||||
{
|
|
||||||
[FieldOffset( 0 )]
|
|
||||||
public ulong Value;
|
|
||||||
|
|
||||||
[FieldOffset( 0 )]
|
|
||||||
public MetaType Type;
|
|
||||||
|
|
||||||
[FieldOffset( 1 )]
|
|
||||||
public EquipSlot Slot;
|
|
||||||
|
|
||||||
[FieldOffset( 2 )]
|
|
||||||
public ushort SetId;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
|
||||||
public struct EqdpIdentifier
|
|
||||||
{
|
|
||||||
[FieldOffset( 0 )]
|
|
||||||
public ulong Value;
|
|
||||||
|
|
||||||
[FieldOffset( 0 )]
|
|
||||||
public MetaType Type;
|
|
||||||
|
|
||||||
[FieldOffset( 1 )]
|
|
||||||
public EquipSlot Slot;
|
|
||||||
|
|
||||||
[FieldOffset( 2 )]
|
|
||||||
public GenderRace GenderRace;
|
|
||||||
|
|
||||||
[FieldOffset( 4 )]
|
|
||||||
public ushort SetId;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
|
||||||
public struct GmpIdentifier
|
|
||||||
{
|
|
||||||
[FieldOffset( 0 )]
|
|
||||||
public ulong Value;
|
|
||||||
|
|
||||||
[FieldOffset( 0 )]
|
|
||||||
public MetaType Type;
|
|
||||||
|
|
||||||
[FieldOffset( 1 )]
|
|
||||||
public ushort SetId;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
|
||||||
public struct EstIdentifier
|
|
||||||
{
|
|
||||||
[FieldOffset( 0 )]
|
|
||||||
public ulong Value;
|
|
||||||
|
|
||||||
[FieldOffset( 0 )]
|
|
||||||
public MetaType Type;
|
|
||||||
|
|
||||||
[FieldOffset( 1 )]
|
|
||||||
public ObjectType ObjectType;
|
|
||||||
|
|
||||||
[FieldOffset( 2 )]
|
|
||||||
public EquipSlot EquipSlot;
|
|
||||||
|
|
||||||
[FieldOffset( 3 )]
|
|
||||||
public BodySlot BodySlot;
|
|
||||||
|
|
||||||
[FieldOffset( 4 )]
|
|
||||||
public GenderRace GenderRace;
|
|
||||||
|
|
||||||
[FieldOffset( 6 )]
|
|
||||||
public ushort PrimaryId;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
|
||||||
public struct ImcIdentifier
|
|
||||||
{
|
|
||||||
[FieldOffset( 0 )]
|
|
||||||
public ulong Value;
|
|
||||||
|
|
||||||
[FieldOffset( 0 )]
|
|
||||||
public MetaType Type;
|
|
||||||
|
|
||||||
[FieldOffset( 1 )]
|
|
||||||
public byte _objectAndBody;
|
|
||||||
|
|
||||||
public ObjectType ObjectType
|
|
||||||
{
|
{
|
||||||
get => ( ObjectType )( _objectAndBody & 0b00011111 );
|
var s = Convert.ToBase64String( manip.ToBytes() );
|
||||||
set => _objectAndBody = ( byte )( ( _objectAndBody & 0b11100000 ) | ( byte )value );
|
writer.WriteValue( s );
|
||||||
}
|
}
|
||||||
|
|
||||||
public BodySlot BodySlot
|
public override MetaManipulation ReadJson( JsonReader reader, Type objectType, MetaManipulation existingValue, bool hasExistingValue,
|
||||||
|
JsonSerializer serializer )
|
||||||
|
|
||||||
{
|
{
|
||||||
get => ( BodySlot )( _objectAndBody & 0b11100000 );
|
if( reader.TokenType != JsonToken.String )
|
||||||
set => _objectAndBody = ( byte )( ( _objectAndBody & 0b00011111 ) | ( byte )value );
|
{
|
||||||
|
throw new JsonReaderException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = Convert.FromBase64String( ( string )reader.Value! );
|
||||||
|
using MemoryStream m = new( bytes );
|
||||||
|
using BinaryReader br = new( m );
|
||||||
|
var i = br.ReadUInt64();
|
||||||
|
var v = br.ReadUInt64();
|
||||||
|
return new MetaManipulation( i, v );
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldOffset( 2 )]
|
|
||||||
public ushort PrimaryId;
|
|
||||||
|
|
||||||
[FieldOffset( 4 )]
|
|
||||||
public ushort Variant;
|
|
||||||
|
|
||||||
[FieldOffset( 6 )]
|
|
||||||
public ushort SecondaryId;
|
|
||||||
|
|
||||||
[FieldOffset( 6 )]
|
|
||||||
public EquipSlot EquipSlot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
|
[JsonConverter( typeof( MetaManipulationConverter ) )]
|
||||||
public struct MetaManipulation : IComparable
|
public struct MetaManipulation : IComparable
|
||||||
{
|
{
|
||||||
public static MetaManipulation Eqp( EquipSlot equipSlot, ushort setId, EqpEntry value )
|
public static MetaManipulation Eqp( EquipSlot equipSlot, ushort setId, EqpEntry value )
|
||||||
|
|
@ -138,9 +49,9 @@ namespace Penumbra.Mods
|
||||||
{
|
{
|
||||||
Type = MetaType.Eqp,
|
Type = MetaType.Eqp,
|
||||||
Slot = equipSlot,
|
Slot = equipSlot,
|
||||||
SetId = setId
|
SetId = setId,
|
||||||
},
|
},
|
||||||
EqpValue = value
|
EqpValue = value,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static MetaManipulation Eqdp( EquipSlot equipSlot, GenderRace gr, ushort setId, EqdpEntry value )
|
public static MetaManipulation Eqdp( EquipSlot equipSlot, GenderRace gr, ushort setId, EqdpEntry value )
|
||||||
|
|
@ -151,9 +62,9 @@ namespace Penumbra.Mods
|
||||||
Type = MetaType.Eqdp,
|
Type = MetaType.Eqdp,
|
||||||
Slot = equipSlot,
|
Slot = equipSlot,
|
||||||
GenderRace = gr,
|
GenderRace = gr,
|
||||||
SetId = setId
|
SetId = setId,
|
||||||
},
|
},
|
||||||
EqdpValue = value
|
EqdpValue = value,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static MetaManipulation Gmp( ushort setId, GmpEntry value )
|
public static MetaManipulation Gmp( ushort setId, GmpEntry value )
|
||||||
|
|
@ -162,9 +73,9 @@ namespace Penumbra.Mods
|
||||||
GmpIdentifier = new GmpIdentifier()
|
GmpIdentifier = new GmpIdentifier()
|
||||||
{
|
{
|
||||||
Type = MetaType.Gmp,
|
Type = MetaType.Gmp,
|
||||||
SetId = setId
|
SetId = setId,
|
||||||
},
|
},
|
||||||
GmpValue = value
|
GmpValue = value,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static MetaManipulation Est( ObjectType type, EquipSlot equipSlot, GenderRace gr, BodySlot bodySlot, ushort setId,
|
public static MetaManipulation Est( ObjectType type, EquipSlot equipSlot, GenderRace gr, BodySlot bodySlot, ushort setId,
|
||||||
|
|
@ -178,9 +89,9 @@ namespace Penumbra.Mods
|
||||||
GenderRace = gr,
|
GenderRace = gr,
|
||||||
EquipSlot = equipSlot,
|
EquipSlot = equipSlot,
|
||||||
BodySlot = bodySlot,
|
BodySlot = bodySlot,
|
||||||
PrimaryId = setId
|
PrimaryId = setId,
|
||||||
},
|
},
|
||||||
EstValue = value
|
EstValue = value,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static MetaManipulation Imc( ObjectType type, BodySlot secondaryType, ushort primaryId, ushort secondaryId
|
public static MetaManipulation Imc( ObjectType type, BodySlot secondaryType, ushort primaryId, ushort secondaryId
|
||||||
|
|
@ -194,9 +105,9 @@ namespace Penumbra.Mods
|
||||||
BodySlot = secondaryType,
|
BodySlot = secondaryType,
|
||||||
PrimaryId = primaryId,
|
PrimaryId = primaryId,
|
||||||
SecondaryId = secondaryId,
|
SecondaryId = secondaryId,
|
||||||
Variant = idx
|
Variant = idx,
|
||||||
},
|
},
|
||||||
ImcValue = value
|
ImcValue = value,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static MetaManipulation Imc( EquipSlot slot, ushort primaryId, ushort idx, ImcFile.ImageChangeData value )
|
public static MetaManipulation Imc( EquipSlot slot, ushort primaryId, ushort idx, ImcFile.ImageChangeData value )
|
||||||
|
|
@ -208,11 +119,18 @@ namespace Penumbra.Mods
|
||||||
ObjectType = slot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment,
|
ObjectType = slot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment,
|
||||||
EquipSlot = slot,
|
EquipSlot = slot,
|
||||||
PrimaryId = primaryId,
|
PrimaryId = primaryId,
|
||||||
Variant = idx
|
Variant = idx,
|
||||||
},
|
},
|
||||||
ImcValue = value
|
ImcValue = value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
internal MetaManipulation( ulong identifier, ulong value )
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
Identifier = identifier;
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
[FieldOffset( 0 )]
|
[FieldOffset( 0 )]
|
||||||
public readonly ulong Identifier;
|
public readonly ulong Identifier;
|
||||||
|
|
||||||
|
|
@ -257,7 +175,7 @@ namespace Penumbra.Mods
|
||||||
=> Identifier.GetHashCode();
|
=> Identifier.GetHashCode();
|
||||||
|
|
||||||
public int CompareTo( object? rhs )
|
public int CompareTo( object? rhs )
|
||||||
=> Identifier.CompareTo( rhs );
|
=> Identifier.CompareTo( rhs is MetaManipulation m ? m.Identifier : null );
|
||||||
|
|
||||||
public GamePath CorrespondingFilename()
|
public GamePath CorrespondingFilename()
|
||||||
{
|
{
|
||||||
|
|
@ -268,7 +186,7 @@ namespace Penumbra.Mods
|
||||||
MetaType.Est => MetaFileNames.Est( EstIdentifier.ObjectType, EstIdentifier.EquipSlot, EstIdentifier.BodySlot ),
|
MetaType.Est => MetaFileNames.Est( EstIdentifier.ObjectType, EstIdentifier.EquipSlot, EstIdentifier.BodySlot ),
|
||||||
MetaType.Gmp => MetaFileNames.Gmp(),
|
MetaType.Gmp => MetaFileNames.Gmp(),
|
||||||
MetaType.Imc => MetaFileNames.Imc( ImcIdentifier.ObjectType, ImcIdentifier.PrimaryId, ImcIdentifier.SecondaryId ),
|
MetaType.Imc => MetaFileNames.Imc( ImcIdentifier.ObjectType, ImcIdentifier.PrimaryId, ImcIdentifier.SecondaryId ),
|
||||||
_ => throw new InvalidEnumArgumentException()
|
_ => throw new InvalidEnumArgumentException(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,5 +214,18 @@ namespace Penumbra.Mods
|
||||||
value = ImcValue;
|
value = ImcValue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string IdentifierString()
|
||||||
|
{
|
||||||
|
return Type switch
|
||||||
|
{
|
||||||
|
MetaType.Eqp => $"EQP - {EqpIdentifier}",
|
||||||
|
MetaType.Eqdp => $"EQDP - {EqdpIdentifier}",
|
||||||
|
MetaType.Est => $"EST - {EstIdentifier}",
|
||||||
|
MetaType.Gmp => $"GMP - {GmpIdentifier}",
|
||||||
|
MetaType.Imc => $"IMC - {ImcIdentifier}",
|
||||||
|
_ => throw new InvalidEnumArgumentException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
84
Penumbra/MigrateConfiguration.cs
Normal file
84
Penumbra/MigrateConfiguration.cs
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.Mod;
|
||||||
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra
|
||||||
|
{
|
||||||
|
public static class MigrateConfiguration
|
||||||
|
{
|
||||||
|
public static void Version0To1( Configuration config )
|
||||||
|
{
|
||||||
|
if( config.Version != 0 )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.ModDirectory = config.CurrentCollection;
|
||||||
|
config.CurrentCollection = "Default";
|
||||||
|
config.Version = 1;
|
||||||
|
ResettleCollectionJson( config );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ResettleCollectionJson( Configuration config )
|
||||||
|
{
|
||||||
|
var collectionJson = new FileInfo( Path.Combine( config.ModDirectory, "collection.json" ) );
|
||||||
|
if( !collectionJson.Exists )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultCollection = new ModCollection();
|
||||||
|
var defaultCollectionFile = defaultCollection.FileName();
|
||||||
|
if( defaultCollectionFile.Exists )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var text = File.ReadAllText( collectionJson.FullName );
|
||||||
|
var data = JArray.Parse( text );
|
||||||
|
|
||||||
|
var maxPriority = 0;
|
||||||
|
foreach( var setting in data.Cast< JObject >() )
|
||||||
|
{
|
||||||
|
var modName = ( string )setting[ "FolderName" ]!;
|
||||||
|
var enabled = ( bool )setting[ "Enabled" ]!;
|
||||||
|
var priority = ( int )setting[ "Priority" ]!;
|
||||||
|
var settings = setting[ "Settings" ]!.ToObject< Dictionary< string, int > >()
|
||||||
|
?? setting[ "Conf" ]!.ToObject< Dictionary< string, int > >();
|
||||||
|
|
||||||
|
var save = new ModSettings()
|
||||||
|
{
|
||||||
|
Enabled = enabled,
|
||||||
|
Priority = priority,
|
||||||
|
Settings = settings!,
|
||||||
|
};
|
||||||
|
defaultCollection.Settings.Add( modName, save );
|
||||||
|
maxPriority = Math.Max( maxPriority, priority );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( config.InvertModListOrder )
|
||||||
|
{
|
||||||
|
foreach( var setting in defaultCollection.Settings.Values )
|
||||||
|
{
|
||||||
|
setting.Priority = maxPriority - setting.Priority;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultCollection.Save( Service< DalamudPluginInterface >.Get() );
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not migrate the old collection file to new collection files:\n{e}" );
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Penumbra/Mod/Mod.cs
Normal file
32
Penumbra/Mod/Mod.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Mod
|
||||||
|
{
|
||||||
|
public class Mod
|
||||||
|
{
|
||||||
|
public ModSettings Settings { get; }
|
||||||
|
public ModData Data { get; }
|
||||||
|
public ModCache Cache { get; }
|
||||||
|
|
||||||
|
public Mod( ModSettings settings, ModData data )
|
||||||
|
{
|
||||||
|
Settings = settings;
|
||||||
|
Data = data;
|
||||||
|
Cache = new ModCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FixSettings()
|
||||||
|
=> Settings.FixInvalidSettings( Data.Meta );
|
||||||
|
|
||||||
|
public HashSet< GamePath > GetFiles( FileInfo file )
|
||||||
|
{
|
||||||
|
var relPath = new RelPath( file, Data.BasePath );
|
||||||
|
return ModFunctions.GetFilesForConfig( relPath, Settings, Data.Meta );
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> Data.Meta.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
57
Penumbra/Mod/ModCache.cs
Normal file
57
Penumbra/Mod/ModCache.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Penumbra.Meta;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Mod
|
||||||
|
{
|
||||||
|
public class ModCache
|
||||||
|
{
|
||||||
|
public Dictionary< Mod, (List< GamePath > Files, List< MetaManipulation > Manipulations) > Conflicts { get; private set; } = new();
|
||||||
|
|
||||||
|
public void AddConflict( Mod precedingMod, GamePath gamePath )
|
||||||
|
{
|
||||||
|
if( Conflicts.TryGetValue( precedingMod, out var conflicts ) && !conflicts.Files.Contains( gamePath ) )
|
||||||
|
{
|
||||||
|
conflicts.Files.Add( gamePath );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Conflicts[ precedingMod ] = ( new List< GamePath > { gamePath }, new List< MetaManipulation >() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddConflict( Mod precedingMod, MetaManipulation manipulation )
|
||||||
|
{
|
||||||
|
if( Conflicts.TryGetValue( precedingMod, out var conflicts ) && !conflicts.Manipulations.Contains( manipulation ) )
|
||||||
|
{
|
||||||
|
conflicts.Manipulations.Add( manipulation );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Conflicts[ precedingMod ] = ( new List< GamePath >(), new List< MetaManipulation > { manipulation } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearConflicts()
|
||||||
|
=> Conflicts.Clear();
|
||||||
|
|
||||||
|
public void ClearFileConflicts()
|
||||||
|
{
|
||||||
|
Conflicts = Conflicts.Where( kvp => kvp.Value.Manipulations.Count > 0 ).ToDictionary( kvp => kvp.Key, kvp =>
|
||||||
|
{
|
||||||
|
kvp.Value.Files.Clear();
|
||||||
|
return kvp.Value;
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearMetaConflicts()
|
||||||
|
{
|
||||||
|
Conflicts = Conflicts.Where( kvp => kvp.Value.Files.Count > 0 ).ToDictionary( kvp => kvp.Key, kvp =>
|
||||||
|
{
|
||||||
|
kvp.Value.Manipulations.Clear();
|
||||||
|
return kvp.Value;
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,9 +6,10 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
|
using Penumbra.Structs;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Models
|
namespace Penumbra.Mod
|
||||||
{
|
{
|
||||||
public class ModCleanup
|
public class ModCleanup
|
||||||
{
|
{
|
||||||
|
|
@ -202,7 +203,7 @@ namespace Penumbra.Models
|
||||||
private static bool FileIsInAnyGroup( ModMeta meta, RelPath relPath, bool exceptDuplicates = false )
|
private static bool FileIsInAnyGroup( ModMeta meta, RelPath relPath, bool exceptDuplicates = false )
|
||||||
{
|
{
|
||||||
var groupEnumerator = exceptDuplicates
|
var groupEnumerator = exceptDuplicates
|
||||||
? meta.Groups.Values.Where( G => G.GroupName != Duplicates )
|
? meta.Groups.Values.Where( g => g.GroupName != Duplicates )
|
||||||
: meta.Groups.Values;
|
: meta.Groups.Values;
|
||||||
return groupEnumerator.SelectMany( group => group.Options )
|
return groupEnumerator.SelectMany( group => group.Options )
|
||||||
.Any( option => option.OptionFiles.ContainsKey( relPath ) );
|
.Any( option => option.OptionFiles.ContainsKey( relPath ) );
|
||||||
|
|
@ -252,7 +253,7 @@ namespace Penumbra.Models
|
||||||
};
|
};
|
||||||
|
|
||||||
private static void RemoveFromGroups( ModMeta meta, RelPath relPath, GamePath gamePath, GroupType type = GroupType.Both,
|
private static void RemoveFromGroups( ModMeta meta, RelPath relPath, GamePath gamePath, GroupType type = GroupType.Both,
|
||||||
bool skipDuplicates = true )
|
bool skipDuplicates = true )
|
||||||
{
|
{
|
||||||
if( meta.Groups.Count == 0 )
|
if( meta.Groups.Count == 0 )
|
||||||
{
|
{
|
||||||
|
|
@ -315,7 +316,8 @@ namespace Penumbra.Models
|
||||||
|
|
||||||
private static void RemoveUselessGroups( ModMeta meta )
|
private static void RemoveUselessGroups( ModMeta meta )
|
||||||
{
|
{
|
||||||
meta.Groups = meta.Groups.Where( kvp => kvp.Value.Options.Any( o => o.OptionFiles.Count > 0 ) ).ToDictionary( kvp => kvp.Key, kvp => kvp.Value );
|
meta.Groups = meta.Groups.Where( kvp => kvp.Value.Options.Any( o => o.OptionFiles.Count > 0 ) )
|
||||||
|
.ToDictionary( kvp => kvp.Key, kvp => kvp.Value );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Goes through all Single-Select options and checks if file links are in each of them.
|
// Goes through all Single-Select options and checks if file links are in each of them.
|
||||||
|
|
@ -356,7 +358,7 @@ namespace Penumbra.Models
|
||||||
var usedRelPath = new RelPath( usedGamePath );
|
var usedRelPath = new RelPath( usedGamePath );
|
||||||
required.AddFile( usedRelPath, gamePath );
|
required.AddFile( usedRelPath, gamePath );
|
||||||
required.AddFile( usedRelPath, usedGamePath );
|
required.AddFile( usedRelPath, usedGamePath );
|
||||||
RemoveFromGroups( meta, relPath, gamePath, GroupType.Single, true );
|
RemoveFromGroups( meta, relPath, gamePath, GroupType.Single );
|
||||||
}
|
}
|
||||||
else if( MoveFile( meta, baseDir.FullName, path, relPath ) )
|
else if( MoveFile( meta, baseDir.FullName, path, relPath ) )
|
||||||
{
|
{
|
||||||
|
|
@ -366,7 +368,7 @@ namespace Penumbra.Models
|
||||||
FindOrCreateDuplicates( meta ).AddFile( relPath, gamePath );
|
FindOrCreateDuplicates( meta ).AddFile( relPath, gamePath );
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoveFromGroups( meta, relPath, gamePath, GroupType.Single, true );
|
RemoveFromGroups( meta, relPath, gamePath, GroupType.Single );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
59
Penumbra/Mod/ModData.cs
Normal file
59
Penumbra/Mod/ModData.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
using System.IO;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
|
||||||
|
namespace Penumbra.Mod
|
||||||
|
{
|
||||||
|
public class ModData
|
||||||
|
{
|
||||||
|
public DirectoryInfo BasePath;
|
||||||
|
public ModMeta Meta;
|
||||||
|
public ModResources Resources;
|
||||||
|
|
||||||
|
public FileInfo MetaFile { get; set; }
|
||||||
|
|
||||||
|
private ModData( DirectoryInfo basePath, ModMeta meta, ModResources resources )
|
||||||
|
{
|
||||||
|
BasePath = basePath;
|
||||||
|
Meta = meta;
|
||||||
|
Resources = resources;
|
||||||
|
MetaFile = MetaFileInfo( basePath );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FileInfo MetaFileInfo( DirectoryInfo basePath )
|
||||||
|
=> new( Path.Combine( basePath.FullName, "meta.json" ) );
|
||||||
|
|
||||||
|
public static ModData? LoadMod( DirectoryInfo basePath )
|
||||||
|
{
|
||||||
|
basePath.Refresh();
|
||||||
|
if( !basePath.Exists )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Supplied mod directory {basePath} does not exist." );
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var metaFile = MetaFileInfo( basePath );
|
||||||
|
if( !metaFile.Exists )
|
||||||
|
{
|
||||||
|
PluginLog.Debug( "No mod meta found for {ModLocation}.", basePath.Name );
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var meta = ModMeta.LoadFromFile( metaFile );
|
||||||
|
if( meta == null )
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new ModResources();
|
||||||
|
if( data.RefreshModFiles( basePath ).HasFlag( ResourceChange.Meta ) )
|
||||||
|
{
|
||||||
|
data.SetManipulations( meta, basePath );
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ModData( basePath, meta, data );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveMeta()
|
||||||
|
=> Meta.SaveToFile( MetaFile );
|
||||||
|
}
|
||||||
|
}
|
||||||
82
Penumbra/Mod/ModFunctions.cs
Normal file
82
Penumbra/Mod/ModFunctions.cs
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Penumbra.Structs;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Mod
|
||||||
|
{
|
||||||
|
public static class ModFunctions
|
||||||
|
{
|
||||||
|
public static bool CleanUpCollection( Dictionary< string, ModSettings > settings, IEnumerable< DirectoryInfo > modPaths )
|
||||||
|
{
|
||||||
|
var hashes = modPaths.Select( p => p.Name ).ToHashSet();
|
||||||
|
var missingMods = settings.Keys.Where( k => !hashes.Contains( k ) ).ToArray();
|
||||||
|
var anyChanges = false;
|
||||||
|
foreach( var toRemove in missingMods )
|
||||||
|
{
|
||||||
|
anyChanges |= settings.Remove( toRemove );
|
||||||
|
}
|
||||||
|
|
||||||
|
return anyChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HashSet< GamePath > GetFilesForConfig( RelPath relPath, ModSettings settings, ModMeta meta )
|
||||||
|
{
|
||||||
|
var doNotAdd = false;
|
||||||
|
var files = new HashSet< GamePath >();
|
||||||
|
foreach( var group in meta.Groups.Values.Where( g => g.Options.Count > 0 ) )
|
||||||
|
{
|
||||||
|
doNotAdd |= group.ApplyGroupFiles( relPath, settings.Settings[ group.GroupName ], files );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !doNotAdd )
|
||||||
|
{
|
||||||
|
files.Add( new GamePath( relPath ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModSettings ConvertNamedSettings( NamedModSettings namedSettings, ModMeta meta )
|
||||||
|
{
|
||||||
|
ModSettings ret = new()
|
||||||
|
{
|
||||||
|
Priority = namedSettings.Priority,
|
||||||
|
Settings = namedSettings.Settings.Keys.ToDictionary( k => k, _ => 0 ),
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach( var kvp in namedSettings.Settings )
|
||||||
|
{
|
||||||
|
if( !meta.Groups.TryGetValue( kvp.Key, out var info ) )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( info.SelectionType == SelectType.Single )
|
||||||
|
{
|
||||||
|
if( namedSettings.Settings[ kvp.Key ].Count == 0 )
|
||||||
|
{
|
||||||
|
ret.Settings[ kvp.Key ] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var idx = info.Options.FindIndex( o => o.OptionName == namedSettings.Settings[ kvp.Key ].Last() );
|
||||||
|
ret.Settings[ kvp.Key ] = idx < 0 ? 0 : idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach( var idx in namedSettings.Settings[ kvp.Key ]
|
||||||
|
.Select( option => info.Options.FindIndex( o => o.OptionName == option ) )
|
||||||
|
.Where( idx => idx >= 0 ) )
|
||||||
|
{
|
||||||
|
ret.Settings[ kvp.Key ] |= 1 << idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
103
Penumbra/Mod/ModMeta.cs
Normal file
103
Penumbra/Mod/ModMeta.cs
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Penumbra.Structs;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Mod
|
||||||
|
{
|
||||||
|
// Contains descriptive data about the mod as well as possible settings and fileswaps.
|
||||||
|
public class ModMeta
|
||||||
|
{
|
||||||
|
public uint FileVersion { get; set; }
|
||||||
|
public string Name { get; set; } = "Mod";
|
||||||
|
public string Author { get; set; } = "";
|
||||||
|
public string Description { get; set; } = "";
|
||||||
|
public string Version { get; set; } = "";
|
||||||
|
public string Website { get; set; } = "";
|
||||||
|
public List< string > ChangedItems { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty( ItemConverterType = typeof( GamePathConverter ) )]
|
||||||
|
public Dictionary< GamePath, GamePath > FileSwaps { get; set; } = new();
|
||||||
|
|
||||||
|
public Dictionary< string, OptionGroup > Groups { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
private int FileHash { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool HasGroupsWithConfig { get; private set; }
|
||||||
|
|
||||||
|
public bool RefreshFromFile( FileInfo filePath )
|
||||||
|
{
|
||||||
|
var newMeta = LoadFromFile( filePath );
|
||||||
|
if( newMeta == null )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( newMeta.FileHash == FileHash )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileVersion = newMeta.FileVersion;
|
||||||
|
Name = newMeta.Name;
|
||||||
|
Author = newMeta.Author;
|
||||||
|
Description = newMeta.Description;
|
||||||
|
Version = newMeta.Version;
|
||||||
|
Website = newMeta.Website;
|
||||||
|
ChangedItems = newMeta.ChangedItems;
|
||||||
|
FileSwaps = newMeta.FileSwaps;
|
||||||
|
Groups = newMeta.Groups;
|
||||||
|
FileHash = newMeta.FileHash;
|
||||||
|
HasGroupsWithConfig = newMeta.HasGroupsWithConfig;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static ModMeta? LoadFromFile( FileInfo filePath )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var text = File.ReadAllText( filePath.FullName );
|
||||||
|
|
||||||
|
var meta = JsonConvert.DeserializeObject< ModMeta >( text,
|
||||||
|
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore } );
|
||||||
|
if( meta != null )
|
||||||
|
{
|
||||||
|
meta.FileHash = text.GetHashCode();
|
||||||
|
meta.HasGroupsWithConfig = meta.Groups.Values.Any( g => g.SelectionType == SelectType.Multi || g.Options.Count > 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not load mod meta:\n{e}" );
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveToFile( FileInfo filePath )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var text = JsonConvert.SerializeObject( this, Formatting.Indented );
|
||||||
|
var newHash = text.GetHashCode();
|
||||||
|
if( newHash != FileHash )
|
||||||
|
{
|
||||||
|
File.WriteAllText( filePath.FullName, text );
|
||||||
|
FileHash = newHash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not write meta file for mod {Name} to {filePath.FullName}:\n{e}" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
Penumbra/Mod/ModResources.cs
Normal file
86
Penumbra/Mod/ModResources.cs
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Penumbra.Meta;
|
||||||
|
|
||||||
|
namespace Penumbra.Mod
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
public enum ResourceChange
|
||||||
|
{
|
||||||
|
Files = 1,
|
||||||
|
Meta = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains static mod data that should only change on filesystem changes.
|
||||||
|
public class ModResources
|
||||||
|
{
|
||||||
|
public List< FileInfo > ModFiles { get; private set; } = new();
|
||||||
|
public List< FileInfo > MetaFiles { get; private set; } = new();
|
||||||
|
|
||||||
|
public MetaCollection MetaManipulations { get; private set; } = new();
|
||||||
|
|
||||||
|
|
||||||
|
private void ForceManipulationsUpdate( ModMeta meta, DirectoryInfo basePath )
|
||||||
|
{
|
||||||
|
MetaManipulations.Update( MetaFiles, basePath, meta );
|
||||||
|
MetaManipulations.SaveToFile( MetaCollection.FileName( basePath ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetManipulations( ModMeta meta, DirectoryInfo basePath )
|
||||||
|
{
|
||||||
|
var newManipulations = MetaCollection.LoadFromFile( MetaCollection.FileName( basePath ) );
|
||||||
|
if( newManipulations == null )
|
||||||
|
{
|
||||||
|
ForceManipulationsUpdate( meta, basePath );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MetaManipulations = newManipulations;
|
||||||
|
if( !MetaManipulations.Validate( meta ) )
|
||||||
|
{
|
||||||
|
ForceManipulationsUpdate( meta, basePath );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Update the current set of files used by the mod,
|
||||||
|
// returns true if anything changed.
|
||||||
|
public ResourceChange RefreshModFiles( DirectoryInfo basePath )
|
||||||
|
{
|
||||||
|
List< FileInfo > tmpFiles = new( ModFiles.Count );
|
||||||
|
List< FileInfo > tmpMetas = new( MetaFiles.Count );
|
||||||
|
// we don't care about any _files_ in the root dir, but any folders should be a game folder/file combo
|
||||||
|
foreach( var file in basePath.EnumerateDirectories()
|
||||||
|
.SelectMany( dir => dir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
|
||||||
|
.OrderBy( f => f.FullName ) )
|
||||||
|
{
|
||||||
|
if( file.Extension != ".meta" )
|
||||||
|
{
|
||||||
|
tmpFiles.Add( file );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tmpMetas.Add( file );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceChange changes = 0;
|
||||||
|
if( !tmpFiles.SequenceEqual( ModFiles ) )
|
||||||
|
{
|
||||||
|
ModFiles = tmpFiles;
|
||||||
|
changes |= ResourceChange.Files;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !tmpMetas.SequenceEqual( MetaFiles ) )
|
||||||
|
{
|
||||||
|
MetaFiles = tmpMetas;
|
||||||
|
changes |= ResourceChange.Meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
Penumbra/Mod/ModSettings.cs
Normal file
74
Penumbra/Mod/ModSettings.cs
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Penumbra.Structs;
|
||||||
|
|
||||||
|
namespace Penumbra.Mod
|
||||||
|
{
|
||||||
|
public class ModSettings
|
||||||
|
{
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
public int Priority { get; set; }
|
||||||
|
public Dictionary< string, int > Settings { get; set; } = new();
|
||||||
|
|
||||||
|
// For backwards compatibility
|
||||||
|
private Dictionary< string, int > Conf
|
||||||
|
{
|
||||||
|
set => Settings = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModSettings DeepCopy()
|
||||||
|
{
|
||||||
|
var settings = new ModSettings
|
||||||
|
{
|
||||||
|
Enabled = Enabled,
|
||||||
|
Priority = Priority,
|
||||||
|
Settings = Settings.ToDictionary( kvp => kvp.Key, kvp => kvp.Value ),
|
||||||
|
};
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModSettings DefaultSettings( ModMeta meta )
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Enabled = false,
|
||||||
|
Priority = 0,
|
||||||
|
Settings = meta.Groups.ToDictionary( kvp => kvp.Key, _ => 0 ),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FixSpecificSetting( string name, ModMeta meta )
|
||||||
|
{
|
||||||
|
if( !meta.Groups.TryGetValue( name, out var group ) )
|
||||||
|
{
|
||||||
|
return Settings.Remove( name );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( Settings.TryGetValue( name, out var oldSetting ) )
|
||||||
|
{
|
||||||
|
Settings[ name ] = group.SelectionType switch
|
||||||
|
{
|
||||||
|
SelectType.Single => Math.Min( Math.Max( oldSetting, 0 ), group.Options.Count - 1 ),
|
||||||
|
SelectType.Multi => Math.Min( Math.Max( oldSetting, 0 ), ( 1 << group.Options.Count ) - 1 ),
|
||||||
|
_ => Settings[ group.GroupName ],
|
||||||
|
};
|
||||||
|
return oldSetting != Settings[ group.GroupName ];
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings[ name ] = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FixInvalidSettings( ModMeta meta )
|
||||||
|
{
|
||||||
|
if( meta.Groups.Count == 0 )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Settings.Keys.ToArray().Union( meta.Groups.Keys )
|
||||||
|
.Aggregate( false, ( current, name ) => current | FixSpecificSetting( name, meta ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Penumbra.Structs;
|
||||||
|
|
||||||
namespace Penumbra.Models
|
namespace Penumbra.Mod
|
||||||
{
|
{
|
||||||
public class NamedModSettings
|
public class NamedModSettings
|
||||||
{
|
{
|
||||||
|
|
@ -11,7 +12,7 @@ namespace Penumbra.Models
|
||||||
public void AddFromModSetting( ModSettings s, ModMeta meta )
|
public void AddFromModSetting( ModSettings s, ModMeta meta )
|
||||||
{
|
{
|
||||||
Priority = s.Priority;
|
Priority = s.Priority;
|
||||||
Settings = s.Settings.Keys.ToDictionary( K => K, K => new HashSet< string >() );
|
Settings = s.Settings.Keys.ToDictionary( k => k, _ => new HashSet< string >() );
|
||||||
|
|
||||||
foreach( var kvp in Settings )
|
foreach( var kvp in Settings )
|
||||||
{
|
{
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Penumbra.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.Models
|
|
||||||
{
|
|
||||||
public enum SelectType
|
|
||||||
{
|
|
||||||
Single,
|
|
||||||
Multi
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct Option
|
|
||||||
{
|
|
||||||
public string OptionName;
|
|
||||||
public string OptionDesc;
|
|
||||||
|
|
||||||
[JsonProperty( ItemConverterType = typeof( SingleOrArrayConverter< GamePath > ) )]
|
|
||||||
public Dictionary< RelPath, HashSet< GamePath > > OptionFiles;
|
|
||||||
|
|
||||||
public bool AddFile( RelPath filePath, GamePath gamePath )
|
|
||||||
{
|
|
||||||
if( OptionFiles.TryGetValue( filePath, out var set ) )
|
|
||||||
{
|
|
||||||
return set.Add( gamePath );
|
|
||||||
}
|
|
||||||
|
|
||||||
OptionFiles[ filePath ] = new HashSet< GamePath >() { gamePath };
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct OptionGroup
|
|
||||||
{
|
|
||||||
public string GroupName;
|
|
||||||
|
|
||||||
[JsonConverter( typeof( Newtonsoft.Json.Converters.StringEnumConverter ) )]
|
|
||||||
public SelectType SelectionType;
|
|
||||||
|
|
||||||
public List< Option > Options;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Penumbra.Mods;
|
|
||||||
|
|
||||||
namespace Penumbra.Models
|
|
||||||
{
|
|
||||||
public class ModInfo : ModSettings
|
|
||||||
{
|
|
||||||
public ModInfo( ResourceMod mod )
|
|
||||||
=> Mod = mod;
|
|
||||||
|
|
||||||
public string FolderName { get; set; } = "";
|
|
||||||
public bool Enabled { get; set; }
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public ResourceMod Mod { get; set; }
|
|
||||||
|
|
||||||
public bool FixSpecificSetting( string name )
|
|
||||||
=> FixSpecificSetting( Mod.Meta, name );
|
|
||||||
|
|
||||||
public bool FixInvalidSettings()
|
|
||||||
=> FixInvalidSettings( Mod.Meta );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,134 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Penumbra.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.Models
|
|
||||||
{
|
|
||||||
public class ModMeta
|
|
||||||
{
|
|
||||||
public uint FileVersion { get; set; }
|
|
||||||
public string Name { get; set; } = "Mod";
|
|
||||||
public string Author { get; set; } = "";
|
|
||||||
public string Description { get; set; } = "";
|
|
||||||
|
|
||||||
public string Version { get; set; } = "";
|
|
||||||
|
|
||||||
public string Website { get; set; } = "";
|
|
||||||
|
|
||||||
public List< string > ChangedItems { get; set; } = new();
|
|
||||||
|
|
||||||
|
|
||||||
[JsonProperty( ItemConverterType = typeof( GamePathConverter ))]
|
|
||||||
public Dictionary< GamePath, GamePath > FileSwaps { get; } = new();
|
|
||||||
|
|
||||||
public Dictionary< string, OptionGroup > Groups { get; set; } = new();
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public bool HasGroupWithConfig { get; set; } = false;
|
|
||||||
|
|
||||||
private static readonly JsonSerializerSettings JsonSettings
|
|
||||||
= new() { NullValueHandling = NullValueHandling.Ignore };
|
|
||||||
|
|
||||||
public static ModMeta? LoadFromFile( string filePath )
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var meta = JsonConvert.DeserializeObject< ModMeta >( File.ReadAllText( filePath ), JsonSettings );
|
|
||||||
if( meta != null )
|
|
||||||
{
|
|
||||||
meta.HasGroupWithConfig = meta.Groups.Count > 0
|
|
||||||
&& meta.Groups.Values.Any( G => G.SelectionType == SelectType.Multi || G.Options.Count > 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
catch( Exception )
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
// todo: handle broken mods properly
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool ApplySingleGroupFiles( OptionGroup group, RelPath relPath, int selection, HashSet< GamePath > paths )
|
|
||||||
{
|
|
||||||
if( group.Options[ selection ].OptionFiles.TryGetValue( relPath, out var groupPaths ) )
|
|
||||||
{
|
|
||||||
paths.UnionWith( groupPaths );
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for( var i = 0; i < group.Options.Count; ++i )
|
|
||||||
{
|
|
||||||
if( i == selection )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( group.Options[ i ].OptionFiles.ContainsKey( relPath ) )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool ApplyMultiGroupFiles( OptionGroup group, RelPath relPath, int selection, HashSet< GamePath > paths )
|
|
||||||
{
|
|
||||||
var doNotAdd = false;
|
|
||||||
for( var i = 0; i < group.Options.Count; ++i )
|
|
||||||
{
|
|
||||||
if( ( selection & ( 1 << i ) ) != 0 )
|
|
||||||
{
|
|
||||||
if( group.Options[ i ].OptionFiles.TryGetValue( relPath, out var groupPaths ) )
|
|
||||||
{
|
|
||||||
paths.UnionWith( groupPaths );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if( group.Options[ i ].OptionFiles.ContainsKey( relPath ) )
|
|
||||||
{
|
|
||||||
doNotAdd = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return doNotAdd;
|
|
||||||
}
|
|
||||||
|
|
||||||
public (bool configChanged, HashSet< GamePath > paths) GetFilesForConfig( RelPath relPath, ModSettings settings )
|
|
||||||
{
|
|
||||||
var doNotAdd = false;
|
|
||||||
var configChanged = false;
|
|
||||||
|
|
||||||
HashSet< GamePath > paths = new();
|
|
||||||
foreach( var group in Groups.Values )
|
|
||||||
{
|
|
||||||
configChanged |= settings.FixSpecificSetting( this, group.GroupName );
|
|
||||||
|
|
||||||
if( group.Options.Count == 0 )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch( group.SelectionType )
|
|
||||||
{
|
|
||||||
case SelectType.Single:
|
|
||||||
doNotAdd |= ApplySingleGroupFiles( group, relPath, settings.Settings[ group.GroupName ], paths );
|
|
||||||
break;
|
|
||||||
case SelectType.Multi:
|
|
||||||
doNotAdd |= ApplyMultiGroupFiles( group, relPath, settings.Settings[ group.GroupName ], paths );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( !doNotAdd )
|
|
||||||
{
|
|
||||||
paths.Add( new GamePath( relPath ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return ( configChanged, paths );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Penumbra.Models
|
|
||||||
{
|
|
||||||
public class ModSettings
|
|
||||||
{
|
|
||||||
public int Priority { get; set; }
|
|
||||||
public Dictionary< string, int > Settings { get; set; } = new();
|
|
||||||
|
|
||||||
// For backwards compatibility
|
|
||||||
private Dictionary< string, int > Conf
|
|
||||||
{
|
|
||||||
set => Settings = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ModSettings CreateFrom( NamedModSettings n, ModMeta meta )
|
|
||||||
{
|
|
||||||
ModSettings ret = new()
|
|
||||||
{
|
|
||||||
Priority = n.Priority,
|
|
||||||
Settings = n.Settings.Keys.ToDictionary( K => K, K => 0 )
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach( var kvp in n.Settings )
|
|
||||||
{
|
|
||||||
if( !meta.Groups.TryGetValue( kvp.Key, out var info ) )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( info.SelectionType == SelectType.Single )
|
|
||||||
{
|
|
||||||
if( n.Settings[ kvp.Key ].Count == 0 )
|
|
||||||
{
|
|
||||||
ret.Settings[ kvp.Key ] = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var idx = info.Options.FindIndex( O => O.OptionName == n.Settings[ kvp.Key ].Last() );
|
|
||||||
ret.Settings[ kvp.Key ] = idx < 0 ? 0 : idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach( var idx in n.Settings[ kvp.Key ]
|
|
||||||
.Select( option => info.Options.FindIndex( O => O.OptionName == option ) )
|
|
||||||
.Where( idx => idx >= 0 ) )
|
|
||||||
{
|
|
||||||
ret.Settings[ kvp.Key ] |= 1 << idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool FixSpecificSetting( ModMeta meta, string name )
|
|
||||||
{
|
|
||||||
if( !meta.Groups.TryGetValue( name, out var group ) )
|
|
||||||
{
|
|
||||||
return Settings.Remove( name );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( Settings.TryGetValue( name, out var oldSetting ) )
|
|
||||||
{
|
|
||||||
Settings[ name ] = group.SelectionType switch
|
|
||||||
{
|
|
||||||
SelectType.Single => Math.Min( Math.Max( oldSetting, 0 ), group.Options.Count - 1 ),
|
|
||||||
SelectType.Multi => Math.Min( Math.Max( oldSetting, 0 ), ( 1 << group.Options.Count ) - 1 ),
|
|
||||||
_ => Settings[ group.GroupName ]
|
|
||||||
};
|
|
||||||
return oldSetting != Settings[ group.GroupName ];
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings[ name ] = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool FixInvalidSettings( ModMeta meta )
|
|
||||||
{
|
|
||||||
if( meta.Groups.Count == 0 )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Settings.Keys.ToArray().Union( meta.Groups.Keys )
|
|
||||||
.Aggregate( false, ( current, name ) => current | FixSpecificSetting( meta, name ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,229 +1,236 @@
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Plugin;
|
using Penumbra.Mod;
|
||||||
using Newtonsoft.Json;
|
using Penumbra.Util;
|
||||||
using Penumbra.Models;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods
|
namespace Penumbra.Mods
|
||||||
{
|
{
|
||||||
public class ModCollection
|
public class ModCollection
|
||||||
{
|
{
|
||||||
private readonly DirectoryInfo _basePath;
|
public const string DefaultCollection = "Default";
|
||||||
|
|
||||||
public List< ModInfo >? ModSettings { get; set; }
|
public string Name { get; set; }
|
||||||
public ResourceMod[]? EnabledMods { get; set; }
|
|
||||||
|
|
||||||
|
public Dictionary< string, ModSettings > Settings { get; }
|
||||||
|
|
||||||
public ModCollection( DirectoryInfo basePath )
|
public ModCollection()
|
||||||
=> _basePath = basePath;
|
|
||||||
|
|
||||||
public void Load( bool invertOrder = false )
|
|
||||||
{
|
{
|
||||||
// find the collection json
|
Name = DefaultCollection;
|
||||||
var collectionPath = Path.Combine( _basePath.FullName, "collection.json" );
|
Settings = new Dictionary< string, ModSettings >();
|
||||||
if( File.Exists( collectionPath ) )
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ModSettings = JsonConvert.DeserializeObject< List< ModInfo > >( File.ReadAllText( collectionPath ) );
|
|
||||||
ModSettings = ModSettings.OrderBy( x => x.Priority ).ToList();
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
PluginLog.Error( $"failed to read log collection information, failed path: {collectionPath}, err: {e.Message}" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
if( ModSettings != null )
|
|
||||||
{
|
|
||||||
foreach( var ms in ModSettings )
|
|
||||||
{
|
|
||||||
PluginLog.Debug(
|
|
||||||
"mod: {ModName} Enabled: {Enabled} Priority: {Priority}",
|
|
||||||
ms.FolderName, ms.Enabled, ms.Priority
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ModSettings ??= new List< ModInfo >();
|
|
||||||
var foundMods = new List< string >();
|
|
||||||
|
|
||||||
foreach( var modDir in _basePath.EnumerateDirectories() )
|
|
||||||
{
|
|
||||||
if( modDir.Name.ToLowerInvariant() == MetaManager.TmpDirectory )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var metaFile = modDir.EnumerateFiles().FirstOrDefault( f => f.Name == "meta.json" );
|
|
||||||
|
|
||||||
if( metaFile == null )
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
PluginLog.Error( "mod meta is missing for resource mod: {ResourceModLocation}", modDir );
|
|
||||||
#else
|
|
||||||
PluginLog.Debug( "mod meta is missing for resource mod: {ResourceModLocation}", modDir );
|
|
||||||
#endif
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var meta = ModMeta.LoadFromFile( metaFile.FullName ) ?? new ModMeta();
|
|
||||||
|
|
||||||
var mod = new ResourceMod( meta, modDir );
|
|
||||||
FindOrCreateModSettings( mod );
|
|
||||||
foundMods.Add( modDir.Name );
|
|
||||||
mod.RefreshModFiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove any mods from the collection we didn't find
|
|
||||||
ModSettings = ModSettings.Where(
|
|
||||||
x =>
|
|
||||||
foundMods.Any(
|
|
||||||
fm => string.Equals( x.FolderName, fm, StringComparison.InvariantCultureIgnoreCase )
|
|
||||||
)
|
|
||||||
).ToList();
|
|
||||||
|
|
||||||
// if anything gets removed above, the priority ordering gets fucked, so we need to resort and reindex them otherwise BAD THINGS HAPPEN
|
|
||||||
ModSettings = ModSettings.OrderBy( x => x.Priority ).ToList();
|
|
||||||
var p = 0;
|
|
||||||
foreach( var modSetting in ModSettings )
|
|
||||||
{
|
|
||||||
modSetting.Priority = p++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// reorder the resourcemods list so we can just directly iterate
|
|
||||||
EnabledMods = GetOrderedAndEnabledModList( invertOrder ).ToArray();
|
|
||||||
|
|
||||||
// write the collection metadata back to disk
|
|
||||||
Save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
public ModCollection( string name, Dictionary< string, ModSettings > settings )
|
||||||
{
|
{
|
||||||
var collectionPath = Path.Combine( _basePath.FullName, "collection.json" );
|
Name = name;
|
||||||
|
Settings = settings.ToDictionary( kvp => kvp.Key, kvp => kvp.Value.DeepCopy() );
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CleanUnavailableSettings( Dictionary< string, ModData > data )
|
||||||
|
{
|
||||||
|
if( Settings.Count <= data.Count )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List< string > removeList = new();
|
||||||
|
foreach( var settingKvp in Settings )
|
||||||
|
{
|
||||||
|
if( !data.ContainsKey( settingKvp.Key ) )
|
||||||
|
{
|
||||||
|
removeList.Add( settingKvp.Key );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var s in removeList )
|
||||||
|
{
|
||||||
|
Settings.Remove( s );
|
||||||
|
}
|
||||||
|
|
||||||
|
return removeList.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateCache( DirectoryInfo modDirectory, Dictionary< string, ModData > data, bool cleanUnavailable = false )
|
||||||
|
{
|
||||||
|
Cache = new ModCollectionCache( Name, modDirectory );
|
||||||
|
var changedSettings = false;
|
||||||
|
foreach( var modKvp in data )
|
||||||
|
{
|
||||||
|
if( Settings.TryGetValue( modKvp.Key, out var settings ) )
|
||||||
|
{
|
||||||
|
Cache.AvailableMods.Add( new Mod.Mod( settings, modKvp.Value ) );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
changedSettings = true;
|
||||||
|
var newSettings = ModSettings.DefaultSettings( modKvp.Value.Meta );
|
||||||
|
Settings.Add( modKvp.Key, newSettings );
|
||||||
|
Cache.AvailableMods.Add( new Mod.Mod( newSettings, modKvp.Value ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( cleanUnavailable )
|
||||||
|
{
|
||||||
|
changedSettings |= CleanUnavailableSettings( data );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( changedSettings )
|
||||||
|
{
|
||||||
|
Save( Service< DalamudPluginInterface >.Get() );
|
||||||
|
}
|
||||||
|
|
||||||
|
Cache.SortMods();
|
||||||
|
CalculateEffectiveFileList( modDirectory, true );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSetting( ModData mod )
|
||||||
|
{
|
||||||
|
if( !Settings.TryGetValue( mod.BasePath.Name, out var settings ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( settings.FixInvalidSettings( mod.Meta ) )
|
||||||
|
{
|
||||||
|
Save( Service< DalamudPluginInterface >.Get() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSettings()
|
||||||
|
{
|
||||||
|
if( Cache == null )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var changes = false;
|
||||||
|
foreach( var mod in Cache.AvailableMods )
|
||||||
|
{
|
||||||
|
changes |= mod.FixSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( changes )
|
||||||
|
{
|
||||||
|
Save( Service< DalamudPluginInterface >.Get() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CalculateEffectiveFileList( DirectoryInfo modDir, bool withMetaManipulations )
|
||||||
|
{
|
||||||
|
Cache ??= new ModCollectionCache( Name, modDir );
|
||||||
|
UpdateSettings();
|
||||||
|
Cache.CalculateEffectiveFileList();
|
||||||
|
if( withMetaManipulations )
|
||||||
|
{
|
||||||
|
Cache.UpdateMetaManipulations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public ModCollectionCache? Cache { get; private set; }
|
||||||
|
|
||||||
|
public static ModCollection? LoadFromFile( FileInfo file )
|
||||||
|
{
|
||||||
|
if( !file.Exists )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not read collection because {file.FullName} does not exist." );
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var data = JsonConvert.SerializeObject( ModSettings.OrderBy( x => x.Priority ).ToList() );
|
var collection = JsonConvert.DeserializeObject< ModCollection >( File.ReadAllText( file.FullName ) );
|
||||||
File.WriteAllText( collectionPath, data );
|
return collection;
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
{
|
{
|
||||||
PluginLog.Error( $"failed to write log collection information, failed path: {collectionPath}, err: {e.Message}" );
|
PluginLog.Error( $"Could not read collection information from {file.FullName}:\n{e}" );
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveToFile( FileInfo file )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText( file.FullName, JsonConvert.SerializeObject( this, Formatting.Indented ) );
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not write collection {Name} to {file.FullName}:\n{e}" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int CleanPriority( int priority )
|
public static DirectoryInfo CollectionDir( DalamudPluginInterface pi )
|
||||||
=> priority < 0 ? 0 : priority >= ModSettings!.Count ? ModSettings.Count - 1 : priority;
|
=> new( Path.Combine( pi.GetPluginConfigDirectory(), "collections" ) );
|
||||||
|
|
||||||
public void ReorderMod( ModInfo info, int newPriority )
|
private static FileInfo FileName( DirectoryInfo collectionDir, string name )
|
||||||
|
=> new( Path.Combine( collectionDir.FullName, $"{name.RemoveInvalidPathSymbols()}.json" ) );
|
||||||
|
|
||||||
|
public FileInfo FileName()
|
||||||
|
=> new( Path.Combine( Service< DalamudPluginInterface >.Get().GetPluginConfigDirectory(),
|
||||||
|
$"{Name.RemoveInvalidPathSymbols()}.json" ) );
|
||||||
|
|
||||||
|
public void Save( DalamudPluginInterface pi )
|
||||||
{
|
{
|
||||||
if( ModSettings == null )
|
try
|
||||||
{
|
{
|
||||||
return;
|
var dir = CollectionDir( pi );
|
||||||
|
dir.Create();
|
||||||
|
var file = FileName( dir, Name );
|
||||||
|
SaveToFile( file );
|
||||||
}
|
}
|
||||||
|
catch( Exception e )
|
||||||
var oldPriority = info.Priority;
|
|
||||||
newPriority = CleanPriority( newPriority );
|
|
||||||
if( oldPriority == newPriority )
|
|
||||||
{
|
{
|
||||||
return;
|
PluginLog.Error( $"Could not save collection {Name}:\n{e}" );
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
info.Priority = newPriority;
|
public static ModCollection? Load( string name, DalamudPluginInterface pi )
|
||||||
if( newPriority < oldPriority )
|
{
|
||||||
|
var file = FileName( CollectionDir( pi ), name );
|
||||||
|
return file.Exists ? LoadFromFile( file ) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete( DalamudPluginInterface pi )
|
||||||
|
{
|
||||||
|
var file = FileName( CollectionDir( pi ), Name );
|
||||||
|
if( file.Exists )
|
||||||
{
|
{
|
||||||
for( var i = oldPriority - 1; i >= newPriority; --i )
|
try
|
||||||
{
|
{
|
||||||
++ModSettings![ i ].Priority;
|
file.Delete();
|
||||||
ModSettings.Swap( i, i + 1 );
|
|
||||||
}
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not delete collection file {file} for {Name}:\n{e}" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddMod( ModData data )
|
||||||
|
{
|
||||||
|
if( Cache == null )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( Settings.TryGetValue( data.BasePath.Name, out var settings ) )
|
||||||
|
{
|
||||||
|
Cache.AddMod( settings, data );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for( var i = oldPriority + 1; i <= newPriority; ++i )
|
Cache.AddMod( ModSettings.DefaultSettings( data.Meta ), data );
|
||||||
{
|
|
||||||
--ModSettings![ i ].Priority;
|
|
||||||
ModSettings.Swap( i - 1, i );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EnabledMods = GetOrderedAndEnabledModList().ToArray();
|
|
||||||
Save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReorderMod( ModInfo info, bool up )
|
public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )
|
||||||
=> ReorderMod( info, info.Priority + ( up ? 1 : -1 ) );
|
=> Cache?.ResolveSwappedOrReplacementPath( gameResourcePath );
|
||||||
|
|
||||||
public ModInfo? FindModSettings( string name )
|
|
||||||
{
|
|
||||||
var settings = ModSettings?.FirstOrDefault(
|
|
||||||
x => string.Equals( x.FolderName, name, StringComparison.InvariantCultureIgnoreCase )
|
|
||||||
);
|
|
||||||
#if DEBUG
|
|
||||||
PluginLog.Information( "finding mod {ModName} - found: {ModSettingsExist}", name, settings != null );
|
|
||||||
#endif
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModInfo AddModSettings( ResourceMod mod )
|
|
||||||
{
|
|
||||||
var entry = new ModInfo( mod )
|
|
||||||
{
|
|
||||||
Priority = ModSettings?.Count ?? 0,
|
|
||||||
FolderName = mod.ModBasePath.Name,
|
|
||||||
Enabled = true,
|
|
||||||
};
|
|
||||||
entry.FixInvalidSettings();
|
|
||||||
#if DEBUG
|
|
||||||
PluginLog.Information( "creating mod settings {ModName}", entry.FolderName );
|
|
||||||
#endif
|
|
||||||
ModSettings ??= new List< ModInfo >();
|
|
||||||
ModSettings.Add( entry );
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModInfo FindOrCreateModSettings( ResourceMod mod )
|
|
||||||
{
|
|
||||||
var settings = FindModSettings( mod.ModBasePath.Name );
|
|
||||||
if( settings == null )
|
|
||||||
{
|
|
||||||
return AddModSettings( mod );
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Mod = mod;
|
|
||||||
settings.FixInvalidSettings();
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable< ModInfo > GetOrderedAndEnabledModSettings( bool invertOrder = false )
|
|
||||||
{
|
|
||||||
var query = ModSettings?
|
|
||||||
.Where( x => x.Enabled )
|
|
||||||
?? Enumerable.Empty< ModInfo >();
|
|
||||||
|
|
||||||
if( !invertOrder )
|
|
||||||
{
|
|
||||||
return query.OrderBy( x => x.Priority );
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.OrderByDescending( x => x.Priority );
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable< ResourceMod > GetOrderedAndEnabledModList( bool invertOrder = false )
|
|
||||||
{
|
|
||||||
return GetOrderedAndEnabledModSettings( invertOrder )
|
|
||||||
.Select( x => x.Mod );
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable< (ResourceMod, ModInfo) > GetOrderedAndEnabledModListWithSettings( bool invertOrder = false )
|
|
||||||
{
|
|
||||||
return GetOrderedAndEnabledModSettings( invertOrder )
|
|
||||||
.Select( x => ( x.Mod, x ) );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
167
Penumbra/Mods/ModCollectionCache.cs
Normal file
167
Penumbra/Mods/ModCollectionCache.cs
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Penumbra.Meta;
|
||||||
|
using Penumbra.Mod;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Mods
|
||||||
|
{
|
||||||
|
public class ModCollectionCache
|
||||||
|
{
|
||||||
|
public readonly List< Mod.Mod > AvailableMods = new();
|
||||||
|
|
||||||
|
public readonly Dictionary< GamePath, FileInfo > ResolvedFiles = new();
|
||||||
|
public readonly Dictionary< GamePath, GamePath > SwappedFiles = new();
|
||||||
|
public readonly MetaManager MetaManipulations;
|
||||||
|
|
||||||
|
public ModCollectionCache( string collectionName, DirectoryInfo modDir )
|
||||||
|
=> MetaManipulations = new MetaManager( collectionName, ResolvedFiles, modDir );
|
||||||
|
|
||||||
|
public void SortMods()
|
||||||
|
{
|
||||||
|
AvailableMods.Sort( ( m1, m2 ) => string.Compare( m1.Data.Meta.Name, m2.Data.Meta.Name, StringComparison.InvariantCulture ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddFiles( Dictionary< GamePath, Mod.Mod > registeredFiles, Mod.Mod mod )
|
||||||
|
{
|
||||||
|
foreach( var file in mod.Data.Resources.ModFiles )
|
||||||
|
{
|
||||||
|
var gamePaths = mod.GetFiles( file );
|
||||||
|
foreach( var gamePath in gamePaths )
|
||||||
|
{
|
||||||
|
if( !registeredFiles.TryGetValue( gamePath, out var oldMod ) )
|
||||||
|
{
|
||||||
|
registeredFiles.Add( gamePath, mod );
|
||||||
|
ResolvedFiles[ gamePath ] = file;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mod.Cache.AddConflict( oldMod, gamePath );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddSwaps( Dictionary< GamePath, Mod.Mod > registeredFiles, Mod.Mod mod )
|
||||||
|
{
|
||||||
|
foreach( var swap in mod.Data.Meta.FileSwaps )
|
||||||
|
{
|
||||||
|
if( !registeredFiles.TryGetValue( swap.Key, out var oldMod ) )
|
||||||
|
{
|
||||||
|
registeredFiles.Add( swap.Key, mod );
|
||||||
|
SwappedFiles.Add( swap.Key, swap.Value );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mod.Cache.AddConflict( oldMod, swap.Key );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddManipulations( Mod.Mod mod )
|
||||||
|
{
|
||||||
|
foreach( var manip in mod.Data.Resources.MetaManipulations.GetManipulationsForConfig( mod.Settings, mod.Data.Meta ) )
|
||||||
|
{
|
||||||
|
if( MetaManipulations.TryGetValue( manip, out var precedingMod ) )
|
||||||
|
{
|
||||||
|
mod.Cache.AddConflict( precedingMod, manip );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MetaManipulations.ApplyMod( manip, mod );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateMetaManipulations()
|
||||||
|
{
|
||||||
|
MetaManipulations.Reset();
|
||||||
|
|
||||||
|
foreach( var mod in AvailableMods.Where( m => m.Settings.Enabled && m.Data.Resources.MetaManipulations.Count > 0 )
|
||||||
|
.OrderByDescending( m => m.Settings.Priority ) )
|
||||||
|
{
|
||||||
|
mod.Cache.ClearMetaConflicts();
|
||||||
|
AddManipulations( mod );
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaManipulations.WriteNewFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CalculateEffectiveFileList()
|
||||||
|
{
|
||||||
|
ResolvedFiles.Clear();
|
||||||
|
SwappedFiles.Clear();
|
||||||
|
|
||||||
|
var registeredFiles = new Dictionary< GamePath, Mod.Mod >();
|
||||||
|
foreach( var mod in AvailableMods.Where( m => m.Settings.Enabled ).OrderByDescending( m => m.Settings.Priority ) )
|
||||||
|
{
|
||||||
|
mod.Cache.ClearFileConflicts();
|
||||||
|
AddFiles( registeredFiles, mod );
|
||||||
|
AddSwaps( registeredFiles, mod );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveMod( DirectoryInfo basePath )
|
||||||
|
{
|
||||||
|
var hadMeta = false;
|
||||||
|
var wasEnabled = false;
|
||||||
|
AvailableMods.RemoveAll( m =>
|
||||||
|
{
|
||||||
|
if( m.Settings.Enabled )
|
||||||
|
{
|
||||||
|
wasEnabled = true;
|
||||||
|
hadMeta |= m.Data.Resources.MetaManipulations.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.Data.BasePath.Name == basePath.Name;
|
||||||
|
} );
|
||||||
|
|
||||||
|
if( wasEnabled )
|
||||||
|
{
|
||||||
|
CalculateEffectiveFileList();
|
||||||
|
if( hadMeta )
|
||||||
|
{
|
||||||
|
UpdateMetaManipulations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddMod( ModSettings settings, ModData data )
|
||||||
|
{
|
||||||
|
AvailableMods.Add( new Mod.Mod( settings, data ) );
|
||||||
|
SortMods();
|
||||||
|
if( settings.Enabled )
|
||||||
|
{
|
||||||
|
CalculateEffectiveFileList();
|
||||||
|
if( data.Resources.MetaManipulations.Count > 0 )
|
||||||
|
{
|
||||||
|
UpdateMetaManipulations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo? GetCandidateForGameFile( GamePath gameResourcePath )
|
||||||
|
{
|
||||||
|
if( !ResolvedFiles.TryGetValue( gameResourcePath, out var candidate ) )
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidate.Refresh();
|
||||||
|
if( candidate.FullName.Length >= 260 || !candidate.Exists )
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GamePath? GetSwappedFilePath( GamePath gameResourcePath )
|
||||||
|
=> SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null;
|
||||||
|
|
||||||
|
public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )
|
||||||
|
=> GetCandidateForGameFile( gameResourcePath )?.FullName.Replace( '\\', '/' ) ?? GetSwappedFilePath( gameResourcePath ) ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,207 +1,267 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Penumbra.Hooks;
|
using Penumbra.Meta;
|
||||||
using Penumbra.Models;
|
using Penumbra.Mod;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Mods
|
namespace Penumbra.Mods
|
||||||
{
|
{
|
||||||
public class ModManager : IDisposable
|
public class ModManager
|
||||||
{
|
{
|
||||||
private readonly Plugin _plugin;
|
private readonly Plugin _plugin;
|
||||||
public readonly Dictionary< GamePath, FileInfo > ResolvedFiles = new();
|
public DirectoryInfo BasePath { get; private set; }
|
||||||
public readonly Dictionary< GamePath, GamePath > SwappedFiles = new();
|
|
||||||
public MetaManager? MetaManipulations;
|
|
||||||
|
|
||||||
public ModCollection? Mods { get; set; }
|
public Dictionary< string, ModData > Mods { get; } = new();
|
||||||
private DirectoryInfo? _basePath;
|
public Dictionary< string, ModCollection > Collections { get; } = new();
|
||||||
|
|
||||||
|
public ModCollection CurrentCollection { get; private set; }
|
||||||
|
|
||||||
public ModManager( Plugin plugin )
|
public ModManager( Plugin plugin )
|
||||||
=> _plugin = plugin;
|
|
||||||
|
|
||||||
public void DiscoverMods()
|
|
||||||
=> DiscoverMods( _basePath );
|
|
||||||
|
|
||||||
public void DiscoverMods( string? basePath )
|
|
||||||
=> DiscoverMods( basePath == null ? null : new DirectoryInfo( basePath ) );
|
|
||||||
|
|
||||||
public void DiscoverMods( DirectoryInfo? basePath )
|
|
||||||
{
|
{
|
||||||
_basePath = basePath;
|
_plugin = plugin;
|
||||||
if( basePath == null || !basePath.Exists )
|
BasePath = new DirectoryInfo( plugin.Configuration!.ModDirectory );
|
||||||
|
ReadCollections();
|
||||||
|
CurrentCollection = Collections.Values.First();
|
||||||
|
if( !SetCurrentCollection( plugin.Configuration!.CurrentCollection ) )
|
||||||
{
|
{
|
||||||
Mods = null;
|
PluginLog.Debug( "Last choice of collection {Name} is not available, reset to Default.",
|
||||||
return;
|
plugin.Configuration!.CurrentCollection );
|
||||||
}
|
|
||||||
|
|
||||||
// FileSystemPasta();
|
if( SetCurrentCollection( ModCollection.DefaultCollection ) )
|
||||||
|
|
||||||
Mods = new ModCollection( basePath );
|
|
||||||
Mods.Load();
|
|
||||||
|
|
||||||
CalculateEffectiveFileList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CalculateEffectiveFileList()
|
|
||||||
{
|
|
||||||
ResolvedFiles.Clear();
|
|
||||||
SwappedFiles.Clear();
|
|
||||||
MetaManipulations?.Dispose();
|
|
||||||
|
|
||||||
if( Mods == null )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MetaManipulations = new MetaManager( ResolvedFiles, _basePath! );
|
|
||||||
|
|
||||||
var changedSettings = false;
|
|
||||||
var registeredFiles = new Dictionary< GamePath, string >();
|
|
||||||
foreach( var (mod, settings) in Mods.GetOrderedAndEnabledModListWithSettings( _plugin!.Configuration!.InvertModListOrder ) )
|
|
||||||
{
|
|
||||||
mod.FileConflicts.Clear();
|
|
||||||
|
|
||||||
changedSettings |= ProcessModFiles( registeredFiles, mod, settings );
|
|
||||||
ProcessSwappedFiles( registeredFiles, mod, settings );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( changedSettings )
|
|
||||||
{
|
|
||||||
Mods.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
MetaManipulations.WriteNewFiles();
|
|
||||||
|
|
||||||
Service< GameResourceManagement >.Get().ReloadPlayerResources();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ProcessSwappedFiles( Dictionary< GamePath, string > registeredFiles, ResourceMod mod, ModInfo settings )
|
|
||||||
{
|
|
||||||
foreach( var swap in mod.Meta.FileSwaps )
|
|
||||||
{
|
|
||||||
// just assume people put not fucked paths in here lol
|
|
||||||
if( !SwappedFiles.ContainsKey( swap.Value ) )
|
|
||||||
{
|
{
|
||||||
SwappedFiles[ swap.Key ] = swap.Value;
|
PluginLog.Error( "Could not load any collection. Default collection unavailable." );
|
||||||
registeredFiles[ swap.Key ] = mod.Meta.Name;
|
CurrentCollection = new ModCollection();
|
||||||
}
|
|
||||||
else if( registeredFiles.TryGetValue( swap.Key, out var modName ) )
|
|
||||||
{
|
|
||||||
mod.AddConflict( modName, swap.Key );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ProcessModFiles( Dictionary< GamePath, string > registeredFiles, ResourceMod mod, ModInfo settings )
|
public bool SetCurrentCollection( string name )
|
||||||
{
|
{
|
||||||
var changedConfig = settings.FixInvalidSettings();
|
if( Collections.TryGetValue( name, out var collection ) )
|
||||||
foreach( var file in mod.ModFiles )
|
|
||||||
{
|
{
|
||||||
RelPath relativeFilePath = new( file, mod.ModBasePath );
|
CurrentCollection = collection;
|
||||||
var (configChanged, gamePaths) = mod.Meta.GetFilesForConfig( relativeFilePath, settings );
|
if( CurrentCollection.Cache == null )
|
||||||
changedConfig |= configChanged;
|
|
||||||
if( file.Extension == ".meta" && gamePaths.Count > 0 )
|
|
||||||
{
|
{
|
||||||
AddManipulations( file, mod );
|
CurrentCollection.CreateCache( BasePath, Mods );
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReadCollections()
|
||||||
|
{
|
||||||
|
var collectionDir = ModCollection.CollectionDir( _plugin.PluginInterface! );
|
||||||
|
if( collectionDir.Exists )
|
||||||
|
{
|
||||||
|
foreach( var file in collectionDir.EnumerateFiles( "*.json" ) )
|
||||||
{
|
{
|
||||||
AddFiles( gamePaths, file, registeredFiles, mod );
|
var collection = ModCollection.LoadFromFile( file );
|
||||||
|
if( collection != null )
|
||||||
|
{
|
||||||
|
if( file.Name != $"{collection.Name.RemoveInvalidPathSymbols()}.json" )
|
||||||
|
{
|
||||||
|
PluginLog.Warning( $"Collection {file.Name} does not correspond to {collection.Name}." );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( Collections.ContainsKey( collection.Name ) )
|
||||||
|
{
|
||||||
|
PluginLog.Warning( $"Duplicate collection found: {collection.Name} already exists." );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Collections.Add( collection.Name, collection );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return changedConfig;
|
if( !Collections.ContainsKey( ModCollection.DefaultCollection ) )
|
||||||
}
|
|
||||||
|
|
||||||
private void AddFiles( IEnumerable< GamePath > gamePaths, FileInfo file, Dictionary< GamePath, string > registeredFiles,
|
|
||||||
ResourceMod mod )
|
|
||||||
{
|
|
||||||
foreach( var gamePath in gamePaths )
|
|
||||||
{
|
{
|
||||||
if( !ResolvedFiles.ContainsKey( gamePath ) )
|
var defaultCollection = new ModCollection();
|
||||||
{
|
SaveCollection( defaultCollection );
|
||||||
ResolvedFiles[ gamePath ] = file;
|
Collections.Add( defaultCollection.Name, defaultCollection );
|
||||||
registeredFiles[ gamePath ] = mod.Meta.Name;
|
|
||||||
}
|
|
||||||
else if( registeredFiles.TryGetValue( gamePath, out var modName ) )
|
|
||||||
{
|
|
||||||
mod.AddConflict( modName, gamePath );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddManipulations( FileInfo file, ResourceMod mod )
|
public void SaveCollection( ModCollection collection )
|
||||||
|
=> collection.Save( _plugin.PluginInterface! );
|
||||||
|
|
||||||
|
|
||||||
|
public bool AddCollection( string name, Dictionary< string, ModSettings > settings )
|
||||||
{
|
{
|
||||||
if( !mod.MetaManipulations.TryGetValue( file, out var meta ) )
|
var nameFixed = name.RemoveInvalidPathSymbols().ToLowerInvariant();
|
||||||
|
if( Collections.Values.Any( c => c.Name.RemoveInvalidPathSymbols().ToLowerInvariant() == nameFixed ) )
|
||||||
{
|
{
|
||||||
PluginLog.Error( $"{file.FullName} is a TexTools Meta File without meta information." );
|
PluginLog.Warning( $"The new collection {name} would lead to the same path as one that already exists." );
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach( var manipulation in meta.Manipulations )
|
var newCollection = new ModCollection( name, settings );
|
||||||
|
Collections.Add( name, newCollection );
|
||||||
|
SaveCollection( newCollection );
|
||||||
|
CurrentCollection = newCollection;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveCollection( string name )
|
||||||
|
{
|
||||||
|
if( name == ModCollection.DefaultCollection )
|
||||||
{
|
{
|
||||||
MetaManipulations!.ApplyMod( manipulation );
|
PluginLog.Error( "Can not remove the default collection." );
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void ChangeModPriority( ModInfo info, int newPriority )
|
if( Collections.TryGetValue( name, out var collection ) )
|
||||||
{
|
|
||||||
Mods!.ReorderMod( info, newPriority );
|
|
||||||
CalculateEffectiveFileList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ChangeModPriority( ModInfo info, bool up = false )
|
|
||||||
{
|
|
||||||
Mods!.ReorderMod( info, up );
|
|
||||||
CalculateEffectiveFileList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeleteMod( ResourceMod? mod )
|
|
||||||
{
|
|
||||||
if( mod?.ModBasePath.Exists ?? false )
|
|
||||||
{
|
{
|
||||||
try
|
if( CurrentCollection == collection )
|
||||||
{
|
{
|
||||||
Directory.Delete( mod.ModBasePath.FullName, true );
|
SetCurrentCollection( ModCollection.DefaultCollection );
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
PluginLog.Error( $"Could not delete the mod {mod.ModBasePath.Name}:\n{e}" );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
collection.Delete( _plugin.PluginInterface! );
|
||||||
|
Collections.Remove( name );
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DiscoverMods( DirectoryInfo basePath )
|
||||||
|
{
|
||||||
|
BasePath = basePath;
|
||||||
DiscoverMods();
|
DiscoverMods();
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileInfo? GetCandidateForGameFile( GamePath gameResourcePath )
|
public void DiscoverMods()
|
||||||
{
|
{
|
||||||
var val = ResolvedFiles.TryGetValue( gameResourcePath, out var candidate );
|
Mods.Clear();
|
||||||
if( !val )
|
if( !BasePath.Exists )
|
||||||
{
|
{
|
||||||
return null;
|
PluginLog.Debug( "The mod directory {Directory} does not exist.", BasePath.FullName );
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory( BasePath.FullName );
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"The mod directory {BasePath.FullName} does not exist and could not be created:\n{e}" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( candidate.FullName.Length >= 260 || !candidate.Exists )
|
foreach( var modFolder in BasePath.EnumerateDirectories() )
|
||||||
{
|
{
|
||||||
return null;
|
var mod = ModData.LoadMod( modFolder );
|
||||||
|
if( mod == null )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mods.Add( modFolder.Name, mod );
|
||||||
}
|
}
|
||||||
|
|
||||||
return candidate;
|
foreach( var collection in Collections.Values.Where( c => c.Cache != null ) )
|
||||||
|
{
|
||||||
|
collection.CreateCache( BasePath, Mods );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public GamePath? GetSwappedFilePath( GamePath gameResourcePath )
|
public void DeleteMod( DirectoryInfo modFolder )
|
||||||
=> SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null;
|
|
||||||
|
|
||||||
public string? ResolveSwappedOrReplacementFilePath( GamePath gameResourcePath )
|
|
||||||
=> GetCandidateForGameFile( gameResourcePath )?.FullName.Replace( '\\', '/' ) ?? GetSwappedFilePath( gameResourcePath ) ?? null;
|
|
||||||
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
{
|
||||||
MetaManipulations?.Dispose();
|
modFolder.Refresh();
|
||||||
// _fileSystemWatcher?.Dispose();
|
if( modFolder.Exists )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.Delete( modFolder.FullName, true );
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not delete the mod {modFolder.Name}:\n{e}" );
|
||||||
|
}
|
||||||
|
|
||||||
|
Mods.Remove( modFolder.Name );
|
||||||
|
foreach( var collection in Collections.Values.Where( c => c.Cache != null ) )
|
||||||
|
{
|
||||||
|
collection.Cache!.RemoveMod( modFolder );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddMod( DirectoryInfo modFolder )
|
||||||
|
{
|
||||||
|
var mod = ModData.LoadMod( modFolder );
|
||||||
|
if( mod == null )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( Mods.ContainsKey( modFolder.Name ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mods.Add( modFolder.Name, mod );
|
||||||
|
foreach( var collection in Collections.Values )
|
||||||
|
{
|
||||||
|
collection.AddMod( mod );
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public bool UpdateMod( ModData mod, bool recomputeMeta = false )
|
||||||
|
{
|
||||||
|
var oldName = mod.Meta.Name;
|
||||||
|
var metaChanges = mod.Meta.RefreshFromFile( mod.MetaFile );
|
||||||
|
var fileChanges = mod.Resources.RefreshModFiles( mod.BasePath );
|
||||||
|
|
||||||
|
if( !( recomputeMeta || metaChanges || fileChanges == 0 ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nameChange = !string.Equals( oldName, mod.Meta.Name, StringComparison.InvariantCulture );
|
||||||
|
|
||||||
|
recomputeMeta |= fileChanges.HasFlag( ResourceChange.Meta );
|
||||||
|
if( recomputeMeta )
|
||||||
|
{
|
||||||
|
mod.Resources.MetaManipulations.Update( mod.Resources.MetaFiles, mod.BasePath, mod.Meta );
|
||||||
|
mod.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( mod.BasePath ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var collection in Collections.Values )
|
||||||
|
{
|
||||||
|
if( metaChanges )
|
||||||
|
{
|
||||||
|
collection.UpdateSetting( mod );
|
||||||
|
if( nameChange )
|
||||||
|
{
|
||||||
|
collection.Cache?.SortMods();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( fileChanges.HasFlag( ResourceChange.Files )
|
||||||
|
&& collection.Settings.TryGetValue( mod.BasePath.Name, out var settings )
|
||||||
|
&& settings.Enabled )
|
||||||
|
{
|
||||||
|
collection.Cache?.CalculateEffectiveFileList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( recomputeMeta )
|
||||||
|
{
|
||||||
|
collection.Cache?.UpdateMetaManipulations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// private void FileSystemWatcherOnChanged( object sender, FileSystemEventArgs e )
|
// private void FileSystemWatcherOnChanged( object sender, FileSystemEventArgs e )
|
||||||
|
|
|
||||||
188
Penumbra/Mods/ModManagerEditExtensions.cs
Normal file
188
Penumbra/Mods/ModManagerEditExtensions.cs
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Penumbra.Mod;
|
||||||
|
using Penumbra.Structs;
|
||||||
|
|
||||||
|
namespace Penumbra.Mods
|
||||||
|
{
|
||||||
|
public static class ModManagerEditExtensions
|
||||||
|
{
|
||||||
|
public static bool RenameMod( this ModManager manager, string newName, ModData mod )
|
||||||
|
{
|
||||||
|
if( newName.Length == 0 || string.Equals( newName, mod.Meta.Name, StringComparison.InvariantCulture ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mod.Meta.Name = newName;
|
||||||
|
mod.SaveMeta();
|
||||||
|
foreach( var collection in manager.Collections.Values.Where( c => c.Cache != null ) )
|
||||||
|
{
|
||||||
|
collection.Cache!.SortMods();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool RenameModFolder( this ModManager manager, ModData mod, DirectoryInfo newDir, bool move = true )
|
||||||
|
{
|
||||||
|
if( move )
|
||||||
|
{
|
||||||
|
newDir.Refresh();
|
||||||
|
if( newDir.Exists )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldDir = new DirectoryInfo( mod.BasePath.FullName );
|
||||||
|
try
|
||||||
|
{
|
||||||
|
oldDir.MoveTo( newDir.FullName );
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Error while renaming directory {oldDir.FullName} to {newDir.FullName}:\n{e}" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.Mods.Remove( mod.BasePath.Name );
|
||||||
|
manager.Mods[ newDir.Name ] = mod;
|
||||||
|
|
||||||
|
var oldBasePath = mod.BasePath;
|
||||||
|
mod.BasePath = newDir;
|
||||||
|
mod.MetaFile = ModData.MetaFileInfo( newDir );
|
||||||
|
manager.UpdateMod( mod );
|
||||||
|
|
||||||
|
foreach( var collection in manager.Collections.Values )
|
||||||
|
{
|
||||||
|
if( collection.Settings.TryGetValue( oldBasePath.Name, out var settings ) )
|
||||||
|
{
|
||||||
|
collection.Settings[ newDir.Name ] = settings;
|
||||||
|
collection.Settings.Remove( oldBasePath.Name );
|
||||||
|
manager.SaveCollection( collection );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( collection.Cache != null )
|
||||||
|
{
|
||||||
|
collection.Cache.RemoveMod( newDir );
|
||||||
|
collection.AddMod( mod );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ChangeModGroup( this ModManager manager, string oldGroupName, string newGroupName, ModData mod,
|
||||||
|
SelectType type = SelectType.Single )
|
||||||
|
{
|
||||||
|
if( newGroupName == oldGroupName || mod.Meta.Groups.ContainsKey( newGroupName ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mod.Meta.Groups.TryGetValue( oldGroupName, out var oldGroup ) )
|
||||||
|
{
|
||||||
|
if( newGroupName.Length > 0 )
|
||||||
|
{
|
||||||
|
mod.Meta.Groups[ newGroupName ] = new OptionGroup()
|
||||||
|
{
|
||||||
|
GroupName = newGroupName,
|
||||||
|
SelectionType = oldGroup.SelectionType,
|
||||||
|
Options = oldGroup.Options,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod.Meta.Groups.Remove( oldGroupName );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if( newGroupName.Length == 0 )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mod.Meta.Groups[ newGroupName ] = new OptionGroup()
|
||||||
|
{
|
||||||
|
GroupName = newGroupName,
|
||||||
|
SelectionType = type,
|
||||||
|
Options = new List< Option >(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod.SaveMeta();
|
||||||
|
|
||||||
|
foreach( var collection in manager.Collections.Values )
|
||||||
|
{
|
||||||
|
if( !collection.Settings.TryGetValue( mod.BasePath.Name, out var settings ) )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( newGroupName.Length > 0 )
|
||||||
|
{
|
||||||
|
settings.Settings[ newGroupName ] = settings.Settings.TryGetValue( oldGroupName, out var value ) ? value : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.Settings.Remove( oldGroupName );
|
||||||
|
manager.SaveCollection( collection );
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool RemoveModOption( this ModManager manager, int optionIdx, OptionGroup group, ModData mod )
|
||||||
|
{
|
||||||
|
if( optionIdx < 0 || optionIdx >= group.Options.Count )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Options.RemoveAt( optionIdx );
|
||||||
|
mod.SaveMeta();
|
||||||
|
|
||||||
|
static int MoveMultiSetting( int oldSetting, int idx )
|
||||||
|
{
|
||||||
|
var bitmaskFront = ( 1 << idx ) - 1;
|
||||||
|
var bitmaskBack = ~( bitmaskFront | ( 1 << idx ) );
|
||||||
|
return ( oldSetting & bitmaskFront ) | ( ( oldSetting & bitmaskBack ) >> 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var collection in manager.Collections.Values )
|
||||||
|
{
|
||||||
|
if( !collection.Settings.TryGetValue( mod.BasePath.Name, out var settings ) )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !settings.Settings.TryGetValue( group.GroupName, out var setting ) )
|
||||||
|
{
|
||||||
|
setting = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newSetting = group.SelectionType switch
|
||||||
|
{
|
||||||
|
SelectType.Single => setting >= optionIdx ? setting - 1 : setting,
|
||||||
|
SelectType.Multi => MoveMultiSetting( setting, optionIdx ),
|
||||||
|
_ => throw new InvalidEnumArgumentException(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if( newSetting != setting )
|
||||||
|
{
|
||||||
|
settings.Settings[ group.GroupName ] = newSetting;
|
||||||
|
manager.SaveCollection( collection );
|
||||||
|
if( collection.Cache != null && settings.Enabled )
|
||||||
|
{
|
||||||
|
collection.CalculateEffectiveFileList( manager.BasePath, mod.Resources.MetaManipulations.Count > 0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Penumbra.Importer;
|
|
||||||
using Penumbra.Models;
|
|
||||||
using Penumbra.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods
|
|
||||||
{
|
|
||||||
public class ResourceMod
|
|
||||||
{
|
|
||||||
public ResourceMod( ModMeta meta, DirectoryInfo dir )
|
|
||||||
{
|
|
||||||
Meta = meta;
|
|
||||||
ModBasePath = dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModMeta Meta { get; set; }
|
|
||||||
|
|
||||||
public DirectoryInfo ModBasePath { get; set; }
|
|
||||||
|
|
||||||
public List< FileInfo > ModFiles { get; } = new();
|
|
||||||
public Dictionary< FileInfo, TexToolsMeta > MetaManipulations { get; } = new();
|
|
||||||
|
|
||||||
public Dictionary< string, List< GamePath > > FileConflicts { get; } = new();
|
|
||||||
|
|
||||||
|
|
||||||
public void RefreshModFiles()
|
|
||||||
{
|
|
||||||
FileConflicts.Clear();
|
|
||||||
ModFiles.Clear();
|
|
||||||
MetaManipulations.Clear();
|
|
||||||
// we don't care about any _files_ in the root dir, but any folders should be a game folder/file combo
|
|
||||||
foreach( var file in ModBasePath.EnumerateDirectories()
|
|
||||||
.SelectMany( dir => dir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) ) )
|
|
||||||
{
|
|
||||||
if( file.Extension == ".meta" )
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
MetaManipulations[ file ] = new TexToolsMeta( File.ReadAllBytes( file.FullName ) );
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
PluginLog.Error( $"Could not parse meta file {file.FullName}:\n{e}" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ModFiles.Add( file );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddConflict( string modName, GamePath path )
|
|
||||||
{
|
|
||||||
if( FileConflicts.TryGetValue( modName, out var arr ) )
|
|
||||||
{
|
|
||||||
if( !arr.Contains( path ) )
|
|
||||||
{
|
|
||||||
arr.Add( path );
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FileConflicts[ modName ] = new List< GamePath > { path };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,9 +6,10 @@ using EmbedIO.WebApi;
|
||||||
using Penumbra.API;
|
using Penumbra.API;
|
||||||
using Penumbra.Game;
|
using Penumbra.Game;
|
||||||
using Penumbra.Hooks;
|
using Penumbra.Hooks;
|
||||||
using Penumbra.MetaData;
|
using Penumbra.Meta.Files;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra
|
namespace Penumbra
|
||||||
{
|
{
|
||||||
|
|
@ -25,34 +26,35 @@ namespace Penumbra
|
||||||
|
|
||||||
private const string CommandName = "/penumbra";
|
private const string CommandName = "/penumbra";
|
||||||
|
|
||||||
public DalamudPluginInterface? PluginInterface { get; set; }
|
public DalamudPluginInterface PluginInterface { get; set; } = null!;
|
||||||
public Configuration? Configuration { get; set; }
|
public Configuration Configuration { get; set; } = null!;
|
||||||
public ResourceLoader? ResourceLoader { get; set; }
|
public ResourceLoader ResourceLoader { get; set; } = null!;
|
||||||
public SettingsInterface? SettingsInterface { get; set; }
|
public SettingsInterface SettingsInterface { get; set; } = null!;
|
||||||
public SoundShit? SoundShit { get; set; }
|
public MusicManager SoundShit { get; set; } = null!;
|
||||||
|
|
||||||
private WebServer? _webServer;
|
private WebServer? _webServer;
|
||||||
|
|
||||||
public void Initialize( DalamudPluginInterface pluginInterface )
|
public void Initialize( DalamudPluginInterface pluginInterface )
|
||||||
{
|
{
|
||||||
PluginInterface = pluginInterface;
|
PluginInterface = pluginInterface;
|
||||||
|
Service< DalamudPluginInterface >.Set( PluginInterface );
|
||||||
|
|
||||||
Configuration = PluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
|
Configuration = Configuration.Load( PluginInterface );
|
||||||
Configuration.Initialize( PluginInterface );
|
|
||||||
|
|
||||||
SoundShit = new SoundShit( this );
|
|
||||||
|
|
||||||
|
SoundShit = new MusicManager( this );
|
||||||
|
SoundShit.DisableStreaming();
|
||||||
|
|
||||||
var gameUtils = Service< GameResourceManagement >.Set( PluginInterface );
|
var gameUtils = Service< GameResourceManagement >.Set( PluginInterface );
|
||||||
var modManager = Service< ModManager >.Set( this );
|
|
||||||
Service< MetaDefaults >.Set( PluginInterface );
|
Service< MetaDefaults >.Set( PluginInterface );
|
||||||
modManager.DiscoverMods( Configuration.CurrentCollection );
|
var modManager = Service< ModManager >.Set( this );
|
||||||
|
|
||||||
|
modManager.DiscoverMods();
|
||||||
|
|
||||||
ResourceLoader = new ResourceLoader( this );
|
ResourceLoader = new ResourceLoader( this );
|
||||||
|
|
||||||
PluginInterface.CommandManager.AddHandler( CommandName, new CommandInfo( OnCommand )
|
PluginInterface.CommandManager.AddHandler( CommandName, new CommandInfo( OnCommand )
|
||||||
{
|
{
|
||||||
HelpMessage = "/penumbra - toggle ui\n/penumbra reload - reload mod file lists & discover any new mods"
|
HelpMessage = "/penumbra - toggle ui\n/penumbra reload - reload mod file lists & discover any new mods",
|
||||||
} );
|
} );
|
||||||
|
|
||||||
ResourceLoader.Init();
|
ResourceLoader.Init();
|
||||||
|
|
@ -77,11 +79,11 @@ namespace Penumbra
|
||||||
ShutdownWebServer();
|
ShutdownWebServer();
|
||||||
|
|
||||||
_webServer = new WebServer( o => o
|
_webServer = new WebServer( o => o
|
||||||
.WithUrlPrefix( prefix )
|
.WithUrlPrefix( prefix )
|
||||||
.WithMode( HttpListenerMode.EmbedIO ) )
|
.WithMode( HttpListenerMode.EmbedIO ) )
|
||||||
.WithCors( prefix )
|
.WithCors( prefix )
|
||||||
.WithWebApi( "/api", m => m
|
.WithWebApi( "/api", m => m
|
||||||
.WithController( () => new ModsController( this ) ) );
|
.WithController( () => new ModsController( this ) ) );
|
||||||
|
|
||||||
_webServer.StateChanged += ( s, e ) => PluginLog.Information( $"WebServer New State - {e.NewState}" );
|
_webServer.StateChanged += ( s, e ) => PluginLog.Information( $"WebServer New State - {e.NewState}" );
|
||||||
|
|
||||||
|
|
@ -96,14 +98,12 @@ namespace Penumbra
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
// ModManager?.Dispose();
|
PluginInterface.UiBuilder.OnBuildUi -= SettingsInterface.Draw;
|
||||||
|
|
||||||
PluginInterface!.UiBuilder.OnBuildUi -= SettingsInterface!.Draw;
|
|
||||||
|
|
||||||
PluginInterface.CommandManager.RemoveHandler( CommandName );
|
PluginInterface.CommandManager.RemoveHandler( CommandName );
|
||||||
PluginInterface.Dispose();
|
PluginInterface.Dispose();
|
||||||
|
|
||||||
ResourceLoader?.Dispose();
|
ResourceLoader.Dispose();
|
||||||
|
|
||||||
ShutdownWebServer();
|
ShutdownWebServer();
|
||||||
}
|
}
|
||||||
|
|
@ -118,8 +118,8 @@ namespace Penumbra
|
||||||
case "reload":
|
case "reload":
|
||||||
{
|
{
|
||||||
Service< ModManager >.Get().DiscoverMods();
|
Service< ModManager >.Get().DiscoverMods();
|
||||||
PluginInterface!.Framework.Gui.Chat.Print(
|
PluginInterface.Framework.Gui.Chat.Print(
|
||||||
$"Reloaded Penumbra mods. You have {Service< ModManager >.Get()?.Mods?.ModSettings?.Count ?? 0} mods, {Service< ModManager >.Get()?.Mods?.EnabledMods?.Length ?? 0} of which are enabled."
|
$"Reloaded Penumbra mods. You have {Service< ModManager >.Get()?.Mods.Count} mods."
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -127,11 +127,11 @@ namespace Penumbra
|
||||||
{
|
{
|
||||||
if( args.Length > 1 )
|
if( args.Length > 1 )
|
||||||
{
|
{
|
||||||
RefreshActors.RedrawSpecific( PluginInterface!.ClientState.Actors, string.Join( " ", args.Skip( 1 ) ) );
|
RefreshActors.RedrawSpecific( PluginInterface.ClientState.Actors, string.Join( " ", args.Skip( 1 ) ) );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RefreshActors.RedrawAll( PluginInterface!.ClientState.Actors );
|
RefreshActors.RedrawAll( PluginInterface.ClientState.Actors );
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -141,7 +141,7 @@ namespace Penumbra
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsInterface!.FlipVisibility();
|
SettingsInterface.FlipVisibility();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,6 +7,6 @@ namespace Penumbra.Structs
|
||||||
|
|
||||||
// some shit here, the game does some jump if its < 0xA for other files for some reason but there's no impl, probs debug?
|
// some shit here, the game does some jump if its < 0xA for other files for some reason but there's no impl, probs debug?
|
||||||
LoadIndexResource = 0xA, // load index/index2
|
LoadIndexResource = 0xA, // load index/index2
|
||||||
LoadSqPackResource = 0xB
|
LoadSqPackResource = 0xB,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
99
Penumbra/Structs/GroupInformation.cs
Normal file
99
Penumbra/Structs/GroupInformation.cs
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Structs
|
||||||
|
{
|
||||||
|
public enum SelectType
|
||||||
|
{
|
||||||
|
Single,
|
||||||
|
Multi,
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Option
|
||||||
|
{
|
||||||
|
public string OptionName;
|
||||||
|
public string OptionDesc;
|
||||||
|
|
||||||
|
[JsonProperty( ItemConverterType = typeof( SingleOrArrayConverter< GamePath > ) )]
|
||||||
|
public Dictionary< RelPath, HashSet< GamePath > > OptionFiles;
|
||||||
|
|
||||||
|
public bool AddFile( RelPath filePath, GamePath gamePath )
|
||||||
|
{
|
||||||
|
if( OptionFiles.TryGetValue( filePath, out var set ) )
|
||||||
|
{
|
||||||
|
return set.Add( gamePath );
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionFiles[ filePath ] = new HashSet< GamePath >() { gamePath };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct OptionGroup
|
||||||
|
{
|
||||||
|
public string GroupName;
|
||||||
|
|
||||||
|
[JsonConverter( typeof( Newtonsoft.Json.Converters.StringEnumConverter ) )]
|
||||||
|
public SelectType SelectionType;
|
||||||
|
|
||||||
|
public List< Option > Options;
|
||||||
|
|
||||||
|
private bool ApplySingleGroupFiles( RelPath relPath, int selection, HashSet< GamePath > paths )
|
||||||
|
{
|
||||||
|
if( Options[ selection ].OptionFiles.TryGetValue( relPath, out var groupPaths ) )
|
||||||
|
{
|
||||||
|
paths.UnionWith( groupPaths );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for( var i = 0; i < Options.Count; ++i )
|
||||||
|
{
|
||||||
|
if( i == selection )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( Options[ i ].OptionFiles.ContainsKey( relPath ) )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ApplyMultiGroupFiles( RelPath relPath, int selection, HashSet< GamePath > paths )
|
||||||
|
{
|
||||||
|
var doNotAdd = false;
|
||||||
|
for( var i = 0; i < Options.Count; ++i )
|
||||||
|
{
|
||||||
|
if( ( selection & ( 1 << i ) ) != 0 )
|
||||||
|
{
|
||||||
|
if( Options[ i ].OptionFiles.TryGetValue( relPath, out var groupPaths ) )
|
||||||
|
{
|
||||||
|
paths.UnionWith( groupPaths );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if( Options[ i ].OptionFiles.ContainsKey( relPath ) )
|
||||||
|
{
|
||||||
|
doNotAdd = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return doNotAdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds all game paths from the given option that correspond to the given RelPath to paths, if any exist.
|
||||||
|
internal bool ApplyGroupFiles( RelPath relPath, int selection, HashSet< GamePath > paths )
|
||||||
|
{
|
||||||
|
return SelectionType switch
|
||||||
|
{
|
||||||
|
SelectType.Single => ApplySingleGroupFiles( relPath, selection, paths ),
|
||||||
|
SelectType.Multi => ApplyMultiGroupFiles( relPath, selection, paths ),
|
||||||
|
_ => throw new InvalidEnumArgumentException( "Invalid option group type." ),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,15 +3,21 @@ using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
namespace Penumbra.UI
|
namespace Penumbra.UI.Custom
|
||||||
{
|
{
|
||||||
public static partial class ImGuiCustom
|
public static partial class ImGuiCustom
|
||||||
{
|
{
|
||||||
public static void BeginFramedGroup( string label ) => BeginFramedGroupInternal( ref label, ZeroVector, false );
|
public static void BeginFramedGroup( string label )
|
||||||
public static void BeginFramedGroup( string label, Vector2 minSize ) => BeginFramedGroupInternal( ref label, minSize, false );
|
=> BeginFramedGroupInternal( ref label, ZeroVector, false );
|
||||||
|
|
||||||
public static bool BeginFramedGroupEdit( ref string label ) => BeginFramedGroupInternal( ref label, ZeroVector, true );
|
public static void BeginFramedGroup( string label, Vector2 minSize )
|
||||||
public static bool BeginFramedGroupEdit( ref string label, Vector2 minSize ) => BeginFramedGroupInternal( ref label, minSize, true );
|
=> BeginFramedGroupInternal( ref label, minSize, false );
|
||||||
|
|
||||||
|
public static bool BeginFramedGroupEdit( ref string label )
|
||||||
|
=> BeginFramedGroupInternal( ref label, ZeroVector, true );
|
||||||
|
|
||||||
|
public static bool BeginFramedGroupEdit( ref string label, Vector2 minSize )
|
||||||
|
=> BeginFramedGroupInternal( ref label, minSize, true );
|
||||||
|
|
||||||
private static bool BeginFramedGroupInternal( ref string label, Vector2 minSize, bool edit )
|
private static bool BeginFramedGroupInternal( ref string label, Vector2 minSize, bool edit )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
namespace Penumbra.UI
|
namespace Penumbra.UI.Custom
|
||||||
{
|
{
|
||||||
public static partial class ImGuiCustom
|
public static partial class ImGuiCustom
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
namespace Penumbra.UI
|
namespace Penumbra.UI.Custom
|
||||||
{
|
{
|
||||||
public static partial class ImGuiCustom
|
public static partial class ImGuiCustom
|
||||||
{
|
{
|
||||||
|
|
@ -16,8 +16,8 @@ namespace Penumbra.UI
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ResizingTextInput( string label, ref string input, uint maxLength ) =>
|
public static bool ResizingTextInput( string label, ref string input, uint maxLength )
|
||||||
ResizingTextInputIntern( label, ref input, maxLength ).Item1;
|
=> ResizingTextInputIntern( label, ref input, maxLength ).Item1;
|
||||||
|
|
||||||
public static bool ResizingTextInput( ref string input, uint maxLength )
|
public static bool ResizingTextInput( ref string input, uint maxLength )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
namespace Penumbra.UI
|
namespace Penumbra.UI.Custom
|
||||||
{
|
{
|
||||||
public static partial class ImGuiCustom
|
public static partial class ImGuiCustom
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,13 @@ namespace Penumbra.UI
|
||||||
private static readonly Vector2 WindowPosOffset = new( Padding + Width, Padding + Height );
|
private static readonly Vector2 WindowPosOffset = new( Padding + Width, Padding + Height );
|
||||||
|
|
||||||
private const ImGuiWindowFlags ButtonFlags =
|
private const ImGuiWindowFlags ButtonFlags =
|
||||||
ImGuiWindowFlags.AlwaysAutoResize
|
ImGuiWindowFlags.AlwaysAutoResize
|
||||||
| ImGuiWindowFlags.NoBackground
|
| ImGuiWindowFlags.NoBackground
|
||||||
| ImGuiWindowFlags.NoDecoration
|
| ImGuiWindowFlags.NoDecoration
|
||||||
| ImGuiWindowFlags.NoMove
|
| ImGuiWindowFlags.NoMove
|
||||||
| ImGuiWindowFlags.NoScrollbar
|
| ImGuiWindowFlags.NoScrollbar
|
||||||
| ImGuiWindowFlags.NoResize
|
| ImGuiWindowFlags.NoResize
|
||||||
| ImGuiWindowFlags.NoSavedSettings;
|
| ImGuiWindowFlags.NoSavedSettings;
|
||||||
|
|
||||||
private readonly SettingsInterface _base;
|
private readonly SettingsInterface _base;
|
||||||
private readonly Dalamud.Game.ClientState.Condition _condition;
|
private readonly Dalamud.Game.ClientState.Condition _condition;
|
||||||
|
|
@ -43,7 +43,7 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
var ss = ImGui.GetMainViewport().Size + ImGui.GetMainViewport().Pos;
|
var ss = ImGui.GetMainViewport().Size + ImGui.GetMainViewport().Pos;
|
||||||
ImGui.SetNextWindowViewport(ImGui.GetMainViewport().ID);
|
ImGui.SetNextWindowViewport( ImGui.GetMainViewport().ID );
|
||||||
|
|
||||||
ImGui.SetNextWindowPos( ss - WindowPosOffset, ImGuiCond.Always );
|
ImGui.SetNextWindowPos( ss - WindowPosOffset, ImGuiCond.Always );
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@ namespace Penumbra.UI
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private readonly SettingsInterface _base;
|
private readonly SettingsInterface _base;
|
||||||
public MenuBar( SettingsInterface ui ) => _base = ui;
|
|
||||||
|
public MenuBar( SettingsInterface ui )
|
||||||
|
=> _base = ui;
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
120
Penumbra/UI/MenuTabs/TabCollections.cs
Normal file
120
Penumbra/UI/MenuTabs/TabCollections.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Penumbra.Hooks;
|
||||||
|
using Penumbra.Mod;
|
||||||
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.UI
|
||||||
|
{
|
||||||
|
public partial class SettingsInterface
|
||||||
|
{
|
||||||
|
private class TabCollections
|
||||||
|
{
|
||||||
|
private readonly Selector _selector;
|
||||||
|
private readonly ModManager _manager;
|
||||||
|
private string[] _collectionNames = null!;
|
||||||
|
private int _currentIndex = 0;
|
||||||
|
private string _newCollectionName = string.Empty;
|
||||||
|
|
||||||
|
private void UpdateNames()
|
||||||
|
=> _collectionNames = _manager.Collections.Values.Select( c => c.Name ).ToArray();
|
||||||
|
|
||||||
|
private void UpdateIndex()
|
||||||
|
{
|
||||||
|
_currentIndex = _collectionNames.IndexOf( c => c == _manager.CurrentCollection.Name );
|
||||||
|
if( _currentIndex < 0 )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Current Collection {_manager.CurrentCollection.Name} is not found in collections." );
|
||||||
|
_currentIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TabCollections( Selector selector )
|
||||||
|
{
|
||||||
|
_selector = selector;
|
||||||
|
_manager = Service< ModManager >.Get();
|
||||||
|
UpdateNames();
|
||||||
|
UpdateIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void CreateNewCollection( Dictionary< string, ModSettings > settings )
|
||||||
|
{
|
||||||
|
_manager.AddCollection( _newCollectionName, settings );
|
||||||
|
_manager.SetCurrentCollection( _newCollectionName );
|
||||||
|
_newCollectionName = string.Empty;
|
||||||
|
UpdateNames();
|
||||||
|
UpdateIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawNewCollectionInput()
|
||||||
|
{
|
||||||
|
ImGui.InputTextWithHint( "##New Collection", "New Collection", ref _newCollectionName, 64 );
|
||||||
|
|
||||||
|
var changedStyle = false;
|
||||||
|
if( _newCollectionName.Length == 0 )
|
||||||
|
{
|
||||||
|
changedStyle = true;
|
||||||
|
ImGui.PushStyleVar( ImGuiStyleVar.Alpha, 0.5f );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if( ImGui.Button( "Create New Empty Collection" ) && _newCollectionName.Length > 0 )
|
||||||
|
{
|
||||||
|
CreateNewCollection( new Dictionary< string, ModSettings >() );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if( ImGui.Button( "Duplicate Current Collection" ) && _newCollectionName.Length > 0 )
|
||||||
|
{
|
||||||
|
CreateNewCollection( _manager.CurrentCollection.Settings );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( changedStyle )
|
||||||
|
{
|
||||||
|
ImGui.PopStyleVar();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( _manager.Collections.Count > 1 )
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
if( ImGui.Button( "Delete Current Collection" ) )
|
||||||
|
{
|
||||||
|
_manager.RemoveCollection( _manager.CurrentCollection.Name );
|
||||||
|
UpdateNames();
|
||||||
|
UpdateIndex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
if( !ImGui.BeginTabItem( "Collections" ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = _currentIndex;
|
||||||
|
if( ImGui.Combo( "Current Collection", ref index, _collectionNames, _collectionNames.Length ) )
|
||||||
|
{
|
||||||
|
if( index != _currentIndex && _manager.SetCurrentCollection( _collectionNames[ index ] ) )
|
||||||
|
{
|
||||||
|
_currentIndex = index;
|
||||||
|
_selector.ReloadSelection();
|
||||||
|
var resourceManager = Service< GameResourceManagement >.Get();
|
||||||
|
resourceManager.ReloadPlayerResources();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Dummy( new Vector2( 0, 5 ) );
|
||||||
|
DrawNewCollectionInput();
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using Penumbra.Meta;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
|
@ -10,24 +11,40 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
private class TabEffective
|
private class TabEffective
|
||||||
{
|
{
|
||||||
private const string LabelTab = "Effective File List";
|
private const string LabelTab = "Effective Changes";
|
||||||
private const float TextSizePadding = 5f;
|
private static readonly string LongArrowLeft = $"{( char )FontAwesomeIcon.LongArrowAltLeft}";
|
||||||
|
private readonly ModManager _modManager;
|
||||||
|
|
||||||
|
public TabEffective()
|
||||||
|
=> _modManager = Service< ModManager >.Get();
|
||||||
|
|
||||||
private ModManager _mods
|
|
||||||
=> Service< ModManager >.Get();
|
|
||||||
|
|
||||||
private static void DrawFileLine( FileInfo file, GamePath path )
|
private static void DrawFileLine( FileInfo file, GamePath path )
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGuiCustom.CopyOnClickSelectable( path );
|
Custom.ImGuiCustom.CopyOnClickSelectable( path );
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.PushFont( UiBuilder.IconFont );
|
ImGui.PushFont( UiBuilder.IconFont );
|
||||||
ImGui.TextUnformatted( $"{( char )FontAwesomeIcon.LongArrowAltLeft}" );
|
ImGui.TextUnformatted( LongArrowLeft );
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGuiCustom.CopyOnClickSelectable( file.FullName );
|
Custom.ImGuiCustom.CopyOnClickSelectable( file.FullName );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawManipulationLine( MetaManipulation manip, Mod.Mod mod )
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Selectable( manip.IdentifierString() );
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.PushFont( UiBuilder.IconFont );
|
||||||
|
ImGui.TextUnformatted( LongArrowLeft );
|
||||||
|
ImGui.PopFont();
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Selectable( mod.Data.Meta.Name );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
|
|
@ -40,14 +57,21 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
const ImGuiTableFlags flags = ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX;
|
const ImGuiTableFlags flags = ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX;
|
||||||
|
|
||||||
if( ImGui.BeginTable( "##effective_files", 3, flags, AutoFillSize ) )
|
if( ImGui.BeginTable( "##effective_changes", 3, flags, AutoFillSize ) )
|
||||||
{
|
{
|
||||||
foreach ( var file in _mods.ResolvedFiles )
|
var currentCollection = _modManager.CurrentCollection.Cache!;
|
||||||
|
foreach( var file in currentCollection.ResolvedFiles )
|
||||||
{
|
{
|
||||||
DrawFileLine( file.Value, file.Key );
|
DrawFileLine( file.Value, file.Key );
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach( var (manip, mod) in currentCollection.MetaManipulations.Manipulations )
|
||||||
|
{
|
||||||
|
DrawManipulationLine( manip, mod );
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.EndTable();
|
ImGui.EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Windows.Forms;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Penumbra.Importer;
|
using Penumbra.Importer;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.UI
|
namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
|
|
@ -25,54 +26,63 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private static readonly Vector2 ImportBarSize = new( -1, 0 );
|
private static readonly Vector2 ImportBarSize = new( -1, 0 );
|
||||||
|
|
||||||
private bool _isImportRunning = false;
|
private bool _isImportRunning;
|
||||||
private bool _hasError = false;
|
private bool _hasError;
|
||||||
private TexToolsImport? _texToolsImport;
|
private TexToolsImport? _texToolsImport;
|
||||||
private readonly SettingsInterface _base;
|
private readonly SettingsInterface _base;
|
||||||
|
|
||||||
public TabImport( SettingsInterface ui ) => _base = ui;
|
public TabImport( SettingsInterface ui )
|
||||||
|
=> _base = ui;
|
||||||
|
|
||||||
public bool IsImporting() => _isImportRunning;
|
public bool IsImporting()
|
||||||
|
=> _isImportRunning;
|
||||||
|
|
||||||
private void RunImportTask()
|
private void RunImportTask()
|
||||||
{
|
{
|
||||||
_isImportRunning = true;
|
_isImportRunning = true;
|
||||||
Task.Run( async () =>
|
Task.Run( async () =>
|
||||||
{
|
{
|
||||||
var picker = new OpenFileDialog
|
try
|
||||||
{
|
{
|
||||||
Multiselect = true,
|
var picker = new OpenFileDialog
|
||||||
Filter = FileTypeFilter,
|
|
||||||
CheckFileExists = true,
|
|
||||||
Title = LabelFileDialog
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await picker.ShowDialogAsync();
|
|
||||||
|
|
||||||
if( result == DialogResult.OK )
|
|
||||||
{
|
|
||||||
_hasError = false;
|
|
||||||
|
|
||||||
foreach( var fileName in picker.FileNames )
|
|
||||||
{
|
{
|
||||||
PluginLog.Log( $"-> {fileName} START" );
|
Multiselect = true,
|
||||||
|
Filter = FileTypeFilter,
|
||||||
|
CheckFileExists = true,
|
||||||
|
Title = LabelFileDialog,
|
||||||
|
};
|
||||||
|
|
||||||
try
|
var result = await picker.ShowDialogAsync();
|
||||||
{
|
|
||||||
_texToolsImport = new TexToolsImport( new DirectoryInfo( _base._plugin!.Configuration!.CurrentCollection ) );
|
|
||||||
_texToolsImport.ImportModPack( new FileInfo( fileName ) );
|
|
||||||
|
|
||||||
PluginLog.Log( $"-> {fileName} OK!" );
|
if( result == DialogResult.OK )
|
||||||
}
|
{
|
||||||
catch( Exception ex )
|
_hasError = false;
|
||||||
|
|
||||||
|
foreach( var fileName in picker.FileNames )
|
||||||
{
|
{
|
||||||
PluginLog.LogError( ex, "Failed to import modpack at {0}", fileName );
|
PluginLog.Log( $"-> {fileName} START" );
|
||||||
_hasError = true;
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_texToolsImport = new TexToolsImport( new DirectoryInfo( _base._plugin!.Configuration!.ModDirectory ) );
|
||||||
|
_texToolsImport.ImportModPack( new FileInfo( fileName ) );
|
||||||
|
|
||||||
|
PluginLog.Log( $"-> {fileName} OK!" );
|
||||||
|
}
|
||||||
|
catch( Exception ex )
|
||||||
|
{
|
||||||
|
PluginLog.LogError( ex, "Failed to import modpack at {0}", fileName );
|
||||||
|
_hasError = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_texToolsImport = null;
|
||||||
|
_base.ReloadMods();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_texToolsImport = null;
|
catch( Exception e )
|
||||||
_base.ReloadMods();
|
{
|
||||||
|
PluginLog.Error( $"Error opening file picker dialogue:\n{e}" );
|
||||||
}
|
}
|
||||||
|
|
||||||
_isImportRunning = false;
|
_isImportRunning = false;
|
||||||
|
|
@ -98,8 +108,7 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
switch( _texToolsImport.State )
|
switch( _texToolsImport.State )
|
||||||
{
|
{
|
||||||
case ImporterState.None:
|
case ImporterState.None: break;
|
||||||
break;
|
|
||||||
case ImporterState.WritingPackToDisk:
|
case ImporterState.WritingPackToDisk:
|
||||||
ImGui.Text( TooltipModpack1 );
|
ImGui.Text( TooltipModpack1 );
|
||||||
break;
|
break;
|
||||||
|
|
@ -111,10 +120,8 @@ namespace Penumbra.UI
|
||||||
ImGui.ProgressBar( _texToolsImport.Progress, ImportBarSize, str );
|
ImGui.ProgressBar( _texToolsImport.Progress, ImportBarSize, str );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ImporterState.Done:
|
case ImporterState.Done: break;
|
||||||
break;
|
default: throw new ArgumentOutOfRangeException();
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.UI
|
namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
|
|
@ -9,21 +10,21 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
private const string LabelTab = "Installed Mods";
|
private const string LabelTab = "Installed Mods";
|
||||||
|
|
||||||
private readonly SettingsInterface _base;
|
private readonly ModManager _modManager;
|
||||||
public readonly Selector Selector;
|
public readonly Selector Selector;
|
||||||
public readonly ModPanel ModPanel;
|
public readonly ModPanel ModPanel;
|
||||||
|
|
||||||
public TabInstalled( SettingsInterface ui )
|
public TabInstalled( SettingsInterface ui )
|
||||||
{
|
{
|
||||||
_base = ui;
|
Selector = new Selector( ui );
|
||||||
Selector = new Selector( _base );
|
ModPanel = new ModPanel( ui, Selector );
|
||||||
ModPanel = new ModPanel( _base, Selector );
|
_modManager = Service< ModManager >.Get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawNoModsAvailable()
|
private static void DrawNoModsAvailable()
|
||||||
{
|
{
|
||||||
ImGui.Text( "You don't have any mods :(" );
|
ImGui.Text( "You don't have any mods :(" );
|
||||||
ImGuiCustom.VerticalDistance( 20f );
|
Custom.ImGuiCustom.VerticalDistance( 20f );
|
||||||
ImGui.Text( "You'll need to install them first by creating a folder close to the root of your drive (preferably an SSD)." );
|
ImGui.Text( "You'll need to install them first by creating a folder close to the root of your drive (preferably an SSD)." );
|
||||||
ImGui.Text( "For example: D:/ffxiv/mods/" );
|
ImGui.Text( "For example: D:/ffxiv/mods/" );
|
||||||
ImGui.Text( "And pasting that path into the settings tab and clicking the 'Rediscover Mods' button." );
|
ImGui.Text( "And pasting that path into the settings tab and clicking the 'Rediscover Mods' button." );
|
||||||
|
|
@ -38,7 +39,7 @@ namespace Penumbra.UI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( Service< ModManager >.Get().Mods != null )
|
if( _modManager.Mods.Count > 0 )
|
||||||
{
|
{
|
||||||
Selector.Draw();
|
Selector.Draw();
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Penumbra.Models;
|
using Penumbra.Game.Enums;
|
||||||
|
using Penumbra.Meta;
|
||||||
|
using Penumbra.Mod;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Structs;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.UI
|
namespace Penumbra.UI
|
||||||
|
|
@ -35,7 +38,7 @@ namespace Penumbra.UI
|
||||||
private const string LabelChangedItemsHeader = "##changedItems";
|
private const string LabelChangedItemsHeader = "##changedItems";
|
||||||
private const string LabelChangedItemIdx = "##citem_";
|
private const string LabelChangedItemIdx = "##citem_";
|
||||||
private const string LabelChangedItemNew = "##citem_new";
|
private const string LabelChangedItemNew = "##citem_new";
|
||||||
private const string LabelConflictsTab = "File Conflicts";
|
private const string LabelConflictsTab = "Mod Conflicts";
|
||||||
private const string LabelConflictsHeader = "##conflicts";
|
private const string LabelConflictsHeader = "##conflicts";
|
||||||
private const string LabelFileSwapTab = "File Swaps";
|
private const string LabelFileSwapTab = "File Swaps";
|
||||||
private const string LabelFileSwapHeader = "##fileSwaps";
|
private const string LabelFileSwapHeader = "##fileSwaps";
|
||||||
|
|
@ -49,7 +52,6 @@ namespace Penumbra.UI
|
||||||
"Green files replace their standard game path counterpart (not in any option) or are in all options of a Single-Select option.\n"
|
"Green files replace their standard game path counterpart (not in any option) or are in all options of a Single-Select option.\n"
|
||||||
+ "Yellow files are restricted to some options.";
|
+ "Yellow files are restricted to some options.";
|
||||||
|
|
||||||
private const float TextSizePadding = 5f;
|
|
||||||
private const float OptionSelectionWidth = 140f;
|
private const float OptionSelectionWidth = 140f;
|
||||||
private const float CheckMarkSize = 50f;
|
private const float CheckMarkSize = 50f;
|
||||||
private const uint ColorGreen = 0xFF00C800;
|
private const uint ColorGreen = 0xFF00C800;
|
||||||
|
|
@ -68,11 +70,12 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private readonly Selector _selector;
|
private readonly Selector _selector;
|
||||||
private readonly SettingsInterface _base;
|
private readonly SettingsInterface _base;
|
||||||
|
private readonly ModManager _modManager;
|
||||||
|
|
||||||
private void SelectGroup( int idx )
|
private void SelectGroup( int idx )
|
||||||
{
|
{
|
||||||
// Not using the properties here because we need it to be not null forgiving in this case.
|
// Not using the properties here because we need it to be not null forgiving in this case.
|
||||||
var numGroups = _selector.Mod?.Mod.Meta.Groups.Count ?? 0;
|
var numGroups = _selector.Mod?.Data.Meta.Groups.Count ?? 0;
|
||||||
_selectedGroupIndex = idx;
|
_selectedGroupIndex = idx;
|
||||||
if( _selectedGroupIndex >= numGroups )
|
if( _selectedGroupIndex >= numGroups )
|
||||||
{
|
{
|
||||||
|
|
@ -126,20 +129,19 @@ namespace Penumbra.UI
|
||||||
_base = ui;
|
_base = ui;
|
||||||
_selector = s;
|
_selector = s;
|
||||||
ResetState();
|
ResetState();
|
||||||
|
_modManager = Service< ModManager >.Get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is only drawn when we have a mod selected, so we can forgive nulls.
|
// This is only drawn when we have a mod selected, so we can forgive nulls.
|
||||||
private ModInfo Mod
|
private Mod.Mod Mod
|
||||||
=> _selector.Mod!;
|
=> _selector.Mod!;
|
||||||
|
|
||||||
private ModMeta Meta
|
private ModMeta Meta
|
||||||
=> Mod.Mod.Meta;
|
=> Mod.Data.Meta;
|
||||||
|
|
||||||
private void Save()
|
private void Save()
|
||||||
{
|
{
|
||||||
var modManager = Service< ModManager >.Get();
|
_modManager.CurrentCollection.Save( _base._plugin.PluginInterface! );
|
||||||
modManager.Mods?.Save();
|
|
||||||
modManager.CalculateEffectiveFileList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawAboutTab()
|
private void DrawAboutTab()
|
||||||
|
|
@ -161,7 +163,8 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
if( _editMode )
|
if( _editMode )
|
||||||
{
|
{
|
||||||
if( ImGui.InputTextMultiline( LabelDescEdit, ref desc, 1 << 16, AutoFillSize, flags ) )
|
if( ImGui.InputTextMultiline( LabelDescEdit, ref desc, 1 << 16,
|
||||||
|
AutoFillSize, flags ) )
|
||||||
{
|
{
|
||||||
Meta.Description = desc;
|
Meta.Description = desc;
|
||||||
_selector.SaveCurrentMod();
|
_selector.SaveCurrentMod();
|
||||||
|
|
@ -194,6 +197,7 @@ namespace Penumbra.UI
|
||||||
if( ImGui.BeginTabItem( LabelChangedItemsTab ) )
|
if( ImGui.BeginTabItem( LabelChangedItemsTab ) )
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth( -1 );
|
ImGui.SetNextItemWidth( -1 );
|
||||||
|
var changedItems = false;
|
||||||
if( ImGui.BeginListBox( LabelChangedItemsHeader, AutoFillSize ) )
|
if( ImGui.BeginListBox( LabelChangedItemsHeader, AutoFillSize ) )
|
||||||
{
|
{
|
||||||
_changedItemsList ??= Meta.ChangedItems
|
_changedItemsList ??= Meta.ChangedItems
|
||||||
|
|
@ -205,6 +209,7 @@ namespace Penumbra.UI
|
||||||
if( ImGui.InputText( _changedItemsList[ i ].label, ref _changedItemsList[ i ].name, 128, flags ) )
|
if( ImGui.InputText( _changedItemsList[ i ].label, ref _changedItemsList[ i ].name, 128, flags ) )
|
||||||
{
|
{
|
||||||
Meta.ChangedItems.RemoveOrChange( _changedItemsList[ i ].name, i );
|
Meta.ChangedItems.RemoveOrChange( _changedItemsList[ i ].name, i );
|
||||||
|
changedItems = true;
|
||||||
_selector.SaveCurrentMod();
|
_selector.SaveCurrentMod();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -217,10 +222,16 @@ namespace Penumbra.UI
|
||||||
&& newItem.Length > 0 )
|
&& newItem.Length > 0 )
|
||||||
{
|
{
|
||||||
Meta.ChangedItems.Add( newItem );
|
Meta.ChangedItems.Add( newItem );
|
||||||
|
changedItems = true;
|
||||||
_selector.SaveCurrentMod();
|
_selector.SaveCurrentMod();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( changedItems )
|
||||||
|
{
|
||||||
|
_changedItemsList = null;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.EndListBox();
|
ImGui.EndListBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,7 +245,7 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private void DrawConflictTab()
|
private void DrawConflictTab()
|
||||||
{
|
{
|
||||||
if( !Mod.Mod.FileConflicts.Any() || !ImGui.BeginTabItem( LabelConflictsTab ) )
|
if( !Mod.Cache.Conflicts.Any() || !ImGui.BeginTabItem( LabelConflictsTab ) )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -242,20 +253,28 @@ namespace Penumbra.UI
|
||||||
ImGui.SetNextItemWidth( -1 );
|
ImGui.SetNextItemWidth( -1 );
|
||||||
if( ImGui.BeginListBox( LabelConflictsHeader, AutoFillSize ) )
|
if( ImGui.BeginListBox( LabelConflictsHeader, AutoFillSize ) )
|
||||||
{
|
{
|
||||||
foreach( var kv in Mod.Mod.FileConflicts )
|
foreach( var kv in Mod.Cache.Conflicts )
|
||||||
{
|
{
|
||||||
var mod = kv.Key;
|
var mod = kv.Key;
|
||||||
if( ImGui.Selectable( mod ) )
|
if( ImGui.Selectable( mod.Data.Meta.Name ) )
|
||||||
{
|
{
|
||||||
_selector.SelectModByName( mod );
|
_selector.SelectModByName( mod.Data.Meta.Name );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text( $"(Priority {mod.Settings.Priority})" );
|
||||||
|
|
||||||
ImGui.Indent( 15 );
|
ImGui.Indent( 15 );
|
||||||
foreach( var file in kv.Value )
|
foreach( var file in kv.Value.Files )
|
||||||
{
|
{
|
||||||
ImGui.Selectable( file );
|
ImGui.Selectable( file );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach( var manip in kv.Value.Manipulations )
|
||||||
|
{
|
||||||
|
ImGui.Text( manip.IdentifierString() );
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.Unindent( 15 );
|
ImGui.Unindent( 15 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,7 +305,7 @@ namespace Penumbra.UI
|
||||||
foreach( var file in Meta.FileSwaps )
|
foreach( var file in Meta.FileSwaps )
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGuiCustom.CopyOnClickSelectable( file.Key );
|
Custom.ImGuiCustom.CopyOnClickSelectable( file.Key );
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.PushFont( UiBuilder.IconFont );
|
ImGui.PushFont( UiBuilder.IconFont );
|
||||||
|
|
@ -294,7 +313,7 @@ namespace Penumbra.UI
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGuiCustom.CopyOnClickSelectable( file.Value );
|
Custom.ImGuiCustom.CopyOnClickSelectable( file.Value );
|
||||||
|
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
}
|
}
|
||||||
|
|
@ -312,16 +331,15 @@ namespace Penumbra.UI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var len = Mod.Mod.ModBasePath.FullName.Length;
|
_fullFilenameList = Mod.Data.Resources.ModFiles
|
||||||
_fullFilenameList = Mod.Mod.ModFiles
|
.Select( f => ( f, false, ColorGreen, new RelPath( f, Mod.Data.BasePath ) ) ).ToArray();
|
||||||
.Select( F => ( F, false, ColorGreen, new RelPath( F, Mod.Mod.ModBasePath ) ) ).ToArray();
|
|
||||||
|
|
||||||
if( Meta.Groups.Count == 0 )
|
if( Meta.Groups.Count == 0 )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for( var i = 0; i < Mod.Mod.ModFiles.Count; ++i )
|
for( var i = 0; i < Mod.Data.Resources.ModFiles.Count; ++i )
|
||||||
{
|
{
|
||||||
foreach( var group in Meta.Groups.Values )
|
foreach( var group in Meta.Groups.Values )
|
||||||
{
|
{
|
||||||
|
|
@ -362,10 +380,10 @@ namespace Penumbra.UI
|
||||||
if( ImGui.BeginListBox( LabelFileListHeader, AutoFillSize ) )
|
if( ImGui.BeginListBox( LabelFileListHeader, AutoFillSize ) )
|
||||||
{
|
{
|
||||||
UpdateFilenameList();
|
UpdateFilenameList();
|
||||||
foreach( var file in _fullFilenameList! )
|
foreach( var (name, _, color, _) in _fullFilenameList! )
|
||||||
{
|
{
|
||||||
ImGui.PushStyleColor( ImGuiCol.Text, file.color );
|
ImGui.PushStyleColor( ImGuiCol.Text, color );
|
||||||
ImGui.Selectable( file.name.FullName );
|
ImGui.Selectable( name.FullName );
|
||||||
ImGui.PopStyleColor();
|
ImGui.PopStyleColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -379,10 +397,11 @@ namespace Penumbra.UI
|
||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int HandleDefaultString( GamePath[] gamePaths, out int removeFolders )
|
private static int HandleDefaultString( GamePath[] gamePaths, out int removeFolders )
|
||||||
{
|
{
|
||||||
removeFolders = 0;
|
removeFolders = 0;
|
||||||
var defaultIndex = gamePaths.IndexOf( p => ( ( string )p ).StartsWith( TextDefaultGamePath ) );
|
var defaultIndex =
|
||||||
|
gamePaths.IndexOf( p => ( ( string )p ).StartsWith( TextDefaultGamePath ) );
|
||||||
if( defaultIndex < 0 )
|
if( defaultIndex < 0 )
|
||||||
{
|
{
|
||||||
return defaultIndex;
|
return defaultIndex;
|
||||||
|
|
@ -412,7 +431,7 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
var option = ( Option )_selectedOption;
|
var option = ( Option )_selectedOption;
|
||||||
|
|
||||||
var gamePaths = _currentGamePaths.Split( ';' ).Select( P => new GamePath( P ) ).ToArray();
|
var gamePaths = _currentGamePaths.Split( ';' ).Select( p => new GamePath( p ) ).ToArray();
|
||||||
if( gamePaths.Length == 0 || ( ( string )gamePaths[ 0 ] ).Length == 0 )
|
if( gamePaths.Length == 0 || ( ( string )gamePaths[ 0 ] ).Length == 0 )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -420,7 +439,7 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
var defaultIndex = HandleDefaultString( gamePaths, out var removeFolders );
|
var defaultIndex = HandleDefaultString( gamePaths, out var removeFolders );
|
||||||
var changed = false;
|
var changed = false;
|
||||||
for( var i = 0; i < Mod.Mod.ModFiles.Count; ++i )
|
for( var i = 0; i < Mod.Data.Resources.ModFiles.Count; ++i )
|
||||||
{
|
{
|
||||||
if( !_fullFilenameList![ i ].selected )
|
if( !_fullFilenameList![ i ].selected )
|
||||||
{
|
{
|
||||||
|
|
@ -435,7 +454,7 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
if( remove && option.OptionFiles.TryGetValue( relName, out var setPaths ) )
|
if( remove && option.OptionFiles.TryGetValue( relName, out var setPaths ) )
|
||||||
{
|
{
|
||||||
if( setPaths.RemoveWhere( P => gamePaths.Contains( P ) ) > 0 )
|
if( setPaths.RemoveWhere( p => gamePaths.Contains( p ) ) > 0 )
|
||||||
{
|
{
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
@ -477,7 +496,8 @@ namespace Penumbra.UI
|
||||||
private void DrawGamePathInput()
|
private void DrawGamePathInput()
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth( -1 );
|
ImGui.SetNextItemWidth( -1 );
|
||||||
ImGui.InputTextWithHint( LabelGamePathsEditBox, "Hover for help...", ref _currentGamePaths, 128 );
|
ImGui.InputTextWithHint( LabelGamePathsEditBox, "Hover for help...", ref _currentGamePaths,
|
||||||
|
128 );
|
||||||
if( ImGui.IsItemHovered() )
|
if( ImGui.IsItemHovered() )
|
||||||
{
|
{
|
||||||
ImGui.SetTooltip( TooltipGamePathsEdit );
|
ImGui.SetTooltip( TooltipGamePathsEdit );
|
||||||
|
|
@ -580,7 +600,7 @@ namespace Penumbra.UI
|
||||||
var oldEnabled = enabled;
|
var oldEnabled = enabled;
|
||||||
if( ImGui.Checkbox( label, ref enabled ) && oldEnabled != enabled )
|
if( ImGui.Checkbox( label, ref enabled ) && oldEnabled != enabled )
|
||||||
{
|
{
|
||||||
Mod.Settings[ group.GroupName ] ^= 1 << idx;
|
Mod.Settings.Settings[ group.GroupName ] ^= 1 << idx;
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -592,14 +612,14 @@ namespace Penumbra.UI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiCustom.BeginFramedGroup( group.GroupName );
|
Custom.ImGuiCustom.BeginFramedGroup( group.GroupName );
|
||||||
for( var i = 0; i < group.Options.Count; ++i )
|
for( var i = 0; i < group.Options.Count; ++i )
|
||||||
{
|
{
|
||||||
DrawMultiSelectorCheckBox( group, i, Mod.Settings[ group.GroupName ],
|
DrawMultiSelectorCheckBox( group, i, Mod.Settings.Settings[ group.GroupName ],
|
||||||
$"{group.Options[ i ].OptionName}##{group.GroupName}" );
|
$"{group.Options[ i ].OptionName}##{group.GroupName}" );
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiCustom.EndFramedGroup();
|
Custom.ImGuiCustom.EndFramedGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawSingleSelector( OptionGroup group )
|
private void DrawSingleSelector( OptionGroup group )
|
||||||
|
|
@ -609,11 +629,12 @@ namespace Penumbra.UI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var code = Mod.Settings[ group.GroupName ];
|
var code = Mod.Settings.Settings[ group.GroupName ];
|
||||||
if( ImGui.Combo( group.GroupName, ref code
|
if( ImGui.Combo( group.GroupName, ref code
|
||||||
, group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) )
|
, group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count )
|
||||||
|
&& code != Mod.Settings.Settings[ group.GroupName ] )
|
||||||
{
|
{
|
||||||
Mod.Settings[ group.GroupName ] = code;
|
Mod.Settings.Settings[ group.GroupName ] = code;
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -633,7 +654,7 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private void DrawConfigurationTab()
|
private void DrawConfigurationTab()
|
||||||
{
|
{
|
||||||
if( !_editMode && !Meta.HasGroupWithConfig )
|
if( !_editMode && !Meta.HasGroupsWithConfig )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -653,6 +674,178 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void DrawManipulationRow( MetaManipulation manip )
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text( manip.Type.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
|
switch( manip.Type )
|
||||||
|
{
|
||||||
|
case MetaType.Eqp:
|
||||||
|
{
|
||||||
|
ImGui.Text( manip.EqpIdentifier.Slot.IsAccessory()
|
||||||
|
? ObjectType.Accessory.ToString()
|
||||||
|
: ObjectType.Equipment.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text( manip.EqpIdentifier.SetId.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text( manip.EqpIdentifier.Slot.ToString() );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MetaType.Gmp:
|
||||||
|
{
|
||||||
|
ImGui.Text( ObjectType.Equipment.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text( manip.GmpIdentifier.SetId.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text( EquipSlot.Head.ToString() );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MetaType.Eqdp:
|
||||||
|
{
|
||||||
|
ImGui.Text( manip.EqpIdentifier.Slot.IsAccessory()
|
||||||
|
? ObjectType.Accessory.ToString()
|
||||||
|
: ObjectType.Equipment.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text( manip.EqdpIdentifier.SetId.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text( manip.EqpIdentifier.Slot.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var (gender, race) = manip.EqdpIdentifier.GenderRace.Split();
|
||||||
|
ImGui.Text( race.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text( gender.ToString() );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MetaType.Est:
|
||||||
|
{
|
||||||
|
ImGui.Text( manip.EstIdentifier.ObjectType.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text( manip.EstIdentifier.PrimaryId.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text( manip.EstIdentifier.ObjectType == ObjectType.Equipment
|
||||||
|
? manip.EstIdentifier.EquipSlot.ToString()
|
||||||
|
: manip.EstIdentifier.BodySlot.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var (gender, race) = manip.EstIdentifier.GenderRace.Split();
|
||||||
|
ImGui.Text( race.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text( gender.ToString() );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MetaType.Imc:
|
||||||
|
{
|
||||||
|
ImGui.Text( manip.ImcIdentifier.ObjectType.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text( manip.ImcIdentifier.PrimaryId.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( manip.ImcIdentifier.ObjectType == ObjectType.Accessory
|
||||||
|
|| manip.ImcIdentifier.ObjectType == ObjectType.Equipment )
|
||||||
|
{
|
||||||
|
ImGui.Text( manip.ImcIdentifier.ObjectType == ObjectType.Equipment
|
||||||
|
|| manip.ImcIdentifier.ObjectType == ObjectType.Accessory
|
||||||
|
? manip.ImcIdentifier.EquipSlot.ToString()
|
||||||
|
: manip.ImcIdentifier.BodySlot.ToString() );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( manip.ImcIdentifier.ObjectType != ObjectType.Equipment
|
||||||
|
&& manip.ImcIdentifier.ObjectType != ObjectType.Accessory )
|
||||||
|
{
|
||||||
|
ImGui.Text( manip.ImcIdentifier.SecondaryId.ToString() );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text( manip.ImcIdentifier.Variant.ToString() );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableSetColumnIndex( 9 );
|
||||||
|
ImGui.Text( manip.Value.ToString() );
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawMetaManipulationsTable( string label, List< MetaManipulation > list )
|
||||||
|
{
|
||||||
|
if( list.Count == 0
|
||||||
|
|| !ImGui.BeginTable( label, 10,
|
||||||
|
ImGuiTableFlags.BordersInner | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( $"Type##{label}" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( $"Object Type##{label}" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( $"Set##{label}" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( $"Slot##{label}" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( $"Race##{label}" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( $"Gender##{label}" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( $"Secondary ID##{label}" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( $"Variant##{label}" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( $"Value##{label}" );
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
foreach( var manip in list )
|
||||||
|
{
|
||||||
|
DrawManipulationRow( manip );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMetaManipulationsTab()
|
||||||
|
{
|
||||||
|
if( Mod.Data.Resources.MetaManipulations.Count == 0 )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !ImGui.BeginTabItem( "Meta Manipulations" ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( ImGui.BeginListBox( "##MetaManipulations", AutoFillSize ) )
|
||||||
|
{
|
||||||
|
var manips = Mod.Data.Resources.MetaManipulations;
|
||||||
|
if( manips.DefaultData.Count > 0 )
|
||||||
|
{
|
||||||
|
if( ImGui.CollapsingHeader( "Default" ) )
|
||||||
|
{
|
||||||
|
DrawMetaManipulationsTable( "##DefaultManips", manips.DefaultData );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var group in manips.GroupData )
|
||||||
|
{
|
||||||
|
foreach( var option in @group.Value )
|
||||||
|
{
|
||||||
|
if( ImGui.CollapsingHeader( $"{@group.Key} - {option.Key}" ) )
|
||||||
|
{
|
||||||
|
DrawMetaManipulationsTable( $"##{@group.Key}{option.Key}manips", option.Value );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndListBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
public void Draw( bool editMode )
|
public void Draw( bool editMode )
|
||||||
{
|
{
|
||||||
_editMode = editMode;
|
_editMode = editMode;
|
||||||
|
|
@ -660,6 +853,7 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
DrawAboutTab();
|
DrawAboutTab();
|
||||||
DrawChangedItemsTab();
|
DrawChangedItemsTab();
|
||||||
|
|
||||||
DrawConfigurationTab();
|
DrawConfigurationTab();
|
||||||
if( _editMode )
|
if( _editMode )
|
||||||
{
|
{
|
||||||
|
|
@ -671,8 +865,8 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawFileSwapTab();
|
DrawFileSwapTab();
|
||||||
|
DrawMetaManipulationsTab();
|
||||||
DrawConflictTab();
|
DrawConflictTab();
|
||||||
|
|
||||||
ImGui.EndTabBar();
|
ImGui.EndTabBar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Penumbra.Models;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Structs;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.UI
|
namespace Penumbra.UI
|
||||||
|
|
@ -44,7 +45,7 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
if( ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex
|
if( ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex
|
||||||
, Meta.Groups.Values.Select( G => G.GroupName ).ToArray()
|
, Meta.Groups.Values.Select( g => g.GroupName ).ToArray()
|
||||||
, Meta.Groups.Count ) )
|
, Meta.Groups.Count ) )
|
||||||
{
|
{
|
||||||
SelectGroup();
|
SelectGroup();
|
||||||
|
|
@ -65,7 +66,7 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
var group = ( OptionGroup )_selectedGroup!;
|
var group = ( OptionGroup )_selectedGroup!;
|
||||||
if( ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, group.Options.Select( O => O.OptionName ).ToArray(),
|
if( ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, group.Options.Select( o => o.OptionName ).ToArray(),
|
||||||
group.Options.Count ) )
|
group.Options.Count ) )
|
||||||
{
|
{
|
||||||
SelectOption();
|
SelectOption();
|
||||||
|
|
@ -87,7 +88,7 @@ namespace Penumbra.UI
|
||||||
ImGui.SetNextItemWidth( -1 );
|
ImGui.SetNextItemWidth( -1 );
|
||||||
if( ImGui.BeginListBox( LabelFileListHeader, AutoFillSize - new Vector2( 0, 1.5f * ImGui.GetTextLineHeight() ) ) )
|
if( ImGui.BeginListBox( LabelFileListHeader, AutoFillSize - new Vector2( 0, 1.5f * ImGui.GetTextLineHeight() ) ) )
|
||||||
{
|
{
|
||||||
for( var i = 0; i < Mod!.Mod.ModFiles.Count; ++i )
|
for( var i = 0; i < Mod!.Data.Resources.ModFiles.Count; ++i )
|
||||||
{
|
{
|
||||||
DrawFileAndGamePaths( i );
|
DrawFileAndGamePaths( i );
|
||||||
}
|
}
|
||||||
|
|
@ -104,31 +105,13 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool DrawMultiSelectorEditBegin( OptionGroup group )
|
private void DrawMultiSelectorEditBegin( OptionGroup group )
|
||||||
{
|
{
|
||||||
var groupName = group.GroupName;
|
var groupName = group.GroupName;
|
||||||
if( ImGuiCustom.BeginFramedGroupEdit( ref groupName )
|
if( Custom.ImGuiCustom.BeginFramedGroupEdit( ref groupName ) )
|
||||||
&& groupName != group.GroupName
|
|
||||||
&& !Meta!.Groups.ContainsKey( groupName ) )
|
|
||||||
{
|
{
|
||||||
var oldConf = Mod!.Settings[ group.GroupName ];
|
_modManager.ChangeModGroup( group.GroupName, groupName, Mod.Data );
|
||||||
Meta.Groups.Remove( group.GroupName );
|
|
||||||
Mod.FixSpecificSetting( group.GroupName );
|
|
||||||
if( groupName.Length > 0 )
|
|
||||||
{
|
|
||||||
Meta.Groups[ groupName ] = new OptionGroup()
|
|
||||||
{
|
|
||||||
GroupName = groupName,
|
|
||||||
SelectionType = SelectType.Multi,
|
|
||||||
Options = group.Options,
|
|
||||||
};
|
|
||||||
Mod.Settings[ groupName ] = oldConf;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawMultiSelectorEditAdd( OptionGroup group, float nameBoxStart )
|
private void DrawMultiSelectorEditAdd( OptionGroup group, float nameBoxStart )
|
||||||
|
|
@ -149,9 +132,9 @@ namespace Penumbra.UI
|
||||||
private void DrawMultiSelectorEdit( OptionGroup group )
|
private void DrawMultiSelectorEdit( OptionGroup group )
|
||||||
{
|
{
|
||||||
var nameBoxStart = CheckMarkSize;
|
var nameBoxStart = CheckMarkSize;
|
||||||
var flag = Mod!.Settings[ group.GroupName ];
|
var flag = Mod!.Settings.Settings[ group.GroupName ];
|
||||||
var modChanged = DrawMultiSelectorEditBegin( group );
|
|
||||||
|
|
||||||
|
DrawMultiSelectorEditBegin( group );
|
||||||
for( var i = 0; i < group.Options.Count; ++i )
|
for( var i = 0; i < group.Options.Count; ++i )
|
||||||
{
|
{
|
||||||
var opt = group.Options[ i ];
|
var opt = group.Options[ i ];
|
||||||
|
|
@ -171,11 +154,7 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
if( newName.Length == 0 )
|
if( newName.Length == 0 )
|
||||||
{
|
{
|
||||||
group.Options.RemoveAt( i );
|
_modManager.RemoveModOption( i, group, Mod.Data );
|
||||||
var bitmaskFront = ( 1 << i ) - 1;
|
|
||||||
var bitmaskBack = ~( bitmaskFront | ( 1 << i ) );
|
|
||||||
Mod.Settings[ group.GroupName ] = ( flag & bitmaskFront ) | ( ( flag & bitmaskBack ) >> 1 );
|
|
||||||
modChanged = true;
|
|
||||||
}
|
}
|
||||||
else if( newName != opt.OptionName )
|
else if( newName != opt.OptionName )
|
||||||
{
|
{
|
||||||
|
|
@ -188,144 +167,85 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
DrawMultiSelectorEditAdd( group, nameBoxStart );
|
DrawMultiSelectorEditAdd( group, nameBoxStart );
|
||||||
|
|
||||||
if( modChanged )
|
Custom.ImGuiCustom.EndFramedGroup();
|
||||||
{
|
|
||||||
_selector.SaveCurrentMod();
|
|
||||||
Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGuiCustom.EndFramedGroup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool DrawSingleSelectorEditGroup( OptionGroup group, ref bool selectionChanged )
|
private void DrawSingleSelectorEditGroup( OptionGroup group )
|
||||||
{
|
{
|
||||||
var groupName = group.GroupName;
|
var groupName = group.GroupName;
|
||||||
if( ImGui.InputText( $"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue )
|
if( ImGui.InputText( $"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
|
||||||
&& !Meta!.Groups.ContainsKey( groupName ) )
|
|
||||||
{
|
{
|
||||||
var oldConf = Mod!.Settings[ group.GroupName ];
|
_modManager.ChangeModGroup( group.GroupName, groupName, Mod.Data );
|
||||||
if( groupName != group.GroupName )
|
|
||||||
{
|
|
||||||
Meta.Groups.Remove( group.GroupName );
|
|
||||||
selectionChanged |= Mod.FixSpecificSetting( group.GroupName );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( groupName.Length > 0 )
|
|
||||||
{
|
|
||||||
Meta.Groups.Add( groupName, new OptionGroup()
|
|
||||||
{
|
|
||||||
GroupName = groupName,
|
|
||||||
Options = group.Options,
|
|
||||||
SelectionType = SelectType.Single,
|
|
||||||
} );
|
|
||||||
Mod.Settings[ groupName ] = oldConf;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private float DrawSingleSelectorEdit( OptionGroup group )
|
private float DrawSingleSelectorEdit( OptionGroup group )
|
||||||
{
|
{
|
||||||
var code = Mod!.Settings[ group.GroupName ];
|
var oldSetting = Mod!.Settings.Settings[ group.GroupName ];
|
||||||
var selectionChanged = false;
|
var code = oldSetting;
|
||||||
var modChanged = false;
|
if( Custom.ImGuiCustom.RenameableCombo( $"##{group.GroupName}", ref code, out var newName,
|
||||||
if( ImGuiCustom.RenameableCombo( $"##{group.GroupName}", ref code, out var newName,
|
|
||||||
group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) )
|
group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) )
|
||||||
{
|
{
|
||||||
if( code == group.Options.Count )
|
if( code == group.Options.Count )
|
||||||
{
|
{
|
||||||
if( newName.Length > 0 )
|
if( newName.Length > 0 )
|
||||||
{
|
{
|
||||||
selectionChanged = true;
|
Mod.Settings.Settings[ group.GroupName ] = code;
|
||||||
modChanged = true;
|
|
||||||
Mod.Settings[ group.GroupName ] = code;
|
|
||||||
group.Options.Add( new Option()
|
group.Options.Add( new Option()
|
||||||
{
|
{
|
||||||
OptionName = newName,
|
OptionName = newName,
|
||||||
OptionDesc = "",
|
OptionDesc = "",
|
||||||
OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >(),
|
OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >(),
|
||||||
} );
|
} );
|
||||||
|
_selector.SaveCurrentMod();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if( newName.Length == 0 )
|
if( newName.Length == 0 )
|
||||||
{
|
{
|
||||||
modChanged = true;
|
_modManager.RemoveModOption( code, group, Mod.Data );
|
||||||
group.Options.RemoveAt( code );
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if( newName != group.Options[ code ].OptionName )
|
if( newName != group.Options[ code ].OptionName )
|
||||||
{
|
{
|
||||||
modChanged = true;
|
|
||||||
group.Options[ code ] = new Option()
|
group.Options[ code ] = new Option()
|
||||||
{
|
{
|
||||||
OptionName = newName, OptionDesc = group.Options[ code ].OptionDesc,
|
OptionName = newName, OptionDesc = group.Options[ code ].OptionDesc,
|
||||||
OptionFiles = group.Options[ code ].OptionFiles,
|
OptionFiles = group.Options[ code ].OptionFiles,
|
||||||
};
|
};
|
||||||
|
_selector.SaveCurrentMod();
|
||||||
}
|
}
|
||||||
|
|
||||||
selectionChanged |= Mod.Settings[ group.GroupName ] != code;
|
|
||||||
Mod.Settings[ group.GroupName ] = code;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
selectionChanged |= Mod.FixSpecificSetting( group.GroupName );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
if( code != oldSetting )
|
||||||
var labelEditPos = ImGui.GetCursorPosX();
|
|
||||||
modChanged |= DrawSingleSelectorEditGroup( group, ref selectionChanged );
|
|
||||||
|
|
||||||
if( modChanged )
|
|
||||||
{
|
|
||||||
_selector.SaveCurrentMod();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( selectionChanged )
|
|
||||||
{
|
{
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
var labelEditPos = ImGui.GetCursorPosX();
|
||||||
|
DrawSingleSelectorEditGroup( group );
|
||||||
|
|
||||||
return labelEditPos;
|
return labelEditPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddNewGroup( string newGroup, SelectType selectType )
|
|
||||||
{
|
|
||||||
if( Meta!.Groups.ContainsKey( newGroup ) || newGroup.Length <= 0 )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Meta.Groups[ newGroup ] = new OptionGroup()
|
|
||||||
{
|
|
||||||
GroupName = newGroup,
|
|
||||||
SelectionType = selectType,
|
|
||||||
Options = new List< Option >(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Mod.Settings[ newGroup ] = 0;
|
|
||||||
_selector.SaveCurrentMod();
|
|
||||||
Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawAddSingleGroupField( float labelEditPos )
|
private void DrawAddSingleGroupField( float labelEditPos )
|
||||||
{
|
{
|
||||||
const string hint = "Add new Single Group...";
|
var newGroup = "";
|
||||||
var newGroup = "";
|
|
||||||
ImGui.SetCursorPosX( labelEditPos );
|
ImGui.SetCursorPosX( labelEditPos );
|
||||||
if( labelEditPos == CheckMarkSize )
|
if( labelEditPos == CheckMarkSize )
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth( MultiEditBoxWidth );
|
ImGui.SetNextItemWidth( MultiEditBoxWidth );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( ImGui.InputTextWithHint( LabelNewSingleGroupEdit, hint, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
|
if( ImGui.InputTextWithHint( LabelNewSingleGroupEdit, "Add new Single Group...", ref newGroup, 64,
|
||||||
|
ImGuiInputTextFlags.EnterReturnsTrue ) )
|
||||||
{
|
{
|
||||||
AddNewGroup( newGroup, SelectType.Single );
|
_modManager.ChangeModGroup( "", newGroup, Mod.Data, SelectType.Single );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -337,21 +257,22 @@ namespace Penumbra.UI
|
||||||
if( ImGui.InputTextWithHint( LabelNewMultiGroup, "Add new Multi Group...", ref newGroup, 64,
|
if( ImGui.InputTextWithHint( LabelNewMultiGroup, "Add new Multi Group...", ref newGroup, 64,
|
||||||
ImGuiInputTextFlags.EnterReturnsTrue ) )
|
ImGuiInputTextFlags.EnterReturnsTrue ) )
|
||||||
{
|
{
|
||||||
AddNewGroup( newGroup, SelectType.Multi );
|
_modManager.ChangeModGroup( "", newGroup, Mod.Data, SelectType.Multi );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawGroupSelectorsEdit()
|
private void DrawGroupSelectorsEdit()
|
||||||
{
|
{
|
||||||
var labelEditPos = CheckMarkSize;
|
var labelEditPos = CheckMarkSize;
|
||||||
foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Single ) )
|
var groups = Meta.Groups.Values.ToArray();
|
||||||
|
foreach( var g in groups.Where( g => g.SelectionType == SelectType.Single ) )
|
||||||
{
|
{
|
||||||
labelEditPos = DrawSingleSelectorEdit( g );
|
labelEditPos = DrawSingleSelectorEdit( g );
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawAddSingleGroupField( labelEditPos );
|
DrawAddSingleGroupField( labelEditPos );
|
||||||
|
|
||||||
foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Multi ) )
|
foreach( var g in groups.Where( g => g.SelectionType == SelectType.Multi ) )
|
||||||
{
|
{
|
||||||
DrawMultiSelectorEdit( g );
|
DrawMultiSelectorEdit( g );
|
||||||
}
|
}
|
||||||
|
|
@ -403,7 +324,7 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
_selector.SaveCurrentMod();
|
_selector.SaveCurrentMod();
|
||||||
if( Mod.Enabled )
|
if( Mod.Settings.Enabled )
|
||||||
{
|
{
|
||||||
_selector.ReloadCurrentMod();
|
_selector.ReloadCurrentMod();
|
||||||
}
|
}
|
||||||
|
|
@ -428,7 +349,7 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
Meta.FileSwaps[ key ] = newValue;
|
Meta.FileSwaps[ key ] = newValue;
|
||||||
_selector.SaveCurrentMod();
|
_selector.SaveCurrentMod();
|
||||||
if( Mod.Enabled )
|
if( Mod.Settings.Enabled )
|
||||||
{
|
{
|
||||||
_selector.ReloadCurrentMod();
|
_selector.ReloadCurrentMod();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@ using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Penumbra.Models;
|
using Penumbra.Mod;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.UI
|
namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
|
|
@ -20,6 +21,7 @@ namespace Penumbra.UI
|
||||||
private const string LabelEditWebsite = "##editWebsite";
|
private const string LabelEditWebsite = "##editWebsite";
|
||||||
private const string LabelModEnabled = "Enabled";
|
private const string LabelModEnabled = "Enabled";
|
||||||
private const string LabelEditingEnabled = "Enable Editing";
|
private const string LabelEditingEnabled = "Enable Editing";
|
||||||
|
private const string LabelOverWriteDir = "OverwriteDir";
|
||||||
private const string ButtonOpenWebsite = "Open Website";
|
private const string ButtonOpenWebsite = "Open Website";
|
||||||
private const string ButtonOpenModFolder = "Open Mod Folder";
|
private const string ButtonOpenModFolder = "Open Mod Folder";
|
||||||
private const string ButtonRenameModFolder = "Rename Mod Folder";
|
private const string ButtonRenameModFolder = "Rename Mod Folder";
|
||||||
|
|
@ -45,6 +47,7 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private readonly SettingsInterface _base;
|
private readonly SettingsInterface _base;
|
||||||
private readonly Selector _selector;
|
private readonly Selector _selector;
|
||||||
|
private readonly ModManager _modManager;
|
||||||
public readonly PluginDetails Details;
|
public readonly PluginDetails Details;
|
||||||
|
|
||||||
private bool _editMode;
|
private bool _editMode;
|
||||||
|
|
@ -57,23 +60,22 @@ namespace Penumbra.UI
|
||||||
_selector = s;
|
_selector = s;
|
||||||
Details = new PluginDetails( _base, _selector );
|
Details = new PluginDetails( _base, _selector );
|
||||||
_currentWebsite = Meta?.Website ?? "";
|
_currentWebsite = Meta?.Website ?? "";
|
||||||
|
_modManager = Service< ModManager >.Get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModInfo? Mod
|
private Mod.Mod? Mod
|
||||||
=> _selector.Mod;
|
=> _selector.Mod;
|
||||||
|
|
||||||
private ModMeta? Meta
|
private ModMeta? Meta
|
||||||
=> Mod?.Mod.Meta;
|
=> Mod?.Data.Meta;
|
||||||
|
|
||||||
private void DrawName()
|
private void DrawName()
|
||||||
{
|
{
|
||||||
var name = Meta!.Name;
|
var name = Meta!.Name;
|
||||||
if( ImGuiCustom.InputOrText( _editMode, LabelEditName, ref name, 64 )
|
if( Custom.ImGuiCustom.InputOrText( _editMode, LabelEditName, ref name, 64 ) && _modManager.RenameMod( name, Mod!.Data ) )
|
||||||
&& name.Length > 0
|
|
||||||
&& name != Meta.Name )
|
|
||||||
{
|
{
|
||||||
Meta.Name = name;
|
_selector.RenameCurrentModLower( name );
|
||||||
_selector.SaveCurrentMod();
|
_selector.SelectModByDir( Mod.Data.BasePath.Name );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,7 +89,7 @@ namespace Penumbra.UI
|
||||||
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
|
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var version = Meta!.Version;
|
var version = Meta!.Version;
|
||||||
if( ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16 )
|
if( Custom.ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16 )
|
||||||
&& version != Meta.Version )
|
&& version != Meta.Version )
|
||||||
{
|
{
|
||||||
Meta.Version = version;
|
Meta.Version = version;
|
||||||
|
|
@ -112,7 +114,7 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var author = Meta!.Author;
|
var author = Meta!.Author;
|
||||||
if( ImGuiCustom.InputOrText( _editMode, LabelEditAuthor, ref author, 64 )
|
if( Custom.ImGuiCustom.InputOrText( _editMode, LabelEditAuthor, ref author, 64 )
|
||||||
&& author != Meta.Author )
|
&& author != Meta.Author )
|
||||||
{
|
{
|
||||||
Meta.Author = author;
|
Meta.Author = author;
|
||||||
|
|
@ -130,7 +132,7 @@ namespace Penumbra.UI
|
||||||
ImGui.TextColored( GreyColor, "from" );
|
ImGui.TextColored( GreyColor, "from" );
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var website = Meta!.Website;
|
var website = Meta!.Website;
|
||||||
if( ImGuiCustom.ResizingTextInput( LabelEditWebsite, ref website, 512 )
|
if( Custom.ImGuiCustom.ResizingTextInput( LabelEditWebsite, ref website, 512 )
|
||||||
&& website != Meta.Website )
|
&& website != Meta.Website )
|
||||||
{
|
{
|
||||||
Meta.Website = website;
|
Meta.Website = website;
|
||||||
|
|
@ -191,15 +193,34 @@ namespace Penumbra.UI
|
||||||
DrawWebsite();
|
DrawWebsite();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawPriority()
|
||||||
|
{
|
||||||
|
var priority = Mod!.Settings.Priority;
|
||||||
|
ImGui.SetNextItemWidth( 50 );
|
||||||
|
if( ImGui.InputInt( "Priority", ref priority, 0 ) && priority != Mod!.Settings.Priority )
|
||||||
|
{
|
||||||
|
Mod.Settings.Priority = priority;
|
||||||
|
var collection = _modManager.CurrentCollection;
|
||||||
|
collection.Save( _base._plugin.PluginInterface! );
|
||||||
|
collection.CalculateEffectiveFileList( _modManager.BasePath, Mod.Data.Resources.MetaManipulations.Count > 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( ImGui.IsItemHovered() )
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip( "Higher priority mods take precedence over other mods in the case of file conflicts.\n"
|
||||||
|
+ "In case of identical priority, the alphabetically first mod takes precedence." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawEnabledMark()
|
private void DrawEnabledMark()
|
||||||
{
|
{
|
||||||
var enabled = Mod!.Enabled;
|
var enabled = Mod!.Settings.Enabled;
|
||||||
if( ImGui.Checkbox( LabelModEnabled, ref enabled ) )
|
if( ImGui.Checkbox( LabelModEnabled, ref enabled ) )
|
||||||
{
|
{
|
||||||
Mod.Enabled = enabled;
|
Mod.Settings.Enabled = enabled;
|
||||||
var modManager = Service< ModManager >.Get();
|
var collection = _modManager.CurrentCollection;
|
||||||
modManager.Mods!.Save();
|
collection.Save( _base._plugin.PluginInterface! );
|
||||||
modManager.CalculateEffectiveFileList();
|
collection.CalculateEffectiveFileList( _modManager.BasePath, Mod.Data.Resources.MetaManipulations.Count > 0 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,7 +233,7 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
if( ImGui.Button( ButtonOpenModFolder ) )
|
if( ImGui.Button( ButtonOpenModFolder ) )
|
||||||
{
|
{
|
||||||
Process.Start( Mod!.Mod.ModBasePath.FullName );
|
Process.Start( Mod!.Data.BasePath.FullName );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( ImGui.IsItemHovered() )
|
if( ImGui.IsItemHovered() )
|
||||||
|
|
@ -224,7 +245,98 @@ namespace Penumbra.UI
|
||||||
private string _newName = "";
|
private string _newName = "";
|
||||||
private bool _keyboardFocus = true;
|
private bool _keyboardFocus = true;
|
||||||
|
|
||||||
private void DrawRenameModFolderButton()
|
private void RenameModFolder( string newName )
|
||||||
|
{
|
||||||
|
_newName = newName.RemoveNonAsciiSymbols().RemoveInvalidPathSymbols();
|
||||||
|
if( _newName.Length == 0 )
|
||||||
|
{
|
||||||
|
PluginLog.Debug( "New Directory name {NewName} was empty after removing invalid symbols.", newName );
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
else if( !string.Equals( _newName, Mod!.Data.BasePath.Name, StringComparison.InvariantCultureIgnoreCase ) )
|
||||||
|
{
|
||||||
|
DirectoryInfo dir = Mod!.Data.BasePath;
|
||||||
|
DirectoryInfo newDir = new( Path.Combine( dir.Parent!.FullName, _newName ) );
|
||||||
|
|
||||||
|
if( newDir.Exists )
|
||||||
|
{
|
||||||
|
ImGui.OpenPopup( LabelOverWriteDir );
|
||||||
|
}
|
||||||
|
else if( Service< ModManager >.Get()!.RenameModFolder( Mod.Data, newDir ) )
|
||||||
|
{
|
||||||
|
_selector.ReloadCurrentMod();
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MergeFolderInto( DirectoryInfo source, DirectoryInfo target )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach( var file in source.EnumerateFiles( "*", SearchOption.AllDirectories ) )
|
||||||
|
{
|
||||||
|
var targetFile = new FileInfo( Path.Combine( target.FullName, file.FullName.Substring( source.FullName.Length + 1 ) ) );
|
||||||
|
if( targetFile.Exists )
|
||||||
|
{
|
||||||
|
targetFile.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
targetFile.Directory?.Create();
|
||||||
|
file.MoveTo( targetFile.FullName );
|
||||||
|
}
|
||||||
|
|
||||||
|
source.Delete( true );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not merge directory {source.FullName} into {target.FullName}:\n{e}" );
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool OverwriteDirPopup()
|
||||||
|
{
|
||||||
|
var closeParent = false;
|
||||||
|
var _ = true;
|
||||||
|
if( ImGui.BeginPopupModal( LabelOverWriteDir, ref _, ImGuiWindowFlags.AlwaysAutoResize ) )
|
||||||
|
{
|
||||||
|
DirectoryInfo dir = Mod!.Data.BasePath;
|
||||||
|
DirectoryInfo newDir = new( Path.Combine( dir.Parent!.FullName, _newName ) );
|
||||||
|
ImGui.Text(
|
||||||
|
$"The mod directory {newDir} already exists.\nDo you want to merge / overwrite both mods?\nThis may corrupt the resulting mod in irrecoverable ways." );
|
||||||
|
var buttonSize = new Vector2( 120, 0 );
|
||||||
|
if( ImGui.Button( "Yes", buttonSize ) )
|
||||||
|
{
|
||||||
|
if( MergeFolderInto( dir, newDir ) )
|
||||||
|
{
|
||||||
|
Service< ModManager >.Get()!.RenameModFolder( Mod.Data, newDir, false );
|
||||||
|
|
||||||
|
_selector.ResetModNamesLower();
|
||||||
|
_selector.SelectModByDir( _newName );
|
||||||
|
|
||||||
|
closeParent = true;
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if( ImGui.Button( "Cancel", buttonSize ) )
|
||||||
|
{
|
||||||
|
_keyboardFocus = true;
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
return closeParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRenameModFolderPopup()
|
||||||
{
|
{
|
||||||
var _ = true;
|
var _ = true;
|
||||||
_keyboardFocus |= !ImGui.IsPopupOpen( PopupRenameFolder );
|
_keyboardFocus |= !ImGui.IsPopupOpen( PopupRenameFolder );
|
||||||
|
|
@ -237,121 +349,38 @@ namespace Penumbra.UI
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
var newName = Mod!.FolderName;
|
var newName = Mod!.Data.BasePath.Name;
|
||||||
|
|
||||||
if( _keyboardFocus )
|
if( _keyboardFocus )
|
||||||
{
|
{
|
||||||
PluginLog.Log( "Fuck you" );
|
|
||||||
ImGui.SetKeyboardFocusHere();
|
ImGui.SetKeyboardFocusHere();
|
||||||
_keyboardFocus = false;
|
_keyboardFocus = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( ImGui.InputText( "New Folder Name##RenameFolderInput", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
|
if( ImGui.InputText( "New Folder Name##RenameFolderInput", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
|
||||||
{
|
{
|
||||||
_newName = newName.RemoveNonAsciiSymbols().RemoveInvalidPathSymbols();
|
RenameModFolder( newName );
|
||||||
if( _newName.Length == 0 )
|
|
||||||
{
|
|
||||||
ImGui.CloseCurrentPopup();
|
|
||||||
}
|
|
||||||
else if( !string.Equals( _newName, Mod!.FolderName, StringComparison.InvariantCultureIgnoreCase ) )
|
|
||||||
{
|
|
||||||
DirectoryInfo dir = Mod!.Mod.ModBasePath;
|
|
||||||
DirectoryInfo newDir = new( Path.Combine( dir.Parent!.FullName, _newName ) );
|
|
||||||
if( newDir.Exists )
|
|
||||||
{
|
|
||||||
PluginLog.Error( "GOTT" );
|
|
||||||
ImGui.OpenPopup( "OverwriteDir" );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
dir.MoveTo( newDir.FullName );
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
PluginLog.Error( $"Error while renaming directory {dir.FullName} to {newDir.FullName}:\n{e}" );
|
|
||||||
}
|
|
||||||
|
|
||||||
Mod!.FolderName = _newName;
|
|
||||||
Mod!.Mod.ModBasePath = newDir;
|
|
||||||
_selector.ReloadCurrentMod();
|
|
||||||
Service< ModManager >.Get()!.Mods!.Save();
|
|
||||||
ImGui.CloseCurrentPopup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TextColored( GreyColor,
|
ImGui.TextColored( GreyColor,
|
||||||
"Please restrict yourself to ascii symbols that are valid in a windows path,\nother symbols will be replaced by underscores." );
|
"Please restrict yourself to ascii symbols that are valid in a windows path,\nother symbols will be replaced by underscores." );
|
||||||
|
|
||||||
var closeParent = false;
|
|
||||||
_ = true;
|
|
||||||
ImGui.SetNextWindowPos( ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, Vector2.One / 2 );
|
ImGui.SetNextWindowPos( ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, Vector2.One / 2 );
|
||||||
if( ImGui.BeginPopupModal( "OverwriteDir", ref _, ImGuiWindowFlags.AlwaysAutoResize ) )
|
|
||||||
{
|
|
||||||
DirectoryInfo dir = Mod!.Mod.ModBasePath;
|
|
||||||
DirectoryInfo newDir = new( Path.Combine( dir.Parent!.FullName, _newName ) );
|
|
||||||
ImGui.Text(
|
|
||||||
$"The mod directory {newDir} already exists.\nDo you want to merge / overwrite both mods?\nThis may corrupt the resulting mod in irrecoverable ways." );
|
|
||||||
var buttonSize = new Vector2( 120, 0 );
|
|
||||||
if( ImGui.Button( "Yes", buttonSize ) )
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach( var file in dir.EnumerateFiles( "*", SearchOption.AllDirectories ) )
|
|
||||||
{
|
|
||||||
var target = new FileInfo( Path.Combine( newDir.FullName,
|
|
||||||
file.FullName.Substring( dir.FullName.Length ) ) );
|
|
||||||
if( target.Exists )
|
|
||||||
{
|
|
||||||
target.Delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
target.Directory?.Create();
|
|
||||||
file.MoveTo( target.FullName );
|
|
||||||
}
|
|
||||||
|
|
||||||
dir.Delete( true );
|
if( OverwriteDirPopup() )
|
||||||
|
|
||||||
var mod = Service< ModManager >.Get()!.Mods!.ModSettings!
|
|
||||||
.RemoveAll( m => m.FolderName == _newName );
|
|
||||||
|
|
||||||
Mod!.FolderName = _newName;
|
|
||||||
Mod!.Mod.ModBasePath = newDir;
|
|
||||||
Service< ModManager >.Get()!.Mods!.Save();
|
|
||||||
_base.ReloadMods();
|
|
||||||
_selector.SelectModByDir( _newName );
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
PluginLog.Error( $"Error while renaming directory {dir.FullName} to {newDir.FullName}:\n{e}" );
|
|
||||||
}
|
|
||||||
|
|
||||||
closeParent = true;
|
|
||||||
ImGui.CloseCurrentPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
if( ImGui.Button( "Cancel", buttonSize ) )
|
|
||||||
{
|
|
||||||
PluginLog.Error( "FUCKFUCK" );
|
|
||||||
_keyboardFocus = true;
|
|
||||||
ImGui.CloseCurrentPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( closeParent )
|
|
||||||
{
|
{
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndPopup();
|
ImGui.EndPopup();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void DrawRenameModFolderButton()
|
||||||
|
{
|
||||||
|
DrawRenameModFolderPopup();
|
||||||
if( ImGui.Button( ButtonRenameModFolder ) )
|
if( ImGui.Button( ButtonRenameModFolder ) )
|
||||||
{
|
{
|
||||||
ImGui.OpenPopup( PopupRenameFolder );
|
ImGui.OpenPopup( PopupRenameFolder );
|
||||||
|
|
@ -367,7 +396,8 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
if( ImGui.Button( ButtonEditJson ) )
|
if( ImGui.Button( ButtonEditJson ) )
|
||||||
{
|
{
|
||||||
Process.Start( _selector.SaveCurrentMod() );
|
_selector.SaveCurrentMod();
|
||||||
|
Process.Start( Mod!.Data.MetaFile.FullName );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( ImGui.IsItemHovered() )
|
if( ImGui.IsItemHovered() )
|
||||||
|
|
@ -389,14 +419,27 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawResetMetaButton()
|
||||||
|
{
|
||||||
|
if( ImGui.Button( "Recompute Metadata" ) )
|
||||||
|
{
|
||||||
|
_selector.ReloadCurrentMod( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( ImGui.IsItemHovered() )
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip(
|
||||||
|
"Force a recomputation of the metadata_manipulations.json file from all .meta files in the folder.\nAlso reloads the mod." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawDeduplicateButton()
|
private void DrawDeduplicateButton()
|
||||||
{
|
{
|
||||||
if( ImGui.Button( ButtonDeduplicate ) )
|
if( ImGui.Button( ButtonDeduplicate ) )
|
||||||
{
|
{
|
||||||
ModCleanup.Deduplicate( Mod!.Mod.ModBasePath, Meta! );
|
ModCleanup.Deduplicate( Mod!.Data.BasePath, Meta! );
|
||||||
_selector.SaveCurrentMod();
|
_selector.SaveCurrentMod();
|
||||||
Mod.Mod.RefreshModFiles();
|
_selector.ReloadCurrentMod();
|
||||||
Service< ModManager >.Get().CalculateEffectiveFileList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if( ImGui.IsItemHovered() )
|
if( ImGui.IsItemHovered() )
|
||||||
|
|
@ -409,10 +452,9 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
if( ImGui.Button( ButtonNormalize ) )
|
if( ImGui.Button( ButtonNormalize ) )
|
||||||
{
|
{
|
||||||
ModCleanup.Normalize( Mod!.Mod.ModBasePath, Meta! );
|
ModCleanup.Normalize( Mod!.Data.BasePath, Meta! );
|
||||||
_selector.SaveCurrentMod();
|
_selector.SaveCurrentMod();
|
||||||
Mod.Mod.RefreshModFiles();
|
_selector.ReloadCurrentMod();
|
||||||
Service< ModManager >.Get().CalculateEffectiveFileList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if( ImGui.IsItemHovered() )
|
if( ImGui.IsItemHovered() )
|
||||||
|
|
@ -430,6 +472,8 @@ namespace Penumbra.UI
|
||||||
DrawEditJsonButton();
|
DrawEditJsonButton();
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
DrawReloadJsonButton();
|
DrawReloadJsonButton();
|
||||||
|
|
||||||
|
DrawResetMetaButton();
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
DrawDeduplicateButton();
|
DrawDeduplicateButton();
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
@ -454,9 +498,11 @@ namespace Penumbra.UI
|
||||||
DrawHeaderLine();
|
DrawHeaderLine();
|
||||||
|
|
||||||
// Next line with fixed distance.
|
// Next line with fixed distance.
|
||||||
ImGuiCustom.VerticalDistance( HeaderLineDistance );
|
Custom.ImGuiCustom.VerticalDistance( HeaderLineDistance );
|
||||||
|
|
||||||
DrawEnabledMark();
|
DrawEnabledMark();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawPriority();
|
||||||
if( _base._plugin!.Configuration!.ShowAdvanced )
|
if( _base._plugin!.Configuration!.ShowAdvanced )
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Penumbra.Importer;
|
using Penumbra.Importer;
|
||||||
using Penumbra.Models;
|
using Penumbra.Mod;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.UI
|
namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
|
|
@ -16,80 +17,92 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
private class Selector
|
private class Selector
|
||||||
{
|
{
|
||||||
private const string LabelSelectorList = "##availableModList";
|
[Flags]
|
||||||
private const string LabelModFilter = "##ModFilter";
|
private enum ModFilter
|
||||||
private const string LabelPriorityPopup = "Priority";
|
{
|
||||||
private const string LabelAddModPopup = "AddMod";
|
Enabled = 1 << 0,
|
||||||
private const string TooltipModFilter = "Filter mods for those containing the given substring.";
|
Disabled = 1 << 1,
|
||||||
private const string TooltipMoveDown = "Move the selected mod down in priority";
|
NoConflict = 1 << 2,
|
||||||
private const string TooltipMoveUp = "Move the selected mod up in priority";
|
SolvedConflict = 1 << 3,
|
||||||
private const string TooltipDelete = "Delete the selected mod";
|
UnsolvedConflict = 1 << 4,
|
||||||
private const string TooltipAdd = "Add an empty mod";
|
HasNoMetaManipulations = 1 << 5,
|
||||||
private const string DialogDeleteMod = "PenumbraDeleteMod";
|
HasMetaManipulations = 1 << 6,
|
||||||
private const string ButtonYesDelete = "Yes, delete it";
|
HasNoFileSwaps = 1 << 7,
|
||||||
private const string ButtonNoDelete = "No, keep it";
|
HasFileSwaps = 1 << 8,
|
||||||
private const string DescPriorityPopup = "New Priority:";
|
HasConfig = 1 << 9,
|
||||||
|
HasNoConfig = 1 << 10,
|
||||||
|
HasNoFiles = 1 << 11,
|
||||||
|
HasFiles = 1 << 12,
|
||||||
|
};
|
||||||
|
|
||||||
private const float SelectorPanelWidth = 240f;
|
private const ModFilter UnfilteredStateMods = ( ModFilter )( ( 1 << 13 ) - 1 );
|
||||||
private const uint DisabledModColor = 0xFF666666;
|
|
||||||
private const uint ConflictingModColor = 0xFFAAAAFF;
|
|
||||||
|
|
||||||
private static readonly Vector2 SelectorButtonSizes = new( 60, 0 );
|
private static readonly Dictionary< ModFilter, string > ModFilterNames = new()
|
||||||
private static readonly string ArrowUpString = FontAwesomeIcon.ArrowUp.ToIconString();
|
{
|
||||||
private static readonly string ArrowDownString = FontAwesomeIcon.ArrowDown.ToIconString();
|
{ ModFilter.Enabled, "Enabled" },
|
||||||
|
{ ModFilter.Disabled, "Disabled" },
|
||||||
|
{ ModFilter.NoConflict, "No Conflicts" },
|
||||||
|
{ ModFilter.SolvedConflict, "Solved Conflicts" },
|
||||||
|
{ ModFilter.UnsolvedConflict, "Unsolved Conflicts" },
|
||||||
|
{ ModFilter.HasNoMetaManipulations, "No Meta Manipulations" },
|
||||||
|
{ ModFilter.HasMetaManipulations, "Meta Manipulations" },
|
||||||
|
{ ModFilter.HasNoFileSwaps, "No File Swaps" },
|
||||||
|
{ ModFilter.HasFileSwaps, "File Swaps" },
|
||||||
|
{ ModFilter.HasNoConfig, "No Configuration" },
|
||||||
|
{ ModFilter.HasConfig, "Configuration" },
|
||||||
|
{ ModFilter.HasNoFiles, "No Files" },
|
||||||
|
{ ModFilter.HasFiles, "Files" },
|
||||||
|
};
|
||||||
|
|
||||||
|
private const string LabelSelectorList = "##availableModList";
|
||||||
|
private const string LabelModFilter = "##ModFilter";
|
||||||
|
private const string LabelAddModPopup = "AddMod";
|
||||||
|
private const string TooltipModFilter = "Filter mods for those containing the given substring.";
|
||||||
|
private const string TooltipDelete = "Delete the selected mod";
|
||||||
|
private const string TooltipAdd = "Add an empty mod";
|
||||||
|
private const string DialogDeleteMod = "PenumbraDeleteMod";
|
||||||
|
private const string ButtonYesDelete = "Yes, delete it";
|
||||||
|
private const string ButtonNoDelete = "No, keep it";
|
||||||
|
|
||||||
|
private const float SelectorPanelWidth = 240f;
|
||||||
|
private const uint DisabledModColor = 0xFF666666;
|
||||||
|
private const uint ConflictingModColor = 0xFFAAAAFF;
|
||||||
|
private const uint HandledConflictModColor = 0xFF88DDDD;
|
||||||
|
|
||||||
|
private static readonly Vector2 SelectorButtonSizes = new( 120, 0 );
|
||||||
|
|
||||||
private readonly SettingsInterface _base;
|
private readonly SettingsInterface _base;
|
||||||
|
private readonly ModManager _modManager;
|
||||||
|
|
||||||
private static ModCollection? Mods
|
private List< Mod.Mod >? Mods
|
||||||
=> Service< ModManager >.Get().Mods;
|
=> _modManager.CurrentCollection.Cache?.AvailableMods;
|
||||||
|
|
||||||
public ModInfo? Mod { get; private set; }
|
public Mod.Mod? Mod { get; private set; }
|
||||||
private int _index;
|
private int _index;
|
||||||
private int? _deleteIndex;
|
private int? _deleteIndex;
|
||||||
private string _modFilter = "";
|
private string _modFilter = "";
|
||||||
private string[]? _modNamesLower;
|
private string[] _modNamesLower;
|
||||||
|
private ModFilter _stateFilter = UnfilteredStateMods;
|
||||||
|
|
||||||
public Selector( SettingsInterface ui )
|
public Selector( SettingsInterface ui )
|
||||||
{
|
{
|
||||||
_base = ui;
|
_base = ui;
|
||||||
|
_modNamesLower = Array.Empty< string >();
|
||||||
|
_modManager = Service<ModManager>.Get();
|
||||||
ResetModNamesLower();
|
ResetModNamesLower();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetModNamesLower()
|
public void ResetModNamesLower()
|
||||||
{
|
{
|
||||||
_modNamesLower = Mods?.ModSettings?.Where( I => I.Mod != null )
|
_modNamesLower = Mods?.Select( m => m.Data.Meta.Name.ToLowerInvariant() ).ToArray()
|
||||||
.Select( I => I.Mod!.Meta.Name.ToLowerInvariant() ).ToArray()
|
?? Array.Empty< string >();
|
||||||
?? new string[] { };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawPriorityChangeButton( string iconString, bool up, int unavailableWhen )
|
public void RenameCurrentModLower( string newName )
|
||||||
{
|
{
|
||||||
ImGui.PushFont( UiBuilder.IconFont );
|
if( _index >= 0 )
|
||||||
if( _index != unavailableWhen )
|
|
||||||
{
|
{
|
||||||
if( ImGui.Button( iconString, SelectorButtonSizes ) )
|
_modNamesLower[ _index ] = newName.ToLowerInvariant();
|
||||||
{
|
|
||||||
SetSelection( _index );
|
|
||||||
Service< ModManager >.Get().ChangeModPriority( Mod!, up );
|
|
||||||
_modNamesLower!.Swap( _index, _index + ( up ? 1 : -1 ) );
|
|
||||||
_index += up ? 1 : -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.PushStyleVar( ImGuiStyleVar.Alpha, 0.5f );
|
|
||||||
ImGui.Button( iconString, SelectorButtonSizes );
|
|
||||||
ImGui.PopStyleVar();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.PopFont();
|
|
||||||
|
|
||||||
if( ImGui.IsItemHovered() )
|
|
||||||
{
|
|
||||||
ImGui.SetTooltip(
|
|
||||||
_base._plugin!.Configuration!.InvertModListOrder ^ up ? TooltipMoveDown : TooltipMoveUp
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,7 +140,7 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var newDir = TexToolsImport.CreateModFolder( new DirectoryInfo( _base._plugin.Configuration!.CurrentCollection ),
|
var newDir = TexToolsImport.CreateModFolder( new DirectoryInfo( _base._plugin.Configuration!.ModDirectory ),
|
||||||
newName );
|
newName );
|
||||||
var modMeta = new ModMeta
|
var modMeta = new ModMeta
|
||||||
{
|
{
|
||||||
|
|
@ -135,9 +148,10 @@ namespace Penumbra.UI
|
||||||
Name = newName,
|
Name = newName,
|
||||||
Description = string.Empty,
|
Description = string.Empty,
|
||||||
};
|
};
|
||||||
var metaPath = Path.Combine( newDir.FullName, "meta.json" );
|
|
||||||
File.WriteAllText( metaPath, JsonConvert.SerializeObject( modMeta, Formatting.Indented ) );
|
var metaFile = new FileInfo( Path.Combine( newDir.FullName, "meta.json" ) );
|
||||||
_base.ReloadMods();
|
modMeta.SaveToFile( metaFile );
|
||||||
|
_modManager.AddMod( newDir );
|
||||||
SelectModByDir( newDir.Name );
|
SelectModByDir( newDir.Name );
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
|
|
@ -174,7 +188,7 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private void DrawModsSelectorFilter()
|
private void DrawModsSelectorFilter()
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth( SelectorButtonSizes.X * 4 );
|
ImGui.SetNextItemWidth( SelectorButtonSizes.X * 2 - 22 );
|
||||||
var tmp = _modFilter;
|
var tmp = _modFilter;
|
||||||
if( ImGui.InputTextWithHint( LabelModFilter, "Filter Mods...", ref tmp, 256 ) )
|
if( ImGui.InputTextWithHint( LabelModFilter, "Filter Mods...", ref tmp, 256 ) )
|
||||||
{
|
{
|
||||||
|
|
@ -185,6 +199,26 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
ImGui.SetTooltip( TooltipModFilter );
|
ImGui.SetTooltip( TooltipModFilter );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if( ImGui.BeginCombo( "##ModStateFilter", "",
|
||||||
|
ImGuiComboFlags.NoPreview | ImGuiComboFlags.PopupAlignLeft | ImGuiComboFlags.HeightLargest ) )
|
||||||
|
{
|
||||||
|
var flags = ( int )_stateFilter;
|
||||||
|
foreach( ModFilter flag in Enum.GetValues( typeof( ModFilter ) ) )
|
||||||
|
{
|
||||||
|
ImGui.CheckboxFlags( ModFilterNames[ flag ], ref flags, ( int )flag );
|
||||||
|
}
|
||||||
|
|
||||||
|
_stateFilter = ( ModFilter )flags;
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( ImGui.IsItemHovered() )
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip( "Filter mods for their activation status." );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawModsSelectorButtons()
|
private void DrawModsSelectorButtons()
|
||||||
|
|
@ -193,10 +227,6 @@ namespace Penumbra.UI
|
||||||
ImGui.PushStyleVar( ImGuiStyleVar.WindowPadding, ZeroVector );
|
ImGui.PushStyleVar( ImGuiStyleVar.WindowPadding, ZeroVector );
|
||||||
ImGui.PushStyleVar( ImGuiStyleVar.FrameRounding, 0 );
|
ImGui.PushStyleVar( ImGuiStyleVar.FrameRounding, 0 );
|
||||||
|
|
||||||
DrawPriorityChangeButton( ArrowUpString, false, 0 );
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawPriorityChangeButton( ArrowDownString, true, Mods?.ModSettings?.Count - 1 ?? 0 );
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawModTrashButton();
|
DrawModTrashButton();
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
DrawModAddButton();
|
DrawModAddButton();
|
||||||
|
|
@ -221,7 +251,7 @@ namespace Penumbra.UI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( Mod?.Mod == null )
|
if( Mod == null )
|
||||||
{
|
{
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
ImGui.EndPopup();
|
ImGui.EndPopup();
|
||||||
|
|
@ -231,16 +261,15 @@ namespace Penumbra.UI
|
||||||
ImGui.Text( "Are you sure you want to delete the following mod:" );
|
ImGui.Text( "Are you sure you want to delete the following mod:" );
|
||||||
// todo: why the fuck does this become null??????
|
// todo: why the fuck does this become null??????
|
||||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||||
ImGui.TextColored( new Vector4( 0.7f, 0.1f, 0.1f, 1 ), Mod?.Mod?.Meta?.Name ?? "Unknown" );
|
ImGui.TextColored( new Vector4( 0.7f, 0.1f, 0.1f, 1 ), Mod.Data.Meta.Name ?? "Unknown" );
|
||||||
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() ) / 2 );
|
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() ) / 2 );
|
||||||
|
|
||||||
var buttonSize = new Vector2( 120, 0 );
|
var buttonSize = new Vector2( 120, 0 );
|
||||||
if( ImGui.Button( ButtonYesDelete, buttonSize ) )
|
if( ImGui.Button( ButtonYesDelete, buttonSize ) )
|
||||||
{
|
{
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
Service< ModManager >.Get().DeleteMod( Mod?.Mod );
|
_modManager.DeleteMod( Mod.Data.BasePath );
|
||||||
ClearSelection();
|
ClearSelection();
|
||||||
_base.ReloadMods();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
@ -254,7 +283,93 @@ namespace Penumbra.UI
|
||||||
ImGui.EndPopup();
|
ImGui.EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int _priorityPopupIdx = 0;
|
private bool CheckFlags( int count, ModFilter hasNoFlag, ModFilter hasFlag )
|
||||||
|
{
|
||||||
|
if( count == 0 )
|
||||||
|
{
|
||||||
|
if( _stateFilter.HasFlag( hasNoFlag ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if( _stateFilter.HasFlag( hasFlag ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawMod( Mod.Mod mod, int modIndex )
|
||||||
|
{
|
||||||
|
if( _modFilter.Length > 0 && !_modNamesLower[ modIndex ].Contains( _modFilter )
|
||||||
|
|| CheckFlags( mod.Data.Resources.ModFiles.Count, ModFilter.HasNoFiles, ModFilter.HasFiles )
|
||||||
|
|| CheckFlags( mod.Data.Meta.FileSwaps.Count, ModFilter.HasNoFileSwaps, ModFilter.HasFileSwaps )
|
||||||
|
|| CheckFlags( mod.Data.Resources.MetaManipulations.Count, ModFilter.HasNoMetaManipulations, ModFilter.HasMetaManipulations )
|
||||||
|
|| CheckFlags( mod.Data.Meta.HasGroupsWithConfig ? 1 : 0, ModFilter.HasNoConfig, ModFilter.HasConfig ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var changedColour = false;
|
||||||
|
if( !mod.Settings.Enabled )
|
||||||
|
{
|
||||||
|
if( !_stateFilter.HasFlag( ModFilter.Disabled ) || !_stateFilter.HasFlag( ModFilter.NoConflict ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PushStyleColor( ImGuiCol.Text, DisabledModColor );
|
||||||
|
changedColour = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if( !_stateFilter.HasFlag( ModFilter.Enabled ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mod.Cache.Conflicts.Any() )
|
||||||
|
{
|
||||||
|
if( mod.Cache.Conflicts.Keys.Any( m => m.Settings.Priority == mod.Settings.Priority ) )
|
||||||
|
{
|
||||||
|
if( !_stateFilter.HasFlag( ModFilter.UnsolvedConflict ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PushStyleColor( ImGuiCol.Text, ConflictingModColor );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if( !_stateFilter.HasFlag( ModFilter.SolvedConflict ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PushStyleColor( ImGuiCol.Text, HandledConflictModColor );
|
||||||
|
}
|
||||||
|
|
||||||
|
changedColour = true;
|
||||||
|
}
|
||||||
|
else if( !_stateFilter.HasFlag( ModFilter.NoConflict ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selected = ImGui.Selectable( $"{mod.Data.Meta.Name}##{modIndex}", modIndex == _index );
|
||||||
|
|
||||||
|
if( changedColour )
|
||||||
|
{
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( selected )
|
||||||
|
{
|
||||||
|
SetSelection( modIndex, mod );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
|
|
@ -271,61 +386,9 @@ namespace Penumbra.UI
|
||||||
// Inlay selector list
|
// Inlay selector list
|
||||||
ImGui.BeginChild( LabelSelectorList, new Vector2( SelectorPanelWidth, -ImGui.GetFrameHeightWithSpacing() ), true );
|
ImGui.BeginChild( LabelSelectorList, new Vector2( SelectorPanelWidth, -ImGui.GetFrameHeightWithSpacing() ), true );
|
||||||
|
|
||||||
if( Mods.ModSettings != null )
|
for( var modIndex = 0; modIndex < Mods.Count; modIndex++ )
|
||||||
{
|
{
|
||||||
for( var modIndex = 0; modIndex < Mods.ModSettings.Count; modIndex++ )
|
DrawMod( Mods[ modIndex ], modIndex );
|
||||||
{
|
|
||||||
var settings = Mods.ModSettings[ modIndex ];
|
|
||||||
var modName = settings.Mod.Meta.Name;
|
|
||||||
if( _modFilter.Length > 0 && !_modNamesLower![ modIndex ].Contains( _modFilter ) )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var changedColour = false;
|
|
||||||
if( !settings.Enabled )
|
|
||||||
{
|
|
||||||
ImGui.PushStyleColor( ImGuiCol.Text, DisabledModColor );
|
|
||||||
changedColour = true;
|
|
||||||
}
|
|
||||||
else if( settings.Mod.FileConflicts.Any() )
|
|
||||||
{
|
|
||||||
ImGui.PushStyleColor( ImGuiCol.Text, ConflictingModColor );
|
|
||||||
changedColour = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
var selected = ImGui.Selectable(
|
|
||||||
$"id={modIndex} {modName}",
|
|
||||||
modIndex == _index
|
|
||||||
);
|
|
||||||
#else
|
|
||||||
var selected = ImGui.Selectable( modName, modIndex == _index );
|
|
||||||
#endif
|
|
||||||
if( ImGui.IsItemClicked( ImGuiMouseButton.Right ) )
|
|
||||||
{
|
|
||||||
if( ImGui.IsPopupOpen( LabelPriorityPopup ) )
|
|
||||||
{
|
|
||||||
ImGui.CloseCurrentPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
_priorityPopupIdx = modIndex;
|
|
||||||
_keyboardFocus = true;
|
|
||||||
ImGui.OpenPopup( LabelPriorityPopup );
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.OpenPopupOnItemClick( LabelPriorityPopup, ImGuiPopupFlags.MouseButtonRight );
|
|
||||||
|
|
||||||
if( changedColour )
|
|
||||||
{
|
|
||||||
ImGui.PopStyleColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( selected )
|
|
||||||
{
|
|
||||||
SetSelection( modIndex, settings );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndChild();
|
ImGui.EndChild();
|
||||||
|
|
@ -334,52 +397,9 @@ namespace Penumbra.UI
|
||||||
ImGui.EndGroup();
|
ImGui.EndGroup();
|
||||||
|
|
||||||
DrawDeleteModal();
|
DrawDeleteModal();
|
||||||
DrawPriorityPopup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawPriorityPopup()
|
private void SetSelection( int idx, Mod.Mod? info )
|
||||||
{
|
|
||||||
if( !ImGui.BeginPopupContextItem( LabelPriorityPopup ) )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var size = ImGui.CalcTextSize( DescPriorityPopup ).X;
|
|
||||||
//ImGui.Text( DescPriorityPopup );
|
|
||||||
var newPriority = _priorityPopupIdx;
|
|
||||||
|
|
||||||
if( _keyboardFocus )
|
|
||||||
{
|
|
||||||
ImGui.SetKeyboardFocusHere( -1 );
|
|
||||||
_keyboardFocus = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SetNextItemWidth( size );
|
|
||||||
if( ImGui.InputInt( "New Priority", ref newPriority, 0, 0,
|
|
||||||
ImGuiInputTextFlags.EnterReturnsTrue )
|
|
||||||
&& newPriority != _priorityPopupIdx )
|
|
||||||
{
|
|
||||||
Service< ModManager >.Get().ChangeModPriority( Mods!.ModSettings![ _priorityPopupIdx ], newPriority );
|
|
||||||
ResetModNamesLower();
|
|
||||||
if( _priorityPopupIdx == _index )
|
|
||||||
{
|
|
||||||
_index = newPriority;
|
|
||||||
SetSelection( _index );
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.CloseCurrentPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ImGui.IsKeyPressed( ImGui.GetKeyIndex( ImGuiKey.Escape ) ) )
|
|
||||||
{
|
|
||||||
ImGui.CloseCurrentPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void SetSelection( int idx, ModInfo? info )
|
|
||||||
{
|
{
|
||||||
Mod = info;
|
Mod = info;
|
||||||
if( idx != _index )
|
if( idx != _index )
|
||||||
|
|
@ -393,7 +413,7 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private void SetSelection( int idx )
|
private void SetSelection( int idx )
|
||||||
{
|
{
|
||||||
if( idx >= ( Mods?.ModSettings?.Count ?? 0 ) )
|
if( idx >= ( Mods?.Count ?? 0 ) )
|
||||||
{
|
{
|
||||||
idx = -1;
|
idx = -1;
|
||||||
}
|
}
|
||||||
|
|
@ -404,58 +424,45 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SetSelection( idx, Mods!.ModSettings![ idx ] );
|
SetSelection( idx, Mods![ idx ] );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ReloadSelection()
|
||||||
|
=> SetSelection( _index, Mods![ _index ] );
|
||||||
|
|
||||||
public void ClearSelection()
|
public void ClearSelection()
|
||||||
=> SetSelection( -1 );
|
=> SetSelection( -1 );
|
||||||
|
|
||||||
public void SelectModByName( string name )
|
public void SelectModByName( string name )
|
||||||
{
|
{
|
||||||
var idx = Mods?.ModSettings?.FindIndex( mod => mod.Mod.Meta.Name == name ) ?? -1;
|
var idx = Mods?.FindIndex( mod => mod.Data.Meta.Name == name ) ?? -1;
|
||||||
SetSelection( idx );
|
SetSelection( idx );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SelectModByDir( string name )
|
public void SelectModByDir( string name )
|
||||||
{
|
{
|
||||||
var idx = Mods?.ModSettings?.FindIndex( mod => mod.FolderName == name ) ?? -1;
|
var idx = Mods?.FindIndex( mod => mod.Data.BasePath.Name == name ) ?? -1;
|
||||||
SetSelection( idx );
|
SetSelection( idx );
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetCurrentModMetaFile()
|
public void ReloadCurrentMod( bool recomputeMeta = false )
|
||||||
=> Mod == null ? "" : Path.Combine( Mod.Mod.ModBasePath.FullName, "meta.json" );
|
|
||||||
|
|
||||||
public void ReloadCurrentMod()
|
|
||||||
{
|
|
||||||
var metaPath = GetCurrentModMetaFile();
|
|
||||||
if( metaPath.Length > 0 && File.Exists( metaPath ) )
|
|
||||||
{
|
|
||||||
Mod!.Mod.Meta = ModMeta.LoadFromFile( metaPath ) ?? Mod.Mod.Meta;
|
|
||||||
_base._menu.InstalledTab.ModPanel.Details.ResetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
Mod!.Mod.RefreshModFiles();
|
|
||||||
Service< ModManager >.Get().CalculateEffectiveFileList();
|
|
||||||
ResetModNamesLower();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string SaveCurrentMod()
|
|
||||||
{
|
{
|
||||||
if( Mod == null )
|
if( Mod == null )
|
||||||
{
|
{
|
||||||
return "";
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var metaPath = GetCurrentModMetaFile();
|
if( _index >= 0 && _modManager.UpdateMod( Mod.Data, recomputeMeta ) )
|
||||||
if( metaPath.Length > 0 )
|
|
||||||
{
|
{
|
||||||
File.WriteAllText( metaPath, JsonConvert.SerializeObject( Mod.Mod.Meta, Formatting.Indented ) );
|
ResetModNamesLower();
|
||||||
|
SelectModByDir( Mod.Data.BasePath.Name );
|
||||||
|
_base._menu.InstalledTab.ModPanel.Details.ResetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
_base._menu.InstalledTab.ModPanel.Details.ResetState();
|
|
||||||
return metaPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SaveCurrentMod()
|
||||||
|
=> Mod?.Data.SaveMeta();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Dalamud.Plugin;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Penumbra.Hooks;
|
using Penumbra.Hooks;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.UI
|
namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
|
|
@ -13,7 +17,6 @@ namespace Penumbra.UI
|
||||||
private const string LabelRediscoverButton = "Rediscover Mods";
|
private const string LabelRediscoverButton = "Rediscover Mods";
|
||||||
private const string LabelOpenFolder = "Open Mods Folder";
|
private const string LabelOpenFolder = "Open Mods Folder";
|
||||||
private const string LabelEnabled = "Enable Mods";
|
private const string LabelEnabled = "Enable Mods";
|
||||||
private const string LabelInvertModOrder = "Invert mod load order (mods are loaded bottom up)";
|
|
||||||
private const string LabelShowAdvanced = "Show Advanced Settings";
|
private const string LabelShowAdvanced = "Show Advanced Settings";
|
||||||
private const string LabelLogLoadedFiles = "Log all loaded files";
|
private const string LabelLogLoadedFiles = "Log all loaded files";
|
||||||
private const string LabelDisableNotifications = "Disable filesystem change notifications";
|
private const string LabelDisableNotifications = "Disable filesystem change notifications";
|
||||||
|
|
@ -21,7 +24,7 @@ namespace Penumbra.UI
|
||||||
private const string LabelReloadResource = "Reload Player Resource";
|
private const string LabelReloadResource = "Reload Player Resource";
|
||||||
|
|
||||||
private readonly SettingsInterface _base;
|
private readonly SettingsInterface _base;
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private bool _configChanged;
|
private bool _configChanged;
|
||||||
|
|
||||||
public TabSettings( SettingsInterface ui )
|
public TabSettings( SettingsInterface ui )
|
||||||
|
|
@ -33,11 +36,11 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private void DrawRootFolder()
|
private void DrawRootFolder()
|
||||||
{
|
{
|
||||||
var basePath = _config.CurrentCollection;
|
var basePath = _config.ModDirectory;
|
||||||
if( ImGui.InputText( LabelRootFolder, ref basePath, 255 ) && _config.CurrentCollection != basePath )
|
if( ImGui.InputText( LabelRootFolder, ref basePath, 255 ) && _config.ModDirectory != basePath )
|
||||||
{
|
{
|
||||||
_config.CurrentCollection = basePath;
|
_config.ModDirectory = basePath;
|
||||||
_configChanged = true;
|
_configChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,7 +57,7 @@ namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
if( ImGui.Button( LabelOpenFolder ) )
|
if( ImGui.Button( LabelOpenFolder ) )
|
||||||
{
|
{
|
||||||
Process.Start( _config.CurrentCollection );
|
Process.Start( _config.ModDirectory );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,17 +72,6 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawInvertModOrderBox()
|
|
||||||
{
|
|
||||||
var invertOrder = _config.InvertModListOrder;
|
|
||||||
if( ImGui.Checkbox( LabelInvertModOrder, ref invertOrder ) )
|
|
||||||
{
|
|
||||||
_config.InvertModListOrder = invertOrder;
|
|
||||||
_base.ReloadMods();
|
|
||||||
_configChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawShowAdvancedBox()
|
private void DrawShowAdvancedBox()
|
||||||
{
|
{
|
||||||
var showAdvanced = _config.ShowAdvanced;
|
var showAdvanced = _config.ShowAdvanced;
|
||||||
|
|
@ -92,9 +84,21 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private void DrawLogLoadedFilesBox()
|
private void DrawLogLoadedFilesBox()
|
||||||
{
|
{
|
||||||
if( _base._plugin.ResourceLoader != null )
|
ImGui.Checkbox( LabelLogLoadedFiles, ref _base._plugin.ResourceLoader.LogAllFiles );
|
||||||
|
ImGui.SameLine();
|
||||||
|
var regex = _base._plugin.ResourceLoader.LogFileFilter?.ToString() ?? string.Empty;
|
||||||
|
var tmp = regex;
|
||||||
|
if( ImGui.InputTextWithHint( "##LogFilter", "Matching this Regex...", ref tmp, 64 ) && tmp != regex )
|
||||||
{
|
{
|
||||||
ImGui.Checkbox( LabelLogLoadedFiles, ref _base._plugin.ResourceLoader.LogAllFiles );
|
try
|
||||||
|
{
|
||||||
|
var newRegex = tmp.Length > 0 ? new Regex( tmp, RegexOptions.Compiled ) : null;
|
||||||
|
_base._plugin.ResourceLoader.LogFileFilter = newRegex;
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Debug( "Could not create regex:\n{Exception}", e );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,11 +131,11 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawReloadResourceButton()
|
private static void DrawReloadResourceButton()
|
||||||
{
|
{
|
||||||
if( ImGui.Button( LabelReloadResource ) )
|
if( ImGui.Button( LabelReloadResource ) )
|
||||||
{
|
{
|
||||||
Service<GameResourceManagement>.Get().ReloadPlayerResources();
|
Service< GameResourceManagement >.Get().ReloadPlayerResources();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,13 +161,10 @@ namespace Penumbra.UI
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
DrawOpenModsButton();
|
DrawOpenModsButton();
|
||||||
|
|
||||||
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
|
Custom.ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
|
||||||
DrawEnabledBox();
|
DrawEnabledBox();
|
||||||
|
|
||||||
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
|
Custom.ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
|
||||||
DrawInvertModOrderBox();
|
|
||||||
|
|
||||||
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
|
|
||||||
DrawShowAdvancedBox();
|
DrawShowAdvancedBox();
|
||||||
|
|
||||||
if( _config.ShowAdvanced )
|
if( _config.ShowAdvanced )
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.UI
|
namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
|
|
@ -14,18 +15,19 @@ namespace Penumbra.UI
|
||||||
private readonly Plugin _plugin;
|
private readonly Plugin _plugin;
|
||||||
|
|
||||||
private readonly ManageModsButton _manageModsButton;
|
private readonly ManageModsButton _manageModsButton;
|
||||||
private readonly MenuBar _menuBar;
|
private readonly MenuBar _menuBar;
|
||||||
private readonly SettingsMenu _menu;
|
private readonly SettingsMenu _menu;
|
||||||
|
|
||||||
public SettingsInterface( Plugin plugin )
|
public SettingsInterface( Plugin plugin )
|
||||||
{
|
{
|
||||||
_plugin = plugin;
|
_plugin = plugin;
|
||||||
_manageModsButton = new ManageModsButton( this );
|
_manageModsButton = new ManageModsButton( this );
|
||||||
_menuBar = new MenuBar( this );
|
_menuBar = new MenuBar( this );
|
||||||
_menu = new SettingsMenu( this );
|
_menu = new SettingsMenu( this );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FlipVisibility() => _menu.Visible = !_menu.Visible;
|
public void FlipVisibility()
|
||||||
|
=> _menu.Visible = !_menu.Visible;
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
|
|
@ -39,10 +41,10 @@ namespace Penumbra.UI
|
||||||
_menu.InstalledTab.Selector.ResetModNamesLower();
|
_menu.InstalledTab.Selector.ResetModNamesLower();
|
||||||
_menu.InstalledTab.Selector.ClearSelection();
|
_menu.InstalledTab.Selector.ClearSelection();
|
||||||
// create the directory if it doesn't exist
|
// create the directory if it doesn't exist
|
||||||
Directory.CreateDirectory( _plugin!.Configuration!.CurrentCollection );
|
Directory.CreateDirectory( _plugin!.Configuration!.ModDirectory );
|
||||||
|
|
||||||
var modManager = Service< ModManager >.Get();
|
var modManager = Service< ModManager >.Get();
|
||||||
modManager.DiscoverMods( _plugin.Configuration.CurrentCollection );
|
modManager.DiscoverMods( new DirectoryInfo( _plugin.Configuration.ModDirectory ) );
|
||||||
_menu.InstalledTab.Selector.ResetModNamesLower();
|
_menu.InstalledTab.Selector.ResetModNamesLower();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,19 @@ namespace Penumbra.UI
|
||||||
private readonly TabSettings _settingsTab;
|
private readonly TabSettings _settingsTab;
|
||||||
private readonly TabImport _importTab;
|
private readonly TabImport _importTab;
|
||||||
private readonly TabBrowser _browserTab;
|
private readonly TabBrowser _browserTab;
|
||||||
|
private readonly TabCollections _collectionsTab;
|
||||||
public readonly TabInstalled InstalledTab;
|
public readonly TabInstalled InstalledTab;
|
||||||
public readonly TabEffective EffectiveTab;
|
private readonly TabEffective _effectiveTab;
|
||||||
|
|
||||||
public SettingsMenu( SettingsInterface ui )
|
public SettingsMenu( SettingsInterface ui )
|
||||||
{
|
{
|
||||||
_base = ui;
|
_base = ui;
|
||||||
_settingsTab = new TabSettings( _base );
|
_settingsTab = new TabSettings( _base );
|
||||||
_importTab = new TabImport( _base );
|
_importTab = new TabImport( _base );
|
||||||
_browserTab = new TabBrowser();
|
_browserTab = new TabBrowser();
|
||||||
InstalledTab = new TabInstalled( _base );
|
InstalledTab = new TabInstalled( _base );
|
||||||
EffectiveTab = new TabEffective();
|
_collectionsTab = new TabCollections( InstalledTab.Selector );
|
||||||
|
_effectiveTab = new TabEffective();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|
@ -57,6 +59,7 @@ namespace Penumbra.UI
|
||||||
ImGui.BeginTabBar( PenumbraSettingsLabel );
|
ImGui.BeginTabBar( PenumbraSettingsLabel );
|
||||||
|
|
||||||
_settingsTab.Draw();
|
_settingsTab.Draw();
|
||||||
|
_collectionsTab.Draw();
|
||||||
_importTab.Draw();
|
_importTab.Draw();
|
||||||
|
|
||||||
if( !_importTab.IsImporting() )
|
if( !_importTab.IsImporting() )
|
||||||
|
|
@ -66,7 +69,7 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
if( _base._plugin!.Configuration!.ShowAdvanced )
|
if( _base._plugin!.Configuration!.ShowAdvanced )
|
||||||
{
|
{
|
||||||
EffectiveTab.Draw();
|
_effectiveTab.Draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Penumbra
|
namespace Penumbra.Util
|
||||||
{
|
{
|
||||||
public static class ArrayExtensions
|
public static class ArrayExtensions
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Penumbra.Util
|
namespace Penumbra.Util
|
||||||
{
|
{
|
||||||
|
|
@ -38,10 +36,10 @@ namespace Penumbra.Util
|
||||||
|
|
||||||
var list = new List< T >( count );
|
var list = new List< T >( count );
|
||||||
|
|
||||||
for( int i = 0; i < count; i++ )
|
for( var i = 0; i < count; i++ )
|
||||||
{
|
{
|
||||||
var offset = size * i;
|
var offset = size * i;
|
||||||
var span = new ReadOnlySpan< byte >( data, offset, size );
|
var span = new ReadOnlySpan< byte >( data, offset, size );
|
||||||
|
|
||||||
list.Add( MemoryMarshal.Read< T >( span ) );
|
list.Add( MemoryMarshal.Read< T >( span ) );
|
||||||
}
|
}
|
||||||
|
|
@ -55,12 +53,12 @@ namespace Penumbra.Util
|
||||||
var data = br.ReadBytes( size * count );
|
var data = br.ReadBytes( size * count );
|
||||||
|
|
||||||
// im a pirate arr
|
// im a pirate arr
|
||||||
var arr = new T[ count ];
|
var arr = new T[count];
|
||||||
|
|
||||||
for( int i = 0; i < count; i++ )
|
for( var i = 0; i < count; i++ )
|
||||||
{
|
{
|
||||||
var offset = size * i;
|
var offset = size * i;
|
||||||
var span = new ReadOnlySpan< byte >( data, offset, size );
|
var span = new ReadOnlySpan< byte >( data, offset, size );
|
||||||
|
|
||||||
arr[ i ] = MemoryMarshal.Read< T >( span );
|
arr[ i ] = MemoryMarshal.Read< T >( span );
|
||||||
}
|
}
|
||||||
|
|
@ -76,9 +74,7 @@ namespace Penumbra.Util
|
||||||
/// <param name="offset">The offset to read a string starting from.</param>
|
/// <param name="offset">The offset to read a string starting from.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string ReadStringOffsetData( this BinaryReader br, long offset )
|
public static string ReadStringOffsetData( this BinaryReader br, long offset )
|
||||||
{
|
=> Encoding.UTF8.GetString( ReadRawOffsetData( br, offset ) );
|
||||||
return Encoding.UTF8.GetString( ReadRawOffsetData( br, offset ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Moves the BinaryReader position to offset, reads raw bytes until a null byte, then
|
/// Moves the BinaryReader position to offset, reads raw bytes until a null byte, then
|
||||||
|
|
@ -108,7 +104,8 @@ namespace Penumbra.Util
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Seeks this BinaryReader's position to the given offset. Syntactic sugar.
|
/// Seeks this BinaryReader's position to the given offset. Syntactic sugar.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void Seek( this BinaryReader br, long offset ) {
|
public static void Seek( this BinaryReader br, long offset )
|
||||||
|
{
|
||||||
br.BaseStream.Position = offset;
|
br.BaseStream.Position = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ namespace Penumbra.Util
|
||||||
return k;
|
return k;
|
||||||
} ).ToArray();
|
} ).ToArray();
|
||||||
|
|
||||||
public uint Checksum => ~_crc32;
|
public uint Checksum
|
||||||
|
=> ~_crc32;
|
||||||
|
|
||||||
private uint _crc32 = 0xFFFFFFFF;
|
private uint _crc32 = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
|
@ -50,8 +51,7 @@ namespace Penumbra.Util
|
||||||
[MethodImpl( MethodImplOptions.AggressiveInlining )]
|
[MethodImpl( MethodImplOptions.AggressiveInlining )]
|
||||||
public void Update( byte b )
|
public void Update( byte b )
|
||||||
{
|
{
|
||||||
_crc32 = CrcArray[ ( _crc32 ^ b ) & 0xFF ] ^
|
_crc32 = CrcArray[ ( _crc32 ^ b ) & 0xFF ] ^ ( ( _crc32 >> 8 ) & 0x00FFFFFF );
|
||||||
( ( _crc32 >> 8 ) & 0x00FFFFFF );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
namespace Penumbra
|
namespace Penumbra.Util
|
||||||
{
|
{
|
||||||
public static class DialogExtensions
|
public static class DialogExtensions
|
||||||
{
|
{
|
||||||
|
|
@ -38,7 +38,8 @@ namespace Penumbra
|
||||||
{
|
{
|
||||||
public IntPtr Handle { get; set; }
|
public IntPtr Handle { get; set; }
|
||||||
|
|
||||||
public DialogHandle( IntPtr handle ) => Handle = handle;
|
public DialogHandle( IntPtr handle )
|
||||||
|
=> Handle = handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HiddenForm : Form
|
public class HiddenForm : Form
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Penumbra.Util
|
namespace Penumbra.Util
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ namespace Penumbra.Util
|
||||||
{
|
{
|
||||||
if( value != null )
|
if( value != null )
|
||||||
{
|
{
|
||||||
var v = ( GamePath) value;
|
var v = ( GamePath )value;
|
||||||
serializer.Serialize( writer, v.ToString() );
|
serializer.Serialize( writer, v.ToString() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -153,8 +153,8 @@ namespace Penumbra.Util
|
||||||
totalBlocks += mdlBlock.IndexBufferBlockNum[ i ];
|
totalBlocks += mdlBlock.IndexBufferBlockNum[ i ];
|
||||||
}
|
}
|
||||||
|
|
||||||
var compressedBlockSizes = Reader.ReadStructures< ushort >( totalBlocks );
|
var compressedBlockSizes = Reader.ReadStructures< ushort >( totalBlocks );
|
||||||
var currentBlock = 0;
|
var currentBlock = 0;
|
||||||
var vertexDataOffsets = new int[3];
|
var vertexDataOffsets = new int[3];
|
||||||
var indexDataOffsets = new int[3];
|
var indexDataOffsets = new int[3];
|
||||||
var vertexBufferSizes = new int[3];
|
var vertexBufferSizes = new int[3];
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Penumbra
|
namespace Penumbra.Util
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Basic service locator
|
/// Basic service locator
|
||||||
|
|
@ -11,8 +11,7 @@ namespace Penumbra
|
||||||
private static T? _object;
|
private static T? _object;
|
||||||
|
|
||||||
static Service()
|
static Service()
|
||||||
{
|
{ }
|
||||||
}
|
|
||||||
|
|
||||||
public static void Set( T obj )
|
public static void Set( T obj )
|
||||||
{
|
{
|
||||||
|
|
@ -7,7 +7,8 @@ namespace Penumbra.Util
|
||||||
{
|
{
|
||||||
public class SingleOrArrayConverter< T > : JsonConverter
|
public class SingleOrArrayConverter< T > : JsonConverter
|
||||||
{
|
{
|
||||||
public override bool CanConvert( Type objectType ) => objectType == typeof( HashSet< T > );
|
public override bool CanConvert( Type objectType )
|
||||||
|
=> objectType == typeof( HashSet< T > );
|
||||||
|
|
||||||
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
|
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
|
||||||
{
|
{
|
||||||
|
|
@ -15,7 +16,7 @@ namespace Penumbra.Util
|
||||||
|
|
||||||
if( token.Type == JTokenType.Array )
|
if( token.Type == JTokenType.Array )
|
||||||
{
|
{
|
||||||
return token.ToObject< HashSet< T > >() ?? new HashSet<T>();
|
return token.ToObject< HashSet< T > >() ?? new HashSet< T >();
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmp = token.ToObject< T >();
|
var tmp = token.ToObject< T >();
|
||||||
|
|
@ -24,7 +25,8 @@ namespace Penumbra.Util
|
||||||
: new HashSet< T >();
|
: new HashSet< T >();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanWrite => true;
|
public override bool CanWrite
|
||||||
|
=> true;
|
||||||
|
|
||||||
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
|
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
|
||||||
{
|
{
|
||||||
|
|
@ -37,6 +39,7 @@ namespace Penumbra.Util
|
||||||
serializer.Serialize( writer, val?.ToString() );
|
serializer.Serialize( writer, val?.ToString() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteEndArray();
|
writer.WriteEndArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Penumbra
|
namespace Penumbra.Util
|
||||||
{
|
{
|
||||||
public static class StringPathExtensions
|
public static class StringPathExtensions
|
||||||
{
|
{
|
||||||
private static readonly char[] _invalid = Path.GetInvalidFileNameChars();
|
private static readonly char[] Invalid = Path.GetInvalidFileNameChars();
|
||||||
|
|
||||||
public static string ReplaceInvalidPathSymbols( this string s, string replacement = "_" )
|
public static string ReplaceInvalidPathSymbols( this string s, string replacement = "_" )
|
||||||
=> string.Join( replacement, s.Split( _invalid ) );
|
=> string.Join( replacement, s.Split( Invalid ) );
|
||||||
|
|
||||||
public static string RemoveInvalidPathSymbols( this string s )
|
public static string RemoveInvalidPathSymbols( this string s )
|
||||||
=> string.Concat( s.Split( _invalid ) );
|
=> string.Concat( s.Split( Invalid ) );
|
||||||
|
|
||||||
public static string RemoveNonAsciiSymbols( this string s, string replacement = "_" )
|
public static string RemoveNonAsciiSymbols( this string s, string replacement = "_" )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue