ResourceReader.cs 44 KB

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