载入中...
搜索中...
未找到
UnitySource.cpp
浏览该文件的文档.
2
3#include <charconv>
4#include <regex>
5#include <set>
6#include <tuple>
7
9
10Result<std::string> metaScalar(std::span<const std::uint8_t> bytes, std::string_view key, std::string_view path) {
11 if (std::find(bytes.begin(), bytes.end(), 0) != bytes.end())
13 std::string(path), {}, "asset.import.unity"));
14 const std::string_view text(reinterpret_cast<const char*>(bytes.data()), bytes.size());
17 std::string(path), {}, "asset.import.unity"));
18 std::string value;
19 bool found = false;
20 for (std::size_t at = 0; at < text.size();) {
21 auto end = text.find('\n', at);
22 if (end == std::string_view::npos) end = text.size();
23 auto line = text.substr(at, end - at);
24 if (at == 0 && line.starts_with("\xef\xbb\xbf")) line.remove_prefix(3);
25 if (line.starts_with(key) && line.size() > key.size() && line[key.size()] == ':') {
26 if (found)
28 Diagnostic::error(DiagnosticCode::Conflict, "duplicate Unity metadata key: " + std::string(key),
29 std::string(path), {}, "asset.import.unity"));
30 found = true;
31 line.remove_prefix(key.size() + 1);
32 const auto first = line.find_first_not_of(" \t\r");
33 if (first != std::string_view::npos)
34 value = std::string(line.substr(first, line.find_last_not_of(" \t\r") - first + 1));
35 }
36 at = end + 1;
37 }
38 return Result<std::string>::success(std::move(value));
39}
40
41} // namespace eve::asset_import::unity_detail
42
43namespace eve::asset_import {
44namespace {
45using namespace unity_detail;
46
47UnitySourceKind classify(std::string_view path) {
48 const auto dot = path.find_last_of('.');
49 const auto ext = dot == std::string_view::npos ? std::string{} : foldAscii(std::string(path.substr(dot)));
50 if (ext == ".prefab") return UnitySourceKind::Prefab;
51 if (ext == ".unity") return UnitySourceKind::Scene;
52 if (ext == ".fbx" || ext == ".obj" || ext == ".gltf" || ext == ".glb" || ext == ".blend" || ext == ".dae")
54 if (ext == ".mat") return UnitySourceKind::Material;
55 if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".tga" || ext == ".psd" || ext == ".exr" ||
56 ext == ".tif" || ext == ".tiff" || ext == ".bmp")
58 if (ext == ".anim" || ext == ".controller" || ext == ".overridecontroller") return UnitySourceKind::Animation;
59 if (ext == ".wav" || ext == ".ogg" || ext == ".mp3" || ext == ".aiff" || ext == ".flac")
61 if (ext == ".ttf" || ext == ".otf") return UnitySourceKind::Font;
62 if (ext == ".shader" || ext == ".shadergraph" || ext == ".shadersubgraph" || ext == ".compute")
64 if (ext == ".cs" || ext == ".dll" || ext == ".asmdef") return UnitySourceKind::Script;
65 if (ext == ".asset" || ext == ".terrainlayer") return UnitySourceKind::Data;
67}
68
69bool builtin(std::string_view guid) {
70 return guid == "00000000000000000000000000000000" || guid == "0000000000000000e000000000000000" ||
71 guid == "0000000000000000f000000000000000";
72}
73
74Result<std::vector<UnitySourceReference>> references(std::span<const std::uint8_t> bytes, const std::string& path,
75 const AssetImportLimits& limits) {
76 std::vector<UnitySourceReference> out;
77 if (bytes.empty() || std::find(bytes.begin(), bytes.end(), 0) != bytes.end())
78 return Result<std::vector<UnitySourceReference>>::success(std::move(out));
79 const std::string_view text(reinterpret_cast<const char*>(bytes.data()), bytes.size());
80 const std::regex guidField(R"(\bguid:\s*([^,}\s]+))");
81 const std::regex idField(R"(\bfileID:\s*([^,}\s]+))");
82 for (std::size_t at = 0; at < text.size();) {
83 const auto begin = text.find('{', at);
84 if (begin == std::string_view::npos) break;
85 const auto end = text.find_first_of("{}\r\n", begin + 1);
86 if (end == std::string_view::npos) break;
87 at = text[end] == '{' ? end : end + 1;
88 if (text[end] != '}') continue;
90 return Result<std::vector<UnitySourceReference>>::failure(
91 Diagnostic::error(DiagnosticCode::InvalidArgument, "Unity serialized flow record exceeds string budget",
92 path, {}, "asset.import.unity"));
93 const std::string ref(text.substr(begin, end - begin + 1));
94 std::smatch guid, id;
95 if (!std::regex_search(ref, guid, guidField)) continue;
96 if (!validGuid(guid[1].str()) || !std::regex_search(ref, id, idField))
97 return Result<std::vector<UnitySourceReference>>::failure(Diagnostic::error(
98 DiagnosticCode::ParseError, "malformed Unity object reference", path, {}, "asset.import.unity"));
99 const auto idText = id[1].str();
100 std::int64_t fileId = 0;
101 auto parsed = std::from_chars(idText.data(), idText.data() + idText.size(), fileId);
102 if (parsed.ec != std::errc{} || parsed.ptr != idText.data() + idText.size())
103 return Result<std::vector<UnitySourceReference>>::failure(
104 Diagnostic::error(DiagnosticCode::ParseError, "Unity reference fileID exceeds signed 64-bit range",
105 path, {}, "asset.import.unity"));
106 if (out.size() >= limits.maximumAssets)
107 return Result<std::vector<UnitySourceReference>>::failure(
108 Diagnostic::error(DiagnosticCode::InvalidArgument, "Unity reference count exceeds budget", path, {},
109 "asset.import.unity"));
110 out.push_back({foldAscii(guid[1].str()), fileId});
111 }
112 std::sort(out.begin(), out.end(),
113 [](const auto& a, const auto& b) { return std::tie(a.guid, a.fileId) < std::tie(b.guid, b.fileId); });
114 out.erase(std::unique(out.begin(), out.end(),
115 [](const auto& a, const auto& b) { return a.guid == b.guid && a.fileId == b.fileId; }),
116 out.end());
117 return Result<std::vector<UnitySourceReference>>::success(std::move(out));
118}
119} // namespace
120
122 if (files.empty() || files.size() > std::uint64_t(limits.maximumAssets) * 2)
124 "Unity source count exceeds budget or is empty", {},
125 {}, "asset.import.unity"));
126 std::uint64_t total = 0;
127 std::set<std::string> paths;
128 for (const auto& [path, bytes] : files) {
129 if (!validPath(path, limits) || bytes.size() > limits.maximumSourceBytes ||
130 total > limits.maximumSourceBytes - bytes.size())
132 "Unity path or source budget is invalid", path,
133 {}, "asset.import.unity"));
134 total += bytes.size();
135 if (!paths.insert(foldAscii(path)).second)
137 DiagnosticCode::Conflict, "Unity source paths differ only by case", path, {}, "asset.import.unity"));
138 }
140 std::map<std::string, std::string> guidPaths;
141 std::map<std::string, std::size_t> assetPaths;
142 for (const auto& [path, bytes] : files) {
143 if (path.ends_with(".meta")) continue;
144 assetPaths.emplace(path, index.assets.size());
145 index.assets.push_back({path, {}, classify(path), {}, {}});
146 }
147 for (const auto& [path, bytes] : files) {
148 if (!path.ends_with(".meta")) continue;
149 const std::string sourcePath = path.substr(0, path.size() - 5);
150 auto guid = metaScalar(bytes, "guid", path);
151 if (!guid) return Result<UnitySourceIndex>::failure(guid.status());
152 if (!validGuid(guid.value()) || builtin(foldAscii(guid.value())))
154 DiagnosticCode::ParseError, "Unity .meta needs a non-reserved GUID", path, {}, "asset.import.unity"));
155 const auto normalizedGuid = foldAscii(guid.value());
156 if (!guidPaths.emplace(normalizedGuid, sourcePath).second)
158 Diagnostic::error(DiagnosticCode::Conflict, "duplicate Unity GUID", path, {}, "asset.import.unity"));
159 auto folder = metaScalar(bytes, "folderAsset", path);
160 if (!folder) return Result<UnitySourceIndex>::failure(folder.status());
161 const auto found = assetPaths.find(sourcePath);
162 if (folder.value() == "yes") {
163 if (found != assetPaths.end())
165 "Unity folder also contains a file payload",
166 path, {}, "asset.import.unity"));
167 index.assets.push_back({sourcePath, normalizedGuid, UnitySourceKind::Folder, {}, {}});
168 continue;
169 }
170 if (found == assetPaths.end())
172 DiagnosticCode::NotFound, "Unity metadata has no source payload", path, {}, "asset.import.unity"));
173 auto& source = index.assets[found->second];
174 source.guid = normalizedGuid;
175 const std::string_view meta(reinterpret_cast<const char*>(bytes.data()), bytes.size());
176 for (std::size_t at = 0; at < meta.size();) {
177 auto end = meta.find('\n', at);
178 if (end == std::string_view::npos) end = meta.size();
179 auto line = meta.substr(at, end - at);
180 if (line.ends_with('\r')) line.remove_suffix(1);
181 if (line.ends_with("Importer:") && line.find_first_of(" \t") == std::string_view::npos)
182 source.importer = std::string(line.substr(0, line.size() - 1));
183 at = end + 1;
184 }
185 auto refs = references(bytes, path, limits);
186 if (!refs) return Result<UnitySourceIndex>::failure(refs.status());
187 source.references = std::move(refs).takeValue();
188 }
189 if (index.assets.size() > limits.maximumAssets)
191 DiagnosticCode::InvalidArgument, "Unity asset count exceeds budget", {}, {}, "asset.import.unity"));
192 std::map<std::string, UnitySourceKind> sourcePaths;
193 for (const auto& entry : index.assets) {
194 if (!sourcePaths.emplace(foldAscii(entry.path), entry.kind).second)
196 "Unity source identities share a path",
197 entry.path, {}, "asset.import.unity"));
198 }
199 for (const auto& entry : index.assets) {
200 for (auto slash = entry.path.find('/'); slash != std::string::npos; slash = entry.path.find('/', slash + 1)) {
201 const auto parent = sourcePaths.find(foldAscii(entry.path.substr(0, slash)));
202 if (parent != sourcePaths.end() && parent->second != UnitySourceKind::Folder)
204 Diagnostic::error(DiagnosticCode::Conflict, "Unity source file is also used as a directory",
205 entry.path, {}, "asset.import.unity"));
206 }
207 }
208 std::sort(index.assets.begin(), index.assets.end(), [](const auto& a, const auto& b) { return a.path < b.path; });
209 for (auto& entry : index.assets) {
210 if (entry.kind == UnitySourceKind::Folder) continue;
211 if (entry.guid.empty())
212 index.findings.push_back({entry.path, "identity.meta", ImportDisposition::Unsupported,
213 "source has no .meta GUID; stable Unity reimport identity is unavailable"});
214 if (entry.kind == UnitySourceKind::Prefab || entry.kind == UnitySourceKind::Scene ||
215 entry.kind == UnitySourceKind::Material || entry.kind == UnitySourceKind::Data ||
216 entry.kind == UnitySourceKind::Animation) {
217 auto refs = references(files.at(entry.path), entry.path, limits);
218 if (!refs) return Result<UnitySourceIndex>::failure(refs.status());
219 for (auto& ref : refs.value()) entry.references.push_back(std::move(ref));
220 }
221 for (const auto& ref : entry.references)
222 if (ref.fileId != 0 && !builtin(ref.guid) && !guidPaths.contains(ref.guid))
223 index.findings.push_back({entry.path, "dependency:" + ref.guid + ":" + std::to_string(ref.fileId),
224 ImportDisposition::Unsupported, "referenced Unity asset was not supplied"});
225 }
226 return Result<UnitySourceIndex>::success(std::move(index));
227}
228} // namespace eve::asset_import
double value
std::uint32_t key
std::int32_t first
std::string text
std::int32_t parent
std::uint64_t bytes
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
float begin
bool found
std::string sourcePath
std::int64_t fileId
std::map< std::string, std::string > paths
uint32_t index
const UnitySourceAsset & source
const AssetImportLimits & limits
std::size_t at
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.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
std::string foldAscii(std::string value)
Fold ascii.
bool validPath(std::string_view path, const AssetImportLimits &limits)
Valid path.
Result< std::string > metaScalar(std::span< const std::uint8_t > bytes, std::string_view key, std::string_view path)
Read an unindented scalar from .meta; rejects duplicate keys and binary metadata.
bool validGuid(std::string_view value)
Valid guid.
std::map< std::string, std::vector< std::uint8_t > > UnitySourceFiles
Owning project-relative source bytes, including the original .meta files.
Definition UnitySource.h:12
UnitySourceKind
Source category; classification does not imply runtime conversion support.
Definition UnitySource.h:15
Result< UnitySourceIndex > indexUnitySources(const UnitySourceFiles &files, const AssetImportLimits &limits)
Validate project paths and .meta identities and index serialized GUID/fileID references.
bool isValidUtf8(std::string_view text, Utf8NullPolicy nullPolicy) noexcept
Validate shortest-form UTF-8, Unicode scalar range and optional NUL exclusion.
Parser and allocation budgets applied to untrusted importer inputs.
std::vector< UnitySourceReference > references
Definition UnitySource.h:43
Owning deterministic index sorted by path, plus missing-reference diagnostics.
Definition UnitySource.h:47
std::vector< UnitySourceAsset > assets
Definition UnitySource.h:48