Improve N64 ROM Database (#30)

<!--- Provide a general summary of your changes in the Title above -->

## Description
* Add documentation
* Add all header types.
* Improve fread sizeof types for maintainability.
* Update functions as needed.

## Motivation and Context
<!--- What does this sample do? What problem does it solve? -->
<!--- If it fixes/closes/resolves an open issue, please link to the
issue here -->
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?
<!-- (if applicable) -->
<!--- Please describe in detail how you tested your sample/changes. -->
<!--- Include details of your testing environment, and the tests you ran
to -->
<!--- see how your change affects other areas of the code, etc. -->

## Screenshots
<!-- (if appropriate): -->

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [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:
<!--- Go over all the following points, and put an `x` in all the boxes
that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->
- [ ] 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.

<!--- It would be nice if you could sign off your contribution by
replacing the name with your GitHub user name and GitHub email contact.
-->
Signed-off-by: GITHUB_USER <GITHUB_USER_EMAIL>
This commit is contained in:
Robin Jones 2023-07-31 11:08:19 +01:00 committed by GitHub
parent ae314c1411
commit eb9668c721
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 166 additions and 45 deletions

View File

@ -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++) {

View File

@ -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 <stdint.h>
// 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

View File

@ -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);

View File

@ -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