diff --git a/src/rcheevos/consoleinfo.c b/src/rcheevos/consoleinfo.c index aeecb38d..e279b335 100644 --- a/src/rcheevos/consoleinfo.c +++ b/src/rcheevos/consoleinfo.c @@ -976,6 +976,17 @@ static const rc_memory_region_t _rc_memory_regions_wii[] = { }; static const rc_memory_regions_t rc_memory_regions_wii = { _rc_memory_regions_wii, 3 }; +/* ===== Wii U ===== */ +/* https://wiiubrew.org/wiki/Memory_map */ +static const rc_memory_region_t _rc_memory_regions_wii_u[] = { + { 0x00000000U, 0x00FFFFFFU, 0x00000000U, RC_MEMORY_TYPE_UNUSED, "Unused" }, + { 0x01000000U, 0x0FFFFFFFU, 0x01000000U, RC_MEMORY_TYPE_READONLY, "Code / Heap" }, + { 0x10000000U, 0x8FFFFFFFU, 0x10000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "MEM2" }, + { 0x90000000U, 0xF3FFFFFFU, 0x90000000U, RC_MEMORY_TYPE_UNUSED, "Unused" }, + { 0xF4000000U, 0xF5FFFFFFU, 0xF4000000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "MEM1" } +}; +static const rc_memory_regions_t rc_memory_regions_wii_u = { _rc_memory_regions_wii_u, 5 }; + /* ===== WonderSwan ===== */ /* http://daifukkat.su/docs/wsman/#ovr_memmap */ static const rc_memory_region_t _rc_memory_regions_wonderswan[] = { @@ -1203,6 +1214,9 @@ const rc_memory_regions_t* rc_console_memory_regions(uint32_t console_id) case RC_CONSOLE_WII: return &rc_memory_regions_wii; + case RC_CONSOLE_WII_U: + return &rc_memory_regions_wii_u; + case RC_CONSOLE_WONDERSWAN: return &rc_memory_regions_wonderswan; diff --git a/src/rhash/hash.c b/src/rhash/hash.c index fe6f3ca9..84fbb464 100644 --- a/src/rhash/hash.c +++ b/src/rhash/hash.c @@ -895,6 +895,9 @@ static int rc_hash_from_file(char hash[33], uint32_t console_id, const rc_hash_i #ifndef RC_HASH_NO_ROM case RC_CONSOLE_NINTENDO_64: return rc_hash_n64(hash, iterator); + + case RC_CONSOLE_WII_U: + return rc_hash_wiiu_rpx(hash, iterator); #endif #ifndef RC_HASH_NO_ENCRYPTED @@ -1239,6 +1242,7 @@ static const rc_hash_iterator_ext_handler_entry_t rc_hash_iterator_ext_handlers[ { "pzx", rc_hash_initialize_iterator_single, RC_CONSOLE_ZX_SPECTRUM }, { "ri", rc_hash_initialize_iterator_single, RC_CONSOLE_MSX }, { "rom", rc_hash_initialize_iterator_rom, 0 }, + { "rpx", rc_hash_initialize_iterator_single, RC_CONSOLE_WII_U }, { "sap", rc_hash_initialize_iterator_single, RC_CONSOLE_THOMSONTO8 }, /* disk */ { "scl", rc_hash_initialize_iterator_single, RC_CONSOLE_ZX_SPECTRUM }, { "sfc", rc_hash_initialize_iterator_single, RC_CONSOLE_SUPER_NINTENDO }, diff --git a/src/rhash/hash_rom.c b/src/rhash/hash_rom.c index 996c3080..619bbd5d 100644 --- a/src/rhash/hash_rom.c +++ b/src/rhash/hash_rom.c @@ -424,3 +424,37 @@ int rc_hash_snes(char hash[33], const rc_hash_iterator_t* iterator) return rc_hash_iterator_buffer(hash, iterator); } + +int rc_hash_wiiu_rpx(char hash[33], const rc_hash_iterator_t* iterator) +{ + uint8_t buffer[52]; + void* file_handle; + + file_handle = rc_file_open(iterator, iterator->path); + if (!file_handle) + return rc_hash_iterator_error(iterator, "Could not open file"); + + if (rc_file_read(iterator, file_handle, buffer, sizeof(buffer)) != sizeof(buffer)) { + rc_file_close(iterator, file_handle); + return rc_hash_iterator_error(iterator, "Could not read header"); + } + + rc_file_close(iterator, file_handle); + + /* Magic Number: 0x7F 'E' 'L' 'F' */ + if (buffer[0] != 0x7F || buffer[1] != 'E' || buffer[2] != 'L' || buffer[3] != 'F') { + return rc_hash_iterator_error(iterator, "Not a valid Wii U RPX file"); + } + + /* Endianness: 0x02 = Big Endian */ + if (buffer[5] != 0x02) { + return rc_hash_iterator_error(iterator, "Not a valid Wii U RPX file"); + } + + /* Machine Architecture: 0x14 = PowerPC */ + if (buffer[18] != 0x14) { + return rc_hash_iterator_error(iterator, "Not a valid Wii U RPX file"); + } + + return rc_hash_whole_file(hash, iterator); +} diff --git a/src/rhash/rc_hash_internal.h b/src/rhash/rc_hash_internal.h index f50a9348..c3fff77b 100644 --- a/src/rhash/rc_hash_internal.h +++ b/src/rhash/rc_hash_internal.h @@ -78,6 +78,7 @@ int rc_hash_buffered_file(char hash[33], uint32_t console_id, const rc_hash_iter int rc_hash_pce(char hash[33], const rc_hash_iterator_t* iterator); int rc_hash_scv(char hash[33], const rc_hash_iterator_t* iterator); int rc_hash_snes(char hash[33], const rc_hash_iterator_t* iterator); + int rc_hash_wiiu_rpx(char hash[33], const rc_hash_iterator_t* iterator); #endif #ifndef RC_HASH_NO_DISC diff --git a/test/rcheevos/test_consoleinfo.c b/test/rcheevos/test_consoleinfo.c index f761016f..7e6cb45b 100644 --- a/test/rcheevos/test_consoleinfo.c +++ b/test/rcheevos/test_consoleinfo.c @@ -194,6 +194,7 @@ void test_consoleinfo(void) { TEST_PARAMS3(test_memory, RC_CONSOLE_UZEBOX, 0x001000, 0x001000); TEST_PARAMS3(test_memory, RC_CONSOLE_WASM4, 0x010000, 0x010000); TEST_PARAMS3(test_memory, RC_CONSOLE_WII, 0x14000000, 0x05800000); + TEST_PARAMS3(test_memory, RC_CONSOLE_WII_U, 0xF6000000, 0x91000000); TEST_PARAMS3(test_memory, RC_CONSOLE_WONDERSWAN, 0x090000, 0x090000); TEST_PARAMS3(test_memory, RC_CONSOLE_VECTREX, 0x000400, 0x000400); TEST_PARAMS3(test_memory, RC_CONSOLE_VIRTUAL_BOY, 0x020000, 0x020000); diff --git a/test/rhash/test_hash_rom.c b/test/rhash/test_hash_rom.c index 323dda2b..576cdb6e 100644 --- a/test/rhash/test_hash_rom.c +++ b/test/rhash/test_hash_rom.c @@ -700,6 +700,70 @@ static void test_hash_scv_cart() /* ========================================================================= */ +static void test_hash_wiiu_rpx() +{ + size_t image_size = 1024 * 1024; + uint8_t* image = generate_generic_file(image_size); + char hash_file[33], hash_iterator[33]; + + /* Found with TDD */ + const char* expected_md5 = "2c0b393597ad45952138f79c7180fc88"; + + image[0] = 0x7F; + image[1] = 'E'; + image[2] = 'L'; + image[3] = 'F'; + image[5] = 0x02; /* Big Endian */ + image[18] = 0x14; /* PowerPC */ + + mock_file(0, "game.rpx", image, image_size); + + /* Test file hash */ + int result_file = rc_hash_generate_from_file(hash_file, RC_CONSOLE_WII_U, "game.rpx"); + + /* Test file identification from iterator */ + int result_iterator; + struct rc_hash_iterator iterator; + + rc_hash_initialize_iterator(&iterator, "game.rpx", NULL, 0); + result_iterator = rc_hash_iterate(hash_iterator, &iterator); + rc_hash_destroy_iterator(&iterator); + + /* Cleanup */ + free(image); + + /* Validation */ + ASSERT_NUM_EQUALS(result_file, 1); + ASSERT_STR_EQUALS(hash_file, expected_md5); + + ASSERT_NUM_EQUALS(result_iterator, 1); + ASSERT_STR_EQUALS(hash_iterator, expected_md5); +} + +static void test_hash_wiiu_rpx_invalid() +{ + size_t image_size = 1024; + uint8_t* image = generate_generic_file(image_size); + char hash_file[33]; + + /* "Corrupted" header */ + image[0] = 0x7F; + image[1] = 'E'; + image[2] = 'L'; + image[3] = 'X'; + image[5] = 0x02; + image[18] = 0x14; + + mock_file(0, "bad.rpx", image, image_size); + + int result_file = rc_hash_generate_from_file(hash_file, RC_CONSOLE_WII_U, "bad.rpx"); + free(image); + + ASSERT_NUM_EQUALS(result_file, 0); +} + +/* ========================================================================= */ + void test_hash_rom(void) { TEST_SUITE_BEGIN(); @@ -891,6 +955,10 @@ void test_hash_rom(void) { /* WASM-4 */ TEST_PARAMS4(test_hash_full_file, RC_CONSOLE_WASM4, "test.wasm", 33454, "bce38bb5f05622fc7e0e56757059d180"); + /* Wii U */ + TEST(test_hash_wiiu_rpx); + TEST(test_hash_wiiu_rpx_invalid); + /* WonderSwan */ TEST_PARAMS4(test_hash_full_file, RC_CONSOLE_WONDERSWAN, "test.ws", 524288, "68f0f13b598e0b66461bc578375c3888"); TEST_PARAMS4(test_hash_full_file, RC_CONSOLE_WONDERSWAN, "test.wsc", 4194304, "a247ec8a8c42e18fcb80702dfadac14b");