Enums and support for parsing and interpreting game paths.

This commit is contained in:
Ottermandias 2021-03-04 17:48:37 +01:00
parent b97d0ddc37
commit 88ba14e595
8 changed files with 1074 additions and 0 deletions

View file

@ -0,0 +1,43 @@
using System.Collections.Generic;
using System.ComponentModel;
namespace Penumbra.Game
{
public enum BodySlot : byte
{
Unknown,
Hair,
Face,
Tail,
Body,
Zear
}
public static class BodySlotEnumExtension
{
public static string ToSuffix( this BodySlot value )
{
return value switch
{
BodySlot.Zear => "zear",
BodySlot.Face => "face",
BodySlot.Hair => "hair",
BodySlot.Body => "body",
BodySlot.Tail => "tail",
_ => throw new InvalidEnumArgumentException()
};
}
}
public static partial class GameData
{
public static readonly Dictionary< string, BodySlot > StringToBodySlot = new()
{
{ BodySlot.Zear.ToSuffix(), BodySlot.Zear },
{ BodySlot.Face.ToSuffix(), BodySlot.Face },
{ BodySlot.Hair.ToSuffix(), BodySlot.Hair },
{ BodySlot.Body.ToSuffix(), BodySlot.Body },
{ BodySlot.Tail.ToSuffix(), BodySlot.Tail }
};
}
}

View file

@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.ComponentModel;
namespace Penumbra.Game
{
public enum CustomizationType : byte
{
Unknown,
Body,
Tail,
Face,
Iris,
Accessory,
Hair,
DecalFace,
DecalEquip,
Skin,
Etc
}
public static class CustomizationTypeEnumExtension
{
public static string ToSuffix( this CustomizationType value )
{
return value switch
{
CustomizationType.Face => "fac",
CustomizationType.Iris => "iri",
CustomizationType.Accessory => "acc",
CustomizationType.Hair => "hir",
CustomizationType.Tail => "til",
CustomizationType.Etc => "etc",
_ => throw new InvalidEnumArgumentException()
};
}
}
public static partial class GameData
{
public static readonly Dictionary< string, CustomizationType > SuffixToCustomizationType = new()
{
{ CustomizationType.Face.ToSuffix(), CustomizationType.Face },
{ CustomizationType.Iris.ToSuffix(), CustomizationType.Iris },
{ CustomizationType.Accessory.ToSuffix(), CustomizationType.Accessory },
{ CustomizationType.Hair.ToSuffix(), CustomizationType.Hair },
{ CustomizationType.Tail.ToSuffix(), CustomizationType.Tail },
{ CustomizationType.Etc.ToSuffix(), CustomizationType.Etc }
};
}
}

View file

@ -0,0 +1,96 @@
using System.Collections.Generic;
using System.ComponentModel;
namespace Penumbra.Game
{
public enum EquipSlot : byte
{
Unknown = 0,
MainHand = 1,
Offhand = 2,
Head = 3,
Body = 4,
Hands = 5,
Belt = 6,
Legs = 7,
Feet = 8,
Ears = 9,
Neck = 10,
RingR = 12,
RingL = 14,
Wrists = 11,
BothHand = 13,
HeadBody = 15,
BodyHandsLegsFeet = 16,
SoulCrystal = 17,
LegsFeet = 18,
FullBody = 19,
BodyHands = 20,
BodyLegsFeet = 21,
All = 22
}
public static class EquipSlotEnumExtension
{
public static string ToSuffix( this EquipSlot value )
{
return value switch
{
EquipSlot.Head => "met",
EquipSlot.Hands => "glv",
EquipSlot.Legs => "dwn",
EquipSlot.Feet => "sho",
EquipSlot.Body => "top",
EquipSlot.Ears => "ear",
EquipSlot.Neck => "nek",
EquipSlot.RingR => "rir",
EquipSlot.RingL => "ril",
EquipSlot.Wrists => "wrs",
_ => throw new InvalidEnumArgumentException()
};
}
public static bool IsEquipment( this EquipSlot value )
{
return value switch
{
EquipSlot.Head => true,
EquipSlot.Hands => true,
EquipSlot.Legs => true,
EquipSlot.Feet => true,
EquipSlot.Body => true,
_ => false
};
}
public static bool IsAccessory( this EquipSlot value )
{
return value switch
{
EquipSlot.Ears => true,
EquipSlot.Neck => true,
EquipSlot.RingR => true,
EquipSlot.RingL => true,
EquipSlot.Wrists => true,
_ => false
};
}
}
public static partial class GameData
{
public static readonly Dictionary< string, EquipSlot > SuffixToEquipSlot = new()
{
{ EquipSlot.Head.ToSuffix(), EquipSlot.Head },
{ EquipSlot.Hands.ToSuffix(), EquipSlot.Hands },
{ EquipSlot.Legs.ToSuffix(), EquipSlot.Legs },
{ EquipSlot.Feet.ToSuffix(), EquipSlot.Feet },
{ EquipSlot.Body.ToSuffix(), EquipSlot.Body },
{ EquipSlot.Ears.ToSuffix(), EquipSlot.Ears },
{ EquipSlot.Neck.ToSuffix(), EquipSlot.Neck },
{ EquipSlot.RingR.ToSuffix(), EquipSlot.RingR },
{ EquipSlot.RingL.ToSuffix(), EquipSlot.RingL },
{ EquipSlot.Wrists.ToSuffix(), EquipSlot.Wrists }
};
}
}

