using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using Dalamud.Utility; namespace CustomizePlus.Core.Data; public static class BoneData //todo: DI, do not show IVCS unless IVCS is installed/user enabled it, do not show weapon bones { public enum BoneFamily { Root, Spine, Hair, Face, Eyes, Ears, Cheeks, Lips, Tongue, Jaw, Chest, Arms, Hands, Tail, Groin, Legs, Feet, Earrings, Hat, Cape, Armor, Skirt, Equipment, Unknown } //TODO move the csv data to an external (compressed?) file private static readonly string[] BoneRawTable = { //Codename, Display Name, Bone Family, Parent (if any), Mirrored Bone (if any) "n_root,Root,Root,TRUE,FALSE,,", "n_hara,Abdomen,Root,TRUE,FALSE,,", "j_kao,Head,Spine,TRUE,FALSE,j_kubi,", "j_kubi,Neck,Spine,TRUE,FALSE,j_sebo_c,", "j_sebo_c,Spine C,Spine,TRUE,FALSE,j_sebo_b,", "j_sebo_b,Spine B,Spine,TRUE,FALSE,j_sebo_a,", "j_sebo_a,Spine A,Spine,TRUE,FALSE,j_kosi,", "j_kosi,Waist,Spine,TRUE,FALSE,,", "j_kami_a,Hair A,Hair,TRUE,FALSE,j_kao,", "j_kami_b,Hair B,Hair,TRUE,FALSE,j_kami_a,", "j_kami_f_l,Hair Front Left,Hair,TRUE,FALSE,j_kao,j_kami_f_r", "j_kami_f_r,Hair Front Right,Hair,TRUE,FALSE,j_kao,j_kami_f_l", "j_f_mayu_l,Brow Outer Left,Face,TRUE,FALSE,j_kao,j_f_mayu_r", "j_f_mayu_r,Brow Outer Right,Face,TRUE,FALSE,j_kao,j_f_mayu_l", "j_f_miken_l,Brow Inner Left,Face,TRUE,FALSE,j_kao,j_f_miken_r", "j_f_miken_r,Brow Inner Right,Face,TRUE,FALSE,j_kao,j_f_miken_l", "j_f_memoto,Bridge,Face,TRUE,FALSE,j_kao,", "j_f_umab_l,Eyelid Upper Left,Face,TRUE,FALSE,j_kao,j_f_umab_r", "j_f_umab_r,Eyelid Upper Right,Face,TRUE,FALSE,j_kao,j_f_umab_l", "j_f_dmab_l,Eyelid Lower Left,Face,TRUE,FALSE,j_kao,j_f_dmab_r", "j_f_dmab_r,Eyelid Lower Right,Face,TRUE,FALSE,j_kao,j_f_dmab_l", "j_f_eye_l,Eye Left,Face,TRUE,FALSE,j_kao,j_f_eye_r", "j_f_eye_r,Eye Right,Face,TRUE,FALSE,j_kao,j_f_eye_l", "j_f_hoho_l,Cheek Left,Face,TRUE,FALSE,j_kao,j_f_hoho_r", "j_f_hoho_r,Cheek Right,Face,TRUE,FALSE,j_kao,j_f_hoho_l", "j_f_hige_l,Hrothgar Whiskers Left,Face,FALSE,FALSE,j_kao,j_f_hige_r", "j_f_hige_r,Hrothgar Whiskers Right,Face,FALSE,FALSE,j_kao,j_f_hige_l", "j_f_hana,Nose,Face,TRUE,FALSE,j_kao,", "j_f_lip_l,Lips Left,Face,TRUE,FALSE,j_kao,j_f_lip_r", "j_f_lip_r,Lips Right,Face,TRUE,FALSE,j_kao,j_f_lip_l", "j_f_ulip_a,Lip Upper A,Face,TRUE,FALSE,j_kao,", "j_f_ulip_b,Lip Upper B,Face,TRUE,FALSE,j_kao,", "j_f_dlip_a,Lip Lower A,Face,TRUE,FALSE,j_kao,", "j_f_dlip_b,Lip Lower B,Face,TRUE,FALSE,j_kao,", "n_f_lip_l,Hrothgar Cheek Left,Face,FALSE,FALSE,j_kao,n_f_lip_r", "n_f_lip_r,Hrothgar Cheek Right,Face,FALSE,FALSE,j_kao,n_f_lip_l", "n_f_ulip_l,Hrothgar Lip Upper Left,Face,FALSE,FALSE,j_kao,n_f_ulip_r", "n_f_ulip_r,Hrothgar Lip Upper Right,Face,FALSE,FALSE,j_kao,n_f_ulip_l", "j_f_dlip,Hrothgar Lip Lower,Face,FALSE,FALSE,j_kao,", "j_ago,Jaw,Face,TRUE,FALSE,j_kao,", "j_f_uago,Hrothgar Palate Upper,Face,FALSE,FALSE,j_kao,", "j_f_ulip,Hrothgar Palate Lower,Face,FALSE,FALSE,j_kao,", "j_mimi_l,Ear Left,Ears,TRUE,FALSE,j_kao,j_mimi_r", "j_mimi_r,Ear Right,Ears,TRUE,FALSE,j_kao,j_mimi_l", "j_zera_a_l,Viera Ear 01 A Left,Ears,FALSE,FALSE,j_kao,j_zera_a_r", "j_zera_a_r,Viera Ear 01 A Right,Ears,FALSE,FALSE,j_kao,j_zera_a_l", "j_zera_b_l,Viera Ear 01 B Left,Ears,FALSE,FALSE,j_kao,j_zera_b_r", "j_zera_b_r,Viera Ear 01 B Right,Ears,FALSE,FALSE,j_kao,j_zera_b_l", "j_zerb_a_l,Viera Ear 02 A Left,Ears,FALSE,FALSE,j_kao,j_zerb_a_r", "j_zerb_a_r,Viera Ear 02 A Right,Ears,FALSE,FALSE,j_kao,j_zerb_a_l", "j_zerb_b_l,Viera Ear 02 B Left,Ears,FALSE,FALSE,j_kao,j_zerb_b_r", "j_zerb_b_r,Viera Ear 02 B Right,Ears,FALSE,FALSE,j_kao,j_zerb_b_l", "j_zerc_a_l,Viera Ear 03 A Left,Ears,FALSE,FALSE,j_kao,j_zerc_a_r", "j_zerc_a_r,Viera Ear 03 A Right,Ears,FALSE,FALSE,j_kao,j_zerc_a_l", "j_zerc_b_l,Viera Ear 03 B Left,Ears,FALSE,FALSE,j_kao,j_zerc_b_r", "j_zerc_b_r,Viera Ear 03 B Right,Ears,FALSE,FALSE,j_kao,j_zerc_b_l", "j_zerd_a_l,Viera Ear 04 A Left,Ears,FALSE,FALSE,j_kao,j_zerd_a_r", "j_zerd_a_r,Viera Ear 04 A Right,Ears,FALSE,FALSE,j_kao,j_zerd_a_l", "j_zerd_b_l,Viera Ear 04 B Left,Ears,FALSE,FALSE,j_kao,j_zerd_b_r", "j_zerd_b_r,Viera Ear 04 B Right,Ears,FALSE,FALSE,j_kao,j_zerd_b_l", "j_sako_l,Clavicle Left,Chest,TRUE,FALSE,j_sebo_c,j_sako_r", "j_sako_r,Clavicle Right,Chest,TRUE,FALSE,j_sebo_c,j_sako_l", "j_mune_l,Breast Left,Chest,TRUE,FALSE,j_sebo_b,j_mune_r", "j_mune_r,Breast Right,Chest,TRUE,FALSE,j_sebo_b,j_mune_l", "iv_c_mune_l,Breast B Left,Chest,FALSE,TRUE,j_mune_l,iv_c_mune_r", "iv_c_mune_r,Breast B Right,Chest,FALSE,TRUE,j_mune_r,iv_c_mune_l", "n_hkata_l,Shoulder Left,Arms,TRUE,FALSE,j_ude_a_l,n_hkata_r", "n_hkata_r,Shoulder Right,Arms,TRUE,FALSE,j_ude_a_r,n_hkata_l", "j_ude_a_l,Arm Left,Arms,TRUE,FALSE,j_sako_l,j_ude_a_r", "j_ude_a_r,Arm Right,Arms,TRUE,FALSE,j_sako_r,j_ude_a_l", "iv_nitoukin_l,Bicep Left,Arms,FALSE,TRUE,j_ude_a_l,iv_nitoukin_r", "iv_nitoukin_r,Bicep Right,Arms,FALSE,TRUE,j_ude_a_r,iv_nitoukin_l", "n_hhiji_l,Elbow Left,Arms,TRUE,FALSE,j_ude_b_l,n_hhiji_r", "n_hhiji_r,Elbow Right,Arms,TRUE,FALSE,j_ude_b_r,n_hhiji_l", "j_ude_b_l,Forearm Left,Arms,TRUE,FALSE,j_ude_a_l,j_ude_b_r", "j_ude_b_r,Forearm Right,Arms,TRUE,FALSE,j_ude_a_r,j_ude_b_l", "n_hte_l,Wrist Left,Arms,TRUE,FALSE,j_ude_b_l,n_hte_r", "n_hte_r,Wrist Right,Arms,TRUE,FALSE,j_ude_b_r,n_hte_l", "j_te_l,Hand Left,Hands,TRUE,FALSE,n_hte_l,j_te_r", "j_te_r,Hand Right,Hands,TRUE,FALSE,n_hte_r,j_te_l", "j_oya_a_l,Thumb A Left,Hands,TRUE,FALSE,j_te_l,j_oya_a_r", "j_oya_a_r,Thumb A Right,Hands,TRUE,FALSE,j_te_r,j_oya_a_l", "j_oya_b_l,Thumb B Left,Hands,TRUE,FALSE,j_oya_a_l,j_oya_b_r", "j_oya_b_r,Thumb B Right,Hands,TRUE,FALSE,j_oya_a_r,j_oya_b_l", "j_hito_a_l,Index A Left,Hands,TRUE,FALSE,j_te_l,j_hito_a_r", "j_hito_a_r,Index A Right,Hands,TRUE,FALSE,j_te_r,j_hito_a_l", "j_hito_b_l,Index B Left,Hands,TRUE,FALSE,j_hito_a_l,j_hito_b_r", "j_hito_b_r,Index B Right,Hands,TRUE,FALSE,j_hito_a_r,j_hito_b_l", "j_naka_a_l,Middle A Left,Hands,TRUE,FALSE,j_te_l,j_naka_a_r", "j_naka_a_r,Middle A Right,Hands,TRUE,FALSE,j_te_r,j_naka_a_l", "j_naka_b_l,Middle B Left,Hands,TRUE,FALSE,j_naka_a_l,j_naka_b_r", "j_naka_b_r,Middle B Right,Hands,TRUE,FALSE,j_naka_a_r,j_naka_b_l", "j_kusu_a_l,Ring A Left,Hands,TRUE,FALSE,j_te_l,j_kusu_a_r", "j_kusu_a_r,Ring A Right,Hands,TRUE,FALSE,j_te_r,j_kusu_a_l", "j_kusu_b_l,Ring B Left,Hands,TRUE,FALSE,j_kusu_a_l,j_kusu_b_r", "j_kusu_b_r,Ring B Right,Hands,TRUE,FALSE,j_kusu_a_r,j_kusu_b_l", "j_ko_a_l,Pinky A Left,Hands,TRUE,FALSE,j_te_l,j_ko_a_r", "j_ko_a_r,Pinky A Right,Hands,TRUE,FALSE,j_te_r,j_ko_a_l", "j_ko_b_l,Pinky B Left,Hands,TRUE,FALSE,j_ko_a_l,j_ko_b_r", "j_ko_b_r,Pinky B Right,Hands,TRUE,FALSE,j_ko_a_r,j_ko_b_l", "iv_hito_c_l,Index C Left,Hands,FALSE,TRUE,j_hito_b_l,iv_hito_c_r", "iv_hito_c_r,Index C Right,Hands,FALSE,TRUE,j_hito_b_r,iv_hito_c_l", "iv_naka_c_l,Middle C Left,Hands,FALSE,TRUE,j_naka_b_l,iv_naka_c_r", "iv_naka_c_r,Middle C Right,Hands,FALSE,TRUE,j_naka_b_r,iv_naka_c_l", "iv_kusu_c_l,Ring C Left,Hands,FALSE,TRUE,j_kusu_b_l,iv_kusu_c_r", "iv_kusu_c_r,Ring C Right,Hands,FALSE,TRUE,j_kusu_b_r,iv_kusu_c_l", "iv_ko_c_l,Pinky C Left,Hands,FALSE,TRUE,j_ko_b_l,iv_ko_c_r", "iv_ko_c_r,Pinky C Right,Hands,FALSE,TRUE,j_ko_b_r,iv_ko_c_l", "n_sippo_a,Tail A,Tail,FALSE,FALSE,j_kosi,", "n_sippo_b,Tail B,Tail,FALSE,FALSE,n_sippo_a,", "n_sippo_c,Tail C,Tail,FALSE,FALSE,n_sippo_b,", "n_sippo_d,Tail D,Tail,FALSE,FALSE,n_sippo_c,", "n_sippo_e,Tail E,Tail,FALSE,FALSE,n_sippo_d,", "iv_shiri_l,Buttock Left,Groin,FALSE,TRUE,j_kosi,iv_shiri_r", "iv_shiri_r,Buttock Right,Groin,FALSE,TRUE,j_kosi,iv_shiri_l", "iv_kougan_l,Scrotum Left,Groin,FALSE,TRUE,iv_ochinko_a,iv_kougan_r", "iv_kougan_r,Scrotum Right,Groin,FALSE,TRUE,iv_ochinko_a,iv_kougan_l", "iv_ochinko_a,Penis A,Groin,FALSE,TRUE,j_kosi,", "iv_ochinko_b,Penis B,Groin,FALSE,TRUE,iv_ochinko_a,", "iv_ochinko_c,Penis C,Groin,FALSE,TRUE,iv_ochinko_b,", "iv_ochinko_d,Penis D,Groin,FALSE,TRUE,iv_ochinko_c,", "iv_ochinko_e,Penis E,Groin,FALSE,TRUE,iv_ochinko_d,", "iv_ochinko_f,Penis F,Groin,FALSE,TRUE,iv_ochinko_e,", "iv_omanko,Vagina,Groin,FALSE,TRUE,j_kosi,", "iv_kuritto,Clitoris,Groin,FALSE,TRUE,iv_omanko,", "iv_inshin_l,Labia Left,Groin,FALSE,TRUE,iv_omanko,iv_inshin_r", "iv_inshin_r,Labia Right,Groin,FALSE,TRUE,iv_omanko,iv_inshin_l", "iv_koumon,Anus,Groin,FALSE,TRUE,j_kosi,", "iv_koumon_l,Anus B Right,Groin,FALSE,TRUE,iv_koumon,iv_koumon_r", "iv_koumon_r,Anus B Left,Groin,FALSE,TRUE,iv_koumon,iv_koumon_l", "j_asi_a_l,Leg Left,Legs,TRUE,FALSE,j_kosi,j_asi_a_r", "j_asi_a_r,Leg Right,Legs,TRUE,FALSE,j_kosi,j_asi_a_l", "j_asi_b_l,Knee Left,Legs,TRUE,FALSE,j_asi_a_l,j_asi_b_r", "j_asi_b_r,Knee Right,Legs,TRUE,FALSE,j_asi_a_r,j_asi_b_l", "j_asi_c_l,Calf Left,Legs,TRUE,FALSE,j_asi_b_l,j_asi_c_r", "j_asi_c_r,Calf Right,Legs,TRUE,FALSE,j_asi_b_r,j_asi_c_l", "j_asi_d_l,Foot Left,Feet,TRUE,FALSE,j_asi_c_l,j_asi_d_r", "j_asi_d_r,Foot Right,Feet,TRUE,FALSE,j_asi_c_r,j_asi_d_l", "j_asi_e_l,Toes Left,Feet,TRUE,FALSE,j_asi_d_l,j_asi_e_r", "j_asi_e_r,Toes Right,Feet,TRUE,FALSE,j_asi_d_r,j_asi_e_l", "iv_asi_oya_a_l,Big Toe A Left,Feet,FALSE,TRUE,j_asi_e_l,iv_asi_oya_a_r", "iv_asi_oya_a_r,Big Toe A Right,Feet,FALSE,TRUE,j_asi_e_r,iv_asi_oya_a_l", "iv_asi_oya_b_l,Big Toe B Left,Feet,FALSE,TRUE,j_asi_oya_a_l,iv_asi_oya_b_r", "iv_asi_oya_b_r,Big Toe B Right,Feet,FALSE,TRUE,j_asi_oya_a_r,iv_asi_oya_b_l", "iv_asi_hito_a_l,Index Toe A Left,Feet,FALSE,TRUE,j_asi_e_l,iv_asi_hito_a_r", "iv_asi_hito_a_r,Index Toe A Right,Feet,FALSE,TRUE,j_asi_e_r,iv_asi_hito_a_l", "iv_asi_hito_b_l,Index Toe B Left,Feet,FALSE,TRUE,j_asi_hito_a_l,iv_asi_hito_b_r", "iv_asi_hito_b_r,Index Toe B Right,Feet,FALSE,TRUE,j_asi_hito_a_r,iv_asi_hito_b_l", "iv_asi_naka_a_l,Middle Toe A Left,Feet,FALSE,TRUE,j_asi_e_l,iv_asi_naka_a_r", "iv_asi_naka_a_r,Middle Toe A Right,Feet,FALSE,TRUE,j_asi_e_r,iv_asi_naka_a_l", "iv_asi_naka_b_l,Middle Toe B Left,Feet,FALSE,TRUE,j_asi_naka_b_l,iv_asi_naka_b_r", "iv_asi_naka_b_r,Middle Toe B Right,Feet,FALSE,TRUE,j_asi_naka_b_r,iv_asi_naka_b_l", "iv_asi_kusu_a_l,Fore Toe A Left,Feet,FALSE,TRUE,j_asi_e_l,iv_asi_kusu_a_r", "iv_asi_kusu_a_r,Fore Toe A Right,Feet,FALSE,TRUE,j_asi_e_r,iv_asi_kusu_a_l", "iv_asi_kusu_b_l,Fore Toe B Left,Feet,FALSE,TRUE,j_asi_kusu_a_l,iv_asi_kusu_b_r", "iv_asi_kusu_b_r,Fore Toe B Right,Feet,FALSE,TRUE,j_asi_kusu_a_r,iv_asi_kusu_b_l", "iv_asi_ko_a_l,Pinky Toe A Left,Feet,FALSE,TRUE,j_asi_e_l,iv_asi_ko_a_r", "iv_asi_ko_a_r,Pinky Toe A Right,Feet,FALSE,TRUE,j_asi_e_r,iv_asi_ko_a_l", "iv_asi_ko_b_l,Pinky Toe B Left,Feet,FALSE,TRUE,j_asi_ko_a_l,iv_asi_ko_b_r", "iv_asi_ko_b_r,Pinky Toe B Right,Feet,FALSE,TRUE,j_asi_ko_a_r,iv_asi_ko_b_l", "j_ex_met_va,Visor,Hat,FALSE,FALSE,j_kao,", "j_ex_met_a,Hat Accessory A,Hat,FALSE,FALSE,j_kao,", "j_ex_met_b,Hat Accessory B,Hat,FALSE,FALSE,j_kao,", "n_ear_b_l,Earring B Left,Earrings,FALSE,FALSE,n_ear_a_l,n_ear_b_r", "n_ear_b_r,Earring B Right,Earrings,FALSE,FALSE,n_ear_a_r,n_ear_b_l", "n_ear_a_l,Earring A Left,Earrings,FALSE,FALSE,j_kao,n_ear_a_r", "n_ear_a_r,Earring A Right,Earrings,FALSE,FALSE,j_kao,n_ear_a_l", "j_ex_top_a_l,Cape A Left,Cape,FALSE,FALSE,j_sebo_c,j_ex_top_a_r", "j_ex_top_a_r,Cape A Right,Cape,FALSE,FALSE,j_sebo_c,j_ex_top_a_l", "j_ex_top_b_l,Cape B Left,Cape,FALSE,FALSE,j_ex_top_a_l,j_ex_top_b_r", "j_ex_top_b_r,Cape B Right,Cape,FALSE,FALSE,j_ex_top_a_r,j_ex_top_b_l", "n_kataarmor_l,Pauldron Left,Armor,FALSE,FALSE,n_hkata_l,n_kataarmor_r", "n_kataarmor_r,Pauldron Right,Armor,FALSE,FALSE,n_hkata_r,n_kataarmor_l", "n_hijisoubi_l,Elbow Plate Left,Armor,FALSE,FALSE,n_hhiji_l,n_hijisoubi_r", "n_hijisoubi_r,Elbow Plate Right,Armor,FALSE,FALSE,n_hhiji_r,n_hijisoubi_l", "n_hizasoubi_l,Knee Plate Left,Armor,FALSE,FALSE,j_asi_b_l,n_hizasoubi_r", "n_hizasoubi_r,Knee Plate Right,Armor,FALSE,FALSE,j_asi_b_r,n_hizasoubi_l", "j_sk_b_a_l,Skirt Back A Left,Skirt,FALSE,FALSE,j_kosi,j_sk_b_a_r", "j_sk_b_a_r,Skirt Back A Right,Skirt,FALSE,FALSE,j_kosi,j_sk_b_a_l", "j_sk_b_b_l,Skirt Back B Left,Skirt,FALSE,FALSE,j_sk_b_a_l,j_sk_b_b_r", "j_sk_b_b_r,Skirt Back B Right,Skirt,FALSE,FALSE,j_sk_b_a_r,j_sk_b_b_l", "j_sk_b_c_l,Skirt Back C Left,Skirt,FALSE,FALSE,j_sk_b_b_l,j_sk_b_c_r", "j_sk_b_c_r,Skirt Back C Right,Skirt,FALSE,FALSE,j_sk_b_b_r,j_sk_b_c_l", "j_sk_f_a_l,Skirt Front A Left,Skirt,FALSE,FALSE,j_kosi,j_sk_f_a_r", "j_sk_f_a_r,Skirt Front A Right,Skirt,FALSE,FALSE,j_kosi,j_sk_f_a_l", "j_sk_f_b_l,Skirt Front B Left,Skirt,FALSE,FALSE,j_sk_f_a_l,j_sk_f_b_r", "j_sk_f_b_r,Skirt Front B Right,Skirt,FALSE,FALSE,j_sk_f_a_r,j_sk_f_b_l", "j_sk_f_c_l,Skirt Front C Left,Skirt,FALSE,FALSE,j_sk_f_b_l,j_sk_f_c_r", "j_sk_f_c_r,Skirt Front C Right,Skirt,FALSE,FALSE,j_sk_f_b_r,j_sk_f_c_l", "j_sk_s_a_l,Skirt Side A Left,Skirt,FALSE,FALSE,j_kosi,j_sk_s_a_r", "j_sk_s_a_r,Skirt Side A Right,Skirt,FALSE,FALSE,j_kosi,j_sk_s_a_l", "j_sk_s_b_l,Skirt Side B Left,Skirt,FALSE,FALSE,j_sk_s_a_l,j_sk_s_b_r", "j_sk_s_b_r,Skirt Side B Right,Skirt,FALSE,FALSE,j_sk_s_a_r,j_sk_s_b_l", "j_sk_s_c_l,Skirt Side C Left,Skirt,FALSE,FALSE,j_sk_s_b_l,j_sk_s_c_r", "j_sk_s_c_r,Skirt Side C Right,Skirt,FALSE,FALSE,j_sk_s_b_r,j_sk_s_c_l", "n_throw,Throw,Root,FALSE,FALSE,j_kosi,", "j_buki_sebo_l,Scabbard Left,Equipment,FALSE,FALSE,j_kosi,j_buki_sebo_r", "j_buki_sebo_r,Scabbard Right,Equipment,FALSE,FALSE,j_kosi,j_buki_sebo_l", "j_buki2_kosi_l,Holster Left,Equipment,FALSE,FALSE,j_kosi,j_buki2_kosi_r", "j_buki2_kosi_r,Holster Right,Equipment,FALSE,FALSE,j_kosi,j_buki2_kosi_l", "j_buki_kosi_l,Sheath Left,Equipment,FALSE,FALSE,j_kosi,j_buki_kosi_r", "j_buki_kosi_r,Sheath Right,Equipment,FALSE,FALSE,j_kosi,j_buki_kosi_l", "n_buki_tate_l,Shield Left,Equipment,FALSE,FALSE,n_hte_l,n_buki_tate_r", "n_buki_tate_r,Shield Right,Equipment,FALSE,FALSE,n_hte_r,n_buki_tate_l", "n_buki_l,Weapon Left,Equipment,FALSE,FALSE,j_te_l,n_buki_r", "n_buki_r,Weapon Right,Equipment,FALSE,FALSE,j_te_r,n_buki_l", "j_f_face,Face Root (Dawntrail),Face,TRUE,FALSE,j_kao,", "j_f_hana,Nose,Face,TRUE,FALSE,j_kao,", "j_f_hana_l,Nose Left,Face,TRUE,FALSE,j_f_hana,j_f_hana_r", "j_f_hana_r,Nose Right,Face,TRUE,FALSE,j_f_hana,j_f_hana_l", "j_f_uhana,Bridge,Face,TRUE,FALSE,j_f_hana,", "j_f_hoho_l,Cheek Left,Cheeks,TRUE,FALSE,j_f_face,j_f_hoho_r", "j_f_hoho_r,Cheek Right,Cheeks,TRUE,FALSE,j_f_face,j_f_hoho_l", "j_f_dhoho_l,Outer Cheek Left,Cheeks,TRUE,FALSE,j_f_face,j_f_dhoho_r", "j_f_dhoho_r,Outer Cheek Right,Cheeks,TRUE,FALSE,j_f_face,j_f_dhoho_l", "j_f_shoho_l,Middle Cheek Left,Cheeks,TRUE,FALSE,j_f_face,j_f_shoho_r", "j_f_shoho_r,Middle Cheek Right,Cheeks,TRUE,FALSE,j_f_face,j_f_shoho_l", "j_f_dmemoto_l,Inner Cheek Left,Cheeks,TRUE,FALSE,j_f_face,j_f_dmemoto_r", "j_f_dmemoto_r,Inner Cheek Right,Cheeks,TRUE,FALSE,j_f_face,j_f_dmemoto_l", "j_f_dmiken_l,Glabella Left,Face,TRUE,FALSE,j_f_face,j_f_dmiken_r", "j_f_dmiken_r,Glabella Right,Face,TRUE,FALSE,j_f_face,j_f_dmiken_l", "j_f_ago,Jaw,Jaw,TRUE,FALSE,j_f_face,", "j_f_dago,Lower Jaw,Jaw,TRUE,FALSE,j_f_face,", "j_f_hagukiup,Upper Teeth,Jaw,TRUE,FALSE,j_f_face,", "j_f_hagukidn,Lower Teeth,Jaw,TRUE,FALSE,j_f_face,", "j_f_bero_01,Tongue A,Tongue,TRUE,FALSE,j_f_ago,", "j_f_bero_02,Tongue B,Tongue,TRUE,FALSE,j_f_bero_01,", "j_f_bero_03,Tongue C,Tongue,TRUE,FALSE,j_f_bero_02,", "j_f_dmlip_01_l,Lip Lower Left A,Lips,TRUE,FALSE,j_f_ago,j_f_dmlip_01_r", "j_f_dmlip_02_l,Lip Lower Left B,Lips,TRUE,FALSE,j_f_ago,j_f_dmlip_02_r", "j_f_umlip_01_l,Lip Upper Left A,Lips,TRUE,FALSE,j_f_ago,j_f_umlip_01_r", "j_f_umlip_02_l,Lip Upper Left B,Lips,TRUE,FALSE,j_f_ago,j_f_umlip_02_r", "j_f_dmlip_01_r,Lip Lower Right A,Lips,TRUE,FALSE,j_f_ago,j_f_dmlip_01_l", "j_f_dmlip_02_r,Lip Lower Right B,Lips,TRUE,FALSE,j_f_ago,j_f_dmlip_02_l", "j_f_umlip_01_r,Lip Upper Right A,Lips,TRUE,FALSE,j_f_ago,j_f_umlip_01_l", "j_f_umlip_02_r,Lip Upper Right B,Lips,TRUE,FALSE,j_f_ago,j_f_umlip_02_l", "j_f_dlip_01_l,Lip Lower Left Center A,Lips,TRUE,FALSE,j_f_ago,j_f_dlip_01_r", "j_f_dlip_02_l,Lip Lower Left Center B,Lips,TRUE,FALSE,j_f_ago,j_f_dlip_02_r", "j_f_ulip_01_l,Lip Upper Left Center A,Lips,TRUE,FALSE,j_f_ago,j_f_ulip_01_r", "j_f_ulip_02_l,Lip Upper Left Center B,Lips,TRUE,FALSE,j_f_ago,j_f_ulip_02_r", "j_f_dlip_01_r,Lip Lower Right Center A,Lips,TRUE,FALSE,j_f_ago,j_f_dlip_01_l", "j_f_dlip_02_r,Lip Lower Right Center B,Lips,TRUE,FALSE,j_f_ago,j_f_dlip_02_l", "j_f_ulip_01_r,Lip Upper Right Center A,Lips,TRUE,FALSE,j_f_ago,j_f_ulip_01_l", "j_f_ulip_02_r,Lip Upper Right Center B,Lips,TRUE,FALSE,j_f_ago,j_f_ulip_02_l", "j_f_uslip_l,Lip Upper Left Corner A,Lips,TRUE,FALSE,j_f_ago,j_f_uslip_r", "j_f_dslip_l,Lip Lower Left Corner A,Lips,TRUE,FALSE,j_f_ago,j_f_dslip_r", "j_f_uslip_r,Lip Upper Right Corner A,Lips,TRUE,FALSE,j_f_ago,j_f_uslip_l", "j_f_dslip_r,Lip Lower Right Corner A,Lips,TRUE,FALSE,j_f_ago,j_f_dslip_l", "j_f_mab_l,Eye Socket Left,Eyes,TRUE,FALSE,j_f_face,j_f_mab_r", "j_f_eyepuru_l,Iris Left,Eyes,TRUE,FALSE,j_f_face,j_f_eyepuru_r", "j_f_mabdn_01_l,Lower Eyelid Left,Eyes,TRUE,FALSE,j_f_face,j_f_mabdn_01_r", "j_f_mabup_01_l,Upper Eyelid Left,Eyes,TRUE,FALSE,j_f_face,j_f_mabup_01_r", "j_f_mabdn_02out_l,Lower Eyelid Outer Left,Eyes,TRUE,FALSE,j_f_face,j_f_mabdn_02out_r", "j_f_mabdn_03in_l,Lower Eyelid Inner Left,Eyes,TRUE,FALSE,j_f_face,j_f_mabdn_03in_r", "j_f_mabup_02out_l,Upper Eyelid Outer Left,Eyes,TRUE,FALSE,j_f_face,j_f_mabup_02out_r", "j_f_mabup_03in_l,Upper Eyelid Inner Left,Eyes,TRUE,FALSE,j_f_face,j_f_mabup_03in_r", "j_f_mab_r,Eye Socket Right,Eyes,TRUE,FALSE,j_f_face,j_f_mab_l", "j_f_eyepuru_r,Iris Right,Eyes,TRUE,FALSE,j_f_face,j_f_eyepuru_l", "j_f_mabdn_01_r,Lower Eyelid Right,Eyes,TRUE,FALSE,j_f_face,j_f_mabdn_01_l", "j_f_mabup_01_r,Upper Eyelid Right,Eyes,TRUE,FALSE,j_f_face,j_f_mabup_01_l", "j_f_mabdn_02out_r,Lower Eyelid Outer Right,Eyes,TRUE,FALSE,j_f_face,j_f_mabdn_02out_l", "j_f_mabdn_03in_r,Lower Eyelid Inner Right,Eyes,TRUE,FALSE,j_f_face,j_f_mabdn_03in_l", "j_f_mabup_02out_r,Upper Eyelid Outer Right,Eyes,TRUE,FALSE,j_f_face,j_f_mabup_02out_l", "j_f_mabup_03in_r,Upper Eyelid Inner Right,Eyes,TRUE,FALSE,j_f_face,j_f_mabup_03in_l", "j_f_mmayu_l,Eyebrow B Left,Eyes,TRUE,FALSE,j_f_face,j_f_mmayu_r", "j_f_miken_01_l,Brow Ridge A Left,Eyes,TRUE,FALSE,j_f_mmayu_l,j_f_miken_01_r", "j_f_miken_02_l,Brow Ridge B Left,Eyes,TRUE,FALSE,j_f_miken_01_l,j_f_miken_02_r", "j_f_mmayu_r,Eyebrow B Right,Eyes,TRUE,FALSE,j_f_face,j_f_mmayu_l", "j_f_miken_01_r,Brow Ridge A Right,Eyes,TRUE,FALSE,j_f_mmayu_r,j_f_miken_01_l", "j_f_miken_02_r,Brow Ridge B Right,Eyes,TRUE,FALSE,j_f_miken_01_r,j_f_miken_02_l", }; public static readonly Dictionary DisplayableFamilies = new() { { BoneFamily.Cheeks, null }, { BoneFamily.Jaw, null }, { BoneFamily.Tongue, null }, { BoneFamily.Lips, null }, { BoneFamily.Eyes, null }, { BoneFamily.Spine, null }, { BoneFamily.Hair, null }, { BoneFamily.Face, null }, { BoneFamily.Ears, null }, { BoneFamily.Chest, null }, { BoneFamily.Arms, null }, { BoneFamily.Hands, null }, { BoneFamily.Tail, null }, { BoneFamily.Groin, "NSFW IVCS Bones" }, { BoneFamily.Legs, null }, { BoneFamily.Feet, null }, { BoneFamily.Earrings, "Some mods utilize these bones for their physics properties" }, { BoneFamily.Hat, null }, { BoneFamily.Cape, "Some mods utilize these bones for their physics properties" }, { BoneFamily.Armor, null }, { BoneFamily.Skirt, null }, { BoneFamily.Equipment, "These may behave oddly" }, { BoneFamily.Unknown, "These bones weren't immediately identifiable.\nIf you can figure out what they're for, let us know and we'll add them to the table." } }; private static readonly Dictionary BoneTable = new(); private static readonly Dictionary BoneLookupByDispName = new(); static BoneData() { //apparently static constructors are only guaranteed to START before the class is called //which can apparently lead to race conditions, as I've found out //this lock is to make sure the table is fully initialized before anything else can try to look at it lock (BoneTable) { var rowIndex = 0; foreach (var entry in BoneRawTable) { try { var cells = entry.Split(','); var codename = cells[0]; var dispName = cells[1]; BoneTable[codename] = new BoneDatum(rowIndex, cells); BoneLookupByDispName[dispName] = codename; if (BoneTable[codename].Family == BoneFamily.Unknown) { throw new Exception("what the fuck?"); } } catch { throw new InvalidCastException($"Couldn't parse raw bone table @ row {rowIndex}"); } ++rowIndex; } //iterate through the complete collection and assign children to their parents foreach (var kvp in BoneTable) { var datum = BoneTable[kvp.Key]; datum.Children = BoneTable.Where(x => x.Value.Parent == kvp.Key).Select(x => x.Key).ToArray(); BoneTable[kvp.Key] = datum; } } } public static void LogNewBones(params string[] boneNames) { var probablyHairstyleBones = boneNames.Where(IsProbablyHairstyle).ToArray(); foreach (var hairBone in ParseHairstyle(probablyHairstyleBones)) { BoneTable[hairBone.Codename] = hairBone; } foreach (var boneName in boneNames.Except(BoneTable.Keys)) { var newBone = new BoneDatum { RowIndex = -1, Codename = boneName, DisplayName = $"Unknown ({boneName})", Family = BoneFamily.Unknown, Parent = "j_kosi", Children = Array.Empty(), MirroredCodename = null }; } } public static void UpdateParentage(string parentName, string childName) { var child = BoneTable[childName]; var parent = BoneTable[parentName]; child.Parent = parentName; parent.Children = parent.Children.Append(childName).Distinct().ToArray(); BoneTable[childName] = child; BoneTable[parentName] = parent; } public static string GetBoneDisplayName(string codename) { return BoneTable.TryGetValue(codename, out var row) ? row.DisplayName : codename; } public static string? GetBoneCodename(string boneDisplayName) { return BoneLookupByDispName.TryGetValue(boneDisplayName, out var name) ? name : null; } public static List GetBoneCodenames() { return BoneTable.Keys.ToList(); } public static List GetBoneCodenames(Func predicate) { return BoneTable.Where(x => predicate(x.Value)).Select(x => x.Key).ToList(); } public static List GetBoneDisplayNames() { return BoneLookupByDispName.Keys.ToList(); } public static BoneFamily GetBoneFamily(string codename) { return BoneTable.TryGetValue(codename, out var row) ? row.Family : BoneFamily.Unknown; } public static bool IsDefaultBone(string codename) { return BoneTable.TryGetValue(codename, out var row) && row.IsDefault; } public static int GetBoneRanking(string codename) { return BoneTable.TryGetValue(codename, out var row) ? row.RowIndex : 0; } public static bool IsIVCSBone(string codename) { return BoneTable.TryGetValue(codename, out var row) && row.IsIVCS; } public static string? GetBoneMirror(string codename) { return BoneTable.TryGetValue(codename, out var row) ? row.MirroredCodename : null; } public static string[] GetChildren(string codename) { return BoneTable.TryGetValue(codename, out var row) ? row.Children : Array.Empty(); } public static bool IsProbablyHairstyle(string codename) { return Regex.IsMatch(codename, @"j_ex_h\d\d\d\d_ke_[abcdeflrsu](_[abcdeflrsu])?"); } public static bool IsNewBone(string codename) { return !BoneTable.ContainsKey(codename); } private static BoneFamily ParseFamilyName(string n) { var simplified = n.Split(' ').FirstOrDefault()?.ToLower() ?? string.Empty; var fam = simplified switch { "root" => BoneFamily.Root, "spine" => BoneFamily.Spine, "hair" => BoneFamily.Hair, "face" => BoneFamily.Face, "ears" => BoneFamily.Ears, "chest" => BoneFamily.Chest, "arms" => BoneFamily.Arms, "hands" => BoneFamily.Hands, "tail" => BoneFamily.Tail, "groin" => BoneFamily.Groin, "legs" => BoneFamily.Legs, "feet" => BoneFamily.Feet, "earrings" => BoneFamily.Earrings, "hat" => BoneFamily.Hat, "cape" => BoneFamily.Cape, "armor" => BoneFamily.Armor, "skirt" => BoneFamily.Skirt, "cheeks" => BoneFamily.Cheeks, "equipment" => BoneFamily.Equipment, "jaw" => BoneFamily.Jaw, "tongue" => BoneFamily.Tongue, "lips" => BoneFamily.Lips, "eyes" => BoneFamily.Eyes, _ => BoneFamily.Unknown }; return fam; } public struct BoneDatum : IComparable { public int RowIndex; public string Codename; public string DisplayName; public BoneFamily Family; public bool IsDefault; public bool IsIVCS; public string? Parent; public string? MirroredCodename; public string[] Children; public BoneDatum(int row, string[] fields) { RowIndex = row; var i = 0; Codename = fields[i++]; DisplayName = fields[i++]; Family = ParseFamilyName(fields[i++]); IsDefault = bool.Parse(fields[i++]); IsIVCS = bool.Parse(fields[i++]); Parent = fields[i].IsNullOrEmpty() ? null : fields[i]; i++; MirroredCodename = fields[i].IsNullOrEmpty() ? null : fields[i]; i++; Children = Array.Empty(); } public int CompareTo(BoneDatum other) { return RowIndex != other.RowIndex ? RowIndex.CompareTo(other.RowIndex) : string.Compare(DisplayName, other.DisplayName, StringComparison.Ordinal); } } #region hair stuff private static IEnumerable ParseHairstyle(params string[] boneNames) { List output = new(); var index = 0; foreach (var style in boneNames.GroupBy(x => Regex.Match(x, @"\d\d\d\d").Value)) { try { var parsedBones = style.Select(ParseHairBone).ToArray(); // if any of the first subs is nonstandard letter, we can presume that any bcd... are part of a rising sequence var firstAsc = parsedBones.Any(x => x.sub1 is "a" or "c" or "d" or "e"); //and we can then presume that the second subs are directional //or vice versa. the naming conventions aren't really consistent about whether the sequence is first or second foreach (var boneInfo in parsedBones) { StringBuilder dispName = new(); dispName.Append($"Hair #{boneInfo.id}"); var sub1 = GetHairBoneSubLabel(boneInfo.sub1, firstAsc); var sub2 = boneInfo.sub2 == null ? null : GetHairBoneSubLabel(boneInfo.sub2, !firstAsc); dispName.Append($" {sub1}"); if (sub2 != null) { dispName.Append($" {sub2}"); } var result = new BoneDatum { RowIndex = -1, Codename = boneInfo.name, DisplayName = dispName.ToString(), Family = BoneFamily.Hair, IsDefault = false, IsIVCS = false, Parent = "j_kao", Children = Array.Empty(), MirroredCodename = null }; output.Add(result); } } catch (Exception e) { Plugin.Logger.Error($"Failed to dynamically parse bones for hairstyle of '{boneNames[index]}'"); } index++; } return output; } private static (string name, int id, string sub1, string? sub2) ParseHairBone(string boneName) { var groups = Regex.Match(boneName.ToLower(), @"j_ex_h(\d\d\d\d)_ke_([abcdeflrsu])(?:_([abcdeflrsu]))?") .Groups; var idNo = int.Parse(groups[1].Value); var subFirst = groups[2].Value; var subSecond = groups[3].Value.IsNullOrWhitespace() ? null : groups[3].Value; return (boneName, idNo, subFirst, subSecond); } private static string GetHairBoneSubLabel(string sub, bool ascending) { return (sub.ToLower(), ascending) switch { ("a", _) => "A", ("b", true) => "B", ("b", false) => "Back", ("c", _) => "C", ("d", _) => "D", ("e", _) => "E", ("f", true) => "F", ("f", false) => "Front", ("l", _) => "Left", ("r", _) => "Right", ("u", _) => "Upper", ("s", _) => "Side", (_, true) => "Next", (_, false) => "Bone" }; } #endregion }