#!/usr/bin/env python3
# danger_ff_ban/core.py
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from datetime import datetime
import base64
import json
import time
import socket
import urllib.parse
import warnings
from functools import lru_cache
from . import my_pb2
from . import output_pb2

warnings.filterwarnings('ignore')

# ========== CONSTANTS ==========
KEY = bytes([89, 103, 38, 116, 99, 37, 68, 69, 117, 104, 54, 37, 90, 99, 94, 56])
IV = bytes([54, 111, 121, 90, 68, 114, 50, 50, 69, 51, 121, 99, 104, 106, 77, 37])

PLATFORM_DATA = {
    3: {"name": "Facebook", "icon": "FB", "color": "blue"},
    4: {"name": "Guest", "icon": "GU", "color": "gray"},
    5: {"name": "VK", "icon": "VK", "color": "cyan"},
    8: {"name": "Google", "icon": "GG", "color": "red"},
    10: {"name": "Apple", "icon": "AP", "color": "black"},
    11: {"name": "Twitter/X", "icon": "TW", "color": "cyan"}
}

MAX_RETRIES = 3
RETRY_DELAY = 2

VERSIONS_URL = "https://raw.githubusercontent.com/dangerapix/danger-ffjwt/main/versions.json"  # Change karo

# ========== VERSION FETCH ==========
@lru_cache(maxsize=1)
def fetch_latest_versions():
    try:
        resp = requests.get(VERSIONS_URL, timeout=5)
        if resp.status_code == 200:
            data = resp.json()
            return data.get("ob_version", "OB52"), data.get("client_version", "1.120.1")
    except:
        pass
    return "OB52", "1.120.1"

# ========== UTILITY FUNCTIONS ==========
def safe_str(s):
    if isinstance(s, str):
        return s.encode('ascii', 'ignore').decode('ascii')
    return s

def encrypt_message(plaintext):
    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    return cipher.encrypt(pad(plaintext, AES.block_size))

def b64url_decode(input_str: str) -> bytes:
    rem = len(input_str) % 4
    if rem:
        input_str += '=' * (4 - rem)
    return base64.urlsafe_b64decode(input_str)

def extract_jwt_payload_dict(jwt_s: str):
    try:
        parts = jwt_s.split('.')
        if len(parts) < 2:
            return None
        payload_bytes = b64url_decode(parts[1])
        return json.loads(payload_bytes.decode('utf-8', errors='ignore'))
    except:
        return None

