diff --git a/Makefile.common b/Makefile.common index 4b42ae2dc7..56941d2ec9 100644 --- a/Makefile.common +++ b/Makefile.common @@ -188,6 +188,7 @@ OBJ += frontend/frontend.o \ $(LIBRETRO_COMM_DIR)/streams/interface_stream.o \ $(LIBRETRO_COMM_DIR)/streams/memory_stream.o \ $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.o \ + $(LIBRETRO_COMM_DIR)/media/media_detect_cd.o \ $(LIBRETRO_COMM_DIR)/lists/string_list.o \ $(LIBRETRO_COMM_DIR)/string/stdstring.o \ $(LIBRETRO_COMM_DIR)/memmap/memalign.o \ diff --git a/libretro-common/include/media/media_detect_cd.h b/libretro-common/include/media/media_detect_cd.h new file mode 100644 index 0000000000..66181061c5 --- /dev/null +++ b/libretro-common/include/media/media_detect_cd.h @@ -0,0 +1,56 @@ +/* Copyright (C) 2010-2019 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (media_detect_cd.h). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __LIBRETRO_SDK_MEDIA_DETECT_CD_H +#define __LIBRETRO_SDK_MEDIA_DETECT_CD_H + +#include +#include + +RETRO_BEGIN_DECLS + +enum media_detect_cd_system +{ + MEDIA_CD_SYSTEM_MEGA_CD, + MEDIA_CD_SYSTEM_SATURN, + MEDIA_CD_SYSTEM_DREAMCAST, + MEDIA_CD_SYSTEM_PSX, + MEDIA_CD_SYSTEM_3DO +}; + +typedef struct +{ + char title[256]; + char system[128]; + char region[128]; + char serial[64]; + char maker[64]; + char version[32]; + char release_date[32]; + enum media_detect_cd_system system_id; +} media_detect_cd_info_t; + +bool media_detect_cd_info(const char *path, media_detect_cd_info_t *info); + +RETRO_END_DECLS + +#endif diff --git a/libretro-common/media/media_detect_cd.c b/libretro-common/media/media_detect_cd.c new file mode 100644 index 0000000000..4577c88c86 --- /dev/null +++ b/libretro-common/media/media_detect_cd.c @@ -0,0 +1,197 @@ +/* Copyright (C) 2010-2019 The RetroArch team +* +* --------------------------------------------------------------------------------------- +* The following license statement only applies to this file (media_detect_cd.c). +* --------------------------------------------------------------------------------------- +* +* Permission is hereby granted, free of charge, +* to any person obtaining a copy of this software and associated documentation files (the "Software"), +* to deal in the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +* and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include + +static bool media_skip_spaces(const char **buf, size_t len) +{ + bool found = false; + int i; + + if (!buf || !*buf) + return false; + + for (i = 0; i < len; i++) + { + if ((*buf)[i] == ' ') + continue; + + *buf += i; + found = true; + break; + } + + if (found) + return true; + + return false; +} + +bool media_detect_cd_info(const char *path, media_detect_cd_info_t *info) +{ + RFILE *file; + + if (string_is_empty(path) || !info) + return false; + + file = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ, 0); + + if (!file) + { + printf("[MEDIA] Could not open path for reading: %s\n", path); + fflush(stdout); + return false; + } + + { + unsigned offset = 0; + unsigned sector_size = 0; + unsigned buf_size = 17 * 2352; + char *buf = (char*)calloc(1, buf_size); + + if (!buf) + return false; + + int64_t read_bytes = filestream_read(file, buf, buf_size); + + if (read_bytes != buf_size) + { + printf("[MEDIA] Could not read from media.\n"); + fflush(stdout); + filestream_close(file); + free(buf); + return false; + } + + /* 12-byte sync field at the start of every sector, common to both mode1 and mode2 data tracks + * (when at least sync data is requested). This is a CD-ROM standard feature and not specific to any game devices, + * and as such should not be part of any system-specific detection or "magic" bytes. + * Depending on what parts of a sector were requested from the disc, the user data might start at + * byte offset 0, 4, 8, 12, 16 or 24. Cue sheets only specify the total number of bytes requested from the sectors + * of a track (like 2048 or 2352) and it is then assumed based on the size/mode as to what fields are present. */ + if (!memcmp(buf, "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", 12)) + { + /* Assume track data contains all fields. */ + sector_size = 2352; + + if (buf[15] == 2) + { + /* assume Mode 2 formed (formless is rarely used) */ + offset = 24; + } + else + { + /* assume Mode 1 */ + offset = 16; + } + } + else + { + /* Assume sectors only contain user data instead. */ + offset = 0; + sector_size = 2048; + } + + if (!memcmp(buf + offset, "SEGADISCSYSTEM", strlen("SEGADISCSYSTEM"))) + { + const char *title_pos; + const char *serial_pos; + bool title_found = false; + + /* All discs currently in Redump for MCD start with SEGADISCSYSTEM. There are other strings mentioned elsewhere online, + * but I have not seen any real examples of them. */ + info->system_id = MEDIA_CD_SYSTEM_MEGA_CD; + + strlcpy(info->system, "Sega CD / Mega CD", sizeof(info->system)); + + title_pos = buf + offset + 0x150; + + if (media_skip_spaces(&title_pos, 48)) + memcpy(info->title, title_pos, 48 - (title_pos - (buf + offset + 0x150))); + else + strlcpy(info->title, "N/A", sizeof(info->title)); + + serial_pos = buf + offset + 0x183; + + if (media_skip_spaces(&serial_pos, 8)) + memcpy(info->serial, serial_pos, 8 - (serial_pos - (buf + offset + 0x183))); + else + strlcpy(info->serial, "N/A", sizeof(info->title)); + } + else if (!memcmp(buf + offset, "SEGA SEGASATURN", strlen("SEGA SEGASATURN"))) + { + const char *title_pos; + const char *serial_pos; + bool title_found = false; + + info->system_id = MEDIA_CD_SYSTEM_SATURN; + + strlcpy(info->system, "Sega Saturn", sizeof(info->system)); + + title_pos = buf + offset + 0x60; + + if (media_skip_spaces(&title_pos, 112)) + memcpy(info->title, title_pos, 112 - (title_pos - (buf + offset + 0x60))); + else + strlcpy(info->title, "N/A", sizeof(info->title)); + + serial_pos = buf + offset + 0x20; + + if (media_skip_spaces(&serial_pos, 10)) + memcpy(info->serial, serial_pos, 10 - (serial_pos - (buf + offset + 0x20))); + else + strlcpy(info->serial, "N/A", sizeof(info->title)); + } + /* Primary Volume Descriptor fields of ISO9660 */ + else if (!memcmp(buf + offset + (16 * sector_size), "\1CD001\1\0PLAYSTATION", 19)) + { + const char *title_pos; + const char *serial_pos; + bool title_found = false; + + info->system_id = MEDIA_CD_SYSTEM_PSX; + + strlcpy(info->system, "Sony PlayStation", sizeof(info->system)); + + title_pos = buf + offset + (16 * sector_size) + 40; + + if (media_skip_spaces(&title_pos, 32)) + memcpy(info->title, title_pos, 32 - (title_pos - (buf + offset + (16 * sector_size) + 40))); + else + strlcpy(info->title, "N/A", sizeof(info->title)); + } + else if (!memcmp(buf + offset, "\x01\x5a\x5a\x5a\x5a\x5a\x01\x00\x00\x00\x00\x00", 12)) + { + info->system_id = MEDIA_CD_SYSTEM_3DO; + + strlcpy(info->system, "3DO", sizeof(info->system)); + } + + free(buf); + } + + filestream_close(file); + + return true; +} diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index f900f1372a..1a6b63dda7 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -61,7 +61,8 @@ #endif #ifdef HAVE_CDROM -#include +#include +#include #endif #include "menu_cbs.h" @@ -4951,8 +4952,188 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, { #ifdef HAVE_CDROM case DISPLAYLIST_CDROM_DETAIL_INFO: + { + media_detect_cd_info_t cd_info = {0}; + char file_path[PATH_MAX_LENGTH]; + RFILE *file; + char drive = info->path[0]; + bool atip = false; + menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); count = 0; + file_path[0] = '\0'; + + if (cdrom_drive_has_media(drive)) + { + cdrom_device_fillpath(file_path, sizeof(file_path), drive, 0, true); + + /* opening the cue triggers storing of TOC info internally */ + file = filestream_open(file_path, RETRO_VFS_FILE_ACCESS_READ, 0); + + if (file) + { + const cdrom_toc_t *toc = retro_vfs_file_get_cdrom_toc(); + + atip = cdrom_has_atip(filestream_get_vfs_handle(file)); + + filestream_close(file); + + /* open first track */ + cdrom_device_fillpath(file_path, sizeof(file_path), drive, 1, false); + + if (media_detect_cd_info(file_path, &cd_info)) + { + if (!string_is_empty(cd_info.title)) + { + char title[256]; + + count++; + + title[0] = '\0'; + + strlcpy(title, "Title: ", sizeof(title)); + strlcat(title, cd_info.title, sizeof(title)); + + menu_entries_append_enum(info->list, + title, + "", + 0, + FILE_TYPE_NONE, 0, 0); + } + + if (!string_is_empty(cd_info.system)) + { + char system[256]; + + count++; + + system[0] = '\0'; + + strlcpy(system, "System: ", sizeof(system)); + strlcat(system, cd_info.system, sizeof(system)); + + menu_entries_append_enum(info->list, + system, + "", + 0, + FILE_TYPE_NONE, 0, 0); + } + + if (!string_is_empty(cd_info.serial)) + { + char serial[256]; + + count++; + + serial[0] = '\0'; + + strlcpy(serial, "Serial#: ", sizeof(serial)); + strlcat(serial, cd_info.serial, sizeof(serial)); + + menu_entries_append_enum(info->list, + serial, + "", + 0, + FILE_TYPE_NONE, 0, 0); + } + + { + char atip_string[16] = {"Genuine CD: "}; + + if (atip) + strlcat(atip_string, "No", sizeof(atip_string)); + else + strlcat(atip_string, "Yes", sizeof(atip_string)); + + menu_entries_append_enum(info->list, + atip_string, + "", + 0, + FILE_TYPE_NONE, 0, 0); + } + + { + char tracks_string[32] = {"Number of tracks: "}; + + snprintf(tracks_string + strlen(tracks_string), sizeof(tracks_string) - strlen(tracks_string), "%d", toc->num_tracks); + + menu_entries_append_enum(info->list, + tracks_string, + "", + 0, + FILE_TYPE_NONE, 0, 0); + } + + { + int i; + + for (i = 0; i < toc->num_tracks; i++) + { + char track_string[16] = {"Track "}; + char mode_string[16] = {" - Mode: "}; + char size_string[32] = {" - Size: "}; + char length_string[32] = {" - Length: "}; + + snprintf(track_string + strlen(track_string), sizeof(track_string) - strlen(track_string), "%d:", i + 1); + + menu_entries_append_enum(info->list, + track_string, + "", + 0, + FILE_TYPE_NONE, 0, 0); + + if (toc->track[i].audio) + snprintf(mode_string + strlen(mode_string), sizeof(mode_string) - strlen(mode_string), "Audio"); + else + snprintf(mode_string + strlen(mode_string), sizeof(mode_string) - strlen(mode_string), "Mode %d", toc->track[i].mode); + + menu_entries_append_enum(info->list, + mode_string, + "", + 0, + FILE_TYPE_NONE, 0, 0); + + snprintf(size_string + strlen(size_string), sizeof(size_string) - strlen(size_string), "%.1f MB", toc->track[i].track_bytes / 1000.0 / 1000.0); + + menu_entries_append_enum(info->list, + size_string, + "", + 0, + FILE_TYPE_NONE, 0, 0); + + { + unsigned char min = 0; + unsigned char sec = 0; + unsigned char frame = 0; + + cdrom_lba_to_msf(toc->track[i].track_size, &min, &sec, &frame); + + snprintf(length_string + strlen(length_string), sizeof(length_string) - strlen(length_string), "%02d:%02d:%02d", min, sec, frame); + + menu_entries_append_enum(info->list, + length_string, + "", + 0, + FILE_TYPE_NONE, 0, 0); + } + } + } + } + else + RARCH_ERR("[CDROM]: Could not detect any disc info.\n"); + } + else + RARCH_ERR("[CDROM]: Error opening file for reading: %s\n", file_path); + } + else + { + RARCH_LOG("[CDROM]: No media is inserted or drive is not ready.\n"); + + runloop_msg_queue_push( + msg_hash_to_str(MSG_NO_DISC_INSERTED), + 1, 100, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } if (count == 0) menu_entries_append_enum(info->list, @@ -4965,6 +5146,7 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, info->need_refresh = true; info->need_clear = true; break; + } case DISPLAYLIST_DISC_INFO: menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); count = menu_displaylist_parse_disc_info(info,