public async Task <XivTex> GetTexData(string path) { var folder = Path.GetDirectoryName(path); folder = folder.Replace("\\", "/"); var file = Path.GetFileName(path); long offset = 0; var hashedfolder = 0; var hashedfile = 0; hashedfolder = HashGenerator.GetHash(folder); hashedfile = HashGenerator.GetHash(file); var df = IOUtil.GetDataFileFromPath(path); offset = await _index.GetDataOffset(hashedfolder, hashedfile, df); if (offset == 0) { throw new Exception($"Could not find offset for {path}"); } XivTex xivTex; try { if (path.Contains(".atex")) { var atex = new ATex(_gameDirectory, df); xivTex = await atex.GetATexData(offset); } else { xivTex = await _dat.GetType4Data(offset, df); } } catch (Exception ex) { throw new Exception($"There was an error reading texture data at offset {offset}"); } var ttp = new TexTypePath(); ttp.DataFile = df; ttp.Name = Path.GetFileName(path); ttp.Type = XivTexType.Other; ttp.Path = path; xivTex.TextureTypeAndPath = ttp; return(xivTex); }
/// <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); } }
private async Task AddFile(FileEntry file, IItem item, byte[] rawData = null) { var dat = new Dat(_gameDirectory); var index = new Index(_gameDirectory); if (file == null || file.Path == null || _selectedModOption == null) return; var includedModsList = IncludedModsList.Items.Cast<FileEntry>().ToList(); if (includedModsList.Any(f => f.Path.Equals(file.Path))) { if (FlexibleMessageBox.Show( string.Format(UIMessages.ExistingOption, file.Name), UIMessages.OverwriteTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question) != System.Windows.Forms.DialogResult.Yes) { return; } } if (rawData == null) { var df = IOUtil.GetDataFileFromPath(file.Path); var offset = await index.GetDataOffset(file.Path); if (offset <= 0) { FlexibleMessageBox.Show("Cannot include file, file offset invalid.", UIMessages.ModDataReadErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); return; } var size = await dat.GetCompressedFileSize(offset, df); rawData = dat.GetRawData(offset, df, size); if (rawData == null) { FlexibleMessageBox.Show("Cannot include file, file offset invalid.", UIMessages.ModDataReadErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); return; } } if(_selectedModOption.Mods.ContainsKey(file.Path)) { _selectedModOption.Mods[file.Path].ModDataBytes = rawData; } else { IncludedModsList.Items.Add(file); var modData = new ModData { Name = item.Name, Category = item.SecondaryCategory, FullPath = file.Path, ModDataBytes = rawData, }; _selectedModOption.Mods.Add(file.Path, modData); } }
/// <summary> /// Gets the original or modded data for type 4 files based on the path specified. /// </summary> /// <remarks> /// Type 4 files are used for Textures /// </remarks> /// <param name="internalPath">The internal file path of the item</param> /// <param name="forceOriginal">Flag used to get original game data</param> /// <param name="xivTex">The XivTex container to fill</param> public void GetType4Data(string internalPath, bool forceOriginal, XivTex xivTex) { var index = new Index(_gameDirectory); ModInfo modInfo = null; var inModList = false; var dataFile = GetDataFileFromPath(internalPath); if (forceOriginal) { // Checks if the item being imported already exists in the modlist using (var streamReader = new StreamReader(_modListDirectory.FullName)) { string line; while ((line = streamReader.ReadLine()) != null) { modInfo = JsonConvert.DeserializeObject <ModInfo>(line); if (modInfo.fullPath.Equals(internalPath)) { inModList = true; break; } } } // If the file exists in the modlist, get the data from the original data if (inModList) { GetType4Data(modInfo.originalOffset, dataFile, xivTex); return; } } // If it doesn't exist in the modlist(the item is not modded) or force original is false, // grab the data directly from them index file. var folder = Path.GetDirectoryName(internalPath); folder = folder.Replace("\\", "/"); var file = Path.GetFileName(internalPath); var offset = index.GetDataOffset(HashGenerator.GetHash(folder), HashGenerator.GetHash(file), dataFile); if (offset == 0) { throw new Exception($"Could not find offest for {internalPath}"); } GetType4Data(offset, dataFile, xivTex); }
/// <summary> /// Checks to see whether the mod is currently enabled /// </summary> /// <param name="internalPath">The internal path of the file</param> /// <param name="dataFile">The data file to check in</param> /// <param name="indexCheck">Flag to determine whether to check the index file or just the modlist</param> /// <returns></returns> public async Task <XivModStatus> IsModEnabled(string internalPath, bool indexCheck) { if (!File.Exists(ModListDirectory.FullName)) { return(XivModStatus.Original); } if (indexCheck) { var index = new Index(_gameDirectory); var modEntry = await TryGetModEntry(internalPath); if (modEntry == null) { return(XivModStatus.Original); } var originalOffset = modEntry.data.originalOffset; var moddedOffset = modEntry.data.modOffset; var offset = await index.GetDataOffset( HashGenerator.GetHash(Path.GetDirectoryName(internalPath).Replace("\\", "/")), HashGenerator.GetHash(Path.GetFileName(internalPath)), XivDataFiles.GetXivDataFile(modEntry.datFile)); if (offset.Equals(originalOffset)) { return(XivModStatus.Disabled); } if (offset.Equals(moddedOffset)) { return(XivModStatus.Enabled); } throw new Exception("Offset in Index does not match either original or modded offset in modlist."); } else { var modEntry = await TryGetModEntry(internalPath); if (modEntry == null) { return(XivModStatus.Original); } return(modEntry.enabled ? XivModStatus.Enabled : XivModStatus.Disabled); } }
public async Task <List <TexTypePath> > GetAtexPaths(string vfxPath) { var index = new Index(_gameDirectory); var avfx = new Avfx(_gameDirectory, _dataFile); var folder = vfxPath.Substring(0, vfxPath.LastIndexOf("/")); var file = Path.GetFileName(vfxPath); var vfxOffset = await index.GetDataOffset(HashGenerator.GetHash(folder), HashGenerator.GetHash(file), _dataFile); var atexTexTypePathList = new List <TexTypePath>(); if (vfxOffset <= 0) { return(new List <TexTypePath>()); } var aTexPaths = new List <string>(); try { aTexPaths = await avfx.GetATexPaths(vfxOffset); } catch (Exception e) { throw new Exception(e.Message); } foreach (var atexPath in aTexPaths) { var ttp = new TexTypePath { DataFile = _dataFile, Name = "VFX: " + Path.GetFileNameWithoutExtension(atexPath), Path = atexPath }; atexTexTypePathList.Add(ttp); } return(atexTexTypePathList); }
/// <summary> /// Reads and parses the ExHeader file /// </summary> /// <param name="exFile">The Ex file to use.</param> private async Task 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 offset = await _index.GetDataOffset(exdFolderHash, exdFileHash, XivDataFile._0A_Exd); if (offset == 0) { throw new Exception($"Could not find offset for exd/{exFile}{ExhExtension}"); } var exhData = await _dat.GetType2Data(offset, XivDataFile._0A_Exd); await Task.Run(() => { // 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(); if (langCode != 0) { LanguageList.Add(langCode); } } } }); }
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 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); }
private async Task CreateBasic() { string modPackPath = Path.Combine(Properties.Settings.Default.ModPack_Directory, $"{ViewModel.Name}.ttmp2"); if (File.Exists(modPackPath)) { DialogResult overwriteDialogResult = FlexibleMessageBox.Show(new Wpf32Window(this), UIMessages.ModPackOverwriteMessage, UIMessages.OverwriteTitle, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning); if (overwriteDialogResult != System.Windows.Forms.DialogResult.Yes) { return; } } TTMP texToolsModPack = new TTMP(new DirectoryInfo(Settings.Default.ModPack_Directory), XivStrings.TexTools); var index = new Index(XivCache.GameInfo.GameDirectory); var dat = new Dat(XivCache.GameInfo.GameDirectory); var modding = new Modding(XivCache.GameInfo.GameDirectory); var ModList = modding.GetModList(); SimpleModPackData simpleModPackData = new SimpleModPackData { Name = ViewModel.Name, Author = ViewModel.Author, Version = ViewModel.Version, Description = ViewModel.Description, Url = ViewModel.Url, SimpleModDataList = new List <SimpleModData>() }; foreach (var entry in ViewModel.Entries) { foreach (var file in entry.AllFiles) { var exists = await index.FileExists(file); // This is a funny case where in order to create the modpack we actually have to write a default meta entry to the dats first. // If we had the right functions we could just load and serialize the data, but we don't atm. if (!exists && Path.GetExtension(file) == ".meta") { var meta = await ItemMetadata.GetMetadata(file); await ItemMetadata.SaveMetadata(meta, XivStrings.TexTools); } var offset = await index.GetDataOffset(file); var dataFile = IOUtil.GetDataFileFromPath(file); var compressedSize = await dat.GetCompressedFileSize(offset, dataFile); var modEntry = ModList.Mods.FirstOrDefault(x => x.fullPath == file); var modded = modEntry != null && modEntry.enabled == true; SimpleModData simpleData = new SimpleModData { Name = entry.Item.Name, Category = entry.Item.SecondaryCategory, FullPath = file, ModOffset = offset, ModSize = compressedSize, IsDefault = !modded, DatFile = dataFile.GetDataFileName() }; simpleModPackData.SimpleModDataList.Add(simpleData); } } try { await LockUi(UIStrings.Creating_Modpack, null, null); Progress <(int current, int total, string message)> progressIndicator = new Progress <(int current, int total, string message)>(ReportProgress); await texToolsModPack.CreateSimpleModPack(simpleModPackData, XivCache.GameInfo.GameDirectory, progressIndicator, true); FlexibleMessageBox.Show(new Wpf32Window(this), "Modpack Created Successfully.", "Modpack Created", MessageBoxButtons.OK, MessageBoxIcon.Information); await UnlockUi(this); DialogResult = true; } catch (Exception ex) { FlexibleMessageBox.Show(new Wpf32Window(this), "An Error occured while creating the modpack.\n\n" + ex.Message, "Modpack Creation Error", MessageBoxButtons.OK, MessageBoxIcon.Error); await UnlockUi(this); } }
private async Task CreateAdvanced() { string modPackPath = Path.Combine(Properties.Settings.Default.ModPack_Directory, $"{ViewModel.Name}.ttmp2"); if (File.Exists(modPackPath)) { DialogResult overwriteDialogResult = FlexibleMessageBox.Show(new Wpf32Window(this), UIMessages.ModPackOverwriteMessage, UIMessages.OverwriteTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (overwriteDialogResult != System.Windows.Forms.DialogResult.Yes) { return; } } await LockUi(UIStrings.Creating_Modpack, null, null); try { TTMP texToolsModPack = new TTMP(new DirectoryInfo(Settings.Default.ModPack_Directory), XivStrings.TexTools); var index = new Index(XivCache.GameInfo.GameDirectory); var dat = new Dat(XivCache.GameInfo.GameDirectory); var modding = new Modding(XivCache.GameInfo.GameDirectory); var ModList = modding.GetModList(); var wizardData = new ModPackData() { Name = ViewModel.Name, Author = ViewModel.Author, Version = ViewModel.Version, Description = ViewModel.Description, Url = ViewModel.Url, ModPackPages = new List <ModPackData.ModPackPage>() }; var page = new ModPackData.ModPackPage() { PageIndex = 1, ModGroups = new List <ModGroup>() }; wizardData.ModPackPages.Add(page); foreach (var e in ViewModel.Entries) { var item = e.Item; var files = e.AllFiles; var group = new ModGroup() { GroupName = item.Name, SelectionType = "Multi", OptionList = new List <ModOption>() }; page.ModGroups.Add(group); var option = new ModOption { GroupName = group.GroupName, IsChecked = true, Name = GetNiceLevelName(e.Level, true, true), Description = "Item: " + item.Name + "\nInclusion Level: " + GetNiceLevelName(e.Level) + "\nPrimary Files:" + e.MainFiles.Count + "\nTotal Files:" + e.AllFiles.Count, SelectionType = "Multi", }; group.OptionList.Add(option); foreach (var file in e.AllFiles) { var exists = await index.FileExists(file); // This is a funny case where in order to create the modpack we actually have to write a default meta entry to the dats first. // If we had the right functions we could just load and serialize the data, but we don't atm. if (!exists && Path.GetExtension(file) == ".meta") { var meta = await ItemMetadata.GetMetadata(file); await ItemMetadata.SaveMetadata(meta, XivStrings.TexTools); } var offset = await index.GetDataOffset(file); var dataFile = IOUtil.GetDataFileFromPath(file); var compressedSize = await dat.GetCompressedFileSize(offset, dataFile); var modEntry = ModList.Mods.FirstOrDefault(x => x.fullPath == file); var modded = modEntry != null && modEntry.enabled == true; var fData = new ModData { Name = e.Item.Name, Category = e.Item.SecondaryCategory, FullPath = file, IsDefault = !modded, ModDataBytes = dat.GetRawData(offset, dataFile, compressedSize) }; option.Mods.Add(file, fData); } } // Okay modpack is now created internally, just need to save it. var progressIndicator = new Progress <double>(ReportProgressAdv); await texToolsModPack.CreateWizardModPack(wizardData, progressIndicator, true); FlexibleMessageBox.Show(new Wpf32Window(this), "Modpack Created Successfully.", "Modpack Created", MessageBoxButtons.OK, MessageBoxIcon.Information); await UnlockUi(this); DialogResult = true; } catch (Exception ex) { FlexibleMessageBox.Show(new Wpf32Window(this), "An Error occured while creating the modpack.\n\n" + ex.Message, "Modpack Creation Error", MessageBoxButtons.OK, MessageBoxIcon.Error); await UnlockUi(this); } }