ref: b584fd08c6c5f67cb759b62acff90cf2fb52b518
dir: /snes/snes_other.c/
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <stdbool.h> #include "snes.h" #include "cart.h" #include "ppu.h" #include "dsp.h" typedef struct CartHeader { // normal header uint8_t headerVersion; // 1, 2, 3 char name[22]; // $ffc0-$ffd4 (max 21 bytes + \0), $ffd4=$00: header V2 uint8_t speed; // $ffd5.7-4 (always 2 or 3) uint8_t type; // $ffd5.3-0 uint8_t coprocessor; // $ffd6.7-4 uint8_t chips; // $ffd6.3-0 uint32_t romSize; // $ffd7 (0x400 << x) uint32_t ramSize; // $ffd8 (0x400 << x) uint8_t region; // $ffd9 (also NTSC/PAL) uint8_t maker; // $ffda ($33: header V3) uint8_t version; // $ffdb uint16_t checksumComplement; // $ffdc,$ffdd uint16_t checksum; // $ffde,$ffdf // v2/v3 (v2 only exCoprocessor) char makerCode[3]; // $ffb0,$ffb1: (2 chars + \0) char gameCode[5]; // $ffb2-$ffb5: (4 chars + \0) uint32_t flashSize; // $ffbc (0x400 << x) uint32_t exRamSize; // $ffbd (0x400 << x) (used for GSU?) uint8_t specialVersion; // $ffbe uint8_t exCoprocessor; // $ffbf (if coprocessor = $f) // calculated stuff int16_t score; // score for header, to see which mapping is most likely bool pal; // if this is a rom for PAL regions instead of NTSC uint8_t cartType; // calculated type } CartHeader; static void readHeader(uint8_t* data, int location, CartHeader* header); bool snes_loadRom(Snes* snes, uint8_t* data, int length) { // if smaller than smallest possible, don't load if(length < 0x8000) { printf("Failed to load rom: rom to small (%d bytes)\n", length); return false; } // check headers CartHeader headers[4]; memset(headers, 0, sizeof(headers)); for(int i = 0; i < 4; i++) { headers[i].score = -50; } if(length >= 0x8000) readHeader(data, 0x7fc0, &headers[0]); if(length >= 0x8200) readHeader(data, 0x81c0, &headers[1]); if(length >= 0x10000) readHeader(data, 0xffc0, &headers[2]); if(length >= 0x10200) readHeader(data, 0x101c0, &headers[3]); // see which it is int max = 0; int used = 0; for(int i = 0; i < 4; i++) { if(headers[i].score > max) { max = headers[i].score; used = i; } } if(used & 1) { // odd-numbered ones are for headered roms data += 0x200; // move pointer past header length -= 0x200; // and subtract from size } // check if we can load it if(headers[used].cartType > 2) { printf("Failed to load rom: unsupported type (%d)\n", headers[used].cartType); return false; } // expand to a power of 2 int newLength = 0x8000; while(true) { if(length <= newLength) { break; } newLength *= 2; } uint8_t* newData = (uint8_t * )malloc(newLength); memcpy(newData, data, length); int test = 1; while(length != newLength) { if(length & test) { memcpy(newData + length, newData + length - test, test); length += test; } test *= 2; } // load it printf("Loaded %s rom\n\"%s\"\n", headers[used].cartType == 2 ? "HiROM" : "LoROM", headers[used].name); cart_load( snes->cart, headers[used].cartType, newData, newLength, headers[used].chips > 0 ? headers[used].ramSize : 0 ); snes_reset(snes, true); // reset after loading free(newData); return true; } static void readHeader(uint8_t* data, int location, CartHeader* header) { // read name, TODO: non-ASCII names? for(int i = 0; i < 21; i++) { uint8_t ch = data[location + i]; if(ch >= 0x20 && ch < 0x7f) { header->name[i] = ch; } else { header->name[i] = '.'; } } header->name[21] = 0; // read rest header->speed = data[location + 0x15] >> 4; header->type = data[location + 0x15] & 0xf; header->coprocessor = data[location + 0x16] >> 4; header->chips = data[location + 0x16] & 0xf; header->romSize = 0x400 << data[location + 0x17]; header->ramSize = 0x400 << data[location + 0x18]; header->region = data[location + 0x19]; header->maker = data[location + 0x1a]; header->version = data[location + 0x1b]; header->checksumComplement = (data[location + 0x1d] << 8) + data[location + 0x1c]; header->checksum = (data[location + 0x1f] << 8) + data[location + 0x1e]; // read v3 and/or v2 header->headerVersion = 1; if(header->maker == 0x33) { header->headerVersion = 3; // maker code for(int i = 0; i < 2; i++) { uint8_t ch = data[location - 0x10 + i]; if(ch >= 0x20 && ch < 0x7f) { header->makerCode[i] = ch; } else { header->makerCode[i] = '.'; } } header->makerCode[2] = 0; // game code for(int i = 0; i < 4; i++) { uint8_t ch = data[location - 0xe + i]; if(ch >= 0x20 && ch < 0x7f) { header->gameCode[i] = ch; } else { header->gameCode[i] = '.'; } } header->gameCode[4] = 0; header->flashSize = 0x400 << data[location - 4]; header->exRamSize = 0x400 << data[location - 3]; header->specialVersion = data[location - 2]; header->exCoprocessor = data[location - 1]; } else if(data[location + 0x14] == 0) { header->headerVersion = 2; header->exCoprocessor = data[location - 1]; } // get region header->pal = (header->region >= 0x2 && header->region <= 0xc) || header->region == 0x11; header->cartType = location < 0x9000 ? 1 : 2; // get score // TODO: check name, maker/game-codes (if V3) for ASCII, more vectors, // more first opcode, rom-sizes (matches?), type (matches header location?) int score = 0; score += (header->speed == 2 || header->speed == 3) ? 5 : -4; score += (header->type <= 3 || header->type == 5) ? 5 : -2; score += (header->coprocessor <= 5 || header->coprocessor >= 0xe) ? 5 : -2; score += (header->chips <= 6 || header->chips == 9 || header->chips == 0xa) ? 5 : -2; score += (header->region <= 0x14) ? 5 : -2; score += (header->checksum + header->checksumComplement == 0xffff) ? 8 : -6; uint16_t resetVector = data[location + 0x3c] | (data[location + 0x3d] << 8); score += (resetVector >= 0x8000) ? 8 : -20; // check first opcode after reset uint8_t opcode = data[location + 0x40 - 0x8000 + (resetVector & 0x7fff)]; if(opcode == 0x78 || opcode == 0x18) { // sei, clc (for clc:xce) score += 6; } if(opcode == 0x4c || opcode == 0x5c || opcode == 0x9c) { // jmp abs, jml abl, stz abs score += 3; } if(opcode == 0x00 || opcode == 0xff || opcode == 0xdb) { // brk, sbc alx, stp score -= 6; } header->score = score; }