public static unsafe PK_header ReadHeader(byte[] bytes)
        {
            PK_header header = new PK_header {
                data = new List <PK_field>()
            };

            fixed(byte *buffer = bytes)
            {
                byte *cursor = buffer;

                header.tag  = *(ushort *)cursor;
                cursor     += 2;
                header.size = *(ushort *)cursor;
                cursor     += 2;
                byte *startOfData = cursor;

                header.crc32 = *(uint *)cursor;
                cursor      += 4;

                while (cursor - startOfData < header.size)
                {
                    var field = new PK_field();
                    field.tag   = *(ushort *)cursor;
                    cursor     += 2;
                    field.size  = *(ushort *)cursor;
                    cursor     += 2;
                    field.value = new byte[field.size];
                    Marshal.Copy((IntPtr)cursor, field.value, 0, field.size);
                    cursor += field.size;
                    header.data.Add(field);
                }
            }

            return(header);
        }
        public static unsafe byte[] WriteHeader(PK_header header)
        {
            var bufferSize = 8; //initial fixed size of PK_header
            var crc        = new CRC32();

            foreach (var field in header.data)
            {
                bufferSize += 4; //base field size
                bufferSize += field.size;
            }

            var buffer = new byte[bufferSize];

            fixed(byte *bufferPtr = buffer)
            {
                var cursor = bufferPtr;

                *(ushort *)cursor = header.tag;
                cursor           += 2;
                *(ushort *)cursor = header.size;
                cursor           += 2;
                //skip past the crc, well fill it in later
                int *crcPtr = (int *)cursor;

                cursor += 4;

                foreach (var field in header.data)
                {
                    *(ushort *)cursor = field.tag;
                    cursor           += 2;
                    *(ushort *)cursor = field.size;
                    cursor           += 2;
                    Marshal.Copy(field.value, 0, (IntPtr)cursor, field.value.Length);
                    cursor += field.value.Length;
                }

                crc.SlurpBlock(buffer, 8, bufferSize - 8);
                *crcPtr = crc.Crc32Result;
            }

            return(buffer);
        }
        public static byte[] MakeVMSExtraBlock(RecordTypes recordType, RecordLayouts layout, FileAttributes attributes,
                                               ushort recordSize, uint fileSize, byte bucketSize, ushort maxRecordSize, ushort defaultExtend,
                                               DateTime created,
                                               DateTime modified, uint ownerId, FileProtection system, FileProtection owner, FileProtection group,
                                               FileProtection world)
        {
            var headerResult = new PK_header
            {
                tag = VMSAttributeHeader, size = VMSAttributeSize, data = new List <PK_field>()
            };
            var    evenFileSize = Math.DivRem(fileSize, 512, out var fileSizeRemainder) + 1;
            FatDef fatDef       = new FatDef
            {
                b_rtype   = MakeRecordType(recordType, layout),
                b_rattrib = (byte)attributes,
                w_rsize   = recordSize,
                l_hiblk   = (uint)(((evenFileSize + 1) << 16) | ((evenFileSize + 1) >> 16)),
                l_efblk   = (uint)((evenFileSize << 16) | (evenFileSize >> 16)),
                w_ffbyte  = (ushort)fileSizeRemainder,
                b_bktsize = bucketSize,
                b_vfcsize = (byte)(recordType == RecordTypes.C_VFC ? 2 : 0),
                w_maxrec  = maxRecordSize,
                w_defext  = defaultExtend,
                w_gbc     = 0
            };

            headerResult.data.Add(new PK_field {
                size = 32, tag = 4, value = WriteFATDef(fatDef)
            });
            headerResult.data.Add(new PK_field {
                size = 4, tag = 3, value = BitConverter.GetBytes((int)0)
            });
            headerResult.data.Add(new PK_field {
                size = 8, tag = 17, value = ConvertToSmithsonianTime(created)
            });
            headerResult.data.Add(new PK_field {
                size = 8, tag = 18, value = ConvertToSmithsonianTime(modified)
            });
            headerResult.data.Add(new PK_field {
                size = 8, tag = 19, value = BitConverter.GetBytes((long)0)
            });
            headerResult.data.Add(new PK_field {
                size = 8, tag = 20, value = BitConverter.GetBytes((long)0)
            });
            headerResult.data.Add(new PK_field {
                size = 2, tag = 13, value = new byte[] { 1, 0 }
            });
            headerResult.data.Add(new PK_field {
                size = 4, tag = 21, value = BitConverter.GetBytes(ownerId)
            });
            headerResult.data.Add(
                new PK_field {
                size = 2, tag = 22, value = MakeProtection(system, owner, group, world)
            });
            headerResult.data.Add(new PK_field {
                size = 2, tag = 23, value = new byte[] { 0, 0 }
            });
            headerResult.data.Add(new PK_field {
                size = 1, tag = 29, value = new byte[] { 0 }
            });

            return(WriteHeader(headerResult));
        }