/// <summary> /// Gets the ATex data /// </summary> /// <param name="offset">The offset to the ATex file</param> /// <returns>An XivTex with all the texture data</returns> public XivTex GetATexData(int offset) { var dat = new Dat(_gameDirectory); var atexData = dat.GetType2Data(offset, _dataFile); var xivTex = new XivTex(); using (var br = new BinaryReader(new MemoryStream(atexData))) { var signature = br.ReadInt32(); xivTex.TextureFormat = Dat.TextureTypeDictionary[br.ReadInt32()]; xivTex.Width = br.ReadInt16(); xivTex.Height = br.ReadInt16(); br.ReadBytes(2); xivTex.MipMapCount = br.ReadInt16(); br.ReadBytes(64); xivTex.TexData = br.ReadBytes(atexData.Length - 80); } return(xivTex); }
/// <summary> /// Applies a custom .rgsp file to the main Human.CMP file. /// </summary> /// <param name="filePath"></param> /// <param name="index"></param> /// <param name="modlist"></param> /// <returns></returns> internal static async Task ApplyRgspFile(string filePath, IndexFile index = null, ModList modlist = null) { var _dat = new Dat(XivCache.GameInfo.GameDirectory); var rgspData = await _dat.GetType2Data(filePath, false, index, modlist); await ApplyRgspFile(rgspData, index, modlist); }
private static async Task <Dictionary <XivRace, Dictionary <ushort, ExtraSkeletonEntry> > > GetEstFile(EstType type, bool forceDefault = false) { var _dat = new Dat(_gameDirectory); var data = await _dat.GetType2Data(EstFiles[type], forceDefault); var count = BitConverter.ToUInt32(data, 0); Dictionary <XivRace, Dictionary <ushort, ExtraSkeletonEntry> > entries = new Dictionary <XivRace, Dictionary <ushort, ExtraSkeletonEntry> >(); for (int i = 0; i < count; i++) { var entry = ExtraSkeletonEntry.Read(data, count, (uint)i); if (!entries.ContainsKey(entry.Race)) { entries.Add(entry.Race, new Dictionary <ushort, ExtraSkeletonEntry>()); } if (!entries[entry.Race].ContainsKey(entry.SetId)) { // For whatever reason there is exactly one dupe in the game files, where a Lalafell M face has two identical entries. // Doesn't seem to matter to SE, so shouldn't matter to us. entries[entry.Race].Add(entry.SetId, entry); } } return(entries); }
/// <summary> /// Resolves the original skeleton path in the FFXIV file system and raw extracts it. /// </summary> /// <param name="fullMdlPath">Full path to the MDL.</param> /// <param name="internalSkelName">Internal skeleton name (for hair). This can be resolved if missing, though it is slightly expensive to do so.</param> private static async Task <string> ExtractSkelb(string skelBPath) { var index = new Index(XivCache.GameInfo.GameDirectory); var dat = new Dat(XivCache.GameInfo.GameDirectory); var dataFile = IOUtil.GetDataFileFromPath(skelBPath); var offset = await index.GetDataOffset(skelBPath); if (offset == 0) { throw new Exception($"Could not find offset for {skelBPath}"); } var sklbData = await dat.GetType2Data(offset, dataFile); using (var br = new BinaryReader(new MemoryStream(sklbData))) { br.BaseStream.Seek(0, SeekOrigin.Begin); var magic = br.ReadInt32(); var format = br.ReadInt32(); br.ReadBytes(2); if (magic != 0x736B6C62) { throw new FormatException(); } var dataOffset = 0; switch (format) { case 0x31323030: dataOffset = br.ReadInt16(); break; case 0x31333030: case 0x31333031: br.ReadBytes(2); dataOffset = br.ReadInt16(); break; default: throw new Exception($"Unkown Data Format ({format})"); } br.BaseStream.Seek(dataOffset, SeekOrigin.Begin); var havokData = br.ReadBytes(sklbData.Length - dataOffset); var skelName = Path.GetFileNameWithoutExtension(skelBPath).Replace("skl_", ""); var outputFile = skelName + ".sklb"; var cwd = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location); var parsedFile = Path.Combine(cwd, SkeletonsFolder, outputFile); File.WriteAllBytes(parsedFile, havokData); return(parsedFile); } }
/// <summary> /// Retrieves the item metadata from a cached index setup (or raw) /// </summary> /// <param name="root"></param> /// <param name="index"></param> /// <returns></returns> public static async Task <ItemMetadata> GetFromCachedIndex(XivDependencyRoot root, IndexFile index) { var _dat = new Dat(XivCache.GameInfo.GameDirectory); var df = IOUtil.GetDataFileFromPath(root.Info.GetRootFile()); long offset = 0; if (index != null) { offset = index.Get8xDataOffset(root.Info.GetRootFile()); } ItemMetadata mData = null; if (offset == 0) { mData = await ItemMetadata.GetMetadata(root); } else { var data = await _dat.GetType2Data(offset, df); mData = await ItemMetadata.Deserialize(data); } return(mData); }
/// <summary> /// Gets the metadata file for a given root. /// </summary> /// <param name="root"></param> /// <returns></returns> public static async Task <ItemMetadata> GetMetadata(XivDependencyRoot root, bool forceDefault = false) { if (root == null) { return(null); } Mod mod = null; var filePath = root.Info.GetRootFile(); if (!forceDefault) { var _modding = new Modding(XivCache.GameInfo.GameDirectory); mod = await _modding.TryGetModEntry(filePath); } if (mod != null && mod.enabled) { var _dat = new Dat(XivCache.GameInfo.GameDirectory); // We have modded metadata stored in the .meta file in the DAT we can use. var data = await _dat.GetType2Data(filePath, false); // Run it through the binary deserializer and we're good. //return await Deserialize(data); return(await CreateFromRaw(root, forceDefault)); } else { // This is the fun part where we get to pull the Metadata from all the disparate files around the FFXIV File System. return(await CreateFromRaw(root, forceDefault)); } }
/// <summary> /// Reads and parses the ExHeader file /// </summary> /// <param name="exFile">The Ex file to use.</param> private void ReadExHeader(XivEx exFile) { OffsetTypeDict = new Dictionary <int, int>(); PageList = new List <int>(); LanguageList = new List <int>(); var exdFolderHash = HashGenerator.GetHash("exd"); var exdFileHash = HashGenerator.GetHash(exFile + ExhExtension); var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); var offset = index.GetDataOffset(exdFolderHash, exdFileHash, XivDataFile._0A_Exd); if (offset == 0) { throw new Exception($"Could not find offest for exd/{exFile}{ExhExtension}"); } var exhData = dat.GetType2Data(offset, XivDataFile._0A_Exd); // Big Endian Byte Order using (var br = new BinaryReaderBE(new MemoryStream(exhData))) { var signature = br.ReadInt32(); var version = br.ReadInt16(); var dataSetChunk = br.ReadInt16(); var dataSetCount = br.ReadInt16(); var pageTableCount = br.ReadInt16(); var langTableCount = br.ReadInt16(); var unknown = br.ReadInt16(); var unknown1 = br.ReadInt32(); var entryCount = br.ReadInt32(); br.ReadBytes(8); for (var i = 0; i < dataSetCount; i++) { var dataType = br.ReadInt16(); var dataOffset = br.ReadInt16(); if (!OffsetTypeDict.ContainsKey(dataOffset)) { OffsetTypeDict.Add(dataOffset, dataType); } } for (var i = 0; i < pageTableCount; i++) { var pageNumber = br.ReadInt32(); var pageSize = br.ReadInt32(); PageList.Add(pageNumber); } for (var i = 0; i < langTableCount; i++) { var langCode = br.ReadInt16(); LanguageList.Add(langCode); } } }
/// <summary> /// Retrieves an arbitrary selection of IMC entries based on their path::binaryoffset. /// </summary> /// <param name="pathsWithOffsets"></param> /// <returns></returns> public async Task <List <XivImc> > GetEntries(List <string> pathsWithOffsets) { var entries = new List <XivImc>(); var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); var lastPath = ""; int imcOffset = 0; byte[] imcByteData = new byte[0]; foreach (var combinedPath in pathsWithOffsets) { var binaryMatch = _binaryOffsetRegex.Match(combinedPath); var pathMatch = _pathOnlyRegex.Match(combinedPath); // Invalid format. if (!pathMatch.Success || !binaryMatch.Success) { continue; } long offset = Int64.Parse(binaryMatch.Groups[1].Value) / 8; string path = pathMatch.Groups[1].Value; // Only reload this data if we need to. if (path != lastPath) { imcOffset = await index.GetDataOffset(path); imcByteData = await dat.GetType2Data(imcOffset, IOUtil.GetDataFileFromPath(path)); } lastPath = path; // Offset would run us past the end of the file. const int entrySize = 6; if (offset > imcByteData.Length - entrySize) { continue; } using (var br = new BinaryReader(new MemoryStream(imcByteData))) { var subsetCount = br.ReadInt16(); var identifier = (ImcType)br.ReadInt16(); br.BaseStream.Seek(offset, SeekOrigin.Begin); entries.Add(new XivImc { Variant = br.ReadByte(), Unknown = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }); } } return(entries); }
/// <summary> /// Gets the .atex paths that are within the .avfx file /// </summary> /// <param name="offset">The offset to the avfx file</param> /// <returns>A list of atex paths</returns> public async Task <List <string> > GetATexPaths(int offset) { var atexList = new List <string>(); var dat = new Dat(_gameDirectory); var avfxData = await dat.GetType2Data(offset, _dataFile); await Task.Run(() => { using (var br = new BinaryReader(new MemoryStream(avfxData))) { var data = br.ReadInt32(); // Advance to the path data header while (data != 5531000) { try { data = br.ReadInt32(); } catch (EndOfStreamException e) { throw new System.Exception("VFX textures were detected but no texture paths could be found.\n" + "VFX textures will not be accessible."); } } // While the data is a path data header while (data == 5531000) { var pathLength = br.ReadInt32(); atexList.Add(Encoding.UTF8.GetString(br.ReadBytes(pathLength)).Replace("\0", "")); try { while (br.PeekChar() != 120) { if (br.PeekChar() == -1) { break; } br.ReadByte(); } data = br.ReadInt32(); } catch { break; } } } }); return(atexList); }
public static void ExportMsb(int slot, string gamePath, string outPath) { var index = new Index(new DirectoryInfo(gamePath)); var dat = new Dat(new DirectoryInfo(gamePath)); var msbData = Task.Run(() => dat.GetType2Data($"sound/score/bgm_score_{slot:D3}.msb", true)).Result; ExportMsb(new MemoryStream(msbData), outPath); }
public static async Task <StainingTemplateFile> GetStainingTemplateFile(bool forceOriginal = false, IndexFile index = null, ModList modlist = null) { var _dat = new Dat(XivCache.GameInfo.GameDirectory); var data = await _dat.GetType2Data(GearStainingTemplatePath, forceOriginal, index, modlist); var ret = new StainingTemplateFile(data); return(ret); }
private static async Task <CharaMakeParameterSet> GetCharaMakeParameterSet(bool forceOriginal = false, IndexFile index = null, ModList modlist = null) { var _dat = new Dat(XivCache.GameInfo.GameDirectory); var data = await _dat.GetType2Data(HumanCmpPath, forceOriginal, index, modlist); var cmp = new CharaMakeParameterSet(data); return(cmp); }
/// <summary> /// Gets the relevant IMC information for a given item /// </summary> /// <param name="item">The item to get the version for</param> /// <param name="modelInfo">The model info of the item</param> /// <returns>The XivImc Data</returns> public XivImc GetImcInfo(IItemModel item, XivModelInfo modelInfo) { var xivImc = new XivImc(); // These are the offsets to relevant data // These will need to be changed if data gets added or removed with a patch const int headerLength = 4; const int variantLength = 6; const int variantSetLength = 30; var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); var itemType = ItemType.GetItemType(item); var imcPath = GetImcPath(modelInfo, itemType); var imcOffset = index.GetDataOffset(HashGenerator.GetHash(imcPath.Folder), HashGenerator.GetHash(imcPath.File), _dataFile); if (imcOffset == 0) { throw new Exception($"Could not find offest for {imcPath.Folder}/{imcPath.File}"); } var imcData = dat.GetType2Data(imcOffset, _dataFile); using (var br = new BinaryReader(new MemoryStream(imcData))) { int variantOffset; if (itemType == XivItemType.weapon || itemType == XivItemType.monster) { // weapons and monsters do not have variant sets variantOffset = (modelInfo.Variant * variantLength) + headerLength; } else { // Variant Sets contain 5 variants for each slot // These can be Head, Body, Hands, Legs, Feet or Ears, Neck, Wrists, LRing, RRing // This skips to the correct variant set, then to the correct slot within that set for the item variantOffset = (modelInfo.Variant * variantSetLength) + (_slotOffsetDictionary[item.ItemCategory] * variantLength) + headerLength; } br.BaseStream.Seek(variantOffset, SeekOrigin.Begin); xivImc.Version = br.ReadByte(); var unknown = br.ReadByte(); xivImc.Mask = br.ReadUInt16(); xivImc.Vfx = br.ReadUInt16(); } return(xivImc); }
/// <summary> /// Gets the .atex paths that are within the .avfx file /// </summary> /// <param name="offset">The offset to the avfx file</param> /// <returns>A list of atex paths</returns> public async Task <List <string> > GetATexPaths(int offset) { var atexList = new List <string>(); var dat = new Dat(_gameDirectory); var avfxData = await dat.GetType2Data(offset, _dataFile); await Task.Run(() => { using (var br = new BinaryReader(new MemoryStream(avfxData))) { var data = br.ReadInt32(); // Advance to the path data header while (data != 5531000) { data = br.ReadInt32(); } // While the data is a path data header while (data == 5531000) { var pathLength = br.ReadInt32(); atexList.Add(Encoding.UTF8.GetString(br.ReadBytes(pathLength)).Replace("\0", "")); try { while (br.PeekChar() != 120) { if (br.PeekChar() == -1) { break; } br.ReadByte(); } data = br.ReadInt32(); } catch { break; } } } }); return(atexList); }
/// <summary> /// Gets the texture paths from the uld file /// </summary> /// <returns>List of texture paths from the uld file</returns> public List <string> GetTexFromUld() { var hashedFolder = HashGenerator.GetHash("ui/uld"); var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); var uldStringList = new HashSet <string>(); var uldOffsetList = index.GetAllFileOffsetsInFolder(hashedFolder, XivDataFile._06_Ui); foreach (var offset in uldOffsetList) { var uldData = dat.GetType2Data(offset, XivDataFile._06_Ui); using (var br = new BinaryReader(new MemoryStream(uldData))) { var signature = br.ReadInt32(); if (signature != 1751411829) { continue; } br.ReadBytes(56); int pathCount = br.ReadByte(); br.ReadBytes(7); for (var i = 0; i < pathCount; i++) { br.ReadBytes(4); var path = Encoding.UTF8.GetString(br.ReadBytes(48)).Replace("\0", ""); if (path.Length <= 2 || !path.Contains("uld")) { continue; } var uldPath = path.Substring(0, path.LastIndexOf(".", StringComparison.Ordinal) + 4); uldStringList.Add(uldPath); } } } return(uldStringList.ToList()); }
private static async Task <ItemMetadata> GetCachedMetadata(IndexFile index, ModList modlist, XivDependencyRoot root, XivDataFile df, Dat _dat) { var originalMetadataOffset = index.Get8xDataOffset(root.Info.GetRootFile()); ItemMetadata originalMetadata = null; if (originalMetadataOffset == 0) { originalMetadata = await ItemMetadata.GetMetadata(root); } else { var data = await _dat.GetType2Data(originalMetadataOffset, df); originalMetadata = await ItemMetadata.Deserialize(data); } return(originalMetadata); }
/// <summary> /// Gets additional assets when the original asset file contains asset file paths within it /// </summary> /// <param name="assets">The current asset object</param> private async Task GetAdditionalAssets(HousingAssets assets) { var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); foreach (var additionalAsset in assets.AdditionalAssetList.ToList()) { var assetFolder = Path.GetDirectoryName(additionalAsset).Replace("\\", "/"); var assetFile = Path.GetFileName(additionalAsset); var assetOffset = await index.GetDataOffset(HashGenerator.GetHash(assetFolder), HashGenerator.GetHash(assetFile), XivDataFile._01_Bgcommon); var assetData = await dat.GetType2Data(assetOffset, XivDataFile._01_Bgcommon); await Task.Run(() => { using (var br = new BinaryReader(new MemoryStream(assetData))) { br.BaseStream.Seek(20, SeekOrigin.Begin); var skip = br.ReadInt32() + 20; br.BaseStream.Seek(skip + 4, SeekOrigin.Begin); var stringsOffset = br.ReadInt32(); br.BaseStream.Seek(skip + stringsOffset, SeekOrigin.Begin); var pathCounts = 0; while (true) { // Because we don't know the length of the string, we read the data until we reach a 0 value // That 0 value is the space between strings byte a; var pathName = new List <byte>(); while ((a = br.ReadByte()) != 0) { if (a == 0xFF) { break; } pathName.Add(a); } if (a == 0xFF) { break; } // Read the string from the byte array and remove null terminators var path = Encoding.ASCII.GetString(pathName.ToArray()).Replace("\0", ""); if (path.Equals(string.Empty)) { continue; } // Add the attribute to the list if (pathCounts == 0) { assets.Shared = path; } else if (pathCounts == 1) { assets.BaseFileName = path; } else { if (path.Contains(".mdl")) { assets.MdlList.Add(path); } else if (path.Contains(".sgb")) { assets.AdditionalAssetList.Add(path); } else if (!path.Contains(".")) { assets.BaseFolder = path; } else { assets.OthersList.Add(path); } } pathCounts++; } } }); } }
/// <summary> /// Gets the assets for furniture /// </summary> /// <param name="modelID">The model id to get the assets for</param> /// <returns>A HousingAssets object containing the asset info</returns> private async Task <HousingAssets> GetFurnitureAssets(int modelID, string category) { var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); var id = modelID.ToString().PadLeft(4, '0'); var assetFolder = ""; var assetFile = ""; if (category.Equals(XivStrings.Furniture_Indoor)) { assetFolder = $"bgcommon/hou/indoor/general/{id}/asset"; assetFile = $"fun_b0_m{id}.sgb"; } else if (category.Equals(XivStrings.Furniture_Outdoor)) { assetFolder = $"bgcommon/hou/outdoor/general/{id}/asset"; assetFile = $"gar_b0_m{id}.sgb"; } var assetOffset = await index.GetDataOffset(HashGenerator.GetHash(assetFolder), HashGenerator.GetHash(assetFile), XivDataFile._01_Bgcommon); var assetData = await dat.GetType2Data(assetOffset, XivDataFile._01_Bgcommon); var housingAssets = new HousingAssets(); await Task.Run(() => { using (var br = new BinaryReader(new MemoryStream(assetData))) { br.BaseStream.Seek(20, SeekOrigin.Begin); var skip = br.ReadInt32() + 20; br.BaseStream.Seek(skip + 4, SeekOrigin.Begin); var stringsOffset = br.ReadInt32(); br.BaseStream.Seek(skip + stringsOffset, SeekOrigin.Begin); var pathCounts = 0; while (true) { // Because we don't know the length of the string, we read the data until we reach a 0 value // That 0 value is the space between strings byte a; var pathName = new List <byte>(); while ((a = br.ReadByte()) != 0) { if (a == 0xFF) { break; } pathName.Add(a); } if (a == 0xFF) { break; } // Read the string from the byte array and remove null terminators var path = Encoding.ASCII.GetString(pathName.ToArray()).Replace("\0", ""); if (path.Equals(string.Empty)) { continue; } // Add the attribute to the list if (pathCounts == 0) { housingAssets.Shared = path; } else if (pathCounts == 1) { housingAssets.BaseFileName = path; } else { if (path.Contains(".mdl")) { housingAssets.MdlList.Add(path); } else if (path.Contains(".sgb")) { housingAssets.AdditionalAssetList.Add(path); } else if (!path.Contains(".")) { housingAssets.BaseFolder = path; } else { housingAssets.OthersList.Add(path); } } pathCounts++; } } }); if (housingAssets.AdditionalAssetList.Count > 0) { await GetAdditionalAssets(housingAssets); } return(housingAssets); }
/// <summary> /// Gets the full IMC information for a given item /// </summary> /// <param name="item"></param> /// <param name="useSecondary">Determines if the SecondaryModelInfo should be used instead.(XivGear only)</param> /// <returns>The ImcData data</returns> public async Task <FullImcInfo> GetFullImcInfo(string path) { var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); var imcOffset = await index.GetDataOffset(path); if (imcOffset == 0) { throw new InvalidDataException($"Could not find offset for {path}"); } var imcByteData = await dat.GetType2Data(imcOffset, IOUtil.GetDataFileFromPath(path)); return(await Task.Run(() => { using (var br = new BinaryReader(new MemoryStream(imcByteData))) { var subsetCount = br.ReadInt16(); var identifier = br.ReadInt16(); var imcData = new FullImcInfo() { TypeIdentifier = (ImcType)identifier, DefaultSubset = new List <XivImc>(), SubsetList = new List <List <XivImc> >(subsetCount) }; //weapons and monsters do not have variant sets if (imcData.TypeIdentifier == ImcType.NonSet) { // This type uses the first short for both Variant and VFX. byte variant = br.ReadByte(); byte unknown = br.ReadByte(); ushort mask = br.ReadUInt16(); ushort vfx = br.ReadUInt16(); imcData.DefaultSubset.Add(new XivImc { Variant = variant, Unknown = unknown, Mask = mask, Vfx = variant }); for (var i = 0; i < subsetCount; i++) { variant = br.ReadByte(); unknown = br.ReadByte(); mask = br.ReadUInt16(); vfx = br.ReadUInt16(); var newEntry = new XivImc { Variant = variant, Unknown = unknown, Mask = mask, Vfx = vfx }; var subset = new List <XivImc>() { newEntry }; imcData.SubsetList.Add(subset); } } else if (imcData.TypeIdentifier == ImcType.Set) { // Identifier used by Equipment. imcData.DefaultSubset = new List <XivImc>() { new XivImc { Variant = br.ReadByte(), Unknown = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, new XivImc { Variant = br.ReadByte(), Unknown = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, new XivImc { Variant = br.ReadByte(), Unknown = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, new XivImc { Variant = br.ReadByte(), Unknown = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, new XivImc { Variant = br.ReadByte(), Unknown = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, }; for (var i = 0; i < subsetCount; i++) { // gets the data for each slot in the current variant set var imcGear = new List <XivImc>() { new XivImc { Variant = br.ReadByte(), Unknown = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, new XivImc { Variant = br.ReadByte(), Unknown = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, new XivImc { Variant = br.ReadByte(), Unknown = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, new XivImc { Variant = br.ReadByte(), Unknown = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, new XivImc { Variant = br.ReadByte(), Unknown = br.ReadByte(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, }; imcData.SubsetList.Add(imcGear); } } else { throw new NotSupportedException("Unknown IMC Type Identifier. (Please report this item in the TexTools Discord #bug_reports channel.)"); } return imcData; } })); }
/// <summary> /// Gets the MTRL data for the given item /// </summary> /// <remarks> /// It requires a race (The default is usually <see cref="XivRace.Hyur_Midlander_Male"/>) /// It also requires an mtrl part <see cref="GearInfo.GetPartList(IItemModel, XivRace)"/> (default is 'a') /// </remarks> /// <param name="itemModel">Item that contains model data</param> /// <param name="race">The race for the requested data</param> /// <param name="part">The Mtrl part </param> /// <returns></returns> public XivMtrl GetMtrlData(IItemModel itemModel, XivRace race, char part) { var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); var itemType = ItemType.GetItemType(itemModel); // Get mtrl path var mtrlPath = GetMtrlPath(itemModel, race, part, itemType); // Get mtrl offset var mtrlOffset = index.GetDataOffset(HashGenerator.GetHash(mtrlPath.Folder), HashGenerator.GetHash(mtrlPath.File), _dataFile); if (mtrlOffset == 0) { throw new Exception($"Could not find offest for {mtrlPath.Folder}/{mtrlPath.File}"); } // Get uncompressed mtrl data var mtrlData = dat.GetType2Data(mtrlOffset, _dataFile); XivMtrl xivMtrl; using (var br = new BinaryReader(new MemoryStream(mtrlData))) { xivMtrl = new XivMtrl { Signature = br.ReadInt32(), FileSize = br.ReadInt16(), ColorSetDataSize = br.ReadInt16(), MaterialDataSize = br.ReadInt16(), TexturePathsDataSize = br.ReadByte(), Unknown = br.ReadByte(), TextureCount = br.ReadByte(), MapCount = br.ReadByte(), ColorSetCount = br.ReadByte(), Unknown1 = br.ReadByte(), TextureTypePathList = new List <TexTypePath>(), MTRLPath = $"{mtrlPath.Folder}/{mtrlPath.File}" }; var pathSizeList = new List <int>(); // get the texture path offsets xivMtrl.TexturePathOffsetList = new List <int>(xivMtrl.TextureCount); for (var i = 0; i < xivMtrl.TextureCount; i++) { xivMtrl.TexturePathOffsetList.Add(br.ReadInt16()); br.ReadBytes(2); // add the size of the paths if (i > 0) { pathSizeList.Add(xivMtrl.TexturePathOffsetList[i] - xivMtrl.TexturePathOffsetList[i - 1]); } } // get the map path offsets xivMtrl.MapPathOffsetList = new List <int>(xivMtrl.MapCount); for (var i = 0; i < xivMtrl.MapCount; i++) { xivMtrl.MapPathOffsetList.Add(br.ReadInt16()); br.ReadBytes(2); // add the size of the paths if (i > 0) { pathSizeList.Add(xivMtrl.MapPathOffsetList[i] - xivMtrl.MapPathOffsetList[i - 1]); } else { pathSizeList.Add(xivMtrl.MapPathOffsetList[i] - xivMtrl.TexturePathOffsetList[xivMtrl.TextureCount - 1]); } } // get the color set offsets xivMtrl.ColorSetPathOffsetList = new List <int>(xivMtrl.ColorSetCount); for (var i = 0; i < xivMtrl.ColorSetCount; i++) { xivMtrl.ColorSetPathOffsetList.Add(br.ReadInt16()); br.ReadBytes(2); // add the size of the paths if (i > 0) { pathSizeList.Add(xivMtrl.ColorSetPathOffsetList[i] - xivMtrl.ColorSetPathOffsetList[i - 1]); } else { pathSizeList.Add(xivMtrl.ColorSetPathOffsetList[i] - xivMtrl.MapPathOffsetList[xivMtrl.MapCount - 1]); } } pathSizeList.Add(xivMtrl.TexturePathsDataSize - xivMtrl.ColorSetPathOffsetList[xivMtrl.ColorSetCount - 1]); var count = 0; // get the texture path strings xivMtrl.TexturePathList = new List <string>(xivMtrl.TextureCount); for (var i = 0; i < xivMtrl.TextureCount; i++) { xivMtrl.TexturePathList.Add(Encoding.UTF8.GetString(br.ReadBytes(pathSizeList[count])).Replace("\0", "")); count++; } // add the textures to the TextureTypePathList xivMtrl.TextureTypePathList.AddRange(GetTexNames(xivMtrl.TexturePathList, _dataFile)); // get the map path strings xivMtrl.MapPathList = new List <string>(xivMtrl.MapCount); for (var i = 0; i < xivMtrl.MapCount; i++) { xivMtrl.MapPathList.Add(Encoding.UTF8.GetString(br.ReadBytes(pathSizeList[count])).Replace("\0", "")); count++; } // get the color set path strings xivMtrl.ColorSetPathList = new List <string>(xivMtrl.ColorSetCount); for (var i = 0; i < xivMtrl.ColorSetCount; i++) { xivMtrl.ColorSetPathList.Add(Encoding.UTF8.GetString(br.ReadBytes(pathSizeList[count])).Replace("\0", "")); count++; } // If the mtrl file contains a color set, add it to the TextureTypePathList if (xivMtrl.ColorSetDataSize > 0) { var ttp = new TexTypePath { Path = mtrlPath.Folder + "/" + mtrlPath.File, Type = XivTexType.ColorSet, DataFile = _dataFile }; xivMtrl.TextureTypePathList.Add(ttp); } var shaderPathSize = xivMtrl.MaterialDataSize - xivMtrl.TexturePathsDataSize; xivMtrl.Shader = Encoding.UTF8.GetString(br.ReadBytes(shaderPathSize)).Replace("\0", ""); xivMtrl.Unknown2 = br.ReadInt32(); xivMtrl.ColorSetData = new List <Half>(); for (var i = 0; i < xivMtrl.ColorSetDataSize / 2; i++) { xivMtrl.ColorSetData.Add(new Half(br.ReadUInt16())); } xivMtrl.AdditionalDataSize = br.ReadInt16(); xivMtrl.DataStruct1Count = br.ReadInt16(); xivMtrl.DataStruct2Count = br.ReadInt16(); xivMtrl.ParameterStructCount = br.ReadInt16(); xivMtrl.ShaderNumber = br.ReadInt16(); xivMtrl.Unknown3 = br.ReadInt16(); xivMtrl.DataStruct1List = new List <DataStruct1>(xivMtrl.DataStruct1Count); for (var i = 0; i < xivMtrl.DataStruct1Count; i++) { xivMtrl.DataStruct1List.Add(new DataStruct1 { ID = br.ReadUInt32(), Unknown1 = br.ReadUInt32() }); } xivMtrl.DataStruct2List = new List <DataStruct2>(xivMtrl.DataStruct2Count); for (var i = 0; i < xivMtrl.DataStruct2Count; i++) { xivMtrl.DataStruct2List.Add(new DataStruct2 { ID = br.ReadUInt32(), Offset = br.ReadInt16(), Size = br.ReadInt16() }); } xivMtrl.ParameterStructList = new List <ParameterStruct>(xivMtrl.ParameterStructCount); for (var i = 0; i < xivMtrl.ParameterStructCount; i++) { xivMtrl.ParameterStructList.Add(new ParameterStruct { ID = br.ReadUInt32(), Unknown1 = br.ReadInt16(), Unknown2 = br.ReadInt16(), TextureIndex = br.ReadUInt32() }); } xivMtrl.AdditionalData = br.ReadBytes(xivMtrl.AdditionalDataSize); } return(xivMtrl); }
/// <summary> /// Resolves the original skeleton path in the FFXIV file system and raw extracts it. /// </summary> /// <param name="fullMdlPath">Full path to the MDL.</param> /// <param name="internalSkelName">Internal skeleton name (for hair). This can be resolved if missing, though it is slightly expensive to do so.</param> private async Task <string> ExtractSkelb(string fullMdlPath, string internalSkelName = null) { var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); var dataFile = IOUtil.GetDataFileFromPath(fullMdlPath); var fileName = Path.GetFileNameWithoutExtension(fullMdlPath); var skelFolder = ""; var skelFile = ""; var slotAbr = ""; if (IsNonhuman(fullMdlPath)) { // Weapons / Monsters / Demihumans are simple enough cases, we just have to use different formatting strings. if (IsWeapon(fullMdlPath)) { skelFolder = string.Format(XivStrings.WeapSkelFolder, fileName.Substring(1, 4), "0001"); skelFile = string.Format(XivStrings.WeapSkelFile, fileName.Substring(1, 4), "0001"); } else if (IsMonster(fullMdlPath)) { skelFolder = string.Format(XivStrings.MonsterSkelFolder, fileName.Substring(1, 4), "0001"); skelFile = string.Format(XivStrings.MonsterSkelFile, fileName.Substring(1, 4), "0001"); } else if (IsDemihuman(fullMdlPath)) { skelFolder = string.Format(XivStrings.DemiSkelFolder, fileName.Substring(1, 4), "0001"); skelFile = string.Format(XivStrings.DemiSkelFile, fileName.Substring(1, 4), "0001"); } } else if (IsHair(fullMdlPath)) { // Hair we have to potentially scrape the MDL file to check for EX bones to scrape those EX bones for their skeleton name. // Pretty ugly, but you do what you gotta do. if (internalSkelName == null) { internalSkelName = await GetInternalSkelName(fullMdlPath); } // First arg is the constant string to format based on. Second arg is Race #. skelFolder = string.Format(XivStrings.EquipSkelFolder, fileName.Substring(1, 4), "hair", internalSkelName, ""); skelFile = string.Format(XivStrings.EquipSkelFile, fileName.Substring(1, 4), internalSkelName, ""); } else { // This is some jank in order to determine what id/slotAbr to use. var slotRegex = new Regex("_([a-z]{3})$"); var match = slotRegex.Match(fileName); var normalSlotAbr = match.Groups[1].Value; var category = Mdl.SlotAbbreviationDictionary.First(x => x.Value == normalSlotAbr).Key; slotAbr = SlotAbbreviationDictionary[category]; var id = fileName.Substring(6, 4); // Most subtypes just use body 0001 always, but not *all* of them. // This weird construct is to check for those exceptions. if (slotAbr.Equals("base")) { id = "0001"; } skelFolder = string.Format(XivStrings.EquipSkelFolder, fileName.Substring(1, 4), slotAbr, slotAbr[0], id); skelFile = string.Format(XivStrings.EquipSkelFile, fileName.Substring(1, 4), slotAbr[0], id); } // Continue only if the skeleton file exists if (!await index.FileExists(HashGenerator.GetHash(skelFile), HashGenerator.GetHash(skelFolder), dataFile)) { // Sometimes for face skeletons id 0001 does not exist but 0002 does if (IsFace(fullMdlPath)) { skelFolder = string.Format(XivStrings.EquipSkelFolder, fileName.Substring(1, 4), slotAbr, slotAbr[0], "0002"); skelFile = string.Format(XivStrings.EquipSkelFile, fileName.Substring(1, 4), slotAbr[0], "0002"); if (!await index.FileExists(HashGenerator.GetHash(skelFile), HashGenerator.GetHash(skelFolder), dataFile)) { return(null); } } else { return(null); } } var offset = await index.GetDataOffset(HashGenerator.GetHash(skelFolder), HashGenerator.GetHash(skelFile), dataFile); if (offset == 0) { throw new Exception($"Could not find offset for {skelFolder}/{skelFile}"); } var sklbData = await dat.GetType2Data(offset, dataFile); using (var br = new BinaryReader(new MemoryStream(sklbData))) { br.BaseStream.Seek(0, SeekOrigin.Begin); var magic = br.ReadInt32(); var format = br.ReadInt32(); br.ReadBytes(2); if (magic != 0x736B6C62) { throw new FormatException(); } var dataOffset = 0; switch (format) { case 0x31323030: dataOffset = br.ReadInt16(); break; case 0x31333030: case 0x31333031: br.ReadBytes(2); dataOffset = br.ReadInt16(); break; default: throw new Exception($"Unkown Data Format ({format})"); } br.BaseStream.Seek(dataOffset, SeekOrigin.Begin); var havokData = br.ReadBytes(sklbData.Length - dataOffset); var cwd = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location); var outputPath = cwd + "/Skeletons/" + internalSkelName + ".sklb"; File.WriteAllBytes(outputPath, havokData); return(outputPath); } }
/// <summary> /// Gets the skeleton sklb file /// </summary> /// <param name="modelName">The name of the model</param> /// <param name="category">The items category</param> private void GetSkeleton(string modelName, string category) { var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); var skelFolder = ""; var skelFile = ""; if (modelName[0].Equals('w')) { skelFolder = string.Format(XivStrings.WeapSkelFolder, modelName.Substring(1, 4), "0001"); skelFile = string.Format(XivStrings.WeapSkelFile, modelName.Substring(1, 4), "0001"); } else if (modelName[0].Equals('m')) { skelFolder = string.Format(XivStrings.MonsterSkelFolder, modelName.Substring(1, 4), "0001"); skelFile = string.Format(XivStrings.MonsterSkelFile, modelName.Substring(1, 4), "0001"); } else if (modelName[0].Equals('d')) { skelFolder = string.Format(XivStrings.DemiSkelFolder, modelName.Substring(1, 4), "0001"); skelFile = string.Format(XivStrings.DemiSkelFile, modelName.Substring(1, 4), "0001"); } else { var abr = SlotAbbreviationDictionary[category]; var id = modelName.Substring(6, 4); if (abr.Equals("base")) { id = "0001"; } skelFolder = string.Format(XivStrings.EquipSkelFolder, modelName.Substring(1, 4), abr, abr[0], id); skelFile = string.Format(XivStrings.EquipSkelFile, modelName.Substring(1, 4), abr[0], id); } // Continue only if the skeleton file exists if (!index.FileExists(HashGenerator.GetHash(skelFile), HashGenerator.GetHash(skelFolder), _dataFile) ) { return; } var offset = index.GetDataOffset(HashGenerator.GetHash(skelFolder), HashGenerator.GetHash(skelFile), _dataFile); if (offset == 0) { throw new Exception($"Could not find offest for {skelFolder}/{skelFile}"); } var sklbData = dat.GetType2Data(offset, _dataFile); using (var br = new BinaryReader(new MemoryStream(sklbData))) { br.BaseStream.Seek(0, SeekOrigin.Begin); var magic = br.ReadInt32(); var format = br.ReadInt32(); br.ReadBytes(2); if (magic != 0x736B6C62) { throw new FormatException(); } var dataOffset = 0; switch (format) { case 0x31323030: dataOffset = br.ReadInt16(); break; case 0x31333030: case 0x31333031: br.ReadBytes(2); dataOffset = br.ReadInt16(); break; default: throw new Exception($"Unkown Data Format ({format})"); } br.BaseStream.Seek(dataOffset, SeekOrigin.Begin); var havokData = br.ReadBytes(sklbData.Length - dataOffset); var mName = modelName.Substring(0, 5); if (category.Equals(XivStrings.Head)) { mName = modelName.Substring(5, 5); } File.WriteAllBytes(Directory.GetCurrentDirectory() + "/Skeletons/" + mName + ".sklb", havokData); } }
/// <summary> /// Performs the most low-level mod enable/disable functions, without saving the modlist, /// ergo this should only be called by functions which will handle saving the modlist after /// they're done performing all modlist operations. /// /// If the Index and modlist are provided, the actions are only applied to those cached entries, rather /// than to the live files. /// </summary> /// <param name="enable"></param> /// <param name="mod"></param> /// <returns></returns> public async Task <bool> ToggleModUnsafe(bool enable, Mod mod, bool includeInternal, bool updateCache, IndexFile cachedIndex = null, ModList cachedModlist = null) { if (mod == null) { return(false); } if (string.IsNullOrEmpty(mod.name)) { return(false); } if (string.IsNullOrEmpty(mod.fullPath)) { return(false); } if (mod.data.originalOffset <= 0 && !enable) { throw new Exception("Cannot disable mod with invalid original offset."); } if (enable && mod.data.modOffset <= 0) { throw new Exception("Cannot enable mod with invalid mod offset."); } if (mod.IsInternal() && !includeInternal) { // Don't allow toggling internal mods unless we were specifically told to. return(false); } var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); // Added file. if (enable) { if (cachedIndex != null) { cachedIndex.SetDataOffset(mod.fullPath, mod.data.modOffset); } else { await index.UpdateDataOffset(mod.data.modOffset, mod.fullPath, false); } mod.enabled = true; if (cachedIndex == null) { // Check if we're re-enabling a metadata mod. var ext = Path.GetExtension(mod.fullPath); if (ext == ".meta") { var df = IOUtil.GetDataFileFromPath(mod.fullPath); // Retreive the uncompressed meta entry we just enabled. var data = await dat.GetType2Data(mod.data.modOffset, df); var meta = await ItemMetadata.Deserialize(data); meta.Validate(mod.fullPath); // And write that metadata to the actual constituent files. await ItemMetadata.ApplyMetadata(meta, cachedIndex, cachedModlist); } else if (ext == ".rgsp") { await CMP.ApplyRgspFile(mod.fullPath, cachedIndex, cachedModlist); } } } else if (!enable) { if (mod.IsCustomFile()) { // Delete file descriptor handles removing metadata as needed on its own. if (cachedIndex != null) { cachedIndex.SetDataOffset(mod.fullPath, 0); } else { await index.DeleteFileDescriptor(mod.fullPath, IOUtil.GetDataFileFromPath(mod.fullPath), false); } } else { if (cachedIndex != null) { cachedIndex.SetDataOffset(mod.fullPath, mod.data.originalOffset); } else { await index.UpdateDataOffset(mod.data.originalOffset, mod.fullPath, false); } } mod.enabled = false; } if (updateCache) { XivCache.QueueDependencyUpdate(mod.fullPath); } return(true); }
/// <summary> /// Gets the texture paths from the uld file /// </summary> /// <returns>List of texture paths from the uld file</returns> public List <string> GetTexFromUld() { var hashedFolder = HashGenerator.GetHash("ui/uld"); var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); var uldStringList = new HashSet <string>(); var uldOffsetList = index.GetAllFileOffsetsInFolder(hashedFolder, XivDataFile._06_Ui); foreach (var offset in uldOffsetList) { byte[] uldData; try { uldData = dat.GetType2Data(offset, XivDataFile._06_Ui); } catch (Exception ex) { Debug.WriteLine($"Error at offset: {offset}"); Debug.WriteLine($"Message: {ex.Message}"); continue; } if (uldData.Length < 10) { continue; } using (var br = new BinaryReader(new MemoryStream(uldData))) { var signature = br.ReadInt32(); if (signature != 1751411829) { continue; } br.ReadBytes(56); int pathCount = br.ReadByte(); br.ReadBytes(7); for (var i = 0; i < pathCount; i++) { var pathNum = br.ReadInt32(); while (pathNum != i + 1) { pathNum = br.ReadInt32(); } var path = Encoding.UTF8.GetString(br.ReadBytes(48)).Replace("\0", ""); path = new string(path.Where(c => !char.IsControl(c)).ToArray()); if (path.Length <= 2 || !path.Contains("uld")) { continue; } var uldPath = path.Substring(0, path.LastIndexOf(".", StringComparison.Ordinal) + 4); uldStringList.Add(uldPath); } } } return(uldStringList.ToList()); }
/// <summary> /// Gets the MTRL data for the given offset and path /// </summary> /// <param name="mtrlOffset">The offset to the mtrl in the dat file</param> /// <param name="mtrlPath">The full internal game path for the mtrl</param> /// <returns>XivMtrl containing all the mtrl data</returns> public XivMtrl GetMtrlData(int mtrlOffset, string mtrlPath, int dxVersion) { var dat = new Dat(_gameDirectory); var index = new Index(_gameDirectory); // Get uncompressed mtrl data var mtrlData = dat.GetType2Data(mtrlOffset, _dataFile); XivMtrl xivMtrl; using (var br = new BinaryReader(new MemoryStream(mtrlData))) { xivMtrl = new XivMtrl { Signature = br.ReadInt32(), FileSize = br.ReadInt16(), ColorSetDataSize = br.ReadUInt16(), MaterialDataSize = br.ReadUInt16(), TexturePathsDataSize = br.ReadUInt16(), TextureCount = br.ReadByte(), MapCount = br.ReadByte(), ColorSetCount = br.ReadByte(), UnknownDataSize = br.ReadByte(), TextureTypePathList = new List <TexTypePath>(), MTRLPath = mtrlPath }; var pathSizeList = new List <int>(); // get the texture path offsets xivMtrl.TexturePathOffsetList = new List <int>(xivMtrl.TextureCount); xivMtrl.TexturePathUnknownList = new List <short>(xivMtrl.TextureCount); for (var i = 0; i < xivMtrl.TextureCount; i++) { xivMtrl.TexturePathOffsetList.Add(br.ReadInt16()); xivMtrl.TexturePathUnknownList.Add(br.ReadInt16()); // add the size of the paths if (i > 0) { pathSizeList.Add(xivMtrl.TexturePathOffsetList[i] - xivMtrl.TexturePathOffsetList[i - 1]); } } // get the map path offsets xivMtrl.MapPathOffsetList = new List <int>(xivMtrl.MapCount); xivMtrl.MapPathUnknownList = new List <short>(xivMtrl.MapCount); for (var i = 0; i < xivMtrl.MapCount; i++) { xivMtrl.MapPathOffsetList.Add(br.ReadInt16()); xivMtrl.MapPathUnknownList.Add(br.ReadInt16()); // add the size of the paths if (i > 0) { pathSizeList.Add(xivMtrl.MapPathOffsetList[i] - xivMtrl.MapPathOffsetList[i - 1]); } else { pathSizeList.Add(xivMtrl.MapPathOffsetList[i] - xivMtrl.TexturePathOffsetList[xivMtrl.TextureCount - 1]); } } // get the color set offsets xivMtrl.ColorSetPathOffsetList = new List <int>(xivMtrl.ColorSetCount); xivMtrl.ColorSetPathUnknownList = new List <short>(xivMtrl.ColorSetCount); for (var i = 0; i < xivMtrl.ColorSetCount; i++) { xivMtrl.ColorSetPathOffsetList.Add(br.ReadInt16()); xivMtrl.ColorSetPathUnknownList.Add(br.ReadInt16()); // add the size of the paths if (i > 0) { pathSizeList.Add(xivMtrl.ColorSetPathOffsetList[i] - xivMtrl.ColorSetPathOffsetList[i - 1]); } else { pathSizeList.Add(xivMtrl.ColorSetPathOffsetList[i] - xivMtrl.MapPathOffsetList[xivMtrl.MapCount - 1]); } } pathSizeList.Add(xivMtrl.TexturePathsDataSize - xivMtrl.ColorSetPathOffsetList[xivMtrl.ColorSetCount - 1]); var count = 0; // get the texture path strings xivMtrl.TexturePathList = new List <string>(xivMtrl.TextureCount); for (var i = 0; i < xivMtrl.TextureCount; i++) { var texturePath = Encoding.UTF8.GetString(br.ReadBytes(pathSizeList[count])).Replace("\0", ""); var dx11FileName = Path.GetFileName(texturePath).Insert(0, "--"); if (index.FileExists(HashGenerator.GetHash(dx11FileName), HashGenerator.GetHash(Path.GetDirectoryName(texturePath).Replace("\\", "/")), _dataFile)) { texturePath = texturePath.Insert(texturePath.LastIndexOf("/") + 1, "--"); } xivMtrl.TexturePathList.Add(texturePath); count++; } // add the textures to the TextureTypePathList xivMtrl.TextureTypePathList.AddRange(GetTexNames(xivMtrl.TexturePathList, _dataFile)); // get the map path strings xivMtrl.MapPathList = new List <string>(xivMtrl.MapCount); for (var i = 0; i < xivMtrl.MapCount; i++) { xivMtrl.MapPathList.Add(Encoding.UTF8.GetString(br.ReadBytes(pathSizeList[count])).Replace("\0", "")); count++; } // get the color set path strings xivMtrl.ColorSetPathList = new List <string>(xivMtrl.ColorSetCount); for (var i = 0; i < xivMtrl.ColorSetCount; i++) { xivMtrl.ColorSetPathList.Add(Encoding.UTF8.GetString(br.ReadBytes(pathSizeList[count])).Replace("\0", "")); count++; } // If the mtrl file contains a color set, add it to the TextureTypePathList if (xivMtrl.ColorSetDataSize > 0) { var ttp = new TexTypePath { Path = mtrlPath, Type = XivTexType.ColorSet, DataFile = _dataFile }; xivMtrl.TextureTypePathList.Add(ttp); } var shaderPathSize = xivMtrl.MaterialDataSize - xivMtrl.TexturePathsDataSize; xivMtrl.Shader = Encoding.UTF8.GetString(br.ReadBytes(shaderPathSize)).Replace("\0", ""); xivMtrl.Unknown2 = br.ReadBytes(xivMtrl.UnknownDataSize); if (xivMtrl.ColorSetDataSize > 0) { // Color Data is always 512 (6 x 14 = 64 x 8bpp = 512) var colorDataSize = 512; xivMtrl.ColorSetData = new List <Half>(); for (var i = 0; i < colorDataSize / 2; i++) { xivMtrl.ColorSetData.Add(new Half(br.ReadUInt16())); } // If the color set is 544 in length, it has an extra 32 bytes at the end if (xivMtrl.ColorSetDataSize == 544) { xivMtrl.ColorSetExtraData = br.ReadBytes(32); } } xivMtrl.AdditionalDataSize = br.ReadUInt16(); xivMtrl.DataStruct1Count = br.ReadUInt16(); xivMtrl.DataStruct2Count = br.ReadUInt16(); xivMtrl.ParameterStructCount = br.ReadUInt16(); xivMtrl.ShaderNumber = br.ReadUInt16(); xivMtrl.Unknown3 = br.ReadUInt16(); xivMtrl.DataStruct1List = new List <DataStruct1>(xivMtrl.DataStruct1Count); for (var i = 0; i < xivMtrl.DataStruct1Count; i++) { xivMtrl.DataStruct1List.Add(new DataStruct1 { ID = br.ReadUInt32(), Unknown1 = br.ReadUInt32() }); } xivMtrl.DataStruct2List = new List <DataStruct2>(xivMtrl.DataStruct2Count); for (var i = 0; i < xivMtrl.DataStruct2Count; i++) { xivMtrl.DataStruct2List.Add(new DataStruct2 { ID = br.ReadUInt32(), Offset = br.ReadInt16(), Size = br.ReadInt16() }); } xivMtrl.ParameterStructList = new List <ParameterStruct>(xivMtrl.ParameterStructCount); for (var i = 0; i < xivMtrl.ParameterStructCount; i++) { xivMtrl.ParameterStructList.Add(new ParameterStruct { ID = br.ReadUInt32(), Unknown1 = br.ReadInt16(), Unknown2 = br.ReadInt16(), TextureIndex = br.ReadUInt32() }); } xivMtrl.AdditionalData = br.ReadBytes(xivMtrl.AdditionalDataSize); } return(xivMtrl); }
/// <summary> /// Reads and parses the ExData file /// </summary> /// <remarks> /// This reads the data at each index of the exd file /// It then places the data in a dictionary with format [index, raw data] /// </remarks> /// <param name="exFile"></param> /// <returns>A dictionary containing the Index and Raw Data of the ex file</returns> public Dictionary <int, byte[]> ReadExData(XivEx exFile) { var exdOffsetList = new List <int>(); var exdDataDictionary = new Dictionary <int, byte[]>(); ReadExHeader(exFile); var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); var language = "_" + _langCode; // Some Ex files are universal and do not have a language code if (LanguageList.Count <= 1) { language = ""; } // Each page is a new exd file // A good example is item_[page]_[language].exd // item_0_en.exd, item_500_en.exd, item_1000_en.exd, etc. foreach (var page in PageList) { var exdFile = exFile + "_" + page + language + ExdExtension; var exdFolderHash = HashGenerator.GetHash("exd"); var exdFileHash = HashGenerator.GetHash(exdFile); exdOffsetList.Add(index.GetDataOffset(exdFolderHash, exdFileHash, XivDataFile._0A_Exd)); } foreach (var offset in exdOffsetList) { var exData = dat.GetType2Data(offset, XivDataFile._0A_Exd); // Big Endian Byte Order using (var br = new BinaryReaderBE(new MemoryStream(exData))) { br.ReadBytes(8); var offsetTableSize = br.ReadInt32(); for (var i = 0; i < offsetTableSize; i += 8) { br.BaseStream.Seek(i + 32, SeekOrigin.Begin); var entryNum = br.ReadInt32(); var entryOffset = br.ReadInt32(); br.BaseStream.Seek(entryOffset, SeekOrigin.Begin); var entrySize = br.ReadInt32(); br.ReadBytes(2); exdDataDictionary.Add(entryNum, br.ReadBytes(entrySize)); } } } return(exdDataDictionary); }
/// <summary> /// Gets the full IMC information for a given item /// </summary> /// <param name="item"></param> /// <param name="modelInfo"></param> /// <returns>The ImcData data</returns> public async Task <ImcData> GetFullImcInfo(IItemModel item, XivModelInfo modelInfo) { var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); var itemType = ItemType.GetItemType(item); var imcPath = GetImcPath(modelInfo, itemType); var imcOffset = await index.GetDataOffset(HashGenerator.GetHash(imcPath.Folder), HashGenerator.GetHash(imcPath.File), _dataFile); if (imcOffset == 0) { throw new Exception($"Could not find offset for {imcPath.Folder}/{imcPath.File}"); } var imcByteData = await dat.GetType2Data(imcOffset, _dataFile); return(await Task.Run(() => { using (var br = new BinaryReader(new MemoryStream(imcByteData))) { var imcData = new ImcData() { VariantCount = br.ReadInt16(), Unknown = br.ReadInt16(), GearVariantList = new List <VariantSet>() }; //weapons and monsters do not have variant sets if (itemType == XivItemType.weapon || itemType == XivItemType.monster) { imcData.OtherVariantList = new List <XivImc>(); imcData.DefaultVariant = new XivImc { Version = br.ReadUInt16(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }; for (var i = 0; i < imcData.VariantCount; i++) { imcData.OtherVariantList.Add(new XivImc { Version = br.ReadUInt16(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }); } } else { imcData.GearVariantList = new List <VariantSet>(); imcData.DefaultVariantSet = new VariantSet { Slot1 = new XivImc { Version = br.ReadUInt16(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, Slot2 = new XivImc { Version = br.ReadUInt16(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, Slot3 = new XivImc { Version = br.ReadUInt16(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, Slot4 = new XivImc { Version = br.ReadUInt16(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, Slot5 = new XivImc { Version = br.ReadUInt16(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, }; for (var i = 0; i < imcData.VariantCount; i++) { // gets the data for each slot in the current variant set var imcGear = new VariantSet { Slot1 = new XivImc { Version = br.ReadUInt16(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, Slot2 = new XivImc { Version = br.ReadUInt16(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, Slot3 = new XivImc { Version = br.ReadUInt16(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, Slot4 = new XivImc { Version = br.ReadUInt16(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, Slot5 = new XivImc { Version = br.ReadUInt16(), Mask = br.ReadUInt16(), Vfx = br.ReadUInt16() }, }; imcData.GearVariantList.Add(imcGear); } } return imcData; } })); }
private async void ExtractButton_Click(object sender, RoutedEventArgs e) { if (String.IsNullOrWhiteSpace(FromBox.Text)) { return; } var path = FromBox.Text; var ext = Path.GetExtension(path); var _dat = new Dat(XivCache.GameInfo.GameDirectory); var _index = new Index(XivCache.GameInfo.GameDirectory); byte[] data = null; var sd = new SaveFileDialog(); if (ext.Length > 0) { ext = ext.Substring(1); sd.Filter = $"{ext.ToUpper()} Files (*.{ext})|*.{ext}"; } sd.FileName = Path.GetFileName(path); sd.RestoreDirectory = true; if (sd.ShowDialog() != System.Windows.Forms.DialogResult.OK) { return; } try { var offset = await _index.GetDataOffset(path); var df = IOUtil.GetDataFileFromPath(path); if (offset <= 0) { FlexibleMessageBox.Show("File does not exist.\n\nFile: " + path, "File Not Found", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } var type = _dat.GetFileType(offset, df); if (type < 2 || type > 4) { throw new InvalidDataException("Invalid or Unknown Data Type."); } var size = await _dat.GetCompressedFileSize(offset, df); if (type == 2) { if (DecompressType2Box.IsChecked == true) { data = await _dat.GetType2Data(offset, df); } else { data = _dat.GetRawData(offset, df, size); } } if (type == 3) { data = _dat.GetRawData(offset, df, size); } if (type == 4) { data = _dat.GetRawData(offset, df, size); } using (var stream = new BinaryWriter(sd.OpenFile())) { stream.Write(data); } FlexibleMessageBox.Show("Raw file extracted successfully to path:\n" + sd.FileName, "Extraction Success", MessageBoxButtons.OK, MessageBoxIcon.Information); this.Close(); } catch (Exception Ex) { FlexibleMessageBox.Show("Unable to decompress or read file:\n" + path + "\n\nError: " + Ex.Message, "File Not Found", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } }
/// <summary> /// Gets the raw equipment parameter file. /// </summary> /// <returns></returns> private async Task <byte[]> LoadEquipmentParameterFile() { return(await _dat.GetType2Data(EquipmentParameterFile, false)); }