mirror of
https://github.com/libretro/libretro-common.git
synced 2026-11-07 11:06:12 -10:00
Resync
This commit is contained in:
1 parent
2b96a82bd8
commit
a0403d4273
161 files changed
+15062
-541
No files matched your search
@@ -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"))
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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]);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
**/
|
||||
|
||||
@@ -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 ----
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/** @} */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -95,6 +95,7 @@ typedef int ssize_t;
|
||||
#ifndef PRId64
|
||||
#define PRId64 "I64d"
|
||||
#define PRIu64 "I64u"
|
||||
#define PRIx64 "I64x"
|
||||
#define PRIuPTR "Iu"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
Reference in new issue
Block a user