Skip to content

Instantly share code, notes, and snippets.

@stungeye
Last active October 9, 2026 15:32
Show Gist options
  • Select an option

  • Save stungeye/d0abb2df510334ddf734304a51fbf242 to your computer and use it in GitHub Desktop.

Select an option

Save stungeye/d0abb2df510334ddf734304a51fbf242 to your computer and use it in GitHub Desktop.
Unofficial UE 5.8.x Clean-install Metadata Finalization Workaround

UE 5.8.x Launcher stuck on Resume after a fresh installation

Unofficial, community-provided workaround. Not affiliated with or endorsed by Epic Games.

This PowerShell script addresses a narrow clean-install finalization failure: Unreal Engine 5.8.x has already downloaded, BuildPatch/BPS finished successfully, and the editor works when launched directly, but Epic Games Launcher displays Resume because it never committed the installation metadata.

It is based on one successful UE 5.8.3 recovery. The generalized version has not yet been tested on additional machines.

When to use

You must have all of these:

  • Epic Launcher gets stuck at Resume after an apparently completed UE 5.8.x fresh install (not an update).
  • For me install failed with the following messages:
    • Install failed at 100%
    • Error Codes: IS-EO-ih_early_exit
  • InstallHelper logs independently show a successful BPS/BuildPatch operation (e.g. BPS installation complete, processSuccess: true, errorCode: OK), before the helper stalls/exits.
  • You can launch the installed Engine\Binaries\Win64\UnrealEditor.exe directly. Close the editor before running the script.
  • One matching pending UE_5.8 .item exists, and there is no already-completed UE_5.8 item or LauncherInstalled.dat registration.
  • .egi uses the known v4 installation-helper schema and remains Incomplete.

Do not use if the download/verification failed, required engine files are missing, or an older installed UE_5.8 registration already exists. For existing-installation updates, use an update-specific approach instead; see the Epic forum thread.

How to run

Close Epic Games Launcher completely (including its tray icon), any editor windows, and helper processes.

Run a diagnostic first in PowerShell (no administrator privileges required):

.\UE58-CleanInstall-Metadata-Repair.ps1

The default mode is read-only. A successful result will say DRY RUN PASSED and show the discovered install location, build, ID, and pending manifest hash. This does not prove the engine downloaded correctly; review the InstallHelper logs yourself.

If and only if all prerequisites above are met, start PowerShell as administrator and run:

.\UE58-CleanInstall-Metadata-Repair.ps1 -Apply

The script makes a new verified backup on the Desktop and asks you to type both VERIFIED (you independently confirmed the completed download and direct editor launch) and APPLY before changing anything.

If PowerShell blocks execution of the script because of local execution policy, and your system policy permits it, you can allow script execution for that PowerShell session only:

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

Only run scripts you understand and trust; do not override an organization-managed execution policy.

What it discovers and checks

  • The one pending UE_5.8 .item and its installation GUID (no hard-coded GUID)
  • Actual installation directory, including custom install drives
  • UE 5.8.x patch number and changelist from the pending version, matching Build.version and UnrealEditor.exe
  • Matching .egi v4 installation ID, version, revision, catalog identity, manifest paths and size
  • A pending manifest exists; no complete manifest or .item already exists
  • LauncherInstalled.dat has no UE_5.8 registration; no older completed UE_5.8 metadata exists
  • Launcher/helper/editor processes are closed

The script does not independently validate the roughly 30 GB of engine contents against Epic's binary BuildPatch manifest. This is why log evidence and direct editor launch are required.

What Apply changes

With verification, explicit confirmation, and backups, it:

  1. Copies the existing UE 5.8 pending .manifest into the complete .egstore location and verifies SHA-256.
  2. Changes only that installation's .egi state and metadata from pending to installed.
  3. Creates the completed .item from the corresponding pending .item.
  4. Adds the matching UE_5.8 record to LauncherInstalled.dat, preserving unrelated entries.
  5. Validates the records, then removes only this installation's pending .item and manifest.

No engine executable, source, or content files are rewritten. On an error after changes begin, the script attempts to restore original metadata from the fresh backup. Automatic rollback cannot be guaranteed if permissions/files/processes interfere.

Backups and recovery

