Compare commits

...

2 Commits

Author SHA1 Message Date
Maxluli 1a42b0e955 Added to the cookie retrieval doc 2023-05-15 09:16:05 +02:00
Maxluli 16d297e038 Added to doc 2023-05-12 14:09:01 +02:00
3 changed files with 271 additions and 5 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+152 -3
View File
@@ -338,8 +338,6 @@ J'ai mis une durée arbitraire de deux jours, mais je ne sais pas vraiment combi
----
[A remplir au fur et à mesure que les features sont développées]
### Récupération des images
Voici la première grande étape du projet.
@@ -458,6 +456,10 @@ fullScreenButton.Click();
Ca peut paraître plutôt simple dit comme ca et quand tout fonctionne ca l'est mais la difficulté vient du fait que à peu près nimporte laquelle de ces étapes peut rater et qu'il faut donc faire un bon système de gestion d'erreurs qui puisse aider l'utilisateur en cas de problème.
Parfois il est aussi difficile de trouver un élément selon son ID,sa classe, ou sa value.
Par exemple l'option qui permet de passer en 1080P peut avoir comme value `1080_9011456` ou `1080_9011200` si on refresh la page. Cela demande de passer par des expression régulières ce qui n'est pas compliqué en soi mais c'est toutes ces petites choses qui rendent le processus long à mettre en place.
Il faut dire aussi que les sites ne sont pas forcément très content de voir des bots passer car cela peut être un risque de DDOS et de Scraping (Comme moi) et donc ils mettent en place des systèmes pour nous empêcher de faire ce que l'on veut
On peut utiliser différntes techniques pour passer outre ces restrictions comme :
@@ -481,7 +483,154 @@ J'ai donc été obligé de faire appel à la connexion par Cookies pour pouvoir
Alors, on va mettre de côté toutes les questions de sécurité et de violation de la vie privée et de protection des données des utilisateurs pour ce chapitre. Car pour faire simple, je siphonne TOUS les cookies de la persone qui utilise mon app.
[FINIR CETTE EXPLICATIOn]
Alors évidemment ca n'est pas pour faire des bétises avec et c'est pour une "bonne" raison, mais bon quand même ca peut faire bizarre comme ca.
Je pense que vous savez déja ce qu'est un Cookie, mais je vais quand même faire un petit point la dessus car c'est important pour la suite.
Quand on va sur un site internet et que l'on se connecte avec nos identifiants nous sommes connectés sur la session.
Cependant, si on quitte le site ou que l'on ferme le navigateur, le site ne peut pas garder en mémoire que c'est bien vous quand le lendemain vous retournez dessus. Pour palier à cette limitation on a inventé cette chose magnifique (hem...) que sont les cookies !
Les cookies sont des petits fichiers qui sont stockés dans votre navigateur et qui peuvent servir à beaucoup de choses comme traquer votre activité sur internet et espionner un peu ou aussi par exemple, servir de jeton de connexion.
L'idée est que quand vous vous connectez sur le site avec vos identifiants, le site envoie un petit fichier dans votre navigateur qui va servir de jeton. Et donc quand vous reviendrez, le site pourra voir que vous avez le jeton et vous connectera automatiquement.
Ca peut paraître génial, et c'est effectivement bien pratique, cependant ce n'est pas sans risques. En effet, imaginons qu'un acteur malveillant parvienne à s'emparer de ces petits fichiers, il pourrait ainsi facilement se faire passer pour vous. Alors un cookie expire à un moment donné pour temperer les risques, mais ils sont toujours présents.
Dans notre cas on peut vite comprendre pourquoi cela peut être intéressant de récupèrer ces cookies. En effet, si on peut mettre la main sur le jeton de connexion de l'utilisateur de notre application. On pourra se connecter automatiquement à la F1TV et aller prendre des photos directement sans que l'utilisateur aie à faire quoi que ce soit.
Sauf que les cookies ne sont pas stockés en clair comme ca. Evidemment Google Chrome a mis en place quelques techniques pour éviter que nimporte qui puisse s'amuser à aller taper dans les cookies de la machine.
Tous les cookies sont stockés dans une base de données sqlite avec les noms en clair et les valeurs sont encryptées en utilisant la methode AES 256 qui est une methode de cryptage très utilisée et efficace.
Tellement efficace qu'il serait complêtement inutile de tenter de les decrypter en utilisant de la force brute pour trouver la valeur ou même une attaque de dictionnaire ou quoi que ce soit.
Si ces valeurs peuvent être encodées et décodées en local sur la machine sans connexion internet, cela veut dire que la clé est stockée sur la machine. Et si je peux mettre la mais sur cette clé alors je pourrai lire tous les cookies de la machine.
Cette clé est stocké dans les fichiers de Google Chrome sous `Google\Chrome\User Data\Local State`. Et dans ce fichier on peut trouver une liste de données en clé valeurs et on peut trouver la clé sous `os_crypt` `encrypted_key`. On pourrait croire que l'on a déja touché le jackpot mais il reste encore une étape. Cette clé est cryptée en utilisant le système d'encryption de Windows. Cette encryption est utilisée pour empêcher des utilisateurs non connectés d'accèder à certaines données. Mais comme nous sommes connectés nous pouvons facilement utiliser les librairies de decryption pour trouver la valeur de cette clé.
Et à partir de la il suffit d'utiliser cette clé pour décrypter tous les cookies de la machine pour aller chercher ceux qui nous intéressent.
Voici un exemple du code python qui permet d'aller chercher la clé d'encryption dans les fichiers de Google Chrome :
```python
def get_master_key():
with open(
os.getenv("localappdata") + "\\Google\\Chrome\\User Data\\Local State", "r"
) as f:
local_state = f.read()
local_state = json.loads(local_state)
master_key = base64.b64decode(local_state["os_crypt"]["encrypted_key"])
master_key = master_key[5:] # removing DPAPI
master_key = win32crypt.CryptUnprotectData(master_key, None, None, None, 0)[1]
print("MASTER KEY :")
print(master_key)
print(len(master_key))
return master_key
```
Python ?? Et oui j'ai choisit d'utiliser un srypt python pour aller chercher les cookies.
Ce choix a été fait pour trois raisons :
1. Le python est un language que je n'aime pas particulièrement mais qui possède un éventail de librairies absolument fantastique. Et pour ce genre de choses qui demandent une constante mise à jour des librairies et qui sont un peu niches le python est une option juste géniale.
2. Comme c'est une des parties qui est le plus suceptible de changer vu que Chrome change relativement souvent le système de stockage des cookies. Dans une optique de facilité de maintenance, avoir un seul fichier qui concerne cette partie du projet et qui est dans un language que plus de gens maitrisent que C# est pratique.
3. Je n'ai pas réussi à trouver de librairies C# qui me donne des résultats identiques à celles que j'utilise dans ce script python.
Pour faire la liaison entre le C# et le python, j'appelle le script depuis mon C# et ensuite le python s'occupe de mettre tous les cookies dans un CSV qui est ensuite lu depuis le C#.
Voici la partie python qui écrit dans le csv :
```Python
if not cookies_path.exists():
raise ValueError("Cookies file not found")
with sqlite3.connect(cookies_path) as connection:
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
cursor.execute("SELECT * FROM cookies")
with open('cookies.csv', 'a', newline='') as csvfile:
fieldnames = ['host_key', 'name', 'value', 'path', 'expires_utc', 'is_secure', 'is_httponly']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
if csvfile.tell() == 0:
writer.writeheader()
for row in cursor.fetchall():
decrypted_value = decrypt_password(row["encrypted_value"], master_key)
writer.writerow({
'host_key': row["host_key"],
'name': row["name"],
'value': decrypted_value,
'path': row["path"],
'expires_utc': row["expires_utc"],
'is_secure': row["is_secure"],
'is_httponly': row["is_httponly"]
})
print("Finished CSV")
```
Et la partie C# qui appelle le script et qui lit le CSV :
```Csharp
private void StartCookieRecovering()
{
string scriptPath = PYTHON_COOKIE_RETRIEVAL_FILENAME;
Process process = new Process();
process.StartInfo.FileName = "python.exe";
process.StartInfo.Arguments = scriptPath;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
}
```
```Csharp
public string GetCookie(string host, string name)
{
StartCookieRecovering();
string value = "";
List<Cookie> cookies = new List<Cookie>();
using (var reader = new StreamReader(COOKIES_CSV_FILENAME))
{
// Read the header row and validate column order
string header = reader.ReadLine();
string[] expectedColumns = { "host_key", "name", "value", "path", "expires_utc", "is_secure", "is_httponly" };
string[] actualColumns = header.Split(',');
for (int i = 0; i < expectedColumns.Length; i++)
{
if (expectedColumns[i] != actualColumns[i])
{
throw new InvalidOperationException($"Expected column '{expectedColumns[i]}' at index {i} but found '{actualColumns[i]}'");
}
}
// Read each data row and parse values into a Cookie object
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
string[] fields = line.Split(',');
string hostname = fields[0];
string cookieName = fields[1];
if (hostname == host && cookieName == name)
{
value = fields[2];
}
}
}
return value;
}
```
Maintenant que l'on sait comment simuler et manipuler un navigateur internet, que l'on sait comment se connecter sur le compte F1TV de l'utilisateur sans qu'il n'aie rien à faire. On a tous les ingrédients pour automatiquement récupèrer des images de la F1TV du Grand Prix que l'on souhaite.
#### Calibration
+119 -2
View File
@@ -2607,9 +2607,9 @@ Et normalement la lecture devrait être encore plus simple.
En fait c'était pas beaucoup plus simple mais au moins maintenant ca marche. Je vais pas mettre le code de lecture ici car c'est un peu trop long donc il va falloir me croire sur parole. (Ou aller sur Git)
Bon bah on est au même endroit qu'hier...
Bon bah on est au même endroit qu'hier...
Bon pour demain le plan de bataille ca va être :
Bon pour demain le plan de bataille ca va être :
Changer complêtement la methode "GetTimeFromPng" pour qu'elle prenne en compte toutes les possibilités de bugs et d'oubli de '.' ou de ':' mais pas selon le nombre de blocs mais selon le type de temps que l'on cherche
@@ -2618,3 +2618,120 @@ Pour le moment je regarde le nombre de blocs et si il y en a deux alors c'est qu
Il faut que je bosse juste un peu vite fait la dessus et que j'arrête de putain de crasher dès que un truc est pas au bon format. Ensuite quand ca aura arrêté de crasher je vais reprendre l'OCR et voir pourquoi les resultats sont nuls a chier comme ca.
Et le but c'est que demain soir j'ai une reconnaissance de caractères plus proche de ce que j'avais dans d'autres projets... J'y croit 0 mais bon l'espoir fait vivre comme on dit.
## vendredi 12 Mai 2023
Bon aujourd'hui il faut que ca marche.
On va y aller par étape. Je vais revoir toutes les methodes d'OCR et essayer de réapliquer les filtres differemment et revenir au point de départ.
Avant de commencer je note plusieurs soucis avec les premiers tests :
- Les positions des pilotes ont l'air pas mal (Pas besoin de tout changer mais peut-être simplement checker que les filtres sont bons)
- L'écart avec le leader est étonnamment pas mal aussi
- Le temps au tour est tout simplement horrible. Aucun n'est juste et de très loin même si les décimales ne sont pas forcément loin
- Le DRS je n'ai pas eu l'occasion de bien le tester mais je dirais que ca devrait être bon (à verifier quand le reste sera bon)
- Les pneus ne sont étonnamment pas SI horribles, même si parfois les lettres sont prises comme des chiffres
- Les noms de pilotes sont très bon (pas étonnant vu le système de distance de Levenstein donc ca mérite quand même un petit check)
- Les secteurs sont en général horribles mais pas toujours. C'est peut-être un soucis de décimale ou des 4 qui se transforment en 1
J'ai remarqué que les 4 sont souvent pris comme des 1. Peut-être que en ayant des images 4K l'interpolation est un peu différente de ce que j'ai l'habitude de voir.
Mais donc le plan aujourd'hui c'est de checker tous ces points et les faire fonctionner (youpi...)
J'ai désactivé toutes les methodes de cette facon :
```Csharp
int sectorCount = 0;
DriverData result = new DriverData();
foreach(Window w in Windows)
{
// A switch would be prettier but I dont think its supported in this C# version
if (w is DriverNameWindow)
//result.Name = (string)await (w as DriverNameWindow).DecodePng(driverList);
result.Name = "Unknown";
if (w is DriverDrsWindow)
//result.DRS = (bool)await (w as DriverDrsWindow).DecodePng();
result.DRS = false;
if (w is DriverGapToLeaderWindow)
//result.GapToLeader = (int)await (w as DriverGapToLeaderWindow).DecodePng();
result.GapToLeader = 0;
if (w is DriverLapTimeWindow)
//result.LapTime = (int)await (w as DriverLapTimeWindow).DecodePng();
result.LapTime = 0;
if (w is DriverPositionWindow)
//result.Position = (int)await (w as DriverPositionWindow).DecodePng();
result.Position = 0;
if (w is DriverSectorWindow)
{
sectorCount++;
if (sectorCount == 1)
//result.Sector1 = (int)await (w as DriverSectorWindow).DecodePng();
result.Sector1 = 0;
if (sectorCount == 2)
//result.Sector2 = (int)await (w as DriverSectorWindow).DecodePng();
result.Sector2 = 0;
if (sectorCount == 3)
//result.Sector3 = (int)await (w as DriverSectorWindow).DecodePng();
result.Sector3 = 0;
}
if (w is DriverTyresWindow)
//result.CurrentTyre = (Tyre)await (w as DriverTyresWindow).DecodePng();
result.CurrentTyre = new Tyre(Tyre.Type.Undefined,0);
}
return result;
```
Le but c'est que ensuite je puisse y aller étape par étape.
Position :
Alors pour cette reconnaissance je dirais que la 4K fait des merveilles qui permettent de retirer du processing. La position après un simple Treshold est assez bien reconnue et la dilataion et/ou Erosion ne sont pas nescessaires finalement.
On va donc pouvoir gagner un certain temps et c'est un bon signe pour la suite.
On peut aussi noter que quand un pilote est hors course toutes ses valeurs sont grisées et sa position est prise comme un -1
Sectors, alors j'ai refait toute la partie qui concerne les secteurs et qui les nettoie. Et je me suis rendu compte qu'ils étaient bien souvent juste, le seul truc c'est que ils s'affichent de manière un peu spéciale. En fait dans la page de la F1TV les secteurs peuvent faire plus de 60 secondes sans passer sur un affichage de minutes. Ce qui fait que c'est un peu bizarre à regarder mais c'est parfaitement juste.
J'ai aussi pu simplifier la reconnaissance grâce à l'image de meilleure qualité et maintenant les temps de secteur sont plutôt corrects.
En fait le plus long et complexe c'est de prévoir les cas particuliers ou un '.' a été mal interprêté... Et en parlant de ca, je vais aller m'occuper des temps au tour qui ajoutent une couche de complexité avec un '.' ET un ':' qui peuvent être oubliés...
Oh et j'y pense, un truc malin pourrait être de comparer les temps au tour et les temps de secteur. Ils devraient concorder normalement je pense.
Ok je viens de finir la gestion des temps au tour... J'ai un code de genre 170 lignes pour juste nettoyer le resultat dans le cas ou des '.' n'ont pas été trouvés ou des ':' ont été n'ont pas été trouvé ou si l'un des deux s'est transformé en chiffre etc.. etc.. etc..
Le soucis c'est que la maintenant je me rend compte que les écarts entre les pilotes vont juste être un enfer à nettoyer... Ils peuvent aussi bien être "0.760" comme "1:34.456" du coup... je sais pas vraiment comment faire pour tout nettoyer.
Je pense que je vais juste en avoir rien à faire et tant pis si de temps en temps c'est pas génial.
Bon du coup j'ai pas pris en compte TOUS les cas possibles mais déja un certain nombre et c'est déja pas mal. Sur les différents Grand Prix d'exemples ca a l'air de plutôt bien tourner !
Mais ca demande tellement de tests et de code que c'est un peu ridicule...
La methode "GetTimeFromPng" fait déja presque 430 lignes à cause de tous les cas possibles et tous les try catch. Ca peut paraître peu élégant mais j'ai essayé de mettre des commentaires un peu partout pour permettre à nimporte qui de comprendre ce qui se passe.
J'ai aussi pu faire les pneus et maintenant (roulement de tambour) Ca marche (presque)
En fait j'ai des soucis parfois quand les pneus sont un peu cachés dans les permiers tours :
![Pneus cachés](./Images/Screens/CroppedTyres.png)
Le soucis c'est que du coup le chiffre est un peu illisible... mais je pense que avec l'historique il devrait y avoir moyen de ne pas prendre en compte les chiffres de pneus pendant cinq tours après le changement de pneu ou de simplement tenter de faire les calculs de pneus.
Pour conclure la journée je pense que je devrais avoir le temps de faire un système qui permet de refresh à volonté.
Par contre je viens de découvrir que quand un temps de secteur est en couleur on arrive pas à le lire.
Ah et la detection prend un peu moins de trois secondes sur mon pc je crois. Mais c'est seulement si les driver zones sont faites en même temps mais en faisant ca de temps en temps ca crash et à chaques fois c'est d'un endroit différent du coup je comprend pas vraiment. Sinon ca prend dix secondes.
## Lundi 15 Mai 2023
Aujourd'hui c'est journée poster et visites. Comme on va avoir des visites de premières années voire de terminales et que le soir c'est visite des parents.
Je pense que j'ai finit de tout regrouper (à part évidemment le traitement et le stockage des données) ce qui veut dire que je suis pas dans une superbe posture. Il va falloir que je sois très efficace dans la partie stockage de données et mise en place du modèle si je veux avoir une chance de rendre un joli travail de diplôme. (et même comme ca je peux voir que le temps commence à manquer)
Dans l'idéal je devrais avoir terminé la partie stockage jeudi... Ce qui veut dire que je n'ai que trois jours pour le faire et que en plus jeudi je dois travailler depuis la maison. Ca va pas être simple.
Mais aujourd'hui je vais m'occuper d'adapter la documentation de l'OCR et faire la documentation de toute la partie récupèration d'images et de la calibration.