UNPKG

node-apk

Version:

A library to parse Android application manifest and signature

116 lines (101 loc) 3.36 kB
/*Copyright (c) 2019 XdevL. All rights reserved. This work is licensed under the terms of the MIT license. For a copy, see <https://opensource.org/licenses/MIT>.*/ import Source from "./source"; export enum ChunkType { NULL = 0x0000, STRING_POOL = 0x0001, TABLE = 0x0002, XML = 0x0003, XML_FIRST_CHUNK = 0x0100, XML_START_NAMESPACE = 0x0100, XML_END_NAMESPACE = 0x0101, XML_START_ELEMENT = 0x0102, XML_END_ELEMENT = 0x0103, XML_CDATA = 0x0104, XML_LAST_CHUNK = 0x017f, XML_RESOURCE_MAP = 0x0180, TABLE_PACKAGE = 0x0200, TABLE_TYPE = 0x0201, TABLE_TYPE_SPEC = 0x0202, TABLE_LIBRARY = 0x0203, } export class Chunk { public readonly type: number; public readonly headerSize: number; public readonly chunkSize: number; public readonly headerSource: Source; public readonly chunkSource: Source; constructor(source: Source, chunkType?: ChunkType) { this.type = source.readUShort(); if (!chunkType || this.type === chunkType) { this.headerSize = source.readUShort(); this.chunkSize = source.readUInt(); this.headerSource = source.source(this.headerSize - 8); this.chunkSource = source.source(this.chunkSize - this.headerSize); } else { throw Error(`Found incorrect chunk type: ${this.type}, expected: ${chunkType}`); } } } export class StringPool { private static readUtf8String(source: Source): string { source.getCursorAndMove(1); // skip char length return source.readUtf8String(source.readUByte()); } private static readUtf16String(source: Source): string { return source.readUtf16String(source.readUShort() * 2); } public readonly stringCount: number; public readonly styleCount: number; public readonly flags: number; public readonly stringsStart: number; public readonly stylesStart: number; public readonly values: string[]; constructor(chunk: Chunk) { this.stringCount = chunk.headerSource.readUInt(); this.styleCount = chunk.headerSource.readUInt(); this.flags = chunk.headerSource.readUInt(); this.stringsStart = chunk.headerSource.readUInt(); this.stylesStart = chunk.headerSource.readUInt(); this.values = []; const indexes: number[] = []; for (let i = 0; i < this.stringCount; ++i) { indexes.push(chunk.chunkSource.readUInt()); } // TODO: make sure indexes are sorted for (const index of indexes) { chunk.chunkSource.moveAt(this.stringsStart - chunk.headerSize + index); if (this.flags & 256) { this.values.push(StringPool.readUtf8String(chunk.chunkSource)); } else { this.values.push(StringPool.readUtf16String(chunk.chunkSource)); } } } } enum ResourceType { NULL = 0x00, REFERENCE = 0x01, STRING = 0x03, INT_DEC = 0x10, INT_BOOLEAN = 0x12, } export function parseResourceValue(source: Source, stringPool: StringPool): any { source.getCursorAndMove(3); // size + res0 const type = source.readUByte(); switch (type) { case ResourceType.REFERENCE: return source.readInt(); case ResourceType.STRING: { const index = source.readInt(); return index >= 0 ? stringPool.values[index] : null; } case ResourceType.INT_DEC: return source.readInt(); case ResourceType.INT_BOOLEAN: return source.readInt() === 0 ? false : true; default: return null; } }