Skip to content

Instantly share code, notes, and snippets.

@wumb0
Created September 18, 2024 03:16
Show Gist options
  • Save wumb0/41c2f54af704b638d728961222256718 to your computer and use it in GitHub Desktop.
Save wumb0/41c2f54af704b638d728961222256718 to your computer and use it in GitHub Desktop.
param (
[string]$DesktopDeploymentCab,
[string]$PsfFile,
[string]$OutPath,
[switch]$Verbose = $false
)
mkdir -Force $OutPath | Out-Null
$OutPath = Resolve-Path $OutPath
$oldpath = $env:PATH
$env:PATH = $env:PATH + ";$OutPath"
$deltadll = "UpdateCompression.dll"
$ucpath = (Join-Path $OutPath $deltadll)
if (-not (Test-Path $ucpath)) {
# we need to get and import UpdateCompression
if ($null -ne $DesktopDeploymentCab) {
expand -F:UpdateCompression.dll $ddcab $OutPath | Out-Null
}
if (-not (Test-Path $ucpath)) {
$uri = "https://msdl.microsoft.com/download/symbols/updatecompression.dll/2F47410A82000/updatecompression.dll"
Write-Warning "UpdateCompression.dll was not present in $DesktopDeploymentCab"
Write-Host "Downloading a copy from $uri"
Invoke-WebRequest -UseBasicParsing -Uri $uri -OutFile $ucpath
}
}
try { [FFI.UpdateCompression] | Out-Null } catch {
Add-Type -Name UpdateCompression -Namespace FFI -MemberDefinition @"
[StructLayout(LayoutKind.Sequential)]
public struct DELTA_INPUT {
public IntPtr lpcStart;
public IntPtr uSize;
public int Editable;
}
[StructLayout(LayoutKind.Sequential)]
public struct DELTA_OUTPUT {
public IntPtr lpStart;
public IntPtr uSize;
}
[DllImport("$deltadll", SetLastError=true)]
public static extern bool ApplyDeltaB(
int ApplyFlags,
ref DELTA_INPUT Source,
ref DELTA_INPUT Delta,
ref DELTA_OUTPUT lpTarget
);
[DllImport("$deltadll")]
public static extern bool DeltaFree(IntPtr Buffer);
"@
}
# get the manifest delta from the PSF file
$psf = [IO.File]::OpenRead((Resolve-Path $psffile))
$psf.Seek(4, [IO.SeekOrigin]::Begin) | Out-Null
$psfreader = [IO.BinaryReader]$psf
$manifest_len = [bitconverter]::ToUInt32($psfreader.ReadBytes(4), 0)
# skip PSTREAM header, read the PA30 delta
$psf.Seek(0x80, [IO.SeekOrigin]::Begin) | Out-Null
$manifest_delta = $psfreader.ReadBytes($manifest_len)
$md_buffer = [Runtime.InteropServices.Marshal]::AllocHGlobal($manifest_len)
[Runtime.InteropServices.Marshal]::Copy($manifest_delta, 0, $md_buffer, $manifest_len) | Out-Null
# expand the manifest
$Source = New-Object FFI.UpdateCompression+DELTA_INPUT
$Delta = New-Object FFI.UpdateCompression+DELTA_INPUT
$Target = New-Object FFI.UpdateCompression+DELTA_OUTPUT
$Source.lpcStart = [IntPtr]::Zero
$Source.uSize = [IntPtr]::Zero
$Delta.lpcStart = $md_buffer
$Delta.uSize = [IntPtr]$manifest_len
$res = [FFI.UpdateCompression]::ApplyDeltaB(
0,
[ref]$Source,
[ref]$Delta,
[ref]$Target
)
# dll was loaded, can restore path
$env:PATH = $oldpath
[Runtime.InteropServices.Marshal]::FreeHGlobal($md_buffer) | Out-Null
if ($res -eq 0) {
Write-Error "ApplyDeltaB failed to expand the PSF metadata"
return
}
# create the manifest from the output bytes
$buf = [byte[]]::new($Target.uSize)
[Runtime.InteropServices.Marshal]::Copy($Target.lpStart, $buf, 0, $Target.uSize) | Out-Null
[FFI.UpdateCompression]::DeltaFree($Target.lpStart) | Out-Null
$xml = [xml]([Text.Encoding]::UTF8.GetString($buf))
if ($xml.Container.type -ne "PSF") {
Write-Error "Invalid PSF manifest XML"
return
}
# unpack the patch!
Write-Host "Manifest expanded. Unpacking patch"
$hasher = [Security.Cryptography.HashAlgorithm]::Create("sha256")
$tot = $xml.Container.Files.ChildNodes.Count
$n = 0
$xml.Container.Files.ChildNodes | ForEach-Object {
$offset = [int]$_.Delta.Source.offset
$length = [int]$_.Delta.Source.length
$expectedhash = $_.Delta.Source.Hash.value.ToUpper()
$fn = $_.name
Write-Progress -PercentComplete ($n/$tot*100) -Status "Unpacking patch $PsfFile" -Activity "$fn"
$psf.Seek($offset, [IO.SeekOrigin]::Begin) | Out-Null
$patch = $psfreader.ReadBytes($length)
$path = Join-Path $OutPath $fn
if ($Verbose) {
Write-Host "Unpacking $fn from offset $offset with length $length to $path"
}
mkdir -Force (Split-Path -Parent -Path $path) | Out-Null
# check the hash
$shasum = [BitConverter]::ToString($hasher.ComputeHash($patch)).Replace("-", "").ToUpper()
if ($shasum -ne $expectedhash) {
Write-Error Unexpected hash
}
Set-Content -Encoding Byte -Path $path $patch
$n += 1
}
$psf.Close()
Write-Host "Done!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment