2009-09-16 08:41:12 +02:00

4404 lines
151 KiB
C

/*
snes.c - Super NES support for uCON64
Copyright (c) 1999 - 2002 NoisyB <noisyb@gmx.net>
Copyright (c) 2001 - 2004 dbjh
Copyright (c) 2002 - 2003 John Weidman
Copyright (c) 2004 JohnDie
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 <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/stat.h>
#include "misc/bswap.h"
#include "misc/misc.h"
#include "misc/string.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 "ucon64.h"
#include "ucon64_misc.h"
#include "snes.h"
#include "backup/mgd.h"
#include "backup/gd.h"
#include "backup/swc.h"
#include "backup/fig.h"
#include "backup/ufo.h"
#define SNES_HEADER_LEN (sizeof (st_snes_header_t))
#define SNES_NAME_LEN 21
#define GD3_HEADER_MAPSIZE 0x18
#define NSRT_HEADER_VERSION 22 // version 2.2 header
#define DETECT_NOTGOOD_DUMPS // makes _a_ complete GoodSNES 0.999.5 set detected
#define DETECT_SMC_COM_FUCKED_UP_LOROM // adds support for interleaved LoROMs
#define DETECT_INSNEST_FUCKED_UP_LOROM // only adds support for its 24 Mbit
// interleaved LoROM "format"
//#define PAD_40MBIT_GD3_DUMPS // padding works for
// Dai Kaiju Monogatari 2 (J)
static int snes_chksum (st_rominfo_t *rominfo, unsigned char **rom_buffer,
int rom_size);
static int snes_deinterleave (st_rominfo_t *rominfo, unsigned char **rom_buffer,
int rom_size);
static unsigned short int get_internal_sums (st_rominfo_t *rominfo);
static int snes_check_bs (void);
static inline int snes_isprint (char *s, int len);
static int check_banktype (unsigned char *rom_buffer, int header_offset);
static void reset_header (void *header);
static void set_nsrt_info (st_rominfo_t *rominfo, unsigned char *header);
static void get_nsrt_info (unsigned char *rom_buffer, int header_start,
unsigned char *buheader);
static void handle_nsrt_header (st_rominfo_t *rominfo, unsigned char *header,
const char **snes_country);
const st_getopt2_t snes_usage[] =
{
{
NULL, 0, 0, 0,
NULL, "Super Nintendo Entertainment System/SNES/Super Famicom"
/*"1990 Nintendo http://www.nintendo.com"*/,
NULL
},
{
"snes", 0, 0, UCON64_SNES,
NULL, "force recognition",
&ucon64_wf[WF_OBJ_SNES_SWITCH]
},
{
"hi", 0, 0, UCON64_HI,
NULL, "force ROM is HiROM",
&ucon64_wf[WF_OBJ_SNES_SWITCH]
},
{
"nhi", 0, 0, UCON64_NHI,
NULL, "force ROM is not HiROM",
&ucon64_wf[WF_OBJ_SNES_SWITCH]
},
{
"erom", 0, 0, UCON64_EROM,
NULL, "force ROM is \"Extended\" (combine with -hi for Extended HiROM)",
&ucon64_wf[WF_OBJ_SNES_SWITCH]
},
#if 0
{
"hd", 0, 0, UCON64_HD,
NULL, "force ROM has SMC/FIG/SWC header (+512 Bytes)",
&ucon64_wf[WF_OBJ_ALL_SWITCH]
},
{
"nhd", 0, 0, UCON64_NHD,
NULL, "force ROM has no SMC/FIG/SWC header (MGD2/MGH/RAW)",
&ucon64_wf[WF_OBJ_ALL_SWITCH]
},
{
"ns", 0, 0, UCON64_NS,
NULL, "force ROM is not split",
&ucon64_wf[WF_OBJ_ALL_SWITCH]
},
#endif
#if 0
// the next switch remains undocumented until we know of a good checksum algorithm
{
"id", 0, 0, UCON64_ID,
NULL, "force -gd3 to produce a unique file name",
&ucon64_wf[WF_OBJ_ALL_SWITCH]
},
#endif
{
"int", 0, 0, UCON64_INT,
NULL, "force ROM is in interleaved format (GD3/UFO)",
&ucon64_wf[WF_OBJ_ALL_SWITCH]
},
{
"int2", 0, 0, UCON64_INT2,
NULL, "force ROM is in interleaved format 2 (SFX)",
&ucon64_wf[WF_OBJ_ALL_SWITCH]
},
{
"nint", 0, 0, UCON64_NINT,
NULL, "force ROM is not in interleaved format",
&ucon64_wf[WF_OBJ_ALL_SWITCH]
},
{
"bs", 0, 0, UCON64_BS,
NULL, "force ROM is a Broadcast Satellaview dump",
&ucon64_wf[WF_OBJ_SNES_SWITCH]
},
{
"nbs", 0, 0, UCON64_NBS,
NULL, "force ROM is a regular cartridge dump",
&ucon64_wf[WF_OBJ_SNES_SWITCH]
},
{
"n", 1, 0, UCON64_N,
"NEW_NAME", "change internal ROM name to NEW_NAME",
&ucon64_wf[WF_OBJ_ALL_DEFAULT]
},
{
"fig", 0, 0, UCON64_FIG,
NULL, "convert to *Pro Fighter*/FIG",
&ucon64_wf[WF_OBJ_SNES_DEFAULT_NO_SPLIT]
},
{
"figs", 0, 0, UCON64_FIGS,
NULL, "convert emulator *.srm (SRAM) to *Pro Fighter*/FIG",
&ucon64_wf[WF_OBJ_SNES_INIT_PROBE]
},
{
"gd3", 0, 0, UCON64_GD3,
NULL, "convert to Game Doctor SF3(SF6/SF7)/Professor SF(SF II)",
&ucon64_wf[WF_OBJ_SNES_DEFAULT_NO_SPLIT]
},
{
"gd3s", 0, 0, UCON64_GD3S,
NULL, "convert emulator *.srm (SRAM) to GD SF3(SF6/SF7)/Professor SF*",
&ucon64_wf[WF_OBJ_SNES_INIT_PROBE]
},
{
"mgd", 0, 0, UCON64_MGD,
NULL, "convert to Multi Game*/MGD2/MGH/RAW",
&ucon64_wf[WF_OBJ_ALL_DEFAULT_NO_SPLIT]
},
{
"smc", 0, 0, UCON64_SMC,
NULL, "convert to Super Magicom/SMC",
&ucon64_wf[WF_OBJ_SNES_DEFAULT_NO_SPLIT]
},
{
"swc", 0, 0, UCON64_SWC,
NULL, "convert to Super Wild Card*/SWC",
&ucon64_wf[WF_OBJ_SNES_DEFAULT_NO_SPLIT]
},
{
"swcs", 0, 0, UCON64_SWCS,
NULL, "convert emulator *.srm (SRAM) to Super Wild Card*/SWC",
&ucon64_wf[WF_OBJ_SNES_INIT_PROBE]
},
{
"ufo", 0, 0, UCON64_UFO,
NULL, "convert to Super UFO",
&ucon64_wf[WF_OBJ_SNES_DEFAULT_NO_SPLIT]
},
{
"ufos", 0, 0, UCON64_UFOS,
NULL, "convert emulator *.srm (SRAM) to Super UFO",
&ucon64_wf[WF_OBJ_SNES_INIT_PROBE]
},
{
"stp", 0, 0, UCON64_STP,
NULL, "convert SRAM from backup unit for use with an emulator\n"
OPTION_LONG_S "stp just strips the first 512 bytes",
NULL
},
{
"dbuh", 0, 0, UCON64_DBUH,
NULL, "display (relevant part of) backup unit header",
&ucon64_wf[WF_OBJ_SNES_DEFAULT]
},
{
"dint", 0, 0, UCON64_DINT,
NULL, "deinterleave ROM (regardless whether the ROM is interleaved)",
&ucon64_wf[WF_OBJ_ALL_INIT_PROBE_NO_SPLIT]
},
{
"ctrl", 1, 0, UCON64_CTRL,
"TYPE", "specify type of controller in port 1 for emu when converting\n"
"TYPE=0 gamepad\n"
"TYPE=1 mouse\n"
"TYPE=2 mouse / gamepad\n"
"TYPE=6 multitap",
&ucon64_wf[WF_OBJ_ALL_SWITCH]
},
{
"ctrl2", 1, 0, UCON64_CTRL2,
"TYPE", "specify type of controller in port 2 for emu when converting\n"
"TYPE=0 gamepad\n"
"TYPE=1 mouse\n"
"TYPE=2 mouse / gamepad\n"
"TYPE=3 super scope\n"
"TYPE=4 super scope / gamepad\n"
"TYPE=5 Konami's justifier\n"
"TYPE=6 multitap\n"
"TYPE=7 mouse / super scope / gamepad",
&ucon64_wf[WF_OBJ_SNES_SWITCH]
},
{
"col", 1, 0, UCON64_COL,
"0xCOLOR", "convert 0xRRGGBB (HTML) <-> 0xXXXX (SNES)"
/*"this routine was used to find green colors in games and\n"
"to replace them with red colors (blood mode)"*/,
&ucon64_wf[WF_OBJ_SNES_NO_ROM]
},
{
"j", 0, 0, UCON64_J,
NULL, "join split ROM",
&ucon64_wf[WF_OBJ_ALL_INIT_PROBE]
},
{
"s", 0, 0, UCON64_S,
NULL, "split ROM; default part size is 8 Mb",
&ucon64_wf[WF_OBJ_ALL_DEFAULT_NO_SPLIT]
},
{
"ssize", 1, 0, UCON64_SSIZE,
"SIZE", "specify split part size in Mbit (not for Game Doctor SF3)",
&ucon64_wf[WF_OBJ_ALL_SWITCH]
},
#if 0
{
"p", 0, 0, UCON64_P,
NULL, "pad ROM to full Mb",
&ucon64_wf[WF_OBJ_ALL_DEFAULT]
},
#endif
{
"k", 0, 0, UCON64_K,
NULL, "remove protection (crack)",
&ucon64_wf[WF_OBJ_SNES_DEFAULT]
},
{
"f", 0, 0, UCON64_F,
NULL, "remove NTSC/PAL protection",
&ucon64_wf[WF_OBJ_ALL_DEFAULT]
},
{
"l", 0, 0, UCON64_L,
NULL, "remove SlowROM checks",
&ucon64_wf[WF_OBJ_SNES_DEFAULT]
},
{
"chk", 0, 0, UCON64_CHK,
NULL, "fix ROM checksum",
&ucon64_wf[WF_OBJ_ALL_DEFAULT]
},
{
"multi", 1, 0, UCON64_MULTI,
"SIZE", "make multi-game file for use with Super Flash flash card,\n"
"truncated to SIZE Mbit; file with loader must be specified\n"
"first, then all the ROMs, multi-game file to create last",
&ucon64_wf[WF_OBJ_ALL_INIT_PROBE_STOP]
},
{
"dmirr", 0, 0, UCON64_DMIRR,
NULL, "\"de-mirror\" ROM (strip mirrored block from end of ROM)",
&ucon64_wf[WF_OBJ_SNES_DEFAULT]
},
{
"dnsrt", 0, 0, UCON64_DNSRT,
NULL, "\"de-NSRT\" ROM (restore name and checksum from NSRT header)",
&ucon64_wf[WF_OBJ_SNES_DEFAULT]
},
{NULL, 0, 0, 0, NULL, NULL, NULL}
};
typedef struct st_snes_header
{
unsigned char maker_high; // 0
unsigned char maker_low; // 1
unsigned char game_id_prefix; // 2
unsigned char game_id_low; // 3
unsigned char game_id_high; // 4
unsigned char game_id_country; // 5
// 'E' = USA, 'F' = France, 'G' = Germany, 'J' = Japan, 'P' = Europe, 'S' = Spain
unsigned char pad1[7]; // 6
unsigned char sfx_sram_size; // 13
unsigned char pad2[2]; // 14
unsigned char name[SNES_NAME_LEN]; // 16
unsigned char map_type; // 37, a.k.a. ROM makeup
unsigned char rom_type; // 38
#define bs_month rom_type // release date, month
unsigned char rom_size; // 39
#define bs_day rom_size // release date, day
unsigned char sram_size; // 40
#define bs_map_type sram_size
unsigned char country; // 41
#define bs_type country
unsigned char maker; // 42
unsigned char version; // 43
/*
If we combine the following 4 bytes in 2 short int variables,
inverse_checksum and checksum, they will have an incorrect value on big
endian machines.
*/
unsigned char inverse_checksum_low; // 44
unsigned char inverse_checksum_high; // 45
unsigned char checksum_low; // 46
unsigned char checksum_high; // 47
} st_snes_header_t;
static st_snes_header_t snes_header;
static int snes_split, snes_sramsize, snes_sfx_sramsize, snes_header_base,
snes_hirom, snes_hirom_ok, nsrt_header, bs_dump, st_dump;
static snes_file_t type;
static unsigned char gd3_hirom_8mb_map[GD3_HEADER_MAPSIZE] =
{
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22
};
static unsigned char gd3_hirom_16mb_map[GD3_HEADER_MAPSIZE] =
{
0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21,
0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21,
0x22, 0x23, 0x22, 0x23, 0x22, 0x23, 0x22, 0x23
};
static unsigned char gd3_hirom_24mb_map[GD3_HEADER_MAPSIZE] =
{
0x20, 0x21, 0x22, 0x00, 0x20, 0x21, 0x22, 0x00,
0x20, 0x21, 0x22, 0x00, 0x20, 0x21, 0x22, 0x00,
0x24, 0x25, 0x23, 0x00, 0x24, 0x25, 0x23, 0x00
};
static unsigned char gd3_hirom_32mb_map[GD3_HEADER_MAPSIZE] =
{
0x20, 0x21, 0x22, 0x23, 0x20, 0x21, 0x22, 0x23,
0x20, 0x21, 0x22, 0x23, 0x20, 0x21, 0x22, 0x23,
0x24, 0x25, 0x26, 0x27, 0x24, 0x25, 0x26, 0x27
};
// map for Dai Kaiju Monogatari 2 (J)
static unsigned char gd3_hirom_40mb_map[GD3_HEADER_MAPSIZE] =
{
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x22, 0x23, 0x24, 0x25, 0x22, 0x23, 0x24, 0x25,
0x21, 0x21, 0x21, 0x21, 0x26, 0x27, 0x28, 0x29
};
// map for Tales of Phantasia (J)
static unsigned char gd3_hirom_48mb_map[GD3_HEADER_MAPSIZE] =
{
0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x40, 0x40,
0x24, 0x25, 0x26, 0x27, 0x24, 0x25, 0x26, 0x27,
0x22, 0x23, 0x40, 0x40, 0x28, 0x29, 0x2a, 0x2b
};
static unsigned char gd3_lorom_4mb_map[GD3_HEADER_MAPSIZE] =
{
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20
};
static unsigned char gd3_lorom_8mb_map[GD3_HEADER_MAPSIZE] =
{
0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21,
0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21,
0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21
};
static unsigned char gd3_lorom_16mb_map[GD3_HEADER_MAPSIZE] =
{
0x20, 0x21, 0x22, 0x23, 0x20, 0x21, 0x22, 0x23,
0x20, 0x21, 0x22, 0x23, 0x20, 0x21, 0x22, 0x23,
0x20, 0x21, 0x22, 0x23, 0x20, 0x21, 0x22, 0x23
};
static unsigned char gd3_lorom_32mb_map[GD3_HEADER_MAPSIZE] =
{
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x24, 0x25, 0x26, 0x27, 0x24, 0x25, 0x26, 0x27
};
int
snes_get_snes_hirom (void)
{
return snes_hirom;
}
snes_file_t
snes_get_file_type (void)
{
return type;
}
int
snes_col (const char *color)
/*
The Nintendo Super Famicom is capable of displaying 256 colours from a
palette of 32,768. These 256 colours are split into 8 palettes of 32 colours
each.
To change the colours the following needs to be done:
Loading the palette control register ($2121) with the colour number you wish
to change (0-255, 0=background).
Then load the colour into the palette data register first the low 8 bits,
followed by the high 7 bits (this gives you the maximum 32768 colours
possible $0000-$7fff).
Colour data is made up of 3 components (Red,Green,Blue) each of 5 bits (The
Amiga uses exactly the same system, but only using 4 bits per component).
Saying that, Nintendo being the stupid japanese idiots they are decided that
R,G,B wasn't alphabetically correct and so opted to store the bits as B,G,R.
00000 00000 00000
\ / \ / \ /
\ / \ / \ /
B G R
Examples:
~~~~~~~~~
11111 00000 00000 = $7C00 (Bright Blue)
00000 11111 00000 = $03E0 (Bright Green)
00000 00000 11111 = $001F (Bright Red)
00000 00000 00000 = $0000 (Black)
11111 11111 11111 = $7FFF (White)
Remember to load the lowest 8 bits first, then the top 7 bits.
*/
{
int r, g, b;
unsigned int col;
sscanf (color, "%x", &col);
r = (col & 0xff0000) >> 16;
g = (col & 0xff00) >> 8;
b = col & 0xff;
printf ("0x%02x%02x%02x (html) == ", r, g, b);
r = (r * 0x1f) / 0xff;
g = (g * 0x1f) / 0xff;
b = (b * 0x1f) / 0xff;
col = b;
col = col << 5;
col = col + g;
col = col << 5;
col = col + r;
printf ("0x%02x%02x (snes)\n", col & 0xff, (col & 0x7f00) / 0x100);
sscanf (color, "%x", &col);
if (col < 0xff7f + 1)
{
printf ("0x%04x (snes) == ", col & 0xff7f);
col = ((col & 0x7f) * 0x100) + ((col & 0xff00) / 0x100);
r = col & 0x1f;
g = (col & (0x1f << 5)) >> 5;
b = (col & (0x1f << 10)) >> 10;
r = r * 0xff / 0x1f;
g = g * 0xff / 0x1f;
b = b * 0xff / 0x1f;
printf ("0x%02x%02x%02x (html)\n", r, g, b);
}
fputc ('\n', stdout);
return 0;
}
static int
snes_convert_sramfile (int org_header_len, const void *new_header)
{
FILE *srcfile, *destfile;
char src_name[FILENAME_MAX], dest_name[FILENAME_MAX], buf[32 * 1024];
unsigned int blocksize, byteswritten, new_header_len;
strcpy (src_name, ucon64.rom);
if (new_header)
{
new_header_len = SWC_HEADER_LEN;
strcpy (dest_name, ucon64.rom);
set_suffix (dest_name, ".sav");
}
else // code for Game Doctor SRAM file
{
int n;
new_header_len = 0;
sprintf (dest_name, "SF8%.3s", basename2 (ucon64.rom));
strupr (dest_name);
// avoid trouble with filenames containing spaces
for (n = 3; n < 6; n++) // skip "SF" and first digit
if (dest_name[n] == ' ')
dest_name[n] = '_';
set_suffix (dest_name, ".B00");
}
ucon64_file_handler (dest_name, src_name, 0);
if ((srcfile = fopen (src_name, "rb")) == NULL)
{
fprintf (stderr, ucon64_msg[OPEN_READ_ERROR], src_name);
return -1;
}
if ((destfile = fopen (dest_name, "wb")) == NULL)
{
fprintf (stderr, ucon64_msg[OPEN_WRITE_ERROR], dest_name);
return -1;
}
fseek (srcfile, org_header_len, SEEK_SET);
if (new_header)
{
fwrite (new_header, 1, new_header_len, destfile); // write header
byteswritten = new_header_len;
}
else
byteswritten = 0;
blocksize = fread (buf, 1, 32 * 1024, srcfile); // read 32 kB at max
while (byteswritten < 32 * 1024 + new_header_len)
{
// Pad SRAM data to 32 kB by repeating it. At least the SWC DX2 does
// something similar.
fwrite (buf, 1, byteswritten + blocksize <= 32 * 1024 + new_header_len ?
blocksize : 32 * 1024 + new_header_len - byteswritten, destfile);
byteswritten += blocksize;
}
fclose (srcfile);
fclose (destfile);
printf (ucon64_msg[WROTE], dest_name);
remove_temp_file ();
return 0;
}
int
snes_swcs (st_rominfo_t *rominfo)
{
st_swc_header_t header;
memset (&header, 0, SWC_HEADER_LEN);
header.id1 = 0xaa;
header.id2 = 0xbb;
header.type = 5; // size needn't be set for the SWC
// (SWC itself doesn't set it either)
return snes_convert_sramfile (rominfo->buheader_len, &header);
}
int
snes_figs (st_rominfo_t *rominfo)
{
st_fig_header_t header;
memset (&header, 0, FIG_HEADER_LEN);
header.size_low = 4; // 32 kB == 4*8 kB, size_high is already 0
return snes_convert_sramfile (rominfo->buheader_len, &header);
}
int
snes_ufos (st_rominfo_t *rominfo)
{
unsigned char header[SWC_HEADER_LEN];
memset (&header, 0, SWC_HEADER_LEN);
memcpy (&header[8], "SUPERUFO", 8);
return snes_convert_sramfile (rominfo->buheader_len, &header);
}
int
snes_gd3s (st_rominfo_t *rominfo)
{
return snes_convert_sramfile (rominfo->buheader_len, NULL);
}
static void
write_deinterleaved_data (st_rominfo_t *rominfo, const char *src_name,
const char *dest_name, int size, int buheader_len)
{
unsigned char *buffer;
if (!(buffer = (unsigned char *) malloc (size)))
{
fprintf (stderr, ucon64_msg[ROM_BUFFER_ERROR], size);
exit (1);
}
ucon64_fread (buffer, rominfo->buheader_len, size, src_name);
snes_deinterleave (rominfo, &buffer, size);
ucon64_fwrite (buffer, buheader_len, size, dest_name, buheader_len ? "ab" : "wb");
free (buffer);
}
int
snes_dint (st_rominfo_t *rominfo)
{
char src_name[FILENAME_MAX], dest_name[FILENAME_MAX];
int buheader_len = rominfo->buheader_len > (int) SWC_HEADER_LEN ?
(int) SWC_HEADER_LEN : rominfo->buheader_len;
puts ("Converting to deinterleaved format...");
strcpy (src_name, ucon64.rom);
strcpy (dest_name, ucon64.rom);
set_suffix (dest_name, ".tmp");
ucon64_file_handler (dest_name, src_name, 0);
if (!rominfo->interleaved)
printf ("WARNING: Deinterleaving a ROM that was not detected as interleaved\n");
fcopy (src_name, 0, buheader_len, dest_name, "wb");
write_deinterleaved_data (rominfo, src_name, dest_name,
ucon64.file_size - rominfo->buheader_len, buheader_len);
printf (ucon64_msg[WROTE], dest_name);
remove_temp_file ();
return 0;
}
static int
snes_ffe (st_rominfo_t *rominfo, char *ext)
{
st_swc_header_t header;
int size = ucon64.file_size - rominfo->buheader_len;
char src_name[FILENAME_MAX], dest_name[FILENAME_MAX];
ucon64_fread (&header, 0, rominfo->buheader_len > (int) SWC_HEADER_LEN ?
(int) SWC_HEADER_LEN : rominfo->buheader_len, ucon64.rom);
reset_header (&header);
header.size_low = size / 8192;
header.size_high = size / 8192 >> 8;
header.emulation = snes_split ? 0x40 : 0;
header.emulation |= snes_hirom ? 0x30 : 0;
// bit 3 & 2 are already ok for 32 kB SRAM size
if (snes_sramsize == 8 * 1024)
header.emulation |= 0x04;
else if (snes_sramsize == 2 * 1024)
header.emulation |= 0x08;
else if (snes_sramsize == 0)
header.emulation |= 0x0c;
header.id1 = 0xaa;
header.id2 = 0xbb;
header.type = 4;
set_nsrt_info (rominfo, (unsigned char *) &header);
strcpy (src_name, ucon64.rom);
strcpy (dest_name, ucon64.rom);
set_suffix (dest_name, ext);
ucon64_file_handler (dest_name, src_name, 0);
ucon64_fwrite (&header, 0, SWC_HEADER_LEN, dest_name, "wb");
if (rominfo->interleaved)
write_deinterleaved_data (rominfo, src_name, dest_name, size, SWC_HEADER_LEN);
else
fcopy (src_name, rominfo->buheader_len, size, dest_name, "ab");
printf (ucon64_msg[WROTE], dest_name);
remove_temp_file ();
return 0;
}
// header format is specified in src/backup/ffe.h
int
snes_smc (st_rominfo_t *rominfo)
{
if ((bs_dump ? snes_header.bs_map_type : snes_header.map_type) & 0x10)
printf ("NOTE: This game might not work with a Super Magicom because it's a FastROM game\n");
return snes_ffe (rominfo, ".smc");
}
// header format is specified in src/backup/ffe.h
int
snes_swc (st_rominfo_t *rominfo)
{
return snes_ffe (rominfo, ".swc");
}
// header format is specified in src/backup/fig.h
void
snes_set_fig_header (st_rominfo_t *rominfo, st_fig_header_t *header)
{
int size = ucon64.file_size - rominfo->buheader_len, uses_DSP;
header->size_low = size / 8192;
header->size_high = size / 8192 >> 8;
header->multi = snes_split ? 0x40 : 0;
header->hirom = snes_hirom ? 0x80 : 0;
uses_DSP = snes_header.rom_type == 3 || snes_header.rom_type == 5 ||
snes_header.rom_type == 0xf6;
if ((snes_header.rom_type & 0xf0) == 0x10) // uses FX(2) chip
{
header->emulation1 = 0x11;
header->emulation2 = 2;
}
else
{
#if 0 // memset() set all fields to 0
header->emulation1 = 0; // default value for LoROM dumps
if (snes_sramsize == 32 * 1024)
header->emulation2 = 0;
else
#endif
if (snes_sramsize == 8 * 1024 || snes_sramsize == 2 * 1024)
header->emulation2 = 0x80;
else if (snes_sramsize == 0)
{
header->emulation1 = 0x77;
header->emulation2 = 0x83;
}
if (snes_hirom)
{
header->emulation2 |= 2;
if (uses_DSP)
header->emulation1 |= 0xf0;
if (snes_sramsize != 0)
header->emulation1 |= 0xdd;
}
else if (uses_DSP) // LoROM
{
header->emulation1 &= 0x0f;
header->emulation1 |= 0x40; // LoROM && SRAM == 0 && DSP => 0x47
}
}
}
int
snes_fig (st_rominfo_t *rominfo)
{
st_fig_header_t header;
int size = ucon64.file_size - rominfo->buheader_len;
char src_name[FILENAME_MAX], dest_name[FILENAME_MAX];
ucon64_fread (&header, 0, rominfo->buheader_len > (int) FIG_HEADER_LEN ?
(int) FIG_HEADER_LEN : rominfo->buheader_len, ucon64.rom);
reset_header (&header);
snes_set_fig_header (rominfo, &header);
set_nsrt_info (rominfo, (unsigned char *) &header);
strcpy (src_name, ucon64.rom);
strcpy (dest_name, ucon64.rom);
set_suffix (dest_name, ".fig");
ucon64_file_handler (dest_name, src_name, 0);
ucon64_fwrite (&header, 0, FIG_HEADER_LEN, dest_name, "wb");
if (rominfo->interleaved)
write_deinterleaved_data (rominfo, src_name, dest_name, size, FIG_HEADER_LEN);
else
fcopy (src_name, rominfo->buheader_len, size, dest_name, "ab");
printf (ucon64_msg[WROTE], dest_name);
remove_temp_file ();
return 0;
}
// see src/backup/mgd.h for the file naming scheme
int
snes_mgd (st_rominfo_t *rominfo)
{
char src_name[FILENAME_MAX], dest_name[FILENAME_MAX];
int size = ucon64.file_size - rominfo->buheader_len;
if (snes_hirom)
printf ("NOTE: This game might not work with a MGD because it's a HiROM game\n");
strcpy (src_name, ucon64.rom);
mgd_make_name (ucon64.rom, UCON64_SNES, size, dest_name);
ucon64_file_handler (dest_name, src_name, OF_FORCE_BASENAME);
if (rominfo->interleaved)
write_deinterleaved_data (rominfo, src_name, dest_name, size, 0);
else
fcopy (src_name, rominfo->buheader_len, ucon64.file_size, dest_name, "wb");
printf (ucon64_msg[WROTE], dest_name);
remove_temp_file ();
mgd_write_index_file ((char *) basename2 (dest_name), 1);
return 0;
}
void
snes_int_blocks (const unsigned char *deintptr, unsigned char *ipl,
unsigned char *iph, int nblocks)
{
int i;
// interleave 64 K blocks
for (i = nblocks; i > 0; i--)
{
memmove (ipl, deintptr, 0x8000);
memmove (iph, deintptr + 0x8000, 0x8000);
deintptr += 0x10000;
ipl += 0x8000;
iph += 0x8000;
}
}
void
snes_mirror (unsigned char *dstbuf, unsigned int start, unsigned int data_end,
unsigned int mirror_end)
{
int datasize, totsize, nchunks, surplus;
datasize = data_end - start;
totsize = mirror_end - start;
if (datasize >= totsize)
return;
nchunks = totsize / datasize - 1;
surplus = totsize % datasize;
while (nchunks-- > 0)
{
memcpy (dstbuf + data_end, dstbuf + start, datasize);
data_end += datasize;
}
if (surplus > 0)
memmove (dstbuf + data_end, dstbuf + start, mirror_end - data_end);
}
static void
make_gd_name (const char *filename, st_rominfo_t *rominfo, char *name,
unsigned char *buffer, int newsize)
{
char dest_name[FILENAME_MAX], *p, id_str[3];
int n, size = ucon64.file_size - rominfo->buheader_len;
strcpy (dest_name, filename);
if (UCON64_ISSET (ucon64.id))
{
/*
We include the underscore so that we can encode a base 37 number (10
digits + 26 characters in alphabet + underscore = 37). The ID is 3
characters long which makes it possible to have 37^3 = 50653 different
IDs. If we wouldn't include the underscore we would have 46656
different IDs.
We can't use the SNES checksum because several ROM dumps have the same
checksum (not only PD files!). Nor can we use the internal SNES
checksum, because several beta ROM dumps have an internal checksum of
0 or 0xffff.
*/
unsigned int local_buffer = !buffer, d2, d1, d0, id = 0;
if (local_buffer)
{
if (!(buffer = (unsigned char *) malloc (size)))
{
fprintf (stderr, ucon64_msg[ROM_BUFFER_ERROR], size);
exit (1);
}
ucon64_fread (buffer, ucon64.rominfo->buheader_len, size, dest_name);
if (rominfo->interleaved)
snes_deinterleave (rominfo, &buffer, size);
}
for (n = 0; n < size; n++)
id += buffer[n] ^ (n & 0xff);
id %= 37 * 37 * 37; // ensure value can be encoded with 3 base 37 digits
if (local_buffer)
free (buffer);
d2 = id / (37 * 37);
d1 = (id % (37 * 37)) / 37;
d0 = id % 37;
id_str[0] = d2 == 36 ? '_' : (d2 <= 9 ? d2 + '0' : d2 + 'A' - 10);
id_str[1] = d1 == 36 ? '_' : (d1 <= 9 ? d1 + '0' : d1 + 'A' - 10);
id_str[2] = d0 == 36 ? '_' : (d0 <= 9 ? d0 + '0' : d0 + 'A' - 10);
p = id_str;
}
else
p = (char *) basename2 (dest_name);
sprintf (name, "sf%d%s", newsize / MBIT, p);
if (newsize < 10 * MBIT)
{
if (!strnicmp (name, p, 3))
strcpy (name, p);
}
else
{
if (!strnicmp (name, p, 4))
strcpy (name, p);
}
if ((p = strrchr (name, '.')))
*p = 0;
strcat (name, "___");
if (newsize < 10 * MBIT)
name[6] = 0;
else
name[7] = 0;
// avoid trouble with filenames containing spaces
for (n = 3; n < 7; n++) // skip "sf" and first digit
if (name[n] == ' ')
name[n] = '_';
}
int
snes_gd3 (st_rominfo_t *rominfo)
{
char header[GD_HEADER_LEN], src_name[FILENAME_MAX], dest_name[FILENAME_MAX];
unsigned char *srcbuf, *dstbuf;
int n, n4Mbparts, surplus4Mb, total4Mbparts, size, newsize, pad,
half_size_4Mb, half_size_1Mb;
size = ucon64.file_size - rominfo->buheader_len;
n4Mbparts = size / (4 * MBIT);
surplus4Mb = size % (4 * MBIT);
total4Mbparts = n4Mbparts + (surplus4Mb > 0 ? 1 : 0);
strcpy (src_name, ucon64.rom);
if (!(srcbuf = (unsigned char *) malloc (size)))
{
fprintf (stderr, ucon64_msg[ROM_BUFFER_ERROR], size);
exit (1);
}
ucon64_fread (srcbuf, rominfo->buheader_len, size, ucon64.rom);
if (rominfo->interleaved)
snes_deinterleave (rominfo, &srcbuf, size);
if (snes_hirom)
{
if (!((size >= 2 * MBIT && total4Mbparts <= 8) ||
total4Mbparts == 10 || total4Mbparts == 12))
{
fprintf (stderr, "ERROR: ROM size is %d Mbit -- conversion not yet implemented/verified\n",
size / MBIT);
return -1;
}
else if (total4Mbparts > 8 && snes_header_base != SNES_EROM)
{
fprintf (stderr, "ERROR: Normal ROM > 32 Mbit -- conversion not yet implemented\n");
return -1;
}
if (total4Mbparts == 5)
total4Mbparts = 6; // 20 Mbit HiROMs get padded to 24 Mbit
else if (total4Mbparts == 7)
total4Mbparts = 8; // 28 Mbit HiROMs get padded to 32 Mbit
#ifdef PAD_40MBIT_GD3_DUMPS // (a 28 Mbit ROM needs 40 Mbit of GD DRAM)
else if (total4Mbparts == 10)
{
total4Mbparts = 12; // 40 Mbit HiROMs get padded to 48 Mbit
printf ("NOTE: Paddding to 48 Mbit\n");
}
#endif
// create the header
ucon64_fread (header, 0, rominfo->buheader_len > GD_HEADER_LEN ?
GD_HEADER_LEN : rominfo->buheader_len, ucon64.rom);
reset_header (header);
memcpy (header, "GAME DOCTOR SF 3", 0x10);
if (snes_sramsize == 8 * 1024)
header[0x10] = (unsigned char) 0x81; // 64 kb
else if (snes_sramsize == 2 * 1024)
header[0x10] = (unsigned char) 0x82; // 16 kb
else
header[0x10] = (unsigned char) 0x80; // 0 kb or 256 kb
if (total4Mbparts <= 2)
memcpy (&header[0x11], gd3_hirom_8mb_map, GD3_HEADER_MAPSIZE);
else if (total4Mbparts <= 4)
memcpy (&header[0x11], gd3_hirom_16mb_map, GD3_HEADER_MAPSIZE);
else if (total4Mbparts <= 6)
memcpy (&header[0x11], gd3_hirom_24mb_map, GD3_HEADER_MAPSIZE);
else if (total4Mbparts <= 8)
memcpy (&header[0x11], gd3_hirom_32mb_map, GD3_HEADER_MAPSIZE);
else if (total4Mbparts <= 10)
memcpy (&header[0x11], gd3_hirom_40mb_map, GD3_HEADER_MAPSIZE);
else
memcpy (&header[0x11], gd3_hirom_48mb_map, GD3_HEADER_MAPSIZE);
if (snes_sramsize != 0)
{
if (snes_header_base == SNES_EROM)
{
header[0x29] = 0x00;
header[0x2a] = 0x0f;
}
else
{
header[0x29] = 0x0c;
header[0x2a] = 0x0c;
}
}
// Adjust sram map for exceptions - a couple of 10-12 Mb HiROM games
// (Liberty or Death, Brandish). May not be necessary
// interleave the image
if (n4Mbparts)
newsize = 4 * total4Mbparts * MBIT;
else
newsize = ((size + MBIT - 1) / MBIT) * MBIT;
// special pad code should only be executed for 10, 20 and if
// PAD_40MBIT_GD3_DUMPS is defined, 40 Mbit ROMs
if (total4Mbparts == 3 || total4Mbparts == 6 || total4Mbparts == 12)
pad = (newsize - size) / 2;
else
pad = 0;
if (!(dstbuf = (unsigned char *) malloc (newsize)))
{
fprintf (stderr, ucon64_msg[ROM_BUFFER_ERROR], newsize);
exit (1);
}
if (newsize > size)
{
if (!(srcbuf = (unsigned char *) realloc (srcbuf, newsize)))
{
fprintf (stderr, ucon64_msg[ROM_BUFFER_ERROR], newsize);
exit (1);
}
memset (srcbuf + size, 0, newsize - size);
memset (dstbuf + size, 0, newsize - size);
}
if (snes_header_base == SNES_EROM)
{
int size2 = newsize - 32 * MBIT; // size of second ROM (16 Mbit if ToP)
// interleave the 32 Mbit ROM
snes_int_blocks (srcbuf, dstbuf + size2 + 16 * MBIT, dstbuf + size2,
32 * MBIT / 0x10000);
// interleave the second ROM
snes_int_blocks (srcbuf + 32 * MBIT, dstbuf + size2 / 2, dstbuf,
size2 / 0x10000);
if (pad > 0)
{
snes_mirror (dstbuf, 0, 4 * MBIT, 8 * MBIT);
snes_mirror (dstbuf, 8 * MBIT, 12 * MBIT, 16 * MBIT);
}
}
else if (total4Mbparts == 6)
{
snes_int_blocks (srcbuf, dstbuf + 16 * MBIT, dstbuf, 16 * MBIT / 0x10000);
snes_int_blocks (srcbuf + 16 * MBIT, dstbuf + 12 * MBIT,
dstbuf + 8 * MBIT, (size - 16 * MBIT) / 0x10000);
if (pad > 0)
{
snes_mirror (dstbuf, 8 * MBIT, 10 * MBIT, 12 * MBIT);
snes_mirror (dstbuf, 12 * MBIT, 14 * MBIT, 16 * MBIT);
}
}
else
{
n = newsize / 2;
snes_int_blocks (srcbuf, dstbuf + n, dstbuf, newsize / 0x10000);
if (pad > 0)
{
half_size_4Mb = (size / 2) & ~(4 * MBIT - 1);
half_size_1Mb = (size / 2 + MBIT - 1) & ~(MBIT - 1);
snes_mirror (dstbuf, half_size_4Mb, half_size_1Mb, n);
snes_mirror (dstbuf, n + half_size_4Mb, n + half_size_1Mb, newsize);
}
}
}
else
{
if (total4Mbparts > 8)
{
fprintf (stderr, "ERROR: This ROM > 32 Mbit LoROM -- can't convert\n");
return -1;
}
ucon64_fread (header, 0, rominfo->buheader_len > GD_HEADER_LEN ?
GD_HEADER_LEN : rominfo->buheader_len, ucon64.rom);
reset_header (header);
memcpy (header, "GAME DOCTOR SF 3", 0x10);
if (snes_sramsize == 8 * 1024)
header[0x10] = (unsigned char) 0x81; // 64 kb
else if (snes_sramsize == 2 * 1024)
header[0x10] = (unsigned char) 0x82; // 16 kb
else
header[0x10] = (unsigned char) 0x80; // 0 kb or 256 kb
if (total4Mbparts <= 1)
memcpy (&header[0x11], gd3_lorom_4mb_map, GD3_HEADER_MAPSIZE);
else if (total4Mbparts <= 2)
memcpy (&header[0x11], gd3_lorom_8mb_map, GD3_HEADER_MAPSIZE);
else if (total4Mbparts <= 4)
memcpy (&header[0x11], gd3_lorom_16mb_map, GD3_HEADER_MAPSIZE);
else
memcpy (&header[0x11], gd3_lorom_32mb_map, GD3_HEADER_MAPSIZE);
if (snes_sramsize != 0)
{
header[0x24] = 0x40;
header[0x28] = 0x40;
}
dstbuf = srcbuf;
newsize = size;
}
set_nsrt_info (rominfo, (unsigned char *) &header);
make_gd_name (ucon64.rom, rominfo, dest_name, srcbuf, newsize);
// here we could also use NULL as second argument for
// ucon64_file_handler(), because we've already loaded the data
ucon64_file_handler (dest_name, src_name, OF_FORCE_BASENAME);
ucon64_fwrite (header, 0, GD_HEADER_LEN, dest_name, "wb");
ucon64_fwrite (dstbuf, GD_HEADER_LEN, newsize, dest_name, "ab");
printf (ucon64_msg[WROTE], dest_name);
remove_temp_file ();
free (srcbuf);
if (snes_hirom)
free (dstbuf);
return 0;
}
// header format is specified in src/backup/ufo.h
int
snes_ufo (st_rominfo_t *rominfo)
{
st_ufo_header_t header;
int size = ucon64.file_size - rominfo->buheader_len;
char src_name[FILENAME_MAX], dest_name[FILENAME_MAX];
ucon64_fread (&header, 0, rominfo->buheader_len > (int) UFO_HEADER_LEN ?
(int) UFO_HEADER_LEN : rominfo->buheader_len, ucon64.rom);
reset_header (&header);
header.multi = snes_split ? 0x40 : 0; // TODO
memcpy (header.id, "SUPERUFO", 8);
header.isrom = 1;
header.banktype = snes_hirom ? 0 : 1;
if (snes_sramsize > 32 * 1024)
header.sram_size = 8;
else if (snes_sramsize > 8 * 1024) // 64 kb < size <= 256 kb
header.sram_size = 3;
else if (snes_sramsize > 2 * 1024) // 16 kb < size <= 64 kb
header.sram_size = 2;
else if (snes_sramsize > 0) // 1 - 16 kb
header.sram_size = 1;
// header.sram_size is already ok for snes_sramsize == 0
header.sram_type = snes_hirom ? 0 : 3;
set_nsrt_info (rominfo, (unsigned char *) &header);
strcpy (src_name, ucon64.rom);
strcpy (dest_name, ucon64.rom);
set_suffix (dest_name, ".ufo");
ucon64_file_handler (dest_name, src_name, 0);
if (snes_hirom)
{
unsigned char *srcbuf, *dstbuf;
int half_size_4Mb, half_size_1Mb,
newsize = size >= 10 * MBIT && size <= 12 * MBIT ?
12 * MBIT : ((size + MBIT - 1) & ~(MBIT - 1)),
half_newsize = newsize / 2, pad = (newsize - size) / 2;
header.size_low = newsize / 8192;
header.size_high = newsize / 8192 >> 8;
header.size = newsize / MBIT;
if (snes_sramsize != 0)
header.sram_a20_a21 = 0x0c; // try 3 if game gives protection message
header.sram_a22_a23 = 2;
// Tales of Phantasia (J) & Dai Kaiju Monogatari 2 (J) [14-17]: 0 0x0e 0 0
if (!(srcbuf = (unsigned char *) malloc (size)))
{
fprintf (stderr, ucon64_msg[ROM_BUFFER_ERROR], size);
exit (1);
}
if (!(dstbuf = (unsigned char *) malloc (newsize)))
{
fprintf (stderr, ucon64_msg[ROM_BUFFER_ERROR], newsize);
exit (1);
}
ucon64_fread (srcbuf, rominfo->buheader_len, size, src_name);
if (rominfo->interleaved)
snes_deinterleave (rominfo, &srcbuf, size);
if (newsize > size)
memset (dstbuf + size, 0, newsize - size);
snes_int_blocks (srcbuf, dstbuf + half_newsize, dstbuf, size / 0x10000);
if (pad > 0)
{
half_size_4Mb = (size / 2) & ~(4 * MBIT - 1);
half_size_1Mb = (size / 2 + MBIT - 1) & ~(MBIT - 1);
snes_mirror (dstbuf, half_size_4Mb, half_size_1Mb, half_newsize);
snes_mirror (dstbuf, half_newsize + half_size_4Mb,
half_newsize + half_size_1Mb, newsize);
}
ucon64_fwrite (&header, 0, UFO_HEADER_LEN, dest_name, "wb");
ucon64_fwrite (dstbuf, UFO_HEADER_LEN, newsize, dest_name, "ab");
free (srcbuf);
free (dstbuf);
}
else // LoROM
{
header.size_low = size / 8192;
header.size_high = size / 8192 >> 8;
header.size = size / MBIT;
if (snes_sramsize == 0)
{
// check if the game uses a DSP chip
if (snes_header.rom_type == 3 || snes_header.rom_type == 5 ||
snes_header.rom_type == 0xf6)
{
header.sram_a15 = 1;
header.sram_a20_a21 = 0x0c;
}
else // no SRAM & doesn't use a DSP chip
{
header.sram_a22_a23 = 2;
header.sram_type = 0;
}
}
else // cartridge contains SRAM
{
header.sram_a15 = 2; // try 1 if game gives protection error
header.sram_a20_a21 = 0x0f;
header.sram_a22_a23 = 3;
}
ucon64_fwrite (&header, 0, UFO_HEADER_LEN, dest_name, "wb");
if (rominfo->interleaved)
write_deinterleaved_data (rominfo, src_name, dest_name, size, UFO_HEADER_LEN);
else
fcopy (src_name, rominfo->buheader_len, size, dest_name, "ab");
}
printf (ucon64_msg[WROTE], dest_name);
remove_temp_file ();
return 0;
}
int
snes_make_gd_names (const char *filename, st_rominfo_t *rominfo, char **names)
{
char dest_name[FILENAME_MAX];
int nparts, surplus, n, n_names = 0, size = ucon64.file_size - rominfo->buheader_len;
// Don't use PARTSIZE here, because the Game Doctor doesn't support
// arbitrary part sizes
nparts = size / (8 * MBIT);
surplus = size % (8 * MBIT);
make_gd_name (filename, rominfo, dest_name, NULL, size);
strupr (dest_name);
dest_name[7] = 'A';
dest_name[8] = 0;
// avoid trouble with filenames containing spaces
for (n = 3; n < 7; n++) // skip "SF" and first digit
if (dest_name[n] == ' ')
dest_name[n] = '_';
if (snes_hirom && size <= 16 * MBIT)
{
// 8 Mbit or less HiROMs, X is used to pad filename to 8 (SF4###XA)
if (size < 10 * MBIT)
dest_name[8 - 2] = 'X';
strcpy (names[n_names++], dest_name);
dest_name[8 - 1]++;
strcpy (names[n_names++], dest_name);
}
else
{
for (n = 0; n < nparts; n++)
{
strcpy (names[n_names++], dest_name);
dest_name[8 - 1]++;
}
if (surplus != 0)
strcpy (names[n_names++], dest_name);
}
if (n_names == 1)
names[0][7] = 0; // 'A' causes trouble for 1-part split files
return n_names;
}
static void
snes_split_gd3 (st_rominfo_t *rominfo, int size)
{
char dest_name[FILENAME_MAX], *names[GD3_MAX_UNITS],
names_mem[GD3_MAX_UNITS][9];
int nparts, surplus, n, half_size, name_i = 0;
// Don't use part_size here, because the Game Doctor doesn't support
// arbitrary part sizes
nparts = size / (8 * MBIT);
surplus = size % (8 * MBIT);
// We don't want to malloc() ridiculously small chunks (of 9 bytes)
for (n = 0; n < GD3_MAX_UNITS; n++)
names[n] = names_mem[n];
snes_make_gd_names (ucon64.rom, rominfo, (char **) names);
if (snes_hirom && size <= 16 * MBIT)
{
half_size = size / 2;
sprintf (dest_name, "%s.078", names[name_i++]);
ucon64_output_fname (dest_name, OF_FORCE_BASENAME);
// don't write backups of parts, because one name is used
fcopy (ucon64.rom, 0, half_size + rominfo->buheader_len, dest_name, "wb");
printf (ucon64_msg[WROTE], dest_name);
sprintf (dest_name, "%s.078", names[name_i++]);
ucon64_output_fname (dest_name, OF_FORCE_BASENAME);
fcopy (ucon64.rom, half_size + rominfo->buheader_len, size - half_size, dest_name, "wb");
printf (ucon64_msg[WROTE], dest_name);
}
else
{
for (n = 0; n < nparts; n++)
{
// don't write backups of parts, because one name is used
sprintf (dest_name, "%s.078", names[name_i++]);
ucon64_output_fname (dest_name, OF_FORCE_BASENAME);
fcopy (ucon64.rom, n * 8 * MBIT + (n ? rominfo->buheader_len : 0),
8 * MBIT + (n ? 0 : rominfo->buheader_len), dest_name, "wb");
printf (ucon64_msg[WROTE], dest_name);
}
if (surplus != 0)
{
// don't write backups of parts, because one name is used
sprintf (dest_name, "%s.078", names[name_i++]);
ucon64_output_fname (dest_name, OF_FORCE_BASENAME);
fcopy (ucon64.rom, n * 8 * MBIT + (n ? rominfo->buheader_len : 0),
surplus + (n ? 0 : rominfo->buheader_len), dest_name, "wb");
printf (ucon64_msg[WROTE], dest_name);
}
}
// An index file is not used by the GD, but by the MGD. We don't have a
// special function for splitting MGD files, so we do it here.
if (!rominfo->buheader_len)
mgd_write_index_file (names, name_i);
}
static void
snes_split_ufo (st_rominfo_t *rominfo, int size, int part_size)
{
char header[512], dest_name[FILENAME_MAX], *p;
int nparts, surplus, n, nbytesdone;
if (snes_hirom)
{
if (size > 32 * MBIT)
{
fprintf (stderr, "ERROR: HiROM > 32 Mbit -- conversion not yet implemented\n");
return;
}
if (UCON64_ISSET (ucon64.part_size))
printf ("WARNING: Splitting Super UFO HiROM, ignoring switch "OPTION_LONG_S"ssize\n");
}
strcpy (dest_name, ucon64.rom);
set_suffix (dest_name, ".1gm");
ucon64_output_fname (dest_name, 0);
p = strrchr (dest_name, '.') + 1;
ucon64_fread (header, 0, UFO_HEADER_LEN, ucon64.rom);
if (snes_hirom)
{
typedef struct
{
unsigned char value;
unsigned char size;
unsigned char list[8];
} st_value_list_t;
st_value_list_t size_to_partsizes[5] =
{
{ 2, 2, {1, 1} },
{ 4, 2, {2, 2} },
{ 12, 4, {4, 2, 4, 2} }, // 10 Mbit files are padded by snes_ufo()
{ 20, 6, {4, 4, 2, 4, 4, 2} },
{ 32, 8, {4, 4, 4, 4, 4, 4, 4, 4} }
}, *size_to_partsizes_ptr = NULL;
st_value_list_t size_to_flags[8] =
{
{ 2, 2, {0x10, 0} },
{ 4, 2, {0x10, 0} },
{ 8, 2, {0x10, 0} },
{ 12, 4, {0x40, 0x10, 0x10, 0} },
{ 16, 4, {0x40, 0x10, 0x10, 0} },
{ 20, 6, {0x40, 0x40, 0x10, 0x10, 0x10, 0} },
{ 24, 6, {0x40, 0x40, 0x10, 0x10, 0x10, 0} },
{ 32, 8, {0x40, 0x40, 0x40, 0x10, 0x10, 0x10, 0x10, 0} }
}, *size_to_flags_ptr = NULL;
int x = size / MBIT;
nparts = 0;
surplus = 0;
for (n = 0; n < 4; n++)
if (size_to_partsizes[n].value == x)
{
size_to_partsizes_ptr = &size_to_partsizes[n];
nparts = size_to_partsizes[n].size;
surplus = 0;
}
if (!size_to_partsizes_ptr) // size was not found
{
size_to_partsizes_ptr = &size_to_partsizes[4];
nparts = size / (4 * MBIT);
surplus = size % (4 * MBIT);
}
for (n = 0; n < 7; n++)
if (size_to_flags[n].value == x)
size_to_flags_ptr = &size_to_flags[n];
if (!size_to_flags_ptr)
size_to_flags_ptr = &size_to_flags[7];
nbytesdone = rominfo->buheader_len;
for (n = 0; n < nparts; n++)
{
part_size = size_to_partsizes_ptr->list[n] * MBIT;
header[0] = part_size / 8192;
header[1] = part_size / 8192 >> 8;
header[2] = size_to_flags_ptr->list[n];
if (surplus == 0 && n == nparts - 1)
header[2] = 0; // last file -> clear bit 6
ucon64_fwrite (&header, 0, SWC_HEADER_LEN, dest_name, "wb");
fcopy (ucon64.rom, nbytesdone, part_size, dest_name, "ab");
printf (ucon64_msg[WROTE], dest_name);
nbytesdone += part_size;
(*p)++;
}
}
else
{
nparts = size / part_size;
surplus = size % part_size;
header[0] = part_size / 8192;
header[1] = part_size / 8192 >> 8;
header[2] |= 0x40;
nbytesdone = rominfo->buheader_len;
for (n = 0; n < nparts; n++)
{
if (surplus == 0 && n == nparts - 1)
header[2] = 0; // last file -> clear bit 6
ucon64_fwrite (&header, 0, SWC_HEADER_LEN, dest_name, "wb");
fcopy (ucon64.rom, nbytesdone, part_size, dest_name, "ab");
printf (ucon64_msg[WROTE], dest_name);
nbytesdone += part_size;
(*p)++;
}
}
if (surplus != 0)
{
header[0] = surplus / 8192;
header[1] = surplus / 8192 >> 8;
header[2] = 0; // last file -> clear bit 6
ucon64_fwrite (&header, 0, SWC_HEADER_LEN, dest_name, "wb");
fcopy (ucon64.rom, nbytesdone, surplus, dest_name, "ab");
printf (ucon64_msg[WROTE], dest_name);
}
}
static void
snes_split_smc (st_rominfo_t *rominfo, int size, int part_size)
// this function splits both SWC and FIG files
{
char header[512], dest_name[FILENAME_MAX], *p;
int nparts, surplus, n;
nparts = size / part_size;
surplus = size % part_size;
strcpy (dest_name, ucon64.rom);
set_suffix (dest_name, ".1");
ucon64_output_fname (dest_name, 0);
p = strrchr (dest_name, '.') + 1;
ucon64_fread (header, 0, SWC_HEADER_LEN, ucon64.rom);
header[0] = part_size / 8192;
header[1] = part_size / 8192 >> 8;
// if header[2], bit 6 == 0 -> SWC/FIG knows this is the last file of the ROM
header[2] |= 0x40;
for (n = 0; n < nparts; n++)
{
if (surplus == 0 && n == nparts - 1)
header[2] &= ~0x40; // last file -> clear bit 6
// don't write backups of parts, because one name is used
ucon64_fwrite (header, 0, SWC_HEADER_LEN, dest_name, "wb");
fcopy (ucon64.rom, n * part_size + rominfo->buheader_len, part_size, dest_name, "ab");
printf (ucon64_msg[WROTE], dest_name);
(*p)++;
}
if (surplus != 0)
{
header[0] = surplus / 8192;
header[1] = surplus / 8192 >> 8;
header[2] &= ~0x40; // last file -> clear bit 6
// don't write backups of parts, because one name is used
ucon64_fwrite (header, 0, SWC_HEADER_LEN, dest_name, "wb");
fcopy (ucon64.rom, n * part_size + rominfo->buheader_len, surplus, dest_name, "ab");
printf (ucon64_msg[WROTE], dest_name);
}
}
#define PARTSIZE (8 * MBIT) // default split part size
int
snes_s (st_rominfo_t *rominfo)
{
int size = ucon64.file_size - rominfo->buheader_len, part_size;
if (UCON64_ISSET (ucon64.part_size) && !(type == GD3 || type == UFO))
{
part_size = ucon64.part_size;
/*
Don't allow too small part sizes, because then the files that come
after the file with suffix ".9" will get filenames that can't be stored
on a FAT filesystem (filesystem that SWC requires to be on diskette).
For example the first file after the file with suffix ".9" will get
suffix ".:". The SWC does ask for that filename though.
Also don't base the minimum part size on the actual file size, because
that will probably only confuse users.
We ignore the few ROMs that are greater than 32 MBit. Just use -ssize
to specify a larger part size for those.
*/
if (part_size < 4 * MBIT)
{
fprintf (stderr,
"ERROR: Split part size must be larger than or equal to 4 Mbit\n");
return -1;
}
}
else
part_size = PARTSIZE;
if (type == GD3)
// part_size is ignored for Game Doctor
{
if (size < 4 * MBIT && size != 2 * MBIT)
{ // "&& size != 2 * MBIT" is a fix for BS Chrono Trigger - Jet Bike Special (J)
printf ("NOTE: ROM size is smaller than 4 Mbit -- won't be split\n");
return -1;
}
}
else if (type == UFO && snes_hirom)
{
if (size < 2 * MBIT)
{
printf ("NOTE: ROM size is smaller than 2 Mbit -- won't be split\n");
return -1;
}
}
else if (size <= part_size)
{
printf ("NOTE: ROM size is smaller than or equal to %d Mbit -- won't be split\n",
part_size / MBIT);
return -1;
}
if (!rominfo->buheader_len || type == GD3) // GD3 format
{
if (UCON64_ISSET (ucon64.part_size))
printf ("WARNING: ROM will be split as Game Doctor SF3 ROM, ignoring switch "OPTION_LONG_S"ssize\n");
snes_split_gd3 (rominfo, size);
}
else if (type == UFO)
snes_split_ufo (rominfo, size, part_size);
else
snes_split_smc (rominfo, size, part_size);
return 0;
}
int
snes_j (st_rominfo_t *rominfo)
{
char src_name[FILENAME_MAX], dest_name[FILENAME_MAX], *p = NULL;
int block_size, total_size = 0, header_len = rominfo->buheader_len;
strcpy (src_name, ucon64.rom);
strcpy (dest_name, ucon64.rom);
set_suffix (dest_name, ".tmp");
ucon64_file_handler (dest_name, NULL, 0);
fcopy (src_name, 0, rominfo->buheader_len, dest_name, "wb"); // copy header (if any)
p = strrchr (src_name, '.');
if (p == NULL) // filename doesn't contain a period
p = src_name + strlen (src_name) - 1;
else
(type == GD3 || type == MGD_SNES) ? p-- : p++;
// Split GD3 files don't have a header _except_ the first one
block_size = fsizeof (src_name) - header_len;
while (fcopy (src_name, header_len, block_size, dest_name, "ab") != -1)
{
printf ("Joined: %s\n", src_name);
total_size += block_size;
(*p)++;
if (type == GD3)
header_len = 0;
block_size = fsizeof (src_name) - header_len;
}
if (rominfo->buheader_len && type != GD3)
{ // fix header
ucon64_fputc (dest_name, 0, total_size / 8192, "r+b"); // # 8K blocks low byte
ucon64_fputc (dest_name, 1, total_size / 8192 >> 8, "r+b"); // # 8K blocks high byte
ucon64_fputc (dest_name, 2, ucon64_fgetc (dest_name, 2) & ~0x40, "r+b"); // last file -> clear bit 6
}
printf (ucon64_msg[WROTE], dest_name);
return 0;
}
int
snes_k (st_rominfo_t *rominfo)
/*
See the document "src/backup/SWC-compatibility.txt".
Don't touch this code if you don't know what you're doing!
Some SNES games check to see how much SRAM is connected to the SNES as a form
of copy protection. As most copiers have 256 kbits standard, the game will
know it's running on a backup unit and stop to prevent people copying the
games. However, newer copiers like the SWC DX2 get around this detection by
limiting the SRAM size for the game to the size specified in the backup unit
header.
(original uCON)
8f/9f XX YY 70 cf/df XX YY 70 d0
=> 8f/9f XX YY 70 cf/df XX YY 70 ea ea if snes_sramsize == 64 kbits
=> 8f/9f XX YY 70 cf/df XX YY 70 80
sta $70YYXX/sta $70YYXX,x; cmp $70YYXX/cmp $70YYXX,x; bne
TODO: The following three codes should be verified for many games. For example,
the first code replaces D0 (bne) with 80 (bra), but for some games (like Donkey
Kong Country (U|E)) it should do the opposite, i.e., writing EA EA (nop nop).
8f/9f XX YY 30/31/32/33 cf/df XX YY 30/31/32/33 d0
=> 8f/9f XX YY 30/31/32/33 cf/df XX YY 30/31/32/33 80
8f/9f XX YY 30/31/32/33 cf/df XX YY 30/31/32/33 f0 beq
=> 8f/9f XX YY 30/31/32/33 cf/df XX YY 30/31/32/33 ea ea nop; nop
8f/9f XX YY 30/31/32/33 af XX YY 30/31/32/33 c9 XX YY d0 bne
=> 8f/9f XX YY 30/31/32/33 af XX YY 30/31/32/33 c9 XX YY 80 bra
(uCON64)
- Mega Man X
8f/9f XX YY 70 cf/df XX YY 70 f0
=> 8f/9f XX YY 70 cf/df XX YY 70 ea ea
The code above could be combined with the first original uCON code. However, we
don't want to copy (or remove) the SRAM size determined behaviour, without
knowing when that is necessary.
- Mega Man X
af/bf XX 80 00 cf/df XX 80 40 f0
=> af/bf XX 80 00 cf/df XX 80 40 80
lda $0080XX/lda $0080XX,x; cmp $408000/cmp $408000,x; beq ...
- Demon's Crest (af, 80, cf) / Breath of Fire II (bf, c0, df)
af/bf XX ff 80/c0 cf/df XX ff 40 f0
=> af/bf XX ff 80/c0 cf/df XX ff 40 80
- Breath of Fire II (bf, 30, df, 31)
af/bf XX YY 30/31/32/33 cf/df XX YY 30/31/32/33 f0
=> af/bf XX YY 30/31/32/33 cf/df XX YY 30/31/32/33 80
- Super Metroid
a9 00 00 a2 fe 1f df 00 00 70 d0 lda #$0000; ldx #$1ffe; cmp $700000,x; bne ...
=> a9 00 00 a2 fe 1f df 00 00 70 ea ea lda #$0000; ldx #$1ffe; cmp $700000,x; nop; nop
- Tetris Attack
(generic)
8f XX YY 70 af XX YY 70 c9 XX YY d0
=> 8f XX YY 70 af XX YY 70 c9 XX YY 80
(game specific)
c2 30 ad fc 1f c9 50 44 d0
=> c2 30 4c d1 80 c9 50 44 d0
- Uniracers/Unirally
8f XX YY 77 e2 XX af XX YY 77 c9 XX f0
=> 8f XX YY 77 e2 XX af XX YY 77 c9 XX 80
- Mario no Super Picross
8f/af XX YY b0 cf XX YY b1 d0
=> 8f/af XX YY b0 cf XX YY b1 ea ea
- most probably only Killer Instinct
5c 7f d0 83 18 fb 78 c2 30 jmp $83d07f; clc; xce; sei; rep #$30
=> ea ea ea ea ea ea ea ea ea nop; nop; nop; nop; nop; nop; nop; nop; nop
- most probably only Donkey Kong Country (8f, 30, cf, 30)
Note that this code must be searched for before the less specific uCON code.
8f/9f 57/59 60/68 30/31/32/33 cf/df 57/59 60 30/31/32/33 d0
=> 8f/9f 57/59 60/68 30/31/32/33 cf/df 57/59 60 30/31/32/33 ea ea
- most probably only Diddy's Kong Quest
26 38 e9 48 12 c9 af 71 f0
=> 26 38 e9 48 12 c9 af 71 80
- most probably only Diddy's Kong Quest
a0 5c 2f 77 32 e9 c7 04 f0
=> a0 5c 2f 77 32 e9 c7 04 80
- most probably only Diddy's Kong Quest
'K' 'O' 'N' 'G' 00 f8 f7
=> 'K' 'O' 'N' 'G' 00 f8 f8
TODO: make sense of Diddy's Kong Quest codes
- most probably only BS The Legend of Zelda Remix
22 08 5c 10 b0 28 jsl $105c08; bcs ...
=> ea ea ea ea ea ea nop; nop; nop; nop; nop; nop
- most probably only BS The Legend of Zelda Remix (enables music)
da e2 30 c9 01 f0 18 c9 02
=> da e2 30 c9 09 f0 18 c9 07
- most probably only BS The Legend of Zelda Remix (enables music)
29 ff 00 c9 07 00 90 16
=> 29 ff 00 c9 00 00 90 16
- most probably only Kirby's Dream Course
ca 10 f8 38 ef 1a 80 81 8d
=> ca 10 f8 38 ef 1a 80 81 9c
- most probably only Kirby's Dream Course
81 ca 10 f8 cf 39 80 87 f0
=> 81 ca 10 f8 cf 39 80 87 80
- probably only Earthbound
84 26 ad 39 b5 d0 1a
=> 84 26 ad 39 b5 ea ea
I don't know what this does, but it isn't necessary to start the game.
- probably only Earthbound
10 f8 38 ef ef ff c1
=> 10 f8 38 ea a9 00 00
- probably only Earthbound
10 f8 38 ef f2 fd c3 f0
=> 10 f8 38 ea a9 00 00 80
Same here.
- Dixie Kong's Double Trouble E. U version looks like it already has been "patched"
a9 c3 80 dd ff ff f0 6c
=> a9 c3 f0 cc ff ff 80 7d
- Front Mission - Gun Hazard
d0 f4 ab cf ae ff 00 d0 01
=> d0 f4 ab cf ae ff 00 d0 00
Modification protection. Not needed to play the game on an NTSC SNES. Needed
when it has been patched with -f.
*/
{
char header[512], src_name[FILENAME_MAX], dest_name[FILENAME_MAX],
buffer[32 * 1024];
FILE *srcfile, *destfile;
int bytesread, n = 0, n_extra_patterns, n2;
st_cm_pattern_t *patterns = NULL;
strcpy (src_name, "snescopy.txt");
// First try the current directory, then the configuration directory
if (access (src_name, F_OK | R_OK) == -1)
sprintf (src_name, "%s" FILE_SEPARATOR_S "snescopy.txt", ucon64.configdir);
n_extra_patterns = build_cm_patterns (&patterns, src_name, ucon64.quiet == -1 ? 1 : 0);
if (n_extra_patterns >= 0)
printf ("Found %d additional code%s in %s\n",
n_extra_patterns, n_extra_patterns != 1 ? "s" : "", src_name);
puts ("Attempting crack...");
strcpy (src_name, ucon64.rom);
strcpy (dest_name, ucon64.rom);
ucon64_file_handler (dest_name, src_name, 0);
if ((srcfile = fopen (src_name, "rb")) == NULL)
{
fprintf (stderr, ucon64_msg[OPEN_READ_ERROR], src_name);
return -1;
}
if ((destfile = fopen (dest_name, "wb")) == NULL)
{
fprintf (stderr, ucon64_msg[OPEN_WRITE_ERROR], dest_name);
return -1;
}
if (rominfo->buheader_len) // copy header (if present)
{
fread (header, 1, SWC_HEADER_LEN, srcfile);
fseek (srcfile, rominfo->buheader_len, SEEK_SET);
fwrite (header, 1, SWC_HEADER_LEN, destfile);
}
while ((bytesread = fread (buffer, 1, 32 * 1024, srcfile)))
{ // '!' == ASCII 33 (\x21), '*' == 42 (\x2a)
// First use the extra patterns, so that their precedence is higher than
// the built-in patterns
for (n2 = 0; n2 < n_extra_patterns; n2++)
n += change_mem2 (buffer, bytesread,
patterns[n2].search,
patterns[n2].search_size,
patterns[n2].wildcard,
patterns[n2].escape,
patterns[n2].replace,
patterns[n2].replace_size,
patterns[n2].offset,
patterns[n2].sets);
// SRAM
if (snes_sramsize == 8 * 1024) // 8 kB == 64 kb
{
n += change_mem (buffer, bytesread, "!**\x70!**\x70\xd0", 9, '*', '!', "\xea\xea", 2, 0,
"\x8f\x9f", 2, "\xcf\xdf", 2);
// actually Kirby's Dream Course, Lufia II - Rise of the Sinistrals
n += change_mem (buffer, bytesread, "!**\x70!**\x70\xf0", 9, '*', '!', "\x80", 1, 0,
"\x8f\x9f", 2, "\xcf\xdf", 2);
#if 1 // TODO: check which games really need this
n += change_mem (buffer, bytesread, "!**!!**!\xf0", 9, '*', '!', "\x80", 1, 0,
"\x8f\x9f", 2, "\x30\x31\x32\x33", 4, "\xcf\xdf", 2, "\x30\x31\x32\x33", 4);
#endif
}
else
{
n += change_mem (buffer, bytesread, "!**\x70!**\x70\xd0", 9, '*', '!', "\x80", 1, 0,
"\x8f\x9f", 2, "\xcf\xdf", 2);
// Mega Man X
n += change_mem (buffer, bytesread, "!**\x70!**\x70\xf0", 9, '*', '!', "\xea\xea", 2, 0,
"\x8f\x9f", 2, "\xcf\xdf", 2);
n += change_mem (buffer, bytesread, "!**!!**!\xf0", 9, '*', '!', "\xea\xea", 2, 0,
"\x8f\x9f", 2, "\x30\x31\x32\x33", 4, "\xcf\xdf", 2, "\x30\x31\x32\x33", 4);
}
n += change_mem (buffer, bytesread, "\x8f**\x77\xe2*\xaf**\x77\xc9*\xf0", 13, '*', '!', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "!!!!!!\x60!\xd0", 9, '*', '!', "\xea\xea", 2, 0,
"\x8f\x9f", 2, "\x57\x59", 2, "\x60\x68", 2, "\x30\x31\x32\x33", 4,
"\xcf\xdf", 2, "\x57\x59", 2, "\x30\x31\x32\x33", 4);
n += change_mem (buffer, bytesread, "!**!!**!\xd0", 9, '*', '!', "\x80", 1, 0,
"\x8f\x9f", 2, "\x30\x31\x32\x33", 4, "\xcf\xdf", 2, "\x30\x31\x32\x33", 4);
n += change_mem (buffer, bytesread, "!**\xb0\xcf**\xb1\xd0", 9, '*', '!', "\xea\xea", 2, 0,
"\x8f\xaf", 2);
n += change_mem (buffer, bytesread, "!**!\xaf**!\xc9**\xd0", 12, '*', '!', "\x80", 1, 0,
"\x8f\x9f", 2, "\x30\x31\x32\x33", 4, "\x30\x31\x32\x33", 4);
n += change_mem (buffer, bytesread, "\xa9\x00\x00\xa2\xfe\x1f\xdf\x00\x00\x70\xd0", 11, '*', '!', "\xea\xea", 2, 0);
n += change_mem (buffer, bytesread, "\x8f**\x70\xaf**\x70\xc9**\xd0", 12, '*', '!', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "!**!!**!\xf0", 9, '*', '!', "\x80", 1, 0,
"\xaf\xbf", 2, "\x30\x31\x32\x33", 4, "\xcf\xdf", 2, "\x30\x31\x32\x33", 4);
// mirroring
n += change_mem (buffer, bytesread, "!*\x80\x00!*\x80\x40\xf0", 9, '*', '!', "\x80", 1, 0,
"\xaf\xbf", 2, "\xcf\xdf", 2);
n += change_mem (buffer, bytesread, "!*\xff!!*\xff\x40\xf0", 9, '*', '!', "\x80", 1, 0,
"\xaf\xbf", 2, "\x80\xc0", 2, "\xcf\xdf", 2);
// game specific
n += change_mem (buffer, bytesread, "\x5c\x7f\xd0\x83\x18\xfb\x78\xc2\x30", 9, '*', '!',
"\xea\xea\xea\xea\xea\xea\xea\xea\xea", 9, -8);
n += change_mem (buffer, bytesread, "KONG\x00\xf8\xf7", 7, '*', '!', "\xf8", 1, 0);
n += change_mem (buffer, bytesread, "\x26\x38\xe9\x48\x12\xc9\xaf\x71\xf0", 9, '*', '!', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "\xa0\x5c\x2f\x77\x32\xe9\xc7\x04\xf0", 9, '*', '!', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "\x22\x08\x5c\x10\xb0\x28", 6, '*', '!',
"\xea\xea\xea\xea\xea\xea", 6, -5);
n += change_mem (buffer, bytesread, "\xda\xe2\x30\xc9\x01\xf0\x18\xc9\x02", 9, '*', '!',
"\x09\xf0\x18\xc9\x07", 5, -4);
n += change_mem (buffer, bytesread, "\x29\xff\x00\xc9\x07\x00\x90\x16", 8, '*', '!', "\x00", 1, -3);
n += change_mem (buffer, bytesread, "\xca\x10\xf8\x38\xef\x1a\x80\x81\x8d", 9, '*', '!', "\x9c", 1, 0);
n += change_mem (buffer, bytesread, "\x81\xca\x10\xf8\xcf\x39\x80\x87\xf0", 9, '*', '!', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "\x84\x26\xad\x39\xb5\xd0\x1a", 7, '*', '!', "\xea\xea", 2, -1);
n += change_mem (buffer, bytesread, "\x10\xf8\x38\xef\xef\xff\xc1", 7, '*', '!',
"\xea\xa9\x00\x00", 4, -3);
n += change_mem (buffer, bytesread, "\x10\xf8\x38\xef\xf2\xfd\xc3\xf0", 8, '*', '!',
"\xea\xa9\x00\x00\x80", 5, -4);
n += change_mem (buffer, bytesread, "\xc2\x30\xad\xfc\x1f\xc9\x50\x44\xd0", 9, '*', '!', "\x4c\xd1\x80", 3, -6);
n += change_mem (buffer, bytesread, "\xa9\xc3\x80\xdd\xff\xff\xf0\x6c", 8, '*', '!', "\xf0\xcc\xff\xff\x80\x7d", 6, -5);
n += change_mem (buffer, bytesread, "\xd0\xf4\xab\xcf\xae\xff\x00\xd0\x01", 9, '*', '!', "\x00", 1, 0);
fwrite (buffer, 1, bytesread, destfile);
}
fclose (srcfile);
fclose (destfile);
cleanup_cm_patterns (&patterns, n_extra_patterns);
printf ("Found %d pattern%s\n", n, n != 1 ? "s" : "");
printf (ucon64_msg[WROTE], dest_name);
remove_temp_file ();
return n;
}
static int
snes_fix_pal_protection (st_rominfo_t *rominfo)
/*
This function searches for PAL protection codes. If it finds one it will
fix the code so that the game will run on an NTSC SNES.
Don't touch this code if you don't know what you're doing!
Search for Replace with
ad 3f 21 89 10 d0 ad 3f 21 89 10 80 - Terranigma
ad 3f 21 29 10 00 d0 ad 3f 21 29 10 00 80
ad 3f 21 89 10 00 d0 a9 10 00 89 10 00 d0 - Eric Cantona Football ?
ad 3f 21 29 10 cf bd ff XX f0 ad 3f 21 29 10 cf bd ff XX 80 - Pop'n Twinbee E
af 3f 21 00 29 10 d0 af 3f 21 00 29 10 80
af 3f 21 00 29 10 00 d0 af 3f 21 00 29 10 00 ea ea
af 3f 21 00 29 XX c9 XX f0 af 3f 21 00 29 XX c9 XX 80 - Secret of Mana E
a2 18 01 bd 27 20 89 10 00 f0 01 a2 18 01 bd 27 20 89 10 00 ea ea - Donkey Kong Country E
*/
{
char header[512], src_name[FILENAME_MAX], dest_name[FILENAME_MAX],
buffer[32 * 1024];
FILE *srcfile, *destfile;
int bytesread, n = 0, n_extra_patterns, n2;
st_cm_pattern_t *patterns = NULL;
strcpy (src_name, "snespal.txt");
// First try the current directory, then the configuration directory
if (access (src_name, F_OK | R_OK) == -1)
sprintf (src_name, "%s" FILE_SEPARATOR_S "snespal.txt", ucon64.configdir);
n_extra_patterns = build_cm_patterns (&patterns, src_name, ucon64.quiet == -1 ? 1 : 0);
if (n_extra_patterns >= 0)
printf ("Found %d additional code%s in %s\n",
n_extra_patterns, n_extra_patterns != 1 ? "s" : "", src_name);
puts ("Attempting to fix PAL protection code...");
strcpy (src_name, ucon64.rom);
strcpy (dest_name, ucon64.rom);
ucon64_file_handler (dest_name, src_name, 0);
if ((srcfile = fopen (src_name, "rb")) == NULL)
{
fprintf (stderr, ucon64_msg[OPEN_READ_ERROR], src_name);
return -1;
}
if ((destfile = fopen (dest_name, "wb")) == NULL)
{
fprintf (stderr, ucon64_msg[OPEN_WRITE_ERROR], dest_name);
return -1;
}
if (rominfo->buheader_len) // copy header (if present)
{
fread (header, 1, SWC_HEADER_LEN, srcfile);
fseek (srcfile, rominfo->buheader_len, SEEK_SET);
fwrite (header, 1, SWC_HEADER_LEN, destfile);
}
while ((bytesread = fread (buffer, 1, 32 * 1024, srcfile)))
{
// First use the extra patterns, so that their precedence is higher than
// the built-in patterns
for (n2 = 0; n2 < n_extra_patterns; n2++)
n += change_mem2 (buffer, bytesread,
patterns[n2].search,
patterns[n2].search_size,
patterns[n2].wildcard,
patterns[n2].escape,
patterns[n2].replace,
patterns[n2].replace_size,
patterns[n2].offset,
patterns[n2].sets);
n += change_mem (buffer, bytesread, "\xad\x3f\x21\x89\x10\xd0", 6, '\x01', '\x02', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "\xad\x3f\x21\x29\x10\x00\xd0", 7, '\x01', '\x02', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "\xad\x3f\x21\x89\x10\x00\xd0", 7, '\x01', '\x02', "\xa9\x10\x00", 3, -6);
n += change_mem (buffer, bytesread, "\xad\x3f\x21\x29\x10\xcf\xbd\xff\x01\xf0", 10, '\x01', '\x02', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "\xaf\x3f\x21\x00\x29\x10\xd0", 7, '\x01', '\x02', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "\xaf\x3f\x21\x00\x29\x10\x00\xd0", 8, '\x01', '\x02', "\xea\xea", 2, 0);
n += change_mem (buffer, bytesread, "\xaf\x3f\x21\x00\x29\x01\xc9\x01\xf0", 9, '\x01', '\x02', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "\xa2\x18\x01\xbd\x27\x20\x89\x10\x00\xf0\x01", 11, '*', '!', "\xea\xea", 2, -1);
fwrite (buffer, 1, bytesread, destfile);
}
fclose (srcfile);
fclose (destfile);
cleanup_cm_patterns (&patterns, n_extra_patterns);
printf ("Found %d pattern%s\n", n, n != 1 ? "s" : "");
printf (ucon64_msg[WROTE], dest_name);
remove_temp_file ();
return n;
}
static int
snes_fix_ntsc_protection (st_rominfo_t *rominfo)
/*
This function searches for NTSC protection codes. If it finds one it will
fix the code so that the game will run on a PAL SNES.
Don't touch this code if you don't know what you're doing!
Search for Replace with
3f 21 29/89 10 f0 3f 21 29/89 10 80
ad 3f 21 29 10 d0 ad 3f 21 29 10 ea ea
ad 3f 21 89 10 d0 ad 3f 21 89 10 80/(ea ea) - Live-a-Live (ea ea)
3f 21 29/89 10 00 f0 3f 21 29/89 10 00 80 - Clock Tower (29)
3f 21 29/89 10 00 d0 3f 21 29/89 10 00 ea ea - Mario no Super Picross (89)
3f 21 89 10 c2 XX f0 3f 21 89 10 c2 XX 80 - Front Mission - Gun Hazard
3f 21 89 10 c2 XX d0 3f 21 89 10 c2 XX ea ea - Robotrek
3f 21 29/89 10 c9 10 f0 3f 21 29/89 10 c9 10 80
ad 3f 21 29 10 c9 00 f0 ad 3f 21 29 10 c9 00 80/(ea ea) <= original uCON used 80
ad 3f 21 29 10 c9 00 d0 ad 3f 21 29 10 c9 00 80
ad 3f 21 29 10 c9 10 d0 ad 3f 21 29 10 c9 10 ea ea
3f 21 29 10 cf XX YY 80 f0 3f 21 29 10 cf XX YY 80 80 - Gokujyou Parodius/Tokimeki Memorial
ad 3f 21 8d XX YY 29 10 8d ad 3f 21 8d XX YY 29 00 8d - Dragon Ball Z - Super Butoden 2 ?
3f 21 00 29/89 10 f0 3f 21 00 29/89 10 80 - Kirby's Dream Course U (29)
af 3f 21 00 29/89 10 d0 af 3f 21 00 29/89 10 ea ea - Kirby No Kira Kizzu (29)/Final Fight Guy (89)
af 3f 21 00 29/89 10 00 f0 af 3f 21 00 29/89 10 00 80
af 3f 21 00 29 XX c9 XX f0 af 3f 21 00 29 XX c9 XX 80 - Seiken Densetsu 3
af 3f 21 00 29 10 80 2d 00 1b af 3f 21 00 29 00 80 2d 00 1b - Seiken Densetsu 2/Secret of Mana U
3f 21 00 89 10 c2 XX f0 3f 21 00 89 10 c2 XX 80 - Dragon - The Bruce Lee Story U
af 3f 21 00 XX YY 29 10 00 d0 af 3f 21 00 XX YY 29 10 00 ea ea - Fatal Fury Special ?
3f 21 c2 XX 29 10 00 f0 3f 21 c2 XX 29 10 00 80 - Metal Warriors
3f 21 c2 XX 29 10 00 d0 3f 21 c2 XX 29 10 00 ea ea - Dual Orb 2
af 3f 21 ea 89 10 00 d0 a9 00 00 ea 89 10 00 d0 - Super Famista 3 ?
a2 18 01 bd 27 20 89 10 00 d0 01 a2 18 01 bd 27 20 89 10 00 ea ea - Donkey Kong Country U
29 10 00 a2 00 00 c9 10 00 d0 29 10 00 a2 00 00 c9 10 00 80 - Wolfenstein 3D U
*/
{
char header[512], src_name[FILENAME_MAX], dest_name[FILENAME_MAX],
buffer[32 * 1024];
FILE *srcfile, *destfile;
int bytesread, n = 0, n_extra_patterns, n2;
st_cm_pattern_t *patterns = NULL;
strcpy (src_name, "snesntsc.txt");
// First try the current directory, then the configuration directory
if (access (src_name, F_OK | R_OK) == -1)
sprintf (src_name, "%s" FILE_SEPARATOR_S "snesntsc.txt", ucon64.configdir);
n_extra_patterns = build_cm_patterns (&patterns, src_name, ucon64.quiet == -1 ? 1 : 0);
if (n_extra_patterns >= 0)
printf ("Found %d additional code%s in %s\n",
n_extra_patterns, n_extra_patterns != 1 ? "s" : "", src_name);
puts ("Attempting to fix NTSC protection code...");
strcpy (src_name, ucon64.rom);
strcpy (dest_name, ucon64.rom);
ucon64_file_handler (dest_name, src_name, 0);
if ((srcfile = fopen (src_name, "rb")) == NULL)
{
fprintf (stderr, ucon64_msg[OPEN_READ_ERROR], src_name);
return -1;
}
if ((destfile = fopen (dest_name, "wb")) == NULL)
{
fprintf (stderr, ucon64_msg[OPEN_WRITE_ERROR], dest_name);
return -1;
}
if (rominfo->buheader_len) // copy header (if present)
{
fread (header, 1, SWC_HEADER_LEN, srcfile);
fseek (srcfile, rominfo->buheader_len, SEEK_SET);
fwrite (header, 1, SWC_HEADER_LEN, destfile);
}
while ((bytesread = fread (buffer, 1, 32 * 1024, srcfile)))
{
// First use the extra patterns, so that their precedence is higher than
// the built-in patterns
for (n2 = 0; n2 < n_extra_patterns; n2++)
n += change_mem2 (buffer, bytesread,
patterns[n2].search,
patterns[n2].search_size,
patterns[n2].wildcard,
patterns[n2].escape,
patterns[n2].replace,
patterns[n2].replace_size,
patterns[n2].offset,
patterns[n2].sets);
n += change_mem (buffer, bytesread, "\x3f\x21\x02\x10\xf0", 5, '\x01', '\x02', "\x80", 1, 0,
"\x29\x89", 2);
n += change_mem (buffer, bytesread, "\xad\x3f\x21\x29\x10\xd0", 6, '\x01', '\x02', "\xea\xea", 2, 0);
n += change_mem (buffer, bytesread, "\xad\x3f\x21\x89\x10\xd0", 6, '\x01', '\x02', "\xea\xea", 2, 0);
// The next statement could be the alternative for the previous one. Leave it
// disabled until we find a game that needs it.
// n += change_mem (buffer, bytesread, "\xad\x3f\x21\x89\x10\xd0", 6, '\x01', '\x02', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "\x3f\x21\x02\x10\x00\xf0", 6, '\x01', '\x02', "\x80", 1, 0,
"\x29\x89", 2);
n += change_mem (buffer, bytesread, "\x3f\x21\x02\x10\x00\xd0", 6, '\x01', '\x02', "\xea\xea", 2, 0,
"\x29\x89", 2);
n += change_mem (buffer, bytesread, "\x3f\x21\x89\x10\xc2\x01\xf0", 7, '\x01', '\x02', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "\x3f\x21\x89\x10\xc2\x01\xd0", 7, '\x01', '\x02', "\xea\xea", 2, 0);
n += change_mem (buffer, bytesread, "\x3f\x21\x02\x10\xc9\x10\xf0", 7, '\x01', '\x02', "\x80", 1, 0,
"\x29\x89", 2);
n += change_mem (buffer, bytesread, "\xad\x3f\x21\x29\x10\xc9\x00\xf0", 8, '\x01', '\x02', "\xea\xea", 2, 0);
n += change_mem (buffer, bytesread, "\xad\x3f\x21\x29\x10\xc9\x00\xd0", 8, '\x01', '\x02', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "\xad\x3f\x21\x29\x10\xc9\x10\xd0", 8, '\x01', '\x02', "\xea\xea", 2, 0);
n += change_mem (buffer, bytesread, "\x3f\x21\x29\x10\xcf\x01\x01\x80\xf0", 9, '\x01', '\x02', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "\xad\x3f\x21\x8d\x01\x01\x29\x10\x8d", 9, '\x01', '\x02', "\x00", 1, -1);
n += change_mem (buffer, bytesread, "\x3f\x21\x00\x02\x10\xf0", 6, '\x01', '\x02', "\x80", 1, 0,
"\x29\x89", 2);
n += change_mem (buffer, bytesread, "\xaf\x3f\x21\x00\x02\x10\xd0", 7, '\x01', '\x02', "\xea\xea", 2, 0,
"\x29\x89", 2);
n += change_mem (buffer, bytesread, "\xaf\x3f\x21\x00\x02\x10\x00\xf0", 8, '\x01', '\x02', "\x80", 1, 0,
"\x29\x89", 2);
n += change_mem (buffer, bytesread, "\xaf\x3f\x21\x00\x29\x01\xc9\x01\xf0", 9, '\x01', '\x02', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "\xaf\x3f\x21\x00\x29\x10\x80\x2d\x00\x1b", 10, '\x01', '\x02', "\x00", 1, -4);
n += change_mem (buffer, bytesread, "\x3f\x21\x00\x89\x10\xc2\x01\xf0", 8, '\x01', '\x02', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "\xaf\x3f\x21\x00\x01\x01\x29\x10\x00\xd0", 10, '\x01', '\x02', "\xea\xea", 2, 0);
n += change_mem (buffer, bytesread, "\x3f\x21\xc2\x01\x29\x10\x00\xf0", 8, '\x01', '\x02', "\x80", 1, 0);
n += change_mem (buffer, bytesread, "\x3f\x21\xc2\x01\x29\x10\x00\xd0", 8, '\x01', '\x02', "\xea\xea", 2, 0);
n += change_mem (buffer, bytesread, "\xaf\x3f\x21\xea\x89\x10\x00\xd0", 8, '\x01', '\x02', "\xa9\x00\x00", 3, -7);
n += change_mem (buffer, bytesread, "\xa2\x18\x01\xbd\x27\x20\x89\x10\x00\xd0\x01", 11, '*', '!', "\xea\xea", 2, -1);
n += change_mem (buffer, bytesread, "\x29\x10\x00\xa2\x00\x00\xc9\x10\x00\xd0", 10, '\x01', '\x02', "\x80", 1, 0);
fwrite (buffer, 1, bytesread, destfile);
}
fclose (srcfile);
fclose (destfile);
cleanup_cm_patterns (&patterns, n_extra_patterns);
printf ("Found %d pattern%s\n", n, n != 1 ? "s" : "");
printf (ucon64_msg[WROTE], dest_name);
remove_temp_file ();
return n;
}
int
snes_f (st_rominfo_t *rominfo)
// See the document "src/backup/NTSC-PAL notes.txt".
{
switch (snes_header.country)
{
// In the Philipines the television standard is NTSC, but do games made
// for the Philipines exist?
case 0: // Japan
case 1: // U.S.A.
return snes_fix_ntsc_protection (rominfo);
default:
return snes_fix_pal_protection (rominfo);
}
}
int
snes_l (st_rominfo_t *rominfo)
/*
The order is important. Don't touch this code if you don't know what you're doing!
Search for Replace with
(uCON64)
8c/8d/8e/8f 0d 42 9c 0d 42
01 0d 42 00 0d 42
a9 01 85 0d a9 00 85 0d // special one (used by Konami and Jaleco? sometimes)
a2 01 86 0d a2 00 86 0d
a0 01 84 0d a0 00 84 0d
(original uCON)
a9/a2 01 8d/8e 0d 42 a9/a2 00 8d/8e 0d 42
a9 01 00 8d 0d 42 a9 00 00 8d 0d 42
a9 01 8f 0d 42 00 a9 00 8f 0d 42 00
*/
{
char header[512], src_name[FILENAME_MAX], dest_name[FILENAME_MAX],
buffer[32 * 1024];
FILE *srcfile, *destfile;
int bytesread, n = 0, n_extra_patterns, n2;
st_cm_pattern_t *patterns = NULL;
strcpy (src_name, "snesslow.txt");
// First try the current directory, then the configuration directory
if (access (src_name, F_OK | R_OK) == -1)
sprintf (src_name, "%s" FILE_SEPARATOR_S "snesslow.txt", ucon64.configdir);
n_extra_patterns = build_cm_patterns (&patterns, src_name, ucon64.quiet == -1 ? 1 : 0);
if (n_extra_patterns >= 0)
printf ("Found %d additional code%s in %s\n",
n_extra_patterns, n_extra_patterns != 1 ? "s" : "", src_name);
puts ("Attempting SlowROM fix...");
strcpy (src_name, ucon64.rom);
strcpy (dest_name, ucon64.rom);
ucon64_file_handler (dest_name, src_name, 0);
if ((srcfile = fopen (src_name, "rb")) == NULL)
{
fprintf (stderr, ucon64_msg[OPEN_READ_ERROR], src_name);
return -1;
}
if ((destfile = fopen (dest_name, "wb")) == NULL)
{
fprintf (stderr, ucon64_msg[OPEN_WRITE_ERROR], dest_name);
return -1;
}
if (rominfo->buheader_len) // copy header (if present)
{
fread (header, 1, SWC_HEADER_LEN, srcfile);
fseek (srcfile, rominfo->buheader_len, SEEK_SET);
fwrite (header, 1, SWC_HEADER_LEN, destfile);
}
while ((bytesread = fread (buffer, 1, 32 * 1024, srcfile)))
{ // '!' == ASCII 33 (\x21), '*' == 42 (\x2a)
// First use the extra patterns, so that their precedence is higher than
// the built-in patterns
for (n2 = 0; n2 < n_extra_patterns; n2++)
n += change_mem2 (buffer, bytesread,
patterns[n2].search,
patterns[n2].search_size,
patterns[n2].wildcard,
patterns[n2].escape,
patterns[n2].replace,
patterns[n2].replace_size,
patterns[n2].offset,
patterns[n2].sets);
n += change_mem (buffer, bytesread, "!\x0d\x42", 3, '*', '!', "\x9c", 1, -2,
"\x8c\x8d\x8e\x8f", 4);
n += change_mem (buffer, bytesread, "\x01\x0d\x42", 3, '*', '!', "\x00", 1, -2);
n += change_mem (buffer, bytesread, "\xa9\x01\x85\x0d", 4, '*', '!', "\x00", 1, -2);
n += change_mem (buffer, bytesread, "\xa2\x01\x86\x0d", 4, '*', '!', "\x00", 1, -2);
n += change_mem (buffer, bytesread, "\xa0\x01\x84\x0d", 4, '*', '!', "\x00", 1, -2);
// original uCON
n += change_mem (buffer, bytesread, "!\x01!\x0d\x42", 5, '*', '!', "\x00", 1, -3,
"\xa9\xa2", 2, "\x8d\x8e", 2);
n += change_mem (buffer, bytesread, "\xa9\x01\x00\x8d\x0d\x42", 6, '*', '!', "\x00", 1, -4);
n += change_mem (buffer, bytesread, "\xa9\x01\x8f\x0d\x42\x00", 6, '*', '!', "\x00", 1, -4);
fwrite (buffer, 1, bytesread, destfile);
}
fclose (srcfile);
fclose (destfile);
cleanup_cm_patterns (&patterns, n_extra_patterns);
printf ("Found %d pattern%s\n", n, n != 1 ? "s" : "");
printf (ucon64_msg[WROTE], dest_name);
remove_temp_file ();
return n;
}
int
snes_n (st_rominfo_t *rominfo, const char *name)
{
char buf[SNES_NAME_LEN], dest_name[FILENAME_MAX];
int size = ucon64.file_size - rominfo->buheader_len, header_start,
name_len = (bs_dump || st_dump) ? 16 : SNES_NAME_LEN;
memset (buf, ' ', name_len);
strncpy (buf, name, strlen (name) > (unsigned int) name_len ?
(unsigned int) name_len : strlen (name));
strcpy (dest_name, ucon64.rom);
ucon64_file_handler (dest_name, NULL, 0);
fcopy (ucon64.rom, 0, ucon64.file_size, dest_name, "wb");
if (rominfo->interleaved)
header_start = SNES_HEADER_START + (snes_hirom ? 0 : size / 2); // (Ext.) HiROM : LoROM
else if (st_dump) // ignore interleaved ST dumps
header_start = 8 * MBIT;
else
header_start = rominfo->header_start;
ucon64_fwrite (buf, header_start + rominfo->buheader_len + 16, name_len, dest_name, "r+b");
printf (ucon64_msg[WROTE], dest_name);
return 0;
}
int
snes_chk (st_rominfo_t *rominfo)
{
char buf[4], dest_name[FILENAME_MAX];
int size = ucon64.file_size - rominfo->buheader_len, header_start;
strcpy (dest_name, ucon64.rom);
ucon64_file_handler (dest_name, NULL, 0);
fcopy (ucon64.rom, 0, ucon64.file_size, dest_name, "wb");
/*
The internal checksum bytes have been included in the checksum
calculation, but they will be changed after this function returns. We
account for that. Otherwise we could have to run uCON64 on the ROM twice.
*/
rominfo->current_internal_crc += (-snes_header.inverse_checksum_low -
snes_header.inverse_checksum_high -
snes_header.checksum_low -
snes_header.checksum_high) +
2 * 0xff; // + 2 * 0;
// change inverse checksum
buf[0] = 0xffff - rominfo->current_internal_crc; // low byte
buf[1] = (0xffff - rominfo->current_internal_crc) >> 8; // high byte
// change checksum
buf[2] = rominfo->current_internal_crc; // low byte
buf[3] = rominfo->current_internal_crc >> 8; // high byte
if (rominfo->interleaved)
header_start = SNES_HEADER_START + (snes_hirom ? 0 : size / 2); // (Ext.) HiROM : LoROM
else
header_start = rominfo->header_start;
ucon64_fwrite (buf, header_start + rominfo->buheader_len + 44, 4, dest_name, "r+b");
dumper (stdout, buf, 4, header_start + rominfo->buheader_len + 44, DUMPER_HEX);
printf (ucon64_msg[WROTE], dest_name);
return 0;
}
static int
snes_testinterleaved (unsigned char *rom_buffer, int size, int banktype_score)
/*
The only way to determine whether a HiROM dump is interleaved or not seems to
be to check the value of the map type byte. Valid HiROM values (hexadecimal):
21, 31, 35, 3a
Valid LoROM values:
20, 23, 30, 32, 44 [, 41, 53]
41 is the hexadecimal value of 'A' (WWF Super Wrestlemania (E)). 53 is the
hexadecimal value value of 'S' (Contra III - The Alien Wars (U)).
So, if a ROM dump seems LoROM, but the map type byte is that of a HiROM dump
we assume it is interleaved. Interleaved LoROM dumps are not produced by any
copier, but by incorrect ROM tools...
*/
{
int interleaved = 0, check_map_type = 1;
unsigned int crc;
if (size < 64 * 1024) // snes_deinterleave() reads blocks of 32 kB
return 0; // file cannot be interleaved
crc = crc32 (0, rom_buffer, 512);
/*
Special case hell
0x4a70ad38: Double Dragon, Return of (J), Super Double Dragon (E/U) {[!], [a1]}
0x0b34ddad: Kakinoki Shogi (J)
0x348b5357: King of Rally, The (J)
0xc39b8d3a: Pro Kishi Simulation Kishi no Hanamichi (J)
0xbd7bc39f: Shin Syogi Club (J)
0x9b4638d0: Street Fighter Alpha 2 (E/U) {[b1]}, Street Fighter Zero 2 (J)
Only really necessary for (U). The other versions can be detected because
one of the two internal headers has checksum bytes ff ff 00 00.
0x0085b742: Super Bowling (U)
0x30cbf83c: Super Bowling (J)
These games have two headers.
BUG ALERT: We don't check for 0xbd7bc39f. The first 512 bytes of what
uCON64 detects as the interleaved dump of Shin Syogi Club (J) are identical
to the first 512 bytes of what we detect as the uninterleaved dump of
Kakinoki Shogi (J). We prefer uninterleaved dumps. Besides, concluding a
dump is interleaved if the first 512 bytes have CRC32 0xbd7bc39f would mess
up the detection of some BS dumps. See below.
0x7039388a: Ys 3 - Wanderers from Ys (J)
This game has 31 internal headers...
0xd7470b37/0x9f1d6284: Dai Kaiju Monogatari 2 (J) (GD3/UFO)
0xa2c5fd29/0xfe536fc9: Tales of Phantasia (J) (GD3/UFO)
These are Extended HiROM games. By "coincidence" ToP can be detected in
another way, but DKM2 (40 Mbit) can't. The CRC32's are checked for below.
0xdbc88ebf: BS Satella2 1 (J)
This game has a LoROM map type byte while it is a HiROM game.
0x29226b62: BS Busters - Digital Magazine 5-24-98 (J),
BS Do-Re-Mi No.2 5-10 (J),
BS Do-Re-Mi No.2 5-25 (J),
BS Furoito No Chousenjou {2, 3, 4, 5, 6} (J),
BS Nintendo HP 5-17 (J),
BS Nintendo HP 5-31 (J)
0xbd7bc39f: BS Goods Press 6 Gatsu Gou (J),
BS NP Magazine 107 (J),
BS Tora no Maki 5-17 (J),
BS Tora no Maki 5-31 (J)
0x4ef3d27b: BS Lord Monarke (J)
These games are *not* special cases. uCON64 detects them correctly, but the
tool that was used to create GoodSNES - 0.999.5 for RC 2.5.dat, does not.
This has been verified on a real SNES for the games with CRC 0x29226b62 and
0x4ef3d27b. The games with CRC 0xbd7bc39f don't seem to run on a copier.
0xc3194ad7: Yu Yu No Quiz De Go! Go! (J)
0x89d09a77: Infernal's Evil Demo! (PD)
0xd3095af3: Legend - SNDS Info, Incredible Hulk Walkthru (PD)
0x9b161d4d: Pop 'N Twinbee Sample (J)
0x6910700a: Rock Fall (PD)
0x447df9d5: SM Choukyousi Hitomi (PD)
0x02f401df: SM Choukyousi Hitomi Vol 2 (PD)
0xf423997a: World of Manga 2 (PD)
These games/dumps have a HiROM map type byte while they are LoROM.
0x0f802e41: Mortal Kombat 3 Final (Anthrox Beta Hack)
0xbd8f1b20: Rise of the Robots (Beta)
0x05926d17: Shaq Fu (E)/(J)(NG-Dump Known)
0x3e2e5619: Super Adventure Island II (Beta)
0x023e1298: Super Air Driver (E) [b]
These are also not special cases (not: HiROM map type byte + LoROM game).
GoodSNES - 0.999.5 for RC 2.5.dat simply contains errors.
0x2a4c6a9b: Super Noah's Ark 3D (U)
0xfa83b519: Mortal Kombat (Beta)
0xf3aa1eca: Power Piggs of the Dark Age (Pre-Release) {[h1]}
0x65485afb: Super Aleste (J) {[t1]} <= header == trainer
0xaad23842/0x5ee74558: Super Wild Card DX DOS ROM V1.122/interleaved
0x422c95c4: Time Slip (Beta)
0x7a44bd18: Total Football (E)(NG-Dump Known)
0xf0bf8d7c/0x92180571: Utyu no Kishi Tekkaman Blade (Beta) {[h1]}/interleaved
0x8e1933d0: Wesley Orangee Hotel (PD)
0xe2b95725/0x9ca5ed58: Zool (Sample Cart)/interleaved
These games/dumps have garbage in their header.
*/
if (crc == 0xc3194ad7
#ifdef DETECT_NOTGOOD_DUMPS
||
crc == 0x89d09a77 || crc == 0xd3095af3 || crc == 0x9b161d4d ||
crc == 0x6910700a || crc == 0x447df9d5 || crc == 0x02f401df ||
crc == 0xf423997a || crc == 0xfa83b519 || crc == 0xf3aa1eca ||
crc == 0xaad23842 || crc == 0x422c95c4 || crc == 0x7a44bd18 ||
crc == 0xf0bf8d7c || crc == 0x8e1933d0 || crc == 0xe2b95725
#endif
)
check_map_type = 0; // not interleaved
else if (crc == 0x4a70ad38 || crc == 0x0b34ddad || crc == 0x348b5357 ||
crc == 0xc39b8d3a || crc == 0x9b4638d0 || crc == 0x0085b742 ||
crc == 0x30cbf83c || crc == 0x7039388a || crc == 0xdbc88ebf ||
crc == 0x2a4c6a9b
#ifdef DETECT_NOTGOOD_DUMPS
||
crc == 0x65485afb || crc == 0x5ee74558 || crc == 0x92180571 ||
crc == 0x9ca5ed58
#endif
)
{
interleaved = 1;
snes_hirom = 0;
snes_hirom_ok = 1;
check_map_type = 0; // interleaved
}
// WARNING: st_dump won't be set if it's an interleaved dump
else if (st_dump)
check_map_type = 0;
else
{
#ifdef DETECT_SMC_COM_FUCKED_UP_LOROM
if (size > SNES_HEADER_START + SNES_HIROM + 0x4d)
if (check_banktype (rom_buffer, size / 2) > banktype_score)
{
interleaved = 1;
snes_hirom = 0;
snes_hirom_ok = 1; // keep snes_deinterleave()
check_map_type = 0; // from changing snes_hirom
}
#endif
#ifdef DETECT_INSNEST_FUCKED_UP_LOROM
/*
"the most advanced and researched Super Nintendo ROM utility available"
What a joke. They don't support their own "format"...
For some games we never reach this code, because the previous code
detects them (incorrectly). I (dbjh) don't think there are games in
this format available on the internet, so I won't add special-case code
(like CRC32 checks) to fix that -- it's a bug in inSNESt. Examples are:
Lufia II - Rise of the Sinistrals (H)
Super Mario All-Stars & World (E) [!]
*/
if (!interleaved && size == 24 * MBIT)
if (check_banktype (rom_buffer, 16 * MBIT) > banktype_score)
{
interleaved = 1;
snes_hirom = 0;
snes_hirom_ok = 2; // fix for snes_deinterleave()
check_map_type = 0;
}
#endif
}
if (check_map_type && !snes_hirom)
{
// first check if it's an interleaved Extended HiROM dump
if (ucon64.file_size >= (int) (SNES_HEADER_START + SNES_EROM + SNES_HEADER_LEN))
{
// don't set snes_header_base to SNES_EROM for too small files (split files)
if (crc == 0xd7470b37 || crc == 0xa2c5fd29) // GD3
snes_header_base = SNES_EROM;
else if (crc == 0x9f1d6284 || crc == 0xfe536fc9) // UFO
{
snes_header_base = SNES_EROM;
interleaved = 1;
}
}
if (snes_header.map_type == 0x21 || snes_header.map_type == 0x31 ||
snes_header.map_type == 0x35 || snes_header.map_type == 0x3a ||
snes_header.bs_map_type == 0x21 || snes_header.bs_map_type == 0x31)
interleaved = 1;
}
return interleaved;
}
int
snes_deinterleave (st_rominfo_t *rominfo, unsigned char **rom_buffer, int rom_size)
{
unsigned char blocks[256], *rom_buffer2;
int nblocks, i, j, org_hirom;
org_hirom = snes_hirom;
nblocks = rom_size >> 16; // # 32 kB blocks / 2
if (nblocks * 2 > 256)
return -1; // file > 8 MB
if (rominfo->interleaved == 2) // SFX(2) games (Doom, Yoshi's Island)
{
for (i = 0; i < nblocks * 2; i++)
{
blocks[i] = (i & ~0x1e) | ((i & 2) << 2) | ((i & 4) << 2) |
((i & 8) >> 2) | ((i & 16) >> 2);
if (blocks[i] * 0x8000 + 0x8000 > rom_size)
{
printf ("WARNING: This ROM cannot be handled as if it is in interleaved format 2\n");
rominfo->interleaved = 0;
return -1;
}
}
}
else // rominfo->interleaved == 1
{
int blocksset = 0;
if (!snes_hirom_ok)
{
snes_hirom = SNES_HIROM;
snes_hirom_ok = 1;
}
if (type == GD3)
{
// deinterleaving schemes specific for the Game Doctor
if ((snes_hirom || snes_hirom_ok == 2) && rom_size == 24 * MBIT)
{
for (i = 0; i < nblocks; i++)
{
blocks[i * 2] = i + ((i < (16 * MBIT >> 16) ? 16 : 4) * MBIT >> 15);
blocks[i * 2 + 1] = i;
}
blocksset = 1;
}
else if (snes_header_base == SNES_EROM)
{
int size2 = rom_size - 32 * MBIT; // size of second ROM
j = 32 * MBIT >> 16;
for (i = 0; i < j; i++)
{
blocks[i * 2] = i + j + (size2 >> 15);
blocks[i * 2 + 1] = i + (size2 >> 15);
}
j = size2 >> 16;
for (; i < j + (32 * MBIT >> 16); i++)
{
blocks[i * 2] = (unsigned char) (i + j - (32 * MBIT >> 16));
blocks[i * 2 + 1] = (unsigned char) (i - (32 * MBIT >> 16));
}
blocksset = 1;
}
}
if (!blocksset)
for (i = 0; i < nblocks; i++)
{
blocks[i * 2] = i + nblocks;
blocks[i * 2 + 1] = i;
}
}
if (!(rom_buffer2 = (unsigned char *) malloc (rom_size)))
{
fprintf (stderr, ucon64_msg[ROM_BUFFER_ERROR], rom_size);
exit (1);
}
for (i = 0; i < nblocks * 2; i++)
memcpy (rom_buffer2 + i * 0x8000, (*rom_buffer) + blocks[i] * 0x8000, 0x8000);
free (*rom_buffer);
*rom_buffer = rom_buffer2;
return 0;
}
static const char *
matches_deviates (int equal)
{
return
#ifdef USE_ANSI_COLOR
ucon64.ansi_color ?
(equal ? "\x1b[01;32mMatches\x1b[0m" : "\x1b[01;33mDeviates\x1b[0m") :
(equal ? "Matches" : "Deviates");
#else
(equal ? "Matches" : "Deviates");
#endif
}
int
snes_buheader_info (st_rominfo_t *rominfo)
// -dbuh
{
unsigned char header[512];
int x, y;
snes_file_t org_type = type;
if (rominfo->buheader_len == 0) // type == MGD_SNES
{
printf ("This ROM has no backup unit header\n");
return -1;
}
else
{
printf ("Backup unit header info (%s)\n\n",
type == SWC ? "SWC" :
type == FIG ? "FIG" :
type == GD3 ? "GD3" :
type == UFO ? "UFO" :
"unknown header type, but interpreted as SWC");
if (type == SMC)
type = SWC;
}
ucon64_fread (&header, 0, 512, ucon64.rom);
dumper (stdout, header, 48, 0, DUMPER_HEX); // show only the part that is
fputc ('\n', stdout); // interpreted by copier
if (type == SWC || type == FIG || type == SMC || type == UFO)
{
x = ucon64.file_size - rominfo->buheader_len;
y = (header[0] + (header[1] << 8)) * 8 * 1024;
printf ("[0-1] File size: %d Bytes (%.4f Mb) => %s\n",
y, TOMBIT_F (y), matches_deviates (x == y));
}
if (type == SWC || type == SMC)
{
int z;
unsigned char sram_sizes[] = {0, 2, 8, 32};
if (header[2] & 0x80) // bit 7 has higher precedence
z = 0; // than bit 1
else if (header[2] & 0x02)
z = 2;
else
z = 3;
printf ("[2:7] Run program in mode: %d", z);
if (z == 2)
printf (" (bit 1=1)\n");
else
fputc ('\n', stdout);
y = header[2] & 0x40 ? 1 : 0;
printf ("[2:6] Split: %s => %s\n",
y ? "Yes" : "No", matches_deviates ((snes_split ? 1 : 0) == y));
x = snes_hirom ? 1 : 0;
y = header[2] & 0x20 ? 1 : 0;
printf ("[2:5] SRAM mapping mode: %s => %s\n",
y ? "HiROM" : "LoROM", matches_deviates (x == y));
y = header[2] & 0x10 ? 1 : 0;
printf ("[2:4] DRAM mapping mode: %s => %s\n",
y ? "HiROM" : "LoROM", matches_deviates (x == y));
y = sram_sizes[(~header[2] & 0x0c) >> 2]; // 32 => 12, 8 => 8, 2 => 4, 0 => 0
printf ("[2:3-2] SRAM size: %d kB => %s\n",
y, matches_deviates (snes_sramsize == y * 1024));
printf ("[2:1] Run program in mode: %d", z);
if (z == 0)
printf (" (bit 7=1)\n");
else
fputc ('\n', stdout);
printf ("[2:0] External cartridge memory: %s\n",
header[2] & 0x01 ? "Enabled" : "Disabled");
}
else if (type == FIG)
{
y = header[2] & 0x40 ? 1 : 0;
printf ("[2] Split: %s => %s\n",
y ? "Yes" : "No", matches_deviates ((snes_split ? 1 : 0) == y));
y = header[3] & 0x80 ? 1 : 0;
printf ("[3] Memory mapping mode: %s => %s\n",
y ? "HiROM" : "LoROM", matches_deviates ((snes_hirom ? 1 : 0) == y));
y = -1;
if (snes_hirom)
{
if ((header[4] == 0x77 && header[5] == 0x83) ||
(header[4] == 0xf7 && header[5] == 0x83))
y = 0;
else if ((header[4] == 0xfd && header[5] == 0x82) ||
(header[4] == 0xdd && header[5] == 0x82))
y = 8 * 1024; // or 2 * 1024
else if (header[4] == 0xdd && header[5] == 0x02)
y = 32 * 1024;
}
else
{
if ((header[4] == 0x77 && header[5] == 0x83) ||
(header[4] == 0x47 && header[5] == 0x83))
y = 0;
else if (header[4] == 0x00 && header[5] == 0x80)
y = 8 * 1024; // or 2 * 1024
else if ((header[4] == 0x00 && header[5] == 0x00) ||
(header[4] == 0x11 && header[5] == 0x02))
y = 32 * 1024;
}
if (y == 8 * 1024)
printf ("[4-5] SRAM size: 2 kB / 8 kB => %s\n",
matches_deviates (snes_sramsize == 2 * 1024 ||
snes_sramsize == 8 * 1024));
else
printf ("[4-5] SRAM size: %d kB => %s\n",
y / 1024, matches_deviates (snes_sramsize == y));
}
else if (type == UFO)
{
unsigned char sram_sizes[] = {0, 2, 8, 32};
int z;
y = header[2] ? 1 : 0;
printf ("[2] Split: %s => %s\n",
y ? "Yes" : "No", matches_deviates ((snes_split ? 1 : 0) == y));
x = ucon64.file_size - rominfo->buheader_len;
y = header[0x11] * MBIT;
printf ("[11] ROM size: %d Bytes (%d.0000 Mb) => %s\n",
y, y / MBIT, matches_deviates (x == y));
y = header[0x12] & 1;
printf ("[12] DRAM mapping mode: %s => %s\n",
y ? "LoROM" : "HiROM", matches_deviates ((snes_hirom ? 0 : 1) == y));
y = (header[0x13] <= 3 ? sram_sizes[header[0x13]] : 128) * 1024;
printf ("[13] SRAM size: %d kB => %s\n",
y / 1024, matches_deviates (snes_sramsize == y));
y = header[0x14];
if (y)
printf ("[14] A15=%s selects SRAM\n", y == 1 ? "x" : y == 2 ? "0" : "1");
else
printf ("[14] A15 not used for SRAM control\n");
for (x = 0; x < 4; x++)
{
if (x & 1)
{
y = header[0x15 + x / 2] >> 2;
z = 2;
}
else
{
y = header[0x15 + x / 2] & 0x3;
z = 0;
}
if (y != 1)
printf ("[%x:%d-%d] A%d=%s selects SRAM\n",
0x15 + x / 2, z + 1, z, 20 + x, y == 0 ? "x" : y == 2 ? "0" : "1");
}
y = header[0x17];
printf ("[17] SRAM mapping mode: %s => %s\n",
y == 3 ? "LoROM" : y == 0 ? "HiROM" : "unknown",
matches_deviates ((snes_hirom ? 0 : 3) == y));
}
else if (type == GD3)
{
y = -1;
if (header[0x10] == 0x81)
y = 8 * 1024;
else if (header[0x10] == 0x82)
y = 2 * 1024;
else if (header[0x10] == 0x80)
y = 32 * 1024; // or 0
if (y == 32 * 1024)
printf ("[10] SRAM size: 0 kB / 32 kB => %s\n",
matches_deviates (snes_sramsize == 0 || snes_sramsize == 32 * 1024));
else
printf ("[10] SRAM size: %d kB => %s\n",
y / 1024, matches_deviates (snes_sramsize == y));
}
type = org_type;
return 0;
}
unsigned short int
get_internal_sums (st_rominfo_t *rominfo)
/*
Returns the sum of the internal checksum and the internal inverse checksum
if the values for snes_hirom and rominfo->buheader_len are correct. If the
values are correct the sum will be 0xffff. Note that the sum for bad ROM
dumps can also be 0xffff, because this function adds the internal checksum
bytes and doesn't do anything with the real, i.e. calculated, checksum.
*/
{
int image = SNES_HEADER_START + snes_header_base + snes_hirom +
rominfo->buheader_len;
// don't use rominfo->header_start here!
unsigned char buf[4];
ucon64_fread (buf, image + 44, 4, ucon64.rom);
return buf[0] + (buf[1] << 8) + buf[2] + (buf[3] << 8);
}
static void
snes_handle_buheader (st_rominfo_t *rominfo, st_unknown_header_t *header)
/*
Determine the size of a possible backup unit header. This function also tries
to determine the bank type in the process. However, snes_set_hirom() has the
final word about that.
*/
{
int x = 0, y;
/*
Check for "Extended" ROM dumps first, because at least one of them
(Tales of Phantasia (J)) has two headers; an incorrect one at the normal
location and a correct one at the Extended HiROM location.
*/
if (ucon64.file_size >= (int) (SNES_HEADER_START + SNES_EROM + SNES_HEADER_LEN))
{
snes_header_base = SNES_EROM;
snes_hirom = SNES_HIROM;
rominfo->buheader_len = 0;
if ((x = get_internal_sums (rominfo)) != 0xffff)
{
rominfo->buheader_len = SWC_HEADER_LEN;
if ((x = get_internal_sums (rominfo)) != 0xffff)
{
snes_hirom = 0;
if ((x = get_internal_sums (rominfo)) != 0xffff)
{
rominfo->buheader_len = 0;
x = get_internal_sums (rominfo);
}
}
}
}
if (x != 0xffff)
{
snes_header_base = 0;
snes_hirom = 0;
rominfo->buheader_len = 0;
if ((x = get_internal_sums (rominfo)) != 0xffff)
{
rominfo->buheader_len = SWC_HEADER_LEN;
if ((x = get_internal_sums (rominfo)) != 0xffff)
{
snes_hirom = SNES_HIROM;
if ((x = get_internal_sums (rominfo)) != 0xffff)
{
rominfo->buheader_len = 0;
x = get_internal_sums (rominfo);
}
}
}
}
if (header->id1 == 0xaa && header->id2 == 0xbb && header->type == 4)
type = SWC;
else if (!strncmp ((char *) header, "GAME DOCTOR SF 3", 16))
type = GD3;
else if (!strncmp ((char *) header + 8, "SUPERUFO", 8))
type = UFO;
else if ((header->hirom == 0x80 && // HiROM
((header->emulation1 == 0x77 && header->emulation2 == 0x83) ||
(header->emulation1 == 0xdd && header->emulation2 == 0x82) ||
(header->emulation1 == 0xdd && header->emulation2 == 0x02) ||
(header->emulation1 == 0xf7 && header->emulation2 == 0x83) ||
(header->emulation1 == 0xfd && header->emulation2 == 0x82)))
||
(header->hirom == 0x00 && // LoROM
((header->emulation1 == 0x77 && header->emulation2 == 0x83) ||
(header->emulation1 == 0x00 && header->emulation2 == 0x80) ||
#if 1
// This makes NES FFE ROMs & Game Boy ROMs be detected as SNES
// ROMs, see src/console/nes.c & src/console/gb.c
(header->emulation1 == 0x00 && header->emulation2 == 0x00) ||
#endif
(header->emulation1 == 0x47 && header->emulation2 == 0x83) ||
(header->emulation1 == 0x11 && header->emulation2 == 0x02)))
)
type = FIG;
else if (rominfo->buheader_len == 0 && x == 0xffff)
type = MGD_SNES;
/*
x can be better trusted than type == FIG, but x being 0xffff is definitely
not a guarantee that rominfo->buheader_len already has the right value
(e.g. Earthworm Jim (U), Alfred Chicken (U|E), Soldiers of Fortune (U)).
*/
#if 0
if (type != MGD_SNES) // don't do "&& type != SMC" or we'll miss a lot of PD ROMs
#endif
{
y = ((header->size_high << 8) + header->size_low) * 8 * 1024;
y += SWC_HEADER_LEN; // if SWC-like header -> hdr[1] high byte,
if (y == ucon64.file_size) // hdr[0] low byte of # 8 kB blocks in ROM
rominfo->buheader_len = SWC_HEADER_LEN;
else
{
int surplus = ucon64.file_size % 32768;
if (surplus == 0)
// most likely we guessed the copier type wrong
{
rominfo->buheader_len = 0;
type = MGD_SNES;
}
/*
Check for surplus being smaller than 31232 instead of MAXBUFSIZE
(32768) to detect "Joystick Sampler with Still Picture (PD)" (64000
bytes, including SWC header).
"Super Wild Card V2.255 DOS ROM (BIOS)" is 16384 bytes (without
header), so check for surplus being smaller than 16384.
Shadow, The (Beta) [b3] has a surplus of 7680 bytes (15 * 512). So,
accept a surplus of up to 7680 bytes as a header...
*/
else if (surplus % SWC_HEADER_LEN == 0 &&
surplus < (int) (15 * SWC_HEADER_LEN) &&
ucon64.file_size > surplus)
rominfo->buheader_len = surplus;
// special case for Infinity Demo (PD)... (has odd size, but SWC
// header). Don't add "|| type == FIG" as it is too unreliable
else if (type == SWC || type == GD3 || type == UFO)
rominfo->buheader_len = SWC_HEADER_LEN;
}
}
if (UCON64_ISSET (ucon64.buheader_len)) // -hd, -nhd or -hdn switch was specified
{
rominfo->buheader_len = ucon64.buheader_len;
if (type == MGD_SNES && rominfo->buheader_len)
type = SMC;
}
if (rominfo->buheader_len && !memcmp ((unsigned char *) header + 0x1e8, "NSRT", 4))
nsrt_header = 1;
else
nsrt_header = 0;
}
static int
snes_set_hirom (unsigned char *rom_buffer, int size)
/*
This function tries to determine if the ROM dump is LoROM or HiROM. It returns
the highest value that check_banktype() returns. A higher value means a higher
chance the bank type is correct.
*/
{
int x, score_hi = 0, score_lo = 0;
if (size >= (int) (8 * MBIT + SNES_HEADER_START + SNES_HIROM + SNES_HEADER_LEN) &&
!strncmp ((char *) rom_buffer + SNES_HEADER_START + 16, "ADD-ON BASE CASSETE", 19))
{ // A Sufami Turbo dump contains 4 copies of the ST BIOS, which is 2 Mbit.
// After the BIOS comes the game data.
st_dump = 1;
snes_header_base = 8 * MBIT;
x = 8 * MBIT + SNES_HIROM;
}
else if (snes_header_base == SNES_EROM)
x = SNES_EROM + SNES_HIROM;
else
{
snes_header_base = 0;
x = SNES_HIROM;
}
if (size > SNES_HEADER_START + SNES_HIROM + 0x4d)
{
score_hi = check_banktype (rom_buffer, x);
score_lo = check_banktype (rom_buffer, snes_header_base);
}
if (score_hi > score_lo) // yes, a preference for LoROM
{ // (">" vs. ">=")
snes_hirom = SNES_HIROM;
x = score_hi;
}
else
{
snes_hirom = 0;
x = score_lo;
}
/*
It would be nice if snes_header.map_type & 1 could be used to verify that
snes_hirom has the correct value, but it doesn't help much. For games like
Batman Revenge of the Joker (U) it matches what check_banktype() finds.
snes_hirom must be 0x8000 for that game in order to display correct
information. However it should be 0 when writing a copier header.
So, snes_header.map_type can't be used to recognize such cases.
*/
// step 3.
if (UCON64_ISSET (ucon64.snes_hirom)) // -hi or -nhi switch was specified
{
snes_hirom = ucon64.snes_hirom;
// keep snes_deinterleave() from changing snes_hirom
snes_hirom_ok = 1;
if (size < (int) (SNES_HEADER_START + SNES_HIROM + SNES_HEADER_LEN))
snes_hirom = 0;
}
if (UCON64_ISSET (ucon64.snes_header_base)) // -erom switch was specified
{
snes_header_base = ucon64.snes_header_base;
if (snes_header_base &&
size < (int) (snes_header_base + SNES_HEADER_START + snes_hirom + SNES_HEADER_LEN))
snes_header_base = 0; // Don't let -erom crash on a too small ROM
}
return x;
}
static void
snes_set_bs_dump (st_rominfo_t *rominfo, unsigned char *rom_buffer, int size)
{
bs_dump = snes_check_bs ();
/*
Do the following check before checking for ucon64.bs_dump. Then it's
possible to specify both -erom and -bs with effect, for what it's worth ;-)
The main reason to test this case is to display correct info for "SD Gundam
G-NEXT + Rom Pack Collection (J) [!]". Note that testing for SNES_EROM
causes the code to be skipped for Sufami Turbo dumps.
*/
if (bs_dump &&
snes_header_base == SNES_EROM && !UCON64_ISSET (ucon64.snes_header_base))
{
bs_dump = 0;
snes_header_base = 0;
snes_set_hirom (rom_buffer, size);
rominfo->header_start = snes_header_base + SNES_HEADER_START + snes_hirom;
memcpy (&snes_header, rom_buffer + rominfo->header_start, rominfo->header_len);
}
if (UCON64_ISSET (ucon64.bs_dump)) // -bs or -nbs switch was specified
{
bs_dump = ucon64.bs_dump;
if (bs_dump && snes_header_base == SNES_EROM)
bs_dump = 2; // Extended ROM => must be add-on cart
}
}
int
snes_init (st_rominfo_t *rominfo)
{
int x, y, size, calc_checksums, result = -1; // it's no SNES ROM dump until detected otherwise
unsigned char *rom_buffer;
st_unknown_header_t header = { 0, 0, 0, 0, 0, 0, { 0 }, 0, 0, 0, { 0 } };
char buf[MAXBUFSIZE], *str;
#define SNES_COUNTRY_MAX 0xe
static const char *snes_country[SNES_COUNTRY_MAX] =
{
"Japan",
"U.S.A.",
"Europe, Oceania and Asia", // Australia is part of Oceania
"Sweden",
"Finland",
"Denmark",
"France",
"The Netherlands", // Holland is an incorrect name for The Netherlands
"Spain",
"Germany, Austria and Switzerland",
"Italy",
"Hong Kong and China",
"Indonesia",
"South Korea"
},
*snes_rom_type[3] =
{
"ROM", // NOT ROM only, ROM + other chip is possible
"ROM + SRAM",
"ROM + SRAM + Battery"
},
*snes_bs_type[4] =
{
"Full size + Sound link",
"Full size",
"Part size + Sound link",
"Part size"
};
snes_hirom_ok = 0; // init these vars here, for -lsv
snes_sramsize = 0; // idem
type = SMC; // idem, SMC indicates unknown copier type
bs_dump = 0; // for -lsv, but also just to init it
st_dump = 0; // idem
x = 0;
ucon64_fread (&header, UNKNOWN_HEADER_START, UNKNOWN_HEADER_LEN, ucon64.rom);
if (header.id1 == 0xaa && header.id2 == 0xbb)
x = SWC;
else if (!strncmp ((char *) &header + 8, "SUPERUFO", 8))
x = UFO;
if ((x == SWC && (header.type == 5 || header.type == 8)) ||
(x == UFO && (OFFSET (header, 0x10) == 0)))
{
rominfo->buheader_len = SWC_HEADER_LEN;
strcpy (rominfo->name, "Name: N/A");
rominfo->console_usage = NULL;
rominfo->maker = "Publisher: You?";
rominfo->country = "Country: Your country?";
rominfo->has_internal_crc = 0;
ucon64.split = 0; // SRAM & RTS files are never split
if (x == SWC)
{
rominfo->copier_usage = swc_usage[0].help;
type = SWC;
if (header.type == 5)
strcat (rominfo->misc, "Type: Super Wild Card SRAM file\n");
else if (header.type == 8)
strcat (rominfo->misc, "Type: Super Wild Card RTS file\n");
}
else if (x == UFO)
{
rominfo->copier_usage = ufo_usage[0].help;
type = UFO;
strcat (rominfo->misc, "Type: Super UFO SRAM file\n");
}
return 0; // rest is nonsense for SRAM/RTS file
}
/*
snes_testinterleaved() needs the correct value for snes_hirom and
rominfo->header_start. snes_hirom may be used only after the check for
-hi/-nhi has been done. However, rominfo->buheader_len must have the
correct value in order to determine the value for snes_hirom. This can only
be known after the backup unit header length detection (including the check
for -hd/-nhd/-hdn). So, the order must be
1. - rominfo->buheader_len
2. - snes_hirom
3. - check for -hi/-nhi
4. - snes_testinterleaved()
*/
snes_handle_buheader (rominfo, &header); // step 1. & first part of step 2.
if (UCON64_ISSET (ucon64.split))
snes_split = ucon64.split;
else
{
if (type == SWC || type == FIG || type == UFO)
{
// TODO?: fix this code for last split file
snes_split = 0;
if (header.emulation & 0x40 || (type == UFO && header.emulation & 0x10))
snes_split = ucon64_testsplit (ucon64.rom);
ucon64.split = snes_split; // force displayed info to be correct
} // if not split (see ucon64.c)
else
snes_split = ucon64_testsplit (ucon64.rom);
}
size = ucon64.file_size - rominfo->buheader_len;
if (size < (int) (SNES_HEADER_START + SNES_HEADER_LEN))
{
snes_hirom = 0;
if (UCON64_ISSET (ucon64.snes_hirom)) // see snes_set_hirom()
snes_hirom = ucon64.snes_hirom;
snes_hirom_ok = 1;
rominfo->interleaved = 0;
if (UCON64_ISSET (ucon64.interleaved))
rominfo->interleaved = ucon64.interleaved;
return -1; // don't continue (seg faults!)
}
if (ucon64.console == UCON64_SNES || (type != SMC && size <= 16 * 1024 * 1024))
result = 0; // it seems to be a SNES ROM dump
if (!(rom_buffer = (unsigned char *) malloc (size)))
{
fprintf (stderr, ucon64_msg[ROM_BUFFER_ERROR], size);
return -1; // don't exit(), we might've been
} // called with -lsv
ucon64_fread (rom_buffer, rominfo->buheader_len, size, ucon64.rom);
x = snes_set_hirom (rom_buffer, size); // second part of step 2. & step 3.
rominfo->header_start = snes_header_base + SNES_HEADER_START + snes_hirom;
rominfo->header_len = SNES_HEADER_LEN;
// set snes_header before calling snes_testinterleaved()
memcpy (&snes_header, rom_buffer + rominfo->header_start, rominfo->header_len);
rominfo->header = &snes_header;
// step 4.
rominfo->interleaved = UCON64_ISSET (ucon64.interleaved) ?
ucon64.interleaved : snes_testinterleaved (rom_buffer, size, x);
calc_checksums = !UCON64_ISSET (ucon64.do_not_calc_crc) && result == 0;
// we want the CRC32 of the "raw" data (too)
if (calc_checksums)
ucon64.fcrc32 = crc32 (0, rom_buffer, size);
// bs_dump has to be set before calling snes_chksum(), but snes_check_bs()
// needs snes_header to be filled with the correct data
if (rominfo->interleaved)
{
snes_deinterleave (rominfo, &rom_buffer, size);
snes_set_hirom (rom_buffer, size);
rominfo->header_start = snes_header_base + SNES_HEADER_START + snes_hirom;
memcpy (&snes_header, rom_buffer + rominfo->header_start, rominfo->header_len);
}
snes_set_bs_dump (rominfo, rom_buffer, size);
// internal ROM name
if (!bs_dump && st_dump)
memcpy (rominfo->name, rom_buffer + 8 * MBIT + 16, SNES_NAME_LEN);
else
{
memcpy (rominfo->name, snes_header.name, SNES_NAME_LEN);
for (x = 0; x < SNES_NAME_LEN; x++)
if (!isprint ((int) rominfo->name[x])) // we can't use mkprint(), because it skips \n
rominfo->name[x] = '.';
}
rominfo->name[(bs_dump || st_dump) ? 16 : SNES_NAME_LEN] = 0;
// terminate string (at 1st byte _after_ string)
if (calc_checksums)
{
// internal ROM crc
rominfo->has_internal_crc = 1;
rominfo->internal_crc_len = 2;
rominfo->current_internal_crc = snes_chksum (rominfo, &rom_buffer, size);
rominfo->internal_crc = snes_header.checksum_low;
rominfo->internal_crc += snes_header.checksum_high << 8;
x = snes_header.inverse_checksum_low;
x += snes_header.inverse_checksum_high << 8;
y = ~rominfo->current_internal_crc & 0xffff;
sprintf (rominfo->internal_crc2,
"Inverse checksum: %s, 0x%04x (calculated) %c= 0x%04x (internal)",
#ifdef USE_ANSI_COLOR
ucon64.ansi_color ?
((y == x) ? "\x1b[01;32mOk\x1b[0m" : "\x1b[01;31mBad\x1b[0m")
:
((y == x) ? "Ok" : "Bad"),
#else
(y == x) ? "Ok" : "Bad",
#endif
y, (y == x) ? '=' : '!', x);
if (bs_dump == 1) // bs_dump == 2 for BS add-on dumps
{
unsigned short int *bs_date_ptr = (unsigned short int *)
(rom_buffer + snes_header_base + SNES_HEADER_START + snes_hirom + 38);
/*
We follow the "uCONSRT standard" for calculating the CRC32 of BS
dumps. At the time of this writing (20 June 2003) the uCONSRT
standard defines that the date of BS dumps has to be "skipped"
(overwritten with a constant number), because the date is variable.
When a BS dump is made the BSX fills in the date. Otherwise two
dumps of the same memory card would have a different CRC32.
For BS add-on cartridge dumps we don't do anything special as they
come from cartridges (with a constant date).
Why 42? It's the answer to life, the universe and everything :-)
*/
*bs_date_ptr = me2le_16 (0x0042);
get_nsrt_info (rom_buffer, rominfo->header_start, (unsigned char *) &header);
ucon64.crc32 = crc32 (0, rom_buffer, size);
}
else if (rominfo->interleaved || nsrt_header)
{
get_nsrt_info (rom_buffer, rominfo->header_start, (unsigned char *) &header);
ucon64.crc32 = crc32 (0, rom_buffer, size);
}
else
{
ucon64.crc32 = ucon64.fcrc32;
ucon64.fcrc32 = 0;
}
}
rominfo->console_usage = snes_usage[0].help;
if (!rominfo->buheader_len)
rominfo->copier_usage = mgd_usage[0].help;
else
{
switch (type)
{
case GD3:
rominfo->copier_usage = gd_usage[0].help;
break;
case UFO:
rominfo->copier_usage = ufo_usage[0].help;
break;
case FIG:
rominfo->copier_usage = fig_usage[0].help;
break;
// just assume it's in SWC format... (there are _many_ ROMs on the
// internet with incorrect headers)
default:
rominfo->copier_usage = swc_usage[0].help;
}
}
// ROM maker
if (snes_header.maker == 0x33 || bs_dump)
{
int ih = snes_header.maker_high <= '9' ?
snes_header.maker_high - '0' : snes_header.maker_high - 'A' + 10,
il = snes_header.maker_low <= '9' ?
snes_header.maker_low - '0' : snes_header.maker_low - 'A' + 10;
x = ih * 36 + il;
}
else if (snes_header.maker != 0)
x = (snes_header.maker >> 4) * 36 + (snes_header.maker & 0x0f);
else
x = 0; // warning remover
if (x < 0 || x >= NINTENDO_MAKER_LEN)
x = 0;
rominfo->maker = snes_header.maker == 0 ? "Demo or Beta ROM?" :
NULL_TO_UNKNOWN_S (nintendo_maker[x]);
if (!bs_dump)
{
// ROM country
rominfo->country = NULL_TO_UNKNOWN_S (snes_country[MIN (snes_header.country, SNES_COUNTRY_MAX - 1)]);
// misc stuff
sprintf (buf, "HiROM: %s\n", snes_hirom ? "Yes" : "No");
strcat (rominfo->misc, buf);
sprintf (buf, "Internal size: %d Mb\n", 1 << (snes_header.rom_size - 7));
strcat (rominfo->misc, buf);
/*
sprintf (buf, "Map type: %x\n", snes_header.map_type);
strcat (rominfo->misc, buf);
*/
sprintf (buf, "ROM type: (%x) %s", snes_header.rom_type,
snes_rom_type[(snes_header.rom_type & 7) % 3]);
strcat (rominfo->misc, buf);
if ((snes_header.rom_type & 0xf) >= 3)
{
if (snes_header.rom_type == 3 || snes_header.rom_type == 5)
str = "DSP";
else if (snes_header.rom_type == 0x13)
str = "SRAM + Super FX (Mario Chip 1)";
else if (snes_header.rom_type == 0x1a)
str = "Super FX";
else if (snes_header.rom_type == 0x14 || snes_header.rom_type == 0x15)
{
if (snes_header.rom_size > 10)
str = "Super FX 2"; // larger than 8 Mbit
else
str = "Super FX";
}
else if (snes_header.rom_type == 0x25)
str = "OBC1";
else if (snes_header.rom_type == 0x34 || snes_header.rom_type == 0x35)
str = "SA-1";
else if (snes_header.rom_type == 0x43 || snes_header.rom_type == 0x45)
str = "S-DD1";
else if (snes_header.rom_type == 0x55)
str = "S-RTC";
else if (snes_header.rom_type == 0xe3)
str = "Game Boy data";
else if (snes_header.rom_type == 0xf3)
str = "C4";
else if (snes_header.rom_type == 0xf5)
{
if (snes_header.map_type == 0x30)
str = "Seta RISC";
else
str = "SPC7110";
}
else if (snes_header.rom_type == 0xf6)
str = "Seta DSP";
else if (snes_header.rom_type == 0xf9)
str = "SPC7110 + RTC";
else
str = "Unknown";
sprintf (buf, " + %s", str);
strcat (rominfo->misc, buf);
}
strcat (rominfo->misc, "\n");
sprintf (buf, "ROM speed: %s\n",
snes_header.map_type & 0x10 ? "120 ns (FastROM)" : "200 ns (SlowROM)");
strcat (rominfo->misc, buf);
if (snes_header.rom_type == 0x13 || snes_header.rom_type == 0x1a ||
snes_header.rom_type == 0x14 || snes_header.rom_type == 0x15)
{
snes_sramsize = 32 * 1024;
if (snes_header.maker == 0x33)
snes_sfx_sramsize = snes_header.sfx_sram_size ? 1 << (snes_header.sfx_sram_size + 10) : 0;
else
snes_sfx_sramsize = 32 * 1024;
}
else
{
snes_sramsize = snes_header.sram_size ? 1 << (snes_header.sram_size + 10) : 0;
snes_sfx_sramsize = 0;
}
if (!snes_sramsize && !snes_sfx_sramsize)
sprintf (buf, "SRAM: No\n");
else
sprintf (buf, "SRAM: Yes, %d kBytes\n", (snes_sfx_sramsize ? snes_sfx_sramsize : snes_sramsize) / 1024);
strcat (rominfo->misc, buf);
}
else // BS info
{
// ROM country
rominfo->country = "Japan";
// misc stuff
if (bs_dump == 2)
sprintf (buf, "\nBroadcast Satellaview add-on cartridge dump\n");
else
sprintf (buf, "\nBroadcast Satellaview dump\n"); // new line is intentional
strcat (rominfo->misc, buf);
x = snes_header.bs_day & 0x0f;
if (x <= 3)
y = (snes_header.bs_day >> 4) * 2;
else if (x >= 8 && x <= 0xb)
y = (snes_header.bs_day >> 4) * 2 + 1;
else // incorrect data
y = 0;
sprintf (buf, "Dumping date: %d/%d\n", y, snes_header.bs_month >> 4);
strcat (rominfo->misc, buf);
sprintf (buf, "HiROM: %s\n", snes_hirom ? "Yes" : "No");
strcat (rominfo->misc, buf);
// misc stuff
sprintf (buf, "Internal size: %d Mb\n", 8 - (snes_header.bs_type >> (4 + 1)) * 4);
strcat (rominfo->misc, buf);
/*
sprintf (buf, "Map type: %x\n", snes_header.bs_map_type);
strcat (rominfo->misc, buf);
*/
x = snes_header.bs_type >> 4;
sprintf (buf, "ROM type: (%x) %s\n", snes_header.bs_type,
x > 3 ? "Unknown" : snes_bs_type[x]);
strcat (rominfo->misc, buf);
/*
It seems logical that the same condition as for regular cartridge dumps
tells whether it's a FastROM or a SlowROM. The original condition was
"(snes_header.bs_map_type >> 4) > 2".
*/
sprintf (buf, "ROM speed: %s\n",
snes_header.bs_map_type & 0x10 ? "120 ns (FastROM)" : "200 ns (SlowROM)");
strcat (rominfo->misc, buf);
}
sprintf (buf, "Version: 1.%d", snes_header.version);
strcat (rominfo->misc, buf);
if (nsrt_header)
handle_nsrt_header (rominfo, (unsigned char *) &header, snes_country);
free (rom_buffer);
return result;
}
#if 1
int
snes_check_bs (void)
{
if ((snes_header.maker == 0x33 || snes_header.maker == 0xff) &&
(snes_header.map_type == 0 || (snes_header.map_type & 0x83) == 0x80))
{
int date = (snes_header.bs_day << 8) | snes_header.bs_month;
if (date == 0)
return 2; // BS add-on cartridge dump
else if (date == 0xffff ||
((snes_header.bs_month & 0xf) == 0 &&
((unsigned int) ((snes_header.bs_month >> 4) - 1)) < 12))
return 1; // BS dump (via BSX)
}
return 0;
}
#else
static int
check_char (unsigned char c)
{
if ((c & 0x80) == 0)
return 0;
if ((c - 0x20) & 0x40)
return 1;
else
return 0;
}
static int
snes_bs_name (void)
{
unsigned int value;
int n, n_valid = 0;
for (n = 0; n < 16; n++)
{
value = snes_header.name[n];
if (check_char ((unsigned char) value) != 0)
{
value = snes_header.name[n + 1];
if (value < 0x20)
if ((n_valid != 11) || (value != 0)) // Dr. Mario Hack
break;
n_valid++;
n++;
}
else
{
if (value == 0)
{
if (n_valid == 0)
break;
continue;
}
if (value < 0x20)
break;
if (value >= 0x80)
if (value < 0xa0 || value >= 0xf0)
break;
n_valid++;
}
}
return n == 16 && n_valid > 0 ? 1 : 0;
}
int
snes_check_bs (void)
{
unsigned int value;
if (snes_header.bs_type & 0x4f)
return 0;
if (snes_header.maker != 0x33 && snes_header.maker != 0xff)
return 0;
value = (snes_header.bs_day << 8) | snes_header.bs_month;
if (value != 0x0000 && value != 0xffff)
{
if ((value & 0x040f) != 0)
return 0;
if ((value & 0xff) > 0xc0)
return 0;
}
if (snes_header.bs_map_type & 0xce || ((snes_header.bs_map_type & 0x30) == 0))
return 0;
if ((snes_header.map_type & 0x03) != 0)
return 0;
value = ((unsigned char *) &snes_header)[35];
if (value != 0x00 && value != 0xff)
return 0;
if (((unsigned char *) &snes_header)[36] != 0x00)
return 0;
return snes_bs_name ();
}
#endif
#if 1
int
snes_chksum (st_rominfo_t *rominfo, unsigned char **rom_buffer, int rom_size)
/*
Calculate the checksum of a SNES ROM. This version of snes_chksum() has one
advantage over the one below in that it is a bit more sensitive to overdumps.
*/
{
int i, internal_rom_size, half_internal_rom_size, remainder;
unsigned short int sum1, sum2;
if (!bs_dump && snes_header.rom_size <= 13) // largest known cart size is 64 Mbit
internal_rom_size = 1 << (snes_header.rom_size + 10);
else
internal_rom_size = st_dump ? rom_size - 8 * MBIT : rom_size;
half_internal_rom_size = internal_rom_size >> 1;
sum1 = 0;
if ((snes_header.rom_type == 0xf5 && snes_header.map_type != 0x30)
|| snes_header.rom_type == 0xf9 || bs_dump)
{
for (i = 0; i < rom_size; i++)
sum1 += (*rom_buffer)[i]; // Far East of Eden Zero (J)
if (rom_size == 24 * MBIT)
sum1 *= 2; // Momotaro Dentetsu Happy (J)
if (bs_dump) // Broadcast Satellaview "ROM"
for (i = rominfo->header_start;
i < (int) (rominfo->header_start + SNES_HEADER_LEN); i++)
sum1 -= (*rom_buffer)[i];
}
else
{
// Handle split files. Don't make this dependent of ucon64.split as
// the last file doesn't get detected as being split. Besides, we don't
// want to crash on *any* input data.
int i_start = st_dump ? 8 * MBIT : 0,
i_end = i_start +
(half_internal_rom_size > rom_size ? rom_size : half_internal_rom_size);
for (i = i_start; i < i_end; i++) // normal ROM
sum1 += (*rom_buffer)[i];
remainder = rom_size - i_start - half_internal_rom_size;
if (!remainder) // don't divide by zero below
remainder = half_internal_rom_size;
sum2 = 0;
for (i = i_start + half_internal_rom_size; i < rom_size; i++)
sum2 += (*rom_buffer)[i];
sum1 += sum2 * (half_internal_rom_size / remainder);
// printf ("DEBUG internal_rom_size: %d; half_internal_rom_size: %d; remainder: %d\n",
// internal_rom_size, half_internal_rom_size, remainder);
}
return sum1;
}
#else
int
snes_chksum (st_rominfo_t *rominfo, unsigned char **rom_buffer, int rom_size)
// Calculate the checksum of a SNES ROM
{
int i, internal_rom_size;
unsigned short int sum;
if (!bs_dump)
{
internal_rom_size = 1 << (snes_header.rom_size + 10);
if (internal_rom_size < rom_size)
internal_rom_size = rom_size;
if (internal_rom_size > 16 * 1024 *1024)
internal_rom_size = 16 * 1024 *1024;
}
else
internal_rom_size = rom_size;
// printf ("DEBUG internal_rom_size: %d; rom_size: %d\n", internal_rom_size, rom_size);
if (internal_rom_size > rom_size)
{
int blocksize;
unsigned char *ptr;
if (!(*rom_buffer = (unsigned char *) realloc (*rom_buffer, internal_rom_size)))
{
fprintf (stderr, ucon64_msg[ROM_BUFFER_ERROR], internal_rom_size);
return -1; // don't exit(), we might've been
} // called with -lsv
blocksize = internal_rom_size - rom_size;
ptr = *rom_buffer + rom_size;
if (blocksize % (3 * MBIT) == 0) // 6 (16 - 10), 12 (32 - 20), 24 (64 - 40)
{
blocksize /= 3;
for (i = 0; i < 3; i++)
memcpy (ptr + i * blocksize, ptr - blocksize, blocksize);
}
else
memcpy (ptr, ptr - blocksize, blocksize);
}
sum = 0;
if ((snes_header.rom_type == 0xf5 && snes_header.map_type != 0x30)
|| snes_header.rom_type == 0xf9 || bs_dump)
{
for (i = 0; i < rom_size; i++)
sum += (*rom_buffer)[i]; // Far East of Eden Zero (J)
if (rom_size == 24 * MBIT)
sum *= 2; // Momotaro Dentetsu Happy (J)
if (bs_dump)
for (i = rominfo->header_start;
i < (int) (rominfo->header_start + SNES_HEADER_LEN); i++)
sum -= (*rom_buffer)[i];
}
else
{
int i_start = st_dump ? 8 * MBIT : 0;
for (i = i_start; i < internal_rom_size; i++)
sum += (*rom_buffer)[i];
}
return sum;
}
#endif
int
snes_isprint (char *s, int len)
{
unsigned char *p = (unsigned char *) s;
for (; len >= 0; p++, len--)
// we don't use isprint(), because we don't want to get different results
// of check_banktype() for different locale settings
if (*p < 0x20 || *p > 0x7e)
return FALSE;
return TRUE;
}
int
check_banktype (unsigned char *rom_buffer, int header_offset)
/*
This function is used to check if the value of header_offset is a good guess
for the location of the internal SNES header (and thus of the bank type
(LoROM, HiROM or Extended HiROM)). The higher the returned value, the higher
the chance the guess was correct.
*/
{
int score = 0, x, y;
// dumper (stdout, (char *) rom_buffer + SNES_HEADER_START + header_offset,
// SNES_HEADER_LEN, SNES_HEADER_START + header_offset, DUMPER_HEX);
// game ID info (many games don't have useful info here)
if (snes_isprint ((char *) rom_buffer + SNES_HEADER_START + header_offset + 2, 4))
score += 1;
if (!bs_dump)
{
if (snes_isprint ((char *) rom_buffer + SNES_HEADER_START + header_offset + 16,
SNES_NAME_LEN))
score += 1;
// map type
x = rom_buffer[SNES_HEADER_START + header_offset + 37];
if ((x & 0xf) < 4)
score += 2;
y = rom_buffer[SNES_HEADER_START + header_offset + 38];
if (snes_hirom_ok && !(y == 0x34 || y == 0x35)) // ROM type for SA-1
// map type, HiROM flag (only if we're sure about value of snes_hirom)
if ((x & 1) == ((header_offset >= snes_header_base + SNES_HIROM) ? 1 : 0))
score += 1;
// ROM size
if (1 << (rom_buffer[SNES_HEADER_START + header_offset + 39] - 7) <= 64)
score += 1;
// SRAM size
if (1 << rom_buffer[SNES_HEADER_START + header_offset + 40] <= 256)
score += 1;
// country
if (rom_buffer[SNES_HEADER_START + header_offset + 41] <= 13)
score += 1;
}
else
{
if (snes_hirom_ok)
// map type, HiROM flag
if ((rom_buffer[SNES_HEADER_START + header_offset + 40] & 1) ==
((header_offset >= snes_header_base + SNES_HIROM) ? 1 : 0))
score += 1;
}
// publisher "escape code"
if (rom_buffer[SNES_HEADER_START + header_offset + 42] == 0x33)
score += 2;
else // publisher code
if (snes_isprint ((char *) rom_buffer + SNES_HEADER_START + header_offset, 2))
score += 2;
// version
if (rom_buffer[SNES_HEADER_START + header_offset + 43] <= 2)
score += 2;
// checksum bytes
x = rom_buffer[SNES_HEADER_START + header_offset + 44] +
(rom_buffer[SNES_HEADER_START + header_offset + 45] << 8);
y = rom_buffer[SNES_HEADER_START + header_offset + 46] +
(rom_buffer[SNES_HEADER_START + header_offset + 47] << 8);
if (x + y == 0xffff)
{
if (x == 0xffff || y == 0xffff)
score += 3;
else
score += 4;
}
// reset vector
if (rom_buffer[SNES_HEADER_START + header_offset + 0x4d] & 0x80)
score += 3;
return score;
}
int
snes_demirror (st_rominfo_t *rominfo) // nice verb :-)
{
int fixed = 0, size = ucon64.file_size - rominfo->buheader_len, mirror_size = 0;
char src_name[FILENAME_MAX], dest_name[FILENAME_MAX];
unsigned char *buffer;
if (!(buffer = (unsigned char *) malloc (size)))
{
fprintf (stderr, ucon64_msg[ROM_BUFFER_ERROR], size);
exit (1);
}
ucon64_fread (buffer, rominfo->buheader_len, size, ucon64.rom);
if (rominfo->interleaved)
{
printf ("NOTE: ROM is interleaved -- deinterleaving\n");
snes_deinterleave (rominfo, &buffer, size);
}
if (size % (12 * MBIT) == 0 && size != 36 * MBIT) // 12, 24 or 48 Mbit dumps can be mirrored
mirror_size = size / 12 * 2;
else if (size == 16 * MBIT) // ...and some C4 dumps too
mirror_size = 4 * MBIT;
else if (size == 32 * MBIT) // ...and some SA-1 dumps too
mirror_size = 8 * MBIT;
if (mirror_size)
{
if (memcmp (buffer + size - mirror_size, buffer + size - 2 * mirror_size,
mirror_size) == 0)
{
if (ucon64.quiet == -1)
printf ("Mirrored: %d - %d == %d - %d\n",
(size - 2 * mirror_size) / MBIT, (size - mirror_size) / MBIT,
(size - mirror_size) / MBIT, size / MBIT);
size -= mirror_size;
fixed = 1;
}
}
if (!fixed)
{
if (ucon64.quiet < 1)
printf ("NOTE: Did not detect a mirrored block -- no file has been written\n");
free (buffer);
return 1;
}
strcpy (src_name, ucon64.rom);
strcpy (dest_name, ucon64.rom);
ucon64_file_handler (dest_name, src_name, 0);
fcopy (src_name, 0, rominfo->buheader_len, dest_name, "wb");
ucon64_fwrite (buffer, rominfo->buheader_len, size, dest_name, "ab");
printf (ucon64_msg[WROTE], dest_name);
remove_temp_file ();
free (buffer);
return 0;
}
static void
write_game_table_entry (FILE *destfile, int file_no, st_rominfo_t *rominfo, int size)
{
int n, uses_DSP;
unsigned char name[0x1c], flags1, flags2;
static int slot = 0;
uses_DSP = snes_header.rom_type == 3 || snes_header.rom_type == 5 ||
snes_header.rom_type == 0xf6;
fseek (destfile, 0x4000 + (file_no - 1) * 0x20, SEEK_SET);
fputc (0xff, destfile); // 0x0 = 0xff
memcpy (name, rominfo->name, 0x1c);
for (n = 0; n < 0x1c; n++)
{
if (!isprint ((int) name[n]))
name[n] = '.';
else
name[n] = toupper (name[n]); // The Super Flash loader (SFBOTX2.GS)
} // only supports upper case characters
fwrite (name, 1, 0x1c, destfile); // 0x1 - 0x1c = name
if (snes_sramsize)
{
if (snes_sramsize == 2 * 1024)
flags2 = 0x00;
else if (snes_sramsize == 8 * 1024)
flags2 = 0x10;
else if (snes_sramsize == 32 * 1024)
flags2 = 0x20;
else // if (snes_sramsize == 128 * 1024) // Default to 1024 kbit SRAM
flags2 = 0x30;
}
else
flags2 = 0x40;
if (snes_header_base == SNES_EROM) // Enable Extended Map for >32 Mbit ROMs
flags2 |= 0x80;
flags1 = snes_hirom ? 0x10 : 0x00;
if (!snes_hirom && uses_DSP) // Set LoROM DSP flag if necessary
flags1 |= 0x01;
if (slot == 0)
flags1 |= 0x00;
else if (slot == 0x200000)
flags1 |= 0x40;
else if (slot == 0x400000)
flags1 |= 0x20;
else if (slot == 0x600000)
flags1 |= 0x60;
slot += (size + 16 * MBIT - 1) & ~(16 * MBIT - 1);
fputc (flags1, destfile); // 0x1d = mapping flags
fputc (flags2, destfile); // 0x1e = SRAM flags
fputc (size / 0x8000, destfile); // 0x1f = ROM size (not used by loader)
}
int
snes_multi (int truncate_size, char *fname)
{
#define BUFSIZE (32 * 1024)
int n, n_files, file_no, bytestowrite, byteswritten, done, truncated = 0,
totalsize_disk = 0, totalsize_card = 0, org_do_not_calc_crc = ucon64.do_not_calc_crc;
struct stat fstate;
FILE *srcfile, *destfile;
char destname[FILENAME_MAX];
unsigned char buffer[BUFSIZE];
if (truncate_size == 0)
{
fprintf (stderr, "ERROR: Can't make multi-game file of 0 bytes\n");
return -1;
}
if (fname != NULL)
{
strcpy (destname, fname);
n_files = ucon64.argc;
}
else
{
strcpy (destname, ucon64.argv[ucon64.argc - 1]);
n_files = ucon64.argc - 1;
}
ucon64_file_handler (destname, NULL, OF_FORCE_BASENAME);
if ((destfile = fopen (destname, "wb")) == NULL)
{
fprintf (stderr, ucon64_msg[OPEN_WRITE_ERROR], destname);
return -1;
}
printf ("Creating multi-game file for Super Flash: %s\n", destname);
file_no = 0;
for (n = 1; n < n_files; n++)
{
if (access (ucon64.argv[n], F_OK))
continue; // "file" does not exist (option)
stat (ucon64.argv[n], &fstate);
if (!S_ISREG (fstate.st_mode))
continue;
if (file_no == 5) // loader + 4 games
{
puts ("WARNING: A multi-game file can contain a maximum of 4 games. The other files\n"
" are ignored.");
break;
}
ucon64.console = UCON64_UNKNOWN;
ucon64.rom = ucon64.argv[n];
ucon64.file_size = fsizeof (ucon64.rom);
// DON'T use fstate.st_size, because file could be compressed
ucon64.rominfo->buheader_len = UCON64_ISSET (ucon64.buheader_len) ?
ucon64.buheader_len : 0;
ucon64.rominfo->interleaved = UCON64_ISSET (ucon64.interleaved) ?
ucon64.interleaved : 0;
ucon64.do_not_calc_crc = 1;
if (snes_init (ucon64.rominfo) != 0)
printf ("WARNING: %s does not appear to be a SNES ROM\n", ucon64.rom);
else if (ucon64.rominfo->interleaved)
printf ("WARNING: %s appears to be interleaved\n", ucon64.rom);
if ((srcfile = fopen (ucon64.rom, "rb")) == NULL)
{
fprintf (stderr, ucon64_msg[OPEN_READ_ERROR], ucon64.rom);
continue;
}
if (ucon64.rominfo->buheader_len)
fseek (srcfile, ucon64.rominfo->buheader_len, SEEK_SET);
if (file_no == 0)
{
printf ("Loader: %s\n", ucon64.rom);
if (ucon64.file_size - ucon64.rominfo->buheader_len != 32 * 1024)
printf ("WARNING: Are you sure %s is a loader binary?\n", ucon64.rom);
}
else
{
printf ("ROM%d: %s\n", file_no, ucon64.rom);
write_game_table_entry (destfile, file_no, ucon64.rominfo,
ucon64.file_size - ucon64.rominfo->buheader_len);
fseek (destfile, totalsize_disk, SEEK_SET); // restore file pointer
}
done = 0;
byteswritten = 0; // # of bytes written per file
while (!done)
{
bytestowrite = fread (buffer, 1, BUFSIZE, srcfile);
if (totalsize_disk + bytestowrite > truncate_size)
{
bytestowrite = truncate_size - totalsize_disk;
done = 1;
truncated = 1;
printf ("Output file is %d Mbit, truncating %s, skipping %d bytes\n",
truncate_size / MBIT, ucon64.rom, ucon64.file_size -
ucon64.rominfo->buheader_len - (byteswritten + bytestowrite));
}
else if (totalsize_card + bytestowrite > 64 * MBIT - 32 * 1024)
{
/*
Note that it is correct to check for any size larger than 64
Mbit - 32 kB, as we always overwrite the last 32 kB of the flash
card. Note also that this means it's useless to write a smaller
loader.
*/
bytestowrite = 64 * MBIT - 32 * 1024 - totalsize_card;
done = 1;
truncated = 1;
printf ("Output file needs 64 Mbit on flash card, truncating %s, skipping %d bytes\n",
ucon64.rom, ucon64.file_size -
ucon64.rominfo->buheader_len - (byteswritten + bytestowrite));
}
totalsize_disk += bytestowrite;
if (file_no > 0)
totalsize_card += bytestowrite;
if (bytestowrite == 0)
done = 1;
fwrite (buffer, 1, bytestowrite, destfile);
byteswritten += bytestowrite;
}
file_no++;
// We don't need padding for Super Flash as sf_write_rom() will care
// about alignment. Games have to be aligned to a 16 Mbit boundary.
totalsize_card = (totalsize_card + 16 * MBIT - 1) & ~(16 * MBIT - 1);
fclose (srcfile);
if (truncated)
break;
}
// fill the next game table entry
fseek (destfile, 0x4000 + (file_no - 1) * 0x20, SEEK_SET);
fputc (0, destfile); // indicate no next game
fclose (destfile);
ucon64.console = UCON64_SNES;
ucon64.do_not_calc_crc = org_do_not_calc_crc;
return 0;
}
int
snes_densrt (st_rominfo_t *rominfo)
{
int size = ucon64.file_size - rominfo->buheader_len, header_start;
char src_name[FILENAME_MAX], dest_name[FILENAME_MAX];
unsigned char buheader[512], *buffer;
if (!nsrt_header)
{
if (ucon64.quiet < 1)
printf ("NOTE: ROM has no NSRT header -- no file has been written\n");
return 1;
}
ucon64_fread (buheader, 0, 512, ucon64.rom);
if (!(buffer = (unsigned char *) malloc (size)))
{
fprintf (stderr, ucon64_msg[ROM_BUFFER_ERROR], size);
exit (1);
}
ucon64_fread (buffer, rominfo->buheader_len, size, ucon64.rom);
if (rominfo->interleaved)
header_start = SNES_HEADER_START + (snes_hirom ? 0 : size / 2); // (Ext.) HiROM : LoROM
else
header_start = rominfo->header_start;
get_nsrt_info (buffer, header_start, buheader);
memset (buheader + 0x1d0, 0, 32); // remove NSRT header
strcpy (src_name, ucon64.rom);
strcpy (dest_name, ucon64.rom);
ucon64_file_handler (dest_name, src_name, 0);
ucon64_fwrite (buheader, 0, 512, dest_name, "wb");
if (rominfo->buheader_len > 512)
fcopy (src_name, 512, rominfo->buheader_len - 512, dest_name, "ab");
ucon64_fwrite (buffer, rominfo->buheader_len, size, dest_name, "ab");
printf (ucon64_msg[WROTE], dest_name);
remove_temp_file ();
free (buffer);
return 0;
}
static void
set_nsrt_checksum (unsigned char *header)
{
int n;
char checksum = -1;
for (n = 0x1d0; n <= 0x1ed; n++)
checksum += header[n];
header[0x1ee] = checksum;
header[0x1ef] = ~checksum;
}
static void
set_nsrt_info (st_rominfo_t *rominfo, unsigned char *header)
/*
This function will write an NSRT header if the user specified a controller
type, but only if the checksum is correct. We write a complete NSRT header
only to be 100% compatible with NSRT. We are only interested in the
controller type feature, though.
NSRT is a SNES ROM tool. See developers.html.
NSRT header format (0x1d0 - 0x1ef, offsets in _copier header_):
0x1d0 low nibble = original country value
high nibble = bank type
1 = LoROM
2 = HiROM
3 = "Extended" HiROM
0x1d1 - 0x1e5 original game name
0x1e6 low byte of original SNES checksum
0x1e7 high byte of original SNES checksum
0x1e8 - 0x1eb "NSRT"
0x1ec header version; a value of for example 15 should be
interpreted as 1.5
0x1ed low nibble = port 2 controller type
high nibble = port 1 controller type
0 = gamepad
1 = mouse
2 = mouse / gamepad
3 = super scope
4 = super scope / gamepad
5 = Konami's justifier
6 = multitap
7 = mouse / super scope / gamepad
0x1ee NSRT header checksum
the checksum is calculated by adding all bytes of the
NSRT header (except the checksum bytes themselves)
and then subtracting 1
0x1ef inverse NSRT header checksum
*/
{
int x;
if ((UCON64_ISSET (ucon64.controller) || UCON64_ISSET (ucon64.controller2))
&& !nsrt_header) // don't overwrite these values
{
if (rominfo->current_internal_crc != rominfo->internal_crc)
{
printf ("WARNING: The controller type info will be discarded (checksum is bad)\n");
return;
}
header[0x1d0] = bs_dump ? 0 : snes_header.country;
if (rominfo->header_start == SNES_EROM + SNES_HEADER_START + SNES_HIROM)
header[0x1d0] |= 0x30; // Note: Extended LoROM is not supported
else
header[0x1d0] |= snes_hirom ? 0x20 : 0x10;
if (!bs_dump && st_dump)
memcpy (header + 0x1d1, rominfo->name, 16);
else // for ST dumps, rominfo->name may be used
memcpy (header + 0x1d1, &snes_header.name, SNES_NAME_LEN);
header[0x1e6] = snes_header.checksum_low;
header[0x1e7] = snes_header.checksum_high;
memcpy (header + 0x1e8, "NSRT", 4);
header[0x1ec] = NSRT_HEADER_VERSION;
}
if (UCON64_ISSET (ucon64.controller))
{
for (x = 0; x < 8; x++)
if ((ucon64.controller >> x) & 1)
break;
if (x != 0 && x != 1 && x != 2 && x != 6)
{
printf ("WARNING: Invalid value for controller in port 1, using \"0\"\n");
x = 0;
}
header[0x1ed] = x << 4;
}
if (UCON64_ISSET (ucon64.controller2))
{
for (x = 0; x < 8; x++)
if ((ucon64.controller2 >> x) & 1)
break;
if (x >= 8)
{
printf ("WARNING: Invalid value for controller in port 2, using \"0\"\n");
x = 0;
}
header[0x1ed] |= x;
}
// set the checksum bytes
if (UCON64_ISSET (ucon64.controller) || UCON64_ISSET (ucon64.controller2))
set_nsrt_checksum (header);
}
static void
get_nsrt_info (unsigned char *rom_buffer, int header_start, unsigned char *buheader)
{
if (nsrt_header)
{
memcpy (rom_buffer + header_start + 16 - (st_dump ? SNES_HEADER_START : 0),
buheader + 0x1d1, (bs_dump || st_dump) ? 16 : SNES_NAME_LEN); // name
// we ignore interleaved ST dumps
if (!bs_dump)
{
// According to the NSRT specification, the region byte should be set
// to 0 for BS dumps.
rom_buffer[header_start + 41] = buheader[0x1d0] & 0x0f; // region
// NSRT only modifies the internal header. For BS dumps the internal
// checksum does not include the header. So, we don't have to
// overwrite the checksum.
rom_buffer[header_start + 44] = ~buheader[0x1e6]; // inverse checksum low
rom_buffer[header_start + 45] = ~buheader[0x1e7]; // inverse checksum high
rom_buffer[header_start + 46] = buheader[0x1e6]; // checksum low
rom_buffer[header_start + 47] = buheader[0x1e7]; // checksum high
}
}
}
static void
reset_header (void *header)
{
// preserve possible NSRT header
if (nsrt_header)
{
memset (header, 0, 0x1d0);
memset ((unsigned char *) header + 0x1f0, 0, 16);
((unsigned char *) header)[0x1ec] = NSRT_HEADER_VERSION;
set_nsrt_checksum ((unsigned char *) header);
}
else
memset (header, 0, SWC_HEADER_LEN);
}
static void
handle_nsrt_header (st_rominfo_t *rominfo, unsigned char *header,
const char **snes_country)
{
char buf[800], name[SNES_NAME_LEN + 1], *str_list[9] =
{
"Gamepad", "Mouse", "Mouse / Gamepad", "Super Scope",
"Super Scope / Gamepad", "Konami's Justifier", "Multitap",
"Mouse / Super Scope / Gamepad", "Unknown"
};
int x = header[0x1ed], ctrl1 = x >> 4, ctrl2 = x & 0xf,
name_len = (bs_dump || st_dump) ? 16 : SNES_NAME_LEN;
memcpy (name, header + 0x1d1, name_len);
name[name_len] = 0;
for (x = 0; x < name_len; x++)
if (!isprint ((int) name[x]))
name[x] = '.';
if (ctrl1 > 8)
ctrl1 = 8;
if (ctrl2 > 8)
ctrl2 = 8;
sprintf (buf, "\nNSRT info:\n"
" Original country: %s\n"
" Original game name: \"%s\"\n"
" Original checksum: 0x%04x\n"
" Port 1 controller type: %s\n"
" Port 2 controller type: %s\n"
" Header version: %.1f",
NULL_TO_UNKNOWN_S (snes_country[MIN (header[0x1d0] & 0xf, SNES_COUNTRY_MAX - 1)]),
name,
header[0x1e6] + (header[0x1e7] << 8),
str_list[ctrl1],
str_list[ctrl2],
header[0x1ec] / 10.f);
strcat (rominfo->misc, buf);
}