2 // System.Resources.ResourceReader.cs
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>
11 // (C) 2001, 2002 Ximian Inc, http://www.ximian.com
12 // Copyright (C) 2004-2005,2007 Novell, Inc (http://www.novell.com)
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System.Collections;
35 using System.Resources;
38 using System.Runtime.InteropServices;
39 using System.Runtime.Serialization;
40 using System.Runtime.Serialization.Formatters.Binary;
41 using System.Security.Permissions;
43 using System.Collections.Generic;
46 namespace System.Resources
48 internal enum PredefinedResourceType
73 [System.Runtime.InteropServices.ComVisible (true)]
75 public sealed class ResourceReader : IResourceReader, IEnumerable, IDisposable
79 public readonly long ValuePosition;
80 public readonly string ResourceName;
81 public readonly int TypeIndex;
83 public ResourceInfo (string resourceName, long valuePosition, int type_index)
85 ValuePosition = valuePosition;
86 ResourceName = resourceName;
87 TypeIndex = type_index;
91 struct ResourceCacheItem
93 public readonly string ResourceName;
94 public readonly object ResourceValue;
96 public ResourceCacheItem (string name, object value)
99 ResourceValue = value;
104 object readerLock = new object ();
105 IFormatter formatter;
106 internal int resourceCount = 0;
110 ResourceInfo[] infos;
111 int dataSectionOffset;
112 long nameSectionOffset;
114 ResourceCacheItem[] cache;
115 object cache_lock = new object ();
118 [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
119 public ResourceReader (Stream stream)
122 throw new ArgumentNullException ("stream");
125 throw new ArgumentException ("Stream was not readable.");
127 reader = new BinaryReader(stream, Encoding.UTF8);
128 formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
133 public ResourceReader (string fileName)
135 reader = new BinaryReader (new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read));
136 formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
141 /* Read the ResourceManager header and the
142 * ResourceReader header.
144 private void ReadHeaders()
147 int manager_magic = reader.ReadInt32();
149 if(manager_magic != ResourceManager.MagicNumber)
150 throw new ArgumentException(String.Format ("Stream is not a valid .resources file, magic=0x{0:x}", manager_magic));
152 int manager_ver = reader.ReadInt32();
153 int manager_len = reader.ReadInt32();
155 /* We know how long the header is, even if
156 * the version number is too new
158 if(manager_ver > ResourceManager.HeaderVersionNumber) {
159 reader.BaseStream.Seek(manager_len, SeekOrigin.Current);
161 string reader_class=reader.ReadString();
162 if(!reader_class.StartsWith("System.Resources.ResourceReader")) {
163 throw new NotSupportedException("This .resources file requires reader class " + reader_class);
166 string set_class=reader.ReadString();
167 if(!set_class.StartsWith(typeof(ResourceSet).FullName) && !set_class.StartsWith("System.Resources.RuntimeResourceSet")) {
168 throw new NotSupportedException("This .resources file requires set class " + set_class);
172 /* Now read the ResourceReader header */
173 resource_ver = reader.ReadInt32();
180 throw new NotSupportedException("This .resources file requires unsupported set class version: " + resource_ver.ToString());
183 resourceCount = reader.ReadInt32();
184 typeCount = reader.ReadInt32();
186 typeNames=new string[typeCount];
188 for(int i=0; i<typeCount; i++) {
189 typeNames[i]=reader.ReadString();
192 /* There are between 0 and 7 bytes of
193 * padding here, consisting of the
194 * letters PAD. The next item (Hash
195 * values for each resource name) need
196 * to be aligned on an 8-byte
200 int pad_align=(int)(reader.BaseStream.Position & 7);
204 pad_chars=8-pad_align;
207 for(int i=0; i<pad_chars; i++) {
208 byte pad_byte=reader.ReadByte();
209 if(pad_byte!="PAD"[i%3]) {
210 throw new ArgumentException("Malformed .resources file (padding values incorrect)");
213 /* Read in the hash values for each
214 * resource name. These can be used
215 * by ResourceSet (calling internal
216 * methods) to do a fast compare on
217 * resource names without doing
218 * expensive string compares (but we
219 * dont do that yet, so far we only
220 * implement the Enumerator interface)
222 hashes=new int[resourceCount];
223 for(int i=0; i<resourceCount; i++) {
224 hashes[i]=reader.ReadInt32();
227 /* Read in the virtual offsets for
230 long[] positions = new long [resourceCount];
231 for(int i = 0; i < resourceCount; i++)
232 positions [i] = reader.ReadInt32();
234 dataSectionOffset = reader.ReadInt32();
235 nameSectionOffset = reader.BaseStream.Position;
237 long origPosition = reader.BaseStream.Position;
238 infos = new ResourceInfo [resourceCount];
239 for (int i = 0; i < resourceCount; i++)
240 CreateResourceInfo (positions [i], ref infos [i]);
241 reader.BaseStream.Seek (origPosition, SeekOrigin.Begin);
244 } catch(EndOfStreamException e) {
245 throw new ArgumentException("Stream is not a valid .resources file! It was possibly truncated.", e);
249 void CreateResourceInfo (long position, ref ResourceInfo info)
251 long pos = position + nameSectionOffset;
253 reader.BaseStream.Seek (pos, SeekOrigin.Begin);
256 int len = Read7BitEncodedInt ();
257 byte[] str = new byte [len];
259 reader.Read (str, 0, len);
260 string resourceName = Encoding.Unicode.GetString (str);
262 long data_offset = reader.ReadInt32 () + dataSectionOffset;
263 reader.BaseStream.Seek (data_offset, SeekOrigin.Begin);
264 int type_index = Read7BitEncodedInt ();
266 info = new ResourceInfo (resourceName, reader.BaseStream.Position, type_index);
269 /* Cut and pasted from BinaryReader, because it's
272 private int Read7BitEncodedInt() {
278 b = reader.ReadByte();
280 ret = ret | ((b & 0x7f) << shift);
282 } while ((b & 0x80) == 0x80);
287 object ReadValueVer2 (int type_index)
289 switch ((PredefinedResourceType)type_index)
291 case PredefinedResourceType.Null:
294 case PredefinedResourceType.String:
295 return reader.ReadString();
297 case PredefinedResourceType.Bool:
298 return reader.ReadBoolean ();
300 case PredefinedResourceType.Char:
301 return (char)reader.ReadUInt16();
303 case PredefinedResourceType.Byte:
304 return reader.ReadByte();
306 case PredefinedResourceType.SByte:
307 return reader.ReadSByte();
309 case PredefinedResourceType.Int16:
310 return reader.ReadInt16();
312 case PredefinedResourceType.UInt16:
313 return reader.ReadUInt16();
315 case PredefinedResourceType.Int32:
316 return reader.ReadInt32();
318 case PredefinedResourceType.UInt32:
319 return reader.ReadUInt32();
321 case PredefinedResourceType.Int64:
322 return reader.ReadInt64();
324 case PredefinedResourceType.UInt64:
325 return reader.ReadUInt64();
327 case PredefinedResourceType.Single:
328 return reader.ReadSingle();
330 case PredefinedResourceType.Double:
331 return reader.ReadDouble();
333 case PredefinedResourceType.Decimal:
334 return reader.ReadDecimal();
336 case PredefinedResourceType.DateTime:
337 return new DateTime(reader.ReadInt64());
339 case PredefinedResourceType.TimeSpan:
340 return new TimeSpan(reader.ReadInt64());
342 case PredefinedResourceType.ByteArray:
343 return reader.ReadBytes (reader.ReadInt32 ());
345 case PredefinedResourceType.Stream:
346 // FIXME: create pinned UnmanagedMemoryStream for efficiency.
347 byte [] bytes = new byte [reader.ReadUInt32 ()];
348 reader.Read (bytes, 0, bytes.Length);
349 return new MemoryStream (bytes);
352 type_index -= (int)PredefinedResourceType.FistCustom;
353 return ReadNonPredefinedValue (Type.GetType (typeNames[type_index], true));
356 object ReadValueVer1 (Type type)
358 // The most common first
359 if (type==typeof(String))
360 return reader.ReadString();
361 if (type==typeof(Int32))
362 return reader.ReadInt32();
363 if (type==typeof(Byte))
364 return(reader.ReadByte());
365 if (type==typeof(Double))
366 return(reader.ReadDouble());
367 if (type==typeof(Int16))
368 return(reader.ReadInt16());
369 if (type==typeof(Int64))
370 return(reader.ReadInt64());
371 if (type==typeof(SByte))
372 return(reader.ReadSByte());
373 if (type==typeof(Single))
374 return(reader.ReadSingle());
375 if (type==typeof(TimeSpan))
376 return(new TimeSpan(reader.ReadInt64()));
377 if (type==typeof(UInt16))
378 return(reader.ReadUInt16());
379 if (type==typeof(UInt32))
380 return(reader.ReadUInt32());
381 if (type==typeof(UInt64))
382 return(reader.ReadUInt64());
383 if (type==typeof(Decimal))
384 return(reader.ReadDecimal());
385 if (type==typeof(DateTime))
386 return(new DateTime(reader.ReadInt64()));
388 return ReadNonPredefinedValue(type);
391 // TODO: Add security checks
392 object ReadNonPredefinedValue (Type exp_type)
394 object obj=formatter.Deserialize(reader.BaseStream);
395 if(obj.GetType() != exp_type) {
402 * BadImageFormatException,
409 throw new InvalidOperationException("Deserialized object is wrong type");
414 void LoadResourceValues (ResourceCacheItem[] store)
420 for (int i = 0; i < resourceCount; i++) {
422 if (ri.TypeIndex == -1) {
423 store [i] = new ResourceCacheItem (ri.ResourceName, null);
427 reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
429 if (resource_ver == 2)
430 value = ReadValueVer2 (ri.TypeIndex);
433 value = ReadValueVer1 (Type.GetType (typeNames [ri.TypeIndex], true));
435 store [i] = new ResourceCacheItem (ri.ResourceName, value);
441 internal UnmanagedMemoryStream ResourceValueAsStream (string name, int index)
443 ResourceInfo ri = infos [index];
444 if ((PredefinedResourceType)ri.TypeIndex != PredefinedResourceType.Stream)
445 throw new InvalidOperationException (String.Format ("Resource '{0}' was not a Stream. Use GetObject() instead.", name));
448 reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
450 // here we return a Stream from exactly
451 // current position so that the returned
452 // Stream represents a single object stream.
453 long slen = reader.ReadInt32();
454 IntPtrStream basePtrStream = reader.BaseStream as IntPtrStream;
456 if (basePtrStream != null) {
457 byte* addr = (byte*) basePtrStream.BaseAddress.ToPointer ();
458 addr += basePtrStream.Position;
459 return new UnmanagedMemoryStream ((byte*) (void*) addr, slen);
461 IntPtr ptr = Marshal.AllocHGlobal ((int) slen);
462 byte* addr = (byte*) ptr.ToPointer ();
463 UnmanagedMemoryStream ms = new UnmanagedMemoryStream (addr, slen, slen, FileAccess.ReadWrite);
464 // The memory resource must be freed
465 // when the stream is disposed.
466 ms.Closed += delegate (object o, EventArgs e) {
467 Marshal.FreeHGlobal (ptr);
469 byte [] bytes = new byte [slen < 1024 ? slen : 1024];
470 for (int i = 0; i < slen; i += bytes.Length) {
471 int x = reader.Read (bytes, 0, bytes.Length);
472 ms.Write (bytes, 0, x);
474 ms.Seek (0, SeekOrigin.Begin);
487 public IDictionaryEnumerator GetEnumerator () {
489 throw new InvalidOperationException("ResourceReader is closed.");
492 return new ResourceEnumerator (this);
496 IEnumerator IEnumerable.GetEnumerator ()
498 return ((IResourceReader) this).GetEnumerator();
501 public void GetResourceData (string resourceName, out string resourceType, out byte [] resourceData)
503 if (resourceName == null)
504 throw new ArgumentNullException ("resourceName");
505 ResourceEnumerator en = new ResourceEnumerator (this);
506 while (en.MoveNext ())
507 if ((string) en.Key == resourceName) {
508 GetResourceDataAt (en.Index, out resourceType, out resourceData);
511 throw new ArgumentException (String.Format ("Specified resource not found: {0}", resourceName));
514 private void GetResourceDataAt (int index, out string resourceType, out byte [] data)
516 ResourceInfo ri = infos [index];
517 int type_index = ri.TypeIndex;
518 if (type_index == -1)
519 throw new FormatException ("The resource data is corrupt");
522 reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
523 long pos2 = reader.BaseStream.Position;
525 // Simply read data, and seek back to the original position
526 if (resource_ver == 2) {
527 if (type_index >= (int) PredefinedResourceType.FistCustom) {
528 int typenameidx = type_index - (int)PredefinedResourceType.FistCustom;
529 if (typenameidx >= typeNames.Length)
530 throw new FormatException ("The resource data is corrupt. Invalid index to types");
531 resourceType = typeNames[typenameidx];
534 resourceType = "ResourceTypeCode." + (PredefinedResourceType) type_index;
535 ReadValueVer2 (type_index);
537 // resource ver 1 == untyped
538 resourceType = "ResourceTypeCode.Null";
539 ReadValueVer1 (Type.GetType (typeNames [type_index], true));
542 // FIXME: the data size is wrong.
543 int datalen = (int) (reader.BaseStream.Position - pos2);
544 reader.BaseStream.Seek (-datalen, SeekOrigin.Current);
545 data = new byte [datalen];
546 reader.BaseStream.Read (data, 0, datalen);
550 void IDisposable.Dispose ()
555 private void Dispose (bool disposing)
570 internal sealed class ResourceEnumerator : IDictionaryEnumerator
572 private ResourceReader reader;
573 private int index = -1;
574 private bool finished;
576 internal ResourceEnumerator(ResourceReader readerToEnumerate)
578 reader = readerToEnumerate;
584 get { return index; }
587 public DictionaryEntry Entry {
589 if (reader.reader == null)
590 throw new InvalidOperationException("ResourceReader is closed.");
592 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
594 return new DictionaryEntry(Key, Value);
601 if (reader.reader == null)
602 throw new InvalidOperationException("ResourceReader is closed.");
604 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
606 return reader.cache [index].ResourceName;
613 if (reader.reader == null)
614 throw new InvalidOperationException("ResourceReader is closed.");
616 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
617 return reader.cache [index].ResourceValue;
622 public UnmanagedMemoryStream ValueAsStream
625 if (reader.reader == null)
626 throw new InvalidOperationException("ResourceReader is closed.");
628 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
629 return(reader.ResourceValueAsStream((string) Key, index));
634 public object Current
637 /* Entry does the checking, no
638 * need to repeat it here
644 public bool MoveNext ()
646 if (reader.reader == null)
647 throw new InvalidOperationException("ResourceReader is closed.");
652 if (++index < reader.resourceCount){
660 public void Reset () {
661 if (reader.reader == null)
662 throw new InvalidOperationException("ResourceReader is closed.");
669 if (reader.cache != null)
672 lock (reader.cache_lock) {
673 if (reader.cache != null)
676 ResourceCacheItem[] resources = new ResourceCacheItem [reader.resourceCount];
677 reader.LoadResourceValues (resources);
678 reader.cache = resources;
681 } // internal class ResourceEnumerator
682 } // public sealed class ResourceReader
683 } // namespace System.Resources