src/ConstProgramCheck.ps1

# 常量输出上限:以 TinySharp 实际内嵌字节数为预算(ASCII 1x / UTF-16 2x);原文超预算时,若 TinySharp
# 会对负载做 XPRESS 压缩且压缩后能落回预算内,则放行——大而可压缩的常量输出因此也能用 ~1KB 的壳。
# 仅 TinySharp 可用时适用(Core / requireAdmin 直接回退到普通编译)。
#
# 预算 = 「非 const hello world」与「const hello world」的体积差,按目标架构 / 是否 noConsole 查表
# (Framework4.0 实测;Framework2.0 同值)。含义:常量壳一旦大过这个差值,就不比普通编译更小了。
# console anycpu/x86 14848-1024=13824 x64 13824-1024=12800
# noConsole anycpu/x86 18432-1536=16896 x64 17920-1536=16384
$ConstBudgetTable = @{
    'console_anycpu'   = 13824
    'console_x86'      = 13824
    'console_x64'      = 12800
    'noConsole_anycpu' = 16896
    'noConsole_x86'    = 16896
    'noConsole_x64'    = 16384
}
# 压缩壳比未压缩壳多一个 512B 文件块(cabinet 解压 P/Invoke + 解压 CIL),须与 TinySharp.cs 的 CompressionOverhead 一致
$ConstCompressedOverhead = 512

# 注意:预算值为 TinySharp 实测量;requireAdmin/Core 实际走 constexpr.cs(内嵌转义的 $ConstResult),
# 无法用 TinySharp 壳,这里仅用同一预算做保守门槛,超出即回退普通编译。
function Test-ConstResultTooLong([string]$Output) {
    $mode = if ($noConsole) { 'noConsole' } else { 'console' }
    $archKey = if ($architecture -in 'x86', 'x64') { $architecture } else { 'anycpu' }
    $rawBudget = $ConstBudgetTable["${mode}_${archKey}"]

    # MessageBox(noConsole) 恒用 UTF-16;控制台按 ASCII/UTF-16 自适应(与 TinySharp.cs 保持一致)
    $payloadIsAscii = (-not $noConsole) -and (-not ($Output -match '[^\x00-\x7F]'))
    if ($payloadIsAscii) {
        $payload = [Text.Encoding]::ASCII.GetBytes($Output + [char]0)
    }
    else {
        $payload = [Text.Encoding]::Unicode.GetBytes($Output + [char]0)
    }
    # 预算衡量的是 TinySharp 实际内嵌的字节数(未压缩原文,或压缩后)
    if ($payload.Length -le $rawBudget) { return $false }
    if ($requireAdmin -or $isCoreTarget) { return $true }
    $estimated = Get-XpressCompressedSize $payload
    return (($estimated -lt 0) -or (($estimated + $ConstCompressedOverhead) -gt $rawBudget))
}

# 调用 cabinet.dll 的 XPRESS 压缩,返回压缩后字节数;API 不可用或失败返回 -1。
function Get-XpressCompressedSize([byte[]]$Bytes) {
    try {
        if (-not ('ps12exeConstCompressor' -as [type])) {
            Add-Type @"
using System;
using System.Runtime.InteropServices;
public static class ps12exeConstCompressor {
    [DllImport("cabinet.dll", SetLastError=true)] private static extern bool CreateCompressor(uint a, IntPtr al, out IntPtr c);
    [DllImport("cabinet.dll", SetLastError=true)] private static extern bool Compress(IntPtr c, byte[] i, IntPtr il, byte[] o, IntPtr ol, out IntPtr r);
    [DllImport("cabinet.dll", SetLastError=true)] private static extern bool CloseCompressor(IntPtr c);
    public static int CompressedSize(byte[] input) {
        IntPtr h;
        if (!CreateCompressor(3u, IntPtr.Zero, out h)) return -1;
        try {
            byte[] buf = new byte[input.Length + input.Length / 2 + 1024];
            IntPtr r;
            if (!Compress(h, input, (IntPtr)input.Length, buf, (IntPtr)buf.Length, out r)) return -1;
            return (int)r;
        } finally { CloseCompressor(h); }
    }
}
"@
 *> $null
        }
        return [ps12exeConstCompressor]::CompressedSize($Bytes)
    }
    catch { return -1 }
}

