/// <summary> /// Get the gear entries list, optionally with a substring filter. /// </summary> /// <param name="substring"></param> /// <returns></returns> public async Task <List <XivGear> > GetCachedGearList(string substring = null) { WhereClause where = null; if (substring != null) { where = new WhereClause(); where.Comparer = WhereClause.ComparisonType.Like; where.Column = "name"; where.Value = "%" + substring + "%"; } List <XivGear> mainHands = new List <XivGear>(); List <XivGear> offHands = new List <XivGear>(); var list = await BuildListFromTable("items", where, async (reader) => { var primaryMi = new XivGearModelInfo(); var item = new XivGear { ExdID = reader.GetInt32("exd_id"), PrimaryCategory = XivStrings.Gear, SecondaryCategory = reader.GetString("slot_full"), ModelInfo = primaryMi, }; item.Name = reader.GetString("name"); item.IconNumber = (uint)reader.GetInt32("icon_id"); primaryMi.IsWeapon = reader.GetBoolean("is_weapon"); primaryMi.PrimaryID = reader.GetInt32("primary_id"); primaryMi.SecondaryID = reader.GetInt32("secondary_id"); primaryMi.ImcSubsetID = reader.GetInt32("imc_variant"); if (item.Name.Contains(XivStrings.Main_Hand)) { mainHands.Add(item); } else if (item.Name.Contains(XivStrings.Off_Hand)) { offHands.Add(item); } return(item); }); // Assign pairs based on items that came out of the same EXD row. foreach (var item in mainHands) { var pair = offHands.FirstOrDefault(x => x.ExdID == item.ExdID); if (pair != null) { pair.PairedItem = item; item.PairedItem = pair; } } return(list); }
/// <summary> /// Gets any missing gear that must be added manualy as it does not exist in the items exd /// </summary> /// <returns>The list of missing gear</returns> private List <XivGear> GetMissingGear() { var xivGearList = new List <XivGear>(); var xivGear = new XivGear { Name = "SmallClothes Body", Category = XivStrings.Gear, ItemCategory = _slotNameDictionary[4], ModelInfo = new XivModelInfo { ModelID = 0, Variant = 1, Body = 0 } }; xivGearList.Add(xivGear); xivGear = new XivGear { Name = "SmallClothes Legs", Category = XivStrings.Gear, ItemCategory = _slotNameDictionary[7], ModelInfo = new XivModelInfo { ModelID = 0, Variant = 1, Body = 0 } }; xivGearList.Add(xivGear); xivGear = new XivGear { Name = "SmallClothes Feet", Category = XivStrings.Gear, ItemCategory = _slotNameDictionary[8], ModelInfo = new XivModelInfo { ModelID = 0, Variant = 1, Body = 0 } }; xivGearList.Add(xivGear); return(xivGearList); }
/// <summary> /// Gets the Icon info for a specific gear item /// </summary> /// <param name="gearItem">The gear item</param> /// <returns>A list of TexTypePath containing Icon Info</returns> public async Task <List <TexTypePath> > GetIconInfo(XivGear gearItem) { var ttpList = new List <TexTypePath>(); var iconString = gearItem.IconNumber.ToString(); var iconBaseNum = iconString.Substring(0, 2).PadRight(iconString.Length, '0'); var iconFolder = $"ui/icon/{iconBaseNum.PadLeft(6, '0')}"; var iconHQFolder = $"{iconFolder}/hq"; var iconFile = $"{iconString.PadLeft(6, '0')}.tex"; if (await _index.FileExists(HashGenerator.GetHash(iconFile), HashGenerator.GetHash(iconFolder), XivDataFile._06_Ui)) { ttpList.Add(new TexTypePath { Name = "Icon", Path = $"{iconFolder}/{iconFile}", Type = XivTexType.Icon, DataFile = XivDataFile._06_Ui }); } if (await _index.FileExists(HashGenerator.GetHash(iconFile), HashGenerator.GetHash(iconHQFolder), XivDataFile._06_Ui)) { ttpList.Add(new TexTypePath { Name = "HQ Icon", Path = $"{iconHQFolder}/{iconFile}", Type = XivTexType.Icon, DataFile = XivDataFile._06_Ui }); } return(ttpList); }
/// <summary> /// A getter for available gear in the Item exd files /// </summary> /// <returns>A list containing XivGear data</returns> public async Task <List <XivGear> > GetUnCachedGearList() { // These are the offsets to relevant data // These will need to be changed if data gets added or removed with a patch const int modelDataCheckOffset = 30; int dataLength = 160; const int nameDataOffset = 14; const int modelDataOffset = 24; const int iconDataOffset = 136; int slotDataOffset = 154; if (_xivLanguage == XivLanguage.Chinese) { dataLength = 168; slotDataOffset = 156; } if (_xivLanguage == XivLanguage.Korean) { dataLength = 160; slotDataOffset = 154; } var xivGearList = new List <XivGear>(); xivGearList.AddRange(GetMissingGear()); var ex = new Ex(_gameDirectory, _xivLanguage); var itemDictionary = await ex.ReadExData(XivEx.item); // Loops through all the items in the item exd files // Item files start at 0 and increment by 500 for each new file // Item_0, Item_500, Item_1000, etc. await Task.Run(() => Parallel.ForEach(itemDictionary, (item) => { try { // This checks whether there is any model data present in the current item if (item.Value[modelDataCheckOffset] <= 0 && item.Value[modelDataCheckOffset + 1] <= 0) { return; } var primaryMi = new XivGearModelInfo(); var secondaryMi = new XivGearModelInfo(); var hasSecondary = false; var xivGear = new XivGear { ExdID = item.Key, PrimaryCategory = XivStrings.Gear, ModelInfo = primaryMi, }; /* Used to determine if the given model is a weapon * This is important because the data is formatted differently * The model data is a 16 byte section separated into two 8 byte parts (primary model, secondary model) * Format is 8 bytes in length with 2 bytes per data point [short, short, short, short] * Gear: primary model [blank, blank, variant, ID] nothing in secondary model * Weapon: primary model [blank, variant, body, ID] secondary model [blank, variant, body, ID] */ var isWeapon = false; // Big Endian Byte Order using (var br = new BinaryReaderBE(new MemoryStream(item.Value))) { br.BaseStream.Seek(nameDataOffset, SeekOrigin.Begin); var nameOffset = br.ReadInt16(); // Model Data br.BaseStream.Seek(modelDataOffset, SeekOrigin.Begin); // Primary Model Key primaryMi.ModelKey = Quad.Read(br.ReadBytes(8), 0); br.BaseStream.Seek(-8, SeekOrigin.Current); // Primary Blank var unused = br.ReadInt16(); // Primary Variant for weapon, blank otherwise var weaponVariant = br.ReadInt16(); if (weaponVariant != 0) { primaryMi.ImcSubsetID = weaponVariant; primaryMi.IsWeapon = true; isWeapon = true; } // Primary Body if weapon, Variant otherwise if (isWeapon) { primaryMi.SecondaryID = br.ReadInt16(); } else { primaryMi.ImcSubsetID = br.ReadInt16(); } // Primary Model ID primaryMi.PrimaryID = br.ReadInt16(); // Secondary Model Key isWeapon = false; secondaryMi.ModelKey = Quad.Read(br.ReadBytes(8), 0); br.BaseStream.Seek(-8, SeekOrigin.Current); // Secondary Blank var unused2 = br.ReadInt16(); // Secondary Variant for weapon, blank otherwise weaponVariant = br.ReadInt16(); if (weaponVariant != 0) { secondaryMi.ImcSubsetID = weaponVariant; secondaryMi.IsWeapon = true; isWeapon = true; } // Secondary Body if weapon, Variant otherwise if (isWeapon) { secondaryMi.SecondaryID = br.ReadInt16(); } else { secondaryMi.ImcSubsetID = br.ReadInt16(); } // Secondary Model ID secondaryMi.PrimaryID = br.ReadInt16(); // Icon br.BaseStream.Seek(iconDataOffset, SeekOrigin.Begin); xivGear.IconNumber = br.ReadUInt16(); // Gear Slot/Category br.BaseStream.Seek(slotDataOffset, SeekOrigin.Begin); int slotNum = br.ReadByte(); // Waist items do not have texture or model data if (slotNum == 6) { return; } xivGear.EquipSlotCategory = slotNum; xivGear.SecondaryCategory = _slotNameDictionary.ContainsKey(slotNum) ? _slotNameDictionary[slotNum] : "Unknown"; // Gear Name var gearNameOffset = dataLength + nameOffset; var gearNameLength = item.Value.Length - gearNameOffset; br.BaseStream.Seek(gearNameOffset, SeekOrigin.Begin); var nameString = Encoding.UTF8.GetString(br.ReadBytes(gearNameLength)).Replace("\0", ""); xivGear.Name = new string(nameString.Where(c => !char.IsControl(c)).ToArray()); xivGear.Name = xivGear.Name.Trim(); // If we have a secondary model XivGear secondaryItem = null; if (secondaryMi.PrimaryID != 0) { // Make a new item for it. secondaryItem = (XivGear)xivGear.Clone(); secondaryItem.ModelInfo = secondaryMi; xivGear.Name += " - " + XivStrings.Main_Hand; secondaryItem.Name += " - " + XivStrings.Off_Hand; xivGear.PairedItem = secondaryItem; secondaryItem.PairedItem = xivGear; xivGear.SecondaryCategory = XivStrings.Dual_Wield; secondaryItem.SecondaryCategory = XivStrings.Dual_Wield; } lock (_gearLock) { xivGearList.Add(xivGear); if (secondaryItem != null) { xivGearList.Add(secondaryItem); } } } } catch (Exception ex) { throw; } })); xivGearList.Sort(); return(xivGearList); }
/// <summary> /// Gets the available races that contain model data for the given gear /// </summary> /// <remarks> /// This checks to see if the mdl file for each race exists in the mdl folder /// It creates a list of the races which do have an available mdl file /// </remarks> /// <param name="xivGear">A gear item</param> /// <returns>A list of XivRace data</returns> public async Task <List <XivRace> > GetRacesForModels(XivGear xivGear, XivDataFile dataFile) { var itemType = xivGear.GetPrimaryItemType(); var modelID = xivGear.ModelInfo.PrimaryID.ToString().PadLeft(4, '0'); var raceList = new List <XivRace>(); if (itemType == XivItemType.weapon) { return(new List <XivRace> { XivRace.All_Races }); } string mdlFolder; var id = xivGear.ModelInfo.PrimaryID.ToString().PadLeft(4, '0'); switch (itemType) { case XivItemType.equipment: mdlFolder = $"chara/{itemType}/e{id}/model"; break; case XivItemType.accessory: mdlFolder = $"chara/{itemType}/a{id}/model"; break; default: mdlFolder = ""; break; } var testFilesDictionary = new Dictionary <int, string>(); // loop through each race ID to create a dictionary containing [Hashed file name, race ID] foreach (var ID in IDRaceDictionary.Keys) { string mdlFile; switch (itemType) { case XivItemType.equipment: mdlFile = $"c{ID}e{modelID}_{xivGear.GetItemSlotAbbreviation()}.mdl"; break; case XivItemType.accessory: mdlFile = $"c{ID}a{modelID}_{xivGear.GetItemSlotAbbreviation()}.mdl"; break; default: mdlFile = ""; break; } testFilesDictionary.Add(HashGenerator.GetHash(mdlFile), ID); } // get the list of hashed file names from the mtrl folder var files = await _index.GetAllHashedFilesInFolder(HashGenerator.GetHash(mdlFolder), dataFile); // Loop through each entry in the dictionary foreach (var testFile in testFilesDictionary) { // if the file in the dictionary entry is contained in the list of files from the folder // add that race to the race list if (files.Contains(testFile.Key)) { raceList.Add(IDRaceDictionary[testFile.Value]); } } return(raceList); }
/// <summary> /// Gets any missing gear that must be added manualy as it does not exist in the items exd /// </summary> /// <returns>The list of missing gear</returns> private List <XivGear> GetMissingGear() { var xivGearList = new List <XivGear>(); var xivGear = new XivGear { Name = "SmallClothes Body", PrimaryCategory = XivStrings.Gear, SecondaryCategory = _slotNameDictionary[4], ModelInfo = new XivGearModelInfo { PrimaryID = 0, ImcSubsetID = 1, SecondaryID = 0 } }; xivGearList.Add(xivGear); xivGear = new XivGear { Name = "SmallClothes Hands", PrimaryCategory = XivStrings.Gear, SecondaryCategory = _slotNameDictionary[5], ModelInfo = new XivGearModelInfo { PrimaryID = 0, ImcSubsetID = 1, SecondaryID = 0 } }; xivGearList.Add(xivGear); xivGear = new XivGear { Name = "SmallClothes Legs", PrimaryCategory = XivStrings.Gear, SecondaryCategory = _slotNameDictionary[7], ModelInfo = new XivGearModelInfo { PrimaryID = 0, ImcSubsetID = 1, SecondaryID = 0 } }; xivGearList.Add(xivGear); xivGear = new XivGear { Name = "SmallClothes Feet", PrimaryCategory = XivStrings.Gear, SecondaryCategory = _slotNameDictionary[8], ModelInfo = new XivGearModelInfo { PrimaryID = 0, ImcSubsetID = 1, SecondaryID = 0 } }; xivGearList.Add(xivGear); xivGear = new XivGear { Name = "SmallClothes Body (NPC)", PrimaryCategory = XivStrings.Gear, SecondaryCategory = _slotNameDictionary[4], ModelInfo = new XivGearModelInfo { PrimaryID = 9903, ImcSubsetID = 1, SecondaryID = 0 } }; xivGearList.Add(xivGear); xivGear = new XivGear { Name = "SmallClothes Hands (NPC)", PrimaryCategory = XivStrings.Gear, SecondaryCategory = _slotNameDictionary[5], ModelInfo = new XivGearModelInfo { PrimaryID = 9903, ImcSubsetID = 1, SecondaryID = 0 } }; xivGearList.Add(xivGear); xivGear = new XivGear { Name = "SmallClothes Legs (NPC)", PrimaryCategory = XivStrings.Gear, SecondaryCategory = _slotNameDictionary[7], ModelInfo = new XivGearModelInfo { PrimaryID = 9903, ImcSubsetID = 1, SecondaryID = 0 } }; xivGearList.Add(xivGear); xivGear = new XivGear { Name = "SmallClothes Feet (NPC)", PrimaryCategory = XivStrings.Gear, SecondaryCategory = _slotNameDictionary[8], ModelInfo = new XivGearModelInfo { PrimaryID = 9903, ImcSubsetID = 1, SecondaryID = 0 } }; xivGearList.Add(xivGear); xivGear = new XivGear { Name = "SmallClothes Feet 2 (NPC)", PrimaryCategory = XivStrings.Gear, SecondaryCategory = _slotNameDictionary[8], ModelInfo = new XivGearModelInfo { PrimaryID = 9901, ImcSubsetID = 1, SecondaryID = 0 } }; xivGearList.Add(xivGear); return(xivGearList); }
/// <summary> /// Gets the available races that contain texture data for the given gear /// </summary> /// <remarks> /// This checks to see if the mtrl file for each race exists in the mtrl folder /// It creates a list of the races which do have an available mtrl folder /// </remarks> /// <param name="xivGear">A gear item</param> /// <returns>A list of XivRace data</returns> public async Task <List <XivRace> > GetRacesForTextures(XivGear xivGear, XivDataFile dataFile) { // Get the material version for the item from the imc file var imc = new Imc(_gameDirectory, dataFile); var gearVersion = (await imc.GetImcInfo(xivGear, xivGear.ModelInfo)).Version.ToString().PadLeft(4, '0'); var modelID = xivGear.ModelInfo.ModelID.ToString().PadLeft(4, '0'); var raceList = new List <XivRace>(); var itemType = ItemType.GetItemType(xivGear); string mtrlFolder; if (itemType == XivItemType.weapon) { return(new List <XivRace> { XivRace.All_Races }); } switch (itemType) { case XivItemType.equipment: mtrlFolder = $"chara/{itemType}/e{modelID}/material/v{gearVersion}"; break; case XivItemType.accessory: mtrlFolder = $"chara/{itemType}/a{modelID}/material/v{gearVersion}"; break; default: mtrlFolder = ""; break; } var testFilesDictionary = new Dictionary <int, string>(); // loop through each race ID to create a dictionary containing [Hashed file name, race ID] foreach (var ID in IDRaceDictionary.Keys) { string mtrlFile; switch (itemType) { case XivItemType.equipment: mtrlFile = $"mt_c{ID}e{modelID}_{SlotAbbreviationDictionary[xivGear.ItemCategory]}_a.mtrl"; break; case XivItemType.accessory: mtrlFile = $"mt_c{ID}a{modelID}_{SlotAbbreviationDictionary[xivGear.ItemCategory]}_a.mtrl"; break; default: mtrlFile = ""; break; } testFilesDictionary.Add(HashGenerator.GetHash(mtrlFile), ID); } // get the list of hashed file names from the mtrl folder var files = await _index.GetAllHashedFilesInFolder(HashGenerator.GetHash(mtrlFolder), dataFile); // Loop through each entry in the dictionary foreach (var testFile in testFilesDictionary) { // if the file in the dictionary entry is contained in the list of files from the folder // add that race to the race list if (files.Contains(testFile.Key)) { raceList.Add(IDRaceDictionary[testFile.Value]); } } return(raceList); }
/// <summary> /// A getter for available gear in the Item exd files /// </summary> /// <returns>A list containing XivGear data</returns> public List <XivGear> GetGearList() { // These are the offsets to relevant data // These will need to be changed if data gets added or removed with a patch const int modelDataCheckOffset = 31; const int dataLength = 152; const int nameDataOffset = 14; const int modelDataOffset = 24; const int iconDataOffset = 128; const int slotDataOffset = 146; var xivGearList = new List <XivGear>(); var ex = new Ex(_gameDirectory, _xivLanguage); var itemDictionary = ex.ReadExData(XivEx.item); // Loops through all the items in the item exd files // Item files start at 0 and increment by 500 for each new file // Item_0, Item_500, Item_1000, etc. foreach (var item in itemDictionary.Values) { // This checks whether there is any model data present in the current item if (item[modelDataCheckOffset] <= 0) { continue; } // Gear can have 2 separate models (MNK weapons for example) var primaryMi = new XivModelInfo(); var secondaryMi = new XivModelInfo(); var xivGear = new XivGear { Category = XivStrings.Gear, PrimaryModelInfo = primaryMi, SecondaryModelInfo = secondaryMi }; /* Used to determine if the given model is a weapon * This is important because the data is formated differently * The model data is a 16 byte section separated into two 8 byte parts (primary model, secondary model) * Format is 8 bytes in length with 2 bytes per data point [short, short, short, short] * Gear: primary model [blank, blank, variant, ID] nothing in secondary model * Weapon: primary model [blank, variant, body, ID] secondary model [blank, variant, body, ID] */ var isWeapon = false; // Big Endian Byte Order using (var br = new BinaryReaderBE(new MemoryStream(item))) { br.BaseStream.Seek(nameDataOffset, SeekOrigin.Begin); var nameOffset = br.ReadInt16(); // Model Data br.BaseStream.Seek(modelDataOffset, SeekOrigin.Begin); // Primary Blank primaryMi.Unused = br.ReadInt16(); // Primary Variant for weapon, blank otherwise var weaponVariant = br.ReadInt16(); if (weaponVariant != 0) { primaryMi.Variant = weaponVariant; isWeapon = true; } // Primary Body if weapon, Variant otherwise if (isWeapon) { primaryMi.Body = br.ReadInt16(); } else { primaryMi.Variant = br.ReadInt16(); } // Primary Model ID primaryMi.ModelID = br.ReadInt16(); // Secondary Blank secondaryMi.Unused = br.ReadInt16(); // Secondary Variant for weapon, blank otherwise weaponVariant = br.ReadInt16(); if (weaponVariant != 0) { secondaryMi.Variant = weaponVariant; isWeapon = true; } // Secondary Body if weapon, Variant otherwise if (isWeapon) { secondaryMi.Body = br.ReadInt16(); } else { secondaryMi.Variant = br.ReadInt16(); } // Secondary Model ID secondaryMi.ModelID = br.ReadInt16(); // Icon br.BaseStream.Seek(iconDataOffset, SeekOrigin.Begin); xivGear.IconNumber = br.ReadUInt16(); // Gear Slot/Category br.BaseStream.Seek(slotDataOffset, SeekOrigin.Begin); int slotNum = br.ReadByte(); // Waist items do not have texture or model data if (slotNum == 6) { continue; } xivGear.ItemCategory = _slotNameDictionary.ContainsKey(slotNum) ? _slotNameDictionary[slotNum] : "Unknown"; // Gear Name var gearNameOffset = dataLength + nameOffset; var gearNameLength = item.Length - gearNameOffset; br.BaseStream.Seek(gearNameOffset, SeekOrigin.Begin); var nameString = Encoding.UTF8.GetString(br.ReadBytes(gearNameLength)).Replace("\0", ""); xivGear.Name = new string(nameString.Where(c => !char.IsControl(c)).ToArray()); xivGearList.Add(xivGear); } } xivGearList.Sort(); return(xivGearList); }