From 5904756eb80299f86c93750939e6ee1d2734ec80 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 8 Sep 2023 21:58:44 -0400 Subject: [PATCH] Add and convert GCF --- Bytes/GCF.Deserializer.cs | 27 ++ Streams/GCF.Deserializer.cs | 746 ++++++++++++++++++++++++++++++++++++ Streams/GCF.Serializer.cs | 15 + 3 files changed, 788 insertions(+) create mode 100644 Bytes/GCF.Deserializer.cs create mode 100644 Streams/GCF.Deserializer.cs create mode 100644 Streams/GCF.Serializer.cs diff --git a/Bytes/GCF.Deserializer.cs b/Bytes/GCF.Deserializer.cs new file mode 100644 index 00000000..d7e4ba6e --- /dev/null +++ b/Bytes/GCF.Deserializer.cs @@ -0,0 +1,27 @@ +using System.IO; + +namespace SabreTools.Serialization.Bytes +{ + public partial class GCF : IByteSerializer + { + /// +#if NET48 + public Models.GCF.File Deserialize(byte[] data, int offset) +#else + public Models.GCF.File? Deserialize(byte[]? data, int offset) +#endif + { + // If the data is invalid + if (data == null) + return null; + + // If the offset is out of bounds + if (offset < 0 || offset >= data.Length) + return null; + + // Create a memory stream and parse that + MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset); + return new Streams.GCF().Deserialize(dataStream); + } + } +} \ No newline at end of file diff --git a/Streams/GCF.Deserializer.cs b/Streams/GCF.Deserializer.cs new file mode 100644 index 00000000..4e35c446 --- /dev/null +++ b/Streams/GCF.Deserializer.cs @@ -0,0 +1,746 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using SabreTools.IO; +using SabreTools.Models.GCF; + +namespace SabreTools.Serialization.Streams +{ + public partial class GCF : IStreamSerializer + { + /// +#if NET48 + public Models.GCF.File Deserialize(Stream data) +#else + public Models.GCF.File? Deserialize(Stream? data) +#endif + { + // If the data is invalid + if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead) + return null; + + // If the offset is out of bounds + if (data.Position < 0 || data.Position >= data.Length) + return null; + + // Cache the current offset + long initialOffset = data.Position; + + // Create a new Half-Life Game Cache to fill + var file = new Models.GCF.File(); + + #region Header + + // Try to parse the header + var header = ParseHeader(data); + if (header == null) + return null; + + // Set the game cache header + file.Header = header; + + #endregion + + #region Block Entry Header + + // Try to parse the block entry header + var blockEntryHeader = ParseBlockEntryHeader(data); + if (blockEntryHeader == null) + return null; + + // Set the game cache block entry header + file.BlockEntryHeader = blockEntryHeader; + + #endregion + + #region Block Entries + + // Create the block entry array + file.BlockEntries = new BlockEntry[blockEntryHeader.BlockCount]; + + // Try to parse the block entries + for (int i = 0; i < blockEntryHeader.BlockCount; i++) + { + var blockEntry = ParseBlockEntry(data); + file.BlockEntries[i] = blockEntry; + } + + #endregion + + #region Fragmentation Map Header + + // Try to parse the fragmentation map header + var fragmentationMapHeader = ParseFragmentationMapHeader(data); + if (fragmentationMapHeader == null) + return null; + + // Set the game cache fragmentation map header + file.FragmentationMapHeader = fragmentationMapHeader; + + #endregion + + #region Fragmentation Maps + + // Create the fragmentation map array + file.FragmentationMaps = new FragmentationMap[fragmentationMapHeader.BlockCount]; + + // Try to parse the fragmentation maps + for (int i = 0; i < fragmentationMapHeader.BlockCount; i++) + { + var fragmentationMap = ParseFragmentationMap(data); + file.FragmentationMaps[i] = fragmentationMap; + } + + #endregion + + #region Block Entry Map Header + + if (header.MinorVersion < 6) + { + // Try to parse the block entry map header + var blockEntryMapHeader = ParseBlockEntryMapHeader(data); + if (blockEntryMapHeader == null) + return null; + + // Set the game cache block entry map header + file.BlockEntryMapHeader = blockEntryMapHeader; + } + + #endregion + + #region Block Entry Maps + + if (header.MinorVersion < 6) + { + // Create the block entry map array + file.BlockEntryMaps = new BlockEntryMap[file.BlockEntryMapHeader.BlockCount]; + + // Try to parse the block entry maps + for (int i = 0; i < file.BlockEntryMapHeader.BlockCount; i++) + { + var blockEntryMap = ParseBlockEntryMap(data); + file.BlockEntryMaps[i] = blockEntryMap; + } + } + + #endregion + + // Cache the current offset + initialOffset = data.Position; + + #region Directory Header + + // Try to parse the directory header + var directoryHeader = ParseDirectoryHeader(data); + if (directoryHeader == null) + return null; + + // Set the game cache directory header + file.DirectoryHeader = directoryHeader; + + #endregion + + #region Directory Entries + + // Create the directory entry array + file.DirectoryEntries = new DirectoryEntry[directoryHeader.ItemCount]; + + // Try to parse the directory entries + for (int i = 0; i < directoryHeader.ItemCount; i++) + { + var directoryEntry = ParseDirectoryEntry(data); + file.DirectoryEntries[i] = directoryEntry; + } + + #endregion + + #region Directory Names + + if (directoryHeader.NameSize > 0) + { + // Get the current offset for adjustment + long directoryNamesStart = data.Position; + + // Get the ending offset + long directoryNamesEnd = data.Position + directoryHeader.NameSize; + + // Create the string dictionary + file.DirectoryNames = new Dictionary(); + + // Loop and read the null-terminated strings + while (data.Position < directoryNamesEnd) + { + long nameOffset = data.Position - directoryNamesStart; + string directoryName = data.ReadString(Encoding.ASCII); + if (data.Position > directoryNamesEnd) + { + data.Seek(-directoryName.Length, SeekOrigin.Current); + byte[] endingData = data.ReadBytes((int)(directoryNamesEnd - data.Position)); + if (endingData != null) + directoryName = Encoding.ASCII.GetString(endingData); + else + directoryName = null; + } + + file.DirectoryNames[nameOffset] = directoryName; + } + + // Loop and assign to entries + foreach (var directoryEntry in file.DirectoryEntries) + { + directoryEntry.Name = file.DirectoryNames[directoryEntry.NameOffset]; + } + } + + #endregion + + #region Directory Info 1 Entries + + // Create the directory info 1 entry array + file.DirectoryInfo1Entries = new DirectoryInfo1Entry[directoryHeader.Info1Count]; + + // Try to parse the directory info 1 entries + for (int i = 0; i < directoryHeader.Info1Count; i++) + { + var directoryInfo1Entry = ParseDirectoryInfo1Entry(data); + file.DirectoryInfo1Entries[i] = directoryInfo1Entry; + } + + #endregion + + #region Directory Info 2 Entries + + // Create the directory info 2 entry array + file.DirectoryInfo2Entries = new DirectoryInfo2Entry[directoryHeader.ItemCount]; + + // Try to parse the directory info 2 entries + for (int i = 0; i < directoryHeader.ItemCount; i++) + { + var directoryInfo2Entry = ParseDirectoryInfo2Entry(data); + file.DirectoryInfo2Entries[i] = directoryInfo2Entry; + } + + #endregion + + #region Directory Copy Entries + + // Create the directory copy entry array + file.DirectoryCopyEntries = new DirectoryCopyEntry[directoryHeader.CopyCount]; + + // Try to parse the directory copy entries + for (int i = 0; i < directoryHeader.CopyCount; i++) + { + var directoryCopyEntry = ParseDirectoryCopyEntry(data); + file.DirectoryCopyEntries[i] = directoryCopyEntry; + } + + #endregion + + #region Directory Local Entries + + // Create the directory local entry array + file.DirectoryLocalEntries = new DirectoryLocalEntry[directoryHeader.LocalCount]; + + // Try to parse the directory local entries + for (int i = 0; i < directoryHeader.LocalCount; i++) + { + var directoryLocalEntry = ParseDirectoryLocalEntry(data); + file.DirectoryLocalEntries[i] = directoryLocalEntry; + } + + #endregion + + // Seek to end of directory section, just in case + data.Seek(initialOffset + directoryHeader.DirectorySize, SeekOrigin.Begin); + + #region Directory Map Header + + if (header.MinorVersion >= 5) + { + // Try to parse the directory map header + var directoryMapHeader = ParseDirectoryMapHeader(data); + if (directoryMapHeader == null) + return null; + + // Set the game cache directory map header + file.DirectoryMapHeader = directoryMapHeader; + } + + #endregion + + #region Directory Map Entries + + // Create the directory map entry array + file.DirectoryMapEntries = new DirectoryMapEntry[directoryHeader.ItemCount]; + + // Try to parse the directory map entries + for (int i = 0; i < directoryHeader.ItemCount; i++) + { + var directoryMapEntry = ParseDirectoryMapEntry(data); + file.DirectoryMapEntries[i] = directoryMapEntry; + } + + #endregion + + #region Checksum Header + + // Try to parse the checksum header + var checksumHeader = ParseChecksumHeader(data); + if (checksumHeader == null) + return null; + + // Set the game cache checksum header + file.ChecksumHeader = checksumHeader; + + #endregion + + // Cache the current offset + initialOffset = data.Position; + + #region Checksum Map Header + + // Try to parse the checksum map header + var checksumMapHeader = ParseChecksumMapHeader(data); + if (checksumMapHeader == null) + return null; + + // Set the game cache checksum map header + file.ChecksumMapHeader = checksumMapHeader; + + #endregion + + #region Checksum Map Entries + + // Create the checksum map entry array + file.ChecksumMapEntries = new ChecksumMapEntry[checksumMapHeader.ItemCount]; + + // Try to parse the checksum map entries + for (int i = 0; i < checksumMapHeader.ItemCount; i++) + { + var checksumMapEntry = ParseChecksumMapEntry(data); + file.ChecksumMapEntries[i] = checksumMapEntry; + } + + #endregion + + #region Checksum Entries + + // Create the checksum entry array + file.ChecksumEntries = new ChecksumEntry[checksumMapHeader.ChecksumCount]; + + // Try to parse the checksum entries + for (int i = 0; i < checksumMapHeader.ChecksumCount; i++) + { + var checksumEntry = ParseChecksumEntry(data); + file.ChecksumEntries[i] = checksumEntry; + } + + #endregion + + // Seek to end of checksum section, just in case + data.Seek(initialOffset + checksumHeader.ChecksumSize, SeekOrigin.Begin); + + #region Data Block Header + + // Try to parse the data block header + var dataBlockHeader = ParseDataBlockHeader(data, header.MinorVersion); + if (dataBlockHeader == null) + return null; + + // Set the game cache data block header + file.DataBlockHeader = dataBlockHeader; + + #endregion + + return file; + } + + /// + /// Parse a Stream into a Half-Life Game Cache header + /// + /// Stream to parse + /// Filled Half-Life Game Cache on success, null on error + private static Header ParseHeader(Stream data) + { + // TODO: Use marshalling here instead of building + Header header = new Header(); + + header.Dummy0 = data.ReadUInt32(); + if (header.Dummy0 != 0x00000001) + return null; + + header.MajorVersion = data.ReadUInt32(); + if (header.MajorVersion != 0x00000001) + return null; + + header.MinorVersion = data.ReadUInt32(); + if (header.MinorVersion != 3 && header.MinorVersion != 5 && header.MinorVersion != 6) + return null; + + header.CacheID = data.ReadUInt32(); + header.LastVersionPlayed = data.ReadUInt32(); + header.Dummy1 = data.ReadUInt32(); + header.Dummy2 = data.ReadUInt32(); + header.FileSize = data.ReadUInt32(); + header.BlockSize = data.ReadUInt32(); + header.BlockCount = data.ReadUInt32(); + header.Dummy3 = data.ReadUInt32(); + + return header; + } + + /// + /// Parse a Stream into a Half-Life Game Cache block entry header + /// + /// Stream to parse + /// Filled Half-Life Game Cache block entry header on success, null on error + private static BlockEntryHeader ParseBlockEntryHeader(Stream data) + { + // TODO: Use marshalling here instead of building + BlockEntryHeader blockEntryHeader = new BlockEntryHeader(); + + blockEntryHeader.BlockCount = data.ReadUInt32(); + blockEntryHeader.BlocksUsed = data.ReadUInt32(); + blockEntryHeader.Dummy0 = data.ReadUInt32(); + blockEntryHeader.Dummy1 = data.ReadUInt32(); + blockEntryHeader.Dummy2 = data.ReadUInt32(); + blockEntryHeader.Dummy3 = data.ReadUInt32(); + blockEntryHeader.Dummy4 = data.ReadUInt32(); + blockEntryHeader.Checksum = data.ReadUInt32(); + + return blockEntryHeader; + } + + /// + /// Parse a Stream into a Half-Life Game Cache block entry + /// + /// Stream to parse + /// Filled Half-Life Game Cache block entry on success, null on error + private static BlockEntry ParseBlockEntry(Stream data) + { + // TODO: Use marshalling here instead of building + BlockEntry blockEntry = new BlockEntry(); + + blockEntry.EntryFlags = data.ReadUInt32(); + blockEntry.FileDataOffset = data.ReadUInt32(); + blockEntry.FileDataSize = data.ReadUInt32(); + blockEntry.FirstDataBlockIndex = data.ReadUInt32(); + blockEntry.NextBlockEntryIndex = data.ReadUInt32(); + blockEntry.PreviousBlockEntryIndex = data.ReadUInt32(); + blockEntry.DirectoryIndex = data.ReadUInt32(); + + return blockEntry; + } + + /// + /// Parse a Stream into a Half-Life Game Cache fragmentation map header + /// + /// Stream to parse + /// Filled Half-Life Game Cache fragmentation map header on success, null on error + private static FragmentationMapHeader ParseFragmentationMapHeader(Stream data) + { + // TODO: Use marshalling here instead of building + FragmentationMapHeader fragmentationMapHeader = new FragmentationMapHeader(); + + fragmentationMapHeader.BlockCount = data.ReadUInt32(); + fragmentationMapHeader.FirstUnusedEntry = data.ReadUInt32(); + fragmentationMapHeader.Terminator = data.ReadUInt32(); + fragmentationMapHeader.Checksum = data.ReadUInt32(); + + return fragmentationMapHeader; + } + + /// + /// Parse a Stream into a Half-Life Game Cache fragmentation map + /// + /// Stream to parse + /// Filled Half-Life Game Cache fragmentation map on success, null on error + private static FragmentationMap ParseFragmentationMap(Stream data) + { + // TODO: Use marshalling here instead of building + FragmentationMap fragmentationMap = new FragmentationMap(); + + fragmentationMap.NextDataBlockIndex = data.ReadUInt32(); + + return fragmentationMap; + } + + /// + /// Parse a Stream into a Half-Life Game Cache block entry map header + /// + /// Stream to parse + /// Filled Half-Life Game Cache block entry map header on success, null on error + private static BlockEntryMapHeader ParseBlockEntryMapHeader(Stream data) + { + // TODO: Use marshalling here instead of building + BlockEntryMapHeader blockEntryMapHeader = new BlockEntryMapHeader(); + + blockEntryMapHeader.BlockCount = data.ReadUInt32(); + blockEntryMapHeader.FirstBlockEntryIndex = data.ReadUInt32(); + blockEntryMapHeader.LastBlockEntryIndex = data.ReadUInt32(); + blockEntryMapHeader.Dummy0 = data.ReadUInt32(); + blockEntryMapHeader.Checksum = data.ReadUInt32(); + + return blockEntryMapHeader; + } + + /// + /// Parse a Stream into a Half-Life Game Cache block entry map + /// + /// Stream to parse + /// Filled Half-Life Game Cache block entry map on success, null on error + private static BlockEntryMap ParseBlockEntryMap(Stream data) + { + // TODO: Use marshalling here instead of building + BlockEntryMap blockEntryMap = new BlockEntryMap(); + + blockEntryMap.PreviousBlockEntryIndex = data.ReadUInt32(); + blockEntryMap.NextBlockEntryIndex = data.ReadUInt32(); + + return blockEntryMap; + } + + /// + /// Parse a Stream into a Half-Life Game Cache directory header + /// + /// Stream to parse + /// Filled Half-Life Game Cache directory header on success, null on error + private static DirectoryHeader ParseDirectoryHeader(Stream data) + { + // TODO: Use marshalling here instead of building + DirectoryHeader directoryHeader = new DirectoryHeader(); + + directoryHeader.Dummy0 = data.ReadUInt32(); + directoryHeader.CacheID = data.ReadUInt32(); + directoryHeader.LastVersionPlayed = data.ReadUInt32(); + directoryHeader.ItemCount = data.ReadUInt32(); + directoryHeader.FileCount = data.ReadUInt32(); + directoryHeader.Dummy1 = data.ReadUInt32(); + directoryHeader.DirectorySize = data.ReadUInt32(); + directoryHeader.NameSize = data.ReadUInt32(); + directoryHeader.Info1Count = data.ReadUInt32(); + directoryHeader.CopyCount = data.ReadUInt32(); + directoryHeader.LocalCount = data.ReadUInt32(); + directoryHeader.Dummy2 = data.ReadUInt32(); + directoryHeader.Dummy3 = data.ReadUInt32(); + directoryHeader.Checksum = data.ReadUInt32(); + + return directoryHeader; + } + + /// + /// Parse a Stream into a Half-Life Game Cache directory entry + /// + /// Stream to parse + /// Filled Half-Life Game Cache directory entry on success, null on error + private static DirectoryEntry ParseDirectoryEntry(Stream data) + { + // TODO: Use marshalling here instead of building + DirectoryEntry directoryEntry = new DirectoryEntry(); + + directoryEntry.NameOffset = data.ReadUInt32(); + directoryEntry.ItemSize = data.ReadUInt32(); + directoryEntry.ChecksumIndex = data.ReadUInt32(); + directoryEntry.DirectoryFlags = (HL_GCF_FLAG)data.ReadUInt32(); + directoryEntry.ParentIndex = data.ReadUInt32(); + directoryEntry.NextIndex = data.ReadUInt32(); + directoryEntry.FirstIndex = data.ReadUInt32(); + + return directoryEntry; + } + + /// + /// Parse a Stream into a Half-Life Game Cache directory info 1 entry + /// + /// Stream to parse + /// Filled Half-Life Game Cache directory info 1 entry on success, null on error + private static DirectoryInfo1Entry ParseDirectoryInfo1Entry(Stream data) + { + // TODO: Use marshalling here instead of building + DirectoryInfo1Entry directoryInfo1Entry = new DirectoryInfo1Entry(); + + directoryInfo1Entry.Dummy0 = data.ReadUInt32(); + + return directoryInfo1Entry; + } + + /// + /// Parse a Stream into a Half-Life Game Cache directory info 2 entry + /// + /// Stream to parse + /// Filled Half-Life Game Cache directory info 2 entry on success, null on error + private static DirectoryInfo2Entry ParseDirectoryInfo2Entry(Stream data) + { + // TODO: Use marshalling here instead of building + DirectoryInfo2Entry directoryInfo2Entry = new DirectoryInfo2Entry(); + + directoryInfo2Entry.Dummy0 = data.ReadUInt32(); + + return directoryInfo2Entry; + } + + /// + /// Parse a Stream into a Half-Life Game Cache directory copy entry + /// + /// Stream to parse + /// Filled Half-Life Game Cache directory copy entry on success, null on error + private static DirectoryCopyEntry ParseDirectoryCopyEntry(Stream data) + { + // TODO: Use marshalling here instead of building + DirectoryCopyEntry directoryCopyEntry = new DirectoryCopyEntry(); + + directoryCopyEntry.DirectoryIndex = data.ReadUInt32(); + + return directoryCopyEntry; + } + + /// + /// Parse a Stream into a Half-Life Game Cache directory local entry + /// + /// Stream to parse + /// Filled Half-Life Game Cache directory local entry on success, null on error + private static DirectoryLocalEntry ParseDirectoryLocalEntry(Stream data) + { + // TODO: Use marshalling here instead of building + DirectoryLocalEntry directoryLocalEntry = new DirectoryLocalEntry(); + + directoryLocalEntry.DirectoryIndex = data.ReadUInt32(); + + return directoryLocalEntry; + } + + /// + /// Parse a Stream into a Half-Life Game Cache directory map header + /// + /// Stream to parse + /// Filled Half-Life Game Cache directory map header on success, null on error + private static DirectoryMapHeader ParseDirectoryMapHeader(Stream data) + { + // TODO: Use marshalling here instead of building + DirectoryMapHeader directoryMapHeader = new DirectoryMapHeader(); + + directoryMapHeader.Dummy0 = data.ReadUInt32(); + if (directoryMapHeader.Dummy0 != 0x00000001) + return null; + + directoryMapHeader.Dummy1 = data.ReadUInt32(); + if (directoryMapHeader.Dummy1 != 0x00000000) + return null; + + return directoryMapHeader; + } + + /// + /// Parse a Stream into a Half-Life Game Cache directory map entry + /// + /// Stream to parse + /// Filled Half-Life Game Cache directory map entry on success, null on error + private static DirectoryMapEntry ParseDirectoryMapEntry(Stream data) + { + // TODO: Use marshalling here instead of building + DirectoryMapEntry directoryMapEntry = new DirectoryMapEntry(); + + directoryMapEntry.FirstBlockIndex = data.ReadUInt32(); + + return directoryMapEntry; + } + + /// + /// Parse a Stream into a Half-Life Game Cache checksum header + /// + /// Stream to parse + /// Filled Half-Life Game Cache checksum header on success, null on error + private static ChecksumHeader ParseChecksumHeader(Stream data) + { + // TODO: Use marshalling here instead of building + ChecksumHeader checksumHeader = new ChecksumHeader(); + + checksumHeader.Dummy0 = data.ReadUInt32(); + if (checksumHeader.Dummy0 != 0x00000001) + return null; + + checksumHeader.ChecksumSize = data.ReadUInt32(); + + return checksumHeader; + } + + /// + /// Parse a Stream into a Half-Life Game Cache checksum map header + /// + /// Stream to parse + /// Filled Half-Life Game Cache checksum map header on success, null on error + private static ChecksumMapHeader ParseChecksumMapHeader(Stream data) + { + // TODO: Use marshalling here instead of building + ChecksumMapHeader checksumMapHeader = new ChecksumMapHeader(); + + checksumMapHeader.Dummy0 = data.ReadUInt32(); + if (checksumMapHeader.Dummy0 != 0x14893721) + return null; + + checksumMapHeader.Dummy1 = data.ReadUInt32(); + if (checksumMapHeader.Dummy1 != 0x00000001) + return null; + + checksumMapHeader.ItemCount = data.ReadUInt32(); + checksumMapHeader.ChecksumCount = data.ReadUInt32(); + + return checksumMapHeader; + } + + /// + /// Parse a Stream into a Half-Life Game Cache checksum map entry + /// + /// Stream to parse + /// Filled Half-Life Game Cache checksum map entry on success, null on error + private static ChecksumMapEntry ParseChecksumMapEntry(Stream data) + { + // TODO: Use marshalling here instead of building + ChecksumMapEntry checksumMapEntry = new ChecksumMapEntry(); + + checksumMapEntry.ChecksumCount = data.ReadUInt32(); + checksumMapEntry.FirstChecksumIndex = data.ReadUInt32(); + + return checksumMapEntry; + } + + /// + /// Parse a Stream into a Half-Life Game Cache checksum entry + /// + /// Stream to parse + /// Filled Half-Life Game Cache checksum entry on success, null on error + private static ChecksumEntry ParseChecksumEntry(Stream data) + { + // TODO: Use marshalling here instead of building + ChecksumEntry checksumEntry = new ChecksumEntry(); + + checksumEntry.Checksum = data.ReadUInt32(); + + return checksumEntry; + } + + /// + /// Parse a Stream into a Half-Life Game Cache data block header + /// + /// Stream to parse + /// Minor version field from the header + /// Filled Half-Life Game Cache data block header on success, null on error + private static DataBlockHeader ParseDataBlockHeader(Stream data, uint minorVersion) + { + // TODO: Use marshalling here instead of building + DataBlockHeader dataBlockHeader = new DataBlockHeader(); + + // In version 3 the DataBlockHeader is missing the LastVersionPlayed field. + if (minorVersion >= 5) + dataBlockHeader.LastVersionPlayed = data.ReadUInt32(); + + dataBlockHeader.BlockCount = data.ReadUInt32(); + dataBlockHeader.BlockSize = data.ReadUInt32(); + dataBlockHeader.FirstBlockOffset = data.ReadUInt32(); + dataBlockHeader.BlocksUsed = data.ReadUInt32(); + dataBlockHeader.Checksum = data.ReadUInt32(); + + return dataBlockHeader; + } + } +} \ No newline at end of file diff --git a/Streams/GCF.Serializer.cs b/Streams/GCF.Serializer.cs new file mode 100644 index 00000000..0b812aee --- /dev/null +++ b/Streams/GCF.Serializer.cs @@ -0,0 +1,15 @@ +using System; +using System.IO; + +namespace SabreTools.Serialization.Streams +{ + public partial class GCF : IStreamSerializer + { + /// +#if NET48 + public Stream Serialize(Models.GCF.File obj) => throw new NotImplementedException(); +#else + public Stream? Serialize(Models.GCF.File? obj) => throw new NotImplementedException(); +#endif + } +} \ No newline at end of file