This commit is contained in:
libretroadmin committed 2026-10-10 21:23:55 +02:00
1 parent 2b96a82bd8
commit a0403d4273
161 files changed
+15062 -541

No files matched your search

+1
View File
@@ -75,6 +75,7 @@ bool retro_resampler_int16_new(retro_resampler_int16_t *out,
out->process = sinc_resampler_int16_process;
out->reset = sinc_resampler_int16_reset;
out->free = sinc_resampler_int16_free;
out->sibling = sinc_resampler_int16_sibling;
}
#ifdef HAVE_NEAREST_RESAMPLER
else if (string_is_equal(short_ident, "nearest"))
+382 -53
View File
@@ -34,6 +34,7 @@
#include <retro_environment.h>
#include <retro_inline.h>
#include <retro_atomic.h>
#include <filters.h>
#include <memalign.h>
@@ -67,21 +68,61 @@ enum sinc_window
SINC_WINDOW_LANCZOS
};
/* Decimation by two ahead of the kernel when fast-forward takes the
* live ratio below what the table rejects images for. Each stage is the
* preset's filter as its table would be built for a ratio of
* SINC_DEC_ENGAGE / 2, at the one phase decimation needs, so behind a
* stage the kernel may run down to SINC_DEC_ENGAGE of its design ratio.
* The first stage joins below SINC_DEC_ENGAGE_FIRST, under every
* preset's passband edge and over rate control's reach, and leaves
* inside rate control's band; deeper ones leave well clear of where they
* join, which keeps the powers of two mid-band against speed jitter. */
#define SINC_DEC_STAGES 4
#define SINC_DEC_BLOCK 256
#define SINC_DEC_ENGAGE_FIRST 0.97
#define SINC_DEC_ENGAGE 0.9
#define SINC_DEC_RELEASE_LAST 0.975
#define SINC_DEC_RELEASE 1.25
/* A change of stages renders the old path alongside until the new one
* has filled its rings, then crossfades; this holds the old output. */
#define SINC_DEC_FADE_FRAMES 256
#define SINC_DEC_HALF (2 * (SINC_DEC_BLOCK / 2 + 1))
/* For the little amount of taps we're using,
* SSE1 is faster than AVX for some reason.
* AVX code is kept here though as by increasing number
* of sinc taps, the AVX code is clearly faster than SSE1.
*/
/* Leads the block that holds the immutable tables, which every sibling
* of an instance reads; the last one out frees it. */
typedef struct sinc_tables
{
retro_atomic_int_t refs;
} sinc_tables_t;
/* In floats: keeps the tables on the block's 128-byte alignment. */
#define SINC_TABLES_OFFSET 32
typedef struct rarch_sinc_resampler
{
/* A buffer for phase_table, buffer_l and buffer_r
* are created in a single calloc().
* Ensure that we get as good cache locality as we can hope for. */
double design_ratio;
/* The stream state - rings, the stages' rings, the shadow and the
* blocks - in one block, and the shared tables in another. */
float *main_buffer;
sinc_tables_t *tables;
float *phase_table;
float *buffer_l;
float *buffer_r;
/* SINC_DEC_STAGES one-phase instances sharing one table, then the
* kernel's shadow for a crossfade; two blocks stages hand through. */
struct rarch_sinc_resampler *dec;
float *dec_block;
float *fade_block;
unsigned dec_stages;
unsigned fade_stages;
unsigned fade_left;
unsigned fade_len;
unsigned phase_bits;
unsigned subphase_bits;
unsigned subphase_mask;
@@ -101,6 +142,8 @@ typedef struct rarch_sinc_resampler
void (*process)(void *re, struct resampler_data *data);
} rarch_sinc_resampler_t;
typedef void (*sinc_process_t)(void *re, struct resampler_data *data);
/*
* Macro to handle the common "push input samples into ring buffer"
* logic shared by every process function. Reduces code duplication and
@@ -128,12 +171,13 @@ typedef struct rarch_sinc_resampler
#ifdef HAVE_ARM_NEON_ASM_OPTIMIZATIONS
void process_sinc_neon_asm(float *out, const float *left,
const float *right, const float *coeff, unsigned taps);
#else
void process_sinc_neon_kaiser_asm(float *out, const float *left,
const float *right, const float *coeff, unsigned taps,
const float *frac);
#endif
#include <arm_neon.h>
/* Assumes that taps >= 8, and that taps is a multiple of 8.
* Not bothering to reimplement this one for the external .S
*/
/* Assumes that taps >= 8, and that taps is a multiple of 8. */
static void resampler_sinc_process_neon_kaiser(void *re_, struct resampler_data *data)
{
rarch_sinc_resampler_t *resamp = (rarch_sinc_resampler_t*)re_;
@@ -155,11 +199,17 @@ static void resampler_sinc_process_neon_kaiser(void *re_, struct resampler_data
const float *buffer_r = resamp->buffer_r + resamp->ptr;
while (resamp->time < phases)
{
unsigned phase = resamp->time >> resamp->subphase_bits;
const float *phase_table = resamp->phase_table + phase * taps2;
#ifdef HAVE_ARM_NEON_ASM_OPTIMIZATIONS
float frac = (resamp->time & resamp->subphase_mask)
* resamp->subphase_mod;
process_sinc_neon_kaiser_asm(output, buffer_l, buffer_r,
phase_table, taps, &frac);
#else
/* C89: all declarations at top of block */
int i;
float32x2_t p3, p4;
unsigned phase = resamp->time >> resamp->subphase_bits;
const float *phase_table = resamp->phase_table + phase * taps2;
const float *delta_table = phase_table + taps;
float32x4_t delta = vdupq_n_f32((resamp->time & resamp->subphase_mask) * resamp->subphase_mod);
float32x4_t p1 = vdupq_n_f32(0.0f);
@@ -184,6 +234,7 @@ static void resampler_sinc_process_neon_kaiser(void *re_, struct resampler_data
p3 = vadd_f32(vget_low_f32(p1), vget_high_f32(p1));
p4 = vadd_f32(vget_low_f32(p2), vget_high_f32(p2));
vst1_f32(output, vpadd_f32(p3, p4));
#endif
output += 2;
out_frames++;
resamp->time += ratio;
@@ -193,7 +244,6 @@ static void resampler_sinc_process_neon_kaiser(void *re_, struct resampler_data
data->output_frames = out_frames;
}
#endif
/* Assumes that taps >= 8, and that taps is a multiple of 8. */
static void resampler_sinc_process_neon(void *re_, struct resampler_data *data)
@@ -673,10 +723,83 @@ static void resampler_sinc_free(void *data)
{
rarch_sinc_resampler_t *resamp = (rarch_sinc_resampler_t*)data;
if (resamp)
{
memalign_free(resamp->main_buffer);
free(resamp->dec);
if ( resamp->tables
&& retro_atomic_fetch_sub_int(&resamp->tables->refs, 1) == 1)
memalign_free(resamp->tables);
}
free(resamp);
}
static size_t sinc_state_elems(unsigned taps, unsigned dec_taps)
{
return 8 * taps + 4 * dec_taps * SINC_DEC_STAGES
+ 2 * SINC_DEC_FADE_FRAMES + 2 * SINC_DEC_HALF;
}
/* Lays this instance's state out over main_buffer: the kernel's rings,
* each stage's, the shadow's, then the blocks. */
static void sinc_bind_state(rarch_sinc_resampler_t *re)
{
unsigned s;
unsigned dec_taps = re->dec[0].taps;
float *at = re->main_buffer + 4 * re->taps;
re->buffer_l = re->main_buffer;
re->buffer_r = re->buffer_l + 2 * re->taps;
for (s = 0; s < SINC_DEC_STAGES; s++)
{
re->dec[s].buffer_l = at;
re->dec[s].buffer_r = at + 2 * dec_taps;
at += 4 * dec_taps;
}
re->dec[SINC_DEC_STAGES] = *re;
re->dec[SINC_DEC_STAGES].dec = NULL;
re->dec[SINC_DEC_STAGES].buffer_l = at;
re->dec[SINC_DEC_STAGES].buffer_r = at + 2 * re->taps;
re->fade_block = at + 4 * re->taps;
re->dec_block = re->fade_block
+ 2 * SINC_DEC_FADE_FRAMES;
}
/* A fresh stream over another instance's tables. */
static void *resampler_sinc_sibling(void *data)
{
unsigned s;
rarch_sinc_resampler_t *src = (rarch_sinc_resampler_t*)data;
rarch_sinc_resampler_t *re;
size_t elems;
if (!src || !(re = (rarch_sinc_resampler_t*)calloc(1, sizeof(*re))))
return NULL;
*re = *src;
elems = sinc_state_elems(src->taps, src->dec[0].taps);
re->main_buffer = (float*)memalign_alloc(128, sizeof(float) * elems);
re->dec = (rarch_sinc_resampler_t*)
calloc(SINC_DEC_STAGES + 1, sizeof(*re->dec));
if (!re->main_buffer || !re->dec)
{
memalign_free(re->main_buffer);
free(re->dec);
free(re);
return NULL;
}
memset(re->main_buffer, 0, sizeof(float) * elems);
for (s = 0; s < SINC_DEC_STAGES; s++)
{
re->dec[s] = src->dec[s];
re->dec[s].ptr = 0;
re->dec[s].time = 0;
}
re->ptr = 0;
re->time = 0;
re->dec_stages = 0;
re->fade_left = 0;
sinc_bind_state(re);
retro_atomic_inc_int(&re->tables->refs);
return re;
}
/* The rings, the ring pointer and the phase; the table stands. */
static void resampler_sinc_reset(void *data)
{
@@ -685,8 +808,173 @@ static void resampler_sinc_reset(void *data)
return;
memset(resamp->buffer_l, 0, sizeof(float) * 2 * resamp->taps);
memset(resamp->buffer_r, 0, sizeof(float) * 2 * resamp->taps);
resamp->ptr = 0;
resamp->time = 0;
resamp->ptr = 0;
resamp->time = 0;
resamp->dec_stages = 0;
resamp->fade_left = 0;
}
/* Clean rings, and a clock that takes input before it emits: a joining
* path then produces no output ahead of the frames it was given. */
static void sinc_dec_restart(rarch_sinc_resampler_t *stage)
{
memset(stage->buffer_l, 0, sizeof(float) * 4 * stage->taps);
stage->ptr = 0;
stage->time = 1u << (stage->phase_bits + stage->subphase_bits);
}
/* Runs stages [first, last) from in; returns where the output is. */
static const float *sinc_dec_chain(rarch_sinc_resampler_t *re,
unsigned first, unsigned last, const float *in, size_t *frames)
{
struct resampler_data chunk;
float *out = (in == re->dec_block) ? re->dec_block + SINC_DEC_HALF
: re->dec_block;
chunk.ratio = 0.5;
for (; first < last; first++)
{
chunk.data_in = in;
chunk.input_frames = *frames;
chunk.data_out = out;
chunk.output_frames = 0;
re->dec[first].process(&re->dec[first], &chunk);
*frames = chunk.output_frames;
in = out;
out = (out == re->dec_block)
? re->dec_block + SINC_DEC_HALF : re->dec_block;
}
return in;
}
static size_t sinc_dec_kernel(rarch_sinc_resampler_t *kernel,
const float *in, size_t frames, float *out, double ratio)
{
struct resampler_data chunk;
chunk.data_in = in;
chunk.input_frames = frames;
chunk.data_out = out;
chunk.output_frames = 0;
chunk.ratio = ratio;
kernel->process(kernel, &chunk);
return chunk.output_frames;
}
/* The kernel's rings move to the shadow, which carries on with the old
* stages; the kernel and any stage joining start clean. */
static void sinc_dec_switch(rarch_sinc_resampler_t *re, unsigned stages,
double ratio)
{
unsigned s;
double warm = re->taps * ratio * (double)(1u << stages);
rarch_sinc_resampler_t *shadow = &re->dec[SINC_DEC_STAGES];
memcpy(shadow->buffer_l, re->buffer_l, sizeof(float) * 4 * re->taps);
shadow->ptr = re->ptr;
shadow->time = re->time;
sinc_dec_restart(re);
for (s = re->dec_stages; s < stages; s++)
{
sinc_dec_restart(&re->dec[s]);
warm += re->dec[s].taps * ratio * (double)(1u << s);
}
if (warm > 8192.0)
warm = 8192.0;
/* The paths' delays differ, so a fade shorter than a few
* milliseconds would splice rather than blend. */
re->fade_len = (warm < 256.0) ? 256 : (unsigned)warm + 1;
re->fade_left = (unsigned)warm + 1 + re->fade_len;
re->fade_stages = re->dec_stages;
re->dec_stages = stages;
}
static void sinc_dec_blend(rarch_sinc_resampler_t *re, float *out,
size_t frames, const float *old, size_t old_frames)
{
size_t i;
if (!old_frames)
return;
for (i = 0; i < frames && re->fade_left; i++, re->fade_left--)
{
size_t o = (i < old_frames) ? i : old_frames - 1;
float w = (re->fade_left > re->fade_len) ? 0.0f
: 1.0f - (float)re->fade_left / (float)re->fade_len;
out[2 * i] = old[2 * o] + (out[2 * i] - old[2 * o]) * w;
out[2 * i + 1] = old[2 * o + 1] + (out[2 * i + 1] - old[2 * o + 1]) * w;
}
}
static void resampler_sinc_process_decimated(rarch_sinc_resampler_t *re,
struct resampler_data *data)
{
const float *input = data->data_in;
float *output = data->data_out;
size_t frames = data->input_frames;
size_t produced = 0;
size_t step = SINC_DEC_BLOCK;
double ratio = data->ratio;
if (!re->fade_left)
{
unsigned stages = re->dec_stages;
while ( stages < SINC_DEC_STAGES
&& ratio * (double)(1u << stages)
< re->design_ratio
* (stages ? SINC_DEC_ENGAGE : SINC_DEC_ENGAGE_FIRST))
stages++;
while (stages && ratio * (double)(1u << (stages - 1))
>= re->design_ratio
* ((stages == 1) ? SINC_DEC_RELEASE_LAST : SINC_DEC_RELEASE))
stages--;
if (stages != re->dec_stages)
sinc_dec_switch(re, stages, ratio);
}
/* The old path's output must fit the fade block. */
if (re->fade_left)
while (step > 1 && step * ratio + 4.0 > SINC_DEC_FADE_FRAMES)
step >>= 1;
while (frames)
{
size_t n = (frames < step) ? frames : step;
size_t got;
unsigned now_k = re->dec_stages;
float *out = output + 2 * produced;
input += 2 * n;
frames -= n;
if (!re->fade_left)
{
const float *p = sinc_dec_chain(re, 0, now_k, input - 2 * n, &n);
got = sinc_dec_kernel(re, p, n, out, ratio * (double)(1u << now_k));
}
else
{
rarch_sinc_resampler_t *shadow = &re->dec[SINC_DEC_STAGES];
unsigned old_k = re->fade_stages;
unsigned common = (now_k < old_k) ? now_k : old_k;
const float *p = sinc_dec_chain(re, 0, common, input - 2 * n, &n);
size_t old;
/* The path without stages of its own reads p before the other
* path's stages reuse its block. */
if (now_k == common)
{
got = sinc_dec_kernel(re, p, n, out,
ratio * (double)(1u << now_k));
p = sinc_dec_chain(re, common, old_k, p, &n);
old = sinc_dec_kernel(shadow, p, n, re->fade_block,
ratio * (double)(1u << old_k));
}
else
{
old = sinc_dec_kernel(shadow, p, n, re->fade_block,
ratio * (double)(1u << old_k));
p = sinc_dec_chain(re, common, now_k, p, &n);
got = sinc_dec_kernel(re, p, n, out,
ratio * (double)(1u << now_k));
}
sinc_dec_blend(re, out, got, re->fade_block, old);
}
produced += got;
}
data->output_frames = produced;
}
static void sinc_init_table_kaiser(rarch_sinc_resampler_t *resamp,
@@ -825,11 +1113,41 @@ static void sinc_init_table_lanczos(
}
}
/* Widest kernel both compiled in and allowed by the mask. */
static sinc_process_t sinc_select_process(int kaiser,
resampler_simd_mask_t mask, unsigned enable_avx)
{
sinc_process_t process = kaiser
? resampler_sinc_process_c_kaiser : resampler_sinc_process_c;
#if defined(__SSE__)
if (mask & RESAMPLER_SIMD_SSE)
process = kaiser
? resampler_sinc_process_sse_kaiser : resampler_sinc_process_sse;
#endif
#if defined(__AVX__)
if ((mask & RESAMPLER_SIMD_AVX) && enable_avx)
process = kaiser
? resampler_sinc_process_avx_kaiser : resampler_sinc_process_avx;
#else
(void)enable_avx;
#endif
#if (defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(HAVE_NEON))
if (mask & RESAMPLER_SIMD_NEON)
process = kaiser
? resampler_sinc_process_neon_kaiser : resampler_sinc_process_neon;
#endif
return process;
}
void *sinc_resampler_init_hq(double bandwidth_mod,
enum resampler_quality quality, resampler_simd_mask_t mask,
int hq_oversampling)
{
double cutoff = 0.0;
double dec_cutoff = 0.0;
float dec_beta = 0.0f;
unsigned dec_taps = 0;
unsigned s;
size_t phase_elems = 0;
size_t elems = 0;
unsigned enable_avx = 0;
@@ -904,6 +1222,21 @@ void *sinc_resampler_init_hq(double bandwidth_mod,
re->subphase_mask = (1 << re->subphase_bits) - 1;
re->subphase_mod = 1.0f / (1 << re->subphase_bits);
re->taps = sidelobes * 2;
re->design_ratio = (bandwidth_mod < 1.0) ? bandwidth_mod : 1.0;
/* The Lanczos presets decimate through Normal's filter. */
if (window_type == SINC_WINDOW_KAISER)
{
dec_cutoff = cutoff;
dec_beta = re->kaiser_beta;
dec_taps = 4 * sidelobes;
}
else
{
dec_cutoff = 0.825;
dec_beta = 5.5f;
dec_taps = 32;
}
dec_taps = ((unsigned)ceil(dec_taps / SINC_DEC_ENGAGE) + 7) & ~7;
/* Downsampling, must lower cutoff, and extend number of
* taps accordingly to keep same stopband attenuation. */
@@ -929,17 +1262,35 @@ void *sinc_resampler_init_hq(double bandwidth_mod,
phase_elems = ((1 << re->phase_bits) * re->taps);
if (window_type == SINC_WINDOW_KAISER)
phase_elems = phase_elems * 2;
elems = phase_elems + 4 * re->taps;
elems = sinc_state_elems(re->taps, dec_taps);
re->main_buffer = (float*)memalign_alloc(128, sizeof(float) * elems);
if (!re->main_buffer)
re->tables = (sinc_tables_t*)memalign_alloc(128, sizeof(float)
* (SINC_TABLES_OFFSET + phase_elems + dec_taps));
re->dec = (rarch_sinc_resampler_t*)
calloc(SINC_DEC_STAGES + 1, sizeof(*re->dec));
if (!re->main_buffer || !re->tables || !re->dec)
goto error;
memset(re->main_buffer, 0, sizeof(float) * elems);
retro_atomic_int_init(&re->tables->refs, 1);
re->phase_table = (float*)re->tables + SINC_TABLES_OFFSET;
re->phase_table = re->main_buffer;
re->buffer_l = re->main_buffer + phase_elems;
re->buffer_r = re->buffer_l + 2 * re->taps;
/* Each stage is the preset's table for a ratio of SINC_DEC_ENGAGE / 2,
* at the single phase an exact halving visits. */
for (s = 0; s < SINC_DEC_STAGES; s++)
{
rarch_sinc_resampler_t *stage = &re->dec[s];
stage->phase_table = re->phase_table + phase_elems;
stage->taps = dec_taps;
stage->subphase_bits = 1;
stage->subphase_mask = 1;
stage->subphase_mod = 0.5f;
stage->kaiser_beta = dec_beta;
stage->process = sinc_select_process(0, mask, enable_avx);
}
sinc_init_table_kaiser(&re->dec[0], 0.5 * SINC_DEC_ENGAGE * dec_cutoff,
re->dec[0].phase_table, 1, dec_taps, 0);
switch (window_type)
{
@@ -955,44 +1306,17 @@ void *sinc_resampler_init_hq(double bandwidth_mod,
goto error;
}
re->process = resampler_sinc_process_c;
if (window_type == SINC_WINDOW_KAISER)
re->process = resampler_sinc_process_c_kaiser;
if (mask & RESAMPLER_SIMD_AVX && enable_avx)
{
#if defined(__AVX__)
re->process = resampler_sinc_process_avx;
if (window_type == SINC_WINDOW_KAISER)
re->process = resampler_sinc_process_avx_kaiser;
#endif
}
else if (mask & RESAMPLER_SIMD_SSE)
{
#if defined(__SSE__)
re->process = resampler_sinc_process_sse;
if (window_type == SINC_WINDOW_KAISER)
re->process = resampler_sinc_process_sse_kaiser;
#endif
}
else if (mask & RESAMPLER_SIMD_NEON)
{
#if (defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(HAVE_NEON))
#ifdef HAVE_ARM_NEON_ASM_OPTIMIZATIONS
if (window_type != SINC_WINDOW_KAISER)
re->process = resampler_sinc_process_neon;
#else
re->process = resampler_sinc_process_neon;
if (window_type == SINC_WINDOW_KAISER)
re->process = resampler_sinc_process_neon_kaiser;
#endif
#endif
}
re->process = sinc_select_process(window_type == SINC_WINDOW_KAISER,
mask, enable_avx);
sinc_bind_state(re);
return re;
error:
resampler_sinc_free(re);
memalign_free(re->main_buffer);
memalign_free(re->tables);
free(re->dec);
free(re);
return NULL;
}
@@ -1021,7 +1345,11 @@ static void resampler_sinc_process(void *re_, struct resampler_data *data)
data->output_frames = 0;
return;
}
re->process(re_, data);
if ( !re->dec_stages && !re->fade_left
&& data->ratio >= re->design_ratio * SINC_DEC_ENGAGE_FIRST)
re->process(re_, data);
else
resampler_sinc_process_decimated(re, data);
}
retro_resampler_t sinc_resampler = {
@@ -1032,7 +1360,8 @@ retro_resampler_t sinc_resampler = {
"sinc",
"sinc",
resampler_sinc_reset,
RESAMPLER_CAP_QUALITY | RESAMPLER_CAP_HQ_OVERSAMPLE
RESAMPLER_CAP_QUALITY | RESAMPLER_CAP_HQ_OVERSAMPLE,
resampler_sinc_sibling
};
#if defined(__GNUC__) && defined(__OPTIMIZE__) && !defined(__clang__)
+360 -24
View File
@@ -51,6 +51,7 @@
#include <string.h>
#include <memalign.h>
#include <math.h>
#include <retro_atomic.h>
#include "sinc_resampler_internal.h"
@@ -90,17 +91,43 @@
#define SINC_I16_PREFETCH(addr) ((void)0)
#endif
/* Decimation by two ahead of the kernel when fast-forward takes the live
* ratio below what the table rejects images for, as in the float driver:
* each stage is the preset's table for a ratio of SINC_I16_DEC_ENGAGE / 2
* at its single phase, with the same thresholds and crossfade. Decisions
* run only when the ratio's bit pattern changes, so a stable ratio stays
* integer-only. */
#define SINC_I16_DEC_STAGES 4
#define SINC_I16_DEC_BLOCK 256
#define SINC_I16_DEC_HALF (2 * (SINC_I16_DEC_BLOCK / 2 + 1))
#define SINC_I16_DEC_ENGAGE_FIRST 0.97
#define SINC_I16_DEC_ENGAGE 0.9
#define SINC_I16_DEC_RELEASE_LAST 0.975
#define SINC_I16_DEC_RELEASE 1.25
#define SINC_I16_FADE_FRAMES 256
enum sinc_i16_window
{
SINC_I16_WINDOW_LANCZOS = 0,
SINC_I16_WINDOW_KAISER
};
/* Leads the block that holds the immutable tables, which every sibling
* of an instance reads; the last one out frees it. */
typedef struct sinc_i16_tables
{
retro_atomic_int_t refs;
} sinc_i16_tables_t;
#define SINC_I16_TABLES_OFFSET 128
#define SINC_I16_ALIGN64(n) (((n) + 63) & ~(size_t)63)
typedef struct rarch_sinc_resampler_int16
{
/* buffer_l owns one block that also holds the table and, on NEON,
* the coefficient scratch; each region starts on a 64-byte boundary
* from the rings' start and the others are views. */
/* buffer_l owns the stream state - the rings, on NEON the coefficient
* scratch, the stages' rings, the shadow's and the blocks - each on a
* 64-byte boundary; tables owns the shared coefficients. */
sinc_i16_tables_t *tables;
int32_t *phase_table; /* Q1.30 coefficients (+ interleaved deltas for Kaiser) */
int16_t *buffer_l; /* 2 * taps int16 ring (doubled to stay contiguous) */
int16_t *buffer_r;
@@ -116,7 +143,17 @@ typedef struct rarch_sinc_resampler_int16
uint32_t time;
uint32_t ratio_fixed; /* cached (uint32)(phases / ratio); recomputed only */
uint64_t ratio_bits; /* when the double ratio's bit pattern changes */
uint64_t dec_bits; /* ratio the stage decision was last taken for */
double kaiser_beta;
double design_ratio;
/* SINC_I16_DEC_STAGES one-phase stages, then the kernel's shadow. */
struct rarch_sinc_resampler_int16 *dec;
int16_t *dec_block;
int16_t *fade_block;
unsigned dec_stages;
unsigned fade_stages;
unsigned fade_left;
unsigned fade_len;
} rarch_sinc_resampler_int16_t;
/* ------------------------------------------------------------------------- */
@@ -450,8 +487,167 @@ static void sinc_i16_process_lanczos(rarch_sinc_resampler_int16_t *re,
data->output_frames = out_frames;
}
static void sinc_i16_kernel(rarch_sinc_resampler_int16_t *re,
struct resampler_data_int16 *data)
{
if (re->window == SINC_I16_WINDOW_KAISER)
sinc_i16_process_kaiser(re, data);
else
sinc_i16_process_lanczos(re, data);
}
static size_t sinc_i16_dec_run(rarch_sinc_resampler_int16_t *kernel,
const int16_t *in, size_t frames, int16_t *out, double ratio)
{
struct resampler_data_int16 chunk;
chunk.data_in = in;
chunk.input_frames = frames;
chunk.data_out = out;
chunk.output_frames = 0;
chunk.ratio = ratio;
sinc_i16_kernel(kernel, &chunk);
return chunk.output_frames;
}
/* Runs stages [first, last) from in; returns where the output is. */
static const int16_t *sinc_i16_dec_chain(rarch_sinc_resampler_int16_t *re,
unsigned first, unsigned last, const int16_t *in, size_t *frames)
{
int16_t *out = (in == re->dec_block) ? re->dec_block + SINC_I16_DEC_HALF
: re->dec_block;
for (; first < last; first++)
{
*frames = sinc_i16_dec_run(&re->dec[first], in, *frames, out, 0.5);
in = out;
out = (out == re->dec_block)
? re->dec_block + SINC_I16_DEC_HALF : re->dec_block;
}
return in;
}
/* Clean rings, and a clock that takes input before it emits: a joining
* path then produces no output ahead of the frames it was given. */
static void sinc_i16_dec_restart(rarch_sinc_resampler_int16_t *stage)
{
memset(stage->buffer_l, 0, 4 * stage->taps * sizeof(*stage->buffer_l));
stage->ptr = 0;
stage->time = 1u << (stage->phase_bits + stage->subphase_bits);
}
/* The kernel's rings move to the shadow, which carries on with the old
* stages; the kernel and any stage joining start clean. */
static void sinc_i16_dec_switch(rarch_sinc_resampler_int16_t *re,
unsigned stages, double ratio)
{
unsigned s;
double warm = re->taps * ratio * (double)(1u << stages);
rarch_sinc_resampler_int16_t *shadow = &re->dec[SINC_I16_DEC_STAGES];
memcpy(shadow->buffer_l, re->buffer_l,
4 * re->taps * sizeof(*re->buffer_l));
shadow->ptr = re->ptr;
shadow->time = re->time;
sinc_i16_dec_restart(re);
for (s = re->dec_stages; s < stages; s++)
{
sinc_i16_dec_restart(&re->dec[s]);
warm += re->dec[s].taps * ratio * (double)(1u << s);
}
if (warm > 8192.0)
warm = 8192.0;
re->fade_len = (warm < 256.0) ? 256 : (unsigned)warm + 1;
re->fade_left = (unsigned)warm + 1 + re->fade_len;
re->fade_stages = re->dec_stages;
re->dec_stages = stages;
}
static int16_t sinc_i16_mix(int16_t old, int16_t now, int32_t w)
{
int32_t prod = ((int32_t)now - old) * w;
if (prod >= 0)
return (int16_t)(old + ((prod + 16384) >> 15));
return (int16_t)(old - (((-prod) + 16384) >> 15));
}
static void sinc_i16_dec_blend(rarch_sinc_resampler_int16_t *re,
int16_t *out, size_t frames, const int16_t *old, size_t old_frames)
{
size_t i;
if (!old_frames)
return;
for (i = 0; i < frames && re->fade_left; i++, re->fade_left--)
{
size_t o = (i < old_frames) ? i : old_frames - 1;
int32_t w = (re->fade_left > re->fade_len) ? 0
: (int32_t)(((uint32_t)(re->fade_len - re->fade_left) << 15)
/ re->fade_len);
out[2 * i] = sinc_i16_mix(old[2 * o], out[2 * i], w);
out[2 * i + 1] = sinc_i16_mix(old[2 * o + 1], out[2 * i + 1], w);
}
}
static void sinc_i16_process_decimated(rarch_sinc_resampler_int16_t *re,
struct resampler_data_int16 *data)
{
const int16_t *input = data->data_in;
int16_t *output = data->data_out;
size_t frames = data->input_frames;
size_t produced = 0;
size_t step = SINC_I16_DEC_BLOCK;
double ratio = data->ratio;
if (re->fade_left)
while (step > 1 && step * ratio + 4.0 > SINC_I16_FADE_FRAMES)
step >>= 1;
while (frames)
{
size_t n = (frames < step) ? frames : step;
size_t got;
unsigned now_k = re->dec_stages;
int16_t *out = output + 2 * produced;
const int16_t *p = input;
input += 2 * n;
frames -= n;
if (!re->fade_left)
{
p = sinc_i16_dec_chain(re, 0, now_k, p, &n);
got = sinc_i16_dec_run(re, p, n, out, ratio * (double)(1u << now_k));
}
else
{
rarch_sinc_resampler_int16_t *shadow = &re->dec[SINC_I16_DEC_STAGES];
unsigned old_k = re->fade_stages;
unsigned common = (now_k < old_k) ? now_k : old_k;
size_t old;
p = sinc_i16_dec_chain(re, 0, common, p, &n);
/* The path without stages of its own reads p before the other
* path's stages reuse its block. */
if (now_k == common)
{
got = sinc_i16_dec_run(re, p, n, out,
ratio * (double)(1u << now_k));
p = sinc_i16_dec_chain(re, common, old_k, p, &n);
old = sinc_i16_dec_run(shadow, p, n, re->fade_block,
ratio * (double)(1u << old_k));
}
else
{
old = sinc_i16_dec_run(shadow, p, n, re->fade_block,
ratio * (double)(1u << old_k));
p = sinc_i16_dec_chain(re, common, now_k, p, &n);
got = sinc_i16_dec_run(re, p, n, out,
ratio * (double)(1u << now_k));
}
sinc_i16_dec_blend(re, out, got, re->fade_block, old);
}
produced += got;
}
data->output_frames = produced;
}
void sinc_resampler_int16_process(void *re_, struct resampler_data_int16 *data)
{
uint64_t bits;
rarch_sinc_resampler_int16_t *re = (rarch_sinc_resampler_int16_t*)re_;
if (!sinc_resampler_ratio_valid(data->ratio,
re->phase_bits, re->subphase_bits))
@@ -459,10 +655,28 @@ void sinc_resampler_int16_process(void *re_, struct resampler_data_int16 *data)
data->output_frames = 0;
return;
}
if (re->window == SINC_I16_WINDOW_KAISER)
sinc_i16_process_kaiser(re, data);
memcpy(&bits, &data->ratio, sizeof(bits));
if (bits != re->dec_bits && !re->fade_left)
{
unsigned stages = re->dec_stages;
double ratio = data->ratio;
while ( stages < SINC_I16_DEC_STAGES
&& ratio * (double)(1u << stages)
< re->design_ratio
* (stages ? SINC_I16_DEC_ENGAGE : SINC_I16_DEC_ENGAGE_FIRST))
stages++;
while (stages && ratio * (double)(1u << (stages - 1))
>= re->design_ratio * ((stages == 1)
? SINC_I16_DEC_RELEASE_LAST : SINC_I16_DEC_RELEASE))
stages--;
if (stages != re->dec_stages)
sinc_i16_dec_switch(re, stages, ratio);
re->dec_bits = bits;
}
if (!re->dec_stages && !re->fade_left)
sinc_i16_kernel(re, data);
else
sinc_i16_process_lanczos(re, data);
sinc_i16_process_decimated(re, data);
}
/* ------------------------------------------------------------------------- */
@@ -479,6 +693,9 @@ void sinc_resampler_int16_reset(void *re_)
re->time = 0;
re->ratio_fixed = 0;
re->ratio_bits = 0;
re->dec_bits = 0;
re->dec_stages = 0;
re->fade_left = 0;
}
void sinc_resampler_int16_free(void *re_)
@@ -487,10 +704,94 @@ void sinc_resampler_int16_free(void *re_)
if (re)
{
memalign_free(re->buffer_l);
free(re->dec);
if ( re->tables
&& retro_atomic_fetch_sub_int(&re->tables->refs, 1) == 1)
memalign_free(re->tables);
}
free(re);
}
/* The state block's size; with bind, lays this instance's views over
* buffer_l. */
static size_t sinc_i16_state(rarch_sinc_resampler_int16_t *re,
unsigned dec_taps, int bind)
{
unsigned s;
size_t o_dec, o_shadow, o_fade;
size_t o_coef = SINC_I16_ALIGN64(sizeof(int16_t) * 4 * re->taps);
o_dec = o_coef;
#ifdef SINC_I16_KAISER_FISSION
o_dec += SINC_I16_ALIGN64(sizeof(int32_t) * (re->taps + 4u));
#endif
o_shadow = o_dec + SINC_I16_ALIGN64(sizeof(int16_t) * 4 * dec_taps
* SINC_I16_DEC_STAGES);
o_fade = o_shadow + SINC_I16_ALIGN64(sizeof(int16_t) * 4 * re->taps);
if (bind)
{
uint8_t *base = (uint8_t*)re->buffer_l;
re->buffer_r = re->buffer_l + 2 * re->taps;
#ifdef SINC_I16_KAISER_FISSION
re->coef_scratch = (int32_t*)(base + o_coef);
#endif
for (s = 0; s < SINC_I16_DEC_STAGES; s++)
{
re->dec[s].buffer_l = (int16_t*)(base + o_dec) + 4 * dec_taps * s;
re->dec[s].buffer_r = re->dec[s].buffer_l + 2 * dec_taps;
}
re->fade_block = (int16_t*)(base + o_fade);
re->dec_block = re->fade_block + 2 * SINC_I16_FADE_FRAMES;
re->dec[SINC_I16_DEC_STAGES] = *re;
re->dec[SINC_I16_DEC_STAGES].dec = NULL;
re->dec[SINC_I16_DEC_STAGES].buffer_l = (int16_t*)(base + o_shadow);
re->dec[SINC_I16_DEC_STAGES].buffer_r =
re->dec[SINC_I16_DEC_STAGES].buffer_l + 2 * re->taps;
}
return o_fade + sizeof(int16_t)
* (2 * SINC_I16_FADE_FRAMES + 2 * SINC_I16_DEC_HALF);
}
void *sinc_resampler_int16_sibling(void *re_)
{
unsigned s;
size_t len;
rarch_sinc_resampler_int16_t *src = (rarch_sinc_resampler_int16_t*)re_;
rarch_sinc_resampler_int16_t *re;
if (!src || !(re = (rarch_sinc_resampler_int16_t*)calloc(1, sizeof(*re))))
return NULL;
*re = *src;
len = sinc_i16_state(src, src->dec[0].taps, 0);
re->buffer_l = (int16_t*)memalign_alloc(128, len);
re->dec = (rarch_sinc_resampler_int16_t*)
calloc(SINC_I16_DEC_STAGES + 1, sizeof(*re->dec));
if (!re->buffer_l || !re->dec)
{
memalign_free(re->buffer_l);
free(re->dec);
free(re);
return NULL;
}
memset(re->buffer_l, 0, sizeof(int16_t) * 4 * re->taps);
for (s = 0; s < SINC_I16_DEC_STAGES; s++)
{
re->dec[s] = src->dec[s];
re->dec[s].ptr = 0;
re->dec[s].time = 0;
re->dec[s].ratio_fixed = 0;
re->dec[s].ratio_bits = 0;
}
re->ptr = 0;
re->time = 0;
re->ratio_fixed = 0;
re->ratio_bits = 0;
re->dec_bits = 0;
re->dec_stages = 0;
re->fade_left = 0;
sinc_i16_state(re, re->dec[0].taps, 1);
retro_atomic_inc_int(&re->tables->refs);
return re;
}
void *sinc_resampler_int16_init_hq(double bandwidth_mod,
enum sinc_int16_quality quality, int hq_oversampling)
{
@@ -498,8 +799,10 @@ void *sinc_resampler_int16_init_hq(double bandwidth_mod,
unsigned sidelobes = 0;
int window = SINC_I16_WINDOW_LANCZOS;
int stride;
size_t phase_elems, o_table, o_coef, len;
size_t phase_elems, o_dec_table;
int phases;
unsigned s, dec_taps;
double dec_cutoff, dec_beta;
rarch_sinc_resampler_int16_t *re =
(rarch_sinc_resampler_int16_t*)calloc(1, sizeof(*re));
@@ -566,6 +869,21 @@ void *sinc_resampler_int16_init_hq(double bandwidth_mod,
re->window = (unsigned)window;
re->subphase_mask = (1u << re->subphase_bits) - 1u;
re->taps = sidelobes * 2;
re->design_ratio = (bandwidth_mod < 1.0) ? bandwidth_mod : 1.0;
/* The Lanczos presets decimate through Normal's filter. */
if (window == SINC_I16_WINDOW_KAISER)
{
dec_cutoff = cutoff;
dec_beta = re->kaiser_beta;
dec_taps = 4 * sidelobes;
}
else
{
dec_cutoff = 0.825;
dec_beta = 5.5;
dec_taps = 32;
}
dec_taps = ((unsigned)ceil(dec_taps / SINC_I16_DEC_ENGAGE) + 7u) & ~7u;
/* Downsampling: lower cutoff and extend taps to hold stopband. */
if (bandwidth_mod < 1.0)
@@ -583,24 +901,23 @@ void *sinc_resampler_int16_init_hq(double bandwidth_mod,
phases = 1 << re->phase_bits;
phase_elems = (size_t)phases * re->taps * stride;
/* The two rings first, then the table, then (NEON) the coefficient
* scratch, out of one block; the rings start zeroed as before. The
* block is owned through buffer_l. */
o_table = (sizeof(int16_t) * 4 * re->taps + 63) & ~(size_t)63;
o_coef = o_table + ((sizeof(int32_t) * phase_elems + 63) & ~(size_t)63);
len = o_coef;
#ifdef SINC_I16_KAISER_FISSION
len += sizeof(int32_t) * (re->taps + 4u);
#endif
re->buffer_l = (int16_t*)memalign_alloc(128, len);
if (!re->buffer_l)
re->dec = (rarch_sinc_resampler_int16_t*)
calloc(SINC_I16_DEC_STAGES + 1, sizeof(*re->dec));
if (!re->dec)
goto error;
re->dec[0].taps = dec_taps;
o_dec_table = SINC_I16_TABLES_OFFSET
+ SINC_I16_ALIGN64(sizeof(int32_t) * phase_elems);
re->buffer_l = (int16_t*)memalign_alloc(128,
sinc_i16_state(re, dec_taps, 0));
re->tables = (sinc_i16_tables_t*)memalign_alloc(128,
o_dec_table + sizeof(int32_t) * 2 * dec_taps);
if (!re->buffer_l || !re->tables)
goto error;
memset(re->buffer_l, 0, sizeof(int16_t) * 4 * re->taps);
re->buffer_r = re->buffer_l + 2 * re->taps;
re->phase_table = (int32_t*)((uint8_t*)re->buffer_l + o_table);
#ifdef SINC_I16_KAISER_FISSION
re->coef_scratch = (int32_t*)((uint8_t*)re->buffer_l + o_coef);
#endif
retro_atomic_int_init(&re->tables->refs, 1);
re->phase_table = (int32_t*)((uint8_t*)re->tables
+ SINC_I16_TABLES_OFFSET);
if (window == SINC_I16_WINDOW_KAISER)
sinc_i16_init_table_kaiser(re, cutoff, re->phase_table,
@@ -609,10 +926,29 @@ void *sinc_resampler_int16_init_hq(double bandwidth_mod,
sinc_i16_init_table_lanczos(re, cutoff, re->phase_table,
phases, (int)re->taps);
/* The Kaiser builder's delta row follows the one the stages read. */
for (s = 0; s < SINC_I16_DEC_STAGES; s++)
{
rarch_sinc_resampler_int16_t *stage = &re->dec[s];
stage->phase_table = (int32_t*)((uint8_t*)re->tables + o_dec_table);
stage->taps = dec_taps;
stage->subphase_bits = 1;
stage->subphase_mask = 1;
stage->window = SINC_I16_WINDOW_LANCZOS;
stage->kaiser_beta = dec_beta;
}
sinc_i16_init_table_kaiser(&re->dec[0],
0.5 * SINC_I16_DEC_ENGAGE * dec_cutoff,
re->dec[0].phase_table, 1, (int)dec_taps);
sinc_i16_state(re, dec_taps, 1);
return re;
error:
sinc_resampler_int16_free(re);
memalign_free(re->buffer_l);
memalign_free(re->tables);
free(re->dec);
free(re);
return NULL;
}
@@ -71,6 +71,61 @@ _process_sinc_neon_asm:
pop {r4, pc}
.align 4
.globl process_sinc_neon_kaiser_asm
#ifndef __MACH__
.type process_sinc_neon_kaiser_asm, %function
#endif
.globl _process_sinc_neon_kaiser_asm
#ifndef __MACH__
.type _process_sinc_neon_kaiser_asm, %function
#endif
# void process_sinc_neon_kaiser(float *out, const float *left, const float *right, const float *coeff, unsigned taps, const float *frac)
# The delta row follows the taps coefficients; each tap is coeff + delta * frac.
# The fraction comes by pointer so hard- and soft-float ABIs agree.
# Assumes taps is >= 8, and a multiple of 8.
process_sinc_neon_kaiser_asm:
_process_sinc_neon_kaiser_asm:
push {r4, r5, r6, lr}
vmov.f32 q0, #0.0
vmov.f32 q8, #0.0
# Taps and the fraction pointer go on stack in armeabi.
ldr r4, [sp, #16]
ldr r5, [sp, #20]
vld1.32 {d2[], d3[]}, [r5]
add r5, r3, r4, lsl #2
1:
# Left
vld1.f32 {q2-q3}, [r1]!
# Right
vld1.f32 {q10-q11}, [r2]!
# Coeff, delta
vld1.f32 {q12-q13}, [r3, :128]!
vld1.f32 {q14-q15}, [r5, :128]!
vmla.f32 q12, q14, q1
vmla.f32 q13, q15, q1
# Left / Right
vmla.f32 q0, q2, q12
vmla.f32 q8, q10, q12
vmla.f32 q0, q3, q13
vmla.f32 q8, q11, q13
subs r4, r4, #8
bne 1b
# Add everything together
vadd.f32 d0, d0, d1
vadd.f32 d16, d16, d17
vpadd.f32 d0, d0, d16
vst1.f32 d0, [r0]
pop {r4, r5, r6, pc}
#endif
/* Prevent the stack from becoming executable for no reason...
+1 -1
View File
@@ -1362,7 +1362,7 @@ static int cdrom_read_track_info(libretro_vfs_implementation_file *stream, unsig
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 = (track_size - pregap_lba_len) * 2352;
toc->track[track - 1].track_bytes = (unsigned)((track_size - pregap_lba_len) * 2352);
toc->track[track - 1].mode = buf[6] & 0xF;
#ifdef CDROM_DEBUG
+17 -29
View File
@@ -2254,42 +2254,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)
+2
View File
@@ -929,7 +929,9 @@ static size_t cpu_features_processor_order_masked(
size_t cpu_features_get_processor_order(unsigned *s, size_t len)
{
#if defined(__linux__) || (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__))
unsigned char allowed[CPU_CLASS_MAX_IDS];
#endif
const unsigned char *mask = NULL;
#if defined(__linux__)
{
+15 -1
View File
@@ -883,9 +883,18 @@ const struct file_archive_file_backend *file_archive_get_zstd_file_backend(void)
#endif
}
const struct file_archive_file_backend *file_archive_get_rar_file_backend(void)
{
#ifdef HAVE_RRAR
return &rar_backend;
#else
return NULL;
#endif
}
const struct file_archive_file_backend* file_archive_get_file_backend(const char *path)
{
#if defined(HAVE_7ZIP) || defined(HAVE_ZLIB) || defined(HAVE_RZSTD) || defined(HAVE_COMPRESSION)
#if defined(HAVE_7ZIP) || defined(HAVE_ZLIB) || defined(HAVE_RZSTD) || defined(HAVE_RRAR) || defined(HAVE_COMPRESSION)
char newpath[PATH_MAX_LENGTH];
const char *file_ext = NULL;
char *last = NULL;
@@ -915,6 +924,11 @@ const struct file_archive_file_backend* file_archive_get_file_backend(const char
if (string_is_equal_noncase(file_ext, "zst"))
return &zstd_backend;
#endif
#ifdef HAVE_RRAR
if (string_is_equal_noncase(file_ext, "rar"))
return &rar_backend;
#endif
#endif
return NULL;
+394
View File
@@ -0,0 +1,394 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (archive_file_rar.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <stdlib.h>
#include <string.h>
#include <boolean.h>
#include <file/archive_file.h>
#include <streams/file_stream.h>
#include <retro_miscellaneous.h>
#include <encodings/crc32.h>
#include <string/stdstring.h>
#include <compat/strl.h>
#include <rar/rrar_archive.h>
/* The first six bytes of both containers: RAR 1.5 to 4.x carries 0x00
* after them, RAR 5 0x01 0x00. rrar_archive_open() tells them apart. */
#define RAR_MAGIC "Rar!\x1A\x07"
#define RAR_MAGIC_LEN 6
/* rrar_archive parses from a buffer rather than a stream.
*
* archive_file.c has already opened the file and, for archives up to
* 256 MiB, mapped it before this backend is entered. Where that
* mapping exists it is used directly, as the zip, 7z and zstd backends
* do; only when there is none does this read the file itself, and
* owns_data records which of the two happened. */
struct rar_context_t
{
rrar_archive_t *archive;
const uint8_t *data;
size_t data_len;
/* The last extracted member, owned here so the handle handed to the
* caller stays valid until the next call. */
uint8_t *output;
uint32_t parse_index;
uint32_t decompress_index;
int owns_data;
};
/* Read a whole file into memory, for the no-mapping case only. */
static uint8_t *rar_slurp(const char *path, size_t *out_len)
{
RFILE *f;
int64_t len;
uint8_t *buf;
if (string_is_empty(path))
return NULL;
if (!(f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
return NULL;
filestream_seek(f, 0, SEEK_END);
len = filestream_tell(f);
filestream_seek(f, 0, SEEK_SET);
if ( len <= 0
|| (uint64_t)len > (uint64_t)((size_t)-1)
|| !(buf = (uint8_t *)malloc((size_t)len)))
{
filestream_close(f);
return NULL;
}
if (filestream_read(f, buf, (int64_t)len) != len)
{
free(buf);
filestream_close(f);
return NULL;
}
filestream_close(f);
*out_len = (size_t)len;
return buf;
}
static int rar_bind_data(struct rar_context_t *ctx,
file_archive_transfer_t *state, const char *file)
{
#ifdef VFS_HAVE_FILE_MAPPING
if (state->archive_mmap_data && state->archive_size > 0)
{
ctx->data = state->archive_mmap_data;
ctx->data_len = (size_t)state->archive_size;
ctx->owns_data = 0;
return 1;
}
#else
(void)state;
#endif
if (!(ctx->data = rar_slurp(file, &ctx->data_len)))
return 0;
ctx->owns_data = 1;
return 1;
}
static void rar_parse_file_free(void *context)
{
struct rar_context_t *ctx = (struct rar_context_t*)context;
if (!ctx)
return;
free(ctx->output);
rrar_archive_close(ctx->archive);
/* The mapping belongs to archive_file.c and is unmapped there. */
if (ctx->owns_data)
free((void*)ctx->data);
free(ctx);
}
/* State for the compressed_file_read driver below. */
typedef struct
{
char *opt_file;
char *needle;
void **buf;
size_t size;
bool found;
} rar_decomp_state_t;
/* Called once per entry while the driver walks the archive: takes the
* wanted member and stops the scan. */
static int rar_file_decompressed(
const char *name, const char *valid_exts,
const uint8_t *cdata, unsigned cmode,
uint32_t csize, uint32_t size,
uint32_t crc32, struct archive_extract_userdata *userdata)
{
rar_decomp_state_t *st = (rar_decomp_state_t*)userdata->cb_data;
if (!name || !*name || !st || !st->needle)
return 1;
if (!string_is_equal(name, st->needle))
return 1;
if (st->opt_file)
{
if (file_archive_perform_mode_start(st->opt_file, valid_exts,
cdata, cmode, csize, size, crc32, userdata) == -1)
return 1;
st->size = size;
st->found = true;
return 0;
}
{
struct rar_context_t *ctx =
(struct rar_context_t*)userdata->transfer->context;
uint8_t *member = NULL;
size_t member_len = 0;
if ( !ctx || !ctx->archive
|| rrar_archive_extract(ctx->archive, (uint32_t)(size_t)cdata,
&member, &member_len) != RRAR_OK)
return 1;
/* RetroArch expects a NUL after the data. */
if ((*st->buf = malloc(member_len + 1)))
{
((char*)(*st->buf))[member_len] = '\0';
if (member_len)
memcpy(*st->buf, member, member_len);
st->size = member_len;
st->found = true;
}
free(member);
}
return 0;
}
/* Extract the relative path (needle) from a RAR archive (path) into an
* allocated buffer, or to optional_outfile when that is set. Driven
* through file_archive_parse_file_iterate(), as the zip and 7z
* backends are. */
static int64_t rar_file_read(
const char *path,
const char *needle, void **buf,
const char *optional_outfile)
{
file_archive_transfer_t state;
rar_decomp_state_t decomp;
struct archive_extract_userdata userdata;
int ret = 0;
memset(&state, 0, sizeof(state));
memset(&decomp, 0, sizeof(decomp));
memset(&userdata, 0, sizeof(userdata));
if (needle)
decomp.needle = strdup(needle);
if (optional_outfile)
decomp.opt_file = strdup(optional_outfile);
if ( (needle && !decomp.needle)
|| (optional_outfile && !decomp.opt_file))
{
free(decomp.needle);
free(decomp.opt_file);
return -1;
}
state.type = ARCHIVE_TRANSFER_INIT;
userdata.transfer = &state;
userdata.cb_data = &decomp;
decomp.buf = buf;
do
{
bool returnerr = true;
ret = file_archive_parse_file_iterate(&state, &returnerr, path,
"", rar_file_decompressed, &userdata);
if (!returnerr)
break;
} while (ret == 0 && (!decomp.found || state.pending_active));
file_archive_parse_file_iterate_stop(&state);
free(decomp.opt_file);
free(decomp.needle);
if (!decomp.found)
return -1;
return (int64_t)decomp.size;
}
static bool rar_stream_decompress_data_to_file_init(
void *context, file_archive_file_handle_t *handle,
const uint8_t *cdata, unsigned cmode, uint32_t csize, uint32_t size)
{
struct rar_context_t *ctx = (struct rar_context_t*)context;
(void)handle;
(void)cmode;
(void)csize;
(void)size;
if (!ctx)
return false;
/* The parse step hands the entry index through cdata. */
ctx->decompress_index = (uint32_t)(size_t)cdata;
return true;
}
/* 1 when the member is in handle->data, -1 on failure: the member is
* unpacked whole, so there is no "call again". */
static int rar_stream_decompress_data_to_file_iterate(
void *context, file_archive_file_handle_t *handle)
{
struct rar_context_t *ctx = (struct rar_context_t*)context;
uint8_t *member = NULL;
size_t member_len = 0;
if ( !ctx || !ctx->archive
|| rrar_archive_extract(ctx->archive, ctx->decompress_index,
&member, &member_len) != RRAR_OK)
return -1;
/* The caller reads through handle->data until it calls back in, so
* the previous member cannot be released any earlier than this. */
free(ctx->output);
ctx->output = member;
if (handle)
handle->data = ctx->output;
return 1;
}
static int rar_parse_file_init(file_archive_transfer_t *state,
const char *file)
{
uint8_t magic_buf[RAR_MAGIC_LEN];
struct rar_context_t *ctx = NULL;
if (state->archive_size < RRAR_SIGNATURE_SIZE)
goto error;
filestream_seek(state->archive_file, 0, SEEK_SET);
if (filestream_read(state->archive_file, magic_buf, RAR_MAGIC_LEN)
!= RAR_MAGIC_LEN)
goto error;
if (memcmp(magic_buf, RAR_MAGIC, RAR_MAGIC_LEN) != 0)
goto error;
if (!(ctx = (struct rar_context_t*)calloc(1, sizeof(*ctx))))
goto error;
state->context = ctx;
if (!rar_bind_data(ctx, state, file))
goto error;
if (rrar_archive_open(&ctx->archive, ctx->data, ctx->data_len)
!= RRAR_OK)
goto error;
state->step_total = rrar_archive_num_entries(ctx->archive);
return 0;
error:
if (ctx)
rar_parse_file_free(ctx);
state->context = NULL;
return -1;
}
static int rar_parse_file_iterate_step(void *context,
const char *valid_exts,
struct archive_extract_userdata *userdata,
file_archive_file_cb file_cb)
{
const rrar_entry_t *entry;
struct rar_context_t *ctx = (struct rar_context_t*)context;
userdata->current_file_path[0] = '\0';
if (!ctx || !ctx->archive)
return -1;
if (ctx->parse_index >= rrar_archive_num_entries(ctx->archive))
return 0;
entry = rrar_archive_entry(ctx->archive, ctx->parse_index);
/* Directories, members this reader cannot unpack (solid, encrypted,
* an older packer) and members of 4 GiB or more, which the uint32_t
* sizes here cannot describe, are passed over: the rest of the
* archive stays usable. */
if ( entry
&& !entry->is_dir
&& entry->supported
&& entry->size <= (uint64_t)0xFFFFFFFFu)
{
uint32_t size = (uint32_t)entry->size;
strlcpy(userdata->current_file_path, entry->name,
sizeof(userdata->current_file_path));
userdata->crc = entry->crc;
userdata->size = size;
/* The entry index travels in cdata; the packed size is reported
* only for progress. */
if (file_cb && !file_cb(userdata->current_file_path, valid_exts,
(const uint8_t*)(size_t)ctx->parse_index, 0,
entry->packed_size > (uint64_t)0xFFFFFFFFu
? 0xFFFFFFFFu : (uint32_t)entry->packed_size,
size, entry->crc, userdata))
return 0;
}
ctx->parse_index++;
return 1;
}
const struct file_archive_file_backend rar_backend = {
rar_parse_file_init,
rar_parse_file_iterate_step,
rar_parse_file_free,
rar_stream_decompress_data_to_file_init,
rar_stream_decompress_data_to_file_iterate,
encoding_crc32,
rar_file_read,
"rar"
};
+118 -3
View File
@@ -457,6 +457,85 @@ static const struct config_entry_list **config_file_sort_entries(
* has no programmatic value. In this case, the comment
* is removed from the end of 'str' and NULL is returned
**/
/* The closing quote of a string literal whose text starts at @s. A
* quote after an odd run of backslashes is escaped (see
* config_file_dump_value). A line with every quote escaped was written
* before values were escaped - a directory ending in a backslash - and
* its first quote closes it, the backslashes kept as they are. NULL
* when there is no quote. *escaped is set when the text up to the
* returned quote has escaping to undo: an escaped quote, or backslashes
* before the closing one. Nearly every value has neither, and costs the
* one strchr and a look at the byte before. */
static char *config_file_literal_end(char *s, bool *escaped)
{
char *first = NULL;
char *q = s;
*escaped = false;
while ((q = strchr(q, '\"')))
{
size_t n = 0;
while (q - n > s && *(q - n - 1) == '\\')
n++;
if (!(n & 1))
{
*escaped = (first != NULL) || n;
return q;
}
if (!first)
first = q;
q++;
}
return first;
}
#if defined(__GNUC__) || defined(__clang__)
#define CONFIG_FILE_NOINLINE __attribute__((noinline))
#elif defined(_MSC_VER)
#define CONFIG_FILE_NOINLINE __declspec(noinline)
#else
#define CONFIG_FILE_NOINLINE
#endif
/* Undoes config_file_dump_value()'s escaping in place over the first
* @len bytes of @s: a run of backslashes before a quote, or at the end,
* is halved, and the quote kept. Returns the new length. */
static size_t config_file_unescape(char *s, size_t len)
{
size_t r = 0;
size_t w = 0;
while (r < len)
{
size_t n = 0;
size_t keep;
while (r + n < len && s[r + n] == '\\')
n++;
if (!n)
{
s[w++] = s[r++];
continue;
}
keep = (r + n == len || s[r + n] == '\"') ? n / 2 : n;
r += n;
while (keep--)
s[w++] = '\\';
}
return w;
}
/* The value of a string literal at @line whose first quote has a
* backslash before it, unescaped in place; its length. Kept out of
* config_file_extract_value(), which nearly every value leaves through
* the one strchr. */
static CONFIG_FILE_NOINLINE size_t config_file_literal_escaped(char *line)
{
bool escaped;
char *end = config_file_literal_end(line, &escaped);
size_t idx = end ? (size_t)(end - line) : strlen(line);
return escaped ? config_file_unescape(line, idx) : idx;
}
static char *config_file_strip_comment(char *str)
{
/* Search for a comment (#) character */
@@ -486,7 +565,9 @@ static char *config_file_strip_comment(char *str)
{
/* Search for the end of the string literal
* value */
char *literal_end = strchr(literal_start + 1, '\"');
bool escaped;
char *literal_end = config_file_literal_end(literal_start + 1,
&escaped);
/* Check whether string literal end occurs
* *after* the comment character
@@ -536,7 +617,12 @@ static char *config_file_extract_value(char *line, unsigned p_opts,
* the value. */
size_t idx;
char *end = strchr(line, '\"');
idx = end ? (size_t)(end - line) : strlen(line);
/* A backslash before it: escaping to undo, or an older
* file's directory (config_file_literal_end) */
if (end && end[-1] == '\\')
idx = config_file_literal_escaped(line);
else
idx = end ? (size_t)(end - line) : strlen(line);
line[idx] = '\0';
if (idx)
@@ -2277,6 +2363,35 @@ static void config_file_dump_put(struct config_file_dump_buf *b,
#define CONFIG_FILE_ENTRY_SERIALISABLE(e) \
(!(e)->readonly && (e)->key && (e)->value)
/* A value goes out as it is, but for its quotes: each is escaped with a
* backslash, and a run of backslashes before one, or at the end of the
* value, is doubled, so the run cannot be read as escaping the quote
* after it (config_file_unescape). Other backslashes - a Windows path -
* stay single. A value without either costs one memchr. */
static void config_file_dump_value(struct config_file_dump_buf *b,
const char *v, size_t len)
{
const char *end = v + len;
const char *q;
size_t tail = 0;
while (tail < len && v[len - 1 - tail] == '\\')
tail++;
while ((q = (const char*)memchr(v, '\"', (size_t)(end - tail - v))))
{
size_t n = 0;
while (q - n > v && *(q - n - 1) == '\\')
n++;
config_file_dump_put(b, v, (size_t)(q - v));
config_file_dump_put(b, q - n, n);
config_file_dump_put(b, "\\\"", STRLEN_CONST("\\\""));
v = q + 1;
}
config_file_dump_put(b, v, (size_t)(end - v));
if (tail)
config_file_dump_put(b, end - tail, tail);
}
static void config_file_dump_entry(struct config_file_dump_buf *b,
const struct config_entry_list *e)
{
@@ -2285,7 +2400,7 @@ static void config_file_dump_entry(struct config_file_dump_buf *b,
config_file_dump_put(b, e->key,
e->key_len ? e->key_len : strlen(e->key));
config_file_dump_put(b, " = \"", STRLEN_CONST(" = \""));
config_file_dump_put(b, e->value,
config_file_dump_value(b, e->value,
e->value_len ? e->value_len : strlen(e->value));
config_file_dump_put(b, "\"\n", STRLEN_CONST("\"\n"));
}
+7
View File
@@ -201,6 +201,9 @@ const char *path_get_archive_delim(const char *path)
/* Check ".apk" */
else if (c3 == 'a' && c2 == 'p' && c1 == 'k')
return delim;
/* Check ".rar" */
else if (c3 == 'r' && c2 == 'a' && c1 == 'r')
return delim;
}
}
}
@@ -297,6 +300,10 @@ bool path_is_compressed_file(const char *path)
return tolower((unsigned char)ext[1]) == 'p'
&& tolower((unsigned char)ext[2]) == 'k'
&& ext[3] == '\0';
case 'r':
return tolower((unsigned char)ext[1]) == 'a'
&& tolower((unsigned char)ext[2]) == 'r'
&& ext[3] == '\0';
case 'z':
switch (tolower((unsigned char)ext[1]))
{
+422 -40
View File
@@ -49,6 +49,7 @@
#include <string.h>
#include <encodings/crc32.h>
#include <retro_atomic.h>
#include <7z/r7z_archive.h>
#include <7z/r7z_lzma.h>
@@ -312,8 +313,11 @@ typedef struct
struct r7z_archive
{
const uint8_t *data;
const uint8_t *data; /* the whole archive, or NULL when it is read */
size_t len;
r7z_read_t read_cb; /* positioned reads of it, when data is NULL */
void *read_ud;
uint8_t *next_buf; /* the header block, when it had to be read in */
/* Decoded header, when the archive used an encoded one. */
uint8_t *header_buf;
@@ -355,6 +359,7 @@ struct r7z_archive
uint32_t pend_folder;
uint32_t pend_coder; /* index within the folder's chain */
uint8_t *pend_in; /* previous stage's output */
int pend_in_borrowed;/* ...or the archive's own bytes: not freed */
size_t pend_in_len;
uint8_t *pend_out; /* buffer being filled */
size_t pend_out_len;
@@ -363,6 +368,17 @@ struct r7z_archive
size_t pend_fed; /* input consumed by this stage */
uint32_t pend_base; /* coder_unpack_sizes base for folder */
/* What a reader on another thread may look at while one thread calls
* r7z_archive_decode_step(): the buffer the folder is being decoded
* into in order, and how much of it is final. prog_state is 0 when
* there is nothing to look at, 1 while it is being decoded, 2 when
* the folder is decoded and held; the buffer and the folder are
* written before the state says they are there. */
const uint8_t *prog_buf;
uint32_t prog_folder;
retro_atomic_int_t prog_state;
retro_atomic_size_t prog_pos;
/* A BCJ2 folder part way through. Its four inputs must all be
* complete before the converter can run, but they need not be
* resolved in the same call: one port per call keeps each call
@@ -1541,6 +1557,20 @@ static int decode_coder(r7z_archive_t *a, const coder_t *c,
* single-input coders (7z usually compresses main and call with LZMA).
* This resolves each input independently and then runs the converter.
*/
/* @n of the archive's bytes at @off, copied to @dst: from memory, or read
* in through the callback. The range is the caller's to have checked
* against the archive's length. */
static int r7z_fetch(const r7z_archive_t *a, uint64_t off, uint8_t *dst,
size_t n)
{
if (a->data)
memcpy(dst, a->data + (size_t)off, n);
else if (n && (!a->read_cb
|| a->read_cb(a->read_ud, off, dst, n) != (int64_t)n))
return R7Z_ERROR_DATA;
return R7Z_OK;
}
static int folder_input_offsets(r7z_archive_t *a, uint32_t fi,
uint32_t *pack_first_index, uint64_t *pack_file_offset)
{
@@ -1598,7 +1628,12 @@ static int resolve_input(r7z_archive_t *a, uint32_t fi, uint32_t port,
*out = (uint8_t *)malloc(n ? n : 1);
if (!*out)
return R7Z_ERROR_MEM;
memcpy(*out, a->data + 32 + off, n);
if ((res = r7z_fetch(a, 32 + off, *out, n)) != R7Z_OK)
{
free(*out);
*out = NULL;
return res;
}
*out_len = n;
return R7Z_OK;
}
@@ -1765,6 +1800,7 @@ static int decode_folder(r7z_archive_t *a, uint32_t fi, uint8_t **out)
folder_t *f;
uint8_t *cur = NULL;
size_t cur_len = 0;
int cur_owned = 1; /* cur is ours to free */
uint32_t ci;
int res;
@@ -1822,10 +1858,27 @@ static int decode_folder(r7z_archive_t *a, uint32_t fi, uint8_t **out)
if ((uint64_t)cur_len + pack_off + 32 > (uint64_t)a->len)
return R7Z_ERROR_DATA;
}
cur = (uint8_t *)malloc(cur_len ? cur_len : 1);
if (!cur)
return R7Z_ERROR_MEM;
memcpy(cur, a->data + 32 + pack_off, cur_len);
/* The first coder reads the packed stream where it lies when the
* archive is in memory: it was copied whole - as many bytes again
* as the folder's compressed size - only to be read once and
* freed. An archive that is read has it read in here, and that
* is the only time those bytes are in memory. */
if (a->data)
{
cur = (uint8_t *)(a->data + 32 + (size_t)pack_off);
cur_owned = 0;
}
else
{
cur = (uint8_t *)malloc(cur_len ? cur_len : 1);
if (!cur)
return R7Z_ERROR_MEM;
if ((res = r7z_fetch(a, 32 + pack_off, cur, cur_len)) != R7Z_OK)
{
free(cur);
return res;
}
}
for (ci = 0; ci < f->num_coders; ci++)
{
@@ -1835,12 +1888,14 @@ static int decode_folder(r7z_archive_t *a, uint32_t fi, uint8_t **out)
if (base + ci >= a->num_coder_unpack_sizes)
{
free(cur);
if (cur_owned)
free(cur);
return R7Z_ERROR_DATA;
}
if (a->coder_unpack_sizes[base + ci] > (uint64_t)((size_t)-1))
{
free(cur);
if (cur_owned)
free(cur);
return R7Z_ERROR_DATA;
}
next_len = (size_t)a->coder_unpack_sizes[base + ci];
@@ -1848,12 +1903,15 @@ static int decode_folder(r7z_archive_t *a, uint32_t fi, uint8_t **out)
next = (uint8_t *)malloc(next_len ? next_len : 1);
if (!next)
{
free(cur);
if (cur_owned)
free(cur);
return R7Z_ERROR_MEM;
}
res = decode_coder(a, c, cur, cur_len, next, next_len);
free(cur);
if (cur_owned)
free(cur);
cur_owned = 1;
if (res != R7Z_OK)
{
free(next);
@@ -1866,16 +1924,27 @@ static int decode_folder(r7z_archive_t *a, uint32_t fi, uint8_t **out)
if (cur_len != (size_t)f->unpack_size)
{
free(cur);
if (cur_owned)
free(cur);
return R7Z_ERROR_DATA;
}
if (f->has_crc && encoding_crc32(0, cur, cur_len) != f->crc)
{
free(cur);
if (cur_owned)
free(cur);
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;
return R7Z_OK;
}
@@ -2025,8 +2094,10 @@ static int decode_encoded_header(r7z_archive_t *a, rd_t *r,
memset(&tmp, 0, sizeof(tmp));
memset(&ss, 0, sizeof(ss));
tmp.data = a->data;
tmp.len = a->len;
tmp.data = a->data;
tmp.len = a->len;
tmp.read_cb = a->read_cb;
tmp.read_ud = a->read_ud;
res = parse_streams_info(&tmp, r, &ss);
substreams_free(&ss);
@@ -2055,35 +2126,43 @@ static int decode_encoded_header(r7z_archive_t *a, rd_t *r,
return res;
}
int r7z_archive_open(r7z_archive_t **out, const uint8_t *data, size_t len)
/* The archive in memory (@data), or read through @read_cb. */
static int r7z_open(r7z_archive_t **out, const uint8_t *data, size_t len,
r7z_read_t read_cb, void *ud)
{
static const uint8_t sig[R7Z_SIGNATURE_SIZE] =
{ '7', 'z', 0xBC, 0xAF, 0x27, 0x1C };
r7z_archive_t *a;
uint8_t start[32];
const uint8_t *next;
uint64_t next_off, next_size;
uint32_t next_crc;
rd_t r;
int res;
if (!out || !data)
if (!out || (!data && !read_cb))
return R7Z_ERROR_PARAM;
*out = NULL;
if (len < 32)
return R7Z_ERROR_DATA;
if (memcmp(data, sig, R7Z_SIGNATURE_SIZE) != 0)
if (data)
memcpy(start, data, 32);
else if (read_cb(ud, 0, start, 32) != 32)
return R7Z_ERROR_DATA;
if (memcmp(start, sig, R7Z_SIGNATURE_SIZE) != 0)
return R7Z_ERROR_DATA;
next_off = (uint64_t)data[12] | ((uint64_t)data[13] << 8)
| ((uint64_t)data[14] << 16) | ((uint64_t)data[15] << 24)
| ((uint64_t)data[16] << 32) | ((uint64_t)data[17] << 40)
| ((uint64_t)data[18] << 48) | ((uint64_t)data[19] << 56);
next_size = (uint64_t)data[20] | ((uint64_t)data[21] << 8)
| ((uint64_t)data[22] << 16) | ((uint64_t)data[23] << 24)
| ((uint64_t)data[24] << 32) | ((uint64_t)data[25] << 40)
| ((uint64_t)data[26] << 48) | ((uint64_t)data[27] << 56);
next_crc = (uint32_t)data[28] | ((uint32_t)data[29] << 8)
| ((uint32_t)data[30] << 16) | ((uint32_t)data[31] << 24);
next_off = (uint64_t)start[12] | ((uint64_t)start[13] << 8)
| ((uint64_t)start[14] << 16) | ((uint64_t)start[15] << 24)
| ((uint64_t)start[16] << 32) | ((uint64_t)start[17] << 40)
| ((uint64_t)start[18] << 48) | ((uint64_t)start[19] << 56);
next_size = (uint64_t)start[20] | ((uint64_t)start[21] << 8)
| ((uint64_t)start[22] << 16) | ((uint64_t)start[23] << 24)
| ((uint64_t)start[24] << 32) | ((uint64_t)start[25] << 40)
| ((uint64_t)start[26] << 48) | ((uint64_t)start[27] << 56);
next_crc = (uint32_t)start[28] | ((uint32_t)start[29] << 8)
| ((uint32_t)start[30] << 16) | ((uint32_t)start[31] << 24);
/* The header sits after the 32-byte signature block. Both bounds
* are checked against the real file length before either is used. */
@@ -2094,18 +2173,43 @@ int r7z_archive_open(r7z_archive_t **out, const uint8_t *data, size_t len)
if (next_size == 0)
return R7Z_ERROR_DATA;
if (encoding_crc32(0, data + 32 + next_off, (size_t)next_size) != next_crc)
return R7Z_ERROR_CRC;
a = (r7z_archive_t *)calloc(1, sizeof(*a));
if (!a)
return R7Z_ERROR_MEM;
a->data = data;
a->len = len;
a->read_cb = read_cb;
a->read_ud = ud;
retro_atomic_int_init(&a->prog_state, 0);
retro_atomic_size_init(&a->prog_pos, 0);
a->cached_folder = 0xFFFFFFFFu;
a->pend_folder = 0xFFFFFFFFu;
r.p = data + 32 + next_off;
/* The header block: where it lies, or read in and kept - what is
* parsed out of it may point into it. */
if (data)
next = data + 32 + (size_t)next_off;
else
{
if (!(a->next_buf = (uint8_t *)malloc((size_t)next_size)))
{
r7z_archive_close(a);
return R7Z_ERROR_MEM;
}
if (r7z_fetch(a, 32 + next_off, a->next_buf, (size_t)next_size) != R7Z_OK)
{
r7z_archive_close(a);
return R7Z_ERROR_DATA;
}
next = a->next_buf;
}
if (encoding_crc32(0, next, (size_t)next_size) != next_crc)
{
r7z_archive_close(a);
return R7Z_ERROR_CRC;
}
r.p = next;
r.end = r.p + (size_t)next_size;
/* An encoded header needs decoding before it can be parsed. */
@@ -2141,13 +2245,30 @@ int r7z_archive_open(r7z_archive_t **out, const uint8_t *data, size_t len)
return R7Z_OK;
}
int r7z_archive_open(r7z_archive_t **out, const uint8_t *data, size_t len)
{
if (!data)
return R7Z_ERROR_PARAM;
return r7z_open(out, data, len, NULL, NULL);
}
int r7z_archive_open_read(r7z_archive_t **out, uint64_t len,
r7z_read_t read_cb, void *ud)
{
if (!read_cb || len > (uint64_t)((size_t)-1))
return R7Z_ERROR_PARAM;
return r7z_open(out, NULL, (size_t)len, read_cb, ud);
}
void r7z_archive_close(r7z_archive_t *a)
{
if (!a)
return;
decode_pending_reset(a);
retro_atomic_store_release_int(&a->prog_state, 0);
free(a->cached_data);
free(a->header_buf);
free(a->next_buf);
free(a->pack_sizes);
free(a->coders);
free(a->props);
@@ -2199,11 +2320,16 @@ static void decode_pending_reset(r7z_archive_t *a)
{
if (!a)
return;
free(a->pend_in);
/* (a folder decoded and held stays to be looked at: 2) */
if (retro_atomic_load_acquire_int(&a->prog_state) == 1)
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);
a->pend_in = NULL;
a->pend_in = NULL;
a->pend_in_borrowed = 0;
a->pend_out = NULL;
a->pend_dec = NULL;
a->pend_probs = NULL;
@@ -2243,6 +2369,24 @@ static int coder_is_resumable(const coder_t *c)
return (c->method == METHOD_LZMA2 || c->method == METHOD_LZMA);
}
/* The cached folder, decoded whole and checked, is there to be read. */
static void r7z_progress_held(r7z_archive_t *a)
{
a->prog_buf = a->cached_data;
a->prog_folder = a->cached_folder;
retro_atomic_store_release_size(&a->prog_pos, a->cached_len);
retro_atomic_store_release_int(&a->prog_state, 2);
}
/* The stage that is being decoded writes its output in order, and is the
* folder's last: what it has written so far is the folder's bytes, there
* to be read (r7z_archive_decode_peek()). */
static void r7z_progress_at(r7z_archive_t *a, size_t pos)
{
if (retro_atomic_load_acquire_int(&a->prog_state) == 1)
retro_atomic_store_release_size(&a->prog_pos, pos);
}
/* Run one slice of a paused LZMA2 stage. Returns R7Z_OK with *done set
* when the stage has produced its whole output. */
static int decode_slice_lzma2(r7z_archive_t *a, const coder_t *c, int *done)
@@ -2265,6 +2409,7 @@ static int decode_slice_lzma2(r7z_archive_t *a, const coder_t *c, int *done)
return R7Z_ERROR_DATA;
a->pend_fed += got;
r7z_progress_at(a, dec->lzma.dic_pos);
if (dec->lzma.dic_pos >= a->pend_out_len
|| status == RLZMA2_STATUS_FINISHED)
@@ -2304,6 +2449,7 @@ static int decode_slice_lzma(r7z_archive_t *a, int *done)
return R7Z_ERROR_DATA;
a->pend_fed += got;
r7z_progress_at(a, s->dic_pos);
if (s->dic_pos >= a->pend_out_len
|| status == RLZMA_STATUS_FINISHED)
@@ -2343,6 +2489,16 @@ static int decode_stage_begin(r7z_archive_t *a, const folder_t *f)
if (!coder_is_resumable(c))
return R7Z_OK;
/* The folder's last coder, decoding in order into the buffer the
* folder will be held in: its progress can be watched. */
if (a->pend_coder + 1 == f->num_coders)
{
a->prog_buf = a->pend_out;
a->prog_folder = a->pend_folder;
retro_atomic_store_release_size(&a->prog_pos, 0);
retro_atomic_store_release_int(&a->prog_state, 1);
}
/* Stand up a decoder that survives between slices. */
if (c->method == METHOD_LZMA2)
{
@@ -2410,7 +2566,9 @@ static int decode_stage_finish(r7z_archive_t *a, const folder_t *f)
a->pend_dec = NULL;
a->pend_probs = NULL;
free(a->pend_in);
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;
@@ -2594,13 +2752,19 @@ static int decode_folder_bcj2_slice(r7z_archive_t *a, uint32_t fi,
decode_pending_reset(a);
return R7Z_ERROR_DATA;
}
free(a->pend_in);
if (!a->pend_in_borrowed)
free(a->pend_in);
a->pend_in_borrowed = 0;
if (!(a->pend_in = (uint8_t *)malloc(n ? n : 1)))
{
decode_pending_reset(a);
return R7Z_ERROR_MEM;
}
memcpy(a->pend_in, a->data + 32 + off, n);
if (r7z_fetch(a, 32 + off, a->pend_in, n) != R7Z_OK)
{
decode_pending_reset(a);
return R7Z_ERROR_DATA;
}
a->pend_in_len = n;
break;
}
@@ -2710,6 +2874,7 @@ static int decode_folder_bcj2_slice(r7z_archive_t *a, uint32_t fi,
return R7Z_ERROR_CRC;
}
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;
@@ -2796,12 +2961,26 @@ static int decode_folder_slice(r7z_archive_t *a, uint32_t fi, int *done)
decode_pending_reset(a);
return R7Z_ERROR_DATA;
}
if (!(a->pend_in = (uint8_t *)malloc(n ? n : 1)))
/* Where it lies, when the archive is in memory: see
* decode_folder(). */
if (a->data)
{
decode_pending_reset(a);
return R7Z_ERROR_MEM;
a->pend_in = (uint8_t *)(a->data + 32 + (size_t)off);
a->pend_in_borrowed = 1;
}
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)
{
decode_pending_reset(a);
return R7Z_ERROR_DATA;
}
}
memcpy(a->pend_in, a->data + 32 + off, n);
a->pend_in_len = n;
}
@@ -2865,10 +3044,25 @@ 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;
}
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_progress_held(a);
a->pend_in = NULL;
a->pend_in_len = 0;
@@ -2961,6 +3155,7 @@ int r7z_archive_extract_slice(r7z_archive_t *a, uint32_t index,
if ((res = decode_folder(a, e->folder, &folder_data)) != 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;
@@ -3021,6 +3216,7 @@ int r7z_archive_extract(r7z_archive_t *a, uint32_t index,
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;
@@ -3059,3 +3255,189 @@ int r7z_archive_extract(r7z_archive_t *a, uint32_t index,
*out_len = (size_t)e->size;
return R7Z_OK;
}
int r7z_archive_extract_detach(r7z_archive_t *a, uint32_t index,
uint8_t **out, size_t *out_len)
{
const r7z_entry_t *e;
uint8_t *buf;
int res;
if (!a || !out || !out_len)
return R7Z_ERROR_PARAM;
if (index >= a->num_entries)
return R7Z_ERROR_PARAM;
e = &a->entries[index];
if (e->is_dir || e->size == 0 || e->folder >= a->num_folders
|| e->offset_in_folder != 0
|| e->size != a->folders[e->folder].unpack_size)
return r7z_archive_extract(a, index, out, out_len);
*out = NULL;
*out_len = 0;
if (a->cached_folder == e->folder && a->cached_data)
buf = a->cached_data;
else
{
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)
return res;
}
/* 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;
if (e->has_crc && encoding_crc32(0, buf, (size_t)e->size) != e->crc)
{
free(buf);
return R7Z_ERROR_CRC;
}
*out = buf;
*out_len = (size_t)e->size;
return R7Z_OK;
}
int r7z_archive_entry_borrow(r7z_archive_t *a, uint32_t index,
const uint8_t **out, size_t *out_len)
{
const r7z_entry_t *e;
uint8_t *folder_data;
int res;
if (!a || !out || !out_len)
return R7Z_ERROR_PARAM;
if (index >= a->num_entries)
return R7Z_ERROR_PARAM;
e = &a->entries[index];
*out = NULL;
*out_len = 0;
if (e->is_dir)
return R7Z_ERROR_PARAM;
if (e->size == 0)
return R7Z_OK;
if (e->folder >= a->num_folders)
return R7Z_ERROR_DATA;
if (a->cached_folder == e->folder && a->cached_data)
folder_data = a->cached_data;
else
{
if ((res = decode_folder(a, e->folder, &folder_data)) != 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;
}
if (e->offset_in_folder > a->cached_len
|| e->size > a->cached_len - (size_t)e->offset_in_folder)
return R7Z_ERROR_DATA;
folder_data += (size_t)e->offset_in_folder;
if (e->has_crc && encoding_crc32(0, folder_data, (size_t)e->size) != e->crc)
return R7Z_ERROR_CRC;
*out = folder_data;
*out_len = (size_t)e->size;
return R7Z_OK;
}
uint64_t r7z_archive_folder_size(const r7z_archive_t *a, uint32_t folder)
{
if (!a || folder >= a->num_folders)
return 0;
return a->folders[folder].unpack_size;
}
int r7z_archive_decode_step(r7z_archive_t *a, uint32_t index)
{
const r7z_entry_t *e;
int done = 0;
int res;
if (!a || index >= a->num_entries)
return R7Z_ERROR_PARAM;
e = &a->entries[index];
if (e->is_dir || e->size == 0)
return R7Z_ERROR_PARAM;
if (e->folder >= a->num_folders)
return R7Z_ERROR_DATA;
if (a->cached_folder == e->folder && a->cached_data)
{
if (retro_atomic_load_acquire_int(&a->prog_state) != 2)
r7z_progress_held(a);
return R7Z_OK;
}
res = decode_folder_slice(a, e->folder, &done);
if (res == R7Z_ERROR_UNSUPPORTED)
{
/* A shape the slicer will not take: whole, in this one call. */
uint8_t *folder_data;
if ((res = decode_folder(a, e->folder, &folder_data)) != 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_progress_held(a);
return R7Z_OK;
}
if (res != R7Z_OK)
return res;
if (!done)
return R7Z_PENDING;
/* (a folder that went through the BCJ2 slicer says so only now) */
if (retro_atomic_load_acquire_int(&a->prog_state) != 2)
r7z_progress_held(a);
return R7Z_OK;
}
int r7z_archive_decode_peek(const r7z_archive_t *a, uint32_t index,
const uint8_t **data, size_t *avail)
{
const r7z_entry_t *e;
size_t pos;
int state;
if (!a || !data || !avail || index >= a->num_entries)
return R7Z_ERROR_PARAM;
*data = NULL;
*avail = 0;
e = &a->entries[index];
if (e->is_dir)
return R7Z_ERROR_PARAM;
/* the state, and then what it says is there */
state = retro_atomic_load_acquire_int((retro_atomic_int_t*)&a->prog_state);
if (!state || a->prog_folder != e->folder)
return R7Z_OK;
pos = retro_atomic_load_acquire_size((retro_atomic_size_t*)&a->prog_pos);
if (e->offset_in_folder > (uint64_t)((size_t)-1))
return R7Z_ERROR_DATA;
*data = a->prog_buf + (size_t)e->offset_in_folder;
if (pos > (size_t)e->offset_in_folder)
{
size_t have = pos - (size_t)e->offset_in_folder;
*avail = (uint64_t)have < e->size ? have : (size_t)e->size;
}
return R7Z_OK;
}
+6 -2
View File
@@ -2656,10 +2656,14 @@ int rchd_read_extent(const rchd_t *chd, uint32_t lba, uint32_t count,
if (!chd->track_count)
return RCHD_ERROR_STATE;
/* Frame numbers are 32-bit: an extent running past the last one
* cannot be in the image. */
if ((uint64_t)lba + count > 0xFFFFFFFFu + (uint64_t)1)
return RCHD_ERROR_PARAM;
for (i = 0; i < count; i++)
{
const rchd_track_t *t = rchd_track_for_lba(chd,
(uint64_t)lba + i);
const rchd_track_t *t = rchd_track_for_lba(chd, lba + i);
if (!t)
return RCHD_ERROR_PARAM;
+30 -34
View File
@@ -5031,13 +5031,10 @@ typedef struct rh264_pic_ctx
#define RH264_MAX_CTX 8
#define RH264_RBSP_POOL 32 /* slice jobs kept between slices */
#if defined(HAVE_THREADS) && !defined(RETRO_ATOMIC_HAS_PTR)
static slock_t *rh264_slots_lock;
#endif
/* A pool slot holds a pointer or nothing, and changes hands in one
* atomic swap: whoever swaps a pointer out owns it. Backends with no
* pointer atomics keep the slots under a lock. */
* atomic swap: whoever swaps a pointer out owns it. Without pointer
* atomics a decoder runs on the one thread that calls it and keeps no
* pool shared between decoders, so its slots are plain. */
#ifdef RETRO_ATOMIC_HAS_PTR
typedef retro_atomic_ptr_t rh264_slot_t;
#define rh264_slot_peek(s) retro_atomic_load_relaxed_ptr(s)
@@ -5049,34 +5046,17 @@ typedef void *rh264_slot_t;
static void *rh264_slot_take(rh264_slot_t *s)
{
void *p;
#ifdef HAVE_THREADS
if (rh264_slots_lock)
slock_lock(rh264_slots_lock);
#endif
p = *s;
*s = NULL;
#ifdef HAVE_THREADS
if (rh264_slots_lock)
slock_unlock(rh264_slots_lock);
#endif
void *p = *s;
*s = NULL;
return p;
}
static int rh264_slot_fill(rh264_slot_t *s, void *p)
{
int ok;
#ifdef HAVE_THREADS
if (rh264_slots_lock)
slock_lock(rh264_slots_lock);
#endif
if ((ok = !*s))
*s = p;
#ifdef HAVE_THREADS
if (rh264_slots_lock)
slock_unlock(rh264_slots_lock);
#endif
return ok;
if (*s)
return 0;
*s = p;
return 1;
}
#endif
@@ -5274,24 +5254,38 @@ static void rh264_block_publish_rows(void *data, int rows)
#endif
}
/* Planes released by any decoder wait here for the next of the same
* size. Shared between decoders on any threads, so only where the
* slots are atomic; elsewhere a block is simply freed. */
#ifdef RETRO_ATOMIC_HAS_PTR
#define RH264_FREE_BLOCKS 16
#else
#define RH264_FREE_BLOCKS 0
#endif
#if RH264_FREE_BLOCKS
static rh264_slot_t rh264_free_blocks[RH264_FREE_BLOCKS];
#endif
static int rh264_block_park(rh264_block_hdr *b)
{
#if RH264_FREE_BLOCKS
int i;
for (i = 0; i < RH264_FREE_BLOCKS; i++)
if ( !rh264_slot_peek(&rh264_free_blocks[i])
&& rh264_slot_fill(&rh264_free_blocks[i], b))
return 1;
#else
(void)b;
#endif
return 0;
}
static uint8_t *rh264_block_new(size_t len)
{
int i;
rh264_block_hdr *b;
#if RH264_FREE_BLOCKS
int i;
for (i = 0; i < RH264_FREE_BLOCKS; i++)
{
if ( !rh264_slot_peek(&rh264_free_blocks[i])
@@ -5308,6 +5302,7 @@ static uint8_t *rh264_block_new(size_t len)
&& !rh264_block_park(b))
free(b);
}
#endif
b = (rh264_block_hdr*)calloc(len + RH264_PLANES_HDR, 1);
if (!b)
return NULL;
@@ -10628,6 +10623,11 @@ void rh264_video_set_thread_pool(rh264_video *v, void *pool, int threads)
#ifdef HAVE_THREADS
if (!v)
return;
#ifndef RETRO_ATOMIC_HAS_PTR
/* Pictures in flight on the pool hand their buffers over through
* the slots: no pool without pointer atomics */
pool = NULL;
#endif
if (!pool || threads < 2)
{
v->pool = NULL;
@@ -10636,10 +10636,6 @@ void rh264_video_set_thread_pool(rh264_video *v, void *pool, int threads)
}
if (!rh264_rows_ec_ok)
{
#ifndef RETRO_ATOMIC_HAS_PTR
if (!rh264_slots_lock && !(rh264_slots_lock = slock_new()))
return;
#endif
if (!retro_eventcount_init(&rh264_rows_ec))
{
retro_eventcount_free(&rh264_rows_ec);
+43 -37
View File
@@ -130,9 +130,6 @@
#ifdef HAVE_THREADS
static retro_eventcount_t rh265_rows_ec;
static int rh265_rows_ec_ok;
#ifndef RETRO_ATOMIC_HAS_PTR
static slock_t *rh265_pool_lock;
#endif
#endif
@@ -162,10 +159,11 @@ typedef struct
const uint8_t *buf;
size_t size;
size_t bitpos;
int malformed;
} rh265_bits;
static void rh265_bits_init(rh265_bits *b, const uint8_t *buf, size_t size)
{ b->buf = buf; b->size = size; b->bitpos = 0; }
{ b->buf = buf; b->size = size; b->bitpos = 0; b->malformed = 0; }
static uint32_t rh265_u1(rh265_bits *b)
{
@@ -183,15 +181,38 @@ static uint32_t rh265_un(rh265_bits *b, int n)
return v;
}
/* No ue(v) field this decoder reads goes past 2^25 - 2 - the largest
* are picture sizes and cropping, which stop at 16888 - so a longer
* code marks the unit malformed (and reads as 0). That keeps every
* value well inside int, and the sums the parsers make of them from
* overflowing. A field the spec lets run to 2^32 - 2 that nothing
* reads is skipped with rh265_ue_skip(). */
#define RH265_UE_MAX_LZ 24
static uint32_t rh265_ue(rh265_bits *b)
{
int lz = 0;
while (lz < 32 && !rh265_u1(b)) lz++;
if (lz >= 32) return 0xffffffffu;
while (lz <= RH265_UE_MAX_LZ && !rh265_u1(b)) lz++;
if (lz > RH265_UE_MAX_LZ)
{
b->malformed = 1;
return 0;
}
if (!lz) return 0;
return (uint32_t)((1u << lz) - 1u + rh265_un(b, lz));
}
/* A ue(v) of any length the spec allows, skipped */
static void rh265_ue_skip(rh265_bits *b)
{
int lz = 0;
while (lz < 32 && !rh265_u1(b)) lz++;
if (lz >= 32)
b->malformed = 1;
else if (lz)
rh265_un(b, lz);
}
static int32_t rh265_se(rh265_bits *b)
{
uint32_t k = rh265_ue(b);
@@ -200,7 +221,7 @@ static int32_t rh265_se(rh265_bits *b)
}
static int rh265_bits_overrun(const rh265_bits *b)
{ return b->bitpos > b->size * 8; }
{ return b->malformed || b->bitpos > b->size * 8; }
@@ -670,7 +691,7 @@ static int rh265_parse_sps(const uint8_t *rbsp, size_t size, rh265_sps *s,
{
s->max_dec_pic_buffering = (int)rh265_ue(&b) + 1;
s->max_num_reorder_pics = (int)rh265_ue(&b);
rh265_ue(&b); /* sps_max_latency_increase_plus1 */
rh265_ue_skip(&b); /* sps_max_latency_increase_plus1 */
}
}
s->log2_min_cb = (int)rh265_ue(&b) + 3;
@@ -1454,8 +1475,9 @@ typedef struct
#define RH265_NAL_POOL 32
/* A pool slot holds a pointer or nothing, and changes hands in one
* atomic swap: whoever swaps a pointer out owns it. Backends with no
* pointer atomics keep the slots under a lock. */
* atomic swap: whoever swaps a pointer out owns it. Without pointer
* atomics a decoder runs on the one thread that calls it, so its
* slots are plain. */
#ifdef RETRO_ATOMIC_HAS_PTR
typedef retro_atomic_ptr_t rh265_slot_t;
#define rh265_slot_peek(s) retro_atomic_load_relaxed_ptr(s)
@@ -1467,34 +1489,17 @@ typedef void *rh265_slot_t;
static void *rh265_slot_take(rh265_slot_t *s)
{
void *p;
#ifdef HAVE_THREADS
if (rh265_pool_lock)
slock_lock(rh265_pool_lock);
#endif
p = *s;
*s = NULL;
#ifdef HAVE_THREADS
if (rh265_pool_lock)
slock_unlock(rh265_pool_lock);
#endif
void *p = *s;
*s = NULL;
return p;
}
static int rh265_slot_fill(rh265_slot_t *s, void *p)
{
int ok;
#ifdef HAVE_THREADS
if (rh265_pool_lock)
slock_lock(rh265_pool_lock);
#endif
if ((ok = !*s))
*s = p;
#ifdef HAVE_THREADS
if (rh265_pool_lock)
slock_unlock(rh265_pool_lock);
#endif
return ok;
if (*s)
return 0;
*s = p;
return 1;
}
#endif
@@ -4178,6 +4183,11 @@ void rh265_video_set_thread_pool(rh265_video *v, void *pool,
}
if (threads > RH265_MAX_CTX - 1)
threads = RH265_MAX_CTX - 1;
#ifndef RETRO_ATOMIC_HAS_PTR
/* Pictures in flight on the pool hand their buffers over through
* the slots: no pool without pointer atomics */
pool = NULL;
#endif
if (!pool || threads <= 1)
{
v->pool = NULL;
@@ -4193,10 +4203,6 @@ void rh265_video_set_thread_pool(rh265_video *v, void *pool,
* rows of the ones it predicts from instead. */
if (!rh265_rows_ec_ok)
{
#ifndef RETRO_ATOMIC_HAS_PTR
if (!rh265_pool_lock && !(rh265_pool_lock = slock_new()))
return;
#endif
if (!retro_eventcount_init(&rh265_rows_ec))
{
retro_eventcount_free(&rh265_rows_ec);
+154
View File
@@ -0,0 +1,154 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (image_yuv_blit.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stddef.h>
#include <formats/image_yuv_blit.h>
/* The matrices in 18.14 fixed point, the luma term pre-biased so the
* rounding and the black level are one add. Indexed by
* (BT709 << 1) | FULL_RANGE. */
typedef struct
{
int ym; /* luma scale */
int yb; /* luma bias, with the rounding half in it */
int rv; /* Cr -> R */
int gu; /* Cb -> G (negative) */
int gv; /* Cr -> G (negative) */
int bu; /* Cb -> B */
} image_yuv_coef_t;
static const image_yuv_coef_t image_yuv_coefs[4] =
{
/* BT.601 limited: 1.164, 1.596, -0.392, -0.813, 2.017 */
{ 19077, -19077 * 16 + 8192, 26149, -6419, -13320, 33050 },
/* BT.601 full: 1.000, 1.402, -0.344, -0.714, 1.772 */
{ 16384, 8192, 22970, -5638, -11700, 29032 },
/* BT.709 limited: 1.164, 1.793, -0.213, -0.533, 2.112 */
{ 19077, -19077 * 16 + 8192, 29372, -3494, -8731, 34610 },
/* BT.709 full: 1.000, 1.575, -0.187, -0.468, 1.856 */
{ 16384, 8192, 25802, -3069, -7670, 30402 }
};
#define IMAGE_YUV_SHIFT 14
#define IMAGE_YUV_CLAMP(v) ((v) < 0 ? 0 : ((v) > 255 ? 255 : (v)))
/* Two luma pixels sharing one chroma sample, into @d[0] and @d[1]
* (or @d[0] alone when @n is 1). */
static void image_yuv_pair(uint32_t *d, unsigned n,
const uint8_t *yp, int cb, int cr,
const image_yuv_coef_t *c, unsigned rgba)
{
int rr = c->rv * cr;
int gg = c->gu * cb + c->gv * cr;
int bb = c->bu * cb;
unsigned i;
for (i = 0; i < n; i++)
{
int yy = c->ym * yp[i] + c->yb;
int r = IMAGE_YUV_CLAMP((yy + rr) >> IMAGE_YUV_SHIFT);
int g = IMAGE_YUV_CLAMP((yy + gg) >> IMAGE_YUV_SHIFT);
int b = IMAGE_YUV_CLAMP((yy + bb) >> IMAGE_YUV_SHIFT);
d[i] = rgba
? (0xff000000u | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r)
: (0xff000000u | ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b);
}
}
/* The frame, two rows at a time, with the chroma of pixel pair @x
* fetched by @cb_at / @cr_at as the layout dictates. */
static void image_yuv_frame(uint32_t *dst, unsigned dst_stride,
const uint8_t *y, unsigned y_stride,
const uint8_t *c0, unsigned c0_stride, unsigned c0_step,
const uint8_t *c1, unsigned c1_stride, unsigned c1_step,
unsigned w, unsigned h, unsigned flags)
{
const image_yuv_coef_t *c =
&image_yuv_coefs[((flags & IMAGE_YUV_FLAG_BT709) ? 2 : 0)
| ((flags & IMAGE_YUV_FLAG_FULL_RANGE) ? 1 : 0)];
unsigned rgba = flags & IMAGE_YUV_FLAG_RGBA;
unsigned row;
for (row = 0; row < h; row += 2)
{
const uint8_t *y0 = y + (size_t)row * y_stride;
const uint8_t *y1 = y0 + y_stride;
const uint8_t *cb = c0 + (size_t)(row >> 1) * c0_stride;
const uint8_t *cr = c1 + (size_t)(row >> 1) * c1_stride;
uint32_t *d0 = dst + (size_t)row * dst_stride;
uint32_t *d1 = d0 + dst_stride;
unsigned rows = (row + 1 < h) ? 2 : 1;
unsigned x;
for (x = 0; x < w; x += 2)
{
unsigned n = (x + 1 < w) ? 2 : 1;
int u = (int)cb[(x >> 1) * c0_step] - 128;
int v = (int)cr[(x >> 1) * c1_step] - 128;
image_yuv_pair(d0 + x, n, y0 + x, u, v, c, rgba);
if (rows == 2)
image_yuv_pair(d1 + x, n, y1 + x, u, v, c, rgba);
}
}
}
void image_yuv_420_to_rgb32(uint32_t *dst, unsigned dst_stride,
const uint8_t *y, unsigned y_stride,
const uint8_t *cb, unsigned cb_stride,
const uint8_t *cr, unsigned cr_stride,
unsigned chroma_step, unsigned w, unsigned h, unsigned flags)
{
image_yuv_frame(dst, dst_stride, y, y_stride,
cb, cb_stride, chroma_step, cr, cr_stride, chroma_step,
w, h, flags);
}
void image_yuv_nv12_to_rgb32(uint32_t *dst, unsigned dst_stride,
const uint8_t *y, unsigned y_stride,
const uint8_t *uv, unsigned uv_stride,
unsigned w, unsigned h, unsigned flags)
{
const uint8_t *cb = uv;
const uint8_t *cr = uv + 1;
if (flags & IMAGE_YUV_FLAG_VU)
{
cb = uv + 1;
cr = uv;
}
image_yuv_frame(dst, dst_stride, y, y_stride,
cb, uv_stride, 2, cr, uv_stride, 2, w, h, flags);
}
void image_yuv_i420_to_rgb32(uint32_t *dst, unsigned dst_stride,
const uint8_t *y, unsigned y_stride,
const uint8_t *u, unsigned u_stride,
const uint8_t *v, unsigned v_stride,
unsigned w, unsigned h, unsigned flags)
{
if (flags & IMAGE_YUV_FLAG_VU)
image_yuv_frame(dst, dst_stride, y, y_stride,
v, v_stride, 1, u, u_stride, 1, w, h, flags);
else
image_yuv_frame(dst, dst_stride, y, y_stride,
u, u_stride, 1, v, v_stride, 1, w, h, flags);
}
+453 -147
View File
@@ -28,6 +28,21 @@
#include <boolean.h>
#include <formats/image.h>
#include <formats/data_transfer.h>
#ifdef HAVE_RPNG
#include <formats/rpng.h>
#endif
#ifdef HAVE_RJPEG
#include <formats/rjpeg.h>
#endif
#ifdef HAVE_RTGA
#include <formats/rtga.h>
#endif
#ifdef HAVE_RBMP
#include <formats/rbmp.h>
#endif
#ifdef HAVE_RWEBP
#include <formats/rwebp.h>
#endif
enum image_type_enum image_texture_get_type(const char *path)
{
@@ -125,125 +140,417 @@ enum image_type_enum image_texture_get_type(const char *path)
return IMAGE_TYPE_NONE;
}
static bool image_texture_load_internal(
enum image_type_enum type,
void *ptr,
size_t len,
struct texture_image *out_img,
bool (*should_abort)(void *ud), void *abort_ud)
struct image_loader
{
int ret;
void *img;
void *xfer;
const uint8_t *buf;
uint32_t *pixels;
struct texture_compressed *compressed;
bool (*should_abort)(void *ud);
void *abort_ud;
size_t len;
size_t avail;
unsigned width;
unsigned height;
enum image_type_enum type;
enum image_loader_state state;
uint8_t phase; /* 0: the transfer, 1: the pixels */
bool pix10;
bool fp16;
image_texture_request_t req;
};
out_img->compressed = NULL;
img = image_transfer_new(type);
if (!img)
bool image_loader_ready(enum image_type_enum type,
const void *buf, size_t avail)
{
if (!buf || !avail)
return false;
image_transfer_set_buffer_ptr(img, type, (uint8_t*)ptr, len);
/* A caller that wants the extra precision says so through
* ->pix10; the decoders that have a 10-bit path (PNG 16-bit, and
* the video stills) then emit XRGB2101010 and report what they
* actually produced. A decoder or a file without one leaves the
* flag clear and the caller gets the ordinary 8-bit image. */
if (out_img->pix10)
image_transfer_set_want_10bit(img, type, 1);
if (!image_transfer_start(img, type))
switch (type)
{
image_transfer_free(img, type);
/* The video stills decode against a growing buffer from the
* first byte; the picture decoders paint from a prefix once
* their header is resident. WEBP has no wall to stall at, so
* it starts only once the still's chunk is whole. */
case IMAGE_TYPE_WEBM:
case IMAGE_TYPE_MP4:
return true;
#ifdef HAVE_RWEBP
case IMAGE_TYPE_WEBP:
return rwebp_still_ready(buf, avail);
#endif
#ifdef HAVE_RPNG
case IMAGE_TYPE_PNG:
return rpng_header_ready((const uint8_t*)buf, avail);
#endif
#ifdef HAVE_RJPEG
case IMAGE_TYPE_JPEG:
return rjpeg_header_ready((const uint8_t*)buf, avail);
#endif
#ifdef HAVE_RTGA
case IMAGE_TYPE_TGA:
return rtga_header_ready((const uint8_t*)buf, avail);
#endif
#ifdef HAVE_RBMP
case IMAGE_TYPE_BMP:
return rbmp_header_ready((const uint8_t*)buf, avail);
#endif
default:
break;
}
return false;
}
image_loader_t *image_loader_new(enum image_type_enum type,
const image_texture_request_t *req)
{
image_loader_t *l;
if (type == IMAGE_TYPE_NONE)
return NULL;
if (!(l = (image_loader_t*)calloc(1, sizeof(*l))))
return NULL;
l->type = type;
l->state = IMAGE_LOADER_RUNNING;
if (req)
l->req = *req;
return l;
}
bool image_loader_start(image_loader_t *l, const void *buf, size_t len,
size_t avail)
{
if (!l || l->xfer || !buf || !len)
return false;
if (!(l->xfer = image_transfer_new(l->type)))
return false;
l->buf = (const uint8_t*)buf;
l->len = len;
l->avail = avail < len ? avail : len;
/* WEBP has no avail wall: it sees the bytes read so far and no
* more, and its start gate above admits it only once the still's
* chunk lies within them. The others wall at the frontier. */
if (l->type == IMAGE_TYPE_WEBP)
image_transfer_set_buffer_ptr(l->xfer, l->type,
(uint8_t*)buf, l->avail);
else
{
image_transfer_set_buffer_ptr(l->xfer, l->type, (uint8_t*)buf, len);
if (l->avail < len)
image_transfer_set_avail(l->xfer, l->type, l->avail);
}
if (l->req.want_10bit)
image_transfer_set_want_10bit(l->xfer, l->type, 1);
if (l->req.want_fp16)
image_transfer_set_want_fp16(l->xfer, l->type, true);
/* The channel order now: the JPEG decoder emits final pixels
* during the transfer, before the process call could name it. */
image_transfer_set_rgba(l->xfer, l->type, l->req.rgba);
if (!image_transfer_start(l->xfer, l->type))
{
image_transfer_free(l->xfer, l->type);
l->xfer = NULL;
l->state = IMAGE_LOADER_ERROR;
return false;
}
return true;
}
/* Chunk by chunk, with the abort hook between chunks. */
while (image_transfer_iterate(img, type))
if (should_abort && should_abort(abort_ud))
{
image_transfer_free(img, type);
return false;
}
void image_loader_set_avail(image_loader_t *l, size_t avail)
{
if (!l || !l->xfer)
return;
l->avail = avail < l->len ? avail : l->len;
image_transfer_set_avail(l->xfer, l->type, avail);
}
if (!image_transfer_is_valid(img, type))
void image_loader_set_abort(image_loader_t *l,
bool (*should_abort)(void *ud), void *ud)
{
if (!l)
return;
l->should_abort = should_abort;
l->abort_ud = ud;
}
/* The transfer is through: valid, and either the compressed payload
* the caller asked for is copied out, or the pixels are next. */
static enum image_loader_state image_loader_transferred(image_loader_t *l)
{
struct image_gpu_layout lay;
if (!image_transfer_is_valid(l->xfer, l->type))
return IMAGE_LOADER_ERROR;
l->phase = 1;
if ( l->req.want_compressed
&& image_transfer_get_gpu_layout(l->xfer, l->type, l->len, &lay))
{
image_transfer_free(img, type);
return false;
}
out_img->pix10 = image_transfer_is_10bit(img, type);
/* No decoder emits half floats: an answer, never an ask, and the
* caller's struct may hold anything there. */
out_img->fp16 = false;
/* GPU-native fast path: if the loader can hand back BCn blocks for
* direct upload, copy the source (so the mip pointers survive the
* caller freeing its buffer) and defer any RGBA8 decode. */
{
struct image_gpu_layout lay;
if (image_transfer_get_gpu_layout(img, type, len, &lay))
/* The source copied, so the mip pointers outlive the caller's
* buffer; the CPU decode is deferred to image_texture_realize_rgba */
struct texture_compressed *tc = (struct texture_compressed*)
calloc(1, sizeof(*tc));
if (tc)
{
struct texture_compressed *tc = (struct texture_compressed*)
calloc(1, sizeof(*tc));
if (tc)
tc->mips = (struct texture_mip*)
malloc((size_t)lay.num_mips * sizeof(*tc->mips));
tc->storage = malloc(l->len);
if (tc->mips && tc->storage)
{
tc->mips = (struct texture_mip*)
malloc((size_t)lay.num_mips * sizeof(*tc->mips));
tc->storage = malloc(len);
if (tc->mips && tc->storage)
unsigned i;
memcpy(tc->storage, l->buf, l->len);
tc->storage_len = l->len;
tc->num_mips = lay.num_mips;
tc->format = lay.format;
tc->type = l->type;
for (i = 0; i < lay.num_mips; i++)
{
unsigned i;
memcpy(tc->storage, ptr, len);
tc->storage_len = len;
tc->num_mips = lay.num_mips;
tc->format = lay.format;
tc->type = type;
for (i = 0; i < lay.num_mips; i++)
{
tc->mips[i].data = (const unsigned char*)tc->storage
+ lay.offset[i];
tc->mips[i].width = lay.width[i];
tc->mips[i].height = lay.height[i];
tc->mips[i].size = lay.size[i];
}
out_img->compressed = tc;
out_img->pixels = NULL;
out_img->width = lay.width[0];
out_img->height = lay.height[0];
image_transfer_free(img, type);
return true;
tc->mips[i].data = (const unsigned char*)tc->storage
+ lay.offset[i];
tc->mips[i].width = lay.width[i];
tc->mips[i].height = lay.height[i];
tc->mips[i].size = lay.size[i];
}
free(tc->storage);
free(tc->mips);
free(tc);
l->compressed = tc;
l->width = lay.width[0];
l->height = lay.height[0];
return IMAGE_LOADER_DONE;
}
/* allocation failure: fall through to the CPU decode below */
free(tc->storage);
free(tc->mips);
free(tc);
/* Out of memory for the copy: the CPU decode below instead */
}
}
return IMAGE_LOADER_RUNNING;
}
enum image_loader_state image_loader_step(image_loader_t *l,
int64_t (*now)(void), int64_t deadline)
{
if (!l || !l->xfer)
return IMAGE_LOADER_ERROR;
if (l->state == IMAGE_LOADER_DONE || l->state == IMAGE_LOADER_ERROR)
return l->state;
l->state = IMAGE_LOADER_RUNNING;
/* Each loop runs its first iteration whatever the budget, so a
* caller whose window is spent still progresses; a timed step ends
* at a phase change rather than beginning the next phase's first
* pass on top of the time it already took. */
if (l->phase == 0)
{
do
{
if (!image_transfer_iterate(l->xfer, l->type))
{
/* False both when the transfer is through and when a
* decoder painting from a prefix stalled at the byte
* frontier: the next step resumes once more has arrived. */
if (image_transfer_need_more(l->xfer, l->type))
return (l->state = IMAGE_LOADER_WAIT);
if ((l->state = image_loader_transferred(l)) != IMAGE_LOADER_RUNNING
|| now)
return l->state;
break;
}
if (l->should_abort && l->should_abort(l->abort_ud))
return (l->state = IMAGE_LOADER_ERROR);
} while (!now || now() < deadline);
if (l->phase == 0)
return IMAGE_LOADER_RUNNING;
}
do
{
ret = image_transfer_process(img, type,
(uint32_t**)&out_img->pixels, len, &out_img->width,
&out_img->height, out_img->supports_rgba);
/* Pass by pass (inflate + unfilter), the abort hook between. */
if (ret == IMAGE_PROCESS_NEXT && should_abort && should_abort(abort_ud))
int ret = image_transfer_process(l->xfer, l->type,
&l->pixels, l->len, &l->width, &l->height, l->req.rgba);
switch (ret)
{
free(out_img->pixels);
out_img->pixels = NULL;
image_transfer_free(img, type);
return false;
case IMAGE_PROCESS_NEXT:
/* Pass by pass (inflate + unfilter), the abort hook between */
if (l->should_abort && l->should_abort(l->abort_ud))
return (l->state = IMAGE_LOADER_ERROR);
break;
case IMAGE_PROCESS_WAIT:
/* A video still at the frontier: nothing was consumed */
return (l->state = IMAGE_LOADER_WAIT);
case IMAGE_PROCESS_END:
l->pix10 = image_transfer_is_10bit(l->xfer, l->type);
l->fp16 = image_transfer_is_fp16(l->xfer, l->type);
return (l->state = IMAGE_LOADER_DONE);
default:
return (l->state = IMAGE_LOADER_ERROR);
}
} while (ret == IMAGE_PROCESS_NEXT);
} while (!now || now() < deadline);
return IMAGE_LOADER_RUNNING;
}
if (ret == IMAGE_PROCESS_ERROR || ret == IMAGE_PROCESS_ERROR_END)
bool image_loader_finish(image_loader_t *l, struct texture_image *img)
{
if (!l || !img || l->state != IMAGE_LOADER_DONE)
return false;
img->pixels = l->pixels;
img->compressed = l->compressed;
img->width = l->width;
img->height = l->height;
img->supports_rgba = l->req.rgba;
img->pix10 = l->pix10;
img->fp16 = l->fp16;
l->pixels = NULL;
l->compressed = NULL;
return true;
}
int image_loader_png_probe(const image_loader_t *l)
{
#ifdef HAVE_RPNG
int more = 0;
/* Only a complete read is a verdict: over a partial buffer the walk
* can only say "not yet", which would read as "still" */
if (!l || l->type != IMAGE_TYPE_PNG || !l->buf || l->avail < l->len)
return -1;
if (rpng_is_apng_ex(l->buf, l->len, &more))
return 1;
return 0;
#else
(void)l;
return -1;
#endif
}
void *image_loader_detach_anim_stream(image_loader_t *l)
{
if (!l || !l->xfer)
return NULL;
return image_transfer_detach_anim_stream(l->xfer, l->type);
}
void image_loader_free(image_loader_t *l)
{
if (!l)
return;
if (l->xfer)
image_transfer_free(l->xfer, l->type);
if (l->compressed)
{
image_transfer_free(img, type);
free(l->compressed->storage);
free(l->compressed->mips);
free(l->compressed);
}
free(l->pixels);
free(l);
}
static void image_texture_clear(struct texture_image *img)
{
img->supports_rgba = false;
img->pixels = NULL;
img->width = 0;
img->height = 0;
img->compressed = NULL;
img->pix10 = false;
img->fp16 = false;
}
static bool image_texture_load_buffer_probe(struct texture_image *out_img,
enum image_type_enum type, const void *ptr, size_t len,
const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *abort_ud, int *png_probe)
{
image_loader_t *l;
enum image_loader_state st;
if (!out_img)
return false;
out_img->compressed = NULL;
if (!ptr || !len || !(l = image_loader_new(type, req)))
{
image_texture_clear(out_img);
return false;
}
image_loader_set_abort(l, should_abort, abort_ud);
if (!image_loader_start(l, ptr, len, len))
{
image_loader_free(l);
return false;
}
st = image_loader_step(l, NULL, 0);
if (st == IMAGE_LOADER_DONE && image_loader_finish(l, out_img))
{
if (png_probe)
*png_probe = image_loader_png_probe(l);
image_loader_free(l);
return true;
}
image_loader_free(l);
image_texture_clear(out_img);
return false;
}
image_transfer_free(img, type);
return true;
bool image_texture_load_buffer_request(struct texture_image *out_img,
enum image_type_enum type, const void *ptr, size_t len,
const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *abort_ud)
{
return image_texture_load_buffer_probe(out_img, type, ptr, len, req,
should_abort, abort_ud, NULL);
}
bool image_texture_load_request(struct texture_image *out_img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud)
{
return image_texture_load_request_ex(out_img, path, req,
should_abort, ud, NULL);
}
bool image_texture_load_request_ex(struct texture_image *out_img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud, int *png_probe)
{
enum image_type_enum type = image_texture_get_type(path);
if (png_probe)
*png_probe = -1;
if (type != IMAGE_TYPE_NONE)
{
/* The synchronous read rides the data_transfer prefix spine
* like every other loader: filestream/VFS routing (overlays
* and driver assets from archive members or content://
* documents), 64-bit lengths, the hardware guard behind the
* read, and honest short-read detection. A zero budget fills
* to completion in one blocking call, which is this API's
* contract. */
struct data_transfer *dt = data_transfer_open_prefix(path, 0);
if (dt)
{
size_t file_len = 0;
const uint8_t *ptr = NULL;
data_transfer_iterate(dt, 0);
ptr = data_transfer_ptr(dt, &file_len);
if (data_transfer_complete(dt) && ptr && file_len
&& image_texture_load_buffer_probe(out_img, type,
ptr, file_len, req, should_abort, ud, png_probe))
{
data_transfer_free(dt);
return true;
}
data_transfer_free(dt);
}
}
if (out_img)
image_texture_clear(out_img);
return false;
}
/* The request the plain loads carry: the caller's image, whose
* ->pix10 asks for 10-bit on the way in */
static void image_texture_request_of(const struct texture_image *img,
image_texture_request_t *req)
{
req->rgba = img->supports_rgba;
req->want_10bit = img->pix10;
req->want_fp16 = false;
req->want_compressed = true;
}
bool image_texture_load(struct texture_image *out_img, const char *path)
@@ -417,66 +724,65 @@ bool image_texture_tile_gx(struct texture_image *img)
return true;
}
size_t image_texture_tile_gx_size(unsigned width, unsigned height)
{
return (size_t)((width + 3) & ~3u) * ((height + 3) & ~3u)
* sizeof(uint32_t);
}
/* See image.h. A tile is 32 halfwords: AR of its sixteen texels row by
* row, then GB. Values, not memory order, so it is right on the GX's
* big-endian bus whatever host runs the tests. */
void image_texture_tile_gx_copy(uint16_t *dst, const uint32_t *src,
unsigned width, unsigned height, bool rgba)
{
unsigned tx, ty, r, c;
unsigned tw = (width + 3) & ~3u;
unsigned th = (height + 3) & ~3u;
for (ty = 0; ty < th; ty += 4)
for (tx = 0; tx < tw; tx += 4, dst += 32)
for (r = 0; r < 4; r++)
for (c = 0; c < 4; c++)
{
unsigned x = tx + c;
unsigned y = ty + r;
unsigned k = r * 4 + c;
if (x >= width || y >= height)
{
dst[k] = 0;
dst[16 + k] = 0;
}
else if (rgba)
{
const uint8_t *b = (const uint8_t*)(src
+ (size_t)y * width + x);
dst[k] = (uint16_t)((b[3] << 8) | b[0]);
dst[16 + k] = (uint16_t)((b[1] << 8) | b[2]);
}
else
{
uint32_t p = src[(size_t)y * width + x];
dst[k] = (uint16_t)(p >> 16);
dst[16 + k] = (uint16_t)p;
}
}
}
bool image_texture_load_buffer(struct texture_image *out_img,
enum image_type_enum type, void *buffer, size_t buffer_len)
{
if (type != IMAGE_TYPE_NONE)
{
if (image_texture_load_internal(
type, buffer, buffer_len, out_img, NULL, NULL))
return true;
}
out_img->supports_rgba = false;
out_img->pixels = NULL;
out_img->width = 0;
out_img->height = 0;
out_img->compressed = NULL;
return false;
image_texture_request_t req;
image_texture_request_of(out_img, &req);
return image_texture_load_buffer_request(out_img, type, buffer,
buffer_len, &req, NULL, NULL);
}
bool image_texture_load_ex(struct texture_image *out_img,
const char *path, bool (*should_abort)(void *ud), void *ud)
{
enum image_type_enum type = image_texture_get_type(path);
if (type != IMAGE_TYPE_NONE)
{
/* The synchronous read rides the data_transfer prefix spine
* like every other loader: filestream/VFS routing (overlays
* and driver assets from archive members or content://
* documents), 64-bit lengths, the hardware guard behind the
* read, and honest short-read detection. A zero budget fills
* to completion in one blocking call, which is this API's
* contract. */
struct data_transfer *dt = data_transfer_open_prefix(path, 0);
if (dt)
{
size_t file_len = 0;
const uint8_t *ptr = NULL;
data_transfer_iterate(dt, 0);
ptr = data_transfer_ptr(dt, &file_len);
if (data_transfer_complete(dt) && ptr && file_len
&& image_texture_load_internal(
type,
(void*)ptr, file_len, out_img, should_abort, ud))
{
data_transfer_free(dt);
return true;
}
data_transfer_free(dt);
}
}
out_img->supports_rgba = false;
out_img->pixels = NULL;
out_img->width = 0;
out_img->height = 0;
out_img->compressed = NULL;
out_img->pix10 = false;
out_img->fp16 = false;
return false;
image_texture_request_t req;
image_texture_request_of(out_img, &req);
return image_texture_load_request(out_img, path, &req,
should_abort, ud);
}
+330
View File
@@ -0,0 +1,330 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (image_texture_scale.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <boolean.h>
#include <formats/image.h>
#include <gfx/scaler/scaler.h>
#define UPSCALE_MAX_PIXELS (256u * 1024u * 1024u)
static bool upscale_image(
unsigned scale_factor,
struct texture_image *image_src,
struct texture_image *image_dst)
{
struct scaler_ctx ctx;
size_t total_pixels;
/* Sanity check */
if ((scale_factor < 2) || !image_src || !image_dst)
return false;
if (!image_src->pixels || (image_src->width < 1) || (image_src->height < 1))
return false;
/* Get output dimensions */
image_dst->width = image_src->width * scale_factor;
image_dst->height = image_src->height * scale_factor;
total_pixels = (size_t)image_dst->width * (size_t)image_dst->height;
if (total_pixels == 0 || total_pixels > UPSCALE_MAX_PIXELS)
return false;
if (!(image_dst->pixels = (uint32_t*)malloc(total_pixels * sizeof(uint32_t))))
return false;
/* Duplicate rather than interpolate.
*
* This looks like the wrong choice in isolation - at 4x, point
* sampling leaves 75% of pixels identical to their left neighbour
* and steps edges 4 levels at once, where interpolation would step
* 1 - and it was changed to bilinear on that reasoning. But this
* texture is not what anyone sees. The menu hands it to the GPU,
* which samples it bilinearly into a thumbnail box that is a
* different size again, so whatever softening happens here happens
* a second time on top. Interpolating twice is measurably worse
* than the GPU interpolating once, which makes upscaling actively
* harmful: an Amiga save state screenshot drawn into a 960px box
* came out 3.7% softer with the upscaler on than with it off, and
* boxart the same way to a lesser degree. The setting existed to
* sharpen thumbnails and was blurring them.
*
* Duplication survives the GPU's pass instead of compounding with
* it. The hard steps land on texel boundaries the bilinear sample
* then softens by exactly one output pixel, which is the crisp
* look the setting is for - the same screenshot comes out 12.5%
* sharper than with no upscaling at all, against -3.7% for
* bilinear. It is also the only faithful option for 10-bit, where
* point sampling copies the packed words untouched.
*
* This is why the factor has to stay a whole number: at a
* fractional ratio the runs come out uneven, some source pixels
* doubled and some tripled, which is worse than either. */
memset(&ctx, 0, sizeof(ctx));
ctx.in_width = image_src->width;
ctx.in_height = image_src->height;
ctx.in_stride = image_src->width * sizeof(uint32_t);
ctx.out_width = image_dst->width;
ctx.out_height = image_dst->height;
ctx.out_stride = image_dst->width * sizeof(uint32_t);
ctx.in_fmt = image_src->pix10
? SCALER_FMT_XRGB2101010 : SCALER_FMT_ARGB8888;
ctx.out_fmt = ctx.in_fmt;
ctx.scaler_type = SCALER_TYPE_POINT;
if (!scaler_ctx_gen_filter(&ctx))
{
free(image_dst->pixels);
image_dst->pixels = NULL;
return false;
}
scaler_ctx_scale(&ctx, image_dst->pixels, image_src->pixels);
scaler_ctx_gen_reset(&ctx);
return true;
}
static uint32_t *downscale_box(const uint32_t *src,
unsigned sw, unsigned sh, unsigned f, bool pix10,
unsigned *out_w, unsigned *out_h)
{
unsigned x, y, i, j;
unsigned n = f * f;
unsigned dw = sw / f;
unsigned dh = sh / f;
uint32_t *d;
if ((dw < 1) || (dh < 1))
return NULL;
if (!(d = (uint32_t*)malloc((size_t)dw * dh * sizeof(uint32_t))))
return NULL;
for (y = 0; y < dh; y++)
{
for (x = 0; x < dw; x++)
{
unsigned a = 0, r = 0, g = 0, b = 0;
for (j = 0; j < f; j++)
{
const uint32_t *row = src + (size_t)(y * f + j) * sw + x * f;
if (pix10)
{
for (i = 0; i < f; i++)
{
uint32_t p = row[i];
r += (p >> 20) & 0x3ff;
g += (p >> 10) & 0x3ff;
b += p & 0x3ff;
}
}
else
{
for (i = 0; i < f; i++)
{
uint32_t p = row[i];
a += (p >> 24) & 0xff;
r += (p >> 16) & 0xff;
g += (p >> 8) & 0xff;
b += p & 0xff;
}
}
}
if (pix10)
d[(size_t)y * dw + x] = 0xc0000000u
| ((r / n) << 20) | ((g / n) << 10) | (b / n);
else
d[(size_t)y * dw + x] =
((a / n) << 24) | ((r / n) << 16)
| ((g / n) << 8) | (b / n);
}
}
*out_w = dw;
*out_h = dh;
return d;
}
/* Cap a decoded image to 'cap' pixels on its longest side, preserving
* aspect ratio.
*
* The sidebar thumbnail and the fullscreen view share one texture -
* going fullscreen only raises a flag and fades alpha, it never
* re-requests the image - so the cap has to be the size the larger of
* the two views can use, which is the display, not the sidebar box.
* At display size the fullscreen view is unchanged, since it can
* never be drawn larger than the panel, and the sidebar downsamples
* from it on the GPU for free. Sizing to the sidebar instead would
* make the fullscreen view soft.
*
* Returns true if the image was replaced. */
static bool downscale_image(unsigned cap, struct texture_image *img)
{
struct scaler_ctx ctx;
unsigned sw, sh, dw, dh, f;
uint32_t *mid = NULL;
uint32_t *out = NULL;
const uint32_t *src;
double ratio;
if (!cap || !img || !img->pixels)
return false;
if ((img->width < 1) || (img->height < 1))
return false;
if ((img->width <= cap) && (img->height <= cap))
return false;
ratio = (img->width > img->height)
? (double)cap / (double)img->width
: (double)cap / (double)img->height;
dw = (unsigned)(img->width * ratio);
dh = (unsigned)(img->height * ratio);
if ((dw < 1) || (dh < 1))
return false;
src = img->pixels;
sw = img->width;
sh = img->height;
/* Stage 1: decimate as far as can be done without undershooting
* the target, leaving the sinc stage a ratio below 2 - and so its
* minimum 8-tap kernel. Stopping earlier (at a ratio of 2 or
* more) widens the kernel to 16 taps or beyond and costs another
* LSB of DC per step, since the accumulate truncates once per
* tap. */
for (f = 1; (sw / (f * 2)) >= dw; f *= 2) ;
if (f > 1)
{
unsigned bw, bh;
if ((mid = downscale_box(src, sw, sh, f, img->pix10, &bw, &bh)))
{
src = mid;
sw = bw;
sh = bh;
}
}
/* Stage 2: sinc to the exact target. */
if (!(out = (uint32_t*)malloc((size_t)dw * dh * sizeof(uint32_t))))
{
free(mid);
return false;
}
memset(&ctx, 0, sizeof(ctx));
ctx.in_width = sw;
ctx.in_height = sh;
ctx.in_stride = sw * sizeof(uint32_t);
ctx.out_width = dw;
ctx.out_height = dh;
ctx.out_stride = dw * sizeof(uint32_t);
ctx.in_fmt = img->pix10
? SCALER_FMT_XRGB2101010 : SCALER_FMT_ARGB8888;
ctx.out_fmt = ctx.in_fmt;
ctx.scaler_type = SCALER_TYPE_SINC;
if (!scaler_ctx_gen_filter(&ctx))
{
free(out);
free(mid);
return false;
}
scaler_ctx_scale(&ctx, out, src);
scaler_ctx_gen_reset(&ctx);
free(mid);
free(img->pixels);
img->pixels = out;
img->width = dw;
img->height = dh;
return true;
}
bool image_texture_scale(struct texture_image *img,
unsigned upscale_threshold, unsigned downscale_cap)
{
bool changed = false;
if (!img || !img->pixels || !img->width || !img->height)
return false;
/* The resamplers know 8-bit and packed 10-bit texels; a still of
* half floats is taken at its own size. */
if (img->fp16)
return false;
if ( upscale_threshold > 0
&& (img->width < upscale_threshold || img->height < upscale_threshold))
{
unsigned min_size = (img->width < img->height) ? img->width : img->height;
unsigned max_size = (img->width > img->height) ? img->width : img->height;
unsigned scale_factor_int = (upscale_threshold + min_size - 1) / min_size;
/* The factor comes from the shorter side, so the longer one
* lands at the threshold times the aspect ratio - which on
* anything wider than 1:1 can overshoot the cap the image is
* about to be reduced to anyway, filtering pixels into existence
* only to filter them away again. So the largest whole factor
* that stays under the cap; whole, not fractional, so the
* duplication runs stay even (see upscale_image). */
if (downscale_cap > 0)
{
unsigned cap_factor = downscale_cap / max_size;
if (scale_factor_int > cap_factor)
scale_factor_int = cap_factor;
}
/* Nothing to gain below 2x: the source is already as large as
* the cap allows */
if (scale_factor_int > 1)
{
struct texture_image img_resampled;
img_resampled.pixels = NULL;
img_resampled.width = 0;
img_resampled.height = 0;
if (upscale_image(scale_factor_int, img, &img_resampled))
{
free(img->pixels);
img->pixels = img_resampled.pixels;
img->width = img_resampled.width;
img->height = img_resampled.height;
changed = true;
}
}
}
/* Cap oversized images before they reach the GPU: an 11000x11000
* PNG is 462 MB of RGBA for a few hundred pixels of screen. 10-bit
* too - a 16-bit PNG, which rpng packs as XRGB2101010, is exactly
* the kind of file that gets this large. */
if (downscale_cap > 0 && downscale_image(downscale_cap, img))
changed = true;
return changed;
}
+160
View File
@@ -0,0 +1,160 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (image_texture_set.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include <boolean.h>
#include <formats/image.h>
#include <formats/data_transfer.h>
#ifdef HAVE_GCD
#include <dispatch/dispatch.h>
#elif defined(HAVE_THREADS)
#include <retro_atomic.h>
#include <rthreads/rthreads.h>
#include <features/features_cpu.h>
#endif
#if !defined(HAVE_GCD) && defined(HAVE_THREADS)
/* The next index a worker takes */
typedef struct
{
void (*fn)(unsigned i, void *ud);
void (*thread_done)(void);
void *ud;
retro_atomic_int_t next;
unsigned n;
} image_texture_set_t;
static void image_texture_set_worker(void *data)
{
image_texture_set_t *set = (image_texture_set_t*)data;
for (;;)
{
unsigned i = (unsigned)retro_atomic_fetch_add_int(&set->next, 1);
if (i >= set->n)
return;
set->fn(i, set->ud);
}
}
static void image_texture_set_thread(void *data)
{
image_texture_set_t *set = (image_texture_set_t*)data;
image_texture_set_worker(set);
if (set->thread_done)
set->thread_done();
}
#endif
void image_texture_set_run(unsigned n,
void (*fn)(unsigned i, void *ud), void *ud)
{
image_texture_set_run_ex(n, fn, ud, NULL);
}
void image_texture_set_run_ex(unsigned n,
void (*fn)(unsigned i, void *ud), void *ud,
void (*thread_done)(void))
{
unsigned i;
/* A dispatch pool's threads live on, and their pools with them:
* bounded, as the task queue's own are */
(void)thread_done;
if (!n || !fn)
return;
#ifdef HAVE_GCD
if (n > 1)
{
dispatch_apply(n,
dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0),
^(size_t at) { fn((unsigned)at, ud); });
return;
}
#elif defined(HAVE_THREADS)
{
unsigned workers = cpu_features_get_core_amount();
if (workers > 8)
workers = 8;
if (workers > 1 && n > 1)
{
/* This thread works alongside the others; a worker that
* could not be made costs nothing but its share */
image_texture_set_t set;
sthread_t *thread[8];
unsigned t, spawned = workers - 1;
set.fn = fn;
set.thread_done = thread_done;
set.ud = ud;
set.n = n;
retro_atomic_int_init(&set.next, 0);
if (spawned > n - 1)
spawned = n - 1;
for (t = 0; t < spawned; t++)
thread[t] = sthread_create(image_texture_set_thread, &set);
image_texture_set_worker(&set);
for (t = 0; t < spawned; t++)
if (thread[t])
sthread_join(thread[t]);
return;
}
}
#endif
for (i = 0; i < n; i++)
fn(i, ud);
}
typedef struct
{
const char *const *paths;
struct texture_image *imgs;
const image_texture_request_t *req;
} image_texture_load_set_t;
static void image_texture_load_one(unsigned i, void *ud)
{
image_texture_load_set_t *set = (image_texture_load_set_t*)ud;
struct texture_image *img = &set->imgs[i];
if ( !set->paths[i] || !*set->paths[i]
|| !image_texture_load_request(img, set->paths[i], set->req,
NULL, NULL))
img->pixels = NULL;
}
unsigned image_texture_load_set(const char *const *paths,
struct texture_image *imgs, unsigned n,
const image_texture_request_t *req)
{
image_texture_load_set_t set;
unsigned i, done = 0;
if (!paths || !imgs || !n)
return 0;
memset(imgs, 0, (size_t)n * sizeof(*imgs));
set.paths = paths;
set.imgs = imgs;
set.req = req;
image_texture_set_run_ex(n, image_texture_load_one, &set,
data_transfer_pool_flush);
for (i = 0; i < n; i++)
if (imgs[i].pixels || imgs[i].compressed)
done++;
return done;
}
+5 -2
View File
@@ -609,10 +609,13 @@ static void rmp3_L3_decode_scalefactors(const uint8_t *hdr, uint8_t *ist_pos, rm
}
rmp3_L3_read_scalefactors(iscf, ist_pos, scf_size, scf_partition, bs, scfsi);
if (gr->n_short_sfb)
/* n_short_sfb is a multiple of 3 and n_long_sfb + n_short_sfb at
* most 39 (rmp3_L3_read_side_info sets only 22+0, 0+39, 8+30 and
* 6+30); written so the compiler can see the writes stay in iscf. */
if (gr->n_short_sfb && gr->n_long_sfb + gr->n_short_sfb <= (int)sizeof(iscf))
{
int sh = 3 - scf_shift;
for (i = 0; i < gr->n_short_sfb; i += 3)
for (i = 0; i + 2 < gr->n_short_sfb; i += 3)
{
iscf[gr->n_long_sfb + i + 0] += gr->subblock_gain[0] << sh;
iscf[gr->n_long_sfb + i + 1] += gr->subblock_gain[1] << sh;
+22 -2
View File
@@ -956,6 +956,14 @@ static void rmp4_parse_traf(rmp4_t *m, const uint8_t *body, uint64_t size,
first_flags = rmp4_be32(b.body + off); off += 4;
have_first = 1;
}
/* No per-sample fields: n samples of the default size. Of
* no size, there is nothing to append, only their time to
* pass - and n is the file's to choose, up to 2^32-1. */
if (!(f & 0x000F00) && !dflt_size)
{
t->frag_dts += (uint64_t)n * dflt_dur;
n = 0;
}
for (i = 0; i < n; i++)
{
uint32_t dur = dflt_dur, sz = dflt_size, fl = dflt_flags;
@@ -987,9 +995,16 @@ static void rmp4_parse_traf(rmp4_t *m, const uint8_t *body, uint64_t size,
fl = first_flags;
pts = rmp4_ticks_to_ns(
(int64_t)t->frag_dts + cts, t->timescale);
if (sz && doff + sz <= m->len)
if (sz)
{
/* A fragment can supply a 64-bit base offset. Check the
* range before adding, or the end offset can wrap. */
if (doff > (uint64_t)m->len
|| sz > (uint64_t)m->len - doff)
return;
rmp4_track_append(t, doff, sz, pts,
(uint8_t)!((fl >> 16) & 1));
}
doff += sz;
t->frag_dts += dur;
}
@@ -1291,9 +1306,14 @@ int rmp4_read_packet(rmp4_t *m, rmp4_packet *pkt)
{
rmp4_itrack *t = &m->trk[best];
uint32_t c = t->cursor;
/* Reject a malformed sample table before forming a file pointer. */
if (t->off[c] > (uint64_t)m->len
|| t->size[c] > (uint64_t)m->len - t->off[c])
return -1;
/* Sample bytes not yet in the buffer: consume nothing and let
* the caller retry once more of the file has been read. */
if (t->off[c] + t->size[c] > (uint64_t)m->avail)
if (t->off[c] > (uint64_t)m->avail
|| t->size[c] > (uint64_t)m->avail - t->off[c])
return RMP4_READ_AGAIN;
t->cursor++;
if (t->off[c] + t->size[c] > m->media_max_end)
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+128
View File
@@ -0,0 +1,128 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (rrar_ppmd7.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.
*/
/* The PPMd variant H model RAR 2.9 packs with, and RAR's range decoder.
*
* 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.
* Decoding only. What comes out has to be what RAR put in, so the model
* makes every decision theirs does: the 12-byte units, the free lists
* and when blocks are glued, when the model restarts for want of
* memory, and the arithmetic of every frequency.
*
* Nothing here trusts the data: the model only ever grows from the
* symbols it decodes, and the range decoder reads its input from a span
* with made-up zeros past the end, which it counts.
*/
#ifndef __LIBRETRO_SDK_RRAR_PPMD7_H
#define __LIBRETRO_SDK_RRAR_PPMD7_H
#include <stddef.h>
#include <stdint.h>
#include <boolean.h>
#include <retro_common_api.h>
RETRO_BEGIN_DECLS
#define RRAR_PPMD7_NUM_INDEXES 38
struct rrar_ppmd7_state;
struct rrar_ppmd7_context;
/* Secondary escape estimation: one of the 25x16 adaptive escape counts. */
typedef struct rrar_ppmd7_see
{
uint16_t summ;
uint8_t shift; /* how fast summ moves; low is fast */
uint8_t count; /* until shift next changes */
} rrar_ppmd7_see_t;
typedef struct rrar_ppmd7
{
struct rrar_ppmd7_context *min_ctx;
struct rrar_ppmd7_context *max_ctx;
struct rrar_ppmd7_state *found;
/* The model's memory: text grows up from the bottom, units are
* carved from lo_unit up and hi_unit down. Links inside it are 32-bit
* offsets from base; 0 is none. */
uint8_t *base;
uint8_t *lo_unit;
uint8_t *hi_unit;
uint8_t *text;
uint8_t *units_start;
int32_t run_length;
int32_t init_rl;
uint32_t size;
uint32_t align_offset;
uint32_t glue_count;
uint32_t free_list[RRAR_PPMD7_NUM_INDEXES];
unsigned order_fall;
unsigned init_esc;
unsigned prev_success;
unsigned max_order;
unsigned hi_bits_flag;
uint16_t bin_summ[128][64];
rrar_ppmd7_see_t see[25][16];
rrar_ppmd7_see_t dummy_see;
uint8_t indx2units[RRAR_PPMD7_NUM_INDEXES];
uint8_t units2indx[128];
uint8_t ns2indx[256];
uint8_t ns2bsindx[256];
uint8_t hb2flag[256];
} rrar_ppmd7_t;
/* RAR's range decoder, over a span of the packed data. */
typedef struct rrar_ppmd7_range
{
const uint8_t *p;
const uint8_t *end;
uint32_t range;
uint32_t code;
uint32_t low;
uint32_t past; /* bytes read past the end, as zeros */
} rrar_ppmd7_range_t;
/* Once, before anything else. */
void rrar_ppmd7_construct(rrar_ppmd7_t *p);
/* The model's memory, @size bytes; kept when the size is the same. */
bool rrar_ppmd7_alloc(rrar_ppmd7_t *p, uint32_t size);
void rrar_ppmd7_free(rrar_ppmd7_t *p);
/* A new model of order @max_order. */
void rrar_ppmd7_init(rrar_ppmd7_t *p, unsigned max_order);
/* The decoder at @data, its first four bytes read; @past bytes are
* already counted as read past @end. false if the bytes cannot start a
* block. */
bool rrar_ppmd7_range_init(rrar_ppmd7_range_t *rc, const uint8_t *data,
const uint8_t *end, uint32_t past);
/* 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);
RETRO_END_DECLS
#endif
+5
View File
@@ -1010,6 +1010,11 @@ static void rvp9_d135_predictor(RVP9_PIXEL *dst, int stride, int bs,
RVP9_PIXEL border[32 + 32 - 1]; /* outer border from bottom-left to top-right */
#endif
/* Blocks are at most 32 wide, so the border's last write is
* border[2 * 32 - 2]; said here so the compiler can see it. */
if (bs > 32)
return;
/* dst(bs, bs - 2)[0], i.e., border starting at bottom-left */
for (i = 0; i < bs - 2; ++i) {
border[i] = RVP9_AVG3(left[bs - 3 - i], left[bs - 2 - i], left[bs - 1 - i]);
+936
View File
@@ -0,0 +1,936 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (rzip_archive.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include <encodings/crc32.h>
#include <encodings/deflate.h>
#include <retro_atomic.h>
#include <zip/rzip_archive.h>
#define SIG_LOCAL 0x04034b50u
#define SIG_CENTRAL 0x02014b50u
#define SIG_EOCD 0x06054b50u
#define SIG_EOCD64 0x06064b50u
#define SIG_EOCD64_LOC 0x07064b50u
#define EOCD_LEN 22
#define EOCD_MAX_TAIL (EOCD_LEN + 0xffff) /* a comment may follow */
#define CENTRAL_LEN 46
#define LOCAL_LEN 30
#define EXTRA_ZIP64 0x0001
/* Compressed input is pulled in pieces of this size when the archive is
* read through the callback; it is the inflater's window, not a limit
* on member size. */
#define READ_CHUNK (64 * 1024)
struct rzip_archive
{
const uint8_t *data;
rzip_read_t read_cb;
void *ud;
rzip_entry_t *entries;
char *names;
uint64_t len;
uint32_t num_entries;
};
static uint32_t rd16(const uint8_t *p)
{
return (uint32_t)p[0] | ((uint32_t)p[1] << 8);
}
static uint32_t rd32(const uint8_t *p)
{
return (uint32_t)p[0] | ((uint32_t)p[1] << 8)
| ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
static uint64_t rd64(const uint8_t *p)
{
return (uint64_t)rd32(p) | ((uint64_t)rd32(p + 4) << 32);
}
/* [off, off + n) of the archive: in place from memory, or read into
* @buf. NULL when the range is outside the archive or the read fails. */
static const uint8_t *rzip_bytes(const rzip_archive_t *a, uint64_t off,
size_t n, uint8_t *buf)
{
if (off > a->len || n > a->len - off)
return NULL;
if (a->data)
return a->data + (size_t)off;
if (a->read_cb(a->ud, off, buf, n) != (int64_t)n)
return NULL;
return buf;
}
/* Finds the end-of-central-directory record, honouring the zip64
* record when the 32-bit one defers to it. */
static int rzip_find_directory(rzip_archive_t *a, uint64_t *cd_off,
uint64_t *cd_size, uint64_t *cd_count)
{
uint8_t *tail_buf = NULL;
const uint8_t *tail;
uint64_t tail_off;
size_t tail_len, i;
int res = RZIP_ERROR_DATA;
if (a->len < EOCD_LEN)
return RZIP_ERROR_DATA;
tail_len = a->len < EOCD_MAX_TAIL ? (size_t)a->len : EOCD_MAX_TAIL;
tail_off = a->len - tail_len;
if (!a->data && !(tail_buf = (uint8_t*)malloc(tail_len)))
return RZIP_ERROR_MEM;
if (!(tail = rzip_bytes(a, tail_off, tail_len, tail_buf)))
{
free(tail_buf);
return RZIP_ERROR_IO;
}
for (i = tail_len - EOCD_LEN + 1; i-- > 0; )
{
const uint8_t *e = tail + i;
uint64_t eocd_off;
if (rd32(e) != SIG_EOCD)
continue;
/* the comment must fit between the record and the end */
if (rd16(e + 20) > tail_len - EOCD_LEN - i)
continue;
eocd_off = tail_off + i;
*cd_count = rd16(e + 10);
*cd_size = rd32(e + 12);
*cd_off = rd32(e + 16);
if ((*cd_count == 0xffff || *cd_size == 0xffffffffu
|| *cd_off == 0xffffffffu) && eocd_off >= 20)
{
uint8_t loc_buf[20];
uint8_t rec_buf[56];
const uint8_t *loc;
const uint8_t *rec;
if (!(loc = rzip_bytes(a, eocd_off - 20, 20, loc_buf))
|| rd32(loc) != SIG_EOCD64_LOC)
break;
if (!(rec = rzip_bytes(a, rd64(loc + 8), 56, rec_buf))
|| rd32(rec) != SIG_EOCD64)
break;
*cd_count = rd64(rec + 32);
*cd_size = rd64(rec + 40);
*cd_off = rd64(rec + 48);
}
res = RZIP_OK;
break;
}
free(tail_buf);
return res;
}
/* The zip64 extra field carries, in this order, only the fields whose
* 32-bit slots are saturated. */
static void rzip_parse_zip64(const uint8_t *extra, size_t extra_len,
uint64_t *size, uint64_t *csize, uint64_t *local)
{
while (extra_len >= 4)
{
size_t flen = rd16(extra + 2);
if (flen > extra_len - 4)
return;
if (rd16(extra) == EXTRA_ZIP64)
{
const uint8_t *q = extra + 4;
const uint8_t *end = q + flen;
if (*size == 0xffffffffu && end - q >= 8)
{
*size = rd64(q);
q += 8;
}
if (*csize == 0xffffffffu && end - q >= 8)
{
*csize = rd64(q);
q += 8;
}
if (*local == 0xffffffffu && end - q >= 8)
*local = rd64(q);
return;
}
extra += 4 + flen;
extra_len -= 4 + flen;
}
}
/* A member's data follows its local header, whose name and extra
* fields need not be the central directory's. A member whose local
* header cannot be read keeps data_off at the end of the archive, so
* no range can be served from it. */
static void rzip_locate(rzip_archive_t *a, rzip_entry_t *e, uint64_t local)
{
uint8_t hdr_buf[LOCAL_LEN];
const uint8_t *hdr = rzip_bytes(a, local, LOCAL_LEN, hdr_buf);
e->data_off = a->len;
if (hdr && rd32(hdr) == SIG_LOCAL)
e->data_off = local + LOCAL_LEN + rd16(hdr + 26) + rd16(hdr + 28);
}
int rzip_archive_open(rzip_archive_t **out, const uint8_t *data,
uint64_t len, rzip_read_t read_cb, void *ud)
{
rzip_archive_t *a;
uint8_t *cd_buf = NULL;
const uint8_t *cd;
const uint8_t *p;
uint64_t cd_off, cd_size, cd_count;
size_t name_bytes = 0;
uint32_t n = 0, i;
char *name_out;
int res;
if (!out || (!data && !read_cb))
return RZIP_ERROR_PARAM;
*out = NULL;
if (!(a = (rzip_archive_t*)calloc(1, sizeof(*a))))
return RZIP_ERROR_MEM;
a->data = data;
a->read_cb = read_cb;
a->ud = ud;
a->len = len;
if ((res = rzip_find_directory(a, &cd_off, &cd_size, &cd_count)) != RZIP_OK)
goto fail;
res = RZIP_ERROR_DATA;
if (cd_size > (uint64_t)((size_t)-1) - 1 || cd_count > 0x7fffffffu)
goto fail;
if (!a->data && !(cd_buf = (uint8_t*)malloc((size_t)cd_size + 1)))
{
res = RZIP_ERROR_MEM;
goto fail;
}
if (!(cd = rzip_bytes(a, cd_off, (size_t)cd_size, cd_buf)))
{
res = RZIP_ERROR_IO;
goto fail;
}
/* count the records that fit, and size the name pool */
for (p = cd; (size_t)(p - cd) + CENTRAL_LEN <= (size_t)cd_size
&& n < cd_count; n++)
{
size_t vlen;
if (rd32(p) != SIG_CENTRAL)
break;
vlen = rd16(p + 28) + rd16(p + 30) + rd16(p + 32);
if ((size_t)(p - cd) + CENTRAL_LEN + vlen > (size_t)cd_size)
break;
name_bytes += rd16(p + 28) + 1;
p += CENTRAL_LEN + vlen;
}
res = RZIP_ERROR_MEM;
if (!(a->entries = (rzip_entry_t*)calloc(n ? n : 1, sizeof(*a->entries))))
goto fail;
if (!(a->names = (char*)malloc(name_bytes ? name_bytes : 1)))
goto fail;
a->num_entries = n;
name_out = a->names;
for (i = 0, p = cd; i < n; i++)
{
rzip_entry_t *e = &a->entries[i];
size_t name_len = rd16(p + 28);
size_t extra_len = rd16(p + 30);
uint64_t size = rd32(p + 24);
uint64_t csize = rd32(p + 20);
uint64_t local = rd32(p + 42);
memcpy(name_out, p + CENTRAL_LEN, name_len);
name_out[name_len] = '\0';
e->name = name_out;
name_out += name_len + 1;
rzip_parse_zip64(p + CENTRAL_LEN + name_len, extra_len,
&size, &csize, &local);
e->size = size;
e->csize = csize;
e->crc = rd32(p + 16);
e->method = rd16(p + 10);
e->is_dir = name_len > 0
&& (e->name[name_len - 1] == '/' || e->name[name_len - 1] == '\\');
if (e->is_dir)
e->data_off = a->len;
else
rzip_locate(a, e, local);
p += CENTRAL_LEN + name_len + extra_len + rd16(p + 32);
}
free(cd_buf);
*out = a;
return RZIP_OK;
fail:
free(cd_buf);
rzip_archive_close(a);
return res;
}
void rzip_archive_close(rzip_archive_t *a)
{
if (!a)
return;
free(a->entries);
free(a->names);
free(a);
}
uint32_t rzip_archive_num_entries(const rzip_archive_t *a)
{
return a ? a->num_entries : 0;
}
const rzip_entry_t *rzip_archive_entry(const rzip_archive_t *a,
uint32_t index)
{
if (!a || index >= a->num_entries)
return NULL;
return &a->entries[index];
}
int rzip_archive_find(const rzip_archive_t *a, const char *name)
{
uint32_t i;
if (!a || !name)
return -1;
for (i = 0; i < a->num_entries; i++)
if (!a->entries[i].is_dir && !strcmp(a->entries[i].name, name))
return (int)i;
return -1;
}
/* True when the member's bytes lie inside the archive. */
static int rzip_entry_in_range(const rzip_archive_t *a, const rzip_entry_t *e)
{
return e->data_off <= a->len && e->csize <= a->len - e->data_off;
}
const uint8_t *rzip_archive_entry_view(const rzip_archive_t *a,
uint32_t index, size_t *len)
{
const rzip_entry_t *e;
if (!a || !a->data || index >= a->num_entries)
return NULL;
e = &a->entries[index];
if (e->is_dir || e->method != RZIP_METHOD_STORED
|| e->size > (uint64_t)((size_t)-1) || !rzip_entry_in_range(a, e))
return NULL;
if (len)
*len = (size_t)e->size;
return a->data + (size_t)e->data_off;
}
/* Inflates @e into @out, which holds @e->size bytes, from memory or in
* chunks through the callback. */
static int rzip_inflate(rzip_archive_t *a, const rzip_entry_t *e,
uint8_t *out)
{
void *inf;
uint8_t *chunk = NULL;
uint64_t in_off = e->data_off;
uint64_t in_left = e->csize;
size_t out_pos = 0;
int res = RZIP_ERROR_DATA;
if (!(inf = rinflate_new(-15)))
return RZIP_ERROR_MEM;
if (!a->data && !(chunk = (uint8_t*)malloc(READ_CHUNK)))
{
rinflate_free(inf);
return RZIP_ERROR_MEM;
}
rinflate_set_out(inf, out, (size_t)e->size);
for (;;)
{
size_t take, rd, wr;
int st;
if (a->data)
{
take = (size_t)in_left;
rinflate_set_in(inf, a->data + (size_t)in_off, take);
}
else
{
take = in_left < READ_CHUNK ? (size_t)in_left : READ_CHUNK;
if (take && a->read_cb(a->ud, in_off, chunk, take) != (int64_t)take)
{
res = RZIP_ERROR_IO;
break;
}
rinflate_set_in(inf, chunk, take);
}
in_off += take;
in_left -= take;
st = rinflate_process(inf, &rd, &wr);
out_pos += wr;
if (st == RDEFLATE_PROCESS_ERROR)
break;
if (st == RDEFLATE_PROCESS_END)
{
res = out_pos == (size_t)e->size ? RZIP_OK : RZIP_ERROR_DATA;
break;
}
/* more input wanted, and there is none */
if (!in_left)
break;
}
free(chunk);
rinflate_free(inf);
return res;
}
int rzip_archive_extract_into(rzip_archive_t *a, uint32_t index,
uint8_t *dst, size_t dst_size, size_t *out_len)
{
const rzip_entry_t *e;
int res;
if (!a || !dst || !out_len || index >= a->num_entries)
return RZIP_ERROR_PARAM;
e = &a->entries[index];
*out_len = 0;
if (e->is_dir)
return RZIP_ERROR_PARAM;
if (e->method != RZIP_METHOD_STORED && e->method != RZIP_METHOD_DEFLATE)
return RZIP_ERROR_UNSUPPORTED;
if (e->size > (uint64_t)((size_t)-1) || !rzip_entry_in_range(a, e))
return RZIP_ERROR_DATA;
if (e->method == RZIP_METHOD_STORED && e->csize != e->size)
return RZIP_ERROR_DATA;
if (e->size > (uint64_t)dst_size)
return RZIP_ERROR_PARAM;
if (e->method == RZIP_METHOD_STORED)
{
if (a->data)
memcpy(dst, a->data + (size_t)e->data_off, (size_t)e->size);
else if (e->size && a->read_cb(a->ud, e->data_off, dst,
(size_t)e->size) != (int64_t)e->size)
return RZIP_ERROR_IO;
res = RZIP_OK;
}
else
res = rzip_inflate(a, e, dst);
if (res == RZIP_OK
&& encoding_crc32(0, dst, (size_t)e->size) != e->crc)
res = RZIP_ERROR_CRC;
if (res != RZIP_OK)
return res;
*out_len = (size_t)e->size;
return RZIP_OK;
}
int rzip_archive_extract(rzip_archive_t *a, uint32_t index,
uint8_t **out, size_t *out_len)
{
const rzip_entry_t *e;
uint8_t *buf;
size_t size;
int res;
if (!a || !out || !out_len || index >= a->num_entries)
return RZIP_ERROR_PARAM;
e = &a->entries[index];
*out = NULL;
*out_len = 0;
if (e->is_dir)
return RZIP_ERROR_PARAM;
if (e->size > (uint64_t)((size_t)-1))
return RZIP_ERROR_DATA;
size = e->size ? (size_t)e->size : 1;
if (!(buf = (uint8_t*)malloc(size)))
return RZIP_ERROR_MEM;
if ((res = rzip_archive_extract_into(a, index, buf, size, out_len)) != RZIP_OK)
{
free(buf);
return res;
}
*out = buf;
return RZIP_OK;
}
/* ------------------------------------------------------------------ */
/* A deflated member read at any offset: see rzip_archive.h */
/* ------------------------------------------------------------------ */
#define RZIP_SEEK_WINDOW 32768
#define RZIP_SEEK_WORK (256 * 1024)
#define RZIP_SEEK_CACHE 8
typedef struct
{
uint64_t out_off; /* where in the member decoding can start */
uint64_t in_bit; /* and where that is in its compressed bytes */
uint8_t *window; /* the member's bytes before it, up to 32 KiB */
uint32_t window_len;
} rzip_seek_point_t;
typedef struct
{
uint8_t *data;
size_t len;
size_t cap;
uint32_t age;
int32_t point; /* the span from this point to the next; -1 none */
} rzip_seek_span_t;
struct rzip_seek
{
rzip_archive_t *a;
const rzip_entry_t *e;
rzip_seek_point_t *points;
uint32_t max_points;
uint32_t span;
/* What the builder has published. Points below num_points are never
* written again; state is 0 while there is more to index, 1 when the
* member is indexed to its end, negative when the stream is bad. */
retro_atomic_int_t num_points;
retro_atomic_int_t state;
/* The builder's. Output goes to b_buf after a 32 KiB prefix that
* holds the end of what came before, so the 32 KiB before any
* position are always in one piece. */
void *b_inf;
uint8_t *b_buf;
uint8_t *b_chunk; /* compressed bytes, when they are read in */
size_t b_fill;
uint64_t b_out;
uint64_t b_in_off; /* of the compressed bytes, how many are fed */
uint64_t b_in_base; /* and where the current input window starts */
size_t b_in_take;
uint32_t b_crc;
int b_in_set;
/* The reader's. */
void *r_inf;
uint8_t *r_chunk;
uint32_t r_clock;
rzip_seek_span_t r_span[RZIP_SEEK_CACHE];
};
rzip_seek_t *rzip_seek_new(rzip_archive_t *a, uint32_t index, uint32_t span)
{
rzip_seek_t *s;
const rzip_entry_t *e;
unsigned i;
if (!a || index >= a->num_entries)
return NULL;
e = &a->entries[index];
if ( e->is_dir || e->method != RZIP_METHOD_DEFLATE
|| !rzip_entry_in_range(a, e))
return NULL;
if (!span)
span = RZIP_SEEK_SPAN_DEFAULT;
if (span < RZIP_SEEK_WINDOW)
span = RZIP_SEEK_WINDOW;
/* a point is at least a span after the one before it */
if (e->size / span >= 0x7ffffff0u)
return NULL;
if (!(s = (rzip_seek_t*)calloc(1, sizeof(*s))))
return NULL;
s->a = a;
s->e = e;
s->span = span;
s->max_points = (uint32_t)(e->size / span) + 2;
s->points = (rzip_seek_point_t*)calloc(s->max_points, sizeof(*s->points));
s->b_inf = rinflate_new(-15);
s->r_inf = rinflate_new(-15);
s->b_buf = (uint8_t*)malloc(RZIP_SEEK_WINDOW + RZIP_SEEK_WORK);
if (!a->data)
{
s->b_chunk = (uint8_t*)malloc(READ_CHUNK);
s->r_chunk = (uint8_t*)malloc(READ_CHUNK);
}
if ( !s->points || !s->b_inf || !s->r_inf || !s->b_buf
|| (!a->data && (!s->b_chunk || !s->r_chunk)))
{
rzip_seek_free(s);
return NULL;
}
for (i = 0; i < RZIP_SEEK_CACHE; i++)
s->r_span[i].point = -1;
rinflate_set_stop_at_block(s->b_inf, 1);
/* the first point is the start of the stream */
retro_atomic_int_init(&s->num_points, 1);
retro_atomic_int_init(&s->state, 0);
return s;
}
void rzip_seek_free(rzip_seek_t *s)
{
unsigned i;
if (!s)
return;
if (s->points)
{
for (i = 0; i < s->max_points; i++)
free(s->points[i].window);
free(s->points);
}
for (i = 0; i < RZIP_SEEK_CACHE; i++)
free(s->r_span[i].data);
if (s->b_inf)
rinflate_free(s->b_inf);
if (s->r_inf)
rinflate_free(s->r_inf);
free(s->b_buf);
free(s->b_chunk);
free(s->r_chunk);
free(s);
}
uint64_t rzip_seek_size(const rzip_seek_t *s)
{
return s ? s->e->size : 0;
}
uint64_t rzip_seek_covered(const rzip_seek_t *s)
{
/* state after the count: a count read first is never ahead of it */
int n;
if (!s)
return 0;
n = retro_atomic_load_acquire_int((retro_atomic_int_t*)&s->num_points);
if (retro_atomic_load_acquire_int((retro_atomic_int_t*)&s->state) == 1)
return s->e->size;
return s->points[n - 1].out_off;
}
int rzip_seek_state(const rzip_seek_t *s)
{
if (!s)
return RZIP_ERROR_PARAM;
return retro_atomic_load_acquire_int((retro_atomic_int_t*)&s->state);
}
int rzip_seek_build(rzip_seek_t *s, uint64_t upto)
{
const rzip_entry_t *e;
int n;
int state;
if (!s)
return RZIP_ERROR_PARAM;
if ((state = retro_atomic_load_acquire_int(&s->state)) != 0)
return state;
e = s->e;
n = retro_atomic_load_acquire_int(&s->num_points);
while (s->points[n - 1].out_off < upto)
{
size_t rd = 0;
size_t wr = 0;
int st;
/* More compressed bytes: all of them at once from a mapping, a
* chunk at a time through the callback. */
if (s->a->data)
{
if (!s->b_in_set)
{
rinflate_set_in(s->b_inf, s->a->data + (size_t)e->data_off,
(size_t)e->csize);
s->b_in_set = 1;
s->b_in_take = (size_t)e->csize;
s->b_in_off = e->csize;
}
}
else if (!s->b_in_set)
{
size_t take = (e->csize - s->b_in_off) < READ_CHUNK
? (size_t)(e->csize - s->b_in_off) : READ_CHUNK;
if (take && s->a->read_cb(s->a->ud, e->data_off + s->b_in_off,
s->b_chunk, take) != (int64_t)take)
{
retro_atomic_store_release_int(&s->state, RZIP_ERROR_IO);
return RZIP_ERROR_IO;
}
rinflate_set_in(s->b_inf, s->b_chunk, take);
s->b_in_base = s->b_in_off;
s->b_in_off += take;
s->b_in_take = take;
s->b_in_set = 1;
}
rinflate_set_out(s->b_inf, s->b_buf + RZIP_SEEK_WINDOW + s->b_fill,
RZIP_SEEK_WORK - s->b_fill);
st = rinflate_process(s->b_inf, &rd, &wr);
if (wr)
{
s->b_crc = encoding_crc32(s->b_crc,
s->b_buf + RZIP_SEEK_WINDOW + s->b_fill, wr);
s->b_fill += wr;
s->b_out += wr;
}
if (st == RDEFLATE_PROCESS_ERROR || s->b_out > e->size)
{
retro_atomic_store_release_int(&s->state, RZIP_ERROR_DATA);
return RZIP_ERROR_DATA;
}
if (st == RDEFLATE_PROCESS_END)
{
state = (s->b_out == e->size && s->b_crc == e->crc)
? 1 : (s->b_out == e->size ? RZIP_ERROR_CRC : RZIP_ERROR_DATA);
retro_atomic_store_release_int(&s->state, state);
return state;
}
if ( st == RDEFLATE_PROCESS_BLOCK
&& s->b_out - s->points[n - 1].out_off >= s->span
&& (uint32_t)n < s->max_points)
{
/* A place decoding can start from: here, with what came before */
rzip_seek_point_t *pt = &s->points[n];
uint32_t wl = s->b_out < RZIP_SEEK_WINDOW
? (uint32_t)s->b_out : RZIP_SEEK_WINDOW;
if (!(pt->window = (uint8_t*)malloc(wl)))
{
retro_atomic_store_release_int(&s->state, RZIP_ERROR_MEM);
return RZIP_ERROR_MEM;
}
memcpy(pt->window,
s->b_buf + RZIP_SEEK_WINDOW + s->b_fill - wl, wl);
pt->window_len = wl;
pt->out_off = s->b_out;
pt->in_bit = s->b_in_base * 8 + rinflate_tell_bits(s->b_inf);
n++;
retro_atomic_store_release_int(&s->num_points, n);
}
/* The buffer is full: its last 32 KiB go in front, and it is
* filled from its start again. */
if (s->b_fill == RZIP_SEEK_WORK)
{
memmove(s->b_buf, s->b_buf + RZIP_SEEK_WORK, RZIP_SEEK_WINDOW);
s->b_fill = 0;
}
/* All of the input window taken: the next chunk, if there is one */
if (st == RDEFLATE_PROCESS_NEXT && !wr && rd == 0)
{
if (s->a->data || s->b_in_off >= e->csize)
{
/* it wants more and there is none */
retro_atomic_store_release_int(&s->state, RZIP_ERROR_DATA);
return RZIP_ERROR_DATA;
}
s->b_in_set = 0;
}
}
return 0;
}
/* The span that starts at point @p, decoded: from the cache, or decoded
* now in place of the span used longest ago. */
static int rzip_seek_span(rzip_seek_t *s, uint32_t p, uint64_t end,
const uint8_t **data)
{
const rzip_seek_point_t *pt = &s->points[p];
const rzip_entry_t *e = s->e;
size_t len = (size_t)(end - pt->out_off);
rzip_seek_span_t *slot = &s->r_span[0];
uint64_t in_off;
size_t out_pos = 0;
unsigned i;
for (i = 0; i < RZIP_SEEK_CACHE; i++)
{
if (s->r_span[i].point == (int32_t)p)
{
s->r_span[i].age = ++s->r_clock;
*data = s->r_span[i].data;
return RZIP_OK;
}
if (s->r_span[i].age < slot->age)
slot = &s->r_span[i];
}
slot->point = -1;
if (len > slot->cap)
{
free(slot->data);
slot->cap = 0;
if (!(slot->data = (uint8_t*)malloc(len)))
return RZIP_ERROR_MEM;
slot->cap = len;
}
rinflate_reset(s->r_inf, -15);
if (pt->window_len)
rinflate_set_dictionary(s->r_inf, pt->window, pt->window_len);
rinflate_set_start_bit(s->r_inf, (int)(pt->in_bit & 7));
rinflate_set_out(s->r_inf, slot->data, len);
in_off = pt->in_bit >> 3;
if (in_off > e->csize)
return RZIP_ERROR_DATA;
while (out_pos < len)
{
size_t take, rd = 0, wr = 0;
int st;
if (s->a->data)
{
take = (size_t)(e->csize - in_off);
rinflate_set_in(s->r_inf,
s->a->data + (size_t)(e->data_off + in_off), take);
}
else
{
take = (e->csize - in_off) < READ_CHUNK
? (size_t)(e->csize - in_off) : READ_CHUNK;
if (take && s->a->read_cb(s->a->ud, e->data_off + in_off,
s->r_chunk, take) != (int64_t)take)
return RZIP_ERROR_IO;
rinflate_set_in(s->r_inf, s->r_chunk, take);
}
in_off += take;
st = rinflate_process(s->r_inf, &rd, &wr);
out_pos += wr;
if (st == RDEFLATE_PROCESS_ERROR)
return RZIP_ERROR_DATA;
if (out_pos >= len)
break;
/* the end of the stream, or of its bytes, before the span's */
if (st == RDEFLATE_PROCESS_END || in_off >= e->csize)
return RZIP_ERROR_DATA;
}
slot->len = len;
slot->point = (int32_t)p;
slot->age = ++s->r_clock;
*data = slot->data;
return RZIP_OK;
}
/* The span that holds @offset, decoded: its bytes, where it starts and
* where it ends. RZIP_ERROR_PARAM when the index has not got that far. */
static int rzip_seek_span_at(rzip_seek_t *s, uint64_t offset,
const uint8_t **span, uint64_t *start, uint64_t *end)
{
uint32_t lo = 0;
uint32_t hi;
int n;
int complete;
/* the count, then whether it is the last: see rzip_seek_covered() */
n = retro_atomic_load_acquire_int(&s->num_points);
complete = retro_atomic_load_acquire_int(&s->state) == 1;
if (complete)
n = retro_atomic_load_acquire_int(&s->num_points);
hi = (uint32_t)n;
/* the last point at or before the offset */
while (hi - lo > 1)
{
uint32_t mid = lo + (hi - lo) / 2;
if (s->points[mid].out_off <= offset)
lo = mid;
else
hi = mid;
}
if (lo + 1 < (uint32_t)n)
*end = s->points[lo + 1].out_off;
else if (complete)
*end = s->e->size;
else
return RZIP_ERROR_PARAM;
*start = s->points[lo].out_off;
return rzip_seek_span(s, lo, *end, span);
}
int rzip_seek_read(rzip_seek_t *s, uint64_t offset, uint8_t *dst, size_t len)
{
if (!s || (!dst && len))
return RZIP_ERROR_PARAM;
if (offset > s->e->size || len > s->e->size - offset)
return RZIP_ERROR_PARAM;
while (len)
{
const uint8_t *span;
uint64_t start, end;
size_t take;
int res;
if ((res = rzip_seek_span_at(s, offset, &span, &start, &end)) != RZIP_OK)
return res;
take = (size_t)(end - offset) < len ? (size_t)(end - offset) : len;
memcpy(dst, span + (size_t)(offset - start), take);
dst += take;
offset += take;
len -= take;
}
return RZIP_OK;
}
int rzip_seek_view(rzip_seek_t *s, uint64_t offset, size_t len,
const uint8_t **data)
{
const uint8_t *span;
uint64_t start, end;
int res;
if (!s || !data)
return RZIP_ERROR_PARAM;
*data = NULL;
if (!len || offset >= s->e->size || len > s->e->size - offset)
return RZIP_ERROR_PARAM;
if ((res = rzip_seek_span_at(s, offset, &span, &start, &end)) != RZIP_OK)
return res;
if (len <= end - offset)
*data = span + (size_t)(offset - start);
return RZIP_OK;
}
+4 -4
View File
@@ -933,10 +933,10 @@ int sha1_calculate(const char *path, char *result)
goto error;
sprintf(result, "%08X%08X%08X%08X%08X",
sha.digest[0],
sha.digest[1],
sha.digest[2],
sha.digest[3], sha.digest[4]);
(unsigned)sha.digest[0],
(unsigned)sha.digest[1],
(unsigned)sha.digest[2],
(unsigned)sha.digest[3], (unsigned)sha.digest[4]);
filestream_close(fd);
return 0;
+100
View File
@@ -117,6 +117,28 @@ typedef struct r7z_archive r7z_archive_t;
int r7z_archive_open(r7z_archive_t **out,
const uint8_t *data, size_t len);
/* Positioned read of @len bytes at @off into @dst; returns the bytes
* read, 0 at the end, negative on error. */
typedef int64_t (*r7z_read_t)(void *ud, uint64_t off, void *dst,
size_t len);
/**
* r7z_archive_open_read:
* @out : receives the opened archive
* @len : length of the archive file
* @read_cb : positioned reads of it
* @ud : handed to @read_cb
*
* The same, for an archive that is not in memory and is not to be put
* there whole: the header is read at open, and a folder's packed bytes
* are read in when it is decoded and let go of when it has been. A
* member is extracted as it is from an archive in memory.
*
* Returns: R7Z_OK, or a negative R7Z_ERROR_* code.
*/
int r7z_archive_open_read(r7z_archive_t **out, uint64_t len,
r7z_read_t read_cb, void *ud);
/**
* r7z_archive_close:
* @a : archive, may be NULL
@@ -192,6 +214,84 @@ int r7z_archive_extract_slice(r7z_archive_t *a, uint32_t index,
int r7z_archive_extract(r7z_archive_t *a, uint32_t index,
uint8_t **out, size_t *out_len);
/**
* r7z_archive_extract_detach:
*
* The same as r7z_archive_extract(), except that an entry which is its
* folder's entire output - every member of a non-solid archive - is
* handed over as the decoded folder buffer itself, with nothing copied
* and nothing left in the folder cache. Any other entry extracts
* exactly as r7z_archive_extract() does.
*/
int r7z_archive_extract_detach(r7z_archive_t *a, uint32_t index,
uint8_t **out, size_t *out_len);
/**
* r7z_archive_entry_borrow:
*
* The entry's bytes inside the decoded folder cache, decoding the folder
* first if it is not the one held. Nothing is copied: the pointer is
* owned by the archive and stays valid until a different folder is
* decoded, the folder is detached, or the archive is closed. The CRC is
* checked on every call, so a caller keeping the pointer calls once.
* A solid archive is one folder, so every member of it can be borrowed
* at the same time.
*/
int r7z_archive_entry_borrow(r7z_archive_t *a, uint32_t index,
const uint8_t **out, size_t *out_len);
/**
* r7z_archive_folder_size:
*
* The decoded length of folder @folder (an entry's is in its
* r7z_entry_t), or 0 if there is none. What decoding it will take.
*/
uint64_t r7z_archive_folder_size(const r7z_archive_t *a, uint32_t folder);
/**
* r7z_archive_decode_step:
* @a : opened archive
* @index : an entry of the folder to decode
*
* One slice of decoding the folder that holds @index: for a thread that
* decodes a large folder while another reads what has been decoded so
* far, with r7z_archive_decode_peek(). Call it until it returns
* something other than R7Z_PENDING. On R7Z_OK the folder is decoded,
* checked against its CRC and held, as r7z_archive_entry_borrow()
* leaves it.
*
* One thread, and nothing else done with @a meanwhile but
* r7z_archive_decode_peek() from other threads: an archive decodes one
* folder at a time. A caller that wants another folder at the same time
* opens the archive a second time for it.
*
* Returns: R7Z_PENDING, R7Z_OK, or a negative R7Z_ERROR_* code.
*/
int r7z_archive_decode_step(r7z_archive_t *a, uint32_t index);
/**
* r7z_archive_decode_peek:
* @a : the archive r7z_archive_decode_step() is called on
* @index : an entry of the folder being decoded
* @data : receives where the entry's bytes are, or NULL
* @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.
*
* Any thread.
*
* Returns: R7Z_OK, or a negative R7Z_ERROR_* code.
*/
int r7z_archive_decode_peek(const r7z_archive_t *a, uint32_t index,
const uint8_t **data, size_t *avail);
RETRO_END_DECLS
#endif
+9
View File
@@ -165,6 +165,10 @@ typedef void (*resampler_process_t)(void *_data, struct resampler_data *data);
* on the thread that resamples. */
typedef void (*resampler_reset_t)(void *data);
/* A new instance over data's tables with a fresh stream of its own,
* freed like any other. */
typedef void *(*resampler_sibling_t)(void *data);
/* What a backend reads of the settings the frontend exposes. One that
* reads neither leaves this zero, and the frontend has no control to
* offer for it. */
@@ -193,6 +197,10 @@ typedef struct retro_resampler
/* RESAMPLER_CAP_*, zero for a backend that reads neither. */
unsigned caps;
/* Optional: NULL where the backend has no tables worth sharing, and
* the caller makes a fresh instance instead. */
resampler_sibling_t sibling;
} retro_resampler_t;
typedef struct audio_frame_float
@@ -216,6 +224,7 @@ typedef struct retro_resampler_int16
void (*process)(void *, struct resampler_data_int16 *);
void (*reset)(void *);
void (*free)(void *);
void *(*sibling)(void *);
} retro_resampler_int16_t;
/* Builds the int16 counterpart of @short_ident - the short_ident of a
+4
View File
@@ -78,6 +78,10 @@ void sinc_resampler_int16_reset(void *re);
void sinc_resampler_int16_free(void *re);
/* A fresh stream over re's tables, freed like any instance; the tables
* go with the last one. */
void *sinc_resampler_int16_sibling(void *re);
#ifdef __cplusplus
}
#endif
+8
View File
@@ -104,10 +104,18 @@
#define NSAlertStyleWarning NSWarningAlertStyle
#define NSEventModifierFlagCapsLock NSAlphaShiftKeyMask
#define NSControlSizeRegular NSRegularControlSize
#define NSEventMaskLeftMouseDragged NSLeftMouseDraggedMask
#define NSEventMaskLeftMouseUp NSLeftMouseUpMask
#define NSOpenGLContextParameterSwapInterval NSOpenGLCPSwapInterval
#else
#define HAS_MACOSX_10_12 1
#endif
/* Names the 10.14 SDK gave to older AppKit constants: the same values. */
#if !defined(MAC_OS_X_VERSION_MAX_ALLOWED) || MAC_OS_X_VERSION_MAX_ALLOWED < 101400
#define NSBezelStyleRounded NSRoundedBezelStyle
#endif
/* Window-related names the AppKit glue needs whatever SDK it is built
* against. NSFullScreenWindowMask (10.7) is an enum, so a #define is
* not enough to know whether the SDK has it; the bit is spelled out
+14
View File
@@ -100,6 +100,20 @@ const char *utf8skip(const char *str, size_t chars);
**/
uint32_t utf8_walk(const char **string);
/* utf8_walk with its ASCII case inline, for loops that walk text a
* codepoint at a time every frame: a call per character is most of
* what such a loop costs when the text is ASCII. */
static INLINE uint32_t utf8_walk_inline(const char **string)
{
const uint8_t *s = (const uint8_t*)*string;
if (*s < 0x80)
{
*string = (const char*)(s + 1);
return *s;
}
return utf8_walk(string);
}
/**
* utf16_to_char_string:
**/
+2
View File
@@ -260,6 +260,7 @@ int file_archive_compressed_read(
const struct file_archive_file_backend* file_archive_get_zlib_file_backend(void);
const struct file_archive_file_backend* file_archive_get_7z_file_backend(void);
const struct file_archive_file_backend* file_archive_get_zstd_file_backend(void);
const struct file_archive_file_backend* file_archive_get_rar_file_backend(void);
const struct file_archive_file_backend* file_archive_get_file_backend(const char *path);
@@ -287,6 +288,7 @@ uint32_t file_archive_get_file_crc32_and_size(const char *path, uint64_t *size);
extern const struct file_archive_file_backend zlib_backend;
extern const struct file_archive_file_backend sevenzip_backend;
extern const struct file_archive_file_backend zstd_backend;
extern const struct file_archive_file_backend rar_backend;
/* ---- incremental archive entry source ----
*
+150
View File
@@ -163,9 +163,148 @@ struct texture_compressed
enum image_type_enum image_texture_get_type(const char *path);
/* ---- The still loader ----------------------------------------------
* One decoder for every still the frontend or a core takes from a
* file: the bytes are the caller's, read whole or still arriving, and
* the decode runs in steps that stop at a time budget, at an abort
* hook, or at the byte frontier. image_texture_load() is this loader
* run to completion in one call. */
/* What the caller takes of a still, asked of it once by whoever knows
* (the video driver, a core's own renderer) and handed down. Zero is
* the ordinary 8-bit image, ARGB words. */
typedef struct
{
/* Memory-order R,G,B,A rather than ARGB words */
bool rgba;
/* XRGB2101010 where the file has more than 8 bits a channel */
bool want_10bit;
/* Linear scRGB half floats where the source is HDR (video stills) */
bool want_fp16;
/* A GPU-native compressed payload (BCn) as it lies in the file,
* rather than its CPU decode; the caller then samples it as such or
* decodes it later with image_texture_realize_rgba() */
bool want_compressed;
} image_texture_request_t;
typedef struct image_loader image_loader_t;
enum image_loader_state
{
IMAGE_LOADER_ERROR = -1,
IMAGE_LOADER_RUNNING, /* more steps to take */
IMAGE_LOADER_WAIT, /* needs bytes not yet available */
IMAGE_LOADER_DONE /* image_loader_finish() has the image */
};
/* Whether a decode of @type may start on the first @avail bytes of
* a file still being read: the header is resident for the decoders
* that paint from a prefix (PNG, JPEG, TGA, BMP, the video stills),
* the still's chunk is whole for WEBP. False means wait for the whole
* file. */
bool image_loader_ready(enum image_type_enum type,
const void *buf, size_t avail);
/* A loader for a still of @type. @req may be NULL for the ordinary
* image. NULL when out of memory. */
image_loader_t *image_loader_new(enum image_type_enum type,
const image_texture_request_t *req);
/* The file's bytes: @buf of @len bytes once fully read, of which
* @avail are resident now (@len for a whole file). The buffer is the
* caller's and stays where it is until the loader is freed. False
* when the decoder refused the start. */
bool image_loader_start(image_loader_t *l, const void *buf, size_t len,
size_t avail);
/* More of the file has arrived: the first @avail bytes are resident
* ((size_t)-1: all of them). */
void image_loader_set_avail(image_loader_t *l, size_t avail);
/* Asked between steps of the decode: true abandons it, and the loader
* reports IMAGE_LOADER_ERROR. For decodes that must stop promptly at
* shutdown or once their result is no longer wanted. */
void image_loader_set_abort(image_loader_t *l,
bool (*should_abort)(void *ud), void *ud);
/* Decode until @now's clock reaches @deadline, at least one iteration
* and, between a transfer and its pixels, never both in one step;
* @now NULL runs until the decode is done, aborts, or wants bytes. */
enum image_loader_state image_loader_step(image_loader_t *l,
int64_t (*now)(void), int64_t deadline);
/* The decoded image, the loader's no more: @img's pixels or compressed
* payload are the caller's to free with image_texture_free(). Only
* after IMAGE_LOADER_DONE. */
bool image_loader_finish(image_loader_t *l, struct texture_image *img);
/* Whether the loader's PNG is animated: 1 yes, 0 a still, -1 unknown
* (not a PNG, or the file is not wholly resident). */
int image_loader_png_probe(const image_loader_t *l);
/* The animation stream a video still's decode opened, detached for
* the caller to play on; NULL when there is none. The stream borrows
* the loader's buffer, which the caller keeps alive for it. */
void *image_loader_detach_anim_stream(image_loader_t *l);
void image_loader_free(image_loader_t *l);
/* Resample a decoded still in place: a whole-factor upscale when an
* edge is under @upscale_threshold, then a box reduction to keep the
* longer edge within @downscale_cap; 0 skips either. Half-float
* images are left as they are. In formats/image_texture_scale.c,
* which brings the scaler with it. */
bool image_texture_scale(struct texture_image *img,
unsigned upscale_threshold, unsigned downscale_cap);
bool image_texture_load_buffer(struct texture_image *img,
enum image_type_enum type, void *s, size_t len);
/* The loader run to completion over a whole buffer or a file, with
* @req naming what the caller takes (NULL: the ordinary image). The
* abort hook is asked between steps; it may be NULL. False leaves
* @img empty. */
bool image_texture_load_buffer_request(struct texture_image *img,
enum image_type_enum type, const void *buf, size_t len,
const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud);
bool image_texture_load_request(struct texture_image *img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud);
/* image_texture_load_request answering, from the bytes it read, whether
* the file is an animated PNG: 1 yes, 0 a still, -1 not a PNG or no
* decode. Spares a caller that keeps an animation's bytes a second
* read of every still to ask. @png_probe may be NULL. */
bool image_texture_load_request_ex(struct texture_image *img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud, int *png_probe);
/* Run @fn for every index below @n, across the cores at once where
* there are threads: on Apple over the dispatch pool, elsewhere on
* threads of its own with the caller working alongside, and in order
* on one thread where there is only one. Returns once every call has.
* In formats/image_texture_set.c. */
void image_texture_set_run(unsigned n,
void (*fn)(unsigned i, void *ud), void *ud);
/* image_texture_set_run with @thread_done called on each thread of
* the set's own as it finishes, for state it kept per thread - the
* data_transfer pool above all, which a thread that exits with it
* full would leave resident for good. Not called on the caller's
* thread, nor on a dispatch pool's, which live on. May be NULL. */
void image_texture_set_run_ex(unsigned n,
void (*fn)(unsigned i, void *ud), void *ud,
void (*thread_done)(void));
/* Decode the @n files @paths name into @imgs together, each as
* image_texture_load_request does with @req; a path that is NULL,
* empty or unreadable leaves its image empty. Returns how many
* decoded. In formats/image_texture_set.c. */
unsigned image_texture_load_set(const char *const *paths,
struct texture_image *imgs, unsigned n,
const image_texture_request_t *req);
/* ->pix10 is an ask on the way in and an answer on the way out: set
* it before the call to have a decoder that can emit XRGB2101010 do
* so, and read it after to find out whether it did. Clear on entry
@@ -204,6 +343,17 @@ void image_texture_narrow_10bit(struct texture_image *img);
* scratch cannot be allocated. */
bool image_texture_tile_gx(struct texture_image *img);
/* Write @width x @height linear 32-bit texels, @src (ARGB8888 words,
* or memory-order R,G,B,A when @rgba), to @dst as GX RGBA8 tiles of a
* texture rounded up to multiples of 4 a side, the padding clear: the
* layout image_texture_tile_gx() makes, but in a buffer of its own and
* keeping every texel. @dst holds image_texture_tile_gx_size() bytes. */
void image_texture_tile_gx_copy(uint16_t *dst, const uint32_t *src,
unsigned width, unsigned height, bool rgba);
/* The bytes image_texture_tile_gx_copy() writes for @width x @height */
size_t image_texture_tile_gx_size(unsigned width, unsigned height);
/* Image transfer */
void image_transfer_free(void *data, enum image_type_enum type);
+71
View File
@@ -0,0 +1,71 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (image_yuv_blit.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_FORMAT_IMAGE_YUV_BLIT_H__
#define __LIBRETRO_SDK_FORMAT_IMAGE_YUV_BLIT_H__
#include <stdint.h>
#include <retro_common_api.h>
RETRO_BEGIN_DECLS
/* 8-bit YCbCr 4:2:0 to 32-bit RGB, in fixed point. Strides are in
* samples (bytes for the planes, pixels for the destination). A frame
* of odd width or height takes its last column and row from the
* chroma sample they share with their neighbour. Rows are independent
* in pairs, so a band starting on an even row converts on its own
* (image_blit_bands with an align of 2). */
/* Chroma order and colour space of the source, and the word order of
* the destination. The default is BT.601 limited range into XRGB8888,
* which is what a camera preview and the libretro frontend speak. */
#define IMAGE_YUV_FLAG_VU (1 << 0) /* interleaved plane is V,U (NV21) */
#define IMAGE_YUV_FLAG_FULL_RANGE (1 << 1) /* Y 0..255, not 16..235 */
#define IMAGE_YUV_FLAG_BT709 (1 << 2) /* HD matrix, not BT.601 */
#define IMAGE_YUV_FLAG_RGBA (1 << 3) /* R in the low byte, A set */
/* Any 4:2:0 layout: Cb and Cr each by their own base pointer and row
* stride, with @chroma_step bytes between horizontally adjacent
* samples of a plane (1 for I420, 2 for NV12/NV21, whose two planes
* are the one interleaved plane at offsets 0 and 1). */
void image_yuv_420_to_rgb32(uint32_t *dst, unsigned dst_stride,
const uint8_t *y, unsigned y_stride,
const uint8_t *cb, unsigned cb_stride,
const uint8_t *cr, unsigned cr_stride,
unsigned chroma_step, unsigned w, unsigned h, unsigned flags);
/* Y plane and one interleaved chroma plane (NV12; NV21 with FLAG_VU). */
void image_yuv_nv12_to_rgb32(uint32_t *dst, unsigned dst_stride,
const uint8_t *y, unsigned y_stride,
const uint8_t *uv, unsigned uv_stride,
unsigned w, unsigned h, unsigned flags);
/* Three planes (I420; FLAG_VU swaps the two chroma planes). */
void image_yuv_i420_to_rgb32(uint32_t *dst, unsigned dst_stride,
const uint8_t *y, unsigned y_stride,
const uint8_t *u, unsigned u_stride,
const uint8_t *v, unsigned v_stride,
unsigned w, unsigned h, unsigned flags);
RETRO_END_DECLS
#endif
+36
View File
@@ -24,6 +24,8 @@
#define _LIBRETRO_SDK_VIDEO_FRAME_H
#include <stdint.h>
#include <string.h>
#include <boolean.h>
#include <retro_common_api.h>
#include <retro_inline.h>
@@ -207,6 +209,40 @@ static INLINE void video_frame_convert_rgba_to_bgr(
}
}
/* Copy w x h 32-bit pixels into a dst_w x dst_h buffer of B,G,R,X
* bytes with a row pitch of dst_w * 4, keeping the source row order.
* src_rgba: source bytes are R,G,B,A and are swapped; otherwise they
* are already B,G,R,A/X and rows are copied as-is. Anything outside
* the w x h source region is cleared to black. */
static INLINE void video_frame_copy_to_bgrx(
uint8_t *dst, unsigned dst_w, unsigned dst_h,
const uint8_t *src, size_t src_pitch,
unsigned w, unsigned h, bool src_rgba)
{
unsigned x, y;
if (w > dst_w)
w = dst_w;
if (h > dst_h)
h = dst_h;
if (w < dst_w || h < dst_h)
memset(dst, 0, (size_t)dst_w * dst_h * 4);
for (y = 0; y < h; y++, dst += (size_t)dst_w * 4, src += src_pitch)
{
if (!src_rgba)
memcpy(dst, src, (size_t)w * 4);
else
for (x = 0; x < w; x++)
{
dst[4 * x + 0] = src[4 * x + 2];
dst[4 * x + 1] = src[4 * x + 1];
dst[4 * x + 2] = src[4 * x + 0];
dst[4 * x + 3] = 0xff;
}
}
}
static INLINE void video_pixel_frame_scale(
struct scaler_ctx *scaler,
void *output, const void *data,
+219
View File
@@ -0,0 +1,219 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (rjni.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_RJNI_H__
#define __LIBRETRO_SDK_RJNI_H__
#include <jni.h>
#include <retro_common_api.h>
#include <retro_atomic.h>
/* Process-wide JNI plumbing shared by every JNI user in the tree: one
* registered JavaVM, one per-thread JNIEnv cached in destructor-capable
* TLS so a thread this layer attached is detached again when it exits
* (an attached thread exiting undetached aborts the process on Android),
* and once-caches for the class / method / field lookups, which cross
* into the VM and are the expensive part of a JNI call site. Everything
* is lock-free: caches are CAS-published, losers discard their own. */
RETRO_BEGIN_DECLS
/* A once-cache slot for a jclass global reference, a jmethodID or a
* jfieldID. Zero-initialise (static storage) and hand the same slot to
* every lookup of the same thing. */
typedef struct rjni_ref
{
retro_atomic_ptr_t p;
} rjni_ref;
/* Register the process JavaVM. First registration wins; later calls
* with a different VM are ignored. Safe from any thread. */
void rjni_set_vm(JavaVM *vm);
JavaVM *rjni_vm(void);
/* The calling thread's JNIEnv, attaching it to the VM on first use.
* Returns NULL if no VM is registered or the attach fails. A thread
* already attached by the VM (a Java-created thread) is recognised and
* left attached at exit; only threads this function attached are
* detached by the TLS destructor. */
JNIEnv *rjni_env(void);
/* Nonzero, after describing and clearing it, if an exception was
* pending. Uses ExceptionCheck, so no local reference is created. */
int rjni_exception_clear(JNIEnv *env);
/* FindClass + NewGlobalRef once per slot; the global reference is held
* for process lifetime. Returns NULL (exception cleared) on failure,
* which is not cached, so a later call retries. */
jclass rjni_class(rjni_ref *cache, JNIEnv *env, const char *name);
/* As rjni_class, but resolved through loader.loadClass(name) so
* application classes resolve from natively-attached threads, where
* FindClass only sees the system class loader. @name is dot-separated
* here, as loadClass expects. */
jclass rjni_class_from_loader(rjni_ref *cache, JNIEnv *env,
jobject loader, const char *name);
jmethodID rjni_method(rjni_ref *cache, JNIEnv *env, jclass cls,
const char *name, const char *sig);
jmethodID rjni_method_static(rjni_ref *cache, JNIEnv *env, jclass cls,
const char *name, const char *sig);
jfieldID rjni_field(rjni_ref *cache, JNIEnv *env, jclass cls,
const char *name, const char *sig);
jfieldID rjni_field_static(rjni_ref *cache, JNIEnv *env, jclass cls,
const char *name, const char *sig);
RETRO_END_DECLS
#ifndef __cplusplus
/* Thin call wrappers for C translation units, each a JNI call followed
* by an exception describe-and-clear. (*env)-> syntax is C-only; C++
* callers use env->X() directly. */
/* Every macro below is wrapped in do/while(0). Without it the trailing
* JNI_EXCEPTION escapes any unbraced guard at the call site, so
*
* if (env != NULL)
* CALL_BOOLEAN_METHOD(env, ...);
*
* expanded to a guarded call followed by an *unguarded* exception check
* that dereferences env regardless - a null dereference on exactly the
* path the guard existed to protect. */
#define JNI_EXCEPTION(env) \
do { \
if ((*env)->ExceptionCheck(env)) \
{ \
(*env)->ExceptionDescribe(env); \
(*env)->ExceptionClear(env); \
} \
} while (0)
#define FIND_CLASS(env, var, classname) \
do { \
var = (*env)->FindClass(env, classname); \
JNI_EXCEPTION(env); \
} while (0)
#define GET_OBJECT_CLASS(env, var, clazz_obj) \
do { \
var = (*env)->GetObjectClass(env, clazz_obj); \
JNI_EXCEPTION(env); \
} while (0)
#define GET_FIELD_ID(env, var, clazz, fieldName, fieldDescriptor) \
do { \
var = (*env)->GetFieldID(env, clazz, fieldName, fieldDescriptor); \
JNI_EXCEPTION(env); \
} while (0)
#define GET_METHOD_ID(env, var, clazz, methodName, fieldDescriptor) \
do { \
var = (*env)->GetMethodID(env, clazz, methodName, fieldDescriptor); \
JNI_EXCEPTION(env); \
} while (0)
#define GET_STATIC_METHOD_ID(env, var, clazz, methodName, fieldDescriptor) \
do { \
var = (*env)->GetStaticMethodID(env, clazz, methodName, fieldDescriptor); \
JNI_EXCEPTION(env); \
} while (0)
#define CALL_OBJ_METHOD(env, var, clazz_obj, methodId) \
do { \
var = (*env)->CallObjectMethod(env, clazz_obj, methodId); \
JNI_EXCEPTION(env); \
} while (0)
#define CALL_OBJ_STATIC_METHOD(env, var, clazz, methodId) \
do { \
var = (*env)->CallStaticObjectMethod(env, clazz, methodId); \
JNI_EXCEPTION(env); \
} while (0)
#define CALL_OBJ_STATIC_METHOD_PARAM(env, var, clazz, methodId, ...) \
do { \
var = (*env)->CallStaticObjectMethod(env, clazz, methodId, __VA_ARGS__); \
JNI_EXCEPTION(env); \
} while (0)
#define CALL_OBJ_METHOD_PARAM(env, var, clazz_obj, methodId, ...) \
do { \
var = (*env)->CallObjectMethod(env, clazz_obj, methodId, __VA_ARGS__); \
JNI_EXCEPTION(env); \
} while (0)
#define CALL_VOID_METHOD(env, clazz_obj, methodId) \
do { \
(*env)->CallVoidMethod(env, clazz_obj, methodId); \
JNI_EXCEPTION(env); \
} while (0)
#define CALL_VOID_METHOD_PARAM(env, clazz_obj, methodId, ...) \
do { \
(*env)->CallVoidMethod(env, clazz_obj, methodId, __VA_ARGS__); \
JNI_EXCEPTION(env); \
} while (0)
#define CALL_BOOLEAN_METHOD(env, var, clazz_obj, methodId) \
do { \
var = (*env)->CallBooleanMethod(env, clazz_obj, methodId); \
JNI_EXCEPTION(env); \
} while (0)
#define CALL_BOOLEAN_METHOD_PARAM(env, var, clazz_obj, methodId, ...) \
do { \
var = (*env)->CallBooleanMethod(env, clazz_obj, methodId, __VA_ARGS__); \
JNI_EXCEPTION(env); \
} while (0)
#define CALL_DOUBLE_METHOD(env, var, clazz_obj, methodId) \
do { \
var = (*env)->CallDoubleMethod(env, clazz_obj, methodId); \
JNI_EXCEPTION(env); \
} while (0)
#define CALL_INT_METHOD(env, var, clazz_obj, methodId) \
do { \
var = (*env)->CallIntMethod(env, clazz_obj, methodId); \
JNI_EXCEPTION(env); \
} while (0)
#define CALL_INT_METHOD_PARAM(env, var, clazz_obj, methodId, ...) \
do { \
var = (*env)->CallIntMethod(env, clazz_obj, methodId, __VA_ARGS__); \
JNI_EXCEPTION(env); \
} while (0)
#define CALL_FLOAT_METHOD(env, var, clazz_obj, methodId) \
do { \
var = (*env)->CallFloatMethod(env, clazz_obj, methodId); \
JNI_EXCEPTION(env); \
} while (0)
#endif /* !__cplusplus */
#endif
+366
View File
@@ -2918,6 +2918,301 @@ enum retro_mod
*/
#define RETRO_ENVIRONMENT_GET_AUDIO_SAMPLE_BATCH_MULTI (94 | RETRO_ENVIRONMENT_EXPERIMENTAL)
enum retro_vr_reference_space {
RETRO_VR_REFERENCE_SPACE_LOCAL = 0, /* seated: origin = head at session start/recenter */
RETRO_VR_REFERENCE_SPACE_STAGE = 1, /* standing: origin = floor */
RETRO_VR_REFERENCE_SPACE_DUMMY = INT_MAX
};
/**
* Describes how the frame the core passes to \c retro_video_refresh_t
* divides into views: the screens of a multi-screen system and the eyes
* of a stereo image.
*
* The core keeps drawing every view into its one frame, packed however it
* likes, and this call says where each view is. A frontend that presents
* views lays them out, shades and composes them itself. Any other frontend
* shows the packed frame as it is, so the core needs no second code path.
*
* Rules:
* - Call from \c retro_load_game() or \c retro_run(). The map applies from
* the next \c retro_video_refresh_t call and the frontend copies it.
* Cores should send it every \c retro_run(): an unchanged map costs a
* comparison, and resending covers state loads and run-ahead.
* - Rectangles are pixels in the frame of each refresh call (for a
* hardware frame, the \c width and \c height passed to the refresh
* call), measured from the top-left of the image as displayed. A GL core
* rendering bottom-up still measures \c y from the top. Width and height
* are non-zero. Rectangles may overlap. A frame that some rectangle does
* not fit inside is shown as the packed frame; the map applies again to
* the next frame they all fit.
* - \c num_views is 1 to \c RETRO_VIDEO_VIEWS_MAX, or 0 to clear the map,
* in which case \c views may be NULL.
* Screens number from 0 with no gaps; screen 0 is the primary. Each
* screen has one \c RETRO_VIDEO_VIEW_EYE_NONE view, or one
* \c RETRO_VIDEO_VIEW_EYE_LEFT and one \c RETRO_VIDEO_VIEW_EYE_RIGHT view
* of equal width and height.
* - \c aspect_ratio is the view's display aspect ratio. At or below zero
* means \c width / \c height. Above zero it must be from 0.01 to 100,
* or the map is invalid.
* - \c retro_game_geometry keeps describing the whole packed frame.
* \c RETRO_ENVIRONMENT_SET_ROTATION rotates each view on its own.
* - Touch and light gun coordinates stay in packed-frame terms. A touch
* on any copy of a screen is reported inside that screen's
* \c RETRO_VIDEO_VIEW_EYE_NONE or \c RETRO_VIDEO_VIEW_EYE_LEFT view, at
* the same fraction of its width and height as the touch is across the
* screen as displayed. Rotation is not undone: x follows the display's
* horizontal axis, as it does for a frame without views, so a core that
* sets a rotation turns the point itself.
* - The frontend clears the map when content unloads.
*
* @param[in] data <tt>const struct retro_video_views *</tt>.
* @return \c true if the map was accepted. \c false for an invalid map,
* which leaves the previous map in effect, or from a frontend that does
* not know this call. Either way the core keeps sending the packed frame.
* @see retro_video_views
* @see RETRO_ENVIRONMENT_GET_VIDEO_VIEWS_STATUS
*/
#define RETRO_ENVIRONMENT_SET_VIDEO_VIEWS (95 | RETRO_ENVIRONMENT_EXPERIMENTAL)
/**
* Asks whether the frontend presents views, and whether it shows both eyes.
*
* The answer can change at any time (the user's stereo mode, HDR output,
* the video driver), so a core asks every \c retro_run(), before it draws.
* - With \c RETRO_VIDEO_VIEWS_STATUS_PRESENTS, the core may pack its frame
* for views and send the map with \c RETRO_ENVIRONMENT_SET_VIDEO_VIEWS
* before the \c retro_video_refresh_t call of the frame it describes.
* - Without it, a core with layouts of its own uses them, and clears any
* map it sent by sending one with \c num_views 0.
* - With \c RETRO_VIDEO_VIEWS_STATUS_STEREO, a core with stereo content
* sends left/right pairs. A screen with no 3D content may still be one
* \c RETRO_VIDEO_VIEW_EYE_NONE view, which appears the same in both eyes.
* - Without it, the core sends one \c RETRO_VIDEO_VIEW_EYE_NONE view per
* screen and can skip rendering the second eye. A left/right pair sent
* anyway is shown as its left eye where views are presented, and within
* the packed frame elsewhere.
*
* @param[out] data <tt>unsigned *</tt>. Set to a combination of the
* \c RETRO_VIDEO_VIEWS_STATUS_ flags.
* @return \c true if the call is recognised. A frontend that does not know
* it returns \c false, which means no flags.
* @see RETRO_ENVIRONMENT_SET_VIDEO_VIEWS
*/
#define RETRO_ENVIRONMENT_GET_VIDEO_VIEWS_STATUS (96 | RETRO_ENVIRONMENT_EXPERIMENTAL)
/* Flags of RETRO_ENVIRONMENT_GET_VIDEO_VIEWS_STATUS. PRESENTS: the frontend
* would lay out a valid map sent now, whether or not one is set. STEREO:
* both eyes are shown; only set together with PRESENTS. */
#define RETRO_VIDEO_VIEWS_STATUS_PRESENTS (1 << 0)
#define RETRO_VIDEO_VIEWS_STATUS_STEREO (1 << 1)
/** An active headset session is presenting; eye-tagged views render on it. */
#define RETRO_VIDEO_VIEWS_STATUS_HMD (1 << 2)
/* The eye a view shows; see RETRO_ENVIRONMENT_SET_VIDEO_VIEWS. */
#define RETRO_VIDEO_VIEW_EYE_NONE 0
#define RETRO_VIDEO_VIEW_EYE_LEFT 1
#define RETRO_VIDEO_VIEW_EYE_RIGHT 2
/* The most views one map may hold. */
#define RETRO_VIDEO_VIEWS_MAX 8
/** The core asks for a flat presentation even on a headset. */
#define RETRO_VIDEO_VIEWS_FLAG_REQUEST_FLAT (1 << 0)
/**
* One view of the frame.
* @see RETRO_ENVIRONMENT_SET_VIDEO_VIEWS
*/
struct retro_video_view
{
/** The view's rectangle in the frame, from the top-left as displayed. */
unsigned x;
unsigned y;
unsigned width;
unsigned height;
/** Screen number, from 0. */
unsigned screen;
/** One of the \c RETRO_VIDEO_VIEW_EYE_ values. */
unsigned eye;
/** Display aspect ratio; at or below zero means \c width / \c height. */
float aspect_ratio;
};
/**
* The argument of \c RETRO_ENVIRONMENT_SET_VIDEO_VIEWS.
*/
struct retro_video_views
{
const struct retro_video_view *views;
unsigned num_views;
/** RETRO_VIDEO_VIEWS_FLAG_ values. */
unsigned flags;
/** Headset reference space for eye-tagged views. */
enum retro_vr_reference_space reference_space;
/**
* Requested interpupillary distance in meters, used only if the
* frontend cannot obtain one from the runtime/HMD itself.
* 0.0f means "use whatever the frontend/runtime already knows".
*/
float ipd_hint_m;
/**
* Output: recommended size of one eye-tagged view's render target.
* The core must not use these to determine its system AV geometry;
* the frame geometry stays retro_get_system_av_info()'s.
*/
unsigned recommended_view_width;
unsigned recommended_view_height;
};
#define RETRO_VR_FRAME_RECENTERED (1u << 0) /* user recentered: re-capture any reference */
#define RETRO_VR_FRAME_TARGET_RESIZED (1u << 1) /* eye size changed: call SET_VIDEO_VIEWS again */
/**
* Retrieves the per-eye state plus per-frame event flags
* (RETRO_VR_FRAME_RECENTERED / RETRO_VR_FRAME_TARGET_RESIZED) for the
* current retro_run() call.
*
* The frontend samples both eye states once per frame, immediately before
* retro_run(), and returns that same sample for the duration of that
* retro_run() call.
*
* The position and orientation in each eye state are the eye-to-tracking
* transforms used for that frame. The FOV values are the same values the
* frontend must use when submitting the compositor layer for the
* corresponding frame.
*
* @param[out] data <tt>struct retro_vr_frame_state *</tt>.
* @return false if no VR session exists or no valid sample is available.
*/
#define RETRO_ENVIRONMENT_GET_VR_FRAME_STATE (97 | RETRO_ENVIRONMENT_EXPERIMENTAL)
/**
* Identifies which eye a \ref retro_vr_eye_state describes, and the index
* of that eye within the two-element array passed to
* RETRO_ENVIRONMENT_GET_VR_FRAME_STATE.
*/
enum retro_vr_eye
{
RETRO_VR_EYE_LEFT = 0,
RETRO_VR_EYE_RIGHT = 1,
/** @private Defined to ensure <tt>sizeof(retro_vr_eye) == sizeof(int)</tt>. Do not use. */
RETRO_VR_EYE_DUMMY = INT_MAX
};
/**
* One eye's head-tracked pose and asymmetric field of view for the current
* frame, in the frontend's tracking space (meters, right-handed, Y up).
*
* position and orientation describe the eye-to-tracking-space transform,
* not its inverse.
*
* The quaternion is stored in (x, y, z, w) order. This is intentionally
* different from APIs such as GLM where glm::quat is constructed as
* (w, x, y, z).
*
* @see RETRO_ENVIRONMENT_GET_VR_FRAME_STATE
*/
struct retro_vr_eye_state
{
/**
* Eye position in tracking space, in meters.
*/
float position[3];
/**
* Eye orientation in tracking space, as a quaternion (x, y, z, w).
*
* This is the orientation of the eye relative to tracking space, not
* the inverse/view orientation.
*/
float orientation[4];
/**
* Tangent of the positive half-angle from the eye's forward axis to
* each frustum edge, in the order:
*
* [0] left
* [1] right
* [2] up
* [3] down
*
* All four values are positive magnitudes. They describe the distance
* of each frustum edge from the forward axis in tangent space; they are
* not signed OpenXR angles.
*
* The four values are independent because HMD eye frustums are normally
* asymmetric.
*
* For example, if the left and right tangent values are l and r, and
* the up and down tangent values are u and d, the projection frustum
* spans:
*
* left = -l
* right = r
* bottom = -d
* top = u
*
* A frontend converting from APIs that represent the corresponding
* angles as signed values must use their magnitudes.
*/
float fov_tan[4];
};
struct retro_vr_frame_state {
struct retro_vr_eye_state eyes[2];
uint32_t flags;
};
/**
* Retrieves the current head-tracked pose.
*
* The frontend samples the HMD once per frame, immediately before
* retro_run(), and returns the same sample for the duration of that
* retro_run() call.
*/
#define RETRO_ENVIRONMENT_GET_VR_HEAD_POSE (98 | RETRO_ENVIRONMENT_EXPERIMENTAL)
#define RETRO_VR_HEAD_POSE_POSITION_VALID (1u << 0)
#define RETRO_VR_HEAD_POSE_ORIENTATION_VALID (1u << 1)
#define RETRO_VR_HEAD_POSE_VELOCITY_VALID (1u << 2)
/**
* Head pose and motion in the frontend's tracking space (meters,
* seconds, right-handed, Y up).
*
* position and orientation describe the head-to-tracking-space transform,
* not its inverse. The orientation quaternion rotates vectors from head
* space into tracking space.
*
* linear_velocity is the velocity of the head-space origin, expressed in
* tracking-space coordinates. angular_velocity is the head's angular
* velocity, expressed in tracking-space coordinates. Velocity fields are
* valid only when RETRO_VR_HEAD_POSE_VELOCITY_VALID is set.
*/
struct retro_vr_head_pose
{
/** Head position in tracking space, in meters. */
float position[3];
/** Head orientation in tracking space, quaternion (x, y, z, w). */
float orientation[4];
/** Linear velocity in meters/second. */
float linear_velocity[3];
/** Angular velocity in radians/second. */
float angular_velocity[3];
/* orientation/position are valid */
uint32_t flags;
};
/* Speaker positions, as bits of a layout mask; a frame's channels are
* interleaved in ascending bit order. The bits are those of the
* WAVEFORMATEXTENSIBLE channel mask. */
@@ -5341,6 +5636,19 @@ enum retro_camera_buffer
*/
RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER,
/**
* Indicates that camera frames should be delivered to the core as
* the planar 4:2:0 YCbCr the capture device produces, in place.
*
* No conversion happens between the device and the core: the planes
* are the capture buffer itself. A core that can sample or convert
* YCbCr should prefer this over \c RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER,
* which costs the frontend a conversion pass per frame.
*
* @see retro_camera_frame_planar_t
*/
RETRO_CAMERA_BUFFER_PLANAR,
/**
* @private Defined to ensure <tt>sizeof(enum retro_camera_buffer) == sizeof(int)</tt>.
* Do not use.
@@ -5348,6 +5656,44 @@ enum retro_camera_buffer
RETRO_CAMERA_BUFFER_DUMMY = INT_MAX
};
/** The luma plane is full range (0..255) rather than limited (16..235). */
#define RETRO_CAMERA_PLANAR_FULL_RANGE (1 << 0)
/** BT.709 matrix rather than BT.601. */
#define RETRO_CAMERA_PLANAR_BT709 (1 << 1)
/**
* One 8-bit 4:2:0 YCbCr frame, as planes.
*
* Covers NV12, NV21 and I420 without a format tag: the chroma planes
* are addressed by their own base pointers and a common sample step,
* so NV12 is <tt>planes[2] == planes[1] + 1</tt> with \c chroma_step 2,
* NV21 the other way round, and I420 two separate planes with
* \c chroma_step 1. Row pairs share a chroma row.
*
* @see RETRO_CAMERA_BUFFER_PLANAR
*/
struct retro_camera_planar_frame
{
/** Y, Cb, Cr. Owned by the frontend, read-only, valid for the duration of the callback. */
const uint8_t *planes[3];
/** Row strides of the three planes, in bytes. */
size_t strides[3];
/** Distance between horizontally adjacent chroma samples of one plane, in bytes: 1 or 2. */
size_t chroma_step;
/** Frame size in luma pixels. */
unsigned width;
unsigned height;
/** Degrees the frame must be rotated clockwise to be upright: 0, 90, 180 or 270. */
unsigned rotation;
/** \c RETRO_CAMERA_PLANAR_* bits. */
unsigned flags;
};
/**
* Starts an initialized camera.
* The camera is disabled by default,
@@ -5426,6 +5772,17 @@ typedef void (RETRO_CALLCONV *retro_camera_frame_raw_framebuffer_t)(const uint32
typedef void (RETRO_CALLCONV *retro_camera_frame_opengl_texture_t)(unsigned texture_id,
unsigned texture_target, const float *affine);
/**
* Called by the frontend to report a new camera frame,
* delivered as the capture device's own planar YCbCr buffer.
*
* @param frame The frame. Its planes may be invalidated when this function returns,
* so the core should make its own copy if necessary.
* @see RETRO_CAMERA_BUFFER_PLANAR
*/
typedef void (RETRO_CALLCONV *retro_camera_frame_planar_t)(
const struct retro_camera_planar_frame *frame);
/**
* An interface that the core can use to access a device's camera.
*
@@ -5501,6 +5858,15 @@ struct retro_camera_callback
* May be \c NULL, in which case this function is skipped.
*/
retro_camera_lifetime_status_t deinitialized;
/**
* @copydoc retro_camera_frame_planar_t
* @note Read by the frontend only when \c caps has the
* \c RETRO_CAMERA_BUFFER_PLANAR bit, so a core built against an
* older header, whose struct ends above, is never read past.
* If \c NULL, this function will not be called.
*/
retro_camera_frame_planar_t frame_planar;
};
/** @} */
+1 -1
View File
@@ -324,7 +324,7 @@ struct retro_hw_render_interface_vulkan
*
* Images passed to set_image should be created with TILING_OPTIMAL.
* The image layout should be transitioned to either
* VK_IMAGE_LAYOUT_GENERIC or VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL.
* VK_IMAGE_LAYOUT_GENERAL or VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL.
* The actual image layout used must be set in image_layout.
*
* The image must be a 2D texture which may or not be layered
+7
View File
@@ -31,6 +31,13 @@
RETRO_BEGIN_DECLS
/* How long a TLS read, in the handshake or in a record, waits for the
* server's next bytes before it fails: a server that goes silent must
* not hold the caller's thread for good. */
#ifndef SSL_SOCKET_IO_TIMEOUT_MS
#define SSL_SOCKET_IO_TIMEOUT_MS 30000
#endif
void* ssl_socket_init(int fd, const char *domain);
int ssl_socket_connect(void *state_data, void *data, bool timeout_enable, bool nonblock);
+238
View File
@@ -0,0 +1,238 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (rrar_archive.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.
*/
/* RAR archive reader for libretro-common.
*
* The container and the decoder follow libarchive's RAR reader
* (archive_read_support_format_rar.c, Tim Kientzle and Andres Mejia,
* 2-clause BSD - see rrar_archive.c for its notice); the PPMd model next
* to it (rrar_ppmd7.c) is an implementation of Dmitry Shkarin's
* public-domain PPMd var.H.
*
* Deliberately not a general RAR implementation. libretro-common opens
* archives of ROMs, so this covers what those are packed as and rejects
* everything else rather than half-supporting it:
*
* - the RAR 1.5 to 4.x container ("Rar!\x1a\x07\x00") and the RAR 5
* one ("Rar!\x1a\x07\x01\x00")
* - stored members of any version
* - members packed by RAR 2.9 to 4.x (unpack version 29 and 36), all
* five methods: the LZ coder with its Huffman tables, PPMd blocks,
* and the standard filters (delta, x86 E8 and E8/E9, RGB, audio)
* - members packed by RAR 5 and later in the RAR 5 format, all five
* methods, with its filters (delta, x86 E8 and E8/E9, ARM)
*
* Not supported, and said so per entry or at open: members packed by
* RAR 1.5 or 2.x, the RAR 7 format for dictionaries over 4 GB, solid
* archives (but for their first member), encryption, multi-volume and
* self-extracting archives, and for RAR 2.9 filter programs other than
* the standard ones (there is no RAR virtual machine here).
*
* A RAR 5 archive can be made with BLAKE2 hashes in place of CRC-32s.
* Its members are unpacked and not checked (has_crc is 0): there is no
* BLAKE2 here.
*
* ------------------------------------------------------------------
* On untrusted input
* ------------------------------------------------------------------
*
* Everything here comes from the file. Header sizes, member sizes,
* match distances and lengths, filter positions and the code tables are
* all the archive's to claim. So:
*
* - every header is checked against the archive's length before it is
* read, and against its own checksum
* - a member is decoded into a buffer of the size its header gives and
* never past it; a match that reaches back before the start of the
* member reads zeros
* - a code table that cannot be a prefix code is refused
* - filters are checked against the member's size before they run
* - what comes out is checked against the member's CRC-32
*/
#ifndef __LIBRETRO_SDK_RRAR_ARCHIVE_H
#define __LIBRETRO_SDK_RRAR_ARCHIVE_H
#include <stddef.h>
#include <stdint.h>
#include <retro_common_api.h>
RETRO_BEGIN_DECLS
#define RRAR_OK 0
#define RRAR_ERROR_DATA (-1)
#define RRAR_ERROR_PARAM (-2)
#define RRAR_ERROR_MEM (-3)
#define RRAR_ERROR_UNSUPPORTED (-4)
#define RRAR_ERROR_CRC (-5)
/* A watcher asked for the decode to stop: rrar_archive_extract_to(). */
#define RRAR_ERROR_CANCELLED (-6)
/* Signature length at the head of every archive (a RAR 5 one has an
* eighth byte, zero). */
#define RRAR_SIGNATURE_SIZE 7
/* One entry in the archive. */
typedef struct rrar_entry
{
/* UTF-8 name, '/' between its parts, NUL terminated. Owned by the
* archive. */
const char *name;
uint64_t size; /* unpacked */
uint64_t packed_size;
uint64_t data_offset; /* of the packed bytes in the archive */
uint32_t crc;
uint8_t method; /* 0x30 stored, 0x31 to 0x35 packed */
uint8_t version; /* of RAR needed to unpack: 29 is 2.9, 50 is 5.0 */
uint8_t is_dir;
/* 0: this reader cannot extract the entry (see the list above) */
uint8_t supported;
/* 0: the archive has no CRC-32 for the entry (a RAR 5 archive made to
* carry BLAKE2 hashes only), and extraction checks nothing */
uint8_t has_crc;
/* How far back the packed data can refer: its dictionary size. */
uint64_t window;
} rrar_entry_t;
typedef struct rrar_archive rrar_archive_t;
/**
* rrar_archive_open:
* @out : receives the opened archive
* @data : the whole archive file in memory
* @len : length of @data
*
* Parses the archive's headers. @data must stay valid and unmodified
* until rrar_archive_close(); the archive borrows it rather than
* copying.
*
* Returns: RRAR_OK, or a negative RRAR_ERROR_* code. A malformed archive
* is an error, never a partial success; an archive of a kind this reader
* does not read (multi-volume, encrypted headers) is
* RRAR_ERROR_UNSUPPORTED.
*/
int rrar_archive_open(rrar_archive_t **out,
const uint8_t *data, size_t len);
/* Positioned read of @len bytes at @off into @dst; returns the bytes
* read, 0 at the end, negative on error. */
typedef int64_t (*rrar_read_t)(void *ud, uint64_t off, void *dst,
size_t len);
/**
* rrar_archive_open_read:
* @out : receives the opened archive
* @len : length of the archive file
* @read_cb : positioned reads of it
* @ud : handed to @read_cb
*
* The same, for an archive that is not in memory and is not to be put
* there whole: its headers are read one at a time at open, a stored
* member is read straight into the buffer it is extracted to, and a
* compressed one has its packed bytes read in while it is decoded.
*
* Returns: RRAR_OK, or a negative RRAR_ERROR_* code.
*/
int rrar_archive_open_read(rrar_archive_t **out, uint64_t len,
rrar_read_t read_cb, void *ud);
/**
* rrar_archive_close:
* @a : archive, may be NULL
*
* Releases everything rrar_archive_open() allocated.
*/
void rrar_archive_close(rrar_archive_t *a);
/**
* rrar_archive_num_entries:
* @a : opened archive
*
* Returns: the number of entries, including directories.
*/
uint32_t rrar_archive_num_entries(const rrar_archive_t *a);
/**
* rrar_archive_entry:
* @a : opened archive
* @index : entry index, below rrar_archive_num_entries()
*
* Returns: the entry, or NULL if @index is out of range.
*/
const rrar_entry_t *rrar_archive_entry(const rrar_archive_t *a,
uint32_t index);
/**
* rrar_archive_extract:
* @a : opened archive
* @index : entry index
* @out : receives a malloc'd buffer holding the entry's data
* @out_len : receives the entry's size
*
* Unpacks one entry and checks it against the archive's CRC-32, if the
* archive has one for it. The
* caller frees *@out. An empty entry yields a valid one-byte buffer and
* a length of zero.
*
* Returns: RRAR_OK, or a negative RRAR_ERROR_* code, in which case
* *@out is NULL.
*/
int rrar_archive_extract(rrar_archive_t *a, uint32_t index,
uint8_t **out, size_t *out_len);
/* Told, on the thread that decodes, how much of a member is final:
* @done bytes from its start, which no later call takes back. Returns
* nonzero to have the decode stop. See rrar_archive_extract_to(). */
typedef struct rrar_watch
{
int (*progress)(void *ud, size_t done);
void *ud;
} rrar_watch_t;
/**
* rrar_archive_extract_to:
* @a : opened archive
* @index : entry index
* @dst : where the entry's data goes
* @dst_size : how much room there is at @dst
* @watch : who is told how far the decode has got, or NULL
*
* The same as rrar_archive_extract(), into memory the caller has, and
* with a watcher: for a member that takes long to decode, which another
* thread can read from @dst as it is decoded. The watcher is called
* about every 64 KiB with the count of bytes that are final - behind
* the decoder by a filter's block and the dictionary where the member
* has filters, since a filter is run over its block only once nothing
* can refer back into it - and with the whole size once the CRC has
* been checked. A member is decoded in the calling thread from start to
* end; an archive may have several decoded at once, each on a thread.
*
* Returns: RRAR_OK, RRAR_ERROR_CANCELLED if the watcher stopped it, or
* another negative RRAR_ERROR_* code. On an error what the watcher was
* told is final was never checked.
*/
int rrar_archive_extract_to(rrar_archive_t *a, uint32_t index,
uint8_t *dst, size_t dst_size, const rrar_watch_t *watch);
RETRO_END_DECLS
#endif
+14 -2
View File
@@ -1555,9 +1555,21 @@ static INLINE int retro_atomic_load_seq_cst_int_fb_(retro_atomic_int_t *p)
* word. Every real backend can express these (OSAtomic and
* Interlocked both have 64-bit CAS; cmpxchg8b covers 32-bit x86), but
* old 32-bit __sync targets may not - RETRO_ATOMIC_HAS_64 gates.
* Callers on the volatile fallback get nothing, as with CAS/PTR. */
* Callers on the volatile fallback get nothing, as with CAS/PTR.
*
* C11, C++11 and GCC __atomic accept an 8-byte operation on any target,
* but where the target has no 8-byte atomic instructions (32-bit
* PowerPC, MIPS32: the compiler reports 8-byte atomics as not always
* lock-free) each one compiles to a libatomic call (__atomic_load_8),
* which Darwin and the console toolchains do not ship, and the link
* fails. There the 64-bit operations are left out, as on the 32-bit
* __sync targets. */
#if defined(RETRO_ATOMIC_BACKEND_C11)
#if (defined(RETRO_ATOMIC_BACKEND_C11) || defined(RETRO_ATOMIC_BACKEND_CXX11) \
|| defined(RETRO_ATOMIC_BACKEND_GCC_NEW)) \
&& defined(__GCC_ATOMIC_LLONG_LOCK_FREE) && __GCC_ATOMIC_LLONG_LOCK_FREE < 2
/* RETRO_ATOMIC_HAS_64 stays undefined. */
#elif defined(RETRO_ATOMIC_BACKEND_C11)
#include <stdint.h>
typedef _Atomic(int64_t) retro_atomic_64_t;
+1
View File
@@ -95,6 +95,7 @@ typedef int ssize_t;
#ifndef PRId64
#define PRId64 "I64d"
#define PRIu64 "I64u"
#define PRIx64 "I64x"
#define PRIuPTR "Iu"
#endif
#endif
+7
View File
@@ -523,8 +523,15 @@ 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__)
/* Darwin's size_t is unsigned long on every architecture, 32-bit
* (armv7, i386, PowerPC) included, where SIZE_MAX alone says "u". */
# define PRI_SIZET "lu"
#else
# if (SIZE_MAX == 0xFFFF)
# define PRI_SIZET "hu"
+30
View File
@@ -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.
*
@@ -520,6 +542,14 @@ const uint8_t *filestream_get_mapped_ptr(RFILE *stream, int64_t *len);
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);
/**
* Ask the OS to start reading [offset, offset + len) of \c stream into
* memory ahead of a read or a touch of its mapping. A hint: it returns
* at once and does nothing on a frontend-supplied VFS or where the
* platform has no such request. See retro_vfs_file_prefetch_impl().
*/
void filestream_prefetch(RFILE *stream, uint64_t offset, uint64_t len);
/**
* Size of the window filestream_vscanf() reads and scans at a time,
* i.e. the furthest a single conversion can reach.
+15
View File
@@ -85,6 +85,21 @@ 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_prefetch_impl:
* @offset : first byte of the range about to be read
* @len : bytes of it
*
* 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. */
void retro_vfs_file_prefetch_impl(
libretro_vfs_implementation_file *stream, uint64_t offset,
uint64_t len);
int retro_vfs_stat_impl(const char *path, int32_t *size);
int retro_vfs_stat_64_impl(const char *path, int64_t *size);
+198
View File
@@ -0,0 +1,198 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (rzip_archive.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_RZIP_ARCHIVE_H
#define __LIBRETRO_SDK_RZIP_ARCHIVE_H
#include <stddef.h>
#include <stdint.h>
#include <retro_common_api.h>
RETRO_BEGIN_DECLS
/* Clean-room reader for the zip container: the central directory, zip64
* records and extra fields, stored and deflated members. It performs no
* file I/O of its own: the archive is either handed over whole in memory
* or read through a positioned-read callback, which is what lets a
* mapped archive serve stored members in place and an unmapped one be
* walked without being loaded. */
#define RZIP_OK 0
#define RZIP_ERROR_DATA (-1)
#define RZIP_ERROR_PARAM (-2)
#define RZIP_ERROR_MEM (-3)
#define RZIP_ERROR_UNSUPPORTED (-4)
#define RZIP_ERROR_CRC (-5)
#define RZIP_ERROR_IO (-6)
#define RZIP_METHOD_STORED 0
#define RZIP_METHOD_DEFLATE 8
typedef struct rzip_entry
{
/* Name as stored (UTF-8 or the archiver's code page), NUL terminated.
* Owned by the archive. */
const char *name;
uint64_t size; /* uncompressed */
uint64_t csize; /* bytes in the archive */
uint64_t data_off; /* where those bytes start in the archive */
uint32_t crc;
uint32_t method; /* RZIP_METHOD_* or another the format allows */
uint32_t is_dir;
} rzip_entry_t;
typedef struct rzip_archive rzip_archive_t;
/* Positioned read of @len bytes at @off into @dst; returns the bytes
* read, 0 at the end, negative on error. */
typedef int64_t (*rzip_read_t)(void *ud, uint64_t off, void *dst,
size_t len);
/**
* rzip_archive_open:
* @out : receives the archive
* @data : the whole archive in memory, or NULL
* @len : length of the archive in bytes, in memory or not
* @read_cb : positioned reads of the archive when @data is NULL
* @ud : handed to @read_cb
*
* Parses the central directory and locates every member's data. With
* @data the bytes must stay valid and unmodified until
* rzip_archive_close(); with @read_cb only the directory and each
* member's local header are read, at open.
*
* Returns: RZIP_OK, or a negative RZIP_ERROR_* code.
*/
int rzip_archive_open(rzip_archive_t **out, const uint8_t *data,
uint64_t len, rzip_read_t read_cb, void *ud);
void rzip_archive_close(rzip_archive_t *a);
uint32_t rzip_archive_num_entries(const rzip_archive_t *a);
const rzip_entry_t *rzip_archive_entry(const rzip_archive_t *a,
uint32_t index);
/* Index of the member named @name exactly, or -1. */
int rzip_archive_find(const rzip_archive_t *a, const char *name);
/**
* rzip_archive_entry_view:
*
* A stored member of an in-memory archive, in place: a pointer into
* @data, valid as long as @data is. NULL for a deflated member, a
* member of an archive read through a callback, or one whose data lies
* outside the archive.
*/
const uint8_t *rzip_archive_entry_view(const rzip_archive_t *a,
uint32_t index, size_t *len);
/**
* rzip_archive_extract:
* @out : receives a malloc'd buffer holding the member, which the
* caller frees
* @out_len : receives its length
*
* The member decoded into its own buffer: a stored one copied, a
* deflated one inflated, either checked against the directory's CRC.
* A member of a size this build cannot address, or of a method it
* cannot decode, is refused.
*/
int rzip_archive_extract(rzip_archive_t *a, uint32_t index,
uint8_t **out, size_t *out_len);
/**
* rzip_archive_extract_into:
* @dst : where the member goes
* @dst_size : how much room there is at @dst
* @out_len : receives the member's length
*
* The same as rzip_archive_extract(), into memory the caller has: for a
* member whose bytes have a place of their own to go to, which would
* otherwise be decoded into a buffer and copied from it. A member
* longer than @dst_size is refused with RZIP_ERROR_PARAM and nothing is
* written; on any other error @dst may hold part of the member.
*/
int rzip_archive_extract_into(rzip_archive_t *a, uint32_t index,
uint8_t *dst, size_t dst_size, size_t *out_len);
/* ---- a deflated member read at any offset, without decoding it whole ----
*
* A deflate stream can only be decoded from its start, so reading the
* end of a large member means decoding all of it - and keeping all of
* it, if the middle is wanted next. The index remembers restart points
* along the stream instead: at a block boundary, the position in the
* compressed bytes and the 32 KiB of output before it are all it takes
* to decode from there. With a point about every @span bytes, any byte
* of the member is within one span's decoding of a point, and nothing
* but the points (32 KiB each) and a few decoded spans is kept.
*
* The points are found by decoding the member through once: that is
* rzip_seek_build(), which may be called a piece at a time, and on a
* thread of its own while another thread reads what is already covered.
* One thread builds and one thread reads; the two share only the points
* already published. (An archive opened with a read callback has both
* calling it, so there the two must be the same thread unless the
* callback takes that.)
*/
typedef struct rzip_seek rzip_seek_t;
#define RZIP_SEEK_SPAN_DEFAULT (1024 * 1024)
/* Member @index of @a, which must outlive the index. NULL unless it is a
* deflated file. @span: bytes between restart points, 0 for the default. */
rzip_seek_t *rzip_seek_new(rzip_archive_t *a, uint32_t index, uint32_t span);
void rzip_seek_free(rzip_seek_t *s);
/* The member's decoded length. */
uint64_t rzip_seek_size(const rzip_seek_t *s);
/* Decode on from where the index ends until it covers @upto bytes of the
* member, or all of it. Returns 1 when the whole member is indexed (and
* its checksum was right), 0 when there is more to do, and a negative
* RZIP_ERROR_ when the stream is bad - nothing more is covered then. */
int rzip_seek_build(rzip_seek_t *s, uint64_t upto);
/* How much of the member, from its start, can be read now. Any thread. */
uint64_t rzip_seek_covered(const rzip_seek_t *s);
/* Where the index stands: 0 while there is more to index, 1 when the
* whole member is, a negative RZIP_ERROR_ once the stream has turned out
* bad. Any thread. */
int rzip_seek_state(const rzip_seek_t *s);
/* @len bytes at @offset to @dst. Returns RZIP_OK; RZIP_ERROR_PARAM when
* the index does not cover them yet (or they are past the end); another
* RZIP_ERROR_ when they cannot be decoded. */
int rzip_seek_read(rzip_seek_t *s, uint64_t offset, uint8_t *dst, size_t len);
/* @len bytes at @offset where they lie decoded, for a reader that would
* only copy them on: *@data points at them until the next call on @s, or
* is NULL when they straddle two spans and have to be read instead.
* Returns as rzip_seek_read() does. The reader's thread. */
int rzip_seek_view(rzip_seek_t *s, uint64_t offset, size_t len,
const uint8_t **data);
RETRO_END_DECLS
#endif
+282
View File
@@ -0,0 +1,282 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (rjni.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <jni/rjni.h>
#include <rthreads/rthreads.h>
/* The registered VM, and the TLS key holding each thread's cached env.
* The key lives in a small heap record published with a CAS so that
* first use from any thread needs no lock and no wait: concurrent
* creators race, exactly one record is published, losers delete their
* own. The same pattern publishes every rjni_ref cache. */
static retro_atomic_ptr_t rjni_vm_slot;
#ifdef HAVE_THREAD_STORAGE
static retro_atomic_ptr_t rjni_key_slot;
struct rjni_key_rec
{
sthread_tls_t key;
};
#endif
/* Per-thread record: the env, and whether this layer attached the
* thread (only then may the destructor detach it). */
struct rjni_tls_rec
{
JNIEnv *env;
int owns_attach;
};
void rjni_set_vm(JavaVM *vm)
{
retro_atomic_cas_ptr(&rjni_vm_slot, NULL, (void*)vm);
}
JavaVM *rjni_vm(void)
{
return (JavaVM*)retro_atomic_load_acquire_ptr(&rjni_vm_slot);
}
#ifdef HAVE_THREAD_STORAGE
static void rjni_tls_destruct(void *value)
{
struct rjni_tls_rec *rec = (struct rjni_tls_rec*)value;
JavaVM *vm;
if (!rec)
return;
if (rec->owns_attach && (vm = rjni_vm()))
(*vm)->DetachCurrentThread(vm);
free(rec);
}
static struct rjni_key_rec *rjni_key(void)
{
struct rjni_key_rec *rec =
(struct rjni_key_rec*)retro_atomic_load_acquire_ptr(&rjni_key_slot);
if (rec)
return rec;
if (!(rec = (struct rjni_key_rec*)malloc(sizeof(*rec))))
return NULL;
if (!sthread_tls_create_with_dtor(&rec->key, rjni_tls_destruct))
{
free(rec);
return NULL;
}
if (retro_atomic_cas_ptr(&rjni_key_slot, NULL, rec))
return rec;
sthread_tls_delete(&rec->key);
free(rec);
return (struct rjni_key_rec*)retro_atomic_load_acquire_ptr(&rjni_key_slot);
}
#endif
JNIEnv *rjni_env(void)
{
#ifdef HAVE_THREAD_STORAGE
struct rjni_key_rec *key = rjni_key();
struct rjni_tls_rec *rec;
#endif
JNIEnv *env = NULL;
JavaVM *vm;
#ifdef HAVE_THREAD_STORAGE
if (key && (rec = (struct rjni_tls_rec*)sthread_tls_get(&key->key)))
return rec->env;
#endif
if (!(vm = rjni_vm()))
return NULL;
/* GetEnv distinguishes a thread the VM already knows (a Java-created
* thread calling into native) from one we must attach: detaching the
* former at exit is not ours to do, the latter we own. */
switch ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6))
{
case JNI_OK:
#ifdef HAVE_THREAD_STORAGE
if (key && (rec = (struct rjni_tls_rec*)calloc(1, sizeof(*rec))))
{
rec->env = env;
sthread_tls_set(&key->key, rec);
}
#endif
return env;
case JNI_EDETACHED:
/* Android's AttachCurrentThread takes JNIEnv**, the JDK's takes
* void**; both fill in the same pointer. */
#ifdef __ANDROID__
if ((*vm)->AttachCurrentThread(vm, &env, NULL) != JNI_OK)
return NULL;
#else
if ((*vm)->AttachCurrentThread(vm, (void**)&env, NULL) != JNI_OK)
return NULL;
#endif
/* Without a key there is nowhere to remember the attach and no
* destructor to detach - the env is still usable, so return it
* rather than fail the call. */
#ifdef HAVE_THREAD_STORAGE
if (key && (rec = (struct rjni_tls_rec*)calloc(1, sizeof(*rec))))
{
rec->env = env;
rec->owns_attach = 1;
sthread_tls_set(&key->key, rec);
}
#endif
return env;
default:
break;
}
return NULL;
}
int rjni_exception_clear(JNIEnv *env)
{
if (!(*env)->ExceptionCheck(env))
return 0;
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
return 1;
}
/* Publish @global into @cache; on losing the race, release ours and
* return the winner. */
static jclass rjni_class_publish(rjni_ref *cache, JNIEnv *env, jclass global)
{
if (retro_atomic_cas_ptr(&cache->p, NULL, (void*)global))
return global;
(*env)->DeleteGlobalRef(env, global);
return (jclass)retro_atomic_load_acquire_ptr(&cache->p);
}
jclass rjni_class(rjni_ref *cache, JNIEnv *env, const char *name)
{
jclass local;
jclass global;
jclass cached = (jclass)retro_atomic_load_acquire_ptr(&cache->p);
if (cached)
return cached;
local = (*env)->FindClass(env, name);
if (rjni_exception_clear(env) || !local)
return NULL;
global = (jclass)(*env)->NewGlobalRef(env, local);
(*env)->DeleteLocalRef(env, local);
if (!global)
return NULL;
return rjni_class_publish(cache, env, global);
}
jclass rjni_class_from_loader(rjni_ref *cache, JNIEnv *env,
jobject loader, const char *name)
{
static rjni_ref loader_class;
static rjni_ref load_method;
jclass cls;
jmethodID load;
jstring jname;
jclass local;
jclass global;
jclass cached = (jclass)retro_atomic_load_acquire_ptr(&cache->p);
if (cached)
return cached;
if (!(cls = rjni_class(&loader_class, env, "java/lang/ClassLoader")))
return NULL;
if (!(load = rjni_method(&load_method, env, cls, "loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;")))
return NULL;
if (!(jname = (*env)->NewStringUTF(env, name)))
return NULL;
local = (jclass)(*env)->CallObjectMethod(env, loader, load, jname);
(*env)->DeleteLocalRef(env, jname);
if (rjni_exception_clear(env) || !local)
return NULL;
global = (jclass)(*env)->NewGlobalRef(env, local);
(*env)->DeleteLocalRef(env, local);
if (!global)
return NULL;
return rjni_class_publish(cache, env, global);
}
/* Method and field IDs are plain values, valid as long as their class
* is; losing the publish race costs nothing. */
jmethodID rjni_method(rjni_ref *cache, JNIEnv *env, jclass cls,
const char *name, const char *sig)
{
jmethodID id = (jmethodID)retro_atomic_load_acquire_ptr(&cache->p);
if (id)
return id;
id = (*env)->GetMethodID(env, cls, name, sig);
if (rjni_exception_clear(env) || !id)
return NULL;
retro_atomic_cas_ptr(&cache->p, NULL, (void*)id);
return id;
}
jmethodID rjni_method_static(rjni_ref *cache, JNIEnv *env, jclass cls,
const char *name, const char *sig)
{
jmethodID id = (jmethodID)retro_atomic_load_acquire_ptr(&cache->p);
if (id)
return id;
id = (*env)->GetStaticMethodID(env, cls, name, sig);
if (rjni_exception_clear(env) || !id)
return NULL;
retro_atomic_cas_ptr(&cache->p, NULL, (void*)id);
return id;
}
jfieldID rjni_field(rjni_ref *cache, JNIEnv *env, jclass cls,
const char *name, const char *sig)
{
jfieldID id = (jfieldID)retro_atomic_load_acquire_ptr(&cache->p);
if (id)
return id;
id = (*env)->GetFieldID(env, cls, name, sig);
if (rjni_exception_clear(env) || !id)
return NULL;
retro_atomic_cas_ptr(&cache->p, NULL, (void*)id);
return id;
}
jfieldID rjni_field_static(rjni_ref *cache, JNIEnv *env, jclass cls,
const char *name, const char *sig)
{
jfieldID id = (jfieldID)retro_atomic_load_acquire_ptr(&cache->p);
if (id)
return id;
id = (*env)->GetStaticFieldID(env, cls, name, sig);
if (rjni_exception_clear(env) || !id)
return NULL;
retro_atomic_cas_ptr(&cache->p, NULL, (void*)id);
return id;
}
+4 -2
View File
@@ -320,12 +320,14 @@ static int file_list_type_cmp(const void *a_, const void *b_)
void file_list_sort_on_alt(file_list_t *list)
{
qsort(list->list, list->size, sizeof(list->list[0]), file_list_alt_cmp);
if (list && list->size > 1)
qsort(list->list, list->size, sizeof(list->list[0]), file_list_alt_cmp);
}
void file_list_sort_on_type(file_list_t *list)
{
qsort(list->list, list->size, sizeof(list->list[0]), file_list_type_cmp);
if (list && list->size > 1)
qsort(list->list, list->size, sizeof(list->list[0]), file_list_type_cmp);
}
void *file_list_get_userdata_at_offset(const file_list_t *list, size_t idx)
+1 -1
View File
@@ -34,7 +34,7 @@
static void media_zero_trailing_spaces(char *s, size_t len)
{
int i;
for (i = len - 1; i >= 0; i--)
for (i = (int)len - 1; i >= 0; i--)
{
if (s[i] == ' ')
s[i] = '\0';
+29 -4
View File
@@ -59,6 +59,18 @@
#define FILE_MAP_EXECUTE 0x0020
#endif
/* A UWP build's headers leave MapViewOfFileEx out, and C takes a call
* to a function it has not seen declared to return int: on a 64-bit
* build the address it hands back loses its upper half. The function
* is there to link against; this is the declaration the desktop
* headers give it. */
#if defined(WINAPI_FAMILY) && defined(WINAPI_FAMILY_APP) \
&& (WINAPI_FAMILY == WINAPI_FAMILY_APP)
WINBASEAPI LPVOID WINAPI MapViewOfFileEx(HANDLE hFileMappingObject,
DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap, LPVOID lpBaseAddress);
#endif
/* Map POSIX prot bits to a PAGE_* protection constant. Windows has
* no write-only or exec-only protections; those requests take the
* nearest expressible superset, as every mman shim does. */
@@ -627,20 +639,33 @@ void memshm_unmap(void *addr, size_t len) { (void)addr; (void)len; }
* TARGET_OS_OSX is the test, not the architecture. */
#if defined(__APPLE__) && defined(__aarch64__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
#include <pthread.h>
#include <dlfcn.h>
/* pthread_jit_write_protect_np is per thread, and so is this depth:
* one thread's nesting must not flip another's pages. */
static __thread int memjit_depth;
/* Every macOS that runs on arm64 has pthread_jit_write_protect_np, but
* the SDK declares it for macOS 11 against a lower deployment target,
* so it is resolved by name once, at load: the call through the
* pointer is the same indirect call the library stub makes. */
static void (*memjit_protect)(int);
static void __attribute__((constructor)) memjit_resolve(void)
{
*(void **)(&memjit_protect) = dlsym(RTLD_DEFAULT,
"pthread_jit_write_protect_np");
}
void memjit_write_begin(void)
{
if (memjit_depth++ == 0)
pthread_jit_write_protect_np(0);
if (memjit_depth++ == 0 && memjit_protect)
memjit_protect(0);
}
void memjit_write_end(void)
{
if (--memjit_depth == 0)
pthread_jit_write_protect_np(1);
if (--memjit_depth == 0 && memjit_protect)
memjit_protect(1);
}
#else
void memjit_write_begin(void) { }
+29 -13
View File
@@ -394,11 +394,25 @@ uint64_t mem_stats_total(void)
}
#else /* the iOS family */
{
uint64_t size = 0;
int mib[2] = { CTL_HW, HW_MEMSIZE };
size_t len = sizeof(size);
#ifdef TASK_VM_INFO_REV4_COUNT
/* What this process may use: its footprint plus the headroom left
* before jetsam. limit_bytes_remaining is revision 4 of the
* structure (iOS 13): read where the SDK declares it and the
* running kernel filled it in, which the count it hands back
* says. */
task_vm_info_data_t vm_info;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
if (task_info(mach_task_self(), TASK_VM_INFO,
(task_info_t)&vm_info, &count) == KERN_SUCCESS)
if ( task_info(mach_task_self(), TASK_VM_INFO,
(task_info_t)&vm_info, &count) == KERN_SUCCESS
&& count >= TASK_VM_INFO_REV4_COUNT)
return vm_info.phys_footprint + vm_info.limit_bytes_remaining;
#endif
/* Before that, the device's memory */
if (sysctl(mib, 2, &size, &len, NULL, 0) >= 0)
return size;
return 0;
}
#endif
@@ -551,7 +565,19 @@ uint64_t mem_stats_free(void)
return cached;
}
#elif defined(MEM_STATS_APPLE)
#if !TARGET_OS_IPHONE
#if TARGET_OS_IPHONE && defined(TASK_VM_INFO_REV4_COUNT)
/* iOS 13 on: the headroom left before jetsam, revision 4 of the
* structure - read only when the running kernel filled it in. Older
* systems take the page count below, as macOS does. */
{
task_vm_info_data_t vm_info;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
if ( task_info(mach_task_self(), TASK_VM_INFO,
(task_info_t)&vm_info, &count) == KERN_SUCCESS
&& count >= TASK_VM_INFO_REV4_COUNT)
return vm_info.limit_bytes_remaining;
}
#endif
/* Free plus reclaimable (inactive) pages, through the 32-bit
* host_statistics interface: it exists back to 10.0 and runs on 10.5
* kernels, unlike host_statistics64 (10.6+) or a task_vm_info
@@ -574,16 +600,6 @@ uint64_t mem_stats_free(void)
mach_port_deallocate(mach_task_self(), host);
return avail;
}
#else /* the iOS family */
{
task_vm_info_data_t vm_info;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
if (task_info(mach_task_self(), TASK_VM_INFO,
(task_info_t)&vm_info, &count) == KERN_SUCCESS)
return vm_info.limit_bytes_remaining;
return 0;
}
#endif
#elif defined(MEM_STATS_PROC)
{
uint64_t avail = 0;
+131 -3
View File
@@ -35,6 +35,7 @@
#include <net/net_socket_ssl.h>
#endif
#include <compat/strl.h>
#include <encodings/base64.h>
#include <features/features_cpu.h>
#include <lists/string_list.h>
#include <retro_common_api.h>
@@ -113,6 +114,19 @@
* URLs used to be followed forever. */
#define NET_HTTP_MAX_REDIRECTS 8
/* A transfer that moves nothing - no DNS answer, no byte sent or
* received - for this long fails. A peer that goes silent mid-request
* reports no error of its own, so this is the only end such a transfer
* has. Only time the caller spends driving the transfer counts: a gap
* of NET_HTTP_STALL_POLL_GAP between two calls is a caller that was not
* driving it (a suspended app, a paused queue), not a silent peer. */
#ifndef NET_HTTP_STALL_TIMEOUT
#define NET_HTTP_STALL_TIMEOUT ((retro_time_t)60 * 1000000)
#endif
#ifndef NET_HTTP_STALL_POLL_GAP
#define NET_HTTP_STALL_POLL_GAP ((retro_time_t)1000000)
#endif
enum response_part
{
P_HEADER_TOP = 0,
@@ -231,6 +245,7 @@ typedef struct request
void *postdata;
char *useragent;
char *headers;
char *auth;
size_t contentlength;
/* Streamed body: pulled from source in runs as the socket takes
* them, instead of being held whole in postdata. */
@@ -281,6 +296,14 @@ struct http_t
bool retried;
unsigned redirects;
/* For the stall bound: when the transfer last moved and how far it
* had got, and when it was last driven. */
retro_time_t moved_at;
retro_time_t polled_at;
size_t moved_rx;
size_t moved_tx;
unsigned moved_stage;
request_t request;
response_t response;
};
@@ -296,6 +319,7 @@ struct http_connection_t
void *postdata;
char *useragent;
char *headers;
char *auth;
size_t contentlength; /* ptr alignment */
net_http_sink_t sink;
void *sink_data;
@@ -713,6 +737,22 @@ int net_http_urldecode_inplace(char *s)
return string_percent_decode(s, (size_t)-1, s);
}
/* Base64 "user:pass" for Basic auth, from a URL's userinfo. */
static char *net_http_basic_auth(const char *userinfo, size_t len)
{
int flen;
char *out;
char *raw = (char*)malloc(len + 1);
if (!raw)
return NULL;
memcpy(raw, userinfo, len);
raw[len] = '\0';
out = base64(raw, net_http_urldecode_inplace(raw), &flen);
free(raw);
return out;
}
struct http_connection_t *net_http_connection_new(const char *url,
const char *method, const char *data)
{
@@ -748,11 +788,31 @@ struct http_connection_t *net_http_connection_new(const char *url,
}
else
goto error;
{
/* RFC 3986 3.2.1 userinfo, sent as Basic credentials */
char *end = conn->scan + strcspn(conn->scan, "/?#");
char *at = NULL;
char *p;
for (p = conn->scan; p < end; p++)
if (*p == '@')
at = p;
if (at)
{
if (at + 1 == end || at[1] == ':')
goto error;
/* An empty userinfo names no one: nothing is sent. */
if ( at != conn->scan
&& !(conn->auth = net_http_basic_auth(conn->scan, (size_t)(at - conn->scan))))
goto error;
conn->scan = at + 1;
}
}
if (*conn->scan == '\0')
goto error;
conn->domain = conn->scan;
return conn;
error:
free(conn->auth);
free(conn->url);
free(conn->method);
free(conn->postdata);
@@ -889,6 +949,9 @@ void net_http_connection_free(struct http_connection_t *conn)
if (conn->headers)
free(conn->headers);
if (conn->auth)
free(conn->auth);
free(conn);
}
@@ -1391,6 +1454,7 @@ struct http_t *net_http_new(struct http_connection_t *conn)
state->request.source_data = conn->source_data;
state->request.useragent= conn->useragent ? strdup(conn->useragent) : NULL;
state->request.headers = conn->headers ? strdup(conn->headers) : NULL;
state->request.auth = conn->auth ? strdup(conn->auth) : NULL;
state->request.port = conn->port;
state->response.status = -1;
@@ -1419,7 +1483,8 @@ struct http_t *net_http_new(struct http_connection_t *conn)
|| !state->request.method
|| (conn->contenttype && !state->request.contenttype)
|| (conn->useragent && !state->request.useragent)
|| (conn->headers && !state->request.headers))
|| (conn->headers && !state->request.headers)
|| (conn->auth && !state->request.auth))
{
/* Note: no postdata OOM check here. Ownership of postdata is
* moved from conn (above), not copied, so the transfer cannot
@@ -1434,6 +1499,9 @@ struct http_t *net_http_new(struct http_connection_t *conn)
return NULL;
}
state->moved_at = cpu_features_get_time_usec();
state->polled_at = state->moved_at;
return state;
}
@@ -1827,6 +1895,8 @@ static bool net_http_build_head(struct http_t *state)
size_t cap = 192
+ strlen(method) + strlen(request->path) + strlen(request->domain)
+ (request->headers ? strlen(request->headers) : 0)
+ (request->auth ? strlen(request->auth)
+ STRLEN_CONST("Authorization: Basic \r\n") : 0)
+ (request->contenttype ? strlen(request->contenttype) : 0)
+ (request->useragent ? strlen(request->useragent) : 0);
char *h;
@@ -1842,6 +1912,12 @@ static bool net_http_build_head(struct http_t *state)
if (request->port && request->port != 80 && request->port != 443)
n += (size_t)snprintf(h + n, cap - n, ":%i", request->port);
n = net_http_put(h, n, "\r\n");
if (request->auth)
{
n = net_http_put(h, n, "Authorization: Basic ");
n = net_http_put(h, n, request->auth);
n = net_http_put(h, n, "\r\n");
}
/* Pre-formatted headers */
if (request->headers)
n = net_http_put(h, n, request->headers);
@@ -2936,7 +3012,7 @@ static bool net_http_redirect(struct http_t *state, const char *location)
/* Credentials go only where the caller sent them: another host or
* port gets the request without Authorization or Cookie. Moving
* the same host from http onto https keeps them. */
if (state->request.headers)
if (state->request.headers || state->request.auth)
{
bool same_host = strlen(state->request.domain) == host_len
&& !strncasecmp(new_domain, state->request.domain, host_len);
@@ -2944,7 +3020,12 @@ static bool net_http_redirect(struct http_t *state, const char *location)
|| (!state->ssl && ssl
&& state->request.port == 80 && port == 443);
if (!same_host || !same_port)
net_http_headers_drop_credentials(state->request.headers);
{
if (state->request.headers)
net_http_headers_drop_credentials(state->request.headers);
free(state->request.auth);
state->request.auth = NULL;
}
}
free(state->request.domain);
@@ -3173,6 +3254,39 @@ bool net_http_wait(struct http_t *state, int timeout_ms)
return rd || wr;
}
/* True when the transfer has moved nothing for NET_HTTP_STALL_TIMEOUT
* of the time it was driven. Moving is any change in how far it got:
* a connection, a byte sent, a byte received, a stage passed. */
static bool net_http_stalled(struct http_t *state)
{
retro_time_t now = cpu_features_get_time_usec();
size_t rx = state->response.flushed + state->response.pos;
size_t tx = state->body_queued + state->out_off;
unsigned stage = (state->conn ? 1u : 0u)
| ((state->conn && state->conn->connected) ? 2u : 0u)
| (state->request_sent ? 4u : 0u)
| (state->retried ? 8u : 0u)
| ((unsigned)state->send_phase << 4)
| ((unsigned)state->response.part << 8)
| (state->redirects << 12);
if (now - state->polled_at > NET_HTTP_STALL_POLL_GAP)
state->moved_at += now - state->polled_at;
state->polled_at = now;
if ( rx != state->moved_rx
|| tx != state->moved_tx
|| stage != state->moved_stage)
{
state->moved_rx = rx;
state->moved_tx = tx;
state->moved_stage = stage;
state->moved_at = now;
return false;
}
return now - state->moved_at > NET_HTTP_STALL_TIMEOUT;
}
bool net_http_update(struct http_t *state, size_t* progress, size_t* total)
{
return net_http_update_budget(state, progress, total, NULL, NULL);
@@ -3189,6 +3303,18 @@ bool net_http_update_budget(struct http_t *state,
if (!state || state->err)
return true;
if (net_http_stalled(state))
{
net_http_log_transport_state(state, "stalled", -1);
if (state->conn)
net_http_conn_pool_remove(state->conn);
state->conn = NULL;
state->err = true;
state->response.part = P_DONE;
state->response.status = -1;
return true;
}
/* Re-established below wherever the pass ends without progress. */
state->blocked = false;
@@ -3728,6 +3854,8 @@ void net_http_delete(struct http_t *state)
free(state->request.useragent);
if (state->request.headers)
free(state->request.headers);
if (state->request.auth)
free(state->request.auth);
free(state);
}
+67
View File
@@ -20,17 +20,21 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <net/net_socket_ssl.h>
#include <net/net_socket.h>
#include <encodings/base64.h>
#include <streams/file_stream.h>
#include <string/stdstring.h>
#include <compat/strl.h>
#include <bearssl.h>
struct ssl_state
{
int fd;
char domain[256];
br_ssl_client_context sc;
br_x509_minimal_context xc;
uint8_t iobuf[BR_SSL_BUFSIZE_BIDI];
@@ -269,6 +273,8 @@ void* ssl_socket_init(int fd, const char *domain)
br_ssl_client_reset(&state->sc, domain, false);
state->fd = fd;
if (domain)
strlcpy(state->domain, domain, sizeof(state->domain));
return state;
}
@@ -300,6 +306,16 @@ static bool process_inner(struct ssl_state *state, bool blocking)
buf = br_ssl_engine_recvrec_buf(&state->sc.eng, &buflen);
if (buflen)
{
/* A blocking read waits for the server's next bytes, but no
* longer than SSL_SOCKET_IO_TIMEOUT_MS: a server that goes silent
* would otherwise hold recv() for good. */
if (blocking)
{
bool rd = true;
if ( !socket_wait(state->fd, &rd, NULL, SSL_SOCKET_IO_TIMEOUT_MS)
|| !rd)
return false;
}
/* if the socket is blocking, socket_receive_all_nonblocking blocks,
* but only to read at least 1 byte which is exactly what we want */
bytes = socket_receive_all_nonblocking(state->fd, &dummy, buf, buflen);
@@ -320,6 +336,51 @@ int ssl_socket_last_error(void *state_data)
return br_ssl_engine_last_error(&state->sc.eng);
}
/* Weak no-op logging hooks; RetroArch overrides these in network/tls_log.c.
* See net_socket_ssl_mbed.c for why they are weak and not in griffin. */
#if (defined(__GNUC__) || defined(__clang__)) && !defined(HAVE_GRIFFIN)
__attribute__((weak))
void ssl_socket_log_verify_fail(int mode_required, const char *domain,
const char *verify_info)
{
(void)mode_required; (void)domain; (void)verify_info;
}
__attribute__((weak))
void ssl_socket_log_verify_disabled(const char *domain)
{
(void)domain;
}
#endif
/* A handshake that ended on the certificate goes to the verify hook,
* as the other backends' refusals do. BearSSL always verifies, so every
* such failure is a required one. */
static void ssl_bear_report_verify(struct ssl_state *state)
{
char info[64];
int err = br_ssl_engine_last_error(&state->sc.eng);
if (err <= BR_ERR_X509_OK || err >= BR_ERR_X509_OK + 32)
return;
switch (err)
{
case BR_ERR_X509_EXPIRED:
strlcpy(info, "certificate expired or not yet valid", sizeof(info));
break;
case BR_ERR_X509_BAD_SERVER_NAME:
strlcpy(info, "certificate does not match the host", sizeof(info));
break;
case BR_ERR_X509_NOT_TRUSTED:
strlcpy(info, "certificate is not trusted", sizeof(info));
break;
default:
sprintf(info, "BearSSL X.509 error %d", err);
break;
}
ssl_socket_log_verify_fail(1, state->domain, info);
}
int ssl_socket_connect(void *state_data,
void *data, bool timeout_enable, bool nonblock)
{
@@ -343,13 +404,19 @@ int ssl_socket_connect(void *state_data,
for (;;)
{
if (!process_inner(state, true))
{
ssl_bear_report_verify(state);
return -1;
}
bearstate = br_ssl_engine_current_state(&state->sc.eng);
if (bearstate & BR_SSL_SENDAPP)
break; /* handshake done */
if (bearstate & BR_SSL_CLOSED)
{
ssl_bear_report_verify(state);
return -1; /* failed */
}
}
return 1;
+19 -1
View File
@@ -365,9 +365,25 @@ int ssl_socket_connect(void *state_data,
mbedtls_ssl_set_bio(&state->ctx, &state->net_ctx, mbedtls_net_send, mbedtls_net_recv, NULL);
/* The handshake runs on a non-blocking socket, each wait for the
* server bounded: on a blocking one, a server that goes silent holds
* recv() for good. */
if (!socket_set_block(state->net_ctx.fd, false))
return -1;
while ((ret = mbedtls_ssl_handshake(&state->ctx)) != 0)
{
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE)
if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE)
{
bool rd = (ret == MBEDTLS_ERR_SSL_WANT_READ);
bool wr = !rd;
if ( !socket_wait(state->net_ctx.fd, &rd, &wr, SSL_SOCKET_IO_TIMEOUT_MS)
|| !(rd || wr))
{
state->last_err = MBEDTLS_ERR_SSL_TIMEOUT;
return -1;
}
}
else
{
state->last_err = ret;
/* Fail-closed: under REQUIRED a bad certificate makes the
@@ -384,6 +400,8 @@ int ssl_socket_connect(void *state_data,
return -1;
}
}
if (!socket_set_block(state->net_ctx.fd, true))
return -1;
if ((flags = mbedtls_ssl_get_verify_result(&state->ctx)) != 0)
{
+23 -2
View File
@@ -623,6 +623,27 @@ static int tls_send_record(struct ssl_state *s, uint8_t type,
return 0;
}
/* The socket is in blocking mode, so recv() itself would wait for good
* on a server that stops mid-handshake or mid-record; each recv() here
* waits for the socket to be readable first, for at most
* SSL_SOCKET_IO_TIMEOUT_MS, and a wait that runs out fails the read. */
static bool tls_recv_exact(struct ssl_state *s, uint8_t *buf, size_t len)
{
while (len)
{
bool rd = true;
bool err = false;
ssize_t n;
if (!socket_wait(s->fd, &rd, NULL, SSL_SOCKET_IO_TIMEOUT_MS) || !rd)
return false;
if ((n = socket_receive_all_nonblocking(s->fd, &err, buf, len)) < 0)
return false;
buf += n;
len -= (size_t)n;
}
return true;
}
/* Reads one record into s->rx, decrypting in place once the server
* write keys are in force. On return *type is the content type, and
* *body / *len the plaintext. */
@@ -635,7 +656,7 @@ static int tls_read_record_to(struct ssl_state *s, uint8_t *type,
{
size_t rlen;
if (socket_receive_all_blocking(s->fd, s->rx, 5) <= 0)
if (!tls_recv_exact(s, s->rx, 5))
{
s->last_err = TLS_ERR_SOCKET;
return -1;
@@ -646,7 +667,7 @@ static int tls_read_record_to(struct ssl_state *s, uint8_t *type,
s->last_err = TLS_ERR_RECORD;
return -1;
}
if (socket_receive_all_blocking(s->fd, s->rx + 5, rlen) <= 0)
if (!tls_recv_exact(s, s->rx + 5, rlen))
{
s->last_err = TLS_ERR_SOCKET;
return -1;
+21
View File
@@ -1237,11 +1237,31 @@ static bool retro_task_sync_primitives_new(void)
return false;
}
/* A switch from the regular queue hands over tasks it never counted.
* Called before any worker can touch the lists. */
static void retro_task_threaded_count_lists(void)
{
retro_task_t *task;
int running = 0;
int finished = 0;
for (task = tasks_running.front; task; task = task->next)
running++;
for (task = tasks_finished.front; task; task = task->next)
finished++;
/* Leftovers of an earlier threaded gather, as it marks them */
if (!finished && tasks_retiring.front)
finished = 1;
retro_atomic_store_release_int(&tasks_running_count, running);
retro_atomic_store_release_int(&tasks_finished_count, finished);
}
static bool retro_task_threaded_init(void)
{
if (!retro_task_sync_primitives_new())
return false;
retro_task_threaded_count_lists();
if (!(worker_self = (struct task_worker*)calloc(1, sizeof(*worker_self))))
{
retro_task_sync_primitives_free();
@@ -1495,6 +1515,7 @@ static bool retro_task_gcd_init(void)
slock_lock(running_lock);
worker_continue = true;
retro_task_threaded_count_lists();
for (task = tasks_running.front; task; task = task->next)
{
if (task_is_main_thread_task(task))
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := retro_atomic_test
LIBRETRO_COMM_DIR := ../../..
@@ -22,7 +24,7 @@ ifeq ($(HAVE_THREADS),1)
LDFLAGS += -lrt
endif
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
ifneq ($(SANITIZER),)
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
@@ -34,6 +36,10 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
@@ -51,6 +57,7 @@ check-barriers:
./check-barriers.sh
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: all check check-barriers clean
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := audio_mixer_loop_test
LIBRETRO_COMM_DIR := ../../..
@@ -47,7 +49,7 @@ COMMON_SOURCES := \
$(LIBRETRO_COMM_DIR)/time/rtime.c \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
COMMON_OBJS := $(COMMON_SOURCES:.c=.o)
COMMON_OBJS := $(call sample_objs,$(COMMON_SOURCES:.c=.o))
OBJS := audio_mixer_loop_test.o $(COMMON_OBJS)
CFLAGS += -Wall -pedantic -std=gnu99 -g -O0 -I$(LIBRETRO_COMM_DIR)/include \
@@ -64,10 +66,15 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: clean
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := audio_mixer_rate_guard_test
LIBRETRO_COMM_DIR := ../../..
@@ -39,7 +41,7 @@ COMMON_SOURCES := \
$(LIBRETRO_COMM_DIR)/time/rtime.c \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
COMMON_OBJS := $(COMMON_SOURCES:.c=.o)
COMMON_OBJS := $(call sample_objs,$(COMMON_SOURCES:.c=.o))
OBJS := audio_mixer_rate_guard_test.o $(COMMON_OBJS)
CFLAGS += -Wall -pedantic -std=gnu99 -g -O0 -I$(LIBRETRO_COMM_DIR)/include \
@@ -56,10 +58,15 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: clean
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := audio_mixer_voice_threads_test
LIBRETRO_COMM_DIR := ../../..
@@ -39,7 +41,7 @@ COMMON_SOURCES := \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c \
$(LIBRETRO_COMM_DIR)/rthreads/rthreads.c
COMMON_OBJS := $(COMMON_SOURCES:.c=.o)
COMMON_OBJS := $(call sample_objs,$(COMMON_SOURCES:.c=.o))
OBJS := audio_mixer_voice_threads_test.o $(COMMON_OBJS)
CFLAGS += -Wall -pedantic -std=gnu99 -g -O0 -I$(LIBRETRO_COMM_DIR)/include \
@@ -56,10 +58,15 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: all clean
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := float_to_s32_test
LIBRETRO_COMM_DIR := ../../..
@@ -7,7 +9,7 @@ COMMON_SOURCES := \
$(LIBRETRO_COMM_DIR)/features/features_cpu.c \
$(LIBRETRO_COMM_DIR)/compat/compat_strl.c
COMMON_OBJS := $(COMMON_SOURCES:.c=.o)
COMMON_OBJS := $(call sample_objs,$(COMMON_SOURCES:.c=.o))
OBJS := float_to_s32_test.o $(COMMON_OBJS)
CFLAGS += -Wall -pedantic -std=gnu99 -g -O2 -I$(LIBRETRO_COMM_DIR)/include
@@ -23,10 +25,15 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: clean
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := compat_fnmatch_test
LIBRETRO_COMM_DIR := ../../..
@@ -6,7 +8,7 @@ SOURCES := \
compat_fnmatch_test.c \
$(LIBRETRO_COMM_DIR)/compat/compat_fnmatch.c
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
CFLAGS += -Wall -pedantic -std=gnu99 -g -I$(LIBRETRO_COMM_DIR)/include
@@ -25,10 +27,15 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: clean
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := snprintf
CORE_DIR := .
@@ -7,7 +9,7 @@ SOURCES_C := \
$(CORE_DIR)/snprintf_test.c \
$(LIBRETRO_COMM_DIR)/compat/compat_snprintf.c
OBJS := $(SOURCES_C:.c=.o)
OBJS := $(call sample_objs,$(SOURCES_C:.c=.o))
CFLAGS += -Wall -pedantic -std=gnu99 -I$(LIBRETRO_COMM_DIR)/include
@@ -26,10 +28,15 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: clean
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := unbase64_test
CORE_DIR := .
@@ -7,7 +9,7 @@ SOURCES_C := \
$(CORE_DIR)/unbase64_test.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_base64.c
OBJS := $(SOURCES_C:.c=.o)
OBJS := $(call sample_objs,$(SOURCES_C:.c=.o))
CFLAGS += -Wall -pedantic -std=gnu99 -I$(LIBRETRO_COMM_DIR)/include
@@ -26,10 +28,15 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: clean
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := crc32_test
LIBRETRO_COMM_DIR := ../../..
@@ -26,7 +28,7 @@ ifeq ($(HAVE_THREADS),1)
LDFLAGS += -lrt
endif
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
ifneq ($(SANITIZER),)
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
@@ -38,10 +40,15 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: clean
+35 -7
View File
@@ -1,37 +1,65 @@
TARGET := rinflate_window_test
include ../../sample_objdir.mk
TARGET := rinflate_window_test
TARGET_TEST := rdeflate_hash_test
LIBRETRO_COMM_DIR := ../../..
# encoding_deflate.c folds CRC-32 through encoding_crc32.c, which asks
# cpu_features_get() for PCLMULQDQ; features_cpu.c needs compat_strl.c.
# cpu_features_get() for PCLMULQDQ; features_cpu.c needs compat_strl.c,
# and on Windows rtime.c (retro_sleep_us).
SOURCES := \
rinflate_window_test.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_deflate.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_crc32.c \
$(LIBRETRO_COMM_DIR)/features/features_cpu.c \
$(LIBRETRO_COMM_DIR)/compat/compat_strl.c
$(LIBRETRO_COMM_DIR)/compat/compat_strl.c \
$(LIBRETRO_COMM_DIR)/time/rtime.c
CFLAGS += -Wall -pedantic -std=gnu99 -g -O2 -I$(LIBRETRO_COMM_DIR)/include
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
# The hash test compiles encoding_deflate.c into itself; it needs the
# rest of what the deflater links, not the window test.
HASH_SOURCES := rdeflate_hash_test.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_crc32.c \
$(LIBRETRO_COMM_DIR)/features/features_cpu.c \
$(LIBRETRO_COMM_DIR)/compat/compat_strl.c \
$(LIBRETRO_COMM_DIR)/time/rtime.c
# x86: built for a CPU without SSE4.2, so the hash is the run-time one
HASH_ARCH :=
ifneq ($(filter x86_64 i%86,$(shell $(CC) -dumpmachine 2>/dev/null | cut -d- -f1)),)
HASH_ARCH := -march=core2
endif
ifneq ($(SANITIZER),)
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif
all: $(TARGET)
all: $(TARGET) $(TARGET_TEST)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
check: $(TARGET)
$(TARGET_TEST): $(HASH_SOURCES) $(LIBRETRO_COMM_DIR)/encodings/encoding_deflate.c
$(CC) $(CFLAGS) $(HASH_ARCH) -o $@ $(HASH_SOURCES) $(LDFLAGS)
check: all
./$(TARGET)
./$(TARGET_TEST)
clean:
rm -f $(TARGET) $(OBJS)
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(TARGET_TEST) $(TARGET).exe $(TARGET_TEST).exe $(OBJS)
.PHONY: all check clean
@@ -0,0 +1,70 @@
/* The CRC32C match-finder hash in encodings/encoding_deflate.c, as a
* build without SSE4.2 (-march=core2) reaches it at run time:
* rd_hash_crc() must give what the CRC32C of the three-byte key does,
* computed here bit by bit, for each value of each key byte and for
* random keys.
*
* Without the runtime path (another architecture, an old compiler) or
* on a CPU without SSE4.2 there is nothing to check, and it says so. */
#include <stdio.h>
#include <stdlib.h>
#include "../../../encodings/encoding_deflate.c"
/* CRC32C (Castagnoli, reflected) of the 32-bit little-endian word @v,
* from @crc, with no pre- or post-inversion: what the crc32 instruction
* computes */
static uint32_t crc32c_word(uint32_t crc, uint32_t v)
{
int i;
crc ^= v;
for (i = 0; i < 32; i++)
crc = (crc >> 1) ^ (0x82F63B78u & (0u - (crc & 1u)));
return crc;
}
int main(void)
{
#if defined(RD_CRC32_HASH_RUNTIME)
uint32_t seed = 0x12345678u;
unsigned i, fails = 0;
if (!(cpu_features_get() & RETRO_SIMD_SSE42))
{
printf("rdeflate_hash: no SSE4.2 on this CPU, nothing to check\n");
return 0;
}
for (i = 0; i < 200000; i++)
{
uint8_t p[3];
uint32_t k, want, got;
seed = seed * 1664525u + 1013904223u;
p[0] = (uint8_t)(seed >> 8);
p[1] = (uint8_t)(seed >> 16);
p[2] = (uint8_t)(seed >> 24);
if (i < 256)
{
/* every value of each byte on its own */
p[0] = (uint8_t)i;
p[1] = p[2] = (uint8_t)(i * 7);
}
k = (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16);
want = crc32c_word(0, k) & RD_HASH_MASK;
got = rd_hash_crc(p);
if (got != want && fails++ < 5)
printf("FAIL: key %02x %02x %02x hashes to %04x, CRC32C says %04x\n",
p[0], p[1], p[2], (unsigned)got, (unsigned)want);
}
if (fails)
{
printf("rdeflate_hash: %u of 200000 keys wrong\n", fails);
return 1;
}
printf("rdeflate_hash: 200000 keys hash as CRC32C\n");
#else
printf("rdeflate_hash: no runtime CRC32 hash in this build, nothing to check\n");
#endif
return 0;
}
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := vcdiff_test
LIBRETRO_COMM_DIR := ../../..
@@ -6,7 +8,7 @@ SOURCES := \
vcdiff_test.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_vcdiff.c
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
CFLAGS += -Wall -pedantic -std=gnu99 -g -O0 -I$(LIBRETRO_COMM_DIR)/include
@@ -20,10 +22,15 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: clean
+8 -1
View File
@@ -1,8 +1,10 @@
include ../../sample_objdir.mk
TARGET := fastjmp_test
LIBRETRO_COMM_DIR := ../../..
SOURCES := fastjmp_test.c $(LIBRETRO_COMM_DIR)/fastjmp/fastjmp.c
CFLAGS += -Wall -W -std=gnu99 -g -O2 -I$(LIBRETRO_COMM_DIR)/include
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
ifneq ($(SAN),)
SANITIZER := $(SAN)
endif
@@ -13,10 +15,15 @@ endif
all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
check: all
./$(TARGET)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(OBJS) $(TARGET)
.PHONY: all check clean
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := faulthandler_test
LIBRETRO_COMM_DIR := ../../..
SOURCES := faulthandler_test.c \
@@ -6,7 +8,7 @@ SOURCES := faulthandler_test.c \
$(LIBRETRO_COMM_DIR)/rthreads/rthreads.c
CFLAGS += -Wall -W -std=gnu99 -g -O1 -I$(LIBRETRO_COMM_DIR)/include -DHAVE_THREADS
LDFLAGS += -lpthread -lrt
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
ifneq ($(SAN),)
SANITIZER := $(SAN)
endif
@@ -17,10 +19,15 @@ endif
all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
check: all
./$(TARGET)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(OBJS) $(TARGET)
.PHONY: all check clean
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := archive_zip_test
LIBRETRO_COMM_DIR := ../../..
@@ -25,7 +27,7 @@ SOURCES := \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c \
$(LIBRETRO_COMM_DIR)/time/rtime.c
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
override CPPFLAGS += -DHAVE_COMPRESSION -I$(LIBRETRO_COMM_DIR)/include
CFLAGS += -Wall -pedantic -std=gnu99 -g
@@ -45,6 +47,10 @@ all: $(TARGET)
%.o: %.c Makefile
$(CC) -c -o $@ $< $(CPPFLAGS) $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c Makefile
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CPPFLAGS) $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
@@ -52,6 +58,7 @@ check: $(TARGET)
./$(TARGET)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: all check clean
+67
View File
@@ -0,0 +1,67 @@
include ../../sample_objdir.mk
TARGET := archive_rar_test
LIBRETRO_COMM_DIR := ../../..
SOURCES := \
archive_rar_test.c \
$(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \
$(LIBRETRO_COMM_DIR)/compat/compat_strl.c \
$(LIBRETRO_COMM_DIR)/compat/compat_strcasestr.c \
$(LIBRETRO_COMM_DIR)/compat/compat_posix_string.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_crc32.c \
$(LIBRETRO_COMM_DIR)/features/features_cpu.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \
$(LIBRETRO_COMM_DIR)/file/archive_file.c \
$(LIBRETRO_COMM_DIR)/file/archive_file_zlib.c \
$(LIBRETRO_COMM_DIR)/file/archive_file_rar.c \
$(LIBRETRO_COMM_DIR)/formats/rar/rrar_archive.c \
$(LIBRETRO_COMM_DIR)/formats/rar/rrar_ppmd7.c \
$(LIBRETRO_COMM_DIR)/file/file_path.c \
$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
$(LIBRETRO_COMM_DIR)/lists/string_list.c \
$(LIBRETRO_COMM_DIR)/streams/file_stream.c \
$(LIBRETRO_COMM_DIR)/string/rstrtod.c \
$(LIBRETRO_COMM_DIR)/streams/trans_stream.c \
$(LIBRETRO_COMM_DIR)/streams/trans_stream_pipe.c \
$(LIBRETRO_COMM_DIR)/streams/trans_stream_deflate.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_deflate.c \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c \
$(LIBRETRO_COMM_DIR)/time/rtime.c
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
override CPPFLAGS += -DHAVE_COMPRESSION -DHAVE_RRAR -I$(LIBRETRO_COMM_DIR)/include
CFLAGS += -Wall -pedantic -std=gnu99 -g
ifneq ($(SANITIZER),)
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif
ifneq ($(SANITIZER),)
CFLAGS := -fsanitize=$(SANITIZER) $(CFLAGS)
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif
all: $(TARGET)
%.o: %.c Makefile
$(CC) -c -o $@ $< $(CPPFLAGS) $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c Makefile
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CPPFLAGS) $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
check: $(TARGET)
./$(TARGET)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: all check clean
+212
View File
@@ -0,0 +1,212 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (archive_rar_test.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.
*/
/* RAR archives through the archive layer, as the frontend reaches them:
* a .rar path is an archive (the file browser's Open Archive / Load
* Archive), "x.rar#member" names a member, the layer picks the RAR
* backend, lists the members it can unpack, reads one into memory or
* out to a file, and gives its CRC-32. The archives are the reader's
* own, in ../../formats/rrar/archives. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <boolean.h>
#include <compat/strl.h>
#include <file/archive_file.h>
#include <file/file_path.h>
#include <lists/string_list.h>
#include <encodings/crc32.h>
#include <streams/file_stream.h>
#include <string/stdstring.h>
static unsigned failures = 0;
static void fail(const char *what, const char *detail)
{
fprintf(stderr, "FAIL %s: %s\n", what, detail ? detail : "");
failures++;
}
static void archive_path(char *s, size_t len, const char *dir,
const char *archive, const char *member)
{
strlcpy(s, dir, len);
strlcat(s, "/", len);
strlcat(s, archive, len);
if (member)
{
strlcat(s, "#", len);
strlcat(s, member, len);
}
}
static void check_paths(void)
{
const char *p = "roms/set.rar#dir/game.bin";
const struct file_archive_file_backend *b =
file_archive_get_file_backend("roms/Set.RAR");
if (!path_is_compressed_file("roms/set.rar"))
fail("path_is_compressed_file", "set.rar is not an archive");
if (!path_is_compressed_file("roms/SET.Rar"))
fail("path_is_compressed_file", "SET.Rar is not an archive");
if (path_is_compressed_file("roms/set.rarx"))
fail("path_is_compressed_file", "set.rarx is an archive");
if (path_get_archive_delim(p) != p + strlen("roms/set.rar"))
fail("path_get_archive_delim", p);
if (!b || b != file_archive_get_rar_file_backend()
|| !string_is_equal(b->ident, "rar"))
fail("file_archive_get_file_backend", "not the RAR backend");
}
static bool list_has(const struct string_list *list, const char *name)
{
size_t i;
for (i = 0; list && i < list->size; i++)
if (string_is_equal(list->elems[i].data, name))
return true;
return false;
}
static void check_list(const char *dir)
{
char path[2048];
struct string_list *list;
archive_path(path, sizeof(path), dir, "compress_best.rar", NULL);
if (!(list = file_archive_get_file_list(path, NULL)))
fail("list compress_best.rar", "no list");
else
{
if ( list->size != 4
|| !list_has(list, "LibarchiveAddingTest.html")
|| !list_has(list, "testlink")
|| !list_has(list, "testdir/test.txt")
|| !list_has(list, "testdir/LibarchiveAddingTest.html"))
fail("list compress_best.rar", "not its four members");
string_list_free(list);
}
/* Solid: only the first member stands alone, the rest are passed
* over rather than listed and then refused. */
archive_path(path, sizeof(path), dir, "rar5_multiple_files_solid.rar", NULL);
if (!(list = file_archive_get_file_list(path, NULL)))
fail("list rar5_multiple_files_solid.rar", "no list");
else
{
if (list->size != 1 || !list_has(list, "test1.bin"))
fail("list rar5_multiple_files_solid.rar", "not its first member alone");
string_list_free(list);
}
}
static void check_read(const char *dir, const char *archive,
const char *member, int64_t size, uint32_t crc)
{
char path[2048];
void *buf = NULL;
int64_t len = 0;
archive_path(path, sizeof(path), dir, archive, member);
if (!file_archive_compressed_read(path, &buf, NULL, &len) || !buf)
fail("read", path);
else if (len != size)
fail("read size", path);
else if (encoding_crc32(0, (const uint8_t*)buf, (size_t)len) != crc)
fail("read CRC-32", path);
free(buf);
if (file_archive_get_file_crc32(path) != crc)
fail("file_archive_get_file_crc32", path);
}
static void check_extract(const char *dir)
{
char path[2048];
const char *out = "archive_rar_test.out";
void *buf = NULL;
int64_t len = 0;
filestream_delete(out);
archive_path(path, sizeof(path), dir, "rar5_stored.rar", "helloworld.txt");
if (!file_archive_compressed_read(path, NULL, out, &len))
fail("extract", path);
else if (!filestream_read_file(out, &buf, &len) || len != 29
|| encoding_crc32(0, (const uint8_t*)buf, (size_t)len) != 0x95a043b4u)
fail("extracted file", out);
free(buf);
filestream_delete(out);
}
static void check_refused(const char *dir)
{
char path[2048];
void *buf = NULL;
int64_t len = 0;
RFILE *f;
struct string_list *list;
const char *junk = "archive_rar_test_junk.rar";
archive_path(path, sizeof(path), dir, "rar5_multiple_files_solid.rar", "test2.bin");
if (file_archive_compressed_read(path, &buf, NULL, &len))
fail("solid member", "read where it cannot be unpacked alone");
free(buf);
/* Named .rar, not a RAR archive. */
if ((f = filestream_open(junk, RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
{
static const char not_rar[] = "PK\x03\x04 not a rar archive";
filestream_write(f, not_rar, sizeof(not_rar) - 1);
filestream_close(f);
}
list = file_archive_get_file_list(junk, NULL);
if (list && list->size)
fail("junk.rar", "listed members");
string_list_free(list);
filestream_delete(junk);
}
int main(int argc, char *argv[])
{
const char *dir = argc > 1 ? argv[1] : "../../formats/rrar/archives";
check_paths();
check_list(dir);
check_read(dir, "compress_best.rar", "testdir/test.txt", 20, 0xbec8a242u);
check_read(dir, "compress_best.rar", "LibarchiveAddingTest.html", 20111, 0x5e05a663u);
check_read(dir, "rar5_compressed.rar", "test.bin", 1200, 0x7cca70cdu);
check_read(dir, "rar5_arm.rar", "elf-Linux-ARMv7-ls", 90808, 0x886f91ebu);
check_read(dir, "filter.rar", "bsdcat.exe", 204288, 0x4db10349u);
check_extract(dir);
check_refused(dir);
if (failures)
{
fprintf(stderr, "%u failure(s)\n", failures);
return 1;
}
printf("PASS archive_rar_test\n");
return 0;
}
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := config_file_test
LIBRETRO_COMM_DIR := ../../..
@@ -19,7 +21,7 @@ SOURCES := \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c \
$(LIBRETRO_COMM_DIR)/time/rtime.c
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
# CONFIG_FILE_TEST_HOOKS exposes config_file_force_linked_sort, which
# lets the suite reach config_file.c's zero-allocation sort fallback
@@ -36,10 +38,15 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: clean
@@ -1632,6 +1632,78 @@ static void test_config_setter_entries_are_pooled(void)
"survive replace, unset and append\n");
}
/* A value with double quotes, backslashes and '#' in it is written so
* that reading the file back gives the same value: every string over a
* small alphabet up to five characters, written by config_file_dump
* and read back by the parser (with a comment after the value too). */
static void test_config_quoted_values_round_trip(void)
{
static const char alphabet[] = { 'a', '"', '\\', '#', ' ', '/' };
enum { K = sizeof(alphabet), MAXLEN = 5 };
char val[MAXLEN + 1];
unsigned digits[MAXLEN];
unsigned len, i, checked = 0;
for (len = 0; len <= MAXLEN; len++)
{
memset(digits, 0, sizeof(digits));
for (;;)
{
int pass;
for (i = 0; i < len; i++)
val[i] = alphabet[digits[i]];
val[len] = '\0';
for (pass = 0; pass < 2; pass++)
{
config_file_t *out = config_file_new_alloc();
config_file_t *in;
char *text, *line, *got = NULL;
config_set_string(out, "k", val);
text = cfg_dump_to_string(out, false);
config_file_free(out);
if (pass)
{
/* the same line with a comment after it */
size_t n = strlen(text);
char *t = (char*)malloc(n + 32);
memcpy(t, text, n);
while (n && (t[n - 1] == '\n' || t[n - 1] == '\r'))
n--;
strcpy(t + n, " # \"note\" #\n");
free(text);
text = t;
}
line = strdup(text);
in = config_file_new_from_string(line, NULL);
free(line);
if (!in || !config_get_string(in, "k", &got)
|| strcmp(got ? got : "", val))
{
printf("[FAILED] value [%s] written as [%s] read back as [%s]\n",
val, text, got ? got : "(none)");
abort();
}
free(got);
free(text);
config_file_free(in);
checked++;
}
for (i = 0; i < len; i++)
{
if (++digits[i] < K)
break;
digits[i] = 0;
}
if (i == len)
break;
}
}
printf("[SUCCESS] %u quoted values round-trip\n", checked);
}
int main(void)
{
test_config_file_parse_contains("foo = \"bar\"\n", "foo", "bar");
@@ -1697,4 +1769,23 @@ int main(void)
test_config_set_over_parsed_keys_round_trips();
test_config_set_insert_is_not_quadratic();
test_config_setter_entries_are_pooled();
/* Escaped quotes inside a value; other backslashes are kept */
test_config_file_parse_contains(
"k = \"it's a \\\"secret\\\".\"\n", "k", "it's a \"secret\".");
test_config_file_parse_contains(
"k = \"C:\\Games\\ROMs\"\n", "k", "C:\\Games\\ROMs");
test_config_file_parse_contains(
"k = \"C:\\Games\\\\\"\n", "k", "C:\\Games\\");
test_config_file_parse_contains(
"k = \"a \\\"#b\\\"\" # c\n", "k", "a \"#b\"");
/* Written before values were escaped: a directory ending in a
* backslash keeps it, with a comment after it or not */
test_config_file_parse_contains(
"k = \"C:\\Games\\\"\n", "k", "C:\\Games\\");
test_config_file_parse_contains(
"k = \"C:\\Games\\\" # c\n", "k", "C:\\Games\\");
test_config_file_parse_contains(
"k = \"\\\\server\\share\"\n", "k", "\\\\server\\share");
test_config_quoted_values_round_trip();
}
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGETS := path_resolve_realpath_test fill_pathname_test path_mkdir_test
LIBRETRO_COMM_DIR := ../../..
@@ -16,7 +18,7 @@ COMMON_SOURCES := \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c \
$(LIBRETRO_COMM_DIR)/time/rtime.c
COMMON_OBJS := $(COMMON_SOURCES:.c=.o)
COMMON_OBJS := $(call sample_objs,$(COMMON_SOURCES:.c=.o))
CFLAGS += -Wall -pedantic -std=gnu99 -g -DRARCH_INTERNAL -I$(LIBRETRO_COMM_DIR)/include
@@ -35,10 +37,15 @@ all: $(TARGETS)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGETS): %: %.o $(COMMON_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGETS) $(addsuffix .o,$(TARGETS)) $(COMMON_OBJS)
.PHONY: all clean
+16 -4
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := vfs_read_overflow_test
TARGET_TEST := vfs_mapped_ptr_test
TARGET_TEST2 := vfs_large_file_test
@@ -6,6 +8,7 @@ TARGET_TEST4 := vfs_bulk_read_test
TARGET_TEST5 := filestream_rbuf_fault_test
TARGET_TEST6 := vfs_v5_metadata_test
TARGET_TEST7 := vfs_rename_replace_test
TARGET_TEST8 := vfs_prefetch_test
LIBRETRO_COMM_DIR := ../../..
@@ -21,11 +24,12 @@ COMMON_SOURCES := \
$(LIBRETRO_COMM_DIR)/time/rtime.c \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
COMMON_OBJS := $(COMMON_SOURCES:.c=.o)
COMMON_OBJS := $(call sample_objs,$(COMMON_SOURCES:.c=.o))
OBJS := vfs_read_overflow_test.o vfs_mapped_ptr_test.o \
vfs_large_file_test.o vfs_seek_contract_test.o \
vfs_bulk_read_test.o filestream_rbuf_fault_test.o \
vfs_v5_metadata_test.o vfs_rename_replace_test.o $(COMMON_OBJS)
vfs_v5_metadata_test.o vfs_rename_replace_test.o \
vfs_prefetch_test.o $(COMMON_OBJS)
# filestream_rbuf_fault_test drives the two arms on which
# filestream_rbuf_fill() gives up and hands the caller back to the
@@ -114,17 +118,24 @@ ifeq ($(UNAME_S),Linux)
endif
all: $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) $(TARGET_TEST4) \
$(TARGET_TEST5) $(TARGET_TEST6) $(RENAME_TARGET)
$(TARGET_TEST5) $(TARGET_TEST6) $(TARGET_TEST8) $(RENAME_TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): vfs_read_overflow_test.o $(COMMON_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
$(TARGET_TEST): vfs_mapped_ptr_test.o $(COMMON_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
$(TARGET_TEST8): vfs_prefetch_test.o $(COMMON_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
# vfs_large_file_test builds sparse fixtures of 3 GiB and 5 GiB. They
# cost no space on a filesystem with sparse files and are removed
# afterwards; where the filesystem cannot hold them the test skips.
@@ -170,9 +181,10 @@ $(TARGET_TEST7): vfs_rename_replace_test.o $(RENAME_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) -Wl,--wrap=rename,--wrap=remove,--wrap=stat
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) \
$(TARGET_TEST4) $(TARGET_TEST5) $(TARGET_TEST6) \
$(TARGET_TEST7) $(OBJS) $(FAULT_FS_OBJ) $(RENAME_VFS_OBJ)
$(TARGET_TEST7) $(TARGET_TEST8) $(OBJS) $(FAULT_FS_OBJ) $(RENAME_VFS_OBJ)
rm -rf v5_meta_dir
rm -f large_3gib.bin large_5gib.bin seek_small.bin seek_4gib.bin
rm -f rbuf_fault.txt
+199
View File
@@ -0,0 +1,199 @@
/* filestream_prefetch(): the read-ahead hint on a local file.
*
* The hint asks the OS to start bringing a range of the file in, so a
* read or a touch of the mapping that follows does not wait on the
* disk. It can only be a hint, which bounds what a test may assert:
* the OS may ignore it, and whether a page is resident afterwards is a
* race with the readahead. What is pinned is the contract around it:
*
* - every shape of call returns: a range inside the file, one
* starting inside and running past the end, one entirely past
* the end, a zero length, a NULL stream;
* - the file's contents are unchanged by any of them, read through
* the mapping (FREQUENT_ACCESS) and through the descriptor;
* - a frontend VFS gets no call at all: with the read callbacks
* installed the hint is a no-op and nothing is dereferenced.
*
* Build: make (SANITIZER=address,undefined for a checked run)
* Run: ./vfs_prefetch_test
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <boolean.h>
#include <streams/file_stream.h>
#include <vfs/vfs_implementation.h>
#define FIXTURE_LEN (1024 * 1024 + 777)
static int test_fails;
#define CHECK(cond, msg) \
do { \
if (!(cond)) \
{ \
printf("FAIL: %s\n", msg); \
test_fails++; \
} \
} while (0)
static uint8_t pattern(size_t i)
{
return (uint8_t)((i * 2654435761u) >> 13);
}
static bool write_fixture(const char *path)
{
RFILE *f = filestream_open(path, RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
uint8_t *buf;
size_t i;
bool ok;
if (!f)
return false;
if (!(buf = (uint8_t*)malloc(FIXTURE_LEN)))
{
filestream_close(f);
return false;
}
for (i = 0; i < FIXTURE_LEN; i++)
buf[i] = pattern(i);
ok = filestream_write(f, buf, FIXTURE_LEN) == FIXTURE_LEN;
free(buf);
filestream_close(f);
return ok;
}
/* Prefetches every shape of range on @f, then reads the whole file
* back and compares it with the pattern. */
static void check_stream(RFILE *f, const char *what)
{
uint8_t *buf = (uint8_t*)malloc(FIXTURE_LEN);
size_t i;
int64_t got;
CHECK(buf != NULL, what);
if (!buf)
return;
filestream_prefetch(f, 0, 65536);
filestream_prefetch(f, 4096 + 100, 300000);
filestream_prefetch(f, FIXTURE_LEN - 10, 1000000);
filestream_prefetch(f, FIXTURE_LEN, 4096);
filestream_prefetch(f, FIXTURE_LEN + 1000000, 4096);
filestream_prefetch(f, 0, 0);
filestream_prefetch(f, 0, (uint64_t)-1);
filestream_prefetch(NULL, 0, 4096);
filestream_seek(f, 0, RETRO_VFS_SEEK_POSITION_START);
got = filestream_read(f, buf, FIXTURE_LEN);
CHECK(got == FIXTURE_LEN, what);
for (i = 0; got == FIXTURE_LEN && i < FIXTURE_LEN; i++)
if (buf[i] != pattern(i))
break;
CHECK(got == FIXTURE_LEN && i == FIXTURE_LEN, what);
free(buf);
}
/* A frontend VFS: open/read callbacks that count their calls. Nothing
* in the interface carries a prefetch, so the hint must not reach
* them and must touch nothing of theirs. */
static int frontend_calls;
static struct retro_vfs_file_handle *fe_open(const char *path,
unsigned mode, unsigned hints)
{
frontend_calls++;
(void)path; (void)mode; (void)hints;
return (struct retro_vfs_file_handle*)&frontend_calls;
}
static int fe_close(struct retro_vfs_file_handle *h)
{
frontend_calls++;
(void)h;
return 0;
}
static int64_t fe_read(struct retro_vfs_file_handle *h, void *s,
uint64_t len)
{
frontend_calls++;
(void)h; (void)s; (void)len;
return 0;
}
int main(void)
{
char path[256];
RFILE *f;
strcpy(path, "vfs_prefetch_fixture.bin");
if (!write_fixture(path))
{
printf("FAIL: cannot write %s\n", path);
return 1;
}
/* mapped, where the platform maps */
f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS);
CHECK(f != NULL, "open with the mapping hint");
if (f)
{
int64_t map_len = 0;
printf("mapping: %s\n",
filestream_get_mapped_ptr(f, &map_len) ? "yes" : "no");
check_stream(f, "mapped stream");
filestream_close(f);
}
/* the descriptor path */
f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
CHECK(f != NULL, "open without the hint");
if (f)
{
check_stream(f, "descriptor stream");
filestream_close(f);
}
/* a frontend VFS: the hint stops at filestream */
{
struct retro_vfs_interface iface;
struct retro_vfs_interface_info info;
memset(&iface, 0, sizeof(iface));
iface.open = fe_open;
iface.close = fe_close;
iface.read = fe_read;
info.required_interface_version = 1;
info.iface = &iface;
filestream_vfs_init(&info);
f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS);
CHECK(f != NULL, "open through the frontend VFS");
if (f)
{
int before = frontend_calls;
filestream_prefetch(f, 0, 65536);
filestream_prefetch(f, FIXTURE_LEN + 1, 1);
CHECK(frontend_calls == before, "frontend VFS sees no prefetch");
filestream_close(f);
}
}
remove(path);
if (test_fails)
{
printf("%d FAIL\n", test_fails);
return 1;
}
printf("vfs_prefetch_test: PASS\n");
return 0;
}
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := cdrom_cuesheet_overflow_test
LIBRETRO_COMM_DIR := ../../..
@@ -29,7 +31,7 @@ SOURCES := \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation_cdrom.c
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
CFLAGS += -Wall -pedantic -std=gnu99 -g -DHAVE_CDROM -I$(LIBRETRO_COMM_DIR)/include
@@ -50,10 +52,15 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: clean
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := vfs_hybrid_test
LIBRETRO_COMM_DIR := ../../..
@@ -19,7 +21,7 @@ SOURCES := \
$(LIBRETRO_COMM_DIR)/vfs/vfs_hybrid.c \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
# -DHAVE_MMAP so the local branch really produces a mapping; without
# it the zero-copy check has nothing to assert and only reports a skip.
@@ -44,10 +46,15 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: clean
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
compiler := gcc
TARGET := rac3_frame_test
TARGET2 := rac3_tables_test
@@ -25,7 +27,7 @@ ifneq ($(SANITIZER),)
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif
OBJS := $(SOURCES_C:.c=.o)
OBJS := $(call sample_objs,$(SOURCES_C:.c=.o))
all: $(TARGET) $(TARGET2) $(TARGET3) $(TARGET4)
@@ -41,6 +43,10 @@ $(TARGET2): rac3_tables_test.c $(LIBRETRO_COMM_DIR)/formats/ac3/rac3_tables.h
%.o: %.c
$(CC) $(INCDIRS) $(CFLAGS) $(flags) -c -o$@ $<
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) $(INCDIRS) $(CFLAGS) $(flags) -c -o$@ $<
$(TARGET): $(OBJS)
$(CC) -o$@ $(OBJS) $(LDFLAGS)
@@ -54,6 +60,7 @@ check: $(TARGET) $(TARGET2) $(TARGET3) $(TARGET4)
./$(TARGET4)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(OBJS)
rm -f $(TARGET) $(TARGET2) $(TARGET3) $(TARGET4)
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := audio_transfer_window_test
LIBRETRO_COMM_DIR := ../../..
@@ -27,7 +29,7 @@ COMMON_SOURCES := \
$(LIBRETRO_COMM_DIR)/time/rtime.c \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
COMMON_OBJS := $(COMMON_SOURCES:.c=.o)
COMMON_OBJS := $(call sample_objs,$(COMMON_SOURCES:.c=.o))
OBJS := audio_transfer_window_test.o $(COMMON_OBJS)
CFLAGS += -Wall -pedantic -std=gnu99 -g -O0 -I$(LIBRETRO_COMM_DIR)/include \
@@ -44,10 +46,15 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: clean
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
compiler := gcc
TARGET := audio_transfer_ac3_test
@@ -29,13 +31,17 @@ ifneq ($(SANITIZER),)
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif
OBJS := $(SOURCES_C:.c=.o)
OBJS := $(call sample_objs,$(SOURCES_C:.c=.o))
all: $(TARGET)
%.o: %.c
$(CC) $(INCDIRS) $(CFLAGS) $(flags) -c -o$@ $<
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) $(INCDIRS) $(CFLAGS) $(flags) -c -o$@ $<
$(TARGET): $(OBJS)
$(CC) -o$@ $(OBJS) $(LDFLAGS)
@@ -44,6 +50,7 @@ check: $(TARGET)
./$(TARGET)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(OBJS)
rm -f $(TARGET)
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := audio_transfer_flac_test
LIBRETRO_COMM_DIR := ../../..
@@ -23,7 +25,7 @@ SOURCES := audio_transfer_flac_test.c \
$(LIBRETRO_COMM_DIR)/time/rtime.c \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
CFLAGS += -Wall -pedantic -std=gnu99 -g -O1 -ffunction-sections \
-I$(LIBRETRO_COMM_DIR)/include $(DEFINES)
@@ -39,6 +41,10 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
@@ -46,6 +52,7 @@ check: all
./$(TARGET)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: all check clean
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := screenshot_io_bench
LIBRETRO_COMM_DIR := ../../..
@@ -31,7 +33,7 @@ SOURCES := \
$(LIBRETRO_COMM_DIR)/time/rtime.c \
$(LIBRETRO_COMM_DIR)/lists/string_list.c
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
CFLAGS += -Wall -pedantic -std=gnu99 -O2 -I$(LIBRETRO_COMM_DIR)/include
LDFLAGS += -lz
@@ -46,10 +48,15 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: clean
+9 -2
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := rbmp_test
TARGET_TEST2 := rbmp_encode_test
@@ -39,8 +41,8 @@ TEST2_SOURCES := \
$(LIBRETRO_COMM_DIR)/time/rtime.c \
$(LIBRETRO_COMM_DIR)/lists/string_list.c
OBJS := $(SOURCES:.c=.o)
TEST2_OBJS := $(TEST2_SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
TEST2_OBJS := $(call sample_objs,$(TEST2_SOURCES:.c=.o))
CFLAGS += -Wall -pedantic -std=gnu99 -g -I$(LIBRETRO_COMM_DIR)/include
@@ -59,6 +61,10 @@ all: $(TARGET) $(TARGET_TEST2)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
@@ -66,6 +72,7 @@ $(TARGET_TEST2): $(TEST2_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) -lz
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(TARGET_TEST2) $(OBJS) $(TEST2_OBJS)
.PHONY: clean
+9 -2
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := cdfs_dir_record_test
TARGET_TEST2 := cdfs_pure_api_test
@@ -36,8 +38,8 @@ TEST2_SOURCES := \
$(LIBRETRO_COMM_DIR)/time/rtime.c \
$(LIBRETRO_COMM_DIR)/lists/string_list.c
OBJS := $(SOURCES:.c=.o)
TEST2_OBJS := $(TEST2_SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
TEST2_OBJS := $(call sample_objs,$(TEST2_SOURCES:.c=.o))
CFLAGS += -Wall -pedantic -std=gnu99 -g -O0 -I$(LIBRETRO_COMM_DIR)/include
LDFLAGS += -lz
@@ -52,6 +54,10 @@ all: $(TARGET) $(TARGET_TEST2)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
@@ -59,6 +65,7 @@ $(TARGET_TEST2): $(TEST2_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(TARGET_TEST2) $(OBJS) $(TEST2_OBJS)
.PHONY: clean
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := data_transfer_source_test
TARGET_TEST2 := data_transfer_prefix_test
TARGET_TEST3 := data_transfer_window_test
@@ -18,7 +20,7 @@ COMMON_SOURCES := \
$(LIBRETRO_COMM_DIR)/time/rtime.c \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
COMMON_OBJS := $(COMMON_SOURCES:.c=.o)
COMMON_OBJS := $(call sample_objs,$(COMMON_SOURCES:.c=.o))
OBJS := data_transfer_source_test.o data_transfer_prefix_test.o \
data_transfer_window_test.o data_transfer_pool_scrub_test.o \
$(COMMON_OBJS)
@@ -35,6 +37,10 @@ all: $(TARGET) $(TARGET_TEST2) $(TARGET_TEST3) $(TARGET_TEST4)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): data_transfer_source_test.o $(COMMON_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
@@ -48,6 +54,7 @@ $(TARGET_TEST4): data_transfer_pool_scrub_test.o $(COMMON_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(TARGET_TEST2) $(TARGET_TEST3) $(TARGET_TEST4) $(OBJS)
.PHONY: clean
+8 -1
View File
@@ -1,9 +1,11 @@
include ../../sample_objdir.mk
TARGET := rdds_block_test
LIBRETRO_COMM_DIR := ../../..
SOURCES := rdds_block_test.c $(LIBRETRO_COMM_DIR)/formats/dds/rdds.c
CFLAGS += -Wall -W -std=gnu99 -g -O1 -I$(LIBRETRO_COMM_DIR)/include
LDFLAGS += -lm
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
ifneq ($(SAN),)
SANITIZER := $(SAN)
endif
@@ -14,10 +16,15 @@ endif
all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
check: all
./$(TARGET)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(OBJS) $(TARGET)
.PHONY: all check clean
+32 -4
View File
@@ -1,7 +1,12 @@
include ../../sample_objdir.mk
TARGET := rh264_lossless_test
# The same oracle built without threads: a decoder path that is right
# only with HAVE_THREADS - or wrong only without it - shows here.
TARGET_TEST := rh264_lossless_test_nothreads
# And built threaded on the volatile atomic backend, which has no
# pointer atomics: the decoder takes no pool there and decodes alone.
TARGET_VOLATILE := rh264_lossless_test_volatile
LIBRETRO_COMM_DIR := ../../..
@@ -13,9 +18,10 @@ SOURCES := \
$(LIBRETRO_COMM_DIR)/rthreads/retro_eventcount.c \
$(LIBRETRO_COMM_DIR)/rthreads/tpool.c
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
NOTHREADS_SOURCES := $(filter-out %/rthreads.c %/retro_eventcount.c %/tpool.c,$(SOURCES))
NOTHREADS_OBJS := $(NOTHREADS_SOURCES:.c=.nt.o)
NOTHREADS_OBJS := $(call sample_objs,$(NOTHREADS_SOURCES:.c=.nt.o))
VOLATILE_OBJS := $(call sample_objs,$(SOURCES:.c=.vo.o))
# HAVE_THREADS: the oracle decodes every case on a pool as well, and
# the threaded output has to be the single-thread output to the byte.
@@ -31,21 +37,43 @@ endif
LDLIBS += -lm -lpthread
all: $(TARGET) $(TARGET_TEST)
all: $(TARGET) $(TARGET_TEST) $(TARGET_VOLATILE)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
%.nt.o: %.c
$(CC) -c -o $@ $< $(filter-out -DHAVE_THREADS,$(CFLAGS))
$(SAMPLE_OBJDIR)/%.nt.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(filter-out -DHAVE_THREADS,$(CFLAGS))
$(TARGET_TEST): $(NOTHREADS_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(filter-out -lpthread,$(LDLIBS))
%.vo.o: %.c
$(CC) -c -o $@ $< $(CFLAGS) -DRETRO_ATOMIC_FORCE_VOLATILE \
-DRETRO_ATOMIC_SUPPRESS_WARNING
$(SAMPLE_OBJDIR)/%.vo.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS) -DRETRO_ATOMIC_FORCE_VOLATILE \
-DRETRO_ATOMIC_SUPPRESS_WARNING
$(TARGET_VOLATILE): $(VOLATILE_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
clean:
rm -f $(TARGET) $(OBJS) $(TARGET_TEST) $(NOTHREADS_OBJS)
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS) $(TARGET_TEST) $(NOTHREADS_OBJS) \
$(TARGET_VOLATILE) $(VOLATILE_OBJS)
.PHONY: clean
@@ -39,6 +39,7 @@
#include <formats/rmp4.h>
#include <formats/rh264.h>
#include <retro_atomic.h>
#ifdef HAVE_THREADS
#include <rthreads/tpool.h>
#endif
@@ -109,6 +110,9 @@ static int g_contexts = 1;
static int g_threads = 0;
static void *g_pool = NULL;
/* Pictures posted to a pool over the run */
static long g_posted;
static long compare(const char *mp4, const uint8_t *ref_a, size_t alen,
const uint8_t *ref_b, int split, int *frames_out)
{
@@ -258,6 +262,11 @@ static long compare(const char *mp4, const uint8_t *ref_a, size_t alen,
bad = bad < 0 ? bad : bad + 1; /* short: not every reference frame came out */
done:
*frames_out = frames;
{
int posted = 0;
rh264_video_stats(h, &posted, NULL, NULL, NULL, NULL, NULL);
g_posted += posted;
}
rh264_video_close(h);
rmp4_close(m);
free(b);
@@ -735,6 +744,13 @@ int main(void)
rh264_video_ref_wait_misses());
check("row progress never short on one thread",
rh264_video_ref_wait_misses() == 0);
#if defined(HAVE_THREADS) && !defined(RETRO_ATOMIC_HAS_PTR)
/* Without pointer atomics the decoder takes no pool: the threaded
* cases above decoded on one thread, still byte-exact */
printf("pictures posted to a pool without pointer atomics: %ld\n",
g_posted);
check("no pool without pointer atomics", g_posted == 0);
#endif
printf("rh264_lossless_test: %s (%d failure%s)\n", fails ? "FAIL" : "PASS",
fails, fails == 1 ? "" : "s");
return fails ? 1 : 0;
+39 -4
View File
@@ -1,7 +1,14 @@
include ../../sample_objdir.mk
TARGET := rh265_lossless_test
# The same oracle built without threads: a decoder path that is right
# only with HAVE_THREADS - or wrong only without it - shows here.
TARGET_TEST := rh265_lossless_test_nothreads
# And built threaded on the volatile atomic backend, which has no
# pointer atomics: the decoder takes no pool there and decodes alone.
TARGET_VOLATILE := rh265_lossless_test_volatile
# Parameter sets with ue(v) codes past what any field holds
TARGET_MALFORMED := rh265_malformed_test
LIBRETRO_COMM_DIR := ../../..
@@ -13,9 +20,10 @@ SOURCES := \
$(LIBRETRO_COMM_DIR)/rthreads/retro_eventcount.c \
$(LIBRETRO_COMM_DIR)/rthreads/tpool.c
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
NOTHREADS_SOURCES := $(filter-out %/rthreads.c %/retro_eventcount.c %/tpool.c,$(SOURCES))
NOTHREADS_OBJS := $(NOTHREADS_SOURCES:.c=.nt.o)
NOTHREADS_OBJS := $(call sample_objs,$(NOTHREADS_SOURCES:.c=.nt.o))
VOLATILE_OBJS := $(call sample_objs,$(SOURCES:.c=.vo.o))
# HAVE_THREADS: the oracle decodes every case on a pool as well, and
# the threaded output has to be the single-thread output to the byte.
@@ -31,21 +39,48 @@ endif
LDLIBS += -lm -lpthread
all: $(TARGET) $(TARGET_TEST)
all: $(TARGET) $(TARGET_TEST) $(TARGET_VOLATILE) $(TARGET_MALFORMED)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
%.nt.o: %.c
$(CC) -c -o $@ $< $(filter-out -DHAVE_THREADS,$(CFLAGS))
$(SAMPLE_OBJDIR)/%.nt.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(filter-out -DHAVE_THREADS,$(CFLAGS))
$(TARGET_TEST): $(NOTHREADS_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(filter-out -lpthread,$(LDLIBS))
%.vo.o: %.c
$(CC) -c -o $@ $< $(CFLAGS) -DRETRO_ATOMIC_FORCE_VOLATILE \
-DRETRO_ATOMIC_SUPPRESS_WARNING
$(SAMPLE_OBJDIR)/%.vo.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS) -DRETRO_ATOMIC_FORCE_VOLATILE \
-DRETRO_ATOMIC_SUPPRESS_WARNING
$(TARGET_VOLATILE): $(VOLATILE_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
MALFORMED_OBJS := rh265_malformed_test.o $(filter-out rh265_lossless_test.o,$(OBJS))
$(TARGET_MALFORMED): $(MALFORMED_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
clean:
rm -f $(TARGET) $(OBJS) $(TARGET_TEST) $(NOTHREADS_OBJS)
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS) $(TARGET_TEST) $(NOTHREADS_OBJS) \
$(TARGET_VOLATILE) $(VOLATILE_OBJS) \
$(TARGET_MALFORMED) rh265_malformed_test.o
.PHONY: clean
+211
View File
@@ -0,0 +1,211 @@
/* Parameter sets whose ue(v) codes run past what any field holds.
*
* An SPS is read for its values and then refused when its bit depth
* is outside the supported profile (9 here), which set_extradata
* reports as -1; an SPS refused as malformed leaves set_extradata at 0.
* That tells, without looking inside the decoder, whether a crafted
* SPS was taken as a parameter set at all:
* - an sps_id coded as 32 zero bits, or of 2^32 - 2, or a
* picture width of 2^25 - 1, is malformed. Read as an int those
* ids were negative, passed the id bound, and were stored at
* v->sps[id];
* - sps_max_latency_increase_plus1 at 2^32 - 2, which the spec
* allows and nothing reads, still parses.
* Exit status is the outcome. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <formats/rh265.h>
static int failures;
/* An RBSP built a bit at a time */
typedef struct
{
uint8_t buf[256];
size_t bits;
} bitw;
static void put1(bitw *w, unsigned bit)
{
if (bit)
w->buf[w->bits >> 3] |= (uint8_t)(0x80 >> (w->bits & 7));
w->bits++;
}
static void putn(bitw *w, uint32_t v, int n)
{
while (n--)
put1(w, (v >> n) & 1);
}
/* ue(v) of @v; v up to 2^32 - 2 */
static void putue(bitw *w, uint32_t v)
{
uint64_t x = (uint64_t)v + 1;
int lz = 0;
while ((x >> (lz + 1)) != 0)
lz++;
putn(w, 0, lz);
put1(w, 1);
while (lz--)
put1(w, (unsigned)((x >> lz) & 1));
}
/* 32 zero bits: a code no 32-bit value has. A reader that gives up
* after them as 2^32 - 1 carries on at the next field. */
static void putue_overlong(bitw *w)
{
putn(w, 0, 32);
}
static void trailing(bitw *w)
{
put1(w, 1);
while (w->bits & 7)
put1(w, 0);
}
/* A NAL unit of @type around the RBSP, emulation-prevented */
static size_t nal(uint8_t *out, int type, const bitw *w)
{
size_t i, n = 0, len = w->bits >> 3;
int zeros = 0;
out[n++] = (uint8_t)(type << 1);
out[n++] = 1;
for (i = 0; i < len; i++)
{
if (zeros >= 2 && w->buf[i] <= 3)
{
out[n++] = 3;
zeros = 0;
}
out[n++] = w->buf[i];
zeros = w->buf[i] ? 0 : zeros + 1;
}
return n;
}
/* hvcC extradata (as an MP4 carries it) holding the NAL units given,
* each in an array of its own */
static size_t hvcc(uint8_t *out, const uint8_t *const *nals,
const size_t *lens, int count)
{
size_t n = 23;
int i;
memset(out, 0, 23);
out[0] = 1; /* configurationVersion */
out[21] = 3; /* lengthSizeMinusOne */
out[22] = (uint8_t)count; /* numOfArrays */
for (i = 0; i < count; i++)
{
out[n++] = (uint8_t)(nals[i][0] >> 1);
out[n++] = 0; out[n++] = 1; /* numNalus */
out[n++] = (uint8_t)(lens[i] >> 8);
out[n++] = (uint8_t)lens[i];
memcpy(out + n, nals[i], lens[i]);
n += lens[i];
}
return n;
}
enum field { F_NONE = 0, F_ID_OVERLONG, F_ID_HUGE, F_WIDTH_HUGE,
F_LATENCY_HUGE };
/* An SPS for a 64x64 8-bit-or-@depth 4:2:0 picture, one field crafted */
static void sps(bitw *w, enum field f, int depth)
{
memset(w, 0, sizeof(*w));
putn(w, 0, 4); /* sps_video_parameter_set_id */
putn(w, 0, 3); /* sps_max_sub_layers_minus1 */
put1(w, 1); /* temporal_id_nesting */
putn(w, 0x01, 8); /* profile_tier_level: Main */
putn(w, 0x60000000u, 32);
putn(w, 0, 32); putn(w, 0, 16);
putn(w, 93, 8); /* level 3.1 */
if (f == F_ID_OVERLONG)
putue_overlong(w);
else
putue(w, f == F_ID_HUGE ? 0xfffffffeu : 0); /* sps_id */
putue(w, 1); /* chroma_format_idc 4:2:0 */
putue(w, f == F_WIDTH_HUGE ? 0x1ffffffu : 64);
putue(w, 64);
put1(w, 0); /* conformance_window */
putue(w, (uint32_t)(depth - 8)); /* bit_depth_luma_minus8 */
putue(w, (uint32_t)(depth - 8)); /* bit_depth_chroma_minus8 */
putue(w, 4); /* log2_max_poc_lsb_minus4 */
put1(w, 1); /* sub_layer_ordering_info */
putue(w, 0); /* max_dec_pic_buffering_minus1 */
putue(w, 0); /* max_num_reorder_pics */
putue(w, f == F_LATENCY_HUGE ? 0xfffffffeu : 0);
putue(w, 0); /* log2_min_cb_minus3 */
putue(w, 1); /* log2_diff_max_min_cb */
putue(w, 0); /* log2_min_tb_minus2 */
putue(w, 2); /* log2_diff_max_min_tb */
putue(w, 0); /* max_transform_hierarchy_depth_inter */
putue(w, 0); /* ..._intra */
put1(w, 0); /* scaling_list_enabled */
put1(w, 0); /* amp */
put1(w, 0); /* sao */
put1(w, 0); /* pcm */
putue(w, 0); /* num_short_term_ref_pic_sets */
put1(w, 0); /* long_term_ref_pics_present */
put1(w, 0); /* temporal_mvp */
put1(w, 0); /* strong_intra_smoothing */
put1(w, 0); /* vui_parameters_present */
put1(w, 0); /* sps_extension_present */
trailing(w);
}
/* set_extradata's answer for an SPS: -1 when it was read (and refused
* for its bit depth), 0 when it was refused as malformed */
static int feed_sps(enum field f, int depth)
{
bitw w;
uint8_t unit[512], buf[600];
const uint8_t *units[1];
size_t lens[1], n;
int ret;
rh265_video *v = rh265_video_open();
if (!v)
abort();
sps(&w, f, depth);
units[0] = unit;
lens[0] = nal(unit, 33, &w);
n = hvcc(buf, units, lens, 1);
ret = rh265_video_set_extradata(v, buf, n);
rh265_video_close(v);
return ret;
}
static void expect(const char *what, int got, int want)
{
if (got != want)
{
printf("[FAIL] %s: set_extradata %d, want %d\n", what, got, want);
failures++;
}
else
printf("[pass] %s\n", what);
}
int main(void)
{
expect("a well-formed SPS is read",
feed_sps(F_NONE, 9), -1);
expect("sps_id coded as 32 zero bits is malformed",
feed_sps(F_ID_OVERLONG, 9), 0);
expect("sps_id 2^32 - 2 is malformed",
feed_sps(F_ID_HUGE, 9), 0);
expect("a width of 2^25 - 1 is malformed",
feed_sps(F_WIDTH_HUGE, 9), 0);
expect("sps_max_latency_increase_plus1 at 2^32 - 2 is read",
feed_sps(F_LATENCY_HUGE, 9), -1);
if (failures)
return 1;
printf("[pass] rh265_malformed_test\n");
return 0;
}
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
compiler := gcc
TARGET := iec61937_test
@@ -25,13 +27,17 @@ ifneq ($(SANITIZER),)
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif
OBJS := $(SOURCES_C:.c=.o)
OBJS := $(call sample_objs,$(SOURCES_C:.c=.o))
all: $(TARGET)
%.o: %.c
$(CC) $(INCDIRS) $(CFLAGS) $(flags) -c -o$@ $<
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) $(INCDIRS) $(CFLAGS) $(flags) -c -o$@ $<
$(TARGET): $(OBJS)
$(CC) -o$@ $(OBJS) $(LDFLAGS)
@@ -40,6 +46,7 @@ check: $(TARGET)
./$(TARGET)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(OBJS)
rm -f $(TARGET)
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := image_blit_bands_test
LIBRETRO_COMM_DIR := ../../..
SOURCES := image_blit_bands_test.c \
@@ -9,7 +11,7 @@ SOURCES := image_blit_bands_test.c \
CFLAGS += -Wall -W -std=gnu99 -g -O1 -DHAVE_THREADS -I$(LIBRETRO_COMM_DIR)/include
# malloc/free are wrapped so the test can count heap traffic per blit.
LDFLAGS += -lpthread -lrt -Wl,--wrap=malloc,--wrap=free
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
ifneq ($(SAN),)
SANITIZER := $(SAN)
endif
@@ -20,10 +22,15 @@ endif
all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
check: all
./$(TARGET)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(OBJS) $(TARGET)
.PHONY: all check clean
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := image_hdr_blit_test
LIBRETRO_COMM_DIR := ../../..
@@ -5,7 +7,7 @@ LIBRETRO_COMM_DIR := ../../..
SOURCES := image_hdr_blit_test.c \
$(LIBRETRO_COMM_DIR)/formats/image/image_hdr_blit.c
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
CFLAGS += -Wall -pedantic -std=gnu99 -g -O1 -I$(LIBRETRO_COMM_DIR)/include
LDFLAGS += -lm
@@ -20,6 +22,10 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
@@ -27,6 +33,7 @@ check: all
./$(TARGET)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: all check clean
+69
View File
@@ -0,0 +1,69 @@
include ../../sample_objdir.mk
TARGET := image_loader_test
# The still loader (formats/image_texture.c) and the scale unit
# (formats/image_texture_scale.c), driven as a frontend task or a core
# would: whole buffer, timed steps against a fake clock, a prefix that
# grows, an abort, the APNG verdict, the request's answers, and the
# set decode (formats/image_texture_set.c) across threads.
LIBRETRO_COMM_DIR := ../../..
SOURCES := image_loader_test.c \
$(LIBRETRO_COMM_DIR)/compat/compat_strl.c \
$(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_crc32.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_deflate.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \
$(LIBRETRO_COMM_DIR)/features/features_cpu.c \
$(LIBRETRO_COMM_DIR)/file/file_path.c \
$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c \
$(LIBRETRO_COMM_DIR)/formats/image_texture.c \
$(LIBRETRO_COMM_DIR)/formats/image_texture_scale.c \
$(LIBRETRO_COMM_DIR)/formats/image_texture_set.c \
$(LIBRETRO_COMM_DIR)/formats/image_transfer.c \
$(LIBRETRO_COMM_DIR)/formats/png/rpng.c \
$(LIBRETRO_COMM_DIR)/formats/png/rpng_apng.c \
$(LIBRETRO_COMM_DIR)/gfx/scaler/pixconv.c \
$(LIBRETRO_COMM_DIR)/gfx/scaler/scaler.c \
$(LIBRETRO_COMM_DIR)/gfx/scaler/scaler_filter.c \
$(LIBRETRO_COMM_DIR)/gfx/scaler/scaler_int.c \
$(LIBRETRO_COMM_DIR)/memmap/memmap.c \
$(LIBRETRO_COMM_DIR)/rthreads/rthreads.c \
$(LIBRETRO_COMM_DIR)/streams/file_stream.c \
$(LIBRETRO_COMM_DIR)/streams/trans_stream.c \
$(LIBRETRO_COMM_DIR)/streams/trans_stream_deflate.c \
$(LIBRETRO_COMM_DIR)/streams/trans_stream_pipe.c \
$(LIBRETRO_COMM_DIR)/string/rstrtod.c \
$(LIBRETRO_COMM_DIR)/string/stdstring.c \
$(LIBRETRO_COMM_DIR)/time/rtime.c \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
CFLAGS += -Wall -pedantic -std=gnu99 -g -O0 -DHAVE_RPNG -DHAVE_THREADS \
-I$(LIBRETRO_COMM_DIR)/include
ifneq ($(SANITIZER),)
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif
all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS) -lm -lpthread
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(TARGET).exe $(OBJS)
.PHONY: all clean
@@ -0,0 +1,430 @@
/* The still loader, driven the ways its callers drive it:
*
* 1 a whole buffer in one untimed step decodes to what
* image_texture_load_buffer gives, pixel for pixel
* 2 timed steps against a fake clock: a deadline already passed
* still runs one iteration, and the step that completes the
* transfer does not go on into the pixels
* 3 a prefix that grows: the loader waits at the frontier and
* finishes once the rest has arrived, with the same pixels
* 4 an abort hook that fires fails the decode and leaks nothing
* 5 the APNG verdict: a still, an animation, an incomplete read
* 6 the request's channel order is answered and honoured
* 7 image_texture_scale: the cap, the whole-factor upscale, and
* a half-float image left alone
* 8 image_texture_load_set: a set of files decoded together, an
* unreadable one left empty, every index run exactly once
*
* The PNG is built here - RGB, every row filter 0, the zlib stream
* stored and split across many small IDATs so the transfer has many
* iterations to be budgeted. */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <boolean.h>
#include <encodings/crc32.h>
#include <formats/image.h>
#define W 24
#define H 16
#define IDAT_SPLIT 64
static int fails;
#define CHECK(cond, msg) \
do { \
if (!(cond)) \
{ \
printf("FAIL: %s\n", msg); \
fails++; \
} \
else \
printf("ok: %s\n", msg); \
} while (0)
static void put_be32(uint8_t *p, uint32_t v)
{
p[0] = (uint8_t)(v >> 24);
p[1] = (uint8_t)(v >> 16);
p[2] = (uint8_t)(v >> 8);
p[3] = (uint8_t)(v );
}
static size_t png_chunk(uint8_t *out, const char *type,
const uint8_t *data, size_t len)
{
uint32_t c;
put_be32(out, (uint32_t)len);
memcpy(out + 4, type, 4);
if (len)
memcpy(out + 8, data, len);
c = encoding_crc32(0, out + 4, 4);
if (len)
c = encoding_crc32(c, data, len);
put_be32(out + 8 + len, c);
return 12 + len;
}
/* R = x, G = y, B = x ^ y */
static uint32_t expect_argb(unsigned x, unsigned y)
{
return 0xff000000u | ((x & 0xff) << 16) | ((y & 0xff) << 8) | ((x ^ y) & 0xff);
}
static uint32_t expect_rgba(unsigned x, unsigned y)
{
uint32_t v;
uint8_t b[4];
b[0] = (uint8_t)x;
b[1] = (uint8_t)y;
b[2] = (uint8_t)(x ^ y);
b[3] = 0xff;
memcpy(&v, b, 4);
return v;
}
static uint8_t *make_png(size_t *out_len, bool apng)
{
static const uint8_t sig[8] = { 0x89, 'P', 'N', 'G', 0x0d, 0x0a, 0x1a, 0x0a };
size_t raw_len = (size_t)H * (W * 3 + 1);
uint8_t *raw = (uint8_t*)malloc(raw_len);
size_t zcap = raw_len + (raw_len / 65535 + 2) * 5 + 8;
uint8_t *z = (uint8_t*)malloc(zcap);
uint8_t *png = (uint8_t*)malloc(zcap + 4096 + (zcap / IDAT_SPLIT + 2) * 12);
size_t zlen = 0, off, len = 0;
uint32_t s1 = 1, s2 = 0;
unsigned x, y;
if (!raw || !z || !png)
exit(1);
for (y = 0; y < H; y++)
{
uint8_t *row = raw + y * (W * 3 + 1);
row[0] = 0;
for (x = 0; x < W; x++)
{
row[1 + x * 3] = (uint8_t)x;
row[2 + x * 3] = (uint8_t)y;
row[3 + x * 3] = (uint8_t)(x ^ y);
}
}
z[zlen++] = 0x78;
z[zlen++] = 0x01;
for (off = 0; off < raw_len; )
{
size_t blk = raw_len - off;
if (blk > 65535)
blk = 65535;
z[zlen++] = (off + blk >= raw_len) ? 0x01 : 0x00;
z[zlen++] = (uint8_t)(blk & 0xff);
z[zlen++] = (uint8_t)(blk >> 8);
z[zlen++] = (uint8_t)(~blk & 0xff);
z[zlen++] = (uint8_t)(~(blk >> 8) & 0xff);
memcpy(z + zlen, raw + off, blk);
zlen += blk;
off += blk;
}
for (off = 0; off < raw_len; off++)
{
s1 = (s1 + raw[off]) % 65521;
s2 = (s2 + s1) % 65521;
}
put_be32(z + zlen, (s2 << 16) | s1);
zlen += 4;
memcpy(png, sig, 8);
len = 8;
{
uint8_t ihdr[13];
put_be32(ihdr, W);
put_be32(ihdr + 4, H);
ihdr[8] = 8;
ihdr[9] = 2; /* RGB */
ihdr[10] = 0;
ihdr[11] = 0;
ihdr[12] = 0;
len += png_chunk(png + len, "IHDR", ihdr, 13);
}
if (apng)
{
uint8_t actl[8];
put_be32(actl, 1); /* frames */
put_be32(actl + 4, 0); /* plays */
len += png_chunk(png + len, "acTL", actl, 8);
}
for (off = 0; off < zlen; off += IDAT_SPLIT)
{
size_t c = zlen - off;
if (c > IDAT_SPLIT)
c = IDAT_SPLIT;
len += png_chunk(png + len, "IDAT", z + off, c);
}
len += png_chunk(png + len, "IEND", NULL, 0);
free(raw);
free(z);
*out_len = len;
return png;
}
static int pixels_match(const struct texture_image *img, bool rgba)
{
unsigned x, y;
if (!img->pixels || img->width != W || img->height != H)
return 0;
for (y = 0; y < H; y++)
for (x = 0; x < W; x++)
if (img->pixels[y * W + x] != (rgba ? expect_rgba(x, y) : expect_argb(x, y)))
return 0;
return 1;
}
/* Every read advances the fake clock one quantum */
static int64_t fake_now_v = 1000000;
static int64_t fake_now(void)
{
fake_now_v += 250;
return fake_now_v;
}
static void set_hit(unsigned i, void *ud)
{
((unsigned*)ud)[i]++;
}
static int aborts;
static bool abort_now(void *ud)
{
(void)ud;
aborts++;
return true;
}
int main(void)
{
size_t len = 0, apng_len = 0;
uint8_t *png = make_png(&len, false);
uint8_t *apng = make_png(&apng_len, true);
struct texture_image ref, img;
image_texture_request_t req;
image_loader_t *l;
memset(&ref, 0, sizeof(ref));
memset(&req, 0, sizeof(req));
CHECK(image_texture_load_buffer(&ref, IMAGE_TYPE_PNG, png, len)
&& pixels_match(&ref, false), "reference decode through image_texture_load_buffer");
/* 1: whole buffer, one untimed step */
memset(&img, 0, sizeof(img));
l = image_loader_new(IMAGE_TYPE_PNG, &req);
CHECK(l && image_loader_start(l, png, len, len), "loader started on a whole buffer");
CHECK(image_loader_step(l, NULL, 0) == IMAGE_LOADER_DONE, "untimed step runs to DONE");
CHECK(image_loader_finish(l, &img) && pixels_match(&img, false)
&& !img.pix10 && !img.fp16 && !img.supports_rgba,
"finished image equals the reference, format fields clear");
image_loader_free(l);
image_texture_free(&img);
/* 2: timed steps */
{
int steps = 0, transfer_steps = 0, first_pixels_step = -1;
enum image_loader_state st = IMAGE_LOADER_RUNNING;
memset(&img, 0, sizeof(img));
l = image_loader_new(IMAGE_TYPE_PNG, &req);
image_loader_start(l, png, len, len);
/* a deadline already behind the clock: one iteration a step, so
* the transfer takes at least as many steps as there are IDATs */
while (st == IMAGE_LOADER_RUNNING && steps < 10000)
{
st = image_loader_step(l, fake_now, fake_now_v);
steps++;
}
CHECK(st == IMAGE_LOADER_DONE, "timed steps reach DONE");
CHECK(steps > (int)(len / IDAT_SPLIT), "a spent deadline still runs one iteration a step");
CHECK(image_loader_finish(l, &img) && pixels_match(&img, false),
"timed decode equals the reference");
image_loader_free(l);
image_texture_free(&img);
/* a generous deadline: the transfer completes in one step and
* that step stops there; the pixels come from the next */
memset(&img, 0, sizeof(img));
l = image_loader_new(IMAGE_TYPE_PNG, &req);
image_loader_start(l, png, len, len);
steps = 0;
st = IMAGE_LOADER_RUNNING;
while (st == IMAGE_LOADER_RUNNING && steps < 100)
{
st = image_loader_step(l, fake_now, fake_now_v + 100000000);
steps++;
if (st == IMAGE_LOADER_RUNNING)
transfer_steps++;
else if (first_pixels_step < 0)
first_pixels_step = steps;
}
CHECK(st == IMAGE_LOADER_DONE && transfer_steps == 1 && first_pixels_step == 2,
"the step that completes the transfer ends there; the next brings the pixels");
CHECK(image_loader_finish(l, &img) && pixels_match(&img, false),
"two-step decode equals the reference");
image_loader_free(l);
image_texture_free(&img);
}
/* 3: a prefix that grows */
{
size_t avail = 8 + 25 + 40; /* signature, IHDR, into the first IDAT */
enum image_loader_state st;
memset(&img, 0, sizeof(img));
CHECK(image_loader_ready(IMAGE_TYPE_PNG, png, avail), "a PNG may start once its header is resident");
CHECK(!image_loader_ready(IMAGE_TYPE_PNG, png, 8), "and not before");
l = image_loader_new(IMAGE_TYPE_PNG, &req);
CHECK(image_loader_start(l, png, len, avail), "loader started on a prefix");
st = image_loader_step(l, NULL, 0);
CHECK(st == IMAGE_LOADER_WAIT, "an untimed step waits at the frontier");
CHECK(image_loader_png_probe(l) == -1, "no APNG verdict over a partial read");
image_loader_set_avail(l, len / 2);
st = image_loader_step(l, NULL, 0);
CHECK(st == IMAGE_LOADER_WAIT, "waits again at the next frontier");
image_loader_set_avail(l, (size_t)-1);
st = image_loader_step(l, NULL, 0);
CHECK(st == IMAGE_LOADER_DONE, "finishes once the rest has arrived");
CHECK(image_loader_png_probe(l) == 0, "a complete still PNG is no animation");
CHECK(image_loader_finish(l, &img) && pixels_match(&img, false),
"prefix decode equals the reference");
image_loader_free(l);
image_texture_free(&img);
}
/* 4: abort */
l = image_loader_new(IMAGE_TYPE_PNG, &req);
image_loader_start(l, png, len, len);
image_loader_set_abort(l, abort_now, NULL);
CHECK(image_loader_step(l, NULL, 0) == IMAGE_LOADER_ERROR && aborts == 1,
"an abort hook fails the decode at its first ask");
CHECK(!image_loader_finish(l, &img), "nothing to finish after an abort");
image_loader_free(l);
/* 5: APNG */
l = image_loader_new(IMAGE_TYPE_PNG, &req);
image_loader_start(l, apng, apng_len, apng_len);
CHECK(image_loader_png_probe(l) == 1, "an acTL makes the verdict animated");
image_loader_free(l);
/* 6: channel order */
memset(&img, 0, sizeof(img));
req.rgba = true;
l = image_loader_new(IMAGE_TYPE_PNG, &req);
image_loader_start(l, png, len, len);
image_loader_step(l, NULL, 0);
CHECK(image_loader_finish(l, &img) && img.supports_rgba && pixels_match(&img, true),
"rgba requested: answered and honoured");
image_loader_free(l);
image_texture_free(&img);
req.rgba = false;
/* 7: scaling */
{
struct texture_image s;
unsigned i;
memset(&s, 0, sizeof(s));
s.width = 8;
s.height = 8;
s.pixels = (uint32_t*)malloc(64 * sizeof(uint32_t));
for (i = 0; i < 64; i++)
s.pixels[i] = 0xff336699u;
CHECK(image_texture_scale(&s, 0, 4) && s.width == 4 && s.height == 4
&& s.pixels[0] == 0xff336699u && s.pixels[15] == 0xff336699u,
"the cap reduces 8x8 to 4x4 and a flat image stays flat");
CHECK(image_texture_scale(&s, 8, 0) && s.width == 8 && s.height == 8
&& s.pixels[63] == 0xff336699u,
"an edge under the threshold is upscaled by a whole factor");
CHECK(!image_texture_scale(&s, 0, 16), "within the cap: untouched");
s.fp16 = true;
CHECK(!image_texture_scale(&s, 64, 4), "half floats are left at their size");
free(s.pixels);
}
/* 5b: a file load answers the APNG question from its own read */
{
static const char *still_name = "image_loader_probe_still.png";
static const char *anim_name = "image_loader_probe_anim.png";
FILE *f;
int probe_still = -2, probe_anim = -2;
if ((f = fopen(still_name, "wb")))
{
fwrite(png, 1, len, f);
fclose(f);
}
if ((f = fopen(anim_name, "wb")))
{
fwrite(apng, 1, apng_len, f);
fclose(f);
}
memset(&img, 0, sizeof(img));
CHECK(image_texture_load_request_ex(&img, still_name, &req, NULL,
NULL, &probe_still) && pixels_match(&img, false)
&& probe_still == 0, "a still file decodes and says so");
image_texture_free(&img);
memset(&img, 0, sizeof(img));
CHECK(image_texture_load_request_ex(&img, anim_name, &req, NULL,
NULL, &probe_anim) && probe_anim == 1,
"an APNG file decodes its first frame and says it is animated");
image_texture_free(&img);
remove(still_name);
remove(anim_name);
}
/* 8: a set */
{
static const char *names[5] = {
"image_loader_set_0.png", "image_loader_set_1.png",
"image_loader_none.png", "", "image_loader_set_2.png" };
const char *paths[6];
struct texture_image imgs[6];
unsigned i, ok = 1, got;
for (i = 0; i < 5; i++)
{
FILE *f;
paths[i] = names[i];
if (i == 2 || i == 3)
continue;
if ((f = fopen(names[i], "wb")))
{
fwrite(png, 1, len, f);
fclose(f);
}
}
paths[5] = NULL;
req.rgba = false;
got = image_texture_load_set(paths, imgs, 6, &req);
for (i = 0; i < 6; i++)
{
bool want = (i == 0 || i == 1 || i == 4);
if (want ? !pixels_match(&imgs[i], false) : (imgs[i].pixels != NULL))
ok = 0;
image_texture_free(&imgs[i]);
}
CHECK(got == 3 && ok, "a set decodes its files; missing, empty and NULL paths stay empty");
for (i = 0; i < 5; i++)
if (i != 2 && i != 3)
remove(names[i]);
}
{
static unsigned hits[64];
unsigned i, ok = 1;
memset(hits, 0, sizeof(hits));
image_texture_set_run(64, set_hit, hits);
for (i = 0; i < 64; i++)
if (hits[i] != 1)
ok = 0;
CHECK(ok, "image_texture_set_run calls every index exactly once");
}
image_texture_free(&ref);
free(png);
free(apng);
printf("%s\n", fails ? "FAILED" : "PASS");
return fails ? 1 : 0;
}
+8 -1
View File
@@ -1,3 +1,5 @@
include ../../sample_objdir.mk
TARGET := rjpeg_test
LIBRETRO_COMM_DIR := ../../..
@@ -7,7 +9,7 @@ SOURCES := \
$(LIBRETRO_COMM_DIR)/formats/jpeg/rjpeg.c \
$(LIBRETRO_COMM_DIR)/features/features_cpu.c
OBJS := $(SOURCES:.c=.o)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
CFLAGS += -Wall -pedantic -std=gnu99 -g -I$(LIBRETRO_COMM_DIR)/include
@@ -25,10 +27,15 @@ all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(OBJS)
.PHONY: clean
Loaded 100 of 161 files, more files were not shown because too many files have changed in this diff. Show more