From 5ce2ba044348e255811cf2d62b58c9cf44bc696c Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Tue, 27 Oct 2026 15:37:37 +0100 Subject: [PATCH] libretro-common: sync from RetroArch The Win32 read-ahead over a mapped view now runs on a thread of the file's own, also from Windows 8 on, so a disc read never waits on PrefetchVirtualMemory. The 7z reader takes bzip2 folders through the new encoding_rbzip2.c, added to every list that builds the 7z reader. The Vulkan symbol wrapper stays the core's own. --- Makefile.common | 1 + core/libretro-common/cdrom/cdrom.c | 524 ++++++++++--- .../encodings/encoding_deflate.c | 606 ++++++++++++++- .../encodings/encoding_rbzip2.c | 690 ++++++++++++++++++ .../encodings/encoding_rzstd.c | 10 + core/libretro-common/encodings/encoding_utf.c | 2 +- core/libretro-common/features/cpu_class.h | 12 +- core/libretro-common/features/features_cpu.c | 98 ++- core/libretro-common/file/file_path.c | 34 +- core/libretro-common/formats/7z/r7z_archive.c | 633 ++++++++++++---- core/libretro-common/formats/7z/r7z_filters.c | 35 + core/libretro-common/formats/chd/rchd.c | 11 +- core/libretro-common/formats/rar/rrar_ppmd7.c | 49 +- core/libretro-common/formats/rar/rrar_ppmd7.h | 14 +- core/libretro-common/include/7z/r7z_archive.h | 14 +- core/libretro-common/include/7z/r7z_filters.h | 2 + core/libretro-common/include/cdrom/cdrom.h | 37 +- .../include/encodings/deflate.h | 64 ++ .../include/encodings/rbzip2.h | 149 ++++ .../libretro-common/include/encodings/rzstd.h | 2 +- .../libretro-common/include/glsym/glsym_es3.h | 4 + core/libretro-common/include/libretro.h | 6 +- core/libretro-common/include/retro_atomic.h | 4 +- core/libretro-common/include/retro_math.h | 5 +- .../include/retro_miscellaneous.h | 3 + core/libretro-common/include/retro_spsc.h | 74 +- .../include/rthreads/retro_eventcount.h | 3 +- .../include/streams/file_stream.h | 41 ++ core/libretro-common/include/vfs/vfs.h | 22 +- .../include/vfs/vfs_implementation.h | 25 +- .../include/vfs/vfs_implementation_cdrom.h | 13 +- core/libretro-common/queues/retro_spsc.c | 321 +++++++- .../rthreads/retro_eventcount.c | 294 ++++++-- core/libretro-common/rthreads/rthreads.c | 6 +- core/libretro-common/streams/file_stream.c | 279 +++++-- core/libretro-common/string/stdstring.c | 94 ++- core/libretro-common/vfs/vfs_implementation.c | 241 ++++-- .../vfs/vfs_implementation_cdrom.c | 620 +++++++--------- tools/archive/run.sh | 2 +- tools/chd/run.sh | 4 +- 40 files changed, 4112 insertions(+), 936 deletions(-) create mode 100644 core/libretro-common/encodings/encoding_rbzip2.c create mode 100644 core/libretro-common/include/encodings/rbzip2.h diff --git a/Makefile.common b/Makefile.common index 508423c4..570bb8c4 100644 --- a/Makefile.common +++ b/Makefile.common @@ -126,6 +126,7 @@ ARCHIVE_SOURCES_C = \ $(LIBRETRO_COMM_DIR)/encodings/encoding_deflate.c \ $(LIBRETRO_COMM_DIR)/encodings/encoding_crc32.c \ $(LIBRETRO_COMM_DIR)/formats/7z/r7z_archive.c \ + $(LIBRETRO_COMM_DIR)/encodings/encoding_rbzip2.c \ $(LIBRETRO_COMM_DIR)/formats/7z/r7z_lzma.c \ $(LIBRETRO_COMM_DIR)/formats/7z/r7z_lzma2.c \ $(LIBRETRO_COMM_DIR)/formats/7z/r7z_lzma_stream.c \ diff --git a/core/libretro-common/cdrom/cdrom.c b/core/libretro-common/cdrom/cdrom.c index 6a488e32..06bbf573 100644 --- a/core/libretro-common/cdrom/cdrom.c +++ b/core/libretro-common/cdrom/cdrom.c @@ -348,7 +348,14 @@ static int cdrom_send_command_win32(const libretro_vfs_implementation_file *stre increment_msf(&last_min, &last_sec, &last_frame); #endif - if (!ioctl_rv || sptd.s.ScsiStatus != 0) + if (sense && sense_len) + memcpy(sense, sptd.sense, sense_len < sizeof(sptd.sense) + ? sense_len : sizeof(sptd.sense)); + + if (!ioctl_rv || sptd.s.ScsiStatus != 0 + || (sptd.s.DataTransferLength != len + && (cmd[0] == 0x28 || cmd[0] == 0xB9 || cmd[0] == 0xBE + || cmd[0] == 0x25 || (cmd[0] == 0x46 && len == 8)))) return 1; return 0; @@ -386,7 +393,10 @@ static int cdrom_send_command_linux(const libretro_vfs_implementation_file *stre rv = ioctl(fileno(stream->fp), SG_IO, &sgio); - if (rv == -1 || sgio.info & SG_INFO_CHECK) + if (rv == -1 || (sgio.info & SG_INFO_CHECK) || (sgio.resid != 0 + && (cmd[0] == 0x28 || cmd[0] == 0xB9 || cmd[0] == 0xBE + || cmd[0] == 0x25 || (cmd[0] == 0x46 && len == 8))) + || sgio.status || sgio.host_status || sgio.driver_status) return 1; return 0; @@ -398,7 +408,7 @@ static int cdrom_send_command_linux(const libretro_vfs_implementation_file *stre * so there is no cdrom_send_command_macos analogous to the linux/win32 * ones: dispatch by CDB opcode to either the curated MMC plug-in * (metadata) or a DKIOC ioctl on /dev/rdiskN (sector reads, speed). - * Unmappable CDBs are stubbed success or illegal-request. */ + * Unmappable CDBs return illegal-request, never a synthetic success. */ static int cdrom_send_command_macos(libretro_vfs_implementation_file *stream, CDROM_CMD_Direction dir, void *buf, size_t len, unsigned char *cmd, size_t cmd_len, @@ -450,12 +460,6 @@ static int cdrom_send_command_macos(libretro_vfs_implementation_file *stream, buf, (SCSICmdField2Byte)len, &ts, &ss); break; - case 0x55: /* MODE SELECT(10) - MMC only exposes the write- - * parameters page, which RetroArch never sets. Stub - * success so read-cache-disable etc. don't error. */ - (void)sense; (void)sense_len; - return 0; - case 0x5A: /* MODE SENSE(10) */ if (!mmc || !buf) return 1; r = (*mmc)->ModeSense10(mmc, @@ -535,6 +539,7 @@ static int cdrom_send_command_macos(libretro_vfs_implementation_file *stream, memset(sense, 0, sense_len); sense[0] = 0x70; /* current error */ sense[2] = 0x03; /* MEDIUM ERROR */ + sense[7] = 6; /* bytes through ASC/ASCQ */ sense[12] = 0x11; /* UNRECOVERED READ */ } return 1; @@ -550,6 +555,7 @@ static int cdrom_send_command_macos(libretro_vfs_implementation_file *stream, memset(sense, 0, sense_len); sense[0] = 0x70; sense[2] = 0x05; /* ILLEGAL REQUEST */ + sense[7] = 6; /* bytes through ASC/ASCQ */ sense[12] = 0x20; /* INVALID COMMAND OPERATION CODE */ } return 1; @@ -741,6 +747,38 @@ void cdrom_macos_close(void *plugin, void *mmc, int fd) } #endif +/* Preserve bound geometry until close, but never reuse it after an observed + * medium change/removal or device reset. Probes without geometry may retry. + * This is local to the handle, not a cross-handle media generation. */ +static bool cdrom_observe_media_change(libretro_vfs_implementation_file *stream, + const unsigned char *sense) +{ + unsigned key, asc; + unsigned response = sense[0] & 0x7f; + if (!stream || !stream->cdrom || (!stream->cdrom->logical_blocks + && (!stream->cdrom->toc || !stream->cdrom->toc->num_tracks))) + return false; + if ((response == 0x70 || response == 0x71) && sense[7] >= 5) + { + key = sense[2] & 0xf; + asc = sense[12]; + } + else if (response == 0x72 || response == 0x73) + { + key = sense[1] & 0xf; + asc = sense[2]; + } + else + return false; + if (!((key == 6 && (asc == 0x28 || asc == 0x29)) + || (key == 2 && asc == 0x3a))) + return false; + stream->cdrom->media_changed = true; + stream->cdrom->last_frame_valid = false; + stream->cdrom->io_error = true; + return true; +} + static int cdrom_send_command(libretro_vfs_implementation_file *stream, CDROM_CMD_Direction dir, void *s, size_t len, unsigned char *cmd, size_t cmd_len, size_t skip) { @@ -751,16 +789,29 @@ static int cdrom_send_command(libretro_vfs_implementation_file *stream, CDROM_CM unsigned char sense[CDROM_MAX_SENSE_BYTES] = {0}; unsigned char retries_left = CDROM_MAX_RETRIES; size_t padded_req_bytes; + size_t allocation_bytes; size_t copied_bytes = 0; bool read_cd = false; if (!cmd || cmd_len == 0 || cmd_len < CDROM_MIN_BUFSIZE) return 1; + if (len > SIZE_MAX - skip) + return 1; if (cmd[0] == 0xBE || cmd[0] == 0xB9) { - frames = ceil((len + skip) / 2352.0); - padded_req_bytes = 2352 * frames; + size_t count; + if (stream && stream->cdrom && stream->cdrom->media_changed) + return 1; + if (dir != DIRECTION_IN || skip >= 2352) + return 1; + if (!len) + return 0; + count = (len + skip) / 2352 + ((len + skip) % 2352 != 0); + if (count > INT_MAX || count > SIZE_MAX / 2352) + return 1; + frames = (int)count; + padded_req_bytes = (size_t)2352 * count; read_cd = true; /* these will be incremented below */ cmd[6] = cmd[3]; @@ -772,13 +823,29 @@ static int cdrom_send_command(libretro_vfs_implementation_file *stream, CDROM_CM padded_req_bytes = len + skip; } - xfer_buf = (unsigned char*)memalign_alloc(4096, padded_req_bytes); + /* A complete cache hit needs neither transport nor staging, including + * unaligned and partial destinations. The sector remains handle-owned. */ + if (read_cd && frames == 1 && s && stream && stream->cdrom + && stream->cdrom->last_frame_valid + && cdrom_msf_to_lba(cmd[3], cmd[4], cmd[5]) + == stream->cdrom->last_frame_lba) + { + memmove(s, stream->cdrom->last_frame + skip, len); + increment_msf(&cmd[6], &cmd[7], &cmd[8]); + return 0; + } + + allocation_bytes = padded_req_bytes; + if (!read_cd) + { + xfer_buf = (unsigned char*)memalign_alloc(4096, allocation_bytes); + if (!xfer_buf) + return 1; + memset(xfer_buf, 0, allocation_bytes); + if (dir == DIRECTION_OUT && s && len) + memcpy(xfer_buf + skip, s, len); + } xfer_buf_pos = xfer_buf; - - if (!xfer_buf) - return 1; - - memset(xfer_buf, 0, padded_req_bytes); #ifdef CDROM_DEBUG printf("Number of frames to read: %d\n", frames); fflush(stdout); @@ -787,7 +854,7 @@ static int cdrom_send_command(libretro_vfs_implementation_file *stream, CDROM_CM { size_t request_len = padded_req_bytes; size_t copy_len = request_len; - bool cached_read = false; + bool direct_sector = false; if (read_cd) { @@ -815,25 +882,32 @@ static int cdrom_send_command(libretro_vfs_implementation_file *stream, CDROM_CM } lba_req = cdrom_msf_to_lba(cmd[3], cmd[4], cmd[5]); - - if ( stream->cdrom - && stream->cdrom->last_frame_valid + if (s && stream->cdrom && stream->cdrom->last_frame_valid && lba_req == stream->cdrom->last_frame_lba) { - /* use cached frame */ - cached_read = true; -#ifdef CDROM_DEBUG - printf("[CDROM] Using cached frame\n"); - fflush(stdout); -#endif - /* assumes request_len is always equal to the size of last_frame */ - memcpy(xfer_buf_pos, stream->cdrom->last_frame, sizeof(stream->cdrom->last_frame)); + memmove((unsigned char*)s + copied_bytes, + stream->cdrom->last_frame + skip, copy_len); + copied_bytes += copy_len; + continue; } - + direct_sector = s && !skip && copy_len == 2352 + && (((uintptr_t)s + copied_bytes) & 4095) == 0; + if (!direct_sector) + { + if (!xfer_buf) + xfer_buf = (unsigned char*)memalign_alloc(4096, 2352); + if (!xfer_buf) + { + rv = 1; + break; + } + memset(xfer_buf, 0, 2352); + } + xfer_buf_pos = direct_sector ? (unsigned char*)s + copied_bytes + : xfer_buf; } #ifdef CDROM_DEBUG - if (!cached_read) { unsigned j; @@ -855,22 +929,23 @@ static int cdrom_send_command(libretro_vfs_implementation_file *stream, CDROM_CM retry: #if defined(__linux__) && !defined(ANDROID) - if (cached_read || !cdrom_send_command_linux(stream, dir, xfer_buf_pos, request_len, cmd, cmd_len, sense, sizeof(sense))) + if (!cdrom_send_command_linux(stream, dir, xfer_buf_pos, request_len, cmd, cmd_len, sense, sizeof(sense))) #elif defined(_WIN32) && !defined(_XBOX) - if (cached_read || !cdrom_send_command_win32(stream, dir, xfer_buf_pos, request_len, cmd, cmd_len, sense, sizeof(sense))) + if (!cdrom_send_command_win32(stream, dir, xfer_buf_pos, request_len, cmd, cmd_len, sense, sizeof(sense))) #elif defined(__APPLE__) - if (cached_read || !cdrom_send_command_macos(stream, dir, xfer_buf_pos, request_len, cmd, cmd_len, sense, sizeof(sense))) + if (!cdrom_send_command_macos(stream, dir, xfer_buf_pos, request_len, cmd, cmd_len, sense, sizeof(sense))) #endif { rv = 0; - if (s) + if (s && dir == DIRECTION_IN) { #if 0 printf("offsetting %" PRId64 " from buf, copying at xfer_buf offset %" PRId64 ", copying %" PRId64 " bytes\n", copied_bytes, (xfer_buf_pos + skip) - xfer_buf, copy_len); fflush(stdout); #endif - memcpy((char*)s + copied_bytes, xfer_buf_pos + skip, copy_len); + if (!direct_sector) + memcpy((char*)s + copied_bytes, xfer_buf_pos + skip, copy_len); copied_bytes += copy_len; /* The sector cache only exists on cdrom:// handles. The @@ -879,17 +954,18 @@ retry: * of /dev/sg* or \\.\X:, where stream->cdrom is NULL. */ if (stream->cdrom) { - if (read_cd && !cached_read && request_len >= 2352) + if (read_cd && request_len >= 2352) { unsigned frame_end = cdrom_msf_to_lba(cmd[6], cmd[7], cmd[8]); /* cache the last received frame */ - memcpy(stream->cdrom->last_frame, xfer_buf_pos, sizeof(stream->cdrom->last_frame)); + if (stream->cdrom->last_frame != xfer_buf_pos) + memcpy(stream->cdrom->last_frame, xfer_buf_pos, sizeof(stream->cdrom->last_frame)); stream->cdrom->last_frame_valid = true; /* the ending frame is never actually read, so what we really just read is the one right before that */ stream->cdrom->last_frame_lba = frame_end - 1; } - else + else if (!read_cd) stream->cdrom->last_frame_valid = false; } @@ -897,11 +973,17 @@ retry: printf("Frame %d, adding %" PRId64 " to buf_pos, is now %" PRId64 ". skip is %" PRId64 "\n", i, request_len, (xfer_buf_pos + request_len) - xfer_buf, skip); fflush(stdout); #endif - xfer_buf_pos += request_len; + if (!read_cd) + xfer_buf_pos += request_len; } } else { + if (cdrom_observe_media_change(stream, sense)) + { + rv = 1; + break; + } #ifdef CDROM_DEBUG cdrom_print_sense_data(sense, sizeof(sense)); #endif @@ -945,6 +1027,7 @@ retry: } rv = 1; + break; } } @@ -1342,7 +1425,8 @@ static int cdrom_read_track_info(libretro_vfs_implementation_file *stream, unsig unsigned lba = 0; unsigned track_size = 0; int rv; - ssize_t pregap_lba_len; + unsigned pregap_lba_len; + int64_t absolute_start; cdb[5] = track; @@ -1350,19 +1434,29 @@ static int cdrom_read_track_info(libretro_vfs_implementation_file *stream, unsig sizeof(buf), cdb, sizeof(cdb), 0))) return 1; + if ((((unsigned)buf[0] << 8) | buf[1]) < 26) + return 1; + memcpy(&lba, buf + 8, 4); memcpy(&track_size, buf + 24, 4); lba = swap_if_little32(lba); track_size = swap_if_little32(track_size); - /* lba_start may be earlier than the MSF start times seen in read_subq */ - toc->track[track - 1].lba_start = lba; + /* TOC addresses are absolute MSF frames; track info uses device LBAs. */ + absolute_start = (int64_t)(int32_t)lba + 150; + if (absolute_start < 0 || absolute_start > UINT32_MAX + || (uint64_t)absolute_start > toc->track[track - 1].lba) + return 1; + toc->track[track - 1].lba_start = (unsigned)absolute_start; toc->track[track - 1].track_size = track_size; pregap_lba_len = (toc->track[track - 1].audio ? 0 : (toc->track[track - 1].lba - toc->track[track - 1].lba_start)); - toc->track[track - 1].track_bytes = (unsigned)((track_size - pregap_lba_len) * 2352); + if (!track_size || pregap_lba_len > track_size + || track_size - pregap_lba_len > UINT32_MAX / 2352) + return 1; + toc->track[track - 1].track_bytes = (track_size - pregap_lba_len) * 2352; toc->track[track - 1].mode = buf[6] & 0xF; #ifdef CDROM_DEBUG @@ -1393,7 +1487,9 @@ int cdrom_set_read_speed(libretro_vfs_implementation_file *stream, unsigned spee int cdrom_write_cue(libretro_vfs_implementation_file *stream, char **out_buf, size_t *out_len, char cdrom_drive, unsigned char *num_tracks, cdrom_toc_t *toc) { int i; - unsigned char buf[2352] = {0}; + unsigned char *buf = NULL; + cdrom_toc_t *built = NULL; + unsigned char tracks = 0; unsigned short data_len = 0; size_t _len = 0, pos = 0; int rv = 0; @@ -1407,14 +1503,24 @@ int cdrom_write_cue(libretro_vfs_implementation_file *stream, char **out_buf, si return 1; } + *out_buf = NULL; + *out_len = 0; + *num_tracks = 0; + buf = (unsigned char*)calloc(1, 2352); + built = (cdrom_toc_t*)calloc(1, sizeof(*built)); + if (!buf || !built) + goto error; + built->drive = cdrom_drive; cdrom_set_read_speed(stream, 0xFFFFFFFF); - rv = cdrom_read_subq(stream, buf, sizeof(buf)); + rv = cdrom_read_subq(stream, buf, 2352); if (rv) - return rv; + goto error; data_len = buf[0] << 8 | buf[1]; + if (data_len < 2 || data_len > 2350 || (data_len - 2) % 11) + goto error; for (i = 0; i < (data_len - 2) / 11; i++) { @@ -1425,28 +1531,31 @@ int cdrom_write_cue(libretro_vfs_implementation_file *stream, char **out_buf, si if (/*(control == 4 || control == 6) && */adr == 1 && tno == 0 && point == 0xA1) { - *num_tracks = pmin; + if (!pmin || pmin > 99) + goto error; + if (pmin > tracks) + tracks = pmin; #ifdef CDROM_DEBUG - printf("[CDROM] Number of CDROM tracks: %d\n", *num_tracks); + printf("[CDROM] Number of CDROM tracks: %d\n", tracks); fflush(stdout); #endif - break; } } - if (!*num_tracks || *num_tracks > 99) + if (!tracks || tracks > 99) { #ifdef CDROM_DEBUG printf("[CDROM] Invalid number of CDROM tracks: %d\n", *num_tracks); fflush(stdout); #endif - return 1; + goto error; } - _len = CDROM_CUE_TRACK_BYTES * (*num_tracks); - toc->num_tracks = *num_tracks; + _len = CDROM_CUE_TRACK_BYTES * tracks; + built->num_tracks = tracks; *out_buf = (char*)calloc(1, _len); - *out_len = _len; + if (!*out_buf) + goto error; for (i = 0; i < (data_len - 2) / 11; i++) { @@ -1470,6 +1579,9 @@ int cdrom_write_cue(libretro_vfs_implementation_file *stream, char **out_buf, si { bool audio = false; const char *track_type = "MODE1/2352"; + if (point > tracks || built->track[point - 1].track_num + || psec >= 60 || pframe >= 75) + goto error; audio = (!(control & 0x4) && !(control & 0x5)); @@ -1478,20 +1590,21 @@ int cdrom_write_cue(libretro_vfs_implementation_file *stream, char **out_buf, si fflush(stdout); #endif - toc->track[point - 1].track_num = point; - toc->track[point - 1].min = pmin; - toc->track[point - 1].sec = psec; - toc->track[point - 1].frame = pframe; - toc->track[point - 1].lba = lba; - toc->track[point - 1].audio = audio; + built->track[point - 1].track_num = point; + built->track[point - 1].min = pmin; + built->track[point - 1].sec = psec; + built->track[point - 1].frame = pframe; + built->track[point - 1].lba = lba; + built->track[point - 1].audio = audio; - cdrom_read_track_info(stream, point, toc); + if (cdrom_read_track_info(stream, point, built)) + goto error; if (audio) track_type = "AUDIO"; - else if (toc->track[point - 1].mode == 1) + else if (built->track[point - 1].mode == 1) track_type = "MODE1/2352"; - else if (toc->track[point - 1].mode == 2) + else if (built->track[point - 1].mode == 2) track_type = "MODE2/2352"; #if defined(_WIN32) && !defined(_XBOX) @@ -1502,9 +1615,9 @@ int cdrom_write_cue(libretro_vfs_implementation_file *stream, char **out_buf, si pos += snprintf(*out_buf + pos, _len - pos, " TRACK %02d %s\n", point, track_type); { - unsigned pregap_lba_len = toc->track[point - 1].lba - toc->track[point - 1].lba_start; + unsigned pregap_lba_len = built->track[point - 1].lba - built->track[point - 1].lba_start; - if (toc->track[point - 1].audio && pregap_lba_len > 0) + if (built->track[point - 1].audio && pregap_lba_len > 0) { unsigned char min = 0; unsigned char sec = 0; @@ -1521,7 +1634,23 @@ int cdrom_write_cue(libretro_vfs_implementation_file *stream, char **out_buf, si } } + for (i = 0; i < tracks; i++) + if (!built->track[i].track_num) + goto error; + memcpy(toc, built, sizeof(*toc)); + *num_tracks = tracks; + *out_len = pos + 1; + free(built); + free(buf); return 0; + +error: + free(built); + free(buf); + free(*out_buf); + *out_buf = NULL; + *out_len = 0; + return 1; } /* needs 32 bytes for full vendor, product and version */ @@ -1560,6 +1689,139 @@ int cdrom_get_inquiry(libretro_vfs_implementation_file *stream, char *s, size_t return 0; } +/* Unlike READ CD, cooked DVD reads carry a logical block address and + * never use CD MSF, pregaps, or the 2352-byte raw-sector cache. */ +static int cdrom_transfer_blocks(libretro_vfs_implementation_file *stream, + void *s, size_t len, unsigned char *cdb, size_t cdb_len) +{ + unsigned char sense[CDROM_MAX_SENSE_BYTES] = {0}; + int rv; + if (!stream || !s) + return 1; + if (stream->cdrom && stream->cdrom->media_changed && cdb[0] == 0x28) + return 1; +#if defined(__linux__) && !defined(ANDROID) + if (!stream->fp) + return 1; + rv = cdrom_send_command_linux(stream, DIRECTION_IN, + s, len, cdb, cdb_len, sense, sizeof(sense)); +#elif defined(_WIN32) && !defined(_XBOX) + if (!stream->fh || stream->fh == INVALID_HANDLE_VALUE) + return 1; + rv = cdrom_send_command_win32(stream, DIRECTION_IN, + s, len, cdb, cdb_len, sense, sizeof(sense)); +#else + (void)len; + (void)cdb; + (void)cdb_len; + (void)sense; + return 1; +#endif + if (rv) + cdrom_observe_media_change(stream, sense); + return rv; +} + +int cdrom_get_media_profile(libretro_vfs_implementation_file *stream, + uint16_t *profile) +{ + unsigned char cdb[10] = {0x46, 0, 0, 0, 0, 0, 0, 0, 8, 0}; + unsigned char *buf; + uint32_t size; + int rv; + if (!profile) + return 1; + *profile = 0; + buf = (unsigned char*)memalign_alloc(4096, 8); + if (!buf) + return 1; + memset(buf, 0, 8); + rv = cdrom_transfer_blocks(stream, buf, 8, cdb, sizeof(cdb)); + size = ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) + | ((uint32_t)buf[2] << 8) | buf[3]; + if (!rv && size >= 4) + *profile = (uint16_t)(((unsigned)buf[6] << 8) | buf[7]); + memalign_free(buf); + return rv || size < 4 ? 1 : 0; +} + +int cdrom_get_capacity(libretro_vfs_implementation_file *stream, + uint64_t *blocks, uint32_t *block_size) +{ + unsigned char cdb[10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char *buf; + uint32_t last, size; + int rv; + if (blocks) + *blocks = 0; + if (block_size) + *block_size = 0; + if (!stream || !blocks || !block_size) + return 1; + buf = (unsigned char*)memalign_alloc(4096, 8); + if (!buf) + return 1; + memset(buf, 0, 8); + rv = cdrom_transfer_blocks(stream, buf, 8, cdb, sizeof(cdb)); + last = ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) + | ((uint32_t)buf[2] << 8) | buf[3]; + size = ((uint32_t)buf[4] << 24) | ((uint32_t)buf[5] << 16) + | ((uint32_t)buf[6] << 8) | buf[7]; + memalign_free(buf); + if (rv || last == UINT32_MAX || !size) + return 1; + *blocks = (uint64_t)last + 1; + *block_size = size; + return 0; +} + +int cdrom_read_blocks(libretro_vfs_implementation_file *stream, + uint32_t lba, uint32_t count, void *s) +{ + unsigned char cdb[10] = {0x28, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char *dst = (unsigned char*)s; + unsigned char *bounce = NULL; + int rv = 0; + if (!stream || (!s && count) + || (uint64_t)lba + count > (uint64_t)UINT32_MAX + 1 + || (uint64_t)count * 2048 > SIZE_MAX) + return 1; + if (!count) + return 0; + /* Bounded 64KiB commands suit older optical adapters as well as DVD. + * A conservative page alignment avoids imposing device-specific + * alignment requirements on VFS callers. */ + if ((uintptr_t)dst & 4095) + { + bounce = (unsigned char*)memalign_alloc(4096, (count > 32 ? 32 : count) * 2048); + if (!bounce) + return 1; + } + while (count) + { + uint32_t n = count > 32 ? 32 : count; + size_t bytes = (size_t)n * 2048; + cdb[2] = (unsigned char)(lba >> 24); + cdb[3] = (unsigned char)(lba >> 16); + cdb[4] = (unsigned char)(lba >> 8); + cdb[5] = (unsigned char)lba; + cdb[7] = (unsigned char)(n >> 8); + cdb[8] = (unsigned char)n; + rv = cdrom_transfer_blocks(stream, bounce ? bounce : dst, + bytes, cdb, sizeof(cdb)); + if (rv) + break; + if (bounce) + memcpy(dst, bounce, bytes); + dst += bytes; + lba += n; + count -= n; + } + if (bounce) + memalign_free(bounce); + return rv; +} + int cdrom_read(libretro_vfs_implementation_file *stream, cdrom_group_timeouts_t *timeouts, unsigned char min, unsigned char sec, unsigned char frame, void *s, @@ -1568,8 +1830,16 @@ int cdrom_read(libretro_vfs_implementation_file *stream, /* MMC Command: READ CD MSF */ unsigned char cdb[] = {0xB9, 0, 0, 0, 0, 0, 0, 0, 0, 0xF8, 0, 0}; int rv; - double frames = ceil((len + skip) / 2352.0); - unsigned frame_end = cdrom_msf_to_lba(min, sec, frame) + frames; + size_t frames; + unsigned frame_end; + unsigned start = cdrom_msf_to_lba(min, sec, frame); + if (!stream || (!s && len) || sec >= 60 || frame >= 75 + || skip >= 2352 || len > SIZE_MAX - skip) + return 1; + frames = (len + skip) / 2352 + ((len + skip) % 2352 != 0); + if (frames > 256 * 60 * 75 - 1 - start) + return 1; + frame_end = start + (unsigned)frames; cdb[3] = min; cdb[4] = sec; @@ -1593,7 +1863,8 @@ int cdrom_read(libretro_vfs_implementation_file *stream, #endif } - /* regardless of the length specified here, a new buffer will be allocated and padded to a sector multiple inside cdrom_send_command */ + /* The dispatcher stages partial/unaligned or multi-sector reads; a + * complete page-aligned single sector can use the caller's buffer. */ rv = cdrom_send_command(stream, DIRECTION_IN, s, len, cdb, sizeof(cdb), skip); #ifdef CDROM_DEBUG @@ -1816,10 +2087,9 @@ struct string_list* cdrom_get_available_drives(void) for (i = 0; i < (int)(sizeof(DWORD) * 8); i++) { char path[] = {"a:\\"}; - char cdrom_path[] = {"cdrom://a:/drive-track01.bin"}; + char cdrom_path[32]; path[0] += i; - cdrom_path[8] += i; /* this drive letter doesn't exist */ if (!(drive_mask & (1 << i))) @@ -1828,6 +2098,12 @@ struct string_list* cdrom_get_available_drives(void) if (GetDriveType(path) != DRIVE_CDROM) continue; + /* INQUIRY needs only the device handle; a track or cue path + * would read the TOC and spin up the drive. */ + if (!cdrom_device_filldevicepath(cdrom_path, sizeof(cdrom_path), + (char)('A' + i))) + continue; + { char drive_string[33]; libretro_vfs_implementation_file *stream; @@ -1924,7 +2200,7 @@ bool cdrom_drive_has_media(const char drive) RFILE *file; char cdrom_path_bin[256] = {0}; - cdrom_device_fillpath(cdrom_path_bin, sizeof(cdrom_path_bin), drive, 1, false); + cdrom_device_filldevicepath(cdrom_path_bin, sizeof(cdrom_path_bin), drive); file = filestream_open(cdrom_path_bin, RETRO_VFS_FILE_ACCESS_READ, 0); @@ -2093,52 +2369,68 @@ bool cdrom_has_atip(libretro_vfs_implementation_file *stream) return true; } -size_t cdrom_device_fillpath(char *s, size_t len, char drive, unsigned char track, bool is_cue) +size_t cdrom_device_filldevicepath(char *s, size_t len, char drive) { - if (s && len > 0) - { - if (is_cue) - { -#ifdef _WIN32 - size_t _len = strlcpy_lit(s, "cdrom://", len); - if (len > _len) - s[_len++] = drive; - _len += strlcpy_lit(s + _len, ":/drive.cue", len - _len); - return _len; + if (!s || !len) + return 0; + s[0] = '\0'; +#if defined(_WIN32) && !defined(_XBOX) + if ((drive >= 'A' && drive <= 'Z') || (drive >= 'a' && drive <= 'z')) + return snprintf(s, len, "cdrom://%c:/drive.device", drive); +#elif (defined(__linux__) && !defined(ANDROID)) || defined(__APPLE__) + if (drive >= '0' && drive <= '9') + return snprintf(s, len, "cdrom://drive%c.device", drive); #else -#if defined(__linux__) || defined(__APPLE__) - size_t _len = strlcpy_lit(s, "cdrom://drive", len); - if (len > _len + 1) - { - s[_len++] = drive; - s[_len] = '\0'; - } - _len += strlcpy_lit(s + _len, ".cue", len - _len); - return _len; + (void)drive; #endif -#endif - } - else - { -#ifdef _WIN32 - size_t _len = strlcpy_lit(s, "cdrom://", len); - if (len > _len + 1) - { - s[_len++] = drive; - s[_len] = '\0'; - } - _len += snprintf(s + _len, len - _len, ":/drive-track%02d.bin", track); - return _len; -#else -#if defined(__linux__) || defined(__APPLE__) - size_t _len = strlcpy_lit(s, "cdrom://drive", len); - if (len > _len) - s[_len++] = drive; - _len += snprintf(s + _len, len - _len, "-track%02d.bin", track); - return _len; -#endif -#endif - } - } return 0; } + +size_t cdrom_device_fillimagepath(char *s, size_t len, char drive) +{ + if (!s || !len) + return 0; + s[0] = '\0'; +#if defined(_WIN32) && !defined(_XBOX) + if ((drive >= 'A' && drive <= 'Z') || (drive >= 'a' && drive <= 'z')) + return snprintf(s, len, "cdrom://%c:/drive.iso", drive); +#elif defined(__linux__) && !defined(ANDROID) + if (drive >= '0' && drive <= '9') + return snprintf(s, len, "cdrom://drive%c.iso", drive); +#else + (void)drive; +#endif + return 0; +} + +size_t cdrom_device_fillpath(char *s, size_t len, char drive, + unsigned char track, bool is_cue) +{ + int _len = -1; + if (!s || !len) + return 0; + s[0] = '\0'; +#if defined(_WIN32) + if (is_cue) + _len = snprintf(s, len, "cdrom://%c:/drive.cue", drive); + else + _len = snprintf(s, len, "cdrom://%c:/drive-track%02d.bin", + drive, track); +#elif defined(__linux__) || defined(__APPLE__) + if (is_cue) + _len = snprintf(s, len, "cdrom://drive%c.cue", drive); + else + _len = snprintf(s, len, "cdrom://drive%c-track%02d.bin", + drive, track); +#else + (void)drive; + (void)track; + (void)is_cue; +#endif + if (_len < 0) + { + s[0] = '\0'; + return 0; + } + return (size_t)_len; +} diff --git a/core/libretro-common/encodings/encoding_deflate.c b/core/libretro-common/encodings/encoding_deflate.c index 6e997667..f1d02a8e 100644 --- a/core/libretro-common/encodings/encoding_deflate.c +++ b/core/libretro-common/encodings/encoding_deflate.c @@ -900,6 +900,13 @@ void rinflate_set_out(void *data, uint8_t *out, size_t size) s->wprior = s->whave; } +void rinflate_extend_out(void *data, size_t size) +{ + struct rinflate *s = (struct rinflate*)data; + if (size >= s->out_pos) + s->out_size = size; +} + /* Prime the back-reference window with the tail of @dict, for resuming * raw-deflate decode mid-stream (indexed random access into gzip * members: the index stores the 32KB of plaintext preceding each entry @@ -2254,42 +2261,30 @@ static void rd_fixed_lit_lengths(uint8_t *ll) # endif #endif -/* Clang moved the SSE4.2 CRC32 intrinsics out of the smmintrin.h / - * nmmintrin.h chain and into their own crc32intrin.h, which the umbrella - * headers pull in only when __CRC32__ (or __SSE4_2__) is already defined - * at the point of inclusion. Neither is, in a translation unit that - * reaches the instruction through __attribute__((target("sse4.2"))) - * rather than through a global -msse4.2 - so __has_include() - * answered yes above, the declaration never arrived, and the build broke - * on _mm_crc32_u32 with clang telling us which header it wanted. x86 - * only: the ARM path below reaches its intrinsic through arm_acle.h. - * - * crc32intrin.h carries the target attribute on the intrinsics - * themselves, so including it directly is safe whether or not the TU is - * built for SSE4.2, and harmless on toolchains that never split it out. */ -#if (defined(RD_CRC32_HASH_DIRECT) || defined(RD_CRC32_HASH_RUNTIME)) \ - && (defined(__x86_64__) || defined(__i386__) \ - || defined(_M_X64) || defined(_M_IX86)) -# if defined(__has_include) -# if __has_include() -# include -# endif -# endif -#endif - #if defined(RD_CRC32_HASH_RUNTIME) -#include #include #if defined(_MSC_VER) -#define RD_TARGET_CRC32 -#else -#define RD_TARGET_CRC32 __attribute__((target("sse4.2"))) -#endif -static RD_TARGET_CRC32 uint32_t rd_hash_crc(const uint8_t *p) +#include +static uint32_t rd_hash_crc(const uint8_t *p) { uint32_t k = (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16); return _mm_crc32_u32(0, k) & RD_HASH_MASK; } +#else +/* Emitted as an instruction rather than via _mm_crc32_u32: clang's + * intrinsic requires the separate 'crc32' target feature, which + * target("sse4.2") does not reliably imply across versions, so the + * build failed on toolchains/targets without SSE4.2. The assembler + * accepts the mnemonic regardless of -m flags, and rdeflate_new only + * selects this path when cpu_features_get() reports SSE4.2. */ +static uint32_t rd_hash_crc(const uint8_t *p) +{ + uint32_t k = (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16); + uint32_t r = 0; + __asm__("crc32l %1, %0" : "+r"(r) : "rm"(k)); + return r & RD_HASH_MASK; +} +#endif #endif #if !defined(RD_CRC32_HASH_DIRECT) @@ -3470,3 +3465,556 @@ suspend: return RDEFLATE_PROCESS_END; return RDEFLATE_PROCESS_NEXT; } + +/* -------- Deflate64, one-shot -------- + * + * Deflate64 (PKWARE's "enhanced deflate", zip method 9, 7z's + * Deflate64) is RFC 1951 with a 64 KiB window, length code 285 taking + * 16 extra bits over a base of 3 rather than standing for 258, and + * distance codes 30 and 31 in use: 32769 and 49153, with 14 extra + * bits. A stream decodes into a buffer of the caller's a piece at a + * time, matches reaching back past it into a 64 KiB ring of what came + * before; the decoder above stays RFC 1951's alone. */ + +#define RINF64_LUT_BITS 9 + +struct rinf64_table +{ + uint16_t lut[1 << RINF64_LUT_BITS]; /* symbol << 4 | length; 0: longer */ + uint16_t count[16]; + uint16_t sym[288]; +}; + +#define RINF64_WINDOW 65536 + +enum rinf64_state +{ + RINF64_HEADER = 0, /* a block's header next */ + RINF64_SYMBOLS, /* in a coded block */ + RINF64_STORED, /* in a stored block */ + RINF64_DONE +}; + +struct rinf64 +{ + struct rinf64_table lit; + struct rinf64_table dist; + const uint8_t *p; + const uint8_t *end; + uint64_t bits; + uint64_t total; /* octets decoded so far */ + size_t over; /* zeros read past the end */ + unsigned n; + uint32_t copy_len; /* a match part way, */ + uint32_t copy_dist; /* from this far back */ + uint32_t stored_left; + uint32_t rpos; /* where the ring is written next */ + int state; + int last; /* the block in hand is the last */ + int held; /* a literal the out had no room + for, or -1 */ + uint8_t ring[RINF64_WINDOW]; /* what came before the out */ +}; + +static const uint16_t rinf64_len_base[29] = { + 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,3 }; +static const uint8_t rinf64_len_extra[29] = { + 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,16 }; +static const uint16_t rinf64_dist_base[32] = { + 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769, + 1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,32769,49153 }; +static const uint8_t rinf64_dist_extra[32] = { + 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13, + 14,14 }; + +/* At least @k bits in hand, k <= 57. With eight octets or more to go, + * the next eight are taken in one load and the whole ones that fit + * counted: the bits above n are then the octets still to come, which + * the next load puts there again, so they stay as they are. */ +static INLINE void rinf64_need(struct rinf64 *s, unsigned k) +{ + if (s->n >= k) + return; + if (s->end - s->p >= 8) + { + const uint8_t *q = s->p; + uint64_t w = (uint64_t)q[0] | ((uint64_t)q[1] << 8) + | ((uint64_t)q[2] << 16) | ((uint64_t)q[3] << 24) + | ((uint64_t)q[4] << 32) | ((uint64_t)q[5] << 40) + | ((uint64_t)q[6] << 48) | ((uint64_t)q[7] << 56); + s->bits |= w << s->n; + s->p += (63 - s->n) >> 3; + s->n |= 56; + return; + } + while (s->n < k) + { + uint64_t b = s->p < s->end ? *s->p++ : (s->over++, 0u); + s->bits |= b << s->n; + s->n += 8; + } +} + +/* @k bits, 0 <= k <= 24 */ +static INLINE uint32_t rinf64_bits(struct rinf64 *s, unsigned k) +{ + uint32_t v; + rinf64_need(s, k); + v = (uint32_t)(s->bits & ((1u << k) - 1)); + s->bits >>= k; + s->n -= k; + return v; +} + +/* Canonical codes for @n lengths; incomplete codes are allowed, their + * unused codes failing to decode, oversubscribed ones are not. */ +static int rinf64_build(struct rinf64_table *t, const uint8_t *len, + unsigned n) +{ + uint16_t offs[16]; + unsigned i, l; + int left = 1; + uint32_t code = 0; + + memset(t->count, 0, sizeof(t->count)); + for (i = 0; i < n; i++) + t->count[len[i]]++; + t->count[0] = 0; + for (l = 1; l < 16; l++) + { + left = (left << 1) - t->count[l]; + if (left < 0) + return -1; + } + offs[1] = 0; + for (l = 1; l < 15; l++) + offs[l + 1] = (uint16_t)(offs[l] + t->count[l]); + for (i = 0; i < n; i++) + if (len[i]) + t->sym[offs[len[i]]++] = (uint16_t)i; + + /* each code no longer than the table looks, reversed, as it arrives */ + memset(t->lut, 0, sizeof(t->lut)); + for (l = 1, i = 0; l <= RINF64_LUT_BITS; l++) + { + unsigned c; + for (c = 0; c < t->count[l]; c++, code++, i++) + { + unsigned rev = 0, b, k; + for (b = 0; b < l; b++) + rev |= ((code >> b) & 1u) << (l - 1 - b); + for (k = rev; k < (1u << RINF64_LUT_BITS); k += 1u << l) + t->lut[k] = (uint16_t)((t->sym[i] << 4) | l); + } + code <<= 1; + } + return 0; +} + +static INLINE int rinf64_decode_sym(struct rinf64 *s, + const struct rinf64_table *t) +{ + uint32_t e; + rinf64_need(s, 15); + if ((e = t->lut[s->bits & ((1u << RINF64_LUT_BITS) - 1)])) + { + s->bits >>= e & 15; + s->n -= e & 15; + return (int)(e >> 4); + } + { + /* longer than the table: a bit at a time */ + uint32_t code = 0, first = 0, index = 0; + unsigned l; + for (l = 1; l < 16; l++) + { + uint32_t cnt = t->count[l]; + code |= (uint32_t)(s->bits & 1); + s->bits >>= 1; + s->n--; + if (code - first < cnt) + return t->sym[index + code - first]; + index += cnt; + first = (first + cnt) << 1; + code <<= 1; + } + } + return -1; +} + +/* The fixed codes, or a dynamic block's, into s->lit and s->dist */ +static int rinf64_tables(struct rinf64 *s, int dynamic) +{ + static const uint8_t order[19] = + { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + uint8_t len[288 + 32]; + unsigned i, nlit = 288, ndist = 32; + + if (!dynamic) + { + for (i = 0; i < 144; i++) len[i] = 8; + for (; i < 256; i++) len[i] = 9; + for (; i < 280; i++) len[i] = 7; + for (; i < 288; i++) len[i] = 8; + for (i = 0; i < 32; i++) len[288 + i] = 5; + if ( rinf64_build(&s->lit, len, 288) + || rinf64_build(&s->dist, len + 288, 32)) + return -1; + return 0; + } + { + uint8_t cl[19]; + unsigned ncl, n = 0; + nlit = rinf64_bits(s, 5) + 257; + ndist = rinf64_bits(s, 5) + 1; + ncl = rinf64_bits(s, 4) + 4; + if (nlit > 286) + return -1; + memset(cl, 0, sizeof(cl)); + for (i = 0; i < ncl; i++) + cl[order[i]] = (uint8_t)rinf64_bits(s, 3); + if (rinf64_build(&s->lit, cl, 19)) + return -1; + while (n < nlit + ndist) + { + int sym = rinf64_decode_sym(s, &s->lit); + unsigned rep, val = 0; + if (sym < 0) + return -1; + if (sym < 16) + { + len[n++] = (uint8_t)sym; + continue; + } + if (sym == 16) + { + if (!n) + return -1; + val = len[n - 1]; + rep = 3 + rinf64_bits(s, 2); + } + else if (sym == 17) + rep = 3 + rinf64_bits(s, 3); + else + rep = 11 + rinf64_bits(s, 7); + if (n + rep > nlit + ndist) + return -1; + while (rep--) + len[n++] = (uint8_t)val; + } + if (!len[256]) + return -1; /* no end of block */ + if ( rinf64_build(&s->lit, len, nlit) + || rinf64_build(&s->dist, len + nlit, ndist)) + return -1; + } + return 0; +} + +void *rinflate64_new(void) +{ + struct rinf64 *s = (struct rinf64*)malloc(sizeof(*s)); + if (!s) + return NULL; + s->p = NULL; + s->end = NULL; + s->bits = 0; + s->total = 0; + s->over = 0; + s->n = 0; + s->copy_len = 0; + s->copy_dist = 0; + s->stored_left = 0; + s->rpos = 0; + s->state = RINF64_HEADER; + s->last = 0; + s->held = -1; + return s; +} + +void rinflate64_free(void *stream) +{ + free(stream); +} + +/* @len octets of a match from @dist back into @o at @a: from the out + * where it reaches that far, from the ring before it. Where the out + * is then. */ +static INLINE size_t rinf64_copy(struct rinf64 *s, uint8_t *o, size_t a, + uint32_t dist, uint32_t len) +{ + if (dist <= a) + { + const uint8_t *f = o + a - dist; + if (dist >= len) + memmove(o + a, f, len); + else + { + uint32_t k; + for (k = 0; k < len; k++) + o[a + k] = f[k]; + } + return a + len; + } + { + /* the part before the out, from the ring, then on in the out */ + uint32_t back = dist - (uint32_t)a; + uint32_t r = (s->rpos - back) & (RINF64_WINDOW - 1); + uint32_t k = 0; + for (; k < len && k < back; k++) + { + o[a + k] = s->ring[r]; + r = (r + 1) & (RINF64_WINDOW - 1); + } + for (; k < len; k++) + o[a + k] = o[a + k - dist]; + } + return a + len; +} + +/* The last of @n octets decoded into @o, kept for the next call's + * matches */ +static void rinf64_keep(struct rinf64 *s, const uint8_t *o, size_t n) +{ + if (n >= RINF64_WINDOW) + { + memcpy(s->ring, o + n - RINF64_WINDOW, RINF64_WINDOW); + s->rpos = 0; + return; + } + while (n) + { + uint32_t room = RINF64_WINDOW - s->rpos; + uint32_t k = n < room ? (uint32_t)n : room; + memcpy(s->ring + s->rpos, o, k); + s->rpos = (s->rpos + k) & (RINF64_WINDOW - 1); + o += k; + n -= k; + } +} + +int rinflate64_process(void *stream, const uint8_t *in, size_t in_len, + int final, size_t *consumed, uint8_t *out, size_t out_len, + size_t *wrote) +{ + struct rinf64 *s = (struct rinf64*)stream; + size_t at = 0; + int ret = RDEFLATE_PROCESS_NEXT; + + s->p = in; + s->end = in + in_len; + +/* not the last input, and less of it than a header or a symbol may + * read: the caller brings more first */ +#define RINF64_SHORT() (!final \ + && (size_t)(s->end - s->p) < RINFLATE64_LOOKAHEAD) + + for (;;) + { + /* a literal, or a match, the out filled up before */ + if (s->held >= 0) + { + if (at == out_len) + break; + out[at++] = (uint8_t)s->held; + s->held = -1; + } + if (s->copy_len) + { + uint32_t k = out_len - at < s->copy_len + ? (uint32_t)(out_len - at) : s->copy_len; + at = rinf64_copy(s, out, at, s->copy_dist, k); + s->copy_len -= k; + if (s->copy_len) + break; + } + + if (s->state == RINF64_DONE) + { + ret = RDEFLATE_PROCESS_END; + break; + } + + if (s->state == RINF64_HEADER) + { + uint32_t type; + if (s->last) + { + s->state = RINF64_DONE; + continue; + } + if (RINF64_SHORT()) + break; + s->last = (int)rinf64_bits(s, 1); + type = rinf64_bits(s, 2); + if (type == 0) + { + /* from the next octet boundary, a length and its + * complement, then that many octets */ + uint32_t len, nlen; + s->bits >>= s->n & 7; + s->n -= s->n & 7; + len = rinf64_bits(s, 16); + nlen = rinf64_bits(s, 16); + if ((len ^ 0xffffu) != nlen) + goto error; + s->stored_left = len; + s->state = RINF64_STORED; + } + else if (type == 3 || rinf64_tables(s, type == 2)) + goto error; + else + s->state = RINF64_SYMBOLS; + if (s->over * 8 > s->n) + goto error; /* ran past the input */ + continue; + } + + if (s->state == RINF64_STORED) + { + /* what the bit buffer holds first, then the input itself */ + while (s->stored_left && s->n >= 8 && at < out_len) + { + out[at++] = (uint8_t)s->bits; + s->bits >>= 8; + s->n -= 8; + s->stored_left--; + } + if (s->stored_left && at < out_len) + { + size_t k = (size_t)(s->end - s->p); + if (k > s->stored_left) + k = s->stored_left; + if (k > out_len - at) + k = out_len - at; + memcpy(out + at, s->p, k); + s->p += k; + at += k; + s->stored_left -= (uint32_t)k; + if (s->stored_left && s->p == s->end) + { + if (final) + goto error; + break; + } + } + if (s->stored_left) + break; + s->state = RINF64_HEADER; + continue; + } + + /* a coded block's symbols */ + { + /* where the input runs short of a symbol's worth, unless it + * is the last */ + uintptr_t lim = final ? (uintptr_t)-1 + : (uintptr_t)s->end - RINFLATE64_LOOKAHEAD; + uint64_t before = s->total; + for (;;) + { + int sym; + if ((uintptr_t)s->p > lim) + goto suspend; + if ((sym = rinf64_decode_sym(s, &s->lit)) < 0) + goto error; + if (sym < 256) + { + if (at < out_len) + { + out[at++] = (uint8_t)sym; + continue; + } + s->held = sym; + goto suspend; + } + if (sym == 256) + break; + { + uint32_t len, dist, room; + int dsym; + sym -= 257; + if (sym >= 29) + goto error; + len = rinf64_len_base[sym] + rinf64_bits(s, rinf64_len_extra[sym]); + if ((dsym = rinf64_decode_sym(s, &s->dist)) < 0 || dsym >= 32) + goto error; + dist = rinf64_dist_base[dsym] + rinf64_bits(s, rinf64_dist_extra[dsym]); + if (dist <= at && len <= out_len - at) + { + /* within the out, and room for it: as a whole + * stream decoded at once always is */ + uint8_t *o = out + at; + const uint8_t *f = o - dist; + if (dist >= len) + memmove(o, f, len); + else + { + uint32_t k; + for (k = 0; k < len; k++) + o[k] = f[k]; + } + at += len; + continue; + } + if ((uint64_t)dist > before + at) + goto error; + room = out_len - at < len ? (uint32_t)(out_len - at) : len; + at = rinf64_copy(s, out, at, dist, room); + if (room < len) + { + s->copy_len = len - room; + s->copy_dist = dist; + goto suspend; + } + } + } + } + if (s->over * 8 > s->n) + goto error; + s->state = RINF64_HEADER; + } +suspend: +#undef RINF64_SHORT + if (s->over * 8 > s->n) + goto error; + /* the final input, used up with the stream not done */ + if ( ret == RDEFLATE_PROCESS_NEXT && final && at < out_len + && s->p == s->end && s->state != RINF64_DONE + && !s->copy_len && s->held < 0) + goto error; + rinf64_keep(s, out, at); + s->total += at; + if (consumed) + *consumed = (size_t)(s->p - in); + if (wrote) + *wrote = at; + return ret; +error: + if (consumed) + *consumed = (size_t)(s->p - in); + if (wrote) + *wrote = at; + return RDEFLATE_PROCESS_ERROR; +} + +int rinflate64_decode(uint8_t *dst, size_t dst_len, + const uint8_t *src, size_t src_len, size_t *wrote) +{ + void *z = rinflate64_new(); + size_t got = 0; + int ret; + if (wrote) + *wrote = 0; + if (!z) + return RDEFLATE_PROCESS_ERROR; + ret = rinflate64_process(z, src, src_len, 1, NULL, dst, dst_len, &got); + /* more than the buffer holds */ + if (ret == RDEFLATE_PROCESS_NEXT) + ret = RDEFLATE_PROCESS_ERROR; + if (ret == RDEFLATE_PROCESS_END && wrote) + *wrote = got; + rinflate64_free(z); + return ret; +} diff --git a/core/libretro-common/encodings/encoding_rbzip2.c b/core/libretro-common/encodings/encoding_rbzip2.c new file mode 100644 index 00000000..445e8866 --- /dev/null +++ b/core/libretro-common/encodings/encoding_rbzip2.c @@ -0,0 +1,690 @@ +/* Copyright (C) 2010-2026 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (encoding_rbzip2.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. + */ + +/* bzip2 stream decoding; see for the format. */ + +#include +#include + +#include + +#define RBZ_GROUPS_MAX 6 +#define RBZ_ALPHA_MAX 258 /* 256 octets, two run symbols */ +#define RBZ_SEL_MAX 18002 /* 900 000 / 50, rounded up, and one */ +#define RBZ_LEN_MAX 20 +#define RBZ_LUT_BITS 10 /* codes this short decode in one look */ +#define RBZ_GROUP_SIZE 50 /* symbols coded with one table */ + +/* A Huffman table, canonical: codes of one length are consecutive and + * in symbol order, each length's after the shorter ones'. */ +struct rbz_table +{ + uint16_t lut[1 << RBZ_LUT_BITS]; /* symbol << 5 | length; 0: longer */ + uint16_t perm[RBZ_ALPHA_MAX]; /* symbols by length, then value */ + uint16_t count[RBZ_LEN_MAX + 1]; /* codes of each length */ + unsigned max_len; +}; + +/* The input, read most significant bit first through a 64-bit window. + * Past the end it reads zeros, counted in @over: a stream is short when + * more of them were taken than the window still holds. */ +struct rbz_in +{ + const uint8_t *p; + const uint8_t *end; + uint64_t buf; + size_t over; + unsigned n; +}; + +/* Where a block's output goes: a buffer of fixed size, or one grown + * as it fills. */ +struct rbz_out +{ + uint8_t *dst; + size_t cap; + size_t at; + int grow; +}; + +struct rbzip2 +{ + /* the stream in hand, between rbzip2_step() calls */ + struct rbz_in in; + struct rbz_out out; + uint32_t *tt; /* a block, then each entry's next */ + uint32_t *inv; /* each entry's previous */ + uint8_t *raw; /* the block untransformed */ + uint8_t *own; /* a block at a time: where it goes */ + size_t own_cap; + int blocks; /* rbzip2_begin_blocks() bound it */ + uint32_t max_block; + uint32_t combined; /* the block CRCs so far */ + uint32_t tt_cap; + uint32_t crc[8][256]; /* CRC-32, MSB first, eight at a time */ + uint32_t unz[256]; /* each octet's count in the block */ + struct rbz_table table[RBZ_GROUPS_MAX]; + uint8_t len[RBZ_GROUPS_MAX][RBZ_ALPHA_MAX]; + uint8_t selector[RBZ_SEL_MAX]; +}; + + +#define RBZ_FILL(p, end, buf, n, over) \ + while ((n) <= 56) \ + { \ + (buf) = ((buf) << 8) | ((p) < (end) ? *(p)++ : ((over)++, 0u)); \ + (n) += 8; \ + } + +/* @k octets wide or less, 1 <= k <= 24 */ +static uint32_t rbz_bits(struct rbz_in *in, unsigned k) +{ + RBZ_FILL(in->p, in->end, in->buf, in->n, in->over) + in->n -= k; + return (uint32_t)(in->buf >> in->n) & ((1u << k) - 1); +} + +static int rbz_short(const struct rbz_in *in) +{ + return in->over * 8 > in->n; +} + +rbzip2_t *rbzip2_new(void) +{ + rbzip2_t *bz = (rbzip2_t*)calloc(1, sizeof(*bz)); + unsigned i, j; + if (!bz) + return NULL; + for (i = 0; i < 256; i++) + { + uint32_t c = (uint32_t)i << 24; + for (j = 0; j < 8; j++) + c = (c & 0x80000000u) ? (c << 1) ^ 0x04c11db7u : c << 1; + bz->crc[0][i] = c; + } + for (j = 1; j < 8; j++) + for (i = 0; i < 256; i++) + bz->crc[j][i] = (bz->crc[j - 1][i] << 8) + ^ bz->crc[0][bz->crc[j - 1][i] >> 24]; + return bz; +} + +void rbzip2_free(rbzip2_t *bz) +{ + if (!bz) + return; + free(bz->tt); + free(bz->inv); + free(bz->raw); + free(bz->own); + free(bz); +} + +/* The table for code lengths @len over @alpha symbols. Codes may leave + * some of the code space unused, which decodes as an error, but none + * may claim space another already has. */ +static int rbz_table_build(struct rbz_table *t, const uint8_t *len, + unsigned alpha) +{ + uint16_t offs[RBZ_LEN_MAX + 2]; + unsigned i, l; + int32_t left = 1; + uint32_t code = 0; + unsigned at = 0; + + memset(t->count, 0, sizeof(t->count)); + for (i = 0; i < alpha; i++) + t->count[len[i]]++; + t->max_len = 0; + for (l = 1; l <= RBZ_LEN_MAX; l++) + { + left = (left << 1) - t->count[l]; + if (left < 0) + return RBZIP2_ERROR_DATA; + if (t->count[l]) + t->max_len = l; + } + offs[1] = 0; + for (l = 1; l <= RBZ_LEN_MAX; l++) + offs[l + 1] = (uint16_t)(offs[l] + t->count[l]); + for (i = 0; i < alpha; i++) + t->perm[offs[len[i]]++] = (uint16_t)i; + + /* codes up to RBZ_LUT_BITS long, every window they begin */ + memset(t->lut, 0, sizeof(t->lut)); + for (l = 1; l <= RBZ_LUT_BITS; l++) + { + unsigned c; + for (c = 0; c < t->count[l]; c++, code++, at++) + { + unsigned span = 1u << (RBZ_LUT_BITS - l); + unsigned first = code << (RBZ_LUT_BITS - l); + uint16_t e = (uint16_t)((t->perm[at] << 5) | l); + unsigned j; + for (j = 0; j < span; j++) + t->lut[first + j] = e; + } + code <<= 1; + } + return RBZIP2_OK; +} + +/* A code longer than the table looks at: @peek holds the next max_len + * bits. Returns the symbol, its length in *@used, or -1. */ +static int rbz_slow(const struct rbz_table *t, uint32_t peek, + unsigned *used) +{ + uint32_t code = 0; + uint32_t first = 0; + unsigned index = 0; + unsigned l; + for (l = 1; l <= t->max_len; l++) + { + uint32_t cnt = t->count[l]; + code |= (peek >> (t->max_len - l)) & 1; + if (code - first < cnt) + { + *used = l; + return t->perm[index + code - first]; + } + index += cnt; + first = (first + cnt) << 1; + code <<= 1; + } + return -1; +} + +/* The block CRC of @len octets: eight at a time, the table for each + * octet giving its effect that many octets on. */ +static uint32_t rbz_crc(const rbzip2_t *bz, const uint8_t *p, size_t len) +{ + uint32_t v = 0xffffffffu; + for (; len >= 8; p += 8, len -= 8) + { + uint32_t a = v ^ (((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) + | ((uint32_t)p[2] << 8) | p[3]); + v = bz->crc[7][a >> 24] ^ bz->crc[6][(a >> 16) & 0xff] + ^ bz->crc[5][(a >> 8) & 0xff] ^ bz->crc[4][a & 0xff] + ^ bz->crc[3][p[4]] ^ bz->crc[2][p[5]] + ^ bz->crc[1][p[6]] ^ bz->crc[0][p[7]]; + } + for (; len; p++, len--) + v = (v << 8) ^ bz->crc[0][(v >> 24) ^ *p]; + return ~v; +} + + +/* Room for @n more octets */ +static int rbz_room(struct rbz_out *o, size_t n) +{ + size_t cap; + uint8_t *d; + if (n <= o->cap - o->at) + return RBZIP2_OK; + if (!o->grow) + return RBZIP2_ERROR_SPACE; + cap = o->cap + o->cap / 2 + n + 65536; + if (cap < o->cap || !(d = (uint8_t*)realloc(o->dst, cap + 1))) + return RBZIP2_ERROR_MEM; + o->dst = d; + o->cap = cap; + return RBZIP2_OK; +} + +/* One block, after its magic: decoded and appended to @out. *@crc + * receives the block's stored CRC once it has been checked. */ +static int rbz_block(rbzip2_t *bz, struct rbz_in *in, uint32_t max_block, + struct rbz_out *out, uint32_t *crc) +{ + uint8_t mtf[256]; + uint8_t pos[RBZ_GROUPS_MAX]; + uint32_t stored, orig, used, i, j; + uint32_t nblock = 0, n_sel, n_groups, n_in_use = 0, alpha, eob; + uint32_t run = 0, run_bit = 1, sel = 0, left = 0, nsym = 0; + uint32_t *tt = bz->tt; + const struct rbz_table *t = NULL; + const uint8_t *p, *end; + uint64_t buf; + unsigned n; + size_t over; + int e; + + stored = rbz_bits(in, 16) << 16; + stored |= rbz_bits(in, 16); + if (rbz_bits(in, 1)) + return RBZIP2_ERROR_DATA; /* randomised */ + orig = rbz_bits(in, 24); + + /* the octets the block uses, sixteen at a time */ + used = rbz_bits(in, 16); + for (i = 0; i < 16; i++) + if (used & (0x8000u >> i)) + { + uint32_t m = rbz_bits(in, 16); + for (j = 0; j < 16; j++) + if (m & (0x8000u >> j)) + mtf[n_in_use++] = (uint8_t)(i * 16 + j); + } + if (!n_in_use) + return RBZIP2_ERROR_DATA; + alpha = n_in_use + 2; + eob = n_in_use + 1; + + n_groups = rbz_bits(in, 3); + n_sel = rbz_bits(in, 15); + if (n_groups < 2 || n_groups > RBZ_GROUPS_MAX || !n_sel) + return RBZIP2_ERROR_DATA; + /* each table's selector, move-to-front coded in unary */ + for (i = 0; i < n_groups; i++) + pos[i] = (uint8_t)i; + for (i = 0; i < n_sel; i++) + { + uint8_t v; + j = 0; + while (rbz_bits(in, 1)) + if (++j >= n_groups) + return RBZIP2_ERROR_DATA; + v = pos[j]; + for (; j > 0; j--) + pos[j] = pos[j - 1]; + pos[0] = v; + if (i < RBZ_SEL_MAX) + bz->selector[i] = v; + } + if (n_sel > RBZ_SEL_MAX) + n_sel = RBZ_SEL_MAX; + + /* each table's code lengths, as steps from a five-bit start */ + for (i = 0; i < n_groups; i++) + { + uint32_t cur = rbz_bits(in, 5); + for (j = 0; j < alpha; j++) + { + for (;;) + { + if (cur < 1 || cur > RBZ_LEN_MAX) + return RBZIP2_ERROR_DATA; + if (!rbz_bits(in, 1)) + break; + cur = rbz_bits(in, 1) ? cur - 1 : cur + 1; + } + bz->len[i][j] = (uint8_t)cur; + } + if ((e = rbz_table_build(&bz->table[i], bz->len[i], alpha)) != RBZIP2_OK) + return e; + } + if (rbz_short(in)) + return RBZIP2_ERROR_DATA; + + /* the symbols: runs of rank 0, other ranks, the end of the block */ + memset(bz->unz, 0, sizeof(bz->unz)); + p = in->p; + end = in->end; + buf = in->buf; + n = in->n; + over = in->over; + for (;;) + { + uint32_t sym, ent; + if (!left) + { + if (sel >= n_sel) + return RBZIP2_ERROR_DATA; + t = &bz->table[bz->selector[sel++]]; + left = RBZ_GROUP_SIZE; + } + left--; + nsym++; + RBZ_FILL(p, end, buf, n, over) + ent = t->lut[(uint32_t)(buf >> (n - RBZ_LUT_BITS)) & ((1u << RBZ_LUT_BITS) - 1)]; + if (ent) + { + n -= ent & 31; + sym = ent >> 5; + } + else + { + unsigned l = 0; + int s = rbz_slow(t, (uint32_t)(buf >> (n - t->max_len)) + & ((1u << t->max_len) - 1), &l); + if (s < 0) + return RBZIP2_ERROR_DATA; + n -= l; + sym = (uint32_t)s; + } + + if (sym <= 1) + { + /* RUNA adds one at this digit, RUNB two */ + if (run_bit > max_block) + return RBZIP2_ERROR_DATA; + run += run_bit << sym; + run_bit <<= 1; + continue; + } + if (run) + { + uint8_t c = mtf[0]; + if (run > max_block - nblock) + return RBZIP2_ERROR_DATA; + bz->unz[c] += run; + while (run--) + tt[nblock++] = c; + run = 0; + run_bit = 1; + } + if (sym == eob) + break; + if (sym >= alpha || nblock >= max_block) + return RBZIP2_ERROR_DATA; + { + uint32_t r = sym - 1; + uint8_t c = mtf[r]; + memmove(mtf + 1, mtf, r); + mtf[0] = c; + bz->unz[c]++; + tt[nblock++] = c; + } + } + in->p = p; + in->buf = buf; + in->n = n; + in->over = over; + if (rbz_short(in) || orig >= nblock) + return RBZIP2_ERROR_DATA; + + /* the inverse transform: each entry linked to the one after it in + * the block. Where the block is mostly long runs of rank 0 its walk + * stays near itself; otherwise each step is a load from anywhere, + * so each entry is linked to the one before it as well and the + * block is walked from both ends at once, one walk's loads waiting + * alongside the other's. */ + { + uint32_t sum = 0; + int both = nsym > nblock / 8; + for (i = 0; i < 256; i++) + { + uint32_t c = bz->unz[i]; + bz->unz[i] = sum; + sum += c; + } + if (both) + { + uint32_t *inv = bz->inv; + uint8_t *raw = bz->raw; + uint32_t f, b, k; + for (i = 0; i < nblock; i++) + { + uint32_t c = tt[i] & 0xff; + uint32_t to = bz->unz[c]++; + tt[to] |= i << 8; + inv[i] = (to << 8) | c; + } + /* the walk from orig comes round to orig last */ + f = tt[orig] >> 8; + b = orig; + for (i = 0, k = nblock - 1; i < k; i++, k--) + { + uint32_t ef = tt[f], eb = inv[b]; + raw[i] = (uint8_t)ef; + raw[k] = (uint8_t)eb; + f = ef >> 8; + b = eb >> 8; + } + if (i == k) + raw[i] = (uint8_t)tt[f]; + } + else + for (i = 0; i < nblock; i++) + tt[bz->unz[tt[i] & 0xff]++] |= i << 8; + + /* and out, undoing the runs of four and a count */ + { + const uint8_t *raw = bz->raw; + size_t from = out->at; + uint8_t *d = out->dst; + size_t at = out->at; + size_t cap = out->cap; + uint32_t tp = tt[orig] >> 8; + uint32_t last = 0x100; /* no octet */ + unsigned rep = 0; + uint32_t k = 0; + uint32_t c; +/* the next octet of the block, from the walk done or walking */ +#define RBZ_NEXT() \ + if (both) \ + c = raw[k]; \ + else \ + { \ + tp = tt[tp]; \ + c = tp & 0xff; \ + tp >>= 8; \ + } \ + k++ + while (k < nblock) + { + RBZ_NEXT(); + if (at >= cap) + { + out->at = at; + if ((e = rbz_room(out, 1)) != RBZIP2_OK) + return e; + d = out->dst; + cap = out->cap; + } + d[at++] = (uint8_t)c; + if (c != last) + { + last = c; + rep = 1; + continue; + } + if (++rep < 4) + continue; + /* four alike: the next octet counts more of them */ + rep = 0; + if (k == nblock) + break; + RBZ_NEXT(); + if (c > cap - at) + { + out->at = at; + if ((e = rbz_room(out, c)) != RBZIP2_OK) + return e; + d = out->dst; + cap = out->cap; + } + memset(d + at, (int)last, c); + at += c; + } +#undef RBZ_NEXT + out->at = at; + if (rbz_crc(bz, out->dst + from, out->at - from) != stored) + return RBZIP2_ERROR_DATA; + } + } + *crc = stored; + return RBZIP2_OK; +} + +/* The stream at @src: its header read and the block buffers sized for + * it, ready for its first block */ +static int rbz_start(rbzip2_t *bz, const uint8_t *src, size_t src_len) +{ + uint32_t max_block; + + if ( !bz || src_len < 4 + || src[0] != 'B' || src[1] != 'Z' || src[2] != 'h' + || src[3] < '1' || src[3] > '9') + return RBZIP2_ERROR_DATA; + max_block = (uint32_t)(src[3] - '0') * 100000u; + if (bz->tt_cap < max_block) + { + free(bz->tt); + free(bz->inv); + free(bz->raw); + bz->tt = (uint32_t*)malloc(max_block * sizeof(uint32_t)); + bz->inv = (uint32_t*)malloc(max_block * sizeof(uint32_t)); + bz->raw = (uint8_t*)malloc(max_block); + bz->tt_cap = max_block; + if (!bz->tt || !bz->inv || !bz->raw) + { + bz->tt_cap = 0; + return RBZIP2_ERROR_MEM; + } + } + + bz->max_block = max_block; + bz->combined = 0; + bz->in.p = src + 4; + bz->in.end = src + src_len; + bz->in.buf = 0; + bz->in.n = 0; + bz->in.over = 0; + return RBZIP2_OK; +} + +/* The next block, or the end of the stream and its CRC */ +static int rbz_next(rbzip2_t *bz) +{ + uint32_t hi = rbz_bits(&bz->in, 24); + uint32_t lo = rbz_bits(&bz->in, 24); + if (hi == 0x314159u && lo == 0x265359u) + { + uint32_t crc; + int e = rbz_block(bz, &bz->in, bz->max_block, &bz->out, &crc); + if (e != RBZIP2_OK) + return e; + bz->combined = ((bz->combined << 1) | (bz->combined >> 31)) ^ crc; + return RBZIP2_PENDING; + } + if (hi == 0x177245u && lo == 0x385090u) + { + uint32_t want = rbz_bits(&bz->in, 16) << 16; + want |= rbz_bits(&bz->in, 16); + if (rbz_short(&bz->in) || want != bz->combined) + return RBZIP2_ERROR_DATA; + return RBZIP2_OK; + } + return RBZIP2_ERROR_DATA; +} + +/* The rest of the stream rbz_start() began */ +static int rbz_stream(rbzip2_t *bz) +{ + int e; + while ((e = rbz_next(bz)) == RBZIP2_PENDING) { } + return e; +} + +int rbzip2_decode(rbzip2_t *bz, uint8_t *dst, size_t dst_len, + const uint8_t *src, size_t src_len, size_t *wrote) +{ + int e = rbzip2_begin(bz, dst, dst_len, src, src_len); + if (e == RBZIP2_OK) + e = rbz_stream(bz); + if (wrote) + *wrote = e == RBZIP2_OK ? bz->out.at : 0; + return e; +} + +int rbzip2_begin(rbzip2_t *bz, uint8_t *dst, size_t dst_len, + const uint8_t *src, size_t src_len) +{ + if (!bz) + return RBZIP2_ERROR_DATA; + bz->blocks = 0; + bz->out.dst = dst; + bz->out.cap = dst_len; + bz->out.at = 0; + bz->out.grow = 0; + return rbz_start(bz, src, src_len); +} + +int rbzip2_begin_blocks(rbzip2_t *bz, const uint8_t *src, size_t src_len) +{ + if (!bz) + return RBZIP2_ERROR_DATA; + bz->blocks = 1; + return rbz_start(bz, src, src_len); +} + +int rbzip2_step_block(rbzip2_t *bz, const uint8_t **block, size_t *len) +{ + int e; + *block = NULL; + *len = 0; + if (!bz || !bz->blocks) + return RBZIP2_ERROR_DATA; + /* each block from the start of the decoder's own buffer, which + * grows to the largest */ + bz->out.dst = bz->own; + bz->out.cap = bz->own_cap; + bz->out.at = 0; + bz->out.grow = 1; + e = rbz_next(bz); + bz->own = bz->out.dst; + bz->own_cap = bz->out.cap; + if (e == RBZIP2_PENDING) + { + *block = bz->own; + *len = bz->out.at; + } + return e; +} + +int rbzip2_step(rbzip2_t *bz, size_t *wrote) +{ + int e = rbz_next(bz); + if (wrote) + *wrote = bz->out.at; + return e; +} + +int rbzip2_decode_alloc(rbzip2_t *bz, const uint8_t *src, size_t src_len, + uint8_t **dst, size_t *wrote) +{ + int e; + *dst = NULL; + *wrote = 0; + if (!bz) + return RBZIP2_ERROR_DATA; + bz->blocks = 0; + bz->out.dst = NULL; + bz->out.cap = 0; + bz->out.at = 0; + bz->out.grow = 1; + /* about what bzip2 makes of most data, to start */ + if ( (e = rbz_room(&bz->out, src_len * 4)) == RBZIP2_OK + && (e = rbz_start(bz, src, src_len)) == RBZIP2_OK) + e = rbz_stream(bz); + if (e != RBZIP2_OK) + { + free(bz->out.dst); + bz->out.dst = NULL; + return e; + } + *dst = bz->out.dst; + *wrote = bz->out.at; + bz->out.dst = NULL; + return RBZIP2_OK; +} diff --git a/core/libretro-common/encodings/encoding_rzstd.c b/core/libretro-common/encodings/encoding_rzstd.c index 522be08c..75433de4 100644 --- a/core/libretro-common/encodings/encoding_rzstd.c +++ b/core/libretro-common/encodings/encoding_rzstd.c @@ -4110,6 +4110,10 @@ int rzstd_encode(uint8_t *dst, size_t dst_len, const uint8_t *src, * saves. */ { size_t produced = 0; + /* The search moves the recent offsets as it goes; a block + * stored raw instead leaves the decoder's where they were, + * so they go back with it. */ + uint32_t rep_was[3]; rzstd_seq_t *seq = (rzstd_seq_t*)malloc( (take / RZSTD_ENC_MIN_MATCH + 1) * sizeof(rzstd_seq_t)); uint8_t *lits = (uint8_t*)malloc(take); @@ -4117,6 +4121,9 @@ int rzstd_encode(uint8_t *dst, size_t dst_len, const uint8_t *src, size_t lit_len = 0; int ok = 0; + rep_was[0] = rep[0]; + rep_was[1] = rep[1]; + rep_was[2] = rep[2]; if (!hash) { /* Cleared once for the whole input, not once a block. */ @@ -4460,6 +4467,9 @@ int rzstd_encode(uint8_t *dst, size_t dst_len, const uint8_t *src, in += take; continue; } + rep[0] = rep_was[0]; + rep[1] = rep_was[1]; + rep[2] = rep_was[2]; } rzstd_write_block_header(dst + at, (uint32_t)take, diff --git a/core/libretro-common/encodings/encoding_utf.c b/core/libretro-common/encodings/encoding_utf.c index d9363665..8ff36032 100644 --- a/core/libretro-common/encodings/encoding_utf.c +++ b/core/libretro-common/encodings/encoding_utf.c @@ -350,7 +350,7 @@ size_t utf8cpy(char *s, size_t len, const char *in, size_t chars) if (!in || !len) return 0; - while (*sb && chars-- > 0) + while ((size_t)(sb - sb_org) < len - 1 && *sb && chars-- > 0) { /* Stepping over continuation bytes stops at the terminator by * itself, so a truncated sequence cannot overrun the buffer. */ diff --git a/core/libretro-common/features/cpu_class.h b/core/libretro-common/features/cpu_class.h index b5a4e852..c71b0eb5 100644 --- a/core/libretro-common/features/cpu_class.h +++ b/core/libretro-common/features/cpu_class.h @@ -137,12 +137,17 @@ static size_t cpu_class_by_value(const char *leaf, unsigned pct, unsigned long best = 0; size_t i, n = 0; /* 32-bit is plenty: kHz clocks top out below 10^7 and capacities - * at 1024; unsigned long would double the frame past the 2 KiB - * thread-stack budget (PSP, GX). */ - unsigned vals[CPU_CLASS_MAX_IDS]; + * at 1024. On the heap, so the callers' frames stay within the + * 2 KiB thread-stack budget (PSP, GX). */ + unsigned *vals; if (len > CPU_CLASS_MAX_IDS) len = CPU_CLASS_MAX_IDS; + if (!(vals = (unsigned*)malloc(len * sizeof(*vals) + 1))) + { + memset(cls, 0, len); + return 0; + } for (i = 0; i < len; i++) { char path[256]; @@ -158,6 +163,7 @@ static size_t cpu_class_by_value(const char *leaf, unsigned pct, } for (i = 0; i < len; i++) cls[i] = (vals[i] && vals[i] * 100 >= best * pct) ? 1 : 0; + free(vals); return n; } diff --git a/core/libretro-common/features/features_cpu.c b/core/libretro-common/features/features_cpu.c index 40c7b0e6..868b674a 100644 --- a/core/libretro-common/features/features_cpu.c +++ b/core/libretro-common/features/features_cpu.c @@ -39,6 +39,17 @@ * build, where glibc hides it, sees the same one. */ extern long syscall(long number, ...); #endif +#if defined(__linux__) && (defined(__arm__) || defined(__aarch64__)) +#if defined(HAVE_GETAUXVAL) || \ + (defined(__GLIBC__) && ((__GLIBC__ > 2) || \ + (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 16))) || \ + (defined(__ANDROID__) && defined(__ANDROID_API__) && __ANDROID_API__ >= 18) +#define CPU_ARM_GETAUXVAL 1 +#include +#include +#include +#endif +#endif #include #include @@ -109,9 +120,6 @@ extern long syscall(long number, ...); #if defined(HAVE_LIBNX) #include -#elif defined(SWITCH) -#include -#include #endif #if defined(_3DS) @@ -386,7 +394,89 @@ static unsigned char check_arm_cpu_feature(const char* feature) { char line[1024]; unsigned char status = 0; - FILE *fp = fopen("/proc/cpuinfo", "r"); + FILE *fp; + +#ifdef CPU_ARM_GETAUXVAL + unsigned long mask = 0; + unsigned long caps; + unsigned long type = AT_HWCAP; +#if defined(__aarch64__) +#ifdef HWCAP_ASIMD + if (!strcmp(feature, "asimd")) + mask = HWCAP_ASIMD; +#endif +#ifdef HWCAP_AES + if (!strcmp(feature, "aes")) + mask = HWCAP_AES; +#endif +#ifdef HWCAP_CRC32 + if (!strcmp(feature, "crc32")) + mask = HWCAP_CRC32; +#endif +#ifdef HWCAP_SHA1 + if (!strcmp(feature, "sha1")) + mask = HWCAP_SHA1; +#endif +#ifdef HWCAP_SHA2 + if (!strcmp(feature, "sha2")) + mask = HWCAP_SHA2; +#endif +#ifdef HWCAP_SHA512 + if (!strcmp(feature, "sha512")) + mask = HWCAP_SHA512; +#endif +#else +#ifdef HWCAP_NEON + if (!strcmp(feature, "neon")) + mask = HWCAP_NEON; +#endif +#ifdef HWCAP_VFPv3 + if (!strcmp(feature, "vfpv3")) + mask = HWCAP_VFPv3; +#endif +#ifdef HWCAP_VFPv4 + if (!strcmp(feature, "vfpv4")) + mask = HWCAP_VFPv4; +#endif +#if defined(AT_HWCAP2) && defined(HWCAP2_AES) + if (!strcmp(feature, "aes")) + { + mask = HWCAP2_AES; + type = AT_HWCAP2; + } +#endif +#if defined(AT_HWCAP2) && defined(HWCAP2_CRC32) + if (!strcmp(feature, "crc32")) + { + mask = HWCAP2_CRC32; + type = AT_HWCAP2; + } +#endif +#if defined(AT_HWCAP2) && defined(HWCAP2_SHA1) + if (!strcmp(feature, "sha1")) + { + mask = HWCAP2_SHA1; + type = AT_HWCAP2; + } +#endif +#if defined(AT_HWCAP2) && defined(HWCAP2_SHA2) + if (!strcmp(feature, "sha2")) + { + mask = HWCAP2_SHA2; + type = AT_HWCAP2; + } +#endif +#endif + if (mask) + { + errno = 0; + caps = getauxval(type); + if (errno == 0) + return (caps & mask) != 0; + } +#endif + + fp = fopen("/proc/cpuinfo", "r"); if (!fp) return 0; diff --git a/core/libretro-common/file/file_path.c b/core/libretro-common/file/file_path.c index d85289a4..4a102b97 100644 --- a/core/libretro-common/file/file_path.c +++ b/core/libretro-common/file/file_path.c @@ -204,6 +204,9 @@ const char *path_get_archive_delim(const char *path) /* Check ".rar" */ else if (c3 == 'r' && c2 == 'a' && c1 == 'r') return delim; + /* Check ".bz2" */ + else if (c3 == 'b' && c2 == 'z' && delim[-1] == '2') + return delim; } } } @@ -1326,14 +1329,24 @@ size_t fill_pathname_abbreviated_or_relative(char *s, size_t _len; int rel_slashes; const char *trimmed_path, *trimmed_base; - char abs_path[PATH_MAX_LENGTH]; - char ref_path[PATH_MAX_LENGTH]; + /* Both working paths on the heap, to keep the frame within the + * 2 KiB thread-stack budget. */ + char *abs_path = (char*)malloc(2 * PATH_MAX_LENGTH); + char *ref_path; + + if (!abs_path) + { + if (len) + *s = '\0'; + return 0; + } + ref_path = abs_path + PATH_MAX_LENGTH; /* Expand paths which start with :\ to an absolute path */ - fill_pathname_expand_special(abs_path, in_path, sizeof(abs_path)); + fill_pathname_expand_special(abs_path, in_path, PATH_MAX_LENGTH); pathname_conform_slashes_to_os(abs_path); - strlcpy(ref_path, in_refpath, sizeof(ref_path)); + strlcpy(ref_path, in_refpath, PATH_MAX_LENGTH); pathname_conform_slashes_to_os(ref_path); /* Resolve a relative path against the referencing file's directory. @@ -1342,12 +1355,12 @@ size_t fill_pathname_abbreviated_or_relative(char *s, * (only its directory is needed for the join) and never in @s. */ if (!path_is_absolute(abs_path)) { - _len = fill_pathname_basedir(ref_path, ref_path, sizeof(ref_path)); - strlcpy(ref_path + _len, abs_path, sizeof(ref_path) - _len); - path_resolve_realpath(ref_path, sizeof(ref_path), false); - strlcpy(abs_path, ref_path, sizeof(abs_path)); + _len = fill_pathname_basedir(ref_path, ref_path, PATH_MAX_LENGTH); + strlcpy(ref_path + _len, abs_path, PATH_MAX_LENGTH - _len); + path_resolve_realpath(ref_path, PATH_MAX_LENGTH, false); + strlcpy(abs_path, ref_path, PATH_MAX_LENGTH); pathname_conform_slashes_to_os(abs_path); - strlcpy(ref_path, in_refpath, sizeof(ref_path)); + strlcpy(ref_path, in_refpath, PATH_MAX_LENGTH); pathname_conform_slashes_to_os(ref_path); } @@ -1363,7 +1376,8 @@ size_t fill_pathname_abbreviated_or_relative(char *s, /* Use the shortest path, preferring the relative path */ if (rel_slashes <= get_pathname_num_slashes(s)) - return path_relative_to(s, abs_path, ref_path, len); + _len = path_relative_to(s, abs_path, ref_path, len); + free(abs_path); return _len; } diff --git a/core/libretro-common/formats/7z/r7z_archive.c b/core/libretro-common/formats/7z/r7z_archive.c index 65e5f339..201abb1a 100644 --- a/core/libretro-common/formats/7z/r7z_archive.c +++ b/core/libretro-common/formats/7z/r7z_archive.c @@ -57,6 +57,12 @@ #include <7z/r7z_lzma2.h> #include <7z/r7z_bcj2.h> #include <7z/r7z_filters.h> +#include +#ifdef HAVE_RBZIP2 +#include +#endif + +#include "../rar/rrar_ppmd7.h" /* -------------------------------------------------------------------- * Header block ids @@ -91,6 +97,10 @@ /* Coder method ids, as stored in the folder's coder list. */ #define METHOD_COPY 0x00 +#define METHOD_PPMD 0x030401 +#define METHOD_DEFLATE 0x040108 +#define METHOD_DEFLATE64 0x040109 +#define METHOD_BZIP2 0x040202 #define METHOD_DELTA 0x03 #define METHOD_LZMA2 0x21 #define METHOD_LZMA 0x030101 @@ -346,14 +356,19 @@ struct r7z_archive uint8_t *cached_data; size_t cached_len; uint32_t cached_folder; + int cached_borrowed; /* the archive's own bytes: not freed */ + + void *bz; /* the bzip2 decoder, made at the first + BZip2 coder */ /* A folder decode that has been started and paused part way. * - * Only the LZMA and LZMA2 stages pause: they are 97% of the time - * and their decoders already take an output limit. Every other - * coder in a chain, the branch filters included, runs to - * completion in one go because each is a single linear pass of a - * few milliseconds over the whole buffer. + * The LZMA, LZMA2, PPMd, Deflate and BZip2 stages pause: they are + * nearly all of the time, and each decodes in order to an output + * limit, BZip2's a block. + * Every other coder in a chain, the branch filters included, runs + * to completion in one go because each is a single linear pass of + * a few milliseconds over the whole buffer. * * pend_folder is 0xFFFFFFFF when nothing is in flight. */ uint32_t pend_folder; @@ -363,10 +378,14 @@ struct r7z_archive size_t pend_in_len; uint8_t *pend_out; /* buffer being filled */ size_t pend_out_len; - void *pend_dec; /* rlzma2_dec_t, when one is live */ + int pend_pass; /* a Copy stage: pend_out is pend_in */ + void *pend_dec; /* the stage's decoder, when one is + live: see stage_dec_free() */ uint16_t *pend_probs; size_t pend_fed; /* input consumed by this stage */ + size_t pend_made; /* output a Deflate stage has made */ uint32_t pend_base; /* coder_unpack_sizes base for folder */ + uint32_t pend_method; /* the method pend_dec decodes */ /* What a reader on another thread may look at while one thread calls * r7z_archive_decode_step(): the buffer the folder is being decoded @@ -385,6 +404,7 @@ struct r7z_archive * bounded by that port's own decode. */ uint8_t *pend_b2buf[4]; size_t pend_b2len[4]; + int pend_b2borrowed[4]; /* the archive's own bytes: not freed */ uint32_t pend_b2port; /* next port to resolve, 0..4 */ uint32_t pend_b2at; /* index of the BCJ2 coder */ uint32_t pend_b2in; /* input-port base of that coder */ @@ -405,6 +425,19 @@ struct r7z_archive /* Defined with the resumable decoder below; declared here because * r7z_archive_close() has to release a decode left in flight. */ +/* Holds @data, @len octets of folder @folder, as the folder cache, in + * place of what was held; @borrowed when it is the archive's own. */ +static void r7z_cache_hold(r7z_archive_t *a, uint8_t *data, size_t len, + uint32_t folder, int borrowed) +{ + if (!a->cached_borrowed) + free(a->cached_data); + a->cached_data = data; + a->cached_len = len; + a->cached_folder = folder; + a->cached_borrowed = borrowed; +} + static void decode_pending_reset(r7z_archive_t *a); /* -------------------------------------------------------------------- @@ -1389,6 +1422,79 @@ fail: * the end of one. * -------------------------------------------------------------------- */ +/* A PPMd coder part way: the model, the range decoder over the + * coder's whole input, and the octets decoded so far. */ +typedef struct r7z_ppmd +{ + rrar_ppmd7_t m; + rrar_ppmd7_range_t rc; + size_t pos; +} r7z_ppmd_t; + +static void ppmd_close(r7z_ppmd_t *p) +{ + if (!p) + return; + rrar_ppmd7_free(&p->m); + free(p); +} + +/* The PPMd decoder for coder @c over @in. */ +static int ppmd_open(const r7z_archive_t *a, const coder_t *c, + const uint8_t *in, size_t in_len, r7z_ppmd_t **out) +{ + /* order, then the model's memory, little endian */ + const uint8_t *pr = a->props + c->prop_offset; + r7z_ppmd_t *p; + uint32_t mem; + unsigned order; + *out = NULL; + if (c->prop_size < 5) + return R7Z_ERROR_DATA; + order = pr[0]; + mem = (uint32_t)pr[1] | ((uint32_t)pr[2] << 8) + | ((uint32_t)pr[3] << 16) | ((uint32_t)pr[4] << 24); + if (order < 2 || order > 64 || mem < (1u << 11) + || mem > 0xFFFFFFFFu - 12 * 3) + return R7Z_ERROR_DATA; + if (!(p = (r7z_ppmd_t*)malloc(sizeof(*p)))) + return R7Z_ERROR_MEM; + rrar_ppmd7_construct(&p->m); + if (!rrar_ppmd7_alloc(&p->m, mem)) + { + free(p); + return R7Z_ERROR_MEM; + } + rrar_ppmd7_init(&p->m, order); + p->pos = 0; + if (!rrar_ppmd7z_range_init(&p->rc, in, in + in_len)) + { + ppmd_close(p); + return R7Z_ERROR_DATA; + } + *out = p; + return R7Z_OK; +} + +/* Decode on to @limit of @out's @out_len octets; at the end, the + * stream must not have run past its input. */ +static int ppmd_run(r7z_ppmd_t *p, uint8_t *out, size_t limit, + size_t out_len) +{ + size_t i; + for (i = p->pos; i < limit; i++) + { + int sym = rrar_ppmd7_decode_symbol(&p->m, &p->rc); + if (sym < 0) + return R7Z_ERROR_DATA; + out[i] = (uint8_t)sym; + } + p->pos = limit; + if (limit == out_len && p->rc.past > 4) + return R7Z_ERROR_DATA; + return R7Z_OK; +} + static int decode_coder(r7z_archive_t *a, const coder_t *c, const uint8_t *in, size_t in_len, uint8_t *out, size_t out_len) @@ -1522,6 +1628,27 @@ static int decode_coder(r7z_archive_t *a, const coder_t *c, rfilters_armt_decode(0, out, out_len); return R7Z_OK; + case METHOD_ARM64: + { + /* the stream's offset, when it is not 0 */ + uint32_t ip = 0; + if (in_len != out_len) + return R7Z_ERROR_DATA; + if (c->prop_size == 4) + { + const uint8_t *pr = a->props + c->prop_offset; + ip = (uint32_t)pr[0] | ((uint32_t)pr[1] << 8) + | ((uint32_t)pr[2] << 16) | ((uint32_t)pr[3] << 24); + if (ip & 3) + return R7Z_ERROR_DATA; + } + else if (c->prop_size) + return R7Z_ERROR_DATA; + memcpy(out, in, out_len); + rfilters_arm64_decode(ip, out, out_len); + return R7Z_OK; + } + case METHOD_BRA_PPC: if (in_len != out_len) return R7Z_ERROR_DATA; @@ -1543,6 +1670,54 @@ static int decode_coder(r7z_archive_t *a, const coder_t *c, rfilters_ia64_decode(0, out, out_len); return R7Z_OK; + case METHOD_DEFLATE: + { + void *z = rinflate_new(-15); + size_t rd = 0, wr = 0; + int st; + if (!z) + return R7Z_ERROR_MEM; + rinflate_set_in(z, in, in_len); + rinflate_set_out(z, out, out_len); + st = rinflate_process(z, &rd, &wr); + rinflate_free(z); + return (st == RDEFLATE_PROCESS_END && wr == out_len) + ? R7Z_OK : R7Z_ERROR_DATA; + } + + case METHOD_DEFLATE64: + { + size_t wr = 0; + return ( rinflate64_decode(out, out_len, in, in_len, &wr) + == RDEFLATE_PROCESS_END + && wr == out_len) ? R7Z_OK : R7Z_ERROR_DATA; + } + +#ifdef HAVE_RBZIP2 + case METHOD_BZIP2: + { + size_t wr = 0; + int e; + if (!a->bz && !(a->bz = rbzip2_new())) + return R7Z_ERROR_MEM; + e = rbzip2_decode((rbzip2_t*)a->bz, out, out_len, in, in_len, &wr); + if (e == RBZIP2_ERROR_MEM) + return R7Z_ERROR_MEM; + return (e == RBZIP2_OK && wr == out_len) ? R7Z_OK : R7Z_ERROR_DATA; + } +#endif + + case METHOD_PPMD: + { + r7z_ppmd_t *p; + int res = ppmd_open(a, c, in, in_len, &p); + if (res != R7Z_OK) + return res; + res = ppmd_run(p, out, out_len, out_len); + ppmd_close(p); + return res; + } + default: break; } @@ -1587,9 +1762,11 @@ static int folder_input_offsets(r7z_archive_t *a, uint32_t fi, } /* Produce the bytes arriving at one input port of the folder's coder - * graph. Returns a malloc'd buffer the caller frees. */ + * graph: a malloc'd buffer the caller frees, or, for a packed stream + * of an archive in memory, the archive's own bytes, *@borrowed set. */ static int resolve_input(r7z_archive_t *a, uint32_t fi, uint32_t port, - uint32_t coder_out_base, uint8_t **out, size_t *out_len) + uint32_t coder_out_base, uint8_t **out, size_t *out_len, + int *borrowed) { folder_t *f = &a->folders[fi]; uint32_t b; @@ -1625,6 +1802,13 @@ static int resolve_input(r7z_archive_t *a, uint32_t fi, uint32_t port, || (uint64_t)n > (uint64_t)a->len - 32 - off) return R7Z_ERROR_DATA; + *out_len = n; + *borrowed = a->data != NULL; + if (a->data) + { + *out = (uint8_t *)(a->data + 32 + (size_t)off); + return R7Z_OK; + } *out = (uint8_t *)malloc(n ? n : 1); if (!*out) return R7Z_ERROR_MEM; @@ -1634,7 +1818,6 @@ static int resolve_input(r7z_archive_t *a, uint32_t fi, uint32_t port, *out = NULL; return res; } - *out_len = n; return R7Z_OK; } } @@ -1657,7 +1840,8 @@ static int resolve_input(r7z_archive_t *a, uint32_t fi, uint32_t port, { uint8_t *in = NULL; size_t in_len = 0; - size_t want; + int in_borrowed = 0; + size_t want = 0; uint8_t *dst; uint32_t in_base = 0; uint32_t z; @@ -1670,38 +1854,33 @@ static int resolve_input(r7z_archive_t *a, uint32_t fi, uint32_t port, in_base += a->coders[f->coder_first + z].num_in; res = resolve_input(a, fi, in_base, coder_out_base, - &in, &in_len); + &in, &in_len, &in_borrowed); if (res != R7Z_OK) return res; - if (coder_out_base + out_port >= a->num_coder_unpack_sizes) + dst = NULL; + if ( coder_out_base + out_port >= a->num_coder_unpack_sizes + || a->coder_unpack_sizes[coder_out_base + out_port] + > (uint64_t)((size_t)-1)) + res = R7Z_ERROR_DATA; + else { - free(in); - return R7Z_ERROR_DATA; + want = (size_t)a->coder_unpack_sizes[coder_out_base + out_port]; + if (!(dst = (uint8_t *)malloc(want ? want : 1))) + res = R7Z_ERROR_MEM; + else + res = decode_coder(a, c, in, in_len, dst, want); } - if (a->coder_unpack_sizes[coder_out_base + out_port] - > (uint64_t)((size_t)-1)) - { + if (!in_borrowed) free(in); - return R7Z_ERROR_DATA; - } - want = (size_t)a->coder_unpack_sizes[coder_out_base + out_port]; - - dst = (uint8_t *)malloc(want ? want : 1); - if (!dst) - { - free(in); - return R7Z_ERROR_MEM; - } - res = decode_coder(a, c, in, in_len, dst, want); - free(in); if (res != R7Z_OK) { free(dst); return res; } - *out = dst; - *out_len = want; + *out = dst; + *out_len = want; + *borrowed = 0; return R7Z_OK; } seen += c->num_out; @@ -1719,6 +1898,7 @@ static int decode_folder_bcj2(r7z_archive_t *a, uint32_t fi, folder_t *f = &a->folders[fi]; uint8_t *bufs[4]; size_t lens[4]; + int borrowed[4]; uint32_t in_base = 0; uint32_t coder_out_base = 0; uint32_t i; @@ -1728,8 +1908,9 @@ static int decode_folder_bcj2(r7z_archive_t *a, uint32_t fi, for (i = 0; i < 4; i++) { - bufs[i] = NULL; - lens[i] = 0; + bufs[i] = NULL; + lens[i] = 0; + borrowed[i] = 0; } if (a->coders[f->coder_first + bcj2_at].num_in != 4) @@ -1747,7 +1928,7 @@ static int decode_folder_bcj2(r7z_archive_t *a, uint32_t fi, for (i = 0; i < 4; i++) { res = resolve_input(a, fi, in_base + i, coder_out_base, - &bufs[i], &lens[i]); + &bufs[i], &lens[i], &borrowed[i]); if (res != R7Z_OK) goto done; } @@ -1789,13 +1970,15 @@ static int decode_folder_bcj2(r7z_archive_t *a, uint32_t fi, done: for (i = 0; i < 4; i++) - free(bufs[i]); + if (!borrowed[i]) + free(bufs[i]); return res; } -/* Decode one folder in full. Returns a malloc'd buffer of - * f->unpack_size bytes. */ -static int decode_folder(r7z_archive_t *a, uint32_t fi, uint8_t **out) +/* Decode one folder in full: f->unpack_size bytes, malloc'd, or the + * archive's own where no coder changed them, *@borrowed set. */ +static int decode_folder(r7z_archive_t *a, uint32_t fi, uint8_t **out, + int *borrowed) { folder_t *f; uint8_t *cur = NULL; @@ -1821,6 +2004,7 @@ static int decode_folder(r7z_archive_t *a, uint32_t fi, uint8_t **out) if (a->coders[f->coder_first + ci].method == METHOD_BCJ2) bcj2_at = ci; + *borrowed = 0; if (bcj2_at != 0xFFFFFFFFu) return decode_folder_bcj2(a, fi, bcj2_at, out); } @@ -1900,6 +2084,10 @@ static int decode_folder(r7z_archive_t *a, uint32_t fi, uint8_t **out) } next_len = (size_t)a->coder_unpack_sizes[base + ci]; + /* Copy hands its input on as its output */ + if (c->method == METHOD_COPY && next_len == cur_len) + continue; + next = (uint8_t *)malloc(next_len ? next_len : 1); if (!next) { @@ -1936,16 +2124,10 @@ static int decode_folder(r7z_archive_t *a, uint32_t fi, uint8_t **out) return R7Z_ERROR_CRC; } - /* (a folder no coder touched hands out a copy, not the archive) */ - if (!cur_owned) - { - uint8_t *copy = (uint8_t *)malloc(cur_len ? cur_len : 1); - if (!copy) - return R7Z_ERROR_MEM; - memcpy(copy, cur, cur_len); - cur = copy; - } - *out = cur; + /* a folder no coder changed is the archive's own bytes, when it is + * in memory */ + *out = cur; + *borrowed = !cur_owned; return R7Z_OK; } @@ -2107,7 +2289,21 @@ static int decode_encoded_header(r7z_archive_t *a, rd_t *r, if (tmp.num_folders != 1) res = R7Z_ERROR_DATA; else - res = decode_folder(&tmp, 0, &buf); + { + int borrowed = 0; + /* the header is the caller's to free */ + if ( (res = decode_folder(&tmp, 0, &buf, &borrowed)) == R7Z_OK + && borrowed) + { + size_t n = (size_t)tmp.folders[0].unpack_size; + uint8_t *copy = (uint8_t *)malloc(n ? n : 1); + if (!copy) + res = R7Z_ERROR_MEM; + else + memcpy(copy, buf, n); + buf = copy; + } + } } if (res == R7Z_OK) @@ -2116,6 +2312,9 @@ static int decode_encoded_header(r7z_archive_t *a, rd_t *r, *out_len = (size_t)tmp.folders[0].unpack_size; } +#ifdef HAVE_RBZIP2 + rbzip2_free((rbzip2_t*)tmp.bz); +#endif free(tmp.pack_sizes); free(tmp.coders); free(tmp.props); @@ -2266,7 +2465,11 @@ void r7z_archive_close(r7z_archive_t *a) return; decode_pending_reset(a); retro_atomic_store_release_int(&a->prog_state, 0); - free(a->cached_data); + if (!a->cached_borrowed) + free(a->cached_data); +#ifdef HAVE_RBZIP2 + rbzip2_free((rbzip2_t*)a->bz); +#endif free(a->header_buf); free(a->next_buf); free(a->pack_sizes); @@ -2302,9 +2505,10 @@ const r7z_entry_t *r7z_archive_entry(const r7z_archive_t *a, uint32_t index) * single uninterruptible block of work: a 2.8 MiB solid folder takes * about 67 ms, four dropped frames. * - * These two functions do the same work in bounded slices. Only the - * LZMA and LZMA2 stages pause, because they are 97% of the time and - * their decoders already accept an output limit. Every other coder, + * These two functions do the same work in bounded slices. The LZMA, + * LZMA2, PPMd, Deflate and BZip2 stages pause, because they are nearly + * all of the time and each decodes in order to an output limit, BZip2's + * a block. Every other coder, * the branch filters included, is a single linear pass of one to three * milliseconds over the whole buffer, so it runs to completion within * one slice rather than being restructured. @@ -2316,6 +2520,28 @@ const r7z_entry_t *r7z_archive_entry(const r7z_archive_t *a, uint32_t index) * Larger slices scale linearly: 256 KiB is already 7 ms. */ #define R7Z_SLICE_BYTES (64 * 1024) +/* The paused stage's decoder, freed as what it is. */ +static void stage_dec_free(r7z_archive_t *a) +{ + if (a->pend_method == METHOD_PPMD) + ppmd_close((r7z_ppmd_t *)a->pend_dec); +#ifdef HAVE_RBZIP2 + else if (a->pend_method == METHOD_BZIP2) + rbzip2_free((rbzip2_t *)a->pend_dec); +#endif + else if (a->pend_method == METHOD_DEFLATE) + { + if (a->pend_dec) + rinflate_free(a->pend_dec); + } + else + free(a->pend_dec); + free(a->pend_probs); + a->pend_dec = NULL; + a->pend_probs = NULL; + a->pend_method = 0; +} + static void decode_pending_reset(r7z_archive_t *a) { if (!a) @@ -2325,23 +2551,25 @@ static void decode_pending_reset(r7z_archive_t *a) retro_atomic_store_release_int(&a->prog_state, 0); if (!a->pend_in_borrowed) free(a->pend_in); - free(a->pend_out); - free(a->pend_dec); - free(a->pend_probs); + if (!a->pend_pass) + free(a->pend_out); + stage_dec_free(a); a->pend_in = NULL; a->pend_in_borrowed = 0; + a->pend_pass = 0; a->pend_out = NULL; - a->pend_dec = NULL; - a->pend_probs = NULL; a->pend_in_len = 0; a->pend_out_len = 0; a->pend_fed = 0; + a->pend_made = 0; { uint32_t k; for (k = 0; k < 4; k++) { - free(a->pend_b2buf[k]); - a->pend_b2buf[k] = NULL; + if (!a->pend_b2borrowed[k]) + free(a->pend_b2buf[k]); + a->pend_b2buf[k] = NULL; + a->pend_b2borrowed[k] = 0; a->pend_b2len[k] = 0; } } @@ -2361,12 +2589,18 @@ static void decode_pending_reset(r7z_archive_t *a) a->pend_base = 0; } -/* True when this coder is one whose decode can be paused. Both LZMA - * flavours qualify: LZMA2 through r7z_lzma2, plain LZMA through - * r7z_lzma_stream. Everything else is a single linear pass. */ +/* True when this coder is one whose decode can be paused: LZMA2 + * through r7z_lzma2, plain LZMA through r7z_lzma_stream, PPMd a symbol + * at a time, Deflate through rinflate and BZip2 a block at a time. + * Everything else is a single linear pass. */ static int coder_is_resumable(const coder_t *c) { - return (c->method == METHOD_LZMA2 || c->method == METHOD_LZMA); + return ( c->method == METHOD_LZMA2 || c->method == METHOD_LZMA + || c->method == METHOD_PPMD || c->method == METHOD_DEFLATE +#ifdef HAVE_RBZIP2 + || c->method == METHOD_BZIP2 +#endif + ); } /* The cached folder, decoded whole and checked, is there to be read. */ @@ -2466,6 +2700,90 @@ static int decode_slice_lzma(r7z_archive_t *a, int *done) return R7Z_OK; } +/* Run one slice of a paused PPMd stage. */ +static int decode_slice_ppmd(r7z_archive_t *a, int *done) +{ + r7z_ppmd_t *p = (r7z_ppmd_t *)a->pend_dec; + size_t limit = p->pos + R7Z_SLICE_BYTES; + int res; + if (limit > a->pend_out_len) + limit = a->pend_out_len; + res = ppmd_run(p, a->pend_out, limit, a->pend_out_len); + r7z_progress_at(a, p->pos); + *done = (res == R7Z_OK && p->pos == a->pend_out_len); + return res; +} + +/* Run one slice of a paused Deflate stage: its whole input and the + * buffer it decodes into are bound once, and each slice moves the end + * of the output on, so matches reach back into the buffer itself. */ +static int decode_slice_deflate(r7z_archive_t *a, int *done) +{ + size_t limit = a->pend_made + R7Z_SLICE_BYTES; + size_t wr = 0; + int st; + if (limit > a->pend_out_len) + limit = a->pend_out_len; + rinflate_extend_out(a->pend_dec, limit); + st = rinflate_process(a->pend_dec, NULL, &wr); + a->pend_made += wr; + r7z_progress_at(a, a->pend_made); + *done = 0; + if (st == RDEFLATE_PROCESS_END) + { + if (a->pend_made != a->pend_out_len) + return R7Z_ERROR_DATA; + *done = 1; + return R7Z_OK; + } + /* short of the limit, the input ran out; at the end of the + * output, the stream goes on past it */ + if ( st != RDEFLATE_PROCESS_NEXT || a->pend_made < limit + || a->pend_made == a->pend_out_len) + return R7Z_ERROR_DATA; + return R7Z_OK; +} + +#ifdef HAVE_RBZIP2 +/* Run one slice of a paused BZip2 stage: a block, up to 900 000 + * octets, its CRC checked. */ +static int decode_slice_bzip2(r7z_archive_t *a, int *done) +{ + size_t wrote = 0; + int e = rbzip2_step((rbzip2_t *)a->pend_dec, &wrote); + *done = 0; + r7z_progress_at(a, wrote); + if (e == RBZIP2_PENDING) + return R7Z_OK; + if (e == RBZIP2_ERROR_MEM) + return R7Z_ERROR_MEM; + if (e != RBZIP2_OK || wrote != a->pend_out_len) + return R7Z_ERROR_DATA; + *done = 1; + return R7Z_OK; +} +#endif + +/* One slice of whichever paused stage c is. */ +static int decode_slice(r7z_archive_t *a, const coder_t *c, int *done) +{ + switch (c->method) + { + case METHOD_LZMA2: + return decode_slice_lzma2(a, c, done); + case METHOD_LZMA: + return decode_slice_lzma(a, done); + case METHOD_PPMD: + return decode_slice_ppmd(a, done); +#ifdef HAVE_RBZIP2 + case METHOD_BZIP2: + return decode_slice_bzip2(a, done); +#endif + default: + return decode_slice_deflate(a, done); + } +} + /* Set up the stage at a->pend_coder, ready for slicing or immediate * execution. */ static int decode_stage_begin(r7z_archive_t *a, const folder_t *f) @@ -2481,6 +2799,16 @@ static int decode_stage_begin(r7z_archive_t *a, const folder_t *f) out_len = (size_t)a->coder_unpack_sizes[a->pend_base + a->pend_coder]; + /* Copy hands its input on as its output */ + if (c->method == METHOD_COPY && out_len == a->pend_in_len) + { + a->pend_out = a->pend_in; + a->pend_out_len = out_len; + a->pend_fed = 0; + a->pend_pass = 1; + return R7Z_OK; + } + if (!(a->pend_out = (uint8_t *)malloc(out_len ? out_len : 1))) return R7Z_ERROR_MEM; a->pend_out_len = out_len; @@ -2500,6 +2828,34 @@ static int decode_stage_begin(r7z_archive_t *a, const folder_t *f) } /* Stand up a decoder that survives between slices. */ + a->pend_method = c->method; + if (c->method == METHOD_PPMD) + return ppmd_open(a, c, a->pend_in, a->pend_in_len, + (r7z_ppmd_t **)&a->pend_dec); +#ifdef HAVE_RBZIP2 + /* a decoder of its own: a folder decoded whole meanwhile uses the + * archive's */ + if (c->method == METHOD_BZIP2) + { + int e; + if (!(a->pend_dec = rbzip2_new())) + return R7Z_ERROR_MEM; + e = rbzip2_begin((rbzip2_t *)a->pend_dec, a->pend_out, + a->pend_out_len, a->pend_in, a->pend_in_len); + if (e == RBZIP2_ERROR_MEM) + return R7Z_ERROR_MEM; + return e == RBZIP2_OK ? R7Z_OK : R7Z_ERROR_DATA; + } +#endif + if (c->method == METHOD_DEFLATE) + { + if (!(a->pend_dec = rinflate_new(-15))) + return R7Z_ERROR_MEM; + rinflate_set_in(a->pend_dec, a->pend_in, a->pend_in_len); + rinflate_set_out(a->pend_dec, a->pend_out, 0); + a->pend_made = 0; + return R7Z_OK; + } if (c->method == METHOD_LZMA2) { if (c->prop_size < 1) @@ -2561,17 +2917,19 @@ static int decode_stage_begin(r7z_archive_t *a, const folder_t *f) * Returns 1 when the whole chain is done. */ static int decode_stage_finish(r7z_archive_t *a, const folder_t *f) { - free(a->pend_dec); - free(a->pend_probs); - a->pend_dec = NULL; - a->pend_probs = NULL; + stage_dec_free(a); - if (!a->pend_in_borrowed) - free(a->pend_in); - a->pend_in_borrowed = 0; - a->pend_in = a->pend_out; - a->pend_in_len = a->pend_out_len; - a->pend_out = NULL; + if (a->pend_pass) + a->pend_pass = 0; /* pend_in as it was, borrowed or not */ + else + { + if (!a->pend_in_borrowed) + free(a->pend_in); + a->pend_in_borrowed = 0; + a->pend_in = a->pend_out; + a->pend_in_len = a->pend_out_len; + } + a->pend_out = NULL; a->pend_out_len = 0; a->pend_coder++; @@ -2754,13 +3112,16 @@ static int decode_folder_bcj2_slice(r7z_archive_t *a, uint32_t fi, } if (!a->pend_in_borrowed) free(a->pend_in); - a->pend_in_borrowed = 0; - if (!(a->pend_in = (uint8_t *)malloc(n ? n : 1))) + /* where it lies, when the archive is in memory */ + a->pend_in_borrowed = a->data != NULL; + if (a->data) + a->pend_in = (uint8_t *)(a->data + 32 + (size_t)off); + else if (!(a->pend_in = (uint8_t *)malloc(n ? n : 1))) { decode_pending_reset(a); return R7Z_ERROR_MEM; } - if (r7z_fetch(a, 32 + off, a->pend_in, n) != R7Z_OK) + else if (r7z_fetch(a, 32 + off, a->pend_in, n) != R7Z_OK) { decode_pending_reset(a); return R7Z_ERROR_DATA; @@ -2781,9 +3142,7 @@ static int decode_folder_bcj2_slice(r7z_archive_t *a, uint32_t fi, { int stage_done = 0; - res = (pc->method == METHOD_LZMA2) - ? decode_slice_lzma2(a, pc, &stage_done) - : decode_slice_lzma(a, &stage_done); + res = decode_slice(a, pc, &stage_done); if (res != R7Z_OK) { @@ -2794,13 +3153,11 @@ static int decode_folder_bcj2_slice(r7z_archive_t *a, uint32_t fi, return R7Z_OK; /* Port complete: hand its buffer over and move on. */ - free(a->pend_dec); - free(a->pend_probs); - a->pend_dec = NULL; - a->pend_probs = NULL; + stage_dec_free(a); - a->pend_b2buf[i] = a->pend_out; - a->pend_b2len[i] = a->pend_out_len; + a->pend_b2buf[i] = a->pend_out; + a->pend_b2len[i] = a->pend_out_len; + a->pend_b2borrowed[i] = 0; a->pend_out = NULL; a->pend_out_len = 0; a->pend_b2port++; @@ -2813,7 +3170,7 @@ static int decode_folder_bcj2_slice(r7z_archive_t *a, uint32_t fi, * whose port is fed by a chain. See the coverage note in * r7z_bcj2.h before changing this. */ res = resolve_input(a, fi, a->pend_b2in + i, a->pend_base, - &a->pend_b2buf[i], &a->pend_b2len[i]); + &a->pend_b2buf[i], &a->pend_b2len[i], &a->pend_b2borrowed[i]); if (res != R7Z_OK) { decode_pending_reset(a); @@ -2875,10 +3232,7 @@ static int decode_folder_bcj2_slice(r7z_archive_t *a, uint32_t fi, } retro_atomic_store_release_int(&a->prog_state, 0); - free(a->cached_data); - a->cached_data = a->pend_b2dst; - a->cached_len = a->pend_b2dstlen; - a->cached_folder = fi; + r7z_cache_hold(a, a->pend_b2dst, a->pend_b2dstlen, fi, 0); a->pend_b2dst = NULL; decode_pending_reset(a); @@ -2997,9 +3351,7 @@ static int decode_folder_slice(r7z_archive_t *a, uint32_t fi, int *done) { int stage_done = 0; - res = (c->method == METHOD_LZMA2) - ? decode_slice_lzma2(a, c, &stage_done) - : decode_slice_lzma(a, &stage_done); + res = decode_slice(a, c, &stage_done); if (res != R7Z_OK) { @@ -3012,8 +3364,8 @@ static int decode_folder_slice(r7z_archive_t *a, uint32_t fi, int *done) else { /* Not sliceable: run it whole. One to three milliseconds. */ - res = decode_coder(a, c, a->pend_in, a->pend_in_len, - a->pend_out, a->pend_out_len); + res = a->pend_pass ? R7Z_OK : decode_coder(a, c, a->pend_in, + a->pend_in_len, a->pend_out, a->pend_out_len); if (res != R7Z_OK) { decode_pending_reset(a); @@ -3044,28 +3396,15 @@ static int decode_folder_slice(r7z_archive_t *a, uint32_t fi, int *done) return R7Z_ERROR_CRC; } - /* (a folder no coder touched is kept as a copy, not as the archive) */ - if (a->pend_in_borrowed) - { - uint8_t *copy = (uint8_t *)malloc(a->pend_in_len ? a->pend_in_len : 1); - if (!copy) - { - decode_pending_reset(a); - return R7Z_ERROR_MEM; - } - memcpy(copy, a->pend_in, a->pend_in_len); - a->pend_in = copy; - a->pend_in_borrowed = 0; - } + /* a folder no coder touched is held where it lies, when the + * archive is in memory */ retro_atomic_store_release_int(&a->prog_state, 0); - free(a->cached_data); - a->cached_data = a->pend_in; - a->cached_len = a->pend_in_len; - a->cached_folder = fi; + r7z_cache_hold(a, a->pend_in, a->pend_in_len, fi, a->pend_in_borrowed); r7z_progress_held(a); - a->pend_in = NULL; - a->pend_in_len = 0; + a->pend_in = NULL; + a->pend_in_len = 0; + a->pend_in_borrowed = 0; decode_pending_reset(a); *done = 1; @@ -3151,15 +3490,15 @@ int r7z_archive_extract_slice(r7z_archive_t *a, uint32_t index, * correct, and the caller sees one long call rather than many * short ones. */ uint8_t *folder_data; + int folder_borrowed = 0; - if ((res = decode_folder(a, e->folder, &folder_data)) != R7Z_OK) + if ((res = decode_folder(a, e->folder, &folder_data, &folder_borrowed)) != R7Z_OK) return res; retro_atomic_store_release_int(&a->prog_state, 0); - free(a->cached_data); - a->cached_data = folder_data; - a->cached_len = (size_t)a->folders[e->folder].unpack_size; - a->cached_folder = e->folder; + r7z_cache_hold(a, folder_data, + (size_t)a->folders[e->folder].unpack_size, e->folder, + folder_borrowed); return extract_from_cache(a, e, out, out_len); } @@ -3177,6 +3516,7 @@ int r7z_archive_extract(r7z_archive_t *a, uint32_t index, { const r7z_entry_t *e; uint8_t *folder_data; + int folder_borrowed = 0; uint8_t *buf; int res; @@ -3212,15 +3552,14 @@ int r7z_archive_extract(r7z_archive_t *a, uint32_t index, folder_data = a->cached_data; else { - res = decode_folder(a, e->folder, &folder_data); + res = decode_folder(a, e->folder, &folder_data, &folder_borrowed); if (res != R7Z_OK) return res; retro_atomic_store_release_int(&a->prog_state, 0); - free(a->cached_data); - a->cached_data = folder_data; - a->cached_len = (size_t)a->folders[e->folder].unpack_size; - a->cached_folder = e->folder; + r7z_cache_hold(a, folder_data, + (size_t)a->folders[e->folder].unpack_size, e->folder, + folder_borrowed); } { @@ -3279,23 +3618,33 @@ int r7z_archive_extract_detach(r7z_archive_t *a, uint32_t index, *out_len = 0; if (a->cached_folder == e->folder && a->cached_data) + { + /* the archive's own bytes are not the caller's to have */ + if (a->cached_borrowed) + return r7z_archive_extract(a, index, out, out_len); buf = a->cached_data; + } else { + int borrowed = 0; retro_atomic_store_release_int(&a->prog_state, 0); - free(a->cached_data); - a->cached_data = NULL; - a->cached_folder = 0xFFFFFFFFu; - if ((res = decode_folder(a, e->folder, &buf)) != R7Z_OK) + r7z_cache_hold(a, NULL, 0, 0xFFFFFFFFu, 0); + if ((res = decode_folder(a, e->folder, &buf, &borrowed)) != R7Z_OK) return res; + if (borrowed) + { + r7z_cache_hold(a, buf, (size_t)e->size, e->folder, 1); + return r7z_archive_extract(a, index, out, out_len); + } } /* The folder's output is the member: hand the buffer over rather * than keep it cached and copy it. */ retro_atomic_store_release_int(&a->prog_state, 0); - a->cached_data = NULL; - a->cached_len = 0; - a->cached_folder = 0xFFFFFFFFu; + a->cached_data = NULL; + a->cached_len = 0; + a->cached_folder = 0xFFFFFFFFu; + a->cached_borrowed = 0; if (e->has_crc && encoding_crc32(0, buf, (size_t)e->size) != e->crc) { @@ -3313,6 +3662,7 @@ int r7z_archive_entry_borrow(r7z_archive_t *a, uint32_t index, { const r7z_entry_t *e; uint8_t *folder_data; + int folder_borrowed = 0; int res; if (!a || !out || !out_len) @@ -3335,13 +3685,12 @@ int r7z_archive_entry_borrow(r7z_archive_t *a, uint32_t index, folder_data = a->cached_data; else { - if ((res = decode_folder(a, e->folder, &folder_data)) != R7Z_OK) + if ((res = decode_folder(a, e->folder, &folder_data, &folder_borrowed)) != R7Z_OK) return res; retro_atomic_store_release_int(&a->prog_state, 0); - free(a->cached_data); - a->cached_data = folder_data; - a->cached_len = (size_t)a->folders[e->folder].unpack_size; - a->cached_folder = e->folder; + r7z_cache_hold(a, folder_data, + (size_t)a->folders[e->folder].unpack_size, e->folder, + folder_borrowed); } if (e->offset_in_folder > a->cached_len @@ -3390,14 +3739,14 @@ int r7z_archive_decode_step(r7z_archive_t *a, uint32_t index) { /* A shape the slicer will not take: whole, in this one call. */ uint8_t *folder_data; + int folder_borrowed = 0; - if ((res = decode_folder(a, e->folder, &folder_data)) != R7Z_OK) + if ((res = decode_folder(a, e->folder, &folder_data, &folder_borrowed)) != R7Z_OK) return res; retro_atomic_store_release_int(&a->prog_state, 0); - free(a->cached_data); - a->cached_data = folder_data; - a->cached_len = (size_t)a->folders[e->folder].unpack_size; - a->cached_folder = e->folder; + r7z_cache_hold(a, folder_data, + (size_t)a->folders[e->folder].unpack_size, e->folder, + folder_borrowed); r7z_progress_held(a); return R7Z_OK; } diff --git a/core/libretro-common/formats/7z/r7z_filters.c b/core/libretro-common/formats/7z/r7z_filters.c index 002bb570..ac010d2e 100644 --- a/core/libretro-common/formats/7z/r7z_filters.c +++ b/core/libretro-common/formats/7z/r7z_filters.c @@ -398,3 +398,38 @@ size_t rfilters_ia64_decode(uint32_t ip, uint8_t *data, size_t len) return i; } + +size_t rfilters_arm64_decode(uint32_t ip, uint8_t *data, size_t len) +{ + size_t i; + + len &= ~(size_t)3; + for (i = 0; i < len; i += 4) + { + uint32_t pc = ip + (uint32_t)i; + uint32_t v = rf_get_le32(data + i); + + if ((v >> 26) == 0x25) + { + /* BL: a 26-bit word offset */ + v = 0x94000000 | ((v - (pc >> 2)) & 0x03FFFFFF); + rf_set_le32(data + i, v); + } + else if ((v & 0x9F000000) == 0x90000000) + { + /* ADRP: a 21-bit page offset, converted only within +-512 MiB, + * the range one test of the sum covers */ + uint32_t src = ((v >> 29) & 3) | ((v >> 3) & 0x001FFFFC); + uint32_t dst; + if ((src + 0x00020000) & 0x001C0000) + continue; + dst = src - (pc >> 12); + v &= 0x9000001F; + v |= (dst & 3) << 29; + v |= (dst & 0x0003FFFC) << 3; + v |= (0u - (dst & 0x00020000)) & 0x00E00000; + rf_set_le32(data + i, v); + } + } + return len; +} diff --git a/core/libretro-common/formats/chd/rchd.c b/core/libretro-common/formats/chd/rchd.c index 8f636b46..653781c7 100644 --- a/core/libretro-common/formats/chd/rchd.c +++ b/core/libretro-common/formats/chd/rchd.c @@ -356,6 +356,7 @@ struct rchd * audio/video pays nothing for them. */ uint16_t *av_lookup; int16_t *av_samples; + uint32_t av_samples_capacity; struct rchd_av_scratch *av_scratch; /* One decoded hunk, kept so a range spanning several hunks, or two @@ -1550,11 +1551,15 @@ static int rchd_decode_avhuff(rchd_t *chd, const uint8_t *src, if (mode == 0xffff) { - if (!chd->av_samples) + if (samples > chd->av_samples_capacity) { - chd->av_samples = (int16_t*)malloc((size_t)samples * sizeof(int16_t)); - if (!chd->av_samples) + int16_t *grown = (int16_t*)realloc(chd->av_samples, + (size_t)samples * sizeof(int16_t)); + + if (!grown) return RCHD_ERROR_MEM; + chd->av_samples = grown; + chd->av_samples_capacity = samples; } fmt.sample_rate = 44100; diff --git a/core/libretro-common/formats/rar/rrar_ppmd7.c b/core/libretro-common/formats/rar/rrar_ppmd7.c index 415bf3fa..56aeb157 100644 --- a/core/libretro-common/formats/rar/rrar_ppmd7.c +++ b/core/libretro-common/formats/rar/rrar_ppmd7.c @@ -146,6 +146,16 @@ static INLINE uint32_t ppmd_rc_byte(rrar_ppmd7_range_t *rc) static INLINE void ppmd_rc_normalize(rrar_ppmd7_range_t *rc) { + if (rc->sevenz) + { + while (rc->range < PPMD_RC_TOP) + { + rc->code = (rc->code << 8) | ppmd_rc_byte(rc); + rc->range <<= 8; + rc->low <<= 8; + } + return; + } for (;;) { if ((rc->low ^ (rc->low + rc->range)) >= PPMD_RC_TOP) @@ -180,6 +190,21 @@ static INLINE void ppmd_rc_decode(rrar_ppmd7_range_t *rc, * size0 * range exactly when the quotient is under size0. */ static INLINE unsigned ppmd_rc_bit(rrar_ppmd7_range_t *rc, uint32_t size0) { + if (rc->sevenz) + { + /* 7z's gives the 1 side what the scale does not divide */ + uint32_t bound = (rc->range >> (PPMD_INT_BITS + PPMD_PERIOD_BITS)) * size0; + if (rc->code - rc->low < bound) + { + rc->range = bound; + ppmd_rc_normalize(rc); + return 0; + } + rc->low += bound; + rc->range -= bound; + ppmd_rc_normalize(rc); + return 1; + } rc->range >>= PPMD_INT_BITS + PPMD_PERIOD_BITS; if (rc->code - rc->low < size0 * rc->range) { @@ -1031,13 +1056,25 @@ bool rrar_ppmd7_range_init(rrar_ppmd7_range_t *rc, const uint8_t *data, const uint8_t *end, uint32_t past) { unsigned i; - rc->p = data; - rc->end = end; - rc->past = past; - rc->low = 0; - rc->range = 0xFFFFFFFFu; - rc->code = 0; + rc->p = data; + rc->end = end; + rc->past = past; + rc->low = 0; + rc->range = 0xFFFFFFFFu; + rc->code = 0; + rc->sevenz = 0; for (i = 0; i < 4; i++) rc->code = (rc->code << 8) | ppmd_rc_byte(rc); return rc->code < 0xFFFFFFFFu; } + +bool rrar_ppmd7z_range_init(rrar_ppmd7_range_t *rc, const uint8_t *data, + const uint8_t *end) +{ + if (data >= end || *data) + return false; + if (!rrar_ppmd7_range_init(rc, data + 1, end, 0)) + return false; + rc->sevenz = 1; + return true; +} diff --git a/core/libretro-common/formats/rar/rrar_ppmd7.h b/core/libretro-common/formats/rar/rrar_ppmd7.h index 29486fe1..aa3e8703 100644 --- a/core/libretro-common/formats/rar/rrar_ppmd7.h +++ b/core/libretro-common/formats/rar/rrar_ppmd7.h @@ -20,7 +20,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* The PPMd variant H model RAR 2.9 packs with, and RAR's range decoder. +/* The PPMd variant H model RAR 2.9 packs with, and RAR's range decoder + * and 7z's. * * PPMd var.H is Dmitry Shkarin's prediction by partial matching (public * domain, 2001); RAR pairs it with a carry-less range coder of its own. @@ -92,7 +93,10 @@ typedef struct rrar_ppmd7 uint8_t hb2flag[256]; } rrar_ppmd7_t; -/* RAR's range decoder, over a span of the packed data. */ +/* The range decoder, RAR's or 7z's, over a span of the packed data. + * 7z's renormalises on the range alone, without RAR's carry-less step, + * and a binary decision leaves the 1 side what the scale does not + * divide. */ typedef struct rrar_ppmd7_range { const uint8_t *p; @@ -101,6 +105,7 @@ typedef struct rrar_ppmd7_range uint32_t code; uint32_t low; uint32_t past; /* bytes read past the end, as zeros */ + int sevenz; /* 7z's coder */ } rrar_ppmd7_range_t; /* Once, before anything else. */ @@ -120,6 +125,11 @@ void rrar_ppmd7_init(rrar_ppmd7_t *p, unsigned max_order); bool rrar_ppmd7_range_init(rrar_ppmd7_range_t *rc, const uint8_t *data, const uint8_t *end, uint32_t past); +/* 7z's decoder at @data, a PPMd coder's whole stream: a zero octet, + * then the code. false if the bytes cannot start one. */ +bool rrar_ppmd7z_range_init(rrar_ppmd7_range_t *rc, const uint8_t *data, + const uint8_t *end); + /* The next byte, or a negative number if the data is not PPMd's. */ int rrar_ppmd7_decode_symbol(rrar_ppmd7_t *p, rrar_ppmd7_range_t *rc); diff --git a/core/libretro-common/include/7z/r7z_archive.h b/core/libretro-common/include/7z/r7z_archive.h index 55670164..9323085b 100644 --- a/core/libretro-common/include/7z/r7z_archive.h +++ b/core/libretro-common/include/7z/r7z_archive.h @@ -277,13 +277,13 @@ int r7z_archive_decode_step(r7z_archive_t *a, uint32_t index); * @avail : receives how many of them are there to be read * * How much of entry @index has been decoded. A folder whose last coder - * decodes in order - LZMA or LZMA2, which is to say nearly all of them - - * fills the buffer it will be held in from the front, and what is in it - * is final: @data points into it, at the same place from the first call - * to the last and for as long as the folder is held, and @avail grows - * to the entry's size. A folder that ends in a filter gives nothing - * until it is done, and then everything. The bytes are not checked - * against a CRC until the folder is. + * decodes in order - LZMA, LZMA2, PPMd, Deflate or BZip2, which is to + * say nearly all of them - fills the buffer it will be held in from the + * front, and what is in it is final: @data points into it, at the same + * place from the first call to the last and for as long as the folder + * is held, and @avail grows to the entry's size. A folder that ends in + * a filter gives nothing until it is done, and then everything. The + * bytes are not checked against a CRC until the folder is. * * Any thread. * diff --git a/core/libretro-common/include/7z/r7z_filters.h b/core/libretro-common/include/7z/r7z_filters.h index b7543fc0..cb21da0b 100644 --- a/core/libretro-common/include/7z/r7z_filters.h +++ b/core/libretro-common/include/7z/r7z_filters.h @@ -87,6 +87,8 @@ size_t rfilters_armt_decode (uint32_t ip, uint8_t *data, size_t len); size_t rfilters_ppc_decode (uint32_t ip, uint8_t *data, size_t len); size_t rfilters_sparc_decode(uint32_t ip, uint8_t *data, size_t len); size_t rfilters_ia64_decode (uint32_t ip, uint8_t *data, size_t len); +/* ARM64: BL, and ADRP within +-512 MiB of the instruction */ +size_t rfilters_arm64_decode(uint32_t ip, uint8_t *data, size_t len); RETRO_END_DECLS diff --git a/core/libretro-common/include/cdrom/cdrom.h b/core/libretro-common/include/cdrom/cdrom.h index 732ee67e..f95a3200 100644 --- a/core/libretro-common/include/cdrom/cdrom.h +++ b/core/libretro-common/include/cdrom/cdrom.h @@ -60,7 +60,7 @@ typedef struct bool audio; } cdrom_track_t; -typedef struct +typedef struct cdrom_toc { cdrom_track_t track[99]; /* unsigned alignment */ cdrom_group_timeouts_t timeouts; /* unsigned short alignment */ @@ -68,6 +68,7 @@ typedef struct char drive; } cdrom_toc_t; +/* Legacy helpers use absolute MSF frames (device LBA + 150). */ void cdrom_lba_to_msf(unsigned lba, unsigned char *min, unsigned char *sec, unsigned char *frame); unsigned cdrom_msf_to_lba(unsigned char min, unsigned char sec, unsigned char frame); @@ -81,8 +82,31 @@ int cdrom_write_cue(libretro_vfs_implementation_file *stream, char **out_buf, si /* needs 32 bytes for full vendor, product and version */ int cdrom_get_inquiry(libretro_vfs_implementation_file *stream, char *s, size_t len, bool *is_cdrom); +/* Returns 0 only for a complete read. On error the destination may be + * partly filled and must be discarded; VFS leaves the position unchanged. */ int cdrom_read(libretro_vfs_implementation_file *stream, cdrom_group_timeouts_t *timeouts, unsigned char min, unsigned char sec, unsigned char frame, void *s, size_t len, size_t skip); +/* Cooked optical-disc access (DVD uses 2048-byte logical blocks). + * Supported transports: Linux SG_IO and Windows SCSI pass-through. + * Capacity is a block count, including the last LBA. Returns 0 on + * success, 1 on error/unsupported transport; outputs stay zero on error. + * READ CAPACITY (10)'s saturated capacity is rejected, not truncated. */ +/* MMC current media profile, zeroed on error. DVD profiles are 0x10-0x17, + * 0x1a, 0x1b, 0x2a and 0x2b; CD profiles are 0x08-0x0a. */ +int cdrom_get_media_profile(libretro_vfs_implementation_file *stream, + uint16_t *profile); + +int cdrom_get_capacity(libretro_vfs_implementation_file *stream, + uint64_t *blocks, uint32_t *block_size); + +/* Read count DVD blocks from a 32-bit LBA. The caller supplies count*2048 + * writable bytes. Aligned buffers go directly to the device; other buffers + * use a bounded bounce buffer. Returns 0 only after the complete transfer. + * On failure the destination may contain a partially completed transfer. + * Does not retry or sleep: recovery belongs to the off-thread caller. */ +int cdrom_read_blocks(libretro_vfs_implementation_file *stream, + uint32_t lba, uint32_t count, void *s); + int cdrom_set_read_speed(libretro_vfs_implementation_file *stream, unsigned speed); int cdrom_stop(libretro_vfs_implementation_file *stream); @@ -118,6 +142,17 @@ bool cdrom_get_timeouts(libretro_vfs_implementation_file *stream, cdrom_group_ti bool cdrom_has_atip(libretro_vfs_implementation_file *stream); +/* Metadata/control handle: no track or filesystem data is exposed. */ +size_t cdrom_device_filldevicepath(char *s, size_t len, char drive); + +/* Build a cooked-image URI for a Linux/Windows drive. Returns required + * length excluding NUL, or zero for invalid/unsupported arguments. */ +size_t cdrom_device_fillimagepath(char *s, size_t len, char drive); + +/* Build a cue or track URI for a drive. Same contract as + * cdrom_device_fillimagepath: output is always NUL-terminated and the + * required length excluding NUL is returned, so a result >= len means + * the path was truncated. */ size_t cdrom_device_fillpath(char *s, size_t len, char drive, unsigned char track, bool is_cue); RETRO_END_DECLS diff --git a/core/libretro-common/include/encodings/deflate.h b/core/libretro-common/include/encodings/deflate.h index e2be7eb9..ce748b65 100644 --- a/core/libretro-common/include/encodings/deflate.h +++ b/core/libretro-common/include/encodings/deflate.h @@ -87,6 +87,19 @@ void rinflate_reset(void *stream, int window_bits); void rinflate_set_in(void *stream, const uint8_t *in, size_t in_size); void rinflate_set_out(void *stream, uint8_t *out, size_t out_size); +/** + * rinflate_extend_out: + * @stream : stream returned by rinflate_new() + * @out_size : the output's new size, from where rinflate_set_out() + * put its start + * + * Moves the end of the bound output, keeping what is written and where: + * a decode into one buffer runs a part at a time, its matches reaching + * back into the buffer itself. A size short of what is written is + * ignored. + **/ +void rinflate_extend_out(void *stream, size_t out_size); + /** * rinflate_set_dictionary: * @@ -110,6 +123,57 @@ void rinflate_set_start_bit(void *stream, int bits); * one of the RDEFLATE_PROCESS_* codes. */ int rinflate_process(void *stream, size_t *read, size_t *wrote); +/** + * rinflate64_decode: + * @dst : where the stream decodes to, @dst_len octets of room + * @src : a raw Deflate64 stream (no header), @src_len octets + * @wrote : receives the octets decoded (may be NULL) + * + * Deflate64 - RFC 1951 with a 64 KiB window, a 16-bit length extension + * and distance codes 30 and 31 - decoded whole, in one call, into a + * buffer that holds all of it, as zip (method 9) and 7z store it. + * + * Returns: RDEFLATE_PROCESS_END, or RDEFLATE_PROCESS_ERROR for a stream + * that is malformed, ends early or decodes to more than @dst_len. + **/ +int rinflate64_decode(uint8_t *dst, size_t dst_len, + const uint8_t *src, size_t src_len, size_t *wrote); + +/* What rinflate64_process() leaves unread of an input that is not the + * last: fewer octets than this, which the next call is given again in + * front of what follows them. */ +#define RINFLATE64_LOOKAHEAD 1024 + +/** + * rinflate64_new: + * + * A Deflate64 decoder that runs a piece at a time: into a buffer of + * the caller's that need not hold the whole stream, from input that + * can arrive in parts. It keeps the last 64 KiB it decoded for the + * matches that reach back into it. NULL when out of memory. + **/ +void *rinflate64_new(void); +void rinflate64_free(void *stream); + +/** + * rinflate64_process: + * @in : the next of the stream, @in_len octets + * @final : nonzero when nothing follows @in + * @consumed : receives how many of @in were taken (may be NULL); an + * input that is not the last is taken up to fewer than + * RINFLATE64_LOOKAHEAD octets from its end, and the rest + * goes first in the next call's + * @out : where to decode to, @out_len octets of room + * @wrote : receives the octets decoded into @out (may be NULL) + * + * Returns: RDEFLATE_PROCESS_END at the end of the stream, + * RDEFLATE_PROCESS_NEXT when @out is full or more input is wanted, or + * RDEFLATE_PROCESS_ERROR. + **/ +int rinflate64_process(void *stream, const uint8_t *in, size_t in_len, + int final, size_t *consumed, uint8_t *out, size_t out_len, + size_t *wrote); + /* -------- compression (deflate) -------- */ /* level: 0 (store) .. 9 (maximum). */ diff --git a/core/libretro-common/include/encodings/rbzip2.h b/core/libretro-common/include/encodings/rbzip2.h new file mode 100644 index 00000000..8e09803c --- /dev/null +++ b/core/libretro-common/include/encodings/rbzip2.h @@ -0,0 +1,149 @@ +/* Copyright (C) 2010-2026 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (rbzip2.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. + */ + +/* Clean-room decoder for the bzip2 stream format. + * + * A stream is "BZh", a block size digit, then blocks: each the + * Burrows-Wheeler transform of up to 100 000 x digit octets, coded as + * move-to-front ranks with runs of rank 0 in bijective base 2 and two + * to six Huffman tables, under a run-length coding of the input + * applied before the transform. Each block carries the CRC of what it + * decodes to, and the end of the stream the combination of them all; + * both are checked. + * + * Decoding only, of one whole stream held in memory: into a buffer + * already sized for it, as the WIA disc image format and zip archives + * store theirs, or into one grown as it goes, for a .bz2 file. Blocks marked randomised, which no bzip2 has written since + * 0.9.5, are refused. No file I/O. */ + +#ifndef __LIBRETRO_SDK_FORMAT_RBZIP2_H__ +#define __LIBRETRO_SDK_FORMAT_RBZIP2_H__ + +#include +#include + +#include + +RETRO_BEGIN_DECLS + +#define RBZIP2_PENDING 1 /* rbzip2_step(): a block, and more to come */ +#define RBZIP2_OK 0 +#define RBZIP2_ERROR_DATA -1 /* not a stream, corrupt, or a CRC fails */ +#define RBZIP2_ERROR_SPACE -2 /* decodes to more than the buffer holds */ +#define RBZIP2_ERROR_MEM -3 + +typedef struct rbzip2 rbzip2_t; + +/* A decoder; its block buffer is allocated at the first block and kept + * for the next stream. NULL when out of memory. */ +rbzip2_t *rbzip2_new(void); + +void rbzip2_free(rbzip2_t *bz); + +/** + * rbzip2_decode: + * @dst : where the stream decodes to + * @dst_len : capacity of @dst + * @src : the stream, from "BZh" on + * @src_len : octets at @src; anything after the stream's end is ignored + * @wrote : receives the octets decoded (may be NULL) + * + * Returns: RBZIP2_OK, or an RBZIP2_ERROR_* value. + **/ +int rbzip2_decode(rbzip2_t *bz, uint8_t *dst, size_t dst_len, + const uint8_t *src, size_t src_len, size_t *wrote); + +/** + * rbzip2_begin: + * @dst : where the stream decodes to + * @dst_len : capacity of @dst + * @src : the stream, from "BZh" on, which stays where it is + * until the stream is done + * @src_len : octets at @src + * + * The same, a block at a time: binds the stream and its output, for + * rbzip2_step() to decode. A block is at most 900 000 octets. + * + * Returns: RBZIP2_OK, or an RBZIP2_ERROR_* value. + **/ +int rbzip2_begin(rbzip2_t *bz, uint8_t *dst, size_t dst_len, + const uint8_t *src, size_t src_len); + +/** + * rbzip2_step: + * @wrote : receives the octets decoded so far (may be NULL), what is + * in @dst before them final + * + * Decodes the next block of the stream rbzip2_begin() bound, its CRC + * checked, or reads the end of the stream and checks the CRC of them + * all. + * + * Returns: RBZIP2_PENDING after a block, RBZIP2_OK at the end, or an + * RBZIP2_ERROR_* value. + **/ +int rbzip2_step(rbzip2_t *bz, size_t *wrote); + +/** + * rbzip2_begin_blocks: + * @src : the stream, from "BZh" on, which stays where it is until + * the stream is done + * @src_len : octets at @src + * + * The same, each block into the decoder's own buffer, for a stream + * whose whole output is not wanted in one place. The buffer grows to + * the largest block, which the run-length coding before the transform + * lets reach 51 times the block size. + * + * Returns: RBZIP2_OK, or an RBZIP2_ERROR_* value. + **/ +int rbzip2_begin_blocks(rbzip2_t *bz, const uint8_t *src, size_t src_len); + +/** + * rbzip2_step_block: + * @block : receives where the block's octets are, until the next call + * @len : receives how many + * + * The next block of the stream rbzip2_begin_blocks() bound, its CRC + * checked, or the end of the stream and the CRC of them all. + * + * Returns: RBZIP2_PENDING with a block, RBZIP2_OK at the end, or an + * RBZIP2_ERROR_* value. + **/ +int rbzip2_step_block(rbzip2_t *bz, const uint8_t **block, size_t *len); + +/** + * rbzip2_decode_alloc: + * @dst : receives the decoded octets, allocated with malloc() and + * the caller's to free; one octet past them is room to spare + * @wrote : receives how many there are + * + * The same, for a stream whose size is not known: the buffer grows as + * blocks are decoded into it. + * + * Returns: RBZIP2_OK, or an RBZIP2_ERROR_* value, with *@dst NULL. + **/ +int rbzip2_decode_alloc(rbzip2_t *bz, const uint8_t *src, size_t src_len, + uint8_t **dst, size_t *wrote); + +RETRO_END_DECLS + +#endif diff --git a/core/libretro-common/include/encodings/rzstd.h b/core/libretro-common/include/encodings/rzstd.h index 3cc8a240..7e92455e 100644 --- a/core/libretro-common/include/encodings/rzstd.h +++ b/core/libretro-common/include/encodings/rzstd.h @@ -54,7 +54,7 @@ * compressed size before it allocates. * This is the only caller that compresses, * and the reason the encoder exists at all. - * cheevos/cheevos_rvz.c decompresses one-shot, and reads a + * formats/rvz/rvz.c decompresses one-shot, and reads a * frame's declared content size to size * its buffer. * file/archive_file_zstd.c decompresses an archive member, and diff --git a/core/libretro-common/include/glsym/glsym_es3.h b/core/libretro-common/include/glsym/glsym_es3.h index ea4841de..f0174114 100644 --- a/core/libretro-common/include/glsym/glsym_es3.h +++ b/core/libretro-common/include/glsym/glsym_es3.h @@ -648,6 +648,10 @@ extern RGLSYMGLTEXTUREVIEWEXTPROC __rglgen_glTextureViewEXT; extern RGLSYMGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC __rglgen_glFramebufferTextureMultiviewOVR; extern RGLSYMGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC __rglgen_glFramebufferTextureMultisampleMultiviewOVR; +typedef void (GL_APIENTRYP RGLSYMGLGETTEXLEVELPARAMETERIVPROC) (GLenum target, GLint level, GLenum pname, GLint *params); +#define glGetTexLevelParameteriv __rglgen_glGetTexLevelParameteriv +extern RGLSYMGLGETTEXLEVELPARAMETERIVPROC __rglgen_glGetTexLevelParameteriv; + struct rglgen_sym_map { const char *sym; void *ptr; }; extern const struct rglgen_sym_map rglgen_symbol_map[]; #ifdef __cplusplus diff --git a/core/libretro-common/include/libretro.h b/core/libretro-common/include/libretro.h index 7d6a4624..d3a08cd4 100644 --- a/core/libretro-common/include/libretro.h +++ b/core/libretro-common/include/libretro.h @@ -3460,7 +3460,11 @@ struct retro_vr_controller_poses * it wants, and how it hands over the depth, motion vectors and jitter * the upscaler needs beside the color image it already presents. * - * Call after the hardware context is reset (context_reset), once; the + * Call it first in retro_load_game, beside SET_HW_RENDER: there it + * returns false and tells the frontend to make the context for the + * upscaler, which loads its libraries and adds what they need to the + * context; a frontend does neither for a core that has not asked. Then + * call after the hardware context is reset (context_reset), once; the * interface is valid until context_destroy. Returns false when the * frontend has no upscaler for this context type or video driver; the * core then renders as it always has. A core that never calls this is diff --git a/core/libretro-common/include/retro_atomic.h b/core/libretro-common/include/retro_atomic.h index f913de31..5b8b01a9 100644 --- a/core/libretro-common/include/retro_atomic.h +++ b/core/libretro-common/include/retro_atomic.h @@ -1085,14 +1085,14 @@ typedef volatile size_t retro_atomic_size_t; static INLINE int retro_atomic_fetch_add_int_fb(retro_atomic_int_t *p, int v) { int old = *p; - *p = old + v; + *p = (int)((unsigned)old + (unsigned)v); return old; } static INLINE int retro_atomic_fetch_sub_int_fb(retro_atomic_int_t *p, int v) { int old = *p; - *p = old - v; + *p = (int)((unsigned)old - (unsigned)v); return old; } diff --git a/core/libretro-common/include/retro_math.h b/core/libretro-common/include/retro_math.h index dd84cd49..d7ff7e8f 100644 --- a/core/libretro-common/include/retro_math.h +++ b/core/libretro-common/include/retro_math.h @@ -204,7 +204,10 @@ static INLINE void convert_yxy_to_rgb(const float* Yxy, float* rgb) */ static INLINE size_t random_range(unsigned min, unsigned max) { - return (min == max) ? min : (size_t)((float)rand() / (float)RAND_MAX * (max + 1 - min) + min); + /* rand() / (RAND_MAX + 1) stays below 1, so the result never + * passes max; double keeps that exact for any RAND_MAX */ + return (min == max) ? min : min + (size_t)((double)rand() + / ((double)RAND_MAX + 1.0) * ((double)max - (double)min + 1.0)); } #endif diff --git a/core/libretro-common/include/retro_miscellaneous.h b/core/libretro-common/include/retro_miscellaneous.h index f0be4812..e2717a08 100644 --- a/core/libretro-common/include/retro_miscellaneous.h +++ b/core/libretro-common/include/retro_miscellaneous.h @@ -523,6 +523,9 @@ typedef struct # endif #elif defined(PS2) # define PRI_SIZET "u" +#elif defined(DJGPP) +/* DJGPP's size_t is unsigned long, 32 bits as it is */ +# define PRI_SIZET "lu" #elif defined(__EMSCRIPTEN__) # define PRI_SIZET "zu" #elif defined(__APPLE__) diff --git a/core/libretro-common/include/retro_spsc.h b/core/libretro-common/include/retro_spsc.h index e0a33bf6..afb3808b 100644 --- a/core/libretro-common/include/retro_spsc.h +++ b/core/libretro-common/include/retro_spsc.h @@ -64,6 +64,12 @@ * tail's line and the consumer's publish starts invalidating it - * which defeats the point for exactly the callers this primitive * exists for. + * - The struct carries no alignment of its own: an embedder on the + * heap gets whatever malloc gives, so the cursors can start at any + * offset within a line. The pads are therefore sized so that each + * cursor and its private copy sit a full line past the end of the + * previous group, which keeps them on different lines wherever the + * struct lands. * * Lifetime: * - retro_spsc_init allocates an internal buffer; retro_spsc_free @@ -130,21 +136,28 @@ RETRO_BEGIN_DECLS * subtraction would produce a giant array; guard with a max() so * the pad is always at least 1 byte and never underflows. */ #define RETRO_SPSC_PAD0_BYTES \ - ((RETRO_SPSC_CACHE_LINE > (sizeof(uint8_t*) + sizeof(size_t))) \ - ? (RETRO_SPSC_CACHE_LINE - (sizeof(uint8_t*) + sizeof(size_t))) \ + ((RETRO_SPSC_CACHE_LINE > (sizeof(uint8_t*) + 3 * sizeof(size_t))) \ + ? (RETRO_SPSC_CACHE_LINE - (sizeof(uint8_t*) + 3 * sizeof(size_t))) \ : 1) /* Each cursor shares its line with that side's private copy of the - * other cursor (see cached_tail / cached_head below), so the pad is - * a line less both. */ -#define RETRO_SPSC_PAD1_BYTES \ - ((RETRO_SPSC_CACHE_LINE > sizeof(retro_atomic_size_t) + sizeof(size_t)) \ - ? (RETRO_SPSC_CACHE_LINE - sizeof(retro_atomic_size_t) - sizeof(size_t)) \ - : 1) + * other cursor (see cached_tail / cached_head below). A full line of + * pad after the pair puts the next group a line past the pair's last + * byte, so the two cannot share a line at any placement of the struct; + * a line less the pair would leave them sharing one whenever the + * struct starts late enough in a line. */ +#define RETRO_SPSC_PAD1_BYTES RETRO_SPSC_CACHE_LINE typedef struct retro_spsc { uint8_t *buffer; - size_t capacity; /* power of 2; mask = capacity - 1 */ + size_t capacity; /* power of 2: the most the ring holds */ + /* The buffer's size less one, also a power of 2: positions wrap + * here. Equal to capacity - 1 unless the buffer is mirrored. */ + size_t mask; + /* The buffer's size when its pages are mapped a second time right + * behind it, else 0: a span then runs past the end into the + * mirror, and no reservation is cut short at the wrap. */ + size_t mirror; /* Pad so head sits on a fresh cache line, isolating it from * the buffer/capacity fields that init may touch. */ uint8_t _pad0[RETRO_SPSC_PAD0_BYTES]; @@ -185,6 +198,22 @@ typedef struct retro_spsc */ bool retro_spsc_init(retro_spsc_t *q, size_t min_capacity); +/** + * retro_spsc_init_mirrored: + * @q : The queue. + * @min_capacity : As retro_spsc_init; capacity is the same. + * + * As retro_spsc_init, but the buffer's pages are mapped twice, back + * to back, where the platform allows it, so write_begin and read_begin + * return every free or filled byte contiguously, across the wrap. The + * buffer is at least a page (an allocation granule on Windows); a ring + * smaller than that still holds only @min_capacity rounded up, so + * capacity, room and fill read exactly as they would from + * retro_spsc_init. Where mirroring is unavailable the queue is a plain + * one and q->mirror is 0. + */ +bool retro_spsc_init_mirrored(retro_spsc_t *q, size_t min_capacity); + /** * retro_spsc_free: * @q : The queue. @@ -215,6 +244,25 @@ void retro_spsc_free(retro_spsc_t *q); */ void retro_spsc_clear(retro_spsc_t *q); +/** + * retro_spsc_grow: + * @q : The queue. + * @min_capacity : Requested capacity in bytes, rounded up as for + * retro_spsc_init. + * + * Moves the queue to a larger buffer of the same kind, mirrored or + * not. The unread data and both cursors carry over unchanged, so a + * position the caller recorded in the ring still names the same byte. + * A queue already holding @min_capacity is left as it is. + * + * SAFETY: as retro_spsc_clear, both the producer and the consumer must + * be quiesced, and any span either holds is invalid afterwards. + * + * Returns: true on success; false on allocation failure, with the + * queue untouched. + */ +bool retro_spsc_grow(retro_spsc_t *q, size_t min_capacity); + /** * retro_spsc_write_avail: * @q : The queue. @@ -304,7 +352,8 @@ size_t retro_spsc_peek(const retro_spsc_t *q, void *data, size_t bytes); * * Producer-side zero-copy reservation. Returns the number of bytes * that can be written CONTIGUOUSLY at *@ptr - this is min(total free - * space, distance to the physical end of the ring), so it can be + * space, distance to the physical end of the ring; on a mirrored ring + * the total free space alone), so it can be * smaller than retro_spsc_write_avail() when the head is near the * wrap point. No cursor moves; call retro_spsc_write_end() with the * number of bytes actually written (which may be 0, and must not @@ -345,8 +394,9 @@ void retro_spsc_write_end(retro_spsc_t *q, size_t bytes); * * Consumer-side zero-copy drain. Returns the number of bytes * readable CONTIGUOUSLY at *@ptr - min(total buffered, distance to - * the physical end of the ring). Wrapped data needs a second - * begin/end round after consuming the first span. No cursor moves; + * the physical end of the ring; on a mirrored ring all of it). + * Otherwise wrapped data needs a second begin/end round after + * consuming the first span. No cursor moves; * call retro_spsc_read_end() with the bytes actually consumed. * * The region stays stable until read_end: the producer cannot write diff --git a/core/libretro-common/include/rthreads/retro_eventcount.h b/core/libretro-common/include/rthreads/retro_eventcount.h index 65b0657a..54b20aab 100644 --- a/core/libretro-common/include/rthreads/retro_eventcount.h +++ b/core/libretro-common/include/rthreads/retro_eventcount.h @@ -268,7 +268,8 @@ void retro_eventcount_commit_wait(retro_eventcount_t *ec, int key); * polls. A bound long enough to overflow the backend's * own unit is clamped, never wrapped. * - * As retro_eventcount_commit_wait(), bounded in time. + * As retro_eventcount_commit_wait(), bounded in time. Nonpositive bounds + * only check the epoch and close the registered wait. * * @return true if @ec was notified, false if the bound expired. A * spurious early return reports true, so the caller's loop still owns diff --git a/core/libretro-common/include/streams/file_stream.h b/core/libretro-common/include/streams/file_stream.h index a0cdfc00..6e1a5367 100644 --- a/core/libretro-common/include/streams/file_stream.h +++ b/core/libretro-common/include/streams/file_stream.h @@ -337,6 +337,28 @@ RFILE *filestream_open_atomic(const char *path); */ int filestream_commit_atomic(RFILE *file, const char *path, bool ok); +/** + * The name of the sibling temporary file an atomic write of \c path + * goes through, for a writer that opens files by name. + * + * @param s Where the name is written. + * @param len The size of \c s. + * @param path Path of the file that will be replaced. + * @return \c false if the name does not fit in \c s. + */ +bool filestream_atomic_temp_name(char *s, size_t len, const char *path); + +/** + * Finishes an atomic write of \c path whose temporary file, named by + * filestream_atomic_temp_name(), was written and closed by other means: + * as filestream_commit_atomic() does after its close. + * + * @param path Path of the file that will be replaced. + * @param ok Whether the temporary file holds everything it should. + * @return As filestream_commit_atomic(). + */ +int filestream_finish_atomic(const char *path, bool ok); + /** * Writes a single character to the given file. * @@ -517,6 +539,19 @@ const char* filestream_get_path(RFILE *stream); */ const uint8_t *filestream_get_mapped_ptr(RFILE *stream, int64_t *len); +/** + * filestream_read() without the copy: points \c ptr at up to \c len + * octets at the stream position - in the lookahead, the file's + * mapping or a network stream's read-ahead window - and moves past + * them, as a read of that many would. + * + * @return The octets lent, 0 at the end of the file, or -1 when + * nothing there can be lent; the caller then reads as before. Lent + * octets stay valid until the next call on \c stream and must not be + * written. + */ +int64_t filestream_view(RFILE *stream, int64_t len, const uint8_t **ptr); + typedef const uint8_t *(*filestream_mapped_ptr_cb_t)(void *hfile, int64_t *len); void filestream_set_mapped_ptr_cb(filestream_mapped_ptr_cb_t cb); @@ -528,6 +563,12 @@ void filestream_set_mapped_ptr_cb(filestream_mapped_ptr_cb_t cb); */ void filestream_prefetch(RFILE *stream, uint64_t offset, uint64_t len); +/* An installed VFS that keeps local handles (vfs_hybrid) takes the hint + * for them through this; without one the hint stops at filestream. */ +typedef void (*filestream_prefetch_cb_t)(void *hfile, uint64_t offset, + uint64_t len); +void filestream_set_prefetch_cb(filestream_prefetch_cb_t cb); + /** * Size of the window filestream_vscanf() reads and scans at a time, * i.e. the furthest a single conversion can reach. diff --git a/core/libretro-common/include/vfs/vfs.h b/core/libretro-common/include/vfs/vfs.h index 04d7f3f0..77dc962f 100644 --- a/core/libretro-common/include/vfs/vfs.h +++ b/core/libretro-common/include/vfs/vfs.h @@ -41,9 +41,12 @@ typedef void* HANDLE; #endif #ifdef HAVE_CDROM +struct cdrom_toc; typedef struct { int64_t byte_pos; + uint64_t disc_bytes; /* cooked 2048-byte image; zero for CD paths */ + struct cdrom_toc *toc; char *cue_buf; size_t cue_len; unsigned cur_lba; @@ -55,6 +58,10 @@ typedef struct unsigned char last_frame[2352]; char drive; bool last_frame_valid; + bool logical_blocks; + bool device_only; + bool media_changed; /* bound geometry is stale; reopen before reading */ + bool io_error; } vfs_cdrom_t; #endif @@ -88,7 +95,7 @@ typedef struct #if defined(VFS_HAVE_FILE_MAPPING) \ || defined(VITA) \ || defined(_3DS) \ - || defined(WIIU) \ + || (defined(WIIU) && defined(HAVE_WIIU_DESCRIPTOR_IO)) \ || defined(__SWITCH__) \ || defined(GEKKO) \ || defined(PSP) \ @@ -97,6 +104,17 @@ typedef struct #endif #endif +/* Wii U takes the descriptor path only when built with + * HAVE_WIIU_DESCRIPTOR_IO (Makefile.wiiu), until it has run on + * hardware. Its SD reads land by DMA in whole cache lines, so a + * whole-file read there allocates its buffer on a line and asks for + * whole lines, which lets the read land in place in one FS call; + * VFS_DESCRIPTOR_READ_ALIGN is that line. */ +#if defined(WIIU) && defined(HAVE_WIIU_DESCRIPTOR_IO) \ + && !defined(VFS_DESCRIPTOR_READ_ALIGN) +#define VFS_DESCRIPTOR_READ_ALIGN 64 +#endif + /* Descriptor-backed I/O for one-shot writers as well: a stream opened * for writing with RETRO_VFS_FILE_ACCESS_HINT_SEQUENTIAL_BULK hands its * bytes straight to the descriptor, so a whole-file write is one open, @@ -146,7 +164,7 @@ struct libretro_vfs_implementation_file /* CreateFileMapping handle backing `mapped`; NULL when unmapped */ HANDLE map_handle; #if !defined(_XBOX) && !defined(HAVE_MMAP) - void *warm; /* pre-Windows 8 read-ahead thread, NULL until used */ + void *warm; /* mapped read-ahead thread, NULL until used */ #endif #endif char *buf; diff --git a/core/libretro-common/include/vfs/vfs_implementation.h b/core/libretro-common/include/vfs/vfs_implementation.h index ffb57c44..aa856520 100644 --- a/core/libretro-common/include/vfs/vfs_implementation.h +++ b/core/libretro-common/include/vfs/vfs_implementation.h @@ -42,6 +42,7 @@ int retro_vfs_file_punch_hole_impl(libretro_vfs_implementation_file *stream, int * filestream_get_sparse_granularity. 0 when unknown. */ int64_t retro_vfs_file_get_sparse_granularity_impl(libretro_vfs_implementation_file *stream); +/* Consumes stream even when reporting final I/O failure; do not retry close. */ int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream); int retro_vfs_file_error_impl(libretro_vfs_implementation_file *stream); @@ -85,6 +86,19 @@ const char *retro_vfs_file_get_path_impl(libretro_vfs_implementation_file *strea const uint8_t *retro_vfs_file_get_mapped_ptr_impl( libretro_vfs_implementation_file *stream, int64_t *len); +/** + * retro_vfs_file_view_impl: + * + * retro_vfs_file_read_impl() without the copy: points @ptr at up to + * @len octets at the position, in the file's mapping or in a network + * stream's read-ahead window, and moves past them. They stay valid + * until the next call on @stream. Returns the octets lent, 0 at the + * end of the file, or -1 when nothing there can be lent; the caller + * then reads. + **/ +int64_t retro_vfs_file_view_impl(libretro_vfs_implementation_file *stream, + uint64_t len, const uint8_t **ptr); + /** * retro_vfs_file_prefetch_impl: * @offset : first byte of the range about to be read @@ -92,10 +106,13 @@ const uint8_t *retro_vfs_file_get_mapped_ptr_impl( * * Asks the OS to start bringing the range into memory, so a read or a * touch of the mapping that follows finds it resident instead of - * stalling on the disk. Returns at once: madvise(MADV_WILLNEED) or - * PrefetchVirtualMemory() on a mapped file, posix_fadvise(WILLNEED) on - * a descriptor; a no-op where neither exists, on a scheme that is not - * a local file, or when the OS declines. A hint, never an error. */ + * stalling on the disk. Returns at once: madvise(MADV_WILLNEED) on a + * mapped file, posix_fadvise(WILLNEED) on a descriptor; on Windows the + * range is handed to the file's read-ahead thread, which calls + * PrefetchVirtualMemory() (or touches the pages before Windows 8), so + * the caller never waits on the disk. A no-op where none of these + * exists, on a scheme that is not a local file, or when the OS + * declines. A hint, never an error. */ void retro_vfs_file_prefetch_impl( libretro_vfs_implementation_file *stream, uint64_t offset, uint64_t len); diff --git a/core/libretro-common/include/vfs/vfs_implementation_cdrom.h b/core/libretro-common/include/vfs/vfs_implementation_cdrom.h index f809963f..717b38ca 100644 --- a/core/libretro-common/include/vfs/vfs_implementation_cdrom.h +++ b/core/libretro-common/include/vfs/vfs_implementation_cdrom.h @@ -30,6 +30,11 @@ RETRO_BEGIN_DECLS int64_t retro_vfs_file_seek_cdrom(libretro_vfs_implementation_file *stream, int64_t offset, int whence); +/* Linux: cdrom://driveN.iso (sg device N), Windows: cdrom://D:/drive.iso. + * Read-only cooked 2048-byte image; capacity is probed per handle. + * No UDF parsing, DVD navigation or decryption is performed here. + * macOS cooked images are not yet supported. A .device URI is for metadata + * queries only and does not require readable CD geometry. */ void retro_vfs_file_open_cdrom( libretro_vfs_implementation_file *stream, const char *path, unsigned mode, unsigned hints); @@ -43,7 +48,13 @@ int64_t retro_vfs_file_read_cdrom(libretro_vfs_implementation_file *stream, int retro_vfs_file_error_cdrom(libretro_vfs_implementation_file *stream); -const cdrom_toc_t* retro_vfs_file_get_cdrom_toc(void); +/* Borrowed immutable geometry, valid until this handle closes. NULL for + * metadata-only probes and cooked images. BIN opens obtain their own TOC; + * no preceding CUE open is required. Drive-reported medium changes, resets + * or removal make bound data handles unreadable until reopened. Other + * handles and changes not reported to this handle are not tracked here. */ +const cdrom_toc_t* retro_vfs_file_get_cdrom_toc( + const libretro_vfs_implementation_file *stream); const vfs_cdrom_t* retro_vfs_file_get_cdrom_position(const libretro_vfs_implementation_file *stream); diff --git a/core/libretro-common/queues/retro_spsc.c b/core/libretro-common/queues/retro_spsc.c index 7d7d0dd5..b85860cb 100644 --- a/core/libretro-common/queues/retro_spsc.c +++ b/core/libretro-common/queues/retro_spsc.c @@ -20,10 +20,194 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/* memfd_create goes through syscall(), and ftruncate and shm_open + * are POSIX: strict C89 builds hide all three without these. */ +#if defined(__linux__) && !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif +/* Tiger must keep the legacy symbols rather than UNIX2003 variants. */ +#if defined(__APPLE__) && !defined(_DARWIN_C_SOURCE) \ + && !(defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) \ + && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1050)) +#define _DARWIN_C_SOURCE +#endif + #include #include +#include #include +/* Where a buffer can be mapped twice, back to back. RETRO_SPSC_MIRROR_SHM + * selects the POSIX shm path on any POSIX host, for its tests. */ +#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__) +#define SPSC_MIRROR_WIN32 +#elif defined(RETRO_SPSC_MIRROR_SHM) +#define SPSC_MIRROR_SHM +#elif defined(__linux__) && !defined(__EMSCRIPTEN__) +#include +#ifdef __NR_memfd_create +#define SPSC_MIRROR_MEMFD +#endif +#elif defined(__APPLE__) +#include +#if !TARGET_OS_IPHONE +#define SPSC_MIRROR_SHM +#endif +#elif (defined(__FreeBSD__) && !defined(__ORBIS__)) || defined(__NetBSD__) \ + || defined(__OpenBSD__) +#define SPSC_MIRROR_SHM +#endif + +#if defined(SPSC_MIRROR_WIN32) +#include +#elif defined(SPSC_MIRROR_MEMFD) || defined(SPSC_MIRROR_SHM) +#include +#include +#include +#endif + +#if defined(SPSC_MIRROR_WIN32) || defined(SPSC_MIRROR_MEMFD) \ + || defined(SPSC_MIRROR_SHM) +#define SPSC_MIRROR +#endif + +#ifdef SPSC_MIRROR_SHM +#include +#endif + +/* Bytes contiguous at @idx: to the buffer's end, or past it into the + * mirror. */ +static INLINE size_t spsc_contig(const retro_spsc_t *q, size_t idx) +{ + return q->mirror ? (size_t)-1 : q->mask + 1 - idx; +} + +#ifdef SPSC_MIRROR +#if !defined(SPSC_MIRROR_WIN32) && !defined(MAP_ANONYMOUS) +#define MAP_ANONYMOUS MAP_ANON +#endif + +/* What a mapping is made of: a page, or Windows' allocation granule. */ +static size_t spsc_mirror_granule(void) +{ +#ifdef SPSC_MIRROR_WIN32 + SYSTEM_INFO si; + GetSystemInfo(&si); + return (size_t)si.dwAllocationGranularity; +#else + long page = sysconf(_SC_PAGESIZE); + return page > 0 ? (size_t)page : 4096; +#endif +} + +static void spsc_mirror_free(uint8_t *base, size_t size) +{ +#ifdef SPSC_MIRROR_WIN32 + UnmapViewOfFile(base); + UnmapViewOfFile(base + size); +#else + munmap(base, size * 2); +#endif +} + +#ifndef SPSC_MIRROR_WIN32 +/* Both views of a descriptor's pages, inside one reservation so nothing + * can land between them. Takes the descriptor. */ +static uint8_t *spsc_mirror_map(int fd, size_t size) +{ + uint8_t *base = NULL; + void *res; + if (ftruncate(fd, (off_t)size) == 0) + { + res = mmap(NULL, size * 2, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); + if (res != MAP_FAILED) + { + base = (uint8_t*)res; + if ( mmap(base, size, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_FIXED, fd, 0) == MAP_FAILED + || mmap(base + size, size, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_FIXED, fd, 0) == MAP_FAILED) + { + munmap(base, size * 2); + base = NULL; + } + } + } + close(fd); + return base; +} +#endif + +static uint8_t *spsc_mirror_alloc(size_t size) +{ + uint8_t *base = NULL; +#if defined(SPSC_MIRROR_WIN32) + unsigned tries; + HANDLE h = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, + PAGE_READWRITE, (DWORD)((uint64_t)size >> 32), (DWORD)size, NULL); + if (!h) + return NULL; + /* The reservation is released before the views are placed in it, + * so another thread can take the address meanwhile: try again. */ + for (tries = 0; tries < 16 && !base; tries++) + { + uint8_t *at = (uint8_t*)VirtualAlloc(NULL, size * 2, MEM_RESERVE, + PAGE_NOACCESS); + void *lo, *hi; + if (!at) + break; + VirtualFree(at, 0, MEM_RELEASE); + if (!(lo = MapViewOfFileEx(h, FILE_MAP_ALL_ACCESS, 0, 0, size, at))) + continue; + hi = MapViewOfFileEx(h, FILE_MAP_ALL_ACCESS, 0, 0, size, at + size); + if (hi == (void*)(at + size)) + base = at; + else + { + if (hi) + UnmapViewOfFile(hi); + UnmapViewOfFile(lo); + } + } + CloseHandle(h); +#elif defined(SPSC_MIRROR_MEMFD) + int fd = (int)syscall(__NR_memfd_create, "retro_spsc", 1u /* MFD_CLOEXEC */); + if (fd >= 0) + base = spsc_mirror_map(fd, size); +#else + static unsigned serial; + unsigned tries; + for (tries = 0; tries < 8 && !base; tries++) + { + char name[48]; + int fd, length; + length = snprintf(name, sizeof(name), "/retro_spsc.%lu.%u", + (unsigned long)getpid(), serial++); + if (length < 0 || (size_t)length >= sizeof(name)) + continue; + if ((fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600)) < 0) + continue; + shm_unlink(name); + base = spsc_mirror_map(fd, size); + break; + } +#endif + /* A write through one view must read back through the other. */ + if (base) + { + base[0] = 0x5a; + if (base[size] != 0x5a) + { + spsc_mirror_free(base, size); + return NULL; + } + base[0] = 0; + } + return base; +} +#endif + /* Round @v up to the next power of 2. Returns 0 if @v == 0 or if * the next power of 2 would overflow (caller checks @v <= SIZE_MAX/2). */ static size_t spsc_round_up_pow2(size_t v) @@ -58,6 +242,8 @@ bool retro_spsc_init(retro_spsc_t *q, size_t min_capacity) return false; q->capacity = cap; + q->mask = cap - 1; + q->mirror = 0; q->cached_tail = 0; q->cached_head = 0; retro_atomic_size_init(&q->head, 0); @@ -65,16 +251,53 @@ bool retro_spsc_init(retro_spsc_t *q, size_t min_capacity) return true; } +bool retro_spsc_init_mirrored(retro_spsc_t *q, size_t min_capacity) +{ +#ifdef SPSC_MIRROR + size_t cap, size, granule; + uint8_t *buf; + + if (!q || min_capacity == 0 || min_capacity > (SIZE_MAX / 2)) + return false; + cap = spsc_round_up_pow2(min_capacity); + granule = spsc_mirror_granule(); + /* Positions wrap at the buffer's size, so it has to be a power of + * 2 as well: the capacity, or the granule where that is larger. */ + size = cap > granule ? cap : granule; + if ( cap && !(granule & (granule - 1)) + && size <= (SIZE_MAX / 4) + && (buf = spsc_mirror_alloc(size))) + { + q->buffer = buf; + q->capacity = cap; + q->mask = size - 1; + q->mirror = size; + q->cached_tail = 0; + q->cached_head = 0; + retro_atomic_size_init(&q->head, 0); + retro_atomic_size_init(&q->tail, 0); + return true; + } +#endif + return retro_spsc_init(q, min_capacity); +} + void retro_spsc_free(retro_spsc_t *q) { if (!q) return; if (q->buffer) { - free(q->buffer); +#ifdef SPSC_MIRROR + if (q->mirror) + spsc_mirror_free(q->buffer, q->mirror); + else +#endif + free(q->buffer); q->buffer = NULL; } q->capacity = 0; + q->mirror = 0; } void retro_spsc_clear(retro_spsc_t *q) @@ -92,6 +315,47 @@ void retro_spsc_clear(retro_spsc_t *q) retro_atomic_size_init(&q->tail, 0); } +bool retro_spsc_grow(retro_spsc_t *q, size_t min_capacity) +{ + retro_spsc_t n; + size_t head, tail, pos; + + if (!q || !q->buffer) + return false; + if (min_capacity <= q->capacity) + return true; + if (!(q->mirror + ? retro_spsc_init_mirrored(&n, min_capacity) + : retro_spsc_init(&n, min_capacity))) + return false; + + head = retro_atomic_load_acquire_size(&q->head); + tail = retro_atomic_load_acquire_size(&q->tail); + for (pos = tail; pos != head; ) + { + size_t from = pos & q->mask; + size_t to = pos & n.mask; + size_t run = head - pos; + if (!q->mirror && run > q->mask + 1 - from) + run = q->mask + 1 - from; + if (!n.mirror && run > n.mask + 1 - to) + run = n.mask + 1 - to; + memcpy(n.buffer + to, q->buffer + from, run); + pos += run; + } + + retro_spsc_free(q); + q->buffer = n.buffer; + q->capacity = n.capacity; + q->mask = n.mask; + q->mirror = n.mirror; + q->cached_tail = tail; + q->cached_head = head; + retro_atomic_size_init(&q->head, head); + retro_atomic_size_init(&q->tail, tail); + return true; +} + size_t retro_spsc_write_avail(const retro_spsc_t *q) { /* Producer query. We read our own head (which we wrote) and the @@ -130,7 +394,7 @@ size_t retro_spsc_read_avail(const retro_spsc_t *q) size_t retro_spsc_write(retro_spsc_t *q, const void *data, size_t bytes) { - size_t mask, head_idx, first; + size_t head_idx, first; const uint8_t *src = (const uint8_t*)data; /* head is ours (relaxed). Room is first computed from our private * copy of tail, which is never ahead of the real one; only when it @@ -149,11 +413,10 @@ size_t retro_spsc_write(retro_spsc_t *q, const void *data, size_t bytes) if (bytes == 0) return 0; - mask = q->capacity - 1; - head_idx = head & mask; + head_idx = head & q->mask; /* first = bytes from head_idx to end-of-buffer */ - first = q->capacity - head_idx; + first = spsc_contig(q, head_idx); if (first > bytes) first = bytes; @@ -183,8 +446,8 @@ size_t retro_spsc_write_frames(retro_spsc_t *q, const void *data, if (!frames) return 0; /* Multiplication is bounded by capacity, even for an oversized request. */ bytes = frames * frame_bytes; - index = head & (q->capacity - 1); - first = q->capacity - index; + index = head & q->mask; + first = spsc_contig(q, index); if (first > bytes) first = bytes; memcpy(q->buffer + index, src, first); memcpy(q->buffer, src + first, bytes - first); @@ -194,7 +457,7 @@ size_t retro_spsc_write_frames(retro_spsc_t *q, const void *data, size_t retro_spsc_read(retro_spsc_t *q, void *data, size_t bytes) { - size_t mask, tail_idx, first; + size_t tail_idx, first; uint8_t *dst = (uint8_t*)data; /* acquire on head pairs with producer's release-store; this is * what makes the subsequent memcpys safe to read. */ @@ -214,10 +477,9 @@ size_t retro_spsc_read(retro_spsc_t *q, void *data, size_t bytes) if (bytes == 0) return 0; - mask = q->capacity - 1; - tail_idx = tail & mask; + tail_idx = tail & q->mask; - first = q->capacity - tail_idx; + first = spsc_contig(q, tail_idx); if (first > bytes) first = bytes; @@ -231,7 +493,7 @@ size_t retro_spsc_read(retro_spsc_t *q, void *data, size_t bytes) size_t retro_spsc_peek(const retro_spsc_t *q, void *data, size_t bytes) { - size_t mask, tail_idx, first; + size_t tail_idx, first; uint8_t *dst = (uint8_t*)data; /* Consumer side, same scheme as retro_spsc_read; the cast is only * because peek takes a const queue and the cached copy is state. */ @@ -248,10 +510,9 @@ size_t retro_spsc_peek(const retro_spsc_t *q, void *data, size_t bytes) if (bytes == 0) return 0; - mask = q->capacity - 1; - tail_idx = tail & mask; + tail_idx = tail & q->mask; - first = q->capacity - tail_idx; + first = spsc_contig(q, tail_idx); if (first > bytes) first = bytes; @@ -263,21 +524,20 @@ size_t retro_spsc_peek(const retro_spsc_t *q, void *data, size_t bytes) size_t retro_spsc_write_begin(retro_spsc_t *q, void **ptr) { - size_t mask, head_idx, span; + size_t head_idx, span; /* head is ours (relaxed). Room from the private copy of tail - * first; re-read the consumer's tail only when that says none. - * See retro_spsc_write. */ + * first; re-read the consumer's tail only when that copy, rather + * than the wrap, is what limits the span - a stale copy would hand + * back less than is free. See retro_spsc_write. */ size_t head = retro_atomic_load_relaxed_size(&q->head); size_t avail = q->capacity - (head - q->cached_tail); - if (avail == 0) + head_idx = head & q->mask; + span = spsc_contig(q, head_idx); + if (avail < span && avail < q->capacity) { q->cached_tail = retro_atomic_load_acquire_size(&q->tail); avail = q->capacity - (head - q->cached_tail); } - - mask = q->capacity - 1; - head_idx = head & mask; - span = q->capacity - head_idx; if (span > avail) span = avail; *ptr = q->buffer + head_idx; @@ -301,21 +561,20 @@ void retro_spsc_write_end(retro_spsc_t *q, size_t bytes) size_t retro_spsc_read_begin(retro_spsc_t *q, const void **ptr) { - size_t mask, tail_idx, span; + size_t tail_idx, span; /* tail is ours (relaxed). Available from the private copy of - * head first; re-read the producer's head only when that says - * none. See retro_spsc_read. */ + * head first; re-read the producer's head only when that copy, + * rather than the wrap, is what limits the span. See + * retro_spsc_read. */ size_t tail = retro_atomic_load_relaxed_size(&q->tail); size_t avail = q->cached_head - tail; - if (avail == 0) + tail_idx = tail & q->mask; + span = spsc_contig(q, tail_idx); + if (avail < span && avail < q->capacity) { q->cached_head = retro_atomic_load_acquire_size(&q->head); avail = q->cached_head - tail; } - - mask = q->capacity - 1; - tail_idx = tail & mask; - span = q->capacity - tail_idx; if (span > avail) span = avail; *ptr = q->buffer + tail_idx; diff --git a/core/libretro-common/rthreads/retro_eventcount.c b/core/libretro-common/rthreads/retro_eventcount.c index 0da9cbc0..818918f8 100644 --- a/core/libretro-common/rthreads/retro_eventcount.c +++ b/core/libretro-common/rthreads/retro_eventcount.c @@ -44,6 +44,8 @@ #include #include #include +#include +#include #include #include @@ -161,7 +163,6 @@ typedef char retro_eventcount_epoch_is_a_word_ #if defined(RETRO_EC_ADDR_LINUX) #include #include -#include #include #include @@ -173,6 +174,13 @@ typedef char retro_eventcount_epoch_is_a_word_ #ifndef FUTEX_WAKE_PRIVATE #define FUTEX_WAKE_PRIVATE 129 #endif + +/* The spin before the futex: the same budget, calibration and gates as + * the Win32 backend's (see EC_SPIN_US), on the epoch word itself since + * the futex waiter has no flag of its own. Resolved once, on the first + * wait. */ +static retro_atomic_int_t ec_linux_spin_state; +static unsigned ec_linux_spin; #endif #if defined(RETRO_EC_ADDR_SWITCH) @@ -307,6 +315,59 @@ typedef sys_semaphore_t ec_ps3_sem_t; #include #endif +/* How long a waiter spins before committing to the kernel - on its + * flag word (Win32) or the epoch (Linux), multiprocessor only. A + * budget in microseconds rather than a count of iterations: a PAUSE + * is worth an order of magnitude more cycles on some processors than + * others, so a fixed count is a different amount of time on every + * machine. The count is derived from a measured relax at resolve + * time. + * + * Measured on Windows 11 x64, four waiters on one object, where a + * relax costs 11.8ns. Round trip and broadcast per waiter, against + * what a spin that finds nothing costs: + * + * spin burn round trip broadcast/waiter + * 0 0.00us 4.21us 0.94us + * 64 0.76us 0.27us 0.96us + * 128 1.49us 0.11us 0.51us + * 256 2.99us 0.06us 0.11us + * 512 6.04us 0.08us 0.12us + * 4096 48.63us 0.07us 0.12us + * + * The knee is at 256 iterations there, which is 3us, and nothing above + * it buys anything -- a spin that finds nothing then costs about what + * the wake syscall it avoids would have. RETRO_EVENTCOUNT_SPIN_US + * moves the budget; RETRO_EVENTCOUNT_SPIN sets the count outright. */ +#define EC_SPIN_US 3 + +/* Calibration bounds; explicit overrides share the upper limit. */ +#define EC_SPIN_MIN 32 +#define EC_SPIN_MAX 8192 + +#if defined(RETRO_EC_ADDR_LINUX) || defined(RETRO_EC_ADDR_WIN32) +static unsigned ec_spin_override(const char *text, unsigned fallback) +{ + char *end; + unsigned long value; + while (isspace((unsigned char)*text)) + text++; + if (*text == '-') + return fallback; + errno = 0; + value = strtoul(text, &end, 0); + if (end == text) + return fallback; + while (isspace((unsigned char)*end)) + end++; + if (*end) + return fallback; + if (errno == ERANGE || value > EC_SPIN_MAX) + return EC_SPIN_MAX; + return (unsigned)value; +} +#endif + #if defined(RETRO_EC_WAITLIST) /* Windows has no one primitive that reaches every version, so the * waiter list is ours and only the sleep is delegated. Three tiers, @@ -361,37 +422,6 @@ typedef sem_t ec_sem_t; #endif #endif -/* How long a waiter spins on its flag word before committing to the - * kernel, on multiprocessor only. A budget in microseconds rather - * than a count of iterations: a PAUSE is worth an order of magnitude - * more cycles on some processors than others, so a fixed count is a - * different amount of time on every machine. The count is derived - * from a measured relax at resolve time. - * - * Measured on Windows 11 x64, four waiters on one object, where a - * relax costs 11.8ns. Round trip and broadcast per waiter, against - * what a spin that finds nothing costs: - * - * spin burn round trip broadcast/waiter - * 0 0.00us 4.21us 0.94us - * 64 0.76us 0.27us 0.96us - * 128 1.49us 0.11us 0.51us - * 256 2.99us 0.06us 0.11us - * 512 6.04us 0.08us 0.12us - * 4096 48.63us 0.07us 0.12us - * - * The knee is at 256 iterations there, which is 3us, and nothing above - * it buys anything -- a spin that finds nothing then costs about what - * the wake syscall it avoids would have. RETRO_EVENTCOUNT_SPIN_US - * moves the budget; RETRO_EVENTCOUNT_SPIN sets the count outright and - * skips the calibration. */ -#define EC_SPIN_US 3 - -/* Bounds on the derived count, so a mismeasured relax cannot turn the - * spin into either a no-op or an unbounded burn. */ -#define EC_SPIN_MIN 32 -#define EC_SPIN_MAX 8192 - struct ec_waiter { struct ec_waiter *next; @@ -608,7 +638,7 @@ static void ec_win32_resolve(void) * so the calibration is reachable for measurement on a machine * that would otherwise skip the spin entirely. */ if ((env = getenv("RETRO_EVENTCOUNT_SPIN_US"))) - budget = (unsigned)strtoul(env, NULL, 0); + budget = ec_spin_override(env, budget); else if (si.dwNumberOfProcessors <= 1) budget = 0; @@ -616,7 +646,7 @@ static void ec_win32_resolve(void) /* An explicit count wins over the budget, for sweeping. */ if ((env = getenv("RETRO_EVENTCOUNT_SPIN"))) - ec_g.spin = (unsigned)strtoul(env, NULL, 0); + ec_g.spin = ec_spin_override(env, ec_g.spin); } } @@ -1096,7 +1126,8 @@ static void ec_list_lock(retro_eventcount_t *ec) unsigned spins = 0; for (;;) { - uintptr_t old = ec_head(ec); + /* The successful CAS acquires the list; polling only tests its bit. */ + uintptr_t old = (uintptr_t)retro_atomic_load_relaxed_ptr(&ec->waitlist); if (!(old & EC_HEAD_LOCK) && retro_atomic_cas_ptr(&ec->waitlist, (void*)old, (void*)(old | EC_HEAD_LOCK))) @@ -1546,11 +1577,148 @@ void retro_eventcount_cancel_wait(retro_eventcount_t *ec) #endif } +#if defined(RETRO_EC_ADDR_LINUX) +/* Relax iterations that fit in @budget_us, timed as the Win32 backend + * times its own, on the monotonic clock. */ +static unsigned ec_linux_spin_for_budget(unsigned budget_us) +{ + const unsigned samples = 3; + const unsigned iters = 2048; + double best = 0.0; + unsigned s, i; + + if (!budget_us) + return 0; + + for (s = 0; s < samples; s++) + { + struct timespec t0, t1; + double us; + + if (clock_gettime(CLOCK_MONOTONIC, &t0) != 0) + return EC_SPIN_MIN * 8; /* no clock: a middling fixed count */ + for (i = 0; i < iters; i++) + retro_cpu_relax(); + clock_gettime(CLOCK_MONOTONIC, &t1); + + us = (double)(t1.tv_sec - t0.tv_sec) * 1000000.0 + + (double)(t1.tv_nsec - t0.tv_nsec) / 1000.0; + + if (us > 0.0 && (best == 0.0 || us < best)) + best = us; + } + + if (best <= 0.0) + return EC_SPIN_MIN * 8; + + { + double n = (double)budget_us * (double)iters / best; + + if (n < (double)EC_SPIN_MIN) + return EC_SPIN_MIN; + if (n > (double)EC_SPIN_MAX) + return EC_SPIN_MAX; + return (unsigned)n; + } +} + +static void ec_linux_spin_init(void) +{ + if (retro_atomic_load_acquire_int(&ec_linux_spin_state) == 2) + return; + if (retro_atomic_cas_int(&ec_linux_spin_state, 0, 1)) + { + const char *env; + unsigned budget = EC_SPIN_US; + long cpus = sysconf(_SC_NPROCESSORS_ONLN); + + /* An asked-for budget is honoured whatever the processor count; + * otherwise one processor does not spin. */ + if ((env = getenv("RETRO_EVENTCOUNT_SPIN_US"))) + budget = ec_spin_override(env, budget); + else if (cpus <= 1) + budget = 0; + + ec_linux_spin = ec_linux_spin_for_budget(budget); + + if ((env = getenv("RETRO_EVENTCOUNT_SPIN"))) + ec_linux_spin = ec_spin_override(env, ec_linux_spin); + + retro_atomic_store_release_int(&ec_linux_spin_state, 2); + return; + } + while (retro_atomic_load_acquire_int(&ec_linux_spin_state) != 2) + retro_cpu_relax(); +} + +/* 1: time remains, 0: expired, -1: clock unavailable (a spurious wake). */ +static int ec_linux_remaining(const struct timespec *start, int64_t budget, + struct timespec *remaining) +{ + struct timespec now; + int64_t seconds, left; + long ns, budget_ns; + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) + return -1; + seconds = (int64_t)now.tv_sec - (int64_t)start->tv_sec; + ns = now.tv_nsec - start->tv_nsec; + if (ns < 0) + { + seconds--; + ns += 1000000000L; + } + if (seconds < 0) + { + seconds = 0; + ns = 0; + } + budget_ns = (long)((budget % 1000000) * 1000); + if (seconds > budget / 1000000 + || (seconds == budget / 1000000 && ns >= budget_ns)) + return 0; + if (remaining) + { + left = budget / 1000000 - seconds; + ns = budget_ns - ns; + if (ns < 0) + { + left--; + ns += 1000000000L; + } + if (sizeof(time_t) < sizeof(int64_t) && left > INT32_MAX) + { + left = INT32_MAX; + ns = 999999999L; + } + remaining->tv_sec = (time_t)left; + remaining->tv_nsec = ns; + } + return 1; +} + +/* True when the epoch moved on from @key before the spin ended. */ +static bool ec_linux_spun(retro_eventcount_t *ec, int key, + const struct timespec *start, int64_t budget) +{ + unsigned i; + ec_linux_spin_init(); + for (i = 0; i < ec_linux_spin; i++) + { + if (retro_atomic_load_acquire_int(&ec->epoch) != key) + return true; + if (start && !(i & 31) && ec_linux_remaining(start, budget, NULL) <= 0) + return false; + retro_cpu_relax(); + } + return retro_atomic_load_acquire_int(&ec->epoch) != key; +} +#endif + void retro_eventcount_commit_wait(retro_eventcount_t *ec, int key) { #if defined(RETRO_EC_ADDR_LINUX) int expect = key; - if (retro_atomic_load_acquire_int(&ec->epoch) == key) + if (!ec_linux_spun(ec, key, NULL, 0)) syscall(SYS_futex, (void*)&ec->epoch, FUTEX_WAIT_PRIVATE, expect, NULL, NULL, 0); retro_atomic_fetch_sub_int(&ec->waiters, 1); @@ -1608,21 +1776,50 @@ bool retro_eventcount_commit_wait_timeout(retro_eventcount_t *ec, { bool signalled = true; - if (timeout_us < 0) - timeout_us = 0; + if (timeout_us <= 0) + { +#if defined(RETRO_EC_LOCKED_BOOKKEEPING) + slock_lock(ec->lock); +#endif + signalled = retro_atomic_load_acquire_int(&ec->epoch) != key; + retro_atomic_fetch_sub_int(&ec->waiters, 1); +#if defined(RETRO_EC_LOCKED_BOOKKEEPING) + slock_unlock(ec->lock); +#endif + return signalled; + } #if defined(RETRO_EC_ADDR_LINUX) - if (retro_atomic_load_acquire_int(&ec->epoch) == key) { - struct timespec ts; + struct timespec start, ts; + bool clock_ok = clock_gettime(CLOCK_MONOTONIC, &start) == 0; int expect = key; - - ts.tv_sec = (time_t)(timeout_us / 1000000); - ts.tv_nsec = (long)((timeout_us % 1000000) * 1000); - - if (syscall(SYS_futex, (void*)&ec->epoch, FUTEX_WAIT_PRIVATE, - expect, &ts, NULL, 0) != 0 && errno == ETIMEDOUT) - signalled = false; + if (!(clock_ok ? ec_linux_spun(ec, key, &start, timeout_us) + : retro_atomic_load_acquire_int(&ec->epoch) != key)) + { + int remaining; + if (clock_ok) + remaining = ec_linux_remaining(&start, timeout_us, &ts); + else + { + ts.tv_sec = (time_t)(timeout_us / 1000000); + ts.tv_nsec = (long)((timeout_us % 1000000) * 1000); + if (sizeof(time_t) < sizeof(int64_t) && timeout_us / 1000000 > INT32_MAX) + { + ts.tv_sec = (time_t)INT32_MAX; + ts.tv_nsec = 999999999L; + } + remaining = 1; + } + if (remaining == 0) + signalled = retro_atomic_load_acquire_int(&ec->epoch) != key; + else if (remaining > 0) + { + if (syscall(SYS_futex, (void*)&ec->epoch, FUTEX_WAIT_PRIVATE, + expect, &ts, NULL, 0) != 0 && errno == ETIMEDOUT) + signalled = false; + } + } } retro_atomic_fetch_sub_int(&ec->waiters, 1); #else @@ -1682,6 +1879,9 @@ unsigned retro_eventcount_spin_iters(void) /* The fallback parks on a condition variable, which has no flag word * to spin on. */ return ec_g.sleep ? ec_g.spin : 0; +#elif defined(RETRO_EC_ADDR_LINUX) + ec_linux_spin_init(); + return ec_linux_spin; #else return 0; #endif diff --git a/core/libretro-common/rthreads/rthreads.c b/core/libretro-common/rthreads/rthreads.c index 69c1a82c..339654ea 100644 --- a/core/libretro-common/rthreads/rthreads.c +++ b/core/libretro-common/rthreads/rthreads.c @@ -3741,11 +3741,11 @@ bool scond_wait_timeout(scond_t *cond, slock_t *lock, int64_t timeout_us) clock_gettime(CLOCK_REALTIME, &now); #endif - seconds = timeout_us / INT64_C(1000000); - remainder = timeout_us % INT64_C(1000000); + seconds = timeout_us / 1000000; + remainder = timeout_us % 1000000; now.tv_sec += seconds; - now.tv_nsec += remainder * INT64_C(1000); + now.tv_nsec += remainder * 1000; if (now.tv_nsec >= 1000000000) { diff --git a/core/libretro-common/streams/file_stream.c b/core/libretro-common/streams/file_stream.c index 8d92481d..60731720 100644 --- a/core/libretro-common/streams/file_stream.c +++ b/core/libretro-common/streams/file_stream.c @@ -47,6 +47,10 @@ #include #include +#if defined(VFS_DESCRIPTOR_READ_ALIGN) +#include +#endif + #define VFS_ERROR_RETURN_VALUE -1 /* Size of the per-handle sequential lookahead buffer filled by @@ -152,6 +156,9 @@ struct RFILE size_t rbuf_len; size_t rbuf_pos; int64_t rbuf_file_off; + /* filestream_vprintf()'s, grown to the longest line printed */ + char *pbuf; + size_t pbuf_cap; /* C89 7.9.5.3: on an update stream, output followed by input (or * input followed by output, short of EOF) requires an intervening * file positioning or flush call. Backends over stdio really do @@ -258,8 +265,7 @@ bool filestream_exists(const char *path) RETRO_VFS_FILE_ACCESS_HINT_NONE))) return false; - if (filestream_close(dummy) != 0) - free(dummy); + filestream_close(dummy); dummy = NULL; return true; @@ -456,6 +462,8 @@ RFILE* filestream_open(const char *path, unsigned mode, unsigned hints) output->rbuf_len = 0; output->rbuf_pos = 0; output->rbuf_file_off = 0; + output->pbuf = NULL; + output->pbuf_cap = 0; output->last_io = FILESTREAM_LAST_IO_NONE; return output; } @@ -1641,10 +1649,9 @@ int filestream_rename(const char *old_path, const char *new_path) return retro_vfs_file_rename_impl(old_path, new_path); } -/* v1-only frontends: copy through their open/read/write. Large - * heap buffer rather than the historical 256-byte stack one; the - * destination directory is created before the destination is opened, - * which the old order got backwards. */ +/* v1-only frontends: copy through their open/read/write, a large heap + * buffer at a time. The destination directory is created before the + * destination is opened. */ static int filestream_copy_loop(const char *src, const char *dst) { char *buf = NULL; @@ -1653,12 +1660,14 @@ static int filestream_copy_loop(const char *src, const char *dst) int ret = -1; RFILE *fp_src = NULL; RFILE *fp_dst = NULL; - char path_dst[PATH_MAX_LENGTH]; + char *path_dst; - if (!(buf = (char*)malloc(buf_len))) + /* The copy buffer, then the destination's directory. */ + if (!(buf = (char*)malloc(buf_len + PATH_MAX_LENGTH))) return -1; + path_dst = buf + buf_len; - strlcpy(path_dst, dst, sizeof(path_dst)); + strlcpy(path_dst, dst, PATH_MAX_LENGTH); path_basedir(path_dst); if (!path_is_directory(path_dst)) path_mkdir(path_dst); @@ -1777,8 +1786,6 @@ const char* filestream_get_path(RFILE *stream) bool filestream_matches_buf(const char *path, const void *data, size_t len) { const uint8_t *mem = (const uint8_t*)data; - const uint8_t *map = NULL; - int64_t map_len = 0; bool match = false; RFILE *file; @@ -1795,33 +1802,22 @@ bool filestream_matches_buf(const char *path, const void *data, size_t len) if (filestream_get_size(file) != (int64_t)len) goto done; - if ((map = filestream_get_mapped_ptr(file, &map_len))) { - /* A view that does not span the file would compare the wrong - * bytes; fall through to the copying path rather than guess. */ - if (map_len == (int64_t)len) - { - match = (len == 0) || (memcmp(map, mem, len) == 0); - goto done; - } - if (filestream_seek(file, 0, RETRO_VFS_SEEK_POSITION_START) != 0) - goto done; - } - - { - /* FILESTREAM_MATCHES_BUF_WINDOW, which is sized by the smallest - * thread stack in the tree rather than by throughput. GEKKO - * threads get 8 KiB (STACKSIZE in rthreads.c), 3DS and PSP - * 32 KiB and Vita 64 KiB - and this is libretro-common API, so - * a caller on a spawned thread is not hypothetical. The 8 KiB - * floor is GEKKO's. + /* Compared where it lies when the stream can lend it - the + * mapping, which covers the whole file in one call, or a network + * stream's read-ahead - and through FILESTREAM_MATCHES_BUF_WINDOW + * otherwise. The window is sized by the smallest thread stack in + * the tree rather than by throughput. GEKKO threads get 8 KiB + * (STACKSIZE in rthreads.c), 3DS and PSP 32 KiB and Vita 64 KiB - + * and this is libretro-common API, so a caller on a spawned + * thread is not hypothetical. The 8 KiB floor is GEKKO's. * * Bigger reads are faster, and at or above the VFS's own 64 KiB * stdio buffer they skip it entirely: measured on the unchanged * case, which reads every byte, 1 KiB against 4 KiB against * 64 KiB is 1753 / 1432 / 1153 us on an 8 MiB file. The * platforms paying that are the ones with no memory mapping - - * the mapped branch above copies nothing - which are the same + * a mapping copies nothing - which are the same * ones with the 8 KiB stacks, and this runs when a save is * written rather than in a frame. */ uint8_t window[FILESTREAM_MATCHES_BUF_WINDOW]; @@ -1830,21 +1826,31 @@ bool filestream_matches_buf(const char *path, const void *data, size_t len) match = true; while (off < len) { - int64_t got; - size_t want = len - off; + const uint8_t *ptr = NULL; + int64_t got = filestream_view(file, + (int64_t)(len - off), &ptr); - if (want > sizeof(window)) - want = sizeof(window); - - if ((got = filestream_read(file, window, (int64_t)want)) - != (int64_t)want) + if (got < 0) + { + size_t want = len - off; + if (want > sizeof(window)) + want = sizeof(window); + if ((got = filestream_read(file, window, (int64_t)want)) + != (int64_t)want) + { + match = false; + break; + } + ptr = window; + } + else if (!got) { match = false; break; } /* Stop at the first difference: the caller asked a yes/no * question and a 'no' is final. */ - if (memcmp(window, mem + off, (size_t)got) != 0) + if (memcmp(ptr, mem + off, (size_t)got) != 0) { match = false; break; @@ -1855,7 +1861,7 @@ bool filestream_matches_buf(const char *path, const void *data, size_t len) done: if (filestream_close(file) != 0) - free(file); + match = false; return match; } @@ -1871,12 +1877,24 @@ void filestream_set_mapped_ptr_cb(filestream_mapped_ptr_cb_t cb) filestream_mapped_ptr_cb = cb; } +static filestream_prefetch_cb_t filestream_prefetch_cb; + +void filestream_set_prefetch_cb(filestream_prefetch_cb_t cb) +{ + filestream_prefetch_cb = cb; +} + void filestream_prefetch(RFILE *stream, uint64_t offset, uint64_t len) { if (!stream || !stream->hfile) return; + /* no such request in the libretro VFS interface */ if (filestream_read_cb) - return; /* no such request in the libretro VFS interface */ + { + if (filestream_prefetch_cb) + filestream_prefetch_cb(stream->hfile, offset, len); + return; + } retro_vfs_file_prefetch_impl( (libretro_vfs_implementation_file*)stream->hfile, offset, len); } @@ -1899,6 +1917,55 @@ const uint8_t *filestream_get_mapped_ptr(RFILE *stream, int64_t *len) (libretro_vfs_implementation_file*)stream->hfile, len); } +int64_t filestream_view(RFILE *stream, int64_t len, const uint8_t **ptr) +{ + if (!stream || !ptr || len < 0) + return -1; + if (!len) + return 0; + + if (stream->rbuf_pos < stream->rbuf_len) + { + size_t n = stream->rbuf_len - stream->rbuf_pos; + if ((uint64_t)len < (uint64_t)n) + n = (size_t)len; + *ptr = stream->rbuf + stream->rbuf_pos; + stream->rbuf_pos += n; + /* emptied, it is refilled only by the next gets/getc */ + if (stream->rbuf_pos == stream->rbuf_len) + { + stream->rbuf_pos = 0; + stream->rbuf_len = 0; + } + return (int64_t)n; + } + + if (stream->last_io == FILESTREAM_LAST_IO_WRITE) + filestream_raw_seek(stream, 0, RETRO_VFS_SEEK_POSITION_CURRENT); + stream->last_io = FILESTREAM_LAST_IO_READ; + + if (filestream_read_cb) + { + /* a frontend's mapping, which starts at the start of the file */ + int64_t mlen = 0; + int64_t pos; + const uint8_t *map = filestream_mapped_ptr_cb + ? filestream_mapped_ptr_cb(stream->hfile, &mlen) : NULL; + if (!map || (pos = filestream_raw_tell(stream)) < 0 || pos >= mlen) + return -1; + if (len > mlen - pos) + len = mlen - pos; + if (filestream_raw_seek(stream, pos + len, + RETRO_VFS_SEEK_POSITION_START) < 0) + return -1; + *ptr = map + pos; + return len; + } + return retro_vfs_file_view_impl( + (libretro_vfs_implementation_file*)stream->hfile, + (uint64_t)len, ptr); +} + int64_t filestream_write(RFILE *stream, const void *s, int64_t len) { int64_t output; @@ -1938,16 +2005,48 @@ int filestream_putc(RFILE *stream, int c) : EOF; } +#define FILESTREAM_PRINTF_CAP 1024 + int filestream_vprintf(RFILE *stream, const char* format, va_list args) { - static char buffer[8 * 1024]; - int _len = vsnprintf(buffer, sizeof(buffer), - format, args); + va_list args_copy; + int _len; + + if (!stream) + return -1; + if (!stream->pbuf) + { + if (!(stream->pbuf = (char*)malloc(FILESTREAM_PRINTF_CAP))) + return -1; + stream->pbuf_cap = FILESTREAM_PRINTF_CAP; + } + +#ifdef __va_copy + __va_copy(args_copy, args); +#else + va_copy(args_copy, args); +#endif + _len = vsnprintf(stream->pbuf, stream->pbuf_cap, format, args); + /* a longer line: once more, into room for it, kept for the next */ + if (_len >= 0 && (size_t)_len >= stream->pbuf_cap) + { + char *grown = (char*)malloc((size_t)_len + 1); + if (grown) + { + free(stream->pbuf); + stream->pbuf = grown; + stream->pbuf_cap = (size_t)_len + 1; + vsnprintf(stream->pbuf, stream->pbuf_cap, format, args_copy); + } + else + _len = -1; + } + va_end(args_copy); if (_len < 0) return -1; - else if (_len == 0) + if (_len == 0) return 0; - return (int)filestream_write(stream, buffer, _len); + return (int)filestream_write(stream, stream->pbuf, _len); } int filestream_printf(RFILE *stream, const char* format, ...) @@ -1976,11 +2075,10 @@ int filestream_close(RFILE *stream) output = retro_vfs_file_close_impl( (libretro_vfs_implementation_file*)fp); - if (output == 0) - { - free(stream->rbuf); - free(stream); - } + /* VFS close consumes the handle even on failure (VFS API v1). */ + free(stream->rbuf); + free(stream->pbuf); + free(stream); return output; } @@ -2059,9 +2157,13 @@ static int64_t filestream_read_file_to_eof(RFILE *file, void **buf) int64_t filestream_read_file(const char *path, void **buf, int64_t *len) { + int close_result; int64_t ret = 0; int64_t content_buf_size = 0; void *content_buf = NULL; +#if defined(VFS_DESCRIPTOR_READ_ALIGN) + size_t content_buf_cap = 0; +#endif /* This function is the definition of a bulk sequential read: open, * take the whole file in one go, close. Saying so lets the VFS * skip a read buffer that cannot be reused - the bytes are already @@ -2098,8 +2200,10 @@ int64_t filestream_read_file(const char *path, void **buf, int64_t *len) if ((ret = filestream_read_file_to_eof(file, &content_buf)) < 0) goto error; - if (filestream_close(file) != 0) - free(file); + close_result = filestream_close(file); + file = NULL; + if (close_result != 0) + goto error; *buf = content_buf; if (len) @@ -2131,8 +2235,19 @@ int64_t filestream_read_file(const char *path, void **buf, int64_t *len) || (uint64_t)content_buf_size > FILESTREAM_READ_FILE_MAX) goto error; +#if defined(VFS_DESCRIPTOR_READ_ALIGN) + /* Whole lines from a line boundary, with a line past them for the + * terminator, so each request below can ask for whole lines and + * the read lands in place. memalign()ed storage is what free() + * takes on the targets that set this. */ + content_buf_cap = (((size_t)content_buf_size + VFS_DESCRIPTOR_READ_ALIGN - 1) + & ~(size_t)(VFS_DESCRIPTOR_READ_ALIGN - 1)) + VFS_DESCRIPTOR_READ_ALIGN; + if (!(content_buf = memalign(VFS_DESCRIPTOR_READ_ALIGN, content_buf_cap))) + goto error; +#else if (!(content_buf = malloc((size_t)content_buf_size + 1))) goto error; +#endif /* Read in a loop rather than trusting a single call to satisfy the * whole request. A read is permitted to come up short and try @@ -2159,6 +2274,16 @@ int64_t filestream_read_file(const char *path, void **buf, int64_t *len) if (want > FILESTREAM_BULK_READ_MAX) want = FILESTREAM_BULK_READ_MAX; +#if defined(VFS_DESCRIPTOR_READ_ALIGN) + /* Up to the next whole line: the file ends inside it and the + * read stops there. The terminator's line stays out of reach. */ + want = (want + VFS_DESCRIPTOR_READ_ALIGN - 1) + & ~(int64_t)(VFS_DESCRIPTOR_READ_ALIGN - 1); + if ((uint64_t)want > (uint64_t)(content_buf_cap + - VFS_DESCRIPTOR_READ_ALIGN) - (uint64_t)ret) + want = (int64_t)(content_buf_cap - VFS_DESCRIPTOR_READ_ALIGN + - (size_t)ret); +#endif if ((got = filestream_read(file, (uint8_t*)content_buf + ret, want)) < 0) goto error; @@ -2181,8 +2306,10 @@ int64_t filestream_read_file(const char *path, void **buf, int64_t *len) if (ret == 0) goto error; - if (filestream_close(file) != 0) - free(file); + close_result = filestream_close(file); + file = NULL; + if (close_result != 0) + goto error; *buf = content_buf; @@ -2196,8 +2323,8 @@ int64_t filestream_read_file(const char *path, void **buf, int64_t *len) return 1; error: - if (filestream_close(file) != 0) - free(file); + if (file) + filestream_close(file); if (content_buf) free(content_buf); if (len) @@ -2219,9 +2346,7 @@ bool filestream_write_file(const char *path, const void *data, int64_t size) if (!file) return false; ret = filestream_write(file, data, size); - if (filestream_close(file) != 0) - free(file); - return (ret == size); + return filestream_close(file) == 0 && ret == size; } /* .tmp, the sibling an atomic write goes through; NULL on OOM */ @@ -2253,26 +2378,38 @@ RFILE *filestream_open_atomic(const char *path) return file; } +bool filestream_atomic_temp_name(char *s, size_t len, const char *path) +{ + size_t path_len; + if (!s || !path || !*path) + return false; + path_len = strlen(path); + if (path_len + sizeof(".tmp") > len) + return false; + memcpy(s, path, path_len); + memcpy(s + path_len, ".tmp", sizeof(".tmp")); + return true; +} + int filestream_commit_atomic(RFILE *file, const char *path, bool ok) { - char *temp_path; - if (!file) return -1; - if (!path || !*path || !(temp_path = filestream_atomic_temp_path(path))) - { - if (filestream_close(file) != 0) - free(file); - return -1; - } /* A buffered write reports a full disk at close, not at write, * so both have to agree before the rename goes ahead. */ if (filestream_close(file) != 0) - { - free(file); ok = false; - } + + return filestream_finish_atomic(path, ok); +} + +int filestream_finish_atomic(const char *path, bool ok) +{ + char *temp_path; + + if (!path || !*path || !(temp_path = filestream_atomic_temp_path(path))) + return -1; if (!ok) { diff --git a/core/libretro-common/string/stdstring.c b/core/libretro-common/string/stdstring.c index 66815f76..2c2f5e7c 100644 --- a/core/libretro-common/string/stdstring.c +++ b/core/libretro-common/string/stdstring.c @@ -221,10 +221,12 @@ size_t word_wrap( { char *last_space = NULL; unsigned counter = 0; + unsigned space_counter = 0; + bool space_on_line = false; unsigned lines = 1; const char *src_end = src + src_len; char *s_start = s; - size_t _len = len; + size_t text_len = 0; /* Prevent buffer overflow */ if (len < src_len + 1) @@ -235,54 +237,66 @@ size_t word_wrap( if (src_len < (size_t)line_width) return strlcpy(s, src, len); + /* Only whitespace changes; rewound spans already contain their text. */ + text_len = strlcpy(s, src, len); + while (*src != '\0') { - unsigned char_len = (unsigned)(utf8skip(src, 1) - src); + unsigned char_len = (unsigned char)*src < 0x80 + ? 1 : (unsigned)(utf8skip(src, 1) - src); counter++; if (*src == ' ') + { last_space = s; /* Remember the location of the whitespace */ + space_counter = counter; + space_on_line = true; + } else if (*src == '\n') { /* If newlines embedded in the input, * reset the index */ lines++; counter = 0; + space_on_line = false; /* Early return if remaining src string * length is less than line width */ if (src_end - src <= line_width) - { - _len = len - (size_t)(s - s_start); - return (size_t)(s - s_start) + strlcpy(s, src, _len); - } + return text_len; } - while (char_len--) - *s++ = *src++; + s += char_len; + src += char_len; if (counter >= (unsigned)line_width) { + unsigned scanned = counter; counter = 0; if (last_space && (max_lines == 0 || lines < max_lines)) { + const char *restart = src - (s - last_space - 1); /* Replace nearest (previous) whitespace * with newline character */ *last_space = '\n'; lines++; - src -= s - last_space - 1; - s = last_space + 1; + if (space_on_line) + counter = scanned - space_counter; + else + { + /* A saved space may precede an embedded newline. */ + src = restart; + s = last_space + 1; + } last_space = NULL; + space_on_line = false; /* Early return if remaining src string * length is less than line width */ - if (src_end - src < line_width) - { - _len = len - (size_t)(s - s_start); - return (size_t)(s - s_start) + strlcpy(s, src, _len); - } + if (src_end - restart < line_width) + return text_len; } } } @@ -337,6 +351,8 @@ size_t word_wrap_wideglyph(char *s, size_t len, const char *src_end = src + src_len; char *s_start = s; unsigned lines = 1; + unsigned space_counter = 0; + bool space_on_line = false; /* 'line_width' means max numbers of characters per line, * but this metric is only meaningful when dealing with * 'regular' glyphs that have an on-screen pixel width @@ -384,7 +400,8 @@ size_t word_wrap_wideglyph(char *s, size_t len, while (*src != '\0') { size_t remaining; - unsigned char_len = (unsigned)(utf8skip(src, 1) - src); + unsigned char_len = (unsigned char)*src < 0x80 + ? 1 : (unsigned)(utf8skip(src, 1) - src); counter_normalized += 100; /* Prevent buffer overflow. `remaining` is computed from @@ -399,13 +416,18 @@ size_t word_wrap_wideglyph(char *s, size_t len, break; if (*src == ' ') - lastspace = s; /* Remember the location of the whitespace */ + { + lastspace = s; + space_counter = counter_normalized; + space_on_line = true; + } else if (*src == '\n') { /* If newlines embedded in the input, * reset the index */ lines++; counter_normalized = 0; + space_on_line = false; /* Early return if remaining src string * length is less than line width */ @@ -425,12 +447,42 @@ size_t word_wrap_wideglyph(char *s, size_t len, counter_normalized += additional_counter_normalized; } + /* A break before this glyph need not copy and decode it twice. */ + if (lastwideglyph == s && char_len >= 3 + && counter_normalized >= (unsigned)line_width_normalized + && (max_lines == 0 || lines < max_lines) + && (unsigned)wideglyph_width < (unsigned)line_width_normalized + && char_len + 1 < remaining) + { + *s++ = '\n'; + lines++; + if (src_end - src <= line_width) + { + size_t copied; + remaining--; + copied = strlcpy(s, src, remaining); + if (copied >= remaining) + copied = remaining - 1; + return (size_t)(s - s_start) + copied; + } + lastwideglyph = s; + counter_normalized = (unsigned)wideglyph_width; + space_on_line = false; + memcpy(s, src, char_len); + s += char_len; + src += char_len; + continue; + } + while (char_len--) *s++ = *src++; if (counter_normalized >= (unsigned)line_width_normalized) { + unsigned scanned = counter_normalized; + bool carry_space = space_on_line; counter_normalized = 0; + space_on_line = false; if (max_lines != 0 && lines >= max_lines) continue; @@ -462,6 +514,14 @@ size_t word_wrap_wideglyph(char *s, size_t len, * with newline character */ *lastspace = '\n'; lines++; + /* The tail after this space has already been measured. */ + if (carry_space + && src_end - (src - (s - lastspace - 1)) >= line_width) + { + counter_normalized = scanned - space_counter; + lastspace = NULL; + continue; + } src -= s - lastspace - 1; s = lastspace + 1; lastspace = NULL; diff --git a/core/libretro-common/vfs/vfs_implementation.c b/core/libretro-common/vfs/vfs_implementation.c index fd7343f7..73a0748c 100644 --- a/core/libretro-common/vfs/vfs_implementation.c +++ b/core/libretro-common/vfs/vfs_implementation.c @@ -788,7 +788,17 @@ libretro_vfs_implementation_file *retro_vfs_file_open_impl( stream->orig_path = strdup(path); #ifdef VFS_HAVE_FILE_MAPPING - if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS && mode == RETRO_VFS_FILE_ACCESS_READ) + /* Only a descriptor can be mapped: plain files, and SAF, which the + * descriptor branch opens. cdrom://, smb:// and nfs:// streams are + * opened by their own backends in the buffered branch and read + * through them, unmapped. */ + if ( (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS) + && mode == RETRO_VFS_FILE_ACCESS_READ + && ( stream->scheme == VFS_SCHEME_NONE +#if defined(ANDROID) && defined(HAVE_SAF) + || stream->scheme == VFS_SCHEME_SAF +#endif + )) stream->hints |= RFILE_HINT_UNBUFFERED; else #endif @@ -872,7 +882,7 @@ libretro_vfs_implementation_file *retro_vfs_file_open_impl( case VFS_SCHEME_CDROM: retro_vfs_file_open_cdrom(stream, path, mode, hints); #if defined(_WIN32) && !defined(_XBOX) - if (!stream->fh) + if (!stream->fh || stream->fh == INVALID_HANDLE_VALUE) goto error; #elif defined(__APPLE__) if (!stream->iokit_mmc) @@ -1213,13 +1223,14 @@ error: int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream) { + int result = 0; if (!stream) return -1; #ifdef HAVE_CDROM if (stream->scheme == VFS_SCHEME_CDROM) { - retro_vfs_file_close_cdrom(stream); + result = retro_vfs_file_close_cdrom(stream); goto end; } #endif @@ -1242,7 +1253,8 @@ int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream) if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0) { if (stream->fp) - fclose(stream->fp); + if (fclose(stream->fp) != 0) + result = -1; } else { @@ -1271,9 +1283,11 @@ int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream) if (stream->fd >= 0) { #if defined(VITA) - sceIoClose((SceUID)stream->fd); + if (sceIoClose((SceUID)stream->fd) < 0) + result = -1; #else - close(stream->fd); + if (close(stream->fd) != 0) + result = -1; #endif } #ifdef HAVE_CDROM @@ -1300,7 +1314,7 @@ netend: free(stream); - return 0; + return result; } int retro_vfs_file_error_impl(libretro_vfs_implementation_file *stream) @@ -1638,7 +1652,7 @@ int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file *stream, i return 0; } #endif -#elif !defined(VITA) && !defined(PSP) && !defined(PS2) && !defined(ORBIS) && (!defined(SWITCH) || defined(HAVE_LIBNX)) +#elif !defined(VITA) && !defined(PSP) && !defined(PS2) && !defined(ORBIS) if (stream && stream->fp && ftruncate(fileno(stream->fp), (off_t)len) == 0) { stream->size = len; @@ -1685,6 +1699,43 @@ int64_t retro_vfs_file_seek_impl(libretro_vfs_implementation_file *stream, return retro_vfs_file_seek_internal(stream, offset, seek_position); } +int64_t retro_vfs_file_view_impl(libretro_vfs_implementation_file *stream, + uint64_t len, const uint8_t **ptr) +{ + if (!stream || !ptr || len > (uint64_t)INT64_MAX) + return -1; + + if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0) + { +#ifdef HAVE_SMBCLIENT + if (stream->scheme == VFS_SCHEME_SMB) + return retro_vfs_file_view_smb(stream, len, ptr); +#endif +#ifdef HAVE_NFSCLIENT + if (stream->scheme == VFS_SCHEME_NFS) + return retro_vfs_file_view_nfs(stream, len, ptr); +#endif + return -1; + } +#ifdef VFS_HAVE_FILE_MAPPING + if (stream->mapped + && (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)) + { + if (stream->mappos >= stream->mapsize) + return stream->mappos == stream->mapsize ? 0 : -1; + if (len > stream->mapsize - stream->mappos) + len = stream->mapsize - stream->mappos; +#if defined(_WIN32) && !defined(_XBOX) && !defined(HAVE_MMAP) + vfs_win32_readahead(stream, stream->mappos, len); +#endif + *ptr = &stream->mapped[stream->mappos]; + stream->mappos += len; + return (int64_t)len; + } +#endif + return -1; +} + int64_t retro_vfs_file_read_impl(libretro_vfs_implementation_file *stream, void *s, uint64_t len) { @@ -2245,7 +2296,7 @@ int retro_vfs_file_rename_impl(const char *old_path, const char *new_path) free(aside); return ret; } -#elif defined(_3DS) || defined(PS2) || defined(WIIU) || defined(GEKKO) || defined(__PSL1GHT__) || defined(__PS3__) || (defined(SWITCH) && defined(HAVE_LIBNX)) +#elif defined(_3DS) || defined(PS2) || defined(WIIU) || defined(GEKKO) || defined(__PSL1GHT__) || defined(__PS3__) || defined(SWITCH) /* The filesystems here refuse to rename onto an existing entry - * FS on 3DS and Switch, FatFs behind bdmfs_fatfs on PS2, libfat * on Wii and GameCube, LV2's sys_fs_rename on PS3 - and libnx, @@ -2320,8 +2371,26 @@ typedef struct typedef BOOL (WINAPI *vfs_prefetch_t)(HANDLE, ULONG_PTR, vfs_win32_memory_range_t*, ULONG); +#ifdef VFS_WIN32_PREFETCH_HOOK +/* A test build points the call at its own stand-in (see + * samples/file/vfs/vfs_prefetch_offthread_test.c); nothing else + * defines this. */ +static BOOL WINAPI vfs_win32_prefetch_hooked(HANDLE process, + ULONG_PTR count, vfs_win32_memory_range_t *ranges, ULONG flags) +{ + (void)process; + (void)count; + (void)flags; + return VFS_WIN32_PREFETCH_HOOK(ranges->VirtualAddress, + (size_t)ranges->NumberOfBytes); +} +#endif + static vfs_prefetch_t vfs_win32_p_prefetch; -/* 0 unresolved, 1 PrefetchVirtualMemory, 2 toucher thread, 3 nothing */ +/* 0 unresolved, 1 PrefetchVirtualMemory, 2 page toucher, 3 nothing. + * Either of the first two runs on the stream's read-ahead thread, never + * on the caller: PrefetchVirtualMemory is not documented to return + * before its reads complete, and the caller is usually a frame. */ static int vfs_win32_prefetch_mode; /* RETRO_VFS_PREFETCH=touch|none overrides the choice, for tests */ @@ -2330,21 +2399,28 @@ static int vfs_win32_prefetch_resolve(void) if (!vfs_win32_prefetch_mode) { const char *force = getenv("RETRO_VFS_PREFETCH"); - HMODULE k32 = GetModuleHandleA("kernel32.dll"); int mode = 2; if (force && !*force) force = NULL; - if (k32 && !force) - vfs_win32_p_prefetch = (vfs_prefetch_t)GetProcAddress(k32, - "PrefetchVirtualMemory"); + if (!force) + { +#ifdef VFS_WIN32_PREFETCH_HOOK + vfs_win32_p_prefetch = vfs_win32_prefetch_hooked; +#else + HMODULE k32 = GetModuleHandleA("kernel32.dll"); + if (k32) + vfs_win32_p_prefetch = (vfs_prefetch_t)GetProcAddress(k32, + "PrefetchVirtualMemory"); +#endif + } if (vfs_win32_p_prefetch) mode = 1; if (force && !strcmp(force, "none")) mode = 3; #ifndef HAVE_THREADS - if (mode == 2) - mode = 3; + /* no thread to run either on */ + mode = 3; #endif vfs_win32_prefetch_mode = mode; } @@ -2353,17 +2429,18 @@ static int vfs_win32_prefetch_resolve(void) /* Distance read ahead of a sequential reader, how far past the last * read's end the next read may start and still count as sequential, - * and the toucher's step. */ + * and the unit ranges are published and touched in. */ #define VFS_RA_AHEAD (4u * 1024u * 1024u) #define VFS_RA_GAP (256u * 1024u) #define VFS_WARM_UNIT (64u * 1024u) #ifdef HAVE_THREADS -/* Before Windows 8: a thread touches the view ahead of the reader, one - * read per page. The fault brings the page into the process as a - * mapped read would, so the reader finds it resident, with nothing - * copied. The range is published as unit indices and a generation; - * a torn pair only warms a stale range once, and is clamped. */ +/* The stream's read-ahead thread. The caller publishes a range as unit + * indices and a generation and returns; the thread brings in what of it + * is not already done, with PrefetchVirtualMemory or, before Windows 8, + * by touching a byte per page so the fault brings each page into the + * process as a mapped read would. A torn pair only warms a stale range + * once, and is clamped. */ typedef struct vfs_win32_warm { uint64_t size; @@ -2375,6 +2452,7 @@ typedef struct vfs_win32_warm retro_atomic_int_t hi; retro_atomic_int_t gen; retro_atomic_int_t stop; + int mode; /* 1 or 2, as resolved */ int req_lo; /* reader side only */ int req_hi; } vfs_win32_warm_t; @@ -2399,18 +2477,39 @@ static void vfs_win32_warm_thread(void *data) int lo = retro_atomic_load_acquire_int(&w->lo); seen = g; hi = retro_atomic_load_acquire_int(&w->hi); + /* a range that goes on from the last continues where that one + * got to, so no byte is asked for twice */ if (cur < lo || cur >= hi) cur = lo; } if (cur < hi) { uint64_t o = (uint64_t)cur * VFS_WARM_UNIT; - uint64_t end = o + VFS_WARM_UNIT; + uint64_t end = (uint64_t)hi * VFS_WARM_UNIT; if (end > w->size) end = w->size; - for (; o < end; o += w->page) - sink += w->base[o]; - cur++; + if (o >= end) + { + cur = hi; + continue; + } + if (w->mode == 1) + { + vfs_win32_memory_range_t range; + range.VirtualAddress = (PVOID)(w->base + o); + range.NumberOfBytes = (SIZE_T)(end - o); + vfs_win32_p_prefetch(GetCurrentProcess(), 1, &range, 0); + cur = hi; + } + else + { + /* a unit at a time, so a new range is seen between units */ + if (end > o + VFS_WARM_UNIT) + end = o + VFS_WARM_UNIT; + for (; o < end; o += w->page) + sink += w->base[o]; + cur++; + } continue; } { @@ -2439,7 +2538,7 @@ static void vfs_win32_warm_free(void *data) } static void vfs_win32_warm(libretro_vfs_implementation_file *stream, - size_t lo_byte, size_t hi_byte, size_t page) + size_t lo_byte, size_t hi_byte, size_t page, int mode) { vfs_win32_warm_t *w = (vfs_win32_warm_t*)stream->warm; int lo, hi; @@ -2452,6 +2551,7 @@ static void vfs_win32_warm(libretro_vfs_implementation_file *stream, w->base = stream->mapped; w->size = stream->mapsize; w->page = page; + w->mode = mode; retro_atomic_int_init(&w->lo, 0); retro_atomic_int_init(&w->hi, 0); retro_atomic_int_init(&w->gen, 0); @@ -2478,9 +2578,10 @@ static void vfs_win32_warm(libretro_vfs_implementation_file *stream, } #endif -/* A mapped read that follows on from the last one hints the next - * VFS_RA_AHEAD bytes each time half of the hinted span is used up. - * The view itself starts no read-ahead on Windows. */ +/* A mapped read that follows on from the last one hints the + * VFS_RA_AHEAD bytes past its end each time half of the hinted span is + * used up. The read's own bytes are not in the hint: the read is about + * to fault them in itself. The view starts no read-ahead on Windows. */ static void vfs_win32_readahead(libretro_vfs_implementation_file *stream, uint64_t pos, uint64_t len) { @@ -2496,7 +2597,7 @@ static void vfs_win32_readahead(libretro_vfs_implementation_file *stream, if (!seq || end <= stream->ra_next || end >= stream->mapsize) return; stream->ra_next = end + VFS_RA_AHEAD / 2; - retro_vfs_file_prefetch_impl(stream, pos, len + VFS_RA_AHEAD); + retro_vfs_file_prefetch_impl(stream, end, VFS_RA_AHEAD); } #endif @@ -2545,23 +2646,16 @@ void retro_vfs_file_prefetch_impl( posix_madvise(stream->mapped + lo, hi - lo, POSIX_MADV_WILLNEED); #endif #else - switch (vfs_win32_prefetch_resolve()) { - case 1: - { - vfs_win32_memory_range_t range; - range.VirtualAddress = stream->mapped + lo; - range.NumberOfBytes = hi - lo; - vfs_win32_p_prefetch(GetCurrentProcess(), 1, &range, 0); - } - break; + const int mode = vfs_win32_prefetch_resolve(); #ifdef HAVE_THREADS - case 2: - vfs_win32_warm(stream, lo, hi, page); - break; + if (mode == 1 || mode == 2) + vfs_win32_warm(stream, lo, hi, page, mode); +#else + (void)mode; + (void)lo; + (void)hi; #endif - default: - break; } #endif return; @@ -3655,7 +3749,6 @@ struct retro_vfs_copy_handle *retro_vfs_copy_begin_impl( int64_t src_size = 0; int sflags, dflags; bool native = true; - char dst_buf[PATH_MAX_LENGTH]; if (!src || !*src || !dst || !*dst) return NULL; @@ -3702,14 +3795,18 @@ struct retro_vfs_copy_handle *retro_vfs_copy_begin_impl( if (last && last > dst) { size_t n = (size_t)(last - dst); - if (n >= sizeof(dst_buf)) + char *dst_dir; + int parent_ok; + if (n >= PATH_MAX_LENGTH || !(dst_dir = (char*)malloc(n + 1))) return NULL; - memcpy(dst_buf, dst, n); - dst_buf[n] = '\0'; + memcpy(dst_dir, dst, n); + dst_dir[n] = '\0'; /* mkdir reports "exists" for a file of that name too, so * confirm the parent really is a directory. */ - if ( vfs_copy_mkdir_parents(dst_buf) != 0 - || !(retro_vfs_stat_full(dst_buf, NULL, NULL) & RETRO_VFS_STAT_IS_DIRECTORY)) + parent_ok = vfs_copy_mkdir_parents(dst_dir) == 0 + && (retro_vfs_stat_full(dst_dir, NULL, NULL) & RETRO_VFS_STAT_IS_DIRECTORY); + free(dst_dir); + if (!parent_ok) return NULL; } } @@ -4152,23 +4249,24 @@ const char *retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir *rdir #if !defined(_WIN32) && !defined(VITA) && !defined(__PSL1GHT__) && !defined(__PS3__) /* Split out of retro_vfs_dirent_is_dir_impl() so that the d_type test - * there does not have to carry this scratch buffer. Filesystems that - * populate d_type - ext4, APFS, NTFS - answer from the dirent alone and - * never reach this, but the array and the struct stat were declared in - * the same scope as the test, so every entry paid a 2224-byte frame - * plus, under -fstack-protector-strong, a canary written and re-read at - * offset 2200 of a frame the fast path otherwise never touches. */ + * there does not carry this one's frame. Filesystems that populate + * d_type - ext4, APFS, NTFS - answer from the dirent alone and never + * reach this. The joined path is on the heap: next to the stat it is + * cheap, and it keeps the frame within the 2 KiB thread-stack budget. */ static VFS_NOINLINE bool retro_vfs_dirent_is_dir_stat( libretro_vfs_implementation_dir *rdir) { struct stat buf; - char path[PATH_MAX_LENGTH]; + bool is_dir; + char *path = (char*)malloc(PATH_MAX_LENGTH); - fill_pathname_join_special(path, rdir->orig_path, - retro_vfs_dirent_get_name_impl(rdir), sizeof(path)); - if (stat(path, &buf) < 0) + if (!path) return false; - return S_ISDIR(buf.st_mode); + fill_pathname_join_special(path, rdir->orig_path, + retro_vfs_dirent_get_name_impl(rdir), PATH_MAX_LENGTH); + is_dir = stat(path, &buf) == 0 && S_ISDIR(buf.st_mode); + free(path); + return is_dir; } #endif @@ -4213,22 +4311,25 @@ bool retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir *rdir) } /* The join-and-stat fallback: one full-path stat per entry, which is - * exactly what a caller without dirent_stat would do itself, so it is - * never worse than today. Split out so the PATH_MAX_LENGTH local - * stays off the fast paths' stack. Only compiled where some branch - * of dirent_stat reaches it. */ + * exactly what a caller without dirent_stat would do itself. Split out + * so the fast paths do not carry its frame, and the joined path is on + * the heap to keep this one within the 2 KiB thread-stack budget. Only + * compiled where some branch of dirent_stat reaches it. */ #if defined(HAVE_SMBCLIENT) || defined(HAVE_NFSCLIENT) || (defined(ANDROID) && defined(HAVE_SAF)) \ || !(defined(_WIN32) || defined(VITA) \ || defined(VFS_HAVE_FSTATAT)) static VFS_NOINLINE int retro_vfs_dirent_stat_slow( libretro_vfs_implementation_dir *rdir, int64_t *size, int64_t *mtime) { - char path[PATH_MAX_LENGTH]; + char *path; + int flags; const char *name = retro_vfs_dirent_get_name_impl(rdir); - if (!name || !rdir->orig_path) + if (!name || !rdir->orig_path || !(path = (char*)malloc(PATH_MAX_LENGTH))) return 0; - fill_pathname_join_special(path, rdir->orig_path, name, sizeof(path)); - return retro_vfs_stat_full(path, size, mtime); + fill_pathname_join_special(path, rdir->orig_path, name, PATH_MAX_LENGTH); + flags = retro_vfs_stat_full(path, size, mtime); + free(path); + return flags; } #endif diff --git a/core/libretro-common/vfs/vfs_implementation_cdrom.c b/core/libretro-common/vfs/vfs_implementation_cdrom.c index fdd93133..25ba4bbd 100644 --- a/core/libretro-common/vfs/vfs_implementation_cdrom.c +++ b/core/libretro-common/vfs/vfs_implementation_cdrom.c @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -38,12 +39,10 @@ int cdrom_macos_open(int index, void **out_plugin, void **out_mmc, int *out_fd); void cdrom_macos_close(void *plugin, void *mmc, int fd); #endif -/* TODO/FIXME - static global variable */ -static cdrom_toc_t vfs_cdrom_toc; - -const cdrom_toc_t* retro_vfs_file_get_cdrom_toc(void) +const cdrom_toc_t* retro_vfs_file_get_cdrom_toc( + const libretro_vfs_implementation_file *stream) { - return &vfs_cdrom_toc; + return stream && stream->cdrom ? stream->cdrom->toc : NULL; } int64_t retro_vfs_file_seek_cdrom( @@ -51,24 +50,58 @@ int64_t retro_vfs_file_seek_cdrom( int64_t offset, int whence) { const char *ext = path_get_extension(stream->orig_path); + const cdrom_toc_t *toc = retro_vfs_file_get_cdrom_toc(stream); + if (stream->cdrom->device_only) + return offset == 0 && (whence == SEEK_SET || whence == SEEK_CUR + || whence == SEEK_END) ? 0 : -1; + if (stream->cdrom->logical_blocks) + { + int64_t base; + switch (whence) + { + case SEEK_SET: + base = 0; + break; + case SEEK_CUR: + base = stream->cdrom->byte_pos; + break; + case SEEK_END: + base = (int64_t)stream->cdrom->disc_bytes; + break; + default: + return -1; + } + if (offset < -base || offset > INT64_MAX - base) + return -1; + stream->cdrom->byte_pos = base + offset; + return 0; + } if ( (ext[0] == 'c' || ext[0] == 'C') && (ext[1] == 'u' || ext[1] == 'U') && (ext[2] == 'e' || ext[2] == 'E') && ext[3] == '\0') { + int64_t base; switch (whence) { case SEEK_SET: - stream->cdrom->byte_pos = offset; + base = 0; break; case SEEK_CUR: - stream->cdrom->byte_pos += offset; + base = stream->cdrom->byte_pos; break; case SEEK_END: - stream->cdrom->byte_pos = (stream->cdrom->cue_len - 1) + offset; + /* cue_len counts the buffer's terminating NUL. */ + base = stream->cdrom->cue_len + ? (int64_t)stream->cdrom->cue_len - 1 : 0; break; + default: + return -1; } + if (offset < -base || offset > INT64_MAX - base) + return -1; + stream->cdrom->byte_pos = base + offset; #ifdef CDROM_DEBUG printf("[CDROM] Seek: Path %s Offset %" PRIu64 " is now at %" PRIu64 "\n", stream->orig_path, @@ -82,70 +115,37 @@ int64_t retro_vfs_file_seek_cdrom( && (ext[2] == 'n' || ext[2] == 'N') && ext[3] == '\0') { - int lba = (int)(offset / 2352); - unsigned char min = 0; - unsigned char sec = 0; - unsigned char frame = 0; -#ifdef CDROM_DEBUG - const char *seek_type = "SEEK_SET"; -#endif - + int64_t base, pos; + unsigned origin; + const cdrom_track_t *track; + if (!toc || !stream->cdrom->cur_track + || stream->cdrom->cur_track > toc->num_tracks) + return -1; + track = &toc->track[stream->cdrom->cur_track - 1]; switch (whence) { + case SEEK_SET: + base = 0; + break; case SEEK_CUR: - { - unsigned new_lba; -#ifdef CDROM_DEBUG - seek_type = "SEEK_CUR"; -#endif - stream->cdrom->byte_pos += offset; - new_lba = vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].lba + (unsigned)(stream->cdrom->byte_pos / 2352); - cdrom_lba_to_msf(new_lba, &min, &sec, &frame); - } + base = stream->cdrom->byte_pos; break; case SEEK_END: - { - ssize_t pregap_lba_len = (vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].audio - ? 0 - : (vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].lba - vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].lba_start)); - ssize_t lba_len = vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].track_size - pregap_lba_len; -#ifdef CDROM_DEBUG - seek_type = "SEEK_END"; -#endif - cdrom_lba_to_msf((unsigned)(lba_len + lba), &min, &sec, &frame); - stream->cdrom->byte_pos = lba_len * 2352; - } + base = track->track_bytes; break; - case SEEK_SET: default: - { -#ifdef CDROM_DEBUG - seek_type = "SEEK_SET"; -#endif - stream->cdrom->byte_pos = offset; - cdrom_lba_to_msf(vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].lba + (unsigned)(stream->cdrom->byte_pos / 2352), &min, &sec, &frame); - } - break; + return -1; } - - stream->cdrom->cur_min = min; - stream->cdrom->cur_sec = sec; - stream->cdrom->cur_frame = frame; - stream->cdrom->cur_lba = cdrom_msf_to_lba(min, sec, frame); - -#ifdef CDROM_DEBUG - printf( - "[CDROM] Seek %s: Path %s Offset %" PRIu64 " is now at %" PRIu64 " (MSF %02u:%02u:%02u) (LBA %u)...\n", - seek_type, - stream->orig_path, - offset, - stream->cdrom->byte_pos, - (unsigned)stream->cdrom->cur_min, - (unsigned)stream->cdrom->cur_sec, - (unsigned)stream->cdrom->cur_frame, - stream->cdrom->cur_lba); - fflush(stdout); -#endif + if (offset < -base || offset > INT64_MAX - base) + return -1; + pos = base + offset; + origin = track->audio ? track->lba_start : track->lba; + if ((uint64_t)(pos / 2352) > UINT32_MAX - origin) + return -1; + stream->cdrom->byte_pos = pos; + stream->cdrom->cur_lba = origin + (unsigned)(pos / 2352); + cdrom_lba_to_msf(stream->cdrom->cur_lba, &stream->cdrom->cur_min, + &stream->cdrom->cur_sec, &stream->cdrom->cur_frame); } else return -1; @@ -153,300 +153,149 @@ int64_t retro_vfs_file_seek_cdrom( return 0; } -void retro_vfs_file_open_cdrom( - libretro_vfs_implementation_file *stream, +static bool retro_vfs_cdrom_parse_path(const char *path, vfs_cdrom_t *cdrom) +{ + const char *tail; +#if defined(_WIN32) && !defined(_XBOX) + if (!path || strlen(path) < 8 || memcmp(path + 1, ":/drive", 7) + || !((path[0] >= 'A' && path[0] <= 'Z') + || (path[0] >= 'a' && path[0] <= 'z'))) + return false; + cdrom->drive = path[0]; + tail = path + 8; +#elif (defined(__linux__) && !defined(ANDROID)) || defined(__APPLE__) + if (!path || strlen(path) < 6 || memcmp(path, "drive", 5) + || path[5] < '0' || path[5] > '9') + return false; + cdrom->drive = path[5]; + tail = path + 6; +#else + (void)path; + (void)cdrom; + return false; +#endif + cdrom->cur_track = 1; + cdrom->device_only = strcmp(tail, ".device") == 0; + if (cdrom->device_only || strcmp(tail, ".cue") == 0) + return true; +#if !defined(__APPLE__) + if (strcmp(tail, ".iso") == 0) + return true; +#endif + if (strlen(tail) != 12 || memcmp(tail, "-track", 6) + || tail[6] < '0' || tail[6] > '9' + || tail[7] < '0' || tail[7] > '9' || strcmp(tail + 8, ".bin")) + return false; + cdrom->cur_track = (unsigned char)((tail[6] - '0') * 10 + tail[7] - '0'); + return cdrom->cur_track != 0; +} + +void retro_vfs_file_open_cdrom(libretro_vfs_implementation_file *stream, const char *path, unsigned mode, unsigned hints) { + unsigned origin; + const cdrom_track_t *track; + const char *ext; + (void)hints; + if (!stream || !stream->cdrom || mode != RETRO_VFS_FILE_ACCESS_READ + || !retro_vfs_cdrom_parse_path(path, stream->cdrom)) + return; + ext = path_get_extension(path); #if defined(__linux__) && !defined(ANDROID) - char cdrom_path[] = "/dev/sg1"; - size_t path_len = strlen(path); - const char *ext = path_get_extension(path); - - stream->cdrom->cur_track = 1; - - if ( !(ext[0] == 'c' && ext[1] == 'u' && ext[2] == 'e' && ext[3] == '\0') - && !(ext[0] == 'b' && ext[1] == 'i' && ext[2] == 'n' && ext[3] == '\0')) + { + char device[] = "/dev/sg1"; + device[7] = stream->cdrom->drive; + stream->fp = (FILE*)fopen_utf8(device, "r+b"); + if (!stream->fp) + return; + } +#elif defined(_WIN32) && !defined(_XBOX) + { + char device[] = "\\\\.\\D:"; + device[4] = stream->cdrom->drive; + stream->fh = CreateFile(device, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (stream->fh == INVALID_HANDLE_VALUE) + { + stream->fh = NULL; + return; + } + } +#elif defined(__APPLE__) + if (cdrom_macos_open(stream->cdrom->drive - '0', &stream->iokit_plugin, + &stream->iokit_mmc, &stream->fd) != 0) return; - - if (path_len >= (sizeof("drive1-track01.bin")-1)) - { - if (!memcmp(path, "drive", (sizeof("drive")-1))) - { - if (!memcmp(path + 6, "-track", (sizeof("-track")-1))) - { - if ( path[12] >= '0' && path[12] <= '9' - && path[13] >= '0' && path[13] <= '9') - { - unsigned parsed = (path[12] - '0') * 10 + (path[13] - '0'); - /* Reject track 00 -- track numbers are 1-based, and - * cur_track == 0 would later index track[-1] in the - * TOC array (OOB read). Leave the init value (1) - * in place on reject. */ - if (parsed >= 1 && parsed <= 99) - stream->cdrom->cur_track = parsed; -#ifdef CDROM_DEBUG - printf("[CDROM] Opening track %d\n", stream->cdrom->cur_track); - fflush(stdout); +#else + return; #endif - } - } - } - } - - if (path_len >= (sizeof("drive1.cue")-1)) - { - if (!memcmp(path, "drive", (sizeof("drive")-1))) - { - if (path[5] >= '0' && path[5] <= '9') - { - cdrom_path[7] = path[5]; - stream->cdrom->drive = path[5]; - vfs_cdrom_toc.drive = stream->cdrom->drive; - } - } - } - -#ifdef CDROM_DEBUG - printf("[CDROM] Open: Path %s URI %s\n", cdrom_path, path); - fflush(stdout); -#endif - stream->fp = (FILE*)fopen_utf8(cdrom_path, "r+b"); - - if (!stream->fp) + if (stream->cdrom->device_only) return; - - if (ext[0] == 'c' && ext[1] == 'u' && ext[2] == 'e' && ext[3] == '\0') + if (strcmp(ext, "iso") == 0) { - if (stream->cdrom->cue_buf) - { - free(stream->cdrom->cue_buf); - stream->cdrom->cue_buf = NULL; - } - - cdrom_write_cue(stream, - &stream->cdrom->cue_buf, - &stream->cdrom->cue_len, - stream->cdrom->drive, - &vfs_cdrom_toc.num_tracks, - &vfs_cdrom_toc); - cdrom_get_timeouts(stream, &vfs_cdrom_toc.timeouts); - -#ifdef CDROM_DEBUG - if (!stream->cdrom->cue_buf || !*stream->cdrom->cue_buf) - { - printf("[CDROM] Error writing cue sheet.\n"); - fflush(stdout); - } - else - { - printf("[CDROM] CUE Sheet:\n%s\n", stream->cdrom->cue_buf); - fflush(stdout); - } -#endif - } -#endif -#if defined(_WIN32) && !defined(_XBOX) - char cdrom_path[] = "\\\\.\\D:"; - size_t path_len = strlen(path); - const char *ext = path_get_extension(path); - - if ( !(ext[0] == 'c' && ext[1] == 'u' && ext[2] == 'e' && ext[3] == '\0') - && !(ext[0] == 'b' && ext[1] == 'i' && ext[2] == 'n' && ext[3] == '\0')) + uint64_t blocks = 0; + uint32_t size = 0; + if (cdrom_get_capacity(stream, &blocks, &size) || size != 2048) + goto error; + stream->cdrom->logical_blocks = true; + stream->cdrom->disc_bytes = blocks * size; return; - - if (path_len >= (sizeof("d:/drive-track01.bin")-1)) - { - if (!memcmp(path + 1, ":/drive-track", (sizeof(":/drive-track")-1))) - { - if ( path[14] >= '0' && path[14] <= '9' - && path[15] >= '0' && path[15] <= '9') - { - unsigned parsed = (path[14] - '0') * 10 + (path[15] - '0'); - /* Reject track 00 -- track numbers are 1-based, and - * cur_track == 0 would later index track[-1] in the - * TOC array (OOB read). Leave the init value (1) in - * place on reject. */ - if (parsed >= 1 && parsed <= 99) - stream->cdrom->cur_track = parsed; -#ifdef CDROM_DEBUG - printf("[CDROM] Opening track %d\n", stream->cdrom->cur_track); - fflush(stdout); -#endif - } - } } - - if (path_len >= (sizeof("d:/drive.cue")-1)) + stream->cdrom->toc = (cdrom_toc_t*)calloc(1, sizeof(*stream->cdrom->toc)); + if (!stream->cdrom->toc || cdrom_write_cue(stream, + &stream->cdrom->cue_buf, &stream->cdrom->cue_len, + stream->cdrom->drive, &stream->cdrom->toc->num_tracks, + stream->cdrom->toc)) + goto error; + if (stream->cdrom->cur_track > stream->cdrom->toc->num_tracks) + goto error; + cdrom_get_timeouts(stream, &stream->cdrom->toc->timeouts); + if (strcmp(ext, "bin") == 0) { - if (!memcmp(path + 1, ":/drive", (sizeof(":/drive")-1))) - { - if ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) - { - cdrom_path[4] = path[0]; - stream->cdrom->drive = path[0]; - vfs_cdrom_toc.drive = stream->cdrom->drive; - } - } - } - -#ifdef CDROM_DEBUG - printf("[CDROM] Open: Path %s URI %s\n", cdrom_path, path); - fflush(stdout); -#endif - stream->fh = CreateFile(cdrom_path, - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL); - - if (stream->fh == INVALID_HANDLE_VALUE) - return; - - if (ext[0] == 'c' && ext[1] == 'u' && ext[2] == 'e' && ext[3] == '\0') - { - if (stream->cdrom->cue_buf) - { - free(stream->cdrom->cue_buf); - stream->cdrom->cue_buf = NULL; - } - - cdrom_write_cue(stream, - &stream->cdrom->cue_buf, - &stream->cdrom->cue_len, - stream->cdrom->drive, - &vfs_cdrom_toc.num_tracks, - &vfs_cdrom_toc); - cdrom_get_timeouts(stream, - &vfs_cdrom_toc.timeouts); - - } -#endif -#if defined(__APPLE__) - size_t path_len = strlen(path); - const char *ext = path_get_extension(path); - void *plugin = NULL; - void *mmc = NULL; - int fd = -1; - int index = 0; - - stream->cdrom->cur_track = 1; - - if ( !(ext[0] == 'c' && ext[1] == 'u' && ext[2] == 'e' && ext[3] == '\0') - && !(ext[0] == 'b' && ext[1] == 'i' && ext[2] == 'n' && ext[3] == '\0')) - return; - - if (path_len >= (sizeof("drive1-track01.bin")-1)) - { - if (!memcmp(path, "drive", (sizeof("drive")-1))) - { - if (!memcmp(path + 6, "-track", (sizeof("-track")-1))) - { - if ( path[12] >= '0' && path[12] <= '9' - && path[13] >= '0' && path[13] <= '9') - { - unsigned parsed = (path[12] - '0') * 10 + (path[13] - '0'); - /* Reject track 00 -- track numbers are 1-based, and - * cur_track == 0 would later index track[-1] in the - * TOC array (OOB read). Leave the init value (1) - * in place on reject. */ - if (parsed >= 1 && parsed <= 99) - stream->cdrom->cur_track = parsed; - } - } - } - } - - if (path_len >= (sizeof("drive1.cue")-1)) - { - if (!memcmp(path, "drive", (sizeof("drive")-1))) - { - if (path[5] >= '0' && path[5] <= '9') - { - index = path[5] - '0'; - stream->cdrom->drive = path[5]; - vfs_cdrom_toc.drive = stream->cdrom->drive; - } - } - } - - if (cdrom_macos_open(index, &plugin, &mmc, &fd) != 0) - return; - - stream->iokit_plugin = plugin; - stream->iokit_mmc = mmc; - stream->fd = fd; - - if (ext[0] == 'c' && ext[1] == 'u' && ext[2] == 'e' && ext[3] == '\0') - { - if (stream->cdrom->cue_buf) - { - free(stream->cdrom->cue_buf); - stream->cdrom->cue_buf = NULL; - } - - cdrom_write_cue(stream, - &stream->cdrom->cue_buf, - &stream->cdrom->cue_len, - stream->cdrom->drive, - &vfs_cdrom_toc.num_tracks, - &vfs_cdrom_toc); - cdrom_get_timeouts(stream, &vfs_cdrom_toc.timeouts); - -#ifdef CDROM_DEBUG - if (!stream->cdrom->cue_buf || !*stream->cdrom->cue_buf) - { - printf("[CDROM] Error writing cue sheet.\n"); - fflush(stdout); - } - else - { - printf("[CDROM] CUE Sheet:\n%s\n", stream->cdrom->cue_buf); - fflush(stdout); - } -#endif - } -#endif - if (vfs_cdrom_toc.num_tracks > 1 && stream->cdrom->cur_track) - { - stream->cdrom->cur_min = vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].min; - stream->cdrom->cur_sec = vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].sec; - stream->cdrom->cur_frame = vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].frame; - stream->cdrom->cur_lba = cdrom_msf_to_lba(stream->cdrom->cur_min, stream->cdrom->cur_sec, stream->cdrom->cur_frame); - } - else - { - stream->cdrom->cur_min = vfs_cdrom_toc.track[0].min; - stream->cdrom->cur_sec = vfs_cdrom_toc.track[0].sec; - stream->cdrom->cur_frame = vfs_cdrom_toc.track[0].frame; - stream->cdrom->cur_lba = cdrom_msf_to_lba(stream->cdrom->cur_min, stream->cdrom->cur_sec, stream->cdrom->cur_frame); + free(stream->cdrom->cue_buf); + stream->cdrom->cue_buf = NULL; + stream->cdrom->cue_len = 0; } + track = &stream->cdrom->toc->track[stream->cdrom->cur_track - 1]; + origin = track->audio ? track->lba_start : track->lba; + stream->cdrom->cur_lba = origin; + cdrom_lba_to_msf(origin, &stream->cdrom->cur_min, + &stream->cdrom->cur_sec, &stream->cdrom->cur_frame); + return; +error: + retro_vfs_file_close_cdrom(stream); } int retro_vfs_file_close_cdrom(libretro_vfs_implementation_file *stream) { -#ifdef CDROM_DEBUG - printf("[CDROM] Close: Path %s\n", stream->orig_path); - fflush(stdout); -#endif - + int rv = 0; #if defined(_WIN32) && !defined(_XBOX) - if (!stream->fh || !CloseHandle(stream->fh)) - return -1; + if (!stream->fh || stream->fh == INVALID_HANDLE_VALUE || !CloseHandle(stream->fh)) + rv = -1; + stream->fh = NULL; #elif defined(__APPLE__) if (!stream->iokit_mmc) - return -1; - cdrom_macos_close(stream->iokit_plugin, stream->iokit_mmc, stream->fd); + rv = -1; + else + cdrom_macos_close(stream->iokit_plugin, stream->iokit_mmc, stream->fd); stream->iokit_plugin = NULL; - stream->iokit_mmc = NULL; - stream->fd = -1; + stream->iokit_mmc = NULL; + stream->fd = -1; #else if (!stream->fp || fclose(stream->fp)) - return -1; + rv = -1; + stream->fp = NULL; #endif - - return 0; + if (stream->cdrom) + { + free(stream->cdrom->toc); + stream->cdrom->toc = NULL; + free(stream->cdrom->cue_buf); + stream->cdrom->cue_buf = NULL; + stream->cdrom->cue_len = 0; + stream->cdrom->last_frame_valid = false; + } + return rv; } int64_t retro_vfs_file_tell_cdrom(libretro_vfs_implementation_file *stream) @@ -455,6 +304,9 @@ int64_t retro_vfs_file_tell_cdrom(libretro_vfs_implementation_file *stream) if (!stream) return -1; + if (stream->cdrom->logical_blocks || stream->cdrom->device_only) + return stream->cdrom->byte_pos; + ext = path_get_extension(stream->orig_path); if ( (ext[0] == 'c' || ext[0] == 'C') @@ -488,6 +340,81 @@ int64_t retro_vfs_file_read_cdrom(libretro_vfs_implementation_file *stream, { int rv; const char *ext = path_get_extension(stream->orig_path); + const cdrom_toc_t *toc = retro_vfs_file_get_cdrom_toc(stream); + if (!len) + return 0; + if (stream->cdrom->media_changed) + { + stream->cdrom->io_error = true; + return -1; + } + if (stream->cdrom->device_only) + { + if (len) + { + stream->cdrom->io_error = true; + return -1; + } + return 0; + } + if (stream->cdrom->logical_blocks) + { + unsigned char *dst = (unsigned char*)s; + uint64_t done = 0; + uint64_t pos = (uint64_t)stream->cdrom->byte_pos; + if (!len || pos >= stream->cdrom->disc_bytes) + return 0; + if (!s) + return -1; + if (len > stream->cdrom->disc_bytes - pos) + len = stream->cdrom->disc_bytes - pos; + if (len > SIZE_MAX) + len = SIZE_MAX; + while (done < len) + { + uint32_t lba = (uint32_t)(pos / 2048); + size_t skip = (size_t)(pos % 2048); + size_t n; + if (skip || len - done < 2048 + || (stream->cdrom->last_frame_valid + && stream->cdrom->last_frame_lba == lba)) + { + if (!stream->cdrom->last_frame_valid + || stream->cdrom->last_frame_lba != lba) + { + stream->cdrom->last_frame_valid = false; + if (cdrom_read_blocks(stream, lba, 1, + stream->cdrom->last_frame)) + break; + stream->cdrom->last_frame_lba = lba; + stream->cdrom->last_frame_valid = true; + } + n = 2048 - skip; + if (n > len - done) + n = (size_t)(len - done); + memcpy(dst + (size_t)done, stream->cdrom->last_frame + skip, n); + } + else + { + uint32_t blocks = (uint32_t)((len - done) / 2048 > 32 + ? 32 : (len - done) / 2048); + n = (size_t)blocks * 2048; + if (cdrom_read_blocks(stream, lba, blocks, dst + (size_t)done)) + break; + } + done += n; + pos += n; + } + stream->cdrom->byte_pos += (int64_t)done; + if (done != len) + { + stream->cdrom->io_error = true; + stream->cdrom->last_frame_valid = false; + if (!done) + return -1; + } + return (int64_t)done; + } if ( (ext[0] == 'c' || ext[0] == 'C') && (ext[1] == 'u' || ext[1] == 'U') @@ -543,12 +470,12 @@ int64_t retro_vfs_file_read_cdrom(libretro_vfs_implementation_file *stream, * against an out-of-range byte_pos. cur_track can become * 0 via a crafted VFS path like "driveN-track00.bin" unless * the parser rejects it (see retro_vfs_file_open_cdrom). */ - if (stream->cdrom->cur_track == 0 - || stream->cdrom->cur_track > 99) - return 0; + if (!toc || stream->cdrom->cur_track == 0 + || stream->cdrom->cur_track > toc->num_tracks) + return -1; { size_t track_bytes = - vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].track_bytes; + toc->track[stream->cdrom->cur_track - 1].track_bytes; if ( stream->cdrom->byte_pos < 0 || (size_t)stream->cdrom->byte_pos >= track_bytes) return 0; @@ -564,7 +491,7 @@ int64_t retro_vfs_file_read_cdrom(libretro_vfs_implementation_file *stream, cdrom_lba_to_msf(stream->cdrom->cur_lba, &min, &sec, &frame); cdrom_lba_to_msf(stream->cdrom->cur_lba - - vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].lba, + - toc->track[stream->cdrom->cur_track - 1].lba, &rmin, &rsec, &rframe); #ifdef CDROM_DEBUG @@ -585,7 +512,7 @@ int64_t retro_vfs_file_read_cdrom(libretro_vfs_implementation_file *stream, #endif #if 1 - rv = cdrom_read(stream, &vfs_cdrom_toc.timeouts, min, sec, + rv = cdrom_read(stream, &stream->cdrom->toc->timeouts, min, sec, frame, s, (size_t)len, skip); #else rv = cdrom_read_lba(stream, stream->cdrom->cur_lba, s, @@ -598,12 +525,15 @@ int64_t retro_vfs_file_read_cdrom(libretro_vfs_implementation_file *stream, printf("[CDROM] Failed to read %" PRIu64 " bytes from CD.\n", len); fflush(stdout); #endif - return 0; + stream->cdrom->io_error = true; + return -1; } stream->cdrom->byte_pos += len; stream->cdrom->cur_lba = - vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].lba + (toc->track[stream->cdrom->cur_track - 1].audio + ? toc->track[stream->cdrom->cur_track - 1].lba_start + : toc->track[stream->cdrom->cur_track - 1].lba) + (unsigned)(stream->cdrom->byte_pos / 2352); cdrom_lba_to_msf(stream->cdrom->cur_lba, @@ -635,7 +565,7 @@ int64_t retro_vfs_file_read_cdrom(libretro_vfs_implementation_file *stream, int retro_vfs_file_error_cdrom(libretro_vfs_implementation_file *stream) { - return 0; + return stream && stream->cdrom && stream->cdrom->io_error ? 1 : 0; } const vfs_cdrom_t* retro_vfs_file_get_cdrom_position( diff --git a/tools/archive/run.sh b/tools/archive/run.sh index ec1593cc..1919caea 100755 --- a/tools/archive/run.sh +++ b/tools/archive/run.sh @@ -40,7 +40,7 @@ SOURCES="tools/archive/archive_test.c core/archive/archive.c \ core/deps/coreio/coreio.c \ $L/encodings/encoding_deflate.c $L/encodings/encoding_crc32.c \ $L/encodings/encoding_utf.c \ - $L/formats/7z/r7z_archive.c $L/formats/7z/r7z_lzma.c \ + $L/formats/7z/r7z_archive.c $L/encodings/encoding_rbzip2.c $L/formats/7z/r7z_lzma.c \ $L/formats/7z/r7z_lzma2.c $L/formats/7z/r7z_lzma_stream.c \ $L/formats/7z/r7z_bcj2.c $L/formats/7z/r7z_filters.c \ $L/formats/zip/rzip_archive.c \ diff --git a/tools/chd/run.sh b/tools/chd/run.sh index acfcd992..60ab210e 100755 --- a/tools/chd/run.sh +++ b/tools/chd/run.sh @@ -54,7 +54,7 @@ $CC -O1 -g $VDEFS -fsanitize=address,undefined \ $L/formats/chd/rchd.c $L/encodings/encoding_huffman.c \ $L/encodings/encoding_rzstd.c $L/encodings/encoding_deflate.c \ $L/encodings/encoding_crc32.c $L/formats/7z/r7z_lzma.c \ - $L/formats/7z/r7z_archive.c $L/formats/7z/r7z_lzma2.c \ + $L/formats/7z/r7z_archive.c $L/encodings/encoding_rbzip2.c $L/formats/7z/r7z_lzma2.c \ $L/formats/7z/r7z_lzma_stream.c $L/formats/7z/r7z_bcj2.c \ $L/formats/7z/r7z_filters.c $L/formats/zip/rzip_archive.c \ $L/formats/rar/rrar_archive.c $L/formats/rar/rrar_ppmd7.c \ @@ -74,7 +74,7 @@ STREAM_SRC="core/imgread/chd_image.c core/deps/coreio/coreio.c core/archive/archive.c $L/formats/chd/rchd.c $L/encodings/encoding_huffman.c $L/encodings/encoding_rzstd.c $L/encodings/encoding_deflate.c $L/encodings/encoding_crc32.c - $L/formats/7z/r7z_lzma.c $L/formats/7z/r7z_archive.c + $L/formats/7z/r7z_lzma.c $L/formats/7z/r7z_archive.c $L/encodings/encoding_rbzip2.c $L/formats/7z/r7z_lzma2.c $L/formats/7z/r7z_lzma_stream.c $L/formats/7z/r7z_bcj2.c $L/formats/7z/r7z_filters.c $L/formats/zip/rzip_archive.c $L/formats/rar/rrar_archive.c