Advance N-023 through N-026 audited parity

This commit is contained in:
mes123456
2026-08-12 15:23:35 -04:00
parent b3cefaeec5
commit 86136139e2
30 changed files with 5573 additions and 66 deletions

View File

@@ -23,6 +23,7 @@
#include "BLI_endian_defines.h"
#include "BLI_filereader.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "BLO_core_bhead.hh"
@@ -36,6 +37,9 @@
#include "DNA_node_types.h"
#include "DNA_sdna_types.h"
#include "DNA_sequence_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_enums.h"
#include "DNA_workspace_types.h"
namespace {
@@ -44,6 +48,73 @@ using namespace blender;
constexpr uint64_t MAX_BLEND_BLOCK_BYTES = UINT64_C(1024) * 1024 * 1024;
constexpr size_t MAX_NON_MESH_POINTS = 1000000;
constexpr size_t MAX_SCRIPT_SOURCE_BYTES = 1024 * 1024;
uint32_t rotate_right(const uint32_t value, const uint32_t bits)
{
return (value >> bits) | (value << (32 - bits));
}
std::string sha256_hex(const std::string &value)
{
static constexpr std::array<uint32_t, 64> round_constants = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
std::array<uint32_t, 8> digest = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
std::vector<uint8_t> message(value.begin(), value.end());
const uint64_t bit_length = uint64_t(message.size()) * 8;
message.push_back(0x80);
while (message.size() % 64 != 56) message.push_back(0);
for (int shift = 56; shift >= 0; shift -= 8) message.push_back(uint8_t(bit_length >> shift));
for (size_t offset = 0; offset < message.size(); offset += 64) {
std::array<uint32_t, 64> words{};
for (size_t index = 0; index < 16; index++) {
const size_t byte = offset + index * 4;
words[index] = uint32_t(message[byte]) << 24 | uint32_t(message[byte + 1]) << 16 |
uint32_t(message[byte + 2]) << 8 | uint32_t(message[byte + 3]);
}
for (size_t index = 16; index < words.size(); index++) {
const uint32_t s0 = rotate_right(words[index - 15], 7) ^
rotate_right(words[index - 15], 18) ^ (words[index - 15] >> 3);
const uint32_t s1 = rotate_right(words[index - 2], 17) ^
rotate_right(words[index - 2], 19) ^ (words[index - 2] >> 10);
words[index] = words[index - 16] + s0 + words[index - 7] + s1;
}
uint32_t a = digest[0], b = digest[1], c = digest[2], d = digest[3];
uint32_t e = digest[4], f = digest[5], g = digest[6], h = digest[7];
for (size_t index = 0; index < words.size(); index++) {
const uint32_t sum1 = rotate_right(e, 6) ^ rotate_right(e, 11) ^ rotate_right(e, 25);
const uint32_t choice = (e & f) ^ (~e & g);
const uint32_t temporary1 = h + sum1 + choice + round_constants[index] + words[index];
const uint32_t sum0 = rotate_right(a, 2) ^ rotate_right(a, 13) ^ rotate_right(a, 22);
const uint32_t majority = (a & b) ^ (a & c) ^ (b & c);
const uint32_t temporary2 = sum0 + majority;
h = g; g = f; f = e; e = d + temporary1;
d = c; c = b; b = a; a = temporary1 + temporary2;
}
digest[0] += a; digest[1] += b; digest[2] += c; digest[3] += d;
digest[4] += e; digest[5] += f; digest[6] += g; digest[7] += h;
}
static constexpr char hex[] = "0123456789abcdef";
std::string result(64, '0');
for (size_t index = 0; index < digest.size(); index++) {
for (size_t nibble = 0; nibble < 8; nibble++) {
result[index * 8 + nibble] = hex[(digest[index] >> (28 - nibble * 4)) & 0xf];
}
}
return result;
}
struct FileReaderDeleter {
void operator()(FileReader *reader) const
@@ -465,6 +536,9 @@ std::string id_prefix(const std::string &type_name)
if (type_name == "Text") return "text";
if (type_name == "VFont") return "vfont";
if (type_name == "Mask") return "mask";
if (type_name == "Library") return "library";
if (type_name == "WorkSpace") return "workspace";
if (type_name == "bScreen") return "screen";
return "datablock";
}
@@ -477,7 +551,8 @@ bool is_exported_id_type(const std::string &type_name)
type_name == "bArmature" || type_name == "World" || type_name == "Curve" ||
type_name == "MetaBall" || type_name == "PointCloud" || type_name == "Curves" ||
type_name == "Volume" || type_name == "GreasePencil" || type_name == "Text" ||
type_name == "VFont" || type_name == "Mask";
type_name == "VFont" || type_name == "Mask" || type_name == "Library" ||
type_name == "WorkSpace" || type_name == "bScreen";
}
std::string unique_id(const std::string &prefix,
@@ -1050,6 +1125,178 @@ std::optional<json> mask_from_record(const ParsedBlend &blend,
return json{{"id", mask_id}, {"name", mask_name}, {"layers", std::move(layers)}};
}
const char *editor_type_from_space(const int64_t space_type)
{
switch (space_type) {
case SPACE_VIEW3D: return "VIEW_3D";
case SPACE_OUTLINER: return "OUTLINER";
case SPACE_PROPERTIES: return "PROPERTIES";
case SPACE_IMAGE: return "UV_IMAGE";
case SPACE_NODE: return "NODE";
case SPACE_GRAPH: return "GRAPH";
case SPACE_ACTION: return "DOPE_SHEET";
case SPACE_NLA: return "NLA";
case SPACE_SPREADSHEET: return "SPREADSHEET";
case SPACE_SEQ: return "SEQUENCER";
case SPACE_CLIP: return "CLIP";
default: return nullptr;
}
}
const char *editor_region_kind(const int64_t region_type)
{
if (region_type == RGN_TYPE_WINDOW || region_type == RGN_TYPE_PREVIEW) return "MAIN";
if (region_type == RGN_TYPE_HEADER || region_type == RGN_TYPE_TOOL_HEADER ||
region_type == RGN_TYPE_ASSET_SHELF_HEADER || region_type == RGN_TYPE_SCRUBBING)
{
return "HEADER";
}
if (region_type == RGN_TYPE_TOOLS || region_type == RGN_TYPE_TOOL_PROPS) return "TOOLBAR";
if (region_type == RGN_TYPE_UI || region_type == RGN_TYPE_CHANNELS ||
region_type == RGN_TYPE_NAV_BAR || region_type == RGN_TYPE_ASSET_SHELF)
{
return "SIDEBAR";
}
if (region_type == RGN_TYPE_FOOTER || region_type == RGN_TYPE_EXECUTE) return "FOOTER";
return nullptr;
}
std::optional<json> editor_workflow_from_records(
const ParsedBlend &blend,
const std::vector<IdRecord> &records,
const uint64_t revision,
const std::string &active_object_id)
{
json workspaces = json::array();
size_t area_count = 0;
size_t region_count = 0;
for (const IdRecord &record : records) {
if (record.type_name != "WorkSpace" || workspaces.size() >= 64) continue;
const ElementRef workspace{record.block, 0, record.type_name};
const std::vector<ElementRef> layouts = linked_list_elements(blend, workspace, "layouts");
if (layouts.empty()) continue;
const std::optional<uint64_t> screen_pointer = read_pointer(*blend.sdna, layouts.front(), "screen");
const std::optional<ElementRef> screen = screen_pointer ? element_for_pointer(blend, *screen_pointer) :
std::nullopt;
if (!screen) continue;
const std::vector<ElementRef> source_areas = linked_list_elements(blend, *screen, "areabase");
if (source_areas.empty() || area_count > 1024 - source_areas.size()) continue;
struct AreaBounds {
int x_min;
int y_min;
int x_max;
int y_max;
};
std::vector<AreaBounds> bounds;
bounds.reserve(source_areas.size());
bool valid = true;
int screen_x_min = std::numeric_limits<int>::max();
int screen_y_min = std::numeric_limits<int>::max();
int screen_x_max = std::numeric_limits<int>::min();
int screen_y_max = std::numeric_limits<int>::min();
for (const ElementRef &area : source_areas) {
if (editor_type_from_space(read_integer(*blend.sdna, area, "spacetype").value_or(-1)) == nullptr) {
valid = false;
break;
}
const std::optional<ElementRef> total_rect = embedded_element(*blend.sdna, area, "totrct");
if (!total_rect) {
valid = false;
break;
}
const std::vector<int64_t> area_rect = {
read_integer(*blend.sdna, *total_rect, "xmin").value_or(0),
read_integer(*blend.sdna, *total_rect, "ymin").value_or(0),
read_integer(*blend.sdna, *total_rect, "xmax").value_or(0),
read_integer(*blend.sdna, *total_rect, "ymax").value_or(0)};
if (area_rect[2] <= area_rect[0] || area_rect[3] <= area_rect[1]) {
valid = false;
break;
}
bounds.push_back({int(area_rect[0]), int(area_rect[1]), int(area_rect[2]), int(area_rect[3])});
screen_x_min = std::min(screen_x_min, int(area_rect[0]));
screen_y_min = std::min(screen_y_min, int(area_rect[1]));
screen_x_max = std::max(screen_x_max, int(area_rect[2]));
screen_y_max = std::max(screen_y_max, int(area_rect[3]));
}
const int screen_width = screen_x_max - screen_x_min;
const int screen_height = screen_y_max - screen_y_min;
if (!valid || screen_width <= 0 || screen_height <= 0) continue;
json areas = json::array();
size_t workspace_region_count = 0;
for (size_t area_index = 0; area_index < source_areas.size(); area_index++) {
const ElementRef &area = source_areas[area_index];
const char *editor = editor_type_from_space(
read_integer(*blend.sdna, area, "spacetype").value_or(-1));
json regions = json::array();
bool has_main_region = false;
for (const ElementRef &region : linked_list_elements(blend, area, "regionbase")) {
const char *kind = editor_region_kind(
read_integer(*blend.sdna, region, "regiontype").value_or(-1));
if (kind == nullptr) continue;
if (++workspace_region_count > 4096 - region_count) {
valid = false;
break;
}
const int64_t flags = read_integer(*blend.sdna, region, "flag").value_or(0);
regions.push_back({{"id", record.id + ":area:" + std::to_string(area_index) +
":region:" + std::to_string(regions.size())},
{"kind", kind},
{"visible", (flags & RGN_FLAG_HIDDEN) == 0}});
has_main_region |= std::strcmp(kind, "MAIN") == 0;
}
if (!valid || regions.empty() || !has_main_region) {
valid = false;
break;
}
const AreaBounds &rect = bounds[area_index];
const double x = double(rect.x_min - screen_x_min) / screen_width;
const double y = double(rect.y_min - screen_y_min) / screen_height;
const double width = rect.x_max == screen_x_max ? 1.0 - x :
double(rect.x_max - rect.x_min) / screen_width;
const double height = rect.y_max == screen_y_max ? 1.0 - y :
double(rect.y_max - rect.y_min) / screen_height;
areas.push_back({{"id", record.id + ":area:" + std::to_string(area_index)},
{"editor", editor},
{"regions", std::move(regions)},
{"rect",
{{"x", x}, {"y", y}, {"width", width}, {"height", height}}},
{"maximized", false}});
}
if (!valid || areas.empty()) continue;
area_count += areas.size();
region_count += workspace_region_count;
workspaces.push_back({{"id", record.id},
{"name", record.name},
{"areas", std::move(areas)},
{"activeAreaId", record.id + ":area:0"},
{"revision", revision}});
}
if (workspaces.empty()) return std::nullopt;
const json &workspace = workspaces.front();
const json &active_area = workspace["areas"].front();
const std::string workspace_id = workspace.value("id", "");
const std::string active_area_id = active_area.value("id", "");
const std::string active_editor = active_area.value("editor", "");
json selection = json::array();
if (!active_object_id.empty()) selection.push_back(active_object_id);
return json{{"schemaVersion", 1},
{"workspaces", std::move(workspaces)},
{"context",
{{"workspaceId", workspace_id},
{"activeAreaId", active_area_id},
{"activeEditor", active_editor},
{"mode", "OBJECT"},
{"activeObjectId", active_object_id.empty() ? json(nullptr) : json(active_object_id)},
{"selection", std::move(selection)},
{"viewLayer", "ViewLayer"},
{"pinnedData", nullptr},
{"revision", revision}}},
{"keymaps", json::array()}};
}
struct RawBlockSpan {
const BlendBlock *block = nullptr;
size_t offset = 0;
@@ -1670,6 +1917,64 @@ std::string read_raw_string(const ParsedBlend &blend,
return std::string(data, strnlen(data, available));
}
std::optional<json> script_source_from_record(const ParsedBlend &blend,
const ElementRef &element,
const IdRecord &record)
{
const std::vector<ElementRef> lines = linked_list_elements(blend, element, "lines");
if (lines.empty() || lines.size() > 65536) return std::nullopt;
std::string source;
for (size_t index = 0; index < lines.size(); index++) {
const int64_t length = read_integer(*blend.sdna, lines[index], "len").value_or(-1);
const std::optional<uint64_t> pointer = read_pointer(*blend.sdna, lines[index], "line");
const size_t separator_bytes = index > 0 ? 1 : 0;
if (length < 0 || length > int64_t(MAX_SCRIPT_SOURCE_BYTES) || !pointer ||
size_t(length) > MAX_SCRIPT_SOURCE_BYTES - separator_bytes ||
source.size() > MAX_SCRIPT_SOURCE_BYTES - separator_bytes - size_t(length))
{
return std::nullopt;
}
const std::vector<uint8_t> bytes = read_raw_byte_array(blend, *pointer, size_t(length));
if (bytes.size() != size_t(length) ||
std::find(bytes.begin(), bytes.end(), uint8_t(0)) != bytes.end() ||
(!bytes.empty() &&
BLI_str_utf8_invalid_byte(reinterpret_cast<const char *>(bytes.data()), bytes.size()) != -1))
{
return std::nullopt;
}
if (index > 0) source.push_back('\n');
source.append(reinterpret_cast<const char *>(bytes.data()), bytes.size());
}
const std::optional<uint64_t> path_pointer = read_pointer(*blend.sdna, element, "filepath");
const std::string source_path = path_pointer ? read_raw_string(blend, *path_pointer, 2049) :
std::string();
if (!source_path.empty()) {
const bool project_path = source_path.rfind("//", 0) == 0 && source_path.size() > 2 &&
source_path.size() <= 2050 &&
source_path.find('\\') == std::string::npos &&
source_path.find('%') == std::string::npos &&
source_path.find("/../") == std::string::npos &&
source_path.find("//", 2) == std::string::npos;
if (!project_path) return std::nullopt;
}
const int64_t flags = read_integer(*blend.sdna, element, "flags").value_or(0);
const bool module_autorun_requested = (flags & (1 << 4)) != 0;
json result = {{"id", record.id},
{"name", record.name},
{"source", source},
{"sourceSha256", sha256_hex(source)},
{"byteLength", source.size()},
{"lineCount", lines.size()},
{"internal", source_path.empty()},
{"moduleAutorunRequested", module_autorun_requested},
{"readOnly", true},
{"executionStatus", "BLOCKED"},
{"errorCode", module_autorun_requested ? "SCRIPT_POLICY_DENIED" :
"SCRIPT_SANDBOX_UNAVAILABLE"}};
if (!source_path.empty()) result["sourcePath"] = source_path;
return result;
}
std::vector<uint64_t> read_raw_pointer_array(const ParsedBlend &blend,
const uint64_t pointer,
const size_t count)
@@ -1829,8 +2134,7 @@ json non_mesh_data_from_record(const ParsedBlend &blend,
record.type_name == "MetaBall" ? "METABALL" :
record.type_name == "PointCloud" ? "POINT_CLOUD" :
record.type_name == "Curves" ? "CURVES" :
record.type_name == "Volume" ? "VOLUME" :
record.type_name == "Text" ? "FONT" : "CURVE";
record.type_name == "Volume" ? "VOLUME" : "CURVE";
json data = { {"id", record.id},
{"name", record.name},
{"type", type_name},
@@ -2122,19 +2426,6 @@ json non_mesh_data_from_record(const ParsedBlend &blend,
"NON_MESH_DATA_UNSUPPORTED";
}
}
else if (record.type_name == "Text") {
json lines = json::array();
size_t total_chars = 0;
for (const ElementRef &line : linked_list_elements(blend, element, "lines")) {
const std::optional<uint64_t> pointer = read_pointer(*blend.sdna, line, "line");
const std::string value = pointer ? read_raw_string(blend, *pointer, 4096) : std::string();
total_chars += value.size();
lines.push_back(value);
}
data["text"] = lines.empty() ? "" : lines[0].get<std::string>();
data["pointCount"] = total_chars;
data["splineCount"] = lines.size();
}
return data;
}
@@ -2756,8 +3047,12 @@ json scene_ir_from_blend(const ParsedBlend &blend,
json non_mesh_data = json::array();
json vfonts = json::array();
json libraries = json::array();
json script_sources = json::array();
size_t script_source_bytes = 0;
json masks = json::array();
bool masks_blocked = false;
bool libraries_blocked = false;
bool script_sources_blocked = false;
json animations = json::array();
json nla_tracks = json::array();
json armatures = json::array();
@@ -3234,9 +3529,29 @@ json scene_ir_from_blend(const ParsedBlend &blend,
}
else if (record.type_name == "Curve" || record.type_name == "MetaBall" ||
record.type_name == "PointCloud" || record.type_name == "Curves" ||
record.type_name == "Volume" || record.type_name == "Text") {
record.type_name == "Volume") {
non_mesh_data.push_back(non_mesh_data_from_record(blend, element, record, ids_by_pointer));
}
else if (record.type_name == "Text") {
if (script_sources.size() >= 1024) {
script_sources_blocked = true;
}
else if (const std::optional<json> source = script_source_from_record(
blend, element, record))
{
const size_t byte_length = source->value("byteLength", size_t(0));
if (byte_length > MAX_SCRIPT_SOURCE_BYTES - script_source_bytes) {
script_sources_blocked = true;
}
else {
script_source_bytes += byte_length;
script_sources.push_back(*source);
}
}
else {
script_sources_blocked = true;
}
}
else if (record.type_name == "VFont") {
const std::string filepath = read_string(*blend.sdna, element, "filepath");
vfonts.push_back({{"id", record.id}, {"name", record.name}, {"sourcePath", filepath},
@@ -3460,14 +3775,36 @@ json scene_ir_from_blend(const ParsedBlend &blend,
if (block.type_name != "Library") continue;
const ElementRef library{&block, 0, block.type_name};
const std::string path = read_string(*blend.sdna, library, "filepath");
const std::string library_name = read_id_name(*blend.sdna, library);
const std::string library_id = "library:" + library_name;
const bool project_path = path.rfind("//", 0) == 0 && path.size() > 2 && path.size() <= 2050 &&
path.find('\\') == std::string::npos && path.find('%') == std::string::npos &&
path.find("/../") == std::string::npos && path.find("//", 2) == std::string::npos;
if (!project_path || library_name.empty() || library_id.size() > 256) {
libraries_blocked = true;
continue;
}
const std::optional<uint64_t> packed_file = read_pointer(*blend.sdna, library, "packedfile");
const std::vector<uint8_t> packed_bytes = packed_file ? packed_file_bytes(blend, *packed_file) :
std::vector<uint8_t>();
json resource = {{"id", "library:" + read_id_name(*blend.sdna, library)},
{"name", read_id_name(*blend.sdna, library)},
json dependencies = json::array();
const std::optional<uint64_t> parent_pointer = read_pointer(
*blend.sdna, library, "archive_parent_library");
if (parent_pointer && *parent_pointer != 0) {
const auto parent_id = ids_by_pointer.find(*parent_pointer);
if (parent_id == ids_by_pointer.end()) {
libraries_blocked = true;
continue;
}
dependencies.push_back(parent_id->second);
}
json resource = {{"id", library_id},
{"name", library_name},
{"sourcePath", path},
{"packed", !packed_bytes.empty()},
{"status", packed_bytes.empty() ? "EXTERNAL_REQUIRED" : "PACKED"}};
{"status", packed_bytes.empty() ? "EXTERNAL_REQUIRED" : "PACKED"},
{"dependencyIds", std::move(dependencies)},
{"readOnly", true}};
if (packed_bytes.empty()) resource["errorCode"] = "LINKED_LIBRARY_RESOURCE_REQUIRED";
else resource["packedByteLength"] = packed_bytes.size();
libraries.push_back(std::move(resource));
@@ -3511,13 +3848,17 @@ json scene_ir_from_blend(const ParsedBlend &blend,
sort_by_id(non_mesh_data);
sort_by_id(vfonts);
sort_by_id(libraries);
sort_by_id(script_sources);
sort_by_id(animations);
sort_by_id(armatures);
sort_by_id(collections);
sort_by_id(scenes);
populate_world_matrices(nodes);
const std::string active_object_id = first_mesh_object_id.empty() ? first_object_id :
first_mesh_object_id;
std::optional<json> editor_workflow = editor_workflow_from_records(
blend, records, revision, active_object_id);
json snapshot = {
{"schemaVersion", 1},
{"revision", revision},
@@ -3544,17 +3885,16 @@ json scene_ir_from_blend(const ParsedBlend &blend,
{"images", std::move(images)},
{"nonMeshData", std::move(non_mesh_data)},
{"vfonts", std::move(vfonts)},
{"libraries", std::move(libraries)},
{"libraryStatus", libraries_blocked ? "BLOCKED" : "AVAILABLE"},
{"trackingMaskStatus", masks_blocked ? "BLOCKED" : "AVAILABLE"},
{"editorWorkflowStatus", editor_workflow ? "AVAILABLE" : "BLOCKED"},
{"scriptSourceStatus", script_sources_blocked ? "BLOCKED" : "AVAILABLE"},
{"animations", std::move(animations)},
{"nlaTracks", std::move(nla_tracks)},
{"armatures", std::move(armatures)},
{"collections", std::move(collections)},
{"scenes", std::move(scenes)},
{"activeObjectId",
(first_mesh_object_id.empty() ? first_object_id : first_mesh_object_id).empty() ?
json(nullptr) :
json(first_mesh_object_id.empty() ? first_object_id : first_mesh_object_id)},
{"activeObjectId", active_object_id.empty() ? json(nullptr) : json(active_object_id)},
{"frame", {{"current", frame_current}, {"start", frame_start}, {"end", frame_end}}}};
if (!masks_blocked) {
snapshot["trackingMasks"] = {{"schemaVersion", 1},
@@ -3563,6 +3903,11 @@ json scene_ir_from_blend(const ParsedBlend &blend,
{"masks", std::move(masks)},
{"bindings", json::array()}};
}
if (!libraries_blocked) snapshot["libraries"] = std::move(libraries);
if (editor_workflow) snapshot["editorWorkflow"] = std::move(*editor_workflow);
if (!script_sources_blocked) {
snapshot["scriptSources"] = {{"schemaVersion", 1}, {"sources", std::move(script_sources)}};
}
return snapshot;
}