2026-07-19 22:55:06 +01:00
|
|
|
param(
|
|
|
|
|
[string] $Configuration = "Release",
|
|
|
|
|
[string] $Architecture = "x64",
|
Give the bridge a Linux build the deploy host's own distribution can produce
There is no Linux MsQuic binary in any NuGet package, and the v2.5.9 GitHub
release carries only test bundles, so the Windows script's approach does not
transfer. What does exist is Microsoft's Debian 13 pool, which is the same
distribution Yggdrasil runs, and the msquic source tree at the matching tag for
the POSIX headers the NuGet omits.
`scripts/build-quic-native.sh` fetches the pinned `libmsquic_2.5.9_amd64.deb`
and the three headers `msquic.h`, `msquic_posix.h` and `quic_sal_stub.h`, and
verifies all four by SHA-256 the same way `build-quic-native.ps1:14` verifies
the NuGet. A digest that stops matching is a changed dependency, not a warning.
Two details the deb forced. It carries three library files and no licence text,
so the MIT licence is copied from the one this repo already committed for the
Windows plugin — the same text for both platforms. And it ships only
`libmsquic.so.2.5.9`, with no `libmsquic.so` for `-lmsquic` to resolve against,
so the script makes that link in the extracted tree rather than in the image.
The Linux CMake branch links `-fvisibility=hidden`, so only the twelve v2
exports leave the `.so` and the Windows-only v1 five are absent by construction
rather than by discipline, and sets `RPATH $ORIGIN` so the `libmsquic.so.2`
shipped beside the bridge resolves without being installed system-wide.
`build-quic-native.ps1`'s default output directory becomes
`artifacts/quic-native/win32-x64` so the two platforms' outputs sit beside each
other instead of on top of each other. Both existing callers —
`build-unity-package.ps1:80-82` and the Native.Tests csproj `:20-22` — pass an
explicit `-OutputDirectory`, so neither is affected.
Built and smoked in the workstation's Docker `debian:13` engine pinned by
digest: g++ 14.2.0, glibc 2.41, `libgamecult_mesh_quic_native.so` 85,456 bytes
with `NEEDED libmsquic.so.2` and `RUNPATH $ORIGIN`, and the same 15-check v2 FFI
smoke that runs on Windows passes under Node 24 on that image. That is a Linux
build on a Linux host and nothing more: Q6 ruled the release artifact comes from
the Actions job, and this one is the development loop.
A Linux host still needs MsQuic's own runtime dependencies present — libssl3t64,
libnuma1, libxdp1, libnl-route-3-200 — which is now written down, because the
first smoke run failed on a missing libxdp.so.1 and the reason was not obvious
from the error.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 21:04:14 +02:00
|
|
|
# Per-platform, so the Linux script's output sits beside this one's rather
|
|
|
|
|
# than on top of it. build-unity-package.ps1 and the Native.Tests csproj both
|
|
|
|
|
# pass an explicit -OutputDirectory, so neither is affected by this moving.
|
|
|
|
|
[string] $OutputDirectory = "artifacts\quic-native\win32-x64"
|
2026-07-19 22:55:06 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
|
if ($Architecture -ne "x64") {
|
|
|
|
|
throw "The first CultMesh native QUIC package supports x64 Windows desktop only."
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$repoRoot = Split-Path -Parent $PSScriptRoot
|
|
|
|
|
$version = "2.5.9"
|
Pin the Windows MsQuic to the OpenSSL build, and both of its digests
Q9 ruled A. The Schannel MsQuic refuses QUIC_CREDENTIAL_TYPE_CERTIFICATE_PKCS12
with QUIC_STATUS_NOT_SUPPORTED, and PKCS12 is the only credential type a
provider has on both platforms, so a Windows provider could not open a
listener against the runtime this repo was shipping. The same bridge
binary passes all twenty smoke checks and the v1 gate against the OpenSSL
build, which is what Soul verified and what this now downloads.
Two digests, not one. The zip is pinned by FEE9A664 and 19,179,817 bytes;
the msquic.dll inside it by C17C6581 and 4,181,856 bytes. The earlier
report and the cut map's first record put the zip's digest on the DLL,
which is a pin that checks the archive and then ships a file nobody looked
at. Sizes ride along with both, because a digest that fails tells you less
than a digest and a length that fail together.
The CRT is linked statically. A Node or Unity host without the Visual C++
redistributable cannot load a DLL that imports vcruntime140.dll, and
Windows reports that as "the specified module could not be found" while
naming the module that was found. Confirmed on the rebuild: msquic.dll,
CRYPT32, ntdll, KERNEL32, and no CRT import at all. The DLL goes from
83,456 to 302,080 bytes, which is the price of the host not needing
anything installed.
The Unity plugin's committed msquic.dll is still the Schannel one and this
does not touch it; the next build-unity-package.ps1 run is what replaces
it, and that is not in this batch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 22:04:27 +02:00
|
|
|
# OpenSSL, not Schannel (Q9 ruled A). The Schannel build of MsQuic refuses
|
|
|
|
|
# QUIC_CREDENTIAL_TYPE_CERTIFICATE_PKCS12 with QUIC_STATUS_NOT_SUPPORTED, which
|
|
|
|
|
# is the only credential type a provider has on both platforms, so a Windows
|
|
|
|
|
# provider could not open a listener at all. The identical bridge binary passes
|
|
|
|
|
# the whole v2 smoke and the v1 gate against this runtime.
|
|
|
|
|
#
|
|
|
|
|
# Two digests, because they answer different questions. The zip is what the
|
|
|
|
|
# download is pinned to; the DLL is what actually ships beside the bridge, and
|
|
|
|
|
# pinning only the archive leaves the file nobody checked.
|
|
|
|
|
$expectedPackageSha256 = "FEE9A664C0052DEEE66D40CED2E19BFE20BC423DE5AE5234CFC0CEBC22CA3F5E"
|
|
|
|
|
$expectedPackageBytes = 19179817
|
|
|
|
|
$expectedRuntimeSha256 = "C17C65813FE007EB486A5270D244F3659261355795B27ABB99BD19FF77539263"
|
|
|
|
|
$expectedRuntimeBytes = 4181856
|
|
|
|
|
$cacheRoot = Join-Path $repoRoot "artifacts\dependencies\msquic-openssl-$version"
|
|
|
|
|
$packagePath = Join-Path $cacheRoot "Microsoft.Native.Quic.MsQuic.OpenSSL.$version.zip"
|
2026-07-19 22:55:06 +01:00
|
|
|
$extractRoot = Join-Path $cacheRoot "package"
|
|
|
|
|
$nativeRoot = Join-Path $extractRoot "build\native"
|
|
|
|
|
$buildRoot = Join-Path $repoRoot "artifacts\quic-native-build\$Architecture\$Configuration"
|
|
|
|
|
$outputRoot = if ([IO.Path]::IsPathRooted($OutputDirectory)) {
|
|
|
|
|
$OutputDirectory
|
|
|
|
|
} else {
|
|
|
|
|
Join-Path $repoRoot $OutputDirectory
|
|
|
|
|
}
|
|
|
|
|
|
Pin the Windows MsQuic to the OpenSSL build, and both of its digests
Q9 ruled A. The Schannel MsQuic refuses QUIC_CREDENTIAL_TYPE_CERTIFICATE_PKCS12
with QUIC_STATUS_NOT_SUPPORTED, and PKCS12 is the only credential type a
provider has on both platforms, so a Windows provider could not open a
listener against the runtime this repo was shipping. The same bridge
binary passes all twenty smoke checks and the v1 gate against the OpenSSL
build, which is what Soul verified and what this now downloads.
Two digests, not one. The zip is pinned by FEE9A664 and 19,179,817 bytes;
the msquic.dll inside it by C17C6581 and 4,181,856 bytes. The earlier
report and the cut map's first record put the zip's digest on the DLL,
which is a pin that checks the archive and then ships a file nobody looked
at. Sizes ride along with both, because a digest that fails tells you less
than a digest and a length that fail together.
The CRT is linked statically. A Node or Unity host without the Visual C++
redistributable cannot load a DLL that imports vcruntime140.dll, and
Windows reports that as "the specified module could not be found" while
naming the module that was found. Confirmed on the rebuild: msquic.dll,
CRYPT32, ntdll, KERNEL32, and no CRT import at all. The DLL goes from
83,456 to 302,080 bytes, which is the price of the host not needing
anything installed.
The Unity plugin's committed msquic.dll is still the Schannel one and this
does not touch it; the next build-unity-package.ps1 run is what replaces
it, and that is not in this batch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 22:04:27 +02:00
|
|
|
function Assert-Pinned {
|
|
|
|
|
param([string] $Path, [string] $ExpectedSha256, [long] $ExpectedBytes, [string] $What)
|
|
|
|
|
$actualBytes = (Get-Item -LiteralPath $Path).Length
|
|
|
|
|
$sha256 = [Security.Cryptography.SHA256]::Create()
|
|
|
|
|
try {
|
|
|
|
|
$stream = [IO.File]::OpenRead($Path)
|
|
|
|
|
try { $actualSha256 = [BitConverter]::ToString($sha256.ComputeHash($stream)).Replace("-", "") }
|
|
|
|
|
finally { $stream.Dispose() }
|
|
|
|
|
} finally { $sha256.Dispose() }
|
|
|
|
|
if ($actualSha256 -ne $ExpectedSha256 -or $actualBytes -ne $ExpectedBytes) {
|
|
|
|
|
throw ("$What does not match the pinned release: " +
|
|
|
|
|
"expected $ExpectedSha256 ($ExpectedBytes bytes), got $actualSha256 ($actualBytes bytes).")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-19 22:55:06 +01:00
|
|
|
New-Item -ItemType Directory -Force -Path $cacheRoot | Out-Null
|
|
|
|
|
if (-not (Test-Path -LiteralPath $packagePath)) {
|
|
|
|
|
& curl.exe -L --fail --silent --show-error `
|
|
|
|
|
-o $packagePath `
|
Pin the Windows MsQuic to the OpenSSL build, and both of its digests
Q9 ruled A. The Schannel MsQuic refuses QUIC_CREDENTIAL_TYPE_CERTIFICATE_PKCS12
with QUIC_STATUS_NOT_SUPPORTED, and PKCS12 is the only credential type a
provider has on both platforms, so a Windows provider could not open a
listener against the runtime this repo was shipping. The same bridge
binary passes all twenty smoke checks and the v1 gate against the OpenSSL
build, which is what Soul verified and what this now downloads.
Two digests, not one. The zip is pinned by FEE9A664 and 19,179,817 bytes;
the msquic.dll inside it by C17C6581 and 4,181,856 bytes. The earlier
report and the cut map's first record put the zip's digest on the DLL,
which is a pin that checks the archive and then ships a file nobody looked
at. Sizes ride along with both, because a digest that fails tells you less
than a digest and a length that fail together.
The CRT is linked statically. A Node or Unity host without the Visual C++
redistributable cannot load a DLL that imports vcruntime140.dll, and
Windows reports that as "the specified module could not be found" while
naming the module that was found. Confirmed on the rebuild: msquic.dll,
CRYPT32, ntdll, KERNEL32, and no CRT import at all. The DLL goes from
83,456 to 302,080 bytes, which is the price of the host not needing
anything installed.
The Unity plugin's committed msquic.dll is still the Schannel one and this
does not touch it; the next build-unity-package.ps1 run is what replaces
it, and that is not in this batch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 22:04:27 +02:00
|
|
|
"https://www.nuget.org/api/v2/package/Microsoft.Native.Quic.MsQuic.OpenSSL/$version"
|
2026-07-19 22:55:06 +01:00
|
|
|
if ($LASTEXITCODE -ne 0) { throw "MsQuic package download failed with exit code $LASTEXITCODE" }
|
|
|
|
|
}
|
Pin the Windows MsQuic to the OpenSSL build, and both of its digests
Q9 ruled A. The Schannel MsQuic refuses QUIC_CREDENTIAL_TYPE_CERTIFICATE_PKCS12
with QUIC_STATUS_NOT_SUPPORTED, and PKCS12 is the only credential type a
provider has on both platforms, so a Windows provider could not open a
listener against the runtime this repo was shipping. The same bridge
binary passes all twenty smoke checks and the v1 gate against the OpenSSL
build, which is what Soul verified and what this now downloads.
Two digests, not one. The zip is pinned by FEE9A664 and 19,179,817 bytes;
the msquic.dll inside it by C17C6581 and 4,181,856 bytes. The earlier
report and the cut map's first record put the zip's digest on the DLL,
which is a pin that checks the archive and then ships a file nobody looked
at. Sizes ride along with both, because a digest that fails tells you less
than a digest and a length that fail together.
The CRT is linked statically. A Node or Unity host without the Visual C++
redistributable cannot load a DLL that imports vcruntime140.dll, and
Windows reports that as "the specified module could not be found" while
naming the module that was found. Confirmed on the rebuild: msquic.dll,
CRYPT32, ntdll, KERNEL32, and no CRT import at all. The DLL goes from
83,456 to 302,080 bytes, which is the price of the host not needing
anything installed.
The Unity plugin's committed msquic.dll is still the Schannel one and this
does not touch it; the next build-unity-package.ps1 run is what replaces
it, and that is not in this batch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 22:04:27 +02:00
|
|
|
Assert-Pinned -Path $packagePath -ExpectedSha256 $expectedPackageSha256 `
|
|
|
|
|
-ExpectedBytes $expectedPackageBytes -What "The MsQuic NuGet package"
|
2026-07-19 22:55:06 +01:00
|
|
|
if (-not (Test-Path -LiteralPath (Join-Path $nativeRoot "include\msquic.h"))) {
|
|
|
|
|
if (Test-Path -LiteralPath $extractRoot) { Remove-Item -LiteralPath $extractRoot -Recurse -Force }
|
|
|
|
|
Expand-Archive -LiteralPath $packagePath -DestinationPath $extractRoot
|
|
|
|
|
}
|
Pin the Windows MsQuic to the OpenSSL build, and both of its digests
Q9 ruled A. The Schannel MsQuic refuses QUIC_CREDENTIAL_TYPE_CERTIFICATE_PKCS12
with QUIC_STATUS_NOT_SUPPORTED, and PKCS12 is the only credential type a
provider has on both platforms, so a Windows provider could not open a
listener against the runtime this repo was shipping. The same bridge
binary passes all twenty smoke checks and the v1 gate against the OpenSSL
build, which is what Soul verified and what this now downloads.
Two digests, not one. The zip is pinned by FEE9A664 and 19,179,817 bytes;
the msquic.dll inside it by C17C6581 and 4,181,856 bytes. The earlier
report and the cut map's first record put the zip's digest on the DLL,
which is a pin that checks the archive and then ships a file nobody looked
at. Sizes ride along with both, because a digest that fails tells you less
than a digest and a length that fail together.
The CRT is linked statically. A Node or Unity host without the Visual C++
redistributable cannot load a DLL that imports vcruntime140.dll, and
Windows reports that as "the specified module could not be found" while
naming the module that was found. Confirmed on the rebuild: msquic.dll,
CRYPT32, ntdll, KERNEL32, and no CRT import at all. The DLL goes from
83,456 to 302,080 bytes, which is the price of the host not needing
anything installed.
The Unity plugin's committed msquic.dll is still the Schannel one and this
does not touch it; the next build-unity-package.ps1 run is what replaces
it, and that is not in this batch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 22:04:27 +02:00
|
|
|
Assert-Pinned -Path (Join-Path $nativeRoot "bin\x64\msquic.dll") `
|
|
|
|
|
-ExpectedSha256 $expectedRuntimeSha256 -ExpectedBytes $expectedRuntimeBytes -What "The MsQuic runtime DLL"
|
2026-07-19 22:55:06 +01:00
|
|
|
|
|
|
|
|
cmake -S (Join-Path $repoRoot "native\GameCult.Mesh.Quic.Native") -B $buildRoot -A x64 `
|
|
|
|
|
"-DMSQUIC_ROOT=$($nativeRoot.Replace('\', '/'))"
|
|
|
|
|
if ($LASTEXITCODE -ne 0) { throw "CultMesh native QUIC configure failed with exit code $LASTEXITCODE" }
|
|
|
|
|
cmake --build $buildRoot --config $Configuration
|
|
|
|
|
if ($LASTEXITCODE -ne 0) { throw "CultMesh native QUIC build failed with exit code $LASTEXITCODE" }
|
|
|
|
|
|
|
|
|
|
New-Item -ItemType Directory -Force -Path $outputRoot | Out-Null
|
|
|
|
|
Copy-Item -LiteralPath (Join-Path $buildRoot "bin\$Configuration\gamecult_mesh_quic_native.dll") `
|
|
|
|
|
-Destination $outputRoot -Force
|
|
|
|
|
Copy-Item -LiteralPath (Join-Path $nativeRoot "bin\x64\msquic.dll") -Destination $outputRoot -Force
|
|
|
|
|
Copy-Item -LiteralPath (Join-Path $extractRoot "LICENSE") -Destination (Join-Path $outputRoot "MSQUIC-LICENSE.txt") -Force
|
Correct stale Schannel/40,448-byte claims, ship OpenSSL's license notice
Soul flagged (F3) two docs still describing the pre-release native QUIC
bridge: unity/org.gamecult.cultlib/README.md said Schannel, and
native/GameCult.Mesh.Quic.Native/README.md still described the older
40,448-byte committed plugin as current. The shipped bridge (since the
1.0.60/1.4.0 release, commit 0155ba8) is the OpenSSL MsQuic 2.5.9 build,
302,080 bytes, static CRT, no CRT import. Both docs now describe that build
and name what it replaced.
The OpenSSL build's NuGet package carries an Apache-2.0 notice for OpenSSL
itself (Microsoft.Native.Quic.MsQuic.OpenSSL's THIRD-PARTY-NOTICES) that the
committed Third Party Notices folder never carried - the Schannel build had
no such dependency to notice. build-quic-native.ps1 now copies it out
alongside MSQUIC-LICENSE.txt, build-unity-package.ps1 stages it into the
package output and the tracked template, and it is now committed as
OPENSSL-NOTICE.txt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 19:49:14 +02:00
|
|
|
Copy-Item -LiteralPath (Join-Path $extractRoot "THIRD-PARTY-NOTICES") -Destination (Join-Path $outputRoot "OPENSSL-NOTICE.txt") -Force
|
2026-07-19 22:55:06 +01:00
|
|
|
|
|
|
|
|
Write-Host "CultMesh native QUIC runtime: $outputRoot"
|
|
|
|
|
Get-ChildItem -LiteralPath $outputRoot -File | Select-Object Name,Length
|