src/Lzma.ps1
|
# LZMA1 打包支持。打包负载默认走 gzip(Framework)/ Brotli(Core),但这两者的窗口/建模对大脚本的长距重复不够好; # 这里把 7-Zip LZMA SDK 的 C# 源码(public domain,见 programFrames/LzmaDecode.cs 与 LzmaEncode.cs)在打包时按需 # 编进宿主进程并缓存 DLL,供 Compress-Lzma 使用。解码器则随产物 launcher 一起编译(pack.cs 的 CodecLzma 分支)。 # 是否启用交给调用方按「压完产物是否更小」决定,见 CodeDomCompiler.ps1 / CoreCompiler.ps1。 # # 注意编码侧类型名是 LzmaPackCodec(而非解码侧的 LzmaCodec):ps12exe 自身被编成 exe 且该 exe 的 launcher 走 LZMA 时, # 产物里会带一个只有解码器的 LzmaCodec;按名字找编码器若用 LzmaCodec 会误命中它。 # 低于该字节数的负载,LZMA 相对 gzip/Brotli 省下的必然不及解码器的固定开销,直接跳过以免白算。 $LzmaPackMinBytes = 32768 $script:LzmaPackCodecType = $null #_if PSEXE #_include_as_value lzmaDecodeSource "$PSScriptRoot/programFrames/LzmaDecode.cs" #_include_as_value lzmaEncodeSource "$PSScriptRoot/programFrames/LzmaEncode.cs" #_else [string]$lzmaDecodeSource = Get-Content -LiteralPath "$PSScriptRoot/programFrames/LzmaDecode.cs" -Raw -Encoding UTF8 [string]$lzmaEncodeSource = Get-Content -LiteralPath "$PSScriptRoot/programFrames/LzmaEncode.cs" -Raw -Encoding UTF8 #_endif # 在已加载程序集里按名字找类型并转成 [type]。编译成 exe 的宿主(PSEXE)下 `'X' -as [type]` 不一定能解析动态加载的 # 程序集,用反射更稳;返回的是 Type 对象而非类型字面量,调用方走反射。 function Find-LoadedType([string]$Name) { foreach ($assembly in [AppDomain]::CurrentDomain.GetAssemblies()) { try { $type = $assembly.GetType($Name, $false, $false) if ($type) { return $type } } catch {} } return $null } # 取得(必要时编译并缓存)包含 LzmaPackCodec 的宿主程序集 Type。按宿主 PSEdition/版本与源码内容分键,跨进程复用。 # 失败(例如宿主缺少 csc/Roslyn)返回 $null,Compress-Lzma 据此回退到 gzip/Brotli。 function Get-LzmaPackCodecType { if ($script:LzmaPackCodecType) { return $script:LzmaPackCodecType } $loaded = Find-LoadedType 'LzmaPackCodec' if ($loaded) { $script:LzmaPackCodecType = $loaded; return $loaded } $cacheRoot = Get-CacheRoot 'lzma' Clear-StaleCache $cacheRoot $key = Get-TextHash ("$($PSVersionTable.PSEdition)|$($PSVersionTable.PSVersion)|$lzmaDecodeSource|$lzmaEncodeSource") $decPath = Join-Path $cacheRoot "LzmaDecode_$key.cs" $encPath = Join-Path $cacheRoot "LzmaEncode_$key.cs" $dllPath = Join-Path $cacheRoot "lzma_$key.dll" $mutex = [System.Threading.Mutex]::new($false, "ps12exe-lzma-$key") $locked = $false try { try { $locked = $mutex.WaitOne(120000) } catch { $locked = $false } if (Test-Path -LiteralPath $dllPath) { [void][System.Reflection.Assembly]::LoadFrom($dllPath) } else { [System.IO.File]::WriteAllText($decPath, $lzmaDecodeSource, [System.Text.UTF8Encoding]::new($true)) [System.IO.File]::WriteAllText($encPath, $lzmaEncodeSource, [System.Text.UTF8Encoding]::new($true)) Add-Type -Path @($decPath, $encPath) -OutputAssembly $dllPath -OutputType Library [void][System.Reflection.Assembly]::LoadFrom($dllPath) } } finally { if ($locked) { try { $mutex.ReleaseMutex() } catch {} } $mutex.Dispose() } $script:LzmaPackCodecType = Find-LoadedType 'LzmaPackCodec' return $script:LzmaPackCodecType } # 用 LZMA 压缩字节;任何环节失败都返回 $null,让调用方回退到默认压缩。 function Compress-Lzma([byte[]]$Data) { try { $codec = Get-LzmaPackCodecType if (-not $codec) { return $null } return , ($codec.GetMethod('Compress').Invoke($null, @(, $Data))) } catch { Write-Debug "LZMA 打包不可用,回退默认压缩:$_" return $null } } |