Update for 6.4, 0.1.2.0

This commit is contained in:
Ottermandias 2023-05-25 01:54:09 +02:00
parent 956e1fc1a7
commit f97829ab7f
8 changed files with 187 additions and 200 deletions

View file

@ -1,181 +1,183 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.PlayerWatch;
namespace Glamourer
namespace Glamourer;
public static class WriteExtensions
{
public static class WriteExtensions
private static unsafe void Write(IntPtr characterPtr, EquipSlot slot, SetId? id, WeaponType? type, ushort? variant, StainId? stain)
{
private static unsafe void Write(IntPtr characterPtr, EquipSlot slot, SetId? id, WeaponType? type, ushort? variant, StainId? stain)
void WriteWeapon(int offset)
{
void WriteWeapon(int offset)
{
var address = (byte*) characterPtr + offset;
if (id.HasValue)
*(ushort*) address = (ushort) id.Value;
var address = (byte*)characterPtr + offset;
if (id.HasValue)
*(ushort*)address = (ushort)id.Value;
if (type.HasValue)
*(ushort*) (address + 2) = (ushort) type.Value;
if (type.HasValue)
*(ushort*)(address + 2) = (ushort)type.Value;
if (variant.HasValue)
*(ushort*) (address + 4) = variant.Value;
if (variant.HasValue)
*(ushort*)(address + 4) = variant.Value;
if (*(ushort*) address == 0)
*(address + 6) = 0;
else if (stain.HasValue)
*(address + 6) = (byte) stain.Value;
}
void WriteEquip(int offset)
{
var address = (byte*) characterPtr + offset;
if (id.HasValue)
*(ushort*) address = (ushort) id.Value;
if (variant < byte.MaxValue)
*(address + 2) = (byte) variant.Value;
if (stain.HasValue)
*(address + 3) = (byte) stain.Value;
}
switch (slot)
{
case EquipSlot.MainHand:
WriteWeapon(CharacterEquipment.MainWeaponOffset);
break;
case EquipSlot.OffHand:
WriteWeapon(CharacterEquipment.OffWeaponOffset);
break;
case EquipSlot.Head:
WriteEquip(CharacterEquipment.EquipmentOffset);
break;
case EquipSlot.Body:
WriteEquip(CharacterEquipment.EquipmentOffset + 4);
break;
case EquipSlot.Hands:
WriteEquip(CharacterEquipment.EquipmentOffset + 8);
break;
case EquipSlot.Legs:
WriteEquip(CharacterEquipment.EquipmentOffset + 12);
break;
case EquipSlot.Feet:
WriteEquip(CharacterEquipment.EquipmentOffset + 16);
break;
case EquipSlot.Ears:
WriteEquip(CharacterEquipment.EquipmentOffset + 20);
break;
case EquipSlot.Neck:
WriteEquip(CharacterEquipment.EquipmentOffset + 24);
break;
case EquipSlot.Wrists:
WriteEquip(CharacterEquipment.EquipmentOffset + 28);
break;
case EquipSlot.RFinger:
WriteEquip(CharacterEquipment.EquipmentOffset + 32);
break;
case EquipSlot.LFinger:
WriteEquip(CharacterEquipment.EquipmentOffset + 36);
break;
default: throw new InvalidEnumArgumentException();
}
if (*(ushort*)address == 0)
*(address + 6) = 0;
else if (stain.HasValue)
*(address + 6) = (byte)stain.Value;
}
public static void Write(this Stain stain, IntPtr characterPtr, EquipSlot slot)
=> Write(characterPtr, slot, null, null, null, stain.RowIndex);
public static void Write(this Item item, IntPtr characterAddress)
void WriteEquip(int offset)
{
var (id, type, variant) = item.MainModel;
Write(characterAddress, item.EquippableTo, id, type, variant, null);
if (item.EquippableTo == EquipSlot.MainHand && item.HasSubModel)
{
var (subId, subType, subVariant) = item.SubModel;
Write(characterAddress, EquipSlot.OffHand, subId, subType, subVariant, null);
}
var address = (byte*)characterPtr + offset;
if (id.HasValue)
*(ushort*)address = (ushort)id.Value;
if (variant < byte.MaxValue)
*(address + 2) = (byte)variant.Value;
if (stain.HasValue)
*(address + 3) = (byte)stain.Value;
}
public static void Write(this CharacterArmor armor, IntPtr characterAddress, EquipSlot slot)
=> Write(characterAddress, slot, armor.Set, null, armor.Variant, armor.Stain);
public static void Write(this CharacterWeapon weapon, IntPtr characterAddress, EquipSlot slot)
=> Write(characterAddress, slot, weapon.Set, weapon.Type, weapon.Variant, weapon.Stain);
public static unsafe void Write(this CharacterEquipment equip, IntPtr characterAddress)
var drawDataOffset = (int) Marshal.OffsetOf<Character>(nameof(Character.DrawData));
switch (slot)
{
if (equip.IsSet == 0)
return;
Write(characterAddress, EquipSlot.MainHand, equip.MainHand.Set, equip.MainHand.Type, equip.MainHand.Variant, equip.MainHand.Stain);
Write(characterAddress, EquipSlot.OffHand, equip.OffHand.Set, equip.OffHand.Type, equip.OffHand.Variant, equip.OffHand.Stain);
fixed (CharacterArmor* equipment = &equip.Head)
{
Buffer.MemoryCopy(equipment, (byte*) characterAddress + CharacterEquipment.EquipmentOffset,
CharacterEquipment.EquipmentSlots * sizeof(CharacterArmor), CharacterEquipment.EquipmentSlots * sizeof(CharacterArmor));
}
}
public static void Write(this CharacterEquipment equip, IntPtr characterAddress, CharacterEquipMask models, CharacterEquipMask stains)
{
if (models == CharacterEquipMask.All && stains == CharacterEquipMask.All)
{
equip.Write(characterAddress);
return;
}
if (models.HasFlag(CharacterEquipMask.MainHand))
Write(characterAddress, EquipSlot.MainHand, equip.MainHand.Set, equip.MainHand.Type, equip.MainHand.Variant, null);
if (stains.HasFlag(CharacterEquipMask.MainHand))
Write(characterAddress, EquipSlot.MainHand, null, null, null, equip.MainHand.Stain);
if (models.HasFlag(CharacterEquipMask.OffHand))
Write(characterAddress, EquipSlot.OffHand, equip.OffHand.Set, equip.OffHand.Type, equip.OffHand.Variant, null);
if (stains.HasFlag(CharacterEquipMask.OffHand))
Write(characterAddress, EquipSlot.OffHand, null, null, null, equip.OffHand.Stain);
if (models.HasFlag(CharacterEquipMask.Head))
Write(characterAddress, EquipSlot.Head, equip.Head.Set, null, equip.Head.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Head))
Write(characterAddress, EquipSlot.Head, null, null, null, equip.Head.Stain);
if (models.HasFlag(CharacterEquipMask.Body))
Write(characterAddress, EquipSlot.Body, equip.Body.Set, null, equip.Body.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Body))
Write(characterAddress, EquipSlot.Body, null, null, null, equip.Body.Stain);
if (models.HasFlag(CharacterEquipMask.Hands))
Write(characterAddress, EquipSlot.Hands, equip.Hands.Set, null, equip.Hands.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Hands))
Write(characterAddress, EquipSlot.Hands, null, null, null, equip.Hands.Stain);
if (models.HasFlag(CharacterEquipMask.Legs))
Write(characterAddress, EquipSlot.Legs, equip.Legs.Set, null, equip.Legs.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Legs))
Write(characterAddress, EquipSlot.Legs, null, null, null, equip.Legs.Stain);
if (models.HasFlag(CharacterEquipMask.Feet))
Write(characterAddress, EquipSlot.Feet, equip.Feet.Set, null, equip.Feet.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Feet))
Write(characterAddress, EquipSlot.Feet, null, null, null, equip.Feet.Stain);
if (models.HasFlag(CharacterEquipMask.Ears))
Write(characterAddress, EquipSlot.Ears, equip.Ears.Set, null, equip.Ears.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Ears))
Write(characterAddress, EquipSlot.Ears, null, null, null, equip.Ears.Stain);
if (models.HasFlag(CharacterEquipMask.Neck))
Write(characterAddress, EquipSlot.Neck, equip.Neck.Set, null, equip.Neck.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Neck))
Write(characterAddress, EquipSlot.Neck, null, null, null, equip.Neck.Stain);
if (models.HasFlag(CharacterEquipMask.Wrists))
Write(characterAddress, EquipSlot.Wrists, equip.Wrists.Set, null, equip.Wrists.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Wrists))
Write(characterAddress, EquipSlot.Wrists, null, null, null, equip.Wrists.Stain);
if (models.HasFlag(CharacterEquipMask.LFinger))
Write(characterAddress, EquipSlot.LFinger, equip.LFinger.Set, null, equip.LFinger.Variant, null);
if (stains.HasFlag(CharacterEquipMask.LFinger))
Write(characterAddress, EquipSlot.LFinger, null, null, null, equip.LFinger.Stain);
if (models.HasFlag(CharacterEquipMask.RFinger))
Write(characterAddress, EquipSlot.RFinger, equip.RFinger.Set, null, equip.RFinger.Variant, null);
if (stains.HasFlag(CharacterEquipMask.RFinger))
Write(characterAddress, EquipSlot.RFinger, null, null, null, equip.RFinger.Stain);
case EquipSlot.MainHand:
WriteWeapon(drawDataOffset + (int)Marshal.OffsetOf<DrawDataContainer>(nameof(DrawDataContainer.MainHandModel)));
break;
case EquipSlot.OffHand:
WriteWeapon(drawDataOffset + (int)Marshal.OffsetOf<DrawDataContainer>(nameof(DrawDataContainer.OffHandModel)));
break;
case EquipSlot.Head:
WriteEquip(drawDataOffset + (int)Marshal.OffsetOf<DrawDataContainer>(nameof(DrawDataContainer.Head)));
break;
case EquipSlot.Body:
WriteEquip(drawDataOffset + (int)Marshal.OffsetOf<DrawDataContainer>(nameof(DrawDataContainer.Top)));
break;
case EquipSlot.Hands:
WriteEquip(drawDataOffset + (int)Marshal.OffsetOf<DrawDataContainer>(nameof(DrawDataContainer.Arms)));
break;
case EquipSlot.Legs:
WriteEquip(drawDataOffset + (int)Marshal.OffsetOf<DrawDataContainer>(nameof(DrawDataContainer.Legs)));
break;
case EquipSlot.Feet:
WriteEquip(drawDataOffset + (int)Marshal.OffsetOf<DrawDataContainer>(nameof(DrawDataContainer.Feet)));
break;
case EquipSlot.Ears:
WriteEquip(drawDataOffset + (int)Marshal.OffsetOf<DrawDataContainer>(nameof(DrawDataContainer.Ear)));
break;
case EquipSlot.Neck:
WriteEquip(drawDataOffset + (int)Marshal.OffsetOf<DrawDataContainer>(nameof(DrawDataContainer.Neck)));
break;
case EquipSlot.Wrists:
WriteEquip(drawDataOffset + (int)Marshal.OffsetOf<DrawDataContainer>(nameof(DrawDataContainer.Wrist)));
break;
case EquipSlot.RFinger:
WriteEquip(drawDataOffset + (int)Marshal.OffsetOf<DrawDataContainer>(nameof(DrawDataContainer.RFinger)));
break;
case EquipSlot.LFinger:
WriteEquip(drawDataOffset + (int)Marshal.OffsetOf<DrawDataContainer>(nameof(DrawDataContainer.LFinger)));
break;
default: throw new InvalidEnumArgumentException();
}
}
public static void Write(this Stain stain, IntPtr characterPtr, EquipSlot slot)
=> Write(characterPtr, slot, null, null, null, stain.RowIndex);
public static void Write(this Item item, IntPtr characterAddress)
{
var (id, type, variant) = item.MainModel;
Write(characterAddress, item.EquippableTo, id, type, variant, null);
if (item.EquippableTo == EquipSlot.MainHand && item.HasSubModel)
{
var (subId, subType, subVariant) = item.SubModel;
Write(characterAddress, EquipSlot.OffHand, subId, subType, subVariant, null);
}
}
public static void Write(this CharacterArmor armor, IntPtr characterAddress, EquipSlot slot)
=> Write(characterAddress, slot, armor.Set, null, armor.Variant, armor.Stain);
public static void Write(this CharacterWeapon weapon, IntPtr characterAddress, EquipSlot slot)
=> Write(characterAddress, slot, weapon.Set, weapon.Type, weapon.Variant, weapon.Stain);
public static unsafe void Write(this CharacterEquipment equip, IntPtr characterAddress)
{
if (equip.IsSet == 0)
return;
Write(characterAddress, EquipSlot.MainHand, equip.MainHand.Set, equip.MainHand.Type, equip.MainHand.Variant, equip.MainHand.Stain);
Write(characterAddress, EquipSlot.OffHand, equip.OffHand.Set, equip.OffHand.Type, equip.OffHand.Variant, equip.OffHand.Stain);
fixed (CharacterArmor* equipment = &equip.Head)
{
Buffer.MemoryCopy(equipment, &((Character*)characterAddress)->DrawData.Head,
CharacterEquipment.EquipmentSlots * sizeof(CharacterArmor), CharacterEquipment.EquipmentSlots * sizeof(CharacterArmor));
}
}
public static void Write(this CharacterEquipment equip, IntPtr characterAddress, CharacterEquipMask models, CharacterEquipMask stains)
{
if (models == CharacterEquipMask.All && stains == CharacterEquipMask.All)
{
equip.Write(characterAddress);
return;
}
if (models.HasFlag(CharacterEquipMask.MainHand))
Write(characterAddress, EquipSlot.MainHand, equip.MainHand.Set, equip.MainHand.Type, equip.MainHand.Variant, null);
if (stains.HasFlag(CharacterEquipMask.MainHand))
Write(characterAddress, EquipSlot.MainHand, null, null, null, equip.MainHand.Stain);
if (models.HasFlag(CharacterEquipMask.OffHand))
Write(characterAddress, EquipSlot.OffHand, equip.OffHand.Set, equip.OffHand.Type, equip.OffHand.Variant, null);
if (stains.HasFlag(CharacterEquipMask.OffHand))
Write(characterAddress, EquipSlot.OffHand, null, null, null, equip.OffHand.Stain);
if (models.HasFlag(CharacterEquipMask.Head))
Write(characterAddress, EquipSlot.Head, equip.Head.Set, null, equip.Head.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Head))
Write(characterAddress, EquipSlot.Head, null, null, null, equip.Head.Stain);
if (models.HasFlag(CharacterEquipMask.Body))
Write(characterAddress, EquipSlot.Body, equip.Body.Set, null, equip.Body.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Body))
Write(characterAddress, EquipSlot.Body, null, null, null, equip.Body.Stain);
if (models.HasFlag(CharacterEquipMask.Hands))
Write(characterAddress, EquipSlot.Hands, equip.Hands.Set, null, equip.Hands.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Hands))
Write(characterAddress, EquipSlot.Hands, null, null, null, equip.Hands.Stain);
if (models.HasFlag(CharacterEquipMask.Legs))
Write(characterAddress, EquipSlot.Legs, equip.Legs.Set, null, equip.Legs.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Legs))
Write(characterAddress, EquipSlot.Legs, null, null, null, equip.Legs.Stain);
if (models.HasFlag(CharacterEquipMask.Feet))
Write(characterAddress, EquipSlot.Feet, equip.Feet.Set, null, equip.Feet.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Feet))
Write(characterAddress, EquipSlot.Feet, null, null, null, equip.Feet.Stain);
if (models.HasFlag(CharacterEquipMask.Ears))
Write(characterAddress, EquipSlot.Ears, equip.Ears.Set, null, equip.Ears.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Ears))
Write(characterAddress, EquipSlot.Ears, null, null, null, equip.Ears.Stain);
if (models.HasFlag(CharacterEquipMask.Neck))
Write(characterAddress, EquipSlot.Neck, equip.Neck.Set, null, equip.Neck.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Neck))
Write(characterAddress, EquipSlot.Neck, null, null, null, equip.Neck.Stain);
if (models.HasFlag(CharacterEquipMask.Wrists))
Write(characterAddress, EquipSlot.Wrists, equip.Wrists.Set, null, equip.Wrists.Variant, null);
if (stains.HasFlag(CharacterEquipMask.Wrists))
Write(characterAddress, EquipSlot.Wrists, null, null, null, equip.Wrists.Stain);
if (models.HasFlag(CharacterEquipMask.LFinger))
Write(characterAddress, EquipSlot.LFinger, equip.LFinger.Set, null, equip.LFinger.Variant, null);
if (stains.HasFlag(CharacterEquipMask.LFinger))
Write(characterAddress, EquipSlot.LFinger, null, null, null, equip.LFinger.Stain);
if (models.HasFlag(CharacterEquipMask.RFinger))
Write(characterAddress, EquipSlot.RFinger, equip.RFinger.Set, null, equip.RFinger.Variant, null);
if (stains.HasFlag(CharacterEquipMask.RFinger))
Write(characterAddress, EquipSlot.RFinger, null, null, null, equip.RFinger.Stain);
}
}

