From a9366014372d614e4d7ab97c268573ed01340593 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Sat, 19 Sep 2026 00:48:28 +0200 Subject: [PATCH 01/20] Package and export the Windows CEF sandbox --- .../streamlabs-windows-artifacts/action.yaml | 41 ++++++ .github/workflows/main-streamlabs.yml | 4 +- .github/workflows/main.yml | 6 +- CMakePresets.json | 4 +- cmake/finders/FindCEF.cmake | 132 +++++++++++++++++- cmake/windows/cef-sandbox-link-smoke.cpp | 12 ++ cmake/windows/helpers.cmake | 130 +++++++++++++---- libobs/cmake/libobsConfig.cmake.in | 43 ++++++ plugins/obs-browser | 2 +- 9 files changed, 339 insertions(+), 35 deletions(-) create mode 100644 cmake/windows/cef-sandbox-link-smoke.cpp diff --git a/.github/actions/streamlabs-windows-artifacts/action.yaml b/.github/actions/streamlabs-windows-artifacts/action.yaml index 88d209a92e0823..75a2bbb61650a6 100644 --- a/.github/actions/streamlabs-windows-artifacts/action.yaml +++ b/.github/actions/streamlabs-windows-artifacts/action.yaml @@ -63,6 +63,47 @@ runs: $kept = 0 $skipped = 0 + if ('${{ inputs.target }}' -eq 'x64') { + $presets = Get-Content -LiteralPath 'CMakePresets.json' -Raw | ConvertFrom-Json + $dependencyPreset = $presets.configurePresets | Where-Object name -eq 'dependencies' + $cefVersion = [string]$dependencyPreset.vendor.'obsproject.com/obs-studio'.dependencies.cef.version + $cefHash = [string]$dependencyPreset.vendor.'obsproject.com/obs-studio'.dependencies.cef.hashes.'windows-x64' + $cefRootPath = Join-Path '.deps' "cef_binary_${cefVersion}_windows_x64" + if (-Not (Test-Path -LiteralPath $cefRootPath -PathType Container)) { + throw "The configured Windows x64 CEF dependency directory is missing: $cefRootPath" + } + $cefRoot = Get-Item -LiteralPath $cefRootPath + + $installedCefHash = (Get-Content -LiteralPath '.deps/.dependency_cef_x64.sha256' -Raw).Trim() + if ($installedCefHash -ne $cefHash) { + throw "The extracted CEF dependency hash does not match CMakePresets.json" + } + + $cefVersionHeader = Join-Path $cefRoot.FullName 'include/cef_version.h' + $cefBuild = Select-String -LiteralPath $cefVersionHeader -Pattern '^#define CHROME_VERSION_BUILD ([0-9]+)$' + if (-Not $cefBuild -Or $cefBuild.Matches[0].Groups[1].Value -ne $cefVersion) { + throw "CEF version header does not identify configured Chromium build $cefVersion" + } + + $expectedCefPdbs = @( + 'Release/chrome_elf.dll.pdb', + 'Release/dxcompiler.dll.pdb', + 'Release/libcef.dll.pdb', + 'Release/libEGL.dll.pdb', + 'Release/libGLESv2.dll.pdb', + 'Release/vk_swiftshader.dll.pdb', + 'Release/vulkan-1.dll.pdb', + 'build/libcef_dll_wrapper/Release/libcef_dll_wrapper.pdb' + ) + foreach ($relativePath in $expectedCefPdbs) { + $pdbPath = Join-Path $cefRoot.FullName $relativePath + if (-Not (Test-Path -LiteralPath $pdbPath -PathType Leaf)) { + throw "The CEF runtime dependency is missing required symbol file: $relativePath" + } + } + Write-Host "Validated all $($expectedCefPdbs.Count) embedded CEF PDBs in $($cefRoot.Name)" + } + # The relative layout is preserved so the symbol server job can still report which pdb's # share a file name when it flattens them foreach ($searchPath in @("build_${{ inputs.target }}", '.deps')) { diff --git a/.github/workflows/main-streamlabs.yml b/.github/workflows/main-streamlabs.yml index fe7b9750947837..fe82319021d130 100644 --- a/.github/workflows/main-streamlabs.yml +++ b/.github/workflows/main-streamlabs.yml @@ -116,8 +116,8 @@ jobs: CefBuildConfig: Debug env: OS_TAG: "windows64" - CEF_VERSION: 5060 - CEF_REVISION: v3 + CEF_VERSION: 6533 + CEF_REVISION: v4 WIN_DEPS_VERSION: windows-deps-2024-05-08sl2-x64 WIN_DEPS_VERSION_X86: 2024-05-08 GRPC_VERSION: v1.47.0 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c3a32f6139d28f..e527a4aba2f76d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ env: PACKAGE_NAME: "libobs" CACHE_REVISION: '006' CEF_BUILD_VERSION_LINUX: '5060' - CEF_BUILD_VERSION_WIN: '5060' + CEF_BUILD_VERSION_WIN: '6533' QT_VERSION_MAC: '6.4.3' QT_VERSION_WIN: '6.4.3' DEPS_VERSION_WIN: '2023-08-31sl1' @@ -422,8 +422,8 @@ jobs: CefBuildConfig: Debug env: OS_TAG: "windows64" - CEF_VERSION: 5060 - CEF_REVISION: v3 + CEF_VERSION: 6533 + CEF_REVISION: v4 WIN_DEPS_VERSION: windows-deps-2024-05-08sl2-x64 WIN_DEPS_VERSION_X86: 2024-05-08 GRPC_VERSION: v1.47.0 diff --git a/CMakePresets.json b/CMakePresets.json index 01ff8d57447bf5..8979cc99b60c2a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -60,7 +60,7 @@ "macos-arm64": "429b50e74f6c174dcfe2f14d8204b54add497eaafe117f7b69ce6bb2354d2626", "ubuntu-x86_64": "7963335519a19ccdc5233f7334c5ab023026e2f3e9a0cc417007c09d86608146", "ubuntu-aarch64": "642514469eaa29a5c887891084d2e73f7dc2d7405f7dfa7726b2dbc24b309999", - "windows-x64": "110452488fb043761f5dbe0aeb1f84e2f0c8ecfbe187abb8632f5877f0bbb5d6", + "windows-x64": "26460a8219b0d666427190efba47bae3bf3abc19589c19f9710b4cb75b8ae620", "windows-arm64": "df9df4bd85826b4c071c6db404fd59cf93efd9c58ec3ab64e204466ae19bb02a" }, "revision": { @@ -68,7 +68,7 @@ "macos-arm64": 5, "ubuntu-x86_64": 6, "ubuntu-aarch64": 6, - "windows-x64": 3 + "windows-x64": 4 } }, "openssl": { diff --git a/cmake/finders/FindCEF.cmake b/cmake/finders/FindCEF.cmake index f07eda39f34102..8705b641a5fe83 100644 --- a/cmake/finders/FindCEF.cmake +++ b/cmake/finders/FindCEF.cmake @@ -20,6 +20,10 @@ This module defines the :prop_tgt:`IMPORTED` targets: ``CEF::Library`` Chromium Embedded Library +``CEF::Sandbox`` + Windows Chromium sandbox static library. Available when a 64-bit Windows + browser build requests sandbox support. + Result Variables ^^^^^^^^^^^^^^^^ @@ -43,6 +47,8 @@ The following cache variables may also be set: Path to the library component of CEF. ``CEF_LIBRARY_DEBUG`` Path to the debug library component of CEF. +``CEF_SANDBOX_LIBRARY_RELEASE`` + Path to the optimized Windows CEF sandbox static library. ``CEF_INCLUDE_DIR`` Directory containing ``cef_version.h``. @@ -69,6 +75,21 @@ find_path( DOC "Chromium Embedded Framework include directory." ) +set(_CEF_WINDOWS_SANDBOX_REQUIRED FALSE) +if(WIN32 AND ((CMAKE_VS_PLATFORM_NAME STREQUAL "x64" AND ENABLE_BROWSER) OR CEF_REQUIRE_SANDBOX)) + set(_CEF_WINDOWS_SANDBOX_REQUIRED TRUE) +endif() + +# The CEF binary distribution carries the authoritative system-library list +# for cef_sandbox. Use it when present instead of maintaining a second copy. +if(_CEF_WINDOWS_SANDBOX_REQUIRED AND EXISTS "${CEF_ROOT_DIR}/cmake/cef_variables.cmake") + set(_CEF_ROOT_EXPLICIT TRUE) + set(_CEF_ROOT "${CEF_ROOT_DIR}") + include("${CEF_ROOT_DIR}/cmake/cef_variables.cmake") + unset(_CEF_ROOT) + unset(_CEF_ROOT_EXPLICIT) +endif() + if(CEF_INCLUDE_DIR) file( STRINGS "${CEF_INCLUDE_DIR}/cef_version.h" @@ -178,13 +199,82 @@ endif() include(SelectLibraryConfigurations) select_library_configurations(CEF) +if(_CEF_WINDOWS_SANDBOX_REQUIRED) + find_library( + CEF_SANDBOX_LIBRARY_RELEASE + NAMES cef_sandbox + NO_DEFAULT_PATH + PATHS "${CEF_ROOT_DIR}/Release" + DOC "Optimized Chromium Embedded Framework sandbox static library." + ) + if(NOT CEF_SANDBOX_LIBRARY_RELEASE OR NOT EXISTS "${CEF_SANDBOX_LIBRARY_RELEASE}") + message( + FATAL_ERROR + "64-bit Windows browser sandbox support requires Release/cef_sandbox.lib " + "in CEF_ROOT_DIR (${CEF_ROOT_DIR}). Install the matching CEF 6533 v4 distribution." + ) + endif() + + # MSVC static and import libraries are COFF archives. Reject a misplaced + # file early; otherwise the subsequent link failure is opaque. + file(READ "${CEF_SANDBOX_LIBRARY_RELEASE}" _CEF_SANDBOX_MAGIC OFFSET 0 LIMIT 8 HEX) + string(TOLOWER "${_CEF_SANDBOX_MAGIC}" _CEF_SANDBOX_MAGIC) + if(NOT _CEF_SANDBOX_MAGIC STREQUAL "213c617263683e0a") + message( + FATAL_ERROR + "${CEF_SANDBOX_LIBRARY_RELEASE} is not a valid MSVC archive for CEF sandbox support." + ) + endif() + + if(NOT CEF_SANDBOX_STANDARD_LIBS) + # This fallback mirrors CEF 6533's cef_sandbox usage requirements for + # distributions that omit cmake/cef_variables.cmake. + set( + CEF_SANDBOX_STANDARD_LIBS + Advapi32.lib + Dbghelp.lib + Delayimp.lib + ntdll.lib + OleAut32.lib + PowrProf.lib + Propsys.lib + psapi.lib + SetupAPI.lib + Shell32.lib + Shcore.lib + Userenv.lib + version.lib + wbemuuid.lib + WindowsApp.lib + winmm.lib + ) + endif() + + if(NOT CEF_STANDARD_LIBS) + # Base CEF Windows dependencies are also used by objects pulled from + # cef_sandbox.lib. CEF's generated project links both lists. + set(CEF_STANDARD_LIBS comctl32.lib gdi32.lib rpcrt4.lib shlwapi.lib ws2_32.lib) + endif() +endif() + +set(_CEF_REQUIRED_VARS CEF_LIBRARY_RELEASE CEF_LIBRARY_WRAPPER_RELEASE CEF_INCLUDE_DIR) +if(_CEF_WINDOWS_SANDBOX_REQUIRED) + list(APPEND _CEF_REQUIRED_VARS CEF_SANDBOX_LIBRARY_RELEASE) +endif() + find_package_handle_standard_args( CEF - REQUIRED_VARS CEF_LIBRARY_RELEASE CEF_LIBRARY_WRAPPER_RELEASE CEF_INCLUDE_DIR + REQUIRED_VARS ${_CEF_REQUIRED_VARS} VERSION_VAR CEF_VERSION REASON_FAILURE_MESSAGE "Ensure that location of pre-compiled Chromium Embedded Framework is set as CEF_ROOT_DIR." ) -mark_as_advanced(CEF_LIBRARY CEF_LIBRARY_WRAPPER_RELEASE CEF_LIBRARY_WRAPPER_DEBUG CEF_INCLUDE_DIR) +mark_as_advanced( + CEF_LIBRARY + CEF_LIBRARY_WRAPPER_RELEASE + CEF_LIBRARY_WRAPPER_DEBUG + CEF_SANDBOX_LIBRARY_RELEASE + CEF_INCLUDE_DIR +) if(NOT TARGET CEF::Wrapper) if(IS_ABSOLUTE "${CEF_LIBRARY_WRAPPER_RELEASE}") @@ -203,8 +293,13 @@ if(NOT TARGET CEF::Wrapper) set_property(TARGET CEF::Wrapper PROPERTY IMPORTED_LIBNAME_DEBUG "${CEF_LIBRARY_WRAPPER_DEBUG}") endif() set_property(TARGET CEF::Wrapper APPEND PROPERTY IMPORTED_CONFIGURATIONS "Debug") + else() + set_property(TARGET CEF::Wrapper PROPERTY MAP_IMPORTED_CONFIG_DEBUG Release) endif() + set_property(TARGET CEF::Wrapper PROPERTY MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release) + set_property(TARGET CEF::Wrapper PROPERTY MAP_IMPORTED_CONFIG_MINSIZEREL Release) + set_property(TARGET CEF::Wrapper APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CEF_INCLUDE_DIR}" "${CEF_ROOT_DIR}") endif() @@ -228,6 +323,39 @@ if(NOT TARGET CEF::Library) set_property(TARGET CEF::Library APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CEF_INCLUDE_DIR}" "${CEF_ROOT_DIR}") set_property(TARGET CEF::Library PROPERTY IMPORTED_CONFIGURATIONS "Release") + set_property(TARGET CEF::Library PROPERTY MAP_IMPORTED_CONFIG_DEBUG Release) + set_property(TARGET CEF::Library PROPERTY MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release) + set_property(TARGET CEF::Library PROPERTY MAP_IMPORTED_CONFIG_MINSIZEREL Release) +endif() + +if(_CEF_WINDOWS_SANDBOX_REQUIRED AND NOT TARGET CEF::Sandbox) + # The browser plugin discovers CEF in a subdirectory, while the host + # executable links the sandbox from the top-level directory. + add_library(CEF::Sandbox STATIC IMPORTED GLOBAL) + set_property(TARGET CEF::Sandbox PROPERTY IMPORTED_CONFIGURATIONS "Release") + set_property(TARGET CEF::Sandbox PROPERTY IMPORTED_LOCATION_RELEASE "${CEF_SANDBOX_LIBRARY_RELEASE}") + set_property(TARGET CEF::Sandbox PROPERTY MAP_IMPORTED_CONFIG_DEBUG Release) + set_property(TARGET CEF::Sandbox PROPERTY MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release) + set_property(TARGET CEF::Sandbox PROPERTY MAP_IMPORTED_CONFIG_MINSIZEREL Release) + set_property( + TARGET CEF::Sandbox + PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CEF_INCLUDE_DIR}" "${CEF_ROOT_DIR}" + ) + set_property( + TARGET CEF::Sandbox + PROPERTY INTERFACE_LINK_LIBRARIES "${CEF_STANDARD_LIBS};${CEF_SANDBOX_STANDARD_LIBS}" + ) + + set_property( + TARGET CEF::Sandbox + PROPERTY + INTERFACE_COMPILE_DEFINITIONS + "PSAPI_VERSION=1;CEF_USE_SANDBOX;$<$:NDEBUG>;$<$:_HAS_ITERATOR_DEBUGGING=0>" + ) + set_property( + TARGET CEF::Sandbox + PROPERTY INTERFACE_COMPILE_OPTIONS "$<$:/U_DEBUG>" + ) endif() include(FeatureSummary) diff --git a/cmake/windows/cef-sandbox-link-smoke.cpp b/cmake/windows/cef-sandbox-link-smoke.cpp new file mode 100644 index 00000000000000..2756865730a35e --- /dev/null +++ b/cmake/windows/cef-sandbox-link-smoke.cpp @@ -0,0 +1,12 @@ +/* Link-only smoke test for the CEF Windows sandbox import target. */ +#include "include/cef_sandbox_win.h" + +int main() +{ + void *sandbox_info = cef_sandbox_info_create(); + if (!sandbox_info) + return 1; + + cef_sandbox_info_destroy(sandbox_info); + return 0; +} diff --git a/cmake/windows/helpers.cmake b/cmake/windows/helpers.cmake index f50624ff6cf1f5..c975dcd1b58006 100644 --- a/cmake/windows/helpers.cmake +++ b/cmake/windows/helpers.cmake @@ -116,46 +116,126 @@ function(set_target_properties_obs target) if(imported_location) cmake_path(GET imported_location PARENT_PATH cef_location) cmake_path(GET cef_location PARENT_PATH cef_root_location) + + if(NOT CEF_BINARY_FILES OR NOT CEF_RESOURCE_FILES) + message( + FATAL_ERROR + "The CEF distribution does not declare its required runtime binary and resource files." + ) + endif() + + set(cef_runtime_files) + foreach(cef_binary_file IN LISTS CEF_BINARY_FILES) + set(cef_binary_path "${cef_location}/${cef_binary_file}") + if(NOT EXISTS "${cef_binary_path}") + message(FATAL_ERROR "The CEF runtime is missing required binary: ${cef_binary_path}") + endif() + list(APPEND cef_runtime_files "${cef_binary_path}") + endforeach() + + set(cef_resource_files) + set(cef_resource_directories) + foreach(cef_resource_file IN LISTS CEF_RESOURCE_FILES) + set(cef_resource_path "${cef_root_location}/Resources/${cef_resource_file}") + if(IS_DIRECTORY "${cef_resource_path}") + list(APPEND cef_resource_directories "${cef_resource_path}") + elseif(EXISTS "${cef_resource_path}") + list(APPEND cef_resource_files "${cef_resource_path}") + else() + message(FATAL_ERROR "The CEF runtime is missing required resource: ${cef_resource_path}") + endif() + endforeach() + add_custom_command( TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E echo "Add Chromium Embedded Framework to library directory" COMMAND "${CMAKE_COMMAND}" -E make_directory "${OBS_OUTPUT_DIR}/$/${target_destination}" COMMAND - "${CMAKE_COMMAND}" -E copy_if_different "${imported_location}" "${cef_location}/chrome_elf.dll" - "${cef_location}/libEGL.dll" "${cef_location}/libGLESv2.dll" "${cef_location}/v8_context_snapshot.bin" - "${OBS_OUTPUT_DIR}/$/${target_destination}" - COMMAND - "${CMAKE_COMMAND}" -E copy_if_different "${cef_root_location}/Resources/chrome_100_percent.pak" - "${cef_root_location}/Resources/chrome_200_percent.pak" "${cef_root_location}/Resources/icudtl.dat" - "${cef_root_location}/Resources/resources.pak" "${OBS_OUTPUT_DIR}/$/${target_destination}/" - COMMAND - "${CMAKE_COMMAND}" -E copy_directory "${cef_root_location}/Resources/locales" - "${OBS_OUTPUT_DIR}/$/${target_destination}/locales" + "${CMAKE_COMMAND}" -E copy_if_different ${cef_runtime_files} ${cef_resource_files} + "${OBS_OUTPUT_DIR}/$/${target_destination}/" COMMENT "" ) + foreach(cef_resource_directory IN LISTS cef_resource_directories) + cmake_path(GET cef_resource_directory FILENAME cef_resource_directory_name) + add_custom_command( + TARGET ${target} + POST_BUILD + COMMAND + "${CMAKE_COMMAND}" -E copy_directory "${cef_resource_directory}" + "${OBS_OUTPUT_DIR}/$/${target_destination}/${cef_resource_directory_name}" + COMMENT "" + ) + endforeach() + install( - FILES - "${imported_location}" - "${cef_location}/chrome_elf.dll" - "${cef_location}/libEGL.dll" - "${cef_location}/libGLESv2.dll" - "${cef_location}/v8_context_snapshot.bin" - "${cef_root_location}/Resources/chrome_100_percent.pak" - "${cef_root_location}/Resources/chrome_200_percent.pak" - "${cef_root_location}/Resources/icudtl.dat" - "${cef_root_location}/Resources/resources.pak" + FILES ${cef_runtime_files} ${cef_resource_files} DESTINATION "${target_destination}" COMPONENT Runtime ) - install( - DIRECTORY "${cef_root_location}/Resources/locales" - DESTINATION "${target_destination}" - USE_SOURCE_PERMISSIONS - COMPONENT Runtime + foreach(cef_resource_directory IN LISTS cef_resource_directories) + install( + DIRECTORY "${cef_resource_directory}" + DESTINATION "${target_destination}" + USE_SOURCE_PERMISSIONS + COMPONENT Runtime + ) + endforeach() + endif() + endif() + + if(CMAKE_VS_PLATFORM_NAME STREQUAL "x64") + if(NOT TARGET CEF::Sandbox) + message( + FATAL_ERROR + "64-bit Windows obs-browser requires the CEF::Sandbox target. " + "Use the CEF 6533 v4 distribution containing Release/cef_sandbox.lib." + ) + endif() + + get_target_property(cef_sandbox_release CEF::Sandbox IMPORTED_LOCATION_RELEASE) + if(NOT cef_sandbox_release) + message(FATAL_ERROR "CEF::Sandbox must provide its Release import location.") + endif() + + set(obs_browser_sandbox_header "${CMAKE_SOURCE_DIR}/plugins/obs-browser/obs-browser-sandbox.h") + if(NOT EXISTS "${obs_browser_sandbox_header}") + message( + FATAL_ERROR + "obs-browser-sandbox.h is required for the Windows browser sandbox development package: " + "${obs_browser_sandbox_header}" + ) + endif() + + install( + FILES "${cef_sandbox_release}" + DESTINATION "${OBS_LIBRARY_DESTINATION}/cef/Release" + COMPONENT Development + ) + install( + DIRECTORY "${CEF_INCLUDE_DIR}/" + DESTINATION "${OBS_INCLUDE_DESTINATION}/include" + COMPONENT Development + ) + install( + FILES "${obs_browser_sandbox_header}" + DESTINATION "${OBS_INCLUDE_DESTINATION}" + COMPONENT Development + ) + + if(NOT TARGET cef-sandbox-link-smoke) + add_executable(cef-sandbox-link-smoke "${CMAKE_SOURCE_DIR}/cmake/windows/cef-sandbox-link-smoke.cpp") + target_link_libraries(cef-sandbox-link-smoke PRIVATE CEF::Sandbox) + set_target_properties( + cef-sandbox-link-smoke + PROPERTIES FOLDER "CMake/Smoke Tests" + MSVC_RUNTIME_LIBRARY "MultiThreaded" ) + if(BUILD_TESTING) + add_test(NAME cef-sandbox-link-smoke COMMAND cef-sandbox-link-smoke) + endif() endif() endif() endif() diff --git a/libobs/cmake/libobsConfig.cmake.in b/libobs/cmake/libobsConfig.cmake.in index c96975a86e4b45..6d1b0361488887 100644 --- a/libobs/cmake/libobsConfig.cmake.in +++ b/libobs/cmake/libobsConfig.cmake.in @@ -26,6 +26,49 @@ unset(_OBS_BUNDLED_DEPENDENCY_PREFIXES) unset(_OBS_DEPENDENCY_PREFIX) include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake") + +set(_OBS_CEF_SANDBOX_ENABLED FALSE) +if("@CMAKE_VS_PLATFORM_NAME@" STREQUAL "x64" AND "@ENABLE_BROWSER@") + set(_OBS_CEF_SANDBOX_ENABLED TRUE) +endif() +if(WIN32 AND _OBS_CEF_SANDBOX_ENABLED AND NOT TARGET OBS::cef-sandbox) + set(_OBS_CEF_SANDBOX_RELEASE "${PACKAGE_PREFIX_DIR}/lib/cef/Release/cef_sandbox.lib") + set(_OBS_CEF_SANDBOX_HEADER "${PACKAGE_PREFIX_DIR}/include/include/cef_sandbox_win.h") + set(_OBS_BROWSER_SANDBOX_ABI_HEADER "${PACKAGE_PREFIX_DIR}/include/obs-browser-sandbox.h") + if( + NOT EXISTS "${_OBS_CEF_SANDBOX_RELEASE}" + OR NOT EXISTS "${_OBS_CEF_SANDBOX_HEADER}" + OR NOT EXISTS "${_OBS_BROWSER_SANDBOX_ABI_HEADER}" + ) + message( + FATAL_ERROR + "The installed OBS development package is missing its Windows CEF sandbox " + "library or CEF include tree. Reinstall the matching development package." + ) + endif() + + add_library(OBS::cef-sandbox STATIC IMPORTED) + set_target_properties( + OBS::cef-sandbox + PROPERTIES + IMPORTED_CONFIGURATIONS "Release" + IMPORTED_LOCATION_RELEASE "${_OBS_CEF_SANDBOX_RELEASE}" + MAP_IMPORTED_CONFIG_DEBUG Release + MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release + MAP_IMPORTED_CONFIG_MINSIZEREL Release + INTERFACE_INCLUDE_DIRECTORIES "${PACKAGE_PREFIX_DIR}/include" + INTERFACE_COMPILE_DEFINITIONS + "PSAPI_VERSION=1;CEF_USE_SANDBOX;$<$:NDEBUG>;$<$:_HAS_ITERATOR_DEBUGGING=0>" + INTERFACE_COMPILE_OPTIONS "$<$:/U_DEBUG>" + INTERFACE_LINK_LIBRARIES + "comctl32.lib;gdi32.lib;rpcrt4.lib;shlwapi.lib;ws2_32.lib;Advapi32.lib;Dbghelp.lib;Delayimp.lib;ntdll.lib;OleAut32.lib;PowrProf.lib;Propsys.lib;psapi.lib;SetupAPI.lib;Shell32.lib;Shcore.lib;Userenv.lib;version.lib;wbemuuid.lib;WindowsApp.lib;winmm.lib" + ) + unset(_OBS_CEF_SANDBOX_RELEASE) + unset(_OBS_CEF_SANDBOX_HEADER) + unset(_OBS_BROWSER_SANDBOX_ABI_HEADER) +endif() +unset(_OBS_CEF_SANDBOX_ENABLED) + check_required_components("@PROJECT_NAME@") set_property(TARGET OBS::libobs APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS HAVE_OBSCONFIG_H) diff --git a/plugins/obs-browser b/plugins/obs-browser index 2f5161c5e036c3..9377dc167b85a4 160000 --- a/plugins/obs-browser +++ b/plugins/obs-browser @@ -1 +1 @@ -Subproject commit 2f5161c5e036c39eee02367b00e8c07d34c36bce +Subproject commit 9377dc167b85a42ad3485ce17613ee80d60e8a7d From 466602e664a2b10da5c9b53f6b8c6b30b24ddad5 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Sat, 19 Sep 2026 01:00:04 +0200 Subject: [PATCH 02/20] Apply CMake formatting --- cmake/finders/FindCEF.cmake | 26 ++++++++++++-------------- cmake/windows/helpers.cmake | 20 ++++---------------- 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/cmake/finders/FindCEF.cmake b/cmake/finders/FindCEF.cmake index 8705b641a5fe83..fc376d8c446fcd 100644 --- a/cmake/finders/FindCEF.cmake +++ b/cmake/finders/FindCEF.cmake @@ -220,10 +220,7 @@ if(_CEF_WINDOWS_SANDBOX_REQUIRED) file(READ "${CEF_SANDBOX_LIBRARY_RELEASE}" _CEF_SANDBOX_MAGIC OFFSET 0 LIMIT 8 HEX) string(TOLOWER "${_CEF_SANDBOX_MAGIC}" _CEF_SANDBOX_MAGIC) if(NOT _CEF_SANDBOX_MAGIC STREQUAL "213c617263683e0a") - message( - FATAL_ERROR - "${CEF_SANDBOX_LIBRARY_RELEASE} is not a valid MSVC archive for CEF sandbox support." - ) + message(FATAL_ERROR "${CEF_SANDBOX_LIBRARY_RELEASE} is not a valid MSVC archive for CEF sandbox support.") endif() if(NOT CEF_SANDBOX_STANDARD_LIBS) @@ -253,7 +250,14 @@ if(_CEF_WINDOWS_SANDBOX_REQUIRED) if(NOT CEF_STANDARD_LIBS) # Base CEF Windows dependencies are also used by objects pulled from # cef_sandbox.lib. CEF's generated project links both lists. - set(CEF_STANDARD_LIBS comctl32.lib gdi32.lib rpcrt4.lib shlwapi.lib ws2_32.lib) + set( + CEF_STANDARD_LIBS + comctl32.lib + gdi32.lib + rpcrt4.lib + shlwapi.lib + ws2_32.lib + ) endif() endif() @@ -337,10 +341,7 @@ if(_CEF_WINDOWS_SANDBOX_REQUIRED AND NOT TARGET CEF::Sandbox) set_property(TARGET CEF::Sandbox PROPERTY MAP_IMPORTED_CONFIG_DEBUG Release) set_property(TARGET CEF::Sandbox PROPERTY MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release) set_property(TARGET CEF::Sandbox PROPERTY MAP_IMPORTED_CONFIG_MINSIZEREL Release) - set_property( - TARGET CEF::Sandbox - PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CEF_INCLUDE_DIR}" "${CEF_ROOT_DIR}" - ) + set_property(TARGET CEF::Sandbox PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CEF_INCLUDE_DIR}" "${CEF_ROOT_DIR}") set_property( TARGET CEF::Sandbox PROPERTY INTERFACE_LINK_LIBRARIES "${CEF_STANDARD_LIBS};${CEF_SANDBOX_STANDARD_LIBS}" @@ -350,12 +351,9 @@ if(_CEF_WINDOWS_SANDBOX_REQUIRED AND NOT TARGET CEF::Sandbox) TARGET CEF::Sandbox PROPERTY INTERFACE_COMPILE_DEFINITIONS - "PSAPI_VERSION=1;CEF_USE_SANDBOX;$<$:NDEBUG>;$<$:_HAS_ITERATOR_DEBUGGING=0>" - ) - set_property( - TARGET CEF::Sandbox - PROPERTY INTERFACE_COMPILE_OPTIONS "$<$:/U_DEBUG>" + "PSAPI_VERSION=1;CEF_USE_SANDBOX;$<$:NDEBUG>;$<$:_HAS_ITERATOR_DEBUGGING=0>" ) + set_property(TARGET CEF::Sandbox PROPERTY INTERFACE_COMPILE_OPTIONS "$<$:/U_DEBUG>") endif() include(FeatureSummary) diff --git a/cmake/windows/helpers.cmake b/cmake/windows/helpers.cmake index c975dcd1b58006..e351ccbb60e5a5 100644 --- a/cmake/windows/helpers.cmake +++ b/cmake/windows/helpers.cmake @@ -118,10 +118,7 @@ function(set_target_properties_obs target) cmake_path(GET cef_location PARENT_PATH cef_root_location) if(NOT CEF_BINARY_FILES OR NOT CEF_RESOURCE_FILES) - message( - FATAL_ERROR - "The CEF distribution does not declare its required runtime binary and resource files." - ) + message(FATAL_ERROR "The CEF distribution does not declare its required runtime binary and resource files.") endif() set(cef_runtime_files) @@ -214,24 +211,15 @@ function(set_target_properties_obs target) DESTINATION "${OBS_LIBRARY_DESTINATION}/cef/Release" COMPONENT Development ) - install( - DIRECTORY "${CEF_INCLUDE_DIR}/" - DESTINATION "${OBS_INCLUDE_DESTINATION}/include" - COMPONENT Development - ) - install( - FILES "${obs_browser_sandbox_header}" - DESTINATION "${OBS_INCLUDE_DESTINATION}" - COMPONENT Development - ) + install(DIRECTORY "${CEF_INCLUDE_DIR}/" DESTINATION "${OBS_INCLUDE_DESTINATION}/include" COMPONENT Development) + install(FILES "${obs_browser_sandbox_header}" DESTINATION "${OBS_INCLUDE_DESTINATION}" COMPONENT Development) if(NOT TARGET cef-sandbox-link-smoke) add_executable(cef-sandbox-link-smoke "${CMAKE_SOURCE_DIR}/cmake/windows/cef-sandbox-link-smoke.cpp") target_link_libraries(cef-sandbox-link-smoke PRIVATE CEF::Sandbox) set_target_properties( cef-sandbox-link-smoke - PROPERTIES FOLDER "CMake/Smoke Tests" - MSVC_RUNTIME_LIBRARY "MultiThreaded" + PROPERTIES FOLDER "CMake/Smoke Tests" MSVC_RUNTIME_LIBRARY "MultiThreaded" ) if(BUILD_TESTING) add_test(NAME cef-sandbox-link-smoke COMMAND cef-sandbox-link-smoke) From 5eebdb2639b7b432e46b24eca086c8cd0ce3ed0c Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Sat, 19 Sep 2026 02:07:58 +0200 Subject: [PATCH 03/20] Skip oversized CEF PDB in symbol upload --- .github/actions/upload-debug-symbols/action.yaml | 6 +++++- .github/workflows/streamlabs-windows-release.yaml | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/actions/upload-debug-symbols/action.yaml b/.github/actions/upload-debug-symbols/action.yaml index 0a9dc32d92417f..5e376cd5aa1c07 100644 --- a/.github/actions/upload-debug-symbols/action.yaml +++ b/.github/actions/upload-debug-symbols/action.yaml @@ -31,6 +31,10 @@ inputs: description: "Comma separated folders to search for pdb's. Defaults to the whole workspace." required: false default: "" + excludePdbNames: + description: "Comma separated pdb file names or wildcard patterns to exclude." + required: false + default: "" forceUpload: description: "Upload every pdb instead of skipping the ones the symbol store already has." required: false @@ -59,7 +63,7 @@ runs: - name: Run symbol server scripts if: runner.os != 'macOS' shell: powershell - run: ./symsrv-scripts/main.bat "${{ inputs.githubWorkspace }}/symsrv-scripts" ".\main.ps1 -localSourceDir '${{ inputs.githubWorkspace }}' -repo_userId '${{ inputs.repoUserId }}' -repo_name '${{ inputs.repoName }}' -repo_branch '${{ inputs.repoBranch }}'${{ inputs.pdbPaths != '' && format(' -pdbPaths ''{0}''', inputs.pdbPaths) || '' }}${{ fromJSON(inputs.forceUpload) && ' -forceUpload' || '' }} -subModules 'plugins/mediasoup-connector,stream-labs,mediasoup-connector,streamlabs;plugins/enc-amf,stream-labs,obs-amd-encoder,streamlabs;plugins/motion-effect,stream-labs,motion-effect,master;plugins/obs-browser,stream-labs,obs-browser,streamlabs;plugins/obs-ndi,stream-labs,obs-ndi,streamlabs;plugins/obs-ndi,stream-labs,obs-amd-encoder,streamlabs;plugins/obs-openvr,stream-labs,OBS-OpenVR-Input-Plugin,master;plugins/sl-vst,stream-labs,sl-vst,streamlabs;plugins/slobs-virtual-cam,stream-labs,slobs-virtual-cam,streamlabs;plugins/win-dshow/libdshowcapture,stream-labs,libdshowcapture,streamlabs'" + run: ./symsrv-scripts/main.bat "${{ inputs.githubWorkspace }}/symsrv-scripts" ".\main.ps1 -localSourceDir '${{ inputs.githubWorkspace }}' -repo_userId '${{ inputs.repoUserId }}' -repo_name '${{ inputs.repoName }}' -repo_branch '${{ inputs.repoBranch }}'${{ inputs.pdbPaths != '' && format(' -pdbPaths ''{0}''', inputs.pdbPaths) || '' }}${{ inputs.excludePdbNames != '' && format(' -excludePdbNames ''{0}''', inputs.excludePdbNames) || '' }}${{ fromJSON(inputs.forceUpload) && ' -forceUpload' || '' }} -subModules 'plugins/mediasoup-connector,stream-labs,mediasoup-connector,streamlabs;plugins/enc-amf,stream-labs,obs-amd-encoder,streamlabs;plugins/motion-effect,stream-labs,motion-effect,master;plugins/obs-browser,stream-labs,obs-browser,streamlabs;plugins/obs-ndi,stream-labs,obs-ndi,streamlabs;plugins/obs-ndi,stream-labs,obs-amd-encoder,streamlabs;plugins/obs-openvr,stream-labs,OBS-OpenVR-Input-Plugin,master;plugins/sl-vst,stream-labs,sl-vst,streamlabs;plugins/slobs-virtual-cam,stream-labs,slobs-virtual-cam,streamlabs;plugins/win-dshow/libdshowcapture,stream-labs,libdshowcapture,streamlabs'" env: AWS_SYMB_ACCESS_KEY_ID: ${{ inputs.awsSymbAccessKeyId }} AWS_SYMB_SECRET_ACCESS_KEY: ${{ inputs.awsSymbSecretAccessKey }} diff --git a/.github/workflows/streamlabs-windows-release.yaml b/.github/workflows/streamlabs-windows-release.yaml index 3d1c563f98c46e..b1deda0e60c134 100644 --- a/.github/workflows/streamlabs-windows-release.yaml +++ b/.github/workflows/streamlabs-windows-release.yaml @@ -93,6 +93,9 @@ jobs: repoName: obs-studio repoBranch: ${{ github.sha }} pdbPaths: ${{ env.PDB_PATH }} + # CEF's 5+ GiB PDB exceeds the CAB format's per-file size limit. It + # remains available in the build's debug-symbol artifact. + excludePdbNames: libcef.dll.pdb symsrvRef: ${{ inputs.symsrvRef }} forceUpload: ${{ inputs.forceUpload }} symsrvDebug: ${{ inputs.symsrvDebug }} From 603f8fadcafc33959611a51971ba6263a1df7ea5 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Sun, 20 Sep 2026 10:57:44 +0200 Subject: [PATCH 04/20] Make CEF sandbox gating generator independent --- cmake/finders/FindCEF.cmake | 4 ++-- cmake/windows/helpers.cmake | 4 ++-- libobs/cmake/libobsConfig.cmake.in | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmake/finders/FindCEF.cmake b/cmake/finders/FindCEF.cmake index fc376d8c446fcd..3063c06efec1e7 100644 --- a/cmake/finders/FindCEF.cmake +++ b/cmake/finders/FindCEF.cmake @@ -76,7 +76,7 @@ find_path( ) set(_CEF_WINDOWS_SANDBOX_REQUIRED FALSE) -if(WIN32 AND ((CMAKE_VS_PLATFORM_NAME STREQUAL "x64" AND ENABLE_BROWSER) OR CEF_REQUIRE_SANDBOX)) +if(WIN32 AND ((CMAKE_SIZEOF_VOID_P EQUAL 8 AND ENABLE_BROWSER) OR CEF_REQUIRE_SANDBOX)) set(_CEF_WINDOWS_SANDBOX_REQUIRED TRUE) endif() @@ -211,7 +211,7 @@ if(_CEF_WINDOWS_SANDBOX_REQUIRED) message( FATAL_ERROR "64-bit Windows browser sandbox support requires Release/cef_sandbox.lib " - "in CEF_ROOT_DIR (${CEF_ROOT_DIR}). Install the matching CEF 6533 v4 distribution." + "in CEF_ROOT_DIR (${CEF_ROOT_DIR}). Install the matching CEF distribution." ) endif() diff --git a/cmake/windows/helpers.cmake b/cmake/windows/helpers.cmake index e351ccbb60e5a5..d8a1d455a51c26 100644 --- a/cmake/windows/helpers.cmake +++ b/cmake/windows/helpers.cmake @@ -183,12 +183,12 @@ function(set_target_properties_obs target) endif() endif() - if(CMAKE_VS_PLATFORM_NAME STREQUAL "x64") + if(CMAKE_SIZEOF_VOID_P EQUAL 8) if(NOT TARGET CEF::Sandbox) message( FATAL_ERROR "64-bit Windows obs-browser requires the CEF::Sandbox target. " - "Use the CEF 6533 v4 distribution containing Release/cef_sandbox.lib." + "Use the matching CEF distribution containing Release/cef_sandbox.lib." ) endif() diff --git a/libobs/cmake/libobsConfig.cmake.in b/libobs/cmake/libobsConfig.cmake.in index 6d1b0361488887..8fe3e7a2d1ef72 100644 --- a/libobs/cmake/libobsConfig.cmake.in +++ b/libobs/cmake/libobsConfig.cmake.in @@ -28,7 +28,7 @@ unset(_OBS_DEPENDENCY_PREFIX) include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake") set(_OBS_CEF_SANDBOX_ENABLED FALSE) -if("@CMAKE_VS_PLATFORM_NAME@" STREQUAL "x64" AND "@ENABLE_BROWSER@") +if("@CMAKE_SIZEOF_VOID_P@" EQUAL 8 AND "@ENABLE_BROWSER@") set(_OBS_CEF_SANDBOX_ENABLED TRUE) endif() if(WIN32 AND _OBS_CEF_SANDBOX_ENABLED AND NOT TARGET OBS::cef-sandbox) From 98cc448a87f0aaf370331bff8e9b2d4480bd32e9 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Sun, 20 Sep 2026 11:28:45 +0200 Subject: [PATCH 05/20] Update obs-browser sandbox hardening --- plugins/obs-browser | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/obs-browser b/plugins/obs-browser index 9377dc167b85a4..f44c9c168c6648 160000 --- a/plugins/obs-browser +++ b/plugins/obs-browser @@ -1 +1 @@ -Subproject commit 9377dc167b85a42ad3485ce17613ee80d60e8a7d +Subproject commit f44c9c168c6648941cb7ce691694572dc5d415ec From 6ea2a7c0a5c82bb78c6f7495d59565f25f30f852 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Sun, 20 Sep 2026 11:40:22 +0200 Subject: [PATCH 06/20] Fix obs-browser submodule revision --- plugins/obs-browser | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/obs-browser b/plugins/obs-browser index f44c9c168c6648..f44c9c18ac9330 160000 --- a/plugins/obs-browser +++ b/plugins/obs-browser @@ -1 +1 @@ -Subproject commit f44c9c168c6648941cb7ce691694572dc5d415ec +Subproject commit f44c9c18ac9330bfed78ccd62965dd9b22a3d06c From f6141a5a51b46279bb19ff1b460e091e3b5b8e26 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Sun, 20 Sep 2026 11:44:00 +0200 Subject: [PATCH 07/20] Update obs-browser CI hardening --- plugins/obs-browser | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/obs-browser b/plugins/obs-browser index f44c9c18ac9330..01e44b6af86e36 160000 --- a/plugins/obs-browser +++ b/plugins/obs-browser @@ -1 +1 @@ -Subproject commit f44c9c18ac9330bfed78ccd62965dd9b22a3d06c +Subproject commit 01e44b6af86e3632f3ea59b4efba34a8170793dc From 488e9eb54be0b7f7cf4ff2bd931f3828d994d4b6 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Sat, 19 Sep 2026 00:56:11 +0200 Subject: [PATCH 08/20] Update Windows x64 to CEF 6613 --- .github/actions/streamlabs-windows-artifacts/action.yaml | 8 ++++++-- .github/workflows/main.yml | 2 +- CMakePresets.json | 7 +++++-- cmake/common/buildspec_common.cmake | 4 ++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/actions/streamlabs-windows-artifacts/action.yaml b/.github/actions/streamlabs-windows-artifacts/action.yaml index 75a2bbb61650a6..27b772b31524c9 100644 --- a/.github/actions/streamlabs-windows-artifacts/action.yaml +++ b/.github/actions/streamlabs-windows-artifacts/action.yaml @@ -66,8 +66,12 @@ runs: if ('${{ inputs.target }}' -eq 'x64') { $presets = Get-Content -LiteralPath 'CMakePresets.json' -Raw | ConvertFrom-Json $dependencyPreset = $presets.configurePresets | Where-Object name -eq 'dependencies' - $cefVersion = [string]$dependencyPreset.vendor.'obsproject.com/obs-studio'.dependencies.cef.version - $cefHash = [string]$dependencyPreset.vendor.'obsproject.com/obs-studio'.dependencies.cef.hashes.'windows-x64' + $cefDependency = $dependencyPreset.vendor.'obsproject.com/obs-studio'.dependencies.cef + $cefVersion = [string]$cefDependency.version + if ($cefDependency.versions -And $cefDependency.versions.'windows-x64') { + $cefVersion = [string]$cefDependency.versions.'windows-x64' + } + $cefHash = [string]$cefDependency.hashes.'windows-x64' $cefRootPath = Join-Path '.deps' "cef_binary_${cefVersion}_windows_x64" if (-Not (Test-Path -LiteralPath $cefRootPath -PathType Container)) { throw "The configured Windows x64 CEF dependency directory is missing: $cefRootPath" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e527a4aba2f76d..5530e872549da7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ env: PACKAGE_NAME: "libobs" CACHE_REVISION: '006' CEF_BUILD_VERSION_LINUX: '5060' - CEF_BUILD_VERSION_WIN: '6533' + CEF_BUILD_VERSION_WIN: '6613' QT_VERSION_MAC: '6.4.3' QT_VERSION_WIN: '6.4.3' DEPS_VERSION_WIN: '2023-08-31sl1' diff --git a/CMakePresets.json b/CMakePresets.json index 8979cc99b60c2a..13a89473d3361f 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -53,6 +53,9 @@ }, "cef": { "version": "6533", + "versions": { + "windows-x64": "6613" + }, "baseUrl": "https://streamlabs-cef-dist.s3.us-west-2.amazonaws.com", "label": "Chromium Embedded Framework", "hashes": { @@ -60,7 +63,7 @@ "macos-arm64": "429b50e74f6c174dcfe2f14d8204b54add497eaafe117f7b69ce6bb2354d2626", "ubuntu-x86_64": "7963335519a19ccdc5233f7334c5ab023026e2f3e9a0cc417007c09d86608146", "ubuntu-aarch64": "642514469eaa29a5c887891084d2e73f7dc2d7405f7dfa7726b2dbc24b309999", - "windows-x64": "26460a8219b0d666427190efba47bae3bf3abc19589c19f9710b4cb75b8ae620", + "windows-x64": "dce2b2932239a85aa646ede0d9fc00c8ae0bbcb383959028ca7d48d503bacbbb", "windows-arm64": "df9df4bd85826b4c071c6db404fd59cf93efd9c58ec3ab64e204466ae19bb02a" }, "revision": { @@ -68,7 +71,7 @@ "macos-arm64": 5, "ubuntu-x86_64": 6, "ubuntu-aarch64": 6, - "windows-x64": 4 + "windows-x64": 1 } }, "openssl": { diff --git a/cmake/common/buildspec_common.cmake b/cmake/common/buildspec_common.cmake index 730eaf7afcda20..b4aa69f95bcdc2 100644 --- a/cmake/common/buildspec_common.cmake +++ b/cmake/common/buildspec_common.cmake @@ -126,6 +126,10 @@ function(_check_dependencies) string(JSON data GET ${dependency_data} ${dependency}) string(JSON version GET ${data} version) + string(JSON platform_version ERROR_VARIABLE platform_version_error GET ${data} versions ${platform}) + if(NOT platform_version_error) + set(version "${platform_version}") + endif() string(JSON hash GET ${data} hashes ${platform}) string(JSON url GET ${data} baseUrl) string(JSON label GET ${data} label) From 22652cca679a0e0ec3c57550915e1fcbdbd80315 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Sun, 20 Sep 2026 11:46:39 +0200 Subject: [PATCH 09/20] Publish stripped CEF symbols to the symbol server --- .github/scripts/Create-CefPublicPdb.ps1 | 172 ++++++++++++++++++ .../workflows/streamlabs-windows-release.yaml | 20 +- 2 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 .github/scripts/Create-CefPublicPdb.ps1 diff --git a/.github/scripts/Create-CefPublicPdb.ps1 b/.github/scripts/Create-CefPublicPdb.ps1 new file mode 100644 index 00000000000000..14fb6cfa4da931 --- /dev/null +++ b/.github/scripts/Create-CefPublicPdb.ps1 @@ -0,0 +1,172 @@ +#Requires -Version 7.2 + +# Chromium links libcef with lld-link. Although that linker advertises +# /PDBSTRIPPED in its help output, Chromium 128's bundled version rejects the +# option as unimplemented. Produce the equivalent matching public PDB with the +# Windows SDK's supported post-link tool instead. + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string] $CefOutDirectory, + + [string] $PdbCopyPath, + + [string] $LlvmPdbUtilPath, + + [string] $OutputPath, + + [ValidateRange(1, [long]::MaxValue)] + [long] $MaximumBytes = 2147483647, + + [switch] $ReplaceExisting +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Resolve-Executable { + param( + [Parameter(Mandatory)] + [string] $Name, + + [string] $RequestedPath, + + [string[]] $Candidates = @() + ) + + if ($RequestedPath) { + return (Resolve-Path -LiteralPath $RequestedPath).Path + } + + $command = Get-Command $Name -ErrorAction SilentlyContinue + if ($command) { + return $command.Source + } + + foreach ($candidate in $Candidates) { + if ($candidate -and (Test-Path -LiteralPath $candidate -PathType Leaf)) { + return (Resolve-Path -LiteralPath $candidate).Path + } + } + + throw "Could not find $Name. Pass its path explicitly." +} + +function Get-PdbIdentity { + param( + [Parameter(Mandatory)] + [string] $PdbPath, + + [Parameter(Mandatory)] + [string] $PdbUtil + ) + + $summary = @(& $PdbUtil dump -summary $PdbPath 2>&1) + if ($LASTEXITCODE -ne 0) { + throw "llvm-pdbutil could not inspect '$PdbPath':`n$($summary -join "`n")" + } + + $summaryText = $summary -join "`n" + $guid = [regex]::Match($summaryText, '(?m)^\s*GUID:\s*(\{[^}]+\})\s*$') + $age = [regex]::Match($summaryText, '(?m)^\s*Age:\s*(\d+)\s*$') + $stripped = [regex]::Match($summaryText, '(?m)^\s*Is stripped:\s*(true|false)\s*$') + if (-not $guid.Success -or -not $age.Success -or -not $stripped.Success) { + throw "llvm-pdbutil returned an unrecognized summary for '$PdbPath'." + } + + return [pscustomobject]@{ + Guid = $guid.Groups[1].Value.ToUpperInvariant() + Age = [int] $age.Groups[1].Value + IsStripped = $stripped.Groups[1].Value -eq 'true' + } +} + +$cefOut = (Resolve-Path -LiteralPath $CefOutDirectory).Path +$cefRoot = (Resolve-Path -LiteralPath (Join-Path $cefOut '..')).Path +$cefSource = (Resolve-Path -LiteralPath (Join-Path $cefOut '..\..')).Path +$privatePdb = Join-Path $cefOut 'libcef.dll.pdb' +if (-not (Test-Path -LiteralPath $privatePdb -PathType Leaf)) { + throw "The CEF private PDB does not exist: $privatePdb" +} + +if (-not $OutputPath) { + $OutputPath = Join-Path $cefOut 'libcef.dll.public.pdb' +} elseif (-not [System.IO.Path]::IsPathRooted($OutputPath)) { + $OutputPath = Join-Path $cefOut $OutputPath +} +$outputDirectory = Split-Path -Parent $OutputPath +if (-not (Test-Path -LiteralPath $outputDirectory -PathType Container)) { + throw "The public PDB output directory does not exist: $outputDirectory" +} +$publicPdb = [System.IO.Path]::GetFullPath($OutputPath) +if ($publicPdb -eq $privatePdb) { + throw 'The public PDB output must not overwrite the private PDB.' +} +$relativeOutput = [System.IO.Path]::GetRelativePath($cefOut, $publicPdb) +if ( + [System.IO.Path]::IsPathRooted($relativeOutput) -or + $relativeOutput -eq '..' -or + $relativeOutput.StartsWith("..$([System.IO.Path]::DirectorySeparatorChar)") -or + $relativeOutput.StartsWith("..$([System.IO.Path]::AltDirectorySeparatorChar)") +) { + throw "The public PDB output must remain inside the CEF output directory: $cefOut" +} +if (Test-Path -LiteralPath $publicPdb) { + if (-not $ReplaceExisting) { + throw "The public PDB already exists. Pass -ReplaceExisting to regenerate it: $publicPdb" + } + Remove-Item -LiteralPath $publicPdb -Force +} + +$windowsKitsPdbCopy = if (${env:ProgramFiles(x86)}) { + Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\Debuggers\x64\pdbcopy.exe' +} +$pdbCopy = Resolve-Executable -Name 'pdbcopy.exe' -RequestedPath $PdbCopyPath -Candidates @($windowsKitsPdbCopy) +$pdbUtil = Resolve-Executable -Name 'llvm-pdbutil.exe' -RequestedPath $LlvmPdbUtilPath -Candidates @( + (Join-Path $cefSource 'third_party\llvm-build\Release+Asserts\bin\llvm-pdbutil.exe'), + (Join-Path $cefRoot 'third_party\llvm-build\Release+Asserts\bin\llvm-pdbutil.exe'), + (Join-Path $env:ProgramFiles 'LLVM\bin\llvm-pdbutil.exe'), + (Join-Path $env:ProgramFiles 'Microsoft Visual Studio\2022\Enterprise\VC\Tools\Llvm\x64\bin\llvm-pdbutil.exe'), + (Join-Path $env:ProgramFiles 'Microsoft Visual Studio\2022\Professional\VC\Tools\Llvm\x64\bin\llvm-pdbutil.exe'), + (Join-Path $env:ProgramFiles 'Microsoft Visual Studio\2022\Community\VC\Tools\Llvm\x64\bin\llvm-pdbutil.exe'), + (Join-Path $env:ProgramFiles 'Microsoft Visual Studio\2022\BuildTools\VC\Tools\Llvm\x64\bin\llvm-pdbutil.exe') +) + +$pdbCopyOutput = @(& $pdbCopy $privatePdb $publicPdb -p 2>&1) +if ($LASTEXITCODE -ne 0) { + throw ( + "pdbcopy failed. Older Windows SDK versions cannot read PDBs larger than 4 GiB; " + + "rebuild CEF with symbol_level=1 first.`n$($pdbCopyOutput -join "`n")" + ) +} +if (-not (Test-Path -LiteralPath $publicPdb -PathType Leaf)) { + throw "pdbcopy did not produce the requested public PDB: $publicPdb" +} + +$privateIdentity = Get-PdbIdentity -PdbPath $privatePdb -PdbUtil $pdbUtil +$publicIdentity = Get-PdbIdentity -PdbPath $publicPdb -PdbUtil $pdbUtil +if ($privateIdentity.Guid -ne $publicIdentity.Guid -or $privateIdentity.Age -ne $publicIdentity.Age) { + throw 'The public PDB does not have the same GUID and age as the private PDB.' +} +if (-not $publicIdentity.IsStripped) { + throw 'The generated public PDB is not marked as stripped.' +} + +$publicFile = Get-Item -LiteralPath $publicPdb +if ($publicFile.Length -gt $MaximumBytes) { + throw "The public PDB exceeds the configured size limit of $MaximumBytes bytes: $publicPdb" +} + +@($privatePdb, $publicPdb) | ForEach-Object { + $item = Get-Item -LiteralPath $_ + $hash = Get-FileHash -LiteralPath $_ -Algorithm SHA256 + [pscustomobject]@{ + Path = $item.FullName + Bytes = $item.Length + SHA256 = $hash.Hash + Guid = $privateIdentity.Guid + Age = $privateIdentity.Age + Public = $_ -eq $publicPdb + } +} | Format-Table -AutoSize diff --git a/.github/workflows/streamlabs-windows-release.yaml b/.github/workflows/streamlabs-windows-release.yaml index b1deda0e60c134..25d7f4f31a1014 100644 --- a/.github/workflows/streamlabs-windows-release.yaml +++ b/.github/workflows/streamlabs-windows-release.yaml @@ -85,6 +85,23 @@ jobs: Write-Host "Unpacked $count pdb's" "PDB_PATH=$target" | Out-File -FilePath $env:GITHUB_ENV -Append + - name: Create public CEF symbols + run: | + $cefPdbs = @(Get-ChildItem -LiteralPath $env:PDB_PATH -Filter 'libcef.dll.pdb' -Recurse -File) + if ($cefPdbs.Count -ne 1) { + throw "Expected one libcef.dll.pdb in the symbol artifact; found $($cefPdbs.Count)" + } + + $privatePdb = $cefPdbs[0] + $publicPdb = Join-Path $privatePdb.DirectoryName 'libcef.dll.public.pdb' + & ./.github/scripts/Create-CefPublicPdb.ps1 ` + -CefOutDirectory $privatePdb.DirectoryName ` + -OutputPath $publicPdb ` + -MaximumBytes 2147483647 + + Remove-Item -LiteralPath $privatePdb.FullName -Force + Move-Item -LiteralPath $publicPdb -Destination $privatePdb.FullName + - name: Symbol Server Integration 🪲 uses: ./.github/actions/upload-debug-symbols with: @@ -93,9 +110,6 @@ jobs: repoName: obs-studio repoBranch: ${{ github.sha }} pdbPaths: ${{ env.PDB_PATH }} - # CEF's 5+ GiB PDB exceeds the CAB format's per-file size limit. It - # remains available in the build's debug-symbol artifact. - excludePdbNames: libcef.dll.pdb symsrvRef: ${{ inputs.symsrvRef }} forceUpload: ${{ inputs.forceUpload }} symsrvDebug: ${{ inputs.symsrvDebug }} From 831c294cad737432a7d05f43854b7384729d9b7b Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Sun, 20 Sep 2026 16:24:16 +0200 Subject: [PATCH 10/20] Add compatible CEF PDB relink helper --- .../scripts/Relink-CefWithCompatiblePdb.ps1 | 384 ++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 .github/scripts/Relink-CefWithCompatiblePdb.ps1 diff --git a/.github/scripts/Relink-CefWithCompatiblePdb.ps1 b/.github/scripts/Relink-CefWithCompatiblePdb.ps1 new file mode 100644 index 00000000000000..9de72d288da305 --- /dev/null +++ b/.github/scripts/Relink-CefWithCompatiblePdb.ps1 @@ -0,0 +1,384 @@ +#Requires -Version 7.2 + +<# +.SYNOPSIS +Relinks an existing Chromium/CEF libcef.dll with 4 KiB PDB pages. + +.DESCRIPTION +Chromium 128 writes 8 KiB PDB pages by default. Older pdbcopy readers reject +those PDBs with EC_FORMAT. This helper obtains the exact libcef link command +from Ninja, copies (but never modifies) libcef.dll.rsp, changes its one +/pdbpagesize:8192 flag to /pdbpagesize:4096, and relinks into a separate output +directory. It then invokes Create-CefPublicPdb.ps1, which validates the public +PDB's GUID, age, stripped state, and configured size limit. + +No build target is run: `ninja -t commands` is read-only, all linker output is +redirected to OutputDirectory, and the original response file is hash-checked +before and after the relink. +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string] $CefReleaseBuildDirectory, + + [Parameter(Mandatory)] + [string] $OutputDirectory, + + [string] $NinjaPath, + + [string] $LinkerPath, + + [string] $PdbCopyPath, + + [string] $LlvmPdbUtilPath, + + [string] $NinjaTarget = 'libcef.dll', + + [ValidateRange(1, [long]::MaxValue)] + [long] $MaximumPublicPdbBytes = 2147483647, + + [switch] $ReplaceExistingOutputs, + + [switch] $KeepTemporaryResponseFile +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Resolve-Executable { + param( + [Parameter(Mandatory)] + [string] $Name, + + [string] $RequestedPath, + + [string[]] $Candidates = @() + ) + + if ($RequestedPath) { + if (-not (Test-Path -LiteralPath $RequestedPath -PathType Leaf)) { + throw "The requested $Name does not exist: $RequestedPath" + } + return (Resolve-Path -LiteralPath $RequestedPath).Path + } + + $command = Get-Command $Name -CommandType Application -ErrorAction SilentlyContinue + if ($command) { + return $command.Source + } + + foreach ($candidate in $Candidates) { + if ($candidate -and (Test-Path -LiteralPath $candidate -PathType Leaf)) { + return (Resolve-Path -LiteralPath $candidate).Path + } + } + + throw "Could not find $Name. Pass its path explicitly." +} + +function Test-IsSameOrChildPath { + param( + [Parameter(Mandatory)] + [string] $Candidate, + + [Parameter(Mandatory)] + [string] $Parent + ) + + $candidatePath = [System.IO.Path]::GetFullPath($Candidate).TrimEnd('\', '/') + $parentPath = [System.IO.Path]::GetFullPath($Parent).TrimEnd('\', '/') + $comparison = [System.StringComparison]::OrdinalIgnoreCase + return $candidatePath.Equals($parentPath, $comparison) -or + $candidatePath.StartsWith("$parentPath$([System.IO.Path]::DirectorySeparatorChar)", $comparison) +} + +function Resolve-PathFromBuildDirectory { + param( + [Parameter(Mandatory)] + [string] $Path, + + [Parameter(Mandatory)] + [string] $BuildDirectory + ) + + $candidate = if ([System.IO.Path]::IsPathRooted($Path)) { + $Path + } else { + Join-Path $BuildDirectory $Path + } + $fullPath = [System.IO.Path]::GetFullPath($candidate) + if (-not (Test-Path -LiteralPath $fullPath -PathType Leaf)) { + throw "The linker reported by Ninja does not exist: $fullPath" + } + return (Resolve-Path -LiteralPath $fullPath).Path +} + +function Split-WindowsCommandLine { + param( + [Parameter(Mandatory)] + [string] $CommandLine + ) + + if (-not ('CefPdbRelink.NativeCommandLine' -as [type])) { + Add-Type -TypeDefinition @' +using System; +using System.Runtime.InteropServices; + +namespace CefPdbRelink { + public static class NativeCommandLine { + [DllImport("shell32.dll", SetLastError = true)] + public static extern IntPtr CommandLineToArgvW( + [MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, + out int pNumArgs); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr LocalFree(IntPtr hMem); + } +} +'@ + } + + $argumentCount = 0 + $argv = [CefPdbRelink.NativeCommandLine]::CommandLineToArgvW($CommandLine, [ref] $argumentCount) + if ($argv -eq [IntPtr]::Zero -or $argumentCount -lt 1) { + throw "Could not parse Ninja's command line: $CommandLine" + } + + try { + $arguments = for ($index = 0; $index -lt $argumentCount; $index++) { + $argumentPointer = [Runtime.InteropServices.Marshal]::ReadIntPtr( + $argv, $index * [IntPtr]::Size) + [Runtime.InteropServices.Marshal]::PtrToStringUni($argumentPointer) + } + return @($arguments) + } finally { + [void] [CefPdbRelink.NativeCommandLine]::LocalFree($argv) + } +} + +function Get-OptionValue { + param( + [Parameter(Mandatory)] + [string] $Argument, + + [Parameter(Mandatory)] + [string] $OptionName + ) + + if (-not $Argument.StartsWith("/${OptionName}:", [System.StringComparison]::OrdinalIgnoreCase)) { + return $null + } + return $Argument.Substring($OptionName.Length + 2) +} + +function Write-CommandDiagnostic { + param( + [Parameter(Mandatory)] + [string] $Executable, + + [Parameter(Mandatory)] + [string[]] $Arguments + ) + + $formatArgument = { + param([string] $Argument) + if ($Argument -match '[\s"]') { + return '"' + $Argument.Replace('"', '\"') + '"' + } + return $Argument + } + $rendered = @($Executable) + @($Arguments | ForEach-Object { & $formatArgument $_ }) + Write-Host ("Relinking with: " + ($rendered -join ' ')) +} + +$cefOut = (Resolve-Path -LiteralPath $CefReleaseBuildDirectory).Path +$cefSource = (Resolve-Path -LiteralPath (Join-Path $cefOut '..\..')).Path +$outputFull = [System.IO.Path]::GetFullPath($OutputDirectory) +$outputParent = Split-Path -Parent $outputFull +if (-not (Test-Path -LiteralPath $outputParent -PathType Container)) { + throw "The output directory's parent does not exist: $outputParent" +} + +if ((Test-IsSameOrChildPath -Candidate $outputFull -Parent $cefOut) -or + (Test-IsSameOrChildPath -Candidate $cefOut -Parent $outputFull)) { + throw 'OutputDirectory must not be the CEF build directory, its child, or its parent.' +} + +if (Test-Path -LiteralPath $outputFull) { + if (-not (Test-Path -LiteralPath $outputFull -PathType Container)) { + throw "OutputDirectory exists but is not a directory: $outputFull" + } + if (-not $ReplaceExistingOutputs -and (Get-ChildItem -LiteralPath $outputFull -Force | Select-Object -First 1)) { + throw "OutputDirectory is not empty. Use a fresh directory or pass -ReplaceExistingOutputs: $outputFull" + } +} else { + New-Item -ItemType Directory -Path $outputFull | Out-Null +} +$output = (Resolve-Path -LiteralPath $outputFull).Path + +$sourceRsp = Join-Path $cefOut 'libcef.dll.rsp' +if (-not (Test-Path -LiteralPath $sourceRsp -PathType Leaf)) { + throw "The Ninja-generated response file does not exist: $sourceRsp" +} +$sourceRspHashBefore = (Get-FileHash -LiteralPath $sourceRsp -Algorithm SHA256).Hash + +$relinkedDll = Join-Path $output 'libcef.dll' +$relinkedImportLibrary = Join-Path $output 'libcef.dll.lib' +$privatePdb = Join-Path $output 'libcef.dll.pdb' +$publicPdb = Join-Path $output 'libcef.dll.public.pdb' +$temporaryRsp = Join-Path $output 'libcef.dll.pdbpagesize-4096.rsp' +$generatedOutputs = @($relinkedDll, $relinkedImportLibrary, $privatePdb, $publicPdb, $temporaryRsp) +if (-not $ReplaceExistingOutputs) { + $existingGeneratedOutput = $generatedOutputs | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1 + if ($existingGeneratedOutput) { + throw "Refusing to overwrite an existing relink output. Pass -ReplaceExistingOutputs to replace it: $existingGeneratedOutput" + } +} + +$ninja = Resolve-Executable -Name 'ninja.exe' -RequestedPath $NinjaPath -Candidates @( + (Join-Path $cefSource 'third_party\ninja\ninja.exe'), + (Join-Path $cefSource 'third_party\depot_tools\ninja.exe'), + (Join-Path $cefSource 'third_party\depot_tools\ninja.bat') +) +$ninjaOutput = @(& $ninja -C $cefOut -t commands $NinjaTarget 2>&1) +if ($LASTEXITCODE -ne 0) { + throw "Ninja could not print commands for '$NinjaTarget':`n$($ninjaOutput -join "`n")" +} + +$linkCandidates = foreach ($lineObject in $ninjaOutput) { + $line = $lineObject.ToString() + if ($line -notmatch '(?i)lld-link(?:\.exe)?') { + continue + } + + $tokens = Split-WindowsCommandLine -CommandLine $line + if ($tokens.Count -lt 2 -or $tokens[0] -notmatch '(?i)lld-link(?:\.exe)?$') { + continue + } + + $outArguments = @($tokens | Where-Object { (Get-OptionValue -Argument $_ -OptionName 'OUT') }) + $pdbArguments = @($tokens | Where-Object { (Get-OptionValue -Argument $_ -OptionName 'PDB') }) + $implibArguments = @($tokens | Where-Object { (Get-OptionValue -Argument $_ -OptionName 'IMPLIB') }) + $responseArguments = @($tokens | Where-Object { + $_.StartsWith('@') -and + ([System.IO.Path]::GetFileName($_.Substring(1)) -ieq 'libcef.dll.rsp') + }) + + if ($outArguments.Count -ne 1 -or $pdbArguments.Count -ne 1 -or + $implibArguments.Count -ne 1 -or $responseArguments.Count -ne 1) { + continue + } + if ([System.IO.Path]::GetFileName((Get-OptionValue -Argument $outArguments[0] -OptionName 'OUT')) -ine 'libcef.dll') { + continue + } + + [pscustomobject]@{ + Line = $line + Tokens = $tokens + ResponseArgument = $responseArguments[0] + } +} + +if ($linkCandidates.Count -ne 1) { + $lldLines = @($ninjaOutput | Where-Object { $_.ToString() -match '(?i)lld-link(?:\.exe)?' }) + throw ( + "Expected exactly one libcef lld-link command from Ninja; found $($linkCandidates.Count). " + + "Pass -NinjaTarget if this build uses another target. lld-link lines were:`n$($lldLines -join "`n")" + ) +} + +$linkCandidate = $linkCandidates[0] +$ninjaLinker = Resolve-PathFromBuildDirectory -Path $linkCandidate.Tokens[0] -BuildDirectory $cefOut +$linker = Resolve-Executable -Name 'lld-link.exe' -RequestedPath $LinkerPath -Candidates @($ninjaLinker) + +$sourceRspText = [System.IO.File]::ReadAllText($sourceRsp) +$oldPageSize = [regex]::Matches($sourceRspText, '(?i)/pdbpagesize:8192\b') +$newPageSize = [regex]::Matches($sourceRspText, '(?i)/pdbpagesize:4096\b') +if ($oldPageSize.Count -ne 1 -or $newPageSize.Count -ne 0) { + throw ( + "Expected exactly one /pdbpagesize:8192 and no existing /pdbpagesize:4096 in '$sourceRsp'; " + + "found 8192=$($oldPageSize.Count), 4096=$($newPageSize.Count)." + ) +} +$temporaryRspText = [regex]::Replace($sourceRspText, '(?i)/pdbpagesize:8192\b', '/pdbpagesize:4096') +[System.IO.File]::WriteAllText($temporaryRsp, $temporaryRspText, [System.Text.UTF8Encoding]::new($false)) + +$linkArguments = [System.Collections.Generic.List[string]]::new() +$outCount = 0 +$implibCount = 0 +$pdbCount = 0 +foreach ($argument in $linkCandidate.Tokens | Select-Object -Skip 1) { + if ((Get-OptionValue -Argument $argument -OptionName 'OUT')) { + $outCount++ + $linkArguments.Add("/OUT:$relinkedDll") + } elseif ((Get-OptionValue -Argument $argument -OptionName 'IMPLIB')) { + $implibCount++ + $linkArguments.Add("/IMPLIB:$relinkedImportLibrary") + } elseif ((Get-OptionValue -Argument $argument -OptionName 'PDB')) { + $pdbCount++ + $linkArguments.Add("/PDB:$privatePdb") + } elseif ($argument -eq $linkCandidate.ResponseArgument) { + $linkArguments.Add("@$temporaryRsp") + } else { + $linkArguments.Add($argument) + } +} +if ($outCount -ne 1 -or $implibCount -ne 1 -or $pdbCount -ne 1) { + throw "The selected Ninja command did not contain one each of /OUT, /IMPLIB, and /PDB." +} + +try { + Write-Host "Source response file: $sourceRsp" + Write-Host "Temporary response file: $temporaryRsp" + Write-CommandDiagnostic -Executable $linker -Arguments $linkArguments.ToArray() + Push-Location $cefOut + try { + # Invoke lld directly with an argument array. Do not evaluate Ninja's shell text. + & $linker $linkArguments.ToArray() + if ($LASTEXITCODE -ne 0) { + throw "lld-link failed with exit code $LASTEXITCODE." + } + } finally { + Pop-Location + } + + foreach ($requiredOutput in @($relinkedDll, $relinkedImportLibrary, $privatePdb)) { + if (-not (Test-Path -LiteralPath $requiredOutput -PathType Leaf)) { + throw "lld-link did not produce the expected output: $requiredOutput" + } + } + + $pdbUtil = Resolve-Executable -Name 'llvm-pdbutil.exe' -RequestedPath $LlvmPdbUtilPath -Candidates @( + (Join-Path $cefSource 'third_party\llvm-build\Release+Asserts\bin\llvm-pdbutil.exe') + ) + $createPublicPdb = Join-Path $PSScriptRoot 'Create-CefPublicPdb.ps1' + if (-not (Test-Path -LiteralPath $createPublicPdb -PathType Leaf)) { + throw "The public PDB helper is missing: $createPublicPdb" + } + $publicPdbArguments = @{ + CefOutDirectory = $output + LlvmPdbUtilPath = $pdbUtil + OutputPath = $publicPdb + MaximumBytes = $MaximumPublicPdbBytes + } + if ($PdbCopyPath) { + $publicPdbArguments.PdbCopyPath = $PdbCopyPath + } + if ($ReplaceExistingOutputs) { + $publicPdbArguments.ReplaceExisting = $true + } + & $createPublicPdb @publicPdbArguments + + $sourceRspHashAfter = (Get-FileHash -LiteralPath $sourceRsp -Algorithm SHA256).Hash + if ($sourceRspHashBefore -ne $sourceRspHashAfter) { + throw "The source response file changed during relinking: $sourceRsp" + } + + Get-Item -LiteralPath $relinkedDll, $relinkedImportLibrary, $privatePdb, $publicPdb | + Select-Object FullName, Length, LastWriteTime +} finally { + if (-not $KeepTemporaryResponseFile -and (Test-Path -LiteralPath $temporaryRsp -PathType Leaf)) { + Remove-Item -LiteralPath $temporaryRsp -Force + } +} From 343b64d701c6346836e2e6cfc2fb32ab3f62982d Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Sun, 20 Sep 2026 16:29:49 +0200 Subject: [PATCH 11/20] Harden CEF relink output validation --- .../scripts/Relink-CefWithCompatiblePdb.ps1 | 158 ++++++++++++++++-- 1 file changed, 145 insertions(+), 13 deletions(-) diff --git a/.github/scripts/Relink-CefWithCompatiblePdb.ps1 b/.github/scripts/Relink-CefWithCompatiblePdb.ps1 index 9de72d288da305..19b515406dc37a 100644 --- a/.github/scripts/Relink-CefWithCompatiblePdb.ps1 +++ b/.github/scripts/Relink-CefWithCompatiblePdb.ps1 @@ -9,8 +9,9 @@ Chromium 128 writes 8 KiB PDB pages by default. Older pdbcopy readers reject those PDBs with EC_FORMAT. This helper obtains the exact libcef link command from Ninja, copies (but never modifies) libcef.dll.rsp, changes its one /pdbpagesize:8192 flag to /pdbpagesize:4096, and relinks into a separate output -directory. It then invokes Create-CefPublicPdb.ps1, which validates the public -PDB's GUID, age, stripped state, and configured size limit. +directory. It verifies the relinked private PDB has 4 KiB blocks, then invokes +Create-CefPublicPdb.ps1, which validates the public PDB's GUID, age, stripped +state, and configured size limit. No build target is run: `ninja -t commands` is read-only, all linker output is redirected to OutputDirectory, and the original response file is hash-checked @@ -93,6 +94,103 @@ function Test-IsSameOrChildPath { $candidatePath.StartsWith("$parentPath$([System.IO.Path]::DirectorySeparatorChar)", $comparison) } +function Resolve-ExistingPhysicalPath { + param( + [Parameter(Mandatory)] + [string] $Path + ) + + $fullPath = [System.IO.Path]::GetFullPath($Path) + if (-not (Test-Path -LiteralPath $fullPath)) { + throw "Cannot canonicalize a path that does not exist: $fullPath" + } + + $root = [System.IO.Path]::GetPathRoot($fullPath) + $remainder = $fullPath.Substring($root.Length).TrimStart('\', '/') + $current = $root + if ($remainder) { + foreach ($component in ($remainder -split '[\\/]+')) { + $current = Join-Path $current $component + $item = Get-Item -LiteralPath $current -Force + if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { + $target = $item.ResolveLinkTarget($true) + if ($null -eq $target) { + throw "Could not resolve reparse point while canonicalizing: $current" + } + $current = $target.FullName + } + } + } + return [System.IO.Path]::GetFullPath($current) +} + +function Resolve-ProspectivePhysicalPath { + param( + [Parameter(Mandatory)] + [string] $Path + ) + + $fullPath = [System.IO.Path]::GetFullPath($Path) + $missingComponents = [System.Collections.Generic.List[string]]::new() + $existingPath = $fullPath + while (-not (Test-Path -LiteralPath $existingPath)) { + $leaf = Split-Path -Leaf $existingPath + if (-not $leaf) { + throw "Could not find an existing parent while canonicalizing: $fullPath" + } + $missingComponents.Add($leaf) + $parent = Split-Path -Parent $existingPath + if (-not $parent -or $parent -eq $existingPath) { + throw "Could not find an existing parent while canonicalizing: $fullPath" + } + $existingPath = $parent + } + + $physicalPath = Resolve-ExistingPhysicalPath -Path $existingPath + for ($index = $missingComponents.Count - 1; $index -ge 0; $index--) { + $physicalPath = Join-Path $physicalPath $missingComponents[$index] + } + return [System.IO.Path]::GetFullPath($physicalPath) +} + +function Assert-OutputOutsideBuild { + param( + [Parameter(Mandatory)] + [string] $Candidate, + + [Parameter(Mandatory)] + [string] $CefOutCanonical, + + [Parameter(Mandatory)] + [string] $Description + ) + + $candidateCanonical = Resolve-ProspectivePhysicalPath -Path $Candidate + if ((Test-IsSameOrChildPath -Candidate $candidateCanonical -Parent $CefOutCanonical) -or + (Test-IsSameOrChildPath -Candidate $CefOutCanonical -Parent $candidateCanonical)) { + throw "$Description resolves to or overlaps the CEF build directory: $candidateCanonical" + } + return $candidateCanonical +} + +function Assert-GeneratedOutputParentsOutsideBuild { + param( + [Parameter(Mandatory)] + [string[]] $GeneratedPaths, + + [Parameter(Mandatory)] + [string] $CefOutCanonical + ) + + foreach ($generatedPath in $GeneratedPaths) { + $parent = Split-Path -Parent $generatedPath + $parentCanonical = Resolve-ExistingPhysicalPath -Path $parent + if (Test-IsSameOrChildPath -Candidate $parentCanonical -Parent $CefOutCanonical) { + throw "Generated output parent resolves inside the CEF build directory: $generatedPath -> $parentCanonical" + } + } +} + function Resolve-PathFromBuildDirectory { param( [Parameter(Mandatory)] @@ -109,11 +207,31 @@ function Resolve-PathFromBuildDirectory { } $fullPath = [System.IO.Path]::GetFullPath($candidate) if (-not (Test-Path -LiteralPath $fullPath -PathType Leaf)) { - throw "The linker reported by Ninja does not exist: $fullPath" + throw "The path reported by Ninja does not exist: $fullPath" } return (Resolve-Path -LiteralPath $fullPath).Path } +function Get-PdbBlockSize { + param( + [Parameter(Mandatory)] + [string] $PdbPath, + + [Parameter(Mandatory)] + [string] $PdbUtil + ) + + $summary = @(& $PdbUtil dump -summary $PdbPath 2>&1) + if ($LASTEXITCODE -ne 0) { + throw "llvm-pdbutil could not inspect '$PdbPath':`n$($summary -join "`n")" + } + $match = [regex]::Match(($summary -join "`n"), '(?m)^\s*Block Size:\s*(\d+)\s*$') + if (-not $match.Success) { + throw "llvm-pdbutil returned no recognizable PDB block size for '$PdbPath'." + } + return [int] $match.Groups[1].Value +} + function Split-WindowsCommandLine { param( [Parameter(Mandatory)] @@ -193,6 +311,7 @@ function Write-CommandDiagnostic { } $cefOut = (Resolve-Path -LiteralPath $CefReleaseBuildDirectory).Path +$cefOutCanonical = Resolve-ExistingPhysicalPath -Path $cefOut $cefSource = (Resolve-Path -LiteralPath (Join-Path $cefOut '..\..')).Path $outputFull = [System.IO.Path]::GetFullPath($OutputDirectory) $outputParent = Split-Path -Parent $outputFull @@ -200,10 +319,7 @@ if (-not (Test-Path -LiteralPath $outputParent -PathType Container)) { throw "The output directory's parent does not exist: $outputParent" } -if ((Test-IsSameOrChildPath -Candidate $outputFull -Parent $cefOut) -or - (Test-IsSameOrChildPath -Candidate $cefOut -Parent $outputFull)) { - throw 'OutputDirectory must not be the CEF build directory, its child, or its parent.' -} +$null = Assert-OutputOutsideBuild -Candidate $outputFull -CefOutCanonical $cefOutCanonical -Description 'OutputDirectory' if (Test-Path -LiteralPath $outputFull) { if (-not (Test-Path -LiteralPath $outputFull -PathType Container)) { @@ -216,12 +332,8 @@ if (Test-Path -LiteralPath $outputFull) { New-Item -ItemType Directory -Path $outputFull | Out-Null } $output = (Resolve-Path -LiteralPath $outputFull).Path - -$sourceRsp = Join-Path $cefOut 'libcef.dll.rsp' -if (-not (Test-Path -LiteralPath $sourceRsp -PathType Leaf)) { - throw "The Ninja-generated response file does not exist: $sourceRsp" -} -$sourceRspHashBefore = (Get-FileHash -LiteralPath $sourceRsp -Algorithm SHA256).Hash +$outputCanonical = Resolve-ExistingPhysicalPath -Path $output +$null = Assert-OutputOutsideBuild -Candidate $outputCanonical -CefOutCanonical $cefOutCanonical -Description 'OutputDirectory after creation' $relinkedDll = Join-Path $output 'libcef.dll' $relinkedImportLibrary = Join-Path $output 'libcef.dll.lib' @@ -229,6 +341,7 @@ $privatePdb = Join-Path $output 'libcef.dll.pdb' $publicPdb = Join-Path $output 'libcef.dll.public.pdb' $temporaryRsp = Join-Path $output 'libcef.dll.pdbpagesize-4096.rsp' $generatedOutputs = @($relinkedDll, $relinkedImportLibrary, $privatePdb, $publicPdb, $temporaryRsp) +Assert-GeneratedOutputParentsOutsideBuild -GeneratedPaths $generatedOutputs -CefOutCanonical $cefOutCanonical if (-not $ReplaceExistingOutputs) { $existingGeneratedOutput = $generatedOutputs | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1 if ($existingGeneratedOutput) { @@ -292,6 +405,13 @@ $linkCandidate = $linkCandidates[0] $ninjaLinker = Resolve-PathFromBuildDirectory -Path $linkCandidate.Tokens[0] -BuildDirectory $cefOut $linker = Resolve-Executable -Name 'lld-link.exe' -RequestedPath $LinkerPath -Candidates @($ninjaLinker) +$sourceRspArgument = $linkCandidate.ResponseArgument.Substring(1) +$sourceRsp = Resolve-PathFromBuildDirectory -Path $sourceRspArgument -BuildDirectory $cefOut +if (-not (Test-Path -LiteralPath $sourceRsp -PathType Leaf)) { + throw "The response file selected from Ninja does not exist: $sourceRsp" +} +$sourceRspHashBefore = (Get-FileHash -LiteralPath $sourceRsp -Algorithm SHA256).Hash + $sourceRspText = [System.IO.File]::ReadAllText($sourceRsp) $oldPageSize = [regex]::Matches($sourceRspText, '(?i)/pdbpagesize:8192\b') $newPageSize = [regex]::Matches($sourceRspText, '(?i)/pdbpagesize:4096\b') @@ -352,6 +472,12 @@ try { $pdbUtil = Resolve-Executable -Name 'llvm-pdbutil.exe' -RequestedPath $LlvmPdbUtilPath -Candidates @( (Join-Path $cefSource 'third_party\llvm-build\Release+Asserts\bin\llvm-pdbutil.exe') ) + $privatePdbBlockSize = Get-PdbBlockSize -PdbPath $privatePdb -PdbUtil $pdbUtil + if ($privatePdbBlockSize -ne 4096) { + throw "Relinked private PDB has a $privatePdbBlockSize-byte block size, not the required 4096 bytes: $privatePdb" + } + Write-Host "Verified private PDB block size: $privatePdbBlockSize" + $createPublicPdb = Join-Path $PSScriptRoot 'Create-CefPublicPdb.ps1' if (-not (Test-Path -LiteralPath $createPublicPdb -PathType Leaf)) { throw "The public PDB helper is missing: $createPublicPdb" @@ -370,6 +496,12 @@ try { } & $createPublicPdb @publicPdbArguments + $publicPdbBlockSize = Get-PdbBlockSize -PdbPath $publicPdb -PdbUtil $pdbUtil + if ($publicPdbBlockSize -ne 4096) { + throw "Generated public PDB has a $publicPdbBlockSize-byte block size, not the required 4096 bytes: $publicPdb" + } + Write-Host "Verified public PDB block size: $publicPdbBlockSize" + $sourceRspHashAfter = (Get-FileHash -LiteralPath $sourceRsp -Algorithm SHA256).Hash if ($sourceRspHashBefore -ne $sourceRspHashAfter) { throw "The source response file changed during relinking: $sourceRsp" From 181e6aeda3115df67cb4911737238fca6405bb8d Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Thu, 1 Oct 2026 16:18:26 -0700 Subject: [PATCH 12/20] Align workflow metadata with CEF 6613 --- .github/workflows/main-streamlabs.yml | 4 ++-- .github/workflows/main.yml | 4 ++-- cmake/finders/FindCEF.cmake | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main-streamlabs.yml b/.github/workflows/main-streamlabs.yml index fe82319021d130..9547c5977f46c5 100644 --- a/.github/workflows/main-streamlabs.yml +++ b/.github/workflows/main-streamlabs.yml @@ -116,8 +116,8 @@ jobs: CefBuildConfig: Debug env: OS_TAG: "windows64" - CEF_VERSION: 6533 - CEF_REVISION: v4 + CEF_VERSION: 6613 + CEF_REVISION: v1 WIN_DEPS_VERSION: windows-deps-2024-05-08sl2-x64 WIN_DEPS_VERSION_X86: 2024-05-08 GRPC_VERSION: v1.47.0 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5530e872549da7..ecc6b067c207a3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -422,8 +422,8 @@ jobs: CefBuildConfig: Debug env: OS_TAG: "windows64" - CEF_VERSION: 6533 - CEF_REVISION: v4 + CEF_VERSION: 6613 + CEF_REVISION: v1 WIN_DEPS_VERSION: windows-deps-2024-05-08sl2-x64 WIN_DEPS_VERSION_X86: 2024-05-08 GRPC_VERSION: v1.47.0 diff --git a/cmake/finders/FindCEF.cmake b/cmake/finders/FindCEF.cmake index 3063c06efec1e7..e899651a852367 100644 --- a/cmake/finders/FindCEF.cmake +++ b/cmake/finders/FindCEF.cmake @@ -224,8 +224,8 @@ if(_CEF_WINDOWS_SANDBOX_REQUIRED) endif() if(NOT CEF_SANDBOX_STANDARD_LIBS) - # This fallback mirrors CEF 6533's cef_sandbox usage requirements for - # distributions that omit cmake/cef_variables.cmake. + # This fallback mirrors the supported Windows CEF distributions' sandbox + # usage requirements when cmake/cef_variables.cmake is absent. set( CEF_SANDBOX_STANDARD_LIBS Advapi32.lib From 5cc79ea90c7b655262f44a68833341459c595dea Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Fri, 2 Oct 2026 10:46:43 -0700 Subject: [PATCH 13/20] Address CEF sandbox review feedback --- .github/scripts/Relink-CefWithCompatiblePdb.ps1 | 6 +++++- .github/workflows/main-streamlabs.yml | 3 +-- cmake/finders/FindCEF.cmake | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/scripts/Relink-CefWithCompatiblePdb.ps1 b/.github/scripts/Relink-CefWithCompatiblePdb.ps1 index 19b515406dc37a..2f1d0e52e930d0 100644 --- a/.github/scripts/Relink-CefWithCompatiblePdb.ps1 +++ b/.github/scripts/Relink-CefWithCompatiblePdb.ps1 @@ -403,7 +403,11 @@ if ($linkCandidates.Count -ne 1) { $linkCandidate = $linkCandidates[0] $ninjaLinker = Resolve-PathFromBuildDirectory -Path $linkCandidate.Tokens[0] -BuildDirectory $cefOut -$linker = Resolve-Executable -Name 'lld-link.exe' -RequestedPath $LinkerPath -Candidates @($ninjaLinker) +$linker = if ($LinkerPath) { + Resolve-Executable -Name 'lld-link.exe' -RequestedPath $LinkerPath +} else { + $ninjaLinker +} $sourceRspArgument = $linkCandidate.ResponseArgument.Substring(1) $sourceRsp = Resolve-PathFromBuildDirectory -Path $sourceRspArgument -BuildDirectory $cefOut diff --git a/.github/workflows/main-streamlabs.yml b/.github/workflows/main-streamlabs.yml index 9547c5977f46c5..e24934adb6d898 100644 --- a/.github/workflows/main-streamlabs.yml +++ b/.github/workflows/main-streamlabs.yml @@ -1,12 +1,11 @@ name: 'CI Multiplatform Build' on: + # Tag builds and symbol uploads use push.yaml and streamlabs-windows-release.yaml. push: paths-ignore: - '**.md' branches: [ "skippy" ] - tags: - - '*' pull_request: paths-ignore: - '**.md' diff --git a/cmake/finders/FindCEF.cmake b/cmake/finders/FindCEF.cmake index e899651a852367..de5a84d57cdf57 100644 --- a/cmake/finders/FindCEF.cmake +++ b/cmake/finders/FindCEF.cmake @@ -76,7 +76,7 @@ find_path( ) set(_CEF_WINDOWS_SANDBOX_REQUIRED FALSE) -if(WIN32 AND ((CMAKE_SIZEOF_VOID_P EQUAL 8 AND ENABLE_BROWSER) OR CEF_REQUIRE_SANDBOX)) +if(WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 8 AND (ENABLE_BROWSER OR CEF_REQUIRE_SANDBOX)) set(_CEF_WINDOWS_SANDBOX_REQUIRED TRUE) endif() From 58a66c510340571a46b9e2ea3289e81444cacbe1 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Fri, 2 Oct 2026 12:14:08 -0700 Subject: [PATCH 14/20] Update obs-browser submodule to ABI logging fix --- plugins/obs-browser | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/obs-browser b/plugins/obs-browser index 01e44b6af86e36..1479433f0933d5 160000 --- a/plugins/obs-browser +++ b/plugins/obs-browser @@ -1 +1 @@ -Subproject commit 01e44b6af86e3632f3ea59b4efba34a8170793dc +Subproject commit 1479433f0933d56b4da3e9c281b1d892416795df From 526478affdf86b4ad5a10ca3e8fdec66eea28891 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Fri, 2 Oct 2026 12:43:06 -0700 Subject: [PATCH 15/20] cmake: refresh CEF discovery when selected dependency changes --- cmake/common/buildspec_common.cmake | 21 ++++++++++++++++++-- cmake/finders/FindCEF.cmake | 30 +++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/cmake/common/buildspec_common.cmake b/cmake/common/buildspec_common.cmake index b4aa69f95bcdc2..4ae38f3f70e841 100644 --- a/cmake/common/buildspec_common.cmake +++ b/cmake/common/buildspec_common.cmake @@ -135,6 +135,10 @@ function(_check_dependencies) string(JSON label GET ${data} label) string(JSON revision ERROR_VARIABLE error GET ${data} revision ${platform}) + if(dependency STREQUAL cef) + set(OBS_CEF_SELECTED_HASH "${hash}" CACHE INTERNAL "Selected CEF dependency archive hash" FORCE) + endif() + message(STATUS "Setting up ${label} (${arch})") set(file "${${dependency}_filename}") @@ -170,8 +174,21 @@ function(_check_dependencies) elseif(dependency STREQUAL cef) if(NOT ENABLE_BROWSER) set(skip TRUE) - elseif(OBS_DEPENDENCY_${dependency}_${arch}_HASH STREQUAL ${hash} AND (CEF_ROOT_DIR AND EXISTS "${CEF_ROOT_DIR}")) - set(skip TRUE) + elseif(OBS_DEPENDENCY_${dependency}_${arch}_HASH STREQUAL ${hash}) + # A matching archive marker can outlive this build tree's old CEF_ROOT_DIR. + # Keep explicit roots outside .deps, but select the preset's extracted + # directory for roots managed by this dependency setup. + if(CEF_ROOT_DIR) + cmake_path(IS_PREFIX dependencies_dir "${CEF_ROOT_DIR}" NORMALIZE _cef_managed_root) + else() + set(_cef_managed_root TRUE) + endif() + if(_cef_managed_root AND EXISTS "${dependencies_dir}/${destination}") + set(CEF_ROOT_DIR "${dependencies_dir}/${destination}" CACHE PATH "CEF root directory" FORCE) + set(skip TRUE) + elseif(NOT _cef_managed_root AND EXISTS "${CEF_ROOT_DIR}") + set(skip TRUE) + endif() endif() endif() diff --git a/cmake/finders/FindCEF.cmake b/cmake/finders/FindCEF.cmake index de5a84d57cdf57..9c6549cf1167a7 100644 --- a/cmake/finders/FindCEF.cmake +++ b/cmake/finders/FindCEF.cmake @@ -68,6 +68,36 @@ if(NOT DEFINED CEF_ROOT_DIR OR CEF_ROOT_DIR STREQUAL "") ) endif() +# CMake's find_* cache entries do not follow CEF_ROOT_DIR when a build tree is +# reconfigured for another distribution. The selected archive can also change +# without changing its extraction directory. +set(_CEF_DISCOVERY_KEY "${CEF_ROOT_DIR}|${OBS_CEF_SELECTED_HASH}") +if(NOT DEFINED _CEF_CACHED_DISCOVERY_KEY OR NOT "${_CEF_CACHED_DISCOVERY_KEY}" STREQUAL "${_CEF_DISCOVERY_KEY}") + foreach( + _CEF_CACHED_RESULT + IN + ITEMS + CEF_INCLUDE_DIR + CEF_IMPLIB_RELEASE + CEF_LIBRARY_RELEASE + CEF_LIBRARY_DEBUG + CEF_LIBRARY_WRAPPER_RELEASE + CEF_LIBRARY_WRAPPER_DEBUG + CEF_SANDBOX_LIBRARY_RELEASE + CEF_LIBRARY + ) + unset(${_CEF_CACHED_RESULT}) + unset(${_CEF_CACHED_RESULT} CACHE) + endforeach() + set( + _CEF_CACHED_DISCOVERY_KEY + "${_CEF_DISCOVERY_KEY}" + CACHE INTERNAL + "CEF root and archive hash used for discovery" + FORCE + ) +endif() + find_path( CEF_INCLUDE_DIR "cef_version.h" From b0ffe7440add6074c81fdfdb906bda5ccf1d0805 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Fri, 2 Oct 2026 14:17:13 -0700 Subject: [PATCH 16/20] cmake: align CEF sandbox exports with package layout --- .../scripts/Relink-CefWithCompatiblePdb.ps1 | 18 ++++++++++- CMakeLists.txt | 2 ++ cmake/windows/helpers.cmake | 18 +++++++++++ cmake/windows/libobsCefSandboxBuild.cmake.in | 8 +++++ libobs/cmake/libobsConfig.cmake.in | 31 ++++++++++++++----- plugins/CMakeLists.txt | 2 -- 6 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 cmake/windows/libobsCefSandboxBuild.cmake.in diff --git a/.github/scripts/Relink-CefWithCompatiblePdb.ps1 b/.github/scripts/Relink-CefWithCompatiblePdb.ps1 index 2f1d0e52e930d0..68ad9a76af3db1 100644 --- a/.github/scripts/Relink-CefWithCompatiblePdb.ps1 +++ b/.github/scripts/Relink-CefWithCompatiblePdb.ps1 @@ -426,7 +426,23 @@ if ($oldPageSize.Count -ne 1 -or $newPageSize.Count -ne 0) { ) } $temporaryRspText = [regex]::Replace($sourceRspText, '(?i)/pdbpagesize:8192\b', '/pdbpagesize:4096') -[System.IO.File]::WriteAllText($temporaryRsp, $temporaryRspText, [System.Text.UTF8Encoding]::new($false)) +$stagedRsp = Join-Path $output ("libcef.dll.pdbpagesize-4096.$([guid]::NewGuid().ToString('N')).tmp") +try { + # Create a new file, then replace the output path itself so an existing link cannot redirect the write. + $rspBytes = [System.Text.UTF8Encoding]::new($false).GetBytes($temporaryRspText) + $rspStream = [System.IO.File]::Open($stagedRsp, [System.IO.FileMode]::CreateNew, + [System.IO.FileAccess]::Write, [System.IO.FileShare]::None) + try { + $rspStream.Write($rspBytes, 0, $rspBytes.Length) + } finally { + $rspStream.Dispose() + } + [System.IO.File]::Move($stagedRsp, $temporaryRsp, $ReplaceExistingOutputs.IsPresent) +} finally { + if (Test-Path -LiteralPath $stagedRsp -PathType Leaf) { + Remove-Item -LiteralPath $stagedRsp -Force + } +} $linkArguments = [System.Collections.Generic.List[string]]::new() $outCount = 0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 4fc4c7f66f16f5..2f97199c60d8c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,8 @@ include(defaults) include(helpers) option(ENABLE_UI "Enable building with UI (requires Qt)" OFF) +option(ENABLE_PLUGINS "Enable building OBS plugins" ON) +option(ENABLE_BROWSER "Enable browser source plugin (required Chromium Embedded Framework)" OFF) option(ENABLE_SCRIPTING "Enable scripting support" ON) option(ENABLE_HEVC "Enable HEVC encoders" ON) option(BUILD_TESTING "Build native tests" OFF) diff --git a/cmake/windows/helpers.cmake b/cmake/windows/helpers.cmake index d8a1d455a51c26..670f430f366290 100644 --- a/cmake/windows/helpers.cmake +++ b/cmake/windows/helpers.cmake @@ -214,6 +214,24 @@ function(set_target_properties_obs target) install(DIRECTORY "${CEF_INCLUDE_DIR}/" DESTINATION "${OBS_INCLUDE_DESTINATION}/include" COMPONENT Development) install(FILES "${obs_browser_sandbox_header}" DESTINATION "${OBS_INCLUDE_DESTINATION}" COMPONENT Development) + # The build-tree libobs package is generated before obs-browser discovers + # CEF. Record the validated source paths beside its target export now. + get_target_property(_obs_libobs_binary_dir libobs BINARY_DIR) + get_target_property(_obs_cef_sandbox_includes CEF::Sandbox INTERFACE_INCLUDE_DIRECTORIES) + get_target_property(_OBS_BUILD_CEF_SANDBOX_LINK_LIBRARIES CEF::Sandbox INTERFACE_LINK_LIBRARIES) + get_target_property(_OBS_BUILD_CEF_SANDBOX_COMPILE_DEFINITIONS CEF::Sandbox INTERFACE_COMPILE_DEFINITIONS) + get_target_property(_OBS_BUILD_CEF_SANDBOX_COMPILE_OPTIONS CEF::Sandbox INTERFACE_COMPILE_OPTIONS) + cmake_path(GET obs_browser_sandbox_header PARENT_PATH _obs_browser_sandbox_include_dir) + set(_OBS_BUILD_CEF_SANDBOX_RELEASE "${cef_sandbox_release}") + set(_OBS_BUILD_CEF_SANDBOX_HEADER "${CEF_INCLUDE_DIR}/cef_sandbox_win.h") + set(_OBS_BUILD_BROWSER_SANDBOX_ABI_HEADER "${obs_browser_sandbox_header}") + set(_OBS_BUILD_CEF_SANDBOX_INCLUDE_DIRS "${_obs_cef_sandbox_includes};${_obs_browser_sandbox_include_dir}") + configure_file( + "${CMAKE_SOURCE_DIR}/cmake/windows/libobsCefSandboxBuild.cmake.in" + "${_obs_libobs_binary_dir}/libobsCefSandboxBuild.cmake" + @ONLY + ) + if(NOT TARGET cef-sandbox-link-smoke) add_executable(cef-sandbox-link-smoke "${CMAKE_SOURCE_DIR}/cmake/windows/cef-sandbox-link-smoke.cpp") target_link_libraries(cef-sandbox-link-smoke PRIVATE CEF::Sandbox) diff --git a/cmake/windows/libobsCefSandboxBuild.cmake.in b/cmake/windows/libobsCefSandboxBuild.cmake.in new file mode 100644 index 00000000000000..11538d602ae0c6 --- /dev/null +++ b/cmake/windows/libobsCefSandboxBuild.cmake.in @@ -0,0 +1,8 @@ +# Paths for consumers of the uninstalled libobs build-tree package. +set(_OBS_CEF_SANDBOX_RELEASE "@_OBS_BUILD_CEF_SANDBOX_RELEASE@") +set(_OBS_CEF_SANDBOX_HEADER "@_OBS_BUILD_CEF_SANDBOX_HEADER@") +set(_OBS_BROWSER_SANDBOX_ABI_HEADER "@_OBS_BUILD_BROWSER_SANDBOX_ABI_HEADER@") +set(_OBS_CEF_SANDBOX_INCLUDE_DIRS "@_OBS_BUILD_CEF_SANDBOX_INCLUDE_DIRS@") +set(_OBS_CEF_SANDBOX_LINK_LIBRARIES "@_OBS_BUILD_CEF_SANDBOX_LINK_LIBRARIES@") +set(_OBS_CEF_SANDBOX_COMPILE_DEFINITIONS "@_OBS_BUILD_CEF_SANDBOX_COMPILE_DEFINITIONS@") +set(_OBS_CEF_SANDBOX_COMPILE_OPTIONS "@_OBS_BUILD_CEF_SANDBOX_COMPILE_OPTIONS@") diff --git a/libobs/cmake/libobsConfig.cmake.in b/libobs/cmake/libobsConfig.cmake.in index 8fe3e7a2d1ef72..c597183bd7be63 100644 --- a/libobs/cmake/libobsConfig.cmake.in +++ b/libobs/cmake/libobsConfig.cmake.in @@ -28,13 +28,28 @@ unset(_OBS_DEPENDENCY_PREFIX) include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake") set(_OBS_CEF_SANDBOX_ENABLED FALSE) -if("@CMAKE_SIZEOF_VOID_P@" EQUAL 8 AND "@ENABLE_BROWSER@") +if("@CMAKE_SIZEOF_VOID_P@" EQUAL 8 AND "@ENABLE_PLUGINS@" AND "@ENABLE_BROWSER@") set(_OBS_CEF_SANDBOX_ENABLED TRUE) endif() if(WIN32 AND _OBS_CEF_SANDBOX_ENABLED AND NOT TARGET OBS::cef-sandbox) set(_OBS_CEF_SANDBOX_RELEASE "${PACKAGE_PREFIX_DIR}/lib/cef/Release/cef_sandbox.lib") set(_OBS_CEF_SANDBOX_HEADER "${PACKAGE_PREFIX_DIR}/include/include/cef_sandbox_win.h") set(_OBS_BROWSER_SANDBOX_ABI_HEADER "${PACKAGE_PREFIX_DIR}/include/obs-browser-sandbox.h") + set(_OBS_CEF_SANDBOX_INCLUDE_DIRS "${PACKAGE_PREFIX_DIR}/include") + set( + _OBS_CEF_SANDBOX_LINK_LIBRARIES + "comctl32.lib;gdi32.lib;rpcrt4.lib;shlwapi.lib;ws2_32.lib;Advapi32.lib;Dbghelp.lib;Delayimp.lib;ntdll.lib;OleAut32.lib;PowrProf.lib;Propsys.lib;psapi.lib;SetupAPI.lib;Shell32.lib;Shcore.lib;Userenv.lib;version.lib;wbemuuid.lib;WindowsApp.lib;winmm.lib" + ) + set( + _OBS_CEF_SANDBOX_COMPILE_DEFINITIONS + "PSAPI_VERSION=1;CEF_USE_SANDBOX;$<$:NDEBUG>;$<$:_HAS_ITERATOR_DEBUGGING=0>" + ) + set(_OBS_CEF_SANDBOX_COMPILE_OPTIONS "$<$:/U_DEBUG>") + # The build-tree package has its target export in this directory, but its + # sandbox library and headers remain in the CEF and obs-browser source trees. + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/libobsCefSandboxBuild.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/libobsCefSandboxBuild.cmake") + endif() if( NOT EXISTS "${_OBS_CEF_SANDBOX_RELEASE}" OR NOT EXISTS "${_OBS_CEF_SANDBOX_HEADER}" @@ -56,16 +71,18 @@ if(WIN32 AND _OBS_CEF_SANDBOX_ENABLED AND NOT TARGET OBS::cef-sandbox) MAP_IMPORTED_CONFIG_DEBUG Release MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release MAP_IMPORTED_CONFIG_MINSIZEREL Release - INTERFACE_INCLUDE_DIRECTORIES "${PACKAGE_PREFIX_DIR}/include" - INTERFACE_COMPILE_DEFINITIONS - "PSAPI_VERSION=1;CEF_USE_SANDBOX;$<$:NDEBUG>;$<$:_HAS_ITERATOR_DEBUGGING=0>" - INTERFACE_COMPILE_OPTIONS "$<$:/U_DEBUG>" - INTERFACE_LINK_LIBRARIES - "comctl32.lib;gdi32.lib;rpcrt4.lib;shlwapi.lib;ws2_32.lib;Advapi32.lib;Dbghelp.lib;Delayimp.lib;ntdll.lib;OleAut32.lib;PowrProf.lib;Propsys.lib;psapi.lib;SetupAPI.lib;Shell32.lib;Shcore.lib;Userenv.lib;version.lib;wbemuuid.lib;WindowsApp.lib;winmm.lib" + INTERFACE_INCLUDE_DIRECTORIES "${_OBS_CEF_SANDBOX_INCLUDE_DIRS}" + INTERFACE_COMPILE_DEFINITIONS "${_OBS_CEF_SANDBOX_COMPILE_DEFINITIONS}" + INTERFACE_COMPILE_OPTIONS "${_OBS_CEF_SANDBOX_COMPILE_OPTIONS}" + INTERFACE_LINK_LIBRARIES "${_OBS_CEF_SANDBOX_LINK_LIBRARIES}" ) unset(_OBS_CEF_SANDBOX_RELEASE) unset(_OBS_CEF_SANDBOX_HEADER) unset(_OBS_BROWSER_SANDBOX_ABI_HEADER) + unset(_OBS_CEF_SANDBOX_INCLUDE_DIRS) + unset(_OBS_CEF_SANDBOX_LINK_LIBRARIES) + unset(_OBS_CEF_SANDBOX_COMPILE_DEFINITIONS) + unset(_OBS_CEF_SANDBOX_COMPILE_OPTIONS) endif() unset(_OBS_CEF_SANDBOX_ENABLED) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index a3c28bb533d9d2..78ffdc4868c0b4 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,7 +1,5 @@ cmake_minimum_required(VERSION 3.28...3.30) -option(ENABLE_PLUGINS "Enable building OBS plugins" ON) - if(NOT ENABLE_PLUGINS) set_property(GLOBAL APPEND PROPERTY OBS_FEATURES_DISABLED "Plugin Support") return() From 2d6a8135f07ad1f9eefd9bee1835f23ab62685c8 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Fri, 2 Oct 2026 14:39:19 -0700 Subject: [PATCH 17/20] Guard CEF PDB output aliases and retain explicit cache overrides --- .github/scripts/Create-CefPublicPdb.ps1 | 64 +++++++++++++++++++++++++ cmake/finders/FindCEF.cmake | 8 ++++ 2 files changed, 72 insertions(+) diff --git a/.github/scripts/Create-CefPublicPdb.ps1 b/.github/scripts/Create-CefPublicPdb.ps1 index 14fb6cfa4da931..edbfde7b06da93 100644 --- a/.github/scripts/Create-CefPublicPdb.ps1 +++ b/.github/scripts/Create-CefPublicPdb.ps1 @@ -82,6 +82,67 @@ function Get-PdbIdentity { } } +function Resolve-PhysicalPath { + param( + [Parameter(Mandatory)] + [string] $Path + ) + + $fullPath = [System.IO.Path]::GetFullPath($Path) + $root = [System.IO.Path]::GetPathRoot($fullPath) + $current = $root + $components = $fullPath.Substring($root.Length).TrimStart('\', '/') -split '[\\/]+' + for ($index = 0; $index -lt $components.Count; $index++) { + $candidate = Join-Path $current $components[$index] + try { + $item = Get-Item -LiteralPath $candidate -Force -ErrorAction Stop + } catch [System.Management.Automation.ItemNotFoundException] { + if ($index -ne $components.Count - 1) { + throw "Could not inspect path component: $candidate" + } + if ([System.IO.FileInfo]::new($candidate).LinkTarget) { + throw "The public PDB output is a dangling link: $candidate" + } + return [System.IO.Path]::GetFullPath($candidate) + } + if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { + $target = $item.ResolveLinkTarget($true) + if ($null -eq $target -or -not $target.Exists) { + throw "Could not resolve reparse point or its target: $candidate" + } + $current = $target.FullName + } else { + $current = $item.FullName + } + } + return [System.IO.Path]::GetFullPath($current) +} + +function Assert-SafePublicPdbPath { + param( + [Parameter(Mandatory)] + [string] $CefOut, + + [Parameter(Mandatory)] + [string] $PrivatePdb, + + [Parameter(Mandatory)] + [string] $PublicPdb + ) + + $cefOutPhysical = Resolve-PhysicalPath -Path $CefOut + $privatePdbPhysical = Resolve-PhysicalPath -Path $PrivatePdb + $publicPdbPhysical = Resolve-PhysicalPath -Path $PublicPdb + $comparison = [System.StringComparison]::OrdinalIgnoreCase + if ($publicPdbPhysical.Equals($privatePdbPhysical, $comparison)) { + throw 'The public PDB output must not overwrite the private PDB.' + } + if (-not $publicPdbPhysical.StartsWith("$($cefOutPhysical.TrimEnd('\', '/'))$([System.IO.Path]::DirectorySeparatorChar)", + $comparison)) { + throw "The public PDB output resolves outside the CEF output directory: $publicPdbPhysical" + } +} + $cefOut = (Resolve-Path -LiteralPath $CefOutDirectory).Path $cefRoot = (Resolve-Path -LiteralPath (Join-Path $cefOut '..')).Path $cefSource = (Resolve-Path -LiteralPath (Join-Path $cefOut '..\..')).Path @@ -112,10 +173,12 @@ if ( ) { throw "The public PDB output must remain inside the CEF output directory: $cefOut" } +Assert-SafePublicPdbPath -CefOut $cefOut -PrivatePdb $privatePdb -PublicPdb $publicPdb if (Test-Path -LiteralPath $publicPdb) { if (-not $ReplaceExisting) { throw "The public PDB already exists. Pass -ReplaceExisting to regenerate it: $publicPdb" } + Assert-SafePublicPdbPath -CefOut $cefOut -PrivatePdb $privatePdb -PublicPdb $publicPdb Remove-Item -LiteralPath $publicPdb -Force } @@ -133,6 +196,7 @@ $pdbUtil = Resolve-Executable -Name 'llvm-pdbutil.exe' -RequestedPath $LlvmPdbUt (Join-Path $env:ProgramFiles 'Microsoft Visual Studio\2022\BuildTools\VC\Tools\Llvm\x64\bin\llvm-pdbutil.exe') ) +Assert-SafePublicPdbPath -CefOut $cefOut -PrivatePdb $privatePdb -PublicPdb $publicPdb $pdbCopyOutput = @(& $pdbCopy $privatePdb $publicPdb -p 2>&1) if ($LASTEXITCODE -ne 0) { throw ( diff --git a/cmake/finders/FindCEF.cmake b/cmake/finders/FindCEF.cmake index 9c6549cf1167a7..64c9d6b3e1621e 100644 --- a/cmake/finders/FindCEF.cmake +++ b/cmake/finders/FindCEF.cmake @@ -86,6 +86,14 @@ if(NOT DEFINED _CEF_CACHED_DISCOVERY_KEY OR NOT "${_CEF_CACHED_DISCOVERY_KEY}" S CEF_SANDBOX_LIBRARY_RELEASE CEF_LIBRARY ) + if(DEFINED CACHE{${_CEF_CACHED_RESULT}}) + get_property(_CEF_CACHE_HELP CACHE "${_CEF_CACHED_RESULT}" PROPERTY HELPSTRING) + # CMake marks -D cache entries this way, including typed :FILEPATH + # overrides. Keep them; find_* results have their DOC help strings. + if(_CEF_CACHE_HELP STREQUAL "No help, variable specified on the command line.") + continue() + endif() + endif() unset(${_CEF_CACHED_RESULT}) unset(${_CEF_CACHED_RESULT} CACHE) endforeach() From 7156c0a630045c063fac9fd13a5ef96a3b2578e7 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Fri, 2 Oct 2026 15:17:59 -0700 Subject: [PATCH 18/20] cmake: restore libobs package prefix after dependencies --- libobs/cmake/libobsConfig.cmake.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libobs/cmake/libobsConfig.cmake.in b/libobs/cmake/libobsConfig.cmake.in index c597183bd7be63..76d9ff43d81e3e 100644 --- a/libobs/cmake/libobsConfig.cmake.in +++ b/libobs/cmake/libobsConfig.cmake.in @@ -4,6 +4,7 @@ include(CMakeFindDependencyMacro) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/finders") +set(_OBS_PACKAGE_PREFIX_DIR "${PACKAGE_PREFIX_DIR}") set(_OBS_PREV_CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH}") list(PREPEND CMAKE_PREFIX_PATH "${PACKAGE_PREFIX_DIR}" "${CMAKE_CURRENT_LIST_DIR}") @@ -20,7 +21,9 @@ endif() find_dependency(SIMDe REQUIRED) find_dependency(Threads REQUIRED) +set(PACKAGE_PREFIX_DIR "${_OBS_PACKAGE_PREFIX_DIR}") set(CMAKE_PREFIX_PATH "${_OBS_PREV_CMAKE_PREFIX_PATH}") +unset(_OBS_PACKAGE_PREFIX_DIR) unset(_OBS_PREV_CMAKE_PREFIX_PATH) unset(_OBS_BUNDLED_DEPENDENCY_PREFIXES) unset(_OBS_DEPENDENCY_PREFIX) From e12b0daa51192c15b6123ae0fdeccf0cc40183d8 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Wed, 7 Oct 2026 15:27:18 -0700 Subject: [PATCH 19/20] deps: pin Windows x64 CEF 6613 revision 3 --- CMakePresets.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 13a89473d3361f..97b7e42162f8e6 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -63,7 +63,7 @@ "macos-arm64": "429b50e74f6c174dcfe2f14d8204b54add497eaafe117f7b69ce6bb2354d2626", "ubuntu-x86_64": "7963335519a19ccdc5233f7334c5ab023026e2f3e9a0cc417007c09d86608146", "ubuntu-aarch64": "642514469eaa29a5c887891084d2e73f7dc2d7405f7dfa7726b2dbc24b309999", - "windows-x64": "dce2b2932239a85aa646ede0d9fc00c8ae0bbcb383959028ca7d48d503bacbbb", + "windows-x64": "32075b154be69db1ab716f54f98ee3d4bd4c9f65df3eb06d381529115ad27151", "windows-arm64": "df9df4bd85826b4c071c6db404fd59cf93efd9c58ec3ab64e204466ae19bb02a" }, "revision": { @@ -71,7 +71,7 @@ "macos-arm64": 5, "ubuntu-x86_64": 6, "ubuntu-aarch64": 6, - "windows-x64": 1 + "windows-x64": 3 } }, "openssl": { From fc6d94776a5cd82f005d5dfb4cbb16828f64a219 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Wed, 7 Oct 2026 15:28:38 -0700 Subject: [PATCH 20/20] ci: select CEF 6613 revision 3 in legacy Windows jobs --- .github/workflows/main-streamlabs.yml | 2 +- .github/workflows/main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main-streamlabs.yml b/.github/workflows/main-streamlabs.yml index e24934adb6d898..32c659b239021c 100644 --- a/.github/workflows/main-streamlabs.yml +++ b/.github/workflows/main-streamlabs.yml @@ -116,7 +116,7 @@ jobs: env: OS_TAG: "windows64" CEF_VERSION: 6613 - CEF_REVISION: v1 + CEF_REVISION: v3 WIN_DEPS_VERSION: windows-deps-2024-05-08sl2-x64 WIN_DEPS_VERSION_X86: 2024-05-08 GRPC_VERSION: v1.47.0 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ecc6b067c207a3..a23a8d080d564d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -423,7 +423,7 @@ jobs: env: OS_TAG: "windows64" CEF_VERSION: 6613 - CEF_REVISION: v1 + CEF_REVISION: v3 WIN_DEPS_VERSION: windows-deps-2024-05-08sl2-x64 WIN_DEPS_VERSION_X86: 2024-05-08 GRPC_VERSION: v1.47.0