2009-07-30 Gonzalo Paniagua Javier <gonzalo@novell.com>
[mono.git] / mcs / class / corlib / System.Resources / ResourceReader.cs
1 //
2 // System.Resources.ResourceReader.cs
3 //
4 // Authors: 
5 //      Duncan Mak <duncan@ximian.com>
6 //      Nick Drochak <ndrochak@gol.com>
7 //      Dick Porter <dick@ximian.com>
8 //      Marek Safar <marek.safar@seznam.cz>
9 //      Atsushi Enomoto  <atsushi@ximian.com>
10 //      Larry Ewing <lewing@novell.com>
11 //
12 // (C) 2001, 2002 Ximian Inc, http://www.ximian.com
13 // Copyright (C) 2004-2005,2007-2008 Novell, Inc (http://www.novell.com)
14 //
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 //
34
35 using System.Collections;
36 using System.Resources;
37 using System.IO;
38 using System.Text;
39 using System.Runtime.InteropServices;
40 using System.Runtime.Serialization;
41 using System.Runtime.Serialization.Formatters.Binary;
42 using System.Security.Permissions;
43 #if NET_2_0
44 using System.Collections.Generic;
45 #endif
46
47 namespace System.Resources
48 {
49         internal enum PredefinedResourceType
50         {
51                 Null            = 0,
52                 String          = 1,
53                 Bool            = 2,
54                 Char            = 3,
55                 Byte            = 4,
56                 SByte           = 5,
57                 Int16           = 6,
58                 UInt16          = 7,
59                 Int32           = 8,
60                 UInt32          = 9,
61                 Int64           = 10,
62                 UInt64          = 11,
63                 Single          = 12,
64                 Double          = 13,
65                 Decimal         = 14,
66                 DateTime        = 15,
67                 TimeSpan        = 16,
68                 ByteArray       = 32,
69                 Stream          = 33,
70                 FistCustom      = 64
71         }
72
73 #if NET_2_0
74         [System.Runtime.InteropServices.ComVisible (true)]
75 #endif
76         public sealed class ResourceReader : IResourceReader, IEnumerable, IDisposable
77         {
78                 struct ResourceInfo
79                 {
80                         public readonly long ValuePosition;
81                         public readonly string ResourceName;
82                         public readonly int TypeIndex;
83                         
84                         public ResourceInfo (string resourceName, long valuePosition, int type_index)
85                         {
86                                 ValuePosition = valuePosition;
87                                 ResourceName = resourceName;
88                                 TypeIndex = type_index;
89                         }
90                 }
91
92                 struct ResourceCacheItem
93                 {
94                         public readonly string ResourceName;
95                         public readonly object ResourceValue;
96
97                         public ResourceCacheItem (string name, object value)
98                         {
99                                 ResourceName = name;
100                                 ResourceValue = value;
101                         }
102                 }
103                 
104                 BinaryReader reader;
105                 object readerLock = new object ();
106                 IFormatter formatter;
107                 internal int resourceCount = 0;
108                 int typeCount = 0;
109                 string[] typeNames;
110                 int[] hashes;
111                 ResourceInfo[] infos;
112                 int dataSectionOffset;
113                 long nameSectionOffset;
114                 int resource_ver;
115                 ResourceCacheItem[] cache;
116                 object cache_lock = new object ();
117                 
118                 // Constructors
119                 [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
120                 public ResourceReader (Stream stream)
121                 {
122                         if (stream == null)
123                                 throw new ArgumentNullException ("stream");
124                         
125                         if (!stream.CanRead)
126                                 throw new ArgumentException ("Stream was not readable.");
127
128                         reader = new BinaryReader(stream, Encoding.UTF8);
129                         formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
130                         
131                         ReadHeaders();
132                 }
133                 
134                 public ResourceReader (string fileName)
135                 {
136                         reader = new BinaryReader (new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read));
137                         formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
138
139                         ReadHeaders();
140                 }
141                 
142                 /* Read the ResourceManager header and the
143                  * ResourceReader header.
144                  */
145                 private void ReadHeaders()
146                 {
147                         try {
148                                 int manager_magic = reader.ReadInt32();
149
150                                 if(manager_magic != ResourceManager.MagicNumber) 
151                                         throw new ArgumentException(String.Format ("Stream is not a valid .resources file, magic=0x{0:x}", manager_magic));
152
153                                 int manager_ver = reader.ReadInt32();
154                                 int manager_len = reader.ReadInt32();
155                                 
156                                 /* We know how long the header is, even if
157                                  * the version number is too new
158                                  */
159                                 if(manager_ver > ResourceManager.HeaderVersionNumber) {
160                                         reader.BaseStream.Seek(manager_len, SeekOrigin.Current);
161                                 } else {
162                                         string reader_class=reader.ReadString();
163                                         if(!reader_class.StartsWith("System.Resources.ResourceReader")) {
164                                                 throw new NotSupportedException("This .resources file requires reader class " + reader_class);
165                                         }
166                                         
167                                         string set_class=reader.ReadString();
168                                         if(!set_class.StartsWith(typeof(ResourceSet).FullName) && !set_class.StartsWith("System.Resources.RuntimeResourceSet")) {
169                                                 throw new NotSupportedException("This .resources file requires set class " + set_class);
170                                         }
171                                 }
172
173                                 /* Now read the ResourceReader header */
174                                 resource_ver = reader.ReadInt32();
175
176                                 if(resource_ver != 1
177 #if NET_2_0
178                                         && resource_ver != 2
179 #endif
180                                         ) {
181                                         throw new NotSupportedException("This .resources file requires unsupported set class version: " + resource_ver.ToString());
182                                 }
183
184                                 resourceCount = reader.ReadInt32();
185                                 typeCount = reader.ReadInt32();
186                                 
187                                 typeNames=new string[typeCount];
188
189                                 for(int i=0; i<typeCount; i++) {
190                                         typeNames[i]=reader.ReadString();
191                                 }
192
193                                 /* There are between 0 and 7 bytes of
194                                  * padding here, consisting of the
195                                  * letters PAD.  The next item (Hash
196                                  * values for each resource name) need
197                                  * to be aligned on an 8-byte
198                                  * boundary.
199                                  */
200
201                                 int pad_align=(int)(reader.BaseStream.Position & 7);
202                                 int pad_chars=0;
203
204                                 if(pad_align!=0) {
205                                         pad_chars=8-pad_align;
206                                 }
207
208                                 for(int i=0; i<pad_chars; i++) {
209                                         byte pad_byte=reader.ReadByte();
210                                         if(pad_byte!="PAD"[i%3]) {
211                                                 throw new ArgumentException("Malformed .resources file (padding values incorrect)");
212                                         }
213                                 }
214                                 /* Read in the hash values for each
215                                  * resource name.  These can be used
216                                  * by ResourceSet (calling internal
217                                  * methods) to do a fast compare on
218                                  * resource names without doing
219                                  * expensive string compares (but we
220                                  * dont do that yet, so far we only
221                                  * implement the Enumerator interface)
222                                  */
223                                 hashes=new int[resourceCount];
224                                 for(int i=0; i<resourceCount; i++) {
225                                         hashes[i]=reader.ReadInt32();
226                                 }
227                                 
228                                 /* Read in the virtual offsets for
229                                  * each resource name
230                                  */
231                                 long[] positions = new long [resourceCount];
232                                 for(int i = 0; i < resourceCount; i++)
233                                         positions [i] = reader.ReadInt32();
234                                 
235                                 dataSectionOffset = reader.ReadInt32();
236                                 nameSectionOffset = reader.BaseStream.Position;
237
238                                 long origPosition = reader.BaseStream.Position;
239                                 infos = new ResourceInfo [resourceCount];
240                                 for (int i = 0; i < resourceCount; i++)
241                                         CreateResourceInfo (positions [i], ref infos [i]);
242                                 reader.BaseStream.Seek (origPosition, SeekOrigin.Begin);
243                                 
244                                 positions = null;
245                         } catch(EndOfStreamException e) {
246                                 throw new ArgumentException("Stream is not a valid .resources file!  It was possibly truncated.", e);
247                         }
248                 }
249
250                 void CreateResourceInfo (long position, ref ResourceInfo info)
251                 {
252                         long pos = position + nameSectionOffset;
253
254                         reader.BaseStream.Seek (pos, SeekOrigin.Begin);
255
256                         // Resource name
257                         int len = Read7BitEncodedInt ();
258                         byte[] str = new byte [len];
259                         
260                         reader.Read (str, 0, len);
261                         string resourceName = Encoding.Unicode.GetString (str);
262
263                         long data_offset = reader.ReadInt32 () + dataSectionOffset;
264                         reader.BaseStream.Seek (data_offset, SeekOrigin.Begin);
265                         int type_index = Read7BitEncodedInt ();
266                         
267                         info = new ResourceInfo (resourceName, reader.BaseStream.Position, type_index);
268                 }
269                 
270                 /* Cut and pasted from BinaryReader, because it's
271                  * 'protected' there
272                  */
273                 private int Read7BitEncodedInt() {
274                         int ret = 0;
275                         int shift = 0;
276                         byte b;
277
278                         do {
279                                 b = reader.ReadByte();
280                                 
281                                 ret = ret | ((b & 0x7f) << shift);
282                                 shift += 7;
283                         } while ((b & 0x80) == 0x80);
284
285                         return ret;
286                 }
287
288 #if NET_2_0
289                 object ReadValueVer2 (int type_index)
290                 {
291                         switch ((PredefinedResourceType)type_index)
292                         {
293                                 case PredefinedResourceType.Null:
294                                         return null;
295
296                                 case PredefinedResourceType.String:
297                                         return reader.ReadString();
298
299                                 case PredefinedResourceType.Bool:
300                                         return reader.ReadBoolean ();
301
302                                 case PredefinedResourceType.Char:
303                                         return (char)reader.ReadUInt16();
304
305                                 case PredefinedResourceType.Byte:
306                                         return reader.ReadByte();
307
308                                 case PredefinedResourceType.SByte:
309                                         return reader.ReadSByte();
310
311                                 case PredefinedResourceType.Int16:
312                                         return reader.ReadInt16();
313
314                                 case PredefinedResourceType.UInt16:
315                                         return reader.ReadUInt16();
316
317                                 case PredefinedResourceType.Int32:
318                                         return reader.ReadInt32();
319
320                                 case PredefinedResourceType.UInt32:
321                                         return reader.ReadUInt32();
322
323                                 case PredefinedResourceType.Int64:
324                                         return reader.ReadInt64();
325
326                                 case PredefinedResourceType.UInt64:
327                                         return reader.ReadUInt64();
328
329                                 case PredefinedResourceType.Single:
330                                         return reader.ReadSingle();
331
332                                 case PredefinedResourceType.Double:
333                                         return reader.ReadDouble();
334
335                                 case PredefinedResourceType.Decimal:
336                                         return reader.ReadDecimal();
337
338                                 case PredefinedResourceType.DateTime:
339                                         return new DateTime(reader.ReadInt64());
340
341                                 case PredefinedResourceType.TimeSpan:
342                                         return new TimeSpan(reader.ReadInt64());
343
344                                 case PredefinedResourceType.ByteArray:
345                                         return reader.ReadBytes (reader.ReadInt32 ());
346
347                                 case PredefinedResourceType.Stream:
348                                         // FIXME: create pinned UnmanagedMemoryStream for efficiency.
349                                         byte [] bytes = new byte [reader.ReadUInt32 ()];
350                                         reader.Read (bytes, 0, bytes.Length);
351                                         return new MemoryStream (bytes);
352                         }
353
354                         type_index -= (int)PredefinedResourceType.FistCustom;
355                         return ReadNonPredefinedValue (Type.GetType (typeNames[type_index], true));
356                 }
357 #endif          
358
359                 object ReadValueVer1 (Type type)
360                 {
361                         // The most common first
362                         if (type==typeof(String))
363                                 return reader.ReadString();
364                         if (type==typeof(Int32))
365                                 return reader.ReadInt32();
366                         if (type==typeof(Byte))
367                                 return(reader.ReadByte());
368                         if (type==typeof(Double))
369                                 return(reader.ReadDouble());
370                         if (type==typeof(Int16))
371                                 return(reader.ReadInt16());
372                         if (type==typeof(Int64))
373                                 return(reader.ReadInt64());
374                         if (type==typeof(SByte))
375                                 return(reader.ReadSByte());
376                         if (type==typeof(Single))
377                                 return(reader.ReadSingle());
378                         if (type==typeof(TimeSpan))
379                                 return(new TimeSpan(reader.ReadInt64()));
380                         if (type==typeof(UInt16))
381                                 return(reader.ReadUInt16());
382                         if (type==typeof(UInt32))
383                                 return(reader.ReadUInt32());
384                         if (type==typeof(UInt64))
385                                 return(reader.ReadUInt64());
386                         if (type==typeof(Decimal))
387                                 return(reader.ReadDecimal());
388                         if (type==typeof(DateTime))
389                                 return(new DateTime(reader.ReadInt64()));
390
391                         return ReadNonPredefinedValue(type);
392                 }
393
394                 // TODO: Add security checks
395                 object ReadNonPredefinedValue (Type exp_type)
396                 {
397                         object obj=formatter.Deserialize(reader.BaseStream);
398                         if(obj.GetType() != exp_type) {
399                                 /* We got a bogus
400                                                  * object.  This
401                                                  * exception is the
402                                                  * best match I could
403                                                  * find.  (.net seems
404                                                  * to throw
405                                                  * BadImageFormatException,
406                                                  * which the docs
407                                                  * state is used when
408                                                  * file or dll images
409                                                  * cant be loaded by
410                                                  * the runtime.)
411                                                  */
412                                 throw new InvalidOperationException("Deserialized object is wrong type");
413                         }
414                         return obj;
415                 }               
416
417                 void LoadResourceValues (ResourceCacheItem[] store)
418                 {
419                         ResourceInfo ri;
420                         object value;
421                         
422                         lock (readerLock) {
423                                 for (int i = 0; i < resourceCount; i++) {
424                                         ri = infos [i];
425                                         if (ri.TypeIndex == -1) {
426                                                 store [i] = new ResourceCacheItem (ri.ResourceName, null);
427                                                 continue;
428                                         }
429
430                                         reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
431 #if NET_2_0
432                                         if (resource_ver == 2)
433                                                 value = ReadValueVer2 (ri.TypeIndex);
434                                         else
435 #endif
436                                                 value = ReadValueVer1 (Type.GetType (typeNames [ri.TypeIndex], true));
437
438                                         store [i] = new ResourceCacheItem (ri.ResourceName, value);
439                                 }
440                         }
441                 }
442                 
443 #if NET_2_0
444                 internal UnmanagedMemoryStream ResourceValueAsStream (string name, int index)
445                 {
446                         ResourceInfo ri = infos [index];
447                         if ((PredefinedResourceType)ri.TypeIndex != PredefinedResourceType.Stream)
448                                 throw new InvalidOperationException (String.Format ("Resource '{0}' was not a Stream. Use GetObject() instead.", name));
449                         
450                         lock (readerLock) {
451                                 reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
452                                 
453                                 // here we return a Stream from exactly
454                                 // current position so that the returned
455                                 // Stream represents a single object stream.
456                                 long slen = reader.ReadInt32();
457                                 IntPtrStream basePtrStream = reader.BaseStream as IntPtrStream;
458                                 unsafe {
459                                         if (basePtrStream != null) {
460                                                 byte* addr = (byte*) basePtrStream.BaseAddress.ToPointer ();
461                                                 addr += basePtrStream.Position;
462                                                 return new UnmanagedMemoryStream ((byte*) (void*) addr, slen);
463                                         } else {
464                                                 IntPtr ptr = Marshal.AllocHGlobal ((int) slen);
465                                                 byte* addr = (byte*) ptr.ToPointer ();
466                                                 UnmanagedMemoryStream ms = new UnmanagedMemoryStream (addr, slen, slen, FileAccess.ReadWrite);
467                                                 // The memory resource must be freed
468                                                 // when the stream is disposed.
469                                                 ms.Closed += delegate (object o, EventArgs e) {
470                                                         Marshal.FreeHGlobal (ptr);
471                                                 };
472
473                                                 byte [] bytes = new byte [slen < 1024 ? slen : 1024];
474                                                 while (slen > 0 ) {
475                                                         int x = reader.Read (bytes, 0, (int)Math.Min (bytes.Length, slen));
476
477                                                         if (x == 0)
478                                                                 throw new FormatException ("The resource data is corrupt. Resource stream ended");
479
480                                                         ms.Write (bytes, 0, x);
481                                                         slen -= x;
482                                                 }
483                                                 ms.Seek (0, SeekOrigin.Begin);
484                                                 return ms;
485                                         }
486                                 }
487                         }
488                 }
489 #endif
490
491                 public void Close ()
492                 {
493                         Dispose(true);
494                 }
495                 
496                 public IDictionaryEnumerator GetEnumerator () {
497                         if (reader == null){
498                                 throw new InvalidOperationException("ResourceReader is closed.");
499                         }
500                         else {
501                                 return new ResourceEnumerator (this);
502                         }
503                 }
504                 
505                 IEnumerator IEnumerable.GetEnumerator ()
506                 {
507                         return ((IResourceReader) this).GetEnumerator();
508                 }
509 #if NET_2_0
510                 public void GetResourceData (string resourceName, out string resourceType, out byte [] resourceData)
511                 {
512                         if (resourceName == null)
513                                 throw new ArgumentNullException ("resourceName");
514                         ResourceEnumerator en = new ResourceEnumerator (this);
515                         while (en.MoveNext ())
516                                 if ((string) en.Key == resourceName) {
517                                         GetResourceDataAt (en.Index, out resourceType, out resourceData);
518                                         return;
519                                 }
520                         throw new ArgumentException (String.Format ("Specified resource not found: {0}", resourceName));
521                 }
522
523                 private void GetResourceDataAt (int index, out string resourceType, out byte [] data)
524                 {
525                         ResourceInfo ri = infos [index];
526                         int type_index = ri.TypeIndex;
527                         if (type_index == -1)
528                                 throw new FormatException ("The resource data is corrupt");
529                         
530                         lock (readerLock) {
531                                 reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
532                                 long pos2 = reader.BaseStream.Position;
533
534                                 // Simply read data, and seek back to the original position
535                                 if (resource_ver == 2) {
536                                         if (type_index >= (int) PredefinedResourceType.FistCustom) {
537                                                 int typenameidx = type_index - (int)PredefinedResourceType.FistCustom;
538                                                 if (typenameidx >= typeNames.Length)
539                                                         throw new FormatException ("The resource data is corrupt. Invalid index to types");
540                                                 resourceType = typeNames[typenameidx];
541                                         }
542                                         else
543                                                 resourceType = "ResourceTypeCode." + (PredefinedResourceType) type_index;
544                                         ReadValueVer2 (type_index);
545                                 } else {
546                                         // resource ver 1 == untyped
547                                         resourceType = "ResourceTypeCode.Null";
548                                         ReadValueVer1 (Type.GetType (typeNames [type_index], true));
549                                 }
550
551                                 // FIXME: the data size is wrong.
552                                 int datalen = (int) (reader.BaseStream.Position - pos2);
553                                 reader.BaseStream.Seek (-datalen, SeekOrigin.Current);
554                                 data = new byte [datalen];
555                                 reader.BaseStream.Read (data, 0, datalen);
556                         }
557                 }
558 #endif
559                 void IDisposable.Dispose ()
560                 {
561                         Dispose(true);
562                 }
563
564                 private void Dispose (bool disposing)
565                 {
566                         if(disposing) {
567                                 if(reader!=null) {
568                                         reader.Close();
569                                 }
570                         }
571                         
572                         reader=null;
573                         hashes=null;
574                         infos=null;
575                         typeNames=null;
576                         cache = null;
577                 }
578                 
579                 internal sealed class ResourceEnumerator : IDictionaryEnumerator
580                 {
581                         private ResourceReader reader;
582                         private int index = -1;
583                         private bool finished;
584                         
585                         internal ResourceEnumerator(ResourceReader readerToEnumerate)
586                         {
587                                 reader = readerToEnumerate;
588                                 FillCache ();
589                         }
590
591                         public int Index
592                         {
593                                 get { return index; }
594                         }
595
596                         public DictionaryEntry Entry {
597                                 get {
598                                         if (reader.reader == null)
599                                                 throw new InvalidOperationException("ResourceReader is closed.");
600                                         if (index < 0)
601                                                 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
602
603                                         return new DictionaryEntry(Key, Value);
604                                 }
605                         }
606                         
607                         public object Key
608                         {
609                                 get { 
610                                         if (reader.reader == null)
611                                                 throw new InvalidOperationException("ResourceReader is closed.");
612                                         if (index < 0)
613                                                 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
614
615                                         return reader.cache [index].ResourceName;
616                                 }
617                         }
618                         
619                         public object Value
620                         {
621                                 get { 
622                                         if (reader.reader == null)
623                                                 throw new InvalidOperationException("ResourceReader is closed.");
624                                         if (index < 0)
625                                                 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
626                                         return reader.cache [index].ResourceValue;
627                                 }
628                         }
629                         
630 #if NET_2_0
631                         public UnmanagedMemoryStream ValueAsStream
632                         {
633                                 get {
634                                         if (reader.reader == null)
635                                                 throw new InvalidOperationException("ResourceReader is closed.");
636                                         if (index < 0)
637                                                 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
638                                         return(reader.ResourceValueAsStream((string) Key, index));
639                                 }
640                         }
641 #endif
642                         
643                         public object Current
644                         {
645                                 get {
646                                         /* Entry does the checking, no
647                                          * need to repeat it here
648                                          */
649                                         return Entry; 
650                                 }
651                         }
652                         
653                         public bool MoveNext ()
654                         {
655                                 if (reader.reader == null)
656                                         throw new InvalidOperationException("ResourceReader is closed.");
657                                 if (finished) {
658                                         return false;
659                                 }
660                                 
661                                 if (++index < reader.resourceCount){
662                                         return true;
663                                 }
664
665                                 finished=true;
666                                 return false;
667                         }
668                         
669                         public void Reset () {
670                                 if (reader.reader == null)
671                                         throw new InvalidOperationException("ResourceReader is closed.");
672                                 index = -1;
673                                 finished = false;
674                         }
675
676                         void FillCache ()
677                         {
678                                 if (reader.cache != null)
679                                         return;
680                                 
681                                 lock (reader.cache_lock) {
682                                         if (reader.cache != null)
683                                                 return;
684                                         
685                                         ResourceCacheItem[] resources = new ResourceCacheItem [reader.resourceCount];                           
686                                         reader.LoadResourceValues (resources);
687                                         reader.cache = resources;
688                                 }
689                         }
690                 } // internal class ResourceEnumerator
691         }  // public sealed class ResourceReader
692 } // namespace System.Resources