View file

@ -1,29 +1,13 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Objects.Types;
using Penumbra.GameData.Enums;
using Character = Dalamud.Game.ClientState.Objects.Types.Character;
namespace Glamourer.Customization
{
public unsafe struct LazyCustomization
{
public CharacterCustomization* Address;
public LazyCustomization(IntPtr characterPtr)
=> Address = (CharacterCustomization*) (characterPtr + CharacterCustomization.CustomizationOffset);
public ref CharacterCustomization Value
=> ref *Address;
public LazyCustomization(CharacterCustomization data)
=> Address = &data;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CharacterCustomization
{
public const int CustomizationOffset = 0x840;
public const int CustomizationBytes = 26;
public static CharacterCustomization Default = new()
@ -150,13 +134,13 @@ namespace Glamourer.Customization
}
}
public void Read(Character character)
=> Read(character.Address + CustomizationOffset);
public unsafe void Read(Character character)
=> Read((nint) ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)character.Address)->DrawData.CustomizeData.Data);
public CharacterCustomization(Character character)
: this()
{
Read(character.Address + CustomizationOffset);
Read(character);
}
public byte this[CustomizationId id]
@ -282,7 +266,7 @@ namespace Glamourer.Customization
{
fixed (Race* ptr = &Race)
{
Buffer.MemoryCopy(ptr, (byte*) characterAddress + CustomizationOffset, CustomizationBytes, CustomizationBytes);
Buffer.MemoryCopy(ptr, ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)characterAddress)->DrawData.CustomizeData.Data, CustomizationBytes, CustomizationBytes);
}
}