View file

@ -0,0 +1,45 @@
using System.Collections.Generic;
namespace Penumbra.Game
{
public enum FileType : byte
{
Unknown,
Sound,
Imc,
Vfx,
Animation,
Pap,
MetaInfo,
Material,
Texture,
Model,
Shader,
Font,
Environment
}
public static partial class GameData
{
public static readonly Dictionary< string, FileType > ExtensionToFileType = new()
{
{ ".mdl", FileType.Model },
{ ".tex", FileType.Texture },
{ ".mtrl", FileType.Material },
{ ".atex", FileType.Animation },
{ ".avfx", FileType.Vfx },
{ ".scd", FileType.Sound },
{ ".imc", FileType.Imc },
{ ".pap", FileType.Pap },
{ ".eqp", FileType.MetaInfo },
{ ".eqdp", FileType.MetaInfo },
{ ".est", FileType.MetaInfo },
{ ".exd", FileType.MetaInfo },
{ ".exh", FileType.MetaInfo },
{ ".shpk", FileType.Shader },
{ ".shcd", FileType.Shader },
{ ".fdt", FileType.Font },
{ ".envb", FileType.Environment }
};
}
}

View file

@ -0,0 +1,21 @@
namespace Penumbra.Game
{
public enum ObjectType : byte
{
Unknown,
Vfx,
DemiHuman,
Accessory,
World,
Housing,
Monster,
Icon,
LoadingScreen,
Map,
Interface,
Equipment,
Character,
Weapon,
Font
}
}

278
Penumbra/Game/Enums/Race.cs Normal file
View file

