mirror of
https://github.com/Polprzewodnikowy/N64FlashcartMenu.git
synced 2024-11-21 18:19:19 +01:00
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:
parent
ae314c1411
commit
eb9668c721
@ -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++) {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user