11# RC3 orchestration; file writes and recovery use the RC2 journal primitives.
22function Get-AuroraExecutableEvidence ([string ]$Path ) {
3- $stream = $null ; $valid = $false ; $imports = @ ()
3+ $stream = $null ; $guard = $null ; $ valid= $false ; $imports = @ ()
44 try {
55 Assert-AuroraPlainPath $Path
6- $stream = [IO.File ]::Open($Path , ' Open' , ' Read' , ' ReadWrite' ); $reader = New-Object IO.BinaryReader($stream )
6+ $guard = Enter-AuroraPathGuard $Path
7+ $stream = [AuroraDirectoryGuard ]::Read((Get-AuroraPath $Path )); $reader = New-Object IO.BinaryReader($stream )
78 if ($stream.Length -lt 64 -or $reader.ReadUInt16 () -ne 0x5A4D ) { throw ' MZ' }
89 $stream.Position = 60 ; $offset = $reader.ReadInt32 ()
910 if ($offset -lt 64 -or $offset -gt $stream.Length - 264 ) { throw ' PE offset' }
@@ -41,7 +42,7 @@ function Get-AuroraExecutableEvidence([string]$Path) {
4142 if ($name -match ' ^(?i)(d3d(?:9|10|11|12)|dxgi|vulkan-1|opengl32|UnityPlayer)\.dll$' ) { $imports += $name }
4243 }
4344 }
44- } catch { } finally { if ($stream ) { $stream.Dispose () } }
45+ } catch { } finally { if ($stream ) { $stream.Dispose () }; if ( $guard ) { $guard .Dispose () } }
4546 [pscustomobject ]@ {Valid = $valid ;GraphicsImports = $imports }
4647}
4748function Test-AuroraUtilityPath ([string ]$RelativePath , [switch ]$Runtime ) {
@@ -264,22 +265,34 @@ function New-AuroraPayload([string]$PackageDir,[string]$InstallDir,[string]$Prox
264265
265266function Get-AuroraIndexPath ([string ]$Root ) { Join-Path $Root ' OptiScaler\AuroraSetup\AuroraInstallManifest.json' }
266267function Open-AuroraIndex ([string ]$Root ) {
268+ $Root = Get-AuroraMetadataPath $Root
267269 $path = Get-AuroraIndexPath $Root ; Assert-AuroraPlainPath $path
268270 if (-not (Test-Path - LiteralPath $path )) { return $null }
269- $index = Get-Content - LiteralPath $path - Raw - Encoding UTF8 | ConvertFrom-Json
270- if ($index.SchemaVersion -ne 3 -or (Get-AuroraPath $index.GameRoot ) -ine $Root -or $index.Status -notin @ (' Pending' , ' Applied' , ' Removed' , ' NeedsAttention' )) { throw ' RC3 总清单版本、目录或状态不匹配。' }
271+ $index = Read-AuroraJson $path
272+ Assert-AuroraFields $index @ (' SchemaVersion' , ' GameRoot' , ' Status' , ' Targets' , ' RuntimeOwners' , ' RuntimeGroups' )
273+ if ($index.SchemaVersion -isnot [int ] -or $index.Targets -isnot [array ] -or $index.Targets.Count -lt 1 -or $index.Targets.Count -gt 256 -or $index.RuntimeOwners -isnot [array ] -or $index.RuntimeGroups -isnot [array ]) { throw ' RC3 总清单字段类型或目标数量无效。' }
274+ if ($index.PSObject.Properties [' RuntimeJournalExpected' ] -and $index.RuntimeJournalExpected -isnot [bool ]) { throw ' Runtime 日志登记状态无效。' }
275+ if ($index.SchemaVersion -ne 3 -or (Get-AuroraMetadataPath $index.GameRoot ) -ine $Root -or $index.Status -notin @ (' Pending' , ' Applied' , ' Removed' , ' NeedsAttention' )) { throw ' RC3 总清单版本、目录或状态不匹配。' }
276+ $index.GameRoot = $Root
271277 $seen = @ {}
272278 foreach ($target in @ ($index.Targets )) {
273- $dir = Get-AuroraPath $target.Directory
279+ Assert-AuroraFields $target @ (' Directory' , ' Proxy' , ' JournalPath' , ' Executables' )
280+ if ($target.Executables -isnot [array ] -or -not $target.Executables.Count ) { throw ' 入口列表必须是非空数组。' }
281+ $dir = Get-AuroraMetadataPath $target.Directory ; $target.Directory = $dir
274282 if (-not (Test-AuroraWithin $dir $Root ) -or (Test-AuroraWithin $dir (Join-Path $Root ' OptiScaler' )) -or $seen.ContainsKey ($dir )) { throw ' RC3 清单入口重复或越界。' }
275283 $seen [$dir ]= $true ; Assert-AuroraPlainPath $dir
276284 if ($target.Proxy -notin @ (' dxgi.dll' , ' winmm.dll' , ' version.dll' , ' dbghelp.dll' , ' d3d12.dll' , ' wininet.dll' , ' winhttp.dll' , ' OptiScaler.asi' )) { throw ' RC3 清单 Proxy 无效。' }
285+ $target.JournalPath = Get-AuroraMetadataPath $target.JournalPath
277286 if ($target.JournalPath -ine (Join-Path $dir ' OptiScaler\AuroraSetup\manifest.json' )) { throw ' RC3 文件日志路径不匹配。' }
278- if ($index.Status -eq ' Applied' -and -not (Test-Path - LiteralPath $target.JournalPath - PathType Leaf)) { throw ' 部署文件日志缺失,停止操作,不能猜测文件归属。' }
279- foreach ($exe in @ ($target.Executables )) { if ([IO.Path ]::GetDirectoryName((Get-AuroraPath $exe )) -ine $dir ) { throw ' RC3 入口路径与目录不匹配。' } }
287+ if ($target.PSObject.Properties [' JournalExpected' ] -and $target.JournalExpected -isnot [bool ]) { throw ' 核心日志登记状态无效。' }
288+ if (($index.Status -eq ' Applied' -or ($target.PSObject.Properties [' JournalExpected' ] -and $target.JournalExpected )) -and -not (Test-Path - LiteralPath $target.JournalPath - PathType Leaf)) { throw ' 部署文件日志缺失,停止操作,不能猜测文件归属。' }
289+ foreach ($exe in @ ($target.Executables )) { if ([IO.Path ]::GetDirectoryName((Get-AuroraMetadataPath $exe )) -ine $dir ) { throw ' RC3 入口路径与目录不匹配。' } }
280290 }
291+ $owners = @ {}
281292 foreach ($dir in @ ($index.RuntimeOwners )) {
282- if (-not $seen.ContainsKey ((Get-AuroraPath $dir ))) { throw ' RC3 Runtime 日志未关联部署入口。' }
293+ if (-not $seen.ContainsKey ((Get-AuroraMetadataPath $dir ))) { throw ' RC3 Runtime 日志未关联部署入口。' }
294+ $owner = Get-AuroraMetadataPath $dir
295+ if ($owners.ContainsKey ($owner )) { throw ' Runtime 日志所有者重复。' }; $owners [$owner ]= $true
283296 }
284297 return $index
285298}
@@ -342,7 +355,7 @@ function Install-AuroraDeployment([string]$Root,[string]$PackageDir,[string]$Pro
342355 foreach ($candidate in $candidates ) { Assert-AuroraGameClosed $Root $candidate.Path }
343356 $lock = Enter-AuroraLock $Root
344357 $index = Open-AuroraIndex $Root
345- if (-not $index ) { $index = [pscustomobject ]@ {SchemaVersion = 3 ;GameRoot = $Root ;Status = ' Pending' ;Targets = @ ();RuntimeOwners = @ ();RuntimeGroups = @ ()} }
358+ if (-not $index ) { $index = [pscustomobject ]@ {SchemaVersion = 3 ;GameRoot = $Root ;Status = ' Pending' ;Targets = @ ();RuntimeOwners = @ ();RuntimeGroups = @ (); RuntimeJournalExpected = $false } }
346359 foreach ($group in @ ($candidates | Group-Object Directory)) {
347360 $dir = $group.Name
348361 $chosen = Get-AuroraRecommendedProxy @ ($group.Group ) $Proxy
@@ -365,6 +378,7 @@ function Install-AuroraDeployment([string]$Root,[string]$PackageDir,[string]$Pro
365378 $index.Targets += @ ($target )
366379 }
367380 $journal = Open-AuroraJournal $target.JournalPath $Root $dir
381+ $target | Add-Member NoteProperty JournalExpected (Test-Path - LiteralPath $target.JournalPath - PathType Leaf) - Force
368382 $payload = @ (New-AuroraPayload $PackageDir $dir $chosen )
369383 $prepared += @ ([pscustomobject ]@ {Target = $target ;Journal = $journal ;Payload = $payload })
370384 Assert-AuroraPayloadReady $target $journal $payload
@@ -391,6 +405,7 @@ function Install-AuroraDeployment([string]$Root,[string]$PackageDir,[string]$Pro
391405 # Create every payload before activating any new Proxy; each copy is journaled.
392406 foreach ($p in $prepared ) {
393407 Write-AuroraJson $p.Target.JournalPath $p.Journal
408+ $p.Target.JournalExpected = $true ; Write-AuroraJson (Get-AuroraIndexPath $Root ) $index
394409 Write-AuroraJson (Join-Path $p.Target.Directory ' OptiScaler\AuroraSetup\installation.json' ) ([pscustomobject ]@ {GameRoot = $Root ;GameExe = $p.Target.Executables [0 ];Proxy = $p.Target.Proxy ;RC3Manifest = (Get-AuroraIndexPath $Root )})
395410 foreach ($file in @ ($p.Payload | Select-Object - Skip 1 )) {
396411 Assert-AuroraPlannedFile $file
@@ -400,6 +415,11 @@ function Install-AuroraDeployment([string]$Root,[string]$PackageDir,[string]$Pro
400415 # One full-tree synchronization prevents different entries fighting over shared DLLs.
401416 # Recover pre-RC3 journals first only when explicitly requested by Remove/Restore;
402417 # multiple existing owners are kept read-only until that recovery is done.
418+ $runtimeJournalPath = Join-Path $index.RuntimeOwners [0 ] ' OptiScaler\RuntimeSync\manifest.json'
419+ $runtimeJournal = Open-AuroraJournal $runtimeJournalPath $Root $index.RuntimeOwners [0 ]
420+ Write-AuroraJson $runtimeJournalPath $runtimeJournal
421+ $index | Add-Member NoteProperty RuntimeJournalExpected $true - Force
422+ Write-AuroraJson (Get-AuroraIndexPath $Root ) $index
403423 Invoke-AuroraRuntimeTask Install $Root $index.RuntimeOwners [0 ] ([IO.Path ]::ChangeExtension($ReportPath , ' .log.txt' )) - CallerHasLock
404424 foreach ($p in $prepared ) {
405425 $file = $p.Payload [0 ]; Assert-AuroraPlannedFile $file
@@ -409,8 +429,13 @@ function Install-AuroraDeployment([string]$Root,[string]$PackageDir,[string]$Pro
409429 Save-AuroraInstallReport $index $ReportPath
410430 return $index
411431 } catch {
412- if ($started ) { $index.Status = ' NeedsAttention' ; Write-AuroraJson (Get-AuroraIndexPath $Root ) $index ; Save-AuroraInstallReport $index $ReportPath }
413- throw
432+ $originalFailure = $_
433+ if ($started ) {
434+ $index.Status = ' NeedsAttention'
435+ try { Write-AuroraJson (Get-AuroraIndexPath $Root ) $index ; Save-AuroraInstallReport $index $ReportPath }
436+ catch { Write-Warning ' 状态/报告写入也失败;保留原有 Pending 日志,不能视为安装成功。' }
437+ }
438+ throw $originalFailure
414439 } finally {
415440 if ($lock ) { $lock.Dispose () }
416441 foreach ($p in $prepared ) { foreach ($f in $p.Payload ) {
@@ -422,16 +447,37 @@ function Assert-AuroraPlannedFile($File) {
422447 $current = ' ' ; if (Test-Path - LiteralPath $File.Target ) { $current = Get-AuroraHash $File.Target }
423448 if ($current -ne $File.BeforeHash ) { throw " 文件在预检后变化,已停止:$ ( $File.Target ) " }
424449}
450+ function Assert-AuroraCoreJournalScope ($Journal ) {
451+ $names = @ (' OptiScaler.dll' , ' OptiScaler.ini' , ' dxgi.dll' , ' winmm.dll' , ' version.dll' , ' dbghelp.dll' , ' d3d12.dll' , ' wininet.dll' , ' winhttp.dll' , ' OptiScaler.asi' , ' nvngx.dll_dlssnr.dll' , ' LICENSE' , ' READ ME - DLSS Neural Rendering.txt' , ' Aurora_Common.ps1' , ' Aurora_Installer.ps1' , ' Aurora_RuntimeCatalog.ps1' , ' Aurora_Diagnostics.ps1' , ' Aurora_Setup.ps1' , ' Aurora_Setup_Legacy.ps1' , ' runtime_sync.ps1' , ' Check_DLSS_Runtime.bat' , ' Aurora_Setup.bat' , ' Remove_Aurora.bat' )
452+ foreach ($e in @ ($Journal.Entries )) {
453+ if (-not (Test-AuroraWithin $e.TargetPath $Journal.InstallDir )) { throw ' 核心日志目标不属于该入口目录。' }
454+ $rel = $e.TargetPath.Substring ($Journal.InstallDir.Length ).TrimStart(' \' )
455+ if ($rel -match ' (?i)(^|\\)(AuroraSetup|RuntimeSync|_storage[^\\]*|\.git)(\\|$)' ) { throw ' 核心日志不得操作其他恢复记录。' }
456+ if ($rel -notin $names -and $rel -notmatch ' (?i)^OptiScaler\\.+\.(dll|json|ini|txt|md|bin)$|^OptiScaler\\plugins\\OptiPatcher\.asi$|^Licenses\\[^\\]+$' ) { throw ' 核心日志指向非 Aurora payload 文件,保留现状。' }
457+ }
458+ }
425459function Remove-AuroraDeployment ($Index , [string ]$ReportPath , [switch ]$RuntimeOnly ) {
426- $root = $Index.GameRoot ; $lock = $null ; $preserved = @ ()
460+ $root = $Index.GameRoot ; $lock = $null ; $preserved = @ (); $started = $false
427461 try {
428462 foreach ($t in @ ($Index.Targets )) { foreach ($exe in @ ($t.Executables )) { Assert-AuroraGameClosed $root $exe } }
429463 $lock = Enter-AuroraLock $root
430464 $Index = Open-AuroraIndex $root
431465 if (-not $Index ) { throw ' RC3 总清单已变化,停止恢复。' }
432466 $journals = @ ()
433- foreach ($dir in @ ($Index.RuntimeOwners )) { $path = Join-Path $dir ' OptiScaler\RuntimeSync\manifest.json' ; $journals += @ ([pscustomobject ]@ {Path = $path ;Journal = (Open-AuroraJournal $path $root $dir );Runtime = $true }) }
467+ foreach ($dir in @ ($Index.RuntimeOwners )) {
468+ $path = Join-Path $dir ' OptiScaler\RuntimeSync\manifest.json'
469+ if (-not (Test-Path - LiteralPath $path ) -and (-not $Index.PSObject.Properties [' RuntimeJournalExpected' ] -or $Index.RuntimeJournalExpected )) { throw ' Runtime 日志缺失,不能证明原版恢复状态,保留文件。' }
470+ $journals += @ ([pscustomobject ]@ {Path = $path ;Journal = (Open-AuroraJournal $path $root $dir );Runtime = $true })
471+ }
434472 if (-not $RuntimeOnly ) { foreach ($t in @ ($Index.Targets )) { $journals += @ ([pscustomobject ]@ {Path = $t.JournalPath ;Journal = (Open-AuroraJournal $t.JournalPath $root $t.Directory );Runtime = $false }) } }
473+ $ownership = @ {}
474+ foreach ($pair in $journals ) {
475+ if (-not $pair.Runtime ) { Assert-AuroraCoreJournalScope $pair.Journal }
476+ foreach ($e in @ ($pair.Journal.Entries | Where-Object { $_.Status -ne ' Restored' })) {
477+ if ($ownership.ContainsKey ($e.TargetPath )) { throw ' 不同日志对同一路径声明冲突 ownership,未恢复或删除任何文件。' }
478+ $ownership [$e.TargetPath ]= $pair.Path
479+ }
480+ }
435481 # Validate every backup before removing any tool. Changed targets are individually retained.
436482 foreach ($pair in $journals ) {
437483 foreach ($e in @ ($pair.Journal.Entries )) {
@@ -443,19 +489,28 @@ function Remove-AuroraDeployment($Index,[string]$ReportPath,[switch]$RuntimeOnly
443489 if ($current -ne $e.OriginalHash -and -not $e.Created -and (-not $e.BackupPath -or (Get-AuroraHash $e.BackupPath ) -ne $e.OriginalHash )) { throw " 备份校验失败,尚未删除文件:$ ( $e.TargetPath ) " }
444490 }
445491 }
492+ $previousStatus = $Index.Status
493+ $Index.Status = ' Pending' ; Write-AuroraJson (Get-AuroraIndexPath $root ) $Index ; $started = $true
446494 foreach ($pair in $journals ) {
447495 Write-AuroraJson $pair.Path $pair.Journal
448496 $events = @ (Restore-AuroraJournal $pair.Journal $pair.Path 6>&1 )
449497 $failures = @ ($events | Where-Object { $_ -is [int ] })[-1 ]
450498 $events | Where-Object { $_ -isnot [int ] } | Out-File - LiteralPath ([IO.Path ]::ChangeExtension($ReportPath , ' .log.txt' )) - Encoding utf8 - Append
451499 if ($failures ) { throw ' 恢复过程中发现文件变化;保留剩余文件和恢复工具,请查看详细报告。' }
452500 }
453- if (-not $RuntimeOnly ) { $Index.Status = ' Removed' }
501+ if (-not $RuntimeOnly ) { $Index.Status = ' Removed' } else { $Index .Status = $previousStatus }
454502 if ($preserved.Count ) { $Index.Status = ' NeedsAttention' }
455503 # After explicit restoration, use a single owner on the next install.
456504 if ($RuntimeOnly -and -not $preserved.Count ) { $Index.RuntimeOwners = @ ($Index.Targets [0 ].Directory) }
457505 Write-AuroraJson (Get-AuroraIndexPath $root ) $Index
458506 Save-AuroraInstallReport $Index $ReportPath
459507 return @ ($preserved )
508+ } catch {
509+ $failure = $_
510+ if ($started ) {
511+ $Index.Status = ' NeedsAttention'
512+ try { Write-AuroraJson (Get-AuroraIndexPath $root ) $Index } catch { Write-Warning ' 恢复未完成,无法更新状态;请保留 Pending 清单与备份。' }
513+ }
514+ throw $failure
460515 } finally { if ($lock ) { $lock.Dispose () } }
461516}
0 commit comments