1 #include "redump_dat.h"
8 static int redump_path_exists(const char *path) {
10 if (!path || !path[0])
12 f = fopen(path, "rb");
19 static int redump_join_path(
21 const char *child_dir,
28 if (!dir || !dir[0] || !basename || !basename[0] ||
29 !out_path || out_path_size == 0)
32 if (child_dir && child_dir[0])
33 written = snprintf(out_path, out_path_size, "%s/%s/%s",
34 dir, child_dir, basename);
36 written = snprintf(out_path, out_path_size, "%s/%s",
39 if (written < 0 || (size_t)written >= out_path_size) {
47 int redump_resolve_dat_path(
48 const char *explicit_dir,
49 const char *executable_dir,
56 if (!basename || !basename[0] || !out_path || out_path_size == 0)
61 if (explicit_dir && explicit_dir[0]) {
62 if (!redump_join_path(explicit_dir, NULL, basename,
63 out_path, out_path_size))
65 return redump_path_exists(out_path);
68 if (executable_dir && executable_dir[0] &&
69 redump_join_path(executable_dir, "redump_dat", basename,
70 candidate, sizeof(candidate))) {
71 if (redump_path_exists(candidate)) {
72 snprintf(out_path, out_path_size, "%s", candidate);
76 /* Retain the executable-relative path as the preferred diagnostic
77 * candidate if neither automatic location exists. */
78 snprintf(out_path, out_path_size, "%s", candidate);
81 if (redump_join_path(".", "redump_dat", basename,
82 candidate, sizeof(candidate))) {
83 if (redump_path_exists(candidate)) {
84 snprintf(out_path, out_path_size, "%s", candidate);
89 snprintf(out_path, out_path_size, "%s", candidate);
95 static int redump_ascii_equal(const char *a, const char *b) {
100 ca = (unsigned char)tolower((unsigned char)*a++);
101 cb = (unsigned char)tolower((unsigned char)*b++);
105 return *a == '\0' && *b == '\0';
108 static int redump_extract_attr(const char *line, const char *name, char *out, size_t out_size) {
114 if (!line || !name || !out || out_size == 0)
116 snprintf(pattern, sizeof(pattern), "%s=\"", name);
117 start = strstr(line, pattern);
120 start += strlen(pattern);
121 end = strchr(start, '"');
124 len = (size_t)(end - start);
127 memcpy(out, start, len);
132 static void redump_xml_decode(char *text) {
133 struct entity_s { const char *encoded; const char decoded; };
134 static const struct entity_s entities[] = {
135 {"&", '&'}, {""", '"'}, {"'", '\''},
136 {"<", '<'}, {">", '>'}
141 for (i = 0; i < sizeof(entities) / sizeof(entities[0]); i++) {
143 size_t encoded_len = strlen(entities[i].encoded);
144 while ((p = strstr(text, entities[i].encoded)) != NULL) {
145 *p = entities[i].decoded;
146 memmove(p + 1, p + encoded_len, strlen(p + encoded_len) + 1);
151 static void redump_store_candidate(
152 redump_verify_result *result,
153 const char *game_name,
154 const char *rom_name,
155 const char *size_text,
167 result->candidate_available = 1;
168 result->candidate_hash_matches = hash_matches;
169 result->size_status = size_match ? REDUMP_FIELD_PASS : REDUMP_FIELD_FAIL;
170 result->crc32_status = crc32_match ? REDUMP_FIELD_PASS : REDUMP_FIELD_FAIL;
171 result->md5_status = md5_match ? REDUMP_FIELD_PASS : REDUMP_FIELD_FAIL;
172 result->sha1_status = sha1_match ? REDUMP_FIELD_PASS : REDUMP_FIELD_FAIL;
173 snprintf(result->game_name, sizeof(result->game_name), "%s",
174 (game_name && game_name[0]) ? game_name : ((rom_name && rom_name[0]) ? rom_name : ""));
175 snprintf(result->rom_name, sizeof(result->rom_name), "%s", rom_name ? rom_name : "");
176 snprintf(result->expected_size, sizeof(result->expected_size), "%s", size_text ? size_text : "");
177 snprintf(result->expected_crc32, sizeof(result->expected_crc32), "%s", crc32 ? crc32 : "");
178 snprintf(result->expected_md5, sizeof(result->expected_md5), "%s", md5 ? md5 : "");
179 snprintf(result->expected_sha1, sizeof(result->expected_sha1), "%s", sha1 ? sha1 : "");
182 void redump_verify_result_init(redump_verify_result *result) {
185 memset(result, 0, sizeof(*result));
186 result->status = REDUMP_VERIFY_NOT_RUN;
187 result->size_status = REDUMP_FIELD_NOT_AVAILABLE;
188 result->crc32_status = REDUMP_FIELD_NOT_AVAILABLE;
189 result->md5_status = REDUMP_FIELD_NOT_AVAILABLE;
190 result->sha1_status = REDUMP_FIELD_NOT_AVAILABLE;
193 const char *redump_verify_status_string(redump_verify_status status) {
195 case REDUMP_VERIFY_MATCH: return "MATCH";
196 case REDUMP_VERIFY_NO_MATCH: return "NO MATCH";
197 case REDUMP_VERIFY_DAT_NOT_FOUND: return "DAT NOT FOUND";
198 case REDUMP_VERIFY_DAT_READ_ERROR: return "DAT READ ERROR";
199 case REDUMP_VERIFY_INVALID_INPUT: return "INVALID INPUT";
200 default: return "NOT RUN";
204 const char *redump_field_status_string(redump_field_status status) {
206 case REDUMP_FIELD_PASS: return "PASS";
207 case REDUMP_FIELD_FAIL: return "FAIL";
208 default: return "N/A";
212 const char *redump_verify_overall_string(const redump_verify_result *result) {
214 return "NOT VERIFIED";
215 if (result->status == REDUMP_VERIFY_MATCH)
216 return "VERIFIED AGAINST REDUMP";
217 if (result->status == REDUMP_VERIFY_NO_MATCH)
218 return "NO EXACT MATCH";
219 return "NOT VERIFIED";
222 const char *redump_verify_confidence_string(const redump_verify_result *result) {
224 return "Verification unavailable";
225 if (result->status == REDUMP_VERIFY_MATCH)
226 return "Exact archive match";
227 if (result->status == REDUMP_VERIFY_NO_MATCH && result->candidate_available)
228 return "Partial hash correlation; dump not verified";
229 if (result->status == REDUMP_VERIFY_NO_MATCH)
230 return "No archive identity established";
231 if (result->status == REDUMP_VERIFY_INVALID_INPUT)
232 return "Required evidence unavailable";
233 return "Verification unavailable";
236 const char *redump_verify_match_kind_string(const redump_verify_result *result) {
239 if (result->status == REDUMP_VERIFY_MATCH)
241 if (result->candidate_available)
246 int redump_verify_dat_file(
247 const char *dat_path,
252 redump_verify_result *result
256 char current_game[512] = "";
257 int best_hash_matches = 0;
258 int best_size_match = 0;
262 redump_verify_result_init(result);
265 snprintf(result->dat_path, sizeof(result->dat_path), "%s", dat_path);
266 if (!dat_path || !dat_path[0] || image_size == 0 || !crc32 || !md5 || !sha1 ||
267 !crc32[0] || !md5[0] || !sha1[0]) {
268 result->status = REDUMP_VERIFY_INVALID_INPUT;
269 snprintf(result->detail, sizeof(result->detail),
270 "Output size plus complete CRC32, MD5, and SHA-1 values are required.");
274 fp = fopen(dat_path, "rb");
276 result->status = REDUMP_VERIFY_DAT_NOT_FOUND;
277 snprintf(result->detail, sizeof(result->detail), "Could not open DAT file.");
281 while (fgets(line, sizeof(line), fp)) {
282 if (strstr(line, "<game ") != NULL) {
283 if (redump_extract_attr(line, "name", current_game, sizeof(current_game)))
284 redump_xml_decode(current_game);
288 if (strstr(line, "<rom ") != NULL) {
289 char rom_name[512] = "";
290 char size_text[32] = "";
291 char entry_crc[16] = "";
292 char entry_md5[40] = "";
293 char entry_sha1[48] = "";
301 if (!redump_extract_attr(line, "size", size_text, sizeof(size_text)) ||
302 !redump_extract_attr(line, "crc", entry_crc, sizeof(entry_crc)) ||
303 !redump_extract_attr(line, "md5", entry_md5, sizeof(entry_md5)) ||
304 !redump_extract_attr(line, "sha1", entry_sha1, sizeof(entry_sha1)))
307 redump_extract_attr(line, "name", rom_name, sizeof(rom_name));
308 redump_xml_decode(rom_name);
309 entry_size = (uint64_t)strtoull(size_text, NULL, 10);
311 result->entries_scanned++;
312 size_match = (entry_size == image_size);
313 crc32_match = redump_ascii_equal(crc32, entry_crc);
314 md5_match = redump_ascii_equal(md5, entry_md5);
315 sha1_match = redump_ascii_equal(sha1, entry_sha1);
316 hash_matches = crc32_match + md5_match + sha1_match;
318 if (size_match) result->size_matches++;
319 if (crc32_match) result->crc32_matches++;
320 if (md5_match) result->md5_matches++;
321 if (sha1_match) result->sha1_matches++;
323 if (size_match && crc32_match && md5_match && sha1_match) {
324 result->exact_matches++;
325 if (result->status != REDUMP_VERIFY_MATCH) {
326 result->status = REDUMP_VERIFY_MATCH;
327 redump_store_candidate(result, current_game, rom_name, size_text,
328 entry_crc, entry_md5, entry_sha1,
330 snprintf(result->detail, sizeof(result->detail),
331 "Size, CRC32, MD5, and SHA-1 matched.");
336 if (result->status != REDUMP_VERIFY_MATCH && hash_matches > 0 &&
337 (hash_matches > best_hash_matches ||
338 (hash_matches == best_hash_matches && size_match > best_size_match))) {
339 best_hash_matches = hash_matches;
340 best_size_match = size_match;
341 redump_store_candidate(result, current_game, rom_name, size_text,
342 entry_crc, entry_md5, entry_sha1,
343 size_match, crc32_match, md5_match, sha1_match,
351 result->status = REDUMP_VERIFY_DAT_READ_ERROR;
352 snprintf(result->detail, sizeof(result->detail),
353 "An I/O error occurred while reading the DAT file.");
358 if (result->status == REDUMP_VERIFY_MATCH)
361 result->status = REDUMP_VERIFY_NO_MATCH;
362 if (result->candidate_available) {
363 snprintf(result->detail, sizeof(result->detail),
364 "No exact entry matched; the closest hash-correlated candidate is shown.");
366 snprintf(result->detail, sizeof(result->detail),
367 "No entry matched all required fields and no hash-correlated candidate was found.");