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.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-10-27 15:37:52 +01:00
1 parent f62d495d2d
commit 5ce2ba0443
40 files changed
+4112 -936

No files matched your search

+1
View File
@@ -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 \
+408 -116
View File
@@ -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;
}
+577 -29
View File
@@ -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(<nmmintrin.h>)
* 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(<crc32intrin.h>)
# include <crc32intrin.h>
# endif
# endif
#endif
#if defined(RD_CRC32_HASH_RUNTIME)
#include <nmmintrin.h>
#include <features/features_cpu.h>
#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 <nmmintrin.h>
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;
}
@@ -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 <encodings/rbzip2.h> for the format. */
#include <stdlib.h>
#include <string.h>
#include <encodings/rbzip2.h>
#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;
}
@@ -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,
@@ -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. */
+9 -3
View File
@@ -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;
}
+94 -4
View File
@@ -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 <errno.h>
#include <sys/auxv.h>
#include <asm/hwcap.h>
#endif
#endif
#include <retro_atomic.h>
#include <retro_timers.h>
@@ -109,9 +120,6 @@ extern long syscall(long number, ...);
#if defined(HAVE_LIBNX)
#include <switch.h>
#elif defined(SWITCH)
#include <libtransistor/types.h>
#include <libtransistor/svc.h>
#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;
+24 -10
View File
@@ -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;
}
+491 -142
View File
@@ -57,6 +57,12 @@
#include <7z/r7z_lzma2.h>
#include <7z/r7z_bcj2.h>
#include <7z/r7z_filters.h>
#include <encodings/deflate.h>
#ifdef HAVE_RBZIP2
#include <encodings/rbzip2.h>
#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;
}
@@ -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;
}
+8 -3
View File
@@ -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;
+43 -6
View File
@@ -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;
}
+12 -2
View File
@@ -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);
@@ -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.
*
@@ -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
+36 -1
View File
@@ -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
@@ -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). */
@@ -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 <stddef.h>
#include <stdint.h>
#include <retro_common_api.h>
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
@@ -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
@@ -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
+5 -1
View File
@@ -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
+2 -2
View File
@@ -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;
}
+4 -1
View File
@@ -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
@@ -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__)
+62 -12
View File
@@ -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
@@ -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
@@ -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.
+20 -2
View File
@@ -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;
@@ -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);
@@ -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);
+290 -31
View File
@@ -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 <stdlib.h>
#include <string.h>
#include <retro_inline.h>
#include <retro_spsc.h>
/* 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 <sys/syscall.h>
#ifdef __NR_memfd_create
#define SPSC_MIRROR_MEMFD
#endif
#elif defined(__APPLE__)
#include <TargetConditionals.h>
#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 <windows.h>
#elif defined(SPSC_MIRROR_MEMFD) || defined(SPSC_MIRROR_SHM)
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#endif
#if defined(SPSC_MIRROR_WIN32) || defined(SPSC_MIRROR_MEMFD) \
|| defined(SPSC_MIRROR_SHM)
#define SPSC_MIRROR
#endif
#ifdef SPSC_MIRROR_SHM
#include <stdio.h>
#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;
+247 -47
View File
@@ -44,6 +44,8 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <ctype.h>
#include <rthreads/retro_eventcount.h>
#include <rthreads/rthreads.h>
@@ -161,7 +163,6 @@ typedef char retro_eventcount_epoch_is_a_word_
#if defined(RETRO_EC_ADDR_LINUX)
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <sys/syscall.h>
#include <linux/futex.h>
@@ -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 <windows.h>
#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
+3 -3
View File
@@ -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)
{
+208 -71
View File
@@ -47,6 +47,10 @@
#include <vfs/vfs_implementation.h>
#include <string/rstrtod.h>
#if defined(VFS_DESCRIPTOR_READ_ALIGN)
#include <malloc.h>
#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;
}
/* <path>.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)
{
+77 -17
View File
@@ -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;
+171 -70
View File
@@ -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
@@ -24,6 +24,7 @@
#include <string.h>
#include <vfs/vfs_implementation.h>
#include <vfs/vfs_implementation_cdrom.h>
#include <file/file_path.h>
#include <compat/fopen_utf8.h>
#include <cdrom/cdrom.h>
@@ -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(
+1 -1
View File
@@ -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 \
+2 -2
View File
@@ -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