mirror of
https://github.com/libretro/opera-libretro.git
synced 2026-11-07 04:25:58 -10:00
Lock-free DSP thread on libretro-common; trim libretro-common
The threaded DSP takes no mutex or condition variable. The emulation thread publishes a monotonic tick count with a release store and wakes the DSP thread through a retro_eventcount once per 32 ticks and at frame end; the DSP thread runs every posted tick and writes samples into a retro_spsc ring that upload hands to the frontend in place. Mode switches keep samples in order, and the thread runs out every posted tick before it exits. THREADED_DSP defines HAVE_THREADS and builds the eventcount and SPSC sources. libretro-common is synced with RetroArch master and holds only the sources the core builds and the headers they and the core include; rstrtod.c is built since file_stream needs it.
This commit is contained in:
1 parent
787c1dce03
commit
85e45d084f
580 files changed
+15189
-236187
No files matched your search
+5
-2
@@ -74,6 +74,7 @@ SOURCES_C += \
|
||||
$(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \
|
||||
$(LIBRETRO_COMM_DIR)/memmap/memmap.c \
|
||||
$(LIBRETRO_COMM_DIR)/string/stdstring.c \
|
||||
$(LIBRETRO_COMM_DIR)/string/rstrtod.c \
|
||||
$(LIBRETRO_COMM_DIR)/file/file_path.c \
|
||||
$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
|
||||
$(LIBRETRO_COMM_DIR)/file/retro_dirent.c \
|
||||
@@ -106,10 +107,12 @@ FLAGS += -DINLINE="_inline"
|
||||
endif
|
||||
|
||||
ifeq ($(THREADED_DSP), 1)
|
||||
FLAGS += -DTHREADED_DSP
|
||||
FLAGS += -DTHREADED_DSP -DHAVE_THREADS
|
||||
ifneq ($(STATIC_LINKING), 1)
|
||||
SOURCES_C += \
|
||||
$(LIBRETRO_COMM_DIR)/rthreads/rthreads.c
|
||||
$(LIBRETRO_COMM_DIR)/rthreads/rthreads.c \
|
||||
$(LIBRETRO_COMM_DIR)/rthreads/retro_eventcount.c \
|
||||
$(LIBRETRO_COMM_DIR)/queues/retro_spsc.c
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
|
||||
OBJDIR = ../obj-unix
|
||||
|
||||
# libcheck's link line is more than -lcheck on most distributions --
|
||||
# Debian and Ubuntu need -lcheck_pic -lsubunit -lrt -lm -pthread -- so
|
||||
# ask pkg-config and fall back to the bare flag only if it cannot
|
||||
# answer. Overridable for platforms without pkg-config.
|
||||
LIBCHECK_CFLAGS ?= $(shell pkg-config --cflags check 2>/dev/null)
|
||||
LIBCHECK_LIBS ?= $(shell pkg-config --libs check 2>/dev/null || echo -lcheck)
|
||||
|
||||
TEST_UNIT_CFLAGS = $(CFLAGS) -Iinclude $(LDFLAGS) $(LIBCHECK_CFLAGS) -Werror -Wdeclaration-after-statement -fsanitize=address -fsanitize=undefined -ftest-coverage -fprofile-arcs -ggdb
|
||||
|
||||
TEST_GENERIC_QUEUE = test/queues/test_generic_queue
|
||||
TEST_GENERIC_QUEUE_SRC = test/queues/test_generic_queue.c queues/generic_queue.c
|
||||
|
||||
TEST_LINKED_LIST = test/lists/test_linked_list
|
||||
TEST_LINKED_LIST_SRC = test/lists/test_linked_list.c lists/linked_list.c
|
||||
|
||||
TEST_FILE_LIST = test/lists/test_file_list
|
||||
TEST_FILE_LIST_SRC = test/lists/test_file_list.c lists/file_list.c \
|
||||
compat/compat_strcasestr.c
|
||||
|
||||
TEST_STDSTRING = test/string/test_stdstring
|
||||
TEST_STDSTRING_SRC = test/string/test_stdstring.c string/stdstring.c encodings/encoding_utf.c \
|
||||
compat/compat_strl.c compat/compat_strldup.c
|
||||
|
||||
TEST_UTILS = test/utils/test_utils
|
||||
TEST_UTILS_SRC = test/utils/test_utils.c utils/md5.c encodings/encoding_crc32.c \
|
||||
features/features_cpu.c \
|
||||
streams/file_stream.c vfs/vfs_implementation.c file/file_path.c \
|
||||
file/file_path_io.c \
|
||||
compat/compat_strl.c time/rtime.c string/stdstring.c encodings/encoding_utf.c
|
||||
|
||||
TEST_HASH = test/hash/test_hash
|
||||
TEST_HASH_SRC = test/hash/test_hash.c hash/lrc_hash.c \
|
||||
streams/file_stream.c vfs/vfs_implementation.c file/file_path.c \
|
||||
file/file_path_io.c \
|
||||
compat/compat_strl.c time/rtime.c string/stdstring.c encodings/encoding_utf.c
|
||||
|
||||
TEST_RWAV = test/formats/test_rwav
|
||||
TEST_RWAV_SRC = test/formats/test_rwav.c formats/wav/rwav.c
|
||||
|
||||
TEST_RJPEG = test/formats/test_rjpeg
|
||||
TEST_RJPEG_SRC = test/formats/test_rjpeg.c formats/jpeg/rjpeg.c \
|
||||
streams/file_stream.c vfs/vfs_implementation.c file/file_path.c \
|
||||
file/file_path_io.c features/features_cpu.c \
|
||||
compat/compat_strl.c time/rtime.c string/stdstring.c encodings/encoding_utf.c
|
||||
|
||||
# Not a libcheck suite: a plain main() that prints its own verdict and
|
||||
# returns non-zero on failure.
|
||||
TEST_RWEBM_AUDIO = test/formats/test_rwebm_audio
|
||||
TEST_RWEBM_AUDIO_SRC = test/formats/test_rwebm_audio.c \
|
||||
encodings/encoding_crc32.c features/features_cpu.c
|
||||
|
||||
TEST_RPNG = test/formats/test_rpng
|
||||
TEST_RPNG_SRC = test/formats/test_rpng.c formats/png/rpng.c \
|
||||
streams/trans_stream.c streams/trans_stream_deflate.c \
|
||||
encodings/encoding_deflate.c \
|
||||
features/features_cpu.c \
|
||||
streams/trans_stream_pipe.c
|
||||
TEST_RPNG_LIBS = -lz
|
||||
|
||||
|
||||
# --- Windows A/W selection matrix -------------------------------------
|
||||
#
|
||||
# libretro-common picks between the ANSI and the wide Win32 entry points
|
||||
# three different ways (compile-time wide, compile-time ANSI via
|
||||
# LEGACY_WIN32, and per-call via HAVE_WIN9X_RUNTIME), and no
|
||||
# Linux-hosted target builds two of them. That is how the ANSI branch
|
||||
# rots: nothing compiles it, so nothing notices when it stops
|
||||
# compiling, and the report comes from someone on a Windows 98 box.
|
||||
#
|
||||
# Cross-compiling covers "still builds". wine covers rather more than
|
||||
# that: an ANSI build under wine really does call CreateFileA,
|
||||
# FindFirstFileA and _stat64 rather than their wide twins, so the
|
||||
# branch is executed, not merely parsed. What wine cannot do is make
|
||||
# the probe say ANSI - it implements the W entry points, so a
|
||||
# HAVE_WIN9X_RUNTIME build correctly chooses wide there. Only real 9x
|
||||
# exercises win32_needs_local_encoding() returning 1.
|
||||
#
|
||||
# Both tools are optional. Absent either one, the affected steps say
|
||||
# so and pass; a contributor without a cross-compiler is not blocked.
|
||||
WIN32_CC ?= x86_64-w64-mingw32-gcc
|
||||
WIN32_CC_X86 ?= i686-w64-mingw32-gcc
|
||||
WINE ?= wine
|
||||
|
||||
# Every file that selects between A and W.
|
||||
WIN32_AW_SRC = compat/fopen_utf8.c dynamic/dylib.c file/file_path.c \
|
||||
file/nbio/nbio_stdio.c file/nbio/nbio_windowsmmap.c \
|
||||
vfs/vfs_implementation.c
|
||||
|
||||
# What a runnable test needs linked in besides the file under test.
|
||||
WIN32_AW_TEST = samples/file/vfs/vfs_seek_contract_test.c
|
||||
WIN32_AW_TEST_SRC = compat/fopen_utf8.c compat/compat_strl.c \
|
||||
encodings/encoding_utf.c file/file_path.c \
|
||||
file/file_path_io.c streams/file_stream.c \
|
||||
time/rtime.c vfs/vfs_implementation.c
|
||||
|
||||
win32-configs:
|
||||
@fails=0; \
|
||||
for cc in $(WIN32_CC) $(WIN32_CC_X86); do \
|
||||
if ! command -v $$cc >/dev/null 2>&1; then \
|
||||
echo "skip: $$cc not installed"; continue; \
|
||||
fi; \
|
||||
for cfg in "" "-DLEGACY_WIN32" "-DHAVE_WIN9X_RUNTIME"; do \
|
||||
for src in $(WIN32_AW_SRC); do \
|
||||
if ! $$cc -c -Iinclude -Wall -Werror $$cfg \
|
||||
-DLEGACY_WIN32_NO_MIGRATION_NOTE \
|
||||
-o /dev/null $$src 2>/tmp/lrc_aw_err.$$$$; then \
|
||||
echo "FAIL: $$cc [$${cfg:-default}] $$src"; \
|
||||
cat /tmp/lrc_aw_err.$$$$; fails=`expr $$fails + 1`; \
|
||||
fi; \
|
||||
rm -f /tmp/lrc_aw_err.$$$$; \
|
||||
done; \
|
||||
echo "ok: $$cc [$${cfg:-default}] builds"; \
|
||||
done; \
|
||||
done; \
|
||||
wine_ok=0; \
|
||||
if command -v $(WIN32_CC) >/dev/null 2>&1 \
|
||||
&& command -v $(WINE) >/dev/null 2>&1; then \
|
||||
printf 'int main(void){return 0;}\n' > /tmp/lrc_aw_probe.c; \
|
||||
if $(WIN32_CC) -o /tmp/lrc_aw_probe.exe /tmp/lrc_aw_probe.c 2>/dev/null \
|
||||
&& (cd /tmp && WINEDEBUG=-all $(WINE) /tmp/lrc_aw_probe.exe \
|
||||
>/dev/null 2>/tmp/lrc_aw_probe.log); then \
|
||||
wine_ok=1; \
|
||||
else \
|
||||
echo "skip: wine is installed but cannot run a Windows binary here"; \
|
||||
echo " (configurations were built; none were executed)"; \
|
||||
sed 's/^/ /' /tmp/lrc_aw_probe.log 2>/dev/null | head -5; \
|
||||
fi; \
|
||||
rm -f /tmp/lrc_aw_probe.c /tmp/lrc_aw_probe.exe /tmp/lrc_aw_probe.log; \
|
||||
fi; \
|
||||
if [ $$wine_ok -eq 1 ]; then \
|
||||
for cfg in "" "-DLEGACY_WIN32" "-DHAVE_WIN9X_RUNTIME"; do \
|
||||
if ! $(WIN32_CC) -O2 -Iinclude $$cfg \
|
||||
-DLEGACY_WIN32_NO_MIGRATION_NOTE \
|
||||
-o /tmp/lrc_aw_test.exe \
|
||||
$(WIN32_AW_TEST) $(WIN32_AW_TEST_SRC) 2>/dev/null; then \
|
||||
echo "FAIL: [$${cfg:-default}] test would not build"; \
|
||||
fails=`expr $$fails + 1`; continue; \
|
||||
fi; \
|
||||
if (cd /tmp && WINEDEBUG=-all $(WINE) /tmp/lrc_aw_test.exe \
|
||||
>/tmp/lrc_aw_out.txt 2>/tmp/lrc_aw_err.txt); then \
|
||||
echo "ok: [$${cfg:-default}] runs under wine"; \
|
||||
else \
|
||||
echo "FAIL: [$${cfg:-default}] failed under wine"; \
|
||||
cat /tmp/lrc_aw_out.txt; \
|
||||
sed 's/^/ /' /tmp/lrc_aw_err.txt | head -10; \
|
||||
fails=`expr $$fails + 1`; \
|
||||
fi; \
|
||||
rm -f /tmp/lrc_aw_test.exe /tmp/lrc_aw_out.txt /tmp/lrc_aw_err.txt; \
|
||||
done; \
|
||||
elif ! command -v $(WINE) >/dev/null 2>&1; then \
|
||||
echo "skip: wine not installed - configurations built, not run"; \
|
||||
fi; \
|
||||
if [ $$fails -ne 0 ]; then echo "$$fails Windows configuration check(s) failed"; exit 1; fi; \
|
||||
echo "Windows A/W configuration matrix: all checks passed"
|
||||
|
||||
# --- VFS sample regression tests --------------------------------------
|
||||
#
|
||||
# Native runs of the samples that pin file-size, seek and EOF behaviour,
|
||||
# in both mapping configurations because the mapped and unmapped paths
|
||||
# implement those boundaries separately.
|
||||
vfs-samples:
|
||||
$(MAKE) -C samples/file/vfs clean
|
||||
$(MAKE) -C samples/file/vfs SANITIZER=address,undefined
|
||||
cd samples/file/vfs && ./vfs_seek_contract_test && ./vfs_large_file_test
|
||||
# filestream_rbuf_fault_test drives filestream_rbuf_fill()'s two
|
||||
# failure arms through its own VFS, which leaves it lane-independent
|
||||
# - running it a second time under MMAP=0 would be the same run.
|
||||
cd samples/file/vfs && ./filestream_rbuf_fault_test
|
||||
$(MAKE) -C samples/file/vfs clean
|
||||
$(MAKE) -C samples/file/vfs MMAP=0 SANITIZER=address,undefined
|
||||
cd samples/file/vfs && ./vfs_seek_contract_test
|
||||
$(MAKE) -C samples/file/vfs clean
|
||||
|
||||
.PHONY: win32-configs vfs-samples
|
||||
|
||||
all:
|
||||
# Build and execute tests in order, to avoid coverage file collision
|
||||
# string
|
||||
$(CC) $(TEST_UNIT_CFLAGS) $(TEST_STDSTRING_SRC) $(LIBCHECK_LIBS) -o $(TEST_STDSTRING)
|
||||
$(TEST_STDSTRING)
|
||||
lcov -c -d . -o `dirname $(TEST_STDSTRING)`/coverage.info
|
||||
# utils
|
||||
$(CC) $(TEST_UNIT_CFLAGS) $(TEST_UTILS_SRC) $(LIBCHECK_LIBS) -o $(TEST_UTILS)
|
||||
$(TEST_UTILS)
|
||||
lcov -c -d . -o `dirname $(TEST_UTILS)`/coverage.info
|
||||
# utils
|
||||
$(CC) $(TEST_UNIT_CFLAGS) $(TEST_HASH_SRC) $(LIBCHECK_LIBS) -o $(TEST_HASH)
|
||||
$(TEST_HASH)
|
||||
lcov -c -d . -o `dirname $(TEST_HASH)`/coverage.info
|
||||
# list
|
||||
$(CC) $(TEST_UNIT_CFLAGS) $(TEST_LINKED_LIST_SRC) $(LIBCHECK_LIBS) -o $(TEST_LINKED_LIST)
|
||||
$(TEST_LINKED_LIST)
|
||||
lcov -c -d . -o `dirname $(TEST_LINKED_LIST)`/coverage.info
|
||||
# file list
|
||||
$(CC) $(TEST_UNIT_CFLAGS) $(TEST_FILE_LIST_SRC) $(LIBCHECK_LIBS) -o $(TEST_FILE_LIST)
|
||||
$(TEST_FILE_LIST)
|
||||
lcov -c -d . -o `dirname $(TEST_FILE_LIST)`/file_list_coverage.info
|
||||
# queue
|
||||
$(CC) $(TEST_UNIT_CFLAGS) $(TEST_GENERIC_QUEUE_SRC) $(LIBCHECK_LIBS) -o $(TEST_GENERIC_QUEUE)
|
||||
$(TEST_GENERIC_QUEUE)
|
||||
lcov -c -d . -o `dirname $(TEST_GENERIC_QUEUE)`/coverage.info
|
||||
# rpng
|
||||
$(CC) $(TEST_UNIT_CFLAGS) $(TEST_RPNG_SRC) $(TEST_RPNG_LIBS) $(LIBCHECK_LIBS) -o $(TEST_RPNG)
|
||||
$(TEST_RPNG)
|
||||
lcov -c -d . -o `dirname $(TEST_RPNG)`/coverage.info
|
||||
# rwav
|
||||
$(CC) $(TEST_UNIT_CFLAGS) $(TEST_RWAV_SRC) $(LIBCHECK_LIBS) -o $(TEST_RWAV)
|
||||
$(TEST_RWAV)
|
||||
# rjpeg
|
||||
$(CC) $(TEST_UNIT_CFLAGS) $(TEST_RJPEG_SRC) $(LIBCHECK_LIBS) -o $(TEST_RJPEG)
|
||||
$(TEST_RJPEG)
|
||||
# rwebm audio
|
||||
$(CC) $(TEST_UNIT_CFLAGS) $(TEST_RWEBM_AUDIO_SRC) $(LIBCHECK_LIBS) -o $(TEST_RWEBM_AUDIO)
|
||||
$(TEST_RWEBM_AUDIO)
|
||||
# vfs samples: file size, seek and EOF boundaries, mapped and not
|
||||
$(MAKE) -f Makefile.test vfs-samples
|
||||
# the three Windows A/W configurations, two of which nothing else builds
|
||||
$(MAKE) -f Makefile.test win32-configs
|
||||
|
||||
lcov -o test/coverage.info \
|
||||
-a test/utils/coverage.info \
|
||||
-a test/string/coverage.info \
|
||||
-a test/lists/coverage.info \
|
||||
-a test/lists/file_list_coverage.info \
|
||||
-a test/queues/coverage.info \
|
||||
-a test/formats/coverage.info
|
||||
genhtml -o test/coverage/ test/coverage.info
|
||||
|
||||
clean:
|
||||
rm -f *.gcda *.gcno
|
||||
rm -f $(TEST_STDSTRING) $(TEST_UTILS) $(TEST_HASH) \
|
||||
$(TEST_LINKED_LIST) $(TEST_FILE_LIST) $(TEST_GENERIC_QUEUE) \
|
||||
$(TEST_RPNG) $(TEST_RWAV) $(TEST_RJPEG) $(TEST_RWEBM_AUDIO)
|
||||
rm -f test/*/*.gcda test/*/*.gcno test/*/coverage.info \
|
||||
test/lists/file_list_coverage.info test/coverage.info
|
||||
rm -rf test/coverage
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (audio_mix.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <retro_environment.h>
|
||||
|
||||
#if defined(__SSE2__)
|
||||
#include <emmintrin.h>
|
||||
#elif defined(__ALTIVEC__)
|
||||
#include <altivec.h>
|
||||
#endif
|
||||
|
||||
#include <audio/audio_mix.h>
|
||||
|
||||
void audio_mix_volume_C(float *s, const float *in, float vol, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < len; i++)
|
||||
s[i] += in[i] * vol;
|
||||
}
|
||||
|
||||
#ifdef __SSE2__
|
||||
void audio_mix_volume_SSE2(float *s, const float *in, float vol, size_t len)
|
||||
{
|
||||
size_t i, remaining_samples;
|
||||
__m128 volume = _mm_set1_ps(vol);
|
||||
|
||||
for (i = 0; i + 16 <= len; i += 16, s += 16, in += 16)
|
||||
{
|
||||
unsigned j;
|
||||
__m128 input[4];
|
||||
__m128 additive[4];
|
||||
|
||||
input[0] = _mm_loadu_ps(s + 0);
|
||||
input[1] = _mm_loadu_ps(s + 4);
|
||||
input[2] = _mm_loadu_ps(s + 8);
|
||||
input[3] = _mm_loadu_ps(s + 12);
|
||||
|
||||
additive[0] = _mm_mul_ps(volume, _mm_loadu_ps(in + 0));
|
||||
additive[1] = _mm_mul_ps(volume, _mm_loadu_ps(in + 4));
|
||||
additive[2] = _mm_mul_ps(volume, _mm_loadu_ps(in + 8));
|
||||
additive[3] = _mm_mul_ps(volume, _mm_loadu_ps(in + 12));
|
||||
|
||||
for (j = 0; j < 4; j++)
|
||||
_mm_storeu_ps(s + 4 * j, _mm_add_ps(input[j], additive[j]));
|
||||
}
|
||||
|
||||
remaining_samples = len - i;
|
||||
|
||||
for (i = 0; i < remaining_samples; i++)
|
||||
s[i] += in[i] * vol;
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,275 +0,0 @@
|
||||
/* Copyright (C) 2010-2021 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (float_to_s16.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(__SSE2__)
|
||||
#include <emmintrin.h>
|
||||
#elif defined(__ALTIVEC__)
|
||||
#include <altivec.h>
|
||||
#endif
|
||||
|
||||
#include <features/features_cpu.h>
|
||||
#include <audio/conversion/float_to_s16.h>
|
||||
|
||||
#if (defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(HAVE_NEON))
|
||||
static bool float_to_s16_neon_enabled = false;
|
||||
#ifdef HAVE_ARM_NEON_ASM_OPTIMIZATIONS
|
||||
void convert_float_s16_asm(int16_t *s, const float *in, size_t len);
|
||||
#else
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
void convert_float_to_s16(int16_t *s, const float *in, size_t len)
|
||||
{
|
||||
size_t i = 0;
|
||||
if (float_to_s16_neon_enabled)
|
||||
{
|
||||
#ifndef HAVE_ARM_NEON_ASM_OPTIMIZATIONS
|
||||
/* arm_neon.h is only included when the intrinsic path is built,
|
||||
* so these NEON-typed locals must be scoped to it as well - the
|
||||
* asm path calls out to float_to_s16_neon.S and needs none of
|
||||
* them. s16_to_float.c already keeps its equivalents inside the
|
||||
* #else for the same reason. */
|
||||
float gf = (1<<15);
|
||||
float32x4_t vgf = {gf, gf, gf, gf};
|
||||
float32x4_t vhalf = vdupq_n_f32(0.5f);
|
||||
uint32x4_t vsign = vdupq_n_u32(0x80000000u);
|
||||
#endif
|
||||
while (len >= 8)
|
||||
{
|
||||
#ifdef HAVE_ARM_NEON_ASM_OPTIMIZATIONS
|
||||
size_t aligned_samples = len & ~7;
|
||||
if (aligned_samples)
|
||||
convert_float_s16_asm(s, in, aligned_samples);
|
||||
|
||||
s += aligned_samples;
|
||||
in += aligned_samples;
|
||||
len -= aligned_samples;
|
||||
i = 0;
|
||||
#else
|
||||
int16x4x2_t oreg;
|
||||
int32x4x2_t creg;
|
||||
float32x4x2_t inreg = vld2q_f32(in);
|
||||
float32x4_t sc0 = vmulq_f32(inreg.val[0], vgf);
|
||||
float32x4_t sc1 = vmulq_f32(inreg.val[1], vgf);
|
||||
float32x4_t b0 = vreinterpretq_f32_u32(vorrq_u32(
|
||||
vandq_u32(vreinterpretq_u32_f32(sc0), vsign),
|
||||
vreinterpretq_u32_f32(vhalf)));
|
||||
float32x4_t b1 = vreinterpretq_f32_u32(vorrq_u32(
|
||||
vandq_u32(vreinterpretq_u32_f32(sc1), vsign),
|
||||
vreinterpretq_u32_f32(vhalf)));
|
||||
creg.val[0] = vcvtq_s32_f32(vaddq_f32(sc0, b0));
|
||||
creg.val[1] = vcvtq_s32_f32(vaddq_f32(sc1, b1));
|
||||
oreg.val[0] = vqmovn_s32(creg.val[0]);
|
||||
oreg.val[1] = vqmovn_s32(creg.val[1]);
|
||||
vst2_s16(s, oreg);
|
||||
in += 8;
|
||||
s += 8;
|
||||
len -= 8;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < len; i++)
|
||||
{
|
||||
float scaled = in[i] * 0x8000;
|
||||
uint32_t bits;
|
||||
/* Casting a non-finite or out-of-int32-range float is undefined,
|
||||
* and on x86 it yields the "integer indefinite" INT32_MIN, which
|
||||
* then saturates to -32768 - so a NaN or a large positive sample
|
||||
* came out as full-scale negative. The NEON and AltiVec paths
|
||||
* already give NaN -> 0 with correct saturation; match them. The
|
||||
* NaN test is on the bit pattern rather than (scaled != scaled)
|
||||
* because -ffast-math is entitled to fold the latter to false. */
|
||||
memcpy(&bits, &scaled, sizeof(bits));
|
||||
if ((bits & 0x7FFFFFFFu) > 0x7F800000u)
|
||||
s[i] = 0;
|
||||
else
|
||||
{
|
||||
scaled += (scaled >= 0.0f ? 0.5f : -0.5f);
|
||||
if (scaled > 32767.0f)
|
||||
s[i] = 0x7FFF;
|
||||
else if (scaled < -32768.0f)
|
||||
s[i] = -0x8000;
|
||||
else
|
||||
s[i] = (int16_t)(int32_t)scaled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void convert_float_to_s16_init_simd(void)
|
||||
{
|
||||
uint64_t cpu = cpu_features_get();
|
||||
|
||||
if (cpu & RETRO_SIMD_NEON)
|
||||
float_to_s16_neon_enabled = true;
|
||||
}
|
||||
#else
|
||||
void convert_float_to_s16(int16_t *s, const float *in, size_t len)
|
||||
{
|
||||
size_t i = 0;
|
||||
#if defined(__SSE2__)
|
||||
__m128 factor = _mm_set1_ps((float)0x8000);
|
||||
/* Initialize a 4D vector with 32768.0 for its elements */
|
||||
__m128 half = _mm_set1_ps(0.5f);
|
||||
__m128 signmask = _mm_castsi128_ps(_mm_set1_epi32((int)0x80000000));
|
||||
__m128 vmax = _mm_set1_ps( 32767.0f);
|
||||
__m128 vmin = _mm_set1_ps(-32768.0f);
|
||||
/* Round half away from zero: add copysign(0.5, x) then truncate.
|
||||
* This matches the scalar fallback and every other SIMD variant
|
||||
* bit-for-bit, so the same float buffer yields identical s16 output
|
||||
* on all targets (netplay / rewind / regression fixtures).
|
||||
*
|
||||
* NaN is squashed to +0.0 and the biased value clamped into the s16
|
||||
* range before the convert: _mm_cvttps_epi32 returns INT32_MIN for
|
||||
* NaN and for anything outside int32, which _mm_packs_epi32 then
|
||||
* saturates to -32768 - so a NaN or a large positive sample became
|
||||
* full-scale negative. NEON and AltiVec already give NaN -> 0 with
|
||||
* correct saturation. _mm_cmpord_ps survives -ffast-math (it lowers
|
||||
* straight to cmpordps); a plain float compare would not. */
|
||||
|
||||
for (i = 0; i + 8 <= len; i += 8, in += 8, s += 8)
|
||||
{ /* Skip forward 8 samples at a time... */
|
||||
__m128 res_a = _mm_mul_ps(_mm_loadu_ps(in + 0), factor); /* next four samples * 32768 */
|
||||
__m128 res_b = _mm_mul_ps(_mm_loadu_ps(in + 4), factor); /* the *next* next four */
|
||||
__m128 bias_a, bias_b;
|
||||
__m128i ints_a, ints_b, packed;
|
||||
res_a = _mm_and_ps(res_a, _mm_cmpord_ps(res_a, res_a));
|
||||
res_b = _mm_and_ps(res_b, _mm_cmpord_ps(res_b, res_b));
|
||||
bias_a = _mm_or_ps(_mm_and_ps(res_a, signmask), half); /* copysign(0.5, res) */
|
||||
bias_b = _mm_or_ps(_mm_and_ps(res_b, signmask), half);
|
||||
res_a = _mm_max_ps(_mm_min_ps(_mm_add_ps(res_a, bias_a), vmax), vmin);
|
||||
res_b = _mm_max_ps(_mm_min_ps(_mm_add_ps(res_b, bias_b), vmax), vmin);
|
||||
ints_a = _mm_cvttps_epi32(res_a); /* rounded, truncating cvt */
|
||||
ints_b = _mm_cvttps_epi32(res_b);
|
||||
packed = _mm_packs_epi32(ints_a, ints_b); /* Then to 16-bit, clamping to [-32768, 32767] */
|
||||
|
||||
_mm_storeu_si128((__m128i *)s, packed); /* Then put the result in the output array */
|
||||
}
|
||||
|
||||
len = len - i;
|
||||
i = 0;
|
||||
/* If there are any stray samples at the end, we need to convert them
|
||||
* (maybe the original array didn't contain a multiple of 8 samples) */
|
||||
#elif defined(__ALTIVEC__)
|
||||
int samples_in = len;
|
||||
|
||||
/* Unaligned loads/store is a bit expensive,
|
||||
* so we optimize for the good path (very likely). */
|
||||
if (((uintptr_t)s & 15) + ((uintptr_t)in & 15) == 0)
|
||||
{
|
||||
size_t i;
|
||||
const vector float vscale =
|
||||
(vector float){32768.0f, 32768.0f, 32768.0f, 32768.0f};
|
||||
const vector float vzero =
|
||||
(vector float){0.0f, 0.0f, 0.0f, 0.0f};
|
||||
const vector float vhalf =
|
||||
(vector float){0.5f, 0.5f, 0.5f, 0.5f};
|
||||
const vector unsigned int vsign =
|
||||
(vector unsigned int){0x80000000u, 0x80000000u,
|
||||
0x80000000u, 0x80000000u};
|
||||
/* Round half away from zero, matching the scalar/SSE2/NEON paths. */
|
||||
for (i = 0; i + 8 <= len; i += 8, in += 8, s += 8)
|
||||
{
|
||||
vector float input0 = vec_ld( 0, in);
|
||||
vector float input1 = vec_ld(16, in);
|
||||
vector float sc0 = vec_madd(input0, vscale, vzero);
|
||||
vector float sc1 = vec_madd(input1, vscale, vzero);
|
||||
vector float b0 = (vector float)vec_or(
|
||||
vec_and((vector unsigned int)sc0, vsign),
|
||||
(vector unsigned int)vhalf);
|
||||
vector float b1 = (vector float)vec_or(
|
||||
vec_and((vector unsigned int)sc1, vsign),
|
||||
(vector unsigned int)vhalf);
|
||||
vector signed int result0 = vec_cts(vec_add(sc0, b0), 0);
|
||||
vector signed int result1 = vec_cts(vec_add(sc1, b1), 0);
|
||||
vec_st(vec_packs(result0, result1), 0, s);
|
||||
}
|
||||
|
||||
samples_in -= i;
|
||||
}
|
||||
|
||||
len = samples_in;
|
||||
i = 0;
|
||||
#elif defined(_MIPS_ARCH_ALLEGREX)
|
||||
#ifdef DEBUG
|
||||
/* Make sure the buffers are 16 byte aligned, this should be
|
||||
* the default behaviour of malloc in the PSPSDK.
|
||||
* Assume alignment. */
|
||||
retro_assert(((uintptr_t)in & 0xf) == 0);
|
||||
retro_assert(((uintptr_t)s & 0xf) == 0);
|
||||
#endif
|
||||
|
||||
for (i = 0; i + 8 <= len; i += 8)
|
||||
{
|
||||
__asm__ (
|
||||
".set push \n"
|
||||
".set noreorder \n"
|
||||
|
||||
"lv.q c100, 0(%0) \n"
|
||||
"lv.q c110, 16(%0) \n"
|
||||
|
||||
"vf2in.q c100, c100, 31 \n"
|
||||
"vf2in.q c110, c110, 31 \n"
|
||||
"vi2s.q c100, c100 \n"
|
||||
"vi2s.q c102, c110 \n"
|
||||
|
||||
"sv.q c100, 0(%1) \n"
|
||||
|
||||
".set pop \n"
|
||||
:: "r"(in + i), "r"(s + i));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* This loop converts stray samples to the right format,
|
||||
* but it's also a fallback in case no SIMD instructions are available. */
|
||||
for (; i < len; i++)
|
||||
{
|
||||
float scaled = in[i] * 0x8000;
|
||||
uint32_t bits;
|
||||
/* Casting a non-finite or out-of-int32-range float is undefined,
|
||||
* and on x86 it yields the "integer indefinite" INT32_MIN, which
|
||||
* then saturates to -32768 - so a NaN or a large positive sample
|
||||
* came out as full-scale negative. The NEON and AltiVec paths
|
||||
* already give NaN -> 0 with correct saturation; match them. The
|
||||
* NaN test is on the bit pattern rather than (scaled != scaled)
|
||||
* because -ffast-math is entitled to fold the latter to false. */
|
||||
memcpy(&bits, &scaled, sizeof(bits));
|
||||
if ((bits & 0x7FFFFFFFu) > 0x7F800000u)
|
||||
s[i] = 0;
|
||||
else
|
||||
{
|
||||
scaled += (scaled >= 0.0f ? 0.5f : -0.5f);
|
||||
if (scaled > 32767.0f)
|
||||
s[i] = 0x7FFF;
|
||||
else if (scaled < -32768.0f)
|
||||
s[i] = -0x8000;
|
||||
else
|
||||
s[i] = (int16_t)(int32_t)scaled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void convert_float_to_s16_init_simd(void) { }
|
||||
#endif
|
||||
@@ -1,88 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (float_to_s16_neon.S).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#if defined(__ARM_NEON__) && defined(HAVE_ARM_NEON_ASM_OPTIMIZATIONS)
|
||||
|
||||
#ifndef __MACH__
|
||||
.arm
|
||||
#endif
|
||||
|
||||
.align 4
|
||||
.globl convert_float_s16_asm
|
||||
#ifndef __MACH__
|
||||
.type convert_float_s16_asm, %function
|
||||
#endif
|
||||
.globl _convert_float_s16_asm
|
||||
#ifndef __MACH__
|
||||
.type _convert_float_s16_asm, %function
|
||||
#endif
|
||||
# convert_float_s16_asm(int16_t *out, const float *in, size_t samples)
|
||||
convert_float_s16_asm:
|
||||
_convert_float_s16_asm:
|
||||
# Hacky way to get a constant of 2^15.
|
||||
# ((2^4)^2)^2 * 0.5 = 2^15
|
||||
vmov.f32 q8, #16.0
|
||||
vmov.f32 q9, #0.5 @ keep 0.5 for the rounding bias
|
||||
vmul.f32 q8, q8, q8
|
||||
vmul.f32 q8, q8, q8
|
||||
vmul.f32 q8, q8, q9
|
||||
# Sign mask 0x80000000 in every lane (1 << 31).
|
||||
vmov.i32 q10, #1
|
||||
vshl.i32 q10, q10, #31
|
||||
|
||||
1:
|
||||
# Preload here?
|
||||
vld1.f32 {q0-q1}, [r1]!
|
||||
|
||||
vmul.f32 q0, q0, q8
|
||||
vmul.f32 q1, q1, q8
|
||||
|
||||
# Round half away from zero: add copysign(0.5, x) then truncate.
|
||||
vand q11, q0, q10
|
||||
vand q12, q1, q10
|
||||
vorr q11, q11, q9
|
||||
vorr q12, q12, q9
|
||||
vadd.f32 q0, q0, q11
|
||||
vadd.f32 q1, q1, q12
|
||||
|
||||
vcvt.s32.f32 q0, q0
|
||||
vcvt.s32.f32 q1, q1
|
||||
|
||||
vqmovn.s32 d4, q0
|
||||
vqmovn.s32 d5, q1
|
||||
|
||||
vst1.f32 {d4-d5}, [r0]!
|
||||
|
||||
# Guaranteed to get samples in multiples of 8.
|
||||
subs r2, r2, #8
|
||||
bne 1b
|
||||
|
||||
bx lr
|
||||
|
||||
#endif
|
||||
|
||||
/* Prevent the stack from becoming executable for no reason...
|
||||
* Placed last: a bare .section switches the active section, and these
|
||||
* files emit into the assembler default (.text) with no .text directive
|
||||
* of their own to switch back. */
|
||||
#if defined(__linux__) && defined(__ELF__)
|
||||
.section .note.GNU-stack,"",%progbits
|
||||
#endif
|
||||
@@ -1,64 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (float_to_s16_neon.S).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#if defined(__ARM_NEON__) && defined(HAVE_ARM_NEON_ASM_OPTIMIZATIONS)
|
||||
|
||||
#if defined(__thumb__)
|
||||
#define DECL_ARMMODE(x) " .align 2\n" " .global " x "\n" " .thumb\n" " .thumb_func\n" " .type " x ", %function\n" x ":\n"
|
||||
#else
|
||||
#define DECL_ARMMODE(x) " .align 4\n" " .global " x "\n" " .arm\n" x ":\n"
|
||||
#endif
|
||||
|
||||
asm(
|
||||
DECL_ARMMODE("convert_float_s16_asm")
|
||||
DECL_ARMMODE("_convert_float_s16_asm")
|
||||
"# convert_float_s16_asm(int16_t *s, const float *in, size_t len)\n"
|
||||
" # Hacky way to get a constant of 2^15.\n"
|
||||
" # ((2^4)^2)^2 * 0.5 = 2^15\n"
|
||||
" vmov.f32 q8, #16.0\n"
|
||||
" vmov.f32 q9, #0.5\n"
|
||||
" vmul.f32 q8, q8, q8\n"
|
||||
" vmul.f32 q8, q8, q8\n"
|
||||
" vmul.f32 q8, q8, q9\n"
|
||||
"\n"
|
||||
"1:\n"
|
||||
" # Preload here?\n"
|
||||
" vld1.f32 {q0-q1}, [r1]!\n"
|
||||
"\n"
|
||||
" vmul.f32 q0, q0, q8\n"
|
||||
" vmul.f32 q1, q1, q8\n"
|
||||
"\n"
|
||||
" vcvt.s32.f32 q0, q0\n"
|
||||
" vcvt.s32.f32 q1, q1\n"
|
||||
"\n"
|
||||
" vqmovn.s32 d4, q0\n"
|
||||
" vqmovn.s32 d5, q1\n"
|
||||
"\n"
|
||||
" vst1.f32 {d4-d5}, [r0]!\n"
|
||||
"\n"
|
||||
" # Guaranteed to get samples in multiples of 8.\n"
|
||||
" subs r2, r2, #8\n"
|
||||
" bne 1b\n"
|
||||
"\n"
|
||||
" bx lr\n"
|
||||
"\n"
|
||||
);
|
||||
#endif
|
||||
@@ -1,44 +0,0 @@
|
||||
/* Copyright (C) 2010-2023 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (mono_to_stereo.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <audio/conversion/dual_mono.h>
|
||||
|
||||
/* TODO: Use SIMD instructions to make this faster (or show that it's not needed) */
|
||||
void convert_to_dual_mono_float(float *s, const float *in, size_t len)
|
||||
{
|
||||
unsigned i = 0;
|
||||
|
||||
if (!s || !in || !len)
|
||||
return;
|
||||
|
||||
for (; i < len; i++)
|
||||
{
|
||||
s[i * 2] = in[i];
|
||||
s[i * 2 + 1] = in[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Why is there no equivalent for int16_t samples?
|
||||
* No inherent reason, I just didn't need one.
|
||||
* If you do, open a pull request. */
|
||||
@@ -1,205 +0,0 @@
|
||||
/* Copyright (C) 2010-2021 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (s16_to_float.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.
|
||||
*/
|
||||
#if defined(__SSE2__)
|
||||
#include <emmintrin.h>
|
||||
#elif defined(__ALTIVEC__)
|
||||
#include <altivec.h>
|
||||
#endif
|
||||
|
||||
#include <boolean.h>
|
||||
#include <features/features_cpu.h>
|
||||
#include <audio/conversion/s16_to_float.h>
|
||||
|
||||
#if (defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(HAVE_NEON))
|
||||
static bool s16_to_float_neon_enabled = false;
|
||||
|
||||
#ifdef HAVE_ARM_NEON_ASM_OPTIMIZATIONS
|
||||
/* Avoid potential hard-float/soft-float ABI issues. */
|
||||
void convert_s16_float_asm(float *s, const int16_t *in,
|
||||
size_t len, const float *gain);
|
||||
#else
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
void convert_s16_to_float(float *s,
|
||||
const int16_t *in, size_t len, float gain)
|
||||
{
|
||||
unsigned i = 0;
|
||||
|
||||
if (s16_to_float_neon_enabled)
|
||||
{
|
||||
#ifdef HAVE_ARM_NEON_ASM_OPTIMIZATIONS
|
||||
size_t aligned_samples = len & ~7;
|
||||
if (aligned_samples)
|
||||
convert_s16_float_asm(s, in, aligned_samples, &gain);
|
||||
|
||||
/* Could do all conversion in ASM, but keep it simple for now. */
|
||||
s += aligned_samples;
|
||||
in += aligned_samples;
|
||||
len -= aligned_samples;
|
||||
i = 0;
|
||||
#else
|
||||
float gf = gain / (1 << 15);
|
||||
float32x4_t vgf = {gf, gf, gf, gf};
|
||||
while (len >= 8)
|
||||
{
|
||||
float32x4x2_t oreg;
|
||||
int16x4x2_t inreg = vld2_s16(in);
|
||||
int32x4_t p1 = vmovl_s16(inreg.val[0]);
|
||||
int32x4_t p2 = vmovl_s16(inreg.val[1]);
|
||||
oreg.val[0] = vmulq_f32(vcvtq_f32_s32(p1), vgf);
|
||||
oreg.val[1] = vmulq_f32(vcvtq_f32_s32(p2), vgf);
|
||||
vst2q_f32(s, oreg);
|
||||
in += 8;
|
||||
s += 8;
|
||||
len -= 8;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
gain /= 0x8000;
|
||||
|
||||
for (; i < len; i++)
|
||||
s[i] = (float)in[i] * gain;
|
||||
}
|
||||
|
||||
void convert_s16_to_float_init_simd(void)
|
||||
{
|
||||
uint64_t cpu = cpu_features_get();
|
||||
|
||||
if (cpu & RETRO_SIMD_NEON)
|
||||
s16_to_float_neon_enabled = true;
|
||||
}
|
||||
#else
|
||||
void convert_s16_to_float(float *s,
|
||||
const int16_t *in, size_t len, float gain)
|
||||
{
|
||||
unsigned i = 0;
|
||||
|
||||
#if defined(__SSE2__)
|
||||
float fgain = gain / UINT32_C(0x80000000);
|
||||
__m128 factor = _mm_set1_ps(fgain);
|
||||
|
||||
for (i = 0; i + 8 <= len; i += 8, in += 8, s += 8)
|
||||
{
|
||||
__m128i input = _mm_loadu_si128((const __m128i *)in);
|
||||
__m128i regs_l = _mm_unpacklo_epi16(_mm_setzero_si128(), input);
|
||||
__m128i regs_r = _mm_unpackhi_epi16(_mm_setzero_si128(), input);
|
||||
__m128 output_l = _mm_mul_ps(_mm_cvtepi32_ps(regs_l), factor);
|
||||
__m128 output_r = _mm_mul_ps(_mm_cvtepi32_ps(regs_r), factor);
|
||||
|
||||
_mm_storeu_ps(s + 0, output_l);
|
||||
_mm_storeu_ps(s + 4, output_r);
|
||||
}
|
||||
|
||||
len = len - i;
|
||||
i = 0;
|
||||
#elif defined(__ALTIVEC__)
|
||||
size_t samples_in = len;
|
||||
|
||||
/* Unaligned loads/store is a bit expensive, so we
|
||||
* optimize for the good path (very likely). */
|
||||
if (((uintptr_t)s & 15) + ((uintptr_t)in & 15) == 0)
|
||||
{
|
||||
const vector float gain_vec = { gain, gain , gain, gain };
|
||||
const vector float zero_vec = { 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
for (i = 0; i + 8 <= len; i += 8, in += 8, s += 8)
|
||||
{
|
||||
vector signed short input = vec_ld(0, in);
|
||||
vector signed int hi = vec_unpackh(input);
|
||||
vector signed int lo = vec_unpackl(input);
|
||||
vector float out_hi = vec_madd(vec_ctf(hi, 15), gain_vec, zero_vec);
|
||||
vector float out_lo = vec_madd(vec_ctf(lo, 15), gain_vec, zero_vec);
|
||||
|
||||
vec_st(out_hi, 0, s);
|
||||
vec_st(out_lo, 16, s);
|
||||
}
|
||||
|
||||
samples_in -= i;
|
||||
}
|
||||
|
||||
len = samples_in;
|
||||
i = 0;
|
||||
#endif
|
||||
|
||||
gain /= 0x8000;
|
||||
|
||||
#if defined(_MIPS_ARCH_ALLEGREX)
|
||||
#ifdef DEBUG
|
||||
/* Make sure the buffer is 16 byte aligned, this should be the
|
||||
* default behaviour of malloc in the PSPSDK.
|
||||
* Only the output buffer can be assumed to be 16-byte aligned. */
|
||||
retro_assert(((uintptr_t)s & 0xf) == 0);
|
||||
#endif
|
||||
|
||||
__asm__ (
|
||||
".set push \n"
|
||||
".set noreorder \n"
|
||||
"mtv %0, s200 \n"
|
||||
".set pop \n"
|
||||
::"r"(gain));
|
||||
|
||||
for (i = 0; i + 16 <= len; i += 16)
|
||||
{
|
||||
__asm__ (
|
||||
".set push \n"
|
||||
".set noreorder \n"
|
||||
|
||||
"lv.s s100, 0(%0) \n"
|
||||
"lv.s s101, 4(%0) \n"
|
||||
"lv.s s110, 8(%0) \n"
|
||||
"lv.s s111, 12(%0) \n"
|
||||
"lv.s s120, 16(%0) \n"
|
||||
"lv.s s121, 20(%0) \n"
|
||||
"lv.s s130, 24(%0) \n"
|
||||
"lv.s s131, 28(%0) \n"
|
||||
|
||||
"vs2i.p c100, c100 \n"
|
||||
"vs2i.p c110, c110 \n"
|
||||
"vs2i.p c120, c120 \n"
|
||||
"vs2i.p c130, c130 \n"
|
||||
|
||||
"vi2f.q c100, c100, 16 \n"
|
||||
"vi2f.q c110, c110, 16 \n"
|
||||
"vi2f.q c120, c120, 16 \n"
|
||||
"vi2f.q c130, c130, 16 \n"
|
||||
|
||||
"vmscl.q e100, e100, s200 \n"
|
||||
|
||||
"sv.q c100, 0(%1) \n"
|
||||
"sv.q c110, 16(%1) \n"
|
||||
"sv.q c120, 32(%1) \n"
|
||||
"sv.q c130, 48(%1) \n"
|
||||
|
||||
".set pop \n"
|
||||
:: "r"(in + i), "r"(s + i));
|
||||
}
|
||||
#endif
|
||||
|
||||
for (; i < len; i++)
|
||||
s[i] = (float)in[i] * gain;
|
||||
}
|
||||
|
||||
void convert_s16_to_float_init_simd(void) { }
|
||||
#endif
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (s16_to_float_neon.S).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#if defined(__ARM_NEON__) && defined(HAVE_ARM_NEON_ASM_OPTIMIZATIONS)
|
||||
|
||||
#ifndef __MACH__
|
||||
.arm
|
||||
#endif
|
||||
|
||||
.align 4
|
||||
.globl convert_s16_float_asm
|
||||
#ifndef __MACH__
|
||||
.type convert_s16_float_asm, %function
|
||||
#endif
|
||||
.globl _convert_s16_float_asm
|
||||
#ifndef __MACH__
|
||||
.type _convert_s16_float_asm, %function
|
||||
#endif
|
||||
# convert_s16_float_asm(float *out, const int16_t *in, size_t samples, const float *gain)
|
||||
convert_s16_float_asm:
|
||||
_convert_s16_float_asm:
|
||||
# Hacky way to get a constant of 2^-15.
|
||||
# Might be faster to just load a constant from memory.
|
||||
# It's just done once however ...
|
||||
vmov.f32 q8, #0.25
|
||||
vmul.f32 q8, q8, q8
|
||||
vmul.f32 q8, q8, q8
|
||||
vmul.f32 q8, q8, q8
|
||||
vadd.f32 q8, q8, q8
|
||||
|
||||
# Apply gain
|
||||
vld1.f32 {d6[0]}, [r3]
|
||||
vmul.f32 q8, q8, d6[0]
|
||||
|
||||
1:
|
||||
# Preload here?
|
||||
vld1.s16 {q0}, [r1]!
|
||||
|
||||
# Widen to 32-bit
|
||||
vmovl.s16 q1, d0
|
||||
vmovl.s16 q2, d1
|
||||
|
||||
# Convert to float
|
||||
vcvt.f32.s32 q1, q1
|
||||
vcvt.f32.s32 q2, q2
|
||||
|
||||
vmul.f32 q1, q1, q8
|
||||
vmul.f32 q2, q2, q8
|
||||
|
||||
vst1.f32 {q1-q2}, [r0]!
|
||||
|
||||
# Guaranteed to get samples in multiples of 8.
|
||||
subs r2, r2, #8
|
||||
bne 1b
|
||||
|
||||
bx lr
|
||||
|
||||
#endif
|
||||
|
||||
/* Prevent the stack from becoming executable for no reason...
|
||||
* Placed last: a bare .section switches the active section, and these
|
||||
* files emit into the assembler default (.text) with no .text directive
|
||||
* of their own to switch back. */
|
||||
#if defined(__linux__) && defined(__ELF__)
|
||||
.section .note.GNU-stack,"",%progbits
|
||||
#endif
|
||||
@@ -1,71 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (s16_to_float_neon.S).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#if defined(__ARM_NEON__) && defined(HAVE_ARM_NEON_ASM_OPTIMIZATIONS)
|
||||
|
||||
#if defined(__thumb__)
|
||||
#define DECL_ARMMODE(x) " .align 2\n" " .global " x "\n" " .thumb\n" " .thumb_func\n" " .type " x ", %function\n" x ":\n"
|
||||
#else
|
||||
#define DECL_ARMMODE(x) " .align 4\n" " .global " x "\n" " .arm\n" x ":\n"
|
||||
#endif
|
||||
|
||||
asm(
|
||||
DECL_ARMMODE("convert_s16_float_asm")
|
||||
DECL_ARMMODE("_convert_s16_float_asm")
|
||||
"# convert_s16_float_asm(float *s, const int16_t *in, size_t len, const float *gain)\n"
|
||||
" # Hacky way to get a constant of 2^-15.\n"
|
||||
" # Might be faster to just load a constant from memory.\n"
|
||||
" # It's just done once however ...\n"
|
||||
" vmov.f32 q8, #0.25\n"
|
||||
" vmul.f32 q8, q8, q8\n"
|
||||
" vmul.f32 q8, q8, q8\n"
|
||||
" vmul.f32 q8, q8, q8\n"
|
||||
" vadd.f32 q8, q8, q8\n"
|
||||
"\n"
|
||||
" # Apply gain\n"
|
||||
" vld1.f32 {d6[0]}, [r3]\n"
|
||||
" vmul.f32 q8, q8, d6[0]\n"
|
||||
"\n"
|
||||
"1:\n"
|
||||
" # Preload here?\n"
|
||||
" vld1.s16 {q0}, [r1]!\n"
|
||||
"\n"
|
||||
" # Widen to 32-bit\n"
|
||||
" vmovl.s16 q1, d0\n"
|
||||
" vmovl.s16 q2, d1\n"
|
||||
"\n"
|
||||
" # Convert to float\n"
|
||||
" vcvt.f32.s32 q1, q1\n"
|
||||
" vcvt.f32.s32 q2, q2\n"
|
||||
"\n"
|
||||
" vmul.f32 q1, q1, q8\n"
|
||||
" vmul.f32 q2, q2, q8\n"
|
||||
"\n"
|
||||
" vst1.f32 {q1-q2}, [r0]!\n"
|
||||
"\n"
|
||||
" # Guaranteed to get samples in multiples of 8.\n"
|
||||
" subs r2, r2, #8\n"
|
||||
" bne 1b\n"
|
||||
"\n"
|
||||
" bx lr\n"
|
||||
"\n"
|
||||
);
|
||||
#endif
|
||||
@@ -1,45 +0,0 @@
|
||||
/* Copyright (C) 2010-2023 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (mono_to_stereo.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <audio/conversion/dual_mono.h>
|
||||
|
||||
/* TODO: Use SIMD instructions to make this faster (or show that it's not needed) */
|
||||
void convert_to_mono_float_left(float *out, const float *in, size_t frames)
|
||||
{
|
||||
unsigned i = 0;
|
||||
|
||||
if (!out || !in || !frames)
|
||||
return;
|
||||
|
||||
for (; i < frames; i++)
|
||||
{
|
||||
out[i] = in[i * 2];
|
||||
}
|
||||
}
|
||||
|
||||
/* Why is there no equivalent for int16_t samples?
|
||||
* No inherent reason, I just didn't need one.
|
||||
* If you do, open a pull request.
|
||||
* Same goes for the lack of a convert_to_mono_float_right;
|
||||
* I didn't need one, so I didn't write one. */
|
||||
@@ -1,381 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (dsp_filter.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
|
||||
#include <compat/posix_string.h>
|
||||
#include <dynamic/dylib.h>
|
||||
|
||||
#include <file/file_path.h>
|
||||
#include <file/config_file_userdata.h>
|
||||
#include <features/features_cpu.h>
|
||||
#include <lists/string_list.h>
|
||||
#include <string/stdstring.h>
|
||||
#include <libretro_dspfilter.h>
|
||||
|
||||
#include <audio/dsp_filter.h>
|
||||
|
||||
struct retro_dsp_plug
|
||||
{
|
||||
#ifdef HAVE_DYLIB
|
||||
dylib_t lib;
|
||||
#endif
|
||||
const struct dspfilter_implementation *impl;
|
||||
};
|
||||
|
||||
struct retro_dsp_instance
|
||||
{
|
||||
const struct dspfilter_implementation *impl;
|
||||
void *impl_data;
|
||||
};
|
||||
|
||||
struct retro_dsp_filter
|
||||
{
|
||||
config_file_t *conf;
|
||||
|
||||
struct retro_dsp_plug *plugs;
|
||||
unsigned num_plugs;
|
||||
|
||||
struct retro_dsp_instance *instances;
|
||||
unsigned num_instances;
|
||||
|
||||
/* Whether every instance in the chain can process int16, folded once at
|
||||
* creation. audio_driver_mixer_use_s16() asks on every flush and the
|
||||
* answer cannot change until the chain is rebuilt. */
|
||||
bool supports_i16;
|
||||
};
|
||||
|
||||
static const struct dspfilter_implementation *find_implementation(
|
||||
retro_dsp_filter_t *dsp, const char *ident)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < dsp->num_plugs; i++)
|
||||
{
|
||||
if (string_is_equal(dsp->plugs[i].impl->short_ident, ident))
|
||||
return dsp->plugs[i].impl;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct dspfilter_config dspfilter_config = {
|
||||
config_userdata_get_float,
|
||||
config_userdata_get_int,
|
||||
config_userdata_get_float_array,
|
||||
config_userdata_get_int_array,
|
||||
config_userdata_get_string,
|
||||
config_userdata_free,
|
||||
};
|
||||
|
||||
static bool create_filter_graph(retro_dsp_filter_t *dsp, float sample_rate)
|
||||
{
|
||||
unsigned i;
|
||||
struct retro_dsp_instance *instances = NULL;
|
||||
unsigned filters = 0;
|
||||
|
||||
if (!config_get_uint(dsp->conf, "filters", &filters))
|
||||
return false;
|
||||
|
||||
instances = (struct retro_dsp_instance*)calloc(filters, sizeof(*instances));
|
||||
if (!instances)
|
||||
return false;
|
||||
|
||||
dsp->instances = instances;
|
||||
dsp->num_instances = filters;
|
||||
|
||||
for (i = 0; i < filters; i++)
|
||||
{
|
||||
struct config_file_userdata userdata;
|
||||
struct dspfilter_info info;
|
||||
char key[64];
|
||||
char name[64];
|
||||
|
||||
key[0] = name[0] = '\0';
|
||||
|
||||
info.input_rate = sample_rate;
|
||||
|
||||
snprintf(key, sizeof(key), "filter%u", i);
|
||||
|
||||
if (!config_get_array(dsp->conf, key, name, sizeof(name)))
|
||||
return false;
|
||||
|
||||
dsp->instances[i].impl = find_implementation(dsp, name);
|
||||
if (!dsp->instances[i].impl)
|
||||
return false;
|
||||
|
||||
userdata.conf = dsp->conf;
|
||||
/* Index-specific configs take priority over ident-specific. */
|
||||
userdata.prefix[0] = key;
|
||||
userdata.prefix[1] = dsp->instances[i].impl->short_ident;
|
||||
|
||||
dsp->instances[i].impl_data = dsp->instances[i].impl->init(&info,
|
||||
&dspfilter_config, &userdata);
|
||||
if (!dsp->instances[i].impl_data)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Fold the chain's int16 capability once, here. */
|
||||
dsp->supports_i16 = (filters > 0);
|
||||
for (i = 0; i < filters; i++)
|
||||
{
|
||||
const struct dspfilter_implementation *impl = dsp->instances[i].impl;
|
||||
if (impl->api_version < 2 || !impl->process_i16)
|
||||
{
|
||||
dsp->supports_i16 = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(HAVE_FILTERS_BUILTIN)
|
||||
extern const struct dspfilter_implementation *chorus_dspfilter_get_implementation(dspfilter_simd_mask_t mask);
|
||||
extern const struct dspfilter_implementation *delta_dspfilter_get_implementation(dspfilter_simd_mask_t mask);
|
||||
extern const struct dspfilter_implementation *echo_dspfilter_get_implementation(dspfilter_simd_mask_t mask);
|
||||
extern const struct dspfilter_implementation *eq_dspfilter_get_implementation(dspfilter_simd_mask_t mask);
|
||||
extern const struct dspfilter_implementation *iir_dspfilter_get_implementation(dspfilter_simd_mask_t mask);
|
||||
extern const struct dspfilter_implementation *panning_dspfilter_get_implementation(dspfilter_simd_mask_t mask);
|
||||
extern const struct dspfilter_implementation *phaser_dspfilter_get_implementation(dspfilter_simd_mask_t mask);
|
||||
extern const struct dspfilter_implementation *reverb_dspfilter_get_implementation(dspfilter_simd_mask_t mask);
|
||||
extern const struct dspfilter_implementation *tremolo_dspfilter_get_implementation(dspfilter_simd_mask_t mask);
|
||||
extern const struct dspfilter_implementation *vibrato_dspfilter_get_implementation(dspfilter_simd_mask_t mask);
|
||||
extern const struct dspfilter_implementation *wahwah_dspfilter_get_implementation(dspfilter_simd_mask_t mask);
|
||||
|
||||
static const dspfilter_get_implementation_t dsp_plugs_builtin[] = {
|
||||
chorus_dspfilter_get_implementation,
|
||||
delta_dspfilter_get_implementation,
|
||||
echo_dspfilter_get_implementation,
|
||||
eq_dspfilter_get_implementation,
|
||||
iir_dspfilter_get_implementation,
|
||||
panning_dspfilter_get_implementation,
|
||||
phaser_dspfilter_get_implementation,
|
||||
reverb_dspfilter_get_implementation,
|
||||
tremolo_dspfilter_get_implementation,
|
||||
vibrato_dspfilter_get_implementation,
|
||||
wahwah_dspfilter_get_implementation,
|
||||
};
|
||||
|
||||
static bool append_plugs(retro_dsp_filter_t *dsp, struct string_list *list)
|
||||
{
|
||||
unsigned i;
|
||||
dspfilter_simd_mask_t mask = (dspfilter_simd_mask_t)cpu_features_get();
|
||||
struct retro_dsp_plug *plugs = (struct retro_dsp_plug*)
|
||||
calloc(ARRAY_SIZE(dsp_plugs_builtin), sizeof(*plugs));
|
||||
|
||||
if (!plugs)
|
||||
return false;
|
||||
|
||||
dsp->plugs = plugs;
|
||||
dsp->num_plugs = ARRAY_SIZE(dsp_plugs_builtin);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(dsp_plugs_builtin); i++)
|
||||
{
|
||||
dsp->plugs[i].impl = dsp_plugs_builtin[i](mask);
|
||||
if (!dsp->plugs[i].impl)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#elif defined(HAVE_DYLIB)
|
||||
static bool append_plugs(retro_dsp_filter_t *dsp, struct string_list *list)
|
||||
{
|
||||
unsigned i;
|
||||
dspfilter_simd_mask_t mask = (dspfilter_simd_mask_t)cpu_features_get();
|
||||
unsigned list_size = list ? (unsigned)list->size : 0;
|
||||
|
||||
for (i = 0; i < list_size; i++)
|
||||
{
|
||||
dspfilter_get_implementation_t cb;
|
||||
const struct dspfilter_implementation *impl = NULL;
|
||||
struct retro_dsp_plug *new_plugs = NULL;
|
||||
dylib_t lib =
|
||||
dylib_load(list->elems[i].data);
|
||||
|
||||
if (!lib)
|
||||
continue;
|
||||
|
||||
cb = (dspfilter_get_implementation_t)dylib_proc(lib, "dspfilter_get_implementation");
|
||||
if (!cb)
|
||||
{
|
||||
dylib_close(lib);
|
||||
continue;
|
||||
}
|
||||
|
||||
impl = cb(mask);
|
||||
if (!impl)
|
||||
{
|
||||
dylib_close(lib);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Accept any supported ABI version. v1 plugins are float-only; v2 adds
|
||||
* the optional process_i16 entry point. process_i16 is only ever read
|
||||
* after an api_version >= 2 check, so a v1 plugin's shorter struct is
|
||||
* never over-read. */
|
||||
if ( impl->api_version < 1
|
||||
|| impl->api_version > DSPFILTER_API_VERSION)
|
||||
{
|
||||
dylib_close(lib);
|
||||
continue;
|
||||
}
|
||||
|
||||
new_plugs = (struct retro_dsp_plug*)
|
||||
realloc(dsp->plugs, sizeof(*dsp->plugs) * (dsp->num_plugs + 1));
|
||||
if (!new_plugs)
|
||||
{
|
||||
dylib_close(lib);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Found plug. */
|
||||
|
||||
dsp->plugs = new_plugs;
|
||||
dsp->plugs[dsp->num_plugs].lib = lib;
|
||||
dsp->plugs[dsp->num_plugs].impl = impl;
|
||||
dsp->num_plugs++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
retro_dsp_filter_t *retro_dsp_filter_new(
|
||||
const char *filter_config,
|
||||
void *string_data,
|
||||
float sample_rate)
|
||||
{
|
||||
config_file_t *conf = NULL;
|
||||
struct string_list *plugs = NULL;
|
||||
retro_dsp_filter_t *dsp = (retro_dsp_filter_t*)calloc(1, sizeof(*dsp));
|
||||
|
||||
if (!dsp)
|
||||
return NULL;
|
||||
|
||||
if (!(conf = config_file_new_from_path_to_string(filter_config)))
|
||||
goto error;
|
||||
|
||||
dsp->conf = conf;
|
||||
|
||||
if (string_data)
|
||||
plugs = (struct string_list*)string_data;
|
||||
|
||||
#if defined(HAVE_DYLIB) || defined(HAVE_FILTERS_BUILTIN)
|
||||
if (!append_plugs(dsp, plugs))
|
||||
goto error;
|
||||
#endif
|
||||
|
||||
if (plugs)
|
||||
string_list_free(plugs);
|
||||
plugs = NULL;
|
||||
|
||||
if (!create_filter_graph(dsp, sample_rate))
|
||||
goto error;
|
||||
|
||||
return dsp;
|
||||
|
||||
error:
|
||||
if (plugs)
|
||||
string_list_free(plugs);
|
||||
retro_dsp_filter_free(dsp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void retro_dsp_filter_free(retro_dsp_filter_t *dsp)
|
||||
{
|
||||
unsigned i;
|
||||
if (!dsp)
|
||||
return;
|
||||
|
||||
for (i = 0; i < dsp->num_instances; i++)
|
||||
{
|
||||
if (dsp->instances[i].impl_data && dsp->instances[i].impl)
|
||||
dsp->instances[i].impl->free(dsp->instances[i].impl_data);
|
||||
}
|
||||
free(dsp->instances);
|
||||
|
||||
#ifdef HAVE_DYLIB
|
||||
for (i = 0; i < dsp->num_plugs; i++)
|
||||
{
|
||||
if (dsp->plugs[i].lib)
|
||||
dylib_close(dsp->plugs[i].lib);
|
||||
}
|
||||
free(dsp->plugs);
|
||||
#endif
|
||||
|
||||
if (dsp->conf)
|
||||
config_file_free(dsp->conf);
|
||||
|
||||
free(dsp);
|
||||
}
|
||||
|
||||
void retro_dsp_filter_process(retro_dsp_filter_t *dsp,
|
||||
struct retro_dsp_data *data)
|
||||
{
|
||||
unsigned i;
|
||||
struct dspfilter_output output = {0};
|
||||
struct dspfilter_input input = {0};
|
||||
|
||||
output.samples = data->input;
|
||||
output.frames = data->input_frames;
|
||||
|
||||
for (i = 0; i < dsp->num_instances; i++)
|
||||
{
|
||||
input.samples = output.samples;
|
||||
input.frames = output.frames;
|
||||
dsp->instances[i].impl->process(
|
||||
dsp->instances[i].impl_data, &output, &input);
|
||||
}
|
||||
|
||||
data->output = output.samples;
|
||||
data->output_frames = output.frames;
|
||||
}
|
||||
|
||||
bool retro_dsp_filter_supports_int16(retro_dsp_filter_t *dsp)
|
||||
{
|
||||
return dsp && dsp->supports_i16;
|
||||
}
|
||||
|
||||
void retro_dsp_filter_process_int16(retro_dsp_filter_t *dsp,
|
||||
struct retro_dsp_data_int16 *data)
|
||||
{
|
||||
unsigned i;
|
||||
struct dspfilter_output_i16 output = {0};
|
||||
struct dspfilter_input_i16 input = {0};
|
||||
|
||||
output.samples = data->input;
|
||||
output.frames = data->input_frames;
|
||||
|
||||
for (i = 0; i < dsp->num_instances; i++)
|
||||
{
|
||||
input.samples = output.samples;
|
||||
input.frames = output.frames;
|
||||
dsp->instances[i].impl->process_i16(
|
||||
dsp->instances[i].impl_data, &output, &input);
|
||||
}
|
||||
|
||||
data->output = output.samples;
|
||||
data->output_frames = output.frames;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
filters = 2
|
||||
filter0 = iir
|
||||
filter1 = panning
|
||||
|
||||
iir_gain = 10.0
|
||||
iir_type = BBOOST
|
||||
iir_frequency = 200.0
|
||||
|
||||
# Avoids clipping.
|
||||
panning_left_mix = "0.3 0.0"
|
||||
panning_right_mix = "0.0 0.3"
|
||||
@@ -1,7 +0,0 @@
|
||||
filters = 1
|
||||
filter0 = iir
|
||||
|
||||
iir_frequency = 8600.0
|
||||
iir_quality = 0.707
|
||||
iir_gain = 6.0
|
||||
iir_type = LPF
|
||||
@@ -1,22 +0,0 @@
|
||||
filters = 4
|
||||
filter0 = eq
|
||||
filter1 = reverb
|
||||
filter2 = iir
|
||||
filter3 = panning
|
||||
|
||||
eq_frequencies = "32 64 125 250 500 1000 2000 4000 8000 16000 20000"
|
||||
eq_gains = "6 9 12 7 6 5 7 9 11 6 0"
|
||||
|
||||
# Reverb - slight reverb
|
||||
reverb_drytime = 0.5
|
||||
reverb_wettime = 0.15
|
||||
reverb_damping = 0.8
|
||||
reverb_roomwidth = 0.25
|
||||
reverb_roomsize = 0.25
|
||||
|
||||
# IIR - filters out some harsh sounds on the upper end
|
||||
iir_type = RIAA_CD
|
||||
|
||||
# Panning - cut the volume a bit
|
||||
panning_left_mix = "0.75 0.0"
|
||||
panning_right_mix = "0.0 0.75"
|
||||
@@ -1,14 +0,0 @@
|
||||
filters = 1
|
||||
filter0 = chorus
|
||||
|
||||
# Controls the base delay of the chorus (milliseconds).
|
||||
# chorus_delay_ms = 25.0
|
||||
#
|
||||
# Controls the depth of the delay. The delay will vary between delay_ms +/- depth_ms.
|
||||
# chorus_depth_ms = 1.0
|
||||
#
|
||||
# Frequency of LFO which controls delay.
|
||||
# chorus_lfo_freq = 0.5
|
||||
#
|
||||
# Controls dry/wet-ness of effect. 1.0 = full chorus, 0.0 = no chorus.
|
||||
# chorus_drywet = 0.8
|
||||
@@ -1,4 +0,0 @@
|
||||
filters = 1
|
||||
filter0 = crystalizer
|
||||
# Controls dry/wet-ness of effect. 0.0 = none, 10.0 = max.
|
||||
crystalizer_intensity = 5.0
|
||||
@@ -1,41 +0,0 @@
|
||||
filters = 1
|
||||
filter0 = eq
|
||||
|
||||
# Defaults
|
||||
|
||||
# Beta factor for Kaiser window.
|
||||
# Lower values will allow better frequency resolution, but more ripple.
|
||||
# eq_window_beta = 4.0
|
||||
|
||||
# The block size on which FFT is done.
|
||||
# Too high value requires more processing as well as longer latency but
|
||||
# allows finer-grained control over the spectrum.
|
||||
# eq_block_size_log2 = 8
|
||||
|
||||
# An array of which frequencies to control.
|
||||
# You can create an arbitrary amount of these sampling points.
|
||||
# The EQ will try to create a frequency response which fits well to these points.
|
||||
# The filter response is linearly interpolated between sampling points here.
|
||||
#
|
||||
# It is implied that 0 Hz (DC) and Nyquist have predefined gains of 0 dB which are interpolated against.
|
||||
# If you want a "peak" in the spectrum or similar, you have to define close points to say, 0 dB.
|
||||
#
|
||||
# E.g.: A boost of 3 dB at 1 kHz can be expressed as.
|
||||
# eq_frequencies = "500 1000 2000"
|
||||
# eq_gains = "0 3 0"
|
||||
# Due to frequency domain smearing, you will not get exactly +3 dB at 1 kHz.
|
||||
|
||||
# By default, this filter has a flat frequency response.
|
||||
|
||||
# Dumps the impulse response generated by the EQ as a plain-text file
|
||||
# with one coefficient per line.
|
||||
# eq_impulse_response_output = "eq_impulse.txt"
|
||||
#
|
||||
# Using GNU Octave or Matlab, you can plot the response with:
|
||||
#
|
||||
# f = fopen('/path/to/eq_impulse.txt');
|
||||
# l = textscan(f, '%f');
|
||||
# res = l{1};
|
||||
# freqz(res, 1, 4096, 48000);
|
||||
#
|
||||
# It will give the response in Hz; 48000 is the default Output Rate of RetroArch
|
||||
@@ -1,19 +0,0 @@
|
||||
filters = 1
|
||||
filter0 = echo
|
||||
|
||||
# Somewhat fancy Echo filter. Can take any number of echo channels with varying delays (ms) and feedback factors.
|
||||
# Echo output from all channels can be fed back into each other to create a somewhat reverb-like effect if desired.
|
||||
|
||||
# Defaults, 200 ms delay echo with feedback:
|
||||
# Delay in ms. Takes an array with multiple channels.
|
||||
# echo_delay = "200"
|
||||
# Feedback factor for echo.
|
||||
# echo_feedback = "0.5"
|
||||
# Overall echo amplification. If too high, the echo becomes unstable due to feedback.
|
||||
# echo_amp = "0.2"
|
||||
|
||||
# Reverby preset.
|
||||
# echo_delay = " 60 80 120 172 200 320 380"
|
||||
# echo_feedback = "0.5 0.5 0.4 0.3 0.5 0.3 0.2"
|
||||
|
||||
# echo_amp = "0.12"
|
||||
@@ -1,12 +0,0 @@
|
||||
filters = 2
|
||||
filter0 = echo
|
||||
filter1 = reverb
|
||||
|
||||
echo_delay = "200"
|
||||
echo_feedback = "0.6"
|
||||
echo_amp = "0.25"
|
||||
|
||||
reverb_roomwidth = 0.75
|
||||
reverb_roomsize = 0.75
|
||||
reverb_damping = 1.0
|
||||
reverb_wettime = 0.3
|
||||
@@ -1,6 +0,0 @@
|
||||
filters = 1
|
||||
filter0 = iir
|
||||
|
||||
iir_gain = -12.0
|
||||
iir_type = HSH
|
||||
iir_frequency = 8000.0
|
||||
@@ -1,22 +0,0 @@
|
||||
filters = 1
|
||||
filter0 = iir
|
||||
|
||||
# Defaults.
|
||||
#iir_frequency = 1024.0
|
||||
#iir_quality = 0.707
|
||||
#iir_gain = 0.0
|
||||
#iir_type = LPF
|
||||
|
||||
# Filter types:
|
||||
# LPF: Low-pass
|
||||
# HPF: High-pass
|
||||
# BPCSGF: Band-pass #1
|
||||
# BPZPGF: Band-pass #2
|
||||
# APF: Allpass
|
||||
# NOTCH: Notch filter
|
||||
# RIAA_phono: RIAA record/tape deemphasis
|
||||
# PEQ: peaking band EQ
|
||||
# BBOOST: Bassboost
|
||||
# LSH: Low-shelf
|
||||
# HSH: High-shelf
|
||||
# RIAA_CD: CD de-emphasis
|
||||
@@ -1,47 +0,0 @@
|
||||
filters = 1
|
||||
filter0 = eq
|
||||
|
||||
eq_frequencies = "8000 10000 12500 16000 20000"
|
||||
eq_gains = "0 -30 -30 -30 -30"
|
||||
|
||||
# Low pass filter for the QSound chip from CPS-1/2.
|
||||
# Some games have aliasing due low quality samples, so you can hear some annoying noisy near 11 kHz
|
||||
|
||||
# Defaults
|
||||
|
||||
# Beta factor for Kaiser window.
|
||||
# Lower values will allow better frequency resolution, but more ripple.
|
||||
# eq_window_beta = 4.0
|
||||
|
||||
# The block size on which FFT is done.
|
||||
# Too high value requires more processing as well as longer latency but
|
||||
# allows finer-grained control over the spectrum.
|
||||
# eq_block_size_log2 = 8
|
||||
|
||||
# An array of which frequencies to control.
|
||||
# You can create an arbitrary amount of these sampling points.
|
||||
# The EQ will try to create a frequency response which fits well to these points.
|
||||
# The filter response is linearly interpolated between sampling points here.
|
||||
#
|
||||
# It is implied that 0 Hz (DC) and Nyquist have predefined gains of 0 dB which are interpolated against.
|
||||
# If you want a "peak" in the spectrum or similar, you have to define close points to say, 0 dB.
|
||||
#
|
||||
# E.g.: A boost of 3 dB at 1 kHz can be expressed as.
|
||||
# eq_frequencies = "500 1000 2000"
|
||||
# eq_gains = "0 3 0"
|
||||
# Due to frequency domain smearing, you will not get exactly +3 dB at 1 kHz.
|
||||
|
||||
# By default, this filter has a low pass response with cuttof frequency at ~8600 Hz.
|
||||
|
||||
# Dumps the impulse response generated by the EQ as a plain-text file
|
||||
# with one coefficient per line.
|
||||
# eq_impulse_response_output = "eq_impulse.txt"
|
||||
#
|
||||
# Using GNU Octave or Matlab, you can plot the response with:
|
||||
#
|
||||
# f = fopen('/path/to/eq_impulse.txt');
|
||||
# l = textscan(f, '%f');
|
||||
# res = l{1};
|
||||
# freqz(res, 1, 4096, 48000);
|
||||
#
|
||||
# It will give the response in Hz; 48000 is the default Output Rate of RetroArch
|
||||
@@ -1,135 +0,0 @@
|
||||
compiler := gcc
|
||||
extra_flags :=
|
||||
use_neon := 0
|
||||
build = release
|
||||
DYLIB := so
|
||||
PREFIX := /usr
|
||||
INSTALLDIR := $(PREFIX)/lib/retroarch/filters/audio
|
||||
|
||||
ifeq ($(platform),)
|
||||
platform = unix
|
||||
ifeq ($(shell uname -s),)
|
||||
platform = win
|
||||
else ifneq ($(findstring Darwin,$(shell uname -s)),)
|
||||
platform = osx
|
||||
arch = intel
|
||||
ifeq ($(shell uname -p),powerpc)
|
||||
arch = ppc
|
||||
endif
|
||||
else ifneq ($(findstring MINGW,$(shell uname -s)),)
|
||||
platform = win
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(platform),gcc)
|
||||
extra_rules_gcc := $(shell $(compiler) -dumpmachine)
|
||||
endif
|
||||
|
||||
ifneq (,$(findstring armv7,$(extra_rules_gcc)))
|
||||
extra_flags += -mcpu=cortex-a9 -mtune=cortex-a9 -mfpu=neon
|
||||
use_neon := 1
|
||||
endif
|
||||
|
||||
ifneq (,$(findstring hardfloat,$(extra_rules_gcc)))
|
||||
extra_flags += -mfloat-abi=hard
|
||||
endif
|
||||
|
||||
ifeq (release,$(build))
|
||||
extra_flags += -O2
|
||||
endif
|
||||
|
||||
ifeq (debug,$(build))
|
||||
extra_flags += -O0 -g
|
||||
endif
|
||||
|
||||
ldflags := $(LDFLAGS) -shared -lm -Wl,--version-script=link.T
|
||||
|
||||
ifeq ($(platform), unix)
|
||||
DYLIB = so
|
||||
else ifeq ($(platform), osx)
|
||||
compiler := $(CC)
|
||||
DYLIB = dylib
|
||||
ldflags := -dynamiclib
|
||||
ARCHFLAGS=
|
||||
MINVERFLAGS=
|
||||
ifeq ($(shell uname -p),arm)
|
||||
MINVERFLAGS = -mmacosx-version-min=10.15 -stdlib=libc++ # macOS (Metal, ARM 64bit)
|
||||
else ifeq ($(HAVE_METAL),1)
|
||||
MINVERFLAGS = -mmacosx-version-min=10.13 -stdlib=libc++ # macOS (Metal, x86 64bit)
|
||||
else ifeq ($(shell uname -p),powerpc)
|
||||
MINVERFLAGS = -mmacosx-version-min=10.5 # macOSX (PowerPC 32-bit)
|
||||
else ifeq ($(shell uname -m),i386)
|
||||
MINVERFLAGS = -mmacosx-version-min=10.6 # macOSX (OpenGL, x86 32bit)
|
||||
else
|
||||
MINVERFLAGS = -mmacosx-version-min=10.7 -stdlib=libc++ # macOSX (OpenGL, x86 64bit)
|
||||
endif
|
||||
|
||||
# Build for a specific architecture when ARCH is defined as a switch
|
||||
ifeq ($(ARCH),arm64)
|
||||
MINVERFLAGS = -mmacosx-version-min=10.15 -stdlib=libc++ # macOS (Metal, ARM 64bit)
|
||||
ARCHFLAGS = -arch arm64
|
||||
else ifeq ($(ARCH),x86_64)
|
||||
ifeq ($(HAVE_METAL),1)
|
||||
MINVERFLAGS = -mmacosx-version-min=10.13 -stdlib=libc++
|
||||
else
|
||||
MINVERFLAGS = -mmacosx-version-min=10.7 -stdlib=libc++
|
||||
endif
|
||||
ARCHFLAGS = -arch x86_64
|
||||
else ifeq ($(ARCH),x86)
|
||||
MINVERFLAGS = -mmacosx-version-min=10.6
|
||||
ARCHFLAGS = -arch x86
|
||||
else ifeq ($(ARCH),ppc)
|
||||
MINVERFLAGS = -mmacosx-version-min=10.5
|
||||
ARCHFLAGS = -arch ppc
|
||||
endif
|
||||
ifeq ($(BUILDBOT),1)
|
||||
ARCHFLAGS = -target $(LIBRETRO_APPLE_PLATFORM) -isysroot $(LIBRETRO_APPLE_ISYSROOT)
|
||||
endif
|
||||
extraflags += $(MINVERFLAGS) $(ARCHFLAGS)
|
||||
ldflags += $(MINVERFLAGS) $(ARCHFLAGS)
|
||||
else
|
||||
extra_flags += -static-libgcc -static-libstdc++
|
||||
DYLIB = dll
|
||||
endif
|
||||
|
||||
CC := $(compiler) -Wall
|
||||
CXX := $(subst CC,++,$(compiler)) -std=gnu++0x -Wall
|
||||
flags := $(CPPFLAGS) $(CFLAGS) -fPIC $(extra_flags) -I../../include
|
||||
asflags := $(ASFLAGS) -fPIC $(extra_flags)
|
||||
objects :=
|
||||
|
||||
ifeq (1,$(use_neon))
|
||||
ASMFLAGS := -INEON/asm
|
||||
asflags += -mfpu=neon
|
||||
endif
|
||||
|
||||
plugs := $(wildcard *.c)
|
||||
objects := $(plugs:.c=.o)
|
||||
targets := $(objects:.o=.$(DYLIB))
|
||||
|
||||
all: build;
|
||||
|
||||
%.o: %.S
|
||||
$(CC) -c -o $@ $(asflags) $(ASMFLAGS) $<
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c -o $@ $(flags) $<
|
||||
|
||||
%.$(DYLIB): %.o
|
||||
$(CC) -o $@ $(ldflags) $(flags) $^
|
||||
|
||||
build: $(targets)
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
rm -f *.$(DYLIB)
|
||||
|
||||
strip:
|
||||
strip -s *.$(DYLIB)
|
||||
|
||||
install:
|
||||
mkdir -p $(DESTDIR)$(INSTALLDIR)
|
||||
cp -t $(DESTDIR)$(INSTALLDIR) $(targets) *.dsp
|
||||
|
||||
test-install:
|
||||
DESTDIR=/tmp/build $(MAKE) install
|
||||
@@ -1,12 +0,0 @@
|
||||
filters = 1
|
||||
filter0 = panning
|
||||
|
||||
# Gains are linear.
|
||||
|
||||
# Stereo Mono:
|
||||
panning_left_mix = "0.5 0.5"
|
||||
panning_right_mix = "0.5 0.5"
|
||||
|
||||
# Mono on one speaker:
|
||||
# panning_left_mix = "0.5 0.5"
|
||||
# panning_right_mix = "0.0 0.0"
|
||||
@@ -1,22 +0,0 @@
|
||||
filters = 1
|
||||
filter0 = panning
|
||||
|
||||
# Gains are linear.
|
||||
|
||||
# The default. Left and right channels map to each other.
|
||||
panning_left_mix = "1.0 0.0"
|
||||
panning_right_mix = "0.0 1.0"
|
||||
|
||||
# Some examples:
|
||||
#
|
||||
# Mono:
|
||||
# panning_left_mix = "0.5 0.5"
|
||||
# panning_right_mix = "0.5 0.5"
|
||||
|
||||
# Swap left and right channels:
|
||||
# panning_left_mix = "0.0 1.0"
|
||||
# panning_right_mix = "1.0 0.0"
|
||||
#
|
||||
# Mono on one speaker:
|
||||
# panning_left_mix = "0.5 0.5"
|
||||
# panning_right_mix = "0.0 0.0"
|
||||
@@ -1,10 +0,0 @@
|
||||
filters = 1
|
||||
filter0 = phaser
|
||||
|
||||
# Defaults.
|
||||
# phaser_lfo_freq = 0.4
|
||||
# phaser_lfo_start_phase = 0.0
|
||||
# phaser_feedback = 0.0
|
||||
# phaser_depth = 0.4
|
||||
# phaser_dry_wet = 0.5
|
||||
# phaser_stages = 2
|
||||
@@ -1,9 +0,0 @@
|
||||
filters = 1
|
||||
filter0 = reverb
|
||||
|
||||
# Defaults.
|
||||
# reverb_drytime = 0.43
|
||||
# reverb_wettime = 0.4
|
||||
# reverb_damping = 0.8
|
||||
# reverb_roomwidth = 0.56
|
||||
# reverb_roomsize = 0.56
|
||||
@@ -1,6 +0,0 @@
|
||||
filters = 1
|
||||
filter0 = tremolo
|
||||
|
||||
# Defaults.
|
||||
#tremolo_frequency = 4.0
|
||||
#tremolo_depth = 0.9
|
||||
@@ -1,6 +0,0 @@
|
||||
filters = 1
|
||||
filter0 = vibrato
|
||||
|
||||
# Defaults.
|
||||
#vibrato_frequency = 5.0
|
||||
#vibrato_depth = 0.5
|
||||
@@ -1,9 +0,0 @@
|
||||
filters = 1
|
||||
filter0 = wahwah
|
||||
|
||||
# Defaults.
|
||||
# wahwah_lfo_freq = 1.5
|
||||
# wahwah_lfo_start_phase = 0.0
|
||||
# wahwah_freq_offset = 0.3
|
||||
# wahwah_depth = 0.7
|
||||
# wahwah_resonance = 2.5
|
||||
@@ -1,265 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (chorus.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 <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <libretro_dspfilter.h>
|
||||
|
||||
#define CHORUS_MAX_DELAY 4096
|
||||
#define CHORUS_DELAY_MASK (CHORUS_MAX_DELAY - 1)
|
||||
|
||||
struct chorus_data
|
||||
{
|
||||
float old[2][CHORUS_MAX_DELAY];
|
||||
/* int16 mirror of the delay line, plus a precomputed Q16 per-phase delay
|
||||
* LUT and Q16 mix gains, for the deterministic int16 path. */
|
||||
int16_t old_i[2][CHORUS_MAX_DELAY];
|
||||
int32_t *delay_lut;
|
||||
int32_t mix_dry_i;
|
||||
int32_t mix_wet_i;
|
||||
float delay;
|
||||
float depth;
|
||||
float input_rate;
|
||||
float mix_dry;
|
||||
float mix_wet;
|
||||
unsigned old_ptr;
|
||||
unsigned lfo_ptr;
|
||||
unsigned lfo_period;
|
||||
};
|
||||
|
||||
static void chorus_free(void *data)
|
||||
{
|
||||
if (data)
|
||||
{
|
||||
struct chorus_data *ch = (struct chorus_data*)data;
|
||||
free(ch->delay_lut);
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
static void chorus_process(void *data, struct dspfilter_output *output,
|
||||
const struct dspfilter_input *input)
|
||||
{
|
||||
unsigned i;
|
||||
float *out = NULL;
|
||||
struct chorus_data *ch = (struct chorus_data*)data;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
out = output->samples;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
unsigned delay_int;
|
||||
float delay_frac, l_a, l_b, r_a, r_b;
|
||||
float chorus_l, chorus_r;
|
||||
float in[2] = { out[0], out[1] };
|
||||
float delay = ch->delay + ch->depth * sin((2.0 * M_PI * ch->lfo_ptr++) / ch->lfo_period);
|
||||
|
||||
delay *= ch->input_rate;
|
||||
if (ch->lfo_ptr >= ch->lfo_period)
|
||||
ch->lfo_ptr = 0;
|
||||
|
||||
delay_int = (unsigned)delay;
|
||||
|
||||
if (delay_int >= CHORUS_MAX_DELAY - 1)
|
||||
delay_int = CHORUS_MAX_DELAY - 2;
|
||||
|
||||
delay_frac = delay - delay_int;
|
||||
|
||||
ch->old[0][ch->old_ptr] = in[0];
|
||||
ch->old[1][ch->old_ptr] = in[1];
|
||||
|
||||
l_a = ch->old[0][(ch->old_ptr - delay_int - 0) & CHORUS_DELAY_MASK];
|
||||
l_b = ch->old[0][(ch->old_ptr - delay_int - 1) & CHORUS_DELAY_MASK];
|
||||
r_a = ch->old[1][(ch->old_ptr - delay_int - 0) & CHORUS_DELAY_MASK];
|
||||
r_b = ch->old[1][(ch->old_ptr - delay_int - 1) & CHORUS_DELAY_MASK];
|
||||
|
||||
/* Lerp introduces aliasing of the chorus component,
|
||||
* but doing full polyphase here is probably overkill. */
|
||||
chorus_l = l_a * (1.0f - delay_frac) + l_b * delay_frac;
|
||||
chorus_r = r_a * (1.0f - delay_frac) + r_b * delay_frac;
|
||||
|
||||
out[0] = ch->mix_dry * in[0] + ch->mix_wet * chorus_l;
|
||||
out[1] = ch->mix_dry * in[1] + ch->mix_wet * chorus_r;
|
||||
|
||||
ch->old_ptr = (ch->old_ptr + 1) & CHORUS_DELAY_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
/* Deterministic int16 path: same modulated fractional-delay chorus as
|
||||
* chorus_process(), but the LFO delay is read from a precomputed Q16
|
||||
* per-phase LUT, the delay line is int16, and the linear interpolation and
|
||||
* dry/wet mix are done in Q16 with int64 accumulation and s16 saturation. */
|
||||
static void chorus_process_i16(void *data,
|
||||
struct dspfilter_output_i16 *output,
|
||||
const struct dspfilter_input_i16 *input)
|
||||
{
|
||||
unsigned i;
|
||||
int16_t *out = NULL;
|
||||
struct chorus_data *ch = (struct chorus_data*)data;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
out = output->samples;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
unsigned delay_int;
|
||||
int32_t frac_q16;
|
||||
int16_t l_a, l_b, r_a, r_b;
|
||||
int64_t chorus_l, chorus_r, acc;
|
||||
int32_t v;
|
||||
int16_t in0 = out[0];
|
||||
int16_t in1 = out[1];
|
||||
int32_t delay_q16 = ch->delay_lut[ch->lfo_ptr++];
|
||||
|
||||
if (ch->lfo_ptr >= ch->lfo_period)
|
||||
ch->lfo_ptr = 0;
|
||||
|
||||
delay_int = (unsigned)(delay_q16 >> 16);
|
||||
if (delay_int >= CHORUS_MAX_DELAY - 1)
|
||||
delay_int = CHORUS_MAX_DELAY - 2;
|
||||
frac_q16 = delay_q16 - (int32_t)(delay_int << 16);
|
||||
|
||||
ch->old_i[0][ch->old_ptr] = in0;
|
||||
ch->old_i[1][ch->old_ptr] = in1;
|
||||
|
||||
l_a = ch->old_i[0][(ch->old_ptr - delay_int - 0) & CHORUS_DELAY_MASK];
|
||||
l_b = ch->old_i[0][(ch->old_ptr - delay_int - 1) & CHORUS_DELAY_MASK];
|
||||
r_a = ch->old_i[1][(ch->old_ptr - delay_int - 0) & CHORUS_DELAY_MASK];
|
||||
r_b = ch->old_i[1][(ch->old_ptr - delay_int - 1) & CHORUS_DELAY_MASK];
|
||||
|
||||
/* chorus = lerp(a, b, frac), kept in Q16 (value << 16). */
|
||||
chorus_l = (int64_t)l_a * (65536 - frac_q16) + (int64_t)l_b * frac_q16;
|
||||
chorus_r = (int64_t)r_a * (65536 - frac_q16) + (int64_t)r_b * frac_q16;
|
||||
|
||||
/* out = mix_dry*in + mix_wet*chorus, all in Q16, then round + saturate.
|
||||
* The wet term folds the Q16 chorus and the Q16 gain: (gain*chorus)>>16. */
|
||||
acc = (int64_t)ch->mix_dry_i * in0
|
||||
+ ((int64_t)ch->mix_wet_i * chorus_l >> 16);
|
||||
v = (acc >= 0) ? (int32_t)(( acc + 32768) >> 16)
|
||||
: -(int32_t)((-acc + 32768) >> 16);
|
||||
if (v > 32767) v = 32767;
|
||||
else if (v < -32768) v = -32768;
|
||||
out[0] = (int16_t)v;
|
||||
|
||||
acc = (int64_t)ch->mix_dry_i * in1
|
||||
+ ((int64_t)ch->mix_wet_i * chorus_r >> 16);
|
||||
v = (acc >= 0) ? (int32_t)(( acc + 32768) >> 16)
|
||||
: -(int32_t)((-acc + 32768) >> 16);
|
||||
if (v > 32767) v = 32767;
|
||||
else if (v < -32768) v = -32768;
|
||||
out[1] = (int16_t)v;
|
||||
|
||||
ch->old_ptr = (ch->old_ptr + 1) & CHORUS_DELAY_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
static void *chorus_init(const struct dspfilter_info *info,
|
||||
const struct dspfilter_config *config, void *userdata)
|
||||
{
|
||||
float delay, depth, lfo_freq, drywet;
|
||||
struct chorus_data *ch = (struct chorus_data*)calloc(1, sizeof(*ch));
|
||||
if (!ch)
|
||||
return NULL;
|
||||
|
||||
config->get_float(userdata, "delay_ms", &delay, 25.0f);
|
||||
config->get_float(userdata, "depth_ms", &depth, 1.0f);
|
||||
config->get_float(userdata, "lfo_freq", &lfo_freq, 0.5f);
|
||||
config->get_float(userdata, "drywet", &drywet, 0.8f);
|
||||
|
||||
delay /= 1000.0f;
|
||||
depth /= 1000.0f;
|
||||
|
||||
if (depth > delay)
|
||||
depth = delay;
|
||||
|
||||
if (drywet < 0.0f)
|
||||
drywet = 0.0f;
|
||||
else if (drywet > 1.0f)
|
||||
drywet = 1.0f;
|
||||
|
||||
ch->mix_dry = 1.0f - 0.5f * drywet;
|
||||
ch->mix_wet = 0.5f * drywet;
|
||||
|
||||
ch->delay = delay;
|
||||
ch->depth = depth;
|
||||
ch->lfo_period = (1.0f / lfo_freq) * info->input_rate;
|
||||
ch->input_rate = info->input_rate;
|
||||
if (!ch->lfo_period)
|
||||
ch->lfo_period = 1;
|
||||
|
||||
/* Precompute the Q16 per-phase delay LUT (in samples) and Q16 mix gains
|
||||
* for the int16 path: one full LFO period of the same double-precision
|
||||
* sine the float path uses, so no FPU is needed at run time. */
|
||||
ch->mix_dry_i = (int32_t)floor((double)ch->mix_dry * 65536.0 + 0.5);
|
||||
ch->mix_wet_i = (int32_t)floor((double)ch->mix_wet * 65536.0 + 0.5);
|
||||
ch->delay_lut = (int32_t*)malloc(ch->lfo_period * sizeof(int32_t));
|
||||
if (!ch->delay_lut)
|
||||
{
|
||||
chorus_free(ch);
|
||||
return NULL;
|
||||
}
|
||||
{
|
||||
unsigned p;
|
||||
for (p = 0; p < ch->lfo_period; p++)
|
||||
{
|
||||
double d = (ch->delay + ch->depth *
|
||||
sin((2.0 * M_PI * p) / ch->lfo_period)) * ch->input_rate;
|
||||
double di = floor(d); /* matches float (unsigned)delay */
|
||||
int32_t ip = (int32_t)di;
|
||||
int32_t fp = (int32_t)floor((d - di) * 65536.0 + 0.5);
|
||||
if (fp > 65535) /* rounding carried into next int */
|
||||
{
|
||||
fp -= 65536;
|
||||
ip += 1;
|
||||
}
|
||||
ch->delay_lut[p] = (ip << 16) | (fp & 0xFFFF);
|
||||
}
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
|
||||
static const struct dspfilter_implementation chorus_plug = {
|
||||
chorus_init,
|
||||
chorus_process,
|
||||
chorus_free,
|
||||
|
||||
DSPFILTER_API_VERSION,
|
||||
"Chorus",
|
||||
"chorus",
|
||||
|
||||
chorus_process_i16,
|
||||
};
|
||||
|
||||
#ifdef HAVE_FILTERS_BUILTIN
|
||||
#define dspfilter_get_implementation chorus_dspfilter_get_implementation
|
||||
#endif
|
||||
|
||||
const struct dspfilter_implementation *
|
||||
dspfilter_get_implementation(dspfilter_simd_mask_t mask) { return &chorus_plug; }
|
||||
|
||||
#undef dspfilter_get_implementation
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
PACKAGE_NAME=retroarch-filters-audio
|
||||
@@ -1,129 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (echo.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 <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <libretro_dspfilter.h>
|
||||
|
||||
struct delta_data
|
||||
{
|
||||
float intensity;
|
||||
float old[2];
|
||||
/* Q16 fixed-point mirror of intensity, and int16 history, for the
|
||||
* int16 path. */
|
||||
int32_t intensity_i;
|
||||
int32_t old_i[2];
|
||||
};
|
||||
|
||||
static void delta_free(void *data)
|
||||
{
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void delta_process(void *data, struct dspfilter_output *output,
|
||||
const struct dspfilter_input *input)
|
||||
{
|
||||
unsigned i, c;
|
||||
struct delta_data *d = (struct delta_data*)data;
|
||||
float *out = output->samples;
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
|
||||
for (i = 0; i < input->frames; i++)
|
||||
{
|
||||
for (c = 0; c < 2; c++)
|
||||
{
|
||||
float current = *out;
|
||||
*out++ = current + (current - d->old[c]) * d->intensity;
|
||||
d->old[c] = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Deterministic int16 path: same first-difference sharpening as
|
||||
* delta_process() but in Q16 fixed point (int64 term, round-half-away-from-
|
||||
* zero, saturate). The float path leaves clamping to the host; writing int16
|
||||
* directly, this saturates to the s16 range. */
|
||||
static void delta_process_i16(void *data,
|
||||
struct dspfilter_output_i16 *output,
|
||||
const struct dspfilter_input_i16 *input)
|
||||
{
|
||||
unsigned i, c;
|
||||
struct delta_data *d = (struct delta_data*)data;
|
||||
int16_t *out;
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
out = output->samples;
|
||||
|
||||
for (i = 0; i < input->frames; i++)
|
||||
{
|
||||
for (c = 0; c < 2; c++)
|
||||
{
|
||||
int32_t current = *out;
|
||||
int32_t diff = current - d->old_i[c];
|
||||
int64_t term = (int64_t)diff * d->intensity_i;
|
||||
int32_t term_r = (term >= 0)
|
||||
? (int32_t)(( term + 32768) >> 16)
|
||||
: -(int32_t)((-term + 32768) >> 16);
|
||||
int32_t v = current + term_r;
|
||||
if (v > 32767) v = 32767;
|
||||
else if (v < -32768) v = -32768;
|
||||
*out++ = (int16_t)v;
|
||||
d->old_i[c] = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void *delta_init(const struct dspfilter_info *info,
|
||||
const struct dspfilter_config *config, void *userdata)
|
||||
{
|
||||
struct delta_data *d = (struct delta_data*)calloc(1, sizeof(*d));
|
||||
if (!d)
|
||||
return NULL;
|
||||
config->get_float(userdata, "intensity", &d->intensity, 5.0f);
|
||||
d->intensity_i = (int32_t)floor((double)d->intensity * 65536.0 + 0.5);
|
||||
return d;
|
||||
}
|
||||
|
||||
static const struct dspfilter_implementation delta_plug = {
|
||||
delta_init,
|
||||
delta_process,
|
||||
delta_free,
|
||||
DSPFILTER_API_VERSION,
|
||||
"Delta Sharpening",
|
||||
"crystalizer",
|
||||
delta_process_i16,
|
||||
};
|
||||
|
||||
#ifdef HAVE_FILTERS_BUILTIN
|
||||
#define dspfilter_get_implementation delta_dspfilter_get_implementation
|
||||
#endif
|
||||
|
||||
const struct dspfilter_implementation *dspfilter_get_implementation(dspfilter_simd_mask_t mask)
|
||||
{
|
||||
return &delta_plug;
|
||||
}
|
||||
|
||||
#undef dspfilter_get_implementation
|
||||
@@ -1,265 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (echo.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <libretro_dspfilter.h>
|
||||
|
||||
struct echo_channel
|
||||
{
|
||||
float *buffer;
|
||||
/* int64 Q16 mirror of the delay line (headroom + fractional precision so
|
||||
* the feedback loop neither clips nor quantizes), and a Q16 feedback gain,
|
||||
* for the deterministic int16 path. */
|
||||
int64_t *buffer_i;
|
||||
int32_t feedback_q;
|
||||
unsigned ptr;
|
||||
unsigned frames;
|
||||
float feedback;
|
||||
};
|
||||
|
||||
struct echo_data
|
||||
{
|
||||
struct echo_channel *channels;
|
||||
unsigned num_channels;
|
||||
float amp;
|
||||
int32_t amp_q; /* Q16 mirror of amp */
|
||||
};
|
||||
|
||||
static void echo_free(void *data)
|
||||
{
|
||||
unsigned i;
|
||||
struct echo_data *echo = (struct echo_data*)data;
|
||||
|
||||
for (i = 0; i < echo->num_channels; i++)
|
||||
{
|
||||
free(echo->channels[i].buffer);
|
||||
free(echo->channels[i].buffer_i);
|
||||
}
|
||||
free(echo->channels);
|
||||
free(echo);
|
||||
}
|
||||
|
||||
static void echo_process(void *data, struct dspfilter_output *output,
|
||||
const struct dspfilter_input *input)
|
||||
{
|
||||
unsigned i, c;
|
||||
float *out = NULL;
|
||||
struct echo_data *echo = (struct echo_data*)data;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
|
||||
out = output->samples;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
float left, right;
|
||||
float echo_left = 0.0f;
|
||||
float echo_right = 0.0f;
|
||||
|
||||
for (c = 0; c < echo->num_channels; c++)
|
||||
{
|
||||
echo_left += echo->channels[c].buffer[(echo->channels[c].ptr << 1) + 0];
|
||||
echo_right += echo->channels[c].buffer[(echo->channels[c].ptr << 1) + 1];
|
||||
}
|
||||
|
||||
echo_left *= echo->amp;
|
||||
echo_right *= echo->amp;
|
||||
|
||||
left = out[0] + echo_left;
|
||||
right = out[1] + echo_right;
|
||||
|
||||
for (c = 0; c < echo->num_channels; c++)
|
||||
{
|
||||
float feedback_left = out[0] + echo->channels[c].feedback * echo_left;
|
||||
float feedback_right = out[1] + echo->channels[c].feedback * echo_right;
|
||||
|
||||
echo->channels[c].buffer[(echo->channels[c].ptr << 1) + 0] = feedback_left;
|
||||
echo->channels[c].buffer[(echo->channels[c].ptr << 1) + 1] = feedback_right;
|
||||
|
||||
echo->channels[c].ptr = (echo->channels[c].ptr + 1) % echo->channels[c].frames;
|
||||
}
|
||||
|
||||
out[0] = left;
|
||||
out[1] = right;
|
||||
}
|
||||
}
|
||||
|
||||
/* Deterministic int16 path: same multi-tap feedback echo as echo_process(),
|
||||
* with the delay lines and accumulation in Q16 fixed point (int64), the amp
|
||||
* and per-tap feedback gains as Q16, and round-half-away-from-zero on every
|
||||
* narrowing shift. Only the final dry+echo sum is saturated to s16. */
|
||||
static void echo_process_i16(void *data,
|
||||
struct dspfilter_output_i16 *output,
|
||||
const struct dspfilter_input_i16 *input)
|
||||
{
|
||||
unsigned i, c;
|
||||
int16_t *out = NULL;
|
||||
struct echo_data *echo = (struct echo_data*)data;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
out = output->samples;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
int32_t in0 = out[0];
|
||||
int32_t in1 = out[1];
|
||||
int64_t echo_l = 0; /* Q16 */
|
||||
int64_t echo_r = 0; /* Q16 */
|
||||
int64_t p, left, right;
|
||||
int32_t v;
|
||||
|
||||
for (c = 0; c < echo->num_channels; c++)
|
||||
{
|
||||
echo_l += echo->channels[c].buffer_i[(echo->channels[c].ptr << 1) + 0];
|
||||
echo_r += echo->channels[c].buffer_i[(echo->channels[c].ptr << 1) + 1];
|
||||
}
|
||||
|
||||
/* echo *= amp (Q16 * Q16 -> Q16). */
|
||||
p = echo_l * echo->amp_q;
|
||||
echo_l = (p >= 0) ? ((p + 32768) >> 16) : -(((-p) + 32768) >> 16);
|
||||
p = echo_r * echo->amp_q;
|
||||
echo_r = (p >= 0) ? ((p + 32768) >> 16) : -(((-p) + 32768) >> 16);
|
||||
|
||||
left = (int64_t)in0 * 65536 + echo_l; /* Q16 */
|
||||
right = (int64_t)in1 * 65536 + echo_r;
|
||||
|
||||
for (c = 0; c < echo->num_channels; c++)
|
||||
{
|
||||
int32_t fbq = echo->channels[c].feedback_q;
|
||||
int64_t fl = echo_l * fbq; /* Q32 */
|
||||
int64_t fr = echo_r * fbq;
|
||||
fl = (fl >= 0) ? ((fl + 32768) >> 16) : -(((-fl) + 32768) >> 16); /* Q16 */
|
||||
fr = (fr >= 0) ? ((fr + 32768) >> 16) : -(((-fr) + 32768) >> 16);
|
||||
|
||||
echo->channels[c].buffer_i[(echo->channels[c].ptr << 1) + 0] =
|
||||
(int64_t)in0 * 65536 + fl;
|
||||
echo->channels[c].buffer_i[(echo->channels[c].ptr << 1) + 1] =
|
||||
(int64_t)in1 * 65536 + fr;
|
||||
|
||||
echo->channels[c].ptr =
|
||||
(echo->channels[c].ptr + 1) % echo->channels[c].frames;
|
||||
}
|
||||
|
||||
v = (left >= 0) ? (int32_t)(( left + 32768) >> 16)
|
||||
: -(int32_t)((-left + 32768) >> 16);
|
||||
if (v > 32767) v = 32767;
|
||||
else if (v < -32768) v = -32768;
|
||||
out[0] = (int16_t)v;
|
||||
|
||||
v = (right >= 0) ? (int32_t)(( right + 32768) >> 16)
|
||||
: -(int32_t)((-right + 32768) >> 16);
|
||||
if (v > 32767) v = 32767;
|
||||
else if (v < -32768) v = -32768;
|
||||
out[1] = (int16_t)v;
|
||||
}
|
||||
}
|
||||
|
||||
static void *echo_init(const struct dspfilter_info *info,
|
||||
const struct dspfilter_config *config, void *userdata)
|
||||
{
|
||||
unsigned i, channels;
|
||||
struct echo_channel *echo_channels = NULL;
|
||||
float *delay = NULL;
|
||||
float *feedback = NULL;
|
||||
unsigned num_delay = 0;
|
||||
unsigned num_feedback = 0;
|
||||
|
||||
static const float default_delay[] = { 200.0f };
|
||||
static const float default_feedback[] = { 0.5f };
|
||||
struct echo_data *echo = (struct echo_data*)
|
||||
calloc(1, sizeof(*echo));
|
||||
|
||||
if (!echo)
|
||||
return NULL;
|
||||
|
||||
config->get_float_array(userdata, "delay", &delay,
|
||||
&num_delay, default_delay, 1);
|
||||
config->get_float_array(userdata, "feedback", &feedback,
|
||||
&num_feedback, default_feedback, 1);
|
||||
config->get_float(userdata, "amp", &echo->amp, 0.2f);
|
||||
echo->amp_q = (int32_t)floor((double)echo->amp * 65536.0 + 0.5);
|
||||
|
||||
channels = num_feedback = num_delay = MIN(num_delay, num_feedback);
|
||||
|
||||
if (!(echo_channels = (struct echo_channel*)calloc(channels,
|
||||
sizeof(*echo_channels))))
|
||||
goto error;
|
||||
|
||||
echo->channels = echo_channels;
|
||||
echo->num_channels = channels;
|
||||
|
||||
for (i = 0; i < channels; i++)
|
||||
{
|
||||
unsigned frames = (unsigned)(delay[i] * info->input_rate / 1000.0f + 0.5f);
|
||||
if (!frames)
|
||||
goto error;
|
||||
|
||||
if (!(echo->channels[i].buffer = (float*)calloc(frames, 2 * sizeof(float))))
|
||||
goto error;
|
||||
|
||||
if (!(echo->channels[i].buffer_i = (int64_t*)calloc(frames, 2 * sizeof(int64_t))))
|
||||
goto error;
|
||||
|
||||
echo->channels[i].frames = frames;
|
||||
echo->channels[i].feedback = feedback[i];
|
||||
echo->channels[i].feedback_q =
|
||||
(int32_t)floor((double)feedback[i] * 65536.0 + 0.5);
|
||||
}
|
||||
|
||||
config->free(delay);
|
||||
config->free(feedback);
|
||||
return echo;
|
||||
|
||||
error:
|
||||
config->free(delay);
|
||||
config->free(feedback);
|
||||
echo_free(echo);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct dspfilter_implementation echo_plug = {
|
||||
echo_init,
|
||||
echo_process,
|
||||
echo_free,
|
||||
|
||||
DSPFILTER_API_VERSION,
|
||||
"Multi-Echo",
|
||||
"echo",
|
||||
|
||||
echo_process_i16,
|
||||
};
|
||||
|
||||
#ifdef HAVE_FILTERS_BUILTIN
|
||||
#define dspfilter_get_implementation echo_dspfilter_get_implementation
|
||||
#endif
|
||||
|
||||
const struct dspfilter_implementation *dspfilter_get_implementation(dspfilter_simd_mask_t mask)
|
||||
{
|
||||
return &echo_plug;
|
||||
}
|
||||
|
||||
#undef dspfilter_get_implementation
|
||||
@@ -1,418 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (eq.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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <retro_inline.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <filters.h>
|
||||
#include <libretro_dspfilter.h>
|
||||
|
||||
#include "fft/fft.c"
|
||||
|
||||
struct eq_data
|
||||
{
|
||||
fft_t *fft;
|
||||
float *save;
|
||||
float *block;
|
||||
fft_complex_t *filter;
|
||||
fft_complex_t *fftblock;
|
||||
float buffer[8 * 1024];
|
||||
/* Scratch for the int16 bridge: converted float input (one block at a
|
||||
* time) and the s16 image of the float output block. */
|
||||
float scratch_in[8 * 1024];
|
||||
int16_t buffer_i16[8 * 1024];
|
||||
unsigned block_size;
|
||||
unsigned block_ptr;
|
||||
};
|
||||
|
||||
struct eq_gain
|
||||
{
|
||||
float freq;
|
||||
float gain; /* Linear. */
|
||||
};
|
||||
|
||||
static void eq_free(void *data)
|
||||
{
|
||||
struct eq_data *eq = (struct eq_data*)data;
|
||||
if (!eq)
|
||||
return;
|
||||
|
||||
fft_free(eq->fft);
|
||||
free(eq->save);
|
||||
free(eq->block);
|
||||
free(eq->fftblock);
|
||||
free(eq->filter);
|
||||
free(eq);
|
||||
}
|
||||
|
||||
static void eq_process(void *data, struct dspfilter_output *output,
|
||||
const struct dspfilter_input *input)
|
||||
{
|
||||
float *out;
|
||||
const float *in;
|
||||
unsigned input_frames;
|
||||
struct eq_data *eq = (struct eq_data*)data;
|
||||
|
||||
output->samples = eq->buffer;
|
||||
output->frames = 0;
|
||||
|
||||
out = eq->buffer;
|
||||
in = input->samples;
|
||||
input_frames = input->frames;
|
||||
|
||||
while (input_frames)
|
||||
{
|
||||
unsigned write_avail = eq->block_size - eq->block_ptr;
|
||||
|
||||
if (input_frames < write_avail)
|
||||
write_avail = input_frames;
|
||||
|
||||
memcpy(eq->block + eq->block_ptr * 2, in, write_avail * 2 * sizeof(float));
|
||||
|
||||
in += write_avail * 2;
|
||||
input_frames -= write_avail;
|
||||
eq->block_ptr += write_avail;
|
||||
|
||||
/* Convolve a new block. */
|
||||
if (eq->block_ptr == eq->block_size)
|
||||
{
|
||||
unsigned i, c;
|
||||
|
||||
for (c = 0; c < 2; c++)
|
||||
{
|
||||
fft_process_forward(eq->fft, eq->fftblock, eq->block + c, 2);
|
||||
for (i = 0; i < 2 * eq->block_size; i++)
|
||||
eq->fftblock[i] = fft_complex_mul(eq->fftblock[i], eq->filter[i]);
|
||||
fft_process_inverse(eq->fft, out + c, eq->fftblock, 2);
|
||||
}
|
||||
|
||||
/* Overlap add method, so add in saved block now. */
|
||||
for (i = 0; i < 2 * eq->block_size; i++)
|
||||
out[i] += eq->save[i];
|
||||
|
||||
/* Save block for later. */
|
||||
memcpy(eq->save, out + 2 * eq->block_size, 2 * eq->block_size * sizeof(float));
|
||||
|
||||
out += eq->block_size * 2;
|
||||
output->frames += eq->block_size;
|
||||
eq->block_ptr = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* int16 entry point: bridge through the unmodified float eq_process so an
|
||||
* int16 chain containing the equalizer stays on the deterministic s16 path.
|
||||
* A fixed-point FFT would be large and precision-losing for narrow benefit,
|
||||
* so the game signal is converted here (s16 <-> the host's normalized [-1,1]
|
||||
* float domain) around the existing FFT overlap-add convolution. Input is
|
||||
* fed one block at a time so a single eq_process call can never emit more
|
||||
* than one block into eq->buffer, and each emitted block is converted (round
|
||||
* half away from zero, saturate) into eq->buffer_i16 before the next call
|
||||
* overwrites eq->buffer. Filter state persists across the per-block calls,
|
||||
* so the result matches a single call over the whole input. */
|
||||
static void eq_process_i16(void *data, struct dspfilter_output_i16 *output,
|
||||
const struct dspfilter_input_i16 *input)
|
||||
{
|
||||
struct eq_data *eq = (struct eq_data*)data;
|
||||
const int16_t *in = input->samples;
|
||||
unsigned remaining = input->frames;
|
||||
unsigned out_frames = 0;
|
||||
const unsigned cap = (unsigned)(sizeof(eq->buffer_i16) / (2 * sizeof(int16_t)));
|
||||
|
||||
output->samples = eq->buffer_i16;
|
||||
|
||||
while (remaining)
|
||||
{
|
||||
unsigned j, kf;
|
||||
struct dspfilter_input fin;
|
||||
struct dspfilter_output fout;
|
||||
unsigned chunk = remaining;
|
||||
|
||||
if (chunk > eq->block_size)
|
||||
chunk = eq->block_size;
|
||||
|
||||
for (j = 0; j < chunk * 2; j++)
|
||||
eq->scratch_in[j] = (float)in[j] * (1.0f / 32768.0f);
|
||||
|
||||
fin.samples = eq->scratch_in;
|
||||
fin.frames = chunk;
|
||||
eq_process(eq, &fout, &fin);
|
||||
|
||||
for (kf = 0; kf < fout.frames && out_frames < cap; kf++, out_frames++)
|
||||
{
|
||||
int32_t v;
|
||||
float l = fout.samples[2 * kf + 0];
|
||||
float r = fout.samples[2 * kf + 1];
|
||||
|
||||
v = (l >= 0.0f) ? (int32_t)(l * 32768.0f + 0.5f)
|
||||
: (int32_t)(l * 32768.0f - 0.5f);
|
||||
if (v > 32767) v = 32767;
|
||||
else if (v < -32768) v = -32768;
|
||||
eq->buffer_i16[2 * out_frames + 0] = (int16_t)v;
|
||||
|
||||
v = (r >= 0.0f) ? (int32_t)(r * 32768.0f + 0.5f)
|
||||
: (int32_t)(r * 32768.0f - 0.5f);
|
||||
if (v > 32767) v = 32767;
|
||||
else if (v < -32768) v = -32768;
|
||||
eq->buffer_i16[2 * out_frames + 1] = (int16_t)v;
|
||||
}
|
||||
|
||||
in += chunk * 2;
|
||||
remaining -= chunk;
|
||||
}
|
||||
|
||||
output->frames = out_frames;
|
||||
}
|
||||
|
||||
static int gains_cmp(const void *a_, const void *b_)
|
||||
{
|
||||
const struct eq_gain *a = (const struct eq_gain*)a_;
|
||||
const struct eq_gain *b = (const struct eq_gain*)b_;
|
||||
if (a->freq < b->freq)
|
||||
return -1;
|
||||
if (a->freq > b->freq)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void generate_response(fft_complex_t *response,
|
||||
const struct eq_gain *gains, unsigned num_gains, unsigned samples)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
float start_freq = 0.0f;
|
||||
float start_gain = 1.0f;
|
||||
|
||||
float end_freq = 1.0f;
|
||||
float end_gain = 1.0f;
|
||||
|
||||
if (num_gains)
|
||||
{
|
||||
end_freq = gains->freq;
|
||||
end_gain = gains->gain;
|
||||
num_gains--;
|
||||
gains++;
|
||||
}
|
||||
|
||||
/* Create a response by linear interpolation between
|
||||
* known frequency sample points. */
|
||||
for (i = 0; i <= samples; i++)
|
||||
{
|
||||
float gain;
|
||||
float lerp = 0.5f;
|
||||
float freq = (float)i / samples;
|
||||
|
||||
while (freq >= end_freq)
|
||||
{
|
||||
if (num_gains)
|
||||
{
|
||||
start_freq = end_freq;
|
||||
start_gain = end_gain;
|
||||
end_freq = gains->freq;
|
||||
end_gain = gains->gain;
|
||||
|
||||
gains++;
|
||||
num_gains--;
|
||||
}
|
||||
else
|
||||
{
|
||||
start_freq = end_freq;
|
||||
start_gain = end_gain;
|
||||
end_freq = 1.0f;
|
||||
end_gain = 1.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Edge case where i == samples. */
|
||||
if (end_freq > start_freq)
|
||||
lerp = (freq - start_freq) / (end_freq - start_freq);
|
||||
gain = (1.0f - lerp) * start_gain + lerp * end_gain;
|
||||
|
||||
response[i].real = gain;
|
||||
response[i].imag = 0.0f;
|
||||
response[2 * samples - i].real = gain;
|
||||
response[2 * samples - i].imag = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void create_filter(struct eq_data *eq, unsigned size_log2,
|
||||
struct eq_gain *gains, unsigned num_gains, double beta, const char *filter_path)
|
||||
{
|
||||
int i;
|
||||
int half_block_size = eq->block_size >> 1;
|
||||
double window_mod = 1.0 / kaiser_window_function(0.0, beta);
|
||||
fft_t *fft = fft_new(size_log2);
|
||||
float *time_filter = (float*)calloc(eq->block_size * 2 + 1, sizeof(*time_filter));
|
||||
if (!fft || !time_filter)
|
||||
goto end;
|
||||
|
||||
/* Make sure bands are in correct order. */
|
||||
qsort(gains, num_gains, sizeof(*gains), gains_cmp);
|
||||
|
||||
/* Compute desired filter response. */
|
||||
generate_response(eq->filter, gains, num_gains, half_block_size);
|
||||
|
||||
/* Get equivalent time-domain filter. */
|
||||
fft_process_inverse(fft, time_filter, eq->filter, 1);
|
||||
|
||||
/* ifftshift() to create the correct linear phase filter.
|
||||
* The filter response was designed with zero phase, which
|
||||
* won't work unless we compensate
|
||||
* for the repeating property of the FFT here
|
||||
* by flipping left and right blocks. */
|
||||
for (i = 0; i < half_block_size; i++)
|
||||
{
|
||||
float tmp = time_filter[i + half_block_size];
|
||||
time_filter[i + half_block_size] = time_filter[i];
|
||||
time_filter[i] = tmp;
|
||||
}
|
||||
|
||||
/* Apply a window to smooth out the frequency response. */
|
||||
for (i = 0; i < (int)eq->block_size; i++)
|
||||
{
|
||||
/* Kaiser window. */
|
||||
double phase = (double)i / eq->block_size;
|
||||
phase = 2.0 * (phase - 0.5);
|
||||
time_filter[i] *= window_mod * kaiser_window_function(phase, beta);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
/* Debugging. */
|
||||
if (filter_path)
|
||||
{
|
||||
FILE *file = fopen(filter_path, "w");
|
||||
if (file)
|
||||
{
|
||||
for (i = 0; i < (int)eq->block_size - 1; i++)
|
||||
fprintf(file, "%.8f\n", time_filter[i + 1]);
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Padded FFT to create our FFT filter.
|
||||
* Make our even-length filter odd by discarding the first coefficient.
|
||||
* For some interesting reason, this allows us to design an odd-length linear phase filter.
|
||||
*/
|
||||
fft_process_forward(eq->fft, eq->filter, time_filter + 1, 1);
|
||||
|
||||
end:
|
||||
fft_free(fft);
|
||||
free(time_filter);
|
||||
}
|
||||
|
||||
static void *eq_init(const struct dspfilter_info *info,
|
||||
const struct dspfilter_config *config, void *userdata)
|
||||
{
|
||||
int size_log2;
|
||||
float beta;
|
||||
float *frequencies, *gain;
|
||||
unsigned num_freq, num_gain, i, size;
|
||||
struct eq_gain *gains = NULL;
|
||||
char *filter_path = NULL;
|
||||
const float default_freq[] = { 0.0f, info->input_rate };
|
||||
const float default_gain[] = { 0.0f, 0.0f };
|
||||
struct eq_data *eq = (struct eq_data*)calloc(1, sizeof(*eq));
|
||||
if (!eq)
|
||||
return NULL;
|
||||
|
||||
config->get_float(userdata, "window_beta", &beta, 4.0f);
|
||||
|
||||
config->get_int(userdata, "block_size_log2", &size_log2, 8);
|
||||
size = 1 << size_log2;
|
||||
|
||||
config->get_float_array(userdata, "frequencies", &frequencies, &num_freq, default_freq, 2);
|
||||
config->get_float_array(userdata, "gains", &gain, &num_gain, default_gain, 2);
|
||||
|
||||
if (!config->get_string(userdata, "impulse_response_output", &filter_path, ""))
|
||||
{
|
||||
config->free(filter_path);
|
||||
filter_path = NULL;
|
||||
}
|
||||
|
||||
num_gain = num_freq = MIN(num_gain, num_freq);
|
||||
|
||||
if (!(gains = (struct eq_gain*)calloc(num_gain, sizeof(*gains))))
|
||||
goto error;
|
||||
|
||||
for (i = 0; i < num_gain; i++)
|
||||
{
|
||||
gains[i].freq = frequencies[i] / (0.5f * info->input_rate);
|
||||
gains[i].gain = pow(10.0, gain[i] / 20.0);
|
||||
}
|
||||
config->free(frequencies);
|
||||
config->free(gain);
|
||||
|
||||
eq->block_size = size;
|
||||
|
||||
eq->save = (float*)calloc( size, 2 * sizeof(*eq->save));
|
||||
eq->block = (float*)calloc(2 * size, 2 * sizeof(*eq->block));
|
||||
eq->fftblock = (fft_complex_t*)calloc(2 * size, sizeof(*eq->fftblock));
|
||||
eq->filter = (fft_complex_t*)calloc(2 * size, sizeof(*eq->filter));
|
||||
|
||||
/* Use an FFT which is twice the block size with zero-padding
|
||||
* to make circular convolution => proper convolution.
|
||||
*/
|
||||
eq->fft = fft_new(size_log2 + 1);
|
||||
|
||||
if (!eq->fft || !eq->fftblock || !eq->save || !eq->block || !eq->filter)
|
||||
goto error;
|
||||
|
||||
create_filter(eq, size_log2, gains, num_gain, beta, filter_path);
|
||||
config->free(filter_path);
|
||||
filter_path = NULL;
|
||||
|
||||
free(gains);
|
||||
return eq;
|
||||
|
||||
error:
|
||||
free(gains);
|
||||
eq_free(eq);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct dspfilter_implementation eq_plug = {
|
||||
eq_init,
|
||||
eq_process,
|
||||
eq_free,
|
||||
|
||||
DSPFILTER_API_VERSION,
|
||||
"Linear-Phase FFT Equalizer",
|
||||
"eq",
|
||||
|
||||
eq_process_i16,
|
||||
};
|
||||
|
||||
#ifdef HAVE_FILTERS_BUILTIN
|
||||
#define dspfilter_get_implementation eq_dspfilter_get_implementation
|
||||
#endif
|
||||
|
||||
const struct dspfilter_implementation *dspfilter_get_implementation(dspfilter_simd_mask_t mask)
|
||||
{
|
||||
return &eq_plug;
|
||||
}
|
||||
|
||||
#undef dspfilter_get_implementation
|
||||
@@ -1,204 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (fft.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 <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "fft.h"
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
|
||||
struct fft
|
||||
{
|
||||
fft_complex_t *interleave_buffer;
|
||||
fft_complex_t *phase_lut;
|
||||
unsigned *bitinverse_buffer;
|
||||
unsigned size;
|
||||
};
|
||||
|
||||
static unsigned bitswap(unsigned x, unsigned size_log2)
|
||||
{
|
||||
unsigned i;
|
||||
unsigned ret = 0;
|
||||
for (i = 0; i < size_log2; i++)
|
||||
ret |= ((x >> i) & 1) << (size_log2 - i - 1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void build_bitinverse(unsigned *bitinverse, unsigned size_log2)
|
||||
{
|
||||
unsigned i;
|
||||
unsigned size = 1 << size_log2;
|
||||
for (i = 0; i < size; i++)
|
||||
bitinverse[i] = bitswap(i, size_log2);
|
||||
}
|
||||
|
||||
static fft_complex_t exp_imag(double phase)
|
||||
{
|
||||
fft_complex_t out = { cos(phase), sin(phase) };
|
||||
return out;
|
||||
}
|
||||
|
||||
static void build_phase_lut(fft_complex_t *out, int size)
|
||||
{
|
||||
int i;
|
||||
out += size;
|
||||
for (i = -size; i <= size; i++)
|
||||
out[i] = exp_imag((M_PI * i) / size);
|
||||
}
|
||||
|
||||
static void interleave_complex(const unsigned *bitinverse,
|
||||
fft_complex_t *out, const fft_complex_t *in,
|
||||
unsigned samples, unsigned step)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < samples; i++, in += step)
|
||||
out[bitinverse[i]] = *in;
|
||||
}
|
||||
|
||||
static void interleave_float(const unsigned *bitinverse,
|
||||
fft_complex_t *out, const float *in,
|
||||
unsigned samples, unsigned step)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < samples; i++, in += step)
|
||||
{
|
||||
unsigned inv_i = bitinverse[i];
|
||||
out[inv_i].real = *in;
|
||||
out[inv_i].imag = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void resolve_float(float *out, const fft_complex_t *in, unsigned samples,
|
||||
float gain, unsigned step)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < samples; i++, in++, out += step)
|
||||
*out = gain * in->real;
|
||||
}
|
||||
|
||||
fft_t *fft_new(unsigned block_size_log2)
|
||||
{
|
||||
unsigned size;
|
||||
fft_t *fft = (fft_t*)calloc(1, sizeof(*fft));
|
||||
if (!fft)
|
||||
return NULL;
|
||||
|
||||
size = 1 << block_size_log2;
|
||||
fft->interleave_buffer = (fft_complex_t*)calloc(size, sizeof(*fft->interleave_buffer));
|
||||
fft->bitinverse_buffer = (unsigned*)calloc(size, sizeof(*fft->bitinverse_buffer));
|
||||
fft->phase_lut = (fft_complex_t*)calloc(2 * size + 1, sizeof(*fft->phase_lut));
|
||||
|
||||
if (!fft->interleave_buffer || !fft->bitinverse_buffer || !fft->phase_lut)
|
||||
goto error;
|
||||
|
||||
fft->size = size;
|
||||
|
||||
build_bitinverse(fft->bitinverse_buffer, block_size_log2);
|
||||
build_phase_lut(fft->phase_lut, size);
|
||||
return fft;
|
||||
|
||||
error:
|
||||
fft_free(fft);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void fft_free(fft_t *fft)
|
||||
{
|
||||
if (!fft)
|
||||
return;
|
||||
|
||||
free(fft->interleave_buffer);
|
||||
free(fft->bitinverse_buffer);
|
||||
free(fft->phase_lut);
|
||||
free(fft);
|
||||
}
|
||||
|
||||
static void butterfly(fft_complex_t *a, fft_complex_t *b, fft_complex_t mod)
|
||||
{
|
||||
mod = fft_complex_mul(mod, *b);
|
||||
*b = fft_complex_sub(*a, mod);
|
||||
*a = fft_complex_add(*a, mod);
|
||||
}
|
||||
|
||||
static void butterflies(fft_complex_t *butterfly_buf,
|
||||
const fft_complex_t *phase_lut,
|
||||
int phase_dir, unsigned step_size, unsigned samples)
|
||||
{
|
||||
unsigned i, j;
|
||||
for (i = 0; i < samples; i += step_size << 1)
|
||||
{
|
||||
int phase_step = (int)samples * phase_dir / (int)step_size;
|
||||
for (j = i; j < i + step_size; j++)
|
||||
butterfly(&butterfly_buf[j], &butterfly_buf[j + step_size],
|
||||
phase_lut[phase_step * (int)(j - i)]);
|
||||
}
|
||||
}
|
||||
|
||||
void fft_process_forward_complex(fft_t *fft,
|
||||
fft_complex_t *out, const fft_complex_t *in, unsigned step)
|
||||
{
|
||||
unsigned step_size;
|
||||
unsigned samples = fft->size;
|
||||
interleave_complex(fft->bitinverse_buffer, out, in, samples, step);
|
||||
|
||||
for (step_size = 1; step_size < samples; step_size <<= 1)
|
||||
{
|
||||
butterflies(out,
|
||||
fft->phase_lut + samples,
|
||||
-1, step_size, samples);
|
||||
}
|
||||
}
|
||||
|
||||
void fft_process_forward(fft_t *fft,
|
||||
fft_complex_t *out, const float *in, unsigned step)
|
||||
{
|
||||
unsigned step_size;
|
||||
unsigned samples = fft->size;
|
||||
interleave_float(fft->bitinverse_buffer, out, in, samples, step);
|
||||
|
||||
for (step_size = 1; step_size < fft->size; step_size <<= 1)
|
||||
{
|
||||
butterflies(out,
|
||||
fft->phase_lut + samples,
|
||||
-1, step_size, samples);
|
||||
}
|
||||
}
|
||||
|
||||
void fft_process_inverse(fft_t *fft,
|
||||
float *out, const fft_complex_t *in, unsigned step)
|
||||
{
|
||||
unsigned step_size;
|
||||
unsigned samples = fft->size;
|
||||
|
||||
interleave_complex(fft->bitinverse_buffer, fft->interleave_buffer,
|
||||
in, samples, 1);
|
||||
|
||||
for (step_size = 1; step_size < samples; step_size <<= 1)
|
||||
{
|
||||
butterflies(fft->interleave_buffer,
|
||||
fft->phase_lut + samples,
|
||||
1, step_size, samples);
|
||||
}
|
||||
|
||||
resolve_float(out, fft->interleave_buffer, samples, 1.0f / samples, step);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (fft.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 RARCH_FFT_H__
|
||||
#define RARCH_FFT_H__
|
||||
|
||||
#include <retro_inline.h>
|
||||
#include <math/complex.h>
|
||||
|
||||
typedef struct fft fft_t;
|
||||
|
||||
fft_t *fft_new(unsigned block_size_log2);
|
||||
|
||||
void fft_free(fft_t *fft);
|
||||
|
||||
void fft_process_forward_complex(fft_t *fft,
|
||||
fft_complex_t *out, const fft_complex_t *in, unsigned step);
|
||||
|
||||
void fft_process_forward(fft_t *fft,
|
||||
fft_complex_t *out, const float *in, unsigned step);
|
||||
|
||||
void fft_process_inverse(fft_t *fft,
|
||||
float *out, const fft_complex_t *in, unsigned step);
|
||||
|
||||
#endif
|
||||
@@ -1,457 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (iir.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 <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <libretro_dspfilter.h>
|
||||
|
||||
#define sqr(a) ((a) * (a))
|
||||
|
||||
/* filter types */
|
||||
enum IIRFilter
|
||||
{
|
||||
LPF, /* low pass filter */
|
||||
HPF, /* High pass filter */
|
||||
BPCSGF, /* band pass filter 1 */
|
||||
BPZPGF, /* band pass filter 2 */
|
||||
APF, /* Allpass filter*/
|
||||
NOTCH, /* Notch Filter */
|
||||
RIAA_phono, /* RIAA record/tape deemphasis */
|
||||
PEQ, /* Peaking band EQ filter */
|
||||
BBOOST, /* Bassboost filter */
|
||||
LSH, /* Low shelf filter */
|
||||
HSH, /* High shelf filter */
|
||||
RIAA_CD /* CD de-emphasis */
|
||||
};
|
||||
|
||||
struct iir_data
|
||||
{
|
||||
float b0, b1, b2;
|
||||
float a0, a1, a2;
|
||||
|
||||
/* Deterministic int16 path: normalized coefficients (b/a0, a/a0) in Q24,
|
||||
* and per-channel state. The feedback history yn1/yn2 is kept in Q16
|
||||
* int64 so the recursion runs at higher-than-int16 precision (no
|
||||
* in-loop quantization noise) and cannot overflow on boost/overshoot. */
|
||||
int32_t nb0_q, nb1_q, nb2_q, na1_q, na2_q;
|
||||
|
||||
struct
|
||||
{
|
||||
float xn1, xn2;
|
||||
float yn1, yn2;
|
||||
} l, r;
|
||||
|
||||
struct
|
||||
{
|
||||
int32_t xn1, xn2;
|
||||
int64_t yn1, yn2;
|
||||
} li, ri;
|
||||
};
|
||||
|
||||
static void iir_free(void *data)
|
||||
{
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void iir_process(void *data, struct dspfilter_output *output,
|
||||
const struct dspfilter_input *input)
|
||||
{
|
||||
unsigned i;
|
||||
struct iir_data *iir = (struct iir_data*)data;
|
||||
float *out = output->samples;
|
||||
|
||||
float b0 = iir->b0;
|
||||
float b1 = iir->b1;
|
||||
float b2 = iir->b2;
|
||||
float a0 = iir->a0;
|
||||
float a1 = iir->a1;
|
||||
float a2 = iir->a2;
|
||||
|
||||
float xn1_l = iir->l.xn1;
|
||||
float xn2_l = iir->l.xn2;
|
||||
float yn1_l = iir->l.yn1;
|
||||
float yn2_l = iir->l.yn2;
|
||||
|
||||
float xn1_r = iir->r.xn1;
|
||||
float xn2_r = iir->r.xn2;
|
||||
float yn1_r = iir->r.yn1;
|
||||
float yn2_r = iir->r.yn2;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
float in_l = out[0];
|
||||
float in_r = out[1];
|
||||
|
||||
float l = (b0 * in_l + b1 * xn1_l + b2 * xn2_l - a1 * yn1_l - a2 * yn2_l) / a0;
|
||||
float r = (b0 * in_r + b1 * xn1_r + b2 * xn2_r - a1 * yn1_r - a2 * yn2_r) / a0;
|
||||
|
||||
xn2_l = xn1_l;
|
||||
xn1_l = in_l;
|
||||
yn2_l = yn1_l;
|
||||
yn1_l = l;
|
||||
|
||||
xn2_r = xn1_r;
|
||||
xn1_r = in_r;
|
||||
yn2_r = yn1_r;
|
||||
yn1_r = r;
|
||||
|
||||
out[0] = l;
|
||||
out[1] = r;
|
||||
}
|
||||
|
||||
iir->l.xn1 = xn1_l;
|
||||
iir->l.xn2 = xn2_l;
|
||||
iir->l.yn1 = yn1_l;
|
||||
iir->l.yn2 = yn2_l;
|
||||
|
||||
iir->r.xn1 = xn1_r;
|
||||
iir->r.xn2 = xn2_r;
|
||||
iir->r.yn1 = yn1_r;
|
||||
iir->r.yn2 = yn2_r;
|
||||
}
|
||||
|
||||
/* Deterministic int16 Direct Form I biquad. Forward terms accumulate to Q40
|
||||
* (int64), the a0-normalized Q24 coefficients multiply the Q16 feedback
|
||||
* history, and a single symmetric rounding produces the int16 output while
|
||||
* the Q16 history is retained for the next iteration. */
|
||||
static void iir_process_i16(void *data,
|
||||
struct dspfilter_output_i16 *output,
|
||||
const struct dspfilter_input_i16 *input)
|
||||
{
|
||||
unsigned i;
|
||||
struct iir_data *iir = (struct iir_data*)data;
|
||||
int16_t *out;
|
||||
int32_t nb0 = iir->nb0_q;
|
||||
int32_t nb1 = iir->nb1_q;
|
||||
int32_t nb2 = iir->nb2_q;
|
||||
int32_t na1 = iir->na1_q;
|
||||
int32_t na2 = iir->na2_q;
|
||||
int32_t xn1_l = iir->li.xn1, xn2_l = iir->li.xn2;
|
||||
int64_t yn1_l = iir->li.yn1, yn2_l = iir->li.yn2;
|
||||
int32_t xn1_r = iir->ri.xn1, xn2_r = iir->ri.xn2;
|
||||
int64_t yn1_r = iir->ri.yn1, yn2_r = iir->ri.yn2;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
out = output->samples;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
int32_t in_l = out[0];
|
||||
int32_t in_r = out[1];
|
||||
int64_t fwd, acc;
|
||||
int32_t v;
|
||||
|
||||
fwd = (int64_t)nb0 * in_l + (int64_t)nb1 * xn1_l + (int64_t)nb2 * xn2_l;
|
||||
acc = fwd * 65536 - (int64_t)na1 * yn1_l - (int64_t)na2 * yn2_l;
|
||||
v = (acc >= 0) ? (int32_t)(( acc + (1LL << 39)) >> 40)
|
||||
: -(int32_t)((-acc + (1LL << 39)) >> 40);
|
||||
if (v > 32767) v = 32767;
|
||||
else if (v < -32768) v = -32768;
|
||||
xn2_l = xn1_l; xn1_l = in_l;
|
||||
yn2_l = yn1_l;
|
||||
yn1_l = (acc >= 0) ? (( acc + (1LL << 23)) >> 24)
|
||||
: -((-acc + (1LL << 23)) >> 24);
|
||||
out[0] = (int16_t)v;
|
||||
|
||||
fwd = (int64_t)nb0 * in_r + (int64_t)nb1 * xn1_r + (int64_t)nb2 * xn2_r;
|
||||
acc = fwd * 65536 - (int64_t)na1 * yn1_r - (int64_t)na2 * yn2_r;
|
||||
v = (acc >= 0) ? (int32_t)(( acc + (1LL << 39)) >> 40)
|
||||
: -(int32_t)((-acc + (1LL << 39)) >> 40);
|
||||
if (v > 32767) v = 32767;
|
||||
else if (v < -32768) v = -32768;
|
||||
xn2_r = xn1_r; xn1_r = in_r;
|
||||
yn2_r = yn1_r;
|
||||
yn1_r = (acc >= 0) ? (( acc + (1LL << 23)) >> 24)
|
||||
: -((-acc + (1LL << 23)) >> 24);
|
||||
out[1] = (int16_t)v;
|
||||
}
|
||||
|
||||
iir->li.xn1 = xn1_l; iir->li.xn2 = xn2_l;
|
||||
iir->li.yn1 = yn1_l; iir->li.yn2 = yn2_l;
|
||||
iir->ri.xn1 = xn1_r; iir->ri.xn2 = xn2_r;
|
||||
iir->ri.yn1 = yn1_r; iir->ri.yn2 = yn2_r;
|
||||
}
|
||||
|
||||
#define CHECK(x) if (strcmp(str, #x) == 0) return x
|
||||
static enum IIRFilter str_to_type(const char *str)
|
||||
{
|
||||
CHECK(LPF);
|
||||
CHECK(HPF);
|
||||
CHECK(BPCSGF);
|
||||
CHECK(BPZPGF);
|
||||
CHECK(APF);
|
||||
CHECK(NOTCH);
|
||||
CHECK(RIAA_phono);
|
||||
CHECK(PEQ);
|
||||
CHECK(BBOOST);
|
||||
CHECK(LSH);
|
||||
CHECK(HSH);
|
||||
CHECK(RIAA_CD);
|
||||
|
||||
return LPF; /* Fallback. */
|
||||
}
|
||||
#undef CHECK
|
||||
|
||||
static void make_poly_from_roots(
|
||||
const double *roots, unsigned num_roots, float *poly)
|
||||
{
|
||||
unsigned i, j;
|
||||
|
||||
poly[0] = 1;
|
||||
poly[1] = -roots[0];
|
||||
memset(poly + 2, 0, (num_roots + 1 - 2) * sizeof(*poly));
|
||||
|
||||
for (i = 1; i < num_roots; i++)
|
||||
for (j = num_roots; j > 0; j--)
|
||||
poly[j] -= poly[j - 1] * roots[i];
|
||||
}
|
||||
|
||||
static void iir_filter_init(struct iir_data *iir,
|
||||
float sample_rate, float freq, float qual, float gain, enum IIRFilter filter_type)
|
||||
{
|
||||
double omega = 2.0 * M_PI * freq / sample_rate;
|
||||
double cs = cos(omega);
|
||||
double sn = sin(omega);
|
||||
double a1pha = sn / (2.0 * qual);
|
||||
double A = exp(log(10.0) * gain / 40.0);
|
||||
double beta = sqrt(A + A);
|
||||
|
||||
float b0 = 0.0, b1 = 0.0, b2 = 0.0, a0 = 0.0, a1 = 0.0, a2 = 0.0;
|
||||
|
||||
/* Set up filter coefficients according to type */
|
||||
switch (filter_type)
|
||||
{
|
||||
case LPF:
|
||||
b0 = (1.0 - cs) / 2.0;
|
||||
b1 = 1.0 - cs ;
|
||||
b2 = (1.0 - cs) / 2.0;
|
||||
a0 = 1.0 + a1pha;
|
||||
a1 = -2.0 * cs;
|
||||
a2 = 1.0 - a1pha;
|
||||
break;
|
||||
case HPF:
|
||||
b0 = (1.0 + cs) / 2.0;
|
||||
b1 = -(1.0 + cs);
|
||||
b2 = (1.0 + cs) / 2.0;
|
||||
a0 = 1.0 + a1pha;
|
||||
a1 = -2.0 * cs;
|
||||
a2 = 1.0 - a1pha;
|
||||
break;
|
||||
case APF:
|
||||
b0 = 1.0 - a1pha;
|
||||
b1 = -2.0 * cs;
|
||||
b2 = 1.0 + a1pha;
|
||||
a0 = 1.0 + a1pha;
|
||||
a1 = -2.0 * cs;
|
||||
a2 = 1.0 - a1pha;
|
||||
break;
|
||||
case BPZPGF:
|
||||
b0 = a1pha;
|
||||
b1 = 0.0;
|
||||
b2 = -a1pha;
|
||||
a0 = 1.0 + a1pha;
|
||||
a1 = -2.0 * cs;
|
||||
a2 = 1.0 - a1pha;
|
||||
break;
|
||||
case BPCSGF:
|
||||
b0 = sn / 2.0;
|
||||
b1 = 0.0;
|
||||
b2 = -sn / 2.0;
|
||||
a0 = 1.0 + a1pha;
|
||||
a1 = -2.0 * cs;
|
||||
a2 = 1.0 - a1pha;
|
||||
break;
|
||||
case NOTCH:
|
||||
b0 = 1.0;
|
||||
b1 = -2.0 * cs;
|
||||
b2 = 1.0;
|
||||
a0 = 1.0 + a1pha;
|
||||
a1 = -2.0 * cs;
|
||||
a2 = 1.0 - a1pha;
|
||||
break;
|
||||
case RIAA_phono: /* http://www.dsprelated.com/showmessage/73300/3.php */
|
||||
{
|
||||
double y, b_re, a_re, b_im, a_im, g;
|
||||
float b[3] = {0.0f};
|
||||
float a[3] = {0.0f};
|
||||
|
||||
if ((int)sample_rate == 44100)
|
||||
{
|
||||
static const double zeros[] = {-0.2014898, 0.9233820};
|
||||
static const double poles[] = {0.7083149, 0.9924091};
|
||||
make_poly_from_roots(zeros, 2, b);
|
||||
make_poly_from_roots(poles, 2, a);
|
||||
}
|
||||
else if ((int)sample_rate == 48000)
|
||||
{
|
||||
static const double zeros[] = {-0.1766069, 0.9321590};
|
||||
static const double poles[] = {0.7396325, 0.9931330};
|
||||
make_poly_from_roots(zeros, 2, b);
|
||||
make_poly_from_roots(poles, 2, a);
|
||||
}
|
||||
else if ((int)sample_rate == 88200)
|
||||
{
|
||||
static const double zeros[] = {-0.1168735, 0.9648312};
|
||||
static const double poles[] = {0.8590646, 0.9964002};
|
||||
make_poly_from_roots(zeros, 2, b);
|
||||
make_poly_from_roots(poles, 2, a);
|
||||
}
|
||||
else if ((int)sample_rate == 96000)
|
||||
{
|
||||
static const double zeros[] = {-0.1141486, 0.9676817};
|
||||
static const double poles[] = {0.8699137, 0.9966946};
|
||||
make_poly_from_roots(zeros, 2, b);
|
||||
make_poly_from_roots(poles, 2, a);
|
||||
}
|
||||
|
||||
b0 = b[0];
|
||||
b1 = b[1];
|
||||
b2 = b[2];
|
||||
a0 = a[0];
|
||||
a1 = a[1];
|
||||
a2 = a[2];
|
||||
|
||||
/* Normalise to 0dB at 1kHz (Thanks to Glenn Davis) */
|
||||
y = 2.0 * M_PI * 1000.0 / sample_rate;
|
||||
b_re = b0 + b1 * cos(-y) + b2 * cos(-2.0 * y);
|
||||
a_re = a0 + a1 * cos(-y) + a2 * cos(-2.0 * y);
|
||||
b_im = b1 * sin(-y) + b2 * sin(-2.0 * y);
|
||||
a_im = a1 * sin(-y) + a2 * sin(-2.0 * y);
|
||||
g = 1.0 / sqrt((sqr(b_re) + sqr(b_im)) / (sqr(a_re) + sqr(a_im)));
|
||||
b0 *= g; b1 *= g; b2 *= g;
|
||||
break;
|
||||
}
|
||||
case PEQ:
|
||||
b0 = 1.0 + a1pha * A;
|
||||
b1 = -2.0 * cs;
|
||||
b2 = 1.0 - a1pha * A;
|
||||
a0 = 1.0 + a1pha / A;
|
||||
a1 = -2.0 * cs;
|
||||
a2 = 1.0 - a1pha / A;
|
||||
break;
|
||||
case BBOOST:
|
||||
beta = sqrt((A * A + 1) / 1.0 - (pow((A - 1), 2)));
|
||||
b0 = A * ((A + 1) - (A - 1) * cs + beta * sn);
|
||||
b1 = 2 * A * ((A - 1) - (A + 1) * cs);
|
||||
b2 = A * ((A + 1) - (A - 1) * cs - beta * sn);
|
||||
a0 = ((A + 1) + (A - 1) * cs + beta * sn);
|
||||
a1 = -2 * ((A - 1) + (A + 1) * cs);
|
||||
a2 = (A + 1) + (A - 1) * cs - beta * sn;
|
||||
break;
|
||||
case LSH:
|
||||
b0 = A * ((A + 1) - (A - 1) * cs + beta * sn);
|
||||
b1 = 2 * A * ((A - 1) - (A + 1) * cs);
|
||||
b2 = A * ((A + 1) - (A - 1) * cs - beta * sn);
|
||||
a0 = (A + 1) + (A - 1) * cs + beta * sn;
|
||||
a1 = -2 * ((A - 1) + (A + 1) * cs);
|
||||
a2 = (A + 1) + (A - 1) * cs - beta * sn;
|
||||
break;
|
||||
case RIAA_CD:
|
||||
omega = 2.0 * M_PI * 5283.0 / sample_rate;
|
||||
cs = cos(omega);
|
||||
sn = sin(omega);
|
||||
a1pha = sn / (2.0 * 0.4845);
|
||||
A = exp(log(10.0) * -9.477 / 40.0);
|
||||
beta = sqrt(A + A);
|
||||
(void)a1pha;
|
||||
case HSH:
|
||||
b0 = A * ((A + 1.0) + (A - 1.0) * cs + beta * sn);
|
||||
b1 = -2.0 * A * ((A - 1.0) + (A + 1.0) * cs);
|
||||
b2 = A * ((A + 1.0) + (A - 1.0) * cs - beta * sn);
|
||||
a0 = (A + 1.0) - (A - 1.0) * cs + beta * sn;
|
||||
a1 = 2.0 * ((A - 1.0) - (A + 1.0) * cs);
|
||||
a2 = (A + 1.0) - (A - 1.0) * cs - beta * sn;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
iir->b0 = b0;
|
||||
iir->b1 = b1;
|
||||
iir->b2 = b2;
|
||||
iir->a0 = a0;
|
||||
iir->a1 = a1;
|
||||
iir->a2 = a2;
|
||||
}
|
||||
|
||||
static void *iir_init(const struct dspfilter_info *info,
|
||||
const struct dspfilter_config *config, void *userdata)
|
||||
{
|
||||
float freq, qual, gain;
|
||||
enum IIRFilter filter = LPF;
|
||||
char *type = NULL;
|
||||
struct iir_data *iir = (struct iir_data*)calloc(1, sizeof(*iir));
|
||||
if (!iir)
|
||||
return NULL;
|
||||
|
||||
config->get_float(userdata, "frequency", &freq, 1024.0f);
|
||||
config->get_float(userdata, "quality", &qual, 0.707f);
|
||||
config->get_float(userdata, "gain", &gain, 0.0f);
|
||||
|
||||
config->get_string(userdata, "type", &type, "LPF");
|
||||
|
||||
filter = str_to_type(type);
|
||||
config->free(type);
|
||||
|
||||
iir_filter_init(iir, info->input_rate, freq, qual, gain, filter);
|
||||
|
||||
/* Precompute a0-normalized Q24 coefficients for the int16 path. */
|
||||
{
|
||||
double inv_a0 = (iir->a0 != 0.0f) ? 1.0 / (double)iir->a0 : 0.0;
|
||||
iir->nb0_q = (int32_t)floor((double)iir->b0 * inv_a0 * 16777216.0 + 0.5);
|
||||
iir->nb1_q = (int32_t)floor((double)iir->b1 * inv_a0 * 16777216.0 + 0.5);
|
||||
iir->nb2_q = (int32_t)floor((double)iir->b2 * inv_a0 * 16777216.0 + 0.5);
|
||||
iir->na1_q = (int32_t)floor((double)iir->a1 * inv_a0 * 16777216.0 + 0.5);
|
||||
iir->na2_q = (int32_t)floor((double)iir->a2 * inv_a0 * 16777216.0 + 0.5);
|
||||
}
|
||||
return iir;
|
||||
}
|
||||
|
||||
static const struct dspfilter_implementation iir_plug = {
|
||||
iir_init,
|
||||
iir_process,
|
||||
iir_free,
|
||||
|
||||
DSPFILTER_API_VERSION,
|
||||
"IIR",
|
||||
"iir",
|
||||
|
||||
iir_process_i16,
|
||||
};
|
||||
|
||||
#ifdef HAVE_FILTERS_BUILTIN
|
||||
#define dspfilter_get_implementation iir_dspfilter_get_implementation
|
||||
#endif
|
||||
|
||||
const struct dspfilter_implementation *dspfilter_get_implementation(dspfilter_simd_mask_t mask)
|
||||
{
|
||||
return &iir_plug;
|
||||
}
|
||||
|
||||
#undef dspfilter_get_implementation
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
global: dspfilter_get_implementation;
|
||||
local: *;
|
||||
};
|
||||
@@ -1,153 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (panning.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 <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <libretro_dspfilter.h>
|
||||
|
||||
struct panning_data
|
||||
{
|
||||
float left[2];
|
||||
float right[2];
|
||||
/* Q16 fixed-point mirror of left/right, for the int16 path. */
|
||||
int32_t left_i[2];
|
||||
int32_t right_i[2];
|
||||
};
|
||||
|
||||
static void panning_free(void *data)
|
||||
{
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void panning_process(void *data, struct dspfilter_output *output,
|
||||
const struct dspfilter_input *input)
|
||||
{
|
||||
unsigned i;
|
||||
struct panning_data *pan = (struct panning_data*)data;
|
||||
float *out = output->samples;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
float left = out[0];
|
||||
float right = out[1];
|
||||
out[0] = left * pan->left[0] + right * pan->left[1];
|
||||
out[1] = left * pan->right[0] + right * pan->right[1];
|
||||
}
|
||||
}
|
||||
|
||||
/* Deterministic int16 path: same 2x2 mix as panning_process() but in Q16
|
||||
* fixed point (int64 accumulate, round-half-away-from-zero, saturate). */
|
||||
static void panning_process_i16(void *data,
|
||||
struct dspfilter_output_i16 *output,
|
||||
const struct dspfilter_input_i16 *input)
|
||||
{
|
||||
unsigned i;
|
||||
struct panning_data *pan = (struct panning_data*)data;
|
||||
int16_t *out;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
out = output->samples;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
int32_t left = out[0];
|
||||
int32_t right = out[1];
|
||||
int64_t l = (int64_t)left * pan->left_i[0]
|
||||
+ (int64_t)right * pan->left_i[1];
|
||||
int64_t r = (int64_t)left * pan->right_i[0]
|
||||
+ (int64_t)right * pan->right_i[1];
|
||||
l = (l >= 0) ? ((l + 32768) >> 16) : -(((-l) + 32768) >> 16);
|
||||
r = (r >= 0) ? ((r + 32768) >> 16) : -(((-r) + 32768) >> 16);
|
||||
if (l > 32767) l = 32767;
|
||||
else if (l < -32768) l = -32768;
|
||||
if (r > 32767) r = 32767;
|
||||
else if (r < -32768) r = -32768;
|
||||
out[0] = (int16_t)l;
|
||||
out[1] = (int16_t)r;
|
||||
}
|
||||
}
|
||||
|
||||
static void *panning_init(const struct dspfilter_info *info,
|
||||
const struct dspfilter_config *config, void *userdata)
|
||||
{
|
||||
static const float default_left[] = { 1.0f, 0.0f };
|
||||
static const float default_right[] = { 0.0f, 1.0f };
|
||||
float *left = NULL;
|
||||
float *right = NULL;
|
||||
unsigned num_left = 0;
|
||||
unsigned num_right = 0;
|
||||
struct panning_data *pan = (struct panning_data*)
|
||||
calloc(1, sizeof(*pan));
|
||||
|
||||
if (!pan)
|
||||
return NULL;
|
||||
|
||||
config->get_float_array(userdata, "left_mix",
|
||||
&left, &num_left, default_left, 2);
|
||||
config->get_float_array(userdata, "right_mix",
|
||||
&right, &num_right, default_right, 2);
|
||||
|
||||
memcpy(pan->left, (num_left == 2) ?
|
||||
left : default_left, sizeof(pan->left));
|
||||
memcpy(pan->right, (num_right == 2) ?
|
||||
right : default_right, sizeof(pan->right));
|
||||
|
||||
pan->left_i[0] = (int32_t)floor((double)pan->left[0] * 65536.0 + 0.5);
|
||||
pan->left_i[1] = (int32_t)floor((double)pan->left[1] * 65536.0 + 0.5);
|
||||
pan->right_i[0] = (int32_t)floor((double)pan->right[0] * 65536.0 + 0.5);
|
||||
pan->right_i[1] = (int32_t)floor((double)pan->right[1] * 65536.0 + 0.5);
|
||||
|
||||
config->free(left);
|
||||
config->free(right);
|
||||
|
||||
return pan;
|
||||
}
|
||||
|
||||
static const struct dspfilter_implementation panning = {
|
||||
panning_init,
|
||||
panning_process,
|
||||
panning_free,
|
||||
|
||||
DSPFILTER_API_VERSION,
|
||||
"Panning",
|
||||
"panning",
|
||||
|
||||
panning_process_i16,
|
||||
};
|
||||
|
||||
#ifdef HAVE_FILTERS_BUILTIN
|
||||
#define dspfilter_get_implementation panning_dspfilter_get_implementation
|
||||
#endif
|
||||
|
||||
const struct dspfilter_implementation *
|
||||
dspfilter_get_implementation(dspfilter_simd_mask_t mask)
|
||||
{
|
||||
return &panning;
|
||||
}
|
||||
|
||||
#undef dspfilter_get_implementation
|
||||
@@ -1,196 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (phaser.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 <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <libretro_dspfilter.h>
|
||||
|
||||
#define PHASER_LFO_SHAPE 4.0
|
||||
#define PHASER_LFO_SKIP_SAMPLES 20
|
||||
|
||||
struct phaser_data
|
||||
{
|
||||
float freq;
|
||||
float startphase;
|
||||
float fb;
|
||||
float depth;
|
||||
float drywet;
|
||||
float old[2][24];
|
||||
float gain;
|
||||
float fbout[2];
|
||||
float lfoskip;
|
||||
float phase;
|
||||
|
||||
int stages;
|
||||
unsigned long skipcount;
|
||||
};
|
||||
|
||||
static void phaser_free(void *data)
|
||||
{
|
||||
free(data);
|
||||
}
|
||||
|
||||
/* One stereo frame of phasing. Shared by the float and int16 entry points so
|
||||
* the (inherently floating-point) LFO/allpass math is identical for both. */
|
||||
static INLINE void phaser_frame(struct phaser_data *ph,
|
||||
const float in[2], float *out)
|
||||
{
|
||||
unsigned c;
|
||||
int s;
|
||||
float m[2], tmp[2];
|
||||
|
||||
for (c = 0; c < 2; c++)
|
||||
m[c] = in[c] + ph->fbout[c] * ph->fb * 0.01f;
|
||||
|
||||
if ((ph->skipcount++ % PHASER_LFO_SKIP_SAMPLES) == 0)
|
||||
{
|
||||
ph->gain = 0.5 * (1.0 + cos(ph->skipcount * ph->lfoskip + ph->phase));
|
||||
ph->gain = (exp(ph->gain * PHASER_LFO_SHAPE) - 1.0) / (exp(PHASER_LFO_SHAPE) - 1);
|
||||
ph->gain = 1.0 - ph->gain * ph->depth;
|
||||
}
|
||||
|
||||
for (s = 0; s < ph->stages; s++)
|
||||
{
|
||||
for (c = 0; c < 2; c++)
|
||||
{
|
||||
tmp[c] = ph->old[c][s];
|
||||
ph->old[c][s] = ph->gain * tmp[c] + m[c];
|
||||
m[c] = tmp[c] - ph->gain * ph->old[c][s];
|
||||
}
|
||||
}
|
||||
|
||||
for (c = 0; c < 2; c++)
|
||||
{
|
||||
ph->fbout[c] = m[c];
|
||||
out[c] = m[c] * ph->drywet + in[c] * (1.0f - ph->drywet);
|
||||
}
|
||||
}
|
||||
|
||||
static void phaser_process(void *data, struct dspfilter_output *output,
|
||||
const struct dspfilter_input *input)
|
||||
{
|
||||
unsigned i;
|
||||
struct phaser_data *ph = (struct phaser_data*)data;
|
||||
float *out = output->samples;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
float in[2] = { out[0], out[1] };
|
||||
phaser_frame(ph, in, out);
|
||||
}
|
||||
}
|
||||
|
||||
/* int16 entry point: bridge through the shared float frame routine so an
|
||||
* int16 chain containing the phaser stays on the deterministic s16 path.
|
||||
* The phaser recomputes its allpass coefficient from cos()/exp() every LFO
|
||||
* step, so a genuinely FPU-free port would only ever be approximate; the
|
||||
* game signal still stays on the integer path, converted here (s16 <-> the
|
||||
* host's normalized [-1,1] float domain) around the coefficient math. */
|
||||
static void phaser_process_i16(void *data, struct dspfilter_output_i16 *output,
|
||||
const struct dspfilter_input_i16 *input)
|
||||
{
|
||||
unsigned i;
|
||||
struct phaser_data *ph = (struct phaser_data*)data;
|
||||
int16_t *out = NULL;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
out = output->samples;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
float in[2], o[2];
|
||||
int32_t v;
|
||||
|
||||
in[0] = (float)out[0] * (1.0f / 32768.0f);
|
||||
in[1] = (float)out[1] * (1.0f / 32768.0f);
|
||||
|
||||
phaser_frame(ph, in, o);
|
||||
|
||||
v = (o[0] >= 0.0f) ? (int32_t)(o[0] * 32768.0f + 0.5f)
|
||||
: (int32_t)(o[0] * 32768.0f - 0.5f);
|
||||
if (v > 32767) v = 32767;
|
||||
else if (v < -32768) v = -32768;
|
||||
out[0] = (int16_t)v;
|
||||
|
||||
v = (o[1] >= 0.0f) ? (int32_t)(o[1] * 32768.0f + 0.5f)
|
||||
: (int32_t)(o[1] * 32768.0f - 0.5f);
|
||||
if (v > 32767) v = 32767;
|
||||
else if (v < -32768) v = -32768;
|
||||
out[1] = (int16_t)v;
|
||||
}
|
||||
}
|
||||
|
||||
static void *phaser_init(const struct dspfilter_info *info,
|
||||
const struct dspfilter_config *config, void *userdata)
|
||||
{
|
||||
float lfo_freq, lfo_start_phase;
|
||||
struct phaser_data *ph = (struct phaser_data*)calloc(1, sizeof(*ph));
|
||||
if (!ph)
|
||||
return NULL;
|
||||
|
||||
config->get_float(userdata, "lfo_freq", &lfo_freq, 0.4f);
|
||||
config->get_float(userdata, "lfo_start_phase", &lfo_start_phase, 0.0f);
|
||||
config->get_float(userdata, "feedback", &ph->fb, 0.0f);
|
||||
config->get_float(userdata, "depth", &ph->depth, 0.4f);
|
||||
config->get_float(userdata, "dry_wet", &ph->drywet, 0.5f);
|
||||
config->get_int(userdata, "stages", &ph->stages, 2);
|
||||
|
||||
if (ph->stages < 1)
|
||||
ph->stages = 1;
|
||||
else if (ph->stages > 24)
|
||||
ph->stages = 24;
|
||||
|
||||
ph->lfoskip = lfo_freq * 2.0 * M_PI / info->input_rate;
|
||||
ph->phase = lfo_start_phase * M_PI / 180.0;
|
||||
|
||||
return ph;
|
||||
}
|
||||
|
||||
static const struct dspfilter_implementation phaser_plug = {
|
||||
phaser_init,
|
||||
phaser_process,
|
||||
phaser_free,
|
||||
|
||||
DSPFILTER_API_VERSION,
|
||||
"Phaser",
|
||||
"phaser",
|
||||
|
||||
phaser_process_i16,
|
||||
};
|
||||
|
||||
#ifdef HAVE_FILTERS_BUILTIN
|
||||
#define dspfilter_get_implementation phaser_dspfilter_get_implementation
|
||||
#endif
|
||||
|
||||
const struct dspfilter_implementation *dspfilter_get_implementation(dspfilter_simd_mask_t mask)
|
||||
{
|
||||
return &phaser_plug;
|
||||
}
|
||||
|
||||
#undef dspfilter_get_implementation
|
||||
@@ -1,442 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (reverb.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 <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <retro_inline.h>
|
||||
#include <libretro_dspfilter.h>
|
||||
|
||||
struct comb
|
||||
{
|
||||
float *buffer;
|
||||
unsigned bufsize;
|
||||
unsigned bufidx;
|
||||
|
||||
float feedback;
|
||||
float filterstore;
|
||||
float damp1, damp2;
|
||||
|
||||
/* Q16 mirrors for the deterministic int16 path (buffer/filterstore in
|
||||
* int64 Q16 for headroom + precision on the small, resonant signal). */
|
||||
int64_t *buffer_i;
|
||||
int64_t filterstore_i;
|
||||
int32_t feedback_q, damp1_q, damp2_q;
|
||||
};
|
||||
|
||||
struct allpass
|
||||
{
|
||||
float *buffer;
|
||||
float feedback;
|
||||
unsigned bufsize;
|
||||
unsigned bufidx;
|
||||
|
||||
int64_t *buffer_i;
|
||||
int32_t feedback_q;
|
||||
};
|
||||
|
||||
static INLINE float comb_process(struct comb *c, float input)
|
||||
{
|
||||
float output = c->buffer[c->bufidx];
|
||||
c->filterstore = (output * c->damp2) + (c->filterstore * c->damp1);
|
||||
|
||||
c->buffer[c->bufidx] = input + (c->filterstore * c->feedback);
|
||||
|
||||
c->bufidx++;
|
||||
if (c->bufidx >= c->bufsize)
|
||||
c->bufidx = 0;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static INLINE float allpass_process(struct allpass *a, float input)
|
||||
{
|
||||
float bufout = a->buffer[a->bufidx];
|
||||
float output = -input + bufout;
|
||||
a->buffer[a->bufidx] = input + bufout * a->feedback;
|
||||
|
||||
a->bufidx++;
|
||||
if (a->bufidx >= a->bufsize)
|
||||
a->bufidx = 0;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/* Q16 multiply with round-half-away-from-zero, for the int16 path. */
|
||||
static INLINE int64_t rmul_q16(int64_t x, int32_t g)
|
||||
{
|
||||
int64_t p = x * (int64_t)g;
|
||||
return (p >= 0) ? ((p + 32768) >> 16) : -(((-p) + 32768) >> 16);
|
||||
}
|
||||
|
||||
static INLINE int64_t comb_process_i16(struct comb *c, int64_t input)
|
||||
{
|
||||
int64_t output = c->buffer_i[c->bufidx];
|
||||
c->filterstore_i = rmul_q16(output, c->damp2_q)
|
||||
+ rmul_q16(c->filterstore_i, c->damp1_q);
|
||||
c->buffer_i[c->bufidx] = input + rmul_q16(c->filterstore_i, c->feedback_q);
|
||||
|
||||
c->bufidx++;
|
||||
if (c->bufidx >= c->bufsize)
|
||||
c->bufidx = 0;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static INLINE int64_t allpass_process_i16(struct allpass *a, int64_t input)
|
||||
{
|
||||
int64_t bufout = a->buffer_i[a->bufidx];
|
||||
int64_t output = -input + bufout;
|
||||
a->buffer_i[a->bufidx] = input + rmul_q16(bufout, a->feedback_q);
|
||||
|
||||
a->bufidx++;
|
||||
if (a->bufidx >= a->bufsize)
|
||||
a->bufidx = 0;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
#define numcombs 8
|
||||
#define numallpasses 4
|
||||
static const float muted = 0;
|
||||
static const float fixedgain = 0.015f;
|
||||
static const float scalewet = 3;
|
||||
static const float scaledry = 2;
|
||||
static const float scaledamp = 0.4f;
|
||||
static const float scaleroom = 0.28f;
|
||||
static const float offsetroom = 0.7f;
|
||||
static const float initialroom = 0.5f;
|
||||
static const float initialdamp = 0.5f;
|
||||
static const float initialwet = 1.0f / 3.0f;
|
||||
static const float initialdry = 0;
|
||||
static const float initialwidth = 1;
|
||||
static const float initialmode = 0;
|
||||
static const float freezemode = 0.5f;
|
||||
|
||||
struct revmodel
|
||||
{
|
||||
struct comb combL[numcombs];
|
||||
struct allpass allpassL[numallpasses];
|
||||
|
||||
float *bufcomb[numcombs];
|
||||
float *bufallpass[numallpasses];
|
||||
|
||||
int64_t *bufcomb_i[numcombs];
|
||||
int64_t *bufallpass_i[numallpasses];
|
||||
int32_t gain_q, wet1_q, dry_q;
|
||||
|
||||
float gain;
|
||||
float roomsize, roomsize1;
|
||||
float damp, damp1;
|
||||
float wet, wet1, wet2;
|
||||
float dry;
|
||||
float width;
|
||||
float mode;
|
||||
};
|
||||
|
||||
static float revmodel_process(struct revmodel *rev, float in)
|
||||
{
|
||||
unsigned i;
|
||||
float mono_out = 0.0f;
|
||||
float mono_in = in;
|
||||
float input = mono_in * rev->gain;
|
||||
|
||||
for (i = 0; i < numcombs; i++)
|
||||
mono_out += comb_process(&rev->combL[i], input);
|
||||
|
||||
for (i = 0; i < numallpasses; i++)
|
||||
mono_out = allpass_process(&rev->allpassL[i], mono_out);
|
||||
|
||||
return mono_in * rev->dry + mono_out * rev->wet1;
|
||||
}
|
||||
|
||||
/* Deterministic int16 counterpart of revmodel_process(): the input is scaled
|
||||
* by gain into Q16, run through the comb and allpass networks in Q16 int64,
|
||||
* then mixed dry+wet and rounded/saturated to s16. */
|
||||
static int32_t revmodel_process_i16(struct revmodel *rev, int32_t in)
|
||||
{
|
||||
unsigned i;
|
||||
int64_t mono_out = 0;
|
||||
int64_t input = (int64_t)in * rev->gain_q; /* Q16 */
|
||||
int64_t res;
|
||||
int32_t v;
|
||||
|
||||
for (i = 0; i < numcombs; i++)
|
||||
mono_out += comb_process_i16(&rev->combL[i], input);
|
||||
|
||||
for (i = 0; i < numallpasses; i++)
|
||||
mono_out = allpass_process_i16(&rev->allpassL[i], mono_out);
|
||||
|
||||
res = (int64_t)in * rev->dry_q + rmul_q16(mono_out, rev->wet1_q); /* Q16 */
|
||||
v = (res >= 0) ? (int32_t)(( res + 32768) >> 16)
|
||||
: -(int32_t)((-res + 32768) >> 16);
|
||||
if (v > 32767) v = 32767;
|
||||
else if (v < -32768) v = -32768;
|
||||
return v;
|
||||
}
|
||||
|
||||
/* Quantize the finalized float coefficients to Q16 for the int16 path.
|
||||
* Called once after all setters have run. */
|
||||
static void revmodel_quantize(struct revmodel *rev)
|
||||
{
|
||||
unsigned i;
|
||||
rev->gain_q = (int32_t)floor((double)rev->gain * 65536.0 + 0.5);
|
||||
rev->wet1_q = (int32_t)floor((double)rev->wet1 * 65536.0 + 0.5);
|
||||
rev->dry_q = (int32_t)floor((double)rev->dry * 65536.0 + 0.5);
|
||||
|
||||
for (i = 0; i < numcombs; i++)
|
||||
{
|
||||
rev->combL[i].feedback_q = (int32_t)floor((double)rev->combL[i].feedback * 65536.0 + 0.5);
|
||||
rev->combL[i].damp1_q = (int32_t)floor((double)rev->combL[i].damp1 * 65536.0 + 0.5);
|
||||
rev->combL[i].damp2_q = (int32_t)floor((double)rev->combL[i].damp2 * 65536.0 + 0.5);
|
||||
}
|
||||
|
||||
for (i = 0; i < numallpasses; i++)
|
||||
rev->allpassL[i].feedback_q = (int32_t)floor((double)rev->allpassL[i].feedback * 65536.0 + 0.5);
|
||||
}
|
||||
|
||||
static void revmodel_update(struct revmodel *rev)
|
||||
{
|
||||
unsigned i;
|
||||
rev->wet1 = rev->wet * (rev->width / 2.0f + 0.5f);
|
||||
|
||||
if (rev->mode >= freezemode)
|
||||
{
|
||||
rev->roomsize1 = 1.0f;
|
||||
rev->damp1 = 0.0f;
|
||||
rev->gain = muted;
|
||||
}
|
||||
else
|
||||
{
|
||||
rev->roomsize1 = rev->roomsize;
|
||||
rev->damp1 = rev->damp;
|
||||
rev->gain = fixedgain;
|
||||
}
|
||||
|
||||
for (i = 0; i < numcombs; i++)
|
||||
{
|
||||
rev->combL[i].feedback = rev->roomsize1;
|
||||
rev->combL[i].damp1 = rev->damp1;
|
||||
rev->combL[i].damp2 = 1.0f - rev->damp1;
|
||||
}
|
||||
}
|
||||
|
||||
static void revmodel_setroomsize(struct revmodel *rev, float value)
|
||||
{
|
||||
rev->roomsize = value * scaleroom + offsetroom;
|
||||
revmodel_update(rev);
|
||||
}
|
||||
|
||||
static void revmodel_setdamp(struct revmodel *rev, float value)
|
||||
{
|
||||
rev->damp = value * scaledamp;
|
||||
revmodel_update(rev);
|
||||
}
|
||||
|
||||
static void revmodel_setwet(struct revmodel *rev, float value)
|
||||
{
|
||||
rev->wet = value * scalewet;
|
||||
revmodel_update(rev);
|
||||
}
|
||||
|
||||
static void revmodel_setdry(struct revmodel *rev, float value)
|
||||
{
|
||||
rev->dry = value * scaledry;
|
||||
revmodel_update(rev);
|
||||
}
|
||||
|
||||
static void revmodel_setwidth(struct revmodel *rev, float value)
|
||||
{
|
||||
rev->width = value;
|
||||
revmodel_update(rev);
|
||||
}
|
||||
|
||||
static void revmodel_setmode(struct revmodel *rev, float value)
|
||||
{
|
||||
rev->mode = value;
|
||||
revmodel_update(rev);
|
||||
}
|
||||
|
||||
static void revmodel_init(struct revmodel *rev,int srate)
|
||||
{
|
||||
unsigned c;
|
||||
static const int comb_lengths[8] = { 1116,1188,1277,1356,1422,1491,1557,1617 };
|
||||
static const int allpass_lengths[4] = { 225,341,441,556 };
|
||||
double r = srate * (1 / 44100.0);
|
||||
|
||||
for (c = 0; c < numcombs; ++c)
|
||||
{
|
||||
unsigned bufsize = (unsigned)(r * comb_lengths[c]);
|
||||
rev->bufcomb[c] = (float*)calloc(bufsize, sizeof(float));
|
||||
rev->combL[c].buffer = rev->bufcomb[c];
|
||||
rev->combL[c].bufsize = bufsize;
|
||||
rev->bufcomb_i[c] = (int64_t*)calloc(bufsize, sizeof(int64_t));
|
||||
rev->combL[c].buffer_i = rev->bufcomb_i[c];
|
||||
}
|
||||
|
||||
for (c = 0; c < numallpasses; ++c)
|
||||
{
|
||||
unsigned bufsize = (unsigned)(r * allpass_lengths[c]);
|
||||
rev->bufallpass[c] = (float*)calloc(bufsize, sizeof(float));
|
||||
rev->allpassL[c].buffer = rev->bufallpass[c];
|
||||
rev->allpassL[c].bufsize = bufsize;
|
||||
rev->allpassL[c].feedback = 0.5f;
|
||||
rev->bufallpass_i[c] = (int64_t*)calloc(bufsize, sizeof(int64_t));
|
||||
rev->allpassL[c].buffer_i = rev->bufallpass_i[c];
|
||||
}
|
||||
|
||||
revmodel_setwet(rev, initialwet);
|
||||
revmodel_setroomsize(rev, initialroom);
|
||||
revmodel_setdry(rev, initialdry);
|
||||
revmodel_setdamp(rev, initialdamp);
|
||||
revmodel_setwidth(rev, initialwidth);
|
||||
revmodel_setmode(rev, initialmode);
|
||||
}
|
||||
|
||||
struct reverb_data
|
||||
{
|
||||
struct revmodel left, right;
|
||||
};
|
||||
|
||||
static void reverb_free(void *data)
|
||||
{
|
||||
unsigned i;
|
||||
struct reverb_data *rev = (struct reverb_data*)data;
|
||||
|
||||
for (i = 0; i < numcombs; i++)
|
||||
{
|
||||
free(rev->left.bufcomb[i]);
|
||||
free(rev->right.bufcomb[i]);
|
||||
free(rev->left.bufcomb_i[i]);
|
||||
free(rev->right.bufcomb_i[i]);
|
||||
}
|
||||
|
||||
for (i = 0; i < numallpasses; i++)
|
||||
{
|
||||
free(rev->left.bufallpass[i]);
|
||||
free(rev->right.bufallpass[i]);
|
||||
free(rev->left.bufallpass_i[i]);
|
||||
free(rev->right.bufallpass_i[i]);
|
||||
}
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void reverb_process(void *data, struct dspfilter_output *output,
|
||||
const struct dspfilter_input *input)
|
||||
{
|
||||
unsigned i;
|
||||
float *out;
|
||||
struct reverb_data *rev = (struct reverb_data*)data;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
out = output->samples;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
float in[2] = { out[0], out[1] };
|
||||
|
||||
out[0] = revmodel_process(&rev->left, in[0]);
|
||||
out[1] = revmodel_process(&rev->right, in[1]);
|
||||
}
|
||||
}
|
||||
|
||||
static void reverb_process_i16(void *data, struct dspfilter_output_i16 *output,
|
||||
const struct dspfilter_input_i16 *input)
|
||||
{
|
||||
unsigned i;
|
||||
int16_t *out;
|
||||
struct reverb_data *rev = (struct reverb_data*)data;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
out = output->samples;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
int32_t in0 = out[0];
|
||||
int32_t in1 = out[1];
|
||||
|
||||
out[0] = (int16_t)revmodel_process_i16(&rev->left, in0);
|
||||
out[1] = (int16_t)revmodel_process_i16(&rev->right, in1);
|
||||
}
|
||||
}
|
||||
|
||||
static void *reverb_init(const struct dspfilter_info *info,
|
||||
const struct dspfilter_config *config, void *userdata)
|
||||
{
|
||||
float drytime, wettime, damping, roomwidth, roomsize;
|
||||
struct reverb_data *rev = (struct reverb_data*)
|
||||
calloc(1, sizeof(*rev));
|
||||
if (!rev)
|
||||
return NULL;
|
||||
|
||||
config->get_float(userdata, "drytime", &drytime, 0.43f);
|
||||
config->get_float(userdata, "wettime", &wettime, 0.4f);
|
||||
config->get_float(userdata, "damping", &damping, 0.8f);
|
||||
config->get_float(userdata, "roomwidth", &roomwidth, 0.56f);
|
||||
config->get_float(userdata, "roomsize", &roomsize, 0.56f);
|
||||
|
||||
revmodel_init(&rev->left,info->input_rate);
|
||||
revmodel_init(&rev->right,info->input_rate);
|
||||
|
||||
revmodel_setdamp(&rev->left, damping);
|
||||
revmodel_setdry(&rev->left, drytime);
|
||||
revmodel_setwet(&rev->left, wettime);
|
||||
revmodel_setwidth(&rev->left, roomwidth);
|
||||
revmodel_setroomsize(&rev->left, roomsize);
|
||||
|
||||
revmodel_setdamp(&rev->right, damping);
|
||||
revmodel_setdry(&rev->right, drytime);
|
||||
revmodel_setwet(&rev->right, wettime);
|
||||
revmodel_setwidth(&rev->right, roomwidth);
|
||||
revmodel_setroomsize(&rev->right, roomsize);
|
||||
|
||||
revmodel_quantize(&rev->left);
|
||||
revmodel_quantize(&rev->right);
|
||||
|
||||
return rev;
|
||||
}
|
||||
|
||||
static const struct dspfilter_implementation reverb_plug = {
|
||||
reverb_init,
|
||||
reverb_process,
|
||||
reverb_free,
|
||||
|
||||
DSPFILTER_API_VERSION,
|
||||
"Reverb",
|
||||
"reverb",
|
||||
|
||||
reverb_process_i16,
|
||||
};
|
||||
|
||||
#ifdef HAVE_FILTERS_BUILTIN
|
||||
#define dspfilter_get_implementation reverb_dspfilter_get_implementation
|
||||
#endif
|
||||
|
||||
const struct dspfilter_implementation *dspfilter_get_implementation(dspfilter_simd_mask_t mask)
|
||||
{
|
||||
return &reverb_plug;
|
||||
}
|
||||
|
||||
#undef dspfilter_get_implementation
|
||||
@@ -1,176 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (tremolo.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 <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <libretro_dspfilter.h>
|
||||
|
||||
#define sqr(a) ((a) * (a))
|
||||
|
||||
struct tremolo_core
|
||||
{
|
||||
float *wavetable;
|
||||
/* Q16 fixed-point mirror of wavetable, for the int16 path. */
|
||||
int32_t *wavetable_i;
|
||||
float freq;
|
||||
float depth;
|
||||
unsigned index;
|
||||
unsigned maxindex;
|
||||
};
|
||||
|
||||
struct tremolo
|
||||
{
|
||||
struct tremolo_core left, right;
|
||||
};
|
||||
|
||||
static void tremolo_free(void *data)
|
||||
{
|
||||
struct tremolo *tre = (struct tremolo*)data;
|
||||
free(tre->left.wavetable);
|
||||
free(tre->right.wavetable);
|
||||
free(tre->left.wavetable_i);
|
||||
free(tre->right.wavetable_i);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void tremolocore_init(struct tremolo_core *core,float depth,int samplerate,float freq)
|
||||
{
|
||||
double env;
|
||||
unsigned i;
|
||||
const double offset = 1. - depth / 2.;
|
||||
core->index = 0;
|
||||
core->maxindex = samplerate / freq;
|
||||
core->wavetable = (float*)malloc(core->maxindex * sizeof(float));
|
||||
memset(core->wavetable, 0, core->maxindex * sizeof(float));
|
||||
core->wavetable_i = (int32_t*)malloc(core->maxindex * sizeof(int32_t));
|
||||
memset(core->wavetable_i, 0, core->maxindex * sizeof(int32_t));
|
||||
for (i = 0; i < core->maxindex; i++)
|
||||
{
|
||||
env = freq * i / samplerate;
|
||||
env = sin((M_PI*2) * fmod(env + 0.25, 1.0));
|
||||
core->wavetable[i] = env * (1 - fabs(offset)) + offset;
|
||||
core->wavetable_i[i] =
|
||||
(int32_t)floor((double)core->wavetable[i] * 65536.0 + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
float tremolocore_core(struct tremolo_core *core,float in)
|
||||
{
|
||||
core->index = core->index % core->maxindex;
|
||||
return in * core->wavetable[core->index++];
|
||||
}
|
||||
|
||||
static void tremolo_process(void *data, struct dspfilter_output *output,
|
||||
const struct dspfilter_input *input)
|
||||
{
|
||||
unsigned i;
|
||||
float *out;
|
||||
struct tremolo *tre = (struct tremolo*)data;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
out = output->samples;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
float in[2] = { out[0], out[1] };
|
||||
out[0] = tremolocore_core(&tre->left, in[0]);
|
||||
out[1] = tremolocore_core(&tre->right, in[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Deterministic int16 path: multiply by the same per-sample envelope as the
|
||||
* float path, using the Q16 wavetable (int64 product, round-half-away-from-
|
||||
* zero, saturate). */
|
||||
static int16_t tremolocore_core_i16(struct tremolo_core *core, int16_t in)
|
||||
{
|
||||
int64_t v;
|
||||
int32_t r;
|
||||
core->index = core->index % core->maxindex;
|
||||
v = (int64_t)in * core->wavetable_i[core->index++];
|
||||
r = (v >= 0) ? (int32_t)(( v + 32768) >> 16)
|
||||
: -(int32_t)((-v + 32768) >> 16);
|
||||
if (r > 32767) r = 32767;
|
||||
else if (r < -32768) r = -32768;
|
||||
return (int16_t)r;
|
||||
}
|
||||
|
||||
static void tremolo_process_i16(void *data,
|
||||
struct dspfilter_output_i16 *output,
|
||||
const struct dspfilter_input_i16 *input)
|
||||
{
|
||||
unsigned i;
|
||||
int16_t *out;
|
||||
struct tremolo *tre = (struct tremolo*)data;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
out = output->samples;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
int16_t in[2] = { out[0], out[1] };
|
||||
out[0] = tremolocore_core_i16(&tre->left, in[0]);
|
||||
out[1] = tremolocore_core_i16(&tre->right, in[1]);
|
||||
}
|
||||
}
|
||||
|
||||
static void *tremolo_init(const struct dspfilter_info *info,
|
||||
const struct dspfilter_config *config, void *userdata)
|
||||
{
|
||||
float freq, depth;
|
||||
struct tremolo *tre = (struct tremolo*)calloc(1, sizeof(*tre));
|
||||
if (!tre)
|
||||
return NULL;
|
||||
|
||||
config->get_float(userdata, "freq", &freq,4.0f);
|
||||
config->get_float(userdata, "depth", &depth, 0.9f);
|
||||
tremolocore_init(&tre->left,depth,info->input_rate,freq);
|
||||
tremolocore_init(&tre->right,depth,info->input_rate,freq);
|
||||
return tre;
|
||||
}
|
||||
|
||||
static const struct dspfilter_implementation tremolo_plug = {
|
||||
tremolo_init,
|
||||
tremolo_process,
|
||||
tremolo_free,
|
||||
|
||||
DSPFILTER_API_VERSION,
|
||||
"Tremolo",
|
||||
"tremolo",
|
||||
|
||||
tremolo_process_i16,
|
||||
};
|
||||
|
||||
#ifdef HAVE_FILTERS_BUILTIN
|
||||
#define dspfilter_get_implementation tremolo_dspfilter_get_implementation
|
||||
#endif
|
||||
|
||||
const struct dspfilter_implementation *dspfilter_get_implementation(dspfilter_simd_mask_t mask)
|
||||
{
|
||||
return &tremolo_plug;
|
||||
}
|
||||
|
||||
#undef dspfilter_get_implementation
|
||||
@@ -1,281 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (vibrato.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 <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <libretro_dspfilter.h>
|
||||
|
||||
#define sqr(a) ((a) * (a))
|
||||
|
||||
#define VIBRATO_BASE_DELAY_SEC 0.002f /* 2 ms */
|
||||
#define VIBRATO_FREQUENCY_DEFAULT_HZ 2.0f
|
||||
#define VIBRATO_FREQUENCY_MAX_HZ 14.0f
|
||||
#define VIBRATO_DEPTH_DEFAULT_PERCENT 50.0f
|
||||
#define VIBRATO_ADD_DELAY 3
|
||||
|
||||
static float hermite_interp(float x, float *y)
|
||||
{
|
||||
float c0 = y[1];
|
||||
float c1 = (1.0f / 2.0f) * (y[2] - y[0]);
|
||||
float c2 = (y[0] - (5.0f / 2.0f) * y[1]) + (2.0f * y[2] - (1.0f / 2.0f) * y[3]);
|
||||
float c3 = (1.0f / 2.0f) * (y[3] - y[0]) + (3.0f / 2.0f) * (y[1] - y[2]);
|
||||
return ((c3 * x + c2) * x + c1) * x + c0;
|
||||
}
|
||||
|
||||
/* Fixed-point 4-point Hermite, x in Q16 [0,1). The float coefficients carry
|
||||
* halves, so 2*c0..2*c3 are exact integers in the int16 sample domain; run
|
||||
* Horner in Q16 (per-step round-half-away-from-zero), then halve the result
|
||||
* and saturate to s16. */
|
||||
static int32_t hermite_interp_i16(int32_t x, const int16_t *y)
|
||||
{
|
||||
int64_t c0 = 2 * (int64_t)y[1];
|
||||
int64_t c1 = (int64_t)y[2] - y[0];
|
||||
int64_t c2 = 2 * (int64_t)y[0] - 5 * (int64_t)y[1]
|
||||
+ 4 * (int64_t)y[2] - (int64_t)y[3];
|
||||
int64_t c3 = ((int64_t)y[3] - y[0]) + 3 * ((int64_t)y[1] - y[2]);
|
||||
int64_t acc = c3 * 65536; /* Q16, holds 2*intermediate */
|
||||
int64_t p;
|
||||
int32_t r;
|
||||
|
||||
p = acc * x;
|
||||
acc = ((p >= 0) ? ((p + 32768) >> 16)
|
||||
: -(((-p) + 32768) >> 16)) + c2 * 65536;
|
||||
p = acc * x;
|
||||
acc = ((p >= 0) ? ((p + 32768) >> 16)
|
||||
: -(((-p) + 32768) >> 16)) + c1 * 65536;
|
||||
p = acc * x;
|
||||
acc = ((p >= 0) ? ((p + 32768) >> 16)
|
||||
: -(((-p) + 32768) >> 16)) + c0 * 65536;
|
||||
/* acc is Q16 of 2*result; result = acc / (2 * 65536) = acc / 131072. */
|
||||
r = (acc >= 0) ? (int32_t)(( acc + 65536) >> 17)
|
||||
: -(int32_t)((-acc + 65536) >> 17);
|
||||
if (r > 32767) r = 32767;
|
||||
else if (r < -32768) r = -32768;
|
||||
return r;
|
||||
}
|
||||
|
||||
struct vibrato_core
|
||||
{
|
||||
float* buffer;
|
||||
/* int16 mirror of the delay line, plus a precomputed Q16 per-phase delay
|
||||
* LUT and the LFO period, for the deterministic int16 path. */
|
||||
int16_t *buffer_i;
|
||||
int32_t *delay_lut;
|
||||
int maxphase;
|
||||
float freq;
|
||||
float samplerate;
|
||||
float depth;
|
||||
int phase;
|
||||
int writeindex;
|
||||
int size;
|
||||
};
|
||||
|
||||
struct vibrato
|
||||
{
|
||||
struct vibrato_core left, right;
|
||||
};
|
||||
|
||||
static void vibrato_free(void *data)
|
||||
{
|
||||
struct vibrato *vib = (struct vibrato*)data;
|
||||
free(vib->left.buffer);
|
||||
free(vib->right.buffer);
|
||||
free(vib->left.buffer_i);
|
||||
free(vib->right.buffer_i);
|
||||
free(vib->left.delay_lut);
|
||||
free(vib->right.delay_lut);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void vibratocore_init(struct vibrato_core *core,float depth,int samplerate,float freq)
|
||||
{
|
||||
core->size = VIBRATO_BASE_DELAY_SEC * samplerate * 2;
|
||||
core->buffer = (float*)malloc((core->size + VIBRATO_ADD_DELAY) * sizeof(float));
|
||||
memset(core->buffer, 0, (core->size + VIBRATO_ADD_DELAY) * sizeof(float));
|
||||
core->samplerate = samplerate;
|
||||
core->freq = freq;
|
||||
core->depth = depth;
|
||||
core->phase = 0;
|
||||
core->writeindex = 0;
|
||||
|
||||
/* int16 mirror of the delay line, and a precomputed Q16 per-phase delay
|
||||
* LUT built from the same double-precision sine the float path uses, so
|
||||
* the int16 path needs no FPU at run time. */
|
||||
core->buffer_i = (int16_t*)calloc(core->size + VIBRATO_ADD_DELAY, sizeof(int16_t));
|
||||
core->maxphase = samplerate / freq;
|
||||
if (core->maxphase < 1)
|
||||
core->maxphase = 1;
|
||||
core->delay_lut = (int32_t*)malloc(core->maxphase * sizeof(int32_t));
|
||||
if (core->buffer_i && core->delay_lut)
|
||||
{
|
||||
float M = freq / samplerate;
|
||||
int maxdelay = VIBRATO_BASE_DELAY_SEC * samplerate;
|
||||
int p;
|
||||
for (p = 0; p < core->maxphase; p++)
|
||||
{
|
||||
double lfo = sin((double)M * 2.0 * M_PI * p);
|
||||
double delay = (lfo + 1.0) * depth * maxdelay + VIBRATO_ADD_DELAY;
|
||||
core->delay_lut[p] = (int32_t)floor(delay * 65536.0 + 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float vibratocore_core(struct vibrato_core *core,float in)
|
||||
{
|
||||
int ipart;
|
||||
float delay, readindex, fpart, value;
|
||||
float M = core->freq / core->samplerate;
|
||||
int maxphase = core->samplerate / core->freq;
|
||||
float lfo = sin(M * 2. * M_PI * core->phase++);
|
||||
int maxdelay = VIBRATO_BASE_DELAY_SEC * core->samplerate;
|
||||
core->phase = core->phase % maxphase;
|
||||
lfo = (lfo + 1) * 1.; /* Transform from [-1; 1] to [0; 1] */
|
||||
delay = lfo * core->depth * maxdelay;
|
||||
delay += VIBRATO_ADD_DELAY;
|
||||
readindex = core->writeindex - 1 - delay;
|
||||
while (readindex < 0)
|
||||
readindex += core->size;
|
||||
while (readindex >= core->size)
|
||||
readindex -= core->size;
|
||||
ipart = (int)readindex; /* Integer part of the delay */
|
||||
fpart = readindex - ipart; /* fractional part of the delay */
|
||||
value = hermite_interp(fpart, &(core->buffer[ipart]));
|
||||
core->buffer[core->writeindex] = in;
|
||||
if (core->writeindex < VIBRATO_ADD_DELAY)
|
||||
core->buffer[core->size + core->writeindex] = in;
|
||||
core->writeindex++;
|
||||
if (core->writeindex == core->size)
|
||||
core->writeindex = 0;
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Deterministic int16 path: same modulated fractional delay and 4-point
|
||||
* Hermite interpolation as vibratocore_core(), driven by the precomputed Q16
|
||||
* delay LUT and an int16 delay line. */
|
||||
static int16_t vibratocore_core_i16(struct vibrato_core *core, int16_t in)
|
||||
{
|
||||
int ipart;
|
||||
int32_t x_q16, value;
|
||||
int64_t readindex_q16, size_q16;
|
||||
int32_t delay_q16 = core->delay_lut[core->phase++];
|
||||
core->phase = core->phase % core->maxphase;
|
||||
|
||||
size_q16 = (int64_t)core->size << 16;
|
||||
readindex_q16 = (int64_t)(core->writeindex - 1) * 65536 - delay_q16;
|
||||
while (readindex_q16 < 0)
|
||||
readindex_q16 += size_q16;
|
||||
while (readindex_q16 >= size_q16)
|
||||
readindex_q16 -= size_q16;
|
||||
|
||||
ipart = (int)(readindex_q16 >> 16);
|
||||
x_q16 = (int32_t)(readindex_q16 - ((int64_t)ipart << 16));
|
||||
value = hermite_interp_i16(x_q16, &core->buffer_i[ipart]);
|
||||
|
||||
core->buffer_i[core->writeindex] = in;
|
||||
if (core->writeindex < VIBRATO_ADD_DELAY)
|
||||
core->buffer_i[core->size + core->writeindex] = in;
|
||||
core->writeindex++;
|
||||
if (core->writeindex == core->size)
|
||||
core->writeindex = 0;
|
||||
return (int16_t)value;
|
||||
}
|
||||
|
||||
static void vibrato_process_i16(void *data,
|
||||
struct dspfilter_output_i16 *output,
|
||||
const struct dspfilter_input_i16 *input)
|
||||
{
|
||||
unsigned i;
|
||||
int16_t *out;
|
||||
struct vibrato *vib = (struct vibrato*)data;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
out = output->samples;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
int16_t in[2] = { out[0], out[1] };
|
||||
out[0] = vibratocore_core_i16(&vib->left, in[0]);
|
||||
out[1] = vibratocore_core_i16(&vib->right, in[1]);
|
||||
}
|
||||
}
|
||||
|
||||
static void vibrato_process(void *data,
|
||||
struct dspfilter_output *output,
|
||||
const struct dspfilter_input *input)
|
||||
{
|
||||
unsigned i;
|
||||
float *out;
|
||||
struct vibrato *vib = (struct vibrato*)data;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
out = output->samples;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
float in[2] = { out[0], out[1] };
|
||||
out[0] = vibratocore_core(&vib->left, in[0]);
|
||||
out[1] = vibratocore_core(&vib->right, in[1]);
|
||||
}
|
||||
}
|
||||
|
||||
static void *vibrato_init(const struct dspfilter_info *info,
|
||||
const struct dspfilter_config *config, void *userdata)
|
||||
{
|
||||
float freq, depth;
|
||||
struct vibrato *vib = (struct vibrato*)calloc(1, sizeof(*vib));
|
||||
if (!vib)
|
||||
return NULL;
|
||||
|
||||
config->get_float(userdata, "freq", &freq,5.0f);
|
||||
config->get_float(userdata, "depth", &depth, 0.5f);
|
||||
vibratocore_init(&vib->left,depth,info->input_rate,freq);
|
||||
vibratocore_init(&vib->right,depth,info->input_rate,freq);
|
||||
return vib;
|
||||
}
|
||||
|
||||
static const struct dspfilter_implementation vibrato_plug = {
|
||||
vibrato_init,
|
||||
vibrato_process,
|
||||
vibrato_free,
|
||||
|
||||
DSPFILTER_API_VERSION,
|
||||
"Vibrato",
|
||||
"vibrato",
|
||||
|
||||
vibrato_process_i16,
|
||||
};
|
||||
|
||||
#ifdef HAVE_FILTERS_BUILTIN
|
||||
#define dspfilter_get_implementation vibrato_dspfilter_get_implementation
|
||||
#endif
|
||||
|
||||
const struct dspfilter_implementation *dspfilter_get_implementation(dspfilter_simd_mask_t mask)
|
||||
{
|
||||
return &vibrato_plug;
|
||||
}
|
||||
|
||||
#undef dspfilter_get_implementation
|
||||
@@ -1,199 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (wahwah.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 <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <libretro_dspfilter.h>
|
||||
|
||||
#define WAHWAH_LFO_SKIP_SAMPLES 30
|
||||
|
||||
struct wahwah_data
|
||||
{
|
||||
float phase;
|
||||
float lfoskip;
|
||||
float b0, b1, b2, a0, a1, a2;
|
||||
float freq, startphase;
|
||||
float depth, freqofs, res;
|
||||
unsigned long skipcount;
|
||||
|
||||
struct
|
||||
{
|
||||
float xn1, xn2, yn1, yn2;
|
||||
} l, r;
|
||||
};
|
||||
|
||||
static void wahwah_free(void *data)
|
||||
{
|
||||
if (data)
|
||||
free(data);
|
||||
}
|
||||
|
||||
/* One stereo frame of wah-wah. Shared by the float and int16 entry points so
|
||||
* the (inherently floating-point) LFO/biquad math is identical for both. */
|
||||
static INLINE void wahwah_frame(struct wahwah_data *wah,
|
||||
const float in[2], float *out)
|
||||
{
|
||||
float out_l, out_r;
|
||||
|
||||
if ((wah->skipcount++ % WAHWAH_LFO_SKIP_SAMPLES) == 0)
|
||||
{
|
||||
float omega, sn, cs, alpha;
|
||||
float frequency = (1.0f + cos(wah->skipcount * wah->lfoskip + wah->phase)) / 2.0f;
|
||||
|
||||
frequency = frequency * wah->depth * (1.0f - wah->freqofs) + wah->freqofs;
|
||||
frequency = exp((frequency - 1.0f) * 6.0f);
|
||||
|
||||
omega = M_PI * frequency;
|
||||
sn = sin(omega);
|
||||
cs = cos(omega);
|
||||
alpha = sn / (2.0f * wah->res);
|
||||
|
||||
wah->b0 = (1.0f - cs) / 2.0f;
|
||||
wah->b1 = 1.0f - cs;
|
||||
wah->b2 = (1.0f - cs) / 2.0f;
|
||||
wah->a0 = 1.0f + alpha;
|
||||
wah->a1 = -2.0f * cs;
|
||||
wah->a2 = 1.0f - alpha;
|
||||
}
|
||||
|
||||
out_l = (wah->b0 * in[0] + wah->b1 * wah->l.xn1 + wah->b2 * wah->l.xn2 - wah->a1 * wah->l.yn1 - wah->a2 * wah->l.yn2) / wah->a0;
|
||||
out_r = (wah->b0 * in[1] + wah->b1 * wah->r.xn1 + wah->b2 * wah->r.xn2 - wah->a1 * wah->r.yn1 - wah->a2 * wah->r.yn2) / wah->a0;
|
||||
|
||||
wah->l.xn2 = wah->l.xn1;
|
||||
wah->l.xn1 = in[0];
|
||||
wah->l.yn2 = wah->l.yn1;
|
||||
wah->l.yn1 = out_l;
|
||||
|
||||
wah->r.xn2 = wah->r.xn1;
|
||||
wah->r.xn1 = in[1];
|
||||
wah->r.yn2 = wah->r.yn1;
|
||||
wah->r.yn1 = out_r;
|
||||
|
||||
out[0] = out_l;
|
||||
out[1] = out_r;
|
||||
}
|
||||
|
||||
static void wahwah_process(void *data, struct dspfilter_output *output,
|
||||
const struct dspfilter_input *input)
|
||||
{
|
||||
unsigned i;
|
||||
struct wahwah_data *wah = (struct wahwah_data*)data;
|
||||
float *out = output->samples;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
float in[2] = { out[0], out[1] };
|
||||
wahwah_frame(wah, in, out);
|
||||
}
|
||||
}
|
||||
|
||||
/* int16 entry point: bridge through the shared float frame routine so an
|
||||
* int16 chain containing the wah-wah stays on the deterministic s16 path.
|
||||
* The filter recomputes its resonant biquad coefficients from cos()/sin()/
|
||||
* exp() every LFO step, so a genuinely FPU-free port would only ever be
|
||||
* approximate; the game signal still stays on the integer path, converted
|
||||
* here (s16 <-> the host's normalized [-1,1] float domain) around the
|
||||
* coefficient math. */
|
||||
static void wahwah_process_i16(void *data, struct dspfilter_output_i16 *output,
|
||||
const struct dspfilter_input_i16 *input)
|
||||
{
|
||||
unsigned i;
|
||||
struct wahwah_data *wah = (struct wahwah_data*)data;
|
||||
int16_t *out = NULL;
|
||||
|
||||
output->samples = input->samples;
|
||||
output->frames = input->frames;
|
||||
out = output->samples;
|
||||
|
||||
for (i = 0; i < input->frames; i++, out += 2)
|
||||
{
|
||||
float in[2], o[2];
|
||||
int32_t v;
|
||||
|
||||
in[0] = (float)out[0] * (1.0f / 32768.0f);
|
||||
in[1] = (float)out[1] * (1.0f / 32768.0f);
|
||||
|
||||
wahwah_frame(wah, in, o);
|
||||
|
||||
v = (o[0] >= 0.0f) ? (int32_t)(o[0] * 32768.0f + 0.5f)
|
||||
: (int32_t)(o[0] * 32768.0f - 0.5f);
|
||||
if (v > 32767) v = 32767;
|
||||
else if (v < -32768) v = -32768;
|
||||
out[0] = (int16_t)v;
|
||||
|
||||
v = (o[1] >= 0.0f) ? (int32_t)(o[1] * 32768.0f + 0.5f)
|
||||
: (int32_t)(o[1] * 32768.0f - 0.5f);
|
||||
if (v > 32767) v = 32767;
|
||||
else if (v < -32768) v = -32768;
|
||||
out[1] = (int16_t)v;
|
||||
}
|
||||
}
|
||||
|
||||
static void *wahwah_init(const struct dspfilter_info *info,
|
||||
const struct dspfilter_config *config, void *userdata)
|
||||
{
|
||||
struct wahwah_data *wah = (struct wahwah_data*)calloc(1, sizeof(*wah));
|
||||
if (!wah)
|
||||
return NULL;
|
||||
|
||||
config->get_float(userdata, "lfo_freq", &wah->freq, 1.5f);
|
||||
config->get_float(userdata, "lfo_start_phase", &wah->startphase, 0.0f);
|
||||
config->get_float(userdata, "freq_offset", &wah->freqofs, 0.3f);
|
||||
config->get_float(userdata, "depth", &wah->depth, 0.7f);
|
||||
config->get_float(userdata, "resonance", &wah->res, 2.5f);
|
||||
|
||||
wah->lfoskip = wah->freq * 2.0f * M_PI / info->input_rate;
|
||||
wah->phase = wah->startphase * M_PI / 180.0f;
|
||||
|
||||
return wah;
|
||||
}
|
||||
|
||||
static const struct dspfilter_implementation wahwah_plug = {
|
||||
wahwah_init,
|
||||
wahwah_process,
|
||||
wahwah_free,
|
||||
|
||||
DSPFILTER_API_VERSION,
|
||||
"Wah-Wah",
|
||||
"wahwah",
|
||||
|
||||
wahwah_process_i16,
|
||||
};
|
||||
|
||||
#ifdef HAVE_FILTERS_BUILTIN
|
||||
#define dspfilter_get_implementation wahwah_dspfilter_get_implementation
|
||||
#endif
|
||||
|
||||
const struct dspfilter_implementation *
|
||||
dspfilter_get_implementation(dspfilter_simd_mask_t mask)
|
||||
{
|
||||
(void)mask;
|
||||
return &wahwah_plug;
|
||||
}
|
||||
|
||||
#undef dspfilter_get_implementation
|
||||
@@ -1,189 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (audio_resampler.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 <string.h>
|
||||
|
||||
#include <string/stdstring.h>
|
||||
#include <features/features_cpu.h>
|
||||
#include <file/config_file_userdata.h>
|
||||
|
||||
#include <audio/audio_resampler.h>
|
||||
|
||||
static void resampler_null_process(void *a, struct resampler_data *b) { }
|
||||
static void resampler_null_free(void *a) { }
|
||||
static void *resampler_null_init(const struct resampler_config *a, double b,
|
||||
enum resampler_quality c, resampler_simd_mask_t d) { return (void*)0; }
|
||||
|
||||
retro_resampler_t null_resampler = {
|
||||
resampler_null_init,
|
||||
resampler_null_process,
|
||||
resampler_null_free,
|
||||
RESAMPLER_API_VERSION,
|
||||
"null",
|
||||
"null"
|
||||
};
|
||||
|
||||
static const retro_resampler_t *resampler_drivers[] = {
|
||||
&sinc_resampler,
|
||||
#ifdef HAVE_CC_RESAMPLER
|
||||
&CC_resampler,
|
||||
#endif
|
||||
#ifdef HAVE_NEAREST_RESAMPLER
|
||||
&nearest_resampler,
|
||||
#endif
|
||||
&null_resampler,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct resampler_config resampler_config = {
|
||||
config_userdata_get_float,
|
||||
config_userdata_get_int,
|
||||
config_userdata_get_float_array,
|
||||
config_userdata_get_int_array,
|
||||
config_userdata_get_string,
|
||||
config_userdata_free,
|
||||
};
|
||||
|
||||
/**
|
||||
* find_resampler_driver_index:
|
||||
* @ident : Identifier of resampler driver to find.
|
||||
*
|
||||
* Finds resampler driver index by @ident name.
|
||||
*
|
||||
* Returns: resampler driver index if resampler driver was found, otherwise
|
||||
* -1.
|
||||
**/
|
||||
static int find_resampler_driver_index(const char *ident)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; resampler_drivers[i]; i++)
|
||||
if (string_is_equal_noncase(ident, resampler_drivers[i]->ident))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* find_resampler_driver:
|
||||
* @ident : Identifier of resampler driver to find.
|
||||
*
|
||||
* Finds resampler by @ident name.
|
||||
*
|
||||
* Returns: resampler driver if resampler driver was found, otherwise
|
||||
* NULL.
|
||||
**/
|
||||
static const retro_resampler_t *find_resampler_driver(const char *ident)
|
||||
{
|
||||
int i = find_resampler_driver_index(ident);
|
||||
|
||||
if (i >= 0)
|
||||
return resampler_drivers[i];
|
||||
|
||||
return resampler_drivers[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* resampler_append_plugs:
|
||||
* @re : Resampler handle
|
||||
* @backend : Resampler backend that is about to be set.
|
||||
* @bw_ratio : Bandwidth ratio.
|
||||
*
|
||||
* Initializes resampler driver based on queried CPU features.
|
||||
*
|
||||
* Returns: true (1) if successfully initialized, otherwise false (0).
|
||||
**/
|
||||
static bool resampler_append_plugs(void **re,
|
||||
const retro_resampler_t **backend,
|
||||
enum resampler_quality quality,
|
||||
double bw_ratio)
|
||||
{
|
||||
resampler_simd_mask_t mask = (resampler_simd_mask_t)cpu_features_get();
|
||||
|
||||
if (*backend)
|
||||
*re = (*backend)->init(&resampler_config, bw_ratio, quality, mask);
|
||||
|
||||
if (!*re)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* audio_resampler_driver_find_handle:
|
||||
* @idx : index of driver to get handle to.
|
||||
*
|
||||
* Returns: handle to audio resampler driver at index. Can be NULL
|
||||
* if nothing found.
|
||||
**/
|
||||
const void *audio_resampler_driver_find_handle(int idx)
|
||||
{
|
||||
const void *drv = resampler_drivers[idx];
|
||||
if (!drv)
|
||||
return NULL;
|
||||
return drv;
|
||||
}
|
||||
|
||||
/**
|
||||
* audio_resampler_driver_find_ident:
|
||||
* @idx : index of driver to get handle to.
|
||||
*
|
||||
* Returns: Human-readable identifier of audio resampler driver at index.
|
||||
* Can be NULL if nothing found.
|
||||
**/
|
||||
const char *audio_resampler_driver_find_ident(int idx)
|
||||
{
|
||||
const retro_resampler_t *drv = resampler_drivers[idx];
|
||||
if (!drv)
|
||||
return NULL;
|
||||
return drv->ident;
|
||||
}
|
||||
|
||||
/**
|
||||
* retro_resampler_realloc:
|
||||
* @re : Resampler handle
|
||||
* @backend : Resampler backend that is about to be set.
|
||||
* @ident : Identifier name for resampler we want.
|
||||
* @bw_ratio : Bandwidth ratio.
|
||||
*
|
||||
* Reallocates resampler. Will free previous handle before
|
||||
* allocating a new one. If ident is NULL, first resampler will be used.
|
||||
*
|
||||
* Returns: true (1) if successful, otherwise false (0).
|
||||
**/
|
||||
bool retro_resampler_realloc(void **re, const retro_resampler_t **backend,
|
||||
const char *ident, enum resampler_quality quality, double bw_ratio)
|
||||
{
|
||||
if (*re && *backend)
|
||||
(*backend)->free(*re);
|
||||
|
||||
*re = NULL;
|
||||
*backend = find_resampler_driver(ident);
|
||||
|
||||
if (!resampler_append_plugs(re, backend, quality, bw_ratio))
|
||||
{
|
||||
if (!*re)
|
||||
*backend = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
# Integer (s16) SINC resampler — integration notes
|
||||
|
||||
This adds a deterministic, integer-only counterpart to the float `sinc`
|
||||
resampler (`drivers/sinc_resampler.c`). It exists to:
|
||||
|
||||
1. **Determinism.** The float driver is built with `-ffast-math`; its output
|
||||
is not bit-reproducible across compilers/archs/FMA settings. This driver is
|
||||
integer-only and bit-identical everywhere — reproducible output for
|
||||
validation and regression comparison. (Resampling is downstream of the
|
||||
core, so this does not affect netplay or rewind, which synchronise on
|
||||
inputs and savestates.)
|
||||
2. **Round-trip removal.** The libretro audio callback is int16-only, so
|
||||
RetroArch converts s16→float for the float resampler on *every* core, every
|
||||
frame, and back to s16 for s16 output devices. The int16 driver skips both
|
||||
conversions.
|
||||
3. **No FPU dependency** — helps on PS2 / 3DS / older ARM.
|
||||
|
||||
## Files
|
||||
|
||||
- `drivers/sinc_resampler_int16.{c,h}` — the driver (scalar, C89/MSVC-clean).
|
||||
- `test/test_sinc_int16.c`, `test/sinc_ref_float.{c,h}` — bit-exactness harness.
|
||||
|
||||
Build & run the harness standalone (the public header now lives in
|
||||
`libretro-common/include/audio/`, so point `-I` there):
|
||||
|
||||
cc -O2 -std=c89 -pedantic -Wall -Wextra -Ilibretro-common/include \
|
||||
libretro-common/audio/resampler/test/test_sinc_int16.c \
|
||||
libretro-common/audio/resampler/drivers/sinc_resampler_int16.c \
|
||||
libretro-common/audio/resampler/test/sinc_ref_float.c \
|
||||
-lm -o /tmp/test_sinc_int16 && /tmp/test_sinc_int16
|
||||
|
||||
## Measured vs the float driver (int16 endpoint)
|
||||
|
||||
Across a rate × quality × signal matrix, the integer driver is **bit-transparent**
|
||||
to the float driver:
|
||||
|
||||
- worst case **1 LSB**, on **< 0.5 %** of output samples (scales with tap count);
|
||||
- **0 LSB — bit-identical** on integer ratios (48000→96000, 22050→44100);
|
||||
- **THD+N SNR identical** to the float driver to ≤ 0.01 dB in every case
|
||||
(e.g. HIGHER/HIGHEST 48000→96000 both hit the 16-bit ceiling of ~96–97 dB;
|
||||
NORMAL 32040→96000 both 62.80 dB — that floor is the filter's own image
|
||||
rejection, not a fixed-point artifact).
|
||||
|
||||
The ≤1 LSB residual is the float32-accumulation-vs-int64 difference resolved at
|
||||
the final 16-bit quantization; the integer path (int64 exact accumulate) is if
|
||||
anything the more correct of the two.
|
||||
|
||||
## Fixed-point format
|
||||
|
||||
- Coefficients: **Q1.30 int32** (same table footprint as float32 — no memory cost).
|
||||
- Samples: int16; products accumulate in **int64** (16-bit × 30-bit × ≤256 taps
|
||||
≈ 54 bits — int64 is required for HIGHEST).
|
||||
- Output: round-half-away-from-zero shift back to int16 with saturation.
|
||||
Rounding (not truncation) is mandatory — truncation costs a constant −0.5 LSB
|
||||
DC bias and ~2.7 dB at −80 dBFS.
|
||||
- No implementation-defined signed shifts are used (all shifts operate on
|
||||
non-negative magnitudes), so the result is portable and deterministic.
|
||||
|
||||
## Fast path in `audio/audio_driver.c` (wired)
|
||||
|
||||
The shared `retro_resampler` vtable is float-typed, so rather than change the
|
||||
float ABI, the integer driver is held as a parallel handle
|
||||
(`audio_driver_state_t::resampler_data_int16`) and used by the s16 path in
|
||||
`audio_driver_flush()`.
|
||||
|
||||
- **Init** (`audio_driver_init_internal`): after `retro_resampler_realloc`, an
|
||||
int16 instance is created for the selected backend when one exists — this is
|
||||
no longer sinc-only: `"sinc"` (`sinc_resampler_int16_init(src_ratio_orig,
|
||||
<mapped quality>)`), `"nearest"` and `"cc"` all have int16 variants. Logs
|
||||
`"[Audio] <ident> resampler: integer s16 path available"`.
|
||||
- **Deinit** (`audio_driver_deinit_resampler`): frees the int16 handle.
|
||||
- **Flush** (`audio_driver_flush`): a branch after the existing `write_raw`
|
||||
s16 path takes the integer route when int16 core audio arrives and an int16
|
||||
handle exists. The gate has been relaxed since this note was first written:
|
||||
volume is applied in fixed point (non-unity gain stays integer), MIDI synth
|
||||
output is folded in as s16, fast-forward pitch tracking applies to the
|
||||
integer ratio, and int16-capable DSP chains run in the integer domain — only
|
||||
an incompatible (float-only) DSP filter forces the float path. See the
|
||||
comment block above the gate in `audio_driver_flush` for the authoritative
|
||||
list. It runs the same DRC
|
||||
(`src_ratio_orig * audio_driver_compute_rate_adjust`) and slow-motion scaling
|
||||
as the float path, resamples s16->s16 into `output_samples_conv_buf`, and
|
||||
writes directly (or does a single s16->float pass for float-output drivers).
|
||||
Any condition failing falls through to the float path unchanged.
|
||||
|
||||
### Which path is running — log
|
||||
|
||||
`audio_driver_flush()` logs on every **transition** (not per-flush):
|
||||
|
||||
[Audio] SINC resampler active path: integer s16 (no float round-trip)
|
||||
[Audio] SINC resampler active path: float
|
||||
|
||||
So the line at startup tells you which path is live, and it re-logs whenever the
|
||||
conditions flip (e.g. a DSP plugin is toggled, the mixer starts, volume leaves
|
||||
0 dB, or fast-forward-with-speedup engages), which is exactly when it falls back
|
||||
to float.
|
||||
|
||||
### Menu setting
|
||||
|
||||
Exposed as **Settings > Audio > Resampler > "Resample to Fixed Integer (Hint)"**
|
||||
(`audio_fastpath_s16`, default on). When on and a core outputs 16-bit integer
|
||||
audio, any needed resampling uses the integer SINC driver instead of the float
|
||||
one. Ignored for float-output cores and whenever DSP / mixer / MIDI synth is
|
||||
active. Toggling it flips the logged active path (above) without an audio
|
||||
reinit.
|
||||
|
||||
### Volume
|
||||
|
||||
Volume/mute is applied inside the s16 path, so non-unity gain no longer forces
|
||||
the float route:
|
||||
|
||||
- **s16-output** driver: a deterministic Q16 gain multiply + saturate over the
|
||||
output buffer (skipped at unity gain).
|
||||
- **float-output** driver: folded into the single s16->float pass via
|
||||
`convert_s16_to_float(..., audio_volume_gain)` - free.
|
||||
|
||||
Because resampling is linear, attenuation is bit-clean; a boost above 0 dB
|
||||
interacts with s16 saturation slightly differently than the float path's
|
||||
input-side gain, which is acceptable for s16-source content.
|
||||
|
||||
### DSP filters (int16 chain)
|
||||
|
||||
Previously any active audio DSP filter forced the float path. The DSP filter
|
||||
plugin ABI is now **version 2**, adding an optional `process_i16` entry point
|
||||
and `dspfilter_{input,output}_i16` structs (`libretro_dspfilter.h`). The
|
||||
framework gained:
|
||||
|
||||
- `retro_dsp_filter_supports_int16(dsp)` - true only if every filter in the
|
||||
chain provides `process_i16` (API version >= 2);
|
||||
- `retro_dsp_filter_process_int16(dsp, data)` - runs the chain in int16.
|
||||
|
||||
`audio_driver_flush()` now keeps DSP inside the integer s16 path when
|
||||
`retro_dsp_filter_supports_int16()` is true: it copies the core's s16 into an
|
||||
int16 scratch (`input_data_int16`), runs the int16 chain in place, then feeds
|
||||
the result to the integer resampler. If any filter in the chain is float-only,
|
||||
the whole flush falls back to the float path as before.
|
||||
|
||||
The version check was relaxed from strict equality to a range, so existing
|
||||
float-only v1 plugins keep loading (their shorter struct is never over-read
|
||||
because `process_i16` is only touched after an `api_version >= 2` check).
|
||||
|
||||
`panning` is converted as the reference v2 filter: the 2x2 mix runs in Q16
|
||||
fixed point (int64 accumulate, round-half-away, saturate) and measures 1 LSB
|
||||
worst case vs the float version. Other bundled filters remain float-only
|
||||
(`process_i16 == NULL`) until converted, so they transparently keep using the
|
||||
float path.
|
||||
|
||||
### Notes
|
||||
|
||||
- For a float-output driver the integer path saturates to the s16 range before
|
||||
the s16->float pass, so it does not reproduce sinc overshoot headroom a float
|
||||
endpoint could otherwise carry; intended for s16-source content and
|
||||
determinism. Turn the hint off to keep the float path (and that headroom).
|
||||
@@ -1,84 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (nearest_resampler.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <audio/audio_resampler.h>
|
||||
|
||||
typedef struct rarch_nearest_resampler
|
||||
{
|
||||
float fraction;
|
||||
} rarch_nearest_resampler_t;
|
||||
|
||||
static void resampler_nearest_process(
|
||||
void *re_, struct resampler_data *data)
|
||||
{
|
||||
rarch_nearest_resampler_t *re = (rarch_nearest_resampler_t*)re_;
|
||||
audio_frame_float_t *inp = (audio_frame_float_t*)data->data_in;
|
||||
audio_frame_float_t *inp_max = (audio_frame_float_t*)inp + data->input_frames;
|
||||
audio_frame_float_t *outp = (audio_frame_float_t*)data->data_out;
|
||||
float ratio = 1.0 / data->ratio;
|
||||
|
||||
while (inp != inp_max)
|
||||
{
|
||||
while (re->fraction > 1)
|
||||
{
|
||||
*outp++ = *inp;
|
||||
re->fraction -= ratio;
|
||||
}
|
||||
re->fraction++;
|
||||
inp++;
|
||||
}
|
||||
|
||||
data->output_frames = (outp - (audio_frame_float_t*)data->data_out);
|
||||
}
|
||||
|
||||
static void resampler_nearest_free(void *re_)
|
||||
{
|
||||
rarch_nearest_resampler_t *re = (rarch_nearest_resampler_t*)re_;
|
||||
if (re)
|
||||
free(re);
|
||||
}
|
||||
|
||||
static void *resampler_nearest_init(const struct resampler_config *config,
|
||||
double bandwidth_mod,
|
||||
enum resampler_quality quality,
|
||||
resampler_simd_mask_t mask)
|
||||
{
|
||||
rarch_nearest_resampler_t *re = (rarch_nearest_resampler_t*)
|
||||
calloc(1, sizeof(rarch_nearest_resampler_t));
|
||||
if (!re)
|
||||
return NULL;
|
||||
re->fraction = 0;
|
||||
return re;
|
||||
}
|
||||
|
||||
retro_resampler_t nearest_resampler = {
|
||||
resampler_nearest_init,
|
||||
resampler_nearest_process,
|
||||
resampler_nearest_free,
|
||||
RESAMPLER_API_VERSION,
|
||||
"nearest",
|
||||
"nearest"
|
||||
};
|
||||
@@ -1,86 +0,0 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file
|
||||
* (nearest_resampler_int16.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.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <audio/nearest_resampler_int16.h>
|
||||
|
||||
/* The phase accumulator "fraction" and the per-output step are held in a
|
||||
* signed Q32 fixed-point value (int64_t). This mirrors the float driver's
|
||||
* (fraction += 1 per input; fraction -= 1/ratio per output) exactly, but the
|
||||
* accumulation is pure integer so the output timing is bit-identical across
|
||||
* platforms. Signed, so heavy downsampling (step > 1.0) can drive the
|
||||
* fraction negative without the unsigned wrap that would corrupt the loop. */
|
||||
#define NEAREST_I16_FRAC_BITS 32
|
||||
|
||||
typedef struct nearest_resampler_int16
|
||||
{
|
||||
int64_t fraction; /* Q32 */
|
||||
} nearest_resampler_int16_t;
|
||||
|
||||
void *nearest_resampler_int16_init(void)
|
||||
{
|
||||
nearest_resampler_int16_t *re = (nearest_resampler_int16_t*)
|
||||
calloc(1, sizeof(*re));
|
||||
if (!re)
|
||||
return NULL;
|
||||
re->fraction = 0;
|
||||
return re;
|
||||
}
|
||||
|
||||
void nearest_resampler_int16_process(void *re_,
|
||||
struct resampler_data_int16 *data)
|
||||
{
|
||||
nearest_resampler_int16_t *re = (nearest_resampler_int16_t*)re_;
|
||||
const int16_t *inp = data->data_in;
|
||||
const int16_t *inp_max = inp + data->input_frames * 2;
|
||||
int16_t *outp = data->data_out;
|
||||
int16_t *outp_first = outp;
|
||||
const int64_t one = (int64_t)1 << NEAREST_I16_FRAC_BITS;
|
||||
/* step = (1.0 / ratio) in Q32, i.e. input frames consumed per output. */
|
||||
int64_t step = (int64_t)
|
||||
((1.0 / data->ratio) * (double)one + 0.5);
|
||||
|
||||
while (inp != inp_max)
|
||||
{
|
||||
while (re->fraction > one)
|
||||
{
|
||||
outp[0] = inp[0];
|
||||
outp[1] = inp[1];
|
||||
outp += 2;
|
||||
re->fraction -= step;
|
||||
}
|
||||
re->fraction += one;
|
||||
inp += 2;
|
||||
}
|
||||
|
||||
data->output_frames = (size_t)((outp - outp_first) >> 1);
|
||||
}
|
||||
|
||||
void nearest_resampler_int16_free(void *re_)
|
||||
{
|
||||
nearest_resampler_int16_t *re = (nearest_resampler_int16_t*)re_;
|
||||
if (re)
|
||||
free(re);
|
||||
}
|
||||
@@ -1,988 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (sinc_resampler.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.
|
||||
*/
|
||||
|
||||
/* Bog-standard windowed SINC implementation. */
|
||||
|
||||
#if defined(__GNUC__) && defined(__OPTIMIZE__) && !defined(__clang__)
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("fast-math")
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <retro_environment.h>
|
||||
#include <retro_inline.h>
|
||||
#include <filters.h>
|
||||
#include <memalign.h>
|
||||
|
||||
#include <audio/audio_resampler.h>
|
||||
|
||||
#ifdef __SSE__
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
#if defined(__AVX__)
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
/* Rough SNR values for upsampling:
|
||||
* LOWEST: 40 dB
|
||||
* LOWER: 55 dB
|
||||
* NORMAL: 70 dB
|
||||
* HIGHER: 110 dB
|
||||
* HIGHEST: 140 dB
|
||||
*/
|
||||
|
||||
/* TODO, make all this more configurable. */
|
||||
|
||||
enum sinc_window
|
||||
{
|
||||
SINC_WINDOW_NONE = 0,
|
||||
SINC_WINDOW_KAISER,
|
||||
SINC_WINDOW_LANCZOS
|
||||
};
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
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. */
|
||||
float *main_buffer;
|
||||
float *phase_table;
|
||||
float *buffer_l;
|
||||
float *buffer_r;
|
||||
unsigned phase_bits;
|
||||
unsigned subphase_bits;
|
||||
unsigned subphase_mask;
|
||||
unsigned taps;
|
||||
unsigned ptr;
|
||||
uint32_t time;
|
||||
float subphase_mod;
|
||||
float kaiser_beta;
|
||||
/* Per-instance kernel. The global sinc_resampler vtable is shared by
|
||||
* every instance (game audio, microphone, mixer voices), so the kernel
|
||||
* choice must live here: window types lay out phase_table differently
|
||||
* (Kaiser interleaves coeff+delta rows at stride taps*2, Lanczos rows
|
||||
* are stride taps), and letting a later init of one window type rewrite
|
||||
* a shared function pointer makes an earlier instance of the other type
|
||||
* read its table at the wrong stride - deltas get used as coefficients,
|
||||
* collapsing output level on a per-phase basis. */
|
||||
void (*process)(void *re, struct resampler_data *data);
|
||||
} rarch_sinc_resampler_t;
|
||||
|
||||
/*
|
||||
* Macro to handle the common "push input samples into ring buffer"
|
||||
* logic shared by every process function. Reduces code duplication and
|
||||
* ensures the input-feeding logic stays consistent across all paths.
|
||||
*/
|
||||
#define SINC_PUSH_INPUT_SAMPLES(resamp, input, taps, phases, frames) \
|
||||
do { \
|
||||
while ((frames) && (resamp)->time >= (phases)) \
|
||||
{ \
|
||||
if (!(resamp)->ptr) \
|
||||
(resamp)->ptr = (taps); \
|
||||
(resamp)->ptr--; \
|
||||
(resamp)->buffer_l[(resamp)->ptr + (taps)] = \
|
||||
(resamp)->buffer_l[(resamp)->ptr] = *(input)++; \
|
||||
(resamp)->buffer_r[(resamp)->ptr + (taps)] = \
|
||||
(resamp)->buffer_r[(resamp)->ptr] = *(input)++; \
|
||||
(resamp)->time -= (phases); \
|
||||
(frames)--; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
#if (defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(HAVE_NEON))
|
||||
|
||||
#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
|
||||
#include <arm_neon.h>
|
||||
|
||||
/* Assumes that taps >= 8, and that taps is a multiple of 8.
|
||||
* Not bothering to reimplement this one for the external .S
|
||||
*/
|
||||
static void resampler_sinc_process_neon_kaiser(void *re_, struct resampler_data *data)
|
||||
{
|
||||
rarch_sinc_resampler_t *resamp = (rarch_sinc_resampler_t*)re_;
|
||||
unsigned phases = 1 << (resamp->phase_bits + resamp->subphase_bits);
|
||||
uint32_t ratio = phases / data->ratio;
|
||||
const float *input = data->data_in;
|
||||
float *output = data->data_out;
|
||||
size_t frames = data->input_frames;
|
||||
size_t out_frames = 0;
|
||||
unsigned taps = resamp->taps;
|
||||
unsigned taps2 = taps * 2;
|
||||
|
||||
while (frames)
|
||||
{
|
||||
SINC_PUSH_INPUT_SAMPLES(resamp, input, taps, phases, frames);
|
||||
|
||||
{
|
||||
const float *buffer_l = resamp->buffer_l + resamp->ptr;
|
||||
const float *buffer_r = resamp->buffer_r + resamp->ptr;
|
||||
while (resamp->time < phases)
|
||||
{
|
||||
/* 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);
|
||||
float32x4_t p2 = vdupq_n_f32(0.0f);
|
||||
|
||||
for (i = 0; i < (int)taps; i += 8)
|
||||
{
|
||||
float32x4x2_t coeff8 = vld2q_f32(&phase_table[i]);
|
||||
float32x4x2_t delta8 = vld2q_f32(&delta_table[i]);
|
||||
float32x4x2_t left8 = vld2q_f32(&buffer_l[i]);
|
||||
float32x4x2_t right8 = vld2q_f32(&buffer_r[i]);
|
||||
|
||||
coeff8.val[0] = vmlaq_f32(coeff8.val[0], delta8.val[0], delta);
|
||||
coeff8.val[1] = vmlaq_f32(coeff8.val[1], delta8.val[1], delta);
|
||||
|
||||
p1 = vmlaq_f32(p1, left8.val[0], coeff8.val[0]);
|
||||
p2 = vmlaq_f32(p2, right8.val[0], coeff8.val[0]);
|
||||
p1 = vmlaq_f32(p1, left8.val[1], coeff8.val[1]);
|
||||
p2 = vmlaq_f32(p2, right8.val[1], coeff8.val[1]);
|
||||
}
|
||||
|
||||
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));
|
||||
output += 2;
|
||||
out_frames++;
|
||||
resamp->time += ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
rarch_sinc_resampler_t *resamp = (rarch_sinc_resampler_t*)re_;
|
||||
unsigned phases = 1 << (resamp->phase_bits + resamp->subphase_bits);
|
||||
uint32_t ratio = phases / data->ratio;
|
||||
const float *input = data->data_in;
|
||||
float *output = data->data_out;
|
||||
size_t frames = data->input_frames;
|
||||
size_t out_frames = 0;
|
||||
unsigned taps = resamp->taps;
|
||||
|
||||
while (frames)
|
||||
{
|
||||
SINC_PUSH_INPUT_SAMPLES(resamp, input, taps, phases, frames);
|
||||
|
||||
{
|
||||
const float *buffer_l = resamp->buffer_l + resamp->ptr;
|
||||
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 * taps;
|
||||
#ifdef HAVE_ARM_NEON_ASM_OPTIMIZATIONS
|
||||
process_sinc_neon_asm(output, buffer_l, buffer_r, phase_table, taps);
|
||||
#else
|
||||
{
|
||||
/* C89: declarations at top of block */
|
||||
int i;
|
||||
float32x2_t p3, p4;
|
||||
float32x4_t p1 = vdupq_n_f32(0.0f);
|
||||
float32x4_t p2 = vdupq_n_f32(0.0f);
|
||||
|
||||
for (i = 0; i < (int)taps; i += 8)
|
||||
{
|
||||
float32x4x2_t coeff8 = vld2q_f32(&phase_table[i]);
|
||||
float32x4x2_t left8 = vld2q_f32(&buffer_l[i]);
|
||||
float32x4x2_t right8 = vld2q_f32(&buffer_r[i]);
|
||||
|
||||
p1 = vmlaq_f32(p1, left8.val[0], coeff8.val[0]);
|
||||
p2 = vmlaq_f32(p2, right8.val[0], coeff8.val[0]);
|
||||
p1 = vmlaq_f32(p1, left8.val[1], coeff8.val[1]);
|
||||
p2 = vmlaq_f32(p2, right8.val[1], coeff8.val[1]);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data->output_frames = out_frames;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__AVX__)
|
||||
static void resampler_sinc_process_avx_kaiser(void *re_, struct resampler_data *data)
|
||||
{
|
||||
rarch_sinc_resampler_t *resamp = (rarch_sinc_resampler_t*)re_;
|
||||
unsigned phases = 1 << (resamp->phase_bits + resamp->subphase_bits);
|
||||
uint32_t ratio = phases / data->ratio;
|
||||
const float *input = data->data_in;
|
||||
float *output = data->data_out;
|
||||
size_t frames = data->input_frames;
|
||||
size_t out_frames = 0;
|
||||
unsigned taps = resamp->taps;
|
||||
unsigned taps2 = taps * 2;
|
||||
|
||||
while (frames)
|
||||
{
|
||||
SINC_PUSH_INPUT_SAMPLES(resamp, input, taps, phases, frames);
|
||||
|
||||
{
|
||||
const float *buffer_l = resamp->buffer_l + resamp->ptr;
|
||||
const float *buffer_r = resamp->buffer_r + resamp->ptr;
|
||||
while (resamp->time < phases)
|
||||
{
|
||||
/* C89: all declarations at top of block */
|
||||
int i;
|
||||
__m256 res_l, res_r;
|
||||
unsigned phase = resamp->time >> resamp->subphase_bits;
|
||||
float *phase_table = resamp->phase_table + phase * taps2;
|
||||
float *delta_table = phase_table + taps;
|
||||
__m256 delta = _mm256_set1_ps((float)
|
||||
(resamp->time & resamp->subphase_mask) * resamp->subphase_mod);
|
||||
__m256 sum_l = _mm256_setzero_ps();
|
||||
__m256 sum_r = _mm256_setzero_ps();
|
||||
|
||||
for (i = 0; i < (int)taps; i += 8)
|
||||
{
|
||||
__m256 buf_l = _mm256_loadu_ps(buffer_l + i);
|
||||
__m256 buf_r = _mm256_loadu_ps(buffer_r + i);
|
||||
__m256 deltas = _mm256_load_ps(delta_table + i);
|
||||
__m256 sinc_v = _mm256_add_ps(_mm256_load_ps((const float*)phase_table + i),
|
||||
_mm256_mul_ps(deltas, delta));
|
||||
|
||||
sum_l = _mm256_add_ps(sum_l, _mm256_mul_ps(buf_l, sinc_v));
|
||||
sum_r = _mm256_add_ps(sum_r, _mm256_mul_ps(buf_r, sinc_v));
|
||||
}
|
||||
|
||||
/* hadd on AVX is weird, and acts on low-lanes
|
||||
* and high-lanes separately. */
|
||||
res_l = _mm256_hadd_ps(sum_l, sum_l);
|
||||
res_r = _mm256_hadd_ps(sum_r, sum_r);
|
||||
res_l = _mm256_hadd_ps(res_l, res_l);
|
||||
res_r = _mm256_hadd_ps(res_r, res_r);
|
||||
res_l = _mm256_add_ps(_mm256_permute2f128_ps(res_l, res_l, 1), res_l);
|
||||
res_r = _mm256_add_ps(_mm256_permute2f128_ps(res_r, res_r, 1), res_r);
|
||||
|
||||
/* This is optimized to mov %xmmN, [mem].
|
||||
* There doesn't seem to be any _mm256_store_ss intrinsic. */
|
||||
_mm_store_ss(output + 0, _mm256_extractf128_ps(res_l, 0));
|
||||
_mm_store_ss(output + 1, _mm256_extractf128_ps(res_r, 0));
|
||||
|
||||
output += 2;
|
||||
out_frames++;
|
||||
resamp->time += ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data->output_frames = out_frames;
|
||||
}
|
||||
|
||||
static void resampler_sinc_process_avx(void *re_, struct resampler_data *data)
|
||||
{
|
||||
rarch_sinc_resampler_t *resamp = (rarch_sinc_resampler_t*)re_;
|
||||
unsigned phases = 1 << (resamp->phase_bits + resamp->subphase_bits);
|
||||
uint32_t ratio = phases / data->ratio;
|
||||
const float *input = data->data_in;
|
||||
float *output = data->data_out;
|
||||
size_t frames = data->input_frames;
|
||||
size_t out_frames = 0;
|
||||
unsigned taps = resamp->taps;
|
||||
|
||||
while (frames)
|
||||
{
|
||||
SINC_PUSH_INPUT_SAMPLES(resamp, input, taps, phases, frames);
|
||||
|
||||
{
|
||||
const float *buffer_l = resamp->buffer_l + resamp->ptr;
|
||||
const float *buffer_r = resamp->buffer_r + resamp->ptr;
|
||||
while (resamp->time < phases)
|
||||
{
|
||||
/* C89: all declarations at top of block */
|
||||
int i;
|
||||
__m256 res_l, res_r;
|
||||
unsigned phase = resamp->time >> resamp->subphase_bits;
|
||||
float *phase_table = resamp->phase_table + phase * taps;
|
||||
__m256 sum_l = _mm256_setzero_ps();
|
||||
__m256 sum_r = _mm256_setzero_ps();
|
||||
|
||||
for (i = 0; i < (int)taps; i += 8)
|
||||
{
|
||||
__m256 buf_l = _mm256_loadu_ps(buffer_l + i);
|
||||
__m256 buf_r = _mm256_loadu_ps(buffer_r + i);
|
||||
__m256 sinc_v = _mm256_load_ps((const float*)phase_table + i);
|
||||
|
||||
sum_l = _mm256_add_ps(sum_l, _mm256_mul_ps(buf_l, sinc_v));
|
||||
sum_r = _mm256_add_ps(sum_r, _mm256_mul_ps(buf_r, sinc_v));
|
||||
}
|
||||
|
||||
/* hadd on AVX is weird, and acts on low-lanes
|
||||
* and high-lanes separately. */
|
||||
res_l = _mm256_hadd_ps(sum_l, sum_l);
|
||||
res_r = _mm256_hadd_ps(sum_r, sum_r);
|
||||
res_l = _mm256_hadd_ps(res_l, res_l);
|
||||
res_r = _mm256_hadd_ps(res_r, res_r);
|
||||
res_l = _mm256_add_ps(_mm256_permute2f128_ps(res_l, res_l, 1), res_l);
|
||||
res_r = _mm256_add_ps(_mm256_permute2f128_ps(res_r, res_r, 1), res_r);
|
||||
|
||||
/* This is optimized to mov %xmmN, [mem].
|
||||
* There doesn't seem to be any _mm256_store_ss intrinsic. */
|
||||
_mm_store_ss(output + 0, _mm256_extractf128_ps(res_l, 0));
|
||||
_mm_store_ss(output + 1, _mm256_extractf128_ps(res_r, 0));
|
||||
|
||||
output += 2;
|
||||
out_frames++;
|
||||
resamp->time += ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data->output_frames = out_frames;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__SSE__)
|
||||
static void resampler_sinc_process_sse_kaiser(void *re_, struct resampler_data *data)
|
||||
{
|
||||
rarch_sinc_resampler_t *resamp = (rarch_sinc_resampler_t*)re_;
|
||||
unsigned phases = 1 << (resamp->phase_bits + resamp->subphase_bits);
|
||||
uint32_t ratio = phases / data->ratio;
|
||||
const float *input = data->data_in;
|
||||
float *output = data->data_out;
|
||||
size_t frames = data->input_frames;
|
||||
size_t out_frames = 0;
|
||||
unsigned taps = resamp->taps;
|
||||
unsigned taps2 = taps * 2;
|
||||
|
||||
while (frames)
|
||||
{
|
||||
SINC_PUSH_INPUT_SAMPLES(resamp, input, taps, phases, frames);
|
||||
|
||||
{
|
||||
const float *buffer_l = resamp->buffer_l + resamp->ptr;
|
||||
const float *buffer_r = resamp->buffer_r + resamp->ptr;
|
||||
while (resamp->time < phases)
|
||||
{
|
||||
/* C89: all declarations at top of block */
|
||||
int i;
|
||||
__m128 sum;
|
||||
unsigned phase = resamp->time >> resamp->subphase_bits;
|
||||
float *phase_table = resamp->phase_table + phase * taps2;
|
||||
float *delta_table = phase_table + taps;
|
||||
__m128 delta = _mm_set1_ps((float)
|
||||
(resamp->time & resamp->subphase_mask) * resamp->subphase_mod);
|
||||
__m128 sum_l = _mm_setzero_ps();
|
||||
__m128 sum_r = _mm_setzero_ps();
|
||||
|
||||
for (i = 0; i < (int)taps; i += 4)
|
||||
{
|
||||
__m128 buf_l = _mm_loadu_ps(buffer_l + i);
|
||||
__m128 buf_r = _mm_loadu_ps(buffer_r + i);
|
||||
__m128 deltas = _mm_load_ps(delta_table + i);
|
||||
__m128 sinc_v = _mm_add_ps(_mm_load_ps((const float*)phase_table + i),
|
||||
_mm_mul_ps(deltas, delta));
|
||||
sum_l = _mm_add_ps(sum_l, _mm_mul_ps(buf_l, sinc_v));
|
||||
sum_r = _mm_add_ps(sum_r, _mm_mul_ps(buf_r, sinc_v));
|
||||
}
|
||||
|
||||
/* Them annoying shuffles.
|
||||
* sum_l = { l3, l2, l1, l0 }
|
||||
* sum_r = { r3, r2, r1, r0 }
|
||||
*/
|
||||
|
||||
sum = _mm_add_ps(_mm_shuffle_ps(sum_l, sum_r,
|
||||
_MM_SHUFFLE(1, 0, 1, 0)),
|
||||
_mm_shuffle_ps(sum_l, sum_r, _MM_SHUFFLE(3, 2, 3, 2)));
|
||||
|
||||
/* sum = { r1, r0, l1, l0 } + { r3, r2, l3, l2 }
|
||||
* sum = { R1, R0, L1, L0 }
|
||||
*/
|
||||
|
||||
sum = _mm_add_ps(_mm_shuffle_ps(sum, sum, _MM_SHUFFLE(3, 3, 1, 1)), sum);
|
||||
|
||||
/* sum = {R1, R1, L1, L1 } + { R1, R0, L1, L0 }
|
||||
* sum = { X, R, X, L }
|
||||
*/
|
||||
|
||||
/* Store L */
|
||||
_mm_store_ss(output + 0, sum);
|
||||
|
||||
/* movehl { X, R, X, L } == { X, R, X, R } */
|
||||
_mm_store_ss(output + 1, _mm_movehl_ps(sum, sum));
|
||||
|
||||
output += 2;
|
||||
out_frames++;
|
||||
resamp->time += ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data->output_frames = out_frames;
|
||||
}
|
||||
|
||||
static void resampler_sinc_process_sse(void *re_, struct resampler_data *data)
|
||||
{
|
||||
rarch_sinc_resampler_t *resamp = (rarch_sinc_resampler_t*)re_;
|
||||
unsigned phases = 1 << (resamp->phase_bits + resamp->subphase_bits);
|
||||
uint32_t ratio = phases / data->ratio;
|
||||
const float *input = data->data_in;
|
||||
float *output = data->data_out;
|
||||
size_t frames = data->input_frames;
|
||||
size_t out_frames = 0;
|
||||
unsigned taps = resamp->taps;
|
||||
|
||||
while (frames)
|
||||
{
|
||||
SINC_PUSH_INPUT_SAMPLES(resamp, input, taps, phases, frames);
|
||||
|
||||
{
|
||||
const float *buffer_l = resamp->buffer_l + resamp->ptr;
|
||||
const float *buffer_r = resamp->buffer_r + resamp->ptr;
|
||||
while (resamp->time < phases)
|
||||
{
|
||||
/* C89: all declarations at top of block */
|
||||
int i;
|
||||
__m128 sum;
|
||||
unsigned phase = resamp->time >> resamp->subphase_bits;
|
||||
float *phase_table = resamp->phase_table + phase * taps;
|
||||
__m128 sum_l = _mm_setzero_ps();
|
||||
__m128 sum_r = _mm_setzero_ps();
|
||||
|
||||
for (i = 0; i < (int)taps; i += 4)
|
||||
{
|
||||
__m128 buf_l = _mm_loadu_ps(buffer_l + i);
|
||||
__m128 buf_r = _mm_loadu_ps(buffer_r + i);
|
||||
__m128 sinc_v = _mm_load_ps((const float*)phase_table + i);
|
||||
sum_l = _mm_add_ps(sum_l, _mm_mul_ps(buf_l, sinc_v));
|
||||
sum_r = _mm_add_ps(sum_r, _mm_mul_ps(buf_r, sinc_v));
|
||||
}
|
||||
|
||||
/* Them annoying shuffles.
|
||||
* sum_l = { l3, l2, l1, l0 }
|
||||
* sum_r = { r3, r2, r1, r0 }
|
||||
*/
|
||||
|
||||
sum = _mm_add_ps(_mm_shuffle_ps(sum_l, sum_r,
|
||||
_MM_SHUFFLE(1, 0, 1, 0)),
|
||||
_mm_shuffle_ps(sum_l, sum_r, _MM_SHUFFLE(3, 2, 3, 2)));
|
||||
|
||||
/* sum = { r1, r0, l1, l0 } + { r3, r2, l3, l2 }
|
||||
* sum = { R1, R0, L1, L0 }
|
||||
*/
|
||||
|
||||
sum = _mm_add_ps(_mm_shuffle_ps(sum, sum, _MM_SHUFFLE(3, 3, 1, 1)), sum);
|
||||
|
||||
/* sum = {R1, R1, L1, L1 } + { R1, R0, L1, L0 }
|
||||
* sum = { X, R, X, L }
|
||||
*/
|
||||
|
||||
/* Store L */
|
||||
_mm_store_ss(output + 0, sum);
|
||||
|
||||
/* movehl { X, R, X, L } == { X, R, X, R } */
|
||||
_mm_store_ss(output + 1, _mm_movehl_ps(sum, sum));
|
||||
|
||||
output += 2;
|
||||
out_frames++;
|
||||
resamp->time += ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data->output_frames = out_frames;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void resampler_sinc_process_c_kaiser(void *re_, struct resampler_data *data)
|
||||
{
|
||||
rarch_sinc_resampler_t *resamp = (rarch_sinc_resampler_t*)re_;
|
||||
unsigned phases = 1 << (resamp->phase_bits + resamp->subphase_bits);
|
||||
uint32_t ratio = phases / data->ratio;
|
||||
const float *input = data->data_in;
|
||||
float *output = data->data_out;
|
||||
size_t frames = data->input_frames;
|
||||
size_t out_frames = 0;
|
||||
unsigned taps = resamp->taps;
|
||||
unsigned taps2 = taps * 2;
|
||||
|
||||
while (frames)
|
||||
{
|
||||
SINC_PUSH_INPUT_SAMPLES(resamp, input, taps, phases, frames);
|
||||
|
||||
{
|
||||
const float *buffer_l = resamp->buffer_l + resamp->ptr;
|
||||
const float *buffer_r = resamp->buffer_r + resamp->ptr;
|
||||
while (resamp->time < phases)
|
||||
{
|
||||
/* C89: all declarations at top of block */
|
||||
int i;
|
||||
float sum_l = 0.0f;
|
||||
float sum_r = 0.0f;
|
||||
unsigned phase = resamp->time >> resamp->subphase_bits;
|
||||
float *phase_table = resamp->phase_table + phase * taps2;
|
||||
float *delta_table = phase_table + taps;
|
||||
float delta = (float)
|
||||
(resamp->time & resamp->subphase_mask) * resamp->subphase_mod;
|
||||
|
||||
/* Manual 4x unroll for scalar path.
|
||||
* Taps is guaranteed to be a multiple of 4
|
||||
(see SIMD alignment in init). */
|
||||
int taps_aligned = (int)taps & ~3;
|
||||
for (i = 0; i < taps_aligned; i += 4)
|
||||
{
|
||||
float s0 = phase_table[i] + delta_table[i] * delta;
|
||||
float s1 = phase_table[i + 1] + delta_table[i + 1] * delta;
|
||||
float s2 = phase_table[i + 2] + delta_table[i + 2] * delta;
|
||||
float s3 = phase_table[i + 3] + delta_table[i + 3] * delta;
|
||||
|
||||
sum_l += buffer_l[i] * s0 + buffer_l[i + 1] * s1
|
||||
+ buffer_l[i + 2] * s2 + buffer_l[i + 3] * s3;
|
||||
sum_r += buffer_r[i] * s0 + buffer_r[i + 1] * s1
|
||||
+ buffer_r[i + 2] * s2 + buffer_r[i + 3] * s3;
|
||||
}
|
||||
/* Handle any remaining taps (safety net). */
|
||||
for (; i < (int)taps; i++)
|
||||
{
|
||||
float sinc_val = phase_table[i] + delta_table[i] * delta;
|
||||
sum_l += buffer_l[i] * sinc_val;
|
||||
sum_r += buffer_r[i] * sinc_val;
|
||||
}
|
||||
|
||||
output[0] = sum_l;
|
||||
output[1] = sum_r;
|
||||
|
||||
output += 2;
|
||||
out_frames++;
|
||||
resamp->time += ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data->output_frames = out_frames;
|
||||
}
|
||||
|
||||
static void resampler_sinc_process_c(void *re_, struct resampler_data *data)
|
||||
{
|
||||
rarch_sinc_resampler_t *resamp = (rarch_sinc_resampler_t*)re_;
|
||||
unsigned phases = 1 << (resamp->phase_bits + resamp->subphase_bits);
|
||||
uint32_t ratio = phases / data->ratio;
|
||||
const float *input = data->data_in;
|
||||
float *output = data->data_out;
|
||||
size_t frames = data->input_frames;
|
||||
size_t out_frames = 0;
|
||||
unsigned taps = resamp->taps;
|
||||
|
||||
while (frames)
|
||||
{
|
||||
SINC_PUSH_INPUT_SAMPLES(resamp, input, taps, phases, frames);
|
||||
|
||||
{
|
||||
const float *buffer_l = resamp->buffer_l + resamp->ptr;
|
||||
const float *buffer_r = resamp->buffer_r + resamp->ptr;
|
||||
while (resamp->time < phases)
|
||||
{
|
||||
/* C89: all declarations at top of block */
|
||||
int i;
|
||||
float sum_l = 0.0f;
|
||||
float sum_r = 0.0f;
|
||||
unsigned phase = resamp->time >> resamp->subphase_bits;
|
||||
float *phase_table = resamp->phase_table + phase * taps;
|
||||
/* Manual 4x unroll for scalar path. */
|
||||
int taps_aligned = (int)taps & ~3;
|
||||
for (i = 0; i < taps_aligned; i += 4)
|
||||
{
|
||||
float s0 = phase_table[i];
|
||||
float s1 = phase_table[i + 1];
|
||||
float s2 = phase_table[i + 2];
|
||||
float s3 = phase_table[i + 3];
|
||||
|
||||
sum_l += buffer_l[i] * s0 + buffer_l[i + 1] * s1
|
||||
+ buffer_l[i + 2] * s2 + buffer_l[i + 3] * s3;
|
||||
sum_r += buffer_r[i] * s0 + buffer_r[i + 1] * s1
|
||||
+ buffer_r[i + 2] * s2 + buffer_r[i + 3] * s3;
|
||||
}
|
||||
/* Handle any remaining taps. */
|
||||
for (; i < (int)taps; i++)
|
||||
{
|
||||
float sinc_val = phase_table[i];
|
||||
sum_l += buffer_l[i] * sinc_val;
|
||||
sum_r += buffer_r[i] * sinc_val;
|
||||
}
|
||||
|
||||
output[0] = sum_l;
|
||||
output[1] = sum_r;
|
||||
|
||||
output += 2;
|
||||
out_frames++;
|
||||
resamp->time += ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data->output_frames = out_frames;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void sinc_init_table_kaiser(rarch_sinc_resampler_t *resamp,
|
||||
double cutoff,
|
||||
float *phase_table, int phases, int taps, int calculate_delta)
|
||||
{
|
||||
int i, j;
|
||||
/* Kaiser window function - need to normalize w(0) to 1.0f */
|
||||
double kaiser_beta = (double)resamp->kaiser_beta;
|
||||
double window_mod = besseli0(kaiser_beta);
|
||||
int stride = calculate_delta ? 2 : 1;
|
||||
double sidelobes = taps / 2.0;
|
||||
|
||||
for (i = 0; i < phases; i++)
|
||||
{
|
||||
for (j = 0; j < taps; j++)
|
||||
{
|
||||
double sinc_phase;
|
||||
double arg;
|
||||
float val;
|
||||
int n = j * phases + i;
|
||||
double window_phase = (double)n / (phases * taps); /* [0, 1). */
|
||||
window_phase = 2.0 * window_phase - 1.0; /* [-1, 1) */
|
||||
sinc_phase = sidelobes * window_phase;
|
||||
|
||||
/* clamp argument to sqrt to avoid NaN from
|
||||
* floating-point precision loss at boundaries where
|
||||
* 1 - window_phase^2 can go slightly negative.
|
||||
* Also use double-precision sqrt() instead of sqrtf()
|
||||
* for better accuracy during table generation. */
|
||||
arg = 1.0 - window_phase * window_phase;
|
||||
if (arg < 0.0)
|
||||
arg = 0.0;
|
||||
|
||||
val = (float)(cutoff * sinc(M_PI * sinc_phase * cutoff) *
|
||||
besseli0(kaiser_beta * sqrt(arg))
|
||||
/ window_mod);
|
||||
phase_table[i * stride * taps + j] = val;
|
||||
}
|
||||
}
|
||||
|
||||
if (calculate_delta)
|
||||
{
|
||||
int phase;
|
||||
int p;
|
||||
for (p = 0; p < phases - 1; p++)
|
||||
{
|
||||
for (j = 0; j < taps; j++)
|
||||
{
|
||||
float delta = phase_table[(p + 1) * stride * taps + j] -
|
||||
phase_table[p * stride * taps + j];
|
||||
phase_table[(p * stride + 1) * taps + j] = delta;
|
||||
}
|
||||
}
|
||||
|
||||
phase = phases - 1;
|
||||
for (j = 0; j < taps; j++)
|
||||
{
|
||||
double sinc_phase;
|
||||
double arg;
|
||||
float val, delta;
|
||||
int n = j * phases + (phase + 1);
|
||||
double window_phase = (double)n / (phases * taps); /* (0, 1]. */
|
||||
window_phase = 2.0 * window_phase - 1.0; /* (-1, 1] */
|
||||
sinc_phase = sidelobes * window_phase;
|
||||
|
||||
/* Same clamp + double-precision sqrt as above. */
|
||||
arg = 1.0 - window_phase * window_phase;
|
||||
if (arg < 0.0)
|
||||
arg = 0.0;
|
||||
|
||||
val = (float)(cutoff * sinc(M_PI * sinc_phase * cutoff) *
|
||||
besseli0(kaiser_beta * sqrt(arg)) / window_mod);
|
||||
delta = (val - phase_table[phase * stride * taps + j]);
|
||||
phase_table[(phase * stride + 1) * taps + j] = delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sinc_init_table_lanczos(
|
||||
rarch_sinc_resampler_t *resamp, double cutoff,
|
||||
float *phase_table, int phases, int taps, int calculate_delta)
|
||||
{
|
||||
int i, j;
|
||||
/* Lanczos window function - need to normalize w(0) to 1.0f */
|
||||
double window_mod = 1.0;
|
||||
int stride = calculate_delta ? 2 : 1;
|
||||
double sidelobes = taps / 2.0;
|
||||
|
||||
for (i = 0; i < phases; i++)
|
||||
{
|
||||
for (j = 0; j < taps; j++)
|
||||
{
|
||||
double sinc_phase;
|
||||
float val;
|
||||
int n = j * phases + i;
|
||||
double window_phase = (double)n / (phases * taps); /* [0, 1). */
|
||||
window_phase = 2.0 * window_phase - 1.0; /* [-1, 1) */
|
||||
sinc_phase = sidelobes * window_phase;
|
||||
val = (float)(cutoff * sinc(M_PI * sinc_phase * cutoff) *
|
||||
sinc(M_PI * window_phase) / window_mod);
|
||||
phase_table[i * stride * taps + j] = val;
|
||||
}
|
||||
}
|
||||
|
||||
if (calculate_delta)
|
||||
{
|
||||
int p;
|
||||
int phase;
|
||||
|
||||
for (p = 0; p < phases - 1; p++)
|
||||
{
|
||||
for (j = 0; j < taps; j++)
|
||||
{
|
||||
float delta = phase_table[(p + 1) * stride * taps + j] -
|
||||
phase_table[p * stride * taps + j];
|
||||
phase_table[(p * stride + 1) * taps + j] = delta;
|
||||
}
|
||||
}
|
||||
|
||||
phase = phases - 1;
|
||||
for (j = 0; j < taps; j++)
|
||||
{
|
||||
double sinc_phase;
|
||||
float val, delta;
|
||||
int n = j * phases + (phase + 1);
|
||||
double window_phase = (double)n / (phases * taps); /* (0, 1]. */
|
||||
window_phase = 2.0 * window_phase - 1.0; /* (-1, 1] */
|
||||
sinc_phase = sidelobes * window_phase;
|
||||
|
||||
val = (float)(cutoff * sinc(M_PI * sinc_phase * cutoff) *
|
||||
sinc(M_PI * window_phase) / window_mod);
|
||||
delta = (val - phase_table[phase * stride * taps + j]);
|
||||
phase_table[(phase * stride + 1) * taps + j] = delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void *resampler_sinc_new(const struct resampler_config *config,
|
||||
double bandwidth_mod, enum resampler_quality quality,
|
||||
resampler_simd_mask_t mask)
|
||||
{
|
||||
double cutoff = 0.0;
|
||||
size_t phase_elems = 0;
|
||||
size_t elems = 0;
|
||||
unsigned enable_avx = 0;
|
||||
unsigned sidelobes = 0;
|
||||
enum sinc_window window_type = SINC_WINDOW_NONE;
|
||||
rarch_sinc_resampler_t *re = (rarch_sinc_resampler_t*)
|
||||
calloc(1, sizeof(*re));
|
||||
|
||||
if (!re)
|
||||
return NULL;
|
||||
|
||||
switch (quality)
|
||||
{
|
||||
case RESAMPLER_QUALITY_LOWEST:
|
||||
cutoff = 0.98;
|
||||
sidelobes = 2;
|
||||
re->phase_bits = 12;
|
||||
re->subphase_bits = 10;
|
||||
window_type = SINC_WINDOW_LANCZOS;
|
||||
break;
|
||||
case RESAMPLER_QUALITY_LOWER:
|
||||
cutoff = 0.98;
|
||||
sidelobes = 4;
|
||||
re->phase_bits = 12;
|
||||
re->subphase_bits = 10;
|
||||
window_type = SINC_WINDOW_LANCZOS;
|
||||
break;
|
||||
case RESAMPLER_QUALITY_HIGHER:
|
||||
cutoff = 0.90;
|
||||
sidelobes = 32;
|
||||
re->phase_bits = 10;
|
||||
re->subphase_bits = 14;
|
||||
window_type = SINC_WINDOW_KAISER;
|
||||
re->kaiser_beta = 10.5;
|
||||
enable_avx = 1;
|
||||
break;
|
||||
case RESAMPLER_QUALITY_HIGHEST:
|
||||
cutoff = 0.962;
|
||||
sidelobes = 128;
|
||||
re->phase_bits = 10;
|
||||
re->subphase_bits = 14;
|
||||
window_type = SINC_WINDOW_KAISER;
|
||||
re->kaiser_beta = 14.5;
|
||||
enable_avx = 1;
|
||||
break;
|
||||
case RESAMPLER_QUALITY_NORMAL:
|
||||
case RESAMPLER_QUALITY_DONTCARE:
|
||||
cutoff = 0.825;
|
||||
sidelobes = 8;
|
||||
re->phase_bits = 8;
|
||||
re->subphase_bits = 16;
|
||||
window_type = SINC_WINDOW_KAISER;
|
||||
re->kaiser_beta = 5.5;
|
||||
break;
|
||||
}
|
||||
|
||||
re->subphase_mask = (1 << re->subphase_bits) - 1;
|
||||
re->subphase_mod = 1.0f / (1 << re->subphase_bits);
|
||||
re->taps = sidelobes * 2;
|
||||
|
||||
/* Downsampling, must lower cutoff, and extend number of
|
||||
* taps accordingly to keep same stopband attenuation. */
|
||||
if (bandwidth_mod < 1.0)
|
||||
{
|
||||
cutoff *= bandwidth_mod;
|
||||
re->taps = (unsigned)ceil(re->taps / bandwidth_mod);
|
||||
}
|
||||
|
||||
/* Be SIMD-friendly: round taps up to a multiple of 8 on every platform.
|
||||
* NEON and AVX builds always required this; SSE/scalar builds only
|
||||
* required a multiple of 4, which made LOWEST a different filter per
|
||||
* platform: 4 taps on x86, 8 on ARM (identical to LOWER there). The
|
||||
* 4-tap variant carries 0.117 dB of per-phase DC gain ripple - audible
|
||||
* as slow amplitude wobble on sustained tones as rate control sweeps
|
||||
* the phase - while the 8-tap variant measures 0.003 dB. A uniform
|
||||
* multiple of 8 gives every platform the same filter and retires the
|
||||
* one quality tier with audible gain ripple. LOWEST thereby becomes
|
||||
* an alias of LOWER for upsampling, matching what ARM and AVX builds
|
||||
* have always shipped. */
|
||||
re->taps = (re->taps + 7) & ~7;
|
||||
|
||||
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;
|
||||
|
||||
re->main_buffer = (float*)memalign_alloc(128, sizeof(float) * elems);
|
||||
if (!re->main_buffer)
|
||||
goto error;
|
||||
|
||||
memset(re->main_buffer, 0, sizeof(float) * elems);
|
||||
|
||||
re->phase_table = re->main_buffer;
|
||||
re->buffer_l = re->main_buffer + phase_elems;
|
||||
re->buffer_r = re->buffer_l + 2 * re->taps;
|
||||
|
||||
switch (window_type)
|
||||
{
|
||||
case SINC_WINDOW_LANCZOS:
|
||||
sinc_init_table_lanczos(re, cutoff, re->phase_table,
|
||||
1 << re->phase_bits, re->taps, 0);
|
||||
break;
|
||||
case SINC_WINDOW_KAISER:
|
||||
sinc_init_table_kaiser(re, cutoff, re->phase_table,
|
||||
1 << re->phase_bits, re->taps, 1);
|
||||
break;
|
||||
case SINC_WINDOW_NONE:
|
||||
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
|
||||
}
|
||||
|
||||
return re;
|
||||
|
||||
error:
|
||||
resampler_sinc_free(re);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Thin dispatcher: the vtable is shared across all live instances, so the
|
||||
* actual kernel is read from the instance itself. One indirect call per
|
||||
* process() invocation (per chunk, not per sample) - no measurable cost. */
|
||||
static void resampler_sinc_process(void *re_, struct resampler_data *data)
|
||||
{
|
||||
((rarch_sinc_resampler_t*)re_)->process(re_, data);
|
||||
}
|
||||
|
||||
retro_resampler_t sinc_resampler = {
|
||||
resampler_sinc_new,
|
||||
resampler_sinc_process,
|
||||
resampler_sinc_free,
|
||||
RESAMPLER_API_VERSION,
|
||||
"sinc",
|
||||
"sinc"
|
||||
};
|
||||
|
||||
#if defined(__GNUC__) && defined(__OPTIMIZE__) && !defined(__clang__)
|
||||
#pragma GCC pop_options
|
||||
#endif
|
||||
@@ -1,563 +0,0 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file
|
||||
* (sinc_resampler_int16.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.
|
||||
*/
|
||||
|
||||
/* Fixed-point (integer-only) windowed-SINC resampler.
|
||||
*
|
||||
* This is a bit-transparent counterpart to the float "sinc" driver in
|
||||
* drivers/sinc_resampler.c. It exists for two reasons:
|
||||
*
|
||||
* 1) Determinism. The float driver is compiled with -ffast-math and its
|
||||
* output is therefore not bit-reproducible across compilers, archs or
|
||||
* FMA-contraction settings. This driver uses integer MACs only and is
|
||||
* bit-identical everywhere, which matters for netplay / rewind.
|
||||
*
|
||||
* 2) It removes the s16->float->s16 round-trip on the (universal) libretro
|
||||
* int16 audio path, and runs with no FPU dependency at all, which helps
|
||||
* on FPU-poor targets (PS2, 3DS, older ARM).
|
||||
*
|
||||
* The control flow (ring push, phase = time >> subphase_bits, subphase delta,
|
||||
* time += ratio) is identical to the float driver; only the coefficients and
|
||||
* samples are integer. Coefficients are Q1.30 in int32; samples are int16;
|
||||
* products accumulate in int64 and are round-half-away-from-zero shifted back
|
||||
* to int16 with saturation.
|
||||
*
|
||||
* NB: everything here is written to C89 / MSVC rules (declarations at the top
|
||||
* of each block, no // comments) and avoids implementation-defined signed
|
||||
* right shifts so the result is portable and deterministic.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <audio/sinc_resampler_int16.h>
|
||||
|
||||
/* On targets whose compiler can auto-vectorize an int16*int32->int64 MAC
|
||||
* (e.g. AArch64/NEON via smlal), splitting the Kaiser inner loop into a
|
||||
* branchless interpolation pass and a pure MAC pass lets the vectorizer take
|
||||
* the MAC. On targets without that primitive (e.g. x86 SSE2) the fused loop is
|
||||
* faster (no scratch round-trip), so this is gated to NEON only. */
|
||||
#if defined(__ARM_NEON) || defined(__ARM_NEON__)
|
||||
#define SINC_I16_KAISER_FISSION 1
|
||||
#endif
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
/* Coefficient-row prefetch.
|
||||
*
|
||||
* The phase advance is fully determined (time += ratio), so the row the next
|
||||
* output sample will read is known one iteration ahead. The table is swept
|
||||
* pseudo-randomly -- for any non-integer ratio consecutive outputs land in
|
||||
* unrelated rows -- so the row base is exactly the access the hardware
|
||||
* prefetcher cannot predict, while the run *within* a row is sequential and
|
||||
* needs no help.
|
||||
*
|
||||
* This is a hint only: it changes no arithmetic, the index is masked into
|
||||
* range, and the no-op fallback is equally correct. Output stays bit-exact. */
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define SINC_I16_PREFETCH(addr) __builtin_prefetch((const void*)(addr), 0, 3)
|
||||
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
|
||||
#include <intrin.h>
|
||||
#define SINC_I16_PREFETCH(addr) _mm_prefetch((const char*)(addr), _MM_HINT_T0)
|
||||
#else
|
||||
#define SINC_I16_PREFETCH(addr) ((void)0)
|
||||
#endif
|
||||
|
||||
enum sinc_i16_window
|
||||
{
|
||||
SINC_I16_WINDOW_LANCZOS = 0,
|
||||
SINC_I16_WINDOW_KAISER
|
||||
};
|
||||
|
||||
typedef struct rarch_sinc_resampler_int16
|
||||
{
|
||||
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;
|
||||
unsigned phase_bits;
|
||||
unsigned subphase_bits;
|
||||
uint32_t subphase_mask;
|
||||
unsigned taps;
|
||||
unsigned window; /* enum sinc_i16_window */
|
||||
#ifdef SINC_I16_KAISER_FISSION
|
||||
int32_t *coef_scratch;/* per-output effective Kaiser coeffs (NEON fission) */
|
||||
#endif
|
||||
unsigned ptr;
|
||||
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 */
|
||||
double kaiser_beta;
|
||||
} rarch_sinc_resampler_int16_t;
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Local math helpers (kept local so the module is self-contained). */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static double sinc_i16_sinc(double val)
|
||||
{
|
||||
if (fabs(val) < 0.00000000001)
|
||||
return 1.0;
|
||||
return sin(val) / val;
|
||||
}
|
||||
|
||||
static double sinc_i16_besseli0(double x)
|
||||
{
|
||||
double sum = 1.0;
|
||||
double term = 1.0;
|
||||
double half_sq = (x * 0.5) * (x * 0.5);
|
||||
int k = 1;
|
||||
for (;;)
|
||||
{
|
||||
term *= half_sq / ((double)k * (double)k);
|
||||
sum += term;
|
||||
if (term < 1.0e-21 * sum)
|
||||
break;
|
||||
k++;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/* Round a real coefficient to Q1.30, round-half-away-from-zero. */
|
||||
static int32_t sinc_i16_to_q30(double val)
|
||||
{
|
||||
double scale = (double)((int64_t)1 << SINC_INT16_COEFF_BITS);
|
||||
double s = val * scale;
|
||||
if (s >= 0.0)
|
||||
return (int32_t)floor(s + 0.5);
|
||||
return (int32_t)ceil(s - 0.5);
|
||||
}
|
||||
|
||||
/* Round-half-away-from-zero shift of a signed int64 by COEFF_BITS.
|
||||
* Only shifts non-negative values, so it is fully defined in C89. */
|
||||
static int64_t sinc_i16_round_shift(int64_t acc)
|
||||
{
|
||||
int64_t half = (int64_t)1 << (SINC_INT16_COEFF_BITS - 1);
|
||||
if (acc >= 0)
|
||||
return ( acc + half) >> SINC_INT16_COEFF_BITS;
|
||||
return -((-acc + half) >> SINC_INT16_COEFF_BITS);
|
||||
}
|
||||
|
||||
static int16_t sinc_i16_sat(int64_t v)
|
||||
{
|
||||
if (v > 32767)
|
||||
return 32767;
|
||||
if (v < -32768)
|
||||
return -32768;
|
||||
return (int16_t)v;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Table generation. */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static void sinc_i16_init_table_kaiser(rarch_sinc_resampler_int16_t *re,
|
||||
double cutoff, int32_t *table, int phases, int taps)
|
||||
{
|
||||
int i, j, p;
|
||||
double beta = re->kaiser_beta;
|
||||
double window_mod = sinc_i16_besseli0(beta);
|
||||
double sidelobes = taps / 2.0;
|
||||
int stride = 2; /* coeff + delta interleaved per phase */
|
||||
|
||||
for (i = 0; i < phases; i++)
|
||||
{
|
||||
for (j = 0; j < taps; j++)
|
||||
{
|
||||
double sinc_phase;
|
||||
double arg;
|
||||
double val;
|
||||
int n = j * phases + i;
|
||||
double window_phase = (double)n / (phases * taps);
|
||||
window_phase = 2.0 * window_phase - 1.0;
|
||||
sinc_phase = sidelobes * window_phase;
|
||||
arg = 1.0 - window_phase * window_phase;
|
||||
if (arg < 0.0)
|
||||
arg = 0.0;
|
||||
val = cutoff * sinc_i16_sinc(M_PI * sinc_phase * cutoff) *
|
||||
sinc_i16_besseli0(beta * sqrt(arg)) / window_mod;
|
||||
table[i * stride * taps + j] = sinc_i16_to_q30(val);
|
||||
}
|
||||
}
|
||||
|
||||
/* Deltas between adjacent phases (for subphase linear interpolation). */
|
||||
for (p = 0; p < phases - 1; p++)
|
||||
{
|
||||
for (j = 0; j < taps; j++)
|
||||
table[(p * stride + 1) * taps + j] =
|
||||
table[(p + 1) * stride * taps + j]
|
||||
- table[ p * stride * taps + j];
|
||||
}
|
||||
|
||||
/* Final phase: delta towards the coefficients at phase == phases. */
|
||||
for (j = 0; j < taps; j++)
|
||||
{
|
||||
double sinc_phase;
|
||||
double arg;
|
||||
double val;
|
||||
int32_t v;
|
||||
int n = j * phases + phases;
|
||||
double window_phase = (double)n / (phases * taps);
|
||||
window_phase = 2.0 * window_phase - 1.0;
|
||||
sinc_phase = sidelobes * window_phase;
|
||||
arg = 1.0 - window_phase * window_phase;
|
||||
if (arg < 0.0)
|
||||
arg = 0.0;
|
||||
val = cutoff * sinc_i16_sinc(M_PI * sinc_phase * cutoff) *
|
||||
sinc_i16_besseli0(beta * sqrt(arg)) / window_mod;
|
||||
v = sinc_i16_to_q30(val);
|
||||
table[((phases - 1) * stride + 1) * taps + j] =
|
||||
v - table[(phases - 1) * stride * taps + j];
|
||||
}
|
||||
}
|
||||
|
||||
static void sinc_i16_init_table_lanczos(rarch_sinc_resampler_int16_t *re,
|
||||
double cutoff, int32_t *table, int phases, int taps)
|
||||
{
|
||||
int i, j;
|
||||
double sidelobes = taps / 2.0;
|
||||
(void)re;
|
||||
|
||||
for (i = 0; i < phases; i++)
|
||||
{
|
||||
for (j = 0; j < taps; j++)
|
||||
{
|
||||
double sinc_phase;
|
||||
double val;
|
||||
int n = j * phases + i;
|
||||
double window_phase = (double)n / (phases * taps);
|
||||
window_phase = 2.0 * window_phase - 1.0;
|
||||
sinc_phase = sidelobes * window_phase;
|
||||
val = cutoff * sinc_i16_sinc(M_PI * sinc_phase * cutoff) *
|
||||
sinc_i16_sinc(M_PI * window_phase);
|
||||
table[i * taps + j] = sinc_i16_to_q30(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Processing. */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
#define SINC_I16_PUSH(re, input, taps, phases, frames) \
|
||||
do { \
|
||||
while ((frames) && (re)->time >= (phases)) \
|
||||
{ \
|
||||
if (!(re)->ptr) \
|
||||
(re)->ptr = (taps); \
|
||||
(re)->ptr--; \
|
||||
(re)->buffer_l[(re)->ptr + (taps)] = \
|
||||
(re)->buffer_l[(re)->ptr] = (input)[0]; \
|
||||
(re)->buffer_r[(re)->ptr + (taps)] = \
|
||||
(re)->buffer_r[(re)->ptr] = (input)[1]; \
|
||||
(input) += 2; \
|
||||
(re)->time -= (phases); \
|
||||
(frames)--; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* Convert the (double) ratio to the fixed-point time step. This is the only
|
||||
* floating-point operation in the whole runtime path, and it is skipped unless
|
||||
* the ratio actually changes: the double's bit pattern is compared as an
|
||||
* integer, so a stable ratio (the common case, and the only one used for
|
||||
* deterministic netplay) touches no FP after the first flush. Bit-exact with
|
||||
* the previous inline (uint32_t)(phases / data->ratio). */
|
||||
static uint32_t sinc_i16_time_step(rarch_sinc_resampler_int16_t *re,
|
||||
unsigned phases, double ratio)
|
||||
{
|
||||
uint64_t bits;
|
||||
memcpy(&bits, &ratio, sizeof(bits));
|
||||
if (bits != re->ratio_bits)
|
||||
{
|
||||
re->ratio_bits = bits;
|
||||
re->ratio_fixed = (uint32_t)(phases / ratio);
|
||||
}
|
||||
return re->ratio_fixed;
|
||||
}
|
||||
|
||||
static void sinc_i16_process_kaiser(rarch_sinc_resampler_int16_t *re,
|
||||
struct resampler_data_int16 *data)
|
||||
{
|
||||
unsigned phases = 1u << (re->phase_bits + re->subphase_bits);
|
||||
uint32_t ratio = sinc_i16_time_step(re, phases, data->ratio);
|
||||
const int16_t *in = data->data_in;
|
||||
int16_t *output = data->data_out;
|
||||
size_t frames = data->input_frames;
|
||||
size_t out_frames = 0;
|
||||
unsigned taps = re->taps;
|
||||
unsigned taps2 = taps * 2;
|
||||
unsigned sb = re->subphase_bits;
|
||||
uint32_t sub_mask = re->subphase_mask;
|
||||
uint32_t row_mask = (1u << re->phase_bits) - 1u;
|
||||
|
||||
while (frames)
|
||||
{
|
||||
SINC_I16_PUSH(re, in, taps, phases, frames);
|
||||
|
||||
{
|
||||
const int16_t *buffer_l = re->buffer_l + re->ptr;
|
||||
const int16_t *buffer_r = re->buffer_r + re->ptr;
|
||||
while (re->time < phases)
|
||||
{
|
||||
unsigned i;
|
||||
int64_t sum_l = 0;
|
||||
int64_t sum_r = 0;
|
||||
unsigned phase = re->time >> sb;
|
||||
const int32_t *pt = re->phase_table + phase * taps2;
|
||||
const int32_t *dt = pt + taps;
|
||||
uint32_t dsub = re->time & sub_mask;
|
||||
const int32_t *np = re->phase_table
|
||||
+ (((re->time + ratio) >> sb) & row_mask) * taps2;
|
||||
|
||||
/* Both halves of the next row: the delta half is read first. */
|
||||
SINC_I16_PREFETCH(np);
|
||||
SINC_I16_PREFETCH(np + taps);
|
||||
|
||||
#ifdef SINC_I16_KAISER_FISSION
|
||||
{
|
||||
int32_t *cs = re->coef_scratch;
|
||||
/* Interp pass: coeff = pt[i] + trunc(dsub*dt[i] / 2^sb).
|
||||
* Adding (2^sb-1) to negative products before the arithmetic
|
||||
* shift turns floor into truncate-toward-zero (branchless, so
|
||||
* the following MAC pass auto-vectorizes; bit-exact). */
|
||||
for (i = 0; i < taps; i++)
|
||||
{
|
||||
int64_t prod = (int64_t)dsub * dt[i];
|
||||
int64_t bias = (prod >> 63) & (((int64_t)1 << sb) - 1);
|
||||
cs[i] = pt[i] + (int32_t)((prod + bias) >> sb);
|
||||
}
|
||||
for (i = 0; i < taps; i++)
|
||||
{
|
||||
sum_l += (int64_t)buffer_l[i] * cs[i];
|
||||
sum_r += (int64_t)buffer_r[i] * cs[i];
|
||||
}
|
||||
}
|
||||
#else
|
||||
for (i = 0; i < taps; i++)
|
||||
{
|
||||
/* coeff = pt[i] + trunc(dsub * dt[i] / 2^sb); dsub >= 0. */
|
||||
int64_t prod = (int64_t)dsub * dt[i];
|
||||
int32_t c;
|
||||
if (prod >= 0)
|
||||
c = pt[i] + (int32_t)( prod >> sb);
|
||||
else
|
||||
c = pt[i] - (int32_t)((-prod) >> sb);
|
||||
sum_l += (int64_t)buffer_l[i] * c;
|
||||
sum_r += (int64_t)buffer_r[i] * c;
|
||||
}
|
||||
#endif
|
||||
|
||||
output[0] = sinc_i16_sat(sinc_i16_round_shift(sum_l));
|
||||
output[1] = sinc_i16_sat(sinc_i16_round_shift(sum_r));
|
||||
output += 2;
|
||||
out_frames++;
|
||||
re->time += ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data->output_frames = out_frames;
|
||||
}
|
||||
|
||||
static void sinc_i16_process_lanczos(rarch_sinc_resampler_int16_t *re,
|
||||
struct resampler_data_int16 *data)
|
||||
{
|
||||
unsigned phases = 1u << (re->phase_bits + re->subphase_bits);
|
||||
uint32_t ratio = sinc_i16_time_step(re, phases, data->ratio);
|
||||
const int16_t *in = data->data_in;
|
||||
int16_t *output = data->data_out;
|
||||
size_t frames = data->input_frames;
|
||||
size_t out_frames = 0;
|
||||
unsigned taps = re->taps;
|
||||
unsigned sb = re->subphase_bits;
|
||||
uint32_t row_mask = (1u << re->phase_bits) - 1u;
|
||||
|
||||
while (frames)
|
||||
{
|
||||
SINC_I16_PUSH(re, in, taps, phases, frames);
|
||||
|
||||
{
|
||||
const int16_t *buffer_l = re->buffer_l + re->ptr;
|
||||
const int16_t *buffer_r = re->buffer_r + re->ptr;
|
||||
while (re->time < phases)
|
||||
{
|
||||
unsigned i;
|
||||
int64_t sum_l = 0;
|
||||
int64_t sum_r = 0;
|
||||
unsigned phase = re->time >> sb;
|
||||
const int32_t *pt = re->phase_table + phase * taps;
|
||||
const int32_t *np = re->phase_table
|
||||
+ (((re->time + ratio) >> sb) & row_mask) * taps;
|
||||
|
||||
SINC_I16_PREFETCH(np);
|
||||
|
||||
for (i = 0; i < taps; i++)
|
||||
{
|
||||
sum_l += (int64_t)buffer_l[i] * pt[i];
|
||||
sum_r += (int64_t)buffer_r[i] * pt[i];
|
||||
}
|
||||
|
||||
output[0] = sinc_i16_sat(sinc_i16_round_shift(sum_l));
|
||||
output[1] = sinc_i16_sat(sinc_i16_round_shift(sum_r));
|
||||
output += 2;
|
||||
out_frames++;
|
||||
re->time += ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data->output_frames = out_frames;
|
||||
}
|
||||
|
||||
void sinc_resampler_int16_process(void *re_, struct resampler_data_int16 *data)
|
||||
{
|
||||
rarch_sinc_resampler_int16_t *re = (rarch_sinc_resampler_int16_t*)re_;
|
||||
if (re->window == SINC_I16_WINDOW_KAISER)
|
||||
sinc_i16_process_kaiser(re, data);
|
||||
else
|
||||
sinc_i16_process_lanczos(re, data);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Lifecycle. */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
void sinc_resampler_int16_free(void *re_)
|
||||
{
|
||||
rarch_sinc_resampler_int16_t *re = (rarch_sinc_resampler_int16_t*)re_;
|
||||
if (re)
|
||||
{
|
||||
free(re->phase_table);
|
||||
free(re->buffer_l);
|
||||
#ifdef SINC_I16_KAISER_FISSION
|
||||
free(re->coef_scratch);
|
||||
#endif
|
||||
}
|
||||
free(re);
|
||||
}
|
||||
|
||||
void *sinc_resampler_int16_init(double bandwidth_mod,
|
||||
enum sinc_int16_quality quality)
|
||||
{
|
||||
double cutoff = 0.0;
|
||||
unsigned sidelobes = 0;
|
||||
int window = SINC_I16_WINDOW_LANCZOS;
|
||||
int stride;
|
||||
size_t phase_elems;
|
||||
int phases;
|
||||
rarch_sinc_resampler_int16_t *re =
|
||||
(rarch_sinc_resampler_int16_t*)calloc(1, sizeof(*re));
|
||||
|
||||
if (!re)
|
||||
return NULL;
|
||||
|
||||
switch (quality)
|
||||
{
|
||||
case SINC_INT16_QUALITY_LOWEST:
|
||||
cutoff = 0.98;
|
||||
sidelobes = 2;
|
||||
re->phase_bits = 12;
|
||||
re->subphase_bits = 10;
|
||||
window = SINC_I16_WINDOW_LANCZOS;
|
||||
break;
|
||||
case SINC_INT16_QUALITY_LOWER:
|
||||
cutoff = 0.98;
|
||||
sidelobes = 4;
|
||||
re->phase_bits = 12;
|
||||
re->subphase_bits = 10;
|
||||
window = SINC_I16_WINDOW_LANCZOS;
|
||||
break;
|
||||
case SINC_INT16_QUALITY_HIGHER:
|
||||
cutoff = 0.90;
|
||||
sidelobes = 32;
|
||||
re->phase_bits = 10;
|
||||
re->subphase_bits = 14;
|
||||
window = SINC_I16_WINDOW_KAISER;
|
||||
re->kaiser_beta = 10.5;
|
||||
break;
|
||||
case SINC_INT16_QUALITY_HIGHEST:
|
||||
cutoff = 0.962;
|
||||
sidelobes = 128;
|
||||
re->phase_bits = 10;
|
||||
re->subphase_bits = 14;
|
||||
window = SINC_I16_WINDOW_KAISER;
|
||||
re->kaiser_beta = 14.5;
|
||||
break;
|
||||
case SINC_INT16_QUALITY_NORMAL:
|
||||
default:
|
||||
cutoff = 0.825;
|
||||
sidelobes = 8;
|
||||
re->phase_bits = 8;
|
||||
re->subphase_bits = 16;
|
||||
window = SINC_I16_WINDOW_KAISER;
|
||||
re->kaiser_beta = 5.5;
|
||||
break;
|
||||
}
|
||||
|
||||
re->window = (unsigned)window;
|
||||
re->subphase_mask = (1u << re->subphase_bits) - 1u;
|
||||
re->taps = sidelobes * 2;
|
||||
|
||||
/* Downsampling: lower cutoff and extend taps to hold stopband. */
|
||||
if (bandwidth_mod < 1.0)
|
||||
{
|
||||
cutoff *= bandwidth_mod;
|
||||
re->taps = (unsigned)ceil((double)re->taps / bandwidth_mod);
|
||||
}
|
||||
|
||||
/* Keep taps a multiple of 8, matching the float driver's rounding so
|
||||
* the two drivers select the same filter on every platform. (Also
|
||||
* satisfies the scalar unroll's multiple-of-4 requirement.) */
|
||||
re->taps = (re->taps + 7u) & ~7u;
|
||||
|
||||
stride = (window == SINC_I16_WINDOW_KAISER) ? 2 : 1;
|
||||
phases = 1 << re->phase_bits;
|
||||
phase_elems = (size_t)phases * re->taps * stride;
|
||||
|
||||
re->phase_table = (int32_t*)malloc(sizeof(int32_t) * phase_elems);
|
||||
re->buffer_l = (int16_t*)calloc(4 * re->taps, sizeof(int16_t));
|
||||
if (!re->phase_table || !re->buffer_l)
|
||||
goto error;
|
||||
re->buffer_r = re->buffer_l + 2 * re->taps;
|
||||
|
||||
#ifdef SINC_I16_KAISER_FISSION
|
||||
re->coef_scratch = (int32_t*)malloc(sizeof(int32_t) * (re->taps + 4u));
|
||||
if (!re->coef_scratch)
|
||||
goto error;
|
||||
#endif
|
||||
|
||||
if (window == SINC_I16_WINDOW_KAISER)
|
||||
sinc_i16_init_table_kaiser(re, cutoff, re->phase_table,
|
||||
phases, (int)re->taps);
|
||||
else
|
||||
sinc_i16_init_table_lanczos(re, cutoff, re->phase_table,
|
||||
phases, (int)re->taps);
|
||||
|
||||
return re;
|
||||
|
||||
error:
|
||||
sinc_resampler_int16_free(re);
|
||||
return NULL;
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (sinc_resampler_neon.S).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#if defined(__ARM_NEON__) && defined(HAVE_ARM_NEON_ASM_OPTIMIZATIONS)
|
||||
|
||||
#ifndef __MACH__
|
||||
.arm
|
||||
#endif
|
||||
.align 4
|
||||
.globl process_sinc_neon_asm
|
||||
#ifndef __MACH__
|
||||
.type process_sinc_neon_asm, %function
|
||||
#endif
|
||||
.globl _process_sinc_neon_asm
|
||||
#ifndef __MACH__
|
||||
.type _process_sinc_neon_asm, %function
|
||||
#endif
|
||||
# void process_sinc_neon(float *out, const float *left, const float *right, const float *coeff, unsigned taps)
|
||||
# Assumes taps is >= 8, and a multiple of 8.
|
||||
process_sinc_neon_asm:
|
||||
_process_sinc_neon_asm:
|
||||
|
||||
push {r4, lr}
|
||||
vmov.f32 q0, #0.0
|
||||
vmov.f32 q8, #0.0
|
||||
|
||||
# Taps argument (r4) goes on stack in armeabi.
|
||||
ldr r4, [sp, #8]
|
||||
|
||||
1:
|
||||
# Left
|
||||
vld1.f32 {q2-q3}, [r1]!
|
||||
# Right
|
||||
vld1.f32 {q10-q11}, [r2]!
|
||||
# Coeff
|
||||
vld1.f32 {q12-q13}, [r3, :128]!
|
||||
|
||||
# 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, pc}
|
||||
|
||||
#endif
|
||||
|
||||
/* Prevent the stack from becoming executable for no reason...
|
||||
* Placed last: a bare .section switches the active section, and these
|
||||
* files emit into the assembler default (.text) with no .text directive
|
||||
* of their own to switch back. */
|
||||
#if defined(__linux__) && defined(__ELF__)
|
||||
.section .note.GNU-stack,"",%progbits
|
||||
#endif
|
||||
@@ -1,237 +0,0 @@
|
||||
/* Faithful float reference resampler mirroring RetroArch's
|
||||
* drivers/sinc_resampler.c (Kaiser table + subphase delta interpolation, and
|
||||
* Lanczos without interpolation). Used only by the test harness to diff the
|
||||
* integer driver against the float driver. Not for shipping. */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "sinc_ref_float.h"
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
enum { REF_WIN_LANCZOS = 0, REF_WIN_KAISER };
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float *phase_table;
|
||||
float *buffer_l;
|
||||
float *buffer_r;
|
||||
unsigned phase_bits;
|
||||
unsigned subphase_bits;
|
||||
unsigned subphase_mask;
|
||||
unsigned taps;
|
||||
unsigned window;
|
||||
unsigned ptr;
|
||||
uint32_t time;
|
||||
float subphase_mod;
|
||||
double kaiser_beta;
|
||||
} ref_sinc_t;
|
||||
|
||||
static double ref_sinc(double v)
|
||||
{
|
||||
if (fabs(v) < 0.00000000001)
|
||||
return 1.0;
|
||||
return sin(v) / v;
|
||||
}
|
||||
|
||||
static double ref_besseli0(double x)
|
||||
{
|
||||
double sum = 1.0, term = 1.0, hs = (x * 0.5) * (x * 0.5);
|
||||
int k = 1;
|
||||
for (;;)
|
||||
{
|
||||
term *= hs / ((double)k * (double)k);
|
||||
sum += term;
|
||||
if (term < 1.0e-21 * sum)
|
||||
break;
|
||||
k++;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
static void ref_table_kaiser(ref_sinc_t *re, double cutoff,
|
||||
float *t, int phases, int taps)
|
||||
{
|
||||
int i, j, p;
|
||||
double beta = re->kaiser_beta;
|
||||
double wm = ref_besseli0(beta);
|
||||
double sl = taps / 2.0;
|
||||
int st = 2;
|
||||
for (i = 0; i < phases; i++)
|
||||
for (j = 0; j < taps; j++)
|
||||
{
|
||||
double sp, arg, wp;
|
||||
int n = j * phases + i;
|
||||
wp = (double)n / (phases * taps);
|
||||
wp = 2.0 * wp - 1.0;
|
||||
sp = sl * wp;
|
||||
arg = 1.0 - wp * wp;
|
||||
if (arg < 0.0) arg = 0.0;
|
||||
t[i * st * taps + j] = (float)(cutoff * ref_sinc(M_PI * sp * cutoff) *
|
||||
ref_besseli0(beta * sqrt(arg)) / wm);
|
||||
}
|
||||
for (p = 0; p < phases - 1; p++)
|
||||
for (j = 0; j < taps; j++)
|
||||
t[(p * st + 1) * taps + j] =
|
||||
t[(p + 1) * st * taps + j] - t[p * st * taps + j];
|
||||
for (j = 0; j < taps; j++)
|
||||
{
|
||||
double sp, arg, wp; float v;
|
||||
int n = j * phases + phases;
|
||||
wp = (double)n / (phases * taps);
|
||||
wp = 2.0 * wp - 1.0;
|
||||
sp = sl * wp;
|
||||
arg = 1.0 - wp * wp;
|
||||
if (arg < 0.0) arg = 0.0;
|
||||
v = (float)(cutoff * ref_sinc(M_PI * sp * cutoff) *
|
||||
ref_besseli0(beta * sqrt(arg)) / wm);
|
||||
t[((phases - 1) * st + 1) * taps + j] =
|
||||
v - t[(phases - 1) * st * taps + j];
|
||||
}
|
||||
}
|
||||
|
||||
static void ref_table_lanczos(ref_sinc_t *re, double cutoff,
|
||||
float *t, int phases, int taps)
|
||||
{
|
||||
int i, j;
|
||||
double sl = taps / 2.0;
|
||||
(void)re;
|
||||
for (i = 0; i < phases; i++)
|
||||
for (j = 0; j < taps; j++)
|
||||
{
|
||||
double sp, wp;
|
||||
int n = j * phases + i;
|
||||
wp = (double)n / (phases * taps);
|
||||
wp = 2.0 * wp - 1.0;
|
||||
sp = sl * wp;
|
||||
t[i * taps + j] = (float)(cutoff * ref_sinc(M_PI * sp * cutoff) *
|
||||
ref_sinc(M_PI * wp));
|
||||
}
|
||||
}
|
||||
|
||||
#define REF_PUSH(re, in, taps, phases, frames) \
|
||||
do { \
|
||||
while ((frames) && (re)->time >= (phases)) { \
|
||||
if (!(re)->ptr) (re)->ptr = (taps); \
|
||||
(re)->ptr--; \
|
||||
(re)->buffer_l[(re)->ptr + (taps)] = (re)->buffer_l[(re)->ptr] = (in)[0]; \
|
||||
(re)->buffer_r[(re)->ptr + (taps)] = (re)->buffer_r[(re)->ptr] = (in)[1]; \
|
||||
(in) += 2; (re)->time -= (phases); (frames)--; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
void sinc_ref_float_process(void *re_, struct resampler_data_float *data)
|
||||
{
|
||||
ref_sinc_t *re = (ref_sinc_t*)re_;
|
||||
unsigned phases = 1u << (re->phase_bits + re->subphase_bits);
|
||||
uint32_t ratio = (uint32_t)(phases / data->ratio);
|
||||
const float *in = data->data_in;
|
||||
float *output = data->data_out;
|
||||
size_t frames = data->input_frames;
|
||||
size_t out_frames = 0;
|
||||
unsigned taps = re->taps;
|
||||
unsigned taps2 = taps * 2;
|
||||
int kaiser = (re->window == REF_WIN_KAISER);
|
||||
|
||||
while (frames)
|
||||
{
|
||||
REF_PUSH(re, in, taps, phases, frames);
|
||||
{
|
||||
const float *bl = re->buffer_l + re->ptr;
|
||||
const float *br = re->buffer_r + re->ptr;
|
||||
while (re->time < phases)
|
||||
{
|
||||
unsigned i;
|
||||
float sl = 0.0f, sr = 0.0f;
|
||||
unsigned phase = re->time >> re->subphase_bits;
|
||||
if (kaiser)
|
||||
{
|
||||
const float *pt = re->phase_table + phase * taps2;
|
||||
const float *dt = pt + taps;
|
||||
float delta = (float)(re->time & re->subphase_mask) * re->subphase_mod;
|
||||
for (i = 0; i < taps; i++)
|
||||
{
|
||||
float c = pt[i] + dt[i] * delta;
|
||||
sl += bl[i] * c;
|
||||
sr += br[i] * c;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const float *pt = re->phase_table + phase * taps;
|
||||
for (i = 0; i < taps; i++)
|
||||
{
|
||||
sl += bl[i] * pt[i];
|
||||
sr += br[i] * pt[i];
|
||||
}
|
||||
}
|
||||
output[0] = sl;
|
||||
output[1] = sr;
|
||||
output += 2;
|
||||
out_frames++;
|
||||
re->time += ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
data->output_frames = out_frames;
|
||||
}
|
||||
|
||||
void sinc_ref_float_free(void *re_)
|
||||
{
|
||||
ref_sinc_t *re = (ref_sinc_t*)re_;
|
||||
if (re) { free(re->phase_table); free(re->buffer_l); }
|
||||
free(re);
|
||||
}
|
||||
|
||||
void *sinc_ref_float_init(double bandwidth_mod, int quality)
|
||||
{
|
||||
double cutoff = 0.0;
|
||||
unsigned sidelobes = 0;
|
||||
int window = REF_WIN_LANCZOS;
|
||||
int stride, phases;
|
||||
size_t phase_elems;
|
||||
ref_sinc_t *re = (ref_sinc_t*)calloc(1, sizeof(*re));
|
||||
if (!re) return NULL;
|
||||
|
||||
/* quality: 0 LOWEST,1 LOWER,2 NORMAL,3 HIGHER,4 HIGHEST */
|
||||
switch (quality)
|
||||
{
|
||||
case 0: cutoff=0.98; sidelobes=2; re->phase_bits=12; re->subphase_bits=10; window=REF_WIN_LANCZOS; break;
|
||||
case 1: cutoff=0.98; sidelobes=4; re->phase_bits=12; re->subphase_bits=10; window=REF_WIN_LANCZOS; break;
|
||||
case 3: cutoff=0.90; sidelobes=32; re->phase_bits=10; re->subphase_bits=14; window=REF_WIN_KAISER; re->kaiser_beta=10.5; break;
|
||||
case 4: cutoff=0.962; sidelobes=128; re->phase_bits=10; re->subphase_bits=14; window=REF_WIN_KAISER; re->kaiser_beta=14.5; break;
|
||||
case 2:
|
||||
default: cutoff=0.825; sidelobes=8; re->phase_bits=8; re->subphase_bits=16; window=REF_WIN_KAISER; re->kaiser_beta=5.5; break;
|
||||
}
|
||||
|
||||
re->window = (unsigned)window;
|
||||
re->subphase_mask = (1u << re->subphase_bits) - 1u;
|
||||
re->subphase_mod = 1.0f / (1u << re->subphase_bits);
|
||||
re->taps = sidelobes * 2;
|
||||
if (bandwidth_mod < 1.0)
|
||||
{
|
||||
cutoff *= bandwidth_mod;
|
||||
re->taps = (unsigned)ceil((double)re->taps / bandwidth_mod);
|
||||
}
|
||||
re->taps = (re->taps + 7u) & ~7u;
|
||||
|
||||
stride = (window == REF_WIN_KAISER) ? 2 : 1;
|
||||
phases = 1 << re->phase_bits;
|
||||
phase_elems = (size_t)phases * re->taps * stride;
|
||||
|
||||
re->phase_table = (float*)malloc(sizeof(float) * phase_elems);
|
||||
re->buffer_l = (float*)calloc(4 * re->taps, sizeof(float));
|
||||
if (!re->phase_table || !re->buffer_l) { sinc_ref_float_free(re); return NULL; }
|
||||
re->buffer_r = re->buffer_l + 2 * re->taps;
|
||||
|
||||
if (window == REF_WIN_KAISER)
|
||||
ref_table_kaiser(re, cutoff, re->phase_table, phases, (int)re->taps);
|
||||
else
|
||||
ref_table_lanczos(re, cutoff, re->phase_table, phases, (int)re->taps);
|
||||
return re;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef SINC_REF_FLOAT_H
|
||||
#define SINC_REF_FLOAT_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
struct resampler_data_float
|
||||
{
|
||||
const float *data_in;
|
||||
float *data_out;
|
||||
size_t input_frames;
|
||||
size_t output_frames;
|
||||
double ratio;
|
||||
};
|
||||
|
||||
void *sinc_ref_float_init(double bandwidth_mod, int quality);
|
||||
void sinc_ref_float_process(void *re, struct resampler_data_float *data);
|
||||
void sinc_ref_float_free(void *re);
|
||||
|
||||
#endif
|
||||
@@ -1,362 +0,0 @@
|
||||
/* Accuracy / determinism harness: diffs the integer s16 Convoluted Cosine
|
||||
* resampler against the faithful float reference (a verbatim copy of the
|
||||
* production cc_resampler.c C path, CC_RESAMPLER_PRECISION 1) across a
|
||||
* rate x signal matrix. The int16 kernel is a Q24 fixed-point mirror, so
|
||||
* small quantisation diffs are expected; we report max LSB diff, mismatch
|
||||
* rate and resampled-tone SNR for both endpoints.
|
||||
*
|
||||
* Build: cc -O2 -std=c89 -Wall test_cc_int16.c cc_resampler_int16.c \
|
||||
* -I ../../../include -lm -o test_cc_int16 */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <audio/cc_resampler_int16.h>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
#ifndef CC_RESAMPLER_PRECISION
|
||||
#define CC_RESAMPLER_PRECISION 1
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Double-precision ideal reference: the production cc_resampler.c "C"
|
||||
* kernel (degree-5 polynomial at CC_RESAMPLER_PRECISION > 0) evaluated
|
||||
* in double. Using double for the phase accumulator and the kernel
|
||||
* makes this a faithful oracle for *correctness*: unlike the shipped
|
||||
* float driver, its distance accumulator does not drift, so it stays
|
||||
* phase-aligned with the near-exact Q24 int16 driver and the remaining
|
||||
* diff reflects only Q24 kernel quantisation. *
|
||||
* ------------------------------------------------------------------ */
|
||||
struct resampler_data_float
|
||||
{
|
||||
const float *data_in;
|
||||
float *data_out;
|
||||
size_t input_frames;
|
||||
size_t output_frames;
|
||||
double ratio;
|
||||
};
|
||||
|
||||
typedef struct cc_ref
|
||||
{
|
||||
double bl[4];
|
||||
double br[4];
|
||||
double distance;
|
||||
int up;
|
||||
} cc_ref_t;
|
||||
|
||||
static double cc_ref_int(double x, double b)
|
||||
{
|
||||
double val = x * b;
|
||||
#if (CC_RESAMPLER_PRECISION > 0)
|
||||
val = val * (1.0 - 0.25 * val * val * (3.0 - val * val));
|
||||
#endif
|
||||
return (val > 0.5) ? 0.5 : (val < -0.5) ? -0.5 : val;
|
||||
}
|
||||
|
||||
#define cc_ref_kernel(x, b) (cc_ref_int((x) + 0.5, (b)) - cc_ref_int((x) - 0.5, (b)))
|
||||
|
||||
static void *cc_ref_init(double bandwidth_mod)
|
||||
{
|
||||
cc_ref_t *re = (cc_ref_t*)calloc(1, sizeof(*re));
|
||||
if (!re)
|
||||
return NULL;
|
||||
if (bandwidth_mod < 0.75)
|
||||
{
|
||||
re->up = 0;
|
||||
re->distance = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
re->up = 1;
|
||||
re->distance = 2.0;
|
||||
}
|
||||
return re;
|
||||
}
|
||||
|
||||
static void cc_ref_downsample(cc_ref_t *re, struct resampler_data_float *data)
|
||||
{
|
||||
const float *inp = data->data_in;
|
||||
const float *inp_max = inp + data->input_frames * 2;
|
||||
float *outp = data->data_out;
|
||||
float *outp0 = outp;
|
||||
double ratio = 1.0 / data->ratio;
|
||||
double b = data->ratio;
|
||||
|
||||
while (inp != inp_max)
|
||||
{
|
||||
double k0 = cc_ref_kernel(re->distance, b);
|
||||
double k1 = cc_ref_kernel(re->distance - ratio, b);
|
||||
double k2 = cc_ref_kernel(re->distance - ratio - ratio, b);
|
||||
|
||||
re->bl[0] += (double)inp[0] * k0; re->br[0] += (double)inp[1] * k0;
|
||||
re->bl[1] += (double)inp[0] * k1; re->br[1] += (double)inp[1] * k1;
|
||||
re->bl[2] += (double)inp[0] * k2; re->br[2] += (double)inp[1] * k2;
|
||||
|
||||
re->distance += 1.0;
|
||||
inp += 2;
|
||||
|
||||
if (re->distance > (ratio + 0.5))
|
||||
{
|
||||
outp[0] = (float)re->bl[0]; outp[1] = (float)re->br[0];
|
||||
re->bl[0] = re->bl[1]; re->bl[1] = re->bl[2]; re->bl[2] = 0.0;
|
||||
re->br[0] = re->br[1]; re->br[1] = re->br[2]; re->br[2] = 0.0;
|
||||
re->distance -= ratio;
|
||||
outp += 2;
|
||||
}
|
||||
}
|
||||
data->output_frames = (size_t)((outp - outp0) >> 1);
|
||||
}
|
||||
|
||||
static void cc_ref_upsample(cc_ref_t *re, struct resampler_data_float *data)
|
||||
{
|
||||
const float *inp = data->data_in;
|
||||
const float *inp_max = inp + data->input_frames * 2;
|
||||
float *outp = data->data_out;
|
||||
float *outp0 = outp;
|
||||
double b = (data->ratio < 1.0) ? data->ratio : 1.0;
|
||||
double ratio = 1.0 / data->ratio;
|
||||
|
||||
while (inp != inp_max)
|
||||
{
|
||||
re->bl[0] = re->bl[1]; re->bl[1] = re->bl[2]; re->bl[2] = re->bl[3]; re->bl[3] = (double)inp[0];
|
||||
re->br[0] = re->br[1]; re->br[1] = re->br[2]; re->br[2] = re->br[3]; re->br[3] = (double)inp[1];
|
||||
|
||||
while (re->distance < 1.0)
|
||||
{
|
||||
int i;
|
||||
double ol = 0.0, orr = 0.0;
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
double k = cc_ref_kernel(re->distance + 1.0 - (double)i, b);
|
||||
ol += re->bl[i] * k;
|
||||
orr += re->br[i] * k;
|
||||
}
|
||||
outp[0] = (float)ol; outp[1] = (float)orr;
|
||||
re->distance += ratio;
|
||||
outp += 2;
|
||||
}
|
||||
re->distance -= 1.0;
|
||||
inp += 2;
|
||||
}
|
||||
data->output_frames = (size_t)((outp - outp0) >> 1);
|
||||
}
|
||||
|
||||
static void cc_ref_process(void *re_, struct resampler_data_float *data)
|
||||
{
|
||||
cc_ref_t *re = (cc_ref_t*)re_;
|
||||
if (re->up)
|
||||
cc_ref_upsample(re, data);
|
||||
else
|
||||
cc_ref_downsample(re, data);
|
||||
}
|
||||
|
||||
static void cc_ref_free(void *re_)
|
||||
{
|
||||
if (re_)
|
||||
free(re_);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int16_t sat16(double v)
|
||||
{
|
||||
long r;
|
||||
if (v >= 0.0) r = (long)floor(v + 0.5);
|
||||
else r = (long)ceil(v - 0.5);
|
||||
if (r > 32767) return 32767;
|
||||
if (r < -32768) return -32768;
|
||||
return (int16_t)r;
|
||||
}
|
||||
|
||||
static double tone_snr(const int16_t *y, size_t frames, double f, double fs)
|
||||
{
|
||||
double a[3][3], b[3], c[3];
|
||||
double sig, res = 0.0;
|
||||
size_t n, lo = 3000, hi = (frames > 3000) ? frames - 3000 : frames;
|
||||
size_t cnt = 0;
|
||||
int i, j, k;
|
||||
memset(a, 0, sizeof(a)); memset(b, 0, sizeof(b));
|
||||
for (n = lo; n < hi; n++)
|
||||
{
|
||||
double t = 2.0 * M_PI * f * (double)n / fs;
|
||||
double bb[3];
|
||||
double yy = (double)y[2 * n] / 32768.0;
|
||||
bb[0] = cos(t); bb[1] = sin(t); bb[2] = 1.0;
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
b[i] += bb[i] * yy;
|
||||
for (j = 0; j < 3; j++) a[i][j] += bb[i] * bb[j];
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
for (i = 0; i < 3; i++) c[i] = b[i];
|
||||
for (k = 0; k < 3; k++)
|
||||
{
|
||||
int piv = k; double mx = fabs(a[k][k]);
|
||||
for (i = k + 1; i < 3; i++) if (fabs(a[i][k]) > mx) { mx = fabs(a[i][k]); piv = i; }
|
||||
if (piv != k) { for (j = 0; j < 3; j++) { double tmp=a[k][j]; a[k][j]=a[piv][j]; a[piv][j]=tmp; } { double tmp=c[k]; c[k]=c[piv]; c[piv]=tmp; } }
|
||||
for (i = k + 1; i < 3; i++)
|
||||
{
|
||||
double f2 = a[i][k] / a[k][k];
|
||||
for (j = k; j < 3; j++) a[i][j] -= f2 * a[k][j];
|
||||
c[i] -= f2 * c[k];
|
||||
}
|
||||
}
|
||||
for (i = 2; i >= 0; i--) { double s = c[i]; for (j = i+1; j < 3; j++) s -= a[i][j]*c[j]; c[i] = s / a[i][i]; }
|
||||
for (n = lo; n < hi; n++)
|
||||
{
|
||||
double t = 2.0 * M_PI * f * (double)n / fs;
|
||||
double model = c[0]*cos(t) + c[1]*sin(t) + c[2];
|
||||
double e = (double)y[2 * n] / 32768.0 - model;
|
||||
res += e * e;
|
||||
}
|
||||
sig = sqrt((c[0]*c[0] + c[1]*c[1]) / 2.0);
|
||||
res = sqrt(res / (double)cnt);
|
||||
if (res < 1e-300) res = 1e-300;
|
||||
return 20.0 * log10(sig / res);
|
||||
}
|
||||
|
||||
static long g_fail = 0;
|
||||
|
||||
static void run_case(double in_rate, double out_rate, double freq, double level_db,
|
||||
double min_snr)
|
||||
{
|
||||
double bw = out_rate / in_rate;
|
||||
double amp = pow(10.0, level_db / 20.0);
|
||||
size_t Nin = (size_t)(in_rate * 2.0);
|
||||
size_t Nout = (size_t)(out_rate * 2.2) + 64;
|
||||
int16_t *in_i16 = (int16_t*)malloc(sizeof(int16_t) * 2 * Nin);
|
||||
float *in_f = (float*)malloc(sizeof(float) * 2 * Nin);
|
||||
int16_t *out_fx = (int16_t*)malloc(sizeof(int16_t) * 2 * Nout);
|
||||
float *out_ff = (float*)malloc(sizeof(float) * 2 * Nout);
|
||||
int16_t *out_rf = (int16_t*)malloc(sizeof(int16_t) * 2 * Nout);
|
||||
void *re_i = cc_resampler_int16_init(bw);
|
||||
void *re_f = cc_ref_init(bw);
|
||||
struct resampler_data_int16 di;
|
||||
struct resampler_data_float df;
|
||||
size_t n, common;
|
||||
long maxd = 0, mism = 0;
|
||||
double snr_fx, snr_rf;
|
||||
|
||||
for (n = 0; n < Nin; n++)
|
||||
{
|
||||
double s = amp * sin(2.0 * M_PI * freq * (double)n / in_rate);
|
||||
int16_t q = sat16(s * 32767.0);
|
||||
in_i16[2*n] = in_i16[2*n+1] = q;
|
||||
in_f[2*n] = in_f[2*n+1] = (float)q / 32768.0f;
|
||||
}
|
||||
|
||||
di.data_in = in_i16; di.data_out = out_fx; di.input_frames = Nin; di.ratio = bw;
|
||||
cc_resampler_int16_process(re_i, &di);
|
||||
|
||||
df.data_in = in_f; df.data_out = out_ff; df.input_frames = Nin; df.ratio = bw;
|
||||
cc_ref_process(re_f, &df);
|
||||
|
||||
for (n = 0; n < df.output_frames && n < Nout; n++)
|
||||
{
|
||||
out_rf[2*n] = sat16((double)out_ff[2*n] * 32768.0);
|
||||
out_rf[2*n+1] = sat16((double)out_ff[2*n+1] * 32768.0);
|
||||
}
|
||||
|
||||
common = (di.output_frames < df.output_frames) ? di.output_frames : df.output_frames;
|
||||
for (n = 0; n < common; n++)
|
||||
{
|
||||
long d = (long)out_fx[2*n] - (long)out_rf[2*n];
|
||||
if (d < 0) d = -d;
|
||||
if (d > maxd) maxd = d;
|
||||
if (d != 0) mism++;
|
||||
}
|
||||
|
||||
snr_fx = tone_snr(out_fx, common, freq, out_rate);
|
||||
snr_rf = tone_snr(out_rf, common, freq, out_rate);
|
||||
|
||||
printf("%6.0f->%-6.0f %6.0fHz %4.0fdB | frames fx=%lu rf=%lu | maxdiff=%ld LSB mism=%ld/%lu (%.3f%%) | SNR fx=%6.2f rf=%6.2f dB\n",
|
||||
in_rate, out_rate, freq, level_db,
|
||||
(unsigned long)di.output_frames, (unsigned long)df.output_frames,
|
||||
maxd, mism, (unsigned long)common,
|
||||
common ? 100.0 * (double)mism / (double)common : 0.0,
|
||||
snr_fx, snr_rf);
|
||||
|
||||
/* Correctness gate is SNR parity with the double ideal, not raw LSB
|
||||
* diff: the Q24 phase resolution amplifies into larger single-sample
|
||||
* diffs as signal slope rises (worst near Nyquist), but that is
|
||||
* quality-neutral as long as the int16 endpoint holds the same SNR.
|
||||
* - a clean 1 kHz tone must resolve well (absolute floor);
|
||||
* - the int16 SNR must stay within 1 dB of the ideal;
|
||||
* - a loose maxdiff ceiling only catches gross breakage. */
|
||||
if (min_snr > 0.0 && snr_fx < min_snr) g_fail++;
|
||||
if (snr_rf > 6.0 && snr_fx < snr_rf - 1.0) g_fail++;
|
||||
if (maxd > 512) g_fail++;
|
||||
|
||||
cc_resampler_int16_free(re_i);
|
||||
cc_ref_free(re_f);
|
||||
free(in_i16); free(in_f); free(out_fx); free(out_ff); free(out_rf);
|
||||
}
|
||||
|
||||
static void run_determinism(double in_rate, double out_rate)
|
||||
{
|
||||
double bw = out_rate / in_rate;
|
||||
size_t Nin = (size_t)(in_rate);
|
||||
size_t Nout = (size_t)(out_rate * 1.2) + 64;
|
||||
int16_t *in = (int16_t*)malloc(sizeof(int16_t) * 2 * Nin);
|
||||
int16_t *o1 = (int16_t*)malloc(sizeof(int16_t) * 2 * Nout);
|
||||
int16_t *o2 = (int16_t*)malloc(sizeof(int16_t) * 2 * Nout);
|
||||
struct resampler_data_int16 d1, d2;
|
||||
void *r1, *r2;
|
||||
size_t n;
|
||||
int bad = 0;
|
||||
|
||||
for (n = 0; n < Nin; n++)
|
||||
{
|
||||
int16_t q = sat16(0.8 * sin(2.0 * M_PI * 997.0 * (double)n / in_rate) * 32767.0);
|
||||
in[2*n] = q; in[2*n+1] = (int16_t)-q;
|
||||
}
|
||||
r1 = cc_resampler_int16_init(bw);
|
||||
d1.data_in = in; d1.data_out = o1; d1.input_frames = Nin; d1.ratio = bw;
|
||||
cc_resampler_int16_process(r1, &d1);
|
||||
r2 = cc_resampler_int16_init(bw);
|
||||
d2.data_in = in; d2.data_out = o2; d2.input_frames = Nin; d2.ratio = bw;
|
||||
cc_resampler_int16_process(r2, &d2);
|
||||
|
||||
if (d1.output_frames != d2.output_frames) bad = 1;
|
||||
else if (memcmp(o1, o2, d1.output_frames * 2 * sizeof(int16_t))) bad = 1;
|
||||
printf("determinism %6.0f->%-6.0f : %s\n", in_rate, out_rate, bad ? "FAIL" : "identical");
|
||||
if (bad) g_fail++;
|
||||
|
||||
cc_resampler_int16_free(r1);
|
||||
cc_resampler_int16_free(r2);
|
||||
free(in); free(o1); free(o2);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
struct { double in, out; } rates[] = {
|
||||
{ 32040.0, 96000.0 }, { 44100.0, 96000.0 }, { 48000.0, 96000.0 },
|
||||
{ 32040.0, 48000.0 }, { 22050.0, 44100.0 }, { 48000.0, 32040.0 },
|
||||
{ 96000.0, 48000.0 }
|
||||
};
|
||||
size_t r;
|
||||
|
||||
printf("=== integer s16 CC vs double-ideal CC reference (int16 endpoint) ===\n\n");
|
||||
for (r = 0; r < sizeof(rates)/sizeof(rates[0]); r++)
|
||||
run_case(rates[r].in, rates[r].out, 1000.0, 0.0, 30.0);
|
||||
|
||||
printf("\n=== stress: high tone + low level ===\n\n");
|
||||
run_case(32040.0, 96000.0, 12000.0, 0.0, 0.0);
|
||||
run_case(48000.0, 32040.0, 8000.0, 0.0, 0.0);
|
||||
run_case(32040.0, 96000.0, 1000.0, -40.0, 0.0);
|
||||
run_case(32040.0, 96000.0, 1000.0, -60.0, 0.0);
|
||||
|
||||
printf("\n=== determinism (same input -> byte-identical output) ===\n\n");
|
||||
run_determinism(32040.0, 96000.0);
|
||||
run_determinism(48000.0, 32040.0);
|
||||
|
||||
printf("\n%s (%ld check(s) failed)\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS", g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
/* Accuracy / determinism harness: diffs the integer s16 nearest resampler
|
||||
* against a double-precision ideal reference across a rate x signal matrix.
|
||||
*
|
||||
* Nearest is pure sample-and-hold, so the driver only ever *selects* an input
|
||||
* frame - it never arithmetically combines samples. The correctness question
|
||||
* is therefore purely one of phase/timing: does the int16 Q32 accumulator pick
|
||||
* the same input frame the exact (double-precision) accumulator would? The
|
||||
* reference here uses an exact 1.0/ratio step in double, i.e. ground truth.
|
||||
* (The production float driver is a poorer oracle for this: its float fraction
|
||||
* accumulator drifts by ~0.02 frame over 200k outputs and crosses a few
|
||||
* hundred sample boundaries the int16 path does not - which is float error,
|
||||
* not int16 error.) We report frame-count agreement, the per-sample mismatch
|
||||
* rate and the resampled-tone SNR.
|
||||
*
|
||||
* Build: cc -O2 -std=c89 -Wall test_nearest_int16.c nearest_resampler_int16.c \
|
||||
* -I ../../../include -lm -o test_nearest_int16 */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <audio/nearest_resampler_int16.h>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Double-precision ideal reference: sample-and-hold with an exact
|
||||
* 1.0/ratio step (interleaved stereo). Ground truth for phase. *
|
||||
* ------------------------------------------------------------------ */
|
||||
struct resampler_data_float
|
||||
{
|
||||
const float *data_in;
|
||||
float *data_out;
|
||||
size_t input_frames;
|
||||
size_t output_frames;
|
||||
double ratio;
|
||||
};
|
||||
|
||||
typedef struct near_ref
|
||||
{
|
||||
double fraction;
|
||||
} near_ref_t;
|
||||
|
||||
static void *near_ref_init(void)
|
||||
{
|
||||
near_ref_t *re = (near_ref_t*)calloc(1, sizeof(*re));
|
||||
if (re)
|
||||
re->fraction = 0.0;
|
||||
return re;
|
||||
}
|
||||
|
||||
static void near_ref_process(void *re_, struct resampler_data_float *data)
|
||||
{
|
||||
near_ref_t *re = (near_ref_t*)re_;
|
||||
const float *inp = data->data_in;
|
||||
const float *inp_max = inp + data->input_frames * 2;
|
||||
float *outp = data->data_out;
|
||||
float *outp0 = outp;
|
||||
double ratio = 1.0 / data->ratio;
|
||||
|
||||
while (inp != inp_max)
|
||||
{
|
||||
while (re->fraction > 1.0)
|
||||
{
|
||||
outp[0] = inp[0];
|
||||
outp[1] = inp[1];
|
||||
outp += 2;
|
||||
re->fraction -= ratio;
|
||||
}
|
||||
re->fraction += 1.0;
|
||||
inp += 2;
|
||||
}
|
||||
|
||||
data->output_frames = (size_t)((outp - outp0) >> 1);
|
||||
}
|
||||
|
||||
static void near_ref_free(void *re_)
|
||||
{
|
||||
if (re_)
|
||||
free(re_);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int16_t sat16(double v)
|
||||
{
|
||||
long r;
|
||||
if (v >= 0.0) r = (long)floor(v + 0.5);
|
||||
else r = (long)ceil(v - 0.5);
|
||||
if (r > 32767) return 32767;
|
||||
if (r < -32768) return -32768;
|
||||
return (int16_t)r;
|
||||
}
|
||||
|
||||
/* THD+N SNR (dB) of a resampled pure tone: fit-and-remove the fundamental. */
|
||||
static double tone_snr(const int16_t *y, size_t frames, double f, double fs)
|
||||
{
|
||||
double a[3][3], b[3], c[3];
|
||||
double sig, res = 0.0;
|
||||
size_t n, lo = 3000, hi = (frames > 3000) ? frames - 3000 : frames;
|
||||
size_t cnt = 0;
|
||||
int i, j, k;
|
||||
memset(a, 0, sizeof(a)); memset(b, 0, sizeof(b));
|
||||
for (n = lo; n < hi; n++)
|
||||
{
|
||||
double t = 2.0 * M_PI * f * (double)n / fs;
|
||||
double bb[3];
|
||||
double yy = (double)y[2 * n] / 32768.0;
|
||||
bb[0] = cos(t); bb[1] = sin(t); bb[2] = 1.0;
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
b[i] += bb[i] * yy;
|
||||
for (j = 0; j < 3; j++) a[i][j] += bb[i] * bb[j];
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
for (i = 0; i < 3; i++) c[i] = b[i];
|
||||
for (k = 0; k < 3; k++)
|
||||
{
|
||||
int piv = k; double mx = fabs(a[k][k]);
|
||||
for (i = k + 1; i < 3; i++) if (fabs(a[i][k]) > mx) { mx = fabs(a[i][k]); piv = i; }
|
||||
if (piv != k) { for (j = 0; j < 3; j++) { double tmp=a[k][j]; a[k][j]=a[piv][j]; a[piv][j]=tmp; } { double tmp=c[k]; c[k]=c[piv]; c[piv]=tmp; } }
|
||||
for (i = k + 1; i < 3; i++)
|
||||
{
|
||||
double f2 = a[i][k] / a[k][k];
|
||||
for (j = k; j < 3; j++) a[i][j] -= f2 * a[k][j];
|
||||
c[i] -= f2 * c[k];
|
||||
}
|
||||
}
|
||||
for (i = 2; i >= 0; i--) { double s = c[i]; for (j = i+1; j < 3; j++) s -= a[i][j]*c[j]; c[i] = s / a[i][i]; }
|
||||
for (n = lo; n < hi; n++)
|
||||
{
|
||||
double t = 2.0 * M_PI * f * (double)n / fs;
|
||||
double model = c[0]*cos(t) + c[1]*sin(t) + c[2];
|
||||
double e = (double)y[2 * n] / 32768.0 - model;
|
||||
res += e * e;
|
||||
}
|
||||
sig = sqrt((c[0]*c[0] + c[1]*c[1]) / 2.0);
|
||||
res = sqrt(res / (double)cnt);
|
||||
if (res < 1e-300) res = 1e-300;
|
||||
return 20.0 * log10(sig / res);
|
||||
}
|
||||
|
||||
static long g_fail = 0;
|
||||
|
||||
static void run_case(double in_rate, double out_rate, double freq, double level_db)
|
||||
{
|
||||
double bw = out_rate / in_rate;
|
||||
double amp = pow(10.0, level_db / 20.0);
|
||||
size_t Nin = (size_t)(in_rate * 2.0);
|
||||
size_t Nout = (size_t)(out_rate * 2.2) + 64;
|
||||
int16_t *in_i16 = (int16_t*)malloc(sizeof(int16_t) * 2 * Nin);
|
||||
float *in_f = (float*)malloc(sizeof(float) * 2 * Nin);
|
||||
int16_t *out_fx = (int16_t*)malloc(sizeof(int16_t) * 2 * Nout);
|
||||
float *out_ff = (float*)malloc(sizeof(float) * 2 * Nout);
|
||||
int16_t *out_rf = (int16_t*)malloc(sizeof(int16_t) * 2 * Nout);
|
||||
void *re_i = nearest_resampler_int16_init();
|
||||
void *re_f = near_ref_init();
|
||||
struct resampler_data_int16 di;
|
||||
struct resampler_data_float df;
|
||||
size_t n, common;
|
||||
long maxd = 0, mism = 0, fdiff;
|
||||
double snr_fx, snr_rf;
|
||||
|
||||
for (n = 0; n < Nin; n++)
|
||||
{
|
||||
double s = amp * sin(2.0 * M_PI * freq * (double)n / in_rate);
|
||||
int16_t q = sat16(s * 32767.0);
|
||||
in_i16[2*n] = in_i16[2*n+1] = q;
|
||||
in_f[2*n] = in_f[2*n+1] = (float)q / 32768.0f;
|
||||
}
|
||||
|
||||
di.data_in = in_i16; di.data_out = out_fx; di.input_frames = Nin; di.ratio = bw;
|
||||
nearest_resampler_int16_process(re_i, &di);
|
||||
|
||||
df.data_in = in_f; df.data_out = out_ff; df.input_frames = Nin; df.ratio = bw;
|
||||
near_ref_process(re_f, &df);
|
||||
|
||||
for (n = 0; n < df.output_frames && n < Nout; n++)
|
||||
{
|
||||
out_rf[2*n] = sat16((double)out_ff[2*n] * 32768.0);
|
||||
out_rf[2*n+1] = sat16((double)out_ff[2*n+1] * 32768.0);
|
||||
}
|
||||
|
||||
common = (di.output_frames < df.output_frames) ? di.output_frames : df.output_frames;
|
||||
for (n = 0; n < common; n++)
|
||||
{
|
||||
long d = (long)out_fx[2*n] - (long)out_rf[2*n];
|
||||
if (d < 0) d = -d;
|
||||
if (d > maxd) maxd = d;
|
||||
if (d != 0) mism++;
|
||||
}
|
||||
|
||||
fdiff = (long)di.output_frames - (long)df.output_frames;
|
||||
if (fdiff < 0) fdiff = -fdiff;
|
||||
|
||||
snr_fx = tone_snr(out_fx, common, freq, out_rate);
|
||||
snr_rf = tone_snr(out_rf, common, freq, out_rate);
|
||||
|
||||
printf("%6.0f->%-6.0f %6.0fHz %4.0fdB | frames fx=%lu rf=%lu (d=%ld) | maxdiff=%ld LSB mism=%ld/%lu (%.4f%%) | SNR fx=%6.2f rf=%6.2f dB\n",
|
||||
in_rate, out_rate, freq, level_db,
|
||||
(unsigned long)di.output_frames, (unsigned long)df.output_frames, fdiff,
|
||||
maxd, mism, (unsigned long)common,
|
||||
common ? 100.0 * (double)mism / (double)common : 0.0,
|
||||
snr_fx, snr_rf);
|
||||
|
||||
/* Correctness guards:
|
||||
* - frame count must match the ideal to within one boundary frame;
|
||||
* - upsampling / unity must be bit-exact against the ideal (the Q32
|
||||
* accumulator reproduces the exact sample selection);
|
||||
* - downsampling near a rational ratio may resolve phase-boundary ties
|
||||
* at a different fraction than a double accumulator, so per-sample
|
||||
* diffs there are phase-neutral - we require only that quality (SNR)
|
||||
* matches the ideal endpoint. */
|
||||
if (fdiff > 1) g_fail++;
|
||||
if (bw >= 1.0 && mism != 0) g_fail++;
|
||||
if (snr_rf > 6.0 && snr_fx < snr_rf - 0.5) g_fail++;
|
||||
|
||||
nearest_resampler_int16_free(re_i);
|
||||
near_ref_free(re_f);
|
||||
free(in_i16); free(in_f); free(out_fx); free(out_ff); free(out_rf);
|
||||
}
|
||||
|
||||
/* Determinism: same input twice must yield byte-identical output. */
|
||||
static void run_determinism(double in_rate, double out_rate)
|
||||
{
|
||||
double bw = out_rate / in_rate;
|
||||
size_t Nin = (size_t)(in_rate);
|
||||
size_t Nout = (size_t)(out_rate * 1.2) + 64;
|
||||
int16_t *in = (int16_t*)malloc(sizeof(int16_t) * 2 * Nin);
|
||||
int16_t *o1 = (int16_t*)malloc(sizeof(int16_t) * 2 * Nout);
|
||||
int16_t *o2 = (int16_t*)malloc(sizeof(int16_t) * 2 * Nout);
|
||||
struct resampler_data_int16 d1, d2;
|
||||
void *r1, *r2;
|
||||
size_t n;
|
||||
int bad = 0;
|
||||
|
||||
for (n = 0; n < Nin; n++)
|
||||
{
|
||||
int16_t q = sat16(0.8 * sin(2.0 * M_PI * 997.0 * (double)n / in_rate) * 32767.0);
|
||||
in[2*n] = q; in[2*n+1] = (int16_t)-q;
|
||||
}
|
||||
r1 = nearest_resampler_int16_init();
|
||||
d1.data_in = in; d1.data_out = o1; d1.input_frames = Nin; d1.ratio = bw;
|
||||
nearest_resampler_int16_process(r1, &d1);
|
||||
r2 = nearest_resampler_int16_init();
|
||||
d2.data_in = in; d2.data_out = o2; d2.input_frames = Nin; d2.ratio = bw;
|
||||
nearest_resampler_int16_process(r2, &d2);
|
||||
|
||||
if (d1.output_frames != d2.output_frames) bad = 1;
|
||||
else if (memcmp(o1, o2, d1.output_frames * 2 * sizeof(int16_t))) bad = 1;
|
||||
printf("determinism %6.0f->%-6.0f : %s\n", in_rate, out_rate, bad ? "FAIL" : "identical");
|
||||
if (bad) g_fail++;
|
||||
|
||||
nearest_resampler_int16_free(r1);
|
||||
nearest_resampler_int16_free(r2);
|
||||
free(in); free(o1); free(o2);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
struct { double in, out; } rates[] = {
|
||||
{ 32040.0, 96000.0 }, { 44100.0, 96000.0 }, { 48000.0, 96000.0 },
|
||||
{ 32040.0, 48000.0 }, { 22050.0, 44100.0 }, { 48000.0, 32040.0 },
|
||||
{ 48000.0, 48000.0 }
|
||||
};
|
||||
size_t r;
|
||||
|
||||
printf("=== integer s16 nearest vs double-ideal nearest reference (int16 endpoint) ===\n\n");
|
||||
for (r = 0; r < sizeof(rates)/sizeof(rates[0]); r++)
|
||||
run_case(rates[r].in, rates[r].out, 1000.0, 0.0);
|
||||
printf("\n=== stress: high tone + low level ===\n\n");
|
||||
run_case(32040.0, 96000.0, 12000.0, 0.0);
|
||||
run_case(48000.0, 32040.0, 10000.0, 0.0);
|
||||
run_case(32040.0, 96000.0, 1000.0, -60.0);
|
||||
|
||||
printf("\n=== determinism (same input -> byte-identical output) ===\n\n");
|
||||
run_determinism(32040.0, 96000.0);
|
||||
run_determinism(48000.0, 32040.0);
|
||||
|
||||
printf("\n%s (%ld check(s) failed)\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS", g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
/* Bit-exactness / accuracy harness: diffs the integer s16 sinc resampler
|
||||
* against the faithful float reference across a rate x quality x signal matrix.
|
||||
* Build: cc -O2 -std=c89 -Wall test_sinc_int16.c sinc_resampler_int16.c \
|
||||
* sinc_ref_float.c -lm -o test_sinc_int16 */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <audio/sinc_resampler_int16.h>
|
||||
#include "sinc_ref_float.h"
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
static int16_t sat16(double v)
|
||||
{
|
||||
long r;
|
||||
if (v >= 0.0) r = (long)floor(v + 0.5);
|
||||
else r = (long)ceil(v - 0.5);
|
||||
if (r > 32767) return 32767;
|
||||
if (r < -32768) return -32768;
|
||||
return (int16_t)r;
|
||||
}
|
||||
|
||||
/* THD+N SNR (dB) of a resampled pure tone: fit-and-remove the fundamental. */
|
||||
static double tone_snr(const int16_t *y, size_t frames, double f, double fs)
|
||||
{
|
||||
double a[3][3], b[3], c[3];
|
||||
double sig, res = 0.0;
|
||||
size_t n, lo = 3000, hi = (frames > 3000) ? frames - 3000 : frames;
|
||||
size_t cnt = 0;
|
||||
int i, j, k;
|
||||
memset(a, 0, sizeof(a)); memset(b, 0, sizeof(b));
|
||||
for (n = lo; n < hi; n++)
|
||||
{
|
||||
double t = 2.0 * M_PI * f * (double)n / fs;
|
||||
double bb[3];
|
||||
double yy = (double)y[2 * n] / 32768.0;
|
||||
bb[0] = cos(t); bb[1] = sin(t); bb[2] = 1.0;
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
b[i] += bb[i] * yy;
|
||||
for (j = 0; j < 3; j++) a[i][j] += bb[i] * bb[j];
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
/* Solve 3x3 (Gaussian elimination). */
|
||||
for (i = 0; i < 3; i++) c[i] = b[i];
|
||||
for (k = 0; k < 3; k++)
|
||||
{
|
||||
int piv = k; double mx = fabs(a[k][k]);
|
||||
for (i = k + 1; i < 3; i++) if (fabs(a[i][k]) > mx) { mx = fabs(a[i][k]); piv = i; }
|
||||
if (piv != k) { for (j = 0; j < 3; j++) { double tmp=a[k][j]; a[k][j]=a[piv][j]; a[piv][j]=tmp; } { double tmp=c[k]; c[k]=c[piv]; c[piv]=tmp; } }
|
||||
for (i = k + 1; i < 3; i++)
|
||||
{
|
||||
double f2 = a[i][k] / a[k][k];
|
||||
for (j = k; j < 3; j++) a[i][j] -= f2 * a[k][j];
|
||||
c[i] -= f2 * c[k];
|
||||
}
|
||||
}
|
||||
for (i = 2; i >= 0; i--) { double s = c[i]; for (j = i+1; j < 3; j++) s -= a[i][j]*c[j]; c[i] = s / a[i][i]; }
|
||||
for (n = lo; n < hi; n++)
|
||||
{
|
||||
double t = 2.0 * M_PI * f * (double)n / fs;
|
||||
double model = c[0]*cos(t) + c[1]*sin(t) + c[2];
|
||||
double e = (double)y[2 * n] / 32768.0 - model;
|
||||
res += e * e;
|
||||
}
|
||||
sig = sqrt((c[0]*c[0] + c[1]*c[1]) / 2.0);
|
||||
res = sqrt(res / (double)cnt);
|
||||
if (res < 1e-300) res = 1e-300;
|
||||
return 20.0 * log10(sig / res);
|
||||
}
|
||||
|
||||
static void run_case(double in_rate, double out_rate, int quality,
|
||||
const char *qname, double freq, double level_db)
|
||||
{
|
||||
double bw = out_rate / in_rate;
|
||||
double amp = pow(10.0, level_db / 20.0);
|
||||
size_t Nin = (size_t)(in_rate * 2.0);
|
||||
size_t Nout = (size_t)(out_rate * 2.2) + 64;
|
||||
int16_t *in_i16 = (int16_t*)malloc(sizeof(int16_t) * 2 * Nin);
|
||||
float *in_f = (float*)malloc(sizeof(float) * 2 * Nin);
|
||||
int16_t *out_fx = (int16_t*)malloc(sizeof(int16_t) * 2 * Nout);
|
||||
float *out_ff = (float*)malloc(sizeof(float) * 2 * Nout);
|
||||
int16_t *out_rf = (int16_t*)malloc(sizeof(int16_t) * 2 * Nout);
|
||||
void *re_i = sinc_resampler_int16_init(bw, (enum sinc_int16_quality)quality);
|
||||
void *re_f = sinc_ref_float_init(bw, quality);
|
||||
struct resampler_data_int16 di;
|
||||
struct resampler_data_float df;
|
||||
size_t n;
|
||||
long maxd = 0, mism = 0;
|
||||
size_t common;
|
||||
double snr_fx, snr_rf;
|
||||
|
||||
for (n = 0; n < Nin; n++)
|
||||
{
|
||||
double s = amp * sin(2.0 * M_PI * freq * (double)n / in_rate);
|
||||
int16_t q = sat16(s * 32767.0);
|
||||
in_i16[2*n] = in_i16[2*n+1] = q;
|
||||
in_f[2*n] = in_f[2*n+1] = (float)q / 32768.0f;
|
||||
}
|
||||
|
||||
di.data_in = in_i16; di.data_out = out_fx; di.input_frames = Nin; di.ratio = bw;
|
||||
sinc_resampler_int16_process(re_i, &di);
|
||||
|
||||
df.data_in = in_f; df.data_out = out_ff; df.input_frames = Nin; df.ratio = bw;
|
||||
sinc_ref_float_process(re_f, &df);
|
||||
|
||||
for (n = 0; n < df.output_frames && n < Nout; n++)
|
||||
{
|
||||
out_rf[2*n] = sat16((double)out_ff[2*n] * 32768.0);
|
||||
out_rf[2*n+1] = sat16((double)out_ff[2*n+1] * 32768.0);
|
||||
}
|
||||
|
||||
common = (di.output_frames < df.output_frames) ? di.output_frames : df.output_frames;
|
||||
for (n = 0; n < common; n++)
|
||||
{
|
||||
long d = (long)out_fx[2*n] - (long)out_rf[2*n];
|
||||
if (d < 0) d = -d;
|
||||
if (d > maxd) maxd = d;
|
||||
if (d != 0) mism++;
|
||||
}
|
||||
|
||||
snr_fx = tone_snr(out_fx, common, freq, out_rate);
|
||||
snr_rf = tone_snr(out_rf, common, freq, out_rate);
|
||||
|
||||
printf("%-8s %6.0f->%-6.0f %5.0fHz %4.0fdB | frames fx=%lu rf=%lu | maxdiff=%ld LSB mism=%ld/%lu (%.3f%%) | SNR fx=%6.2f rf=%6.2f dB\n",
|
||||
qname, in_rate, out_rate, freq, level_db,
|
||||
(unsigned long)di.output_frames, (unsigned long)df.output_frames,
|
||||
maxd, mism, (unsigned long)common,
|
||||
common ? 100.0 * (double)mism / (double)common : 0.0,
|
||||
snr_fx, snr_rf);
|
||||
|
||||
sinc_resampler_int16_free(re_i);
|
||||
sinc_ref_float_free(re_f);
|
||||
free(in_i16); free(in_f); free(out_fx); free(out_ff); free(out_rf);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
struct { double in, out; } rates[] = {
|
||||
{ 32040.0, 96000.0 }, { 44100.0, 96000.0 }, { 48000.0, 96000.0 },
|
||||
{ 32040.0, 48000.0 }, { 22050.0, 44100.0 }, { 48000.0, 32040.0 }
|
||||
};
|
||||
struct { int q; const char *n; } quals[] = {
|
||||
{ 0, "LOWEST" }, { 1, "LOWER" }, { 2, "NORMAL" }, { 3, "HIGHER" }, { 4, "HIGHEST" }
|
||||
};
|
||||
size_t r, q;
|
||||
|
||||
printf("=== integer s16 sinc vs float sinc reference (int16 endpoint, both round-half-away) ===\n\n");
|
||||
for (q = 0; q < sizeof(quals)/sizeof(quals[0]); q++)
|
||||
{
|
||||
for (r = 0; r < sizeof(rates)/sizeof(rates[0]); r++)
|
||||
run_case(rates[r].in, rates[r].out, quals[q].q, quals[q].n, 1000.0, 0.0);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
printf("=== stress: near-Nyquist tone + low level (NORMAL / HIGHEST, 32040->96000) ===\n\n");
|
||||
run_case(32040.0, 96000.0, 2, "NORMAL", 12000.0, 0.0);
|
||||
run_case(32040.0, 96000.0, 4, "HIGHEST", 12000.0, 0.0);
|
||||
run_case(32040.0, 96000.0, 2, "NORMAL", 1000.0, -40.0);
|
||||
run_case(32040.0, 96000.0, 2, "NORMAL", 1000.0, -60.0);
|
||||
run_case(32040.0, 96000.0, 4, "HIGHEST", 1000.0, -60.0);
|
||||
return 0;
|
||||
}
|
||||
@@ -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
|
||||
@@ -1749,7 +1749,7 @@ struct string_list* cdrom_get_available_drives(void)
|
||||
if (*drive_model)
|
||||
strlcpy(drive_string, drive_model, sizeof(drive_string));
|
||||
else
|
||||
strlcpy(drive_string, "Unknown Drive", sizeof(drive_string));
|
||||
strlcpy_lit(drive_string, "Unknown Drive", sizeof(drive_string));
|
||||
|
||||
string_list_append(list, drive_string, attr);
|
||||
}
|
||||
@@ -1850,7 +1850,7 @@ struct string_list* cdrom_get_available_drives(void)
|
||||
if (*drive_model)
|
||||
strlcpy(drive_string, drive_model, sizeof(drive_string));
|
||||
else
|
||||
strlcpy(drive_string, "Unknown Drive", sizeof(drive_string));
|
||||
strlcpy_lit(drive_string, "Unknown Drive", sizeof(drive_string));
|
||||
|
||||
string_list_append(list, drive_string, attr);
|
||||
}
|
||||
@@ -1891,7 +1891,7 @@ struct string_list* cdrom_get_available_drives(void)
|
||||
if (*drive_model)
|
||||
strlcpy(drive_string, drive_model, sizeof(drive_string));
|
||||
else
|
||||
strlcpy(drive_string, "Unknown Drive", sizeof(drive_string));
|
||||
strlcpy_lit(drive_string, "Unknown Drive", sizeof(drive_string));
|
||||
|
||||
string_list_append(list, drive_string, attr);
|
||||
}
|
||||
@@ -2100,20 +2100,20 @@ size_t cdrom_device_fillpath(char *s, size_t len, char drive, unsigned char trac
|
||||
if (is_cue)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
size_t _len = strlcpy(s, "cdrom://", len);
|
||||
size_t _len = strlcpy_lit(s, "cdrom://", len);
|
||||
if (len > _len)
|
||||
s[_len++] = drive;
|
||||
_len += strlcpy(s + _len, ":/drive.cue", len - _len);
|
||||
_len += strlcpy_lit(s + _len, ":/drive.cue", len - _len);
|
||||
return _len;
|
||||
#else
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
size_t _len = strlcpy(s, "cdrom://drive", len);
|
||||
size_t _len = strlcpy_lit(s, "cdrom://drive", len);
|
||||
if (len > _len + 1)
|
||||
{
|
||||
s[_len++] = drive;
|
||||
s[_len] = '\0';
|
||||
}
|
||||
_len += strlcpy(s + _len, ".cue", len - _len);
|
||||
_len += strlcpy_lit(s + _len, ".cue", len - _len);
|
||||
return _len;
|
||||
#endif
|
||||
#endif
|
||||
@@ -2121,7 +2121,7 @@ size_t cdrom_device_fillpath(char *s, size_t len, char drive, unsigned char trac
|
||||
else
|
||||
{
|
||||
#ifdef _WIN32
|
||||
size_t _len = strlcpy(s, "cdrom://", len);
|
||||
size_t _len = strlcpy_lit(s, "cdrom://", len);
|
||||
if (len > _len + 1)
|
||||
{
|
||||
s[_len++] = drive;
|
||||
@@ -2131,7 +2131,7 @@ size_t cdrom_device_fillpath(char *s, size_t len, char drive, unsigned char trac
|
||||
return _len;
|
||||
#else
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
size_t _len = strlcpy(s, "cdrom://drive", len);
|
||||
size_t _len = strlcpy_lit(s, "cdrom://drive", len);
|
||||
if (len > _len)
|
||||
s[_len++] = drive;
|
||||
_len += snprintf(s + _len, len - _len, "-track%02d.bin", track);
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (compat_fnmatch.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <compat/fnmatch.h>
|
||||
|
||||
/* Implementation of fnmatch(3) so it can be
|
||||
* distributed to non *nix platforms.
|
||||
*
|
||||
* No flags are implemented ATM.
|
||||
* We don't use them. Add flags as needed. */
|
||||
|
||||
int rl_fnmatch(const char *pattern, const char *string, int flags)
|
||||
{
|
||||
int rv;
|
||||
const char *c = NULL;
|
||||
int charmatch = 0;
|
||||
|
||||
for (c = pattern; *c != '\0'; c++)
|
||||
{
|
||||
/* String ended before pattern */
|
||||
if ((*c != '*') && (*string == '\0'))
|
||||
return FNM_NOMATCH;
|
||||
|
||||
switch (*c)
|
||||
{
|
||||
/* Match any number of unknown chars */
|
||||
case '*':
|
||||
/* Find next node in the pattern
|
||||
* ignoring multiple asterixes
|
||||
*/
|
||||
do {
|
||||
c++;
|
||||
if (*c == '\0')
|
||||
return 0;
|
||||
} while (*c == '*');
|
||||
|
||||
/* Match the remaining pattern
|
||||
* ignoring more and more characters. */
|
||||
do {
|
||||
/* We reached the end of the string without a
|
||||
* match. There is a way to optimize this by
|
||||
* calculating the minimum chars needed to
|
||||
* match the remaining pattern but I don't
|
||||
* think it is worth the work ATM.
|
||||
*/
|
||||
if (*string == '\0')
|
||||
return FNM_NOMATCH;
|
||||
|
||||
rv = rl_fnmatch(c, string, flags);
|
||||
string++;
|
||||
} while (rv != 0);
|
||||
|
||||
return 0;
|
||||
/* Match char from list */
|
||||
case '[':
|
||||
charmatch = 0;
|
||||
for (c++; *c != ']'; c++)
|
||||
{
|
||||
/* Bad format */
|
||||
if (*c == '\0')
|
||||
return FNM_NOMATCH;
|
||||
|
||||
/* Match already found */
|
||||
if (charmatch)
|
||||
continue;
|
||||
|
||||
if (*c == *string)
|
||||
charmatch = 1;
|
||||
}
|
||||
|
||||
/* No match in list */
|
||||
if (!charmatch)
|
||||
return FNM_NOMATCH;
|
||||
|
||||
string++;
|
||||
break;
|
||||
/* Has any character */
|
||||
case '?':
|
||||
string++;
|
||||
break;
|
||||
/* Match following character verbatim */
|
||||
case '\\':
|
||||
c++;
|
||||
/* Dangling escape at end of pattern.
|
||||
* FIXME: Was c == '\0' (makes no sense).
|
||||
* Not sure if c == NULL or *c == '\0'
|
||||
* is intended. Assuming *c due to c++ right before. */
|
||||
if (*c == '\0')
|
||||
return FNM_NOMATCH;
|
||||
default:
|
||||
if (*c != *string)
|
||||
return FNM_NOMATCH;
|
||||
string++;
|
||||
}
|
||||
}
|
||||
|
||||
/* End of string and end of pattend */
|
||||
if (*string == '\0')
|
||||
return 0;
|
||||
return FNM_NOMATCH;
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (compat_getopt.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 <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <boolean.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
|
||||
#include <compat/getopt.h>
|
||||
#include <compat/strl.h>
|
||||
#include <compat/strcasestr.h>
|
||||
#include <compat/posix_string.h>
|
||||
|
||||
char *optarg;
|
||||
int optind, opterr, optopt;
|
||||
|
||||
static bool is_short_option(const char *str)
|
||||
{
|
||||
return str[0] == '-' && str[1] != '-';
|
||||
}
|
||||
|
||||
static bool is_long_option(const char *str)
|
||||
{
|
||||
return str[0] == '-' && str[1] == '-';
|
||||
}
|
||||
|
||||
static int find_short_index(char * const *argv)
|
||||
{
|
||||
int idx;
|
||||
for (idx = 0; argv[idx]; idx++)
|
||||
{
|
||||
if (is_short_option(argv[idx]))
|
||||
return idx;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int find_long_index(char * const *argv)
|
||||
{
|
||||
int idx;
|
||||
for (idx = 0; argv[idx]; idx++)
|
||||
{
|
||||
if (is_long_option(argv[idx]))
|
||||
return idx;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_short(const char *optstring, char * const *argv)
|
||||
{
|
||||
bool extra_opt, takes_arg, embedded_arg;
|
||||
const char *opt = NULL;
|
||||
char arg = argv[0][1];
|
||||
|
||||
if (arg == ':')
|
||||
return '?';
|
||||
|
||||
opt = strchr(optstring, arg);
|
||||
if (!opt)
|
||||
return '?';
|
||||
|
||||
extra_opt = argv[0][2];
|
||||
takes_arg = opt[1] == ':';
|
||||
|
||||
/* If we take an argument, and we see additional characters,
|
||||
* this is in fact the argument (i.e. -cfoo is same as -c foo). */
|
||||
embedded_arg = extra_opt && takes_arg;
|
||||
|
||||
if (takes_arg)
|
||||
{
|
||||
if (embedded_arg)
|
||||
{
|
||||
optarg = argv[0] + 2;
|
||||
optind++;
|
||||
}
|
||||
else
|
||||
{
|
||||
optarg = argv[1];
|
||||
optind += 2;
|
||||
}
|
||||
|
||||
return optarg ? opt[0] : '?';
|
||||
}
|
||||
|
||||
/* If we see additional characters,
|
||||
* and they don't take arguments, this
|
||||
* means we have multiple flags in one. */
|
||||
if (embedded_arg)
|
||||
memmove(&argv[0][1], &argv[0][2], strlen(&argv[0][2]) + 1);
|
||||
else
|
||||
optind++;
|
||||
|
||||
return opt[0];
|
||||
}
|
||||
|
||||
static int parse_long(const struct option *longopts, char * const *argv)
|
||||
{
|
||||
const char *arg = &argv[0][2];
|
||||
const char *eq = strchr(arg, '=');
|
||||
size_t len = eq ? (size_t)(eq - arg) : strlen(arg);
|
||||
|
||||
for (; longopts->name; longopts++)
|
||||
{
|
||||
const char *n = longopts->name;
|
||||
const char *a = arg;
|
||||
size_t rem = len;
|
||||
|
||||
while (rem && *n == *a)
|
||||
{
|
||||
n++;
|
||||
a++;
|
||||
rem--;
|
||||
}
|
||||
|
||||
if (rem || *n)
|
||||
continue;
|
||||
|
||||
if (longopts->has_arg)
|
||||
{
|
||||
if (eq)
|
||||
{
|
||||
optarg = (char *)(eq + 1);
|
||||
optind++;
|
||||
}
|
||||
else if (argv[1])
|
||||
{
|
||||
optarg = argv[1];
|
||||
optind += 2;
|
||||
}
|
||||
else
|
||||
return '?';
|
||||
}
|
||||
else
|
||||
optind++;
|
||||
|
||||
if (longopts->flag)
|
||||
{
|
||||
*longopts->flag = longopts->val;
|
||||
return 0;
|
||||
}
|
||||
return longopts->val;
|
||||
}
|
||||
|
||||
return '?';
|
||||
}
|
||||
|
||||
static void shuffle_block(char **begin, char **last, char **end)
|
||||
{
|
||||
ptrdiff_t len = last - begin;
|
||||
const char **tmp = (const char**)calloc(len, sizeof(const char*));
|
||||
|
||||
memcpy((void*)tmp, begin, len * sizeof(const char*));
|
||||
memmove(begin, last, (end - last) * sizeof(const char*));
|
||||
memcpy(end - len, tmp, len * sizeof(const char*));
|
||||
|
||||
free((void*)tmp);
|
||||
}
|
||||
|
||||
int getopt_long(int argc, char *argv[],
|
||||
const char *optstring, const struct option *longopts, int *longindex)
|
||||
{
|
||||
int short_index, long_index;
|
||||
|
||||
if (optind == 0)
|
||||
optind = 1;
|
||||
|
||||
if (argc < 2)
|
||||
return -1;
|
||||
|
||||
short_index = find_short_index(&argv[optind]);
|
||||
long_index = find_long_index(&argv[optind]);
|
||||
|
||||
/* We're done here. */
|
||||
if (short_index == -1 && long_index == -1)
|
||||
return -1;
|
||||
|
||||
/* Reorder argv so that non-options come last.
|
||||
* Non-POSIXy, but that's what getopt does by default. */
|
||||
if ((short_index > 0) && ((short_index < long_index) || (long_index == -1)))
|
||||
{
|
||||
shuffle_block(&argv[optind], &argv[optind + short_index], &argv[argc]);
|
||||
short_index = 0;
|
||||
}
|
||||
else if ((long_index > 0) && ((long_index < short_index)
|
||||
|| (short_index == -1)))
|
||||
{
|
||||
shuffle_block(&argv[optind], &argv[optind + long_index], &argv[argc]);
|
||||
long_index = 0;
|
||||
}
|
||||
|
||||
if (short_index == 0)
|
||||
return parse_short(optstring, &argv[optind]);
|
||||
if (long_index == 0)
|
||||
return parse_long(longopts, &argv[optind]);
|
||||
|
||||
return '?';
|
||||
}
|
||||
@@ -1,662 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2013, Kenneth MacKay
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
|
||||
*/
|
||||
|
||||
#include <compat/strl.h>
|
||||
#include <compat/ifaddrs.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netpacket/packet.h>
|
||||
#include <net/if_arp.h>
|
||||
#include <netinet/in.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
|
||||
typedef struct NetlinkList
|
||||
{
|
||||
struct NetlinkList *m_next;
|
||||
struct nlmsghdr *m_data;
|
||||
unsigned int m_size;
|
||||
} NetlinkList;
|
||||
|
||||
static int netlink_socket(void)
|
||||
{
|
||||
struct sockaddr_nl l_addr;
|
||||
int l_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
||||
|
||||
if (l_socket < 0)
|
||||
return -1;
|
||||
|
||||
memset(&l_addr, 0, sizeof(l_addr));
|
||||
l_addr.nl_family = AF_NETLINK;
|
||||
|
||||
if (bind(l_socket, (struct sockaddr *)&l_addr, sizeof(l_addr)) < 0)
|
||||
{
|
||||
close(l_socket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return l_socket;
|
||||
}
|
||||
|
||||
static int netlink_send(int p_socket, int p_request)
|
||||
{
|
||||
struct
|
||||
{
|
||||
struct nlmsghdr m_hdr;
|
||||
struct rtgenmsg m_msg;
|
||||
} l_data;
|
||||
struct sockaddr_nl l_addr;
|
||||
|
||||
memset(&l_data, 0, sizeof(l_data));
|
||||
|
||||
l_data.m_hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
|
||||
l_data.m_hdr.nlmsg_type = p_request;
|
||||
l_data.m_hdr.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
|
||||
l_data.m_hdr.nlmsg_pid = 0;
|
||||
l_data.m_hdr.nlmsg_seq = p_socket;
|
||||
l_data.m_msg.rtgen_family = AF_UNSPEC;
|
||||
|
||||
memset(&l_addr, 0, sizeof(l_addr));
|
||||
l_addr.nl_family = AF_NETLINK;
|
||||
return (sendto(p_socket, &l_data.m_hdr, l_data.m_hdr.nlmsg_len, 0, (struct sockaddr *)&l_addr, sizeof(l_addr)));
|
||||
}
|
||||
|
||||
static int netlink_recv(int p_socket, void *p_buffer, size_t p_len)
|
||||
{
|
||||
struct msghdr l_msg;
|
||||
struct sockaddr_nl l_addr;
|
||||
struct iovec l_iov = { p_buffer, p_len };
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int l_result;
|
||||
|
||||
l_msg.msg_name = (void *)&l_addr;
|
||||
l_msg.msg_namelen = sizeof(l_addr);
|
||||
l_msg.msg_iov = &l_iov;
|
||||
l_msg.msg_iovlen = 1;
|
||||
l_msg.msg_control = NULL;
|
||||
l_msg.msg_controllen = 0;
|
||||
l_msg.msg_flags = 0;
|
||||
|
||||
l_result = recvmsg(p_socket, &l_msg, 0);
|
||||
|
||||
if (l_result < 0)
|
||||
{
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (l_msg.msg_flags & MSG_TRUNC) /* buffer too small */
|
||||
return -1;
|
||||
return l_result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct nlmsghdr *ifaddrs_get_netlink_response(int p_socket,
|
||||
int *p_size, int *p_done)
|
||||
{
|
||||
size_t l_size = 4096;
|
||||
void *l_buffer = NULL;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int l_read;
|
||||
|
||||
free(l_buffer);
|
||||
l_buffer = malloc(l_size);
|
||||
if (!l_buffer)
|
||||
return NULL;
|
||||
|
||||
l_read = netlink_recv(p_socket, l_buffer, l_size);
|
||||
*p_size = l_read;
|
||||
|
||||
if (l_read == -2)
|
||||
{
|
||||
free(l_buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (l_read >= 0)
|
||||
{
|
||||
pid_t l_pid = getpid();
|
||||
struct nlmsghdr *l_hdr;
|
||||
|
||||
for (l_hdr = (struct nlmsghdr *)l_buffer;
|
||||
NLMSG_OK(l_hdr, (unsigned int)l_read);
|
||||
l_hdr = (struct nlmsghdr *)NLMSG_NEXT(l_hdr, l_read))
|
||||
{
|
||||
if ( (pid_t)l_hdr->nlmsg_pid != l_pid
|
||||
|| (int)l_hdr->nlmsg_seq != p_socket)
|
||||
continue;
|
||||
|
||||
if (l_hdr->nlmsg_type == NLMSG_DONE)
|
||||
{
|
||||
*p_done = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (l_hdr->nlmsg_type == NLMSG_ERROR)
|
||||
{
|
||||
free(l_buffer);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return l_buffer;
|
||||
}
|
||||
|
||||
l_size *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
static NetlinkList *ifaddr_new_list_item(struct nlmsghdr *p_data,
|
||||
unsigned int p_size)
|
||||
{
|
||||
NetlinkList *l_item = (NetlinkList*)malloc(sizeof(NetlinkList));
|
||||
if (!l_item)
|
||||
return NULL;
|
||||
|
||||
l_item->m_next = NULL;
|
||||
l_item->m_data = p_data;
|
||||
l_item->m_size = p_size;
|
||||
return l_item;
|
||||
}
|
||||
|
||||
static void ifaddr_free_result_list(NetlinkList *p_list)
|
||||
{
|
||||
NetlinkList *l_cur;
|
||||
|
||||
while (p_list)
|
||||
{
|
||||
l_cur = p_list;
|
||||
p_list = p_list->m_next;
|
||||
free(l_cur->m_data);
|
||||
free(l_cur);
|
||||
}
|
||||
}
|
||||
|
||||
static NetlinkList *ifaddrs_get_resultlist(int p_socket, int p_request)
|
||||
{
|
||||
int l_size;
|
||||
NetlinkList *l_list = NULL;
|
||||
NetlinkList *l_end = NULL;
|
||||
int l_done = 0;
|
||||
|
||||
if (netlink_send(p_socket, p_request) < 0)
|
||||
return NULL;
|
||||
|
||||
while (!l_done)
|
||||
{
|
||||
NetlinkList *l_item = NULL;
|
||||
struct nlmsghdr *l_hdr = ifaddrs_get_netlink_response(
|
||||
p_socket, &l_size, &l_done);
|
||||
if (!l_hdr)
|
||||
goto error;
|
||||
|
||||
l_item = ifaddr_new_list_item(l_hdr, l_size);
|
||||
if (!l_item)
|
||||
goto error;
|
||||
|
||||
if (!l_list)
|
||||
l_list = l_item;
|
||||
else
|
||||
l_end->m_next = l_item;
|
||||
l_end = l_item;
|
||||
}
|
||||
|
||||
return l_list;
|
||||
|
||||
error:
|
||||
ifaddr_free_result_list(l_list);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static size_t ifaddrs_max_size(size_t a, size_t b) { return (a > b ? a : b); }
|
||||
|
||||
static size_t ifaddrs_calc_addr_len(sa_family_t p_family, int p_dataSize)
|
||||
{
|
||||
switch(p_family)
|
||||
{
|
||||
case AF_INET:
|
||||
return sizeof(struct sockaddr_in);
|
||||
case AF_INET6:
|
||||
return sizeof(struct sockaddr_in6);
|
||||
case AF_PACKET:
|
||||
return ifaddrs_max_size(sizeof(struct sockaddr_ll), offsetof(struct sockaddr_ll, sll_addr) + p_dataSize);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ifaddrs_max_size(sizeof(struct sockaddr), offsetof(struct sockaddr, sa_data) + p_dataSize);
|
||||
}
|
||||
|
||||
static void ifaddrs_make_sock_addr(sa_family_t p_family,
|
||||
struct sockaddr *p_dest, void *p_data, size_t p_size)
|
||||
{
|
||||
switch(p_family)
|
||||
{
|
||||
case AF_INET:
|
||||
memcpy(&((struct sockaddr_in*)p_dest)->sin_addr, p_data, p_size);
|
||||
break;
|
||||
case AF_INET6:
|
||||
memcpy(&((struct sockaddr_in6*)p_dest)->sin6_addr, p_data, p_size);
|
||||
break;
|
||||
case AF_PACKET:
|
||||
memcpy(((struct sockaddr_ll*)p_dest)->sll_addr, p_data, p_size);
|
||||
((struct sockaddr_ll*)p_dest)->sll_halen = p_size;
|
||||
break;
|
||||
default:
|
||||
memcpy(p_dest->sa_data, p_data, p_size);
|
||||
break;
|
||||
}
|
||||
p_dest->sa_family = p_family;
|
||||
}
|
||||
|
||||
static void ifaddrs_add_to_end(struct ifaddrs **p_resultList,
|
||||
struct ifaddrs *p_entry)
|
||||
{
|
||||
if (!*p_resultList)
|
||||
*p_resultList = p_entry;
|
||||
else
|
||||
{
|
||||
struct ifaddrs *l_cur = *p_resultList;
|
||||
while (l_cur->ifa_next)
|
||||
l_cur = l_cur->ifa_next;
|
||||
l_cur->ifa_next = p_entry;
|
||||
}
|
||||
}
|
||||
|
||||
static int ifaddrs_interpret_link(struct nlmsghdr *p_hdr,
|
||||
struct ifaddrs **p_resultList)
|
||||
{
|
||||
char *l_index, *l_addr, *l_name, *l_data;
|
||||
struct ifaddrs *l_entry = NULL;
|
||||
struct rtattr *l_rta = NULL;
|
||||
struct ifinfomsg *l_info = (struct ifinfomsg *)NLMSG_DATA(p_hdr);
|
||||
size_t l_nameSize = 0;
|
||||
size_t l_addrSize = 0;
|
||||
size_t l_dataSize = 0;
|
||||
size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg));
|
||||
|
||||
for (l_rta = IFLA_RTA(l_info); RTA_OK(l_rta, l_rtaSize);
|
||||
l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
||||
{
|
||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
||||
switch(l_rta->rta_type)
|
||||
{
|
||||
case IFLA_ADDRESS:
|
||||
case IFLA_BROADCAST:
|
||||
l_addrSize += NLMSG_ALIGN(ifaddrs_calc_addr_len(AF_PACKET, l_rtaDataSize));
|
||||
break;
|
||||
case IFLA_IFNAME:
|
||||
l_nameSize += NLMSG_ALIGN(l_rtaSize + 1);
|
||||
break;
|
||||
case IFLA_STATS:
|
||||
l_dataSize += NLMSG_ALIGN(l_rtaSize);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
l_entry = (struct ifaddrs*)malloc(sizeof(struct ifaddrs) + sizeof(int) + l_nameSize + l_addrSize + l_dataSize);
|
||||
if (!l_entry)
|
||||
return -1;
|
||||
|
||||
memset(l_entry, 0, sizeof(struct ifaddrs));
|
||||
l_entry->ifa_name = "";
|
||||
|
||||
l_index = ((char *)l_entry) + sizeof(struct ifaddrs);
|
||||
l_name = l_index + sizeof(int);
|
||||
l_addr = l_name + l_nameSize;
|
||||
l_data = l_addr + l_addrSize;
|
||||
|
||||
/* save the interface index so we can look
|
||||
* it up when handling the addresses. */
|
||||
memcpy(l_index, &l_info->ifi_index, sizeof(int));
|
||||
|
||||
l_entry->ifa_flags = l_info->ifi_flags;
|
||||
|
||||
l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg));
|
||||
|
||||
for (l_rta = IFLA_RTA(l_info); RTA_OK(l_rta, l_rtaSize);
|
||||
l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
||||
{
|
||||
void *l_rtaData = RTA_DATA(l_rta);
|
||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
||||
|
||||
switch(l_rta->rta_type)
|
||||
{
|
||||
case IFLA_ADDRESS:
|
||||
case IFLA_BROADCAST:
|
||||
{
|
||||
size_t l_addr_len = ifaddrs_calc_addr_len(AF_PACKET, l_rtaDataSize);
|
||||
ifaddrs_make_sock_addr(AF_PACKET, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize);
|
||||
((struct sockaddr_ll *)l_addr)->sll_ifindex = l_info->ifi_index;
|
||||
((struct sockaddr_ll *)l_addr)->sll_hatype = l_info->ifi_type;
|
||||
if (l_rta->rta_type == IFLA_ADDRESS)
|
||||
l_entry->ifa_addr = (struct sockaddr *)l_addr;
|
||||
else
|
||||
l_entry->ifa_broadaddr = (struct sockaddr *)l_addr;
|
||||
l_addr += NLMSG_ALIGN(l_addr_len);
|
||||
}
|
||||
break;
|
||||
case IFLA_IFNAME:
|
||||
memcpy(l_name, l_rtaData, l_rtaDataSize);
|
||||
l_name[l_rtaDataSize] = '\0';
|
||||
l_entry->ifa_name = l_name;
|
||||
break;
|
||||
case IFLA_STATS:
|
||||
memcpy(l_data, l_rtaData, l_rtaDataSize);
|
||||
l_entry->ifa_data = l_data;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ifaddrs_add_to_end(p_resultList, l_entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ifaddrs *ifaddrs_find_interface(int p_index,
|
||||
struct ifaddrs **p_links, int p_numLinks)
|
||||
{
|
||||
int l_num = 0;
|
||||
struct ifaddrs *l_cur = *p_links;
|
||||
|
||||
while (l_cur && l_num < p_numLinks)
|
||||
{
|
||||
int l_index;
|
||||
char *l_indexPtr = ((char *)l_cur) + sizeof(struct ifaddrs);
|
||||
|
||||
memcpy(&l_index, l_indexPtr, sizeof(int));
|
||||
if (l_index == p_index)
|
||||
return l_cur;
|
||||
|
||||
l_cur = l_cur->ifa_next;
|
||||
++l_num;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int ifaddrs_interpret_addr(struct nlmsghdr *p_hdr,
|
||||
struct ifaddrs **p_resultList, int p_numLinks)
|
||||
{
|
||||
char *l_name, *l_addr;
|
||||
struct rtattr *l_rta;
|
||||
struct ifaddrs *l_entry;
|
||||
size_t l_rtaSize;
|
||||
size_t l_nameSize = 0;
|
||||
size_t l_addrSize = 0;
|
||||
int l_addedNetmask = 0;
|
||||
struct ifaddrmsg *l_info = (struct ifaddrmsg *)NLMSG_DATA(p_hdr);
|
||||
struct ifaddrs *l_interface = ifaddrs_find_interface(l_info->ifa_index, p_resultList, p_numLinks);
|
||||
|
||||
if (l_info->ifa_family == AF_PACKET)
|
||||
return 0;
|
||||
|
||||
l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg));
|
||||
|
||||
for (l_rta = IFA_RTA(l_info); RTA_OK(l_rta, l_rtaSize);
|
||||
l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
||||
{
|
||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
||||
|
||||
switch(l_rta->rta_type)
|
||||
{
|
||||
case IFA_ADDRESS:
|
||||
case IFA_LOCAL:
|
||||
if ((l_info->ifa_family == AF_INET || l_info->ifa_family == AF_INET6) && !l_addedNetmask)
|
||||
{
|
||||
/* make room for netmask */
|
||||
l_addrSize += NLMSG_ALIGN(ifaddrs_calc_addr_len(l_info->ifa_family, l_rtaDataSize));
|
||||
l_addedNetmask = 1;
|
||||
}
|
||||
case IFA_BROADCAST:
|
||||
l_addrSize += NLMSG_ALIGN(ifaddrs_calc_addr_len(l_info->ifa_family, l_rtaDataSize));
|
||||
break;
|
||||
case IFA_LABEL:
|
||||
l_nameSize += NLMSG_ALIGN(l_rtaSize + 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
l_entry = (struct ifaddrs*)malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize);
|
||||
if (!l_entry)
|
||||
return -1;
|
||||
|
||||
memset(l_entry, 0, sizeof(struct ifaddrs));
|
||||
l_entry->ifa_name = (l_interface ? l_interface->ifa_name : "");
|
||||
|
||||
l_name = ((char *)l_entry) + sizeof(struct ifaddrs);
|
||||
l_addr = l_name + l_nameSize;
|
||||
|
||||
l_entry->ifa_flags = l_info->ifa_flags;
|
||||
if (l_interface)
|
||||
l_entry->ifa_flags |= l_interface->ifa_flags;
|
||||
|
||||
l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg));
|
||||
for (l_rta = IFA_RTA(l_info); RTA_OK(l_rta, l_rtaSize);
|
||||
l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
||||
{
|
||||
void *l_rtaData = RTA_DATA(l_rta);
|
||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
||||
switch(l_rta->rta_type)
|
||||
{
|
||||
case IFA_ADDRESS:
|
||||
case IFA_BROADCAST:
|
||||
case IFA_LOCAL:
|
||||
{
|
||||
size_t l_addrLen = ifaddrs_calc_addr_len(l_info->ifa_family, l_rtaDataSize);
|
||||
ifaddrs_make_sock_addr(l_info->ifa_family, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize);
|
||||
if (l_info->ifa_family == AF_INET6)
|
||||
{
|
||||
if (IN6_IS_ADDR_LINKLOCAL((struct in6_addr *)l_rtaData)
|
||||
|| IN6_IS_ADDR_MC_LINKLOCAL((struct in6_addr *)l_rtaData))
|
||||
((struct sockaddr_in6 *)l_addr)->sin6_scope_id = l_info->ifa_index;
|
||||
}
|
||||
|
||||
if (l_rta->rta_type == IFA_ADDRESS)
|
||||
{
|
||||
/* Apparently in a point-to-point network IFA_ADDRESS
|
||||
* contains the dest address and IFA_LOCAL contains
|
||||
* the local address */
|
||||
if (l_entry->ifa_addr)
|
||||
l_entry->ifa_dstaddr = (struct sockaddr *)l_addr;
|
||||
else
|
||||
l_entry->ifa_addr = (struct sockaddr *)l_addr;
|
||||
}
|
||||
else if (l_rta->rta_type == IFA_LOCAL)
|
||||
{
|
||||
if (l_entry->ifa_addr)
|
||||
l_entry->ifa_dstaddr = l_entry->ifa_addr;
|
||||
l_entry->ifa_addr = (struct sockaddr *)l_addr;
|
||||
}
|
||||
else
|
||||
l_entry->ifa_broadaddr = (struct sockaddr *)l_addr;
|
||||
l_addr += NLMSG_ALIGN(l_addrLen);
|
||||
break;
|
||||
}
|
||||
case IFA_LABEL:
|
||||
strlcpy(l_name, l_rtaData, l_rtaDataSize + 1);
|
||||
l_entry->ifa_name = l_name;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( l_entry->ifa_addr
|
||||
&& ( l_entry->ifa_addr->sa_family == AF_INET
|
||||
|| l_entry->ifa_addr->sa_family == AF_INET6))
|
||||
{
|
||||
unsigned i;
|
||||
char l_mask[16];
|
||||
unsigned l_maxPrefix = (l_entry->ifa_addr->sa_family == AF_INET
|
||||
? 32 : 128);
|
||||
unsigned l_prefix = (l_info->ifa_prefixlen > l_maxPrefix
|
||||
? l_maxPrefix : l_info->ifa_prefixlen);
|
||||
|
||||
l_mask[0] = '\0';
|
||||
|
||||
for (i = 0; i < (l_prefix/8); ++i)
|
||||
l_mask[i] = 0xff;
|
||||
if (l_prefix % 8)
|
||||
l_mask[i] = 0xff << (8 - (l_prefix % 8));
|
||||
|
||||
ifaddrs_make_sock_addr(l_entry->ifa_addr->sa_family,
|
||||
(struct sockaddr *)l_addr, l_mask, l_maxPrefix / 8);
|
||||
l_entry->ifa_netmask = (struct sockaddr *)l_addr;
|
||||
}
|
||||
|
||||
ifaddrs_add_to_end(p_resultList, l_entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ifaddrs_interpret_links(int p_socket,
|
||||
NetlinkList *p_netlinkList, struct ifaddrs **p_resultList)
|
||||
{
|
||||
int l_numLinks = 0;
|
||||
pid_t l_pid = getpid();
|
||||
|
||||
for (; p_netlinkList; p_netlinkList = p_netlinkList->m_next)
|
||||
{
|
||||
struct nlmsghdr *l_hdr = NULL;
|
||||
unsigned int l_nlsize = p_netlinkList->m_size;
|
||||
|
||||
for (l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize);
|
||||
l_hdr = NLMSG_NEXT(l_hdr, l_nlsize))
|
||||
{
|
||||
if ( (pid_t)l_hdr->nlmsg_pid != l_pid
|
||||
|| (int)l_hdr->nlmsg_seq != p_socket)
|
||||
continue;
|
||||
|
||||
if (l_hdr->nlmsg_type == NLMSG_DONE)
|
||||
break;
|
||||
|
||||
if (l_hdr->nlmsg_type == RTM_NEWLINK)
|
||||
{
|
||||
if (ifaddrs_interpret_link(l_hdr, p_resultList) == -1)
|
||||
return -1;
|
||||
++l_numLinks;
|
||||
}
|
||||
}
|
||||
}
|
||||
return l_numLinks;
|
||||
}
|
||||
|
||||
static int ifaddrs_interpret_addrs(int p_socket,
|
||||
NetlinkList *p_netlinkList,
|
||||
struct ifaddrs **p_resultList, int p_numLinks)
|
||||
{
|
||||
pid_t l_pid = getpid();
|
||||
for (; p_netlinkList; p_netlinkList = p_netlinkList->m_next)
|
||||
{
|
||||
struct nlmsghdr *l_hdr = NULL;
|
||||
unsigned int l_nlsize = p_netlinkList->m_size;
|
||||
|
||||
for (l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize);
|
||||
l_hdr = NLMSG_NEXT(l_hdr, l_nlsize))
|
||||
{
|
||||
if ( (pid_t)l_hdr->nlmsg_pid != l_pid
|
||||
|| (int)l_hdr->nlmsg_seq != p_socket)
|
||||
continue;
|
||||
|
||||
if (l_hdr->nlmsg_type == NLMSG_DONE)
|
||||
break;
|
||||
|
||||
if (l_hdr->nlmsg_type == RTM_NEWADDR)
|
||||
{
|
||||
if (ifaddrs_interpret_addr(l_hdr, p_resultList, p_numLinks) == -1)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getifaddrs(struct ifaddrs **ifap)
|
||||
{
|
||||
int l_numLinks;
|
||||
NetlinkList *l_linkResults;
|
||||
NetlinkList *l_addrResults;
|
||||
int l_socket = 0;
|
||||
int l_result = 0;
|
||||
if (!ifap)
|
||||
return -1;
|
||||
|
||||
*ifap = NULL;
|
||||
l_socket = netlink_socket();
|
||||
|
||||
if (l_socket < 0)
|
||||
return -1;
|
||||
|
||||
l_linkResults = ifaddrs_get_resultlist(l_socket, RTM_GETLINK);
|
||||
if (!l_linkResults)
|
||||
{
|
||||
close(l_socket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
l_addrResults = ifaddrs_get_resultlist(l_socket, RTM_GETADDR);
|
||||
if (!l_addrResults)
|
||||
{
|
||||
close(l_socket);
|
||||
ifaddr_free_result_list(l_linkResults);
|
||||
return -1;
|
||||
}
|
||||
|
||||
l_numLinks = ifaddrs_interpret_links(l_socket, l_linkResults, ifap);
|
||||
|
||||
if ( l_numLinks == -1
|
||||
|| ifaddrs_interpret_addrs(l_socket, l_addrResults, ifap, l_numLinks) == -1)
|
||||
l_result = -1;
|
||||
|
||||
ifaddr_free_result_list(l_linkResults);
|
||||
ifaddr_free_result_list(l_addrResults);
|
||||
close(l_socket);
|
||||
return l_result;
|
||||
}
|
||||
|
||||
void freeifaddrs(struct ifaddrs *ifa)
|
||||
{
|
||||
struct ifaddrs *l_cur = NULL;
|
||||
|
||||
while (ifa)
|
||||
{
|
||||
l_cur = ifa;
|
||||
ifa = ifa->ifa_next;
|
||||
free(l_cur);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,9 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <compat/strl.h>
|
||||
|
||||
/* Implementation of strlcpy()/strlcat() based on OpenBSD. */
|
||||
@@ -28,6 +31,22 @@
|
||||
size_t strlcpy(char *s, const char *in, size_t len)
|
||||
{
|
||||
size_t src_len = strlen(in);
|
||||
#ifdef LIBRETRO_STRL_CHECK_OVERLAP
|
||||
/* Test builds: fail the way macOS's fortified libc does on an
|
||||
* overlapping copy (__chk_fail_overlap), so the Linux test suites
|
||||
* catch what only Apple's runtime would otherwise catch. */
|
||||
if (len && src_len)
|
||||
{
|
||||
const char *s_end = s + (src_len < len - 1 ? src_len : len - 1) + 1;
|
||||
const char *in_end = in + src_len + 1;
|
||||
if (!(s_end <= in || in_end <= s))
|
||||
{
|
||||
fprintf(stderr, "strlcpy: overlapping copy (dst %p, src %p, %u bytes)\n",
|
||||
(void*)s, (const void*)in, (unsigned)src_len);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (len)
|
||||
{
|
||||
size_t cpy_len = src_len < len - 1 ? src_len : len - 1;
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (compat_strl.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <compat/strl.h>
|
||||
|
||||
char *strldup(const char *s, size_t n)
|
||||
{
|
||||
char *dst = (char*)malloc(sizeof(char) * (n + 1));
|
||||
strlcpy(dst, s, n);
|
||||
return dst;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (compat_snprintf.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.
|
||||
*/
|
||||
|
||||
/* THIS FILE HAS NOT BEEN VALIDATED ON PLATFORMS BESIDES MSVC */
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#include <retro_common.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1800
|
||||
#define va_copy(dst, src) ((dst) = (src))
|
||||
#endif
|
||||
|
||||
int c89_vscprintf_retro__(const char *format, va_list pargs)
|
||||
{
|
||||
int _len;
|
||||
va_list argcopy;
|
||||
va_copy(argcopy, pargs);
|
||||
_len = vsnprintf(NULL, 0, format, argcopy);
|
||||
va_end(argcopy);
|
||||
return _len;
|
||||
}
|
||||
#endif
|
||||
@@ -1,10 +0,0 @@
|
||||
#ifndef __LIBRETRO_SDK_CRT_STRING_H_
|
||||
#define __LIBRETRO_SDK_CRT_STRING_H_
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
void *memcpy(void *dst, const void *src, size_t len);
|
||||
|
||||
void *memset(void *b, int c, size_t len);
|
||||
|
||||
#endif
|
||||
@@ -1,34 +0,0 @@
|
||||
#ifdef _MSC_VER
|
||||
#include <cruntime.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
void *memset(void *dst, int val, size_t count)
|
||||
{
|
||||
void *start = dst;
|
||||
|
||||
#if defined(_M_IA64) || defined (_M_AMD64) || defined(_M_ALPHA) || defined (_M_PPC)
|
||||
extern void RtlFillMemory(void *, size_t count, char);
|
||||
|
||||
RtlFillMemory(dst, count, (char)val);
|
||||
#else
|
||||
while (count--)
|
||||
{
|
||||
*(char*)dst = (char)val;
|
||||
dst = (char*)dst + 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
void *memcpy(void *dst, const void *src, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
((unsigned char *)dst)[i] = ((unsigned char *)src)[i];
|
||||
|
||||
return dst;
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (dylib.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 <string.h>
|
||||
#include <stdio.h>
|
||||
#include <dynamic/dylib.h>
|
||||
#include <encodings/utf.h>
|
||||
#include <string/stdstring.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <file/file_path.h>
|
||||
|
||||
#if defined(ORBIS)
|
||||
#include <orbis/libkernel.h>
|
||||
#endif
|
||||
|
||||
#ifdef NEED_DYNAMIC
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <compat/posix_string.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#if !defined(ORBIS)
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
static char last_dyn_err[512];
|
||||
|
||||
static void set_dl_err(void)
|
||||
{
|
||||
DWORD err = GetLastError();
|
||||
if (FormatMessage(
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS
|
||||
| FORMAT_MESSAGE_FROM_SYSTEM,
|
||||
NULL, err,
|
||||
MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT),
|
||||
last_dyn_err, sizeof(last_dyn_err) - 1,
|
||||
NULL) == 0)
|
||||
snprintf(last_dyn_err, sizeof(last_dyn_err) - 1,
|
||||
"unknown error %lu", err);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* dylib_load:
|
||||
* @path : Path to libretro core library.
|
||||
*
|
||||
* Platform independent dylib loading.
|
||||
*
|
||||
* @return Library handle on success, otherwise NULL.
|
||||
**/
|
||||
dylib_t dylib_load(const char *path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
#ifndef __WINRT__
|
||||
int prevmode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
|
||||
#endif
|
||||
#ifdef __WINRT__
|
||||
dylib_t lib;
|
||||
/* On UWP, you can only load DLLs inside your install directory, using a special function that takes a relative path */
|
||||
char relative_path_abbrev[PATH_MAX_LENGTH];
|
||||
char *relative_path = relative_path_abbrev;
|
||||
wchar_t *path_wide = NULL;
|
||||
|
||||
relative_path_abbrev[0] = '\0';
|
||||
|
||||
if (!path_is_absolute(path))
|
||||
RARCH_WARN("Relative path in dylib_load! This is likely an attempt to load a system library that will fail.\n");
|
||||
|
||||
fill_pathname_abbreviate_special(relative_path_abbrev, path, sizeof(relative_path_abbrev));
|
||||
|
||||
/* Path to dylib_load is not inside app install directory.
|
||||
* Loading will probably fail. */
|
||||
if (relative_path[0] != ':' || !PATH_CHAR_IS_SLASH(relative_path[1])) { }
|
||||
else
|
||||
relative_path += 2;
|
||||
|
||||
path_wide = utf8_to_utf16_string_alloc(relative_path);
|
||||
lib = LoadPackagedLibrary(path_wide, 0);
|
||||
free(path_wide);
|
||||
#elif defined(LEGACY_WIN32_RUNTIME)
|
||||
dylib_t lib = NULL;
|
||||
|
||||
if (win32_needs_local_encoding())
|
||||
lib = LoadLibraryA(path);
|
||||
else
|
||||
{
|
||||
wchar_t *path_wide = utf8_to_utf16_string_alloc(path);
|
||||
lib = LoadLibraryW(path_wide);
|
||||
free(path_wide);
|
||||
}
|
||||
#elif defined(LEGACY_WIN32)
|
||||
dylib_t lib = LoadLibrary(path);
|
||||
#else
|
||||
wchar_t *path_wide = utf8_to_utf16_string_alloc(path);
|
||||
dylib_t lib = LoadLibraryW(path_wide);
|
||||
free(path_wide);
|
||||
#endif
|
||||
|
||||
#ifndef __WINRT__
|
||||
SetErrorMode(prevmode);
|
||||
#endif
|
||||
|
||||
if (!lib)
|
||||
{
|
||||
set_dl_err();
|
||||
return NULL;
|
||||
}
|
||||
last_dyn_err[0] = 0;
|
||||
#elif defined(ORBIS)
|
||||
int res;
|
||||
dylib_t lib = (dylib_t)sceKernelLoadStartModule(path, 0, NULL, 0, NULL, &res);
|
||||
#elif defined(IOS) || defined(OSX)
|
||||
dylib_t lib;
|
||||
static const char fw_suffix[] = ".framework";
|
||||
if (string_ends_with(path, fw_suffix))
|
||||
{
|
||||
char fw_path[PATH_MAX_LENGTH];
|
||||
const char *fw_name = path_basename(path);
|
||||
size_t _len = strlcpy(fw_path, path, sizeof(fw_path));
|
||||
_len += strlcpy(fw_path + _len, "/", sizeof(fw_path) - _len);
|
||||
/* Assume every framework binary is named for the framework. Not always
|
||||
* a great assumption but correct enough for our uses. */
|
||||
strlcpy(fw_path + _len, fw_name, strlen(fw_name) - STRLEN_CONST(fw_suffix) + 1);
|
||||
lib = dlopen(fw_path, RTLD_LAZY | RTLD_LOCAL);
|
||||
}
|
||||
else
|
||||
lib = dlopen(path, RTLD_LAZY | RTLD_LOCAL);
|
||||
#else
|
||||
dylib_t lib = dlopen(path, RTLD_LAZY | RTLD_LOCAL);
|
||||
#endif
|
||||
return lib;
|
||||
}
|
||||
|
||||
char *dylib_error(void)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (last_dyn_err[0])
|
||||
return last_dyn_err;
|
||||
return NULL;
|
||||
#else
|
||||
return (char*)dlerror();
|
||||
#endif
|
||||
}
|
||||
|
||||
function_t dylib_proc(dylib_t lib, const char *proc)
|
||||
{
|
||||
function_t sym;
|
||||
|
||||
#ifdef _WIN32
|
||||
HMODULE mod = (HMODULE)lib;
|
||||
if (!mod)
|
||||
{
|
||||
#ifdef __WINRT__
|
||||
/* GetModuleHandle is not available on UWP */
|
||||
/* It's not possible to lookup symbols in current executable
|
||||
* on UWP. */
|
||||
DebugBreak();
|
||||
return NULL;
|
||||
#else
|
||||
mod = GetModuleHandle(NULL);
|
||||
#endif
|
||||
}
|
||||
if (!(sym = (function_t)GetProcAddress(mod, proc)))
|
||||
{
|
||||
set_dl_err();
|
||||
return NULL;
|
||||
}
|
||||
last_dyn_err[0] = 0;
|
||||
#elif defined(ORBIS)
|
||||
void *ptr_sym = NULL;
|
||||
sym = NULL;
|
||||
|
||||
if (lib)
|
||||
{
|
||||
sceKernelDlsym((SceKernelModule)lib, proc, &ptr_sym);
|
||||
memcpy(&sym, &ptr_sym, sizeof(void*));
|
||||
}
|
||||
#else
|
||||
void *ptr_sym = NULL;
|
||||
|
||||
if (lib)
|
||||
ptr_sym = dlsym(lib, proc);
|
||||
else
|
||||
{
|
||||
void *handle = dlopen(NULL, RTLD_LAZY);
|
||||
if (handle)
|
||||
{
|
||||
ptr_sym = dlsym(handle, proc);
|
||||
dlclose(handle);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dirty hack to workaround the non-legality of
|
||||
* (void*) -> fn-pointer casts. */
|
||||
memcpy(&sym, &ptr_sym, sizeof(void*));
|
||||
#endif
|
||||
|
||||
return sym;
|
||||
}
|
||||
|
||||
/**
|
||||
* dylib_close:
|
||||
* @lib : Library handle.
|
||||
*
|
||||
* Frees library handle.
|
||||
**/
|
||||
void dylib_close(dylib_t lib)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!FreeLibrary((HMODULE)lib))
|
||||
set_dl_err();
|
||||
last_dyn_err[0] = 0;
|
||||
#elif defined(ORBIS)
|
||||
int res;
|
||||
sceKernelStopUnloadModule((SceKernelModule)lib, 0, NULL, 0, NULL, &res);
|
||||
#else
|
||||
#ifndef NO_DLCLOSE
|
||||
dlclose(lib);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,171 +0,0 @@
|
||||
/*
|
||||
https://github.com/superwills/NibbleAndAHalf
|
||||
base64.h -- Fast base64 encoding and decoding.
|
||||
version 1.0.0, April 17, 2013 143a
|
||||
Copyright (C) 2013 William Sherif
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
William Sherif
|
||||
[email protected]
|
||||
YWxsIHlvdXIgYmFzZSBhcmUgYmVsb25nIHRvIHVz
|
||||
|
||||
|
||||
Modified for RetroArch formatting, logging, and header files.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <encodings/base64.h>
|
||||
|
||||
static const char* b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
/* maps A=>0,B=>1.. */
|
||||
static const unsigned char unb64[]={
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 62, 0, 0, 0, 63, 52, 53,
|
||||
54, 55, 56, 57, 58, 59, 60, 61, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 1, 2, 3, 4,
|
||||
5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
|
||||
25, 0, 0, 0, 0, 0, 0, 26, 27, 28,
|
||||
29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
|
||||
39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
|
||||
49, 50, 51, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
}; /* This array has 256 elements */
|
||||
|
||||
/*
|
||||
Converts binary data of length=len to base64 characters.
|
||||
Length of the resultant string is stored in flen
|
||||
(you must pass pointer flen).
|
||||
*/
|
||||
char* base64(const void* binaryData, int len, int *flen)
|
||||
{
|
||||
char* res;
|
||||
int byteNo; /* I need this after the loop */
|
||||
const unsigned char* bin = (const unsigned char*) binaryData;
|
||||
int rc = 0; /* result counter */
|
||||
int modulusLen = len % 3 ;
|
||||
/* 2 gives 1 and 1 gives 2, but 0 gives 0. */
|
||||
int pad = ((modulusLen&1)<<1) + ((modulusLen&2)>>1);
|
||||
|
||||
*flen = 4*(len + pad)/3;
|
||||
if (!(res = (char*) malloc(*flen + 1))) /* and one for the NULL */
|
||||
return 0;
|
||||
|
||||
for (byteNo=0; byteNo <= len-3; byteNo+=3)
|
||||
{
|
||||
unsigned char BYTE0 = bin[byteNo];
|
||||
unsigned char BYTE1 = bin[byteNo+1];
|
||||
unsigned char BYTE2 = bin[byteNo+2];
|
||||
|
||||
res[rc++] = b64[BYTE0 >> 2];
|
||||
res[rc++] = b64[((0x3&BYTE0)<<4) + (BYTE1 >> 4)];
|
||||
res[rc++] = b64[((0x0f&BYTE1)<<2) + (BYTE2>>6)];
|
||||
res[rc++] = b64[0x3f&BYTE2];
|
||||
}
|
||||
|
||||
if (pad==2)
|
||||
{
|
||||
res[rc++] = b64[bin[byteNo] >> 2];
|
||||
res[rc++] = b64[(0x3&bin[byteNo])<<4];
|
||||
res[rc++] = '=';
|
||||
res[rc++] = '=';
|
||||
}
|
||||
else if (pad==1)
|
||||
{
|
||||
res[rc++] = b64[bin[byteNo] >> 2];
|
||||
res[rc++] = b64[((0x3&bin[byteNo])<<4) + (bin[byteNo+1] >> 4)];
|
||||
res[rc++] = b64[(0x0f&bin[byteNo+1])<<2];
|
||||
res[rc++] = '=';
|
||||
}
|
||||
|
||||
res[rc]=0; /* NULL TERMINATOR! ;) */
|
||||
return res;
|
||||
}
|
||||
|
||||
unsigned char* unbase64(const char* ascii, int len, int *flen)
|
||||
{
|
||||
int charNo;
|
||||
unsigned char *bin;
|
||||
const unsigned char *safeAsciiPtr = (const unsigned char*) ascii;
|
||||
int cb = 0;
|
||||
int pad = 0;
|
||||
|
||||
/* Valid base64 has length that is a non-zero multiple of 4.
|
||||
* Shorter or misaligned inputs are malformed and have historically
|
||||
* caused 1-byte heap overflows when pad > 0 (e.g. "AB="). */
|
||||
if (len < 4 || (len & 3) != 0)
|
||||
{
|
||||
*flen = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (safeAsciiPtr[len-1]=='=')
|
||||
++pad;
|
||||
if (safeAsciiPtr[len-2]=='=')
|
||||
++pad;
|
||||
|
||||
*flen = 3*len/4 - pad;
|
||||
if (!(bin = (unsigned char*)malloc(*flen)))
|
||||
return 0;
|
||||
|
||||
for (charNo=0; charNo <= len-4-pad; charNo+=4)
|
||||
{
|
||||
int A = unb64[safeAsciiPtr[charNo]];
|
||||
int B = unb64[safeAsciiPtr[charNo+1]];
|
||||
int C = unb64[safeAsciiPtr[charNo+2]];
|
||||
int D = unb64[safeAsciiPtr[charNo+3]];
|
||||
|
||||
bin[cb++] = (A<<2) | (B>>4);
|
||||
bin[cb++] = (B<<4) | (C>>2);
|
||||
bin[cb++] = (C<<6) | (D);
|
||||
}
|
||||
|
||||
if (pad==1)
|
||||
{
|
||||
int A = unb64[safeAsciiPtr[charNo]];
|
||||
int B = unb64[safeAsciiPtr[charNo+1]];
|
||||
int C = unb64[safeAsciiPtr[charNo+2]];
|
||||
|
||||
bin[cb++] = (A<<2) | (B>>4);
|
||||
bin[cb++] = (B<<4) | (C>>2);
|
||||
}
|
||||
else if (pad==2)
|
||||
{
|
||||
int A = unb64[safeAsciiPtr[charNo]];
|
||||
int B = unb64[safeAsciiPtr[charNo+1]];
|
||||
|
||||
bin[cb++] = (A<<2) | (B>>4);
|
||||
}
|
||||
|
||||
return bin;
|
||||
}
|
||||
|
||||
@@ -399,7 +399,7 @@ int rhuff_read_tree_packed(rhuff_dec_t *d, rhuff_bits_t *b)
|
||||
* 8 KiB task-thread stack. */
|
||||
struct rhuff_small_scr
|
||||
{
|
||||
rhuff_dec_t small;
|
||||
rhuff_dec_t lens;
|
||||
uint16_t small_lookup[1 << RHUFF_SMALL_BITS];
|
||||
} *scr;
|
||||
uint32_t start;
|
||||
@@ -416,7 +416,7 @@ int rhuff_read_tree_packed(rhuff_dec_t *d, rhuff_bits_t *b)
|
||||
if (!(scr = (struct rhuff_small_scr*)malloc(sizeof(*scr))))
|
||||
return RHUFF_ERROR_MEM;
|
||||
|
||||
if ((err = rhuff_dec_init(&scr->small, RHUFF_SMALL_CODES,
|
||||
if ((err = rhuff_dec_init(&scr->lens, RHUFF_SMALL_CODES,
|
||||
RHUFF_SMALL_BITS, scr->small_lookup,
|
||||
RHUFF_LOOKUP_ENTRIES(RHUFF_SMALL_BITS)))
|
||||
!= RHUFF_OK)
|
||||
@@ -424,7 +424,7 @@ int rhuff_read_tree_packed(rhuff_dec_t *d, rhuff_bits_t *b)
|
||||
|
||||
/* The first length stands alone; the value after it says which index
|
||||
* the rest resume at, so leading unused symbols cost nothing. */
|
||||
scr->small.lengths[0] = (uint8_t)rhuff_bits_read(b, RHUFF_SMALL_WIDTH);
|
||||
scr->lens.lengths[0] = (uint8_t)rhuff_bits_read(b, RHUFF_SMALL_WIDTH);
|
||||
start = rhuff_bits_read(b, RHUFF_SMALL_WIDTH) + 1;
|
||||
|
||||
for (index = start; index < RHUFF_SMALL_CODES; index++)
|
||||
@@ -434,13 +434,13 @@ int rhuff_read_tree_packed(rhuff_dec_t *d, rhuff_bits_t *b)
|
||||
if (value == RHUFF_SMALL_END)
|
||||
break;
|
||||
|
||||
scr->small.lengths[index] = (uint8_t)value;
|
||||
scr->lens.lengths[index] = (uint8_t)value;
|
||||
}
|
||||
|
||||
if (rhuff_bits_overflow(b))
|
||||
{ free(scr); return RHUFF_ERROR_DATA; }
|
||||
|
||||
if ((err = rhuff_dec_build(&scr->small)) != RHUFF_OK)
|
||||
if ((err = rhuff_dec_build(&scr->lens)) != RHUFF_OK)
|
||||
{ free(scr); return err; }
|
||||
|
||||
/* Width of the extended run count, scaled to the alphabet being
|
||||
@@ -464,7 +464,7 @@ int rhuff_read_tree_packed(rhuff_dec_t *d, rhuff_bits_t *b)
|
||||
|
||||
while (curcode < d->num_codes)
|
||||
{
|
||||
uint32_t value = rhuff_dec_decode_one(&scr->small, b);
|
||||
uint32_t value = rhuff_dec_decode_one(&scr->lens, b);
|
||||
|
||||
if (rhuff_bits_overflow(b))
|
||||
{ free(scr); return RHUFF_ERROR_DATA; }
|
||||
|
||||
@@ -1,470 +0,0 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (encoding_rlz4.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 <string.h>
|
||||
|
||||
#include <encodings/rlz4.h>
|
||||
|
||||
/* Read a saturating length extension: the nibble said 15, so bytes
|
||||
* follow, each adding its value, ending at the first byte below 255.
|
||||
* Returns 0 on truncation. */
|
||||
static int rlz4_extend(const uint8_t *src, size_t limit, size_t *pos,
|
||||
size_t *len)
|
||||
{
|
||||
uint8_t b;
|
||||
do
|
||||
{
|
||||
if (*pos >= limit)
|
||||
return 0;
|
||||
b = src[(*pos)++];
|
||||
*len += b;
|
||||
} while (b == 255);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Blind match copy for short-to-medium lengths, all offsets, one
|
||||
* branch: the reference decoder's increment tables re-seat the source
|
||||
* pointer so that after the first eight bytes it trails the write
|
||||
* cursor by at least eight regardless of offset, and everything after
|
||||
* is plain 8-byte strides. Writes overshoot to the next multiple of
|
||||
* eight; the caller guarantees the room. */
|
||||
static void rlz4_copy_match_blind(uint8_t *to, const uint8_t *from,
|
||||
size_t match_len, size_t offset)
|
||||
{
|
||||
/* offset 1 would overlap the two-byte base copy (the reference
|
||||
* asserts it away and pre-filters upstream); one splat is both
|
||||
* legal and the fastest thing for it. */
|
||||
if (offset == 1)
|
||||
{
|
||||
memset(to, from[0], match_len);
|
||||
return;
|
||||
}
|
||||
|
||||
static const unsigned inc32[8] = { 0, 1, 2, 1, 0, 4, 4, 4 };
|
||||
static const int dec64[8] = { 0, 0, 0, -1, -4, 1, 2, 3 };
|
||||
uint8_t *end = to + match_len;
|
||||
if (offset < 8)
|
||||
{
|
||||
memcpy(to, from, 2);
|
||||
memcpy(to + 2, from + 2, 2);
|
||||
from += inc32[offset];
|
||||
memcpy(to + 4, from, 4);
|
||||
from -= dec64[offset];
|
||||
}
|
||||
else
|
||||
memcpy(to, from, 8), from += 8;
|
||||
to += 8;
|
||||
while (to < end)
|
||||
{
|
||||
memcpy(to, from, 8);
|
||||
to += 8; from += 8;
|
||||
}
|
||||
}
|
||||
|
||||
/* Exact match copy with no blind writes: used wherever margins are
|
||||
* not statically guaranteed. match_len must already be clamped to
|
||||
* the output capacity. */
|
||||
static void rlz4_copy_match_exact(uint8_t *dst, size_t dst_len,
|
||||
size_t op, size_t offset, size_t match_len)
|
||||
{
|
||||
uint8_t *to = dst + op;
|
||||
if (offset >= 32 && dst_len - op >= match_len + 31)
|
||||
{
|
||||
const uint8_t *from = dst + op - offset;
|
||||
uint8_t *end = to + match_len;
|
||||
do
|
||||
{
|
||||
memcpy(to, from, 16);
|
||||
memcpy(to + 16, from + 16, 16);
|
||||
to += 32; from += 32;
|
||||
} while (to < end);
|
||||
}
|
||||
else if (offset >= 16 && dst_len - op >= match_len + 15)
|
||||
{
|
||||
const uint8_t *from = dst + op - offset;
|
||||
uint8_t *end = to + match_len;
|
||||
do
|
||||
{
|
||||
memcpy(to, from, 16);
|
||||
to += 16; from += 16;
|
||||
} while (to < end);
|
||||
}
|
||||
else if (offset >= 8 && dst_len - op >= match_len + 15)
|
||||
{
|
||||
const uint8_t *from = dst + op - offset;
|
||||
uint8_t *end = to + match_len;
|
||||
do
|
||||
{
|
||||
memcpy(to, from, 8);
|
||||
to += 8; from += 8;
|
||||
} while (to < end);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t have = offset < match_len ? offset : match_len;
|
||||
memcpy(to, dst + op - offset, have);
|
||||
if (offset >= match_len)
|
||||
{
|
||||
if (match_len > have)
|
||||
memcpy(to + have, dst + op - offset + have, match_len - have);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (have * 2 <= match_len)
|
||||
{
|
||||
memcpy(to + have, to, have);
|
||||
have *= 2;
|
||||
}
|
||||
if (have < match_len)
|
||||
memcpy(to + have, to, match_len - have);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int64_t rlz4_decode(uint8_t *dst, size_t dst_len,
|
||||
const uint8_t *src, size_t src_len)
|
||||
{
|
||||
size_t ip = 0; /* input position */
|
||||
size_t op = 0; /* output position */
|
||||
|
||||
if (src_len == 0)
|
||||
return 0;
|
||||
if (!src || (!dst && dst_len))
|
||||
return RLZ4_ERROR_PARAM;
|
||||
|
||||
/* Fast loop: entered per-iteration only with at least 32 bytes of
|
||||
* input and output margin, which makes the token read, a blind
|
||||
* 16-byte short-literal copy, and the offset read unconditionally
|
||||
* safe (reads and scratch writes land inside the caller's buffers;
|
||||
* only the returned length is contractual). Long literals take the
|
||||
* exact path inline - with the reference's stop rules - and the
|
||||
* match section guards every step individually, so a sequence never
|
||||
* straddles loop boundaries. The moment either margin is gone the
|
||||
* careful tail loop below owns the block, including every
|
||||
* truncation rule. */
|
||||
if (src_len >= 64 && dst_len >= 64)
|
||||
{
|
||||
/* The reference's fast-loop safe distance: with 64 bytes of
|
||||
* margin, a 16-byte literal stripe, the offset read, and a
|
||||
* short match's blind copy (at most 24 written) are all
|
||||
* unconditionally in bounds even after the stripe advances the
|
||||
* cursors. */
|
||||
const size_t in_fast = src_len - 64;
|
||||
const size_t out_fast = dst_len - 64;
|
||||
|
||||
while (ip < in_fast && op < out_fast)
|
||||
{
|
||||
size_t lit_len, match_len, offset;
|
||||
uint8_t token = src[ip++];
|
||||
|
||||
/* -- literals -- */
|
||||
lit_len = token >> 4;
|
||||
if (lit_len < 15)
|
||||
{
|
||||
memcpy(dst + op, src + ip, 16); /* blind stripe, <= 14 used */
|
||||
op += lit_len;
|
||||
ip += lit_len;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Long literals: exact copy with the reference's stop
|
||||
* rules, then this sequence's match runs on the exact
|
||||
* path - the stripe guarantees are gone. */
|
||||
if (!rlz4_extend(src, src_len - 15, &ip, &lit_len))
|
||||
return RLZ4_ERROR_TRUNCATED;
|
||||
{
|
||||
size_t avail = src_len - ip;
|
||||
size_t emit = lit_len;
|
||||
int in_short = 0;
|
||||
if (emit > avail)
|
||||
{
|
||||
emit = avail;
|
||||
in_short = 1;
|
||||
}
|
||||
if (emit > dst_len - op)
|
||||
{
|
||||
memcpy(dst + op, src + ip, dst_len - op);
|
||||
return (int64_t)dst_len;
|
||||
}
|
||||
memcpy(dst + op, src + ip, emit);
|
||||
op += emit;
|
||||
ip += emit;
|
||||
if (in_short)
|
||||
return (int64_t)op;
|
||||
}
|
||||
if (src_len - ip <= 2)
|
||||
return (int64_t)op;
|
||||
|
||||
{
|
||||
uint16_t off16;
|
||||
memcpy(&off16, src + ip, 2);
|
||||
#ifdef MSB_FIRST
|
||||
offset = (size_t)((off16 >> 8) | (off16 << 8)) & 0xffff;
|
||||
#else
|
||||
offset = off16;
|
||||
#endif
|
||||
}
|
||||
ip += 2;
|
||||
if (offset - 1 >= op) /* 0 wraps; one compare */
|
||||
return RLZ4_ERROR_OFFSET;
|
||||
|
||||
match_len = token & 15;
|
||||
if (match_len == 15 &&
|
||||
!rlz4_extend(src, src_len >= 6 ? src_len - 6 : 0,
|
||||
&ip, &match_len))
|
||||
return RLZ4_ERROR_TRUNCATED;
|
||||
match_len += 4;
|
||||
if (match_len > dst_len - op)
|
||||
match_len = dst_len - op;
|
||||
rlz4_copy_match_exact(dst, dst_len, op, offset, match_len);
|
||||
op += match_len;
|
||||
if (op == dst_len)
|
||||
return (int64_t)op;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* -- offset (margins hold: at most 16 + 2 consumed) -- */
|
||||
{
|
||||
uint16_t off16;
|
||||
memcpy(&off16, src + ip, 2);
|
||||
#ifdef MSB_FIRST
|
||||
offset = (size_t)((off16 >> 8) | (off16 << 8)) & 0xffff;
|
||||
#else
|
||||
offset = off16;
|
||||
#endif
|
||||
}
|
||||
ip += 2;
|
||||
if (offset - 1 >= op) /* catches 0 via wrap and > op in one compare */
|
||||
return RLZ4_ERROR_OFFSET;
|
||||
|
||||
/* -- match -- */
|
||||
match_len = token & 15;
|
||||
if (match_len != 15)
|
||||
{
|
||||
match_len += 4; /* 4..18 */
|
||||
if (offset >= 8)
|
||||
{
|
||||
/* Reference short-match exit: flat 18, no length
|
||||
* branch. */
|
||||
uint8_t *to = dst + op;
|
||||
const uint8_t *from = dst + op - offset;
|
||||
memcpy(to, from, 8);
|
||||
memcpy(to + 8, from + 8, 8);
|
||||
memcpy(to + 16, from + 16, 2);
|
||||
}
|
||||
else
|
||||
rlz4_copy_match_blind(dst + op, dst + op - offset,
|
||||
match_len, offset);
|
||||
op += match_len;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!rlz4_extend(src, src_len >= 6 ? src_len - 6 : 0, &ip, &match_len))
|
||||
return RLZ4_ERROR_TRUNCATED;
|
||||
match_len += 4;
|
||||
|
||||
if (offset >= 32 && match_len + 31 <= dst_len - op)
|
||||
{
|
||||
/* Single-compare fast path for the long matches that
|
||||
* carry most of the bytes on match-dense data. */
|
||||
uint8_t *to = dst + op;
|
||||
const uint8_t *from = dst + op - offset;
|
||||
uint8_t *end = to + match_len;
|
||||
do
|
||||
{
|
||||
memcpy(to, from, 16);
|
||||
memcpy(to + 16, from + 16, 16);
|
||||
to += 32; from += 32;
|
||||
} while (to < end);
|
||||
op += match_len;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (match_len > dst_len - op)
|
||||
match_len = dst_len - op;
|
||||
rlz4_copy_match_exact(dst, dst_len, op, offset, match_len);
|
||||
op += match_len;
|
||||
if (op == dst_len)
|
||||
return (int64_t)op;
|
||||
}
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
size_t lit_len, match_len, offset;
|
||||
uint8_t token;
|
||||
|
||||
/* Input ending at a token boundary is the end of the block. */
|
||||
if (ip >= src_len)
|
||||
return (int64_t)op;
|
||||
token = src[ip++];
|
||||
|
||||
/* -- literals -- */
|
||||
lit_len = token >> 4;
|
||||
/* >= 15 declared literals imply >= 15 payload bytes after the
|
||||
* extension: its bytes may not reach the last 15 of input -
|
||||
* the reference's read_variable_length() margin, truncated
|
||||
* inputs included. */
|
||||
if (lit_len == 15 &&
|
||||
!rlz4_extend(src, src_len >= 15 ? src_len - 15 : 0, &ip, &lit_len))
|
||||
return RLZ4_ERROR_TRUNCATED;
|
||||
|
||||
if (lit_len && lit_len <= 16 &&
|
||||
lit_len <= src_len - ip && lit_len <= dst_len - op)
|
||||
{
|
||||
/* Hot tier: short literal runs between matches dominate
|
||||
* match-dense data, where copy-routine setup costs more than
|
||||
* the bytes. */
|
||||
const uint8_t *from = src + ip;
|
||||
uint8_t *to = dst + op;
|
||||
size_t n = lit_len;
|
||||
while (n--)
|
||||
*to++ = *from++;
|
||||
op += lit_len;
|
||||
ip += lit_len;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Emit as many of the declared literals as input and output
|
||||
* allow. Running out of OUTPUT is the partial-decode
|
||||
* contract; running out of INPUT mid-literals is how the
|
||||
* reference decoder treats a stream that simply stops - the
|
||||
* bytes so far are the answer. A truncated STRUCTURE - a
|
||||
* length extension, an offset - is an error, matching it. */
|
||||
size_t avail = src_len - ip;
|
||||
size_t emit = lit_len;
|
||||
int in_short = 0;
|
||||
if (emit > avail)
|
||||
{
|
||||
emit = avail;
|
||||
in_short = 1;
|
||||
}
|
||||
if (emit > dst_len - op)
|
||||
{
|
||||
memcpy(dst + op, src + ip, dst_len - op);
|
||||
return (int64_t)dst_len;
|
||||
}
|
||||
memcpy(dst + op, src + ip, emit);
|
||||
op += emit;
|
||||
ip += emit;
|
||||
if (in_short)
|
||||
return (int64_t)op;
|
||||
}
|
||||
|
||||
/* The final sequence is literals only, and the reference also
|
||||
* stops - successfully - when fewer than three bytes remain: an
|
||||
* offset cannot follow, so whatever is left cannot start a
|
||||
* sequence. */
|
||||
if (src_len - ip <= 2)
|
||||
return (int64_t)op;
|
||||
|
||||
/* -- match -- */
|
||||
if (src_len - ip < 2)
|
||||
return RLZ4_ERROR_TRUNCATED;
|
||||
offset = (size_t)src[ip] | ((size_t)src[ip + 1] << 8);
|
||||
ip += 2;
|
||||
if (offset - 1 >= op) /* 0 wraps; one compare */
|
||||
return RLZ4_ERROR_OFFSET;
|
||||
|
||||
match_len = (token & 15);
|
||||
/* after the extension, at least one token and the block's five
|
||||
* trailing literals must remain */
|
||||
if (match_len == 15 &&
|
||||
!rlz4_extend(src, src_len >= 6 ? src_len - 6 : 0, &ip, &match_len))
|
||||
return RLZ4_ERROR_TRUNCATED;
|
||||
match_len += 4; /* minimum match */
|
||||
|
||||
if (match_len > dst_len - op)
|
||||
match_len = dst_len - op; /* partial stop inside the match */
|
||||
|
||||
/* Match copy. Offsets smaller than the length are the RLE idiom
|
||||
* (offset 1 repeats the last byte): semantically a byte-wise
|
||||
* forward copy that reads bytes it has just written. Executed
|
||||
* as region doubling for short offsets - each memcpy source is
|
||||
* the fully-written prefix, so nothing overlaps - and as plain
|
||||
* chunked copies once the gap is at least eight bytes. Every
|
||||
* write stays inside [op, op + match_len): unlike the reference
|
||||
* fast path there is no wildcopy overrun, because the caller's
|
||||
* buffer is exactly the frame it asked for. */
|
||||
{
|
||||
uint8_t *to = dst + op;
|
||||
if (offset >= 8 && dst_len - op >= match_len + 15)
|
||||
{
|
||||
/* The common case: enough room after the match that an
|
||||
* 8-byte-stride copy may overshoot by up to seven bytes.
|
||||
* The scratch lands inside the caller's buffer and is
|
||||
* overwritten by whatever the block produces next; only
|
||||
* the returned length is contractual, exactly as with the
|
||||
* reference wildcopy. Reads trail writes by >= 8, so no
|
||||
* iteration reads its own output. */
|
||||
const uint8_t *from = dst + op - offset;
|
||||
uint8_t *end = to + match_len;
|
||||
do
|
||||
{
|
||||
memcpy(to, from, 8);
|
||||
to += 8; from += 8;
|
||||
} while (to < end);
|
||||
}
|
||||
else if (offset >= 8 || offset >= match_len)
|
||||
{
|
||||
/* Near the end of the buffer: exact chunks, no overshoot. */
|
||||
const uint8_t *from = dst + op - offset;
|
||||
size_t n = match_len;
|
||||
while (n >= 8)
|
||||
{
|
||||
memcpy(to, from, 8);
|
||||
to += 8; from += 8; n -= 8;
|
||||
}
|
||||
while (n--)
|
||||
*to++ = *from++;
|
||||
}
|
||||
else if (match_len <= 16)
|
||||
{
|
||||
/* Short overlapped match: a byte loop beats copy setup. */
|
||||
const uint8_t *from = dst + op - offset;
|
||||
size_t n = match_len;
|
||||
while (n--)
|
||||
*to++ = *from++;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Long short-offset match (the RLE idiom): seed one
|
||||
* period, then double the written region - every memcpy
|
||||
* source is the fully-written prefix, so nothing
|
||||
* overlaps. */
|
||||
size_t have = offset;
|
||||
memcpy(to, dst + op - offset, offset);
|
||||
while (have * 2 <= match_len)
|
||||
{
|
||||
memcpy(to + have, to, have);
|
||||
have *= 2;
|
||||
}
|
||||
if (have < match_len)
|
||||
memcpy(to + have, to, match_len - have);
|
||||
}
|
||||
}
|
||||
op += match_len;
|
||||
|
||||
if (op == dst_len)
|
||||
return (int64_t)op;
|
||||
}
|
||||
}
|
||||
@@ -179,11 +179,14 @@ static int rzstd_cpu_bmi2(void)
|
||||
|
||||
if (have < 0)
|
||||
{
|
||||
unsigned a, b, c, d;
|
||||
unsigned a, b = 0, c, d;
|
||||
|
||||
/* Both racers write the same answer, so the race is benign. */
|
||||
have = (__get_cpuid_count(7, 0, &a, &b, &c, &d) && (b & (1u << 8)))
|
||||
? 1 : 0;
|
||||
/* __get_cpuid_count only exists from GCC 7; __get_cpuid_max and
|
||||
* __cpuid_count are in every cpuid.h this branch accepts. Both
|
||||
* racers write the same answer, so the race is benign. */
|
||||
if (__get_cpuid_max(0, NULL) >= 7)
|
||||
__cpuid_count(7, 0, a, b, c, d);
|
||||
have = (b & (1u << 8)) ? 1 : 0;
|
||||
}
|
||||
return have;
|
||||
}
|
||||
@@ -3921,7 +3924,7 @@ literals_done:
|
||||
/* the block's own: accuracy by the count, as the reference
|
||||
* picks it, capped by the table's maximum */
|
||||
alog = 5;
|
||||
while (alog < log_max[t] && ((uint32_t)1 << alog) < nseq) alog++;
|
||||
while (alog < log_max[t] && ((size_t)1 << alog) < nseq) alog++;
|
||||
if (rzstd_fse_normalize(hist[t], max + 1, (uint32_t)nseq, alog, norm[t]))
|
||||
{
|
||||
for (s = 0; s <= max; s++)
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <retro_posix_source.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -1,741 +0,0 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (encoding_vcdiff.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <encodings/encoding_vcdiff.h>
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* VCDIFF (RFC 3284), decode side only.
|
||||
*
|
||||
* Written from the specification. Section references below are to
|
||||
* RFC 3284.
|
||||
* -------------------------------------------------------------------- */
|
||||
|
||||
/* Instruction types (s5.4). */
|
||||
#define VCD_NOOP 0
|
||||
#define VCD_ADD 1
|
||||
#define VCD_RUN 2
|
||||
#define VCD_COPY 3
|
||||
|
||||
/* Hdr_Indicator bits (s4.1). */
|
||||
#define VCD_DECOMPRESS 0x01
|
||||
#define VCD_CODETABLE 0x02
|
||||
#define VCD_APPHEADER 0x04
|
||||
|
||||
/* Win_Indicator bits (s4.2). */
|
||||
#define VCD_SOURCE 0x01
|
||||
#define VCD_TARGET 0x02
|
||||
|
||||
/* The address cache (s5.1): four "near" slots and three pages of 256
|
||||
* "same" slots. */
|
||||
#define VCD_NEAR 4
|
||||
#define VCD_SAME 3
|
||||
|
||||
struct vcd_inst
|
||||
{
|
||||
uint8_t type1, size1, mode1;
|
||||
uint8_t type2, size2, mode2;
|
||||
};
|
||||
|
||||
/* The default code table (s5.5), generated from the rules that define
|
||||
* it rather than transcribed, then checked against a reference decoder
|
||||
* over a corpus of real patches. */
|
||||
static const struct vcd_inst vcd_table[256] = {
|
||||
{2, 0,0,0,0,0}, {1, 0,0,0,0,0}, {1, 1,0,0,0,0}, {1, 2,0,0,0,0},
|
||||
{1, 3,0,0,0,0}, {1, 4,0,0,0,0}, {1, 5,0,0,0,0}, {1, 6,0,0,0,0},
|
||||
{1, 7,0,0,0,0}, {1, 8,0,0,0,0}, {1, 9,0,0,0,0}, {1,10,0,0,0,0},
|
||||
{1,11,0,0,0,0}, {1,12,0,0,0,0}, {1,13,0,0,0,0}, {1,14,0,0,0,0},
|
||||
{1,15,0,0,0,0}, {1,16,0,0,0,0}, {1,17,0,0,0,0}, {3, 0,0,0,0,0},
|
||||
{3, 4,0,0,0,0}, {3, 5,0,0,0,0}, {3, 6,0,0,0,0}, {3, 7,0,0,0,0},
|
||||
{3, 8,0,0,0,0}, {3, 9,0,0,0,0}, {3,10,0,0,0,0}, {3,11,0,0,0,0},
|
||||
{3,12,0,0,0,0}, {3,13,0,0,0,0}, {3,14,0,0,0,0}, {3,15,0,0,0,0},
|
||||
{3,16,0,0,0,0}, {3,17,0,0,0,0}, {3,18,0,0,0,0}, {3, 0,1,0,0,0},
|
||||
{3, 4,1,0,0,0}, {3, 5,1,0,0,0}, {3, 6,1,0,0,0}, {3, 7,1,0,0,0},
|
||||
{3, 8,1,0,0,0}, {3, 9,1,0,0,0}, {3,10,1,0,0,0}, {3,11,1,0,0,0},
|
||||
{3,12,1,0,0,0}, {3,13,1,0,0,0}, {3,14,1,0,0,0}, {3,15,1,0,0,0},
|
||||
{3,16,1,0,0,0}, {3,17,1,0,0,0}, {3,18,1,0,0,0}, {3, 0,2,0,0,0},
|
||||
{3, 4,2,0,0,0}, {3, 5,2,0,0,0}, {3, 6,2,0,0,0}, {3, 7,2,0,0,0},
|
||||
{3, 8,2,0,0,0}, {3, 9,2,0,0,0}, {3,10,2,0,0,0}, {3,11,2,0,0,0},
|
||||
{3,12,2,0,0,0}, {3,13,2,0,0,0}, {3,14,2,0,0,0}, {3,15,2,0,0,0},
|
||||
{3,16,2,0,0,0}, {3,17,2,0,0,0}, {3,18,2,0,0,0}, {3, 0,3,0,0,0},
|
||||
{3, 4,3,0,0,0}, {3, 5,3,0,0,0}, {3, 6,3,0,0,0}, {3, 7,3,0,0,0},
|
||||
{3, 8,3,0,0,0}, {3, 9,3,0,0,0}, {3,10,3,0,0,0}, {3,11,3,0,0,0},
|
||||
{3,12,3,0,0,0}, {3,13,3,0,0,0}, {3,14,3,0,0,0}, {3,15,3,0,0,0},
|
||||
{3,16,3,0,0,0}, {3,17,3,0,0,0}, {3,18,3,0,0,0}, {3, 0,4,0,0,0},
|
||||
{3, 4,4,0,0,0}, {3, 5,4,0,0,0}, {3, 6,4,0,0,0}, {3, 7,4,0,0,0},
|
||||
{3, 8,4,0,0,0}, {3, 9,4,0,0,0}, {3,10,4,0,0,0}, {3,11,4,0,0,0},
|
||||
{3,12,4,0,0,0}, {3,13,4,0,0,0}, {3,14,4,0,0,0}, {3,15,4,0,0,0},
|
||||
{3,16,4,0,0,0}, {3,17,4,0,0,0}, {3,18,4,0,0,0}, {3, 0,5,0,0,0},
|
||||
{3, 4,5,0,0,0}, {3, 5,5,0,0,0}, {3, 6,5,0,0,0}, {3, 7,5,0,0,0},
|
||||
{3, 8,5,0,0,0}, {3, 9,5,0,0,0}, {3,10,5,0,0,0}, {3,11,5,0,0,0},
|
||||
{3,12,5,0,0,0}, {3,13,5,0,0,0}, {3,14,5,0,0,0}, {3,15,5,0,0,0},
|
||||
{3,16,5,0,0,0}, {3,17,5,0,0,0}, {3,18,5,0,0,0}, {3, 0,6,0,0,0},
|
||||
{3, 4,6,0,0,0}, {3, 5,6,0,0,0}, {3, 6,6,0,0,0}, {3, 7,6,0,0,0},
|
||||
{3, 8,6,0,0,0}, {3, 9,6,0,0,0}, {3,10,6,0,0,0}, {3,11,6,0,0,0},
|
||||
{3,12,6,0,0,0}, {3,13,6,0,0,0}, {3,14,6,0,0,0}, {3,15,6,0,0,0},
|
||||
{3,16,6,0,0,0}, {3,17,6,0,0,0}, {3,18,6,0,0,0}, {3, 0,7,0,0,0},
|
||||
{3, 4,7,0,0,0}, {3, 5,7,0,0,0}, {3, 6,7,0,0,0}, {3, 7,7,0,0,0},
|
||||
{3, 8,7,0,0,0}, {3, 9,7,0,0,0}, {3,10,7,0,0,0}, {3,11,7,0,0,0},
|
||||
{3,12,7,0,0,0}, {3,13,7,0,0,0}, {3,14,7,0,0,0}, {3,15,7,0,0,0},
|
||||
{3,16,7,0,0,0}, {3,17,7,0,0,0}, {3,18,7,0,0,0}, {3, 0,8,0,0,0},
|
||||
{3, 4,8,0,0,0}, {3, 5,8,0,0,0}, {3, 6,8,0,0,0}, {3, 7,8,0,0,0},
|
||||
{3, 8,8,0,0,0}, {3, 9,8,0,0,0}, {3,10,8,0,0,0}, {3,11,8,0,0,0},
|
||||
{3,12,8,0,0,0}, {3,13,8,0,0,0}, {3,14,8,0,0,0}, {3,15,8,0,0,0},
|
||||
{3,16,8,0,0,0}, {3,17,8,0,0,0}, {3,18,8,0,0,0}, {1, 1,0,3,4,0},
|
||||
{1, 1,0,3,5,0}, {1, 1,0,3,6,0}, {1, 2,0,3,4,0}, {1, 2,0,3,5,0},
|
||||
{1, 2,0,3,6,0}, {1, 3,0,3,4,0}, {1, 3,0,3,5,0}, {1, 3,0,3,6,0},
|
||||
{1, 4,0,3,4,0}, {1, 4,0,3,5,0}, {1, 4,0,3,6,0}, {1, 1,0,3,4,1},
|
||||
{1, 1,0,3,5,1}, {1, 1,0,3,6,1}, {1, 2,0,3,4,1}, {1, 2,0,3,5,1},
|
||||
{1, 2,0,3,6,1}, {1, 3,0,3,4,1}, {1, 3,0,3,5,1}, {1, 3,0,3,6,1},
|
||||
{1, 4,0,3,4,1}, {1, 4,0,3,5,1}, {1, 4,0,3,6,1}, {1, 1,0,3,4,2},
|
||||
{1, 1,0,3,5,2}, {1, 1,0,3,6,2}, {1, 2,0,3,4,2}, {1, 2,0,3,5,2},
|
||||
{1, 2,0,3,6,2}, {1, 3,0,3,4,2}, {1, 3,0,3,5,2}, {1, 3,0,3,6,2},
|
||||
{1, 4,0,3,4,2}, {1, 4,0,3,5,2}, {1, 4,0,3,6,2}, {1, 1,0,3,4,3},
|
||||
{1, 1,0,3,5,3}, {1, 1,0,3,6,3}, {1, 2,0,3,4,3}, {1, 2,0,3,5,3},
|
||||
{1, 2,0,3,6,3}, {1, 3,0,3,4,3}, {1, 3,0,3,5,3}, {1, 3,0,3,6,3},
|
||||
{1, 4,0,3,4,3}, {1, 4,0,3,5,3}, {1, 4,0,3,6,3}, {1, 1,0,3,4,4},
|
||||
{1, 1,0,3,5,4}, {1, 1,0,3,6,4}, {1, 2,0,3,4,4}, {1, 2,0,3,5,4},
|
||||
{1, 2,0,3,6,4}, {1, 3,0,3,4,4}, {1, 3,0,3,5,4}, {1, 3,0,3,6,4},
|
||||
{1, 4,0,3,4,4}, {1, 4,0,3,5,4}, {1, 4,0,3,6,4}, {1, 1,0,3,4,5},
|
||||
{1, 1,0,3,5,5}, {1, 1,0,3,6,5}, {1, 2,0,3,4,5}, {1, 2,0,3,5,5},
|
||||
{1, 2,0,3,6,5}, {1, 3,0,3,4,5}, {1, 3,0,3,5,5}, {1, 3,0,3,6,5},
|
||||
{1, 4,0,3,4,5}, {1, 4,0,3,5,5}, {1, 4,0,3,6,5}, {1, 1,0,3,4,6},
|
||||
{1, 2,0,3,4,6}, {1, 3,0,3,4,6}, {1, 4,0,3,4,6}, {1, 1,0,3,4,7},
|
||||
{1, 2,0,3,4,7}, {1, 3,0,3,4,7}, {1, 4,0,3,4,7}, {1, 1,0,3,4,8},
|
||||
{1, 2,0,3,4,8}, {1, 3,0,3,4,8}, {1, 4,0,3,4,8}, {3, 4,0,1,1,0},
|
||||
{3, 4,1,1,1,0}, {3, 4,2,1,1,0}, {3, 4,3,1,1,0}, {3, 4,4,1,1,0},
|
||||
{3, 4,5,1,1,0}, {3, 4,6,1,1,0}, {3, 4,7,1,1,0}, {3, 4,8,1,1,0}
|
||||
};
|
||||
|
||||
struct vcdiff_stream
|
||||
{
|
||||
const uint8_t *patch; /* borrowed, must outlive the stream */
|
||||
size_t patch_len;
|
||||
size_t p_off; /* first window not yet decoded */
|
||||
|
||||
const uint8_t *src; /* caller's buffer, or own_src below */
|
||||
size_t src_len; /* declared total */
|
||||
size_t src_seen; /* how much of it has arrived */
|
||||
uint8_t *own_src; /* retained copy, streaming only */
|
||||
size_t own_cap;
|
||||
uint8_t owns_src; /* 1 when own_src is in use */
|
||||
uint8_t failed;
|
||||
|
||||
uint8_t *out; /* target being built */
|
||||
size_t out_len; /* bytes produced so far */
|
||||
size_t out_cap;
|
||||
|
||||
/* Named near_cache, not near: MSVC still reserves "near" (and "far")
|
||||
* from its segmented-memory days, so a member called near does not
|
||||
* parse there even though it is a perfectly ordinary C identifier. */
|
||||
uint32_t near_cache[VCD_NEAR];
|
||||
uint32_t same[VCD_SAME * 256];
|
||||
unsigned next_slot;
|
||||
};
|
||||
|
||||
/* kept for the internal functions, which predate the streaming form */
|
||||
#define vcd_dec vcdiff_stream
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* primitives
|
||||
* -------------------------------------------------------------------- */
|
||||
|
||||
/* Big-endian base-128, high bit continues (s2). Refuses anything that
|
||||
* will not fit in 32 bits: every length and address in a patch this
|
||||
* decoder will accept is bounded by the buffers it addresses, and a
|
||||
* wider value can only be corrupt. */
|
||||
static bool vcd_varint(const uint8_t **p, const uint8_t *end,
|
||||
uint32_t *out)
|
||||
{
|
||||
uint32_t v = 0;
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < 5; i++)
|
||||
{
|
||||
uint8_t b;
|
||||
if (*p >= end)
|
||||
return false;
|
||||
b = *(*p)++;
|
||||
if (v > (uint32_t)0x01FFFFFF) /* the shift below would drop bits */
|
||||
return false;
|
||||
v = (v << 7) | (uint8_t)(b & 0x7F);
|
||||
if (!(b & 0x80))
|
||||
{
|
||||
*out = v;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool vcd_reserve(struct vcd_dec *d, size_t need)
|
||||
{
|
||||
size_t cap;
|
||||
uint8_t *buf;
|
||||
|
||||
if (need <= d->out_cap)
|
||||
return true;
|
||||
/* The first reservation is the pre-pass's exact total, so take it
|
||||
* verbatim rather than rounding it up a doubling ladder; only
|
||||
* growth beyond a hint that turned out short doubles. */
|
||||
if (!d->out_cap)
|
||||
cap = need;
|
||||
else
|
||||
{
|
||||
cap = d->out_cap;
|
||||
while (cap < need)
|
||||
{
|
||||
if (cap > ((size_t)-1) / 2)
|
||||
return false;
|
||||
cap <<= 1;
|
||||
}
|
||||
}
|
||||
if (!(buf = (uint8_t*)realloc(d->out, cap)))
|
||||
return false;
|
||||
d->out = buf;
|
||||
d->out_cap = cap;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* address cache (s5.1)
|
||||
* -------------------------------------------------------------------- */
|
||||
|
||||
static void vcd_cache_reset(struct vcd_dec *d)
|
||||
{
|
||||
memset(d->near_cache, 0, sizeof(d->near_cache));
|
||||
memset(d->same, 0, sizeof(d->same));
|
||||
d->next_slot = 0;
|
||||
}
|
||||
|
||||
static void vcd_cache_update(struct vcd_dec *d, uint32_t addr)
|
||||
{
|
||||
d->same[addr % (VCD_SAME * 256)] = addr;
|
||||
d->near_cache[d->next_slot] = addr;
|
||||
d->next_slot = (d->next_slot + 1) % VCD_NEAR;
|
||||
}
|
||||
|
||||
/* Decode one address in @mode, for a COPY whose target position is
|
||||
* @here. Advances the address section cursor. */
|
||||
static bool vcd_addr(struct vcd_dec *d, unsigned mode, uint32_t here,
|
||||
const uint8_t **ap, const uint8_t *aend, uint32_t *out)
|
||||
{
|
||||
uint32_t v = 0;
|
||||
|
||||
if (mode == 0) /* SELF: absolute */
|
||||
{
|
||||
if (!vcd_varint(ap, aend, &v))
|
||||
return false;
|
||||
}
|
||||
else if (mode == 1) /* HERE: back from here */
|
||||
{
|
||||
if (!vcd_varint(ap, aend, &v) || v > here)
|
||||
return false;
|
||||
v = here - v;
|
||||
}
|
||||
else if (mode < 2 + VCD_NEAR) /* near cache */
|
||||
{
|
||||
uint32_t base = d->near_cache[mode - 2];
|
||||
uint32_t off = 0;
|
||||
if (!vcd_varint(ap, aend, &off) || off > (uint32_t)0xFFFFFFFFu - base)
|
||||
return false;
|
||||
v = base + off;
|
||||
}
|
||||
else /* same cache: a byte index */
|
||||
{
|
||||
unsigned page = mode - (2 + VCD_NEAR);
|
||||
if (*ap >= aend)
|
||||
return false;
|
||||
v = d->same[page * 256 + *(*ap)++];
|
||||
}
|
||||
|
||||
vcd_cache_update(d, v);
|
||||
*out = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* window decode (s4.3)
|
||||
* -------------------------------------------------------------------- */
|
||||
|
||||
/* Copy @len bytes to the target at @at from address @addr in the
|
||||
* window's address space: [0, seg_len) is the source segment, and
|
||||
* everything above it is the target window itself. A COPY may overlap
|
||||
* its own output - that is how the format expresses runs - so the
|
||||
* target-referencing case must copy byte by byte. */
|
||||
static bool vcd_copy(struct vcd_dec *d, const uint8_t *seg, size_t seg_len,
|
||||
size_t win_start, uint32_t addr, uint32_t len, size_t at)
|
||||
{
|
||||
if (addr < seg_len)
|
||||
{
|
||||
/* wholly inside the source segment, or it would run past its end */
|
||||
if ((size_t)len > seg_len - addr)
|
||||
return false;
|
||||
memcpy(d->out + at, seg + addr, len);
|
||||
return true;
|
||||
}
|
||||
{
|
||||
/* A COPY that reads the target must read bytes already produced.
|
||||
* Anything at or past the cursor names output that does not
|
||||
* exist yet - and the distance arithmetic below would underflow,
|
||||
* handing memcpy overlapping ranges and copying whatever the
|
||||
* allocation happened to contain into the decoded content.
|
||||
*
|
||||
* Compare inside the window's own address space rather than on
|
||||
* the derived pointer offsets: an address near the top of the
|
||||
* 32-bit range added to win_start wraps on a 32-bit host, and a
|
||||
* wrapped value can land below the cursor and pass a check made
|
||||
* after the addition. Both terms here are bounded by the window
|
||||
* itself, so neither can wrap. */
|
||||
size_t produced = at - win_start; /* target bytes so far */
|
||||
size_t off = (size_t)(addr - seg_len);
|
||||
size_t from;
|
||||
uint32_t i;
|
||||
|
||||
if (off >= produced)
|
||||
return false;
|
||||
from = win_start + off;
|
||||
if (from + len <= at)
|
||||
{
|
||||
memcpy(d->out + at, d->out + from, len);
|
||||
return true;
|
||||
}
|
||||
/* Overlapping, which is how the format spells a repeat: the
|
||||
* source is behind the cursor, so the first (at - from) bytes
|
||||
* are already final and each chunk of that width can be copied
|
||||
* whole before the next one needs it. Byte-at-a-time is
|
||||
* correct but needlessly slow for the wide distances that
|
||||
* dominate real patches. */
|
||||
{
|
||||
size_t dist = at - from;
|
||||
size_t done = 0;
|
||||
if (dist == 1)
|
||||
{
|
||||
memset(d->out + at, d->out[from], len);
|
||||
return true;
|
||||
}
|
||||
while (done < (size_t)len)
|
||||
{
|
||||
size_t chunk = (size_t)len - done;
|
||||
if (chunk > dist)
|
||||
chunk = dist;
|
||||
memcpy(d->out + at + done, d->out + from + done, chunk);
|
||||
done += chunk;
|
||||
}
|
||||
}
|
||||
(void)i;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Sum the windows' target lengths without decoding them, so the output
|
||||
* is one allocation of the right size.
|
||||
*
|
||||
* The format carries no total length, only a length per window, so a
|
||||
* decoder that simply grows as it goes pays a copy every time it
|
||||
* doubles - on a 32 MiB target that is a dozen reallocations and twice
|
||||
* the target in memmove traffic, which measured as the single largest
|
||||
* cost in the decode. Walking the headers is cheap by comparison:
|
||||
* every section length is stated, so each window is a handful of
|
||||
* varints and a skip. A malformed patch is left to the real decode to
|
||||
* reject; this pass only ever produces a hint, and returns 0 to mean
|
||||
* "cannot tell", which falls back to growing. */
|
||||
static size_t vcd_total_target(const uint8_t *p, const uint8_t *pend,
|
||||
size_t src_len)
|
||||
{
|
||||
size_t total = 0;
|
||||
|
||||
while (p < pend)
|
||||
{
|
||||
uint32_t seg_len = 0, seg_pos = 0, enc_len = 0, tgt_len = 0;
|
||||
uint32_t data_len = 0, inst_len = 0, addr_len = 0;
|
||||
uint8_t win_ind;
|
||||
|
||||
if (p >= pend)
|
||||
break;
|
||||
win_ind = *p++;
|
||||
if (win_ind & (VCD_SOURCE | VCD_TARGET))
|
||||
{
|
||||
if ( !vcd_varint(&p, pend, &seg_len)
|
||||
|| !vcd_varint(&p, pend, &seg_pos))
|
||||
return 0;
|
||||
}
|
||||
if ( !vcd_varint(&p, pend, &enc_len)
|
||||
|| !vcd_varint(&p, pend, &tgt_len))
|
||||
return 0;
|
||||
if (p >= pend)
|
||||
return 0;
|
||||
p++; /* delta indicator */
|
||||
if ( !vcd_varint(&p, pend, &data_len)
|
||||
|| !vcd_varint(&p, pend, &inst_len)
|
||||
|| !vcd_varint(&p, pend, &addr_len))
|
||||
return 0;
|
||||
if ( (size_t)(pend - p) < (size_t)data_len
|
||||
|| (size_t)(pend - p) - data_len < (size_t)inst_len
|
||||
|| (size_t)(pend - p) - data_len - inst_len < (size_t)addr_len)
|
||||
return 0;
|
||||
p += (size_t)data_len + inst_len + addr_len;
|
||||
if (total > ((size_t)-1) - tgt_len)
|
||||
return 0;
|
||||
total += tgt_len;
|
||||
}
|
||||
(void)src_len;
|
||||
return total;
|
||||
}
|
||||
|
||||
/* A window either decodes, or waits for source it has not been given
|
||||
* yet, or is malformed. The middle case is not an error: the caller
|
||||
* feeds more and the same window is attempted again. */
|
||||
enum vcd_win
|
||||
{
|
||||
VCD_WIN_OK = 0,
|
||||
VCD_WIN_WAIT,
|
||||
VCD_WIN_ERROR
|
||||
};
|
||||
|
||||
static enum vcd_win vcd_window(struct vcd_dec *d, const uint8_t **pp,
|
||||
const uint8_t *pend)
|
||||
{
|
||||
const uint8_t *p = *pp;
|
||||
const uint8_t *dp, *ip, *ap, *dend, *iend, *aend;
|
||||
const uint8_t *seg = NULL;
|
||||
uint32_t seg_len = 0, seg_pos = 0;
|
||||
uint32_t enc_len = 0, tgt_len = 0;
|
||||
uint32_t data_len = 0, inst_len = 0, addr_len = 0;
|
||||
size_t win_start = d->out_len;
|
||||
uint8_t win_ind, delta_ind;
|
||||
|
||||
if (p >= pend)
|
||||
return VCD_WIN_ERROR;
|
||||
win_ind = *p++;
|
||||
|
||||
if (win_ind & (VCD_SOURCE | VCD_TARGET))
|
||||
{
|
||||
if ( !vcd_varint(&p, pend, &seg_len)
|
||||
|| !vcd_varint(&p, pend, &seg_pos))
|
||||
return VCD_WIN_ERROR;
|
||||
if (win_ind & VCD_SOURCE)
|
||||
{
|
||||
if ( (size_t)seg_pos > d->src_len
|
||||
|| (size_t)seg_len > d->src_len - seg_pos)
|
||||
return VCD_WIN_ERROR;
|
||||
/* The segment this window needs may not have arrived yet.
|
||||
* Nothing has been consumed at this point - the cursor is
|
||||
* still at the window header - so the caller can simply try
|
||||
* again once more source has been fed. */
|
||||
if ((size_t)seg_pos + seg_len > d->src_seen)
|
||||
return VCD_WIN_WAIT;
|
||||
seg = d->src + seg_pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* the segment comes from the target already produced */
|
||||
if ( (size_t)seg_pos > d->out_len
|
||||
|| (size_t)seg_len > d->out_len - seg_pos)
|
||||
return VCD_WIN_ERROR;
|
||||
seg = d->out + seg_pos;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !vcd_varint(&p, pend, &enc_len)
|
||||
|| !vcd_varint(&p, pend, &tgt_len))
|
||||
return VCD_WIN_ERROR;
|
||||
if (p >= pend)
|
||||
return VCD_WIN_ERROR;
|
||||
delta_ind = *p++;
|
||||
|
||||
/* Any of the three sections being compressed means a secondary
|
||||
* compressor, which this decoder does not implement and must not
|
||||
* pretend to. */
|
||||
if (delta_ind != 0)
|
||||
return VCD_WIN_ERROR;
|
||||
|
||||
if ( !vcd_varint(&p, pend, &data_len)
|
||||
|| !vcd_varint(&p, pend, &inst_len)
|
||||
|| !vcd_varint(&p, pend, &addr_len))
|
||||
return VCD_WIN_ERROR;
|
||||
|
||||
/* the three sections follow, back to back */
|
||||
if ( (size_t)(pend - p) < (size_t)data_len
|
||||
|| (size_t)(pend - p) - data_len < (size_t)inst_len
|
||||
|| (size_t)(pend - p) - data_len - inst_len < (size_t)addr_len)
|
||||
return VCD_WIN_ERROR;
|
||||
|
||||
dp = p; dend = dp + data_len;
|
||||
ip = dend; iend = ip + inst_len;
|
||||
ap = iend; aend = ap + addr_len;
|
||||
|
||||
/* On a 32-bit host a large enough window could carry the end past
|
||||
* SIZE_MAX; every bound below is expressed against that sum, so it
|
||||
* has to be real before any of them mean anything. */
|
||||
if ((size_t)tgt_len > ((size_t)-1) - win_start)
|
||||
return VCD_WIN_ERROR;
|
||||
if (!vcd_reserve(d, win_start + tgt_len))
|
||||
return VCD_WIN_ERROR;
|
||||
|
||||
vcd_cache_reset(d);
|
||||
|
||||
while (d->out_len < win_start + tgt_len)
|
||||
{
|
||||
const struct vcd_inst *e;
|
||||
unsigned half;
|
||||
|
||||
if (ip >= iend)
|
||||
return VCD_WIN_ERROR;
|
||||
e = &vcd_table[*ip++];
|
||||
|
||||
for (half = 0; half < 2; half++)
|
||||
{
|
||||
unsigned type = half ? e->type2 : e->type1;
|
||||
uint32_t size = half ? e->size2 : e->size1;
|
||||
unsigned mode = half ? e->mode2 : e->mode1;
|
||||
size_t at = d->out_len;
|
||||
|
||||
if (type == VCD_NOOP)
|
||||
continue;
|
||||
if (size == 0 && !vcd_varint(&ip, iend, &size))
|
||||
return VCD_WIN_ERROR;
|
||||
if ( size == 0
|
||||
|| (size_t)size > win_start + tgt_len - at)
|
||||
return VCD_WIN_ERROR;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case VCD_ADD:
|
||||
if ((size_t)(dend - dp) < (size_t)size)
|
||||
return VCD_WIN_ERROR;
|
||||
memcpy(d->out + at, dp, size);
|
||||
dp += size;
|
||||
break;
|
||||
|
||||
case VCD_RUN:
|
||||
if (dp >= dend)
|
||||
return VCD_WIN_ERROR;
|
||||
memset(d->out + at, *dp++, size);
|
||||
break;
|
||||
|
||||
case VCD_COPY:
|
||||
{
|
||||
uint32_t addr = 0;
|
||||
uint32_t here = (uint32_t)(seg_len + (at - win_start));
|
||||
if (!vcd_addr(d, mode, here, &ap, aend, &addr))
|
||||
return VCD_WIN_ERROR;
|
||||
if (!vcd_copy(d, seg, seg_len, win_start, addr, size, at))
|
||||
return VCD_WIN_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return VCD_WIN_ERROR;
|
||||
}
|
||||
d->out_len = at + size;
|
||||
}
|
||||
}
|
||||
|
||||
if (d->out_len != win_start + tgt_len)
|
||||
return VCD_WIN_ERROR;
|
||||
|
||||
*pp = aend;
|
||||
return VCD_WIN_OK;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* public API
|
||||
* -------------------------------------------------------------------- */
|
||||
|
||||
/* Decode every window whose source has arrived. Stops at the first one
|
||||
* that must wait, leaving p_off on its header so the next feed retries
|
||||
* it from a clean cursor. */
|
||||
static bool vcd_drain(struct vcdiff_stream *s)
|
||||
{
|
||||
const uint8_t *pend = s->patch + s->patch_len;
|
||||
const uint8_t *p = s->patch + s->p_off;
|
||||
|
||||
while (p < pend)
|
||||
{
|
||||
const uint8_t *before = p;
|
||||
switch (vcd_window(s, &p, pend))
|
||||
{
|
||||
case VCD_WIN_OK:
|
||||
s->p_off = (size_t)(p - s->patch);
|
||||
break;
|
||||
case VCD_WIN_WAIT:
|
||||
p = before;
|
||||
s->p_off = (size_t)(before - s->patch);
|
||||
return true;
|
||||
default:
|
||||
s->failed = 1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
s->p_off = s->patch_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
vcdiff_stream_t *vcdiff_stream_open(const uint8_t *patch, size_t patch_len,
|
||||
size_t src_len)
|
||||
{
|
||||
struct vcdiff_stream *s;
|
||||
const uint8_t *p;
|
||||
uint8_t hdr;
|
||||
|
||||
if (!patch || patch_len < 5)
|
||||
return NULL;
|
||||
if ( patch[0] != 0xD6 || patch[1] != 0xC3
|
||||
|| patch[2] != 0xC4 || patch[3] != 0x00)
|
||||
return NULL;
|
||||
|
||||
p = patch + 4;
|
||||
hdr = *p++;
|
||||
|
||||
/* A secondary compressor or a replacement code table would change
|
||||
* how everything below is read. Refuse rather than misread. */
|
||||
if (hdr & (VCD_DECOMPRESS | VCD_CODETABLE))
|
||||
return NULL;
|
||||
|
||||
if (hdr & VCD_APPHEADER)
|
||||
{
|
||||
uint32_t alen = 0;
|
||||
if ( !vcd_varint(&p, patch + patch_len, &alen)
|
||||
|| (size_t)(patch + patch_len - p) < (size_t)alen)
|
||||
return NULL;
|
||||
p += alen; /* informational only */
|
||||
}
|
||||
|
||||
if (!(s = (struct vcdiff_stream*)calloc(1, sizeof(*s))))
|
||||
return NULL;
|
||||
|
||||
s->patch = patch;
|
||||
s->patch_len = patch_len;
|
||||
s->p_off = (size_t)(p - patch);
|
||||
s->src_len = src_len;
|
||||
|
||||
{
|
||||
size_t hint = vcd_total_target(p, patch + patch_len, src_len);
|
||||
if (hint && !vcd_reserve(s, hint))
|
||||
{
|
||||
free(s);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
size_t vcdiff_stream_feed(vcdiff_stream_t *s, const uint8_t *chunk,
|
||||
size_t len)
|
||||
{
|
||||
if (!s || !chunk || !len || s->failed)
|
||||
return 0;
|
||||
|
||||
/* Retain the source: a later window may name any part of it. */
|
||||
if (s->src_seen + len > s->own_cap)
|
||||
{
|
||||
size_t cap = s->own_cap ? s->own_cap : 65536;
|
||||
uint8_t *tmp;
|
||||
while (s->src_seen + len > cap)
|
||||
{
|
||||
if (cap > ((size_t)-1) / 2)
|
||||
{
|
||||
s->failed = 1;
|
||||
return 0;
|
||||
}
|
||||
cap <<= 1;
|
||||
}
|
||||
if (!(tmp = (uint8_t*)realloc(s->own_src, cap)))
|
||||
{
|
||||
s->failed = 1;
|
||||
return 0;
|
||||
}
|
||||
s->own_src = tmp;
|
||||
s->own_cap = cap;
|
||||
}
|
||||
memcpy(s->own_src + s->src_seen, chunk, len);
|
||||
s->owns_src = 1;
|
||||
s->src = s->own_src;
|
||||
s->src_seen += len;
|
||||
|
||||
if (!vcd_drain(s))
|
||||
return 0;
|
||||
return len;
|
||||
}
|
||||
|
||||
bool vcdiff_stream_finish(vcdiff_stream_t *s, uint8_t **out,
|
||||
size_t *out_len)
|
||||
{
|
||||
if (!s || !out || !out_len || s->failed)
|
||||
return false;
|
||||
|
||||
/* A feed that stopped short is indistinguishable from a source that
|
||||
* is genuinely shorter, and the windows would decode against
|
||||
* whatever did arrive. Refuse instead. */
|
||||
if (s->src_seen < s->src_len)
|
||||
return false;
|
||||
|
||||
if (!vcd_drain(s) || s->p_off != s->patch_len)
|
||||
return false;
|
||||
|
||||
*out = s->out;
|
||||
*out_len = s->out_len;
|
||||
s->out = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
void vcdiff_stream_free(vcdiff_stream_t *s)
|
||||
{
|
||||
if (!s)
|
||||
return;
|
||||
free(s->out);
|
||||
free(s->own_src);
|
||||
free(s);
|
||||
}
|
||||
|
||||
bool vcdiff_decode(const uint8_t *patch, size_t patch_len,
|
||||
const uint8_t *src, size_t src_len,
|
||||
uint8_t **out, size_t *out_len)
|
||||
{
|
||||
struct vcdiff_stream *s;
|
||||
bool ok;
|
||||
|
||||
if (!src && src_len)
|
||||
return false;
|
||||
if (!(s = vcdiff_stream_open(patch, patch_len, src_len)))
|
||||
return false;
|
||||
|
||||
/* The whole source is already here, so point at the caller's buffer
|
||||
* rather than taking a copy of it - this path decodes patches
|
||||
* against multi-megabyte ROMs and the copy would cost more than the
|
||||
* decode. */
|
||||
s->src = src;
|
||||
s->src_seen = src_len;
|
||||
|
||||
ok = vcd_drain(s) && s->p_off == patch_len;
|
||||
if (ok)
|
||||
{
|
||||
*out = s->out;
|
||||
*out_len = s->out_len;
|
||||
s->out = NULL;
|
||||
}
|
||||
vcdiff_stream_free(s);
|
||||
return ok;
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (cpu_class.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.
|
||||
*/
|
||||
|
||||
/* Per-processor performance class: the one source of truth shared by
|
||||
* cpu_features_get_processor_order() (features_cpu.c) and
|
||||
* sthread_prefer_fast_cores() / sthread_get_core_topology()
|
||||
* (rthreads.c), so a thread pinned "to the fast cores" and a thread
|
||||
* pinned "to the strongest processor" land on the same silicon.
|
||||
*
|
||||
* This is a private implementation header: every function is static
|
||||
* and the file is included by exactly the two .c files above, because
|
||||
* neither may link the other (rthreads.c is built on its own by the
|
||||
* sample harnesses, features_cpu.c by the cores).
|
||||
*
|
||||
* cpu_class_read() fills cls[id] for id < len with a class where a
|
||||
* higher value is a faster core, and returns the number of ids
|
||||
* covered, or 0 when the platform publishes no class at all - a
|
||||
* homogeneous part, or one this code cannot see into - in which case
|
||||
* every processor is to be taken as the same class.
|
||||
*
|
||||
* Linux, in order of trust:
|
||||
* 1. The kernel's own hybrid classification: on Intel hybrid parts
|
||||
* the perf driver publishes the P-cores as cpu_core/cpus and the
|
||||
* E-cores as cpu_atom/cpus.
|
||||
* 2. cpu_capacity, the scheduler's per-CPU throughput figure on ARM
|
||||
* big.LITTLE, where the biggest cluster is 1024 and a little one a
|
||||
* few hundred. A three-tier part (one prime, some big, some
|
||||
* little) keeps its big cores in the fast class by taking
|
||||
* everything within CPU_CLASS_CAPACITY_PCT of the best.
|
||||
* 3. cpuinfo_max_freq, within CPU_CLASS_FREQ_PCT of the highest. The
|
||||
* band is what makes this usable on x86: favoured cores (Intel
|
||||
* Turbo Boost Max 3.0, AMD CPPC preferred cores) and the two CCDs
|
||||
* of an X3D part differ from their siblings by a few percent,
|
||||
* while a real slow cluster sits 20-40% lower.
|
||||
* Windows 10 1607+: CPU Sets EfficiencyClass, which rises with
|
||||
* performance (P = 1, E = 0 on Intel hybrid). Resolved at run time so
|
||||
* older targets still link and simply report nothing.
|
||||
* Everything else: nothing.
|
||||
*
|
||||
* A builder may point CPU_CLASS_SYSFS at a fixture tree in place of
|
||||
* /sys/devices/system/cpu. */
|
||||
|
||||
#ifndef __LIBRETRO_SDK_CPU_CLASS_H
|
||||
#define __LIBRETRO_SDK_CPU_CLASS_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <boolean.h>
|
||||
#include <retro_inline.h>
|
||||
|
||||
#ifndef CPU_CLASS_SYSFS
|
||||
#define CPU_CLASS_SYSFS "/sys/devices/system/cpu"
|
||||
#endif
|
||||
#define CPU_CLASS_CAPACITY_PCT 70
|
||||
#define CPU_CLASS_FREQ_PCT 85
|
||||
#define CPU_CLASS_MAX_IDS 256
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
/* Reads one unsigned long from a sysfs file; 0 when absent or empty. */
|
||||
static unsigned long cpu_class_sysfs_ulong(const char *path)
|
||||
{
|
||||
unsigned long v = 0;
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f)
|
||||
return 0;
|
||||
if (fscanf(f, "%lu", &v) != 1)
|
||||
v = 0;
|
||||
fclose(f);
|
||||
return v;
|
||||
}
|
||||
|
||||
/* Parses a sysfs cpulist ("0-7,16-23") at path, setting mark[id] for
|
||||
* each id < len it names; returns whether the file existed and named
|
||||
* at least one processor. */
|
||||
static bool cpu_class_sysfs_cpulist(const char *path,
|
||||
unsigned char *mark, size_t len)
|
||||
{
|
||||
char line[512];
|
||||
char *p;
|
||||
bool any = false;
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f)
|
||||
return false;
|
||||
line[0] = '\0';
|
||||
if (!fgets(line, sizeof(line), f))
|
||||
line[0] = '\0';
|
||||
fclose(f);
|
||||
for (p = line; *p; )
|
||||
{
|
||||
char *next;
|
||||
unsigned long lo, hi, i;
|
||||
if (*p < '0' || *p > '9')
|
||||
{
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
lo = hi = strtoul(p, &next, 10);
|
||||
if (*next == '-')
|
||||
hi = strtoul(next + 1, &next, 10);
|
||||
for (i = lo; i <= hi && i < len; i++)
|
||||
{
|
||||
mark[i] = 1;
|
||||
any = true;
|
||||
}
|
||||
p = next;
|
||||
}
|
||||
return any;
|
||||
}
|
||||
|
||||
/* Reads "<cpu>/<leaf>" for every id < len; sets cls[id] to 1 where the
|
||||
* value is within pct percent of the highest, 0 otherwise (including
|
||||
* ids with no value). Returns the highest id with a value plus one, or
|
||||
* 0 when none had one. */
|
||||
static size_t cpu_class_by_value(const char *leaf, unsigned pct,
|
||||
unsigned char *cls, size_t len)
|
||||
{
|
||||
unsigned long best = 0;
|
||||
size_t i, n = 0;
|
||||
/* 32-bit is plenty: kHz clocks top out below 10^7 and capacities
|
||||
* at 1024; unsigned long would double the frame past the 2 KiB
|
||||
* thread-stack budget (PSP, GX). */
|
||||
unsigned vals[CPU_CLASS_MAX_IDS];
|
||||
|
||||
if (len > CPU_CLASS_MAX_IDS)
|
||||
len = CPU_CLASS_MAX_IDS;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), CPU_CLASS_SYSFS "/cpu%u/%s",
|
||||
(unsigned)i, leaf);
|
||||
vals[i] = (unsigned)cpu_class_sysfs_ulong(path);
|
||||
if (vals[i])
|
||||
{
|
||||
n = i + 1;
|
||||
if (vals[i] > best)
|
||||
best = vals[i];
|
||||
}
|
||||
}
|
||||
for (i = 0; i < len; i++)
|
||||
cls[i] = (vals[i] && vals[i] * 100 >= best * pct) ? 1 : 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
static size_t cpu_class_read(unsigned char *cls, size_t len)
|
||||
{
|
||||
unsigned char atom[CPU_CLASS_MAX_IDS];
|
||||
size_t i, n;
|
||||
|
||||
if (len > CPU_CLASS_MAX_IDS)
|
||||
len = CPU_CLASS_MAX_IDS;
|
||||
memset(cls, 0, len);
|
||||
memset(atom, 0, sizeof(atom));
|
||||
|
||||
/* 1. Intel hybrid, as the kernel sees it (/sys/devices/cpu_core and
|
||||
* cpu_atom, beside /sys/devices/system). Only meaningful when
|
||||
* both kinds exist: cpu_core alone is what a homogeneous Intel
|
||||
* part publishes. */
|
||||
if ( cpu_class_sysfs_cpulist(CPU_CLASS_SYSFS "/../../cpu_core/cpus", cls, len)
|
||||
&& cpu_class_sysfs_cpulist(CPU_CLASS_SYSFS "/../../cpu_atom/cpus", atom, len))
|
||||
{
|
||||
n = 0;
|
||||
for (i = 0; i < len; i++)
|
||||
if (cls[i] || atom[i])
|
||||
n = i + 1;
|
||||
return n;
|
||||
}
|
||||
memset(cls, 0, len);
|
||||
|
||||
/* 2. Scheduler capacity (ARM), then 3. maximum clock. */
|
||||
if ((n = cpu_class_by_value("cpu_capacity", CPU_CLASS_CAPACITY_PCT, cls, len)))
|
||||
return n;
|
||||
return cpu_class_by_value("cpufreq/cpuinfo_max_freq", CPU_CLASS_FREQ_PCT,
|
||||
cls, len);
|
||||
}
|
||||
|
||||
#elif defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
typedef BOOL (WINAPI *cpu_class_get_cpusets_t)(void*, ULONG, ULONG*,
|
||||
HANDLE, ULONG);
|
||||
|
||||
/* SYSTEM_CPU_SET_INFORMATION, read by offset since older SDKs lack the
|
||||
* type: Size at 0, Type at 4 (0 = CpuSet), Id at 8, Group at 12,
|
||||
* LogicalProcessorIndex at 14, CoreIndex at 15, EfficiencyClass at 18.
|
||||
* Ids are the processor's index within group 0, the numbering an
|
||||
* affinity mask is built from; other groups are beyond a 64-bit mask
|
||||
* and are left out. */
|
||||
static size_t cpu_class_read(unsigned char *cls, size_t len)
|
||||
{
|
||||
HMODULE k32 = GetModuleHandleA("kernel32.dll");
|
||||
cpu_class_get_cpusets_t getinfo = NULL;
|
||||
unsigned char *buf = NULL;
|
||||
ULONG len_needed = 0;
|
||||
ULONG off;
|
||||
size_t n = 0;
|
||||
|
||||
memset(cls, 0, len);
|
||||
if (k32)
|
||||
getinfo = (cpu_class_get_cpusets_t)(void (*)(void))
|
||||
GetProcAddress(k32, "GetSystemCpuSetInformation");
|
||||
if (!getinfo)
|
||||
return 0;
|
||||
getinfo(NULL, 0, &len_needed, GetCurrentProcess(), 0);
|
||||
if (!len_needed || !(buf = (unsigned char*)malloc(len_needed)))
|
||||
return 0;
|
||||
if (!getinfo(buf, len_needed, &len_needed, GetCurrentProcess(), 0))
|
||||
{
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
for (off = 0; off + 20 <= len_needed; )
|
||||
{
|
||||
DWORD size = *(DWORD*)(buf + off);
|
||||
if (size < 20)
|
||||
break;
|
||||
if (*(DWORD*)(buf + off + 4) == 0 && *(WORD*)(buf + off + 12) == 0)
|
||||
{
|
||||
unsigned id = buf[off + 14];
|
||||
if (id < len)
|
||||
{
|
||||
cls[id] = buf[off + 18];
|
||||
if ((size_t)id + 1 > n)
|
||||
n = id + 1;
|
||||
}
|
||||
}
|
||||
off += size;
|
||||
}
|
||||
free(buf);
|
||||
return n;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/* No class source: inline so a platform whose callers are compiled
|
||||
* out of the ranking does not carry an unreferenced function. */
|
||||
static INLINE size_t cpu_class_read(unsigned char *cls, size_t len)
|
||||
{
|
||||
memset(cls, 0, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -32,11 +32,21 @@
|
||||
#include <compat/strl.h>
|
||||
#include <libretro.h>
|
||||
#include <features/features_cpu.h>
|
||||
#include "cpu_class.h" /* per-processor class, shared with rthreads */
|
||||
#if defined(__linux__)
|
||||
#include <sys/syscall.h>
|
||||
/* The prototype every Linux libc uses; spelled out so a strict C89
|
||||
* build, where glibc hides it, sees the same one. */
|
||||
extern long syscall(long number, ...);
|
||||
#endif
|
||||
#include <retro_atomic.h>
|
||||
#include <retro_timers.h>
|
||||
|
||||
#if defined(_WIN32) && !defined(_XBOX)
|
||||
#include <windows.h>
|
||||
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __PSL1GHT__
|
||||
@@ -87,7 +97,9 @@
|
||||
#include <sys/sys_time.h>
|
||||
#endif
|
||||
|
||||
#ifdef GEKKO
|
||||
#if defined(GEKKO_NATIVE)
|
||||
#include <gekko/gekko.h>
|
||||
#elif defined(GEKKO)
|
||||
#include <ogc/lwp_watchdog.h>
|
||||
#endif
|
||||
|
||||
@@ -168,45 +180,39 @@ static int ra_clock_gettime(int clk_ik, struct timespec *t)
|
||||
retro_perf_tick_t cpu_features_get_perf_counter(void)
|
||||
{
|
||||
retro_perf_tick_t time_ticks = 0;
|
||||
#if defined(_WIN32)
|
||||
long tv_sec, tv_usec;
|
||||
#if defined(_MSC_VER) && _MSC_VER <= 1200
|
||||
static const unsigned __int64 epoch = 11644473600000000;
|
||||
#else
|
||||
static const unsigned __int64 epoch = 11644473600000000ULL;
|
||||
#endif
|
||||
FILETIME file_time;
|
||||
SYSTEMTIME system_time;
|
||||
ULARGE_INTEGER ularge;
|
||||
|
||||
GetSystemTime(&system_time);
|
||||
SystemTimeToFileTime(&system_time, &file_time);
|
||||
ularge.LowPart = file_time.dwLowDateTime;
|
||||
ularge.HighPart = file_time.dwHighDateTime;
|
||||
|
||||
tv_sec = (long)((ularge.QuadPart - epoch) / 10000000L);
|
||||
tv_usec = (long)(system_time.wMilliseconds * 1000);
|
||||
time_ticks = (1000000 * tv_sec + tv_usec);
|
||||
/* The CPU's own cycle counter wherever there is one: a register
|
||||
* read, no clock behind it. Platform clocks follow for the rest. */
|
||||
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
|
||||
time_ticks = (retro_perf_tick_t)__rdtsc();
|
||||
#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
|
||||
{
|
||||
unsigned a, d;
|
||||
__asm__ __volatile__ ("rdtsc" : "=a" (a), "=d" (d));
|
||||
time_ticks = (retro_perf_tick_t)a | ((retro_perf_tick_t)d << 32);
|
||||
}
|
||||
#elif defined(__GNUC__) && defined(__aarch64__)
|
||||
__asm__ __volatile__ ("mrs %0, cntvct_el0" : "=r" (time_ticks));
|
||||
#elif defined(__GNUC__) && defined(__ARM_ARCH_6__)
|
||||
__asm__ __volatile__ ("mrc p15, 0, %0, c9, c13, 0" : "=r" (time_ticks));
|
||||
#elif defined(_WIN32)
|
||||
{
|
||||
LARGE_INTEGER c;
|
||||
QueryPerformanceCounter(&c);
|
||||
time_ticks = (retro_perf_tick_t)c.QuadPart;
|
||||
}
|
||||
#elif defined(GEKKO_NATIVE)
|
||||
time_ticks = gk_ticks();
|
||||
#elif defined(GEKKO)
|
||||
time_ticks = gettime();
|
||||
#elif !defined(__MACH__) && !defined(__FreeBSD__) && (defined(_XBOX360) || defined(__powerpc__) || defined(__ppc__) || defined(__POWERPC__) || defined(__PSL1GHT__) || defined(__PPC64__) || defined(__powerpc64__))
|
||||
time_ticks = __mftb();
|
||||
#elif (defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK > 0) || defined(__QNX__) || defined(ANDROID)
|
||||
struct timespec tv;
|
||||
if (ra_clock_gettime(CLOCK_MONOTONIC, &tv) == 0)
|
||||
time_ticks = (retro_perf_tick_t)tv.tv_sec * 1000000000 +
|
||||
(retro_perf_tick_t)tv.tv_nsec;
|
||||
|
||||
#elif defined(__GNUC__) && defined(__i386__) || defined(__i486__) || defined(__i686__) || defined(_M_X64) || defined(_M_AMD64)
|
||||
__asm__ volatile ("rdtsc" : "=A" (time_ticks));
|
||||
#elif defined(__GNUC__) && defined(__x86_64__) || defined(_M_IX86)
|
||||
unsigned a, d;
|
||||
__asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
|
||||
time_ticks = (retro_perf_tick_t)a | ((retro_perf_tick_t)d << 32);
|
||||
#elif defined(__ARM_ARCH_6__)
|
||||
__asm__ volatile( "mrc p15, 0, %0, c9, c13, 0" : "=r"(time_ticks) );
|
||||
#elif defined(__aarch64__)
|
||||
__asm__ volatile( "mrs %0, cntvct_el0" : "=r"(time_ticks) );
|
||||
{
|
||||
struct timespec tv;
|
||||
if (ra_clock_gettime(CLOCK_MONOTONIC, &tv) == 0)
|
||||
time_ticks = (retro_perf_tick_t)tv.tv_sec * 1000000000 +
|
||||
(retro_perf_tick_t)tv.tv_nsec;
|
||||
}
|
||||
#elif defined(PSP) || defined(VITA)
|
||||
time_ticks = sceKernelGetSystemTimeWide();
|
||||
#elif defined(ORBIS)
|
||||
@@ -243,6 +249,8 @@ retro_time_t cpu_features_get_time_usec(void)
|
||||
return sysGetSystemTime();
|
||||
#elif !defined(__PSL1GHT__) && defined(__PS3__)
|
||||
return sys_time_get_system_time();
|
||||
#elif defined(GEKKO_NATIVE)
|
||||
return gk_ticks_to_us(gk_ticks());
|
||||
#elif defined(GEKKO)
|
||||
return ticks_to_microsecs(gettime());
|
||||
#elif defined(WIIU)
|
||||
@@ -385,11 +393,30 @@ static unsigned char check_arm_cpu_feature(const char* feature)
|
||||
|
||||
while (fgets(line, sizeof(line), fp))
|
||||
{
|
||||
const char *list;
|
||||
const char *p;
|
||||
size_t flen;
|
||||
|
||||
if (strncmp(line, "Features\t: ", 11))
|
||||
continue;
|
||||
|
||||
if (strstr(line + 11, feature))
|
||||
status = 1;
|
||||
/* Feature names are space separated and several are prefixes of
|
||||
* others - 'aes' sits inside 'sveaes', 'sha3' inside 'svesha3',
|
||||
* 'asimd' inside 'asimddp' - so compare whole tokens. */
|
||||
flen = strlen(feature);
|
||||
list = line + 11;
|
||||
|
||||
for (p = list; (p = strstr(p, feature)); p += flen)
|
||||
{
|
||||
char after = p[flen];
|
||||
char before = (p == list) ? ' ' : p[-1];
|
||||
if ( (before == ' ')
|
||||
&& (after == ' ' || after == '\n' || after == '\0'))
|
||||
{
|
||||
status = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -520,12 +547,471 @@ static void cpulist_read_from(CpuList* list, const char* filename)
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
||||
/* GetLogicalProcessorInformation arrived in XP SP3 and Server 2003, so
|
||||
* it is resolved rather than linked: on Win9x, NT 4 and 2000 the export
|
||||
* is absent, GetProcAddress reports so, and the caller falls back to
|
||||
* the plain processor count. The Ex form carrying EfficiencyClass is
|
||||
* Windows 7 and later and would rank efficiency cores, which this does
|
||||
* not attempt. */
|
||||
typedef BOOL (WINAPI *cpu_glpi_t)(
|
||||
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD);
|
||||
|
||||
/* Caller frees. Returns NULL, leaving *count untouched, wherever the
|
||||
* export or the query is unavailable. */
|
||||
static SYSTEM_LOGICAL_PROCESSOR_INFORMATION *cpu_win32_slpi(DWORD *count)
|
||||
{
|
||||
SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buf = NULL;
|
||||
DWORD _len = 0;
|
||||
HMODULE k32 = GetModuleHandleA("kernel32.dll");
|
||||
cpu_glpi_t glpi = k32 ? (cpu_glpi_t)(void (*)(void))
|
||||
GetProcAddress(k32, "GetLogicalProcessorInformation") : NULL;
|
||||
|
||||
if (!glpi)
|
||||
return NULL;
|
||||
|
||||
/* The first call is expected to fail, and reports the size wanted. */
|
||||
if (glpi(NULL, &_len))
|
||||
return NULL;
|
||||
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || _len == 0)
|
||||
return NULL;
|
||||
if (_len % sizeof(*buf))
|
||||
return NULL;
|
||||
|
||||
if (!(buf = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION *)malloc(_len)))
|
||||
return NULL;
|
||||
|
||||
if (!glpi(buf, &_len))
|
||||
{
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*count = _len / (DWORD)sizeof(*buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Size in KiB of the level-3 cache whose ProcessorMask covers the
|
||||
* processor, from the same GetLogicalProcessorInformation records
|
||||
* (RelationCache entries carry a level, a size and the mask of
|
||||
* processors behind them); 0 where none does. */
|
||||
static unsigned cpu_win32_llc_kib(
|
||||
const SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buf, DWORD count,
|
||||
unsigned bit)
|
||||
{
|
||||
DWORD i;
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
if (buf[i].Relationship != RelationCache)
|
||||
continue;
|
||||
if (buf[i].Cache.Level != 3)
|
||||
continue;
|
||||
if (buf[i].ProcessorMask & (((ULONG_PTR)1) << bit))
|
||||
return (unsigned)(buf[i].Cache.Size / 1024);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__linux__)
|
||||
/* Number of distinct SMT sibling groups under
|
||||
* /sys/devices/system/cpu, which is one per physical core. The list a
|
||||
* sibling file holds names every processor sharing that core, so the
|
||||
* lowest id in it identifies the group and counting distinct ones
|
||||
* counts cores. */
|
||||
static unsigned linux_core_amount_physical(unsigned logical)
|
||||
{
|
||||
char path[512];
|
||||
char line[64];
|
||||
unsigned seen[128];
|
||||
unsigned n_seen = 0;
|
||||
unsigned found = 0;
|
||||
unsigned i;
|
||||
|
||||
/* One entry per core, so a machine wider than the table is left to
|
||||
* the logical count rather than answered wrongly. */
|
||||
if (logical > (unsigned)(sizeof(seen) / sizeof(seen[0])))
|
||||
return logical;
|
||||
|
||||
for (i = 0; i < 256 && found < logical; i++)
|
||||
{
|
||||
FILE *fp;
|
||||
unsigned first;
|
||||
unsigned j;
|
||||
|
||||
snprintf(path, sizeof(path),
|
||||
CPU_CLASS_SYSFS "/cpu%u/topology/thread_siblings_list", i);
|
||||
|
||||
if (!(fp = fopen(path, "r")))
|
||||
continue;
|
||||
|
||||
line[0] = '\0';
|
||||
if (!fgets(line, sizeof(line), fp))
|
||||
{
|
||||
fclose(fp);
|
||||
continue;
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
found++;
|
||||
|
||||
if (sscanf(line, "%u", &first) != 1)
|
||||
continue;
|
||||
|
||||
for (j = 0; j < n_seen; j++)
|
||||
if (seen[j] == first)
|
||||
break;
|
||||
|
||||
if (j == n_seen && n_seen < (unsigned)(sizeof(seen) / sizeof(seen[0])))
|
||||
seen[n_seen++] = first;
|
||||
}
|
||||
|
||||
/* Nothing readable, so the kernel is not publishing topology here. */
|
||||
if (n_seen == 0)
|
||||
return logical;
|
||||
|
||||
return n_seen;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__linux__)
|
||||
/* First unsigned in a one-line sysfs file, or @fallback where the file
|
||||
* is missing or unreadable. */
|
||||
static unsigned sysfs_read_uint(const char *path, unsigned fallback)
|
||||
{
|
||||
char line[64];
|
||||
unsigned val;
|
||||
FILE *fp = fopen(path, "r");
|
||||
|
||||
if (!fp)
|
||||
return fallback;
|
||||
|
||||
line[0] = '\0';
|
||||
if (!fgets(line, sizeof(line), fp))
|
||||
{
|
||||
fclose(fp);
|
||||
return fallback;
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
if (sscanf(line, "%u", &val) != 1)
|
||||
return fallback;
|
||||
return val;
|
||||
}
|
||||
|
||||
/* Size of the last-level cache the processor sits behind, in KiB,
|
||||
* from "<cpu>/cache/index3/size" (a figure with a K or M suffix); 0
|
||||
* where the kernel publishes none. What separates the two CCDs of an
|
||||
* X3D part, which are one class and near enough one clock. */
|
||||
static unsigned sysfs_read_llc_kib(unsigned cpu)
|
||||
{
|
||||
char path[512];
|
||||
char line[64];
|
||||
unsigned val = 0;
|
||||
char unit = 0;
|
||||
FILE *fp;
|
||||
|
||||
snprintf(path, sizeof(path), CPU_CLASS_SYSFS "/cpu%u/cache/index3/size", cpu);
|
||||
if (!(fp = fopen(path, "r")))
|
||||
return 0;
|
||||
line[0] = '\0';
|
||||
if (!fgets(line, sizeof(line), fp))
|
||||
line[0] = '\0';
|
||||
fclose(fp);
|
||||
if (sscanf(line, "%u%c", &val, &unit) < 1)
|
||||
return 0;
|
||||
if (unit == 'M' || unit == 'm')
|
||||
return val * 1024;
|
||||
if (unit == 'G' || unit == 'g')
|
||||
return val * 1024 * 1024;
|
||||
return val;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || defined(__linux__)
|
||||
struct cpu_proc_rank
|
||||
{
|
||||
unsigned cls; /* performance class from cpu_class.h, higher is faster */
|
||||
unsigned llc; /* last-level cache behind the core, KiB */
|
||||
unsigned freq; /* kHz, higher is a stronger core within its class */
|
||||
unsigned id; /* OS processor identifier */
|
||||
unsigned smt; /* 0 for the first processor on its core, else 1 */
|
||||
};
|
||||
|
||||
/* Fastest class first (the P-cores, the big cluster); within a class
|
||||
* the bigger last-level cache first, then the higher clock; a core
|
||||
* ahead of its own SMT siblings; and the identifier as the tie-break
|
||||
* so the result does not depend on the order the entries were
|
||||
* gathered in. Class comes before everything so an E-core that
|
||||
* happens to clock above a P-core sibling cannot outrank the fast
|
||||
* silicon. Cache comes before clock for the X3D parts: the V-cache
|
||||
* CCD boosts a few percent lower than the other and is the die an
|
||||
* emulator wants to be on. */
|
||||
static int cpu_proc_rank_cmp(const void *a, const void *b)
|
||||
{
|
||||
const struct cpu_proc_rank *l = (const struct cpu_proc_rank *)a;
|
||||
const struct cpu_proc_rank *r = (const struct cpu_proc_rank *)b;
|
||||
|
||||
if (l->cls != r->cls)
|
||||
return (l->cls > r->cls) ? -1 : 1;
|
||||
if (l->llc != r->llc)
|
||||
return (l->llc > r->llc) ? -1 : 1;
|
||||
if (l->freq != r->freq)
|
||||
return (l->freq > r->freq) ? -1 : 1;
|
||||
if (l->smt != r->smt)
|
||||
return (l->smt < r->smt) ? -1 : 1;
|
||||
if (l->id != r->id)
|
||||
return (l->id < r->id) ? -1 : 1;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* The order, restricted to processors whose bit is set in allowed
|
||||
* (NULL: no restriction). Split from the public function so a test
|
||||
* can rank a fixture topology under a synthetic affinity mask. */
|
||||
static size_t cpu_features_processor_order_masked(
|
||||
const unsigned char *allowed, unsigned *s, size_t len)
|
||||
{
|
||||
size_t n = 0;
|
||||
#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || defined(__linux__)
|
||||
unsigned char klass[CPU_CLASS_MAX_IDS];
|
||||
size_t n_class = cpu_class_read(klass, sizeof(klass));
|
||||
#define CPU_PROC_CLASS(id) \
|
||||
(((size_t)(id) < n_class) ? klass[(id)] : 0)
|
||||
#define CPU_PROC_ALLOWED(id) \
|
||||
(!allowed || ((size_t)(id) < CPU_CLASS_MAX_IDS && allowed[(id)]))
|
||||
#endif
|
||||
|
||||
if (!s || !len)
|
||||
return 0;
|
||||
|
||||
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
||||
{
|
||||
DWORD count = 0;
|
||||
SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buf = cpu_win32_slpi(&count);
|
||||
|
||||
if (buf)
|
||||
{
|
||||
struct cpu_proc_rank *rank;
|
||||
size_t cap = (size_t)cpu_features_get_core_amount();
|
||||
|
||||
if (cap < 1)
|
||||
cap = 1;
|
||||
if (cap > 1024)
|
||||
cap = 1024;
|
||||
|
||||
if ((rank = (struct cpu_proc_rank *)
|
||||
malloc(cap * sizeof(struct cpu_proc_rank))))
|
||||
{
|
||||
DWORD i;
|
||||
for (i = 0; i < count && n < cap; i++)
|
||||
{
|
||||
unsigned bit;
|
||||
unsigned seen = 0;
|
||||
ULONG_PTR mask = buf[i].ProcessorMask;
|
||||
|
||||
if (buf[i].Relationship != RelationProcessorCore)
|
||||
continue;
|
||||
|
||||
/* One mask bit per processor on this core, the lowest
|
||||
* of them being the one an SMT sibling shares with. */
|
||||
for (bit = 0; bit < sizeof(ULONG_PTR) * 8 && n < cap; bit++)
|
||||
{
|
||||
if (!(mask & (((ULONG_PTR)1) << bit)))
|
||||
continue;
|
||||
if (!CPU_PROC_ALLOWED(bit))
|
||||
{
|
||||
seen++;
|
||||
continue;
|
||||
}
|
||||
rank[n].cls = CPU_PROC_CLASS(bit);
|
||||
rank[n].llc = cpu_win32_llc_kib(buf, count, bit);
|
||||
rank[n].freq = 0;
|
||||
rank[n].id = bit;
|
||||
rank[n].smt = seen ? 1 : 0;
|
||||
seen++;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
if (n)
|
||||
{
|
||||
qsort(rank, n, sizeof(struct cpu_proc_rank),
|
||||
cpu_proc_rank_cmp);
|
||||
if (n > len)
|
||||
n = len;
|
||||
for (i = 0; i < (DWORD)n; i++)
|
||||
s[i] = rank[i].id;
|
||||
}
|
||||
free(rank);
|
||||
}
|
||||
free(buf);
|
||||
|
||||
if (n)
|
||||
return n;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__linux__)
|
||||
{
|
||||
struct cpu_proc_rank *rank;
|
||||
char path[512];
|
||||
unsigned i;
|
||||
/* Every processor is ranked before any is handed back: gathering
|
||||
* only the first @len of them would sort a set chosen by
|
||||
* identifier and hand back the weakest cores on a layout that
|
||||
* numbers the little cluster first. */
|
||||
size_t cap = 1024; /* every processor the kernel publishes; 16 KiB */
|
||||
|
||||
if (!(rank = (struct cpu_proc_rank *)
|
||||
malloc(cap * sizeof(struct cpu_proc_rank))))
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < 1024 && n < cap; i++)
|
||||
{
|
||||
unsigned first;
|
||||
|
||||
snprintf(path, sizeof(path),
|
||||
CPU_CLASS_SYSFS "/cpu%u/topology/thread_siblings_list", i);
|
||||
/* A processor with no sibling list is one the kernel is not
|
||||
* publishing, rather than one that shares no core. */
|
||||
first = sysfs_read_uint(path, (unsigned)-1);
|
||||
if (first == (unsigned)-1)
|
||||
continue;
|
||||
if (!CPU_PROC_ALLOWED(i))
|
||||
continue;
|
||||
|
||||
snprintf(path, sizeof(path),
|
||||
CPU_CLASS_SYSFS "/cpu%u/cpufreq/cpuinfo_max_freq", i);
|
||||
|
||||
rank[n].cls = CPU_PROC_CLASS(i);
|
||||
rank[n].llc = sysfs_read_llc_kib(i);
|
||||
rank[n].freq = sysfs_read_uint(path, 0);
|
||||
rank[n].id = i;
|
||||
rank[n].smt = (i == first) ? 0 : 1;
|
||||
n++;
|
||||
}
|
||||
|
||||
if (n)
|
||||
{
|
||||
qsort(rank, n, sizeof(struct cpu_proc_rank), cpu_proc_rank_cmp);
|
||||
if (n > len)
|
||||
n = len;
|
||||
for (i = 0; i < (unsigned)n; i++)
|
||||
s[i] = rank[i].id;
|
||||
}
|
||||
|
||||
free(rank);
|
||||
|
||||
if (n)
|
||||
return n;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* No topology to rank by, so name each processor once in order. */
|
||||
{
|
||||
unsigned amount = cpu_features_get_core_amount();
|
||||
unsigned i;
|
||||
for (i = 0; n < len && i < amount; i++)
|
||||
{
|
||||
if (allowed && (i >= CPU_CLASS_MAX_IDS || !allowed[i]))
|
||||
continue;
|
||||
s[n++] = i;
|
||||
}
|
||||
}
|
||||
#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || defined(__linux__)
|
||||
#undef CPU_PROC_CLASS
|
||||
#undef CPU_PROC_ALLOWED
|
||||
#endif
|
||||
return n;
|
||||
}
|
||||
|
||||
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__)
|
||||
{
|
||||
/* The processors this thread may run on: a pin from a parent
|
||||
* process, a container or the user is a boundary, not something
|
||||
* to hand back as a target. */
|
||||
unsigned long bits[CPU_CLASS_MAX_IDS / (8 * sizeof(unsigned long))];
|
||||
memset(bits, 0, sizeof(bits));
|
||||
if (syscall(__NR_sched_getaffinity, 0, sizeof(bits), bits) > 0)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < CPU_CLASS_MAX_IDS; i++)
|
||||
allowed[i] = (unsigned char)((bits[i / (8 * sizeof(unsigned long))]
|
||||
>> (i % (8 * sizeof(unsigned long)))) & 1ul);
|
||||
mask = allowed;
|
||||
}
|
||||
}
|
||||
#elif defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
||||
{
|
||||
DWORD_PTR proc = 0, sys = 0;
|
||||
if (GetProcessAffinityMask(GetCurrentProcess(), &proc, &sys) && proc)
|
||||
{
|
||||
size_t i;
|
||||
memset(allowed, 0, sizeof(allowed));
|
||||
for (i = 0; i < sizeof(DWORD_PTR) * 8; i++)
|
||||
allowed[i] = (unsigned char)((proc >> i) & 1);
|
||||
mask = allowed;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return cpu_features_processor_order_masked(mask, s, len);
|
||||
}
|
||||
|
||||
unsigned cpu_features_get_core_amount_physical(void)
|
||||
{
|
||||
unsigned logical = cpu_features_get_core_amount();
|
||||
|
||||
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
||||
{
|
||||
DWORD count = 0;
|
||||
SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buf = cpu_win32_slpi(&count);
|
||||
|
||||
if (buf)
|
||||
{
|
||||
DWORD i;
|
||||
unsigned cores = 0;
|
||||
for (i = 0; i < count; i++)
|
||||
if (buf[i].Relationship == RelationProcessorCore)
|
||||
cores++;
|
||||
free(buf);
|
||||
if (cores > 0)
|
||||
return cores;
|
||||
}
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
{
|
||||
/* Darwin publishes both counts, so the physical one is a read
|
||||
* rather than a derivation. */
|
||||
int val = 0;
|
||||
size_t _len = sizeof(val);
|
||||
if ( sysctlbyname("hw.physicalcpu", &val, &_len, NULL, 0) == 0
|
||||
&& val > 0)
|
||||
return (unsigned)val;
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
return linux_core_amount_physical(logical);
|
||||
#endif
|
||||
|
||||
/* Every other target either has no SMT to discount or publishes no
|
||||
* way to tell, and the thread count is the safe answer in both
|
||||
* cases. */
|
||||
return logical;
|
||||
}
|
||||
|
||||
unsigned cpu_features_get_core_amount(void)
|
||||
{
|
||||
#if defined(_WIN32) && !defined(_XBOX)
|
||||
/* Win32 */
|
||||
SYSTEM_INFO sysinfo;
|
||||
#if defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
|
||||
#if defined(__WINRT__) || (defined(WINAPI_FAMILY) && defined(WINAPI_FAMILY_PHONE_APP) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP)
|
||||
GetNativeSystemInfo(&sysinfo);
|
||||
#else
|
||||
GetSystemInfo(&sysinfo);
|
||||
@@ -620,9 +1106,9 @@ unsigned cpu_features_get_core_amount(void)
|
||||
#define VENDOR_INTEL_d 0x49656e69
|
||||
|
||||
#if defined(__MACH__) && defined(CPU_X86)
|
||||
/* Whole-token search of machdep.cpu.features, which is a space
|
||||
* separated list of CPUID leaf-1 feature names. */
|
||||
static bool darwin_cpu_feature_present(const char *want)
|
||||
/* Whole-token search of one of the machdep.cpu.*features sysctls, each
|
||||
* of which is a space separated list of CPUID feature names. */
|
||||
static bool darwin_cpu_feature_present(const char *key, const char *want)
|
||||
{
|
||||
char buf[1024];
|
||||
size_t len = sizeof(buf);
|
||||
@@ -630,7 +1116,7 @@ static bool darwin_cpu_feature_present(const char *want)
|
||||
const char *p;
|
||||
|
||||
buf[0] = '\0';
|
||||
if (sysctlbyname("machdep.cpu.features", buf, &len, NULL, 0) != 0)
|
||||
if (sysctlbyname(key, buf, &len, NULL, 0) != 0)
|
||||
return false;
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
@@ -650,6 +1136,9 @@ static uint64_t cpu_features_probe(void)
|
||||
uint64_t cpu = 0;
|
||||
#if defined(CPU_X86) && !defined(__MACH__)
|
||||
int vendor_is_intel = 0;
|
||||
/* Set once the OS is known to preserve YMM state, which AVX, FMA3
|
||||
* and FMA4 all depend on. */
|
||||
int ymm_state = 0;
|
||||
const int avx_flags = (1 << 27) | (1 << 28);
|
||||
#endif
|
||||
#if defined(__MACH__)
|
||||
@@ -703,6 +1192,10 @@ static uint64_t cpu_features_probe(void)
|
||||
cpu |= RETRO_SIMD_AVX;
|
||||
_val = 0;
|
||||
_len = sizeof(_val);
|
||||
if (sysctlbyname("hw.optional.fma", &_val, &_len, NULL, 0) == 0 && _val)
|
||||
cpu |= RETRO_SIMD_FMA3;
|
||||
_val = 0;
|
||||
_len = sizeof(_val);
|
||||
if (sysctlbyname("hw.optional.avx2_0", &_val, &_len, NULL, 0) == 0 && _val)
|
||||
cpu |= RETRO_SIMD_AVX2;
|
||||
_val = 0;
|
||||
@@ -717,18 +1210,50 @@ static uint64_t cpu_features_probe(void)
|
||||
* CPUID leaf-1 feature-name list instead. Matching is on whole
|
||||
* space-separated tokens: a plain strstr() would also fire on a
|
||||
* hypothetical future "PCLMULQDQ2". */
|
||||
if (darwin_cpu_feature_present("PCLMULQDQ"))
|
||||
if (darwin_cpu_feature_present("machdep.cpu.features", "PCLMULQDQ"))
|
||||
cpu |= RETRO_SIMD_PCLMUL;
|
||||
/* SHA-NI lives in the leaf-7 list rather than the leaf-1 one. */
|
||||
if (darwin_cpu_feature_present("machdep.cpu.leaf7_features", "SHA"))
|
||||
cpu |= RETRO_SIMD_SHA1 | RETRO_SIMD_SHA256;
|
||||
#else
|
||||
_val = 0;
|
||||
_len = sizeof(_val);
|
||||
/* Older key first; newer systems also carry the FEAT_ spelling. */
|
||||
if ( (sysctlbyname("hw.optional.armv8_crc32", &_val, &_len, NULL, 0) == 0
|
||||
&& _val)
|
||||
|| (_val = 0, _len = sizeof(_val),
|
||||
sysctlbyname("hw.optional.arm.FEAT_CRC32", &_val, &_len, NULL, 0) == 0
|
||||
&& _val))
|
||||
/* Older key first; newer systems also carry the FEAT_ spelling.
|
||||
* Written out rather than folded into one condition with a comma
|
||||
* operator: the second query needs the buffer and its length reset
|
||||
* first, and doing that inside a short-circuit || is both a warning
|
||||
* and a thing to read twice. */
|
||||
if (sysctlbyname("hw.optional.armv8_crc32", &_val, &_len, NULL, 0) == 0
|
||||
&& _val)
|
||||
cpu |= RETRO_SIMD_CRC32;
|
||||
else
|
||||
{
|
||||
_val = 0;
|
||||
_len = sizeof(_val);
|
||||
if (sysctlbyname("hw.optional.arm.FEAT_CRC32", &_val, &_len, NULL, 0) == 0
|
||||
&& _val)
|
||||
cpu |= RETRO_SIMD_CRC32;
|
||||
}
|
||||
_val = 0;
|
||||
_len = sizeof(_val);
|
||||
if (sysctlbyname("hw.optional.arm.FEAT_AES", &_val, &_len, NULL, 0) == 0
|
||||
&& _val)
|
||||
cpu |= RETRO_SIMD_AES;
|
||||
_val = 0;
|
||||
_len = sizeof(_val);
|
||||
if (sysctlbyname("hw.optional.arm.FEAT_SHA512", &_val, &_len, NULL, 0) == 0
|
||||
&& _val)
|
||||
cpu |= RETRO_SIMD_SHA512;
|
||||
_val = 0;
|
||||
_len = sizeof(_val);
|
||||
if (sysctlbyname("hw.optional.arm.FEAT_SHA1", &_val, &_len, NULL, 0) == 0
|
||||
&& _val)
|
||||
cpu |= RETRO_SIMD_SHA1;
|
||||
_val = 0;
|
||||
_len = sizeof(_val);
|
||||
if (sysctlbyname("hw.optional.arm.FEAT_SHA256", &_val, &_len, NULL, 0) == 0
|
||||
&& _val)
|
||||
cpu |= RETRO_SIMD_SHA256;
|
||||
_val = 0;
|
||||
_len = sizeof(_val);
|
||||
if (sysctlbyname("hw.optional.neon", &_val, &_len, NULL, 0) == 0 && _val)
|
||||
@@ -815,7 +1340,16 @@ static uint64_t cpu_features_probe(void)
|
||||
* AVX CPU support (guaranteed to have at least i686). */
|
||||
if (((flags[2] & avx_flags) == avx_flags)
|
||||
&& ((xgetbv_x86(0) & 0x6) == 0x6))
|
||||
cpu |= RETRO_SIMD_AVX;
|
||||
{
|
||||
ymm_state = 1;
|
||||
cpu |= RETRO_SIMD_AVX;
|
||||
}
|
||||
|
||||
/* FMA3 accumulates in YMM, so it answers only where the OS keeps
|
||||
* that state. Leaf 1 ECX is still in flags here; the extended leaf
|
||||
* carrying FMA4 is read further down. */
|
||||
if (ymm_state && (flags[2] & (1 << 12)))
|
||||
cpu |= RETRO_SIMD_FMA3;
|
||||
|
||||
if (max_flag >= 7)
|
||||
{
|
||||
@@ -854,6 +1388,10 @@ static uint64_t cpu_features_probe(void)
|
||||
if (flags7[1] & (1 << 5))
|
||||
cpu |= RETRO_SIMD_AVX2;
|
||||
|
||||
/* SHA-NI is one bit covering both digests. */
|
||||
if (flags7[1] & (1 << 29))
|
||||
cpu |= RETRO_SIMD_SHA1 | RETRO_SIMD_SHA256;
|
||||
|
||||
/* AVX-512 Foundation detection.
|
||||
* Requires CPUID leaf 7 sub-leaf 0 EBX bit 16 (AVX-512F),
|
||||
* and OS support for saving ZMM state:
|
||||
@@ -861,6 +1399,46 @@ static uint64_t cpu_features_probe(void)
|
||||
if ((flags7[1] & (1 << 16))
|
||||
&& ((xgetbv_x86(0) & 0xe6) == 0xe6))
|
||||
cpu |= RETRO_SIMD_AVX512;
|
||||
|
||||
/* Leaf 7 EAX carries the highest sub-leaf the CPU implements, so
|
||||
* consult it before asking for sub-leaf 1: an out of range
|
||||
* sub-leaf returns zeroes on current parts but is not documented
|
||||
* to. */
|
||||
if (flags7[0] >= 1)
|
||||
{
|
||||
int32_t flags71[4];
|
||||
#if defined(__GNUC__)
|
||||
__asm__ volatile (
|
||||
"mov %%" REG_b ", %%" REG_S "\n"
|
||||
"cpuid\n"
|
||||
"xchg %%" REG_b ", %%" REG_S "\n"
|
||||
: "=a"(flags71[0]), "=S"(flags71[1]), "=c"(flags71[2]), "=d"(flags71[3])
|
||||
: "a"(7), "c"(1));
|
||||
#elif defined(_MSC_VER) && INT_MAX == 2147483647
|
||||
#if _MSC_VER >= 1600
|
||||
__cpuidex((int*)flags71, 7, 1);
|
||||
#else
|
||||
{
|
||||
int *p = (int*)flags71;
|
||||
__asm {
|
||||
mov eax, 7
|
||||
mov ecx, 1
|
||||
cpuid
|
||||
mov esi, p
|
||||
mov [esi], eax
|
||||
mov [esi + 4], ebx
|
||||
mov [esi + 8], ecx
|
||||
mov [esi + 12], edx
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
memset(flags71, 0, sizeof(flags71));
|
||||
#endif
|
||||
|
||||
if (flags71[0] & (1 << 0))
|
||||
cpu |= RETRO_SIMD_SHA512;
|
||||
}
|
||||
}
|
||||
|
||||
x86_cpuid(0x80000000, flags);
|
||||
@@ -876,6 +1454,10 @@ static uint64_t cpu_features_probe(void)
|
||||
* does not report this bit; consumers there fall back accordingly. */
|
||||
if (flags[2] & (1 << 5))
|
||||
cpu |= RETRO_SIMD_LZCNT;
|
||||
/* FMA4, an AMD encoding that no Zen part implements, under the
|
||||
* same OS state as FMA3. */
|
||||
if (ymm_state && (flags[2] & (1 << 16)))
|
||||
cpu |= RETRO_SIMD_FMA4;
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
if (check_arm_cpu_feature("neon"))
|
||||
@@ -892,10 +1474,25 @@ static uint64_t cpu_features_probe(void)
|
||||
if (check_arm_cpu_feature("vfpv4"))
|
||||
cpu |= RETRO_SIMD_VFPV4;
|
||||
|
||||
/* Part of the optional Cryptographic Extension, which an
|
||||
* implementation may leave out or hold in reset, so a 64-bit ARM
|
||||
* CPU does not imply it. */
|
||||
if (check_arm_cpu_feature("aes"))
|
||||
cpu |= RETRO_SIMD_AES;
|
||||
|
||||
/* aarch64 lists it as "crc32" in the Features: line. */
|
||||
if (check_arm_cpu_feature("crc32"))
|
||||
cpu |= RETRO_SIMD_CRC32;
|
||||
|
||||
if (check_arm_cpu_feature("sha512"))
|
||||
cpu |= RETRO_SIMD_SHA512;
|
||||
|
||||
if (check_arm_cpu_feature("sha1"))
|
||||
cpu |= RETRO_SIMD_SHA1;
|
||||
|
||||
if (check_arm_cpu_feature("sha2"))
|
||||
cpu |= RETRO_SIMD_SHA256;
|
||||
|
||||
if (check_arm_cpu_feature("asimd"))
|
||||
{
|
||||
cpu |= RETRO_SIMD_ASIMD;
|
||||
@@ -1121,3 +1718,69 @@ end:
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Sleep until cpu_features_get_time_usec() reads at least @deadline.
|
||||
* It lives here, beside the clock it is measured on, and nowhere a
|
||||
* launcher compiles: the salamanders build rtime.c for localtime alone
|
||||
* and never link this file.
|
||||
*
|
||||
* Absolute where the platform offers it, so the time between reading
|
||||
* the clock and entering the kernel, and any early or interrupted
|
||||
* wake, are not added to when the caller comes back. Never early
|
||||
* against that clock on any backend; late as every sleep may be, and
|
||||
* a caller that needs the instant itself sleeps short and spins the
|
||||
* rest, as the frame limiter does with its measured margin. */
|
||||
#if (defined(__linux__) || defined(ANDROID)) && !defined(__MACH__)
|
||||
/* The exact tool: the clock above is clock_gettime(CLOCK_MONOTONIC)
|
||||
* here, and clock_nanosleep() takes an absolute deadline on the same
|
||||
* clock. EINTR re-arms against the unchanged deadline by definition
|
||||
* of TIMER_ABSTIME. */
|
||||
void retro_sleep_until_us(retro_time_t deadline)
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = (time_t)(deadline / 1000000);
|
||||
ts.tv_nsec = (long)((deadline % 1000000) * 1000);
|
||||
while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL) != 0)
|
||||
;
|
||||
}
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
#include <mach/mach_time.h>
|
||||
/* mach_wait_until() is the absolute wait, on the Mach clock; the
|
||||
* public microsecond clock above is CLOCK_MONOTONIC, which Darwin
|
||||
* derives from the same hardware ticks. The two are bridged with one
|
||||
* paired read - nanoseconds apart - and unlike a relative sleep the
|
||||
* arming is still absolute in Mach terms, so an early wake or the
|
||||
* kernel's leeway re-arms nothing and accumulates nothing. */
|
||||
void retro_sleep_until_us(retro_time_t deadline)
|
||||
{
|
||||
static mach_timebase_info_data_t tb;
|
||||
uint64_t ticks;
|
||||
retro_time_t now = cpu_features_get_time_usec();
|
||||
uint64_t mach_now = mach_absolute_time();
|
||||
if (deadline <= now)
|
||||
return;
|
||||
/* A constant; a racing first read fills in the same values. */
|
||||
if (!tb.denom)
|
||||
mach_timebase_info(&tb);
|
||||
ticks = (uint64_t)(deadline - now) * 1000 * tb.denom / tb.numer;
|
||||
mach_wait_until(mach_now + ticks);
|
||||
}
|
||||
#else
|
||||
/* Windows and everything else: re-arm the platform's best relative
|
||||
* wait - retro_sleep_us from retro_timers.h, included above on every
|
||||
* platform - against the deadline until the clock agrees. On desktop
|
||||
* Windows that wait is rtime.c's per-thread high-resolution timer, so
|
||||
* each lap is microsecond-grained; on a platform whose sleep rounds
|
||||
* up the loop ends one lap past the deadline, no worse than the
|
||||
* relative call was. */
|
||||
void retro_sleep_until_us(retro_time_t deadline)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
retro_time_t now = cpu_features_get_time_usec();
|
||||
if (now >= deadline)
|
||||
return;
|
||||
retro_sleep_us((unsigned)(deadline - now));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,890 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (archive_file.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <compat/strl.h>
|
||||
#include <file/archive_file.h>
|
||||
#include <file/file_path.h>
|
||||
#include <streams/file_stream.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <lists/string_list.h>
|
||||
#include <string/stdstring.h>
|
||||
|
||||
#ifdef HAVE_MMAP
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
static int file_archive_get_file_list_cb(
|
||||
const char *path,
|
||||
const char *valid_exts,
|
||||
const uint8_t *cdata,
|
||||
unsigned cmode,
|
||||
uint32_t csize,
|
||||
uint32_t size,
|
||||
uint32_t checksum,
|
||||
struct archive_extract_userdata *userdata)
|
||||
{
|
||||
union string_list_elem_attr attr;
|
||||
|
||||
if (valid_exts)
|
||||
{
|
||||
size_t _len = strlen(path);
|
||||
|
||||
/* Reject empty names before the strlen-1 read below: a malformed
|
||||
* central directory can carry a zero-length name, which used to
|
||||
* index path[SIZE_MAX]. */
|
||||
if (_len == 0)
|
||||
return 1;
|
||||
|
||||
/* Checks if this entry is a directory or a file.
|
||||
* Skip if directory. */
|
||||
if (path[_len - 1] == '/' || path[_len - 1] == '\\')
|
||||
return 1;
|
||||
|
||||
/* The extension list is split once per walk by the caller and
|
||||
* carried in userdata->ext - the same field, with the same
|
||||
* meaning, that file_archive_extract_cb already reads. It used
|
||||
* to be split here instead, which meant one
|
||||
* string_list_initialize / string_split_noalloc /
|
||||
* string_list_deinitialize cycle per archive member: a pair of
|
||||
* heap allocations per entry, for a list that cannot change
|
||||
* during the walk.
|
||||
*
|
||||
* Gate the extension test on the split list but keep the
|
||||
* directory skip above gated on valid_exts, so that a split
|
||||
* which failed to allocate still skips directories and still
|
||||
* falls through to append rather than rejecting everything -
|
||||
* exactly what the old inline code did on that path. */
|
||||
if (userdata->ext)
|
||||
{
|
||||
const char *file_ext = path_get_extension(path);
|
||||
|
||||
if (!file_ext)
|
||||
return 1;
|
||||
|
||||
if (!string_list_find_elem_prefix(userdata->ext, ".", file_ext))
|
||||
return -1; /* keep iterating */
|
||||
}
|
||||
}
|
||||
|
||||
attr.i = RARCH_COMPRESSED_FILE_IN_ARCHIVE;
|
||||
return string_list_append(userdata->list, path, attr);
|
||||
}
|
||||
|
||||
static int file_archive_extract_cb(const char *name, const char *valid_exts,
|
||||
const uint8_t *cdata,
|
||||
unsigned cmode, uint32_t csize, uint32_t size,
|
||||
uint32_t checksum, struct archive_extract_userdata *userdata)
|
||||
{
|
||||
const char *ext = path_get_extension(name);
|
||||
|
||||
/* Extract first file that matches our list. */
|
||||
if (ext && string_list_find_elem(userdata->ext, ext))
|
||||
{
|
||||
char new_path[PATH_MAX_LENGTH];
|
||||
const char *delim;
|
||||
|
||||
if ((delim = path_get_archive_delim(userdata->archive_path)))
|
||||
{
|
||||
if (!string_is_equal_noncase(
|
||||
userdata->current_file_path, delim + 1))
|
||||
return 1; /* keep searching for the right file */
|
||||
}
|
||||
|
||||
if (userdata->extraction_directory)
|
||||
fill_pathname_join_special(new_path, userdata->extraction_directory,
|
||||
path_basename(name), sizeof(new_path));
|
||||
else
|
||||
fill_pathname_resolve_relative(new_path, userdata->archive_path,
|
||||
path_basename(name), sizeof(new_path));
|
||||
|
||||
/* Start it rather than driving it to completion here. If it
|
||||
* parks, the iterate loop resumes it on later ticks; the
|
||||
* bookkeeping below still has to happen now, because this
|
||||
* callback will not be called again for this member. */
|
||||
{
|
||||
int pret = file_archive_perform_mode_start(new_path,
|
||||
valid_exts, cdata, cmode, csize, size,
|
||||
checksum, userdata);
|
||||
|
||||
if (pret != -1)
|
||||
{
|
||||
userdata->found_file = true;
|
||||
userdata->first_extracted_file_path = strdup(new_path);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int file_archive_parse_file_init(file_archive_transfer_t *state,
|
||||
const char *file)
|
||||
{
|
||||
char path[PATH_MAX_LENGTH];
|
||||
char *last = NULL;
|
||||
|
||||
strlcpy(path, file, sizeof(path));
|
||||
|
||||
if ((last = (char*)path_get_archive_delim(path)))
|
||||
*last = '\0';
|
||||
|
||||
if (!(state->backend = file_archive_get_file_backend(path)))
|
||||
return -1;
|
||||
|
||||
/* Failed to open archive. */
|
||||
if (!(state->archive_file = filestream_open(path,
|
||||
RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
|
||||
return -1;
|
||||
|
||||
state->archive_size = filestream_get_size(state->archive_file);
|
||||
|
||||
#ifdef HAVE_MMAP
|
||||
/* mmap needs a real host fd. Skip VFS URL schemes (smb://, cdrom://,
|
||||
* saf://, ...) where POSIX open() cannot work, and require fd >= 0 —
|
||||
* open() returns -1 on failure, which is truthy and previously slipped
|
||||
* into mmap(). */
|
||||
if ( state->archive_size > 0
|
||||
&& state->archive_size <= (256 * 1024 * 1024)
|
||||
&& !strstr(path, "://"))
|
||||
{
|
||||
state->archive_mmap_fd = open(path, O_RDONLY);
|
||||
if (state->archive_mmap_fd >= 0)
|
||||
{
|
||||
state->archive_mmap_data = (uint8_t*)mmap(NULL,
|
||||
(size_t)state->archive_size,
|
||||
PROT_READ, MAP_SHARED, state->archive_mmap_fd, 0);
|
||||
|
||||
if (state->archive_mmap_data == (uint8_t*)MAP_FAILED)
|
||||
{
|
||||
close(state->archive_mmap_fd);
|
||||
state->archive_mmap_fd = 0;
|
||||
state->archive_mmap_data = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
state->step_current = 0;
|
||||
state->step_total = 0;
|
||||
|
||||
return state->backend->archive_parse_file_init(state, path);
|
||||
}
|
||||
|
||||
void file_archive_parse_file_iterate_stop(file_archive_transfer_t *state)
|
||||
{
|
||||
if (!state || !state->archive_file)
|
||||
return;
|
||||
|
||||
state->type = ARCHIVE_TRANSFER_DEINIT;
|
||||
file_archive_parse_file_iterate(state, NULL, NULL, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
int file_archive_parse_file_iterate(
|
||||
file_archive_transfer_t *state,
|
||||
bool *returnerr,
|
||||
const char *file,
|
||||
const char *valid_exts,
|
||||
file_archive_file_cb file_cb,
|
||||
struct archive_extract_userdata *userdata)
|
||||
{
|
||||
if (!state)
|
||||
return -1;
|
||||
|
||||
switch (state->type)
|
||||
{
|
||||
case ARCHIVE_TRANSFER_NONE:
|
||||
break;
|
||||
case ARCHIVE_TRANSFER_INIT:
|
||||
if (file_archive_parse_file_init(state, file) == 0)
|
||||
{
|
||||
if (userdata)
|
||||
{
|
||||
userdata->transfer = state;
|
||||
strlcpy(userdata->archive_path, file,
|
||||
sizeof(userdata->archive_path));
|
||||
}
|
||||
state->type = ARCHIVE_TRANSFER_ITERATE;
|
||||
}
|
||||
else
|
||||
{
|
||||
state->type = ARCHIVE_TRANSFER_DEINIT_ERROR;
|
||||
goto deinit_error;
|
||||
}
|
||||
break;
|
||||
case ARCHIVE_TRANSFER_ITERATE:
|
||||
if (state->backend)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Finish a member parked by an earlier tick before looking
|
||||
* at the next entry. This is what turns the backends'
|
||||
* "not finished" into a real yield: one slice per call,
|
||||
* then straight back to the caller, which for the
|
||||
* decompress task is once per frame. */
|
||||
if (state->pending_active)
|
||||
{
|
||||
int pret = file_archive_perform_mode_step(state);
|
||||
|
||||
if (pret == 0)
|
||||
return 0; /* more to do; resume next tick */
|
||||
|
||||
if (pret == -1)
|
||||
{
|
||||
state->pending_stop = false;
|
||||
state->pending_counted = false;
|
||||
state->type = ARCHIVE_TRANSFER_DEINIT_ERROR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Finished. Count it, then either honour a stop the
|
||||
* scan asked for while this was parked, or come back
|
||||
* for the next entry on the following tick so that one
|
||||
* call stays one unit of work. */
|
||||
if (!state->pending_counted)
|
||||
state->step_current++;
|
||||
state->pending_counted = false;
|
||||
if (state->pending_stop)
|
||||
{
|
||||
state->pending_stop = false;
|
||||
state->type = ARCHIVE_TRANSFER_DEINIT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = state->backend->archive_parse_file_iterate_step(
|
||||
state->context, valid_exts, userdata, file_cb);
|
||||
|
||||
if (ret == 1)
|
||||
{
|
||||
state->step_current++; /* found another file */
|
||||
/* If that callback parked a decode, the drain branch
|
||||
* must not count it a second time. */
|
||||
if (state->pending_active)
|
||||
state->pending_counted = true;
|
||||
}
|
||||
if (ret != 1)
|
||||
{
|
||||
/* A callback that stopped the scan may have parked a
|
||||
* member on its way out (extract_cb does exactly that:
|
||||
* it starts the decode and returns 0 to stop
|
||||
* searching). Tearing down now would drop it, so stay
|
||||
* in ITERATE until the pending work has drained; the
|
||||
* branch above finishes it and then falls through to
|
||||
* here on a later tick with nothing pending. */
|
||||
if (state->pending_active)
|
||||
state->pending_stop = true;
|
||||
else
|
||||
state->type = ARCHIVE_TRANSFER_DEINIT;
|
||||
}
|
||||
if (ret == -1)
|
||||
state->type = ARCHIVE_TRANSFER_DEINIT_ERROR;
|
||||
|
||||
/* early return to prevent deinit from never firing */
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
case ARCHIVE_TRANSFER_DEINIT_ERROR:
|
||||
deinit_error:
|
||||
if (returnerr)
|
||||
*returnerr = false;
|
||||
case ARCHIVE_TRANSFER_DEINIT:
|
||||
if (state->context)
|
||||
{
|
||||
if (state->backend->archive_parse_file_free)
|
||||
state->backend->archive_parse_file_free(state->context);
|
||||
state->context = NULL;
|
||||
}
|
||||
|
||||
if (state->archive_file)
|
||||
{
|
||||
filestream_close(state->archive_file);
|
||||
state->archive_file = NULL;
|
||||
}
|
||||
|
||||
#ifdef HAVE_MMAP
|
||||
if (state->archive_mmap_data)
|
||||
{
|
||||
munmap(state->archive_mmap_data, (size_t)state->archive_size);
|
||||
close(state->archive_mmap_fd);
|
||||
state->archive_mmap_fd = 0;
|
||||
state->archive_mmap_data = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (userdata)
|
||||
userdata->transfer = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( state->type == ARCHIVE_TRANSFER_DEINIT ||
|
||||
state->type == ARCHIVE_TRANSFER_DEINIT_ERROR)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* file_archive_walk:
|
||||
* @file : filename path of archive
|
||||
* @valid_exts : Valid extensions of archive to be parsed.
|
||||
* If NULL, allow all.
|
||||
* @file_cb : file_cb function pointer
|
||||
* @userdata : userdata to pass to file_cb function pointer.
|
||||
*
|
||||
* Low-level file parsing. Enumerates over all files and calls
|
||||
* file_cb with userdata.
|
||||
*
|
||||
* Returns: true (1) on success, otherwise false (0).
|
||||
**/
|
||||
static bool file_archive_walk(const char *file, const char *valid_exts,
|
||||
file_archive_file_cb file_cb, struct archive_extract_userdata *userdata)
|
||||
{
|
||||
file_archive_transfer_t state;
|
||||
bool returnerr = true;
|
||||
|
||||
/* Zero the whole thing rather than naming each field: the pending
|
||||
* decode state added for resumable extraction lives here too, and a
|
||||
* stale pending_active read off the stack would send the iterate
|
||||
* loop resuming a decode that never started. */
|
||||
memset(&state, 0, sizeof(state));
|
||||
|
||||
state.type = ARCHIVE_TRANSFER_INIT;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (file_archive_parse_file_iterate(&state, &returnerr, file,
|
||||
valid_exts, file_cb, userdata) != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return returnerr;
|
||||
}
|
||||
|
||||
int file_archive_parse_file_progress(file_archive_transfer_t *state)
|
||||
{
|
||||
if (!state || state->step_total == 0)
|
||||
return 0;
|
||||
|
||||
return (int)((state->step_current * 100) / (state->step_total));
|
||||
}
|
||||
|
||||
/**
|
||||
* file_archive_extract_file:
|
||||
* @archive_path : filename path to archive.
|
||||
* @valid_exts : valid extensions for the file.
|
||||
* @extraction_directory : the directory to extract temporary
|
||||
* file to.
|
||||
*
|
||||
* Extract file from archive. If no file inside the archive is
|
||||
* specified, the first file found will be used.
|
||||
*
|
||||
* Returns : true (1) on success, otherwise false (0).
|
||||
**/
|
||||
bool file_archive_extract_file(
|
||||
const char *archive_path,
|
||||
const char *valid_exts,
|
||||
const char *extraction_directory,
|
||||
char *s, size_t len)
|
||||
{
|
||||
struct archive_extract_userdata userdata;
|
||||
struct string_list *list = string_split(valid_exts, "|");
|
||||
|
||||
userdata.archive_path[0] = '\0';
|
||||
userdata.current_file_path[0] = '\0';
|
||||
userdata.first_extracted_file_path = NULL;
|
||||
userdata.extraction_directory = extraction_directory;
|
||||
userdata.ext = list;
|
||||
userdata.list = NULL;
|
||||
userdata.found_file = false;
|
||||
userdata.list_only = false;
|
||||
userdata.crc = 0;
|
||||
userdata.transfer = NULL;
|
||||
userdata.dec = NULL;
|
||||
|
||||
if ( list
|
||||
&& file_archive_walk(archive_path, valid_exts,
|
||||
file_archive_extract_cb, &userdata)
|
||||
&& userdata.found_file
|
||||
)
|
||||
{
|
||||
if ( userdata.first_extracted_file_path
|
||||
&& *userdata.first_extracted_file_path)
|
||||
strlcpy(s, userdata.first_extracted_file_path, len);
|
||||
/* The success path used to return here without freeing either
|
||||
* the split extension list or the path strdup'd by
|
||||
* file_archive_extract_cb, so every archive load through this
|
||||
* function leaked both. Only the failure path below cleaned
|
||||
* up. Fall through to the shared cleanup instead. */
|
||||
free(userdata.first_extracted_file_path);
|
||||
string_list_free(list);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (userdata.first_extracted_file_path)
|
||||
free(userdata.first_extracted_file_path);
|
||||
if (list)
|
||||
string_list_free(list);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Warning: 'list' must zero initialised before
|
||||
* calling this function, otherwise memory leaks/
|
||||
* undefined behaviour will occur */
|
||||
bool file_archive_get_file_list_noalloc(struct string_list *list,
|
||||
const char *path,
|
||||
const char *valid_exts)
|
||||
{
|
||||
struct archive_extract_userdata userdata;
|
||||
bool ret;
|
||||
|
||||
if (!list || !string_list_initialize(list))
|
||||
return false;
|
||||
|
||||
strlcpy(userdata.archive_path, path, sizeof(userdata.archive_path));
|
||||
userdata.current_file_path[0] = '\0';
|
||||
userdata.first_extracted_file_path = NULL;
|
||||
userdata.extraction_directory = NULL;
|
||||
/* Split the extension list once for the whole walk rather than once
|
||||
* per archive member; the listing callback reads it from here. */
|
||||
userdata.ext = valid_exts
|
||||
? string_split(valid_exts, "|") : NULL;
|
||||
userdata.list = list;
|
||||
userdata.found_file = false;
|
||||
userdata.list_only = true;
|
||||
userdata.crc = 0;
|
||||
userdata.transfer = NULL;
|
||||
userdata.dec = NULL;
|
||||
|
||||
ret = file_archive_walk(path, valid_exts,
|
||||
file_archive_get_file_list_cb, &userdata);
|
||||
|
||||
if (userdata.ext)
|
||||
string_list_free(userdata.ext);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* file_archive_get_file_list:
|
||||
* @path : filename path of archive
|
||||
*
|
||||
* Returns: string listing of files from archive on success, otherwise NULL.
|
||||
**/
|
||||
struct string_list *file_archive_get_file_list(const char *path,
|
||||
const char *valid_exts)
|
||||
{
|
||||
struct archive_extract_userdata userdata;
|
||||
|
||||
strlcpy(userdata.archive_path, path, sizeof(userdata.archive_path));
|
||||
userdata.current_file_path[0] = '\0';
|
||||
userdata.first_extracted_file_path = NULL;
|
||||
userdata.extraction_directory = NULL;
|
||||
/* Split once for the whole walk; see the twin above. */
|
||||
userdata.ext = valid_exts
|
||||
? string_split(valid_exts, "|") : NULL;
|
||||
userdata.list = string_list_new();
|
||||
userdata.found_file = false;
|
||||
userdata.list_only = true;
|
||||
userdata.crc = 0;
|
||||
userdata.transfer = NULL;
|
||||
userdata.dec = NULL;
|
||||
|
||||
if (!userdata.list)
|
||||
{
|
||||
if (userdata.ext)
|
||||
string_list_free(userdata.ext);
|
||||
return NULL;
|
||||
}
|
||||
if (!file_archive_walk(path, valid_exts,
|
||||
file_archive_get_file_list_cb, &userdata))
|
||||
{
|
||||
if (userdata.ext)
|
||||
string_list_free(userdata.ext);
|
||||
string_list_free(userdata.list);
|
||||
return NULL;
|
||||
}
|
||||
if (userdata.ext)
|
||||
string_list_free(userdata.ext);
|
||||
return userdata.list;
|
||||
}
|
||||
|
||||
/* Finish a member whose decode is parked in the transfer, doing one
|
||||
* slice of work.
|
||||
*
|
||||
* Returns 1 when the member is complete and written, 0 when there is
|
||||
* more to do and the caller should come back, -1 on failure. The
|
||||
* pending slot is cleared on 1 and -1, so the caller never has to. */
|
||||
int file_archive_perform_mode_step(file_archive_transfer_t *state)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!state || !state->pending_active || !state->backend)
|
||||
return -1;
|
||||
|
||||
ret = state->backend->stream_decompress_data_to_file_iterate(
|
||||
state->context, &state->pending_handle);
|
||||
|
||||
if (ret == 0)
|
||||
return 0; /* not finished; the caller yields and returns here */
|
||||
|
||||
state->pending_active = false;
|
||||
|
||||
if (ret == -1)
|
||||
return -1;
|
||||
|
||||
if (!filestream_write_file(state->pending_path,
|
||||
state->pending_handle.data, state->pending_size))
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Begin decoding a member into the transfer's pending slot.
|
||||
*
|
||||
* Returns 1 if it finished immediately, 0 if it parked and wants to be
|
||||
* resumed through file_archive_perform_mode_step(), -1 on failure. */
|
||||
int file_archive_perform_mode_start(const char *path, const char *valid_exts,
|
||||
const uint8_t *cdata, unsigned cmode, uint32_t csize, uint32_t size,
|
||||
uint32_t crc32, struct archive_extract_userdata *userdata)
|
||||
{
|
||||
file_archive_transfer_t *state;
|
||||
|
||||
if (!userdata || !userdata->transfer || !userdata->transfer->backend)
|
||||
return -1;
|
||||
|
||||
state = userdata->transfer;
|
||||
|
||||
/* One member at a time. A second start while one is parked would
|
||||
* lose the first, so refuse rather than corrupt it. */
|
||||
if (state->pending_active)
|
||||
return -1;
|
||||
|
||||
state->pending_handle.data = NULL;
|
||||
state->pending_handle.real_checksum = 0;
|
||||
|
||||
if (!state->backend->stream_decompress_data_to_file_init(
|
||||
state->context, &state->pending_handle,
|
||||
cdata, cmode, csize, size))
|
||||
return -1;
|
||||
|
||||
strlcpy(state->pending_path, path, sizeof(state->pending_path));
|
||||
state->pending_size = size;
|
||||
state->pending_active = true;
|
||||
|
||||
return file_archive_perform_mode_step(state);
|
||||
}
|
||||
|
||||
bool file_archive_perform_mode(const char *path, const char *valid_exts,
|
||||
const uint8_t *cdata, unsigned cmode, uint32_t csize, uint32_t size,
|
||||
uint32_t crc32, struct archive_extract_userdata *userdata)
|
||||
{
|
||||
/* The blocking form, for callers that need the whole member before
|
||||
* they can continue and have nowhere to yield to. Same work, just
|
||||
* driven to completion here instead of across frames. */
|
||||
int ret = file_archive_perform_mode_start(path, valid_exts,
|
||||
cdata, cmode, csize, size, crc32, userdata);
|
||||
|
||||
while (ret == 0)
|
||||
ret = file_archive_perform_mode_step(userdata->transfer);
|
||||
|
||||
return ret == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* file_archive_filename_split:
|
||||
* @str : filename to turn into a string list
|
||||
*
|
||||
* Creates a new string list based on filename @path, delimited by a hash (#).
|
||||
*
|
||||
* Returns: new string list if successful, otherwise NULL.
|
||||
*/
|
||||
static struct string_list *file_archive_filename_split(const char *path)
|
||||
{
|
||||
union string_list_elem_attr attr;
|
||||
struct string_list *list = string_list_new();
|
||||
const char *delim = path_get_archive_delim(path);
|
||||
|
||||
attr.i = 0;
|
||||
|
||||
if (delim)
|
||||
{
|
||||
/* add archive path to list first */
|
||||
if (!string_list_append_n(list, path, (unsigned)(delim - path), attr))
|
||||
{
|
||||
string_list_free(list);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* now add the path within the archive */
|
||||
delim++;
|
||||
|
||||
if (*delim)
|
||||
{
|
||||
if (!string_list_append(list, delim, attr))
|
||||
{
|
||||
string_list_free(list);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string_list_append(list, path, attr))
|
||||
{
|
||||
string_list_free(list);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/* Generic compressed file loader.
|
||||
* Extracts to buf, unless optional_filename != 0
|
||||
* Then extracts to optional_filename and leaves buf alone.
|
||||
*/
|
||||
int file_archive_compressed_read(
|
||||
const char * path, void **buf,
|
||||
const char* optional_filename, int64_t *len)
|
||||
{
|
||||
const struct
|
||||
file_archive_file_backend *backend = NULL;
|
||||
struct string_list *str_list = NULL;
|
||||
|
||||
/* Safety check.
|
||||
* If optional_filename and optional_filename
|
||||
* exists, we simply return 0,
|
||||
* hoping that optional_filename is the
|
||||
* same as requested.
|
||||
*/
|
||||
if (optional_filename && path_is_valid(optional_filename))
|
||||
{
|
||||
*len = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
str_list = file_archive_filename_split(path);
|
||||
/* We assure that there is something after the '#' symbol.
|
||||
*
|
||||
* This error condition happens for example, when
|
||||
* path = /path/to/file.7z, or
|
||||
* path = /path/to/file.7z#
|
||||
*/
|
||||
if (str_list->size <= 1)
|
||||
{
|
||||
/* could not extract string and substring. */
|
||||
string_list_free(str_list);
|
||||
*len = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
backend = file_archive_get_file_backend(str_list->elems[0].data);
|
||||
*len = backend->compressed_file_read(str_list->elems[0].data,
|
||||
str_list->elems[1].data, buf, optional_filename);
|
||||
|
||||
string_list_free(str_list);
|
||||
|
||||
if (*len != -1)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct file_archive_file_backend *file_archive_get_zlib_file_backend(void)
|
||||
{
|
||||
#if defined(HAVE_ZLIB) || defined(HAVE_COMPRESSION)
|
||||
/* The ZIP DEFLATE backend decodes through the built-in inflate when zlib
|
||||
* is not present, so it is available whenever compression support is. */
|
||||
return &zlib_backend;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
const struct file_archive_file_backend *file_archive_get_7z_file_backend(void)
|
||||
{
|
||||
#ifdef HAVE_7ZIP
|
||||
return &sevenzip_backend;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
const struct file_archive_file_backend *file_archive_get_zstd_file_backend(void)
|
||||
{
|
||||
#if defined(HAVE_ZSTD) || defined(HAVE_RZSTD)
|
||||
return &zstd_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_ZSTD) \
|
||||
|| defined(HAVE_RZSTD) || defined(HAVE_COMPRESSION)
|
||||
char newpath[PATH_MAX_LENGTH];
|
||||
const char *file_ext = NULL;
|
||||
char *last = NULL;
|
||||
|
||||
strlcpy(newpath, path, sizeof(newpath));
|
||||
|
||||
if ((last = (char*)path_get_archive_delim(newpath)))
|
||||
*last = '\0';
|
||||
|
||||
file_ext = path_get_extension(newpath);
|
||||
|
||||
#ifdef HAVE_7ZIP
|
||||
if (string_is_equal_noncase(file_ext, "7z"))
|
||||
return &sevenzip_backend;
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_ZLIB) || defined(HAVE_COMPRESSION)
|
||||
/* ZIP/APK decode via zlib, or via the built-in inflate when zlib is
|
||||
* not compiled in. */
|
||||
if ( string_is_equal_noncase(file_ext, "zip")
|
||||
|| string_is_equal_noncase(file_ext, "apk")
|
||||
)
|
||||
return &zlib_backend;
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_ZSTD) || defined(HAVE_RZSTD)
|
||||
if (string_is_equal_noncase(file_ext, "zst"))
|
||||
return &zstd_backend;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* file_archive_get_file_crc32:
|
||||
* @path : filename path of archive
|
||||
*
|
||||
* Returns: CRC32 of the specified file in the archive, otherwise 0.
|
||||
* If no path within the archive is specified, the first
|
||||
* file found inside is used.
|
||||
**/
|
||||
uint32_t file_archive_get_file_crc32(const char *path)
|
||||
{
|
||||
uint64_t file_size;
|
||||
return file_archive_get_file_crc32_and_size(path, &file_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* file_archive_get_file_crc32_and_size:
|
||||
* @path : filename path of archive
|
||||
* @size : size of the file inside the archive
|
||||
*
|
||||
* Returns: CRC32 of the specified file in the archive, otherwise 0.
|
||||
* If no path within the archive is specified, the first
|
||||
* file found inside is used.
|
||||
**/
|
||||
uint32_t file_archive_get_file_crc32_and_size(const char *path, uint64_t *size)
|
||||
{
|
||||
file_archive_transfer_t state;
|
||||
struct archive_extract_userdata userdata = {0};
|
||||
bool returnerr = false;
|
||||
bool found = true;
|
||||
const char *archive_path = NULL;
|
||||
bool contains_compressed = path_contains_compressed_file(path);
|
||||
|
||||
if (contains_compressed)
|
||||
{
|
||||
archive_path = path_get_archive_delim(path);
|
||||
|
||||
/* move pointer right after the delimiter to give us the path */
|
||||
if (archive_path)
|
||||
archive_path += 1;
|
||||
}
|
||||
|
||||
/* Zeroed wholesale for the same reason as file_archive_walk():
|
||||
* the pending decode fields must start clear. */
|
||||
memset(&state, 0, sizeof(state));
|
||||
|
||||
state.type = ARCHIVE_TRANSFER_INIT;
|
||||
|
||||
/* Initialize and open archive first.
|
||||
Sets next state type to ITERATE. */
|
||||
file_archive_parse_file_iterate(&state,
|
||||
&returnerr, path, NULL, NULL,
|
||||
&userdata);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
/* Nothing left to look at. Without this the loop spins: the
|
||||
* iterate call is skipped once the transfer leaves ITERATE, and
|
||||
* the two tests below then read a current_file_path that can no
|
||||
* longer change. A member that is not in the archive - or an
|
||||
* archive that failed to open at all - hung here rather than
|
||||
* returning. */
|
||||
if (state.type != ARCHIVE_TRANSFER_ITERATE)
|
||||
{
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Now find the first file in the archive. */
|
||||
file_archive_parse_file_iterate(&state,
|
||||
&returnerr, path, NULL, NULL,
|
||||
&userdata);
|
||||
|
||||
/* If no path specified within archive, stop after
|
||||
* finding the first file.
|
||||
*/
|
||||
if (!contains_compressed)
|
||||
break;
|
||||
|
||||
/* Stop when the right file in the archive is found. */
|
||||
if (archive_path)
|
||||
{
|
||||
if (string_is_equal(userdata.current_file_path, archive_path))
|
||||
break;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
file_archive_parse_file_iterate_stop(&state);
|
||||
|
||||
/* Report nothing rather than whichever entry the walk stopped on:
|
||||
* the caller cannot tell a real checksum from a leftover one, and
|
||||
* the scanner would match content against the wrong record. */
|
||||
if (!found)
|
||||
{
|
||||
*size = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
*size = userdata.size;
|
||||
return userdata.crc;
|
||||
}
|
||||
@@ -1,516 +0,0 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (archive_file_7z.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <boolean.h>
|
||||
#include <file/archive_file.h>
|
||||
#include <streams/file_stream.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <encodings/utf.h>
|
||||
#include <encodings/crc32.h>
|
||||
#include <string/stdstring.h>
|
||||
#include <lists/string_list.h>
|
||||
#include <file/file_path.h>
|
||||
#include <compat/strl.h>
|
||||
|
||||
#include <7z/r7z_archive.h>
|
||||
|
||||
#define SEVENZIP_MAGIC "7z\xBC\xAF\x27\x1C"
|
||||
#define SEVENZIP_MAGIC_LEN 6
|
||||
|
||||
/* r7z_archive parses from a buffer rather than a stream.
|
||||
*
|
||||
* archive_file.c has already opened the file and, for archives up to
|
||||
* 256 MiB, mmapped it before this backend is entered. Where that
|
||||
* mapping exists it is used directly, exactly as the zip and zstd
|
||||
* backends do; reading the file again would mean a second full copy of
|
||||
* the archive alongside the mapping. Only when there is no mapping
|
||||
* (mmap unavailable, or an archive over the cap) does this read the
|
||||
* file itself, and `owns_data` records which of the two happened. */
|
||||
struct sevenzip_context_t
|
||||
{
|
||||
r7z_archive_t *archive;
|
||||
const uint8_t *data;
|
||||
size_t data_len;
|
||||
int owns_data;
|
||||
/* Cache of 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;
|
||||
};
|
||||
|
||||
/* Read a whole file into memory, for the no-mapping case only.
|
||||
* Returns NULL on any failure. */
|
||||
static uint8_t *sevenzip_slurp(const char *path, size_t *out_len)
|
||||
{
|
||||
RFILE *f;
|
||||
int64_t len;
|
||||
uint8_t *buf;
|
||||
|
||||
if (string_is_empty(path))
|
||||
return NULL;
|
||||
|
||||
f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
if (!f)
|
||||
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))
|
||||
{
|
||||
filestream_close(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!(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;
|
||||
}
|
||||
|
||||
/* Point the context at the archive bytes, preferring the mapping the
|
||||
* caller already made. */
|
||||
static int sevenzip_bind_data(struct sevenzip_context_t *ctx,
|
||||
file_archive_transfer_t *state, const char *file)
|
||||
{
|
||||
#ifdef HAVE_MMAP
|
||||
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
|
||||
|
||||
{
|
||||
uint8_t *buf = sevenzip_slurp(file, &ctx->data_len);
|
||||
if (!buf)
|
||||
return 0;
|
||||
ctx->data = buf;
|
||||
ctx->owns_data = 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Convert a stored UTF-16 name into the caller's char buffer. */
|
||||
static bool sevenzip_name_to_char(const uint16_t *name, char *s, size_t len)
|
||||
{
|
||||
if (!name)
|
||||
return false;
|
||||
return utf16_to_char_string(name, s, len) ? true : false;
|
||||
}
|
||||
|
||||
static void sevenzip_parse_file_free(void *context)
|
||||
{
|
||||
struct sevenzip_context_t *sevenzip_context =
|
||||
(struct sevenzip_context_t*)context;
|
||||
|
||||
if (!sevenzip_context)
|
||||
return;
|
||||
|
||||
free(sevenzip_context->output);
|
||||
r7z_archive_close(sevenzip_context->archive);
|
||||
/* Only free what this backend allocated; the mapping belongs to
|
||||
* archive_file.c and is unmapped there. */
|
||||
if (sevenzip_context->owns_data)
|
||||
free((void*)sevenzip_context->data);
|
||||
free(sevenzip_context);
|
||||
}
|
||||
|
||||
/* State for the compressed_file_read driver below. */
|
||||
typedef struct
|
||||
{
|
||||
char *opt_file;
|
||||
char *needle;
|
||||
void **buf;
|
||||
size_t size;
|
||||
bool found;
|
||||
} sevenzip_decomp_state_t;
|
||||
|
||||
/* Called once per entry while the driver walks the archive. Starts the
|
||||
* decode of the wanted member and stops the scan; the iterate loop
|
||||
* finishes the decode across later calls. */
|
||||
static int sevenzip_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)
|
||||
{
|
||||
sevenzip_decomp_state_t *st =
|
||||
(sevenzip_decomp_state_t*)userdata->cb_data;
|
||||
|
||||
if (!name || !*name || !st || !st->needle)
|
||||
return 1;
|
||||
if (!string_is_equal(name, st->needle))
|
||||
return 1; /* keep looking */
|
||||
|
||||
if (st->opt_file)
|
||||
{
|
||||
/* Decode straight to the requested path. */
|
||||
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;
|
||||
}
|
||||
|
||||
/* Decode into memory. perform_mode writes to a file, so this path
|
||||
* cannot use it; take the member directly and stop. */
|
||||
{
|
||||
struct sevenzip_context_t *ctx =
|
||||
(struct sevenzip_context_t*)userdata->transfer->context;
|
||||
uint8_t *member = NULL;
|
||||
size_t member_len = 0;
|
||||
int res;
|
||||
|
||||
if (!ctx || !ctx->archive)
|
||||
return 1;
|
||||
|
||||
/* Sliced, but driven to completion here: the caller of
|
||||
* compressed_file_read wants the bytes on return and has nowhere
|
||||
* to yield to. */
|
||||
do
|
||||
{
|
||||
res = r7z_archive_extract_slice(ctx->archive,
|
||||
(uint32_t)(size_t)cdata, &member, &member_len);
|
||||
} while (res == R7Z_PENDING);
|
||||
|
||||
if (res != R7Z_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 7z archive
|
||||
* (path) and allocate a buf for it to write it in.
|
||||
* If optional_outfile is set, extract to that instead
|
||||
* and don't allocate buffer.
|
||||
*
|
||||
* Driven through file_archive_parse_file_iterate() rather than
|
||||
* reimplementing the walk, which is how the zip backend does it. That
|
||||
* inherits the archive mapping, the per-entry stepping and the shared
|
||||
* teardown instead of duplicating all three.
|
||||
*/
|
||||
static int64_t sevenzip_file_read(
|
||||
const char *path,
|
||||
const char *needle, void **buf,
|
||||
const char *optional_outfile)
|
||||
{
|
||||
file_archive_transfer_t state;
|
||||
sevenzip_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);
|
||||
|
||||
/* A needle that failed to duplicate would make the callback compare
|
||||
* against NULL. Bail rather than risk it. */
|
||||
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,
|
||||
"", sevenzip_file_decompressed, &userdata);
|
||||
if (!returnerr)
|
||||
break;
|
||||
/* found is set when the decode *starts*, and the member may
|
||||
* still be parked in the transfer. Keep ticking until it has
|
||||
* drained, or the file mode would stop before anything was
|
||||
* written. */
|
||||
} 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 sevenzip_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 sevenzip_context_t *sevenzip_context =
|
||||
(struct sevenzip_context_t*)context;
|
||||
|
||||
(void)handle;
|
||||
(void)cmode;
|
||||
(void)csize;
|
||||
(void)size;
|
||||
|
||||
if (!sevenzip_context)
|
||||
return false;
|
||||
|
||||
/* The parse step hands the entry index through cdata. */
|
||||
sevenzip_context->decompress_index = (uint32_t)(size_t)cdata;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int sevenzip_stream_decompress_data_to_file_iterate(
|
||||
void *context, file_archive_file_handle_t *handle)
|
||||
{
|
||||
struct sevenzip_context_t *sevenzip_context =
|
||||
(struct sevenzip_context_t*)context;
|
||||
uint8_t *member = NULL;
|
||||
size_t member_len = 0;
|
||||
int res;
|
||||
|
||||
/* The callers loop while this returns 0, so 0 means "not finished,
|
||||
* call again" and never "failed". Returning it on error spins them
|
||||
* forever, and because each spin decodes the member afresh the hang
|
||||
* is not even cheap. Failure is -1. */
|
||||
if (!sevenzip_context || !sevenzip_context->archive)
|
||||
return -1;
|
||||
|
||||
/* Sliced rather than whole: a solid folder is tens of milliseconds
|
||||
* decoded in one go, and this is the return value the archive layer
|
||||
* turns into a yield. Callers that cannot yield still get the whole
|
||||
* member, they just get it through several calls. */
|
||||
res = r7z_archive_extract_slice(sevenzip_context->archive,
|
||||
sevenzip_context->decompress_index,
|
||||
&member, &member_len);
|
||||
|
||||
if (res == R7Z_PENDING)
|
||||
return 0;
|
||||
if (res != R7Z_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(sevenzip_context->output);
|
||||
sevenzip_context->output = member;
|
||||
|
||||
if (handle)
|
||||
handle->data = sevenzip_context->output;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int sevenzip_parse_file_init(file_archive_transfer_t *state,
|
||||
const char *file)
|
||||
{
|
||||
uint8_t magic_buf[SEVENZIP_MAGIC_LEN];
|
||||
struct sevenzip_context_t *sevenzip_context = NULL;
|
||||
|
||||
if (state->archive_size < SEVENZIP_MAGIC_LEN)
|
||||
goto error;
|
||||
|
||||
filestream_seek(state->archive_file, 0, SEEK_SET);
|
||||
if (filestream_read(state->archive_file, magic_buf, SEVENZIP_MAGIC_LEN)
|
||||
!= SEVENZIP_MAGIC_LEN)
|
||||
goto error;
|
||||
|
||||
if (memcmp(magic_buf, SEVENZIP_MAGIC, SEVENZIP_MAGIC_LEN) != 0)
|
||||
goto error;
|
||||
|
||||
if (!(sevenzip_context = (struct sevenzip_context_t*)
|
||||
calloc(1, sizeof(*sevenzip_context))))
|
||||
goto error;
|
||||
|
||||
state->context = sevenzip_context;
|
||||
|
||||
if (!sevenzip_bind_data(sevenzip_context, state, file))
|
||||
goto error;
|
||||
|
||||
if (r7z_archive_open(&sevenzip_context->archive,
|
||||
sevenzip_context->data, sevenzip_context->data_len) != R7Z_OK)
|
||||
goto error;
|
||||
|
||||
state->step_total = r7z_archive_num_entries(sevenzip_context->archive);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
if (sevenzip_context)
|
||||
sevenzip_parse_file_free(sevenzip_context);
|
||||
state->context = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int sevenzip_parse_file_iterate_step_internal(
|
||||
struct sevenzip_context_t *sevenzip_context,
|
||||
char *s,
|
||||
const uint8_t **cdata,
|
||||
unsigned *cmode,
|
||||
uint32_t *size,
|
||||
uint32_t *csize,
|
||||
uint32_t *checksum,
|
||||
unsigned *payback,
|
||||
struct archive_extract_userdata *userdata)
|
||||
{
|
||||
const r7z_entry_t *entry;
|
||||
|
||||
(void)userdata;
|
||||
|
||||
if (!sevenzip_context->archive)
|
||||
return -1;
|
||||
|
||||
if (sevenzip_context->parse_index
|
||||
>= r7z_archive_num_entries(sevenzip_context->archive))
|
||||
return 0;
|
||||
|
||||
entry = r7z_archive_entry(sevenzip_context->archive,
|
||||
sevenzip_context->parse_index);
|
||||
|
||||
if (entry && !entry->is_dir)
|
||||
{
|
||||
s[0] = '\0';
|
||||
if (!sevenzip_name_to_char(entry->name, s, PATH_MAX_LENGTH))
|
||||
return -1;
|
||||
|
||||
/* The vtable reports sizes as uint32_t, but 7z stores them as
|
||||
* 64-bit. A member of 4 GiB or more cannot be described here, and
|
||||
* truncating would hand the caller a wrapped length to write from
|
||||
* a full-length buffer. Skip such an entry instead: the scan
|
||||
* carries on and everything else in the archive stays usable. */
|
||||
if (entry->size > (uint64_t)0xFFFFFFFFu)
|
||||
{
|
||||
/* Blank the name too: the caller passes whatever is in it to
|
||||
* file_cb, and a named entry reporting zero bytes is worse
|
||||
* than no entry at all. */
|
||||
s[0] = '\0';
|
||||
*payback = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
*cmode = 0; /* unused for 7zip */
|
||||
*checksum = entry->crc;
|
||||
*size = (uint32_t)entry->size;
|
||||
/* The packed size of an individual member is not meaningful for
|
||||
* 7z: members share a solid folder's compressed stream. Callers
|
||||
* use this only for progress reporting. */
|
||||
*csize = (uint32_t)entry->size;
|
||||
|
||||
*cdata = (uint8_t *)(size_t)sevenzip_context->parse_index;
|
||||
}
|
||||
|
||||
*payback = 1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int sevenzip_parse_file_iterate_step(void *context,
|
||||
const char *valid_exts,
|
||||
struct archive_extract_userdata *userdata,
|
||||
file_archive_file_cb file_cb)
|
||||
{
|
||||
const uint8_t *cdata = NULL;
|
||||
uint32_t checksum = 0;
|
||||
uint32_t size = 0;
|
||||
uint32_t csize = 0;
|
||||
unsigned cmode = 0;
|
||||
unsigned payload = 0;
|
||||
struct sevenzip_context_t *sevenzip_context =
|
||||
(struct sevenzip_context_t*)context;
|
||||
int ret;
|
||||
|
||||
userdata->current_file_path[0] = '\0';
|
||||
|
||||
ret = sevenzip_parse_file_iterate_step_internal(sevenzip_context,
|
||||
userdata->current_file_path,
|
||||
&cdata, &cmode, &size, &csize,
|
||||
&checksum, &payload, userdata);
|
||||
|
||||
if (ret != 1)
|
||||
return ret;
|
||||
|
||||
userdata->crc = checksum;
|
||||
userdata->size = size;
|
||||
|
||||
if (file_cb && !file_cb(userdata->current_file_path, valid_exts,
|
||||
cdata, cmode,
|
||||
csize, size, checksum, userdata))
|
||||
return 0;
|
||||
|
||||
sevenzip_context->parse_index += payload;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
const struct file_archive_file_backend sevenzip_backend = {
|
||||
sevenzip_parse_file_init,
|
||||
sevenzip_parse_file_iterate_step,
|
||||
sevenzip_parse_file_free,
|
||||
sevenzip_stream_decompress_data_to_file_init,
|
||||
sevenzip_stream_decompress_data_to_file_iterate,
|
||||
encoding_crc32,
|
||||
sevenzip_file_read,
|
||||
"7z"
|
||||
};
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,410 +0,0 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (archive_file_zstd.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <boolean.h>
|
||||
#include <file/archive_file.h>
|
||||
#include <streams/file_stream.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <encodings/crc32.h>
|
||||
#include <string/stdstring.h>
|
||||
#include <file/file_path.h>
|
||||
#include <compat/strl.h>
|
||||
|
||||
/* Either decoder will do; rzstd is preferred where a build has it, and
|
||||
* the reference remains available until every platform has moved. */
|
||||
#ifdef HAVE_RZSTD
|
||||
#include <encodings/rzstd.h>
|
||||
#else
|
||||
#include <zstd.h>
|
||||
#endif
|
||||
|
||||
/* One spelling for whichever decoder is compiled in. */
|
||||
#ifdef HAVE_RZSTD
|
||||
#define ZSTD_FRAME_HEADER_MAX RZSTD_FRAME_HEADER_MAX
|
||||
#define ZSTD_SIZE_UNKNOWN RZSTD_CONTENT_SIZE_UNKNOWN
|
||||
#define ZSTD_SIZE_ERROR RZSTD_CONTENT_SIZE_ERROR
|
||||
#else
|
||||
#define ZSTD_FRAME_HEADER_MAX 18
|
||||
#define ZSTD_SIZE_UNKNOWN ((int64_t)ZSTD_CONTENTSIZE_UNKNOWN)
|
||||
#define ZSTD_SIZE_ERROR ((int64_t)ZSTD_CONTENTSIZE_ERROR)
|
||||
#endif
|
||||
|
||||
#define ZSTD_MAGIC "\x28\xB5\x2F\xFD"
|
||||
#define ZSTD_MAGIC_LEN 4
|
||||
|
||||
struct zstd_file_context
|
||||
{
|
||||
uint8_t *decompressed_data;
|
||||
char inner_filename[PATH_MAX_LENGTH];
|
||||
char archive_path[PATH_MAX_LENGTH];
|
||||
int64_t archive_size;
|
||||
uint32_t decompressed_size;
|
||||
bool parsed;
|
||||
};
|
||||
|
||||
/* Derive the inner filename from the .zst path by stripping the .zst extension */
|
||||
static void zstd_derive_inner_filename(const char *path, char *s, size_t len)
|
||||
{
|
||||
const char *base = path_basename(path);
|
||||
const char *ext;
|
||||
|
||||
strlcpy(s, base, len);
|
||||
ext = strrchr(s, '.');
|
||||
if (ext && string_is_equal_noncase(ext, ".zst"))
|
||||
((char*)ext)[0] = '\0';
|
||||
}
|
||||
|
||||
static int zstd_parse_file_init(file_archive_transfer_t *state,
|
||||
const char *file)
|
||||
{
|
||||
uint8_t magic_buf[ZSTD_MAGIC_LEN];
|
||||
struct zstd_file_context *ctx = NULL;
|
||||
|
||||
if (state->archive_size < ZSTD_MAGIC_LEN)
|
||||
goto error;
|
||||
|
||||
filestream_seek(state->archive_file, 0, SEEK_SET);
|
||||
if (filestream_read(state->archive_file, magic_buf, ZSTD_MAGIC_LEN)
|
||||
!= ZSTD_MAGIC_LEN)
|
||||
goto error;
|
||||
|
||||
if (memcmp(magic_buf, ZSTD_MAGIC, ZSTD_MAGIC_LEN) != 0)
|
||||
goto error;
|
||||
|
||||
ctx = (struct zstd_file_context*)calloc(1, sizeof(*ctx));
|
||||
if (!ctx)
|
||||
goto error;
|
||||
|
||||
zstd_derive_inner_filename(file, ctx->inner_filename,
|
||||
sizeof(ctx->inner_filename));
|
||||
strlcpy(ctx->archive_path, file, sizeof(ctx->archive_path));
|
||||
ctx->archive_size = state->archive_size;
|
||||
|
||||
state->context = ctx;
|
||||
state->step_total = 1;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
if (ctx)
|
||||
free(ctx);
|
||||
state->context = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int zstd_parse_file_iterate_step(void *context,
|
||||
const char *valid_exts,
|
||||
struct archive_extract_userdata *userdata, file_archive_file_cb file_cb)
|
||||
{
|
||||
struct zstd_file_context *ctx = (struct zstd_file_context*)context;
|
||||
file_archive_transfer_t *state;
|
||||
int64_t content_size;
|
||||
|
||||
if (!ctx || ctx->parsed)
|
||||
return 0;
|
||||
|
||||
ctx->parsed = true;
|
||||
state = userdata->transfer;
|
||||
|
||||
/* Read the frame header to get decompressed size */
|
||||
filestream_seek(state->archive_file, 0, SEEK_SET);
|
||||
|
||||
#ifdef HAVE_MMAP
|
||||
if (state->archive_mmap_data)
|
||||
{
|
||||
#ifdef HAVE_RZSTD
|
||||
content_size = rzstd_frame_content_size(
|
||||
state->archive_mmap_data, (size_t)state->archive_size);
|
||||
#else
|
||||
content_size = (int64_t)ZSTD_getFrameContentSize(
|
||||
state->archive_mmap_data, (size_t)state->archive_size);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
/* Only the header is needed, and it is at most
|
||||
* ZSTD_FRAME_HEADER_MAX bytes. */
|
||||
uint8_t header_buf[ZSTD_FRAME_HEADER_MAX];
|
||||
int64_t to_read = state->archive_size < ZSTD_FRAME_HEADER_MAX
|
||||
? state->archive_size : ZSTD_FRAME_HEADER_MAX;
|
||||
if (filestream_read(state->archive_file, header_buf, to_read) != to_read)
|
||||
return -1;
|
||||
#ifdef HAVE_RZSTD
|
||||
content_size = rzstd_frame_content_size(header_buf, (size_t)to_read);
|
||||
#else
|
||||
content_size = (int64_t)ZSTD_getFrameContentSize(header_buf,
|
||||
(size_t)to_read);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* A frame that does not state its size is no use here: the whole
|
||||
* member has to be allocated before it is decoded. */
|
||||
if ( content_size == ZSTD_SIZE_UNKNOWN
|
||||
|| content_size == ZSTD_SIZE_ERROR)
|
||||
return -1;
|
||||
|
||||
/* decompressed_size is a uint32_t and feeds a later malloc; reject
|
||||
* values that would truncate to a smaller size and mismatch the
|
||||
* length the decode actually produces. */
|
||||
if (content_size > UINT32_MAX)
|
||||
return -1;
|
||||
|
||||
ctx->decompressed_size = (uint32_t)content_size;
|
||||
|
||||
strlcpy(userdata->current_file_path, ctx->inner_filename,
|
||||
sizeof(userdata->current_file_path));
|
||||
|
||||
userdata->crc = 0;
|
||||
userdata->size = (uint64_t)content_size;
|
||||
|
||||
if (file_cb && !file_cb(ctx->inner_filename, valid_exts,
|
||||
NULL, 0,
|
||||
(uint32_t)state->archive_size, (uint32_t)content_size,
|
||||
0, userdata))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void zstd_parse_file_free(void *context)
|
||||
{
|
||||
struct zstd_file_context *ctx = (struct zstd_file_context*)context;
|
||||
if (!ctx)
|
||||
return;
|
||||
if (ctx->decompressed_data)
|
||||
free(ctx->decompressed_data);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static bool zstd_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 zstd_file_context *ctx = (struct zstd_file_context*)context;
|
||||
if (!ctx)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int zstd_stream_decompress_data_to_file_iterate(
|
||||
void *context, file_archive_file_handle_t *handle)
|
||||
{
|
||||
struct zstd_file_context *ctx = (struct zstd_file_context*)context;
|
||||
void *compressed_data = NULL;
|
||||
size_t result;
|
||||
int e;
|
||||
RFILE *file;
|
||||
|
||||
if (!ctx || !handle)
|
||||
return -1;
|
||||
|
||||
if (ctx->decompressed_data)
|
||||
{
|
||||
handle->data = ctx->decompressed_data;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Read the entire compressed file */
|
||||
file = filestream_open(ctx->archive_path,
|
||||
RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
if (!file)
|
||||
return -1;
|
||||
|
||||
compressed_data = malloc((size_t)ctx->archive_size);
|
||||
if (!compressed_data)
|
||||
{
|
||||
filestream_close(file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (filestream_read(file, compressed_data, ctx->archive_size)
|
||||
!= ctx->archive_size)
|
||||
{
|
||||
free(compressed_data);
|
||||
filestream_close(file);
|
||||
return -1;
|
||||
}
|
||||
filestream_close(file);
|
||||
|
||||
ctx->decompressed_data = (uint8_t*)malloc(ctx->decompressed_size);
|
||||
if (!ctx->decompressed_data)
|
||||
{
|
||||
free(compressed_data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_RZSTD
|
||||
e = (rzstd_decode(ctx->decompressed_data, ctx->decompressed_size,
|
||||
compressed_data, (size_t)ctx->archive_size, &result)
|
||||
!= RZSTD_PROCESS_END);
|
||||
#else
|
||||
result = ZSTD_decompress(ctx->decompressed_data, ctx->decompressed_size,
|
||||
compressed_data, (size_t)ctx->archive_size);
|
||||
e = ZSTD_isError(result);
|
||||
#endif
|
||||
free(compressed_data);
|
||||
|
||||
if (e)
|
||||
{
|
||||
free(ctx->decompressed_data);
|
||||
ctx->decompressed_data = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
handle->data = ctx->decompressed_data;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Extract the file from a .zst archive (single-file container).
|
||||
* If needle doesn't match the derived inner filename, returns -1.
|
||||
* If optional_outfile is set, writes to that file instead of buf. */
|
||||
static int64_t zstd_file_read(
|
||||
const char *path,
|
||||
const char *needle, void **buf,
|
||||
const char *optional_outfile)
|
||||
{
|
||||
char inner_name[PATH_MAX_LENGTH];
|
||||
int64_t file_size;
|
||||
void *compressed_data = NULL;
|
||||
uint8_t *decompressed = NULL;
|
||||
int64_t content_size;
|
||||
size_t result;
|
||||
int e;
|
||||
RFILE *file;
|
||||
|
||||
zstd_derive_inner_filename(path, inner_name, sizeof(inner_name));
|
||||
|
||||
if (!string_is_equal(inner_name, needle))
|
||||
return -1;
|
||||
|
||||
file = filestream_open(path,
|
||||
RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
if (!file)
|
||||
return -1;
|
||||
|
||||
file_size = filestream_get_size(file);
|
||||
if (file_size <= 0)
|
||||
{
|
||||
filestream_close(file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
compressed_data = malloc((size_t)file_size);
|
||||
if (!compressed_data)
|
||||
{
|
||||
filestream_close(file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (filestream_read(file, compressed_data, (int64_t)file_size) != file_size)
|
||||
{
|
||||
free(compressed_data);
|
||||
filestream_close(file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
filestream_close(file);
|
||||
|
||||
#ifdef HAVE_RZSTD
|
||||
content_size = rzstd_frame_content_size(compressed_data,
|
||||
(size_t)file_size);
|
||||
#else
|
||||
content_size = (int64_t)ZSTD_getFrameContentSize(compressed_data,
|
||||
(size_t)file_size);
|
||||
#endif
|
||||
if ( content_size == ZSTD_SIZE_UNKNOWN
|
||||
|| content_size == ZSTD_SIZE_ERROR)
|
||||
{
|
||||
free(compressed_data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Reject sizes that would overflow the "+1" NUL-byte allocation or
|
||||
* truncate when cast to size_t on 32-bit hosts. Without this guard
|
||||
* content_size = 0xFFFFFFFF on a 32-bit host wraps the +1 to zero,
|
||||
* malloc(0) may return a non-NULL pointer, and the following
|
||||
* decode writes 4 GiB into it. */
|
||||
if ((uint64_t)content_size >= (uint64_t)SIZE_MAX)
|
||||
{
|
||||
free(compressed_data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
decompressed = (uint8_t*)malloc((size_t)(content_size + 1));
|
||||
if (!decompressed)
|
||||
{
|
||||
free(compressed_data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_RZSTD
|
||||
e = (rzstd_decode(decompressed, (size_t)content_size,
|
||||
compressed_data, (size_t)file_size, &result)
|
||||
!= RZSTD_PROCESS_END);
|
||||
#else
|
||||
result = ZSTD_decompress(decompressed, (size_t)content_size,
|
||||
compressed_data, (size_t)file_size);
|
||||
e = ZSTD_isError(result);
|
||||
#endif
|
||||
free(compressed_data);
|
||||
|
||||
if (e)
|
||||
{
|
||||
free(decompressed);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (optional_outfile)
|
||||
{
|
||||
if (!filestream_write_file(optional_outfile, decompressed, (int64_t)result))
|
||||
{
|
||||
free(decompressed);
|
||||
return -1;
|
||||
}
|
||||
free(decompressed);
|
||||
}
|
||||
else
|
||||
{
|
||||
decompressed[result] = '\0';
|
||||
*buf = decompressed;
|
||||
}
|
||||
|
||||
return (int64_t)result;
|
||||
}
|
||||
|
||||
const struct file_archive_file_backend zstd_backend = {
|
||||
zstd_parse_file_init,
|
||||
zstd_parse_file_iterate_step,
|
||||
zstd_parse_file_free,
|
||||
zstd_stream_decompress_data_to_file_init,
|
||||
zstd_stream_decompress_data_to_file_iterate,
|
||||
encoding_crc32,
|
||||
zstd_file_read,
|
||||
"zstd"
|
||||
};
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,206 +0,0 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (config_file_io.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.
|
||||
*/
|
||||
|
||||
/* File access for config_file.
|
||||
*
|
||||
* The parser core (config_file.c) performs no file I/O: it consumes
|
||||
* buffers, the way the codecs under formats/ do, and reaches files
|
||||
* only through the config_file_io interface declared in
|
||||
* config_file.h. This translation unit is where the file system
|
||||
* actually enters the picture: the filestream/VFS-backed
|
||||
* implementation of that interface, the path-based constructors
|
||||
* built on it, and config_file_write - everything that opens a file
|
||||
* by name lives here, so a build that links only config_file.c gets
|
||||
* a pure in-memory parser with no streams/ dependency.
|
||||
*
|
||||
* Every constructor here self-registers the filestream
|
||||
* implementation as the io default (idempotently, never displacing
|
||||
* a host-installed interface), so any binary that loads at least
|
||||
* one config by path resolves '#include' directives exactly as
|
||||
* before this split. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <compat/fopen_utf8.h>
|
||||
#include <file/config_file.h>
|
||||
#include <streams/file_stream.h>
|
||||
|
||||
static char *config_file_io_fs_read_file(const char *path,
|
||||
int64_t *len, void *ud)
|
||||
{
|
||||
void *buf = NULL;
|
||||
/* No path_is_valid() first: filestream_read_file() opens the file
|
||||
* itself and returns 0 when it cannot, so a preceding stat only
|
||||
* repeats the lookup the open already does - and no caller can see
|
||||
* its result, since a missing file and an unreadable one both land
|
||||
* on the same NULL return. Dropping it also closes the window
|
||||
* between the stat and the open in which the file could change. */
|
||||
if (!filestream_read_file(path, &buf, len))
|
||||
return NULL;
|
||||
/* filestream_read_file NUL-terminates past *len, which is the
|
||||
* buffer contract the parser relies on. */
|
||||
return (char*)buf;
|
||||
}
|
||||
|
||||
static void config_file_io_fs_free_file(char *buf, void *ud)
|
||||
{
|
||||
free(buf);
|
||||
}
|
||||
|
||||
const config_file_io_t *config_file_io_filestream(void)
|
||||
{
|
||||
static const config_file_io_t config_file_io_fs = {
|
||||
config_file_io_fs_read_file,
|
||||
config_file_io_fs_free_file,
|
||||
NULL
|
||||
};
|
||||
return &config_file_io_fs;
|
||||
}
|
||||
|
||||
/* Idempotent: never displaces an interface a host installed. */
|
||||
static void config_file_io_ensure_default(void)
|
||||
{
|
||||
if (!config_file_get_io_default())
|
||||
config_file_set_io_default(config_file_io_filestream());
|
||||
}
|
||||
|
||||
/**
|
||||
* config_file_new_with_callback:
|
||||
*
|
||||
* Loads a config file.
|
||||
* If @path is NULL, will create an empty config file.
|
||||
* Includes cb callbacks to run custom code during config file processing.
|
||||
*
|
||||
* @return Returns NULL if file doesn't exist.
|
||||
**/
|
||||
config_file_t *config_file_new_with_callback(
|
||||
const char *path, config_file_cb_t *cb)
|
||||
{
|
||||
int ret = 0;
|
||||
struct config_file *conf = config_file_new_alloc();
|
||||
if (!path || !*path)
|
||||
return conf;
|
||||
config_file_io_ensure_default();
|
||||
if ((ret = config_file_load_file(conf, path, cb)) == -1)
|
||||
{
|
||||
config_file_free(conf);
|
||||
return NULL;
|
||||
}
|
||||
else if (ret == 1)
|
||||
{
|
||||
free(conf);
|
||||
return NULL;
|
||||
}
|
||||
return conf;
|
||||
}
|
||||
|
||||
/**
|
||||
* config_file_new:
|
||||
*
|
||||
* Loads a config file.
|
||||
* If @path is NULL, will create an empty config file.
|
||||
*
|
||||
* @return Returns NULL if file doesn't exist.
|
||||
**/
|
||||
config_file_t *config_file_new(const char *path)
|
||||
{
|
||||
return config_file_new_with_callback(path, NULL);
|
||||
}
|
||||
|
||||
config_file_t *config_file_new_from_path_to_string(const char *path)
|
||||
{
|
||||
/* Historically this read the file and re-parsed it through
|
||||
* config_file_new_from_string; since the line loops were
|
||||
* unified the two path constructors have identical behaviour
|
||||
* (same parse, same include handling, path set before parsing,
|
||||
* no callback), so route through config_file_new - which parses
|
||||
* in borrow mode: the conf adopts the file buffer and entries
|
||||
* point straight into it, skipping the per-entry key/value
|
||||
* allocations and copies the from_string detour forced. */
|
||||
return config_file_new(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* config_append_file:
|
||||
*
|
||||
* Loads a new config, and appends its data to @conf.
|
||||
* The key-value pairs of the new config file takes priority over the old.
|
||||
**/
|
||||
bool config_append_file(config_file_t *conf, const char *path)
|
||||
{
|
||||
config_file_t *new_conf = config_file_new_from_path_to_string(path);
|
||||
if (!new_conf)
|
||||
return false;
|
||||
return config_file_append_conf(conf, new_conf);
|
||||
}
|
||||
|
||||
/**
|
||||
* config_file_write:
|
||||
*
|
||||
* Write the current config to a file.
|
||||
**/
|
||||
bool config_file_write(config_file_t *conf, const char *path, bool sort)
|
||||
{
|
||||
if (!conf)
|
||||
return false;
|
||||
if (conf->flags & CONF_FILE_FLG_MODIFIED)
|
||||
{
|
||||
if (!path || !*path)
|
||||
config_file_dump(conf, stdout, sort);
|
||||
else
|
||||
{
|
||||
/* The stdio buffer is heap, not a local. At 16 KiB it was
|
||||
* twice the whole thread stack on the smallest target -
|
||||
* GEKKO threads get 8 KiB, see STACKSIZE in
|
||||
* rthreads/gx_pthread.h - and this is reached from a task
|
||||
* handler, so it runs on the task thread rather than the
|
||||
* main one whenever the queue is threaded:
|
||||
* input_autoconfigure_connect_handler ->
|
||||
* ..._scan_config_files_external -> ..._index_write ->
|
||||
* here, which is the path a gamepad being plugged in takes.
|
||||
* -fstack-usage put the frame at 16432 bytes.
|
||||
*
|
||||
* The buffer is the C library's, asked for by passing NULL
|
||||
* with a size: it then allocates, owns and releases it with
|
||||
* the stream, so there is nothing here to keep alive across
|
||||
* the writes or to free afterwards. A library that declines
|
||||
* leaves the stream on its own default, which is slower for
|
||||
* small writes rather than fatal - the same outcome the
|
||||
* allocation failing used to have. retro_vfs_file_open_impl()
|
||||
* asks the same way, and for a stronger reason there: on
|
||||
* Apple, a buffer supplied from outside clears __SMBF and
|
||||
* disqualifies fread()'s large-read fast path for the life of
|
||||
* the stream. */
|
||||
FILE *file = (FILE*)fopen_utf8(path, "wb");
|
||||
if (!file)
|
||||
return false;
|
||||
setvbuf(file, NULL, _IOFBF, 0x4000);
|
||||
config_file_dump(conf, file, sort);
|
||||
if (file != stdout)
|
||||
fclose(file);
|
||||
conf->flags &= ~CONF_FILE_FLG_MODIFIED;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (config_file_userdata.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <file/file_path.h>
|
||||
|
||||
#include <file/config_file_userdata.h>
|
||||
|
||||
int config_userdata_get_float(void *userdata, const char *key_str,
|
||||
float *value, float default_value)
|
||||
{
|
||||
char key[256];
|
||||
struct config_file_userdata *usr = (struct config_file_userdata*)userdata;
|
||||
*value = default_value;
|
||||
fill_pathname_join_delim(key, usr->prefix[0], key_str, '_', sizeof(key));
|
||||
if (config_get_float(usr->conf, key, value))
|
||||
return 1;
|
||||
fill_pathname_join_delim(key, usr->prefix[1], key_str, '_', sizeof(key));
|
||||
if (config_get_float(usr->conf, key, value))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_userdata_get_int(void *userdata, const char *key_str,
|
||||
int *value, int default_value)
|
||||
{
|
||||
char key[256];
|
||||
struct config_file_userdata *usr = (struct config_file_userdata*)userdata;
|
||||
*value = default_value;
|
||||
fill_pathname_join_delim(key, usr->prefix[0], key_str, '_', sizeof(key));
|
||||
if (config_get_int(usr->conf, key, value))
|
||||
return 1;
|
||||
fill_pathname_join_delim(key, usr->prefix[1], key_str, '_', sizeof(key));
|
||||
if (config_get_int(usr->conf, key, value))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_userdata_get_hex(void *userdata, const char *key_str,
|
||||
unsigned *value, unsigned default_value)
|
||||
{
|
||||
char key[256];
|
||||
struct config_file_userdata *usr = (struct config_file_userdata*)userdata;
|
||||
*value = default_value;
|
||||
fill_pathname_join_delim(key, usr->prefix[0], key_str, '_', sizeof(key));
|
||||
if (config_get_hex(usr->conf, key, value))
|
||||
return 1;
|
||||
fill_pathname_join_delim(key, usr->prefix[1], key_str, '_', sizeof(key));
|
||||
if (config_get_hex(usr->conf, key, value))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_userdata_get_float_array(void *userdata, const char *key_str,
|
||||
float **values, unsigned *out_num_values,
|
||||
const float *default_values, unsigned num_default_values)
|
||||
{
|
||||
char key[2][256];
|
||||
char *str = NULL;
|
||||
struct config_file_userdata *usr = (struct config_file_userdata*)userdata;
|
||||
fill_pathname_join_delim(key[0], usr->prefix[0], key_str, '_', sizeof(key[0]));
|
||||
fill_pathname_join_delim(key[1], usr->prefix[1], key_str, '_', sizeof(key[1]));
|
||||
if ( config_get_string(usr->conf, key[0], &str)
|
||||
|| config_get_string(usr->conf, key[1], &str))
|
||||
{
|
||||
unsigned count = 0;
|
||||
const char *p = str;
|
||||
char *tok, *end;
|
||||
float *arr;
|
||||
|
||||
/* Count tokens */
|
||||
while (*p)
|
||||
{
|
||||
while (*p == ' ')
|
||||
p++;
|
||||
if (!*p)
|
||||
break;
|
||||
count++;
|
||||
while (*p && *p != ' ')
|
||||
p++;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
free(str);
|
||||
goto use_defaults;
|
||||
}
|
||||
|
||||
arr = (float*)calloc(count, sizeof(float));
|
||||
if (!arr)
|
||||
{
|
||||
free(str);
|
||||
*values = NULL;
|
||||
*out_num_values = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parse tokens */
|
||||
count = 0;
|
||||
tok = str;
|
||||
while (*tok)
|
||||
{
|
||||
while (*tok == ' ')
|
||||
tok++;
|
||||
if (!*tok)
|
||||
break;
|
||||
arr[count++] = (float)strtod(tok, &end);
|
||||
tok = end;
|
||||
}
|
||||
|
||||
*values = arr;
|
||||
*out_num_values = count;
|
||||
free(str);
|
||||
return 1;
|
||||
}
|
||||
|
||||
use_defaults:
|
||||
if (num_default_values > 0 && default_values)
|
||||
{
|
||||
float *arr = (float*)calloc(num_default_values, sizeof(float));
|
||||
if (!arr)
|
||||
{
|
||||
*values = NULL;
|
||||
*out_num_values = 0;
|
||||
return 0;
|
||||
}
|
||||
memcpy(arr, default_values, sizeof(float) * num_default_values);
|
||||
*values = arr;
|
||||
*out_num_values = num_default_values;
|
||||
}
|
||||
else
|
||||
{
|
||||
*values = NULL;
|
||||
*out_num_values = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_userdata_get_int_array(void *userdata, const char *key_str,
|
||||
int **values, unsigned *out_num_values,
|
||||
const int *default_values, unsigned num_default_values)
|
||||
{
|
||||
char key[2][256];
|
||||
char *str = NULL;
|
||||
struct config_file_userdata *usr = (struct config_file_userdata*)userdata;
|
||||
fill_pathname_join_delim(key[0], usr->prefix[0], key_str, '_', sizeof(key[0]));
|
||||
fill_pathname_join_delim(key[1], usr->prefix[1], key_str, '_', sizeof(key[1]));
|
||||
if ( config_get_string(usr->conf, key[0], &str)
|
||||
|| config_get_string(usr->conf, key[1], &str))
|
||||
{
|
||||
unsigned i, count = 0;
|
||||
const char *p = str;
|
||||
|
||||
/* First pass: count tokens */
|
||||
while (*p)
|
||||
{
|
||||
while (*p == ' ')
|
||||
p++;
|
||||
if (!*p)
|
||||
break;
|
||||
count++;
|
||||
while (*p && *p != ' ')
|
||||
p++;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
free(str);
|
||||
*values = NULL;
|
||||
*out_num_values = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
*values = (int*)calloc(count, sizeof(int));
|
||||
if (!*values)
|
||||
{
|
||||
free(str);
|
||||
*out_num_values = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Second pass: parse integers */
|
||||
p = str;
|
||||
i = 0;
|
||||
while (*p && i < count)
|
||||
{
|
||||
char *end;
|
||||
while (*p == ' ')
|
||||
p++;
|
||||
if (!*p)
|
||||
break;
|
||||
(*values)[i++] = (int)strtol(p, &end, 0);
|
||||
p = end;
|
||||
}
|
||||
|
||||
*out_num_values = count;
|
||||
free(str);
|
||||
return 1;
|
||||
}
|
||||
|
||||
*values = (int*)calloc(num_default_values, sizeof(int));
|
||||
if (*values)
|
||||
memcpy(*values, default_values, sizeof(int) * num_default_values);
|
||||
*out_num_values = *values ? num_default_values : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_userdata_get_string(void *userdata, const char *key_str,
|
||||
char **output, const char *default_output)
|
||||
{
|
||||
char key[2][256];
|
||||
char *str = NULL;
|
||||
struct config_file_userdata *usr = (struct config_file_userdata*)userdata;
|
||||
fill_pathname_join_delim(key[0], usr->prefix[0], key_str, '_', sizeof(key[0]));
|
||||
fill_pathname_join_delim(key[1], usr->prefix[1], key_str, '_', sizeof(key[1]));
|
||||
if ( config_get_string(usr->conf, key[0], &str)
|
||||
|| config_get_string(usr->conf, key[1], &str))
|
||||
{
|
||||
*output = str;
|
||||
return 1;
|
||||
}
|
||||
*output = strdup(default_output);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void config_userdata_free(void *ptr)
|
||||
{
|
||||
if (ptr)
|
||||
free(ptr);
|
||||
}
|
||||
@@ -20,6 +20,8 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <retro_posix_source.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -199,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,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]))
|
||||
{
|
||||
@@ -424,12 +433,34 @@ size_t fill_pathname_slash(char *s, size_t len)
|
||||
* E.g..: s = "/tmp/some_dir", in_basename = "/some_content/foo.c",
|
||||
* replace = ".asm" => s = "/tmp/some_dir/foo.c.asm"
|
||||
**/
|
||||
/* Appends @in to the @_len bytes already in @s and returns the new
|
||||
* length, never more than @len - 1.
|
||||
*
|
||||
* strlcpy() reports the length of its *source*, so an accumulator that
|
||||
* adds the return value passes @len as soon as one part does not fit.
|
||||
* The 'len - _len' handed to the next call then underflows to a huge
|
||||
* size_t, and that call writes at 's + _len' -- already past the end --
|
||||
* with no effective bound. Clamping on the way in keeps the size
|
||||
* argument sane, and on the way out keeps the accumulator inside the
|
||||
* buffer for whatever the caller does next. */
|
||||
static size_t path_strlcat(char *s, size_t _len, const char *in, size_t len)
|
||||
{
|
||||
if (!len)
|
||||
return 0;
|
||||
if (_len > len - 1)
|
||||
_len = len - 1;
|
||||
_len += strlcpy(s + _len, in, len - _len);
|
||||
if (_len > len - 1)
|
||||
_len = len - 1;
|
||||
return _len;
|
||||
}
|
||||
|
||||
size_t fill_pathname_dir(char *s, const char *in_basename,
|
||||
const char *replace, size_t len)
|
||||
{
|
||||
size_t _len = fill_pathname_slash(s, len);
|
||||
_len += strlcpy(s + _len, path_basename(in_basename), len - _len);
|
||||
_len += strlcpy(s + _len, replace, len - _len);
|
||||
_len = path_strlcat(s, _len, path_basename(in_basename), len);
|
||||
_len = path_strlcat(s, _len, replace, len);
|
||||
return _len;
|
||||
}
|
||||
|
||||
@@ -542,7 +573,14 @@ size_t fill_pathname_parent_dir(char *s,
|
||||
if (s == in_dir)
|
||||
_len = strlen(s);
|
||||
else
|
||||
{
|
||||
/* strlcpy() reports the length of @in_dir, so a truncated copy
|
||||
* leaves _len past the end of @s; path_parent_dir() would then
|
||||
* scan back from outside the buffer. */
|
||||
_len = strlcpy(s, in_dir, len);
|
||||
if (len && _len > len - 1)
|
||||
_len = len - 1;
|
||||
}
|
||||
return path_parent_dir(s, _len);
|
||||
}
|
||||
|
||||
@@ -884,35 +922,43 @@ char *path_resolve_realpath(char *s, size_t len, bool resolve_symlinks)
|
||||
*
|
||||
* @return Length of the string copied into @s
|
||||
**/
|
||||
size_t path_relative_to(char *s,
|
||||
const char *path, const char *base, size_t len)
|
||||
/* Splits @path and @base at the end of their common leading
|
||||
* directories. Returns false when no relative form exists (Windows
|
||||
* paths on different drives); the relative form is then @path. */
|
||||
static bool path_relative_split(const char *path, const char *base,
|
||||
const char **trimmed_path, const char **trimmed_base)
|
||||
{
|
||||
size_t i, j;
|
||||
size_t _len;
|
||||
const char *trimmed_path, *trimmed_base;
|
||||
|
||||
#ifdef _WIN32
|
||||
/* For different drives, return absolute path */
|
||||
if (
|
||||
path
|
||||
&& base
|
||||
&& path[0] != '\0'
|
||||
path[0] != '\0'
|
||||
&& path[1] != '\0'
|
||||
&& base[0] != '\0'
|
||||
&& base[1] != '\0'
|
||||
&& path[1] == ':'
|
||||
&& base[1] == ':'
|
||||
&& path[0] != base[0])
|
||||
return strlcpy(s, path, len);
|
||||
return false;
|
||||
#endif
|
||||
|
||||
/* Trim common beginning - recognize both slash types */
|
||||
for (i = 0, j = 0; path[i] && base[i] && path[i] == base[i]; i++)
|
||||
if (PATH_CHAR_IS_SLASH(path[i]))
|
||||
j = i + 1;
|
||||
*trimmed_path = path + j;
|
||||
*trimmed_base = base + i;
|
||||
return true;
|
||||
}
|
||||
|
||||
trimmed_path = path + j;
|
||||
trimmed_base = base + i;
|
||||
size_t path_relative_to(char *s,
|
||||
const char *path, const char *base, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
size_t _len;
|
||||
const char *trimmed_path, *trimmed_base;
|
||||
|
||||
/* For different drives, return absolute path */
|
||||
if (!path_relative_split(path, base, &trimmed_path, &trimmed_base))
|
||||
return strlcpy(s, path, len);
|
||||
|
||||
/* Each segment of base turns into ".." */
|
||||
_len = 0;
|
||||
@@ -980,20 +1026,39 @@ void fill_pathname_resolve_relative(char *s,
|
||||
size_t fill_pathname_join(char *s, const char *dir,
|
||||
const char *path, size_t len)
|
||||
{
|
||||
size_t _len = 0;
|
||||
if (s != dir)
|
||||
_len = strlcpy(s, dir, len);
|
||||
if (*s)
|
||||
_len = fill_pathname_slash(s, len);
|
||||
_len += strlcpy(s + _len, path, len - _len);
|
||||
return _len;
|
||||
size_t _len = strlen(dir);
|
||||
|
||||
/* memmove() lands @dir in @s whether or not the two are the
|
||||
* same buffer, so the copy needs no aliasing test of its own.
|
||||
* Losing that test is what keeps @s written before it is read:
|
||||
* a "@s might already be @dir" branch leaves a path on which
|
||||
* the separator test below inspects a caller buffer that
|
||||
* nothing has written yet, which is what the copy is for.
|
||||
* It also covers a partial overlap, which the pointer compare
|
||||
* never did.
|
||||
*
|
||||
* strlcpy() reports the length of its source, so the clamp is
|
||||
* what a truncated copy leaves behind rather than what was
|
||||
* asked for - @s + @_len stays inside the buffer. */
|
||||
if (len)
|
||||
{
|
||||
if (_len > len - 1)
|
||||
_len = len - 1;
|
||||
memmove(s, dir, _len);
|
||||
s[_len] = '\0';
|
||||
}
|
||||
else
|
||||
_len = 0;
|
||||
|
||||
if (_len)
|
||||
_len = fill_pathname_slash(s, len);
|
||||
return _len + strlcpy(s + _len, path, len - _len);
|
||||
}
|
||||
|
||||
/* fill_pathname_join_special() is a macro alias of
|
||||
* fill_pathname_join() - see file_path.h. Historically it was a
|
||||
* separate function that skipped the @s != @dir alias guard (one
|
||||
* pointer compare ahead of two strlcpy calls - never a measurable
|
||||
* saving), which made any aliased call undefined: strlcpy aborts
|
||||
* separate function whose copy could not take an overlapping @s
|
||||
* and @dir, which made any aliased call undefined: strlcpy aborts
|
||||
* via __chk_fail_overlap under fortified libc on macOS while the
|
||||
* portable fallback hides the defect on other platforms.
|
||||
* libretro-common is vendored into cores that never see RetroArch's
|
||||
@@ -1008,8 +1073,8 @@ size_t fill_pathname_join_special_ext(char *s,
|
||||
size_t _len = fill_pathname_join(s, dir, path, len);
|
||||
if (*s)
|
||||
_len = fill_pathname_slash(s, len);
|
||||
_len += strlcpy(s + _len, last, len - _len);
|
||||
_len += strlcpy(s + _len, ext, len - _len);
|
||||
_len = path_strlcat(s, _len, last, len);
|
||||
_len = path_strlcat(s, _len, ext, len);
|
||||
return _len;
|
||||
}
|
||||
|
||||
@@ -1033,12 +1098,15 @@ size_t fill_pathname_join_delim(char *s, const char *dir,
|
||||
_len = strlen(dir);
|
||||
else
|
||||
_len = strlcpy(s, dir, len);
|
||||
if (len - _len < 2)
|
||||
return _len;
|
||||
/* _len is the length of @dir, which strlcpy() reports whether or not
|
||||
* it fit, so this has to be a bounds check and not a subtraction that
|
||||
* can wrap. */
|
||||
if (_len + 2 > len)
|
||||
return (len > 0) ? len - 1 : 0;
|
||||
s[_len++] = delim;
|
||||
s[_len ] = '\0';
|
||||
if (path)
|
||||
_len += strlcpy(s + _len, path, len - _len);
|
||||
_len = path_strlcat(s, _len, path, len);
|
||||
return _len;
|
||||
}
|
||||
|
||||
@@ -1256,38 +1324,46 @@ size_t fill_pathname_abbreviated_or_relative(char *s,
|
||||
const char *in_refpath, const char *in_path, size_t len)
|
||||
{
|
||||
size_t _len;
|
||||
char buf_a[PATH_MAX_LENGTH];
|
||||
char buf_b[PATH_MAX_LENGTH];
|
||||
int rel_slashes;
|
||||
const char *trimmed_path, *trimmed_base;
|
||||
char abs_path[PATH_MAX_LENGTH];
|
||||
char ref_path[PATH_MAX_LENGTH];
|
||||
|
||||
strlcpy(buf_a, in_path, sizeof(buf_a));
|
||||
strlcpy(buf_b, in_refpath, sizeof(buf_b));
|
||||
/* Expand paths which start with :\ to an absolute path */
|
||||
fill_pathname_expand_special(abs_path, in_path, sizeof(abs_path));
|
||||
pathname_conform_slashes_to_os(abs_path);
|
||||
|
||||
pathname_conform_slashes_to_os(buf_a);
|
||||
pathname_conform_slashes_to_os(buf_b);
|
||||
strlcpy(ref_path, in_refpath, sizeof(ref_path));
|
||||
pathname_conform_slashes_to_os(ref_path);
|
||||
|
||||
/* Expand paths which start with :\ to an absolute path.
|
||||
* Write into s (used as scratch for the absolute path). */
|
||||
s[0] = '\0';
|
||||
fill_pathname_expand_special(s, buf_a, len);
|
||||
/* Resolve a relative path against the referencing file's directory.
|
||||
* The join still carries its '..' segments and can be far longer
|
||||
* than the result, so it is built at full path size in ref_path
|
||||
* (only its directory is needed for the join) and never in @s. */
|
||||
if (!path_is_absolute(abs_path))
|
||||
{
|
||||
_len = fill_pathname_basedir(ref_path, ref_path, sizeof(ref_path));
|
||||
strlcpy(ref_path + _len, abs_path, sizeof(ref_path) - _len);
|
||||
path_resolve_realpath(ref_path, sizeof(ref_path), false);
|
||||
strlcpy(abs_path, ref_path, sizeof(abs_path));
|
||||
pathname_conform_slashes_to_os(abs_path);
|
||||
strlcpy(ref_path, in_refpath, sizeof(ref_path));
|
||||
pathname_conform_slashes_to_os(ref_path);
|
||||
}
|
||||
|
||||
/* Get the absolute path if it is not already */
|
||||
if (!path_is_absolute(s))
|
||||
fill_pathname_resolve_relative(s, buf_b, buf_a, len);
|
||||
pathname_conform_slashes_to_os(s);
|
||||
/* Depth of the relative form: one '..' per directory left in the
|
||||
* base past the common prefix, plus what remains of the path */
|
||||
if (path_relative_split(abs_path, ref_path, &trimmed_path, &trimmed_base))
|
||||
rel_slashes = get_pathname_num_slashes(trimmed_base)
|
||||
+ get_pathname_num_slashes(trimmed_path);
|
||||
else
|
||||
rel_slashes = get_pathname_num_slashes(abs_path);
|
||||
|
||||
/* s now holds the absolute path, buf_a is free.
|
||||
* Compute the relative path into buf_a. */
|
||||
path_relative_to(buf_a, s, buf_b, sizeof(buf_a));
|
||||
|
||||
/* buf_b is now also free. Save the absolute path there so we can
|
||||
* pass non-overlapping pointers to fill_pathname_abbreviate_special. */
|
||||
strlcpy(buf_b, s, sizeof(buf_b));
|
||||
_len = fill_pathname_abbreviate_special(s, buf_b, len);
|
||||
_len = fill_pathname_abbreviate_special(s, abs_path, len);
|
||||
|
||||
/* Use the shortest path, preferring the relative path */
|
||||
if ( get_pathname_num_slashes(buf_a)
|
||||
<= get_pathname_num_slashes(s))
|
||||
return strlcpy(s, buf_a, len);
|
||||
if (rel_slashes <= get_pathname_num_slashes(s))
|
||||
return path_relative_to(s, abs_path, ref_path, len);
|
||||
return _len;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <retro_posix_source.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -71,6 +73,12 @@
|
||||
static retro_vfs_stat_t path_stat32_cb = retro_vfs_stat_impl;
|
||||
static retro_vfs_stat_64_t path_stat64_cb = retro_vfs_stat_64_impl;
|
||||
static retro_vfs_mkdir_t path_mkdir_cb = retro_vfs_mkdir_impl;
|
||||
/* VFS API v5. NULL when a frontend older than v5 is in use, so the
|
||||
* wrappers report failure instead of touching the local file system
|
||||
* behind a foreign frontend's back. */
|
||||
static retro_vfs_set_readonly_t path_set_readonly_cb = retro_vfs_set_readonly_impl;
|
||||
static retro_vfs_get_mtime_t path_get_mtime_cb = retro_vfs_get_mtime_impl;
|
||||
static retro_vfs_set_mtime_t path_set_mtime_cb = retro_vfs_set_mtime_impl;
|
||||
|
||||
void path_vfs_init(const struct retro_vfs_interface_info* vfs_info)
|
||||
{
|
||||
@@ -80,6 +88,9 @@ void path_vfs_init(const struct retro_vfs_interface_info* vfs_info)
|
||||
path_stat32_cb = retro_vfs_stat_impl;
|
||||
path_stat64_cb = retro_vfs_stat_64_impl;
|
||||
path_mkdir_cb = retro_vfs_mkdir_impl;
|
||||
path_set_readonly_cb = retro_vfs_set_readonly_impl;
|
||||
path_get_mtime_cb = retro_vfs_get_mtime_impl;
|
||||
path_set_mtime_cb = retro_vfs_set_mtime_impl;
|
||||
|
||||
if (vfs_info->required_interface_version < PATH_REQUIRED_VFS_VERSION || !vfs_iface)
|
||||
return;
|
||||
@@ -91,6 +102,46 @@ void path_vfs_init(const struct retro_vfs_interface_info* vfs_info)
|
||||
path_stat64_cb = vfs_iface->stat_64;
|
||||
else
|
||||
path_stat64_cb = NULL;
|
||||
|
||||
/* Members a v5 frontend left NULL stay NULL: the wrappers then
|
||||
* report "unavailable" rather than dereferencing them. */
|
||||
path_set_readonly_cb = NULL;
|
||||
path_get_mtime_cb = NULL;
|
||||
path_set_mtime_cb = NULL;
|
||||
if (vfs_info->required_interface_version >= METADATA_REQUIRED_VFS_VERSION)
|
||||
{
|
||||
path_set_readonly_cb = vfs_iface->set_readonly;
|
||||
path_get_mtime_cb = vfs_iface->get_mtime;
|
||||
path_set_mtime_cb = vfs_iface->set_mtime;
|
||||
}
|
||||
}
|
||||
|
||||
bool path_is_readonly(const char *path)
|
||||
{
|
||||
if (path_stat64_cb)
|
||||
return (path_stat64_cb(path, NULL) & RETRO_VFS_STAT_IS_READONLY) != 0;
|
||||
return (path_stat32_cb(path, NULL) & RETRO_VFS_STAT_IS_READONLY) != 0;
|
||||
}
|
||||
|
||||
bool path_set_readonly(const char *path, bool readonly)
|
||||
{
|
||||
if (!path_set_readonly_cb)
|
||||
return false;
|
||||
return path_set_readonly_cb(path, readonly ? 1 : 0) == 0;
|
||||
}
|
||||
|
||||
bool path_get_mtime(const char *path, int64_t *mtime)
|
||||
{
|
||||
if (!path_get_mtime_cb || !mtime)
|
||||
return false;
|
||||
return path_get_mtime_cb(path, mtime) == 0;
|
||||
}
|
||||
|
||||
bool path_set_mtime(const char *path, int64_t mtime)
|
||||
{
|
||||
if (!path_set_mtime_cb)
|
||||
return false;
|
||||
return path_set_mtime_cb(path, mtime) == 0;
|
||||
}
|
||||
|
||||
int path_stat(const char *path)
|
||||
@@ -128,6 +179,11 @@ bool path_is_valid(const char *path)
|
||||
return (path_stat32_cb(path, NULL) & RETRO_VFS_STAT_IS_VALID) != 0;
|
||||
}
|
||||
|
||||
bool path_set_private(const char *path)
|
||||
{
|
||||
return retro_vfs_restrict_permissions_impl(path) == 0;
|
||||
}
|
||||
|
||||
int64_t path_get_size(const char *path)
|
||||
{
|
||||
int64_t filesize = 0;
|
||||
@@ -143,6 +199,22 @@ int64_t path_get_size(const char *path)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* path_rmdir:
|
||||
* @dir : directory path.
|
||||
*
|
||||
* Removes the empty directory @dir. There is no libretro VFS entry
|
||||
* for this, so it always goes through the built-in implementation.
|
||||
*
|
||||
* @return true if the directory was removed, otherwise false -
|
||||
* including a directory that is not empty, and platforms with no
|
||||
* directory removal, where an empty directory is simply left.
|
||||
**/
|
||||
bool path_rmdir(const char *dir)
|
||||
{
|
||||
return retro_vfs_rmdir_impl(dir) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* path_mkdir:
|
||||
* @dir : directory
|
||||
@@ -153,6 +225,34 @@ int64_t path_get_size(const char *path)
|
||||
*
|
||||
* @return true if directory could be created, otherwise false.
|
||||
**/
|
||||
/* True for "dev:name", "dev:/name" and "dev:\name" (a trailing
|
||||
* separator allowed): a directory whose parent is a device root. The
|
||||
* root cannot be probed everywhere - the PSP firmware refuses a stat of
|
||||
* "ms0:/" - and path_parent_dir() turns it into a relative "./", but a
|
||||
* mounted device's root always exists. */
|
||||
static bool path_parent_is_device_root(const char *p)
|
||||
{
|
||||
const char *colon = strchr(p, ':');
|
||||
const char *s;
|
||||
|
||||
if (!colon || colon == p)
|
||||
return false;
|
||||
for (s = p; s < colon; s++)
|
||||
if (*s == '/' || *s == '\\')
|
||||
return false;
|
||||
|
||||
s = colon + 1;
|
||||
if (*s == '/' || *s == '\\')
|
||||
s++;
|
||||
if (!*s || *s == '/' || *s == '\\')
|
||||
return false;
|
||||
while (*s && *s != '/' && *s != '\\')
|
||||
s++;
|
||||
if (*s)
|
||||
s++;
|
||||
return *s == '\0';
|
||||
}
|
||||
|
||||
bool path_mkdir(const char *dir)
|
||||
{
|
||||
bool norecurse = false;
|
||||
@@ -183,24 +283,29 @@ bool path_mkdir(const char *dir)
|
||||
if (path_is_directory(dir))
|
||||
return true;
|
||||
|
||||
/* Use heap. Real chance of stack
|
||||
* overflow if we recurse too hard. */
|
||||
if (!(basedir = strdup(dir)))
|
||||
return false;
|
||||
|
||||
path_parent_dir(basedir, strlen(basedir));
|
||||
|
||||
if (!*basedir || !strcmp(basedir, dir))
|
||||
{
|
||||
free(basedir);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( path_is_directory(basedir)
|
||||
|| path_mkdir(basedir))
|
||||
if (path_parent_is_device_root(dir))
|
||||
norecurse = true;
|
||||
else
|
||||
{
|
||||
/* Use heap. Real chance of stack
|
||||
* overflow if we recurse too hard. */
|
||||
if (!(basedir = strdup(dir)))
|
||||
return false;
|
||||
|
||||
free(basedir);
|
||||
path_parent_dir(basedir, strlen(basedir));
|
||||
|
||||
if (!*basedir || !strcmp(basedir, dir))
|
||||
{
|
||||
free(basedir);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( path_is_directory(basedir)
|
||||
|| path_mkdir(basedir))
|
||||
norecurse = true;
|
||||
|
||||
free(basedir);
|
||||
}
|
||||
|
||||
if (norecurse)
|
||||
{
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (nbio_intf.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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <file/nbio.h>
|
||||
|
||||
extern nbio_intf_t nbio_linux;
|
||||
extern nbio_intf_t nbio_mmap_unix;
|
||||
extern nbio_intf_t nbio_mmap_win32;
|
||||
extern nbio_intf_t nbio_stdio;
|
||||
|
||||
#ifndef _XBOX
|
||||
#if defined(_WIN32)
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1500
|
||||
|
||||
#ifndef HAVE_MMAP_WIN32
|
||||
#define HAVE_MMAP_WIN32
|
||||
#endif
|
||||
|
||||
#elif !defined(_MSC_VER)
|
||||
|
||||
#ifndef HAVE_MMAP_WIN32
|
||||
#define HAVE_MMAP_WIN32
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/* Disabled: the Linux AIO backend calls io_setup/io_destroy per
|
||||
* file handle, adding ~35us of kernel overhead per open+close.
|
||||
* For the small-file burst pattern used by menu icon loading
|
||||
* (40+ files of 4-64KB), this makes it ~2x slower than stdio.
|
||||
* Fall through to nbio_stdio on Linux until the AIO backend
|
||||
* shares a single context across handles. */
|
||||
#if 0 /* was: defined(__linux__) */
|
||||
static nbio_intf_t *internal_nbio = &nbio_linux;
|
||||
#elif defined(HAVE_MMAP) && defined(BSD)
|
||||
static nbio_intf_t *internal_nbio = &nbio_mmap_unix;
|
||||
#elif defined(HAVE_MMAP_WIN32)
|
||||
static nbio_intf_t *internal_nbio = &nbio_mmap_win32;
|
||||
#else
|
||||
static nbio_intf_t *internal_nbio = &nbio_stdio;
|
||||
#endif
|
||||
|
||||
void *nbio_open(const char * filename, unsigned mode)
|
||||
{
|
||||
return internal_nbio->open(filename, mode);
|
||||
}
|
||||
|
||||
void nbio_begin_read(void *data)
|
||||
{
|
||||
internal_nbio->begin_read(data);
|
||||
}
|
||||
|
||||
void nbio_begin_write(void *data)
|
||||
{
|
||||
internal_nbio->begin_write(data);
|
||||
}
|
||||
|
||||
bool nbio_iterate(void *data)
|
||||
{
|
||||
return internal_nbio->iterate(data);
|
||||
}
|
||||
|
||||
void nbio_resize(void *data, size_t len)
|
||||
{
|
||||
internal_nbio->resize(data, len);
|
||||
}
|
||||
|
||||
void *nbio_get_ptr(void *data, size_t* len)
|
||||
{
|
||||
return internal_nbio->get_ptr(data, len);
|
||||
}
|
||||
|
||||
void nbio_cancel(void *data)
|
||||
{
|
||||
internal_nbio->cancel(data);
|
||||
}
|
||||
|
||||
void nbio_free(void *data)
|
||||
{
|
||||
internal_nbio->free(data);
|
||||
}
|
||||
|
||||
void nbio_set_chunk_size(void *data, size_t chunk_size)
|
||||
{
|
||||
if (internal_nbio->set_chunk_size)
|
||||
internal_nbio->set_chunk_size(data, chunk_size);
|
||||
}
|
||||
|
||||
int nbio_get_fd(void *data)
|
||||
{
|
||||
if (internal_nbio->get_fd)
|
||||
return internal_nbio->get_fd(data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool nbio_get_progress(void *data, size_t *completed, size_t *total)
|
||||
{
|
||||
if (internal_nbio->get_progress)
|
||||
return internal_nbio->get_progress(data, completed, total);
|
||||
if (completed) *completed = 0;
|
||||
if (total) *total = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
void *nbio_load_entire(void *data, size_t *len)
|
||||
{
|
||||
/* Fast path: backend provides a direct implementation */
|
||||
if (internal_nbio->load_entire)
|
||||
return internal_nbio->load_entire(data, len);
|
||||
|
||||
/* Fallback: use the iterate loop */
|
||||
internal_nbio->begin_read(data);
|
||||
while (!internal_nbio->iterate(data));
|
||||
return internal_nbio->get_ptr(data, len);
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (nbio_linux.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 <file/nbio.h>
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <linux/aio_abi.h>
|
||||
|
||||
struct nbio_linux_t
|
||||
{
|
||||
void* ptr;
|
||||
aio_context_t ctx;
|
||||
struct iocb cb;
|
||||
size_t len;
|
||||
int fd;
|
||||
bool busy;
|
||||
};
|
||||
|
||||
/* there's also a Unix AIO thingy, but it's not in glibc
|
||||
* and we don't want more dependencies */
|
||||
|
||||
static int io_setup(unsigned nr, aio_context_t * ctxp)
|
||||
{
|
||||
return syscall(__NR_io_setup, nr, ctxp);
|
||||
}
|
||||
|
||||
static int io_destroy(aio_context_t ctx)
|
||||
{
|
||||
return syscall(__NR_io_destroy, ctx);
|
||||
}
|
||||
|
||||
static int io_submit(aio_context_t ctx, long nr, struct iocb ** cbp)
|
||||
{
|
||||
return syscall(__NR_io_submit, ctx, nr, cbp);
|
||||
}
|
||||
|
||||
static int io_cancel(aio_context_t ctx, struct iocb * iocb, struct io_event * result)
|
||||
{
|
||||
return syscall(__NR_io_cancel, ctx, iocb, result);
|
||||
}
|
||||
|
||||
static int io_getevents(aio_context_t ctx, long min_nr, long nr,
|
||||
struct io_event * events, struct timespec * timeout)
|
||||
{
|
||||
return syscall(__NR_io_getevents, ctx, min_nr, nr, events, timeout);
|
||||
}
|
||||
|
||||
static void nbio_begin_op(struct nbio_linux_t* handle, uint16_t op)
|
||||
{
|
||||
struct iocb * cbp = &handle->cb;
|
||||
|
||||
memset(&handle->cb, 0, sizeof(handle->cb));
|
||||
|
||||
handle->cb.aio_fildes = handle->fd;
|
||||
handle->cb.aio_lio_opcode = op;
|
||||
|
||||
handle->cb.aio_buf = (uint64_t)(uintptr_t)handle->ptr;
|
||||
handle->cb.aio_offset = 0;
|
||||
handle->cb.aio_nbytes = handle->len;
|
||||
|
||||
if (io_submit(handle->ctx, 1, &cbp) != 1)
|
||||
abort();
|
||||
|
||||
handle->busy = true;
|
||||
}
|
||||
|
||||
static void *nbio_linux_open(const char * filename, unsigned mode)
|
||||
{
|
||||
static const int o_flags[] = { O_RDONLY, O_RDWR|O_CREAT|O_TRUNC, O_RDWR, O_RDONLY, O_RDWR|O_CREAT|O_TRUNC };
|
||||
|
||||
aio_context_t ctx = 0;
|
||||
struct nbio_linux_t* handle = NULL;
|
||||
int fd = open(filename, o_flags[mode]|O_CLOEXEC, 0644);
|
||||
if (fd < 0)
|
||||
return NULL;
|
||||
|
||||
if (io_setup(128, &ctx) < 0)
|
||||
{
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
handle = (struct nbio_linux_t*)malloc(sizeof(struct nbio_linux_t));
|
||||
/* NULL-check the handle malloc: the next five lines dereferenced
|
||||
* 'handle' unconditionally, so OOM segfaulted. On failure we
|
||||
* also have to unwind the fd we open()'d above and the aio
|
||||
* context we io_setup()'d - both are resources that would be
|
||||
* owned by the handle had it been allocated. */
|
||||
if (!handle)
|
||||
{
|
||||
io_destroy(ctx);
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
handle->fd = fd;
|
||||
handle->ctx = ctx;
|
||||
handle->len = lseek(fd, 0, SEEK_END);
|
||||
handle->ptr = malloc(handle->len);
|
||||
/* Same pattern for the data buffer. Subsequent begin_read /
|
||||
* begin_write / iterate paths all assume handle->ptr is a valid
|
||||
* buffer of handle->len bytes; passing a NULL through them
|
||||
* would crash in the iocb setup. Unwind everything we got. */
|
||||
if (!handle->ptr)
|
||||
{
|
||||
free(handle);
|
||||
io_destroy(ctx);
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
handle->busy = false;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
static void nbio_linux_begin_read(void *data)
|
||||
{
|
||||
struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
|
||||
if (handle)
|
||||
nbio_begin_op(handle, IOCB_CMD_PREAD);
|
||||
}
|
||||
|
||||
static void nbio_linux_begin_write(void *data)
|
||||
{
|
||||
struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
|
||||
if (handle)
|
||||
nbio_begin_op(handle, IOCB_CMD_PWRITE);
|
||||
}
|
||||
|
||||
static bool nbio_linux_iterate(void *data)
|
||||
{
|
||||
struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
|
||||
if (!handle)
|
||||
return false;
|
||||
if (handle->busy)
|
||||
{
|
||||
struct io_event ev;
|
||||
if (io_getevents(handle->ctx, 0, 1, &ev, NULL) == 1)
|
||||
handle->busy = false;
|
||||
}
|
||||
return !handle->busy;
|
||||
}
|
||||
|
||||
static void nbio_linux_resize(void *data, size_t len)
|
||||
{
|
||||
struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
|
||||
void *new_ptr;
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
/* This works perfectly fine if this check is removed, but it
|
||||
* won't work on other nbio implementations */
|
||||
/* therefore, it's blocked so nobody accidentally relies on it */
|
||||
if (len < handle->len)
|
||||
abort();
|
||||
|
||||
if (ftruncate(handle->fd, len) != 0)
|
||||
abort(); /* this one returns void and I can't find any other way
|
||||
for it to report failure */
|
||||
|
||||
/* Attempt the realloc BEFORE committing the new length. If it
|
||||
* fails, the old pointer and its old size are still valid; the
|
||||
* caller can retry or abandon. Matches the sibling pattern in
|
||||
* nbio_stdio_resize - the pre-patch form
|
||||
*
|
||||
* handle->ptr = realloc(handle->ptr, len);
|
||||
* handle->len = len;
|
||||
*
|
||||
* set handle->ptr to NULL on OOM (leaking the old buffer) and
|
||||
* then claimed the new length, so subsequent get_ptr()/read/write
|
||||
* walked a NULL pointer assuming len bytes. */
|
||||
if (!(new_ptr = realloc(handle->ptr, len)))
|
||||
return;
|
||||
|
||||
handle->ptr = new_ptr;
|
||||
handle->len = len;
|
||||
}
|
||||
|
||||
static void *nbio_linux_get_ptr(void *data, size_t* len)
|
||||
{
|
||||
struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
|
||||
if (!handle)
|
||||
return NULL;
|
||||
if (len)
|
||||
*len = handle->len;
|
||||
if (!handle->busy)
|
||||
return handle->ptr;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void nbio_linux_cancel(void *data)
|
||||
{
|
||||
struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
if (handle->busy)
|
||||
{
|
||||
struct io_event ev;
|
||||
io_cancel(handle->ctx, &handle->cb, &ev);
|
||||
handle->busy = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void nbio_linux_free(void *data)
|
||||
{
|
||||
struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
io_destroy(handle->ctx);
|
||||
close(handle->fd);
|
||||
free(handle->ptr);
|
||||
free(handle);
|
||||
}
|
||||
|
||||
static int nbio_linux_get_fd(void *data)
|
||||
{
|
||||
struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
|
||||
if (handle)
|
||||
return handle->fd;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool nbio_linux_get_progress(void *data,
|
||||
size_t *completed, size_t *total)
|
||||
{
|
||||
struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
|
||||
if (!handle)
|
||||
{
|
||||
if (completed) *completed = 0;
|
||||
if (total) *total = 0;
|
||||
return false;
|
||||
}
|
||||
/* Linux AIO is all-or-nothing: either busy (0 done) or complete */
|
||||
if (completed) *completed = handle->busy ? 0 : handle->len;
|
||||
if (total) *total = handle->len;
|
||||
return handle->busy;
|
||||
}
|
||||
|
||||
static void *nbio_linux_load_entire(void *data, size_t *len)
|
||||
{
|
||||
struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
|
||||
if (!handle)
|
||||
return NULL;
|
||||
|
||||
/* Submit the read if not already in flight */
|
||||
if (!handle->busy)
|
||||
nbio_begin_op(handle, IOCB_CMD_PREAD);
|
||||
|
||||
/* Blocking wait: min_nr=1 means the kernel won't return
|
||||
* until the I/O is complete — one syscall, no poll loop. */
|
||||
if (handle->busy)
|
||||
{
|
||||
struct io_event ev;
|
||||
while (io_getevents(handle->ctx, 1, 1, &ev, NULL) != 1);
|
||||
handle->busy = false;
|
||||
}
|
||||
|
||||
if (len)
|
||||
*len = handle->len;
|
||||
return handle->ptr;
|
||||
}
|
||||
|
||||
nbio_intf_t nbio_linux = {
|
||||
nbio_linux_open,
|
||||
nbio_linux_begin_read,
|
||||
nbio_linux_begin_write,
|
||||
nbio_linux_iterate,
|
||||
nbio_linux_resize,
|
||||
nbio_linux_get_ptr,
|
||||
nbio_linux_cancel,
|
||||
nbio_linux_free,
|
||||
NULL, /* set_chunk_size - linux AIO submits entire read at once */
|
||||
nbio_linux_get_fd,
|
||||
nbio_linux_get_progress,
|
||||
nbio_linux_load_entire,
|
||||
"nbio_linux",
|
||||
};
|
||||
#else
|
||||
nbio_intf_t nbio_linux = {
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL, /* set_chunk_size */
|
||||
NULL, /* get_fd */
|
||||
NULL, /* get_progress */
|
||||
NULL, /* load_entire */
|
||||
"nbio_linux",
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,434 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (nbio_stdio.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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#if defined(WIIU)
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
#include <file/nbio.h>
|
||||
#include <encodings/utf.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1400
|
||||
#define ATLEAST_VC2005
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE - 0) >= 200112) || (defined(__POSIX_VISIBLE) && __POSIX_VISIBLE >= 200112) || (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112) || __USE_LARGEFILE || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64)
|
||||
#ifndef HAVE_64BIT_OFFSETS
|
||||
#define HAVE_64BIT_OFFSETS
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Default chunk size for non-blocking I/O iterations (bytes).
|
||||
* Callers may override per-handle via nbio_set_chunk_size(). */
|
||||
#define NBIO_DEFAULT_CHUNK_SIZE 65536
|
||||
|
||||
struct nbio_stdio_t
|
||||
{
|
||||
FILE* f;
|
||||
void* data;
|
||||
size_t progress;
|
||||
size_t len;
|
||||
size_t chunk_size;
|
||||
/*
|
||||
* possible values:
|
||||
* NBIO_READ, NBIO_WRITE - obvious
|
||||
* -1 - currently doing nothing
|
||||
* -2 - the pointer was reallocated since the last operation
|
||||
*/
|
||||
signed char op;
|
||||
signed char mode;
|
||||
};
|
||||
|
||||
#if !defined(_WIN32) || defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME)
|
||||
static const char *stdio_modes[] = { "rb", "wb", "r+b", "rb", "wb", "r+b" };
|
||||
#endif
|
||||
#if defined(_WIN32) && (!defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME))
|
||||
static const wchar_t *stdio_modes_w[] = { L"rb", L"wb", L"r+b", L"rb", L"wb", L"r+b" };
|
||||
#endif
|
||||
|
||||
static int64_t fseek_wrap(FILE *f, int64_t offset, int origin)
|
||||
{
|
||||
#ifdef ATLEAST_VC2005
|
||||
/* VC2005 and up have a special 64-bit fseek */
|
||||
return _fseeki64(f, offset, origin);
|
||||
#elif defined(HAVE_64BIT_OFFSETS)
|
||||
return fseeko(f, (off_t)offset, origin);
|
||||
#else
|
||||
return fseek(f, (long)offset, origin);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int64_t ftell_wrap(FILE *f)
|
||||
{
|
||||
#ifdef ATLEAST_VC2005
|
||||
/* VC2005 and up have a special 64-bit ftell */
|
||||
return _ftelli64(f);
|
||||
#elif defined(HAVE_64BIT_OFFSETS)
|
||||
return ftello(f);
|
||||
#else
|
||||
return ftell(f);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void *nbio_stdio_open(const char * filename, unsigned mode)
|
||||
{
|
||||
void *buf = NULL;
|
||||
struct nbio_stdio_t* handle = NULL;
|
||||
int64_t len = 0;
|
||||
#if defined(LEGACY_WIN32_RUNTIME)
|
||||
FILE* f;
|
||||
|
||||
if (win32_needs_local_encoding())
|
||||
f = fopen(filename, stdio_modes[mode]);
|
||||
else
|
||||
{
|
||||
wchar_t *filename_wide = utf8_to_utf16_string_alloc(filename);
|
||||
f = _wfopen(filename_wide, stdio_modes_w[mode]);
|
||||
|
||||
if (filename_wide)
|
||||
free(filename_wide);
|
||||
}
|
||||
#elif !defined(_WIN32) || defined(LEGACY_WIN32)
|
||||
FILE* f = fopen(filename, stdio_modes[mode]);
|
||||
#else
|
||||
wchar_t *filename_wide = utf8_to_utf16_string_alloc(filename);
|
||||
FILE* f = _wfopen(filename_wide, stdio_modes_w[mode]);
|
||||
|
||||
if (filename_wide)
|
||||
free(filename_wide);
|
||||
#endif
|
||||
if (!f)
|
||||
return NULL;
|
||||
|
||||
handle = (struct nbio_stdio_t*)malloc(sizeof(struct nbio_stdio_t));
|
||||
|
||||
if (!handle)
|
||||
{
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
handle->f = f;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case NBIO_WRITE:
|
||||
case BIO_WRITE:
|
||||
break;
|
||||
default:
|
||||
fseek_wrap(handle->f, 0, SEEK_END);
|
||||
len = ftell_wrap(handle->f);
|
||||
break;
|
||||
}
|
||||
|
||||
handle->mode = mode;
|
||||
|
||||
#if defined(WIIU)
|
||||
/* hit the aligned-buffer fast path on Wii U */
|
||||
if (len)
|
||||
buf = memalign(0x40, (size_t)len);
|
||||
#else
|
||||
if (len)
|
||||
buf = malloc((size_t)len);
|
||||
#endif
|
||||
|
||||
if (len && !buf)
|
||||
{
|
||||
free(handle);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
handle->data = buf;
|
||||
handle->len = len;
|
||||
handle->progress = handle->len;
|
||||
handle->chunk_size = NBIO_DEFAULT_CHUNK_SIZE;
|
||||
handle->op = -2;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
static void nbio_stdio_begin_read(void *data)
|
||||
{
|
||||
struct nbio_stdio_t *handle = (struct nbio_stdio_t*)data;
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
if (handle->op >= 0)
|
||||
abort();
|
||||
|
||||
fseek_wrap(handle->f, 0, SEEK_SET);
|
||||
|
||||
handle->op = NBIO_READ;
|
||||
handle->progress = 0;
|
||||
}
|
||||
|
||||
static void nbio_stdio_begin_write(void *data)
|
||||
{
|
||||
struct nbio_stdio_t *handle = (struct nbio_stdio_t*)data;
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
if (handle->op >= 0)
|
||||
abort();
|
||||
|
||||
fseek_wrap(handle->f, 0, SEEK_SET);
|
||||
handle->op = NBIO_WRITE;
|
||||
handle->progress = 0;
|
||||
}
|
||||
|
||||
static bool nbio_stdio_iterate(void *data)
|
||||
{
|
||||
size_t amount = 0;
|
||||
struct nbio_stdio_t *handle = (struct nbio_stdio_t*)data;
|
||||
|
||||
if (!handle)
|
||||
return false;
|
||||
|
||||
amount = handle->chunk_size;
|
||||
|
||||
if (amount > handle->len - handle->progress)
|
||||
amount = handle->len - handle->progress;
|
||||
|
||||
switch (handle->op)
|
||||
{
|
||||
case NBIO_READ:
|
||||
{
|
||||
size_t got;
|
||||
if (handle->mode == BIO_READ)
|
||||
{
|
||||
amount = handle->len;
|
||||
got = fread((char*)handle->data, 1, amount, handle->f);
|
||||
}
|
||||
else
|
||||
got = fread((char*)handle->data + handle->progress,
|
||||
1, amount, handle->f);
|
||||
if (got != amount)
|
||||
{
|
||||
/* I/O error, or the file shrank underneath us: freeze
|
||||
* progress at the bytes actually delivered and end the
|
||||
* operation. The transfer count used to be ignored and
|
||||
* progress advanced regardless, silently presenting
|
||||
* unwritten buffer memory as file content - worse now
|
||||
* that the partial-read video decoders consume bytes as
|
||||
* progress claims them. A finished-but-short state
|
||||
* (get_progress reporting no operation in flight with
|
||||
* progress < len) is the failure signal; a deliberate
|
||||
* cancel is distinct, as it reports progress == len. */
|
||||
handle->progress += got;
|
||||
handle->op = -1;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NBIO_WRITE:
|
||||
if (handle->mode == BIO_WRITE)
|
||||
{
|
||||
size_t written = 0;
|
||||
amount = handle->len;
|
||||
written = fwrite((char*)handle->data, 1, amount, handle->f);
|
||||
if (written != amount)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t put = fwrite((char*)handle->data + handle->progress,
|
||||
1, amount, handle->f);
|
||||
if (put != amount)
|
||||
{
|
||||
/* Same treatment for a short write (disk full, etc.):
|
||||
* an honest progress and a terminated operation
|
||||
* instead of a claim of completion. */
|
||||
handle->progress += put;
|
||||
handle->op = -1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
handle->progress += amount;
|
||||
|
||||
if (handle->progress == handle->len)
|
||||
handle->op = -1;
|
||||
return (handle->op < 0);
|
||||
}
|
||||
|
||||
static void nbio_stdio_resize(void *data, size_t len)
|
||||
{
|
||||
void *new_data = NULL;
|
||||
struct nbio_stdio_t *handle = (struct nbio_stdio_t*)data;
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
if (handle->op >= 0)
|
||||
abort();
|
||||
if (len < handle->len)
|
||||
abort();
|
||||
|
||||
/* Attempt the realloc BEFORE committing the new length. If it
|
||||
* fails, the old pointer and its old size are still valid; the
|
||||
* caller can retry or abandon. Pre-patch we wrote handle->len
|
||||
* = len first, then on realloc failure the handle claimed it
|
||||
* owned a larger buffer than it actually did -- subsequent
|
||||
* fread/fwrite iterate up to handle->len and walk off the end. */
|
||||
if (!(new_data = realloc(handle->data, len)))
|
||||
return;
|
||||
|
||||
handle->data = new_data;
|
||||
handle->len = len;
|
||||
handle->progress = len;
|
||||
handle->op = -1;
|
||||
}
|
||||
|
||||
static void *nbio_stdio_get_ptr(void *data, size_t* len)
|
||||
{
|
||||
struct nbio_stdio_t *handle = (struct nbio_stdio_t*)data;
|
||||
if (!handle)
|
||||
return NULL;
|
||||
if (len)
|
||||
*len = handle->len;
|
||||
/* The buffer is allocated once at open and never moves, so during a
|
||||
* READ it is safe to hand out: the leading get_progress() bytes are
|
||||
* valid, the rest merely unread - which is exactly what the
|
||||
* partial-read video still decoders consume (task_image sets their
|
||||
* byte wall from the progress). Returning NULL here mid-read made
|
||||
* the early decode - and adoption of a still-reading handle - fail
|
||||
* with a NULL buffer. Write-class operations keep the old
|
||||
* conservative NULL: their buffer may be resized. */
|
||||
if ( handle->op == -1
|
||||
|| handle->op == NBIO_READ
|
||||
|| handle->op == BIO_READ)
|
||||
return handle->data;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void nbio_stdio_cancel(void *data)
|
||||
{
|
||||
struct nbio_stdio_t *handle = (struct nbio_stdio_t*)data;
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
handle->op = -1;
|
||||
handle->progress = handle->len;
|
||||
}
|
||||
|
||||
static void nbio_stdio_free(void *data)
|
||||
{
|
||||
struct nbio_stdio_t *handle = (struct nbio_stdio_t*)data;
|
||||
if (!handle)
|
||||
return;
|
||||
if (handle->op >= 0)
|
||||
abort();
|
||||
fclose(handle->f);
|
||||
free(handle->data);
|
||||
|
||||
handle->f = NULL;
|
||||
handle->data = NULL;
|
||||
free(handle);
|
||||
}
|
||||
|
||||
static void nbio_stdio_set_chunk_size(void *data, size_t chunk_size)
|
||||
{
|
||||
struct nbio_stdio_t *handle = (struct nbio_stdio_t*)data;
|
||||
if (!handle)
|
||||
return;
|
||||
handle->chunk_size = (chunk_size > 0) ? chunk_size : NBIO_DEFAULT_CHUNK_SIZE;
|
||||
}
|
||||
|
||||
static int nbio_stdio_get_fd(void *data)
|
||||
{
|
||||
#if !defined(_WIN32) || defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME)
|
||||
struct nbio_stdio_t *handle = (struct nbio_stdio_t*)data;
|
||||
if (handle && handle->f)
|
||||
return fileno(handle->f);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool nbio_stdio_get_progress(void *data,
|
||||
size_t *completed, size_t *total)
|
||||
{
|
||||
struct nbio_stdio_t *handle = (struct nbio_stdio_t*)data;
|
||||
if (!handle)
|
||||
{
|
||||
if (completed) *completed = 0;
|
||||
if (total) *total = 0;
|
||||
return false;
|
||||
}
|
||||
if (completed) *completed = handle->progress;
|
||||
if (total) *total = handle->len;
|
||||
return (handle->op >= 0);
|
||||
}
|
||||
|
||||
static void *nbio_stdio_load_entire(void *data, size_t *len)
|
||||
{
|
||||
struct nbio_stdio_t *handle = (struct nbio_stdio_t*)data;
|
||||
if (!handle || !handle->f)
|
||||
return NULL;
|
||||
|
||||
fseek_wrap(handle->f, 0, SEEK_SET);
|
||||
|
||||
/* Single fread of the entire file */
|
||||
if (handle->len > 0)
|
||||
{
|
||||
size_t got = fread((char*)handle->data, 1, handle->len, handle->f);
|
||||
if (got != handle->len)
|
||||
{
|
||||
/* Report the failure instead of claiming a full buffer; the
|
||||
* dispatch layer's caller falls back to begin_read + iterate
|
||||
* (begin_read rewinds), where a genuinely unreadable file
|
||||
* fails again with a frozen progress and the task layer
|
||||
* cancels cleanly. */
|
||||
handle->progress = got;
|
||||
handle->op = -1;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
handle->progress = handle->len;
|
||||
handle->op = -1;
|
||||
|
||||
if (len)
|
||||
*len = handle->len;
|
||||
return handle->data;
|
||||
}
|
||||
|
||||
nbio_intf_t nbio_stdio = {
|
||||
nbio_stdio_open,
|
||||
nbio_stdio_begin_read,
|
||||
nbio_stdio_begin_write,
|
||||
nbio_stdio_iterate,
|
||||
nbio_stdio_resize,
|
||||
nbio_stdio_get_ptr,
|
||||
nbio_stdio_cancel,
|
||||
nbio_stdio_free,
|
||||
nbio_stdio_set_chunk_size,
|
||||
nbio_stdio_get_fd,
|
||||
nbio_stdio_get_progress,
|
||||
nbio_stdio_load_entire,
|
||||
"nbio_stdio",
|
||||
};
|
||||
@@ -1,253 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (nbio_unixmmap.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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <file/nbio.h>
|
||||
|
||||
#if defined(HAVE_MMAP) && defined(BSD)
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#ifndef O_CLOEXEC
|
||||
#define O_CLOEXEC 0x1000000
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#ifndef O_CLOEXEC
|
||||
#define O_CLOEXEC 0
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
struct nbio_mmap_unix_t
|
||||
{
|
||||
void* ptr;
|
||||
size_t len;
|
||||
int fd;
|
||||
int map_flags;
|
||||
};
|
||||
|
||||
static void *nbio_mmap_unix_open(const char * filename, unsigned mode)
|
||||
{
|
||||
static const int o_flags[] = { O_RDONLY, O_RDWR|O_CREAT|O_TRUNC, O_RDWR, O_RDONLY, O_RDWR|O_CREAT|O_TRUNC };
|
||||
static const int map_flags[] = { PROT_READ, PROT_WRITE|PROT_READ, PROT_WRITE|PROT_READ, PROT_READ, PROT_WRITE|PROT_READ };
|
||||
|
||||
size_t _len;
|
||||
void* ptr = NULL;
|
||||
struct nbio_mmap_unix_t* handle = NULL;
|
||||
int fd = open(filename, o_flags[mode]|O_CLOEXEC, 0644);
|
||||
if (fd < 0)
|
||||
return NULL;
|
||||
|
||||
_len = lseek(fd, 0, SEEK_END);
|
||||
if (_len != 0)
|
||||
ptr = mmap(NULL, _len, map_flags[mode], MAP_SHARED, fd, 0);
|
||||
|
||||
if (ptr == MAP_FAILED)
|
||||
{
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
handle = malloc(sizeof(struct nbio_mmap_unix_t));
|
||||
/* NULL-check the handle malloc: the four field writes below
|
||||
* would segfault on OOM. On failure munmap the region and
|
||||
* close the fd we acquired above - both were destined to be
|
||||
* owned by the handle and will leak otherwise. */
|
||||
if (!handle)
|
||||
{
|
||||
if (_len != 0)
|
||||
munmap(ptr, _len);
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
handle->fd = fd;
|
||||
handle->map_flags = map_flags[mode];
|
||||
handle->len = _len;
|
||||
handle->ptr = ptr;
|
||||
|
||||
/* For read-only mappings the fd is no longer needed once mmap has
|
||||
* succeeded — the mapping remains valid after close(). Release it
|
||||
* immediately so that loading many small files (e.g. hundreds of
|
||||
* menu thumbnails via the async task queue) doesn't exhaust
|
||||
* RLIMIT_NOFILE and cause unrelated fopen() calls to fail. The
|
||||
* resize path (write mode) still needs the fd for ftruncate, so
|
||||
* only close for read-only modes. */
|
||||
if (o_flags[mode] == O_RDONLY)
|
||||
{
|
||||
close(fd);
|
||||
handle->fd = -1;
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
static void nbio_mmap_unix_begin_read(void *data)
|
||||
{
|
||||
/* not needed */
|
||||
}
|
||||
|
||||
static void nbio_mmap_unix_begin_write(void *data)
|
||||
{
|
||||
/* not needed */
|
||||
}
|
||||
|
||||
static bool nbio_mmap_unix_iterate(void *data)
|
||||
{
|
||||
return true; /* not needed */
|
||||
}
|
||||
|
||||
static void nbio_mmap_unix_resize(void *data, size_t len)
|
||||
{
|
||||
struct nbio_mmap_unix_t* handle = (struct nbio_mmap_unix_t*)data;
|
||||
if (!handle)
|
||||
return;
|
||||
if (len < handle->len)
|
||||
{
|
||||
/* this works perfectly fine if this check is removed, but it
|
||||
* won't work on other nbio implementations */
|
||||
/* therefore, it's blocked so nobody accidentally relies on it */
|
||||
abort();
|
||||
}
|
||||
|
||||
if (ftruncate(handle->fd, len) != 0)
|
||||
abort(); /* this one returns void and I can't find any other
|
||||
way for it to report failure */
|
||||
|
||||
munmap(handle->ptr, handle->len);
|
||||
|
||||
handle->ptr = mmap(NULL, len, handle->map_flags, MAP_SHARED, handle->fd, 0);
|
||||
handle->len = len;
|
||||
|
||||
if (handle->ptr == MAP_FAILED)
|
||||
abort();
|
||||
}
|
||||
|
||||
static void *nbio_mmap_unix_get_ptr(void *data, size_t *len)
|
||||
{
|
||||
struct nbio_mmap_unix_t* handle = (struct nbio_mmap_unix_t*)data;
|
||||
if (!handle)
|
||||
return NULL;
|
||||
if (len)
|
||||
*len = handle->len;
|
||||
return handle->ptr;
|
||||
}
|
||||
|
||||
static void nbio_mmap_unix_cancel(void *data)
|
||||
{
|
||||
/* not needed */
|
||||
}
|
||||
|
||||
static void nbio_mmap_unix_free(void *data)
|
||||
{
|
||||
struct nbio_mmap_unix_t* handle = (struct nbio_mmap_unix_t*)data;
|
||||
if (!handle)
|
||||
return;
|
||||
if (handle->fd >= 0)
|
||||
close(handle->fd);
|
||||
munmap(handle->ptr, handle->len);
|
||||
free(handle);
|
||||
}
|
||||
|
||||
static int nbio_mmap_unix_get_fd(void *data)
|
||||
{
|
||||
struct nbio_mmap_unix_t* handle = (struct nbio_mmap_unix_t*)data;
|
||||
if (handle)
|
||||
return handle->fd;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool nbio_mmap_unix_get_progress(void *data,
|
||||
size_t *completed, size_t *total)
|
||||
{
|
||||
struct nbio_mmap_unix_t* handle = (struct nbio_mmap_unix_t*)data;
|
||||
if (!handle)
|
||||
{
|
||||
if (completed) *completed = 0;
|
||||
if (total) *total = 0;
|
||||
return false;
|
||||
}
|
||||
/* mmap is always "complete" — pages fault on demand */
|
||||
if (completed) *completed = handle->len;
|
||||
if (total) *total = handle->len;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void *nbio_mmap_unix_load_entire(void *data, size_t *len)
|
||||
{
|
||||
/* mmap: data is already mapped — just return the pointer.
|
||||
* No begin_read/iterate ceremony needed. */
|
||||
struct nbio_mmap_unix_t* handle = (struct nbio_mmap_unix_t*)data;
|
||||
if (!handle)
|
||||
return NULL;
|
||||
if (len)
|
||||
*len = handle->len;
|
||||
return handle->ptr;
|
||||
}
|
||||
|
||||
nbio_intf_t nbio_mmap_unix = {
|
||||
nbio_mmap_unix_open,
|
||||
nbio_mmap_unix_begin_read,
|
||||
nbio_mmap_unix_begin_write,
|
||||
nbio_mmap_unix_iterate,
|
||||
nbio_mmap_unix_resize,
|
||||
nbio_mmap_unix_get_ptr,
|
||||
nbio_mmap_unix_cancel,
|
||||
nbio_mmap_unix_free,
|
||||
NULL, /* set_chunk_size - mmap doesn't chunk */
|
||||
nbio_mmap_unix_get_fd,
|
||||
nbio_mmap_unix_get_progress,
|
||||
nbio_mmap_unix_load_entire,
|
||||
"nbio_mmap_unix",
|
||||
};
|
||||
#else
|
||||
nbio_intf_t nbio_mmap_unix = {
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL, /* set_chunk_size */
|
||||
NULL, /* get_fd */
|
||||
NULL, /* get_progress */
|
||||
NULL, /* load_entire */
|
||||
"nbio_mmap_unix",
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,271 +0,0 @@
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (nbio_windowsmmap.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 <file/nbio.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1500
|
||||
|
||||
#ifndef HAVE_MMAP_WIN32
|
||||
#define HAVE_MMAP_WIN32
|
||||
#endif
|
||||
|
||||
#elif !defined(_MSC_VER)
|
||||
|
||||
#ifndef HAVE_MMAP_WIN32
|
||||
#define HAVE_MMAP_WIN32
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_MMAP_WIN32)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <encodings/utf.h>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#ifndef FILE_SHARE_ALL
|
||||
#define FILE_SHARE_ALL (FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE)
|
||||
#endif
|
||||
|
||||
struct nbio_mmap_win32_t
|
||||
{
|
||||
HANDLE file;
|
||||
void* ptr;
|
||||
size_t len;
|
||||
bool is_write;
|
||||
};
|
||||
|
||||
static void *nbio_mmap_win32_open(const char * filename, unsigned mode)
|
||||
{
|
||||
static const DWORD dispositions[] = { OPEN_EXISTING, CREATE_ALWAYS, OPEN_ALWAYS, OPEN_EXISTING, CREATE_ALWAYS };
|
||||
HANDLE mem;
|
||||
#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500
|
||||
LARGE_INTEGER len;
|
||||
#else
|
||||
SIZE_T len;
|
||||
#endif
|
||||
struct nbio_mmap_win32_t* handle = NULL;
|
||||
void* ptr = NULL;
|
||||
bool is_write = (mode == NBIO_WRITE || mode == NBIO_UPDATE || mode == BIO_WRITE);
|
||||
DWORD access = (is_write ? GENERIC_READ|GENERIC_WRITE : GENERIC_READ);
|
||||
#if defined(LEGACY_WIN32_RUNTIME)
|
||||
HANDLE file;
|
||||
|
||||
if (win32_needs_local_encoding())
|
||||
file = CreateFileA(filename, access, FILE_SHARE_ALL, NULL, dispositions[mode], FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
else
|
||||
{
|
||||
wchar_t *filename_wide = utf8_to_utf16_string_alloc(filename);
|
||||
file = CreateFileW(filename_wide, access, FILE_SHARE_ALL, NULL, dispositions[mode], FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
|
||||
if (filename_wide)
|
||||
free(filename_wide);
|
||||
}
|
||||
#elif !defined(_WIN32) || defined(LEGACY_WIN32)
|
||||
HANDLE file = CreateFile(filename, access, FILE_SHARE_ALL, NULL, dispositions[mode], FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
#else
|
||||
wchar_t *filename_wide = utf8_to_utf16_string_alloc(filename);
|
||||
#ifdef __WINRT__
|
||||
HANDLE file = CreateFile2(filename_wide, access, FILE_SHARE_ALL, dispositions[mode], NULL);
|
||||
#else
|
||||
HANDLE file = CreateFileW(filename_wide, access, FILE_SHARE_ALL, NULL, dispositions[mode], FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
#endif
|
||||
|
||||
if (filename_wide)
|
||||
free(filename_wide);
|
||||
#endif
|
||||
|
||||
if (file == INVALID_HANDLE_VALUE)
|
||||
return NULL;
|
||||
|
||||
#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500
|
||||
/* GetFileSizeEx is new for Windows 2000 */
|
||||
GetFileSizeEx(file, &len);
|
||||
mem = CreateFileMapping(file, NULL, is_write ? PAGE_READWRITE : PAGE_READONLY, 0, 0, NULL);
|
||||
ptr = MapViewOfFile(mem, is_write ? (FILE_MAP_READ|FILE_MAP_WRITE) : FILE_MAP_READ, 0, 0, len.QuadPart);
|
||||
#else
|
||||
GetFileSize(file, &len);
|
||||
mem = CreateFileMapping(file, NULL, is_write ? PAGE_READWRITE : PAGE_READONLY, 0, 0, NULL);
|
||||
ptr = MapViewOfFile(mem, is_write ? (FILE_MAP_READ|FILE_MAP_WRITE) : FILE_MAP_READ, 0, 0, len);
|
||||
#endif
|
||||
|
||||
CloseHandle(mem);
|
||||
|
||||
handle = (struct nbio_mmap_win32_t*)malloc(sizeof(struct nbio_mmap_win32_t));
|
||||
/* NULL-check the handle malloc: the field writes below would
|
||||
* segfault on OOM. On failure unmap the view and close the
|
||||
* file handle - both are resources destined to be owned by
|
||||
* the handle and will leak otherwise. */
|
||||
if (!handle)
|
||||
{
|
||||
if (ptr)
|
||||
UnmapViewOfFile(ptr);
|
||||
CloseHandle(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
handle->file = file;
|
||||
handle->is_write = is_write;
|
||||
#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500
|
||||
handle->len = len.QuadPart;
|
||||
#else
|
||||
handle->len = len;
|
||||
#endif
|
||||
handle->ptr = ptr;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
static void nbio_mmap_win32_begin_read(void *data)
|
||||
{
|
||||
/* not needed */
|
||||
}
|
||||
|
||||
static void nbio_mmap_win32_begin_write(void *data)
|
||||
{
|
||||
/* not needed */
|
||||
}
|
||||
|
||||
static bool nbio_mmap_win32_iterate(void *data)
|
||||
{
|
||||
/* not needed */
|
||||
return true;
|
||||
}
|
||||
|
||||
static void nbio_mmap_win32_resize(void *data, size_t len)
|
||||
{
|
||||
#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500
|
||||
LARGE_INTEGER len_li;
|
||||
#else
|
||||
SIZE_T len_li;
|
||||
#endif
|
||||
HANDLE mem;
|
||||
struct nbio_mmap_win32_t* handle = (struct nbio_mmap_win32_t*)data;
|
||||
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
if (len < handle->len)
|
||||
{
|
||||
/* this works perfectly fine if this check is removed,
|
||||
* but it won't work on other nbio implementations */
|
||||
/* therefore, it's blocked so nobody accidentally
|
||||
* relies on it. */
|
||||
abort();
|
||||
}
|
||||
|
||||
#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500
|
||||
/* SetFilePointerEx is new for Windows 2000 */
|
||||
len_li.QuadPart = len;
|
||||
SetFilePointerEx(handle->file, len_li, NULL, FILE_BEGIN);
|
||||
#else
|
||||
len_li = len;
|
||||
SetFilePointer(handle->file, len_li, NULL, FILE_BEGIN);
|
||||
#endif
|
||||
|
||||
if (!SetEndOfFile(handle->file))
|
||||
abort(); /* this one returns void and I can't find any other way for it to report failure */
|
||||
handle->len = len;
|
||||
|
||||
UnmapViewOfFile(handle->ptr);
|
||||
mem = CreateFileMapping(handle->file, NULL, handle->is_write ? PAGE_READWRITE : PAGE_READONLY, 0, 0, NULL);
|
||||
handle->ptr = MapViewOfFile(mem, handle->is_write ? (FILE_MAP_READ|FILE_MAP_WRITE) : FILE_MAP_READ, 0, 0, len);
|
||||
CloseHandle(mem);
|
||||
|
||||
if (!handle->ptr)
|
||||
abort();
|
||||
}
|
||||
|
||||
static void *nbio_mmap_win32_get_ptr(void *data, size_t* len)
|
||||
{
|
||||
struct nbio_mmap_win32_t* handle = (struct nbio_mmap_win32_t*)data;
|
||||
if (!handle)
|
||||
return NULL;
|
||||
if (len)
|
||||
*len = handle->len;
|
||||
return handle->ptr;
|
||||
}
|
||||
|
||||
static void nbio_mmap_win32_cancel(void *data)
|
||||
{
|
||||
/* not needed */
|
||||
}
|
||||
|
||||
static void nbio_mmap_win32_free(void *data)
|
||||
{
|
||||
struct nbio_mmap_win32_t* handle = (struct nbio_mmap_win32_t*)data;
|
||||
if (!handle)
|
||||
return;
|
||||
CloseHandle(handle->file);
|
||||
UnmapViewOfFile(handle->ptr);
|
||||
free(handle);
|
||||
}
|
||||
|
||||
static void *nbio_mmap_win32_load_entire(void *data, size_t *len)
|
||||
{
|
||||
/* mmap: data is already mapped — just return the pointer. */
|
||||
struct nbio_mmap_win32_t* handle = (struct nbio_mmap_win32_t*)data;
|
||||
if (!handle)
|
||||
return NULL;
|
||||
if (len)
|
||||
*len = handle->len;
|
||||
return handle->ptr;
|
||||
}
|
||||
|
||||
nbio_intf_t nbio_mmap_win32 = {
|
||||
nbio_mmap_win32_open,
|
||||
nbio_mmap_win32_begin_read,
|
||||
nbio_mmap_win32_begin_write,
|
||||
nbio_mmap_win32_iterate,
|
||||
nbio_mmap_win32_resize,
|
||||
nbio_mmap_win32_get_ptr,
|
||||
nbio_mmap_win32_cancel,
|
||||
nbio_mmap_win32_free,
|
||||
NULL, /* set_chunk_size - mmap doesn't chunk */
|
||||
NULL, /* get_fd - win32 uses HANDLE, not fd */
|
||||
NULL, /* get_progress - mmap is instant */
|
||||
nbio_mmap_win32_load_entire,
|
||||
"nbio_mmap_win32",
|
||||
};
|
||||
#else
|
||||
nbio_intf_t nbio_mmap_win32 = {
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL, /* set_chunk_size */
|
||||
NULL, /* get_fd */
|
||||
NULL, /* get_progress */
|
||||
NULL, /* load_entire */
|
||||
"nbio_mmap_win32",
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,119 +0,0 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (rbmp_file.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.
|
||||
*/
|
||||
|
||||
/* Stream and path adapters for the pure BMP encoder in
|
||||
* formats/bmp/rbmp_encode.c. This TU holds every part of the rbmp
|
||||
* encode surface that touches a stream or the filesystem; the encoder
|
||||
* itself is buffer-only (rbmp_save_image_string / rbmp_row_size /
|
||||
* rbmp_encode_row) with no stream or VFS dependency at all.
|
||||
*
|
||||
* rbmp_save_image() is deprecated: new callers should prefer
|
||||
* rbmp_save_image_string() plus a single filestream_write_file() at
|
||||
* the edge (one open/bulk-write/close instead of a write per row), or
|
||||
* open an intfstream themselves and use rbmp_save_image_stream(). */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <libretro.h>
|
||||
#include <streams/interface_stream.h>
|
||||
#include <formats/rbmp.h>
|
||||
|
||||
bool rbmp_save_image_stream(
|
||||
intfstream_t *intf_s,
|
||||
const void *frame,
|
||||
unsigned width, unsigned height, unsigned pitch,
|
||||
enum rbmp_source_type type)
|
||||
{
|
||||
unsigned j;
|
||||
uint8_t header[54];
|
||||
size_t line_size;
|
||||
uint8_t *line = NULL;
|
||||
bool ret = true;
|
||||
const uint8_t *src = (const uint8_t*)frame;
|
||||
/* See rbmp_save_image_string regarding the signedness of pitch. */
|
||||
int s_pitch = (int)pitch;
|
||||
|
||||
if (!intf_s || !frame || !width || !height)
|
||||
return false;
|
||||
|
||||
line_size = rbmp_row_size(width, type);
|
||||
|
||||
form_bmp_header(header, width, height, type == RBMP_SOURCE_TYPE_ARGB8888);
|
||||
if (intfstream_write(intf_s, header, sizeof(header))
|
||||
!= (int64_t)sizeof(header))
|
||||
return false;
|
||||
|
||||
/* Whole-block fast path: when the source stride equals the padded
|
||||
* BMP row size and the rows already carry the output byte order
|
||||
* (BGR24 with 4-byte-aligned width, or ARGB whose memory layout is
|
||||
* BMP's 32bpp layout verbatim), the pixel block on disk is the
|
||||
* source buffer byte for byte. Hand it to the stream in one write:
|
||||
* no line buffer, no per-row calls, zero copies on this side. */
|
||||
if ( s_pitch > 0
|
||||
&& (size_t)s_pitch == line_size
|
||||
&& ( type == RBMP_SOURCE_TYPE_BGR24
|
||||
|| type == RBMP_SOURCE_TYPE_ARGB8888))
|
||||
{
|
||||
int64_t block = (int64_t)line_size * height;
|
||||
return intfstream_write(intf_s, src, block) == block;
|
||||
}
|
||||
|
||||
if (!(line = (uint8_t*)malloc(line_size)))
|
||||
return false;
|
||||
|
||||
for (j = 0; j < height; j++, src += s_pitch)
|
||||
{
|
||||
const uint8_t *row = rbmp_encode_row(line, src, width,
|
||||
pitch, type);
|
||||
if (intfstream_write(intf_s, row, line_size) != (int64_t)line_size)
|
||||
{
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(line);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool rbmp_save_image(
|
||||
const char *filename,
|
||||
const void *frame,
|
||||
unsigned width, unsigned height,
|
||||
unsigned pitch, enum rbmp_source_type type)
|
||||
{
|
||||
bool ret = false;
|
||||
intfstream_t *intf_s = intfstream_open_file(filename,
|
||||
RETRO_VFS_FILE_ACCESS_WRITE,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
|
||||
if (!intf_s)
|
||||
return false;
|
||||
|
||||
ret = rbmp_save_image_stream(intf_s, frame,
|
||||
width, height, pitch, type);
|
||||
|
||||
intfstream_close(intf_s);
|
||||
free(intf_s);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -37,6 +37,11 @@ static retro_vfs_readdir_t dirent_readdir_cb = NULL;
|
||||
static retro_vfs_dirent_get_name_t dirent_dirent_get_name_cb = NULL;
|
||||
static retro_vfs_dirent_is_dir_t dirent_dirent_is_dir_cb = NULL;
|
||||
static retro_vfs_closedir_t dirent_closedir_cb = NULL;
|
||||
static retro_vfs_dirent_stat_t dirent_dirent_stat_cb = NULL;
|
||||
/* Set when a frontend older than VFS API v5 owns the directory
|
||||
* handles: the local _impl cannot be used on a foreign handle, so
|
||||
* retro_dirent_stat() reports "unavailable" instead. */
|
||||
static bool dirent_stat_unavailable = false;
|
||||
|
||||
void dirent_vfs_init(const struct retro_vfs_interface_info* vfs_info)
|
||||
{
|
||||
@@ -47,6 +52,8 @@ void dirent_vfs_init(const struct retro_vfs_interface_info* vfs_info)
|
||||
dirent_dirent_get_name_cb = NULL;
|
||||
dirent_dirent_is_dir_cb = NULL;
|
||||
dirent_closedir_cb = NULL;
|
||||
dirent_dirent_stat_cb = NULL;
|
||||
dirent_stat_unavailable = false;
|
||||
|
||||
vfs_iface = vfs_info->iface;
|
||||
|
||||
@@ -60,6 +67,14 @@ void dirent_vfs_init(const struct retro_vfs_interface_info* vfs_info)
|
||||
dirent_dirent_get_name_cb = vfs_iface->dirent_get_name;
|
||||
dirent_dirent_is_dir_cb = vfs_iface->dirent_is_dir;
|
||||
dirent_closedir_cb = vfs_iface->closedir;
|
||||
|
||||
/* A frontend that negotiated v5 but left the member NULL is treated
|
||||
* like an older one: the local _impl must not touch its handles. */
|
||||
if (vfs_info->required_interface_version >= DIRENT_STAT_REQUIRED_VFS_VERSION
|
||||
&& vfs_iface->dirent_stat)
|
||||
dirent_dirent_stat_cb = vfs_iface->dirent_stat;
|
||||
else
|
||||
dirent_stat_unavailable = true;
|
||||
}
|
||||
|
||||
struct RDIR *retro_opendir_include_hidden(
|
||||
@@ -113,6 +128,17 @@ bool retro_dirent_is_dir(struct RDIR *rdir, const char *unused)
|
||||
return retro_vfs_dirent_is_dir_impl((struct retro_vfs_dir_handle *)rdir);
|
||||
}
|
||||
|
||||
int retro_dirent_stat(struct RDIR *rdir, int64_t *size, int64_t *mtime)
|
||||
{
|
||||
if (!rdir)
|
||||
return 0;
|
||||
if (dirent_dirent_stat_cb)
|
||||
return dirent_dirent_stat_cb((struct retro_vfs_dir_handle *)rdir, size, mtime);
|
||||
if (dirent_stat_unavailable)
|
||||
return 0;
|
||||
return retro_vfs_dirent_stat_impl((struct retro_vfs_dir_handle *)rdir, size, mtime);
|
||||
}
|
||||
|
||||
void retro_closedir(struct RDIR *rdir)
|
||||
{
|
||||
if (dirent_closedir_cb)
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (rpng_file.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.
|
||||
*/
|
||||
|
||||
/* Path-based convenience adapters for the pure PNG encoder in
|
||||
* rpng_encode.c. This TU is the only part of the rpng encode surface
|
||||
* that opens a path; the encoder itself only speaks intfstreams and
|
||||
* memory buffers.
|
||||
*
|
||||
* Deprecated: new callers should prefer the *_string entry points plus
|
||||
* a single filestream_write_file() at the edge (one open/bulk-write/
|
||||
* close instead of interleaving many small chunk writes with the
|
||||
* compressor), or open an intfstream themselves and call
|
||||
* rpng_save_image_stream_fmt() / rpng_save_image_stream(). */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <libretro.h>
|
||||
#include <streams/interface_stream.h>
|
||||
#include <formats/rpng.h>
|
||||
|
||||
static bool rpng_save_image_file(const char *path, const uint8_t *data,
|
||||
unsigned width, unsigned height, signed pitch,
|
||||
enum rpng_pixfmt fmt, const struct rpng_hdr_metadata *hdr)
|
||||
{
|
||||
bool ret = false;
|
||||
intfstream_t *intf_s = intfstream_open_file(path,
|
||||
RETRO_VFS_FILE_ACCESS_WRITE,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
|
||||
if (!intf_s)
|
||||
return false;
|
||||
|
||||
ret = rpng_save_image_stream_fmt(data, intf_s,
|
||||
width, height, pitch, fmt, hdr);
|
||||
|
||||
intfstream_close(intf_s);
|
||||
free(intf_s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Straight RGBA byte order - R,G,B,A ascending in memory, which on a
|
||||
* little-endian host is the uint32_t 0xAABBGGRR, the mirror of what
|
||||
* rpng_save_image_argb() takes. Callers holding GL_RGBA / VK_FORMAT_
|
||||
* R8G8B8A8 surfaces would otherwise have to swizzle a whole frame into a
|
||||
* scratch buffer just to have the encoder swizzle it back. */
|
||||
bool rpng_save_image_rgba(const char *path, const uint8_t *data,
|
||||
unsigned width, unsigned height, unsigned pitch)
|
||||
{
|
||||
return rpng_save_image_file(path, data,
|
||||
width, height, (signed)pitch, RPNG_PIXFMT_RGBA32, NULL);
|
||||
}
|
||||
|
||||
bool rpng_save_image_argb(const char *path, const uint32_t *data,
|
||||
unsigned width, unsigned height, unsigned pitch)
|
||||
{
|
||||
return rpng_save_image_file(path, (const uint8_t*)data,
|
||||
width, height, (signed)pitch, RPNG_PIXFMT_ARGB32, NULL);
|
||||
}
|
||||
|
||||
bool rpng_save_image_bgr24(const char *path, const uint8_t *data,
|
||||
unsigned width, unsigned height, unsigned pitch)
|
||||
{
|
||||
return rpng_save_image_file(path, data,
|
||||
width, height, (signed)pitch, RPNG_PIXFMT_BGR24, NULL);
|
||||
}
|
||||
|
||||
bool rpng_save_image_rgb48_hdr(const char *path, const uint16_t *data,
|
||||
unsigned width, unsigned height, unsigned pitch,
|
||||
const struct rpng_hdr_metadata *hdr)
|
||||
{
|
||||
return rpng_save_image_file(path, (const uint8_t*)data,
|
||||
width, height, (signed)pitch, RPNG_PIXFMT_RGB48, hdr);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,272 +0,0 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (r7z_bcj2.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.
|
||||
*/
|
||||
|
||||
/* BCJ2 decoder. See r7z_bcj2.h for what the four streams are and why
|
||||
* the format is shaped this way.
|
||||
*
|
||||
* The range coder is the same one LZMA uses: 11-bit probabilities,
|
||||
* 5-bit adaptation, normalize when range drops below 2^24. It is
|
||||
* reproduced here rather than shared with r7z_lzma_stream because that
|
||||
* one is built around a resumable input pointer with a bounds contract
|
||||
* this decoder does not need; the shared part would be three macros.
|
||||
*
|
||||
* Bounds are checked on every read. The 7z container hands this
|
||||
* attacker-controlled data with four independently-sized streams, and
|
||||
* the sizes are only consistent if the archive says so, so nothing here
|
||||
* assumes a stream is as long as the others imply.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <7z/r7z_bcj2.h>
|
||||
|
||||
#undef NUM_MODEL_BITS
|
||||
/* griffin compiles this alongside r7z_lzma.c and r7z_lzma_stream.c,
|
||||
* which define the same range coder constants to the same values.
|
||||
* #undef first so the repeat is silent rather than a warning. */
|
||||
#define NUM_MODEL_BITS 11
|
||||
#undef BIT_MODEL_TOTAL
|
||||
#define BIT_MODEL_TOTAL (1 << NUM_MODEL_BITS)
|
||||
#undef NUM_MOVE_BITS
|
||||
#define NUM_MOVE_BITS 5
|
||||
#undef TOP_VALUE
|
||||
#define TOP_VALUE ((uint32_t)1 << 24)
|
||||
|
||||
/* A byte is a branch candidate if it is E8 (call), E9 (jump), or the
|
||||
* second byte of a 0F 8x near jump. */
|
||||
#define IS_E8_OR_E9(b) (((b) & 0xFE) == 0xE8)
|
||||
#define IS_NEAR_JMP(prev, b) ((prev) == 0x0F && ((b) & 0xF0) == 0x80)
|
||||
|
||||
void r7z_bcj2_state_init(r7z_bcj2_state_t *st)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
if (!st)
|
||||
return;
|
||||
|
||||
memset(st, 0, sizeof(*st));
|
||||
for (i = 0; i < RBCJ2_NUM_PROBS; i++)
|
||||
st->probs[i] = BIT_MODEL_TOTAL >> 1;
|
||||
st->range = 0xFFFFFFFFu;
|
||||
st->started = 0;
|
||||
}
|
||||
|
||||
int r7z_bcj2_decode_part(r7z_bcj2_state_t *st,
|
||||
uint8_t *dst, size_t dst_len, size_t dst_limit,
|
||||
const uint8_t *main_buf, size_t main_len,
|
||||
const uint8_t *call_buf, size_t call_len,
|
||||
const uint8_t *jump_buf, size_t jump_len,
|
||||
const uint8_t *rc_buf, size_t rc_len)
|
||||
{
|
||||
if (!st)
|
||||
return RBCJ2_ERROR_PARAM;
|
||||
if (!dst && dst_len != 0)
|
||||
return RBCJ2_ERROR_PARAM;
|
||||
if (!main_buf && main_len != 0)
|
||||
return RBCJ2_ERROR_PARAM;
|
||||
if (!call_buf && call_len != 0)
|
||||
return RBCJ2_ERROR_PARAM;
|
||||
if (!jump_buf && jump_len != 0)
|
||||
return RBCJ2_ERROR_PARAM;
|
||||
if (!rc_buf && rc_len != 0)
|
||||
return RBCJ2_ERROR_PARAM;
|
||||
|
||||
/* Target streams are arrays of 4-byte big-endian addresses. A
|
||||
* length that is not a multiple of four cannot be one. */
|
||||
if ((call_len & 3) != 0 || (jump_len & 3) != 0)
|
||||
return RBCJ2_ERROR_DATA;
|
||||
|
||||
if (dst_len == 0)
|
||||
return RBCJ2_OK;
|
||||
|
||||
if (!st->started)
|
||||
{
|
||||
/* Range coder prologue: one ignored byte then four value bytes.
|
||||
* The reference reads five and requires the resulting code to
|
||||
* differ from 0xFFFFFFFF. */
|
||||
if (rc_len < 5)
|
||||
return RBCJ2_ERROR_DATA;
|
||||
if (rc_buf[0] != 0)
|
||||
return RBCJ2_ERROR_DATA;
|
||||
st->code = ((uint32_t)rc_buf[1] << 24)
|
||||
| ((uint32_t)rc_buf[2] << 16)
|
||||
| ((uint32_t)rc_buf[3] << 8)
|
||||
| (uint32_t)rc_buf[4];
|
||||
if (st->code == 0xFFFFFFFFu)
|
||||
return RBCJ2_ERROR_DATA;
|
||||
st->range = 0xFFFFFFFFu;
|
||||
st->rc_pos = 5;
|
||||
st->started = 1;
|
||||
}
|
||||
|
||||
if (dst_limit > dst_len)
|
||||
dst_limit = dst_len;
|
||||
|
||||
while (st->dst_pos < dst_len)
|
||||
{
|
||||
uint8_t b;
|
||||
uint32_t idx;
|
||||
uint32_t bound;
|
||||
uint32_t ttt;
|
||||
int converted;
|
||||
|
||||
/* Checked at the top of a symbol, never inside one, so the
|
||||
* four-byte operand write below always completes within the
|
||||
* call that started it. That is why the limit is a floor and
|
||||
* a call may overshoot by up to three bytes. */
|
||||
if (st->dst_pos >= dst_limit)
|
||||
return RBCJ2_PENDING;
|
||||
|
||||
if (st->main_pos == main_len)
|
||||
return RBCJ2_ERROR_DATA;
|
||||
|
||||
b = main_buf[st->main_pos++];
|
||||
dst[st->dst_pos++] = b;
|
||||
st->ip++;
|
||||
|
||||
/* Copy through anything that cannot start a branch. */
|
||||
if (!IS_E8_OR_E9(b) && !IS_NEAR_JMP(st->prev, b))
|
||||
{
|
||||
st->prev = b;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* A candidate. Which context its bit is coded against depends on
|
||||
* the opcode, and for E8 on the byte before it. */
|
||||
if (b == 0xE8)
|
||||
idx = 2 + (uint32_t)st->prev;
|
||||
else if (b == 0xE9)
|
||||
idx = 1;
|
||||
else
|
||||
idx = 0;
|
||||
|
||||
/* Normalize before reading the bit. */
|
||||
if (st->range < TOP_VALUE)
|
||||
{
|
||||
if (st->rc_pos == rc_len)
|
||||
return RBCJ2_ERROR_DATA;
|
||||
st->range <<= 8;
|
||||
st->code = (st->code << 8) | (uint32_t)rc_buf[st->rc_pos++];
|
||||
}
|
||||
|
||||
ttt = (uint32_t)st->probs[idx];
|
||||
bound = (st->range >> NUM_MODEL_BITS) * ttt;
|
||||
|
||||
if (st->code < bound)
|
||||
{
|
||||
st->range = bound;
|
||||
st->probs[idx] = (uint16_t)(ttt
|
||||
+ ((BIT_MODEL_TOTAL - ttt) >> NUM_MOVE_BITS));
|
||||
converted = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
st->range -= bound;
|
||||
st->code -= bound;
|
||||
st->probs[idx] = (uint16_t)(ttt - (ttt >> NUM_MOVE_BITS));
|
||||
converted = 1;
|
||||
}
|
||||
|
||||
if (!converted)
|
||||
{
|
||||
st->prev = b;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Converted: the four operand bytes were moved to the call or
|
||||
* jump stream as a big-endian absolute address, and have to be
|
||||
* turned back into the little-endian relative address the
|
||||
* instruction actually carries. */
|
||||
{
|
||||
const uint8_t *src;
|
||||
size_t *pos;
|
||||
size_t len;
|
||||
uint32_t val;
|
||||
|
||||
if (b == 0xE8)
|
||||
{
|
||||
src = call_buf;
|
||||
pos = &st->call_pos;
|
||||
len = call_len;
|
||||
}
|
||||
else
|
||||
{
|
||||
src = jump_buf;
|
||||
pos = &st->jump_pos;
|
||||
len = jump_len;
|
||||
}
|
||||
|
||||
if (*pos + 4 > len)
|
||||
return RBCJ2_ERROR_DATA;
|
||||
|
||||
val = ((uint32_t)src[*pos] << 24)
|
||||
| ((uint32_t)src[*pos + 1] << 16)
|
||||
| ((uint32_t)src[*pos + 2] << 8)
|
||||
| (uint32_t)src[*pos + 3];
|
||||
*pos += 4;
|
||||
|
||||
/* ip already counts the opcode byte; the displacement is
|
||||
* relative to the end of the instruction, so advance past the
|
||||
* four operand bytes before subtracting. */
|
||||
st->ip += 4;
|
||||
val -= st->ip;
|
||||
|
||||
if (dst_len - st->dst_pos < 4)
|
||||
return RBCJ2_ERROR_DATA;
|
||||
|
||||
dst[st->dst_pos] = (uint8_t)( val & 0xFF);
|
||||
dst[st->dst_pos + 1] = (uint8_t)((val >> 8) & 0xFF);
|
||||
dst[st->dst_pos + 2] = (uint8_t)((val >> 16) & 0xFF);
|
||||
dst[st->dst_pos + 3] = (uint8_t)((val >> 24) & 0xFF);
|
||||
st->dst_pos += 4;
|
||||
|
||||
/* The context byte for whatever follows is the last byte
|
||||
* written, exactly as if it had come from the main stream. */
|
||||
st->prev = (uint8_t)((val >> 24) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
/* Every byte of the target streams must have been used. Leftovers
|
||||
* mean the streams disagree about how many branches were
|
||||
* converted, which would have produced wrong output had the output
|
||||
* been longer. */
|
||||
if (st->call_pos != call_len || st->jump_pos != jump_len)
|
||||
return RBCJ2_ERROR_DATA;
|
||||
|
||||
return RBCJ2_OK;
|
||||
}
|
||||
|
||||
int r7z_bcj2_decode(uint8_t *dst, size_t dst_len,
|
||||
const uint8_t *main_buf, size_t main_len,
|
||||
const uint8_t *call_buf, size_t call_len,
|
||||
const uint8_t *jump_buf, size_t jump_len,
|
||||
const uint8_t *rc_buf, size_t rc_len)
|
||||
{
|
||||
/* The whole-stream form, for callers with nowhere to yield to. */
|
||||
r7z_bcj2_state_t st;
|
||||
|
||||
r7z_bcj2_state_init(&st);
|
||||
|
||||
return r7z_bcj2_decode_part(&st, dst, dst_len, dst_len,
|
||||
main_buf, main_len, call_buf, call_len,
|
||||
jump_buf, jump_len, rc_buf, rc_len);
|
||||
}
|
||||
@@ -1,400 +0,0 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (r7z_filters.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.
|
||||
*/
|
||||
|
||||
/* Branch converters and delta decoding for 7z archive members, derived
|
||||
* from the public-domain LZMA SDK by Igor Pavlov. See rfilters.h.
|
||||
*
|
||||
* Byte order is handled explicitly throughout: the ARM and x86 filters
|
||||
* read little-endian operands, PPC and SPARC big-endian, regardless of
|
||||
* the host. Nothing here dereferences a multi-byte type through a
|
||||
* cast, so there are no alignment or aliasing constraints on the
|
||||
* caller's buffer.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <7z/r7z_filters.h>
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Byte access helpers
|
||||
* -------------------------------------------------------------------- */
|
||||
|
||||
static uint32_t rf_get_le32(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 void rf_set_le32(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);
|
||||
}
|
||||
|
||||
static uint32_t rf_get_be32(const uint8_t *p)
|
||||
{
|
||||
return ((uint32_t)p[0] << 24)
|
||||
| ((uint32_t)p[1] << 16)
|
||||
| ((uint32_t)p[2] << 8)
|
||||
| (uint32_t)p[3];
|
||||
}
|
||||
|
||||
static void rf_set_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;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Delta
|
||||
* -------------------------------------------------------------------- */
|
||||
|
||||
void rfilters_delta_decode(uint8_t *state, unsigned distance,
|
||||
uint8_t *data, size_t len)
|
||||
{
|
||||
uint8_t buf[RFILTERS_DELTA_STATE_SIZE];
|
||||
unsigned j = 0;
|
||||
size_t i;
|
||||
|
||||
if (distance == 0 || distance > RFILTERS_DELTA_STATE_SIZE)
|
||||
return;
|
||||
|
||||
memcpy(buf, state, distance);
|
||||
|
||||
for (i = 0; i < len;)
|
||||
{
|
||||
for (j = 0; j < distance && i < len; i++, j++)
|
||||
{
|
||||
buf[j] = (uint8_t)(buf[j] + data[i]);
|
||||
data[i] = buf[j];
|
||||
}
|
||||
}
|
||||
|
||||
/* Rotate the history so the next call resumes at the right phase. */
|
||||
if (j == distance)
|
||||
j = 0;
|
||||
memcpy(state, buf + j, distance - j);
|
||||
memcpy(state + distance - j, buf, j);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* x86 (BCJ)
|
||||
*
|
||||
* The only converter with running state: a CALL/JMP opcode can be
|
||||
* split across a chunk boundary, and the mask records how far into
|
||||
* such a sequence the previous call ended.
|
||||
* -------------------------------------------------------------------- */
|
||||
|
||||
#define RF_TEST_86_MS_BYTE(b) ((((b) + 1) & 0xFE) == 0)
|
||||
|
||||
size_t rfilters_x86_decode(uint32_t *state, uint32_t ip,
|
||||
uint8_t *data, size_t len)
|
||||
{
|
||||
size_t pos = 0;
|
||||
uint32_t mask = *state & 7;
|
||||
|
||||
if (len < 5)
|
||||
return 0;
|
||||
len -= 4;
|
||||
ip += 5;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
uint8_t *p = data + pos;
|
||||
const uint8_t *limit = data + len;
|
||||
|
||||
while (p < limit && (*p & 0xFE) != 0xE8)
|
||||
p++;
|
||||
|
||||
{
|
||||
size_t d = (size_t)(p - data) - pos;
|
||||
pos = (size_t)(p - data);
|
||||
|
||||
if (p >= limit)
|
||||
{
|
||||
*state = (d > 2 ? 0 : mask >> (unsigned)d);
|
||||
return pos;
|
||||
}
|
||||
|
||||
if (d > 2)
|
||||
mask = 0;
|
||||
else
|
||||
{
|
||||
mask >>= (unsigned)d;
|
||||
if ( mask != 0
|
||||
&& ( mask > 4
|
||||
|| mask == 3
|
||||
|| RF_TEST_86_MS_BYTE(p[(size_t)(mask >> 1) + 1])))
|
||||
{
|
||||
mask = (mask >> 1) | 4;
|
||||
pos++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (RF_TEST_86_MS_BYTE(p[4]))
|
||||
{
|
||||
uint32_t v = ((uint32_t)p[4] << 24)
|
||||
| ((uint32_t)p[3] << 16)
|
||||
| ((uint32_t)p[2] << 8)
|
||||
| (uint32_t)p[1];
|
||||
uint32_t cur = ip + (uint32_t)pos;
|
||||
|
||||
pos += 5;
|
||||
v -= cur;
|
||||
|
||||
if (mask != 0)
|
||||
{
|
||||
unsigned sh = (mask & 6) << 2;
|
||||
if (RF_TEST_86_MS_BYTE((uint8_t)(v >> sh)))
|
||||
{
|
||||
v ^= (((uint32_t)0x100 << sh) - 1);
|
||||
v -= cur;
|
||||
}
|
||||
mask = 0;
|
||||
}
|
||||
|
||||
p[1] = (uint8_t)v;
|
||||
p[2] = (uint8_t)(v >> 8);
|
||||
p[3] = (uint8_t)(v >> 16);
|
||||
p[4] = (uint8_t)(0 - ((v >> 24) & 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
mask = (mask >> 1) | 4;
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* ARM / ARM Thumb
|
||||
* -------------------------------------------------------------------- */
|
||||
|
||||
size_t rfilters_arm_decode(uint32_t ip, uint8_t *data, size_t len)
|
||||
{
|
||||
uint8_t *p;
|
||||
const uint8_t *lim;
|
||||
|
||||
len &= ~(size_t)3;
|
||||
ip += 4;
|
||||
p = data;
|
||||
lim = data + len;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
/* BL is encoded with 0xEB in the top byte. */
|
||||
for (;;)
|
||||
{
|
||||
if (p >= lim)
|
||||
return (size_t)(p - data);
|
||||
p += 4;
|
||||
if (p[-1] == 0xEB)
|
||||
break;
|
||||
}
|
||||
{
|
||||
uint32_t v = rf_get_le32(p - 4);
|
||||
v <<= 2;
|
||||
v -= ip + (uint32_t)(p - data);
|
||||
v >>= 2;
|
||||
v &= 0x00FFFFFF;
|
||||
v |= 0xEB000000;
|
||||
rf_set_le32(p - 4, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t rfilters_armt_decode(uint32_t ip, uint8_t *data, size_t len)
|
||||
{
|
||||
uint8_t *p;
|
||||
const uint8_t *lim;
|
||||
|
||||
len &= ~(size_t)1;
|
||||
if (len < 4)
|
||||
return 0;
|
||||
p = data;
|
||||
lim = data + len - 4;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
uint32_t b1;
|
||||
|
||||
/* A Thumb BL is a pair of halfwords, both with the top five bits
|
||||
* set in the pattern tested here. */
|
||||
for (;;)
|
||||
{
|
||||
uint32_t b3;
|
||||
if (p > lim)
|
||||
return (size_t)(p - data);
|
||||
b1 = p[1];
|
||||
b3 = p[3];
|
||||
p += 2;
|
||||
b1 ^= 8;
|
||||
if ((b3 & b1) >= 0xF8)
|
||||
break;
|
||||
}
|
||||
{
|
||||
uint32_t v = ((uint32_t)b1 << 19)
|
||||
+ (((uint32_t)p[1] & 0x7) << 8)
|
||||
+ ((uint32_t)p[-2] << 11)
|
||||
+ (uint32_t)p[0];
|
||||
|
||||
p += 2;
|
||||
v -= (ip + (uint32_t)(p - data)) >> 1;
|
||||
|
||||
p[-4] = (uint8_t)(v >> 11);
|
||||
p[-3] = (uint8_t)(0xF0 | ((v >> 19) & 0x7));
|
||||
p[-2] = (uint8_t)v;
|
||||
p[-1] = (uint8_t)(0xF8 | (v >> 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* PowerPC / SPARC
|
||||
* -------------------------------------------------------------------- */
|
||||
|
||||
size_t rfilters_ppc_decode(uint32_t ip, uint8_t *data, size_t len)
|
||||
{
|
||||
uint8_t *p;
|
||||
const uint8_t *lim;
|
||||
|
||||
len &= ~(size_t)3;
|
||||
ip -= 4;
|
||||
p = data;
|
||||
lim = data + len;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
/* (v & 0xFC000003) == 0x48000001: branch-and-link, absolute off. */
|
||||
for (;;)
|
||||
{
|
||||
if (p >= lim)
|
||||
return (size_t)(p - data);
|
||||
p += 4;
|
||||
if ((p[-4] & 0xFC) == 0x48 && (p[-1] & 3) == 1)
|
||||
break;
|
||||
}
|
||||
{
|
||||
uint32_t v = rf_get_be32(p - 4);
|
||||
v -= ip + (uint32_t)(p - data);
|
||||
v &= 0x03FFFFFF;
|
||||
v |= 0x48000000;
|
||||
rf_set_be32(p - 4, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t rfilters_sparc_decode(uint32_t ip, uint8_t *data, size_t len)
|
||||
{
|
||||
uint8_t *p;
|
||||
const uint8_t *lim;
|
||||
|
||||
len &= ~(size_t)3;
|
||||
ip -= 4;
|
||||
p = data;
|
||||
lim = data + len;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
if (p >= lim)
|
||||
return (size_t)(p - data);
|
||||
p += 4;
|
||||
if ( (p[-4] == 0x40 && (p[-3] & 0xC0) == 0)
|
||||
|| (p[-4] == 0x7F && p[-3] >= 0xC0))
|
||||
break;
|
||||
}
|
||||
{
|
||||
uint32_t v = rf_get_be32(p - 4);
|
||||
v <<= 2;
|
||||
v -= ip + (uint32_t)(p - data);
|
||||
v &= 0x01FFFFFF;
|
||||
v -= (uint32_t)1 << 24;
|
||||
v ^= 0xFF000000;
|
||||
v >>= 2;
|
||||
v |= 0x40000000;
|
||||
rf_set_be32(p - 4, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* IA-64
|
||||
*
|
||||
* Instructions come in 16-byte bundles holding three 41-bit slots; the
|
||||
* template in the low byte says which slots are branches.
|
||||
* -------------------------------------------------------------------- */
|
||||
|
||||
size_t rfilters_ia64_decode(uint32_t ip, uint8_t *data, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (len < 16)
|
||||
return 0;
|
||||
len -= 16;
|
||||
i = 0;
|
||||
|
||||
do
|
||||
{
|
||||
unsigned m = ((uint32_t)0x334B0000 >> (data[i] & 0x1E)) & 3;
|
||||
|
||||
if (m)
|
||||
{
|
||||
m++;
|
||||
do
|
||||
{
|
||||
uint8_t *p = data + (i + (size_t)m * 5 - 8);
|
||||
|
||||
if ( ((p[3] >> m) & 15) == 5
|
||||
&& (((uint32_t)p[-1] | ((uint32_t)p[0] << 8)) >> m & 0x70) == 0)
|
||||
{
|
||||
uint32_t raw = rf_get_le32(p);
|
||||
uint32_t v = raw >> m;
|
||||
|
||||
v = (v & 0xFFFFF) | ((v & (1 << 23)) >> 3);
|
||||
v <<= 4;
|
||||
v -= ip + (uint32_t)i;
|
||||
v >>= 4;
|
||||
v &= 0x1FFFFF;
|
||||
v += 0x700000;
|
||||
v &= 0x8FFFFF;
|
||||
|
||||
raw &= ~((uint32_t)0x8FFFFF << m);
|
||||
raw |= (v << m);
|
||||
rf_set_le32(p, raw);
|
||||
}
|
||||
} while (++m <= 4);
|
||||
}
|
||||
i += 16;
|
||||
} while (i <= len);
|
||||
|
||||
return i;
|
||||
}
|
||||
@@ -1,516 +0,0 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (r7z_lzma2.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.
|
||||
*/
|
||||
|
||||
/* LZMA2 decoder. See r7z_lzma2.h for the chunk header layout.
|
||||
*
|
||||
* This is a framing layer, not a codec: it parses chunk headers, works
|
||||
* out how much decoder state each chunk wants reset, and hands the
|
||||
* payload to r7z_lzma_stream. All the actual LZMA decoding lives there.
|
||||
*
|
||||
* The header parser is a byte-at-a-time state machine rather than a
|
||||
* "wait until the whole header has arrived" loop, because a chunk
|
||||
* header can be split across calls at any byte and the caller is under
|
||||
* no obligation to present it whole.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <7z/r7z_lzma2.h>
|
||||
|
||||
/* Header parse states, in the order the bytes arrive. */
|
||||
#define ST_CONTROL 0
|
||||
#define ST_UNPACK0 1
|
||||
#define ST_UNPACK1 2
|
||||
#define ST_PACK0 3
|
||||
#define ST_PACK1 4
|
||||
#define ST_PROP 5
|
||||
#define ST_DATA 6
|
||||
#define ST_DATA_CONT 7
|
||||
#define ST_FINISHED 8
|
||||
#define ST_ERROR 9
|
||||
|
||||
/* A control byte with the top bit clear is an uncompressed chunk. */
|
||||
#define IS_UNCOMPRESSED(ctl) (((ctl) & 0x80) == 0)
|
||||
|
||||
/* The only uncompressed control byte that resets the dictionary. */
|
||||
#define CONTROL_COPY_RESET_DIC 1
|
||||
|
||||
/* need_init_level gates what the next chunk is allowed to be. It starts
|
||||
* at 0xE0, meaning "the first chunk must reset both dictionary and
|
||||
* state", and drops to 0 once that has happened. A stream that opens
|
||||
* with a continuation chunk is rejected rather than decoded against
|
||||
* whatever the window happened to contain. */
|
||||
#define NEED_INIT_FULL 0xE0
|
||||
#define NEED_INIT_DIC 0xC0
|
||||
|
||||
int rlzma2_dec_dic_size_from_prop(uint8_t prop, uint32_t *dic_size)
|
||||
{
|
||||
if (!dic_size)
|
||||
return RLZMA_ERROR_PARAM;
|
||||
if (prop > 40)
|
||||
return RLZMA_ERROR_PARAM;
|
||||
if (prop == 40)
|
||||
*dic_size = 0xFFFFFFFFu;
|
||||
else
|
||||
*dic_size = ((uint32_t)2 | ((uint32_t)prop & 1))
|
||||
<< ((uint32_t)prop / 2 + 11);
|
||||
return RLZMA_OK;
|
||||
}
|
||||
|
||||
int rlzma2_dec_init(rlzma2_dec_t *p, uint8_t prop,
|
||||
uint16_t *probs, uint8_t *dic, size_t dic_size)
|
||||
{
|
||||
uint32_t stream_dic_size;
|
||||
uint8_t props[RLZMA_PROPS_SIZE];
|
||||
|
||||
if (!p || !probs || !dic || dic_size == 0)
|
||||
return RLZMA_ERROR_PARAM;
|
||||
if (rlzma2_dec_dic_size_from_prop(prop, &stream_dic_size) != RLZMA_OK)
|
||||
return RLZMA_ERROR_PARAM;
|
||||
|
||||
memset(p, 0, sizeof(*p));
|
||||
|
||||
p->dic = dic;
|
||||
p->dic_size = dic_size;
|
||||
p->probs = probs;
|
||||
|
||||
/* Start the underlying decoder from the widest lc/lp LZMA2 permits,
|
||||
* so the probability array the caller sized for RLZMA2_NUM_PROBS is
|
||||
* always big enough whatever props a later chunk sets. The real
|
||||
* values arrive with the first chunk that carries a props byte. */
|
||||
props[0] = (uint8_t)RLZMA2_LCLP_MAX;
|
||||
props[1] = (uint8_t)( stream_dic_size & 0xFF);
|
||||
props[2] = (uint8_t)((stream_dic_size >> 8) & 0xFF);
|
||||
props[3] = (uint8_t)((stream_dic_size >> 16) & 0xFF);
|
||||
props[4] = (uint8_t)((stream_dic_size >> 24) & 0xFF);
|
||||
|
||||
if (rlzma_stream_init(&p->lzma, props, probs, dic, dic_size) != RLZMA_OK)
|
||||
return RLZMA_ERROR_PARAM;
|
||||
|
||||
memcpy(p->props, props, RLZMA_PROPS_SIZE);
|
||||
p->have_props = 1;
|
||||
|
||||
rlzma2_dec_reset(p);
|
||||
return RLZMA_OK;
|
||||
}
|
||||
|
||||
void rlzma2_dec_reset(rlzma2_dec_t *p)
|
||||
{
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
p->state = ST_CONTROL;
|
||||
p->control = 0;
|
||||
p->unpack_size = 0;
|
||||
p->pack_size = 0;
|
||||
p->need_init_level = NEED_INIT_FULL;
|
||||
|
||||
rlzma_stream_reset(&p->lzma);
|
||||
}
|
||||
|
||||
/* Feed one header byte to the state machine, returning the next state.
|
||||
* Mirrors the reference's Lzma2Dec_UpdateState. */
|
||||
static uint32_t lzma2_update_state(rlzma2_dec_t *p, uint8_t b)
|
||||
{
|
||||
switch (p->state)
|
||||
{
|
||||
case ST_CONTROL:
|
||||
p->control = (uint32_t)b;
|
||||
|
||||
if (b == 0)
|
||||
return ST_FINISHED;
|
||||
|
||||
if (IS_UNCOMPRESSED(b))
|
||||
{
|
||||
if (b == CONTROL_COPY_RESET_DIC)
|
||||
p->need_init_level = NEED_INIT_DIC;
|
||||
else if (b > 2 || p->need_init_level == NEED_INIT_FULL)
|
||||
return ST_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* A chunk may not ask for less reset than the stream still
|
||||
* owes. This is what rejects a stream opening mid-flight. */
|
||||
if ((uint32_t)b < p->need_init_level)
|
||||
return ST_ERROR;
|
||||
p->need_init_level = 0;
|
||||
p->unpack_size = ((uint32_t)b & 0x1F) << 16;
|
||||
}
|
||||
return ST_UNPACK0;
|
||||
|
||||
case ST_UNPACK0:
|
||||
p->unpack_size |= (uint32_t)b << 8;
|
||||
return ST_UNPACK1;
|
||||
|
||||
case ST_UNPACK1:
|
||||
p->unpack_size |= (uint32_t)b;
|
||||
/* Both sizes are stored biased by one. */
|
||||
p->unpack_size++;
|
||||
return IS_UNCOMPRESSED(p->control) ? ST_DATA : ST_PACK0;
|
||||
|
||||
case ST_PACK0:
|
||||
p->pack_size = (uint32_t)b << 8;
|
||||
return ST_PACK1;
|
||||
|
||||
case ST_PACK1:
|
||||
p->pack_size |= (uint32_t)b;
|
||||
p->pack_size++;
|
||||
return (p->control & 0x40) ? ST_PROP : ST_DATA;
|
||||
|
||||
case ST_PROP:
|
||||
{
|
||||
uint32_t d = (uint32_t)b;
|
||||
uint32_t lc, lp;
|
||||
|
||||
if (d >= 9 * 5 * 5)
|
||||
return ST_ERROR;
|
||||
lc = d % 9;
|
||||
d /= 9;
|
||||
lp = d % 5;
|
||||
|
||||
/* LZMA2 is stricter than bare LZMA here, and the probability
|
||||
* array the caller supplied is sized on that promise. */
|
||||
if (lc + lp > RLZMA2_LCLP_MAX)
|
||||
return ST_ERROR;
|
||||
|
||||
p->props[0] = b;
|
||||
if (rlzma_stream_set_props(&p->lzma, p->props) != RLZMA_OK)
|
||||
return ST_ERROR;
|
||||
return ST_DATA;
|
||||
}
|
||||
}
|
||||
return ST_ERROR;
|
||||
}
|
||||
|
||||
int rlzma2_dec_decode(rlzma2_dec_t *p, size_t dic_limit,
|
||||
const uint8_t *src, size_t *src_len, int finish, int *status)
|
||||
{
|
||||
size_t in_size;
|
||||
size_t consumed = 0;
|
||||
|
||||
if (!p || !src || !src_len || !status)
|
||||
return RLZMA_ERROR_PARAM;
|
||||
|
||||
in_size = *src_len;
|
||||
*src_len = 0;
|
||||
*status = RLZMA2_STATUS_NOT_FINISHED;
|
||||
|
||||
if (dic_limit > p->dic_size)
|
||||
return RLZMA_ERROR_PARAM;
|
||||
|
||||
while (p->state != ST_ERROR)
|
||||
{
|
||||
size_t dic_pos = p->lzma.dic_pos;
|
||||
|
||||
if (p->state == ST_FINISHED)
|
||||
{
|
||||
*src_len = consumed;
|
||||
*status = RLZMA2_STATUS_FINISHED;
|
||||
return RLZMA_OK;
|
||||
}
|
||||
|
||||
if (dic_pos == dic_limit && !finish)
|
||||
{
|
||||
*src_len = consumed;
|
||||
*status = RLZMA2_STATUS_NOT_FINISHED;
|
||||
return RLZMA_OK;
|
||||
}
|
||||
|
||||
/* Header bytes, one at a time: a header can be split anywhere. */
|
||||
if (p->state != ST_DATA && p->state != ST_DATA_CONT)
|
||||
{
|
||||
if (consumed == in_size)
|
||||
{
|
||||
*src_len = consumed;
|
||||
*status = RLZMA2_STATUS_NEEDS_MORE_INPUT;
|
||||
return RLZMA_OK;
|
||||
}
|
||||
p->state = lzma2_update_state(p, src[consumed]);
|
||||
consumed++;
|
||||
|
||||
if (dic_pos == dic_limit && p->state != ST_FINISHED)
|
||||
{
|
||||
/* No room to decode the chunk just announced. */
|
||||
p->state = ST_ERROR;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
size_t in_cur = in_size - consumed;
|
||||
size_t out_cur = dic_limit - dic_pos;
|
||||
|
||||
/* Never decode past the end of the chunk's declared output. */
|
||||
if (out_cur >= (size_t)p->unpack_size)
|
||||
out_cur = (size_t)p->unpack_size;
|
||||
|
||||
if (IS_UNCOMPRESSED(p->control))
|
||||
{
|
||||
if (in_cur == 0)
|
||||
{
|
||||
*src_len = consumed;
|
||||
*status = RLZMA2_STATUS_NEEDS_MORE_INPUT;
|
||||
return RLZMA_OK;
|
||||
}
|
||||
|
||||
if (p->state == ST_DATA)
|
||||
{
|
||||
/* 0x01 resets the dictionary; 0x02 continues it. Neither
|
||||
* touches the range coder, but the next LZMA chunk must
|
||||
* still re-init it, since raw bytes carry no coder
|
||||
* state. */
|
||||
int init_dic = (p->control == CONTROL_COPY_RESET_DIC);
|
||||
rlzma_stream_reset_parts(&p->lzma, init_dic, 0);
|
||||
p->lzma.need_init = 1;
|
||||
p->lzma.remain_len = 0;
|
||||
p->lzma.temp_size = 0;
|
||||
}
|
||||
|
||||
if (in_cur > out_cur)
|
||||
in_cur = out_cur;
|
||||
if (in_cur == 0)
|
||||
{
|
||||
p->state = ST_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (rlzma_stream_put_uncompressed(&p->lzma, src + consumed,
|
||||
in_cur) != RLZMA_OK)
|
||||
{
|
||||
p->state = ST_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
consumed += in_cur;
|
||||
p->unpack_size -= (uint32_t)in_cur;
|
||||
p->state = (p->unpack_size == 0) ? ST_CONTROL : ST_DATA_CONT;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t got;
|
||||
size_t produced;
|
||||
int sub_status;
|
||||
int res;
|
||||
|
||||
if (p->state == ST_DATA)
|
||||
{
|
||||
/* 0xE0 and up reset the dictionary; 0xA0 and up reset
|
||||
* the probability model and match history.
|
||||
*
|
||||
* The range coder is different: every LZMA chunk starts
|
||||
* a fresh one, whatever the control byte says. Each
|
||||
* chunk's packed data begins with its own five-byte
|
||||
* range coder prologue, so "no reset" means the
|
||||
* dictionary and probabilities carry over, not the
|
||||
* coder. The reference does this unconditionally in
|
||||
* LzmaDec_InitDicAndState(), which always sets
|
||||
* remainLen to at least kMatchSpecLenStart + 1 ("need
|
||||
* init range coder") and only raises it to + 2 ("and
|
||||
* state") when initState is set.
|
||||
*
|
||||
* Carrying the coder across the boundary instead makes
|
||||
* the next chunk decode its prologue bytes as symbols,
|
||||
* which corrupts output from the chunk's very first
|
||||
* byte. */
|
||||
/* Of the four combinations these two comparisons can
|
||||
* produce, three are reachable with streams liblzma
|
||||
* emits: 0x8x gives (0, 0), 0xEx gives (1, 1), and an
|
||||
* uncompressed 0x01 chunk gives (1, 0). No encoder here
|
||||
* emits 0xA0-0xDF, so (0, 1) is never selected by a test
|
||||
* stream and is verified directly against
|
||||
* rlzma_stream_reset_parts() instead. See the coverage
|
||||
* note in r7z_lzma2.h before changing this. */
|
||||
int init_dic = (p->control >= 0xE0);
|
||||
int init_state = (p->control >= 0xA0);
|
||||
|
||||
rlzma_stream_reset_parts(&p->lzma, init_dic, init_state);
|
||||
|
||||
/* Force the prologue read even when neither reset
|
||||
* applied, since reset_parts() only sets need_init as
|
||||
* part of a state reset. */
|
||||
p->lzma.need_init = 1;
|
||||
p->lzma.remain_len = 0;
|
||||
p->lzma.temp_size = 0;
|
||||
p->lzma.range = 0;
|
||||
p->lzma.code = 0;
|
||||
|
||||
p->state = ST_DATA_CONT;
|
||||
}
|
||||
|
||||
got = in_cur;
|
||||
if (got > (size_t)p->pack_size)
|
||||
got = (size_t)p->pack_size;
|
||||
|
||||
/* The streaming decoder's `finish` means "this is the last
|
||||
* input of the stream", which unlocks its tail path: final
|
||||
* symbols decoded out of the range coder's buffered state
|
||||
* with no input left. That is wrong for a chunk in the
|
||||
* middle of an LZMA2 stream. The bytes after this chunk are
|
||||
* the next chunk's header, not more of this payload, and
|
||||
* letting the decoder reach for them desyncs the range
|
||||
* coder and produces nonsense distances a few symbols
|
||||
* later.
|
||||
*
|
||||
* chunk_finish says only that the output limit covers the
|
||||
* whole chunk, which is a different claim. Signal
|
||||
* end-of-input to the LZMA layer solely when this really is
|
||||
* the caller's last input and the chunk is the last one. */
|
||||
res = rlzma_stream_decode(&p->lzma, dic_pos + out_cur,
|
||||
src + consumed, &got,
|
||||
(finish && got >= (size_t)p->pack_size) ? 1 : 0,
|
||||
&sub_status);
|
||||
|
||||
consumed += got;
|
||||
p->pack_size -= (uint32_t)got;
|
||||
produced = p->lzma.dic_pos - dic_pos;
|
||||
p->unpack_size -= (uint32_t)produced;
|
||||
|
||||
if (res != RLZMA_OK)
|
||||
{
|
||||
p->state = ST_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sub_status == RLZMA_STATUS_NEEDS_MORE_INPUT)
|
||||
{
|
||||
if (p->pack_size == 0)
|
||||
{
|
||||
/* The chunk's packed bytes are used up but it still
|
||||
* owes output. That is normal, not corruption: the
|
||||
* range coder holds four bytes internally, so the
|
||||
* last symbols of a chunk decode with no input left.
|
||||
* The decoder only says NEEDS_MORE_INPUT because it
|
||||
* cannot know the chunk ended here.
|
||||
*
|
||||
* Tell it this is the end of its input by calling
|
||||
* again with finish set, so it takes its buffered
|
||||
* tail path. If that still produces nothing, the
|
||||
* declared sizes really are wrong. */
|
||||
size_t none = 0;
|
||||
size_t before = p->lzma.dic_pos;
|
||||
size_t extra;
|
||||
int res2;
|
||||
|
||||
res2 = rlzma_stream_decode(&p->lzma, dic_pos + out_cur,
|
||||
src + consumed, &none, 1, &sub_status);
|
||||
|
||||
extra = p->lzma.dic_pos - before;
|
||||
|
||||
if (res2 != RLZMA_OK || extra == 0)
|
||||
{
|
||||
p->state = ST_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
p->unpack_size -= (uint32_t)extra;
|
||||
if (p->unpack_size == 0)
|
||||
p->state = ST_CONTROL;
|
||||
continue;
|
||||
}
|
||||
*src_len = consumed;
|
||||
*status = RLZMA2_STATUS_NEEDS_MORE_INPUT;
|
||||
return RLZMA_OK;
|
||||
}
|
||||
|
||||
if (got == 0 && produced == 0 && p->unpack_size != 0)
|
||||
{
|
||||
/* No progress and output still owed: the declared sizes
|
||||
* disagree with the data. */
|
||||
p->state = ST_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (p->unpack_size == 0)
|
||||
{
|
||||
/* The chunk produced everything it promised. Its packed
|
||||
* size may still have bytes left: the range coder holds
|
||||
* four bytes internally and does not need to read the
|
||||
* last normalization byte to finish the final symbol.
|
||||
* Requiring pack_size == 0 would reject nearly every
|
||||
* real stream.
|
||||
*
|
||||
* Those bytes must still be stepped over, or the next
|
||||
* chunk header is read starting one byte late and the
|
||||
* whole rest of the stream is misparsed. This applies
|
||||
* however the chunk finished, including when the final
|
||||
* call made no progress at all, which is why it is not
|
||||
* folded into the branch above. */
|
||||
if (p->pack_size != 0)
|
||||
{
|
||||
size_t left = in_size - consumed;
|
||||
size_t skip = (size_t)p->pack_size;
|
||||
|
||||
if (skip > left)
|
||||
skip = left;
|
||||
consumed += skip;
|
||||
p->pack_size -= (uint32_t)skip;
|
||||
|
||||
if (p->pack_size != 0)
|
||||
{
|
||||
/* Tail of this chunk is in the next input block. */
|
||||
*src_len = consumed;
|
||||
*status = RLZMA2_STATUS_NEEDS_MORE_INPUT;
|
||||
return RLZMA_OK;
|
||||
}
|
||||
}
|
||||
p->state = ST_CONTROL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Output limit reached: the caller must drain before more can be
|
||||
* decoded, whatever the input situation. */
|
||||
if (p->lzma.dic_pos >= dic_limit && p->state != ST_FINISHED)
|
||||
{
|
||||
*src_len = consumed;
|
||||
*status = RLZMA2_STATUS_NOT_FINISHED;
|
||||
return RLZMA_OK;
|
||||
}
|
||||
|
||||
/* Input exhausted. More may still be decodable from the range
|
||||
* coder's buffered state, but only inside a chunk's data phase;
|
||||
* a header needs real bytes. Returning here when the decoder
|
||||
* could still make progress would end the stream early, which is
|
||||
* how a chunk split across calls loses its tail. */
|
||||
if (consumed >= in_size)
|
||||
{
|
||||
if (p->state != ST_DATA && p->state != ST_DATA_CONT)
|
||||
{
|
||||
*src_len = consumed;
|
||||
*status = (p->state == ST_FINISHED)
|
||||
? RLZMA2_STATUS_FINISHED
|
||||
: RLZMA2_STATUS_NEEDS_MORE_INPUT;
|
||||
return RLZMA_OK;
|
||||
}
|
||||
if (!finish)
|
||||
{
|
||||
*src_len = consumed;
|
||||
*status = RLZMA2_STATUS_NEEDS_MORE_INPUT;
|
||||
return RLZMA_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*src_len = consumed;
|
||||
return RLZMA_ERROR_DATA;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Loaded 100 of 580 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user