Auteur Sujet: Installation de Connect TV d'SFR sur box Android TV  (Lu 90478 fois)

0 Membres et 2 Invités sur ce sujet

yarienavoir

  • Abonné SFR fibre FttH
  • *
  • Messages: 8
  • 92130
Installation de Connect TV d'SFR sur box Android TV
« Réponse #204 le: 16 juin 2026 à 14:13:41 »
Bonjour jnq35,
Pas trouvé sur les différents forum et donc pas sûr quelle marche mieux que la 8.1.9. Si quelqu'un peut l'extraire ?
Merci

yarienavoir

  • Abonné SFR fibre FttH
  • *
  • Messages: 8
  • 92130
Installation de Connect TV d'SFR sur box Android TV
« Réponse #205 le: 17 juin 2026 à 10:52:04 »
Bonjour, Finalement retrouvé la 8.1.8 et modded mais même message d'erreur sur le flux M6 4K. Les autres Tf1 4K et France 2 UHD fonctionnent pourtant.
Cordialement

jnq35

  • Abonné SFR fibre FttH
  • *
  • Messages: 91
  • Rennes SFR FTTH XGS-PON
Installation de Connect TV d'SFR sur box Android TV
« Réponse #206 le: 18 juin 2026 à 18:26:37 »
Bonjour il y a des protections et contre-mesure spécifique pour protéger la diffusion de la coupe du monde.

Elcubano

  • Abonné SFR fibre FttH
  • *
  • Messages: 3
Installation de Connect TV d'SFR sur box Android TV
« Réponse #207 le: 25 juin 2026 à 12:25:26 »
Hello, Je cherche à installer la dernière version modifiée de l’app Connect TV SFR. Si quelqu’un a un lien, je suis preneur 🙂
Je suis encore sur la version 7.1.0 qui fonctionne, mais je me demande s’il y a des améliorations dans les versions récentes.

jpmiller

  • Abonné RED by SFR fibre FttH
  • *
  • Messages: 104
  • 68
Installation de Connect TV d'SFR sur box Android TV
« Réponse #208 le: 25 juin 2026 à 13:22:51 »
Un lien vers la dernière version moddée actuellement disponible est sur le post suivant :
https://lafibre.info/sfr-les-news/installation-de-connect-tv-dsfr-sur-box-android-tv-60183/msg1151044/#msg1151044

weezer

  • Abonné Orange Fibre
  • *
  • Messages: 41
Installation de Connect TV d'SFR sur box Android TV
« Réponse #209 le: 25 juin 2026 à 22:24:57 »
Hello,
Version 8.1.10 b1 dispo, le mod est OK ici

Elcubano

  • Abonné SFR fibre FttH
  • *
  • Messages: 3
Installation de Connect TV d'SFR sur box Android TV
« Réponse #210 le: 26 juin 2026 à 17:44:37 »

weezer

  • Abonné Orange Fibre
  • *
  • Messages: 41
Installation de Connect TV d'SFR sur box Android TV
« Réponse #211 le: 01 juillet 2026 à 16:14:21 »
Hello,
Version 8.1.11 dev-mode OK ici

tao

  • Abonné SFR fibre FttH
  • *
  • Messages: 8
Installation de Connect TV d'SFR sur box Android TV
« Réponse #212 le: 01 juillet 2026 à 16:16:55 »
Merci de l'info  :D

Hello,
Version 8.1.11 dev-mode OK ici

Guilou29

  • Abonné RED by SFR fibre FttH
  • *
  • Messages: 2
Installation de Connect TV d'SFR sur box Android TV
« Réponse #213 le: 12 juillet 2026 à 11:49:21 »
Hello,
Version 8.1.11 dev-mode OK ici
Bonjour, vous auriez un lien avec la dernière version svp ?  :)

SuperBaobab

  • Abonné SFR fibre FttH
  • *
  • Messages: 12
  • Lyon (69)
Installation de Connect TV d'SFR sur box Android TV
« Réponse #214 le: Aujourd'hui à 18:05:35 »
Post de la flemme, 100% IA (désolé).
J'ai testé le script sur la 8.1.10 avec succès, c'est fait pour essayer de supporter les prochaines versions aussi.

