mirror of
https://github.com/libretro/parallel-dc.git
synced 2026-11-07 04:25:58 -10:00
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:
1 parent
f62d495d2d
commit
5ce2ba0443
40 files changed
+4112
-936
No files matched your search
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user