# packer.py
import re
import binascii

class cPacker:
    def detect(self, source):
        return bool(re.search(r"eval\s*\(\s*function\s*\(\s*p\s*,\s*a\s*,\s*c\s*,\s*k\s*,\s*e\s*,", source))

    def unpack(self, source):
        payload, symtab, radix, count = self._filterargs(source)

        if len(symtab) > count and count > 0:
            del symtab[count:]
        if len(symtab) < count and count > 0:
            symtab.append('BUGGED')
        if count != len(symtab):
            raise ValueError('Malformed p.a.c.k.e.r. symtab.')

        try:
            unbase = self._Unbaser(radix)
        except Exception:
            raise ValueError('Unknown p.a.c.k.e.r. encoding.')

        def lookup(match):
            word = match.group(0)
            idx = int(word) if radix == 1 else unbase(word)
            return symtab[idx] if 0 <= idx < len(symtab) else word

        payload = payload.replace("\\\\", "\\").replace("\\'", "'")
        uses_from_char_code = 'String.fromCharCode' in source

        if uses_from_char_code:
            def getstring(c, a=radix):
                foo = chr(c % a + 161)
                return foo if c < a else getstring(c // a, a) + foo
            for i in range(count - 1, -1, -1):
                payload = payload.replace(getstring(i), symtab[i])
            return self._replacejsstrings(self._replacestrings(payload))
        else:
            source = re.sub(r"\b\w+\b", lookup, payload)
            return self._replacestrings(source)

    def _filterargs(self, source):
        source = source.replace(',[],', ',0,').replace("\\'", "'")

        juicer = r"}\s*\(\s*(.*?)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*\((.*?)\).split\((.*?)\)"
        args = re.search(juicer, source, re.DOTALL)
        if args:
            a = args.groups()
            return a[0], a[3].split(a[4]), int(a[1]), int(a[2])

        juicer = r"}\('(.*)',\s*(\d+),\s*(\d+),\s*'(.*)'\.split\('(.*?)'\)"
        args = re.search(juicer, source, re.DOTALL)
        if args:
            a = args.groups()
            return a[0], a[3].split(a[4]), int(a[1]), int(a[2])

        raise ValueError('Could not parse p.a.c.k.e.r data.')

    def _replacestrings(self, source):
        match = re.search(r'var\s*(\w+)\s*=\s*\["(.*?)"\];', source, re.DOTALL)
        if match:
            varname, strings = match.groups()
            startpoint = len(match.group(0))
            lookup = strings.split('","')
            for i, val in enumerate(lookup):
                if '\\x' in val:
                    try:
                        decoded = binascii.unhexlify(val.replace('\\x', '')).decode('utf-8')
                        source = source.replace(f'{varname}[{i}]', f'"{decoded}"')
                    except:
                        pass
            return source[startpoint:]
        return source

    def _replacejsstrings(self, source):
        matches = re.findall(r'\\x([0-9A-Fa-f]{2})', source)
        for hex_val in set(matches):
            try:
                char = binascii.unhexlify(hex_val).decode('utf-8')
                source = source.replace(f'\\x{hex_val}', char)
            except:
                pass
        return source

    class _Unbaser:
        ALPHABET = {
            62: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
            95: ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'
        }

        def __init__(self, base):
            self.base = base
            if 2 <= base <= 36:
                self.unbase = lambda s: int(s, base)
            else:
                if base not in self.ALPHABET:
                    if base < 62:
                        self.ALPHABET[base] = self.ALPHABET[62][:base]
                    elif base < 95:
                        self.ALPHABET[base] = self.ALPHABET[95][:base]
                self.dictionary = {ch: i for i, ch in enumerate(self.ALPHABET[base])}
                self.unbase = self._dictunbaser

        def __call__(self, s):
            return self.unbase(s)

        def _dictunbaser(self, s):
            ret = 0
            for i, ch in enumerate(reversed(s)):
                ret += (self.base ** i) * self.dictionary[ch]
            return ret