src/Private/Parse/ConvertFrom-FotonDestListBytes.ps1

function ConvertFrom-FotonDestListBytes {
    <#
        DestList stream from an automatic destinations jumplist (libyal dtformats).
        Versions 1 (Win7) and 2/3/4 (Win10+ extended). Unknown versions: one unsupported row.
        FILETIME is emitted only at the documented offset 100.
    #>

    param(
        [byte[]]$Bytes,
        [int]$MaxEntries = 200
    )
    $rows = @()
    if ($null -eq $Bytes -or $Bytes.Length -eq 0) {
        $rows += [PSCustomObject]@{
            Kind          = 'DestList'
            Supported     = $false
            Identity      = ''
            Hostname      = ''
            LastAccessUtc = ''
            PinStatus     = ''
            EntryNumber   = ''
            Note          = 'DestList stream is empty. Not proof the jumplist never had entries.'
        }
        return $rows
    }
    if ($Bytes.Length -lt 32) {
        $rows += [PSCustomObject]@{
            Kind          = 'DestList'
            Supported     = $false
            Identity      = ''
            Hostname      = ''
            LastAccessUtc = ''
            PinStatus     = ''
            EntryNumber   = ''
            Note          = 'DestList shorter than the 32-byte header. Entries were not guessed.'
        }
        return $rows
    }
    $ver = [BitConverter]::ToUInt32($Bytes, 0)
    $count = [BitConverter]::ToUInt32($Bytes, 4)
    if ($ver -ne 1 -and $ver -ne 2 -and $ver -ne 3 -and $ver -ne 4) {
        $rows += [PSCustomObject]@{
            Kind          = 'DestList'
            Supported     = $false
            Identity      = ''
            Hostname      = ''
            LastAccessUtc = ''
            PinStatus     = ''
            EntryNumber   = ''
            Note          = ("DestList format version {0} is not 1, 2, 3, or 4. Paths and times were not guessed. Use a DestList-aware jumplist tool." -f $ver)
        }
        return $rows
    }
    $pos = 32
    $n = [int]$count
    if ($n -gt $MaxEntries) { $n = $MaxEntries }
    $parsed = 0
    for ($i = 0; $i -lt $n; $i++) {
        $need = 114
        if ($ver -ne 1) { $need = 130 }
        if (($pos + $need) -gt $Bytes.Length) {
            $rows += [PSCustomObject]@{
                Kind          = 'DestList'
                Supported     = $false
                Identity      = ''
                Hostname      = ''
                LastAccessUtc = ''
                PinStatus     = ''
                EntryNumber   = ''
                Note          = ("DestList truncated after {0} of {1} header entries (version {2}). Remaining paths were not guessed." -f $parsed, $count, $ver)
            }
            break
        }
        $hostBytes = New-Object byte[] 16
        [Array]::Copy($Bytes, $pos + 72, $hostBytes, 0, 16)
        $hostname = ([Text.Encoding]::ASCII.GetString($hostBytes)).Trim([char]0)
        $entryNum = [BitConverter]::ToUInt32($Bytes, $pos + 88)
        $lastUtc = ConvertFrom-FotonFileTimeUtc -Bytes $Bytes -Offset ($pos + 100)
        $pinRaw = [BitConverter]::ToInt32($Bytes, $pos + 108)
        $pin = 'Pinned'
        if ($pinRaw -eq -1) { $pin = 'Unpinned' }
        $pathOff = 114
        if ($ver -ne 1) { $pathOff = 130 }
        $pathChars = [BitConverter]::ToUInt16($Bytes, $pos + $pathOff - 2)
        $pathBytes = [int]$pathChars * 2
        if (($pos + $pathOff + $pathBytes) -gt $Bytes.Length) {
            $rows += [PSCustomObject]@{
                Kind          = 'DestList'
                Supported     = $false
                Identity      = ''
                Hostname      = $hostname
                LastAccessUtc = $lastUtc
                PinStatus     = $pin
                EntryNumber   = [string]$entryNum
                Note          = 'DestList path overruns the stream. This entry path was not guessed.'
            }
            break
        }
        $path = ''
        if ($pathBytes -gt 0) {
            $path = [Text.Encoding]::Unicode.GetString($Bytes, $pos + $pathOff, $pathBytes)
        }
        $pad = 0
        if ($ver -ne 1) { $pad = 4 }
        $rows += [PSCustomObject]@{
            Kind          = 'DestList'
            Supported     = [bool]$path
            Identity      = $path
            Hostname      = $hostname
            LastAccessUtc = $lastUtc
            PinStatus     = $pin
            EntryNumber   = [string]$entryNum
            Note          = ("DestList v{0} path. LastAccessUtc is the DestList FILETIME at offset 100, not the LNK timestamps." -f $ver)
        }
        $pos = $pos + $pathOff + $pathBytes + $pad
        $parsed++
    }
    return $rows
}