65 AddError(&result,
"ORACLE_ROM_NOT_LOADED",
"ROM not loaded",
66 absl::StatusCode::kInvalidArgument);
71 if (room_id < 0 || room_id > kMaxWaterFillRuntimeRoomId) {
72 AddError(&result,
"ORACLE_REQUIRED_WATER_FILL_ROOM_OUT_OF_RANGE",
74 "Required WaterFill room 0x%02X is out of valid range "
76 room_id, kMaxWaterFillRuntimeRoomId),
77 absl::StatusCode::kInvalidArgument, room_id);
84 const std::size_t rom_size = rom->
vector().size();
88 AddError(&result,
"ORACLE_WATER_FILL_REGION_MISSING",
89 "WaterFill reserved region not present in this ROM",
90 absl::StatusCode::kFailedPrecondition);
95 AddError(&result,
"ORACLE_COLLISION_WRITE_REGION_MISSING",
96 "Custom collision write support not present in this ROM",
97 absl::StatusCode::kFailedPrecondition);
100 std::vector<WaterFillZoneEntry> water_fill_zones;
101 bool water_fill_table_load_attempted =
false;
102 bool water_fill_table_loaded =
false;
103 auto load_water_fill_table = [&]() {
104 if (water_fill_table_load_attempted) {
107 water_fill_table_load_attempted =
true;
109 if (!zones_or.ok()) {
110 AddError(&result,
"ORACLE_WATER_FILL_TABLE_INVALID",
111 std::string(zones_or.status().message()),
112 zones_or.status().code());
115 water_fill_zones = std::move(*zones_or);
116 water_fill_table_loaded =
true;
121 const auto& data = rom->
vector();
122 const uint8_t zone_count =
124 if (zone_count > 8) {
126 &result,
"ORACLE_WATER_FILL_HEADER_CORRUPT",
127 absl::StrFormat(
"WaterFill table header corrupted (zone_count=%u)",
129 absl::StatusCode::kFailedPrecondition);
132 load_water_fill_table();
139 load_water_fill_table();
141 if (!water_fill_table_loaded) {
145 std::any_of(water_fill_zones.begin(), water_fill_zones.end(),
147 return zone.room_id == room_id;
150 AddError(&result,
"ORACLE_REQUIRED_WATER_FILL_ROOM_MISSING",
152 "Required room 0x%02X is missing from the WaterFill table",
154 absl::StatusCode::kFailedPrecondition, room_id);
162 int collision_errors = 0;
168 AddError(&result,
"ORACLE_COLLISION_POINTER_INVALID",
169 absl::StrFormat(
"Room 0x%02X: %s", room_id,
170 std::string(map_or.status().message()).c_str()),
171 map_or.status().code(), room_id);
173 if (collision_errors >= max_errors) {
174 AddError(&result,
"ORACLE_COLLISION_POINTER_INVALID_TRUNCATED",
176 "Collision pointer validation stopped after %d "
177 "errors (increase max_collision_errors to scan more)",
179 absl::StatusCode::kFailedPrecondition);
195 AddError(&result,
"ORACLE_REQUIRED_ROOM_OUT_OF_RANGE",
197 "Required room 0x%02X is out of valid range (0..0x%02X)",
199 absl::StatusCode::kInvalidArgument);
204 AddError(&result,
"ORACLE_REQUIRED_ROOM_MISSING_COLLISION",
206 "Required room 0x%02X: collision pointer invalid: %s",
207 room_id, std::string(map_or.status().message()).c_str()),
208 map_or.status().code(), room_id);
211 if (!map_or->has_data) {
213 &result,
"ORACLE_REQUIRED_ROOM_MISSING_COLLISION",
214 absl::StrFormat(
"Required room 0x%02X has no custom collision data "
215 "(room not authored)",
217 absl::StatusCode::kFailedPrecondition, room_id);
276 std::ifstream file(file_path, std::ios::binary);
277 if (!file.is_open()) {
278 return absl::NotFoundError(
279 absl::StrFormat(
"Cannot open file for hashing: %s", file_path));
285 CC_SHA256_Init(&ctx);
287 std::array<char, 8192> buffer;
288 while (file.read(buffer.data(), buffer.size()) || file.gcount() > 0) {
289 CC_SHA256_Update(&ctx, buffer.data(),
static_cast<CC_LONG
>(file.gcount()));
292 unsigned char digest[CC_SHA256_DIGEST_LENGTH];
293 CC_SHA256_Final(digest, &ctx);
294 return DigestToHex(digest, CC_SHA256_DIGEST_LENGTH);
301 command = absl::StrFormat(
"certutil -hashfile \"%s\" SHA256", file_path);
304 if (std::system(
"command -v sha256sum >/dev/null 2>&1") == 0) {
305 command = absl::StrFormat(
"sha256sum '%s'", file_path);
306 }
else if (std::system(
"command -v shasum >/dev/null 2>&1") == 0) {
307 command = absl::StrFormat(
"shasum -a 256 '%s'", file_path);
309 return absl::UnavailableError(
310 "Neither sha256sum nor shasum found on this system");
315 FILE* pipe = _popen(command.c_str(),
"r");
317 FILE* pipe = popen(command.c_str(),
"r");
320 return absl::InternalError(
"Failed to execute hash command");
323 std::array<char, 128> result_buf;
325 while (fgets(result_buf.data(), result_buf.size(), pipe) !=
nullptr) {
326 output += result_buf.data();
330 int status = _pclose(pipe);
332 int status = pclose(pipe);
335 return absl::InternalError(
336 absl::StrFormat(
"Hash command failed with status %d", status));
339 const std::string hash = ExtractHexDigestLine(output);
341 return absl::InternalError(
"Unexpected hash command output format");
348 const std::string& expected_hash) {
350 if (!actual_hash_or.ok()) {
351 return actual_hash_or.status();
354 const std::string& actual_hash = *actual_hash_or;
355 if (actual_hash != expected_hash) {
356 return absl::DataLossError(
357 absl::StrFormat(
"SHA-256 mismatch for %s: expected %s, got %s",
358 file_path, expected_hash, actual_hash));
361 return absl::OkStatus();