13std::string find(
const std::string&
text,
const std::string&
pattern) {
15 return std::regex_search(
text,
m, std::regex(
pattern)) ?
m[1].str() : std::string{};
17std::vector<Doc> docs(
const std::string&
text) {
19 std::size_t
start = 0;
20 const std::regex
header(R
"((?:^|\n)--- !u!([0-9]+) &(-?[0-9]+)([^\n]*)\n)");
21 for (std::sregex_iterator it(
text.begin(),
text.end(), header),
end; it !=
end; ++it) {
22 if (!out.empty()) out.back().body =
text.substr(
start, std::size_t(it->position()) -
start);
23 out.push_back({(*it)[1], (*it)[2], {}, (*it)[3].str().find(
"stripped") != std::string::npos});
24 start = std::size_t(it->position() + it->length());
26 if (!out.empty()) out.back().body =
text.substr(
start);
29void replaceCapture(std::string&
body,
const std::string&
pattern,
const std::string&
value) {
31 if (!std::regex_search(
body,
m, std::regex(
pattern)))
throw std::runtime_error(
"Prefab override field missing");
32 body.replace(std::size_t(
m.position(1)), std::size_t(
m.length(1)),
value);
36 std::map<std::string, std::string>
paths;
39 std::vector<Doc> expand(
const std::string& guid,
unsigned depth) {
41 throw std::runtime_error(
"Prefab dependency cycle or depth budget exceeded");
42 if (!
paths.contains(guid))
throw std::runtime_error(
"Prefab source GUID is missing");
46 std::erase(
text,
'\r');
47 auto original = docs(
text);
49 std::set<std::string> used;
50 for (
const auto&
d : original)
51 if (!used.insert(
d.id).second)
throw std::runtime_error(
"duplicate Prefab fileID");
52 for (
const auto&
d : original)
53 if (
d.type !=
"1001" && !
d.stripped) out.push_back(
d);
54 for (
const auto& instance : original) {
55 if (instance.type !=
"1001")
continue;
56 const auto version = find(instance.body, R
"(serializedVersion: *([0-9]+))");
57 if (!version.empty() && version !=
"2")
throw std::runtime_error(
"unsupported PrefabInstance version");
58 if (instance.body.find(
"m_RemovedComponents:") == std::string::npos)
59 throw std::runtime_error(
"Prefab removed-component list missing");
60 const auto source = find(instance.body, R
"(m_SourcePrefab: *\{fileID: *[0-9]+, guid: *([0-9a-fA-F]{32}))");
61 const auto parent = find(instance.body, R
"(m_TransformParent: *\{fileID: *(-?[0-9]+)\})");
62 if (
source.empty() ||
parent.empty())
throw std::runtime_error(
"Prefab instance source or parent missing");
64 const auto removed = find(instance.body, R
"(m_RemovedComponents:([\s\S]*?)\n m_SourcePrefab:)");
65 const std::regex removal(R
"(fileID: (-?[0-9]+), guid: ([0-9a-fA-F]{32}))");
68 throw std::runtime_error(
"removed-component GUID mismatch");
69 const auto id = (*it)[1].str();
71 if (child.
id ==
id && (child.
type ==
"1" || child.
type ==
"4"))
72 throw std::runtime_error(
"cannot remove hierarchy as component");
73 std::erase_if(
children, [&](
const auto& child) {
return child.
id ==
id; });
75 std::map<std::string, std::string>
ids;
76 for (
const auto&
stripped : original)
78 find(
stripped.body, R
"(m_PrefabInstance: *\{fileID: *(-?[0-9]+)\})") == instance.id) {
79 const auto target = find(
stripped.body, R
"(m_CorrespondingSourceObject: *\{fileID: *(-?[0-9]+),)");
81 throw std::runtime_error(
"invalid stripped Prefab alias");
83 std::map<std::string, std::size_t> byId;
84 for (std::size_t i = 0; i <
children.size(); ++i) byId.emplace(
children[i].id, i);
85 const std::regex modification(
86 R
"( - target: \{fileID: (-?[0-9]+), guid: ([0-9a-fA-F]{32}),\s*type: [0-9]+\}\n propertyPath: ([^\n]+)\n value:([^\n]*)\n objectReference: (\{[^\n]+\}))");
87 for (std::sregex_iterator it(instance.body.begin(), instance.body.end(), modification),
end; it !=
end;
90 throw std::runtime_error(
"Prefab override source GUID mismatch");
91 const auto id = (*it)[1].str(),
property = (*it)[3].str();
92 auto value = (*it)[4].str();
94 if (!byId.contains(
id)) {
96 "override target no longer exists in source prefab: " +
id +
" " +
property});
102 "override on a non-converted component: " +
property});
105 if (property ==
"m_RootOrder" || property ==
"m_StaticEditorFlags" ||
106 property.starts_with(
"m_LocalEulerAnglesHint."))
108 if (property.starts_with(
"m_LocalPosition.") ||
property.starts_with(
"m_LocalRotation.") ||
109 property.starts_with(
"m_LocalScale.")) {
110 const auto dot =
property.find(
'.');
111 const auto field =
property.substr(0, dot);
112 const auto axis =
property.substr(dot + 1);
113 if (axis.size() != 1 || std::string(
"xyzw").find(axis) == std::string::npos)
114 throw std::runtime_error(
"invalid transform override axis");
115 replaceCapture(
target.body, field +
": *\\{[^}]*?" + axis +
": *([^,}]+)",
value);
116 }
else if (property ==
"m_Name" || property ==
"m_IsActive" || property ==
"m_Enabled") {
117 replaceCapture(
target.body,
"(?:^|\\n) " + property +
": *([^\\n]*)",
value);
118 }
else if (property ==
"m_Mesh") {
119 replaceCapture(
target.body, R
"(m_Mesh: *(\{[^\n]+\}))", (*it)[5]);
121 throw std::runtime_error(
"unsupported visual Prefab override: " + property);
124 if (!
ids.contains(child.
id)) {
126 request.package.packageId.child(
"prefab-expand:" + guid +
":" + instance.id +
":" + child.
id);
127 std::uint64_t
value = 0;
128 for (
unsigned i = 0; i < 8; ++i)
value = (
value << 8) | identity.bytes()[i + 8];
129 value &= 0x7fffffffffffffffull;
130 const auto id = std::to_string(
value);
131 if (
value == 0 || !used.insert(
id).second)
132 throw std::runtime_error(
"expanded Prefab identity collision");
133 ids.emplace(child.
id,
id);
136 if (child.
type ==
"4" && find(child.body, R
"(m_Father: *\{fileID: *(-?[0-9]+)\})") == "0")
137 replaceCapture(child.body, R
"(m_Father: *(\{fileID: *0\}))", "{fileID: __parent__}");
138 const std::regex
local(R
"(\{fileID: (-?[0-9]+)\})");
139 std::string rewritten;
140 std::size_t start = 0;
141 for (std::sregex_iterator it(child.body.begin(), child.body.end(),
local),
end; it !=
end; ++it) {
142 rewritten += child.body.substr(
start, std::size_t(it->position()) -
start);
143 const auto id = (*it)[1].str();
144 rewritten +=
"{fileID: " + (
ids.contains(
id) ?
ids.at(
id) :
id) +
"}";
145 start = std::size_t(it->position() + it->length());
147 rewritten += child.body.substr(
start);
148 const auto marker = rewritten.find(
"__parent__");
149 if (marker != std::string::npos) rewritten.replace(marker, 10,
parent);
150 child.body = std::move(rewritten);
152 child.stripped =
false;
153 out.push_back(std::move(child));
154 if (out.size() >
request.limits.maximumAssets)
155 throw std::runtime_error(
"expanded Prefab object budget exceeded");
166 Expander expander{
request, {}, {}, {}};
167 for (
const auto& asset :
index.assets)
169 auto expanded = expander.expand(
source.
guid, 0);
170 std::string
text =
"%YAML 1.1\n";
171 for (
const auto& doc : expanded) {
172 text +=
"--- !u!" + doc.type +
" &" + doc.id +
"\n" + doc.body;
173 if (
text.back() !=
'\n')
text +=
'\n';
175 throw std::runtime_error(
"expanded Prefab byte budget exceeded");
178 }
catch (
const std::exception& e) {
const GltfImportRequest & request
Direct Unity text-serialization adapter for TerrainData and Prefab assets.
std::set< std::string > visiting
std::map< std::string, std::string > paths
std::vector< ImportFinding > findings
const UnitySourceAsset & source
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
std::string foldAscii(std::string value)
Fold ascii.
Result< UnityExpandedPrefab > expandUnityPrefab(const UnityProjectImportRequest &request, const UnitySourceIndex &index, const UnitySourceAsset &source)
Resolve bounded nested prefab references into an unpublished owning text candidate.
std::string header(int localX, int localY)
Header.
std::uint64_t maximumSourceBytes
In-memory Unity project slice; keys are normalized project-relative paths.
Immutable-by-convention owning source index entry; no borrowed source addresses.
Owning deterministic index sorted by path, plus missing-reference diagnostics.