/* snes.c - Super NES support for uCON64 Copyright (c) 1999 - 2002 NoisyB 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 #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #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); }