239 lines
7.8 KiB
PowerShell
239 lines
7.8 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[ValidateSet('127.0.0.1', '::1')]
|
|
[string] $PlayoutHost,
|
|
|
|
[Parameter(Mandatory)]
|
|
[ValidateRange(1, 65535)]
|
|
[int] $PlayoutPort,
|
|
|
|
[Parameter(Mandatory)]
|
|
[ValidatePattern('^[0-9A-Fa-f]{64}$')]
|
|
[string] $NativeSha256,
|
|
|
|
[Parameter(Mandatory)]
|
|
[ValidatePattern('^[0-9A-Fa-f]{64}$')]
|
|
[string] $InteropSha256,
|
|
|
|
[ValidateRange(0, 2147483647)]
|
|
[Nullable[int]] $OutputChannel = $null,
|
|
|
|
[switch] $Force,
|
|
|
|
[string] $ConfigurationDirectory = (
|
|
Join-Path $env:LOCALAPPDATA 'MBN_STOCK_WEBVIEW\Config')
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$requiredAuthorization = 'I_AUTHORIZE_LIVE_PROGRAM_OUTPUT_FOR_THIS_LAUNCH'
|
|
$sceneAllowlist = @(
|
|
'5001', '5006', '5011', '5016', '50160', '5023', '5024', '5025',
|
|
'5026', '5029', '5032', '5037', '5068', '5070', '5072', '5074',
|
|
'5076', '5077', '5078', '5079', '5080', '5081', '5082', '5083',
|
|
'5084', '5085', '5086', '50860', '5087', '5088', '6001', '6067',
|
|
'8001', '8002', '8003', '8018', '8032', '8035', '8040', '8046',
|
|
'8051', '8056', '8061', '8067', 'N5001'
|
|
)
|
|
|
|
function Assert-LocalAppDataPath {
|
|
param([Parameter(Mandatory)][string] $Path)
|
|
|
|
$localRoot = [IO.Path]::GetFullPath(
|
|
[Environment]::GetFolderPath(
|
|
[Environment+SpecialFolder]::LocalApplicationData))
|
|
$fullPath = [IO.Path]::GetFullPath($Path)
|
|
$rootPrefix = $localRoot.TrimEnd(
|
|
[IO.Path]::DirectorySeparatorChar,
|
|
[IO.Path]::AltDirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar
|
|
|
|
if (-not $fullPath.StartsWith(
|
|
$rootPrefix,
|
|
[StringComparison]::OrdinalIgnoreCase)) {
|
|
throw 'Development Live configuration must stay under LocalAppData.'
|
|
}
|
|
|
|
return $fullPath
|
|
}
|
|
|
|
function Assert-NoReparsePointChain {
|
|
param(
|
|
[Parameter(Mandatory)][string] $Directory,
|
|
[Parameter(Mandatory)][string] $StopDirectory
|
|
)
|
|
|
|
$current = [IO.Path]::GetFullPath($Directory)
|
|
$stop = [IO.Path]::GetFullPath($StopDirectory)
|
|
while (-not [string]::Equals(
|
|
$current,
|
|
$stop,
|
|
[StringComparison]::OrdinalIgnoreCase)) {
|
|
$item = Get-Item -LiteralPath $current -Force
|
|
if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
|
|
throw 'Development Live configuration directories cannot be reparse points.'
|
|
}
|
|
|
|
$parent = [IO.Path]::GetDirectoryName($current)
|
|
if ([string]::IsNullOrWhiteSpace($parent) -or
|
|
[string]::Equals(
|
|
$parent,
|
|
$current,
|
|
[StringComparison]::OrdinalIgnoreCase)) {
|
|
throw 'The LocalAppData configuration directory chain is invalid.'
|
|
}
|
|
|
|
$current = $parent
|
|
}
|
|
}
|
|
|
|
function Assert-WritableTarget {
|
|
param([Parameter(Mandatory)][string] $Path)
|
|
|
|
if (-not (Test-Path -LiteralPath $Path)) {
|
|
return
|
|
}
|
|
|
|
$item = Get-Item -LiteralPath $Path -Force
|
|
if ($item.PSIsContainer -or
|
|
($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
|
|
throw 'Development Live configuration targets must be ordinary files.'
|
|
}
|
|
|
|
if (-not $Force) {
|
|
throw "Configuration already exists. Review it and rerun with -Force: $Path"
|
|
}
|
|
}
|
|
|
|
function Set-CurrentUserOnlyAcl {
|
|
param([Parameter(Mandatory)][string] $Path)
|
|
|
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
$acl = [Security.AccessControl.FileSecurity]::new()
|
|
$acl.SetAccessRuleProtection($true, $false)
|
|
$rule = [Security.AccessControl.FileSystemAccessRule]::new(
|
|
$identity,
|
|
[Security.AccessControl.FileSystemRights]::FullControl,
|
|
[Security.AccessControl.AccessControlType]::Allow)
|
|
$acl.AddAccessRule($rule)
|
|
[IO.File]::SetAccessControl($Path, $acl)
|
|
}
|
|
|
|
function Invalidate-ExistingAuthorization {
|
|
param([Parameter(Mandatory)][string] $Path)
|
|
|
|
if (-not [IO.File]::Exists($Path)) {
|
|
return
|
|
}
|
|
|
|
$invalidatedPath = (
|
|
$Path + '.invalidated.' + [Guid]::NewGuid().ToString('N'))
|
|
[IO.File]::Move($Path, $invalidatedPath)
|
|
try {
|
|
Set-CurrentUserOnlyAcl $invalidatedPath
|
|
[IO.File]::Delete($invalidatedPath)
|
|
}
|
|
catch {
|
|
throw (
|
|
'The previous Development Live authorization was invalidated but ' +
|
|
"could not be removed. Remove this protected file before retrying: $invalidatedPath")
|
|
}
|
|
|
|
if ([IO.File]::Exists($Path)) {
|
|
throw 'The previous Development Live authorization could not be invalidated.'
|
|
}
|
|
}
|
|
|
|
function Write-ProtectedJson {
|
|
param(
|
|
[Parameter(Mandatory)][string] $Path,
|
|
[Parameter(Mandatory)][string] $Json
|
|
)
|
|
|
|
$temporaryPath = "$Path.tmp.$([Guid]::NewGuid().ToString('N'))"
|
|
try {
|
|
[IO.File]::WriteAllText(
|
|
$temporaryPath,
|
|
$Json,
|
|
[Text.UTF8Encoding]::new($false))
|
|
Set-CurrentUserOnlyAcl $temporaryPath
|
|
Move-Item -LiteralPath $temporaryPath -Destination $Path -Force
|
|
}
|
|
finally {
|
|
if ([IO.File]::Exists($temporaryPath)) {
|
|
Remove-Item -LiteralPath $temporaryPath -Force
|
|
}
|
|
}
|
|
}
|
|
|
|
$configurationRoot = Assert-LocalAppDataPath $ConfigurationDirectory
|
|
[IO.Directory]::CreateDirectory($configurationRoot) | Out-Null
|
|
$localApplicationData = [IO.Path]::GetFullPath(
|
|
[Environment]::GetFolderPath(
|
|
[Environment+SpecialFolder]::LocalApplicationData))
|
|
Assert-NoReparsePointChain $configurationRoot $localApplicationData
|
|
|
|
$playoutPath = Join-Path $configurationRoot 'playout.local.json'
|
|
$authorizationPath = Join-Path $configurationRoot 'playout.development-live.local.json'
|
|
Assert-WritableTarget $playoutPath
|
|
Assert-WritableTarget $authorizationPath
|
|
|
|
$playout = [ordered]@{
|
|
mode = 'DryRun'
|
|
host = $PlayoutHost
|
|
port = $PlayoutPort
|
|
tcpMode = 1
|
|
clientPort = 0
|
|
sceneDirectory = $null
|
|
outputChannel = $OutputChannel
|
|
layoutIndex = 10
|
|
legacySceneFadeDuration = 6
|
|
legacySceneBackgroundKind = 'None'
|
|
legacySceneBackgroundAssetPath = $null
|
|
legacySceneBackgroundVideoLoopCount = 2004
|
|
legacySceneBackgroundVideoLoopInfinite = $true
|
|
legacyBackgroundDirectory = $null
|
|
testProcessWindowTitlePattern = $null
|
|
testSceneAllowlist = $sceneAllowlist
|
|
trustedLiveOutputEnabled = $true
|
|
queueCapacity = 64
|
|
connectTimeoutMilliseconds = 5000
|
|
operationTimeoutMilliseconds = 5000
|
|
disconnectTimeoutMilliseconds = 3000
|
|
processPollIntervalMilliseconds = 1000
|
|
reconnectDelayMilliseconds = 1000
|
|
maximumReconnectAttempts = 0
|
|
reconnectEnabled = $false
|
|
maximumAutomaticRefreshesPerTakeIn = 0
|
|
}
|
|
|
|
$authorization = [ordered]@{
|
|
schemaVersion = 1
|
|
mode = 'Live'
|
|
authorization = $requiredAuthorization
|
|
nativeSha256 = $NativeSha256.ToUpperInvariant()
|
|
interopSha256 = $InteropSha256.ToUpperInvariant()
|
|
}
|
|
|
|
$playoutJson = $playout | ConvertTo-Json -Depth 6
|
|
$authorizationJson = $authorization | ConvertTo-Json -Depth 3
|
|
|
|
# In Force mode, invalidate the old authorization before changing the DryRun
|
|
# base. Any later failure therefore leaves no valid authorization at the exact
|
|
# path consumed by the Debug-only bootstrap.
|
|
Invalidate-ExistingAuthorization -Path $authorizationPath
|
|
|
|
# Write the safe DryRun base first and the new one-launch authorization last.
|
|
# Merely creating these files does not connect: Debug, the exact
|
|
# --development-live argument, vendor hash verification and runtime gates remain
|
|
# mandatory in the application.
|
|
Write-ProtectedJson $playoutPath $playoutJson
|
|
Write-ProtectedJson $authorizationPath $authorizationJson
|
|
|
|
Write-Host "Development Live base configuration created at: $playoutPath"
|
|
Write-Host "Development Live launch authorization created at: $authorizationPath"
|
|
Write-Warning (
|
|
'The base file remains DryRun. Use only Debug|x64 with the exact ' +
|
|
'Development Live (Package) profile after confirming the local development PGM target.')
|