2026-10-03 07:06:48 +00:00
<#
. SYNOPSIS
Cuit un mod d 'assets et l' assemble en archive installable ( lot H3b , Docs / Migration / 16_H3_Mods_Paquets . md ) .
. DESCRIPTION
Sources : ModSources \ < Nom > \ ( < Nom > . uplugin , Content \ , Module \ ; voir Tools / ModSource . ps1 ) .
1 . controles : plugin et module ( MODULE_UNIQUEID = < Nom > ) , version de reference Releases \ < Version > \ Windows , aucun
autre plugin dans Mods \ ;
2 . montage du mod dans Mods \ < Nom > , cuisson DLC contre la version de reference ( BuildCookRun -dlcname
-basedonreleaseversion , via cmd / c : appelee directement depuis PowerShell , la version est coupee en deux ) ,
journal dans Saved \ Logs \ portage \ mods ; remise en place dans ModSources \ < Nom > dans tous les cas ;
3 . controle du conteneur : chaque paquet est sous Mods / < Nom > / ( aucun asset du jeu embarque ) ;
4 . assemblage < Out > \ < Nom > -for -LifePlay - < Version > \ LifePlay \ { Mods \ < Nom > , Content \ Modules \ < Nom > } + notice bilingue ,
archive . zip testee ( 7 -Zip ) ; un assemblage precedent du meme nom est deplace ( precedent_ < date > ) , jamais supprime ;
5 . -Install < dossier du jeu > : copie le mod dans un jeu de test ( dossier contenant LifePlay . exe ) .
. EXAMPLE
H3d : metadonnees des mods, avertissement de version, paquet lpr_hair_pocolov (12 coiffures)
- LPModInfoSubsystem : metadonnees des .lpmod ; fenetre d'avertissement quand un mod actif
a ete construit pour une autre version du jeu (Slate, sans Blueprint).
- NewMod/BuildMod : MODULE_VERSION, _CREATED, _LICENSE, _AUTHOR_LINK, _PRICE, _PACKAGED,
_GAME_VERSION ; ordre des 3 premieres lignes controle ; prix et licences controles.
- lpr_hair_pocolov 1.0 : les 12 coiffures Pocolov CC BY ; lp_Revival et les 6 Pocolov
retires du jeu de base ; mod d'essai LPHairSample retire.
Valide en jeu le 2026-10-03 (CHG-106, CHG-107).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:14:37 +00:00
. \ Tools \ BuildMod . ps1 -Name lpr_hair_pocolov
. \ Tools \ BuildMod . ps1 -Name lpr_hair_pocolov -Install D: \ Game \ LifePlay_Build_5 . 5_H3d \ Windows
2026-10-03 07:06:48 +00:00
#>
param (
[ Parameter ( Mandatory ) ] [ string ] $Name ,
[ string ] $Version = '' ,
[ string ] $Out = 'D:\Game\LifePlay_Publication\Mods' ,
[ string ] $Install = ''
)
$ErrorActionPreference = 'Stop'
$Root = Split-Path $PSScriptRoot -Parent
. ( Join-Path $PSScriptRoot 'ModSource.ps1' )
$UE = 'D:\Game\Launcher\Engine\UE_5.5\Engine'
$SevenZip = 'C:\Program Files\7-Zip\7z.exe'
# 1. Controles.
if ( -not $Version ) {
$line = Select-String -Path ( Join-Path $Root 'Config\DefaultGame.ini' ) -Pattern '^ProjectVersion=(.+)$' | Select-Object -First 1
if ( -not $line ) { throw 'ProjectVersion absent de Config\DefaultGame.ini : preciser -Version' }
$Version = $line . Matches [ 0 ] . Groups [ 1 ] . Value . Trim ( )
}
$src = Join-Path $Root " ModSources\ $Name "
$lpmod = Join-Path $src " Module\ $Name .lpmod "
if ( -not ( Test-Path ( Join-Path $src " $Name .uplugin " ) ) ) { throw " ModSources\ $Name \ $Name .uplugin introuvable " }
if ( -not ( Test-Path $lpmod ) ) { throw " ModSources\ $Name \Module\ $Name .lpmod introuvable " }
$id = ( Select-String -Path $lpmod -Pattern '^MODULE_UNIQUEID:\s*(.+)$' | Select-Object -First 1 ) . Matches [ 0 ] . Groups [ 1 ] . Value . Trim ( )
if ( $id -ne $Name ) { throw " MODULE_UNIQUEID ( $id ) different du nom du mod ( $Name ) " }
$title = ( Select-String -Path $lpmod -Pattern '^MODULE_NAME:\s*(.+)$' | Select-Object -First 1 ) . Matches [ 0 ] . Groups [ 1 ] . Value . Trim ( )
H3d : metadonnees des mods, avertissement de version, paquet lpr_hair_pocolov (12 coiffures)
- LPModInfoSubsystem : metadonnees des .lpmod ; fenetre d'avertissement quand un mod actif
a ete construit pour une autre version du jeu (Slate, sans Blueprint).
- NewMod/BuildMod : MODULE_VERSION, _CREATED, _LICENSE, _AUTHOR_LINK, _PRICE, _PACKAGED,
_GAME_VERSION ; ordre des 3 premieres lignes controle ; prix et licences controles.
- lpr_hair_pocolov 1.0 : les 12 coiffures Pocolov CC BY ; lp_Revival et les 6 Pocolov
retires du jeu de base ; mod d'essai LPHairSample retire.
Valide en jeu le 2026-10-03 (CHG-106, CHG-107).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:14:37 +00:00
# Metadonnees (H3d) : le jeu lit les trois premieres lignes par position.
$lpmodLines = @ ( Get-Content $lpmod -Encoding utf8 )
$expected = 'MODULE_UNIQUEID:' , 'MODULE_NAME:' , 'MODULE_AUTHOR:'
for ( $i = 0 ; $i -lt 3 ; $i + + ) {
if ( $lpmodLines . Count -le $i -or -not $lpmodLines [ $i ] . StartsWith ( $expected [ $i ] ) ) { throw " $Name .lpmod : la ligne $( $i + 1 ) doit commencer par $( $expected [ $i ] ) (le jeu lit ces lignes par position) " }
}
function Get-LpmodValue([string ] $Key ) {
$m = $lpmodLines | Select-String -Pattern " ^ $( [ regex ] :: Escape ( $Key ) ) :\s*(.*) $ " | Select-Object -First 1
if ( $m ) { $m . Matches [ 0 ] . Groups [ 1 ] . Value . Trim ( ) } else { '' }
}
$modVersion = Get-LpmodValue 'MODULE_VERSION'
# Mod payant : aucune licence d'asset non commerciale ou de magasin dans les credits.
$price = Get-LpmodValue 'MODULE_PRICE'
if ( $price -and $price -notmatch '^(free|gratuit|0+([.,]0+)?)$' ) {
$blocking = Select-String -Path ( Join-Path $src 'Module\CREDITS.txt' ) -Pattern '\bNC\b|NonCommercial|Non-Commercial|Standard License|Personal' -ErrorAction SilentlyContinue
if ( $blocking ) { throw " MODULE_PRICE ' $price ' : licence(s) incompatible(s) avec la vente dans CREDITS.txt ( $( $blocking [ 0 ] . Line . Trim ( ) ) ) " }
}
2026-10-03 07:06:48 +00:00
$release = Join-Path $Root " Releases\ $Version \Windows "
if ( -not ( Test-Path ( Join-Path $release 'AssetRegistry.bin' ) ) ) { throw " Version de reference absente : $release (build de publication avec -createreleaseversion= $Version ) " }
$stray = Get-StrayMods $Root
if ( $stray ) { throw " Autres plugins dans Mods\ ( $( $stray -join ', ' ) ) : les remettre dans ModSources avant de cuire " }
# 2. Cuisson DLC.
$logDir = Join-Path $Root 'Saved\Logs\portage\mods'
New-Item -ItemType Directory -Force $logDir | Out-Null
$log = Join-Path $logDir ( " {0}_{1}.log " -f $Name , ( Get-Date -Format 'yyyyMMdd_HHmmss' ) )
$mounted = Mount-ModSource $Root $Name
try {
$uplugin = Join-Path $mounted " $Name .uplugin "
# Commande ecrite dans un .cmd a cote du journal (guillemets intacts, commande exacte conservee).
$batch = [ IO.Path ] :: ChangeExtension ( $log , '.cmd' )
$cmd = " @call `" $UE \Build\BatchFiles\RunUAT.bat `" BuildCookRun -project= `" $Root \LifePlay.uproject `" -platform=Win64 -clientconfig=Shipping -cook -stage -pak -iostore -compressed -dlcname= `" $uplugin `" -basedonreleaseversion= $Version -nodebuginfo -unattended -utf8output -nop4 > `" $log `" 2>&1 "
[ IO.File ] :: WriteAllText ( $batch , $cmd + " `r `n @exit /b %ERRORLEVEL% `r `n " , ( New-Object Text . UTF8Encoding $false ) )
Write-Host " Cuisson DLC de $Name contre $Version (journal : $log ) "
& cmd / c $batch
$exit = $LASTEXITCODE
$cooked = Select-String -Path $log -Pattern 'Packages Cooked: (\d+)' | Select-Object -Last 1
if ( $exit -ne 0 -or -not $cooked ) { throw " Cuisson en echec (code $exit ) : voir $log " }
Write-Host " $( $cooked . Matches [ 0 ] . Groups [ 1 ] . Value ) paquet(s) cuit(s) "
$paks = Join-Path $mounted " Saved\StagedBuilds\Windows\LifePlay\Mods\ $Name \Content\Paks\Windows "
$files = @ ( Get-ChildItem $paks -File | Where-Object { $_ . LastWriteTime -gt ( Get-Item $log ) . CreationTime } )
if ( $files . Count -lt 3 ) { throw " Paquets du mod absents ou anciens dans $paks " }
# 3. Contenu du conteneur : rien hors de Mods/<Nom>/.
$utoc = ( $files | Where-Object Extension -eq '.utoc' | Select-Object -First 1 ) . FullName
$entries = @ ( & " $UE \Binaries\Win64\UnrealPak.exe " $utoc -List 2 > & 1 | ForEach-Object { " $_ " } | Where-Object { $_ -match '"\.\./\.\./\.\./' } | ForEach-Object { ( $_ -split '"' ) [ 1 ] } )
$outside = @ ( $entries | Where-Object { $_ -notlike " ../../../LifePlay/Mods/ $Name /* " } )
if ( -not $entries -or $outside ) { throw " Conteneur : $( $entries . Count ) entree(s), $( $outside . Count ) hors de Mods/ $Name ( $( $outside | Select-Object -First 3 ) ) " }
Write-Host " conteneur : $( $entries . Count ) fichier(s), tous sous Mods/ $Name / "
# 4. Assemblage.
New-Item -ItemType Directory -Force $Out | Out-Null
H3d : metadonnees des mods, avertissement de version, paquet lpr_hair_pocolov (12 coiffures)
- LPModInfoSubsystem : metadonnees des .lpmod ; fenetre d'avertissement quand un mod actif
a ete construit pour une autre version du jeu (Slate, sans Blueprint).
- NewMod/BuildMod : MODULE_VERSION, _CREATED, _LICENSE, _AUTHOR_LINK, _PRICE, _PACKAGED,
_GAME_VERSION ; ordre des 3 premieres lignes controle ; prix et licences controles.
- lpr_hair_pocolov 1.0 : les 12 coiffures Pocolov CC BY ; lp_Revival et les 6 Pocolov
retires du jeu de base ; mod d'essai LPHairSample retire.
Valide en jeu le 2026-10-03 (CHG-106, CHG-107).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:14:37 +00:00
$base = if ( $modVersion ) { " $Name - $modVersion -for-LifePlay- $Version " } else { " $Name -for-LifePlay- $Version " }
2026-10-03 07:06:48 +00:00
$stage = Join-Path $Out $base
$zip = " $stage .zip "
$stamp = Get-Date -Format 'yyyyMMdd_HHmmss'
if ( Test-Path $stage ) { Move-Item $stage " $stage .precedent_ $stamp " }
if ( Test-Path $zip ) { Move-Item $zip " $stage .precedent_ $stamp .zip " }
$modDir = Join-Path $stage " LifePlay\Mods\ $Name "
New-Item -ItemType Directory -Force ( Join-Path $modDir 'Content\Paks\Windows' ) | Out-Null
Copy-Item $uplugin $modDir
$files | Copy-Item -Destination ( Join-Path $modDir 'Content\Paks\Windows' )
Copy-Item ( Join-Path $mounted 'Module' ) ( Join-Path $stage " LifePlay\Content\Modules\ $Name " ) -Recurse
H3d : metadonnees des mods, avertissement de version, paquet lpr_hair_pocolov (12 coiffures)
- LPModInfoSubsystem : metadonnees des .lpmod ; fenetre d'avertissement quand un mod actif
a ete construit pour une autre version du jeu (Slate, sans Blueprint).
- NewMod/BuildMod : MODULE_VERSION, _CREATED, _LICENSE, _AUTHOR_LINK, _PRICE, _PACKAGED,
_GAME_VERSION ; ordre des 3 premieres lignes controle ; prix et licences controles.
- lpr_hair_pocolov 1.0 : les 12 coiffures Pocolov CC BY ; lp_Revival et les 6 Pocolov
retires du jeu de base ; mod d'essai LPHairSample retire.
Valide en jeu le 2026-10-03 (CHG-106, CHG-107).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:14:37 +00:00
# Copie livree (pas les sources) : date de paquet et version du jeu visee (avertissement en jeu si elle differe).
$stagedLpmod = Join-Path $stage " LifePlay\Content\Modules\ $Name \ $Name .lpmod "
$stamped = @ ( $lpmodLines | Where-Object { $_ -notmatch '^MODULE_(PACKAGED|GAME_VERSION):' } ) + " MODULE_PACKAGED: $( Get-Date -Format 'yyyy-MM-dd' ) " + " MODULE_GAME_VERSION: $Version "
[ IO.File ] :: WriteAllText ( $stagedLpmod , ( $stamped -join " `r `n " ) + " `r `n " , ( New-Object Text . UTF8Encoding $false ) )
# Descripteur livre : version du mod et lien de l'auteur dans les champs natifs du plugin.
$stagedPlugin = Join-Path $modDir " $Name .uplugin "
$desc = Get-Content $stagedPlugin -Raw | ConvertFrom-Json
if ( $modVersion ) { $desc . VersionName = $modVersion }
$authorLink = Get-LpmodValue 'MODULE_AUTHOR_LINK'
if ( $authorLink ) { $desc | Add-Member CreatedByURL $authorLink -Force }
[ IO.File ] :: WriteAllText ( $stagedPlugin , ( $desc | ConvertTo-Json ) + " `r `n " , ( New-Object Text . UTF8Encoding $false ) )
2026-10-03 07:06:48 +00:00
$readme = @ (
H3d : metadonnees des mods, avertissement de version, paquet lpr_hair_pocolov (12 coiffures)
- LPModInfoSubsystem : metadonnees des .lpmod ; fenetre d'avertissement quand un mod actif
a ete construit pour une autre version du jeu (Slate, sans Blueprint).
- NewMod/BuildMod : MODULE_VERSION, _CREATED, _LICENSE, _AUTHOR_LINK, _PRICE, _PACKAGED,
_GAME_VERSION ; ordre des 3 premieres lignes controle ; prix et licences controles.
- lpr_hair_pocolov 1.0 : les 12 coiffures Pocolov CC BY ; lp_Revival et les 6 Pocolov
retires du jeu de base ; mod d'essai LPHairSample retire.
Valide en jeu le 2026-10-03 (CHG-106, CHG-107).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:14:37 +00:00
" $title ( $Name ) $( if ( $modVersion ) { " $modVersion " } ) - built for LifePlay $Version " ,
2026-10-03 07:06:48 +00:00
'' ,
'INSTALL: extract this archive into the game folder (the one containing LifePlay.exe), merging the LifePlay folder.' ,
" Then open `" Mod Manager `" on the title screen and enable the mod. To uninstall, delete LifePlay\Mods\ $Name " ,
" and LifePlay\Content\Modules\ $Name . Credits and licenses: LifePlay\Content\Modules\ $Name \CREDITS.txt. " ,
" This mod contains cooked assets: it is made for LifePlay $Version and may need a rebuild for other versions. " ,
'' ,
'INSTALLATION : extraire cette archive dans le dossier du jeu (celui de LifePlay.exe) en fusionnant le dossier' ,
'LifePlay, puis activer le mod dans "Mod Manager" sur l''ecran titre. Desinstallation : supprimer' ,
" LifePlay\Mods\ $Name et LifePlay\Content\Modules\ $Name . Credits et licences : CREDITS.txt du module. " ,
" Ce mod contient des assets cuits pour LifePlay $Version : une autre version du jeu peut demander de le recuire. " )
[ IO.File ] :: WriteAllText ( ( Join-Path $stage 'README.txt' ) , ( $readme -join " `r `n " ) + " `r `n " , ( New-Object Text . UTF8Encoding $false ) )
}
finally {
Dismount-ModSource $Root $Name
}
Push-Location $stage
try {
& $SevenZip a -tzip $zip '.\*' | Out-Null
& $SevenZip t $zip | Out-Null
if ( $LASTEXITCODE -ne 0 ) { throw " Archive $zip invalide " }
}
finally { Pop-Location }
$staged = @ ( Get-ChildItem $stage -Recurse -File ) . Count
$listed = @ ( & $SevenZip l -slt $zip | Where-Object { $_ -match '^Attributes = [^D]*$' } ) . Count
if ( $staged -ne $listed ) { throw " Archive : $listed fichier(s) pour $staged assembles " }
$hash = ( Get-FileHash $zip -Algorithm SHA256 ) . Hash
Write-Host ( " Archive : {0} ({1:N0} octets, {2} fichiers, SHA-256 {3}) " -f $zip , ( Get-Item $zip ) . Length , $listed , $hash )
# 5. Installation dans un jeu de test.
if ( $Install ) {
if ( -not ( Test-Path ( Join-Path $Install 'LifePlay.exe' ) ) ) { throw " $Install ne contient pas LifePlay.exe " }
robocopy ( Join-Path $stage 'LifePlay' ) ( Join-Path $Install 'LifePlay' ) / E / NFL / NDL / NJH / NJS / NP | Out-Null
if ( $LASTEXITCODE -ge 8 ) { throw " Copie vers $Install en echec (robocopy $LASTEXITCODE ) " }
Write-Host " Installe dans $Install "
}
H3d : metadonnees des mods, avertissement de version, paquet lpr_hair_pocolov (12 coiffures)
- LPModInfoSubsystem : metadonnees des .lpmod ; fenetre d'avertissement quand un mod actif
a ete construit pour une autre version du jeu (Slate, sans Blueprint).
- NewMod/BuildMod : MODULE_VERSION, _CREATED, _LICENSE, _AUTHOR_LINK, _PRICE, _PACKAGED,
_GAME_VERSION ; ordre des 3 premieres lignes controle ; prix et licences controles.
- lpr_hair_pocolov 1.0 : les 12 coiffures Pocolov CC BY ; lp_Revival et les 6 Pocolov
retires du jeu de base ; mod d'essai LPHairSample retire.
Valide en jeu le 2026-10-03 (CHG-106, CHG-107).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:14:37 +00:00
# Code de sortie : succes (robocopy renvoie 1 a 7 quand il a copie).
exit 0