View file

@ -5,8 +5,8 @@
<PlatformTarget>x64</PlatformTarget>
<RootNamespace>Glamourer</RootNamespace>
<AssemblyName>Glamourer</AssemblyName>
<FileVersion>0.1.1.5</FileVersion>
<AssemblyVersion>0.1.1.5</AssemblyVersion>
<FileVersion>0.1.2.0</FileVersion>
<AssemblyVersion>0.1.2.0</AssemblyVersion>
<Company>SoftOtter</Company>
<Product>Glamourer</Product>
<Copyright>Copyright © 2020</Copyright>

View file

@ -5,7 +5,7 @@
"Description": "Adds functionality to change and store appearance of players, customization and equip. Requires Penumbra to be installed and activated to work. Can also add preview options to the Changed Items tab for Penumbra.",
"Tags": [ "Appearance", "Glamour", "Race", "Outfit", "Armor", "Clothes", "Skins", "Customization", "Design", "Character" ],
"InternalName": "Glamourer",
"AssemblyVersion": "0.1.1.5",
"AssemblyVersion": "0.1.2.0",
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
"ApplicableVersion": "any",
"DalamudApiLevel": 8,

View file

@ -4,18 +4,18 @@ public static class Offsets
{
public static class Character
{
public const int Wetness = 0x1B1F;
public const int HatVisible = 0x85E;
public const int VisorToggled = 0x85F;
public const int WeaponHidden1 = 0x85F;
public const int WeaponHidden2 = 0x73C;
public const int Alpha = 0x1A18;
public const int Wetness = 0x1B3A;
public const int HatVisible = 0x876;
public const int VisorToggled = 0x877;
public const int WeaponHidden1 = 0x877;
public const int WeaponHidden2 = 0x754;
public const int Alpha = 0x1A4C;
public static class Flags
{
public const byte IsHatHidden = 0x01;
public const byte IsVisorToggled = 0x08;
public const byte IsWet = 0x40;
public const byte IsWet = 0x20;
public const byte IsWeaponHidden1 = 0x01;
public const byte IsWeaponHidden2 = 0x02;
}

View file

@ -10,9 +10,6 @@ namespace Penumbra.PlayerWatch;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CharacterEquipment
{
public const int MainWeaponOffset = 0x6E0;
public const int OffWeaponOffset = 0x748;
public const int EquipmentOffset = 0x818;
public const int EquipmentSlots = 10;
public const int WeaponSlots = 2;
@ -58,16 +55,16 @@ public class CharacterEquipment
private unsafe CharacterEquipment(IntPtr actorAddress)
{
IsSet = 1;
var actorPtr = (byte*)actorAddress.ToPointer();
var actorPtr = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actorAddress.ToPointer();
fixed (CharacterWeapon* main = &MainHand, off = &OffHand)
{
Buffer.MemoryCopy(actorPtr + MainWeaponOffset, main, sizeof(CharacterWeapon), sizeof(CharacterWeapon));
Buffer.MemoryCopy(actorPtr + OffWeaponOffset, off, sizeof(CharacterWeapon), sizeof(CharacterWeapon));
Buffer.MemoryCopy(&actorPtr->DrawData.MainHandModel, main, sizeof(CharacterWeapon), sizeof(CharacterWeapon));
Buffer.MemoryCopy(&actorPtr->DrawData.OffHandModel, off, sizeof(CharacterWeapon), sizeof(CharacterWeapon));
}
fixed (CharacterArmor* equipment = &Head)
{
Buffer.MemoryCopy(actorPtr + EquipmentOffset, equipment, EquipmentSlots * sizeof(CharacterArmor),
Buffer.MemoryCopy(&actorPtr->DrawData.Head, equipment, EquipmentSlots * sizeof(CharacterArmor),
EquipmentSlots * sizeof(CharacterArmor));
}
}

View file

@ -38,6 +38,10 @@
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="FFXIVClientStructs">
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>

View file

@ -6,8 +6,8 @@
"Description": "Adds functionality to change and store appearance of players, customization and equip. Requires Penumbra to be installed and activated to work. Can also add preview options to the Changed Items tab for Penumbra.",
"Tags": [ "Appearance", "Glamour", "Race", "Outfit", "Armor", "Clothes", "Skins", "Customization", "Design", "Character" ],
"InternalName": "Glamourer",
"AssemblyVersion": "0.1.1.5",
"TestingAssemblyVersion": "0.1.1.5",
"AssemblyVersion": "0.1.2.0",
"TestingAssemblyVersion": "0.1.2.0",
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
"ApplicableVersion": "any",
"DalamudApiLevel": 8,