if ($AstAnalyzeResult.IsConst) {
    $timeoutSeconds = 7  # 设置超时限制(秒)

    # #_pragma noConstEval 显式声明本脚本不是常量,直接跳过常量求值
    # #_pragma constEvalTimeout 显式声明本次常量求值已超时,按超时回退处理
    $NoConstEvalPragma = $Content -match '(?m)^\s*#_pragma\s+noConstEval\b'
    $ConstEvalTimeoutPragma = $Content -match '(?m)^\s*#_pragma\s+constEvalTimeout\b'

    if ($NoConstEvalPragma) {
        Write-I18n Verbose ConstEvalNotConstFallback
        $AstAnalyzeResult.IsConst = $false
    }
    else {
        Write-I18n Verbose ConstEvalStart

        # 一个自定义host以便挂钩SetShouldExit
        Add-Type @"
using System;
using System.Globalization;
using System.Management.Automation;
using System.Management.Automation.Host;
 
public class ps12exeConstEvalHost : PSHost {
    public static int LastExitCode = 0;
    public override void SetShouldExit(int exitCode) {
        LastExitCode = exitCode;
    }
    public override PSHostUserInterface UI { get { return null; } }
    public override string Name { get { return "ps12exeConstEvalHost"; } }
    public override Version Version { get { return new Version("72.7"); } }
    public override Guid InstanceId { get { return Guid.NewGuid(); } }
    public override CultureInfo CurrentCulture { get { return new CultureInfo(72); } }
    public override CultureInfo CurrentUICulture { get { return new CultureInfo(72); } }
    public override void EnterNestedPrompt() { }
    public override void ExitNestedPrompt() { }
    public override void NotifyBeginApplication() { }
    public override void NotifyEndApplication() { }
}
"@
 *> $null
        $myhost = [ps12exeConstEvalHost]::New()
        $runspace = [runspacefactory]::CreateRunspace($myhost)
        $runspace.Open()
        $pwsh = [System.Management.Automation.PowerShell]::Create()
        $pwsh.Runspace = $runspace
        $runspace.SessionStateProxy.SetVariable("PSEXEScript", $Content)

        $ConstEvalDone = $false
        if ($ConstEvalTimeoutPragma) {
            # 已声明超时:不实际求值,直接走与真实超时相同的回退路径
            Write-I18n Verbose ConstEvalTimeoutFallback $timeoutSeconds
        }
        else {
            $null = $pwsh.AddScript("function PSEXEMainFunction{$Content};PSEXEMainFunction")

            $asyncResult = $pwsh.BeginInvoke()

            $timeoutTicks = [int]($timeoutSeconds * 20)
            for ($i = 0; $i -lt $timeoutTicks; $i++) {
                if ($asyncResult.IsCompleted) {
                    break
                }
                Start-Sleep -Milliseconds 50
            }

            if ($asyncResult.IsCompleted) {
                try {
                    $RowResult = $pwsh.EndInvoke($asyncResult) | Where-Object { $_ -ne $null }
                    # TinySharp 实际内嵌的负载(各行按换行拼接,与 TinySharpCompiler.ps1 保持一致)
                    $ConstOutput = $RowResult | ForEach-Object { (($_ | Out-String) -replace '\r\n$', '') }
                    $ConstOutput = $ConstOutput -join "`n"
                    $ConstResult = $RowResult | ForEach-Object {
                        (($_ | Out-String) -replace '\r\n$', '').Replace('\', '\\').Replace('"', '\"').Replace("`n", "\n").Replace("`r", "\r")
                    }
                    $ConstResult = $ConstResult -join $(if ($noConsole) { '","' }else { "`n" })
                    Write-I18n Verbose ConstEvalDone $(bytesOfString $ConstResult)
                    if (Test-ConstResultTooLong $ConstOutput) {
                        Write-I18n Verbose ConstEvalTooLongFallback
                    }
                    else {
                        #_if PSEXE #这是该脚本被ps12exe编译时使用的预处理代码
                            #_include_as_value programFrame "$PSScriptRoot/programFrames/constexpr.cs" #将constexpr.cs中的内容内嵌到该脚本中
                        #_else #否则正常读取cs文件
                            [string]$programFrame = Get-Content $PSScriptRoot/programFrames/constexpr.cs -Raw -Encoding UTF8
                        #_endif
                        $programFrame = $programFrame.Replace("`$ConstResult", $ConstResult)
                        $programFrame = $programFrame.Replace("`$ConstExitCodeResult", [ps12exeConstEvalHost]::LastExitCode)
                        if ($RowResult.Count -eq 0) {
                            $noOutput = $true
                        }
                        $ConstEvalDone = $true
                    }
                }
                catch {
                    Write-I18n Verbose ConstEvalThrowErrorFallback
                }
            }
            else {
                Write-I18n Verbose ConstEvalTimeoutFallback $timeoutSeconds
                $pwsh.Stop()
            }
        }

        # 所有回退路径(超时/超长/异常/显式声明)都汇聚到这一处,避免遗漏 IsConst 复位
        if (-not $ConstEvalDone) {
            $AstAnalyzeResult.IsConst = $false
        }

        $runspace.Close()
        $pwsh.Dispose()
        $runspace.Dispose()
    }
}