Merge remote-tracking branch 'origin/master'

This commit is contained in:
2026-09-20 23:45:36 +08:00
14 changed files with 438 additions and 3529 deletions
@@ -6,6 +6,8 @@
## 2026-09-20 Godot 编辑器执行接入
原生引导采用固定版本的官方 `godot-cpp` 和 MSVC x64 构建,绑定及 C++ runtime 静态链接。依赖归档和缓存源码须核验,安装目录仍只分发原生载荷及许可。EDITOR 阶段动态加载/卸载时显式清理 C++ 实例绑定与单例包装,保留纯 GDScript 的异步执行和原有协议;执行权限、项目身份与缓存归属继续由现有宿主处理。
可用性边界按引擎区分:Cocos/Unity 保持不按工程类型过滤,Godot 仍绑定当前 Godot 项目,切项目撤销旧插件上下文;前端统一根据宿主投影启动插件。Runtime 工具目录只对 Godot 追加项目条件,编辑器说明沿用外置提示词及审核 Skill 参考。
Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和不确定执行回执合同,编辑器实现留在 `plugins/agc-godot-editor`。用户选择 DLL 原件随 AGC 安装资源分发,并确认按编辑器实例在 AGC 私有缓存准备临时加载副本,以满足 Godot Windows 加载器的同目录 `~DLL` 写入要求;项目内不复制 DLL,只用受管 `.gdextension` 引导。Godot 自动 UID 伴生文件必须记录归属并在确认卸载后按内容匹配清理。工作区根不迁移到 Godot 子目录,原始项目配置与场景只通过明确编辑操作修改。完整合同及验证范围见 [Godot 编辑器插件接入](<../../technical/【技术方案】AGC Godot编辑器插件接入-2026-09-20.md>)。
@@ -1,9 +1,9 @@
# 踩坑与排障记录
## Godot 原生插件在 MSVC C 模式下的对齐声明
## Godot C++ 扩展构建与对象生命周期
- `native.c` 若首先报 `max_align_t` 语法错误,后面的 `Storage``retained_script` 等未声明通常是连带错误。MSVC C 模式不提供该类型;ABI 存储使用 C11 `_Alignas(16)` 显式对齐并保持 128 字节容量,通过实际 MSVC DLL 构建验证,不逐条修补连带错误
- `No C compiler found` 则属于开发环境问题:先初始化 Visual Studio x64 开发环境,同时设置 PATH、INCLUDE 与 LIB,再运行构建。只把 `cl.exe` 所在目录加到 PATH 不足以提供头文件和链接库
- 原生引导通过官方 `godot-cpp` 管理 Variant、String 和 Ref,不自行维护 ABI 存储。Godot 类型必须在扩展终止回调内释放,不能依赖 DLL 静态对象析构;桥节点可能已经退出,应按实例 ID 核验存活再回调
- 正式 Windows 构建使用 CMake 的 Visual Studio x64 generator,并实际验证 MSVC 编译;不能用 GCC 成功替代 MSVC 验收。固定官方归档按 SHA256 校验,缓存源码被修改时拒绝构建并保留证据
## Rust 同步回调的测试记录按线程隔离
@@ -33,7 +33,10 @@ DLL 原件随 AGC 安装包放在插件资源目录中;Godot Windows 加载器
## 分发与描述文件
- Windows 原生构建使用 x64 C11 编译器;MSVC 需先初始化 Visual Studio 的 x64 开发环境,以同时提供 PATH、INCLUDE 与 LIB。ABI 临时存储使用 128 字节、C11 `_Alignas(16)` 显式对齐,不依赖 MSVC C 模式未提供的 `max_align_t`;定向验证运行 `plugins/agc-godot-editor/native/gdextension/build.ps1 -Compiler cl.exe`
- 原生引导使用官方 `godot-cpp` 的 C++ 类型和初始化接口,不自行声明 Variant 存储或直接装配 ABI 函数指针。绑定源码固定到 `godot-4.5-stable``e83fd0904c13356ed1d4c3d09f8bb9132bdc6b77`,以该版本的稳定 API 构建并在 Godot 4.7.2 验证;产品最低版本仍为 4.7,因为嵌入脚本使用该版本能力。Windows x64 构建使用 Visual Studio C++、CMake 和 Python,静态链接绑定及 C++ runtime,用户无需这些构建工具
- 构建仅从已固定的官方归档获取绑定源码,并核对 SHA256;下载和生成内容只进入 `.build/`。CMake 构建包含引导源码、嵌入脚本、绑定版本/归档摘要和构建配置的身份指纹。许可证与来源继续随 DLL 分发,安装包不包含 SDK、源码缓存、生成绑定或构建工具。
- C++ 状态仅在扩展有效期间持有 GDScript 引用和桥节点身份;终止回调先停用仍存活的桥,再释放绑定对象,不能让静态 Godot 对象析构晚于绑定退出。延迟 bootstrap、原生卸载、Node 已退出及同 PID 重连均须实测。受管文件、会话身份、协议、执行回执与不确定阻断沿用现有合同。
- EDITOR 阶段晚加载不会取得 CORE 阶段终止回调;引导终止时须通过官方绑定接口解除 Node/GDScript 的实例包装回调,并完成单例包装清理。只移除 C++ 包装,不同步销毁仍在 GDScript 调用栈或等待 `queue_free` 的引擎对象;先用 Variant 保活脚本,再解除 Ref 和绑定,避免卸载 DLL 后跳到失效回调。
- 安装资源布局为 `plugins/agc-godot-editor/native/gdextension/bin/win-x64/agc_godot_editor.dll`,邻接元数据记录协议、构建身份和 DLL SHA256。开发模式允许宿主提供仓库插件目录中的同结构产物;RPC 不接受自定义 DLL 候选。
- 缓存根仅由宿主提供,为其私有配置目录下的 `godot-editor-runtime`;按 `PID + startedFileTime + buildId` 隔离,所有路径分量受控且拒绝链接/reparse point。复制前验证安装原件及元数据,缓存已有文件必须匹配来源、归属及 SHA,不能加载被替换的同名文件。受管描述同时保留原件与加载副本身份,重启恢复不得把工程给出的任意 DLL 路径当作受信任来源。
@@ -107,11 +110,13 @@ Windows x64、Godot 4.7.2 标准编辑器的本地实现验收通过。证据保
### 复验入口
从仓库根运行,原生构建要求 Windows x64 C 编译器。先把 `AGC_GODOT_TEST_EXECUTABLE` 设置为待验证的标准 Godot 编辑器绝对路径;未设置时 headless 测试会跳过,不能视为实机通过。
从仓库根运行,原生构建要求 Visual Studio 2022 C++ x64、CMake 3.25 及以上和 Python 3;通过 Visual Studio generator 自动选择完整编译环境。首次构建需访问固定的官方归档,校验后的依赖缓存支持离线复用;`build.ps1` 可用 `-CMake``-Python` 指定构建工具。先把 `AGC_GODOT_TEST_EXECUTABLE` 设置为待验证的标准 Godot 编辑器绝对路径;未设置时 headless 测试会跳过,不能视为实机通过。
```powershell
powershell -NoProfile -File plugins/agc-godot-editor/native/gdextension/build.ps1
python -X utf8 -B plugins/agc-godot-editor/native/gdextension/tests/test_dependencies.py
node --test plugins/agc-godot-editor/native/gdextension/tests/native-smoke.test.mjs
node --test plugins/agc-godot-editor/native/gdextension/tests/guide-examples.test.mjs
cargo test --locked --manifest-path plugins/agc-godot-editor/native/godot-editor-bridge/Cargo.toml
npm run agc:plugins:test
cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --features cocos-editor-execute,unity-editor-execute,godot-editor-execute godot
@@ -124,3 +129,9 @@ git diff --check
```
真实 GUI 使用 `native/godot-editor-bridge/examples/live_smoke.rs`;安装位置变更使用同目录的 `install_location_smoke.rs`。二者要求显式传入自有可丢弃工程、已打开编辑器 PID、可信安装 DLL、工程外私有缓存及 `--allow-fixture-mutations`,具体参数见源码用法。多实例验证分别传入两个工程和 PID,通过 `live_smoke``--hold-ms` 让加载时间重叠,同时核验原生模块路径。Runner 复验须经正式长度前缀 RPC、完整 ACK 和私有配置恢复路径,不能用原生示例替代 Runner 证据。
### C++ 引导验收边界
官方 C++ 绑定版已通过实际 MSVC 构建、原生/指南 headless 回归、Rust 缓存与连接回归、受管资源 staging 校验,以及同一 Godot 4.7.2 GUI 进程中的首次加载、执行、卸载和重新聚焦后的重连。DLL 的导入依赖只有 KERNEL32,生成 SDK/编译缓存不进入 staging;重复构建命中同一载荷身份,损坏归档和改动过的依赖缓存拒绝构建。验证中首次创建的空工程由 Godot 补写版本特征,按编辑器初始化后的基线确认插件运行不修改原有工程文件。
GUI 自动聚焦若未触发扫描,会按原合同返回未派发错误;重新聚焦后再连接,不重放未知执行。此次 C++ 替换未重新运行完整 AGC 发布构建、真实 Provider 或安装包 UI 验证,前述旧版本证据不代替这些验收。
+2 -1
View File
@@ -83,7 +83,8 @@ feature 会构建自包含 Attach helper,并只将运行文件与许可放入
[Unity 插件接入](../docs/technical/【技术方案】AGC Unity编辑器插件接入-2026-09-18.md)。
Godot 插件通过 `godot-editor-execute` feature 构建并校验 GDExtension 载荷,只分发
运行入口、DLL、元数据及许可。构建机需要 Windows x64 C 工具链;DLL 原件留在安装资源,
运行入口、DLL、元数据及许可。原生引导使用固定版本的官方 `godot-cpp`;构建机需要
Visual Studio C++ x64、CMake 和 Python,首次构建下载并校验绑定源码。DLL 原件留在安装资源,
每个编辑器的临时加载副本放在 AGC 私有缓存。连接时维护工程内受管 `.gdextension`
引用及其 UID,通过 Godot 聚焦扫描首次加载。文件归属、真实执行和卸载规则见
[Godot 插件接入](<../docs/technical/【技术方案】AGC Godot编辑器插件接入-2026-09-20.md>)。
@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.25)
project(agc_godot_editor LANGUAGES CXX)
if(NOT MSVC OR NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
message(FATAL_ERROR "Godot editor payload requires MSVC x64")
endif()
find_package(Python3 REQUIRED COMPONENTS Interpreter)
set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded CACHE STRING "Static runtime" FORCE)
set(GODOTCPP_TARGET editor CACHE STRING "Editor bindings" FORCE)
set(GODOTCPP_BUILD_PROFILE "${CMAKE_CURRENT_SOURCE_DIR}/build-profile.json" CACHE FILEPATH "Minimal bindings" FORCE)
set(GODOTCPP_USE_STATIC_CPP ON CACHE BOOL "Static runtime" FORCE)
set(GODOTCPP_USE_HOT_RELOAD OFF CACHE BOOL "Explicit unload/reload only" FORCE)
set(GODOTCPP_ENABLE_TESTING OFF CACHE BOOL "Do not package upstream tests" FORCE)
if(NOT EXISTS "${AGC_GODOT_CPP_SOURCE}/CMakeLists.txt")
message(FATAL_ERROR "Run build.ps1 to prepare the verified godot-cpp source")
endif()
add_subdirectory("${AGC_GODOT_CPP_SOURCE}" godot-cpp EXCLUDE_FROM_ALL SYSTEM)
add_library(agc_godot_editor SHARED src/native.cpp)
target_compile_features(agc_godot_editor PRIVATE cxx_std_17)
target_link_libraries(agc_godot_editor PRIVATE godot::cpp)
target_include_directories(agc_godot_editor PRIVATE "${AGC_GENERATED_DIR}")
target_compile_options(agc_godot_editor PRIVATE /W4 /WX /utf-8)
set_target_properties(agc_godot_editor PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/payload")
@@ -0,0 +1,3 @@
{
"enabled_classes": ["GDScript", "Node", "OS", "ProjectSettings"]
}
@@ -1,84 +1,73 @@
param([string]$Compiler = $env:AGC_GODOT_C_COMPILER)
param(
[string]$CMake = 'cmake.exe',
[string]$Python = 'python.exe',
[string]$Generator = 'Visual Studio 17 2022',
[ValidateRange(1, 32)][int]$Jobs = 4
)
$ErrorActionPreference = 'Stop'
$root = $PSScriptRoot
if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT) { throw 'Godot editor native payload requires Windows x64.' }
if (-not [Environment]::Is64BitProcess) { throw 'A 64-bit build host is required.' }
if (-not $Compiler) {
foreach ($candidate in @('gcc.exe', 'clang.exe', 'cl.exe')) {
$found = Get-Command $candidate -ErrorAction SilentlyContinue
if ($found) { $Compiler = $found.Source; break }
}
if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT -or -not [Environment]::Is64BitProcess) {
throw 'Godot editor payload requires Windows x64.'
}
if (-not $Compiler) { throw 'No C compiler found. Install a Windows x64 C toolchain or pass -Compiler.' }
$Compiler = (Get-Command $Compiler -ErrorAction Stop).Source
$build = Join-Path $root '.build'
$CMake = (Get-Command $CMake -ErrorAction Stop).Source
$Python = (Get-Command $Python -ErrorAction Stop).Source
$build = Join-Path $root '.build/msvc'
$generated = Join-Path $root '.build/generated'
$output = Join-Path $root 'bin/win-x64'
New-Item -ItemType Directory -Path $build,$output -Force | Out-Null
New-Item -ItemType Directory -Path $generated,$output -Force | Out-Null
$utf8 = [Text.UTF8Encoding]::new($false)
$inputs = @('src/native.c','src/bridge.gd','vendor/gdextension_interface.h','vendor/provenance.json','build.ps1')
$fingerprint = 'agc.godot.editor.v1/windows/x86_64/c11/O2' + "`n"
$fingerprint += 'compiler:' + (Get-FileHash -Algorithm SHA256 -LiteralPath $Compiler).Hash.ToLowerInvariant() + "`n"
foreach ($inputPath in $inputs) { $fingerprint += $inputPath + ':' + (Get-FileHash -Algorithm SHA256 -LiteralPath (Join-Path $root $inputPath)).Hash.ToLowerInvariant() + "`n" }
$hasher = [Security.Cryptography.SHA256]::Create()
try { $buildId = 'sha256:' + ([BitConverter]::ToString($hasher.ComputeHash($utf8.GetBytes($fingerprint))).Replace('-','').ToLowerInvariant()) } finally { $hasher.Dispose() }
$existingDll = Join-Path $output 'agc_godot_editor.dll'
$existingMetadata = Join-Path $output 'metadata.json'
if ((Test-Path -LiteralPath $existingDll) -and (Test-Path -LiteralPath $existingMetadata)) {
try {
$existing = Get-Content -LiteralPath $existingMetadata -Raw | ConvertFrom-Json
$existingHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $existingDll).Hash.ToLowerInvariant()
if ($existing.protocol -eq 'agc.godot.editor.v1' -and $existing.buildId -eq $buildId -and $existing.sha256 -eq $existingHash -and $existing.entrySymbol -eq 'agc_godot_editor_init' -and $existing.platform -eq 'windows' -and $existing.arch -eq 'x86_64' -and $existing.minimumGodotVersion -eq '4.7') {
Write-Output "Native payload is current: $buildId"
$previousBytecode = $env:PYTHONDONTWRITEBYTECODE
try {
$env:PYTHONDONTWRITEBYTECODE = '1'
$dependency = & $Python -X utf8 (Join-Path $root 'prepare_dependencies.py')
if ($LASTEXITCODE -ne 0) { throw 'Pinned godot-cpp dependency verification failed.' }
& $CMake -S $root -B $build -G $Generator -A x64 "-DAGC_GODOT_CPP_SOURCE=$dependency" "-DAGC_GENERATED_DIR=$generated" "-DPython3_EXECUTABLE=$Python"
if ($LASTEXITCODE -ne 0) { throw 'Godot C++ configure failed; install Visual Studio C++ x64, CMake and Python.' }
$compilerRecords = @(Get-ChildItem -LiteralPath (Join-Path $build 'CMakeFiles') -Filter 'CMakeCXXCompiler.cmake' -Recurse -File)
if ($compilerRecords.Count -ne 1) { throw 'MSVC compiler identity is ambiguous.' }
$record = [IO.File]::ReadAllText($compilerRecords[0].FullName)
$compilerMatch = [regex]::Match($record, 'set\(CMAKE_CXX_COMPILER "([^"]+)"\)')
if (-not $compilerMatch.Success -or -not $record.Contains('set(CMAKE_CXX_COMPILER_ID "MSVC")')) { throw 'MSVC compiler identity was not verified.' }
$compiler = $compilerMatch.Groups[1].Value
$inputs = @('src/native.cpp','src/bridge.gd','CMakeLists.txt','build-profile.json','prepare_dependencies.py','vendor/provenance.json','vendor/LICENSE.txt','build.ps1')
$fingerprint = 'agc.godot.editor.v1/windows/x86_64/c++17/msvc/Release/static-crt' + "`n"
$fingerprint += 'compiler:' + (Get-FileHash -LiteralPath $compiler -Algorithm SHA256).Hash.ToLowerInvariant() + "`n"
$fingerprint += 'cmake:' + (Get-FileHash -LiteralPath $CMake -Algorithm SHA256).Hash.ToLowerInvariant() + "`n"
$fingerprint += 'configuration:' + (Get-FileHash -LiteralPath (Join-Path $build 'CMakeCache.txt') -Algorithm SHA256).Hash.ToLowerInvariant() + "`n"
$fingerprint += 'toolchain:' + (Get-FileHash -LiteralPath $compilerRecords[0].FullName -Algorithm SHA256).Hash.ToLowerInvariant() + "`n"
foreach ($relative in $inputs) { $fingerprint += $relative + ':' + (Get-FileHash -LiteralPath (Join-Path $root $relative) -Algorithm SHA256).Hash.ToLowerInvariant() + "`n" }
$hasher = [Security.Cryptography.SHA256]::Create()
try { $buildId = 'sha256:' + ([BitConverter]::ToString($hasher.ComputeHash($utf8.GetBytes($fingerprint))).Replace('-','').ToLowerInvariant()) } finally { $hasher.Dispose() }
$dll = Join-Path $output 'agc_godot_editor.dll'
$metadataPath = Join-Path $output 'metadata.json'
if ((Test-Path -LiteralPath $dll) -and (Test-Path -LiteralPath $metadataPath)) {
$existing = Get-Content -LiteralPath $metadataPath -Raw | ConvertFrom-Json
if ($existing.protocol -eq 'agc.godot.editor.v1' -and $existing.entrySymbol -eq 'agc_godot_editor_init' -and $existing.platform -eq 'windows' -and $existing.arch -eq 'x86_64' -and $existing.minimumGodotVersion -eq '4.7' -and $existing.buildId -eq $buildId -and $existing.sha256 -eq (Get-FileHash -LiteralPath $dll -Algorithm SHA256).Hash.ToLowerInvariant()) {
Write-Output "Native C++ payload is current: $buildId"
return
}
} catch { Write-Verbose 'Existing metadata could not be verified; rebuilding.' }
}
$script = [IO.File]::ReadAllBytes((Join-Path $root 'src/bridge.gd'))
$embedded = [Text.StringBuilder]::new()
[void]$embedded.AppendLine('/* Generated from src/bridge.gd; never reads a project-side script. */')
[void]$embedded.AppendLine('#define AGC_BUILD_ID "' + $buildId + '"')
[void]$embedded.AppendLine('static const unsigned char AGC_EMBEDDED_BRIDGE[] = {')
for ($index = 0; $index -lt $script.Length; $index += 32) {
$last = [Math]::Min($index + 31, $script.Length - 1)
[void]$embedded.AppendLine(($script[$index..$last] -join ',') + ',')
}
[void]$embedded.AppendLine('0};')
[IO.File]::WriteAllText((Join-Path $build 'embedded_bridge.h'), $embedded.ToString(), $utf8)
$previousTemp = $env:TEMP
$previousTmp = $env:TMP
$previousLocation = Get-Location
try {
$env:TEMP = $build
$env:TMP = $build
Set-Location -LiteralPath $build
$source = Join-Path $root 'src/native.c'
$vendor = Join-Path $root 'vendor'
$temporaryDll = Join-Path $build 'agc_godot_editor.dll'
$compilerName = [IO.Path]::GetFileName($Compiler).ToLowerInvariant()
if ($compilerName -eq 'cl.exe') {
& $Compiler /nologo /std:c11 /O2 /W4 /WX /LD /D_CRT_SECURE_NO_WARNINGS "/I$vendor" "/I$build" $source "/Fe:$temporaryDll" /link /Brepro
} else {
$flags = @('-std=c11','-O2','-Wall','-Wextra','-Werror','-shared')
if ($compilerName -eq 'gcc.exe') { $flags += @('-static-libgcc','-Wl,--no-insert-timestamp') }
& $Compiler @flags -I $vendor -I $build $source -o $temporaryDll
}
if ($LASTEXITCODE -ne 0) { throw "Native compiler exited with $LASTEXITCODE" }
$dll = Join-Path $output 'agc_godot_editor.dll'
Copy-Item -LiteralPath $temporaryDll -Destination $dll -Force
$script = [IO.File]::ReadAllBytes((Join-Path $root 'src/bridge.gd'))
$embedded = [Text.StringBuilder]::new()
[void]$embedded.AppendLine('#define AGC_BUILD_ID "' + $buildId + '"')
[void]$embedded.AppendLine('static const unsigned char AGC_EMBEDDED_BRIDGE[] = {')
for ($index = 0; $index -lt $script.Length; $index += 32) {
$last = [Math]::Min($index + 31, $script.Length - 1)
[void]$embedded.AppendLine(($script[$index..$last] -join ',') + ',')
}
[void]$embedded.AppendLine('0};')
$header = Join-Path $generated 'embedded_bridge.h'
if (-not (Test-Path -LiteralPath $header) -or [IO.File]::ReadAllText($header) -ne $embedded.ToString()) { [IO.File]::WriteAllText($header, $embedded.ToString(), $utf8) }
& $CMake --build $build --config Release --target agc_godot_editor --parallel $Jobs
if ($LASTEXITCODE -ne 0) { throw 'Godot C++ MSVC build failed.' }
Copy-Item -LiteralPath (Join-Path $build 'payload/agc_godot_editor.dll') -Destination $dll -Force
$metadata = [ordered]@{
protocol = 'agc.godot.editor.v1'
buildId = $buildId
sha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $dll).Hash.ToLowerInvariant()
platform = 'windows'
arch = 'x86_64'
entrySymbol = 'agc_godot_editor_init'
minimumGodotVersion = '4.7'
protocol = 'agc.godot.editor.v1'; buildId = $buildId
sha256 = (Get-FileHash -LiteralPath $dll -Algorithm SHA256).Hash.ToLowerInvariant()
platform = 'windows'; arch = 'x86_64'; entrySymbol = 'agc_godot_editor_init'; minimumGodotVersion = '4.7'
}
[IO.File]::WriteAllText((Join-Path $output 'metadata.json'), ($metadata | ConvertTo-Json) + "`n", $utf8)
Write-Output "Built $dll"
Write-Output "Build identity: $buildId"
[IO.File]::WriteAllText($metadataPath, ($metadata | ConvertTo-Json) + "`n", $utf8)
Write-Output "Built C++ payload: $buildId"
} finally {
Set-Location -LiteralPath $previousLocation
$env:TEMP = $previousTemp
$env:TMP = $previousTmp
$env:PYTHONDONTWRITEBYTECODE = $previousBytecode
}
@@ -0,0 +1,91 @@
"""Prepare only the pinned official SDK; never execute an unchecked archive."""
import hashlib
import json
import os
from pathlib import Path, PurePosixPath
import re
import stat
import sys
import tempfile
import urllib.request
import zipfile
def plain(path):
for item in (path, *path.parents):
if item.exists() or item.is_symlink():
info = item.lstat()
if item.is_symlink() or getattr(info, "st_file_attributes", 0) & 0x400:
raise ValueError(f"Dependency path cannot contain links: {item}")
def digest(data):
return hashlib.sha256(data).hexdigest()
def prepare(root):
provenance = json.loads((root / "vendor/provenance.json").read_text(encoding="utf-8"))
commit = provenance["commit"]
expected = provenance["archiveSha256"]
if not re.fullmatch(r"[a-f0-9]{40}", commit) or not re.fullmatch(r"[a-f0-9]{64}", expected):
raise ValueError("Invalid pinned dependency identity")
url = f"https://codeload.github.com/godotengine/godot-cpp/zip/{commit}"
if provenance["archiveUrl"] != url:
raise ValueError("Dependency URL must identify the pinned official repository")
cache = root / ".build/dependencies"
plain(cache)
cache.mkdir(parents=True, exist_ok=True)
archive = cache / f"{commit}.zip"
plain(archive)
if not archive.exists():
with urllib.request.urlopen(url, timeout=60) as response:
data = response.read(32 * 1024 * 1024 + 1)
if len(data) > 32 * 1024 * 1024 or digest(data) != expected:
raise ValueError("Official godot-cpp archive SHA256 mismatch")
with tempfile.NamedTemporaryFile(dir=cache, delete=False) as output:
output.write(data)
temporary = Path(output.name)
os.replace(temporary, archive)
if digest(archive.read_bytes()) != expected:
raise ValueError("Cached godot-cpp archive SHA256 mismatch; cache was preserved")
source = cache / f"godot-cpp-{commit}"
plain(source)
source.mkdir(exist_ok=True)
expected_files = set()
with zipfile.ZipFile(archive) as bundle:
for entry in bundle.infolist():
relative = PurePosixPath(entry.filename)
if (not relative.parts or relative.parts[0] != source.name or relative.is_absolute()
or ".." in relative.parts or "\\" in entry.filename or ":" in entry.filename):
raise ValueError("Unsafe dependency archive entry")
if stat.S_ISLNK(entry.external_attr >> 16):
raise ValueError("Dependency archive cannot contain symbolic links")
if entry.is_dir():
continue
target = source.joinpath(*relative.parts[1:])
if target in expected_files:
raise ValueError("Duplicate dependency archive entry")
plain(target)
expected_files.add(target)
content = bundle.read(entry)
if target.exists():
if not target.is_file() or target.read_bytes() != content:
raise ValueError(f"Modified godot-cpp source cache was preserved: {target}")
else:
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(content)
for item in source.rglob("*"):
plain(item)
if item.is_file() and item not in expected_files:
raise ValueError(f"Unexpected dependency source cache entry: {item}")
if (source / "LICENSE.md").read_text(encoding="utf-8") != (root / "vendor/LICENSE.txt").read_text(encoding="utf-8"):
raise ValueError("Packaged godot-cpp license differs from pinned upstream license")
return source
if __name__ == "__main__":
try:
print(prepare(Path(__file__).resolve().parent))
except (OSError, ValueError, KeyError, zipfile.BadZipFile) as error:
print(str(error), file=sys.stderr)
sys.exit(1)
@@ -1,258 +0,0 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <wchar.h>
#include "gdextension_interface.h"
#include "embedded_bridge.h"
/* Windows x64 ABI storage is deliberately oversized and 16-byte aligned.
* Explicit C11 alignment also works with MSVC C, which lacks max_align_t.
* Objects are constructed/destructed solely through the official interface. */
typedef struct { _Alignas(16) unsigned char bytes[128]; } Storage;
static GDExtensionInterfacePrintWarning api_warning;
static GDExtensionInterfaceVariantCall api_call;
static GDExtensionInterfaceVariantDestroy api_destroy;
static GDExtensionInterfaceVariantGetType api_type;
static GDExtensionInterfaceGlobalGetSingleton api_singleton;
static GDExtensionInterfaceStringNameNewWithLatin1Chars api_name;
static GDExtensionInterfaceStringNewWithUtf8Chars api_string;
static GDExtensionInterfaceStringToUtf8Chars api_utf8;
static GDExtensionVariantFromTypeConstructorFunc from_object, from_string, from_name;
static GDExtensionTypeFromVariantConstructorFunc to_int, to_string;
static GDExtensionPtrDestructor destroy_name, destroy_string;
static Storage retained_script, retained_node;
static int script_live, node_live, started;
static void report_failure(const char *operation, int code) {
char message[256];
snprintf(message, sizeof(message), "AGC Godot editor bridge: %s failed (%d).", operation, code);
if (api_warning) api_warning(message, "agc_godot_editor", "native.c", 0, 0);
}
static void name_variant(Storage *out, const char *text) {
Storage name;
api_name(&name, text, 0);
from_name(out, &name);
destroy_name(&name);
}
static void string_variant(Storage *out, const char *text) {
Storage string;
api_string(&string, text);
from_string(out, &string);
destroy_string(&string);
}
static int invoke(Storage *receiver, const char *method,
const GDExtensionConstVariantPtr *arguments, int count, Storage *out) {
Storage name;
GDExtensionCallError error = { GDEXTENSION_CALL_OK, 0, 0 };
api_name(&name, method, 0);
api_call(receiver, &name, arguments, count, out, &error);
destroy_name(&name);
if (error.error != GDEXTENSION_CALL_OK) {
report_failure(method, (int)error.error);
return 0;
}
return 1;
}
static int singleton_variant(Storage *out, const char *text) {
Storage name;
api_name(&name, text, 0);
GDExtensionObjectPtr object = api_singleton(&name);
destroy_name(&name);
if (!object) return 0;
from_object(out, &object);
return 1;
}
static char *variant_utf8(Storage *value) {
if (api_type(value) != GDEXTENSION_VARIANT_TYPE_STRING) return NULL;
Storage string;
to_string(&string, value);
GDExtensionInt length = api_utf8(&string, NULL, 0);
char *text = NULL;
if (length >= 0 && length < 131072) {
text = (char *)malloc((size_t)length + 1);
if (text) {
api_utf8(&string, text, length);
text[length] = '\0';
}
}
destroy_string(&string);
return text;
}
static int plain_directory(const wchar_t *path) {
DWORD attrs = GetFileAttributesW(path);
return attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY) &&
!(attrs & FILE_ATTRIBUTE_REPARSE_POINT);
}
static int ensure_cache_directory(wchar_t *path, size_t capacity, const wchar_t *part) {
size_t length = wcslen(path), addition = wcslen(part);
if (length + addition + 2 >= capacity) return 0;
if (length && path[length - 1] != L'\\') path[length++] = L'\\';
memcpy(path + length, part, (addition + 1) * sizeof(wchar_t));
if (!CreateDirectoryW(path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) return 0;
return plain_directory(path);
}
static char *prepare_cache_path(void) {
Storage settings, argument, result;
if (!singleton_variant(&settings, "ProjectSettings")) return NULL;
string_variant(&argument, "res://");
const GDExtensionConstVariantPtr args[] = { &argument };
int ok = invoke(&settings, "globalize_path", args, 1, &result);
char *root_utf8 = ok ? variant_utf8(&result) : NULL;
api_destroy(&result);
api_destroy(&argument);
api_destroy(&settings);
if (!root_utf8) return NULL;
wchar_t path[32768];
int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, root_utf8, -1, path, 32768);
free(root_utf8);
if (length < 4 || path[1] != L':') return NULL;
for (int index = 0; index < length; ++index) if (path[index] == L'/') path[index] = L'\\';
/* Reject links/junctions in every existing directory, including project ancestors. */
for (int index = 3; index < length; ++index) {
if (path[index] != L'\\' && path[index] != L'\0') continue;
wchar_t saved = path[index];
path[index] = L'\0';
int plain = plain_directory(path);
path[index] = saved;
if (!plain) return NULL;
}
if (!ensure_cache_directory(path, 32768, L".godot") ||
!ensure_cache_directory(path, 32768, L"agc")) return NULL;
wchar_t suffix[96];
swprintf(suffix, 96, L"\\editor-bridge-%lu.json", (unsigned long)GetCurrentProcessId());
if (wcslen(path) + wcslen(suffix) + 1 >= 32768) return NULL;
wcscat(path, suffix);
DWORD attrs = GetFileAttributesW(path);
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))) return NULL;
int size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, path, -1, NULL, 0, NULL, NULL);
if (size <= 0) return NULL;
char *cache = (char *)malloc((size_t)size);
if (cache) WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, path, -1, cache, size, NULL, NULL);
return cache;
}
static void release_references(void) {
if (node_live) { api_destroy(&retained_node); node_live = 0; }
if (script_live) { api_destroy(&retained_script); script_live = 0; }
}
static int schedule_bridge(void) {
FILETIME creation, exit_time, kernel, user;
if (!GetProcessTimes(GetCurrentProcess(), &creation, &exit_time, &kernel, &user)) return 0;
ULARGE_INTEGER timestamp;
timestamp.LowPart = creation.dwLowDateTime;
timestamp.HighPart = creation.dwHighDateTime;
char started_file_time[32];
snprintf(started_file_time, sizeof(started_file_time), "%llu", (unsigned long long)timestamp.QuadPart);
char *cache_path = prepare_cache_path();
if (!cache_path) { report_failure("session_cache_path", 0); return 0; }
Storage classdb, class_arg, result, source;
if (!singleton_variant(&classdb, "ClassDB")) { free(cache_path); return 0; }
name_variant(&class_arg, "GDScript");
const GDExtensionConstVariantPtr class_args[] = { &class_arg };
int ok = invoke(&classdb, "instantiate", class_args, 1, &retained_script);
script_live = 1;
api_destroy(&class_arg);
api_destroy(&classdb);
if (!ok || api_type(&retained_script) != GDEXTENSION_VARIANT_TYPE_OBJECT) { free(cache_path); return 0; }
string_variant(&source, (const char *)AGC_EMBEDDED_BRIDGE);
const GDExtensionConstVariantPtr source_args[] = { &source };
ok = invoke(&retained_script, "set_source_code", source_args, 1, &result);
api_destroy(&result);
api_destroy(&source);
if (!ok) { free(cache_path); return 0; }
ok = invoke(&retained_script, "reload", NULL, 0, &result);
int64_t reload_error = -1;
if (ok && api_type(&result) == GDEXTENSION_VARIANT_TYPE_INT) to_int(&reload_error, &result);
api_destroy(&result);
if (!ok || reload_error != 0) { free(cache_path); report_failure("bridge_compile", (int)reload_error); return 0; }
ok = invoke(&retained_script, "new", NULL, 0, &retained_node);
node_live = 1;
if (!ok || api_type(&retained_node) != GDEXTENSION_VARIANT_TYPE_OBJECT) { free(cache_path); return 0; }
Storage method, build, process_identity, cache;
name_variant(&method, "bootstrap");
string_variant(&build, AGC_BUILD_ID);
string_variant(&process_identity, started_file_time);
string_variant(&cache, cache_path);
free(cache_path);
const GDExtensionConstVariantPtr deferred[] = { &method, &build, &process_identity, &cache };
ok = invoke(&retained_node, "call_deferred", deferred, 4, &result);
api_destroy(&result);
api_destroy(&method);
api_destroy(&build);
api_destroy(&process_identity);
api_destroy(&cache);
return ok;
}
static void initialize_bridge(void *userdata, GDExtensionInitializationLevel level) {
(void)userdata;
if (level != GDEXTENSION_INITIALIZATION_EDITOR || started) return;
started = 1;
if (!schedule_bridge()) release_references();
}
static void deinitialize_bridge(void *userdata, GDExtensionInitializationLevel level) {
(void)userdata;
if (level != GDEXTENSION_INITIALIZATION_EDITOR) return;
if (node_live && api_type(&retained_node) == GDEXTENSION_VARIANT_TYPE_OBJECT) {
Storage returned;
invoke(&retained_node, "native_deinitialize", NULL, 0, &returned);
api_destroy(&returned);
}
release_references();
}
__declspec(dllexport) GDExtensionBool agc_godot_editor_init(
GDExtensionInterfaceGetProcAddress get_proc_address,
GDExtensionClassLibraryPtr library,
GDExtensionInitialization *initialization) {
(void)library;
if (!get_proc_address || !initialization) return 0;
#define LOAD(variable, type, symbol) do { \
GDExtensionInterfaceFunctionPtr raw_function = get_proc_address(symbol); \
_Static_assert(sizeof(type) == sizeof(raw_function), "Windows function pointer ABI mismatch"); \
memcpy(&(variable), &raw_function, sizeof(variable)); \
if (!variable) return 0; \
} while (0)
LOAD(api_warning, GDExtensionInterfacePrintWarning, "print_warning");
LOAD(api_call, GDExtensionInterfaceVariantCall, "variant_call");
LOAD(api_destroy, GDExtensionInterfaceVariantDestroy, "variant_destroy");
LOAD(api_type, GDExtensionInterfaceVariantGetType, "variant_get_type");
LOAD(api_singleton, GDExtensionInterfaceGlobalGetSingleton, "global_get_singleton");
LOAD(api_name, GDExtensionInterfaceStringNameNewWithLatin1Chars, "string_name_new_with_latin1_chars");
LOAD(api_string, GDExtensionInterfaceStringNewWithUtf8Chars, "string_new_with_utf8_chars");
LOAD(api_utf8, GDExtensionInterfaceStringToUtf8Chars, "string_to_utf8_chars");
GDExtensionInterfaceGetVariantFromTypeConstructor get_from;
GDExtensionInterfaceGetVariantToTypeConstructor get_to;
GDExtensionInterfaceVariantGetPtrDestructor get_destructor;
LOAD(get_from, GDExtensionInterfaceGetVariantFromTypeConstructor, "get_variant_from_type_constructor");
LOAD(get_to, GDExtensionInterfaceGetVariantToTypeConstructor, "get_variant_to_type_constructor");
LOAD(get_destructor, GDExtensionInterfaceVariantGetPtrDestructor, "variant_get_ptr_destructor");
#undef LOAD
from_object = get_from(GDEXTENSION_VARIANT_TYPE_OBJECT);
from_string = get_from(GDEXTENSION_VARIANT_TYPE_STRING);
from_name = get_from(GDEXTENSION_VARIANT_TYPE_STRING_NAME);
to_int = get_to(GDEXTENSION_VARIANT_TYPE_INT);
to_string = get_to(GDEXTENSION_VARIANT_TYPE_STRING);
destroy_name = get_destructor(GDEXTENSION_VARIANT_TYPE_STRING_NAME);
destroy_string = get_destructor(GDEXTENSION_VARIANT_TYPE_STRING);
if (!from_object || !from_string || !from_name || !to_int || !to_string || !destroy_name || !destroy_string) return 0;
initialization->minimum_initialization_level = GDEXTENSION_INITIALIZATION_EDITOR;
initialization->userdata = NULL;
initialization->initialize = initialize_bridge;
initialization->deinitialize = deinitialize_bridge;
return 1;
}
@@ -0,0 +1,159 @@
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <godot_cpp/classes/gd_script.hpp>
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/project_settings.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/core/object.hpp>
#include <godot_cpp/godot.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include <string>
#include "embedded_bridge.h"
namespace {
using namespace godot;
// Godot 值的析构必须发生在官方绑定终止之前,不能依赖 DLL 静态析构顺序。
struct BridgeState {
Ref<GDScript> script;
uint64_t node_id = 0;
};
BridgeState *bridge = nullptr;
bool plain_directory(const std::wstring &path) {
const DWORD attributes = GetFileAttributesW(path.c_str());
return attributes != INVALID_FILE_ATTRIBUTES &&
(attributes & FILE_ATTRIBUTE_DIRECTORY) &&
!(attributes & FILE_ATTRIBUTE_REPARSE_POINT);
}
bool append_cache_directory(std::wstring &path, const wchar_t *part) {
if (path.back() != L'\\')
path += L'\\';
path += part;
if (!CreateDirectoryW(path.c_str(), nullptr) &&
GetLastError() != ERROR_ALREADY_EXISTS)
return false;
return plain_directory(path);
}
String session_cache_path() {
const String project_root =
ProjectSettings::get_singleton()->globalize_path("res://");
const Char16String utf16 = project_root.utf16();
static_assert(sizeof(wchar_t) == sizeof(char16_t),
"Windows UTF-16 path required");
std::wstring path(reinterpret_cast<const wchar_t *>(utf16.get_data()),
utf16.length());
if (path.size() < 3 || path.size() > 32000 || path[1] != L':')
return {};
for (wchar_t &character : path)
if (character == L'/')
character = L'\\';
// 项目根及其所有祖先都须为普通本地目录,不经过链接或 junction。
for (size_t index = 3; index <= path.size(); ++index) {
if (index == path.size() || path[index] == L'\\') {
if (!plain_directory(path.substr(0, index)))
return {};
}
}
if (!append_cache_directory(path, L".godot") ||
!append_cache_directory(path, L"agc"))
return {};
path +=
L"\\editor-bridge-" + std::to_wstring(GetCurrentProcessId()) + L".json";
const DWORD attributes = GetFileAttributesW(path.c_str());
if (attributes != INVALID_FILE_ATTRIBUTES &&
(attributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)))
return {};
return String::utf16(reinterpret_cast<const char16_t *>(path.c_str()));
}
bool schedule_bridge() {
FILETIME creation, exit_time, kernel, user;
if (!GetProcessTimes(GetCurrentProcess(), &creation, &exit_time, &kernel,
&user))
return false;
ULARGE_INTEGER timestamp;
timestamp.LowPart = creation.dwLowDateTime;
timestamp.HighPart = creation.dwHighDateTime;
const String started = String::num_uint64(timestamp.QuadPart);
const String cache = session_cache_path();
if (cache.is_empty())
return false;
bridge->script.instantiate();
bridge->script->set_source_code(
String::utf8(reinterpret_cast<const char *>(AGC_EMBEDDED_BRIDGE)));
if (bridge->script->reload() != OK)
return false;
const Variant instance = bridge->script->call("new");
Node *node = Object::cast_to<Node>(static_cast<Object *>(instance));
if (!node)
return false;
bridge->node_id = node->get_instance_id();
node->call_deferred("bootstrap", AGC_BUILD_ID, started, cache);
return true;
}
void initialize_bridge(ModuleInitializationLevel level) {
if (level != MODULE_INITIALIZATION_LEVEL_EDITOR || bridge)
return;
bridge = new BridgeState;
if (!schedule_bridge()) {
UtilityFunctions::push_warning(
"AGC Godot editor bridge initialization failed");
delete bridge;
bridge = nullptr;
}
}
void deinitialize_bridge(ModuleInitializationLevel level) {
if (level != MODULE_INITIALIZATION_LEVEL_EDITOR)
return;
if (bridge) {
// bootstrap 失败或编辑器退出时 Node 可能先被销毁,不能保留悬空指针。
if (Object *node = ObjectDB::get_instance(bridge->node_id)) {
const GDExtensionObjectPtr owner = node->_owner;
node->call("native_deinitialize");
// GDScript 的 queue_free 晚于 DLL 卸载;只移除 C++ 包装的回调,不销毁引擎
// Node。
internal::gdextension_interface_object_free_instance_binding(
owner, internal::token);
}
// 脚本仍可能被当前 GDScript 调用栈引用。用 Variant 保活引擎对象,先释放 C++
// Ref, 再解除 DLL 内的包装回调;保活值在本回调返回前销毁。
const Variant script_lifetime = bridge->script;
const GDExtensionObjectPtr script_owner =
bridge->script.is_valid() ? bridge->script->_owner : nullptr;
bridge->script.unref();
if (script_owner)
internal::gdextension_interface_object_free_instance_binding(
script_owner, internal::token);
delete bridge;
bridge = nullptr;
}
// 晚加载扩展只收到 EDITOR 生命周期,官方单例包装清理原本位于 CORE 终止阶段。
ClassDB::deinitialize(GDEXTENSION_INITIALIZATION_CORE);
}
} // namespace
extern "C" GDExtensionBool GDE_EXPORT
agc_godot_editor_init(GDExtensionInterfaceGetProcAddress get_proc_address,
GDExtensionClassLibraryPtr library,
GDExtensionInitialization *initialization) {
godot::GDExtensionBinding::InitObject init(get_proc_address, library,
initialization);
init.register_initializer(initialize_bridge);
init.register_terminator(deinitialize_bridge);
init.set_minimum_library_initialization_level(
godot::MODULE_INITIALIZATION_LEVEL_EDITOR);
return init.init();
}
@@ -0,0 +1,73 @@
import hashlib
import importlib.util
import json
from pathlib import Path
import sys
import tempfile
import unittest
import zipfile
from unittest.mock import patch
sys.dont_write_bytecode = True
spec = importlib.util.spec_from_file_location("dependencies", Path(__file__).parents[1] / "prepare_dependencies.py")
dependencies = importlib.util.module_from_spec(spec)
spec.loader.exec_module(dependencies)
class DependencyTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
self.commit = "a" * 40
self.name = f"godot-cpp-{self.commit}"
self.archive = self.root / ".build/dependencies" / f"{self.commit}.zip"
self.archive.parent.mkdir(parents=True)
(self.root / "vendor").mkdir()
(self.root / "vendor/LICENSE.txt").write_bytes(b"MIT\r\n")
self.write_archive({"LICENSE.md": b"MIT\n", "src/core.cpp": b"verified"})
def write_archive(self, entries):
with zipfile.ZipFile(self.archive, "w") as output:
for name, content in entries.items():
output.writestr(f"{self.name}/{name}", content)
provenance = {"commit": self.commit,
"archiveUrl": f"https://codeload.github.com/godotengine/godot-cpp/zip/{self.commit}",
"archiveSha256": hashlib.sha256(self.archive.read_bytes()).hexdigest()}
(self.root / "vendor/provenance.json").write_text(json.dumps(provenance), encoding="utf-8")
def test_verified_archive_is_reusable_offline(self):
with patch("urllib.request.urlopen", side_effect=AssertionError("Unexpected network")):
source = dependencies.prepare(self.root)
self.assertEqual(source, dependencies.prepare(self.root))
self.assertEqual((source / "src/core.cpp").read_bytes(), b"verified")
def test_corrupt_archive_is_rejected_and_preserved(self):
self.archive.write_bytes(b"corrupt")
with self.assertRaisesRegex(ValueError, "SHA256 mismatch"):
dependencies.prepare(self.root)
self.assertEqual(self.archive.read_bytes(), b"corrupt")
def test_modified_source_is_rejected_even_with_valid_archive(self):
source = dependencies.prepare(self.root)
(source / "src/core.cpp").write_bytes(b"modified")
with self.assertRaisesRegex(ValueError, "Modified godot-cpp"):
dependencies.prepare(self.root)
self.assertEqual((source / "src/core.cpp").read_bytes(), b"modified")
def test_extra_source_is_rejected(self):
source = dependencies.prepare(self.root)
(source / "extra.cpp").write_text("unexpected", encoding="utf-8")
with self.assertRaisesRegex(ValueError, "Unexpected dependency"):
dependencies.prepare(self.root)
def test_archive_paths_cannot_escape_on_windows_or_posix(self):
for name in ["../../escaped", "C:\\escaped", "..\\escaped"]:
with self.subTest(name=name):
self.write_archive({name: b"unexpected"})
with self.assertRaisesRegex(ValueError, "Unsafe dependency"):
dependencies.prepare(self.root)
if __name__ == "__main__":
unittest.main()
@@ -1,5 +1,6 @@
Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md).
Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.
# MIT License
Copyright (c) 2017-present Godot Engine contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
File diff suppressed because it is too large Load Diff
@@ -1,10 +1,10 @@
{
"project": "Godot Engine",
"version": "4.7.2-stable",
"commit": "ed1daf0bf001b61586d9930840f2f1394092c079",
"project": "godot-cpp",
"version": "godot-4.5-stable",
"commit": "e83fd0904c13356ed1d4c3d09f8bb9132bdc6b77",
"license": "MIT",
"licenseFile": "LICENSE.txt",
"interfaceSource": "https://github.com/godotengine/godot/blob/ed1daf0bf001b61586d9930840f2f1394092c079/core/extension/gdextension_interface.json",
"headerGenerator": "https://github.com/godotengine/godot/blob/ed1daf0bf001b61586d9930840f2f1394092c079/core/extension/make_interface_header.py",
"generation": "Official unmodified generator using local file IO helpers; include guard and provenance comments added. No godot-cpp dependency."
"archiveUrl": "https://codeload.github.com/godotengine/godot-cpp/zip/e83fd0904c13356ed1d4c3d09f8bb9132bdc6b77",
"archiveSha256": "579af30c5f62c1084edb28216788188227d80a583f0114463699ac4edd22140f",
"generation": "Unmodified official C++ bindings and interface, generated using the checked-in minimal build profile and statically linked into the editor DLL."
}