LifePlay-Revival/Docs/Migration/etat/REL/MAPDL1/MAPDL1-E5-FICHE.md
2026-10-22 21:10:39 +02:00

126 lines
11 KiB
Markdown

Fiche MAPDL1-E5 — Niveau N3 — Modèle prévu sonnet (agent lp-impl ; escalade lp-impl-max) — Agent lp-impl — Skills sk-protocol, lp-city, lp-lot, ue-cpp-build
# MAPDL1-E5 : téléchargement et installation des packs dans le jeu (C6 de MAPDL1-SPEC-05 v3)
GO de Big Boss. Ce lot fait partie de la réalisation approuvée par l'utilisateur, citée : « Oui : SPEC-05 v3 approuvée (S5-1 à S5-5, D10 = téléchargement HTTP dans le jeu avec navigateur en recours, D11 = un zip par pack). Lance la réalisation avec les modèles réduits ; l'hébergeur et le domaine, je les choisis plus tard. »
**Hors moteur** :
- aucune compilation (E2 bis, sous jeton, plus tard), aucun commandlet, aucun éditeur ;
- **aucun appel réseau réel** ;
- aucun commit, aucun Blueprint ;
- rien sous `Mods\`.
À lire d'abord :
- `MAPDL1-SPEC-05-v3.md` (§ 1 à 4) et `MAPDL1-SPEC-05-v2.md` (§ 2 : fenêtre, licence, accord, deux boutons) ;
- `MAPDL1-E1-IMPL.md` et `MAPDL1-E3-VAL.md` ;
- le code d'E1 : `Source/LifePlay/LPCityMapsSubsystem.h/.cpp` ;
- le modèle de fenêtre : `Source/LifePlay/LPModInfoSubsystem.cpp:375-422` (`ShowWarning`, `AddViewportWidgetContent`).
## 0. Avant toute écriture
- Instantané `.\Tools\Snapshot.ps1 -Lot MAPDL1-E5 -Path <chemin>`, depuis pwsh, un chemin par appel, pour chaque fichier du § 1.
- MD5 avant et après de tous ces fichiers, et MD5 des fichiers de ville de STM1 (`LPCityWater*`, `LPCityNames*`, `LPCityHouses.h`, `LPCityLandmarks.h`, `LPAssetRegistryHelpers.*`) : ils doivent rester inchangés.
- `git status --short` sur les fichiers du § 1.
## 1. Fichiers (et seulement ceux-là)
| Fichier | État | Contenu |
|---|---|---|
| `Source/LifePlay/LPCityMapsDownload.h/.cpp` | A | téléchargement, contrôle et pose (§ 2.2 à 2.5), sans dépendance à l'UI |
| `Source/LifePlay/LPCityMapsWindow.h/.cpp` | A | fenêtre Slate (§ 2.1), sur le modèle de `LPModInfoSubsystem::ShowWarning` |
| `Source/LifePlay/LPCityMapsSubsystem.h/.cpp` | M | le clic d'une ligne téléchargeable ouvre la fenêtre au lieu du navigateur ; nouvel état `RestartRequired` (§ 2.6) |
| `Source/LifePlay/LifePlay.Build.cs` | M | `PrivateDependencyModuleNames` : `"HTTP"`, `"OpenSSL"`, `"libzip"` (commentaire « Lot MAPDL1 (E5) ») |
| `Content/Data/Licenses/OpenStreetMap_Geodata_License.txt` et `Content/Data/Licenses/Vinfamy_License.txt` | A | **copies à l'octet** de `Licenses/…` (MD5 identiques), pour qu'elles soient livrées dans le pak (le dossier `Licenses/` ne l'est pas dans la build) |
| `Content/Data/LPCityMaps.json` | M | `licences.*.fichier` devient `Data/Licenses/<nom>` ; rien d'autre ne change |
Conventions : style des fichiers voisins (commentaires en français sans accents), outil **Edit** pour modifier, **Write** pour les fichiers neufs, LF.
## 2. Contenu
### 2.1 Fenêtre « City map: <ville> » (Slate, en anglais, ordre Z élevé, fermable)
- **Informations** : ville, version, taille en Mo, hôte de `urls[0]` (seulement le nom d'hôte), provenance (`source`), et « Active after the next game launch ».
- **Licence** : les `titre` des `licences` de l'entrée. Le bouton « Read the full licence » affiche dans la fenêtre, avec défilement, le texte de `FPaths::ProjectContentDir()/<fichier>`. Fichier absent : « Licence text not found: <fichier> ».
- **Case** « I have read and accept the licence of this map », **non cochée**. Les deux boutons d'action restent désactivés tant qu'elle n'est pas cochée.
- **Boutons** : « Download only », « Full install », « Cancel ».
- **Progression** : barre, octets reçus / octets totaux, débit, « Cancel » qui interrompt. Le `.part` est gardé pour une reprise.
- **Messages** :
- erreur en clair (« Download failed: … », « SHA-256 mismatch », « Install failed: … ») ;
- réussite « Downloaded: <chemin> » avec un bouton « Open folder » (`FPlatformProcess::ExploreFolder`) ;
- réussite « Installed. Restart the game to play <ville>. ».
- **Recours** quand toutes les URL ont échoué : bouton « Open in browser » (`page`, sinon `urls[0]`, par `FPlatformProcess::LaunchURL`), puis surveillance du dossier Téléchargements (§ 2.5), plus un bouton « Choose file… » (§ 2.5).
- Une seule fenêtre à la fois ; un seul téléchargement à la fois.
### 2.2 Téléchargement HTTP (`FHttpModule`, `IHttpRequest`)
| Point | Règle |
|---|---|
| Fichier partiel | `FPaths::ProjectSavedDir()/Downloads/<pack>/<zip>.part`. Écriture en flux par `SetResponseBodyReceiveStream` (`IHttpRequest.h:271`), sur un `FArchive` ouvert en ajout (`IFileManager::CreateFileWriter`, drapeau d'ajout) |
| Reprise | `.part` présent de taille t > 0 : `SetHeader("Range", "bytes=t-")` (`IHttpRequest.h:297`). Réponse **206** avec un `Content-Range` qui commence à t : on continue. Réponse **200** : le `.part` est **déplacé** dans `Saved/Downloads/_rejet/` (horodaté) et le téléchargement repart de zéro, dans un fichier neuf |
| Progression | `OnRequestProgress64` (`IHttpRequest.h:366`) |
| Délais | `SetActivityTimeout(30)` (`IHttpRequest.h:328`) ; aucun délai total |
| Essais | 3 par URL, avec 2, 4 puis 8 s d'attente (ticker `FTSTicker`, sans bloquer le fil de jeu), puis l'URL suivante. Statut hors 200 et 206, ou taille finale différente d'`octets` : essai suivant |
| Redirections | à **vérifier dans les sources du moteur 5.8** : quel client HTTP sert Win64 (Curl, ou WinHttp ; voir `Engine/Source/Runtime/Online/HTTP/HTTP.Build.cs` et `…/Private/Windows/`) et s'il suit les redirections (Curl : `CURLOPT_FOLLOWLOCATION` posé à 1, `…/Private/Curl/CurlHttp.cpp:183`). Citer fichier:ligne dans l'IMPL. Le journal donne l'URL finale si l'API la fournit (`GetEffectiveURL` ou équivalent, à vérifier) |
| Sûreté | URL `https://` seulement (déjà filtrées par l'index) ; en-tête `User-Agent: LifePlay-CityMaps/1.0` ; aucune donnée du joueur envoyée |
| Journal | `[LPCity] DOWNLOAD <ville> : <url> statut <n> octets <recus>/<total> (reprise a <t>)`, et `… : echec <raison>` |
### 2.3 Contrôle SHA-256 (OpenSSL, par morceaux)
- Module `OpenSSL` du moteur (`Engine/Source/ThirdParty/OpenSSL`, 1.1.1t ; E0 Q4). `#include "openssl/evp.h"` entre `THIRD_PARTY_INCLUDES_START` et `THIRD_PARTY_INCLUDES_END`, puis `EVP_MD_CTX_new`, `EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr)`, `EVP_DigestUpdate` par blocs de 1 Mio lus avec `IFileManager::CreateFileReader`, `EVP_DigestFinal_ex` et `EVP_MD_CTX_free`.
- Le calcul se fait **hors du fil de jeu** (`Async(EAsyncExecution::ThreadPool, …)`), avec un retour sur le fil de jeu (`AsyncTask(ENamedThreads::GameThread, …)`).
- Comparaison sans tenir compte de la casse avec `sha256` de l'index.
- En cas d'écart : le fichier est **déplacé** dans `Saved/Downloads/_rejet/` et le jeu passe à l'URL suivante.
- Journal : `[LPCity] SHA256 <ville> : <valeur> conforme|ECART`.
### 2.4 « Download only » et « Full install »
- **Download only** : quand le téléchargement est complet et conforme, `<zip>.part` est renommé en `Saved/Downloads/<zip>`. La ville reste « téléchargeable ».
- Un `Saved/Downloads/<zip>` déjà présent, de la bonne taille et au bon SHA-256 (recontrôlé), sert directement : aucun nouveau téléchargement.
- **Full install** : la même chose, puis la **pose** :
1. **refus dans l'éditeur** (`GIsEditor`) : « Full install is only available in the packaged game ». La copie de travail ne doit jamais recevoir de pack dans `Mods\` ;
2. ouverture du zip par **libzip** (`zip_open` en lecture seule) et contrôle des membres :
- chaque nom commence par `LifePlay/Mods/<pack>/` ou `LifePlay/Content/Modules/<pack>/`, ou vaut `README.txt` (ignoré) ;
- aucun `..`, aucun `\`, aucun chemin absolu, aucun autre préfixe ;
- le membre `LifePlay/Mods/<pack>/<pack>.uplugin` est présent ;
- sinon : refus, et rien n'est posé ;
3. extraction dans `Saved/Downloads/<pack>/_pose/` (dossier neuf), puis destinations : `FPaths::ProjectDir()` + le chemin du membre sans le préfixe `LifePlay/` ;
4. une installation existante de ce pack (`Mods/<pack>` ou `Content/Modules/<pack>`) est d'abord **déplacée** dans `Saved/Downloads/_ancien/<pack>-<horodatage>/` ;
5. `Content/Modules/<pack>` est posé en premier, `Mods/<pack>` **en dernier**, par `IFileManager::Move` ;
6. **retour en arrière** si une étape échoue : les déplacements déjà faits sont défaits dans l'ordre inverse, puis l'ancienne installation est remise. Rien n'est jamais supprimé (`_pose/` reste, et sert de trace) ;
7. journal `[LPCity] INSTALL <ville> : <n> fichiers poses (Modules puis Mods)`, ou `… : echec <raison>, retour arriere <ok|ECHEC>`.
### 2.5 Recours (c), quand toutes les URL échouent
- « Open in browser » : `page`, sinon `urls[0]`.
- **Surveillance du dossier Téléchargements** de Windows, tant que la fenêtre est ouverte, toutes les 2 s :
- le dossier se trouve par `SHGetKnownFolderPath(FOLDERID_Downloads)` (sous `PLATFORM_WINDOWS`, avec `Windows/AllowWindowsPlatformTypes.h`) ;
- un fichier `.zip` de taille exactement `octets`, au SHA-256 conforme, est **copié** (le fichier du joueur reste) vers `Saved/Downloads/<zip>` ;
- ensuite, comme 2.4 (« Download only » ou « Full install » selon le bouton choisi).
- **« Choose file… »** : sélecteur de fichier Win32 (`GetOpenFileNameW`, `commdlg.h`, sous `PLATFORM_WINDOWS`) ; même contrôle, puis même suite. Si son usage en Shipping pose un problème de liaison, laisser le bouton désactivé avec un commentaire, et le signaler dans l'IMPL.
### 2.6 Changements dans `LPCityMapsSubsystem`
- `ELPCityMapState` reçoit `RestartRequired`, après `Unavailable` (l'ordre des valeurs existantes ne change pas).
- Une ville passe à `RestartRequired` après une pose réussie dans cette session. Dans le menu : texte « Restart required », couleur `(0.6, 0.6, 1.0)`, clic sans effet. Pour le tri, elle compte avec les téléchargeables.
- `ULPCityRowClick::OnClicked` ouvre la fenêtre (§ 2.1) au lieu de `OpenCityPage`, qui reste pour le recours.
- `Deinitialize` : annule le téléchargement en cours, garde le `.part` et ferme la fenêtre.
## 3. Contrôles hors moteur
1. `git diff --stat` et liste des fichiers ajoutés ; MD5 avant et après ; MD5 des fichiers de STM1 inchangés.
2. Fins de ligne : `git ls-files --eol` des fichiers modifiés ; 0 CR et pas de BOM dans les fichiers neufs.
3. Copies de licence : MD5 identiques à `Licenses/…`.
4. `Test-Json` de `LPCityMaps.json`.
5. Lecture critique du code par toi : chemins de pose, retour en arrière, aucun `Delete` (Grep `Delete`), aucun appel réseau dans un chemin d'éditeur au démarrage.
## 4. Livrable
`Docs\Migration\etat\REL\MAPDL1\MAPDL1-E5-IMPL.md` :
- en-tête sk-protocol, avec le modèle réellement utilisé ;
- fichiers, MD5, diff résumé ;
- API vérifiées avec fichier:ligne (HTTP, redirections, OpenSSL, libzip, Win32) ;
- choix faits ;
- points à vérifier à la compilation et en jeu ;
- compte rendu.
**Budget : 70 tours.** En cas de blocage : arrêt et rapport, sans contourner.