mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 20:24:17 +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 EmbedIO;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.WebApi;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.API
|
||||
{
|
||||
|
|
@ -10,37 +12,38 @@ namespace Penumbra.API
|
|||
{
|
||||
private readonly Plugin _plugin;
|
||||
|
||||
public ModsController( Plugin plugin ) => _plugin = plugin;
|
||||
public ModsController( Plugin plugin )
|
||||
=> _plugin = plugin;
|
||||
|
||||
[Route( HttpVerbs.Get, "/mods" )]
|
||||
public object? GetMods()
|
||||
{
|
||||
var modManager = Service< ModManager >.Get();
|
||||
return modManager.Mods?.ModSettings.Select( x => new
|
||||
return modManager.CurrentCollection.Cache?.AvailableMods.Select( x => new
|
||||
{
|
||||
x.Enabled,
|
||||
x.Priority,
|
||||
x.FolderName,
|
||||
x.Mod.Meta,
|
||||
BasePath = x.Mod.ModBasePath.FullName,
|
||||
Files = x.Mod.ModFiles.Select( fi => fi.FullName )
|
||||
} );
|
||||
x.Settings.Enabled,
|
||||
x.Settings.Priority,
|
||||
x.Data.BasePath.Name,
|
||||
x.Data.Meta,
|
||||
BasePath = x.Data.BasePath.FullName,
|
||||
Files = x.Data.Resources.ModFiles.Select( fi => fi.FullName ),
|
||||
} )
|
||||
?? null;
|
||||
}
|
||||
|
||||
[Route( HttpVerbs.Post, "/mods" )]
|
||||
public object CreateMod()
|
||||
{
|
||||
return new { };
|
||||
}
|
||||
=> new { };
|
||||
|
||||
[Route( HttpVerbs.Get, "/files" )]
|
||||
public object GetFiles()
|
||||
{
|
||||
var modManager = Service< ModManager >.Get();
|
||||
return modManager.ResolvedFiles.ToDictionary(
|
||||
o => o.Key,
|
||||
return modManager.CurrentCollection.Cache?.ResolvedFiles.ToDictionary(
|
||||
o => ( string )o.Key,
|
||||
o => o.Value.FullName
|
||||
);
|
||||
)
|
||||
?? new Dictionary< string, string >();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Configuration;
|
||||
using Dalamud.Plugin;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra
|
||||
{
|
||||
[Serializable]
|
||||
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;
|
||||
|
||||
|
|
@ -18,25 +20,39 @@ namespace Penumbra
|
|||
|
||||
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
|
||||
|
||||
[NonSerialized]
|
||||
private DalamudPluginInterface? _pluginInterface;
|
||||
|
||||
public void Initialize( DalamudPluginInterface pluginInterface )
|
||||
public static Configuration Load( DalamudPluginInterface pi )
|
||||
{
|
||||
_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()
|
||||
{
|
||||
_pluginInterface?.SavePluginConfig( this );
|
||||
}
|
||||
=> Save( Service< DalamudPluginInterface >.Get() );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Penumbra.Game
|
||||
namespace Penumbra.Game.Enums
|
||||
{
|
||||
public enum BodySlot : byte
|
||||
{
|
||||
|
|
@ -10,7 +10,7 @@ namespace Penumbra.Game
|
|||
Face,
|
||||
Tail,
|
||||
Body,
|
||||
Zear
|
||||
Zear,
|
||||
}
|
||||
|
||||
public static class BodySlotEnumExtension
|
||||
|
|
@ -24,7 +24,7 @@ namespace Penumbra.Game
|
|||
BodySlot.Hair => "hair",
|
||||
BodySlot.Body => "body",
|
||||
BodySlot.Tail => "tail",
|
||||
_ => throw new InvalidEnumArgumentException()
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ namespace Penumbra.Game
|
|||
{ BodySlot.Face.ToSuffix(), BodySlot.Face },
|
||||
{ BodySlot.Hair.ToSuffix(), BodySlot.Hair },
|
||||
{ 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.ComponentModel;
|
||||
|
||||
namespace Penumbra.Game
|
||||
namespace Penumbra.Game.Enums
|
||||
{
|
||||
public enum CustomizationType : byte
|
||||
{
|
||||
|
|
@ -15,7 +15,7 @@ namespace Penumbra.Game
|
|||
DecalFace,
|
||||
DecalEquip,
|
||||
Skin,
|
||||
Etc
|
||||
Etc,
|
||||
}
|
||||
|
||||
public static class CustomizationTypeEnumExtension
|
||||
|
|
@ -30,7 +30,7 @@ namespace Penumbra.Game
|
|||
CustomizationType.Hair => "hir",
|
||||
CustomizationType.Tail => "til",
|
||||
CustomizationType.Etc => "etc",
|
||||
_ => throw new InvalidEnumArgumentException()
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ namespace Penumbra.Game
|
|||
{ CustomizationType.Accessory.ToSuffix(), CustomizationType.Accessory },
|
||||
{ CustomizationType.Hair.ToSuffix(), CustomizationType.Hair },
|
||||
{ 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.ComponentModel;
|
||||
|
||||
namespace Penumbra.Game
|
||||
namespace Penumbra.Game.Enums
|
||||
{
|
||||
public enum EquipSlot : byte
|
||||
{
|
||||
|
|
@ -27,7 +27,7 @@ namespace Penumbra.Game
|
|||
FullBody = 19,
|
||||
BodyHands = 20,
|
||||
BodyLegsFeet = 21,
|
||||
All = 22
|
||||
All = 22,
|
||||
}
|
||||
|
||||
public static class EquipSlotEnumExtension
|
||||
|
|
@ -46,7 +46,7 @@ namespace Penumbra.Game
|
|||
EquipSlot.RingR => "rir",
|
||||
EquipSlot.RingL => "ril",
|
||||
EquipSlot.Wrists => "wrs",
|
||||
_ => throw new InvalidEnumArgumentException()
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ namespace Penumbra.Game
|
|||
EquipSlot.Legs => true,
|
||||
EquipSlot.Feet => true,
|
||||
EquipSlot.Body => true,
|
||||
_ => false
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ namespace Penumbra.Game
|
|||
EquipSlot.RingR => true,
|
||||
EquipSlot.RingL => true,
|
||||
EquipSlot.Wrists => true,
|
||||
_ => false
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ namespace Penumbra.Game
|
|||
{ EquipSlot.Neck.ToSuffix(), EquipSlot.Neck },
|
||||
{ EquipSlot.RingR.ToSuffix(), EquipSlot.RingR },
|
||||
{ EquipSlot.RingL.ToSuffix(), EquipSlot.RingL },
|
||||
{ EquipSlot.Wrists.ToSuffix(), EquipSlot.Wrists }
|
||||
{ EquipSlot.Wrists.ToSuffix(), EquipSlot.Wrists },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Penumbra.Game
|
||||
namespace Penumbra.Game.Enums
|
||||
{
|
||||
public enum FileType : byte
|
||||
{
|
||||
|
|
@ -16,7 +16,7 @@ namespace Penumbra.Game
|
|||
Model,
|
||||
Shader,
|
||||
Font,
|
||||
Environment
|
||||
Environment,
|
||||
}
|
||||
|
||||
public static partial class GameData
|
||||
|
|
@ -39,7 +39,7 @@ namespace Penumbra.Game
|
|||
{ ".shpk", FileType.Shader },
|
||||
{ ".shcd", FileType.Shader },
|
||||
{ ".fdt", FileType.Font },
|
||||
{ ".envb", FileType.Environment }
|
||||
{ ".envb", FileType.Environment },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Penumbra.Game
|
||||
namespace Penumbra.Game.Enums
|
||||
{
|
||||
public enum ObjectType : byte
|
||||
{
|
||||
|
|
@ -16,6 +16,6 @@ namespace Penumbra.Game
|
|||
Equipment,
|
||||
Character,
|
||||
Weapon,
|
||||
Font
|
||||
Font,
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Penumbra.Game
|
||||
namespace Penumbra.Game.Enums
|
||||
{
|
||||
public enum Gender : byte
|
||||
{
|
||||
|
|
@ -10,7 +10,7 @@ namespace Penumbra.Game
|
|||
Male,
|
||||
Female,
|
||||
MaleNpc,
|
||||
FemaleNpc
|
||||
FemaleNpc,
|
||||
}
|
||||
|
||||
public enum Race : byte
|
||||
|
|
@ -24,7 +24,7 @@ namespace Penumbra.Game
|
|||
Roegadyn,
|
||||
AuRa,
|
||||
Hrothgar,
|
||||
Viera
|
||||
Viera,
|
||||
}
|
||||
|
||||
public enum GenderRace : ushort
|
||||
|
|
@ -63,7 +63,7 @@ namespace Penumbra.Game
|
|||
VieraFemale = 1801,
|
||||
VieraFemaleNpc = 1804,
|
||||
UnknownMaleNpc = 9104,
|
||||
UnknownFemaleNpc = 9204
|
||||
UnknownFemaleNpc = 9204,
|
||||
}
|
||||
|
||||
public static class RaceEnumExtensions
|
||||
|
|
@ -118,7 +118,7 @@ namespace Penumbra.Game
|
|||
GenderRace.VieraFemaleNpc => ( Gender.FemaleNpc, Race.Viera ),
|
||||
GenderRace.UnknownMaleNpc => ( Gender.MaleNpc, 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.UnknownMaleNpc => "9104",
|
||||
GenderRace.UnknownFemaleNpc => "9204",
|
||||
_ => throw new InvalidEnumArgumentException()
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -208,7 +208,7 @@ namespace Penumbra.Game
|
|||
"1804" => GenderRace.VieraFemaleNpc,
|
||||
"9104" => GenderRace.UnknownMaleNpc,
|
||||
"9204" => GenderRace.UnknownFemaleNpc,
|
||||
_ => throw new KeyNotFoundException()
|
||||
_ => throw new KeyNotFoundException(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -233,7 +233,7 @@ namespace Penumbra.Game
|
|||
Race.Roegadyn => GenderRace.RoegadynMale,
|
||||
Race.AuRa => GenderRace.AuRaMale,
|
||||
Race.Hrothgar => GenderRace.HrothgarMale,
|
||||
_ => GenderRace.Unknown
|
||||
_ => GenderRace.Unknown,
|
||||
},
|
||||
Gender.MaleNpc => race switch
|
||||
{
|
||||
|
|
@ -245,7 +245,7 @@ namespace Penumbra.Game
|
|||
Race.Roegadyn => GenderRace.RoegadynMaleNpc,
|
||||
Race.AuRa => GenderRace.AuRaMaleNpc,
|
||||
Race.Hrothgar => GenderRace.HrothgarMaleNpc,
|
||||
_ => GenderRace.Unknown
|
||||
_ => GenderRace.Unknown,
|
||||
},
|
||||
Gender.Female => race switch
|
||||
{
|
||||
|
|
@ -257,7 +257,7 @@ namespace Penumbra.Game
|
|||
Race.Roegadyn => GenderRace.RoegadynFemale,
|
||||
Race.AuRa => GenderRace.AuRaFemale,
|
||||
Race.Viera => GenderRace.VieraFemale,
|
||||
_ => GenderRace.Unknown
|
||||
_ => GenderRace.Unknown,
|
||||
},
|
||||
Gender.FemaleNpc => race switch
|
||||
{
|
||||
|
|
@ -269,9 +269,9 @@ namespace Penumbra.Game
|
|||
Race.Roegadyn => GenderRace.RoegadynFemaleNpc,
|
||||
Race.AuRa => GenderRace.AuRaFemaleNpc,
|
||||
Race.Viera => GenderRace.VieraFemaleNpc,
|
||||
_ => GenderRace.Unknown
|
||||
_ => GenderRace.Unknown,
|
||||
},
|
||||
_ => GenderRace.Unknown
|
||||
_ => GenderRace.Unknown,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Game.Enums;
|
||||
using Penumbra.Meta;
|
||||
|
||||
namespace Penumbra.Game
|
||||
{
|
||||
|
|
@ -46,7 +47,7 @@ namespace Penumbra.Game
|
|||
|
||||
RingL1 = 0b0100000000,
|
||||
RingL2 = 0b1000000000,
|
||||
RingLMask = 0b1100000000
|
||||
RingLMask = 0b1100000000,
|
||||
}
|
||||
|
||||
public static class Eqdp
|
||||
|
|
@ -65,7 +66,7 @@ namespace Penumbra.Game
|
|||
EquipSlot.Wrists => 4,
|
||||
EquipSlot.RingR => 6,
|
||||
EquipSlot.RingL => 8,
|
||||
_ => throw new InvalidEnumArgumentException()
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +101,7 @@ namespace Penumbra.Game
|
|||
EquipSlot.Wrists => EqdpEntry.WristsMask,
|
||||
EquipSlot.RingR => EqdpEntry.RingRMask,
|
||||
EquipSlot.RingL => EqdpEntry.RingLMask,
|
||||
_ => 0
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Game.Enums;
|
||||
using Penumbra.Meta;
|
||||
|
||||
namespace Penumbra.Game
|
||||
{
|
||||
|
|
@ -79,7 +80,7 @@ namespace Penumbra.Game
|
|||
_61 = 0x20_00_00ul << 40,
|
||||
_62 = 0x40_00_00ul << 40,
|
||||
_63 = 0x80_00_00ul << 40,
|
||||
HeadMask = 0xFF_FF_FFul << 40
|
||||
HeadMask = 0xFF_FF_FFul << 40,
|
||||
}
|
||||
|
||||
public static class Eqp
|
||||
|
|
@ -93,7 +94,7 @@ namespace Penumbra.Game
|
|||
EquipSlot.Hands => ( 1, 3 ),
|
||||
EquipSlot.Feet => ( 1, 4 ),
|
||||
EquipSlot.Head => ( 3, 5 ),
|
||||
_ => throw new InvalidEnumArgumentException()
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +124,7 @@ namespace Penumbra.Game
|
|||
EquipSlot.Legs => EqpEntry.LegsMask,
|
||||
EquipSlot.Feet => EqpEntry.FeetMask,
|
||||
EquipSlot.Hands => EqpEntry.HandsMask,
|
||||
_ => 0
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud;
|
||||
using Penumbra.Game.Enums;
|
||||
|
||||
namespace Penumbra.Game
|
||||
{
|
||||
|
|
@ -16,7 +17,7 @@ namespace Penumbra.Game
|
|||
PrimaryId = setId,
|
||||
GenderRace = gr,
|
||||
Variant = variant,
|
||||
EquipSlot = slot
|
||||
EquipSlot = slot,
|
||||
};
|
||||
|
||||
public static GameObjectInfo Weapon( FileType type, ushort setId, ushort weaponId, byte variant = 0 )
|
||||
|
|
@ -26,7 +27,7 @@ namespace Penumbra.Game
|
|||
ObjectType = ObjectType.Weapon,
|
||||
PrimaryId = setId,
|
||||
SecondaryId = weaponId,
|
||||
Variant = variant
|
||||
Variant = variant,
|
||||
};
|
||||
|
||||
public static GameObjectInfo Customization( FileType type, CustomizationType customizationType, ushort id = 0
|
||||
|
|
@ -39,7 +40,7 @@ namespace Penumbra.Game
|
|||
GenderRace = gr,
|
||||
BodySlot = bodySlot,
|
||||
Variant = variant,
|
||||
CustomizationType = customizationType
|
||||
CustomizationType = customizationType,
|
||||
};
|
||||
|
||||
public static GameObjectInfo Monster( FileType type, ushort monsterId, ushort bodyId, byte variant = 0 )
|
||||
|
|
@ -49,7 +50,7 @@ namespace Penumbra.Game
|
|||
ObjectType = ObjectType.Monster,
|
||||
PrimaryId = monsterId,
|
||||
SecondaryId = bodyId,
|
||||
Variant = variant
|
||||
Variant = variant,
|
||||
};
|
||||
|
||||
public static GameObjectInfo DemiHuman( FileType type, ushort demiHumanId, ushort bodyId, byte variant = 0,
|
||||
|
|
@ -61,7 +62,7 @@ namespace Penumbra.Game
|
|||
PrimaryId = demiHumanId,
|
||||
SecondaryId = bodyId,
|
||||
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 )
|
||||
|
|
@ -74,7 +75,7 @@ namespace Penumbra.Game
|
|||
MapC3 = c3,
|
||||
MapC4 = c4,
|
||||
MapSuffix = suffix,
|
||||
Variant = variant
|
||||
Variant = variant,
|
||||
};
|
||||
|
||||
public static GameObjectInfo Icon( FileType type, uint iconId, bool hq, ClientLanguage lang = ClientLanguage.English )
|
||||
|
|
@ -84,7 +85,7 @@ namespace Penumbra.Game
|
|||
ObjectType = ObjectType.Map,
|
||||
IconId = iconId,
|
||||
IconHq = hq,
|
||||
Language = lang
|
||||
Language = lang,
|
||||
};
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Plugin;
|
||||
using Penumbra.Game.Enums;
|
||||
using Penumbra.Util;
|
||||
|
||||
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.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.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
|
||||
|
||||
|
|
@ -90,7 +91,7 @@ namespace Penumbra.Game
|
|||
DemiHumanFolder => ObjectType.DemiHuman,
|
||||
MonsterFolder => ObjectType.Monster,
|
||||
CommonFolder => ObjectType.Character,
|
||||
_ => ObjectType.Unknown
|
||||
_ => ObjectType.Unknown,
|
||||
},
|
||||
UiFolder => folders[ 1 ] switch
|
||||
{
|
||||
|
|
@ -98,22 +99,22 @@ namespace Penumbra.Game
|
|||
LoadingFolder => ObjectType.LoadingScreen,
|
||||
MapFolder => ObjectType.Map,
|
||||
InterfaceFolder => ObjectType.Interface,
|
||||
_ => ObjectType.Unknown
|
||||
_ => ObjectType.Unknown,
|
||||
},
|
||||
CommonFolder => folders[ 1 ] switch
|
||||
{
|
||||
FontFolder => ObjectType.Font,
|
||||
_ => ObjectType.Unknown
|
||||
_ => ObjectType.Unknown,
|
||||
},
|
||||
HousingFolder => ObjectType.Housing,
|
||||
WorldFolder1 => folders[ 1 ] switch
|
||||
{
|
||||
HousingFolder => ObjectType.Housing,
|
||||
_ => ObjectType.World
|
||||
_ => ObjectType.World,
|
||||
},
|
||||
WorldFolder2 => ObjectType.World,
|
||||
VfxFolder => ObjectType.Vfx,
|
||||
_ => ObjectType.Unknown
|
||||
_ => ObjectType.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -297,7 +298,7 @@ namespace Penumbra.Game
|
|||
"ja" => Dalamud.ClientLanguage.Japanese,
|
||||
"de" => Dalamud.ClientLanguage.German,
|
||||
"fr" => Dalamud.ClientLanguage.French,
|
||||
_ => Dalamud.ClientLanguage.English
|
||||
_ => Dalamud.ClientLanguage.English,
|
||||
};
|
||||
return GameObjectInfo.Icon( fileType, id, hq, language );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using System.IO;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Meta;
|
||||
|
||||
namespace Penumbra.Game
|
||||
{
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ namespace Penumbra.Game
|
|||
RedrawAll( actors );
|
||||
}
|
||||
|
||||
foreach( var actor in actors.Where( A => A.Name == name ) )
|
||||
foreach( var actor in actors.Where( a => a.Name == name ) )
|
||||
{
|
||||
Redraw( actor );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,11 +30,14 @@ namespace Penumbra.Hooks
|
|||
|
||||
// Object addresses
|
||||
private readonly IntPtr _playerResourceManagerAddress;
|
||||
public IntPtr PlayerResourceManagerPtr => Marshal.ReadIntPtr( _playerResourceManagerAddress );
|
||||
|
||||
public IntPtr PlayerResourceManagerPtr
|
||||
=> Marshal.ReadIntPtr( _playerResourceManagerAddress );
|
||||
|
||||
private readonly IntPtr _characterResourceManagerAddress;
|
||||
|
||||
public unsafe CharacterResourceManager* CharacterResourceManagerPtr =>
|
||||
( CharacterResourceManager* )Marshal.ReadIntPtr( _characterResourceManagerAddress ).ToPointer();
|
||||
public unsafe CharacterResourceManager* CharacterResourceManagerPtr
|
||||
=> ( CharacterResourceManager* )Marshal.ReadIntPtr( _characterResourceManagerAddress ).ToPointer();
|
||||
|
||||
public GameResourceManagement( DalamudPluginInterface pluginInterface )
|
||||
{
|
||||
|
|
@ -70,7 +73,7 @@ namespace Penumbra.Hooks
|
|||
public unsafe string ResourceToPath( byte* resource )
|
||||
=> Marshal.PtrToStringAnsi( new IntPtr( *( char** )( resource + 9 * 8 ) ) )!;
|
||||
|
||||
public unsafe void ReloadCharacterResources()
|
||||
private unsafe void ReloadCharacterResources()
|
||||
{
|
||||
var oldResources = new IntPtr[NumResources];
|
||||
var resources = new IntPtr( &CharacterResourceManagerPtr->Resources );
|
||||
|
|
@ -88,9 +91,9 @@ namespace Penumbra.Hooks
|
|||
continue;
|
||||
}
|
||||
|
||||
PluginLog.Debug( "Freeing " +
|
||||
$"{ResourceToPath( ( byte* )oldResources[ i ].ToPointer() )}, replaced with " +
|
||||
$"{ResourceToPath( ( byte* )pResources[ i ] )}" );
|
||||
PluginLog.Debug( "Freeing "
|
||||
+ $"{ResourceToPath( ( byte* )oldResources[ i ].ToPointer() )}, replaced with "
|
||||
+ $"{ResourceToPath( ( byte* )pResources[ 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.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Plugin;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Structs;
|
||||
|
|
@ -30,12 +31,12 @@ namespace Penumbra.Hooks
|
|||
public unsafe delegate byte ReadSqpackPrototype( IntPtr pFileHandler, SeFileDescriptor* pFileDesc, int priority, bool isSync );
|
||||
|
||||
[Function( CallingConventions.Microsoft )]
|
||||
public unsafe delegate void* GetResourceSyncPrototype( IntPtr pFileManager, uint* pCategoryId, char* pResourceType,
|
||||
uint* pResourceHash, char* pPath, void* pUnknown );
|
||||
public unsafe delegate void* GetResourceSyncPrototype( IntPtr pFileManager, uint* pCategoryId, char* pResourceType
|
||||
, uint* pResourceHash, char* pPath, void* pUnknown );
|
||||
|
||||
[Function( CallingConventions.Microsoft )]
|
||||
public unsafe delegate void* GetResourceAsyncPrototype( IntPtr pFileManager, uint* pCategoryId, char* pResourceType,
|
||||
uint* pResourceHash, char* pPath, void* pUnknown, bool isUnknown );
|
||||
public unsafe delegate void* GetResourceAsyncPrototype( IntPtr pFileManager, uint* pCategoryId, char* pResourceType
|
||||
, uint* pResourceHash, char* pPath, void* pUnknown, bool isUnknown );
|
||||
|
||||
// Hooks
|
||||
public IHook< GetResourceSyncPrototype >? GetResourceSyncHook { get; private set; }
|
||||
|
|
@ -47,6 +48,7 @@ namespace Penumbra.Hooks
|
|||
|
||||
|
||||
public bool LogAllFiles = false;
|
||||
public Regex? LogFileFilter = null;
|
||||
|
||||
|
||||
public ResourceLoader( Plugin plugin )
|
||||
|
|
@ -87,7 +89,8 @@ namespace Penumbra.Hooks
|
|||
uint* pResourceHash,
|
||||
char* pPath,
|
||||
void* pUnknown
|
||||
) => GetResourceHandler( true, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, false );
|
||||
)
|
||||
=> GetResourceHandler( true, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, false );
|
||||
|
||||
private unsafe void* GetResourceAsyncHandler(
|
||||
IntPtr pFileManager,
|
||||
|
|
@ -97,7 +100,8 @@ namespace Penumbra.Hooks
|
|||
char* pPath,
|
||||
void* pUnknown,
|
||||
bool isUnknown
|
||||
) => GetResourceHandler( false, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, isUnknown );
|
||||
)
|
||||
=> GetResourceHandler( false, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, isUnknown );
|
||||
|
||||
private unsafe void* CallOriginalHandler(
|
||||
bool isSync,
|
||||
|
|
@ -141,24 +145,29 @@ namespace Penumbra.Hooks
|
|||
bool isUnknown
|
||||
)
|
||||
{
|
||||
string file;
|
||||
var modManager = Service< ModManager >.Get();
|
||||
|
||||
if( !Plugin!.Configuration!.IsEnabled || modManager == null )
|
||||
{
|
||||
if( LogAllFiles )
|
||||
{
|
||||
PluginLog.Log( "[GetResourceHandler] {0}",
|
||||
GamePath.GenerateUncheckedLower( Marshal.PtrToStringAnsi( new IntPtr( pPath ) )! ) );
|
||||
file = 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 );
|
||||
}
|
||||
|
||||
var gameFsPath = GamePath.GenerateUncheckedLower( Marshal.PtrToStringAnsi( new IntPtr( pPath ) )! );
|
||||
var replacementPath = modManager.ResolveSwappedOrReplacementFilePath( gameFsPath );
|
||||
if( LogAllFiles )
|
||||
file = Marshal.PtrToStringAnsi( new IntPtr( pPath ) )!;
|
||||
var gameFsPath = GamePath.GenerateUncheckedLower( file );
|
||||
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 :(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
WritingPackToDisk,
|
||||
ExtractingModFiles,
|
||||
Done
|
||||
Done,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Lumina.Data;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Importer
|
||||
{
|
||||
public class MagicTempFileStreamManagerAndDeleterFuckery : PenumbraSqPackStream, IDisposable
|
||||
public class MagicTempFileStreamManagerAndDeleter : PenumbraSqPackStream, IDisposable
|
||||
{
|
||||
private readonly FileStream _fileStream;
|
||||
|
||||
public MagicTempFileStreamManagerAndDeleterFuckery( FileStream stream ) : base( stream ) => _fileStream = stream;
|
||||
public MagicTempFileStreamManagerAndDeleter( FileStream stream )
|
||||
: base( stream )
|
||||
=> _fileStream = stream;
|
||||
|
||||
public new void Dispose()
|
||||
{
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using Penumbra.Models;
|
||||
using Penumbra.Structs;
|
||||
|
||||
namespace Penumbra.Importer.Models
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ using Dalamud.Plugin;
|
|||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.Importer.Models;
|
||||
using Penumbra.Models;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Structs;
|
||||
using Penumbra.Util;
|
||||
using FileMode = System.IO.FileMode;
|
||||
|
||||
namespace Penumbra.Importer
|
||||
{
|
||||
|
|
@ -46,6 +48,12 @@ namespace Penumbra.Importer
|
|||
_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 )
|
||||
{
|
||||
CurrentModPack = modPackFile.Name;
|
||||
|
|
@ -94,7 +102,7 @@ namespace Penumbra.Importer
|
|||
WriteZipEntryToTempFile( s );
|
||||
|
||||
var fs = new FileStream( _resolvedTempFilePath, FileMode.Open );
|
||||
return new MagicTempFileStreamManagerAndDeleterFuckery( fs );
|
||||
return new MagicTempFileStreamManagerAndDeleter( fs );
|
||||
}
|
||||
|
||||
private void VerifyVersionAndImport( FileInfo modPackFile )
|
||||
|
|
@ -187,13 +195,11 @@ namespace Penumbra.Importer
|
|||
|
||||
public static DirectoryInfo CreateModFolder( DirectoryInfo outDirectory, string modListName )
|
||||
{
|
||||
var correctedPath = Path.Combine( outDirectory.FullName,
|
||||
Path.GetFileName( modListName ).RemoveInvalidPathSymbols().RemoveNonAsciiSymbols() );
|
||||
var newModFolder = new DirectoryInfo( correctedPath );
|
||||
var newModFolder = NewOptionDirectory( outDirectory, Path.GetFileName( modListName ) );
|
||||
var i = 2;
|
||||
while( newModFolder.Exists && i < 12 )
|
||||
{
|
||||
newModFolder = new DirectoryInfo( correctedPath + $" ({i++})" );
|
||||
newModFolder = new DirectoryInfo( newModFolder.FullName + $" ({i++})" );
|
||||
}
|
||||
|
||||
if( newModFolder.Exists )
|
||||
|
|
@ -272,7 +278,7 @@ namespace Penumbra.Importer
|
|||
|
||||
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 )
|
||||
{
|
||||
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 ) )
|
||||
{
|
||||
var optionFolder = new DirectoryInfo( Path.Combine( groupFolder.FullName, option.Name!.ReplaceInvalidPathSymbols().RemoveNonAsciiSymbols() ) );
|
||||
var optionFolder = NewOptionDirectory( groupFolder, option.Name! );
|
||||
ExtractSimpleModList( optionFolder, option.ModsJsons!, modData );
|
||||
}
|
||||
|
||||
|
|
@ -311,7 +317,7 @@ namespace Penumbra.Importer
|
|||
OptionDesc = string.IsNullOrEmpty( opt.Description ) ? "" : opt.Description!,
|
||||
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 )
|
||||
{
|
||||
foreach( var file in optDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ using System.Text.RegularExpressions;
|
|||
using Dalamud.Plugin;
|
||||
using Lumina.Data.Files;
|
||||
using Penumbra.Game;
|
||||
using Penumbra.MetaData;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Game.Enums;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Util;
|
||||
using GameData = Penumbra.Game.Enums.GameData;
|
||||
|
||||
namespace Penumbra.Importer
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using System.Linq;
|
|||
using Lumina.Data;
|
||||
using Penumbra.Game;
|
||||
|
||||
namespace Penumbra.MetaData
|
||||
namespace Penumbra.Meta.Files
|
||||
{
|
||||
// EQDP file structure:
|
||||
// [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()
|
||||
|
|
@ -49,8 +50,11 @@ namespace Penumbra.MetaData
|
|||
private ushort ExpandedBlockCount { get; set; }
|
||||
private EqdpEntry[]?[] Blocks { get; }
|
||||
|
||||
private int BlockIdx( ushort id ) => ( ushort )( id / BlockSize );
|
||||
private int SubIdx( ushort id ) => ( ushort )( id % BlockSize );
|
||||
private int BlockIdx( ushort id )
|
||||
=> ( ushort )( id / BlockSize );
|
||||
|
||||
private int SubIdx( ushort id )
|
||||
=> ( ushort )( id % BlockSize );
|
||||
|
||||
private bool ExpandBlock( int idx )
|
||||
{
|
||||
|
|
@ -4,7 +4,7 @@ using System.Linq;
|
|||
using Lumina.Data;
|
||||
using Penumbra.Game;
|
||||
|
||||
namespace Penumbra.MetaData
|
||||
namespace Penumbra.Meta.Files
|
||||
{
|
||||
// EQP Structure:
|
||||
// 64 x [Block collapsed or not bit]
|
||||
|
|
@ -28,7 +28,7 @@ namespace Penumbra.MetaData
|
|||
}
|
||||
|
||||
public byte[] WriteBytes()
|
||||
=> WriteBytes( _entries, E => ( ulong )E );
|
||||
=> WriteBytes( _entries, e => ( ulong )e );
|
||||
|
||||
public EqpFile Clone()
|
||||
=> new( this );
|
||||
|
|
@ -40,7 +40,7 @@ namespace Penumbra.MetaData
|
|||
=> GetEntry( _entries, setId, ( EqpEntry )0 );
|
||||
|
||||
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 ]
|
||||
=> ref GetTrueEntry( _entries, setId );
|
||||
|
|
@ -2,9 +2,9 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using Lumina.Data;
|
||||
using Penumbra.Game;
|
||||
using Penumbra.Game.Enums;
|
||||
|
||||
namespace Penumbra.MetaData
|
||||
namespace Penumbra.Meta.Files
|
||||
{
|
||||
// EST Structure:
|
||||
// 1x [NumEntries : UInt32]
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
using Lumina.Data;
|
||||
using Penumbra.Game;
|
||||
|
||||
namespace Penumbra.MetaData
|
||||
namespace Penumbra.Meta.Files
|
||||
{
|
||||
// GmpFiles use the same structure as Eqp Files.
|
||||
// Entries are also one ulong.
|
||||
|
|
@ -22,19 +22,19 @@ namespace Penumbra.MetaData
|
|||
}
|
||||
|
||||
public byte[] WriteBytes()
|
||||
=> WriteBytes( _entries, E => ( ulong )E );
|
||||
=> WriteBytes( _entries, e => ( ulong )e );
|
||||
|
||||
public GmpFile Clone()
|
||||
=> new( this );
|
||||
|
||||
public GmpFile( FileResource file )
|
||||
=> ReadFile( _entries, file, I => ( GmpEntry )I );
|
||||
=> ReadFile( _entries, file, i => ( GmpEntry )i );
|
||||
|
||||
public GmpEntry GetEntry( ushort setId )
|
||||
=> GetEntry( _entries, setId, ( GmpEntry )0 );
|
||||
|
||||
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 ]
|
||||
=> ref GetTrueEntry( _entries, setId );
|
||||
|
|
@ -2,12 +2,10 @@ using System;
|
|||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin;
|
||||
using Lumina.Data.Files;
|
||||
using Penumbra.Game;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Game.Enums;
|
||||
|
||||
namespace Penumbra.MetaData
|
||||
namespace Penumbra.Meta.Files
|
||||
{
|
||||
public class InvalidImcVariantException : ArgumentOutOfRangeException
|
||||
{
|
||||
|
|
@ -18,6 +16,19 @@ namespace Penumbra.MetaData
|
|||
|
||||
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 )
|
||||
=> lhs.MaterialId == rhs.MaterialId
|
||||
&& lhs.DecalId == rhs.DecalId
|
||||
|
|
@ -35,7 +46,6 @@ namespace Penumbra.MetaData
|
|||
bw.Write( variant.MaterialAnimationId );
|
||||
}
|
||||
|
||||
|
||||
public static byte[] WriteBytes( this ImcFile file )
|
||||
{
|
||||
var parts = file.PartMask == 31 ? 5 : 1;
|
||||
|
|
@ -104,10 +114,10 @@ namespace Penumbra.MetaData
|
|||
Count = file.Count,
|
||||
PartMask = file.PartMask,
|
||||
};
|
||||
var parts = file.GetParts().Select( P => new ImcFile.ImageChangeParts()
|
||||
var parts = file.GetParts().Select( p => new ImcFile.ImageChangeParts()
|
||||
{
|
||||
DefaultVariant = P.DefaultVariant,
|
||||
Variants = ( ImcFile.ImageChangeData[] )P.Variants.Clone(),
|
||||
DefaultVariant = p.DefaultVariant,
|
||||
Variants = ( ImcFile.ImageChangeData[] )p.Variants.Clone(),
|
||||
} ).ToArray();
|
||||
var prop = ret.GetType().GetField( "Parts", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance );
|
||||
prop!.SetValue( ret, parts );
|
||||
|
|
@ -4,10 +4,10 @@ using Dalamud.Plugin;
|
|||
using Lumina.Data;
|
||||
using Lumina.Data.Files;
|
||||
using Penumbra.Game;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Game.Enums;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.MetaData
|
||||
namespace Penumbra.Meta.Files
|
||||
{
|
||||
public class MetaDefaults
|
||||
{
|
||||
|
|
@ -112,14 +112,20 @@ namespace Penumbra.MetaData
|
|||
return m.Type switch
|
||||
{
|
||||
MetaType.Imc => GetDefaultImcFile( m.ImcIdentifier.ObjectType, m.ImcIdentifier.PrimaryId, m.ImcIdentifier.SecondaryId )
|
||||
?.GetValue( m ).Equal( m.ImcValue ) ?? false,
|
||||
MetaType.Gmp => GetDefaultGmpFile()?.GetEntry( m.GmpIdentifier.SetId ) == m.GmpValue,
|
||||
MetaType.Eqp => GetDefaultEqpFile()?.GetEntry( m.EqpIdentifier.SetId ).Reduce( m.EqpIdentifier.Slot ) == m.EqpValue,
|
||||
?.GetValue( m ).Equal( m.ImcValue )
|
||||
?? true,
|
||||
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 )
|
||||
.Reduce( m.EqdpIdentifier.Slot ) == m.EqdpValue,
|
||||
.Reduce( m.EqdpIdentifier.Slot )
|
||||
== m.EqdpValue,
|
||||
MetaType.Est => GetDefaultEstFile( m.EstIdentifier.ObjectType, m.EstIdentifier.EquipSlot, m.EstIdentifier.BodySlot )
|
||||
?.GetEntry( m.EstIdentifier.GenderRace, m.EstIdentifier.PrimaryId ) == m.EstValue,
|
||||
_ => throw new NotImplementedException()
|
||||
?.GetEntry( m.EstIdentifier.GenderRace, m.EstIdentifier.PrimaryId )
|
||||
== m.EstValue,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -132,7 +138,7 @@ namespace Penumbra.MetaData
|
|||
MetaType.Eqp => GetNewEqpFile(),
|
||||
MetaType.Eqdp => GetNewEqdpFile( m.EqdpIdentifier.Slot, m.EqdpIdentifier.GenderRace ),
|
||||
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 Penumbra.Game;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Game.Enums;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.MetaData
|
||||
namespace Penumbra.Meta.Files
|
||||
{
|
||||
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 Lumina.Data.Files;
|
||||
using Penumbra.Hooks;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Util;
|
||||
using Penumbra.MetaData;
|
||||
|
||||
namespace Penumbra.Mods
|
||||
namespace Penumbra.Meta
|
||||
{
|
||||
public class MetaManager : IDisposable
|
||||
{
|
||||
|
|
@ -45,9 +45,15 @@ namespace Penumbra.Mods
|
|||
private readonly GameResourceManagement _resourceManagement;
|
||||
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();
|
||||
|
||||
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 )
|
||||
{
|
||||
if( !( file?.Exists ?? false ) )
|
||||
|
|
@ -65,7 +71,7 @@ namespace Penumbra.Mods
|
|||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
private void Reset( bool reload )
|
||||
{
|
||||
foreach( var file in _currentFiles )
|
||||
{
|
||||
|
|
@ -76,11 +82,26 @@ namespace Penumbra.Mods
|
|||
_currentManipulations.Clear();
|
||||
_currentFiles.Clear();
|
||||
ClearDirectory();
|
||||
if( reload )
|
||||
{
|
||||
_resourceManagement.ReloadPlayerResources();
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
=> Reset( true );
|
||||
|
||||
public void Dispose()
|
||||
=> Reset();
|
||||
|
||||
~MetaManager()
|
||||
{
|
||||
Reset( false );
|
||||
}
|
||||
|
||||
private void ClearDirectory()
|
||||
{
|
||||
_dir.Refresh();
|
||||
if( _dir.Exists )
|
||||
{
|
||||
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;
|
||||
_default = Service< MetaDefaults >.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();
|
||||
Directory.CreateDirectory( _dir.FullName );
|
||||
}
|
||||
|
||||
public void WriteNewFiles()
|
||||
{
|
||||
Directory.CreateDirectory( _dir.FullName );
|
||||
foreach( var kvp in _currentFiles.Where( kvp => kvp.Value.Changed ) )
|
||||
{
|
||||
kvp.Value.Write( _dir );
|
||||
|
|
@ -115,13 +136,14 @@ namespace Penumbra.Mods
|
|||
_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;
|
||||
}
|
||||
|
||||
_currentManipulations.Add( m, mod );
|
||||
var gamePath = m.CorrespondingFilename();
|
||||
try
|
||||
{
|
||||
|
|
@ -1,134 +1,45 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.Game;
|
||||
using Penumbra.MetaData;
|
||||
using Penumbra.Game.Enums;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Util;
|
||||
using Swan;
|
||||
using ImcFile = Lumina.Data.Files.ImcFile;
|
||||
|
||||
namespace Penumbra.Mods
|
||||
namespace Penumbra.Meta
|
||||
{
|
||||
public enum MetaType : byte
|
||||
public class MetaManipulationConverter : JsonConverter< MetaManipulation >
|
||||
{
|
||||
Unknown = 0,
|
||||
Imc = 1,
|
||||
Eqdp = 2,
|
||||
Eqp = 3,
|
||||
Est = 4,
|
||||
Gmp = 5
|
||||
};
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public struct EqpIdentifier
|
||||
public override void WriteJson( JsonWriter writer, MetaManipulation manip, JsonSerializer serializer )
|
||||
{
|
||||
[FieldOffset( 0 )]
|
||||
public ulong Value;
|
||||
var s = Convert.ToBase64String( manip.ToBytes() );
|
||||
writer.WriteValue( s );
|
||||
}
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public MetaType Type;
|
||||
public override MetaManipulation ReadJson( JsonReader reader, Type objectType, MetaManipulation existingValue, bool hasExistingValue,
|
||||
JsonSerializer serializer )
|
||||
|
||||
[FieldOffset( 1 )]
|
||||
public EquipSlot Slot;
|
||||
{
|
||||
if( reader.TokenType != JsonToken.String )
|
||||
{
|
||||
throw new JsonReaderException();
|
||||
}
|
||||
|
||||
[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 );
|
||||
set => _objectAndBody = ( byte )( ( _objectAndBody & 0b11100000 ) | ( byte )value );
|
||||
}
|
||||
|
||||
public BodySlot BodySlot
|
||||
{
|
||||
get => ( BodySlot )( _objectAndBody & 0b11100000 );
|
||||
set => _objectAndBody = ( byte )( ( _objectAndBody & 0b00011111 ) | ( byte )value );
|
||||
}
|
||||
|
||||
[FieldOffset( 2 )]
|
||||
public ushort PrimaryId;
|
||||
|
||||
[FieldOffset( 4 )]
|
||||
public ushort Variant;
|
||||
|
||||
[FieldOffset( 6 )]
|
||||
public ushort SecondaryId;
|
||||
|
||||
[FieldOffset( 6 )]
|
||||
public EquipSlot EquipSlot;
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
[JsonConverter( typeof( MetaManipulationConverter ) )]
|
||||
public struct MetaManipulation : IComparable
|
||||
{
|
||||
public static MetaManipulation Eqp( EquipSlot equipSlot, ushort setId, EqpEntry value )
|
||||
|
|
@ -138,9 +49,9 @@ namespace Penumbra.Mods
|
|||
{
|
||||
Type = MetaType.Eqp,
|
||||
Slot = equipSlot,
|
||||
SetId = setId
|
||||
SetId = setId,
|
||||
},
|
||||
EqpValue = value
|
||||
EqpValue = value,
|
||||
};
|
||||
|
||||
public static MetaManipulation Eqdp( EquipSlot equipSlot, GenderRace gr, ushort setId, EqdpEntry value )
|
||||
|
|
@ -151,9 +62,9 @@ namespace Penumbra.Mods
|
|||
Type = MetaType.Eqdp,
|
||||
Slot = equipSlot,
|
||||
GenderRace = gr,
|
||||
SetId = setId
|
||||
SetId = setId,
|
||||
},
|
||||
EqdpValue = value
|
||||
EqdpValue = value,
|
||||
};
|
||||
|
||||
public static MetaManipulation Gmp( ushort setId, GmpEntry value )
|
||||
|
|
@ -162,9 +73,9 @@ namespace Penumbra.Mods
|
|||
GmpIdentifier = new GmpIdentifier()
|
||||
{
|
||||
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,
|
||||
|
|
@ -178,9 +89,9 @@ namespace Penumbra.Mods
|
|||
GenderRace = gr,
|
||||
EquipSlot = equipSlot,
|
||||
BodySlot = bodySlot,
|
||||
PrimaryId = setId
|
||||
PrimaryId = setId,
|
||||
},
|
||||
EstValue = value
|
||||
EstValue = value,
|
||||
};
|
||||
|
||||
public static MetaManipulation Imc( ObjectType type, BodySlot secondaryType, ushort primaryId, ushort secondaryId
|
||||
|
|
@ -194,9 +105,9 @@ namespace Penumbra.Mods
|
|||
BodySlot = secondaryType,
|
||||
PrimaryId = primaryId,
|
||||
SecondaryId = secondaryId,
|
||||
Variant = idx
|
||||
Variant = idx,
|
||||
},
|
||||
ImcValue = value
|
||||
ImcValue = 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,
|
||||
EquipSlot = slot,
|
||||
PrimaryId = primaryId,
|
||||
Variant = idx
|
||||
Variant = idx,
|
||||
},
|
||||
ImcValue = value
|
||||
ImcValue = value,
|
||||
};
|
||||
|
||||
internal MetaManipulation( ulong identifier, ulong value )
|
||||
: this()
|
||||
{
|
||||
Identifier = identifier;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public readonly ulong Identifier;
|
||||
|
||||
|
|
@ -257,7 +175,7 @@ namespace Penumbra.Mods
|
|||
=> Identifier.GetHashCode();
|
||||
|
||||
public int CompareTo( object? rhs )
|
||||
=> Identifier.CompareTo( rhs );
|
||||
=> Identifier.CompareTo( rhs is MetaManipulation m ? m.Identifier : null );
|
||||
|
||||
public GamePath CorrespondingFilename()
|
||||
{
|
||||
|
|
@ -268,7 +186,7 @@ namespace Penumbra.Mods
|
|||
MetaType.Est => MetaFileNames.Est( EstIdentifier.ObjectType, EstIdentifier.EquipSlot, EstIdentifier.BodySlot ),
|
||||
MetaType.Gmp => MetaFileNames.Gmp(),
|
||||
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;
|
||||
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.Security.Cryptography;
|
||||
using Dalamud.Plugin;
|
||||
using Penumbra.Structs;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Models
|
||||
namespace Penumbra.Mod
|
||||
{
|
||||
public class ModCleanup
|
||||
{
|
||||
|
|
@ -202,7 +203,7 @@ namespace Penumbra.Models
|
|||
private static bool FileIsInAnyGroup( ModMeta meta, RelPath relPath, bool exceptDuplicates = false )
|
||||
{
|
||||
var groupEnumerator = exceptDuplicates
|
||||
? meta.Groups.Values.Where( G => G.GroupName != Duplicates )
|
||||
? meta.Groups.Values.Where( g => g.GroupName != Duplicates )
|
||||
: meta.Groups.Values;
|
||||
return groupEnumerator.SelectMany( group => group.Options )
|
||||
.Any( option => option.OptionFiles.ContainsKey( relPath ) );
|
||||
|
|
@ -315,7 +316,8 @@ namespace Penumbra.Models
|
|||
|
||||
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.
|
||||
|
|
@ -356,7 +358,7 @@ namespace Penumbra.Models
|
|||
var usedRelPath = new RelPath( usedGamePath );
|
||||
required.AddFile( usedRelPath, gamePath );
|
||||
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 ) )
|
||||
{
|
||||
|
|
@ -366,7 +368,7 @@ namespace Penumbra.Models
|
|||
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.Linq;
|
||||
using Penumbra.Structs;
|
||||
|
||||
namespace Penumbra.Models
|
||||
namespace Penumbra.Mod
|
||||
{
|
||||
public class NamedModSettings
|
||||
{
|
||||
|
|
@ -11,7 +12,7 @@ namespace Penumbra.Models
|
|||
public void AddFromModSetting( ModSettings s, ModMeta meta )
|
||||
{
|
||||
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 )
|
||||
{
|
||||
|
|
@ -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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.Models;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods
|
||||
{
|
||||
public class ModCollection
|
||||
{
|
||||
private readonly DirectoryInfo _basePath;
|
||||
public const string DefaultCollection = "Default";
|
||||
|
||||
public List< ModInfo >? ModSettings { get; set; }
|
||||
public ResourceMod[]? EnabledMods { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public Dictionary< string, ModSettings > Settings { get; }
|
||||
|
||||
public ModCollection( DirectoryInfo basePath )
|
||||
=> _basePath = basePath;
|
||||
|
||||
public void Load( bool invertOrder = false )
|
||||
public ModCollection()
|
||||
{
|
||||
// find the collection json
|
||||
var collectionPath = Path.Combine( _basePath.FullName, "collection.json" );
|
||||
if( File.Exists( collectionPath ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
ModSettings = JsonConvert.DeserializeObject< List< ModInfo > >( File.ReadAllText( collectionPath ) );
|
||||
ModSettings = ModSettings.OrderBy( x => x.Priority ).ToList();
|
||||
Name = DefaultCollection;
|
||||
Settings = new Dictionary< string, ModSettings >();
|
||||
}
|
||||
catch( Exception e )
|
||||
|
||||
public ModCollection( string name, Dictionary< string, ModSettings > settings )
|
||||
{
|
||||
PluginLog.Error( $"failed to read log collection information, failed path: {collectionPath}, err: {e.Message}" );
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if( ModSettings != null )
|
||||
foreach( var s in removeList )
|
||||
{
|
||||
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;
|
||||
Settings.Remove( s );
|
||||
}
|
||||
|
||||
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;
|
||||
return removeList.Count > 0;
|
||||
}
|
||||
|
||||
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 )
|
||||
public void CreateCache( DirectoryInfo modDirectory, Dictionary< string, ModData > data, bool cleanUnavailable = false )
|
||||
{
|
||||
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()
|
||||
Cache = new ModCollectionCache( Name, modDirectory );
|
||||
var changedSettings = false;
|
||||
foreach( var modKvp in data )
|
||||
{
|
||||
var collectionPath = Path.Combine( _basePath.FullName, "collection.json" );
|
||||
|
||||
try
|
||||
if( Settings.TryGetValue( modKvp.Key, out var settings ) )
|
||||
{
|
||||
var data = JsonConvert.SerializeObject( ModSettings.OrderBy( x => x.Priority ).ToList() );
|
||||
File.WriteAllText( collectionPath, data );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"failed to write log collection information, failed path: {collectionPath}, err: {e.Message}" );
|
||||
}
|
||||
}
|
||||
|
||||
private int CleanPriority( int priority )
|
||||
=> priority < 0 ? 0 : priority >= ModSettings!.Count ? ModSettings.Count - 1 : priority;
|
||||
|
||||
public void ReorderMod( ModInfo info, int newPriority )
|
||||
{
|
||||
if( ModSettings == null )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var oldPriority = info.Priority;
|
||||
newPriority = CleanPriority( newPriority );
|
||||
if( oldPriority == newPriority )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
info.Priority = newPriority;
|
||||
if( newPriority < oldPriority )
|
||||
{
|
||||
for( var i = oldPriority - 1; i >= newPriority; --i )
|
||||
{
|
||||
++ModSettings![ i ].Priority;
|
||||
ModSettings.Swap( i, i + 1 );
|
||||
}
|
||||
Cache.AvailableMods.Add( new Mod.Mod( settings, modKvp.Value ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
for( var i = oldPriority + 1; i <= newPriority; ++i )
|
||||
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 )
|
||||
{
|
||||
--ModSettings![ i ].Priority;
|
||||
ModSettings.Swap( i - 1, i );
|
||||
}
|
||||
changedSettings |= CleanUnavailableSettings( data );
|
||||
}
|
||||
|
||||
EnabledMods = GetOrderedAndEnabledModList().ToArray();
|
||||
Save();
|
||||
}
|
||||
|
||||
public void ReorderMod( ModInfo info, bool up )
|
||||
=> ReorderMod( info, info.Priority + ( up ? 1 : -1 ) );
|
||||
|
||||
public ModInfo? FindModSettings( string name )
|
||||
if( changedSettings )
|
||||
{
|
||||
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;
|
||||
Save( Service< DalamudPluginInterface >.Get() );
|
||||
}
|
||||
|
||||
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;
|
||||
Cache.SortMods();
|
||||
CalculateEffectiveFileList( modDirectory, true );
|
||||
}
|
||||
|
||||
public ModInfo FindOrCreateModSettings( ResourceMod mod )
|
||||
public void UpdateSetting( ModData mod )
|
||||
{
|
||||
var settings = FindModSettings( mod.ModBasePath.Name );
|
||||
if( settings == null )
|
||||
if( !Settings.TryGetValue( mod.BasePath.Name, out var settings ) )
|
||||
{
|
||||
return AddModSettings( mod );
|
||||
return;
|
||||
}
|
||||
|
||||
settings.Mod = mod;
|
||||
settings.FixInvalidSettings();
|
||||
return settings;
|
||||
}
|
||||
|
||||
public IEnumerable< ModInfo > GetOrderedAndEnabledModSettings( bool invertOrder = false )
|
||||
if( settings.FixInvalidSettings( mod.Meta ) )
|
||||
{
|
||||
var query = ModSettings?
|
||||
.Where( x => x.Enabled )
|
||||
?? Enumerable.Empty< ModInfo >();
|
||||
Save( Service< DalamudPluginInterface >.Get() );
|
||||
}
|
||||
}
|
||||
|
||||
if( !invertOrder )
|
||||
public void UpdateSettings()
|
||||
{
|
||||
return query.OrderBy( x => x.Priority );
|
||||
}
|
||||
|
||||
return query.OrderByDescending( x => x.Priority );
|
||||
}
|
||||
|
||||
public IEnumerable< ResourceMod > GetOrderedAndEnabledModList( bool invertOrder = false )
|
||||
if( Cache == null )
|
||||
{
|
||||
return GetOrderedAndEnabledModSettings( invertOrder )
|
||||
.Select( x => x.Mod );
|
||||
return;
|
||||
}
|
||||
|
||||
public IEnumerable< (ResourceMod, ModInfo) > GetOrderedAndEnabledModListWithSettings( bool invertOrder = false )
|
||||
var changes = false;
|
||||
foreach( var mod in Cache.AvailableMods )
|
||||
{
|
||||
return GetOrderedAndEnabledModSettings( invertOrder )
|
||||
.Select( x => ( x.Mod, x ) );
|
||||
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
|
||||
{
|
||||
var collection = JsonConvert.DeserializeObject< ModCollection >( File.ReadAllText( file.FullName ) );
|
||||
return collection;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
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}" );
|
||||
}
|
||||
}
|
||||
|
||||
public static DirectoryInfo CollectionDir( DalamudPluginInterface pi )
|
||||
=> new( Path.Combine( pi.GetPluginConfigDirectory(), "collections" ) );
|
||||
|
||||
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 )
|
||||
{
|
||||
try
|
||||
{
|
||||
var dir = CollectionDir( pi );
|
||||
dir.Create();
|
||||
var file = FileName( dir, Name );
|
||||
SaveToFile( file );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not save collection {Name}:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
public static ModCollection? Load( string name, DalamudPluginInterface pi )
|
||||
{
|
||||
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 )
|
||||
{
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
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
|
||||
{
|
||||
Cache.AddMod( ModSettings.DefaultSettings( data.Meta ), data );
|
||||
}
|
||||
}
|
||||
|
||||
public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )
|
||||
=> Cache?.ResolveSwappedOrReplacementPath( gameResourcePath );
|
||||
}
|
||||
}
|
||||
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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin;
|
||||
using Penumbra.Hooks;
|
||||
using Penumbra.Models;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods
|
||||
{
|
||||
public class ModManager : IDisposable
|
||||
public class ModManager
|
||||
{
|
||||
private readonly Plugin _plugin;
|
||||
public readonly Dictionary< GamePath, FileInfo > ResolvedFiles = new();
|
||||
public readonly Dictionary< GamePath, GamePath > SwappedFiles = new();
|
||||
public MetaManager? MetaManipulations;
|
||||
public DirectoryInfo BasePath { get; private set; }
|
||||
|
||||
public ModCollection? Mods { get; set; }
|
||||
private DirectoryInfo? _basePath;
|
||||
public Dictionary< string, ModData > Mods { get; } = new();
|
||||
public Dictionary< string, ModCollection > Collections { get; } = new();
|
||||
|
||||
public ModCollection CurrentCollection { get; private set; }
|
||||
|
||||
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;
|
||||
if( basePath == null || !basePath.Exists )
|
||||
_plugin = plugin;
|
||||
BasePath = new DirectoryInfo( plugin.Configuration!.ModDirectory );
|
||||
ReadCollections();
|
||||
CurrentCollection = Collections.Values.First();
|
||||
if( !SetCurrentCollection( plugin.Configuration!.CurrentCollection ) )
|
||||
{
|
||||
Mods = null;
|
||||
return;
|
||||
}
|
||||
PluginLog.Debug( "Last choice of collection {Name} is not available, reset to Default.",
|
||||
plugin.Configuration!.CurrentCollection );
|
||||
|
||||
// FileSystemPasta();
|
||||
|
||||
Mods = new ModCollection( basePath );
|
||||
Mods.Load();
|
||||
|
||||
CalculateEffectiveFileList();
|
||||
}
|
||||
|
||||
public void CalculateEffectiveFileList()
|
||||
if( SetCurrentCollection( ModCollection.DefaultCollection ) )
|
||||
{
|
||||
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;
|
||||
registeredFiles[ swap.Key ] = mod.Meta.Name;
|
||||
}
|
||||
else if( registeredFiles.TryGetValue( swap.Key, out var modName ) )
|
||||
{
|
||||
mod.AddConflict( modName, swap.Key );
|
||||
PluginLog.Error( "Could not load any collection. Default collection unavailable." );
|
||||
CurrentCollection = new ModCollection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ProcessModFiles( Dictionary< GamePath, string > registeredFiles, ResourceMod mod, ModInfo settings )
|
||||
public bool SetCurrentCollection( string name )
|
||||
{
|
||||
var changedConfig = settings.FixInvalidSettings();
|
||||
foreach( var file in mod.ModFiles )
|
||||
if( Collections.TryGetValue( name, out var collection ) )
|
||||
{
|
||||
RelPath relativeFilePath = new( file, mod.ModBasePath );
|
||||
var (configChanged, gamePaths) = mod.Meta.GetFilesForConfig( relativeFilePath, settings );
|
||||
changedConfig |= configChanged;
|
||||
if( file.Extension == ".meta" && gamePaths.Count > 0 )
|
||||
CurrentCollection = collection;
|
||||
if( CurrentCollection.Cache == null )
|
||||
{
|
||||
AddManipulations( file, mod );
|
||||
CurrentCollection.CreateCache( BasePath, Mods );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ReadCollections()
|
||||
{
|
||||
var collectionDir = ModCollection.CollectionDir( _plugin.PluginInterface! );
|
||||
if( collectionDir.Exists )
|
||||
{
|
||||
foreach( var file in collectionDir.EnumerateFiles( "*.json" ) )
|
||||
{
|
||||
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
|
||||
{
|
||||
AddFiles( gamePaths, file, registeredFiles, mod );
|
||||
Collections.Add( collection.Name, collection );
|
||||
}
|
||||
}
|
||||
|
||||
return changedConfig;
|
||||
}
|
||||
|
||||
private void AddFiles( IEnumerable< GamePath > gamePaths, FileInfo file, Dictionary< GamePath, string > registeredFiles,
|
||||
ResourceMod mod )
|
||||
{
|
||||
foreach( var gamePath in gamePaths )
|
||||
{
|
||||
if( !ResolvedFiles.ContainsKey( gamePath ) )
|
||||
{
|
||||
ResolvedFiles[ gamePath ] = file;
|
||||
registeredFiles[ gamePath ] = mod.Meta.Name;
|
||||
}
|
||||
else if( registeredFiles.TryGetValue( gamePath, out var modName ) )
|
||||
{
|
||||
mod.AddConflict( modName, gamePath );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddManipulations( FileInfo file, ResourceMod mod )
|
||||
if( !Collections.ContainsKey( ModCollection.DefaultCollection ) )
|
||||
{
|
||||
if( !mod.MetaManipulations.TryGetValue( file, out var meta ) )
|
||||
{
|
||||
PluginLog.Error( $"{file.FullName} is a TexTools Meta File without meta information." );
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( var manipulation in meta.Manipulations )
|
||||
{
|
||||
MetaManipulations!.ApplyMod( manipulation );
|
||||
var defaultCollection = new ModCollection();
|
||||
SaveCollection( defaultCollection );
|
||||
Collections.Add( defaultCollection.Name, defaultCollection );
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeModPriority( ModInfo info, int newPriority )
|
||||
public void SaveCollection( ModCollection collection )
|
||||
=> collection.Save( _plugin.PluginInterface! );
|
||||
|
||||
|
||||
public bool AddCollection( string name, Dictionary< string, ModSettings > settings )
|
||||
{
|
||||
Mods!.ReorderMod( info, newPriority );
|
||||
CalculateEffectiveFileList();
|
||||
var nameFixed = name.RemoveInvalidPathSymbols().ToLowerInvariant();
|
||||
if( Collections.Values.Any( c => c.Name.RemoveInvalidPathSymbols().ToLowerInvariant() == nameFixed ) )
|
||||
{
|
||||
PluginLog.Warning( $"The new collection {name} would lead to the same path as one that already exists." );
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ChangeModPriority( ModInfo info, bool up = false )
|
||||
{
|
||||
Mods!.ReorderMod( info, up );
|
||||
CalculateEffectiveFileList();
|
||||
var newCollection = new ModCollection( name, settings );
|
||||
Collections.Add( name, newCollection );
|
||||
SaveCollection( newCollection );
|
||||
CurrentCollection = newCollection;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DeleteMod( ResourceMod? mod )
|
||||
public bool RemoveCollection( string name )
|
||||
{
|
||||
if( mod?.ModBasePath.Exists ?? false )
|
||||
if( name == ModCollection.DefaultCollection )
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete( mod.ModBasePath.FullName, true );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not delete the mod {mod.ModBasePath.Name}:\n{e}" );
|
||||
}
|
||||
PluginLog.Error( "Can not remove the default collection." );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( Collections.TryGetValue( name, out var collection ) )
|
||||
{
|
||||
if( CurrentCollection == collection )
|
||||
{
|
||||
SetCurrentCollection( ModCollection.DefaultCollection );
|
||||
}
|
||||
|
||||
collection.Delete( _plugin.PluginInterface! );
|
||||
Collections.Remove( name );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void DiscoverMods( DirectoryInfo basePath )
|
||||
{
|
||||
BasePath = basePath;
|
||||
DiscoverMods();
|
||||
}
|
||||
|
||||
public FileInfo? GetCandidateForGameFile( GamePath gameResourcePath )
|
||||
public void DiscoverMods()
|
||||
{
|
||||
var val = ResolvedFiles.TryGetValue( gameResourcePath, out var candidate );
|
||||
if( !val )
|
||||
Mods.Clear();
|
||||
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;
|
||||
}
|
||||
|
||||
return candidate;
|
||||
Mods.Add( modFolder.Name, mod );
|
||||
}
|
||||
|
||||
public GamePath? GetSwappedFilePath( GamePath gameResourcePath )
|
||||
=> SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null;
|
||||
|
||||
public string? ResolveSwappedOrReplacementFilePath( GamePath gameResourcePath )
|
||||
=> GetCandidateForGameFile( gameResourcePath )?.FullName.Replace( '\\', '/' ) ?? GetSwappedFilePath( gameResourcePath ) ?? null;
|
||||
|
||||
|
||||
public void Dispose()
|
||||
foreach( var collection in Collections.Values.Where( c => c.Cache != null ) )
|
||||
{
|
||||
MetaManipulations?.Dispose();
|
||||
// _fileSystemWatcher?.Dispose();
|
||||
collection.CreateCache( BasePath, Mods );
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteMod( DirectoryInfo modFolder )
|
||||
{
|
||||
modFolder.Refresh();
|
||||
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 )
|
||||
|
|
|
|||
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.Game;
|
||||
using Penumbra.Hooks;
|
||||
using Penumbra.MetaData;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.UI;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra
|
||||
{
|
||||
|
|
@ -25,34 +26,35 @@ namespace Penumbra
|
|||
|
||||
private const string CommandName = "/penumbra";
|
||||
|
||||
public DalamudPluginInterface? PluginInterface { get; set; }
|
||||
public Configuration? Configuration { get; set; }
|
||||
public ResourceLoader? ResourceLoader { get; set; }
|
||||
public SettingsInterface? SettingsInterface { get; set; }
|
||||
public SoundShit? SoundShit { get; set; }
|
||||
public DalamudPluginInterface PluginInterface { get; set; } = null!;
|
||||
public Configuration Configuration { get; set; } = null!;
|
||||
public ResourceLoader ResourceLoader { get; set; } = null!;
|
||||
public SettingsInterface SettingsInterface { get; set; } = null!;
|
||||
public MusicManager SoundShit { get; set; } = null!;
|
||||
|
||||
private WebServer? _webServer;
|
||||
|
||||
public void Initialize( DalamudPluginInterface pluginInterface )
|
||||
{
|
||||
PluginInterface = pluginInterface;
|
||||
Service< DalamudPluginInterface >.Set( PluginInterface );
|
||||
|
||||
Configuration = PluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
|
||||
Configuration.Initialize( PluginInterface );
|
||||
|
||||
SoundShit = new SoundShit( this );
|
||||
Configuration = Configuration.Load( PluginInterface );
|
||||
|
||||
SoundShit = new MusicManager( this );
|
||||
SoundShit.DisableStreaming();
|
||||
|
||||
var gameUtils = Service< GameResourceManagement >.Set( PluginInterface );
|
||||
var modManager = Service< ModManager >.Set( this );
|
||||
Service< MetaDefaults >.Set( PluginInterface );
|
||||
modManager.DiscoverMods( Configuration.CurrentCollection );
|
||||
var modManager = Service< ModManager >.Set( this );
|
||||
|
||||
modManager.DiscoverMods();
|
||||
|
||||
ResourceLoader = new ResourceLoader( this );
|
||||
|
||||
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();
|
||||
|
|
@ -96,14 +98,12 @@ namespace Penumbra
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
// ModManager?.Dispose();
|
||||
|
||||
PluginInterface!.UiBuilder.OnBuildUi -= SettingsInterface!.Draw;
|
||||
PluginInterface.UiBuilder.OnBuildUi -= SettingsInterface.Draw;
|
||||
|
||||
PluginInterface.CommandManager.RemoveHandler( CommandName );
|
||||
PluginInterface.Dispose();
|
||||
|
||||
ResourceLoader?.Dispose();
|
||||
ResourceLoader.Dispose();
|
||||
|
||||
ShutdownWebServer();
|
||||
}
|
||||
|
|
@ -118,8 +118,8 @@ namespace Penumbra
|
|||
case "reload":
|
||||
{
|
||||
Service< ModManager >.Get().DiscoverMods();
|
||||
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."
|
||||
PluginInterface.Framework.Gui.Chat.Print(
|
||||
$"Reloaded Penumbra mods. You have {Service< ModManager >.Get()?.Mods.Count} mods."
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
|
@ -127,11 +127,11 @@ namespace Penumbra
|
|||
{
|
||||
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
|
||||
{
|
||||
RefreshActors.RedrawAll( PluginInterface!.ClientState.Actors );
|
||||
RefreshActors.RedrawAll( PluginInterface.ClientState.Actors );
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -141,7 +141,7 @@ namespace Penumbra
|
|||
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?
|
||||
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 ImGuiNET;
|
||||
|
||||
namespace Penumbra.UI
|
||||
namespace Penumbra.UI.Custom
|
||||
{
|
||||
public static partial class ImGuiCustom
|
||||
{
|
||||
public static void BeginFramedGroup( string label ) => BeginFramedGroupInternal( ref label, ZeroVector, false );
|
||||
public static void BeginFramedGroup( string label, Vector2 minSize ) => BeginFramedGroupInternal( ref label, minSize, false );
|
||||
public static void BeginFramedGroup( string label )
|
||||
=> BeginFramedGroupInternal( ref label, ZeroVector, 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 );
|
||||
public static void BeginFramedGroup( string label, Vector2 minSize )
|
||||
=> 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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using ImGuiNET;
|
||||
|
||||
namespace Penumbra.UI
|
||||
namespace Penumbra.UI.Custom
|
||||
{
|
||||
public static partial class ImGuiCustom
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Penumbra.UI
|
||||
namespace Penumbra.UI.Custom
|
||||
{
|
||||
public static partial class ImGuiCustom
|
||||
{
|
||||
|
|
@ -16,8 +16,8 @@ namespace Penumbra.UI
|
|||
return false;
|
||||
}
|
||||
|
||||
public static bool ResizingTextInput( string label, ref string input, uint maxLength ) =>
|
||||
ResizingTextInputIntern( label, ref input, maxLength ).Item1;
|
||||
public static bool ResizingTextInput( string label, ref string input, uint maxLength )
|
||||
=> ResizingTextInputIntern( label, ref input, maxLength ).Item1;
|
||||
|
||||
public static bool ResizingTextInput( ref string input, uint maxLength )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System.Windows.Forms;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Penumbra.UI
|
||||
namespace Penumbra.UI.Custom
|
||||
{
|
||||
public static partial class ImGuiCustom
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ namespace Penumbra.UI
|
|||
#endif
|
||||
|
||||
private readonly SettingsInterface _base;
|
||||
public MenuBar( SettingsInterface ui ) => _base = ui;
|
||||
|
||||
public MenuBar( SettingsInterface ui )
|
||||
=> _base = ui;
|
||||
|
||||
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 Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
|
|
@ -10,24 +11,40 @@ namespace Penumbra.UI
|
|||
{
|
||||
private class TabEffective
|
||||
{
|
||||
private const string LabelTab = "Effective File List";
|
||||
private const float TextSizePadding = 5f;
|
||||
private const string LabelTab = "Effective Changes";
|
||||
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 )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiCustom.CopyOnClickSelectable( path );
|
||||
Custom.ImGuiCustom.CopyOnClickSelectable( path );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.PushFont( UiBuilder.IconFont );
|
||||
ImGui.TextUnformatted( $"{( char )FontAwesomeIcon.LongArrowAltLeft}" );
|
||||
ImGui.TextUnformatted( LongArrowLeft );
|
||||
ImGui.PopFont();
|
||||
|
||||
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()
|
||||
|
|
@ -40,14 +57,21 @@ namespace Penumbra.UI
|
|||
|
||||
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 );
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
|
||||
foreach( var (manip, mod) in currentCollection.MetaManipulations.Manipulations )
|
||||
{
|
||||
DrawManipulationLine( manip, mod );
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Windows.Forms;
|
|||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using Penumbra.Importer;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.UI
|
||||
{
|
||||
|
|
@ -25,26 +26,30 @@ namespace Penumbra.UI
|
|||
|
||||
private static readonly Vector2 ImportBarSize = new( -1, 0 );
|
||||
|
||||
private bool _isImportRunning = false;
|
||||
private bool _hasError = false;
|
||||
private bool _isImportRunning;
|
||||
private bool _hasError;
|
||||
private TexToolsImport? _texToolsImport;
|
||||
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()
|
||||
{
|
||||
_isImportRunning = true;
|
||||
Task.Run( async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var picker = new OpenFileDialog
|
||||
{
|
||||
Multiselect = true,
|
||||
Filter = FileTypeFilter,
|
||||
CheckFileExists = true,
|
||||
Title = LabelFileDialog
|
||||
Title = LabelFileDialog,
|
||||
};
|
||||
|
||||
var result = await picker.ShowDialogAsync();
|
||||
|
|
@ -59,7 +64,7 @@ namespace Penumbra.UI
|
|||
|
||||
try
|
||||
{
|
||||
_texToolsImport = new TexToolsImport( new DirectoryInfo( _base._plugin!.Configuration!.CurrentCollection ) );
|
||||
_texToolsImport = new TexToolsImport( new DirectoryInfo( _base._plugin!.Configuration!.ModDirectory ) );
|
||||
_texToolsImport.ImportModPack( new FileInfo( fileName ) );
|
||||
|
||||
PluginLog.Log( $"-> {fileName} OK!" );
|
||||
|
|
@ -74,6 +79,11 @@ namespace Penumbra.UI
|
|||
_texToolsImport = null;
|
||||
_base.ReloadMods();
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Error opening file picker dialogue:\n{e}" );
|
||||
}
|
||||
|
||||
_isImportRunning = false;
|
||||
} );
|
||||
|
|
@ -98,8 +108,7 @@ namespace Penumbra.UI
|
|||
|
||||
switch( _texToolsImport.State )
|
||||
{
|
||||
case ImporterState.None:
|
||||
break;
|
||||
case ImporterState.None: break;
|
||||
case ImporterState.WritingPackToDisk:
|
||||
ImGui.Text( TooltipModpack1 );
|
||||
break;
|
||||
|
|
@ -111,10 +120,8 @@ namespace Penumbra.UI
|
|||
ImGui.ProgressBar( _texToolsImport.Progress, ImportBarSize, str );
|
||||
break;
|
||||
}
|
||||
case ImporterState.Done:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
case ImporterState.Done: break;
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using ImGuiNET;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.UI
|
||||
{
|
||||
|
|
@ -9,21 +10,21 @@ namespace Penumbra.UI
|
|||
{
|
||||
private const string LabelTab = "Installed Mods";
|
||||
|
||||
private readonly SettingsInterface _base;
|
||||
private readonly ModManager _modManager;
|
||||
public readonly Selector Selector;
|
||||
public readonly ModPanel ModPanel;
|
||||
|
||||
public TabInstalled( SettingsInterface ui )
|
||||
{
|
||||
_base = ui;
|
||||
Selector = new Selector( _base );
|
||||
ModPanel = new ModPanel( _base, Selector );
|
||||
Selector = new Selector( ui );
|
||||
ModPanel = new ModPanel( ui, Selector );
|
||||
_modManager = Service< ModManager >.Get();
|
||||
}
|
||||
|
||||
private static void DrawNoModsAvailable()
|
||||
{
|
||||
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( "For example: D:/ffxiv/mods/" );
|
||||
ImGui.Text( "And pasting that path into the settings tab and clicking the 'Rediscover Mods' button." );
|
||||
|
|
@ -38,7 +39,7 @@ namespace Penumbra.UI
|
|||
return;
|
||||
}
|
||||
|
||||
if( Service< ModManager >.Get().Mods != null )
|
||||
if( _modManager.Mods.Count > 0 )
|
||||
{
|
||||
Selector.Draw();
|
||||
ImGui.SameLine();
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using Penumbra.Models;
|
||||
using Penumbra.Game.Enums;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Structs;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.UI
|
||||
|
|
@ -35,7 +38,7 @@ namespace Penumbra.UI
|
|||
private const string LabelChangedItemsHeader = "##changedItems";
|
||||
private const string LabelChangedItemIdx = "##citem_";
|
||||
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 LabelFileSwapTab = "File Swaps";
|
||||
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"
|
||||
+ "Yellow files are restricted to some options.";
|
||||
|
||||
private const float TextSizePadding = 5f;
|
||||
private const float OptionSelectionWidth = 140f;
|
||||
private const float CheckMarkSize = 50f;
|
||||
private const uint ColorGreen = 0xFF00C800;
|
||||
|
|
@ -68,11 +70,12 @@ namespace Penumbra.UI
|
|||
|
||||
private readonly Selector _selector;
|
||||
private readonly SettingsInterface _base;
|
||||
private readonly ModManager _modManager;
|
||||
|
||||
private void SelectGroup( int idx )
|
||||
{
|
||||
// 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;
|
||||
if( _selectedGroupIndex >= numGroups )
|
||||
{
|
||||
|
|
@ -126,20 +129,19 @@ namespace Penumbra.UI
|
|||
_base = ui;
|
||||
_selector = s;
|
||||
ResetState();
|
||||
_modManager = Service< ModManager >.Get();
|
||||
}
|
||||
|
||||
// This is only drawn when we have a mod selected, so we can forgive nulls.
|
||||
private ModInfo Mod
|
||||
private Mod.Mod Mod
|
||||
=> _selector.Mod!;
|
||||
|
||||
private ModMeta Meta
|
||||
=> Mod.Mod.Meta;
|
||||
=> Mod.Data.Meta;
|
||||
|
||||
private void Save()
|
||||
{
|
||||
var modManager = Service< ModManager >.Get();
|
||||
modManager.Mods?.Save();
|
||||
modManager.CalculateEffectiveFileList();
|
||||
_modManager.CurrentCollection.Save( _base._plugin.PluginInterface! );
|
||||
}
|
||||
|
||||
private void DrawAboutTab()
|
||||
|
|
@ -161,7 +163,8 @@ namespace Penumbra.UI
|
|||
|
||||
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;
|
||||
_selector.SaveCurrentMod();
|
||||
|
|
@ -194,6 +197,7 @@ namespace Penumbra.UI
|
|||
if( ImGui.BeginTabItem( LabelChangedItemsTab ) )
|
||||
{
|
||||
ImGui.SetNextItemWidth( -1 );
|
||||
var changedItems = false;
|
||||
if( ImGui.BeginListBox( LabelChangedItemsHeader, AutoFillSize ) )
|
||||
{
|
||||
_changedItemsList ??= Meta.ChangedItems
|
||||
|
|
@ -205,6 +209,7 @@ namespace Penumbra.UI
|
|||
if( ImGui.InputText( _changedItemsList[ i ].label, ref _changedItemsList[ i ].name, 128, flags ) )
|
||||
{
|
||||
Meta.ChangedItems.RemoveOrChange( _changedItemsList[ i ].name, i );
|
||||
changedItems = true;
|
||||
_selector.SaveCurrentMod();
|
||||
}
|
||||
}
|
||||
|
|
@ -217,10 +222,16 @@ namespace Penumbra.UI
|
|||
&& newItem.Length > 0 )
|
||||
{
|
||||
Meta.ChangedItems.Add( newItem );
|
||||
changedItems = true;
|
||||
_selector.SaveCurrentMod();
|
||||
}
|
||||
}
|
||||
|
||||
if( changedItems )
|
||||
{
|
||||
_changedItemsList = null;
|
||||
}
|
||||
|
||||
ImGui.EndListBox();
|
||||
}
|
||||
|
||||
|
|
@ -234,7 +245,7 @@ namespace Penumbra.UI
|
|||
|
||||
private void DrawConflictTab()
|
||||
{
|
||||
if( !Mod.Mod.FileConflicts.Any() || !ImGui.BeginTabItem( LabelConflictsTab ) )
|
||||
if( !Mod.Cache.Conflicts.Any() || !ImGui.BeginTabItem( LabelConflictsTab ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -242,20 +253,28 @@ namespace Penumbra.UI
|
|||
ImGui.SetNextItemWidth( -1 );
|
||||
if( ImGui.BeginListBox( LabelConflictsHeader, AutoFillSize ) )
|
||||
{
|
||||
foreach( var kv in Mod.Mod.FileConflicts )
|
||||
foreach( var kv in Mod.Cache.Conflicts )
|
||||
{
|
||||
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 );
|
||||
foreach( var file in kv.Value )
|
||||
foreach( var file in kv.Value.Files )
|
||||
{
|
||||
ImGui.Selectable( file );
|
||||
}
|
||||
|
||||
foreach( var manip in kv.Value.Manipulations )
|
||||
{
|
||||
ImGui.Text( manip.IdentifierString() );
|
||||
}
|
||||
|
||||
ImGui.Unindent( 15 );
|
||||
}
|
||||
|
||||
|
|
@ -286,7 +305,7 @@ namespace Penumbra.UI
|
|||
foreach( var file in Meta.FileSwaps )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiCustom.CopyOnClickSelectable( file.Key );
|
||||
Custom.ImGuiCustom.CopyOnClickSelectable( file.Key );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.PushFont( UiBuilder.IconFont );
|
||||
|
|
@ -294,7 +313,7 @@ namespace Penumbra.UI
|
|||
ImGui.PopFont();
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiCustom.CopyOnClickSelectable( file.Value );
|
||||
Custom.ImGuiCustom.CopyOnClickSelectable( file.Value );
|
||||
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
|
|
@ -312,16 +331,15 @@ namespace Penumbra.UI
|
|||
return;
|
||||
}
|
||||
|
||||
var len = Mod.Mod.ModBasePath.FullName.Length;
|
||||
_fullFilenameList = Mod.Mod.ModFiles
|
||||
.Select( F => ( F, false, ColorGreen, new RelPath( F, Mod.Mod.ModBasePath ) ) ).ToArray();
|
||||
_fullFilenameList = Mod.Data.Resources.ModFiles
|
||||
.Select( f => ( f, false, ColorGreen, new RelPath( f, Mod.Data.BasePath ) ) ).ToArray();
|
||||
|
||||
if( Meta.Groups.Count == 0 )
|
||||
{
|
||||
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 )
|
||||
{
|
||||
|
|
@ -362,10 +380,10 @@ namespace Penumbra.UI
|
|||
if( ImGui.BeginListBox( LabelFileListHeader, AutoFillSize ) )
|
||||
{
|
||||
UpdateFilenameList();
|
||||
foreach( var file in _fullFilenameList! )
|
||||
foreach( var (name, _, color, _) in _fullFilenameList! )
|
||||
{
|
||||
ImGui.PushStyleColor( ImGuiCol.Text, file.color );
|
||||
ImGui.Selectable( file.name.FullName );
|
||||
ImGui.PushStyleColor( ImGuiCol.Text, color );
|
||||
ImGui.Selectable( name.FullName );
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
|
|
@ -379,10 +397,11 @@ namespace Penumbra.UI
|
|||
ImGui.EndTabItem();
|
||||
}
|
||||
|
||||
private int HandleDefaultString( GamePath[] gamePaths, out int removeFolders )
|
||||
private static int HandleDefaultString( GamePath[] gamePaths, out int removeFolders )
|
||||
{
|
||||
removeFolders = 0;
|
||||
var defaultIndex = gamePaths.IndexOf( p => ( ( string )p ).StartsWith( TextDefaultGamePath ) );
|
||||
var defaultIndex =
|
||||
gamePaths.IndexOf( p => ( ( string )p ).StartsWith( TextDefaultGamePath ) );
|
||||
if( defaultIndex < 0 )
|
||||
{
|
||||
return defaultIndex;
|
||||
|
|
@ -412,7 +431,7 @@ namespace Penumbra.UI
|
|||
|
||||
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 )
|
||||
{
|
||||
return;
|
||||
|
|
@ -420,7 +439,7 @@ namespace Penumbra.UI
|
|||
|
||||
var defaultIndex = HandleDefaultString( gamePaths, out var removeFolders );
|
||||
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 )
|
||||
{
|
||||
|
|
@ -435,7 +454,7 @@ namespace Penumbra.UI
|
|||
|
||||
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;
|
||||
}
|
||||
|
|
@ -477,7 +496,8 @@ namespace Penumbra.UI
|
|||
private void DrawGamePathInput()
|
||||
{
|
||||
ImGui.SetNextItemWidth( -1 );
|
||||
ImGui.InputTextWithHint( LabelGamePathsEditBox, "Hover for help...", ref _currentGamePaths, 128 );
|
||||
ImGui.InputTextWithHint( LabelGamePathsEditBox, "Hover for help...", ref _currentGamePaths,
|
||||
128 );
|
||||
if( ImGui.IsItemHovered() )
|
||||
{
|
||||
ImGui.SetTooltip( TooltipGamePathsEdit );
|
||||
|
|
@ -580,7 +600,7 @@ namespace Penumbra.UI
|
|||
var oldEnabled = enabled;
|
||||
if( ImGui.Checkbox( label, ref enabled ) && oldEnabled != enabled )
|
||||
{
|
||||
Mod.Settings[ group.GroupName ] ^= 1 << idx;
|
||||
Mod.Settings.Settings[ group.GroupName ] ^= 1 << idx;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
|
@ -592,14 +612,14 @@ namespace Penumbra.UI
|
|||
return;
|
||||
}
|
||||
|
||||
ImGuiCustom.BeginFramedGroup( group.GroupName );
|
||||
Custom.ImGuiCustom.BeginFramedGroup( group.GroupName );
|
||||
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}" );
|
||||
}
|
||||
|
||||
ImGuiCustom.EndFramedGroup();
|
||||
Custom.ImGuiCustom.EndFramedGroup();
|
||||
}
|
||||
|
||||
private void DrawSingleSelector( OptionGroup group )
|
||||
|
|
@ -609,11 +629,12 @@ namespace Penumbra.UI
|
|||
return;
|
||||
}
|
||||
|
||||
var code = Mod.Settings[ group.GroupName ];
|
||||
var code = Mod.Settings.Settings[ group.GroupName ];
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -633,7 +654,7 @@ namespace Penumbra.UI
|
|||
|
||||
private void DrawConfigurationTab()
|
||||
{
|
||||
if( !_editMode && !Meta.HasGroupWithConfig )
|
||||
if( !_editMode && !Meta.HasGroupsWithConfig )
|
||||
{
|
||||
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 )
|
||||
{
|
||||
_editMode = editMode;
|
||||
|
|
@ -660,6 +853,7 @@ namespace Penumbra.UI
|
|||
|
||||
DrawAboutTab();
|
||||
DrawChangedItemsTab();
|
||||
|
||||
DrawConfigurationTab();
|
||||
if( _editMode )
|
||||
{
|
||||
|
|
@ -671,8 +865,8 @@ namespace Penumbra.UI
|
|||
}
|
||||
|
||||
DrawFileSwapTab();
|
||||
DrawMetaManipulationsTab();
|
||||
DrawConflictTab();
|
||||
|
||||
ImGui.EndTabBar();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ using System.Linq;
|
|||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using Penumbra.Models;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Structs;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.UI
|
||||
|
|
@ -44,7 +45,7 @@ namespace Penumbra.UI
|
|||
}
|
||||
|
||||
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 ) )
|
||||
{
|
||||
SelectGroup();
|
||||
|
|
@ -65,7 +66,7 @@ namespace Penumbra.UI
|
|||
}
|
||||
|
||||
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 ) )
|
||||
{
|
||||
SelectOption();
|
||||
|
|
@ -87,7 +88,7 @@ namespace Penumbra.UI
|
|||
ImGui.SetNextItemWidth( -1 );
|
||||
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 );
|
||||
}
|
||||
|
|
@ -104,31 +105,13 @@ namespace Penumbra.UI
|
|||
}
|
||||
}
|
||||
|
||||
private bool DrawMultiSelectorEditBegin( OptionGroup group )
|
||||
private void DrawMultiSelectorEditBegin( OptionGroup group )
|
||||
{
|
||||
var groupName = group.GroupName;
|
||||
if( ImGuiCustom.BeginFramedGroupEdit( ref groupName )
|
||||
&& groupName != group.GroupName
|
||||
&& !Meta!.Groups.ContainsKey( groupName ) )
|
||||
if( Custom.ImGuiCustom.BeginFramedGroupEdit( ref groupName ) )
|
||||
{
|
||||
var oldConf = Mod!.Settings[ group.GroupName ];
|
||||
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;
|
||||
_modManager.ChangeModGroup( group.GroupName, groupName, Mod.Data );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void DrawMultiSelectorEditAdd( OptionGroup group, float nameBoxStart )
|
||||
|
|
@ -149,9 +132,9 @@ namespace Penumbra.UI
|
|||
private void DrawMultiSelectorEdit( OptionGroup group )
|
||||
{
|
||||
var nameBoxStart = CheckMarkSize;
|
||||
var flag = Mod!.Settings[ group.GroupName ];
|
||||
var modChanged = DrawMultiSelectorEditBegin( group );
|
||||
var flag = Mod!.Settings.Settings[ group.GroupName ];
|
||||
|
||||
DrawMultiSelectorEditBegin( group );
|
||||
for( var i = 0; i < group.Options.Count; ++i )
|
||||
{
|
||||
var opt = group.Options[ i ];
|
||||
|
|
@ -171,11 +154,7 @@ namespace Penumbra.UI
|
|||
{
|
||||
if( newName.Length == 0 )
|
||||
{
|
||||
group.Options.RemoveAt( i );
|
||||
var bitmaskFront = ( 1 << i ) - 1;
|
||||
var bitmaskBack = ~( bitmaskFront | ( 1 << i ) );
|
||||
Mod.Settings[ group.GroupName ] = ( flag & bitmaskFront ) | ( ( flag & bitmaskBack ) >> 1 );
|
||||
modChanged = true;
|
||||
_modManager.RemoveModOption( i, group, Mod.Data );
|
||||
}
|
||||
else if( newName != opt.OptionName )
|
||||
{
|
||||
|
|
@ -188,134 +167,74 @@ namespace Penumbra.UI
|
|||
|
||||
DrawMultiSelectorEditAdd( group, nameBoxStart );
|
||||
|
||||
if( modChanged )
|
||||
{
|
||||
_selector.SaveCurrentMod();
|
||||
Save();
|
||||
Custom.ImGuiCustom.EndFramedGroup();
|
||||
}
|
||||
|
||||
ImGuiCustom.EndFramedGroup();
|
||||
}
|
||||
|
||||
private bool DrawSingleSelectorEditGroup( OptionGroup group, ref bool selectionChanged )
|
||||
private void DrawSingleSelectorEditGroup( OptionGroup group )
|
||||
{
|
||||
var groupName = group.GroupName;
|
||||
if( ImGui.InputText( $"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue )
|
||||
&& !Meta!.Groups.ContainsKey( groupName ) )
|
||||
if( ImGui.InputText( $"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
|
||||
{
|
||||
var oldConf = Mod!.Settings[ group.GroupName ];
|
||||
if( groupName != group.GroupName )
|
||||
{
|
||||
Meta.Groups.Remove( group.GroupName );
|
||||
selectionChanged |= Mod.FixSpecificSetting( group.GroupName );
|
||||
_modManager.ChangeModGroup( group.GroupName, groupName, Mod.Data );
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
var code = Mod!.Settings[ group.GroupName ];
|
||||
var selectionChanged = false;
|
||||
var modChanged = false;
|
||||
if( ImGuiCustom.RenameableCombo( $"##{group.GroupName}", ref code, out var newName,
|
||||
var oldSetting = Mod!.Settings.Settings[ group.GroupName ];
|
||||
var code = oldSetting;
|
||||
if( Custom.ImGuiCustom.RenameableCombo( $"##{group.GroupName}", ref code, out var newName,
|
||||
group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) )
|
||||
{
|
||||
if( code == group.Options.Count )
|
||||
{
|
||||
if( newName.Length > 0 )
|
||||
{
|
||||
selectionChanged = true;
|
||||
modChanged = true;
|
||||
Mod.Settings[ group.GroupName ] = code;
|
||||
Mod.Settings.Settings[ group.GroupName ] = code;
|
||||
group.Options.Add( new Option()
|
||||
{
|
||||
OptionName = newName,
|
||||
OptionDesc = "",
|
||||
OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >(),
|
||||
} );
|
||||
_selector.SaveCurrentMod();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( newName.Length == 0 )
|
||||
{
|
||||
modChanged = true;
|
||||
group.Options.RemoveAt( code );
|
||||
_modManager.RemoveModOption( code, group, Mod.Data );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( newName != group.Options[ code ].OptionName )
|
||||
{
|
||||
modChanged = true;
|
||||
group.Options[ code ] = new Option()
|
||||
{
|
||||
OptionName = newName, OptionDesc = group.Options[ code ].OptionDesc,
|
||||
OptionFiles = group.Options[ code ].OptionFiles,
|
||||
};
|
||||
_selector.SaveCurrentMod();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectionChanged |= Mod.Settings[ group.GroupName ] != code;
|
||||
Mod.Settings[ group.GroupName ] = code;
|
||||
}
|
||||
|
||||
selectionChanged |= Mod.FixSpecificSetting( group.GroupName );
|
||||
}
|
||||
if( code != oldSetting )
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
var labelEditPos = ImGui.GetCursorPosX();
|
||||
modChanged |= DrawSingleSelectorEditGroup( group, ref selectionChanged );
|
||||
|
||||
if( modChanged )
|
||||
{
|
||||
_selector.SaveCurrentMod();
|
||||
}
|
||||
|
||||
if( selectionChanged )
|
||||
{
|
||||
Save();
|
||||
}
|
||||
DrawSingleSelectorEditGroup( group );
|
||||
|
||||
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 )
|
||||
{
|
||||
const string hint = "Add new Single Group...";
|
||||
var newGroup = "";
|
||||
ImGui.SetCursorPosX( labelEditPos );
|
||||
if( labelEditPos == CheckMarkSize )
|
||||
|
|
@ -323,9 +242,10 @@ namespace Penumbra.UI
|
|||
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,
|
||||
ImGuiInputTextFlags.EnterReturnsTrue ) )
|
||||
{
|
||||
AddNewGroup( newGroup, SelectType.Multi );
|
||||
_modManager.ChangeModGroup( "", newGroup, Mod.Data, SelectType.Multi );
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawGroupSelectorsEdit()
|
||||
{
|
||||
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 );
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
|
|
@ -403,7 +324,7 @@ namespace Penumbra.UI
|
|||
}
|
||||
|
||||
_selector.SaveCurrentMod();
|
||||
if( Mod.Enabled )
|
||||
if( Mod.Settings.Enabled )
|
||||
{
|
||||
_selector.ReloadCurrentMod();
|
||||
}
|
||||
|
|
@ -428,7 +349,7 @@ namespace Penumbra.UI
|
|||
{
|
||||
Meta.FileSwaps[ key ] = newValue;
|
||||
_selector.SaveCurrentMod();
|
||||
if( Mod.Enabled )
|
||||
if( Mod.Settings.Enabled )
|
||||
{
|
||||
_selector.ReloadCurrentMod();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ using System.IO;
|
|||
using System.Numerics;
|
||||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using Penumbra.Models;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.UI
|
||||
{
|
||||
|
|
@ -20,6 +21,7 @@ namespace Penumbra.UI
|
|||
private const string LabelEditWebsite = "##editWebsite";
|
||||
private const string LabelModEnabled = "Enabled";
|
||||
private const string LabelEditingEnabled = "Enable Editing";
|
||||
private const string LabelOverWriteDir = "OverwriteDir";
|
||||
private const string ButtonOpenWebsite = "Open Website";
|
||||
private const string ButtonOpenModFolder = "Open Mod Folder";
|
||||
private const string ButtonRenameModFolder = "Rename Mod Folder";
|
||||
|
|
@ -45,6 +47,7 @@ namespace Penumbra.UI
|
|||
|
||||
private readonly SettingsInterface _base;
|
||||
private readonly Selector _selector;
|
||||
private readonly ModManager _modManager;
|
||||
public readonly PluginDetails Details;
|
||||
|
||||
private bool _editMode;
|
||||
|
|
@ -57,23 +60,22 @@ namespace Penumbra.UI
|
|||
_selector = s;
|
||||
Details = new PluginDetails( _base, _selector );
|
||||
_currentWebsite = Meta?.Website ?? "";
|
||||
_modManager = Service< ModManager >.Get();
|
||||
}
|
||||
|
||||
private ModInfo? Mod
|
||||
private Mod.Mod? Mod
|
||||
=> _selector.Mod;
|
||||
|
||||
private ModMeta? Meta
|
||||
=> Mod?.Mod.Meta;
|
||||
=> Mod?.Data.Meta;
|
||||
|
||||
private void DrawName()
|
||||
{
|
||||
var name = Meta!.Name;
|
||||
if( ImGuiCustom.InputOrText( _editMode, LabelEditName, ref name, 64 )
|
||||
&& name.Length > 0
|
||||
&& name != Meta.Name )
|
||||
if( Custom.ImGuiCustom.InputOrText( _editMode, LabelEditName, ref name, 64 ) && _modManager.RenameMod( name, Mod!.Data ) )
|
||||
{
|
||||
Meta.Name = name;
|
||||
_selector.SaveCurrentMod();
|
||||
_selector.RenameCurrentModLower( name );
|
||||
_selector.SelectModByDir( Mod.Data.BasePath.Name );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +89,7 @@ namespace Penumbra.UI
|
|||
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
|
||||
ImGui.SameLine();
|
||||
var version = Meta!.Version;
|
||||
if( ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16 )
|
||||
if( Custom.ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16 )
|
||||
&& version != Meta.Version )
|
||||
{
|
||||
Meta.Version = version;
|
||||
|
|
@ -112,7 +114,7 @@ namespace Penumbra.UI
|
|||
|
||||
ImGui.SameLine();
|
||||
var author = Meta!.Author;
|
||||
if( ImGuiCustom.InputOrText( _editMode, LabelEditAuthor, ref author, 64 )
|
||||
if( Custom.ImGuiCustom.InputOrText( _editMode, LabelEditAuthor, ref author, 64 )
|
||||
&& author != Meta.Author )
|
||||
{
|
||||
Meta.Author = author;
|
||||
|
|
@ -130,7 +132,7 @@ namespace Penumbra.UI
|
|||
ImGui.TextColored( GreyColor, "from" );
|
||||
ImGui.SameLine();
|
||||
var website = Meta!.Website;
|
||||
if( ImGuiCustom.ResizingTextInput( LabelEditWebsite, ref website, 512 )
|
||||
if( Custom.ImGuiCustom.ResizingTextInput( LabelEditWebsite, ref website, 512 )
|
||||
&& website != Meta.Website )
|
||||
{
|
||||
Meta.Website = website;
|
||||
|
|
@ -191,15 +193,34 @@ namespace Penumbra.UI
|
|||
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()
|
||||
{
|
||||
var enabled = Mod!.Enabled;
|
||||
var enabled = Mod!.Settings.Enabled;
|
||||
if( ImGui.Checkbox( LabelModEnabled, ref enabled ) )
|
||||
{
|
||||
Mod.Enabled = enabled;
|
||||
var modManager = Service< ModManager >.Get();
|
||||
modManager.Mods!.Save();
|
||||
modManager.CalculateEffectiveFileList();
|
||||
Mod.Settings.Enabled = enabled;
|
||||
var collection = _modManager.CurrentCollection;
|
||||
collection.Save( _base._plugin.PluginInterface! );
|
||||
collection.CalculateEffectiveFileList( _modManager.BasePath, Mod.Data.Resources.MetaManipulations.Count > 0 );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +233,7 @@ namespace Penumbra.UI
|
|||
{
|
||||
if( ImGui.Button( ButtonOpenModFolder ) )
|
||||
{
|
||||
Process.Start( Mod!.Mod.ModBasePath.FullName );
|
||||
Process.Start( Mod!.Data.BasePath.FullName );
|
||||
}
|
||||
|
||||
if( ImGui.IsItemHovered() )
|
||||
|
|
@ -224,7 +245,98 @@ namespace Penumbra.UI
|
|||
private string _newName = "";
|
||||
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;
|
||||
_keyboardFocus |= !ImGui.IsPopupOpen( PopupRenameFolder );
|
||||
|
|
@ -237,121 +349,38 @@ namespace Penumbra.UI
|
|||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
var newName = Mod!.FolderName;
|
||||
var newName = Mod!.Data.BasePath.Name;
|
||||
|
||||
if( _keyboardFocus )
|
||||
{
|
||||
PluginLog.Log( "Fuck you" );
|
||||
ImGui.SetKeyboardFocusHere();
|
||||
_keyboardFocus = false;
|
||||
}
|
||||
|
||||
if( ImGui.InputText( "New Folder Name##RenameFolderInput", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
|
||||
{
|
||||
_newName = newName.RemoveNonAsciiSymbols().RemoveInvalidPathSymbols();
|
||||
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();
|
||||
}
|
||||
}
|
||||
RenameModFolder( newName );
|
||||
}
|
||||
|
||||
ImGui.TextColored( GreyColor,
|
||||
"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 );
|
||||
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 );
|
||||
|
||||
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 )
|
||||
if( OverwriteDirPopup() )
|
||||
{
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void DrawRenameModFolderButton()
|
||||
{
|
||||
DrawRenameModFolderPopup();
|
||||
if( ImGui.Button( ButtonRenameModFolder ) )
|
||||
{
|
||||
ImGui.OpenPopup( PopupRenameFolder );
|
||||
|
|
@ -367,7 +396,8 @@ namespace Penumbra.UI
|
|||
{
|
||||
if( ImGui.Button( ButtonEditJson ) )
|
||||
{
|
||||
Process.Start( _selector.SaveCurrentMod() );
|
||||
_selector.SaveCurrentMod();
|
||||
Process.Start( Mod!.Data.MetaFile.FullName );
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if( ImGui.Button( ButtonDeduplicate ) )
|
||||
{
|
||||
ModCleanup.Deduplicate( Mod!.Mod.ModBasePath, Meta! );
|
||||
ModCleanup.Deduplicate( Mod!.Data.BasePath, Meta! );
|
||||
_selector.SaveCurrentMod();
|
||||
Mod.Mod.RefreshModFiles();
|
||||
Service< ModManager >.Get().CalculateEffectiveFileList();
|
||||
_selector.ReloadCurrentMod();
|
||||
}
|
||||
|
||||
if( ImGui.IsItemHovered() )
|
||||
|
|
@ -409,10 +452,9 @@ namespace Penumbra.UI
|
|||
{
|
||||
if( ImGui.Button( ButtonNormalize ) )
|
||||
{
|
||||
ModCleanup.Normalize( Mod!.Mod.ModBasePath, Meta! );
|
||||
ModCleanup.Normalize( Mod!.Data.BasePath, Meta! );
|
||||
_selector.SaveCurrentMod();
|
||||
Mod.Mod.RefreshModFiles();
|
||||
Service< ModManager >.Get().CalculateEffectiveFileList();
|
||||
_selector.ReloadCurrentMod();
|
||||
}
|
||||
|
||||
if( ImGui.IsItemHovered() )
|
||||
|
|
@ -430,6 +472,8 @@ namespace Penumbra.UI
|
|||
DrawEditJsonButton();
|
||||
ImGui.SameLine();
|
||||
DrawReloadJsonButton();
|
||||
|
||||
DrawResetMetaButton();
|
||||
ImGui.SameLine();
|
||||
DrawDeduplicateButton();
|
||||
ImGui.SameLine();
|
||||
|
|
@ -454,9 +498,11 @@ namespace Penumbra.UI
|
|||
DrawHeaderLine();
|
||||
|
||||
// Next line with fixed distance.
|
||||
ImGuiCustom.VerticalDistance( HeaderLineDistance );
|
||||
Custom.ImGuiCustom.VerticalDistance( HeaderLineDistance );
|
||||
|
||||
DrawEnabledMark();
|
||||
ImGui.SameLine();
|
||||
DrawPriority();
|
||||
if( _base._plugin!.Configuration!.ShowAdvanced )
|
||||
{
|
||||
ImGui.SameLine();
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.Importer;
|
||||
using Penumbra.Models;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.UI
|
||||
{
|
||||
|
|
@ -16,80 +17,92 @@ namespace Penumbra.UI
|
|||
{
|
||||
private class Selector
|
||||
{
|
||||
[Flags]
|
||||
private enum ModFilter
|
||||
{
|
||||
Enabled = 1 << 0,
|
||||
Disabled = 1 << 1,
|
||||
NoConflict = 1 << 2,
|
||||
SolvedConflict = 1 << 3,
|
||||
UnsolvedConflict = 1 << 4,
|
||||
HasNoMetaManipulations = 1 << 5,
|
||||
HasMetaManipulations = 1 << 6,
|
||||
HasNoFileSwaps = 1 << 7,
|
||||
HasFileSwaps = 1 << 8,
|
||||
HasConfig = 1 << 9,
|
||||
HasNoConfig = 1 << 10,
|
||||
HasNoFiles = 1 << 11,
|
||||
HasFiles = 1 << 12,
|
||||
};
|
||||
|
||||
private const ModFilter UnfilteredStateMods = ( ModFilter )( ( 1 << 13 ) - 1 );
|
||||
|
||||
private static readonly Dictionary< ModFilter, string > ModFilterNames = new()
|
||||
{
|
||||
{ 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 LabelPriorityPopup = "Priority";
|
||||
private const string LabelAddModPopup = "AddMod";
|
||||
private const string TooltipModFilter = "Filter mods for those containing the given substring.";
|
||||
private const string TooltipMoveDown = "Move the selected mod down in priority";
|
||||
private const string TooltipMoveUp = "Move the selected mod up in priority";
|
||||
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 string DescPriorityPopup = "New Priority:";
|
||||
|
||||
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( 60, 0 );
|
||||
private static readonly string ArrowUpString = FontAwesomeIcon.ArrowUp.ToIconString();
|
||||
private static readonly string ArrowDownString = FontAwesomeIcon.ArrowDown.ToIconString();
|
||||
private static readonly Vector2 SelectorButtonSizes = new( 120, 0 );
|
||||
|
||||
private readonly SettingsInterface _base;
|
||||
private readonly ModManager _modManager;
|
||||
|
||||
private static ModCollection? Mods
|
||||
=> Service< ModManager >.Get().Mods;
|
||||
private List< Mod.Mod >? Mods
|
||||
=> _modManager.CurrentCollection.Cache?.AvailableMods;
|
||||
|
||||
public ModInfo? Mod { get; private set; }
|
||||
public Mod.Mod? Mod { get; private set; }
|
||||
private int _index;
|
||||
private int? _deleteIndex;
|
||||
private string _modFilter = "";
|
||||
private string[]? _modNamesLower;
|
||||
|
||||
private string[] _modNamesLower;
|
||||
private ModFilter _stateFilter = UnfilteredStateMods;
|
||||
|
||||
public Selector( SettingsInterface ui )
|
||||
{
|
||||
_base = ui;
|
||||
_modNamesLower = Array.Empty< string >();
|
||||
_modManager = Service<ModManager>.Get();
|
||||
ResetModNamesLower();
|
||||
}
|
||||
|
||||
public void ResetModNamesLower()
|
||||
{
|
||||
_modNamesLower = Mods?.ModSettings?.Where( I => I.Mod != null )
|
||||
.Select( I => I.Mod!.Meta.Name.ToLowerInvariant() ).ToArray()
|
||||
?? new string[] { };
|
||||
_modNamesLower = Mods?.Select( m => m.Data.Meta.Name.ToLowerInvariant() ).ToArray()
|
||||
?? Array.Empty< string >();
|
||||
}
|
||||
|
||||
private void DrawPriorityChangeButton( string iconString, bool up, int unavailableWhen )
|
||||
public void RenameCurrentModLower( string newName )
|
||||
{
|
||||
ImGui.PushFont( UiBuilder.IconFont );
|
||||
if( _index != unavailableWhen )
|
||||
if( _index >= 0 )
|
||||
{
|
||||
if( ImGui.Button( iconString, SelectorButtonSizes ) )
|
||||
{
|
||||
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
|
||||
);
|
||||
_modNamesLower[ _index ] = newName.ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +140,7 @@ namespace Penumbra.UI
|
|||
{
|
||||
try
|
||||
{
|
||||
var newDir = TexToolsImport.CreateModFolder( new DirectoryInfo( _base._plugin.Configuration!.CurrentCollection ),
|
||||
var newDir = TexToolsImport.CreateModFolder( new DirectoryInfo( _base._plugin.Configuration!.ModDirectory ),
|
||||
newName );
|
||||
var modMeta = new ModMeta
|
||||
{
|
||||
|
|
@ -135,9 +148,10 @@ namespace Penumbra.UI
|
|||
Name = newName,
|
||||
Description = string.Empty,
|
||||
};
|
||||
var metaPath = Path.Combine( newDir.FullName, "meta.json" );
|
||||
File.WriteAllText( metaPath, JsonConvert.SerializeObject( modMeta, Formatting.Indented ) );
|
||||
_base.ReloadMods();
|
||||
|
||||
var metaFile = new FileInfo( Path.Combine( newDir.FullName, "meta.json" ) );
|
||||
modMeta.SaveToFile( metaFile );
|
||||
_modManager.AddMod( newDir );
|
||||
SelectModByDir( newDir.Name );
|
||||
}
|
||||
catch( Exception e )
|
||||
|
|
@ -174,7 +188,7 @@ namespace Penumbra.UI
|
|||
|
||||
private void DrawModsSelectorFilter()
|
||||
{
|
||||
ImGui.SetNextItemWidth( SelectorButtonSizes.X * 4 );
|
||||
ImGui.SetNextItemWidth( SelectorButtonSizes.X * 2 - 22 );
|
||||
var tmp = _modFilter;
|
||||
if( ImGui.InputTextWithHint( LabelModFilter, "Filter Mods...", ref tmp, 256 ) )
|
||||
{
|
||||
|
|
@ -185,6 +199,26 @@ namespace Penumbra.UI
|
|||
{
|
||||
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()
|
||||
|
|
@ -193,10 +227,6 @@ namespace Penumbra.UI
|
|||
ImGui.PushStyleVar( ImGuiStyleVar.WindowPadding, ZeroVector );
|
||||
ImGui.PushStyleVar( ImGuiStyleVar.FrameRounding, 0 );
|
||||
|
||||
DrawPriorityChangeButton( ArrowUpString, false, 0 );
|
||||
ImGui.SameLine();
|
||||
DrawPriorityChangeButton( ArrowDownString, true, Mods?.ModSettings?.Count - 1 ?? 0 );
|
||||
ImGui.SameLine();
|
||||
DrawModTrashButton();
|
||||
ImGui.SameLine();
|
||||
DrawModAddButton();
|
||||
|
|
@ -221,7 +251,7 @@ namespace Penumbra.UI
|
|||
return;
|
||||
}
|
||||
|
||||
if( Mod?.Mod == null )
|
||||
if( Mod == null )
|
||||
{
|
||||
ImGui.CloseCurrentPopup();
|
||||
ImGui.EndPopup();
|
||||
|
|
@ -231,16 +261,15 @@ namespace Penumbra.UI
|
|||
ImGui.Text( "Are you sure you want to delete the following mod:" );
|
||||
// todo: why the fuck does this become null??????
|
||||
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 );
|
||||
|
||||
var buttonSize = new Vector2( 120, 0 );
|
||||
if( ImGui.Button( ButtonYesDelete, buttonSize ) )
|
||||
{
|
||||
ImGui.CloseCurrentPopup();
|
||||
Service< ModManager >.Get().DeleteMod( Mod?.Mod );
|
||||
_modManager.DeleteMod( Mod.Data.BasePath );
|
||||
ClearSelection();
|
||||
_base.ReloadMods();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -254,7 +283,93 @@ namespace Penumbra.UI
|
|||
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()
|
||||
{
|
||||
|
|
@ -271,61 +386,9 @@ namespace Penumbra.UI
|
|||
// Inlay selector list
|
||||
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++ )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
DrawMod( Mods[ modIndex ], modIndex );
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
|
|
@ -334,52 +397,9 @@ namespace Penumbra.UI
|
|||
ImGui.EndGroup();
|
||||
|
||||
DrawDeleteModal();
|
||||
DrawPriorityPopup();
|
||||
}
|
||||
|
||||
private void DrawPriorityPopup()
|
||||
{
|
||||
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 )
|
||||
private void SetSelection( int idx, Mod.Mod? info )
|
||||
{
|
||||
Mod = info;
|
||||
if( idx != _index )
|
||||
|
|
@ -393,7 +413,7 @@ namespace Penumbra.UI
|
|||
|
||||
private void SetSelection( int idx )
|
||||
{
|
||||
if( idx >= ( Mods?.ModSettings?.Count ?? 0 ) )
|
||||
if( idx >= ( Mods?.Count ?? 0 ) )
|
||||
{
|
||||
idx = -1;
|
||||
}
|
||||
|
|
@ -404,58 +424,45 @@ namespace Penumbra.UI
|
|||
}
|
||||
else
|
||||
{
|
||||
SetSelection( idx, Mods!.ModSettings![ idx ] );
|
||||
SetSelection( idx, Mods![ idx ] );
|
||||
}
|
||||
}
|
||||
|
||||
public void ReloadSelection()
|
||||
=> SetSelection( _index, Mods![ _index ] );
|
||||
|
||||
public void ClearSelection()
|
||||
=> SetSelection( -1 );
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
private string GetCurrentModMetaFile()
|
||||
=> 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()
|
||||
public void ReloadCurrentMod( bool recomputeMeta = false )
|
||||
{
|
||||
if( Mod == null )
|
||||
{
|
||||
return "";
|
||||
return;
|
||||
}
|
||||
|
||||
var metaPath = GetCurrentModMetaFile();
|
||||
if( metaPath.Length > 0 )
|
||||
if( _index >= 0 && _modManager.UpdateMod( Mod.Data, recomputeMeta ) )
|
||||
{
|
||||
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.Text.RegularExpressions;
|
||||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using Penumbra.Hooks;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.UI
|
||||
{
|
||||
|
|
@ -13,7 +17,6 @@ namespace Penumbra.UI
|
|||
private const string LabelRediscoverButton = "Rediscover Mods";
|
||||
private const string LabelOpenFolder = "Open Mods Folder";
|
||||
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 LabelLogLoadedFiles = "Log all loaded files";
|
||||
private const string LabelDisableNotifications = "Disable filesystem change notifications";
|
||||
|
|
@ -33,10 +36,10 @@ namespace Penumbra.UI
|
|||
|
||||
private void DrawRootFolder()
|
||||
{
|
||||
var basePath = _config.CurrentCollection;
|
||||
if( ImGui.InputText( LabelRootFolder, ref basePath, 255 ) && _config.CurrentCollection != basePath )
|
||||
var basePath = _config.ModDirectory;
|
||||
if( ImGui.InputText( LabelRootFolder, ref basePath, 255 ) && _config.ModDirectory != basePath )
|
||||
{
|
||||
_config.CurrentCollection = basePath;
|
||||
_config.ModDirectory = basePath;
|
||||
_configChanged = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +57,7 @@ namespace Penumbra.UI
|
|||
{
|
||||
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()
|
||||
{
|
||||
var showAdvanced = _config.ShowAdvanced;
|
||||
|
|
@ -91,10 +83,22 @@ namespace Penumbra.UI
|
|||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
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,7 +131,7 @@ namespace Penumbra.UI
|
|||
}
|
||||
}
|
||||
|
||||
private void DrawReloadResourceButton()
|
||||
private static void DrawReloadResourceButton()
|
||||
{
|
||||
if( ImGui.Button( LabelReloadResource ) )
|
||||
{
|
||||
|
|
@ -157,13 +161,10 @@ namespace Penumbra.UI
|
|||
ImGui.SameLine();
|
||||
DrawOpenModsButton();
|
||||
|
||||
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
|
||||
Custom.ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
|
||||
DrawEnabledBox();
|
||||
|
||||
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
|
||||
DrawInvertModOrderBox();
|
||||
|
||||
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
|
||||
Custom.ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
|
||||
DrawShowAdvancedBox();
|
||||
|
||||
if( _config.ShowAdvanced )
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.IO;
|
||||
using System.Numerics;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.UI
|
||||
{
|
||||
|
|
@ -25,7 +26,8 @@ namespace Penumbra.UI
|
|||
_menu = new SettingsMenu( this );
|
||||
}
|
||||
|
||||
public void FlipVisibility() => _menu.Visible = !_menu.Visible;
|
||||
public void FlipVisibility()
|
||||
=> _menu.Visible = !_menu.Visible;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
|
|
@ -39,10 +41,10 @@ namespace Penumbra.UI
|
|||
_menu.InstalledTab.Selector.ResetModNamesLower();
|
||||
_menu.InstalledTab.Selector.ClearSelection();
|
||||
// create the directory if it doesn't exist
|
||||
Directory.CreateDirectory( _plugin!.Configuration!.CurrentCollection );
|
||||
Directory.CreateDirectory( _plugin!.Configuration!.ModDirectory );
|
||||
|
||||
var modManager = Service< ModManager >.Get();
|
||||
modManager.DiscoverMods( _plugin.Configuration.CurrentCollection );
|
||||
modManager.DiscoverMods( new DirectoryInfo( _plugin.Configuration.ModDirectory ) );
|
||||
_menu.InstalledTab.Selector.ResetModNamesLower();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@ namespace Penumbra.UI
|
|||
private readonly TabSettings _settingsTab;
|
||||
private readonly TabImport _importTab;
|
||||
private readonly TabBrowser _browserTab;
|
||||
private readonly TabCollections _collectionsTab;
|
||||
public readonly TabInstalled InstalledTab;
|
||||
public readonly TabEffective EffectiveTab;
|
||||
private readonly TabEffective _effectiveTab;
|
||||
|
||||
public SettingsMenu( SettingsInterface ui )
|
||||
{
|
||||
|
|
@ -26,7 +27,8 @@ namespace Penumbra.UI
|
|||
_importTab = new TabImport( _base );
|
||||
_browserTab = new TabBrowser();
|
||||
InstalledTab = new TabInstalled( _base );
|
||||
EffectiveTab = new TabEffective();
|
||||
_collectionsTab = new TabCollections( InstalledTab.Selector );
|
||||
_effectiveTab = new TabEffective();
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
|
@ -57,6 +59,7 @@ namespace Penumbra.UI
|
|||
ImGui.BeginTabBar( PenumbraSettingsLabel );
|
||||
|
||||
_settingsTab.Draw();
|
||||
_collectionsTab.Draw();
|
||||
_importTab.Draw();
|
||||
|
||||
if( !_importTab.IsImporting() )
|
||||
|
|
@ -66,7 +69,7 @@ namespace Penumbra.UI
|
|||
|
||||
if( _base._plugin!.Configuration!.ShowAdvanced )
|
||||
{
|
||||
EffectiveTab.Draw();
|
||||
_effectiveTab.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Penumbra
|
||||
namespace Penumbra.Util
|
||||
{
|
||||
public static class ArrayExtensions
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Penumbra.Util
|
||||
{
|
||||
|
|
@ -38,7 +36,7 @@ namespace Penumbra.Util
|
|||
|
||||
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 span = new ReadOnlySpan< byte >( data, offset, size );
|
||||
|
|
@ -57,7 +55,7 @@ namespace Penumbra.Util
|
|||
// im a pirate arr
|
||||
var arr = new T[count];
|
||||
|
||||
for( int i = 0; i < count; i++ )
|
||||
for( var i = 0; i < count; i++ )
|
||||
{
|
||||
var offset = size * i;
|
||||
var span = new ReadOnlySpan< byte >( data, offset, size );
|
||||
|
|
@ -76,9 +74,7 @@ namespace Penumbra.Util
|
|||
/// <param name="offset">The offset to read a string starting from.</param>
|
||||
/// <returns></returns>
|
||||
public static string ReadStringOffsetData( this BinaryReader br, long offset )
|
||||
{
|
||||
return Encoding.UTF8.GetString( ReadRawOffsetData( br, offset ) );
|
||||
}
|
||||
=> Encoding.UTF8.GetString( ReadRawOffsetData( br, offset ) );
|
||||
|
||||
/// <summary>
|
||||
/// Moves the BinaryReader position to offset, reads raw bytes until a null byte, then
|
||||
|
|
@ -108,7 +104,8 @@ namespace Penumbra.Util
|
|||
/// <summary>
|
||||
/// Seeks this BinaryReader's position to the given offset. Syntactic sugar.
|
||||
/// </summary>
|
||||
public static void Seek( this BinaryReader br, long offset ) {
|
||||
public static void Seek( this BinaryReader br, long offset )
|
||||
{
|
||||
br.BaseStream.Position = offset;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ namespace Penumbra.Util
|
|||
return k;
|
||||
} ).ToArray();
|
||||
|
||||
public uint Checksum => ~_crc32;
|
||||
public uint Checksum
|
||||
=> ~_crc32;
|
||||
|
||||
private uint _crc32 = 0xFFFFFFFF;
|
||||
|
||||
|
|
@ -50,8 +51,7 @@ namespace Penumbra.Util
|
|||
[MethodImpl( MethodImplOptions.AggressiveInlining )]
|
||||
public void Update( byte b )
|
||||
{
|
||||
_crc32 = CrcArray[ ( _crc32 ^ b ) & 0xFF ] ^
|
||||
( ( _crc32 >> 8 ) & 0x00FFFFFF );
|
||||
_crc32 = CrcArray[ ( _crc32 ^ b ) & 0xFF ] ^ ( ( _crc32 >> 8 ) & 0x00FFFFFF );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Penumbra
|
||||
namespace Penumbra.Util
|
||||
{
|
||||
public static class DialogExtensions
|
||||
{
|
||||
|
|
@ -38,7 +38,8 @@ namespace Penumbra
|
|||
{
|
||||
public IntPtr Handle { get; set; }
|
||||
|
||||
public DialogHandle( IntPtr handle ) => Handle = handle;
|
||||
public DialogHandle( IntPtr handle )
|
||||
=> Handle = handle;
|
||||
}
|
||||
|
||||
public class HiddenForm : Form
|
||||
|
|
@ -1,9 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Penumbra.Util
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace Penumbra
|
||||
namespace Penumbra.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// Basic service locator
|
||||
|
|
@ -11,8 +11,7 @@ namespace Penumbra
|
|||
private static T? _object;
|
||||
|
||||
static Service()
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
public static void Set( T obj )
|
||||
{
|
||||
|
|
@ -7,7 +7,8 @@ namespace Penumbra.Util
|
|||
{
|
||||
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 )
|
||||
{
|
||||
|
|
@ -24,7 +25,8 @@ namespace Penumbra.Util
|
|||
: new HashSet< T >();
|
||||
}
|
||||
|
||||
public override bool CanWrite => true;
|
||||
public override bool CanWrite
|
||||
=> true;
|
||||
|
||||
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
|
||||
{
|
||||
|
|
@ -37,6 +39,7 @@ namespace Penumbra.Util
|
|||
serializer.Serialize( writer, val?.ToString() );
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Penumbra
|
||||
namespace Penumbra.Util
|
||||
{
|
||||
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 = "_" )
|
||||
=> string.Join( replacement, s.Split( _invalid ) );
|
||||
=> string.Join( replacement, s.Split( Invalid ) );
|
||||
|
||||
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 = "_" )
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue