/// <summary> /// Loads the thumbnail and display info for the file /// </summary> /// <param name="inputStream">The file data stream</param> /// <param name="fileExtension">The file extension</param> /// <param name="width">The thumbnail width</param> /// <param name="manager">The manager</param> /// <returns>The thumbnail data</returns> public virtual ArchiveFileThumbnailData LoadThumbnail(ArchiveFileStream inputStream, FileExtension fileExtension, int width, IArchiveDataManager manager) { return(new ArchiveFileThumbnailData(null, new DuoGridItemViewModel[] { // TODO: Read and include .wav metadata, such as track length etc. })); }
/// <summary> /// Indicates if a file with the specifies file extension and data is of this type /// </summary> /// <param name="fileExtension">The file extension to check</param> /// <param name="inputStream">The file data to check</param> /// <param name="manager">The manager</param> /// <returns>True if it is of this type, otherwise false</returns> public override bool IsOfType(FileExtension fileExtension, ArchiveFileStream inputStream, IArchiveDataManager manager) { if (fileExtension != new FileExtension(".tga.ckd", multiple: true) && fileExtension != new FileExtension(".png.ckd", multiple: true)) { return(false); } // Set the Stream position TextureCooked?tex = ReadTEXHeader(inputStream, manager); // Check for type match if (IsOfType(inputStream, manager, tex)) { return(true); } // If the format has a magic header we check for it if (FormatMagic != null) { // Use a reader using Reader reader = new(inputStream.Stream, manager.Context !.GetSettings <UbiArtSettings>().GetEndian == BinarySerializer.Endian.Little, true); // Get the magic header uint magic = reader.ReadUInt32(); // Check if it matches the magic return(magic == FormatMagic); } return(false); }
public override bool IsOfType(ArchiveFileStream inputStream, IArchiveDataManager manager, TextureCooked?tex) { UbiArtSettings settings = manager.Context !.GetSettings <UbiArtSettings>(); // TODO: Find better way to check this return(settings.Platform == Platform.Xbox360); }
/// <summary> /// Loads the thumbnail and display info for the file /// </summary> /// <param name="inputStream">The file data stream</param> /// <param name="fileExtension">The file extension</param> /// <param name="width">The thumbnail width</param> /// <param name="manager">The manager</param> /// <returns>The thumbnail data</returns> public ArchiveFileThumbnailData LoadThumbnail(ArchiveFileStream inputStream, FileExtension fileExtension, int width, IArchiveDataManager manager) { // Load the file GF file = GetFileContent(inputStream, manager); // Load the raw bitmap data RawBitmapData rawBmp = file.GetRawBitmapData(width, (int)(file.Height / ((double)file.Width / width))); var format = rawBmp.PixelFormat == PixelFormat.Format32bppArgb ? PixelFormats.Bgra32 : PixelFormats.Bgr24; // Get a thumbnail source BitmapSource thumbnailSource = BitmapSource.Create(rawBmp.Width, rawBmp.Height, 96, 96, format, null, rawBmp.PixelData, (rawBmp.Width * format.BitsPerPixel + 7) / 8); // Get the thumbnail with the specified size return(new ArchiveFileThumbnailData(thumbnailSource, new DuoGridItemViewModel[] { new DuoGridItemViewModel( header: new ResourceLocString(nameof(Resources.Archive_FileInfo_Img_Size)), text: $"{file.Width}x{file.Height}"), new DuoGridItemViewModel( header: new ResourceLocString(nameof(Resources.Archive_FileInfo_Img_HasAlpha)), text: new GeneratedLocString(() => $"{file.PixelFormat.SupportsTransparency()}")), new DuoGridItemViewModel( header: new ResourceLocString(nameof(Resources.Archive_FileInfo_Img_Mipmaps)), text: $"{file.ExclusiveMipmapCount}"), new DuoGridItemViewModel( header: new ResourceLocString(nameof(Resources.Archive_FileInfo_Format)), text: $"{file.PixelFormat.ToString().Replace("Format_", "")}", minUserLevel: UserLevel.Technical), })); }
/// <summary> /// Converts the file data from the specified format /// </summary> /// <param name="inputFormat">The format to convert from</param> /// <param name="outputFormat">The format to convert to</param> /// <param name="currentFileStream">The current file stream</param> /// <param name="inputStream">The input file data stream to convert from</param> /// <param name="outputStream">The output stream for the converted data</param> /// <param name="manager">The manager</param> public void ConvertFrom(FileExtension inputFormat, FileExtension outputFormat, ArchiveFileStream currentFileStream, ArchiveFileStream inputStream, ArchiveFileStream outputStream, IArchiveDataManager manager) { // Load the bitmap using Bitmap bmp = new(inputStream.Stream); // Load the current file GF gf = GetFileContent(currentFileStream, manager); // IDEA: If bmp is not in supported format, then convert it? RawBitmapData rawBitmapData; // Get the bitmap lock using (BitmapLock bmpLock = new(bmp)) { // Get the raw bitmap data rawBitmapData = new RawBitmapData(bmp.Width, bmp.Height, bmpLock.Pixels, bmp.PixelFormat); // Force the new pixel format to be 888 or 8888 if set to do so if (Services.Data.Archive_GF_ForceGF8888Import) { gf.PixelFormat = gf.PixelFormat.SupportsTransparency() ? GF_Format.Format_32bpp_BGRA_8888 : GF_Format.Format_24bpp_BGR_888; } // Check if the format should be updated for transparency if (Services.Data.Archive_GF_UpdateTransparency != UserData_Archive_GF_TransparencyMode.PreserveFormat) { // NOTE: Only 24 and 32 bpp bitmaps are supported // Check if the imported file is transparent bool?isTransparent = bmp.PixelFormat switch { PixelFormat.Format32bppArgb => (Services.Data.Archive_GF_UpdateTransparency == UserData_Archive_GF_TransparencyMode.UpdateBasedOnPixelFormat || bmpLock.UtilizesAlpha()), PixelFormat.Format24bppRgb => false, _ => null }; // NOTE: Currently only supported for formats with 3 or 4 channels // Check if the format should be updated for transparency if (gf.Channels >= 3 && isTransparent != null) { // Update the format gf.PixelFormat = isTransparent.Value ? GF_Format.Format_32bpp_BGRA_8888 : GF_Format.Format_24bpp_BGR_888; } } } byte oldRepeatByte = gf.RepeatByte; OpenSpaceSettings settings = manager.Context !.GetSettings <OpenSpaceSettings>(); // Import the bitmap gf.ImportFromBitmap(settings, rawBitmapData, Services.Data.Archive_GF_GenerateMipmaps); Logger.Debug("The repeat byte has been updated for a .gf file from {0} to {1}", oldRepeatByte, gf.RepeatByte); // Serialize the data to get the bytes manager.Context.WriteStreamData(outputStream.Stream, gf, name: outputStream.Name, leaveOpen: true); }
/// <summary> /// Converts the file data from the specified format /// </summary> /// <param name="inputFormat">The format to convert from</param> /// <param name="outputFormat">The format to convert to</param> /// <param name="inputStream">The input file data stream to convert from</param> /// <param name="outputStream">The output stream for the converted data</param> public void ConvertFrom(FileExtension inputFormat, MagickFormat outputFormat, ArchiveFileStream inputStream, ArchiveFileStream outputStream) { // Get the image in specified format using MagickImage img = new(inputStream.Stream, GetMagickFormat(inputFormat.FileExtensions)); // Write to stream as native format img.Write(outputStream.Stream, outputFormat); }
/// <summary> /// Gets an image from the file data /// </summary> /// <param name="inputStream">The file data stream</param> /// <param name="format">The file format</param> /// <param name="manager">The manager to check</param> /// <returns>The image</returns> protected override MagickImage GetImage(ArchiveFileStream inputStream, FileExtension format, IArchiveDataManager manager) { // Set the Stream position ReadTEXHeader(inputStream, manager); // Return the image return(new MagickImage(inputStream.Stream)); }
/// <summary> /// Loads the thumbnail and display info for the file /// </summary> /// <param name="inputStream">The file data stream</param> /// <param name="fileExtension">The file extension</param> /// <param name="width">The thumbnail width</param> /// <param name="manager">The manager</param> /// <returns>The thumbnail data</returns> public override ArchiveFileThumbnailData LoadThumbnail(ArchiveFileStream inputStream, FileExtension fileExtension, int width, IArchiveDataManager manager) { // Only load thumbnails for supported formats if (!IsFormatSupported) { return(new ArchiveFileThumbnailData(null, Array.Empty <DuoGridItemViewModel>())); } else { return(base.LoadThumbnail(inputStream, fileExtension, width, manager)); } }
public IArchiveFileType GetFileType(ArchiveFileStream stream) { // Get types supported by the current manager IArchiveFileType[] types = FileTypes.Where(x => x.IsSupported(Manager)).ToArray(); // First attempt to find matching file type based off of the file extension to avoid having to read the file IArchiveFileType?match = types.FirstOrDefault(x => x.IsOfType(FileExtension)); // If no match, check the data if (match == null) { // Find a match from the stream data match = types.FirstOrDefault(x => x.IsOfType(FileExtension, stream, Manager)); } // Return the type and set to default if still null return(match ?? DefaultFileType); }
public ArchiveFileStream GetFileData(IDisposable?generator) { if (!IsPendingImport && generator == null) { throw new ArgumentNullException(nameof(generator), "A generator must be specified if there is no pending import"); } // Get the stream ArchiveFileStream stream = IsPendingImport ? new ArchiveFileStream(PendingImport, FileName, false) : new ArchiveFileStream(() => Manager.GetFileData(generator !, ArchiveEntry), FileName, true); // Seek to the beginning stream.SeekToBeginning(); // Return the stream return(stream); }
/// <summary> /// Converts the file data to the specified format /// </summary> /// <param name="inputFormat">The format to convert from</param> /// <param name="outputFormat">The format to convert to</param> /// <param name="inputStream">The input file data stream</param> /// <param name="outputStream">The output stream for the converted data</param> /// <param name="manager">The manager</param> public void ConvertTo(FileExtension inputFormat, FileExtension outputFormat, ArchiveFileStream inputStream, Stream outputStream, IArchiveDataManager manager) { // Get the image format ImageFormat imgFormat = outputFormat.PrimaryFileExtension switch { ".png" => ImageFormat.Png, ".jpeg" => ImageFormat.Jpeg, ".jpg" => ImageFormat.Jpeg, ".bmp" => ImageFormat.Bmp, _ => throw new Exception($"The specified file format {outputFormat.PrimaryFileExtension} is not supported") }; // Get the bitmap using Bitmap bmp = GetFileContent(inputStream, manager).GetRawBitmapData().GetBitmap(); // Save the bitmap to the output stream bmp.Save(outputStream, imgFormat); }
/// <summary> /// Converts the file data from the specified format /// </summary> /// <param name="inputFormat">The format to convert from</param> /// <param name="outputFormat">The format to convert to</param> /// <param name="currentFileStream">The current file stream</param> /// <param name="inputStream">The input file data stream to convert from</param> /// <param name="outputStream">The output stream for the converted data</param> /// <param name="manager">The manager</param> public override void ConvertFrom(FileExtension inputFormat, FileExtension outputFormat, ArchiveFileStream currentFileStream, ArchiveFileStream inputStream, ArchiveFileStream outputStream, IArchiveDataManager manager) { // Get the current TEX data TextureCooked?tex = ReadTEXHeader(currentFileStream, manager); // If there's no TEX header we handle the image data directly if (tex == null) { if (outputFormat == Format) { inputStream.Stream.CopyTo(outputStream.Stream); } else { ConvertFrom(inputFormat, MagickFormat, inputStream, outputStream); } } else { // Get the image in specified format using MagickImage img = new(inputStream.Stream, GetMagickFormat(inputFormat.FileExtensions)); // Change the type to the output format img.Format = MagickFormat; // Get the image bytes byte[] bytes = img.ToByteArray(); // Update the TEX header tex.Height = (ushort)img.Height; tex.Width = (ushort)img.Width; // TODO: Figure out what the values are on Wii U where they don't match the actual size tex.TextureSize = (uint)bytes.Length; tex.TextureSize2 = (uint)bytes.Length; tex.ImageData = bytes; tex.Pre_SerializeImageData = true; // Write the TEX file manager.Context !.WriteStreamData(outputStream.Stream, tex, name: outputStream.Name, leaveOpen: true); } }
public override void Dispose() { // Cancel refreshing thumbnails ExplorerDialogViewModel.CancelRefreshingThumbnails = true; // Dispose base class base.Dispose(); // Dispose the stream ArchiveFileStream?.Dispose(); // Dispose every directory ClearAndDisposeItems(); // Dispose the generator ArchiveFileGenerator?.Dispose(); RL.Logger?.LogInformationSource($"The archive {DisplayName} has been disposed"); }
/// <summary> /// Loads the thumbnail and display info for the file /// </summary> /// <param name="inputStream">The file data stream</param> /// <param name="fileExtension">The file extension</param> /// <param name="width">The thumbnail width</param> /// <param name="manager">The manager</param> /// <returns>The thumbnail data</returns> public virtual ArchiveFileThumbnailData LoadThumbnail(ArchiveFileStream inputStream, FileExtension fileExtension, int width, IArchiveDataManager manager) { // Get the image using MagickImage img = GetImage(inputStream, fileExtension, manager); // Resize to a thumbnail img.Thumbnail(width, (int)(img.Height / ((double)img.Width / width))); BitmapSource thumb = img.ToBitmapSource(); return(new ArchiveFileThumbnailData(thumb, new DuoGridItemViewModel[] { new DuoGridItemViewModel( header: new ResourceLocString(nameof(Resources.Archive_FileInfo_Img_Size)), text: $"{img.Width}x{img.Height}"), new DuoGridItemViewModel( header: new ResourceLocString(nameof(Resources.Archive_FileInfo_Format)), text: new GeneratedLocString(() => $"{GetFormat(fileExtension)}")), })); }
/// <summary> /// Reads the TEX header if there is one /// </summary> /// <param name="inputStream">The input stream</param> /// <param name="manager">The manager</param> /// <returns>The TEX header, if available</returns> protected TextureCooked?ReadTEXHeader(ArchiveFileStream inputStream, IArchiveDataManager manager) { // Use a reader using Reader reader = new(inputStream.Stream, manager.Context !.GetSettings <UbiArtSettings>().GetEndian == BinarySerializer.Endian.Little, true); // Check if it's in a TEX wrapper inputStream.Stream.Position = 4; bool usesTexWrapper = reader.ReadUInt32() == TEXHeader; // Reset the position inputStream.Stream.Position = 0; // If it uses a TEX wrapper we need to serialize the header if (usesTexWrapper) { // Serialize the header return(manager.Context.ReadStreamData <TextureCooked>(inputStream.Stream, name: inputStream.Name, leaveOpen: true, onPreSerialize: x => x.Pre_SerializeImageData = false)); } return(null); }
/// <summary> /// Gets an image from the file data /// </summary> /// <param name="inputStream">The file data stream</param> /// <param name="format">The file format</param> /// <param name="manager">The manager to check</param> /// <returns>The image</returns> protected override MagickImage GetImage(ArchiveFileStream inputStream, FileExtension format, IArchiveDataManager manager) { // Serialize data TextureCooked tex = manager.Context !.ReadStreamData <TextureCooked>(inputStream.Stream, name: inputStream.Name, leaveOpen: true, onPreSerialize: x => { x.Pre_SerializeImageData = true; x.Pre_FileSize = inputStream.Stream.Length; }); // Get the untiled image data byte[] untiledImgData = tex.Header_Xbox360.Untile(tex.ImageData, true); DDSParser.DDSStruct header = new() { pixelformat = new DDSParser.DDSStruct.pixelformatstruct() { rgbbitcount = 32 }, width = (uint)tex.Header_Xbox360.Width, height = (uint)tex.Header_Xbox360.Height, depth = 1 }; byte[] rawImgData = tex.Header_Xbox360.CompressionType switch { TextureCooked_Xbox360Header.TextureCompressionType.DXT1 => DDSParser.DecompressDXT1(header, untiledImgData), TextureCooked_Xbox360Header.TextureCompressionType.DXT3 => DDSParser.DecompressDXT3(header, untiledImgData), TextureCooked_Xbox360Header.TextureCompressionType.DXT5 => DDSParser.DecompressDXT5(header, untiledImgData), _ => throw new ArgumentOutOfRangeException(nameof(tex.Header_Xbox360.CompressionType), tex.Header_Xbox360.CompressionType, null) }; // Return the image return(new MagickImage(rawImgData, new MagickReadSettings() { Format = MagickFormat.Rgba, Width = tex.Header_Xbox360.Width, Height = tex.Header_Xbox360.Height })); } }
/// <summary> /// Disposes the archive and its folders and files /// </summary> public override void Dispose() { // Cancel refreshing thumbnails ExplorerDialogViewModel.CancelInitializeFiles = true; // Dispose base class base.Dispose(); // Dispose the stream ArchiveFileStream.Dispose(); // Dispose every directory ClearAndDisposeItems(); // Dispose the generator ArchiveFileGenerator?.Dispose(); // Dispose the cache ThumbnailCache.Dispose(); Logger.Info("The archive {0} has been disposed", DisplayName); }
/// <summary> /// Converts the file data to the specified format /// </summary> /// <param name="inputFormat">The format to convert from</param> /// <param name="outputFormat">The format to convert to</param> /// <param name="inputStream">The input file data stream</param> /// <param name="outputStream">The output stream for the converted data</param> /// <param name="manager">The manager</param> public virtual void ConvertTo(FileExtension inputFormat, FileExtension outputFormat, ArchiveFileStream inputStream, Stream outputStream, IArchiveDataManager manager) { // Get the image using MagickImage img = GetImage(inputStream, inputFormat, manager); // Write to stream as new format img.Write(outputStream, GetMagickFormat(outputFormat.FileExtensions)); }
/// <summary> /// Writes the files to the archive /// </summary> /// <param name="generator">The generator</param> /// <param name="archive">The loaded archive data</param> /// <param name="outputFileStream">The file output stream for the archive</param> /// <param name="files">The files to include</param> public void WriteArchive(IDisposable?generator, object archive, ArchiveFileStream outputFileStream, IList <ArchiveFileItem> files) { Logger.Info("An R1 PC archive is being repacked..."); // Get the archive data var data = (PC_FileArchive)archive; // Create the file generator using ArchiveFileGenerator <PC_FileArchiveEntry> fileGenerator = new(); // Get files and entries var archiveFiles = files.Select(x => new { Entry = (PC_FileArchiveEntry)x.ArchiveEntry, FileItem = x }).ToArray(); // Set files and directories data.Entries = archiveFiles.Select(x => x.Entry).ToArray(); BinaryFile binaryFile = new StreamFile(Context, outputFileStream.Name, outputFileStream.Stream, leaveOpen: true); try { Context.AddFile(binaryFile); // Initialize the data data.Init(binaryFile.StartPointer); // Set the current pointer position to the header size data.RecalculateSize(); uint pointer = (uint)data.Size; // Load each file foreach (var file in archiveFiles) { // Process the file name file.Entry.FileName = ProcessFileName(file.Entry.FileName); // Get the file entry PC_FileArchiveEntry entry = file.Entry; // Add to the generator fileGenerator.Add(entry, () => { // Get the file stream to write to the archive Stream fileStream = file.FileItem.GetFileData(generator).Stream; // Set the pointer entry.FileOffset = pointer; // Update the pointer by the file size pointer += entry.FileSize; // Invoke event OnWritingFileToArchive?.Invoke(this, new ValueEventArgs <ArchiveFileItem>(file.FileItem)); return(fileStream); }); } // Make sure we have a generator for each file if (fileGenerator.Count != data.Entries.Length) { throw new Exception("The .dat file can't be serialized without a file generator for each file"); } // Write the file contents foreach (PC_FileArchiveEntry file in data.Entries) { // Get the file stream using Stream fileStream = fileGenerator.GetFileStream(file); // Set the position to the pointer outputFileStream.Stream.Position = file.FileOffset; // Write the contents from the generator fileStream.CopyTo(outputFileStream.Stream); } outputFileStream.Stream.Position = 0; // Serialize the data FileFactory.Write(Context, binaryFile.FilePath, data); Logger.Info("The R1 PC archive has been repacked"); } finally { Context.RemoveFile(binaryFile); } }
/// <summary> /// Indicates if a file with the specifies file extension and data is of this type /// </summary> /// <param name="fileExtension">The file extension to check</param> /// <param name="inputStream">The file data to check</param> /// <param name="manager">The manager</param> /// <returns>True if it is of this type, otherwise false</returns> public virtual bool IsOfType(FileExtension fileExtension, ArchiveFileStream inputStream, IArchiveDataManager manager) => false;
/// <summary> /// Converts the file data to the specified format /// </summary> /// <param name="inputFormat">The format to convert from</param> /// <param name="outputFormat">The format to convert to</param> /// <param name="inputStream">The input file data stream</param> /// <param name="outputStream">The output stream for the converted data</param> /// <param name="manager">The manager</param> public override void ConvertTo(FileExtension inputFormat, FileExtension outputFormat, ArchiveFileStream inputStream, Stream outputStream, IArchiveDataManager manager) { // Check if it's the native format if (outputFormat == Format) { // Set the start position ReadTEXHeader(inputStream, manager); // Copy the image data inputStream.Stream.CopyTo(outputStream); } else { // Convert the image normally base.ConvertTo(inputFormat, outputFormat, inputStream, outputStream, manager); } }
/// <summary> /// Gets an image from the file data /// </summary> /// <param name="inputStream">The file data stream</param> /// <param name="format">The file format</param> /// <param name="manager">The manager to check</param> /// <returns>The image</returns> protected virtual MagickImage GetImage(ArchiveFileStream inputStream, FileExtension format, IArchiveDataManager manager) => new MagickImage(inputStream.Stream, GetMagickFormat(format));
public virtual bool IsOfType(ArchiveFileStream inputStream, IArchiveDataManager manager, TextureCooked?tex) => false;
/// <summary> /// Writes the files to the archive /// </summary> /// <param name="generator">The generator</param> /// <param name="archive">The loaded archive data</param> /// <param name="outputFileStream">The file output stream for the archive</param> /// <param name="files">The files to include</param> public void WriteArchive(IDisposable?generator, object archive, ArchiveFileStream outputFileStream, IList <ArchiveFileItem> files) { Logger.Info("An IPK archive is being repacked..."); // Get the archive data var data = (BundleFile)archive; // Create the file generator using ArchiveFileGenerator <BundleFile_FileEntry> fileGenerator = new(); // Get files and entries var archiveFiles = files.Select(x => new { Entry = (BundleFile_FileEntry)x.ArchiveEntry, FileItem = x }).ToArray(); // Set the files data.FilePack.Files = archiveFiles.Select(x => x.Entry).ToArray(); data.BootHeader.FilesCount = (uint)data.FilePack.Files.Length; // Save the old base offset uint oldBaseOffset = data.BootHeader.BaseOffset; // Keep track of the current pointer position ulong currentOffset = 0; // Handle each file foreach (var file in archiveFiles) { // Get the file BundleFile_FileEntry entry = file.Entry; // Reset the offset array to always contain 1 item entry.Offsets = new ulong[] { entry.Offsets?.FirstOrDefault() ?? 0 }; // Set the count entry.OffsetsCount = (uint)entry.Offsets.Length; // Add to the generator fileGenerator.Add(entry, () => { // When reading the original file we need to use the old base offset uint newBaseOffset = data.BootHeader.BaseOffset; data.BootHeader.BaseOffset = oldBaseOffset; // Get the file bytes to write to the archive Stream fileStream = file.FileItem.GetFileData(generator).Stream; data.BootHeader.BaseOffset = newBaseOffset; // Set the offset entry.Offsets[0] = currentOffset; // Increase by the file size currentOffset += entry.ArchiveSize; // Invoke event OnWritingFileToArchive?.Invoke(this, new ValueEventArgs <ArchiveFileItem>(file.FileItem)); return(fileStream); }); } BinaryFile binaryFile = new StreamFile(Context, outputFileStream.Name, outputFileStream.Stream, leaveOpen: true); try { Context.AddFile(binaryFile); // Initialize the data data.Init(binaryFile.StartPointer); // Set the base offset data.RecalculateSize(); data.BootHeader.BaseOffset = (uint)data.Size; // Write the files WriteArchiveContent(data, outputFileStream.Stream, fileGenerator, Config.ShouldCompress(data.BootHeader)); outputFileStream.Stream.Position = 0; // Serialize the data FileFactory.Write(Context, binaryFile.FilePath, data); Logger.Info("The IPK archive has been repacked"); } finally { Context.RemoveFile(binaryFile); } }
/// <summary> /// Converts the file data from the specified format /// </summary> /// <param name="inputFormat">The format to convert from</param> /// <param name="outputFormat">The format to convert to</param> /// <param name="currentFileStream">The current file stream</param> /// <param name="inputStream">The input file data stream to convert from</param> /// <param name="outputStream">The output stream for the converted data</param> /// <param name="manager">The manager</param> public virtual void ConvertFrom(FileExtension inputFormat, FileExtension outputFormat, ArchiveFileStream currentFileStream, ArchiveFileStream inputStream, ArchiveFileStream outputStream, IArchiveDataManager manager) { ConvertFrom(inputFormat, GetMagickFormat(outputFormat), inputStream, outputStream); }
/// <summary> /// Exports the directory /// </summary> /// <param name="forceNativeFormat">Indicates if the native format should be forced</param> /// <param name="selectedFilesOnly">Indicates if only selected files in the current directory should be exported</param> /// <returns>The task</returns> public async Task ExportAsync(bool forceNativeFormat, bool selectedFilesOnly = false) { // Run as a load operation using (await Archive.LoadOperation.RunAsync()) { // Lock the access to the archive using (await Archive.ArchiveLock.LockAsync()) { // Get the output path DirectoryBrowserResult result = await Services.BrowseUI.BrowseDirectoryAsync(new DirectoryBrowserViewModel() { Title = Resources.Archive_ExportHeader }); if (result.CanceledByUser) { return; } // Make sure there isn't an existing file at the output path if ((result.SelectedDirectory + ExportDirName).FileExists) { await Services.MessageUI.DisplayMessageAsync(String.Format(Resources.Archive_ExportDirFileConflict, ExportDirName), MessageType.Error); return; } // Run as a task await Task.Run(async() => { // Get the manager IArchiveDataManager manager = Archive.Manager; // Save the selected format for each collection Dictionary <IArchiveFileType, FileExtension?> selectedFormats = new(); try { ArchiveDirectoryViewModel[] allDirs; if (selectedFilesOnly) { allDirs = new ArchiveDirectoryViewModel[] { this } } ; else { allDirs = this.GetAllChildren(true).ToArray(); } int fileIndex = 0; int filesCount = allDirs.SelectMany(x => x.Files).Count(x => !selectedFilesOnly || x.IsSelected); // Handle each directory foreach (ArchiveDirectoryViewModel item in allDirs) { // Get the directory path FileSystemPath path = result.SelectedDirectory + ExportDirName + item.FullPath.Remove(0, FullPath.Length).Trim(manager.PathSeparatorCharacter); // Create the directory Directory.CreateDirectory(path); // Save each file foreach (ArchiveFileViewModel file in item.Files.Where(x => !selectedFilesOnly || x.IsSelected)) { // Get the file stream using ArchiveFileStream fileStream = file.GetDecodedFileStream(); // Initialize the file without loading the thumbnail file.InitializeFile(fileStream, ArchiveFileViewModel.ThumbnailLoadMode.None); fileStream.SeekToBeginning(); // Check if the format has not been selected if (!forceNativeFormat && !selectedFormats.ContainsKey(file.FileType) && file.FileType is not ArchiveFileType_Default) { // Get the available extensions string[] ext = new string[] { Resources.Archive_Export_Format_Original }.Concat(file.FileType.ExportFormats.Select(x => x.FileExtensions)).ToArray(); // Have user select the format FileExtensionSelectionDialogResult extResult = await Services.UI.SelectFileExtensionAsync(new FileExtensionSelectionDialogViewModel(ext, String.Format(Resources.Archive_FileExtensionSelectionInfoHeader, file.FileType.TypeDisplayName))); // Since this operation can't be canceled we get the first format if (extResult.CanceledByUser) { extResult.SelectedFileFormat = ext.First(); } // Add the selected format FileExtension?e = extResult.SelectedFileFormat == ext.First() ? null : new FileExtension(extResult.SelectedFileFormat, multiple: true); selectedFormats.Add(file.FileType, e); } // Get the selected format FileExtension?format = forceNativeFormat || file.FileType is ArchiveFileType_Default ? null : selectedFormats[file.FileType]; // Get the final file name to use when exporting FileSystemPath exportFileName = format == null ? new FileSystemPath(file.FileName) : new FileSystemPath(file.FileName).ChangeFileExtension(format, true); Archive.SetDisplayStatus($"{String.Format(Resources.Archive_ExportingFileStatus, file.FileName)}" + $"{Environment.NewLine}{++fileIndex}/{filesCount}"); try { // Export the file file.ExportFile(path + exportFileName, fileStream, format); } catch (Exception ex) { // If the export failed for a native format we throw if (format == null) { throw; } Logger.Error(ex, "Exporting archive file {0}", file.FileName); // If the export failed and we tried converting it we instead export it as the native format // Start by setting the file in the error state, thus changing the type file.InitializeAsError(); // Seek to the beginning of the stream in case some bytes were read fileStream.SeekToBeginning(); // Export the file as the native format file.ExportFile(path + file.FileName, fileStream, null); } } } } catch (Exception ex) { Logger.Error(ex, "Exporting archive directory {0}", DisplayName); await Services.MessageUI.DisplayExceptionMessageAsync(ex, String.Format(Resources.Archive_ExportError, DisplayName)); return; } finally { Archive.SetDisplayStatus(String.Empty); } await Services.MessageUI.DisplaySuccessfulActionMessageAsync(Resources.Archive_ExportFilesSuccess); }); } } }
/// <summary> /// Converts the file data from the specified format /// </summary> /// <param name="inputFormat">The format to convert from</param> /// <param name="outputFormat">The format to convert to</param> /// <param name="currentFileStream">The current file stream</param> /// <param name="inputStream">The input file data stream to convert from</param> /// <param name="outputStream">The output stream for the converted data</param> /// <param name="manager">The manager</param> public virtual void ConvertFrom(FileExtension inputFormat, FileExtension outputFormat, ArchiveFileStream currentFileStream, ArchiveFileStream inputStream, ArchiveFileStream outputStream, IArchiveDataManager manager) { throw new NotSupportedException("Converting .wav files is not supported"); }
/// <summary> /// Imports files to the directory /// </summary> /// <returns>The task</returns> public async Task ImportAsync() { // Run as a load operation using (await Archive.LoadOperation.RunAsync()) { // Lock the access to the archive using (await Archive.ArchiveLock.LockAsync()) { // Get the directory DirectoryBrowserResult result = await Services.BrowseUI.BrowseDirectoryAsync(new DirectoryBrowserViewModel() { Title = Resources.Archive_ImportDirectoryHeader, }); if (result.CanceledByUser) { return; } // Run as a task await Task.Run(async() => { // Keep track of the number of files getting imported int imported = 0; try { // Enumerate each directory view model foreach (ArchiveDirectoryViewModel dir in this.GetAllChildren(true)) { // Enumerate each file foreach (ArchiveFileViewModel file in dir.Files) { // Get the file directory, relative to the selected directory FileSystemPath fileDir = result.SelectedDirectory + dir.FullPath.Remove(0, FullPath.Length).Trim(Path.DirectorySeparatorChar); if (!fileDir.DirectoryExists) { continue; } // Get the base file path FileSystemPath baseFilePath = fileDir + new FileSystemPath(file.FileName); // Get the file path, without an extension FileSystemPath filePath = baseFilePath.RemoveFileExtension(true); // Make sure there are potential file matches if (!Directory.GetFiles(fileDir, $"{filePath.Name}*", SearchOption.TopDirectoryOnly).Any()) { continue; } // Get the file stream using ArchiveFileStream fileStream = file.GetDecodedFileStream(); // Initialize the file without loading the thumbnail file.InitializeFile(fileStream, ArchiveFileViewModel.ThumbnailLoadMode.None); // Check if the base file exists without changing the extensions if (baseFilePath.FileExists) { // Import the file file.ImportFile(baseFilePath, false); imported++; continue; } // Attempt to find a file for each supported extension foreach (FileExtension ext in file.FileType.ImportFormats) { // Get the path FileSystemPath fullFilePath = filePath.ChangeFileExtension(ext); // Make sure the file exists if (!fullFilePath.FileExists) { continue; } // Import the file file.ImportFile(fullFilePath, true); imported++; // Break the loop break; } } } } catch (Exception ex) { Logger.Error(ex, "Importing archive directory {0}", DisplayName); await Services.MessageUI.DisplayExceptionMessageAsync(ex, Resources.Archive_ImportDir_Error); return; } // Make sure at least one file has been imported if (imported == 0) { await Services.MessageUI.DisplayMessageAsync(Resources.Archive_ImportNoFilesError, MessageType.Warning); } }); } } }
/// <summary> /// Writes the files to the archive /// </summary> /// <param name="generator">The generator</param> /// <param name="archive">The loaded archive data</param> /// <param name="outputFileStream">The file output stream for the archive</param> /// <param name="files">The files to include</param> public void WriteArchive(IDisposable?generator, object archive, ArchiveFileStream outputFileStream, IList <ArchiveFileItem> files) { Logger.Info("A CNT archive is being repacked..."); // Get the archive data var data = (CNT)archive; // Create the file generator using ArchiveFileGenerator <CNT_File> fileGenerator = new(); // Get files and entries var archiveFiles = files.Select(x => new { Entry = (CNT_File)x.ArchiveEntry, FileItem = x }).ToArray(); // Set files and directories data.Directories = files.Select(x => x.Directory).Distinct().Where(x => !x.IsNullOrWhiteSpace()).ToArray(); data.Files = archiveFiles.Select(x => x.Entry).ToArray(); // Set the directory indexes foreach (var file in archiveFiles) { // Set the directory index file.Entry.DirectoryIndex = file.FileItem.Directory == String.Empty ? -1 : data.Directories.FindItemIndex(x => x == file.FileItem.Directory); } BinaryFile binaryFile = new StreamFile(Context, outputFileStream.Name, outputFileStream.Stream, leaveOpen: true); try { Context.AddFile(binaryFile); // Initialize the data data.Init(binaryFile.StartPointer); // Set the current pointer position to the header size data.RecalculateSize(); uint pointer = (uint)data.Size; // Disable checksum data.IsChecksumUsed = false; // NOTE: We can't disable the XOR key entirely as that would disable it for the file bytes too, which would require them all to be decrypted // Reset XOR keys data.StringsXORKey = 0; // Load each file foreach (var file in archiveFiles) { // Get the file entry CNT_File entry = file.Entry; // Reset checksum and XOR key entry.FileChecksum = 0; entry.Pre_FileNameXORKey = 0; // Add to the generator fileGenerator.Add(entry, () => { // Get the file stream to write to the archive Stream fileStream = file.FileItem.GetFileData(generator).Stream; // Set the pointer entry.FileOffset = pointer; // Update the pointer by the file size pointer += entry.FileSize; // Invoke event OnWritingFileToArchive?.Invoke(this, new ValueEventArgs <ArchiveFileItem>(file.FileItem)); return(fileStream); }); } // Make sure we have a generator for each file if (fileGenerator.Count != data.Files.Length) { throw new Exception("The .cnt file can't be serialized without a file generator for each file"); } // Write the file contents foreach (CNT_File file in data.Files) { // Get the file stream using Stream fileStream = fileGenerator.GetFileStream(file); // Set the position to the pointer outputFileStream.Stream.Position = file.FileOffset; // Write the contents from the generator fileStream.CopyTo(outputFileStream.Stream); } outputFileStream.Stream.Position = 0; // Serialize the data FileFactory.Write(Context, binaryFile.FilePath, data); Logger.Info("The CNT archive has been repacked"); } finally { Context.RemoveFile(binaryFile); } }
/// <summary> /// Saves any pending changes to the archive and reloads it /// </summary> /// <returns>The task</returns> public async Task SaveAsync() { Logger.Info("The archive {0} is being repacked", DisplayName); // Run as a load operation using (await Archive.LoadOperation.RunAsync()) { // Lock the access to the archive using (await Archive.ArchiveLock.LockAsync()) { // Find the selected item path string?selectedDirAddr = ExplorerDialogViewModel.SelectedDir == null ? null : ExplorerDialogViewModel.GetDirectoryAddress(ExplorerDialogViewModel.SelectedDir); // Run as a task await Task.Run(async() => { // Stop file initialization if (ExplorerDialogViewModel.IsInitializingFiles) { ExplorerDialogViewModel.CancelInitializeFiles = true; } Archive.SetDisplayStatus(String.Format(Resources.Archive_RepackingStatus, DisplayName)); try { // Get a temporary file path to write to using TempFile tempOutputFile = new(false); // Create the file and get the stream using (ArchiveFileStream outputStream = new(File.Create(tempOutputFile.TempPath), tempOutputFile.TempPath.Name, true)) { // Write to the stream Manager.WriteArchive( generator: ArchiveFileGenerator, archive: ArchiveData ?? throw new Exception("Archive data has not been loaded"), outputFileStream: outputStream, files: this.GetAllChildren <ArchiveDirectoryViewModel>(true). SelectMany(x => x.Files). Select(x => x.FileData). ToArray()); } // Dispose the archive file stream ArchiveFileStream.Dispose(); ArchiveData = null; // If the operation succeeded, replace the archive file with the temporary output Services.File.MoveFile(tempOutputFile.TempPath, FilePath, true); // Re-open the file stream OpenFile(); } catch (Exception ex) { Logger.Error(ex, "Repacking archive {0}", DisplayName); await Services.MessageUI.DisplayExceptionMessageAsync(ex, Resources.Archive_RepackError); // Re-open the file stream if closed if (ArchiveFileStream.SafeFileHandle?.IsClosed != false) { OpenFile(); } } }); // Reload the archive LoadArchive(); // Load the previously selected directory if it still exists if (selectedDirAddr != null) { ExplorerDialogViewModel.LoadDirectory(selectedDirAddr); } Archive.SetDisplayStatus(String.Empty); } } }