载入中...
搜索中...
未找到
UnityPackage.cpp
浏览该文件的文档.
2
3#include <zlib.h>
4#include <array>
5#include <limits>
6#include <set>
7
8namespace eve::asset_import {
9namespace {
10using namespace unity_detail;
11
12Result<std::vector<std::uint8_t>> inflatePackage(std::span<const std::uint8_t> input, const AssetImportLimits& limits) {
13 if (input.empty() || input.size() > limits.maximumSourceBytes)
14 return Result<std::vector<std::uint8_t>>::failure(Diagnostic::error(
15 DiagnosticCode::InvalidArgument, "Unity package compressed size exceeds budget or is empty", {}, {},
16 "asset.import.unity"));
17 z_stream stream{};
18 if (inflateInit2(&stream, 16 + MAX_WBITS) != Z_OK)
19 return Result<std::vector<std::uint8_t>>::failure(Diagnostic::error(
20 DiagnosticCode::Failed, "cannot initialize Unity gzip decoder", {}, {}, "asset.import.unity"));
21 struct EndInflate {
22 z_stream& stream;
23 ~EndInflate() { inflateEnd(&stream); }
24 } cleanup{stream};
25 std::vector<std::uint8_t> out;
26 std::array<std::uint8_t, 65536> buffer{};
27 std::size_t cursor = 0;
28 for (;;) {
29 if (stream.avail_in == 0 && cursor < input.size()) {
30 const auto count = std::min(input.size() - cursor, std::size_t(std::numeric_limits<uInt>::max()));
31 stream.next_in = const_cast<Bytef*>(input.data() + cursor);
32 stream.avail_in = static_cast<uInt>(count);
33 cursor += count;
34 }
35 stream.next_out = buffer.data();
36 stream.avail_out = static_cast<uInt>(buffer.size());
37 const int code = inflate(&stream, Z_NO_FLUSH);
38 const auto produced = buffer.size() - stream.avail_out;
39 if (produced > limits.maximumDecodedBytes || out.size() > limits.maximumDecodedBytes - produced)
40 return Result<std::vector<std::uint8_t>>::failure(
41 Diagnostic::error(DiagnosticCode::InvalidArgument, "Unity package decompressed size exceeds budget", {},
42 {}, "asset.import.unity"));
43 out.insert(out.end(), buffer.begin(), buffer.begin() + produced);
44 if (code == Z_STREAM_END) {
45 if (stream.avail_in != 0 || cursor != input.size())
46 return Result<std::vector<std::uint8_t>>::failure(Diagnostic::error(
47 DiagnosticCode::ParseError, "Unity package has trailing or concatenated gzip data", {}, {},
48 "asset.import.unity"));
49 return Result<std::vector<std::uint8_t>>::success(std::move(out));
50 }
51 if (code != Z_OK || (produced == 0 && stream.avail_in == 0 && cursor == input.size()))
52 return Result<std::vector<std::uint8_t>>::failure(
53 Diagnostic::error(DiagnosticCode::ParseError, "Unity package gzip is truncated or corrupt", {}, {},
54 "asset.import.unity"));
55 }
56}
57
58std::string tarString(std::span<const std::uint8_t> bytes) {
59 const auto end = std::find(bytes.begin(), bytes.end(), 0);
60 return {bytes.begin(), end};
61}
62
63Result<std::uint64_t> octal(std::span<const std::uint8_t> bytes) {
64 std::uint64_t value = 0;
65 bool ended = false, digit = false;
66 for (auto byte : bytes) {
67 if (byte == 0 || byte == ' ') {
68 if (digit || byte == 0) ended = true;
69 continue;
70 }
71 if (ended || byte < '0' || byte > '7' || value > (std::numeric_limits<std::uint64_t>::max() - (byte - '0')) / 8)
73 DiagnosticCode::ParseError, "invalid Unity tar numeric field", {}, {}, "asset.import.unity"));
74 digit = true;
75 value = value * 8 + (byte - '0');
76 }
78}
79
80struct PackageAsset {
81 std::map<std::string, std::span<const std::uint8_t>> members;
82};
83
84Result<UnitySourceFiles> unpack(std::span<const std::uint8_t> tar, const AssetImportLimits& limits) {
85 if (tar.size() < 1024 || tar.size() % 512 != 0)
87 DiagnosticCode::ParseError, "Unity tar block size is invalid", {}, {}, "asset.import.unity"));
88 std::map<std::string, PackageAsset> assets;
89 std::set<std::string> memberNames;
90 bool ended = false;
91 for (std::size_t cursor = 0; cursor < tar.size();) {
92 const auto header = tar.subspan(cursor, 512);
93 if (std::all_of(header.begin(), header.end(), [](auto b) { return b == 0; })) {
94 if (tar.size() - cursor < 1024 ||
95 !std::all_of(tar.begin() + cursor, tar.end(), [](auto b) { return b == 0; }))
97 DiagnosticCode::ParseError, "Unity tar termination is invalid", {}, {}, "asset.import.unity"));
98 ended = true;
99 break;
100 }
101 auto storedSum = octal(header.subspan(148, 8));
102 if (!storedSum) return Result<UnitySourceFiles>::failure(storedSum.status());
103 std::uint64_t sum = 0;
104 for (std::size_t i = 0; i < 512; ++i) sum += i >= 148 && i < 156 ? ' ' : header[i];
105 if (sum != storedSum.value())
107 DiagnosticCode::ParseError, "Unity tar checksum mismatch", {}, {}, "asset.import.unity"));
108 auto sizeResult = octal(header.subspan(124, 12));
109 if (!sizeResult) return Result<UnitySourceFiles>::failure(sizeResult.status());
110 const auto size = sizeResult.value();
111 cursor += 512;
112 if (size > limits.maximumSourceBytes || size > tar.size() - cursor)
114 DiagnosticCode::InvalidArgument, "Unity tar entry size exceeds bounds", {}, {}, "asset.import.unity"));
115 const auto payload = tar.subspan(cursor, static_cast<std::size_t>(size));
116 const auto padding = (512 - size % 512) % 512;
117 if (size + padding > tar.size() - cursor)
119 DiagnosticCode::ParseError, "Unity tar padding is truncated", {}, {}, "asset.import.unity"));
120 cursor += static_cast<std::size_t>(size + padding);
121 auto name = tarString(header.first(100));
122 if (tarString(header.subspan(257, 6)) == "ustar") {
123 const auto prefix = tarString(header.subspan(345, 155));
124 if (!prefix.empty()) name = prefix + "/" + name;
125 }
126 if (name.starts_with("./")) name.erase(0, 2);
127 const auto type = header[156];
128 if (type != 0 && type != '0' && type != '5')
130 DiagnosticCode::Unsupported, "Unity tar links and extended entry types are not accepted", name, {},
131 "asset.import.unity"));
132 if (type == '5' && name.ends_with('/')) name.pop_back();
133 if (type == '5' && (name.empty() || name == ".")) continue;
134 if (!validPath(name, limits) || !memberNames.insert(name).second)
136 DiagnosticCode::Conflict, "unsafe or duplicate Unity tar path", name, {}, "asset.import.unity"));
137 if (memberNames.size() > std::uint64_t(limits.maximumAssets) * 6)
139 DiagnosticCode::InvalidArgument, "Unity tar entry count exceeds budget", {}, {}, "asset.import.unity"));
140 // Package thumbnail, like per-asset preview.png, is not an import source.
141 if (name == ".icon.png" && type != '5') continue;
142 const auto slash = name.find('/');
143 const auto guid = foldAscii(name.substr(0, slash));
144 if (!validGuid(guid) && guid != "packagemanagermanifest")
146 DiagnosticCode::ParseError, "Unity tar entry needs a GUID directory", name, {}, "asset.import.unity"));
147 if (type == '5') {
148 if (slash != std::string::npos || size != 0)
150 DiagnosticCode::ParseError, "invalid Unity tar directory", name, {}, "asset.import.unity"));
151 continue;
152 }
153 if (slash == std::string::npos)
155 DiagnosticCode::ParseError, "Unity tar entry has no member name", name, {}, "asset.import.unity"));
156 const auto member = name.substr(slash + 1);
157 if (member != "asset" && member != "asset.meta" && member != "pathname" && member != "preview.png")
159 DiagnosticCode::Unsupported, "unknown Unity package member", name, {}, "asset.import.unity"));
160 if (!assets[guid].members.emplace(member, payload).second)
162 DiagnosticCode::Conflict, "duplicate Unity package member", name, {}, "asset.import.unity"));
163 if (assets.size() > limits.maximumAssets)
165 "Unity package asset count exceeds budget", {},
166 {}, "asset.import.unity"));
167 }
168 if (!ended || assets.empty())
170 DiagnosticCode::ParseError, "Unity tar is empty or unterminated", {}, {}, "asset.import.unity"));
171 UnitySourceFiles files;
172 std::set<std::string> paths;
173 for (const auto& [guid, entry] : assets) {
174 const auto pathname = entry.members.find("pathname"), meta = entry.members.find("asset.meta");
175 const bool dependencyManifest = guid == "packagemanagermanifest";
176 if (pathname == entry.members.end() || (!dependencyManifest && meta == entry.members.end()))
178 "Unity package needs pathname and asset.meta",
179 guid, {}, "asset.import.unity"));
180 if (pathname->second.size() > limits.maximumStringBytes + std::uint64_t(4))
182 DiagnosticCode::InvalidArgument, "Unity pathname exceeds budget", guid, {}, "asset.import.unity"));
183 std::string path(pathname->second.begin(), pathname->second.end());
184 // Legacy Asset Store exports append this exact trailer to the pathname record.
185 if (path.ends_with("\n00"))
186 path.resize(path.size() - 3);
187 else if (path.ends_with('\n'))
188 path.pop_back();
189 if (path.ends_with('\r')) path.pop_back();
190 if (dependencyManifest) {
191 const auto asset = entry.members.find("asset");
192 if (!validPath(path, limits) || path != "Packages/manifest.json" || asset == entry.members.end() ||
193 entry.members.size() != 2)
195 "invalid Unity package dependency manifest",
196 path, {}, "asset.import.unity"));
197 // Preserve as source data; it never authorizes package installation.
198 files.emplace(path, std::vector<std::uint8_t>(asset->second.begin(), asset->second.end()));
199 continue;
200 }
201 if (!validPath(path, limits) || !(path == "Assets" || path.starts_with("Assets/")))
203 "Unity pathname must be inside Assets", path, {},
204 "asset.import.unity"));
205 for (const auto& candidate : {path, path + ".meta"})
206 if (!paths.insert(foldAscii(candidate)).second)
208 DiagnosticCode::Conflict, "duplicate Unity destination path", path, {}, "asset.import.unity"));
209 auto metaGuid = metaScalar(meta->second, "guid", path);
210 if (!metaGuid) return Result<UnitySourceFiles>::failure(metaGuid.status());
211 if (foldAscii(metaGuid.value()) != guid)
213 DiagnosticCode::Conflict, "Unity directory GUID disagrees with .meta", path, {}, "asset.import.unity"));
214 files.emplace(path + ".meta", std::vector<std::uint8_t>(meta->second.begin(), meta->second.end()));
215 const auto asset = entry.members.find("asset");
216 if (asset != entry.members.end())
217 files.emplace(path, std::vector<std::uint8_t>(asset->second.begin(), asset->second.end()));
218 }
219 auto index = indexUnitySources(files, limits);
220 if (!index) return Result<UnitySourceFiles>::failure(index.status());
221 return Result<UnitySourceFiles>::success(std::move(files));
222}
223} // namespace
224
226 auto decoded = inflatePackage(bytes, limits);
227 if (!decoded) return Result<UnitySourceFiles>::failure(decoded.status());
228 return unpack(decoded.value(), limits);
229}
230} // namespace eve::asset_import
double value
Value::Object payload
EvpackChunkInput input
Definition Evpack.cpp:170
DiagnosticCode code
std::uint64_t bytes
bool ended
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
std::unique_ptr< gpgpu::GpuBuffer > buffer
Definition OnnxGpgpu.cpp:26
std::string path
Definition PlayHost.cpp:110
std::uint32_t count
std::size_t cursor
float size
Definition TreeMesh.cpp:156
float(ui::Theme::* member)[4]
std::map< std::string, std::span< const std::uint8_t > > members
std::map< std::string, std::string > paths
uint32_t index
const AssetImportLimits & limits
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.
Result< UnitySourceFiles > readUnityPackage(std::span< const std::uint8_t > bytes, const AssetImportLimits &limits)
Decode a gzip/tar .unitypackage into project-relative source bytes without extraction.
std::map< std::string, std::vector< std::uint8_t > > UnitySourceFiles
Owning project-relative source bytes, including the original .meta files.
Definition UnitySource.h:12
Result< UnitySourceIndex > indexUnitySources(const UnitySourceFiles &files, const AssetImportLimits &limits)
Validate project paths and .meta identities and index serialized GUID/fileID references.
std::string header(int localX, int localY)
Header.
Definition KernelGen.cpp:13
Parser and allocation budgets applied to untrusted importer inputs.