mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Enums and support for parsing and interpreting game paths.
This commit is contained in:
parent
b97d0ddc37
commit
88ba14e595
8 changed files with 1074 additions and 0 deletions
43
Penumbra/Game/Enums/BodySlot.cs
Normal file
43
Penumbra/Game/Enums/BodySlot.cs
Normal 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 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
50
Penumbra/Game/Enums/CustomizationType.cs
Normal file
50
Penumbra/Game/Enums/CustomizationType.cs
Normal 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 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
96
Penumbra/Game/Enums/EquipSlot.cs
Normal file
96
Penumbra/Game/Enums/EquipSlot.cs
Normal 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 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
45
Penumbra/Game/Enums/FileType.cs
Normal file
45
Penumbra/Game/Enums/FileType.cs
Normal 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 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Penumbra/Game/Enums/ObjectType.cs
Normal file
21
Penumbra/Game/Enums/ObjectType.cs
Normal 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
278
Penumbra/Game/Enums/Race.cs
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
157
Penumbra/Game/GameObjectInfo.cs
Normal file
157
Penumbra/Game/GameObjectInfo.cs
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
384
Penumbra/Game/GamePathParser.cs
Normal file
384
Penumbra/Game/GamePathParser.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue