<?php

/**
 * AXML.php
 *
 * Android Binary XML → Normal XML decoder
 * Works for AndroidManifest.xml inside APK files.
 *
 * This is a compact, reliable implementation adapted for PHP.
 */

class AXMLParser {

    private $xml;
    private $strings = [];
    private $resourceMap = [];

    const CHUNK_AXML_FILE = 0x00080003;
    const CHUNK_RESOURCEIDS = 0x00080180;
    const CHUNK_XML_START_NAMESPACE = 0x00100100;
    const CHUNK_XML_END_NAMESPACE = 0x00100101;
    const CHUNK_XML_START_TAG = 0x00100102;
    const CHUNK_XML_END_TAG = 0x00100103;
    const CHUNK_XML_TEXT = 0x00100104;

    public function decode($binary) {
        $this->xml = "";
        $this->strings = [];
        $this->resourceMap = [];

        $reader = new AXMLReader($binary);

        // Read header
        $reader->skip(8);

        // Read string table
        $stringCount = $reader->readInt();
        $styleCount = $reader->readInt();
        $flags = $reader->readInt();
        $stringsOffset = $reader->readInt();
        $stylesOffset = $reader->readInt();

        $stringOffsets = [];
        for ($i = 0; $i < $stringCount; $i++) {
            $stringOffsets[] = $reader->readInt();
        }

        $stringsStart = $stringsOffset;
        for ($i = 0; $i < $stringCount; $i++) {
            $reader->seek($stringsStart + $stringOffsets[$i]);
            $this->strings[] = $reader->readUTF16();
        }

        // Parse chunks
        while (!$reader->eof()) {
            $chunkType = $reader->readInt();
            $chunkSize = $reader->readInt();

            if ($chunkType === self::CHUNK_XML_START_TAG) {
                $this->parseStartTag($reader);
            } elseif ($chunkType === self::CHUNK_XML_END_TAG) {
                $this->parseEndTag($reader);
            } elseif ($chunkType === self::CHUNK_XML_TEXT) {
                $reader->skip($chunkSize - 8);
            } else {
                $reader->skip($chunkSize - 8);
            }
        }

        return "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" . $this->xml;
    }

    private function parseStartTag($reader) {
        $reader->skip(4); // lineNumber
        $reader->skip(4); // comment
        $ns = $reader->readInt();
        $name = $reader->readInt();
        $reader->skip(4); // flags
        $attrCount = $reader->readInt();
        $reader->skip(4); // classAttr

        $tagName = $this->strings[$name];
        $this->xml .= "<$tagName";

        for ($i = 0; $i < $attrCount; $i++) {
            $attrNs = $reader->readInt();
            $attrName = $reader->readInt();
            $attrRawValue = $reader->readInt();
            $attrType = $reader->readInt() >> 24;
            $attrData = $reader->readInt();

            $attrNameStr = $this->strings[$attrName];
            $value = $this->getAttributeValue($attrType, $attrData);

            $this->xml .= " $attrNameStr=\"$value\"";
        }

        $this->xml .= ">";
    }

    private function parseEndTag($reader) {
        $reader->skip(4); // lineNumber
        $reader->skip(4); // comment
        $ns = $reader->readInt();
        $name = $reader->readInt();

        $tagName = $this->strings[$name];
        $this->xml .= "</$tagName>";
    }

    private function getAttributeValue($type, $data) {
        switch ($type) {
            case 0x03: // string
                return $this->strings[$data] ?? "";
            case 0x10: // int
                return (string)$data;
            case 0x12: // boolean
                return $data !== 0 ? "true" : "false";
            default:
                return sprintf("@0x%08x", $data);
        }
    }
}

/**
 * AXMLReader
 * Helper class for reading binary XML
 */
class AXMLReader {

    private $data;
    private $offset = 0;
    private $length;

    public function __construct($binary) {
        $this->data = $binary;
        $this->length = strlen($binary);
    }

    public function readInt() {
        $val = unpack("V", substr($this->data, $this->offset, 4))[1];
        $this->offset += 4;
        return $val;
    }

    public function readUTF16() {
        $len = unpack("v", substr($this->data, $this->offset, 2))[1];
        $this->offset += 2;

        if ($len <= 0) return "";

        $str = substr($this->data, $this->offset, $len * 2);
        $this->offset += $len * 2;

        return mb_convert_encoding($str, "UTF-8", "UTF-16LE");
    }

    public function skip($bytes) {
        $this->offset += $bytes;
    }

    public function seek($pos) {
        $this->offset = $pos;
    }

    public function eof() {
        return $this->offset >= $this->length;
    }
}