# ========== SIMPLE PROTOBUF BUILDER (same as your script) ==========
class SimpleProtobuf:
    @staticmethod
    def encode_varint(value):
        result = bytearray()
        while value > 0x7F:
            result.append((value & 0x7F) | 0x80)
            value >>= 7
        result.append(value & 0x7F)
        return bytes(result)

    @staticmethod
    def decode_varint(data, start_index=0):
        value = 0
        shift = 0
        index = start_index
        while index < len(data):
            byte = data[index]
            index += 1
            value |= (byte & 0x7F) << shift
            if not (byte & 0x80):
                break
            shift += 7
        return value, index

    @staticmethod
    def parse_protobuf(data):
        result = {}
        index = 0
        while index < len(data):
            if index >= len(data):
                break
            tag = data[index]
            field_num = tag >> 3
            wire_type = tag & 0x07
            index += 1
            if wire_type == 0:
                value, index = SimpleProtobuf.decode_varint(data, index)
                result[field_num] = value
            elif wire_type == 2:
                length, index = SimpleProtobuf.decode_varint(data, index)
                if index + length <= len(data):
                    value_bytes = data[index:index + length]
                    index += length
                    try:
                        result[field_num] = value_bytes.decode('utf-8')
                    except:
                        result[field_num] = value_bytes.hex()
            else:
                break
        return result

    @staticmethod
    def encode_string(field_number, value):
        if isinstance(value, str):
            value = value.encode('utf-8')
        result = bytearray()
        result.extend(SimpleProtobuf.encode_varint((field_number << 3) | 2))
        result.extend(SimpleProtobuf.encode_varint(len(value)))
        result.extend(value)
        return bytes(result)

    @staticmethod
    def encode_int32(field_number, value):
        result = bytearray()
        result.extend(SimpleProtobuf.encode_varint((field_number << 3) | 0))
        result.extend(SimpleProtobuf.encode_varint(value))
        return bytes(result)

    @classmethod
    def create_login_payload(cls, open_id, access_token, platform, client_version, ob_version):
        payload = bytearray()
        current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        payload.extend(cls.encode_string(3, current_time))
        payload.extend(cls.encode_string(4, 'free fire'))
        payload.extend(cls.encode_int32(5, 1))
        payload.extend(cls.encode_string(7, client_version))
        payload.extend(cls.encode_string(8, 'Android OS 12 / API-31 (SP1A.210812.016/T505NDXS6CXB1)'))
        payload.extend(cls.encode_string(9, 'Handheld'))
        payload.extend(cls.encode_string(10, 'we'))
        payload.extend(cls.encode_string(11, 'WIFI'))
        payload.extend(cls.encode_int32(12, 1334))
        payload.extend(cls.encode_int32(13, 800))
        payload.extend(cls.encode_string(14, '225'))
        payload.extend(cls.encode_string(15, 'ARM64 FP ASIMD AES | 4032 | 8'))
        payload.extend(cls.encode_int32(16, 2705))
        payload.extend(cls.encode_string(17, 'Adreno (TM) 610'))
        payload.extend(cls.encode_string(18, 'OpenGL ES 3.2 V@0502.0 (GIT@5eaa426211, I07ee46fc66, 1633700387) (Date:10/08/21)'))
        payload.extend(cls.encode_string(19, 'Google|dbc5b426-9715-454a-9466-6c82e151d407'))
        payload.extend(cls.encode_string(20, '154.183.6.12'))
        payload.extend(cls.encode_string(21, 'ar'))
        payload.extend(cls.encode_string(22, open_id))
        payload.extend(cls.encode_string(23, str(platform)))
        payload.extend(cls.encode_string(24, 'Handheld'))
        payload.extend(cls.encode_string(25, 'samsung SM-T505N'))
        payload.extend(cls.encode_string(29, access_token))
        payload.extend(cls.encode_int32(30, 1))
        payload.extend(cls.encode_string(41, 'we'))
        payload.extend(cls.encode_string(42, 'WIFI'))
        payload.extend(cls.encode_string(57, 'e89b158e4bcf988ebd09eb83f5378e87'))
        payload.extend(cls.encode_int32(60, 22394))
        payload.extend(cls.encode_int32(61, 1424))
        payload.extend(cls.encode_int32(62, 3349))
        payload.extend(cls.encode_int32(63, 24))
        payload.extend(cls.encode_int32(64, 1552))
        payload.extend(cls.encode_int32(65, 22394))
        payload.extend(cls.encode_int32(66, 1552))
        payload.extend(cls.encode_int32(67, 22394))
        payload.extend(cls.encode_int32(73, 1))
        payload.extend(cls.encode_string(74, '/data/app/~~lqYdjEs9bd43CagTaQ9JPg==/com.dts.freefiremax-i72Sh_-sI0zZHs5Bw6aufg==/lib/arm64'))
        payload.extend(cls.encode_int32(76, 2))
        payload.extend(cls.encode_string(77, 'b4d2689433917e66100ba91db790bf37|/data/app/~~lqYdjEs9bd43CagTaQ9JPg==/com.dts.freefiremax-i72Sh_-sI0zZHs5Bw6aufg==/base.apk'))
        payload.extend(cls.encode_int32(78, 2))
        payload.extend(cls.encode_int32(79, 2))
        payload.extend(cls.encode_string(81, '64'))
        payload.extend(cls.encode_string(83, '2019115296'))
        payload.extend(cls.encode_int32(85, 1))
        payload.extend(cls.encode_string(86, 'OpenGLES3'))
        payload.extend(cls.encode_int32(87, 16383))
        payload.extend(cls.encode_int32(88, 4))
        payload.extend(cls.encode_string(90, 'Damanhur'))
        payload.extend(cls.encode_string(91, 'BH'))
        payload.extend(cls.encode_int32(92, 31095))
        payload.extend(cls.encode_string(93, 'android_max'))
        payload.extend(cls.encode_string(94, 'KqsHTzpfADfqKnEg/KMctJLElsm8bN2M4ts0zq+ifY+560USyjMSDL386RFrwRloT0ZSbMxEuM+Y4FSvjghQQZXWWpY='))
        payload.extend(cls.encode_int32(97, 1))
        payload.extend(cls.encode_int32(98, 1))
        payload.extend(cls.encode_string(99, str(platform)))
        payload.extend(cls.encode_string(100, str(platform)))
        payload.extend(cls.encode_string(102, ''))
        return bytes(payload)

