diff --git a/BurnOutSharp/External/libgsf/GsfMSOleImpl.cs b/BurnOutSharp/External/libgsf/GsfMSOleImpl.cs index e557c47f..5453f6fd 100644 --- a/BurnOutSharp/External/libgsf/GsfMSOleImpl.cs +++ b/BurnOutSharp/External/libgsf/GsfMSOleImpl.cs @@ -109,6 +109,9 @@ namespace LibGSF #region Properties + /// + /// MS-OLE header signature + /// /// 0x00 public byte[] SIGNATURE { get; set; } @@ -136,9 +139,15 @@ namespace LibGSF /// 0x1C public ushort BYTE_ORDER { get; set; } + /// + /// Number of bits to shift to get a big block size + /// /// 0x1E public ushort BB_SHIFT { get; set; } + /// + /// Number of bits to shift to get a small block size + /// /// 0x20 public ushort SB_SHIFT { get; set; } @@ -150,10 +159,16 @@ namespace LibGSF /// 0x28 public uint CSECTDIR { get; set; } - + + /// + /// Reported number of big block BATs in the file + /// /// 0x2C public uint NUM_BAT { get; set; } + /// + /// Directory entry start offset + /// /// 0x30 public uint DIRENT_START { get; set; } @@ -164,23 +179,38 @@ namespace LibGSF public uint TRANSACTING_SIGNATURE { get; set; } /// - /// Transition between small and big blocks + /// File size transition between small and big blocks /// /// 0x38 public uint THRESHOLD { get; set; } + /// + /// Offset where small block BATs start + /// /// 0x3C public uint SBAT_START { get; set; } + /// + /// Reported number of small block BATs in the file + /// /// 0x40 public uint NUM_SBAT { get; set; } + /// + /// Block ID of the first MetaBAT block + /// /// 0x44 public uint METABAT_BLOCK { get; set; } + /// + /// Number of MetaBAT blocks in the file + /// /// 0x48 public uint NUM_METABAT { get; set; } + /// + /// Block ID of the first BAT block + /// /// 0x4C public uint START_BAT { get; set; } @@ -193,10 +223,16 @@ namespace LibGSF /// public bool LITTLE_ENDIAN => BYTE_ORDER == 0xFFFE; + /// + /// Size of a big block, in bytes + /// public int BB_SIZE => 1 << BB_SHIFT; public int BB_FILTER => BB_SIZE << 1; + /// + /// Size of a small block, in bytes + /// public int SB_SIZE => 1 << SB_SHIFT; public int SB_FILTER => SB_SIZE << 1; @@ -340,12 +376,24 @@ namespace LibGSF { #region Constants + /// + /// Maximum length of the entry name including the null terminator, in bytes + /// public const int DIRENT_MAX_NAME_SIZE = 0x40; + /// + /// Size of the non-name parts of directory entry header, in bytes + /// public const int DIRENT_DETAILS_SIZE = 0x40; + /// + /// Total size of the directory entry header + /// public const int DIRENT_SIZE = (DIRENT_MAX_NAME_SIZE + DIRENT_DETAILS_SIZE); + /// + /// Magic number indicating the termination of a node + /// public const uint DIRENT_MAGIC_END = 0xffffffff; #region Offsets @@ -384,27 +432,42 @@ namespace LibGSF #region Properties + /// + /// Name of the entry as a byte array + /// /// 0x00 public byte[] NAME { get; set; } /// - /// Length in bytes incl 0 terminator + /// Length of the entry name including the null terminator, in bytes /// /// 0x40 public ushort NAME_LEN { get; set; } + /// + /// Indicates the type of directory entry + /// /// 0x42 public DIRENT_TYPE TYPE_FLAG { get; set; } /// 0x43 public byte COLOR { get; set; } + /// + /// Index of the previous item in the linked list, DIRENT_MAGIC_END if none + /// /// 0x44 public uint PREV { get; set; } + /// + /// Index of the next item in the linked list, DIRENT_MAGIC_END if none + /// /// 0x48 public uint NEXT { get; set; } + /// + /// Index of the first child directory entry, DIRENT_MAGIC_END if none + /// /// 0x4C public uint CHILD { get; set; } @@ -421,20 +484,26 @@ namespace LibGSF public uint USERFLAGS { get; set; } /// - /// For files + /// Entry creation time; For files /// /// 0x64 public ulong CREATE_TIME { get; set; } /// - /// For files + /// Entry modification time; For files /// /// 0x6C public ulong MODIFY_TIME { get; set; } + /// + /// Index of the first block that contains this directory entry + /// /// 0x74 public uint FIRSTBLOCK { get; set; } + /// + /// Size of the file contained within this directory entry + /// /// 0x78 public uint FILE_SIZE { get; set; } diff --git a/BurnOutSharp/External/libgsf/Input/GsfInfileMSOle.cs b/BurnOutSharp/External/libgsf/Input/GsfInfileMSOle.cs index a8384e8f..36659ad6 100644 --- a/BurnOutSharp/External/libgsf/Input/GsfInfileMSOle.cs +++ b/BurnOutSharp/External/libgsf/Input/GsfInfileMSOle.cs @@ -32,8 +32,7 @@ using static LibGSF.GsfUtils; namespace LibGSF.Input { - // TODO: Can this be made internal? - public class MSOleBAT + internal class MSOleBAT { #region Properties @@ -65,9 +64,8 @@ namespace LibGSF.Input /// The first block in the list. /// Where to store the result. /// True on error. - public static bool Create(MSOleBAT metabat, uint block, out MSOleBAT res) + public static bool Create(in MSOleBAT metabat, uint block, out MSOleBAT res) { - // NOTE : Only use size as a suggestion, sometimes it is wrong List bat = new List(); byte[] used = new byte[1 + metabat.NumBlocks / 8]; @@ -98,11 +96,15 @@ namespace LibGSF.Input #endregion } - // TODO: Can this be made internal? - public class MSOleDirent + internal class MSOleDirent { #region Properties + /// + /// Internal representation of the MS-OLE directory entry header + /// + public MSOleDirectoryEntry Header { get; set; } + public GsfMSOleSortingKey Key { get; set; } public uint Index { get; set; } @@ -111,11 +113,6 @@ namespace LibGSF.Input public List Children { get; set; } = new List(); - /// - /// Internal representation of the MS-OLE directory entry header - /// - internal MSOleDirectoryEntry Header { get; set; } - #endregion #region Functions @@ -136,15 +133,24 @@ namespace LibGSF.Input #endregion } - // TODO: Can this be made internal? - public class MSOleInfo + internal class MSOleInfo { #region Properties + /// + /// Internal representation of the MS-OLE header + /// + public MSOleHeader Header { get; set; } + public MSOleBAT BigBlockBat { get; set; } public MSOleBAT SmallBlockBat { get; set; } + /// + /// MetaBAT for all BATs in the file + /// + public uint[] MetaBAT { get; set; } + /// /// Maximum number of blocks derived from total input length and block size /// @@ -156,11 +162,6 @@ namespace LibGSF.Input public int RefCount { get; set; } - /// - /// Internal representation of the MS-OLE header - /// - internal MSOleHeader Header { get; set; } - #endregion #region Functions @@ -170,6 +171,8 @@ namespace LibGSF.Input if (RefCount-- != 1) return; + Header = null; + if (RootDir != null) { RootDir.Free(); @@ -178,8 +181,6 @@ namespace LibGSF.Input if (SmallBlockFile != null) SmallBlockFile = null; - - Header = null; } public MSOleInfo Ref() @@ -195,18 +196,18 @@ namespace LibGSF.Input { #region Properties - public GsfInput Input { get; private set; } = null; + internal GsfInput Input { get; private set; } = null; - public MSOleInfo Info { get; private set; } = null; + internal MSOleInfo Info { get; private set; } = null; - public MSOleDirent DirectoryEntry { get; private set; } + internal MSOleDirent DirectoryEntry { get; private set; } - public MSOleBAT Bat { get; private set; } + internal MSOleBAT Bat { get; private set; } - public long CurBlock { get; private set; } = BAT_MAGIC_UNUSED; + internal long CurBlock { get; private set; } = BAT_MAGIC_UNUSED; /// Actually `{ byte[] Buf, long BufSize }` - public byte[] Stream { get; private set; } + internal byte[] Stream { get; private set; } #endregion @@ -251,8 +252,7 @@ namespace LibGSF.Input /// ~GsfInfileMSOle() { - if (Input != null) - Input = null; + Input = null; if (Info != null && Info.SmallBlockFile != this) { @@ -320,14 +320,11 @@ namespace LibGSF.Input optional_buffer_ptr = 0; } - int ptr = optional_buffer_ptr; // optional_buffer[0] + int ptr = optional_buffer_ptr; // optional_buffer[optional_buffer_ptr] int count; for (i = first_block; i <= last_block; i++, ptr += count, num_bytes -= count) { - count = (int)(Info.Header.BB_SIZE - offset); - if (count > num_bytes) - count = num_bytes; - + count = (int)Math.Min(Info.Header.BB_SIZE - offset, num_bytes); if (!SeekBlock(Bat.Blocks[i], offset)) return null; @@ -450,9 +447,9 @@ namespace LibGSF.Input /// either from the OLE header, or a meta-bat block. /// /// A pointer to the element after the last position filled - private int? ReadMetabat(uint[] bats, int batsPtr, uint max_bat, uint[] metabat, uint metabat_end) + private int? ReadMetabat(uint[] bats, int batsPtr, uint max_bat, in uint[] metabat, int metabatPtr, in uint metabat_end) { - for (int metabatPtr = 0; metabatPtr < metabat_end; metabatPtr++) + for (; metabatPtr < metabat_end; metabatPtr++) { if (metabat[metabatPtr] != BAT_MAGIC_UNUSED) { @@ -477,6 +474,7 @@ namespace LibGSF.Input // 'unused' entries in the metabat. Let's assume that // corresponds to lots of unused blocks // http://bugzilla.gnome.org/show_bug.cgi?id=336858 + uint i = (uint)(Info.Header.BB_SIZE / BAT_INDEX_SIZE); while (i-- > 0) { @@ -491,7 +489,7 @@ namespace LibGSF.Input /// /// Copy some some raw data into an array of uint. /// - private static void GetUnsignedInts(uint[] dst, ref int dstPtr, byte[] src, int srcPtr, int num_bytes) + private static void GetUnsignedInts(uint[] dst, int dstPtr, in byte[] src, int srcPtr, int num_bytes) { for (; (num_bytes -= BAT_INDEX_SIZE) >= 0; srcPtr += BAT_INDEX_SIZE) { @@ -510,8 +508,7 @@ namespace LibGSF.Input return null; // Avoid creating a circular reference - if (Info.SmallBlockFile is GsfInfileMSOle) - (Info.SmallBlockFile as GsfInfileMSOle).Info.Unref(); + (Info.SmallBlockFile as GsfInfileMSOle)?.Info?.Unref(); if (Info.SmallBlockBat.Blocks != null) return null; @@ -520,15 +517,13 @@ namespace LibGSF.Input return null; Info.SmallBlockBat.Blocks = new uint[meta_sbat.NumBlocks * (Info.Header.BB_SIZE / BAT_INDEX_SIZE)]; - ReadMetabat(Info.SmallBlockBat.Blocks, 0, Info.SmallBlockBat.NumBlocks, meta_sbat.Blocks, meta_sbat.NumBlocks); + ReadMetabat(Info.SmallBlockBat.Blocks, 0, Info.SmallBlockBat.NumBlocks, meta_sbat.Blocks, 0, meta_sbat.NumBlocks); return Info.SmallBlockFile; } private static int DirectoryEntryCompare(MSOleDirent a, MSOleDirent b) => SortingKeyCompare(a.Key, b.Key); - private static DateTime? DateTimeFromFileTime(ulong ft) => ft == 0 ? (DateTime?)null : DateTime.FromFileTime((long)ft); - /// /// Parse dirent number and recursively handle its siblings and children. /// parent is optional. @@ -540,7 +535,7 @@ namespace LibGSF.Input if (entry > uint.MaxValue / MSOleDirectoryEntry.DIRENT_SIZE) return null; - uint block = ((entry * MSOleDirectoryEntry.DIRENT_SIZE) >> Info.Header.BB_SHIFT); + uint block = (entry * MSOleDirectoryEntry.DIRENT_SIZE) >> Info.Header.BB_SHIFT; if (block >= Bat.NumBlocks) return null; @@ -577,14 +572,13 @@ namespace LibGSF.Input MSOleDirent dirent = new MSOleDirent { + Header = directoryEntry, Key = GsfMSOleSortingKey.Create(directoryEntry.NAME_STRING), Index = entry, // Root dir is always big block UseSmallBlock = parent != null && (directoryEntry.FILE_SIZE < Info.Header.THRESHOLD), Children = new List(), - - Header = directoryEntry, }; if (parent != null) @@ -622,7 +616,7 @@ namespace LibGSF.Input { Input = input, Info = Info.Ref(), - Stream = new byte[0], + Stream = null, }; return dst; @@ -665,18 +659,17 @@ namespace LibGSF.Input Info = new MSOleInfo { + Header = headerImpl, BigBlockBat = new MSOleBAT(), SmallBlockBat = new MSOleBAT(), + MetaBAT = null, MaxBlock = (Input.Size - MSOleHeader.OLE_HEADER_SIZE + headerImpl.BB_SIZE - 1) / headerImpl.BB_SIZE, RootDir = null, SmallBlockFile = null, RefCount = 1, - - Header = headerImpl, }; - uint[] metabat = null; - int metabatPtr = 0; // metabat[0] + int metabatPtr = 0; // MetaBAT[0] uint last; int? ptr = null; @@ -686,14 +679,13 @@ namespace LibGSF.Input { Info.BigBlockBat.Blocks = new uint[num_bat * (headerImpl.BB_SIZE / BAT_INDEX_SIZE)]; - metabat = new uint[Math.Max(headerImpl.BB_SIZE, MSOleHeader.OLE_HEADER_SIZE)]; + Info.MetaBAT = new uint[Math.Max(headerImpl.BB_SIZE, MSOleHeader.OLE_HEADER_SIZE)]; // Reading the elements invalidates this memory, make copy - - GetUnsignedInts(metabat, ref metabatPtr, header, OLE_HEADER_START_BAT, MSOleHeader.OLE_HEADER_SIZE - OLE_HEADER_START_BAT); + GetUnsignedInts(Info.MetaBAT, metabatPtr, header, OLE_HEADER_START_BAT, MSOleHeader.OLE_HEADER_SIZE - OLE_HEADER_START_BAT); last = Math.Min(num_bat, OLE_HEADER_METABAT_SIZE); - ptr = ReadMetabat(Info.BigBlockBat.Blocks, 0, Info.BigBlockBat.NumBlocks, metabat, last); // TODO: Does this need to be offset by metabatPtr? + ptr = ReadMetabat(Info.BigBlockBat.Blocks, 0, Info.BigBlockBat.NumBlocks, Info.MetaBAT, metabatPtr, (uint)(metabatPtr + last)); num_bat -= last; } @@ -703,7 +695,7 @@ namespace LibGSF.Input last = (uint)((Info.Header.BB_SIZE - BAT_INDEX_SIZE) / BAT_INDEX_SIZE); while (ptr != null && num_metabat-- > 0) { - byte[] tmp = GetBlock(metabat_block - 1, null); + byte[] tmp = GetBlock(metabat_block, null); if (tmp == null) { ptr = null; @@ -711,7 +703,7 @@ namespace LibGSF.Input } // Reading the elements invalidates this memory, make copy - GetUnsignedInts(metabat, ref metabatPtr, tmp, 0, Info.Header.BB_SIZE); + GetUnsignedInts(Info.MetaBAT, metabatPtr, tmp, 0, Info.Header.BB_SIZE); if (num_metabat == 0) { @@ -726,7 +718,7 @@ namespace LibGSF.Input } else if (num_metabat > 0) { - metabat_block = metabat[last]; + metabat_block = Info.MetaBAT[last]; if (num_bat < last) { // ::num_bat and ::num_metabat are @@ -739,7 +731,7 @@ namespace LibGSF.Input num_bat -= last; } - ptr = ReadMetabat(Info.BigBlockBat.Blocks, ptr.Value, Info.BigBlockBat.NumBlocks, metabat, last); + ptr = ReadMetabat(Info.BigBlockBat.Blocks, ptr.Value, Info.BigBlockBat.NumBlocks, Info.MetaBAT, metabatPtr, (uint)(metabatPtr + last)); } bool fail = (ptr == null); @@ -843,13 +835,19 @@ namespace LibGSF.Input if (sb_file == null) return null; - uint remaining = dirent.Header.FILE_SIZE; + int remaining = (int)dirent.Header.FILE_SIZE; child.Stream = new byte[remaining]; - for (uint i = 0; remaining > 0 && i < child.Bat.NumBlocks; i++, remaining -= (uint)Info.Header.SB_SIZE) + for (uint i = 0; remaining > 0 && i < child.Bat.NumBlocks; i++, remaining -= Info.Header.SB_SIZE) { - if (sb_file.Seek(child.Bat.Blocks[i] << Info.Header.SB_SHIFT, SeekOrigin.Begin) - || sb_file.Read((int)Math.Min(remaining, Info.Header.SB_SIZE), child.Stream, (int)(i << Info.Header.SB_SHIFT)) == null) + if (sb_file.Seek(child.Bat.Blocks[i] << Info.Header.SB_SHIFT, SeekOrigin.Begin)) + { + Console.Error.WriteLine($"Failure seeking to block {i} for '{dirent.Header.NAME_STRING}'"); + err = new Exception("Failure seeking block"); + return null; + } + + if (sb_file.Read((int)Math.Min(remaining, Info.Header.SB_SIZE), child.Stream, (int)(i << Info.Header.SB_SHIFT)) == null) { Console.Error.WriteLine($"Failure reading block {i} for '{dirent.Header.NAME_STRING}'"); err = new Exception("Failure reading block"); @@ -857,13 +855,12 @@ namespace LibGSF.Input } } - // TODO: Debug as to why this block would be hit - //if (remaining > 0) - //{ - // err = new Exception("Insufficient blocks"); - // Console.Error.WriteLine($"Small-block file '{dirent.Header.NAME_STRING}' has insufficient blocks ({child.Bat.NumBlocks}) for the stated size ({dirent.Header.FILE_SIZE})"); - // return null; - //} + if (remaining > 0) + { + err = new Exception("Insufficient blocks"); + Console.Error.WriteLine($"Small-block file '{dirent.Header.NAME_STRING}' has insufficient blocks ({child.Bat.NumBlocks}) for the stated size ({dirent.Header.FILE_SIZE})"); + return null; + } } return child; diff --git a/BurnOutSharp/External/libmsi/LibmsiDatabase.cs b/BurnOutSharp/External/libmsi/LibmsiDatabase.cs index 3ad27930..d4f770ee 100644 --- a/BurnOutSharp/External/libmsi/LibmsiDatabase.cs +++ b/BurnOutSharp/External/libmsi/LibmsiDatabase.cs @@ -1956,8 +1956,8 @@ namespace LibMSI { Exception err = null; GsfInput input = Infile.ChildByIndex(i, ref err); - string name = input.Name; - byte[] name8 = Encoding.ASCII.GetBytes(name); + string name = input?.Name; + byte[] name8 = Encoding.ASCII.GetBytes(name ?? string.Empty); if (name == null) {