Penumbra/Penumbra/Game/GamePathParser.cs
2021-04-19 17:17:47 +02:00

383 lines
No EOL
19 KiB
C#

using System;
using System.Collections.Generic;
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.Empty )
{
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;
}
}
}