public override void Read(BlamLib.IO.EndianReader s)
        {
            bool is_beta = false;
            bool is_odst = !((s.Owner as Blam.CacheFile).EngineVersion == BlamVersion.Halo3);

            Blam.CacheFile.ValidateHeader(s, kSizeOf);

            s.Seek(4);
            version = s.ReadInt32(); is_beta = version == 9;
            if (version != 9 && version != 11)
            {
                throw new InvalidCacheFileException(s.FileName);
            }
            fileLength = s.ReadInt32();
            s.ReadInt32();
            tagIndexAddress    = s.ReadUInt32();
            memoryBufferOffset = s.ReadInt32();
            memoryBufferSize   = s.ReadInt32();

            sourceFile = s.ReadAsciiString(256);
            build      = s.ReadTagString();
            cacheType  = (Blam.CacheType)s.ReadInt16();

            if (is_beta)
            {
                s.ReadInt16(); sharedType = Cache.SharedType.Beta;
                ReadBeta(s);
                return;
            }
            else
            {
                sharedType = (Cache.SharedType)s.ReadInt16();
            }

            s.ReadBool();
            s.ReadBool();             // false if it belongs to a untracked build
            s.ReadBool();
            s.ReadByte();             // appears to be an ODST-only field

            s.ReadInt32(); s.ReadInt32();
            s.ReadInt32(); s.ReadInt32(); s.ReadInt32();

            #region string id table
            // 0x158
            // old 128 byte aligned string table not used after the Delta builds of Halo 3

            stringIdsCount        = s.ReadInt32();
            stringIdsBufferSize   = s.ReadInt32();           // cstring buffer total size in bytes
            stringIdIndicesOffset = s.ReadInt32();
            stringIdsBufferOffset = s.ReadInt32();           // cstring buffer
            #endregion

            #region filetimes?
            // pretty sure this is a flags field
            // used to tell which of the following 64bit values
            // are used. Damn sure this are FILETIME structures, but
            // hex workshop doesn't like them so I can't be for sure...
            needsShared             = s.ReadInt32() != 0; // just a little 'hack' if you will. if zero, the map is self reliant, so no worries
            Filetime.dwHighDateTime = s.ReadInt32();
            Filetime.dwLowDateTime  = s.ReadInt32();
            if (s.ReadInt32() != 0)
            {
                flags.Add(CacheHeaderFlags.DependsOnMainMenu);
            }
            s.ReadInt32();
            if (s.ReadInt32() != 0)
            {
                flags.Add(CacheHeaderFlags.DependsOnShared);
            }
            s.ReadInt32();
            if (s.ReadInt32() != 0)
            {
                flags.Add(CacheHeaderFlags.DependsOnCampaign);
            }
            s.ReadInt32();
            #endregion

            name = s.ReadTagString();
            s.ReadInt32();
            scenarioPath = s.ReadAsciiString(256);
            s.ReadInt32();             // minor version, normally not used

            #region tag names
            tagNamesCount        = s.ReadInt32();
            tagNamesBufferOffset = s.ReadInt32();           // cstring buffer
            tagNamesBufferSize   = s.ReadInt32();           // cstring buffer total size in bytes
            tagNameIndicesOffset = s.ReadInt32();
            #endregion

            checksum = s.ReadUInt32();                // 0x2C4
            s.Seek(32, System.IO.SeekOrigin.Current); // these bytes are always the same

            baseAddress = s.ReadUInt32();             // expected base address
            xdkVersion  = s.ReadInt32();              // xdk version

            #region memory partitions
            // 0x2E8

            // memory partitions
            memoryPartitions = new Partition[6];
            memoryPartitions[0].BaseAddress = s.ReadUInt32();             // cache resource buffer
            memoryPartitions[0].Size        = s.ReadInt32();

            // readonly
            memoryPartitions[1].BaseAddress = s.ReadUInt32();             // sound cache resource buffer
            memoryPartitions[1].Size        = s.ReadInt32();

            memoryPartitions[2].BaseAddress = s.ReadUInt32();             // global tags buffer
            memoryPartitions[2].Size        = s.ReadInt32();
            memoryPartitions[3].BaseAddress = s.ReadUInt32();             // shared tag blocks?
            memoryPartitions[3].Size        = s.ReadInt32();
            memoryPartitions[4].BaseAddress = s.ReadUInt32();             // address
            memoryPartitions[4].Size        = s.ReadInt32();

            // readonly
            memoryPartitions[5].BaseAddress = s.ReadUInt32();             // map tags buffer
            memoryPartitions[5].Size        = s.ReadInt32();
            #endregion

            int count = s.ReadInt32();
            s.Seek(12, System.IO.SeekOrigin.Current);             // these bytes are always the same
            // if there is a hash in the header, this is the ONLY
            // place where it can be
            s.Seek(20 /*SHA1*/ + 40 + 256 /*RSA*/, System.IO.SeekOrigin.Current);             // ???

            // 0x46C
            cacheInterop.Read(s);
            cacheInterop.PostprocessForCacheRead(kSizeOf);

            s.Seek(16, System.IO.SeekOrigin.Current);             // GUID?, these bytes are always the same. ODST is different from Halo 3

            #region blah 1
            // 0x4AC

            // campaign has a shit load of these
            // but shared doesn't nor mainmenu
            // I compared the sc110 french and english and both have the SAME counts and element data. So
            // I don't think this is a hash or something. At least, if it is, it's not runtime relative so
            // nothing we have to worry about

            s.ReadInt16();             // I've only seen this be two different values (besides zero).
            count = s.ReadInt16();
            s.ReadInt32();             // seems to always be zero
            CompressionGuid = new Guid(s.ReadBytes(16));

            s.Seek(count * 28, System.IO.SeekOrigin.Current);             // seek past the elements
            // dword
            // long
            // buffer [0x14] (probably a sha1 hash)
            s.Seek((320 - count) * 28, System.IO.SeekOrigin.Current);             // seek past the unused elements
            #endregion

            #region blah 2
            {
                // 0x27C4
                // This on the other hand, sc110 french and english had MINOR differences. Those differences were in
                // DWORDs @ 0x4 and 0x8

                // 005 - 1
                // 010 - 3
                // 020 - 3
                // 030 - 2
                // 040 - 2
                // 050 - 2
                // 070 - 3
                // 100 - 3
                // 110 - 2
                // 120 - 2
                // 130 - 1

                // c100  - 1
                // c200  - 1
                // h100  - 9
                // l200  - 7
                // l300  - 8
                // sc100 - 5
                // sc110 - 5
                // sc120 - 6
                // sc130 - 6
                // sc140 - 6
                // sc150 - 6

                // going to punt and just assume there is a max count of 10 of these possible

                // maybe related to bsp\'zones'?
                int blah2_sizeof = !is_odst ? 172 : 180;

                count = (int)(s.ReadUInt32() >> 24);                               // did someone forget to f*****g byte swap something?
                s.Seek(count * blah2_sizeof, System.IO.SeekOrigin.Current);        // seek past the elements
                s.Seek((10 - count) * blah2_sizeof, System.IO.SeekOrigin.Current); // seek past the unused elements
            }
            #endregion

            s.Seek((!is_odst ? 380 : 300) + sizeof(uint), System.IO.SeekOrigin.Current);             // zero


            ReadPostprocessForInterop();

            ReadPostprocessForBaseAddresses(s);
        }