2026-10-02 18:55:48 +00:00
<#
. SYNOPSIS
Separe les modules optionnels d ' un build empaquete ( Docs / Migration / 15_Modules_Optionnels . md ) .
. DESCRIPTION
Les modules de Content / Modules sont livres en fichiers nus a cote du jeu . Ce script garde dans le build les modules
listes comme essentiels dans le fichier de configuration ( Tools / modules / < version > . json ) et deplace tous les autres
dans une archive zip , avec l ' arborescence LifePlay \ Content \ Modules \ < module > \ . . . , a extraire dans le dossier du jeu
( celui de LifePlay . exe ) . Une notice LISEZMOI / README est ajoutee a l ' archive .
Le jeu gere l 'absence d' un module : au demarrage , GI . removeDeletedMods retire de GI . ModEnableds tout module dont le
fichier . lpmod est absent , et GI . IsModEnabled repond alors faux .
Controles : chaque module essentiel doit exister ; nombre de fichiers et d 'octets deplaces = contenu de l' archive
( 7 -Zip , test d ' integrite ) ; aucun fichier de module optionnel ne reste dans le build . Les fichiers deplaces sont
conserves dans < build > \ . . \ < nom > _modules_optionnels ( rien n ' est supprime ) .
2026-10-03 08:43:53 +00:00
. PARAMETER ( configuration " per_module " : true , depuis la publication qui suit la V1 . 4 )
Une archive par module optionnel , < module > -for -LifePlay - < version > . zip ( LifePlay \ Content \ Modules \ < module > + notice
bilingue tiree du . lpmod ) , dans < Out > \ < out_dir > ; un index INDEX . txt ( nom , archive , taille , SHA - 256 ) sert au message
de diffusion ( Discord ) . Les mods d ' assets ( Tools / BuildMod . ps1 ) se deposent dans le meme dossier .
. EXAMPLE
. \ Tools \ SplitModules . ps1 -Build D: \ Game \ LifePlay_Build_5 . 5_Shipping_V1 . 5 -Config Tools \ modules
ext . json -Out D: \ Game \ LifePlay_Publication
2026-10-02 18:55:48 +00:00
. EXAMPLE
. \ Tools \ SplitModules . ps1 -Build D: \ Game \ LifePlay_Build_5 . 5_Shipping_V1 . 4 -Config Tools \ modules \ V1 . 4 . json -Out D: \ Game \ LifePlay_Publication
#>
param (
[ Parameter ( Mandatory ) ] [ string ] $Build ,
[ Parameter ( Mandatory ) ] [ string ] $Config ,
[ Parameter ( Mandatory ) ] [ string ] $Out
)
$ErrorActionPreference = 'Stop'
$SevenZip = 'C:\Program Files\7-Zip\7z.exe'
$cfg = Get-Content $Config -Raw | ConvertFrom-Json
$modules = Join-Path $Build 'Windows\LifePlay\Content\Modules'
if ( -not ( Test-Path $modules ) ) { throw " Dossier des modules introuvable : $modules " }
2026-10-03 08:43:53 +00:00
# Un build de distribution ne contient aucun mod d'assets installe (sa partie texte serait livree sans ses paquets) :
# ces mods sont livres par Tools/BuildMod.ps1.
$assetMods = @ ( Get-ChildItem ( Join-Path $Build 'Windows\LifePlay\Mods' ) -Directory -ErrorAction SilentlyContinue | ForEach-Object Name )
if ( $assetMods ) { throw " Mod(s) d'assets installe(s) dans le build ( $( $assetMods -join ', ' ) ) : utiliser un build neuf, ces mods sont livres par BuildMod.ps1 " }
2026-10-02 18:55:48 +00:00
foreach ( $m in $cfg . essential ) {
if ( -not ( Test-Path ( Join-Path $modules " $m \ $m .lpmod " ) ) ) { throw " Module essentiel absent du build : $m " }
}
$optional = Get-ChildItem $modules -Directory | Where-Object { ( Test-Path ( Join-Path $_ . FullName " $( $_ . Name ) .lpmod " ) ) -and $_ . Name -notin $cfg . essential }
if ( -not $optional ) { Write-Host 'Aucun module optionnel dans le build.' ; return }
# Arborescence de l'archive : LifePlay\Content\Modules\<module>
$stage = Join-Path ( Split-Path $Build -Parent ) ( ( Split-Path $Build -Leaf ) + '_modules_optionnels' )
$target = Join-Path $stage 'LifePlay\Content\Modules'
New-Item -ItemType Directory -Force $target | Out-Null
$files = 0 ; $bytes = 0
foreach ( $m in $optional ) {
$items = Get-ChildItem $m . FullName -Recurse -File
$files + = $items . Count ; $bytes + = ( $items | Measure-Object Length -Sum ) . Sum
Move-Item $m . FullName ( Join-Path $target $m . Name )
}
$list = ( $optional . Name | Sort-Object ) -join ', '
2026-10-03 08:43:53 +00:00
if ( $cfg . per_module ) {
# Une archive par module (publication suivant la V1.4).
$dir = Join-Path $Out $ ( if ( $cfg . out_dir ) { $cfg . out_dir } else { " Modules_ $( $cfg . version ) " } )
New-Item -ItemType Directory -Force $dir | Out-Null
$index = @ ( " LifePlay Revival $( $cfg . version ) - optional modules / modules optionnels " , '' ,
'Install: extract the zip into the game folder (the one containing LifePlay.exe), merging the LifePlay folder, then enable' ,
'the module in "Mod Manager" (title screen). Installation : extraire le zip dans le dossier du jeu, puis activer le module.' ,
'Adult content, adults only / contenu adulte, reserve aux adultes.' , '' )
2026-10-03 09:19:44 +00:00
# Paquets : un groupe de la configuration ("groups" : name, pattern, title, description) reunit ses modules dans une
# seule archive (ex. vin_* = modules de base du jeu, decision du 2026-10-03) ; chaque autre module a la sienne.
$packages = @ ( )
$grouped = @ ( )
foreach ( $g in @ ( $cfg . groups ) ) {
if ( -not $g ) { continue }
$members = @ ( $optional | Where-Object { $_ . Name -like $g . pattern } | Sort-Object Name )
if ( $members ) { $packages + = [ pscustomobject ] @ { Name = $g . name ; Title = $g . title ; Description = $g . description ; Members = $members } ; $grouped + = $members . Name }
}
foreach ( $m in ( $optional | Where-Object { $_ . Name -notin $grouped } | Sort-Object Name ) ) {
$packages + = [ pscustomobject ] @ { Name = $m . Name ; Title = '' ; Description = '' ; Members = @ ( $m ) }
}
2026-10-03 08:43:53 +00:00
$zipped = 0
2026-10-03 09:19:44 +00:00
foreach ( $p in $packages ) {
$one = Join-Path $stage " _zip\ $( $p . Name ) "
2026-10-03 08:43:53 +00:00
New-Item -ItemType Directory -Force ( Join-Path $one 'LifePlay\Content\Modules' ) | Out-Null
2026-10-03 09:19:44 +00:00
$lines = @ ( ) ; $expected = 0
foreach ( $m in $p . Members ) {
$src = Join-Path $target $m . Name
$lp = Get-Content ( Join-Path $src " $( $m . Name ) .lpmod " ) -Encoding utf8
$val = { param ( $k ) $l = $lp | Where-Object { $_ -match " ^ $k \s*: " } | Select-Object -First 1 ; if ( $l ) { ( $l -split ':' , 2 ) [ 1 ] . Trim ( ) } else { '' } }
$title = & $val 'MODULE_NAME' ; $desc = & $val 'MODULE_DESCRIPTION' ; $req = & $val 'MODULE_REQUIREMENTS'
Copy-Item $src ( Join-Path $one " LifePlay\Content\Modules\ $( $m . Name ) " ) -Recurse
$expected + = @ ( Get-ChildItem $src -Recurse -File ) . Count
$lines + = " - $title ( $( $m . Name ) ) $( if ( $req ) { " , requires / necessite : $req " } ) "
if ( $p . Members . Count -eq 1 ) { $p . Title = $title ; $p . Description = $desc }
}
$modulesDir = if ( $p . Members . Count -eq 1 ) { " LifePlay\Content\Modules\ $( $p . Members [ 0 ] . Name ) " } else { 'LifePlay\Content\Modules\<module>' }
$note = @ ( " $( $p . Title ) - LifePlay Revival $( $cfg . version ) " , '' , $p . Description , '' ) + $lines + @ ( '' ,
2026-10-03 08:43:53 +00:00
'EN: extract this archive into the game folder (the one containing LifePlay.exe), merging the LifePlay folder, then' ,
2026-10-03 09:19:44 +00:00
" enable the module(s) in `" Mod Manager `" (title screen). To remove one, delete its folder $modulesDir . " ,
2026-10-03 08:43:53 +00:00
'FR : extraire cette archive dans le dossier du jeu (celui de LifePlay.exe) en fusionnant le dossier LifePlay, puis' ,
2026-10-03 09:19:44 +00:00
" activer le ou les modules dans `" Mod Manager `" (ecran titre). Pour en retirer un : supprimer $modulesDir . " ,
2026-10-03 08:43:53 +00:00
'Adult content, adults only / contenu adulte, reserve aux adultes.' )
[ IO.File ] :: WriteAllText ( ( Join-Path $one 'README.txt' ) , ( $note -join " `r `n " ) + " `r `n " , ( New-Object Text . UTF8Encoding $false ) )
2026-10-03 09:19:44 +00:00
$zip = Join-Path $dir " $( $p . Name ) -for-LifePlay- $( $cfg . version ) .zip "
2026-10-03 08:43:53 +00:00
if ( Test-Path $zip ) { throw " L'archive existe deja : $zip " }
Push-Location $one
& $SevenZip a -tzip -mx = 7 $zip 'LifePlay' 'README.txt' | Out-Null
Pop-Location
& $SevenZip t $zip | Out-Null
if ( $LASTEXITCODE -ne 0 ) { throw " Test 7-Zip en echec : $zip " }
$n = ( & $SevenZip l -slt $zip | Select-String '^Folder = -' ) . Count - 1
2026-10-03 09:19:44 +00:00
if ( $n -ne $expected ) { throw " $( $p . Name ) : $n fichier(s) dans l'archive pour $expected " }
2026-10-03 08:43:53 +00:00
$zipped + = $n
$h = ( Get-FileHash $zip -Algorithm SHA256 ) . Hash . ToLower ( )
2026-10-03 09:19:44 +00:00
$index + = ( '{0} ({1}{2}) : {3}, {4:N0} bytes, SHA-256 {5}' -f $p . Title , $p . Name , $ ( if ( $p . Members . Count -gt 1 ) { ': ' + ( $p . Members . Name -join ', ' ) } else { '' } ) , ( Split-Path $zip -Leaf ) , ( Get-Item $zip ) . Length , $h )
Write-Host " $( $p . Name ) : $( Split-Path $zip -Leaf ) ( $( $p . Members . Count ) module(s), $n fichiers) "
2026-10-03 08:43:53 +00:00
}
[ IO.File ] :: WriteAllText ( ( Join-Path $dir 'INDEX.txt' ) , ( $index -join " `r `n " ) + " `r `n " , ( New-Object Text . UTF8Encoding $false ) )
$left = Get-ChildItem $modules -Directory | Where-Object { $_ . Name -in $optional . Name }
Write-Host " Modules gardes dans le build : $( $cfg . essential -join ', ' ) "
2026-10-03 09:19:44 +00:00
Write-Host " Modules optionnels : $( $optional . Count ) en $( $packages . Count ) archive(s) dans $dir ; fichiers deplaces $files , archives $zipped ; restes dans le build $( @ ( $left ) . Count ) "
2026-10-03 08:43:53 +00:00
if ( $zipped -ne $files -or @ ( $left ) . Count -ne 0 ) { throw 'Controle en echec : contenu des archives different des fichiers deplaces' }
return
}
2026-10-02 18:55:48 +00:00
$readme = @"
LifePlay Revival $ ( $cfg . version ) - modules optionnels / optional modules
FR : extraire cette archive dans le dossier du jeu ( celui qui contient LifePlay . exe ) , en fusionnant le dossier
LifePlay . Les modules apparaissent ensuite dans le gestionnaire de mods du jeu , ou ils s ' activent et se desactivent .
vin_NTR est desactive par defaut . Pour retirer un module : supprimer son dossier LifePlay \ Content \ Modules \ < module > .
EN : extract this archive into the game folder ( the one containing LifePlay . exe ) , merging the LifePlay folder . The
modules then appear in the in-game mod manager , where they can be enabled or disabled . vin_NTR is disabled by
default . To remove a module , delete its LifePlay \ Content \ Modules \ < module > folder .
Modules : $list
Contenu adulte , reserve aux adultes / adult content , adults only .
" @
Set-Content ( Join-Path $stage 'LISEZMOI_README.txt' ) $readme -Encoding utf8
New-Item -ItemType Directory -Force $Out | Out-Null
$zip = Join-Path $Out $cfg . zip
if ( Test-Path $zip ) { throw " L'archive existe deja : $zip " }
Push-Location $stage
& $SevenZip a -tzip -mx = 7 $zip 'LifePlay' 'LISEZMOI_README.txt' | Out-Null
Pop-Location
& $SevenZip t $zip | Out-Null
if ( $LASTEXITCODE -ne 0 ) { throw " Test 7-Zip en echec : $zip " }
$zipFiles = ( & $SevenZip l -slt $zip | Select-String '^Folder = -' ) . Count - 1 # moins la notice
$left = Get-ChildItem $modules -Directory | Where-Object { $_ . Name -in $optional . Name }
$hash = ( Get-FileHash $zip -Algorithm SHA256 ) . Hash . ToLower ( )
Write-Host " Modules gardes dans le build : $( $cfg . essential -join ', ' ) "
Write-Host " Modules optionnels : $( $optional . Count ) ( $list ) "
Write-Host " Fichiers deplaces : $files ( $bytes octets) ; dans l'archive : $zipFiles fichier(s) de module "
Write-Host " Restes dans le build : $( @ ( $left ) . Count ) "
Write-Host " Archive : $zip ( $( ( Get-Item $zip ) . Length ) octets), SHA-256 $hash "
if ( $zipFiles -ne $files -or @ ( $left ) . Count -ne 0 ) { throw 'Controle en echec : contenu de l''archive different des fichiers deplaces' }