Vous devriez pouvoir être autonomes entre ce script et celui pour récupérer les derniers APK ici : https://lafibre.info/sfr-les-news/installation-de-connect-tv-dsfr-sur-box-android-tv-60183/msg1146711/#msg1146711

# Guide : retirer la vérification de matériel supporté sur l'APK Connect TV (SFR)

> **Contexte.** L'application Android TV « Connect TV » de SFR vérifie au
> démarrage/à la connexion que l'appareil sur lequel elle tourne est une
> des box officielles SFR (Sagemcom, SDMC, etc.). Si ce n'est pas le cas
> (Chromecast avec Google TV, Nvidia Shield, box Android générique...),
> l'app se bloque même si le compte SFR et la ligne sont valides. Ce
> guide explique comment identifier et retirer cette vérification dans
> l'APK, pour un usage personnel sur votre propre matériel.
>
> **Avertissement.** Ceci modifie un exécutable qui ne vous appartient
> pas légalement (il reste la propriété de SFR) : vous le faites sous
> votre propre responsabilité, pour un usage strictement personnel et
> non redistribué. Ce guide ne concerne QUE la vérification de modèle de
> box. Il existe une autre vérification, séparée, qui teste que la ligne
> est bien raccordée au réseau SFR (test multicast FTTH/ADSL) : ce n'est
> **pas** une vérification matérielle et ce guide ne la contourne pas —
> sans un vrai abonnement/ligne SFR, l'app ne fonctionnera de toute façon
> pas complètement.

Ce dossier contient deux scripts qui automatisent la partie répétitive :

- `find_hardware_check.py` : repère automatiquement, dans le code
  décompilé, la méthode qui fait la vérification de modèle — même si les
  noms de classes changent à chaque version de l'APK (obfuscation).
- `build_and_sign.sh` : recompile, aligne et signe l'APK patché.

Aucun des deux scripts ne dépend d'un environnement particulier ; ils
détectent automatiquement les outils installés sur votre machine.

---

## 1. Prérequis

Il vous faut, sous Linux/macOS (sous Windows, utilisez WSL) :

1. **Java (JDK) 11 ou plus récent** — fournit `keytool` (signature).
   - Debian/Ubuntu : `sudo apt install openjdk-17-jdk`
   - Fedora : `sudo dnf install java-17-openjdk`
   - macOS (Homebrew) : `brew install openjdk`
   - Vérifier : `java -version`

2. **apktool** — décompile/recompile l'APK (smali + ressources).
   - Documentation officielle : https://apktool.org/docs/install
   - Sur beaucoup de distributions il est aussi packagé :
     `sudo apt install apktool` / `sudo dnf install apktool` / `brew install apktool`.
   - Vérifier : `apktool --version` (ce guide a été testé avec apktool 3.x)

3. **Android SDK Build-Tools** — fournit `zipalign` et `apksigner`.
   - Le plus simple : installer *Android Studio*, puis dans le
     SDK Manager cocher « Android SDK Build-Tools ».
   - Ou en ligne de commande seule, via les *command line tools* :
     https://developer.android.com/studio#command-tools puis
     `sdkmanager "build-tools;34.0.0"`.
   - Définissez ensuite la variable d'environnement `ANDROID_HOME` (ou
     `ANDROID_SDK_ROOT`) vers le dossier du SDK — les scripts de ce
     guide s'en servent pour retrouver `zipalign`/`apksigner`
     automatiquement. Exemple (à mettre dans votre `~/.bashrc`) :
     ```bash
     export ANDROID_HOME="$HOME/Android/Sdk"
     ```

4. **ADB** (Android Debug Bridge) — pour installer l'APK patché sur
   votre appareil.
   - `sudo apt install android-tools-adb` / `sudo dnf install android-tools`
     / `brew install android-platform-tools`
   - Ou fourni avec le SDK Android (`$ANDROID_HOME/platform-tools/adb`).

5. **Python 3** (déjà présent sur la plupart des systèmes Linux/macOS) —
   pour lancer `find_hardware_check.py`. Aucune bibliothèque externe
   requise.

