]> FriiDump Source - friidump.git/blob - src/redump_dat.c
FriiDump 0.5.3.10: add Redump evidence and optimize Xbox flow
[friidump.git] / src / redump_dat.c
1 #include "redump_dat.h"
2
3 #include <ctype.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7
8 static int redump_ascii_equal(const char *a, const char *b) {
9     unsigned char ca, cb;
10     if (!a || !b)
11         return 0;
12     while (*a && *b) {
13         ca = (unsigned char)tolower((unsigned char)*a++);
14         cb = (unsigned char)tolower((unsigned char)*b++);
15         if (ca != cb)
16             return 0;
17     }
18     return *a == '\0' && *b == '\0';
19 }
20
21 static int redump_extract_attr(const char *line, const char *name, char *out, size_t out_size) {
22     char pattern[64];
23     const char *start;
24     const char *end;
25     size_t len;
26
27     if (!line || !name || !out || out_size == 0)
28         return 0;
29     snprintf(pattern, sizeof(pattern), "%s=\"", name);
30     start = strstr(line, pattern);
31     if (!start)
32         return 0;
33     start += strlen(pattern);
34     end = strchr(start, '"');
35     if (!end)
36         return 0;
37     len = (size_t)(end - start);
38     if (len >= out_size)
39         len = out_size - 1;
40     memcpy(out, start, len);
41     out[len] = '\0';
42     return 1;
43 }
44
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         {"&amp;", '&'}, {"&quot;", '"'}, {"&apos;", '\''},
49         {"&lt;", '<'}, {"&gt;", '>'}
50     };
51     size_t i;
52     if (!text)
53         return;
54     for (i = 0; i < sizeof(entities) / sizeof(entities[0]); i++) {
55         char *p;
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);
60         }
61     }
62 }
63
64 static void redump_store_candidate(
65     redump_verify_result *result,
66     const char *game_name,
67     const char *rom_name,
68     const char *size_text,
69     const char *crc32,
70     const char *md5,
71     const char *sha1,
72     int size_match,
73     int crc32_match,
74     int md5_match,
75     int sha1_match,
76     int hash_matches
77 ) {
78     if (!result)
79         return;
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 : "");
93 }
94
95 void redump_verify_result_init(redump_verify_result *result) {
96     if (!result)
97         return;
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;
104 }
105
106 const char *redump_verify_status_string(redump_verify_status status) {
107     switch (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";
114     }
115 }
116
117 const char *redump_field_status_string(redump_field_status status) {
118     switch (status) {
119         case REDUMP_FIELD_PASS: return "PASS";
120         case REDUMP_FIELD_FAIL: return "FAIL";
121         default: return "N/A";
122     }
123 }
124
125 const char *redump_verify_overall_string(const redump_verify_result *result) {
126     if (!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";
133 }
134
135 const char *redump_verify_confidence_string(const redump_verify_result *result) {
136     if (!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";
147 }
148
149 const char *redump_verify_match_kind_string(const redump_verify_result *result) {
150     if (!result)
151         return "none";
152     if (result->status == REDUMP_VERIFY_MATCH)
153         return "exact";
154     if (result->candidate_available)
155         return "closest";
156     return "none";
157 }
158
159 int redump_verify_dat_file(
160     const char *dat_path,
161     uint64_t image_size,
162     const char *crc32,
163     const char *md5,
164     const char *sha1,
165     redump_verify_result *result
166 ) {
167     FILE *fp;
168     char line[8192];
169     char current_game[512] = "";
170     int best_hash_matches = 0;
171     int best_size_match = 0;
172
173     if (!result)
174         return 0;
175     redump_verify_result_init(result);
176
177     if (dat_path)
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.");
184         return 0;
185     }
186
187     fp = fopen(dat_path, "rb");
188     if (!fp) {
189         result->status = REDUMP_VERIFY_DAT_NOT_FOUND;
190         snprintf(result->detail, sizeof(result->detail), "Could not open DAT file.");
191         return 0;
192     }
193
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);
198             continue;
199         }
200
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] = "";
207             uint64_t entry_size;
208             int size_match;
209             int crc32_match;
210             int md5_match;
211             int sha1_match;
212             int hash_matches;
213
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)))
218                 continue;
219
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);
223
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;
230
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++;
235
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,
242                                            1, 1, 1, 1, 3);
243                     snprintf(result->detail, sizeof(result->detail),
244                              "Size, CRC32, MD5, and SHA-1 matched.");
245                 }
246                 continue;
247             }
248
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,
257                                        hash_matches);
258             }
259         }
260     }
261
262     if (ferror(fp)) {
263         fclose(fp);
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.");
267         return 0;
268     }
269     fclose(fp);
270
271     if (result->status == REDUMP_VERIFY_MATCH)
272         return 1;
273
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.");
278     } else {
279         snprintf(result->detail, sizeof(result->detail),
280                  "No entry matched all required fields and no hash-correlated candidate was found.");
281     }
282     return 0;
283 }