# ========== MAIN FUNCTIONS ==========
def guest_to_access(uid, password):
    """UID/password se access_token aur open_id prapt kare."""
    url = "https://100067.connect.garena.com/oauth/guest/token/grant"
    payload = {
        'uid': safe_str(uid),
        'password': safe_str(password),
        'response_type': "token",
        'client_type': "2",
        'client_secret': "2ee44819e9b4598845141067b281621874d0d5d7af9d8f7e00c1e54715b7d1e3",
        'client_id': "100067"
    }
    headers = {
        'User-Agent': "GarenaMSDK/4.0.19P9(SM-M526B ;Android 13;pt;BR;)",
        'Connection': "Keep-Alive",
        'Accept-Encoding': "gzip"
    }
    for attempt in range(MAX_RETRIES):
        try:
            resp = requests.post(url, data=payload, headers=headers, verify=False, timeout=30)
            if resp.status_code == 200:
                data = resp.json()
                if 'access_token' in data and 'open_id' in data:
                    return {
                        'success': True,
                        'access_token': data['access_token'],
                        'open_id': data['open_id'],
                        'platform': data.get('platform', 4),
                        'uid': uid
                    }
                else:
                    return {'success': False, 'error': 'Invalid response', 'details': data}
        except Exception as e:
            if attempt == MAX_RETRIES-1:
                return {'success': False, 'error': str(e)}
            time.sleep(RETRY_DELAY * (2**attempt))
    return {'success': False, 'error': 'Max retries exceeded'}

def inspect_access_token(access_token):
    """Access token se open_id, platform, uid prapt kare."""
    for attempt in range(MAX_RETRIES):
        try:
            url = f"https://100067.connect.garena.com/oauth/token/inspect?token={access_token}"
            headers = {"User-Agent": "GarenaMSDK/4.0.19P4(G011A ;Android 9;en;US;)"}
            resp = requests.get(url, headers=headers, verify=False, timeout=10)
            if resp.status_code == 200:
                data = resp.json()
                if 'open_id' in data:
                    return {
                        'success': True,
                        'open_id': data['open_id'],
                        'platform': data.get('platform', 4),
                        'uid': data.get('uid') or data.get('user_id')
                    }
        except:
            pass
        time.sleep(RETRY_DELAY * (2**attempt))
    return {'success': False, 'error': 'Inspect failed'}

def convert_eat_to_access_token(eat_token):
    """EAT token ko access_token mein convert kare."""
    for attempt in range(MAX_RETRIES):
        try:
            callback_url = f"https://api-otrss.garena.com/support/callback/?access_token={eat_token}"
            resp = requests.get(callback_url, allow_redirects=True, timeout=30, verify=False)
            if 'help.garena.com' in resp.url:
                parsed = urllib.parse.urlparse(resp.url)
                params = urllib.parse.parse_qs(parsed.query)
                if 'access_token' in params:
                    access_token = params['access_token'][0]
                    inspect = inspect_access_token(access_token)
                    if inspect.get('success'):
                        inspect['access_token'] = access_token
                        inspect['success'] = True
                        return inspect
        except:
            pass
        time.sleep(RETRY_DELAY * (2**attempt))
    return {'success': False, 'error': 'EAT conversion failed'}

def major_login(access_token, open_id, platform_type=4, client_version=None, ob_version=None):
    """MajorLogin endpoint se JWT token generate kare."""
    if client_version is None or ob_version is None:
        ob_version, client_version = fetch_latest_versions()

    game_data = my_pb2.GameData()
    game_data.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    game_data.game_name = "free fire"
    game_data.game_version = 1
    game_data.version_code = client_version
    game_data.os_info = "Android OS 9 / API-28 (PI/rel.cjw.20220518.114133)"
    game_data.device_type = "Handheld"
    game_data.network_provider = "Verizon Wireless"
    game_data.connection_type = "WIFI"
    game_data.screen_width = 1280
    game_data.screen_height = 960
    game_data.dpi = "240"
    game_data.cpu_info = "ARMv7 VFPv3 NEON VMH | 2400 | 4"
    game_data.total_ram = 5951
    game_data.gpu_name = "Adreno (TM) 640"
    game_data.gpu_version = "OpenGL ES 3.0"
    game_data.user_id = "Google|74b585a9-0268-4ad3-8f36-ef41d2e53610"
    game_data.ip_address = "172.190.111.97"
    game_data.language = "en"
    game_data.open_id = open_id
    game_data.access_token = access_token
    game_data.platform_type = platform_type
    game_data.field_99 = str(platform_type)
    game_data.field_100 = str(platform_type)

    serialized = game_data.SerializeToString()
    encrypted = encrypt_message(serialized)

    endpoints = [
        "https://loginbp.ggpolarbear.com/MajorLogin",
        "https://loginbp.ggblueshark.com/MajorLogin",
        "https://loginbp.common.ggbluefox.com/MajorLogin"
    ]
    headers = {
        "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 9; ASUS_Z01QD Build/PI)",
        "Connection": "Keep-Alive",
        "Accept-Encoding": "gzip",
        "Content-Type": "application/octet-stream",
        "Expect": "100-continue",
        "X-Unity-Version": "2018.4.11f1",
        "X-GA": "v1 1",
        "ReleaseVersion": ob_version
    }

    for endpoint in endpoints:
        try:
            resp = requests.post(endpoint, data=encrypted, headers=headers, verify=False, timeout=15)
            if resp.status_code == 200:
                jwt_msg = output_pb2.Garena_420()
                jwt_msg.ParseFromString(resp.content)
                if jwt_msg.token:
                    jwt_payload = extract_jwt_payload_dict(jwt_msg.token)
                    key_from_response = KEY
                    iv_from_response = IV
                    if jwt_msg.binary and len(jwt_msg.binary) >= 32:
                        key_from_response = jwt_msg.binary[:16]
                        iv_from_response = jwt_msg.binary[16:32]
                    elif jwt_msg.binary_data and len(jwt_msg.binary_data) >= 32:
                        key_from_response = jwt_msg.binary_data[:16]
                        iv_from_response = jwt_msg.binary_data[16:32]
                    return {
                        'success': True,
                        'jwt_token': jwt_msg.token,
                        'jwt_payload': jwt_payload,
                        'account_id': jwt_msg.account_id if jwt_msg.account_id else (jwt_payload.get('account_id') if jwt_payload else None),
                        'key': key_from_response,
                        'iv': iv_from_response,
                        'endpoint': endpoint
                    }
        except:
            continue
    return {'success': False, 'error': 'MajorLogin failed'}

