We need you! We're working hard on the next version of Developer Fusion -
Let us know what you think we should be up to!
- namespace System.Resources
- {
- using System;
- using System.IO;
- using System.Text;
- using System.Collections;
- using System.Collections.Generic;
- using System.Runtime.Serialization;
- using System.Runtime.Serialization.Formatters;
- using System.Runtime.Serialization.Formatters.Binary;
- using System.Reflection;
- using System.Security.Permissions;
- using System.Security;
- using System.Globalization;
- using System.Configuration.Assemblies;
- using System.Runtime.Versioning;
-
-
-
-
-
-
-
-
-
- internal struct ResourceLocator
- {
- internal object _value;
- internal int _dataPos;
-
- internal ResourceLocator(int dataPos, object value)
- {
- _dataPos = dataPos;
- _value = value;
- }
-
- internal int DataPosition {
- get { return _dataPos; }
- }
-
-
-
-
- internal object Value {
- get { return _value; }
- set { _value = value; }
- }
-
- static internal bool CanCache(ResourceTypeCode value)
- {
- BCLDebug.Assert(value >= 0, "negative ResourceTypeCode. What?");
- return value <= ResourceTypeCode.LastPrimitive;
- }
- }
-
-
- [System.Runtime.InteropServices.ComVisible(true)]
- public sealed class ResourceReader : IResourceReader
- {
- private BinaryReader _store;
-
-
-
-
- internal Dictionary<string, ResourceLocator> _resCache;
- private long _nameSectionOffset;
-
- private long _dataSectionOffset;
-
-
-
-
-
-
- private int[] _nameHashes;
-
- unsafe private int* _nameHashesPtr;
-
- private int[] _namePositions;
-
- unsafe private int* _namePositionsPtr;
-
- private Type[] _typeTable;
-
- private int[] _typeNamePositions;
-
- private BinaryFormatter _objFormatter;
-
- private int _numResources;
-
-
-
- private UnmanagedMemoryStream _ums;
-
-
- private int _version;
-
- #if RESOURCE_FILE_FORMAT_DEBUG
- private bool _debug;
-
- #endif
-
- [ResourceExposure(ResourceScope.Machine)]
- [ResourceConsumption(ResourceScope.Machine)]
- public ResourceReader(string fileName)
- {
- _resCache = new Dictionary<string, ResourceLocator>(FastResourceComparer.Default);
- _store = new BinaryReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8);
- BCLDebug.Log("RESMGRFILEFORMAT", "ResourceReader .ctor(String). UnmanagedMemoryStream: " + (_ums != null));
-
- try {
- ReadResources();
- }
- catch {
- _store.Close();
-
- throw;
- }
- }
-
- [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
- public ResourceReader(Stream stream)
- {
- if (stream == null)
- throw new ArgumentNullException("stream");
- if (!stream.CanRead)
- throw new ArgumentException(Environment.GetResourceString("Argument_StreamNotReadable"));
-
- _resCache = new Dictionary<string, ResourceLocator>(FastResourceComparer.Default);
- _store = new BinaryReader(stream, Encoding.UTF8);
-
- _ums = stream as UnmanagedMemoryStream;
-
- BCLDebug.Log("RESMGRFILEFORMAT", "ResourceReader .ctor(Stream). UnmanagedMemoryStream: " + (_ums != null));
- ReadResources();
- }
-
-
-
-
-
- internal ResourceReader(Stream stream, Dictionary<string, ResourceLocator> resCache)
- {
- BCLDebug.Assert(stream != null, "Need a stream!");
- BCLDebug.Assert(stream.CanRead, "Stream should be readable!");
- BCLDebug.Assert(resCache != null, "Need a Dictionary!");
-
- _resCache = resCache;
- _store = new BinaryReader(stream, Encoding.UTF8);
-
- _ums = stream as UnmanagedMemoryStream;
-
- BCLDebug.Log("RESMGRFILEFORMAT", "ResourceReader .ctor(Stream, Hashtable). UnmanagedMemoryStream: " + (_ums != null));
- ReadResources();
- }
-
-
- public void Close()
- {
- Dispose(true);
- }
-
- void IDisposable.Dispose()
- {
- Dispose(true);
- }
-
- unsafe private void Dispose(bool disposing)
- {
- if (_store != null) {
- _resCache = null;
- if (disposing) {
-
-
- BinaryReader copyOfStore = _store;
- _store = null;
- if (copyOfStore != null)
- copyOfStore.Close();
- }
- _store = null;
- _namePositions = null;
- _nameHashes = null;
- _ums = null;
- _namePositionsPtr = null;
- _nameHashesPtr = null;
- }
- }
-
- unsafe static internal int ReadUnalignedI4(int* p)
- {
- byte* buffer = (byte*)p;
-
- return buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24);
- }
-
- private void SkipInt32()
- {
- _store.BaseStream.Seek(4, SeekOrigin.Current);
- }
-
-
- private void SkipString()
- {
- int stringLength = _store.Read7BitEncodedInt();
- _store.BaseStream.Seek(stringLength, SeekOrigin.Current);
- }
-
- unsafe private int GetNameHash(int index)
- {
-
- BCLDebug.Assert(index >= 0 && index < _numResources, "Bad index into hash array. index: " + index);
- BCLDebug.Assert((_ums == null && _nameHashes != null && _nameHashesPtr == null) || (_ums != null && _nameHashes == null && _nameHashesPtr != null), "Internal state mangled.");
- if (_ums == null)
- return _nameHashes[index];
- else
- return ReadUnalignedI4(&_nameHashesPtr[index]);
- }
-
- unsafe private int GetNamePosition(int index)
- {
- BCLDebug.Assert(index >= 0 && index < _numResources, "Bad index into name position array. index: " + index);
- BCLDebug.Assert((_ums == null && _namePositions != null && _namePositionsPtr == null) || (_ums != null && _namePositions == null && _namePositionsPtr != null), "Internal state mangled.");
- int r;
- if (_ums == null)
- r = _namePositions[index];
- else
- r = ReadUnalignedI4(&_namePositionsPtr[index]);
- if (r < 0 || r > _dataSectionOffset - _nameSectionOffset) {
- BCLDebug.Assert(false, "Corrupt .resources file! NamePosition is outside of the name section!");
- throw new FormatException(Environment.GetResourceString("BadImageFormat_ResourcesNameOutOfSection", index, r.ToString("x", CultureInfo.InvariantCulture)));
- }
- return r;
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- public IDictionaryEnumerator GetEnumerator()
- {
- if (_resCache == null)
- throw new InvalidOperationException(Environment.GetResourceString("ResourceReaderIsClosed"));
- return new ResourceEnumerator(this);
- }
-
- internal ResourceEnumerator GetEnumeratorInternal()
- {
- return new ResourceEnumerator(this);
- }
-
-
-
-
-
- internal int FindPosForResource(string name)
- {
- BCLDebug.Assert(_store != null, "ResourceReader is closed!");
- int hash = FastResourceComparer.HashFunction(name);
- BCLDebug.Log("RESMGRFILEFORMAT", "FindPosForResource for " + name + " hash: " + hash.ToString("x", CultureInfo.InvariantCulture));
-
-
- int lo = 0;
- int hi = _numResources - 1;
- int index = -1;
- bool success = false;
- while (lo <= hi) {
- index = (lo + hi) >> 1;
-
-
- 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) {
- #if RESOURCE_FILE_FORMAT_DEBUG
- string lastReadString;
- lock (this) {
- _store.BaseStream.Seek(_nameSectionOffset + GetNamePosition(index), SeekOrigin.Begin);
- lastReadString = _store.ReadString();
- }
- BCLDebug.Log("RESMGRFILEFORMAT", LogLevel.Status, "FindPosForResource for ", name, " failed. i: ", index, " lo: ", lo, " hi: ", hi,
- " last read string: \"", lastReadString, '\'');
- #endif
- return -1;
- }
-
-
-
-
-
-
- if (lo != index) {
- lo = index;
- while (lo > 0 && GetNameHash(lo - 1) == hash)
- lo--;
- }
- if (hi != index) {
- hi = index;
- while (hi < _numResources && 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();
- BCLDebug.Assert(dataPos >= 0 || dataPos < _store.BaseStream.Length - _dataSectionOffset, "Data section relative offset is out of the bounds of the data section! dataPos: " + dataPos);
- return dataPos;
- }
- }
- }
- BCLDebug.Log("RESMGRFILEFORMAT", "FindPosForResource for " + name + ": Found a hash collision, HOWEVER, neither of these collided values equaled the given string.");
- return -1;
- }
-
-
-
-
-
- unsafe private bool CompareStringEqualsName(string name)
- {
- BCLDebug.Assert(_store != null, "ResourceReader is closed!");
-
- int byteLen = _store.Read7BitEncodedInt();
- if (_ums != null) {
-
- byte* bytes = _ums.PositionPointer;
-
- _ums.Seek(byteLen, SeekOrigin.Current);
-
- if (_ums.Position > _ums.Length)
- throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesNameTooLong"));
-
-
-
-
- return FastResourceComparer.CompareOrdinal(bytes, byteLen, name) == 0;
- }
- else {
-
- 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(Environment.GetResourceString("BadImageFormat_ResourceNameCorrupted"));
- numBytesToRead -= n;
- }
- return FastResourceComparer.CompareOrdinal(bytes, byteLen / 2, name) == 0;
- }
- }
-
-
-
-
- unsafe private string AllocateStringForNameIndex(int index, out int dataOffset)
- {
- BCLDebug.Assert(_store != null, "ResourceReader is closed!");
- byte[] bytes;
- int byteLen;
- long nameVA = GetNamePosition(index);
- lock (this) {
- _store.BaseStream.Seek(nameVA + _nameSectionOffset, SeekOrigin.Begin);
-
- byteLen = _store.Read7BitEncodedInt();
-
- if (_ums != null) {
- if (_ums.Position > _ums.Length - byteLen)
- throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesIndexTooLong", index));
-
- char* charPtr = (char*)_ums.PositionPointer;
- string s = new string(charPtr, 0, byteLen / 2);
- _ums.Position += byteLen;
- dataOffset = _store.ReadInt32();
- return s;
- }
-
- bytes = new byte[byteLen];
-
-
-
- int count = byteLen;
- while (count > 0) {
- int n = _store.Read(bytes, byteLen - count, count);
- if (n == 0)
- throw new EndOfStreamException(Environment.GetResourceString("BadImageFormat_ResourceNameCorrupted_NameIndex", index));
- count -= n;
- }
- dataOffset = _store.ReadInt32();
- }
- return Encoding.Unicode.GetString(bytes, 0, byteLen);
- }
-
-
-
-
- private object GetValueForNameIndex(int index)
- {
- BCLDebug.Assert(_store != null, "ResourceReader is closed!");
- long nameVA = GetNamePosition(index);
- lock (this) {
- _store.BaseStream.Seek(nameVA + _nameSectionOffset, SeekOrigin.Begin);
- SkipString();
-
- int dataPos = _store.ReadInt32();
- BCLDebug.Log("RESMGRFILEFORMAT", "GetValueForNameIndex: dataPos: " + dataPos);
- ResourceTypeCode junk;
- if (_version == 1)
- return LoadObjectV1(dataPos);
- else
- return LoadObjectV2(dataPos, out junk);
- }
- }
-
-
-
-
-
- internal string LoadString(int pos)
- {
- BCLDebug.Assert(_store != null, "ResourceReader is closed!");
- _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(Environment.GetResourceString("InvalidOperation_ResourceNotString_Type", FindType(typeIndex).GetType().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(Environment.GetResourceString("InvalidOperation_ResourceNotString_Type", typeString));
- }
- if (typeCode == ResourceTypeCode.String)
-
- s = _store.ReadString();
- }
- BCLDebug.Log("RESMGRFILEFORMAT", "LoadString(" + pos.ToString("x", CultureInfo.InvariantCulture) + " returned " + (s == null ? "[a null string]" : s));
- return s;
- }
-
-
- internal object LoadObject(int pos)
- {
- if (_version == 1)
- return LoadObjectV1(pos);
- ResourceTypeCode typeCode;
- return LoadObjectV2(pos, out typeCode);
- }
-
- internal object LoadObject(int pos, out ResourceTypeCode typeCode)
- {
- if (_version == 1) {
- object o = LoadObjectV1(pos);
- typeCode = (o is string) ? ResourceTypeCode.String : ResourceTypeCode.StartOfUserTypes;
- return o;
- }
- return LoadObjectV2(pos, out typeCode);
- }
-
-
-
-
-
-
- internal object LoadObjectV1(int pos)
- {
- BCLDebug.Assert(_store != null, "ResourceReader is closed!");
- BCLDebug.Assert(_version == 1, ".resources file was not a V1 .resources file!");
- _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin);
- int typeIndex = _store.Read7BitEncodedInt();
- if (typeIndex == -1)
- return null;
- Type type = FindType(typeIndex);
- BCLDebug.Log("RESMGRFILEFORMAT", "LoadObject type: " + type.Name + " pos: 0x" + _store.BaseStream.Position.ToString("x", CultureInfo.InvariantCulture));
- if (type == typeof(string))
- return _store.ReadString();
- else if (type == typeof(Int32))
- return _store.ReadInt32();
- else if (type == typeof(byte))
- return _store.ReadByte();
- else if (type == typeof(sbyte))
- return _store.ReadSByte();
- else if (type == typeof(Int16))
- return _store.ReadInt16();
- else if (type == typeof(Int64))
- return _store.ReadInt64();
- else if (type == typeof(UInt16))
- return _store.ReadUInt16();
- else if (type == typeof(UInt32))
- return _store.ReadUInt32();
- else if (type == typeof(UInt64))
- return _store.ReadUInt64();
- else if (type == typeof(float))
- return _store.ReadSingle();
- else if (type == typeof(double))
- return _store.ReadDouble();
- else if (type == typeof(DateTime)) {
-
-
- return new DateTime(_store.ReadInt64());
- }
- else if (type == typeof(TimeSpan))
- return new TimeSpan(_store.ReadInt64());
- else if (type == typeof(decimal)) {
- int[] bits = new int[4];
- for (int i = 0; i < bits.Length; i++)
- bits[i] = _store.ReadInt32();
- return new decimal(bits);
- }
- else {
- return DeserializeObject(typeIndex);
- }
- }
-
- internal object LoadObjectV2(int pos, out ResourceTypeCode typeCode)
- {
- BCLDebug.Assert(_store != null, "ResourceReader is closed!");
- BCLDebug.Assert(_version >= 2, ".resources file was not a V2 (or higher) .resources file!");
- _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin);
- typeCode = (ResourceTypeCode)_store.Read7BitEncodedInt();
-
- BCLDebug.Log("RESMGRFILEFORMAT", "LoadObjectV2 type: " + typeCode + " pos: 0x" + _store.BaseStream.Position.ToString("x", CultureInfo.InvariantCulture));
-
- 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:
-
-
- Int64 data = _store.ReadInt64();
- return DateTime.FromBinary(data);
- case ResourceTypeCode.TimeSpan:
-
- Int64 ticks = _store.ReadInt64();
- return new TimeSpan(ticks);
- case ResourceTypeCode.ByteArray:
-
-
-
- {
- int len = _store.ReadInt32();
- if (_ums == null)
- return _store.ReadBytes(len);
-
- if (len > _ums.Length - _ums.Position)
- throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourceDataTooLong"));
-
- byte[] bytes = new byte[len];
- int r = _ums.Read(bytes, 0, len);
- BCLDebug.Assert(r == len, "ResourceReader needs to use a blocking read here. (Call _store.ReadBytes(len)?)");
- return bytes;
- }
- break;
- case ResourceTypeCode.Stream:
-
-
- {
- int len = _store.ReadInt32();
- if (_ums == null) {
- byte[] bytes = _store.ReadBytes(len);
-
- return new PinnedBufferMemoryStream(bytes);
- }
-
-
- if (len > _ums.Length - _ums.Position)
- throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourceDataTooLong"));
-
-
-
- unsafe {
- return new UnmanagedMemoryStream(_ums.PositionPointer, len, len, FileAccess.Read, true);
- }
- }
- break;
- default:
-
- BCLDebug.Assert(typeCode >= ResourceTypeCode.StartOfUserTypes, String.Format(CultureInfo.InvariantCulture, "ResourceReader: Unsupported ResourceTypeCode in .resources file! {0}", typeCode));
-
- break;
- }
-
-
- int typeIndex = typeCode - ResourceTypeCode.StartOfUserTypes;
- return DeserializeObject(typeIndex);
- }
-
-
-
-
-
- private object DeserializeObject(int typeIndex)
- {
- Type type = FindType(typeIndex);
-
-
-
-
-
-
-
- object graph;
- graph = _objFormatter.Deserialize(_store.BaseStream);
-
-
- if (graph.GetType() != type)
- throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResType&SerBlobMismatch", type.FullName, graph.GetType().FullName));
-
- return graph;
- }
-
-
-
-
- private void ReadResources()
- {
- BCLDebug.Assert(_store != null, "ResourceReader is closed!");
- BinaryFormatter bf = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File | StreamingContextStates.Persistence));
- _objFormatter = bf;
-
- try {
-
-
- int magicNum = _store.ReadInt32();
- if (magicNum != ResourceManager.MagicNumber)
- throw new ArgumentException(Environment.GetResourceString("Resources_StreamNotValid"));
-
-
-
- int resMgrHeaderVersion = _store.ReadInt32();
- if (resMgrHeaderVersion > 1) {
- int numBytesToSkip = _store.ReadInt32();
- BCLDebug.Log("RESMGRFILEFORMAT", LogLevel.Status, "ReadResources: Unexpected ResMgr header version: {0} Skipping ahead {1} bytes.", resMgrHeaderVersion, numBytesToSkip);
- BCLDebug.Assert(numBytesToSkip >= 0, "numBytesToSkip in ResMgr header should be positive!");
- _store.BaseStream.Seek(numBytesToSkip, SeekOrigin.Current);
- }
- else {
- BCLDebug.Log("RESMGRFILEFORMAT", "ReadResources: Parsing ResMgr header v1.");
- SkipInt32();
-
-
-
- string readerType = _store.ReadString();
- AssemblyName mscorlib = new AssemblyName(ResourceManager.MscorlibName);
-
- if (!ResourceManager.CompareNames(readerType, ResourceManager.ResReaderTypeName, mscorlib))
- throw new NotSupportedException(Environment.GetResourceString("NotSupported_WrongResourceReader_Type", readerType));
-
-
- SkipString();
- }
-
-
-
- int version = _store.ReadInt32();
- if (version != RuntimeResourceSet.Version && version != 1)
- throw new ArgumentException(Environment.GetResourceString("Arg_ResourceFileUnsupportedVersion", RuntimeResourceSet.Version, version));
- _version = version;
-
- #if RESOURCE_FILE_FORMAT_DEBUG
-
- long oldPos = _store.BaseStream.Position;
- _debug = false;
- try {
- string debugString = _store.ReadString();
- _debug = String.Equals("***DEBUG***", debugString);
- }
- catch (IOException) {
- }
- catch (OutOfMemoryException) {
- }
- if (_debug) {
- Console.WriteLine("ResourceReader is looking at a debuggable .resources file, version {0}", _version);
- }
- else {
- _store.BaseStream.Position = oldPos;
- }
- #endif
-
- _numResources = _store.ReadInt32();
- BCLDebug.Log("RESMGRFILEFORMAT", "ReadResources: Expecting " + _numResources + " resources.");
- #if _DEBUG
- if (ResourceManager.DEBUG >= 4)
- Console.WriteLine("ResourceReader::ReadResources - Reading in " + _numResources + " resources");
- #endif
-
-
-
- int numTypes = _store.ReadInt32();
- _typeTable = new Type[numTypes];
- _typeNamePositions = new int[numTypes];
- for (int i = 0; i < numTypes; i++) {
- _typeNamePositions[i] = (int)_store.BaseStream.Position;
-
-
- SkipString();
- }
-
- #if _DEBUG
- if (ResourceManager.DEBUG >= 5)
- Console.WriteLine("ResourceReader::ReadResources - Reading in " + numTypes + " type table entries");
- #endif
-
-
-
-
-
-
-
- long pos = _store.BaseStream.Position;
- int alignBytes = ((int)pos) & 7;
- if (alignBytes != 0) {
- for (int i = 0; i < 8 - alignBytes; i++) {
- _store.ReadByte();
- }
- }
-
-
- #if RESOURCE_FILE_FORMAT_DEBUG
-
- if (_debug) {
- _store.BaseStream.Position += 8;
- }
- #endif
-
- if (_ums == null) {
- _nameHashes = new int[_numResources];
- for (int i = 0; i < _numResources; i++)
- _nameHashes[i] = _store.ReadInt32();
- }
- else {
- unsafe {
- _nameHashesPtr = (int*)_ums.PositionPointer;
-
- _ums.Seek(4 * _numResources, SeekOrigin.Current);
-
- byte* junk = _ums.PositionPointer;
- }
- }
-
-
- #if RESOURCE_FILE_FORMAT_