From eb9668c721ba8d71a16a1f12fb5f39e08f7fe33d Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 31 Jul 2023 11:08:19 +0100 Subject: [PATCH] Improve N64 ROM Database (#30) ## Description * Add documentation * Add all header types. * Improve fread sizeof types for maintainability. * Update functions as needed. ## Motivation and Context Maintainability and loading. As a bonus, this adds certain info to the "file info" screen, which unfortunately shows some info is incorrect in the N64 Brew wiki when interigating certain ROM's. ## How Has This Been Tested? ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- src/menu/rom_database.c | 50 ++++++++----- src/menu/rom_database.h | 144 +++++++++++++++++++++++++++++++------ src/menu/views/file_info.c | 13 +++- src/menu/views/load.c | 4 +- 4 files changed, 166 insertions(+), 45 deletions(-) diff --git a/src/menu/rom_database.c b/src/menu/rom_database.c index cc8c43d0..2bedb25f 100644 --- a/src/menu/rom_database.c +++ b/src/menu/rom_database.c @@ -9,6 +9,9 @@ uint8_t extract_homebrew_setting(uint8_t setting, uint8_t bit_position) { return (setting & (1 << bit_position)) ? 1 : 0; } +/** + * @brief Normalize the Homebrew save type. + */ uint8_t extract_homebrew_save_type(uint8_t save_type) { switch (save_type) { case HB_SAVE_TYPE_NONE: @@ -30,6 +33,10 @@ uint8_t extract_homebrew_save_type(uint8_t save_type) { } } + +/** + * @brief Reads the N64 ROM header from a file. @see https://n64brew.dev/wiki/ROM_Header + */ rom_header_t file_read_rom_header(char *path) { char *sd_path = calloc(4 + strlen(path) + 1, sizeof(char)); sprintf(sd_path, "sd:/%s", path); @@ -43,30 +50,35 @@ rom_header_t file_read_rom_header(char *path) { rom_header_t *rom_header = malloc(sizeof(rom_header_t)); - //Rom File Info - // CheckCode 0x10, 8 bytes (sometimes refered to as CRC Hi and CRC Lo) - // GameTitle 0x20, 20 bytes - // GameCode -> - // CategoryCode 0x3b - // UniqueCode 0x3c and 0x3d - // DestinationCode 0x3e - // RomVersion 0x3f - fseek(fp, 0x00, SEEK_SET); - fread(&(rom_header->endian), sizeof(uint32_t), 1, fp); + fread(&(rom_header->config_flags), sizeof(rom_header->config_flags), 1, fp); + // FIXME: handle endian appropriately, perhaps: cart_card_byteswap + + fseek(fp, 0x04, SEEK_SET); + fread(&(rom_header->clock_rate), sizeof(rom_header->clock_rate), 1, fp); + fseek(fp, 0x08, SEEK_SET); + fread(&(rom_header->boot_address), sizeof(rom_header->boot_address), 1, fp); + fseek(fp, 0x0C, SEEK_SET); + fread(&(rom_header->sdk_version), sizeof(rom_header->sdk_version), 1, fp); fseek(fp, 0x10, SEEK_SET); - fread(&(rom_header->checksum), sizeof(uint64_t), 1, fp); + fread(&(rom_header->checksum), sizeof(rom_header->checksum), 1, fp); + fseek(fp, 0x18, SEEK_SET); + fread(&(rom_header->unknown_reserved_1), sizeof(rom_header->unknown_reserved_1), 1, fp); fseek(fp, 0x20, SEEK_SET); - fgets(rom_header->title, sizeof(rom_header->title), fp); - fseek(fp, 0x3b, SEEK_SET); + fgets(rom_header->title, sizeof(rom_header->title), fp); + fseek(fp, 0x34, SEEK_SET); + fread(&(rom_header->unknown_reserved_2), sizeof(rom_header->unknown_reserved_2), 1, fp); + fseek(fp, 0x3B, SEEK_SET); fread(&(rom_header->metadata.media_type), sizeof(rom_header->metadata.media_type), 1, fp); - //fseek(fp, 0x3c, SEEK_SET); // Consecutive read (no need to seek). + fseek(fp, 0x3C, SEEK_SET); fread(&(rom_header->metadata.unique_identifier), sizeof(rom_header->metadata.unique_identifier), 1, fp); - //fseek(fp, 0x3e, SEEK_SET); // Consecutive read (no need to seek). + fseek(fp, 0x3E, SEEK_SET); fread(&(rom_header->metadata.destination_market), sizeof(rom_header->metadata.destination_market), 1, fp); - //fseek(fp, 0x3f, SEEK_SET); // Consecutive read (no need to seek). + fseek(fp, 0x3F, SEEK_SET); fread(&(rom_header->version), sizeof(rom_header->version), 1, fp); + fseek(fp, 0x40, SEEK_SET); + fread(&(rom_header->ipl3_boot_code), sizeof(rom_header->ipl3_boot_code), 1, fp); fclose(fp); @@ -198,10 +210,10 @@ uint8_t rom_db_match_save_type(rom_header_t rom_header) { 0x10, 0x10, 0x10, 0x10, 0x10, // Last entry. - 0xff + 0xFF }; - for (int i = 0; save_types[i] != 0xff; i++) { + for (int i = 0; save_types[i] != 0xFF; i++) { if (rom_header.metadata.unique_identifier == *(uint16_t *) cart_ids[i]) { return save_types[i]; @@ -255,7 +267,7 @@ uint8_t rom_db_match_expansion_pak(rom_header_t rom_header) { 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // Last entry. - 0xff + 0xFF }; for (int i = 0; exp_types[i] != 0xff; i++) { diff --git a/src/menu/rom_database.h b/src/menu/rom_database.h index 8d3d3d54..5fff49c5 100644 --- a/src/menu/rom_database.h +++ b/src/menu/rom_database.h @@ -1,6 +1,7 @@ /** * @file rom_database.h - * @brief ROM Database + * @brief N64 ROM Database. + * @note Only works with N64 ROM's by checking the first 1024 bytes of the file. * @ingroup menu */ @@ -10,118 +11,219 @@ #include -// NOTE: these values are independent of flashcart / OS -// But by default align to SC64. +/** + * @brief ROM database save type enumeration. + * @note These values are independent of flashcart / OS + * but by default align to SC64. +*/ typedef enum { + /** @brief The ROM has no save type. */ DB_SAVE_TYPE_NONE = 0x00, + /** @brief The ROM uses EEPROM 4K saves. */ DB_SAVE_TYPE_EEPROM_4K = 0x01, + /** @brief The ROM uses EEPROM 16K saves. */ DB_SAVE_TYPE_EEPROM_16K = 0x02, + /** @brief The ROM uses SRAM saves. */ DB_SAVE_TYPE_SRAM = 0x03, + /** @brief The ROM uses SRAM Banked saves. */ DB_SAVE_TYPE_SRAM_BANKED = 0x04, + /** @brief The ROM uses SRAM 128K saves @note This is not supported by all flashcarts. */ DB_SAVE_TYPE_SRAM_128K = 0x05, + /** @brief The ROM uses FLASHRAM saves. */ DB_SAVE_TYPE_FLASHRAM = 0x06, + /** @brief The ROM uses CPAK saves @note This must be handled by user code. */ DB_SAVE_TYPE_CPAK = 0x10, + /** @brief The ROM uses Disk Drive saves @note This is not supported by all flashcarts. */ DB_SAVE_TYPE_DD = 0x20, - DB_SAVE_TYPE_INVALID = 0xff, + /** @brief The ROM uses Disk Drive conversion saves @note This must be handled by user code. */ + DB_SAVE_TYPE_DD_CONVERSION = 0x30, + /** @brief The ROM uses a save type that was not recognised. */ + DB_SAVE_TYPE_INVALID = 0xFF, } db_savetype_t; -/** @brief ROM System Memory requirements enumeration */ + +/** @brief ROM system memory requirements enumeration. */ typedef enum { - /** @brief The ROM is happy with 4MB of memory */ + /** @brief The ROM is happy with 4MB of memory. */ DB_MEMORY_EXPANSION_NONE = 0x00, - /** @brief The ROM requires 8MB of memory */ + /** @brief The ROM requires 8MB of memory. */ DB_MEMORY_EXPANSION_REQUIRED = 0x01, + /** @brief The ROM recommends 8MB of memory. */ DB_MEMORY_EXPANSION_RECOMMENDED = 0x02, + /** @brief The ROM suggests 8MB of memory. */ DB_MEMORY_EXPANSION_SUGGESTED = 0x03, - /** @brief The ROM is faulty when using 8MB of memory */ + /** @brief The ROM is faulty when using 8MB of memory. */ DB_MEMORY_EXPANSION_FAULTY = 0x04, } rom_memorytype_t; -/** @brief N64 ROM Homebrew save type enumeration */ +/** + * @brief ROM homebrew save type enumeration. + * @note These align to the Krikzz ED64 save types and are generally accepted + * by all emulators. + * +*/ typedef enum { + /** @brief The ROM has no save type. */ HB_SAVE_TYPE_NONE = 0x00, + /** @brief The ROM uses EEPROM 4K saves. */ HB_SAVE_TYPE_EEPROM_4K = 0x01, + /** @brief The ROM uses EEPROM 16K saves. */ HB_SAVE_TYPE_EEPROM_16K = 0x02, + /** @brief The ROM uses SRAM saves. */ HB_SAVE_TYPE_SRAM = 0x03, + /** @brief The ROM uses SRAM Banked saves. */ HB_SAVE_TYPE_SRAM_BANKED = 0x04, + /** @brief The ROM uses FLASHRAM saves. */ HB_SAVE_TYPE_FLASHRAM = 0x05, + /** @brief The ROM uses SRAM 128K saves @note This is not supported by all flashcarts. */ HB_SAVE_TYPE_SRAM_128K = 0x06, } homebrew_savetype_t; -/** @brief N64 ROM endian enumeration */ +/** + * @brief ROM file endian enumeration. + * + * @note this is a hack used for checking ROM's against expected Big Endian + * when reading from the file system. + */ typedef enum { + /** @brief Big Endian ROM */ ROM_BIG_ENDIAN = 0x80371240, + /** @brief Little Endian ROM */ ROM_LITTLE_ENDIAN = 0x40123780, + /** @brief Mid Big Endian ROM */ ROM_MID_BIG_ENDIAN = 0x37804012, + /** @brief Mid Little Endian ROM */ ROM_MID_LITTLE_ENDIAN = 0x12408037, + /** @brief Big Endian IPL ROM */ IPL_BIG_ENDIAN = 0x80270740, } rom_endian_type_t; -/** @brief N64 ROM media type enumeration */ +/** @brief ROM media type enumeration. */ typedef enum { + /** @brief Is a stand alone Cartridge program. */ N64_CART = 'N', + /** @brief Is a stand alone Disk Drive program. */ N64_DISK = 'D', + /** @brief Is a Cartridge program that could use an extra Disk Drive program to expand its capabilities. */ N64_CART_EXPANDABLE = 'C', + /** @brief Is a Disk Drive program that could use an extra Cartridge program to expand its capabilities. */ N64_DISK_EXPANDABLE = 'E', + /** @brief Is an Aleck64 program. */ N64_ALECK64 = 'Z' } rom_media_type_t; -/** @brief N64 ROM market type enumeration */ +/** @brief ROM market type enumeration. */ typedef enum { + /** @brief The ROM is designed for all regions. */ MARKET_ALL = 'A', + /** @brief The ROM is designed for Brazil (probably PAL-M). */ MARKET_BRAZIL = 'B', + /** @brief The ROM is designed for China (probably PAL-D). */ MARKET_CHINA = 'C', + /** @brief The ROM is designed for Germany (probably PAL). */ MARKET_GERMANY = 'D', + /** @brief The ROM is designed for USA. */ MARKET_USA = 'E', + /** @brief The ROM is designed for France (probably PAL). */ MARKET_FRANCE = 'F', + /** @brief The ROM is designed for a NTSC Gateway 64. */ MARKET_GATEWAY64_NTSC = 'G', + /** @brief The ROM is designed for Netherlands (probably PAL). */ MARKET_NETHERLANDS = 'H', + /** @brief The ROM is designed for Italy (probably PAL). */ MARKET_ITALY = 'I', + /** @brief The ROM is designed for Japan. */ MARKET_JAPAN = 'J', + /** @brief The ROM is designed for Korea. */ MARKET_KOREA = 'K', + /** @brief The ROM is designed for a PAL Gateway 64. */ MARKET_GATEWAY64_PAL = 'L', // MARKET_UNKNOWN_M = 'M', + /** @brief The ROM is designed for Canada. */ MARKET_CANADA = 'N', // MARKET_UNKNOWN_O = 'O', + /** @brief The ROM is designed for all PAL regions. */ MARKET_PAL_GENERIC = 'P', // MARKET_UNKNOWN_Q = 'Q', // MARKET_UNKNOWN_R = 'R', + /** @brief The ROM is designed for Spain (probably PAL). */ MARKET_SPAIN = 'S', // MARKET_UNKNOWN_T = 'T', + /** @brief The ROM is designed for Australia (probably PAL). */ MARKET_AUSTRAILA = 'U', // MARKET_UNKNOWN_V = 'V', + /** @brief The ROM is designed for Scandinavia. */ MARKET_SCANDINAVAIA = 'W', + /** @brief The ROM is designed for a PAL market (just unsure which and why). */ MARKET_PAL_X = 'X', + /** @brief The ROM is designed for a PAL market (just unsure which and why). */ MARKET_PAL_Y = 'Y', + /** @brief The ROM is designed for a PAL market (just unsure which and why). */ MARKET_PAL_Z = 'Z' } rom_destination_market_t; -/** @brief N64 ROM Metadata Structure */ +/** + * @brief ROM Metadata Structure + * @note This information is derived from the ROM header. i.e. + * 0x3B = Media Type + * 0x3C and 0x3D = Unique Identifier + * 0x3E = Destination Market + */ typedef struct { - uint8_t media_type; // rom_media_type_t + rom_media_type_t media_type; uint16_t unique_identifier; uint8_t destination_market; // rom_destination_market_t } rom_metadata_t; -/** @brief N64 ROM Header Structure */ +/** + * @brief ROM Header Structure + * @note This information is derived from the ROM header. @see https://n64brew.dev/wiki/ROM_Header + */ typedef struct { - /** @brief The N64 ROM file endian */ - uint32_t endian; // rom_endian_type_t - /** @brief The N64 ROM file checksum */ + /** @brief The ROM configuration flags @note we currently use this to work out the endian @see rom_endian_type_t. */ + uint32_t config_flags; + + /** @brief The ROM file clock rate. */ + uint32_t clock_rate; + /** @brief The ROM file boot address. */ + uint32_t boot_address; + /** @brief The ROM file SDK version. */ + uint32_t sdk_version; + + /** @brief The ROM file checksum. */ uint64_t checksum; - /** @brief The N64 ROM file title */ + + /** @brief The ROM file unknown reserved region at 0x18. */ + uint64_t unknown_reserved_1; + + /** @brief The ROM file title */ char title[21]; // 20 chars + null - /** @brief The N64 ROM file metadata */ + + /** @brief The ROM file unknown reserved region at 0x34. */ + char unknown_reserved_2[7]; + + /** @brief The ROM file metadata @see rom_metadata_t. */ rom_metadata_t metadata; - /** @brief The N64 ROM file version */ + /** @brief The ROM file release version. */ uint8_t version; + + char ipl3_boot_code[0xFC0]; + } rom_header_t; +#ifdef __cplusplus +extern "C" { +#endif + rom_header_t file_read_rom_header(char *path); uint8_t rom_db_match_save_type(rom_header_t rom_header); uint8_t rom_db_match_expansion_pak(rom_header_t rom_header); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/menu/views/file_info.c b/src/menu/views/file_info.c index e9a8f4aa..654dac32 100644 --- a/src/menu/views/file_info.c +++ b/src/menu/views/file_info.c @@ -258,8 +258,8 @@ static void menu_fileinfo_draw_n64_rom_info(surface_t *d, layout_t *layout) { ); text_y += fragment_textf(text_x, text_y, "\n"); - text_y += fragment_textf(text_x, text_y, "N64 ROM Information:\n\n"); - text_y += fragment_textf(text_x, text_y, " Endian: %s\n", format_rom_endian(rom_header.endian)); + text_y += fragment_textf(text_x, text_y, "N64 ROM Information:\n"); + text_y += fragment_textf(text_x, text_y, " Endian: %s\n", format_rom_endian(rom_header.config_flags)); text_y += fragment_textf(text_x, text_y, " Title: %s\n", rom_header.title); text_y += fragment_textf(text_x, text_y, " Media Type: %c - %s\n", rom_header.metadata.media_type, format_rom_media_type(rom_header.metadata.media_type)); text_y += fragment_textf(text_x, text_y, " Unique ID: %.2s\n", (char*)&(rom_header.metadata.unique_identifier)); @@ -267,7 +267,14 @@ static void menu_fileinfo_draw_n64_rom_info(surface_t *d, layout_t *layout) { text_y += fragment_textf(text_x, text_y, " Version: %hhu\n", rom_header.version); text_y += fragment_textf(text_x, text_y, " Checksum: 0x%016llX\n", rom_header.checksum); text_y += fragment_textf(text_x, text_y, " Save Type: %s\n", format_rom_save_type(rom_db_match_save_type(rom_header))); - text_y += fragment_textf(text_x, text_y, " Expansion PAK: %s\n\n", format_rom_memory_type(rom_db_match_expansion_pak(rom_header))); + text_y += fragment_textf(text_x, text_y, " Expansion PAK: %s\n", format_rom_memory_type(rom_db_match_expansion_pak(rom_header))); + // TODO: Should Extra Info be optional. + text_y += fragment_textf(text_x, text_y, " Extra Info:"); + if ((rom_header.clock_rate & 0xFFFFFFF0) != 0) { + text_y += fragment_textf(text_x, text_y, " Clock Rate?!: %d.%02dMHz\n", (rom_header.clock_rate & 0xFFFFFFF0) /1000000, (rom_header.clock_rate & 0xFFFFFFF0) % 1000000); + } + text_y += fragment_textf(text_x, text_y, " Boot address: 0x%08lX\n", rom_header.boot_address); + text_y += fragment_textf(text_x, text_y, " SDK Version: %d\n", rom_header.sdk_version); if (boxart_image) { uint16_t x = (640 - 150) - (boxart_image->width / 2); diff --git a/src/menu/views/load.c b/src/menu/views/load.c index 91835ace..369ccf4a 100644 --- a/src/menu/views/load.c +++ b/src/menu/views/load.c @@ -160,7 +160,7 @@ static void load (menu_t *menu) { path_t *path = path_clone(menu->browser.directory); path_push(path, menu->browser.list[menu->browser.selected].name); - bool byte_swap = (rom_header.endian == ROM_MID_BIG_ENDIAN); + bool byte_swap = (rom_header.config_flags == ROM_MID_BIG_ENDIAN); menu->flashcart_error = flashcart_load_rom(path_get(path), byte_swap); if (menu->flashcart_error != FLASHCART_OK) { menu->next_mode = MENU_MODE_FAULT; @@ -225,7 +225,7 @@ static void draw (menu_t *menu, surface_t *d) { text_y += fragment_textf(text_x, text_y, "Destination market: %c - %s", rom_header.metadata.destination_market, format_rom_destination_market(rom_header.metadata.destination_market)); text_y += fragment_textf(text_x, text_y, "Version: %hhu", rom_header.version); text_y += fragment_textf(text_x, text_y, "Checksum: 0x%016llX", rom_header.checksum); - text_y += fragment_textf(text_x, text_y, "ROM endian: %s", format_rom_endian(rom_header.endian)); + text_y += fragment_textf(text_x, text_y, "ROM endian: %s", format_rom_endian(rom_header.config_flags)); text_y += fragment_textf(text_x, text_y, "Expansion PAK: %s", format_rom_memory_type(rom_db_match_expansion_pak(rom_header))); // Actions bar