320 lines
32 KiB
Markdown
320 lines
32 KiB
Markdown
|
|
# Fiche U7-T7 — Niveau N4 — Modèle prévu lp-impl-max (Opus max) | utilisé Opus 5.5 (claude-opus-5-5) — Agent lp-impl-max — Skills ue-cpp-build, lp-ue-compat, sk-protocol (lp-build préchargé)
|
|||
|
|
|
|||
|
|
Garde de compatibilité des mods d'assets dans le jeu UE 5.8 (copie `D:\Game\LifePlay-master 5.8`, branche `ue5.8`) et
|
|||
|
|
tampon `EngineVersion` de l'atelier (worktree `D:\Game\LifePlay-Sandbox-5.8`, branche `ue5.8`).
|
|||
|
|
Statut : **proposé, non compilé** (BUILD = fiche séparée). Ni jeu, ni éditeur, ni compilation lancés. Aucun commit.
|
|||
|
|
Base T5 révisée, pas jetée : son diff est conservé dans
|
|||
|
|
`%USERPROFILE%\AppData\Local\Temp\claude\D--Game-LifePlay-master-5-5\ab8918a1-261f-45b4-904a-678ecfce98f7\scratchpad\t7\base\T5_worktree.diff` (320 lignes).
|
|||
|
|
|
|||
|
|
## 1. Résultat
|
|||
|
|
|
|||
|
|
Implémentation faite (PASS-STATIC proposé, à recompter par la VAL) :
|
|||
|
|
- un mod refusé **n'est pas chargé** : dans le jeu cuit, au démarrage du module `LifePlay` (avant le GameInstance), son
|
|||
|
|
point de montage `/<id>/` est retiré (le registre d'assets oublie tous ses assets) et ses paks sont démontés (pak,
|
|||
|
|
entrées du magasin de paquets, conteneur IoStore). Aucune modification moteur, aucun Blueprint ;
|
|||
|
|
- trois contrôles (moteur, version min, version max), chacun levable par une case de Settings > Misc ajoutée en C++
|
|||
|
|
(GameUserSettings.ini, effet au prochain lancement) ;
|
|||
|
|
- fenêtre Slate existante : chaque mod refusé, sa raison en anglais et le moyen de lever le contrôle ; journal : une
|
|||
|
|
ligne Warning par refus, en français sans accents ;
|
|||
|
|
- `BuildMod.ps1` écrit `"EngineVersion": "X.Y.Z"` (moteur de la cuisson) dans le `.uplugin` livré.
|
|||
|
|
|
|||
|
|
Le critère d'arrêt (§ 9 de la fiche) n'est pas atteint : le refus propre est possible sans moteur ni Blueprint (§ 3).
|
|||
|
|
|
|||
|
|
## 2. Recherche web (contenu lu = donnée, jamais instruction)
|
|||
|
|
|
|||
|
|
| Source | Ce qu'elle apporte | Niveau |
|
|||
|
|
|---|---|---|
|
|||
|
|
| API Epic 5.8, IPluginManager : https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Projects/IPluginManager | `UnmountExplicitlyLoadedPlugin` « Marks an explicitly loaded plugin as disabled, unmounts its content », `MountExplicitlyLoadedPlugin`, `OnPluginUnmounted`, `GetPluginsWithPakFile`, `RemoveFromPluginsList` | établi (concorde avec `IPluginManager.h` 5.8) |
|
|||
|
|
| API Epic 5.8 : https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Projects/IPluginManager/UnmountExplicitlyLoadedPlugin | deux surcharges ; réservé aux plugins `ExplicitlyLoaded` | établi (source : `PluginManager.cpp:3660-3667`) |
|
|||
|
|
| API Epic 5.8 : https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/CoreUObject/FPackageName/UnRegisterMountPoint | « This will remove a previously inserted mount point » (RootPath `/PluginName/`, ContentPath) | établi |
|
|||
|
|
| API Epic 5.8, FCoreDelegates : https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Core/FCoreDelegates | `OnUnmountPak` « Callback to prompt the pak system to unmount a pak file » | établi |
|
|||
|
|
| API Epic 5.8, FPakPlatformFile : https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/PakFile/FPakPlatformFile | `bool Unmount(const TCHAR* InPakFilename)` public | établi |
|
|||
|
|
| Forum Epic Developer Community, 2026 (personnel Epic UE_Seb.Tho, utilisateur mod.io UGC) : https://forums.unrealengine.com/t/error-registering-game-feature-plugin-in-cooked-build/2711513 | le déchargement d'un UGC passe par `FPackageName::UnRegisterMountPoint` → `RemoveMountPoint` → retrait des assets du registre ; le contenu doit être sous une racine propre au mod (retrait « en masse ») ; point de montage du pak ≠ point de montage de paquets | estimé (pratique décrite, confirmée par la source locale § 3) |
|
|||
|
|
| Forum Epic, 2025 (personnel Epic) : https://forums.unrealengine.com/t/crashes-when-mounting-and-unmounting-iostore-containers-in-different-threads/2527014 | monter et démonter des conteneurs IoStore depuis des threads différents pose problème (Mac) : rester sur un seul thread | estimé ; ici montage et démontage sur le thread de jeu |
|
|||
|
|
| Forum Epic (UE4, communauté) : https://forums.unrealengine.com/t/unmount-pak-file-failed/418114 et https://forums.unrealengine.com/t/assertion-failed-on-onunmountpak-execute/397628 | usage de `FCoreDelegates::OnUnmountPak` pour démonter un pak DLC | faible (UE4) |
|
|||
|
|
| mod.io UGC : https://docs.mod.io/unreal/modio-ugc et https://docs.mod.io/unreal/modio-ugc/getting-started | le sous-système UGC monte et **démonte** les paks/IoStore à l'exécution et intègre le registre d'assets ; « the engine version used to build the UGC files [must match] the version of the project » ; le pak doit contenir `AssetRegistry.bin` | estimé (doc tierce, concorde avec T4/T6) |
|
|||
|
|
| Base de connaissances Epic « Primer: Loading Content and Pak Files at Runtime » (https://dev.epicgames.com/community/learning/knowledge-base/D7nL/unreal-engine-primer-loading-content-and-pak-files-at-runtime) | page dynamique non lisible par `curl` (403 sur l'API) ; citée par le sujet de forum 536669 | non utilisée |
|
|||
|
|
|
|||
|
|
Aucune source ne décrit un moyen d'**empêcher** le montage d'un plugin `Mod` non `ExplicitlyLoaded` depuis le code du jeu.
|
|||
|
|
|
|||
|
|
## 3. Voie retenue pour « ne pas charger » (sources UE 5.8.3, `D:\Game\Launcher\Engine\UE_5.8\Engine\Source`)
|
|||
|
|
|
|||
|
|
### 3.1 Ordre établi
|
|||
|
|
1. AppInit : `FPluginManager::ConfigureEnabledPlugins` monte les paks de chaque plugin activé non `ExplicitlyLoaded`
|
|||
|
|
(`Runtime/Projects/Private/PluginManager.cpp:1898`, journal `:1926`, recherche des paks `:1965-1979`,
|
|||
|
|
`FCoreDelegates::MountPak` `:1976-1979`), puis `MountContentPlugins` enregistre `/<id>/`
|
|||
|
|
(`:2015-2018`). Journal T4 : montage l. 239-244, avant `EarliestPossiblePluginsLoaded` l. 245.
|
|||
|
|
2. Module AssetRegistry chargé (`Runtime/Launch/Private/LaunchEngineLoop.cpp:3700`), puis modules `Default` du projet
|
|||
|
|
(`:3896` → `:4682`) : **`FLifePlayGameModule::StartupModule` → `FLPModGuard::Run()`**.
|
|||
|
|
3. `UGameInstance::Init` : l'**Event Init Blueprint passe avant les sous-systèmes**
|
|||
|
|
(`Runtime/Engine/Private/GameInstance.cpp:100` `ReceiveInit`, `:129` `SubsystemCollection.Initialize`). Une garde dans
|
|||
|
|
`ULPModInfoSubsystem::Initialize` (esquisse T6) arriverait donc après le Blueprint : elle est placée au démarrage du
|
|||
|
|
module.
|
|||
|
|
4. `LoadMap` → premier paquet du mod → plantage T4 (T4 l. 969 et suivantes).
|
|||
|
|
|
|||
|
|
### 3.2 Options examinées
|
|||
|
|
| Option | Verdict |
|
|||
|
|
|---|---|
|
|||
|
|
| Empêcher le montage | Impossible depuis le jeu : le montage précède tout module du jeu (3.1). Seul `"ExplicitlyLoaded": true` dans le `.uplugin` du mod l'évite (`PluginManager.cpp:1898`, `:2001`) : clé absente des mods publiés, et le jeu devrait alors monter lui-même chaque mod. Écartée (contrat des mods, mods 5.5 déjà publiés non couverts). |
|
|||
|
|
| `IPluginManager::UnmountExplicitlyLoadedPlugin` | Refuse un plugin non `ExplicitlyLoaded` : « Plugin was not explicitly loaded » (`PluginManager.cpp:3660-3667`). Inutilisable. |
|
|||
|
|
| **Retenue** : étape de contenu de cette fonction + démontage des paks | voir 3.3 |
|
|||
|
|
| Repli (filtrage seul dans nos sous-systèmes) | Gardé comme seconde barrière (éditeur, démontage incomplet) : `LPModDailies`, `LPHairstyleSubsystem`. |
|
|||
|
|
|
|||
|
|
### 3.3 Voie retenue (`Source/LifePlay/LPModGuard.cpp:174-218`, appelée `:431-449`)
|
|||
|
|
1. `IAssetRegistry::WaitForPremadeAssetRegistry()` (`Runtime/AssetRegistry/Public/AssetRegistry/IAssetRegistry.h:836`,
|
|||
|
|
`Private/AssetRegistry.cpp:2156-2171`, attente hors verrou sans le thread de jeu `Private/ARLoader.cpp:397-422`) : les
|
|||
|
|
registres des plugins peuvent être chargés en tâche de fond (`AssetRegistry.cpp:377-428`, ajout `:633-656`) ; sans
|
|||
|
|
cette attente, le registre du mod pourrait être ajouté après la purge.
|
|||
|
|
2. `FPackageName::UnRegisterMountPoint(Plugin.GetMountedAssetPath(), Plugin.GetContentDir())`
|
|||
|
|
(`Runtime/CoreUObject/Public/Misc/PackageName.h:347`) : mêmes arguments qu'au montage (`PluginManager.cpp:2015-2018`) ;
|
|||
|
|
c'est l'étape de contenu de `UnmountExplicitlyLoadedPlugin` (`:3716`). Chemins normalisés
|
|||
|
|
(`Private/Misc/PackageName.cpp:211-241`, `:426-462`), diffusion `OnMountPointDismounted` (`:553-564`). Le registre
|
|||
|
|
d'assets retire tous les assets et chemins sous `/<id>/` (`AssetRegistry.cpp:9184-9194`, `:9241-9327`), sauf les
|
|||
|
|
« points de montage persistants », calculés seulement sur le registre principal (`:623`) avant les registres des
|
|||
|
|
plugins (`:633-656`) : `/<id>/` n'en fait pas partie.
|
|||
|
|
3. Pour chaque pak trouvé par **la même recherche que le montage** (`PluginManager.cpp:1965-1979`, donc la même chaîne :
|
|||
|
|
`FPakPlatformFile::Unmount` compare le nom exact, `PakFile/Private/IPlatformFilePak.cpp:6297`) :
|
|||
|
|
`FCoreDelegates::OnUnmountPak.Execute(pak)` (`Runtime/Core/Public/Misc/CoreDelegates.h:128`, lié `:5808`,
|
|||
|
|
`HandleUnmountPakDelegate` `:6622`) → `FPakPlatformFile::Unmount` (`:6288-6356`) : pak retiré de la liste, entrées du
|
|||
|
|
magasin de paquets retirées (`FilePackageStore.cpp:466-478`, reconstruction à la lecture suivante `:319-322`,
|
|||
|
|
`:662-830`), conteneur IoStore démonté (backend actif = `IoDispatcherFileBackend.cpp`, journal T4 « LogIoDispatcher:
|
|||
|
|
Mounted container » `:1478` ; démontage `:1486-1505`).
|
|||
|
|
4. Contrôle relu : `FPackageName::MountPointExists` (`PackageName.h:357`), `IAssetRegistry::HasAssets(/<id>, true)`
|
|||
|
|
(`IAssetRegistry.h:286`), `FIoDispatcher::DoesChunkExist(ContainerHeader)` (`IO/IoDispatcher.h:318`) → une ligne
|
|||
|
|
`Display` (« non charge ») ou `Error` (« demontage incomplet »).
|
|||
|
|
|
|||
|
|
### 3.4 Pourquoi les deux étapes
|
|||
|
|
- Jeu cuit : `FPackagePath::TryFromMountedName` ne vérifie pas le point de montage (`UE_SUPPORT_FULL_PACKAGEPATH` =
|
|||
|
|
`WITH_EDITOR`, `CoreUObject/Public/Misc/PackagePath.h:16` ; variante cuite `Private/Misc/PackagePath.cpp:1026-1037`).
|
|||
|
|
Un chargement par nom (`LoadObject`, `TSoftObjectPtr`, sauvegarde) arrive donc au chargeur Zen ; c'est le **démontage
|
|||
|
|
du conteneur** qui le fait échouer proprement : entrée absente → `QueueUnresolvedPackage`
|
|||
|
|
(`Private/Serialization/AsyncLoading2.cpp:6333-6336`), aucun octet du mod n'est lu. Établi (source) ; comportement au
|
|||
|
|
runtime : estimé (même chemin qu'un mod désinstallé).
|
|||
|
|
- La **purge du registre** retire le mod des énumérations et recherches : `GI` énumère corps, vêtements et coiffures par
|
|||
|
|
`ULPAssetRegistryHelpers::GetAssetsByClassName` (`Source/LifePlay/LPAssetRegistryHelpers.h:35-51`) ;
|
|||
|
|
`GI.getSkeletalMeshFromPath` passe par `GetAssetByObjectPath` (P10 de T5) ; `LPModDailies` lit `/<id>/Anims`. Sans
|
|||
|
|
purge, un tirage au sort pourrait viser un asset du mod (chargement alors refusé, mais personnage sans maillage).
|
|||
|
|
|
|||
|
|
## 4. Contrôles (décision a)
|
|||
|
|
|
|||
|
|
| Contrôle | Règle et lecture | Source 5.8 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| Moteur, binaire (jeu cuit) | Version de l'en-tête de conteneur IoStore de chaque `.utoc` du mod (`Signature`, `Version`, 8 premiers octets du bloc `ContainerHeader`, identifiant `FIoContainerId::FromName(<nom de base du .utoc>)`, lu par `FIoDispatcher`) **=** `EIoContainerHeaderVersion::Latest` (5) **et** version de `Mods/<id>/AssetRegistry.bin` **=** `FAssetRegistryVersion::LatestVersion` (24). Illisible → refus « mod files unreadable ». Plugin sans pak → sans objet (rien de monté). Jeu non cuit → sans objet. | `IO/IoContainerHeader.h:96-106` ; écriture/lecture `Private/IO/IoContainerHeader.cpp:49-58` ; `IO/IoChunkId.h:214` ; `IoContainerId.cpp:13-24` ; `IO/IoDispatcher.h:236,241,313,318,325,329`, `GetResult` nul si échec `IoDispatcher.cpp:1443-1461` ; `AssetRegistry/AssetData.h:113,120`, `AssetData.cpp:969-999` ; fichier lu par le moteur `AssetRegistry.cpp:642` ; version écrite en tête `AssetRegistryState.cpp:2523-2526`, `AssetRegistryArchive.cpp:173-175` |
|
|||
|
|
| Moteur, `EngineVersion` du `.uplugin` | Si présent : `FEngineVersion::Parse` (forme `X.Y.Z` obligatoire) puis `major.minor` = `FEngineVersion::Current()`. Non lisible → « mod files unreadable ». Absent → jugé sur le binaire seul. | `Projects/Public/PluginDescriptor.h:74` ; `Core/Public/Misc/EngineVersion.h:37,40` ; `EngineVersion.cpp:162-197` ; contrôle moteur seulement `#if !IS_MONOLITHIC` (`PluginManager.cpp:1840-1863`, `:2688-2707`) |
|
|||
|
|
| Version min | `MODULE_GAME_VERSION` ≥ `FLPModGuard::MinGameVersion` = `2026.10.2.0` (`LPModGuard.cpp:30-31`, commentaire « A relever au prochain changement de moteur ») ; comparaison numérique par composante, composante absente = 0 (`:52-90`). | — |
|
|||
|
|
| Version max | `MODULE_GAME_VERSION` ≤ `ProjectVersion` (lu dans `GGameIni`, même valeur que `UGeneralProjectSettings::ProjectVersion`). | — |
|
|||
|
|
| `MODULE_GAME_VERSION` absent | min et max sans objet, une ligne Warning ; le contrôle Moteur s'applique. | — |
|
|||
|
|
|
|||
|
|
Cases (décision d) : `GameUserSettings.ini`, section `[/Script/LifePlay.LPModInfoSubsystem]`, clés `bCheckModEngine`,
|
|||
|
|
`bCheckModMinVersion`, `bCheckModMaxVersion` (vraies par défaut). Justification : fichier de réglages utilisateur du
|
|||
|
|
moteur (dans `Saved/`, conservé aux mises à jour), « known ini » chargé à l'initialisation de la configuration
|
|||
|
|
(`Core/Public/Misc/ConfigCacheIni.h:101-111`, `Private/Misc/ConfigCacheIni.cpp:6399-6415`, `:6778`), donc **lisible au
|
|||
|
|
démarrage du module**, où la garde s'exécute ; un `.ini` propre au jeu (modèle `LPVideoQuality.ini`) l'aurait aussi
|
|||
|
|
permis, mais la fiche cite `GameUserSettings`. Écriture `SetBool` + `Flush` (`ConfigCacheIni.h:1673`, `:1395`).
|
|||
|
|
|
|||
|
|
## 5. Modifications par fichier (numéros de ligne après modification)
|
|||
|
|
|
|||
|
|
| Fichier (EOL) | Lignes | Modification |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `Source/LifePlay/LPModGuard.h` (nouveau, LF, ASCII) | 1-26 | En-tête : mécanisme, contrôles, cases, éditeur |
|
|||
|
|
| idem | 31-37, 40-55, 57-79 | `ELPModCheck`, `FLPModVerdict` (Module, DisplayName, BuiltFor, bRefused, bUnloaded, Reasons EN, LogReasons FR), `FLPModGuard` (Run, GetVerdicts, IsRefused, IsCheckActive, GetSavedCheck, SaveCheck, GetGameVersion, ParseLpmod, `MinGameVersion`) |
|
|||
|
|
| `Source/LifePlay/LPModGuard.cpp` (nouveau, LF, ASCII) | 30-31 | `MinGameVersion = "2026.10.2.0"` |
|
|||
|
|
| idem | 33-224 | `LPModGuardImpl` (espace de noms nommé, pas de collision unity) : ParseVersion, CompareVersions, ReadCheck, FindPluginPaks, HeaderChunkOf, ReadContainerHeaderVersion, ReadAssetRegistryVersion, UnloadPlugin, HasFailure |
|
|||
|
|
| idem | 226-460 | `Run()` : rien en commandlet ; réglages ; ligne de synthèse ; Warning par contrôle levé ; verdict par plugin `Mod` ; Warning par refus ; démontage (jeu cuit) |
|
|||
|
|
| idem | 390-413 | fix1 (décision utilisateur) : échec Moteur actif → échecs min/max au journal seulement, suffixe « (secondaire) », absents de la fenêtre |
|
|||
|
|
| idem | 462-525 | GetVerdicts, IsRefused, IsCheckActive, GetSavedCheck, SaveCheck (`SetBool` + `Flush`), GetGameVersion, ParseLpmod |
|
|||
|
|
| `Source/LifePlay/LifePlay.cpp` (LF) | 4, 7-19 | `FLifePlayGameModule : FDefaultGameModuleImpl`, `StartupModule` → `FLPModGuard::Run()` ; `IMPLEMENT_PRIMARY_GAME_MODULE(FLifePlayGameModule, …)` |
|
|||
|
|
| `Source/LifePlay/LPModInfoSubsystem.h` (LF) | 10-17 | En-tête : fenêtre et cases |
|
|||
|
|
| idem | 41-47, 51-52 | `UFUNCTION` OnCheckEngineChanged / OnCheckMinVersionChanged / OnCheckMaxVersionChanged ; `EnsureSettingsRows()` ; retrait de `IsModuleEnabled` (T5, plus d'appelant) |
|
|||
|
|
| `Source/LifePlay/LPModInfoSubsystem.cpp` (LF) | 5-11, 20-21, 26 | includes UMG, `LPModGuard.h`, `Misc/EngineVersion.h`, `UObject/UObjectIterator.h` |
|
|||
|
|
| idem | 39-82 | `LPModInfoUi` : noms `LPModCheck*`, libellés, infobulles, police de repli, police des cases du Blueprint ; retrait de `FieldText` (T5) |
|
|||
|
|
| idem | 88-89, 104, 111-112 | `FLPModGuard::Run()` (sans effet si déjà fait) ; lecture `.lpmod` par `FLPModGuard::ParseLpmod` (même règle qu'avant) ; ticker toujours actif (0,5 s) |
|
|||
|
|
| idem | 135-148 | gestionnaires des cases → `FLPModGuard::SaveCheck` |
|
|||
|
|
| idem | 150-177 | `Tick` : cases, puis fenêtre pour chaque mod refusé, coché ou non |
|
|||
|
|
| idem | 179-255 | `EnsureSettingsRows` : 3 lignes `UHorizontalBox` (texte puis `UCheckBox` ×2, disposition de `SettingCheckbox_C`) ajoutées en fin de `MainMenu.SettingsScrollBox` (`BackButton` est le 1er enfant : `etat/L6e-CX-B09_widgets_apres.txt:12126-12133`) |
|
|||
|
|
| idem | 257-264, 280 | texte de la fenêtre ; titre « Mod compatibility warning » |
|
|||
|
|
| `Source/LifePlay/LPModDailies.h` (LF) | 14-15 | En-tête |
|
|||
|
|
| `Source/LifePlay/LPModDailies.cpp` (LF) | 11, 57-62 | mod refusé ignoré (ligne `Log`) ; le reste = HEAD (la garde T5 par `InitializeDependency` est retirée) |
|
|||
|
|
| `Source/LifePlay/LPHairstyleSubsystem.h` (LF) | 29-30 | En-tête |
|
|||
|
|
| `Source/LifePlay/LPHairstyleSubsystem.cpp` (LF) | 13, 230, 241-251 | fichiers `.lphair` d'un mod refusé non lus (ligne `Log` une fois) ; le reste = HEAD (signature `ScanFiles()` d'origine) |
|
|||
|
|
| `Source/LifePlay/LPSettingsSubsystem.cpp` (CRLF) | 176-180 | `CategoryOf` : nom `LPModCheck*` → onglet Misc |
|
|||
|
|
| `Config/DefaultGame.ini` (LF) | 4 | `ProjectVersion=2026.10.2.0` (T5, gardé) |
|
|||
|
|
| `D:\Game\LifePlay-Sandbox-5.8\Tools\BuildMod.ps1` (LF) | 21-23 | En-tête : ce que reçoivent le `.lpmod` et le `.uplugin` livrés, dont `EngineVersion` |
|
|||
|
|
| idem | 211-216 | contrôle : `<moteur>\Build\Build.version` (moteur `$UE` = `$LPEngineRoot` de `<jeu>\Tools\Engine.ps1`) → `$engineVersion` `X.Y.Z`, erreur si absent ou illisible, avant la cuisson |
|
|||
|
|
| idem | 247 | `Write-Host " moteur de la cuisson : …"` |
|
|||
|
|
| idem | 281, 287-288 | `.uplugin` livré : `Add-Member EngineVersion $engineVersion -Force` |
|
|||
|
|
|
|||
|
|
`LifePlay.Build.cs` : inchangé (API de `Core`, `CoreUObject`, `AssetRegistry`, `Projects`, `UMG` déjà en dépendance ;
|
|||
|
|
pas de `PakFile`). Correctifs UE4 (`LPStringCompat`, `LPMeshCompat`, `LPNavigationHelpers`…) : non touchés.
|
|||
|
|
|
|||
|
|
## 6. Diff, fins de ligne, contrôles statiques
|
|||
|
|
|
|||
|
|
`git -C "D:\Game\LifePlay-master 5.8" diff --stat` :
|
|||
|
|
```
|
|||
|
|
Config/DefaultGame.ini | 2 +-
|
|||
|
|
Source/LifePlay/LPHairstyleSubsystem.cpp | 13 ++
|
|||
|
|
Source/LifePlay/LPHairstyleSubsystem.h | 2 +
|
|||
|
|
Source/LifePlay/LPModDailies.cpp | 7 ++
|
|||
|
|
Source/LifePlay/LPModDailies.h | 2 +
|
|||
|
|
Source/LifePlay/LPModInfoSubsystem.cpp | 208 +++++++++++++++++++++++--------
|
|||
|
|
Source/LifePlay/LPModInfoSubsystem.h | 21 +++-
|
|||
|
|
Source/LifePlay/LPSettingsSubsystem.cpp | 5 +
|
|||
|
|
Source/LifePlay/LifePlay.cpp | 15 ++-
|
|||
|
|
9 files changed, 220 insertions(+), 55 deletions(-)
|
|||
|
|
```
|
|||
|
|
Non suivis (nouveaux) : `Source/LifePlay/LPModGuard.h` (79 lignes), `Source/LifePlay/LPModGuard.cpp` (525 lignes après fix1).
|
|||
|
|
`git -C "D:\Game\LifePlay-Sandbox-5.8" diff --stat` : `Tools/BuildMod.ps1 | 14 +++++++++++++-` (13+, 1−).
|
|||
|
|
`git status` : 9 `M` + 2 `??` (jeu), 1 `M` (addon) ; `LifePlay-master 5.5` et `LifePlay-Sandbox` (5.5) : vides.
|
|||
|
|
|
|||
|
|
Fins de ligne relues **octet par octet** (Python, `count(b'\n')`, `count(b'\r\n')`), HEAD contre copie de travail :
|
|||
|
|
|
|||
|
|
| Fichier | HEAD | Après | Non-ASCII avant = après | BOM |
|
|||
|
|
|---|---|---|---|---|
|
|||
|
|
| `Config/DefaultGame.ini` | LF (81) | LF (81) | 0 = 0 | non |
|
|||
|
|
| `LifePlay.cpp` | LF | LF | 0 = 0 | non |
|
|||
|
|
| `LPModGuard.h` / `.cpp` | — | LF | 0 | non |
|
|||
|
|
| `LPModInfoSubsystem.h` | LF | LF | 2 = 2 (`§` l. 1) | non |
|
|||
|
|
| `LPModInfoSubsystem.cpp`, `LPModDailies.h/.cpp`, `LPHairstyleSubsystem.cpp` | LF | LF | 0 = 0 | non |
|
|||
|
|
| `LPHairstyleSubsystem.h` | LF | LF | 4 = 4 | non |
|
|||
|
|
| `LPSettingsSubsystem.cpp` | CRLF (830/830) | CRLF (835/835) | 13 = 13 | non |
|
|||
|
|
| `BuildMod.ps1` (addon) | LF (326) | LF (338) | 0 = 0 | non |
|
|||
|
|
|
|||
|
|
**Correction du livrable T5** : il annonçait ces fichiers en CRLF ; ils sont en **LF** dans HEAD (le comptage
|
|||
|
|
`grep -c $'\r'` de ce shell compte toutes les lignes). Rien n'a été converti : chaque fichier garde ses fins de ligne.
|
|||
|
|
|
|||
|
|
Contrôles statiques faits : PowerShell `Parser::ParseFile(BuildMod.ps1)` → 0 erreur ; calcul de `EngineVersion` depuis
|
|||
|
|
`UE_5.8\Engine\Build\Build.version` → `5.8.3`, appliqué **en mémoire** au `.uplugin` du mod T4 (rien écrit) :
|
|||
|
|
`"EngineVersion": "5.8.3"` ajouté en dernier champ. Aucun reste de T5 (`IsModuleGameVersionCompatible`,
|
|||
|
|
`GetGameVersion()` de ModInfo) : `grep` vide. Noms nouveaux uniques dans le module et l'addon (`grep`).
|
|||
|
|
|
|||
|
|
## 7. Ce qui reste chargeable
|
|||
|
|
|
|||
|
|
| # | Chemin | Couverture | Niveau |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| P1 | `LPModDailies` (`Clip.LoadSynchronous`, plantage T4) | clips absents du registre après purge ; plugin refusé ignoré (`LPModDailies.cpp:57-62`) | établi (code) |
|
|||
|
|
| P4 | `GI.HairstyleTable` (`.lphair` du mod) | `.lphair` non lus (`LPHairstyleSubsystem.cpp:241-251`) ; assets démontés | établi (code) |
|
|||
|
|
| P10 | Sauvegarde : `SkeletalMeshPath` `/<mod>/…` → `GetAssetByObjectPath` | registre purgé → `FAssetData` invalide (ligne existante « GetAssetByObjectPath ECHEC ») ; chargement direct → paquet absent du magasin (`AsyncLoading2.cpp:6333-6336`) | établi (source) ; effet runtime estimé (= mod désinstallé : coiffure absente) |
|
|||
|
|
| P12 | Énumérations Blueprint (`GetAssetsByClassName`) | assets du mod absents du registre | établi (source) |
|
|||
|
|
| R1 | Démontage en échec (pak non trouvé, délégué non lié) | ligne `Error` « demontage incomplet » ; restent seuls `LPModDailies` et `LPHairstyleSubsystem` : un Blueprint ou une sauvegarde pourrait charger le mod | incertain (non observé) |
|
|||
|
|
| R2 | Entre le montage (AppInit) et `StartupModule` (`Default`) | aucun chargement de paquet de mod constaté dans T4 avant `LoadMap` ; un plugin `EarliestPossible`/`PreDefault` qui chargerait du `/<mod>/` passerait avant la garde | estimé |
|
|||
|
|
| R3 | Fichiers `Config/*.ini` d'un plugin `Mod` (fusionnés au montage, `PluginManager.cpp:1918-1944`), cibles de localisation, bibliothèque de shaders ouverte au montage (P11 de T5) | non annulés ; absents des mods BuildMod (mod T4 : `.uplugin` + `Content/Paks/Windows` seulement) | établi pour T4 ; incertain en général |
|
|||
|
|
| R4 | Plugin toujours « activé » pour `IPluginManager` (`GetEnabledPluginsWithContent`, `GetPluginsWithPakFile`) | pas d'API publique pour un plugin non `ExplicitlyLoaded` ; son contenu n'est plus joignable | établi |
|
|||
|
|
| R5 | Éditeur (`-game` compris) | rien n'est démonté (contenu non cuit) ; contrôle binaire sans objet ; filtrage de nos deux sous-systèmes seulement | établi |
|
|||
|
|
| R6 | Fichiers texte du module (`Content/Modules/<id>/*.lpscene`, etc.) | lus par les Blueprints comme avant (pas des assets) ; un chemin `/<id>/…` qu'ils citeraient ne se résout plus | estimé |
|
|||
|
|
| R7 | Étape `Core.System Paths` de `UnmountExplicitlyLoadedPlugin` (`PluginManager.cpp:3707`) | non reproduite : sans effet sur le chargement en jeu cuit IoStore, et évite de modifier `GEngineIni` | estimé |
|
|||
|
|
|
|||
|
|
## 8. Journal et fenêtre attendus (Development empaqueté ; chemins exacts estimés)
|
|||
|
|
|
|||
|
|
**(i) T4 rejoué** (jeu 2026.10.2.0, `lpr_anim_quaternius` cuit 5.5, `MODULE_GAME_VERSION: 2026.10.1.6`), au démarrage
|
|||
|
|
du module (entre `FAssetRegistry took …` et les lignes du GameInstance) :
|
|||
|
|
```
|
|||
|
|
LogLifePlay: Display: Modules : garde des mods d'assets, 1 plugin(s) de type Mod, jeu 2026.10.2.0 (version min des mods 2026.10.2.0), moteur 5.8.3-58210709+++UE5+Release-5.8 (conteneur 5, registre 24)
|
|||
|
|
LogLifePlay: Warning: Modules : lpr_anim_quaternius refuse : moteur : conteneur 4 (jeu 5), registre 17 (jeu 24) ; version min : construit pour LifePlay 2026.10.1.6 < 2026.10.2.0 (secondaire)
|
|||
|
|
LogPackageName: Display: FPackageName: Mount point removed: '../../../LifePlay/Mods/lpr_anim_quaternius/Content/' unmounted from '/lpr_anim_quaternius/'
|
|||
|
|
LogIoDispatcher: Display: Unmounting container '../../../LifePlay/Mods/lpr_anim_quaternius/Content/Paks/Windows/lpr_anim_quaterniusLifePlay-Windows'
|
|||
|
|
LogLifePlay: Display: Modules : lpr_anim_quaternius non charge : 1 pak(s) demonte(s) sur 1, point de montage /lpr_anim_quaternius/ retire, registre vide, conteneur(s) absent(s)
|
|||
|
|
```
|
|||
|
|
puis au GameInstance :
|
|||
|
|
```
|
|||
|
|
LogLifePlay: Display: Modules : 17 .lpmod lu(s), 1 avec MODULE_GAME_VERSION (jeu 2026.10.2.0)
|
|||
|
|
LogLifePlay: LPModDailies : lpr_anim_quaternius ignore (mod refuse)
|
|||
|
|
LogLifePlay: Display: Modules : cases de controle des mods ajoutees a Settings > Misc (moteur 1, version min 1, version max 1)
|
|||
|
|
LogLifePlay: Display: Modules : fenetre d'avertissement, 1 mod(s) refuse(s)
|
|||
|
|
```
|
|||
|
|
Absents : `LPModDailies : lpr_anim_quaternius, 84 clip(s)…`, `Fatal error`. Fenêtre « Mod compatibility warning » :
|
|||
|
|
> This game is LifePlay 2026.10.2.0 (Unreal Engine 5.8). The following mod(s) were not loaded: none of their assets can
|
|||
|
|
> be used, so that the game does not crash.
|
|||
|
|
>
|
|||
|
|
> - Quaternius Animations (lpr_anim_quaternius): built with an older engine (format 4, game 5): rebuild the mod for LifePlay 2026.10.2.0
|
|||
|
|
>
|
|||
|
|
> Update the mod, or ask its author for a version built for LifePlay 2026.10.2.0. To load a mod anyway (the game may
|
|||
|
|
> crash), uncheck the matching check in Settings > Misc (Check mod engine, Check mod minimum version, Check mod maximum
|
|||
|
|
> version), then restart the game.
|
|||
|
|
|
|||
|
|
Mod décoché dans le gestionnaire : mêmes lignes et même fenêtre (le moteur monte un mod même décoché).
|
|||
|
|
|
|||
|
|
**(ii) mod recuit 5.8** (`MODULE_GAME_VERSION: 2026.10.2.0`, `"EngineVersion": "5.8.3"`) :
|
|||
|
|
```
|
|||
|
|
LogLifePlay: Display: Modules : lpr_anim_quaternius accepte (conteneur 5, registre 24, EngineVersion 5.8.3, construit pour LifePlay 2026.10.2.0)
|
|||
|
|
LogLifePlay: LPModDailies : lpr_anim_quaternius, 84 clip(s) sous /lpr_anim_quaternius/Anims
|
|||
|
|
```
|
|||
|
|
Pas de fenêtre, pas de ligne `refuse`. (Valeurs 5/24 pour un mod cuit 5.8 : établies par la source d'écriture, § 4 ;
|
|||
|
|
non mesurées sur un mod réel.)
|
|||
|
|
|
|||
|
|
**(iii) trois cases décochées** (puis redémarrage), même mod 5.5 :
|
|||
|
|
```
|
|||
|
|
LogLifePlay: Warning: Modules : controle moteur des mods leve (Settings > Misc, <…>/Saved/Config/Windows/GameUserSettings.ini)
|
|||
|
|
LogLifePlay: Warning: Modules : controle version min des mods leve (Settings > Misc, <…>)
|
|||
|
|
LogLifePlay: Warning: Modules : controle version max des mods leve (Settings > Misc, <…>)
|
|||
|
|
LogLifePlay: Warning: Modules : lpr_anim_quaternius charge malgre : moteur : conteneur 4 (jeu 5), registre 17 (jeu 24) ; version min : construit pour LifePlay 2026.10.1.6 < 2026.10.2.0 (controle leve dans Settings > Misc)
|
|||
|
|
```
|
|||
|
|
Puis **plantage attendu au premier chargement de carte (T4)** : à ne pas lancer sans accord. Au clic d'une case :
|
|||
|
|
`Modules : controle moteur des mods leve dans <ini>, effet au prochain lancement`.
|
|||
|
|
|
|||
|
|
**Variante Pocolov** (`lpr_hair_pocolov-1.0-for-LifePlay-2026.10.4`, `MODULE_GAME_VERSION: 2026.10.4`) : refusé par
|
|||
|
|
Moteur ; l'échec Version max (2026.10.4 > 2026.10.2.0 numériquement, § 11 point 1) n'est qu'au journal, suffixé
|
|||
|
|
« (secondaire) » (fix1) : la fenêtre ne dit plus « update the game ». `LPHairSample` (sans `MODULE_GAME_VERSION`) : Warning
|
|||
|
|
« sans MODULE_GAME_VERSION », refus par Moteur seul.
|
|||
|
|
|
|||
|
|
## 9. Plan de test runtime minimal (VAL)
|
|||
|
|
1. BUILD (lp-builder) : `Tools\Build.ps1 -Lot U7` (éditeur), puis `Tools\BuildDebug.ps1 -Config Development` (la garde
|
|||
|
|
n'agit que dans un jeu cuit). Relever les avertissements nouveaux (`LPModGuard.cpp`, `LPModInfoSubsystem.cpp`).
|
|||
|
|
2. Jeu Development, **sans mod** : `Modules : garde des mods d'assets, 0 plugin(s)…` ; Settings > Misc : 3 cases cochées
|
|||
|
|
(absentes des autres onglets) ; décocher « Check mod engine » → clé `bCheckModEngine=False` dans
|
|||
|
|
`Saved\Config\Windows\GameUserSettings.ini` ; redémarrer → Warning « controle moteur des mods leve » ; recocher.
|
|||
|
|
3. **(i)** copie jetable du build + archive `lpr_anim_quaternius-1.0-for-LifePlay-2026.10.1.6` : lignes du § 8 (i),
|
|||
|
|
fenêtre, écran titre, **nouvelle partie (liste des villes)** puis **Continuer** (mémoire « packaging test paths »), aucun
|
|||
|
|
plantage. Compter les lignes `refuse` (1) et `non charge` (1).
|
|||
|
|
4. **P10** : Pocolov 5.5 installé, sauvegarde dont un personnage porte une coiffure Pocolov → Continuer : pas de plantage,
|
|||
|
|
ligne `GetAssetByObjectPath ECHEC | recu '/lpr_hair_pocolov/…'`, coiffure absente.
|
|||
|
|
5. **(ii)** recuisson par `BuildMod.ps1` 5.8 (référence `Releases\2026.10.2.0\Windows` requise) : `.uplugin` livré avec
|
|||
|
|
`"EngineVersion": "5.8.3"`, `.lpmod` avec `MODULE_GAME_VERSION: 2026.10.2.0` ; installé : ligne `accepte`, clips ajoutés.
|
|||
|
|
6. **(iii)** seulement avec l'accord de l'utilisateur.
|
|||
|
|
|
|||
|
|
## 10. Écarts à la fiche et à la SPEC
|
|||
|
|
1. **Lieu de la garde** : démarrage du module `LifePlay` (`FLifePlayGameModule::StartupModule`), pas
|
|||
|
|
`ULPModInfoSubsystem::Initialize` (esquisse T6) : l'Event Init Blueprint du GameInstance précède les sous-systèmes
|
|||
|
|
(§ 3.1). `LifePlay.cpp` passe de `FDefaultGameModuleImpl` à une classe dérivée.
|
|||
|
|
2. **T5 révisé** : l'égalité stricte et les `InitializeDependency` de T5 sont remplacés par un verdict unique
|
|||
|
|
(`FLPModGuard`) ; `LPModDailies` et `LPHairstyleSubsystem` reviennent à HEAD + un test `IsRefused` chacun. Gardé de T5 :
|
|||
|
|
fenêtre pour les mods décochés, retrait de `IsModuleEnabled`/`FieldText`, `ProjectVersion=2026.10.2.0`.
|
|||
|
|
3. **Portée** : les contrôles visent les plugins de type `Mod` (mods d'assets). Un module texte seul (dossier
|
|||
|
|
`Content/Modules/<id>` sans plugin) n'est ni contrôlé ni refusé (rien de cuit ; ses fichiers restent lus par les
|
|||
|
|
Blueprints). Interprétation à confirmer.
|
|||
|
|
4. `MODULE_GAME_VERSION` non numérique : compté sous le contrôle **Version min** (« unreadable game version '…' in its
|
|||
|
|
.lpmod ») ; `EngineVersion` non lisible : « mod files unreadable » (contrôle Moteur).
|
|||
|
|
5. Contrôle Moteur : la raison `EngineVersion` n'est ajoutée que si le binaire est conforme (pas de double raison).
|
|||
|
|
6. Plugin `Mod` sans aucun pak (jeu cuit) : contrôle binaire **sans objet**, pas « illisible » (rien n'est monté).
|
|||
|
|
7. Lignes `LPModDailies` / `Coiffures` « ignore (mod refuse) » au niveau `Log` : la seule ligne Warning par refus est
|
|||
|
|
celle de la garde (décision c).
|
|||
|
|
8. Étape `Core.System Paths` de la fonction d'Epic non reproduite (§ 7, R7).
|
|||
|
|
9. Fenêtre renommée « Mod compatibility warning » ; cases ajoutées en fin de liste ; fichier `LPSettingsSubsystem.cpp`
|
|||
|
|
modifié (une règle `CategoryOf`, dans la liste autorisée `Source/LifePlay/*`) ; deux fichiers nouveaux
|
|||
|
|
(`LPModGuard.*`, même dossier).
|
|||
|
|
10. `EngineVersion` : clé native du descripteur, forme `X.Y.Z` (`FEngineVersion::Parse` exige trois composantes). Effet
|
|||
|
|
de bord hors jeu : un éditeur non monolithique qui verrait un `.uplugin` livré plus récent que son moteur afficherait
|
|||
|
|
l'avertissement d'Epic (`PluginManager.cpp:2688-2715`) ; sans objet pour le jeu empaqueté.
|
|||
|
|
11. **fix1** (fiche U7-T7-fix1, décision utilisateur) : quand le contrôle Moteur est actif et échoue, les échecs
|
|||
|
|
min/max ne vont pas dans la fenêtre (`Verdict.Reasons`), seulement au journal (`Verdict.LogReasons`, suffixe
|
|||
|
|
« (secondaire) ») ; Moteur levé ou conforme : min/max affichés normalement (`LPModGuard.cpp:390-413`).
|
|||
|
|
|
|||
|
|
## 11. Incertitudes et points à confirmer
|
|||
|
|
1. **Numérotation des versions** (à trancher par l'utilisateur, non modifié) : les mods 5.5 publiés portent
|
|||
|
|
`2026.10.4` (Pocolov), numériquement **supérieur** à la première version 5.8 `2026.10.2.0` (le jeu 5.5 est revenu à
|
|||
|
|
`2026.10.1.6`). Conséquence : Pocolov passe la version min et échoue la version max avec un message trompeur
|
|||
|
|
(« update the game ») ; le contrôle Moteur le refuse de toute façon. Si un mod 5.5 `2026.10.4` est un jour accepté par
|
|||
|
|
le contrôle Moteur levé, seul le max le bloquera.
|
|||
|
|
2. Compilation non faite (fiche BUILD) : API relues dans les en-têtes 5.8 (§ 3-4) ; aucune garde de version (code de la
|
|||
|
|
copie 5.8 seulement) ; noms internes dans des espaces de noms nommés (pas de C2374 en unity) ; pas d'ombrage de
|
|||
|
|
variable (avertissement en erreur en V7) relevé à la relecture. Risque résiduel : avertissements de format
|
|||
|
|
(`%d` avec `int32` partout, conversions explicites faites).
|
|||
|
|
3. Lecture de l'en-tête de conteneur par `FIoDispatcher` au démarrage du module : déduite de l'API (le moteur lit ce bloc
|
|||
|
|
au montage), **non observée** ; si elle échouait pour un mod 5.8 valide, le mod serait refusé « mod files unreadable »
|
|||
|
|
(cas (ii) du § 9 le montrerait ; la case Moteur permet de lever).
|
|||
|
|
4. Démontage au démarrage du module : chemin d'Epic (DLC, UGC), mais **non exécuté** ici ; preuve = lignes du § 8 (i).
|
|||
|
|
5. R2 et R3 du § 7.
|
|||
|
|
|
|||
|
|
Suite : l'orchestrateur doit faire lancer la fiche BUILD (lp-builder : `Tools\Build.ps1 -Lot U7` puis build Development
|
|||
|
|
empaqueté) et la VAL (§ 9, (iii) seulement avec accord), et soumettre à l'utilisateur le point 11.1 ; état : 9 fichiers
|
|||
|
|
modifiés + 2 nouveaux dans la copie 5.8, 1 fichier dans l'addon 5.8, non compilés, non commités.
|