1 // license:BSD-3-Clause
2 // copyright-holders:Aaron Giles,Nicola Salmoria
3 /***************************************************************************
4
5 romcmp.c
6
7 ROM comparison utility program.
8
9 ***************************************************************************/
10
11 #include "unzip.h"
12 #include "osdcore.h"
13 #include "osdcomm.h"
14 #include "hash.h"
15
16 #include <cstdarg>
17 #include <cstdlib>
18
19
20 #define MAX_FILES 1000
21
22 #ifndef MAX_FILENAME_LEN
23 #define MAX_FILENAME_LEN 255
24 #endif
25
26 #ifndef PATH_DELIM
27 #define PATH_DELIM '/'
28 #endif
29
30
31
32 /* compare modes when one file is twice as long as the other */
33 /* A = All file */
34 /* 12 = 1st half */
35 /* 22 = 2nd half */
36 /* E = Even bytes */
37 /* O = Odd bytes */
38 /* E1 = Even bytes 1st half */
39 /* O1 = Odd bytes 1st half */
40 /* E2 = Even bytes 2nd half */
41 /* O2 = Odd bytes 2nd half */
42 enum
43 {
44 MODE_A,
45 MODE_NIB1,MODE_NIB2,
46 MODE_12, MODE_22,
47 MODE_14, MODE_24, MODE_34, MODE_44,
48 MODE_E, MODE_O,
49 MODE_E12, MODE_O12, MODE_E22, MODE_O22,
50 TOTAL_MODES
51 };
52
53 static const char *const modenames[] =
54 {
55 " ",
56 "[bits 0-3]",
57 "[bits 4-7]",
58 "[1/2] ",
59 "[2/2] ",
60 "[1/4] ",
61 "[2/4] ",
62 "[3/4] ",
63 "[4/4] ",
64 "[even] ",
65 "[odd] ",
66 "[even 1/2]",
67 "[odd 1/2] ",
68 "[even 2/2]",
69 "[odd 2/2] ",
70 };
71
compatiblemodes(int mode,int * start,int * end)72 static void compatiblemodes(int mode,int *start,int *end)
73 {
74 if (mode == MODE_A)
75 {
76 *start = MODE_A;
77 *end = MODE_A;
78 }
79 if (mode >= MODE_NIB1 && mode <= MODE_NIB2)
80 {
81 *start = MODE_NIB1;
82 *end = MODE_NIB2;
83 }
84 if (mode >= MODE_12 && mode <= MODE_22)
85 {
86 *start = MODE_12;
87 *end = MODE_22;
88 }
89 if (mode >= MODE_14 && mode <= MODE_44)
90 {
91 *start = MODE_14;
92 *end = MODE_44;
93 }
94 if (mode >= MODE_E && mode <= MODE_O)
95 {
96 *start = MODE_E;
97 *end = MODE_O;
98 }
99 if (mode >= MODE_E12 && mode <= MODE_O22)
100 {
101 *start = MODE_E12;
102 *end = MODE_O22;
103 }
104 }
105
106 struct fileinfo
107 {
108 char name[MAX_FILENAME_LEN+1];
109 int size;
110 unsigned char *buf; /* file is read in here */
111 int listed;
112 };
113
114 static fileinfo files[2][MAX_FILES];
115 static float matchscore[MAX_FILES][MAX_FILES][TOTAL_MODES][TOTAL_MODES];
116
117
is_ascii_char(int ch)118 static bool is_ascii_char(int ch)
119 {
120 return (ch >= 0x20 && ch < 0x7f) || (ch == '\n') || (ch == '\r') || (ch == '\t');
121 }
122
checkintegrity(const fileinfo * file,int side)123 static void checkintegrity(const fileinfo *file, int side)
124 {
125 if (file->buf == nullptr) return;
126
127 /* check for bad data lines */
128 unsigned mask0 = 0x0000;
129 unsigned mask1 = 0xffff;
130
131 bool is_ascii = true;
132 for (unsigned i = 0; i < file->size; i += 2)
133 {
134 is_ascii = is_ascii && is_ascii_char(file->buf[i]);
135 mask0 |= file->buf[i] << 8;
136 mask1 &= (file->buf[i] << 8) | 0x00ff;
137 if (i < file->size - 1)
138 {
139 is_ascii = is_ascii && is_ascii_char(file->buf[i+1]);
140 mask0 |= file->buf[i+1];
141 mask1 &= file->buf[i+1] | 0xff00;
142 }
143 if (mask0 == 0xffff && mask1 == 0x0000) break;
144 }
145
146 if (is_ascii && mask0 == 0x7f7f && mask1 == 0)
147 {
148 printf("%-23s %-23s ASCII TEXT FILE\n", side ? "" : file->name, side ? file->name : "");
149 return;
150 }
151
152 if (mask0 != 0xffff || mask1 != 0x0000)
153 {
154 int fixedmask;
155 int bits;
156
157 fixedmask = (~mask0 | mask1) & 0xffff;
158
159 if (((mask0 >> 8) & 0xff) == (mask0 & 0xff) && ((mask1 >> 8) & 0xff) == (mask1 & 0xff))
160 bits = 8;
161 else bits = 16;
162
163 printf("%-23s %-23s FIXED BITS (",side ? "" : file->name,side ? file->name : "");
164 for (int i = 0; i < bits; i++)
165 {
166 if (~mask0 & 0x8000) printf("0");
167 else if (mask1 & 0x8000) printf("1");
168 else printf("x");
169
170 mask0 <<= 1;
171 mask1 <<= 1;
172 }
173 printf(")\n");
174
175 /* if the file contains a fixed value, we don't need to do the other */
176 /* validity checks */
177 if (fixedmask == 0xffff || fixedmask == 0x00ff || fixedmask == 0xff00)
178 return;
179 }
180
181 unsigned addrbit = 1;
182 unsigned addrmirror = 0;
183 while (addrbit <= file->size/2)
184 {
185 unsigned i = 0;
186 for (i = 0; i < file->size; i++)
187 {
188 if ((i ^ addrbit) < file->size && file->buf[i] != file->buf[i ^ addrbit]) break;
189 }
190
191 if (i == file->size)
192 addrmirror |= addrbit;
193
194 addrbit <<= 1;
195 }
196
197 if (addrmirror != 0)
198 {
199 if (addrmirror == file->size/2)
200 {
201 printf("%-23s %-23s 1ST AND 2ND HALF IDENTICAL\n", side ? "" : file->name, side ? file->name : "");
202 util::hash_collection hash;
203 hash.begin();
204 hash.buffer(file->buf, file->size / 2);
205 hash.end();
206 printf("%-23s %-23s %s\n", side ? "" : file->name, side ? file->name : "", hash.attribute_string().c_str());
207 }
208 else
209 {
210 printf("%-23s %-23s BADADDR",side ? "" : file->name,side ? file->name : "");
211 for (int i = 0; i < 24; i++)
212 {
213 if (file->size <= (1<<(23-i))) printf(" ");
214 else if (addrmirror & 0x800000) printf("-");
215 else printf("x");
216 addrmirror <<= 1;
217 }
218 printf("\n");
219 }
220 return;
221 }
222
223 unsigned sizemask = 1;
224 while (sizemask < file->size - 1)
225 sizemask = (sizemask << 1) | 1;
226
227 mask0 = 0x000000;
228 mask1 = sizemask;
229 for (unsigned i = 0; i < file->size; i++)
230 {
231 if (file->buf[i] != 0xff)
232 {
233 mask0 |= i;
234 mask1 &= i;
235 if (mask0 == sizemask && mask1 == 0x00) break;
236 }
237 }
238
239 if (mask0 != sizemask || mask1 != 0x00)
240 {
241 printf("%-23s %-23s ",side ? "" : file->name,side ? file->name : "");
242 for (int i = 0; i < 24; i++)
243 {
244 if (file->size <= (1<<(23-i))) printf(" ");
245 else if (~mask0 & 0x800000) printf("1");
246 else if (mask1 & 0x800000) printf("0");
247 else printf("x");
248 mask0 <<= 1;
249 mask1 <<= 1;
250 }
251 printf(" = 0xFF\n");
252
253 return;
254 }
255
256
257 mask0 = 0x000000;
258 mask1 = sizemask;
259 for (unsigned i = 0; i < file->size; i++)
260 {
261 if (file->buf[i] != 0x00)
262 {
263 mask0 |= i;
264 mask1 &= i;
265 if (mask0 == sizemask && mask1 == 0x00) break;
266 }
267 }
268
269 if (mask0 != sizemask || mask1 != 0x00)
270 {
271 printf("%-23s %-23s ",side ? "" : file->name,side ? file->name : "");
272 for (int i = 0; i < 24; i++)
273 {
274 if (file->size <= (1<<(23-i))) printf(" ");
275 else if ((mask0 & 0x800000) == 0) printf("1");
276 else if (mask1 & 0x800000) printf("0");
277 else printf("x");
278 mask0 <<= 1;
279 mask1 <<= 1;
280 }
281 printf(" = 0x00\n");
282
283 return;
284 }
285
286 mask0 = 0xff;
287 for (unsigned i = 0; i < file->size/4 && mask0 != 0x00; i++)
288 {
289 if (file->buf[ 2*i ] != 0x00) mask0 &= ~0x01;
290 if (file->buf[ 2*i ] != 0xff) mask0 &= ~0x02;
291 if (file->buf[ 2*i+1] != 0x00) mask0 &= ~0x04;
292 if (file->buf[ 2*i+1] != 0xff) mask0 &= ~0x08;
293 if (file->buf[file->size/2 + 2*i ] != 0x00) mask0 &= ~0x10;
294 if (file->buf[file->size/2 + 2*i ] != 0xff) mask0 &= ~0x20;
295 if (file->buf[file->size/2 + 2*i+1] != 0x00) mask0 &= ~0x40;
296 if (file->buf[file->size/2 + 2*i+1] != 0xff) mask0 &= ~0x80;
297 }
298
299 if (mask0 & 0x01) printf("%-23s %-23s 1ST HALF = 00xx\n",side ? "" : file->name,side ? file->name : "");
300 if (mask0 & 0x02) printf("%-23s %-23s 1ST HALF = FFxx\n",side ? "" : file->name,side ? file->name : "");
301 if (mask0 & 0x04) printf("%-23s %-23s 1ST HALF = xx00\n",side ? "" : file->name,side ? file->name : "");
302 if (mask0 & 0x08) printf("%-23s %-23s 1ST HALF = xxFF\n",side ? "" : file->name,side ? file->name : "");
303 if (mask0 & 0x10) printf("%-23s %-23s 2ND HALF = 00xx\n",side ? "" : file->name,side ? file->name : "");
304 if (mask0 & 0x20) printf("%-23s %-23s 2ND HALF = FFxx\n",side ? "" : file->name,side ? file->name : "");
305 if (mask0 & 0x40) printf("%-23s %-23s 2ND HALF = xx00\n",side ? "" : file->name,side ? file->name : "");
306 if (mask0 & 0x80) printf("%-23s %-23s 2ND HALF = xxFF\n",side ? "" : file->name,side ? file->name : "");
307 }
308
309
usedbytes(const fileinfo * file,int mode)310 static int usedbytes(const fileinfo *file,int mode)
311 {
312 switch (mode)
313 {
314 case MODE_A:
315 case MODE_NIB1:
316 case MODE_NIB2:
317 return file->size;
318 case MODE_12:
319 case MODE_22:
320 case MODE_E:
321 case MODE_O:
322 return file->size / 2;
323 case MODE_14:
324 case MODE_24:
325 case MODE_34:
326 case MODE_44:
327 case MODE_E12:
328 case MODE_O12:
329 case MODE_E22:
330 case MODE_O22:
331 return file->size / 4;
332 default:
333 return 0;
334 }
335 }
336
basemultmask(const fileinfo * file,int mode,int * base,int * mult,int * mask)337 static void basemultmask(const fileinfo *file,int mode,int *base,int *mult,int *mask)
338 {
339 *mult = 1;
340 if (mode >= MODE_E) *mult = 2;
341
342 switch (mode)
343 {
344 case MODE_A:
345 case MODE_12:
346 case MODE_14:
347 case MODE_E:
348 case MODE_E12:
349 *base = 0; *mask = 0xff; break;
350 case MODE_NIB1:
351 *base = 0; *mask = 0x0f; break;
352 case MODE_NIB2:
353 *base = 0; *mask = 0xf0; break;
354 case MODE_O:
355 case MODE_O12:
356 *base = 1; *mask = 0xff; break;
357 case MODE_22:
358 case MODE_E22:
359 *base = file->size / 2; *mask = 0xff; break;
360 case MODE_O22:
361 *base = 1 + file->size / 2; *mask = 0xff; break;
362 case MODE_24:
363 *base = file->size / 4; *mask = 0xff; break;
364 case MODE_34:
365 *base = 2*file->size / 4; *mask = 0xff; break;
366 case MODE_44:
367 *base = 3*file->size / 4; *mask = 0xff; break;
368 }
369 }
370
filecompare(const fileinfo * file1,const fileinfo * file2,int mode1,int mode2)371 static float filecompare(const fileinfo *file1,const fileinfo *file2,int mode1,int mode2)
372 {
373 int i;
374 int match = 0;
375 int size1,size2;
376 int base1=0,base2=0,mult1=0,mult2=0,mask1=0,mask2=0;
377
378
379 if (file1->buf == nullptr || file2->buf == nullptr) return 0.0;
380
381 size1 = usedbytes(file1,mode1);
382 size2 = usedbytes(file2,mode2);
383
384 if (size1 != size2) return 0.0;
385
386 basemultmask(file1,mode1,&base1,&mult1,&mask1);
387 basemultmask(file2,mode2,&base2,&mult2,&mask2);
388
389 if (mask1 == mask2)
390 {
391 if (mask1 == 0xff)
392 {
393 /* normal compare */
394 for (i = 0;i < size1;i++)
395 if (file1->buf[base1 + mult1 * i] == file2->buf[base2 + mult2 * i]) match++;
396 }
397 else
398 {
399 /* nibble compare, abort if other half is not empty */
400 for (i = 0;i < size1;i++)
401 {
402 if (((file1->buf[base1 + mult1 * i] & ~mask1) != (0x00 & ~mask1) &&
403 (file1->buf[base1 + mult1 * i] & ~mask1) != (0xff & ~mask1)) ||
404 ((file2->buf[base1 + mult1 * i] & ~mask2) != (0x00 & ~mask2) &&
405 (file2->buf[base1 + mult1 * i] & ~mask2) != (0xff & ~mask2)))
406 {
407 match = 0;
408 break;
409 }
410 if ((file1->buf[base1 + mult1 * i] & mask1) == (file2->buf[base2 + mult2 * i] & mask2)) match++;
411 }
412 }
413 }
414
415 return (float)match / size1;
416 }
417
418
readfile(const char * path,fileinfo * file)419 static void readfile(const char *path,fileinfo *file)
420 {
421 osd_file::error filerr;
422 uint64_t filesize;
423 uint32_t actual;
424 char fullname[256];
425 osd_file::ptr f;
426
427 if (path)
428 {
429 char delim[2] = { PATH_DELIM, '\0' };
430 strcpy(fullname,path);
431 strcat(fullname,delim);
432 }
433 else fullname[0] = 0;
434 strcat(fullname,file->name);
435
436 if ((file->buf = (unsigned char *)malloc(file->size)) == nullptr)
437 {
438 printf("%s: out of memory!\n",file->name);
439 return;
440 }
441
442 filerr = osd_file::open(fullname, OPEN_FLAG_READ, f, filesize);
443 if (filerr != osd_file::error::NONE)
444 {
445 printf("%s: error %d\n", fullname, int(filerr));
446 return;
447 }
448
449 filerr = f->read(file->buf, 0, file->size, actual);
450 if (filerr != osd_file::error::NONE)
451 {
452 printf("%s: error %d\n", fullname, int(filerr));
453 return;
454 }
455 }
456
457
freefile(fileinfo * file)458 static void freefile(fileinfo *file)
459 {
460 free(file->buf);
461 file->buf = nullptr;
462 }
463
464
printname(const fileinfo * file1,const fileinfo * file2,float score,int mode1,int mode2)465 static void printname(const fileinfo *file1,const fileinfo *file2,float score,int mode1,int mode2)
466 {
467 printf("%-12s %s %-12s %s ",file1 ? file1->name : "",modenames[mode1],file2 ? file2->name : "",modenames[mode2]);
468 if (score == 0.0f) printf("NO MATCH\n");
469 else if (score == 1.0f) printf("IDENTICAL\n");
470 else printf("%3.6f%%\n",(double) (score*100));
471 }
472
473
load_files(int i,int * found,const char * path)474 static int load_files(int i, int *found, const char *path)
475 {
476 /* attempt to open as a directory first */
477 auto dir = osd::directory::open(path);
478 if (dir)
479 {
480 const osd::directory::entry *d;
481
482 /* load all files in directory */
483 while ((d = dir->read()) != nullptr)
484 {
485 const char *d_name = d->name;
486 char buf[255+1];
487
488 sprintf(buf, "%s%c%s", path, PATH_DELIM, d_name);
489 if (d->type == osd::directory::entry::entry_type::FILE)
490 {
491 uint64_t size = d->size;
492 while (size && (size & 1) == 0) size >>= 1;
493 //if (size & ~1)
494 // printf("%-23s %-23s ignored (not a ROM)\n",i ? "" : d_name,i ? d_name : "");
495 //else
496 {
497 strcpy(files[i][found[i]].name,d_name);
498 files[i][found[i]].size = d->size;
499 readfile(path,&files[i][found[i]]);
500 files[i][found[i]].listed = 0;
501 if (found[i] >= MAX_FILES)
502 {
503 printf("%s: max of %d files exceeded\n",path,MAX_FILES);
504 break;
505 }
506 found[i]++;
507 }
508 }
509 }
510 dir.reset();
511 }
512
513 /* if not, try to open as a ZIP file */
514 else
515 {
516 util::archive_file::ptr zip;
517
518 /* wasn't a directory, so try to open it as a zip file */
519 if ((util::archive_file::open_zip(path, zip) != util::archive_file::error::NONE) &&
520 (util::archive_file::open_7z(path, zip) != util::archive_file::error::NONE))
521 {
522 printf("Error, cannot open zip file '%s' !\n", path);
523 return 1;
524 }
525
526 /* load all files in zip file */
527 for (int zipent = zip->first_file(); zipent >= 0; zipent = zip->next_file())
528 {
529 if (zip->current_is_directory()) continue;
530
531 int size;
532
533 size = zip->current_uncompressed_length();
534 while (size && (size & 1) == 0) size >>= 1;
535 if (zip->current_uncompressed_length() == 0) // || (size & ~1))
536 {
537 printf("%-23s %-23s ignored (not a ROM)\n",
538 i ? "" : zip->current_name().c_str(), i ? zip->current_name().c_str() : "");
539 }
540 else
541 {
542 fileinfo *file = &files[i][found[i]];
543 const char *delim = strrchr(zip->current_name().c_str(), '/');
544
545 if (delim)
546 strcpy (file->name,delim+1);
547 else
548 strcpy(file->name,zip->current_name().c_str());
549 file->size = zip->current_uncompressed_length();
550 if ((file->buf = (unsigned char *)malloc(file->size)) == nullptr)
551 printf("%s: out of memory!\n",file->name);
552 else
553 {
554 if (zip->decompress(file->buf, file->size) != util::archive_file::error::NONE)
555 {
556 free(file->buf);
557 file->buf = nullptr;
558 }
559 }
560
561 file->listed = 0;
562 if (found[i] >= MAX_FILES)
563 {
564 printf("%s: max of %d files exceeded\n",path,MAX_FILES);
565 break;
566 }
567 found[i]++;
568 }
569 }
570 }
571 return 0;
572 }
573
574
main(int argc,char * argv[])575 int CLIB_DECL main(int argc,char *argv[])
576 {
577 int err;
578 int total_modes = MODE_NIB2; /* by default, use only MODE_A, MODE_NIB1 and MODE_NIB2 */
579
580 if (argc >= 2 && strcmp(argv[1],"-d") == 0)
581 {
582 argc--;
583 argv++;
584 total_modes = TOTAL_MODES;
585 }
586
587 if (argc < 2)
588 {
589 printf("usage: romcmp [-d] [dir1 | zip1] [dir2 | zip2]\n");
590 printf("-d enables a slower, more comprehensive comparison.\n");
591 return 0;
592 }
593
594 {
595 int found[2];
596 int i,j,mode1,mode2;
597 int besti,bestj;
598
599
600 found[0] = found[1] = 0;
601 for (i = 0;i < 2;i++)
602 {
603 if (argc > i+1)
604 {
605 err = load_files (i, found, argv[i+1]);
606 if (err != 0)
607 return err;
608 }
609 }
610
611 if (argc >= 3)
612 printf("%d and %d files\n",found[0],found[1]);
613 else
614 printf("%d files\n",found[0]);
615
616 for (i = 0;i < found[0];i++)
617 {
618 checkintegrity(&files[0][i],0);
619 }
620
621 for (j = 0;j < found[1];j++)
622 {
623 checkintegrity(&files[1][j],1);
624 }
625
626 if (argc < 3)
627 {
628 /* find duplicates in one dir */
629 for (i = 0;i < found[0];i++)
630 {
631 for (j = i+1;j < found[0];j++)
632 {
633 for (mode1 = 0;mode1 < total_modes;mode1++)
634 {
635 for (mode2 = 0;mode2 < total_modes;mode2++)
636 {
637 if (filecompare(&files[0][i],&files[0][j],mode1,mode2) == 1.0f)
638 printname(&files[0][i],&files[0][j],1.0,mode1,mode2);
639 }
640 }
641 }
642 }
643 }
644 else
645 {
646 /* compare two dirs */
647 for (i = 0;i < found[0];i++)
648 {
649 for (j = 0;j < found[1];j++)
650 {
651 fprintf(stderr,"%2d%%\r",100*(i*found[1]+j)/(found[0]*found[1]));
652 for (mode1 = 0;mode1 < total_modes;mode1++)
653 {
654 for (mode2 = 0;mode2 < total_modes;mode2++)
655 {
656 matchscore[i][j][mode1][mode2] = filecompare(&files[0][i],&files[1][j],mode1,mode2);
657 }
658 }
659 }
660 }
661 fprintf(stderr," \r");
662
663 do
664 {
665 float bestscore;
666 int bestmode1,bestmode2;
667
668 besti = -1;
669 bestj = -1;
670 bestscore = 0.0;
671 bestmode1 = bestmode2 = -1;
672
673 for (mode1 = 0;mode1 < total_modes;mode1++)
674 {
675 for (mode2 = 0;mode2 < total_modes;mode2++)
676 {
677 for (i = 0;i < found[0];i++)
678 {
679 for (j = 0;j < found[1];j++)
680 {
681 if (matchscore[i][j][mode1][mode2] > bestscore
682 || (matchscore[i][j][mode1][mode2] == 1.0f && mode2 == 0 && bestmode2 > 0))
683 {
684 bestscore = matchscore[i][j][mode1][mode2];
685 besti = i;
686 bestj = j;
687 bestmode1 = mode1;
688 bestmode2 = mode2;
689 }
690 }
691 }
692 }
693 }
694
695 if (besti != -1)
696 {
697 int start=0,end=0;
698
699 printname(&files[0][besti],&files[1][bestj],bestscore,bestmode1,bestmode2);
700 files[0][besti].listed = 1;
701 files[1][bestj].listed = 1;
702
703 matchscore[besti][bestj][bestmode1][bestmode2] = 0.0;
704
705 /* remove all matches using the same sections with a worse score */
706 for (j = 0;j < found[1];j++)
707 {
708 for (mode2 = 0;mode2 < total_modes;mode2++)
709 {
710 if (matchscore[besti][j][bestmode1][mode2] < bestscore)
711 matchscore[besti][j][bestmode1][mode2] = 0.0;
712 }
713 }
714 for (i = 0;i < found[0];i++)
715 {
716 for (mode1 = 0;mode1 < total_modes;mode1++)
717 {
718 if (matchscore[i][bestj][mode1][bestmode2] < bestscore)
719 matchscore[i][bestj][mode1][bestmode2] = 0.0;
720 }
721 }
722
723 /* remove all matches using incompatible sections */
724 compatiblemodes(bestmode1,&start,&end);
725 for (j = 0;j < found[1];j++)
726 {
727 for (mode2 = 0;mode2 < total_modes;mode2++)
728 {
729 for (mode1 = 0;mode1 < start;mode1++)
730 matchscore[besti][j][mode1][mode2] = 0.0;
731 for (mode1 = end+1;mode1 < total_modes;mode1++)
732 matchscore[besti][j][mode1][mode2] = 0.0;
733 }
734 }
735 compatiblemodes(bestmode2,&start,&end);
736 for (i = 0;i < found[0];i++)
737 {
738 for (mode1 = 0;mode1 < total_modes;mode1++)
739 {
740 for (mode2 = 0;mode2 < start;mode2++)
741 matchscore[i][bestj][mode1][mode2] = 0.0;
742 for (mode2 = end+1;mode2 < total_modes;mode2++)
743 matchscore[i][bestj][mode1][mode2] = 0.0;
744 }
745 }
746 }
747 } while (besti != -1);
748
749
750 for (i = 0;i < found[0];i++)
751 {
752 if (files[0][i].listed == 0) printname(&files[0][i],nullptr,0.0,0,0);
753 }
754 for (i = 0;i < found[1];i++)
755 {
756 if (files[1][i].listed == 0) printname(nullptr,&files[1][i],0.0,0,0);
757 }
758 }
759
760
761 for (i = 0;i < found[0];i++)
762 {
763 freefile(&files[0][i]);
764 }
765 for (i = 0;i < found[1];i++)
766 {
767 freefile(&files[1][i]);
768 }
769 }
770
771 util::archive_file::cache_clear();
772 return 0;
773 }
774