ResourceReader.cs 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. #nullable enable
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. using System.Diagnostics;
  8. using System.IO;
  9. using System.Text;
  10. namespace System.Resources
  11. #if RESOURCES_EXTENSIONS
  12. .Extensions
  13. #endif
  14. {
  15. #if RESOURCES_EXTENSIONS
  16. using ResourceReader = DeserializingResourceReader;
  17. #endif
  18. // Provides the default implementation of IResourceReader, reading
  19. // .resources file from the system default binary format. This class
  20. // can be treated as an enumerator once.
  21. //
  22. // See the RuntimeResourceSet overview for details on the system
  23. // default file format.
  24. //
  25. internal struct ResourceLocator
  26. {
  27. internal object? _value; // Can be null.
  28. internal int _dataPos;
  29. internal ResourceLocator(int dataPos, object? value)
  30. {
  31. _dataPos = dataPos;
  32. _value = value;
  33. }
  34. internal int DataPosition => _dataPos;
  35. // Allows adding in profiling data in a future version, or a special
  36. // resource profiling build. We could also use WeakReference.
  37. internal object? Value
  38. {
  39. get => _value;
  40. set => _value = value;
  41. }
  42. internal static bool CanCache(ResourceTypeCode value)
  43. {
  44. Debug.Assert(value >= 0, "negative ResourceTypeCode. What?");
  45. return value <= ResourceTypeCode.LastPrimitive;
  46. }
  47. }
  48. public sealed partial class
  49. #if RESOURCES_EXTENSIONS
  50. DeserializingResourceReader
  51. #else
  52. ResourceReader
  53. #endif
  54. : IResourceReader
  55. {
  56. // A reasonable default buffer size for reading from files, especially
  57. // when we will likely be seeking frequently. Could be smaller, but does
  58. // it make sense to use anything less than one page?
  59. private const int DefaultFileStreamBufferSize = 4096;
  60. private BinaryReader _store; // backing store we're reading from.
  61. // Used by RuntimeResourceSet and this class's enumerator. Maps
  62. // resource name to a value, a ResourceLocator, or a
  63. // LooselyLinkedManifestResource.
  64. internal Dictionary<string, ResourceLocator>? _resCache;
  65. private long _nameSectionOffset; // Offset to name section of file.
  66. private long _dataSectionOffset; // Offset to Data section of file.
  67. // Note this class is tightly coupled with UnmanagedMemoryStream.
  68. // At runtime when getting an embedded resource from an assembly,
  69. // we're given an UnmanagedMemoryStream referring to the mmap'ed portion
  70. // of the assembly. The pointers here are pointers into that block of
  71. // memory controlled by the OS's loader.
  72. private int[]? _nameHashes; // hash values for all names.
  73. private unsafe int* _nameHashesPtr; // In case we're using UnmanagedMemoryStream
  74. private int[]? _namePositions; // relative locations of names
  75. private unsafe int* _namePositionsPtr; // If we're using UnmanagedMemoryStream
  76. private Type?[] _typeTable = null!; // Lazy array of Types for resource values.
  77. private int[] _typeNamePositions = null!; // To delay initialize type table
  78. private int _numResources; // Num of resources files, in case arrays aren't allocated.
  79. // We'll include a separate code path that uses UnmanagedMemoryStream to
  80. // avoid allocating String objects and the like.
  81. private UnmanagedMemoryStream? _ums;
  82. // Version number of .resources file, for compatibility
  83. private int _version;
  84. public
  85. #if RESOURCES_EXTENSIONS
  86. DeserializingResourceReader(string fileName)
  87. #else
  88. ResourceReader(string fileName)
  89. #endif
  90. {
  91. _resCache = new Dictionary<string, ResourceLocator>(FastResourceComparer.Default);
  92. _store = new BinaryReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.RandomAccess), Encoding.UTF8);
  93. try
  94. {
  95. ReadResources();
  96. }
  97. catch
  98. {
  99. _store.Close(); // If we threw an exception, close the file.
  100. throw;
  101. }
  102. }
  103. public
  104. #if RESOURCES_EXTENSIONS
  105. DeserializingResourceReader(Stream stream)
  106. #else
  107. ResourceReader(Stream stream)
  108. #endif
  109. {
  110. if (stream == null)
  111. throw new ArgumentNullException(nameof(stream));
  112. if (!stream.CanRead)
  113. throw new ArgumentException(SR.Argument_StreamNotReadable);
  114. _resCache = new Dictionary<string, ResourceLocator>(FastResourceComparer.Default);
  115. _store = new BinaryReader(stream, Encoding.UTF8);
  116. // We have a faster code path for reading resource files from an assembly.
  117. _ums = stream as UnmanagedMemoryStream;
  118. ReadResources();
  119. }
  120. public void Close()
  121. {
  122. Dispose(true);
  123. }
  124. public void Dispose()
  125. {
  126. Close();
  127. }
  128. private unsafe void Dispose(bool disposing)
  129. {
  130. if (_store != null)
  131. {
  132. _resCache = null;
  133. if (disposing)
  134. {
  135. // Close the stream in a thread-safe way. This fix means
  136. // that we may call Close n times, but that's safe.
  137. BinaryReader copyOfStore = _store;
  138. _store = null!; // TODO-NULLABLE: Avoid nulling out in Dispose
  139. if (copyOfStore != null)
  140. copyOfStore.Close();
  141. }
  142. _store = null!; // TODO-NULLABLE: Avoid nulling out in Dispose
  143. _namePositions = null;
  144. _nameHashes = null;
  145. _ums = null;
  146. _namePositionsPtr = null;
  147. _nameHashesPtr = null;
  148. }
  149. }
  150. internal static unsafe int ReadUnalignedI4(int* p)
  151. {
  152. byte* buffer = (byte*)p;
  153. // Unaligned, little endian format
  154. return buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24);
  155. }
  156. private void SkipString()
  157. {
  158. int stringLength = _store.Read7BitEncodedInt();
  159. if (stringLength < 0)
  160. {
  161. throw new BadImageFormatException(SR.BadImageFormat_NegativeStringLength);
  162. }
  163. _store.BaseStream.Seek(stringLength, SeekOrigin.Current);
  164. }
  165. private unsafe int GetNameHash(int index)
  166. {
  167. Debug.Assert(index >= 0 && index < _numResources, "Bad index into hash array. index: " + index);
  168. if (_ums == null)
  169. {
  170. Debug.Assert(_nameHashes != null && _nameHashesPtr == null, "Internal state mangled.");
  171. return _nameHashes[index];
  172. }
  173. else
  174. {
  175. Debug.Assert(_nameHashes == null && _nameHashesPtr != null, "Internal state mangled.");
  176. return ReadUnalignedI4(&_nameHashesPtr[index]);
  177. }
  178. }
  179. private unsafe int GetNamePosition(int index)
  180. {
  181. Debug.Assert(index >= 0 && index < _numResources, "Bad index into name position array. index: " + index);
  182. int r;
  183. if (_ums == null)
  184. {
  185. Debug.Assert(_namePositions != null && _namePositionsPtr == null, "Internal state mangled.");
  186. r = _namePositions[index];
  187. }
  188. else
  189. {
  190. Debug.Assert(_namePositions == null && _namePositionsPtr != null, "Internal state mangled.");
  191. r = ReadUnalignedI4(&_namePositionsPtr[index]);
  192. }
  193. if (r < 0 || r > _dataSectionOffset - _nameSectionOffset)
  194. {
  195. throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesNameInvalidOffset, r));
  196. }
  197. return r;
  198. }
  199. IEnumerator IEnumerable.GetEnumerator()
  200. {
  201. return GetEnumerator();
  202. }
  203. public IDictionaryEnumerator GetEnumerator()
  204. {
  205. if (_resCache == null)
  206. throw new InvalidOperationException(SR.ResourceReaderIsClosed);
  207. return new ResourceEnumerator(this);
  208. }
  209. internal ResourceEnumerator GetEnumeratorInternal()
  210. {
  211. return new ResourceEnumerator(this);
  212. }
  213. // From a name, finds the associated virtual offset for the data.
  214. // To read the data, seek to _dataSectionOffset + dataPos, then
  215. // read the resource type & data.
  216. // This does a binary search through the names.
  217. internal int FindPosForResource(string name)
  218. {
  219. Debug.Assert(_store != null, "ResourceReader is closed!");
  220. int hash = FastResourceComparer.HashFunction(name);
  221. // Binary search over the hashes. Use the _namePositions array to
  222. // determine where they exist in the underlying stream.
  223. int lo = 0;
  224. int hi = _numResources - 1;
  225. int index = -1;
  226. bool success = false;
  227. while (lo <= hi)
  228. {
  229. index = (lo + hi) >> 1;
  230. // Do NOT use subtraction here, since it will wrap for large
  231. // negative numbers.
  232. int currentHash = GetNameHash(index);
  233. int c;
  234. if (currentHash == hash)
  235. c = 0;
  236. else if (currentHash < hash)
  237. c = -1;
  238. else
  239. c = 1;
  240. if (c == 0)
  241. {
  242. success = true;
  243. break;
  244. }
  245. if (c < 0)
  246. lo = index + 1;
  247. else
  248. hi = index - 1;
  249. }
  250. if (!success)
  251. {
  252. return -1;
  253. }
  254. // index is the location in our hash array that corresponds with a
  255. // value in the namePositions array.
  256. // There could be collisions in our hash function. Check on both sides
  257. // of index to find the range of hash values that are equal to the
  258. // target hash value.
  259. if (lo != index)
  260. {
  261. lo = index;
  262. while (lo > 0 && GetNameHash(lo - 1) == hash)
  263. lo--;
  264. }
  265. if (hi != index)
  266. {
  267. hi = index;
  268. while (hi < _numResources - 1 && GetNameHash(hi + 1) == hash)
  269. hi++;
  270. }
  271. lock (this)
  272. {
  273. for (int i = lo; i <= hi; i++)
  274. {
  275. _store.BaseStream.Seek(_nameSectionOffset + GetNamePosition(i), SeekOrigin.Begin);
  276. if (CompareStringEqualsName(name))
  277. {
  278. int dataPos = _store.ReadInt32();
  279. if (dataPos < 0 || dataPos >= _store.BaseStream.Length - _dataSectionOffset)
  280. {
  281. throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataPos));
  282. }
  283. return dataPos;
  284. }
  285. }
  286. }
  287. return -1;
  288. }
  289. // This compares the String in the .resources file at the current position
  290. // with the string you pass in.
  291. // Whoever calls this method should make sure that they take a lock
  292. // so no one else can cause us to seek in the stream.
  293. private unsafe bool CompareStringEqualsName(string name)
  294. {
  295. Debug.Assert(_store != null, "ResourceReader is closed!");
  296. int byteLen = _store.Read7BitEncodedInt();
  297. if (byteLen < 0)
  298. {
  299. throw new BadImageFormatException(SR.BadImageFormat_NegativeStringLength);
  300. }
  301. if (_ums != null)
  302. {
  303. byte* bytes = _ums.PositionPointer;
  304. // Skip over the data in the Stream, positioning ourselves right after it.
  305. _ums.Seek(byteLen, SeekOrigin.Current);
  306. if (_ums.Position > _ums.Length)
  307. {
  308. throw new BadImageFormatException(SR.BadImageFormat_ResourcesNameTooLong);
  309. }
  310. // On 64-bit machines, these char*'s may be misaligned. Use a
  311. // byte-by-byte comparison instead.
  312. return FastResourceComparer.CompareOrdinal(bytes, byteLen, name) == 0;
  313. }
  314. else
  315. {
  316. // This code needs to be fast
  317. byte[] bytes = new byte[byteLen];
  318. int numBytesToRead = byteLen;
  319. while (numBytesToRead > 0)
  320. {
  321. int n = _store.Read(bytes, byteLen - numBytesToRead, numBytesToRead);
  322. if (n == 0)
  323. throw new BadImageFormatException(SR.BadImageFormat_ResourceNameCorrupted);
  324. numBytesToRead -= n;
  325. }
  326. return FastResourceComparer.CompareOrdinal(bytes, byteLen / 2, name) == 0;
  327. }
  328. }
  329. // This is used in the enumerator. The enumerator iterates from 0 to n
  330. // of our resources and this returns the resource name for a particular
  331. // index. The parameter is NOT a virtual offset.
  332. private unsafe string AllocateStringForNameIndex(int index, out int dataOffset)
  333. {
  334. Debug.Assert(_store != null, "ResourceReader is closed!");
  335. byte[] bytes;
  336. int byteLen;
  337. long nameVA = GetNamePosition(index);
  338. lock (this)
  339. {
  340. _store.BaseStream.Seek(nameVA + _nameSectionOffset, SeekOrigin.Begin);
  341. // Can't use _store.ReadString, since it's using UTF-8!
  342. byteLen = _store.Read7BitEncodedInt();
  343. if (byteLen < 0)
  344. {
  345. throw new BadImageFormatException(SR.BadImageFormat_NegativeStringLength);
  346. }
  347. if (_ums != null)
  348. {
  349. if (_ums.Position > _ums.Length - byteLen)
  350. throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourcesIndexTooLong, index));
  351. string? s = null;
  352. char* charPtr = (char*)_ums.PositionPointer;
  353. s = new string(charPtr, 0, byteLen / 2);
  354. _ums.Position += byteLen;
  355. dataOffset = _store.ReadInt32();
  356. if (dataOffset < 0 || dataOffset >= _store.BaseStream.Length - _dataSectionOffset)
  357. {
  358. throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataOffset));
  359. }
  360. return s;
  361. }
  362. bytes = new byte[byteLen];
  363. // We must read byteLen bytes, or we have a corrupted file.
  364. // Use a blocking read in case the stream doesn't give us back
  365. // everything immediately.
  366. int count = byteLen;
  367. while (count > 0)
  368. {
  369. int n = _store.Read(bytes, byteLen - count, count);
  370. if (n == 0)
  371. throw new EndOfStreamException(SR.Format(SR.BadImageFormat_ResourceNameCorrupted_NameIndex, index));
  372. count -= n;
  373. }
  374. dataOffset = _store.ReadInt32();
  375. if (dataOffset < 0 || dataOffset >= _store.BaseStream.Length - _dataSectionOffset)
  376. {
  377. throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataOffset));
  378. }
  379. }
  380. return Encoding.Unicode.GetString(bytes, 0, byteLen);
  381. }
  382. // This is used in the enumerator. The enumerator iterates from 0 to n
  383. // of our resources and this returns the resource value for a particular
  384. // index. The parameter is NOT a virtual offset.
  385. private object? GetValueForNameIndex(int index)
  386. {
  387. Debug.Assert(_store != null, "ResourceReader is closed!");
  388. long nameVA = GetNamePosition(index);
  389. lock (this)
  390. {
  391. _store.BaseStream.Seek(nameVA + _nameSectionOffset, SeekOrigin.Begin);
  392. SkipString();
  393. int dataPos = _store.ReadInt32();
  394. if (dataPos < 0 || dataPos >= _store.BaseStream.Length - _dataSectionOffset)
  395. {
  396. throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataPos));
  397. }
  398. ResourceTypeCode junk;
  399. if (_version == 1)
  400. return LoadObjectV1(dataPos);
  401. else
  402. return LoadObjectV2(dataPos, out junk);
  403. }
  404. }
  405. // This takes a virtual offset into the data section and reads a String
  406. // from that location.
  407. // Anyone who calls LoadObject should make sure they take a lock so
  408. // no one can cause us to do a seek in here.
  409. internal string? LoadString(int pos)
  410. {
  411. Debug.Assert(_store != null, "ResourceReader is closed!");
  412. _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin);
  413. string? s = null;
  414. int typeIndex = _store.Read7BitEncodedInt();
  415. if (_version == 1)
  416. {
  417. if (typeIndex == -1)
  418. return null;
  419. if (FindType(typeIndex) != typeof(string))
  420. throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Type, FindType(typeIndex).FullName));
  421. s = _store.ReadString();
  422. }
  423. else
  424. {
  425. ResourceTypeCode typeCode = (ResourceTypeCode)typeIndex;
  426. if (typeCode != ResourceTypeCode.String && typeCode != ResourceTypeCode.Null)
  427. {
  428. string? typeString;
  429. if (typeCode < ResourceTypeCode.StartOfUserTypes)
  430. typeString = typeCode.ToString();
  431. else
  432. typeString = FindType(typeCode - ResourceTypeCode.StartOfUserTypes).FullName;
  433. throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Type, typeString));
  434. }
  435. if (typeCode == ResourceTypeCode.String) // ignore Null
  436. s = _store.ReadString();
  437. }
  438. return s;
  439. }
  440. // Called from RuntimeResourceSet
  441. internal object? LoadObject(int pos)
  442. {
  443. if (_version == 1)
  444. return LoadObjectV1(pos);
  445. return LoadObjectV2(pos, out _);
  446. }
  447. internal object? LoadObject(int pos, out ResourceTypeCode typeCode)
  448. {
  449. if (_version == 1)
  450. {
  451. object? o = LoadObjectV1(pos);
  452. typeCode = (o is string) ? ResourceTypeCode.String : ResourceTypeCode.StartOfUserTypes;
  453. return o;
  454. }
  455. return LoadObjectV2(pos, out typeCode);
  456. }
  457. // This takes a virtual offset into the data section and reads an Object
  458. // from that location.
  459. // Anyone who calls LoadObject should make sure they take a lock so
  460. // no one can cause us to do a seek in here.
  461. internal object? LoadObjectV1(int pos)
  462. {
  463. Debug.Assert(_store != null, "ResourceReader is closed!");
  464. Debug.Assert(_version == 1, ".resources file was not a V1 .resources file!");
  465. try
  466. {
  467. // mega try-catch performs exceptionally bad on x64; factored out body into
  468. // _LoadObjectV1 and wrap here.
  469. return _LoadObjectV1(pos);
  470. }
  471. catch (EndOfStreamException eof)
  472. {
  473. throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, eof);
  474. }
  475. catch (ArgumentOutOfRangeException e)
  476. {
  477. throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, e);
  478. }
  479. }
  480. private object? _LoadObjectV1(int pos)
  481. {
  482. _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin);
  483. int typeIndex = _store.Read7BitEncodedInt();
  484. if (typeIndex == -1)
  485. return null;
  486. Type type = FindType(typeIndex);
  487. // Consider putting in logic to see if this type is a
  488. // primitive or a value type first, so we can reach the
  489. // deserialization code faster for arbitrary objects.
  490. if (type == typeof(string))
  491. return _store.ReadString();
  492. else if (type == typeof(int))
  493. return _store.ReadInt32();
  494. else if (type == typeof(byte))
  495. return _store.ReadByte();
  496. else if (type == typeof(sbyte))
  497. return _store.ReadSByte();
  498. else if (type == typeof(short))
  499. return _store.ReadInt16();
  500. else if (type == typeof(long))
  501. return _store.ReadInt64();
  502. else if (type == typeof(ushort))
  503. return _store.ReadUInt16();
  504. else if (type == typeof(uint))
  505. return _store.ReadUInt32();
  506. else if (type == typeof(ulong))
  507. return _store.ReadUInt64();
  508. else if (type == typeof(float))
  509. return _store.ReadSingle();
  510. else if (type == typeof(double))
  511. return _store.ReadDouble();
  512. else if (type == typeof(DateTime))
  513. {
  514. // Ideally we should use DateTime's ToBinary & FromBinary,
  515. // but we can't for compatibility reasons.
  516. return new DateTime(_store.ReadInt64());
  517. }
  518. else if (type == typeof(TimeSpan))
  519. return new TimeSpan(_store.ReadInt64());
  520. else if (type == typeof(decimal))
  521. {
  522. int[] bits = new int[4];
  523. for (int i = 0; i < bits.Length; i++)
  524. bits[i] = _store.ReadInt32();
  525. return new decimal(bits);
  526. }
  527. else
  528. {
  529. return DeserializeObject(typeIndex);
  530. }
  531. }
  532. internal object? LoadObjectV2(int pos, out ResourceTypeCode typeCode)
  533. {
  534. Debug.Assert(_store != null, "ResourceReader is closed!");
  535. Debug.Assert(_version >= 2, ".resources file was not a V2 (or higher) .resources file!");
  536. try
  537. {
  538. // mega try-catch performs exceptionally bad on x64; factored out body into
  539. // _LoadObjectV2 and wrap here.
  540. return _LoadObjectV2(pos, out typeCode);
  541. }
  542. catch (EndOfStreamException eof)
  543. {
  544. throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, eof);
  545. }
  546. catch (ArgumentOutOfRangeException e)
  547. {
  548. throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, e);
  549. }
  550. }
  551. private object? _LoadObjectV2(int pos, out ResourceTypeCode typeCode)
  552. {
  553. _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin);
  554. typeCode = (ResourceTypeCode)_store.Read7BitEncodedInt();
  555. switch (typeCode)
  556. {
  557. case ResourceTypeCode.Null:
  558. return null;
  559. case ResourceTypeCode.String:
  560. return _store.ReadString();
  561. case ResourceTypeCode.Boolean:
  562. return _store.ReadBoolean();
  563. case ResourceTypeCode.Char:
  564. return (char)_store.ReadUInt16();
  565. case ResourceTypeCode.Byte:
  566. return _store.ReadByte();
  567. case ResourceTypeCode.SByte:
  568. return _store.ReadSByte();
  569. case ResourceTypeCode.Int16:
  570. return _store.ReadInt16();
  571. case ResourceTypeCode.UInt16:
  572. return _store.ReadUInt16();
  573. case ResourceTypeCode.Int32:
  574. return _store.ReadInt32();
  575. case ResourceTypeCode.UInt32:
  576. return _store.ReadUInt32();
  577. case ResourceTypeCode.Int64:
  578. return _store.ReadInt64();
  579. case ResourceTypeCode.UInt64:
  580. return _store.ReadUInt64();
  581. case ResourceTypeCode.Single:
  582. return _store.ReadSingle();
  583. case ResourceTypeCode.Double:
  584. return _store.ReadDouble();
  585. case ResourceTypeCode.Decimal:
  586. return _store.ReadDecimal();
  587. case ResourceTypeCode.DateTime:
  588. // Use DateTime's ToBinary & FromBinary.
  589. long data = _store.ReadInt64();
  590. return DateTime.FromBinary(data);
  591. case ResourceTypeCode.TimeSpan:
  592. long ticks = _store.ReadInt64();
  593. return new TimeSpan(ticks);
  594. // Special types
  595. case ResourceTypeCode.ByteArray:
  596. {
  597. int len = _store.ReadInt32();
  598. if (len < 0)
  599. {
  600. throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
  601. }
  602. if (_ums == null)
  603. {
  604. if (len > _store.BaseStream.Length)
  605. {
  606. throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
  607. }
  608. return _store.ReadBytes(len);
  609. }
  610. if (len > _ums.Length - _ums.Position)
  611. {
  612. throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
  613. }
  614. byte[] bytes = new byte[len];
  615. int r = _ums.Read(bytes, 0, len);
  616. Debug.Assert(r == len, "ResourceReader needs to use a blocking read here. (Call _store.ReadBytes(len)?)");
  617. return bytes;
  618. }
  619. case ResourceTypeCode.Stream:
  620. {
  621. int len = _store.ReadInt32();
  622. if (len < 0)
  623. {
  624. throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
  625. }
  626. if (_ums == null)
  627. {
  628. byte[] bytes = _store.ReadBytes(len);
  629. // Lifetime of memory == lifetime of this stream.
  630. return new PinnedBufferMemoryStream(bytes);
  631. }
  632. // make sure we don't create an UnmanagedMemoryStream that is longer than the resource stream.
  633. if (len > _ums.Length - _ums.Position)
  634. {
  635. throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
  636. }
  637. // For the case that we've memory mapped in the .resources
  638. // file, just return a Stream pointing to that block of memory.
  639. unsafe
  640. {
  641. return new UnmanagedMemoryStream(_ums.PositionPointer, len, len, FileAccess.Read);
  642. }
  643. }
  644. default:
  645. if (typeCode < ResourceTypeCode.StartOfUserTypes)
  646. {
  647. throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch);
  648. }
  649. break;
  650. }
  651. // Normal serialized objects
  652. int typeIndex = typeCode - ResourceTypeCode.StartOfUserTypes;
  653. return DeserializeObject(typeIndex);
  654. }
  655. // Reads in the header information for a .resources file. Verifies some
  656. // of the assumptions about this resource set, and builds the class table
  657. // for the default resource file format.
  658. private void ReadResources()
  659. {
  660. Debug.Assert(_store != null, "ResourceReader is closed!");
  661. try
  662. {
  663. // mega try-catch performs exceptionally bad on x64; factored out body into
  664. // _ReadResources and wrap here.
  665. _ReadResources();
  666. }
  667. catch (EndOfStreamException eof)
  668. {
  669. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted, eof);
  670. }
  671. catch (IndexOutOfRangeException e)
  672. {
  673. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted, e);
  674. }
  675. }
  676. private void _ReadResources()
  677. {
  678. // Read ResourceManager header
  679. // Check for magic number
  680. int magicNum = _store.ReadInt32();
  681. if (magicNum != ResourceManager.MagicNumber)
  682. throw new ArgumentException(SR.Resources_StreamNotValid);
  683. // Assuming this is ResourceManager header V1 or greater, hopefully
  684. // after the version number there is a number of bytes to skip
  685. // to bypass the rest of the ResMgr header. For V2 or greater, we
  686. // use this to skip to the end of the header
  687. int resMgrHeaderVersion = _store.ReadInt32();
  688. int numBytesToSkip = _store.ReadInt32();
  689. if (numBytesToSkip < 0 || resMgrHeaderVersion < 0)
  690. {
  691. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  692. }
  693. if (resMgrHeaderVersion > 1)
  694. {
  695. _store.BaseStream.Seek(numBytesToSkip, SeekOrigin.Current);
  696. }
  697. else
  698. {
  699. // We don't care about numBytesToSkip; read the rest of the header
  700. // Read in type name for a suitable ResourceReader
  701. // Note ResourceWriter & InternalResGen use different Strings.
  702. string readerType = _store.ReadString();
  703. if (!ValidateReaderType(readerType))
  704. throw new NotSupportedException(SR.Format(SR.NotSupported_WrongResourceReader_Type, readerType));
  705. // Skip over type name for a suitable ResourceSet
  706. SkipString();
  707. }
  708. // Read RuntimeResourceSet header
  709. // Do file version check
  710. int version = _store.ReadInt32();
  711. if (version != RuntimeResourceSet.Version && version != 1)
  712. throw new ArgumentException(SR.Format(SR.Arg_ResourceFileUnsupportedVersion, RuntimeResourceSet.Version, version));
  713. _version = version;
  714. _numResources = _store.ReadInt32();
  715. if (_numResources < 0)
  716. {
  717. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  718. }
  719. // Read type positions into type positions array.
  720. // But delay initialize the type table.
  721. int numTypes = _store.ReadInt32();
  722. if (numTypes < 0)
  723. {
  724. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  725. }
  726. _typeTable = new Type[numTypes];
  727. _typeNamePositions = new int[numTypes];
  728. for (int i = 0; i < numTypes; i++)
  729. {
  730. _typeNamePositions[i] = (int)_store.BaseStream.Position;
  731. // Skip over the Strings in the file. Don't create types.
  732. SkipString();
  733. }
  734. // Prepare to read in the array of name hashes
  735. // Note that the name hashes array is aligned to 8 bytes so
  736. // we can use pointers into it on 64 bit machines. (4 bytes
  737. // may be sufficient, but let's plan for the future)
  738. // Skip over alignment stuff. All public .resources files
  739. // should be aligned No need to verify the byte values.
  740. long pos = _store.BaseStream.Position;
  741. int alignBytes = ((int)pos) & 7;
  742. if (alignBytes != 0)
  743. {
  744. for (int i = 0; i < 8 - alignBytes; i++)
  745. {
  746. _store.ReadByte();
  747. }
  748. }
  749. // Read in the array of name hashes
  750. if (_ums == null)
  751. {
  752. _nameHashes = new int[_numResources];
  753. for (int i = 0; i < _numResources; i++)
  754. {
  755. _nameHashes[i] = _store.ReadInt32();
  756. }
  757. }
  758. else
  759. {
  760. int seekPos = unchecked(4 * _numResources);
  761. if (seekPos < 0)
  762. {
  763. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  764. }
  765. unsafe
  766. {
  767. _nameHashesPtr = (int*)_ums.PositionPointer;
  768. // Skip over the array of nameHashes.
  769. _ums.Seek(seekPos, SeekOrigin.Current);
  770. // get the position pointer once more to check that the whole table is within the stream
  771. _ = _ums.PositionPointer;
  772. }
  773. }
  774. // Read in the array of relative positions for all the names.
  775. if (_ums == null)
  776. {
  777. _namePositions = new int[_numResources];
  778. for (int i = 0; i < _numResources; i++)
  779. {
  780. int namePosition = _store.ReadInt32();
  781. if (namePosition < 0)
  782. {
  783. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  784. }
  785. _namePositions[i] = namePosition;
  786. }
  787. }
  788. else
  789. {
  790. int seekPos = unchecked(4 * _numResources);
  791. if (seekPos < 0)
  792. {
  793. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  794. }
  795. unsafe
  796. {
  797. _namePositionsPtr = (int*)_ums.PositionPointer;
  798. // Skip over the array of namePositions.
  799. _ums.Seek(seekPos, SeekOrigin.Current);
  800. // get the position pointer once more to check that the whole table is within the stream
  801. _ = _ums.PositionPointer;
  802. }
  803. }
  804. // Read location of data section.
  805. _dataSectionOffset = _store.ReadInt32();
  806. if (_dataSectionOffset < 0)
  807. {
  808. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  809. }
  810. // Store current location as start of name section
  811. _nameSectionOffset = _store.BaseStream.Position;
  812. // _nameSectionOffset should be <= _dataSectionOffset; if not, it's corrupt
  813. if (_dataSectionOffset < _nameSectionOffset)
  814. {
  815. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  816. }
  817. }
  818. // This allows us to delay-initialize the Type[]. This might be a
  819. // good startup time savings, since we might have to load assemblies
  820. // and initialize Reflection.
  821. private Type FindType(int typeIndex)
  822. {
  823. if (typeIndex < 0 || typeIndex >= _typeTable.Length)
  824. {
  825. throw new BadImageFormatException(SR.BadImageFormat_InvalidType);
  826. }
  827. if (_typeTable[typeIndex] == null)
  828. {
  829. long oldPos = _store.BaseStream.Position;
  830. try
  831. {
  832. _store.BaseStream.Position = _typeNamePositions[typeIndex];
  833. string typeName = _store.ReadString();
  834. _typeTable[typeIndex] = Type.GetType(typeName, true);
  835. }
  836. // If serialization isn't supported, we convert FileNotFoundException to
  837. // NotSupportedException for consistency with v2. This is a corner-case, but the
  838. // idea is that we want to give the user a more accurate error message. Even if
  839. // the dependency were found, we know it will require serialization since it
  840. // can't be one of the types we special case. So if the dependency were found,
  841. // it would go down the serialization code path, resulting in NotSupported for
  842. // SKUs without serialization.
  843. //
  844. // We don't want to regress the expected case by checking the type info before
  845. // getting to Type.GetType -- this is costly with v1 resource formats.
  846. catch (FileNotFoundException)
  847. {
  848. throw new NotSupportedException(SR.NotSupported_ResourceObjectSerialization);
  849. }
  850. finally
  851. {
  852. _store.BaseStream.Position = oldPos;
  853. }
  854. }
  855. Debug.Assert(_typeTable[typeIndex] != null, "Should have found a type!");
  856. return _typeTable[typeIndex]!; // TODO-NULLABLE: Indexer nullability tracked (https://github.com/dotnet/roslyn/issues/34644)
  857. }
  858. private string TypeNameFromTypeCode(ResourceTypeCode typeCode)
  859. {
  860. Debug.Assert(typeCode >= 0, "can't be negative");
  861. if (typeCode < ResourceTypeCode.StartOfUserTypes)
  862. {
  863. Debug.Assert(!string.Equals(typeCode.ToString(), "LastPrimitive"), "Change ResourceTypeCode metadata order so LastPrimitive isn't what Enum.ToString prefers.");
  864. return "ResourceTypeCode." + typeCode.ToString();
  865. }
  866. else
  867. {
  868. int typeIndex = typeCode - ResourceTypeCode.StartOfUserTypes;
  869. Debug.Assert(typeIndex >= 0 && typeIndex < _typeTable.Length, "TypeCode is broken or corrupted!");
  870. long oldPos = _store.BaseStream.Position;
  871. try
  872. {
  873. _store.BaseStream.Position = _typeNamePositions[typeIndex];
  874. return _store.ReadString();
  875. }
  876. finally
  877. {
  878. _store.BaseStream.Position = oldPos;
  879. }
  880. }
  881. }
  882. internal sealed class ResourceEnumerator : IDictionaryEnumerator
  883. {
  884. private const int ENUM_DONE = int.MinValue;
  885. private const int ENUM_NOT_STARTED = -1;
  886. private readonly ResourceReader _reader;
  887. private bool _currentIsValid;
  888. private int _currentName;
  889. private int _dataPosition; // cached for case-insensitive table
  890. internal ResourceEnumerator(ResourceReader reader)
  891. {
  892. _currentName = ENUM_NOT_STARTED;
  893. _reader = reader;
  894. _dataPosition = -2;
  895. }
  896. public bool MoveNext()
  897. {
  898. if (_currentName == _reader._numResources - 1 || _currentName == ENUM_DONE)
  899. {
  900. _currentIsValid = false;
  901. _currentName = ENUM_DONE;
  902. return false;
  903. }
  904. _currentIsValid = true;
  905. _currentName++;
  906. return true;
  907. }
  908. public object Key
  909. {
  910. get
  911. {
  912. if (_currentName == ENUM_DONE) throw new InvalidOperationException(SR.InvalidOperation_EnumEnded);
  913. if (!_currentIsValid) throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
  914. if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
  915. return _reader.AllocateStringForNameIndex(_currentName, out _dataPosition);
  916. }
  917. }
  918. public object Current => Entry;
  919. // Warning: This requires that you call the Key or Entry property FIRST before calling it!
  920. internal int DataPosition => _dataPosition;
  921. public DictionaryEntry Entry
  922. {
  923. get
  924. {
  925. if (_currentName == ENUM_DONE) throw new InvalidOperationException(SR.InvalidOperation_EnumEnded);
  926. if (!_currentIsValid) throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
  927. if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
  928. string key;
  929. object? value = null;
  930. lock (_reader)
  931. { // locks should be taken in the same order as in RuntimeResourceSet.GetObject to avoid deadlock
  932. lock (_reader._resCache)
  933. {
  934. key = _reader.AllocateStringForNameIndex(_currentName, out _dataPosition); // AllocateStringForNameIndex could lock on _reader
  935. ResourceLocator locator;
  936. if (_reader._resCache.TryGetValue(key, out locator))
  937. {
  938. value = locator.Value;
  939. }
  940. if (value == null)
  941. {
  942. if (_dataPosition == -1)
  943. value = _reader.GetValueForNameIndex(_currentName);
  944. else
  945. value = _reader.LoadObject(_dataPosition);
  946. // If enumeration and subsequent lookups happen very
  947. // frequently in the same process, add a ResourceLocator
  948. // to _resCache here. But WinForms enumerates and
  949. // just about everyone else does lookups. So caching
  950. // here may bloat working set.
  951. }
  952. }
  953. }
  954. return new DictionaryEntry(key, value);
  955. }
  956. }
  957. public object? Value
  958. {
  959. get
  960. {
  961. if (_currentName == ENUM_DONE) throw new InvalidOperationException(SR.InvalidOperation_EnumEnded);
  962. if (!_currentIsValid) throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
  963. if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
  964. // Consider using _resCache here, eventually, if
  965. // this proves to be an interesting perf scenario.
  966. // But mixing lookups and enumerators shouldn't be
  967. // particularly compelling.
  968. return _reader.GetValueForNameIndex(_currentName);
  969. }
  970. }
  971. public void Reset()
  972. {
  973. if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
  974. _currentIsValid = false;
  975. _currentName = ENUM_NOT_STARTED;
  976. }
  977. }
  978. }
  979. }