/// <summary> /// Deserializes binary byte data into an IteMetadata object. /// </summary> /// <param name="internalPath"></param> /// <param name="bytes"></param> /// <returns></returns> public static async Task <ItemMetadata> Deserialize(byte[] data) { using (var reader = new BinaryReader(new MemoryStream(data))) { uint version = reader.ReadUInt32(); // File path name. var path = ""; char c; while ((c = reader.ReadChar()) != '\0') { path += c; } var root = await XivCache.GetFirstRoot(path); var ret = new ItemMetadata(root); // General header data. uint headerCount = reader.ReadUInt32(); uint perHeaderSize = reader.ReadUInt32(); uint headerEntryStart = reader.ReadUInt32(); // Per-Segment Header data. reader.BaseStream.Seek(headerEntryStart, SeekOrigin.Begin); List <(MetaDataType type, uint offset, uint size)> entries = new List <(MetaDataType type, uint size, uint offset)>(); for (int i = 0; i < headerCount; i++) { // Save offset. var currentOffset = reader.BaseStream.Position; // Read data. MetaDataType type = (MetaDataType)reader.ReadUInt32(); uint offset = reader.ReadUInt32(); uint size = reader.ReadUInt32(); entries.Add((type, offset, size)); // Seek to next. reader.BaseStream.Seek(currentOffset + perHeaderSize, SeekOrigin.Begin); } var imc = entries.FirstOrDefault(x => x.type == MetaDataType.Imc); if (imc.type != MetaDataType.Invalid) { reader.BaseStream.Seek(imc.offset, SeekOrigin.Begin); var bytes = reader.ReadBytes((int)imc.size); // Deserialize IMC entry bytes here. ret.ImcEntries = DeserializeImcData(bytes, root, version); } var eqp = entries.FirstOrDefault(x => x.type == MetaDataType.Eqp); if (eqp.type != MetaDataType.Invalid) { reader.BaseStream.Seek(eqp.offset, SeekOrigin.Begin); var bytes = reader.ReadBytes((int)eqp.size); // Deserialize EQP entry bytes here. ret.EqpEntry = DeserializeEqpData(bytes, root, version); } var eqdp = entries.FirstOrDefault(x => x.type == MetaDataType.Eqdp); if (eqdp.type != MetaDataType.Invalid) { reader.BaseStream.Seek(eqdp.offset, SeekOrigin.Begin); var bytes = reader.ReadBytes((int)eqdp.size); // Deserialize EQDP entry bytes here. ret.EqdpEntries = DeserializeEqdpData(bytes, root, version); } var est = entries.FirstOrDefault(x => x.type == MetaDataType.Est); if (est.type != MetaDataType.Invalid) { reader.BaseStream.Seek(est.offset, SeekOrigin.Begin); var bytes = reader.ReadBytes((int)est.size); // Deserialize EQDP entry bytes here. ret.EstEntries = await DeserializeEstData(bytes, root, version); } var gmp = entries.FirstOrDefault(x => x.type == MetaDataType.Gmp); if (gmp.type != MetaDataType.Invalid) { reader.BaseStream.Seek(gmp.offset, SeekOrigin.Begin); var bytes = reader.ReadBytes((int)gmp.size); // Deserialize EQDP entry bytes here. ret.GmpEntry = await DeserializeGmpData(bytes, root, version); } // Done deserializing all the parts. return(ret); } }
/// <summary> /// Serializes the EQP data entries for the given meta file. /// </summary> /// <param name="meta"></param> /// <returns></returns> private static byte[] SerializeEqpData(ItemMetadata meta) { return(meta.EqpEntry.GetBytes()); }
private static byte[] SerializeGmpData(ItemMetadata meta) { // GMP data is relatively straight forward. Just stick the 5 btes in the file. return(meta.GmpEntry.GetBytes()); }
/// <summary> /// Serializes a Metadata object into a byte representation for storage in dat or mod files. /// </summary> /// <param name="meta"></param> /// <returns></returns> public static async Task <byte[]> Serialize(ItemMetadata meta) { List <byte> bytes = new List <byte>(); // Write general header. bytes.AddRange(BitConverter.GetBytes(_METADATA_VERSION)); var path = meta.Root.Info.GetRootFile(); bytes.AddRange(System.Text.Encoding.UTF8.GetBytes(path)); bytes.Add(0); uint entries = 0; bool hasImc = false, hasEqp = false, hasEqdp = false, hasEst = false, hasGmp = false; if (meta.ImcEntries.Count > 0) { entries++; hasImc = true; } // Set 0 is a unique exception where SE hard-coded it to use Set 1's EQP data. As such, we don't allow that data to be altered // via writing to set 0, just for safety purposes. if (meta.EqpEntry != null && !(meta.Root.Info.PrimaryType == Items.Enums.XivItemType.equipment && meta.Root.Info.PrimaryId == 0)) { entries++; hasEqp = true; } if (meta.EqdpEntries.Count > 0) { entries++; hasEqdp = true; } if (meta.EstEntries.Count > 0) { entries++; hasEst = true; } if (meta.GmpEntry != null) { entries++; hasGmp = true; } bytes.AddRange(BitConverter.GetBytes(entries)); bytes.AddRange(BitConverter.GetBytes(_METADATA_HEADER_SIZE)); bytes.AddRange(BitConverter.GetBytes((uint)bytes.Count + 4)); // Write per part header; save the offset for the metadata we need to fill in later. int imcHeaderInfoOffset = 0; if (hasImc) { imcHeaderInfoOffset = bytes.Count; bytes.AddRange(BitConverter.GetBytes((uint)MetaDataType.Imc)); bytes.AddRange(BitConverter.GetBytes(0)); bytes.AddRange(BitConverter.GetBytes(0)); } int eqpHeaderInfoOffset = 0; if (hasEqp) { eqpHeaderInfoOffset = bytes.Count; bytes.AddRange(BitConverter.GetBytes((uint)MetaDataType.Eqp)); bytes.AddRange(BitConverter.GetBytes(0)); bytes.AddRange(BitConverter.GetBytes(0)); } int eqdpHeaderInfoOffset = 0; if (hasEqdp) { eqdpHeaderInfoOffset = bytes.Count; bytes.AddRange(BitConverter.GetBytes((uint)MetaDataType.Eqdp)); bytes.AddRange(BitConverter.GetBytes(0)); bytes.AddRange(BitConverter.GetBytes(0)); } int estHeaderInfoOffset = 0; if (hasEst) { estHeaderInfoOffset = bytes.Count; bytes.AddRange(BitConverter.GetBytes((uint)MetaDataType.Est)); bytes.AddRange(BitConverter.GetBytes(0)); bytes.AddRange(BitConverter.GetBytes(0)); } int gmpHeaderOffset = 0; if (hasGmp) { gmpHeaderOffset = bytes.Count; bytes.AddRange(BitConverter.GetBytes((uint)MetaDataType.Gmp)); bytes.AddRange(BitConverter.GetBytes(0)); bytes.AddRange(BitConverter.GetBytes(0)); } // Write the actual data. if (hasImc) { // Serialize IMC Data here. var imcData = SerializeImcData(meta); var offset = bytes.Count; bytes.AddRange(imcData); IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)offset), imcHeaderInfoOffset + 4); IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)imcData.Length), imcHeaderInfoOffset + 8); } if (hasEqp) { // Serialize EQP Data here. var eqpData = SerializeEqpData(meta); var offset = bytes.Count; bytes.AddRange(eqpData); IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)offset), eqpHeaderInfoOffset + 4); IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)eqpData.Length), eqpHeaderInfoOffset + 8); } if (hasEqdp) { // Serialize EQP Data here. var eqdpData = SerializeEqdpData(meta); var offset = bytes.Count; bytes.AddRange(eqdpData); IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)offset), eqdpHeaderInfoOffset + 4); IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)eqdpData.Length), eqdpHeaderInfoOffset + 8); } if (hasEst) { // Serialize EST Data here. var estData = SerializeEstData(meta); var offset = bytes.Count; bytes.AddRange(estData); IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)offset), estHeaderInfoOffset + 4); IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)estData.Length), estHeaderInfoOffset + 8); } if (hasGmp) { // Serialize EST Data here. var gmpData = SerializeGmpData(meta); var offset = bytes.Count; bytes.AddRange(gmpData); IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)offset), gmpHeaderOffset + 4); IOUtil.ReplaceBytesAt(bytes, BitConverter.GetBytes((uint)gmpData.Length), gmpHeaderOffset + 8); } return(bytes.ToArray()); }
/// <summary> /// Restores the original SE metadata for this root. /// This should only be called by Index.DeleteFileDescriptor(). /// </summary> public static async Task RestoreDefaultMetadata(XivDependencyRoot root, IndexFile index = null, ModList modlist = null) { var original = await ItemMetadata.CreateFromRaw(root, true); await ApplyMetadata(original, index, modlist); }
/// <summary> /// Applies this Metadata object to the FFXIV file system. /// This should only called by Dat.WriteToDat() / RestoreDefaultMetadata() /// </summary> internal static async Task ApplyMetadata(ItemMetadata meta, IndexFile index = null, ModList modlist = null) { var _eqp = new Eqp(XivCache.GameInfo.GameDirectory); var _modding = new Modding(XivCache.GameInfo.GameDirectory); var _index = new Index(XivCache.GameInfo.GameDirectory); var df = IOUtil.GetDataFileFromPath(meta.Root.Info.GetRootFile()); var dummyItem = new XivGenericItemModel(); dummyItem.Name = Constants.InternalModSourceName; dummyItem.SecondaryCategory = Constants.InternalModSourceName; // Beep boop bool doSave = false; if (index == null) { doSave = true; index = await _index.GetIndexFile(df); modlist = await _modding.GetModListAsync(); } if (meta.ImcEntries.Count > 0) { var _imc = new Imc(XivCache.GameInfo.GameDirectory); var imcPath = meta.Root.GetRawImcFilePath(); await _imc.SaveEntries(imcPath, meta.Root.Info.Slot, meta.ImcEntries, dummyItem, index, modlist); } // Applying EQP data via set 0 is not allowed, as it is a special set hard-coded to use Set 1's data. if (meta.EqpEntry != null && !(meta.Root.Info.PrimaryType == Items.Enums.XivItemType.equipment && meta.Root.Info.PrimaryId == 0)) { await _eqp.SaveEqpEntry(meta.Root.Info.PrimaryId, meta.EqpEntry, dummyItem, index, modlist); } if (meta.EqdpEntries.Count > 0) { await _eqp.SaveEqdpEntries((uint)meta.Root.Info.PrimaryId, meta.Root.Info.Slot, meta.EqdpEntries, dummyItem, index, modlist); } if (meta.EstEntries.Count > 0) { var type = Est.GetEstType(meta.Root); var entries = meta.EstEntries.Values.ToList(); await Est.SaveExtraSkeletonEntries(type, entries, dummyItem, index, modlist); } if (meta.GmpEntry != null) { await _eqp.SaveGimmickParameter(meta.Root.Info.PrimaryId, meta.GmpEntry, dummyItem, index, modlist); } if (doSave && !XivCache.GameInfo.UseLumina) { await _index.SaveIndexFile(index); await _modding.SaveModListAsync(modlist); } }