2009-08-28 09:12:33 +02:00

1441 lines
41 KiB
C

/*
uCON64 - a tool to modify video game ROMs and to transfer ROMs to the
different backup units/emulators that exist. It is based on the old uCON but
with completely new source. It aims to support all cartridge consoles and
handhelds like N64, JAG, SNES, NG, GENESIS, GB, LYNX, PCE, SMS, GG, NES and
their backup units
Copyright (c) 1999 - 2004 NoisyB <noisyb@gmx.net>
Copyright (c) 2001 - 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.
*/
/*
First I want to thank SiGMA SEVEN! who was my mentor and taught me how to
write programs in C.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
#include <stddef.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif
#include <sys/stat.h>
#include <sys/types.h>
#ifdef _WIN32
#include <windows.h>
#endif
#ifdef DEBUG
#ifdef __GNUC__
#warning DEBUG active
#else
#pragma message ("DEBUG active")
#endif
#endif
#ifdef USE_PARALLEL
#include "misc/parallel.h"
#endif
#include "misc/bswap.h"
#include "misc/misc.h"
#include "misc/property.h"
#include "misc/chksum.h"
#include "misc/file.h"
#ifdef USE_ZLIB
#include "misc/archive.h"
#endif
#include "misc/getopt2.h" // st_getopt2_t
#include "misc/string.h"
#include "ucon64.h"
#include "ucon64_misc.h"
#include "ucon64_opts.h"
#include "ucon64_dat.h"
#include "console/console.h"
#include "patch/patch.h"
#include "backup/backup.h"
static void ucon64_exit (void);
static int ucon64_execute_options (void);
static void ucon64_rom_nfo (const st_rominfo_t *rominfo);
static st_rominfo_t *ucon64_probe (st_rominfo_t *rominfo);
static int ucon64_rom_handling (void);
static int ucon64_process_rom (char *fname);
st_ucon64_t ucon64; // containes ptr to image, dat and rominfo
static const char *ucon64_title = "uCON64 " UCON64_VERSION_S " " CURRENT_OS_S " 1999-2005";
#ifdef AMIGA
unsigned long __stacksize = 102400; // doesn't work on PPC? is StormC specific?
//unsigned long __stack = 102400; // for SAS/C, DICE, GCC etc.?
char vers[] = "$VER: uCON64 "UCON64_VERSION_S" "CURRENT_OS_S" ("__DATE__") ("__TIME__")";
#endif
typedef struct
{
int val; // (st_getopt2_t->val)
// const
char *optarg; // option argument
int console; // the console (st_getopt2_t->object)
int flags; // workflow flags (st_getopt2_t->object)
} st_args_t;
static st_args_t arg[UCON64_MAX_ARGS];
static st_getopt2_t options[UCON64_MAX_ARGS];
static const st_getopt2_t lf[] =
{
{NULL, 0, 0, 0, NULL, "", NULL},
{NULL, 0, 0, 0, NULL, NULL, NULL}
},
*option[] =
{
ucon64_options_usage,
ucon64_options_without_usage,
lf,
ucon64_padding_usage,
lf,
ucon64_dat_usage,
lf,
ucon64_patching_usage,
bsl_usage,
ips_usage,
aps_usage,
pal4u_usage,
ppf_usage,
xps_usage,
gg_usage,
lf,
#ifdef USE_DISCMAGE
libdm_usage,
lf,
#endif
dc_usage,
lf,
psx_usage,
#ifdef USE_PARALLEL
dex_usage,
#endif
lf,
gba_usage,
#if defined USE_PARALLEL || defined USE_USB
// f2a_usage has to come before fal_usage in case only USE_USB is defined
// (no support for parallel port)
f2a_usage,
#ifdef USE_PARALLEL
fal_usage,
#endif
#endif // USE_PARALLEL || USE_USB
lf,
n64_usage,
#ifdef USE_PARALLEL
doctor64_usage,
doctor64jr_usage,
#ifdef USE_LIBCD64
cd64_usage,
#endif
dex_usage,
#endif // USE_PARALLEL
lf,
snes_usage,
#ifdef USE_PARALLEL
swc_usage,
gd_usage,
fig_usage,
sflash_usage,
// mgd_usage,
#endif
#ifdef USE_USB
quickdev16_usage,
#endif
lf,
neogeo_usage,
lf,
genesis_usage,
#ifdef USE_PARALLEL
smd_usage,
mdpro_usage,
mcd_usage,
cmc_usage,
// mgd_usage,
#endif
lf,
gameboy_usage,
#ifdef USE_PARALLEL
gbx_usage,
mccl_usage,
#endif
lf,
lynx_usage,
#ifdef USE_PARALLEL
lynxit_usage,
#endif
lf,
pcengine_usage,
#ifdef USE_PARALLEL
msg_usage,
pcepro_usage,
// mgd_usage,
#endif
lf,
nes_usage,
#ifdef USE_PARALLEL
smc_usage,
#endif
lf,
sms_usage,
#ifdef USE_PARALLEL
smsggpro_usage,
#endif
lf,
swan_usage,
lf,
jaguar_usage,
lf,
ngp_usage,
#ifdef USE_PARALLEL
pl_usage,
#endif
lf,
atari_usage,
NULL
};
static st_rominfo_t *
ucon64_rom_flush (st_rominfo_t * rominfo)
{
if (rominfo)
memset (rominfo, 0, sizeof (st_rominfo_t));
ucon64.rominfo = NULL;
ucon64.crc32 = ucon64.fcrc32 = 0; // yes, this belongs here
rominfo->data_size = UCON64_UNKNOWN;
return rominfo;
}
#ifdef DEBUG
void
ucon64_runtime_debug_output (st_getopt2_t *p)
{
printf ("{\"%s\", %d, 0, %d, \"%s\", \"%s\", %d}, // console: %d workflow: %d\n",
p->name,
p->has_arg,
p->val,
p->arg_name,
p->help ? "usage" : p->help, // i (nb) mean it
// p->help,
0,
p->object ? ((st_ucon64_obj_t *) p->object)->console : 0,
p->object ? ((st_ucon64_obj_t *) p->object)->flags : 0);
}
static void
ucon64_runtime_debug (void)
{
int x = 0, y = 0, c = 0;
(void) x;
(void) y;
(void) c;
#if 0
// how many options (incl. dupes) do we have?
for (x = y = 0; options[x].name || options[x].help; x++)
if (options[x].name)
y++;
printf ("DEBUG: Total options (with dupes): %d\n", y);
printf ("DEBUG: UCON64_MAX_ARGS == %d, %s\n", UCON64_MAX_ARGS,
(y < UCON64_MAX_ARGS ? "good" : "\nERROR: too small; must be larger than options"));
#endif
#if 1
// list all options as a single st_getopt2_t array
for (x = 0; options[x].name || options[x].help; x++)
if (options[x].name)
ucon64_runtime_debug_output ((st_getopt2_t *) &options[x]);
#endif
#if 0
// how many consoles does uCON64 support?
for (x = y = 0; options[x].name || options[x].help; x++)
if (options[x].name && options[x].object)
if (options[x].val == ((st_ucon64_obj_t *) options[x].object)->console)
ucon64_runtime_debug_output ((st_getopt2_t *) &options[x]);
#endif
#if 0
// find options without an object (allowed)
for (x = 0; options[x].name || options[x].help; x++)
if (options[x].name && !options[x].object)
ucon64_runtime_debug_output ((st_getopt2_t *) &options[x]);
#endif
#if 0
// find options without a console (allowed)
for (x = 0; options[x].name || options[x].help; x++)
if (options[x].name && !((st_ucon64_obj_t *) options[x].object)->console)
ucon64_runtime_debug_output ((st_getopt2_t *) &options[x]);
#endif
#if 0
// find options without a workflow (allowed)
for (x = 0; options[x].name || options[x].help; x++)
if (options[x].name && !((st_ucon64_obj_t *) options[x].object)->flags)
ucon64_runtime_debug_output ((st_getopt2_t *) &options[x]);
#endif
#if 0
// find options without a val (NOT allowed)
for (x = 0; options[x].name || options[x].help; x++)
if (options[x].name && !options[x].val)
ucon64_runtime_debug_output ((st_getopt2_t *) &options[x]);
#endif
#if 0
// find options with has_arg but without arg_name AND/OR usage
// hidden options without arg_name AND usage are allowed
for (x = 0; options[x].name || options[x].help; x++)
if (options[x].name &&
((!options[x].has_arg && options[x].arg_name) ||
(options[x].has_arg && !options[x].arg_name) ||
!options[x].help))
ucon64_runtime_debug_output ((st_getopt2_t *) &options[x]);
#endif
#if 0
// find dupe (NOT a problem) options that have different values for val,
// flag, and/or object (NOT allowed)
// getopt1() will always use the 1st option in the array
// (st_getopt2_t *)->arg_name and (st_getopt2_t *)->help can be as
// different as you like
for (x = 0; options[x].name || options[x].help; x++)
if (options[x].name)
for (y = 0; options[y].name || options[y].help; y++)
if (options[y].name && x != y) // IS option
if (!strcmp (options[y].name, options[x].name))
if (options[y].has_arg != options[x].has_arg || // (NOT allowed)
options[y].flag != options[x].flag || // (NOT allowed)
options[y].val != options[x].val || // (NOT allowed)
// options[y].arg_name != options[x].arg_name || // (allowed)
// options[y].help != options[x].help || // (allowed)
((st_ucon64_obj_t *) options[y].object)->console != ((st_ucon64_obj_t *) options[x].object)->console // (NOT allowed)
((st_ucon64_obj_t *) options[x].object)->flags != ((st_ucon64_obj_t *) options[x].object)->flags) // (NOT allowed)
{
fputs ("ERROR: different dupe options found\n ", stdout);
ucon64_runtime_debug_output ((st_getopt2_t *) &options[x]);
fputs (" ", stdout);
ucon64_runtime_debug_output ((st_getopt2_t *) &options[y]);
fputs ("\n\n", stdout);
}
#endif
puts ("DEBUG: Sanity check finished");
fflush (stdout);
}
#endif // DEBUG
void
ucon64_exit (void)
{
#ifdef USE_DISCMAGE
if (ucon64.discmage_enabled)
if (ucon64.image)
dm_close ((dm_image_t *) ucon64.image);
#endif
handle_registered_funcs ();
fflush (stdout);
}
int
main (int argc, char **argv)
{
int x = 0, y = 0, rom_index = 0, c = 0;
#if (FILENAME_MAX < MAXBUFSIZE)
static char buf[MAXBUFSIZE];
#else
static char buf[FILENAME_MAX];
#endif
struct stat fstate;
struct option long_options[UCON64_MAX_ARGS];
printf ("%s\n"
"Uses code from various people. See 'developers.html' for more!\n"
"This may be freely redistributed under the terms of the GNU Public License\n\n",
ucon64_title);
if (atexit (ucon64_exit) == -1)
{
fputs ("ERROR: Could not register function with atexit()\n", stderr);
exit (1);
}
// flush st_ucon64_t
memset (&ucon64, 0, sizeof (st_ucon64_t));
ucon64.rom =
ucon64.file =
ucon64.mapr =
ucon64.comment = "";
ucon64.parport_needed = 0;
ucon64.flags = WF_DEFAULT;
ucon64.fname_arch[0] = 0;
ucon64.argc = argc;
ucon64.argv = argv; // must be set prior to calling
// ucon64_load_discmage() (for DOS)
// convert (st_getopt2_t **) to (st_getopt2_t *)
memset (&options, 0, sizeof (st_getopt2_t) * UCON64_MAX_ARGS);
for (c = x = 0; option[x]; x++)
for (y = 0; option[x][y].name || option[x][y].help; y++)
if (c < UCON64_MAX_ARGS)
{
memcpy (&options[c], &option[x][y], sizeof (st_getopt2_t));
c++;
}
ucon64.options = options;
#ifdef DEBUG
ucon64_runtime_debug (); // check (st_getopt2_t *) options consistency
#endif
#ifdef __unix__
// We need to modify the umask, because the configfile is made while we are
// still running in root mode. Maybe 0 is even better (in case root did
// `chmod +s').
umask (002);
#endif
ucon64_configfile ();
#ifdef USE_ANSI_COLOR
// ANSI colors?
ucon64.ansi_color = get_property_int (ucon64.configfile, "ansi_color");
// the conditional call to ansi_init() has to be done *after* the check for
// the switch -ncol
#endif
// parallel port?
#ifdef USE_PPDEV
get_property (ucon64.configfile, "parport_dev", ucon64.parport_dev, "/dev/parport0");
#elif defined AMIGA
get_property (ucon64.configfile, "parport_dev", ucon64.parport_dev, "parallel.device");
#endif
// use -1 (UCON64_UNKNOWN) to force probing if the config file doesn't contain
// a parport line
sscanf (get_property (ucon64.configfile, "parport", buf, "-1"), "%x", &ucon64.parport);
// make backups?
ucon64.backup = get_property_int (ucon64.configfile, "backups");
// $HOME/.ucon64/ ?
get_property_fname (ucon64.configfile, "ucon64_configdir", ucon64.configdir, "");
// DAT file handling
ucon64.dat_enabled = 0;
get_property_fname (ucon64.configfile, "ucon64_datdir", ucon64.datdir, "");
// we use ucon64.datdir as path to the dats
if (!access (ucon64.datdir,
// !W_OK doesn't mean that files can't be written to dir for Win32 exe's
#if !defined __CYGWIN__ && !defined _WIN32
W_OK |
#endif
R_OK | X_OK))
if (!stat (ucon64.datdir, &fstate))
if (S_ISDIR (fstate.st_mode))
ucon64.dat_enabled = 1;
if (!ucon64.dat_enabled)
if (!access (ucon64.configdir,
#if !defined __CYGWIN__ && !defined _WIN32
W_OK |
#endif
R_OK | X_OK))
if (!stat (ucon64.configdir, &fstate))
if (S_ISDIR (fstate.st_mode))
{
// fprintf (stderr, "Please move your DAT files from %s to %s\n\n", ucon64.configdir, ucon64.datdir);
strcpy (ucon64.datdir, ucon64.configdir); // use .ucon64/ instead of .ucon64/dat/
ucon64.dat_enabled = 1;
}
if (ucon64.dat_enabled)
ucon64_dat_indexer (); // update cache (index) files if necessary
#ifdef USE_DISCMAGE
// load libdiscmage
ucon64.discmage_enabled = ucon64_load_discmage ();
#endif
if (argc < 2)
{
ucon64_usage (argc, argv);
return 0;
}
// turn st_getopt2_t into struct option
getopt2_long_only (long_options, options, UCON64_MAX_ARGS);
// getopt() is utilized to make uCON64 handle/parse cmdlines in a sane
// and expected way
x = optind = 0;
memset (&arg, 0, sizeof (st_args_t) * UCON64_MAX_ARGS);
while ((c = getopt_long_only (argc, argv, "", long_options, NULL)) != -1)
{
if (c == '?') // getopt() returns 0x3f ('?') when an unknown option was given
{
fprintf (stderr,
"Try '%s " OPTION_LONG_S "help' for more information.\n",
argv[0]);
exit (1);
}
if (x < UCON64_MAX_ARGS)
{
const st_ucon64_obj_t *p = (st_ucon64_obj_t *) getopt2_get_index_by_val (options, c)->object;
arg[x].console = UCON64_UNKNOWN; // default
if (p)
{
arg[x].flags = p->flags;
if (p->console)
arg[x].console = p->console;
}
arg[x].val = c;
arg[x++].optarg = (optarg ? optarg : NULL);
}
else
// this shouldn't happen
exit (1);
}
#ifdef DEBUG
for (x = 0; arg[x].val; x++)
printf ("%d %s %d %d\n\n",
arg[x].val,
arg[x].optarg ? arg[x].optarg : "(null)",
arg[x].flags,
arg[x].console);
#endif
rom_index = optind; // save index of first file
if (rom_index == argc)
ucon64_execute_options();
else
for (; rom_index < argc; rom_index++)
{
int result = 0;
char buf2[FILENAME_MAX];
#ifndef _WIN32
struct dirent *ep;
DIR *dp;
#else
char search_pattern[FILENAME_MAX];
WIN32_FIND_DATA find_data;
HANDLE dp;
#endif
realpath2 (argv[rom_index], buf);
if (stat (buf, &fstate) != -1)
{
if (S_ISREG (fstate.st_mode))
result = ucon64_process_rom (buf);
else if (S_ISDIR (fstate.st_mode)) // a dir?
{
char *p;
#if defined __MSDOS__ || defined _WIN32 || defined __CYGWIN__
/*
Note that this code doesn't make much sense for Cygwin,
because at least the version I use (1.3.6, dbjh) doesn't
support current directories for drives.
*/
c = toupper (buf[0]);
if (buf[strlen (buf) - 1] == FILE_SEPARATOR ||
(c >= 'A' && c <= 'Z' && buf[1] == ':' && buf[2] == 0))
#else
if (buf[strlen (buf) - 1] == FILE_SEPARATOR)
#endif
p = "";
else
p = FILE_SEPARATOR_S;
#ifndef _WIN32
if ((dp = opendir (buf)))
{
while ((ep = readdir (dp)))
{
sprintf (buf2, "%s%s%s", buf, p, ep->d_name);
if (stat (buf2, &fstate) != -1)
if (S_ISREG (fstate.st_mode))
{
result = ucon64_process_rom (buf2);
if (result == 1)
break;
}
}
closedir (dp);
}
#else
sprintf (search_pattern, "%s%s*", buf, p);
if ((dp = FindFirstFile (search_pattern, &find_data)) != INVALID_HANDLE_VALUE)
{
do
{
sprintf (buf2, "%s%s%s", buf, p, find_data.cFileName);
if (stat (buf2, &fstate) != -1)
if (S_ISREG (fstate.st_mode))
{
result = ucon64_process_rom (buf2);
if (result == 1)
break;
}
}
while (FindNextFile (dp, &find_data));
FindClose (dp);
}
#endif
}
else
result = ucon64_process_rom (buf);
}
else
result = ucon64_process_rom (buf);
if (result == 1)
break;
}
return 0;
}
int
ucon64_process_rom (char *fname)
{
#ifdef USE_ZLIB
int n_entries = unzip_get_number_entries (fname);
if (n_entries != -1) // it's a zip file
{
for (unzip_current_file_nr = 0; unzip_current_file_nr < n_entries;
unzip_current_file_nr++)
{
ucon64_fname_arch (fname);
/*
There seems to be no other way to detect directories in ZIP files
than by looking at the file name. Paths in ZIP files should contain
forward slashes. ucon64_fname_arch() changes forward slashes into
backslashes (FILE_SEPARATORs) when uCON64 is compiled with Visual
C++ or MinGW so that basename2() always produces a correct base
name. So, if the entry in the ZIP file is a directory
ucon64.fname_arch will be an empty string.
*/
if (ucon64.fname_arch[0] == 0)
continue;
ucon64.rom = fname;
ucon64_execute_options();
if (ucon64.flags & WF_STOP)
break;
}
unzip_current_file_nr = 0;
ucon64.fname_arch[0] = 0;
if (ucon64.flags & WF_STOP)
return 1;
}
else
#endif
{
ucon64.rom = fname;
ucon64_execute_options();
if (ucon64.flags & WF_STOP)
return 1;
}
return 0;
}
int
ucon64_execute_options (void)
/*
Execute all options for a single file.
Please, if you experience problems then try your luck with the flags
in ucon64_misc.c/ucon64_wf[] before changing things here or in
ucon64_rom_handling()
*/
{
int c = 0, result = 0, x = 0, opts = 0;
static int first_call = 1; // first call to this function
ucon64.dat = NULL;
#ifdef USE_DISCMAGE
ucon64.image = NULL;
#endif
ucon64.rominfo = NULL;
ucon64.battery =
ucon64.bs_dump =
ucon64.buheader_len =
ucon64.console =
ucon64.controller =
ucon64.controller2 =
ucon64.do_not_calc_crc =
ucon64.id =
ucon64.interleaved =
ucon64.mirror =
ucon64.part_size =
ucon64.region =
ucon64.snes_header_base =
ucon64.snes_hirom =
ucon64.split =
ucon64.tv_standard =
ucon64.use_dump_info =
ucon64.vram = UCON64_UNKNOWN;
ucon64.file_size =
ucon64.crc32 =
ucon64.fcrc32 =
ucon64.io_mode = 0;
// switches
for (x = 0; arg[x].val; x++)
{
if (arg[x].console != UCON64_UNKNOWN)
ucon64.console = arg[x].console;
if (arg[x].flags)
ucon64.flags = arg[x].flags;
if (arg[x].val)
ucon64.option = arg[x].val;
ucon64.optarg = arg[x].optarg;
// if (ucon64.flags & WF_SWITCH)
ucon64_switches (&ucon64);
}
#ifdef USE_ANSI_COLOR
if (ucon64.ansi_color && first_call)
ucon64.ansi_color = ansi_init ();
#endif
#ifdef USE_PARALLEL
/*
The copier options need root privileges for parport_open()
We can't use ucon64.flags & WF_PAR to detect whether a (parallel port)
copier option has been specified, because another switch might've been
specified after -port.
*/
if (ucon64.parport_needed == 1)
ucon64.parport = parport_open (ucon64.parport);
#endif // USE_PARALLEL
#if defined __unix__ && !defined __MSDOS__
/*
We can drop privileges after we have set up parallel port access. We cannot
drop privileges if the user wants to communicate with the USB version of the
F2A.
SECURITY WARNING: We stay in root mode if the user specified an F2A option!
We could of course drop privileges which requires the user to run uCON64 as
root (not setuid root), but we want to be user friendly. Besides, doing
things as root is bad anyway (from a security viewpoint).
*/
if (first_call && ucon64.parport_needed != 2
#ifdef USE_USB
&& !ucon64.usbport
#endif
)
drop_privileges ();
#endif // __unix__ && !__MSDOS__
first_call = 0;
for (x = 0; arg[x].val; x++)
if (!(arg[x].flags & WF_SWITCH))
{
if (ucon64.console == UCON64_UNKNOWN)
ucon64.console = arg[x].console;
ucon64.flags = arg[x].flags;
ucon64.option = arg[x].val;
ucon64.optarg = arg[x].optarg;
opts++;
// WF_NO_SPLIT, WF_INIT, WF_PROBE, CRC32, DATabase and WF_NFO
result = ucon64_rom_handling ();
if (result == -1) // no rom, but WF_NO_ROM
return -1;
if (ucon64_options (&ucon64) == -1)
{
const st_getopt2_t *p = getopt2_get_index_by_val (options, c);
const char *opt = p ? p->name : NULL;
fprintf (stderr, "ERROR: %s%s encountered a problem\n",
opt ? (!opt[1] ? OPTION_S : OPTION_LONG_S) : "",
opt ? opt : "uCON64");
// if (p)
// getopt2_usage (p);
fputs (" Is the option you used available for the current console system?\n"
" Please report bugs to noisyb@gmx.net or ucon64-announce@lists.sf.net\n\n",
stderr);
return -1;
}
#if 0
// WF_NFO_AFTER?!
if (!result && (ucon64.flags & WF_NFO_AFTER) && ucon64.quiet < 1)
ucon64_rom_handling ();
#endif
/*
"stop" options:
- -multi (and -xfalmulti) takes more than one file as argument, but
should be executed only once.
- stop after sending one ROM to a copier ("multizip")
- stop after applying a patch so that the patch file won't be
interpreted as ROM
*/
if (ucon64.flags & WF_STOP)
break;
}
if (!opts) // no options => just display ROM info
{
ucon64.flags = WF_DEFAULT;
// WF_NO_SPLIT WF_INIT, WF_PROBE, CRC32, DATabase and WF_NFO
if (ucon64_rom_handling () == -1)
return -1; // no rom, but WF_NO_ROM
}
fflush (stdout);
return 0;
}
int
ucon64_rom_handling (void)
{
int no_rom = 0;
static st_rominfo_t rominfo;
struct stat fstate;
ucon64_rom_flush (&rominfo);
// a ROM (file)?
if (!ucon64.rom)
no_rom = 1;
else if (!ucon64.rom[0])
no_rom = 1;
else if (access (ucon64.rom, F_OK | R_OK) == -1 && (!(ucon64.flags & WF_NO_ROM)))
{
fprintf (stderr, "ERROR: Could not open %s\n", ucon64.rom);
no_rom = 1;
}
else if (stat (ucon64.rom, &fstate) == -1)
no_rom = 1;
else if (S_ISREG (fstate.st_mode) != TRUE)
no_rom = 1;
#if 0
// printing the no_rom error message for files of 0 bytes only confuses people
else if (!fstate.st_size)
no_rom = 1;
#endif
if (no_rom)
{
if (!(ucon64.flags & WF_NO_ROM))
{
fputs ("ERROR: This option requires a file argument (ROM/image/SRAM file/directory)\n", stderr);
return -1;
}
return 0;
}
// The next statement is important and should be executed as soon as
// possible (and sensible) in this function
ucon64.file_size = fsizeof (ucon64.rom);
// We have to do this here, because we don't know the file size until now
if (ucon64.buheader_len > ucon64.file_size)
{
fprintf (stderr,
"ERROR: A backup unit header length was specified that is larger than the file\n"
" size (%d > %d)\n", ucon64.buheader_len, ucon64.file_size);
return -1;
}
if (!(ucon64.flags & WF_INIT))
return 0;
// "walk through" <console>_init()
if (ucon64.flags & WF_PROBE)
{
if (ucon64.rominfo)
{
// Restore any overrides from st_ucon64_t
// We have to do this *before* calling ucon64_probe(), *not* afterwards
if (UCON64_ISSET (ucon64.buheader_len))
rominfo.buheader_len = ucon64.buheader_len;
if (UCON64_ISSET (ucon64.interleaved))
rominfo.interleaved = ucon64.interleaved;
// ucon64.rominfo = (st_rominfo_t *) &rominfo;
}
ucon64.rominfo = ucon64_probe (&rominfo); // determines console type
#ifdef USE_DISCMAGE
// check for disc image only if ucon64_probe() failed or --disc was used
if (ucon64.discmage_enabled)
// if (!ucon64.rominfo || ucon64.force_disc)
if (ucon64.force_disc)
ucon64.image = dm_reopen (ucon64.rom, 0, (dm_image_t *) ucon64.image);
#endif
}
// end of WF_PROBE
// Does the option allow split ROMs?
if (ucon64.flags & WF_NO_SPLIT)
/*
Test for split files only if the console type knows about split files at
all. However we only know the console type after probing.
*/
if (ucon64.console == UCON64_NES || ucon64.console == UCON64_SNES ||
ucon64.console == UCON64_GEN || ucon64.console == UCON64_NG)
if ((UCON64_ISSET (ucon64.split)) ? ucon64.split : ucon64_testsplit (ucon64.rom))
{
fprintf (stderr, "ERROR: %s seems to be split. You have to join it first\n",
basename2 (ucon64.rom));
return -1;
}
/*
CRC32
Calculating the CRC32 checksum for the ROM data of a UNIF file (NES)
shouldn't be done with ucon64_fcrc32(). nes_init() uses crc32().
The CRC32 checksum is used to search in the DAT files, but at the time
of this writing (Februari the 7th 2003) all DAT files contain checksums
of files in only one format. This matters for SNES and Genesis ROMs in
interleaved format and Nintendo 64 ROMs in non-interleaved format. The
corresponding initialization functions calculate the CRC32 checksum of
the data in the format of which the checksum is stored in the DAT
files. For these "problematic" files, their "real" checksum is stored
in ucon64.fcrc32.
*/
if (ucon64.crc32 == 0)
if (!ucon64.force_disc) // NOT for disc images
if (!(ucon64.flags & WF_NO_CRC32) && ucon64.file_size <= MAXROMSIZE)
ucon64_chksum (NULL, NULL, &ucon64.crc32, ucon64.rom, ucon64.rominfo ? ucon64.rominfo->buheader_len : 0);
// DATabase
ucon64.dat = NULL;
if (ucon64.crc32 != 0 && ucon64.dat_enabled)
{
ucon64.dat = ucon64_dat_search (ucon64.crc32, NULL);
if (ucon64.dat)
{
// detected file size must match DAT file size
int size = ucon64.rominfo ?
UCON64_ISSET (ucon64.rominfo->data_size) ?
ucon64.rominfo->data_size :
ucon64.file_size - ucon64.rominfo->buheader_len :
ucon64.file_size;
if ((int) (((st_ucon64_dat_t *) ucon64.dat)->fsize) != size)
ucon64.dat = NULL;
}
if (ucon64.dat)
switch (ucon64.console)
{
case UCON64_SNES:
case UCON64_GEN:
case UCON64_GB:
case UCON64_GBA:
case UCON64_N64:
// These ROMs have internal headers with name, country, maker, etc.
break;
default:
// Use ucon64.dat instead of ucon64.dat_enabled in case the index
// file could not be created/opened -> no segmentation fault
if (ucon64.dat && ucon64.rominfo)
{
if (!ucon64.rominfo->name[0])
strcpy (ucon64.rominfo->name, NULL_TO_EMPTY (((st_ucon64_dat_t *) ucon64.dat)->name));
else if (ucon64.console == UCON64_NES)
{ // override the three-character FDS or FAM name
int t = nes_get_file_type ();
if (t == FDS || t == FAM)
strcpy (ucon64.rominfo->name, NULL_TO_EMPTY (((st_ucon64_dat_t *) ucon64.dat)->name));
}
if (!ucon64.rominfo->country)
ucon64.rominfo->country = NULL_TO_EMPTY (((st_ucon64_dat_t *) ucon64.dat)->country);
}
break;
}
}
// display info
if ((ucon64.flags & WF_NFO || ucon64.flags & WF_NFO_AFTER) && ucon64.quiet < 1)
ucon64_nfo ();
return 0;
}
st_rominfo_t *
ucon64_probe (st_rominfo_t * rominfo)
{
typedef struct
{
int console;
int (*init) (st_rominfo_t *);
uint32_t flags;
} st_probe_t;
// auto recognition
#define AUTO (1)
int x = 0;
st_probe_t probe[] =
{
/*
The order of the init functions is important. snes_init() must be
called before nes_init(), but after gameboy_init() and sms_init().
sms_init() must be called before snes_init(), but after genesis_init().
There may be more dependencies, so don't change the order unless you
can verify it won't break anything.
*/
{UCON64_GBA, gba_init, AUTO},
{UCON64_N64, n64_init, AUTO},
{UCON64_GEN, genesis_init, AUTO},
{UCON64_LYNX, lynx_init, AUTO},
{UCON64_GB, gameboy_init, AUTO},
{UCON64_SMS, sms_init, AUTO},
{UCON64_SNES, snes_init, AUTO},
{UCON64_NES, nes_init, AUTO},
{UCON64_NGP, ngp_init, AUTO},
{UCON64_SWAN, swan_init, AUTO},
{UCON64_JAG, jaguar_init, AUTO},
{UCON64_PCE, pcengine_init, AUTO},
{UCON64_NG, neogeo_init, 0},
{UCON64_SWAN, swan_init, 0},
{UCON64_DC, dc_init, 0},
{UCON64_PSX, psx_init, 0},
#if 0
{UCON64_GC, NULL, 0},
{UCON64_GP32, NULL, 0},
{UCON64_COLECO, NULL, 0},
{UCON64_INTELLI, NULL, 0},
{UCON64_S16, NULL, 0},
{UCON64_ATA, NULL, 0},
{UCON64_VEC, NULL, 0},
{UCON64_VBOY, NULL, 0},
#endif
{UCON64_UNKNOWN, unknown_init, 0},
{0, NULL, 0}
};
if (ucon64.console != UCON64_UNKNOWN) // force recognition option was used
{
for (x = 0; probe[x].console != 0; x++)
if (probe[x].console == ucon64.console)
{
ucon64_rom_flush (rominfo);
probe[x].init (rominfo);
return rominfo;
}
}
else if (ucon64.file_size <= MAXROMSIZE) // give auto_recognition a try
{
for (x = 0; probe[x].console != 0; x++)
if (probe[x].flags & AUTO)
{
ucon64_rom_flush (rominfo);
if (!probe[x].init (rominfo))
{
ucon64.console = probe[x].console;
return rominfo;
}
}
}
return NULL;
}
int
ucon64_nfo (void)
{
puts (ucon64.rom);
if (ucon64.fname_arch[0])
printf (" (%s)\n", ucon64.fname_arch);
fputc ('\n', stdout);
#ifdef USE_DISCMAGE
if (ucon64.console == UCON64_UNKNOWN && !ucon64.image)
#else
if (ucon64.console == UCON64_UNKNOWN)
#endif
fprintf (stderr, "%s\n", ucon64_msg[CONSOLE_ERROR]);
if (ucon64.rominfo && ucon64.console != UCON64_UNKNOWN && !ucon64.force_disc)
ucon64_rom_nfo (ucon64.rominfo);
#ifdef USE_DISCMAGE
if (ucon64.discmage_enabled)
if (ucon64.image)
{
dm_nfo ((dm_image_t *) ucon64.image, ucon64.quiet < 0 ? 1 : 0,
#ifdef USE_ANSI_COLOR
ucon64.ansi_color ? 1 :
#endif
0);
fputc ('\n', stdout);
return 0; // no crc calc. for disc images and therefore no dat entry either
}
#endif
// Use ucon64.fcrc32 for SNES, Genesis & SMS interleaved/N64 non-interleaved
if (ucon64.fcrc32 && ucon64.crc32)
printf ("Search checksum (CRC32): 0x%08x\n"
"Data checksum (CRC32): 0x%08x\n", ucon64.crc32, ucon64.fcrc32);
else if (ucon64.fcrc32 || ucon64.crc32)
printf ("Checksum (CRC32): 0x%08x\n", ucon64.fcrc32 ? ucon64.fcrc32 : ucon64.crc32);
// The check for the size of the file is made, so that uCON64 won't display a
// (nonsense) DAT info line when dumping a ROM (file doesn't exist, so
// ucon64.file_size is 0).
if (ucon64.file_size > 0 && ucon64.dat_enabled)
if (ucon64.dat)
ucon64_dat_nfo ((st_ucon64_dat_t *) ucon64.dat, 1);
fputc ('\n', stdout);
return 0;
}
static inline char *
to_func (char *s, int len, int (*func) (int))
{
char *p = s;
for (; len > 0; p++, len--)
*p = func (*p);
return s;
}
static inline int
toprint (int c)
{
if (isprint (c))
return c;
// characters that also work with printf()
#ifdef USE_ANSI_COLOR
if (c == '\x1b')
return ucon64.ansi_color ? c : '.';
#endif
return strchr ("\t\n\r", c) ? c : '.';
}
void
ucon64_rom_nfo (const st_rominfo_t *rominfo)
{
unsigned int padded = ucon64_testpad (ucon64.rom),
intro = ((ucon64.file_size - rominfo->buheader_len) > MBIT) ?
((ucon64.file_size - rominfo->buheader_len) % MBIT) : 0;
int x, split = (UCON64_ISSET (ucon64.split)) ? ucon64.split :
ucon64_testsplit (ucon64.rom);
char buf[MAXBUFSIZE];
// backup unit header
if (rominfo->buheader && rominfo->buheader_len && rominfo->buheader_len != UNKNOWN_HEADER_LEN)
{
dumper (stdout, rominfo->buheader, rominfo->buheader_len, rominfo->buheader_start, DUMPER_HEX);
fputc ('\n', stdout);
}
else
if (rominfo->buheader_len && ucon64.quiet < 0)
{
ucon64_dump (stdout, ucon64.rom, rominfo->buheader_start, rominfo->buheader_len, DUMPER_HEX);
fputc ('\n', stdout);
}
// backup unit type?
if (rominfo->copier_usage != NULL)
{
puts (rominfo->copier_usage);
fputc ('\n', stdout);
}
// ROM header
if (rominfo->header && rominfo->header_len)
{
dumper (stdout, rominfo->header, rominfo->header_len,
rominfo->header_start + rominfo->buheader_len, DUMPER_HEX);
fputc ('\n', stdout);
}
// console type
if (rominfo->console_usage != NULL)
puts (rominfo->console_usage);
// name, maker, country and size
strcpy (buf, NULL_TO_EMPTY (rominfo->name));
x = UCON64_ISSET (rominfo->data_size) ?
rominfo->data_size :
ucon64.file_size - rominfo->buheader_len;
printf ("%s\n%s\n%s\n%d Bytes (%.4f Mb)\n\n",
// some ROMs have a name with control chars in it -> replace control chars
to_func (buf, strlen (buf), toprint),
NULL_TO_EMPTY (rominfo->maker),
NULL_TO_EMPTY (rominfo->country),
x,
TOMBIT_F (x));
// padded?
if (!padded)
puts ("Padded: No");
else
printf ("Padded: Maybe, %d Bytes (%.4f Mb)\n", padded, TOMBIT_F (padded));
// intro, trainer?
// nes.c determines itself whether or not there is a trainer
if (intro && ucon64.console != UCON64_NES)
printf ("Intro/Trainer: Maybe, %d Bytes\n", intro);
// interleaved?
if (rominfo->interleaved != UCON64_UNKNOWN)
// printing this is handy for SNES, N64 & Genesis ROMs, but maybe
// nonsense for others
printf ("Interleaved/Swapped: %s\n",
rominfo->interleaved ?
(rominfo->interleaved > 1 ? "Yes (2)" : "Yes") :
"No");
// backup unit header?
if (rominfo->buheader_len)
printf ("Backup unit/emulator header: Yes, %d Bytes\n",
rominfo->buheader_len);
else
// for NoisyB: <read only mode ON>
puts ("Backup unit/emulator header: No"); // printing No is handy for SNES ROMs
// for NoisyB: <read only mode OFF>
// split?
if (split)
{
printf ("Split: Yes, %d part%s\n", split, (split != 1) ? "s" : "");
// nes.c calculates the correct checksum for split ROMs (=Pasofami
// format), so there is no need to join the files
if (ucon64.console != UCON64_NES)
puts ("NOTE: To get the correct checksum the ROM parts must be joined");
}
// miscellaneous info
if (rominfo->misc[0])
{
strcpy (buf, rominfo->misc);
printf ("%s\n", to_func (buf, strlen (buf), toprint));
}
// internal checksums?
if (rominfo->has_internal_crc)
{
char *fstr;
// the internal checksum of GBA ROMS stores only the checksum of the
// internal header
if (ucon64.console != UCON64_GBA)
fstr = "Checksum: %%s, 0x%%0%dlx (calculated) %%c= 0x%%0%dlx (internal)\n";
else
fstr = "Header checksum: %%s, 0x%%0%dlx (calculated) %%c= 0x%%0%dlx (internal)\n";
sprintf (buf, fstr,
rominfo->internal_crc_len * 2, rominfo->internal_crc_len * 2);
#ifdef USE_ANSI_COLOR
printf (buf,
ucon64.ansi_color ?
((rominfo->current_internal_crc == rominfo->internal_crc) ?
"\x1b[01;32mOk\x1b[0m" : "\x1b[01;31mBad\x1b[0m")
:
((rominfo->current_internal_crc == rominfo->internal_crc) ? "Ok" : "Bad"),
rominfo->current_internal_crc,
(rominfo->current_internal_crc == rominfo->internal_crc) ? '=' : '!',
rominfo->internal_crc);
#else
printf (buf,
(rominfo->current_internal_crc == rominfo->internal_crc) ? "Ok" : "Bad",
rominfo->current_internal_crc,
(rominfo->current_internal_crc == rominfo->internal_crc) ? '=' : '!',
rominfo->internal_crc);
#endif
if (rominfo->internal_crc2[0])
printf ("%s\n", rominfo->internal_crc2);
}
fflush (stdout);
}
#ifdef USE_ZLIB
void
ucon64_fname_arch (const char *fname)
{
char name[FILENAME_MAX];
unzFile file = unzOpen (fname);
unzip_goto_file (file, unzip_current_file_nr);
unzGetCurrentFileInfo (file, NULL, name, FILENAME_MAX, NULL, 0, NULL, 0);
unzClose (file);
#if defined _WIN32 || defined __MSDOS__
{
int n, l = strlen (name);
for (n = 0; n < l; n++)
if (name[n] == '/')
name[n] = FILE_SEPARATOR;
}
#endif
strncpy (ucon64.fname_arch, basename2 (name), FILENAME_MAX)[FILENAME_MAX - 1] = 0;
}
#endif
void
ucon64_usage (int argc, char *argv[])
{
int x = 0, y = 0, c = 0, single = 0;
const char *name_exe = basename2 (argv[0]);
#ifdef USE_DISCMAGE
char *name_discmage;
#endif
(void) argc; // warning remover
#ifdef USE_ZLIB
printf ("Usage: %s [OPTION]... [ROM|IMAGE|SRAM|FILE|DIR|ARCHIVE]...\n\n", name_exe);
#else
printf ("Usage: %s [OPTION]... [ROM|IMAGE|SRAM|FILE|DIR]...\n\n", name_exe);
#endif
// single usage
for (x = 0; arg[x].val; x++)
if (arg[x].console) // IS console
for (y = 0; option[y]; y++)
for (c = 0; option[y][c].name || option[y][c].help; c++)
if (option[y][c].object)
if (((st_ucon64_obj_t *) option[y][c].object)->console == arg[x].console)
{
getopt2_usage (option[y]);
single = 1;
break;
}
if (!single)
getopt2_usage (options);
fputc ('\n', stdout);
printf ("DATabase: %d known ROMs (DAT files: %s)\n\n",
ucon64_dat_total_entries (), ucon64.datdir);
#ifdef USE_DISCMAGE
name_discmage =
#ifdef DLOPEN
ucon64.discmage_path;
#else
#if defined __MSDOS__
"discmage.dxe";
#elif defined __CYGWIN__ || defined _WIN32
"discmage.dll";
#elif defined __APPLE__ // Mac OS X actually
"libdiscmage.dylib";
#elif defined __unix__ || defined __BEOS__
"libdiscmage.so";
#else
"unknown";
#endif
#endif
if (!ucon64.discmage_enabled)
{
printf (ucon64_msg[NO_LIB], name_discmage);
fputc ('\n', stdout);
}
#endif
#ifdef USE_PARALLEL
puts ("NOTE: You only need to specify PORT if uCON64 doesn't detect the (right)\n"
" parallel port. If that is the case give a hardware address. For example:\n"
" ucon64 " OPTION_LONG_S "xswc \"rom.swc\" " OPTION_LONG_S "port=0x378\n"
" In order to connect a copier to a PC's parallel port you need a standard\n"
" bidirectional parallel cable\n");
#endif
printf ("TIP: %s " OPTION_LONG_S "help " OPTION_LONG_S "snes (would show only SNES related help)\n", name_exe);
#if defined __MSDOS__ || defined _WIN32
printf (" %s " OPTION_LONG_S "help|more (to see everything in more)\n", name_exe);
#else
printf (" %s " OPTION_LONG_S "help|less (to see everything in less)\n", name_exe); // less is more ;-)
#endif
puts (" Give the force recognition switch a try if something went wrong\n"
"\n"
"Please report any problems/ideas/fixes to noisyb@gmx.net or\n"
"ucon64-announce@lists.sf.net or visit http://ucon64.sf.net\n");
}