6. **L'APK de Connect TV** que vous voulez patcher (récupéré depuis
   votre propre appareil ou une source de confiance — ce guide ne
   fournit pas l'APK).

---

## 2. Activer le débogage USB sur votre appareil Android TV

1. Paramètres → À propos → cliquer 7 fois sur « Build » / « Numéro de
   build » pour activer le mode développeur.
2. Paramètres → Options pour les développeurs → activer « Débogage USB ».
3. Branchez l'appareil en USB à votre ordinateur (le débogage sans-fil
   ADB nécessite que l'appareil soit déjà connecté en Wi-Fi, ce qui n'est
   pas toujours pratique au premier essai — préférez l'USB).
4. `adb devices` — la première fois, une popup apparaît sur la TV pour
   autoriser l'ordinateur : acceptez avec la télécommande.

---

## 3. Décompiler l'APK

```bash
apktool d --no-res -f -o connect_tv_decompiled Connect_TV_original.apk
```

**Important : l'option `--no-res` est nécessaire.** Sans elle, apktool
décode aussi `resources.arsc` (les ressources Android), et sur les
versions récentes de cette app (compileSdk 35 / Android 15), le
ré-encodage de ce fichier lors du rebuild (étape 6) corrompt l'APK
("le paquet semble ne pas être valide" à l'installation) — même sans
aucune modification du code. `--no-res` laisse `resources.arsc` intact
(copié tel quel), ce qui évite complètement ce problème puisqu'on n'a de
toute façon rien à modifier dans les ressources pour ce patch.

---

## 4. Repérer automatiquement la vérification à patcher

```bash
python3 find_hardware_check.py connect_tv_decompiled
```

Le script affiche quelque chose comme :

```
=== Hiérarchie candidate : LNg/g; ===
  Sous-classes (6): LNg/a;, LNg/b;, LNg/c;, LNg/d;, LNg/e;, LNg/f;
  -> smali_classes2/Mg/a.smali  méthode c()Z  (lignes 155-263, 5 comparaisons)
  -> smali_classes2/Mg/a.smali  méthode e()Z  (lignes 292-352, 2 comparaisons)
```

(Les noms de classes/méthodes réels varient à chaque version de l'APK à
cause de l'obfuscation — c'est normal et attendu, le script est fait pour
s'adapter à ça.)

### Comment lire ce résultat

- La **hiérarchie candidate** est une classe abstraite (ici `LNg/g;`)
  avec plusieurs sous-classes qui représentent chacune un modèle de box
  connu (Sagemcom, SDMC...) plus un cas générique "Other" pour le
  matériel non reconnu.
- Les **méthodes candidates** sont celles qui comparent le modèle
  détecté à cette liste et retournent vrai/faux. La méthode avec le plus
  de comparaisons (`hits`) est généralement la bonne — dans l'exemple
  ci-dessus, `c()Z` avec 5 comparaisons contre `e()Z` avec seulement 2.

### Vérification manuelle (obligatoire avant de patcher)

1. Ouvrez le fichier indiqué (ex : `smali_classes2/Mg/a.smali`) et
   regardez le corps de la méthode candidate. Vous devez voir une suite
   de comparaisons (`sget-object` + `invoke-static ... Z`) qui, si l'une
   d'elles matche, saute vers un `return` avec `1` (vrai), et sinon
   retourne `0` (faux). C'est la signature typique d'une fonction
   `isSupportedDevice()`.
2. Cherchez qui appelle cette méthode, pour confirmer sa pertinence :
   ```bash
   grep -rn "Mg/a;->c()Z" connect_tv_decompiled --include=*.smali
   ```
   (remplacez `Mg/a;->c()Z` par la classe/méthode réelle trouvée par le
   script). Si un des appelants a un nom qui évoque l'authentification
   ou le compte (ex : `ConnectAccountWorker`, `AuthWorker`, `LoginX`...),
   c'est un bon signe que vous avez la bonne méthode.
3. Si plusieurs méthodes candidates sont listées, privilégiez toujours
   celle avec le plus de comparaisons ET la plus utilisée par des
   classes liées à l'authentification.

### Si le script ne trouve rien

L'obfuscation peut avoir changé de forme d'une version à l'autre. Dans ce
cas, cherchez manuellement :

```bash
grep -rln "Landroid/os/Build;->MODEL\|Landroid/os/Build;->PRODUCT" connect_tv_decompiled --include=*.smali
```

puis remontez à la main vers la classe abstraite dont héritent ces
fichiers (`.super` en haut du fichier `.smali`), et vers les méthodes qui
utilisent cette hiérarchie.

### Piège à éviter

Ne patchez **pas** la classe qui *résout* le modèle de l'appareil (celle
qui lit `Build.MODEL`/`Build.PRODUCT` et choisit quelle sous-classe
instancier). Elle gère déjà correctement le cas "matériel inconnu" en
retournant l'objet générique "Other" — ce n'est pas une erreur/exception.
Si vous forcez cette résolution à renvoyer un modèle de box connu (par
exemple en essayant de "se faire passer pour" une SDMC), l'app croira
tourner sur une vraie box SFR et essaiera d'utiliser une logique réseau
spécifique à cette box, ce qui casse la détection de ligne/connexion. Le
bon patch est bien la méthode "isSupportedDevice" (`c()Z` dans notre
exemple), pas le résolveur de modèle.

---

## 5. Patcher la méthode

Une fois la bonne méthode confirmée (par exemple
`smali_classes2/Mg/a.smali`, méthode `c()Z`), ouvrez le fichier et
remplacez **tout le corps de la méthode** (de `.method ... c()Z` à
`.end method` inclus) par une version qui retourne toujours vrai :

```smali
.method public final c()Z
    .locals 1

    const/4 v0, 0x1

    return v0
.end method
```

Adaptez le nom de la méthode (`c`), les modificateurs (`public final`)
et les éventuels paramètres à ce que vous avez trouvé dans votre version
— gardez la signature `.method` identique à l'originale, seul le corps
change.

---

## 6. Recompiler, aligner et signer

```bash
./build_and_sign.sh connect_tv_decompiled Connect_TV_patched
```

Ce script :
1. Recompile le dossier décompilé en APK (`apktool b`).
2. Aligne l'APK avec `zipalign` — **avant** la signature (sinon vous
   obtiendrez `INSTALL_FAILED_INTERNAL_ERROR: Failed to extract native
   libraries` à l'installation).
3. Signe l'APK avec `apksigner`, en générant automatiquement un
   keystore auto-signé (`patch_key.jks`) si vous n'en avez pas encore un
   dans le dossier courant.

À la fin, vous obtenez `Connect_TV_patched_aligned.apk`.

Si vous préférez faire les étapes à la main :

```bash
apktool b connect_tv_decompiled -o Connect_TV_patched.apk
zipalign -f -p 4 Connect_TV_patched.apk Connect_TV_patched_aligned.apk
apksigner sign --ks patch_key.jks Connect_TV_patched_aligned.apk
```

---

## 7. Installer sur l'appareil

```bash
adb install -r Connect_TV_patched_aligned.apk
```

- `-r` réinstalle par-dessus une version existante en gardant les
  données (si l'app originale est déjà installée avec la même
  signature — sinon désinstallez-la d'abord :
  `adb uninstall com.sfr.androidtv.gen8` puis installez la version
  patchée).
- Si `adb` renvoie `unauthorized`, vérifiez la popup d'autorisation sur
  l'écran de la TV.

---

## 8. Dépannage

| Symptôme | Cause probable | Solution |
|---|---|---|
| "Le paquet semble ne pas être valide" à l'installation | Ressources corrompues au rebuild | Redécompilez avec `apktool d --no-res` |
| `INSTALL_FAILED_INTERNAL_ERROR` / `Failed to extract native libraries` | Mauvais ordre alignement/signature | `zipalign` doit toujours passer **avant** `apksigner sign` |
| `INSTALL_FAILED_UPDATE_INCOMPATIBLE` | Signature différente de l'app déjà installée | `adb uninstall <package>` puis réinstallez |
| L'app s'ouvre mais bloque toujours "matériel non supporté" | Mauvaise méthode patchée, ou méthode annexe (`e()Z`, etc.) plutôt que la principale | Revérifiez les appelants (étape 4.3), essayez le candidat suivant dans la liste |
| L'app se connecte mais échoue à détecter la ligne/le login après patch | Vous avez patché le *résolveur* de modèle au lieu de la méthode "isSupported" | Revenez à l'APK décompilé original pour ce fichier et ne patchez que la méthode booléenne (étape 4, piège à éviter) |
| `adb devices` montre l'appareil en `unauthorized` | Popup d'autorisation pas acceptée | Débranchez/rebranchez l'USB, acceptez la popup sur la TV |
| `zipalign`/`apksigner` introuvables par `build_and_sign.sh` | SDK Android non détecté | Définissez `ANDROID_HOME` vers votre SDK, ou passez `ZIPALIGN_BIN=/chemin/... APKSIGNER_BIN=/chemin/...` en variables d'environnement avant d'appeler le script |

---

## 9. Résumé rapide (pour ceux qui ont déjà fait ça une fois)

```bash
apktool d --no-res -f -o decomp Connect_TV.apk
python3 find_hardware_check.py decomp        # note la classe/méthode candidate
# éditer decomp/.../Xx/y.smali -> forcer la méthode isSupportedDevice à `return 1`
./build_and_sign.sh decomp Connect_TV_patched
adb install -r Connect_TV_patched_aligned.apk
```


build_and_sign.sh
#!/usr/bin/env bash
#
# build_and_sign.sh — Recompile, aligne et signe un dossier décompilé
# apktool (générique, ne dépend d'aucun chemin personnel).
#
# Usage:
#   ./build_and_sign.sh <dossier_decompile> [nom_sortie]
#
# Exemple:
#   ./build_and_sign.sh connect_tv_patched Connect_TV_patched
#
# Le script essaie de localiser automatiquement :
#   - apktool          (PATH)
#   - zipalign/apksigner (PATH, ou $ANDROID_HOME/$ANDROID_SDK_ROOT/build-tools/<dernière version>)
#   - un keystore de signature (créé automatiquement si absent)
#
# Variables d'environnement optionnelles pour surcharger la détection :
#   APKTOOL_BIN, ZIPALIGN_BIN, APKSIGNER_BIN, KEYSTORE, KEY_ALIAS, KEYSTORE_PASS
#
# Etapes : apktool build -> zipalign (AVANT signature, sinon erreur
# "Failed to extract native libraries" / INSTALL_FAILED_INTERNAL_ERROR à
# l'installation) -> apksigner sign.

set -euo pipefail

DECOMPILE_DIR="${1:?Usage: $0 <dossier_decompile> [nom_sortie]}"
OUT_NAME="${2:-Connect_TV_patched}"

# --- 1. Localiser apktool -----------------------------------------------
find_bin() {
    local name="$1"
    local override_var="$2"
    if [[ -n "${!override_var:-}" ]]; then
        echo "${!override_var}"
        return 0
    fi
    if command -v "$name" >/dev/null 2>&1; then
        command -v "$name"
        return 0
    fi
    return 1
}

# Cherche zipalign/apksigner dans les build-tools du SDK Android.
find_build_tool() {
    local name="$1"
    local override_var="$2"
    if [[ -n "${!override_var:-}" ]]; then
        echo "${!override_var}"
        return 0
    fi
    if command -v "$name" >/dev/null 2>&1; then
        command -v "$name"
        return 0
    fi
    local sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
    if [[ -n "$sdk_root" && -d "$sdk_root/build-tools" ]]; then
        # Prend la version la plus récente disponible.
        local candidate
        candidate=$(find "$sdk_root/build-tools" -maxdepth 2 -name "$name" -print 2>/dev/null | sort -V | tail -n1)
        if [[ -n "$candidate" ]]; then
            echo "$candidate"
            return 0
        fi
    fi
    return 1
}

APKTOOL_BIN="$(find_bin apktool APKTOOL_BIN || true)"
if [[ -z "$APKTOOL_BIN" ]]; then
    echo "ERREUR: apktool introuvable dans le PATH." >&2
    echo "Installe-le (voir GUIDE.md, section Prérequis) ou définis APKTOOL_BIN=/chemin/vers/apktool." >&2
    exit 1
fi

ZIPALIGN_BIN="$(find_build_tool zipalign ZIPALIGN_BIN || true)"
if [[ -z "$ZIPALIGN_BIN" ]]; then
    echo "ERREUR: zipalign introuvable." >&2
    echo "Installe les 'Android SDK Build-Tools' (via Android Studio / sdkmanager)," >&2
    echo "assure-toi que \$ANDROID_HOME (ou \$ANDROID_SDK_ROOT) pointe vers le SDK," >&2
    echo "ou définis ZIPALIGN_BIN=/chemin/vers/zipalign." >&2
    exit 1
fi

APKSIGNER_BIN="$(find_build_tool apksigner APKSIGNER_BIN || true)"
if [[ -z "$APKSIGNER_BIN" ]]; then
    echo "ERREUR: apksigner introuvable (même remarque que pour zipalign)." >&2
    echo "Définis APKSIGNER_BIN=/chemin/vers/apksigner si besoin." >&2
    exit 1
fi

echo "apktool   : $APKTOOL_BIN"
echo "zipalign  : $ZIPALIGN_BIN"
echo "apksigner : $APKSIGNER_BIN"

# --- 2. Keystore ---------------------------------------------------------
# Un keystore de signature "auto-signé" suffit : Android accepte n'importe
# quelle signature valide à l'installation tant qu'elle est cohérente
# (l'app n'est pas republiée sur le Play Store, juste réinstallée en local).
KEYSTORE="${KEYSTORE:-$PWD/patch_key.jks}"
KEY_ALIAS="${KEY_ALIAS:-patch_key}"
KEYSTORE_PASS="${KEYSTORE_PASS:-changeit}"

if [[ ! -f "$KEYSTORE" ]]; then
    echo "[0/3] Aucun keystore trouvé, création de '$KEYSTORE'..."
    if ! command -v keytool >/dev/null 2>&1; then
        echo "ERREUR: keytool introuvable (fourni avec un JDK). Installe un JDK (voir GUIDE.md)." >&2
        exit 1
    fi
    keytool -genkeypair -v \
        -keystore "$KEYSTORE" \
        -alias "$KEY_ALIAS" \
        -keyalg RSA -keysize 2048 -validity 10000 \
        -storepass "$KEYSTORE_PASS" -keypass "$KEYSTORE_PASS" \
        -dname "CN=patch, OU=patch, O=patch, L=patch, ST=patch, C=FR"
fi

# --- 3. Build --------------------------------------------------------------
echo "[1/3] apktool build..."
"$APKTOOL_BIN" b "$DECOMPILE_DIR" -o "${OUT_NAME}.apk"

echo "[2/3] zipalign..."
"$ZIPALIGN_BIN" -f -p 4 "${OUT_NAME}.apk" "${OUT_NAME}_aligned.apk"

echo "[3/3] apksigner sign..."
"$APKSIGNER_BIN" sign \
    --ks "$KEYSTORE" \
    --ks-key-alias "$KEY_ALIAS" \
    --ks-pass "pass:$KEYSTORE_PASS" \
    --key-pass "pass:$KEYSTORE_PASS" \
    "${OUT_NAME}_aligned.apk"

echo
echo "APK prêt : ${OUT_NAME}_aligned.apk"
echo "Installer avec : adb install -r \"${OUT_NAME}_aligned.apk\""


SuperBaobab

  • Abonné SFR fibre FttH
  • *
  • Messages: 12
  • Lyon (69)
Installation de Connect TV d'SFR sur box Android TV
« Réponse #215 le: Aujourd'hui à 18:05:47 »
find_hardware_check.py
#!/usr/bin/env python3
"""
find_hardware_check.py
=======================

Détecte automatiquement, dans un decompile apktool de l'app "Connect TV"
(SFR / Group SFR - Android TV), la vérification "modèle de box supporté"
qui bloque l'application quand elle tourne sur un appareil non officiel
(Chromecast avec Google TV, Nvidia Shield, box Android générique, etc.).

Ce script est générique : il ne contient AUCUN chemin ni nom de fichier
codé en dur pour une version particulière de l'APK. Le nommage des
classes/méthodes change à chaque build (obfuscation R8/minification
différente), mais la STRUCTURE du code reste la même d'une version à
l'autre :

  - Une classe abstraite "sealed-class style" (héritage Kotlin) avec
    plusieurs sous-classes qui sont des singletons `object` Kotlin
    (compilées en `.field public static final <x>:L<Super>;` rempli une
    seule fois dans un <clinit>).
  - Au moins une de ces sous-classes lit `Landroid/os/Build;->MODEL` ou
    `Landroid/os/Build;->PRODUCT` (c'est la liste des box connues:
    Sagemcom, SDMC, etc., plus un cas "Other" pour le matériel inconnu).
  - Une méthode qui retourne un booléen (`)Z`) quelque part dans le code
    et qui compare le modèle détecté à plusieurs de ces singletons
    (plusieurs `sget-object` suivis d'un appel d'égalité `invoke-static`
    à 2 arguments retournant Z, motif classique de compilation d'un
    `when (x) { A -> ...; B -> ... }` ou d'une suite de `x == A || x == B`
    en Kotlin).

Ce script se contente de repérer les CANDIDATS. Il ne modifie aucun
fichier. Une vérification manuelle du résultat reste nécessaire avant de
patcher quoi que ce soit (voir le GUIDE.md fourni à côté de ce script).

Usage :
    python3 find_hardware_check.py /chemin/vers/decompile_apktool

Aucune dépendance externe : Python 3 standard uniquement.
"""

import re
import sys
from pathlib import Path
from collections import defaultdict

SGET_SINGLETON_RE = re.compile(
    r'sget-object\s+v\d+,\s+(L[\w/$]+;)->(\w+):(L[\w/$]+;)'
)
METHOD_RE = re.compile(
    r'\s*\.method\s.*?\s(\S+)\(([^)]*)\)(\S+)'
)

# Super-classes trop génériques pour être une "liste de box matérielles" :
# on les ignore pour éviter les hiérarchies bruyantes (Enum, Exception,
# classes internes de coroutines/serialization qui ont parfois des
# centaines de sous-types).
IGNORED_SUPERS = {
    "Ljava/lang/Object;",
    "Ljava/lang/Enum;",
    "Ljava/lang/Exception;",
    "Ljava/lang/RuntimeException;",
    "Ljava/lang/Throwable;",
}

# Une vraie liste de modèles de box reste courte (quelques box connues +
# un cas générique "Other"). Bornes larges pour tolérer les variations
# entre versions.
MIN_SUBCLASSES = 3
MAX_SUBCLASSES = 20


def iter_smali_files(root: Path):
    yield from root.rglob("*.smali")


def find_singleton_hierarchies(root: Path):
    """
    Repère les classes ABSTRAITES ayant plusieurs sous-classes directes.
    Retourne (hierarchies, file_of_class) où :
      - hierarchies: dict{super_class -> set(sous_classes)}
      - file_of_class: dict{class_name -> Path du fichier .smali}
    """
    super_of = {}
    file_of_class = {}
    abstract_classes = set()

    for f in iter_smali_files(root):
        try:
            text = f.read_text(errors="ignore")
        except Exception:
            continue
        m_class = re.search(r'^\.class\s+(.*?)\s(L[\w/$]+;)', text, re.M)
        m_super = re.search(r'^\.super\s+(L[\w/$]+;)', text, re.M)
        if m_class:
            modifiers, cls_name = m_class.group(1), m_class.group(2)
            file_of_class[cls_name] = f
            if "abstract" in modifiers:
                abstract_classes.add(cls_name)
            if m_super:
                super_of[cls_name] = m_super.group(1)

    hierarchies = defaultdict(set)
    for cls, sup in super_of.items():
        if sup in IGNORED_SUPERS:
            continue
        if sup not in abstract_classes:
            continue
        hierarchies[sup].add(cls)

    return hierarchies, file_of_class


def hierarchy_has_model_check(hierarchy_classes, file_of_class):
    """Vrai si une des sous-classes référence Build.MODEL ou Build.PRODUCT."""
    for cls in hierarchy_classes:
        f = file_of_class.get(cls)
        if not f:
            continue
        try:
            text = f.read_text(errors="ignore")
        except Exception:
            continue
        if "Landroid/os/Build;->MODEL" in text or "Landroid/os/Build;->PRODUCT" in text:
            return True
    return False


def find_boolean_gate_methods(root: Path, known_subclasses):
    """
    Cherche les méthodes ()Z qui font au moins 2 comparaisons contre des
    singletons appartenant à `known_subclasses`.
    """
    candidates = []
    for f in iter_smali_files(root):
        try:
            text = f.read_text(errors="ignore")
        except Exception:
            continue
        lines = text.splitlines()
        method_start = None
        method_name = None
        method_sig = None
        hits = 0
        for i, line in enumerate(lines):
            m = METHOD_RE.match(line)
            if m:
                method_start = i
                method_name = m.group(1)
                method_sig = m.group(3)
                hits = 0
                continue
            if line.strip() == ".end method":
                if method_start is not None and method_sig == "Z" and hits >= 2:
                    candidates.append({
                        "file": f,
                        "method": method_name,
                        "start_line": method_start + 1,
                        "end_line": i + 1,
                        "hits": hits,
                    })
                method_start = None
                continue
            if method_start is not None:
                m2 = SGET_SINGLETON_RE.search(line)
                if m2 and m2.group(1) in known_subclasses:
                    hits += 1
    return candidates


def main():
    if len(sys.argv) != 2:
        print(f"Usage: {sys.argv[0]} /chemin/vers/decompile_apktool")
        sys.exit(1)

    root = Path(sys.argv[1])
    if not root.is_dir():
        print(f"Dossier introuvable: {root}")
        sys.exit(1)

    print("[1/3] Indexation des hiérarchies de classes (sealed-class style)...")
    hierarchies, file_of_class = find_singleton_hierarchies(root)

    print("[2/3] Recherche des hiérarchies liées à Build.MODEL/PRODUCT...")
    model_hierarchies = {}
    for sup, subs in hierarchies.items():
        if not (MIN_SUBCLASSES <= len(subs) <= MAX_SUBCLASSES):
            continue
        if hierarchy_has_model_check(subs, file_of_class):
            model_hierarchies[sup] = subs

    if not model_hierarchies:
        print("Aucune hiérarchie candidate trouvée. La logique a peut-être "
              "changé de forme dans cette version -> inspection manuelle "
              "nécessaire (chercher 'Landroid/os/Build;->MODEL' à la main "
              "avec grep dans tout le dossier décompilé).")
        sys.exit(2)

    print("[3/3] Recherche des méthodes de vérification (gate) associées...")
    found_any_gate = False
    for sup, subs in model_hierarchies.items():
        gates = find_boolean_gate_methods(root, subs)
        if not gates:
            continue

        found_any_gate = True
        print(f"\n=== Hiérarchie candidate : {sup} ===")
        print(f"  Sous-classes ({len(subs)}): {', '.join(sorted(subs))}")
        for g in sorted(gates, key=lambda x: -x["hits"]):
            rel = g["file"].relative_to(root)
            print(f"  -> {rel}  méthode {g['method']}()Z  "
                  f"(lignes {g['start_line']}-{g['end_line']}, "
                  f"{g['hits']} comparaisons)")

    if not found_any_gate:
        print("Aucune méthode de gate trouvée automatiquement. "
              "Inspection manuelle nécessaire (voir GUIDE.md, section "
              "'Si le script ne trouve rien').")
        sys.exit(2)

    print("\nVérifie manuellement le meilleur candidat (celui avec le plus "
          "de comparaisons) avant de patcher : ouvre le fichier, confirme "
          "que la méthode retourne bien 'vrai si le modèle est supporté', "
          "et regarde ses appelants (recherche du nom de la méthode dans "
          "tout le dossier décompilé) pour t'assurer qu'elle est utilisée "
          "à un endroit lié à l'authentification / au démarrage de l'app. "
          "Voir GUIDE.md pour la marche à suivre complète.")


if __name__ == "__main__":
    main()