@ -0,0 +1,278 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace Penumbra.Game
{
public enum Gender : byte
{
Unknown,
Male,
Female,
MaleNpc,
FemaleNpc
}
public enum Race : byte
{
Unknown,
Midlander,
Highlander,
Elezen,
Lalafell,
Miqote,
Roegadyn,
AuRa,
Hrothgar,
Viera
}
public enum GenderRace : ushort
{
Unknown = 0,
MidlanderMale = 0101,
MidlanderMaleNpc = 0104,
MidlanderFemale = 0201,
MidlanderFemaleNpc = 0204,
HighlanderMale = 0301,
HighlanderMaleNpc = 0304,
HighlanderFemale = 0401,
HighlanderFemaleNpc = 0404,
ElezenMale = 0501,
ElezenMaleNpc = 0504,
ElezenFemale = 0601,
ElezenFemaleNpc = 0604,
LalafellMale = 0701,
LalafellMaleNpc = 0704,
LalafellFemale = 0801,
LalafellFemaleNpc = 0804,
MiqoteMale = 0901,
MiqoteMaleNpc = 0904,
MiqoteFemale = 1001,
MiqoteFemaleNpc = 1004,
RoegadynMale = 1101,
RoegadynMaleNpc = 1104,
RoegadynFemale = 1201,
RoegadynFemaleNpc = 1204,
AuRaMale = 1301,
AuRaMaleNpc = 1304,
AuRaFemale = 1401,
AuRaFemaleNpc = 1404,
HrothgarMale = 1501,
HrothgarMaleNpc = 1504,
VieraFemale = 1801,
VieraFemaleNpc = 1804,
UnknownMaleNpc = 9104,
UnknownFemaleNpc = 9204
}
public static class RaceEnumExtensions
{
public static byte ToByte( this Gender gender, Race race )
=> ( byte )( ( int )gender | ( ( int )race << 3 ) );
public static byte ToByte( this Race race, Gender gender )
=> gender.ToByte( race );
public static byte ToByte( this GenderRace value )
{
var (gender, race) = value.Split();
return gender.ToByte( race );
}
public static (Gender, Race) Split( this GenderRace value )
{
return value switch
{
GenderRace.MidlanderMale => ( Gender.Male, Race.Midlander ),
GenderRace.MidlanderMaleNpc => ( Gender.MaleNpc, Race.Midlander ),
GenderRace.MidlanderFemale => ( Gender.Female, Race.Midlander ),
GenderRace.MidlanderFemaleNpc => ( Gender.FemaleNpc, Race.Midlander ),
GenderRace.HighlanderMale => ( Gender.Male, Race.Highlander ),
GenderRace.HighlanderMaleNpc => ( Gender.MaleNpc, Race.Highlander ),
GenderRace.HighlanderFemale => ( Gender.Female, Race.Highlander ),
GenderRace.HighlanderFemaleNpc => ( Gender.FemaleNpc, Race.Highlander ),
GenderRace.ElezenMale => ( Gender.Male, Race.Elezen ),
GenderRace.ElezenMaleNpc => ( Gender.MaleNpc, Race.Elezen ),
GenderRace.ElezenFemale => ( Gender.Female, Race.Elezen ),
GenderRace.ElezenFemaleNpc => ( Gender.FemaleNpc, Race.Elezen ),
GenderRace.LalafellMale => ( Gender.Male, Race.Lalafell ),
GenderRace.LalafellMaleNpc => ( Gender.MaleNpc, Race.Lalafell ),
GenderRace.LalafellFemale => ( Gender.Female, Race.Lalafell ),
GenderRace.LalafellFemaleNpc => ( Gender.FemaleNpc, Race.Lalafell ),
GenderRace.MiqoteMale => ( Gender.Male, Race.Miqote ),
GenderRace.MiqoteMaleNpc => ( Gender.MaleNpc, Race.Miqote ),
GenderRace.MiqoteFemale => ( Gender.Female, Race.Miqote ),
GenderRace.MiqoteFemaleNpc => ( Gender.FemaleNpc, Race.Miqote ),
GenderRace.RoegadynMale => ( Gender.Male, Race.Roegadyn ),
GenderRace.RoegadynMaleNpc => ( Gender.MaleNpc, Race.Roegadyn ),
GenderRace.RoegadynFemale => ( Gender.Female, Race.Roegadyn ),
GenderRace.RoegadynFemaleNpc => ( Gender.FemaleNpc, Race.Roegadyn ),
GenderRace.AuRaMale => ( Gender.Male, Race.AuRa ),
GenderRace.AuRaMaleNpc => ( Gender.MaleNpc, Race.AuRa ),
GenderRace.AuRaFemale => ( Gender.Female, Race.AuRa ),
GenderRace.AuRaFemaleNpc => ( Gender.FemaleNpc, Race.AuRa ),
GenderRace.HrothgarMale => ( Gender.Male, Race.Hrothgar ),
GenderRace.HrothgarMaleNpc => ( Gender.MaleNpc, Race.Hrothgar ),
GenderRace.VieraFemale => ( Gender.Female, Race.Viera ),
GenderRace.VieraFemaleNpc => ( Gender.FemaleNpc, Race.Viera ),
GenderRace.UnknownMaleNpc => ( Gender.MaleNpc, Race.Unknown ),
GenderRace.UnknownFemaleNpc => ( Gender.FemaleNpc, Race.Unknown ),
_ => throw new InvalidEnumArgumentException()
};
}
public static bool IsValid( this GenderRace value )
=> value != GenderRace.Unknown && Enum.IsDefined( typeof( GenderRace ), value );
public static string ToRaceCode( this GenderRace value )
{
return value switch
{
GenderRace.MidlanderMale => "0101",
GenderRace.MidlanderMaleNpc => "0104",
GenderRace.MidlanderFemale => "0201",
GenderRace.MidlanderFemaleNpc => "0204",
GenderRace.HighlanderMale => "0301",
GenderRace.HighlanderMaleNpc => "0304",
GenderRace.HighlanderFemale => "0401",
GenderRace.HighlanderFemaleNpc => "0404",
GenderRace.ElezenMale => "0501",
GenderRace.ElezenMaleNpc => "0504",
GenderRace.ElezenFemale => "0601",
GenderRace.ElezenFemaleNpc => "0604",
GenderRace.LalafellMale => "0701",
GenderRace.LalafellMaleNpc => "0704",
GenderRace.LalafellFemale => "0801",
GenderRace.LalafellFemaleNpc => "0804",
GenderRace.MiqoteMale => "0901",
GenderRace.MiqoteMaleNpc => "0904",
GenderRace.MiqoteFemale => "1001",
GenderRace.MiqoteFemaleNpc => "1004",
GenderRace.RoegadynMale => "1101",
GenderRace.RoegadynMaleNpc => "1104",
GenderRace.RoegadynFemale => "1201",
GenderRace.RoegadynFemaleNpc => "1204",
GenderRace.AuRaMale => "1301",
GenderRace.AuRaMaleNpc => "1304",
GenderRace.AuRaFemale => "1401",
GenderRace.AuRaFemaleNpc => "1404",
GenderRace.HrothgarMale => "1501",
GenderRace.HrothgarMaleNpc => "1504",
GenderRace.VieraFemale => "1801",
GenderRace.VieraFemaleNpc => "1804",
GenderRace.UnknownMaleNpc => "9104",
GenderRace.UnknownFemaleNpc => "9204",
_ => throw new InvalidEnumArgumentException()
};
}
}
public static partial class GameData
{
public static GenderRace GenderRaceFromCode( string code )
{
return code switch
{
"0101" => GenderRace.MidlanderMale,
"0104" => GenderRace.MidlanderMaleNpc,
"0201" => GenderRace.MidlanderFemale,
"0204" => GenderRace.MidlanderFemaleNpc,
"0301" => GenderRace.HighlanderMale,
"0304" => GenderRace.HighlanderMaleNpc,
"0401" => GenderRace.HighlanderFemale,
"0404" => GenderRace.HighlanderFemaleNpc,
"0501" => GenderRace.ElezenMale,
"0504" => GenderRace.ElezenMaleNpc,
"0601" => GenderRace.ElezenFemale,
"0604" => GenderRace.ElezenFemaleNpc,
"0701" => GenderRace.LalafellMale,
"0704" => GenderRace.LalafellMaleNpc,
"0801" => GenderRace.LalafellFemale,
"0804" => GenderRace.LalafellFemaleNpc,
"0901" => GenderRace.MiqoteMale,
"0904" => GenderRace.MiqoteMaleNpc,
"1001" => GenderRace.MiqoteFemale,
"1004" => GenderRace.MiqoteFemaleNpc,
"1101" => GenderRace.RoegadynMale,
"1104" => GenderRace.RoegadynMaleNpc,
"1201" => GenderRace.RoegadynFemale,
"1204" => GenderRace.RoegadynFemaleNpc,
"1301" => GenderRace.AuRaMale,
"1304" => GenderRace.AuRaMaleNpc,
"1401" => GenderRace.AuRaFemale,
"1404" => GenderRace.AuRaFemaleNpc,
"1501" => GenderRace.HrothgarMale,
"1504" => GenderRace.HrothgarMaleNpc,
"1801" => GenderRace.VieraFemale,
"1804" => GenderRace.VieraFemaleNpc,
"9104" => GenderRace.UnknownMaleNpc,
"9204" => GenderRace.UnknownFemaleNpc,
_ => throw new KeyNotFoundException()
};
}
public static GenderRace GenderRaceFromByte( byte value )
{
var gender = ( Gender )( value & 0b111 );
var race = ( Race )( value >> 3 );
return CombinedRace( gender, race );
}
public static GenderRace CombinedRace( Gender gender, Race race )
{
return gender switch
{
Gender.Male => race switch
{
Race.Midlander => GenderRace.MidlanderMale,
Race.Highlander => GenderRace.HighlanderMale,
Race.Elezen => GenderRace.ElezenMale,
Race.Lalafell => GenderRace.LalafellMale,
Race.Miqote => GenderRace.MiqoteMale,
Race.Roegadyn => GenderRace.RoegadynMale,
Race.AuRa => GenderRace.AuRaMale,
Race.Hrothgar => GenderRace.HrothgarMale,
_ => GenderRace.Unknown
},
Gender.MaleNpc => race switch
{
Race.Midlander => GenderRace.MidlanderMaleNpc,
Race.Highlander => GenderRace.HighlanderMaleNpc,
Race.Elezen => GenderRace.ElezenMaleNpc,
Race.Lalafell => GenderRace.LalafellMaleNpc,
Race.Miqote => GenderRace.MiqoteMaleNpc,
Race.Roegadyn => GenderRace.RoegadynMaleNpc,
Race.AuRa => GenderRace.AuRaMaleNpc,
Race.Hrothgar => GenderRace.HrothgarMaleNpc,
_ => GenderRace.Unknown
},
Gender.Female => race switch
{
Race.Midlander => GenderRace.MidlanderFemale,
Race.Highlander => GenderRace.HighlanderFemale,
Race.Elezen => GenderRace.ElezenFemale,
Race.Lalafell => GenderRace.LalafellFemale,
Race.Miqote => GenderRace.MiqoteFemale,
Race.Roegadyn => GenderRace.RoegadynFemale,
Race.AuRa => GenderRace.AuRaFemale,
Race.Viera => GenderRace.VieraFemale,
_ => GenderRace.Unknown
},
Gender.FemaleNpc => race switch
{
Race.Midlander => GenderRace.MidlanderFemaleNpc,
Race.Highlander => GenderRace.HighlanderFemaleNpc,
Race.Elezen => GenderRace.ElezenFemaleNpc,
Race.Lalafell => GenderRace.LalafellFemaleNpc,
Race.Miqote => GenderRace.MiqoteFemaleNpc,
Race.Roegadyn => GenderRace.RoegadynFemaleNpc,
Race.AuRa => GenderRace.AuRaFemaleNpc,
Race.Viera => GenderRace.VieraFemaleNpc,
_ => GenderRace.Unknown
},
_ => GenderRace.Unknown
};
}
}
}

