quickdev16/tools/ucon64/2.0/src/ucon64_dat.c
2009-04-22 20:04:28 +02:00

1249 lines
37 KiB
C

/*
ucon64_dat.c - support for DAT files as known from RomCenter, GoodXXXX, etc.
Copyright (c) 1999 - 2004 NoisyB <noisyb@gmx.net>
Copyright (c) 2002 - 2004 dbjh
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif
#ifdef _WIN32
#include <windows.h>
#endif
#include "misc/misc.h"
#include "misc/getopt2.h"
#include "misc/property.h"
#include "misc/string.h"
#include "misc/file.h"
#ifdef USE_ZLIB
#include "misc/archive.h"
#endif
#include "ucon64.h"
#include "ucon64_misc.h"
#include "ucon64_dat.h"
#include "console/console.h"
#define MAX_FIELDS_IN_DAT 32
#define DAT_FIELD_SEPARATOR (0xac)
#define DAT_FIELD_SEPARATOR_S "\xac"
#define MAX_GAMES_FOR_CONSOLE 50000 // TODO?: dynamic size
typedef struct
{
const char *id; // string to detect console from datfile name
int (*compare) (const void *a, const void *b); // the function which compares the id with the filename
// compare() == 0 means success
int8_t console; // UCON64_SNES, UCON64_NES, etc.
const st_getopt2_t *console_usage;
} st_console_t;
typedef struct
{
uint32_t crc32;
long filepos;
} st_idx_entry_t;
typedef struct
{
uint32_t crc32;
char *fname;
} st_mkdat_entry_t;
#ifndef _WIN32
static DIR *ddat = NULL;
#else
static HANDLE ddat = NULL;
#endif
static FILE *fdat = NULL;
static int ucon64_n_files = 0, filepos_line = 0, warning = 1; // show the warning only
static FILE *ucon64_datfile; // once when indexing
static char ucon64_dat_fname[FILENAME_MAX];
static st_mkdat_entry_t *ucon64_mkdat_entries = NULL;
const st_getopt2_t ucon64_dat_usage[] =
{
{
NULL, 0, 0, 0,
NULL, "DATabase (support for DAT files)",
NULL
},
{
"db", 0, 0, UCON64_DB,
NULL, "DATabase statistics",
&ucon64_wf[WF_OBJ_ALL_STOP_NO_ROM]
},
{
"dbv", 0, 0, UCON64_DBV,
NULL, "like " OPTION_LONG_S "db but more verbose",
&ucon64_wf[WF_OBJ_ALL_STOP_NO_ROM]
},
{
"dbs", 1, 0, UCON64_DBS,
"CRC32", "search ROM with CRC32 in DATabase",
&ucon64_wf[WF_OBJ_ALL_STOP_NO_ROM]
},
{
"scan", 0, 0, UCON64_SCAN,
NULL, "generate ROM list for all ROMs using DATabase\n"
"like: GoodXXXX scan ...",
&ucon64_wf[WF_OBJ_ALL_INIT_PROBE_NO_SPLIT]
},
{
"lsd", 0, 0, UCON64_LSD,
NULL, "same as " OPTION_LONG_S "scan",
&ucon64_wf[WF_OBJ_ALL_INIT_PROBE]
},
{
"mkdat", 1, 0, UCON64_MKDAT,
"DATFILE", "create DAT file; use -o to specify an output directory",
&ucon64_wf[WF_OBJ_ALL_INIT_PROBE]
},
{
"rrom", 0, 0, UCON64_RROM,
NULL, "rename ROMs to their internal names",
&ucon64_wf[WF_OBJ_ALL_INIT_PROBE_NO_SPLIT]
},
{
"rename", 0, 0, UCON64_RENAME,
NULL, "rename ROMs to their DATabase names\n"
"use -o to specify an output directory",
&ucon64_wf[WF_OBJ_ALL_INIT_PROBE_NO_SPLIT]
},
{
"rr83", 0, 0, UCON64_RR83,
NULL, "force to rename to 8.3 filenames",
&ucon64_wf[WF_OBJ_ALL_INIT_PROBE_NO_SPLIT]
},
{
"force63", 0, 0, UCON64_FORCE63,
NULL, "force to rename all filenames into Joliet CD format\n"
"like: GoodXXXX rename inplace force63 ...\n"
"TIP: using " OPTION_LONG_S "nes would process only NES ROMs",
&ucon64_wf[WF_OBJ_ALL_SWITCH]
},
{
"rl", 0, 0, UCON64_RL,
NULL, "rename ROMs to lowercase",
NULL
},
{
"ru", 0, 0, UCON64_RU,
NULL, "rename ROMs to uppercase",
NULL
},
#if 0
{
"good", 0, 0, UCON64_GOOD,
NULL, "used with " OPTION_LONG_S "rrom and " OPTION_LONG_S "rr83 ROMs will be renamed using\n"
"the DATabase",
NULL
},
#endif
{
NULL, 0, 0, 0,
NULL, NULL,
NULL
}
};
static void
closedir_ddat (void)
{
if (ddat)
#ifndef _WIN32
closedir (ddat);
#else
FindClose (ddat);
#endif
ddat = NULL;
}
static void
fclose_fdat (void)
{
if (fdat)
fclose (fdat);
fdat = NULL;
}
static int
custom_stristr (const void *a, const void *b)
{
return !stristr ((const char *) a, (const char *) b);
}
static int
custom_strnicmp (const void *a, const void *b)
{
return strnicmp ((const char *) a, (const char *) b,
MIN (strlen ((const char *) a), strlen ((const char *) b)));
}
#if 0
static int
custom_stricmp (const void *a, const void *b)
{
return stricmp ((const char *) a, (const char *) b);
}
#endif
static char *
get_next_file (char *fname)
{
#ifndef _WIN32
struct dirent *ep;
if (!ddat)
if (!(ddat = opendir (ucon64.datdir)))
{
fprintf (stderr, ucon64_msg[OPEN_READ_ERROR], ucon64.datdir);
return NULL;
}
while ((ep = readdir (ddat)) != NULL)
if (!stricmp (get_suffix (ep->d_name), ".dat"))
{
sprintf (fname, "%s" FILE_SEPARATOR_S "%s", ucon64.datdir, ep->d_name);
return fname;
}
#else
char search_pattern[FILENAME_MAX];
WIN32_FIND_DATA find_data;
if (!ddat)
{
// Note that FindFirstFile() & FindNextFile() are case insensitive
sprintf (search_pattern, "%s" FILE_SEPARATOR_S "*.dat", ucon64.datdir);
if ((ddat = FindFirstFile (search_pattern, &find_data)) == INVALID_HANDLE_VALUE)
{
// Not being able to find a DAT file is not a real error
if (GetLastError () != ERROR_FILE_NOT_FOUND)
{
fprintf (stderr, ucon64_msg[OPEN_READ_ERROR], ucon64.datdir);
return NULL;
}
}
else
{
sprintf (fname, "%s" FILE_SEPARATOR_S "%s", ucon64.datdir, find_data.cFileName);
return fname;
}
}
while (FindNextFile (ddat, &find_data))
{
sprintf (fname, "%s" FILE_SEPARATOR_S "%s", ucon64.datdir, find_data.cFileName);
return fname;
}
#endif
closedir_ddat ();
return NULL;
}
static st_ucon64_dat_t *
get_dat_header (char *fname, st_ucon64_dat_t *dat)
{
char buf[50 * 80]; // should be enough
// Hell yes! I (NoisyB) use get_property() here...
strncpy (dat->author, get_property (fname, "author", buf, "Unknown"), sizeof (dat->author))[sizeof (dat->author) - 1] = 0;
strncpy (dat->version, get_property (fname, "version", buf, "?"), sizeof (dat->version))[sizeof (dat->version) - 1] = 0;
strncpy (dat->refname, get_property (fname, "refname", buf, ""), sizeof (dat->refname))[sizeof (dat->refname) - 1] = 0;
strcpy (dat->comment, get_property (fname, "comment", buf, ""));
strncpy (dat->date, get_property (fname, "date", buf, "?"), sizeof (dat->date))[sizeof (dat->date) - 1] = 0;
return dat;
}
static int
fname_to_console (const char *fname, st_ucon64_dat_t *dat)
{
int pos;
// We use the filename to find out for what console a DAT file is meant.
// The field "refname" seems too unreliable.
static const st_console_t console_type[] =
{
{"GoodSNES", custom_strnicmp, UCON64_SNES, snes_usage},
{"SNES", custom_strnicmp, UCON64_SNES, snes_usage},
{"GoodNES", custom_strnicmp, UCON64_NES, nes_usage},
{"NES", custom_strnicmp, UCON64_NES, nes_usage},
{"FDS", custom_stristr, UCON64_NES, nes_usage},
{"GoodGBA", custom_strnicmp, UCON64_GBA, gba_usage},
{"GBA", custom_strnicmp, UCON64_GBA, gba_usage},
{"GoodGBX", custom_strnicmp, UCON64_GB, gameboy_usage},
{"GBX", custom_strnicmp, UCON64_GB, gameboy_usage},
{"GoodGEN", custom_strnicmp, UCON64_GEN, genesis_usage},
{"GEN", custom_strnicmp, UCON64_GEN, genesis_usage},
{"GoodGG", custom_strnicmp, UCON64_SMS, sms_usage},
{"GG", custom_strnicmp, UCON64_SMS, sms_usage},
{"GoodSMS", custom_strnicmp, UCON64_SMS, sms_usage},
{"SMS", custom_strnicmp, UCON64_SMS, sms_usage},
{"GoodJAG", custom_strnicmp, UCON64_JAG, jaguar_usage},
{"JAG", custom_strnicmp, UCON64_JAG, jaguar_usage},
{"GoodLynx", custom_strnicmp, UCON64_LYNX, lynx_usage},
{"Lynx", custom_strnicmp, UCON64_LYNX, lynx_usage},
{"GoodN64", custom_strnicmp, UCON64_N64, n64_usage},
{"N64", custom_strnicmp, UCON64_N64, n64_usage},
{"GoodPCE", custom_strnicmp, UCON64_PCE, pcengine_usage},
{"PCE", custom_strnicmp, UCON64_PCE, pcengine_usage},
{"Good2600", custom_strnicmp, UCON64_ATA, atari_usage},
{"2600", custom_strnicmp, UCON64_ATA, atari_usage},
{"Good5200", custom_strnicmp, UCON64_ATA, atari_usage},
{"5200", custom_strnicmp, UCON64_ATA, atari_usage},
{"Good7800", custom_strnicmp, UCON64_ATA, atari_usage},
{"7800", custom_strnicmp, UCON64_ATA, atari_usage},
{"GoodVECT", custom_strnicmp, UCON64_VEC, vectrex_usage},
{"Vectrex", custom_stristr, UCON64_VEC, vectrex_usage},
{"GoodWSX", custom_strnicmp, UCON64_SWAN, swan_usage},
{"swan", custom_stristr, UCON64_SWAN, swan_usage},
{"GoodCOL", custom_strnicmp, UCON64_COLECO, coleco_usage},
{"Coleco", custom_stristr, UCON64_COLECO, coleco_usage},
{"GoodINTV", custom_strnicmp, UCON64_INTELLI, intelli_usage},
{"Intelli", custom_stristr, UCON64_INTELLI, intelli_usage},
{"GoodNGPX", custom_strnicmp, UCON64_NGP, ngp_usage},
{"NGP", custom_strnicmp, UCON64_NGP, ngp_usage},
{"GoodVBOY", custom_strnicmp, UCON64_VBOY, vboy_usage},
{"VBOY", custom_strnicmp, UCON64_VBOY, vboy_usage},
{"Neo-Geo", custom_strnicmp, UCON64_NG, neogeo_usage},
{"MAME", custom_stristr, UCON64_MAME, mame_usage},
{"Dreamcast", custom_stristr, UCON64_DC, dc_usage},
{"Saturn", custom_stristr, UCON64_SAT, sat_usage},
{"3do", custom_stristr, UCON64_3DO, real3do_usage},
{"CDi", custom_stristr, UCON64_CDI, cdi_usage},
{"XBox", custom_stristr, UCON64_XBOX, xbox_usage},
{"CD32", custom_stristr, UCON64_CD32, cd32_usage},
/* TODO:
{"psx", custom_stristr, UCON64_PSX, psx_usage},
{"ps1", custom_stristr, UCON64_PSX, psx_usage},
{"psone", custom_stristr, UCON64_PSX, psx_usage},
{"ps2", custom_stristr, UCON64_PS2, ps2_usage},
{"dc", custom_stristr, UCON64_DC, dc_usage},
{"system", custom_stristr, UCON64_S16, s16_usage},
{"pocket", custom_stristr, UCON64_NGP, ngp_usage},
{"virtual", custom_stristr, UCON64_VBOY, vboy_usage},
{"", custom_stristr, 0, channelf_usage},
{"", custom_stristr, 0, gamecom_usage},
{"", custom_stristr, 0, gc_usage},
{"", custom_stristr, 0, gp32_usage},
{"", custom_stristr, 0, odyssey2_usage},
{"", custom_stristr, 0, odyssey_usage},
{"", custom_stristr, 0, s16_usage},
{"", custom_stristr, 0, sat_usage},
{"", custom_stristr, 0, vc4000_usage},
*/
{0, 0, 0, 0}
};
for (pos = 0; console_type[pos].id; pos++)
{
if (!console_type[pos].compare (fname, console_type[pos].id))
{
dat->console = console_type[pos].console;
dat->console_usage = (console_type[pos].console_usage[0].help);
break;
}
}
if (console_type[pos].id == 0)
{
if (warning)
{
printf ("WARNING: \"%s\" is meant for a console unknown to uCON64\n\n", fname);
warning = 0;
}
dat->console = UCON64_UNKNOWN;
dat->console_usage = NULL;
}
return dat->console;
}
static st_ucon64_dat_t *
line_to_dat (const char *fname, const char *dat_entry, st_ucon64_dat_t *dat)
// parse a dat entry into st_ucon64_dat_t
{
static const char *dat_country[28][2] =
{
{"(FC)", "French Canada"},
{"(FN)", "Finland"},
{"(G)", "Germany"},
{"(GR)", "Greece"},
{"(H)", "Holland"}, // other (incorrect) name for The Netherlands
{"(HK)", "Hong Kong"},
{"(I)", "Italy"},
{"(J)", "Japan"},
{"(JE)", "Japan & Europe"},
{"(JU)", "Japan & U.S.A."},
{"(JUE)", "Japan, U.S.A. & Europe"},
{"(K)", "Korea"},
{"(NL)", "The Netherlands"},
{"(PD)", "Public Domain"},
{"(S)", "Spain"},
{"(SW)", "Sweden"},
{"(U)", "U.S.A."},
{"(UE)", "U.S.A. & Europe"},
{"(UK)", "England"},
{"(Unk)", "Unknown country"},
/*
At least (A), (B), (C), (E) and (F) have to come after the other
countries, because some games have (A), (B) etc. in their name (the
non-country part). For example, the SNES games
"SD Gundam Generations (A) 1 Nen Sensouki (J) (ST)" or
"SD Gundam Generations (B) Guripus Senki (J) (ST)".
*/
{"(1)", "Japan & Korea"},
{"(4)", "U.S.A. & Brazil NTSC"},
{"(A)", "Australia"},
{"(B)", "non U.S.A. (Genesis)"},
{"(C)", "China"},
{"(E)", "Europe"},
{"(F)", "France"},
{NULL, NULL}
};
static const char *dat_flags[][2] =
{
// Often flags contain numbers, so don't search for the closing bracket
{"[a", "Alternate"},
{"[p", "Pirate"},
{"[b", "Bad dump"},
{"[t", "Trained"},
{"[f", "Fixed"},
{"[T", "Translation"},
{"[h", "Hack"},
{"[x", "Bad checksum"},
{"[o", "Overdump"},
{"[!]", "Verified good dump"}, // [!] is ok
{NULL, NULL}
};
char *dat_field[MAX_FIELDS_IN_DAT + 2] = { NULL }, buf[MAXBUFSIZE], *p = NULL;
uint32_t pos = 0;
int x = 0;
if ((unsigned char) dat_entry[0] != DAT_FIELD_SEPARATOR)
return NULL;
strcpy (buf, dat_entry);
strarg (dat_field, buf, DAT_FIELD_SEPARATOR_S, MAX_FIELDS_IN_DAT);
memset (dat, 0, sizeof (st_ucon64_dat_t));
strcpy (dat->datfile, basename2 (fname));
if (dat_field[3])
strcpy (dat->name, dat_field[3]);
if (dat_field[4])
strcpy (dat->fname, dat_field[4]);
if (dat_field[5])
sscanf (dat_field[5], "%x", (unsigned int *) &dat->crc32);
if (dat_field[6][0] == 'N' && dat_field[7][0] == 'O')
// e.g. GoodSNES bad crc & Nintendo FDS DAT
sscanf (dat_field[8], "%d", (int *) &dat->fsize);
else
sscanf (dat_field[6], "%d", (int *) &dat->fsize);
*buf = 0;
for (x = 0, p = buf; dat_flags[x][0]; x++, p += strlen (p))
if (strstr (dat->name, dat_flags[x][0]))
sprintf (p, "%s, ", dat_flags[x][1]);
if (buf[0])
{
if ((p = strrchr (buf, ',')))
*p = 0;
sprintf (dat->misc, "Flags: %s", buf);
}
p = dat->name;
dat->country = NULL;
for (pos = 0; dat_country[pos][0]; pos++)
if (stristr (p, dat_country[pos][0]))
{
dat->country = dat_country[pos][1];
break;
}
fname_to_console (dat->datfile, dat);
dat->copier_usage = unknown_usage[0].help;
return dat;
}
uint32_t
line_to_crc (const char *dat_entry)
// get crc32 of current line
{
char *dat_field[MAX_FIELDS_IN_DAT + 2] = { NULL }, buf[MAXBUFSIZE];
unsigned int crc32 = 0; // has to be unsigned int to
// avoid a stupid gcc warning
if ((unsigned char) dat_entry[0] != DAT_FIELD_SEPARATOR)
return 0;
strcpy (buf, dat_entry);
strarg (dat_field, buf, DAT_FIELD_SEPARATOR_S, MAX_FIELDS_IN_DAT);
if (dat_field[5])
sscanf (dat_field[5], "%x", &crc32);
return (uint32_t) crc32;
}
static st_ucon64_dat_t *
get_dat_entry (char *fname, st_ucon64_dat_t *dat, uint32_t crc32, long start)
{
char buf[MAXBUFSIZE];
if (!fdat)
if (!(fdat = fopen (fname, "rb")))
{
fprintf (stderr, ucon64_msg[OPEN_READ_ERROR], fname);
#if defined _WIN32 || defined __CYGWIN__ || defined __MSDOS__
if (!stricmp (basename2 (fname), "ntuser.dat"))
fputs (" Please see the FAQ, question 47 & 36\n", stderr);
// "ERROR: "
#endif
return NULL;
}
if (start >= 0)
fseek (fdat, start, SEEK_SET);
filepos_line = ftell (fdat);
while (fgets (buf, MAXBUFSIZE, fdat) != NULL)
{
if ((unsigned char) buf[0] == DAT_FIELD_SEPARATOR)
if (!crc32 || line_to_crc (buf) == crc32)
if (line_to_dat (fname, buf, dat))
return dat;
filepos_line = ftell (fdat);
}
fclose_fdat ();
return NULL;
}
int
ucon64_dat_view (int console, int verbose)
{
char fname_dat[FILENAME_MAX], fname_index[FILENAME_MAX];
const char *fname;
unsigned char *p;
static st_ucon64_dat_t dat;
int n, fsize, n_entries, n_entries_sum = 0, n_datfiles = 0;
st_idx_entry_t *idx_entry;
while (get_next_file (fname_dat))
{
fname = basename2 (fname_dat);
if (console != UCON64_UNKNOWN)
if (fname_to_console (fname, &dat) != console)
continue;
get_dat_header (fname_dat, &dat);
strcpy (fname_index, fname_dat);
set_suffix (fname_index, ".idx");
fsize = fsizeof (fname_index);
n_entries = fsize / sizeof (st_idx_entry_t);
n_entries_sum += n_entries;
n_datfiles++;
printf ("DAT info:\n"
" %s\n"
// " Console: %s\n"
" Version: %s (%s, %s)\n"
" Author: %s\n"
" Comment: %s\n"
" Entries: %d\n\n",
fname,
// dat.console_usage[0],
dat.version,
dat.date,
dat.refname,
dat.author,
dat.comment,
n_entries);
if (!(p = (unsigned char *) malloc (fsize)))
{
fprintf (stderr, ucon64_msg[BUFFER_ERROR], fsize);
continue;
}
if (ucon64_fread (p, 0, fsize, fname_index) != fsize)
{
fprintf (stderr, ucon64_msg[READ_ERROR], fname_index);
free (p);
continue;
}
if (verbose)
{
// display all DAT entries
for (n = 0; n < n_entries; n++)
{
idx_entry = &((st_idx_entry_t *) p)[n];
printf ("Checksum (CRC32): 0x%08x\n", (unsigned int) idx_entry->crc32);
if (get_dat_entry (fname_dat, &dat, idx_entry->crc32, idx_entry->filepos))
ucon64_dat_nfo (&dat, 0);
fputc ('\n', stdout);
}
fclose_fdat ();
}
free (p);
}
printf ("DAT files: %d; entries: %d; total entries: %d\n",
n_datfiles, n_entries_sum, ucon64_dat_total_entries ());
return 0;
}
unsigned int
ucon64_dat_total_entries (void)
{
uint32_t entries = 0;
int fsize;
char fname[FILENAME_MAX];
if (!ucon64.dat_enabled)
return 0;
while (get_next_file (fname))
{
set_suffix (fname, ".idx");
fsize = fsizeof (fname);
entries += (fsize < 0 ? 0 : fsize / sizeof (st_idx_entry_t));
}
return entries;
}
static int
idx_compare (const void *key, const void *found)
{
/*
The return statement looks overly complicated, but is really necessary.
This construct:
return ((st_idx_entry_t *) key)->crc32 - ((st_idx_entry_t *) found)->crc32;
does *not* work correctly for all cases.
*/
return (int) (((int64_t) ((st_idx_entry_t *) key)->crc32 -
(int64_t) ((st_idx_entry_t *) found)->crc32) / 2);
}
st_ucon64_dat_t *
ucon64_dat_search (uint32_t crc32, st_ucon64_dat_t *datinfo)
{
char fname_dat[FILENAME_MAX], fname_index[FILENAME_MAX];
const char *fname;
unsigned char *p = NULL;
int32_t fsize = 0;
st_idx_entry_t *idx_entry, key;
static st_ucon64_dat_t dat;
st_ucon64_dat_t *dat_p = NULL;
memset (&dat, 0, sizeof (st_ucon64_dat_t));
if (!crc32)
return NULL;
while (get_next_file (fname_dat))
{
fname = basename2 (fname_dat);
if (ucon64.console != UCON64_UNKNOWN)
if (fname_to_console (fname, &dat) != ucon64.console)
continue;
strcpy (fname_index, fname_dat);
set_suffix (fname_index, ".idx");
if (access (fname_index, F_OK) != 0) // for a "bad" DAT file
continue;
fsize = fsizeof (fname_index);
if (!(p = (unsigned char *) malloc (fsize)))
{
fprintf (stderr, ucon64_msg[BUFFER_ERROR], fsize);
closedir_ddat ();
return NULL;
}
// load the index for the current dat file
if (ucon64_fread (p, 0, fsize, fname_index) != fsize)
{
fprintf (stderr, ucon64_msg[READ_ERROR], fname_index);
closedir_ddat ();
free (p);
return NULL;
}
// search index for crc
key.crc32 = crc32;
idx_entry = (st_idx_entry_t *) bsearch (&key, p, fsize / sizeof (st_idx_entry_t),
sizeof (st_idx_entry_t), idx_compare);
if (idx_entry) // crc32 found
{
if (!datinfo)
dat_p = (st_ucon64_dat_t *) &dat; // TODO?: malloc()
else
dat_p = (st_ucon64_dat_t *) &datinfo;
// open dat file and read entry
if (get_dat_entry (fname_dat, dat_p, crc32, idx_entry->filepos))
if (crc32 == dat_p->crc32)
{
strcpy (dat_p->datfile, basename2 (fname_dat));
get_dat_header (fname_dat, dat_p);
closedir_ddat ();
fclose_fdat ();
free (p);
return dat_p;
}
fclose_fdat ();
}
free (p);
}
return NULL;
}
int
ucon64_dat_indexer (void)
// create or update index of DAT file
{
char fname_dat[FILENAME_MAX], fname_index[FILENAME_MAX], errorfname[FILENAME_MAX];
struct stat fstate_dat, fstate_index;
st_ucon64_dat_t dat;
FILE *errorfile;
time_t start_time = 0;
int update = 0, n_duplicates, n, size = 0, pos;
st_idx_entry_t *idx_entries, *idx_entry;
warning = 1; // enable warning again for DATs with unrecognized console systems
if (!(idx_entries = (st_idx_entry_t *)
malloc (MAX_GAMES_FOR_CONSOLE * sizeof (st_idx_entry_t))))
{
fprintf (stderr, ucon64_msg[BUFFER_ERROR],
MAX_GAMES_FOR_CONSOLE * sizeof (st_idx_entry_t));
exit (1);
}
while (get_next_file (fname_dat))
{
strcpy (fname_index, fname_dat);
set_suffix (fname_index, ".idx");
if (!stat (fname_dat, &fstate_dat) && !stat (fname_index, &fstate_index))
{
if (fstate_dat.st_mtime < fstate_index.st_mtime)
continue; // index file seems to be present and up-to-date
update = 1;
}
start_time = time (0);
size = fsizeof (fname_dat);
printf ("%s: %s\n", (update ? "Update" : "Create"), basename2 (fname_index));
pos = 0;
n_duplicates = 0;
errorfile = NULL;
while (get_dat_entry (fname_dat, &dat, 0, -1))
{
if (pos == MAX_GAMES_FOR_CONSOLE)
{
fprintf (stderr,
"\n"
"INTERNAL ERROR: MAX_GAMES_FOR_CONSOLE is too small (%d)\n",
MAX_GAMES_FOR_CONSOLE);
break;
}
/*
Doing a linear search removes the need of using the slow qsort()
function inside the loop. Doing a binary search doesn't improve the
speed much, but is much more efficient of course. Using qsort()
inside the loop slows it down with a factor greater than 10.
*/
idx_entry = NULL;
for (n = 0; n < pos; n++)
if (idx_entries[n].crc32 == dat.crc32)
idx_entry = &idx_entries[n];
if (idx_entry)
{
// This really makes one loose trust in the DAT files...
char current_name[2 * 80];
long current_filepos = ftell (fdat);
if (!errorfile)
{
strcpy (errorfname, fname_index);
set_suffix (errorfname, ".err");
if (!(errorfile = fopen (errorfname, "w"))) // text file for WinDOS
{
fprintf (stderr, ucon64_msg[OPEN_WRITE_ERROR], errorfname);
continue;
}
}
strcpy (current_name, dat.name);
get_dat_entry (fname_dat, &dat, 0, idx_entry->filepos);
fprintf (errorfile,
"\n"
"WARNING: DAT file contains a duplicate CRC32 (0x%x)!\n"
" First game with this CRC32: \"%s\"\n"
" Ignoring game: \"%s\"\n",
(unsigned int) dat.crc32, dat.name, current_name);
n_duplicates++;
fseek (fdat, current_filepos, SEEK_SET);
continue;
}
idx_entries[pos].crc32 = dat.crc32;
idx_entries[pos].filepos = filepos_line;
if (!(pos % 20))
ucon64_gauge (start_time, ftell (fdat), size);
pos++;
}
fclose_fdat ();
if (pos > 0)
{
qsort (idx_entries, pos, sizeof (st_idx_entry_t), idx_compare);
if (ucon64_fwrite (idx_entries, 0, pos * sizeof (st_idx_entry_t), fname_index, "wb")
!= (int) (pos * sizeof (st_idx_entry_t)))
{
fputc ('\n', stderr);
fprintf (stderr, ucon64_msg[WRITE_ERROR], fname_index);
}
ucon64_gauge (start_time, size, size);
}
if (n_duplicates > 0)
printf ("\n"
"\n"
"WARNING: DAT file contains %d duplicate CRC32%s\n"
" Warnings have been written to \"%s\"",
n_duplicates, n_duplicates != 1 ? "s" : "", errorfname);
if (errorfile)
{
fclose (errorfile);
errorfile = NULL;
}
fputs ("\n\n", stdout);
}
free (idx_entries);
return 0;
}
#if 0
st_ucon64_dat_t *
ucon64_dat_flush (st_ucon64_dat_t *dat)
{
memset (dat, 0, sizeof (st_ucon64_dat_t));
ucon64.dat = NULL;
return NULL;
}
#endif
void
ucon64_dat_nfo (const st_ucon64_dat_t *dat, int display_version)
{
char buf[MAXBUFSIZE], *p = NULL;
int n;
if (!dat)
{
printf (ucon64_msg[DAT_NOT_FOUND], ucon64.crc32);
return;
}
fputs ("DAT info:\n", stdout);
// console type?
if (dat->console_usage != NULL)
{
strcpy (buf, dat->console_usage);
// fix ugly multi-line console "usages" (PC-Engine)
if ((p = strchr (buf, '\n')) != NULL)
*p = 0;
printf (" %s\n", buf);
}
printf (" %s\n", dat->name);
if (dat->country)
printf (" %s\n", dat->country);
/*
The DAT files are not consistent. Some include the file suffix, but
others don't. We want to display the canonical file name only if it
really differs from the canonical game name (usually file name without
suffix).
*/
n = strlen (dat->fname);
p = (char *) get_suffix (dat->fname);
if (!(stricmp (p, ".nes") && // NES
stricmp (p, ".fds") && // NES FDS
stricmp (p, ".gb") && // Game Boy
stricmp (p, ".gbc") && // Game Boy Color
stricmp (p, ".gba") && // Game Boy Advance
stricmp (p, ".smc") && // SNES
stricmp (p, ".sc") && // Sega Master System
stricmp (p, ".sg") && // Sega Master System
stricmp (p, ".sms") && // Sega Master System
stricmp (p, ".gg") && // Game Gear
stricmp (p, ".smd") && // Genesis
stricmp (p, ".v64"))) // Nintendo 64
((char *) dat->fname)[strlen (dat->fname) - strlen (p)] = 0;
if (stricmp (dat->name, dat->fname) != 0)
printf (" Filename: %s\n", dat->fname);
printf (" %d Bytes (%.4f Mb)\n", (int) dat->fsize, TOMBIT_F (dat->fsize));
if (dat->misc[0])
printf (" %s\n", dat->misc);
if (display_version)
{
if (stristr (dat->datfile, dat->version))
printf (" %s (%s, %s)\n",
dat->datfile,
dat->date,
dat->refname);
else
printf (" %s (%s, %s, %s)\n",
dat->datfile,
dat->version,
dat->date,
dat->refname);
}
}
static void
ucon64_close_datfile (void)
{
int n;
if (ucon64_datfile)
{
fclose (ucon64_datfile);
printf (ucon64_msg[WROTE], ucon64_dat_fname);
ucon64_datfile = NULL;
for (n = 0; n < ucon64_n_files; n++)
{
free (ucon64_mkdat_entries[n].fname);
ucon64_mkdat_entries[n].fname = NULL;
}
ucon64_n_files = 0;
}
}
int
ucon64_create_dat (const char *dat_file_name, const char *filename,
int buheader_len)
{
static int first_file = 1, console;
int n, x;
static char *console_name;
char fname[FILENAME_MAX], *ptr;
time_t time_t_val;
struct tm *t;
if (first_file)
{
char *plugin = "";
first_file = 0;
console = ucon64.console;
switch (ucon64.console)
{
case UCON64_3DO:
console_name = "3DO";
break;
case UCON64_ATA:
console_name = "NES";
break;
case UCON64_CD32:
console_name = "CD32";
break;
case UCON64_CDI:
console_name = "CD-i";
break;
case UCON64_COLECO:
console_name = "Coleco";
break;
case UCON64_DC:
console_name = "Dreamcast";
break;
case UCON64_GB:
console_name = "Game Boy";
break;
case UCON64_GBA:
console_name = "Game Boy Advance";
break;
case UCON64_GC:
console_name = "Game Cube";
break;
case UCON64_GEN:
console_name = "Genesis/Mega Drive";
plugin = "genesis.dll";
break;
case UCON64_INTELLI:
console_name = "Intellivision";
break;
case UCON64_JAG:
console_name = "Jaguar";
break;
case UCON64_LYNX:
console_name = "Lynx";
break;
case UCON64_MAME:
console_name = "M.A.M.E.";
plugin = "arcade.dll";
break;
case UCON64_N64:
console_name = "Nintendo 64";
plugin = "n64.dll";
break;
case UCON64_NES:
console_name = "NES";
plugin = "nes.dll";
break;
case UCON64_NG:
console_name = "Neo Geo";
plugin = "arcade.dll";
break;
case UCON64_NGP:
console_name = "Neo Geo Pocket";
break;
case UCON64_PCE:
console_name = "PC-Engine";
break;
case UCON64_PS2:
console_name = "Playstation 2";
break;
case UCON64_PSX:
console_name = "Playstation";
break;
case UCON64_S16:
console_name = "S16";
break;
case UCON64_SAT:
console_name = "Saturn";
break;
case UCON64_SMS:
console_name = "SMS/Game Gear";
break;
case UCON64_SNES:
console_name = "SNES";
plugin = "snes.dll"; // be sure to use the new SNES plug-in (RC 2.62)
break;
case UCON64_SWAN:
console_name = "Wonderswan";
break;
case UCON64_VBOY:
console_name = "Virtual Boy";
break;
case UCON64_VEC:
console_name = "Vectrex";
break;
case UCON64_XBOX:
console_name = "XBox";
break;
default:
fputs (ucon64_msg[CONSOLE_ERROR], stderr);
exit (1);
break;
}
if (!(ucon64_mkdat_entries = (st_mkdat_entry_t *)
malloc (MAX_GAMES_FOR_CONSOLE * sizeof (st_mkdat_entry_t))))
{
fprintf (stderr, ucon64_msg[BUFFER_ERROR],
MAX_GAMES_FOR_CONSOLE * sizeof (st_mkdat_entry_t));
exit (1);
}
strcpy (ucon64_dat_fname, dat_file_name);
ucon64_file_handler (ucon64_dat_fname, NULL, OF_FORCE_BASENAME);
if (!(ucon64_datfile = fopen (ucon64_dat_fname, "wb")))
{
fprintf (stderr, ucon64_msg[OPEN_WRITE_ERROR], ucon64_dat_fname);
exit (1);
}
register_func (ucon64_close_datfile);
time_t_val = time (NULL);
t = localtime (&time_t_val);
// RomCenter uses files in DOS text format, so we generate a file in that format
fprintf (ucon64_datfile, "[CREDITS]\r\n"
"author=uCON64\r\n"
"email=noisyb@gmx.net\r\n"
"homepage=uCON64 homepage\r\n"
"url=ucon64.sf.net\r\n"
"version=%s-%s\r\n"
"date=%d/%d/%d\r\n"
"comment=%s DAT file generated by uCON64\r\n"
"[DAT]\r\n"
"version=2.50\r\n" // required by RomCenter!
"plugin=%s\r\n"
"[EMULATOR]\r\n"
"refname=%s\r\n"
"version=\r\n"
"[GAMES]\r\n",
UCON64_VERSION_S, console_name,
t->tm_mday, t->tm_mon + 1, t->tm_year + 1900,
console_name,
plugin,
console_name);
} // first_file
if (ucon64_n_files == MAX_GAMES_FOR_CONSOLE)
{
fprintf (stderr,
"INTERNAL ERROR: MAX_GAMES_FOR_CONSOLE is too small (%d)\n",
MAX_GAMES_FOR_CONSOLE);
exit (1);
}
strcpy (fname, basename2 (filename));
// Check the console type
n = 0;
if (ucon64.console != console)
{
if (ucon64.quiet == -1)
printf ("WARNING: Skipping (!%s) ", console_name);
else
return -1;
}
else
{
// Check if the CRC32 is unique. We don't want to be as stupid as
// the tool used to create the GoodDAT files.
// Yes, a plain and simple linear search. Analysing the files is orders
// of magnitude slower than this search
for (; n < ucon64_n_files; n++)
if (ucon64_mkdat_entries[n].crc32 == ucon64.crc32)
break;
if (n != ucon64_n_files)
{
if (ucon64.quiet < 1) // better print this by default
fputs ("WARNING: Skipping (duplicate) ", stdout);
else
return -1;
}
}
fputs (filename, stdout);
if (ucon64.quiet == -1) // -v was specified
if (ucon64.fname_arch[0])
printf (" (%s)", ucon64.fname_arch);
fputc ('\n', stdout);
if (ucon64.console != console) // ucon64.quiet == -1
return -1;
if (n != ucon64_n_files)
{
if (ucon64.quiet < 1) // better print this by default
printf (" First file with this CRC32 (0x%x) is:\n"
" \"%s\"\n", ucon64.crc32, ucon64_mkdat_entries[n].fname);
return -1;
}
// Store the CRC32 to check if a file is unique
ucon64_mkdat_entries[ucon64_n_files].crc32 = ucon64.crc32;
/*
Also store the name of the file to display a helpful error message if a
file is not unique (a duplicate). We store the current filename inside the
archive as well, to be even more helpful :-)
*/
x = strlen (fname) + (ucon64.fname_arch[0] ? strlen (ucon64.fname_arch) + 4 : 1);
if (!(ucon64_mkdat_entries[ucon64_n_files].fname = (char *) malloc (x)))
{ // + 3 for " ()"
fprintf (stderr, ucon64_msg[BUFFER_ERROR], x); // + 1 for ASCII-z
exit (1);
}
sprintf (ucon64_mkdat_entries[ucon64_n_files].fname, "%s%s%s%s",
fname,
ucon64.fname_arch[0] ? " (" : "",
ucon64.fname_arch[0] ? ucon64.fname_arch : "",
ucon64.fname_arch[0] ? ")" : "");
ptr = (char *) get_suffix (fname);
if (*ptr)
*ptr = 0;
fprintf (ucon64_datfile, DAT_FIELD_SEPARATOR_S "%s" // set file name
DAT_FIELD_SEPARATOR_S "%s" // set full name
DAT_FIELD_SEPARATOR_S "%s" // clone file name
DAT_FIELD_SEPARATOR_S "%s" // clone full name
DAT_FIELD_SEPARATOR_S "%s" // rom file name
DAT_FIELD_SEPARATOR_S "%08x" // RC quirck: leading zeroes are required
DAT_FIELD_SEPARATOR_S "%d"
DAT_FIELD_SEPARATOR_S // merged clone name
DAT_FIELD_SEPARATOR_S // merged rom name
DAT_FIELD_SEPARATOR_S "\r\n",
fname,
fname,
fname,
fname,
fname,
ucon64.crc32,
ucon64.file_size - buheader_len);
ucon64_n_files++;
return 0;
}