From 94ff9347e3c625cba9d6c1bc11906f651860a3db Mon Sep 17 00:00:00 2001 From: Loloseiz Date: Tue, 5 May 2026 14:42:56 +0200 Subject: [PATCH 1/3] Add virtual memory map for Wii U --- src/rcheevos/consoleinfo.c | 14 ++++++++++++++ test/rcheevos/test_consoleinfo.c | 1 + 2 files changed, 15 insertions(+) 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/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); From f6a43d7dc5bb48db846b70f12166f404d81e5e8e Mon Sep 17 00:00:00 2001 From: Loloseiz Date: Tue, 5 May 2026 16:16:17 +0200 Subject: [PATCH 2/3] Add ELF parser and hashing for RPX files --- src/rhash/hash.c | 4 +++ src/rhash/hash_rom.c | 24 +++++++++++++ src/rhash/rc_hash_internal.h | 1 + test/rhash/test_hash_rom.c | 68 ++++++++++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+) diff --git a/src/rhash/hash.c b/src/rhash/hash.c index fe6f3ca9..a2ac5c57 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(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..8216ac45 100644 --- a/src/rhash/hash_rom.c +++ b/src/rhash/hash_rom.c @@ -424,3 +424,27 @@ int rc_hash_snes(char hash[33], const rc_hash_iterator_t* iterator) return rc_hash_iterator_buffer(hash, iterator); } + +int rc_hash_wiiu(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); + + if (buffer[0] != 0x7F || buffer[1] != 'E' || buffer[2] != 'L' || buffer[3] != 'F' || + buffer[5] != 0x02 || 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..e91a92b1 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(char hash[33], const rc_hash_iterator_t* iterator); #endif #ifndef RC_HASH_NO_DISC diff --git a/test/rhash/test_hash_rom.c b/test/rhash/test_hash_rom.c index 323dda2b..5b42584a 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_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_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"); From c503936ce0a821d84ffa992aa33a91b0ce9aa103 Mon Sep 17 00:00:00 2001 From: Loloseiz Date: Mon, 18 May 2026 16:30:40 +0200 Subject: [PATCH 3/3] Fixed the issues stated in PR comments --- src/rhash/hash.c | 2 +- src/rhash/hash_rom.c | 16 +++++++++++++--- src/rhash/rc_hash_internal.h | 2 +- test/rhash/test_hash_rom.c | 4 ++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/rhash/hash.c b/src/rhash/hash.c index a2ac5c57..84fbb464 100644 --- a/src/rhash/hash.c +++ b/src/rhash/hash.c @@ -897,7 +897,7 @@ static int rc_hash_from_file(char hash[33], uint32_t console_id, const rc_hash_i return rc_hash_n64(hash, iterator); case RC_CONSOLE_WII_U: - return rc_hash_wiiu(hash, iterator); + return rc_hash_wiiu_rpx(hash, iterator); #endif #ifndef RC_HASH_NO_ENCRYPTED diff --git a/src/rhash/hash_rom.c b/src/rhash/hash_rom.c index 8216ac45..619bbd5d 100644 --- a/src/rhash/hash_rom.c +++ b/src/rhash/hash_rom.c @@ -425,7 +425,7 @@ int rc_hash_snes(char hash[33], const rc_hash_iterator_t* iterator) return rc_hash_iterator_buffer(hash, iterator); } -int rc_hash_wiiu(char hash[33], const rc_hash_iterator_t* iterator) +int rc_hash_wiiu_rpx(char hash[33], const rc_hash_iterator_t* iterator) { uint8_t buffer[52]; void* file_handle; @@ -441,8 +441,18 @@ int rc_hash_wiiu(char hash[33], const rc_hash_iterator_t* iterator) rc_file_close(iterator, file_handle); - if (buffer[0] != 0x7F || buffer[1] != 'E' || buffer[2] != 'L' || buffer[3] != 'F' || - buffer[5] != 0x02 || buffer[18] != 0x14) { + /* 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"); } diff --git a/src/rhash/rc_hash_internal.h b/src/rhash/rc_hash_internal.h index e91a92b1..c3fff77b 100644 --- a/src/rhash/rc_hash_internal.h +++ b/src/rhash/rc_hash_internal.h @@ -78,7 +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(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/rhash/test_hash_rom.c b/test/rhash/test_hash_rom.c index 5b42584a..576cdb6e 100644 --- a/test/rhash/test_hash_rom.c +++ b/test/rhash/test_hash_rom.c @@ -740,7 +740,7 @@ static void test_hash_wiiu_rpx() ASSERT_STR_EQUALS(hash_iterator, expected_md5); } -static void test_hash_wiiu_invalid() +static void test_hash_wiiu_rpx_invalid() { size_t image_size = 1024; uint8_t* image = generate_generic_file(image_size); @@ -957,7 +957,7 @@ void test_hash_rom(void) { /* Wii U */ TEST(test_hash_wiiu_rpx); - TEST(test_hash_wiiu_invalid); + TEST(test_hash_wiiu_rpx_invalid); /* WonderSwan */ TEST_PARAMS4(test_hash_full_file, RC_CONSOLE_WONDERSWAN, "test.ws", 524288, "68f0f13b598e0b66461bc578375c3888");