Every Apply attempt produces UE58_MetadataBackup_<timestamp>_<suffix> on the Desktop. It contains the pending .item, .egi, original LauncherInstalled.dat, pending .manifest, Build.version, and RESTORE_PATHS.txt identifying the exact original file locations.

Keep these backups. If you need to undo an apparently successful repair, do not restore blindly after another engine update. Close the launcher, compare current records with those backups and seek help before reverting. Reverting the completed records will put the Launcher back into its prior pending state.

Limitations

  • Only supports the observed UE 5.8.x clean-install pending metadata shape on Windows.
  • Intentionally refuses existing-installation updates, multiple pending transactions, mismatched versions/IDs, unknown helper schemas, and existing completed metadata.
  • A passed dry run confirms metadata consistency, not that engine files have all downloaded.
  • An Epic Launcher or helper update may alter these formats. A successful metadata write does not guarantee the Launcher accepts the result.
  • Do not post your .egi file or metadata backups publicly without inspecting/redacting them: they can contain signed download URLs and other installation details.
<#
.SYNOPSIS
Unofficial UE 5.8.x clean-install metadata finalization workaround.
.DESCRIPTION
For ONE narrow situation: UE 5.8.x was completely downloaded and verified,
UnrealEditor.exe starts directly, but Epic Launcher stays at Resume because
the pending .item/.egi/.manifest was never committed and there is NO
UE_5.8 registration in LauncherInstalled.dat.
Discovers the installation ID, install location, and build from existing
metadata. No installation-specific identifiers are embedded in this script.
Runs a READ-ONLY diagnostic by default. -Apply requires administrator,
fresh backup, confirmation of independent file verification, and APPLY.
Not for upgrades where an older completed UE_5.8 record already exists,
partially downloaded/failed installs, other UE releases, or other .egi
formats. Not an official Epic Games fix. Test carefully at your own risk.
.EXAMPLE
.\UE58-CleanInstall-Metadata-Repair.ps1
.EXAMPLE
.\UE58-CleanInstall-Metadata-Repair.ps1 -Apply
#>
[CmdletBinding()]
param([switch]$Apply)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
$AppName = 'UE_5.8'
$Utf8 = New-Object System.Text.UTF8Encoding($false)
$ProgramDataPath = [Environment]::GetFolderPath('CommonApplicationData')
$ManifestRoot = Join-Path $ProgramDataPath 'Epic\EpicGamesLauncher\Data\Manifests'
$PendingRoot = Join-Path $ManifestRoot 'Pending'
$ItemsRoot = Join-Path $ProgramDataPath 'Epic\EpicOnlineServicesShared\InstallHelper\InstalledItems'
$LauncherDatPath = Join-Path $ProgramDataPath 'Epic\UnrealEngineLauncher\LauncherInstalled.dat'
function Assert-Condition([bool]$Condition, [string]$Reason) {
if (-not $Condition) { throw ('STOP: ' + $Reason) }
}
function Read-JsonFile([string]$Path) {
Assert-Condition (Test-Path -LiteralPath $Path -PathType Leaf) "File not found: $Path"
return (Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json)
}
function CanonicalPath([string]$Path) {
Assert-Condition (-not [string]::IsNullOrWhiteSpace($Path)) 'An installation path is blank.'
return [System.IO.Path]::GetFullPath($Path).TrimEnd([char]'\')
}
function PathsMatch([string]$Left, [string]$Right) {
return ((CanonicalPath $Left) -ieq (CanonicalPath $Right))
}
function EnsureClosed {
$Open = @(Get-Process -Name EpicGamesLauncher,EpicWebHelper,EpicOnlineServicesInstallHelper,InstallHelper,UnrealEditor -ErrorAction SilentlyContinue)
Assert-Condition ($Open.Count -eq 0) ('Close these processes before continuing: ' + (($Open | Select-Object -ExpandProperty ProcessName -Unique) -join ', '))
}
function Write-JsonAtomic([string]$Path, $Object) {
# A temporary file on the same volume avoids leaving partly-written JSON.
$Temp = "$Path.$([guid]::NewGuid().ToString('N')).tmp"
try {
$Json = ConvertTo-Json -InputObject $Object -Depth 40
[System.IO.File]::WriteAllText($Temp, $Json + "`r`n", $Utf8)
if (Test-Path -LiteralPath $Path) {
[System.IO.File]::Replace($Temp, $Path, $null)
} else {
[System.IO.File]::Move($Temp, $Path)
}
}
finally {
if (Test-Path -LiteralPath $Temp) { Remove-Item -LiteralPath $Temp -Force -ErrorAction SilentlyContinue }
}
}
function VerifyBackup([string]$Original, [string]$Copy) {
Copy-Item -LiteralPath $Original -Destination $Copy -ErrorAction Stop
$Before = (Get-FileHash -LiteralPath $Original -Algorithm SHA256).Hash
$After = (Get-FileHash -LiteralPath $Copy -Algorithm SHA256).Hash
Assert-Condition ($Before -eq $After) "Backup verification failed for $Original"
}
function Read-InstallationState {
EnsureClosed
Assert-Condition (Test-Path -LiteralPath $PendingRoot -PathType Container) 'Epic pending manifest folder not found.'
Assert-Condition (Test-Path -LiteralPath $ItemsRoot -PathType Container) 'Epic InstallHelper database folder not found.'
# Do not guess when multiple pending UE_5.8 transactions are present.
$Matches = @(
Get-ChildItem -LiteralPath $PendingRoot -Filter '*.item' -File | ForEach-Object {
$CandidatePath = $_.FullName
try {
$Candidate = Read-JsonFile $CandidatePath
if ($Candidate.AppName -eq $AppName) {
[PSCustomObject]@{ Path = $CandidatePath; Data = $Candidate }
}
}
catch {
# Other apps' .item files are irrelevant.
}
}
)
Assert-Condition ($Matches.Count -eq 1) "Expected exactly one pending $AppName .item, found $($Matches.Count). This tool cannot handle an already committed install or ambiguous transactions."
$ItemPath = $Matches[0].Path
$Item = $Matches[0].Data
$Id = [string]$Item.InstallationGuid
Assert-Condition ($Id -cmatch '^[A-Fa-f0-9]{32}$') 'Unexpected installation GUID format.'
Assert-Condition (([IO.Path]::GetFileNameWithoutExtension($ItemPath)) -ieq $Id) 'Pending .item filename and installation GUID disagree.'
Assert-Condition ($Item.AppName -eq $AppName) 'Pending .item app mismatch.'
Assert-Condition ($Item.bIsIncompleteInstall -eq $true) 'Pending .item is not marked incomplete.'
Assert-Condition ($Item.CatalogNamespace -eq 'ue') 'Unexpected Epic catalog namespace.'
Assert-Condition ($Item.CatalogItemId -cmatch '^[A-Fa-f0-9]{32}$') 'Unexpected Epic catalog item ID.'
Assert-Condition ($Item.EoshRevision -cmatch '^[A-Fa-f0-9]{32}$') 'Unexpected helper revision format.'
Assert-Condition ($Item.PrereqSHA1Hash -match '^[A-Fa-f0-9]{40}$') 'Unexpected prerequisite hash format.'
Assert-Condition ($Item.LaunchExecutable -eq 'Engine/Binaries/Win64/UnrealEditor.exe') 'Unexpected editor executable path.'
# Only UE 5.8.x build/version strings in the known Windows format.
$Version = [string]$Item.AppVersionString
$VersionMatch = [regex]::Match($Version, '^5\.8\.(?<patch>\d+)-(?<cl>\d+)\+\+\+UE5\+Release-5\.8-Windows$')
Assert-Condition $VersionMatch.Success "Not a recognized UE 5.8.x Windows version: $Version"
$Patch = [int]$VersionMatch.Groups['patch'].Value
$CL = [int64]$VersionMatch.Groups['cl'].Value
$EngineDir = CanonicalPath ([string]$Item.InstallLocation)
$EgStore = Join-Path $EngineDir '.egstore'
$PendingManifestPath = Join-Path (Join-Path $EgStore 'Pending') ($Id + '.manifest')
$CompleteManifestPath = Join-Path $EgStore ($Id + '.manifest')
$CompleteItemPath = Join-Path $ManifestRoot ($Id + '.item')
$EgiPath = Join-Path $ItemsRoot ($Id + '.egi')
$BuildPath = Join-Path $EngineDir 'Engine\Build\Build.version'
$EditorPath = Join-Path $EngineDir 'Engine\Binaries\Win64\UnrealEditor.exe'
Assert-Condition (PathsMatch $Item.ManifestLocation $EgStore) 'Unexpected .item manifest directory.'
Assert-Condition (PathsMatch $Item.PendingManifestPath $PendingManifestPath) 'Unexpected pending manifest path in .item.'
Assert-Condition (PathsMatch $Item.CompleteManifestPath $CompleteManifestPath) 'Unexpected completed manifest path in .item.'
Assert-Condition (-not (Test-Path -LiteralPath $CompleteItemPath)) 'Completed .item already exists; this is not the clean-install case.'
Assert-Condition (-not (Test-Path -LiteralPath $CompleteManifestPath)) 'Completed manifest already exists; this is not the clean-install case.'
Assert-Condition (Test-Path -LiteralPath $PendingManifestPath -PathType Leaf) 'Pending engine manifest missing.'
Assert-Condition (Test-Path -LiteralPath $EditorPath -PathType Leaf) 'UnrealEditor.exe missing.'
$Build = Read-JsonFile $BuildPath
Assert-Condition ([int]$Build.MajorVersion -eq 5 -and [int]$Build.MinorVersion -eq 8 -and [int]$Build.PatchVersion -eq $Patch) 'Physical Engine Build.version does not match pending version.'
Assert-Condition ([int64]$Build.Changelist -eq $CL) 'Physical Engine changelist does not match pending version.'
$ExeVersion = (Get-Item -LiteralPath $EditorPath).VersionInfo
Assert-Condition ($ExeVersion.ProductVersion -match ('CL-' + $CL + '(\D|$)')) 'UnrealEditor.exe changelist does not match pending version.'
if ($ExeVersion.FileMajorPart -gt 0) {
Assert-Condition ($ExeVersion.FileMajorPart -eq 5 -and $ExeVersion.FileMinorPart -eq 8 -and $ExeVersion.FileBuildPart -eq $Patch) 'UnrealEditor.exe numeric version does not match pending version.'
}
$Egi = Read-JsonFile $EgiPath
Assert-Condition ($null -ne $Egi.v4) 'Unsupported helper .egi schema (expected v4).'
Assert-Condition ($Egi.v4.installationId -ieq $Id) '.egi installation GUID mismatch.'
Assert-Condition ($Egi.v4.state -eq 'Incomplete') '.egi is not in the expected Incomplete state.'
Assert-Condition ($Egi.v4.artifactId -eq $AppName) '.egi artifact mismatch.'
Assert-Condition ($Egi.v4.platform -eq 'Windows') '.egi platform mismatch.'
Assert-Condition ($Egi.v4.sandboxId -eq $Item.CatalogNamespace) '.egi namespace mismatch.'
Assert-Condition ($Egi.v4.itemId -eq $Item.CatalogItemId) '.egi catalog item mismatch.'
Assert-Condition ($Egi.v4.revision -eq $Item.EoshRevision) '.egi helper revision mismatch.'
Assert-Condition (PathsMatch $Egi.v4.dir $EngineDir) '.egi install directory mismatch.'
Assert-Condition (PathsMatch $Egi.v4.metaDir $EgStore) '.egi metadata directory mismatch.'
Assert-Condition (PathsMatch $Egi.v4.pendingManifestPath $PendingManifestPath) '.egi pending manifest path mismatch.'
Assert-Condition (PathsMatch $Egi.v4.manifestPath $CompleteManifestPath) '.egi completed manifest path mismatch.'
Assert-Condition ($Egi.v4.pendingManifestData.version -eq $Version) '.egi pending version mismatch.'
Assert-Condition ($Egi.v4.manifestData.version -eq '') '.egi already has a completed manifest version.'
Assert-Condition ($Egi.v4.pendingManifestData.prereqSHA1Hash -eq $Item.PrereqSHA1Hash) '.egi prerequisite hash mismatch.'
Assert-Condition ($Egi.v4.pendingManifestData.launchExe -eq $Item.LaunchExecutable) '.egi editor path mismatch.'
Assert-Condition ([int64]$Egi.v4.pendingManifestData.buildSize -eq [int64]$Item.InstallSize) '.egi build size mismatch.'
Assert-Condition ([int64]$Item.InstallSize -gt 1000000000) 'Reported engine installation size is unexpectedly small.'
Assert-Condition (@($Egi.v4.pendingTags).Count -gt 0) 'No helper pending tags; unexpected state.'
Assert-Condition (@($Egi.v4.tags).Count -eq 0) 'Helper already has installed tags.'
$LauncherDat = Read-JsonFile $LauncherDatPath
Assert-Condition ($null -ne $LauncherDat.InstallationList) 'LauncherInstalled.dat lacks InstallationList.'
Assert-Condition (@($LauncherDat.InstallationList | Where-Object { $_.AppName -eq $AppName }).Count -eq 0) 'LauncherInstalled.dat already has UE_5.8 registration; use a different workaround.'
# A completed UE_5.8 item with some OTHER GUID is also a different case.
$OtherCompleted = @(
Get-ChildItem -LiteralPath $ManifestRoot -Filter '*.item' -File | ForEach-Object {
try {
$Candidate = Read-JsonFile $_.FullName
if ($Candidate.AppName -eq $AppName) { $_.FullName }
}
catch { }
}
)
Assert-Condition ($OtherCompleted.Count -eq 0) 'An existing completed UE_5.8 item was found; refusing to overwrite an older installation.'
$ManifestLength = (Get-Item -LiteralPath $PendingManifestPath).Length
Assert-Condition ($ManifestLength -gt 1000000) 'Pending manifest is unusually small.'
$ManifestHash = (Get-FileHash -LiteralPath $PendingManifestPath -Algorithm SHA256).Hash
return [PSCustomObject]@{
AppName = $AppName; Version = $Version; CL = $CL; Patch = $Patch
Id = $Id; EngineDir = $EngineDir; EgStore = $EgStore
ItemPath = $ItemPath; CompleteItemPath = $CompleteItemPath
EgiPath = $EgiPath; LauncherDatPath = $LauncherDatPath
PendingManifestPath = $PendingManifestPath; CompleteManifestPath = $CompleteManifestPath
BuildPath = $BuildPath; ManifestLength = $ManifestLength; ManifestHash = $ManifestHash
Item = $Item; Egi = $Egi; LauncherDat = $LauncherDat
}
}
Write-Host ''
Write-Host 'UE 5.8.x CLEAN-INSTALL METADATA WORKAROUND (unofficial)'
Write-Host ('Mode: ' + $(if ($Apply) { 'APPLY / confirmation required' } else { 'READ-ONLY / DRY RUN' }))
Write-Host ''
$State = Read-InstallationState
Write-Host ('Installation: ' + $State.EngineDir)
Write-Host ('Version: ' + $State.Version)
Write-Host ('GUID: ' + $State.Id)
Write-Host ('Manifest: {0:N0} bytes' -f $State.ManifestLength)
Write-Host ('SHA256: ' + $State.ManifestHash)
Write-Host ''
Write-Host 'MATCH: One uncommitted UE_5.8 installation; matching .item/.egi records;'
Write-Host ' engine executable and Build.version match; no completed registration.'
Write-Host 'NOTE: These checks DO NOT verify every engine file. Inspect InstallHelper logs'
Write-Host ' for successful BuildPatch/BPS completion, and launch UnrealEditor.exe'
Write-Host ' directly before deciding to apply. Do NOT apply to partial downloads.'
Write-Host ''
Write-Host 'Would commit pending manifest, .item and .egi, register UE_5.8, and remove'
Write-Host 'ONLY the corresponding pending .item and manifest. No Engine binaries change.'
if (-not $Apply) {
Write-Host ''
Write-Host 'DRY RUN PASSED. Nothing was changed.' -ForegroundColor Green
return
}
$Identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$Principal = New-Object Security.Principal.WindowsPrincipal($Identity)
Assert-Condition ($Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) 'Apply mode requires an administrator PowerShell window.'
$BackupRoot = [Environment]::GetFolderPath('Desktop')
Assert-Condition (-not [string]::IsNullOrWhiteSpace($BackupRoot)) 'Could not locate Desktop backup folder.'
$BackupDir = Join-Path $BackupRoot ("UE58_MetadataBackup_{0}_{1}" -f (Get-Date -Format 'yyyyMMdd_HHmmss'), ([guid]::NewGuid().ToString('N').Substring(0,8)))
New-Item -ItemType Directory -Path $BackupDir -ErrorAction Stop | Out-Null
VerifyBackup $State.ItemPath (Join-Path $BackupDir 'OriginalPending.item')
VerifyBackup $State.EgiPath (Join-Path $BackupDir 'Original.egi')
VerifyBackup $State.LauncherDatPath (Join-Path $BackupDir 'OriginalLauncherInstalled.dat')
VerifyBackup $State.PendingManifestPath (Join-Path $BackupDir 'OriginalPending.manifest')
VerifyBackup $State.BuildPath (Join-Path $BackupDir 'OriginalBuild.version')
# Keep exact restore paths in the backup, for inspection / manual recovery.
@(
'ORIGINAL FILES (keep this entire folder; Launcher must be closed to restore):'
"OriginalPending.item => $($State.ItemPath)"
"Original.egi => $($State.EgiPath)"
"OriginalLauncherInstalled.dat => $($State.LauncherDatPath)"
"OriginalPending.manifest => $($State.PendingManifestPath)"
"OriginalBuild.version => $($State.BuildPath) (reference only; never changed)"
''
'The script creates these new files, absent at start:'
"COMPLETED .item => $($State.CompleteItemPath)"
"COMPLETED manifest => $($State.CompleteManifestPath)"
''
'Do not manually restore this backup after a later update without reviewing changed state.'
) | Set-Content -LiteralPath (Join-Path $BackupDir 'RESTORE_PATHS.txt') -Encoding UTF8
Write-Host ''
Write-Host ('Verified backup created: ' + $BackupDir) -ForegroundColor Yellow
Write-Host 'WARNING: The script cannot itself prove a complete BuildPatch installation.' -ForegroundColor Yellow
$Verified = Read-Host 'If InstallHelper log confirms BPS success AND the editor opens directly, type VERIFIED'
if ($Verified -cne 'VERIFIED') { Write-Host 'Cancelled. Installation untouched.'; return }
$Confirmation = Read-Host 'Type APPLY to commit the metadata, anything else to cancel'
if ($Confirmation -cne 'APPLY') { Write-Host 'Cancelled. Installation untouched.'; return }
$Final = Read-InstallationState
Assert-Condition ($Final.Id -eq $State.Id -and $Final.Version -eq $State.Version -and $Final.ManifestHash -eq $State.ManifestHash) 'Metadata or manifest changed while waiting; refusing to apply.'
# Detect edits that occurred after our backup, including edits to helper metadata.
$RecheckSources = @(
[PSCustomObject]@{ Original = $Final.ItemPath; Backup = (Join-Path $BackupDir 'OriginalPending.item') }
[PSCustomObject]@{ Original = $Final.EgiPath; Backup = (Join-Path $BackupDir 'Original.egi') }
[PSCustomObject]@{ Original = $Final.LauncherDatPath; Backup = (Join-Path $BackupDir 'OriginalLauncherInstalled.dat') }
[PSCustomObject]@{ Original = $Final.PendingManifestPath; Backup = (Join-Path $BackupDir 'OriginalPending.manifest') }
[PSCustomObject]@{ Original = $Final.BuildPath; Backup = (Join-Path $BackupDir 'OriginalBuild.version') }
)
foreach ($Pair in $RecheckSources) {
$LiveHash = (Get-FileHash -LiteralPath $Pair.Original -Algorithm SHA256).Hash
$BackupHash = (Get-FileHash -LiteralPath $Pair.Backup -Algorithm SHA256).Hash
Assert-Condition ($LiveHash -eq $BackupHash) ("File changed after backup: " + $Pair.Original)
}
EnsureClosed
$ChangesStarted = $false
try {
$ChangesStarted = $true
Copy-Item -LiteralPath $Final.PendingManifestPath -Destination $Final.CompleteManifestPath -ErrorAction Stop
Assert-Condition ((Get-FileHash -LiteralPath $Final.CompleteManifestPath -Algorithm SHA256).Hash -eq $Final.ManifestHash) 'Completed manifest copy failed SHA256 check.'
$Egi = $Final.Egi
$Egi.v4.state = 'Installed'
$Egi.v4.tags = @($Egi.v4.pendingTags)
$Egi.v4.pendingTags = @()
$Egi.v4.manifestData = $Egi.v4.pendingManifestData
$Egi.v4.pendingManifestData = [PSCustomObject]@{
version = ''; launchCommand = ''; launchExe = ''; prereqSHA1Hash = ''; buildSize = 0
}
Write-JsonAtomic $Final.EgiPath $Egi
$Item = $Final.Item
$Item.bIsIncompleteInstall = $false
$Item.InstallTags = @($Egi.v4.tags)
Write-JsonAtomic $Final.CompleteItemPath $Item
$Registration = [PSCustomObject]@{
InstallLocation = $Final.EngineDir
NamespaceId = 'ue'
ItemId = $Item.CatalogItemId
ArtifactId = $Final.AppName
AppVersion = $Final.Version
AppName = $Final.AppName
}
$LauncherDat = $Final.LauncherDat
$LauncherDat.InstallationList = @($LauncherDat.InstallationList) + @($Registration)
Write-JsonAtomic $Final.LauncherDatPath $LauncherDat
# Commit checks before removing the original pending files.
$NewItem = Read-JsonFile $Final.CompleteItemPath
$NewEgi = Read-JsonFile $Final.EgiPath
$NewLauncher = Read-JsonFile $Final.LauncherDatPath
$NewRegistrations = @($NewLauncher.InstallationList | Where-Object { $_.AppName -eq $Final.AppName })
Assert-Condition ($NewItem.bIsIncompleteInstall -eq $false -and $NewItem.AppVersionString -eq $Final.Version) 'Committed .item validation failed.'
Assert-Condition ($NewEgi.v4.state -eq 'Installed' -and $NewEgi.v4.manifestData.version -eq $Final.Version) 'Committed .egi validation failed.'
Assert-Condition (@($NewEgi.v4.pendingTags).Count -eq 0) 'Committed .egi still has pending tags.'
Assert-Condition ($NewRegistrations.Count -eq 1 -and $NewRegistrations[0].AppVersion -eq $Final.Version) 'LauncherInstalled.dat registration validation failed.'
Assert-Condition ((Get-FileHash -LiteralPath $Final.CompleteManifestPath -Algorithm SHA256).Hash -eq $Final.ManifestHash) 'Committed manifest hash mismatch.'
Remove-Item -LiteralPath $Final.ItemPath -ErrorAction Stop
Remove-Item -LiteralPath $Final.PendingManifestPath -ErrorAction Stop
$ChangesStarted = $false
Write-Host ''
Write-Host 'METADATA REPAIR WRITTEN AND VERIFIED.' -ForegroundColor Green
Write-Host ('Backup: ' + $BackupDir)
Write-Host 'Restart Epic Games Launcher and check for the Launch button.'
Write-Host 'Keep your backup. This workaround does not fix Epic InstallHelper itself.'
}
catch {
Write-Warning ('Repair failed: ' + $_.Exception.Message)
if ($ChangesStarted) {
Write-Warning 'Attempting to restore pre-repair metadata. Keep Epic Launcher closed.'
try {
$PendingDir = Split-Path -Parent $Final.ItemPath
$ManifestPendingDir = Split-Path -Parent $Final.PendingManifestPath
if (-not (Test-Path -LiteralPath $PendingDir)) { New-Item -ItemType Directory -Path $PendingDir | Out-Null }
if (-not (Test-Path -LiteralPath $ManifestPendingDir)) { New-Item -ItemType Directory -Path $ManifestPendingDir | Out-Null }
Copy-Item -LiteralPath (Join-Path $BackupDir 'OriginalPending.item') -Destination $Final.ItemPath -Force
Copy-Item -LiteralPath (Join-Path $BackupDir 'OriginalPending.manifest') -Destination $Final.PendingManifestPath -Force
Copy-Item -LiteralPath (Join-Path $BackupDir 'Original.egi') -Destination $Final.EgiPath -Force
Copy-Item -LiteralPath (Join-Path $BackupDir 'OriginalLauncherInstalled.dat') -Destination $Final.LauncherDatPath -Force
Remove-Item -LiteralPath $Final.CompleteItemPath -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $Final.CompleteManifestPath -Force -ErrorAction SilentlyContinue
Write-Warning 'Rollback attempted. Confirm the original files using RESTORE_PATHS.txt.'
}
catch {
Write-Warning ('ROLLBACK FAILED: ' + $_.Exception.Message)
Write-Warning ('Do not restart the launcher until reconciled. Backup: ' + $BackupDir)
}
}
throw
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment