Files
RetroArch/libretro-common/media/media_detect_cd.c
T
LibretroAdmin e5fc0a50e4 apple: the App Store builds compile without warnings, and their uploads stop failing on one bad core
Built the way Xcode builds them - the App Store floors (macOS 12,
iOS/tvOS 15), the projects' warnings, every translation unit - the
tree still warned:

- Delegate methods the SDK deprecates that an older OS still calls
  are no longer implemented in source.  apple_rt_add_method() gives
  the class each one at run time, from +initialize and only on the
  releases that call it: -locationManager:didChangeAuthorizationStatus:
  before macOS 11 / iOS 14 (-locationManagerDidChangeAuthorization:
  from there), -documentPicker:didPickDocumentAtURL: before iOS 11
  (the URL-array method from there), -shouldAutorotateTo-
  InterfaceOrientation: before iOS 6, and NSView's
  -layer:shouldInheritContentsScale:fromWindow: from 10.7.
  class_addMethod is looked up by name, as the 10.4 binary may not
  import it.  CocoaView no longer declares UIDocumentPickerDelegate,
  whose pre-11 SDKs require the method it now adds at run time, and
  -prefersPointerLocked is not built for tvOS, where it is
  unavailable.
- The open panel's file types, the companion's folder icon and the
  cores folder's open are -setAllowedContentTypes:, -iconForContent-
  Type: and -openURL: from macOS 11, the 10.x methods before it, by
  selector where they are not on every release.
- The alert's end selector is looked up by name, and on SDKs older
  than the selectors ui_cocoa.m names (10.6, 10.7, 10.9) they are
  declared in an unimplemented NSObject category, so @selector()
  stays a constant.
- size_t and 64-bit values narrowed without a cast in configuration.c,
  gl3.c, cdrom.c, media_detect_cd.c and vfs_implementation_cdrom.c;
  rchd_read_extent() rejects an extent past the last 32-bit frame
  instead of wrapping into the wrong track.

App Store uploads: one iOS or tvOS core without an unencrypted
LC_ENCRYPTION_INFO_64 - written by Apple's ld, not by other linkers -
fails the whole upload (ITMS-90125).  make-frameworks.sh leaves such a
core out of an App Store build with a warning naming it.  The
Fastfile's check for a build already in TestFlight review passed
Build.all a filter it does not take, so it always failed and the
upload went on to external distribution and stopped there; it asks
App Store Connect for the newest five builds of the platform with
their review submissions instead.
2026-10-07 09:38:29 +02:00

558 lines
19 KiB
C

/* Copyright (C) 2010-2020 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 <retro_posix_source.h>
#include <media/media_detect_cd.h>
#include <streams/file_stream.h>
#include <string/stdstring.h>
#include <file/file_path.h>
#include <retro_miscellaneous.h>
#include <compat/strl.h>
/*#define MEDIA_CUE_PARSE_DEBUG*/
static void media_zero_trailing_spaces(char *s, size_t len)
{
int i;
for (i = (int)len - 1; i >= 0; i--)
{
if (s[i] == ' ')
s[i] = '\0';
else if (s[i] != '\0')
break;
}
}
static bool media_skip_spaces(const char **s, size_t len)
{
if (s && *s && **s)
{
size_t i;
for (i = 0; i < len; i++)
{
if ((*s)[i] == ' ' || (*s)[i] == '\t')
continue;
*s += i;
return true;
}
}
return false;
}
/* Fill in "info" with detected CD info. Use this when you have a cue file and want it parsed to find the first data track and any pregap info. */
bool media_detect_cd_info_cue(const char *path, media_detect_cd_info_t *info)
{
char *buf = NULL;
char *line = NULL;
char track_path[PATH_MAX_LENGTH] = {0};
char track_abs_path[PATH_MAX_LENGTH] = {0};
char track_mode[11] = {0};
bool found_file = false;
bool found_track = false;
unsigned first_data_track = 0;
uint64_t data_track_pregap_bytes = 0;
if (!path || !*path || !info)
return false;
/* Read the whole cue once, then walk it line by line in memory:
* filestream_getline reads a byte per call through the VFS
* (getc == a 1-byte filestream_read), so the previous loop cost
* one virtual read per character. The cue grammar is a flat
* sequence of lines with no includes or mid-parse seeks, so a
* single slurp and an in-place newline scan parse identically -
* each line is exactly what getline would have returned (it split
* on \n and kept any trailing \r, and so does this). */
if (!filestream_read_file(path, (void**)&buf, NULL) || !buf)
{
#ifdef MEDIA_CUE_PARSE_DEBUG
printf("[MEDIA] Could not open cue path for reading: %s\n", path);
fflush(stdout);
#endif
return false;
}
line = buf;
while (line && *line)
{
size_t _len = 0;
const char *command = NULL;
char *next = strchr(line, '\n');
if (next)
*next = '\0';
if (!*line)
goto next_line;
_len = strlen(line);
command = line;
media_skip_spaces(&command, _len);
if (!found_file && !strncasecmp(command, "FILE", 4))
{
const char *file = command + 4;
media_skip_spaces(&file, _len - 4);
if (file && *file)
{
const char *file_end = NULL;
size_t file_len = 0;
bool quoted = false;
if (file[0] == '"')
{
quoted = true;
file++;
}
if (quoted)
file_end = strchr(file, '\"');
else
file_end = strchr(file, ' ');
if (file_end)
{
file_len = file_end - file;
memcpy(track_path, file, file_len);
found_file = true;
#ifdef MEDIA_CUE_PARSE_DEBUG
printf("Found file: %s\n", track_path);
fflush(stdout);
#endif
}
}
}
else if (found_file && !found_track && !strncasecmp(command, "TRACK", 5))
{
const char *track = command + 5;
media_skip_spaces(&track, _len - 5);
if (track && *track)
{
char *ptr = NULL;
unsigned track_number = (unsigned)strtol(track, &ptr, 10);
#ifdef MEDIA_CUE_PARSE_DEBUG
printf("Found track: %d\n", track_number);
fflush(stdout);
#endif
track++;
if (track[0] && track[0] != ' ' && track[0] != '\t')
track++;
if (track && *track)
{
media_skip_spaces(&track, strlen(track));
#ifdef MEDIA_CUE_PARSE_DEBUG
printf("Found track type: %s\n", track);
fflush(stdout);
#endif
if (!strncasecmp(track, "MODE", 4))
{
first_data_track = track_number;
found_track = true;
strlcpy(track_mode, track, sizeof(track_mode));
}
else
found_file = false;
}
}
}
else if (found_file && found_track && first_data_track && !strncasecmp(command, "INDEX", 5))
{
const char *index = command + 5;
media_skip_spaces(&index, _len - 5);
if (index && *index)
{
char *ptr = NULL;
unsigned index_number = (unsigned)strtol(index, &ptr, 10);
if (index_number == 1)
{
const char *pregap = index + 1;
if (pregap[0] && pregap[0] != ' ' && pregap[0] != '\t')
pregap++;
if (pregap && *pregap)
{
media_skip_spaces(&pregap, strlen(pregap));
found_file = false;
found_track = false;
if (first_data_track && *track_mode)
{
unsigned track_sector_size = 0;
unsigned track_mode_number = 0;
if (strlen(track_mode) == 10)
{
if (!strncasecmp(track_mode, "MODE", 4))
{
char *ptr = NULL;
track_mode_number = (unsigned)strtol(track_mode + 4, &ptr, 10);
if (ptr && *ptr == '/')
track_sector_size = (unsigned)strtol(ptr + 1, NULL, 10);
}
#ifdef MEDIA_CUE_PARSE_DEBUG
printf("Found track mode %d with sector size %d\n", track_mode_number, track_sector_size);
fflush(stdout);
#endif
if ((track_mode_number == 1 || track_mode_number == 2) && track_sector_size)
{
unsigned min = 0;
unsigned sec = 0;
unsigned frame = 0;
{
char *ptr = NULL;
min = (unsigned)strtol(pregap, &ptr, 10);
if (ptr && *ptr == ':')
{
sec = (unsigned)strtol(ptr + 1, &ptr, 10);
if (ptr && *ptr == ':')
frame = (unsigned)strtol(ptr + 1, NULL, 10);
}
}
if (min || sec || frame || strstr(pregap, "00:00:00"))
{
data_track_pregap_bytes = ((min * 60 + sec) * 75 + frame) * track_sector_size;
#ifdef MEDIA_CUE_PARSE_DEBUG
printf("Found pregap of %02d:%02d:%02d (bytes: %" PRIu64 ")\n", min, sec, frame, data_track_pregap_bytes);
fflush(stdout);
#endif
break;
}
}
}
}
}
}
}
}
next_line:
if (!next)
break;
line = next + 1;
}
free(buf);
if (*track_path)
{
size_t _len;
if (strchr(track_path, '/') || strchr(track_path, '\\'))
{
#ifdef MEDIA_CUE_PARSE_DEBUG
printf("using path %s\n", track_path);
fflush(stdout);
#endif
return media_detect_cd_info(track_path, data_track_pregap_bytes, info);
}
_len = fill_pathname_basedir(track_abs_path, path, sizeof(track_abs_path));
strlcpy(track_abs_path + _len, track_path, sizeof(track_abs_path) - _len);
#ifdef MEDIA_CUE_PARSE_DEBUG
printf("using abs path %s\n", track_abs_path);
fflush(stdout);
#endif
return media_detect_cd_info(track_abs_path, data_track_pregap_bytes, info);
}
return true;
}
/* Fill in "info" with detected CD info. Use this when you want to open a specific track file directly, and the pregap is known. */
bool media_detect_cd_info(const char *path, uint64_t pregap_bytes, media_detect_cd_info_t *info)
{
RFILE *file;
if (!path || !*path || !info)
return false;
if (!(file = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ, 0)))
{
#ifdef MEDIA_CUE_PARSE_DEBUG
printf("[MEDIA] Could not open path for reading: %s\n", path);
fflush(stdout);
#endif
return false;
}
{
unsigned offset = 0;
unsigned sector_size = 0;
unsigned buf_size = 17 * 2352;
char *buf = (char*)calloc(1, buf_size);
int64_t read_bytes = 0;
if (!buf)
return false;
if (pregap_bytes)
filestream_seek(file, pregap_bytes, RETRO_VFS_SEEK_POSITION_START);
read_bytes = filestream_read(file, buf, buf_size);
if (read_bytes != buf_size)
{
#ifdef MEDIA_CUE_PARSE_DEBUG
printf("[MEDIA] Could not read from media: got %" PRId64 " bytes instead of %d.\n", read_bytes, buf_size);
fflush(stdout);
#endif
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;
/* Assume Mode 2 formed (formless is rarely used) */
if (buf[15] == 2)
offset = 24;
else /* Assume Mode 1 */
offset = 16;
}
else /* Assume sectors only contain user data instead. */
sector_size = 2048;
if (!memcmp(buf + offset, "SEGADISCSYSTEM",
STRLEN_CONST("SEGADISCSYSTEM")))
{
const char *title_pos = NULL;
const char *serial_pos = NULL;
/* 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_lit(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)));
media_zero_trailing_spaces(info->title, sizeof(info->title));
}
else
strlcpy_lit(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)));
media_zero_trailing_spaces(info->serial, sizeof(info->serial));
}
else
{
info->serial[0] = 'N';
info->serial[1] = '/';
info->serial[2] = 'A';
info->serial[3] = '\0';
}
}
else if (!memcmp(buf + offset, "SEGA SEGASATURN",
STRLEN_CONST("SEGA SEGASATURN")))
{
const char *title_pos = NULL;
const char *serial_pos = NULL;
const char *version_pos = NULL;
const char *release_date_pos = NULL;
info->system_id = MEDIA_CD_SYSTEM_SATURN;
strlcpy_lit(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)));
media_zero_trailing_spaces(info->title, sizeof(info->title));
}
else
strlcpy_lit(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)));
media_zero_trailing_spaces(info->serial, sizeof(info->serial));
}
else
{
info->serial[0] = 'N';
info->serial[1] = '/';
info->serial[2] = 'A';
info->serial[3] = '\0';
}
version_pos = buf + offset + 0x2a;
if (media_skip_spaces(&version_pos, 6))
{
memcpy(info->version, version_pos, 6 - (version_pos - (buf + offset + 0x2a)));
media_zero_trailing_spaces(info->version, sizeof(info->version));
}
else
{
info->version[0] = 'N';
info->version[1] = '/';
info->version[2] = 'A';
info->version[3] = '\0';
}
release_date_pos = buf + offset + 0x30;
if (media_skip_spaces(&release_date_pos, 8))
{
memcpy(info->release_date, release_date_pos, 8 - (release_date_pos - (buf + offset + 0x30)));
media_zero_trailing_spaces(info->release_date, sizeof(info->release_date));
}
else
{
info->release_date[0] = 'N';
info->release_date[1] = '/';
info->release_date[2] = 'A';
info->release_date[3] = '\0';
}
}
else if (!memcmp(buf + offset, "SEGA SEGAKATANA", STRLEN_CONST("SEGA SEGAKATANA")))
{
const char *title_pos = NULL;
const char *serial_pos = NULL;
const char *version_pos = NULL;
const char *release_date_pos = NULL;
info->system_id = MEDIA_CD_SYSTEM_DREAMCAST;
strlcpy_lit(info->system, "Sega Dreamcast", sizeof(info->system));
title_pos = buf + offset + 0x80;
if (media_skip_spaces(&title_pos, 96))
{
memcpy(info->title, title_pos, 96 - (title_pos - (buf + offset + 0x80)));
media_zero_trailing_spaces(info->title, sizeof(info->title));
}
else
strlcpy_lit(info->title, "N/A", sizeof(info->title));
serial_pos = buf + offset + 0x40;
if (media_skip_spaces(&serial_pos, 10))
{
memcpy(info->serial, serial_pos, 10 - (serial_pos - (buf + offset + 0x40)));
media_zero_trailing_spaces(info->serial, sizeof(info->serial));
}
else
{
info->serial [0] = 'N';
info->serial [1] = '/';
info->serial [2] = 'A';
info->serial [3] = '\0';
}
version_pos = buf + offset + 0x4a;
if (media_skip_spaces(&version_pos, 6))
{
memcpy(info->version, version_pos, 6 - (version_pos - (buf + offset + 0x4a)));
media_zero_trailing_spaces(info->version, sizeof(info->version));
}
else
{
info->version [0] = 'N';
info->version [1] = '/';
info->version [2] = 'A';
info->version [3] = '\0';
}
release_date_pos = buf + offset + 0x50;
if (media_skip_spaces(&release_date_pos, 8))
{
memcpy(info->release_date, release_date_pos, 8 - (release_date_pos - (buf + offset + 0x50)));
media_zero_trailing_spaces(info->release_date, sizeof(info->release_date));
}
else
{
info->release_date[0] = 'N';
info->release_date[1] = '/';
info->release_date[2] = 'A';
info->release_date[3] = '\0';
}
}
/* Primary Volume Descriptor fields of ISO9660 */
else if (!memcmp(buf + offset + (16 * sector_size), "\1CD001\1\0PLAYSTATION", 19))
{
const char *title_pos = NULL;
info->system_id = MEDIA_CD_SYSTEM_PSX;
strlcpy_lit(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)));
media_zero_trailing_spaces(info->title, sizeof(info->title));
}
else
strlcpy_lit(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_lit(info->system, "3DO", sizeof(info->system));
}
else if (!memcmp(buf + offset + 0x950, "PC Engine CD-ROM SYSTEM", 23))
{
info->system_id = MEDIA_CD_SYSTEM_PC_ENGINE_CD;
strlcpy_lit(info->system, "TurboGrafx-CD / PC-Engine CD", sizeof(info->system));
}
free(buf);
}
filestream_close(file);
return true;
}