diff --git a/MiSTer.vcxproj b/MiSTer.vcxproj index f20aea7..12d66ac 100644 --- a/MiSTer.vcxproj +++ b/MiSTer.vcxproj @@ -95,6 +95,8 @@ + + @@ -172,6 +174,8 @@ + + diff --git a/MiSTer.vcxproj.filters b/MiSTer.vcxproj.filters index 7be54f0..39e1bab 100644 --- a/MiSTer.vcxproj.filters +++ b/MiSTer.vcxproj.filters @@ -231,6 +231,12 @@ Source Files + + + Source Files\support + + + Source Files\support Source Files\support @@ -461,6 +467,12 @@ Header Files + + + Header Files\support + + + Header Files\support Header Files\support diff --git a/support.h b/support.h index adcc6df..e1055f9 100644 --- a/support.h +++ b/support.h @@ -19,6 +19,10 @@ // SNES support #include "support/snes/snes.h" +// N64 support +#include "support/n64/n64.h" +#include "support/n64/n64_joy_emu.h" + // NeoGeo support #include "support/neogeo/neogeo_loader.h" #include "support/neogeo/neogeocd.h" diff --git a/support/n64/n64.cpp b/support/n64/n64.cpp index 2303eb5..772c39a 100644 --- a/support/n64/n64.cpp +++ b/support/n64/n64.cpp @@ -9,30 +9,44 @@ #include "lib/md5/md5.h" -// Simple hash function, see: https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function +static constexpr size_t CARTID_LENGTH = 6; // Ex: NSME00 +static constexpr size_t MD5_LENGTH = 16; +static constexpr auto CARTID_PREFIX = "ID:"; +static constexpr auto AR_TYPE_OPT = "[48:47]"; +static constexpr auto AUTODETECT_OPT = "[64]"; +static constexpr auto CIC_TYPE_OPT = "[68:65]"; +static constexpr auto NO_EPAK_OPT = "[70]"; +static constexpr auto CPAK_OPT = "[71]"; +static constexpr auto RPAK_OPT = "[72]"; +static constexpr auto TPAK_OPT = "[73]"; +static constexpr auto RTC_OPT = "[74]"; +static constexpr auto SAVE_TYPE_OPT = "[77:75]"; +static constexpr auto SYS_TYPE_OPT = "[80:79]"; +static constexpr const char *const CONTROLLER_OPTS[] = { "[51:49]", "[54:52]", "[57:55]", "[60:58]" }; +// Simple hash function, see: https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function +// (Modified to make it case-insensitive) static constexpr uint64_t FNV_PRIME = 0x100000001b3; static constexpr uint64_t FNV_OFFSET_BASIS = 0xcbf29ce484222325; -static constexpr uint8_t CARTID_LENGTH = 6; -static constexpr auto CARTID_PREFIX = "ID:"; - -static constexpr uint64_t fnv_hash(const char *s, uint64_t h = FNV_OFFSET_BASIS) { - if (s) while (uint8_t a = *(s++)) h = (h ^ a) * FNV_PRIME; +static constexpr uint64_t fnv_hash(const char* s, uint64_t h = FNV_OFFSET_BASIS) { + if (s) while (uint8_t a = *(s++)) h = (h ^ (a >= 'A' && a <= 'Z' ? a += ('a' - 'A') : a)) * FNV_PRIME; return h; } -enum class MemoryType { +enum class MemoryType : uint32_t { NONE = 0, EEPROM_512, EEPROM_2k, SRAM_32k, SRAM_96k, - FLASH_128k + FLASH_128k, + CPAK = ~2U, + TPAK = ~1U, + UNKNOWN = ~0U }; -enum class CIC { - UNKNOWN = -1, - CIC_NUS_6101, +enum class CIC : uint32_t { + CIC_NUS_6101 = 0, CIC_NUS_6102, CIC_NUS_7101, CIC_NUS_7102, @@ -46,47 +60,141 @@ enum class CIC { CIC_NUS_8401, CIC_NUS_5167, CIC_NUS_DDUS, - CIC_NUS_5101 + CIC_NUS_5101, + UNKNOWN = ~0U }; -enum class SystemType { - UNKNOWN = -1, - NTSC, - PAL +enum class SystemType : uint32_t { + NTSC = 0, + PAL, + UNKNOWN = ~0U }; -enum class RomFormat { - UNKNOWN = 0, - BIG_ENDIAN, +enum class RomFormat : uint32_t { + BIG_ENDIAN = 0, BYTE_SWAPPED, - LITTLE_ENDIAN + LITTLE_ENDIAN, + UNKNOWN = ~0U }; -enum class AutoDetect { +enum class PadType : uint32_t { + N64_PAD = 0, + UNPLUGGED, + N64_PAD_WITH_CPAK, + N64_PAD_WITH_RPAK, + SNAC, + N64_PAD_WITH_TPAK, + UNKNOWN = ~0U +}; + +enum class AutoDetect : uint32_t { ON = 0, - OFF = 1 + OFF }; -static RomFormat detectRomFormat(const uint8_t* data) { - // data should be aligned - const uint32_t val = *(uint32_t*)data; +static const char* stringify(MemoryType v) { + switch (v) { + default: return "(none)"; + case MemoryType::EEPROM_512: return "4K EEPROM"; + case MemoryType::EEPROM_2k: return "16K EEPROM"; + case MemoryType::SRAM_32k: return "256K SRAM"; + case MemoryType::SRAM_96k: return "768K SRAM"; + case MemoryType::FLASH_128k: return "Flash RAM"; + } +} - // the following checks assume we're on a little-endian platform - // for each check, the first value is for regular roms, the 2nd is for 64DD images - // third is a malformed magic word used in homebrew (mostly pointless) - if (val == UINT32_C(0x40123780) || val == UINT32_C(0x40072780) || val == UINT32_C(0x41123780)) return RomFormat::BIG_ENDIAN; - if (val == UINT32_C(0x12408037) || val == UINT32_C(0x07408027) || val == UINT32_C(0x12418037)) return RomFormat::BYTE_SWAPPED; - if (val == UINT32_C(0x80371240) || val == UINT32_C(0x80270740) || val == UINT32_C(0x80371241)) return RomFormat::LITTLE_ENDIAN; +static size_t get_save_size(MemoryType v) { + switch (v) { + case MemoryType::EEPROM_512: return 0x200; + case MemoryType::EEPROM_2k: return 0x800; + case MemoryType::SRAM_32k: return 0x8000; + case MemoryType::SRAM_96k: return 0x18000; + case MemoryType::FLASH_128k: return 0x20000; + case MemoryType::CPAK: + case MemoryType::TPAK: return 0x8000; // 32 kByte + default: return 0; + } +} + +static const char* stringify(CIC v) { + switch (v) { + case CIC::CIC_NUS_6101: return "6101"; + case CIC::CIC_NUS_6102: return "6102"; + case CIC::CIC_NUS_7101: return "7101"; + case CIC::CIC_NUS_7102: return "7102"; + case CIC::CIC_NUS_6103: return "6103"; + case CIC::CIC_NUS_7103: return "7103"; + case CIC::CIC_NUS_6105: return "6105"; + case CIC::CIC_NUS_7105: return "7105"; + case CIC::CIC_NUS_6106: return "6106"; + case CIC::CIC_NUS_7106: return "7106"; + case CIC::CIC_NUS_8303: return "8303"; + case CIC::CIC_NUS_8401: return "8401"; + case CIC::CIC_NUS_5167: return "5167"; + case CIC::CIC_NUS_DDUS: return "DDUS"; + case CIC::CIC_NUS_5101: return "5101"; + default: return "Unknown"; + } +} + +static const char* stringify(SystemType v) { + switch (v) { + case SystemType::NTSC: return "NTSC"; + case SystemType::PAL: return "PAL"; + default: return "Unknown"; + } +} + +static const char* stringify(bool v) { + return v + ? "Yes" + : "No"; +} + +static RomFormat detectRomFormat(const uint8_t *data) { + // Data should be aligned + const uint32_t val = *(uint32_t *)data; + + /* The following checks assume we're on a little-endian platform. + For each check, the first value is for regular ROMs, the 2nd is for 64DD images and + the third is a malformed "word" used in some homebrew(?) */ + switch (val) { + case UINT32_C(0x40123780): + case UINT32_C(0x40072780): + case UINT32_C(0x41123780): + return RomFormat::BIG_ENDIAN; + case UINT32_C(0x12408037): + case UINT32_C(0x07408027): + case UINT32_C(0x12418037): + return RomFormat::BYTE_SWAPPED; + case UINT32_C(0x80371240): + case UINT32_C(0x80270740): + case UINT32_C(0x80371241): + return RomFormat::LITTLE_ENDIAN; + default: + break; + } + + // Endianness could not be determined, use just first byte. + switch (val & 0xff) { + case 0x80: + return RomFormat::BIG_ENDIAN; + case 0x37: + case 0x27: + return RomFormat::BYTE_SWAPPED; + case 0x40: + case 0x41: + return RomFormat::LITTLE_ENDIAN; + default: + return RomFormat::UNKNOWN; + } - return RomFormat::UNKNOWN; } static void normalizeData(uint8_t* data, size_t size, RomFormat format) { - switch(format) { - default: - // nothing to do - break; + switch (format) { case RomFormat::BYTE_SWAPPED: + size &= ~1U; for (size_t i = 0; i < size; i += 2) { auto c0 = data[0]; auto c1 = data[1]; @@ -96,6 +204,7 @@ static void normalizeData(uint8_t* data, size_t size, RomFormat format) { } break; case RomFormat::LITTLE_ENDIAN: + size &= ~3U; for (size_t i = 0; i < size; i += 4) { auto c0 = data[0]; auto c1 = data[1]; @@ -108,107 +217,206 @@ static void normalizeData(uint8_t* data, size_t size, RomFormat format) { data += 4; } break; + default: + // Nothing to do + break; } } -static void normalizeString(char* s) { - // change the string to lower-case - while (*s) { *s = tolower(*s); ++s; } +static MemoryType get_save_type() { + auto tmp = (MemoryType)user_io_status_get(SAVE_TYPE_OPT); + return (get_save_size(tmp) ? tmp : MemoryType::NONE); } -// return true if cic and system region is detected -static bool parse_and_apply_db_tags(char* tags) { +static void set_save_type(MemoryType v) { + user_io_status_set(SAVE_TYPE_OPT, + (uint32_t)(get_save_size(v) ? v : MemoryType::NONE)); +} + +static bool is_auto() { + return (AutoDetect)user_io_status_get(AUTODETECT_OPT) == AutoDetect::ON; +} + +static int8_t hex_to_dec(const char x) { + if (x >= '0' && x <= '9') return x - '0'; + if (x >= 'A' && x <= 'F') return x - 'A' + 10; + if (x >= 'a' && x <= 'f') return x - 'a' + 10; + return -1; +} + +static void trim(char* out, size_t len, const char* str) +{ + if (len == 0) { + return; + } + + const char* end; + size_t out_size; + + // Trim leading space + while (isspace((unsigned char)*str)) { + str++; + } + + // All spaces? + if (*str == '\0') { + *out = '\0'; + return; + } + + // Trim trailing space + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) { + end--; + } + end++; + + // Set output size to minimum of trimmed string length and buffer size minus 1 + out_size = (size_t)(end - str) < (len - 1) ? (end - str) : len - 1; + + // Copy trimmed string and add null terminator + memcpy(out, str, out_size); + out[out_size] = '\0'; + + // Obfuscate illegal characters + for (size_t i = 0; i < out_size; i++) { + if (out[i] == '\0') { + break; + } + // Valid characters + if ((out[i] >= 0x20) && (out[i] < 0xa0)) { + continue; + } + out[i] = '?'; + } +} + +// Returns true if CIC and System Region is detected, or if auto-detection is turned off +static bool parse_and_apply_db_tags(char *tags) { + if (tags == nullptr) return false; + + const char* separator = "| "; MemoryType save_type = MemoryType::NONE; SystemType system_type = SystemType::UNKNOWN; - CIC cic = CIC::UNKNOWN; + CIC cic_type = CIC::UNKNOWN; + bool no_epak = false; bool cpak = false; bool rpak = false; bool tpak = false; bool rtc = false; + bool wide = false; - const char separator[] = "|"; - - for (char* tag = strtok(tags, separator); tag; tag = strtok(nullptr, separator)) { - printf("Tag: %s\n", tag); - - normalizeString(tag); + for (auto tag = strtok(tags, separator); tag; tag = strtok(nullptr, separator)) { switch (fnv_hash(tag)) { - case fnv_hash("eeprom512"): save_type = MemoryType::EEPROM_512; break; - case fnv_hash("eeprom2k"): save_type = MemoryType::EEPROM_2k; break; - case fnv_hash("sram32k"): save_type = MemoryType::SRAM_32k; break; - case fnv_hash("sram96k"): save_type = MemoryType::SRAM_96k; break; - case fnv_hash("flash128k"): save_type = MemoryType::FLASH_128k; break; - case fnv_hash("ntsc"): system_type = SystemType::NTSC; break; - case fnv_hash("pal"): system_type = SystemType::PAL; break; - case fnv_hash("cpak"): cpak = true; break; - case fnv_hash("rpak"): rpak = true; break; - case fnv_hash("tpak"): tpak = true; break; - case fnv_hash("rtc"): rtc = true; break; - case fnv_hash("cic6101"): cic = CIC::CIC_NUS_6101; break; - case fnv_hash("cic6102"): cic = CIC::CIC_NUS_6102; break; - case fnv_hash("cic6103"): cic = CIC::CIC_NUS_6103; break; - case fnv_hash("cic6105"): cic = CIC::CIC_NUS_6105; break; - case fnv_hash("cic6106"): cic = CIC::CIC_NUS_6106; break; - case fnv_hash("cic7101"): cic = CIC::CIC_NUS_7101; break; - case fnv_hash("cic7102"): cic = CIC::CIC_NUS_7102; break; - case fnv_hash("cic7103"): cic = CIC::CIC_NUS_7103; break; - case fnv_hash("cic7105"): cic = CIC::CIC_NUS_7105; break; - case fnv_hash("cic7106"): cic = CIC::CIC_NUS_7106; break; - case fnv_hash("cic8303"): cic = CIC::CIC_NUS_8303; break; - case fnv_hash("cic8401"): cic = CIC::CIC_NUS_8401; break; - case fnv_hash("cic5167"): cic = CIC::CIC_NUS_5167; break; - case fnv_hash("cicDDUS"): cic = CIC::CIC_NUS_DDUS; break; - case fnv_hash("cic5101"): cic = CIC::CIC_NUS_5101; break; - default: printf("Unknown tag: %s\n", tag); break; + case fnv_hash("eeprom512"): save_type = MemoryType::EEPROM_512; break; + case fnv_hash("eeprom2k"): save_type = MemoryType::EEPROM_2k; break; + case fnv_hash("sram32k"): save_type = MemoryType::SRAM_32k; break; + case fnv_hash("sram96k"): save_type = MemoryType::SRAM_96k; break; + case fnv_hash("flash128k"): save_type = MemoryType::FLASH_128k; break; + case fnv_hash("noepak"): no_epak = true; break; + case fnv_hash("cpak"): cpak = true; break; + case fnv_hash("rpak"): rpak = true; break; + case fnv_hash("tpak"): tpak = true; break; + case fnv_hash("rtc"): rtc = true; break; + case fnv_hash("ntsc"): ntsc: system_type = SystemType::NTSC; break; + case fnv_hash("pal"): pal: system_type = SystemType::PAL; break; + case fnv_hash("wide"): wide = true; break; + case fnv_hash("cic6101"): cic_type = CIC::CIC_NUS_6101; goto ntsc; + case fnv_hash("cic6102"): cic_type = CIC::CIC_NUS_6102; goto ntsc; + case fnv_hash("cic6103"): cic_type = CIC::CIC_NUS_6103; goto ntsc; + case fnv_hash("cic6105"): cic_type = CIC::CIC_NUS_6105; goto ntsc; + case fnv_hash("cic6106"): cic_type = CIC::CIC_NUS_6106; goto ntsc; + case fnv_hash("cic7101"): cic_type = CIC::CIC_NUS_7101; goto pal; + case fnv_hash("cic7102"): cic_type = CIC::CIC_NUS_7102; goto pal; + case fnv_hash("cic7103"): cic_type = CIC::CIC_NUS_7103; goto pal; + case fnv_hash("cic7105"): cic_type = CIC::CIC_NUS_7105; goto pal; + case fnv_hash("cic7106"): cic_type = CIC::CIC_NUS_7106; goto pal; + case fnv_hash("cic8303"): cic_type = CIC::CIC_NUS_8303; break; + case fnv_hash("cic8401"): cic_type = CIC::CIC_NUS_8401; break; + case fnv_hash("cic5167"): cic_type = CIC::CIC_NUS_5167; break; + case fnv_hash("cicddus"): cic_type = CIC::CIC_NUS_DDUS; break; + case fnv_hash("cic5101"): cic_type = CIC::CIC_NUS_5101; break; + default: printf("Unknown tag: [%s] (skipping)\n", tag); break; } } - printf("System: %d, Save Type: %d, CIC: %d, CPak: %d, RPak: %d, TPak %d, RTC: %d\n", (int)system_type, (int)save_type, (int)cic, cpak, rpak, tpak, rtc); - const auto auto_detect = (AutoDetect)user_io_status_get("[64]"); - - if (auto_detect == AutoDetect::ON) { - printf("Auto-detect is on, updating OSD settings\n"); - - if (system_type != SystemType::UNKNOWN) user_io_status_set("[80:79]", (uint32_t)system_type); - if (cic != CIC::UNKNOWN) user_io_status_set("[68:65]", (uint32_t)cic); - user_io_status_set("[71]", (uint32_t)cpak); - user_io_status_set("[72]", (uint32_t)rpak); - user_io_status_set("[73]", (uint32_t)tpak); - user_io_status_set("[74]", (uint32_t)rtc); - user_io_status_set("[77:75]", (uint32_t)save_type); - } - else { - printf("Auto-detect is off, not updating OSD settings\n"); + if (system_type == SystemType::UNKNOWN && cic_type != CIC::UNKNOWN) { + system_type = SystemType::NTSC; } - return (auto_detect != AutoDetect::ON) || (system_type != SystemType::UNKNOWN && cic != CIC::UNKNOWN); + printf("System: %s, Save Type: %s, CIC: %s, CPak: %s, RPak: %s, TPak %s, RTC: %s, Mem: %uMB\n", + stringify(system_type), + stringify(save_type), + stringify(cic_type), + stringify(cpak), + stringify(rpak), + stringify(tpak), + stringify(rtc), + (no_epak ? 4 : 8)); + + if (!is_auto()) { + printf("Auto-detect is OFF, not updating OSD settings\n"); + return true; + } + + printf("Auto-detect is ON, updating OSD settings\n"); + + if (system_type != SystemType::UNKNOWN) user_io_status_set(SYS_TYPE_OPT, (uint32_t)system_type); + if (cic_type != CIC::UNKNOWN) user_io_status_set(CIC_TYPE_OPT, (uint32_t)cic_type); + + user_io_status_set(NO_EPAK_OPT, (uint32_t)no_epak); + user_io_status_set(CPAK_OPT, (uint32_t)cpak); + user_io_status_set(RPAK_OPT, (uint32_t)rpak); + user_io_status_set(TPAK_OPT, (uint32_t)tpak); + user_io_status_set(RTC_OPT, (uint32_t)rtc); + set_save_type(save_type); + + if ((PadType)user_io_status_get(CONTROLLER_OPTS[0]) != PadType::SNAC) { + user_io_status_set(CONTROLLER_OPTS[0], (uint32_t)( + tpak ? PadType::N64_PAD_WITH_TPAK : + cpak ? PadType::N64_PAD_WITH_CPAK : + rpak ? PadType::N64_PAD_WITH_RPAK : + PadType::N64_PAD)); + } + + if (wide) user_io_status_set(AR_TYPE_OPT, 1); + + return (system_type != SystemType::UNKNOWN && cic_type != CIC::UNKNOWN); } -bool md5_matches(const char* line, const char* md5) { - for (auto i = 0; i < 32; i++) { - if (line[i] == '\0' || md5[i] != tolower(line[i])) +static bool md5_matches(const char* line, const char* md5) { + for (size_t i = MD5_LENGTH * 2; i > 0; --i, line++, md5++) { + int c = *line; + if (!c || *md5 != tolower(c)) { return false; + } } return true; } -bool cart_id_matches(const char* line, const char* cart_id) { +// Returns numbers of matching characters if match, otherwize 0 +static size_t cart_id_matches(const char* line, const char* cart_id) { + const auto prefix_len = strlen(CARTID_PREFIX); + // A valid ID line should start with "ID:" - if (strncmp(line, CARTID_PREFIX, strlen(CARTID_PREFIX)) != 0) - return false; + if (strncmp(line, CARTID_PREFIX, prefix_len) != 0) { + return 0; + } // Skip the line if it doesn't match our cart_id, '_' = don't care - auto lp = (char*)line + strlen(CARTID_PREFIX); - for (auto i = 0; i < CARTID_LENGTH; i++, lp++) { - if (*lp != '_' && *lp != cart_id[i]) - return false; // character didn't match + const char* lp = line + prefix_len; + for (size_t i = 0; i < CARTID_LENGTH && *lp; i++, lp++) { + if (*lp != '_' && *lp != cart_id[i]) { + return 0; // Character didn't match pattern + } - if (*lp == ' ') // early termination - return true; + if (isspace(*lp)) { + return i; // Early termination + } } - return true; + return CARTID_LENGTH; } static uint8_t detect_rom_settings_in_db(const char* lookup_hash, const char* db_file_name) { @@ -219,25 +427,25 @@ static uint8_t detect_rom_settings_in_db(const char* lookup_hash, const char* db if (!FileOpenTextReader(&reader, file_path)) { printf("Failed to open N64 data file %s\n", file_path); - return false; + return 0; } - char tags[128]; - - const char* line; - while ((line = FileReadLine(&reader))) { + while (const char* line = FileReadLine(&reader)) { // Skip the line if it doesn't start with our hash - if (!md5_matches(line, lookup_hash)) + if (!md5_matches(line, lookup_hash)) { continue; + } - printf("Found ROM entry: %s\n", line); - - if (sscanf(line, "%*s %s", tags) != 1) { - printf("No tags found.\n"); + const char* s = line + (MD5_LENGTH * 2); + char* tags = new char[strlen(s) + 1]; + if (sscanf(s, "%*[ \t]%[^#;]", tags) <= 0) { + printf("Found ROM entry for MD5 [%s], but the tag was malformed! (%s)\n", lookup_hash, s); return 2; } - // 2 = System region and/or CIC wasn't in DB, will need detection + printf("Found ROM entry for MD5 [%s]: %s\n", lookup_hash, tags); + + // 2 = System region and/or CIC wasn't in DB, will need further detection return parse_and_apply_db_tags(tags) ? 3 : 2; } @@ -252,177 +460,267 @@ static uint8_t detect_rom_settings_in_db_with_cartid(const char* cart_id, const if (!FileOpenTextReader(&reader, file_path)) { printf("Failed to open N64 data file %s\n", file_path); - return false; + return 0; } - char tags[128]; + size_t i; - const char* line; - while ((line = FileReadLine(&reader))) { - // Skip the line if it doesn't start with our ID - if (!cart_id_matches(line, cart_id)) - continue; + while (const char* line = FileReadLine(&reader)) { + // Skip lines that doesn't start with our ID + if (!(i = cart_id_matches(line, cart_id))) continue; - printf("Found ROM entry: %s\n", line); - - if (sscanf(line, "%*s %s", tags) != 1) { - printf("No tags found.\n"); + auto s = line + strlen(CARTID_PREFIX) + i; + auto tags = new char[strlen(s) + 1]; + if (sscanf(s, "%*[ \t]%[^#;]", tags) <= 0) { + printf("Found ROM entry for ID [%s], but the tag was malformed! (%s)\n", cart_id, s); return 2; } - // 2 = System region and/or CIC wasn't in DB, will need detection + printf("Found ROM entry for ID [%s]: %s\n", cart_id, tags); + + // 2 = System region and/or CIC wasn't in DB, will need further detection return parse_and_apply_db_tags(tags) ? 3 : 2; } return 0; } -static const char* DB_FILE_NAMES[] = -{ +static const char* DB_FILE_NAMES[] = { "N64-database.txt", "N64-database_user.txt", }; -static uint8_t detect_rom_settings_in_dbs_with_md5(const char* lookup) { +static uint8_t detect_rom_settings_in_dbs_with_md5(const char* lookup_hash) { for (const char* db_file_name : DB_FILE_NAMES) { - const auto detected = detect_rom_settings_in_db(lookup, db_file_name); - if (detected != 0) return detected; + const auto detected = detect_rom_settings_in_db(lookup_hash, db_file_name); + if (detected) { + return detected; + } } return 0; } -static uint8_t detect_rom_settings_in_dbs_with_cartid(const char* lookup) { - if (strlen(lookup) < CARTID_LENGTH) - return 0; - - for (auto i = 0; i < CARTID_LENGTH; i++) { - if ((lookup[i] >= '0' && lookup[i] <= '9') || (lookup[i] >= 'A' && lookup[i] <= 'Z')) +static uint8_t detect_rom_settings_in_dbs_with_cartid(const char* lookup_id) { + // Check if all characters in the lookup are valid + for (size_t i = 0; i < CARTID_LENGTH; i++) { + auto c = lookup_id[i]; + if (isupper(c) || isdigit(c)) { continue; + } + printf("Not a valid Cart ID: [%s]!\n", lookup_id); return 0; } for (const char* db_file_name : DB_FILE_NAMES) { - const auto detected = detect_rom_settings_in_db_with_cartid(lookup, db_file_name); - if (detected != 0) return detected; + const auto detected = detect_rom_settings_in_db_with_cartid(lookup_id, db_file_name); + if (detected) { + return detected; + } } - + return 0; } -static bool detect_rom_settings_from_first_chunk(char region_code, uint64_t crc) { - SystemType system_type = SystemType::NTSC; - CIC cic; +static bool detect_homebrew_header(const uint8_t* controller_settings, const char* cart_id) { + if (!(cart_id[1] == 'E' && cart_id[2] == 'D')) { + return false; + } + + printf("Detected Advanced Homebrew ROM Header, how fancy!\n"); + + if (!is_auto()) { + printf("Auto-detect is OFF, not updating OSD settings\n"); + return false; + } + + printf("Auto-detect is ON, updating OSD settings\n"); + + set_save_type((MemoryType)hex_to_dec(cart_id[4])); + user_io_status_set(RTC_OPT, (uint32_t)(hex_to_dec(cart_id[5]) & 1)); + + user_io_status_set(RPAK_OPT, (uint32_t)( + (controller_settings[0]) == 0x01 || + (controller_settings[1]) == 0x01 || + (controller_settings[2]) == 0x01 || + (controller_settings[3]) == 0x01 ? 1 : 0)); // Rumble Pak + + user_io_status_set(CPAK_OPT, (uint32_t)( + (controller_settings[0] == 0x02) || + (controller_settings[1] == 0x02) || + (controller_settings[2] == 0x02) || + (controller_settings[3] == 0x02) ? 1 : 0)); // Controller Pak + + user_io_status_set(TPAK_OPT, (uint32_t)( + (controller_settings[0] == 0x03) || + (controller_settings[1] == 0x03) || + (controller_settings[2] == 0x03) || + (controller_settings[3] == 0x03) ? 1 : 0)); // Transfer Pak + + uint32_t c_idx = 0; + for (auto c_opt : CONTROLLER_OPTS) { + if (controller_settings[c_idx] && ((PadType)user_io_status_get(c_opt) != PadType::SNAC)) { + if (controller_settings[c_idx] < 0x80) { + user_io_status_set(c_opt, (uint32_t)( + (controller_settings[c_idx] == 0x01) ? PadType::N64_PAD_WITH_RPAK : + (controller_settings[c_idx] == 0x02) ? PadType::N64_PAD_WITH_CPAK : + (controller_settings[c_idx] == 0x03) && (c_idx == 0) ? PadType::N64_PAD_WITH_TPAK : + PadType::N64_PAD)); + } + else if (controller_settings[c_idx] == 0xff) { + user_io_status_set(c_opt, (uint32_t)PadType::UNPLUGGED); + } + } + c_idx++; + } + + return true; +} + +static bool detect_rom_settings_from_first_chunk(const char region_code, const uint64_t *signatures) { + SystemType system_type; + CIC cic = CIC::UNKNOWN; bool is_known_cic = true; - if ((AutoDetect)user_io_status_get("[64]") != AutoDetect::ON) { - printf("Auto-detect is off, not updating OSD settings\n"); + switch (region_code) { + case 'D': // Germany + case 'F': // France + case 'H': // Netherlands (Dutch) + case 'I': // Italy + case 'L': // Gateway 64 + case 'P': // Europe + case 'S': // Spain + case 'U': // Australia + case 'W': // Scandinavia + case 'X': // Europe + case 'Y': // Europe + system_type = SystemType::PAL; break; + default: + system_type = SystemType::NTSC; break; + } + + // How many signatures are we checking against? (Normal and Aleck64) + size_t n = 2; + + if (n) do { + // The following checks assume we're on a little-endian platform + switch (*signatures) { + default: + if (--n) { + printf("Unknown CIC (Signature: 0x%016llx), tries next.\n", *signatures); + signatures++; + break; + } + printf("Unknown CIC (Signature: 0x%016llx), uses default.\n", *signatures); + is_known_cic = false; + // Fall through + case UINT64_C(0x000000a316adc55a): + case UINT64_C(0x000000a30dacd530): // NOP:ed out CRC check + case UINT64_C(0x000000039c981107): // hcs64's CIC-6102 IPL3 replacement + case UINT64_C(0x000000d2828281b0): // Unknown. Used in some homebrew + case UINT64_C(0x0000009acc31e644): // Unknown. Used in some betas and homebrew. Dev boot code? + case UINT64_C(0x0000009474732e6b): // iQue hacks + cic = (system_type != SystemType::PAL) ? CIC::CIC_NUS_6102 : CIC::CIC_NUS_7101; break; + case UINT64_C(0x000000a405397b05): + case UINT64_C(0x000000a3fc388adb): // NOP:ed out CRC check + system_type = SystemType::PAL; cic = CIC::CIC_NUS_7102; break; + case UINT64_C(0x000000a0f26f62fe): + case UINT64_C(0x000000a0e96e72d4): // NOP:ed out CRC check + system_type = SystemType::NTSC; cic = CIC::CIC_NUS_6101; break; + case UINT64_C(0x000000a9229d7c45): + case UINT64_C(0x000000a9199c8c1b): // NOP:ed out CRC check + case UINT64_C(0x000000271316d406): // iQue hacks + cic = (system_type == SystemType::NTSC) ? CIC::CIC_NUS_6103 : CIC::CIC_NUS_7103; break; + case UINT64_C(0x000000f8b860ed00): + case UINT64_C(0x000000f8af5ffcd6): // NOP:ed out CRC check + cic = (system_type == SystemType::NTSC) ? CIC::CIC_NUS_6105 : CIC::CIC_NUS_7105; break; + case UINT64_C(0x000000ba5ba4b8cd): + cic = (system_type == SystemType::NTSC) ? CIC::CIC_NUS_6106 : CIC::CIC_NUS_7106; break; + case UINT64_C(0x0000012daafc8aab): cic = CIC::CIC_NUS_5167; break; + case UINT64_C(0x000000a9df4b39e1): cic = CIC::CIC_NUS_8303; break; + case UINT64_C(0x000000aa764e39e1): cic = CIC::CIC_NUS_8401; break; + case UINT64_C(0x000000abb0b739e1): cic = CIC::CIC_NUS_DDUS; break; + case UINT64_C(0x00000081ce470326): + case UINT64_C(0x000000827a47195a): // Kuru Kuru Fever + case UINT64_C(0x00000082551e4848): // Tower & Shaft + cic = CIC::CIC_NUS_5101; break; + } + } while (cic == CIC::UNKNOWN); + + printf("System: %s, CIC: %s\n", stringify(system_type), stringify(cic)); + + if (!is_auto()) { + printf("Auto-detect is OFF, not updating OSD settings\n"); return true; } - switch (region_code) { - case 'D': // Germany - case 'F': // France - case 'H': // Netherlands (Dutch) - case 'I': // Italy - case 'L': // Gateway 64 - case 'P': // Europe - case 'S': // Spain - case 'U': // Australia - case 'W': // Scandinavia - case 'X': // Europe - case 'Y': // Europe - system_type = SystemType::PAL; break; - } + printf("Auto-detect is ON, updating OSD settings\n"); - // the following checks assume we're on a little-endian platform - switch (crc) { - default: - printf("Unknown CIC (0x%016llx), uses default\n", crc); - is_known_cic = false; - // fall through - case UINT64_C(0x000000a316adc55a): - case UINT64_C(0x000000a30dacd530): // NOP:ed out CRC check - case UINT64_C(0x000000039c981107): // hcs64's CIC-6102 IPL3 replacement - case UINT64_C(0x000000d2828281b0): // Unknown. Used in some homebrew - case UINT64_C(0x0000009acc31e644): // Unknown. Used in some betas and homebrew. Dev boot code? - cic = system_type == SystemType::NTSC - ? CIC::CIC_NUS_6102 - : CIC::CIC_NUS_7101; break; - case UINT64_C(0x000000a405397b05): - case UINT64_C(0x000000a3fc388adb): // NOP:ed out CRC check - system_type = SystemType::PAL; cic = CIC::CIC_NUS_7102; break; - case UINT64_C(0x000000a0f26f62fe): - case UINT64_C(0x000000a0e96e72d4): // NOP:ed out CRC check - system_type = SystemType::NTSC; cic = CIC::CIC_NUS_6101; break; - case UINT64_C(0x000000a9229d7c45): - case UINT64_C(0x000000a9199c8c1b): // NOP:ed out CRC check - cic = system_type == SystemType::NTSC - ? CIC::CIC_NUS_6103 - : CIC::CIC_NUS_7103; break; - case UINT64_C(0x000000f8b860ed00): - case UINT64_C(0x000000f8af5ffcd6): // NOP:ed out CRC check - cic = system_type == SystemType::NTSC - ? CIC::CIC_NUS_6105 - : CIC::CIC_NUS_7105; break; - case UINT64_C(0x000000ba5ba4b8cd): - cic = system_type == SystemType::NTSC - ? CIC::CIC_NUS_6106 - : CIC::CIC_NUS_7106; break; - case UINT64_C(0x0000012daafc8aab): cic = CIC::CIC_NUS_5167; break; - case UINT64_C(0x000000a9df4b39e1): cic = CIC::CIC_NUS_8303; break; - case UINT64_C(0x000000aa764e39e1): cic = CIC::CIC_NUS_8401; break; - case UINT64_C(0x000000abb0b739e1): cic = CIC::CIC_NUS_DDUS; break; - case UINT64_C(0x00000081ce470326): cic = CIC::CIC_NUS_5101; break; - } - - if (is_known_cic) printf("System: %d, CIC: %d\n", (int)system_type, (int)cic); - printf("Auto-detect is on, updating OSD settings\n"); - - user_io_status_set("[80:79]", (uint32_t)system_type); - user_io_status_set("[68:65]", (uint32_t)cic); + user_io_status_set(SYS_TYPE_OPT, (uint32_t)system_type); + user_io_status_set(CIC_TYPE_OPT, (uint32_t)cic); return is_known_cic; } -static void md5_to_hex(uint8_t* in, char* out) { - for (auto i = 0; i < 16; i++) { - sprintf(out, "%02x", in[i]); +// Creates a lower-case hex string out of the MD5 buffer +static void md5_to_hex(uint8_t* md5, char* out) { + for (size_t i = 0; i < MD5_LENGTH; i++) { + sprintf(out, "%02x", md5[i]); out += 2; } } -int n64_rom_tx(const char* name, unsigned char index) { +static void calc_bootcode_checksums(uint64_t bootcode_sums[2], const uint8_t* buf) +{ + size_t i; + uint64_t sum = 0; + + // Calculate boot code checksum for bytes 0x40 - 0xc00 (Aleck64) + for (i = 0x40 / sizeof(uint32_t); i < 0xc00 / sizeof(uint32_t); i++) { + sum += ((uint32_t*)buf)[i]; + } + + bootcode_sums[1] = sum; + + // Calculate boot code checksum for bytes 0x40 - 0x1000 + for (; i < 0x1000 / sizeof(uint32_t); i++) { + sum += ((uint32_t*)buf)[i]; + } + + bootcode_sums[0] = sum; +} + +int n64_rom_tx(const char *name, unsigned char idx) { static uint8_t buf[4096]; - fileTYPE f = {}; + fileTYPE f; if (!FileOpen(&f, name, 1)) return 0; - unsigned long bytes2send = f.size; + uint32_t data_size = f.size; + auto data_left = data_size; - printf("N64 file %s with %lu bytes to send for index %04X\n", name, bytes2send, index); + printf("N64 file %s with %u bytes to send for index %04X\n", name, data_size, idx); // set index byte - user_io_set_index(index); + user_io_set_index(idx); // prepare transmission of new file user_io_set_download(1); - int use_progress = 1; - int size = bytes2send; - if (use_progress) ProgressMessage(0, 0, 0, 0); + const int use_progress = 1; + if (use_progress) ProgressMessage(); - if ((index & 63) == 2) { - // Handle non-N64 files (Game Boy) - while (bytes2send) { - uint32_t chunk = (bytes2send > sizeof(buf)) ? sizeof(buf) : bytes2send; + if ((idx & 0x3f) == 2) { + // Handle non-N64 files (GameBoy) + while (data_left) { + uint32_t chunk = (data_left > sizeof(buf)) ? sizeof(buf) : data_left; FileReadAdv(&f, buf, chunk); user_io_file_tx_data(buf, chunk); - if (use_progress) ProgressMessage("Loading", f.name, size - bytes2send, size); - bytes2send -= chunk; + if (use_progress) ProgressMessage("Loading", f.name, data_size - data_left, data_size); + data_left -= chunk; } printf("Done.\n"); @@ -438,23 +736,25 @@ int n64_rom_tx(const char* name, unsigned char index) { // save state processing process_ss(name); - // 0 = Nothing detected - // 1 = System region and CIC detected - // 2 = Found some ROM info in DB (Save type etc.), but System region and CIC has not been determined - // 3 = Has detected everything, System type, CIC, Save type etc. + /* 0 = Nothing detected + 1 = System region and CIC detected + 2 = Found some ROM info in DB (Save type etc.), but System region and/or CIC has not been determined + 3 = Has detected everything, System type, CIC, Save type etc. */ uint8_t rom_settings_detected = 0; RomFormat rom_format; + uint8_t md5[MD5_LENGTH]; + char md5_hex[MD5_LENGTH * 2 + 1]; + uint64_t bootcode_sums[2] = { }; + uint8_t controller_settings[4]; + char cart_id[CARTID_LENGTH + 1] = { '\0', '\0', '\0', '\0', '0', '0' }; + bool is_first_chunk = true; + char* internal_name = new char[21] { }; MD5Context ctx; MD5Init(&ctx); - uint8_t md5[16]; - char md5_hex[40]; - uint64_t bootcode_sum = 0; - char cart_id[8]; - bool is_first_chunk = true; - while (bytes2send) { - uint32_t chunk = (bytes2send > sizeof(buf)) ? sizeof(buf) : bytes2send; + while (data_left) { + size_t chunk = (data_left > sizeof(buf)) ? sizeof(buf) : data_left; FileReadAdv(&f, buf, chunk); @@ -462,83 +762,119 @@ int n64_rom_tx(const char* name, unsigned char index) { if (is_first_chunk) { if (chunk < 4096) { printf("Failed to load ROM: must be at least 4096 bytes\n"); + Info("Invalid ROM!"); return 0; } rom_format = detectRomFormat(buf); - if (rom_format == RomFormat::UNKNOWN) - printf("Unknown ROM format\n"); } // normalize data to big-endian format normalizeData(buf, chunk, rom_format); - MD5Update(&ctx, buf, chunk); if (is_first_chunk) { - // Try to detect ROM settings based on header MD5 hash + /* Try to detect ROM settings based on header MD5 hash. + For calculating the MD5 hash of the header, we need to make a + copy of the context before calling MD5Final, otherwise the file + hash will be incorrect later on. */ - // For calculating the MD5 hash of the header, we need to make a - // copy of the context before calling MD5Final, otherwise the file - // hash will be incorrect lateron. MD5Context ctx_header; memcpy(&ctx_header, &ctx, sizeof(struct MD5Context)); MD5Final(md5, &ctx_header); md5_to_hex(md5, md5_hex); printf("Header MD5: %s\n", md5_hex); + trim(internal_name, 20, (char *)(&buf[0x20])); rom_settings_detected = detect_rom_settings_in_dbs_with_md5(md5_hex); - if (rom_settings_detected == 0) + if (rom_settings_detected == 0) { printf("No ROM information found for header hash: %s\n", md5_hex); + } - // Calculate boot ROM checksum - for (uint32_t i = 0x40 / sizeof(uint32_t); i < (buf[0x3b] == 'Z' ? 0xc00 : 0x1000) / sizeof(uint32_t); i++) - bootcode_sum += ((uint32_t*)buf)[i]; + memcpy(controller_settings, &buf[0x34], sizeof(controller_settings)); + calc_bootcode_checksums(bootcode_sums, buf); - /* The first byte (starting at 0x3b) indicates the type of ROM - * 'N' = cart - * 'D' = 64DD disk - * 'C' = cartridge part of expandable game - * 'E' = 64DD expansion for cart - * 'Z' = Aleck64 cart - * The second and third byte form a 2-letter ID for the game - * The fourth byte indicates the region and language for the game - * The fifth byte indicates the revision of the game */ + /* The first byte (starting at 0x3b) indicates the type of ROM + 'N' = cartridge + 'D' = 64DD disk + 'C' = cartridge part of expandable game + 'E' = 64DD expansion for cart + 'Z' = Aleck64 cart + The 2nd and 3rd byte form a 2-letter ID for the game + The 4th byte indicates the region and language for the game + The 5th byte indicates the revision of the game */ - strncpy(cart_id, (char*)(buf + 0x3b), 4); - sprintf((char*)(cart_id + 4), "%02X", buf[0x3f]); - printf("Cartridge ID: %s\n", cart_id); + auto p_buf = (char *)&buf[0x3b]; + for (auto i = 0; i < 4; i++, p_buf++) { + if (isalnum(*p_buf)) { + cart_id[i] = *p_buf; + } + else { + cart_id[i] = '?'; + } + } + + if (strncmp(cart_id, "????", 4) != 0) { + sprintf(cart_id + 4, "%02X", buf[0x3f]); + printf("Cartridge ID: %s\n", cart_id); + } + else { + memset(cart_id, '\0', CARTID_LENGTH); + } } user_io_file_tx_data(buf, chunk); - if (use_progress) ProgressMessage("Loading", f.name, size - bytes2send, size); - bytes2send -= chunk; + if (use_progress) ProgressMessage("Loading", f.name, data_size - data_left, data_size); + data_left -= chunk; is_first_chunk = false; } + + MD5Final(md5, &ctx); md5_to_hex(md5, md5_hex); - printf("File MD5: %s\n", md5_hex); + printf("File MD5: %s\n", md5_hex); // Try to detect ROM settings from full file MD5 if they're are not detected yet - if (rom_settings_detected == 0) + if (rom_settings_detected == 0) { rom_settings_detected = detect_rom_settings_in_dbs_with_md5(md5_hex); + } // Try to detect ROM settings by cart ID if they're are not detected yet if (rom_settings_detected == 0) { printf("No ROM information found for file hash: %s\n", md5_hex); rom_settings_detected = detect_rom_settings_in_dbs_with_cartid(cart_id); - if (rom_settings_detected == 0) - printf("No ROM information found for cart ID: %s\n", cart_id); - // Try detect (partial) ROM settings by analyzing the ROM itself. (System region and CIC) + if (rom_settings_detected == 0) { + // "Advanced" Homebrew ROM Header https://n64brew.dev/wiki/ROM_Header + if (detect_homebrew_header(controller_settings, cart_id)) { + rom_settings_detected = 2; + } + else { + printf("No ROM information found for Cart ID: %s\n", cart_id); + if (is_auto()) { + // Defaulting misc. System Settings + // auto is_wide = user_io_status_get(AR_OPT) == 1; + // if (is_wide) user_io_status_set(AR_TYPE_OPT, 0); // Resetting Aspect Ratio to Original + user_io_status_set(NO_EPAK_OPT, 0); // Enable Expansion Pak + user_io_status_set(CPAK_OPT, 1); // Enable Controller Pak (?) + user_io_status_set(RPAK_OPT, 0); // Disable Rumble Pak + user_io_status_set(TPAK_OPT, 0); // Disable Transfer Pak + user_io_status_set(RTC_OPT, 0); // Disable RTC + set_save_type(MemoryType::EEPROM_512); // Use the most common save type (?) + } + } + } + if ((rom_settings_detected == 0 || rom_settings_detected == 2) && - detect_rom_settings_from_first_chunk(cart_id[3], bootcode_sum)) + detect_rom_settings_from_first_chunk(cart_id[3], bootcode_sums)) { + // Try detect (partial) ROM settings by analyzing the ROM itself. (System region and CIC) rom_settings_detected |= 1; + } } - // Complement info found in DB with System region and CIC - else if (rom_settings_detected == 2 && - detect_rom_settings_from_first_chunk(cart_id[3], bootcode_sum)) { + else if (rom_settings_detected == 2 && + detect_rom_settings_from_first_chunk(cart_id[3], bootcode_sums)) { + // Complement info found in DB with System region and CIC rom_settings_detected = 3; } @@ -552,12 +888,53 @@ int n64_rom_tx(const char* name, unsigned char index) { // Signal end of transmission user_io_set_download(0); + ProgressMessage(0, 0, 0, 0); - if (rom_settings_detected == 0 || rom_settings_detected == 2) - Info("Auto-detect failed:\nUnknown CIC type."); - else if (rom_settings_detected == 1) - Info("Auto-detect failed:\nROM missing from database,\nyou might not be able to save.", 5000); + if (!is_auto()) { + return 1; + } + + auto system_type = SystemType::UNKNOWN; + auto cic = CIC::UNKNOWN; + + char info[256]; + size_t len = sprintf(info, "Auto-detect:"); + if (strlen(cart_id) && ((cart_id[1] != 'E') || (cart_id[2] != 'D'))) len += sprintf(info + len, "\n[%.4s] v.%u.%u", cart_id, hex_to_dec(cart_id[4]) + 1, hex_to_dec(cart_id[5])); + if (strlen(internal_name)) len += sprintf(info + len, "\n\"%s\"", internal_name); + if ((rom_settings_detected & 1) == 0) { + len += sprintf(info + len, "\nUnknown Region/CIC"); + } + else { + system_type = (SystemType)user_io_status_get(SYS_TYPE_OPT); + cic = (CIC)user_io_status_get(CIC_TYPE_OPT); + len += sprintf(info + len, "\nRegion: %s (%s)", stringify(system_type), stringify(cic)); + } + + if ((rom_settings_detected & 2) == 0) { + sprintf(info + len, "\nROM missing from database.\nYou might not be able to save."); + + Info(info, 4000); + } + else { + auto save_type = get_save_type(); + auto no_epak = (bool)user_io_status_get(NO_EPAK_OPT); + auto cpak = (bool)user_io_status_get(CPAK_OPT); + auto rpak = (bool)user_io_status_get(RPAK_OPT); + auto tpak = (bool)user_io_status_get(TPAK_OPT); + auto rtc = (bool)user_io_status_get(RTC_OPT); + + if (save_type != MemoryType::NONE) len += sprintf(info + len, "\nSave Type: %s", stringify(save_type)); + if (cpak) len += sprintf(info + len, "\nController Pak \x96"); + if (rpak) len += sprintf(info + len, "\nRumble Pak \x96"); + if (tpak) len += sprintf(info + len, "\nTransfer Pak \x96"); + if (rtc) len += sprintf(info + len, "\nRTC \x96"); + if (no_epak) sprintf(info + len, "\nDisable Expansion Pak \x96"); + + Info(info, 6000); + } + + printf("%s\n", info); return 1; } diff --git a/support/n64/n64.h b/support/n64/n64.h index c98bfef..8b66bc6 100644 --- a/support/n64/n64.h +++ b/support/n64/n64.h @@ -1 +1,8 @@ +#ifndef N64_H +#define N64_H + +#include + int n64_rom_tx(const char* name, unsigned char index); + +#endif \ No newline at end of file