View file

@ -0,0 +1,157 @@
using System;
using System.Runtime.InteropServices;
using Dalamud;
namespace Penumbra.Game
{
[StructLayout( LayoutKind.Explicit )]
public struct GameObjectInfo : IComparable
{
public static GameObjectInfo Equipment( FileType type, ushort setId, GenderRace gr = GenderRace.Unknown
, EquipSlot slot = EquipSlot.Unknown, byte variant = 0 )
=> new()
{
FileType = type,
ObjectType = slot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment,
PrimaryId = setId,
GenderRace = gr,
Variant = variant,
EquipSlot = slot
};
public static GameObjectInfo Weapon( FileType type, ushort setId, ushort weaponId, byte variant = 0 )
=> new()
{
FileType = type,
ObjectType = ObjectType.Weapon,
PrimaryId = setId,
SecondaryId = weaponId,
Variant = variant
};
public static GameObjectInfo Customization( FileType type, CustomizationType customizationType, ushort id = 0
, GenderRace gr = GenderRace.Unknown, BodySlot bodySlot = BodySlot.Unknown, byte variant = 0 )
=> new()
{
FileType = type,
ObjectType = ObjectType.Character,
PrimaryId = id,
GenderRace = gr,
BodySlot = bodySlot,
Variant = variant,
CustomizationType = customizationType
};
public static GameObjectInfo Monster( FileType type, ushort monsterId, ushort bodyId, byte variant = 0 )
=> new()
{
FileType = type,
ObjectType = ObjectType.Monster,
PrimaryId = monsterId,
SecondaryId = bodyId,
Variant = variant
};
public static GameObjectInfo DemiHuman( FileType type, ushort demiHumanId, ushort bodyId, byte variant = 0,
EquipSlot slot = EquipSlot.Unknown )
=> new()
{
FileType = type,
ObjectType = ObjectType.DemiHuman,
PrimaryId = demiHumanId,
SecondaryId = bodyId,
Variant = variant,
EquipSlot = slot
};
public static GameObjectInfo Map( FileType type, byte c1, byte c2, byte c3, byte c4, byte variant, byte suffix = 0 )
=> new()
{
FileType = type,
ObjectType = ObjectType.Map,
MapC1 = c1,
MapC2 = c2,
MapC3 = c3,
MapC4 = c4,
MapSuffix = suffix,
Variant = variant
};
public static GameObjectInfo Icon( FileType type, uint iconId, bool hq, ClientLanguage lang = ClientLanguage.English )
=> new()
{
FileType = type,
ObjectType = ObjectType.Map,
IconId = iconId,
IconHq = hq,
Language = lang
};
[FieldOffset( 0 )]
public readonly ulong Identifier;
[FieldOffset( 0 )]
public FileType FileType;
[FieldOffset( 1 )]
public ObjectType ObjectType;
[FieldOffset( 2 )]
public ushort PrimaryId; // Equipment, Weapon, Customization, Monster, DemiHuman
[FieldOffset( 2 )]
public uint IconId; // Icon
[FieldOffset( 2 )]
public byte MapC1; // Map
[FieldOffset( 3 )]
public byte MapC2; // Map
[FieldOffset( 4 )]
public ushort SecondaryId; // Weapon, Monster, Demihuman
[FieldOffset( 4 )]
public byte MapC3; // Map
[FieldOffset( 4 )]
private byte _genderRaceByte; // Equipment, Customization
public GenderRace GenderRace
{
get => GameData.GenderRaceFromByte( _genderRaceByte );
set => _genderRaceByte = value.ToByte();
}
[FieldOffset( 5 )]
public BodySlot BodySlot; // Customization
[FieldOffset( 5 )]
public byte MapC4; // Map
[FieldOffset( 6 )]
public byte Variant; // Equipment, Weapon, Customization, Map, Monster, Demihuman
[FieldOffset( 6 )]
public bool IconHq; // Icon
[FieldOffset( 7 )]
public EquipSlot EquipSlot; // Equipment, Demihuman
[FieldOffset( 7 )]
public CustomizationType CustomizationType; // Customization
[FieldOffset( 7 )]
public ClientLanguage Language; // Icon
[FieldOffset( 7 )]
public byte MapSuffix;
public override int GetHashCode()
=> Identifier.GetHashCode();
public int CompareTo( object? r )
=> Identifier.CompareTo( r );
}
}

