using System; using System.IO; using static SabreTools.Data.Models.VPK.Constants; namespace SabreTools.Wrappers { public partial class VPK : WrapperBase { #region Descriptive Properties /// public override string DescriptionString => "Valve Package File (VPK)"; #endregion #region Extension Properties /// /// Array of archive filenames attached to the given VPK /// public string[] ArchiveFilenames { get { // Use the cached value, if it exists if (field is not null) return field; // If we don't have a source filename if (string.IsNullOrEmpty(Filename)) { field = []; return field; } // If the filename is not the right format string extension = Path.GetExtension(Filename).TrimStart('.'); string? directoryName = Path.GetDirectoryName(Filename); string fileName = directoryName is null ? Path.GetFileNameWithoutExtension(Filename) : Path.Combine(directoryName, Path.GetFileNameWithoutExtension(Filename)); if (fileName.Length < 3) { field = []; return field; } #if NETCOREAPP || NETSTANDARD2_1_OR_GREATER else if (fileName[^3..] != "dir") #else else if (fileName.Substring(fileName.Length - 3) != "dir") #endif { field = []; return field; } // Get the archive count ushort archiveCount = 0; foreach (var di in DirectoryItems) { if (di.DirectoryEntry.ArchiveIndex == HL_VPK_NO_ARCHIVE) continue; archiveCount = Math.Max(archiveCount, di.DirectoryEntry.ArchiveIndex); } // Build the list of archive filenames to populate field = new string[archiveCount]; // Loop through and create the archive filenames for (int i = 0; i < archiveCount; i++) { // We need 5 digits to print a short, but we already have 3 for dir. #if NETCOREAPP || NETSTANDARD2_1_OR_GREATER string archiveFileName = $"{fileName[..^3]}{i.ToString().PadLeft(3, '0')}.{extension}"; #else string archiveFileName = $"{fileName.Substring(0, fileName.Length - 3)}{i.ToString().PadLeft(3, '0')}.{extension}"; #endif field[i] = archiveFileName; } // Return the array return field; } } = null; /// public Data.Models.VPK.DirectoryItem[] DirectoryItems => Model.DirectoryItems; #endregion #region Constructors /// public VPK(Data.Models.VPK.File model, byte[] data) : base(model, data) { } /// public VPK(Data.Models.VPK.File model, byte[] data, int offset) : base(model, data, offset) { } /// public VPK(Data.Models.VPK.File model, byte[] data, int offset, int length) : base(model, data, offset, length) { } /// public VPK(Data.Models.VPK.File model, Stream data) : base(model, data) { } /// public VPK(Data.Models.VPK.File model, Stream data, long offset) : base(model, data, offset) { } /// public VPK(Data.Models.VPK.File model, Stream data, long offset, long length) : base(model, data, offset, length) { } #endregion #region Static Constructors /// /// Create a VPK from a byte array and offset /// /// Byte array representing the VPK /// Offset within the array to parse /// A VPK wrapper on success, null on failure public static VPK? Create(byte[]? data, int offset) { // If the data is invalid if (data is null || data.Length == 0) return null; // If the offset is out of bounds if (offset < 0 || offset >= data.Length) return null; // Create a memory stream and use that var dataStream = new MemoryStream(data, offset, data.Length - offset); return Create(dataStream); } /// /// Create a VPK from a Stream /// /// Stream representing the VPK /// A VPK wrapper on success, null on failure public static VPK? Create(Stream? data) { // If the data is invalid if (data is null || !data.CanRead) return null; try { // Cache the current offset long currentOffset = data.Position; var model = new Serialization.Readers.VPK().Deserialize(data); if (model is null) return null; return new VPK(model, data, currentOffset); } catch { return null; } } #endregion } }