def get_login_data(jwt_token, ob_version=None, lock_region=None, enc_data=None):
    """GetLoginData endpoint se server IP/port prapt kare."""
    if ob_version is None:
        ob_version, _ = fetch_latest_versions()

    if not lock_region:
        payload = extract_jwt_payload_dict(jwt_token)
        lock_region = payload.get('lock_region', '').upper() if payload else ''

    # Endpoint select
    if lock_region == "IND":
        url = "https://client.ind.freefiremobile.com/GetLoginData"
        host = "client.ind.freefiremobile.com"
    elif lock_region in ["BR", "US", "NA", "SAC"]:
        url = "https://client.us.freefiremobile.com/GetLoginData"
        host = "client.us.freefiremobile.com"
    else:
        url = "https://clientbp.ggpolarbear.com/GetLoginData"
        host = "clientbp.ggblueshark.com"

    headers = {
        'Expect': '100-continue',
        'Authorization': f'Bearer {jwt_token}',
        'X-Unity-Version': '2018.4.11f1',
        'X-GA': 'v1 1',
        'ReleaseVersion': ob_version,
        'Content-Type': 'application/x-www-form-urlencoded',
        'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 9; G011A Build/PI)',
        'Host': host,
        'Connection': 'close',
        'Accept-Encoding': 'gzip, deflate, br',
    }

    if enc_data is None:
        # dummy payload
        dummy = SimpleProtobuf.create_login_payload("dummy", "dummy", "4", "1.120.1", ob_version)
        enc_data = encrypt_message(pad(dummy, 16))

    for attempt in range(MAX_RETRIES):
        try:
            resp = requests.post(url, headers=headers, data=enc_data, timeout=12, verify=False)
            if resp.status_code == 200:
                parsed = SimpleProtobuf.parse_protobuf(resp.content)
                if '14' in parsed:
                    addr = parsed['14']
                    if isinstance(addr, str):
                        ip = addr[:-6]
                        port = int(addr[-5:])
                    else:
                        ip, port = None, None
                    return {
                        'success': True,
                        'online_ip': ip,
                        'online_port': port,
                        'region': lock_region,
                        'endpoint_used': url
                    }
        except:
            pass
        time.sleep(RETRY_DELAY * (2**attempt))
    return {'success': False, 'error': 'GetLoginData failed'}

def build_start_packet(account_id, timestamp, jwt_token, key, iv):
    """Game server ke liye initial packet banaye."""
    try:
        jwt_hex = jwt_token.encode().hex()
        cipher = AES.new(key, AES.MODE_CBC, iv)
        encrypted = cipher.encrypt(pad(bytes.fromhex(jwt_hex), AES.block_size)).hex()
        head_len = hex(len(encrypted)//2)[2:]
        ide_hex = hex(int(account_id))[2:]
        zeros = "0" * (16 - len(ide_hex))
        timestamp_hex = hex(timestamp)[2:].zfill(16)
        head = f"0115{zeros}{ide_hex}{timestamp_hex}00000{head_len}"
        return head + encrypted
    except:
        return None

def connect_to_game_server(account_id, timestamp, jwt_token, key, iv, online_ip, online_port):
    """Game server se socket connection banaye aur packet bheje."""
    packet = build_start_packet(account_id, timestamp, jwt_token, key, iv)
    if not packet:
        return False
    for attempt in range(MAX_RETRIES):
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(10)
            sock.connect((online_ip, online_port))
            sock.send(bytes.fromhex(packet))
            sock.close()
            return True
        except:
            time.sleep(RETRY_DELAY * (2**attempt))
    return False