File: src\libraries\System.Private.CoreLib\src\System\Resources\ResourceReader.cs
Web Access
Project: src\src\libraries\System.Resources.Extensions\src\System.Resources.Extensions.csproj (System.Resources.Extensions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers.Binary;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
 
namespace System.Resources
#if RESOURCES_EXTENSIONS
    .Extensions
#endif
{
#pragma warning disable IDE0065
#if RESOURCES_EXTENSIONS
    using ResourceReader = DeserializingResourceReader;
#endif
#pragma warning restore IDE0065
 
    // Provides the default implementation of IResourceReader, reading
    // .resources file from the system default binary format.  This class
    // can be treated as an enumerator once.
    //
    // See the RuntimeResourceSet overview for details on the system
    // default file format.
    //
 
    internal readonly struct ResourceLocator
    {
        internal ResourceLocator(int dataPos, object? value)
        {
            DataPosition = dataPos;
            Value = value;
        }
 
        internal int DataPosition { get; }
        internal object? Value { get; }
 
        internal static bool CanCache(ResourceTypeCode value)
        {
            Debug.Assert(value >= 0, "negative ResourceTypeCode.  What?");
            return value <= ResourceTypeCode.LastPrimitive;
        }
    }
 
    public sealed partial class
#if RESOURCES_EXTENSIONS
        DeserializingResourceReader
#else
        ResourceReader
#endif
        : IResourceReader
    {
        // A reasonable default buffer size for reading from files, especially
        // when we will likely be seeking frequently.  Could be smaller, but does
        // it make sense to use anything less than one page?
        private const int DefaultFileStreamBufferSize = 4096;
 
        // Backing store we're reading from. Usages outside of constructor
        // initialization must be protected by lock (this).
        private BinaryReader _store;
        // Used by RuntimeResourceSet and this class's enumerator.
        // Accesses must be protected by lock(_resCache).
        internal Dictionary<string, ResourceLocator>? _resCache;
        private long _nameSectionOffset;  // Offset to name section of file.
        private long _dataSectionOffset;  // Offset to Data section of file.
 
        // Note this class is tightly coupled with UnmanagedMemoryStream.
        // At runtime when getting an embedded resource from an assembly,
        // we're given an UnmanagedMemoryStream referring to the mmap'ed portion
        // of the assembly.  The pointers here are pointers into that block of
        // memory controlled by the OS's loader.
        private int[]? _nameHashes;    // hash values for all names.
        private unsafe int* _nameHashesPtr;  // In case we're using UnmanagedMemoryStream
        private int[]? _namePositions; // relative locations of names
        private unsafe int* _namePositionsPtr;  // If we're using UnmanagedMemoryStream
        private Type?[] _typeTable;    // Lazy array of Types for resource values.
        private int[] _typeNamePositions;  // To delay initialize type table
        private int _numResources;    // Num of resources files, in case arrays aren't allocated.
 
        // We'll include a separate code path that uses UnmanagedMemoryStream to
        // avoid allocating String objects and the like.
        private UnmanagedMemoryStream? _ums;
 
        // Version number of .resources file, for compatibility
        private int _version;
 
        public
#if RESOURCES_EXTENSIONS
        DeserializingResourceReader(string fileName)
#else
        ResourceReader(string fileName)
#endif
        {
            _resCache = new Dictionary<string, ResourceLocator>(FastResourceComparer.Default);
            _store = new BinaryReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.RandomAccess), Encoding.UTF8);
 
            try
            {
                ReadResources();
            }
            catch
            {
                _store.Close(); // If we threw an exception, close the file.
                throw;
            }
        }
 
        public
#if RESOURCES_EXTENSIONS
        DeserializingResourceReader(Stream stream)
#else
        ResourceReader(Stream stream)
#endif
        {
#if RESOURCES_EXTENSIONS
            if (stream is null)
            {
                throw new ArgumentNullException(nameof(stream));
            }
#else
            ArgumentNullException.ThrowIfNull(stream);
#endif
 
            if (!stream.CanRead)
            {
                throw new ArgumentException(SR.Argument_StreamNotReadable);
            }
 
            _resCache = new Dictionary<string, ResourceLocator>(FastResourceComparer.Default);
            _store = new BinaryReader(stream, Encoding.UTF8);
            // We have a faster code path for reading resource files from an assembly.
            _ums = stream as UnmanagedMemoryStream;
 
            ReadResources();
        }
 
        internal static bool AllowCustomResourceTypes { get; } = AppContext.TryGetSwitch("System.Resources.ResourceManager.AllowCustomResourceTypes", out bool allowReflection) ? allowReflection : true;
 
        public void Close()
        {
            Dispose(true);
        }
 
        public void Dispose()
        {
            Close();
        }
 
        private unsafe void Dispose(bool disposing)
        {
            if (_store != null)
            {
                _resCache = null;
                if (disposing)
                {
                    // Close the stream in a thread-safe way.  This fix means
                    // that we may call Close n times, but that's safe.
                    BinaryReader copyOfStore = _store;
                    _store = null!;
                    copyOfStore?.Close();
                }
                _store = null!;
                _namePositions = null;
                _nameHashes = null;
                _ums = null;
                _namePositionsPtr = null;
                _nameHashesPtr = null;
            }
        }
 
        private static unsafe int ReadUnalignedI4(int* p)
        {
            return BinaryPrimitives.ReadInt32LittleEndian(new ReadOnlySpan<byte>(p, sizeof(int)));
        }
 
        private void SkipString()
        {
            // Note: this method assumes that it is called either during object
            // construction or within another method that locks on this.
 
            int stringLength = _store.Read7BitEncodedInt();
            if (stringLength < 0)
            {
                throw new BadImageFormatException(SR.BadImageFormat_NegativeStringLength);
            }
            _store.BaseStream.Seek(stringLength, SeekOrigin.Current);
        }
 
        private unsafe int GetNameHash(int index)
        {
            Debug.Assert(index >= 0 && index < _numResources, $"Bad index into hash array.  index: {index}");
 
            if (_ums == null)
            {
                Debug.Assert(_nameHashes != null && _nameHashesPtr == null, "Internal state mangled.");
                return _nameHashes[index];
            }
            else
            {
                Debug.Assert(_nameHashes == null && _nameHashesPtr != null, "Internal state mangled.");
                return ReadUnalignedI4(&_nameHashesPtr[index]);
            }
        }
 
        private unsafe int GetNamePosition(int index)
        {
            Debug.Assert(index >= 0 && index < _numResources, $"Bad index into name position array.  index: {index}");
            int r;
            if (_ums == null)
            {
                Debug.Assert(_namePositions != null && _namePositionsPtr == null, "Internal state mangled.");
                r = _namePositions[index];
            }
            else
            {
                Debug.Assert(_namePositions == null && _namePositionsPtr != null, "Internal state mangled.");
                r = ReadUnalignedI4(&_namePositionsPtr[index]);
            }
 
            if (r < 0 || r > _dataSectionOffset - _nameSectionOffset)
            {
                throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesNameInvalidOffset, r));
            }
            return r;
        }
 
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
 
        public IDictionaryEnumerator GetEnumerator()
        {
            if (_resCache == null)
                throw new InvalidOperationException(SR.ResourceReaderIsClosed);
            return new ResourceEnumerator(this);
        }
 
        // Called from RuntimeResourceSet
        internal ResourceEnumerator GetEnumeratorInternal()
        {
            return new ResourceEnumerator(this);
        }
 
        // From a name, finds the associated virtual offset for the data.
        // To read the data, seek to _dataSectionOffset + dataPos, then
        // read the resource type & data.
        // This does a binary search through the names.
        // Called from RuntimeResourceSet
        internal int FindPosForResource(string name)
        {
            Debug.Assert(_store != null, "ResourceReader is closed!");
            int hash = FastResourceComparer.HashFunction(name);
 
            // Binary search over the hashes.  Use the _namePositions array to
            // determine where they exist in the underlying stream.
            int lo = 0;
            int hi = _numResources - 1;
            int index = -1;
            bool success = false;
            while (lo <= hi)
            {
                index = (lo + hi) >> 1;
                // Do NOT use subtraction here, since it will wrap for large
                // negative numbers.
                int currentHash = GetNameHash(index);
                int c;
                if (currentHash == hash)
                    c = 0;
                else if (currentHash < hash)
                    c = -1;
                else
                    c = 1;
 
                if (c == 0)
                {
                    success = true;
                    break;
                }
                if (c < 0)
                    lo = index + 1;
                else
                    hi = index - 1;
            }
            if (!success)
            {
                return -1;
            }
 
            // index is the location in our hash array that corresponds with a
            // value in the namePositions array.
            // There could be collisions in our hash function.  Check on both sides
            // of index to find the range of hash values that are equal to the
            // target hash value.
            if (lo != index)
            {
                lo = index;
                while (lo > 0 && GetNameHash(lo - 1) == hash)
                    lo--;
            }
            if (hi != index)
            {
                hi = index;
                while (hi < _numResources - 1 && GetNameHash(hi + 1) == hash)
                    hi++;
            }
 
            lock (this)
            {
                for (int i = lo; i <= hi; i++)
                {
                    _store.BaseStream.Seek(_nameSectionOffset + GetNamePosition(i), SeekOrigin.Begin);
                    if (CompareStringEqualsName(name))
                    {
                        int dataPos = _store.ReadInt32();
                        if (dataPos < 0 || dataPos >= _store.BaseStream.Length - _dataSectionOffset)
                        {
                            throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataPos));
                        }
                        return dataPos;
                    }
                }
            }
            return -1;
        }
 
        // This compares the String in the .resources file at the current position
        // with the string you pass in.
        // Whoever calls this method should make sure that they take a lock
        // so no one else can cause us to seek in the stream.
        private unsafe bool CompareStringEqualsName(string name)
        {
            Debug.Assert(_store != null, "ResourceReader is closed!");
            Debug.Assert(Monitor.IsEntered(this)); // uses _store
 
            int byteLen = _store.Read7BitEncodedInt();
            if (byteLen < 0)
            {
                throw new BadImageFormatException(SR.BadImageFormat_NegativeStringLength);
            }
            if (_ums != null)
            {
                byte* bytes = _ums.PositionPointer;
                // Skip over the data in the Stream, positioning ourselves right after it.
                _ums.Seek(byteLen, SeekOrigin.Current);
                if (_ums.Position > _ums.Length)
                {
                    throw new BadImageFormatException(SR.BadImageFormat_ResourcesNameTooLong);
                }
 
                // On 64-bit machines, these char*'s may be misaligned.  Use a
                // byte-by-byte comparison instead.
                return FastResourceComparer.CompareOrdinal(bytes, byteLen, name) == 0;
            }
            else
            {
                // This code needs to be fast
                byte[] bytes = new byte[byteLen];
                int numBytesToRead = byteLen;
                while (numBytesToRead > 0)
                {
                    int n = _store.Read(bytes, byteLen - numBytesToRead, numBytesToRead);
                    if (n == 0)
                        throw new BadImageFormatException(SR.BadImageFormat_ResourceNameCorrupted);
                    numBytesToRead -= n;
                }
                return FastResourceComparer.CompareOrdinal(bytes, byteLen / 2, name) == 0;
            }
        }
 
        // This is used in the enumerator.  The enumerator iterates from 0 to n
        // of our resources and this returns the resource name for a particular
        // index.  The parameter is NOT a virtual offset.
        private unsafe string AllocateStringForNameIndex(int index, out int dataOffset)
        {
            Debug.Assert(_store != null, "ResourceReader is closed!");
            byte[] bytes;
            int byteLen;
            long nameVA = GetNamePosition(index);
            lock (this)
            {
                _store.BaseStream.Seek(nameVA + _nameSectionOffset, SeekOrigin.Begin);
                // Can't use _store.ReadString, since it's using UTF-8!
                byteLen = _store.Read7BitEncodedInt();
                if (byteLen < 0)
                {
                    throw new BadImageFormatException(SR.BadImageFormat_NegativeStringLength);
                }
 
                if (_ums != null)
                {
                    if (_ums.Position > _ums.Length - byteLen)
                        throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourcesIndexTooLong, index));
 
                    string? s = null;
                    char* charPtr = (char*)_ums.PositionPointer;
 
                    if (BitConverter.IsLittleEndian)
                    {
                        s = new string(charPtr, 0, byteLen / 2);
                    }
                    else
                    {
                        char[] arr = new char[byteLen / 2];
                        for (int i = 0; i < arr.Length; i++)
                        {
                            arr[i] = (char)BinaryPrimitives.ReverseEndianness((short)charPtr[i]);
                        }
                        s = new string(arr);
                    }
 
                    _ums.Position += byteLen;
                    dataOffset = _store.ReadInt32();
                    if (dataOffset < 0 || dataOffset >= _store.BaseStream.Length - _dataSectionOffset)
                    {
                        throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataOffset));
                    }
                    return s;
                }
 
                bytes = new byte[byteLen];
                // We must read byteLen bytes, or we have a corrupted file.
                // Use a blocking read in case the stream doesn't give us back
                // everything immediately.
                int count = byteLen;
                while (count > 0)
                {
                    int n = _store.Read(bytes, byteLen - count, count);
                    if (n == 0)
                        throw new EndOfStreamException(SR.Format(SR.BadImageFormat_ResourceNameCorrupted_NameIndex, index));
                    count -= n;
                }
                dataOffset = _store.ReadInt32();
                if (dataOffset < 0 || dataOffset >= _store.BaseStream.Length - _dataSectionOffset)
                {
                    throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataOffset));
                }
            }
            return Encoding.Unicode.GetString(bytes, 0, byteLen);
        }
 
        // This is used in the enumerator.  The enumerator iterates from 0 to n
        // of our resources and this returns the resource value for a particular
        // index.  The parameter is NOT a virtual offset.
        private object? GetValueForNameIndex(int index)
        {
            Debug.Assert(_store != null, "ResourceReader is closed!");
            long nameVA = GetNamePosition(index);
            lock (this)
            {
                _store.BaseStream.Seek(nameVA + _nameSectionOffset, SeekOrigin.Begin);
                SkipString();
 
                int dataPos = _store.ReadInt32();
                if (dataPos < 0 || dataPos >= _store.BaseStream.Length - _dataSectionOffset)
                {
                    throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataPos));
                }
 
                if (_version == 1)
                    return LoadObjectV1(dataPos);
                else
                    return LoadObjectV2(dataPos, out _);
            }
        }
 
        // This takes a virtual offset into the data section and reads a String
        // from that location. Called from RuntimeResourceSet
        internal string? LoadString(int pos)
        {
            Debug.Assert(_store != null, "ResourceReader is closed!");
 
            lock (this)
            {
                _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin);
                string? s = null;
                int typeIndex = _store.Read7BitEncodedInt();
                if (_version == 1)
                {
                    if (typeIndex == -1)
                        return null;
                    if (FindType(typeIndex) != typeof(string))
                        throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Type, FindType(typeIndex).FullName));
                    s = _store.ReadString();
                }
                else
                {
                    ResourceTypeCode typeCode = (ResourceTypeCode)typeIndex;
                    if (typeCode != ResourceTypeCode.String && typeCode != ResourceTypeCode.Null)
                    {
                        string? typeString;
                        if (typeCode < ResourceTypeCode.StartOfUserTypes)
                            typeString = typeCode.ToString();
                        else
                            typeString = FindType(typeCode - ResourceTypeCode.StartOfUserTypes).FullName;
                        throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Type, typeString));
                    }
                    if (typeCode == ResourceTypeCode.String) // ignore Null
                        s = _store.ReadString();
                }
                return s;
            }
        }
 
        // Called from RuntimeResourceSet
        internal object? LoadObject(int pos)
        {
            lock (this)
            {
                return _version == 1 ? LoadObjectV1(pos) : LoadObjectV2(pos, out _);
            }
        }
 
        // Called from RuntimeResourceSet
        internal object? LoadObject(int pos, out ResourceTypeCode typeCode)
        {
            lock (this)
            {
                if (_version == 1)
                {
                    object? o = LoadObjectV1(pos);
                    typeCode = (o is string) ? ResourceTypeCode.String : ResourceTypeCode.StartOfUserTypes;
                    return o;
                }
                return LoadObjectV2(pos, out typeCode);
            }
        }
 
        // This takes a virtual offset into the data section and reads an Object
        // from that location.
        private object? LoadObjectV1(int pos)
        {
            Debug.Assert(_store != null, "ResourceReader is closed!");
            Debug.Assert(_version == 1, ".resources file was not a V1 .resources file!");
            Debug.Assert(Monitor.IsEntered(this)); // uses _store
 
            try
            {
                // mega try-catch performs exceptionally bad on x64; factored out body into
                // _LoadObjectV1 and wrap here.
                return _LoadObjectV1(pos);
            }
            catch (EndOfStreamException eof)
            {
                throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, eof);
            }
            catch (ArgumentOutOfRangeException e)
            {
                throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, e);
            }
        }
 
        private object? _LoadObjectV1(int pos)
        {
            Debug.Assert(Monitor.IsEntered(this)); // uses _store
 
            _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin);
            int typeIndex = _store.Read7BitEncodedInt();
            if (typeIndex == -1)
                return null;
            Type type = FindType(typeIndex);
            // Consider putting in logic to see if this type is a
            // primitive or a value type first, so we can reach the
            // deserialization code faster for arbitrary objects.
 
            if (type == typeof(string))
                return _store.ReadString();
            else if (type == typeof(int))
                return _store.ReadInt32();
            else if (type == typeof(byte))
                return _store.ReadByte();
            else if (type == typeof(sbyte))
                return _store.ReadSByte();
            else if (type == typeof(short))
                return _store.ReadInt16();
            else if (type == typeof(long))
                return _store.ReadInt64();
            else if (type == typeof(ushort))
                return _store.ReadUInt16();
            else if (type == typeof(uint))
                return _store.ReadUInt32();
            else if (type == typeof(ulong))
                return _store.ReadUInt64();
            else if (type == typeof(float))
                return _store.ReadSingle();
            else if (type == typeof(double))
                return _store.ReadDouble();
            else if (type == typeof(DateTime))
            {
                // Ideally we should use DateTime's ToBinary & FromBinary,
                // but we can't for compatibility reasons.
                return new DateTime(_store.ReadInt64());
            }
            else if (type == typeof(TimeSpan))
                return new TimeSpan(_store.ReadInt64());
            else if (type == typeof(decimal))
            {
#if RESOURCES_EXTENSIONS
                int[] bits = new int[4];
#else
                Span<int> bits = stackalloc int[4];
#endif
                for (int i = 0; i < bits.Length; i++)
                    bits[i] = _store.ReadInt32();
                return new decimal(bits);
            }
            else
            {
                return DeserializeObject(typeIndex);
            }
        }
 
        private object? LoadObjectV2(int pos, out ResourceTypeCode typeCode)
        {
            Debug.Assert(_store != null, "ResourceReader is closed!");
            Debug.Assert(_version >= 2, ".resources file was not a V2 (or higher) .resources file!");
            Debug.Assert(Monitor.IsEntered(this)); // uses _store
 
            try
            {
                // mega try-catch performs exceptionally bad on x64; factored out body into
                // _LoadObjectV2 and wrap here.
                return _LoadObjectV2(pos, out typeCode);
            }
            catch (EndOfStreamException eof)
            {
                throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, eof);
            }
            catch (ArgumentOutOfRangeException e)
            {
                throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, e);
            }
        }
 
        private object? _LoadObjectV2(int pos, out ResourceTypeCode typeCode)
        {
            Debug.Assert(Monitor.IsEntered(this)); // uses _store
 
            _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin);
            typeCode = (ResourceTypeCode)_store.Read7BitEncodedInt();
 
            switch (typeCode)
            {
                case ResourceTypeCode.Null:
                    return null;
 
                case ResourceTypeCode.String:
                    return _store.ReadString();
 
                case ResourceTypeCode.Boolean:
                    return _store.ReadBoolean();
 
                case ResourceTypeCode.Char:
                    return (char)_store.ReadUInt16();
 
                case ResourceTypeCode.Byte:
                    return _store.ReadByte();
 
                case ResourceTypeCode.SByte:
                    return _store.ReadSByte();
 
                case ResourceTypeCode.Int16:
                    return _store.ReadInt16();
 
                case ResourceTypeCode.UInt16:
                    return _store.ReadUInt16();
 
                case ResourceTypeCode.Int32:
                    return _store.ReadInt32();
 
                case ResourceTypeCode.UInt32:
                    return _store.ReadUInt32();
 
                case ResourceTypeCode.Int64:
                    return _store.ReadInt64();
 
                case ResourceTypeCode.UInt64:
                    return _store.ReadUInt64();
 
                case ResourceTypeCode.Single:
                    return _store.ReadSingle();
 
                case ResourceTypeCode.Double:
                    return _store.ReadDouble();
 
                case ResourceTypeCode.Decimal:
                    return _store.ReadDecimal();
 
                case ResourceTypeCode.DateTime:
                    // Use DateTime's ToBinary & FromBinary.
                    long data = _store.ReadInt64();
                    return DateTime.FromBinary(data);
 
                case ResourceTypeCode.TimeSpan:
                    long ticks = _store.ReadInt64();
                    return new TimeSpan(ticks);
 
                // Special types
                case ResourceTypeCode.ByteArray:
                    {
                        int len = _store.ReadInt32();
                        if (len < 0)
                        {
                            throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
                        }
 
                        if (_ums == null)
                        {
                            if (len > _store.BaseStream.Length)
                            {
                                throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
                            }
                            return _store.ReadBytes(len);
                        }
 
                        if (len > _ums.Length - _ums.Position)
                        {
                            throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
                        }
 
                        byte[] bytes = new byte[len];
                        int r = _ums.Read(bytes, 0, len);
                        Debug.Assert(r == len, "ResourceReader needs to use a blocking read here.  (Call _store.ReadBytes(len)?)");
                        return bytes;
                    }
 
                case ResourceTypeCode.Stream:
                    {
                        int len = _store.ReadInt32();
                        if (len < 0)
                        {
                            throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
                        }
                        if (_ums == null)
                        {
                            byte[] bytes = _store.ReadBytes(len);
                            // Lifetime of memory == lifetime of this stream.
                            return new PinnedBufferMemoryStream(bytes);
                        }
 
                        // make sure we don't create an UnmanagedMemoryStream that is longer than the resource stream.
                        if (len > _ums.Length - _ums.Position)
                        {
                            throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
                        }
 
                        // For the case that we've memory mapped in the .resources
                        // file, just return a Stream pointing to that block of memory.
                        unsafe
                        {
                            return new UnmanagedMemoryStream(_ums.PositionPointer, len, len, FileAccess.Read);
                        }
                    }
 
                default:
                    if (typeCode < ResourceTypeCode.StartOfUserTypes)
                    {
                        throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch);
                    }
                    break;
            }
 
            // Normal serialized objects
            int typeIndex = typeCode - ResourceTypeCode.StartOfUserTypes;
            return DeserializeObject(typeIndex);
        }
 
        // Reads in the header information for a .resources file.  Verifies some
        // of the assumptions about this resource set, and builds the class table
        // for the default resource file format.
        [MemberNotNull(nameof(_typeTable))]
        [MemberNotNull(nameof(_typeNamePositions))]
        private void ReadResources()
        {
            Debug.Assert(!Monitor.IsEntered(this)); // only called during init
            Debug.Assert(_store != null, "ResourceReader is closed!");
 
            try
            {
                // mega try-catch performs exceptionally bad on x64; factored out body into
                // _ReadResources and wrap here.
                _ReadResources();
            }
            catch (EndOfStreamException eof)
            {
                throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted, eof);
            }
            catch (IndexOutOfRangeException e)
            {
                throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted, e);
            }
        }
 
        [MemberNotNull(nameof(_typeTable))]
        [MemberNotNull(nameof(_typeNamePositions))]
        private void _ReadResources()
        {
            Debug.Assert(!Monitor.IsEntered(this)); // only called during init
 
            // Read ResourceManager header
            // Check for magic number
            int magicNum = _store.ReadInt32();
            if (magicNum != ResourceManager.MagicNumber)
                throw new ArgumentException(SR.Resources_StreamNotValid);
            // Assuming this is ResourceManager header V1 or greater, hopefully
            // after the version number there is a number of bytes to skip
            // to bypass the rest of the ResMgr header. For V2 or greater, we
            // use this to skip to the end of the header
            int resMgrHeaderVersion = _store.ReadInt32();
            int numBytesToSkip = _store.ReadInt32();
            if (numBytesToSkip < 0 || resMgrHeaderVersion < 0)
            {
                throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
            }
            if (resMgrHeaderVersion > 1)
            {
                _store.BaseStream.Seek(numBytesToSkip, SeekOrigin.Current);
            }
            else
            {
                // We don't care about numBytesToSkip; read the rest of the header
 
                // Read in type name for a suitable ResourceReader
                // Note ResourceWriter & InternalResGen use different Strings.
                string readerType = _store.ReadString();
 
                if (!ValidateReaderType(readerType))
                    throw new NotSupportedException(SR.Format(SR.NotSupported_WrongResourceReader_Type, readerType));
 
                // Skip over type name for a suitable ResourceSet
                SkipString();
            }
 
            // Read RuntimeResourceSet header
            // Do file version check
            int version = _store.ReadInt32();
 
            // File format version number
            const int CurrentVersion = 2;
 
            if (version != CurrentVersion && version != 1)
                throw new ArgumentException(SR.Format(SR.Arg_ResourceFileUnsupportedVersion, CurrentVersion, version));
            _version = version;
 
            _numResources = _store.ReadInt32();
            if (_numResources < 0)
            {
                throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
            }
 
            // Read type positions into type positions array.
            // But delay initialize the type table.
            int numTypes = _store.ReadInt32();
            if (numTypes < 0)
            {
                throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
            }
            _typeTable = new Type[numTypes];
            _typeNamePositions = new int[numTypes];
            for (int i = 0; i < numTypes; i++)
            {
                _typeNamePositions[i] = (int)_store.BaseStream.Position;
 
                // Skip over the Strings in the file.  Don't create types.
                SkipString();
            }
 
            // Prepare to read in the array of name hashes
            //  Note that the name hashes array is aligned to 8 bytes so
            //  we can use pointers into it on 64 bit machines. (4 bytes
            //  may be sufficient, but let's plan for the future)
            //  Skip over alignment stuff.  All public .resources files
            //  should be aligned   No need to verify the byte values.
            long pos = _store.BaseStream.Position;
            int alignBytes = ((int)pos) & 7;
            if (alignBytes != 0)
            {
                for (int i = 0; i < 8 - alignBytes; i++)
                {
                    _store.ReadByte();
                }
            }
 
            // Read in the array of name hashes
            if (_ums == null)
            {
                _nameHashes = new int[_numResources];
                for (int i = 0; i < _numResources; i++)
                {
                    _nameHashes[i] = _store.ReadInt32();
                }
            }
            else
            {
                int seekPos = unchecked(4 * _numResources);
                if (seekPos < 0)
                {
                    throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
                }
                unsafe
                {
                    _nameHashesPtr = (int*)_ums.PositionPointer;
                    // Skip over the array of nameHashes.
                    _ums.Seek(seekPos, SeekOrigin.Current);
                    // get the position pointer once more to check that the whole table is within the stream
                    _ = _ums.PositionPointer;
                }
            }
 
            // Read in the array of relative positions for all the names.
            if (_ums == null)
            {
                _namePositions = new int[_numResources];
                for (int i = 0; i < _numResources; i++)
                {
                    int namePosition = _store.ReadInt32();
                    if (namePosition < 0)
                    {
                        throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
                    }
 
                    _namePositions[i] = namePosition;
                }
            }
            else
            {
                int seekPos = unchecked(4 * _numResources);
                if (seekPos < 0)
                {
                    throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
                }
                unsafe
                {
                    _namePositionsPtr = (int*)_ums.PositionPointer;
                    // Skip over the array of namePositions.
                    _ums.Seek(seekPos, SeekOrigin.Current);
                    // get the position pointer once more to check that the whole table is within the stream
                    _ = _ums.PositionPointer;
                }
            }
 
            // Read location of data section.
            _dataSectionOffset = _store.ReadInt32();
            if (_dataSectionOffset < 0)
            {
                throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
            }
 
            // Store current location as start of name section
            _nameSectionOffset = _store.BaseStream.Position;
 
            // _nameSectionOffset should be <= _dataSectionOffset; if not, it's corrupt
            if (_dataSectionOffset < _nameSectionOffset)
            {
                throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
            }
        }
 
        // This allows us to delay-initialize the Type[].  This might be a
        // good startup time savings, since we might have to load assemblies
        // and initialize Reflection.
        private Type FindType(int typeIndex)
        {
            if (!AllowCustomResourceTypes)
            {
                throw new NotSupportedException(SR.ResourceManager_ReflectionNotAllowed);
            }
 
            if (typeIndex < 0 || typeIndex >= _typeTable.Length)
            {
                throw new BadImageFormatException(SR.BadImageFormat_InvalidType);
            }
 
            [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
                Justification = "UseReflectionToGetType will get trimmed out when AllowCustomResourceTypes is set to false. " +
                "When set to true, we will already throw a warning for this feature switch, so we suppress this one in order for" +
                "the user to only get one error.")]
            Type UseReflectionToGetTypeLocal(int typeIndex) => UseReflectionToGetType(typeIndex);
 
            return _typeTable[typeIndex] ?? UseReflectionToGetTypeLocal(typeIndex);
        }
 
        [RequiresUnreferencedCode("The CustomResourceTypesSupport feature switch has been enabled for this app which is being trimmed. " +
            "Custom readers as well as custom objects on the resources file are not observable by the trimmer and so required assemblies, types and members may be removed.")]
        private Type UseReflectionToGetType(int typeIndex)
        {
            Debug.Assert(Monitor.IsEntered(this)); // uses _store
 
            long oldPos = _store.BaseStream.Position;
            try
            {
                _store.BaseStream.Position = _typeNamePositions[typeIndex];
                string typeName = _store.ReadString();
                _typeTable[typeIndex] = Type.GetType(typeName, true);
                Debug.Assert(_typeTable[typeIndex] != null, "Should have found a type!");
                return _typeTable[typeIndex]!;
            }
// If-defing this coud out from Resources Extensions since they will by definition always support deserialization
// So we shouldn't attempt to wrap the original exception with a NotSupportedException since that can be misleading.
// For that reason, the bellow code is only relevant when building CoreLib's ResourceReader.
#if !RESOURCES_EXTENSIONS
            // If serialization isn't supported, we convert FileNotFoundException to
            // NotSupportedException for consistency with v2. This is a corner-case, but the
            // idea is that we want to give the user a more accurate error message. Even if
            // the dependency were found, we know it will require serialization since it
            // can't be one of the types we special case. So if the dependency were found,
            // it would go down the serialization code path, resulting in NotSupported for
            // SKUs without serialization.
            //
            // We don't want to regress the expected case by checking the type info before
            // getting to Type.GetType -- this is costly with v1 resource formats.
            catch (FileNotFoundException fileNotFoundException) when (!_permitDeserialization)
            {
                // Include the FileNotFoundException as an inner exception to make it more diagnosable
                throw new NotSupportedException(SR.NotSupported_ResourceObjectSerialization, fileNotFoundException);
            }
#endif
            finally
            {
                _store.BaseStream.Position = oldPos;
            }
        }
 
        private string TypeNameFromTypeCode(ResourceTypeCode typeCode)
        {
            Debug.Assert(typeCode >= 0, "can't be negative");
            Debug.Assert(Monitor.IsEntered(this)); // uses _store
 
            if (typeCode < ResourceTypeCode.StartOfUserTypes)
            {
                Debug.Assert(!string.Equals(typeCode.ToString(), "LastPrimitive"), "Change ResourceTypeCode metadata order so LastPrimitive isn't what Enum.ToString prefers.");
                return "ResourceTypeCode." + typeCode.ToString();
            }
            else
            {
                int typeIndex = typeCode - ResourceTypeCode.StartOfUserTypes;
                Debug.Assert(typeIndex >= 0 && typeIndex < _typeTable.Length, "TypeCode is broken or corrupted!");
                long oldPos = _store.BaseStream.Position;
                try
                {
                    _store.BaseStream.Position = _typeNamePositions[typeIndex];
                    return _store.ReadString();
                }
                finally
                {
                    _store.BaseStream.Position = oldPos;
                }
            }
        }
 
        internal sealed class ResourceEnumerator : IDictionaryEnumerator
        {
            private const int ENUM_DONE = int.MinValue;
            private const int ENUM_NOT_STARTED = -1;
 
            private readonly ResourceReader _reader;
            private bool _currentIsValid;
            private int _currentName;
            private int _dataPosition; // cached for case-insensitive table
 
            internal ResourceEnumerator(ResourceReader reader)
            {
                _currentName = ENUM_NOT_STARTED;
                _reader = reader;
                _dataPosition = -2;
            }
 
            public bool MoveNext()
            {
                if (_currentName == _reader._numResources - 1 || _currentName == ENUM_DONE)
                {
                    _currentIsValid = false;
                    _currentName = ENUM_DONE;
                    return false;
                }
                _currentIsValid = true;
                _currentName++;
                return true;
            }
 
            public object Key
            {
                get
                {
                    if (_currentName == ENUM_DONE) throw new InvalidOperationException(SR.InvalidOperation_EnumEnded);
                    if (!_currentIsValid) throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
                    if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
 
                    return _reader.AllocateStringForNameIndex(_currentName, out _dataPosition);
                }
            }
 
            public object Current => Entry;
 
            // Warning: This requires that you call the Key or Entry property FIRST before calling it!
            internal int DataPosition => _dataPosition;
 
            public DictionaryEntry Entry
            {
                get
                {
                    if (_currentName == ENUM_DONE) throw new InvalidOperationException(SR.InvalidOperation_EnumEnded);
                    if (!_currentIsValid) throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
                    if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
 
                    string key = _reader.AllocateStringForNameIndex(_currentName, out _dataPosition); // AllocateStringForNameIndex could lock on _reader
 
                    object? value = null;
                    // Lock the cache first, then the reader (in this case, we don't actually need to lock the reader and cache at the same time).
                    // Lock order MUST match RuntimeResourceSet.GetObject to avoid deadlock.
                    Debug.Assert(!Monitor.IsEntered(_reader));
                    lock (_reader._resCache)
                    {
                        if (_reader._resCache.TryGetValue(key, out ResourceLocator locator))
                        {
                            value = locator.Value;
                        }
                    }
                    if (value is null)
                    {
                        if (_dataPosition == -1)
                            value = _reader.GetValueForNameIndex(_currentName);
                        else
                            value = _reader.LoadObject(_dataPosition);
                        // If enumeration and subsequent lookups happen very
                        // frequently in the same process, add a ResourceLocator
                        // to _resCache here (we'll also need to extend the lock block!).
                        // But WinForms enumerates and just about everyone else does lookups.
                        // So caching here may bloat working set.
                    }
                    return new DictionaryEntry(key, value);
                }
            }
 
            public object? Value
            {
                get
                {
                    if (_currentName == ENUM_DONE) throw new InvalidOperationException(SR.InvalidOperation_EnumEnded);
                    if (!_currentIsValid) throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
                    if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
 
                    // Consider using _resCache here, eventually, if
                    // this proves to be an interesting perf scenario.
                    // But mixing lookups and enumerators shouldn't be
                    // particularly compelling.
                    return _reader.GetValueForNameIndex(_currentName);
                }
            }
 
            public void Reset()
            {
                if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
                _currentIsValid = false;
                _currentName = ENUM_NOT_STARTED;
            }
        }
    }
}