61 AddError(&result,
"ORACLE_ROM_NOT_LOADED",
"ROM not loaded",
62 absl::StatusCode::kInvalidArgument);
66 const std::size_t rom_size = rom->
vector().size();
70 AddError(&result,
"ORACLE_WATER_FILL_REGION_MISSING",
71 "WaterFill reserved region not present in this ROM",
72 absl::StatusCode::kFailedPrecondition);
77 AddError(&result,
"ORACLE_COLLISION_WRITE_REGION_MISSING",
78 "Custom collision write support not present in this ROM",
79 absl::StatusCode::kFailedPrecondition);
84 const auto& data = rom->
vector();
85 const uint8_t zone_count =
89 &result,
"ORACLE_WATER_FILL_HEADER_CORRUPT",
90 absl::StrFormat(
"WaterFill table header corrupted (zone_count=%u)",
92 absl::StatusCode::kFailedPrecondition);
97 AddError(&result,
"ORACLE_WATER_FILL_TABLE_INVALID",
98 std::string(zones_or.status().message()),
99 zones_or.status().code());
106 int collision_errors = 0;
112 AddError(&result,
"ORACLE_COLLISION_POINTER_INVALID",
113 absl::StrFormat(
"Room 0x%02X: %s", room_id,
114 std::string(map_or.status().message()).c_str()),
115 map_or.status().code(), room_id);
117 if (collision_errors >= max_errors) {
118 AddError(&result,
"ORACLE_COLLISION_POINTER_INVALID_TRUNCATED",
120 "Collision pointer validation stopped after %d "
121 "errors (increase max_collision_errors to scan more)",
123 absl::StatusCode::kFailedPrecondition);
139 AddError(&result,
"ORACLE_REQUIRED_ROOM_OUT_OF_RANGE",
141 "Required room 0x%02X is out of valid range (0..0x%02X)",
143 absl::StatusCode::kInvalidArgument);
148 AddError(&result,
"ORACLE_REQUIRED_ROOM_MISSING_COLLISION",
150 "Required room 0x%02X: collision pointer invalid: %s",
151 room_id, std::string(map_or.status().message()).c_str()),
152 map_or.status().code(), room_id);
155 if (!map_or->has_data) {
157 &result,
"ORACLE_REQUIRED_ROOM_MISSING_COLLISION",
158 absl::StrFormat(
"Required room 0x%02X has no custom collision data "
159 "(room not authored)",
161 absl::StatusCode::kFailedPrecondition, room_id);
220 std::ifstream file(file_path, std::ios::binary);
221 if (!file.is_open()) {
222 return absl::NotFoundError(
223 absl::StrFormat(
"Cannot open file for hashing: %s", file_path));
229 CC_SHA256_Init(&ctx);
231 std::array<char, 8192> buffer;
232 while (file.read(buffer.data(), buffer.size()) || file.gcount() > 0) {
233 CC_SHA256_Update(&ctx, buffer.data(),
static_cast<CC_LONG
>(file.gcount()));
236 unsigned char digest[CC_SHA256_DIGEST_LENGTH];
237 CC_SHA256_Final(digest, &ctx);
238 return DigestToHex(digest, CC_SHA256_DIGEST_LENGTH);
245 command = absl::StrFormat(
"certutil -hashfile \"%s\" SHA256", file_path);
248 if (std::system(
"command -v sha256sum >/dev/null 2>&1") == 0) {
249 command = absl::StrFormat(
"sha256sum '%s'", file_path);
250 }
else if (std::system(
"command -v shasum >/dev/null 2>&1") == 0) {
251 command = absl::StrFormat(
"shasum -a 256 '%s'", file_path);
253 return absl::UnavailableError(
254 "Neither sha256sum nor shasum found on this system");
259 FILE* pipe = _popen(command.c_str(),
"r");
261 FILE* pipe = popen(command.c_str(),
"r");
264 return absl::InternalError(
"Failed to execute hash command");
267 std::array<char, 128> result_buf;
269 while (fgets(result_buf.data(), result_buf.size(), pipe) !=
nullptr) {
270 output += result_buf.data();
274 int status = _pclose(pipe);
276 int status = pclose(pipe);
279 return absl::InternalError(
280 absl::StrFormat(
"Hash command failed with status %d", status));
283 const std::string hash = ExtractHexDigestLine(output);
285 return absl::InternalError(
"Unexpected hash command output format");
292 const std::string& expected_hash) {
294 if (!actual_hash_or.ok()) {
295 return actual_hash_or.status();
298 const std::string& actual_hash = *actual_hash_or;
299 if (actual_hash != expected_hash) {
300 return absl::DataLossError(
301 absl::StrFormat(
"SHA-256 mismatch for %s: expected %s, got %s",
302 file_path, expected_hash, actual_hash));
305 return absl::OkStatus();