/*****************************************************************************\ Snes9x - Portable Super Nintendo Entertainment System (TM) emulator. This file is licensed under the Snes9x License. For further information, consult the LICENSE file in the root directory. \*****************************************************************************/ #include #include #include #ifdef UNZIP_SUPPORT # ifdef SYSTEM_ZIP # include # else # include "unzip/unzip.h" # endif #endif #ifdef JMA_SUPPORT #include "jma/s9x-jma.h" #endif #include #include #include "snes9x.h" #include "memmap.h" #include "apu/apu.h" #include "fxemu.h" #include "sdd1.h" #include "srtc.h" #include "controls.h" #include "cheats.h" #include "movie.h" #include "display.h" #include "sha256.h" #include "snapshot.h" #ifndef SET_UI_COLOR #define SET_UI_COLOR(r, g, b) ; #endif #ifndef max #define max(a, b) (((a) > (b)) ? (a) : (b)) #endif #ifndef min #define min(a, b) (((a) < (b)) ? (a) : (b)) #endif static bool8 stopMovie = TRUE; // from NSRT static const char *nintendo_licensees[] = { "Unlicensed", "Nintendo", "Rocket Games/Ajinomoto", "Imagineer-Zoom", "Gray Matter", "Zamuse", "Falcom", NULL, "Capcom", "Hot B Co.", "Jaleco", "Coconuts Japan", "Coconuts Japan/G.X.Media", "Micronet", "Technos", "Mebio Software", "Shouei System", "Starfish", NULL, "Mitsui Fudosan/Dentsu", NULL, "Warashi Inc.", NULL, "Nowpro", NULL, "Game Village", "IE Institute", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Banarex", "Starfish", "Infocom", "Electronic Arts Japan", NULL, "Cobra Team", "Human/Field", "KOEI", "Hudson Soft", "S.C.P./Game Village", "Yanoman", NULL, "Tecmo Products", "Japan Glary Business", "Forum/OpenSystem", "Virgin Games (Japan)", "SMDE", "Yojigen", NULL, "Daikokudenki", NULL, NULL, NULL, NULL, NULL, "Creatures Inc.", "TDK Deep Impresion", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Destination Software/KSS", "Sunsoft/Tokai Engineering", "POW (Planning Office Wada)/VR 1 Japan", "Micro World", NULL, "San-X", "Enix", "Loriciel/Electro Brain", "Kemco Japan", "Seta Co.,Ltd.", "Culture Brain", "Irem Corp.", "Palsoft", "Visit Co., Ltd.", "Intec", "System Sacom", "Poppo", "Ubisoft Japan", NULL, "Media Works", "NEC InterChannel", "Tam", "Gajin/Jordan", "Smilesoft", NULL, NULL, "Mediakite", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Viacom", "Carrozzeria", "Dynamic", NULL, "Magifact", "Hect", "Codemasters", "Taito/GAGA Communications", "Laguna", "Telstar Fun & Games/Event/Taito", NULL, "Arcade Zone Ltd.", "Entertainment International/Empire Software", "Loriciel", "Gremlin Graphics", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Seika Corp.", "UBI SOFT Entertainment Software", "Sunsoft US", NULL, "Life Fitness", NULL, "System 3", "Spectrum Holobyte", NULL, "Irem", NULL, "Raya Systems", "Renovation Products", "Malibu Games", NULL, "Eidos/U.S. Gold", "Playmates Interactive", NULL, NULL, "Fox Interactive", "Time Warner Interactive", NULL, NULL, NULL, NULL, NULL, "Disney Interactive", NULL, "Black Pearl", NULL, "Advanced Productions", NULL, NULL, "GT Interactive", "RARE", "Crave Entertainment", "Absolute Entertainment", "Acclaim", "Activision", "American Sammy", "Take 2/GameTek", "Hi Tech", "LJN Ltd.", NULL, "Mattel", NULL, "Mindscape/Red Orb Entertainment", "Romstar", "Taxan", "Midway/Tradewest", NULL, "American Softworks Corp.", "Majesco Sales Inc.", "3DO", NULL, NULL, "Hasbro", "NewKidCo", "Telegames", "Metro3D", NULL, "Vatical Entertainment", "LEGO Media", NULL, "Xicat Interactive", "Cryo Interactive", NULL, NULL, "Red Storm Entertainment", "Microids", NULL, "Conspiracy/Swing", "Titus", "Virgin Interactive", "Maxis", NULL, "LucasArts Entertainment", NULL, NULL, "Ocean", NULL, "Electronic Arts", NULL, "Laser Beam", NULL, NULL, "Elite Systems", "Electro Brain", "The Learning Company", "BBC", NULL, "Software 2000", NULL, "BAM! Entertainment", "Studio 3", NULL, NULL, NULL, "Classified Games", NULL, "TDK Mediactive", NULL, "DreamCatcher", "JoWood Produtions", "SEGA", "Wannado Edition", "LSP (Light & Shadow Prod.)", "ITE Media", "Infogrames", "Interplay", "JVC (US)", "Parker Brothers", NULL, "SCI (Sales Curve Interactive)/Storm", NULL, NULL, "THQ Software", "Accolade Inc.", "Triffix Entertainment", NULL, "Microprose Software", "Universal Interactive/Sierra/Simon & Schuster", NULL, "Kemco", "Rage Software", "Encore", NULL, "Zoo", "Kiddinx", "Simon & Schuster Interactive", "Asmik Ace Entertainment Inc./AIA", "Empire Interactive", NULL, NULL, "Jester Interactive", NULL, "Rockstar Games", "Scholastic", "Ignition Entertainment", "Summitsoft", "Stadlbauer", NULL, NULL, NULL, "Misawa", "Teichiku", "Namco Ltd.", "LOZC", "KOEI", NULL, "Tokuma Shoten Intermedia", "Tsukuda Original", "DATAM-Polystar", NULL, NULL, "Bullet-Proof Software", "Vic Tokai Inc.", NULL, "Character Soft", "I'Max", "Saurus", NULL, NULL, "General Entertainment", NULL, NULL, "I'Max", "Success", NULL, "SEGA Japan", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Takara", "Chun Soft", "Video System Co., Ltd./McO'River", "BEC", NULL, "Varie", "Yonezawa/S'pal", "Kaneko", NULL, "Victor Interactive Software/Pack-in-Video", "Nichibutsu/Nihon Bussan", "Tecmo", "Imagineer", NULL, NULL, "Nova", "Den'Z", "Bottom Up", NULL, "TGL (Technical Group Laboratory)", NULL, "Hasbro Japan", NULL, "Marvelous Entertainment", NULL, "Keynet Inc.", "Hands-On Entertainment", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Telenet", "Hori", NULL, NULL, "Konami", "K.Amusement Leasing Co.", "Kawada", "Takara", NULL, "Technos Japan Corp.", "JVC (Europe/Japan)/Victor Musical Industries", NULL, "Toei Animation", "Toho", NULL, "Namco", "Media Rings Corp.", "J-Wing", NULL, "Pioneer LDC", "KID", "Mediafactory", NULL, NULL, NULL, "Infogrames Hudson", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Acclaim Japan", "ASCII Co./Nexoft", "Bandai", NULL, "Enix", NULL, "HAL Laboratory/Halken", "SNK", NULL, "Pony Canyon Hanbai", "Culture Brain", "Sunsoft", "Toshiba EMI", "Sony Imagesoft", NULL, "Sammy", "Magical", "Visco", NULL, "Compile", NULL, "MTO Inc.", NULL, "Sunrise Interactive", NULL, "Global A Entertainment", "Fuuki", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Taito", NULL, "Kemco", "Square", "Tokuma Shoten", "Data East", "Tonkin House", NULL, "KOEI", NULL, "Konami/Ultra/Palcom", "NTVIC/VAP", "Use Co., Ltd.", "Meldac", "Pony Canyon (Japan)/FCI (US)", "Angel/Sotsu Agency/Sunrise", "Yumedia/Aroma Co., Ltd.", NULL, NULL, "Boss", "Axela/Crea-Tech", "Sekaibunka-Sha/Sumire kobo/Marigul Management Inc.", "Konami Computer Entertainment Osaka", NULL, NULL, "Enterbrain", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Taito/Disco", "Sofel", "Quest Corp.", "Sigma", "Ask Kodansha", NULL, "Naxat", "Copya System", "Capcom Co., Ltd.", "Banpresto", "TOMY", "Acclaim/LJN Japan", NULL, "NCS", "Human Entertainment", "Altron", "Jaleco", "Gaps Inc.", NULL, NULL, NULL, NULL, NULL, "Elf", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Jaleco", NULL, "Yutaka", "Varie", "T&ESoft", "Epoch Co., Ltd.", NULL, "Athena", "Asmik", "Natsume", "King Records", "Atlus", "Epic/Sony Records (Japan)", NULL, "IGS (Information Global Service)", NULL, "Chatnoir", "Right Stuff", NULL, "NTT COMWARE", NULL, "Spike", "Konami Computer Entertainment Tokyo", "Alphadream Corp.", NULL, "Sting", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "A Wave", "Motown Software", "Left Field Entertainment", "Extreme Entertainment Group", "TecMagik", NULL, NULL, NULL, NULL, "Cybersoft", NULL, "Psygnosis", NULL, NULL, "Davidson/Western Tech.", "Unlicensed", NULL, NULL, NULL, NULL, "The Game Factory Europe", "Hip Games", "Aspyr", NULL, NULL, "Mastiff", "iQue", "Digital Tainment Pool", "XS Games", "Daiwon", NULL, NULL, NULL, NULL, NULL, NULL, NULL, "PCCW Japan", NULL, NULL, "KiKi Co. Ltd.", "Open Sesame Inc.", "Sims", "Broccoli", "Avex", "D3 Publisher", NULL, "Konami Computer Entertainment Japan", NULL, "Square-Enix", "KSG", "Micott & Basara Inc.", NULL, "Orbital Media", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "The Game Factory USA", NULL, NULL, "Treasure", "Aruze", "Ertain", "SNK Playmore", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Yojigen" }; static const uint32 crc32Table[256] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; static void S9xDeinterleaveType1 (int, uint8 *); static void S9xDeinterleaveType2 (int, uint8 *); static void S9xDeinterleaveGD24 (int, uint8 *); static bool8 allASCII (uint8 *, int); static bool8 is_SufamiTurbo_BIOS (const uint8 *, uint32); static bool8 is_SufamiTurbo_Cart (const uint8 *, uint32); static bool8 is_BSCart_BIOS (const uint8 *, uint32); static bool8 is_BSCartSA1_BIOS(const uint8 *, uint32); static bool8 is_GNEXT_Add_On (const uint8 *, uint32); static uint32 caCRC32 (uint8 *, uint32, uint32 crc32 = 0xffffffff); static bool8 ReadUPSPatch (Stream *, long, int32 &); static long ReadInt (Stream *, unsigned); static bool8 ReadIPSPatch (Stream *, long, int32 &); #ifdef UNZIP_SUPPORT static int unzFindExtension (unzFile &, const char *, bool restart = TRUE, bool print = TRUE, bool allowExact = FALSE); #endif // deinterleave static void S9xDeinterleaveType1 (int size, uint8 *base) { Settings.DisplayColor = BUILD_PIXEL(0, 31, 0); SET_UI_COLOR(0, 255, 0); uint8 blocks[256]; int nblocks = size >> 16; for (int i = 0; i < nblocks; i++) { blocks[i * 2] = i + nblocks; blocks[i * 2 + 1] = i; } uint8 *tmp = (uint8 *) malloc(0x8000); if (tmp) { for (int i = 0; i < nblocks * 2; i++) { for (int j = i; j < nblocks * 2; j++) { if (blocks[j] == i) { memmove(tmp, &base[blocks[j] * 0x8000], 0x8000); memmove(&base[blocks[j] * 0x8000], &base[blocks[i] * 0x8000], 0x8000); memmove(&base[blocks[i] * 0x8000], tmp, 0x8000); uint8 b = blocks[j]; blocks[j] = blocks[i]; blocks[i] = b; break; } } } free(tmp); } } static void S9xDeinterleaveType2 (int size, uint8 *base) { // for odd Super FX images Settings.DisplayColor = BUILD_PIXEL(31, 14, 6); SET_UI_COLOR(255, 119, 25); uint8 blocks[256]; int nblocks = size >> 16; int step = 64; while (nblocks <= step) step >>= 1; nblocks = step; for (int i = 0; i < nblocks * 2; i++) blocks[i] = (i & ~0xf) | ((i & 3) << 2) | ((i & 12) >> 2); uint8 *tmp = (uint8 *) malloc(0x10000); if (tmp) { for (int i = 0; i < nblocks * 2; i++) { for (int j = i; j < nblocks * 2; j++) { if (blocks[j] == i) { memmove(tmp, &base[blocks[j] * 0x10000], 0x10000); memmove(&base[blocks[j] * 0x10000], &base[blocks[i] * 0x10000], 0x10000); memmove(&base[blocks[i] * 0x10000], tmp, 0x10000); uint8 b = blocks[j]; blocks[j] = blocks[i]; blocks[i] = b; break; } } } free(tmp); } } static void S9xDeinterleaveGD24 (int size, uint8 *base) { // for 24Mb images dumped with Game Doctor if (size != 0x300000) return; Settings.DisplayColor = BUILD_PIXEL(0, 31, 31); SET_UI_COLOR(0, 255, 255); uint8 *tmp = (uint8 *) malloc(0x80000); if (tmp) { memmove(tmp, &base[0x180000], 0x80000); memmove(&base[0x180000], &base[0x200000], 0x80000); memmove(&base[0x200000], &base[0x280000], 0x80000); memmove(&base[0x280000], tmp, 0x80000); free(tmp); S9xDeinterleaveType1(size, base); } } // allocation and deallocation bool8 CMemory::Init (void) { IPPU.TileCache[TILE_2BIT] = (uint8 *) malloc(MAX_2BIT_TILES * 64); IPPU.TileCache[TILE_4BIT] = (uint8 *) malloc(MAX_4BIT_TILES * 64); IPPU.TileCache[TILE_8BIT] = (uint8 *) malloc(MAX_8BIT_TILES * 64); IPPU.TileCache[TILE_2BIT_EVEN] = (uint8 *) malloc(MAX_2BIT_TILES * 64); IPPU.TileCache[TILE_2BIT_ODD] = (uint8 *) malloc(MAX_2BIT_TILES * 64); IPPU.TileCache[TILE_4BIT_EVEN] = (uint8 *) malloc(MAX_4BIT_TILES * 64); IPPU.TileCache[TILE_4BIT_ODD] = (uint8 *) malloc(MAX_4BIT_TILES * 64); IPPU.TileCached[TILE_2BIT] = (uint8 *) malloc(MAX_2BIT_TILES); IPPU.TileCached[TILE_4BIT] = (uint8 *) malloc(MAX_4BIT_TILES); IPPU.TileCached[TILE_8BIT] = (uint8 *) malloc(MAX_8BIT_TILES); IPPU.TileCached[TILE_2BIT_EVEN] = (uint8 *) malloc(MAX_2BIT_TILES); IPPU.TileCached[TILE_2BIT_ODD] = (uint8 *) malloc(MAX_2BIT_TILES); IPPU.TileCached[TILE_4BIT_EVEN] = (uint8 *) malloc(MAX_4BIT_TILES); IPPU.TileCached[TILE_4BIT_ODD] = (uint8 *) malloc(MAX_4BIT_TILES); if (!IPPU.TileCache[TILE_2BIT] || !IPPU.TileCache[TILE_4BIT] || !IPPU.TileCache[TILE_8BIT] || !IPPU.TileCache[TILE_2BIT_EVEN] || !IPPU.TileCache[TILE_2BIT_ODD] || !IPPU.TileCache[TILE_4BIT_EVEN] || !IPPU.TileCache[TILE_4BIT_ODD] || !IPPU.TileCached[TILE_2BIT] || !IPPU.TileCached[TILE_4BIT] || !IPPU.TileCached[TILE_8BIT] || !IPPU.TileCached[TILE_2BIT_EVEN] || !IPPU.TileCached[TILE_2BIT_ODD] || !IPPU.TileCached[TILE_4BIT_EVEN] || !IPPU.TileCached[TILE_4BIT_ODD]) { Deinit(); return (FALSE); } ROMStorage.resize(MAX_ROM_SIZE + 0x200 + 0x8000); std::fill(ROMStorage.begin(), ROMStorage.end(), 0); SRAMStorage.resize(0x80000); std::fill(SRAMStorage.begin(), SRAMStorage.end(), 0); SRAM = &SRAMStorage[0]; memset(RAM, 0, sizeof(RAM)); memset(VRAM, 0, sizeof(VRAM)); memset(IPPU.TileCache[TILE_2BIT], 0, MAX_2BIT_TILES * 64); memset(IPPU.TileCache[TILE_4BIT], 0, MAX_4BIT_TILES * 64); memset(IPPU.TileCache[TILE_8BIT], 0, MAX_8BIT_TILES * 64); memset(IPPU.TileCache[TILE_2BIT_EVEN], 0, MAX_2BIT_TILES * 64); memset(IPPU.TileCache[TILE_2BIT_ODD], 0, MAX_2BIT_TILES * 64); memset(IPPU.TileCache[TILE_4BIT_EVEN], 0, MAX_4BIT_TILES * 64); memset(IPPU.TileCache[TILE_4BIT_ODD], 0, MAX_4BIT_TILES * 64); memset(IPPU.TileCached[TILE_2BIT], 0, MAX_2BIT_TILES); memset(IPPU.TileCached[TILE_4BIT], 0, MAX_4BIT_TILES); memset(IPPU.TileCached[TILE_8BIT], 0, MAX_8BIT_TILES); memset(IPPU.TileCached[TILE_2BIT_EVEN], 0, MAX_2BIT_TILES); memset(IPPU.TileCached[TILE_2BIT_ODD], 0, MAX_2BIT_TILES); memset(IPPU.TileCached[TILE_4BIT_EVEN], 0, MAX_4BIT_TILES); memset(IPPU.TileCached[TILE_4BIT_ODD], 0, MAX_4BIT_TILES); // FillRAM uses first 32K of ROM image area, otherwise space just // wasted. Might be read by the SuperFX code. FillRAM = &ROMStorage[0]; // Add 0x8000 to ROM image pointer to stop SuperFX code accessing // unallocated memory (can cause crash on some ports). ROM = &ROMStorage[0x8000]; C4RAM = ROM + 0x400000 + 8192 * 8; // C4 OBC1RAM = ROM + 0x400000; // OBC1 BIOSROM = ROM + 0x300000; // BS BSRAM = ROM + 0x400000; // BS SuperFX.pvRegisters = FillRAM + 0x3000; SuperFX.nRamBanks = 2; // Most only use 1. 1=64KB=512Mb, 2=128KB=1024Mb SuperFX.pvRam = SRAM; SuperFX.nRomBanks = (2 * 1024 * 1024) / (32 * 1024); SuperFX.pvRom = (uint8 *) ROM; PostRomInitFunc = NULL; return (TRUE); } void CMemory::Deinit (void) { ROM = NULL; for (int t = 0; t < 7; t++) { if (IPPU.TileCache[t]) { free(IPPU.TileCache[t]); IPPU.TileCache[t] = NULL; } if (IPPU.TileCached[t]) { free(IPPU.TileCached[t]); IPPU.TileCached[t] = NULL; } } } // file management and ROM detection static bool8 allASCII (uint8 *b, int size) { for (int i = 0; i < size; i++) { if (b[i] < 32 || b[i] > 126) return (FALSE); } return (TRUE); } static bool8 is_SufamiTurbo_BIOS (const uint8 *data, uint32 size) { if (size == 0x40000 && strncmp((char *) data, "BANDAI SFC-ADX", 14) == 0 && strncmp((char * ) (data + 0x10), "SFC-ADX BACKUP", 14) == 0) return (TRUE); else return (FALSE); } static bool8 is_SufamiTurbo_Cart (const uint8 *data, uint32 size) { if (size >= 0x80000 && size <= 0x100000 && strncmp((char *) data, "BANDAI SFC-ADX", 14) == 0 && strncmp((char * ) (data + 0x10), "SFC-ADX BACKUP", 14) != 0) return (TRUE); else return (FALSE); } static bool8 is_BSCart_BIOS(const uint8 *data, uint32 size) { if ((data[0x7FB2] == 0x5A) && (data[0x7FB5] != 0x20) && (data[0x7FDA] == 0x33)) { Memory.LoROM = TRUE; Memory.HiROM = FALSE; return (TRUE); } else if ((data[0xFFB2] == 0x5A) && (data[0xFFB5] != 0x20) && (data[0xFFDA] == 0x33)) { Memory.LoROM = FALSE; Memory.HiROM = TRUE; return (TRUE); } else return (FALSE); } static bool8 is_BSCartSA1_BIOS (const uint8 *data, uint32 size) { //Same basic check as BSCart if (!is_BSCart_BIOS(data, size)) return (FALSE); //Checks if the game is Itoi's Bass Fishing No. 1 (ZBPJ) or SD Gundam G-NEXT (ZX3J) if (strncmp((char *)(data + 0x7fb2), "ZBPJ", 4) == 0 || strncmp((char *)(data + 0x7fb2), "ZX3J", 4) == 0) return (TRUE); else return (FALSE); } static bool8 is_GNEXT_Add_On (const uint8 *data, uint32 size) { if (size == 0x80000) return (TRUE); else return (FALSE); } int CMemory::ScoreHiROM (bool8 skip_header, int32 romoff) { uint8 *buf = ROM + 0xff00 + romoff + (skip_header ? 0x200 : 0); int score = 0; // Check for extended HiROM expansion used in Mother 2 Deluxe et al. // Looks for size byte 13 (8MB) and an actual ROM size greater than 4MB if (buf[0xd7] == 13 && CalculatedSize > 1024 * 1024 * 4) score += 5; if (buf[0xd5] & 0x1) score += 2; // Mode23 is SA-1 if (buf[0xd5] == 0x23) score -= 2; if (buf[0xd4] == 0x20) score += 2; if ((buf[0xdc] + (buf[0xdd] << 8)) + (buf[0xde] + (buf[0xdf] << 8)) == 0xffff) { score += 2; if (0 != (buf[0xde] + (buf[0xdf] << 8))) score++; } if (buf[0xda] == 0x33) score += 2; if ((buf[0xd5] & 0xf) < 4) score += 2; if (!(buf[0xfd] & 0x80)) score -= 6; if ((buf[0xfc] + (buf[0xfd] << 8)) > 0xffb0) score -= 2; // reduced after looking at a scan by Cowering if (CalculatedSize > 1024 * 1024 * 3) score += 4; if ((1 << (buf[0xd7] - 7)) > 48) score -= 1; if (!allASCII(&buf[0xb0], 6)) score -= 1; if (!allASCII(&buf[0xc0], ROM_NAME_LEN - 1)) score -= 1; return (score); } int CMemory::ScoreLoROM (bool8 skip_header, int32 romoff) { uint8 *buf = ROM + 0x7f00 + romoff + (skip_header ? 0x200 : 0); int score = 0; if (!(buf[0xd5] & 0x1)) score += 3; // Mode23 is SA-1 if (buf[0xd5] == 0x23) score += 2; if ((buf[0xdc] + (buf[0xdd] << 8)) + (buf[0xde] + (buf[0xdf] << 8)) == 0xffff) { score += 2; if (0 != (buf[0xde] + (buf[0xdf] << 8))) score++; } if (buf[0xda] == 0x33) score += 2; if ((buf[0xd5] & 0xf) < 4) score += 2; if (!(buf[0xfd] & 0x80)) score -= 6; if ((buf[0xfc] + (buf[0xfd] << 8)) > 0xffb0) score -= 2; // reduced per Cowering suggestion if (CalculatedSize <= 1024 * 1024 * 16) score += 2; if ((1 << (buf[0xd7] - 7)) > 48) score -= 1; if (!allASCII(&buf[0xb0], 6)) score -= 1; if (!allASCII(&buf[0xc0], ROM_NAME_LEN - 1)) score -= 1; return (score); } int CMemory::First512BytesCountZeroes() const { const uint8 *buf = ROM; int zeroCount = 0; for (int i = 0; i < 512; i++) { if (buf[i] == 0) { zeroCount++; } } return zeroCount; } uint32 CMemory::HeaderRemove (uint32 size, uint8 *buf) { uint32 calc_size = (size / 0x2000) * 0x2000; if ((size - calc_size == 512 && !Settings.ForceNoHeader) || Settings.ForceHeader) { uint8 *NSRTHead = buf + 0x1D0; // NSRT Header Location // detect NSRT header if (!strncmp("NSRT", (char *) &NSRTHead[24], 4)) { if (NSRTHead[28] == 22) { if (((std::accumulate(NSRTHead, NSRTHead + sizeof(NSRTHeader), 0) & 0xFF) == NSRTHead[30]) && (NSRTHead[30] + NSRTHead[31] == 255) && ((NSRTHead[0] & 0x0F) <= 13) && (((NSRTHead[0] & 0xF0) >> 4) <= 3) && ((NSRTHead[0] & 0xF0) >> 4)) memcpy(NSRTHeader, NSRTHead, sizeof(NSRTHeader)); } } memmove(buf, buf + 512, calc_size); HeaderCount++; size -= 512; } return (size); } uint32 CMemory::FileLoader (uint8 *buffer, const char *filename, uint32 maxsize) { // <- ROM size without header // ** Memory.HeaderCount // ** Memory.ROMFilename uint32 totalSize = 0; memset(NSRTHeader, 0, sizeof(NSRTHeader)); HeaderCount = 0; auto path = splitpath(filename); int nFormat = FILE_DEFAULT; if (path.ext_is(".zip") || path.ext_is(".msu1")) nFormat = FILE_ZIP; else if (path.ext_is(".jma")) nFormat = FILE_JMA; switch (nFormat) { case FILE_ZIP: { #ifdef UNZIP_SUPPORT if (!LoadZip(filename, &totalSize, buffer)) { S9xMessage(S9X_ERROR, S9X_ROM_INFO, "Invalid Zip archive."); return (0); } ROMFilename = filename; #else S9xMessage(S9X_ERROR, S9X_ROM_INFO, "This binary was not created with Zip support."); return (0); #endif break; } case FILE_JMA: { #ifdef JMA_SUPPORT size_t size = load_jma_file(filename, buffer); if (!size) { S9xMessage(S9X_ERROR, S9X_ROM_INFO, "Invalid JMA archive."); return (0); } totalSize = HeaderRemove(size, buffer); ROMFilename = filename; #else S9xMessage(S9X_ERROR, S9X_ROM_INFO, "This binary was not created with JMA support."); return (0); #endif break; } case FILE_DEFAULT: default: { STREAM fp = OPEN_STREAM(filename, "rb"); if (!fp) return (0); ROMFilename = filename; uint32 size = 0; size = READ_STREAM(buffer, maxsize + 0x200, fp); CLOSE_STREAM(fp); totalSize = HeaderRemove(size, buffer); break; } } if (HeaderCount == 0) S9xMessage(S9X_INFO, S9X_HEADERS_INFO, "No ROM file header found."); else if (HeaderCount == 1) S9xMessage(S9X_INFO, S9X_HEADERS_INFO, "Found ROM file header (and ignored it)."); else S9xMessage(S9X_INFO, S9X_HEADERS_INFO, "Found multiple ROM file headers (and ignored them)."); return ((uint32) totalSize); } bool8 CMemory::LoadROMMem (const uint8 *source, uint32 sourceSize) { if(!source || sourceSize > MAX_ROM_SIZE) return FALSE; ROMFilename = "MemoryROM"; do { memset(ROM,0, MAX_ROM_SIZE); memset(&Multi, 0,sizeof(Multi)); memcpy(ROM,source,sourceSize); } while(!LoadROMInt(sourceSize)); return TRUE; } bool8 CMemory::LoadROM (const char *filename) { if(!filename || !*filename) return FALSE; S9xResetSaveTimer(FALSE); // reset oops timer here so that .oops file has rom name of previous rom int32 totalFileSize; do { memset(ROM,0, MAX_ROM_SIZE); memset(&Multi, 0,sizeof(Multi)); totalFileSize = FileLoader(ROM, filename, MAX_ROM_SIZE); if (!totalFileSize) return (FALSE); CheckForAnyPatch(filename, HeaderCount != 0, totalFileSize); } while(!LoadROMInt(totalFileSize)); return TRUE; } bool8 CMemory::LoadROMInt (int32 ROMfillSize) { Settings.DisplayColor = BUILD_PIXEL(31, 31, 31); SET_UI_COLOR(255, 255, 255); CalculatedSize = 0; ExtendedFormat = NOPE; int hi_score, lo_score; int score_headered; int score_nonheadered; hi_score = ScoreHiROM(FALSE); lo_score = ScoreLoROM(FALSE); score_nonheadered = max(hi_score, lo_score); score_headered = max(ScoreHiROM(TRUE), ScoreLoROM(TRUE)); bool size_is_likely_headered = ((ROMfillSize - 512) & 0xFFFF) == 0; if (size_is_likely_headered) { score_headered += 2; } else { score_headered -= 2; } if (First512BytesCountZeroes() >= 0x1E0) { score_headered += 2; } else { score_headered -= 2; } bool headered_score_highest = score_headered > score_nonheadered; if (HeaderCount == 0 && !Settings.ForceNoHeader && headered_score_highest) { memmove(ROM, ROM + 512, ROMfillSize - 512); ROMfillSize -= 512; S9xMessage(S9X_INFO, S9X_HEADER_WARNING, "Try 'force no-header' option if the game doesn't work"); // modifying ROM, so we need to rescore hi_score = ScoreHiROM(FALSE); lo_score = ScoreLoROM(FALSE); } CalculatedSize = ((ROMfillSize + 0x1fff) / 0x2000) * 0x2000; if (CalculatedSize > 0x400000 && (ROM[0x7fd5] + (ROM[0x7fd6] << 8)) != 0x1320 && // exclude SuperFX (ROM[0x7fd5] + (ROM[0x7fd6] << 8)) != 0x1420 && (ROM[0x7fd5] + (ROM[0x7fd6] << 8)) != 0x1520 && (ROM[0x7fd5] + (ROM[0x7fd6] << 8)) != 0x1A20 && (ROM[0x7fd5] + (ROM[0x7fd6] << 8)) != 0x3423 && // exclude SA-1 (ROM[0x7fd5] + (ROM[0x7fd6] << 8)) != 0x3523 && (ROM[0x7fd5] + (ROM[0x7fd6] << 8)) != 0x4332 && // exclude S-DD1 (ROM[0x7fd5] + (ROM[0x7fd6] << 8)) != 0x4532 && (ROM[0xffd5] + (ROM[0xffd6] << 8)) != 0xF93a && // exclude SPC7110 (ROM[0xffd5] + (ROM[0xffd6] << 8)) != 0xF53a) ExtendedFormat = YEAH; // if both vectors are invalid, it's type 1 interleaved LoROM if (ExtendedFormat == NOPE && ((ROM[0x7ffc] + (ROM[0x7ffd] << 8)) < 0x8000) && ((ROM[0xfffc] + (ROM[0xfffd] << 8)) < 0x8000)) { if (!Settings.ForceInterleaved && !Settings.ForceNotInterleaved) S9xDeinterleaveType1(ROMfillSize, ROM); } // CalculatedSize is now set, so rescore hi_score = ScoreHiROM(FALSE); lo_score = ScoreLoROM(FALSE); uint8 *RomHeader = ROM; if (ExtendedFormat != NOPE) { int swappedhirom, swappedlorom; swappedhirom = ScoreHiROM(FALSE, 0x400000); swappedlorom = ScoreLoROM(FALSE, 0x400000); // set swapped here if (max(swappedlorom, swappedhirom) >= max(lo_score, hi_score)) { ExtendedFormat = BIGFIRST; hi_score = swappedhirom; lo_score = swappedlorom; RomHeader += 0x400000; } else ExtendedFormat = SMALLFIRST; } bool8 interleaved, tales = FALSE; interleaved = Settings.ForceInterleaved || Settings.ForceInterleaved2 || Settings.ForceInterleaveGD24; if (Settings.ForceLoROM || (!Settings.ForceHiROM && lo_score >= hi_score)) { LoROM = TRUE; HiROM = FALSE; // ignore map type byte if not 0x2x or 0x3x if ((RomHeader[0x7fd5] & 0xf0) == 0x20 || (RomHeader[0x7fd5] & 0xf0) == 0x30) { switch (RomHeader[0x7fd5] & 0xf) { case 1: interleaved = TRUE; break; case 5: interleaved = TRUE; tales = TRUE; break; } } } else { LoROM = FALSE; HiROM = TRUE; if ((RomHeader[0xffd5] & 0xf0) == 0x20 || (RomHeader[0xffd5] & 0xf0) == 0x30) { switch (RomHeader[0xffd5] & 0xf) { case 0: case 3: interleaved = TRUE; break; } } } // this two games fail to be detected if (!Settings.ForceHiROM && !Settings.ForceLoROM) { if (strncmp((char *) &ROM[0x7fc0], "YUYU NO QUIZ DE GO!GO!", 22) == 0 || (strncmp((char *) &ROM[0xffc0], "BATMAN--REVENGE JOKER", 21) == 0)) { LoROM = TRUE; HiROM = FALSE; interleaved = FALSE; tales = FALSE; } } if (!Settings.ForceNotInterleaved && interleaved) { S9xMessage(S9X_INFO, S9X_ROM_INTERLEAVED_INFO, "ROM image is in interleaved format - converting..."); if (tales) { if (ExtendedFormat == BIGFIRST) { S9xDeinterleaveType1(0x400000, ROM); S9xDeinterleaveType1(CalculatedSize - 0x400000, ROM + 0x400000); } else { S9xDeinterleaveType1(CalculatedSize - 0x400000, ROM); S9xDeinterleaveType1(0x400000, ROM + CalculatedSize - 0x400000); } LoROM = FALSE; HiROM = TRUE; } else if (Settings.ForceInterleaveGD24 && CalculatedSize == 0x300000) { bool8 t = LoROM; LoROM = HiROM; HiROM = t; S9xDeinterleaveGD24(CalculatedSize, ROM); } else if (Settings.ForceInterleaved2) S9xDeinterleaveType2(CalculatedSize, ROM); else { bool8 t = LoROM; LoROM = HiROM; HiROM = t; S9xDeinterleaveType1(CalculatedSize, ROM); } hi_score = ScoreHiROM(FALSE); lo_score = ScoreLoROM(FALSE); if ((HiROM && (lo_score >= hi_score || hi_score < 0)) || (LoROM && (hi_score > lo_score || lo_score < 0))) { S9xMessage(S9X_INFO, S9X_ROM_CONFUSING_FORMAT_INFO, "ROM lied about its type! Trying again."); Settings.ForceNotInterleaved = TRUE; Settings.ForceInterleaved = FALSE; return (FALSE); } } if (ExtendedFormat == SMALLFIRST) tales = TRUE; if (tales) { uint8 *tmp = (uint8 *) malloc(CalculatedSize - 0x400000); if (tmp) { S9xMessage(S9X_INFO, S9X_ROM_INTERLEAVED_INFO, "Fixing swapped ExHiROM..."); memmove(tmp, ROM, CalculatedSize - 0x400000); memmove(ROM, ROM + CalculatedSize - 0x400000, 0x400000); memmove(ROM + 0x400000, tmp, CalculatedSize - 0x400000); free(tmp); } } memset(&SNESGameFixes, 0, sizeof(SNESGameFixes)); SNESGameFixes.SRAMInitialValue = 0x60; InitROM(); S9xReset(); S9xDeleteCheats(); S9xLoadCheatFile(S9xGetFilename(".cht", CHEAT_DIR).c_str()); return (TRUE); } bool8 CMemory::LoadMultiCartMem (const uint8 *sourceA, uint32 sourceASize, const uint8 *sourceB, uint32 sourceBSize, const uint8 *bios, uint32 biosSize) { uint32 offset = 0; memset(ROM, 0, MAX_ROM_SIZE); memset(&Multi, 0, sizeof(Multi)); if(bios) { if(!is_SufamiTurbo_BIOS(bios,biosSize)) return FALSE; memcpy(ROM,bios,biosSize); offset+=biosSize; } if(sourceA) { memcpy(ROM + offset,sourceA,sourceASize); Multi.cartOffsetA = offset; Multi.cartSizeA = sourceASize; offset += sourceASize; strcpy(Multi.fileNameA,"MemCartA"); } if(sourceB) { memcpy(ROM + offset,sourceB,sourceBSize); Multi.cartOffsetB = offset; Multi.cartSizeB = sourceBSize; offset += sourceBSize; strcpy(Multi.fileNameB,"MemCartB"); } return LoadMultiCartInt(); } bool8 CMemory::LoadMultiCart (const char *cartA, const char *cartB) { S9xResetSaveTimer(FALSE); // reset oops timer here so that .oops file has rom name of previous rom memset(ROM, 0, MAX_ROM_SIZE); memset(&Multi, 0, sizeof(Multi)); Settings.DisplayColor = BUILD_PIXEL(31, 31, 31); SET_UI_COLOR(255, 255, 255); if (cartB && cartB[0]) Multi.cartSizeB = FileLoader(ROM, cartB, MAX_ROM_SIZE); if (Multi.cartSizeB) { strcpy(Multi.fileNameB, cartB); CheckForAnyPatch(cartB, HeaderCount != 0, Multi.cartSizeB); Multi.cartOffsetB = 0x400000; memcpy(ROM + Multi.cartOffsetB,ROM,Multi.cartSizeB); } if (cartA && cartA[0]) Multi.cartSizeA = FileLoader(ROM, cartA, MAX_ROM_SIZE); if (Multi.cartSizeA) { strcpy(Multi.fileNameA, cartA); CheckForAnyPatch(cartA, HeaderCount != 0, Multi.cartSizeA); } return LoadMultiCartInt(); } bool8 CMemory::LoadMultiCartInt () { bool8 r = TRUE; CalculatedSize = 0; ExtendedFormat = NOPE; if (Multi.cartSizeA) { if (is_SufamiTurbo_Cart(ROM + Multi.cartOffsetA, Multi.cartSizeA)) Multi.cartType = 4; else if (is_BSCartSA1_BIOS(ROM + Multi.cartOffsetA, Multi.cartSizeA)) Multi.cartType = 5; else if (is_BSCart_BIOS(ROM + Multi.cartOffsetA, Multi.cartSizeA)) Multi.cartType = 3; } else if (Multi.cartSizeB) { if (is_SufamiTurbo_Cart(ROM + Multi.cartOffsetB, Multi.cartSizeB)) Multi.cartType = 4; } else Multi.cartType = 4; // assuming BIOS only if(Multi.cartType == 4 && Multi.cartOffsetA == 0) { // try to load bios from file Multi.cartOffsetA = 0x40000; if(Multi.cartSizeA) memmove(ROM + Multi.cartOffsetA, ROM, Multi.cartSizeA + Multi.cartSizeB); else if(Multi.cartOffsetB) // clear cart A so the bios can detect that it's not present memset(ROM, 0, Multi.cartOffsetB); FILE *fp; size_t size; std::string path = S9xGetDirectory(BIOS_DIR) + SLASH_STR + "STBIOS.bin"; fp = fopen(path.c_str(), "rb"); if (fp) { size = fread((void *) ROM, 1, 0x40000, fp); fclose(fp); if (!is_SufamiTurbo_BIOS(ROM, size)) return (FALSE); } else return (FALSE); ROMFilename = path; } switch (Multi.cartType) { case 4: r = LoadSufamiTurbo(); break; case 3: case 5: r = LoadBSCart(); break; default: r = FALSE; } if (!r) { memset(&Multi, 0, sizeof(Multi)); return (FALSE); } if (Multi.cartSizeA) ROMFilename = Multi.fileNameA; else if (Multi.cartSizeB) ROMFilename = Multi.fileNameB; memset(&SNESGameFixes, 0, sizeof(SNESGameFixes)); SNESGameFixes.SRAMInitialValue = 0x60; InitROM(); S9xReset(); S9xDeleteCheats(); S9xLoadCheatFile(S9xGetFilename(".cht", CHEAT_DIR).c_str()); return (TRUE); } bool8 CMemory::LoadSufamiTurbo () { Multi.sramA = SRAM; Multi.sramB = SRAM + 0x10000; if (Multi.cartSizeA) { Multi.sramSizeA = 4; // ROM[0x37]? Multi.sramMaskA = Multi.sramSizeA ? ((1 << (Multi.sramSizeA + 3)) * 128 - 1) : 0; } if (Multi.cartSizeB) { if (!is_SufamiTurbo_Cart(ROM + Multi.cartOffsetB, Multi.cartSizeB)) Multi.cartSizeB = 0; } if (Multi.cartSizeB) { Multi.sramSizeB = 4; // ROM[0x37]? Multi.sramMaskB = Multi.sramSizeB ? ((1 << (Multi.sramSizeB + 3)) * 128 - 1) : 0; } LoROM = TRUE; HiROM = FALSE; CalculatedSize = 0x40000; return (TRUE); } bool8 CMemory::LoadBSCart () { Multi.sramA = SRAM; Multi.sramB = NULL; if (LoROM) Multi.sramSizeA = ROM[0x7fd8]; else Multi.sramSizeA = ROM[0xffd8]; Multi.sramMaskA = Multi.sramSizeA ? ((1 << (Multi.sramSizeA + 3)) * 128 - 1) : 0; Multi.sramSizeB = 0; Multi.sramMaskB = 0; CalculatedSize = Multi.cartSizeA; if (Multi.cartSizeB == 0 && Multi.cartSizeA <= (int32)(MAX_ROM_SIZE - 0x100000 - Multi.cartOffsetA)) { //Initialize 1MB Empty Memory Pack only if cart B is cleared //It does not make a Memory Pack if game is loaded like a normal ROM Multi.cartOffsetB = Multi.cartOffsetA + CalculatedSize; Multi.cartSizeB = 0x100000; memset(Memory.ROM + Multi.cartOffsetB, 0xFF, 0x100000); } return (TRUE); } bool8 CMemory::LoadGNEXT () { Multi.sramA = SRAM; Multi.sramB = NULL; Multi.sramSizeA = ROM[0x7fd8]; Multi.sramMaskA = Multi.sramSizeA ? ((1 << (Multi.sramSizeA + 3)) * 128 - 1) : 0; Multi.sramSizeB = 0; Multi.sramMaskB = 0; if (Multi.cartSizeB) { if (!is_GNEXT_Add_On(ROM + Multi.cartOffsetB, Multi.cartSizeB)) Multi.cartSizeB = 0; } LoROM = TRUE; HiROM = FALSE; CalculatedSize = Multi.cartSizeA; return (TRUE); } bool8 CMemory::LoadSRTC (void) { FILE *fp; fp = fopen(S9xGetFilename(".rtc", SRAM_DIR).c_str(), "rb"); if (!fp) return (FALSE); if (fread(RTCData.reg, 1, 20, fp) < 20) memset (RTCData.reg, 0, 20); fclose(fp); return (TRUE); } bool8 CMemory::SaveSRTC (void) { FILE *fp; fp = fopen(S9xGetFilename(".rtc", SRAM_DIR).c_str(), "wb"); if (!fp) return (FALSE); if (fwrite(RTCData.reg, 1, 20, fp) < 20) { printf ("Failed to save clock data.\n"); } fclose(fp); return (TRUE); } void CMemory::ClearSRAM (bool8 onlyNonSavedSRAM) { if (onlyNonSavedSRAM) if (!(Settings.SuperFX && ROMType < 0x15) && !(Settings.SA1 && ROMType == 0x34)) // can have SRAM return; // TODO: If SRAM size changes change this value as well memset(SRAM, SNESGameFixes.SRAMInitialValue, 0x80000); } bool8 CMemory::LoadSRAM (const char *filename) { FILE *file; int size, len; ClearSRAM(); if (Multi.cartType && Multi.sramSizeB) { size = (1 << (Multi.sramSizeB + 3)) * 128; file = fopen(S9xGetFilename(Multi.fileNameB, ".srm", SRAM_DIR).c_str(), "rb"); if (file) { len = fread((char *) Multi.sramB, 1, 0x10000, file); fclose(file); if (len - size == 512) memmove(Multi.sramB, Multi.sramB + 512, size); } } size = SRAMSize ? (1 << (SRAMSize + 3)) * 128 : 0; if (LoROM) size = size < 0x70000 ? size : 0x70000; else if (HiROM) size = size < 0x40000 ? size : 0x40000; if (size) { file = fopen(filename, "rb"); if (file) { len = fread((char *) SRAM, 1, size, file); fclose(file); if (len - size == 512) memmove(SRAM, SRAM + 512, size); if (Settings.SRTC || Settings.SPC7110RTC) LoadSRTC(); return (TRUE); } else if (Settings.BS && !Settings.BSXItself) { // The BS game's SRAM was not found // Try to read BS-X.srm instead std::string path = S9xGetDirectory(SRAM_DIR) + SLASH_STR + "BS-X.srm"; file = fopen(path.c_str(), "rb"); if (file) { len = fread((char *) SRAM, 1, size, file); fclose(file); if (len - size == 512) memmove(SRAM, SRAM + 512, size); S9xMessage(S9X_INFO, S9X_ROM_INFO, "The SRAM file wasn't found: BS-X.srm was read instead."); return (TRUE); } else { S9xMessage(S9X_INFO, S9X_ROM_INFO, "The SRAM file wasn't found, BS-X.srm wasn't found either."); return (FALSE); } } return (FALSE); } return (TRUE); } bool8 CMemory::SaveSRAM (const char *filename) { if (Settings.SuperFX && ROMType < 0x15) // doesn't have SRAM return (TRUE); if (Settings.SA1 && ROMType == 0x34) // doesn't have SRAM return (TRUE); FILE *file; int size; if (Multi.cartType && Multi.sramSizeB) { std::string name = S9xGetFilename(Multi.fileNameB, ".srm", SRAM_DIR); size = (1 << (Multi.sramSizeB + 3)) * 128; file = fopen(name.c_str(), "wb"); if (file) { if (!fwrite((char *) Multi.sramB, size, 1, file)) printf ("Couldn't write to subcart SRAM file.\n"); fclose(file); } } size = SRAMSize ? (1 << (SRAMSize + 3)) * 128 : 0; if (LoROM) size = size < 0x70000 ? size : 0x70000; else if (HiROM) size = size < 0x40000 ? size : 0x40000; if (size) { file = fopen(filename, "wb"); if (file) { if (!fwrite((char *) SRAM, size, 1, file)) printf ("Couldn't write to SRAM file.\n"); fclose(file); if (Settings.SRTC || Settings.SPC7110RTC) SaveSRTC(); return (TRUE); } } return (FALSE); } bool8 CMemory::SaveMPAK (const char *filename) { if (Settings.BS || (Multi.cartSizeB && (Multi.cartType == 3))) { FILE *file; int size; size = 0x100000; if (size) { file = fopen(filename, "wb"); if (file) { size_t written; written = fwrite((char *)Memory.ROM + Multi.cartOffsetB, size, 1, file); fclose(file); return (written > 0); } } } return (FALSE); } // initialization static uint32 caCRC32 (uint8 *array, uint32 size, uint32 crc32) { for (uint32 i = 0; i < size; i++) crc32 = ((crc32 >> 8) & 0x00FFFFFF) ^ crc32Table[(crc32 ^ array[i]) & 0xFF]; return (~crc32); } void CMemory::ParseSNESHeader (uint8 *RomHeader) { bool8 bs = Settings.BS & !Settings.BSXItself; strncpy(ROMName, (char *) &RomHeader[0x10], ROM_NAME_LEN - 1); if (bs) memset(ROMName + 16, 0x20, ROM_NAME_LEN - 17); if (bs) { if (!(((RomHeader[0x29] & 0x20) && CalculatedSize < 0x100000) || (!(RomHeader[0x29] & 0x20) && CalculatedSize == 0x100000))) printf("BS: Size mismatch\n"); // FIXME int p = 0; while ((1 << p) < (int) CalculatedSize) p++; ROMSize = p - 10; } else ROMSize = RomHeader[0x27]; SRAMSize = bs ? 5 /* BS-X */ : RomHeader[0x28]; ROMSpeed = bs ? RomHeader[0x28] : RomHeader[0x25]; ROMType = bs ? 0xE5 /* BS-X */ : RomHeader[0x26]; ROMRegion = bs ? 0 : RomHeader[0x29]; ROMChecksum = RomHeader[0x2E] + (RomHeader[0x2F] << 8); ROMComplementChecksum = RomHeader[0x2C] + (RomHeader[0x2D] << 8); memmove(ROMId, &RomHeader[0x02], 4); if (RomHeader[0x2A] != 0x33) CompanyId = ((RomHeader[0x2A] >> 4) & 0x0F) * 36 + (RomHeader[0x2A] & 0x0F); else if (isalnum(RomHeader[0x00]) && isalnum(RomHeader[0x01])) { int l, r, l2, r2; l = toupper(RomHeader[0x00]); r = toupper(RomHeader[0x01]); l2 = (l > '9') ? l - '7' : l - '0'; r2 = (r > '9') ? r - '7' : r - '0'; CompanyId = l2 * 36 + r2; } } void CMemory::InitROM (void) { Settings.SuperFX = FALSE; Settings.DSP = 0; Settings.SA1 = FALSE; Settings.C4 = FALSE; Settings.SDD1 = FALSE; Settings.SPC7110 = FALSE; Settings.SPC7110RTC = FALSE; Settings.OBC1 = FALSE; Settings.SETA = 0; Settings.SRTC = FALSE; Settings.BS = FALSE; Settings.MSU1 = FALSE; SuperFX.nRomBanks = CalculatedSize >> 15; //// Parse ROM header and read ROM informatoin CompanyId = -1; memset(ROMId, 0, 5); uint8 *RomHeader = ROM + 0x7FB0; if (ExtendedFormat == BIGFIRST) RomHeader += 0x400000; if (HiROM) RomHeader += 0x8000; S9xInitBSX(); // Set BS header before parsing ParseSNESHeader(RomHeader); //// Detect and initialize chips //// detection codes are compatible with NSRT // DSP1/2/3/4 if (ROMType == 0x03) { if (ROMSpeed == 0x30) Settings.DSP = 4; // DSP4 else Settings.DSP = 1; // DSP1 } else if (ROMType == 0x05) { if (ROMSpeed == 0x20) Settings.DSP = 2; // DSP2 else if (ROMSpeed == 0x30 && RomHeader[0x2a] == 0xb2) Settings.DSP = 3; // DSP3 else Settings.DSP = 1; // DSP1 } switch (Settings.DSP) { case 1: // DSP1 if (HiROM) { DSP0.boundary = 0x7000; DSP0.maptype = M_DSP1_HIROM; } else if (CalculatedSize > 0x100000) { DSP0.boundary = 0x4000; DSP0.maptype = M_DSP1_LOROM_L; } else { DSP0.boundary = 0xc000; DSP0.maptype = M_DSP1_LOROM_S; } SetDSP = &DSP1SetByte; GetDSP = &DSP1GetByte; break; case 2: // DSP2 DSP0.boundary = 0x10000; DSP0.maptype = M_DSP2_LOROM; SetDSP = &DSP2SetByte; GetDSP = &DSP2GetByte; break; case 3: // DSP3 DSP0.boundary = 0xc000; DSP0.maptype = M_DSP3_LOROM; SetDSP = &DSP3SetByte; GetDSP = &DSP3GetByte; break; case 4: // DSP4 DSP0.boundary = 0xc000; DSP0.maptype = M_DSP4_LOROM; SetDSP = &DSP4SetByte; GetDSP = &DSP4GetByte; break; default: SetDSP = NULL; GetDSP = NULL; break; } uint32 identifier = ((ROMType & 0xff) << 8) + (ROMSpeed & 0xff); switch (identifier) { // SRTC case 0x5535: Settings.SRTC = TRUE; S9xInitSRTC(); break; // SPC7110 case 0xF93A: Settings.SPC7110RTC = TRUE; // Fall through case 0xF53A: Settings.SPC7110 = TRUE; S9xInitSPC7110(); break; // OBC1 case 0x2530: Settings.OBC1 = TRUE; break; // SA1 case 0x3423: case 0x3523: Settings.SA1 = TRUE; break; // SuperFX case 0x1320: case 0x1420: case 0x1520: case 0x1A20: // SuperFX FastROM for ROM hacks case 0x1330: case 0x1430: case 0x1530: case 0x1A30: Settings.SuperFX = TRUE; S9xInitSuperFX(); if (ROM[0x7FDA] == 0x33) SRAMSize = ROM[0x7FBD]; else SRAMSize = 5; break; // SDD1 case 0x4332: case 0x4532: Settings.SDD1 = TRUE; break; // ST018 case 0xF530: Settings.SETA = ST_018; SetSETA = NULL; GetSETA = NULL; SRAMSize = 2; SNESGameFixes.SRAMInitialValue = 0x00; break; // ST010/011 case 0xF630: if (ROM[0x7FD7] == 0x09) { Settings.SETA = ST_011; SetSETA = &S9xSetST011; GetSETA = &S9xGetST011; } else { Settings.SETA = ST_010; SetSETA = &S9xSetST010; GetSETA = &S9xGetST010; } SRAMSize = 2; SNESGameFixes.SRAMInitialValue = 0x00; break; // C4 case 0xF320: Settings.C4 = TRUE; break; } // MSU1 Settings.MSU1 = S9xMSU1ROMExists(); //// Map memory and calculate checksum Map_Initialize(); CalculatedChecksum = 0; if (HiROM) { if (Settings.BS) /* Do nothing */; else if (Settings.SPC7110) Map_SPC7110HiROMMap(); else if (ExtendedFormat != NOPE) Map_ExtendedHiROMMap(); else if (Multi.cartType == 3) Map_BSCartHiROMMap(); else Map_HiROMMap(); } else { if (Settings.BS) /* Do nothing */; else if (Settings.SETA && Settings.SETA != ST_018) Map_SetaDSPLoROMMap(); else if (Settings.SuperFX) Map_SuperFXLoROMMap(); else if (Settings.SA1) { if (Multi.cartType == 5) Map_BSSA1LoROMMap(); else Map_SA1LoROMMap(); } else if (Settings.SDD1) Map_SDD1LoROMMap(); else if (ExtendedFormat != NOPE) Map_JumboLoROMMap(); else if (strncmp(ROMName, "WANDERERS FROM YS", 17) == 0) Map_NoMAD1LoROMMap(); else if (Multi.cartType == 3) if (strncmp(ROMName, "SOUND NOVEL-TCOOL", 17) == 0 || strncmp(ROMName, "DERBY STALLION 96", 17) == 0) Map_BSCartLoROMMap(1); else Map_BSCartLoROMMap(0); else if (strncmp(ROMName, "SOUND NOVEL-TCOOL", 17) == 0 || strncmp(ROMName, "DERBY STALLION 96", 17) == 0) Map_ROM24MBSLoROMMap(); else if (strncmp(ROMName, "THOROUGHBRED BREEDER3", 21) == 0 || strncmp(ROMName, "RPG-TCOOL 2", 11) == 0) Map_SRAM512KLoROMMap(); else if (strncmp(ROMName, "ADD-ON BASE CASSETE", 19) == 0) { if (Multi.cartType == 4) { SRAMSize = Multi.sramSizeA; Map_SufamiTurboLoROMMap(); } else { SRAMSize = 5; Map_SufamiTurboPseudoLoROMMap(); } } else Map_LoROMMap(); } Checksum_Calculate(); bool8 isChecksumOK = (ROMChecksum + ROMComplementChecksum == 0xffff) & (ROMChecksum == CalculatedChecksum); //// Build more ROM information // CRC32 if (!Settings.BS || Settings.BSXItself) // Not BS Dump { ROMCRC32 = caCRC32(ROM, CalculatedSize); sha256sum(ROM, CalculatedSize, ROMSHA256); } else // Convert to correct format before scan { int offset = HiROM ? 0xffc0 : 0x7fc0; // Backup uint8 BSMagic0 = ROM[offset + 22], BSMagic1 = ROM[offset + 23]; // uCONSRT standard ROM[offset + 22] = 0x42; ROM[offset + 23] = 0x00; // Calc ROMCRC32 = caCRC32(ROM, CalculatedSize); sha256sum(ROM, CalculatedSize, ROMSHA256); // Convert back ROM[offset + 22] = BSMagic0; ROM[offset + 23] = BSMagic1; } // NTSC/PAL if (Settings.ForceNTSC) Settings.PAL = FALSE; else if (Settings.ForcePAL) Settings.PAL = TRUE; else if (!Settings.BS && (((ROMRegion >= 2) && (ROMRegion <= 12)) || ROMRegion == 18)) // 18 is used by "Tintin in Tibet (Europe) (En,Es,Sv)" Settings.PAL = TRUE; else Settings.PAL = FALSE; if (Settings.PAL) { Settings.FrameTime = Settings.FrameTimePAL; ROMFramesPerSecond = 50; } else { Settings.FrameTime = Settings.FrameTimeNTSC; ROMFramesPerSecond = 60; } // truncate cart name ROMName[ROM_NAME_LEN - 1] = 0; if (strlen(ROMName)) { char *p = ROMName + strlen(ROMName); if (p > ROMName + 21 && ROMName[20] == ' ') p = ROMName + 21; while (p > ROMName && *(p - 1) == ' ') p--; *p = 0; } // SRAM size SRAMMask = SRAMSize ? ((1 << (SRAMSize + 3)) * 128) - 1 : 0; // checksum if (!isChecksumOK || ((uint32) CalculatedSize > (uint32) (((1 << (ROMSize - 7)) * 128) * 1024))) { Settings.DisplayColor = BUILD_PIXEL(31, 31, 0); SET_UI_COLOR(255, 255, 0); } // Use slight blue tint to indicate ROM was patched. if (Settings.IsPatched) { Settings.DisplayColor = BUILD_PIXEL(26, 26, 31); SET_UI_COLOR(216, 216, 255); } if (Multi.cartType == 4) { Settings.DisplayColor = BUILD_PIXEL(0, 16, 31); SET_UI_COLOR(0, 128, 255); } //// Initialize emulation Timings.H_Max_Master = SNES_CYCLES_PER_SCANLINE; Timings.H_Max = Timings.H_Max_Master; Timings.HBlankStart = SNES_HBLANK_START_HC; Timings.HBlankEnd = SNES_HBLANK_END_HC; Timings.HDMAInit = SNES_HDMA_INIT_HC; Timings.HDMAStart = SNES_HDMA_START_HC; Timings.RenderPos = SNES_RENDER_START_HC; Timings.V_Max_Master = Settings.PAL ? SNES_MAX_PAL_VCOUNTER : SNES_MAX_NTSC_VCOUNTER; Timings.V_Max = Timings.V_Max_Master; /* From byuu: The total delay time for both the initial (H)DMA sync (to the DMA clock), and the end (H)DMA sync (back to the last CPU cycle's mcycle rate (6, 8, or 12)) always takes between 12-24 mcycles. Possible delays: { 12, 14, 16, 18, 20, 22, 24 } XXX: Snes9x can't emulate this timing :( so let's use the average value... */ Timings.DMACPUSync = 18; /* If the CPU is halted (i.e. for DMA) while /NMI goes low, the NMI will trigger after the DMA completes (even if /NMI goes high again before the DMA completes). In this case, there is a 24-30 cycle delay between the end of DMA and the NMI handler, time enough for an instruction or two. */ // Wild Guns, Mighty Morphin Power Rangers - The Fighting Edition Timings.NMIDMADelay = 24; Timings.IRQTriggerCycles = 14; Timings.APUSpeedup = 0; S9xAPUTimingSetSpeedup(Timings.APUSpeedup); IPPU.TotalEmulatedFrames = 0; //// Hack games ApplyROMFixes(); //// Show ROM information ROMId[4] = 0; strcpy(ROMId, SafeString(ROMId).c_str()); sprintf(String, "\"%s\" [%s] %s, %s, %s, %s, SRAM:%s, ID:%s, CRC32:%08X", SafeString(ROMName).c_str(), isChecksumOK ? "checksum ok" : ((Multi.cartType == 4) ? "no checksum" : "bad checksum"), MapType(), Size(), KartContents(), Settings.PAL ? "PAL" : "NTSC", StaticRAMSize(), ROMId, ROMCRC32); S9xMessage(S9X_INFO, S9X_ROM_INFO, String); Settings.ForceLoROM = FALSE; Settings.ForceHiROM = FALSE; Settings.ForceHeader = FALSE; Settings.ForceNoHeader = FALSE; Settings.ForceInterleaved = FALSE; Settings.ForceInterleaved2 = FALSE; Settings.ForceInterleaveGD24 = FALSE; Settings.ForceNotInterleaved = FALSE; Settings.ForcePAL = FALSE; Settings.ForceNTSC = FALSE; Settings.TakeScreenshot = FALSE; if (stopMovie) S9xMovieStop(TRUE); if (PostRomInitFunc) PostRomInitFunc(); S9xVerifyControllers(); } // memory map uint32 CMemory::map_mirror (uint32 size, uint32 pos) { // from bsnes if (size == 0) return (0); if (pos < size) return (pos); uint32 mask = 1 << 31; while (!(pos & mask)) mask >>= 1; if (size <= (pos & mask)) return (map_mirror(size, pos - mask)); else return (mask + map_mirror(size - mask, pos - mask)); } void CMemory::map_lorom (uint32 bank_s, uint32 bank_e, uint32 addr_s, uint32 addr_e, uint32 size) { uint32 c, i, p, addr; for (c = bank_s; c <= bank_e; c++) { for (i = addr_s; i <= addr_e; i += 0x1000) { p = (c << 4) | (i >> 12); addr = (c & 0x7f) * 0x8000; Map[p] = ROM + map_mirror(size, addr) - (i & 0x8000); BlockIsROM[p] = TRUE; BlockIsRAM[p] = FALSE; } } } void CMemory::map_hirom (uint32 bank_s, uint32 bank_e, uint32 addr_s, uint32 addr_e, uint32 size) { uint32 c, i, p, addr; for (c = bank_s; c <= bank_e; c++) { for (i = addr_s; i <= addr_e; i += 0x1000) { p = (c << 4) | (i >> 12); addr = c << 16; Map[p] = ROM + map_mirror(size, addr); BlockIsROM[p] = TRUE; BlockIsRAM[p] = FALSE; } } } void CMemory::map_lorom_offset (uint32 bank_s, uint32 bank_e, uint32 addr_s, uint32 addr_e, uint32 size, uint32 offset) { uint32 c, i, p, addr; for (c = bank_s; c <= bank_e; c++) { for (i = addr_s; i <= addr_e; i += 0x1000) { p = (c << 4) | (i >> 12); addr = ((c - bank_s) & 0x7f) * 0x8000; Map[p] = ROM + offset + map_mirror(size, addr) - (i & 0x8000); BlockIsROM[p] = TRUE; BlockIsRAM[p] = FALSE; } } } void CMemory::map_hirom_offset (uint32 bank_s, uint32 bank_e, uint32 addr_s, uint32 addr_e, uint32 size, uint32 offset) { uint32 c, i, p, addr; for (c = bank_s; c <= bank_e; c++) { for (i = addr_s; i <= addr_e; i += 0x1000) { p = (c << 4) | (i >> 12); addr = (c - bank_s) << 16; Map[p] = ROM + offset + map_mirror(size, addr); BlockIsROM[p] = TRUE; BlockIsRAM[p] = FALSE; } } } void CMemory::map_space (uint32 bank_s, uint32 bank_e, uint32 addr_s, uint32 addr_e, uint8 *data) { uint32 c, i, p; for (c = bank_s; c <= bank_e; c++) { for (i = addr_s; i <= addr_e; i += 0x1000) { p = (c << 4) | (i >> 12); Map[p] = data; BlockIsROM[p] = FALSE; BlockIsRAM[p] = TRUE; } } } void CMemory::map_index (uint32 bank_s, uint32 bank_e, uint32 addr_s, uint32 addr_e, int index, int type) { uint32 c, i, p; bool8 isROM, isRAM; isROM = ((type == MAP_TYPE_I_O) || (type == MAP_TYPE_RAM)) ? FALSE : TRUE; isRAM = ((type == MAP_TYPE_I_O) || (type == MAP_TYPE_ROM)) ? FALSE : TRUE; for (c = bank_s; c <= bank_e; c++) { for (i = addr_s; i <= addr_e; i += 0x1000) { p = (c << 4) | (i >> 12); Map[p] = (uint8 *) (pint) index; BlockIsROM[p] = isROM; BlockIsRAM[p] = isRAM; } } } void CMemory::map_System (void) { // will be overwritten map_space(0x00, 0x3f, 0x0000, 0x1fff, RAM); map_index(0x00, 0x3f, 0x2000, 0x3fff, MAP_PPU, MAP_TYPE_I_O); map_index(0x00, 0x3f, 0x4000, 0x5fff, MAP_CPU, MAP_TYPE_I_O); map_space(0x80, 0xbf, 0x0000, 0x1fff, RAM); map_index(0x80, 0xbf, 0x2000, 0x3fff, MAP_PPU, MAP_TYPE_I_O); map_index(0x80, 0xbf, 0x4000, 0x5fff, MAP_CPU, MAP_TYPE_I_O); } void CMemory::map_WRAM (void) { // will overwrite others map_space(0x7e, 0x7e, 0x0000, 0xffff, RAM); map_space(0x7f, 0x7f, 0x0000, 0xffff, RAM + 0x10000); } void CMemory::map_LoROMSRAM (void) { uint32 hi; if (ROMSize > 11 || SRAMSize > 5) hi = 0x7fff; else hi = 0xffff; map_index(0x70, 0x7d, 0x0000, hi, MAP_LOROM_SRAM, MAP_TYPE_RAM); if (SRAMSize > 0) map_index(0xf0, 0xff, 0x0000, hi, MAP_LOROM_SRAM, MAP_TYPE_RAM); } void CMemory::map_HiROMSRAM (void) { map_index(0x20, 0x3f, 0x6000, 0x7fff, MAP_HIROM_SRAM, MAP_TYPE_RAM); map_index(0xa0, 0xbf, 0x6000, 0x7fff, MAP_HIROM_SRAM, MAP_TYPE_RAM); } void CMemory::map_DSP (void) { switch (DSP0.maptype) { case M_DSP1_LOROM_S: map_index(0x20, 0x3f, 0x8000, 0xffff, MAP_DSP, MAP_TYPE_I_O); map_index(0xa0, 0xbf, 0x8000, 0xffff, MAP_DSP, MAP_TYPE_I_O); break; case M_DSP1_LOROM_L: map_index(0x60, 0x6f, 0x0000, 0x7fff, MAP_DSP, MAP_TYPE_I_O); map_index(0xe0, 0xef, 0x0000, 0x7fff, MAP_DSP, MAP_TYPE_I_O); break; case M_DSP1_HIROM: map_index(0x00, 0x1f, 0x6000, 0x7fff, MAP_DSP, MAP_TYPE_I_O); map_index(0x80, 0x9f, 0x6000, 0x7fff, MAP_DSP, MAP_TYPE_I_O); break; case M_DSP2_LOROM: map_index(0x20, 0x3f, 0x6000, 0x6fff, MAP_DSP, MAP_TYPE_I_O); map_index(0x20, 0x3f, 0x8000, 0xbfff, MAP_DSP, MAP_TYPE_I_O); map_index(0xa0, 0xbf, 0x6000, 0x6fff, MAP_DSP, MAP_TYPE_I_O); map_index(0xa0, 0xbf, 0x8000, 0xbfff, MAP_DSP, MAP_TYPE_I_O); break; case M_DSP3_LOROM: map_index(0x20, 0x3f, 0x8000, 0xffff, MAP_DSP, MAP_TYPE_I_O); map_index(0xa0, 0xbf, 0x8000, 0xffff, MAP_DSP, MAP_TYPE_I_O); break; case M_DSP4_LOROM: map_index(0x30, 0x3f, 0x8000, 0xffff, MAP_DSP, MAP_TYPE_I_O); map_index(0xb0, 0xbf, 0x8000, 0xffff, MAP_DSP, MAP_TYPE_I_O); break; } } void CMemory::map_C4 (void) { map_index(0x00, 0x3f, 0x6000, 0x7fff, MAP_C4, MAP_TYPE_I_O); map_index(0x80, 0xbf, 0x6000, 0x7fff, MAP_C4, MAP_TYPE_I_O); } void CMemory::map_OBC1 (void) { map_index(0x00, 0x3f, 0x6000, 0x7fff, MAP_OBC_RAM, MAP_TYPE_I_O); map_index(0x80, 0xbf, 0x6000, 0x7fff, MAP_OBC_RAM, MAP_TYPE_I_O); } void CMemory::map_SetaRISC (void) { map_index(0x00, 0x3f, 0x3000, 0x3fff, MAP_SETA_RISC, MAP_TYPE_I_O); map_index(0x80, 0xbf, 0x3000, 0x3fff, MAP_SETA_RISC, MAP_TYPE_I_O); } void CMemory::map_SetaDSP (void) { // where does the SETA chip access, anyway? // please confirm this? map_index(0x68, 0x6f, 0x0000, 0x7fff, MAP_SETA_DSP, MAP_TYPE_RAM); // and this! map_index(0x60, 0x67, 0x0000, 0x3fff, MAP_SETA_DSP, MAP_TYPE_I_O); // ST-0010: // map_index(0x68, 0x6f, 0x0000, 0x0fff, MAP_SETA_DSP, ?); } void CMemory::map_WriteProtectROM (void) { memmove((void *) WriteMap, (void *) Map, sizeof(Map)); for (int c = 0; c < 0x1000; c++) { if (BlockIsROM[c]) WriteMap[c] = (uint8 *) MAP_NONE; } } void CMemory::Map_Initialize (void) { for (int c = 0; c < 0x1000; c++) { Map[c] = (uint8 *) MAP_NONE; WriteMap[c] = (uint8 *) MAP_NONE; BlockIsROM[c] = FALSE; BlockIsRAM[c] = FALSE; } } void CMemory::Map_LoROMMap (void) { printf("Map_LoROMMap\n"); map_System(); map_lorom(0x00, 0x3f, 0x8000, 0xffff, CalculatedSize); map_lorom(0x40, 0x7f, 0x0000, 0xffff, CalculatedSize); map_lorom(0x80, 0xbf, 0x8000, 0xffff, CalculatedSize); map_lorom(0xc0, 0xff, 0x0000, 0xffff, CalculatedSize); if (Settings.DSP) map_DSP(); else if (Settings.C4) map_C4(); else if (Settings.OBC1) map_OBC1(); else if (Settings.SETA == ST_018) map_SetaRISC(); map_LoROMSRAM(); map_WRAM(); map_WriteProtectROM(); } void CMemory::Map_NoMAD1LoROMMap (void) { printf("Map_NoMAD1LoROMMap\n"); map_System(); map_lorom(0x00, 0x3f, 0x8000, 0xffff, CalculatedSize); map_lorom(0x40, 0x7f, 0x0000, 0xffff, CalculatedSize); map_lorom(0x80, 0xbf, 0x8000, 0xffff, CalculatedSize); map_lorom(0xc0, 0xff, 0x0000, 0xffff, CalculatedSize); map_index(0x70, 0x7f, 0x0000, 0xffff, MAP_LOROM_SRAM, MAP_TYPE_RAM); map_index(0xf0, 0xff, 0x0000, 0xffff, MAP_LOROM_SRAM, MAP_TYPE_RAM); map_WRAM(); map_WriteProtectROM(); } void CMemory::Map_JumboLoROMMap (void) { // XXX: Which game uses this? printf("Map_JumboLoROMMap\n"); map_System(); map_lorom_offset(0x00, 0x3f, 0x8000, 0xffff, CalculatedSize - 0x400000, 0x400000); map_lorom_offset(0x40, 0x7f, 0x0000, 0xffff, CalculatedSize - 0x600000, 0x600000); map_lorom_offset(0x80, 0xbf, 0x8000, 0xffff, 0x400000, 0); map_lorom_offset(0xc0, 0xff, 0x0000, 0xffff, 0x400000, 0x200000); map_LoROMSRAM(); map_WRAM(); map_WriteProtectROM(); } void CMemory::Map_ROM24MBSLoROMMap (void) { // PCB: BSC-1A5M-01, BSC-1A7M-10 printf("Map_ROM24MBSLoROMMap\n"); map_System(); map_lorom_offset(0x00, 0x1f, 0x8000, 0xffff, 0x100000, 0); map_lorom_offset(0x20, 0x3f, 0x8000, 0xffff, 0x100000, 0x100000); map_lorom_offset(0x80, 0x9f, 0x8000, 0xffff, 0x100000, 0x200000); map_lorom_offset(0xa0, 0xbf, 0x8000, 0xffff, 0x100000, 0x100000); map_LoROMSRAM(); map_WRAM(); map_WriteProtectROM(); } void CMemory::Map_SRAM512KLoROMMap (void) { printf("Map_SRAM512KLoROMMap\n"); map_System(); map_lorom(0x00, 0x3f, 0x8000, 0xffff, CalculatedSize); map_lorom(0x40, 0x7f, 0x0000, 0xffff, CalculatedSize); map_lorom(0x80, 0xbf, 0x8000, 0xffff, CalculatedSize); map_lorom(0xc0, 0xff, 0x0000, 0xffff, CalculatedSize); map_space(0x70, 0x70, 0x0000, 0xffff, SRAM); map_space(0x71, 0x71, 0x0000, 0xffff, SRAM + 0x8000); map_space(0x72, 0x72, 0x0000, 0xffff, SRAM + 0x10000); map_space(0x73, 0x73, 0x0000, 0xffff, SRAM + 0x18000); map_WRAM(); map_WriteProtectROM(); } void CMemory::Map_SufamiTurboLoROMMap (void) { printf("Map_SufamiTurboLoROMMap\n"); map_System(); map_lorom_offset(0x00, 0x1f, 0x8000, 0xffff, 0x40000, 0); map_lorom_offset(0x20, 0x3f, 0x8000, 0xffff, Multi.cartSizeA, Multi.cartOffsetA); map_lorom_offset(0x40, 0x5f, 0x8000, 0xffff, Multi.cartSizeB, Multi.cartOffsetB); map_lorom_offset(0x80, 0x9f, 0x8000, 0xffff, 0x40000, 0); map_lorom_offset(0xa0, 0xbf, 0x8000, 0xffff, Multi.cartSizeA, Multi.cartOffsetA); map_lorom_offset(0xc0, 0xdf, 0x8000, 0xffff, Multi.cartSizeB, Multi.cartOffsetB); if (Multi.sramSizeA) { map_index(0x60, 0x63, 0x8000, 0xffff, MAP_LOROM_SRAM, MAP_TYPE_RAM); map_index(0xe0, 0xe3, 0x8000, 0xffff, MAP_LOROM_SRAM, MAP_TYPE_RAM); } if (Multi.sramSizeB) { map_index(0x70, 0x73, 0x8000, 0xffff, MAP_LOROM_SRAM_B, MAP_TYPE_RAM); map_index(0xf0, 0xf3, 0x8000, 0xffff, MAP_LOROM_SRAM_B, MAP_TYPE_RAM); } map_WRAM(); map_WriteProtectROM(); } void CMemory::Map_SufamiTurboPseudoLoROMMap (void) { // for combined images printf("Map_SufamiTurboPseudoLoROMMap\n"); map_System(); map_lorom_offset(0x00, 0x1f, 0x8000, 0xffff, 0x40000, 0); map_lorom_offset(0x20, 0x3f, 0x8000, 0xffff, 0x100000, 0x100000); map_lorom_offset(0x40, 0x5f, 0x8000, 0xffff, 0x100000, 0x200000); map_lorom_offset(0x80, 0x9f, 0x8000, 0xffff, 0x40000, 0); map_lorom_offset(0xa0, 0xbf, 0x8000, 0xffff, 0x100000, 0x100000); map_lorom_offset(0xc0, 0xdf, 0x8000, 0xffff, 0x100000, 0x200000); // I don't care :P map_space(0x60, 0x63, 0x8000, 0xffff, SRAM - 0x8000); map_space(0xe0, 0xe3, 0x8000, 0xffff, SRAM - 0x8000); map_space(0x70, 0x73, 0x8000, 0xffff, SRAM + 0x4000 - 0x8000); map_space(0xf0, 0xf3, 0x8000, 0xffff, SRAM + 0x4000 - 0x8000); map_WRAM(); map_WriteProtectROM(); } void CMemory::Map_SuperFXLoROMMap (void) { printf("Map_SuperFXLoROMMap\n"); map_System(); // Replicate the first 2Mb of the ROM at ROM + 8MB such that each 32K // block is repeated twice in each 64K block. for (int c = 0; c < 64; c++) { memmove(&ROM[0x800000 + c * 0x10000], &ROM[c * 0x8000], 0x8000); memmove(&ROM[0x808000 + c * 0x10000], &ROM[c * 0x8000], 0x8000); } // Check GSU revision (not 100% accurate but it works) // GSU2 if (CalculatedSize > 0x400000) { map_lorom(0x00, 0x3f, 0x8000, 0xffff, 0x200000); map_lorom_offset(0x80, 0xbf, 0x8000, 0xffff, 0x200000, 0x200000); map_hirom_offset(0x40, 0x5f, 0x0000, 0xffff, 0x200000, 0); map_hirom_offset(0xc0, 0xff, 0x0000, 0xffff, CalculatedSize - 0x400000, 0x400000); map_space(0x00, 0x3f, 0x6000, 0x7fff, SRAM - 0x6000); map_space(0x80, 0xbf, 0x6000, 0x7fff, SRAM - 0x6000); map_space(0x70, 0x70, 0x0000, 0xffff, SRAM); map_space(0x71, 0x71, 0x0000, 0xffff, SRAM + 0x10000); } else if (CalculatedSize > 0x200000) { map_lorom(0x00, 0x3f, 0x8000, 0xffff, 0x200000); map_lorom_offset(0x80, 0xbf, 0x8000, 0xffff, CalculatedSize - 0x200000, 0x200000); map_hirom_offset(0x40, 0x5f, 0x0000, 0xffff, 0x200000, 0); map_hirom_offset(0xc0, 0xff, 0x0000, 0xffff, CalculatedSize - 0x200000, 0x200000); map_space(0x00, 0x3f, 0x6000, 0x7fff, SRAM - 0x6000); map_space(0x80, 0xbf, 0x6000, 0x7fff, SRAM - 0x6000); map_space(0x70, 0x70, 0x0000, 0xffff, SRAM); map_space(0x71, 0x71, 0x0000, 0xffff, SRAM + 0x10000); } // GSU1 else { map_lorom(0x00, 0x3f, 0x8000, 0xffff, CalculatedSize); map_lorom(0x80, 0xbf, 0x8000, 0xffff, CalculatedSize); map_hirom_offset(0x40, 0x5f, 0x0000, 0xffff, CalculatedSize, 0); map_hirom_offset(0xc0, 0xdf, 0x0000, 0xffff, CalculatedSize, 0); map_space(0x00, 0x3f, 0x6000, 0x7fff, SRAM - 0x6000); map_space(0x80, 0xbf, 0x6000, 0x7fff, SRAM - 0x6000); map_space(0x70, 0x70, 0x0000, 0xffff, SRAM); map_space(0x71, 0x71, 0x0000, 0xffff, SRAM + 0x10000); map_space(0xf0, 0xf0, 0x0000, 0xffff, SRAM); map_space(0xf1, 0xf1, 0x0000, 0xffff, SRAM + 0x10000); } map_WRAM(); map_WriteProtectROM(); } void CMemory::Map_SetaDSPLoROMMap (void) { printf("Map_SetaDSPLoROMMap\n"); map_System(); map_lorom(0x00, 0x3f, 0x8000, 0xffff, CalculatedSize); map_lorom(0x40, 0x7f, 0x8000, 0xffff, CalculatedSize); map_lorom(0x80, 0xbf, 0x8000, 0xffff, CalculatedSize); map_lorom(0xc0, 0xff, 0x8000, 0xffff, CalculatedSize); map_SetaDSP(); map_LoROMSRAM(); map_WRAM(); map_WriteProtectROM(); } void CMemory::Map_SDD1LoROMMap (void) { printf("Map_SDD1LoROMMap\n"); map_System(); map_lorom(0x00, 0x3f, 0x8000, 0xffff, CalculatedSize); map_lorom(0x80, 0xbf, 0x8000, 0xffff, CalculatedSize); map_hirom_offset(0x60, 0x7f, 0x0000, 0xffff, CalculatedSize, 0); map_hirom_offset(0xc0, 0xff, 0x0000, 0xffff, CalculatedSize, 0); // will be overwritten dynamically map_index(0x70, 0x7f, 0x0000, 0x7fff, MAP_LOROM_SRAM, MAP_TYPE_RAM); map_index(0xa0, 0xbf, 0x6000, 0x7fff, MAP_LOROM_SRAM, MAP_TYPE_RAM); map_WRAM(); map_WriteProtectROM(); } void CMemory::Map_SA1LoROMMap (void) { printf("Map_SA1LoROMMap\n"); map_System(); map_lorom(0x00, 0x3f, 0x8000, 0xffff, CalculatedSize); map_lorom(0x80, 0xbf, 0x8000, 0xffff, CalculatedSize); map_hirom_offset(0xc0, 0xff, 0x0000, 0xffff, CalculatedSize, 0); map_space(0x00, 0x3f, 0x3000, 0x37ff, FillRAM); map_space(0x80, 0xbf, 0x3000, 0x37ff, FillRAM); map_index(0x00, 0x3f, 0x6000, 0x7fff, MAP_BWRAM, MAP_TYPE_I_O); map_index(0x80, 0xbf, 0x6000, 0x7fff, MAP_BWRAM, MAP_TYPE_I_O); for (int c = 0x40; c < 0x4f; c++) map_space(c, c, 0x0000, 0xffff, SRAM + (c & 3) * 0x10000); map_WRAM(); map_WriteProtectROM(); // Now copy the map and correct it for the SA1 CPU. memmove((void *) SA1.Map, (void *) Map, sizeof(Map)); memmove((void *) SA1.WriteMap, (void *) WriteMap, sizeof(WriteMap)); // SA-1 Banks 00->3f and 80->bf for (int c = 0x000; c < 0x400; c += 0x10) { SA1.Map[c + 0] = SA1.Map[c + 0x800] = FillRAM + 0x3000; SA1.Map[c + 1] = SA1.Map[c + 0x801] = (uint8 *) MAP_NONE; SA1.WriteMap[c + 0] = SA1.WriteMap[c + 0x800] = FillRAM + 0x3000; SA1.WriteMap[c + 1] = SA1.WriteMap[c + 0x801] = (uint8 *) MAP_NONE; } // SA-1 Banks 40->4f for (int c = 0x400; c < 0x500; c++) SA1.Map[c] = SA1.WriteMap[c] = (uint8*)MAP_HIROM_SRAM; // SA-1 Banks 60->6f for (int c = 0x600; c < 0x700; c++) SA1.Map[c] = SA1.WriteMap[c] = (uint8 *) MAP_BWRAM_BITMAP; // WRAM is inaccessable for (int c = 0x7e0; c < 0x800; c++) SA1.Map[c] = SA1.WriteMap[c] = (uint8 *) MAP_NONE; BWRAM = SRAM; } void CMemory::Map_BSSA1LoROMMap(void) { printf("Map_BSSA1LoROMMap\n"); map_System(); map_lorom_offset(0x00, 0x3f, 0x8000, 0xffff, Multi.cartSizeA, Multi.cartOffsetA); map_lorom_offset(0x80, 0xbf, 0x8000, 0xffff, Multi.cartSizeA, Multi.cartOffsetA); map_hirom_offset(0xc0, 0xff, 0x0000, 0xffff, Multi.cartSizeA, Multi.cartOffsetA); map_space(0x00, 0x3f, 0x3000, 0x3fff, FillRAM); map_space(0x80, 0xbf, 0x3000, 0x3fff, FillRAM); map_index(0x00, 0x3f, 0x6000, 0x7fff, MAP_BWRAM, MAP_TYPE_I_O); map_index(0x80, 0xbf, 0x6000, 0x7fff, MAP_BWRAM, MAP_TYPE_I_O); for (int c = 0x40; c < 0x80; c++) map_space(c, c, 0x0000, 0xffff, SRAM + (c & 1) * 0x10000); map_WRAM(); map_WriteProtectROM(); // Now copy the map and correct it for the SA1 CPU. memmove((void *) SA1.Map, (void *) Map, sizeof(Map)); memmove((void *) SA1.WriteMap, (void *) WriteMap, sizeof(WriteMap)); // SA-1 Banks 00->3f and 80->bf for (int c = 0x000; c < 0x400; c += 0x10) { SA1.Map[c + 0] = SA1.Map[c + 0x800] = FillRAM + 0x3000; SA1.Map[c + 1] = SA1.Map[c + 0x801] = (uint8 *) MAP_NONE; SA1.WriteMap[c + 0] = SA1.WriteMap[c + 0x800] = FillRAM + 0x3000; SA1.WriteMap[c + 1] = SA1.WriteMap[c + 0x801] = (uint8 *) MAP_NONE; } // SA-1 Banks 60->6f for (int c = 0x600; c < 0x700; c++) SA1.Map[c] = SA1.WriteMap[c] = (uint8 *) MAP_BWRAM_BITMAP; // WRAM is inaccessable for (int c = 0x7e0; c < 0x800; c++) SA1.Map[c] = SA1.WriteMap[c] = (uint8 *) MAP_NONE; BWRAM = SRAM; } void CMemory::Map_HiROMMap (void) { printf("Map_HiROMMap\n"); map_System(); map_hirom(0x00, 0x3f, 0x8000, 0xffff, CalculatedSize); map_hirom(0x40, 0x7f, 0x0000, 0xffff, CalculatedSize); map_hirom(0x80, 0xbf, 0x8000, 0xffff, CalculatedSize); map_hirom(0xc0, 0xff, 0x0000, 0xffff, CalculatedSize); if (Settings.DSP) map_DSP(); map_HiROMSRAM(); map_WRAM(); map_WriteProtectROM(); } void CMemory::Map_ExtendedHiROMMap (void) { printf("Map_ExtendedHiROMMap\n"); map_System(); map_hirom_offset(0x00, 0x3f, 0x8000, 0xffff, CalculatedSize - 0x400000, 0x400000); map_hirom_offset(0x40, 0x7f, 0x0000, 0xffff, CalculatedSize - 0x400000, 0x400000); map_hirom_offset(0x80, 0xbf, 0x8000, 0xffff, 0x400000, 0); map_hirom_offset(0xc0, 0xff, 0x0000, 0xffff, 0x400000, 0); map_HiROMSRAM(); map_WRAM(); map_WriteProtectROM(); } void CMemory::Map_SPC7110HiROMMap (void) { printf("Map_SPC7110HiROMMap\n"); map_System(); map_index(0x00, 0x00, 0x6000, 0x7fff, MAP_HIROM_SRAM, MAP_TYPE_RAM); map_hirom(0x00, 0x0f, 0x8000, 0xffff, CalculatedSize); map_index(0x30, 0x30, 0x6000, 0x7fff, MAP_HIROM_SRAM, MAP_TYPE_RAM); if(Memory.ROMSize >= 13) map_hirom_offset(0x40, 0x4f, 0x0000, 0xffff, CalculatedSize, 0x600000); map_index(0x50, 0x50, 0x0000, 0xffff, MAP_SPC7110_DRAM, MAP_TYPE_ROM); map_hirom(0x80, 0x8f, 0x8000, 0xffff, CalculatedSize); map_hirom_offset(0xc0, 0xcf, 0x0000, 0xffff, CalculatedSize, 0); map_index(0xd0, 0xff, 0x0000, 0xffff, MAP_SPC7110_ROM, MAP_TYPE_ROM); map_WRAM(); map_WriteProtectROM(); } void CMemory::Map_BSCartLoROMMap(uint8 mapping) { printf("Map_BSCartLoROMMap\n"); BSX.MMC[0x02] = 0x00; BSX.MMC[0x0C] = 0x80; map_System(); if (mapping) { map_lorom_offset(0x00, 0x1f, 0x8000, 0xffff, 0x100000, 0); map_lorom_offset(0x20, 0x3f, 0x8000, 0xffff, 0x100000, 0x100000); map_lorom_offset(0x80, 0x9f, 0x8000, 0xffff, 0x100000, 0x200000); map_lorom_offset(0xa0, 0xbf, 0x8000, 0xffff, 0x100000, 0x100000); } else { map_lorom(0x00, 0x3f, 0x8000, 0xffff, CalculatedSize); map_lorom(0x40, 0x7f, 0x0000, 0x7fff, CalculatedSize); map_lorom(0x80, 0xbf, 0x8000, 0xffff, CalculatedSize); map_lorom(0xc0, 0xff, 0x0000, 0x7fff, CalculatedSize); } map_LoROMSRAM(); map_index(0xc0, 0xef, 0x0000, 0xffff, MAP_BSX, MAP_TYPE_RAM); map_WRAM(); map_WriteProtectROM(); } void CMemory::Map_BSCartHiROMMap(void) { printf("Map_BSCartHiROMMap\n"); BSX.MMC[0x02] = 0x80; BSX.MMC[0x0C] = 0x80; map_System(); map_hirom_offset(0x00, 0x1f, 0x8000, 0xffff, Multi.cartSizeA, Multi.cartOffsetA); map_hirom_offset(0x20, 0x3f, 0x8000, 0xffff, Multi.cartSizeB, Multi.cartOffsetB); map_hirom_offset(0x40, 0x5f, 0x0000, 0xffff, Multi.cartSizeA, Multi.cartOffsetA); map_hirom_offset(0x60, 0x7f, 0x0000, 0xffff, Multi.cartSizeB, Multi.cartOffsetB); map_hirom_offset(0x80, 0x9f, 0x8000, 0xffff, Multi.cartSizeA, Multi.cartOffsetA); map_hirom_offset(0xa0, 0xbf, 0x8000, 0xffff, Multi.cartSizeB, Multi.cartOffsetB); map_hirom_offset(0xc0, 0xdf, 0x0000, 0xffff, Multi.cartSizeA, Multi.cartOffsetA); if ((ROM[Multi.cartOffsetB + 0xFF00] == 0x4D) && (ROM[Multi.cartOffsetB + 0xFF02] == 0x50) && ((ROM[Multi.cartOffsetB + 0xFF06] & 0xF0) == 0x70)) { //Type 7 Memory Pack detection - if detected, emulate it as Mask ROM map_hirom_offset(0xe0, 0xff, 0x0000, 0xffff, Multi.cartSizeB, Multi.cartOffsetB); } else { map_index(0xe0, 0xff, 0x0000, 0xffff, MAP_BSX, MAP_TYPE_RAM); } map_HiROMSRAM(); map_WRAM(); map_WriteProtectROM(); } // checksum uint16 CMemory::checksum_calc_sum (uint8 *data, uint32 length) { uint16 sum = 0; for (uint32 i = 0; i < length; i++) sum += data[i]; return (sum); } uint16 CMemory::checksum_mirror_sum (uint8 *start, uint32 &length, uint32 mask) { // from NSRT while (!(length & mask) && mask) mask >>= 1; uint16 part1 = checksum_calc_sum(start, mask); uint16 part2 = 0; uint32 next_length = length - mask; if (next_length) { part2 = checksum_mirror_sum(start + mask, next_length, mask >> 1); while (next_length < mask) { next_length += next_length; part2 += part2; } length = mask + mask; } return (part1 + part2); } void CMemory::Checksum_Calculate (void) { // from NSRT uint16 sum = 0; if (Settings.BS && !Settings.BSXItself) sum = checksum_calc_sum(ROM, CalculatedSize) - checksum_calc_sum(ROM + (HiROM ? 0xffb0 : 0x7fb0), 48); else if (Settings.SPC7110) { sum = checksum_calc_sum(ROM, CalculatedSize); if (CalculatedSize == 0x300000) sum += sum; } else { if (CalculatedSize & 0x7fff) sum = checksum_calc_sum(ROM, CalculatedSize); else { uint32 length = CalculatedSize; sum = checksum_mirror_sum(ROM, length); } } CalculatedChecksum = sum; } // information const char * CMemory::MapType (void) { return (HiROM ? ((ExtendedFormat != NOPE) ? "ExHiROM": "HiROM") : "LoROM"); } const char * CMemory::StaticRAMSize (void) { static char str[20]; if (SRAMSize > 16) strcpy(str, "Corrupt"); else sprintf(str, "%dKbits", 8 * (SRAMMask + 1) / 1024); return (str); } const char * CMemory::Size (void) { static char str[20]; if (Multi.cartType == 4) strcpy(str, "N/A"); else if (ROMSize < 7 || ROMSize - 7 > 23) strcpy(str, "Corrupt"); else sprintf(str, "%dMbits", 1 << (ROMSize - 7)); return (str); } const char * CMemory::Revision (void) { static char str[20]; sprintf(str, "1.%d", HiROM ? ((ExtendedFormat != NOPE) ? ROM[0x40ffdb] : ROM[0xffdb]) : ROM[0x7fdb]); return (str); } const char * CMemory::KartContents (void) { static char str[64]; static const char *contents[3] = { "ROM", "ROM+RAM", "ROM+RAM+BAT" }; char chip[20]; if (ROMType == 0 && !Settings.BS) return ("ROM"); if (Settings.BS) strcpy(chip, "+BS"); else if (Settings.SuperFX) strcpy(chip, "+Super FX"); else if (Settings.SDD1) strcpy(chip, "+S-DD1"); else if (Settings.OBC1) strcpy(chip, "+OBC1"); else if (Settings.SA1) strcpy(chip, "+SA-1"); else if (Settings.SPC7110RTC) strcpy(chip, "+SPC7110+RTC"); else if (Settings.SPC7110) strcpy(chip, "+SPC7110"); else if (Settings.SRTC) strcpy(chip, "+S-RTC"); else if (Settings.C4) strcpy(chip, "+C4"); else if (Settings.SETA == ST_010) strcpy(chip, "+ST-010"); else if (Settings.SETA == ST_011) strcpy(chip, "+ST-011"); else if (Settings.SETA == ST_018) strcpy(chip, "+ST-018"); else if (Settings.DSP) sprintf(chip, "+DSP-%d", Settings.DSP); else strcpy(chip, ""); if (Settings.MSU1) sprintf(chip + strlen(chip), "+MSU-1"); sprintf(str, "%s%s", contents[(ROMType & 0xf) % 3], chip); return (str); } const char * CMemory::Country (void) { switch (ROMRegion) { case 0: return("Japan"); case 1: return("USA and Canada"); case 2: return("Oceania, Europe and Asia"); case 3: return("Sweden"); case 4: return("Finland"); case 5: return("Denmark"); case 6: return("France"); case 7: return("Holland"); case 8: return("Spain"); case 9: return("Germany, Austria and Switzerland"); case 10: return("Italy"); case 11: return("Hong Kong and China"); case 12: return("Indonesia"); case 13: return("South Korea"); default: return("Unknown"); } } const char * CMemory::PublishingCompany (void) { if (CompanyId >= (int) (sizeof(nintendo_licensees) / sizeof(nintendo_licensees[0])) || CompanyId < 0) return ("Unknown"); if (nintendo_licensees[CompanyId] == NULL) return ("Unknown"); return (nintendo_licensees[CompanyId]); } void CMemory::MakeRomInfoText (char *romtext) { char temp[256]; romtext[0] = 0; sprintf(temp, " Cart Name: %s", ROMName); strcat(romtext, temp); sprintf(temp, "\n Game Code: %s", ROMId); strcat(romtext, temp); sprintf(temp, "\n Contents: %s", KartContents()); strcat(romtext, temp); sprintf(temp, "\n Map: %s", MapType()); strcat(romtext, temp); sprintf(temp, "\n Speed: 0x%02X (%s)", ROMSpeed, (ROMSpeed & 0x10) ? "FastROM" : "SlowROM"); strcat(romtext, temp); sprintf(temp, "\n Type: 0x%02X", ROMType); strcat(romtext, temp); sprintf(temp, "\n Size (calculated): %dMbits", CalculatedSize / 0x20000); strcat(romtext, temp); sprintf(temp, "\n Size (header): %s", Size()); strcat(romtext, temp); sprintf(temp, "\n SRAM size: %s", StaticRAMSize()); strcat(romtext, temp); sprintf(temp, "\nChecksum (calculated): 0x%04X", CalculatedChecksum); strcat(romtext, temp); sprintf(temp, "\n Checksum (header): 0x%04X", ROMChecksum); strcat(romtext, temp); sprintf(temp, "\n Complement (header): 0x%04X", ROMComplementChecksum); strcat(romtext, temp); sprintf(temp, "\n Video Output: %s", (ROMRegion > 12 || ROMRegion < 2) ? "NTSC 60Hz" : "PAL 50Hz"); strcat(romtext, temp); sprintf(temp, "\n Revision: %s", Revision()); strcat(romtext, temp); sprintf(temp, "\n Licensee: %s", PublishingCompany()); strcat(romtext, temp); sprintf(temp, "\n Region: %s", Country()); strcat(romtext, temp); sprintf(temp, "\n CRC32: 0x%08X", ROMCRC32); strcat(romtext, temp); } // hack bool8 CMemory::match_na (const char *str) { return (strcmp(ROMName, str) == 0); } bool8 CMemory::match_nn (const char *str) { return (strncmp(ROMName, str, strlen(str)) == 0); } bool8 CMemory::match_nc (const char *str) { return (strncasecmp(ROMName, str, strlen(str)) == 0); } bool8 CMemory::match_id (const char *str) { return (strncmp(ROMId, str, strlen(str)) == 0); } void CMemory::ApplyROMFixes (void) { Settings.BlockInvalidVRAMAccess = Settings.BlockInvalidVRAMAccessMaster; if (Settings.DisableGameSpecificHacks) return; // APU timing hacks if (match_na("CIRCUIT USA")) Timings.APUSpeedup = 3; S9xAPUTimingSetSpeedup(Timings.APUSpeedup); // Other timing hacks // The delay to sync CPU and DMA which Snes9x does not emulate. // Some games need really severe delay timing... if (match_na("BATTLE GRANDPRIX")) // Battle Grandprix Timings.DMACPUSync = 20; else if (match_na("KORYU NO MIMI ENG")) // Koryu no Mimi translation by rpgone) { // An infinite loop reads $4210 and checks NMI flag. This only works if LDA instruction executes before the NMI triggers, // which doesn't work very well with s9x's default DMA timing. Timings.DMACPUSync = 20; } if (Timings.DMACPUSync != 18) printf("DMA sync: %d\n", Timings.DMACPUSync); // SRAM initial value if (match_na("HITOMI3")) { SRAMSize = 1; SRAMMask = ((1 << (SRAMSize + 3)) * 128) - 1; } // SRAM value fixes if (match_na("SUPER DRIFT OUT") || // Super Drift Out match_na("SATAN IS OUR FATHER!") || match_na("S.F.S.95 della SerieA") || match_id("AACJ") || // Nichibutsu Arcade Classics match_na("goemon 4")) // Ganbare Goemon Kirakira Douchuu SNESGameFixes.SRAMInitialValue = 0x00; // Additional game fixes by sanmaiwashi ... // XXX: unnecessary? if (match_na("SFX \xC5\xB2\xC4\xB6\xDE\xDD\xC0\xDE\xD1\xD3\xC9\xB6\xDE\xC0\xD8 1")) // SD Gundam Gaiden - Knight Gundam Monogatari SNESGameFixes.SRAMInitialValue = 0x6b; // others: BS and ST-01x games are 0x00. // OAM hacks :( // OAM hacks because we don't fully understand the behavior of the SNES. // Totally wacky display in 2P mode... // seems to need a disproven behavior, so we're definitely overlooking some other bug? if (match_nn("UNIRACERS")) // Uniracers { SNESGameFixes.Uniracers = TRUE; printf("Applied Uniracers hack.\n"); } // Render Position if (match_na("Sugoro Quest++")) Timings.RenderPos = 128; else if (match_na("FIREPOWER 2000") || match_na("SUPER SWIV")) Timings.RenderPos = 32; else if (match_na("DERBY STALLION 98")) Timings.RenderPos = 128; else if (match_na("AIR STRIKE PATROL") || match_na("DESERT FIGHTER")) Timings.RenderPos = 128; // Just hides shadow else if (match_na("FULL THROTTLE RACING")) Timings.RenderPos = 128; // From bsnes else if (match_na("NHL '94") || match_na("NHL PROHOCKEY'94")) Timings.RenderPos = 32; else if (match_na("ADVENTURES OF FRANKEN") && Settings.PAL) Timings.RenderPos = 32; } std::string CMemory::SafeString(std::string s, bool allow_jis /*=false*/) { std::string safe; for (size_t i = 0; i < s.length(); i++) { if (s[i] >= 32 && s[i] < 127) // ASCII safe += s[i]; else if (allow_jis && ROMRegion == 0 && ((uint8)s[i] >= 0xa0 && (uint8)s[i] < 0xe0)) // JIS X 201 - Katakana safe += s[i]; else safe += '_'; } return safe; } // BPS % UPS % IPS // number decoding used for both BPS and UPS static uint32 XPSdecode (const uint8 *data, unsigned &addr, unsigned size) { uint32 offset = 0, shift = 1; while(addr < size) { uint8 x = data[addr++]; offset += (x & 0x7f) * shift; if(x & 0x80) break; shift <<= 7; offset += shift; } return offset; } static std::vector ReadStreamUntilEOF(Stream *r) { const size_t max_buffer_size = 4096; std::vector data; uint8_t buffer[max_buffer_size]; size_t total_size = 0; size_t buffer_size = 0; int value = 0; while (value != EOF) { value = r->get_char(); if (value != EOF) buffer[buffer_size++] = value; if (buffer_size == max_buffer_size || (value == EOF && buffer_size > 0)) { data.resize(data.size() + buffer_size); memcpy(&data[total_size], buffer, buffer_size); total_size += buffer_size; buffer_size = 0; } } return data; } //NOTE: UPS patches are *never* created against a headered ROM! //this is per the UPS file specification. however, do note that it is //technically possible for a non-compliant patcher to ignore this requirement. //therefore, it is *imperative* that no emulator support such patches. //thusly, we ignore the "long offset" parameter below. failure to do so would //completely invalidate the purpose of UPS; which is to avoid header vs //no-header patching errors that result in IPS patches having a 50/50 chance of //being applied correctly. static bool8 ReadUPSPatch (Stream *r, long, int32 &rom_size) { //Reader lacks size() and rewind(), so we need to read in the file to get its size auto data_vector = ReadStreamUntilEOF(r); uint8 *data = &data_vector[0]; uint32 size = data_vector.size(); //4-byte header + 1-byte input size + 1-byte output size + 4-byte patch CRC32 + 4-byte unpatched CRC32 + 4-byte patched CRC32 if(size < 18) return false; //patch is too small uint32 addr = 4; if (memcmp(data, "UPS1", 4) != 0) return false; //patch has an invalid header uint32 patch_crc32 = caCRC32(data, size - 4); //don't include patch CRC32 itself in CRC32 calculation uint32 rom_crc32 = caCRC32(Memory.ROM, rom_size); uint32 px_crc32 = (data[size - 12] << 0) + (data[size - 11] << 8) + (data[size - 10] << 16) + (data[size - 9] << 24); uint32 py_crc32 = (data[size - 8] << 0) + (data[size - 7] << 8) + (data[size - 6] << 16) + (data[size - 5] << 24); uint32 pp_crc32 = (data[size - 4] << 0) + (data[size - 3] << 8) + (data[size - 2] << 16) + (data[size - 1] << 24); if(patch_crc32 != pp_crc32) { return false; } //patch is corrupted if(!Settings.IgnorePatchChecksum && (rom_crc32 != px_crc32) && (rom_crc32 != py_crc32)) return false; //patch is for a different ROM uint32 px_size = XPSdecode(data, addr, size); uint32 py_size = XPSdecode(data, addr, size); uint32 out_size = ((uint32) rom_size == px_size) ? py_size : px_size; if(out_size > CMemory::MAX_ROM_SIZE) { return false; } //applying this patch will overflow Memory.ROM buffer //fill expanded area with 0x00s; so that XORing works as expected below. //note that this is needed (and works) whether output ROM is larger or smaller than pre-patched ROM for(unsigned i = min((uint32) rom_size, out_size); i < max((uint32) rom_size, out_size); i++) { Memory.ROM[i] = 0x00; } uint32 relative = 0; while(addr < size - 12) { relative += XPSdecode(data, addr, size); while(addr < size - 12) { uint8 x = data[addr++]; Memory.ROM[relative++] ^= x; if(!x) break; } } rom_size = out_size; uint32 out_crc32 = caCRC32(Memory.ROM, rom_size); if(Settings.IgnorePatchChecksum || ((rom_crc32 == px_crc32) && (out_crc32 == py_crc32)) || ((rom_crc32 == py_crc32) && (out_crc32 == px_crc32)) ) { Settings.IsPatched = 3; return true; } else { //technically, reaching here means that patching has failed. //we should return false, but unfortunately Memory.ROM has already //been modified above and cannot be undone. to do this properly, we //would need to make a copy of Memory.ROM, apply the patch, and then //copy that back to Memory.ROM. // //however, the only way for this case to happen is if the UPS patch file //itself is corrupted, which should be detected by the patch CRC32 check //above anyway. errors due to the wrong ROM or patch file being used are //already caught above. fprintf(stderr, "WARNING: UPS patching appears to have failed.\nGame may not be playable.\n"); return true; } } // header notes for UPS patches also apply to BPS // // logic taken from http://byuu.org/programming/bps and the accompanying source // static bool8 ReadBPSPatch (Stream *r, long, int32 &rom_size) { auto data_vector = ReadStreamUntilEOF(r); uint8 *data = &data_vector[0]; uint32 size = data_vector.size(); /* 4-byte header + 1-byte input size + 1-byte output size + 1-byte metadata size + 4-byte unpatched CRC32 + 4-byte patched CRC32 + 4-byte patch CRC32 */ if(size < 19) return false; //patch is too small uint32 addr = 4; if (memcmp(data, "BPS1", 4) != 0) return false; //patch has an invalid header uint32 patch_crc32 = caCRC32(data, size - 4); //don't include patch CRC32 itself in CRC32 calculation uint32 rom_crc32 = caCRC32(Memory.ROM, rom_size); uint32 source_crc32 = (data[size - 12] << 0) + (data[size - 11] << 8) + (data[size - 10] << 16) + (data[size - 9] << 24); uint32 target_crc32 = (data[size - 8] << 0) + (data[size - 7] << 8) + (data[size - 6] << 16) + (data[size - 5] << 24); uint32 pp_crc32 = (data[size - 4] << 0) + (data[size - 3] << 8) + (data[size - 2] << 16) + (data[size - 1] << 24); if(patch_crc32 != pp_crc32) return false; //patch is corrupted if(!Settings.IgnorePatchChecksum && rom_crc32 != source_crc32) return false; //patch is for a different ROM XPSdecode(data, addr, size); uint32 target_size = XPSdecode(data, addr, size); uint32 metadata_size = XPSdecode(data, addr, size); addr += metadata_size; if(target_size > CMemory::MAX_ROM_SIZE) return false; //applying this patch will overflow Memory.ROM buffer enum { SourceRead, TargetRead, SourceCopy, TargetCopy }; uint32 outputOffset = 0, sourceRelativeOffset = 0, targetRelativeOffset = 0; std::vector patched_rom_vector(target_size); uint8 *patched_rom = &patched_rom_vector[0]; memset(patched_rom, 0, target_size); while(addr < size - 12) { uint32 length = XPSdecode(data, addr, size); uint32 mode = length & 3; length = (length >> 2) + 1; switch((int)mode) { case SourceRead: while(length--) { patched_rom[outputOffset] = Memory.ROM[outputOffset]; outputOffset++; } break; case TargetRead: while(length--) patched_rom[outputOffset++] = data[addr++]; break; case SourceCopy: case TargetCopy: int32 offset = XPSdecode(data, addr, size); bool negative = offset & 1; offset >>= 1; if(negative) offset = -offset; if(mode == SourceCopy) { sourceRelativeOffset += offset; while(length--) patched_rom[outputOffset++] = Memory.ROM[sourceRelativeOffset++]; } else { targetRelativeOffset += offset; while(length--) patched_rom[outputOffset++] = patched_rom[targetRelativeOffset++]; } break; } } uint32 out_crc32 = caCRC32(patched_rom, target_size); if(Settings.IgnorePatchChecksum || out_crc32 == target_crc32) { memcpy(Memory.ROM, patched_rom, target_size); rom_size = target_size; Settings.IsPatched = 2; return true; } else { fprintf(stderr, "WARNING: BPS patching failed.\nROM has not been altered.\n"); return false; } } static long ReadInt (Stream *r, unsigned nbytes) { long v = 0; while (nbytes--) { int c = r->get_char(); if (c == EOF) return (-1); v = (v << 8) | (c & 0xFF); } return (v); } static bool8 ReadIPSPatch (Stream *r, long offset, int32 &rom_size) { const int32 IPS_EOF = 0x00454F46l; int32 ofs; char fname[6]; fname[5] = 0; for (int i = 0; i < 5; i++) { int c = r->get_char(); if (c == EOF) return (0); fname[i] = (char) c; } if (strncmp(fname, "PATCH", 5)) return (0); for (;;) { long len, rlen; int rchar; ofs = ReadInt(r, 3); if (ofs == -1) return (0); if (ofs == IPS_EOF) break; ofs -= offset; len = ReadInt(r, 2); if (len == -1) return (0); if (len) { if (ofs + len > CMemory::MAX_ROM_SIZE) return (0); while (len--) { rchar = r->get_char(); if (rchar == EOF) return (0); Memory.ROM[ofs++] = (uint8) rchar; } if (ofs > rom_size) rom_size = ofs; } else { rlen = ReadInt(r, 2); if (rlen == -1) return (0); rchar = r->get_char(); if (rchar == EOF) return (0); if (ofs + rlen > CMemory::MAX_ROM_SIZE) return (0); while (rlen--) Memory.ROM[ofs++] = (uint8) rchar; if (ofs > rom_size) rom_size = ofs; } } ofs = ReadInt(r, 3); if (ofs != -1 && ofs - offset < rom_size) rom_size = ofs - offset; Settings.IsPatched = 1; return (1); } #ifdef UNZIP_SUPPORT static int unzFindExtension (unzFile &file, const char *ext, bool restart, bool print, bool allowExact) { unz_file_info info; int port, l = strlen(ext), e = allowExact ? 0 : 1; if (restart) port = unzGoToFirstFile(file); else port = unzGoToNextFile(file); while (port == UNZ_OK) { int len; char name[132]; unzGetCurrentFileInfo(file, &info, name, 128, NULL, 0, NULL, 0); len = strlen(name); if (len >= l + e && name[len - l - 1] == '.' && strcasecmp(name + len - l, ext) == 0 && unzOpenCurrentFile(file) == UNZ_OK) { if (print) printf("Using patch %s", name); return (port); } port = unzGoToNextFile(file); } return (port); } #endif void CMemory::CheckForAnyPatch(const char *rom_filename, bool8 header, int32 &rom_size) { Settings.IsPatched = false; if (Settings.NoPatch) return; FSTREAM patch_file = NULL; long offset = header ? 512 : 0; int ret; bool flag = false; auto path = splitpath(rom_filename); auto try_patch = [&](const char *type, std::string filename, bool8(*read_patch_func)(Stream * r, long offset, int32 &rom_size)) -> bool { if ((patch_file = OPEN_FSTREAM(filename.c_str(), "rb")) != NULL) { printf("Using %s patch %s", type, filename.c_str()); Stream *s = new fStream(patch_file); ret = read_patch_func(s, offset, rom_size); s->closeStream(); if (ret) { printf("!\n"); flag = true; return true; } else printf(" failed!\n"); } return false; }; auto try_ips_sequence = [&](const char *pattern, enum s9x_getdirtype dirtype) -> bool { for (int i = 0; i < 1000; i++) { char ips[9]; snprintf(ips, 9, pattern, i); if (!try_patch("IPS", S9xGetFilename(ips, dirtype), ReadIPSPatch)) break; } return flag; }; auto try_patch_type_sequence = [&](enum s9x_getdirtype dirtype) -> bool { if (try_patch("BPS", S9xGetFilename(".bps", dirtype), ReadBPSPatch)) return true; if (try_patch("UPS", S9xGetFilename(".ups", dirtype), ReadUPSPatch)) return true; if (try_patch("IPS", S9xGetFilename(".ips", dirtype), ReadIPSPatch)) return true; if (try_ips_sequence(".%03d.ips", dirtype)) return true; if (try_ips_sequence(".ips%d", dirtype)) return true; if (try_ips_sequence(".ip%d", dirtype)) return true; return false; }; if (try_patch_type_sequence(ROMFILENAME_DIR)) return; #ifdef UNZIP_SUPPORT if (path.ext_is(".zip")) { unzFile file = unzOpen(rom_filename); if (file) { auto try_zip_patch = [&](const char *ext, bool8 (*read_patch_func)(Stream * r, long offset, int32 &rom_size)) -> bool { if (unzFindExtension(file, ext) == UNZ_OK) { printf(" in %s", rom_filename); Stream *s = new unzStream(file); ret = read_patch_func(s, offset, rom_size); delete s; if (ret) { printf("!\n"); flag = true; return true; } printf(" failed!\n"); } return false; }; auto try_zip_ips_sequence = [&](const char *pattern) { for (int i = 0; i < 1000; i++) { char ips[8]; snprintf(ips, 8, pattern, i); if (!try_zip_patch(ips, ReadIPSPatch)) break; } }; if (!flag) try_zip_patch("bps", ReadBPSPatch); if (!flag) try_zip_patch("ups", ReadUPSPatch); if (!flag) try_zip_patch("ips", ReadIPSPatch); if (!flag) try_zip_ips_sequence("%03d.ips"); if (!flag) try_zip_ips_sequence("ips%d"); if (!flag) try_zip_ips_sequence("ip%d"); assert(unzClose(file) == UNZ_OK); if (flag) return; } } // Mercurial Magic (MSU-1 distribution pack) if (path.ext_is(".msu1")) // ROM was *NOT* loaded from a .msu1 pack { Stream *s = S9xMSU1OpenFile("patch.bps", TRUE); if (s) { printf("Using BPS patch from msu1"); ret = ReadBPSPatch(s, offset, rom_size); s->closeStream(); if (ret) printf("!\n"); else printf(" failed!\n"); } } #endif if (try_patch_type_sequence(PATCH_DIR)) return; }