1 #include "redump_dat.h"
8 static int redump_ascii_equal(const char *a, const char *b) {
13 ca = (unsigned char)tolower((unsigned char)*a++);
14 cb = (unsigned char)tolower((unsigned char)*b++);
18 return *a == '\0' && *b == '\0';
21 static int redump_extract_attr(const char *line, const char *name, char *out, size_t out_size) {
27 if (!line || !name || !out || out_size == 0)
29 snprintf(pattern, sizeof(pattern), "%s=\"", name);
30 start = strstr(line, pattern);
33 start += strlen(pattern);
34 end = strchr(start, '"');
37 len = (size_t)(end - start);
40 memcpy(out, start, len);
45 static void redump_xml_decode(char *text) {
46 struct entity_s { const char *encoded; const char decoded; };
47 static const struct entity_s entities[] = {
48 {"&", '&'}, {""", '"'}, {"'", '\''},
49 {"<", '<'}, {">", '>'}
54 for (i = 0; i < sizeof(entities) / sizeof(entities[0]); i++) {
56 size_t encoded_len = strlen(entities[i].encoded);
57 while ((p = strstr(text, entities[i].encoded)) != NULL) {
58 *p = entities[i].decoded;
59 memmove(p + 1, p + encoded_len, strlen(p + encoded_len) + 1);
64 static void redump_store_candidate(
65 redump_verify_result *result,
66 const char *game_name,
68 const char *size_text,
80 result->candidate_available = 1;
81 result->candidate_hash_matches = hash_matches;
82 result->size_status = size_match ? REDUMP_FIELD_PASS : REDUMP_FIELD_FAIL;
83 result->crc32_status = crc32_match ? REDUMP_FIELD_PASS : REDUMP_FIELD_FAIL;
84 result->md5_status = md5_match ? REDUMP_FIELD_PASS : REDUMP_FIELD_FAIL;
85 result->sha1_status = sha1_match ? REDUMP_FIELD_PASS : REDUMP_FIELD_FAIL;
86 snprintf(result->game_name, sizeof(result->game_name), "%s",
87 (game_name && game_name[0]) ? game_name : ((rom_name && rom_name[0]) ? rom_name : ""));
88 snprintf(result->rom_name, sizeof(result->rom_name), "%s", rom_name ? rom_name : "");
89 snprintf(result->expected_size, sizeof(result->expected_size), "%s", size_text ? size_text : "");
90 snprintf(result->expected_crc32, sizeof(result->expected_crc32), "%s", crc32 ? crc32 : "");
91 snprintf(result->expected_md5, sizeof(result->expected_md5), "%s", md5 ? md5 : "");
92 snprintf(result->expected_sha1, sizeof(result->expected_sha1), "%s", sha1 ? sha1 : "");
95 void redump_verify_result_init(redump_verify_result *result) {
98 memset(result, 0, sizeof(*result));
99 result->status = REDUMP_VERIFY_NOT_RUN;
100 result->size_status = REDUMP_FIELD_NOT_AVAILABLE;
101 result->crc32_status = REDUMP_FIELD_NOT_AVAILABLE;
102 result->md5_status = REDUMP_FIELD_NOT_AVAILABLE;
103 result->sha1_status = REDUMP_FIELD_NOT_AVAILABLE;
106 const char *redump_verify_status_string(redump_verify_status status) {
108 case REDUMP_VERIFY_MATCH: return "MATCH";
109 case REDUMP_VERIFY_NO_MATCH: return "NO MATCH";
110 case REDUMP_VERIFY_DAT_NOT_FOUND: return "DAT NOT FOUND";
111 case REDUMP_VERIFY_DAT_READ_ERROR: return "DAT READ ERROR";
112 case REDUMP_VERIFY_INVALID_INPUT: return "INVALID INPUT";
113 default: return "NOT RUN";
117 const char *redump_field_status_string(redump_field_status status) {
119 case REDUMP_FIELD_PASS: return "PASS";
120 case REDUMP_FIELD_FAIL: return "FAIL";
121 default: return "N/A";
125 const char *redump_verify_overall_string(const redump_verify_result *result) {
127 return "NOT VERIFIED";
128 if (result->status == REDUMP_VERIFY_MATCH)
129 return "VERIFIED AGAINST REDUMP";
130 if (result->status == REDUMP_VERIFY_NO_MATCH)
131 return "NO EXACT MATCH";
132 return "NOT VERIFIED";
135 const char *redump_verify_confidence_string(const redump_verify_result *result) {
137 return "Verification unavailable";
138 if (result->status == REDUMP_VERIFY_MATCH)
139 return "Exact archive match";
140 if (result->status == REDUMP_VERIFY_NO_MATCH && result->candidate_available)
141 return "Partial hash correlation; dump not verified";
142 if (result->status == REDUMP_VERIFY_NO_MATCH)
143 return "No archive identity established";
144 if (result->status == REDUMP_VERIFY_INVALID_INPUT)
145 return "Required evidence unavailable";
146 return "Verification unavailable";
149 const char *redump_verify_match_kind_string(const redump_verify_result *result) {
152 if (result->status == REDUMP_VERIFY_MATCH)
154 if (result->candidate_available)
159 int redump_verify_dat_file(
160 const char *dat_path,
165 redump_verify_result *result
169 char current_game[512] = "";
170 int best_hash_matches = 0;
171 int best_size_match = 0;
175 redump_verify_result_init(result);
178 snprintf(result->dat_path, sizeof(result->dat_path), "%s", dat_path);
179 if (!dat_path || !dat_path[0] || image_size == 0 || !crc32 || !md5 || !sha1 ||
180 !crc32[0] || !md5[0] || !sha1[0]) {
181 result->status = REDUMP_VERIFY_INVALID_INPUT;
182 snprintf(result->detail, sizeof(result->detail),
183 "Output size plus complete CRC32, MD5, and SHA-1 values are required.");
187 fp = fopen(dat_path, "rb");
189 result->status = REDUMP_VERIFY_DAT_NOT_FOUND;
190 snprintf(result->detail, sizeof(result->detail), "Could not open DAT file.");
194 while (fgets(line, sizeof(line), fp)) {
195 if (strstr(line, "<game ") != NULL) {
196 if (redump_extract_attr(line, "name", current_game, sizeof(current_game)))
197 redump_xml_decode(current_game);
201 if (strstr(line, "<rom ") != NULL) {
202 char rom_name[512] = "";
203 char size_text[32] = "";
204 char entry_crc[16] = "";
205 char entry_md5[40] = "";
206 char entry_sha1[48] = "";
214 if (!redump_extract_attr(line, "size", size_text, sizeof(size_text)) ||
215 !redump_extract_attr(line, "crc", entry_crc, sizeof(entry_crc)) ||
216 !redump_extract_attr(line, "md5", entry_md5, sizeof(entry_md5)) ||
217 !redump_extract_attr(line, "sha1", entry_sha1, sizeof(entry_sha1)))
220 redump_extract_attr(line, "name", rom_name, sizeof(rom_name));
221 redump_xml_decode(rom_name);
222 entry_size = (uint64_t)strtoull(size_text, NULL, 10);
224 result->entries_scanned++;
225 size_match = (entry_size == image_size);
226 crc32_match = redump_ascii_equal(crc32, entry_crc);
227 md5_match = redump_ascii_equal(md5, entry_md5);
228 sha1_match = redump_ascii_equal(sha1, entry_sha1);
229 hash_matches = crc32_match + md5_match + sha1_match;
231 if (size_match) result->size_matches++;
232 if (crc32_match) result->crc32_matches++;
233 if (md5_match) result->md5_matches++;
234 if (sha1_match) result->sha1_matches++;
236 if (size_match && crc32_match && md5_match && sha1_match) {
237 result->exact_matches++;
238 if (result->status != REDUMP_VERIFY_MATCH) {
239 result->status = REDUMP_VERIFY_MATCH;
240 redump_store_candidate(result, current_game, rom_name, size_text,
241 entry_crc, entry_md5, entry_sha1,
243 snprintf(result->detail, sizeof(result->detail),
244 "Size, CRC32, MD5, and SHA-1 matched.");
249 if (result->status != REDUMP_VERIFY_MATCH && hash_matches > 0 &&
250 (hash_matches > best_hash_matches ||
251 (hash_matches == best_hash_matches && size_match > best_size_match))) {
252 best_hash_matches = hash_matches;
253 best_size_match = size_match;
254 redump_store_candidate(result, current_game, rom_name, size_text,
255 entry_crc, entry_md5, entry_sha1,
256 size_match, crc32_match, md5_match, sha1_match,
264 result->status = REDUMP_VERIFY_DAT_READ_ERROR;
265 snprintf(result->detail, sizeof(result->detail),
266 "An I/O error occurred while reading the DAT file.");
271 if (result->status == REDUMP_VERIFY_MATCH)
274 result->status = REDUMP_VERIFY_NO_MATCH;
275 if (result->candidate_available) {
276 snprintf(result->detail, sizeof(result->detail),
277 "No exact entry matched; the closest hash-correlated candidate is shown.");
279 snprintf(result->detail, sizeof(result->detail),
280 "No entry matched all required fields and no hash-correlated candidate was found.");