From a990c0bca4a2d6de1a19b5de4a7364c4b52643f6 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Sun, 17 Nov 2019 22:47:01 -0700 Subject: [PATCH] add hashing for Nintendo DS --- cheevos-new/cheevos.c | 304 ++++++++++++++++++++++++++++-------------- 1 file changed, 201 insertions(+), 103 deletions(-) diff --git a/cheevos-new/cheevos.c b/cheevos-new/cheevos.c index e9d75b193f..ee99864548 100644 --- a/cheevos-new/cheevos.c +++ b/cheevos-new/cheevos.c @@ -1228,10 +1228,12 @@ enum RCHEEVOS_DEACTIVATE = -14, RCHEEVOS_PLAYING = -15, RCHEEVOS_DELAY = -16, - RCHEEVOS_PCE_CD_MD5 = -17 + RCHEEVOS_PCE_CD_MD5 = -17, + RCHEEVOS_NDS_MD5 = -18, + RCHEEVOS_BUFFER_FILE = -19 }; -static int rcheevos_hash_psx(rcheevos_coro_t* coro) +static int rcheevos_prepare_hash_psx(rcheevos_coro_t* coro) { char exe_name_buffer[64]; size_t exe_name_size; @@ -1355,6 +1357,82 @@ static int rcheevos_hash_psx(rcheevos_coro_t* coro) return success; } +static int rcheevos_prepare_hash_nintendo_ds(rcheevos_coro_t* coro) +{ + intfstream_t* stream; + unsigned char header[512]; + int success = 0; + + stream = intfstream_open_file(coro->path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); + if (stream) + { + if (intfstream_read(stream, header, sizeof(header)) == 512) + { + unsigned int hash_size, arm9_size, arm9_addr, arm7_size, arm7_addr, icon_addr; + int offset = 0; + + if (header[0] == 0x2E && header[1] == 0x00 && header[2] == 0x00 && header[3] == 0xEA && + header[0xB0] == 0x44 && header[0xB1] == 0x46 && header[0xB2] == 0x96 && header[0xB3] == 0x00) + { + /* SuperCard header detected, ignore it */ + offset = 512; + intfstream_seek(stream, offset, RETRO_VFS_SEEK_POSITION_START); + intfstream_read(stream, header, sizeof(header)); + } + + arm9_addr = header[0x20] | (header[0x21] << 8) | (header[0x22] << 16) | (header[0x23] << 24); + arm9_size = header[0x2C] | (header[0x2D] << 8) | (header[0x2E] << 16) | (header[0x2F] << 24); + arm7_addr = header[0x30] | (header[0x31] << 8) | (header[0x32] << 16) | (header[0x33] << 24); + arm7_size = header[0x3C] | (header[0x3D] << 8) | (header[0x3E] << 16) | (header[0x3F] << 24); + icon_addr = header[0x68] | (header[0x69] << 8) | (header[0x6A] << 16) | (header[0x6B] << 24); + + hash_size = 0x160 + arm9_size + arm7_size + 0xA00; + if (hash_size > 16 * 1024 * 1024) + { + CHEEVOS_LOG(RCHEEVOS_TAG "arm9 code size (%u) + arm7 code size (%u) exceeds 16MB", arm9_size, arm7_size); + } + else + { + if (coro->data) + free(coro->data); + + coro->data = malloc(hash_size); + if (!coro->data) + { + CHEEVOS_LOG(RCHEEVOS_TAG "failed to allocate %u bytes", hash_size); + intfstream_close(stream); + CORO_STOP(); + } + else + { + uint8_t* hash_ptr = (uint8_t*)coro->data; + + memcpy(hash_ptr, header, 0x160); + hash_ptr += 0x160; + + intfstream_seek(stream, arm9_addr + offset, RETRO_VFS_SEEK_POSITION_START); + intfstream_read(stream, hash_ptr, arm9_size); + hash_ptr += arm9_size; + + intfstream_seek(stream, arm7_addr + offset, RETRO_VFS_SEEK_POSITION_START); + intfstream_read(stream, hash_ptr, arm7_size); + hash_ptr += arm7_size; + + intfstream_seek(stream, icon_addr + offset, RETRO_VFS_SEEK_POSITION_START); + intfstream_read(stream, hash_ptr, 0xA00); + + coro->len = hash_size; + success = 1; + } + } + } + + intfstream_close(stream); + } + + return success; +} + static int rcheevos_iterate(rcheevos_coro_t* coro) { const int snes_header_len = 0x200; @@ -1422,11 +1500,18 @@ static int rcheevos_iterate(rcheevos_coro_t* coro) 0 }; + static const uint32_t nds_exts[] = + { + 0x00b88942aU, /* nds */ + 0 + }; + static rcheevos_finder_t finders[] = { {RCHEEVOS_SNES_MD5, "SNES (discards header)", snes_exts}, {RCHEEVOS_LYNX_MD5, "Atari Lynx (discards header)", lynx_exts}, {RCHEEVOS_NES_MD5, "NES (discards header)", nes_exts}, + {RCHEEVOS_NDS_MD5, "Nintendo DS (main executables)", nds_exts}, {RCHEEVOS_PSX_MD5, "Playstation (main executable)", psx_exts}, {RCHEEVOS_PCE_CD_MD5, "PC Engine CD (boot sector)", pce_cd_exts}, {RCHEEVOS_SEGACD_MD5, "Sega CD/Saturn (first sector)", segacd_exts}, @@ -1444,60 +1529,6 @@ static int rcheevos_iterate(rcheevos_coro_t* coro) if (!coro->settings->bools.cheevos_enable) CORO_STOP(); - /* Load the content into memory, or copy it - * over to our own buffer */ - if (!coro->data) - { - coro->stream = intfstream_open_file( - coro->path, - RETRO_VFS_FILE_ACCESS_READ, - RETRO_VFS_FILE_ACCESS_HINT_NONE); - - if (!coro->stream) - CORO_STOP(); - - CORO_YIELD(); - coro->len = 0; - coro->count = intfstream_get_size(coro->stream); - - /* size limit */ - if (coro->count > CHEEVOS_MB(64)) - coro->count = CHEEVOS_MB(64); - - coro->data = malloc(coro->count); - - if (!coro->data) - { - intfstream_close(coro->stream); - CHEEVOS_FREE(coro->stream); - CORO_STOP(); - } - - for (;;) - { - ptr = (uint8_t*)coro->data + coro->len; - to_read = 4096; - - if (to_read > coro->count) - to_read = coro->count; - - num_read = intfstream_read(coro->stream, (void*)ptr, to_read); - if (num_read <= 0) - break; - - coro->len += num_read; - coro->count -= num_read; - - if (coro->count == 0) - break; - - CORO_YIELD(); - } - - intfstream_close(coro->stream); - CHEEVOS_FREE(coro->stream); - } - /* Use the selected file's extension to determine which method to use */ for (coro->i = 0; coro->i < ARRAY_SIZE(finders); coro->i++) { @@ -1685,13 +1716,73 @@ found: CORO_STOP(); + /************************************************************************** + * Info Loads a file into memory + * Input coro->path + * Output coro->data, coro->len + *************************************************************************/ + CORO_SUB(RCHEEVOS_BUFFER_FILE) + if (!coro->data) + { + coro->stream = intfstream_open_file( + coro->path, + RETRO_VFS_FILE_ACCESS_READ, + RETRO_VFS_FILE_ACCESS_HINT_NONE); + + if (!coro->stream) + CORO_STOP(); + + CORO_YIELD(); + coro->len = 0; + coro->count = intfstream_get_size(coro->stream); + + /* size limit */ + if (coro->count > CHEEVOS_MB(64)) + coro->count = CHEEVOS_MB(64); + + coro->data = malloc(coro->count); + + if (!coro->data) + { + intfstream_close(coro->stream); + CHEEVOS_FREE(coro->stream); + CORO_STOP(); + } + + for (;;) + { + ptr = (uint8_t*)coro->data + coro->len; + to_read = 8192; + + if (to_read > coro->count) + to_read = coro->count; + + num_read = intfstream_read(coro->stream, (void*)ptr, to_read); + if (num_read <= 0) + break; + + coro->len += num_read; + coro->count -= num_read; + + if (coro->count == 0) + break; + + CORO_YIELD(); + } + + intfstream_close(coro->stream); + CHEEVOS_FREE(coro->stream); + } + CORO_RET(); + + /************************************************************************** * Info Tries to identify a SNES game - * Input CHEEVOS_VAR_INFO the content info - * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found + * Input coro->path or coro->data+coro->len + * Output coro->gameid *************************************************************************/ CORO_SUB(RCHEEVOS_SNES_MD5) - MD5_Init(&coro->md5); + CORO_GOSUB(RCHEEVOS_BUFFER_FILE); /* Checks for the existence of a headered SNES file. Unheadered files fall back to RCHEEVOS_GENERIC_MD5. */ @@ -1706,17 +1797,16 @@ found: coro->count = 0; CORO_GOSUB(RCHEEVOS_EVAL_MD5); - MD5_Final(coro->hash, &coro->md5); - CORO_GOTO(RCHEEVOS_GET_GAMEID); /************************************************************************** * Info Tries to identify an Atari Lynx game - * Input CHEEVOS_VAR_INFO the content info - * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found + * Input coro->path or coro->data+coro->len + * Output coro->gameid *************************************************************************/ CORO_SUB(RCHEEVOS_LYNX_MD5) + CORO_GOSUB(RCHEEVOS_BUFFER_FILE); /* Checks for the existence of a headered Lynx file. Unheadered files fall back to RCHEEVOS_GENERIC_MD5. */ @@ -1728,21 +1818,20 @@ found: CORO_RET(); } - MD5_Init(&coro->md5); coro->offset = lynx_header_len; coro->count = coro->len - lynx_header_len; - CORO_GOSUB(RCHEEVOS_EVAL_MD5); - MD5_Final(coro->hash, &coro->md5); + CORO_GOSUB(RCHEEVOS_EVAL_MD5); CORO_GOTO(RCHEEVOS_GET_GAMEID); /************************************************************************** * Info Tries to identify a NES game - * Input CHEEVOS_VAR_INFO the content info - * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found + * Input coro->path or coro->data+coro->len + * Output coro->gameid *************************************************************************/ CORO_SUB(RCHEEVOS_NES_MD5) + CORO_GOSUB(RCHEEVOS_BUFFER_FILE); /* Checks for the existence of a headered NES file. Unheadered files fall back to RCHEEVOS_GENERIC_MD5. */ @@ -1765,19 +1854,17 @@ found: CORO_RET(); } - MD5_Init(&coro->md5); coro->offset = sizeof(coro->header); coro->count = coro->len - coro->offset; - CORO_GOSUB(RCHEEVOS_EVAL_MD5); - MD5_Final(coro->hash, &coro->md5); + CORO_GOSUB(RCHEEVOS_EVAL_MD5); CORO_GOTO(RCHEEVOS_GET_GAMEID); /************************************************************************** * Info Tries to identify a Sega CD game - * Input CHEEVOS_VAR_INFO the content info - * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found + * Input coro->path, coro->len + * Output coro->gameid *************************************************************************/ CORO_SUB(RCHEEVOS_SEGACD_MD5) { @@ -1789,8 +1876,6 @@ found: CORO_RET(); } - MD5_Init(&coro->md5); - /* find the data track - it should be the first one */ coro->track = cdfs_open_data_track(coro->path); if (coro->track) @@ -1804,14 +1889,12 @@ found: cdfs_read_file(&coro->cdfp, coro->data, coro->count); coro->len = coro->count; - CORO_GOSUB(RCHEEVOS_EVAL_MD5); - MD5_Final(coro->hash, &coro->md5); - cdfs_close_file(&coro->cdfp); cdfs_close_track(coro->track); coro->track = NULL; + CORO_GOSUB(RCHEEVOS_EVAL_MD5); CORO_GOTO(RCHEEVOS_GET_GAMEID); } @@ -1827,13 +1910,11 @@ found: /************************************************************************** * Info Tries to identify a PC Engine CD game - * Input CHEEVOS_VAR_INFO the content info - * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found + * Input coro->path + * Output coro->gameid *************************************************************************/ CORO_SUB(RCHEEVOS_PCE_CD_MD5) { - MD5_Init(&coro->md5); - /* find the data track - it should be the second one */ coro->track = cdfs_open_data_track(coro->path); if (coro->track) @@ -1877,14 +1958,12 @@ found: cdfs_read_file(&coro->cdfp, ((uint8_t*)coro->data) + 22, to_read); coro->len = coro->count; - CORO_GOSUB(RCHEEVOS_EVAL_MD5); - MD5_Final(coro->hash, &coro->md5); - cdfs_close_file(&coro->cdfp); cdfs_close_track(coro->track); coro->track = NULL; + CORO_GOSUB(RCHEEVOS_EVAL_MD5); CORO_GOTO(RCHEEVOS_GET_GAMEID); } @@ -1900,17 +1979,32 @@ found: /************************************************************************** * Info Tries to identify a Playstation game - * Input CHEEVOS_VAR_INFO the content info - * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found + * Input coro->path + * Output coro->gameid *************************************************************************/ CORO_SUB(RCHEEVOS_PSX_MD5) { - if (rcheevos_hash_psx(coro)) + if (rcheevos_prepare_hash_psx(coro)) { - MD5_Init(&coro->md5); CORO_GOSUB(RCHEEVOS_EVAL_MD5); - MD5_Final(coro->hash, &coro->md5); + CORO_GOTO(RCHEEVOS_GET_GAMEID); + } + coro->gameid = 0; + CORO_RET(); + } + + + /************************************************************************** + * Info Tries to identify a Nintendo DS game + * Input coro->path + * Output coro->gameid + *************************************************************************/ + CORO_SUB(RCHEEVOS_NDS_MD5) + { + if (rcheevos_prepare_hash_nintendo_ds(coro)) + { + CORO_GOSUB(RCHEEVOS_EVAL_MD5); CORO_GOTO(RCHEEVOS_GET_GAMEID); } @@ -1921,30 +2015,31 @@ found: /************************************************************************** * Info Tries to identify a game by examining the entire file (no special processing) - * Input CHEEVOS_VAR_INFO the content info - * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found + * Input coro->path or coro->data+coro->len + * Output coro->gameid *************************************************************************/ CORO_SUB(RCHEEVOS_GENERIC_MD5) - - MD5_Init(&coro->md5); + CORO_GOSUB(RCHEEVOS_BUFFER_FILE); coro->offset = 0; coro->count = 0; + CORO_GOSUB(RCHEEVOS_EVAL_MD5); - MD5_Final(coro->hash, &coro->md5); - if (coro->count == 0) + { + coro->gameid = 0; CORO_RET(); + } CORO_GOTO(RCHEEVOS_GET_GAMEID); /************************************************************************** - * Info Tries to identify an arcade game based on its filename (with no extension). - * An arcade game "rom" is a zip file containing many ROMs. - * Input CHEEVOS_VAR_INFO the content info - * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found + * Info Tries to identify an arcade game based on its filename (with no extension). + * An arcade game "rom" is a zip file containing many ROMs. + * Input coro->path + * Output coro->gameid *************************************************************************/ CORO_SUB(RCHEEVOS_ARCADE_MD5) if (!string_is_empty(coro->path)) @@ -1963,8 +2058,8 @@ found: /************************************************************************** * Info Evaluates the CHEEVOS_VAR_MD5 hash - * Inputs CHEEVOS_VAR_INFO, CHEEVOS_VAR_OFFSET, CHEEVOS_VAR_COUNT - * Outputs CHEEVOS_VAR_MD5, CHEEVOS_VAR_COUNT + * Inputs coro->data, coro->count, coro->offset, coro->len + * Outputs coro->hash *************************************************************************/ CORO_SUB(RCHEEVOS_EVAL_MD5) @@ -1978,16 +2073,19 @@ found: if (coro->count > CHEEVOS_MB(64)) coro->count = CHEEVOS_MB(64); + MD5_Init(&coro->md5); MD5_Update(&coro->md5, (void*)((uint8_t*)coro->data + coro->offset), coro->count); + MD5_Final(coro->hash, &coro->md5); + CORO_RET(); /************************************************************************** * Info Gets the achievements from Retro Achievements * Inputs coro->hash - * Outputs CHEEVOS_VAR_GAMEID + * Outputs coro->gameid *************************************************************************/ CORO_SUB(RCHEEVOS_GET_GAMEID)