16absl::StatusOr<std::filesystem::path>
ResolveBinPath(
const std::string& path) {
17 if (path.empty() || path.find(
'\0') != std::string::npos)
18 return absl::InvalidArgumentError(
"Select a BIN path");
20 const auto absolute = std::filesystem::absolute(path, ec);
22 return absl::InvalidArgumentError(
"Cannot resolve BIN path: " +
24 auto resolved = std::filesystem::weakly_canonical(absolute, ec);
26 return absl::InvalidArgumentError(
"Cannot resolve BIN path: " +
31bool PathsAlias(
const std::filesystem::path& a,
32 const std::filesystem::path& b) {
34 if (std::filesystem::equivalent(a, b, ec) && !ec)
37 const auto first = std::filesystem::weakly_canonical(a, ec);
40 const auto second = std::filesystem::weakly_canonical(b, ec);
41 return !ec && first == second;
45 const std::filesystem::path& path) {
47 const bool exists = std::filesystem::exists(path, ec);
49 return absl::PermissionDeniedError(
"Cannot inspect BIN destination: " +
52 return std::optional<std::string>{};
53 if (!std::filesystem::is_regular_file(path, ec) || ec)
54 return absl::FailedPreconditionError(
55 "BIN destination is not a regular file");
56 std::ifstream input(path, std::ios::binary | std::ios::ate);
58 return absl::PermissionDeniedError(
"Cannot read BIN destination");
59 const auto end = input.tellg();
61 return absl::ResourceExhaustedError(
62 "BIN container exceeds the 64 MiB export limit");
63 std::string bytes(
static_cast<size_t>(end),
'\0');
65 if ((!bytes.empty() && !input.read(bytes.data(), bytes.size())) ||
66 input.peek() != std::char_traits<char>::eof())
67 return absl::DataLossError(
"BIN destination changed while being read");
68 return std::optional<std::string>(std::move(bytes));
71absl::StatusOr<std::string>
ReadLibrary(
const std::string& path) {
73 const auto size = std::filesystem::file_size(path, ec);
75 return absl::InternalError(
"Cannot inspect graphics scratch library: " +
77 if (size > 4 * 1024 * 1024)
78 return absl::ResourceExhaustedError(
79 "Graphics scratch library exceeds 4 MiB");
80 std::ifstream file(path, std::ios::binary);
82 return absl::InternalError(
"Cannot read graphics scratch library");
83 std::string bytes((std::istreambuf_iterator<char>(file)), {});
85 return absl::DataLossError(
86 "Cannot finish reading graphics scratch library");
92absl::StatusOr<LibrarySnapshot>
LoadLibrary(
const std::string& path) {
94 const bool exists = std::filesystem::exists(path, ec);
96 return absl::InternalError(
"Cannot inspect graphics scratch path: " +
102 result.
bytes = std::move(bytes);
109 const std::optional<std::string>& expected_bytes) {
110#ifndef __EMSCRIPTEN__
112 {std::filesystem::path(path)}));
115 const bool exists = std::filesystem::exists(path, ec);
116 if (ec || exists != expected_bytes.has_value())
117 return absl::FailedPreconditionError(
118 "Graphics scratch library changed outside this editor; export pending "
119 "assets before reloading");
122 if (bytes != *expected_bytes)
123 return absl::FailedPreconditionError(
124 "Graphics scratch library changed outside this editor; export "
125 "pending assets before reloading");
128#ifndef __EMSCRIPTEN__
131 return absl::UnimplementedError(
132 "Scratch library persistence requires the desktop editor");
137absl::StatusOr<GraphicsScratchAsset>
ImportBin(
const std::string& path,
int bpp,
139 const std::string& name) {
140 if (bpp < 2 || bpp > 4)
141 return absl::InvalidArgumentError(
"Select 2, 3 or 4 bpp");
142 std::ifstream input(path, std::ios::binary | std::ios::ate);
144 return absl::NotFoundError(
"Cannot open BIN file");
145 const auto size = input.tellg();
146 const size_t count = 64 * 8 * bpp;
147 if (size < 0 || offset >
static_cast<uint64_t
>(size) ||
148 count >
static_cast<uint64_t
>(size) - offset)
149 return absl::OutOfRangeError(
150 "BIN range does not contain 64 complete tiles");
152 std::vector<uint8_t> bytes(count);
153 if (!input.read(
reinterpret_cast<char*
>(bytes.data()), count))
154 return absl::DataLossError(
"Cannot finish reading BIN tiles");
157 return asset.status();
158 asset->source = path +
" @ " + std::to_string(offset);
160 asset->source_path = resolved.string();
161 asset->source_offset = offset;
162 asset->original_chunk = bytes;
163 return std::move(*asset);
168 const std::string& library_path,
169 const std::function<absl::Status(
const std::string&)>& validator) {
172 if (PathsAlias(target, library_path))
173 return absl::FailedPreconditionError(
174 "BIN export cannot replace the scratch library");
177#ifndef __EMSCRIPTEN__
179 {target}, {
"graphics BIN",
"exported BIN"}));
181 const bool same_source =
186 if (target != original_path)
187 return absl::FailedPreconditionError(
188 "BIN destination is a hard-link alias of the source; select the "
189 "original BIN path");
191 std::string after(
reinterpret_cast<const char*
>(encoded.data()),
196 return absl::FailedPreconditionError(
197 "Original BIN source range is missing or truncated; asset retained");
198 const std::string original(
201 if (before->compare(asset.
source_offset, original.size(), original) != 0)
202 return absl::AbortedError(
203 "BIN source chunk changed since import/export; reimport to review "
207 reinterpret_cast<const char*
>(encoded.data()),
209 }
else if (before && before->size() != encoded.size()) {
210 return absl::FailedPreconditionError(
211 "Standalone BIN export cannot replace a differently sized file; choose "
216 *lock, {{target, before, after}},
224 return absl::UnimplementedError(
"BIN export requires the desktop editor");
#define ASSIGN_OR_RETURN(type_variable_name, expression)
absl::Status PublishSourceArtifacts(const SourceArtifactPublicationLock &lock, std::vector< SourceArtifactUpdate > updates, std::string_view expected_primary_sha256, SourceArtifactReadbackValidator readback_validator)
std::string ComputeSourceArtifactSha256(std::string_view content)
absl::StatusOr< std::unique_ptr< SourceArtifactPublicationLock > > AcquireSourceArtifactPublicationLock(const std::vector< fs::path > &targets, const SourceArtifactPublisherLabels &labels, const std::vector< fs::path > &read_only_targets)
absl::StatusOr< std::filesystem::path > ResolveBinPath(const std::string &path)
constexpr uint64_t kMaxBinContainerBytes
absl::StatusOr< std::string > ReadLibrary(const std::string &path)
absl::StatusOr< std::optional< std::string > > ReadBinContainer(const std::filesystem::path &path)
absl::StatusOr< LibrarySnapshot > LoadLibrary(const std::string &path)
absl::StatusOr< BinExportResult > ExportBin(const GraphicsScratchAsset &asset, const std::string &path, const std::string &library_path, const std::function< absl::Status(const std::string &)> &validator)
absl::StatusOr< std::string > SaveLibrary(const std::string &path, const GraphicsScratchLibrary &library, const std::optional< std::string > &expected_bytes)
absl::StatusOr< GraphicsScratchAsset > ImportBin(const std::string &path, int bpp, size_t offset, const std::string &name)
absl::StatusOr< GraphicsScratchAsset > DecodeScratchBin(const std::vector< uint8_t > &bytes, int bpp, std::string name)
absl::StatusOr< std::vector< uint8_t > > EncodeScratchBin(const GraphicsScratchAsset &asset)
absl::Status WriteProjectFileAtomically(absl::string_view target_path, absl::string_view contents, bool replace_existing)
#define RETURN_IF_ERROR(expr)
std::vector< uint8_t > original_chunk
static absl::StatusOr< GraphicsScratchLibrary > Parse(const std::string &text)
std::string Serialize() const
GraphicsScratchLibrary library
std::optional< std::string > bytes