From e7989c300280ba06d7621ae5b4e00ac7fe28d97a Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:33:49 +0900 Subject: [PATCH] Add DOSZ hashing support (#304) --- src/rc_libretro.c | 6 + src/rcheevos/consoleinfo.c | 26 +++ src/rhash/hash.c | 294 +++++++++++++++++++++++++++++++ test/rcheevos/test_consoleinfo.c | 1 + test/rhash/data.c | 94 ++++++++++ test/rhash/data.h | 3 + test/rhash/test_hash.c | 78 ++++++++ test/test_rc_libretro.c | 3 + 8 files changed, 505 insertions(+) diff --git a/src/rc_libretro.c b/src/rc_libretro.c index 93c5cd50..d94d6d5b 100644 --- a/src/rc_libretro.c +++ b/src/rc_libretro.c @@ -51,6 +51,11 @@ static const rc_disallowed_setting_t _rc_disallowed_dolphin_settings[] = { { NULL, NULL } }; +static const rc_disallowed_setting_t _rc_disallowed_dosbox_pure_settings[] = { + { "dosbox_pure_strict_mode", "false" }, + { NULL, NULL } +}; + static const rc_disallowed_setting_t _rc_disallowed_duckstation_settings[] = { { "duckstation_CDROM.LoadImagePatches", "true" }, { NULL, NULL } @@ -149,6 +154,7 @@ static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = { { "bsnes-mercury", _rc_disallowed_bsnes_settings }, { "cap32", _rc_disallowed_cap32_settings }, { "dolphin-emu", _rc_disallowed_dolphin_settings }, + { "DOSBox-pure", _rc_disallowed_dosbox_pure_settings }, { "DuckStation", _rc_disallowed_duckstation_settings }, { "ecwolf", _rc_disallowed_ecwolf_settings }, { "FCEUmm", _rc_disallowed_fceumm_settings }, diff --git a/src/rcheevos/consoleinfo.c b/src/rcheevos/consoleinfo.c index 95eb406c..427db73b 100644 --- a/src/rcheevos/consoleinfo.c +++ b/src/rcheevos/consoleinfo.c @@ -567,6 +567,29 @@ static const rc_memory_region_t _rc_memory_regions_msx[] = { }; static const rc_memory_regions_t rc_memory_regions_msx = { _rc_memory_regions_msx, 1 }; +/* ===== MS DOS ===== */ +static const rc_memory_region_t _rc_memory_regions_ms_dos[] = { + /* DOS emulators split the 640 KB conventional memory into two regions. + * First the part of the conventional memory given to the running game at $000000. + * The part of the conventional memory containing DOS and BIOS controlled memory + * is at $100000. The length of these can vary depending on the hardware + * and DOS version (or emulated DOS shell). + * These first two regions will only ever total to 640 KB but the regions map + * to 1 MB bounds to make resulting memory addresses more readable. + * When emulating a game not under DOS (so called 'PC Booter' games), the entirety + * of the 640 KB conventional memory block will be at $000000. + */ + { 0x00000000U, 0x0009FFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Game Conventional Memory" }, + { 0x000A0000U, 0x000FFFFFU, 0x000A0000U, RC_MEMORY_TYPE_UNUSED, "Padding to align OS Conventional Memory" }, + { 0x00100000U, 0x0019FFFFU, 0x00100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "OS Conventional Memory" }, + { 0x001A0000U, 0x001FFFFFU, 0x001A0000U, RC_MEMORY_TYPE_UNUSED, "Padding to align Expanded Memory" }, + /* Last is all the expanded memory which for now we map up to 64 MB which should be + * enough for the games we want to cover. An emulator might emulate more than that. + */ + { 0x00200000U, 0x041FFFFFU, 0x00200000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Expanded Memory" } +}; +static const rc_memory_regions_t rc_memory_regions_ms_dos = { _rc_memory_regions_ms_dos, 5 }; + /* ===== Neo Geo Pocket ===== */ /* http://neopocott.emuunlim.com/docs/tech-11.txt */ static const rc_memory_region_t _rc_memory_regions_neo_geo_pocket[] = { @@ -972,6 +995,9 @@ const rc_memory_regions_t* rc_console_memory_regions(uint32_t console_id) case RC_CONSOLE_MSX: return &rc_memory_regions_msx; + case RC_CONSOLE_MS_DOS: + return &rc_memory_regions_ms_dos; + case RC_CONSOLE_NEOGEO_POCKET: return &rc_memory_regions_neo_geo_pocket; diff --git a/src/rhash/hash.c b/src/rhash/hash.c index 11dc54a4..ab3e7785 100644 --- a/src/rhash/hash.c +++ b/src/rhash/hash.c @@ -696,6 +696,293 @@ static int rc_hash_7800(char hash[33], const uint8_t* buffer, size_t buffer_size return rc_hash_buffer(hash, buffer, buffer_size); } +struct rc_hash_zip_idx +{ + size_t length; + uint8_t* data; +}; + +static int rc_hash_zip_idx_sort(const void* a, const void* b) +{ + struct rc_hash_zip_idx *A = (struct rc_hash_zip_idx*)a, *B = (struct rc_hash_zip_idx*)b; + size_t len = (A->length < B->length ? A->length : B->length); + return memcmp(A->data, B->data, len); +} + +static int rc_hash_zip_file(md5_state_t* md5, void* file_handle) +{ + uint8_t buf[2048], *alloc_buf, *cdir_start, *cdir_max, *cdir, *hashdata, eocdirhdr_size, cdirhdr_size; + uint32_t cdir_entry_len; + size_t sizeof_idx, indices_offset, alloc_size; + int64_t i_file, archive_size, ecdh_ofs, total_files, cdir_size, cdir_ofs; + struct rc_hash_zip_idx* hashindices, *hashindex; + + rc_file_seek(file_handle, 0, SEEK_END); + archive_size = rc_file_tell(file_handle); + + /* Basic sanity checks - reject files which are too small */ + eocdirhdr_size = 22; /* the 'end of central directory header' is 22 bytes */ + if (archive_size < eocdirhdr_size) + return rc_hash_error("ZIP is too small"); + + /* Macros used for reading ZIP and writing to a buffer for hashing (undefined again at the end of the function) */ + #define RC_ZIP_READ_LE16(p) ((uint16_t)(((const uint8_t*)(p))[0]) | ((uint16_t)(((const uint8_t*)(p))[1]) << 8U)) + #define RC_ZIP_READ_LE32(p) ((uint32_t)(((const uint8_t*)(p))[0]) | ((uint32_t)(((const uint8_t*)(p))[1]) << 8U) | ((uint32_t)(((const uint8_t*)(p))[2]) << 16U) | ((uint32_t)(((const uint8_t*)(p))[3]) << 24U)) + #define RC_ZIP_READ_LE64(p) ((uint64_t)(((const uint8_t*)(p))[0]) | ((uint64_t)(((const uint8_t*)(p))[1]) << 8U) | ((uint64_t)(((const uint8_t*)(p))[2]) << 16U) | ((uint64_t)(((const uint8_t*)(p))[3]) << 24U) | ((uint64_t)(((const uint8_t*)(p))[4]) << 32U) | ((uint64_t)(((const uint8_t*)(p))[5]) << 40U) | ((uint64_t)(((const uint8_t*)(p))[6]) << 48U) | ((uint64_t)(((const uint8_t*)(p))[7]) << 56U)) + #define RC_ZIP_WRITE_LE32(p,v) { ((uint8_t*)(p))[0] = (uint8_t)((uint32_t)(v) & 0xFF); ((uint8_t*)(p))[1] = (uint8_t)(((uint32_t)(v) >> 8) & 0xFF); ((uint8_t*)(p))[2] = (uint8_t)(((uint32_t)(v) >> 16) & 0xFF); ((uint8_t*)(p))[3] = (uint8_t)((uint32_t)(v) >> 24); } + #define RC_ZIP_WRITE_LE64(p,v) { ((uint8_t*)(p))[0] = (uint8_t)((uint64_t)(v) & 0xFF); ((uint8_t*)(p))[1] = (uint8_t)(((uint64_t)(v) >> 8) & 0xFF); ((uint8_t*)(p))[2] = (uint8_t)(((uint64_t)(v) >> 16) & 0xFF); ((uint8_t*)(p))[3] = (uint8_t)(((uint64_t)(v) >> 24) & 0xFF); ((uint8_t*)(p))[4] = (uint8_t)(((uint64_t)(v) >> 32) & 0xFF); ((uint8_t*)(p))[5] = (uint8_t)(((uint64_t)(v) >> 40) & 0xFF); ((uint8_t*)(p))[6] = (uint8_t)(((uint64_t)(v) >> 48) & 0xFF); ((uint8_t*)(p))[7] = (uint8_t)((uint64_t)(v) >> 56); } + + /* Find the end of central directory record by scanning the file from the end towards the beginning */ + for (ecdh_ofs = archive_size - sizeof(buf); ; ecdh_ofs -= (sizeof(buf) - 3)) + { + int i, n = sizeof(buf); + if (ecdh_ofs < 0) + ecdh_ofs = 0; + if (n > archive_size) + n = (int)archive_size; + rc_file_seek(file_handle, ecdh_ofs, SEEK_SET); + if (rc_file_read(file_handle, buf, n) != (size_t)n) + return rc_hash_error("ZIP read error"); + for (i = n - 4; i >= 0; --i) + if (RC_ZIP_READ_LE32(buf + i) == 0x06054b50) /* end of central directory header signature */ + break; + if (i >= 0) + { + ecdh_ofs += i; + break; + } + if (!ecdh_ofs || (archive_size - ecdh_ofs) >= (0xFFFF + eocdirhdr_size)) + return rc_hash_error("Failed to find ZIP central directory"); + } + + /* Read and verify the end of central directory record. */ + rc_file_seek(file_handle, ecdh_ofs, SEEK_SET); + if (rc_file_read(file_handle, buf, eocdirhdr_size) != eocdirhdr_size) + return rc_hash_error("Failed to read ZIP central directory"); + + /* Read central dir information from end of central directory header */ + total_files = RC_ZIP_READ_LE16(buf + 0x0A); + cdir_size = RC_ZIP_READ_LE32(buf + 0x0C); + cdir_ofs = RC_ZIP_READ_LE32(buf + 0x10); + + /* Check if this is a Zip64 file. In the block of code below: + * - 20 is the size of the ZIP64 end of central directory locator + * - 56 is the size of the ZIP64 end of central directory header + */ + if ((cdir_ofs == 0xFFFFFFFF || cdir_size == 0xFFFFFFFF || total_files == 0xFFFF) && ecdh_ofs >= (20 + 56)) + { + /* Read the ZIP64 end of central directory locator if it actually exists */ + rc_file_seek(file_handle, ecdh_ofs - 20, SEEK_SET); + if (rc_file_read(file_handle, buf, 20) == 20 && RC_ZIP_READ_LE32(buf) == 0x07064b50) /* locator signature */ + { + /* Found the locator, now read the actual ZIP64 end of central directory header */ + int64_t ecdh64_ofs = (int64_t)RC_ZIP_READ_LE64(buf + 0x08); + if (ecdh64_ofs <= (archive_size - 56)) + { + rc_file_seek(file_handle, ecdh64_ofs, SEEK_SET); + if (rc_file_read(file_handle, buf, 56) == 56 && RC_ZIP_READ_LE32(buf) == 0x06064b50) /* header signature */ + { + total_files = RC_ZIP_READ_LE64(buf + 0x20); + cdir_size = RC_ZIP_READ_LE64(buf + 0x28); + cdir_ofs = RC_ZIP_READ_LE64(buf + 0x30); + } + } + } + } + + /* Basic verificaton of central directory (limit to a 256MB content directory) */ + cdirhdr_size = 46; /* the 'central directory header' is 46 bytes */ + if ((cdir_size >= 0x10000000) || (cdir_size < total_files * cdirhdr_size) || ((cdir_ofs + cdir_size) > archive_size)) + return rc_hash_error("Central directory of ZIP file is invalid"); + + /* Allocate once for both directory and our temporary sort index (memory aligned to sizeof(rc_hash_zip_idx)) */ + sizeof_idx = sizeof(struct rc_hash_zip_idx); + indices_offset = (size_t)((cdir_size + sizeof_idx - 1) / sizeof_idx * sizeof_idx); + alloc_size = (size_t)(indices_offset + total_files * sizeof_idx); + alloc_buf = (uint8_t*)malloc(alloc_size); + + /* Read entire central directory to a buffer */ + if (!alloc_buf) + return rc_hash_error("Could not allocate temporary buffer"); + rc_file_seek(file_handle, cdir_ofs, SEEK_SET); + if ((int64_t)rc_file_read(file_handle, alloc_buf, (int)cdir_size) != cdir_size) + { + free(alloc_buf); + return rc_hash_error("Failed to read central directory of ZIP file"); + } + + cdir_start = alloc_buf; + cdir_max = cdir_start + cdir_size - cdirhdr_size; + cdir = cdir_start; + + /* Write our temporary hash data to the same buffer we read the central directory from. + * We can do that because the amount of data we keep for each file is guaranteed to be less than the file record. + */ + hashdata = alloc_buf; + hashindices = (struct rc_hash_zip_idx*)(alloc_buf + indices_offset); + hashindex = hashindices; + + /* Now process the central directory file records */ + for (i_file = 0, cdir = cdir_start; i_file < total_files && cdir >= cdir_start && cdir <= cdir_max; i_file++, cdir += cdir_entry_len) + { + const uint8_t *name, *name_end; + uint32_t signature = RC_ZIP_READ_LE32(cdir + 0x00); + uint32_t method = RC_ZIP_READ_LE16(cdir + 0x0A); + uint32_t crc32 = RC_ZIP_READ_LE32(cdir + 0x10); + uint64_t comp_size = RC_ZIP_READ_LE32(cdir + 0x14); + uint64_t decomp_size = RC_ZIP_READ_LE32(cdir + 0x18); + uint32_t filename_len = RC_ZIP_READ_LE16(cdir + 0x1C); + int32_t extra_len = RC_ZIP_READ_LE16(cdir + 0x1E); + int32_t comment_len = RC_ZIP_READ_LE16(cdir + 0x20); + uint64_t local_hdr_ofs = RC_ZIP_READ_LE32(cdir + 0x2A); + cdir_entry_len = cdirhdr_size + filename_len + extra_len + comment_len; + + if (signature != 0x02014b50) /* expected central directory entry signature */ + break; + + /* Handle Zip64 fields */ + if (decomp_size == 0xFFFFFFFF || comp_size == 0xFFFFFFFF || local_hdr_ofs == 0xFFFFFFFF) + { + int invalid = 0; + const uint8_t *x = cdir + cdirhdr_size + filename_len, *xEnd, *field, *fieldEnd; + for (xEnd = x + extra_len; (x + (sizeof(uint16_t) * 2)) < xEnd; x = fieldEnd) + { + field = x + (sizeof(uint16_t) * 2); + fieldEnd = field + RC_ZIP_READ_LE16(x + 2); + if (RC_ZIP_READ_LE16(x) != 0x0001 || fieldEnd > xEnd) + continue; /* Not the Zip64 extended information extra field */ + + if (decomp_size == 0xFFFFFFFF) + { + if ((unsigned)(fieldEnd - field) < sizeof(uint64_t)) { invalid = 1; break; } + decomp_size = RC_ZIP_READ_LE64(field); + field += sizeof(uint64_t); + } + if (comp_size == 0xFFFFFFFF) + { + if ((unsigned)(fieldEnd - field) < sizeof(uint64_t)) { invalid = 1; break; } + comp_size = RC_ZIP_READ_LE64(field); + field += sizeof(uint64_t); + } + if (local_hdr_ofs == 0xFFFFFFFF) + { + if ((unsigned)(fieldEnd - field) < sizeof(uint64_t)) { invalid = 1; break; } + local_hdr_ofs = RC_ZIP_READ_LE64(field); + field += sizeof(uint64_t); + } + break; + } + if (invalid) + { + free(alloc_buf); + return rc_hash_error("Encountered invalid Zip64 file"); + } + } + + /* Basic sanity check on file record */ + /* 30 is the length of the local directory header preceeding the compressed data */ + if ((!method && decomp_size != comp_size) || (decomp_size && !comp_size) || ((local_hdr_ofs + 30 + comp_size) > (uint64_t)archive_size)) + { + free(alloc_buf); + return rc_hash_error("Encountered invalid entry in ZIP central directory"); + } + + /* Write the pointer and length of the data we record about this file */ + hashindex->data = hashdata; + hashindex->length = filename_len + 1 + 4 + 8; + hashindex++; + + /* Convert and store the file name in the hash data buffer */ + for (name = (cdir + cdirhdr_size), name_end = name + filename_len; name != name_end; name++) + { + *(hashdata++) = + (*name == '\\' ? '/' : /* convert back-slashes to regular slashes */ + (*name >= 'A' && *name <= 'Z') ? (*name | 0x20) : /* convert upper case letters to lower case */ + *name); /* else use the byte as-is */ + } + + /* Add zero terminator, CRC32 and decompressed size to the hash data buffer */ + *(hashdata++) = '\0'; + RC_ZIP_WRITE_LE32(hashdata, crc32); + hashdata += 4; + RC_ZIP_WRITE_LE64(hashdata, decomp_size); + hashdata += 8; + + if (verbose_message_callback) + { + char message[1024]; + snprintf(message, sizeof(message), "File in ZIP: %.*s (%u bytes, CRC32 = %08X)", filename_len, (const char*)(cdir + cdirhdr_size), (unsigned)decomp_size, crc32); + verbose_message_callback(message); + } + } + + if (verbose_message_callback) + { + char message[1024]; + snprintf(message, sizeof(message), "Hashing %u files in ZIP archive", (unsigned)(hashindex - hashindices)); + verbose_message_callback(message); + } + + /* Sort the file list indices */ + qsort(hashindices, (hashindex - hashindices), sizeof(struct rc_hash_zip_idx), rc_hash_zip_idx_sort); + + /* Hash the data in the order of the now sorted indices */ + for (; hashindices != hashindex; hashindices++) + md5_append(md5, hashindices->data, (int)hashindices->length); + + free(alloc_buf); + return 1; + + #undef RC_ZIP_READ_LE16 + #undef RC_ZIP_READ_LE32 + #undef RC_ZIP_READ_LE64 + #undef RC_ZIP_WRITE_LE32 + #undef RC_ZIP_WRITE_LE64 +} + +static int rc_hash_ms_dos(char hash[33], const char* path) +{ + md5_state_t md5; + size_t path_len; + int res; + + void* file_handle = rc_file_open(path); + if (!file_handle) + return rc_hash_error("Could not open file"); + + /* hash the main content zip file first */ + md5_init(&md5); + res = rc_hash_zip_file(&md5, file_handle); + rc_file_close(file_handle); + + if (!res) + return 0; + + /* if this is a .dosz file, check if an associated .dosc file exists */ + path_len = strlen(path); + if (path[path_len-1] == 'z' || path[path_len-1] == 'Z') + { + char *dosc_path = strdup(path); + if (!dosc_path) + return rc_hash_error("Could not allocate temporary buffer"); + + /* swap the z to c and use the same capitalization, hash the file if it exists*/ + dosc_path[path_len-1] = (path[path_len-1] == 'z' ? 'c' : 'C'); + file_handle = rc_file_open(dosc_path); + free((void*)dosc_path); + if (file_handle) + { + res = rc_hash_zip_file(&md5, file_handle); + rc_file_close(file_handle); + + if (!res) + return 0; + } + } + + return rc_hash_finalize(&md5, hash); +} + static int rc_hash_arcade(char hash[33], const char* path) { /* arcade hash is just the hash of the filename (no extension) - the cores are pretty stringent about having the right ROM data */ @@ -3032,6 +3319,9 @@ int rc_hash_generate_from_file(char hash[33], uint32_t console_id, const char* p case RC_CONSOLE_GAMECUBE: return rc_hash_gamecube(hash, path); + case RC_CONSOLE_MS_DOS: + return rc_hash_ms_dos(hash, path); + case RC_CONSOLE_NEO_GEO_CD: return rc_hash_neogeo_cd(hash, path); @@ -3316,6 +3606,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* iterator->consoles[0] = RC_CONSOLE_PC8800; iterator->consoles[1] = RC_CONSOLE_SHARPX1; } + else if (rc_path_compare_extension(ext, "dosz")) + { + iterator->consoles[0] = RC_CONSOLE_MS_DOS; + } break; case 'e': diff --git a/test/rcheevos/test_consoleinfo.c b/test/rcheevos/test_consoleinfo.c index cc7a6e1f..0ff928df 100644 --- a/test/rcheevos/test_consoleinfo.c +++ b/test/rcheevos/test_consoleinfo.c @@ -156,6 +156,7 @@ void test_consoleinfo(void) { TEST_PARAMS2(test_memory, RC_CONSOLE_MEGA_DRIVE, 0x020000); TEST_PARAMS2(test_memory, RC_CONSOLE_MEGADUCK, 0x010000); TEST_PARAMS2(test_memory, RC_CONSOLE_MSX, 0x080000); + TEST_PARAMS2(test_memory, RC_CONSOLE_MS_DOS, 0x4200000); TEST_PARAMS2(test_memory, RC_CONSOLE_NEOGEO_POCKET, 0x004000); TEST_PARAMS2(test_memory, RC_CONSOLE_NEO_GEO_CD, 0x010000); TEST_PARAMS2(test_memory, RC_CONSOLE_NINTENDO, 0x010000); diff --git a/test/rhash/data.c b/test/rhash/data.c index 87b80f76..25932e88 100644 --- a/test/rhash/data.c +++ b/test/rhash/data.c @@ -827,3 +827,97 @@ uint8_t* convert_to_2352(uint8_t* input, size_t* size, uint32_t first_sector) *size = output_size; return output; } + +uint8_t* generate_zip_file(size_t* image_size) +{ + const uint8_t data_zip[] = { + 'P','K',0x03,0x04, /* local file header signature */ + 0x14,0x00, 0x02,0x00, 0x08,0x00, 0x00,0xBC, 0x98,0x21, 0x00,0x00,0x00,0x00, 0x02,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, /* info */ + 0x07,0x00, 0x00,0x00, 'F','O','L','D','E','R','/', /* file name length, extra length, name data */ + 0x03, 0x00, /* compressed content */ + + 'P','K',0x03,0x04, /* local file header signature */ + 0x14,0x00, 0x02,0x00, 0x08,0x00, 0x00,0xBC, 0x98,0x21, 0x31,0xCF,0xD0,0x4A, 0x03,0x00,0x00,0x00, 0x01,0x00,0x00,0x00, /* info */ + 0x0E,0x00, 0x00,0x00, 'F','O','L','D','E','R','/','S','U','B','.','T','X','T', /* file name length, extra length, name data */ + 0x73, 0x02, 0x00, /* compressed content */ + + 'P','K',0x03,0x04, /* local file header signature */ + 0x14,0x00, 0x02,0x00, 0x08,0x00, 0x00,0xBC, 0x98,0x21, 0x8B,0x9E,0xD9,0xD3, 0x03,0x00,0x00,0x00, 0x01,0x00,0x00,0x00, /* info */ + 0x08,0x00, 0x00,0x00, 'R','O','O','T','.','T','X','T', /* file name length, extra length, name data */ + 0x73, 0x04, 0x00, /* compressed content */ + + 'P', 'K',0x01,0x02, /* central directory file header signature */ + 0x00,0x00, 0x14,0x00, 0x02,0x00, 0x08,0x00, 0x00,0xBC, 0x98,0x21, 0x00,0x00,0x00,0x00, 0x02,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x07,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 'F','O','L','D','E','R','/', + + 'P','K',0x01,0x02, /* central directory file header signature */ + 0x00,0x00, 0x14,0x00, 0x02,0x00, 0x08,0x00, 0x00,0xBC, 0x98,0x21, 0x31,0xCF,0xD0,0x4A, 0x03,0x00,0x00,0x00, 0x01,0x00,0x00,0x00, + 0x0E,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00,0x00,0x00, 0x27,0x00,0x00,0x00, + 'F','O','L','D','E','R','/','S','U','B','.','T','X','T', + + 'P','K',0x01,0x02, /* central directory file header signature */ + 0x00,0x00, 0x14,0x00, 0x02,0x00, 0x08,0x00, 0x00,0xBC, 0x98,0x21, 0x8B,0x9E,0xD9,0xD3, 0x03,0x00,0x00,0x00, 0x01,0x00,0x00,0x00, + 0x08,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00,0x00,0x00, 0x56,0x00,0x00,0x00, + 'R','O','O','T','.','T','X','T', + + 'P','K',0x05,0x06, /* end of central directory signature */ + 0x00,0x00, 0x00,0x00, /* disk number */ + 0x03,0x00, 0x03,0x00, /* number of directory entries on this disk and total */ + 0xA7,0x00,0x00,0x00, /* size of central directory (bytes) */ + 0x7F,0x00,0x00,0x00, /* offset of start of central directory */ + 0x16,0x00, 'T','O','R','R','E','N','T','Z','I','P','P','E','D','-','F','D','0','7','C','5','2','C' /* comment length and comment data */ + }; + + uint8_t* image = (uint8_t*)malloc(sizeof(data_zip)); + if (image != NULL) + memcpy(image, data_zip, sizeof(data_zip)); + if (image_size) + *image_size = sizeof(data_zip); + return image; +} + +uint8_t* generate_zip64_file(size_t* image_size) +{ + const uint8_t data_zip64[] = { + 'P','K',0x03,0x04, /* local file header signature */ + 0x14,0x00, 0x00,0x00, 0x08,0x00, 0x30,0x74, 0x0A,0x41, 0x7E,0xE7,0xFF,0x69, 0x24,0x00,0x00,0x00, 0x24,0x00,0x00,0x00, + 0x06,0x00, 0x00,0x00, 'R','E','A','D','M','E', + 0x0B,0xC9,0xC8,0x2C,0x56,0x28,0xCE,0x4D,0xCC,0xC9,0x51,0x48,0xCB,0xCC,0x49,0x55,0x00,0xF2, + 0x32,0xF3,0x14,0xA2,0x3C,0x03,0xCC,0x4C,0x14,0xD2,0xF2,0x8B,0x72,0x13,0x4B,0xF4,0xB8,0x00, + + 'P','K',0x01,0x02, /* central directory file header signature */ + 0x2D,0x03, 0x2D,0x00, 0x00,0x00, 0x08,0x00, 0x30,0x74, 0x0A,0x41, 0x7E,0xE7,0xFF,0x69, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, + 0x06,0x00, 0x14,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00,0xA4,0x81, 0x00,0x00,0x00,0x00, + 'R','E','A','D','M','E', + 0x01,0x00, /* Zip64 extended information extra field header id */ + 0x10,0x00, /* Size of extra field chunk */ + 0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* uncompressed file size */ + 0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* size of compressed data */ + + 'P','K',0x06,0x06, /* Zip64 End of central directory record signature */ + 0x2C,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Size of the EOCD64 minus 12 */ + 0x2D,0x00, 0x2D,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 'P','K',0x06,0x07, /* end of central dir locator signature */ + 0x00,0x00,0x00,0x00, /* number of the disk with the start of the zip64 end of central directory */ + 0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* relative offset of the zip64 end of central directory record */ + 0x01,0x00,0x00,0x00, /* total number of disks */ + + 'P','K',0x05,0x06, /* end of central directory signature */ + 0x00,0x00, 0x00,0x00, /* disk number */ + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, /* file and size numbers are 0xFF in Zip64 */ + 0x00,0x00 /* comment length */ + }; + + uint8_t* image = (uint8_t*)malloc(sizeof(data_zip64)); + if (image != NULL) + memcpy(image, data_zip64, sizeof(data_zip64)); + if (image_size) + *image_size = sizeof(data_zip64); + return image; +} diff --git a/test/rhash/data.h b/test/rhash/data.h index 18124a84..2fe897e4 100644 --- a/test/rhash/data.h +++ b/test/rhash/data.h @@ -29,6 +29,9 @@ uint8_t* generate_gamecube_iso(size_t mb, size_t* image_size); uint8_t* generate_iso9660_bin(uint32_t binary_sectors, const char* volume_label, size_t* image_size); uint8_t* generate_iso9660_file(uint8_t* image, const char* filename, const uint8_t* contents, size_t contents_size); +uint8_t* generate_zip_file(size_t* image_size); +uint8_t* generate_zip64_file(size_t* image_size); + extern uint8_t test_rom_z64[64]; extern uint8_t test_rom_n64[64]; extern uint8_t test_rom_v64[64]; diff --git a/test/rhash/test_hash.c b/test/rhash/test_hash.c index 81136cb4..3a0d8707 100644 --- a/test/rhash/test_hash.c +++ b/test/rhash/test_hash.c @@ -886,6 +886,79 @@ static void test_hash_gamecube() /* ========================================================================= */ +static void test_hash_msdos_dosz() +{ + size_t image_size; + uint8_t* image = generate_zip_file(&image_size); + char hash_file[33], hash_iterator[33]; + const char* expected_md5 = "4cef392530883f23ccebf413f1898023"; + + mock_file(0, "game.dosz", image, image_size); + + /* test file hash */ + int result_file = rc_hash_generate_from_file(hash_file, RC_CONSOLE_MS_DOS, "game.dosz"); + + /* test file identification from iterator */ + struct rc_hash_iterator iterator; + rc_hash_initialize_iterator(&iterator, "game.dosz", NULL, 0); + int 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_msdos_dosz_zip64() +{ + size_t image_size; + uint8_t* image = generate_zip64_file(&image_size); + char hash_file[33]; + const char* expected_md5 = "927dad0a57a2860267ab7bcdb8bc3f61"; + + mock_file(0, "game.dosz", image, image_size); + + /* test file hash */ + int result_file = rc_hash_generate_from_file(hash_file, RC_CONSOLE_MS_DOS, "game.dosz"); + + /* cleanup */ + free(image); + + /* validation */ + ASSERT_NUM_EQUALS(result_file, 1); + ASSERT_STR_EQUALS(hash_file, expected_md5); +} + +static void test_hash_msdos_dosz_with_dosc() +{ + size_t image_size; + uint8_t* image = generate_zip_file(&image_size); + char hash_dosc[33]; + const char* expected_dosc_md5 = "b22fae2e4e4c17b9c9c4b094b86aeb1e"; + + /* Add main dosz file and overlay dosc file which will get hashed together */ + mock_file(0, "game.dosz", image, image_size); + mock_file(1, "game.dosc", image, image_size); + + /* test file hash */ + int result_dosc = rc_hash_generate_from_file(hash_dosc, RC_CONSOLE_MS_DOS, "game.dosz"); + + /* cleanup */ + free(image); + + /* validation */ + ASSERT_NUM_EQUALS(result_dosc, 1); + ASSERT_STR_EQUALS(hash_dosc, expected_dosc_md5); +} + +/* ========================================================================= */ + static void test_hash_nes_32k() { size_t image_size; @@ -2189,6 +2262,11 @@ void test_hash(void) { TEST_PARAMS4(test_hash_full_file, RC_CONSOLE_MSX, "test.dsk", 737280, "0e73fe94e5f2e2d8216926eae512b7a6"); TEST_PARAMS4(test_hash_m3u, RC_CONSOLE_MSX, "test.dsk", 737280, "0e73fe94e5f2e2d8216926eae512b7a6"); + /* MS DOS */ + TEST(test_hash_msdos_dosz); + TEST(test_hash_msdos_dosz_zip64); + TEST(test_hash_msdos_dosz_with_dosc); + /* Neo Geo CD */ TEST(test_hash_neogeocd); TEST(test_hash_neogeocd_multiple_prg); diff --git a/test/test_rc_libretro.c b/test/test_rc_libretro.c index e23ba815..1578cc90 100644 --- a/test/test_rc_libretro.c +++ b/test/test_rc_libretro.c @@ -699,6 +699,9 @@ void test_rc_libretro(void) { TEST_PARAMS3(test_allowed_setting, "dolphin-emu", "dolphin_cheats_enabled", "disabled"); TEST_PARAMS3(test_disallowed_setting, "dolphin-emu", "dolphin_cheats_enabled", "enabled"); + TEST_PARAMS3(test_allowed_setting, "DOSBox-pure", "dosbox_pure_strict_mode", "true"); + TEST_PARAMS3(test_disallowed_setting, "DOSBox-pure", "dosbox_pure_strict_mode", "false"); + TEST_PARAMS3(test_allowed_setting, "DuckStation", "duckstation_CDROM.LoadImagePatches", "false"); TEST_PARAMS3(test_disallowed_setting, "DuckStation", "duckstation_CDROM.LoadImagePatches", "true");