/// <summary>This method acts as a factory. It recognizes the next chunk type /// and instanciate a sub class accordingly.</summary> /// <param name="buffer">Buffer to acquire data from.</param> /// <param name="offset">Offset within buffer to start reading. On return the /// offset is updated to reflect bytes consumption.</param> /// <param name="stringPool">An optional string pool that will be used to resolve /// some string reference. This parameter is required when handling an XML document /// content, it is optional for other contents.</param> /// <returns>An instance of the class that handle the chunk type.</returns> internal static ResourceChunkHeader Create(byte[] buffer, ref int offset, StringPool stringPool = null) { ChunkType chunkType = (ChunkType)Helpers.PeekUInt16(buffer, offset); switch (chunkType) { case ChunkType.Null: return NullChunk.Create(buffer, ref offset); case ChunkType.StringPool: return new StringPool(buffer, ref offset); case ChunkType.Table: return new TableHeader(buffer, ref offset); case ChunkType.TablePackage: return new Package(buffer, ref offset); case ChunkType.TableType: if (null == stringPool) { throw new ArgumentNullException("stringPool"); } return new Type(buffer, ref offset, stringPool); case ChunkType.TableTypeSpec: return new TypeSpecification(buffer, ref offset); case ChunkType.XmlElementEnd: case ChunkType.XmlElementStart: case ChunkType.XmlNamespaceEnd: case ChunkType.XmlNamespaceStart: if (null == stringPool) { throw new ArgumentNullException("stringPool"); } return XmlTreeItem.Create(buffer, ref offset, chunkType, stringPool); case ChunkType.XmlResourceMap: return new XmlResourceMap(buffer, ref offset); default: throw new NotSupportedException("Unsupported chunk type : " + chunkType.ToString()); } }
internal XmlElementItem(byte[] buffer, ref int offset, StringPool stringPool, bool startElement) : base(buffer, ref offset, stringPool) { int baseOffset = offset; Namespace = stringPool.GetReferencedString(buffer, ref offset, true); Name = stringPool.GetReferencedString(buffer, ref offset, true); if (startElement) { base.MarkAsStarter(); } if (!startElement) { return; } // End elements don't have these values. ushort attributeStart = Helpers.ReadUInt16(buffer, ref offset); ushort attributeSize = Helpers.ReadUInt16(buffer, ref offset); ushort attributeCount = Helpers.ReadUInt16(buffer, ref offset); // The three next values are 1 based indexes in the attribute array. Their // value is 0 if the attribute doesn't exist. ushort idAttributeIndex = Helpers.ReadUInt16(buffer, ref offset); if (0 != idAttributeIndex) { int i = 1; } ushort classAttributeIndex = Helpers.ReadUInt16(buffer, ref offset); ushort styleAttributeIndex = Helpers.ReadUInt16(buffer, ref offset); // This computaion should not usually modify the current offset value. offset = baseOffset + attributeStart; List<XmlElementAttributeItem> attributes = new List<XmlElementAttributeItem>(); for (int index = 0; index < attributeCount; index++) { attributes.Add(new XmlElementAttributeItem(buffer, ref offset, stringPool)); } Attributes = attributes.ToArray(); return; }
protected XmlTreeItem(byte[] buffer, ref int offset, StringPool stringPool) : base(buffer, ref offset) { LineNumber = Helpers.ReadUInt32(buffer, ref offset); Comment = stringPool.GetReferencedString(buffer, ref offset, true); return; }
internal Type(byte[] buffer, ref int offset, StringPool keyStrings) : base(buffer, ref offset) { int baseOffset = (int)(offset - ResourceChunkHeader.ChunkHeaderSize); Id = buffer[offset++]; // Skip null bytes. offset += 3; // Number of uint32_t entry indices that follow. uint entryCount = Helpers.ReadUInt32(buffer, ref offset); // Offset from header where ResTable_entry data starts. uint entriesStart = Helpers.ReadUInt32(buffer, ref offset); Configuration = new ResourceTableConfiguration(buffer, ref offset); uint[] valuesOffset = new uint[(int)entryCount]; for (int index = 0; index < entryCount; index++) { uint candidateOffset = Helpers.ReadUInt32(buffer, ref offset); valuesOffset[index] = (uint.MaxValue == candidateOffset) ? uint.MaxValue : (uint)(baseOffset + entriesStart + candidateOffset); } Resources = new Resource[entryCount]; for(int index = 0; index < entryCount; index++) { if (uint.MaxValue == valuesOffset[index]) { continue; } offset = (int)valuesOffset[index]; Resources[index] = new Resource(buffer, ref offset, keyStrings); } offset = (int)(baseOffset + base.Size); return; }
internal XmlElementAttributeItem(byte[] buffer, ref int offset, StringPool stringPool) { Namespace = stringPool.GetReferencedString(buffer, ref offset, true); Name = stringPool.GetReferencedString(buffer, ref offset, true); RawValue = stringPool.GetReferencedString(buffer, ref offset, true); TypedValue = new ResourceValue(buffer, ref offset); return; }
internal XmlNamespaceItem(byte[] buffer, ref int offset, StringPool stringPool, bool startElement) : base(buffer, ref offset, stringPool) { if (startElement) { base.MarkAsStarter(); } Prefix = stringPool.GetReferencedString(buffer, ref offset); Uri = stringPool.GetReferencedString(buffer, ref offset); return; }
internal Resource(byte[] buffer, ref int offset, StringPool stringPool) { // Number of bytes in this structure. ushort size = Helpers.ReadUInt16(buffer, ref offset); ResourceFlags flags = (ResourceFlags)Helpers.ReadUInt16(buffer, ref offset); // Reference into ResTable_package::keyStrings identifying this entry. Name = stringPool.GetReferencedString(buffer, ref offset); Value = new ResourceValue(buffer, ref offset); return; }
/// <summary>This method acts as a factory for sub-classes from the <see cref="XmlTreeItem"/> /// class.</summary> /// <param name="buffer">Buffer to get bytes from.</param> /// <param name="offset">Offset of the first unconsumed buffer byte. Will be updated on /// return to denote additional consumed bytes.</param> /// <param name="chunkType">The chunk type that has been detected.</param> /// <param name="stringPool">String pool from the compressed document.</param> /// <returns></returns> internal static XmlTreeItem Create(byte[] buffer, ref int offset, ChunkType chunkType, StringPool stringPool) { bool startElement = false; switch(chunkType) { case ChunkType.XmlNamespaceStart: startElement = true; goto case ChunkType.XmlNamespaceEnd; case ChunkType.XmlNamespaceEnd: return new XmlNamespaceItem(buffer, ref offset, stringPool, startElement); case ChunkType.XmlElementStart: startElement = true; goto case ChunkType.XmlElementEnd; case ChunkType.XmlElementEnd: return new XmlElementItem(buffer, ref offset, stringPool, startElement); default: throw new ArgumentException(); } }
internal string GetStringRepresentation(StringPool stringPool, PackageResolverDelegate packageResolver) { switch (DataType) { case ResourceValueType.Boolean: return (0 == Data) ? "false" : "true"; case ResourceValueType.Decimal: return Data.ToString(); case ResourceValueType.Hexadecimal: return string.Format("0x{0}", Data); case ResourceValueType.Null: return null; case ResourceValueType.String: return stringPool[Data]; case ResourceValueType.Reference: uint packageId = (Data & 0xFF000000) >> 24; uint index = (Data & 0x00FF0000) >> 16; uint entryIndex = Data & 0x0000FFFF; Package package = packageResolver(packageId); TypeSpecification specification = package.GetType((int)index); if (!specification.IsReferenceIndexValid(entryIndex)) { throw new ApkFormatException(); } // TODO : Should be more specific when several types exist under the // specification. Should filter relatively to a target configuration. Resource referencedResource = null; foreach (Type scannedType in specification.EnumerateTypes()) { referencedResource = scannedType.Resources[(int)entryIndex]; if (null != referencedResource) { break; } } if (null == referencedResource) { throw new ApkFormatException(); } return referencedResource.Name; default: throw new CompressedFormatException( "Resource value type not supported : " + DataType.ToString()); } }
internal Package(byte[] buffer, ref int offset) : base(buffer, ref offset) { int startOffset = offset - ResourceChunkHeader.ChunkHeaderSize; Id = Helpers.ReadUInt32(buffer, ref offset); byte[] nameBuffer = new byte[MaximumNameLength]; int nameBytesCount = sizeof(ushort) * MaximumNameLength; int effectiveNameLength; for (effectiveNameLength = 0; effectiveNameLength < MaximumNameLength; effectiveNameLength++) { int unicodeCharOffset = sizeof(ushort) * effectiveNameLength; if ((0 == buffer[offset + unicodeCharOffset]) && (0 == buffer[offset + unicodeCharOffset + 1])) { break; } } Name = UnicodeEncoding.Unicode.GetString(buffer, offset, effectiveNameLength * sizeof(ushort)); offset += nameBytesCount; // Offset to a ResStringPool_header defining the resource // type symbol table. If zero, this package is inheriting from // another base package (overriding specific values in it). uint typeStrings = Helpers.ReadUInt32(buffer, ref offset); // Last index into typeStrings that is for public use by others. uint lastPublicType = Helpers.ReadUInt32(buffer, ref offset); // Offset to a ResStringPool_header defining the resource // key symbol table. If zero, this package is inheriting from // another base package (overriding specific values in it). uint keyStrings = Helpers.ReadUInt32(buffer, ref offset); // Last index into keyStrings that is for public use by others. uint lastPublicKey = Helpers.ReadUInt32(buffer, ref offset); // Go on with string pools offset = (int)(startOffset + typeStrings); ResourceChunkHeader chunk = ResourceChunkHeader.Create(buffer, ref offset); if (ChunkType.StringPool != chunk.Type) { throw new CompressedFormatException( "Expecting a string pool, found a " + chunk.Type.ToString()); } _typeNames = (StringPool)chunk; offset = (int)(startOffset + keyStrings); chunk = ResourceChunkHeader.Create(buffer, ref offset); if (ChunkType.StringPool != chunk.Type) { throw new CompressedFormatException( "Expecting a string pool, found a " + chunk.Type.ToString()); } _keyNames = (StringPool)chunk; int endOfPackageOffset = (int)(startOffset + base.Size); chunk = ResourceChunkHeader.Create(buffer, ref offset); _specifications = new List<TypeSpecification>(); while (offset < endOfPackageOffset) { TypeSpecification specification = chunk as TypeSpecification; if (null == specification) { throw new CompressedFormatException( "Expecting a type specification, found a " + chunk.Type.ToString()); } _specifications.Add(specification); while (offset < endOfPackageOffset) { chunk = ResourceChunkHeader.Create(buffer, ref offset, _keyNames); if (chunk is TypeSpecification) { break; } specification.AddType((Type)chunk); } } return; }
internal static XmlDocument GetDocument(FileStream from, PackageResolverDelegate packageResolver) { if (null == from) { throw new ArgumentNullException(); } if (!from.CanRead) { throw new ArgumentException("Readable stream required."); } if (0 != from.Position) { throw new ArgumentException("Ill positioned stream."); } byte[] buffer = new byte[(int)from.Length]; if (buffer.Length != from.Read(buffer, 0, buffer.Length)) { throw new ApkFormatException(); } int offset = 0; ResourceChunkHeader header = new ResourceChunkHeader(buffer, ref offset); if (ChunkType.Xml != header.Type) { throw new ApkFormatException("XML chunk was expected."); } XmlDocument result = new XmlDocument(); XmlNamespaceManager namespaceManager = new XmlNamespaceManager(result.NameTable); StringPool stringPool = new StringPool(buffer, ref offset); ResourceChunkHeader scannedChunk = ResourceChunkHeader.Create(buffer, ref offset, stringPool); XmlResourceMap resourceMap = scannedChunk as XmlResourceMap; // The resource map is optional. Should we have found it we need to read one // more chunk. if (null != resourceMap) { scannedChunk = ResourceChunkHeader.Create(buffer, ref offset, stringPool); } XmlNamespaceItem namespaceItem = scannedChunk as XmlNamespaceItem; if ((null == namespaceItem) || !namespaceItem.Start) { throw new CompressedFormatException(); } namespaceManager.AddNamespace(namespaceItem.Prefix, namespaceItem.Uri); Stack<XmlTreeItem> stackedItem = new Stack<XmlTreeItem>(); stackedItem.Push(namespaceItem); XmlElementItem currentElementItem = null; XmlElement currentElement = null; while (offset < buffer.Length) { XmlTreeItem item = (XmlTreeItem)ResourceChunkHeader.Create(buffer, ref offset, stringPool); if (item.Start) { stackedItem.Push(item); namespaceItem = item as XmlNamespaceItem; if (null != namespaceItem) { namespaceManager.AddNamespace(namespaceItem.Prefix, namespaceItem.Uri); continue; } currentElementItem = item as XmlElementItem; if (null != currentElementItem) { XmlElement newElement = result.CreateElement(currentElementItem.Name, currentElementItem.Namespace); if (null == currentElement) { result.AppendChild(newElement); } else { currentElement.AppendChild(newElement); } currentElement = newElement; foreach (XmlElementItem.XmlElementAttributeItem scannedAttribute in currentElementItem.Attributes) { XmlAttribute newAttribute = result.CreateAttribute(scannedAttribute.Name, scannedAttribute.Namespace); newAttribute.Value = scannedAttribute.GetStringRepresentation(stringPool, packageResolver); currentElement.Attributes.Append(newAttribute); } continue; } throw new NotImplementedException(); } else { if (0 == stackedItem.Count) { throw new CompressedFormatException("Unbalanced start/end XML elements"); } XmlTreeItem poppedItem = stackedItem.Pop(); if (!item.StartEndMatch(poppedItem)) { throw new CompressedFormatException("Start/end XML elements mismatch"); } // Hem. Little bit loose. if (null != currentElement) { currentElement = (currentElement.ParentNode as XmlElement); } } int i = 1; } return result; }
internal string GetStringRepresentation(StringPool stringPool, PackageResolverDelegate packageResolver) { return RawValue ?? TypedValue.GetStringRepresentation(stringPool, packageResolver); }