node-apk
Version:
A library to parse Android application manifest and signature
116 lines (101 loc) • 3.36 kB
text/typescript
/*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;
}
}