From a0403d4273fbd5ce6971f6f41c90c6df2b02e14b Mon Sep 17 00:00:00 2001 From: libretroadmin Date: Sat, 10 Oct 2026 21:23:55 +0200 Subject: [PATCH] Resync --- audio/resampler/audio_resampler_int16.c | 1 + audio/resampler/drivers/sinc_resampler.c | 435 ++- .../resampler/drivers/sinc_resampler_int16.c | 384 ++- audio/resampler/drivers/sinc_resampler_neon.S | 55 + cdrom/cdrom.c | 2 +- encodings/encoding_deflate.c | 46 +- features/features_cpu.c | 2 + file/archive_file.c | 16 +- file/archive_file_rar.c | 394 +++ file/config_file.c | 121 +- file/file_path.c | 7 + formats/7z/r7z_archive.c | 462 ++- formats/chd/rchd.c | 8 +- formats/h264/rh264.c | 64 +- formats/h265/rh265.c | 80 +- formats/image/image_yuv_blit.c | 154 + formats/image_texture.c | 600 +++- formats/image_texture_scale.c | 330 +++ formats/image_texture_set.c | 160 + formats/mp3/rmp3.c | 7 +- formats/mp4/rmp4.c | 24 +- formats/rar/rrar_archive.c | 2565 +++++++++++++++++ formats/rar/rrar_ppmd7.c | 1043 +++++++ formats/rar/rrar_ppmd7.h | 128 + formats/vp9/rvp9_recon.inc | 5 + formats/zip/rzip_archive.c | 936 ++++++ hash/lrc_hash.c | 8 +- include/7z/r7z_archive.h | 100 + include/audio/audio_resampler.h | 9 + include/audio/sinc_resampler_int16.h | 4 + include/defines/cocoa_defines.h | 8 + include/encodings/utf.h | 14 + include/file/archive_file.h | 2 + include/formats/image.h | 150 + include/formats/image_yuv_blit.h | 71 + include/gfx/video_frame.h | 36 + include/jni/rjni.h | 219 ++ include/libretro.h | 366 +++ include/libretro_vulkan.h | 2 +- include/net/net_socket_ssl.h | 7 + include/rar/rrar_archive.h | 238 ++ include/retro_atomic.h | 16 +- include/retro_common_api.h | 1 + include/retro_miscellaneous.h | 7 + include/streams/file_stream.h | 30 + include/vfs/vfs_implementation.h | 15 + include/zip/rzip_archive.h | 198 ++ jni/rjni.c | 282 ++ lists/file_list.c | 6 +- media/media_detect_cd.c | 2 +- memmap/memmap.c | 33 +- memory/mem_stats.c | 42 +- net/net_http.c | 134 +- net/net_socket_ssl_bear.c | 67 + net/net_socket_ssl_mbed.c | 20 +- net/net_socket_ssl_retro.c | 25 +- queues/task_queue.c | 21 + samples/atomic/retro_atomic_test/Makefile | 9 +- samples/audio/audio_mixer_loop/Makefile | 9 +- samples/audio/audio_mixer_rate_guard/Makefile | 9 +- .../audio/audio_mixer_voice_threads/Makefile | 9 +- samples/audio/float_to_s32/Makefile | 9 +- samples/compat/fnmatch/Makefile | 9 +- samples/compat/snprintf/Makefile | 9 +- samples/encodings/base64/Makefile | 9 +- samples/encodings/crc32/Makefile | 9 +- samples/encodings/deflate/Makefile | 42 +- .../encodings/deflate/rdeflate_hash_test.c | 70 + samples/encodings/vcdiff/Makefile | 9 +- samples/fastjmp/fastjmp_test/Makefile | 9 +- .../faulthandler/faulthandler_test/Makefile | 9 +- samples/file/archive_file/Makefile | 9 +- samples/file/archive_rar/Makefile | 67 + samples/file/archive_rar/archive_rar_test.c | 212 ++ samples/file/config_file/Makefile | 9 +- samples/file/config_file/config_file_test.c | 91 + samples/file/file_path/Makefile | 9 +- samples/file/vfs/Makefile | 20 +- samples/file/vfs/vfs_prefetch_test.c | 199 ++ samples/file/vfs_cdrom/Makefile | 9 +- samples/file/vfs_hybrid/Makefile | 9 +- samples/formats/ac3/Makefile | 9 +- samples/formats/audio_transfer/Makefile | 9 +- samples/formats/audio_transfer_ac3/Makefile | 9 +- samples/formats/audio_transfer_flac/Makefile | 9 +- samples/formats/bench/Makefile | 9 +- samples/formats/bmp/Makefile | 11 +- samples/formats/cdfs/Makefile | 11 +- samples/formats/data_transfer/Makefile | 9 +- samples/formats/dds/Makefile | 9 +- samples/formats/h264/Makefile | 36 +- samples/formats/h264/rh264_lossless_test.c | 16 + samples/formats/h265/Makefile | 43 +- samples/formats/h265/rh265_malformed_test.c | 211 ++ samples/formats/iec61937/Makefile | 9 +- .../formats/image_blit_bands_test/Makefile | 9 +- samples/formats/image_hdr_blit/Makefile | 9 +- samples/formats/image_loader/Makefile | 69 + .../formats/image_loader/image_loader_test.c | 430 +++ samples/formats/jpeg/Makefile | 9 +- samples/formats/json/Makefile | 9 +- samples/formats/m3u/Makefile | 9 +- samples/formats/mp4/Makefile | 39 + samples/formats/mp4/rmp4_fragment_test.c | 204 ++ samples/formats/png/Makefile | 21 +- samples/formats/r7z/Makefile | 35 +- samples/formats/r7z/r7z_archive_detach_test.c | 444 +++ samples/formats/rrar/Makefile | 43 + samples/formats/rrar/archives/README | 15 + .../formats/rrar/archives/compress_best.rar | Bin 0 -> 12186 bytes samples/formats/rrar/archives/filter.rar | Bin 0 -> 94301 bytes .../formats/rrar/archives/lowdist_reset.rar | Bin 0 -> 566 bytes .../rrar/archives/ppmd_lzss_conversion.rar | Bin 0 -> 176700 bytes samples/formats/rrar/archives/rar5_arm.rar | Bin 0 -> 41082 bytes samples/formats/rrar/archives/rar5_blake2.rar | Bin 0 -> 642 bytes .../formats/rrar/archives/rar5_compressed.rar | Bin 0 -> 436 bytes .../archives/rar5_multiple_files_solid.rar | Bin 0 -> 677 bytes samples/formats/rrar/archives/rar5_stored.rar | Bin 0 -> 109 bytes samples/formats/rrar/archives/unicode.rar | Bin 0 -> 666 bytes samples/formats/rrar/rrar_archive_test.c | 446 +++ samples/formats/rzip/Makefile | 48 + samples/formats/rzip/rzip_archive_test.c | 1277 ++++++++ samples/formats/rzip/rzip_seek_test.c | 408 +++ samples/formats/tga/Makefile | 9 +- samples/formats/xml/Makefile | 9 +- samples/memmap/memshm_area_test/Makefile | 9 +- samples/memmap/memshm_test/Makefile | 9 +- samples/net/Makefile | 15 +- samples/queues/mpsc_stack_test/Makefile | 9 +- samples/queues/msg_fifo_queue_test/Makefile | 9 +- samples/queues/retro_spsc_bench/Makefile | 9 +- samples/queues/retro_spsc_test/Makefile | 9 +- .../queues/retro_waitable_spsc_test/Makefile | 9 +- .../queues/task_queue_budget_test/Makefile | 14 +- .../queues/task_queue_detach_test/Makefile | 14 +- .../task_queue_init_failure_test/Makefile | 14 +- .../task_queue_main_thread_test/Makefile | 14 +- .../queues/task_queue_switch_test/Makefile | 48 + .../task_queue_switch_test.c | 127 + .../task_queue_title_error_test/Makefile | 9 +- .../retro_asym_eventcount_test/Makefile | 9 +- .../rthreads/retro_eventcount_bench/Makefile | 9 +- .../rthreads/retro_eventcount_test/Makefile | 9 +- .../rthreads/retro_procbarrier_test/Makefile | 9 +- samples/rthreads/sthread_ex_test/Makefile | 9 +- samples/rthreads/tpool_bench/Makefile | 9 +- samples/rthreads/tpool_handoff_test/Makefile | 9 +- samples/rthreads/tpool_parallel_test/Makefile | 9 +- samples/rthreads/tpool_wait_test/Makefile | 9 +- samples/sample_objdir.mk | 12 + samples/streams/rzip/Makefile | 17 +- samples/string/strlcpy_append/Makefile | 9 +- .../string/word_wrap_overflow_test/Makefile | 9 +- samples/time/retro_sleep_until_test/Makefile | 9 +- samples/time/rtime_localtime_test/Makefile | 9 +- streams/file_stream.c | 41 +- vfs/vfs_implementation.c | 96 + vfs/vfs_implementation_cdrom.c | 10 +- vfs/vfs_implementation_nfs.c | 14 +- vfs/vfs_implementation_nfs.h | 4 +- vfs/vfs_implementation_uwp.cpp | 16 + 161 files changed, 15062 insertions(+), 541 deletions(-) create mode 100644 file/archive_file_rar.c create mode 100644 formats/image/image_yuv_blit.c create mode 100644 formats/image_texture_scale.c create mode 100644 formats/image_texture_set.c create mode 100644 formats/rar/rrar_archive.c create mode 100644 formats/rar/rrar_ppmd7.c create mode 100644 formats/rar/rrar_ppmd7.h create mode 100644 formats/zip/rzip_archive.c create mode 100644 include/formats/image_yuv_blit.h create mode 100644 include/jni/rjni.h create mode 100644 include/rar/rrar_archive.h create mode 100644 include/zip/rzip_archive.h create mode 100644 jni/rjni.c create mode 100644 samples/encodings/deflate/rdeflate_hash_test.c create mode 100644 samples/file/archive_rar/Makefile create mode 100644 samples/file/archive_rar/archive_rar_test.c create mode 100644 samples/file/vfs/vfs_prefetch_test.c create mode 100644 samples/formats/h265/rh265_malformed_test.c create mode 100644 samples/formats/image_loader/Makefile create mode 100644 samples/formats/image_loader/image_loader_test.c create mode 100644 samples/formats/mp4/Makefile create mode 100644 samples/formats/mp4/rmp4_fragment_test.c create mode 100644 samples/formats/r7z/r7z_archive_detach_test.c create mode 100644 samples/formats/rrar/Makefile create mode 100644 samples/formats/rrar/archives/README create mode 100644 samples/formats/rrar/archives/compress_best.rar create mode 100644 samples/formats/rrar/archives/filter.rar create mode 100644 samples/formats/rrar/archives/lowdist_reset.rar create mode 100644 samples/formats/rrar/archives/ppmd_lzss_conversion.rar create mode 100644 samples/formats/rrar/archives/rar5_arm.rar create mode 100644 samples/formats/rrar/archives/rar5_blake2.rar create mode 100644 samples/formats/rrar/archives/rar5_compressed.rar create mode 100644 samples/formats/rrar/archives/rar5_multiple_files_solid.rar create mode 100644 samples/formats/rrar/archives/rar5_stored.rar create mode 100644 samples/formats/rrar/archives/unicode.rar create mode 100644 samples/formats/rrar/rrar_archive_test.c create mode 100644 samples/formats/rzip/Makefile create mode 100644 samples/formats/rzip/rzip_archive_test.c create mode 100644 samples/formats/rzip/rzip_seek_test.c create mode 100644 samples/queues/task_queue_switch_test/Makefile create mode 100644 samples/queues/task_queue_switch_test/task_queue_switch_test.c create mode 100644 samples/sample_objdir.mk diff --git a/audio/resampler/audio_resampler_int16.c b/audio/resampler/audio_resampler_int16.c index 17dc8b2e..dd44c7a9 100644 --- a/audio/resampler/audio_resampler_int16.c +++ b/audio/resampler/audio_resampler_int16.c @@ -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")) diff --git a/audio/resampler/drivers/sinc_resampler.c b/audio/resampler/drivers/sinc_resampler.c index 8176ac84..19d2bf40 100644 --- a/audio/resampler/drivers/sinc_resampler.c +++ b/audio/resampler/drivers/sinc_resampler.c @@ -34,6 +34,7 @@ #include #include +#include #include #include @@ -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 -/* 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__) diff --git a/audio/resampler/drivers/sinc_resampler_int16.c b/audio/resampler/drivers/sinc_resampler_int16.c index 72bad540..af11a4fa 100644 --- a/audio/resampler/drivers/sinc_resampler_int16.c +++ b/audio/resampler/drivers/sinc_resampler_int16.c @@ -51,6 +51,7 @@ #include #include #include +#include #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; } diff --git a/audio/resampler/drivers/sinc_resampler_neon.S b/audio/resampler/drivers/sinc_resampler_neon.S index 50a493f2..2d1be83a 100644 --- a/audio/resampler/drivers/sinc_resampler_neon.S +++ b/audio/resampler/drivers/sinc_resampler_neon.S @@ -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... diff --git a/cdrom/cdrom.c b/cdrom/cdrom.c index 85e621ec..6a488e32 100644 --- a/cdrom/cdrom.c +++ b/cdrom/cdrom.c @@ -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 diff --git a/encodings/encoding_deflate.c b/encodings/encoding_deflate.c index 6e997667..14c49b52 100644 --- a/encodings/encoding_deflate.c +++ b/encodings/encoding_deflate.c @@ -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() - * answered yes above, the declaration never arrived, and the build broke - * on _mm_crc32_u32 with clang telling us which header it wanted. x86 - * only: the ARM path below reaches its intrinsic through arm_acle.h. - * - * crc32intrin.h carries the target attribute on the intrinsics - * themselves, so including it directly is safe whether or not the TU is - * built for SSE4.2, and harmless on toolchains that never split it out. */ -#if (defined(RD_CRC32_HASH_DIRECT) || defined(RD_CRC32_HASH_RUNTIME)) \ - && (defined(__x86_64__) || defined(__i386__) \ - || defined(_M_X64) || defined(_M_IX86)) -# if defined(__has_include) -# if __has_include() -# include -# endif -# endif -#endif - #if defined(RD_CRC32_HASH_RUNTIME) -#include #include #if defined(_MSC_VER) -#define RD_TARGET_CRC32 -#else -#define RD_TARGET_CRC32 __attribute__((target("sse4.2"))) -#endif -static RD_TARGET_CRC32 uint32_t rd_hash_crc(const uint8_t *p) +#include +static uint32_t rd_hash_crc(const uint8_t *p) { uint32_t k = (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16); return _mm_crc32_u32(0, k) & RD_HASH_MASK; } +#else +/* Emitted as an instruction rather than via _mm_crc32_u32: clang's + * intrinsic requires the separate 'crc32' target feature, which + * target("sse4.2") does not reliably imply across versions, so the + * build failed on toolchains/targets without SSE4.2. The assembler + * accepts the mnemonic regardless of -m flags, and rdeflate_new only + * selects this path when cpu_features_get() reports SSE4.2. */ +static uint32_t rd_hash_crc(const uint8_t *p) +{ + uint32_t k = (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16); + uint32_t r = 0; + __asm__("crc32l %1, %0" : "+r"(r) : "rm"(k)); + return r & RD_HASH_MASK; +} +#endif #endif #if !defined(RD_CRC32_HASH_DIRECT) diff --git a/features/features_cpu.c b/features/features_cpu.c index 7bffe594..40c7b0e6 100644 --- a/features/features_cpu.c +++ b/features/features_cpu.c @@ -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__) { diff --git a/file/archive_file.c b/file/archive_file.c index cdb9f143..f0bf7012 100644 --- a/file/archive_file.c +++ b/file/archive_file.c @@ -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; diff --git a/file/archive_file_rar.c b/file/archive_file_rar.c new file mode 100644 index 00000000..a3db38c3 --- /dev/null +++ b/file/archive_file_rar.c @@ -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 + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +/* 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" +}; diff --git a/file/config_file.c b/file/config_file.c index 025e37fd..5f87dcc8 100644 --- a/file/config_file.c +++ b/file/config_file.c @@ -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")); } diff --git a/file/file_path.c b/file/file_path.c index 61d5c53b..d85289a4 100644 --- a/file/file_path.c +++ b/file/file_path.c @@ -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])) { diff --git a/formats/7z/r7z_archive.c b/formats/7z/r7z_archive.c index b9fcdfdb..65e5f339 100644 --- a/formats/7z/r7z_archive.c +++ b/formats/7z/r7z_archive.c @@ -49,6 +49,7 @@ #include #include +#include #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; +} diff --git a/formats/chd/rchd.c b/formats/chd/rchd.c index 5716f642..8f636b46 100644 --- a/formats/chd/rchd.c +++ b/formats/chd/rchd.c @@ -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; diff --git a/formats/h264/rh264.c b/formats/h264/rh264.c index e66ef115..cfe8b206 100644 --- a/formats/h264/rh264.c +++ b/formats/h264/rh264.c @@ -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); diff --git a/formats/h265/rh265.c b/formats/h265/rh265.c index 3b7f3023..44146cc8 100644 --- a/formats/h265/rh265.c +++ b/formats/h265/rh265.c @@ -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); diff --git a/formats/image/image_yuv_blit.c b/formats/image/image_yuv_blit.c new file mode 100644 index 00000000..688ee94e --- /dev/null +++ b/formats/image/image_yuv_blit.c @@ -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 + +#include + +/* 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); +} diff --git a/formats/image_texture.c b/formats/image_texture.c index f8e91ccd..02f71c1c 100644 --- a/formats/image_texture.c +++ b/formats/image_texture.c @@ -28,6 +28,21 @@ #include #include #include +#ifdef HAVE_RPNG +#include +#endif +#ifdef HAVE_RJPEG +#include +#endif +#ifdef HAVE_RTGA +#include +#endif +#ifdef HAVE_RBMP +#include +#endif +#ifdef HAVE_RWEBP +#include +#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); } diff --git a/formats/image_texture_scale.c b/formats/image_texture_scale.c new file mode 100644 index 00000000..f2199cc0 --- /dev/null +++ b/formats/image_texture_scale.c @@ -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 +#include +#include + +#include +#include +#include + +#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; +} diff --git a/formats/image_texture_set.c b/formats/image_texture_set.c new file mode 100644 index 00000000..460f6d8a --- /dev/null +++ b/formats/image_texture_set.c @@ -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 +#include + +#include +#include +#include +#ifdef HAVE_GCD +#include +#elif defined(HAVE_THREADS) +#include +#include +#include +#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; +} diff --git a/formats/mp3/rmp3.c b/formats/mp3/rmp3.c index e3908ee9..062dbbbe 100644 --- a/formats/mp3/rmp3.c +++ b/formats/mp3/rmp3.c @@ -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; diff --git a/formats/mp4/rmp4.c b/formats/mp4/rmp4.c index 49d4a981..f13a4c7d 100644 --- a/formats/mp4/rmp4.c +++ b/formats/mp4/rmp4.c @@ -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) diff --git a/formats/rar/rrar_archive.c b/formats/rar/rrar_archive.c new file mode 100644 index 00000000..705859f8 --- /dev/null +++ b/formats/rar/rrar_archive.c @@ -0,0 +1,2565 @@ +/* Copyright (C) 2010-2026 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (rrar_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. + */ + +/* The unpacking of RAR 2.9 data here - the block header and code tables, + * the symbols of the LZ coder with their length and distance tables, how + * a filter is read, which program it is, and what the standard filters + * do - follows libarchive's archive_read_support_format_rar.c: + * + * Copyright (c) 2003-2007 Tim Kientzle + * Copyright (c) 2011 Andres Mejia + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The unpacking of RAR 5 data - the block header, the code tables, the + * symbols and how lengths and distances are made up from them, and the + * four filters - follows libarchive's archive_read_support_format_rar5.c: + * + * Copyright (c) 2018 Grzegorz Antoniak (http://antoniak.org) + * All rights reserved. + * + * (under the same two-clause licence as above) + * + * How it is put together is this file's own. libarchive unpacks through + * a sliding window, a block at a time, and runs each filter when its + * block has gone by. Here a member is unpacked whole into the buffer the + * caller is given, and that buffer is the window: a match is a copy + * from earlier in it, however far back. The filters are only noted as + * they are met, and run once the member is all there - they change + * bytes that later matches still have to copy from as they were. + */ + +#include +#include + +#include +#include +#include + +#include "rrar_ppmd7.h" + +/* ------------------------------------------------------------ container */ + +#define RAR_HEAD_MAIN 0x73 +#define RAR_HEAD_FILE 0x74 +#define RAR_HEAD_END 0x7b + +#define RAR_MAIN_VOLUME 0x0001 +#define RAR_MAIN_SOLID 0x0008 +#define RAR_MAIN_PASSWORD 0x0080 + +#define RAR_FILE_SPLIT_BEFORE 0x0001 +#define RAR_FILE_SPLIT_AFTER 0x0002 +#define RAR_FILE_PASSWORD 0x0004 +#define RAR_FILE_SOLID 0x0010 +#define RAR_FILE_DIRECTORY 0x00e0 /* (the dictionary size bits, all set) */ +#define RAR_FILE_LARGE 0x0100 +#define RAR_FILE_UNICODE 0x0200 + +#define RAR_HEAD_ADD_SIZE 0x8000 + +#define RAR_METHOD_STORED 0x30 + +/* ------------------------------------------------------------- unpacking */ + +#define MAINCODE_SIZE 299 +/* RAR 5's four codes, and the largest any code here has */ +#define MAINCODE5_SIZE 306 +#define OFFSETCODE5_SIZE 64 +#define LOWOFFSETCODE5_SIZE 16 +#define LENGTHCODE5_SIZE 44 +#define HUFFMAN_TABLE5_SIZE (MAINCODE5_SIZE + OFFSETCODE5_SIZE + LOWOFFSETCODE5_SIZE + LENGTHCODE5_SIZE) +#define MAX_CODE_SIZE MAINCODE5_SIZE +/* a RAR 5 filter's block may be this long */ +#define FILTER5_BLOCK_MAX 0x400000 +#define OFFSETCODE_SIZE 60 +#define LOWOFFSETCODE_SIZE 17 +#define LENGTHCODE_SIZE 28 +#define HUFFMAN_TABLE_SIZE (MAINCODE_SIZE + OFFSETCODE_SIZE + LOWOFFSETCODE_SIZE + LENGTHCODE_SIZE) +#define PRECODE_SIZE 20 +#define MAX_CODE_LENGTH 15 +#define QUICK_BITS 10 + +/* The memory a filter has: its block may be no longer. */ +#define VM_MEMORY_SIZE 0x40000 +#define FILTER_WORK_SIZE 0x3c000 +#define FILTER_GLOBAL_MAX (0x2000 - 0x40) + +enum +{ + FILTER_DELTA = 1, + FILTER_E8, + FILTER_E8E9, + FILTER_RGB, + FILTER_AUDIO, + /* RAR 5's: told by a number in the data, not by a program */ + FILTER5_DELTA, + FILTER5_E8, + FILTER5_E8E9, + FILTER5_ARM +}; + +typedef struct rrar_huff +{ + uint16_t count[MAX_CODE_LENGTH + 1]; + uint16_t first[MAX_CODE_LENGTH + 1]; /* first code of each length */ + uint16_t index[MAX_CODE_LENGTH + 1]; /* and where its symbols start */ + uint32_t limit[MAX_CODE_LENGTH + 1]; /* one past the last, as 16 bits from the top */ + uint16_t symbol[MAX_CODE_SIZE]; + uint16_t quick[1 << QUICK_BITS]; /* length << 12 | symbol, 0: longer */ +} rrar_huff_t; + +/* Bits, the first of a byte first. */ +typedef struct rrar_bits +{ + const uint8_t *p; + const uint8_t *end; + uint64_t buf; /* the bits to come, from the top */ + int bits; /* how many of them */ + uint32_t past; /* bytes made up after the end */ +} rrar_bits_t; + +typedef struct rrar_program +{ + uint32_t kind; /* FILTER_* */ + uint32_t usage; + uint32_t old_length; +} rrar_program_t; + +typedef struct rrar_filter +{ + uint64_t start; /* in the member */ + uint32_t length; + uint32_t kind; + uint32_t r0, r1, r4; /* the registers the standard filters read */ +} rrar_filter_t; + +typedef struct rrar_unpack +{ + rrar_bits_t br; + uint8_t *out; + uint64_t pos; + uint64_t size; + + rrar_huff_t main_code; + rrar_huff_t offset_code; + rrar_huff_t low_offset_code; + rrar_huff_t length_code; + uint8_t lengths[HUFFMAN_TABLE5_SIZE]; + + uint32_t old_offset[4]; + uint32_t last_offset; + uint32_t last_length; + uint32_t last_low_offset; + uint32_t low_offset_repeats; + + int is_ppmd; + int ppmd_valid; + int ppmd_escape; + rrar_ppmd7_t ppmd; + rrar_ppmd7_range_t range; + + rrar_program_t *programs; + uint32_t num_programs; + uint32_t cap_programs; + uint32_t last_program; + rrar_filter_t *filters; + uint32_t num_filters; + uint32_t cap_filters; + + /* For a caller that reads the member while it is being decoded + * (rrar_archive_extract_to()): who is told how far it has got, the + * position at which it is next told, how far back the packed data + * can refer, and the first filter not yet run. Without a watcher the + * position is the member's size, which is never reached before the + * end, and every filter is run then. */ + const rrar_watch_t *watch; + uint64_t watch_at; + uint64_t window; + uint32_t next_filter; + uint8_t *work; /* the filters' second copy */ +} rrar_unpack_t; + +struct rrar_archive +{ + const uint8_t *data; /* the whole archive, or NULL when it is read */ + size_t len; + rrar_read_t read_cb; /* positioned reads of it, when data is NULL */ + void *read_ud; + uint8_t *head; /* the header being parsed, when it was read in */ + size_t head_cap; + rrar_entry_t *entries; + char *names; + uint32_t num_entries; + uint32_t cap_entries; + size_t names_len; + size_t names_cap; +}; + +static INLINE uint32_t rd16(const uint8_t *p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8); +} + +static INLINE 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 INLINE void wr32(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); + p[2] = (uint8_t)(v >> 16); + p[3] = (uint8_t)(v >> 24); +} + +/* ------------------------------------------------------------------ bits */ + +static INLINE void rrar_br_fill(rrar_bits_t *br) +{ + if (br->bits > 32) + return; + if (br->end - br->p >= 4) + { + /* four bytes in one go, anywhere but at the very end */ + uint32_t w = ((uint32_t)br->p[0] << 24) | ((uint32_t)br->p[1] << 16) + | ((uint32_t)br->p[2] << 8) | (uint32_t)br->p[3]; + br->buf |= (uint64_t)w << (32 - br->bits); + br->p += 4; + br->bits += 32; + return; + } + while (br->bits <= 56) + { + uint64_t byte = 0; + if (br->p < br->end) + byte = *br->p++; + else + br->past++; + br->buf |= byte << (56 - br->bits); + br->bits += 8; + } +} + +/* The next @n bits, 1 to 32, without taking them. */ +static INLINE uint32_t rrar_br_peek(rrar_bits_t *br, int n) +{ + rrar_br_fill(br); + return (uint32_t)(br->buf >> (64 - n)); +} + +static INLINE void rrar_br_skip(rrar_bits_t *br, int n) +{ + br->buf <<= n; + br->bits -= n; +} + +static INLINE uint32_t rrar_br_get(rrar_bits_t *br, int n) +{ + uint32_t v = rrar_br_peek(br, n); + rrar_br_skip(br, n); + return v; +} + +static uint32_t rrar_br_get32(rrar_bits_t *br) +{ + return rrar_br_get(br, 32); +} + +/* To the start of the next byte. */ +static void rrar_br_align(rrar_bits_t *br) +{ + rrar_br_skip(br, br->bits & 7); +} + +/* More was read than the data had: up to 8 bytes are looked at ahead of + * where the reading is, so that many past the end mean nothing yet. */ +static INLINE int rrar_br_overrun(const rrar_bits_t *br) +{ + return br->past > 8; +} + +/* --------------------------------------------------------------- Huffman */ + +/* The code RAR means by a list of lengths: codes of a length in the + * order of their symbols, shorter lengths first. false if the lengths + * are of no prefix code. */ +static int huff_build(rrar_huff_t *h, const uint8_t *lengths, unsigned n) +{ + uint16_t next[MAX_CODE_LENGTH + 1]; + uint32_t code = 0; + uint32_t space = 1u << MAX_CODE_LENGTH; + unsigned i, len, total = 0; + + memset(h->count, 0, sizeof(h->count)); + memset(h->quick, 0, sizeof(h->quick)); + for (i = 0; i < n; i++) + h->count[lengths[i] & 15]++; + h->count[0] = 0; + + for (len = 1; len <= MAX_CODE_LENGTH; len++) + { + uint32_t need = (uint32_t)h->count[len] << (MAX_CODE_LENGTH - len); + if (need > space) + return 0; + space -= need; + h->first[len] = (uint16_t)code; + h->index[len] = (uint16_t)total; + next[len] = (uint16_t)total; + total += h->count[len]; + code = code + h->count[len]; + h->limit[len] = code << (16 - len); + code <<= 1; + } + + for (i = 0; i < n; i++) + { + len = lengths[i] & 15; + if (!len) + continue; + h->symbol[next[len]] = (uint16_t)i; + if (len <= QUICK_BITS) + { + uint32_t c = h->first[len] + (next[len] - h->index[len]); + uint32_t from = c << (QUICK_BITS - len); + uint32_t to = from + (1u << (QUICK_BITS - len)); + for (; from < to; from++) + h->quick[from] = (uint16_t)((len << 12) | i); + } + next[len]++; + } + return 1; +} + +/* The next symbol, or -1 if the bits are no code of this table. */ +static INLINE int huff_decode(rrar_bits_t *br, const rrar_huff_t *h) +{ + uint32_t v = rrar_br_peek(br, 16); + uint32_t e = h->quick[v >> (16 - QUICK_BITS)]; + unsigned len; + + if (e) + { + rrar_br_skip(br, (int)(e >> 12)); + return (int)(e & 0xfff); + } + for (len = QUICK_BITS + 1; len <= MAX_CODE_LENGTH; len++) + { + if (v < h->limit[len]) + { + unsigned i = h->index[len] + ((v >> (16 - len)) - h->first[len]); + rrar_br_skip(br, (int)len); + return h->symbol[i]; + } + } + return -1; +} + +/* ------------------------------------------------------------------ PPMd */ + +/* A PPMd block reads its bytes straight from the data. At its start the + * bit reader is on a byte boundary: the bytes it holds and has not given + * out go back, made-up ones past the end staying counted. */ +static int ppmd_range_start(rrar_unpack_t *u) +{ + rrar_bits_t *br = &u->br; + const uint8_t *p = br->p; + uint32_t past = br->past; + uint32_t buffered = (uint32_t)(br->bits >> 3); + + if (buffered <= past) + past -= buffered; + else + { + p -= buffered - past; + past = 0; + } + return rrar_ppmd7_range_init(&u->range, p, br->end, past); +} + +/* And when the block is over, the bit reader goes on from there. */ +static void ppmd_range_end(rrar_unpack_t *u) +{ + u->br.p = u->range.p; + u->br.past = u->range.past; + u->br.buf = 0; + u->br.bits = 0; +} + +/* More was read than the data had, by whichever coder is reading. */ +static int unpack_overrun(const rrar_unpack_t *u) +{ + if (u->is_ppmd) + return u->range.past > 8; + return rrar_br_overrun(&u->br); +} + +/* A byte of what the member says besides its data - a filter - by + * whichever coder the block is in. */ +static int unpack_byte(rrar_unpack_t *u) +{ + if (u->is_ppmd) + return rrar_ppmd7_decode_symbol(&u->ppmd, &u->range); + return (int)rrar_br_get(&u->br, 8); +} + +/* ---------------------------------------------------------- block header */ + +/* Where a block starts: which coder it is in, and that coder's tables or + * model. */ +static int unpack_tables(rrar_unpack_t *u) +{ + rrar_bits_t *br = &u->br; + uint8_t pre_lengths[PRECODE_SIZE]; + rrar_huff_t *pre; + int i, r = RRAR_ERROR_DATA; + + if (u->is_ppmd) + ppmd_range_end(u); + rrar_br_align(br); + + u->is_ppmd = (int)rrar_br_get(br, 1); + if (u->is_ppmd) + { + unsigned flags = rrar_br_get(br, 7); + unsigned mem_mb = 0; + + /* a new model: how much memory it has */ + if (flags & 0x20) + mem_mb = rrar_br_get(br, 8) + 1; + if (flags & 0x40) + u->ppmd_escape = (int)rrar_br_get(br, 8); + + if (flags & 0x20) + { + unsigned order = (flags & 0x1f) + 1; + if (order > 16) + order = 16 + (order - 16) * 3; + if (order == 1) + return RRAR_ERROR_DATA; + if (!rrar_ppmd7_alloc(&u->ppmd, mem_mb << 20)) + return RRAR_ERROR_MEM; + if (!ppmd_range_start(u)) + return RRAR_ERROR_DATA; + rrar_ppmd7_init(&u->ppmd, order); + u->ppmd_valid = 1; + } + else + { + /* the model goes on from where the last PPMd block left it */ + if (!u->ppmd_valid) + return RRAR_ERROR_DATA; + if (!ppmd_range_start(u)) + return RRAR_ERROR_DATA; + } + return unpack_overrun(u) ? RRAR_ERROR_DATA : RRAR_OK; + } + + /* (what the low distance bits repeat belongs to the tables) */ + u->last_low_offset = 0; + u->low_offset_repeats = 0; + + /* the lengths are differences from the last tables', unless told not */ + if (!rrar_br_get(br, 1)) + memset(u->lengths, 0, sizeof(u->lengths)); + + memset(pre_lengths, 0, sizeof(pre_lengths)); + for (i = 0; i < PRECODE_SIZE;) + { + pre_lengths[i++] = (uint8_t)rrar_br_get(br, 4); + if (pre_lengths[i - 1] == 0xf) + { + unsigned zeros = rrar_br_get(br, 4); + if (zeros) + { + unsigned j; + i--; + for (j = 0; j < zeros + 2 && i < PRECODE_SIZE; j++) + pre_lengths[i++] = 0; + } + } + } + + if (!(pre = (rrar_huff_t *)malloc(sizeof(*pre)))) + return RRAR_ERROR_MEM; + if (!huff_build(pre, pre_lengths, PRECODE_SIZE)) + goto done; + + for (i = 0; i < HUFFMAN_TABLE_SIZE;) + { + int val = huff_decode(br, pre); + int n, j; + + if (val < 0 || rrar_br_overrun(br)) + goto done; + if (val < 16) + { + u->lengths[i] = (uint8_t)((u->lengths[i] + val) & 0xf); + i++; + } + else if (val < 18) + { + /* the last length again, a few or many times */ + if (i == 0) + goto done; + n = val == 16 ? (int)rrar_br_get(br, 3) + 3 : (int)rrar_br_get(br, 7) + 11; + for (j = 0; j < n && i < HUFFMAN_TABLE_SIZE; j++, i++) + u->lengths[i] = u->lengths[i - 1]; + } + else + { + /* zeros */ + n = val == 18 ? (int)rrar_br_get(br, 3) + 3 : (int)rrar_br_get(br, 7) + 11; + for (j = 0; j < n && i < HUFFMAN_TABLE_SIZE; j++) + u->lengths[i++] = 0; + } + } + + if ( huff_build(&u->main_code, u->lengths, MAINCODE_SIZE) + && huff_build(&u->offset_code, u->lengths + MAINCODE_SIZE, OFFSETCODE_SIZE) + && huff_build(&u->low_offset_code, + u->lengths + MAINCODE_SIZE + OFFSETCODE_SIZE, LOWOFFSETCODE_SIZE) + && huff_build(&u->length_code, + u->lengths + MAINCODE_SIZE + OFFSETCODE_SIZE + LOWOFFSETCODE_SIZE, + LENGTHCODE_SIZE)) + r = RRAR_OK; + +done: + free(pre); + return r; +} + +/* --------------------------------------------------------------- filters */ + +/* A number as the filters' virtual machine writes them. */ +static uint32_t vm_number(rrar_bits_t *br) +{ + uint32_t val; + + switch (rrar_br_get(br, 2)) + { + case 0: + return rrar_br_get(br, 4); + case 1: + val = rrar_br_get(br, 8); + if (val >= 16) + return val; + return 0xffffff00u | (val << 4) | rrar_br_get(br, 4); + case 2: + return rrar_br_get(br, 16); + } + return rrar_br_get32(br); +} + +/* Which of the standard filters a program is: they are told by their + * length and checksum, and run as what they are known to do. */ +static uint32_t filter_kind(const uint8_t *code, uint32_t len) +{ + uint32_t crc = encoding_crc32(0, code, len); + + if (len == 0x1d && crc == 0x0e06077du) + return FILTER_DELTA; + if (len == 0x35 && crc == 0xad576887u) + return FILTER_E8; + if (len == 0x39 && crc == 0x3cd7e57eu) + return FILTER_E8E9; + if (len == 0x95 && crc == 0x1c2c5dc8u) + return FILTER_RGB; + if (len == 0xd8 && crc == 0xbc85e701u) + return FILTER_AUDIO; + return 0; +} + +/* A filter's description, @length bytes of it: which program (a new one + * comes with its code), the block it is for, and its registers. */ +static int filter_parse(rrar_unpack_t *u, const uint8_t *bytes, uint32_t length, unsigned flags) +{ + rrar_bits_t br; + rrar_program_t *prog = NULL; + rrar_filter_t *f; + uint32_t num, block_length, i; + uint32_t regs[7]; + uint64_t block_start; + + memset(&br, 0, sizeof(br)); + br.p = bytes; + br.end = bytes + length; + memset(regs, 0, sizeof(regs)); + + if (flags & 0x80) + { + num = vm_number(&br); + if (num == 0) + u->num_programs = 0; /* the programs so far are forgotten */ + else + num--; + if (num > u->num_programs) + return RRAR_ERROR_DATA; + u->last_program = num; + } + else + num = u->last_program; + + if (num < u->num_programs) + { + prog = &u->programs[num]; + prog->usage++; + } + + block_start = (uint64_t)vm_number(&br) + u->pos; + if (flags & 0x40) + block_start += 258; + if (flags & 0x20) + block_length = vm_number(&br); + else + block_length = prog ? prog->old_length : 0; + + regs[4] = block_length; + if (flags & 0x10) + { + unsigned mask = rrar_br_get(&br, 7); + for (i = 0; i < 7; i++) + if (mask & (1u << i)) + regs[i] = vm_number(&br); + } + + if (!prog) + { + uint32_t len = vm_number(&br); + uint8_t *code; + uint8_t x = 0; + uint32_t kind; + + if (len == 0 || len > 0x10000 || rrar_br_overrun(&br)) + return RRAR_ERROR_DATA; + if (!(code = (uint8_t *)malloc(len))) + return RRAR_ERROR_MEM; + for (i = 0; i < len; i++) + code[i] = (uint8_t)rrar_br_get(&br, 8); + for (i = 1; i < len; i++) + x ^= code[i]; + kind = (x == code[0] && !rrar_br_overrun(&br)) ? filter_kind(code, len) : 0; + i = x == code[0]; + free(code); + if (!i || rrar_br_overrun(&br)) + return RRAR_ERROR_DATA; + if (!kind) + return RRAR_ERROR_UNSUPPORTED; /* a program of the archive's own */ + + if (u->num_programs == u->cap_programs) + { + uint32_t cap = u->cap_programs ? u->cap_programs * 2 : 8; + rrar_program_t *p = (rrar_program_t *)realloc(u->programs, cap * sizeof(*p)); + if (!p) + return RRAR_ERROR_MEM; + u->programs = p; + u->cap_programs = cap; + } + prog = &u->programs[u->num_programs++]; + prog->kind = kind; + prog->usage = 0; + } + prog->old_length = block_length; + + if (flags & 0x08) + { + /* data of the program's own: the standard ones take none */ + uint32_t len = vm_number(&br); + if (len > FILTER_GLOBAL_MAX) + return RRAR_ERROR_DATA; + for (i = 0; i < len; i++) + rrar_br_get(&br, 8); + } + if (rrar_br_overrun(&br)) + return RRAR_ERROR_DATA; + + if ( block_length > VM_MEMORY_SIZE + || block_start < u->pos + || block_start > u->size + || block_length > u->size - block_start) + return RRAR_ERROR_DATA; + + if (u->num_filters == u->cap_filters) + { + uint32_t cap = u->cap_filters ? u->cap_filters * 2 : 64; + rrar_filter_t *p = (rrar_filter_t *)realloc(u->filters, cap * sizeof(*p)); + if (!p) + return RRAR_ERROR_MEM; + u->filters = p; + u->cap_filters = cap; + } + f = &u->filters[u->num_filters++]; + f->start = block_start; + f->length = block_length; + f->kind = prog->kind; + f->r0 = regs[0]; + f->r1 = regs[1]; + f->r4 = regs[4]; + return RRAR_OK; +} + +/* A filter in the data: a byte of flags, its length, and the description. */ +static int filter_read(rrar_unpack_t *u) +{ + uint8_t *code; + int flags, v, r; + uint32_t length, i; + + if ((flags = unpack_byte(u)) < 0) + return RRAR_ERROR_DATA; + length = ((unsigned)flags & 7) + 1; + if (length == 7) + { + if ((v = unpack_byte(u)) < 0) + return RRAR_ERROR_DATA; + length = (uint32_t)v + 7; + } + else if (length == 8) + { + if ((v = unpack_byte(u)) < 0) + return RRAR_ERROR_DATA; + length = (uint32_t)v << 8; + if ((v = unpack_byte(u)) < 0) + return RRAR_ERROR_DATA; + length |= (uint32_t)v; + } + if (!(code = (uint8_t *)malloc(length ? length : 1))) + return RRAR_ERROR_MEM; + for (i = 0; i < length; i++) + { + if ((v = unpack_byte(u)) < 0 || unpack_overrun(u)) + { + free(code); + return RRAR_ERROR_DATA; + } + code[i] = (uint8_t)v; + } + r = filter_parse(u, code, length, (unsigned)flags); + free(code); + return r; +} + +/* x86: the 32-bit addresses after CALL (and JMP), which were made + * absolute for packing, are relative again. */ +static void filter_e8(uint8_t *mem, uint32_t length, uint32_t pos, int e9_also) +{ + const uint32_t file_size = 0x1000000; + uint32_t i; + + if (length <= 4) + return; + for (i = 0; i <= length - 5; i++) + { + if (mem[i] == 0xe8 || (e9_also && mem[i] == 0xe9)) + { + uint32_t cur = pos + i + 1; + uint32_t address = rd32(mem + i + 1); + + if (address & 0x80000000u) + { + /* negative: within the file if no further back than here */ + if (cur >= (0u - address)) + wr32(mem + i + 1, address + file_size); + } + else if (address < file_size) + wr32(mem + i + 1, address - cur); + i += 4; + } + } +} + +/* Bytes that were stored as differences from the byte @channels back. */ +static void filter_delta(const uint8_t *src, uint8_t *dst, uint32_t length, uint32_t channels) +{ + uint32_t i, idx; + + for (i = 0; i < channels; i++) + { + uint8_t last = 0; + for (idx = i; idx < length; idx += channels) + last = dst[idx] = (uint8_t)(last - *src++); + } +} + +static INLINE uint32_t abs_diff(int v) +{ + return (uint32_t)(v < 0 ? -v : v); +} + +/* A picture of 3 bytes to a pixel, @stride bytes to a row, stored as + * what was left after predicting each byte from its neighbours. */ +static void filter_rgb(const uint8_t *src, uint8_t *dst, uint32_t length, + uint32_t stride, uint32_t byte_offset) +{ + uint32_t i, j; + + for (i = 0; i < 3; i++) + { + uint8_t byte = 0; + for (j = i; j < length; j += 3) + { + if (j >= stride) + { + const uint8_t *prev = dst + j - stride; + if (j - stride + 3 < length) + { + uint32_t d1 = abs_diff((int)prev[3] - prev[0]); + uint32_t d2 = abs_diff((int)byte - prev[0]); + uint32_t d3 = abs_diff((int)prev[3] - prev[0] + byte - prev[0]); + if (d1 > d2 || d1 > d3) + byte = d2 <= d3 ? prev[3] : prev[0]; + } + } + byte -= *src++; + dst[j] = byte; + } + } + for (i = byte_offset; i + 2 < length; i += 3) + { + dst[i] = (uint8_t)(dst[i] + dst[i + 1]); + dst[i + 2] = (uint8_t)(dst[i + 2] + dst[i + 1]); + } +} + +/* Sound, @channels of it, each byte stored as its difference from a + * prediction that adapts as it goes. */ +static void filter_audio(const uint8_t *src, uint8_t *dst, uint32_t length, uint32_t channels) +{ + uint32_t i, j; + + for (i = 0; i < channels; i++) + { + int8_t weight[5]; + int16_t delta[4]; + int8_t last_delta = 0; + int error[11]; + uint32_t count = 0; + uint8_t last_byte = 0; + + memset(weight, 0, sizeof(weight)); + memset(delta, 0, sizeof(delta)); + memset(error, 0, sizeof(error)); + + for (j = i; j < length; j += channels) + { + int8_t d = (int8_t)*src++; + uint8_t pred, byte; + int pred_error; + + delta[2] = delta[1]; + delta[1] = (int16_t)(last_delta - delta[0]); + delta[0] = last_delta; + pred = (uint8_t)(((8 * last_byte + weight[0] * delta[0] + + weight[1] * delta[1] + weight[2] * delta[2]) >> 3) & 0xff); + byte = (uint8_t)((pred - d) & 0xff); + + pred_error = d * 8; + error[0] += (int)abs_diff(pred_error); + error[1] += (int)abs_diff(pred_error - delta[0]); + error[2] += (int)abs_diff(pred_error + delta[0]); + error[3] += (int)abs_diff(pred_error - delta[1]); + error[4] += (int)abs_diff(pred_error + delta[1]); + error[5] += (int)abs_diff(pred_error - delta[2]); + error[6] += (int)abs_diff(pred_error + delta[2]); + + last_delta = (int8_t)(byte - last_byte); + dst[j] = last_byte = byte; + + if (!(count++ & 0x1f)) + { + unsigned k, best = 0; + for (k = 1; k < 7; k++) + if (error[k] < error[best]) + best = k; + memset(error, 0, sizeof(error)); + switch (best) + { + case 1: if (weight[0] >= -16) weight[0]--; break; + case 2: if (weight[0] < 16) weight[0]++; break; + case 3: if (weight[1] >= -16) weight[1]--; break; + case 4: if (weight[1] < 16) weight[1]++; break; + case 5: if (weight[2] >= -16) weight[2]--; break; + case 6: if (weight[2] < 16) weight[2]++; break; + } + } + } + } +} + +/* RAR 5's x86 filter: as RAR 2.9's, with the position taken within 16 MB. */ +static void filter5_e8(uint8_t *mem, uint32_t length, uint64_t start, int e9_also) +{ + const uint32_t file_size = 0x1000000; + uint32_t i; + + for (i = 0; i + 4 < length;) + { + uint8_t b = mem[i++]; + if (b == 0xe8 || (e9_also && b == 0xe9)) + { + uint32_t offset = (uint32_t)((start + i) % file_size); + uint32_t address = rd32(mem + i); + + if (address & 0x80000000u) + { + if (!((address + offset) & 0x80000000u)) + wr32(mem + i, address + file_size); + } + else if ((address - file_size) & 0x80000000u) + wr32(mem + i, address - offset); + i += 4; + } + } +} + +/* ARM: the 24-bit word addresses after BL are relative again. */ +static void filter5_arm(uint8_t *mem, uint32_t length, uint64_t start) +{ + uint32_t i; + + for (i = 0; i + 3 < length; i += 4) + { + if (mem[i + 3] == 0xeb) + { + uint32_t offset = rd32(mem + i) & 0x00ffffffu; + offset -= (uint32_t)((start + i) / 4); + wr32(mem + i, (offset & 0x00ffffffu) | 0xeb000000u); + } + } +} + +/* The member is all unpacked: its filters, in the order they came. Two + * for the same block are one after the other on it. */ +/* One filter over its block, which is all there. */ +static int filter_apply(rrar_unpack_t *u, const rrar_filter_t *f) +{ + uint8_t *block = u->out + (size_t)f->start; + uint32_t length = f->length; + + if (f->kind >= FILTER5_DELTA) + { + /* RAR 5's: their blocks are up to 4 MB, and nothing but the + * delta filter needs a second copy */ + if (f->kind == FILTER5_E8 || f->kind == FILTER5_E8E9) + filter5_e8(block, length, f->start, f->kind == FILTER5_E8E9); + else if (f->kind == FILTER5_ARM) + filter5_arm(block, length, f->start); + else + { + uint8_t *copy = (uint8_t *)malloc(length ? length : 1); + if (!copy) + return RRAR_ERROR_MEM; + filter_delta(block, copy, length, f->r0); + memcpy(block, copy, length); + free(copy); + } + return RRAR_OK; + } + + /* (checked when it was read against the member's size; a filter + * whose result is not its block's length would move everything + * after it) */ + if (f->r4 != length) + return RRAR_ERROR_DATA; + if (f->kind == FILTER_E8 || f->kind == FILTER_E8E9) + { + if (length > FILTER_WORK_SIZE) + return RRAR_ERROR_DATA; + filter_e8(block, length, (uint32_t)f->start, f->kind == FILTER_E8E9); + return RRAR_OK; + } + + if (length > FILTER_WORK_SIZE / 2) + return RRAR_ERROR_DATA; + if (!u->work && !(u->work = (uint8_t *)malloc(FILTER_WORK_SIZE / 2))) + return RRAR_ERROR_MEM; + switch (f->kind) + { + case FILTER_DELTA: + if (f->r0 == 0 || f->r0 > 128) + return RRAR_ERROR_DATA; + filter_delta(block, u->work, length, f->r0); + break; + case FILTER_RGB: + if (f->r0 > length || length < 3 || f->r1 > 2) + return RRAR_ERROR_DATA; + /* (a row no wider than a pixel is predicted from bytes + * not written yet: let them be something) */ + memset(u->work, 0, length); + filter_rgb(block, u->work, length, f->r0, f->r1); + break; + case FILTER_AUDIO: + if (f->r0 == 0 || f->r0 > 128) + return RRAR_ERROR_DATA; + filter_audio(block, u->work, length, f->r0); + break; + default: + return RRAR_ERROR_UNSUPPORTED; + } + memcpy(block, u->work, length); + return RRAR_OK; +} + +/* The filters not run yet, in the order they were read. */ +static int filters_run(rrar_unpack_t *u) +{ + int r = RRAR_OK; + + for (; u->next_filter < u->num_filters && r == RRAR_OK; u->next_filter++) + r = filter_apply(u, &u->filters[u->next_filter]); + return r; +} + +/* The decoder has got to watch_at: the watcher is told how much of the + * member is final. + * + * What has been decoded is final unless a filter is still to be run + * over it. A filter is read before the bytes it is for, so the bytes + * before the first filter not yet run are final; and a filter can be + * run, in place, once the decoder is so far past its block that nothing + * still to be decoded can refer back into it - the packed data refers to + * the bytes as they were before any filter. In the order they were read, + * as at the end. */ +#define RRAR_WATCH_STEP 0x10000 +static int unpack_watch(rrar_unpack_t *u) +{ + uint64_t done = u->pos; + uint32_t n; + int r; + + if (!u->watch) + { + u->watch_at = u->size; + return RRAR_OK; + } + while (u->next_filter < u->num_filters) + { + const rrar_filter_t *f = &u->filters[u->next_filter]; + + if (u->pos < f->start + f->length + u->window) + break; + if ((r = filter_apply(u, f)) != RRAR_OK) + return r; + u->next_filter++; + } + for (n = u->next_filter; n < u->num_filters; n++) + { + if (u->filters[n].start < done) + done = u->filters[n].start; + } + u->watch_at = u->pos + RRAR_WATCH_STEP; + if (u->watch->progress(u->watch->ud, (size_t)done)) + return RRAR_ERROR_CANCELLED; + return RRAR_OK; +} + +/* ------------------------------------------------------------ the member */ + +static INLINE void emit_literal(rrar_unpack_t *u, uint8_t byte) +{ + u->out[(size_t)u->pos++] = byte; +} + +/* A match that reaches back before the member's start, or to no + * distance at all: what is not there is zeros. */ +static size_t copy_match_edge(uint8_t *out, size_t pos, uint32_t offset, uint32_t length) +{ + uint8_t *dst = out + pos; + + if (offset == 0) + memset(dst, 0, length); + else + { + size_t before = offset - pos; + uint32_t zeros = before < length ? (uint32_t)before : length; + uint32_t i; + + memset(dst, 0, zeros); + /* (pos + i is at least offset from here on) */ + for (i = zeros; i < length; i++) + dst[i] = out[pos + i - offset]; + } + return pos + length; +} + +/* @length bytes from @offset back, to @out at @pos. Past the member's end + * they are not written. The position after them. */ +static INLINE size_t copy_match(uint8_t *out, size_t pos, size_t size, + uint32_t offset, uint32_t length) +{ + uint8_t *dst; + const uint8_t *src; + size_t next; + + if (length > size - pos) + length = (uint32_t)(size - pos); + if (offset == 0 || offset > pos) + return copy_match_edge(out, pos, offset, length); + next = pos + length; + + dst = out + pos; + src = dst - offset; + if (offset >= 8 && size - pos >= (size_t)length + 8) + { + /* eight bytes at a time; the few too many are written over by + * what comes next */ + uint8_t *end = dst + length; + do + { + memcpy(dst, src, 8); + dst += 8; + src += 8; + } while (dst < end); + } + else if (offset >= length) + memcpy(dst, src, length); + else + while (length--) + *dst++ = *src++; + return next; +} + +static void emit_match(rrar_unpack_t *u, uint32_t offset, uint32_t length) +{ + u->pos = copy_match(u->out, (size_t)u->pos, (size_t)u->size, offset, length); +} + +/* What an LZ block's symbols stand for. */ +#define LZ_END_OF_FILE 1 +#define LZ_NEW_TABLE 2 +#define LZ_PAUSE 3 + +/* The symbols of an LZ block, until it is over - new tables, or the end + * of the file - or the member is full. The reading and the writing are + * kept in local variables: a store of a byte through the state's + * pointer would have the compiler read every field of the state again + * after it. Returns LZ_END_OF_FILE, LZ_NEW_TABLE, RRAR_OK (the member is + * full) or an error. */ +static int unpack_lz(rrar_unpack_t *u) +{ + static const uint8_t length_bases[] = + { 0, 1, 2, 3, 4, 5, 6, + 7, 8, 10, 12, 14, 16, 20, + 24, 28, 32, 40, 48, 56, 64, + 80, 96, 112, 128, 160, 192, 224 }; + static const uint8_t length_bits[] = + { 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 2, 2, + 2, 2, 3, 3, 3, 3, 4, + 4, 4, 4, 5, 5, 5, 5 }; + static const uint32_t offset_bases[] = + { 0, 1, 2, 3, 4, 6, + 8, 12, 16, 24, 32, 48, + 64, 96, 128, 192, 256, 384, + 512, 768, 1024, 1536, 2048, 3072, + 4096, 6144, 8192, 12288, 16384, 24576, + 32768, 49152, 65536, 98304, 131072, 196608, + 262144, 327680, 393216, 458752, 524288, 589824, + 655360, 720896, 786432, 851968, 917504, 983040, + 1048576, 1310720, 1572864, 1835008, 2097152, 2359296, + 2621440, 2883584, 3145728, 3407872, 3670016, 3932160 }; + static const uint8_t offset_bits[] = + { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, + 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, + 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18 }; + static const uint8_t short_bases[] = { 0, 4, 8, 16, 32, 64, 128, 192 }; + static const uint8_t short_bits[] = { 2, 2, 3, 4, 5, 6, 6, 6 }; + rrar_bits_t br = u->br; + uint8_t *out = u->out; + size_t pos = (size_t)u->pos; + size_t size = (size_t)u->size; + /* (to the member's end, or to where the watcher is next told) */ + size_t stop = u->watch_at < u->size ? (size_t)u->watch_at : size; + int r = RRAR_OK; + + while (pos < stop) + { + int symbol = huff_decode(&br, &u->main_code); + uint32_t offs, len; + int i; + + if (symbol < 256) + { + if (symbol < 0) + { + r = RRAR_ERROR_DATA; + break; + } + out[pos++] = (uint8_t)symbol; + continue; + } + /* (bytes are made up past the end of the data: an archive that is + * cut short is seen here, or by the caller) */ + if (rrar_br_overrun(&br)) + { + r = RRAR_ERROR_DATA; + break; + } + if (symbol == 256) + { + /* the end of the block: of the file too, or new tables */ + r = rrar_br_get(&br, 1) ? LZ_NEW_TABLE : LZ_END_OF_FILE; + break; + } + if (symbol == 257) + { + u->br = br; + u->pos = pos; + r = filter_read(u); + br = u->br; + if (r != RRAR_OK) + break; + continue; + } + if (symbol == 258) + { + /* the last match again */ + if (u->last_length == 0) + continue; + offs = u->last_offset; + len = u->last_length; + } + else if (symbol <= 262) + { + /* one of the last four distances */ + int idx = symbol - 259; + int len_symbol; + + offs = u->old_offset[idx]; + if ((len_symbol = huff_decode(&br, &u->length_code)) < 0 + || len_symbol >= (int)sizeof(length_bases)) + { + r = RRAR_ERROR_DATA; + break; + } + len = length_bases[len_symbol] + 2; + if (length_bits[len_symbol]) + len += rrar_br_get(&br, length_bits[len_symbol]); + for (i = idx; i > 0; i--) + u->old_offset[i] = u->old_offset[i - 1]; + u->old_offset[0] = offs; + } + else if (symbol <= 270) + { + /* two bytes from close by */ + offs = short_bases[symbol - 263] + 1; + if (short_bits[symbol - 263]) + offs += rrar_br_get(&br, short_bits[symbol - 263]); + len = 2; + for (i = 3; i > 0; i--) + u->old_offset[i] = u->old_offset[i - 1]; + u->old_offset[0] = offs; + } + else + { + int offs_symbol; + + if (symbol - 271 >= (int)sizeof(length_bases)) + { + r = RRAR_ERROR_DATA; + break; + } + len = length_bases[symbol - 271] + 3; + if (length_bits[symbol - 271]) + len += rrar_br_get(&br, length_bits[symbol - 271]); + + if ((offs_symbol = huff_decode(&br, &u->offset_code)) < 0 + || offs_symbol >= (int)(sizeof(offset_bases) / sizeof(offset_bases[0]))) + { + r = RRAR_ERROR_DATA; + break; + } + offs = offset_bases[offs_symbol] + 1; + if (offset_bits[offs_symbol]) + { + if (offs_symbol > 9) + { + /* the low four bits have a code of their own, and repeat */ + if (offset_bits[offs_symbol] > 4) + offs += rrar_br_get(&br, offset_bits[offs_symbol] - 4) << 4; + if (u->low_offset_repeats) + { + u->low_offset_repeats--; + offs += u->last_low_offset; + } + else + { + int low = huff_decode(&br, &u->low_offset_code); + if (low < 0) + { + r = RRAR_ERROR_DATA; + break; + } + if (low == 16) + { + u->low_offset_repeats = 15; + offs += u->last_low_offset; + } + else + { + offs += (uint32_t)low; + u->last_low_offset = (uint32_t)low; + } + } + } + else + offs += rrar_br_get(&br, offset_bits[offs_symbol]); + } + if (offs >= 0x40000) + len++; + if (offs >= 0x2000) + len++; + for (i = 3; i > 0; i--) + u->old_offset[i] = u->old_offset[i - 1]; + u->old_offset[0] = offs; + } + u->last_offset = offs; + u->last_length = len; + pos = copy_match(out, pos, size, offs, len); + } + + u->br = br; + u->pos = pos; + if (r == RRAR_OK && rrar_br_overrun(&br)) + r = RRAR_ERROR_DATA; + /* not the member's end: the caller has the watcher told, and comes back */ + if (r == RRAR_OK && pos < size) + r = LZ_PAUSE; + return r; +} + +/* RAR 2.9: blocks of LZ symbols and blocks of PPMd, with filters among + * them, to the member's size or the end of its data. */ +static int unpack29(rrar_unpack_t *u) +{ + int new_table = 1; + int r; + + while (u->pos < u->size) + { + int symbol, code, i; + uint32_t offs; + + if (unpack_overrun(u)) + return RRAR_ERROR_DATA; + if (u->pos >= u->watch_at && (r = unpack_watch(u)) != RRAR_OK) + return r; + if (new_table) + { + if ((r = unpack_tables(u)) != RRAR_OK) + return r; + new_table = 0; + } + + if (!u->is_ppmd) + { + r = unpack_lz(u); + if (r == LZ_NEW_TABLE) + new_table = 1; + else if (r != LZ_PAUSE) + return r == LZ_END_OF_FILE ? RRAR_OK : r; + continue; + } + + if ((symbol = rrar_ppmd7_decode_symbol(&u->ppmd, &u->range)) < 0) + return RRAR_ERROR_DATA; + if (symbol != u->ppmd_escape) + { + emit_literal(u, (uint8_t)symbol); + continue; + } + /* the escape byte: what comes after says what is meant */ + if ((code = rrar_ppmd7_decode_symbol(&u->ppmd, &u->range)) < 0) + return RRAR_ERROR_DATA; + switch (code) + { + case 0: /* a new block */ + new_table = 1; + break; + case 2: /* the end of the data */ + return RRAR_OK; + case 3: + if ((r = filter_read(u)) != RRAR_OK) + return r; + break; + case 4: /* a match: three bytes of distance, one of length */ + offs = 0; + for (i = 2; i >= 0; i--) + { + if ((code = rrar_ppmd7_decode_symbol(&u->ppmd, &u->range)) < 0) + return RRAR_ERROR_DATA; + offs |= (uint32_t)code << (i * 8); + } + if ((code = rrar_ppmd7_decode_symbol(&u->ppmd, &u->range)) < 0) + return RRAR_ERROR_DATA; + emit_match(u, offs + 2, (uint32_t)code + 32); + break; + case 5: /* the last byte again */ + if ((code = rrar_ppmd7_decode_symbol(&u->ppmd, &u->range)) < 0) + return RRAR_ERROR_DATA; + emit_match(u, 1, (uint32_t)code + 4); + break; + default: /* the escape byte itself */ + emit_literal(u, (uint8_t)symbol); + break; + } + } + return RRAR_OK; +} + +/* ----------------------------------------------------------------- RAR 5 */ + +/* A length, from its code: the first eight are themselves, the rest a + * few bits more each. */ +static INLINE uint32_t length5(rrar_bits_t *br, unsigned slot) +{ + uint32_t length = 2; + unsigned bits = 0; + + if (slot < 8) + length += slot; + else + { + bits = slot / 4 - 1; + length += (uint32_t)(4 | (slot & 3)) << bits; + } + if (bits) + length += rrar_br_get(br, (int)bits); + return length; +} + +/* A number in a filter's description: one to four bytes, the low one + * first. */ +static uint32_t filter5_number(rrar_bits_t *br) +{ + unsigned bytes = rrar_br_get(br, 2) + 1; + uint32_t v = 0; + unsigned i; + + for (i = 0; i < bytes; i++) + v += rrar_br_get(br, 8) << (i * 8); + return v; +} + +/* The code tables a block starts with: the lengths of a code of 20, + * which the lengths of the four others are written in. Unlike RAR + * 2.9's they are whole lengths, not differences from the last. */ +static int unpack5_tables(rrar_unpack_t *u, rrar_bits_t *br) +{ + uint8_t pre_lengths[PRECODE_SIZE]; + rrar_huff_t *pre; + int i, r = RRAR_ERROR_DATA; + + for (i = 0; i < PRECODE_SIZE;) + { + unsigned v = rrar_br_get(br, 4); + if (v == 15) + { + unsigned zeros = rrar_br_get(br, 4); + if (!zeros) + pre_lengths[i++] = 15; + else + { + unsigned j; + for (j = 0; j < zeros + 2 && i < PRECODE_SIZE; j++) + pre_lengths[i++] = 0; + } + } + else + pre_lengths[i++] = (uint8_t)v; + } + + if (!(pre = (rrar_huff_t *)malloc(sizeof(*pre)))) + return RRAR_ERROR_MEM; + if (!huff_build(pre, pre_lengths, PRECODE_SIZE)) + goto done; + + for (i = 0; i < HUFFMAN_TABLE5_SIZE;) + { + int val = huff_decode(br, pre); + int n, j; + + if (val < 0 || rrar_br_overrun(br)) + goto done; + if (val < 16) + u->lengths[i++] = (uint8_t)val; + else if (val < 18) + { + if (i == 0) + goto done; + n = val == 16 ? (int)rrar_br_get(br, 3) + 3 : (int)rrar_br_get(br, 7) + 11; + for (j = 0; j < n && i < HUFFMAN_TABLE5_SIZE; j++, i++) + u->lengths[i] = u->lengths[i - 1]; + } + else + { + n = val == 18 ? (int)rrar_br_get(br, 3) + 3 : (int)rrar_br_get(br, 7) + 11; + for (j = 0; j < n && i < HUFFMAN_TABLE5_SIZE; j++) + u->lengths[i++] = 0; + } + } + + if ( huff_build(&u->main_code, u->lengths, MAINCODE5_SIZE) + && huff_build(&u->offset_code, u->lengths + MAINCODE5_SIZE, OFFSETCODE5_SIZE) + && huff_build(&u->low_offset_code, + u->lengths + MAINCODE5_SIZE + OFFSETCODE5_SIZE, LOWOFFSETCODE5_SIZE) + && huff_build(&u->length_code, + u->lengths + MAINCODE5_SIZE + OFFSETCODE5_SIZE + LOWOFFSETCODE5_SIZE, + LENGTHCODE5_SIZE)) + r = RRAR_OK; + +done: + free(pre); + return r; +} + +/* A filter in the data: where its block starts from here, how long it + * is, and which of four it is. */ +static int unpack5_filter(rrar_unpack_t *u, rrar_bits_t *br, size_t pos) +{ + uint32_t start = filter5_number(br); + uint32_t length = filter5_number(br); + unsigned type = rrar_br_get(br, 3); + uint32_t channels = type == 0 ? rrar_br_get(br, 5) + 1 : 0; + uint64_t block = (uint64_t)pos + start; + rrar_filter_t *f; + + if (rrar_br_overrun(br) || length < 4 || length > FILTER5_BLOCK_MAX) + return RRAR_ERROR_DATA; + if (type > 3) + return RRAR_ERROR_UNSUPPORTED; + /* within the member, and after the last filter's block */ + if (block > u->size || length > u->size - block) + return RRAR_ERROR_DATA; + if (u->num_filters) + { + const rrar_filter_t *last = &u->filters[u->num_filters - 1]; + if (block < last->start + last->length) + return RRAR_ERROR_DATA; + } + + if (u->num_filters == u->cap_filters) + { + uint32_t cap = u->cap_filters ? u->cap_filters * 2 : 64; + rrar_filter_t *p = (rrar_filter_t *)realloc(u->filters, cap * sizeof(*p)); + if (!p) + return RRAR_ERROR_MEM; + u->filters = p; + u->cap_filters = cap; + } + f = &u->filters[u->num_filters++]; + f->start = block; + f->length = length; + f->kind = FILTER5_DELTA + type; + f->r0 = channels; + f->r1 = 0; + f->r4 = length; + return RRAR_OK; +} + +/* RAR 5: blocks, each with a header of its own length in bytes and bits, + * of the symbols of one LZ coder - there is no PPMd, and the filters are + * four fixed ones. */ +static int unpack50(rrar_unpack_t *u) +{ + const uint8_t *p = u->br.p; + const uint8_t *end = u->br.end; + uint8_t *out = u->out; + size_t pos = 0; + size_t size = (size_t)u->size; + int have_tables = 0; + int r; + + while (pos < size) + { + rrar_bits_t br; + const uint8_t *data; + unsigned flags, count, i; + uint32_t block_size = 0; + uint64_t end_bits; + uint8_t sum; + int block_done = 0; + + if (pos >= u->watch_at) + { + u->pos = pos; + if ((r = unpack_watch(u)) != RRAR_OK) + return r; + } + /* the block's header: flags, a checksum, and its size */ + if (end - p < 3) + return RRAR_ERROR_DATA; + flags = p[0]; + count = ((flags >> 3) & 7) + 1; + if (count > 3 || (size_t)(end - p) < 2 + count) + return RRAR_ERROR_DATA; + sum = (uint8_t)(0x5a ^ flags); + for (i = 0; i < count; i++) + { + block_size |= (uint32_t)p[2 + i] << (i * 8); + sum ^= p[2 + i]; + } + if (sum != p[1]) + return RRAR_ERROR_DATA; + data = p + 2 + count; + if (block_size > (size_t)(end - data)) + return RRAR_ERROR_DATA; + /* the last byte's bits are not all the block's */ + end_bits = block_size ? (uint64_t)(block_size - 1) * 8 + (flags & 7) + 1 : 0; + + memset(&br, 0, sizeof(br)); + br.p = data; + br.end = data + block_size; + + if (flags & 0x80) + { + if ((r = unpack5_tables(u, &br)) != RRAR_OK) + return r; + have_tables = 1; + } + else if (!have_tables) + return RRAR_ERROR_DATA; + + while (pos < size) + { + int symbol; + uint32_t len; + uint64_t dist; + + /* (the bits read ahead are at most 8 bytes: further than that + * from the block's end, it has not been reached) */ + if (br.end - br.p <= 8 + && (uint64_t)(br.p - data) * 8 + (uint64_t)br.past * 8 - (uint64_t)br.bits >= end_bits) + { + block_done = 1; + break; + } + + symbol = huff_decode(&br, &u->main_code); + if (symbol < 256) + { + if (symbol < 0) + return RRAR_ERROR_DATA; + out[pos++] = (uint8_t)symbol; + continue; + } + if (symbol >= 262) + { + int slot; + unsigned bits; + + len = length5(&br, (unsigned)symbol - 262); + if ((slot = huff_decode(&br, &u->offset_code)) < 0) + return RRAR_ERROR_DATA; + dist = 1; + if (slot < 4) + { + bits = 0; + dist += (unsigned)slot; + } + else + { + bits = (unsigned)slot / 2 - 1; + dist += (uint64_t)(2 | (slot & 1)) << bits; + } + if (bits) + { + if (bits >= 4) + { + int low; + /* the low four bits have a code of their own */ + if (bits > 4) + dist += (uint64_t)rrar_br_get(&br, (int)bits - 4) << 4; + if ((low = huff_decode(&br, &u->low_offset_code)) < 0) + return RRAR_ERROR_DATA; + dist += (unsigned)low; + } + else + dist += rrar_br_get(&br, (int)bits); + } + if (dist > 0x100) + { + len++; + if (dist > 0x2000) + { + len++; + if (dist > 0x40000) + len++; + } + } + /* (a distance of 4 GB or more is from before the member) */ + if (dist > 0xffffffffu) + dist = 0xffffffffu; + u->old_offset[3] = u->old_offset[2]; + u->old_offset[2] = u->old_offset[1]; + u->old_offset[1] = u->old_offset[0]; + u->old_offset[0] = (uint32_t)dist; + u->last_length = len; + } + else if (symbol == 256) + { + if ((r = unpack5_filter(u, &br, pos)) != RRAR_OK) + return r; + continue; + } + else if (symbol == 257) + { + /* the last match again */ + if (!u->last_length) + continue; + len = u->last_length; + dist = u->old_offset[0]; + } + else + { + /* one of the last four distances, which becomes the first */ + int idx = symbol - 258; + int slot, i2; + + dist = u->old_offset[idx]; + for (i2 = idx; i2 > 0; i2--) + u->old_offset[i2] = u->old_offset[i2 - 1]; + u->old_offset[0] = (uint32_t)dist; + if ((slot = huff_decode(&br, &u->length_code)) < 0) + return RRAR_ERROR_DATA; + len = length5(&br, (unsigned)slot); + u->last_length = len; + } + if (rrar_br_overrun(&br)) + return RRAR_ERROR_DATA; + pos = copy_match(out, pos, size, (uint32_t)dist, len); + } + + if (rrar_br_overrun(&br)) + return RRAR_ERROR_DATA; + if (!block_done) + break; /* the member is full */ + if (flags & 0x40) + break; /* the last block */ + p = data + block_size; + } + u->pos = pos; + return RRAR_OK; +} + +static int unpack_member(const uint8_t *packed, size_t packed_len, + uint8_t *out, size_t out_len, int rar5, uint64_t window, + const rrar_watch_t *watch) +{ + rrar_unpack_t *u = (rrar_unpack_t *)calloc(1, sizeof(*u)); + int r; + + if (!u) + return RRAR_ERROR_MEM; + u->br.p = packed; + u->br.end = packed + packed_len; + u->out = out; + u->size = out_len; + u->ppmd_escape = 2; + u->watch = watch; + u->watch_at = watch ? RRAR_WATCH_STEP : out_len; + u->window = window; + rrar_ppmd7_construct(&u->ppmd); + + r = rar5 ? unpack50(u) : unpack29(u); + if (r == RRAR_OK && u->pos != u->size) + r = RRAR_ERROR_DATA; + if (r == RRAR_OK) + r = filters_run(u); + + rrar_ppmd7_free(&u->ppmd); + free(u->programs); + free(u->filters); + free(u->work); + free(u); + return r; +} + +/* -------------------------------------------------------------- headers */ + +/* A name with the Unicode flag: the name in the system's code page, a + * zero, and then its UTF-16 form packed against it. To UTF-8. Returns + * the bytes written, without the terminator, or 0 if they do not fit. */ +static size_t name_unicode(const uint8_t *name, size_t name_len, size_t ascii_len, + char *out, size_t out_cap) +{ + const uint8_t *p = name + ascii_len + 1; + size_t end = name_len - ascii_len - 1; + size_t off = 0, n = 0, chars = 0; + unsigned high, flag_byte = 0, flag_bits = 0; + uint32_t pending = 0; /* a leading surrogate waiting for its pair */ + + if (end < 1) + return 0; + high = p[off++]; + + /* (it has as many characters as the code-page name, at most) */ + while ((off < end && chars < ascii_len) || pending) + { + uint32_t c[130]; + unsigned count = 0, k; + + if (off < end && chars < ascii_len) + { + if (!flag_bits) + { + flag_byte = p[off++]; + flag_bits = 8; + if (off >= end) + break; + } + flag_bits -= 2; + switch ((flag_byte >> flag_bits) & 3) + { + case 0: + c[count++] = p[off++]; + break; + case 1: + c[count++] = p[off++] | (high << 8); + break; + case 2: + if (off + 1 >= end) + return 0; + c[count++] = p[off] | ((uint32_t)p[off + 1] << 8); + off += 2; + break; + default: + { + /* a run taken from the code-page name, shifted */ + unsigned length = p[off++]; + unsigned extra = 0, hi = 0; + if (length & 0x80) + { + if (off >= end) + return 0; + extra = p[off++]; + hi = high; + } + length = (length & 0x7f) + 2; + while (length-- && chars + count < ascii_len) + { + c[count] = ((name[chars + count] + extra) & 0xff) | (hi << 8); + count++; + } + break; + } + } + } + else + { + /* a lone surrogate at the end */ + pending = 0; + break; + } + + for (k = 0; k < count; k++) + { + uint32_t cp = c[k]; + chars++; + if (pending) + { + if (cp >= 0xdc00 && cp < 0xe000) + cp = 0x10000 + ((pending - 0xd800) << 10) + (cp - 0xdc00); + pending = 0; + } + else if (cp >= 0xd800 && cp < 0xdc00) + { + pending = cp; + continue; + } + if (n + 4 >= out_cap) + return 0; + if (cp < 0x80) + out[n++] = (char)cp; + else if (cp < 0x800) + { + out[n++] = (char)(0xc0 | (cp >> 6)); + out[n++] = (char)(0x80 | (cp & 0x3f)); + } + else if (cp < 0x10000) + { + out[n++] = (char)(0xe0 | (cp >> 12)); + out[n++] = (char)(0x80 | ((cp >> 6) & 0x3f)); + out[n++] = (char)(0x80 | (cp & 0x3f)); + } + else + { + out[n++] = (char)(0xf0 | (cp >> 18)); + out[n++] = (char)(0x80 | ((cp >> 12) & 0x3f)); + out[n++] = (char)(0x80 | ((cp >> 6) & 0x3f)); + out[n++] = (char)(0x80 | (cp & 0x3f)); + } + } + } + return n; +} + +/* Takes the entry's name: as UTF-8, with '/' between its parts. */ +static int add_name(rrar_archive_t *a, const uint8_t *name, size_t name_len, + int unicode, size_t *at) +{ + /* (a UTF-16 unit is at most three bytes of UTF-8) */ + size_t need = name_len * 3 + 8; + size_t n = 0, i; + char *dst; + + if (a->names_len + need > a->names_cap) + { + size_t cap = a->names_cap ? a->names_cap * 2 : 1024; + char *p; + while (cap < a->names_len + need) + cap *= 2; + if (!(p = (char *)realloc(a->names, cap))) + return RRAR_ERROR_MEM; + a->names = p; + a->names_cap = cap; + } + dst = a->names + a->names_len; + + if (unicode) + { + size_t ascii_len = 0; + while (ascii_len < name_len && name[ascii_len]) + ascii_len++; + if (ascii_len < name_len) + n = name_unicode(name, name_len, ascii_len, dst, need); + if (!n) + { + /* no second form (the name is UTF-8 already), or a bad one */ + memcpy(dst, name, ascii_len); + n = ascii_len; + } + } + else + { + while (n < name_len && name[n]) + { + dst[n] = (char)name[n]; + n++; + } + } + for (i = 0; i < n; i++) + if (dst[i] == '\\') + dst[i] = '/'; + dst[n] = '\0'; + *at = a->names_len; + a->names_len += n + 1; + return RRAR_OK; +} + +/* @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. */ +static int rrar_fetch(const rrar_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 RRAR_ERROR_DATA; + return RRAR_OK; +} + +/* The @n bytes of a header at @pos, to look at: where they lie when the + * archive is in memory, read into a buffer of the archive's when it is + * not. Good until the next call. */ +static int rrar_head(rrar_archive_t *a, size_t pos, size_t n, + const uint8_t **h) +{ + if (a->data) + { + *h = a->data + pos; + return RRAR_OK; + } + if (n > a->head_cap) + { + size_t cap = n < 4096 ? 4096 : n; + uint8_t *p = (uint8_t *)realloc(a->head, cap); + if (!p) + return RRAR_ERROR_MEM; + a->head = p; + a->head_cap = cap; + } + *h = a->head; + return rrar_fetch(a, pos, a->head, n); +} + +static int parse_headers(rrar_archive_t *a) +{ + size_t len = a->len; + size_t pos = RRAR_SIGNATURE_SIZE; + size_t *name_at = NULL; + int solid = 0, seen_main = 0; + int r = RRAR_OK; + uint32_t i; + + while (pos + 7 <= len) + { + /* The header at pos: its first seven bytes say how long it is, + * and then all of it is looked at. */ + const uint8_t *h; + uint32_t crc; + unsigned type, flags; + size_t size; + uint64_t add = 0; + + if ((r = rrar_head(a, pos, 7, &h)) != RRAR_OK) + break; + crc = rd16(h); + type = h[2]; + flags = rd16(h + 3); + size = rd16(h + 5); + + if (size < 7 || size > len - pos) + { + r = RRAR_ERROR_DATA; + break; + } + /* (a file's header is checked whole; the others only as far as + * their fixed part, which differs by kind: the kinds read here are + * checked whole) */ + if (type == RAR_HEAD_END) + break; + if ((r = rrar_head(a, pos, size, &h)) != RRAR_OK) + break; + + if (type == RAR_HEAD_MAIN) + { + if (size < 13 + || (encoding_crc32(0, h + 2, 11) & 0xffff) != crc) + { + r = RRAR_ERROR_DATA; + break; + } + if (flags & (RAR_MAIN_VOLUME | RAR_MAIN_PASSWORD)) + { + r = RRAR_ERROR_UNSUPPORTED; + break; + } + solid = (flags & RAR_MAIN_SOLID) != 0; + seen_main = 1; + } + else if (type == RAR_HEAD_FILE) + { + rrar_entry_t *e; + uint64_t packed, unpacked; + size_t fixed = 32, name_len; + int is_dir; + + if (size < fixed + || (encoding_crc32(0, h + 2, size - 2) & 0xffff) != crc) + { + r = RRAR_ERROR_DATA; + break; + } + packed = rd32(h + 7); + unpacked = rd32(h + 11); + name_len = rd16(h + 26); + if (flags & RAR_FILE_LARGE) + { + if (size < fixed + 8) + { + r = RRAR_ERROR_DATA; + break; + } + packed |= (uint64_t)rd32(h + 32) << 32; + unpacked |= (uint64_t)rd32(h + 36) << 32; + fixed += 8; + } + if (name_len > size - fixed || packed > len - pos - size) + { + r = RRAR_ERROR_DATA; + break; + } + add = packed; + + if (a->num_entries == a->cap_entries) + { + uint32_t cap = a->cap_entries ? a->cap_entries * 2 : 16; + rrar_entry_t *p = (rrar_entry_t *)realloc(a->entries, cap * sizeof(*p)); + size_t *q = p ? (size_t *)realloc(name_at, cap * sizeof(*q)) : NULL; + if (p) + a->entries = p; + if (q) + name_at = q; + if (!p || !q) + { + r = RRAR_ERROR_MEM; + break; + } + a->cap_entries = cap; + } + if ((r = add_name(a, h + fixed, name_len, + (flags & RAR_FILE_UNICODE) != 0, &name_at[a->num_entries])) != RRAR_OK) + break; + + is_dir = (flags & RAR_FILE_DIRECTORY) == RAR_FILE_DIRECTORY; + e = &a->entries[a->num_entries++]; + memset(e, 0, sizeof(*e)); + e->size = unpacked; + e->packed_size = packed; + e->data_offset = pos + size; + e->crc = rd32(h + 16); + e->has_crc = 1; + e->version = h[24]; + e->method = h[25]; + e->is_dir = (uint8_t)is_dir; + e->window = 0x400000; /* RAR 4's largest dictionary */ + e->supported = !is_dir + && !(flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER | RAR_FILE_PASSWORD)) + && unpacked <= (uint64_t)((size_t)-1) / 2 + && (e->method == RAR_METHOD_STORED + ? packed == unpacked + : (!solid && !(flags & RAR_FILE_SOLID) + && e->method >= 0x31 && e->method <= 0x35 + && (e->version == 29 || e->version == 36))); + } + else if (flags & RAR_HEAD_ADD_SIZE) + { + /* some other block with data after it */ + if (size < 11) + { + r = RRAR_ERROR_DATA; + break; + } + add = rd32(h + 7); + if (add > len - pos - size) + { + r = RRAR_ERROR_DATA; + break; + } + } + pos += size + (size_t)add; + } + + if (r == RRAR_OK && !seen_main) + r = RRAR_ERROR_DATA; + for (i = 0; r == RRAR_OK && i < a->num_entries; i++) + a->entries[i].name = a->names + name_at[i]; + free(name_at); + return r; +} + +/* A number as RAR 5's headers write them: seven bits to a byte, the low + * ones first, the top bit saying there is more. */ +/* (@d holds the bytes from @base on; @pos and @end count from the start of + * the archive) */ +static int rd_vint(const uint8_t *d, size_t base, size_t end, size_t *pos, + uint64_t *v) +{ + uint64_t r = 0; + unsigned shift; + + for (shift = 0; shift < 70; shift += 7) + { + uint8_t b; + if (*pos >= end) + return 0; + b = d[*pos - base]; + (*pos)++; + if (shift < 64) + r |= (uint64_t)(b & 0x7f) << shift; + if (!(b & 0x80)) + { + *v = r; + return 1; + } + } + return 0; +} + +/* The RAR 5 container: every header a CRC-32, its size, its kind and + * flags, and what the kind has; a file's data after its header. */ +static int parse_headers5(rrar_archive_t *a) +{ + size_t len = a->len; + size_t pos = 8; + size_t *name_at = NULL; + int seen_main = 0, ended = 0; + int r = RRAR_OK; + uint32_t i; + + while (!ended && pos + 4 < len) + { + /* The header at pos: its start says how long it is (the size is + * within ten bytes of the checksum), and then all of it is looked + * at. h holds it from pos on. */ + const uint8_t *h; + size_t q = pos + 4, head_end; + size_t pre = len - pos < 16 ? len - pos : 16; + uint64_t hsize, type, flags, extra = 0, data_size = 0; + + if ((r = rrar_head(a, pos, pre, &h)) != RRAR_OK) + break; + if ( !rd_vint(h, pos, pos + pre, &q, &hsize) + || hsize > 0x200000 || hsize > len - q) + { + r = RRAR_ERROR_DATA; + break; + } + head_end = q + (size_t)hsize; + if ((r = rrar_head(a, pos, head_end - pos, &h)) != RRAR_OK) + break; + if (encoding_crc32(0, h + 4, head_end - (pos + 4)) != rd32(h)) + { + r = RRAR_ERROR_DATA; + break; + } + if ( !rd_vint(h, pos, head_end, &q, &type) + || !rd_vint(h, pos, head_end, &q, &flags) + || ((flags & 1) && !rd_vint(h, pos, head_end, &q, &extra)) + || ((flags & 2) && !rd_vint(h, pos, head_end, &q, &data_size)) + || extra > head_end - q + || data_size > len - head_end) + { + r = RRAR_ERROR_DATA; + break; + } + + switch (type) + { + case 1: /* the archive */ + { + uint64_t aflags; + if (!rd_vint(h, pos, head_end, &q, &aflags)) + r = RRAR_ERROR_DATA; + else if (aflags & 1) + r = RRAR_ERROR_UNSUPPORTED; /* a volume */ + seen_main = 1; + break; + } + case 4: /* the headers after this are encrypted */ + r = RRAR_ERROR_UNSUPPORTED; + break; + case 5: + ended = 1; + break; + case 2: /* a file */ + { + rrar_entry_t *e; + uint64_t fflags, unpacked, attr, info, host, name_len; + uint32_t crc = 0; + size_t extra_at = head_end - (size_t)extra; + size_t x; + unsigned method, version; + int encrypted = 0, is_dir; + + if ( !rd_vint(h, pos, extra_at, &q, &fflags) + || !rd_vint(h, pos, extra_at, &q, &unpacked) + || !rd_vint(h, pos, extra_at, &q, &attr)) + { + r = RRAR_ERROR_DATA; + break; + } + if (fflags & 2) + { + if (extra_at - q < 4) + { + r = RRAR_ERROR_DATA; + break; + } + q += 4; /* modification time */ + } + if (fflags & 4) + { + if (extra_at - q < 4) + { + r = RRAR_ERROR_DATA; + break; + } + crc = rd32(h + (q - pos)); + q += 4; + } + if ( !rd_vint(h, pos, extra_at, &q, &info) + || !rd_vint(h, pos, extra_at, &q, &host) + || !rd_vint(h, pos, extra_at, &q, &name_len) + || name_len > extra_at - q) + { + r = RRAR_ERROR_DATA; + break; + } + + /* the records after the name: one of them says the data is + * encrypted */ + for (x = extra_at; x < head_end;) + { + uint64_t rsize, rtype; + size_t y; + if (!rd_vint(h, pos, head_end, &x, &rsize) || rsize > head_end - x) + break; + y = x; + if (rd_vint(h, pos, x + (size_t)rsize, &y, &rtype) && rtype == 1) + encrypted = 1; + x += (size_t)rsize; + } + + if (a->num_entries == a->cap_entries) + { + uint32_t cap = a->cap_entries ? a->cap_entries * 2 : 16; + rrar_entry_t *p = (rrar_entry_t *)realloc(a->entries, cap * sizeof(*p)); + size_t *n = p ? (size_t *)realloc(name_at, cap * sizeof(*n)) : NULL; + if (p) + a->entries = p; + if (n) + name_at = n; + if (!p || !n) + { + r = RRAR_ERROR_MEM; + break; + } + a->cap_entries = cap; + } + /* (the name is UTF-8 as it is) */ + if ((r = add_name(a, h + (q - pos), (size_t)name_len, 0, &name_at[a->num_entries])) != RRAR_OK) + break; + + version = (unsigned)(info & 0x3f); + method = (unsigned)((info >> 7) & 7); + is_dir = (fflags & 1) != 0; + e = &a->entries[a->num_entries++]; + memset(e, 0, sizeof(*e)); + e->size = unpacked; + e->packed_size = data_size; + e->data_offset = head_end; + e->crc = crc; + e->has_crc = (fflags & 4) != 0; + e->version = 50; + e->method = (uint8_t)(RAR_METHOD_STORED + method); + e->is_dir = (uint8_t)is_dir; + /* the dictionary: 128 KiB, doubled as many times as it says */ + e->window = (uint64_t)0x20000 << ((info >> 10) & 0xf); + e->supported = !is_dir + && !encrypted + && !(flags & (0x08 | 0x10)) /* split between volumes */ + && !(fflags & 8) /* of a size not known */ + && unpacked <= (uint64_t)((size_t)-1) / 2 + && (method == 0 + ? data_size == unpacked + : (version == 0 && !(info & 0x40) && method <= 5)); + (void)attr; + (void)host; + break; + } + default: /* a service header, or a kind from later: with its data, skipped */ + break; + } + if (r != RRAR_OK) + break; + pos = head_end + (size_t)data_size; + } + + if (r == RRAR_OK && !seen_main) + r = RRAR_ERROR_DATA; + for (i = 0; r == RRAR_OK && i < a->num_entries; i++) + a->entries[i].name = a->names + name_at[i]; + free(name_at); + return r; +} + +/* ------------------------------------------------------------------ API */ + +/* The archive in memory (@data), or read through @read_cb. */ +static int rrar_open(rrar_archive_t **out, const uint8_t *data, size_t len, + rrar_read_t read_cb, void *ud) +{ + rrar_archive_t *a; + uint8_t sig[8]; + int r; + + if (!out) + return RRAR_ERROR_PARAM; + *out = NULL; + if ((!data && !read_cb) || len < RRAR_SIGNATURE_SIZE) + return RRAR_ERROR_PARAM; + memset(sig, 0, sizeof(sig)); + if (data) + memcpy(sig, data, len < 8 ? len : 8); + else if (read_cb(ud, 0, sig, len < 8 ? len : 8) != (int64_t)(len < 8 ? len : 8)) + return RRAR_ERROR_DATA; + if (memcmp(sig, "Rar!\x1a\x07", 6)) + return RRAR_ERROR_DATA; + /* the seventh byte tells the two containers apart */ + if (sig[6] > 1 || (sig[6] == 1 && (len < 8 || sig[7] != 0))) + return RRAR_ERROR_UNSUPPORTED; + + if (!(a = (rrar_archive_t *)calloc(1, sizeof(*a)))) + return RRAR_ERROR_MEM; + a->data = data; + a->len = len; + a->read_cb = read_cb; + a->read_ud = ud; + r = sig[6] ? parse_headers5(a) : parse_headers(a); + /* (the buffer the headers were read into has done its work) */ + free(a->head); + a->head = NULL; + a->head_cap = 0; + if (r != RRAR_OK) + { + rrar_archive_close(a); + return r; + } + *out = a; + return RRAR_OK; +} + +int rrar_archive_open(rrar_archive_t **out, const uint8_t *data, size_t len) +{ + if (out) + *out = NULL; + if (!data) + return RRAR_ERROR_PARAM; + return rrar_open(out, data, len, NULL, NULL); +} + +int rrar_archive_open_read(rrar_archive_t **out, uint64_t len, + rrar_read_t read_cb, void *ud) +{ + if (out) + *out = NULL; + if (!read_cb || len > (uint64_t)((size_t)-1)) + return RRAR_ERROR_PARAM; + return rrar_open(out, NULL, (size_t)len, read_cb, ud); +} + +void rrar_archive_close(rrar_archive_t *a) +{ + if (!a) + return; + free(a->entries); + free(a->names); + free(a->head); + free(a); +} + +uint32_t rrar_archive_num_entries(const rrar_archive_t *a) +{ + return a ? a->num_entries : 0; +} + +const rrar_entry_t *rrar_archive_entry(const rrar_archive_t *a, uint32_t index) +{ + if (!a || index >= a->num_entries) + return NULL; + return &a->entries[index]; +} + +/* Member @e decoded to @buf, which has room for it. */ +static int rrar_extract_into(rrar_archive_t *a, const rrar_entry_t *e, + uint8_t *buf, const rrar_watch_t *watch) +{ + size_t size = (size_t)e->size; + int r = RRAR_OK; + + if (e->method == RAR_METHOD_STORED) + /* (straight to where it goes, from memory or from the file) */ + r = rrar_fetch(a, e->data_offset, buf, size); + else if (size && a->data) + r = unpack_member(a->data + (size_t)e->data_offset, + (size_t)e->packed_size, buf, size, e->version == 50, + e->window, watch); + else if (size) + { + /* An archive that is read: the member's packed bytes are read in + * for as long as it takes to decode them. */ + uint8_t *in = (uint8_t *)malloc((size_t)e->packed_size ? (size_t)e->packed_size : 1); + + if (!in) + r = RRAR_ERROR_MEM; + else if ((r = rrar_fetch(a, e->data_offset, in, (size_t)e->packed_size)) == RRAR_OK) + r = unpack_member(in, (size_t)e->packed_size, buf, size, + e->version == 50, e->window, watch); + free(in); + } + + if (r == RRAR_OK && e->has_crc && encoding_crc32(0, buf, size) != e->crc) + r = RRAR_ERROR_CRC; + /* all of it, now that it is known to be right */ + if (r == RRAR_OK && watch) + watch->progress(watch->ud, size); + return r; +} + +int rrar_archive_extract(rrar_archive_t *a, uint32_t index, + uint8_t **out, size_t *out_len) +{ + const rrar_entry_t *e; + uint8_t *buf; + size_t size; + int r; + + if (out) + *out = NULL; + if (!a || !out || !out_len || index >= a->num_entries) + return RRAR_ERROR_PARAM; + e = &a->entries[index]; + if (!e->supported) + return RRAR_ERROR_UNSUPPORTED; + + size = (size_t)e->size; + if (!(buf = (uint8_t *)malloc(size ? size : 1))) + return RRAR_ERROR_MEM; + if ((r = rrar_extract_into(a, e, buf, NULL)) != RRAR_OK) + { + free(buf); + return r; + } + *out = buf; + *out_len = size; + return RRAR_OK; +} + +int rrar_archive_extract_to(rrar_archive_t *a, uint32_t index, + uint8_t *dst, size_t dst_size, const rrar_watch_t *watch) +{ + const rrar_entry_t *e; + + if (!a || !dst || index >= a->num_entries || (watch && !watch->progress)) + return RRAR_ERROR_PARAM; + e = &a->entries[index]; + if (!e->supported) + return RRAR_ERROR_UNSUPPORTED; + if (e->size > (uint64_t)dst_size) + return RRAR_ERROR_PARAM; + return rrar_extract_into(a, e, dst, watch); +} diff --git a/formats/rar/rrar_ppmd7.c b/formats/rar/rrar_ppmd7.c new file mode 100644 index 00000000..415bf3fa --- /dev/null +++ b/formats/rar/rrar_ppmd7.c @@ -0,0 +1,1043 @@ +/* Copyright (C) 2010-2026 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (rrar_ppmd7.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. + */ + +/* PPMd var.H decoding for RAR 2.9; see rrar_ppmd7.h. */ + +#include +#include + +#include + +#include "rrar_ppmd7.h" + +#define PPMD_UNIT_SIZE 12 +#define PPMD_MAX_FREQ 124 +#define PPMD_MAX_ORDER 64 +#define PPMD_INT_BITS 7 +#define PPMD_PERIOD_BITS 7 +#define PPMD_BIN_SCALE (1u << (PPMD_INT_BITS + PPMD_PERIOD_BITS)) +#define PPMD_NUM_INDEXES RRAR_PPMD7_NUM_INDEXES + +#if defined(__GNUC__) +#define PPMD_NOINLINE __attribute__((noinline)) +#elif defined(_MSC_VER) && _MSC_VER >= 1300 +#define PPMD_NOINLINE __declspec(noinline) +#else +#define PPMD_NOINLINE +#endif + +/* RAR's range coder: renormalise while the top byte is settled, or while + * the range has fallen under the bottom, in which case it is cut to what + * is left before the next multiple of it. */ +#define PPMD_RC_TOP (1u << 24) +#define PPMD_RC_BOTTOM (1u << 15) + +/* A symbol and how often it was seen in a context; the context it leads + * to, or the text after it, split in two halves to keep the state at six + * bytes on a two-byte boundary. */ +typedef struct rrar_ppmd7_state +{ + uint8_t symbol; + uint8_t freq; + uint16_t successor_lo; + uint16_t successor_hi; +} ppmd_state_t; + +/* A context of one unit: with more than one symbol, the sum of their + * counts and its block of states; with one, that state in their place. + * The halves of the link keep the union on a two-byte boundary, where + * the one state's fields fall. */ +typedef struct rrar_ppmd7_context +{ + uint16_t num_stats; + union + { + struct + { + uint16_t summ_freq; + uint16_t stats_lo; + uint16_t stats_hi; + } m; + ppmd_state_t one; + } u; + uint32_t suffix; +} ppmd_context_t; + +/* A free block while blocks are glued: stamp sits where a context keeps + * num_stats and a state its symbol and freq, so a block in use never + * reads as free (0). */ +typedef struct ppmd_node +{ + uint16_t stamp; + uint16_t nu; + uint32_t next; + uint32_t prev; +} ppmd_node_t; + +#define PPMD_PTR(p, ref) ((void *)((p)->base + (ref))) +#define PPMD_CTX(p, ref) ((ppmd_context_t *)((p)->base + (ref))) +#define PPMD_NODE(p, ref) ((ppmd_node_t *)((p)->base + (ref))) +#define PPMD_STATS_REF(ctx) ((uint32_t)(ctx)->u.m.stats_lo | ((uint32_t)(ctx)->u.m.stats_hi << 16)) +#define PPMD_STATS(p, ctx) ((ppmd_state_t *)((p)->base + PPMD_STATS_REF(ctx))) +#define PPMD_SUFFIX(p, ctx) PPMD_CTX(p, (ctx)->suffix) +#define PPMD_REF(p, ptr) ((uint32_t)((uint8_t *)(ptr) - (p)->base)) +#define PPMD_ONE_STATE(ctx) (&(ctx)->u.one) +#define PPMD_SUCCESSOR(s) ((uint32_t)(s)->successor_lo | ((uint32_t)(s)->successor_hi << 16)) +#define PPMD_U2B(nu) ((uint32_t)(nu) * PPMD_UNIT_SIZE) +#define PPMD_U2I(p, nu) ((p)->units2indx[(nu) - 1]) +#define PPMD_I2U(p, indx) ((p)->indx2units[indx]) + +#define PPMD_SET_SUCCESSOR(s, v) \ + do \ + { \ + ppmd_state_t *st_ = (s); \ + uint32_t succ_ = (v); \ + st_->successor_lo = (uint16_t)succ_; \ + st_->successor_hi = (uint16_t)(succ_ >> 16); \ + } while (0) + +#define PPMD_SET_STATS(ctx, v) \ + do \ + { \ + ppmd_context_t *ctx_ = (ctx); \ + uint32_t ref_ = (v); \ + ctx_->u.m.stats_lo = (uint16_t)ref_; \ + ctx_->u.m.stats_hi = (uint16_t)(ref_ >> 16); \ + } while (0) + +/* The mean a binary context's probability moves by. */ +#define PPMD_GET_MEAN(prob) (((prob) + (1 << (PPMD_PERIOD_BITS - 2))) >> PPMD_PERIOD_BITS) + +static const uint16_t ppmd_init_bin_esc[8] = { + 0x3CDD, 0x1F3F, 0x59BF, 0x48F3, 0x64A1, 0x5ABC, 0x6632, 0x6051 +}; +static const uint8_t ppmd_exp_escape[16] = { + 25, 14, 9, 7, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2 +}; + +/* ----------------------------------------------------------- range coder */ + +static INLINE uint32_t ppmd_rc_byte(rrar_ppmd7_range_t *rc) +{ + if (rc->p < rc->end) + return *rc->p++; + rc->past++; + return 0; +} + +static INLINE void ppmd_rc_normalize(rrar_ppmd7_range_t *rc) +{ + for (;;) + { + if ((rc->low ^ (rc->low + rc->range)) >= PPMD_RC_TOP) + { + if (rc->range >= PPMD_RC_BOTTOM) + break; + rc->range = (0u - rc->low) & (PPMD_RC_BOTTOM - 1); + } + rc->code = (rc->code << 8) | ppmd_rc_byte(rc); + rc->range <<= 8; + rc->low <<= 8; + } +} + +/* Where the code falls among @total. */ +static INLINE uint32_t ppmd_rc_threshold(rrar_ppmd7_range_t *rc, + uint32_t total) +{ + return (rc->code - rc->low) / (rc->range /= total); +} + +static INLINE void ppmd_rc_decode(rrar_ppmd7_range_t *rc, + uint32_t start, uint32_t size) +{ + rc->low += start * rc->range; + rc->range *= size; + ppmd_rc_normalize(rc); +} + +/* A binary decision with @size0 of PPMD_BIN_SCALE for 0. The scale is a + * power of two, so the threshold needs no division: the code is under + * size0 * range exactly when the quotient is under size0. */ +static INLINE unsigned ppmd_rc_bit(rrar_ppmd7_range_t *rc, uint32_t size0) +{ + rc->range >>= PPMD_INT_BITS + PPMD_PERIOD_BITS; + if (rc->code - rc->low < size0 * rc->range) + { + rc->range *= size0; + ppmd_rc_normalize(rc); + return 0; + } + rc->low += size0 * rc->range; + rc->range *= PPMD_BIN_SCALE - size0; + ppmd_rc_normalize(rc); + return 1; +} + +/* ------------------------------------------------------------- allocator */ + +/* A free block's link to the next of its size sits in its first four + * bytes, where the block's own fields are when in use. */ +static INLINE void ppmd_insert_node(rrar_ppmd7_t *p, void *node, + unsigned indx) +{ + memcpy(node, &p->free_list[indx], sizeof(uint32_t)); + p->free_list[indx] = PPMD_REF(p, node); +} + +static INLINE void *ppmd_remove_node(rrar_ppmd7_t *p, unsigned indx) +{ + void *node = PPMD_PTR(p, p->free_list[indx]); + memcpy(&p->free_list[indx], node, sizeof(uint32_t)); + return node; +} + +/* The units of a block of size @old_indx past the first @new_indx's + * worth go back on the free lists. */ +static void ppmd_split_block(rrar_ppmd7_t *p, void *ptr, + unsigned old_indx, unsigned new_indx) +{ + unsigned i; + unsigned nu = PPMD_I2U(p, old_indx) - PPMD_I2U(p, new_indx); + ptr = (uint8_t *)ptr + PPMD_U2B(PPMD_I2U(p, new_indx)); + if (PPMD_I2U(p, i = PPMD_U2I(p, nu)) != nu) + { + unsigned k = PPMD_I2U(p, --i); + ppmd_insert_node(p, (uint8_t *)ptr + PPMD_U2B(k), nu - k - 1); + } + ppmd_insert_node(p, ptr, i); +} + +/* Every free block into one list, neighbours merged, and back onto the + * free lists by size. The list's head is the spare unit past the end of + * the model's memory. */ +static void ppmd_glue_free_blocks(rrar_ppmd7_t *p) +{ + unsigned i; + uint32_t head = p->align_offset + p->size; + uint32_t n = head; + + p->glue_count = 255; + + for (i = 0; i < PPMD_NUM_INDEXES; i++) + { + uint16_t nu = PPMD_I2U(p, i); + uint32_t next = p->free_list[i]; + p->free_list[i] = 0; + while (next != 0) + { + ppmd_node_t *node = PPMD_NODE(p, next); + node->next = n; + PPMD_NODE(p, n)->prev = next; + n = next; + memcpy(&next, node, sizeof(uint32_t)); + node->stamp = 0; + node->nu = nu; + } + } + PPMD_NODE(p, head)->stamp = 1; + PPMD_NODE(p, head)->next = n; + PPMD_NODE(p, n)->prev = head; + if (p->lo_unit != p->hi_unit) + ((ppmd_node_t *)p->lo_unit)->stamp = 1; + + /* each block takes in the free blocks that follow it */ + while (n != head) + { + ppmd_node_t *node = PPMD_NODE(p, n); + uint32_t nu = node->nu; + for (;;) + { + ppmd_node_t *node2 = node + nu; + uint16_t head2[2]; + /* the block may be in use: its first four bytes as they are */ + memcpy(head2, node2, sizeof(head2)); + nu += head2[1]; + if (head2[0] != 0 || nu >= 0x10000) + break; + PPMD_NODE(p, node2->prev)->next = node2->next; + PPMD_NODE(p, node2->next)->prev = node2->prev; + node->nu = (uint16_t)nu; + } + n = node->next; + } + + for (n = PPMD_NODE(p, head)->next; n != head; ) + { + ppmd_node_t *node = PPMD_NODE(p, n); + uint32_t next = node->next; + unsigned nu; + for (nu = node->nu; nu > 128; nu -= 128, node += 128) + ppmd_insert_node(p, node, PPMD_NUM_INDEXES - 1); + if (PPMD_I2U(p, i = PPMD_U2I(p, nu)) != nu) + { + unsigned k = PPMD_I2U(p, --i); + ppmd_insert_node(p, node + k, nu - k - 1); + } + ppmd_insert_node(p, node, i); + n = next; + } +} + +/* A block when its own list and the gap between the unit areas are + * empty: glued lists, a larger block split, or the top of the text area. + * NULL when there is none. */ +static void *ppmd_alloc_units_rare(rrar_ppmd7_t *p, unsigned indx) +{ + unsigned i; + void *block; + + if (p->glue_count == 0) + { + ppmd_glue_free_blocks(p); + if (p->free_list[indx] != 0) + return ppmd_remove_node(p, indx); + } + + i = indx; + do + { + if (++i == PPMD_NUM_INDEXES) + { + uint32_t num_bytes = PPMD_U2B(PPMD_I2U(p, indx)); + p->glue_count--; + if ((uint32_t)(p->units_start - p->text) > num_bytes) + return p->units_start -= num_bytes; + return NULL; + } + } while (p->free_list[i] == 0); + + block = ppmd_remove_node(p, i); + ppmd_split_block(p, block, i, indx); + return block; +} + +static INLINE void *ppmd_alloc_units(rrar_ppmd7_t *p, unsigned indx) +{ + uint32_t num_bytes; + if (p->free_list[indx] != 0) + return ppmd_remove_node(p, indx); + num_bytes = PPMD_U2B(PPMD_I2U(p, indx)); + if (num_bytes <= (uint32_t)(p->hi_unit - p->lo_unit)) + { + void *block = p->lo_unit; + p->lo_unit += num_bytes; + return block; + } + return ppmd_alloc_units_rare(p, indx); +} + +/* A block of states down from @old_nu units to @new_nu: moved when a + * block of the smaller size is free, else split in place. */ +static void *ppmd_shrink_units(rrar_ppmd7_t *p, void *old_ptr, + unsigned old_nu, unsigned new_nu) +{ + unsigned i0 = PPMD_U2I(p, old_nu); + unsigned i1 = PPMD_U2I(p, new_nu); + if (i0 == i1) + return old_ptr; + if (p->free_list[i1] != 0) + { + void *ptr = ppmd_remove_node(p, i1); + memcpy(ptr, old_ptr, PPMD_U2B(new_nu)); + ppmd_insert_node(p, old_ptr, i0); + return ptr; + } + ppmd_split_block(p, old_ptr, i0, i1); + return old_ptr; +} + +/* ----------------------------------------------------------------- model */ + +static void ppmd_restart_model(rrar_ppmd7_t *p) +{ + unsigned i, k, m; + ppmd_state_t *s; + + memset(p->free_list, 0, sizeof(p->free_list)); + p->text = p->base + p->align_offset; + p->hi_unit = p->text + p->size; + p->lo_unit = p->hi_unit - p->size / 8 / PPMD_UNIT_SIZE * 7 * PPMD_UNIT_SIZE; + p->units_start = p->lo_unit; + p->glue_count = 0; + + p->order_fall = p->max_order; + p->init_rl = -(int32_t)((p->max_order < 12) ? p->max_order : 12) - 1; + p->run_length = p->init_rl; + p->prev_success = 0; + + p->hi_unit -= PPMD_UNIT_SIZE; + p->min_ctx = (ppmd_context_t *)p->hi_unit; + p->max_ctx = p->min_ctx; + p->min_ctx->suffix = 0; + p->min_ctx->num_stats = 256; + p->min_ctx->u.m.summ_freq = 256 + 1; + + s = (ppmd_state_t *)p->lo_unit; + p->found = s; + p->lo_unit += PPMD_U2B(256 / 2); + PPMD_SET_STATS(p->min_ctx, PPMD_REF(p, s)); + for (i = 0; i < 256; i++, s++) + { + s->symbol = (uint8_t)i; + s->freq = 1; + s->successor_lo = 0; + s->successor_hi = 0; + } + + for (i = 0; i < 128; i++) + for (k = 0; k < 8; k++) + { + uint16_t val = (uint16_t)(PPMD_BIN_SCALE - ppmd_init_bin_esc[k] / (i + 2)); + for (m = 0; m < 64; m += 8) + p->bin_summ[i][k + m] = val; + } + + for (i = 0; i < 25; i++) + for (k = 0; k < 16; k++) + { + rrar_ppmd7_see_t *see = &p->see[i][k]; + see->shift = PPMD_PERIOD_BITS - 4; + see->summ = (uint16_t)((5 * i + 10) << see->shift); + see->count = 4; + } +} + +/* The contexts the symbol just found leads to, from the longest one that + * already has it down to min_ctx. NULL when memory runs out. */ +static ppmd_context_t *ppmd_create_successors(rrar_ppmd7_t *p, bool skip) +{ + ppmd_state_t *ps[PPMD_MAX_ORDER]; + ppmd_state_t up_state; + ppmd_state_t *s; + ppmd_context_t *c = p->min_ctx; + uint32_t up_branch = PPMD_SUCCESSOR(p->found); + uint8_t symbol = p->found->symbol; + unsigned num_ps = 0; + + if (!skip) + ps[num_ps++] = p->found; + + while (c->suffix) + { + uint32_t successor; + c = PPMD_SUFFIX(p, c); + if (c->num_stats != 1) + for (s = PPMD_STATS(p, c); s->symbol != symbol; s++); + else + s = PPMD_ONE_STATE(c); + successor = PPMD_SUCCESSOR(s); + if (successor != up_branch) + { + c = PPMD_CTX(p, successor); + if (num_ps == 0) + return c; + break; + } + ps[num_ps++] = s; + } + + up_state.symbol = *(const uint8_t *)PPMD_PTR(p, up_branch); + PPMD_SET_SUCCESSOR(&up_state, up_branch + 1); + + if (c->num_stats == 1) + up_state.freq = PPMD_ONE_STATE(c)->freq; + else + { + uint32_t cf, s0; + for (s = PPMD_STATS(p, c); s->symbol != up_state.symbol; s++); + cf = (uint32_t)s->freq - 1; + s0 = (uint32_t)c->u.m.summ_freq - c->num_stats - cf; + up_state.freq = (uint8_t)(1 + ((2 * cf <= s0) + ? (5 * cf > s0) + : ((2 * cf + 3 * s0 - 1) / (2 * s0)))); + } + + while (num_ps != 0) + { + ppmd_context_t *c1; + if (p->hi_unit != p->lo_unit) + c1 = (ppmd_context_t *)(p->hi_unit -= PPMD_UNIT_SIZE); + else if (p->free_list[0] != 0) + c1 = (ppmd_context_t *)ppmd_remove_node(p, 0); + else if (!(c1 = (ppmd_context_t *)ppmd_alloc_units_rare(p, 0))) + return NULL; + c1->num_stats = 1; + memcpy(PPMD_ONE_STATE(c1), &up_state, sizeof(up_state)); + c1->suffix = PPMD_REF(p, c); + PPMD_SET_SUCCESSOR(ps[--num_ps], PPMD_REF(p, c1)); + c = c1; + } + + return c; +} + +static INLINE void ppmd_swap_states(ppmd_state_t *a, ppmd_state_t *b) +{ + ppmd_state_t t = *a; + *a = *b; + *b = t; +} + +/* After a symbol: its count in the next shorter context, the contexts it + * leads to, and it added to every context from max_ctx down to the one + * it was found in. */ +static void ppmd_update_model(rrar_ppmd7_t *p) +{ + ppmd_context_t *c; + uint32_t successor; + unsigned s0, ns; + uint8_t symbol = p->found->symbol; + uint8_t found_freq = p->found->freq; + uint32_t f_successor = PPMD_SUCCESSOR(p->found); + + if (found_freq < PPMD_MAX_FREQ / 4 && p->min_ctx->suffix != 0) + { + c = PPMD_SUFFIX(p, p->min_ctx); + if (c->num_stats == 1) + { + ppmd_state_t *s = PPMD_ONE_STATE(c); + if (s->freq < 32) + s->freq++; + } + else + { + ppmd_state_t *s = PPMD_STATS(p, c); + if (s->symbol != symbol) + { + do + { + s++; + } while (s->symbol != symbol); + if (s[0].freq >= s[-1].freq) + { + ppmd_swap_states(&s[0], &s[-1]); + s--; + } + } + if (s->freq < PPMD_MAX_FREQ - 9) + { + s->freq += 2; + c->u.m.summ_freq += 2; + } + } + } + + if (p->order_fall == 0) + { + p->min_ctx = p->max_ctx = ppmd_create_successors(p, true); + if (!p->min_ctx) + { + ppmd_restart_model(p); + return; + } + PPMD_SET_SUCCESSOR(p->found, PPMD_REF(p, p->min_ctx)); + return; + } + + *p->text++ = symbol; + successor = PPMD_REF(p, p->text); + if (p->text >= p->units_start) + { + ppmd_restart_model(p); + return; + } + + if (f_successor) + { + if (f_successor <= successor) + { + /* it leads into the text: make the contexts it means */ + ppmd_context_t *cs = ppmd_create_successors(p, false); + if (!cs) + { + ppmd_restart_model(p); + return; + } + f_successor = PPMD_REF(p, cs); + } + if (--p->order_fall == 0) + { + successor = f_successor; + p->text -= (p->max_ctx != p->min_ctx); + } + } + else + { + PPMD_SET_SUCCESSOR(p->found, successor); + f_successor = PPMD_REF(p, p->min_ctx); + } + + ns = p->min_ctx->num_stats; + s0 = p->min_ctx->u.m.summ_freq - ns - (found_freq - 1); + + for (c = p->max_ctx; c != p->min_ctx; c = PPMD_SUFFIX(p, c)) + { + ppmd_state_t *s; + uint32_t cf, sf; + unsigned ns1 = c->num_stats; + + if (ns1 != 1) + { + if ((ns1 & 1) == 0) + { + /* the states fill their block: one unit more */ + unsigned old_nu = ns1 >> 1; + unsigned i = PPMD_U2I(p, old_nu); + if (i != PPMD_U2I(p, old_nu + 1)) + { + void *ptr = ppmd_alloc_units(p, i + 1); + void *old_ptr; + if (!ptr) + { + ppmd_restart_model(p); + return; + } + old_ptr = PPMD_STATS(p, c); + memcpy(ptr, old_ptr, PPMD_U2B(old_nu)); + ppmd_insert_node(p, old_ptr, i); + PPMD_SET_STATS(c, PPMD_REF(p, ptr)); + } + } + c->u.m.summ_freq = (uint16_t)(c->u.m.summ_freq + (2 * ns1 < ns) + + 2 * ((4 * ns1 <= ns) & (c->u.m.summ_freq <= 8 * ns1))); + } + else + { + /* one state becomes a block of states */ + ppmd_state_t one; + if (!(s = (ppmd_state_t *)ppmd_alloc_units(p, 0))) + { + ppmd_restart_model(p); + return; + } + memcpy(&one, PPMD_ONE_STATE(c), sizeof(one)); + *s = one; + PPMD_SET_STATS(c, PPMD_REF(p, s)); + if (s->freq < PPMD_MAX_FREQ / 4 - 1) + s->freq <<= 1; + else + s->freq = PPMD_MAX_FREQ - 4; + c->u.m.summ_freq = (uint16_t)(s->freq + p->init_esc + (ns > 3)); + } + + cf = 2 * (uint32_t)found_freq * (c->u.m.summ_freq + 6); + sf = (uint32_t)s0 + c->u.m.summ_freq; + if (cf < 6 * sf) + { + cf = 1 + (cf > sf) + (cf >= 4 * sf); + c->u.m.summ_freq += 3; + } + else + { + cf = 4 + (cf >= 9 * sf) + (cf >= 12 * sf) + (cf >= 15 * sf); + c->u.m.summ_freq = (uint16_t)(c->u.m.summ_freq + cf); + } + + s = PPMD_STATS(p, c) + ns1; + PPMD_SET_SUCCESSOR(s, successor); + s->symbol = symbol; + s->freq = (uint8_t)cf; + c->num_stats = (uint16_t)(ns1 + 1); + } + + p->min_ctx = p->max_ctx = PPMD_CTX(p, f_successor); +} + +/* Halve min_ctx's counts, the found state first, and drop the states + * that reach zero. */ +static void ppmd_rescale(rrar_ppmd7_t *p) +{ + unsigned i, adder, sum_freq, esc_freq; + ppmd_context_t *mc = p->min_ctx; + ppmd_state_t *stats = PPMD_STATS(p, mc); + ppmd_state_t *s = p->found; + + { + ppmd_state_t t = *s; + for (; s != stats; s--) + s[0] = s[-1]; + *s = t; + } + + esc_freq = mc->u.m.summ_freq - s->freq; + s->freq += 4; + adder = (p->order_fall != 0); + s->freq = (uint8_t)((s->freq + adder) >> 1); + sum_freq = s->freq; + + i = mc->num_stats - 1; + do + { + esc_freq -= (++s)->freq; + s->freq = (uint8_t)((s->freq + adder) >> 1); + sum_freq += s->freq; + if (s[0].freq > s[-1].freq) + { + /* keep the states in order of their counts */ + ppmd_state_t *s1 = s; + ppmd_state_t t = *s1; + do + { + s1[0] = s1[-1]; + } while (--s1 != stats && t.freq > s1[-1].freq); + *s1 = t; + } + } while (--i); + + if (s->freq == 0) + { + unsigned num_stats = mc->num_stats; + unsigned n0, n1; + do + { + i++; + } while ((--s)->freq == 0); + esc_freq += i; + mc->num_stats = (uint16_t)(mc->num_stats - i); + if (mc->num_stats == 1) + { + ppmd_state_t t = *stats; + do + { + t.freq = (uint8_t)(t.freq - (t.freq >> 1)); + esc_freq >>= 1; + } while (esc_freq > 1); + ppmd_insert_node(p, stats, PPMD_U2I(p, (num_stats + 1) >> 1)); + p->found = PPMD_ONE_STATE(mc); + memcpy(p->found, &t, sizeof(t)); + return; + } + n0 = (num_stats + 1) >> 1; + n1 = (mc->num_stats + 1) >> 1; + if (n0 != n1) + PPMD_SET_STATS(mc, PPMD_REF(p, ppmd_shrink_units(p, stats, n0, n1))); + } + mc->u.m.summ_freq = (uint16_t)(sum_freq + esc_freq - (esc_freq >> 1)); + p->found = PPMD_STATS(p, mc); +} + +/* The escape estimate for min_ctx with @num_masked of its symbols + * already ruled out, and the SEE context it came from. */ +static rrar_ppmd7_see_t *ppmd_make_esc_freq(rrar_ppmd7_t *p, + unsigned num_masked, uint32_t *esc_freq) +{ + rrar_ppmd7_see_t *see; + ppmd_context_t *mc = p->min_ctx; + unsigned non_masked = mc->num_stats - num_masked; + unsigned r; + + if (mc->num_stats == 256) + { + *esc_freq = 1; + return &p->dummy_see; + } + + see = p->see[p->ns2indx[non_masked - 1]] + + (non_masked < (unsigned)PPMD_SUFFIX(p, mc)->num_stats - mc->num_stats) + + 2 * (mc->u.m.summ_freq < 11 * mc->num_stats) + + 4 * (num_masked > non_masked) + + p->hi_bits_flag; + r = see->summ >> see->shift; + see->summ = (uint16_t)(see->summ - r); + *esc_freq = r + (r == 0); + return see; +} + +static INLINE void ppmd_see_update(rrar_ppmd7_see_t *see) +{ + if (see->shift < PPMD_PERIOD_BITS && --see->count == 0) + { + see->summ <<= 1; + see->count = (uint8_t)(3 << see->shift++); + } +} + +/* Where the model goes after a symbol: straight to the context the found + * state leads to when nothing has to be learnt. */ +static INLINE void ppmd_next_context(rrar_ppmd7_t *p) +{ + ppmd_context_t *c = PPMD_CTX(p, PPMD_SUCCESSOR(p->found)); + if (p->order_fall == 0 && (uint8_t *)c > p->text) + p->min_ctx = p->max_ctx = c; + else + ppmd_update_model(p); +} + +/* -------------------------------------------------------------- decoding */ + +/* After an escape from min_ctx: down the suffixes, leaving out the + * symbols already ruled out, until one of the rest is the symbol. Out of + * line, so that the common paths in rrar_ppmd7_decode_symbol carry + * neither the mask nor the registers this needs. A ruled-out symbol's + * mask is 0 and the rest are all ones, so a count is taken or left out + * without a branch. */ +static PPMD_NOINLINE int ppmd_decode_escape(rrar_ppmd7_t *p, + rrar_ppmd7_range_t *rc) +{ + int8_t mask[256]; + ppmd_context_t *mc = p->min_ctx; + ppmd_state_t *s; + unsigned n; + + memset(mask, -1, sizeof(mask)); + if (mc->num_stats == 1) + mask[PPMD_ONE_STATE(mc)->symbol] = 0; + else + for (s = PPMD_STATS(p, mc), n = mc->num_stats; n != 0; n--, s++) + mask[s->symbol] = 0; + + for (;;) + { + rrar_ppmd7_see_t *see; + uint32_t freq_sum, count, hi_cnt; + unsigned num_masked = mc->num_stats; + + do + { + p->order_fall++; + if (!mc->suffix) + return -1; + mc = PPMD_SUFFIX(p, mc); + } while (mc->num_stats == num_masked); + p->min_ctx = mc; + + hi_cnt = 0; + s = PPMD_STATS(p, mc); + n = mc->num_stats; + if (n & 1) + { + hi_cnt = s->freq & (uint32_t)(int32_t)mask[s->symbol]; + s++; + } + for (n >>= 1; n != 0; n--, s += 2) + { + hi_cnt += s[0].freq & (uint32_t)(int32_t)mask[s[0].symbol]; + hi_cnt += s[1].freq & (uint32_t)(int32_t)mask[s[1].symbol]; + } + + see = ppmd_make_esc_freq(p, num_masked, &freq_sum); + freq_sum += hi_cnt; + count = ppmd_rc_threshold(rc, freq_sum); + + if (count < hi_cnt) + { + /* the first state whose running count passes the code */ + uint8_t symbol; + int32_t left = (int32_t)count; + s = PPMD_STATS(p, mc); + for (;;) + { + left -= s->freq & (uint32_t)(int32_t)mask[s->symbol]; + if (left < 0) + break; + s++; + } + ppmd_rc_decode(rc, count - (uint32_t)left - s->freq, s->freq); + ppmd_see_update(see); + p->found = s; + symbol = s->symbol; + mc->u.m.summ_freq += 4; + if ((s->freq += 4) > PPMD_MAX_FREQ) + ppmd_rescale(p); + p->run_length = p->init_rl; + ppmd_update_model(p); + return symbol; + } + if (count >= freq_sum) + return -2; + ppmd_rc_decode(rc, hi_cnt, freq_sum - hi_cnt); + see->summ = (uint16_t)(see->summ + freq_sum); + + for (s = PPMD_STATS(p, mc), n = mc->num_stats; n != 0; n--, s++) + mask[s->symbol] = 0; + } +} + +int rrar_ppmd7_decode_symbol(rrar_ppmd7_t *p, rrar_ppmd7_range_t *rc) +{ + ppmd_context_t *mc = p->min_ctx; + + if (mc->num_stats != 1) + { + uint32_t count, hi_cnt; + unsigned i; + ppmd_state_t *s = PPMD_STATS(p, mc); + count = ppmd_rc_threshold(rc, mc->u.m.summ_freq); + if (count < (hi_cnt = s->freq)) + { + /* the most probable symbol */ + uint8_t symbol; + ppmd_rc_decode(rc, 0, s->freq); + p->found = s; + symbol = s->symbol; + p->prev_success = (2 * (unsigned)s->freq > mc->u.m.summ_freq); + p->run_length += p->prev_success; + mc->u.m.summ_freq += 4; + if ((s->freq += 4) > PPMD_MAX_FREQ) + ppmd_rescale(p); + ppmd_next_context(p); + return symbol; + } + p->prev_success = 0; + i = mc->num_stats - 1; + do + { + if ((hi_cnt += (++s)->freq) > count) + { + uint8_t symbol; + ppmd_rc_decode(rc, hi_cnt - s->freq, s->freq); + p->found = s; + symbol = s->symbol; + s->freq += 4; + mc->u.m.summ_freq += 4; + if (s[0].freq > s[-1].freq) + { + ppmd_swap_states(&s[0], &s[-1]); + p->found = --s; + if (s->freq > PPMD_MAX_FREQ) + ppmd_rescale(p); + } + ppmd_next_context(p); + return symbol; + } + } while (--i); + if (count >= mc->u.m.summ_freq) + return -2; + p->hi_bits_flag = p->hb2flag[p->found->symbol]; + ppmd_rc_decode(rc, hi_cnt, mc->u.m.summ_freq - hi_cnt); + } + else + { + ppmd_state_t *one = PPMD_ONE_STATE(mc); + uint16_t *prob = &p->bin_summ[one->freq - 1][ + p->prev_success + + p->ns2bsindx[PPMD_SUFFIX(p, mc)->num_stats - 1] + + (p->hi_bits_flag = p->hb2flag[p->found->symbol]) + + 2 * p->hb2flag[one->symbol] + + ((p->run_length >> 26) & 0x20)]; + if (ppmd_rc_bit(rc, *prob) == 0) + { + uint8_t symbol; + *prob = (uint16_t)(*prob + (1 << PPMD_INT_BITS) - PPMD_GET_MEAN(*prob)); + p->found = one; + symbol = one->symbol; + one->freq = (uint8_t)(one->freq + (one->freq < 128)); + p->prev_success = 1; + p->run_length++; + ppmd_next_context(p); + return symbol; + } + *prob = (uint16_t)(*prob - PPMD_GET_MEAN(*prob)); + p->init_esc = ppmd_exp_escape[*prob >> 10]; + p->prev_success = 0; + } + + return ppmd_decode_escape(p, rc); +} + +/* ------------------------------------------------------------------- API */ + +void rrar_ppmd7_construct(rrar_ppmd7_t *p) +{ + unsigned i, k, m; + + p->base = NULL; + p->size = 0; + + /* block sizes in units: 1 to 4 one apart, then two, three and four */ + for (i = 0, k = 0; i < PPMD_NUM_INDEXES; i++) + { + unsigned step = (i >= 12) ? 4 : (i >> 2) + 1; + do + { + p->units2indx[k++] = (uint8_t)i; + } while (--step); + p->indx2units[i] = (uint8_t)k; + } + + p->ns2bsindx[0] = 0 << 1; + p->ns2bsindx[1] = 1 << 1; + memset(p->ns2bsindx + 2, 2 << 1, 9); + memset(p->ns2bsindx + 11, 3 << 1, 256 - 11); + + for (i = 0; i < 3; i++) + p->ns2indx[i] = (uint8_t)i; + for (m = i, k = 1; i < 256; i++) + { + p->ns2indx[i] = (uint8_t)m; + if (--k == 0) + k = ++m - 2; + } + + memset(p->hb2flag, 0, 0x40); + memset(p->hb2flag + 0x40, 8, 0x100 - 0x40); +} + +void rrar_ppmd7_free(rrar_ppmd7_t *p) +{ + free(p->base); + p->base = NULL; + p->size = 0; +} + +bool rrar_ppmd7_alloc(rrar_ppmd7_t *p, uint32_t size) +{ + if (p->base && p->size == size) + return true; + /* the root context takes a unit of its own */ + if (size < PPMD_UNIT_SIZE) + return false; + rrar_ppmd7_free(p); + /* never 0, so no link into the model is 0; a spare unit past the end + * heads the list of free blocks when they are glued */ + p->align_offset = 4 - (size & 3); + if (!(p->base = (uint8_t *)malloc((size_t)p->align_offset + size + + PPMD_UNIT_SIZE))) + return false; + p->size = size; + return true; +} + +void rrar_ppmd7_init(rrar_ppmd7_t *p, unsigned max_order) +{ + p->max_order = max_order; + ppmd_restart_model(p); + p->dummy_see.shift = PPMD_PERIOD_BITS; + p->dummy_see.summ = 0; + p->dummy_see.count = 64; +} + +bool rrar_ppmd7_range_init(rrar_ppmd7_range_t *rc, const uint8_t *data, + const uint8_t *end, uint32_t past) +{ + unsigned i; + rc->p = data; + rc->end = end; + rc->past = past; + rc->low = 0; + rc->range = 0xFFFFFFFFu; + rc->code = 0; + for (i = 0; i < 4; i++) + rc->code = (rc->code << 8) | ppmd_rc_byte(rc); + return rc->code < 0xFFFFFFFFu; +} diff --git a/formats/rar/rrar_ppmd7.h b/formats/rar/rrar_ppmd7.h new file mode 100644 index 00000000..29486fe1 --- /dev/null +++ b/formats/rar/rrar_ppmd7.h @@ -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 +#include + +#include +#include + +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 diff --git a/formats/vp9/rvp9_recon.inc b/formats/vp9/rvp9_recon.inc index 04cb06e7..b366a652 100644 --- a/formats/vp9/rvp9_recon.inc +++ b/formats/vp9/rvp9_recon.inc @@ -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]); diff --git a/formats/zip/rzip_archive.c b/formats/zip/rzip_archive.c new file mode 100644 index 00000000..1dfb7cf3 --- /dev/null +++ b/formats/zip/rzip_archive.c @@ -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 +#include + +#include +#include +#include +#include + +#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; +} diff --git a/hash/lrc_hash.c b/hash/lrc_hash.c index 48136c78..e8a940b7 100644 --- a/hash/lrc_hash.c +++ b/hash/lrc_hash.c @@ -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; diff --git a/include/7z/r7z_archive.h b/include/7z/r7z_archive.h index 755eb73b..55670164 100644 --- a/include/7z/r7z_archive.h +++ b/include/7z/r7z_archive.h @@ -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 diff --git a/include/audio/audio_resampler.h b/include/audio/audio_resampler.h index d142a543..5f80af4d 100644 --- a/include/audio/audio_resampler.h +++ b/include/audio/audio_resampler.h @@ -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 diff --git a/include/audio/sinc_resampler_int16.h b/include/audio/sinc_resampler_int16.h index 385f5314..e907ac82 100644 --- a/include/audio/sinc_resampler_int16.h +++ b/include/audio/sinc_resampler_int16.h @@ -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 diff --git a/include/defines/cocoa_defines.h b/include/defines/cocoa_defines.h index 341266e1..2c04e1e0 100644 --- a/include/defines/cocoa_defines.h +++ b/include/defines/cocoa_defines.h @@ -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 diff --git a/include/encodings/utf.h b/include/encodings/utf.h index 0c52c66b..f75c6f79 100644 --- a/include/encodings/utf.h +++ b/include/encodings/utf.h @@ -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: **/ diff --git a/include/file/archive_file.h b/include/file/archive_file.h index c21c035a..0d52bda4 100644 --- a/include/file/archive_file.h +++ b/include/file/archive_file.h @@ -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 ---- * diff --git a/include/formats/image.h b/include/formats/image.h index e9f20b50..f212a3cc 100644 --- a/include/formats/image.h +++ b/include/formats/image.h @@ -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); diff --git a/include/formats/image_yuv_blit.h b/include/formats/image_yuv_blit.h new file mode 100644 index 00000000..7567f573 --- /dev/null +++ b/include/formats/image_yuv_blit.h @@ -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 +#include + +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 diff --git a/include/gfx/video_frame.h b/include/gfx/video_frame.h index 98f46b70..e08e0b9e 100644 --- a/include/gfx/video_frame.h +++ b/include/gfx/video_frame.h @@ -24,6 +24,8 @@ #define _LIBRETRO_SDK_VIDEO_FRAME_H #include +#include +#include #include #include @@ -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, diff --git a/include/jni/rjni.h b/include/jni/rjni.h new file mode 100644 index 00000000..42007ee7 --- /dev/null +++ b/include/jni/rjni.h @@ -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 + +#include +#include + +/* 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 diff --git a/include/libretro.h b/include/libretro.h index 73ff390c..989900f9 100644 --- a/include/libretro.h +++ b/include/libretro.h @@ -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 const struct retro_video_views *. + * @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 unsigned *. 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 struct retro_vr_frame_state *. + * @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 sizeof(retro_vr_eye) == sizeof(int). 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 sizeof(enum retro_camera_buffer) == sizeof(int). * 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 planes[2] == planes[1] + 1 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; }; /** @} */ diff --git a/include/libretro_vulkan.h b/include/libretro_vulkan.h index 9d21da78..2a47d0ad 100644 --- a/include/libretro_vulkan.h +++ b/include/libretro_vulkan.h @@ -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 diff --git a/include/net/net_socket_ssl.h b/include/net/net_socket_ssl.h index be2e0ca5..231169d2 100644 --- a/include/net/net_socket_ssl.h +++ b/include/net/net_socket_ssl.h @@ -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); diff --git a/include/rar/rrar_archive.h b/include/rar/rrar_archive.h new file mode 100644 index 00000000..8e5dbe9f --- /dev/null +++ b/include/rar/rrar_archive.h @@ -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 +#include + +#include + +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 diff --git a/include/retro_atomic.h b/include/retro_atomic.h index be5ad81c..9f5772c8 100644 --- a/include/retro_atomic.h +++ b/include/retro_atomic.h @@ -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 typedef _Atomic(int64_t) retro_atomic_64_t; diff --git a/include/retro_common_api.h b/include/retro_common_api.h index f01fb82c..043fc07a 100644 --- a/include/retro_common_api.h +++ b/include/retro_common_api.h @@ -95,6 +95,7 @@ typedef int ssize_t; #ifndef PRId64 #define PRId64 "I64d" #define PRIu64 "I64u" +#define PRIx64 "I64x" #define PRIuPTR "Iu" #endif #endif diff --git a/include/retro_miscellaneous.h b/include/retro_miscellaneous.h index a913479b..e2717a08 100644 --- a/include/retro_miscellaneous.h +++ b/include/retro_miscellaneous.h @@ -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" diff --git a/include/streams/file_stream.h b/include/streams/file_stream.h index cbf458d3..8959f596 100644 --- a/include/streams/file_stream.h +++ b/include/streams/file_stream.h @@ -337,6 +337,28 @@ RFILE *filestream_open_atomic(const char *path); */ int filestream_commit_atomic(RFILE *file, const char *path, bool ok); +/** + * The name of the sibling temporary file an atomic write of \c path + * goes through, for a writer that opens files by name. + * + * @param s Where the name is written. + * @param len The size of \c s. + * @param path Path of the file that will be replaced. + * @return \c false if the name does not fit in \c s. + */ +bool filestream_atomic_temp_name(char *s, size_t len, const char *path); + +/** + * Finishes an atomic write of \c path whose temporary file, named by + * filestream_atomic_temp_name(), was written and closed by other means: + * as filestream_commit_atomic() does after its close. + * + * @param path Path of the file that will be replaced. + * @param ok Whether the temporary file holds everything it should. + * @return As filestream_commit_atomic(). + */ +int filestream_finish_atomic(const char *path, bool ok); + /** * Writes a single character to the given file. * @@ -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. diff --git a/include/vfs/vfs_implementation.h b/include/vfs/vfs_implementation.h index c43c6d2c..ffb57c44 100644 --- a/include/vfs/vfs_implementation.h +++ b/include/vfs/vfs_implementation.h @@ -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); diff --git a/include/zip/rzip_archive.h b/include/zip/rzip_archive.h new file mode 100644 index 00000000..60e3f641 --- /dev/null +++ b/include/zip/rzip_archive.h @@ -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 +#include + +#include + +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 diff --git a/jni/rjni.c b/jni/rjni.c new file mode 100644 index 00000000..4789de37 --- /dev/null +++ b/jni/rjni.c @@ -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 + +#include +#include + +/* 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; +} diff --git a/lists/file_list.c b/lists/file_list.c index 73e806ea..2f084a62 100644 --- a/lists/file_list.c +++ b/lists/file_list.c @@ -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) diff --git a/media/media_detect_cd.c b/media/media_detect_cd.c index ae460f1a..7afb987f 100644 --- a/media/media_detect_cd.c +++ b/media/media_detect_cd.c @@ -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'; diff --git a/memmap/memmap.c b/memmap/memmap.c index ffccab6d..8788ff9d 100644 --- a/memmap/memmap.c +++ b/memmap/memmap.c @@ -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 +#include /* 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) { } diff --git a/memory/mem_stats.c b/memory/mem_stats.c index a3616c7d..89a73f2f 100644 --- a/memory/mem_stats.c +++ b/memory/mem_stats.c @@ -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; diff --git a/net/net_http.c b/net/net_http.c index e06074d6..03bdea6e 100644 --- a/net/net_http.c +++ b/net/net_http.c @@ -35,6 +35,7 @@ #include #endif #include +#include #include #include #include @@ -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); } diff --git a/net/net_socket_ssl_bear.c b/net/net_socket_ssl_bear.c index 71a3d287..2a7cbe1e 100644 --- a/net/net_socket_ssl_bear.c +++ b/net/net_socket_ssl_bear.c @@ -20,17 +20,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include + #include #include #include #include #include +#include #include 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; diff --git a/net/net_socket_ssl_mbed.c b/net/net_socket_ssl_mbed.c index a224a066..84834d5a 100644 --- a/net/net_socket_ssl_mbed.c +++ b/net/net_socket_ssl_mbed.c @@ -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) { diff --git a/net/net_socket_ssl_retro.c b/net/net_socket_ssl_retro.c index aed5df15..c1272175 100644 --- a/net/net_socket_ssl_retro.c +++ b/net/net_socket_ssl_retro.c @@ -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; diff --git a/queues/task_queue.c b/queues/task_queue.c index d547e06c..12190f2d 100644 --- a/queues/task_queue.c +++ b/queues/task_queue.c @@ -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)) diff --git a/samples/atomic/retro_atomic_test/Makefile b/samples/atomic/retro_atomic_test/Makefile index 57645eae..b9fdafe8 100644 --- a/samples/atomic/retro_atomic_test/Makefile +++ b/samples/atomic/retro_atomic_test/Makefile @@ -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 diff --git a/samples/audio/audio_mixer_loop/Makefile b/samples/audio/audio_mixer_loop/Makefile index 0bee9d34..4933ad31 100644 --- a/samples/audio/audio_mixer_loop/Makefile +++ b/samples/audio/audio_mixer_loop/Makefile @@ -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 diff --git a/samples/audio/audio_mixer_rate_guard/Makefile b/samples/audio/audio_mixer_rate_guard/Makefile index 842e286c..b8c36f6d 100644 --- a/samples/audio/audio_mixer_rate_guard/Makefile +++ b/samples/audio/audio_mixer_rate_guard/Makefile @@ -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 diff --git a/samples/audio/audio_mixer_voice_threads/Makefile b/samples/audio/audio_mixer_voice_threads/Makefile index fe3fe84c..f4170983 100644 --- a/samples/audio/audio_mixer_voice_threads/Makefile +++ b/samples/audio/audio_mixer_voice_threads/Makefile @@ -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 diff --git a/samples/audio/float_to_s32/Makefile b/samples/audio/float_to_s32/Makefile index fa640368..0f549c3c 100644 --- a/samples/audio/float_to_s32/Makefile +++ b/samples/audio/float_to_s32/Makefile @@ -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 diff --git a/samples/compat/fnmatch/Makefile b/samples/compat/fnmatch/Makefile index 937cca07..10449c6d 100644 --- a/samples/compat/fnmatch/Makefile +++ b/samples/compat/fnmatch/Makefile @@ -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 diff --git a/samples/compat/snprintf/Makefile b/samples/compat/snprintf/Makefile index 80087c6a..959015bf 100644 --- a/samples/compat/snprintf/Makefile +++ b/samples/compat/snprintf/Makefile @@ -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 diff --git a/samples/encodings/base64/Makefile b/samples/encodings/base64/Makefile index 5534e949..a2301e92 100644 --- a/samples/encodings/base64/Makefile +++ b/samples/encodings/base64/Makefile @@ -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 diff --git a/samples/encodings/crc32/Makefile b/samples/encodings/crc32/Makefile index aa3ba1e9..1b1e1ca4 100644 --- a/samples/encodings/crc32/Makefile +++ b/samples/encodings/crc32/Makefile @@ -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 diff --git a/samples/encodings/deflate/Makefile b/samples/encodings/deflate/Makefile index c3374731..6ab82a74 100644 --- a/samples/encodings/deflate/Makefile +++ b/samples/encodings/deflate/Makefile @@ -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 diff --git a/samples/encodings/deflate/rdeflate_hash_test.c b/samples/encodings/deflate/rdeflate_hash_test.c new file mode 100644 index 00000000..67912e8a --- /dev/null +++ b/samples/encodings/deflate/rdeflate_hash_test.c @@ -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 +#include + +#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; +} diff --git a/samples/encodings/vcdiff/Makefile b/samples/encodings/vcdiff/Makefile index 980aa246..4dc96df2 100644 --- a/samples/encodings/vcdiff/Makefile +++ b/samples/encodings/vcdiff/Makefile @@ -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 diff --git a/samples/fastjmp/fastjmp_test/Makefile b/samples/fastjmp/fastjmp_test/Makefile index 6b9b3fb0..60efff70 100644 --- a/samples/fastjmp/fastjmp_test/Makefile +++ b/samples/fastjmp/fastjmp_test/Makefile @@ -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 diff --git a/samples/faulthandler/faulthandler_test/Makefile b/samples/faulthandler/faulthandler_test/Makefile index 195056a1..ae531146 100644 --- a/samples/faulthandler/faulthandler_test/Makefile +++ b/samples/faulthandler/faulthandler_test/Makefile @@ -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 diff --git a/samples/file/archive_file/Makefile b/samples/file/archive_file/Makefile index 3b8195e5..39c11768 100644 --- a/samples/file/archive_file/Makefile +++ b/samples/file/archive_file/Makefile @@ -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 diff --git a/samples/file/archive_rar/Makefile b/samples/file/archive_rar/Makefile new file mode 100644 index 00000000..ff8aca06 --- /dev/null +++ b/samples/file/archive_rar/Makefile @@ -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 diff --git a/samples/file/archive_rar/archive_rar_test.c b/samples/file/archive_rar/archive_rar_test.c new file mode 100644 index 00000000..f46b95eb --- /dev/null +++ b/samples/file/archive_rar/archive_rar_test.c @@ -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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/samples/file/config_file/Makefile b/samples/file/config_file/Makefile index 26191c56..75814ecf 100644 --- a/samples/file/config_file/Makefile +++ b/samples/file/config_file/Makefile @@ -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 diff --git a/samples/file/config_file/config_file_test.c b/samples/file/config_file/config_file_test.c index c72abb5e..48389fc8 100644 --- a/samples/file/config_file/config_file_test.c +++ b/samples/file/config_file/config_file_test.c @@ -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(); } diff --git a/samples/file/file_path/Makefile b/samples/file/file_path/Makefile index fdd9cb3f..3fe4d4e6 100644 --- a/samples/file/file_path/Makefile +++ b/samples/file/file_path/Makefile @@ -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 diff --git a/samples/file/vfs/Makefile b/samples/file/vfs/Makefile index 2f613414..3b39685a 100644 --- a/samples/file/vfs/Makefile +++ b/samples/file/vfs/Makefile @@ -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 diff --git a/samples/file/vfs/vfs_prefetch_test.c b/samples/file/vfs/vfs_prefetch_test.c new file mode 100644 index 00000000..c12e504a --- /dev/null +++ b/samples/file/vfs/vfs_prefetch_test.c @@ -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 +#include +#include +#include + +#include +#include +#include + +#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; +} diff --git a/samples/file/vfs_cdrom/Makefile b/samples/file/vfs_cdrom/Makefile index e9ef83be..70a9c5e4 100644 --- a/samples/file/vfs_cdrom/Makefile +++ b/samples/file/vfs_cdrom/Makefile @@ -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 diff --git a/samples/file/vfs_hybrid/Makefile b/samples/file/vfs_hybrid/Makefile index 90cf09e0..f3c94fd0 100644 --- a/samples/file/vfs_hybrid/Makefile +++ b/samples/file/vfs_hybrid/Makefile @@ -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 diff --git a/samples/formats/ac3/Makefile b/samples/formats/ac3/Makefile index a3657f0f..39ea546f 100644 --- a/samples/formats/ac3/Makefile +++ b/samples/formats/ac3/Makefile @@ -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) diff --git a/samples/formats/audio_transfer/Makefile b/samples/formats/audio_transfer/Makefile index e81631a4..91c30458 100644 --- a/samples/formats/audio_transfer/Makefile +++ b/samples/formats/audio_transfer/Makefile @@ -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 diff --git a/samples/formats/audio_transfer_ac3/Makefile b/samples/formats/audio_transfer_ac3/Makefile index 643511c6..a3648d60 100644 --- a/samples/formats/audio_transfer_ac3/Makefile +++ b/samples/formats/audio_transfer_ac3/Makefile @@ -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) diff --git a/samples/formats/audio_transfer_flac/Makefile b/samples/formats/audio_transfer_flac/Makefile index 38fd323d..14b57128 100644 --- a/samples/formats/audio_transfer_flac/Makefile +++ b/samples/formats/audio_transfer_flac/Makefile @@ -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 diff --git a/samples/formats/bench/Makefile b/samples/formats/bench/Makefile index 8b05340c..9e058aae 100644 --- a/samples/formats/bench/Makefile +++ b/samples/formats/bench/Makefile @@ -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 diff --git a/samples/formats/bmp/Makefile b/samples/formats/bmp/Makefile index e03c0cda..18bf05b9 100644 --- a/samples/formats/bmp/Makefile +++ b/samples/formats/bmp/Makefile @@ -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 diff --git a/samples/formats/cdfs/Makefile b/samples/formats/cdfs/Makefile index 1aba7165..9a28709c 100644 --- a/samples/formats/cdfs/Makefile +++ b/samples/formats/cdfs/Makefile @@ -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 diff --git a/samples/formats/data_transfer/Makefile b/samples/formats/data_transfer/Makefile index a1ebd7c4..3176d3a5 100644 --- a/samples/formats/data_transfer/Makefile +++ b/samples/formats/data_transfer/Makefile @@ -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 diff --git a/samples/formats/dds/Makefile b/samples/formats/dds/Makefile index 55f7a086..396ec3a5 100644 --- a/samples/formats/dds/Makefile +++ b/samples/formats/dds/Makefile @@ -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 diff --git a/samples/formats/h264/Makefile b/samples/formats/h264/Makefile index 970b5747..e2563114 100644 --- a/samples/formats/h264/Makefile +++ b/samples/formats/h264/Makefile @@ -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 diff --git a/samples/formats/h264/rh264_lossless_test.c b/samples/formats/h264/rh264_lossless_test.c index f81d2eee..5bc17b49 100644 --- a/samples/formats/h264/rh264_lossless_test.c +++ b/samples/formats/h264/rh264_lossless_test.c @@ -39,6 +39,7 @@ #include #include +#include #ifdef HAVE_THREADS #include #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; diff --git a/samples/formats/h265/Makefile b/samples/formats/h265/Makefile index 64e3e6e7..6c602507 100644 --- a/samples/formats/h265/Makefile +++ b/samples/formats/h265/Makefile @@ -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 diff --git a/samples/formats/h265/rh265_malformed_test.c b/samples/formats/h265/rh265_malformed_test.c new file mode 100644 index 00000000..adbc7434 --- /dev/null +++ b/samples/formats/h265/rh265_malformed_test.c @@ -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 +#include +#include +#include + +#include + +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; +} diff --git a/samples/formats/iec61937/Makefile b/samples/formats/iec61937/Makefile index 6dd00114..35baceea 100644 --- a/samples/formats/iec61937/Makefile +++ b/samples/formats/iec61937/Makefile @@ -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) diff --git a/samples/formats/image_blit_bands_test/Makefile b/samples/formats/image_blit_bands_test/Makefile index 160e40cb..2299cadb 100644 --- a/samples/formats/image_blit_bands_test/Makefile +++ b/samples/formats/image_blit_bands_test/Makefile @@ -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 diff --git a/samples/formats/image_hdr_blit/Makefile b/samples/formats/image_hdr_blit/Makefile index 48d8123f..7b968f6f 100644 --- a/samples/formats/image_hdr_blit/Makefile +++ b/samples/formats/image_hdr_blit/Makefile @@ -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 diff --git a/samples/formats/image_loader/Makefile b/samples/formats/image_loader/Makefile new file mode 100644 index 00000000..b856cc39 --- /dev/null +++ b/samples/formats/image_loader/Makefile @@ -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 diff --git a/samples/formats/image_loader/image_loader_test.c b/samples/formats/image_loader/image_loader_test.c new file mode 100644 index 00000000..6ff462dd --- /dev/null +++ b/samples/formats/image_loader/image_loader_test.c @@ -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 +#include +#include +#include + +#include +#include +#include + +#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; +} diff --git a/samples/formats/jpeg/Makefile b/samples/formats/jpeg/Makefile index 9188bd9e..27188cf1 100644 --- a/samples/formats/jpeg/Makefile +++ b/samples/formats/jpeg/Makefile @@ -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 diff --git a/samples/formats/json/Makefile b/samples/formats/json/Makefile index d4e15c80..d4789260 100644 --- a/samples/formats/json/Makefile +++ b/samples/formats/json/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := rjson_test LIBRETRO_COMM_DIR := ../../.. @@ -24,7 +26,7 @@ SOURCES := \ $(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 -I$(LIBRETRO_COMM_DIR)/include @@ -43,10 +45,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 diff --git a/samples/formats/m3u/Makefile b/samples/formats/m3u/Makefile index 3d1f931f..e8618fe1 100644 --- a/samples/formats/m3u/Makefile +++ b/samples/formats/m3u/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := rm3u_test LIBRETRO_M3U_DIR := ../../../formats/m3u @@ -18,7 +20,7 @@ SOURCES := \ $(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 -I$(LIBRETRO_COMM_DIR)/include @@ -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 diff --git a/samples/formats/mp4/Makefile b/samples/formats/mp4/Makefile new file mode 100644 index 00000000..91ad071b --- /dev/null +++ b/samples/formats/mp4/Makefile @@ -0,0 +1,39 @@ +include ../../sample_objdir.mk + +TARGET := rmp4_fragment_test + +LIBRETRO_COMM_DIR := ../../.. + +SOURCES := \ + rmp4_fragment_test.c \ + $(LIBRETRO_COMM_DIR)/formats/mp4/rmp4.c + +CFLAGS += -Wall -std=gnu99 -g -O1 -DHAVE_RMP4 -I$(LIBRETRO_COMM_DIR)/include + +ifneq ($(SANITIZER),) + CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS) + LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS) +endif + +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) + +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 + +check: $(TARGET) + ./$(TARGET) + +clean: + rm -rf $(SAMPLE_OBJDIR) + rm -f $(TARGET) $(TARGET).exe $(OBJS) + +.PHONY: all check clean diff --git a/samples/formats/mp4/rmp4_fragment_test.c b/samples/formats/mp4/rmp4_fragment_test.c new file mode 100644 index 00000000..32a22056 --- /dev/null +++ b/samples/formats/mp4/rmp4_fragment_test.c @@ -0,0 +1,204 @@ +/* formats/mp4/rmp4.c's movie fragments (moof/traf/trun), against + * fragmented MP4s built here byte by byte: + * + * - valid: two samples addressed through a trun data offset come + * back as packets holding exactly their bytes; + * - wrap: a tfhd base-data-offset near 2^64 whose sample end wraps + * round to inside the file gives no packet pointing outside it; + * - countless: a trun declaring 2^32-1 samples of no size, with no + * per-sample fields, opens at once rather than walking them all. + * + * Plain C and the C library only. */ + +#include +#include +#include +#include + +#include + +static int failures; + +#define CHECK(cond, ...) do { if (!(cond)) { \ + printf("FAIL: " __VA_ARGS__); printf("\n"); failures++; } } while (0) + +/* ---- a box writer ---- */ +typedef struct { uint8_t b[4096]; size_t n; } buf_t; + +static void put8(buf_t *w, unsigned v) { w->b[w->n++] = (uint8_t)v; } +static void put32(buf_t *w, uint32_t v) +{ put8(w, v >> 24); put8(w, v >> 16); put8(w, v >> 8); put8(w, v); } +static void put64(buf_t *w, uint64_t v) +{ put32(w, (uint32_t)(v >> 32)); put32(w, (uint32_t)v); } +static void putz(buf_t *w, size_t n) { while (n--) put8(w, 0); } +static void put4cc(buf_t *w, const char *s) +{ put8(w, s[0]); put8(w, s[1]); put8(w, s[2]); put8(w, s[3]); } + +/* Opens a box; box_end() writes its size */ +static size_t box(buf_t *w, const char *type) +{ size_t at = w->n; put32(w, 0); put4cc(w, type); return at; } +static void box_end(buf_t *w, size_t at) +{ + uint32_t sz = (uint32_t)(w->n - at); + w->b[at] = sz >> 24; w->b[at + 1] = sz >> 16; + w->b[at + 2] = sz >> 8; w->b[at + 3] = sz; +} + +/* ftyp + moov for one audio track (ID 1, timescale 1000) with an empty + * sample table and trex defaults of @dflt_size bytes, @dflt_dur ticks */ +static void head(buf_t *w, uint32_t dflt_size, uint32_t dflt_dur) +{ + size_t moov, trak, mdia, minf, stbl, b, mvex; + b = box(w, "ftyp"); put4cc(w, "iso6"); put32(w, 0); put4cc(w, "iso6"); + box_end(w, b); + moov = box(w, "moov"); + b = box(w, "mvhd"); putz(w, 12); put32(w, 1000); put32(w, 0); putz(w, 80); + box_end(w, b); + trak = box(w, "trak"); + b = box(w, "tkhd"); putz(w, 12); put32(w, 1); putz(w, 68); box_end(w, b); + mdia = box(w, "mdia"); + b = box(w, "mdhd"); putz(w, 12); put32(w, 1000); putz(w, 8); box_end(w, b); + b = box(w, "hdlr"); putz(w, 8); put4cc(w, "soun"); putz(w, 13); + box_end(w, b); + minf = box(w, "minf"); + stbl = box(w, "stbl"); + b = box(w, "stsd"); putz(w, 8); box_end(w, b); + box_end(w, stbl); box_end(w, minf); box_end(w, mdia); box_end(w, trak); + mvex = box(w, "mvex"); + b = box(w, "trex"); putz(w, 4); put32(w, 1); put32(w, 1); + put32(w, dflt_dur); put32(w, dflt_size); put32(w, 0); box_end(w, b); + box_end(w, mvex); + box_end(w, moov); +} + +/* Every packet the demuxer gives, checked to lie inside the file */ +static int read_all(rmp4_t *m, const buf_t *w, const char *name, + const uint8_t **first) +{ + rmp4_packet pkt; + int n = 0, r; + while ((r = rmp4_read_packet(m, &pkt)) == 1) + { + CHECK(pkt.data >= w->b && pkt.size <= w->n + && (size_t)(pkt.data - w->b) <= w->n - pkt.size, + "%s: packet %d points outside the file", name, n); + if (!n && first) + *first = pkt.data; + if (++n > 16) + break; + } + return n; +} + +static void test_valid(void) +{ + buf_t w; + size_t moof, traf, b, trun_doff, mdat_payload; + const uint8_t *first = NULL; + rmp4_t *m; + int n; + + memset(&w, 0, sizeof(w)); + head(&w, 0, 10); + moof = box(&w, "moof"); + b = box(&w, "mfhd"); putz(&w, 4); put32(&w, 1); box_end(&w, b); + traf = box(&w, "traf"); + b = box(&w, "tfhd"); put32(&w, 0); put32(&w, 1); box_end(&w, b); + b = box(&w, "trun"); put32(&w, 0x000201); put32(&w, 2); + trun_doff = w.n; put32(&w, 0); /* data offset, below */ + put32(&w, 4); put32(&w, 4); /* sample sizes */ + box_end(&w, b); + box_end(&w, traf); box_end(&w, moof); + b = box(&w, "mdat"); + mdat_payload = w.n; + put32(&w, 0x11223344); put32(&w, 0x55667788); + box_end(&w, b); + /* the offset is from the moof's start */ + { + uint32_t d = (uint32_t)(mdat_payload - moof); + w.b[trun_doff] = d >> 24; w.b[trun_doff + 1] = d >> 16; + w.b[trun_doff + 2] = d >> 8; w.b[trun_doff + 3] = d; + } + + m = rmp4_open_memory(w.b, w.n); + CHECK(m != NULL, "valid: opens"); + if (!m) + return; + n = read_all(m, &w, "valid", &first); + CHECK(n == 2, "valid: %d packets, not 2", n); + CHECK(first == w.b + mdat_payload, "valid: the first packet is the mdat's"); + rmp4_close(m); + printf("valid: ok\n"); +} + +static void test_wrap(void) +{ + buf_t w; + size_t moof, traf, b; + rmp4_t *m; + + memset(&w, 0, sizeof(w)); + head(&w, 0, 10); + moof = box(&w, "moof"); + b = box(&w, "mfhd"); putz(&w, 4); put32(&w, 1); box_end(&w, b); + traf = box(&w, "traf"); + /* base-data-offset 2^64 - 16: plus a 32-byte sample, the end wraps + * to 16, inside the file */ + b = box(&w, "tfhd"); put32(&w, 0x000001); put32(&w, 1); + put64(&w, (uint64_t)0 - 16); box_end(&w, b); + b = box(&w, "trun"); put32(&w, 0x000200); put32(&w, 1); put32(&w, 32); + box_end(&w, b); + box_end(&w, traf); box_end(&w, moof); + b = box(&w, "mdat"); putz(&w, 64); box_end(&w, b); + + m = rmp4_open_memory(w.b, w.n); + if (m) + { + read_all(m, &w, "wrap", NULL); + rmp4_close(m); + } + printf("wrap: ok\n"); +} + +static void test_countless(void) +{ + buf_t w; + size_t moof, traf, b; + clock_t t0; + double secs; + rmp4_t *m; + + memset(&w, 0, sizeof(w)); + head(&w, 0, 1); + moof = box(&w, "moof"); + b = box(&w, "mfhd"); putz(&w, 4); put32(&w, 1); box_end(&w, b); + traf = box(&w, "traf"); + b = box(&w, "tfhd"); put32(&w, 0); put32(&w, 1); box_end(&w, b); + b = box(&w, "trun"); put32(&w, 0); put32(&w, 0xFFFFFFFFu); box_end(&w, b); + box_end(&w, traf); box_end(&w, moof); + + t0 = clock(); + m = rmp4_open_memory(w.b, w.n); + secs = (double)(clock() - t0) / CLOCKS_PER_SEC; + CHECK(secs < 1.0, "countless: open took %.1f s", secs); + if (m) + { + read_all(m, &w, "countless", NULL); + rmp4_close(m); + } + printf("countless: ok (%.3f s)\n", secs); +} + +int main(void) +{ + test_valid(); + test_wrap(); + test_countless(); + if (failures) + { + printf("%d failure(s)\n", failures); + return 1; + } + printf("rmp4_fragment: all passed\n"); + return 0; +} diff --git a/samples/formats/png/Makefile b/samples/formats/png/Makefile index 2038a2f4..cad0d57a 100644 --- a/samples/formats/png/Makefile +++ b/samples/formats/png/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := rpng TARGET_TEST := rpng_chunk_overflow_test TARGET_TEST2 := rpng_roundtrip_test @@ -131,11 +133,11 @@ TEST4_SOURCES_C := \ $(LIBRETRO_COMM_DIR)/streams/trans_stream_deflate.c \ $(LIBRETRO_COMM_DIR)/encodings/encoding_deflate.c -OBJS := $(SOURCES_C:.c=.o) -TEST4_OBJS := $(TEST4_SOURCES_C:.c=.o) -TEST_OBJS := $(TEST_SOURCES_C:.c=.o) -TEST2_OBJS := $(TEST2_SOURCES_C:.c=.o) -TEST3_OBJS := $(TEST3_SOURCES_C:.c=.corpus.o) +OBJS := $(call sample_objs,$(SOURCES_C:.c=.o)) +TEST4_OBJS := $(call sample_objs,$(TEST4_SOURCES_C:.c=.o)) +TEST_OBJS := $(call sample_objs,$(TEST_SOURCES_C:.c=.o)) +TEST2_OBJS := $(call sample_objs,$(TEST2_SOURCES_C:.c=.o)) +TEST3_OBJS := $(call sample_objs,$(TEST3_SOURCES_C:.c=.corpus.o)) CFLAGS += -Wall -pedantic -std=gnu99 -O0 -g -I$(LIBRETRO_COMM_DIR)/include @@ -157,9 +159,17 @@ all: $(TARGET) $(TARGET_TEST) $(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) + %.corpus.o: %.c $(CC) -c -o $@ $< $(CORPUS_CFLAGS) +$(SAMPLE_OBJDIR)/%.corpus.o: $$(call sample_src,$$*).c + @mkdir -p $(@D) + $(CC) -c -o $@ $< $(CORPUS_CFLAGS) + $(TARGET): $(OBJS) $(CC) -o $@ $^ $(LDFLAGS) @@ -176,6 +186,7 @@ $(TARGET_TEST4): $(TEST4_OBJS) $(CC) -o $@ $^ $(LDFLAGS) clean: + rm -rf $(SAMPLE_OBJDIR) rm -f $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) $(TARGET_TEST4) \ $(OBJS) $(TEST_OBJS) $(TEST2_OBJS) $(TEST3_OBJS) $(TEST4_OBJS) diff --git a/samples/formats/r7z/Makefile b/samples/formats/r7z/Makefile index 17ff325b..70bfb74a 100644 --- a/samples/formats/r7z/Makefile +++ b/samples/formats/r7z/Makefile @@ -1,4 +1,7 @@ -TARGET := r7z_lzma_props_test +include ../../sample_objdir.mk + +TARGET := r7z_lzma_props_test +TARGET_TEST2 := r7z_archive_detach_test LIBRETRO_COMM_DIR := ../../.. @@ -6,7 +9,20 @@ SOURCES := \ r7z_lzma_props_test.c \ $(LIBRETRO_COMM_DIR)/formats/7z/r7z_lzma.c -OBJS := $(SOURCES:.c=.o) +ARCHIVE_SOURCES := \ + r7z_archive_detach_test.c \ + $(LIBRETRO_COMM_DIR)/encodings/encoding_crc32.c \ + $(LIBRETRO_COMM_DIR)/features/features_cpu.c \ + $(LIBRETRO_COMM_DIR)/time/rtime.c \ + $(LIBRETRO_COMM_DIR)/formats/7z/r7z_archive.c \ + $(LIBRETRO_COMM_DIR)/formats/7z/r7z_bcj2.c \ + $(LIBRETRO_COMM_DIR)/formats/7z/r7z_filters.c \ + $(LIBRETRO_COMM_DIR)/formats/7z/r7z_lzma.c \ + $(LIBRETRO_COMM_DIR)/formats/7z/r7z_lzma2.c \ + $(LIBRETRO_COMM_DIR)/formats/7z/r7z_lzma_stream.c + +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) +ARCHIVE_OBJS := $(call sample_objs,$(ARCHIVE_SOURCES:.c=.o)) CFLAGS += -Wall -pedantic -std=gnu99 -g -I$(LIBRETRO_COMM_DIR)/include @@ -15,18 +31,27 @@ ifneq ($(SANITIZER),) LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS) endif -all: $(TARGET) +all: $(TARGET) $(TARGET_TEST2) %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ +$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c + @mkdir -p $(@D) + $(CC) $(CFLAGS) -c $< -o $@ + $(TARGET): $(OBJS) $(CC) $(OBJS) $(LDFLAGS) -o $@ -check: $(TARGET) +$(TARGET_TEST2): $(ARCHIVE_OBJS) + $(CC) $(ARCHIVE_OBJS) $(LDFLAGS) -o $@ + +check: $(TARGET) $(TARGET_TEST2) ./$(TARGET) + ./$(TARGET_TEST2) clean: - rm -f $(TARGET) $(OBJS) + rm -rf $(SAMPLE_OBJDIR) + rm -f $(TARGET) $(TARGET_TEST2) $(OBJS) $(ARCHIVE_OBJS) .PHONY: all check clean diff --git a/samples/formats/r7z/r7z_archive_detach_test.c b/samples/formats/r7z/r7z_archive_detach_test.c new file mode 100644 index 00000000..c70e1cf3 --- /dev/null +++ b/samples/formats/r7z/r7z_archive_detach_test.c @@ -0,0 +1,444 @@ +/* r7z_archive_extract_detach() and r7z_archive_entry_borrow() against a + * non-solid and a solid archive. + * + * Both archives hold the same two members, a.bin (3000 bytes) and + * b.bin (2000 bytes), with contents a test can regenerate: + * + * a[i] = ((i * 2654435761) >> 13) & 0xff + * b[i] = (i * 7) % 251 + * + * In the non-solid archive (7z a -ms=off -m0=lzma2) each member is its + * folder's whole output, so detach hands the decoded folder itself + * over and leaves nothing cached: a second extract of the same member + * must decode it again rather than read a buffer that was given away. + * In the solid archive (7z a -ms=on -m0=lzma) both members share one + * folder, so detach has to fall back to a copy and keep the folder + * cached for the member after it. Borrowing hands out pointers into + * that cache: in the solid archive both members can be held at once + * and a detach of one of them (a copy, there) leaves them valid; in the + * non-solid archive a borrow after a detach decodes the folder again. + * + * Build: make -C libretro-common/samples/formats/r7z check + */ + +#include +#include +#include +#include + +#include <7z/r7z_archive.h> + +#define A_LEN 3000 +#define B_LEN 2000 + +static const uint8_t nonsolid_7z[985] = +{ + 0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c, 0x00, 0x04, 0x3a, 0xae, 0x52, 0x8a, + 0x98, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x39, 0xe1, 0x62, 0x3e, 0xe0, 0x0b, 0xb7, 0x02, + 0x29, 0x5d, 0x00, 0x00, 0x2f, 0x57, 0xac, 0xe2, 0x6c, 0x03, 0x0c, 0x9d, + 0xa5, 0x57, 0x92, 0xd3, 0x6f, 0x69, 0xa0, 0x95, 0x12, 0xba, 0xb6, 0x2c, + 0xd7, 0x0b, 0xcd, 0xbd, 0x9c, 0x69, 0x8d, 0xfb, 0x15, 0x89, 0x4c, 0x7f, + 0xfc, 0x49, 0x76, 0xd4, 0x3b, 0x7f, 0xcc, 0xb3, 0xad, 0x0f, 0xcd, 0x7c, + 0xac, 0x55, 0x24, 0x55, 0xe1, 0x1a, 0xd5, 0xd4, 0x18, 0x5c, 0x11, 0xde, + 0x3e, 0x23, 0x6c, 0xed, 0xbf, 0xfb, 0xbb, 0xe9, 0x71, 0x68, 0x1e, 0x3c, + 0x13, 0x01, 0x1e, 0xe6, 0x2b, 0x71, 0x05, 0x4a, 0xb4, 0xe5, 0x0b, 0x04, + 0x06, 0x69, 0x84, 0x89, 0x8c, 0x6f, 0x36, 0xdd, 0x81, 0x32, 0x27, 0xe4, + 0x11, 0xa4, 0x75, 0xcb, 0x89, 0xeb, 0x82, 0x97, 0x5a, 0xd3, 0x95, 0x39, + 0x09, 0x6e, 0xc6, 0xe7, 0xb5, 0xe5, 0xac, 0xee, 0x57, 0x47, 0xfc, 0x79, + 0xe5, 0x0a, 0x62, 0x8d, 0x33, 0x8e, 0x7f, 0x59, 0x0c, 0x34, 0x78, 0x62, + 0x0f, 0x4f, 0xcb, 0x44, 0x44, 0xb8, 0xc7, 0x2f, 0xec, 0xfe, 0x7a, 0xf5, + 0x37, 0xd4, 0x67, 0x3b, 0x74, 0xdf, 0xc2, 0x0e, 0x2d, 0x96, 0xb4, 0x26, + 0x5f, 0x3f, 0x8f, 0x7a, 0x27, 0xfc, 0x29, 0xf6, 0x8a, 0xb5, 0xde, 0x19, + 0x20, 0x06, 0x9c, 0xd2, 0x2e, 0xc5, 0x2d, 0x02, 0x24, 0x6b, 0x49, 0x28, + 0x00, 0x5d, 0x83, 0xcf, 0x2e, 0x69, 0xe5, 0x4c, 0x4b, 0x73, 0xa6, 0x2e, + 0x2d, 0x0b, 0x5a, 0x59, 0x5b, 0x1d, 0x50, 0x98, 0x17, 0x63, 0xe8, 0x05, + 0xa1, 0x10, 0x90, 0xba, 0x8b, 0x03, 0x4b, 0x91, 0x97, 0x29, 0xf3, 0x64, + 0x5c, 0x4b, 0x85, 0x11, 0x91, 0x8b, 0x08, 0x50, 0x95, 0x91, 0x5d, 0xbc, + 0xa3, 0x6a, 0x30, 0xe0, 0x1c, 0xa5, 0xb9, 0x55, 0x7b, 0xf3, 0x73, 0xc6, + 0xd3, 0x87, 0x1d, 0xff, 0x57, 0x16, 0xaf, 0x62, 0x03, 0xc9, 0x0a, 0x25, + 0xde, 0x03, 0x10, 0x48, 0x0a, 0xb0, 0x21, 0x6e, 0x03, 0xfc, 0x30, 0xd1, + 0x85, 0xe7, 0x59, 0x04, 0x66, 0x62, 0xb5, 0x60, 0xaa, 0x81, 0x9a, 0x8b, + 0x01, 0xf4, 0xa6, 0x9f, 0xc1, 0x71, 0x52, 0x49, 0x68, 0x8b, 0xed, 0x66, + 0x4b, 0x14, 0x53, 0x0a, 0x6f, 0x06, 0x7c, 0x41, 0xcb, 0xde, 0x5e, 0xdd, + 0x82, 0xa0, 0x40, 0x6b, 0x0c, 0xe2, 0x4e, 0xcd, 0xc7, 0x18, 0xd4, 0x31, + 0x00, 0xb9, 0xde, 0xf5, 0x77, 0x14, 0x59, 0x49, 0x92, 0x50, 0x36, 0x1c, + 0x06, 0xee, 0x95, 0x7f, 0xbd, 0xe8, 0x1d, 0x8c, 0x5d, 0x88, 0x17, 0xf3, + 0x60, 0x52, 0x2f, 0xb4, 0xa6, 0x0a, 0x20, 0x29, 0x10, 0x01, 0x6e, 0x73, + 0x56, 0xbf, 0x2e, 0xeb, 0x87, 0x40, 0x82, 0x75, 0x9b, 0x9a, 0xa0, 0x2a, + 0x67, 0x9b, 0x32, 0xe5, 0x95, 0xe5, 0xc4, 0x0a, 0x02, 0xfe, 0x99, 0x66, + 0xcf, 0xf4, 0xf2, 0x01, 0xba, 0xd0, 0x96, 0xa6, 0x95, 0xfc, 0x73, 0x14, + 0xe2, 0xa8, 0x84, 0x10, 0x20, 0x5b, 0x79, 0xe3, 0xdd, 0x13, 0x4a, 0x9d, + 0x2d, 0xc2, 0x4c, 0x5b, 0x3f, 0x52, 0x9e, 0x8e, 0x3a, 0x04, 0x67, 0x0e, + 0x41, 0xeb, 0xc9, 0x49, 0x1c, 0x8d, 0xc0, 0xac, 0x37, 0x23, 0xa4, 0xd0, + 0x30, 0x4e, 0x29, 0xc5, 0xc7, 0x00, 0x7b, 0x29, 0xe3, 0xe7, 0x33, 0x29, + 0xd5, 0x15, 0xe7, 0x43, 0x49, 0x10, 0x57, 0xbb, 0xf6, 0x72, 0xad, 0xe7, + 0x30, 0x20, 0xdc, 0xfc, 0x0c, 0x43, 0x3e, 0x66, 0x00, 0x9e, 0xd5, 0xdc, + 0x04, 0xec, 0x04, 0x19, 0x28, 0x20, 0xd1, 0xc0, 0x1d, 0x15, 0xd5, 0x4f, + 0xc5, 0x0b, 0x1d, 0xbc, 0xb7, 0x04, 0x0d, 0xb2, 0x2f, 0xf6, 0xc8, 0x25, + 0x5e, 0x01, 0x5f, 0xf5, 0x92, 0x39, 0xa1, 0xa7, 0xfb, 0xbd, 0x8c, 0x91, + 0x0c, 0xa7, 0x53, 0x1d, 0x42, 0x36, 0x86, 0x92, 0x6c, 0xb4, 0x5d, 0x7a, + 0xa1, 0xf5, 0x7b, 0x9e, 0x43, 0x3f, 0xd9, 0x0d, 0x0d, 0x33, 0xe7, 0x2d, + 0x2c, 0xce, 0xbb, 0x65, 0x56, 0x33, 0x9c, 0x9c, 0xa8, 0x68, 0x58, 0x08, + 0x1a, 0x94, 0xa1, 0x9b, 0x0d, 0x8a, 0xd8, 0x9a, 0x63, 0x36, 0x8e, 0x2a, + 0xbe, 0x55, 0x68, 0x7f, 0xfe, 0x82, 0x0b, 0x94, 0x19, 0xab, 0xbe, 0x37, + 0x08, 0x83, 0x74, 0x00, 0x00, 0xe0, 0x07, 0xcf, 0x00, 0xfc, 0x5d, 0x00, + 0x00, 0x02, 0x0f, 0x57, 0x02, 0x68, 0xc6, 0x78, 0xce, 0xd8, 0x0f, 0x90, + 0xe6, 0xeb, 0xb6, 0xdd, 0x1f, 0x70, 0x62, 0xb0, 0x21, 0x27, 0x14, 0xf9, + 0xb1, 0x95, 0x8a, 0x58, 0x60, 0x21, 0x7a, 0x2c, 0xac, 0xe7, 0x77, 0x98, + 0xdf, 0x45, 0x86, 0xb1, 0x88, 0xdf, 0xf8, 0xf7, 0x05, 0x2d, 0xd5, 0x35, + 0xf1, 0x7a, 0xfa, 0x80, 0xad, 0xbb, 0xe5, 0xd1, 0xb2, 0xba, 0xc3, 0x8a, + 0xaa, 0xe4, 0x11, 0x31, 0x7b, 0xe1, 0x6b, 0xce, 0x4e, 0xff, 0xa1, 0x38, + 0x2b, 0xb9, 0x9b, 0x2a, 0xc6, 0x70, 0x70, 0xa5, 0x63, 0x2c, 0x9f, 0xf0, + 0x10, 0x0e, 0xbe, 0x8d, 0x03, 0xd6, 0x0c, 0xbe, 0x64, 0xbb, 0xaf, 0xc8, + 0x45, 0x04, 0x53, 0x97, 0x31, 0xb9, 0xb5, 0xac, 0xed, 0xec, 0x16, 0x3b, + 0x79, 0x30, 0xbc, 0x22, 0xc6, 0x54, 0xcf, 0xe9, 0x5e, 0x30, 0x49, 0x05, + 0x9d, 0xcb, 0x5d, 0xbf, 0x0b, 0x24, 0x72, 0x49, 0x9b, 0x7a, 0x81, 0x88, + 0x93, 0x4d, 0x9f, 0x69, 0x43, 0x3f, 0x0f, 0xe5, 0xd5, 0x35, 0xfa, 0x96, + 0x31, 0xb3, 0x62, 0xd6, 0x2d, 0x48, 0x7a, 0xe3, 0x28, 0x20, 0xb9, 0x16, + 0xac, 0x46, 0x87, 0xd0, 0x7e, 0xa5, 0xe6, 0x40, 0x5c, 0x4b, 0xc8, 0xea, + 0xe4, 0xe6, 0xe9, 0x2e, 0x13, 0xa3, 0x15, 0x2a, 0x6c, 0x47, 0x1c, 0x36, + 0x58, 0x57, 0xf6, 0x03, 0x97, 0xc8, 0x04, 0x65, 0x3c, 0xed, 0x87, 0xbb, + 0xa3, 0x5e, 0xe5, 0xcb, 0x29, 0x00, 0x63, 0x1f, 0xa5, 0x71, 0x81, 0xf7, + 0xb3, 0xe3, 0xae, 0x27, 0x2c, 0x29, 0x8d, 0xe2, 0xcd, 0xcc, 0x2b, 0x42, + 0xeb, 0x7b, 0xc7, 0xff, 0x3a, 0x1a, 0xd0, 0xa8, 0x93, 0xe5, 0xd7, 0xc2, + 0xf4, 0xe7, 0x2e, 0xd0, 0x4c, 0xc9, 0x8a, 0x91, 0x04, 0x62, 0x68, 0x55, + 0x0a, 0xb5, 0x28, 0xf9, 0x53, 0xf7, 0x37, 0xf5, 0xd3, 0x5a, 0x7e, 0x00, + 0x00, 0x00, 0x00, 0x81, 0x33, 0x07, 0xae, 0x31, 0x9e, 0xed, 0x58, 0xf2, + 0x03, 0xf3, 0xf6, 0x75, 0x18, 0x40, 0x4d, 0x9d, 0x63, 0x6a, 0x22, 0x2f, + 0xfa, 0x58, 0xac, 0xd6, 0xe6, 0x87, 0x02, 0x4d, 0xdd, 0xe8, 0xef, 0xa0, + 0xa3, 0xb0, 0x05, 0xed, 0x9b, 0x75, 0xe5, 0xa1, 0x6e, 0x7e, 0xea, 0xfe, + 0xdc, 0xf7, 0x40, 0x40, 0x5e, 0xbf, 0xfc, 0x66, 0x6d, 0x6c, 0x5c, 0xf3, + 0x6a, 0xb9, 0x53, 0x24, 0x9c, 0x3a, 0xac, 0x79, 0x4e, 0x1b, 0x25, 0x86, + 0xd7, 0x4b, 0x8f, 0x14, 0x68, 0xf6, 0x21, 0x2f, 0xf5, 0xcf, 0xc0, 0xe3, + 0x5f, 0x3e, 0x15, 0x3a, 0x8b, 0xf2, 0xdd, 0xb5, 0x3f, 0x89, 0x1c, 0x9c, + 0xd0, 0x00, 0x00, 0x00, 0x17, 0x06, 0x83, 0x35, 0x01, 0x09, 0x63, 0x00, + 0x07, 0x0b, 0x01, 0x00, 0x01, 0x23, 0x03, 0x01, 0x01, 0x05, 0x5d, 0x00, + 0x10, 0x00, 0x00, 0x0c, 0x7e, 0x0a, 0x01, 0x9d, 0x0d, 0x3d, 0x0f, 0x00, + 0x00, +}; + +static const uint8_t solid_7z[977] = +{ + 0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c, 0x00, 0x04, 0x99, 0xb0, 0x7d, 0xed, + 0x90, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x22, 0x39, 0x41, 0xd4, 0x00, 0x00, 0x2f, 0x57, + 0xac, 0xe2, 0x6c, 0x03, 0x0c, 0x9d, 0xa5, 0x57, 0x92, 0xd3, 0x6f, 0x69, + 0xa0, 0x95, 0x12, 0xba, 0xb6, 0x2c, 0xd7, 0x0b, 0xcd, 0xbd, 0x9c, 0x69, + 0x8d, 0xfb, 0x15, 0x89, 0x4c, 0x7f, 0xfc, 0x49, 0x76, 0xd4, 0x3b, 0x7f, + 0xcc, 0xb3, 0xad, 0x0f, 0xcd, 0x7c, 0xac, 0x55, 0x24, 0x55, 0xe1, 0x1a, + 0xd5, 0xd4, 0x18, 0x5c, 0x11, 0xde, 0x3e, 0x23, 0x6c, 0xed, 0xbf, 0xfb, + 0xbb, 0xe9, 0x71, 0x68, 0x1e, 0x3c, 0x13, 0x01, 0x1e, 0xe6, 0x2b, 0x71, + 0x05, 0x4a, 0xb4, 0xe5, 0x0b, 0x04, 0x06, 0x69, 0x84, 0x89, 0x8c, 0x6f, + 0x36, 0xdd, 0x81, 0x32, 0x27, 0xe4, 0x11, 0xa4, 0x75, 0xcb, 0x89, 0xeb, + 0x82, 0x97, 0x5a, 0xd3, 0x95, 0x39, 0x09, 0x6e, 0xc6, 0xe7, 0xb5, 0xe5, + 0xac, 0xee, 0x57, 0x47, 0xfc, 0x79, 0xe5, 0x0a, 0x62, 0x8d, 0x33, 0x8e, + 0x7f, 0x59, 0x0c, 0x34, 0x78, 0x62, 0x0f, 0x4f, 0xcb, 0x44, 0x44, 0xb8, + 0xc7, 0x2f, 0xec, 0xfe, 0x7a, 0xf5, 0x37, 0xd4, 0x67, 0x3b, 0x74, 0xdf, + 0xc2, 0x0e, 0x2d, 0x96, 0xb4, 0x26, 0x5f, 0x3f, 0x8f, 0x7a, 0x27, 0xfc, + 0x29, 0xf6, 0x8a, 0xb5, 0xde, 0x19, 0x20, 0x06, 0x9c, 0xd2, 0x2e, 0xc5, + 0x2d, 0x02, 0x24, 0x6b, 0x49, 0x28, 0x00, 0x5d, 0x83, 0xcf, 0x2e, 0x69, + 0xe5, 0x4c, 0x4b, 0x73, 0xa6, 0x2e, 0x2d, 0x0b, 0x5a, 0x59, 0x5b, 0x1d, + 0x50, 0x98, 0x17, 0x63, 0xe8, 0x05, 0xa1, 0x10, 0x90, 0xba, 0x8b, 0x03, + 0x4b, 0x91, 0x97, 0x29, 0xf3, 0x64, 0x5c, 0x4b, 0x85, 0x11, 0x91, 0x8b, + 0x08, 0x50, 0x95, 0x91, 0x5d, 0xbc, 0xa3, 0x6a, 0x30, 0xe0, 0x1c, 0xa5, + 0xb9, 0x55, 0x7b, 0xf3, 0x73, 0xc6, 0xd3, 0x87, 0x1d, 0xff, 0x57, 0x16, + 0xaf, 0x62, 0x03, 0xc9, 0x0a, 0x25, 0xde, 0x03, 0x10, 0x48, 0x0a, 0xb0, + 0x21, 0x6e, 0x03, 0xfc, 0x30, 0xd1, 0x85, 0xe7, 0x59, 0x04, 0x66, 0x62, + 0xb5, 0x60, 0xaa, 0x81, 0x9a, 0x8b, 0x01, 0xf4, 0xa6, 0x9f, 0xc1, 0x71, + 0x52, 0x49, 0x68, 0x8b, 0xed, 0x66, 0x4b, 0x14, 0x53, 0x0a, 0x6f, 0x06, + 0x7c, 0x41, 0xcb, 0xde, 0x5e, 0xdd, 0x82, 0xa0, 0x40, 0x6b, 0x0c, 0xe2, + 0x4e, 0xcd, 0xc7, 0x18, 0xd4, 0x31, 0x00, 0xb9, 0xde, 0xf5, 0x77, 0x14, + 0x59, 0x49, 0x92, 0x50, 0x36, 0x1c, 0x06, 0xee, 0x95, 0x7f, 0xbd, 0xe8, + 0x1d, 0x8c, 0x5d, 0x88, 0x17, 0xf3, 0x60, 0x52, 0x2f, 0xb4, 0xa6, 0x0a, + 0x20, 0x29, 0x10, 0x01, 0x6e, 0x73, 0x56, 0xbf, 0x2e, 0xeb, 0x87, 0x40, + 0x82, 0x75, 0x9b, 0x9a, 0xa0, 0x2a, 0x67, 0x9b, 0x32, 0xe5, 0x95, 0xe5, + 0xc4, 0x0a, 0x02, 0xfe, 0x99, 0x66, 0xcf, 0xf4, 0xf2, 0x01, 0xba, 0xd0, + 0x96, 0xa6, 0x95, 0xfc, 0x73, 0x14, 0xe2, 0xa8, 0x84, 0x10, 0x20, 0x5b, + 0x79, 0xe3, 0xdd, 0x13, 0x4a, 0x9d, 0x2d, 0xc2, 0x4c, 0x5b, 0x3f, 0x52, + 0x9e, 0x8e, 0x3a, 0x04, 0x67, 0x0e, 0x41, 0xeb, 0xc9, 0x49, 0x1c, 0x8d, + 0xc0, 0xac, 0x37, 0x23, 0xa4, 0xd0, 0x30, 0x4e, 0x29, 0xc5, 0xc7, 0x00, + 0x7b, 0x29, 0xe3, 0xe7, 0x33, 0x29, 0xd5, 0x15, 0xe7, 0x43, 0x49, 0x10, + 0x57, 0xbb, 0xf6, 0x72, 0xad, 0xe7, 0x30, 0x20, 0xdc, 0xfc, 0x0c, 0x43, + 0x3e, 0x66, 0x00, 0x9e, 0xd5, 0xdc, 0x04, 0xec, 0x04, 0x19, 0x28, 0x20, + 0xd1, 0xc0, 0x1d, 0x15, 0xd5, 0x4f, 0xc5, 0x0b, 0x1d, 0xbc, 0xb7, 0x04, + 0x0d, 0xb2, 0x2f, 0xf6, 0xc8, 0x25, 0x5e, 0x01, 0x5f, 0xf5, 0x92, 0x39, + 0xa1, 0xa7, 0xfb, 0xbd, 0x8c, 0x91, 0x0c, 0xa7, 0x53, 0x1d, 0x42, 0x36, + 0x86, 0x92, 0x6c, 0xb4, 0x5d, 0x7a, 0xa1, 0xf5, 0x7b, 0x9e, 0x43, 0x3f, + 0xd9, 0x0d, 0x0d, 0x33, 0xe7, 0x2d, 0x2c, 0xce, 0xbb, 0x65, 0x56, 0x33, + 0x9c, 0x9c, 0xa8, 0x68, 0x58, 0x08, 0x1a, 0x94, 0xa1, 0x9b, 0x0d, 0x8a, + 0xd8, 0x9a, 0x63, 0x36, 0x8e, 0x2a, 0xbe, 0x55, 0x68, 0x7f, 0xfe, 0x82, + 0x0b, 0x94, 0x19, 0xab, 0xbe, 0x37, 0x08, 0x83, 0x74, 0x78, 0xfb, 0x8e, + 0x85, 0xcb, 0x69, 0x2c, 0x75, 0x79, 0x89, 0x20, 0x83, 0x3e, 0xd5, 0x3b, + 0x9f, 0x48, 0xaa, 0xeb, 0x93, 0x2f, 0x6c, 0x7d, 0xbf, 0x41, 0x20, 0x94, + 0x7d, 0x95, 0x4a, 0x25, 0x75, 0x24, 0xe5, 0xa9, 0xca, 0x1f, 0xc5, 0x18, + 0x53, 0x5e, 0x51, 0x78, 0x57, 0x0c, 0xf2, 0x33, 0xd8, 0x2d, 0xa2, 0x51, + 0x32, 0xe5, 0x25, 0x9c, 0x3b, 0xf7, 0x37, 0xb6, 0xf1, 0xab, 0xd9, 0xa4, + 0x9f, 0xde, 0x07, 0xc1, 0x1d, 0x13, 0x79, 0x05, 0x02, 0x6a, 0xe9, 0x1a, + 0x0a, 0xe0, 0xb1, 0xa1, 0x9c, 0x5b, 0xd6, 0x28, 0x4d, 0xd3, 0x36, 0xac, + 0x18, 0x26, 0x88, 0x4e, 0xa6, 0xf8, 0x0c, 0xbf, 0x6f, 0xc1, 0xe9, 0xd0, + 0x8e, 0x3e, 0x0d, 0x66, 0xc8, 0x71, 0xe8, 0x20, 0x74, 0x88, 0xbe, 0x5c, + 0x29, 0xc5, 0x9a, 0x75, 0xbe, 0x58, 0x04, 0xc7, 0x2b, 0x59, 0xd9, 0xf1, + 0xa3, 0x07, 0x95, 0x37, 0x94, 0xa2, 0x0f, 0x39, 0x3c, 0x13, 0xd7, 0x00, + 0x50, 0x25, 0xdf, 0xc1, 0xe3, 0x24, 0x9d, 0x2d, 0x8a, 0x62, 0x9d, 0x19, + 0x4d, 0x3e, 0xd4, 0x0c, 0x76, 0xb0, 0x3c, 0xff, 0xa6, 0xaa, 0xed, 0xad, + 0xef, 0xf0, 0xf2, 0xc4, 0x74, 0xd7, 0xd0, 0x32, 0x47, 0xf8, 0x72, 0x22, + 0x47, 0x74, 0x1c, 0xc6, 0x2b, 0x08, 0x31, 0x0d, 0xea, 0xc6, 0x75, 0x2e, + 0x4e, 0x64, 0x33, 0x8d, 0xef, 0x96, 0x95, 0xe1, 0x86, 0xc9, 0x46, 0x8f, + 0x0a, 0xc7, 0x08, 0x95, 0x55, 0x65, 0xbd, 0x36, 0xab, 0x11, 0xd5, 0xce, + 0x74, 0xda, 0xb4, 0x00, 0xd8, 0x9b, 0xe8, 0xcd, 0x12, 0x07, 0x7b, 0xde, + 0x3d, 0x5b, 0x05, 0x05, 0xf9, 0xdd, 0xe9, 0xcf, 0x5b, 0xca, 0x57, 0xce, + 0x54, 0xb2, 0xb1, 0xb2, 0x59, 0xa9, 0x98, 0x10, 0xf4, 0x7a, 0xaf, 0xfe, + 0x83, 0xcd, 0xc1, 0x6e, 0xc4, 0xdf, 0x3c, 0x5c, 0xef, 0x23, 0x5a, 0x00, + 0x00, 0x00, 0x81, 0x33, 0x07, 0xae, 0x0f, 0xd5, 0x4e, 0xb5, 0xbb, 0x17, + 0x24, 0xd3, 0xfe, 0xb3, 0x7e, 0x31, 0xe2, 0x96, 0x9b, 0xb0, 0xf2, 0x87, + 0xd8, 0xdf, 0x0c, 0xd2, 0xc2, 0x4c, 0x04, 0x7f, 0x03, 0x6c, 0x33, 0x96, + 0xdc, 0x7b, 0x62, 0xaa, 0xd7, 0x10, 0xfe, 0xfe, 0x74, 0x18, 0x43, 0xc2, + 0x1e, 0x13, 0x97, 0x72, 0xca, 0x1b, 0x8a, 0x8e, 0xb0, 0xbc, 0xf3, 0xbd, + 0xa4, 0x6c, 0xc8, 0x6a, 0x32, 0xa9, 0x82, 0x0d, 0xc9, 0x7b, 0x87, 0x97, + 0x3a, 0xc4, 0x45, 0xd9, 0xce, 0x6c, 0x9a, 0x4c, 0x28, 0x7b, 0x54, 0x07, + 0xad, 0x10, 0xe1, 0x4d, 0x29, 0x09, 0x11, 0x52, 0xa3, 0x85, 0x25, 0xda, + 0x85, 0xfc, 0x6e, 0x9b, 0xb8, 0x00, 0x00, 0x00, 0x17, 0x06, 0x83, 0x28, + 0x01, 0x09, 0x68, 0x00, 0x07, 0x0b, 0x01, 0x00, 0x01, 0x23, 0x03, 0x01, + 0x01, 0x05, 0x5d, 0x00, 0x10, 0x00, 0x00, 0x0c, 0x7e, 0x0a, 0x01, 0x03, + 0xeb, 0x1d, 0x2a, 0x00, 0x00, +}; + +static int failures = 0; +static int checks = 0; + +static void check(int ok, const char *what) +{ + checks++; + printf(" %-5s %s\n", ok ? "ok" : "FAIL", what); + if (!ok) + failures++; +} + +static void fill_a(uint8_t *a) +{ + uint32_t i; + for (i = 0; i < A_LEN; i++) + a[i] = (uint8_t)((i * 2654435761u) >> 13); +} + +static void fill_b(uint8_t *b) +{ + uint32_t i; + for (i = 0; i < B_LEN; i++) + b[i] = (uint8_t)((i * 7) % 251); +} + +/* Member @index of @a must come back as @want, through detach and then + * through a plain extract. */ +static void check_member(r7z_archive_t *a, uint32_t index, const char *name, + const uint8_t *want, size_t want_len, int detach_first) +{ + uint8_t *out = NULL; + size_t out_len = 0; + int res; + + if (detach_first) + { + res = r7z_archive_extract_detach(a, index, &out, &out_len); + check(res == R7Z_OK && out_len == want_len + && !memcmp(out, want, want_len), name); + free(out); + out = NULL; + } + + res = r7z_archive_extract(a, index, &out, &out_len); + check(res == R7Z_OK && out_len == want_len + && !memcmp(out, want, want_len), name); + free(out); +} + +static void check_borrow(r7z_archive_t *a, int solid, + const uint8_t *a_want, const uint8_t *b_want) +{ + const uint8_t *pa = NULL, *pb = NULL; + size_t la = 0, lb = 0; + uint8_t *out = NULL; + size_t out_len = 0; + + check(r7z_archive_entry_borrow(a, 0, &pa, &la) == R7Z_OK && la == A_LEN + && !memcmp(pa, a_want, A_LEN), "a.bin: borrow"); + check(r7z_archive_entry_borrow(a, 1, &pb, &lb) == R7Z_OK && lb == B_LEN + && !memcmp(pb, b_want, B_LEN), "b.bin: borrow"); + if (solid) + { + /* one folder: both pointers live in it, and stay valid across a + * detach, which can only copy here */ + check(!memcmp(pa, a_want, A_LEN), "a.bin: still valid after b.bin"); + check(r7z_archive_extract_detach(a, 1, &out, &out_len) == R7Z_OK + && out_len == B_LEN && !memcmp(out, b_want, B_LEN), + "b.bin: detach beside borrows"); + free(out); + check(!memcmp(pa, a_want, A_LEN) && !memcmp(pb, b_want, B_LEN), + "borrows valid after the detach"); + } + else + { + /* a detach gives the folder away; borrowing it again decodes */ + check(r7z_archive_extract_detach(a, 0, &out, &out_len) == R7Z_OK + && out_len == A_LEN && !memcmp(out, a_want, A_LEN), + "a.bin: detach"); + free(out); + check(r7z_archive_entry_borrow(a, 0, &pa, &la) == R7Z_OK && la == A_LEN + && !memcmp(pa, a_want, A_LEN), "a.bin: borrow after detach"); + } +} + +/* The archive as a file that is read, not one that is in memory. */ +typedef struct +{ + const uint8_t *data; + size_t len; + unsigned reads; +} file_t; + +static int64_t read_cb(void *ud, uint64_t off, void *dst, size_t len) +{ + file_t *f = (file_t*)ud; + + f->reads++; + if (off > f->len) + return -1; + if (len > f->len - (size_t)off) + len = f->len - (size_t)off; + memcpy(dst, f->data + (size_t)off, len); + return (int64_t)len; +} + +static void check_archive(const uint8_t *data, size_t len, const char *what, + int solid, int through_reads, const uint8_t *a_want, const uint8_t *b_want) +{ + r7z_archive_t *a = NULL; + file_t file; + int res; + + file.data = data; + file.len = len; + file.reads = 0; + res = through_reads + ? r7z_archive_open_read(&a, len, read_cb, &file) + : r7z_archive_open(&a, data, len); + + printf("%s%s\n", what, through_reads ? ", read through a callback" : ""); + if (through_reads) + check(file.reads > 0, "it was read"); + check(res == R7Z_OK && a && r7z_archive_num_entries(a) == 2, "open"); + if (res != R7Z_OK || !a) + return; + + check_borrow(a, solid, a_want, b_want); + + /* detach, then extract the same member again: the buffer that was + * handed over must not be the one the next extract reads */ + check_member(a, 0, "a.bin: detach, extract", a_want, A_LEN, 1); + check_member(a, 1, "b.bin: detach, extract", b_want, B_LEN, 1); + /* and the other way round, so a stale cache from the detach of one + * member cannot serve the next */ + check_member(a, 1, "b.bin: extract", b_want, B_LEN, 0); + check_member(a, 0, "a.bin: detach, extract", a_want, A_LEN, 1); + check_member(a, 1, "b.bin: detach, extract", b_want, B_LEN, 1); + + r7z_archive_close(a); +} + +/* A folder decoded a slice at a time, and looked at between slices as a + * reader on another thread would: what is there is the member's bytes, + * in the place it stays in, and it only grows. */ +static void check_step(const uint8_t *data, size_t len, const char *what, + const uint8_t *a_want, const uint8_t *b_want) +{ + r7z_archive_t *a = NULL; + uint32_t m; + + printf("%s, a slice at a time\n", what); + check(r7z_archive_open(&a, data, len) == R7Z_OK && a, "open"); + if (!a) + return; + for (m = 0; m < 2; m++) + { + const uint8_t *want = m ? b_want : a_want; + size_t want_len = m ? B_LEN : A_LEN; + const uint8_t *first = NULL; + const uint8_t *view = NULL; + size_t avail = 0, before = 0; + unsigned steps = 0; + int res = R7Z_PENDING; + int ok = 1; + + check(r7z_archive_folder_size(a, r7z_archive_entry(a, m)->folder) >= want_len, + "the folder is at least the member"); + while (res == R7Z_PENDING && steps < 100000) + { + res = r7z_archive_decode_step(a, m); + steps++; + if (r7z_archive_decode_peek(a, m, &view, &avail) != R7Z_OK) + ok = 0; + if (view) + { + if (!first) + first = view; + if (view != first || avail < before || avail > want_len + || memcmp(view, want, avail)) + ok = 0; + before = avail; + } + } + check(res == R7Z_OK, "decoded"); + check(ok, "what was there between slices was the member's, in one place"); + check(r7z_archive_decode_peek(a, m, &view, &avail) == R7Z_OK + && view && avail == want_len && !memcmp(view, want, want_len), + "all of it at the end"); + { + const uint8_t *held = NULL; + size_t held_len = 0; + check(r7z_archive_entry_borrow(a, m, &held, &held_len) == R7Z_OK + && held == view && held_len == want_len, + "and it is the folder that is held"); + } + check(r7z_archive_decode_step(a, m) == R7Z_OK, "a step more changes nothing"); + } + r7z_archive_close(a); +} + +int main(void) +{ + static uint8_t a_want[A_LEN]; + static uint8_t b_want[B_LEN]; + + fill_a(a_want); + fill_b(b_want); + + check_archive(nonsolid_7z, sizeof(nonsolid_7z), + "non-solid: each member is its own folder", 0, 0, a_want, b_want); + check_archive(solid_7z, sizeof(solid_7z), + "solid: both members in one folder", 1, 0, a_want, b_want); + check_archive(nonsolid_7z, sizeof(nonsolid_7z), + "non-solid: each member is its own folder", 0, 1, a_want, b_want); + check_archive(solid_7z, sizeof(solid_7z), + "solid: both members in one folder", 1, 1, a_want, b_want); + check_step(nonsolid_7z, sizeof(nonsolid_7z), + "non-solid: each member is its own folder", a_want, b_want); + check_step(solid_7z, sizeof(solid_7z), + "solid: both members in one folder", a_want, b_want); + { + /* nothing to read from, and a file cut short of its header */ + r7z_archive_t *a = NULL; + file_t file; + + file.data = solid_7z; + file.len = sizeof(solid_7z) - 40; + file.reads = 0; + check(r7z_archive_open_read(&a, sizeof(solid_7z), NULL, NULL) != R7Z_OK && !a, + "no callback is refused"); + check(r7z_archive_open_read(&a, sizeof(solid_7z), read_cb, &file) != R7Z_OK && !a, + "a file shorter than it says is refused"); + } + + printf("%d checks, %d failures\n", checks, failures); + return failures ? 1 : 0; +} diff --git a/samples/formats/rrar/Makefile b/samples/formats/rrar/Makefile new file mode 100644 index 00000000..95f46121 --- /dev/null +++ b/samples/formats/rrar/Makefile @@ -0,0 +1,43 @@ +include ../../sample_objdir.mk + +TARGET := rrar_archive_test + +LIBRETRO_COMM_DIR := ../../.. + +SOURCES := \ + rrar_archive_test.c \ + $(LIBRETRO_COMM_DIR)/encodings/encoding_crc32.c \ + $(LIBRETRO_COMM_DIR)/features/features_cpu.c \ + $(LIBRETRO_COMM_DIR)/time/rtime.c \ + $(LIBRETRO_COMM_DIR)/formats/rar/rrar_archive.c \ + $(LIBRETRO_COMM_DIR)/formats/rar/rrar_ppmd7.c + +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) + +CFLAGS += -Wall -pedantic -std=gnu99 -g -I$(LIBRETRO_COMM_DIR)/include + +ifneq ($(SANITIZER),) + CFLAGS := -fsanitize=$(SANITIZER) $(CFLAGS) + LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS) +endif + +all: $(TARGET) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c + @mkdir -p $(@D) + $(CC) $(CFLAGS) -c $< -o $@ + +$(TARGET): $(OBJS) + $(CC) $(OBJS) $(LDFLAGS) -o $@ -lm + +check: $(TARGET) + ./$(TARGET) + +clean: + rm -rf $(SAMPLE_OBJDIR) + rm -f $(TARGET) $(OBJS) + +.PHONY: all check clean diff --git a/samples/formats/rrar/archives/README b/samples/formats/rrar/archives/README new file mode 100644 index 00000000..a390ecec --- /dev/null +++ b/samples/formats/rrar/archives/README @@ -0,0 +1,15 @@ +RAR archives for ../rrar_archive_test.c. + +They are test data of libarchive (libarchive/test/test_read_format_rar*.rar.uu and test_read_format_rar5*.rar.uu, +2-clause BSD), decoded and otherwise as they come: + + lowdist_reset.rar RAR 2.9, one LZ block whose low distance bits repeat + compress_best.rar RAR 2.9 "best": PPMd blocks; a stored member; directories + filter.rar RAR 2.9: a Windows program, with delta and x86 (E8/E9) filters + unicode.rar names in the packed UTF-16 form, Japanese + ppmd_lzss_conversion.rar RAR 2.9: PPMd blocks and LZ blocks after one another + rar5_stored.rar RAR 5, a stored member + rar5_compressed.rar RAR 5, one packed member + rar5_arm.rar RAR 5: an ARM program, with delta and ARM filters + rar5_multiple_files_solid.rar RAR 5, solid: only its first member stands alone + rar5_blake2.rar RAR 5 with BLAKE2 hashes and no CRC-32 diff --git a/samples/formats/rrar/archives/compress_best.rar b/samples/formats/rrar/archives/compress_best.rar new file mode 100644 index 0000000000000000000000000000000000000000..41155e00bfaa600fc8eb26dbc75740f59bdd3ab9 GIT binary patch literal 12186 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 QA{BTg-6s_U0Ac|C3z3?slmGw# literal 0 HcmV?d00001 diff --git a/samples/formats/rrar/archives/filter.rar b/samples/formats/rrar/archives/filter.rar new file mode 100644 index 0000000000000000000000000000000000000000..5318dbb3fc1504228a7c8899873aa9457046e8c9 GIT binary patch literal 94301 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

L!5}9gh%DB<5|B7BpyA7n3;5CdL#1 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

  • r`^?d@FzI-+6NmoIk@Yy|iDcp848FOoT>3$FJIv>iTdQ>spVwNr5f?^YOP|S^{xv(7 ze-{(@!TLGZuBx7&>lYndrv2Ay&Gi>w==bKyv=_so8Gm*5#h3N-Fw*HJ}>K|-{2VM zy)k&V==50(1)3ALd-Jw>Hl0m>!LaJD8sB--{8{a`HwC>tA*P@C(po+52LJ5fV0HRE zBoB)anT+mhk&W@v!%=S~mw)mYHUB?0U@YB5c)bui)IhhZ} z1Fh#dlEClp7b}}B`rGA6;M5K7K+HM9{ZO%QdOOX}68$}o%Jz61H}8jOvHI{Lw{o*uU4@HF2JiNIvpQ&u!&~w=JaS#f z?R`ge@@}{ivR-a7w9@*U~#{2cCYmeit0s(%gJ>G0mZs%&rU(a6o*XnvYYj|t;# z9Xl^GC3g2YKOW!re7`v{S@}Pe6L#;-YGT^_M|C4-@jaU!anqgj>tDCtCNG5rnpGxa z4R(Tv*uFyjw*6MYk3Tun#Ob6rxeqQwk?z5CJ3H8zN-yh+O;O(dblzU#Tm6?p{bTi~ zEVp}X|7(Ge?q%ROUk^KBr@&z%|2|dsO~1LxxovM6x+Get?}1ML@xPfOgn%4YPo{60}4li5jZ_Zx2utnS>nTMU2AiG0Db zcR0NGXYWPX7OydVkW21;SZ&Sgb&23TLteT+#lu0c{JCwv)|N-?(|r>B=La_1M^6~d zQ+#^->3J7_4x=R`bM~U~VI{s=jYY%C;cH`!`=UBHo-A#~MtXwy{UMgf&fLC3=`=h< zebw4`VPR!=XZ(0Lmx;z*h01G}qxU(#&yNiKi`+3Z;COKj9ZZd>Ke)tS)~vS~%~HF7 zrXFL$X(7@bRyuWkW2YdzoHQGUFaD1W?W|whkdQ&oFX;4nH=4db{?7mS>Ye`M2ZK$I zhVw!^*Sr1lLUi6jb~dT43==cvd667JvSIbRWy9}I7v?~>SaR|?PCZ`^|4D$iR0sy{ zk-TFMG8x@rL+iHgzWJ#jz8kx$*TCiT1eo9q`4IN}{_Bo=dL|Kj0>m;9n+xH$QIosr zZ*hd&n2jdyWHH28xFmTSp(7gNY*`6N-TaJ8lVWi+LWPhKkw$mA_)CC^78|Z|CK)6= z5eS!LaoW-UB~b;I%nB9OIz+`Jfps!Q$t(i?BH}lM?EYa1iQJiBwwMM|yBU>#t4N8oq{w^?>ilDI4qG-~a-JFHRwI1L z<{o6+L|Mx>6uv|ka$yh)jW|bVVZY?y<8k4epPLcc5jCJWXsdRI>pC{&;1XDeu{*L= zZp?_z9$dlf023@*K?m4E;bIxVBTBLTpm%n0-R*y|r-f>~+76v70QH(syW z$w{zH+t?&>%DOu#5Xn~A9geR4%zSGyw#@#`RlBQSn$eo(jJS{*-a$X#z%e4c*TXqu zS@t+4RbUZgc4tOe?fH>OAkg|@tdo!^sircuPUz>;zaUH~W7iQ%LF3XF%Bv@>Z$4yGlh{%kyhx6nt zq+rBxVTM`*5)6FoE1LLcA6KUO4~Osvl$}PbwTcJ`n;7x7ioeGOXaw+3Is?k_&sOS6 z$yPF9*WMgwWEq{=l|vdc$e56lv=lTuu5F>%`ZiQdh)y3|nfGJJ*pUK8BjqCB*r*wT zIK4MZktE16oVZ&d06B~}#9Zabg5(m55K9YIPisBx{8MVV11@@Q0yt+gXU3tEnUOaz z-IGtAz_X2ugKU3YyI>xh9h#IhBWFZ$zOT20;%^q>=2^#iAjbIJJ))EoRFvrxgC^040IH6I)<(BU)&}>}# zM7<=759=3^>D#Mah<86XUXxEyNALpqqzoc5G7Cp{X#GV~4mQ?W8th4Z#!?m5v;_ zft-fKJ24b;Xe5GN`;b-xknT>+>RXgqleQ+WxwDYih_j(XqDvT!OclgTs96vefOl-F z{J}v@wQaU4$c?QPjM|Me5h9>R;xw?`BgsAxW`nkD@m~y>q+R+CsF519x=1PvpdhhM znM*@^F5?(1U?ec&rL}e%z!}VQ5ZLE)#n?%~$VbM}%SO?V+Rufml39jJrK^14V}EeS z6@)P;s|hOAK^fFGuty!iJi7-wZ6e3B1o+1~(7b(E|0psD*j0ctSCS2B1KPBi7^@p% zG{n61X<92{?B?%eVP_=vG|ed55i_AOWfxV-+B%y2NIam!)CA3ZGnW>Tt|>j@h$ms) z&<=9sHKk3VLxOucX4E4ZwHgRn#|os1YevEm6p;jSwSwRO49kE7w1C?T1dWU*i2eyjC>gvFR>bSyQgVHP!jGlig4hn#N;nF3VaJ9sL(>W+#GsUtxg z?iY5EoZy!#=KkqbySa{WzQFfeT(MPv7}rL|LU?u}Fk<6rGVZpbMyG2PZPphEw|+2Z zTe@uu01@#-k z*?`Dy@sUdSWvYT+V!dWq+afzLsUZrGT-&_pq8Y}rBxVURucdJ$;Da`1z}4(>%CqdD zU#lqgS8E!Ul?*LdvC%m#9LO?+*OlUPoH!*jh@>@AIplyuYtr>=s%s#p)wepUqsW8si8qQG`;y8Ftck3EPD} zi@8pbVY$#T4B`r-P?b<##GnbB&lFRddawpO=NLJRIK*)pz!{Vj$~m<=n$r+=0gwI@ zYBlW(8o(WT*;Tkvj1Fi4n&uEMU|QL+kCtI#+|)Q*aZy5)in*bIiEDoU`X zp_LI}dle~HgE-Q&+G4Aeb!&1c4`==g#srWkkuVWP4CNa4UktTo6=2#2HqcHA~!~P>4SVJU$2nElde;#Y@C~f=IiF`)Pz;_sNhZu{TwJy%o z7%f_Z3_DQvq3ur2DqGYy8_@K#(`(kPP%%GsT-U=mk#XrM7xxoDzPK6N7}+&$G$Jt% z>#uM*Wf`$MS_Tz6M3jOxDHK<+#`a3nh>*~VS`cf5P^b{Z)__i<2cYmS$kvgGR$8^A-8RYCsuAbC`4bkNd+jd4(3slBQ1ALxXE%$x6PahVXt9)RAg}eG#|`PwPNQi~m(_Tq z%UmtH&OrIi9rBEdPty=hbBEV?h@~+I!^_u*ar6*1^T&B2x6)Z~f+Hrm5*hK(5nT}u zxOn0!l!ZwDl}QKi!oxwp4Td{|gGeUNNfJdu<)YR;ZU7~v zqYTeFd=_Tw4VWr0*<9L85q+T(OC$s=6dT+oRw@1`4;KuQ7XhAIVYi0>uJra5KX{es zspxDO9FIg8h67(>3(EtTSeS*w8y~}C7&^a(O8g=$71To^T4+p0RvZt&+0)OT_|}qO&7J%er3?y;3=mUU z>`K-|Fn632(NOgT1r%Azb&CxXSq>Py!Fd9hTod*q1%@{&zMe+Sqyw@O9eNW|+h0bk z)*b~Ocr2u@A>wn3%=ig`WVQPETdX*;xo0Vqd&(KJuc#cMd80NEYS07VOg|Y&IKl*F z$>W#yjaW5UyyN+edw#}ZAK!5JE4#V_a`2)YX>!!9;pVN1&503oud;ck%myQX%o z-2kfSTGnom*L$T>V(;F=yi(*tvxWcgDg{2(oW*>;SHcpq^ndufn1QSB0zGFd0ZIRX zmlCS5L0t3C$swNBlrvyQvuSj|+}NJMERgNTp3yOlgeGn4+d26!YZfkR7WB4l9bD>f0!v4FLPWPea`Ge%Z_X^l+tL z;w6YQR_?+t1O6~rS~NIKPiD!^p;6EJ`xKixM~_on`b;rCUF2{1cuZA4>!I-2R5zxy9k zdn?@>of?{_M&e?kv`4M|8miK#gy;9us;M8<=!0~qRY8*eyWS?6N|u#G$@=Emeq(&+ zHQgv$7ECj`qnu>E|9Q2NYYK4Y>k(7vrJZ@2#et#OA=xlvf;R>tJ++h5=bJboxQnQ7e25w1vWOMv;bnnWwua2hBY|E*_ z5oH8rRfSI#%~&_WFsbIN{?3=4uw4KDcL!C}pELh8Tep&`SD7ovt(@bfGib$ssZr1Q zr;$Bd-et@rWTU@!eIGMMq5|zpzMDJm`8GD~R;};7J+$KfQol9_hVwIxcIWM;)Lyc` z6#P_2QeSeLnSZCAw>a={un=6oRlTCB_&J{K`yB_d{cpdfavLphx<|rjqA<8%9q)|u*hmZKK)w)xqWTK6%r8JuimKCc0p z>OSIBwR}y?+RcH*qS)<|?Blwa*UQDx7SiXQv*gkI{_O7=J*CsT)@RZ6**C~Cnwxd^ ziZZH`s*cn#RQkn6T~u8+Y)X|JIZ;=bgIS^tDVPpbeOh#1HUBuWYQoike}wcmu%?`3^5+v!(`aigP6@ z0p-M}yd$6w{mj;Iv!@I5+IH?j9zu{F<@j%y7#>0;vTR04F~k%e_1Wm zQXwy(V)e647zQ7~^C9M%&WLmR2hAH3N=XVN~{r+!Pg2Yra~sua@x<|4_>#^N<3pZIeKDD|74YdO3(kr#muf%GIJe%IG62nU=F~a) z0DqKw*!?p#)w{t;Qe8=zvs38{DK9}e`y>NhQtzP1{nmDFdlTB&UC~Z2ovN&s)aAs?5LiPv#`nQBCZcNtpkrc`rVznA! z&mnIq;Sa>sefrs)9mb#eV{5-bx?MLoe5cmepJn=d{5+?cZNq4ahsjKh55f3-$UD-| z+fFy&s?AN~ow;bAUv;$^t?ttgF?x%r)$#EDzF>zRhuS{%0DZ@4;`%uF%8kA1A5{yz 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 IdjLQO05>KdX8-^I literal 0 HcmV?d00001 diff --git a/samples/formats/rrar/archives/lowdist_reset.rar b/samples/formats/rrar/archives/lowdist_reset.rar new file mode 100644 index 0000000000000000000000000000000000000000..e2e0fb3fe0bf8fb6881f37c812c385cb341617f5 GIT binary patch literal 566 zcmWGaEK-zWXE;Bhn1O+p0RplDOBfmq8D23mFgO53nD1Ep$Op;DnhG)~0O_3k@|4Wt z65XQI;?xqoq|7`)Fc1w54NX#_8%QG>q=tsh!~-C%7ZeQ?1o;3492guL4l+769%6E8 zI?U|ce1yfNoGRBw&U#X?I$=qI!*WJ^-9V literal 0 HcmV?d00001 diff --git a/samples/formats/rrar/archives/ppmd_lzss_conversion.rar b/samples/formats/rrar/archives/ppmd_lzss_conversion.rar new file mode 100644 index 0000000000000000000000000000000000000000..092fe8bd796522a60dbca3078b11f86eaecaf7d8 GIT binary patch literal 176700 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

    c75je@xtUk({-tt`xsYZ! zv#_JWOxiiADbK6_JQdIK20j13foGceBvHmv{6|<@#c#w@!@*!aW5QJP`RZKhJ;kY> z6tObm+6-OL(>eCwTz`G)P{5?9x>5+D6WK+xhCki^XAZf4l$6X}yWV72zj6tA?k}4x z#Dil`C5XOuf4_YoN^MoaVkdFXugmwJFD%rY?@bhY!=>Pv!u~`mp*jJ8y$D?=6R)eM6lAT-8s+gKgOv3*V6INezY&*RpU2(~Y>>=Q&ey!@C zxAa||vx%5NW*KwD&#B^twVpqK&Zu7V+wfW4-m?#T`aB6{J$C~Dic-x%m_~r0LGyLX#Ajmwtzd>o>h$2`Pb#ri(@JN$>b5#hdC(ivpj&ycyuBc6KND zNa_OEvzHub^S3|Dl)DllDl{Rl1RiGeDjDp1NO=Ps_VvsDsz#;BzA|)evWEBn5Hy?& zz!3gk%G&RLj~bW(7A_X80k-AyyS1TlHtS=USm`ZJpAFH)0>e%4Qp+62-itDd zLbrxjm~Q)SlSP1PAU!ZQYCu`o{7KEsemC`FVOtgjLD7Pz1B;5Fj) z`6iFPI179Rg<)}~Ib3uOB+P%vB`ngut{G367XM1Pbu+zsjGCFug18)!7y$1H)|0aY z<~rO@3~atSHJdmQPu-VoR5cash?9C-0Fy(%2n#V4rH-4-o45%M03R% zu&hS$!V0E6sTCF=l-A9~E*VgOE%V&<=xaOWG`2xt0wz*h8!CN!-5pTxW+DpYd^%pD z)Pl(=0g*^!4{Yi!kvWPi=Bq1Arte*F~_|E_6w0_(;L9veTLV9c|_de_ZEWUclu$kh*TI#V?E~fk@D)` zrzS21xZ|>T2&iJqhf9ck?o(%*8Di6Nwms$=HE_5%E){*cj&%}Wa3lK%M&~}iFtih< zqCW-?;~FBEDKjjpC;vGyXT9g)8WBgQ;TFVRzLO0X&i1Dnkf|Gedr(4pG@?46(L^0V zi|d@(!T6P;PU3}}xV{2u)ZO?lJGjBmx|R4Y)wdk9p8*stLZ2l{%88(OFy(!^Xv)=sPOs*PM*(y*kFFwzFQ#VbE->Dijx@j=&sK=gg-eR6uiZ`HR_5P!4 z<{oFOG@7_VVqU1F>nQPWV<_->Pbs35>-`Cxlqm&OpMyE$`KCGIUcZMtcROW4QN*ZD zVIGxQK#6bq#zUd8sO0rz$^mgSm=WDMIwwYShF6gR_(LKMA|Y~Sa~^q|z0uj4Cm$em zk!gA~kfUI=WZ24SR5Mt+G?Q1vz30sO3B4_g%OcMGISEVc>*Myc#b{UdV=joa;G|wK zEyIt&(K!=2Uw4Pfj-bWuBlU4?q+3O{h=kZb=kmXf9cC3~6uVs=V5-4V?R0g3s{u>3(bf8^^)A;(SIV!HyImY0 zFP_N#f0YXCGFPi=rbJi_YfX;G^Vp#591|k5laNgM6%2{F!hHl7u5|77AYt*cA003* zI8Z?2aw$^SY8zi>1q_M-&DRqT0_(%W`QDd559gk@0%UmosBin4%Q08V&*&3#^vuTH zgPPDrW;9fEU*i)Ueec)|!Ir=E-#~wdynocFf9PCZV|tzn^Vo&aVqF3J9|PdX4RhC! zdj|2{gUsz%sEc_a1UsYnq%-mWKD0jXzXl`w1EyML+(9C8WKVSCzM~cQM;=q#YL`G< zeN+0elg=P>gO1b}PG43V;Ax+lYpeu4@hRu75&YJeX)SY54|%5fF)l^4gR0@2G!bNO z@@Krwh|~1Rfo{6;;_Bp!tC!t7AO2tt_8938`kZJhKoE6Hpu`M#f?T^)pHlR~o!o8L5N%}wG>(z;Lw&_v}2iZ8P4ek$2btN1j1AHi0Q*q zYS0>Tu=f{r5Y==q(vYOHw zXBPa%9O1;~3B6?c$`WILAckv=B^WcFXp0)%VlTVncTw7lQz9oUNd`@WYLg%>dml>6 zj0KxursrtJlS>2%MlONS?;n=ifUPICa@bRW4yw7(-t^5A$OkY#(&6a%+)0y{w7t;)J zJujK=!)Os+HY?c%_HTF2jjzMTfw*4e#e$DRLYM|Pp*Fl%@T1%B`pEM%yQuRKyOZDX zN9V@novzt^%2N0=Pd)t;rM%1#^G06L&_@^t95>4E&t6-^LXxg!(bM91-Wlag>#sht zce>OfMzcL~1Zij>(YlS;%0XH3MYz8nX_K&GUtJJTx?OFA#%@9(H~7sdh?K)9<}KMe`%jHV_x7gYrN<6nTsV$> zmF7Z0Tcow&A~xb#81^nRb`^2){y>Vh?U7>tKm@4Bg-OL;wtdoee{zwBilo+p%CS5p zIw>E_+xL2jxwZa6)*or(&dY?IdM?Fd8PV?O7i5?sG62joj;&r@TE8QmZJDKycJy1a zSz^o-qlIDj#OH&NGbABLW4ax79bmwS$X}Udy1VLXm@Gy#h$YIo9Lke3EnM6cbmp{j zEfv+Sjau&o@VCIZ;i+mZ zT#QAVrdx*TjN*6~bau`imRxm``|Kvpsm1vA*Tkr*;8y6`$OxGky9XiY_= z2-35F_AE6S^>c|=R?2lv*(heM%&E1tQ*g8lbE(Bc1mg6Dqe84x=vzGTZ9I9)@5xa( z^xU_W3x?b8NJvq@E~@TjD6iK^v-up>slPX$&Rcxsp-@JO2H zXBQ;empgA<&h5r!v9b!(Mp5oHME37>ch~q$_7rW{yhT{*>5JGA%P+~DT(A?UjL zJQW0^>^Q?r49+wwt~ny0IZ zaYH4ERUZuX7@$+Cx&=oc4sHsy!kL*kpJr)XXg6JC&S!@oAwl-eQHM0X9+#<@NsZyZ zr-19+-dC%kDA!A#0W5X@ZOzU(dYd5?H5;i@xq+_cL0wa;6H?R@u2&;a2L-Of6&&s& zUN_24?JZP!t&OeUbiM-!(-IUyY##SZ+pj)CQg5XDicF<}Z=?)&TpM2nNiv3(dhFd| z106CO{vixOk{vh0{0pgAb;^(F9HUXd~My`xN-I zZpqTz7_-dv$6RyX-}9)XQ1$|qz zudA$Q$mky92sc%~<)tXQd^R&12C z+@>9~jRd=XbxvW-_KI`r&V@}XvoL(M&Sn~#b!SF;@0`pRUmji?3D?P1bdEk-nZq9s zIo#4I^%b7s$C&1H&OF(7*~LEcv!`-?XK^y76qZj@t~kkd{+Uia%+os1t35iL!b^^lmfx7)oZqP5F~^$o$zRO*Mlxil-*~wAQP6PBr>Kpf z(-M2*9a?Slybb_7-~_1oK%~v&yy1THdP6KL?5lS4Pmk{s=*yaCXQbz&RVCLqPI`yx zFjlR{V%>9g4cWK1Z@zBUOd=b$_s%>P?c3rv#cz(?!+tw{L&VHVo}OMA(>)8+I{fDT zYk0HN`+ZY&b(>Oa$94#o50Ay;67OFsy&R|X%iZRin{1nyv0p6y4~#Av#`bM5(`#8{ zWIi$>Yq6@z*I8vMk=3k)aQQsx_CqE+w7--jpikiEy{xa-wLxiHw6g;7w*YcYWZKNN zL>O_S{XyNVC~qC_&g46&t!CQz_R16lWQ6oPj?40B`kx9HHcM!}gB!$0RaF~JrG1-K6;)N1Mcj z17O{P4bg+5Q?tHwLjOM-4zHWTHF^{CO;J=(m?081(*IR}hC3Cz0wY;`KF`K`b*2q% zt3h#~3=nmlR`zw_uL2KUwaFRQ&ce8n%UMQ>EI1lDOS?VDU_W{~zj_S!GHY2XW#Cq&R&#(_c6a)J) zNa-Rg1?r@HZ8)MdZ1#GMCbK~9%jpM*$z)}Awu+j<59Eu3(-VR;*@C!hL6q2KC{=zx z`y+pXcKH?qyC@K39>1~d2)+U{WMR6B%Ek*Y9{~fhmBR351?OY z7ypS#T~Hu!qIa{e5bIxCRJuNk!!O~SV$RaaUX$KS3IuprnpmE^CRQ$3H(<7)FkwI| z#IN=s8M3U+Uq8n*3C_~9ZCE1U7j`6`Qj0UrdeS?WqkTYiUtMXl+F44VQ|<%3Z)e?T z^c~01cf#2O$0x56HPbx~3#`=Y4S(2y2P^scgcPox(*7V3od2z%G=Fa)*(75@SLiKgm6 zX^G1G(yqXueJm+yU0pIVYzcSukt~4U3NOJG~K{b{&Ql`@O|W-9qJdV!1A&dFb`X*|M@& z{t3fLM)P8+v9t&#Xo@YQpYbPn3g`J%UrTn{SB1_v!in7lVXVJukG5+qm&|tK{?TNm zOuhlj&MsXO_ zQG9xDhITceZ@W5N?KL)oPuwF86o6Cmhwz|0*#y?tH1qX^OF{BVf%dFHu~YlF2S{ps zQD<^flBqUP$L1J1S+CxZ=cJqbw{p{N)z_G^&0#`C(EoWk-3SMu^*}8J6^2U+H(TS+ zG!n<3$O%XAGw4k3@J6UsMHMzA{x6c=Nj%nVz@OR=lYV+cwrMYsuW%AC`>7vjwnz)I zt*NaJ+Z>h}{CIXH7nA-tyv_xZtzk-f?FJ*Fk zNO1J`SWJeD|JoIUeJC(vRfpv8!suMMUk$4v@tP-tO_2+nGIk7}f zg`UkOeGh#13W3mA&+03%S-)YMZ7)L5pmvA= zKb8M4uA>O%T2lxKVMBr#DpI||1=9>$%F7tPv#*z~{$f6Nlq@&qiQ@U9LVB{574C9f zEaVvZYXr1>JGv?(ILAs45tHLzkTxuW8<|Fqu!4BB+6wlEZGS4>%7FVLU1gC;eK9Tz z6+=WbW(Ve?xg;^BLU-O;SB6R>EhOYmus&G3zf3SIxmE$AT~)uSVq$v=SGVKF(x0cA zCC>q&KPfz6ldKe*M%v01n)rLQ66|My>*WhA+(SjNLVU3{D%oHTy~<<)U>V>uc$SM5 z=%TV>E}f6aQ&_UC+TY)#oN;6e+`2pc^RrxQE`fg|_E7G6LOPdGEpidzr*OAsT=>wK z+VR&5o4SvUiNvc*;y)_}rAR2!^oLz5zgs-K>dA9UtECpF2&-^@VLK}xhig?rxI#Ar z!4s~itkFEf8+NzCDAwoZQbW>D^0)s{Wzvi6y}bVj79cqU6$}zuP`t1>Pa`<#JJm6= zs|hQb`xlfS0ov@A{mNFB_-K)y?5i+@<2ACAj*;^D3zF6^y+t;zlR@#3JXsibeM9kX z_Xwd!tbn^S%RE>AJwxxV(CKvjzIK}ku^qA4CM=(qHPc3#6WU!aqe-e{f2e4-clYlF zq~`pp{=pR%h%rn|zld{Eivzs&mXi3xu#)mC?U8X(CVbb0b()JcnS5KjmZIY_%Cxlq zcCLG67uP4s)u(dr&;JElH~}RVrCzyZGv8%@R-e}GC6z{!pxl|Y_~2xX?VX`Sw6Quj zD~21s$uT+Z$z}3G*!hkrE(ohdM2PoS`7O$l{>rFBLS5S3)V!7+xGr|05A7CB+pE`s zSX`Pnm*s2C`C<1(s&m$|e85~c^*e@FQc?dX16YLkit8bl1*@W>H~(^UA5&^uKu^&NuTPd3Z=2yl2)#|3a zh*zRM4NhnWppP$QN``i-wxR`WQG6PAheB1 zcXm^T<)>{z*KR-BCdD(_R}BMVGkUNNWz2GS^I@8k{q+<&)oLi*}X7-z=5tS3U})MGiJF^Dfvf#KM1BvTkKjSOKvrw4qii;onMo$Wcb_&O2%vD6kN#|X zeE{v~yNyd%&P$Drot=hjE0x<1?4!@lR?5F$XSNf5vbWBeRT?B+{MRmp%FtSX)Tbmr z;GpzF<(fL>OfWzG?ewt!g6J!YD&^4olt!fkiFUCa*U}N4EC@8?W`N25(4^C|LxDnO zuD+=vxLf$4YR1!Pnq9);_5Q?@0A4>Ia?uR8qBPw zC)#aJmYYf3XuGtpx<2Tn{4pMo>^qv;wVm*2+LoN0N!2rB{ z_0L1k@qq4|JK7UB*dPS2LYr&jf#c8h;Wikms`dK~jc&@jX5nGp1P|LyuG=-Y4l~*P z4vR;m%~E$7Z8#r!txl_1M*378BaA=Xg;-5{MxDy=QbcQwZjMV#vz(v#zk z0^IWEuhxsl+Hl?^b_08SqgsJ*pMm+P+CIE0fIpxv?u+Pc2zWm~4g^+Kz#N)+ z-PF`sRu^BXk`$X>ABwqH7^CNF?6NOq@HB{cT}8Uaj5@8VT1UdxjU@72q5jQER6Q7+ zR972>^KnGQ?B55(2xWL*&o7)33KVU@na7Zve0%)eXfhdG{*zCj&yL63f7*>tX)Wz3 zu8A;1#-Y=sQW-<;pKL;?(fRZLwC>(UwLm_3yV}-wW*rZo2J%gIeVb8GC8p4b*X%Ze zPx`i4nv{P7I9M?&%&^tToBJwXcrl09+05Vt*^rTF`z*d;?)`$CzFnSud(3%vZdn@p z9?LOCP&DQqMg)dK*rPoP#{imH<$CYL+KKRSEkFWPenq*8* z?o#OR`QR3OeD&4C7dlmQOuR)sq@XC_4c#8{uu;S3*X##yW+KWk6R?t9A5A z+43$HOOUB+AQjfK8oTc`o#V<=gnUlb)j~QRs~gvugmJDg?!fm)cTy+Vsy%Gay=L6E zmOCp+F2S)M-G11^T|pfe1{~EAJ}!&Anw)hTv3xY{GdftAcehxk~EVkeTAT z&Wi(zc4GS+OG&lM_1UDb9G^K>!+CRQ{t4czEM+I3JzzCl$Lh#1SXX5OUe3Gd zWhW_(x9I$S}t|0OEvJc6uZ&`xYD)VXLR~} z?{Q753zxd|v(XH&Ur%KL=EGu!tpu~(37B!H zWX2Ctkao*G-jium60Gd&HfIbD-(yi|QNNG|-JeFvf1;GHmHXEsN&8)ITYvT~sD^@- z_EnFS*-EKntO9qCmO1_{CwUoWyDLq2MSnI|I5WF6L!@OTdtN60gxkf~=J;eAe`Thm zOru_RQJ&Ii9dp_|?w?Vhrq0r70|-dIe3i%bmU%)a6&6VW*Tqnu@$>ROUqkrGi|tn{ zqx^S2&jQxF@^n}3pW7Z->N1=LJ+Bf?{LTCA)dN=$*YxncGX9>^s!~3%!#=No$6Flx z;}w6`q0z74?pKbP^;Nr#;$+(MizMp)h|?8)j?2&0z(b7NpG->ru6O1s^Sm~B?$3Nf zahzwDj?_Wmf_vOUkk-9Zo_q7SZ@yps&ecl!ym6!PfB(l$Pdi(ED5r?|Yg0U!pY6Y7 ze#G^#87A#>t3F*5yGl{^f$w@3Zedw`@0T}$b zf4F$&&qF7Uaq|hDN%zKL+2?hxfWGPf4espM%P12?p3i2QpcUqFO=dEkj#Di20w#Y0 zUlr6{#)+ytr5I_8DcomDBct@>_(FB2Bc8xRiKtKT&*vawCMTcbU*ezq$TRQyr}(%Z zHV!fWSOv~f%VJI5hw?mEcsU)#xazYho$WM&>U!8c>QFCgZ7XZ4=!Kd6-5Xwy8okEQ zSqRSH9kHc|KNtT_?6ug|S0kf5EGw+i9xG4&mLtre`g`m(SD&Rf0w1*fzECf(s`lf` zvtkrSCdqWSf8Eqrk4?jymFM)TtX3x>*sZEC_6DcbVD6+?Y-J6I;2*qTK+MD|i!YSO zN!etIAFl;|t;+s-VNs54=R@_|jx8%Q3b@s0Vy9x>Jzi68Z@vzLu%7P)#}hEa7D13eg$#nH!*_4Kbd)LkXV3UVo#^~SC-|-P$?bHPcHj0FEz)SuTP^U zxfAReU%mDWW#wk z-DRaa63DsjunXToZ}NXcGST3a=$ZTgv0s}6V1OToEq=N#01px|U zWny(~VJ>udbRq%(=6i-EnjFy88*OKH&JeYw&ta8WkURjKsKZ9qML1KX3Ih|^ahj{+s_uI&!v}ABH)}_>uf7dt7P*(6 zv%{C_P}2wV{qD50_H<;(zP?^&BDo$ARyNrwc2#5jl71%JcBgLcx;88>p$D2d4Eg_! z=D9D-rncc>W7br?`}zJGT*6meW4o8+_y&ZfXPai3mAgh_twFr~DXbDz;DkkjY&Mv1 zV%iGnZv=R5RmIk3nWB&P`2@c$sf!bmAUt@0!kG@!esP!oGP8X{CiKql5(ES2QCVr} z(Y@Q!q@s*o1(7}n&CV@LX!RC{1SKxcJKIQYHO!%o7iBxmf`xcdbMg^NY>gO5wtYwW zSl+x@EZ+{)<_W9_T#eHq@s>lP8Bk7W)F1GUisk4H$H_FbS%kVT$;l%Z8g6iSo4SqR z6{cjUVLR!wrn-Sw%#MNEI4+Umi3&eW-a)p56tU>^>mc`$CME8@qp%1sTxZRFGQ2Z? z#-Mc;1^1{ z07X-SQ5}YJJqbya&bI_{$rOx;lhdWs_a9O=Ke)jW8kdV;$i1Cb*m{)oDT-K*g5?`p c;j7)@Y*HaUyTHrqd<*UO)E#$LQ3C}80M^Pi%>V!Z literal 0 HcmV?d00001 diff --git a/samples/formats/rrar/archives/rar5_compressed.rar b/samples/formats/rrar/archives/rar5_compressed.rar new file mode 100644 index 0000000000000000000000000000000000000000..6d58585c4acb1035528a53df0d98f65f4cd0e6e7 GIT binary patch literal 436 zcmV;l0ZaZ;VR9iF2LS-{;ezW60R;yD1_1$pfPesr#$Vbd0s{-_0tB!Lq=N#@aLRmu z1px?jWpi{cVrgy)0~3o^tXudPtsctsWdSE;U{oJfc3r=el5xq;WtLeCaM@*(7{!tk zhC)ds;gFD$NhBoWl1U*5NhBc&B#@FwLJ|_qwQqZUGY)`#9RsdEp1Pm+LH_sJ_uYA~ zI$!7jKmh>AAfKO)Z(mO@9{%pmzOJ5*eqK&KE*=g2-Oa6?eQjM$JuM9U+{~<;jC^cd zOgtO=+snI~i+gKZOFJtX`s&)cs+xLQN;)bU3HjN{xtV!sNi?!jG7;!dAC8QQiHACH z*{EnpCi)H_SUwRc2+hO_0_eNbyYGGe_zP`0#oRe=lDdIMT#(ImKSO` zDW)lu7v@6IcAZ8t!#KdGNB^Ld#%T&)Wl*eERtt*(k!UwA2X)~Fzz;D5jDmTXRx}Hy zp_bS;n}>sdU~&(xVH2RHb{G!>ZRmTwhgZ;z{8&JZ7%}Gx9!&{@V))?ZIvgSdso{kd eJ7Yun@lb$lAq@zG(lMNnFC_pScUDmY1q1-jqq>p+ literal 0 HcmV?d00001 diff --git a/samples/formats/rrar/archives/rar5_multiple_files_solid.rar b/samples/formats/rrar/archives/rar5_multiple_files_solid.rar new file mode 100644 index 0000000000000000000000000000000000000000..2e53d7b113097ca7ef870582eadbbe54670f6ed3 GIT binary patch literal 677 zcmWGaEK-zWXJp`fe7XS|;J{Z7=r*;VUj>|1VI=@<8CPVf4zqyHGJ ziyut{Mbbf zPyaDYn{uWdWXLM0Avcvb&W9Od3^k+Uk(Ut2@oBYB77 zmp1DrFymd~_4a!C(|;IxD}r;7op#0Uhyct~6R4?;KvNg0$qG5~L7bL5{o~&^ACkV= U13i8J_^1C2vgKic%&aU70EOKruK)l5 literal 0 HcmV?d00001 diff --git a/samples/formats/rrar/archives/rar5_stored.rar b/samples/formats/rrar/archives/rar5_stored.rar new file mode 100644 index 0000000000000000000000000000000000000000..a9a34301f571216a294baa590b6b092309f46dc3 GIT binary patch literal 109 zcmWGaEK-zWXJjy*wDl<$BP$yND?NNRSVAEuGbynsIU}CK>8R!8W})3x$<;D3K_t9Hf@$`kDHb} zZe9&DA_ng9XL}YsT|52d)a{QOTaZ;gozROBju5q=(3qW7qR`;Lzz?(;=i}xMaA;2WFHrwv=O6WE Q16w_rOGj+0864Od0O9rfasU7T literal 0 HcmV?d00001 diff --git a/samples/formats/rrar/rrar_archive_test.c b/samples/formats/rrar/rrar_archive_test.c new file mode 100644 index 00000000..ed08bc3d --- /dev/null +++ b/samples/formats/rrar/rrar_archive_test.c @@ -0,0 +1,446 @@ +/* libretro-common's RAR reader (formats/rar/rrar_archive.c) on real + * archives. + * + * rrar_archive_test [dir with the archives] + * + * Without an argument the archives are looked for in "archives", next to + * this file, which is where they are when the test is run from here. + * + * Every member that the reader says it can unpack has to come out, which + * means with the CRC-32 the archive gives for it: the reader checks that + * itself. Sizes, checksums and names are checked here against what they + * are known to be - for RAR 2.9 archives and for RAR 5 ones. Then each + * archive is cut short and has bytes changed, + * a few hundred times over: whatever the reader makes of that, it must + * not read or write where it should not (the test is built with ASan and + * UBSan), and what it does hand out still has the archive's CRC-32. + */ +#include +#include +#include + +#include +#include + +static int failures; + +#define CHECK(cond, what) \ + do { if (!(cond)) { printf("FAIL %s:%d: %s (%s)\n", __FILE__, __LINE__, #cond, what); failures++; } } while (0) + +static uint8_t *read_file(const char *dir, const char *name, size_t *len) +{ + char path[1024]; + FILE *f; + uint8_t *buf; + long n; + + snprintf(path, sizeof(path), "%s/%s", dir, name); + if (!(f = fopen(path, "rb"))) + return NULL; + fseek(f, 0, SEEK_END); + n = ftell(f); + fseek(f, 0, SEEK_SET); + buf = (uint8_t *)malloc(n > 0 ? (size_t)n : 1); + if (buf && fread(buf, 1, (size_t)n, f) != (size_t)n) + { + free(buf); + buf = NULL; + } + fclose(f); + *len = (size_t)n; + return buf; +} + +struct expect +{ + const char *name; + uint64_t size; + uint32_t crc; + int is_dir; + int unsupported; /* the reader is to say it cannot unpack it */ +}; + +/* Opens the archive, unpacks everything in it that can be, and finds + * each of @want in it. */ +/* The archive as a file that is read, not one that is in memory. */ +typedef struct +{ + const uint8_t *data; + size_t len; +} file_t; + +static int64_t read_cb(void *ud, uint64_t off, void *dst, size_t len) +{ + const file_t *f = (const file_t*)ud; + + if (off > f->len) + return -1; + if (len > f->len - (size_t)off) + len = f->len - (size_t)off; + memcpy(dst, f->data + (size_t)off, len); + return (int64_t)len; +} + +/* A watcher of a member being decoded: what it is told is final has to + * be the member's bytes then and there, and only ever more of them. */ +typedef struct +{ + const uint8_t *want; /* the member, extracted the plain way */ + const uint8_t *dst; /* where it is being decoded to */ + size_t size; + size_t done; + unsigned calls; + unsigned stop_after; /* 0: never */ + int bad; +} watched_t; + +static int watch_cb(void *ud, size_t done) +{ + watched_t *w = (watched_t*)ud; + + w->calls++; + if (done < w->done || done > w->size) + w->bad = 1; + else if (memcmp(w->dst + w->done, w->want + w->done, done - w->done)) + w->bad = 1; + else + w->done = done; + return w->stop_after && w->calls >= w->stop_after; +} + +static void check_watched(rrar_archive_t *a, uint32_t i, const uint8_t *want, + size_t size, const char *name) +{ + uint8_t *dst = (uint8_t *)malloc(size ? size : 1); + watched_t w; + rrar_watch_t watch; + + if (!dst) + return; + memset(&w, 0, sizeof(w)); + memset(dst, 0xA5, size); + w.want = want; + w.dst = dst; + w.size = size; + watch.progress = watch_cb; + watch.ud = &w; + CHECK(rrar_archive_extract_to(a, i, dst, size, &watch) == RRAR_OK, name); + CHECK(!w.bad, name); + CHECK(w.done == size && w.calls > 0, name); + CHECK(!memcmp(dst, want, size), name); + /* too little room is refused, and without a watcher it is the same */ + if (size) + CHECK(rrar_archive_extract_to(a, i, dst, size - 1, NULL) == RRAR_ERROR_PARAM, name); + memset(dst, 0xA5, size); + CHECK(rrar_archive_extract_to(a, i, dst, size, NULL) == RRAR_OK + && !memcmp(dst, want, size), name); + /* stopped by the watcher at its first call: a member long enough to + * have more than one says so */ + if (w.calls > 1) + { + memset(&w, 0, sizeof(w)); + w.want = want; + w.dst = dst; + w.size = size; + w.stop_after = 1; + CHECK(rrar_archive_extract_to(a, i, dst, size, &watch) == RRAR_ERROR_CANCELLED, name); + CHECK(!w.bad && w.calls == 1, name); + } + free(dst); +} + +static void check_mode(const char *dir, const char *file, + const struct expect *want, unsigned num_want, int through_reads) +{ + size_t len = 0; + uint8_t *data = read_file(dir, file, &len); + rrar_archive_t *a = NULL; + file_t f; + uint32_t i; + unsigned w; + + CHECK(data != NULL, file); + if (!data) + return; + f.data = data; + f.len = len; + if (through_reads) + CHECK(rrar_archive_open_read(&a, len, read_cb, &f) == RRAR_OK, file); + else + CHECK(rrar_archive_open(&a, data, len) == RRAR_OK, file); + if (a) + { + for (i = 0; i < rrar_archive_num_entries(a); i++) + { + const rrar_entry_t *e = rrar_archive_entry(a, i); + uint8_t *out = NULL; + size_t out_len = 0; + + if (e->is_dir) + continue; + if (!e->supported) + { + /* said not to be, and refused as that */ + CHECK(rrar_archive_extract(a, i, &out, &out_len) == RRAR_ERROR_UNSUPPORTED + && out == NULL, e->name); + continue; + } + CHECK(rrar_archive_extract(a, i, &out, &out_len) == RRAR_OK, e->name); + CHECK(out != NULL && out_len == e->size, e->name); + if (out && e->has_crc) + CHECK(encoding_crc32(0, out, out_len) == e->crc, e->name); + if (out) + check_watched(a, i, out, out_len, e->name); + free(out); + } + for (w = 0; w < num_want; w++) + { + const rrar_entry_t *e = NULL; + for (i = 0; i < rrar_archive_num_entries(a); i++) + { + e = rrar_archive_entry(a, i); + if (!strcmp(e->name, want[w].name)) + break; + e = NULL; + } + CHECK(e != NULL, want[w].name); + if (e) + { + CHECK((e->is_dir != 0) == (want[w].is_dir != 0), want[w].name); + if (!e->is_dir) + { + CHECK(e->size == want[w].size, want[w].name); + if (e->has_crc) + CHECK(e->crc == want[w].crc, want[w].name); + else if (e->supported) + { + /* no CRC-32 in the archive: the known one, of what + * comes out */ + uint8_t *out = NULL; + size_t out_len = 0; + CHECK(rrar_archive_extract(a, i, &out, &out_len) == RRAR_OK, want[w].name); + if (out) + CHECK(encoding_crc32(0, out, out_len) == want[w].crc, want[w].name); + free(out); + } + CHECK((e->supported != 0) == (want[w].unsupported == 0), want[w].name); + } + } + } + CHECK(rrar_archive_entry(a, rrar_archive_num_entries(a)) == NULL, file); + } + rrar_archive_close(a); + free(data); +} + +/* In memory, and read through the callback. */ +static void check(const char *dir, const char *file, + const struct expect *want, unsigned num_want) +{ + check_mode(dir, file, want, num_want, 0); + check_mode(dir, file, want, num_want, 1); +} + +/* The archive cut short and with bytes changed. */ +static void mangle(const char *dir, const char *file) +{ + size_t len = 0; + uint8_t *data = read_file(dir, file, &len); + uint8_t *copy; + unsigned round; + uint32_t seed = 12345; + + if (!data || !(copy = (uint8_t *)malloc(len))) + { + free(data); + return; + } + for (round = 0; round < 300; round++) + { + rrar_archive_t *a = NULL; + size_t use = len; + uint32_t i; + + memcpy(copy, data, len); + seed = seed * 1103515245u + 12345u; + if (round % 3 == 0) + use = (seed >> 8) % (len + 1); /* cut short */ + else + { + unsigned k, changes = 1 + (seed >> 28); + for (k = 0; k < changes; k++) + { + seed = seed * 1103515245u + 12345u; + copy[(seed >> 8) % len] ^= (uint8_t)(1u << (seed & 7)); + } + } + { + /* Whatever it is taken for in memory, it is taken for the same + * when it is read: the same verdict, the same members, the same + * bytes or the same refusal for each. */ + rrar_archive_t *b = NULL; + file_t f; + int res_a, res_b; + + f.data = copy; + f.len = use; + res_a = rrar_archive_open(&a, copy, use); + res_b = rrar_archive_open_read(&b, use, read_cb, &f); + CHECK(res_a == res_b, file); + CHECK((a != NULL) == (b != NULL), file); + if (a && b) + { + CHECK(rrar_archive_num_entries(a) == rrar_archive_num_entries(b), file); + for (i = 0; i < rrar_archive_num_entries(a) + && i < rrar_archive_num_entries(b); i++) + { + const rrar_entry_t *ea = rrar_archive_entry(a, i); + const rrar_entry_t *eb = rrar_archive_entry(b, i); + uint8_t *oa = NULL, *ob = NULL; + size_t la = 0, lb = 0; + int xa, xb; + + CHECK(!strcmp(ea->name, eb->name) && ea->size == eb->size + && ea->supported == eb->supported && ea->is_dir == eb->is_dir, file); + if (ea->is_dir || ea->size > 64u * 1024 * 1024) + continue; + xa = rrar_archive_extract(a, i, &oa, &la); + xb = rrar_archive_extract(b, i, &ob, &lb); + CHECK(xa == xb && la == lb, file); + if (oa && ob && la == lb) + CHECK(!memcmp(oa, ob, la), file); + free(oa); + free(ob); + } + } + rrar_archive_close(b); + if (res_a != RRAR_OK) + { + rrar_archive_close(a); + continue; + } + } + for (i = 0; i < rrar_archive_num_entries(a); i++) + { + const rrar_entry_t *e = rrar_archive_entry(a, i); + uint8_t *out = NULL; + size_t out_len = 0; + + /* (a size that was changed into gigabytes is only refused for + * want of memory: not what this is after) */ + if (e->is_dir || e->size > 64u * 1024 * 1024) + continue; + if (rrar_archive_extract(a, i, &out, &out_len) == RRAR_OK) + { + CHECK(out_len == e->size, file); + /* (an archive with no CRC-32s has nothing to hold it to) */ + if (e->has_crc) + CHECK(encoding_crc32(0, out, out_len) == e->crc, file); + } + else + CHECK(out == NULL, file); + free(out); + } + rrar_archive_close(a); + } + free(copy); + free(data); +} + +int main(int argc, char **argv) +{ + static const struct expect lowdist[] = { + { "lowdist-reset.bin", 64, 0x6ff838dcu, 0 }, + }; + static const struct expect best[] = { + { "LibarchiveAddingTest.html", 20111, 0x5e05a663u, 0 }, + { "testlink", 25, 0x11fcd3f1u, 0 }, + { "testdir/test.txt", 20, 0xbec8a242u, 0 }, + { "testdir/LibarchiveAddingTest.html", 20111, 0x5e05a663u, 0 }, + { "testdir", 0, 0, 1 }, + { "testemptydir", 0, 0, 1 }, + }; + static const struct expect filter[] = { + { "bsdcat.exe", 204288, 0x4db10349u, 0 }, + }; + static const struct expect unicode[] = { + { "\xe8\xa1\xa8\xe3\x81\xa0\xe3\x82\x88/\xe6\x96\xb0\xe3\x81\x97\xe3\x81\x84\xe3\x83\x95\xe3\x82\xa9\xe3\x83\xab\xe3\x83\x80/" + "\xe6\x96\xb0\xe8\xa6\x8f\xe3\x83\x86\xe3\x82\xad\xe3\x82\xb9\xe3\x83\x88 \xe3\x83\x89\xe3\x82\xad\xe3\x83\xa5\xe3\x83\xa1\xe3\x83\xb3\xe3\x83\x88.txt", + 0, 0, 0 }, + { "\xe8\xa1\xa8\xe3\x81\xa0\xe3\x82\x88/\xe3\x83\x95\xe3\x82\xa1\xe3\x82\xa4\xe3\x83\xab", 54, 0x5dfb8225u, 0 }, + { "abcdefghijklmnopqrs\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt", 16, 0x15b6d005u, 0 }, + { "\xe8\xa1\xa8\xe3\x81\xa0\xe3\x82\x88", 0, 0, 1 }, + }; + static const struct expect conversion[] = { + /* PPMd blocks between LZ blocks, the reading handed over each way */ + { "ppmd_lzss_conversion_test.txt", 241647978, 0xf72b4477u, 0 }, + }; + /* RAR 5 */ + static const struct expect five_stored[] = { + { "helloworld.txt", 29, 0x95a043b4u, 0, 0 }, + }; + static const struct expect five_compressed[] = { + { "test.bin", 1200, 0x7cca70cdu, 0, 0 }, + }; + static const struct expect five_arm[] = { + { "elf-Linux-ARMv7-ls", 90808, 0x886f91ebu, 0, 0 }, + }; + static const struct expect five_solid[] = { + /* the first member of a solid archive stands by itself */ + { "test1.bin", 4096, 0x7e13b2c6u, 0, 0 }, + { "test2.bin", 4096, 0xf166afcbu, 0, 1 }, + { "test4.bin", 4096, 0x10c43ed4u, 0, 1 }, + }; + static const struct expect five_blake2[] = { + { "cebula.txt", 814, 0x7e5ec49eu, 0, 0 }, + }; + const char *dir; + + if (argc > 2) + { + fprintf(stderr, "usage: rrar_archive_test [dir]\n"); + return 2; + } + dir = argc == 2 ? argv[1] : "archives"; + + check(dir, "lowdist_reset.rar", lowdist, sizeof(lowdist) / sizeof(lowdist[0])); + check(dir, "compress_best.rar", best, sizeof(best) / sizeof(best[0])); + check(dir, "filter.rar", filter, sizeof(filter) / sizeof(filter[0])); + check(dir, "unicode.rar", unicode, sizeof(unicode) / sizeof(unicode[0])); + check(dir, "ppmd_lzss_conversion.rar", conversion, sizeof(conversion) / sizeof(conversion[0])); + + check(dir, "rar5_stored.rar", five_stored, sizeof(five_stored) / sizeof(five_stored[0])); + check(dir, "rar5_compressed.rar", five_compressed, sizeof(five_compressed) / sizeof(five_compressed[0])); + check(dir, "rar5_arm.rar", five_arm, sizeof(five_arm) / sizeof(five_arm[0])); + check(dir, "rar5_multiple_files_solid.rar", five_solid, sizeof(five_solid) / sizeof(five_solid[0])); + check(dir, "rar5_blake2.rar", five_blake2, sizeof(five_blake2) / sizeof(five_blake2[0])); + + mangle(dir, "rar5_stored.rar"); + mangle(dir, "rar5_compressed.rar"); + mangle(dir, "rar5_arm.rar"); + mangle(dir, "rar5_multiple_files_solid.rar"); + mangle(dir, "rar5_blake2.rar"); + mangle(dir, "lowdist_reset.rar"); + mangle(dir, "compress_best.rar"); + mangle(dir, "filter.rar"); + mangle(dir, "unicode.rar"); + + { + /* what is no RAR, and what is one of a kind not read */ + rrar_archive_t *a = (rrar_archive_t *)1; + static const uint8_t rar6[] = { 'R', 'a', 'r', '!', 0x1a, 0x07, 0x02, 0x00, 0, 0, 0, 0 }; + static const uint8_t rar5[] = { 'R', 'a', 'r', '!', 0x1a, 0x07, 0x01, 0x00, 0, 0, 0, 0 }; + CHECK(rrar_archive_open(&a, (const uint8_t *)"PK\3\4....", 8) == RRAR_ERROR_DATA && !a, "zip"); + CHECK(rrar_archive_open(&a, rar6, sizeof(rar6)) == RRAR_ERROR_UNSUPPORTED && !a, "a later container"); + CHECK(rrar_archive_open(&a, rar5, sizeof(rar5)) == RRAR_ERROR_DATA && !a, "rar5 with no headers"); + CHECK(rrar_archive_open(&a, rar5, 3) == RRAR_ERROR_PARAM && !a, "short"); + } + + if (failures) + { + printf("%d checks failed\n", failures); + return 1; + } + printf("rrar_archive_test: PASS\n"); + return 0; +} diff --git a/samples/formats/rzip/Makefile b/samples/formats/rzip/Makefile new file mode 100644 index 00000000..335d78f9 --- /dev/null +++ b/samples/formats/rzip/Makefile @@ -0,0 +1,48 @@ +include ../../sample_objdir.mk + +TARGET := rzip_archive_test +# a deflated member read at any offset through an index (rzip_seek) +SEEK := rzip_seek_test + +LIBRETRO_COMM_DIR := ../../.. + +SOURCES := \ + $(LIBRETRO_COMM_DIR)/encodings/encoding_crc32.c \ + $(LIBRETRO_COMM_DIR)/encodings/encoding_deflate.c \ + $(LIBRETRO_COMM_DIR)/features/features_cpu.c \ + $(LIBRETRO_COMM_DIR)/time/rtime.c \ + $(LIBRETRO_COMM_DIR)/formats/zip/rzip_archive.c + +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) + +CFLAGS += -Wall -pedantic -std=gnu99 -g -I$(LIBRETRO_COMM_DIR)/include + +ifneq ($(SANITIZER),) + CFLAGS := -fsanitize=$(SANITIZER) $(CFLAGS) + LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS) +endif + +all: $(TARGET) $(SEEK) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c + @mkdir -p $(@D) + $(CC) $(CFLAGS) -c $< -o $@ + +$(TARGET): rzip_archive_test.o $(OBJS) + $(CC) rzip_archive_test.o $(OBJS) $(LDFLAGS) -o $@ -lm + +$(SEEK): rzip_seek_test.o $(OBJS) + $(CC) rzip_seek_test.o $(OBJS) $(LDFLAGS) -o $@ -lm + +check: $(TARGET) $(SEEK) + ./$(TARGET) + ./$(SEEK) + +clean: + rm -rf $(SAMPLE_OBJDIR) + rm -f $(TARGET) $(SEEK) rzip_archive_test.o rzip_seek_test.o $(OBJS) + +.PHONY: all check clean diff --git a/samples/formats/rzip/rzip_archive_test.c b/samples/formats/rzip/rzip_archive_test.c new file mode 100644 index 00000000..253e3ed6 --- /dev/null +++ b/samples/formats/rzip/rzip_archive_test.c @@ -0,0 +1,1277 @@ +/* rzip_archive against embedded archives, in memory and through a + * positioned-read callback. + * + * The archives hold a.bin (3000 bytes) and b.bin (2000 bytes) with + * contents a test can regenerate: + * + * a[i] = ((i * 2654435761) >> 13) & 0xff + * b[i] = (i * 7) % 251 + * + * stored_zip stored members with extra fields of two lengths and + * a directory entry, b.bin under dir/ + * deflated_zip deflated members, one with an extra field + * zip64_zip 32-bit size and offset slots saturated, a zip64 extra + * field, zip64 end record and locator + * commented_zip an archive comment that contains the end record's + * own signature + * + * Each is opened both ways; every member must come back as its known + * bytes through extract, a stored member of an in-memory archive also + * through the view, and a member read through the callback never + * through it. + * + * Build: make -C libretro-common/samples/formats/rzip check + */ + +#include +#include +#include +#include + +#include + +#define A_LEN 3000 +#define B_LEN 2000 + +static const uint8_t stored_zip[5364] = +{ + 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x21, 0xe1, 0xef, 0x3c, 0xb8, 0x0b, 0x00, 0x00, 0xb8, 0x0b, + 0x00, 0x00, 0x05, 0x00, 0x18, 0x00, 0x61, 0x2e, 0x62, 0x69, 0x6e, 0x55, + 0x54, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x75, 0x78, 0x0b, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbb, 0x77, 0x33, 0xef, 0xab, 0x66, 0x22, 0xde, 0x9a, 0x56, 0x11, 0xcd, + 0x89, 0x45, 0x01, 0xbc, 0x78, 0x34, 0xf0, 0xac, 0x67, 0x23, 0xdf, 0x9b, + 0x57, 0x12, 0xce, 0x8a, 0x46, 0x02, 0xbd, 0x79, 0x35, 0xf1, 0xad, 0x68, + 0x24, 0xe0, 0x9c, 0x58, 0x13, 0xcf, 0x8b, 0x47, 0x03, 0xbe, 0x7a, 0x36, + 0xf2, 0xae, 0x69, 0x25, 0xe1, 0x9d, 0x59, 0x14, 0xd0, 0x8c, 0x48, 0x04, + 0xbf, 0x7b, 0x37, 0xf3, 0xaf, 0x6a, 0x26, 0xe2, 0x9e, 0x5a, 0x16, 0xd1, + 0x8d, 0x49, 0x05, 0xc1, 0x7c, 0x38, 0xf4, 0xb0, 0x6c, 0x27, 0xe3, 0x9f, + 0x5b, 0x17, 0xd2, 0x8e, 0x4a, 0x06, 0xc2, 0x7d, 0x39, 0xf5, 0xb1, 0x6d, + 0x28, 0xe4, 0xa0, 0x5c, 0x18, 0xd3, 0x8f, 0x4b, 0x07, 0xc3, 0x7e, 0x3a, + 0xf6, 0xb2, 0x6e, 0x29, 0xe5, 0xa1, 0x5d, 0x19, 0xd4, 0x90, 0x4c, 0x08, + 0xc4, 0x7f, 0x3b, 0xf7, 0xb3, 0x6f, 0x2a, 0xe6, 0xa2, 0x5e, 0x1a, 0xd5, + 0x91, 0x4d, 0x09, 0xc5, 0x80, 0x3c, 0xf8, 0xb4, 0x70, 0x2c, 0xe7, 0xa3, + 0x5f, 0x1b, 0xd7, 0x92, 0x4e, 0x0a, 0xc6, 0x82, 0x3d, 0xf9, 0xb5, 0x71, + 0x2d, 0xe8, 0xa4, 0x60, 0x1c, 0xd8, 0x93, 0x4f, 0x0b, 0xc7, 0x83, 0x3e, + 0xfa, 0xb6, 0x72, 0x2e, 0xe9, 0xa5, 0x61, 0x1d, 0xd9, 0x94, 0x50, 0x0c, + 0xc8, 0x84, 0x3f, 0xfb, 0xb7, 0x73, 0x2f, 0xea, 0xa6, 0x62, 0x1e, 0xda, + 0x95, 0x51, 0x0d, 0xc9, 0x85, 0x40, 0xfc, 0xb8, 0x74, 0x30, 0xeb, 0xa7, + 0x63, 0x1f, 0xdb, 0x96, 0x52, 0x0e, 0xca, 0x86, 0x42, 0xfd, 0xb9, 0x75, + 0x31, 0xed, 0xa8, 0x64, 0x20, 0xdc, 0x98, 0x53, 0x0f, 0xcb, 0x87, 0x43, + 0xfe, 0xba, 0x76, 0x32, 0xee, 0xa9, 0x65, 0x21, 0xdd, 0x99, 0x54, 0x10, + 0xcc, 0x88, 0x44, 0xff, 0xbb, 0x77, 0x33, 0xef, 0xaa, 0x66, 0x22, 0xde, + 0x9a, 0x55, 0x11, 0xcd, 0x89, 0x45, 0x00, 0xbc, 0x78, 0x34, 0xf0, 0xab, + 0x67, 0x23, 0xdf, 0x9b, 0x56, 0x12, 0xce, 0x8a, 0x46, 0x01, 0xbd, 0x79, + 0x35, 0xf1, 0xac, 0x68, 0x24, 0xe0, 0x9c, 0x58, 0x13, 0xcf, 0x8b, 0x47, + 0x03, 0xbe, 0x7a, 0x36, 0xf2, 0xae, 0x69, 0x25, 0xe1, 0x9d, 0x59, 0x14, + 0xd0, 0x8c, 0x48, 0x04, 0xbf, 0x7b, 0x37, 0xf3, 0xaf, 0x6a, 0x26, 0xe2, + 0x9e, 0x5a, 0x15, 0xd1, 0x8d, 0x49, 0x05, 0xc0, 0x7c, 0x38, 0xf4, 0xb0, + 0x6b, 0x27, 0xe3, 0x9f, 0x5b, 0x16, 0xd2, 0x8e, 0x4a, 0x06, 0xc1, 0x7d, + 0x39, 0xf5, 0xb1, 0x6c, 0x28, 0xe4, 0xa0, 0x5c, 0x17, 0xd3, 0x8f, 0x4b, + 0x07, 0xc2, 0x7e, 0x3a, 0xf6, 0xb2, 0x6e, 0x29, 0xe5, 0xa1, 0x5d, 0x19, + 0xd4, 0x90, 0x4c, 0x08, 0xc4, 0x7f, 0x3b, 0xf7, 0xb3, 0x6f, 0x2a, 0xe6, + 0xa2, 0x5e, 0x1a, 0xd5, 0x91, 0x4d, 0x09, 0xc5, 0x80, 0x3c, 0xf8, 0xb4, + 0x70, 0x2b, 0xe7, 0xa3, 0x5f, 0x1b, 0xd6, 0x92, 0x4e, 0x0a, 0xc6, 0x81, + 0x3d, 0xf9, 0xb5, 0x71, 0x2c, 0xe8, 0xa4, 0x60, 0x1c, 0xd7, 0x93, 0x4f, + 0x0b, 0xc7, 0x82, 0x3e, 0xfa, 0xb6, 0x72, 0x2d, 0xe9, 0xa5, 0x61, 0x1d, + 0xd9, 0x94, 0x50, 0x0c, 0xc8, 0x84, 0x3f, 0xfb, 0xb7, 0x73, 0x2f, 0xea, + 0xa6, 0x62, 0x1e, 0xda, 0x95, 0x51, 0x0d, 0xc9, 0x85, 0x40, 0xfc, 0xb8, + 0x74, 0x30, 0xeb, 0xa7, 0x63, 0x1f, 0xdb, 0x96, 0x52, 0x0e, 0xca, 0x86, + 0x41, 0xfd, 0xb9, 0x75, 0x31, 0xec, 0xa8, 0x64, 0x20, 0xdc, 0x97, 0x53, + 0x0f, 0xcb, 0x87, 0x42, 0xfe, 0xba, 0x76, 0x32, 0xed, 0xa9, 0x65, 0x21, + 0xdd, 0x98, 0x54, 0x10, 0xcc, 0x88, 0x43, 0xff, 0xbb, 0x77, 0x33, 0xef, + 0xaa, 0x66, 0x22, 0xde, 0x9a, 0x55, 0x11, 0xcd, 0x89, 0x45, 0x00, 0xbc, + 0x78, 0x34, 0xf0, 0xab, 0x67, 0x23, 0xdf, 0x9b, 0x56, 0x12, 0xce, 0x8a, + 0x46, 0x01, 0xbd, 0x79, 0x35, 0xf1, 0xac, 0x68, 0x24, 0xe0, 0x9c, 0x57, + 0x13, 0xcf, 0x8b, 0x47, 0x02, 0xbe, 0x7a, 0x36, 0xf2, 0xad, 0x69, 0x25, + 0xe1, 0x9d, 0x58, 0x14, 0xd0, 0x8c, 0x48, 0x03, 0xbf, 0x7b, 0x37, 0xf3, + 0xae, 0x6a, 0x26, 0xe2, 0x9e, 0x59, 0x15, 0xd1, 0x8d, 0x49, 0x05, 0xc0, + 0x7c, 0x38, 0xf4, 0xb0, 0x6b, 0x27, 0xe3, 0x9f, 0x5b, 0x16, 0xd2, 0x8e, + 0x4a, 0x06, 0xc1, 0x7d, 0x39, 0xf5, 0xb1, 0x6c, 0x28, 0xe4, 0xa0, 0x5c, + 0x17, 0xd3, 0x8f, 0x4b, 0x07, 0xc2, 0x7e, 0x3a, 0xf6, 0xb2, 0x6d, 0x29, + 0xe5, 0xa1, 0x5d, 0x18, 0xd4, 0x90, 0x4c, 0x08, 0xc3, 0x7f, 0x3b, 0xf7, + 0xb3, 0x6e, 0x2a, 0xe6, 0xa2, 0x5e, 0x19, 0xd5, 0x91, 0x4d, 0x09, 0xc4, + 0x80, 0x3c, 0xf8, 0xb4, 0x6f, 0x2b, 0xe7, 0xa3, 0x5f, 0x1b, 0xd6, 0x92, + 0x4e, 0x0a, 0xc6, 0x81, 0x3d, 0xf9, 0xb5, 0x71, 0x2c, 0xe8, 0xa4, 0x60, + 0x1c, 0xd7, 0x93, 0x4f, 0x0b, 0xc7, 0x82, 0x3e, 0xfa, 0xb6, 0x72, 0x2d, + 0xe9, 0xa5, 0x61, 0x1d, 0xd8, 0x94, 0x50, 0x0c, 0xc8, 0x83, 0x3f, 0xfb, + 0xb7, 0x73, 0x2e, 0xea, 0xa6, 0x62, 0x1e, 0xd9, 0x95, 0x51, 0x0d, 0xc9, + 0x84, 0x40, 0xfc, 0xb8, 0x74, 0x2f, 0xeb, 0xa7, 0x63, 0x1f, 0xda, 0x96, + 0x52, 0x0e, 0xca, 0x85, 0x41, 0xfd, 0xb9, 0x75, 0x31, 0xec, 0xa8, 0x64, + 0x20, 0xdc, 0x97, 0x53, 0x0f, 0xcb, 0x87, 0x42, 0xfe, 0xba, 0x76, 0x32, + 0xed, 0xa9, 0x65, 0x21, 0xdd, 0x98, 0x54, 0x10, 0xcc, 0x88, 0x43, 0xff, + 0xbb, 0x77, 0x33, 0xee, 0xaa, 0x66, 0x22, 0xde, 0x99, 0x55, 0x11, 0xcd, + 0x89, 0x44, 0x00, 0xbc, 0x78, 0x34, 0xef, 0xab, 0x67, 0x23, 0xdf, 0x9a, + 0x56, 0x12, 0xce, 0x8a, 0x45, 0x01, 0xbd, 0x79, 0x35, 0xf0, 0xac, 0x68, + 0x24, 0xe0, 0x9c, 0x57, 0x13, 0xcf, 0x8b, 0x47, 0x02, 0xbe, 0x7a, 0x36, + 0xf2, 0xad, 0x69, 0x25, 0xe1, 0x9d, 0x58, 0x14, 0xd0, 0x8c, 0x48, 0x03, + 0xbf, 0x7b, 0x37, 0xf3, 0xae, 0x6a, 0x26, 0xe2, 0x9e, 0x59, 0x15, 0xd1, + 0x8d, 0x49, 0x04, 0xc0, 0x7c, 0x38, 0xf4, 0xaf, 0x6b, 0x27, 0xe3, 0x9f, + 0x5a, 0x16, 0xd2, 0x8e, 0x4a, 0x05, 0xc1, 0x7d, 0x39, 0xf5, 0xb0, 0x6c, + 0x28, 0xe4, 0xa0, 0x5b, 0x17, 0xd3, 0x8f, 0x4b, 0x06, 0xc2, 0x7e, 0x3a, + 0xf6, 0xb2, 0x6d, 0x29, 0xe5, 0xa1, 0x5d, 0x18, 0xd4, 0x90, 0x4c, 0x08, + 0xc3, 0x7f, 0x3b, 0xf7, 0xb3, 0x6e, 0x2a, 0xe6, 0xa2, 0x5e, 0x19, 0xd5, + 0x91, 0x4d, 0x09, 0xc4, 0x80, 0x3c, 0xf8, 0xb4, 0x6f, 0x2b, 0xe7, 0xa3, + 0x5f, 0x1a, 0xd6, 0x92, 0x4e, 0x0a, 0xc5, 0x81, 0x3d, 0xf9, 0xb5, 0x70, + 0x2c, 0xe8, 0xa4, 0x60, 0x1b, 0xd7, 0x93, 0x4f, 0x0b, 0xc6, 0x82, 0x3e, + 0xfa, 0xb6, 0x71, 0x2d, 0xe9, 0xa5, 0x61, 0x1c, 0xd8, 0x94, 0x50, 0x0c, + 0xc8, 0x83, 0x3f, 0xfb, 0xb7, 0x73, 0x2e, 0xea, 0xa6, 0x62, 0x1e, 0xd9, + 0x95, 0x51, 0x0d, 0xc9, 0x84, 0x40, 0xfc, 0xb8, 0x74, 0x2f, 0xeb, 0xa7, + 0x63, 0x1f, 0xda, 0x96, 0x52, 0x0e, 0xca, 0x85, 0x41, 0xfd, 0xb9, 0x75, + 0x30, 0xec, 0xa8, 0x64, 0x20, 0xdb, 0x97, 0x53, 0x0f, 0xcb, 0x86, 0x42, + 0xfe, 0xba, 0x76, 0x31, 0xed, 0xa9, 0x65, 0x21, 0xdc, 0x98, 0x54, 0x10, + 0xcc, 0x87, 0x43, 0xff, 0xbb, 0x77, 0x32, 0xee, 0xaa, 0x66, 0x22, 0xde, + 0x99, 0x55, 0x11, 0xcd, 0x89, 0x44, 0x00, 0xbc, 0x78, 0x34, 0xef, 0xab, + 0x67, 0x23, 0xdf, 0x9a, 0x56, 0x12, 0xce, 0x8a, 0x45, 0x01, 0xbd, 0x79, + 0x35, 0xf0, 0xac, 0x68, 0x24, 0xe0, 0x9b, 0x57, 0x13, 0xcf, 0x8b, 0x46, + 0x02, 0xbe, 0x7a, 0x36, 0xf1, 0xad, 0x69, 0x25, 0xe1, 0x9c, 0x58, 0x14, + 0xd0, 0x8c, 0x47, 0x03, 0xbf, 0x7b, 0x37, 0xf2, 0xae, 0x6a, 0x26, 0xe2, + 0x9d, 0x59, 0x15, 0xd1, 0x8d, 0x48, 0x04, 0xc0, 0x7c, 0x38, 0xf4, 0xaf, + 0x6b, 0x27, 0xe3, 0x9f, 0x5a, 0x16, 0xd2, 0x8e, 0x4a, 0x05, 0xc1, 0x7d, + 0x39, 0xf5, 0xb0, 0x6c, 0x28, 0xe4, 0xa0, 0x5b, 0x17, 0xd3, 0x8f, 0x4b, + 0x06, 0xc2, 0x7e, 0x3a, 0xf6, 0xb1, 0x6d, 0x29, 0xe5, 0xa1, 0x5c, 0x18, + 0xd4, 0x90, 0x4c, 0x07, 0xc3, 0x7f, 0x3b, 0xf7, 0xb2, 0x6e, 0x2a, 0xe6, + 0xa2, 0x5d, 0x19, 0xd5, 0x91, 0x4d, 0x08, 0xc4, 0x80, 0x3c, 0xf8, 0xb3, + 0x6f, 0x2b, 0xe7, 0xa3, 0x5f, 0x1a, 0xd6, 0x92, 0x4e, 0x0a, 0xc5, 0x81, + 0x3d, 0xf9, 0xb5, 0x70, 0x2c, 0xe8, 0xa4, 0x60, 0x1b, 0xd7, 0x93, 0x4f, + 0x0b, 0xc6, 0x82, 0x3e, 0xfa, 0xb6, 0x71, 0x2d, 0xe9, 0xa5, 0x61, 0x1c, + 0xd8, 0x94, 0x50, 0x0c, 0xc7, 0x83, 0x3f, 0xfb, 0xb7, 0x72, 0x2e, 0xea, + 0xa6, 0x62, 0x1d, 0xd9, 0x95, 0x51, 0x0d, 0xc8, 0x84, 0x40, 0xfc, 0xb8, + 0x73, 0x2f, 0xeb, 0xa7, 0x63, 0x1e, 0xda, 0x96, 0x52, 0x0e, 0xc9, 0x85, + 0x41, 0xfd, 0xb9, 0x75, 0x30, 0xec, 0xa8, 0x64, 0x20, 0xdb, 0x97, 0x53, + 0x0f, 0xcb, 0x86, 0x42, 0xfe, 0xba, 0x76, 0x31, 0xed, 0xa9, 0x65, 0x21, + 0xdc, 0x98, 0x54, 0x10, 0xcc, 0x87, 0x43, 0xff, 0xbb, 0x77, 0x32, 0xee, + 0xaa, 0x66, 0x22, 0xdd, 0x99, 0x55, 0x11, 0xcd, 0x88, 0x44, 0x00, 0xbc, + 0x78, 0x33, 0xef, 0xab, 0x67, 0x23, 0xde, 0x9a, 0x56, 0x12, 0xce, 0x89, + 0x45, 0x01, 0xbd, 0x79, 0x34, 0xf0, 0xac, 0x68, 0x24, 0xdf, 0x9b, 0x57, + 0x13, 0xcf, 0x8b, 0x46, 0x02, 0xbe, 0x7a, 0x36, 0xf1, 0xad, 0x69, 0x25, + 0xe1, 0x9c, 0x58, 0x14, 0xd0, 0x8c, 0x47, 0x03, 0xbf, 0x7b, 0x37, 0xf2, + 0xae, 0x6a, 0x26, 0xe2, 0x9d, 0x59, 0x15, 0xd1, 0x8d, 0x48, 0x04, 0xc0, + 0x7c, 0x38, 0xf3, 0xaf, 0x6b, 0x27, 0xe3, 0x9e, 0x5a, 0x16, 0xd2, 0x8e, + 0x49, 0x05, 0xc1, 0x7d, 0x39, 0xf4, 0xb0, 0x6c, 0x28, 0xe4, 0x9f, 0x5b, + 0x17, 0xd3, 0x8f, 0x4a, 0x06, 0xc2, 0x7e, 0x3a, 0xf5, 0xb1, 0x6d, 0x29, + 0xe5, 0xa1, 0x5c, 0x18, 0xd4, 0x90, 0x4c, 0x07, 0xc3, 0x7f, 0x3b, 0xf7, + 0xb2, 0x6e, 0x2a, 0xe6, 0xa2, 0x5d, 0x19, 0xd5, 0x91, 0x4d, 0x08, 0xc4, + 0x80, 0x3c, 0xf8, 0xb3, 0x6f, 0x2b, 0xe7, 0xa3, 0x5e, 0x1a, 0xd6, 0x92, + 0x4e, 0x09, 0xc5, 0x81, 0x3d, 0xf9, 0xb4, 0x70, 0x2c, 0xe8, 0xa4, 0x5f, + 0x1b, 0xd7, 0x93, 0x4f, 0x0a, 0xc6, 0x82, 0x3e, 0xfa, 0xb5, 0x71, 0x2d, + 0xe9, 0xa5, 0x60, 0x1c, 0xd8, 0x94, 0x50, 0x0b, 0xc7, 0x83, 0x3f, 0xfb, + 0xb7, 0x72, 0x2e, 0xea, 0xa6, 0x62, 0x1d, 0xd9, 0x95, 0x51, 0x0d, 0xc8, + 0x84, 0x40, 0xfc, 0xb8, 0x73, 0x2f, 0xeb, 0xa7, 0x63, 0x1e, 0xda, 0x96, + 0x52, 0x0e, 0xc9, 0x85, 0x41, 0xfd, 0xb9, 0x74, 0x30, 0xec, 0xa8, 0x64, + 0x1f, 0xdb, 0x97, 0x53, 0x0f, 0xca, 0x86, 0x42, 0xfe, 0xba, 0x75, 0x31, + 0xed, 0xa9, 0x65, 0x20, 0xdc, 0x98, 0x54, 0x10, 0xcb, 0x87, 0x43, 0xff, + 0xbb, 0x76, 0x32, 0xee, 0xaa, 0x66, 0x22, 0xdd, 0x99, 0x55, 0x11, 0xcd, + 0x88, 0x44, 0x00, 0xbc, 0x78, 0x33, 0xef, 0xab, 0x67, 0x23, 0xde, 0x9a, + 0x56, 0x12, 0xce, 0x89, 0x45, 0x01, 0xbd, 0x79, 0x34, 0xf0, 0xac, 0x68, + 0x24, 0xdf, 0x9b, 0x57, 0x13, 0xcf, 0x8a, 0x46, 0x02, 0xbe, 0x7a, 0x35, + 0xf1, 0xad, 0x69, 0x25, 0xe0, 0x9c, 0x58, 0x14, 0xd0, 0x8b, 0x47, 0x03, + 0xbf, 0x7b, 0x36, 0xf2, 0xae, 0x6a, 0x26, 0xe1, 0x9d, 0x59, 0x15, 0xd1, + 0x8c, 0x48, 0x04, 0xc0, 0x7c, 0x38, 0xf3, 0xaf, 0x6b, 0x27, 0xe3, 0x9e, + 0x5a, 0x16, 0xd2, 0x8e, 0x49, 0x05, 0xc1, 0x7d, 0x39, 0xf4, 0xb0, 0x6c, + 0x28, 0xe4, 0x9f, 0x5b, 0x17, 0xd3, 0x8f, 0x4a, 0x06, 0xc2, 0x7e, 0x3a, + 0xf5, 0xb1, 0x6d, 0x29, 0xe5, 0xa0, 0x5c, 0x18, 0xd4, 0x90, 0x4b, 0x07, + 0xc3, 0x7f, 0x3b, 0xf6, 0xb2, 0x6e, 0x2a, 0xe6, 0xa1, 0x5d, 0x19, 0xd5, + 0x91, 0x4c, 0x08, 0xc4, 0x80, 0x3c, 0xf7, 0xb3, 0x6f, 0x2b, 0xe7, 0xa2, + 0x5e, 0x1a, 0xd6, 0x92, 0x4e, 0x09, 0xc5, 0x81, 0x3d, 0xf9, 0xb4, 0x70, + 0x2c, 0xe8, 0xa4, 0x5f, 0x1b, 0xd7, 0x93, 0x4f, 0x0a, 0xc6, 0x82, 0x3e, + 0xfa, 0xb5, 0x71, 0x2d, 0xe9, 0xa5, 0x60, 0x1c, 0xd8, 0x94, 0x50, 0x0b, + 0xc7, 0x83, 0x3f, 0xfb, 0xb6, 0x72, 0x2e, 0xea, 0xa6, 0x61, 0x1d, 0xd9, + 0x95, 0x51, 0x0c, 0xc8, 0x84, 0x40, 0xfc, 0xb7, 0x73, 0x2f, 0xeb, 0xa7, + 0x62, 0x1e, 0xda, 0x96, 0x52, 0x0d, 0xc9, 0x85, 0x41, 0xfd, 0xb8, 0x74, + 0x30, 0xec, 0xa8, 0x64, 0x1f, 0xdb, 0x97, 0x53, 0x0f, 0xca, 0x86, 0x42, + 0xfe, 0xba, 0x75, 0x31, 0xed, 0xa9, 0x65, 0x20, 0xdc, 0x98, 0x54, 0x10, + 0xcb, 0x87, 0x43, 0xff, 0xbb, 0x76, 0x32, 0xee, 0xaa, 0x66, 0x21, 0xdd, + 0x99, 0x55, 0x11, 0xcc, 0x88, 0x44, 0x00, 0xbc, 0x77, 0x33, 0xef, 0xab, + 0x67, 0x22, 0xde, 0x9a, 0x56, 0x12, 0xcd, 0x89, 0x45, 0x01, 0xbd, 0x78, + 0x34, 0xf0, 0xac, 0x68, 0x23, 0xdf, 0x9b, 0x57, 0x13, 0xce, 0x8a, 0x46, + 0x02, 0xbe, 0x7a, 0x35, 0xf1, 0xad, 0x69, 0x25, 0xe0, 0x9c, 0x58, 0x14, + 0xd0, 0x8b, 0x47, 0x03, 0xbf, 0x7b, 0x36, 0xf2, 0xae, 0x6a, 0x26, 0xe1, + 0x9d, 0x59, 0x15, 0xd1, 0x8c, 0x48, 0x04, 0xc0, 0x7c, 0x37, 0xf3, 0xaf, + 0x6b, 0x27, 0xe2, 0x9e, 0x5a, 0x16, 0xd2, 0x8d, 0x49, 0x05, 0xc1, 0x7d, + 0x38, 0xf4, 0xb0, 0x6c, 0x28, 0xe3, 0x9f, 0x5b, 0x17, 0xd3, 0x8e, 0x4a, + 0x06, 0xc2, 0x7e, 0x39, 0xf5, 0xb1, 0x6d, 0x29, 0xe5, 0xa0, 0x5c, 0x18, + 0xd4, 0x90, 0x4b, 0x07, 0xc3, 0x7f, 0x3b, 0xf6, 0xb2, 0x6e, 0x2a, 0xe6, + 0xa1, 0x5d, 0x19, 0xd5, 0x91, 0x4c, 0x08, 0xc4, 0x80, 0x3c, 0xf7, 0xb3, + 0x6f, 0x2b, 0xe7, 0xa2, 0x5e, 0x1a, 0xd6, 0x92, 0x4d, 0x09, 0xc5, 0x81, + 0x3d, 0xf8, 0xb4, 0x70, 0x2c, 0xe8, 0xa3, 0x5f, 0x1b, 0xd7, 0x93, 0x4e, + 0x0a, 0xc6, 0x82, 0x3e, 0xf9, 0xb5, 0x71, 0x2d, 0xe9, 0xa4, 0x60, 0x1c, + 0xd8, 0x94, 0x4f, 0x0b, 0xc7, 0x83, 0x3f, 0xfb, 0xb6, 0x72, 0x2e, 0xea, + 0xa6, 0x61, 0x1d, 0xd9, 0x95, 0x51, 0x0c, 0xc8, 0x84, 0x40, 0xfc, 0xb7, + 0x73, 0x2f, 0xeb, 0xa7, 0x62, 0x1e, 0xda, 0x96, 0x52, 0x0d, 0xc9, 0x85, + 0x41, 0xfd, 0xb8, 0x74, 0x30, 0xec, 0xa8, 0x63, 0x1f, 0xdb, 0x97, 0x53, + 0x0e, 0xca, 0x86, 0x42, 0xfe, 0xb9, 0x75, 0x31, 0xed, 0xa9, 0x64, 0x20, + 0xdc, 0x98, 0x54, 0x0f, 0xcb, 0x87, 0x43, 0xff, 0xba, 0x76, 0x32, 0xee, + 0xaa, 0x65, 0x21, 0xdd, 0x99, 0x55, 0x11, 0xcc, 0x88, 0x44, 0x00, 0xbc, + 0x77, 0x33, 0xef, 0xab, 0x67, 0x22, 0xde, 0x9a, 0x56, 0x12, 0xcd, 0x89, + 0x45, 0x01, 0xbd, 0x78, 0x34, 0xf0, 0xac, 0x68, 0x23, 0xdf, 0x9b, 0x57, + 0x13, 0xce, 0x8a, 0x46, 0x02, 0xbe, 0x79, 0x35, 0xf1, 0xad, 0x69, 0x24, + 0xe0, 0x9c, 0x58, 0x14, 0xcf, 0x8b, 0x47, 0x03, 0xbf, 0x7a, 0x36, 0xf2, + 0xae, 0x6a, 0x25, 0xe1, 0x9d, 0x59, 0x15, 0xd0, 0x8c, 0x48, 0x04, 0xc0, + 0x7b, 0x37, 0xf3, 0xaf, 0x6b, 0x27, 0xe2, 0x9e, 0x5a, 0x16, 0xd2, 0x8d, + 0x49, 0x05, 0xc1, 0x7d, 0x38, 0xf4, 0xb0, 0x6c, 0x28, 0xe3, 0x9f, 0x5b, + 0x17, 0xd3, 0x8e, 0x4a, 0x06, 0xc2, 0x7e, 0x39, 0xf5, 0xb1, 0x6d, 0x29, + 0xe4, 0xa0, 0x5c, 0x18, 0xd4, 0x8f, 0x4b, 0x07, 0xc3, 0x7f, 0x3a, 0xf6, + 0xb2, 0x6e, 0x2a, 0xe5, 0xa1, 0x5d, 0x19, 0xd5, 0x90, 0x4c, 0x08, 0xc4, + 0x80, 0x3b, 0xf7, 0xb3, 0x6f, 0x2b, 0xe6, 0xa2, 0x5e, 0x1a, 0xd6, 0x91, + 0x4d, 0x09, 0xc5, 0x81, 0x3d, 0xf8, 0xb4, 0x70, 0x2c, 0xe8, 0xa3, 0x5f, + 0x1b, 0xd7, 0x93, 0x4e, 0x0a, 0xc6, 0x82, 0x3e, 0xf9, 0xb5, 0x71, 0x2d, + 0xe9, 0xa4, 0x60, 0x1c, 0xd8, 0x94, 0x4f, 0x0b, 0xc7, 0x83, 0x3f, 0xfa, + 0xb6, 0x72, 0x2e, 0xea, 0xa5, 0x61, 0x1d, 0xd9, 0x95, 0x50, 0x0c, 0xc8, + 0x84, 0x40, 0xfb, 0xb7, 0x73, 0x2f, 0xeb, 0xa6, 0x62, 0x1e, 0xda, 0x96, + 0x51, 0x0d, 0xc9, 0x85, 0x41, 0xfc, 0xb8, 0x74, 0x30, 0xec, 0xa8, 0x63, + 0x1f, 0xdb, 0x97, 0x53, 0x0e, 0xca, 0x86, 0x42, 0xfe, 0xb9, 0x75, 0x31, + 0xed, 0xa9, 0x64, 0x20, 0xdc, 0x98, 0x54, 0x0f, 0xcb, 0x87, 0x43, 0xff, + 0xba, 0x76, 0x32, 0xee, 0xaa, 0x65, 0x21, 0xdd, 0x99, 0x55, 0x10, 0xcc, + 0x88, 0x44, 0x00, 0xbb, 0x77, 0x33, 0xef, 0xab, 0x66, 0x22, 0xde, 0x9a, + 0x56, 0x11, 0xcd, 0x89, 0x45, 0x01, 0xbc, 0x78, 0x34, 0xf0, 0xac, 0x67, + 0x23, 0xdf, 0x9b, 0x57, 0x12, 0xce, 0x8a, 0x46, 0x02, 0xbe, 0x79, 0x35, + 0xf1, 0xad, 0x69, 0x24, 0xe0, 0x9c, 0x58, 0x14, 0xcf, 0x8b, 0x47, 0x03, + 0xbf, 0x7a, 0x36, 0xf2, 0xae, 0x6a, 0x25, 0xe1, 0x9d, 0x59, 0x15, 0xd0, + 0x8c, 0x48, 0x04, 0xc0, 0x7b, 0x37, 0xf3, 0xaf, 0x6b, 0x26, 0xe2, 0x9e, + 0x5a, 0x16, 0xd1, 0x8d, 0x49, 0x05, 0xc1, 0x7c, 0x38, 0xf4, 0xb0, 0x6c, + 0x27, 0xe3, 0x9f, 0x5b, 0x17, 0xd2, 0x8e, 0x4a, 0x06, 0xc2, 0x7d, 0x39, + 0xf5, 0xb1, 0x6d, 0x28, 0xe4, 0xa0, 0x5c, 0x18, 0xd4, 0x8f, 0x4b, 0x07, + 0xc3, 0x7f, 0x3a, 0xf6, 0xb2, 0x6e, 0x2a, 0xe5, 0xa1, 0x5d, 0x19, 0xd5, + 0x90, 0x4c, 0x08, 0xc4, 0x80, 0x3b, 0xf7, 0xb3, 0x6f, 0x2b, 0xe6, 0xa2, + 0x5e, 0x1a, 0xd6, 0x91, 0x4d, 0x09, 0xc5, 0x81, 0x3c, 0xf8, 0xb4, 0x70, + 0x2c, 0xe7, 0xa3, 0x5f, 0x1b, 0xd7, 0x92, 0x4e, 0x0a, 0xc6, 0x82, 0x3d, + 0xf9, 0xb5, 0x71, 0x2d, 0xe8, 0xa4, 0x60, 0x1c, 0xd8, 0x93, 0x4f, 0x0b, + 0xc7, 0x83, 0x3e, 0xfa, 0xb6, 0x72, 0x2e, 0xea, 0xa5, 0x61, 0x1d, 0xd9, + 0x95, 0x50, 0x0c, 0xc8, 0x84, 0x40, 0xfb, 0xb7, 0x73, 0x2f, 0xeb, 0xa6, + 0x62, 0x1e, 0xda, 0x96, 0x51, 0x0d, 0xc9, 0x85, 0x41, 0xfc, 0xb8, 0x74, + 0x30, 0xec, 0xa7, 0x63, 0x1f, 0xdb, 0x97, 0x52, 0x0e, 0xca, 0x86, 0x42, + 0xfd, 0xb9, 0x75, 0x31, 0xed, 0xa8, 0x64, 0x20, 0xdc, 0x98, 0x53, 0x0f, + 0xcb, 0x87, 0x43, 0xfe, 0xba, 0x76, 0x32, 0xee, 0xa9, 0x65, 0x21, 0xdd, + 0x99, 0x54, 0x10, 0xcc, 0x88, 0x44, 0x00, 0xbb, 0x77, 0x33, 0xef, 0xab, + 0x66, 0x22, 0xde, 0x9a, 0x56, 0x11, 0xcd, 0x89, 0x45, 0x01, 0xbc, 0x78, + 0x34, 0xf0, 0xac, 0x67, 0x23, 0xdf, 0x9b, 0x57, 0x12, 0xce, 0x8a, 0x46, + 0x02, 0xbd, 0x79, 0x35, 0xf1, 0xad, 0x68, 0x24, 0xe0, 0x9c, 0x58, 0x13, + 0xcf, 0x8b, 0x47, 0x03, 0xbe, 0x7a, 0x36, 0xf2, 0xae, 0x69, 0x25, 0xe1, + 0x9d, 0x59, 0x14, 0xd0, 0x8c, 0x48, 0x04, 0xbf, 0x7b, 0x37, 0xf3, 0xaf, + 0x6b, 0x26, 0xe2, 0x9e, 0x5a, 0x16, 0xd1, 0x8d, 0x49, 0x05, 0xc1, 0x7c, + 0x38, 0xf4, 0xb0, 0x6c, 0x27, 0xe3, 0x9f, 0x5b, 0x17, 0xd2, 0x8e, 0x4a, + 0x06, 0xc2, 0x7d, 0x39, 0xf5, 0xb1, 0x6d, 0x28, 0xe4, 0xa0, 0x5c, 0x18, + 0xd3, 0x8f, 0x4b, 0x07, 0xc3, 0x7e, 0x3a, 0xf6, 0xb2, 0x6e, 0x29, 0xe5, + 0xa1, 0x5d, 0x19, 0xd4, 0x90, 0x4c, 0x08, 0xc4, 0x7f, 0x3b, 0xf7, 0xb3, + 0x6f, 0x2a, 0xe6, 0xa2, 0x5e, 0x1a, 0xd5, 0x91, 0x4d, 0x09, 0xc5, 0x81, + 0x3c, 0xf8, 0xb4, 0x70, 0x2c, 0xe7, 0xa3, 0x5f, 0x1b, 0xd7, 0x92, 0x4e, + 0x0a, 0xc6, 0x82, 0x3d, 0xf9, 0xb5, 0x71, 0x2d, 0xe8, 0xa4, 0x60, 0x1c, + 0xd8, 0x93, 0x4f, 0x0b, 0xc7, 0x83, 0x3e, 0xfa, 0xb6, 0x72, 0x2e, 0xe9, + 0xa5, 0x61, 0x1d, 0xd9, 0x94, 0x50, 0x0c, 0xc8, 0x84, 0x3f, 0xfb, 0xb7, + 0x73, 0x2f, 0xea, 0xa6, 0x62, 0x1e, 0xda, 0x95, 0x51, 0x0d, 0xc9, 0x85, + 0x40, 0xfc, 0xb8, 0x74, 0x30, 0xeb, 0xa7, 0x63, 0x1f, 0xdb, 0x97, 0x52, + 0x0e, 0xca, 0x86, 0x42, 0xfd, 0xb9, 0x75, 0x31, 0xed, 0xa8, 0x64, 0x20, + 0xdc, 0x98, 0x53, 0x0f, 0xcb, 0x87, 0x43, 0xfe, 0xba, 0x76, 0x32, 0xee, + 0xa9, 0x65, 0x21, 0xdd, 0x99, 0x54, 0x10, 0xcc, 0x88, 0x44, 0xff, 0xbb, + 0x77, 0x33, 0xef, 0xaa, 0x66, 0x22, 0xde, 0x9a, 0x55, 0x11, 0xcd, 0x89, + 0x45, 0x00, 0xbc, 0x78, 0x34, 0xf0, 0xab, 0x67, 0x23, 0xdf, 0x9b, 0x56, + 0x12, 0xce, 0x8a, 0x46, 0x01, 0xbd, 0x79, 0x35, 0xf1, 0xad, 0x68, 0x24, + 0xe0, 0x9c, 0x58, 0x13, 0xcf, 0x8b, 0x47, 0x03, 0xbe, 0x7a, 0x36, 0xf2, + 0xae, 0x69, 0x25, 0xe1, 0x9d, 0x59, 0x14, 0xd0, 0x8c, 0x48, 0x04, 0xbf, + 0x7b, 0x37, 0xf3, 0xaf, 0x6a, 0x26, 0xe2, 0x9e, 0x5a, 0x15, 0xd1, 0x8d, + 0x49, 0x05, 0xc0, 0x7c, 0x38, 0xf4, 0xb0, 0x6b, 0x27, 0xe3, 0x9f, 0x5b, + 0x16, 0xd2, 0x8e, 0x4a, 0x06, 0xc1, 0x7d, 0x39, 0xf5, 0xb1, 0x6c, 0x28, + 0xe4, 0xa0, 0x5c, 0x17, 0xd3, 0x8f, 0x4b, 0x07, 0xc3, 0x7e, 0x3a, 0xf6, + 0xb2, 0x6e, 0x29, 0xe5, 0xa1, 0x5d, 0x19, 0xd4, 0x90, 0x4c, 0x08, 0xc4, + 0x7f, 0x3b, 0xf7, 0xb3, 0x6f, 0x2a, 0xe6, 0xa2, 0x5e, 0x1a, 0xd5, 0x91, + 0x4d, 0x09, 0xc5, 0x80, 0x3c, 0xf8, 0xb4, 0x70, 0x2b, 0xe7, 0xa3, 0x5f, + 0x1b, 0xd6, 0x92, 0x4e, 0x0a, 0xc6, 0x81, 0x3d, 0xf9, 0xb5, 0x71, 0x2c, + 0xe8, 0xa4, 0x60, 0x1c, 0xd7, 0x93, 0x4f, 0x0b, 0xc7, 0x82, 0x3e, 0xfa, + 0xb6, 0x72, 0x2e, 0xe9, 0xa5, 0x61, 0x1d, 0xd9, 0x94, 0x50, 0x0c, 0xc8, + 0x84, 0x3f, 0xfb, 0xb7, 0x73, 0x2f, 0xea, 0xa6, 0x62, 0x1e, 0xda, 0x95, + 0x51, 0x0d, 0xc9, 0x85, 0x40, 0xfc, 0xb8, 0x74, 0x30, 0xeb, 0xa7, 0x63, + 0x1f, 0xdb, 0x96, 0x52, 0x0e, 0xca, 0x86, 0x41, 0xfd, 0xb9, 0x75, 0x31, + 0xec, 0xa8, 0x64, 0x20, 0xdc, 0x97, 0x53, 0x0f, 0xcb, 0x87, 0x42, 0xfe, + 0xba, 0x76, 0x32, 0xed, 0xa9, 0x65, 0x21, 0xdd, 0x98, 0x54, 0x10, 0xcc, + 0x88, 0x44, 0xff, 0xbb, 0x77, 0x33, 0xef, 0xaa, 0x66, 0x22, 0xde, 0x9a, + 0x55, 0x11, 0xcd, 0x89, 0x45, 0x00, 0xbc, 0x78, 0x34, 0xf0, 0xab, 0x67, + 0x23, 0xdf, 0x9b, 0x56, 0x12, 0xce, 0x8a, 0x46, 0x01, 0xbd, 0x79, 0x35, + 0xf1, 0xac, 0x68, 0x24, 0xe0, 0x9c, 0x57, 0x13, 0xcf, 0x8b, 0x47, 0x02, + 0xbe, 0x7a, 0x36, 0xf2, 0xad, 0x69, 0x25, 0xe1, 0x9d, 0x58, 0x14, 0x50, + 0x4b, 0x03, 0x04, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x64, 0x69, 0x72, 0x2f, 0x50, 0x4b, 0x03, + 0x04, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x80, + 0x6c, 0x19, 0x34, 0xd0, 0x07, 0x00, 0x00, 0xd0, 0x07, 0x00, 0x00, 0x09, + 0x00, 0x0f, 0x00, 0x64, 0x69, 0x72, 0x2f, 0x62, 0x2e, 0x62, 0x69, 0x6e, + 0x75, 0x78, 0x0b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, + 0x3f, 0x46, 0x4d, 0x54, 0x5b, 0x62, 0x69, 0x70, 0x77, 0x7e, 0x85, 0x8c, + 0x93, 0x9a, 0xa1, 0xa8, 0xaf, 0xb6, 0xbd, 0xc4, 0xcb, 0xd2, 0xd9, 0xe0, + 0xe7, 0xee, 0xf5, 0x01, 0x08, 0x0f, 0x16, 0x1d, 0x24, 0x2b, 0x32, 0x39, + 0x40, 0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x7f, 0x86, 0x8d, + 0x94, 0x9b, 0xa2, 0xa9, 0xb0, 0xb7, 0xbe, 0xc5, 0xcc, 0xd3, 0xda, 0xe1, + 0xe8, 0xef, 0xf6, 0x02, 0x09, 0x10, 0x17, 0x1e, 0x25, 0x2c, 0x33, 0x3a, + 0x41, 0x48, 0x4f, 0x56, 0x5d, 0x64, 0x6b, 0x72, 0x79, 0x80, 0x87, 0x8e, + 0x95, 0x9c, 0xa3, 0xaa, 0xb1, 0xb8, 0xbf, 0xc6, 0xcd, 0xd4, 0xdb, 0xe2, + 0xe9, 0xf0, 0xf7, 0x03, 0x0a, 0x11, 0x18, 0x1f, 0x26, 0x2d, 0x34, 0x3b, + 0x42, 0x49, 0x50, 0x57, 0x5e, 0x65, 0x6c, 0x73, 0x7a, 0x81, 0x88, 0x8f, + 0x96, 0x9d, 0xa4, 0xab, 0xb2, 0xb9, 0xc0, 0xc7, 0xce, 0xd5, 0xdc, 0xe3, + 0xea, 0xf1, 0xf8, 0x04, 0x0b, 0x12, 0x19, 0x20, 0x27, 0x2e, 0x35, 0x3c, + 0x43, 0x4a, 0x51, 0x58, 0x5f, 0x66, 0x6d, 0x74, 0x7b, 0x82, 0x89, 0x90, + 0x97, 0x9e, 0xa5, 0xac, 0xb3, 0xba, 0xc1, 0xc8, 0xcf, 0xd6, 0xdd, 0xe4, + 0xeb, 0xf2, 0xf9, 0x05, 0x0c, 0x13, 0x1a, 0x21, 0x28, 0x2f, 0x36, 0x3d, + 0x44, 0x4b, 0x52, 0x59, 0x60, 0x67, 0x6e, 0x75, 0x7c, 0x83, 0x8a, 0x91, + 0x98, 0x9f, 0xa6, 0xad, 0xb4, 0xbb, 0xc2, 0xc9, 0xd0, 0xd7, 0xde, 0xe5, + 0xec, 0xf3, 0xfa, 0x06, 0x0d, 0x14, 0x1b, 0x22, 0x29, 0x30, 0x37, 0x3e, + 0x45, 0x4c, 0x53, 0x5a, 0x61, 0x68, 0x6f, 0x76, 0x7d, 0x84, 0x8b, 0x92, + 0x99, 0xa0, 0xa7, 0xae, 0xb5, 0xbc, 0xc3, 0xca, 0xd1, 0xd8, 0xdf, 0xe6, + 0xed, 0xf4, 0x00, 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, 0x3f, + 0x46, 0x4d, 0x54, 0x5b, 0x62, 0x69, 0x70, 0x77, 0x7e, 0x85, 0x8c, 0x93, + 0x9a, 0xa1, 0xa8, 0xaf, 0xb6, 0xbd, 0xc4, 0xcb, 0xd2, 0xd9, 0xe0, 0xe7, + 0xee, 0xf5, 0x01, 0x08, 0x0f, 0x16, 0x1d, 0x24, 0x2b, 0x32, 0x39, 0x40, + 0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x7f, 0x86, 0x8d, 0x94, + 0x9b, 0xa2, 0xa9, 0xb0, 0xb7, 0xbe, 0xc5, 0xcc, 0xd3, 0xda, 0xe1, 0xe8, + 0xef, 0xf6, 0x02, 0x09, 0x10, 0x17, 0x1e, 0x25, 0x2c, 0x33, 0x3a, 0x41, + 0x48, 0x4f, 0x56, 0x5d, 0x64, 0x6b, 0x72, 0x79, 0x80, 0x87, 0x8e, 0x95, + 0x9c, 0xa3, 0xaa, 0xb1, 0xb8, 0xbf, 0xc6, 0xcd, 0xd4, 0xdb, 0xe2, 0xe9, + 0xf0, 0xf7, 0x03, 0x0a, 0x11, 0x18, 0x1f, 0x26, 0x2d, 0x34, 0x3b, 0x42, + 0x49, 0x50, 0x57, 0x5e, 0x65, 0x6c, 0x73, 0x7a, 0x81, 0x88, 0x8f, 0x96, + 0x9d, 0xa4, 0xab, 0xb2, 0xb9, 0xc0, 0xc7, 0xce, 0xd5, 0xdc, 0xe3, 0xea, + 0xf1, 0xf8, 0x04, 0x0b, 0x12, 0x19, 0x20, 0x27, 0x2e, 0x35, 0x3c, 0x43, + 0x4a, 0x51, 0x58, 0x5f, 0x66, 0x6d, 0x74, 0x7b, 0x82, 0x89, 0x90, 0x97, + 0x9e, 0xa5, 0xac, 0xb3, 0xba, 0xc1, 0xc8, 0xcf, 0xd6, 0xdd, 0xe4, 0xeb, + 0xf2, 0xf9, 0x05, 0x0c, 0x13, 0x1a, 0x21, 0x28, 0x2f, 0x36, 0x3d, 0x44, + 0x4b, 0x52, 0x59, 0x60, 0x67, 0x6e, 0x75, 0x7c, 0x83, 0x8a, 0x91, 0x98, + 0x9f, 0xa6, 0xad, 0xb4, 0xbb, 0xc2, 0xc9, 0xd0, 0xd7, 0xde, 0xe5, 0xec, + 0xf3, 0xfa, 0x06, 0x0d, 0x14, 0x1b, 0x22, 0x29, 0x30, 0x37, 0x3e, 0x45, + 0x4c, 0x53, 0x5a, 0x61, 0x68, 0x6f, 0x76, 0x7d, 0x84, 0x8b, 0x92, 0x99, + 0xa0, 0xa7, 0xae, 0xb5, 0xbc, 0xc3, 0xca, 0xd1, 0xd8, 0xdf, 0xe6, 0xed, + 0xf4, 0x00, 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, 0x3f, 0x46, + 0x4d, 0x54, 0x5b, 0x62, 0x69, 0x70, 0x77, 0x7e, 0x85, 0x8c, 0x93, 0x9a, + 0xa1, 0xa8, 0xaf, 0xb6, 0xbd, 0xc4, 0xcb, 0xd2, 0xd9, 0xe0, 0xe7, 0xee, + 0xf5, 0x01, 0x08, 0x0f, 0x16, 0x1d, 0x24, 0x2b, 0x32, 0x39, 0x40, 0x47, + 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x7f, 0x86, 0x8d, 0x94, 0x9b, + 0xa2, 0xa9, 0xb0, 0xb7, 0xbe, 0xc5, 0xcc, 0xd3, 0xda, 0xe1, 0xe8, 0xef, + 0xf6, 0x02, 0x09, 0x10, 0x17, 0x1e, 0x25, 0x2c, 0x33, 0x3a, 0x41, 0x48, + 0x4f, 0x56, 0x5d, 0x64, 0x6b, 0x72, 0x79, 0x80, 0x87, 0x8e, 0x95, 0x9c, + 0xa3, 0xaa, 0xb1, 0xb8, 0xbf, 0xc6, 0xcd, 0xd4, 0xdb, 0xe2, 0xe9, 0xf0, + 0xf7, 0x03, 0x0a, 0x11, 0x18, 0x1f, 0x26, 0x2d, 0x34, 0x3b, 0x42, 0x49, + 0x50, 0x57, 0x5e, 0x65, 0x6c, 0x73, 0x7a, 0x81, 0x88, 0x8f, 0x96, 0x9d, + 0xa4, 0xab, 0xb2, 0xb9, 0xc0, 0xc7, 0xce, 0xd5, 0xdc, 0xe3, 0xea, 0xf1, + 0xf8, 0x04, 0x0b, 0x12, 0x19, 0x20, 0x27, 0x2e, 0x35, 0x3c, 0x43, 0x4a, + 0x51, 0x58, 0x5f, 0x66, 0x6d, 0x74, 0x7b, 0x82, 0x89, 0x90, 0x97, 0x9e, + 0xa5, 0xac, 0xb3, 0xba, 0xc1, 0xc8, 0xcf, 0xd6, 0xdd, 0xe4, 0xeb, 0xf2, + 0xf9, 0x05, 0x0c, 0x13, 0x1a, 0x21, 0x28, 0x2f, 0x36, 0x3d, 0x44, 0x4b, + 0x52, 0x59, 0x60, 0x67, 0x6e, 0x75, 0x7c, 0x83, 0x8a, 0x91, 0x98, 0x9f, + 0xa6, 0xad, 0xb4, 0xbb, 0xc2, 0xc9, 0xd0, 0xd7, 0xde, 0xe5, 0xec, 0xf3, + 0xfa, 0x06, 0x0d, 0x14, 0x1b, 0x22, 0x29, 0x30, 0x37, 0x3e, 0x45, 0x4c, + 0x53, 0x5a, 0x61, 0x68, 0x6f, 0x76, 0x7d, 0x84, 0x8b, 0x92, 0x99, 0xa0, + 0xa7, 0xae, 0xb5, 0xbc, 0xc3, 0xca, 0xd1, 0xd8, 0xdf, 0xe6, 0xed, 0xf4, + 0x00, 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, 0x3f, 0x46, 0x4d, + 0x54, 0x5b, 0x62, 0x69, 0x70, 0x77, 0x7e, 0x85, 0x8c, 0x93, 0x9a, 0xa1, + 0xa8, 0xaf, 0xb6, 0xbd, 0xc4, 0xcb, 0xd2, 0xd9, 0xe0, 0xe7, 0xee, 0xf5, + 0x01, 0x08, 0x0f, 0x16, 0x1d, 0x24, 0x2b, 0x32, 0x39, 0x40, 0x47, 0x4e, + 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x7f, 0x86, 0x8d, 0x94, 0x9b, 0xa2, + 0xa9, 0xb0, 0xb7, 0xbe, 0xc5, 0xcc, 0xd3, 0xda, 0xe1, 0xe8, 0xef, 0xf6, + 0x02, 0x09, 0x10, 0x17, 0x1e, 0x25, 0x2c, 0x33, 0x3a, 0x41, 0x48, 0x4f, + 0x56, 0x5d, 0x64, 0x6b, 0x72, 0x79, 0x80, 0x87, 0x8e, 0x95, 0x9c, 0xa3, + 0xaa, 0xb1, 0xb8, 0xbf, 0xc6, 0xcd, 0xd4, 0xdb, 0xe2, 0xe9, 0xf0, 0xf7, + 0x03, 0x0a, 0x11, 0x18, 0x1f, 0x26, 0x2d, 0x34, 0x3b, 0x42, 0x49, 0x50, + 0x57, 0x5e, 0x65, 0x6c, 0x73, 0x7a, 0x81, 0x88, 0x8f, 0x96, 0x9d, 0xa4, + 0xab, 0xb2, 0xb9, 0xc0, 0xc7, 0xce, 0xd5, 0xdc, 0xe3, 0xea, 0xf1, 0xf8, + 0x04, 0x0b, 0x12, 0x19, 0x20, 0x27, 0x2e, 0x35, 0x3c, 0x43, 0x4a, 0x51, + 0x58, 0x5f, 0x66, 0x6d, 0x74, 0x7b, 0x82, 0x89, 0x90, 0x97, 0x9e, 0xa5, + 0xac, 0xb3, 0xba, 0xc1, 0xc8, 0xcf, 0xd6, 0xdd, 0xe4, 0xeb, 0xf2, 0xf9, + 0x05, 0x0c, 0x13, 0x1a, 0x21, 0x28, 0x2f, 0x36, 0x3d, 0x44, 0x4b, 0x52, + 0x59, 0x60, 0x67, 0x6e, 0x75, 0x7c, 0x83, 0x8a, 0x91, 0x98, 0x9f, 0xa6, + 0xad, 0xb4, 0xbb, 0xc2, 0xc9, 0xd0, 0xd7, 0xde, 0xe5, 0xec, 0xf3, 0xfa, + 0x06, 0x0d, 0x14, 0x1b, 0x22, 0x29, 0x30, 0x37, 0x3e, 0x45, 0x4c, 0x53, + 0x5a, 0x61, 0x68, 0x6f, 0x76, 0x7d, 0x84, 0x8b, 0x92, 0x99, 0xa0, 0xa7, + 0xae, 0xb5, 0xbc, 0xc3, 0xca, 0xd1, 0xd8, 0xdf, 0xe6, 0xed, 0xf4, 0x00, + 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, 0x3f, 0x46, 0x4d, 0x54, + 0x5b, 0x62, 0x69, 0x70, 0x77, 0x7e, 0x85, 0x8c, 0x93, 0x9a, 0xa1, 0xa8, + 0xaf, 0xb6, 0xbd, 0xc4, 0xcb, 0xd2, 0xd9, 0xe0, 0xe7, 0xee, 0xf5, 0x01, + 0x08, 0x0f, 0x16, 0x1d, 0x24, 0x2b, 0x32, 0x39, 0x40, 0x47, 0x4e, 0x55, + 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x7f, 0x86, 0x8d, 0x94, 0x9b, 0xa2, 0xa9, + 0xb0, 0xb7, 0xbe, 0xc5, 0xcc, 0xd3, 0xda, 0xe1, 0xe8, 0xef, 0xf6, 0x02, + 0x09, 0x10, 0x17, 0x1e, 0x25, 0x2c, 0x33, 0x3a, 0x41, 0x48, 0x4f, 0x56, + 0x5d, 0x64, 0x6b, 0x72, 0x79, 0x80, 0x87, 0x8e, 0x95, 0x9c, 0xa3, 0xaa, + 0xb1, 0xb8, 0xbf, 0xc6, 0xcd, 0xd4, 0xdb, 0xe2, 0xe9, 0xf0, 0xf7, 0x03, + 0x0a, 0x11, 0x18, 0x1f, 0x26, 0x2d, 0x34, 0x3b, 0x42, 0x49, 0x50, 0x57, + 0x5e, 0x65, 0x6c, 0x73, 0x7a, 0x81, 0x88, 0x8f, 0x96, 0x9d, 0xa4, 0xab, + 0xb2, 0xb9, 0xc0, 0xc7, 0xce, 0xd5, 0xdc, 0xe3, 0xea, 0xf1, 0xf8, 0x04, + 0x0b, 0x12, 0x19, 0x20, 0x27, 0x2e, 0x35, 0x3c, 0x43, 0x4a, 0x51, 0x58, + 0x5f, 0x66, 0x6d, 0x74, 0x7b, 0x82, 0x89, 0x90, 0x97, 0x9e, 0xa5, 0xac, + 0xb3, 0xba, 0xc1, 0xc8, 0xcf, 0xd6, 0xdd, 0xe4, 0xeb, 0xf2, 0xf9, 0x05, + 0x0c, 0x13, 0x1a, 0x21, 0x28, 0x2f, 0x36, 0x3d, 0x44, 0x4b, 0x52, 0x59, + 0x60, 0x67, 0x6e, 0x75, 0x7c, 0x83, 0x8a, 0x91, 0x98, 0x9f, 0xa6, 0xad, + 0xb4, 0xbb, 0xc2, 0xc9, 0xd0, 0xd7, 0xde, 0xe5, 0xec, 0xf3, 0xfa, 0x06, + 0x0d, 0x14, 0x1b, 0x22, 0x29, 0x30, 0x37, 0x3e, 0x45, 0x4c, 0x53, 0x5a, + 0x61, 0x68, 0x6f, 0x76, 0x7d, 0x84, 0x8b, 0x92, 0x99, 0xa0, 0xa7, 0xae, + 0xb5, 0xbc, 0xc3, 0xca, 0xd1, 0xd8, 0xdf, 0xe6, 0xed, 0xf4, 0x00, 0x07, + 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, 0x3f, 0x46, 0x4d, 0x54, 0x5b, + 0x62, 0x69, 0x70, 0x77, 0x7e, 0x85, 0x8c, 0x93, 0x9a, 0xa1, 0xa8, 0xaf, + 0xb6, 0xbd, 0xc4, 0xcb, 0xd2, 0xd9, 0xe0, 0xe7, 0xee, 0xf5, 0x01, 0x08, + 0x0f, 0x16, 0x1d, 0x24, 0x2b, 0x32, 0x39, 0x40, 0x47, 0x4e, 0x55, 0x5c, + 0x63, 0x6a, 0x71, 0x78, 0x7f, 0x86, 0x8d, 0x94, 0x9b, 0xa2, 0xa9, 0xb0, + 0xb7, 0xbe, 0xc5, 0xcc, 0xd3, 0xda, 0xe1, 0xe8, 0xef, 0xf6, 0x02, 0x09, + 0x10, 0x17, 0x1e, 0x25, 0x2c, 0x33, 0x3a, 0x41, 0x48, 0x4f, 0x56, 0x5d, + 0x64, 0x6b, 0x72, 0x79, 0x80, 0x87, 0x8e, 0x95, 0x9c, 0xa3, 0xaa, 0xb1, + 0xb8, 0xbf, 0xc6, 0xcd, 0xd4, 0xdb, 0xe2, 0xe9, 0xf0, 0xf7, 0x03, 0x0a, + 0x11, 0x18, 0x1f, 0x26, 0x2d, 0x34, 0x3b, 0x42, 0x49, 0x50, 0x57, 0x5e, + 0x65, 0x6c, 0x73, 0x7a, 0x81, 0x88, 0x8f, 0x96, 0x9d, 0xa4, 0xab, 0xb2, + 0xb9, 0xc0, 0xc7, 0xce, 0xd5, 0xdc, 0xe3, 0xea, 0xf1, 0xf8, 0x04, 0x0b, + 0x12, 0x19, 0x20, 0x27, 0x2e, 0x35, 0x3c, 0x43, 0x4a, 0x51, 0x58, 0x5f, + 0x66, 0x6d, 0x74, 0x7b, 0x82, 0x89, 0x90, 0x97, 0x9e, 0xa5, 0xac, 0xb3, + 0xba, 0xc1, 0xc8, 0xcf, 0xd6, 0xdd, 0xe4, 0xeb, 0xf2, 0xf9, 0x05, 0x0c, + 0x13, 0x1a, 0x21, 0x28, 0x2f, 0x36, 0x3d, 0x44, 0x4b, 0x52, 0x59, 0x60, + 0x67, 0x6e, 0x75, 0x7c, 0x83, 0x8a, 0x91, 0x98, 0x9f, 0xa6, 0xad, 0xb4, + 0xbb, 0xc2, 0xc9, 0xd0, 0xd7, 0xde, 0xe5, 0xec, 0xf3, 0xfa, 0x06, 0x0d, + 0x14, 0x1b, 0x22, 0x29, 0x30, 0x37, 0x3e, 0x45, 0x4c, 0x53, 0x5a, 0x61, + 0x68, 0x6f, 0x76, 0x7d, 0x84, 0x8b, 0x92, 0x99, 0xa0, 0xa7, 0xae, 0xb5, + 0xbc, 0xc3, 0xca, 0xd1, 0xd8, 0xdf, 0xe6, 0xed, 0xf4, 0x00, 0x07, 0x0e, + 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, 0x3f, 0x46, 0x4d, 0x54, 0x5b, 0x62, + 0x69, 0x70, 0x77, 0x7e, 0x85, 0x8c, 0x93, 0x9a, 0xa1, 0xa8, 0xaf, 0xb6, + 0xbd, 0xc4, 0xcb, 0xd2, 0xd9, 0xe0, 0xe7, 0xee, 0xf5, 0x01, 0x08, 0x0f, + 0x16, 0x1d, 0x24, 0x2b, 0x32, 0x39, 0x40, 0x47, 0x4e, 0x55, 0x5c, 0x63, + 0x6a, 0x71, 0x78, 0x7f, 0x86, 0x8d, 0x94, 0x9b, 0xa2, 0xa9, 0xb0, 0xb7, + 0xbe, 0xc5, 0xcc, 0xd3, 0xda, 0xe1, 0xe8, 0xef, 0xf6, 0x02, 0x09, 0x10, + 0x17, 0x1e, 0x25, 0x2c, 0x33, 0x3a, 0x41, 0x48, 0x4f, 0x56, 0x5d, 0x64, + 0x6b, 0x72, 0x79, 0x80, 0x87, 0x8e, 0x95, 0x9c, 0xa3, 0xaa, 0xb1, 0xb8, + 0xbf, 0xc6, 0xcd, 0xd4, 0xdb, 0xe2, 0xe9, 0xf0, 0xf7, 0x03, 0x0a, 0x11, + 0x18, 0x1f, 0x26, 0x2d, 0x34, 0x3b, 0x42, 0x49, 0x50, 0x57, 0x5e, 0x65, + 0x6c, 0x73, 0x7a, 0x81, 0x88, 0x8f, 0x96, 0x9d, 0xa4, 0xab, 0xb2, 0xb9, + 0xc0, 0xc7, 0xce, 0xd5, 0xdc, 0xe3, 0xea, 0xf1, 0xf8, 0x04, 0x0b, 0x12, + 0x19, 0x20, 0x27, 0x2e, 0x35, 0x3c, 0x43, 0x4a, 0x51, 0x58, 0x5f, 0x66, + 0x6d, 0x74, 0x7b, 0x82, 0x89, 0x90, 0x97, 0x9e, 0xa5, 0xac, 0xb3, 0xba, + 0xc1, 0xc8, 0xcf, 0xd6, 0xdd, 0xe4, 0xeb, 0xf2, 0xf9, 0x05, 0x0c, 0x13, + 0x1a, 0x21, 0x28, 0x2f, 0x36, 0x3d, 0x44, 0x4b, 0x52, 0x59, 0x60, 0x67, + 0x6e, 0x75, 0x7c, 0x83, 0x8a, 0x91, 0x98, 0x9f, 0xa6, 0xad, 0xb4, 0xbb, + 0xc2, 0xc9, 0xd0, 0xd7, 0xde, 0xe5, 0xec, 0xf3, 0xfa, 0x06, 0x0d, 0x14, + 0x1b, 0x22, 0x29, 0x30, 0x37, 0x3e, 0x45, 0x4c, 0x53, 0x5a, 0x61, 0x68, + 0x6f, 0x76, 0x7d, 0x84, 0x8b, 0x92, 0x99, 0xa0, 0xa7, 0xae, 0xb5, 0xbc, + 0xc3, 0xca, 0xd1, 0xd8, 0xdf, 0xe6, 0xed, 0xf4, 0x00, 0x07, 0x0e, 0x15, + 0x1c, 0x23, 0x2a, 0x31, 0x38, 0x3f, 0x46, 0x4d, 0x54, 0x5b, 0x62, 0x69, + 0x70, 0x77, 0x7e, 0x85, 0x8c, 0x93, 0x9a, 0xa1, 0xa8, 0xaf, 0xb6, 0xbd, + 0xc4, 0xcb, 0xd2, 0xd9, 0xe0, 0xe7, 0xee, 0xf5, 0x01, 0x08, 0x0f, 0x16, + 0x1d, 0x24, 0x2b, 0x32, 0x39, 0x40, 0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, + 0x71, 0x78, 0x7f, 0x86, 0x8d, 0x94, 0x9b, 0xa2, 0xa9, 0xb0, 0xb7, 0xbe, + 0xc5, 0xcc, 0xd3, 0xda, 0xe1, 0xe8, 0xef, 0xf6, 0x02, 0x09, 0x10, 0x17, + 0x1e, 0x25, 0x2c, 0x33, 0x3a, 0x41, 0x48, 0x4f, 0x56, 0x5d, 0x64, 0x6b, + 0x72, 0x79, 0x80, 0x87, 0x8e, 0x95, 0x9c, 0xa3, 0xaa, 0xb1, 0xb8, 0xbf, + 0xc6, 0xcd, 0xd4, 0xdb, 0xe2, 0xe9, 0xf0, 0xf7, 0x03, 0x0a, 0x11, 0x18, + 0x1f, 0x26, 0x2d, 0x34, 0x3b, 0x42, 0x49, 0x50, 0x57, 0x5e, 0x65, 0x6c, + 0x73, 0x7a, 0x81, 0x88, 0x8f, 0x96, 0x9d, 0xa4, 0xab, 0xb2, 0xb9, 0xc0, + 0xc7, 0xce, 0xd5, 0xdc, 0xe3, 0xea, 0xf1, 0xf8, 0x04, 0x0b, 0x12, 0x19, + 0x20, 0x27, 0x2e, 0x35, 0x3c, 0x43, 0x4a, 0x51, 0x58, 0x5f, 0x66, 0x6d, + 0x74, 0x7b, 0x82, 0x89, 0x90, 0x97, 0x9e, 0xa5, 0xac, 0xb3, 0xba, 0xc1, + 0xc8, 0xcf, 0xd6, 0xdd, 0xe4, 0xeb, 0xf2, 0xf9, 0x05, 0x0c, 0x13, 0x1a, + 0x21, 0x28, 0x2f, 0x36, 0x3d, 0x44, 0x4b, 0x52, 0x59, 0x60, 0x67, 0x6e, + 0x75, 0x7c, 0x83, 0x8a, 0x91, 0x98, 0x9f, 0xa6, 0xad, 0xb4, 0xbb, 0xc2, + 0xc9, 0xd0, 0xd7, 0xde, 0xe5, 0xec, 0xf3, 0xfa, 0x06, 0x0d, 0x14, 0x1b, + 0x22, 0x29, 0x30, 0x37, 0x3e, 0x45, 0x4c, 0x53, 0x5a, 0x61, 0x68, 0x6f, + 0x76, 0x7d, 0x84, 0x8b, 0x92, 0x99, 0xa0, 0xa7, 0xae, 0xb5, 0xbc, 0x50, + 0x4b, 0x01, 0x02, 0x14, 0x03, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x21, 0x00, 0x21, 0xe1, 0xef, 0x3c, 0xb8, 0x0b, 0x00, 0x00, 0xb8, + 0x0b, 0x00, 0x00, 0x05, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x2e, 0x62, + 0x69, 0x6e, 0x55, 0x54, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x75, + 0x78, 0x0b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x50, 0x4b, 0x01, 0x02, 0x14, 0x03, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0xf3, 0x0b, 0x00, 0x00, + 0x64, 0x69, 0x72, 0x2f, 0x50, 0x4b, 0x01, 0x02, 0x14, 0x03, 0x14, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x80, 0x6c, 0x19, 0x34, + 0xd0, 0x07, 0x00, 0x00, 0xd0, 0x07, 0x00, 0x00, 0x09, 0x00, 0x0f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x15, 0x0c, + 0x00, 0x00, 0x64, 0x69, 0x72, 0x2f, 0x62, 0x2e, 0x62, 0x69, 0x6e, 0x75, + 0x78, 0x0b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x03, 0x00, 0xc3, 0x00, 0x00, 0x00, 0x1b, 0x14, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t deflated_zip[1362] = +{ + 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x21, 0xe1, 0xef, 0x3c, 0x62, 0x03, 0x00, 0x00, 0xb8, 0x0b, + 0x00, 0x00, 0x05, 0x00, 0x09, 0x00, 0x61, 0x2e, 0x62, 0x69, 0x6e, 0x55, + 0x54, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x8d, 0xd6, 0x67, 0x57, + 0x08, 0x00, 0x18, 0x40, 0xe1, 0xd2, 0x94, 0xad, 0x8c, 0x68, 0xda, 0xb2, + 0xc9, 0xde, 0x2d, 0x45, 0x64, 0xa4, 0x92, 0xa4, 0xec, 0x55, 0xa4, 0x61, + 0x84, 0x52, 0x8a, 0x86, 0x91, 0x4a, 0x28, 0xca, 0x56, 0x1a, 0x22, 0x99, + 0xc9, 0x0a, 0xc9, 0x56, 0x56, 0xa1, 0x6c, 0x52, 0x19, 0xd9, 0xf3, 0xdc, + 0xcf, 0xce, 0xa9, 0xf7, 0x27, 0x3c, 0x9f, 0xee, 0x55, 0x38, 0xb9, 0xa8, + 0x47, 0xc5, 0x81, 0x19, 0x2d, 0x1e, 0x6f, 0xb5, 0x6b, 0x70, 0x2d, 0xc4, + 0x5c, 0xf1, 0xd4, 0x62, 0xe3, 0xf7, 0xc9, 0x33, 0x5b, 0x3e, 0x89, 0xb5, + 0x6f, 0x78, 0x3d, 0xd4, 0xa2, 0x46, 0xd6, 0x92, 0x9e, 0x1f, 0x52, 0x66, + 0xb5, 0x2a, 0x8e, 0x73, 0xd0, 0xbc, 0x11, 0x36, 0x4c, 0xe9, 0xf4, 0xd2, + 0x5e, 0x1f, 0x53, 0x67, 0xb7, 0x2e, 0xd9, 0x36, 0x41, 0xeb, 0x66, 0xb8, + 0xa5, 0x72, 0xb6, 0x6f, 0xef, 0x4f, 0x69, 0x73, 0xda, 0x3c, 0xdd, 0xee, + 0xd8, 0xf8, 0xd6, 0x5a, 0x2b, 0x95, 0xb3, 0xcb, 0xfa, 0x54, 0x1e, 0x9c, + 0xd7, 0xf6, 0x59, 0xfc, 0xc4, 0x26, 0xb7, 0xd7, 0x0d, 0x57, 0x3d, 0xb7, + 0xbc, 0xef, 0xe7, 0x74, 0xb7, 0x76, 0xcf, 0x13, 0x9c, 0x9a, 0xde, 0x59, + 0x3f, 0x42, 0xed, 0xfc, 0x8a, 0x7e, 0x5f, 0x0e, 0xb9, 0xb7, 0x7f, 0xb1, + 0x63, 0x92, 0x76, 0xfe, 0x06, 0x6b, 0xf5, 0x0b, 0x7e, 0xfd, 0xbf, 0x1e, + 0x9e, 0x6f, 0xf4, 0x72, 0xa7, 0x73, 0xb3, 0x82, 0x88, 0x91, 0x35, 0x73, + 0xfc, 0x07, 0x7c, 0xcb, 0x58, 0xd0, 0xf1, 0xd5, 0xae, 0xc9, 0xcd, 0xef, + 0x6d, 0x1c, 0xa5, 0x71, 0x31, 0x60, 0xe0, 0xf7, 0x23, 0x1e, 0x9d, 0x5e, + 0xef, 0x76, 0xd1, 0xb9, 0x1f, 0x69, 0x53, 0xeb, 0x52, 0xe0, 0xa0, 0x1f, + 0x99, 0x0b, 0x3b, 0xbf, 0xd9, 0xe3, 0xaa, 0xfb, 0x20, 0x6a, 0x74, 0xed, + 0xcb, 0xab, 0x06, 0xff, 0x3c, 0xea, 0xd9, 0xe5, 0xed, 0xde, 0x29, 0x7a, + 0x0f, 0xa3, 0xc7, 0xd4, 0xc9, 0x0d, 0x1a, 0xf2, 0xeb, 0x98, 0x57, 0xd7, + 0xd2, 0x7d, 0x53, 0xf5, 0x0b, 0x37, 0x8d, 0xad, 0x7b, 0x25, 0xd8, 0xe4, + 0xf7, 0x71, 0xef, 0x6e, 0x65, 0xfb, 0xa7, 0x19, 0x14, 0x6d, 0x1e, 0x57, + 0x2f, 0x6f, 0xb5, 0xe9, 0x9f, 0x13, 0x3e, 0xdd, 0xcb, 0x13, 0xa7, 0x1b, + 0x3e, 0xda, 0x62, 0x5b, 0xff, 0xea, 0x1a, 0xb3, 0xbf, 0x98, 0x93, 0x30, + 0x8f, 0xc7, 0xac, 0x80, 0xf9, 0x00, 0x66, 0x3b, 0xcc, 0x8a, 0x98, 0x93, + 0x05, 0xe6, 0x46, 0x98, 0xcf, 0x60, 0x9e, 0x8b, 0xb9, 0x31, 0xe6, 0xb3, + 0x98, 0xe7, 0x61, 0x6e, 0x82, 0xf9, 0x9c, 0xc0, 0xdc, 0x01, 0xf3, 0x5d, + 0xcc, 0x2b, 0x31, 0x77, 0xc4, 0x7c, 0x0f, 0x73, 0x00, 0xe6, 0x4e, 0x52, + 0xf3, 0x50, 0xcc, 0xef, 0x30, 0xc7, 0x60, 0x36, 0xc1, 0x5c, 0x86, 0x79, + 0x33, 0x66, 0x53, 0xa9, 0xd9, 0x1e, 0x73, 0x0d, 0xcc, 0x29, 0x98, 0x1d, + 0x30, 0x2b, 0x61, 0x4e, 0xc5, 0x3c, 0x41, 0x6a, 0x76, 0xc3, 0xdc, 0x14, + 0xf3, 0x79, 0xcc, 0xee, 0x98, 0xb5, 0x31, 0x5f, 0xc0, 0x3c, 0x5f, 0x60, + 0xbe, 0x8f, 0x39, 0x10, 0x73, 0x67, 0xcc, 0x0f, 0x30, 0xaf, 0xc2, 0xdc, + 0x05, 0xf3, 0x43, 0xcc, 0x41, 0x02, 0x73, 0x39, 0xe6, 0x2d, 0x98, 0xcd, + 0x30, 0x57, 0x60, 0xde, 0x8a, 0xd9, 0x1c, 0xf3, 0x7b, 0xa9, 0x59, 0x19, + 0x73, 0x1a, 0x66, 0x47, 0xcc, 0x2a, 0x98, 0x0f, 0x62, 0x9e, 0x88, 0x59, + 0x55, 0x6a, 0x6e, 0x86, 0x39, 0x07, 0xf3, 0x02, 0xcc, 0xcd, 0x31, 0x5f, + 0xc4, 0xec, 0x81, 0x59, 0x47, 0x6a, 0xee, 0x8a, 0xb9, 0x10, 0x73, 0x30, + 0xe6, 0x6e, 0x98, 0x8b, 0x30, 0xaf, 0xc6, 0xdc, 0x5d, 0x60, 0x8e, 0xc5, + 0x6c, 0x81, 0xf9, 0x03, 0xe6, 0x38, 0xcc, 0xc3, 0x30, 0x7f, 0xc4, 0xbc, + 0x0d, 0xb3, 0xa5, 0xc0, 0x9c, 0x8e, 0xd9, 0x09, 0xb3, 0x1a, 0xe6, 0x43, + 0x98, 0x27, 0x61, 0x56, 0xc7, 0x7c, 0x58, 0x6a, 0xbe, 0x84, 0x79, 0x21, + 0x66, 0x5d, 0xcc, 0x97, 0x31, 0x7b, 0x62, 0xd6, 0xc3, 0x9c, 0x2b, 0x35, + 0x3f, 0xc2, 0xbc, 0x06, 0x73, 0x0f, 0xcc, 0x8f, 0x31, 0x87, 0x60, 0x36, + 0xc6, 0xfc, 0x44, 0x6a, 0xfe, 0x84, 0x79, 0x3b, 0x66, 0x2b, 0xcc, 0x95, + 0x98, 0xe3, 0x31, 0x0f, 0xc7, 0xfc, 0x59, 0x60, 0x76, 0xc6, 0x5c, 0x13, + 0x73, 0x06, 0xe6, 0xc9, 0x98, 0x35, 0x30, 0x1f, 0xc1, 0xec, 0x82, 0xb9, + 0x96, 0xc0, 0xec, 0x85, 0x59, 0x1f, 0xf3, 0x15, 0xcc, 0xde, 0x98, 0x0d, + 0x30, 0xe7, 0x61, 0xf6, 0x91, 0x9a, 0x43, 0x31, 0xf7, 0xc4, 0x5c, 0x8c, + 0x39, 0x0c, 0x73, 0x2f, 0xcc, 0x25, 0x98, 0xc3, 0xa5, 0xe6, 0x04, 0xcc, + 0x23, 0x30, 0x7f, 0xc1, 0xbc, 0x03, 0xb3, 0x35, 0xe6, 0xaf, 0x98, 0x77, + 0x4a, 0xcd, 0x99, 0x98, 0x5d, 0x31, 0xd7, 0xc6, 0x7c, 0x14, 0xf3, 0x14, + 0xcc, 0x75, 0x30, 0x1f, 0x13, 0x98, 0x0d, 0x31, 0x5f, 0xc5, 0x4c, 0xba, + 0x66, 0x92, 0xae, 0x86, 0xa4, 0x2b, 0x8b, 0x74, 0xcd, 0x22, 0x5d, 0x9a, + 0xd7, 0x05, 0xe6, 0xde, 0x98, 0x49, 0xd7, 0x6d, 0xd2, 0xb5, 0x9c, 0x74, + 0xb5, 0x23, 0x5d, 0x77, 0x48, 0xd7, 0x8a, 0xbe, 0x52, 0xf3, 0x48, 0xcc, + 0xa4, 0xeb, 0x35, 0xe9, 0x8a, 0x24, 0x5d, 0x83, 0x48, 0xd7, 0x1b, 0xd2, + 0x15, 0x65, 0x23, 0x35, 0x4f, 0xc5, 0x4c, 0xba, 0xfe, 0x90, 0xae, 0x44, + 0xd2, 0x65, 0x4b, 0xba, 0xfe, 0x92, 0xae, 0xa4, 0xe9, 0x52, 0x33, 0xb9, + 0x9e, 0x4d, 0xba, 0xb4, 0x48, 0x57, 0x36, 0xe9, 0x9a, 0x43, 0xba, 0x1a, + 0x91, 0xae, 0x33, 0xbe, 0x02, 0x33, 0xb9, 0xce, 0x27, 0xd7, 0x7e, 0xa4, + 0xcb, 0x88, 0x74, 0x15, 0x90, 0x2e, 0x7f, 0xd2, 0xd5, 0x81, 0x74, 0xdd, + 0x8d, 0x10, 0x98, 0xc9, 0xf5, 0x5b, 0xd2, 0x15, 0x4d, 0xba, 0x86, 0x90, + 0xae, 0x52, 0xd2, 0xb5, 0x89, 0x74, 0x0d, 0xfd, 0x25, 0x35, 0x93, 0x6b, + 0x85, 0xaa, 0x17, 0x45, 0x62, 0x16, 0x2c, 0x8a, 0xc4, 0x2c, 0x58, 0x94, + 0x6a, 0xcc, 0xe4, 0x3a, 0x46, 0xb0, 0x28, 0xd5, 0x98, 0x05, 0x5b, 0x26, + 0x31, 0x0b, 0xb6, 0x4c, 0x62, 0x16, 0x2c, 0x8a, 0xc4, 0x2c, 0x58, 0x14, + 0xc9, 0x8a, 0x0a, 0x16, 0x45, 0xb2, 0xa2, 0x55, 0x2f, 0x8a, 0x78, 0x45, + 0xab, 0x5e, 0x14, 0xf1, 0x8a, 0xfe, 0xb7, 0x28, 0xff, 0x00, 0x50, 0x4b, + 0x03, 0x04, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x21, 0x00, + 0x80, 0x6c, 0x19, 0x34, 0x1c, 0x01, 0x00, 0x00, 0xd0, 0x07, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x62, 0x2e, 0x62, 0x69, 0x6e, 0x63, 0x60, 0xe7, + 0x13, 0x95, 0x51, 0xd6, 0x32, 0xb4, 0xb0, 0x77, 0xf3, 0x0d, 0x89, 0x4e, + 0xca, 0x2c, 0x28, 0xaf, 0x6b, 0xed, 0x99, 0x3c, 0x6b, 0xe1, 0x8a, 0xf5, + 0xdb, 0xf6, 0x1e, 0x39, 0x7d, 0xe9, 0xe6, 0x83, 0xe7, 0xef, 0xbe, 0x32, + 0x72, 0xf0, 0x8b, 0xc9, 0xaa, 0x68, 0x1b, 0x59, 0x3a, 0xb8, 0xfb, 0x85, + 0xc6, 0x24, 0x67, 0x15, 0x56, 0xd4, 0xb7, 0xf5, 0x4e, 0x99, 0xbd, 0x68, + 0xe5, 0x86, 0xed, 0xfb, 0x8e, 0x9e, 0xb9, 0x7c, 0xeb, 0xe1, 0x8b, 0xf7, + 0xdf, 0x98, 0x38, 0x05, 0xc4, 0xe5, 0x54, 0x75, 0x8c, 0xad, 0x1c, 0x3d, + 0xfc, 0xc3, 0x62, 0x53, 0xb2, 0x8b, 0x2a, 0x1b, 0xda, 0xfb, 0xa6, 0xce, + 0x59, 0xbc, 0x6a, 0xe3, 0x8e, 0xfd, 0xc7, 0xce, 0x5e, 0xb9, 0xfd, 0xe8, + 0xe5, 0x87, 0xef, 0xcc, 0x5c, 0x82, 0x12, 0xf2, 0x6a, 0xba, 0x26, 0xd6, + 0x4e, 0x9e, 0x01, 0xe1, 0x71, 0xa9, 0x39, 0xc5, 0x55, 0x8d, 0x1d, 0xfd, + 0xd3, 0xe6, 0x2e, 0x59, 0xbd, 0x69, 0xe7, 0x81, 0xe3, 0xe7, 0xae, 0xde, + 0x79, 0xfc, 0xea, 0xe3, 0x0f, 0x16, 0x6e, 0x21, 0x49, 0x05, 0x75, 0x3d, + 0x53, 0x1b, 0x67, 0xaf, 0xc0, 0x88, 0xf8, 0xb4, 0xdc, 0x92, 0xea, 0xa6, + 0xce, 0x09, 0xd3, 0xe7, 0x2d, 0x5d, 0xb3, 0x79, 0xd7, 0xc1, 0x13, 0xe7, + 0xaf, 0xdd, 0x7d, 0xf2, 0xfa, 0xd3, 0x4f, 0x56, 0x1e, 0x61, 0x29, 0x45, + 0x0d, 0x7d, 0x33, 0x5b, 0x17, 0xef, 0xa0, 0xc8, 0x84, 0xf4, 0xbc, 0xd2, + 0x9a, 0xe6, 0xae, 0x89, 0x33, 0xe6, 0x2f, 0x5b, 0xbb, 0x65, 0xf7, 0xa1, + 0x93, 0x17, 0xae, 0xdf, 0x7b, 0xfa, 0xe6, 0xf3, 0x2f, 0x36, 0x5e, 0x11, + 0x69, 0x25, 0x4d, 0x03, 0x73, 0x3b, 0x57, 0x9f, 0xe0, 0xa8, 0xc4, 0x8c, + 0xfc, 0xb2, 0xda, 0x96, 0xee, 0x49, 0x33, 0x17, 0x2c, 0x5f, 0xb7, 0x75, + 0xcf, 0xe1, 0x53, 0x17, 0x6f, 0xdc, 0x7f, 0xf6, 0xf6, 0x0b, 0xc3, 0xa8, + 0xd7, 0x47, 0xbd, 0x3e, 0xea, 0xf5, 0x51, 0xaf, 0x8f, 0x7a, 0x7d, 0xd4, + 0xeb, 0xc3, 0xc5, 0xeb, 0x00, 0x50, 0x4b, 0x01, 0x02, 0x14, 0x03, 0x14, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x21, 0x00, 0x21, 0xe1, 0xef, + 0x3c, 0x62, 0x03, 0x00, 0x00, 0xb8, 0x0b, 0x00, 0x00, 0x05, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x61, 0x2e, 0x62, 0x69, 0x6e, 0x55, 0x54, 0x05, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x01, 0x02, 0x14, 0x03, 0x14, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x21, 0x00, 0x80, 0x6c, 0x19, + 0x34, 0x1c, 0x01, 0x00, 0x00, 0xd0, 0x07, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x8e, + 0x03, 0x00, 0x00, 0x62, 0x2e, 0x62, 0x69, 0x6e, 0x50, 0x4b, 0x05, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x6f, 0x00, 0x00, 0x00, + 0xcd, 0x04, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t zip64_zip[3240] = +{ + 0x50, 0x4b, 0x03, 0x04, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x21, 0xe1, 0xef, 0x3c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x05, 0x00, 0x1c, 0x00, 0x61, 0x2e, 0x62, 0x69, 0x6e, 0x01, + 0x00, 0x18, 0x00, 0xb8, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbb, 0x77, 0x33, 0xef, 0xab, 0x66, 0x22, 0xde, + 0x9a, 0x56, 0x11, 0xcd, 0x89, 0x45, 0x01, 0xbc, 0x78, 0x34, 0xf0, 0xac, + 0x67, 0x23, 0xdf, 0x9b, 0x57, 0x12, 0xce, 0x8a, 0x46, 0x02, 0xbd, 0x79, + 0x35, 0xf1, 0xad, 0x68, 0x24, 0xe0, 0x9c, 0x58, 0x13, 0xcf, 0x8b, 0x47, + 0x03, 0xbe, 0x7a, 0x36, 0xf2, 0xae, 0x69, 0x25, 0xe1, 0x9d, 0x59, 0x14, + 0xd0, 0x8c, 0x48, 0x04, 0xbf, 0x7b, 0x37, 0xf3, 0xaf, 0x6a, 0x26, 0xe2, + 0x9e, 0x5a, 0x16, 0xd1, 0x8d, 0x49, 0x05, 0xc1, 0x7c, 0x38, 0xf4, 0xb0, + 0x6c, 0x27, 0xe3, 0x9f, 0x5b, 0x17, 0xd2, 0x8e, 0x4a, 0x06, 0xc2, 0x7d, + 0x39, 0xf5, 0xb1, 0x6d, 0x28, 0xe4, 0xa0, 0x5c, 0x18, 0xd3, 0x8f, 0x4b, + 0x07, 0xc3, 0x7e, 0x3a, 0xf6, 0xb2, 0x6e, 0x29, 0xe5, 0xa1, 0x5d, 0x19, + 0xd4, 0x90, 0x4c, 0x08, 0xc4, 0x7f, 0x3b, 0xf7, 0xb3, 0x6f, 0x2a, 0xe6, + 0xa2, 0x5e, 0x1a, 0xd5, 0x91, 0x4d, 0x09, 0xc5, 0x80, 0x3c, 0xf8, 0xb4, + 0x70, 0x2c, 0xe7, 0xa3, 0x5f, 0x1b, 0xd7, 0x92, 0x4e, 0x0a, 0xc6, 0x82, + 0x3d, 0xf9, 0xb5, 0x71, 0x2d, 0xe8, 0xa4, 0x60, 0x1c, 0xd8, 0x93, 0x4f, + 0x0b, 0xc7, 0x83, 0x3e, 0xfa, 0xb6, 0x72, 0x2e, 0xe9, 0xa5, 0x61, 0x1d, + 0xd9, 0x94, 0x50, 0x0c, 0xc8, 0x84, 0x3f, 0xfb, 0xb7, 0x73, 0x2f, 0xea, + 0xa6, 0x62, 0x1e, 0xda, 0x95, 0x51, 0x0d, 0xc9, 0x85, 0x40, 0xfc, 0xb8, + 0x74, 0x30, 0xeb, 0xa7, 0x63, 0x1f, 0xdb, 0x96, 0x52, 0x0e, 0xca, 0x86, + 0x42, 0xfd, 0xb9, 0x75, 0x31, 0xed, 0xa8, 0x64, 0x20, 0xdc, 0x98, 0x53, + 0x0f, 0xcb, 0x87, 0x43, 0xfe, 0xba, 0x76, 0x32, 0xee, 0xa9, 0x65, 0x21, + 0xdd, 0x99, 0x54, 0x10, 0xcc, 0x88, 0x44, 0xff, 0xbb, 0x77, 0x33, 0xef, + 0xaa, 0x66, 0x22, 0xde, 0x9a, 0x55, 0x11, 0xcd, 0x89, 0x45, 0x00, 0xbc, + 0x78, 0x34, 0xf0, 0xab, 0x67, 0x23, 0xdf, 0x9b, 0x56, 0x12, 0xce, 0x8a, + 0x46, 0x01, 0xbd, 0x79, 0x35, 0xf1, 0xac, 0x68, 0x24, 0xe0, 0x9c, 0x58, + 0x13, 0xcf, 0x8b, 0x47, 0x03, 0xbe, 0x7a, 0x36, 0xf2, 0xae, 0x69, 0x25, + 0xe1, 0x9d, 0x59, 0x14, 0xd0, 0x8c, 0x48, 0x04, 0xbf, 0x7b, 0x37, 0xf3, + 0xaf, 0x6a, 0x26, 0xe2, 0x9e, 0x5a, 0x15, 0xd1, 0x8d, 0x49, 0x05, 0xc0, + 0x7c, 0x38, 0xf4, 0xb0, 0x6b, 0x27, 0xe3, 0x9f, 0x5b, 0x16, 0xd2, 0x8e, + 0x4a, 0x06, 0xc1, 0x7d, 0x39, 0xf5, 0xb1, 0x6c, 0x28, 0xe4, 0xa0, 0x5c, + 0x17, 0xd3, 0x8f, 0x4b, 0x07, 0xc2, 0x7e, 0x3a, 0xf6, 0xb2, 0x6e, 0x29, + 0xe5, 0xa1, 0x5d, 0x19, 0xd4, 0x90, 0x4c, 0x08, 0xc4, 0x7f, 0x3b, 0xf7, + 0xb3, 0x6f, 0x2a, 0xe6, 0xa2, 0x5e, 0x1a, 0xd5, 0x91, 0x4d, 0x09, 0xc5, + 0x80, 0x3c, 0xf8, 0xb4, 0x70, 0x2b, 0xe7, 0xa3, 0x5f, 0x1b, 0xd6, 0x92, + 0x4e, 0x0a, 0xc6, 0x81, 0x3d, 0xf9, 0xb5, 0x71, 0x2c, 0xe8, 0xa4, 0x60, + 0x1c, 0xd7, 0x93, 0x4f, 0x0b, 0xc7, 0x82, 0x3e, 0xfa, 0xb6, 0x72, 0x2d, + 0xe9, 0xa5, 0x61, 0x1d, 0xd9, 0x94, 0x50, 0x0c, 0xc8, 0x84, 0x3f, 0xfb, + 0xb7, 0x73, 0x2f, 0xea, 0xa6, 0x62, 0x1e, 0xda, 0x95, 0x51, 0x0d, 0xc9, + 0x85, 0x40, 0xfc, 0xb8, 0x74, 0x30, 0xeb, 0xa7, 0x63, 0x1f, 0xdb, 0x96, + 0x52, 0x0e, 0xca, 0x86, 0x41, 0xfd, 0xb9, 0x75, 0x31, 0xec, 0xa8, 0x64, + 0x20, 0xdc, 0x97, 0x53, 0x0f, 0xcb, 0x87, 0x42, 0xfe, 0xba, 0x76, 0x32, + 0xed, 0xa9, 0x65, 0x21, 0xdd, 0x98, 0x54, 0x10, 0xcc, 0x88, 0x43, 0xff, + 0xbb, 0x77, 0x33, 0xef, 0xaa, 0x66, 0x22, 0xde, 0x9a, 0x55, 0x11, 0xcd, + 0x89, 0x45, 0x00, 0xbc, 0x78, 0x34, 0xf0, 0xab, 0x67, 0x23, 0xdf, 0x9b, + 0x56, 0x12, 0xce, 0x8a, 0x46, 0x01, 0xbd, 0x79, 0x35, 0xf1, 0xac, 0x68, + 0x24, 0xe0, 0x9c, 0x57, 0x13, 0xcf, 0x8b, 0x47, 0x02, 0xbe, 0x7a, 0x36, + 0xf2, 0xad, 0x69, 0x25, 0xe1, 0x9d, 0x58, 0x14, 0xd0, 0x8c, 0x48, 0x03, + 0xbf, 0x7b, 0x37, 0xf3, 0xae, 0x6a, 0x26, 0xe2, 0x9e, 0x59, 0x15, 0xd1, + 0x8d, 0x49, 0x05, 0xc0, 0x7c, 0x38, 0xf4, 0xb0, 0x6b, 0x27, 0xe3, 0x9f, + 0x5b, 0x16, 0xd2, 0x8e, 0x4a, 0x06, 0xc1, 0x7d, 0x39, 0xf5, 0xb1, 0x6c, + 0x28, 0xe4, 0xa0, 0x5c, 0x17, 0xd3, 0x8f, 0x4b, 0x07, 0xc2, 0x7e, 0x3a, + 0xf6, 0xb2, 0x6d, 0x29, 0xe5, 0xa1, 0x5d, 0x18, 0xd4, 0x90, 0x4c, 0x08, + 0xc3, 0x7f, 0x3b, 0xf7, 0xb3, 0x6e, 0x2a, 0xe6, 0xa2, 0x5e, 0x19, 0xd5, + 0x91, 0x4d, 0x09, 0xc4, 0x80, 0x3c, 0xf8, 0xb4, 0x6f, 0x2b, 0xe7, 0xa3, + 0x5f, 0x1b, 0xd6, 0x92, 0x4e, 0x0a, 0xc6, 0x81, 0x3d, 0xf9, 0xb5, 0x71, + 0x2c, 0xe8, 0xa4, 0x60, 0x1c, 0xd7, 0x93, 0x4f, 0x0b, 0xc7, 0x82, 0x3e, + 0xfa, 0xb6, 0x72, 0x2d, 0xe9, 0xa5, 0x61, 0x1d, 0xd8, 0x94, 0x50, 0x0c, + 0xc8, 0x83, 0x3f, 0xfb, 0xb7, 0x73, 0x2e, 0xea, 0xa6, 0x62, 0x1e, 0xd9, + 0x95, 0x51, 0x0d, 0xc9, 0x84, 0x40, 0xfc, 0xb8, 0x74, 0x2f, 0xeb, 0xa7, + 0x63, 0x1f, 0xda, 0x96, 0x52, 0x0e, 0xca, 0x85, 0x41, 0xfd, 0xb9, 0x75, + 0x31, 0xec, 0xa8, 0x64, 0x20, 0xdc, 0x97, 0x53, 0x0f, 0xcb, 0x87, 0x42, + 0xfe, 0xba, 0x76, 0x32, 0xed, 0xa9, 0x65, 0x21, 0xdd, 0x98, 0x54, 0x10, + 0xcc, 0x88, 0x43, 0xff, 0xbb, 0x77, 0x33, 0xee, 0xaa, 0x66, 0x22, 0xde, + 0x99, 0x55, 0x11, 0xcd, 0x89, 0x44, 0x00, 0xbc, 0x78, 0x34, 0xef, 0xab, + 0x67, 0x23, 0xdf, 0x9a, 0x56, 0x12, 0xce, 0x8a, 0x45, 0x01, 0xbd, 0x79, + 0x35, 0xf0, 0xac, 0x68, 0x24, 0xe0, 0x9c, 0x57, 0x13, 0xcf, 0x8b, 0x47, + 0x02, 0xbe, 0x7a, 0x36, 0xf2, 0xad, 0x69, 0x25, 0xe1, 0x9d, 0x58, 0x14, + 0xd0, 0x8c, 0x48, 0x03, 0xbf, 0x7b, 0x37, 0xf3, 0xae, 0x6a, 0x26, 0xe2, + 0x9e, 0x59, 0x15, 0xd1, 0x8d, 0x49, 0x04, 0xc0, 0x7c, 0x38, 0xf4, 0xaf, + 0x6b, 0x27, 0xe3, 0x9f, 0x5a, 0x16, 0xd2, 0x8e, 0x4a, 0x05, 0xc1, 0x7d, + 0x39, 0xf5, 0xb0, 0x6c, 0x28, 0xe4, 0xa0, 0x5b, 0x17, 0xd3, 0x8f, 0x4b, + 0x06, 0xc2, 0x7e, 0x3a, 0xf6, 0xb2, 0x6d, 0x29, 0xe5, 0xa1, 0x5d, 0x18, + 0xd4, 0x90, 0x4c, 0x08, 0xc3, 0x7f, 0x3b, 0xf7, 0xb3, 0x6e, 0x2a, 0xe6, + 0xa2, 0x5e, 0x19, 0xd5, 0x91, 0x4d, 0x09, 0xc4, 0x80, 0x3c, 0xf8, 0xb4, + 0x6f, 0x2b, 0xe7, 0xa3, 0x5f, 0x1a, 0xd6, 0x92, 0x4e, 0x0a, 0xc5, 0x81, + 0x3d, 0xf9, 0xb5, 0x70, 0x2c, 0xe8, 0xa4, 0x60, 0x1b, 0xd7, 0x93, 0x4f, + 0x0b, 0xc6, 0x82, 0x3e, 0xfa, 0xb6, 0x71, 0x2d, 0xe9, 0xa5, 0x61, 0x1c, + 0xd8, 0x94, 0x50, 0x0c, 0xc8, 0x83, 0x3f, 0xfb, 0xb7, 0x73, 0x2e, 0xea, + 0xa6, 0x62, 0x1e, 0xd9, 0x95, 0x51, 0x0d, 0xc9, 0x84, 0x40, 0xfc, 0xb8, + 0x74, 0x2f, 0xeb, 0xa7, 0x63, 0x1f, 0xda, 0x96, 0x52, 0x0e, 0xca, 0x85, + 0x41, 0xfd, 0xb9, 0x75, 0x30, 0xec, 0xa8, 0x64, 0x20, 0xdb, 0x97, 0x53, + 0x0f, 0xcb, 0x86, 0x42, 0xfe, 0xba, 0x76, 0x31, 0xed, 0xa9, 0x65, 0x21, + 0xdc, 0x98, 0x54, 0x10, 0xcc, 0x87, 0x43, 0xff, 0xbb, 0x77, 0x32, 0xee, + 0xaa, 0x66, 0x22, 0xde, 0x99, 0x55, 0x11, 0xcd, 0x89, 0x44, 0x00, 0xbc, + 0x78, 0x34, 0xef, 0xab, 0x67, 0x23, 0xdf, 0x9a, 0x56, 0x12, 0xce, 0x8a, + 0x45, 0x01, 0xbd, 0x79, 0x35, 0xf0, 0xac, 0x68, 0x24, 0xe0, 0x9b, 0x57, + 0x13, 0xcf, 0x8b, 0x46, 0x02, 0xbe, 0x7a, 0x36, 0xf1, 0xad, 0x69, 0x25, + 0xe1, 0x9c, 0x58, 0x14, 0xd0, 0x8c, 0x47, 0x03, 0xbf, 0x7b, 0x37, 0xf2, + 0xae, 0x6a, 0x26, 0xe2, 0x9d, 0x59, 0x15, 0xd1, 0x8d, 0x48, 0x04, 0xc0, + 0x7c, 0x38, 0xf4, 0xaf, 0x6b, 0x27, 0xe3, 0x9f, 0x5a, 0x16, 0xd2, 0x8e, + 0x4a, 0x05, 0xc1, 0x7d, 0x39, 0xf5, 0xb0, 0x6c, 0x28, 0xe4, 0xa0, 0x5b, + 0x17, 0xd3, 0x8f, 0x4b, 0x06, 0xc2, 0x7e, 0x3a, 0xf6, 0xb1, 0x6d, 0x29, + 0xe5, 0xa1, 0x5c, 0x18, 0xd4, 0x90, 0x4c, 0x07, 0xc3, 0x7f, 0x3b, 0xf7, + 0xb2, 0x6e, 0x2a, 0xe6, 0xa2, 0x5d, 0x19, 0xd5, 0x91, 0x4d, 0x08, 0xc4, + 0x80, 0x3c, 0xf8, 0xb3, 0x6f, 0x2b, 0xe7, 0xa3, 0x5f, 0x1a, 0xd6, 0x92, + 0x4e, 0x0a, 0xc5, 0x81, 0x3d, 0xf9, 0xb5, 0x70, 0x2c, 0xe8, 0xa4, 0x60, + 0x1b, 0xd7, 0x93, 0x4f, 0x0b, 0xc6, 0x82, 0x3e, 0xfa, 0xb6, 0x71, 0x2d, + 0xe9, 0xa5, 0x61, 0x1c, 0xd8, 0x94, 0x50, 0x0c, 0xc7, 0x83, 0x3f, 0xfb, + 0xb7, 0x72, 0x2e, 0xea, 0xa6, 0x62, 0x1d, 0xd9, 0x95, 0x51, 0x0d, 0xc8, + 0x84, 0x40, 0xfc, 0xb8, 0x73, 0x2f, 0xeb, 0xa7, 0x63, 0x1e, 0xda, 0x96, + 0x52, 0x0e, 0xc9, 0x85, 0x41, 0xfd, 0xb9, 0x75, 0x30, 0xec, 0xa8, 0x64, + 0x20, 0xdb, 0x97, 0x53, 0x0f, 0xcb, 0x86, 0x42, 0xfe, 0xba, 0x76, 0x31, + 0xed, 0xa9, 0x65, 0x21, 0xdc, 0x98, 0x54, 0x10, 0xcc, 0x87, 0x43, 0xff, + 0xbb, 0x77, 0x32, 0xee, 0xaa, 0x66, 0x22, 0xdd, 0x99, 0x55, 0x11, 0xcd, + 0x88, 0x44, 0x00, 0xbc, 0x78, 0x33, 0xef, 0xab, 0x67, 0x23, 0xde, 0x9a, + 0x56, 0x12, 0xce, 0x89, 0x45, 0x01, 0xbd, 0x79, 0x34, 0xf0, 0xac, 0x68, + 0x24, 0xdf, 0x9b, 0x57, 0x13, 0xcf, 0x8b, 0x46, 0x02, 0xbe, 0x7a, 0x36, + 0xf1, 0xad, 0x69, 0x25, 0xe1, 0x9c, 0x58, 0x14, 0xd0, 0x8c, 0x47, 0x03, + 0xbf, 0x7b, 0x37, 0xf2, 0xae, 0x6a, 0x26, 0xe2, 0x9d, 0x59, 0x15, 0xd1, + 0x8d, 0x48, 0x04, 0xc0, 0x7c, 0x38, 0xf3, 0xaf, 0x6b, 0x27, 0xe3, 0x9e, + 0x5a, 0x16, 0xd2, 0x8e, 0x49, 0x05, 0xc1, 0x7d, 0x39, 0xf4, 0xb0, 0x6c, + 0x28, 0xe4, 0x9f, 0x5b, 0x17, 0xd3, 0x8f, 0x4a, 0x06, 0xc2, 0x7e, 0x3a, + 0xf5, 0xb1, 0x6d, 0x29, 0xe5, 0xa1, 0x5c, 0x18, 0xd4, 0x90, 0x4c, 0x07, + 0xc3, 0x7f, 0x3b, 0xf7, 0xb2, 0x6e, 0x2a, 0xe6, 0xa2, 0x5d, 0x19, 0xd5, + 0x91, 0x4d, 0x08, 0xc4, 0x80, 0x3c, 0xf8, 0xb3, 0x6f, 0x2b, 0xe7, 0xa3, + 0x5e, 0x1a, 0xd6, 0x92, 0x4e, 0x09, 0xc5, 0x81, 0x3d, 0xf9, 0xb4, 0x70, + 0x2c, 0xe8, 0xa4, 0x5f, 0x1b, 0xd7, 0x93, 0x4f, 0x0a, 0xc6, 0x82, 0x3e, + 0xfa, 0xb5, 0x71, 0x2d, 0xe9, 0xa5, 0x60, 0x1c, 0xd8, 0x94, 0x50, 0x0b, + 0xc7, 0x83, 0x3f, 0xfb, 0xb7, 0x72, 0x2e, 0xea, 0xa6, 0x62, 0x1d, 0xd9, + 0x95, 0x51, 0x0d, 0xc8, 0x84, 0x40, 0xfc, 0xb8, 0x73, 0x2f, 0xeb, 0xa7, + 0x63, 0x1e, 0xda, 0x96, 0x52, 0x0e, 0xc9, 0x85, 0x41, 0xfd, 0xb9, 0x74, + 0x30, 0xec, 0xa8, 0x64, 0x1f, 0xdb, 0x97, 0x53, 0x0f, 0xca, 0x86, 0x42, + 0xfe, 0xba, 0x75, 0x31, 0xed, 0xa9, 0x65, 0x20, 0xdc, 0x98, 0x54, 0x10, + 0xcb, 0x87, 0x43, 0xff, 0xbb, 0x76, 0x32, 0xee, 0xaa, 0x66, 0x22, 0xdd, + 0x99, 0x55, 0x11, 0xcd, 0x88, 0x44, 0x00, 0xbc, 0x78, 0x33, 0xef, 0xab, + 0x67, 0x23, 0xde, 0x9a, 0x56, 0x12, 0xce, 0x89, 0x45, 0x01, 0xbd, 0x79, + 0x34, 0xf0, 0xac, 0x68, 0x24, 0xdf, 0x9b, 0x57, 0x13, 0xcf, 0x8a, 0x46, + 0x02, 0xbe, 0x7a, 0x35, 0xf1, 0xad, 0x69, 0x25, 0xe0, 0x9c, 0x58, 0x14, + 0xd0, 0x8b, 0x47, 0x03, 0xbf, 0x7b, 0x36, 0xf2, 0xae, 0x6a, 0x26, 0xe1, + 0x9d, 0x59, 0x15, 0xd1, 0x8c, 0x48, 0x04, 0xc0, 0x7c, 0x38, 0xf3, 0xaf, + 0x6b, 0x27, 0xe3, 0x9e, 0x5a, 0x16, 0xd2, 0x8e, 0x49, 0x05, 0xc1, 0x7d, + 0x39, 0xf4, 0xb0, 0x6c, 0x28, 0xe4, 0x9f, 0x5b, 0x17, 0xd3, 0x8f, 0x4a, + 0x06, 0xc2, 0x7e, 0x3a, 0xf5, 0xb1, 0x6d, 0x29, 0xe5, 0xa0, 0x5c, 0x18, + 0xd4, 0x90, 0x4b, 0x07, 0xc3, 0x7f, 0x3b, 0xf6, 0xb2, 0x6e, 0x2a, 0xe6, + 0xa1, 0x5d, 0x19, 0xd5, 0x91, 0x4c, 0x08, 0xc4, 0x80, 0x3c, 0xf7, 0xb3, + 0x6f, 0x2b, 0xe7, 0xa2, 0x5e, 0x1a, 0xd6, 0x92, 0x4e, 0x09, 0xc5, 0x81, + 0x3d, 0xf9, 0xb4, 0x70, 0x2c, 0xe8, 0xa4, 0x5f, 0x1b, 0xd7, 0x93, 0x4f, + 0x0a, 0xc6, 0x82, 0x3e, 0xfa, 0xb5, 0x71, 0x2d, 0xe9, 0xa5, 0x60, 0x1c, + 0xd8, 0x94, 0x50, 0x0b, 0xc7, 0x83, 0x3f, 0xfb, 0xb6, 0x72, 0x2e, 0xea, + 0xa6, 0x61, 0x1d, 0xd9, 0x95, 0x51, 0x0c, 0xc8, 0x84, 0x40, 0xfc, 0xb7, + 0x73, 0x2f, 0xeb, 0xa7, 0x62, 0x1e, 0xda, 0x96, 0x52, 0x0d, 0xc9, 0x85, + 0x41, 0xfd, 0xb8, 0x74, 0x30, 0xec, 0xa8, 0x64, 0x1f, 0xdb, 0x97, 0x53, + 0x0f, 0xca, 0x86, 0x42, 0xfe, 0xba, 0x75, 0x31, 0xed, 0xa9, 0x65, 0x20, + 0xdc, 0x98, 0x54, 0x10, 0xcb, 0x87, 0x43, 0xff, 0xbb, 0x76, 0x32, 0xee, + 0xaa, 0x66, 0x21, 0xdd, 0x99, 0x55, 0x11, 0xcc, 0x88, 0x44, 0x00, 0xbc, + 0x77, 0x33, 0xef, 0xab, 0x67, 0x22, 0xde, 0x9a, 0x56, 0x12, 0xcd, 0x89, + 0x45, 0x01, 0xbd, 0x78, 0x34, 0xf0, 0xac, 0x68, 0x23, 0xdf, 0x9b, 0x57, + 0x13, 0xce, 0x8a, 0x46, 0x02, 0xbe, 0x7a, 0x35, 0xf1, 0xad, 0x69, 0x25, + 0xe0, 0x9c, 0x58, 0x14, 0xd0, 0x8b, 0x47, 0x03, 0xbf, 0x7b, 0x36, 0xf2, + 0xae, 0x6a, 0x26, 0xe1, 0x9d, 0x59, 0x15, 0xd1, 0x8c, 0x48, 0x04, 0xc0, + 0x7c, 0x37, 0xf3, 0xaf, 0x6b, 0x27, 0xe2, 0x9e, 0x5a, 0x16, 0xd2, 0x8d, + 0x49, 0x05, 0xc1, 0x7d, 0x38, 0xf4, 0xb0, 0x6c, 0x28, 0xe3, 0x9f, 0x5b, + 0x17, 0xd3, 0x8e, 0x4a, 0x06, 0xc2, 0x7e, 0x39, 0xf5, 0xb1, 0x6d, 0x29, + 0xe5, 0xa0, 0x5c, 0x18, 0xd4, 0x90, 0x4b, 0x07, 0xc3, 0x7f, 0x3b, 0xf6, + 0xb2, 0x6e, 0x2a, 0xe6, 0xa1, 0x5d, 0x19, 0xd5, 0x91, 0x4c, 0x08, 0xc4, + 0x80, 0x3c, 0xf7, 0xb3, 0x6f, 0x2b, 0xe7, 0xa2, 0x5e, 0x1a, 0xd6, 0x92, + 0x4d, 0x09, 0xc5, 0x81, 0x3d, 0xf8, 0xb4, 0x70, 0x2c, 0xe8, 0xa3, 0x5f, + 0x1b, 0xd7, 0x93, 0x4e, 0x0a, 0xc6, 0x82, 0x3e, 0xf9, 0xb5, 0x71, 0x2d, + 0xe9, 0xa4, 0x60, 0x1c, 0xd8, 0x94, 0x4f, 0x0b, 0xc7, 0x83, 0x3f, 0xfb, + 0xb6, 0x72, 0x2e, 0xea, 0xa6, 0x61, 0x1d, 0xd9, 0x95, 0x51, 0x0c, 0xc8, + 0x84, 0x40, 0xfc, 0xb7, 0x73, 0x2f, 0xeb, 0xa7, 0x62, 0x1e, 0xda, 0x96, + 0x52, 0x0d, 0xc9, 0x85, 0x41, 0xfd, 0xb8, 0x74, 0x30, 0xec, 0xa8, 0x63, + 0x1f, 0xdb, 0x97, 0x53, 0x0e, 0xca, 0x86, 0x42, 0xfe, 0xb9, 0x75, 0x31, + 0xed, 0xa9, 0x64, 0x20, 0xdc, 0x98, 0x54, 0x0f, 0xcb, 0x87, 0x43, 0xff, + 0xba, 0x76, 0x32, 0xee, 0xaa, 0x65, 0x21, 0xdd, 0x99, 0x55, 0x11, 0xcc, + 0x88, 0x44, 0x00, 0xbc, 0x77, 0x33, 0xef, 0xab, 0x67, 0x22, 0xde, 0x9a, + 0x56, 0x12, 0xcd, 0x89, 0x45, 0x01, 0xbd, 0x78, 0x34, 0xf0, 0xac, 0x68, + 0x23, 0xdf, 0x9b, 0x57, 0x13, 0xce, 0x8a, 0x46, 0x02, 0xbe, 0x79, 0x35, + 0xf1, 0xad, 0x69, 0x24, 0xe0, 0x9c, 0x58, 0x14, 0xcf, 0x8b, 0x47, 0x03, + 0xbf, 0x7a, 0x36, 0xf2, 0xae, 0x6a, 0x25, 0xe1, 0x9d, 0x59, 0x15, 0xd0, + 0x8c, 0x48, 0x04, 0xc0, 0x7b, 0x37, 0xf3, 0xaf, 0x6b, 0x27, 0xe2, 0x9e, + 0x5a, 0x16, 0xd2, 0x8d, 0x49, 0x05, 0xc1, 0x7d, 0x38, 0xf4, 0xb0, 0x6c, + 0x28, 0xe3, 0x9f, 0x5b, 0x17, 0xd3, 0x8e, 0x4a, 0x06, 0xc2, 0x7e, 0x39, + 0xf5, 0xb1, 0x6d, 0x29, 0xe4, 0xa0, 0x5c, 0x18, 0xd4, 0x8f, 0x4b, 0x07, + 0xc3, 0x7f, 0x3a, 0xf6, 0xb2, 0x6e, 0x2a, 0xe5, 0xa1, 0x5d, 0x19, 0xd5, + 0x90, 0x4c, 0x08, 0xc4, 0x80, 0x3b, 0xf7, 0xb3, 0x6f, 0x2b, 0xe6, 0xa2, + 0x5e, 0x1a, 0xd6, 0x91, 0x4d, 0x09, 0xc5, 0x81, 0x3d, 0xf8, 0xb4, 0x70, + 0x2c, 0xe8, 0xa3, 0x5f, 0x1b, 0xd7, 0x93, 0x4e, 0x0a, 0xc6, 0x82, 0x3e, + 0xf9, 0xb5, 0x71, 0x2d, 0xe9, 0xa4, 0x60, 0x1c, 0xd8, 0x94, 0x4f, 0x0b, + 0xc7, 0x83, 0x3f, 0xfa, 0xb6, 0x72, 0x2e, 0xea, 0xa5, 0x61, 0x1d, 0xd9, + 0x95, 0x50, 0x0c, 0xc8, 0x84, 0x40, 0xfb, 0xb7, 0x73, 0x2f, 0xeb, 0xa6, + 0x62, 0x1e, 0xda, 0x96, 0x51, 0x0d, 0xc9, 0x85, 0x41, 0xfc, 0xb8, 0x74, + 0x30, 0xec, 0xa8, 0x63, 0x1f, 0xdb, 0x97, 0x53, 0x0e, 0xca, 0x86, 0x42, + 0xfe, 0xb9, 0x75, 0x31, 0xed, 0xa9, 0x64, 0x20, 0xdc, 0x98, 0x54, 0x0f, + 0xcb, 0x87, 0x43, 0xff, 0xba, 0x76, 0x32, 0xee, 0xaa, 0x65, 0x21, 0xdd, + 0x99, 0x55, 0x10, 0xcc, 0x88, 0x44, 0x00, 0xbb, 0x77, 0x33, 0xef, 0xab, + 0x66, 0x22, 0xde, 0x9a, 0x56, 0x11, 0xcd, 0x89, 0x45, 0x01, 0xbc, 0x78, + 0x34, 0xf0, 0xac, 0x67, 0x23, 0xdf, 0x9b, 0x57, 0x12, 0xce, 0x8a, 0x46, + 0x02, 0xbe, 0x79, 0x35, 0xf1, 0xad, 0x69, 0x24, 0xe0, 0x9c, 0x58, 0x14, + 0xcf, 0x8b, 0x47, 0x03, 0xbf, 0x7a, 0x36, 0xf2, 0xae, 0x6a, 0x25, 0xe1, + 0x9d, 0x59, 0x15, 0xd0, 0x8c, 0x48, 0x04, 0xc0, 0x7b, 0x37, 0xf3, 0xaf, + 0x6b, 0x26, 0xe2, 0x9e, 0x5a, 0x16, 0xd1, 0x8d, 0x49, 0x05, 0xc1, 0x7c, + 0x38, 0xf4, 0xb0, 0x6c, 0x27, 0xe3, 0x9f, 0x5b, 0x17, 0xd2, 0x8e, 0x4a, + 0x06, 0xc2, 0x7d, 0x39, 0xf5, 0xb1, 0x6d, 0x28, 0xe4, 0xa0, 0x5c, 0x18, + 0xd4, 0x8f, 0x4b, 0x07, 0xc3, 0x7f, 0x3a, 0xf6, 0xb2, 0x6e, 0x2a, 0xe5, + 0xa1, 0x5d, 0x19, 0xd5, 0x90, 0x4c, 0x08, 0xc4, 0x80, 0x3b, 0xf7, 0xb3, + 0x6f, 0x2b, 0xe6, 0xa2, 0x5e, 0x1a, 0xd6, 0x91, 0x4d, 0x09, 0xc5, 0x81, + 0x3c, 0xf8, 0xb4, 0x70, 0x2c, 0xe7, 0xa3, 0x5f, 0x1b, 0xd7, 0x92, 0x4e, + 0x0a, 0xc6, 0x82, 0x3d, 0xf9, 0xb5, 0x71, 0x2d, 0xe8, 0xa4, 0x60, 0x1c, + 0xd8, 0x93, 0x4f, 0x0b, 0xc7, 0x83, 0x3e, 0xfa, 0xb6, 0x72, 0x2e, 0xea, + 0xa5, 0x61, 0x1d, 0xd9, 0x95, 0x50, 0x0c, 0xc8, 0x84, 0x40, 0xfb, 0xb7, + 0x73, 0x2f, 0xeb, 0xa6, 0x62, 0x1e, 0xda, 0x96, 0x51, 0x0d, 0xc9, 0x85, + 0x41, 0xfc, 0xb8, 0x74, 0x30, 0xec, 0xa7, 0x63, 0x1f, 0xdb, 0x97, 0x52, + 0x0e, 0xca, 0x86, 0x42, 0xfd, 0xb9, 0x75, 0x31, 0xed, 0xa8, 0x64, 0x20, + 0xdc, 0x98, 0x53, 0x0f, 0xcb, 0x87, 0x43, 0xfe, 0xba, 0x76, 0x32, 0xee, + 0xa9, 0x65, 0x21, 0xdd, 0x99, 0x54, 0x10, 0xcc, 0x88, 0x44, 0x00, 0xbb, + 0x77, 0x33, 0xef, 0xab, 0x66, 0x22, 0xde, 0x9a, 0x56, 0x11, 0xcd, 0x89, + 0x45, 0x01, 0xbc, 0x78, 0x34, 0xf0, 0xac, 0x67, 0x23, 0xdf, 0x9b, 0x57, + 0x12, 0xce, 0x8a, 0x46, 0x02, 0xbd, 0x79, 0x35, 0xf1, 0xad, 0x68, 0x24, + 0xe0, 0x9c, 0x58, 0x13, 0xcf, 0x8b, 0x47, 0x03, 0xbe, 0x7a, 0x36, 0xf2, + 0xae, 0x69, 0x25, 0xe1, 0x9d, 0x59, 0x14, 0xd0, 0x8c, 0x48, 0x04, 0xbf, + 0x7b, 0x37, 0xf3, 0xaf, 0x6b, 0x26, 0xe2, 0x9e, 0x5a, 0x16, 0xd1, 0x8d, + 0x49, 0x05, 0xc1, 0x7c, 0x38, 0xf4, 0xb0, 0x6c, 0x27, 0xe3, 0x9f, 0x5b, + 0x17, 0xd2, 0x8e, 0x4a, 0x06, 0xc2, 0x7d, 0x39, 0xf5, 0xb1, 0x6d, 0x28, + 0xe4, 0xa0, 0x5c, 0x18, 0xd3, 0x8f, 0x4b, 0x07, 0xc3, 0x7e, 0x3a, 0xf6, + 0xb2, 0x6e, 0x29, 0xe5, 0xa1, 0x5d, 0x19, 0xd4, 0x90, 0x4c, 0x08, 0xc4, + 0x7f, 0x3b, 0xf7, 0xb3, 0x6f, 0x2a, 0xe6, 0xa2, 0x5e, 0x1a, 0xd5, 0x91, + 0x4d, 0x09, 0xc5, 0x81, 0x3c, 0xf8, 0xb4, 0x70, 0x2c, 0xe7, 0xa3, 0x5f, + 0x1b, 0xd7, 0x92, 0x4e, 0x0a, 0xc6, 0x82, 0x3d, 0xf9, 0xb5, 0x71, 0x2d, + 0xe8, 0xa4, 0x60, 0x1c, 0xd8, 0x93, 0x4f, 0x0b, 0xc7, 0x83, 0x3e, 0xfa, + 0xb6, 0x72, 0x2e, 0xe9, 0xa5, 0x61, 0x1d, 0xd9, 0x94, 0x50, 0x0c, 0xc8, + 0x84, 0x3f, 0xfb, 0xb7, 0x73, 0x2f, 0xea, 0xa6, 0x62, 0x1e, 0xda, 0x95, + 0x51, 0x0d, 0xc9, 0x85, 0x40, 0xfc, 0xb8, 0x74, 0x30, 0xeb, 0xa7, 0x63, + 0x1f, 0xdb, 0x97, 0x52, 0x0e, 0xca, 0x86, 0x42, 0xfd, 0xb9, 0x75, 0x31, + 0xed, 0xa8, 0x64, 0x20, 0xdc, 0x98, 0x53, 0x0f, 0xcb, 0x87, 0x43, 0xfe, + 0xba, 0x76, 0x32, 0xee, 0xa9, 0x65, 0x21, 0xdd, 0x99, 0x54, 0x10, 0xcc, + 0x88, 0x44, 0xff, 0xbb, 0x77, 0x33, 0xef, 0xaa, 0x66, 0x22, 0xde, 0x9a, + 0x55, 0x11, 0xcd, 0x89, 0x45, 0x00, 0xbc, 0x78, 0x34, 0xf0, 0xab, 0x67, + 0x23, 0xdf, 0x9b, 0x56, 0x12, 0xce, 0x8a, 0x46, 0x01, 0xbd, 0x79, 0x35, + 0xf1, 0xad, 0x68, 0x24, 0xe0, 0x9c, 0x58, 0x13, 0xcf, 0x8b, 0x47, 0x03, + 0xbe, 0x7a, 0x36, 0xf2, 0xae, 0x69, 0x25, 0xe1, 0x9d, 0x59, 0x14, 0xd0, + 0x8c, 0x48, 0x04, 0xbf, 0x7b, 0x37, 0xf3, 0xaf, 0x6a, 0x26, 0xe2, 0x9e, + 0x5a, 0x15, 0xd1, 0x8d, 0x49, 0x05, 0xc0, 0x7c, 0x38, 0xf4, 0xb0, 0x6b, + 0x27, 0xe3, 0x9f, 0x5b, 0x16, 0xd2, 0x8e, 0x4a, 0x06, 0xc1, 0x7d, 0x39, + 0xf5, 0xb1, 0x6c, 0x28, 0xe4, 0xa0, 0x5c, 0x17, 0xd3, 0x8f, 0x4b, 0x07, + 0xc3, 0x7e, 0x3a, 0xf6, 0xb2, 0x6e, 0x29, 0xe5, 0xa1, 0x5d, 0x19, 0xd4, + 0x90, 0x4c, 0x08, 0xc4, 0x7f, 0x3b, 0xf7, 0xb3, 0x6f, 0x2a, 0xe6, 0xa2, + 0x5e, 0x1a, 0xd5, 0x91, 0x4d, 0x09, 0xc5, 0x80, 0x3c, 0xf8, 0xb4, 0x70, + 0x2b, 0xe7, 0xa3, 0x5f, 0x1b, 0xd6, 0x92, 0x4e, 0x0a, 0xc6, 0x81, 0x3d, + 0xf9, 0xb5, 0x71, 0x2c, 0xe8, 0xa4, 0x60, 0x1c, 0xd7, 0x93, 0x4f, 0x0b, + 0xc7, 0x82, 0x3e, 0xfa, 0xb6, 0x72, 0x2e, 0xe9, 0xa5, 0x61, 0x1d, 0xd9, + 0x94, 0x50, 0x0c, 0xc8, 0x84, 0x3f, 0xfb, 0xb7, 0x73, 0x2f, 0xea, 0xa6, + 0x62, 0x1e, 0xda, 0x95, 0x51, 0x0d, 0xc9, 0x85, 0x40, 0xfc, 0xb8, 0x74, + 0x30, 0xeb, 0xa7, 0x63, 0x1f, 0xdb, 0x96, 0x52, 0x0e, 0xca, 0x86, 0x41, + 0xfd, 0xb9, 0x75, 0x31, 0xec, 0xa8, 0x64, 0x20, 0xdc, 0x97, 0x53, 0x0f, + 0xcb, 0x87, 0x42, 0xfe, 0xba, 0x76, 0x32, 0xed, 0xa9, 0x65, 0x21, 0xdd, + 0x98, 0x54, 0x10, 0xcc, 0x88, 0x44, 0xff, 0xbb, 0x77, 0x33, 0xef, 0xaa, + 0x66, 0x22, 0xde, 0x9a, 0x55, 0x11, 0xcd, 0x89, 0x45, 0x00, 0xbc, 0x78, + 0x34, 0xf0, 0xab, 0x67, 0x23, 0xdf, 0x9b, 0x56, 0x12, 0xce, 0x8a, 0x46, + 0x01, 0xbd, 0x79, 0x35, 0xf1, 0xac, 0x68, 0x24, 0xe0, 0x9c, 0x57, 0x13, + 0xcf, 0x8b, 0x47, 0x02, 0xbe, 0x7a, 0x36, 0xf2, 0xad, 0x69, 0x25, 0xe1, + 0x9d, 0x58, 0x14, 0x50, 0x4b, 0x01, 0x02, 0x2d, 0x00, 0x2d, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xe1, 0xef, 0x3c, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x05, 0x00, 0x1c, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0x61, 0x2e, 0x62, 0x69, 0x6e, 0x01, 0x00, 0x18, 0x00, 0xb8, 0x0b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b, + 0x06, 0x06, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, + 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf7, 0x0b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x06, 0x07, 0x00, 0x00, + 0x00, 0x00, 0x46, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, +}; + +static const uint8_t commented_zip[3234] = +{ + 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x21, 0xe1, 0xef, 0x3c, 0xb8, 0x0b, 0x00, 0x00, 0xb8, 0x0b, + 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x61, 0x2e, 0x62, 0x69, 0x6e, 0x00, + 0xbb, 0x77, 0x33, 0xef, 0xab, 0x66, 0x22, 0xde, 0x9a, 0x56, 0x11, 0xcd, + 0x89, 0x45, 0x01, 0xbc, 0x78, 0x34, 0xf0, 0xac, 0x67, 0x23, 0xdf, 0x9b, + 0x57, 0x12, 0xce, 0x8a, 0x46, 0x02, 0xbd, 0x79, 0x35, 0xf1, 0xad, 0x68, + 0x24, 0xe0, 0x9c, 0x58, 0x13, 0xcf, 0x8b, 0x47, 0x03, 0xbe, 0x7a, 0x36, + 0xf2, 0xae, 0x69, 0x25, 0xe1, 0x9d, 0x59, 0x14, 0xd0, 0x8c, 0x48, 0x04, + 0xbf, 0x7b, 0x37, 0xf3, 0xaf, 0x6a, 0x26, 0xe2, 0x9e, 0x5a, 0x16, 0xd1, + 0x8d, 0x49, 0x05, 0xc1, 0x7c, 0x38, 0xf4, 0xb0, 0x6c, 0x27, 0xe3, 0x9f, + 0x5b, 0x17, 0xd2, 0x8e, 0x4a, 0x06, 0xc2, 0x7d, 0x39, 0xf5, 0xb1, 0x6d, + 0x28, 0xe4, 0xa0, 0x5c, 0x18, 0xd3, 0x8f, 0x4b, 0x07, 0xc3, 0x7e, 0x3a, + 0xf6, 0xb2, 0x6e, 0x29, 0xe5, 0xa1, 0x5d, 0x19, 0xd4, 0x90, 0x4c, 0x08, + 0xc4, 0x7f, 0x3b, 0xf7, 0xb3, 0x6f, 0x2a, 0xe6, 0xa2, 0x5e, 0x1a, 0xd5, + 0x91, 0x4d, 0x09, 0xc5, 0x80, 0x3c, 0xf8, 0xb4, 0x70, 0x2c, 0xe7, 0xa3, + 0x5f, 0x1b, 0xd7, 0x92, 0x4e, 0x0a, 0xc6, 0x82, 0x3d, 0xf9, 0xb5, 0x71, + 0x2d, 0xe8, 0xa4, 0x60, 0x1c, 0xd8, 0x93, 0x4f, 0x0b, 0xc7, 0x83, 0x3e, + 0xfa, 0xb6, 0x72, 0x2e, 0xe9, 0xa5, 0x61, 0x1d, 0xd9, 0x94, 0x50, 0x0c, + 0xc8, 0x84, 0x3f, 0xfb, 0xb7, 0x73, 0x2f, 0xea, 0xa6, 0x62, 0x1e, 0xda, + 0x95, 0x51, 0x0d, 0xc9, 0x85, 0x40, 0xfc, 0xb8, 0x74, 0x30, 0xeb, 0xa7, + 0x63, 0x1f, 0xdb, 0x96, 0x52, 0x0e, 0xca, 0x86, 0x42, 0xfd, 0xb9, 0x75, + 0x31, 0xed, 0xa8, 0x64, 0x20, 0xdc, 0x98, 0x53, 0x0f, 0xcb, 0x87, 0x43, + 0xfe, 0xba, 0x76, 0x32, 0xee, 0xa9, 0x65, 0x21, 0xdd, 0x99, 0x54, 0x10, + 0xcc, 0x88, 0x44, 0xff, 0xbb, 0x77, 0x33, 0xef, 0xaa, 0x66, 0x22, 0xde, + 0x9a, 0x55, 0x11, 0xcd, 0x89, 0x45, 0x00, 0xbc, 0x78, 0x34, 0xf0, 0xab, + 0x67, 0x23, 0xdf, 0x9b, 0x56, 0x12, 0xce, 0x8a, 0x46, 0x01, 0xbd, 0x79, + 0x35, 0xf1, 0xac, 0x68, 0x24, 0xe0, 0x9c, 0x58, 0x13, 0xcf, 0x8b, 0x47, + 0x03, 0xbe, 0x7a, 0x36, 0xf2, 0xae, 0x69, 0x25, 0xe1, 0x9d, 0x59, 0x14, + 0xd0, 0x8c, 0x48, 0x04, 0xbf, 0x7b, 0x37, 0xf3, 0xaf, 0x6a, 0x26, 0xe2, + 0x9e, 0x5a, 0x15, 0xd1, 0x8d, 0x49, 0x05, 0xc0, 0x7c, 0x38, 0xf4, 0xb0, + 0x6b, 0x27, 0xe3, 0x9f, 0x5b, 0x16, 0xd2, 0x8e, 0x4a, 0x06, 0xc1, 0x7d, + 0x39, 0xf5, 0xb1, 0x6c, 0x28, 0xe4, 0xa0, 0x5c, 0x17, 0xd3, 0x8f, 0x4b, + 0x07, 0xc2, 0x7e, 0x3a, 0xf6, 0xb2, 0x6e, 0x29, 0xe5, 0xa1, 0x5d, 0x19, + 0xd4, 0x90, 0x4c, 0x08, 0xc4, 0x7f, 0x3b, 0xf7, 0xb3, 0x6f, 0x2a, 0xe6, + 0xa2, 0x5e, 0x1a, 0xd5, 0x91, 0x4d, 0x09, 0xc5, 0x80, 0x3c, 0xf8, 0xb4, + 0x70, 0x2b, 0xe7, 0xa3, 0x5f, 0x1b, 0xd6, 0x92, 0x4e, 0x0a, 0xc6, 0x81, + 0x3d, 0xf9, 0xb5, 0x71, 0x2c, 0xe8, 0xa4, 0x60, 0x1c, 0xd7, 0x93, 0x4f, + 0x0b, 0xc7, 0x82, 0x3e, 0xfa, 0xb6, 0x72, 0x2d, 0xe9, 0xa5, 0x61, 0x1d, + 0xd9, 0x94, 0x50, 0x0c, 0xc8, 0x84, 0x3f, 0xfb, 0xb7, 0x73, 0x2f, 0xea, + 0xa6, 0x62, 0x1e, 0xda, 0x95, 0x51, 0x0d, 0xc9, 0x85, 0x40, 0xfc, 0xb8, + 0x74, 0x30, 0xeb, 0xa7, 0x63, 0x1f, 0xdb, 0x96, 0x52, 0x0e, 0xca, 0x86, + 0x41, 0xfd, 0xb9, 0x75, 0x31, 0xec, 0xa8, 0x64, 0x20, 0xdc, 0x97, 0x53, + 0x0f, 0xcb, 0x87, 0x42, 0xfe, 0xba, 0x76, 0x32, 0xed, 0xa9, 0x65, 0x21, + 0xdd, 0x98, 0x54, 0x10, 0xcc, 0x88, 0x43, 0xff, 0xbb, 0x77, 0x33, 0xef, + 0xaa, 0x66, 0x22, 0xde, 0x9a, 0x55, 0x11, 0xcd, 0x89, 0x45, 0x00, 0xbc, + 0x78, 0x34, 0xf0, 0xab, 0x67, 0x23, 0xdf, 0x9b, 0x56, 0x12, 0xce, 0x8a, + 0x46, 0x01, 0xbd, 0x79, 0x35, 0xf1, 0xac, 0x68, 0x24, 0xe0, 0x9c, 0x57, + 0x13, 0xcf, 0x8b, 0x47, 0x02, 0xbe, 0x7a, 0x36, 0xf2, 0xad, 0x69, 0x25, + 0xe1, 0x9d, 0x58, 0x14, 0xd0, 0x8c, 0x48, 0x03, 0xbf, 0x7b, 0x37, 0xf3, + 0xae, 0x6a, 0x26, 0xe2, 0x9e, 0x59, 0x15, 0xd1, 0x8d, 0x49, 0x05, 0xc0, + 0x7c, 0x38, 0xf4, 0xb0, 0x6b, 0x27, 0xe3, 0x9f, 0x5b, 0x16, 0xd2, 0x8e, + 0x4a, 0x06, 0xc1, 0x7d, 0x39, 0xf5, 0xb1, 0x6c, 0x28, 0xe4, 0xa0, 0x5c, + 0x17, 0xd3, 0x8f, 0x4b, 0x07, 0xc2, 0x7e, 0x3a, 0xf6, 0xb2, 0x6d, 0x29, + 0xe5, 0xa1, 0x5d, 0x18, 0xd4, 0x90, 0x4c, 0x08, 0xc3, 0x7f, 0x3b, 0xf7, + 0xb3, 0x6e, 0x2a, 0xe6, 0xa2, 0x5e, 0x19, 0xd5, 0x91, 0x4d, 0x09, 0xc4, + 0x80, 0x3c, 0xf8, 0xb4, 0x6f, 0x2b, 0xe7, 0xa3, 0x5f, 0x1b, 0xd6, 0x92, + 0x4e, 0x0a, 0xc6, 0x81, 0x3d, 0xf9, 0xb5, 0x71, 0x2c, 0xe8, 0xa4, 0x60, + 0x1c, 0xd7, 0x93, 0x4f, 0x0b, 0xc7, 0x82, 0x3e, 0xfa, 0xb6, 0x72, 0x2d, + 0xe9, 0xa5, 0x61, 0x1d, 0xd8, 0x94, 0x50, 0x0c, 0xc8, 0x83, 0x3f, 0xfb, + 0xb7, 0x73, 0x2e, 0xea, 0xa6, 0x62, 0x1e, 0xd9, 0x95, 0x51, 0x0d, 0xc9, + 0x84, 0x40, 0xfc, 0xb8, 0x74, 0x2f, 0xeb, 0xa7, 0x63, 0x1f, 0xda, 0x96, + 0x52, 0x0e, 0xca, 0x85, 0x41, 0xfd, 0xb9, 0x75, 0x31, 0xec, 0xa8, 0x64, + 0x20, 0xdc, 0x97, 0x53, 0x0f, 0xcb, 0x87, 0x42, 0xfe, 0xba, 0x76, 0x32, + 0xed, 0xa9, 0x65, 0x21, 0xdd, 0x98, 0x54, 0x10, 0xcc, 0x88, 0x43, 0xff, + 0xbb, 0x77, 0x33, 0xee, 0xaa, 0x66, 0x22, 0xde, 0x99, 0x55, 0x11, 0xcd, + 0x89, 0x44, 0x00, 0xbc, 0x78, 0x34, 0xef, 0xab, 0x67, 0x23, 0xdf, 0x9a, + 0x56, 0x12, 0xce, 0x8a, 0x45, 0x01, 0xbd, 0x79, 0x35, 0xf0, 0xac, 0x68, + 0x24, 0xe0, 0x9c, 0x57, 0x13, 0xcf, 0x8b, 0x47, 0x02, 0xbe, 0x7a, 0x36, + 0xf2, 0xad, 0x69, 0x25, 0xe1, 0x9d, 0x58, 0x14, 0xd0, 0x8c, 0x48, 0x03, + 0xbf, 0x7b, 0x37, 0xf3, 0xae, 0x6a, 0x26, 0xe2, 0x9e, 0x59, 0x15, 0xd1, + 0x8d, 0x49, 0x04, 0xc0, 0x7c, 0x38, 0xf4, 0xaf, 0x6b, 0x27, 0xe3, 0x9f, + 0x5a, 0x16, 0xd2, 0x8e, 0x4a, 0x05, 0xc1, 0x7d, 0x39, 0xf5, 0xb0, 0x6c, + 0x28, 0xe4, 0xa0, 0x5b, 0x17, 0xd3, 0x8f, 0x4b, 0x06, 0xc2, 0x7e, 0x3a, + 0xf6, 0xb2, 0x6d, 0x29, 0xe5, 0xa1, 0x5d, 0x18, 0xd4, 0x90, 0x4c, 0x08, + 0xc3, 0x7f, 0x3b, 0xf7, 0xb3, 0x6e, 0x2a, 0xe6, 0xa2, 0x5e, 0x19, 0xd5, + 0x91, 0x4d, 0x09, 0xc4, 0x80, 0x3c, 0xf8, 0xb4, 0x6f, 0x2b, 0xe7, 0xa3, + 0x5f, 0x1a, 0xd6, 0x92, 0x4e, 0x0a, 0xc5, 0x81, 0x3d, 0xf9, 0xb5, 0x70, + 0x2c, 0xe8, 0xa4, 0x60, 0x1b, 0xd7, 0x93, 0x4f, 0x0b, 0xc6, 0x82, 0x3e, + 0xfa, 0xb6, 0x71, 0x2d, 0xe9, 0xa5, 0x61, 0x1c, 0xd8, 0x94, 0x50, 0x0c, + 0xc8, 0x83, 0x3f, 0xfb, 0xb7, 0x73, 0x2e, 0xea, 0xa6, 0x62, 0x1e, 0xd9, + 0x95, 0x51, 0x0d, 0xc9, 0x84, 0x40, 0xfc, 0xb8, 0x74, 0x2f, 0xeb, 0xa7, + 0x63, 0x1f, 0xda, 0x96, 0x52, 0x0e, 0xca, 0x85, 0x41, 0xfd, 0xb9, 0x75, + 0x30, 0xec, 0xa8, 0x64, 0x20, 0xdb, 0x97, 0x53, 0x0f, 0xcb, 0x86, 0x42, + 0xfe, 0xba, 0x76, 0x31, 0xed, 0xa9, 0x65, 0x21, 0xdc, 0x98, 0x54, 0x10, + 0xcc, 0x87, 0x43, 0xff, 0xbb, 0x77, 0x32, 0xee, 0xaa, 0x66, 0x22, 0xde, + 0x99, 0x55, 0x11, 0xcd, 0x89, 0x44, 0x00, 0xbc, 0x78, 0x34, 0xef, 0xab, + 0x67, 0x23, 0xdf, 0x9a, 0x56, 0x12, 0xce, 0x8a, 0x45, 0x01, 0xbd, 0x79, + 0x35, 0xf0, 0xac, 0x68, 0x24, 0xe0, 0x9b, 0x57, 0x13, 0xcf, 0x8b, 0x46, + 0x02, 0xbe, 0x7a, 0x36, 0xf1, 0xad, 0x69, 0x25, 0xe1, 0x9c, 0x58, 0x14, + 0xd0, 0x8c, 0x47, 0x03, 0xbf, 0x7b, 0x37, 0xf2, 0xae, 0x6a, 0x26, 0xe2, + 0x9d, 0x59, 0x15, 0xd1, 0x8d, 0x48, 0x04, 0xc0, 0x7c, 0x38, 0xf4, 0xaf, + 0x6b, 0x27, 0xe3, 0x9f, 0x5a, 0x16, 0xd2, 0x8e, 0x4a, 0x05, 0xc1, 0x7d, + 0x39, 0xf5, 0xb0, 0x6c, 0x28, 0xe4, 0xa0, 0x5b, 0x17, 0xd3, 0x8f, 0x4b, + 0x06, 0xc2, 0x7e, 0x3a, 0xf6, 0xb1, 0x6d, 0x29, 0xe5, 0xa1, 0x5c, 0x18, + 0xd4, 0x90, 0x4c, 0x07, 0xc3, 0x7f, 0x3b, 0xf7, 0xb2, 0x6e, 0x2a, 0xe6, + 0xa2, 0x5d, 0x19, 0xd5, 0x91, 0x4d, 0x08, 0xc4, 0x80, 0x3c, 0xf8, 0xb3, + 0x6f, 0x2b, 0xe7, 0xa3, 0x5f, 0x1a, 0xd6, 0x92, 0x4e, 0x0a, 0xc5, 0x81, + 0x3d, 0xf9, 0xb5, 0x70, 0x2c, 0xe8, 0xa4, 0x60, 0x1b, 0xd7, 0x93, 0x4f, + 0x0b, 0xc6, 0x82, 0x3e, 0xfa, 0xb6, 0x71, 0x2d, 0xe9, 0xa5, 0x61, 0x1c, + 0xd8, 0x94, 0x50, 0x0c, 0xc7, 0x83, 0x3f, 0xfb, 0xb7, 0x72, 0x2e, 0xea, + 0xa6, 0x62, 0x1d, 0xd9, 0x95, 0x51, 0x0d, 0xc8, 0x84, 0x40, 0xfc, 0xb8, + 0x73, 0x2f, 0xeb, 0xa7, 0x63, 0x1e, 0xda, 0x96, 0x52, 0x0e, 0xc9, 0x85, + 0x41, 0xfd, 0xb9, 0x75, 0x30, 0xec, 0xa8, 0x64, 0x20, 0xdb, 0x97, 0x53, + 0x0f, 0xcb, 0x86, 0x42, 0xfe, 0xba, 0x76, 0x31, 0xed, 0xa9, 0x65, 0x21, + 0xdc, 0x98, 0x54, 0x10, 0xcc, 0x87, 0x43, 0xff, 0xbb, 0x77, 0x32, 0xee, + 0xaa, 0x66, 0x22, 0xdd, 0x99, 0x55, 0x11, 0xcd, 0x88, 0x44, 0x00, 0xbc, + 0x78, 0x33, 0xef, 0xab, 0x67, 0x23, 0xde, 0x9a, 0x56, 0x12, 0xce, 0x89, + 0x45, 0x01, 0xbd, 0x79, 0x34, 0xf0, 0xac, 0x68, 0x24, 0xdf, 0x9b, 0x57, + 0x13, 0xcf, 0x8b, 0x46, 0x02, 0xbe, 0x7a, 0x36, 0xf1, 0xad, 0x69, 0x25, + 0xe1, 0x9c, 0x58, 0x14, 0xd0, 0x8c, 0x47, 0x03, 0xbf, 0x7b, 0x37, 0xf2, + 0xae, 0x6a, 0x26, 0xe2, 0x9d, 0x59, 0x15, 0xd1, 0x8d, 0x48, 0x04, 0xc0, + 0x7c, 0x38, 0xf3, 0xaf, 0x6b, 0x27, 0xe3, 0x9e, 0x5a, 0x16, 0xd2, 0x8e, + 0x49, 0x05, 0xc1, 0x7d, 0x39, 0xf4, 0xb0, 0x6c, 0x28, 0xe4, 0x9f, 0x5b, + 0x17, 0xd3, 0x8f, 0x4a, 0x06, 0xc2, 0x7e, 0x3a, 0xf5, 0xb1, 0x6d, 0x29, + 0xe5, 0xa1, 0x5c, 0x18, 0xd4, 0x90, 0x4c, 0x07, 0xc3, 0x7f, 0x3b, 0xf7, + 0xb2, 0x6e, 0x2a, 0xe6, 0xa2, 0x5d, 0x19, 0xd5, 0x91, 0x4d, 0x08, 0xc4, + 0x80, 0x3c, 0xf8, 0xb3, 0x6f, 0x2b, 0xe7, 0xa3, 0x5e, 0x1a, 0xd6, 0x92, + 0x4e, 0x09, 0xc5, 0x81, 0x3d, 0xf9, 0xb4, 0x70, 0x2c, 0xe8, 0xa4, 0x5f, + 0x1b, 0xd7, 0x93, 0x4f, 0x0a, 0xc6, 0x82, 0x3e, 0xfa, 0xb5, 0x71, 0x2d, + 0xe9, 0xa5, 0x60, 0x1c, 0xd8, 0x94, 0x50, 0x0b, 0xc7, 0x83, 0x3f, 0xfb, + 0xb7, 0x72, 0x2e, 0xea, 0xa6, 0x62, 0x1d, 0xd9, 0x95, 0x51, 0x0d, 0xc8, + 0x84, 0x40, 0xfc, 0xb8, 0x73, 0x2f, 0xeb, 0xa7, 0x63, 0x1e, 0xda, 0x96, + 0x52, 0x0e, 0xc9, 0x85, 0x41, 0xfd, 0xb9, 0x74, 0x30, 0xec, 0xa8, 0x64, + 0x1f, 0xdb, 0x97, 0x53, 0x0f, 0xca, 0x86, 0x42, 0xfe, 0xba, 0x75, 0x31, + 0xed, 0xa9, 0x65, 0x20, 0xdc, 0x98, 0x54, 0x10, 0xcb, 0x87, 0x43, 0xff, + 0xbb, 0x76, 0x32, 0xee, 0xaa, 0x66, 0x22, 0xdd, 0x99, 0x55, 0x11, 0xcd, + 0x88, 0x44, 0x00, 0xbc, 0x78, 0x33, 0xef, 0xab, 0x67, 0x23, 0xde, 0x9a, + 0x56, 0x12, 0xce, 0x89, 0x45, 0x01, 0xbd, 0x79, 0x34, 0xf0, 0xac, 0x68, + 0x24, 0xdf, 0x9b, 0x57, 0x13, 0xcf, 0x8a, 0x46, 0x02, 0xbe, 0x7a, 0x35, + 0xf1, 0xad, 0x69, 0x25, 0xe0, 0x9c, 0x58, 0x14, 0xd0, 0x8b, 0x47, 0x03, + 0xbf, 0x7b, 0x36, 0xf2, 0xae, 0x6a, 0x26, 0xe1, 0x9d, 0x59, 0x15, 0xd1, + 0x8c, 0x48, 0x04, 0xc0, 0x7c, 0x38, 0xf3, 0xaf, 0x6b, 0x27, 0xe3, 0x9e, + 0x5a, 0x16, 0xd2, 0x8e, 0x49, 0x05, 0xc1, 0x7d, 0x39, 0xf4, 0xb0, 0x6c, + 0x28, 0xe4, 0x9f, 0x5b, 0x17, 0xd3, 0x8f, 0x4a, 0x06, 0xc2, 0x7e, 0x3a, + 0xf5, 0xb1, 0x6d, 0x29, 0xe5, 0xa0, 0x5c, 0x18, 0xd4, 0x90, 0x4b, 0x07, + 0xc3, 0x7f, 0x3b, 0xf6, 0xb2, 0x6e, 0x2a, 0xe6, 0xa1, 0x5d, 0x19, 0xd5, + 0x91, 0x4c, 0x08, 0xc4, 0x80, 0x3c, 0xf7, 0xb3, 0x6f, 0x2b, 0xe7, 0xa2, + 0x5e, 0x1a, 0xd6, 0x92, 0x4e, 0x09, 0xc5, 0x81, 0x3d, 0xf9, 0xb4, 0x70, + 0x2c, 0xe8, 0xa4, 0x5f, 0x1b, 0xd7, 0x93, 0x4f, 0x0a, 0xc6, 0x82, 0x3e, + 0xfa, 0xb5, 0x71, 0x2d, 0xe9, 0xa5, 0x60, 0x1c, 0xd8, 0x94, 0x50, 0x0b, + 0xc7, 0x83, 0x3f, 0xfb, 0xb6, 0x72, 0x2e, 0xea, 0xa6, 0x61, 0x1d, 0xd9, + 0x95, 0x51, 0x0c, 0xc8, 0x84, 0x40, 0xfc, 0xb7, 0x73, 0x2f, 0xeb, 0xa7, + 0x62, 0x1e, 0xda, 0x96, 0x52, 0x0d, 0xc9, 0x85, 0x41, 0xfd, 0xb8, 0x74, + 0x30, 0xec, 0xa8, 0x64, 0x1f, 0xdb, 0x97, 0x53, 0x0f, 0xca, 0x86, 0x42, + 0xfe, 0xba, 0x75, 0x31, 0xed, 0xa9, 0x65, 0x20, 0xdc, 0x98, 0x54, 0x10, + 0xcb, 0x87, 0x43, 0xff, 0xbb, 0x76, 0x32, 0xee, 0xaa, 0x66, 0x21, 0xdd, + 0x99, 0x55, 0x11, 0xcc, 0x88, 0x44, 0x00, 0xbc, 0x77, 0x33, 0xef, 0xab, + 0x67, 0x22, 0xde, 0x9a, 0x56, 0x12, 0xcd, 0x89, 0x45, 0x01, 0xbd, 0x78, + 0x34, 0xf0, 0xac, 0x68, 0x23, 0xdf, 0x9b, 0x57, 0x13, 0xce, 0x8a, 0x46, + 0x02, 0xbe, 0x7a, 0x35, 0xf1, 0xad, 0x69, 0x25, 0xe0, 0x9c, 0x58, 0x14, + 0xd0, 0x8b, 0x47, 0x03, 0xbf, 0x7b, 0x36, 0xf2, 0xae, 0x6a, 0x26, 0xe1, + 0x9d, 0x59, 0x15, 0xd1, 0x8c, 0x48, 0x04, 0xc0, 0x7c, 0x37, 0xf3, 0xaf, + 0x6b, 0x27, 0xe2, 0x9e, 0x5a, 0x16, 0xd2, 0x8d, 0x49, 0x05, 0xc1, 0x7d, + 0x38, 0xf4, 0xb0, 0x6c, 0x28, 0xe3, 0x9f, 0x5b, 0x17, 0xd3, 0x8e, 0x4a, + 0x06, 0xc2, 0x7e, 0x39, 0xf5, 0xb1, 0x6d, 0x29, 0xe5, 0xa0, 0x5c, 0x18, + 0xd4, 0x90, 0x4b, 0x07, 0xc3, 0x7f, 0x3b, 0xf6, 0xb2, 0x6e, 0x2a, 0xe6, + 0xa1, 0x5d, 0x19, 0xd5, 0x91, 0x4c, 0x08, 0xc4, 0x80, 0x3c, 0xf7, 0xb3, + 0x6f, 0x2b, 0xe7, 0xa2, 0x5e, 0x1a, 0xd6, 0x92, 0x4d, 0x09, 0xc5, 0x81, + 0x3d, 0xf8, 0xb4, 0x70, 0x2c, 0xe8, 0xa3, 0x5f, 0x1b, 0xd7, 0x93, 0x4e, + 0x0a, 0xc6, 0x82, 0x3e, 0xf9, 0xb5, 0x71, 0x2d, 0xe9, 0xa4, 0x60, 0x1c, + 0xd8, 0x94, 0x4f, 0x0b, 0xc7, 0x83, 0x3f, 0xfb, 0xb6, 0x72, 0x2e, 0xea, + 0xa6, 0x61, 0x1d, 0xd9, 0x95, 0x51, 0x0c, 0xc8, 0x84, 0x40, 0xfc, 0xb7, + 0x73, 0x2f, 0xeb, 0xa7, 0x62, 0x1e, 0xda, 0x96, 0x52, 0x0d, 0xc9, 0x85, + 0x41, 0xfd, 0xb8, 0x74, 0x30, 0xec, 0xa8, 0x63, 0x1f, 0xdb, 0x97, 0x53, + 0x0e, 0xca, 0x86, 0x42, 0xfe, 0xb9, 0x75, 0x31, 0xed, 0xa9, 0x64, 0x20, + 0xdc, 0x98, 0x54, 0x0f, 0xcb, 0x87, 0x43, 0xff, 0xba, 0x76, 0x32, 0xee, + 0xaa, 0x65, 0x21, 0xdd, 0x99, 0x55, 0x11, 0xcc, 0x88, 0x44, 0x00, 0xbc, + 0x77, 0x33, 0xef, 0xab, 0x67, 0x22, 0xde, 0x9a, 0x56, 0x12, 0xcd, 0x89, + 0x45, 0x01, 0xbd, 0x78, 0x34, 0xf0, 0xac, 0x68, 0x23, 0xdf, 0x9b, 0x57, + 0x13, 0xce, 0x8a, 0x46, 0x02, 0xbe, 0x79, 0x35, 0xf1, 0xad, 0x69, 0x24, + 0xe0, 0x9c, 0x58, 0x14, 0xcf, 0x8b, 0x47, 0x03, 0xbf, 0x7a, 0x36, 0xf2, + 0xae, 0x6a, 0x25, 0xe1, 0x9d, 0x59, 0x15, 0xd0, 0x8c, 0x48, 0x04, 0xc0, + 0x7b, 0x37, 0xf3, 0xaf, 0x6b, 0x27, 0xe2, 0x9e, 0x5a, 0x16, 0xd2, 0x8d, + 0x49, 0x05, 0xc1, 0x7d, 0x38, 0xf4, 0xb0, 0x6c, 0x28, 0xe3, 0x9f, 0x5b, + 0x17, 0xd3, 0x8e, 0x4a, 0x06, 0xc2, 0x7e, 0x39, 0xf5, 0xb1, 0x6d, 0x29, + 0xe4, 0xa0, 0x5c, 0x18, 0xd4, 0x8f, 0x4b, 0x07, 0xc3, 0x7f, 0x3a, 0xf6, + 0xb2, 0x6e, 0x2a, 0xe5, 0xa1, 0x5d, 0x19, 0xd5, 0x90, 0x4c, 0x08, 0xc4, + 0x80, 0x3b, 0xf7, 0xb3, 0x6f, 0x2b, 0xe6, 0xa2, 0x5e, 0x1a, 0xd6, 0x91, + 0x4d, 0x09, 0xc5, 0x81, 0x3d, 0xf8, 0xb4, 0x70, 0x2c, 0xe8, 0xa3, 0x5f, + 0x1b, 0xd7, 0x93, 0x4e, 0x0a, 0xc6, 0x82, 0x3e, 0xf9, 0xb5, 0x71, 0x2d, + 0xe9, 0xa4, 0x60, 0x1c, 0xd8, 0x94, 0x4f, 0x0b, 0xc7, 0x83, 0x3f, 0xfa, + 0xb6, 0x72, 0x2e, 0xea, 0xa5, 0x61, 0x1d, 0xd9, 0x95, 0x50, 0x0c, 0xc8, + 0x84, 0x40, 0xfb, 0xb7, 0x73, 0x2f, 0xeb, 0xa6, 0x62, 0x1e, 0xda, 0x96, + 0x51, 0x0d, 0xc9, 0x85, 0x41, 0xfc, 0xb8, 0x74, 0x30, 0xec, 0xa8, 0x63, + 0x1f, 0xdb, 0x97, 0x53, 0x0e, 0xca, 0x86, 0x42, 0xfe, 0xb9, 0x75, 0x31, + 0xed, 0xa9, 0x64, 0x20, 0xdc, 0x98, 0x54, 0x0f, 0xcb, 0x87, 0x43, 0xff, + 0xba, 0x76, 0x32, 0xee, 0xaa, 0x65, 0x21, 0xdd, 0x99, 0x55, 0x10, 0xcc, + 0x88, 0x44, 0x00, 0xbb, 0x77, 0x33, 0xef, 0xab, 0x66, 0x22, 0xde, 0x9a, + 0x56, 0x11, 0xcd, 0x89, 0x45, 0x01, 0xbc, 0x78, 0x34, 0xf0, 0xac, 0x67, + 0x23, 0xdf, 0x9b, 0x57, 0x12, 0xce, 0x8a, 0x46, 0x02, 0xbe, 0x79, 0x35, + 0xf1, 0xad, 0x69, 0x24, 0xe0, 0x9c, 0x58, 0x14, 0xcf, 0x8b, 0x47, 0x03, + 0xbf, 0x7a, 0x36, 0xf2, 0xae, 0x6a, 0x25, 0xe1, 0x9d, 0x59, 0x15, 0xd0, + 0x8c, 0x48, 0x04, 0xc0, 0x7b, 0x37, 0xf3, 0xaf, 0x6b, 0x26, 0xe2, 0x9e, + 0x5a, 0x16, 0xd1, 0x8d, 0x49, 0x05, 0xc1, 0x7c, 0x38, 0xf4, 0xb0, 0x6c, + 0x27, 0xe3, 0x9f, 0x5b, 0x17, 0xd2, 0x8e, 0x4a, 0x06, 0xc2, 0x7d, 0x39, + 0xf5, 0xb1, 0x6d, 0x28, 0xe4, 0xa0, 0x5c, 0x18, 0xd4, 0x8f, 0x4b, 0x07, + 0xc3, 0x7f, 0x3a, 0xf6, 0xb2, 0x6e, 0x2a, 0xe5, 0xa1, 0x5d, 0x19, 0xd5, + 0x90, 0x4c, 0x08, 0xc4, 0x80, 0x3b, 0xf7, 0xb3, 0x6f, 0x2b, 0xe6, 0xa2, + 0x5e, 0x1a, 0xd6, 0x91, 0x4d, 0x09, 0xc5, 0x81, 0x3c, 0xf8, 0xb4, 0x70, + 0x2c, 0xe7, 0xa3, 0x5f, 0x1b, 0xd7, 0x92, 0x4e, 0x0a, 0xc6, 0x82, 0x3d, + 0xf9, 0xb5, 0x71, 0x2d, 0xe8, 0xa4, 0x60, 0x1c, 0xd8, 0x93, 0x4f, 0x0b, + 0xc7, 0x83, 0x3e, 0xfa, 0xb6, 0x72, 0x2e, 0xea, 0xa5, 0x61, 0x1d, 0xd9, + 0x95, 0x50, 0x0c, 0xc8, 0x84, 0x40, 0xfb, 0xb7, 0x73, 0x2f, 0xeb, 0xa6, + 0x62, 0x1e, 0xda, 0x96, 0x51, 0x0d, 0xc9, 0x85, 0x41, 0xfc, 0xb8, 0x74, + 0x30, 0xec, 0xa7, 0x63, 0x1f, 0xdb, 0x97, 0x52, 0x0e, 0xca, 0x86, 0x42, + 0xfd, 0xb9, 0x75, 0x31, 0xed, 0xa8, 0x64, 0x20, 0xdc, 0x98, 0x53, 0x0f, + 0xcb, 0x87, 0x43, 0xfe, 0xba, 0x76, 0x32, 0xee, 0xa9, 0x65, 0x21, 0xdd, + 0x99, 0x54, 0x10, 0xcc, 0x88, 0x44, 0x00, 0xbb, 0x77, 0x33, 0xef, 0xab, + 0x66, 0x22, 0xde, 0x9a, 0x56, 0x11, 0xcd, 0x89, 0x45, 0x01, 0xbc, 0x78, + 0x34, 0xf0, 0xac, 0x67, 0x23, 0xdf, 0x9b, 0x57, 0x12, 0xce, 0x8a, 0x46, + 0x02, 0xbd, 0x79, 0x35, 0xf1, 0xad, 0x68, 0x24, 0xe0, 0x9c, 0x58, 0x13, + 0xcf, 0x8b, 0x47, 0x03, 0xbe, 0x7a, 0x36, 0xf2, 0xae, 0x69, 0x25, 0xe1, + 0x9d, 0x59, 0x14, 0xd0, 0x8c, 0x48, 0x04, 0xbf, 0x7b, 0x37, 0xf3, 0xaf, + 0x6b, 0x26, 0xe2, 0x9e, 0x5a, 0x16, 0xd1, 0x8d, 0x49, 0x05, 0xc1, 0x7c, + 0x38, 0xf4, 0xb0, 0x6c, 0x27, 0xe3, 0x9f, 0x5b, 0x17, 0xd2, 0x8e, 0x4a, + 0x06, 0xc2, 0x7d, 0x39, 0xf5, 0xb1, 0x6d, 0x28, 0xe4, 0xa0, 0x5c, 0x18, + 0xd3, 0x8f, 0x4b, 0x07, 0xc3, 0x7e, 0x3a, 0xf6, 0xb2, 0x6e, 0x29, 0xe5, + 0xa1, 0x5d, 0x19, 0xd4, 0x90, 0x4c, 0x08, 0xc4, 0x7f, 0x3b, 0xf7, 0xb3, + 0x6f, 0x2a, 0xe6, 0xa2, 0x5e, 0x1a, 0xd5, 0x91, 0x4d, 0x09, 0xc5, 0x81, + 0x3c, 0xf8, 0xb4, 0x70, 0x2c, 0xe7, 0xa3, 0x5f, 0x1b, 0xd7, 0x92, 0x4e, + 0x0a, 0xc6, 0x82, 0x3d, 0xf9, 0xb5, 0x71, 0x2d, 0xe8, 0xa4, 0x60, 0x1c, + 0xd8, 0x93, 0x4f, 0x0b, 0xc7, 0x83, 0x3e, 0xfa, 0xb6, 0x72, 0x2e, 0xe9, + 0xa5, 0x61, 0x1d, 0xd9, 0x94, 0x50, 0x0c, 0xc8, 0x84, 0x3f, 0xfb, 0xb7, + 0x73, 0x2f, 0xea, 0xa6, 0x62, 0x1e, 0xda, 0x95, 0x51, 0x0d, 0xc9, 0x85, + 0x40, 0xfc, 0xb8, 0x74, 0x30, 0xeb, 0xa7, 0x63, 0x1f, 0xdb, 0x97, 0x52, + 0x0e, 0xca, 0x86, 0x42, 0xfd, 0xb9, 0x75, 0x31, 0xed, 0xa8, 0x64, 0x20, + 0xdc, 0x98, 0x53, 0x0f, 0xcb, 0x87, 0x43, 0xfe, 0xba, 0x76, 0x32, 0xee, + 0xa9, 0x65, 0x21, 0xdd, 0x99, 0x54, 0x10, 0xcc, 0x88, 0x44, 0xff, 0xbb, + 0x77, 0x33, 0xef, 0xaa, 0x66, 0x22, 0xde, 0x9a, 0x55, 0x11, 0xcd, 0x89, + 0x45, 0x00, 0xbc, 0x78, 0x34, 0xf0, 0xab, 0x67, 0x23, 0xdf, 0x9b, 0x56, + 0x12, 0xce, 0x8a, 0x46, 0x01, 0xbd, 0x79, 0x35, 0xf1, 0xad, 0x68, 0x24, + 0xe0, 0x9c, 0x58, 0x13, 0xcf, 0x8b, 0x47, 0x03, 0xbe, 0x7a, 0x36, 0xf2, + 0xae, 0x69, 0x25, 0xe1, 0x9d, 0x59, 0x14, 0xd0, 0x8c, 0x48, 0x04, 0xbf, + 0x7b, 0x37, 0xf3, 0xaf, 0x6a, 0x26, 0xe2, 0x9e, 0x5a, 0x15, 0xd1, 0x8d, + 0x49, 0x05, 0xc0, 0x7c, 0x38, 0xf4, 0xb0, 0x6b, 0x27, 0xe3, 0x9f, 0x5b, + 0x16, 0xd2, 0x8e, 0x4a, 0x06, 0xc1, 0x7d, 0x39, 0xf5, 0xb1, 0x6c, 0x28, + 0xe4, 0xa0, 0x5c, 0x17, 0xd3, 0x8f, 0x4b, 0x07, 0xc3, 0x7e, 0x3a, 0xf6, + 0xb2, 0x6e, 0x29, 0xe5, 0xa1, 0x5d, 0x19, 0xd4, 0x90, 0x4c, 0x08, 0xc4, + 0x7f, 0x3b, 0xf7, 0xb3, 0x6f, 0x2a, 0xe6, 0xa2, 0x5e, 0x1a, 0xd5, 0x91, + 0x4d, 0x09, 0xc5, 0x80, 0x3c, 0xf8, 0xb4, 0x70, 0x2b, 0xe7, 0xa3, 0x5f, + 0x1b, 0xd6, 0x92, 0x4e, 0x0a, 0xc6, 0x81, 0x3d, 0xf9, 0xb5, 0x71, 0x2c, + 0xe8, 0xa4, 0x60, 0x1c, 0xd7, 0x93, 0x4f, 0x0b, 0xc7, 0x82, 0x3e, 0xfa, + 0xb6, 0x72, 0x2e, 0xe9, 0xa5, 0x61, 0x1d, 0xd9, 0x94, 0x50, 0x0c, 0xc8, + 0x84, 0x3f, 0xfb, 0xb7, 0x73, 0x2f, 0xea, 0xa6, 0x62, 0x1e, 0xda, 0x95, + 0x51, 0x0d, 0xc9, 0x85, 0x40, 0xfc, 0xb8, 0x74, 0x30, 0xeb, 0xa7, 0x63, + 0x1f, 0xdb, 0x96, 0x52, 0x0e, 0xca, 0x86, 0x41, 0xfd, 0xb9, 0x75, 0x31, + 0xec, 0xa8, 0x64, 0x20, 0xdc, 0x97, 0x53, 0x0f, 0xcb, 0x87, 0x42, 0xfe, + 0xba, 0x76, 0x32, 0xed, 0xa9, 0x65, 0x21, 0xdd, 0x98, 0x54, 0x10, 0xcc, + 0x88, 0x44, 0xff, 0xbb, 0x77, 0x33, 0xef, 0xaa, 0x66, 0x22, 0xde, 0x9a, + 0x55, 0x11, 0xcd, 0x89, 0x45, 0x00, 0xbc, 0x78, 0x34, 0xf0, 0xab, 0x67, + 0x23, 0xdf, 0x9b, 0x56, 0x12, 0xce, 0x8a, 0x46, 0x01, 0xbd, 0x79, 0x35, + 0xf1, 0xac, 0x68, 0x24, 0xe0, 0x9c, 0x57, 0x13, 0xcf, 0x8b, 0x47, 0x02, + 0xbe, 0x7a, 0x36, 0xf2, 0xad, 0x69, 0x25, 0xe1, 0x9d, 0x58, 0x14, 0x50, + 0x4b, 0x01, 0x02, 0x14, 0x03, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x21, 0x00, 0x21, 0xe1, 0xef, 0x3c, 0xb8, 0x0b, 0x00, 0x00, 0xb8, + 0x0b, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x2e, 0x62, + 0x69, 0x6e, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x33, 0x00, 0x00, 0x00, 0xdb, 0x0b, 0x00, 0x00, 0x7e, 0x00, + 0x50, 0x4b, 0x05, 0x06, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x73, 0x20, 0x6c, + 0x69, 0x6b, 0x65, 0x20, 0x61, 0x20, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x20, 0x62, 0x75, 0x74, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x63, 0x6f, + 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x50, 0x4b, 0x05, 0x06, 0x20, 0x6c, + 0x6f, 0x6f, 0x6b, 0x73, 0x20, 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x61, 0x20, + 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x20, 0x62, 0x75, 0x74, 0x20, 0x69, + 0x73, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x20, + 0x50, 0x4b, 0x05, 0x06, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x73, 0x20, 0x6c, + 0x69, 0x6b, 0x65, 0x20, 0x61, 0x20, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x20, 0x62, 0x75, 0x74, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x63, 0x6f, + 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x20, +}; + +static int failures = 0; +static int checks = 0; + +static void check(int ok, const char *what) +{ + checks++; + printf(" %-5s %s\n", ok ? "ok" : "FAIL", what); + if (!ok) + failures++; +} + +typedef struct +{ + const uint8_t *data; + size_t len; + unsigned reads; +} source_t; + +static int64_t read_cb(void *ud, uint64_t off, void *dst, size_t len) +{ + source_t *s = (source_t*)ud; + if (off > s->len) + return -1; + if (len > s->len - (size_t)off) + len = s->len - (size_t)off; + memcpy(dst, s->data + (size_t)off, len); + s->reads++; + return (int64_t)len; +} + +static void check_member(rzip_archive_t *a, const char *name, + const uint8_t *want, size_t want_len, int in_memory, const char *what) +{ + int idx = rzip_archive_find(a, name); + const uint8_t *view; + uint8_t *out = NULL; + size_t len = 0; + char label[96]; + + strcpy(label, what); + strcat(label, ": "); + strcat(label, name); + + check(idx >= 0, label); + if (idx < 0) + return; + check(rzip_archive_entry(a, (uint32_t)idx)->size == want_len, label); + + view = rzip_archive_entry_view(a, (uint32_t)idx, &len); + if (in_memory && rzip_archive_entry(a, (uint32_t)idx)->method == RZIP_METHOD_STORED) + check(view && len == want_len && !memcmp(view, want, want_len), label); + else + check(view == NULL, label); + + check(rzip_archive_extract(a, (uint32_t)idx, &out, &len) == RZIP_OK + && len == want_len && !memcmp(out, want, want_len), label); + free(out); +} + +static void check_archive(const uint8_t *data, size_t len, const char *what, + const char *b_name, const uint8_t *a_want, const uint8_t *b_want) +{ + rzip_archive_t *a = NULL; + source_t src; + int res; + + printf("%s\n", what); + + res = rzip_archive_open(&a, data, len, NULL, NULL); + check(res == RZIP_OK && a, "open in memory"); + if (a) + { + check_member(a, "a.bin", a_want, A_LEN, 1, "memory"); + if (b_name) + check_member(a, b_name, b_want, B_LEN, 1, "memory"); + check(rzip_archive_find(a, "dir/") < 0, "a directory is not a member"); + check(rzip_archive_find(a, "nope.bin") < 0, "a missing name"); + rzip_archive_close(a); + } + + src.data = data; + src.len = len; + src.reads = 0; + a = NULL; + res = rzip_archive_open(&a, NULL, len, read_cb, &src); + check(res == RZIP_OK && a, "open through reads"); + if (a) + { + check_member(a, "a.bin", a_want, A_LEN, 0, "reads"); + if (b_name) + check_member(a, b_name, b_want, B_LEN, 0, "reads"); + rzip_archive_close(a); + } +} + +int main(void) +{ + static uint8_t a_want[A_LEN]; + static uint8_t b_want[B_LEN]; + rzip_archive_t *a = NULL; + uint32_t i; + + for (i = 0; i < A_LEN; i++) + a_want[i] = (uint8_t)((i * 2654435761u) >> 13); + for (i = 0; i < B_LEN; i++) + b_want[i] = (uint8_t)((i * 7) % 251); + + check_archive(stored_zip, sizeof(stored_zip), "stored, extra fields, subdirectory", + "dir/b.bin", a_want, b_want); + check_archive(deflated_zip, sizeof(deflated_zip), "deflated", + "b.bin", a_want, b_want); + check_archive(zip64_zip, sizeof(zip64_zip), "zip64 records", + NULL, a_want, b_want); + check_archive(commented_zip, sizeof(commented_zip), "archive comment", + NULL, a_want, b_want); + + /* not an archive, and one cut short of its directory */ + check(rzip_archive_open(&a, a_want, A_LEN, NULL, NULL) != RZIP_OK && !a, + "random bytes are refused"); + check(rzip_archive_open(&a, stored_zip, sizeof(stored_zip) - 30, NULL, NULL) != RZIP_OK && !a, + "a truncated archive is refused"); + + printf("%d checks, %d failures\n", checks, failures); + return failures ? 1 : 0; +} diff --git a/samples/formats/rzip/rzip_seek_test.c b/samples/formats/rzip/rzip_seek_test.c new file mode 100644 index 00000000..403ec258 --- /dev/null +++ b/samples/formats/rzip/rzip_seek_test.c @@ -0,0 +1,408 @@ +/* The zip reader's index for reading a deflated member at any offset + * (rzip_seek): every read must give the bytes a whole extraction gives. + * + * seek_test checks an archive it makes itself + * seek_test ARCHIVE.zip [span] checks every deflated member + * seek_test -t ARCHIVE.zip times the largest member + */ +#include +#include +#include +#include +#include + +#include +#include +#include + +static int failures; +#define CHECK(c, what) do { if (!(c)) { failures++; \ + fprintf(stderr, "FAIL %s:%d: %s (%s)\n", __FILE__, __LINE__, #c, what); } } while (0) + +static uint8_t *file_data; +static size_t file_len; + +static int64_t read_cb(void *ud, uint64_t off, void *dst, size_t len) +{ + (void)ud; + if (off > file_len) + return -1; + if (len > file_len - off) + len = file_len - (size_t)off; + memcpy(dst, file_data + off, len); + return (int64_t)len; +} + +static uint32_t rng_state = 12345; +static uint32_t rng(void) +{ + rng_state = rng_state * 1664525u + 1013904223u; + return rng_state >> 8; +} + +static void put16(uint8_t *p, uint32_t v) { p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8); } +static void put32(uint8_t *p, uint32_t v) { put16(p, v); put16(p + 2, v >> 16); } + +/* An archive of one deflated member, made here: some of it text-like, + * some of it runs, some of it noise, so the stream has blocks of every + * kind and length. */ +#define OWN_LEN (900 * 1024) +static int make_archive(void) +{ + static const char name[] = "own.bin"; + const size_t name_len = sizeof(name) - 1; + uint8_t *plain = (uint8_t*)malloc(OWN_LEN); + uint8_t *z; + size_t cap = OWN_LEN + OWN_LEN / 8 + 4096; + size_t clen = 0; + size_t i; + void *def; + uint8_t *p; + uint32_t crc; + + for (i = 0; i < OWN_LEN; i++) + { + uint32_t r = rng(); + if ((i >> 16) % 3 == 0) + plain[i] = (uint8_t)("the quick brown fox "[i % 20] + ((r & 0xff) < 8)); + else if ((i >> 16) % 3 == 1) + plain[i] = (uint8_t)((i >> 9) & 0xff); + else + plain[i] = (uint8_t)(r & ((r >> 9) & 1 ? 0xff : 0x0f)); + } + crc = encoding_crc32(0, plain, OWN_LEN); + z = (uint8_t*)malloc(30 + name_len + cap + 46 + name_len + 22); + if (!(def = rdeflate_new(6, -15))) + return 0; + rdeflate_set_in(def, plain, OWN_LEN); + rdeflate_set_out(def, z + 30 + name_len, cap); + rdeflate_finish(def); + for (;;) + { + size_t rd = 0, wr = 0; + int st = rdeflate_process(def, &rd, &wr); + clen += wr; + if (st == RDEFLATE_PROCESS_END) + break; + if (st == RDEFLATE_PROCESS_ERROR || (!rd && !wr)) + return 0; + } + rdeflate_free(def); + free(plain); + + p = z; /* local header */ + memset(p, 0, 30); + put32(p, 0x04034b50); put16(p + 4, 20); put16(p + 8, 8); + put32(p + 14, crc); put32(p + 18, (uint32_t)clen); put32(p + 22, OWN_LEN); + put16(p + 26, (uint32_t)name_len); + memcpy(p + 30, name, name_len); + p = z + 30 + name_len + clen; /* central directory */ + memset(p, 0, 46); + put32(p, 0x02014b50); put16(p + 4, 20); put16(p + 6, 20); put16(p + 10, 8); + put32(p + 16, crc); put32(p + 20, (uint32_t)clen); put32(p + 24, OWN_LEN); + put16(p + 28, (uint32_t)name_len); + memcpy(p + 46, name, name_len); + p += 46 + name_len; /* and its end */ + memset(p, 0, 22); + put32(p, 0x06054b50); put16(p + 8, 1); put16(p + 10, 1); + put32(p + 12, (uint32_t)(46 + name_len)); + put32(p + 16, (uint32_t)(30 + name_len + clen)); + file_data = z; + file_len = (size_t)(p + 22 - z); + return 1; +} + +static double now(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec + ts.tv_nsec / 1e9; +} + +static void check_member(rzip_archive_t *a, uint32_t idx, uint32_t span, + const char *how) +{ + const rzip_entry_t *e = rzip_archive_entry(a, idx); + uint8_t *want = NULL; + uint8_t *got; + size_t want_len = 0; + rzip_seek_t *s; + uint64_t upto; + unsigned i; + int res = 0; + + if (rzip_archive_extract(a, idx, &want, &want_len) != RZIP_OK) + return; + s = rzip_seek_new(a, idx, span); + if (!e->size || e->csize == e->size) + { + /* stored: no index */ + if (s && want_len) { /* a deflated member that did not shrink */ } + } + if (!s) + { + free(want); + return; + } + got = (uint8_t*)malloc(want_len + 1); + CHECK(rzip_seek_size(s) == want_len, how); + CHECK(rzip_seek_covered(s) == 0, how); + /* nothing can be read before it is indexed */ + if (want_len) + CHECK(rzip_seek_read(s, 0, got, 1) == RZIP_ERROR_PARAM, how); + + /* indexed a piece at a time; what is covered reads right at once */ + for (upto = span / 2; res == 0; upto += span + span / 3) + { + uint64_t covered; + + res = rzip_seek_build(s, upto); + covered = rzip_seek_covered(s); + CHECK(res >= 0, how); + CHECK(res == 1 || covered >= upto, how); + CHECK(covered <= want_len, how); + if (covered) + { + size_t len = covered > 5000 ? 5000 : (size_t)covered; + uint64_t off = covered - len; + CHECK(rzip_seek_read(s, off, got, len) == RZIP_OK, how); + CHECK(!memcmp(got, want + off, len), how); + } + if (covered < want_len) + CHECK(rzip_seek_read(s, covered, got, 1) == RZIP_ERROR_PARAM, how); + } + CHECK(res == 1, how); + CHECK(rzip_seek_covered(s) == want_len, how); + CHECK(rzip_seek_build(s, 0) == 1, how); + + /* all of it, and pieces of it from anywhere */ + CHECK(rzip_seek_read(s, 0, got, want_len) == RZIP_OK, how); + CHECK(!memcmp(got, want, want_len), how); + for (i = 0; i < 400 && want_len; i++) + { + uint64_t off = rng() % want_len; + size_t len = rng() % (i % 8 ? 3000 : 3 * span); + if (len > want_len - off) + len = want_len - (size_t)off; + CHECK(rzip_seek_read(s, off, got, len) == RZIP_OK, how); + CHECK(!memcmp(got, want + off, len), how); + } + /* in place: the same bytes when it gives them, and it gives them + * unless they lie across two spans */ + { + unsigned given = 0, asked = 0; + + for (i = 0; i < 400 && want_len > 2352; i++) + { + const uint8_t *view = (const uint8_t*)got; + uint64_t off = rng() % (want_len - 2352); + + CHECK(rzip_seek_view(s, off, 2352, &view) == RZIP_OK, how); + asked++; + if (view) + { + given++; + CHECK(!memcmp(view, want + off, 2352), how); + } + else + { + CHECK(rzip_seek_read(s, off, got, 2352) == RZIP_OK, how); + CHECK(!memcmp(got, want + off, 2352), how); + } + } + if (asked && want_len > 8u * span) + CHECK(given * 2 > asked, how); + } + /* the ends */ + CHECK(rzip_seek_read(s, want_len, got, 0) == RZIP_OK, how); + CHECK(rzip_seek_read(s, want_len, got, 1) == RZIP_ERROR_PARAM, how); + { + const uint8_t *view = (const uint8_t*)got; + CHECK(rzip_seek_view(s, want_len, 1, &view) == RZIP_ERROR_PARAM && !view, how); + } + if (want_len) + { + CHECK(rzip_seek_read(s, want_len - 1, got, 1) == RZIP_OK, how); + CHECK(got[0] == want[want_len - 1], how); + CHECK(rzip_seek_read(s, want_len - 1, got, 2) == RZIP_ERROR_PARAM, how); + } + rzip_seek_free(s); + free(got); + free(want); +} + +int main(int argc, char **argv) +{ + rzip_archive_t *a; + FILE *f; + uint32_t n, i; + uint32_t span; + int timing = argc > 1 && !strcmp(argv[1], "-t"); + const char *path = timing ? argv[2] : argv[1]; + unsigned members = 0; + + if (!path) + { + rzip_seek_t *s; + + if (!make_archive()) + return 2; + path = "an archive made here"; + /* its stream has places to restart from, not one block */ + if (rzip_archive_open(&a, file_data, file_len, NULL, NULL) != RZIP_OK) + return 2; + s = rzip_seek_new(a, 0, 32768); + CHECK(s != NULL, path); + if (s) + { + CHECK(rzip_seek_build(s, 200000) == 0, path); + CHECK(rzip_seek_covered(s) >= 200000 && rzip_seek_covered(s) < OWN_LEN, path); + rzip_seek_free(s); + } + rzip_archive_close(a); + } + else + { + if (!(f = fopen(path, "rb"))) + { + fprintf(stderr, "usage: seek_test [[-t] ARCHIVE.zip [span]]\n"); + return 2; + } + fseek(f, 0, SEEK_END); + file_len = (size_t)ftell(f); + fseek(f, 0, SEEK_SET); + file_data = (uint8_t*)malloc(file_len); + if (fread(file_data, 1, file_len, f) != file_len) + return 2; + fclose(f); + } + span = (!timing && argc > 2) ? (uint32_t)strtoul(argv[2], NULL, 0) : 0; + + if (timing) + { + const rzip_entry_t *big = NULL; + uint32_t big_idx = 0; + rzip_seek_t *s; + uint8_t *buf = (uint8_t*)malloc(2352); + double t0, t1; + unsigned reads = 2000; + + if (rzip_archive_open(&a, file_data, file_len, NULL, NULL) != RZIP_OK) + return 2; + n = rzip_archive_num_entries(a); + for (i = 0; i < n; i++) + { + const rzip_entry_t *e = rzip_archive_entry(a, i); + if (!big || e->size > big->size) { big = e; big_idx = i; } + } + s = rzip_seek_new(a, big_idx, 0); + if (!s) + return 2; + t0 = now(); + if (rzip_seek_build(s, (uint64_t)-1) != 1) + return 1; + t1 = now(); + printf("%s: %.0f MB in %.0f MB; indexed in %.2f s (%.0f MB/s)\n", big->name, + big->size / 1048576.0, big->csize / 1048576.0, t1 - t0, big->size / 1048576.0 / (t1 - t0)); + t0 = now(); + for (i = 0; i < reads; i++) + { + uint64_t off = ((uint64_t)rng() * 2352) % (big->size - 2352); + if (rzip_seek_read(s, off, buf, 2352) != RZIP_OK) + return 1; + } + t1 = now(); + printf("a sector from anywhere: %.2f ms each\n", (t1 - t0) * 1000.0 / reads); + t0 = now(); + for (i = 0; i < 20000; i++) + { + if (rzip_seek_read(s, (uint64_t)i * 2352, buf, 2352) != RZIP_OK) + return 1; + } + t1 = now(); + printf("sectors in order: %.1f MB/s\n", 20000 * 2352 / 1048576.0 / (t1 - t0)); + /* the spans are decoded by now: what a sector costs to hand over */ + { + unsigned pass, sum = 0; + t0 = now(); + for (pass = 0; pass < 50; pass++) + for (i = 0; i < 3000; i++) + { + if (rzip_seek_read(s, (uint64_t)i * 2352, buf, 2352) != RZIP_OK) + return 1; + sum += buf[100]; + } + t1 = now(); + printf("a decoded sector, copied out: %.0f ns", (t1 - t0) * 1e9 / 150000); + t0 = now(); + for (pass = 0; pass < 50; pass++) + for (i = 0; i < 3000; i++) + { + const uint8_t *view = NULL; + if (rzip_seek_view(s, (uint64_t)i * 2352, 2352, &view) != RZIP_OK) + return 1; + if (view) + sum += view[100]; + } + t1 = now(); + printf("; in place: %.0f ns (%u)\n", (t1 - t0) * 1e9 / 150000, sum & 1); + } + rzip_seek_free(s); + return 0; + } + + /* from memory, and through the read callback */ + if (rzip_archive_open(&a, file_data, file_len, NULL, NULL) != RZIP_OK) + return 2; + n = rzip_archive_num_entries(a); + for (i = 0; i < n; i++) + { + const rzip_entry_t *e = rzip_archive_entry(a, i); + if (!e->is_dir && e->csize != e->size) + members++; + check_member(a, i, span ? span : 32768, "memory"); + check_member(a, i, span ? span : 100000, "memory, another span"); + } + rzip_archive_close(a); + if (rzip_archive_open(&a, NULL, file_len, read_cb, NULL) != RZIP_OK) + return 2; + for (i = 0; i < n; i++) + check_member(a, i, span ? span : 32768, "callback"); + rzip_archive_close(a); + + /* a member whose bytes are damaged is refused, not read wrong for good: + * the index says so when it gets there */ + if (file_len > 4096) + { + if (rzip_archive_open(&a, file_data, file_len, NULL, NULL) != RZIP_OK) + return 2; + for (i = 0; i < n; i++) + { + const rzip_entry_t *e = rzip_archive_entry(a, i); + rzip_seek_t *s; + size_t at; + uint8_t old; + + if (e->is_dir || e->csize < 2000 || !(s = rzip_seek_new(a, i, 32768))) + continue; + at = (size_t)(e->data_off + e->csize / 2); + old = file_data[at]; + file_data[at] ^= 0x5a; + CHECK(rzip_seek_build(s, (uint64_t)-1) < 0, "damaged"); + CHECK(rzip_seek_covered(s) < e->size, "damaged"); + file_data[at] = old; + rzip_seek_free(s); + break; + } + rzip_archive_close(a); + } + + free(file_data); + if (failures) + { + printf("seek_test: %d failure(s) in %s\n", failures, path); + return 1; + } + printf("seek_test: ok, %u deflated member(s) of %s\n", members, path); + return 0; +} diff --git a/samples/formats/tga/Makefile b/samples/formats/tga/Makefile index 8de94d29..fc459790 100644 --- a/samples/formats/tga/Makefile +++ b/samples/formats/tga/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := rtga_test LIBRETRO_COMM_DIR := ../../.. @@ -6,7 +8,7 @@ SOURCES := \ rtga_test.c \ $(LIBRETRO_COMM_DIR)/formats/tga/rtga.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 diff --git a/samples/formats/xml/Makefile b/samples/formats/xml/Makefile index d1510e32..91239587 100644 --- a/samples/formats/xml/Makefile +++ b/samples/formats/xml/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := rxml LIBRETRO_XML_DIR := ../../../formats/xml @@ -16,7 +18,7 @@ SOURCES := \ $(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 += -DRXML_TEST -Wall -pedantic -std=gnu99 -g -I$(LIBRETRO_COMM_DIR)/include @@ -35,10 +37,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 diff --git a/samples/memmap/memshm_area_test/Makefile b/samples/memmap/memshm_area_test/Makefile index 2fcc6667..a734e0c3 100644 --- a/samples/memmap/memshm_area_test/Makefile +++ b/samples/memmap/memshm_area_test/Makefile @@ -1,9 +1,11 @@ +include ../../sample_objdir.mk + TARGET := memshm_area_test LIBRETRO_COMM_DIR := ../../.. SOURCES := memshm_area_test.c $(LIBRETRO_COMM_DIR)/memmap/memmap.c CFLAGS += -Wall -W -std=gnu99 -g -O1 -I$(LIBRETRO_COMM_DIR)/include LDFLAGS += -lrt -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) ifneq ($(SAN),) SANITIZER := $(SAN) endif @@ -14,6 +16,10 @@ 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) # On Windows the legacy slot-reservation path is what every system @@ -24,5 +30,6 @@ legacy: $(SOURCES) check: all ./$(TARGET) clean: + rm -rf $(SAMPLE_OBJDIR) rm -f $(OBJS) $(TARGET) $(TARGET)_legacy .PHONY: all check clean legacy diff --git a/samples/memmap/memshm_test/Makefile b/samples/memmap/memshm_test/Makefile index 1119ae7a..8433c2a7 100644 --- a/samples/memmap/memshm_test/Makefile +++ b/samples/memmap/memshm_test/Makefile @@ -1,9 +1,11 @@ +include ../../sample_objdir.mk + TARGET := memshm_test LIBRETRO_COMM_DIR := ../../.. SOURCES := memshm_test.c $(LIBRETRO_COMM_DIR)/memmap/memmap.c CFLAGS += -Wall -W -std=gnu99 -g -O1 -I$(LIBRETRO_COMM_DIR)/include LDFLAGS += -lrt -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 diff --git a/samples/net/Makefile b/samples/net/Makefile index d9226054..eec19a06 100644 --- a/samples/net/Makefile +++ b/samples/net/Makefile @@ -1,3 +1,5 @@ +include ../sample_objdir.mk + TARGETS = http_test http_parse_test net_ifinfo LIBRETRO_COMM_DIR := ../.. @@ -34,6 +36,7 @@ HTTP_TEST_C = \ $(LIBRETRO_COMM_DIR)/compat/compat_strcasestr.c \ $(LIBRETRO_COMM_DIR)/compat/compat_posix_string.c \ $(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \ + $(LIBRETRO_COMM_DIR)/encodings/encoding_base64.c \ $(LIBRETRO_COMM_DIR)/features/features_cpu.c \ $(LIBRETRO_COMM_DIR)/file/file_path.c \ $(LIBRETRO_COMM_DIR)/file/file_path_io.c \ @@ -46,7 +49,7 @@ HTTP_TEST_C = \ $(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \ net_http_test.c -HTTP_TEST_OBJS := $(HTTP_TEST_C:.c=.o) +HTTP_TEST_OBJS := $(call sample_objs,$(HTTP_TEST_C:.c=.o)) HTTP_PARSE_TEST_C = \ $(LIBRETRO_COMM_DIR)/net/net_http.c \ @@ -57,6 +60,7 @@ HTTP_PARSE_TEST_C = \ $(LIBRETRO_COMM_DIR)/compat/compat_strcasestr.c \ $(LIBRETRO_COMM_DIR)/compat/compat_posix_string.c \ $(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \ + $(LIBRETRO_COMM_DIR)/encodings/encoding_base64.c \ $(LIBRETRO_COMM_DIR)/features/features_cpu.c \ $(LIBRETRO_COMM_DIR)/file/file_path.c \ $(LIBRETRO_COMM_DIR)/file/file_path_io.c \ @@ -69,7 +73,7 @@ HTTP_PARSE_TEST_C = \ $(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \ net_http_parse_test.c -HTTP_PARSE_TEST_OBJS := $(HTTP_PARSE_TEST_C:.c=.o) +HTTP_PARSE_TEST_OBJS := $(call sample_objs,$(HTTP_PARSE_TEST_C:.c=.o)) NET_IFINFO_C = \ $(LIBRETRO_COMM_DIR)/net/net_ifinfo.c \ @@ -91,7 +95,7 @@ endif endif -NET_IFINFO_OBJS := $(NET_IFINFO_C:.c=.o) +NET_IFINFO_OBJS := $(call sample_objs,$(NET_IFINFO_C:.c=.o)) .PHONY: all clean @@ -100,6 +104,10 @@ all: $(TARGETS) %.o: %.c $(CC) $(INCFLAGS) $< -c $(CFLAGS) -o $@ +$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c + @mkdir -p $(@D) + $(CC) $(INCFLAGS) $< -c $(CFLAGS) -o $@ + http_parse_test: $(HTTP_PARSE_TEST_OBJS) $(CC) $(INCFLAGS) $(HTTP_PARSE_TEST_OBJS) $(CFLAGS) -o $@ @@ -110,4 +118,5 @@ net_ifinfo: $(NET_IFINFO_OBJS) $(CC) $(INCFLAGS) $(NET_IFINFO_OBJS) $(CFLAGS) -o $@ clean: + rm -rf $(SAMPLE_OBJDIR) rm -rf $(TARGETS) $(HTTP_TEST_OBJS) $(HTTP_PARSE_TEST_OBJS) $(NET_IFINFO_OBJS) diff --git a/samples/queues/mpsc_stack_test/Makefile b/samples/queues/mpsc_stack_test/Makefile index a06ecd66..f099099f 100644 --- a/samples/queues/mpsc_stack_test/Makefile +++ b/samples/queues/mpsc_stack_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := mpsc_stack_test LIBRETRO_COMM_DIR := ../../.. @@ -14,7 +16,7 @@ SOURCES := \ mpsc_stack_test.c \ $(LIBRETRO_COMM_DIR)/rthreads/rthreads.c -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) CFLAGS += -Wall -pedantic -std=gnu99 -g -O0 \ -DHAVE_THREADS -I$(LIBRETRO_COMM_DIR)/include @@ -34,7 +36,12 @@ $(TARGET): $(OBJS) %.o: %.c $(LIBRETRO_COMM_DIR)/include/queues/mpsc_stack.h $(CC) $(CFLAGS) -c -o $@ $< +$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c $(LIBRETRO_COMM_DIR)/include/queues/mpsc_stack.h + @mkdir -p $(@D) + $(CC) $(CFLAGS) -c -o $@ $< + clean: + rm -rf $(SAMPLE_OBJDIR) rm -f $(OBJS) $(TARGET) .PHONY: all clean diff --git a/samples/queues/msg_fifo_queue_test/Makefile b/samples/queues/msg_fifo_queue_test/Makefile index 0dc870a0..a148ff04 100644 --- a/samples/queues/msg_fifo_queue_test/Makefile +++ b/samples/queues/msg_fifo_queue_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := msg_fifo_queue_test LIBRETRO_COMM_DIR := ../../.. @@ -8,7 +10,7 @@ SOURCES := \ $(LIBRETRO_COMM_DIR)/compat/compat_strl.c \ $(LIBRETRO_COMM_DIR)/compat/compat_posix_string.c -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) CFLAGS += -Wall -pedantic -std=gnu99 -g -O0 -I$(LIBRETRO_COMM_DIR)/include # Allocation is fault-injected through these. @@ -24,10 +26,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 $@ $(OBJS) $(LDFLAGS) clean: + rm -rf $(SAMPLE_OBJDIR) rm -f $(TARGET) $(OBJS) .PHONY: all clean diff --git a/samples/queues/retro_spsc_bench/Makefile b/samples/queues/retro_spsc_bench/Makefile index 0ee93274..cfdadae0 100644 --- a/samples/queues/retro_spsc_bench/Makefile +++ b/samples/queues/retro_spsc_bench/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := retro_spsc_bench LIBRETRO_COMM_DIR := ../../.. @@ -11,7 +13,7 @@ SOURCES := \ $(LIBRETRO_COMM_DIR)/rthreads/rthreads.c \ $(LIBRETRO_COMM_DIR)/features/features_cpu.c -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) CFLAGS += $(EXTRA_CFLAGS) -Wall -pedantic -std=gnu99 -O2 \ -DHAVE_THREADS -I$(LIBRETRO_COMM_DIR)/include @@ -29,10 +31,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 diff --git a/samples/queues/retro_spsc_test/Makefile b/samples/queues/retro_spsc_test/Makefile index fffcba4c..7c6665ce 100644 --- a/samples/queues/retro_spsc_test/Makefile +++ b/samples/queues/retro_spsc_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := retro_spsc_test LIBRETRO_COMM_DIR := ../../.. @@ -16,7 +18,7 @@ SOURCES := \ $(LIBRETRO_COMM_DIR)/queues/retro_spsc.c \ $(LIBRETRO_COMM_DIR)/rthreads/rthreads.c -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) CFLAGS += -Wall -pedantic -std=gnu99 -g -O0 \ -DHAVE_THREADS -I$(LIBRETRO_COMM_DIR)/include @@ -35,10 +37,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 diff --git a/samples/queues/retro_waitable_spsc_test/Makefile b/samples/queues/retro_waitable_spsc_test/Makefile index 8f900ef5..b17105c4 100644 --- a/samples/queues/retro_waitable_spsc_test/Makefile +++ b/samples/queues/retro_waitable_spsc_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := retro_waitable_spsc_test LIBRETRO_COMM_DIR := ../../.. @@ -21,7 +23,7 @@ ifneq ($(SCOND),) TARGET := retro_waitable_spsc_test_scond endif -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) ifneq ($(SAN),) SANITIZER := $(SAN) @@ -36,6 +38,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 ./retro_waitable_spsc_test_scond clean: + rm -rf $(SAMPLE_OBJDIR) rm -f $(OBJS) retro_waitable_spsc_test retro_waitable_spsc_test_scond .PHONY: all check clean diff --git a/samples/queues/task_queue_budget_test/Makefile b/samples/queues/task_queue_budget_test/Makefile index 79521c26..9049fde6 100644 --- a/samples/queues/task_queue_budget_test/Makefile +++ b/samples/queues/task_queue_budget_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := task_queue_budget_test LIBRETRO_COMM_DIR := ../../.. @@ -8,9 +10,10 @@ SOURCES := \ task_queue_budget_test.c \ $(LIBRETRO_COMM_DIR)/queues/task_queue.c \ $(LIBRETRO_COMM_DIR)/rthreads/rthreads.c \ - $(LIBRETRO_COMM_DIR)/features/features_cpu.c + $(LIBRETRO_COMM_DIR)/features/features_cpu.c \ + $(LIBRETRO_COMM_DIR)/time/rtime.c -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) CFLAGS += -Wall -pedantic -std=gnu99 -g -O1 -fno-omit-frame-pointer \ -DHAVE_THREADS -I$(LIBRETRO_COMM_DIR)/include @@ -27,6 +30,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) $(LIBS) @@ -34,6 +41,7 @@ check: $(TARGET) ./$(TARGET) clean: - rm -f $(TARGET) $(OBJS) + rm -rf $(SAMPLE_OBJDIR) + rm -f $(TARGET) $(TARGET).exe $(OBJS) .PHONY: all check clean diff --git a/samples/queues/task_queue_detach_test/Makefile b/samples/queues/task_queue_detach_test/Makefile index d586478d..6e1eef02 100644 --- a/samples/queues/task_queue_detach_test/Makefile +++ b/samples/queues/task_queue_detach_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := task_queue_detach_test LIBRETRO_COMM_DIR := ../../.. @@ -8,9 +10,10 @@ SOURCES := \ task_queue_detach_test.c \ $(LIBRETRO_COMM_DIR)/queues/task_queue.c \ $(LIBRETRO_COMM_DIR)/rthreads/rthreads.c \ - $(LIBRETRO_COMM_DIR)/features/features_cpu.c + $(LIBRETRO_COMM_DIR)/features/features_cpu.c \ + $(LIBRETRO_COMM_DIR)/time/rtime.c -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) CFLAGS += -Wall -pedantic -std=gnu99 -g -O1 -fno-omit-frame-pointer \ -DHAVE_THREADS -I$(LIBRETRO_COMM_DIR)/include @@ -27,6 +30,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) $(LIBS) @@ -34,6 +41,7 @@ check: $(TARGET) ./$(TARGET) clean: - rm -f $(TARGET) $(OBJS) + rm -rf $(SAMPLE_OBJDIR) + rm -f $(TARGET) $(TARGET).exe $(OBJS) .PHONY: all check clean diff --git a/samples/queues/task_queue_init_failure_test/Makefile b/samples/queues/task_queue_init_failure_test/Makefile index 538f1884..9a553db6 100644 --- a/samples/queues/task_queue_init_failure_test/Makefile +++ b/samples/queues/task_queue_init_failure_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := task_queue_init_failure_test LIBRETRO_COMM_DIR := ../../.. @@ -9,9 +11,10 @@ SOURCES := \ task_queue_init_failure_test.c \ $(LIBRETRO_COMM_DIR)/queues/task_queue.c \ $(LIBRETRO_COMM_DIR)/rthreads/rthreads.c \ - $(LIBRETRO_COMM_DIR)/features/features_cpu.c + $(LIBRETRO_COMM_DIR)/features/features_cpu.c \ + $(LIBRETRO_COMM_DIR)/time/rtime.c -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) CFLAGS += -Wall -pedantic -std=gnu99 -g -O1 -fno-omit-frame-pointer \ -DHAVE_THREADS -I$(LIBRETRO_COMM_DIR)/include @@ -28,6 +31,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) $(LIBS) @@ -35,6 +42,7 @@ check: $(TARGET) ./$(TARGET) clean: - rm -f $(TARGET) $(OBJS) + rm -rf $(SAMPLE_OBJDIR) + rm -f $(TARGET) $(TARGET).exe $(OBJS) .PHONY: all check clean diff --git a/samples/queues/task_queue_main_thread_test/Makefile b/samples/queues/task_queue_main_thread_test/Makefile index 58521a81..61a75ebe 100644 --- a/samples/queues/task_queue_main_thread_test/Makefile +++ b/samples/queues/task_queue_main_thread_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := task_queue_main_thread_test LIBRETRO_COMM_DIR := ../../.. @@ -8,9 +10,10 @@ SOURCES := \ task_queue_main_thread_test.c \ $(LIBRETRO_COMM_DIR)/queues/task_queue.c \ $(LIBRETRO_COMM_DIR)/rthreads/rthreads.c \ - $(LIBRETRO_COMM_DIR)/features/features_cpu.c + $(LIBRETRO_COMM_DIR)/features/features_cpu.c \ + $(LIBRETRO_COMM_DIR)/time/rtime.c -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) CFLAGS += -Wall -pedantic -std=gnu99 -g -O1 -fno-omit-frame-pointer \ -DHAVE_THREADS -I$(LIBRETRO_COMM_DIR)/include @@ -27,6 +30,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) $(LIBS) @@ -34,6 +41,7 @@ check: $(TARGET) ./$(TARGET) clean: - rm -f $(TARGET) $(OBJS) + rm -rf $(SAMPLE_OBJDIR) + rm -f $(TARGET) $(TARGET).exe $(OBJS) .PHONY: all check clean diff --git a/samples/queues/task_queue_switch_test/Makefile b/samples/queues/task_queue_switch_test/Makefile new file mode 100644 index 00000000..e7603bd8 --- /dev/null +++ b/samples/queues/task_queue_switch_test/Makefile @@ -0,0 +1,48 @@ +include ../../sample_objdir.mk + +TARGET := task_queue_switch_test + +LIBRETRO_COMM_DIR := ../../.. + +# Built with HAVE_THREADS, since the switch under test is from the +# regular implementation to the threaded one. realloc() is wrapped so +# an absurd request is seen, and refused, rather than attempted. +SOURCES := \ + task_queue_switch_test.c \ + $(LIBRETRO_COMM_DIR)/queues/task_queue.c \ + $(LIBRETRO_COMM_DIR)/rthreads/rthreads.c \ + $(LIBRETRO_COMM_DIR)/features/features_cpu.c \ + $(LIBRETRO_COMM_DIR)/time/rtime.c + +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) + +CFLAGS += -Wall -pedantic -std=gnu99 -g -O1 -fno-omit-frame-pointer \ + -DHAVE_THREADS -I$(LIBRETRO_COMM_DIR)/include +LDFLAGS += -Wl,--wrap=realloc +LIBS += -lpthread + +ifneq ($(SANITIZER),) + CFLAGS := -fsanitize=$(SANITIZER) $(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) $(LIBS) + +check: $(TARGET) + ./$(TARGET) + +clean: + rm -rf $(SAMPLE_OBJDIR) + rm -f $(TARGET) $(TARGET).exe $(OBJS) + +.PHONY: all check clean diff --git a/samples/queues/task_queue_switch_test/task_queue_switch_test.c b/samples/queues/task_queue_switch_test/task_queue_switch_test.c new file mode 100644 index 00000000..1c66ec6f --- /dev/null +++ b/samples/queues/task_queue_switch_test/task_queue_switch_test.c @@ -0,0 +1,127 @@ +/* Regression test for switching libretro-common/queues/task_queue.c + * from the regular implementation to the threaded one while tasks are + * queued. + * + * task_queue_check() switches implementations when the threaded flag + * changes, as the Threaded Data Runloop setting does at runtime. The + * regular queue leaves its tasks on the shared lists, and the threaded + * one takes them over. It counted only the tasks pushed to it, so each + * inherited task took the running count one below zero when it + * finished. The gather sizes its report list from that count plus a + * margin; past the margin the unsigned size wrapped and it asked + * realloc() for about 32 GiB on every check. + * + * realloc() is wrapped: a request that size is recorded and refused, + * as it would be on a machine without the memory. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +/* More than the gather's margin of eight, so an undercount wraps. */ +#define TASKS 16 + +static unsigned failures = 0; + +static void check(bool cond, const char *what) +{ + if (cond) + printf(" [pass] %s\n", what); + else + { + printf(" [FAIL] %s\n", what); + failures++; + } +} + +/* --- realloc() that records and refuses an absurd request ------------ */ + +static size_t largest_request = 0; + +void *__real_realloc(void *ptr, size_t size); + +void *__wrap_realloc(void *ptr, size_t size) +{ + if (size > largest_request) + largest_request = size; + if (size > ((size_t)1 << 30)) + return NULL; + return __real_realloc(ptr, size); +} + +/* --- tasks that finish on their first run ---------------------------- */ + +static unsigned tasks_done = 0; + +static void test_task_handler(retro_task_t *task) +{ + task_set_flags(task, RETRO_TASK_FLG_FINISHED, true); +} + +static void test_task_callback(retro_task_t *task, void *task_data, + void *user_data, const char *error) +{ + tasks_done++; +} + +static void push_tasks(unsigned n) +{ + unsigned i; + for (i = 0; i < n; i++) + { + retro_task_t *task = task_init(); + task->handler = test_task_handler; + task->callback = test_task_callback; + task_queue_push(task); + } +} + +static void drain(unsigned want) +{ + unsigned i; + for (i = 0; i < 2000 && tasks_done < want; i++) + { + task_queue_check(); + retro_sleep(1); + } +} + +int main(void) +{ + printf("regular queue with tasks queued, then switched to threaded\n"); + task_queue_init(false, NULL); + push_tasks(TASKS); + task_queue_set_threaded(); + drain(TASKS); + check(task_queue_is_threaded(), "the check switched the queue to threaded"); + check(tasks_done == TASKS, + "every task queued before the switch reached its callback"); + check(largest_request <= ((size_t)1 << 20), + "the gather never sized its report list from a negative count"); + + /* The count has to be back at zero, not merely below the margin: + * a queue that keeps working afterwards shows it. */ + tasks_done = 0; + largest_request = 0; + push_tasks(TASKS); + drain(TASKS); + check(tasks_done == TASKS, "tasks pushed after the switch run"); + check(largest_request <= ((size_t)1 << 20), + "and the report list stays small"); + task_queue_deinit(); + + if (failures) + { + printf("%u failure(s)\n", failures); + return 1; + } + printf("task queue switch keeps its counts\n"); + return 0; +} diff --git a/samples/queues/task_queue_title_error_test/Makefile b/samples/queues/task_queue_title_error_test/Makefile index 8da25fda..fd106635 100644 --- a/samples/queues/task_queue_title_error_test/Makefile +++ b/samples/queues/task_queue_title_error_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := task_queue_title_error_test LIBRETRO_COMM_DIR := ../../.. @@ -14,7 +16,7 @@ SOURCES := \ task_queue_title_error_test.c \ $(LIBRETRO_COMM_DIR)/queues/task_queue.c -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) CFLAGS += -Wall -pedantic -std=gnu99 -g -O0 -I$(LIBRETRO_COMM_DIR)/include @@ -28,10 +30,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 diff --git a/samples/rthreads/retro_asym_eventcount_test/Makefile b/samples/rthreads/retro_asym_eventcount_test/Makefile index 97c72751..3f84c3f3 100644 --- a/samples/rthreads/retro_asym_eventcount_test/Makefile +++ b/samples/rthreads/retro_asym_eventcount_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := retro_asym_eventcount_test LIBRETRO_COMM_DIR := ../../.. @@ -17,7 +19,7 @@ LDFLAGS += -lpthread # -lrt. Harmless on newer glibc. LDFLAGS += -lrt -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) ifneq ($(SAN),) SANITIZER := $(SAN) @@ -32,6 +34,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) @@ -48,6 +54,7 @@ check: all RETRO_PROCBARRIER=signal ./$(TARGET) clean: + rm -rf $(SAMPLE_OBJDIR) rm -f $(OBJS) $(TARGET) .PHONY: all check clean diff --git a/samples/rthreads/retro_eventcount_bench/Makefile b/samples/rthreads/retro_eventcount_bench/Makefile index db23ae46..ddb920f7 100644 --- a/samples/rthreads/retro_eventcount_bench/Makefile +++ b/samples/rthreads/retro_eventcount_bench/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := retro_eventcount_bench LIBRETRO_COMM_DIR := ../../.. @@ -24,7 +26,7 @@ ifneq ($(SEM),) TARGET := retro_eventcount_bench_sem endif -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) CFLAGS += $(EXTRA_CFLAGS) -Wall -pedantic -std=gnu99 -O2 \ -DHAVE_THREADS -I$(LIBRETRO_COMM_DIR)/include @@ -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 $(OBJS) retro_eventcount_bench retro_eventcount_bench_scond retro_eventcount_bench_sem .PHONY: all clean diff --git a/samples/rthreads/retro_eventcount_test/Makefile b/samples/rthreads/retro_eventcount_test/Makefile index fe8a4d70..11467d3b 100644 --- a/samples/rthreads/retro_eventcount_test/Makefile +++ b/samples/rthreads/retro_eventcount_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := retro_eventcount_test LIBRETRO_COMM_DIR := ../../.. @@ -85,7 +87,7 @@ ifneq ($(SWITCH),) TARGET := retro_eventcount_test_switch endif -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) ifneq ($(SAN),) SANITIZER := $(SAN) @@ -100,6 +102,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) @@ -123,6 +129,7 @@ check: all ./retro_eventcount_test_ctr clean: + rm -rf $(SAMPLE_OBJDIR) rm -f $(OBJS) switch_arbiter_shim.o ctr_arbiter_shim.o retro_eventcount_test \ retro_eventcount_test_scond retro_eventcount_test_sem \ retro_eventcount_test_sempool retro_eventcount_test_switch \ diff --git a/samples/rthreads/retro_procbarrier_test/Makefile b/samples/rthreads/retro_procbarrier_test/Makefile index 9b493a5f..41ce136f 100644 --- a/samples/rthreads/retro_procbarrier_test/Makefile +++ b/samples/rthreads/retro_procbarrier_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := retro_procbarrier_test LIBRETRO_COMM_DIR := ../../.. @@ -16,7 +18,7 @@ LDFLAGS += -lpthread # -lrt. Harmless on newer glibc. LDFLAGS += -lrt -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) ifneq ($(SAN),) SANITIZER := $(SAN) @@ -31,6 +33,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) @@ -45,6 +51,7 @@ check: all RETRO_PROCBARRIER=signal ./$(TARGET) clean: + rm -rf $(SAMPLE_OBJDIR) rm -f $(OBJS) $(TARGET) .PHONY: all check clean diff --git a/samples/rthreads/sthread_ex_test/Makefile b/samples/rthreads/sthread_ex_test/Makefile index 0ccdcdc7..d4f9c9dd 100644 --- a/samples/rthreads/sthread_ex_test/Makefile +++ b/samples/rthreads/sthread_ex_test/Makefile @@ -1,9 +1,11 @@ +include ../../sample_objdir.mk + TARGET := sthread_ex_test LIBRETRO_COMM_DIR := ../../.. SOURCES := sthread_ex_test.c $(LIBRETRO_COMM_DIR)/rthreads/rthreads.c CFLAGS += -Wall -W -std=gnu99 -g -O1 -DHAVE_THREADS -I$(LIBRETRO_COMM_DIR)/include LDFLAGS += -lpthread -lrt -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 diff --git a/samples/rthreads/tpool_bench/Makefile b/samples/rthreads/tpool_bench/Makefile index 2aa79b7f..debbb6af 100644 --- a/samples/rthreads/tpool_bench/Makefile +++ b/samples/rthreads/tpool_bench/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := tpool_bench LIBRETRO_COMM_DIR := ../../.. @@ -13,7 +15,7 @@ SOURCES := \ $(LIBRETRO_COMM_DIR)/rthreads/rthreads.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 -O2 -I$(LIBRETRO_COMM_DIR)/include LDFLAGS += -lpthread @@ -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 diff --git a/samples/rthreads/tpool_handoff_test/Makefile b/samples/rthreads/tpool_handoff_test/Makefile index 18fb06a4..f2972392 100644 --- a/samples/rthreads/tpool_handoff_test/Makefile +++ b/samples/rthreads/tpool_handoff_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := tpool_handoff_test LIBRETRO_COMM_DIR := ../../.. @@ -12,7 +14,7 @@ SOURCES := \ $(LIBRETRO_COMM_DIR)/rthreads/rthreads.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 -O0 -I$(LIBRETRO_COMM_DIR)/include LDFLAGS += -lpthread @@ -35,10 +37,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 diff --git a/samples/rthreads/tpool_parallel_test/Makefile b/samples/rthreads/tpool_parallel_test/Makefile index a0baccf9..6d6f6682 100644 --- a/samples/rthreads/tpool_parallel_test/Makefile +++ b/samples/rthreads/tpool_parallel_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := tpool_parallel_test LIBRETRO_COMM_DIR := ../../.. @@ -13,7 +15,7 @@ SOURCES := \ $(LIBRETRO_COMM_DIR)/rthreads/rthreads.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 -O0 -I$(LIBRETRO_COMM_DIR)/include LDFLAGS += -lpthread @@ -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 diff --git a/samples/rthreads/tpool_wait_test/Makefile b/samples/rthreads/tpool_wait_test/Makefile index d9990520..57ed0dc5 100644 --- a/samples/rthreads/tpool_wait_test/Makefile +++ b/samples/rthreads/tpool_wait_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := tpool_wait_test LIBRETRO_COMM_DIR := ../../.. @@ -12,7 +14,7 @@ SOURCES := \ $(LIBRETRO_COMM_DIR)/rthreads/retro_eventcount.c \ $(LIBRETRO_COMM_DIR)/rthreads/rthreads.c -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) CFLAGS += -Wall -pedantic -std=gnu99 -g -O0 -I$(LIBRETRO_COMM_DIR)/include LDFLAGS += -lpthread @@ -35,10 +37,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 diff --git a/samples/sample_objdir.mk b/samples/sample_objdir.mk new file mode 100644 index 00000000..1b9c0904 --- /dev/null +++ b/samples/sample_objdir.mk @@ -0,0 +1,12 @@ +# Objects of sources outside the sample go under its own obj/, so no +# sample links another's leftovers built with other flags. ../ is kept +# as __/ there; sample_src maps it back: +# OBJS := $(call sample_objs,$(SOURCES:.c=.o)) +# %.o: $$(call sample_src,$$*).c + +SAMPLE_OBJDIR ?= obj + +sample_objs = $(foreach o,$(1),$(if $(filter ../% /%,$(o)),$(SAMPLE_OBJDIR)/$(subst ../,__/,$(o)),$(o))) +sample_src = $(subst __/,../,$(patsubst $(SAMPLE_OBJDIR)/%,%,$(1))) + +.SECONDEXPANSION: diff --git a/samples/streams/rzip/Makefile b/samples/streams/rzip/Makefile index ddac3f96..02435e5d 100644 --- a/samples/streams/rzip/Makefile +++ b/samples/streams/rzip/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := rzip TARGET_TEST := rzip_chunk_size_test TARGET_TEST2 := rzip_matches_buf_test @@ -65,11 +67,11 @@ SOURCES_TEST2 := rzip_matches_buf_test.c $(COMMON_SOURCES) SOURCES_TEST3 := rzip_codec_test.c $(COMMON_SOURCES) SOURCES_TEST4 := rzip_parallel_test.c $(COMMON_SOURCES) -OBJS := $(SOURCES:.c=.o) -OBJS_TEST := $(SOURCES_TEST:.c=.o) -OBJS_TEST2 := $(SOURCES_TEST2:.c=.o) -OBJS_TEST3 := $(SOURCES_TEST3:.c=.o) -OBJS_TEST4 := $(SOURCES_TEST4:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) +OBJS_TEST := $(call sample_objs,$(SOURCES_TEST:.c=.o)) +OBJS_TEST2 := $(call sample_objs,$(SOURCES_TEST2:.c=.o)) +OBJS_TEST3 := $(call sample_objs,$(SOURCES_TEST3:.c=.o)) +OBJS_TEST4 := $(call sample_objs,$(SOURCES_TEST4:.c=.o)) INCLUDE_DIRS += -I$(LIBRETRO_COMM_DIR)/include CFLAGS += -DHAVE_COMPRESSION -DHAVE_RZSTD -DHAVE_THREADS -Wall -pedantic -std=gnu99 $(INCLUDE_DIRS) @@ -100,6 +102,10 @@ all: $(TARGET) $(TARGET_TEST) $(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): $(OBJS) $(CC) -o $@ $^ $(LDFLAGS) @@ -118,6 +124,7 @@ $(TARGET_TEST4): $(OBJS_TEST4) $(CC) -o $@ $^ $(LDFLAGS) -Wl,--wrap=cpu_features_get_core_amount clean: + rm -rf $(SAMPLE_OBJDIR) rm -f $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) $(TARGET_TEST4) $(OBJS) $(OBJS_TEST) $(OBJS_TEST2) $(OBJS_TEST3) $(OBJS_TEST4) .PHONY: clean diff --git a/samples/string/strlcpy_append/Makefile b/samples/string/strlcpy_append/Makefile index 0f8a45e4..d6533666 100644 --- a/samples/string/strlcpy_append/Makefile +++ b/samples/string/strlcpy_append/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := strlcpy_append_test LIBRETRO_COMM_DIR := ../../.. @@ -12,7 +14,7 @@ SOURCES := \ $(LIBRETRO_COMM_DIR)/compat/compat_strl.c \ $(LIBRETRO_COMM_DIR)/compat/compat_posix_string.c -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) CFLAGS += -Wall -pedantic -std=gnu99 -g -O0 -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 diff --git a/samples/string/word_wrap_overflow_test/Makefile b/samples/string/word_wrap_overflow_test/Makefile index 2d3e7f32..e27b4cdf 100644 --- a/samples/string/word_wrap_overflow_test/Makefile +++ b/samples/string/word_wrap_overflow_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + TARGET := word_wrap_overflow_test LIBRETRO_COMM_DIR := ../../.. @@ -12,7 +14,7 @@ SOURCES := \ $(LIBRETRO_COMM_DIR)/compat/compat_strl.c \ $(LIBRETRO_COMM_DIR)/compat/compat_posix_string.c -OBJS := $(SOURCES:.c=.o) +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) CFLAGS += -Wall -pedantic -std=gnu99 -g -O0 -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 diff --git a/samples/time/retro_sleep_until_test/Makefile b/samples/time/retro_sleep_until_test/Makefile index 63d3b142..ef183e33 100644 --- a/samples/time/retro_sleep_until_test/Makefile +++ b/samples/time/retro_sleep_until_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + CORE_DIR := ../../.. TARGET := retro_sleep_until_test SOURCES_C := retro_sleep_until_test.c \ @@ -6,12 +8,17 @@ SOURCES_C := retro_sleep_until_test.c \ $(CORE_DIR)/string/stdstring.c \ $(CORE_DIR)/compat/compat_strl.c \ $(CORE_DIR)/encodings/encoding_utf.c -OBJS := $(SOURCES_C:.c=.o) +OBJS := $(call sample_objs,$(SOURCES_C:.c=.o)) CFLAGS += -Wall -I$(CORE_DIR)/include -D_GNU_SOURCE -O1 -g LDFLAGS += -lm -lpthread all: $(TARGET) $(TARGET): $(OBJS) $(CC) -o $@ $(OBJS) $(LDFLAGS) clean: + rm -rf $(SAMPLE_OBJDIR) rm -f $(OBJS) $(TARGET) .PHONY: all clean + +$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c + @mkdir -p $(@D) + $(COMPILE.c) $(OUTPUT_OPTION) $< diff --git a/samples/time/rtime_localtime_test/Makefile b/samples/time/rtime_localtime_test/Makefile index bb67781c..3d86f8c5 100644 --- a/samples/time/rtime_localtime_test/Makefile +++ b/samples/time/rtime_localtime_test/Makefile @@ -1,3 +1,5 @@ +include ../../sample_objdir.mk + CORE_DIR := ../../.. TARGET := rtime_localtime_test SOURCES_C := rtime_localtime_test.c \ @@ -7,7 +9,7 @@ SOURCES_C := rtime_localtime_test.c \ $(CORE_DIR)/string/stdstring.c \ $(CORE_DIR)/compat/compat_strl.c \ $(CORE_DIR)/encodings/encoding_utf.c -OBJS := $(SOURCES_C:.c=.o) +OBJS := $(call sample_objs,$(SOURCES_C:.c=.o)) CFLAGS += -Wall -I$(CORE_DIR)/include -DHAVE_THREADS -D_GNU_SOURCE -O1 -g LDFLAGS += -lm -lpthread @@ -21,6 +23,11 @@ $(TARGET): $(OBJS) $(CC) -o $@ $(OBJS) $(LDFLAGS) %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ + +$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c + @mkdir -p $(@D) + $(CC) $(CFLAGS) -c $< -o $@ clean: + rm -rf $(SAMPLE_OBJDIR) rm -f $(OBJS) $(TARGET) .PHONY: all clean diff --git a/streams/file_stream.c b/streams/file_stream.c index 3657558b..826980ef 100644 --- a/streams/file_stream.c +++ b/streams/file_stream.c @@ -1871,6 +1871,16 @@ void filestream_set_mapped_ptr_cb(filestream_mapped_ptr_cb_t cb) filestream_mapped_ptr_cb = cb; } +void filestream_prefetch(RFILE *stream, uint64_t offset, uint64_t len) +{ + if (!stream || !stream->hfile) + return; + if (filestream_read_cb) + return; /* no such request in the libretro VFS interface */ + retro_vfs_file_prefetch_impl( + (libretro_vfs_implementation_file*)stream->hfile, offset, len); +} + const uint8_t *filestream_get_mapped_ptr(RFILE *stream, int64_t *len) { if (len) @@ -2243,18 +2253,23 @@ RFILE *filestream_open_atomic(const char *path) return file; } +bool filestream_atomic_temp_name(char *s, size_t len, const char *path) +{ + size_t path_len; + if (!s || !path || !*path) + return false; + path_len = strlen(path); + if (path_len + sizeof(".tmp") > len) + return false; + memcpy(s, path, path_len); + memcpy(s + path_len, ".tmp", sizeof(".tmp")); + return true; +} + int filestream_commit_atomic(RFILE *file, const char *path, bool ok) { - char *temp_path; - if (!file) return -1; - if (!path || !*path || !(temp_path = filestream_atomic_temp_path(path))) - { - if (filestream_close(file) != 0) - free(file); - return -1; - } /* A buffered write reports a full disk at close, not at write, * so both have to agree before the rename goes ahead. */ @@ -2264,6 +2279,16 @@ int filestream_commit_atomic(RFILE *file, const char *path, bool ok) ok = false; } + return filestream_finish_atomic(path, ok); +} + +int filestream_finish_atomic(const char *path, bool ok) +{ + char *temp_path; + + if (!path || !*path || !(temp_path = filestream_atomic_temp_path(path))) + return -1; + if (!ok) { filestream_delete(temp_path); diff --git a/vfs/vfs_implementation.c b/vfs/vfs_implementation.c index 0ffdfbf7..debad143 100644 --- a/vfs/vfs_implementation.c +++ b/vfs/vfs_implementation.c @@ -2288,6 +2288,102 @@ const char *retro_vfs_file_get_path_impl( return stream->orig_path; } +#if defined(_WIN32) && !defined(_XBOX) && !defined(HAVE_MMAP) +/* PrefetchVirtualMemory is Windows 8 and later; resolved by name so the + * binary still loads where it is absent. */ +typedef struct +{ + PVOID VirtualAddress; + SIZE_T NumberOfBytes; +} vfs_win32_memory_range_t; +typedef BOOL (WINAPI *vfs_prefetch_t)(HANDLE, ULONG_PTR, + vfs_win32_memory_range_t*, ULONG); + +static vfs_prefetch_t vfs_win32_prefetch(void) +{ + static vfs_prefetch_t p_prefetch; + static int resolved; + + if (!resolved) + { + HMODULE k32 = GetModuleHandleA("kernel32.dll"); + + resolved = 1; + if (k32) + p_prefetch = (vfs_prefetch_t)GetProcAddress(k32, + "PrefetchVirtualMemory"); + } + return p_prefetch; +} +#endif + +void retro_vfs_file_prefetch_impl( + libretro_vfs_implementation_file *stream, uint64_t offset, + uint64_t len) +{ + if (!stream || !len) + return; + if (stream->scheme != VFS_SCHEME_NONE) + return; + +#ifdef VFS_HAVE_FILE_MAPPING + if (stream->mapped + && (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)) + { + size_t page; + size_t lo, hi; + + if (offset >= stream->mapsize) + return; + if (len > stream->mapsize - offset) + len = stream->mapsize - offset; +#if defined(HAVE_MMAP) + { + long sz = sysconf(_SC_PAGESIZE); + page = sz > 0 ? (size_t)sz : 0; + } +#else + { + SYSTEM_INFO si; + GetSystemInfo(&si); + page = si.dwPageSize; + } +#endif + if (!page) + return; + lo = (size_t)offset & ~(page - 1); + hi = (size_t)(offset + len); +#if defined(HAVE_MMAP) + /* Older libc feature profiles may expose only the POSIX advice API. + * Prefetch is advisory: leave it a no-op if neither API is exposed. */ +#if defined(MADV_WILLNEED) + madvise(stream->mapped + lo, hi - lo, MADV_WILLNEED); +#elif defined(POSIX_MADV_WILLNEED) + posix_madvise(stream->mapped + lo, hi - lo, POSIX_MADV_WILLNEED); +#endif +#else + { + vfs_prefetch_t prefetch = vfs_win32_prefetch(); + vfs_win32_memory_range_t range; + + if (!prefetch) + return; + range.VirtualAddress = stream->mapped + lo; + range.NumberOfBytes = hi - lo; + prefetch(GetCurrentProcess(), 1, &range, 0); + } +#endif + return; + } +#endif + +#if defined(POSIX_FADV_WILLNEED) && !defined(_WIN32) + if (stream->fd >= 0) + posix_fadvise(stream->fd, (off_t)offset, (off_t)len, + POSIX_FADV_WILLNEED); +#endif +} + const uint8_t *retro_vfs_file_get_mapped_ptr_impl( libretro_vfs_implementation_file *stream, int64_t *len) { diff --git a/vfs/vfs_implementation_cdrom.c b/vfs/vfs_implementation_cdrom.c index 05564c8b..fdd93133 100644 --- a/vfs/vfs_implementation_cdrom.c +++ b/vfs/vfs_implementation_cdrom.c @@ -82,7 +82,7 @@ int64_t retro_vfs_file_seek_cdrom( && (ext[2] == 'n' || ext[2] == 'N') && ext[3] == '\0') { - int lba = (offset / 2352); + int lba = (int)(offset / 2352); unsigned char min = 0; unsigned char sec = 0; unsigned char frame = 0; @@ -99,7 +99,7 @@ int64_t retro_vfs_file_seek_cdrom( seek_type = "SEEK_CUR"; #endif stream->cdrom->byte_pos += offset; - new_lba = vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].lba + (stream->cdrom->byte_pos / 2352); + new_lba = vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].lba + (unsigned)(stream->cdrom->byte_pos / 2352); cdrom_lba_to_msf(new_lba, &min, &sec, &frame); } break; @@ -112,7 +112,7 @@ int64_t retro_vfs_file_seek_cdrom( #ifdef CDROM_DEBUG seek_type = "SEEK_END"; #endif - cdrom_lba_to_msf(lba_len + lba, &min, &sec, &frame); + cdrom_lba_to_msf((unsigned)(lba_len + lba), &min, &sec, &frame); stream->cdrom->byte_pos = lba_len * 2352; } break; @@ -123,7 +123,7 @@ int64_t retro_vfs_file_seek_cdrom( seek_type = "SEEK_SET"; #endif stream->cdrom->byte_pos = offset; - cdrom_lba_to_msf(vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].lba + (stream->cdrom->byte_pos / 2352), &min, &sec, &frame); + cdrom_lba_to_msf(vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].lba + (unsigned)(stream->cdrom->byte_pos / 2352), &min, &sec, &frame); } break; } @@ -604,7 +604,7 @@ int64_t retro_vfs_file_read_cdrom(libretro_vfs_implementation_file *stream, stream->cdrom->byte_pos += len; stream->cdrom->cur_lba = vfs_cdrom_toc.track[stream->cdrom->cur_track - 1].lba - + (stream->cdrom->byte_pos / 2352); + + (unsigned)(stream->cdrom->byte_pos / 2352); cdrom_lba_to_msf(stream->cdrom->cur_lba, &stream->cdrom->cur_min, diff --git a/vfs/vfs_implementation_nfs.c b/vfs/vfs_implementation_nfs.c index c4574c3f..c4e4a86c 100644 --- a/vfs/vfs_implementation_nfs.c +++ b/vfs/vfs_implementation_nfs.c @@ -98,10 +98,8 @@ static bool nfs_url_split(const char *url, char *server, size_t server_len, return true; } -/* Resolves a URL to the export to mount and the path within it. With - * an export in the settings, the URL path is relative to it (under the - * subdir, when set); otherwise the URL's first component is the export - * itself, as nfs://server/export/... */ +/* With a configured export, the URL path is relative to it. + * Otherwise mount prefixes of nfs://server/export/path. */ static bool nfs_resolve(const char *url, char *server, size_t server_len, char *export_path, size_t export_len, char *path, size_t path_len) { @@ -112,13 +110,7 @@ static bool nfs_resolve(const char *url, char *server, size_t server_len, if (cfg && cfg->export_path && *cfg->export_path) { strlcpy(export_path, cfg->export_path, export_len); - path[0] = '\0'; - if (cfg->subdir && *cfg->subdir) - { - strlcpy(path, cfg->subdir, path_len); - strlcat(path, "/", path_len); - } - strlcat(path, rest, path_len); + strlcpy(path, rest, path_len); } else { diff --git a/vfs/vfs_implementation_nfs.h b/vfs/vfs_implementation_nfs.h index d3730cca..4755b448 100644 --- a/vfs/vfs_implementation_nfs.h +++ b/vfs/vfs_implementation_nfs.h @@ -32,8 +32,7 @@ RETRO_BEGIN_DECLS /* nfs:// backend over the built-in NFSv3 client (net/net_nfs3.c). * URL shape: nfs://server/export/path, or nfs://server/path with the - * export from the settings; the subdir, when set, sits between the - * export and the path. */ + * export from the settings, the path relative to it. */ #define RETRO_NFS_DEFAULT_TIMEOUT 5 #define RETRO_NFS_DEFAULT_NUM_CONTEXTS 4 @@ -45,7 +44,6 @@ struct nfs_settings { const char *server_address; const char *export_path; - const char *subdir; unsigned timeout; /* seconds */ unsigned num_contexts; /* connection pool size */ unsigned nfs_port; /* 0: ask the portmapper */ diff --git a/vfs/vfs_implementation_uwp.cpp b/vfs/vfs_implementation_uwp.cpp index d7ba61ad..2b1eb2c5 100644 --- a/vfs/vfs_implementation_uwp.cpp +++ b/vfs/vfs_implementation_uwp.cpp @@ -753,6 +753,22 @@ const uint8_t *retro_vfs_file_get_mapped_ptr_impl( return NULL; } +void retro_vfs_file_prefetch_impl( + libretro_vfs_implementation_file *stream, uint64_t offset, + uint64_t len) +{ + /* A hint, and one this backend has nothing to give: it never maps a + * file, so there is no mapping to make resident, and its handles + * come from the app's brokered file calls, which offer no read-ahead + * request. Doing nothing is the documented answer where the OS has + * none. Defined all the same, for the reason above: + * filestream_prefetch() calls it whenever no frontend VFS is + * installed. */ + (void)stream; + (void)offset; + (void)len; +} + int retro_vfs_stat_64_impl(const char *path, int64_t *size) { wchar_t *path_wide;

    ^Um#|kyC2d;(0SUmEw7{H!PtqnBh615vF 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 literal 0 HcmV?d00001 diff --git a/samples/formats/rrar/archives/rar5_arm.rar b/samples/formats/rrar/archives/rar5_arm.rar new file mode 100644 index 0000000000000000000000000000000000000000..100c96659185917ccbf48354ce977487162a2ab9 GIT binary patch literal 41082 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