View file

@ -0,0 +1,384 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Dalamud.Plugin;
using Penumbra.Util;
namespace Penumbra.Game
{
public static class GamePathParser
{
private const string CharacterFolder = "chara";
private const string EquipmentFolder = "equipment";
private const string PlayerFolder = "human";
private const string WeaponFolder = "weapon";
private const string AccessoryFolder = "accessory";
private const string DemiHumanFolder = "demihuman";
private const string MonsterFolder = "monster";
private const string CommonFolder = "common";
private const string UiFolder = "ui";
private const string IconFolder = "icon";
private const string LoadingFolder = "loadingimage";
private const string MapFolder = "map";
private const string InterfaceFolder = "uld";
private const string FontFolder = "font";
private const string HousingFolder = "hou";
private const string VfxFolder = "vfx";
private const string WorldFolder1 = "bgcommon";
private const string WorldFolder2 = "bg";
// @formatter:off
private static readonly Dictionary<FileType, Dictionary<ObjectType, Regex[]>> Regexes = new()
{ { FileType.Font, new Dictionary< ObjectType, Regex[] >(){ { ObjectType.Font, new Regex[]{ new(@"common/font/(?'fontname'.*)_(?'id'\d\d)(_lobby)?\.fdt") } } } }
, { FileType.Texture, new Dictionary< ObjectType, Regex[] >()
{ { ObjectType.Icon, new Regex[]{ new(@"ui/icon/(?'group'\d*)(/(?'lang'[a-z]{2}))?(/(?'hq'hq))?/(?'id'\d*)\.tex") } }
, { ObjectType.Map, new Regex[]{ new(@"ui/map/(?'id'[a-z0-9]{4})/(?'variant'\d{2})/\k'id'\k'variant'(?'suffix'[a-z])?(_[a-z])?\.tex") } }
, { ObjectType.Weapon, new Regex[]{ new(@"chara/weapon/w(?'weapon'\d{4})/obj/body/b(?'id'\d{4})/texture/v(?'variant'\d{2})_w\k'weapon'b\k'id'(_[a-z])?_[a-z]\.tex") } }
, { ObjectType.Monster, new Regex[]{ new(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/texture/v(?'variant'\d{2})_m\k'monster'b\k'id'(_[a-z])?_[a-z]\.tex") } }
, { ObjectType.Equipment, new Regex[]{ new(@"chara/equipment/e(?'id'\d{4})/texture/v(?'variant'\d{2})_c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})(_[a-z])?_[a-z]\.tex") } }
, { ObjectType.DemiHuman, new Regex[]{ new(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/texture/v(?'variant'\d{2})_d\k'id'e\k'equip'_(?'slot'[a-z]{3})(_[a-z])?_[a-z]\.tex") } }
, { ObjectType.Accessory, new Regex[]{ new(@"chara/accessory/a(?'id'\d{4})/texture/v(?'variant'\d{2})_c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})_[a-z]\.tex") } }
, { ObjectType.Character, new Regex[]{ new(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/texture/(?'minus'(--)?)c\k'race'\k'typeabr'\k'id'_(?'slot'[a-z]{3})(_[a-z])?_[a-z]\.tex")
, new(@"chara/common/texture/skin(?'skin'.*)\.tex")
, new(@"chara/common/texture/decal_(?'location'[a-z]+)/[-_]?decal_(?'id'\d+).tex") } } } }
, { FileType.Model, new Dictionary< ObjectType, Regex[] >()
{ { ObjectType.Weapon, new Regex[]{ new(@"chara/weapon/w(?'weapon'\d{4})/obj/body/b(?'id'\d{4})/model/w\k'weapon'b\k'id'\.mdl") } }
, { ObjectType.Monster, new Regex[]{ new(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/model/m\k'monster'b\k'id'\.mdl") } }
, { ObjectType.Equipment, new Regex[]{ new(@"chara/equipment/e(?'id'\d{4})/model/c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})\.mdl") } }
, { ObjectType.DemiHuman, new Regex[]{ new(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/model/d\k'id'e\k'equip'_(?'slot'[a-z]{3})\.mdl") } }
, { ObjectType.Accessory, new Regex[]{ new(@"chara/accessory/a(?'id'\d{4})/model/c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})\.mdl") } }
, { ObjectType.Character, new Regex[]{ new(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/model/c\k'race'\k'typeabr'\k'id'_(?'slot'[a-z]{3})\.mdl") } } } }
, { FileType.Material, new Dictionary< ObjectType, Regex[] >()
{ { ObjectType.Weapon, new Regex[]{ new(@"chara/weapon/w(?'weapon'\d{4})/obj/body/b(?'id'\d{4})/material/v(?'variant'\d{4})/mt_w\k'weapon'b\k'id'_[a-z]\.mtrl") } }
, { ObjectType.Monster, new Regex[]{ new(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/material/v(?'variant'\d{4})/mt_m\k'monster'b\k'id'_[a-z]\.mtrl") } }
, { ObjectType.Equipment, new Regex[]{ new(@"chara/equipment/e(?'id'\d{4})/material/v(?'variant'\d{4})/mt_c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})_[a-z]\.mtrl") } }
, { ObjectType.DemiHuman, new Regex[]{ new(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/material/v(?'variant'\d{4})/mt_d\k'id'e\k'equip'_(?'slot'[a-z]{3})_[a-z]\.mtrl") } }
, { ObjectType.Accessory, new Regex[]{ new(@"chara/accessory/a(?'id'\d{4})/material/v(?'variant'\d{4})/mt_c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})_[a-z]\.mtrl") } }
, { ObjectType.Character, new Regex[]{ new(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/material/v(?'variant'\d{4})/mt_c\k'race'\k'typeabr'\k'id'_(?'slot'[a-z]{3})_[a-z]\.mtrl") } } } }
, { FileType.Imc, new Dictionary< ObjectType, Regex[] >()
{ { ObjectType.Weapon, new Regex[]{ new(@"chara/weapon/w(?'weapon'\d{4})/obj/body/b(?'id'\d{4})/b\k'id'\.imc") } }
, { ObjectType.Monster, new Regex[]{ new(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/b\k'id'\.imc") } }
, { ObjectType.Equipment, new Regex[]{ new(@"chara/equipment/e(?'id'\d{4})/e\k'id'\.imc") } }
, { ObjectType.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") } } } }
};
// @formatter:on
public static ObjectType PathToObjectType( GamePath path )
{
if( !path )
{
return ObjectType.Unknown;
}
string p = path;
var folders = p.Split( '/' );
if( folders.Length < 2 )
{
return ObjectType.Unknown;
}
return folders[ 0 ] switch
{
CharacterFolder => folders[ 1 ] switch
{
EquipmentFolder => ObjectType.Equipment,
AccessoryFolder => ObjectType.Accessory,
WeaponFolder => ObjectType.Weapon,
PlayerFolder => ObjectType.Character,
DemiHumanFolder => ObjectType.DemiHuman,
MonsterFolder => ObjectType.Monster,
CommonFolder => ObjectType.Character,
_ => ObjectType.Unknown
},
UiFolder => folders[ 1 ] switch
{
IconFolder => ObjectType.Icon,
LoadingFolder => ObjectType.LoadingScreen,
MapFolder => ObjectType.Map,
InterfaceFolder => ObjectType.Interface,
_ => ObjectType.Unknown
},
CommonFolder => folders[ 1 ] switch
{
FontFolder => ObjectType.Font,
_ => ObjectType.Unknown
},
HousingFolder => ObjectType.Housing,
WorldFolder1 => folders[ 1 ] switch
{
HousingFolder => ObjectType.Housing,
_ => ObjectType.World
},
WorldFolder2 => ObjectType.World,
VfxFolder => ObjectType.Vfx,
_ => ObjectType.Unknown
};
}
private static (FileType, ObjectType, Match?) ParseGamePath( GamePath path )
{
if( !GameData.ExtensionToFileType.TryGetValue( Extension( path ), out var fileType ) )
{
fileType = FileType.Unknown;
}
var objectType = PathToObjectType( path );
if( !Regexes.TryGetValue( fileType, out var objectDict ) )
{
return ( fileType, objectType, null );
}
if( !objectDict.TryGetValue( objectType, out var regexes ) )
{
return ( fileType, objectType, null );
}
foreach( var regex in regexes )
{
var match = regex.Match( path );
if( match.Success )
{
return ( fileType, objectType, match );
}
}
return ( fileType, objectType, null );
}
private static string Extension( string filename )
{
var extIdx = filename.LastIndexOf( '.' );
return extIdx < 0 ? "" : filename.Substring( extIdx );
}
private static GameObjectInfo HandleEquipment( FileType fileType, ObjectType objectType, GroupCollection groups )
{
try
{
var setId = ushort.Parse( groups[ "id" ].Value );
if( fileType == FileType.Imc )
{
return GameObjectInfo.Equipment( fileType, setId );
}
var gr = GameData.GenderRaceFromCode( groups[ "race" ].Value );
var slot = GameData.SuffixToEquipSlot[ groups[ "slot" ].Value ];
if( fileType == FileType.Model )
{
return GameObjectInfo.Equipment( fileType, setId, gr, slot );
}
var variant = byte.Parse( groups[ "variant" ].Value );
return GameObjectInfo.Equipment( fileType, setId, gr, slot, variant );
}
catch( Exception e )
{
PluginLog.Error( $"Parsing game path failed:\n{e}" );
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
}
}
private static GameObjectInfo HandleWeapon( FileType fileType, ObjectType objectType, GroupCollection groups )
{
try
{
var weaponId = ushort.Parse( groups[ "weapon" ].Value );
var setId = ushort.Parse( groups[ "id" ].Value );
if( fileType == FileType.Imc || fileType == FileType.Model )
{
return GameObjectInfo.Weapon( fileType, setId, weaponId );
}
var variant = byte.Parse( groups[ "variant" ].Value );
return GameObjectInfo.Weapon( fileType, setId, weaponId, variant );
}
catch( Exception e )
{
PluginLog.Error( $"Parsing game path failed:\n{e}" );
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
}
}
private static GameObjectInfo HandleMonster( FileType fileType, ObjectType objectType, GroupCollection groups )
{
try
{
var monsterId = ushort.Parse( groups[ "monster" ].Value );
var bodyId = ushort.Parse( groups[ "id" ].Value );
if( fileType == FileType.Imc || fileType == FileType.Model )
{
return GameObjectInfo.Monster( fileType, monsterId, bodyId );
}
var variant = byte.Parse( groups[ "variant" ].Value );
return GameObjectInfo.Monster( fileType, monsterId, bodyId, variant );
}
catch( Exception e )
{
PluginLog.Error( $"Parsing game path failed:\n{e}" );
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
}
}
private static GameObjectInfo HandleDemiHuman( FileType fileType, ObjectType objectType, GroupCollection groups )
{
try
{
var demiHumanId = ushort.Parse( groups[ "monster" ].Value );
var bodyId = ushort.Parse( groups[ "id" ].Value );
if( fileType == FileType.Imc )
{
return GameObjectInfo.DemiHuman( fileType, demiHumanId, bodyId );
}
var slot = GameData.SuffixToEquipSlot[ groups[ "slot" ].Value ];
var variant = byte.Parse( groups[ "variant" ].Value );
return GameObjectInfo.DemiHuman( fileType, demiHumanId, bodyId, variant, slot );
}
catch( Exception e )
{
PluginLog.Error( $"Parsing game path failed:\n{e}" );
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
}
}
private static GameObjectInfo HandleCustomization( FileType fileType, ObjectType objectType, GroupCollection groups )
{
try
{
if( groups[ "skin" ].Success )
{
return GameObjectInfo.Customization( fileType, CustomizationType.Skin );
}
var id = ushort.Parse( groups[ "id" ].Value );
if( groups[ "location" ].Success )
{
var tmpType = groups[ "location" ].Value == "face" ? CustomizationType.DecalFace
: groups[ "location" ].Value == "equip" ? CustomizationType.DecalEquip : CustomizationType.Unknown;
return GameObjectInfo.Customization( fileType, tmpType, id );
}
var gr = GameData.GenderRaceFromCode( groups[ "race" ].Value );
var bodySlot = GameData.StringToBodySlot[ groups[ "type" ].Value ];
var type = GameData.SuffixToCustomizationType[ groups[ "slot" ].Value ];
if( fileType == FileType.Material )
{
var variant = byte.Parse( groups[ "variant" ].Value );
return GameObjectInfo.Customization( fileType, type, id, gr, bodySlot, variant );
}
return GameObjectInfo.Customization( fileType, type, id, gr, bodySlot );
}
catch( Exception e )
{
PluginLog.Error( $"Parsing game path failed:\n{e}" );
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
}
}
private static GameObjectInfo HandleIcon( FileType fileType, ObjectType objectType, GroupCollection groups )
{
try
{
var hq = groups[ "hq" ].Success;
var id = uint.Parse( groups[ "id" ].Value );
if( !groups[ "lang" ].Success )
{
return GameObjectInfo.Icon( fileType, id, hq );
}
var language = groups[ "lang" ].Value switch
{
"en" => Dalamud.ClientLanguage.English,
"ja" => Dalamud.ClientLanguage.Japanese,
"de" => Dalamud.ClientLanguage.German,
"fr" => Dalamud.ClientLanguage.French,
_ => Dalamud.ClientLanguage.English
};
return GameObjectInfo.Icon( fileType, id, hq, language );
}
catch( Exception e )
{
PluginLog.Error( $"Parsing game path failed:\n{e}" );
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
}
}
private static GameObjectInfo HandleMap( FileType fileType, ObjectType objectType, GroupCollection groups )
{
try
{
var map = Encoding.ASCII.GetBytes( groups[ "id" ].Value );
var variant = byte.Parse( groups[ "variant" ].Value );
if( groups[ "suffix" ].Success )
{
var suffix = Encoding.ASCII.GetBytes( groups[ "suffix" ].Value )[ 0 ];
return GameObjectInfo.Map( fileType, map[ 0 ], map[ 1 ], map[ 2 ], map[ 3 ], variant, suffix );
}
return GameObjectInfo.Map( fileType, map[ 0 ], map[ 1 ], map[ 2 ], map[ 3 ], variant );
}
catch( Exception e )
{
PluginLog.Error( $"Parsing game path failed:\n{e}" );
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
}
}
public static GameObjectInfo GetFileInfo( GamePath path )
{
var (fileType, objectType, match) = ParseGamePath( path );
if( match == null || !match.Success )
{
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
}
try
{
var groups = match.Groups;
switch( objectType )
{
case ObjectType.Accessory: return HandleEquipment( fileType, objectType, groups );
case ObjectType.Equipment: return HandleEquipment( fileType, objectType, groups );
case ObjectType.Weapon: return HandleWeapon( fileType, objectType, groups );
case ObjectType.Map: return HandleMap( fileType, objectType, groups );
case ObjectType.Monster: return HandleMonster( fileType, objectType, groups );
case ObjectType.DemiHuman: return HandleDemiHuman( fileType, objectType, groups );
case ObjectType.Character: return HandleCustomization( fileType, objectType, groups );
case ObjectType.Icon: return HandleIcon( fileType, objectType, groups );
}
}
catch( Exception e )
{
PluginLog.Error( $"Could not parse {path}:\n{e}" );
}
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
}
public static bool IsTailTexture( GameObjectInfo info )
{
if( info.ObjectType != ObjectType.Character )
{
return false;
}
return info.BodySlot == BodySlot.Tail && info.FileType == FileType.Texture;
}
public static bool IsSkinTexture( GameObjectInfo info )
{
if( info.ObjectType != ObjectType.Character )
{
return false;
}
return info.FileType == FileType.Texture && info.CustomizationType == CustomizationType.Skin;
}
}
}