mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-10 11:31:41 -10:00
Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
122da7195a | ||
|
|
a5d7f8b5bd | ||
|
|
183ba70c15 | ||
|
|
1a6a5c2e91 | ||
|
|
65f332c151 | ||
|
|
1b87a9af45 | ||
|
|
bbbc50f1f6 | ||
|
|
fed60b7e05 | ||
|
|
49582a9383 | ||
|
|
5f5f2f47ea | ||
|
|
1087a448da | ||
|
|
67c4a68f89 | ||
|
|
bf5c84a903 | ||
|
|
58ccb33e28 | ||
|
|
798dcd7f33 | ||
|
|
024dcba88c | ||
|
|
a86d86a22c | ||
|
|
4c1412cd0f | ||
|
|
cddfd368da | ||
|
|
c1c815b33e | ||
|
|
4356847c42 | ||
|
|
afcfce0d67 | ||
|
|
0580d2d6fc | ||
|
|
378865087c | ||
|
|
7600667d1b | ||
|
|
86a5c9918b | ||
|
|
0484a6d4f7 | ||
|
|
095b916b08 | ||
|
|
97dd672cc6 | ||
|
|
8c62b973fd | ||
|
|
dd3a2d0831 | ||
|
|
dd8f53271f | ||
|
|
22b987902c | ||
|
|
5dd6fbbfc8 | ||
|
|
d10f7ab438 | ||
|
|
a27ef55d56 | ||
|
|
c867b2a34a | ||
|
|
085bf1bf56 | ||
|
|
1dbab030d4 | ||
|
|
ce3ee5174c | ||
|
|
7370c1a268 | ||
|
|
0803d4be13 | ||
|
|
9cbbbcbacf | ||
|
|
1f59acacbe |
No files matched your search
@@ -146,7 +146,26 @@ jobs:
|
||||
# The system upgrade runs in the action: an msys2-runtime update
|
||||
# kills every MSYS2 process, which a run step's own shell would
|
||||
# not survive.
|
||||
- uses: msys2/setup-msys2@v2
|
||||
- name: Set up MSYS2
|
||||
id: msys2
|
||||
continue-on-error: true
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.sys }}
|
||||
update: true
|
||||
|
||||
# setup-msys2 has no retry of its own, and its system upgrade fails
|
||||
# the job when mirror.msys2.org stalls ("Operation too slow") on the
|
||||
# package databases. A failed attempt is run once more after a
|
||||
# minute; the second reinstalls over the first.
|
||||
- name: Wait before retrying MSYS2
|
||||
if: steps.msys2.outcome == 'failure'
|
||||
shell: bash
|
||||
run: sleep 60
|
||||
|
||||
- name: Set up MSYS2 (retry)
|
||||
if: steps.msys2.outcome == 'failure'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.sys }}
|
||||
update: true
|
||||
|
||||
@@ -171,6 +171,9 @@ jobs:
|
||||
make -C ../tls_retro clean
|
||||
SSL_BACKEND=$be ../tls_retro/verify_hook_test.sh
|
||||
make -C ../tls_retro clean
|
||||
SSL_BACKEND=$be CFLAGS='-O1 -g1 -std=gnu89 -Wall -Wextra -Werror=declaration-after-statement -fsanitize=address,undefined -fno-omit-frame-pointer' \
|
||||
sh ../tls_retro/handshake_test.sh
|
||||
make -C ../tls_retro clean
|
||||
|
||||
- name: Operate the libsmb2 backend (SMB VFS against a local smbd)
|
||||
if: matrix.lane == 'libsmb2'
|
||||
|
||||
@@ -126,6 +126,10 @@ jobs:
|
||||
rtmp_control_test
|
||||
rtmp_session_test
|
||||
rtmp_socket_test
|
||||
rtmp_connect_test
|
||||
rtmp_connect_select_test
|
||||
rtmp_resolve_test
|
||||
rtmp_resolve_native_test
|
||||
rslc_test
|
||||
rslc_build_test
|
||||
rslc_ext_test
|
||||
@@ -146,8 +150,11 @@ jobs:
|
||||
rzstd_raw_block_test_novec
|
||||
rzstd_dict_test
|
||||
rbzip2_test
|
||||
shiftjis_test
|
||||
rinflate_window_test
|
||||
rdeflate_hash_test
|
||||
rdeflate_flush_test
|
||||
encoding_adler32_test
|
||||
emscripten_mmap_test
|
||||
archive_zip_test
|
||||
archive_zstd_test
|
||||
@@ -164,6 +171,16 @@ jobs:
|
||||
path_mkdir_test
|
||||
path_parent_dir_test
|
||||
rpng
|
||||
# a PNG a row at a time, as stored, from input in pieces
|
||||
rpng_rows_test
|
||||
# a GIF's first image, straight into the caller's rows
|
||||
rgif_test
|
||||
# YAML: configurations, block scalars, a patch file whose
|
||||
# entries name anchored sequences by alias, node locations
|
||||
ryaml_test
|
||||
# what the YAML writer writes reads back the same, over
|
||||
# tricky scalars and 3000 random documents
|
||||
ryaml_writer_test
|
||||
rzip_chunk_size_test
|
||||
rzip_matches_buf_test
|
||||
rzip_codec_test
|
||||
@@ -184,6 +201,8 @@ jobs:
|
||||
rh264_state_test
|
||||
# rmp3's frame-at-a-time API against its pull API.
|
||||
rmp3_frame_test
|
||||
# rmp3's gapless trimming against ffmpeg's, and its tag parser.
|
||||
rmp3_gapless_test
|
||||
# raac_last_size over units followed by more.
|
||||
raac_size_test
|
||||
# raac's savestates: saved between units, loaded into a new decoder.
|
||||
@@ -253,6 +272,15 @@ jobs:
|
||||
http_parse_test
|
||||
# UPnP gateway client: parsers, and SOAP against a fake gateway.
|
||||
net_upnp_test
|
||||
# net_http: a 3xx handed back unfollowed, a DELETE body's
|
||||
# Content-Length, the caller's Accept-Encoding/User-Agent.
|
||||
net_http_options_test
|
||||
# socket_poll past FD_SETSIZE sockets.
|
||||
socket_poll_test
|
||||
# Half floats both ways, against F16C.
|
||||
float16_test
|
||||
# TrueType and CID-keyed CFF glyphs, metrics and kerning.
|
||||
rtt_test
|
||||
rjson_test
|
||||
rm3u_test
|
||||
rmp4_fragment_test
|
||||
@@ -407,6 +435,11 @@ jobs:
|
||||
# stereo stream (tones, gain points, swap and negate masks)
|
||||
# and a mono one, then bad frames and garbage.
|
||||
ratrac3p_test
|
||||
# ATRAC9 against a reference decoder's output for generated
|
||||
# streams in every channel layout, rate and superframe size,
|
||||
# with reset and savestates; then its own unit checks.
|
||||
ratrac9_vectors_test
|
||||
ratrac9_test
|
||||
# Animation playback against a scripted stream: order,
|
||||
# pacing, loops, catch-up, the worker's two jobs.
|
||||
video_player_test
|
||||
@@ -1420,6 +1453,34 @@ jobs:
|
||||
-lwinmm -o thread_cleanup_test.exe
|
||||
timeout 30 wine ./thread_cleanup_test.exe
|
||||
|
||||
- name: rtt under Wine
|
||||
shell: bash
|
||||
working-directory: libretro-common/samples/gfx/font
|
||||
env:
|
||||
WINEDEBUG: -all
|
||||
run: |
|
||||
set -eu
|
||||
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
|
||||
make clean
|
||||
make CC=x86_64-w64-mingw32-gcc all
|
||||
timeout 60 wine ./rtt_test.exe
|
||||
make clean
|
||||
|
||||
- name: socket_poll past FD_SETSIZE under Wine
|
||||
shell: bash
|
||||
working-directory: libretro-common/samples/net/socket_poll
|
||||
env:
|
||||
WINEDEBUG: -all
|
||||
run: |
|
||||
set -eu
|
||||
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
|
||||
# select() takes 64 sockets a set; past that the sets are sized
|
||||
# for the call.
|
||||
make clean
|
||||
make CC=x86_64-w64-mingw32-gcc all
|
||||
timeout 60 wine ./socket_poll_test.exe
|
||||
make clean
|
||||
|
||||
- name: Build and run the NVIDIA loaders under Wine
|
||||
shell: bash
|
||||
working-directory: libretro-common/samples/gfx/nvidia
|
||||
|
||||
@@ -64,6 +64,15 @@ jobs:
|
||||
run: python3 samples/menu/rgui_media/run.py --c89 --cadence-hz 30 --driver materialui samples/menu/rgui_media/fixture.mp4
|
||||
- name: Real XMB file-browser playback controls
|
||||
run: python3 samples/menu/rgui_media/run.py --c89 --cadence-hz 30 --driver xmb samples/menu/rgui_media/fixture.mp4
|
||||
- name: QuickTime MOV browsing and external launch across all menus
|
||||
run: |
|
||||
ffmpeg -v error -y -i samples/menu/rgui_media/fixture.mp4 -map 0 -c copy -f mov samples/menu/rgui_media/fixture.mov
|
||||
python3 -c "import json,subprocess; p='samples/menu/rgui_media/fixture.mov'; d=json.loads(subprocess.check_output(['ffprobe','-v','error','-show_format','-of','json',p])); assert d['format']['tags']['major_brand']=='qt '"
|
||||
cp samples/menu/rgui_media/fixture.mov samples/menu/rgui_media/fixture-upper.MOV
|
||||
for driver in rgui materialui xmb ozone; do
|
||||
python3 samples/menu/rgui_media/run.py --c89 --driver "$driver" samples/menu/rgui_media/fixture.mov
|
||||
python3 samples/menu/rgui_media/run.py --c89 --external --driver "$driver" samples/menu/rgui_media/fixture-upper.MOV
|
||||
done
|
||||
- name: Real Ozone file-browser playback controls
|
||||
run: python3 samples/menu/rgui_media/run.py --c89 --cadence-hz 30 --driver ozone samples/menu/rgui_media/fixture.mp4
|
||||
- name: UTF-8 subtitle sidecars across all four menus
|
||||
@@ -188,6 +197,13 @@ jobs:
|
||||
xvfb-run -a python3 samples/menu/rgui_media/run.py --c89 --continuous-error --video-driver gl --driver "$driver" $threaded samples/menu/rgui_media/window-toggle.webm
|
||||
done
|
||||
done
|
||||
- name: External movie settings retain the playing preview
|
||||
run: |
|
||||
for driver in materialui xmb ozone; do
|
||||
for threaded in '' --threaded; do
|
||||
MEDIA_TEST_SETTINGS_ORANGE=1 xvfb-run -a python3 samples/menu/rgui_media/run.py --c89 --external --external-settings --video-driver gl --driver "$driver" $threaded samples/menu/rgui_media/settings-preview.webm
|
||||
done
|
||||
done
|
||||
- name: Generate delayed first-picture fixture
|
||||
run: ffmpeg -v error -y -f lavfi -i "testsrc2=size=320x240:rate=30,hue=h=90*t" -vf "setpts=PTS+0.2/TB" -t 3 -c:v libvpx -deadline realtime -cpu-used 8 -g 90 -auto-alt-ref 0 samples/menu/rgui_media/delayed.webm
|
||||
- name: Compare actual visible pictures after delayed-start seeks
|
||||
|
||||
@@ -87,10 +87,22 @@ jobs:
|
||||
# IP address matches only an iPAddress SAN entry, never the
|
||||
# same address written as a DNS name.
|
||||
samples/network/tls_retro/local_server_test.sh
|
||||
# One connection read and written from two threads at once
|
||||
# (SSL_RETRO_DUPLEX), under ThreadSanitizer
|
||||
make -C samples/network/tls_retro clean
|
||||
make -C samples/network/tls_retro tls_duplex CFLAGS='-O1 -g -fsanitize=thread -fno-omit-frame-pointer'
|
||||
TLS_TEST_DUPLEX_ONLY=1 TSAN_OPTIONS=halt_on_error=1 RUNNER="setarch $(uname -m) -R" \
|
||||
samples/network/tls_retro/local_server_test.sh
|
||||
# The same tool cross-linked for Windows: the sample's link
|
||||
# line has to carry Winsock, which nothing on Linux checks.
|
||||
make -C samples/network/tls_retro clean
|
||||
|
||||
- name: Incremental built-in TLS handshakes
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
TLS_TEST_HANDSHAKE_STEP=1 samples/network/tls_retro/local_server_test.sh
|
||||
make -C samples/network/tls_retro clean
|
||||
|
||||
- name: Built-in SMB2/3 client against a local smbd
|
||||
shell: bash
|
||||
|
||||
@@ -7,6 +7,10 @@ on:
|
||||
paths:
|
||||
- 'record/**'
|
||||
- 'libretro-common/net/net_rtmp*'
|
||||
- 'libretro-common/net/net_socket.c'
|
||||
- 'libretro-common/net/net_resolve.c'
|
||||
- 'libretro-common/include/net/net_resolve.h'
|
||||
- 'libretro-common/include/net/net_socket.h'
|
||||
- 'libretro-common/include/net/net_rtmp*'
|
||||
- 'libretro-common/formats/flv/**'
|
||||
- 'libretro-common/include/formats/rflv_mux.h'
|
||||
@@ -24,6 +28,10 @@ on:
|
||||
paths:
|
||||
- 'record/**'
|
||||
- 'libretro-common/net/net_rtmp*'
|
||||
- 'libretro-common/net/net_socket.c'
|
||||
- 'libretro-common/net/net_resolve.c'
|
||||
- 'libretro-common/include/net/net_resolve.h'
|
||||
- 'libretro-common/include/net/net_socket.h'
|
||||
- 'libretro-common/include/net/net_rtmp*'
|
||||
- 'libretro-common/formats/flv/**'
|
||||
- 'libretro-common/include/formats/rflv_mux.h'
|
||||
@@ -52,7 +60,7 @@ jobs:
|
||||
name: Build and run samples/record
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
APT_PKGS: mingw-w64 wine build-essential pkg-config ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev ccache
|
||||
APT_PKGS: mingw-w64 wine build-essential pkg-config ffmpeg openssl python3 libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev ccache
|
||||
timeout-minutes: 25
|
||||
|
||||
steps:
|
||||
@@ -161,14 +169,36 @@ jobs:
|
||||
make clean && make check stream-check CC=x86_64-w64-mingw32-gcc EXE=.exe RUNNER=wine
|
||||
make clean
|
||||
|
||||
- name: RTMP protocol and sockets (ASan + UBSan, mingw-w64)
|
||||
- name: Media Foundation AAC portable and cross builds
|
||||
shell: bash
|
||||
working-directory: samples/record/mf_audio
|
||||
run: |
|
||||
set -eu
|
||||
make clean all SANITIZER=address,undefined
|
||||
make clean all CC=x86_64-w64-mingw32-gcc EXE=.exe
|
||||
x86_64-w64-mingw32-objdump -p encode_test.exe > imports.txt
|
||||
if grep -Ei 'DLL Name:.*(mfplat|mfaacenc|ole32)\.dll' imports.txt; then
|
||||
echo 'Native AAC must load optional entry points at runtime'
|
||||
exit 1
|
||||
fi
|
||||
make clean
|
||||
|
||||
- name: RTMP protocol and sockets (ASan + UBSan, TSan, mingw-w64)
|
||||
shell: bash
|
||||
working-directory: libretro-common/samples/net/rtmp
|
||||
run: |
|
||||
set -eu
|
||||
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
|
||||
make clean && make check peer-check SANITIZER=address,undefined
|
||||
make rtmps_socket_native_test SANITIZER=address,undefined
|
||||
python3 media_peer.py --mux --tls 1.2 ./rtmps_socket_native_test
|
||||
python3 media_peer.py --mux --tls 1.3 ./rtmps_socket_native_test
|
||||
make clean && make rtmp_resolve_native_test SANITIZER=thread
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 30 ./rtmp_resolve_native_test
|
||||
make clean && make check peer-check CC=x86_64-w64-mingw32-gcc EXE=.exe RUNNER=wine
|
||||
make rtmps_socket_native_test.exe CC=x86_64-w64-mingw32-gcc EXE=.exe
|
||||
python3 media_peer.py --runner wine --mux --tls 1.2 ./rtmps_socket_native_test.exe
|
||||
python3 media_peer.py --runner wine --mux --tls 1.3 ./rtmps_socket_native_test.exe
|
||||
make clean
|
||||
|
||||
- name: GPU PCM output pipeline (ASan + UBSan, TSan, mingw-w64)
|
||||
@@ -241,3 +271,42 @@ jobs:
|
||||
with:
|
||||
path: ~/.ccache
|
||||
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
|
||||
|
||||
mf-audio-native:
|
||||
name: Native Windows AAC packets and source clock
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Ensure the server media component is installed
|
||||
shell: pwsh
|
||||
run: |
|
||||
if ((Get-CimInstance Win32_OperatingSystem).ProductType -ne 1) {
|
||||
$feature = Get-WindowsFeature Server-Media-Foundation -ErrorAction Stop
|
||||
if (-not $feature.Installed) {
|
||||
$result = Install-WindowsFeature Server-Media-Foundation -ErrorAction Stop
|
||||
if (-not $result.Success) { throw 'Media Foundation installation failed' }
|
||||
}
|
||||
}
|
||||
|
||||
- name: Native compiler and independent decoder
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: MINGW64
|
||||
update: false
|
||||
install: make mingw-w64-x86_64-gcc mingw-w64-x86_64-python mingw-w64-x86_64-ffmpeg
|
||||
|
||||
- name: Encode and independently decode native AAC
|
||||
shell: msys2 {0}
|
||||
working-directory: samples/record/mf_audio
|
||||
run: |
|
||||
set -eu
|
||||
make clean native-check CC=gcc EXE=.exe
|
||||
objdump -p encode_test.exe > imports.txt
|
||||
if grep -Ei 'DLL Name:.*(mfplat|mfaacenc|ole32)\.dll' imports.txt; then
|
||||
echo 'Native AAC must load optional entry points at runtime'
|
||||
exit 1
|
||||
fi
|
||||
make clean
|
||||
@@ -958,6 +958,14 @@ jobs:
|
||||
python3 samples/menu/rgui_media/run.py --driver rgui --video-driver ${{ matrix.driver }} --gpu-surface --refresh-pixels samples/menu/rgui_media/fixture.mp4
|
||||
python3 samples/menu/rgui_media/run.py --driver rgui --video-driver ${{ matrix.driver }} --gpu-surface --continuous-error samples/menu/rgui_media/fixture.mp4
|
||||
|
||||
- name: External Ozone movie settings at drive root and in a subfolder
|
||||
if: matrix.driver == 'd3d11'
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
set -eu
|
||||
ffmpeg -v error -y -f lavfi -i color=orange:size=320x240:rate=30 -t 3 -c:v libx264 -preset ultrafast -pix_fmt yuv420p samples/menu/rgui_media/settings-preview.mp4
|
||||
MEDIA_TEST_SETTINGS_ORANGE=1 python3 samples/menu/texture_media/external_settings_paths.py samples/menu/rgui_media/settings-preview.mp4 --video-driver ${{ matrix.driver }} --threaded
|
||||
|
||||
views-sdl3:
|
||||
name: Views on SDL3, end to end (Xvfb, llvmpipe)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -56,6 +56,24 @@ jobs:
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up MSYS2
|
||||
id: msys2
|
||||
continue-on-error: true
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.sys }}
|
||||
update: true
|
||||
|
||||
# setup-msys2 has no retry of its own, and its system upgrade fails
|
||||
# the job when mirror.msys2.org stalls ("Operation too slow") on the
|
||||
# package databases. A failed attempt is run once more after a
|
||||
# minute; the second reinstalls over the first.
|
||||
- name: Wait before retrying MSYS2
|
||||
if: steps.msys2.outcome == 'failure'
|
||||
shell: bash
|
||||
run: sleep 60
|
||||
|
||||
- name: Set up MSYS2 (retry)
|
||||
if: steps.msys2.outcome == 'failure'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.sys }}
|
||||
|
||||
@@ -1082,7 +1082,7 @@ check_gates "griffin: HAVE_RETROSMB + cloud sync" "$UIDEFS -DRARCH_INTERNAL -DH
|
||||
# griffin can carry. A static helper in one translation unit that
|
||||
# another one also defines (get32 in net_smb2.c and rvorbis.c) only
|
||||
# collides here, so this lane carries as much of griffin as a host can.
|
||||
GRIFFIN_FULL="-DRARCH_INTERNAL -DHAVE_GRIFFIN=1 -DHAVE_MENU -DHAVE_RGUI -DHAVE_XMB -DHAVE_OZONE -DHAVE_MATERIALUI -DHAVE_GFX_WIDGETS -DHAVE_OVERLAY -DHAVE_CONFIGFILE -DHAVE_CHEEVOS -DRC_DISABLE_LUA -DHAVE_RUNAHEAD -DHAVE_REWIND -DHAVE_PATCH -DHAVE_XDELTA -DHAVE_CHEATS -DHAVE_SCREENSHOTS -DHAVE_TRANSLATE -DHAVE_LIBRETRODB -DHAVE_DYNAMIC -DHAVE_DYLIB -DHAVE_THREADS -DHAVE_AUDIOMIXER -DHAVE_DSP_FILTER -DHAVE_VIDEO_FILTER -DHAVE_RWAV -DHAVE_RFLAC -DHAVE_RVORBIS -DHAVE_RMP3 -DHAVE_RAC3 -DHAVE_RPNG -DHAVE_RJPEG -DHAVE_RBMP -DHAVE_RTGA -DHAVE_RWEBP -DHAVE_RDDS -DHAVE_RZSTD -DHAVE_7ZIP -DHAVE_CHD -DHAVE_RCHD -DHAVE_STB_FONT -DHAVE_NETWORKING -DHAVE_IFINFO -DHAVE_NETWORK_CMD -DHAVE_NETPLAYDISCOVERY -DHAVE_ONLINE_UPDATER -DHAVE_UPDATE_ASSETS -DHAVE_UPDATE_CORES -DHAVE_UPDATE_CORE_INFO -DHAVE_CORE_INFO_CACHE -DHAVE_CLOUDSYNC -DHAVE_S3 -DHAVE_CRYPTO -DHAVE_KEYCHAIN -DHAVE_SSL -DHAVE_RETROSSL -DHAVE_SMBCLIENT -DHAVE_RETROSMB -DHAVE_NFSCLIENT -DHAVE_RETRONFS -DHAVE_LANGEXTRA -DHAVE_SHADERPIPELINE -DHAVE_GLSL -DHAVE_OPENGL -D_GNU_SOURCE"
|
||||
GRIFFIN_FULL="-DRARCH_INTERNAL -DHAVE_GRIFFIN=1 -DHAVE_MENU -DHAVE_RGUI -DHAVE_XMB -DHAVE_OZONE -DHAVE_MATERIALUI -DHAVE_GFX_WIDGETS -DHAVE_OVERLAY -DHAVE_CONFIGFILE -DHAVE_CHEEVOS -DRC_DISABLE_LUA -DHAVE_RUNAHEAD -DHAVE_REWIND -DHAVE_PATCH -DHAVE_XDELTA -DHAVE_CHEATS -DHAVE_SCREENSHOTS -DHAVE_TRANSLATE -DHAVE_LIBRETRODB -DHAVE_DYNAMIC -DHAVE_DYLIB -DHAVE_THREADS -DHAVE_AUDIOMIXER -DHAVE_DSP_FILTER -DHAVE_VIDEO_FILTER -DHAVE_RWAV -DHAVE_RFLAC -DHAVE_RVORBIS -DHAVE_RMP3 -DHAVE_RAC3 -DHAVE_RPNG -DHAVE_RJPEG -DHAVE_RBMP -DHAVE_RTGA -DHAVE_RGIF -DHAVE_RWEBP -DHAVE_RDDS -DHAVE_RZSTD -DHAVE_7ZIP -DHAVE_CHD -DHAVE_RCHD -DHAVE_STB_FONT -DHAVE_NETWORKING -DHAVE_IFINFO -DHAVE_NETWORK_CMD -DHAVE_NETPLAYDISCOVERY -DHAVE_ONLINE_UPDATER -DHAVE_UPDATE_ASSETS -DHAVE_UPDATE_CORES -DHAVE_UPDATE_CORE_INFO -DHAVE_CORE_INFO_CACHE -DHAVE_CLOUDSYNC -DHAVE_S3 -DHAVE_CRYPTO -DHAVE_KEYCHAIN -DHAVE_SSL -DHAVE_RETROSSL -DHAVE_SMBCLIENT -DHAVE_RETROSMB -DHAVE_NFSCLIENT -DHAVE_RETRONFS -DHAVE_LANGEXTRA -DHAVE_SHADERPIPELINE -DHAVE_GLSL -DHAVE_OPENGL -D_GNU_SOURCE"
|
||||
check_gates "griffin: full unity (every codec + network stack)" "$UIDEFS $GRIFFIN_FULL" griffin/griffin.c
|
||||
# The SMB show-settings row read a HAVE_MENU-only default under
|
||||
# HAVE_SMBCLIENT alone; with the built-in client on by default that
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# Future
|
||||
- NETWORK: Conditions compare how far apart two addresses are (485 - 47e <= 32), STEPS takes stop becomes for a stop that fires only once it starts to hold, READ_CORE_MEMORY reads several ranges in one request, and WATCH counts its limit after the offs in a request
|
||||
- D3D11: Software-rendering cores draw straight into lent staging textures, copied to the frame texture on the GPU instead of by the CPU
|
||||
- VULKAN: Keep the pipeline cache on disk between runs, one file per GPU, so stock and slang shader pipelines are not recompiled by the driver on every launch, preset load or driver reinit
|
||||
- LINUX: Game Mode implemented natively (I/O priority plus a power-profiles-daemon performance hold); Feral libgamemode is no longer loaded and deps/feralgamemode is removed
|
||||
|
||||
+15
-1
@@ -1126,7 +1126,8 @@ ifeq ($(HAVE_OVERLAY), 1)
|
||||
led/drivers/led_overlay.o
|
||||
endif
|
||||
|
||||
OBJ += gfx/drivers_font_renderer/stb.o
|
||||
OBJ += gfx/drivers_font_renderer/stb.o \
|
||||
$(LIBRETRO_COMM_DIR)/gfx/font/rtt.o
|
||||
|
||||
ifeq ($(HAVE_FREETYPE), 1)
|
||||
OBJ += gfx/drivers_font_renderer/freetype.o
|
||||
@@ -2038,6 +2039,13 @@ ifeq ($(HAVE_D3D12), 1)
|
||||
DEFINES += -DHAVE_D3D12
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_MF_AUDIO), 1)
|
||||
ifneq ($(findstring Win32,$(OS)),)
|
||||
DEFINES += -DHAVE_MF_AUDIO
|
||||
OBJ += record/record_mf_audio.o
|
||||
endif
|
||||
endif
|
||||
|
||||
# H.264 previews decoded on the GPU, as rh264's backend; D3D12 imports
|
||||
# its frames as well
|
||||
ifneq ($(findstring 1, $(HAVE_D3D11) $(HAVE_D3D12)),)
|
||||
@@ -2523,6 +2531,11 @@ ifeq ($(HAVE_RTGA), 1)
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/formats/tga/rtga.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_RGIF), 1)
|
||||
DEFINES += -DHAVE_RGIF
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/formats/gif/rgif.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_RDDS), 1)
|
||||
DEFINES += -DHAVE_RDDS
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/formats/dds/rdds.o
|
||||
@@ -2766,6 +2779,7 @@ ifeq ($(HAVE_NETWORKING), 1)
|
||||
$(LIBRETRO_COMM_DIR)/net/net_http.o \
|
||||
$(LIBRETRO_COMM_DIR)/net/net_http_parse.o \
|
||||
$(LIBRETRO_COMM_DIR)/net/net_socket.o \
|
||||
$(LIBRETRO_COMM_DIR)/net/net_resolve.o \
|
||||
core_updater_list.o \
|
||||
network/natt.o \
|
||||
network/natt_desc.o \
|
||||
|
||||
+2
-1
@@ -53,7 +53,7 @@ DEFINES :=
|
||||
ifeq ($(GRIFFIN_BUILD), 1)
|
||||
OBJ += griffin/griffin.o
|
||||
DEFINES += -DHAVE_GRIFFIN=1 -DHAVE_MENU -DHAVE_CONFIGFILE -DHAVE_RGUI -DHAVE_XMB -DHAVE_LIBRETRODB -DHAVE_CC_RESAMPLER
|
||||
DEFINES += -DHAVE_7ZIP -DHAVE_RZSTD -DHAVE_RPNG -DHAVE_RWEBP -DHAVE_RDDS -DHAVE_RJPEG -DHAVE_RBMP -DHAVE_RTGA
|
||||
DEFINES += -DHAVE_7ZIP -DHAVE_RZSTD -DHAVE_RPNG -DHAVE_RWEBP -DHAVE_RDDS -DHAVE_RJPEG -DHAVE_RBMP -DHAVE_RTGA -DHAVE_RGIF
|
||||
DEFINES += -DHAVE_NETWORKING -DHAVE_IFINFO -DHAVE_CHEEVOS -DRC_DISABLE_LUA -DHAVE_ONLINE_UPDATER -DHAVE_UPDATE_CORES
|
||||
DEFINES += -DHAVE_UPDATE_CORE_INFO
|
||||
#DEFINES += -DHAVE_UPDATE_ASSETS
|
||||
@@ -73,6 +73,7 @@ else
|
||||
HAVE_CC_RESAMPLER = 1
|
||||
HAVE_MENU_COMMON = 1
|
||||
HAVE_RTGA = 1
|
||||
HAVE_RGIF = 1
|
||||
HAVE_RPNG = 1
|
||||
HAVE_RWEBP = 1
|
||||
HAVE_RDDS = 1
|
||||
|
||||
@@ -34,6 +34,7 @@ HAVE_AUDIOMIXER = 1
|
||||
HAVE_CC_RESAMPLER = 1
|
||||
HAVE_MENU_COMMON = 1
|
||||
HAVE_RTGA = 1
|
||||
HAVE_RGIF = 1
|
||||
HAVE_RPNG = 1
|
||||
HAVE_RWEBP = 1
|
||||
HAVE_RDDS = 1
|
||||
|
||||
@@ -34,6 +34,7 @@ HAVE_AUDIOMIXER = 1
|
||||
HAVE_CC_RESAMPLER = 1
|
||||
HAVE_MENU_COMMON = 1
|
||||
HAVE_RTGA = 1
|
||||
HAVE_RGIF = 1
|
||||
HAVE_RPNG = 1
|
||||
HAVE_RJPEG = 1
|
||||
HAVE_RBMP = 1
|
||||
|
||||
@@ -196,6 +196,7 @@ else ifeq ($(libogc_platform), 1)
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_RMODTRACKER := 1
|
||||
HAVE_OVERLAY := 1
|
||||
HAVE_7ZIP := 1
|
||||
@@ -272,6 +273,7 @@ else ifeq ($(platform), psp1)
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_KERNEL_PRX := 1
|
||||
#HAVE_LANGEXTRA := 1
|
||||
RARCH_CONSOLE = 1
|
||||
@@ -324,6 +326,7 @@ else ifeq ($(platform), vita)
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_7ZIP := 1
|
||||
HAVE_GXM := 1
|
||||
HAVE_NETWORKING := 1
|
||||
@@ -346,6 +349,7 @@ else ifeq ($(platform), windows_msvc6_x86)
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_7ZIP := 1
|
||||
HAVE_NETWORKING := 0
|
||||
HAVE_IFINFO := 0
|
||||
@@ -397,6 +401,7 @@ else ifeq ($(platform), windows_msvc2003_x86)
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_7ZIP := 1
|
||||
HAVE_NETWORKING := 1
|
||||
HAVE_IFINFO := 0
|
||||
@@ -454,6 +459,7 @@ else ifeq ($(platform), windows_msvc2005_x86)
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_7ZIP := 1
|
||||
HAVE_NETWORKING := 1
|
||||
HAVE_IFINFO := 0
|
||||
@@ -542,6 +548,7 @@ else ifneq (,$(findstring windows_msvc2010,$(platform)))
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_7ZIP := 1
|
||||
HAVE_NETWORKING := 1
|
||||
HAVE_IFINFO := 1
|
||||
@@ -638,6 +645,7 @@ else ifneq (,$(findstring windows_msvc2012,$(platform)))
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_7ZIP := 1
|
||||
HAVE_NETWORKING := 1
|
||||
HAVE_IFINFO := 1
|
||||
@@ -725,6 +733,7 @@ else ifneq (,$(findstring windows_msvc2013,$(platform)))
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_7ZIP := 1
|
||||
HAVE_NETWORKING := 1
|
||||
HAVE_IFINFO := 1
|
||||
@@ -812,6 +821,7 @@ else ifneq (,$(findstring windows_msvc2015,$(platform)))
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_7ZIP := 1
|
||||
HAVE_NETWORKING := 1
|
||||
HAVE_IFINFO := 1
|
||||
@@ -914,6 +924,7 @@ else ifeq (qnx,$(platform))
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_7ZIP := 1
|
||||
HAVE_NETWORKING := 1
|
||||
HAVE_IFINFO := 1
|
||||
@@ -960,6 +971,7 @@ else ifneq (,$(findstring unix,$(platform)))
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_7ZIP := 1
|
||||
HAVE_NETWORKING := 1
|
||||
HAVE_IFINFO := 1
|
||||
@@ -1077,6 +1089,9 @@ endif
|
||||
ifeq ($(HAVE_RTGA), 1)
|
||||
CFLAGS += -DHAVE_RTGA
|
||||
endif
|
||||
ifeq ($(HAVE_RGIF), 1)
|
||||
CFLAGS += -DHAVE_RGIF
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_BSV_MOVIE), 1)
|
||||
CFLAGS += -DHAVE_BSV_MOVIE
|
||||
@@ -1129,6 +1144,12 @@ ifeq ($(HAVE_RFLV), 1)
|
||||
CFLAGS += -DHAVE_RFLV
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_MF_AUDIO), 1)
|
||||
ifeq ($(system_platform), win)
|
||||
CFLAGS += -DHAVE_MF_AUDIO
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_IFINFO), 1)
|
||||
CFLAGS += -DHAVE_IFINFO
|
||||
endif
|
||||
|
||||
@@ -29,6 +29,7 @@ HAVE_AUDIOMIXER = 1
|
||||
HAVE_CC_RESAMPLER = 1
|
||||
HAVE_MENU_COMMON = 1
|
||||
HAVE_RTGA = 1
|
||||
HAVE_RGIF = 1
|
||||
HAVE_RPNG = 1
|
||||
HAVE_RWEBP = 1
|
||||
HAVE_RDDS = 1
|
||||
|
||||
@@ -54,6 +54,7 @@ HAVE_MENU_COMMON := 1
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_7ZIP := 1
|
||||
HAVE_NETWORKING := 1
|
||||
HAVE_IFINFO := 1
|
||||
|
||||
@@ -158,6 +158,7 @@ HAVE_RDDS := 0
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_RMODTRACKER := 0
|
||||
HAVE_OVERLAY := 1
|
||||
HAVE_7ZIP := 0
|
||||
|
||||
+2
-1
@@ -34,7 +34,7 @@ ifeq ($(GRIFFIN_BUILD), 1)
|
||||
OBJ += griffin/griffin.o
|
||||
DEFINES += -DHAVE_GRIFFIN=1
|
||||
DEFINES += -DHAVE_MENU -DHAVE_LIBRETRODB -DHAVE_CONFIGFILE
|
||||
DEFINES += -DHAVE_RPNG -DHAVE_RWEBP -DHAVE_RDDS -DHAVE_RJPEG -DHAVE_RBMP -DHAVE_RTGA -DHAVE_CC_RESAMPLER
|
||||
DEFINES += -DHAVE_RPNG -DHAVE_RWEBP -DHAVE_RDDS -DHAVE_RJPEG -DHAVE_RBMP -DHAVE_RTGA -DHAVE_RGIF -DHAVE_CC_RESAMPLER
|
||||
DEFINES += -DHAVE_CORE_INFO_CACHE
|
||||
ifeq ($(DEBUG), 1)
|
||||
DEFINES += -DHAVE_NETLOGGER
|
||||
@@ -50,6 +50,7 @@ else
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_OVERLAY := 1
|
||||
HAVE_7ZIP := 1
|
||||
HAVE_RZSTD := 1
|
||||
|
||||
@@ -37,6 +37,7 @@ ifeq ($(GRIFFIN_BUILD), 1)
|
||||
-DHAVE_RJPEG \
|
||||
-DHAVE_RBMP \
|
||||
-DHAVE_RTGA \
|
||||
-DHAVE_RGIF \
|
||||
-DHAVE_CC_RESAMPLER \
|
||||
-DHAVE_CORE_INFO_CACHE
|
||||
ifeq ($(DEBUG), 1)
|
||||
@@ -58,6 +59,7 @@ else
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_7ZIP := 1
|
||||
HAVE_RZSTD := 1
|
||||
ifeq ($(HAVE_VITAGLES), 1)
|
||||
|
||||
@@ -148,6 +148,7 @@ HAVE_RDDS := 1
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RGIF := 1
|
||||
HAVE_RMODTRACKER := 1
|
||||
HAVE_OVERLAY := 1
|
||||
HAVE_7ZIP := 1
|
||||
|
||||
+2
-1
@@ -122,7 +122,7 @@ endif
|
||||
DEFINES += -DHAVE_RWAV
|
||||
DEFINES += -DHAVE_GRIFFIN=1 -DHAVE_MENU -DHAVE_GFX_WIDGETS -DHAVE_CONFIGFILE -DHAVE_PATCH -DHAVE_CHEATS -DHAVE_RGUI -DHAVE_LIBRETRODB
|
||||
DEFINES += -DHAVE_REWIND
|
||||
DEFINES += -DHAVE_RPNG -DHAVE_RWEBP -DHAVE_RDDS -DHAVE_RJPEG -DHAVE_RBMP -DHAVE_RTGA -DHAVE_CC_RESAMPLER
|
||||
DEFINES += -DHAVE_RPNG -DHAVE_RWEBP -DHAVE_RDDS -DHAVE_RJPEG -DHAVE_RBMP -DHAVE_RTGA -DHAVE_RGIF -DHAVE_CC_RESAMPLER
|
||||
DEFINES += -DHAVE_RSLC -DHAVE_SLANG
|
||||
DEFINES += -DHAVE_RVORBIS -DHAVE_LANGEXTRA -DHAVE_LIBRETRODB -DHAVE_NETWORKING -DHAVE_IFINFO -DHAVE_NETPLAYDISCOVERY
|
||||
DEFINES += -DHAVE_XMB -DHAVE_MATERIALUI
|
||||
@@ -139,6 +139,7 @@ endif
|
||||
HAVE_MENU_COMMON = 1
|
||||
HAVE_GFX_WIDGETS = 1
|
||||
HAVE_RTGA = 1
|
||||
HAVE_RGIF = 1
|
||||
HAVE_RPNG = 1
|
||||
HAVE_RWEBP = 1
|
||||
HAVE_RDDS = 1
|
||||
|
||||
@@ -3194,22 +3194,15 @@ static uint8_t *command_memory_get_pointer(
|
||||
|
||||
/* What RUN_UNTIL, a PRESS that waits after it and a STEPS list wait on:
|
||||
* the byte at @lo, or with @wide the value @hi * 256 + @lo, from two
|
||||
* bytes that need not be adjacent. With @diff, that less the value at
|
||||
* @sub_lo, or @sub_hi:@sub_lo: how far apart two things are, signed. */
|
||||
* bytes that need not be adjacent. */
|
||||
struct command_cond
|
||||
{
|
||||
unsigned hi;
|
||||
unsigned lo;
|
||||
unsigned sub_hi;
|
||||
unsigned sub_lo;
|
||||
unsigned mask; /* "& <mask>": the bits compared; 0 for all */
|
||||
int want;
|
||||
unsigned want;
|
||||
uint8_t op; /* enum cmd_until_op */
|
||||
bool wide;
|
||||
bool sub_wide;
|
||||
bool diff;
|
||||
/* a stop: fires only on a frame it holds after one it did not */
|
||||
bool edge;
|
||||
};
|
||||
|
||||
/* "<address>[:<address>]" into @c, @end after it: 0, or 1 where the
|
||||
@@ -3232,34 +3225,20 @@ static int command_cond_address(const char *s, struct command_cond *c,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The byte at @lo, or with @wide the value @hi * 256 + @lo. */
|
||||
static bool command_cond_value(unsigned hi, unsigned lo, bool wide,
|
||||
static bool command_cond_read(const struct command_cond *c,
|
||||
unsigned *out, char *why, size_t why_len)
|
||||
{
|
||||
unsigned avail = 0;
|
||||
const rarch_system_info_t *sys = &runloop_state_get_ptr()->system;
|
||||
const uint8_t *l = command_memory_get_pointer(sys, lo, &avail, 0,
|
||||
const uint8_t *lo = command_memory_get_pointer(sys, c->lo, &avail, 0,
|
||||
why, why_len);
|
||||
const uint8_t *h = NULL;
|
||||
if (!l || (wide && !(h = command_memory_get_pointer(sys, hi,
|
||||
const uint8_t *hi = NULL;
|
||||
if (!lo || (c->wide && !(hi = command_memory_get_pointer(sys, c->hi,
|
||||
&avail, 0, why, why_len))))
|
||||
return false;
|
||||
*out = h ? ((unsigned)*h << 8) | *l : *l;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool command_cond_read(const struct command_cond *c,
|
||||
int *out, char *why, size_t why_len)
|
||||
{
|
||||
unsigned a = 0;
|
||||
unsigned b = 0;
|
||||
if ( !command_cond_value(c->hi, c->lo, c->wide, &a, why, why_len)
|
||||
|| (c->diff && !command_cond_value(c->sub_hi, c->sub_lo,
|
||||
c->sub_wide, &b, why, why_len)))
|
||||
return false;
|
||||
*out = hi ? ((unsigned)*hi << 8) | *lo : *lo;
|
||||
if (c->mask)
|
||||
a &= c->mask;
|
||||
*out = (int)a - (int)b;
|
||||
*out &= c->mask;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3312,9 +3291,9 @@ static size_t command_watch_lines(char *s, size_t len)
|
||||
i++)
|
||||
{
|
||||
const struct command_watch_entry *e = &command_watches[i];
|
||||
int now = 0;
|
||||
unsigned now = 0;
|
||||
if (command_cond_read(&e->where, &now, reason, sizeof(reason)))
|
||||
snprintf(value, sizeof(value), "%d", now);
|
||||
snprintf(value, sizeof(value), "%u", now);
|
||||
else
|
||||
strlcpy(value, "none", sizeof(value));
|
||||
if (e->where.wide)
|
||||
@@ -3359,17 +3338,15 @@ static bool command_cond_number(const char *s, char **end,
|
||||
return errno != ERANGE;
|
||||
}
|
||||
|
||||
/* "<address>[:<address>] [- <address>[:<address>]] <condition>
|
||||
* <value>", @end after the value; false with what was wrong in @why. */
|
||||
/* "<address>[:<address>] <condition> <value>", @end after the value;
|
||||
* false with what was wrong in @why. */
|
||||
static bool command_cond_parse(const char *s, struct command_cond *c,
|
||||
char **end, char *why, size_t why_len)
|
||||
{
|
||||
unsigned i;
|
||||
unsigned long num;
|
||||
unsigned long max;
|
||||
const char *sp;
|
||||
int bad;
|
||||
bool neg = false;
|
||||
|
||||
memset(c, 0, sizeof(*c));
|
||||
if ((bad = command_cond_address(s, c, end)))
|
||||
@@ -3382,32 +3359,6 @@ static bool command_cond_parse(const char *s, struct command_cond *c,
|
||||
}
|
||||
while (**end == ' ')
|
||||
(*end)++;
|
||||
/* "485 - 47e": the second's value taken from the first's, signed,
|
||||
* so how far an enemy is from the player is one condition */
|
||||
if (**end == '-')
|
||||
{
|
||||
struct command_cond sub;
|
||||
memset(&sub, 0, sizeof(sub));
|
||||
if (command_cond_address(*end + 1, &sub, end))
|
||||
{
|
||||
strlcpy(why, "a hexadecimal address, or two as high:low, after "
|
||||
"the \"-\"", why_len);
|
||||
return false;
|
||||
}
|
||||
c->sub_hi = sub.hi;
|
||||
c->sub_lo = sub.lo;
|
||||
c->sub_wide = sub.wide;
|
||||
c->diff = true;
|
||||
while (**end == ' ')
|
||||
(*end)++;
|
||||
}
|
||||
max = (c->wide || c->sub_wide) ? 65535UL : 255UL;
|
||||
if (**end == '&' && c->diff)
|
||||
{
|
||||
strlcpy(why, "a mask compares one address's bits, not a difference",
|
||||
why_len);
|
||||
return false;
|
||||
}
|
||||
/* "& 0x40" or "& 64": only those bits, as a game keeps its flags */
|
||||
if (**end == '&')
|
||||
{
|
||||
@@ -3446,29 +3397,17 @@ static bool command_cond_parse(const char *s, struct command_cond *c,
|
||||
}
|
||||
while (*sp == ' ')
|
||||
sp++;
|
||||
/* a difference may be below zero: "<= -16" */
|
||||
if (c->diff && *sp == '-')
|
||||
{
|
||||
neg = true;
|
||||
sp++;
|
||||
}
|
||||
/* "== 112" or "== 0x70", as a mask is given */
|
||||
if (!command_cond_number(sp, end, &num) || num > max)
|
||||
if ( !command_cond_number(sp, end, &num)
|
||||
|| num > (c->wide ? 65535UL : 255UL))
|
||||
{
|
||||
if (c->diff)
|
||||
strlcpy(why, max > 255UL
|
||||
? "a value from -65535 to 65535, which is what two values "
|
||||
"of two bytes differ by"
|
||||
: "a value from -255 to 255, which is what two bytes "
|
||||
"differ by", why_len);
|
||||
else
|
||||
strlcpy(why, c->wide
|
||||
? "a value from 0 to 65535 (0xFFFF), which is what two bytes hold"
|
||||
: "a value from 0 to 255 (0xFF), which is what a byte holds",
|
||||
why_len);
|
||||
strlcpy(why, c->wide
|
||||
? "a value from 0 to 65535 (0xFFFF), which is what two bytes hold"
|
||||
: "a value from 0 to 255 (0xFF), which is what a byte holds",
|
||||
why_len);
|
||||
return false;
|
||||
}
|
||||
c->want = neg ? -(int)num : (int)num;
|
||||
c->want = (unsigned)num;
|
||||
c->op = (uint8_t)i;
|
||||
return true;
|
||||
}
|
||||
@@ -3476,7 +3415,7 @@ static bool command_cond_parse(const char *s, struct command_cond *c,
|
||||
/* What @c's memory holds now, looked up afresh as WATCH's bytes are,
|
||||
* since a core may move its memory while the content runs; false where
|
||||
* nothing maps it, with the reason as line clients read it in @why. */
|
||||
static bool command_cond_met(const struct command_cond *c, int now)
|
||||
static bool command_cond_met(const struct command_cond *c, unsigned now)
|
||||
{
|
||||
switch (c->op)
|
||||
{
|
||||
@@ -3498,7 +3437,7 @@ static bool command_cond_mapped(command_t *cmd, const char *name,
|
||||
const struct command_cond *c)
|
||||
{
|
||||
char reason[128];
|
||||
int now = 0;
|
||||
unsigned now = 0;
|
||||
if (command_cond_read(c, &now, reason, sizeof(reason)))
|
||||
return true;
|
||||
command_reply_error(cmd, name, "%s", command_memory_why(reason));
|
||||
@@ -3566,15 +3505,12 @@ static struct
|
||||
unsigned left; /* frames left in a RUN or PRESS step */
|
||||
unsigned budget; /* frames left for the whole job */
|
||||
unsigned ran; /* frames run so far */
|
||||
int value; /* what the last condition read held */
|
||||
unsigned value; /* what the last condition read held */
|
||||
unsigned port; /* the player whose buttons are held */
|
||||
uint16_t held; /* the buttons the job let go of, by bit */
|
||||
uint8_t answer; /* enum cmd_job_answer */
|
||||
unsigned nstops;
|
||||
unsigned stopped; /* which stop held, from 1 */
|
||||
/* whether each stop held on the frame before: one that becomes
|
||||
* fires only where it did not */
|
||||
bool stop_was[COMMAND_STOPS_MAX];
|
||||
/* a frame with the buttons let go between two presses of the same
|
||||
* one, so the game sees two taps rather than one long press */
|
||||
bool gap;
|
||||
@@ -3696,7 +3632,7 @@ static void command_run_frames_end(const char *how)
|
||||
case CMD_ANSWER_UNTIL:
|
||||
snprintf(first, sizeof(first), "%s %u\n", how,
|
||||
command_run_frames_st.ran);
|
||||
snprintf(last, sizeof(last), "VALUE %d\n",
|
||||
snprintf(last, sizeof(last), "VALUE %u\n",
|
||||
command_run_frames_st.value);
|
||||
command_run_frames_answer(first, last, false);
|
||||
break;
|
||||
@@ -3841,14 +3777,8 @@ static void command_run_frames_enter(bool safe)
|
||||
}
|
||||
else
|
||||
{
|
||||
int now = 0;
|
||||
command_run_frames_st.stops[s->idx - 1] = s->cond;
|
||||
command_run_frames_st.nstops = s->idx;
|
||||
/* what it holds as it takes over, so one already true when
|
||||
* the list starts waits for it to stop holding first */
|
||||
command_run_frames_st.stop_was[s->idx - 1] =
|
||||
command_cond_read(&s->cond, &now, why, sizeof(why))
|
||||
&& command_cond_met(&s->cond, now);
|
||||
}
|
||||
command_run_frames_st.cur++;
|
||||
continue;
|
||||
@@ -3997,7 +3927,7 @@ void command_run_frames_between(void)
|
||||
void command_run_frames_tick(void)
|
||||
{
|
||||
char why[128];
|
||||
int now = 0;
|
||||
unsigned now = 0;
|
||||
unsigned i;
|
||||
struct command_step *s;
|
||||
|
||||
@@ -4018,17 +3948,10 @@ void command_run_frames_tick(void)
|
||||
}
|
||||
if (command_cond_met(&command_run_frames_st.stops[i], now))
|
||||
{
|
||||
bool was = command_run_frames_st.stop_was[i];
|
||||
command_run_frames_st.stop_was[i] = true;
|
||||
if (!command_run_frames_st.stops[i].edge || !was)
|
||||
{
|
||||
command_run_frames_st.stopped = i + 1;
|
||||
command_run_frames_end("STOPPED");
|
||||
return;
|
||||
}
|
||||
command_run_frames_st.stopped = i + 1;
|
||||
command_run_frames_end("STOPPED");
|
||||
return;
|
||||
}
|
||||
else
|
||||
command_run_frames_st.stop_was[i] = false;
|
||||
}
|
||||
s = &command_run_frames_st.steps[command_run_frames_st.cur];
|
||||
if (command_run_frames_st.gap)
|
||||
@@ -4361,14 +4284,12 @@ bool command_set_input(command_t *cmd, const char *arg)
|
||||
* With nothing, what is watched now. Each label and address watches the
|
||||
* byte there under that label, carried by RUN_FRAMES, PRESS and
|
||||
* RUN_UNTIL; a label and "off" stops watching it. Every pair is taken
|
||||
* or none is, so a refusal leaves the list as it was. The limit is on
|
||||
* the list they leave, so an off later in the request makes room.
|
||||
* or none is, so a refusal leaves the list as it was.
|
||||
*
|
||||
* Answers with the list either way, so a client sees what it has. */
|
||||
bool command_watch(command_t *cmd, const char *arg)
|
||||
{
|
||||
/* room past the limit for those an off further on takes back out */
|
||||
struct command_watch_entry next[2 * COMMAND_WATCH_MAX];
|
||||
struct command_watch_entry next[COMMAND_WATCH_MAX];
|
||||
char reply[64 + COMMAND_WATCH_MAX * COMMAND_WATCH_LINE];
|
||||
char label[sizeof(next[0].label)];
|
||||
char reason[128];
|
||||
@@ -4378,7 +4299,7 @@ bool command_watch(command_t *cmd, const char *arg)
|
||||
unsigned i;
|
||||
const char *sp;
|
||||
|
||||
memcpy(next, command_watches, sizeof(command_watches));
|
||||
memcpy(next, command_watches, sizeof(next));
|
||||
while (arg && *arg)
|
||||
{
|
||||
while (*arg == ' ')
|
||||
@@ -4425,7 +4346,7 @@ bool command_watch(command_t *cmd, const char *arg)
|
||||
else
|
||||
{
|
||||
struct command_cond where;
|
||||
int now = 0;
|
||||
unsigned now = 0;
|
||||
char *end = NULL;
|
||||
memset(&where, 0, sizeof(where));
|
||||
/* high:low, two bytes as one value, as conditions take them */
|
||||
@@ -4437,7 +4358,7 @@ bool command_watch(command_t *cmd, const char *arg)
|
||||
return false;
|
||||
}
|
||||
/* only a label that is not watched yet needs room */
|
||||
if (i == count && count >= ARRAY_SIZE(next))
|
||||
if (i == count && count >= COMMAND_WATCH_MAX)
|
||||
{
|
||||
command_reply_error(cmd, "WATCH", "at most %u bytes watched "
|
||||
"at once", (unsigned)COMMAND_WATCH_MAX);
|
||||
@@ -4457,13 +4378,7 @@ bool command_watch(command_t *cmd, const char *arg)
|
||||
strlcpy(next[i].label, label, sizeof(next[i].label));
|
||||
}
|
||||
}
|
||||
if (count > COMMAND_WATCH_MAX)
|
||||
{
|
||||
command_reply_error(cmd, "WATCH", "at most %u bytes watched "
|
||||
"at once", (unsigned)COMMAND_WATCH_MAX);
|
||||
return false;
|
||||
}
|
||||
memcpy(command_watches, next, sizeof(command_watches));
|
||||
memcpy(command_watches, next, sizeof(next));
|
||||
command_watch_count = count;
|
||||
|
||||
_len = command_watch_lines(reply, sizeof(reply));
|
||||
@@ -4473,12 +4388,11 @@ bool command_watch(command_t *cmd, const char *arg)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* RUN_UNTIL <address> [- <address>] <condition> <value> [max frames]
|
||||
/* RUN_UNTIL <address> <condition> <value> [max frames]
|
||||
*
|
||||
* Runs the content until the byte at a system address meets what was
|
||||
* asked, then answers with how many frames that took and the value it
|
||||
* found. With "- <address>" it is that byte less the other, signed:
|
||||
* how far apart two things are. Where a client would otherwise run a few frames, read the
|
||||
* found. Where a client would otherwise run a few frames, read the
|
||||
* byte, and go round again - which is where a field run spent most of
|
||||
* its calls - this is one request.
|
||||
*
|
||||
@@ -4690,9 +4604,7 @@ static void command_steps_report(command_t *cmd, long idle,
|
||||
* <player> <controls> <frames>" and "until <condition>", in order, then
|
||||
* answers with how it ended and what WATCH watches. A "stop
|
||||
* <condition>" entry ends the list after any frame where it holds, so a
|
||||
* death does not play out the rest; "stop becomes <condition>" only
|
||||
* after one where it starts to, so one already true does not end the
|
||||
* list at its first frame. "checkpoint" keeps the state
|
||||
* death does not play out the rest, and "checkpoint" keeps the state
|
||||
* STATE rollback returns to. The whole list runs no longer than a wait
|
||||
* does; past that it answers WAITING and the step it was on, and the
|
||||
* next request carries on from there.
|
||||
@@ -4750,10 +4662,9 @@ bool command_steps(command_t *cmd, const char *arg)
|
||||
for (entry = list; entry; entry = next)
|
||||
{
|
||||
struct command_step *s;
|
||||
struct command_cond *c = NULL;
|
||||
char *end = NULL;
|
||||
struct command_cond *c = NULL;
|
||||
char *end = NULL;
|
||||
size_t n;
|
||||
bool edge = false;
|
||||
if ((next = strchr(entry, ';')))
|
||||
*next++ = '\0';
|
||||
while (*entry == ' ')
|
||||
@@ -4775,13 +4686,6 @@ bool command_steps(command_t *cmd, const char *arg)
|
||||
}
|
||||
c = &stops[nstops++];
|
||||
entry += 5;
|
||||
/* "stop becomes <condition>": only once it starts to hold, so
|
||||
* one true as the list starts does not end it at once */
|
||||
if (!strncmp(entry, "becomes ", 8))
|
||||
{
|
||||
entry += 8;
|
||||
edge = true;
|
||||
}
|
||||
}
|
||||
else if (count >= COMMAND_STEPS_MAX)
|
||||
{
|
||||
@@ -4881,7 +4785,6 @@ bool command_steps(command_t *cmd, const char *arg)
|
||||
command_reply_error(cmd, "STEPS", "%s", why);
|
||||
return false;
|
||||
}
|
||||
c->edge = edge;
|
||||
while (*end == ' ')
|
||||
end++;
|
||||
if (*end)
|
||||
@@ -6188,23 +6091,11 @@ bool command_get_status(command_t *cmd, const char* arg)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* READ_CORE_MEMORY <address> <bytes> [<address> <bytes>]...
|
||||
*
|
||||
* Each range a line of its own, in the order asked, so the arrays a
|
||||
* step needs - positions, health - are one request rather than one
|
||||
* apiece. Where nothing maps one, that line alone is the answer. */
|
||||
#define COMMAND_READ_RANGES_MAX 16
|
||||
|
||||
bool command_read_memory(command_t *cmd, const char *arg)
|
||||
{
|
||||
char* reply = NULL;
|
||||
char* reply_at = NULL;
|
||||
const uint8_t* data = NULL;
|
||||
unsigned int addrs[COMMAND_READ_RANGES_MAX];
|
||||
unsigned int counts[COMMAND_READ_RANGES_MAX];
|
||||
unsigned int nranges = 0;
|
||||
unsigned int total = 0;
|
||||
unsigned int r;
|
||||
unsigned int nbytes = 0;
|
||||
unsigned int alloc_size = 0;
|
||||
unsigned int address = -1;
|
||||
@@ -6213,48 +6104,27 @@ bool command_read_memory(command_t *cmd, const char *arg)
|
||||
runloop_state_t *runloop_st = runloop_state_get_ptr();
|
||||
const rarch_system_info_t* sys_info= &runloop_st->system;
|
||||
|
||||
while (arg && *arg)
|
||||
{
|
||||
char *end = NULL;
|
||||
unsigned long num;
|
||||
if (nranges >= COMMAND_READ_RANGES_MAX)
|
||||
{
|
||||
command_reply_error(cmd, "READ_CORE_MEMORY",
|
||||
"at most %u ranges", (unsigned)COMMAND_READ_RANGES_MAX);
|
||||
return false;
|
||||
}
|
||||
if (!command_parse_address(arg, &address, &end) || *end != ' ')
|
||||
{
|
||||
command_reply_error(cmd, "READ_CORE_MEMORY",
|
||||
"a hexadecimal address, a space and a byte count");
|
||||
return false;
|
||||
}
|
||||
num = strtoul(end + 1, &end, 10);
|
||||
num = strtoul(end + 1, NULL, 10);
|
||||
nbytes = num <= COMMAND_READ_NBYTES_MAX ? (unsigned)num : 0;
|
||||
if (nbytes == 0 || total + nbytes > COMMAND_READ_NBYTES_MAX)
|
||||
if (nbytes == 0)
|
||||
{
|
||||
command_reply_error(cmd, "READ_CORE_MEMORY", nranges
|
||||
? "byte counts from 1, %u in all"
|
||||
: "a byte count from 1 to %u", COMMAND_READ_NBYTES_MAX);
|
||||
command_reply_error(cmd, "READ_CORE_MEMORY",
|
||||
"a byte count from 1 to %u", COMMAND_READ_NBYTES_MAX);
|
||||
return false;
|
||||
}
|
||||
addrs[nranges] = address;
|
||||
counts[nranges] = nbytes;
|
||||
nranges++;
|
||||
total += nbytes;
|
||||
/* another address and count, or the end of the line */
|
||||
for (arg = end; *arg && (unsigned char)*arg <= ' '; arg++)
|
||||
;
|
||||
}
|
||||
if (!nranges)
|
||||
{
|
||||
command_reply_error(cmd, "READ_CORE_MEMORY",
|
||||
"a hexadecimal address, a space and a byte count");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Ensure large enough to return all requested bytes or an error message */
|
||||
alloc_size = 96 * nranges + total * 3;
|
||||
alloc_size = 96 + nbytes * 3;
|
||||
reply = (char*)malloc(alloc_size);
|
||||
|
||||
if (!reply)
|
||||
@@ -6262,53 +6132,43 @@ bool command_read_memory(command_t *cmd, const char *arg)
|
||||
command_reply_error(cmd, "READ_CORE_MEMORY", "out of memory");
|
||||
return false;
|
||||
}
|
||||
|
||||
reply_at = reply + snprintf(reply, alloc_size - 1, "READ_CORE_MEMORY %x", address);
|
||||
|
||||
for (r = 0; r < nranges; r++)
|
||||
if ((data = command_memory_get_pointer(
|
||||
sys_info, address, &max_bytes,
|
||||
0, reply_at, alloc_size - strlen(reply))))
|
||||
{
|
||||
char *line = reply + _len;
|
||||
address = addrs[r];
|
||||
nbytes = counts[r];
|
||||
reply_at = line + snprintf(line, alloc_size - _len - 1,
|
||||
"READ_CORE_MEMORY %x", address);
|
||||
|
||||
if ((data = command_memory_get_pointer(
|
||||
sys_info, address, &max_bytes,
|
||||
0, reply_at, alloc_size - (size_t)(reply_at - reply))))
|
||||
char why[128];
|
||||
char *end = reply_at;
|
||||
unsigned int read = 0;
|
||||
/* on into the region that follows, as a core that splits its RAM
|
||||
* maps it; where nothing maps the rest, how much was read */
|
||||
while (data)
|
||||
{
|
||||
char why[128];
|
||||
char *end = reply_at;
|
||||
unsigned int read = 0;
|
||||
/* on into the region that follows, as a core that splits its RAM
|
||||
* maps it; where nothing maps the rest, how much was read */
|
||||
while (data)
|
||||
{
|
||||
unsigned int n = nbytes - read < max_bytes
|
||||
? nbytes - read : max_bytes;
|
||||
if (!n)
|
||||
break;
|
||||
end = command_hex_bytes(end, data, n);
|
||||
read += n;
|
||||
if (read >= nbytes)
|
||||
break;
|
||||
data = command_memory_get_pointer(sys_info, address + read,
|
||||
&max_bytes, 0, why, sizeof(why));
|
||||
}
|
||||
*end++ = '\n';
|
||||
if (read < nbytes)
|
||||
end += snprintf(end, alloc_size - (size_t)(end - reply),
|
||||
"TRUNCATED %u\n", read);
|
||||
_len = (size_t)(end - reply);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* command_memory_get_pointer() has put the reason in the reply,
|
||||
* in the form line clients have always read: this range's line
|
||||
* alone */
|
||||
_len = strlen(line);
|
||||
memmove(reply, line, _len + 1);
|
||||
cmd->error = true;
|
||||
break;
|
||||
unsigned int n = nbytes - read < max_bytes
|
||||
? nbytes - read : max_bytes;
|
||||
if (!n)
|
||||
break;
|
||||
end = command_hex_bytes(end, data, n);
|
||||
read += n;
|
||||
if (read >= nbytes)
|
||||
break;
|
||||
data = command_memory_get_pointer(sys_info, address + read,
|
||||
&max_bytes, 0, why, sizeof(why));
|
||||
}
|
||||
*end++ = '\n';
|
||||
if (read < nbytes)
|
||||
end += snprintf(end, alloc_size - (size_t)(end - reply),
|
||||
"TRUNCATED %u\n", read);
|
||||
_len = (size_t)(end - reply);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* command_memory_get_pointer() has put the reason in the reply,
|
||||
* in the form line clients have always read */
|
||||
_len = strlen(reply);
|
||||
cmd->error = true;
|
||||
}
|
||||
|
||||
cmd->replier(cmd, reply, _len);
|
||||
|
||||
@@ -603,11 +603,6 @@ struct cmd_rows
|
||||
/* The reply may carry a "PREVIOUS ..." line: how a queued STEPS no
|
||||
* reply has told of ended. */
|
||||
bool previous;
|
||||
/* Set, for a record: the field a reply of several such lines - one
|
||||
* for each range READ_CORE_MEMORY was asked - gives them all in, in
|
||||
* order, each with the trailers after it. The record's own fields
|
||||
* are the first. */
|
||||
const char *more;
|
||||
};
|
||||
|
||||
/* A command with its own handler; @arg_desc names the argument, or
|
||||
@@ -881,19 +876,18 @@ static const struct cmd_param cmd_params_watch[] = {
|
||||
{ "watches", "Label and hexadecimal address pairs, as GET_MEMORY_MAP "
|
||||
"gives them: \"x 86 mode 770\"; \"x 6d:86\" reads two bytes as one "
|
||||
"value. A label watched again is replaced; off as its address stops "
|
||||
"it. 16 at most, counted once the offs are taken.",
|
||||
"it. 16 at most.",
|
||||
CMD_PARAM_STRING, false, NULL, NULL, NULL },
|
||||
{ NULL }
|
||||
};
|
||||
static const struct cmd_param cmd_params_until[] = {
|
||||
{ "address", "Hexadecimal, as GET_MEMORY_MAP gives it; high:low "
|
||||
"(6d:86) reads two bytes as one value; & 0x40 after it compares "
|
||||
"only those bits; 485 - 47e compares the first less the second, "
|
||||
"signed.", CMD_PARAM_STRING, true, NULL, NULL, NULL },
|
||||
"only those bits.", CMD_PARAM_STRING, true, NULL, NULL, NULL },
|
||||
{ "condition", "How it compares with the value.",
|
||||
CMD_PARAM_STRING, true, NULL, "==|!=|<|>|<=|>=", NULL },
|
||||
{ "value", "0 to 255, or 65535 for high:low; a difference from "
|
||||
"-255, or -65535.", CMD_PARAM_INT, true, NULL, NULL, NULL },
|
||||
{ "value", "0 to 255, or 65535 for high:low.",
|
||||
CMD_PARAM_INT, true, NULL, NULL, NULL },
|
||||
{ "frames", "Frames to wait at most; a request's nine seconds when "
|
||||
"left out.", CMD_PARAM_INT, false, NULL, NULL, NULL },
|
||||
{ NULL }
|
||||
@@ -916,8 +910,7 @@ static const struct cmd_param cmd_params_steps[] = {
|
||||
"<frames>, until <condition> as RUN_UNTIL's, checkpoint, and up to "
|
||||
"four stop <condition> that end it: \"until 86 >= 200; "
|
||||
"press 1 a 20; until 1d == 0; stop e == 11\". 32 at most. A press "
|
||||
"after a press lets go for a frame first. stop becomes <condition> "
|
||||
"ends it only once it starts to hold, not where it already does.",
|
||||
"after a press lets go for a frame first.",
|
||||
CMD_PARAM_STRING, true, NULL, NULL, NULL },
|
||||
{ "queue", "on: answer now, run after those queued, pause when all "
|
||||
"have (idle: ms since). After a queued list ends the next reply says "
|
||||
@@ -979,16 +972,6 @@ static const struct cmd_param cmd_params_read[] = {
|
||||
CMD_PARAM_INT, true, NULL, NULL, NULL },
|
||||
{ NULL }
|
||||
};
|
||||
static const struct cmd_param cmd_params_read_memory[] = {
|
||||
{ "address", "The address to read from, in hexadecimal.",
|
||||
CMD_PARAM_STRING, true, NULL, NULL, NULL },
|
||||
{ "bytes", "How many.",
|
||||
CMD_PARAM_INT, true, NULL, NULL, NULL },
|
||||
{ "more", "Further ranges in the same request, as address and byte "
|
||||
"count pairs: \"4a2 18 568 18\". Each answers a line, given as "
|
||||
"ranges.", CMD_PARAM_STRING, false, NULL, NULL, NULL },
|
||||
{ NULL }
|
||||
};
|
||||
static const struct cmd_param cmd_params_write[] = {
|
||||
{ "address", "The address to write to, in hexadecimal.",
|
||||
CMD_PARAM_STRING, true, NULL, NULL, NULL },
|
||||
@@ -1229,7 +1212,7 @@ static const struct cmd_trailer cmd_trailers_read[] = {
|
||||
{ "TRUNCATED", "truncated", "read", "Bytes read, short of those asked." },
|
||||
{ NULL }
|
||||
};
|
||||
static const struct cmd_rows cmd_rows_read = { cmd_cols_read, cmd_trailers_read, 0, 0, " ", false, "ranges" };
|
||||
static const struct cmd_rows cmd_rows_read = { cmd_cols_read, cmd_trailers_read, 0, 0, " " };
|
||||
/* "MENU Main Menu", the entries, then which is selected of how many */
|
||||
static const struct cmd_column cmd_cols_menu[] = {
|
||||
{ "index", NULL },
|
||||
@@ -1275,7 +1258,7 @@ static const struct cmd_action_map action_map[] = {
|
||||
{ "RUN_FRAMES", command_run_frames, "<count>", "Pause and advance the content some frames; it finishes paused. One request runs nine seconds of frames at most (540 at 60fps), as PRESS, RUN_UNTIL and STEPS do.", 0, cmd_params_run_frames, &cmd_rows_frames },
|
||||
{ "SET_INPUT", command_set_input, "<player> <control>[+<control>...] <value>", "Hold RetroPad buttons or stick axes for the core, over the player's own input, until set again or released. The core is given exactly what is held: opposing directions are not cleaned and the D-Pad is not turned with the picture. Under netplay the player is this machine's, and what is held is sent like a press. Answers with the player, the buttons held and the axes held, none for either where nothing is.", CMD_INFO_NO_TOOL, cmd_params_input, &cmd_rows_input },
|
||||
{ "PRESS", command_press, "<player> <control>[+<control>...] <frames> [until <condition>]", "Press buttons for some frames and let go; with until, run on until a condition holds. What INPUT holds stays held. Answers with what WATCH watches; finishes paused.", 0, cmd_params_press, &cmd_rows_press },
|
||||
{ "RUN_UNTIL", command_run_until, "<address>[:<address>] [- <address>[:<address>]] <condition> <value> [max frames]", "Run until memory meets a condition. Answers met or waiting (ask again) with the frames run, what WATCH watches and the value.", 0, cmd_params_until, &cmd_rows_until },
|
||||
{ "RUN_UNTIL", command_run_until, "<address>[:<address>] <condition> <value> [max frames]", "Run until memory meets a condition. Answers met or waiting (ask again) with the frames run, what WATCH watches and the value.", 0, cmd_params_until, &cmd_rows_until },
|
||||
{ "STEPS", command_steps, "<step>[; <step>...][|on]", "Play a stretch in one request, or queued to play on unpaused. Answers with how it ended and what WATCH watches.", 0, cmd_params_steps, &cmd_rows_steps },
|
||||
{ "WATCH", command_watch, "[<label> <address|off>]...", "Watch bytes under labels: every step answers with them, so needs no read after. With none, answers each value, in decimal. Cleared when content closes.", 0, cmd_params_watch, &cmd_rows_watch },
|
||||
{ "RELEASE_INPUT", command_release_input, "[player]", "Let go of everything SET_INPUT holds, for one player, or every player when given none. Answers as GET_INPUT does, for that player or the first.", CMD_INFO_NO_TOOL, cmd_params_input_release, &cmd_rows_input },
|
||||
@@ -1292,7 +1275,7 @@ static const struct cmd_action_map action_map[] = {
|
||||
{ "READ_CORE_RAM", command_read_ram, "<address> <number of bytes>", "Read bytes from core memory at an achievement address (needs a game with achievements).", CMD_INFO_READ_ONLY | CMD_INFO_NO_TOOL, cmd_params_read },
|
||||
{ "WRITE_CORE_RAM", command_write_ram, "<address> <byte1> <byte2> ...", "Write bytes to core memory at an achievement address (needs a game with achievements).", CMD_INFO_DESTRUCTIVE | CMD_INFO_NO_TOOL, cmd_params_write },
|
||||
#endif
|
||||
{ "READ_CORE_MEMORY", command_read_memory, "<address> <number of bytes> [<address> <number of bytes>]...", "Read bytes from core memory at a system address. Answers with the address and the bytes, in hexadecimal; a line for each range asked.", CMD_INFO_READ_ONLY, cmd_params_read_memory, &cmd_rows_read },
|
||||
{ "READ_CORE_MEMORY", command_read_memory, "<address> <number of bytes>", "Read bytes from core memory at a system address. Answers with the address and the bytes, in hexadecimal.", CMD_INFO_READ_ONLY, cmd_params_read, &cmd_rows_read },
|
||||
{ "GET_MEMORY_MAP", command_get_memory_map, "No argument", "The core's memory map: start, length, select and disconnect in hexadecimal, as READ_CORE_MEMORY takes them. A core with none lists the RAM it hands over, in space core: system RAM from 0, save RAM after.", CMD_INFO_READ_ONLY, NULL, &cmd_rows_memory },
|
||||
{ "WRITE_CORE_MEMORY",command_write_memory, "<address> <byte1> <byte2> ...", "Write bytes to core memory at a system address. Answers with the address and how many bytes were written.", CMD_INFO_DESTRUCTIVE, cmd_params_write },
|
||||
|
||||
|
||||
@@ -418,6 +418,12 @@
|
||||
#define SUPPORTS_RTGA false
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_RGIF
|
||||
#define SUPPORTS_RGIF true
|
||||
#else
|
||||
#define SUPPORTS_RGIF false
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_RWEBP
|
||||
#define SUPPORTS_RWEBP true
|
||||
#else
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
#include <streams/file_stream.h>
|
||||
|
||||
#if defined(HAVE_RPNG) || defined(HAVE_RJPEG) || defined(HAVE_RTGA) || defined(HAVE_RBMP) || defined(HAVE_RWEBP) || defined(HAVE_RDDS)
|
||||
#if defined(HAVE_RPNG) || defined(HAVE_RJPEG) || defined(HAVE_RTGA) || defined(HAVE_RBMP) || defined(HAVE_RWEBP) || defined(HAVE_RDDS) || defined(HAVE_RGIF)
|
||||
#define PREFER_NON_STB_IMAGE
|
||||
#endif
|
||||
|
||||
@@ -87,6 +87,10 @@ static const char image_formats[] =
|
||||
"|tga"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_RGIF
|
||||
"|gif"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_RWEBP
|
||||
"|webp"
|
||||
#endif
|
||||
@@ -95,7 +99,7 @@ static const char image_formats[] =
|
||||
"|dds"
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_RJPEG) && !defined(HAVE_RPNG) && !defined(HAVE_RBMP) && !defined(HAVE_RTGA) && !defined(HAVE_RWEBP) && !defined(HAVE_RDDS)
|
||||
#if !defined(HAVE_RJPEG) && !defined(HAVE_RPNG) && !defined(HAVE_RBMP) && !defined(HAVE_RTGA) && !defined(HAVE_RWEBP) && !defined(HAVE_RDDS) && !defined(HAVE_RGIF)
|
||||
#error "can't build this core with no image formats"
|
||||
#endif
|
||||
;
|
||||
|
||||
+2
-25
@@ -143,15 +143,6 @@ goes on into the next; where nothing maps the rest it stops there and
|
||||
says so, `TRUNCATED 2` after the bytes, given as `truncated: true,
|
||||
read: 2`.
|
||||
|
||||
`READ_CORE_MEMORY 47e 18 4a2 18 568 18` reads several ranges in one
|
||||
request (`more: "4a2 18 568 18"` over MCP), up to sixteen and 16384
|
||||
bytes in all: a line for each, in the order asked, each with its own
|
||||
`TRUNCATED` after it where it was cut short. The record's fields are
|
||||
the first range's, and `ranges` gives every one, the first too, as
|
||||
`{"address", "bytes"}` with `truncated` and `read` where cut short.
|
||||
Where nothing maps one of them, its line alone is the answer, as a
|
||||
single range's is.
|
||||
|
||||
## Results that are rows
|
||||
|
||||
The replies that are lines of tab-separated fields - `OPTION`,
|
||||
@@ -455,9 +446,7 @@ its place in the list when its address changes; with nothing watched
|
||||
it answers `WATCH none`. Values are decimal, 0 to 255 - or to 65535
|
||||
for a `high:low` watch - as `RUN_UNTIL` takes them; addresses are hexadecimal. A label is up to 15 characters,
|
||||
with no tab or newline in it. `x 6d:86` watches two bytes as one value,
|
||||
high:low, as conditions read them. Sixteen at once, counted on the
|
||||
list the request leaves: `WATCH lives 4d e13h off` with sixteen watched
|
||||
adds one and drops one. Each is checked when it is
|
||||
high:low, as conditions read them. Sixteen at once. Each is checked when it is
|
||||
set - an address nothing maps is refused there -
|
||||
and read afresh on every reply, so a core that moves its memory is
|
||||
followed; one that stops mapping a watched address answers `none` for
|
||||
@@ -484,14 +473,6 @@ x 7e0086 52
|
||||
VALUE 5
|
||||
```
|
||||
|
||||
`<address> - <address>` compares the first's value less the second's,
|
||||
signed, each one byte or `high:low`: `485 - 47e <= 32` waits until an
|
||||
enemy at x `485` is no more than 32 to the right of the player at
|
||||
`47e`, walking in until it is in range. The value may be below zero, from -255 to 255, or -65535 to
|
||||
65535 where either side is `high:low`, and `VALUE` answers the
|
||||
difference, `VALUE -12`. Both addresses must be mapped, and a mask is
|
||||
refused on one.
|
||||
|
||||
Where a client would otherwise run a few frames, read the byte and go
|
||||
round again, this is one request. The address is the kind
|
||||
`GET_MEMORY_MAP` gives and `READ_CORE_MEMORY` takes, and one nothing
|
||||
@@ -512,11 +493,7 @@ told it failed.
|
||||
step is `run <frames>`, `press <player> <controls> <frames>`, `until
|
||||
<condition>` or `checkpoint`, in order; up to four `stop <condition>`
|
||||
entries anywhere in the list end it after any frame where one holds - a
|
||||
death, the level's end - so neither plays out the rest. `stop becomes
|
||||
<condition>` ends it only after a frame where the condition starts to
|
||||
hold: one already true when the list starts - `stop becomes 412 == 4`
|
||||
while the player is still reeling - waits for it to stop holding and
|
||||
hold again, rather than ending the list at its first frame. `checkpoint`
|
||||
death, the level's end - so neither plays out the rest. `checkpoint`
|
||||
keeps the state `STATE rollback` returns to, where the list has got to,
|
||||
so a stretch can mark its own retry point:
|
||||
|
||||
|
||||
@@ -3010,45 +3010,7 @@ void dxgi_set_hdr_metadata(
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <formats/rpng.h>
|
||||
|
||||
/* IEEE 754 binary16 -> binary32. Used to decode FP16 scRGB samples. */
|
||||
static INLINE float dxgi_half_to_float(uint16_t h)
|
||||
{
|
||||
uint32_t sign = (uint32_t)(h >> 15) & 0x1u;
|
||||
uint32_t exp = (uint32_t)(h >> 10) & 0x1Fu;
|
||||
uint32_t mant = (uint32_t) h & 0x3FFu;
|
||||
uint32_t f;
|
||||
union { uint32_t u; float f; } u;
|
||||
|
||||
if (exp == 0)
|
||||
{
|
||||
if (mant == 0)
|
||||
f = sign << 31; /* signed zero */
|
||||
else
|
||||
{
|
||||
/* Subnormal: normalize. */
|
||||
while (!(mant & 0x400u))
|
||||
{
|
||||
mant <<= 1;
|
||||
exp -= 1; /* exp starts at 0, goes negative */
|
||||
}
|
||||
exp += 1;
|
||||
mant &= ~0x400u;
|
||||
f = (sign << 31) | ((exp + 127 - 15) << 23) | (mant << 13);
|
||||
}
|
||||
}
|
||||
else if (exp == 0x1F)
|
||||
{
|
||||
/* Inf / NaN: propagate to float32 Inf/NaN with the same mantissa
|
||||
* low bits (clamped away later, so exact bit-pattern doesn't matter). */
|
||||
f = (sign << 31) | (0xFFu << 23) | (mant << 13);
|
||||
}
|
||||
else
|
||||
f = (sign << 31) | ((exp + 127 - 15) << 23) | (mant << 13);
|
||||
|
||||
u.u = f;
|
||||
return u.f;
|
||||
}
|
||||
#include <math/float16.h>
|
||||
|
||||
/* ST.2084 (PQ) EOTF. Input is the non-linear PQ code value in [0,1];
|
||||
* output is linear light normalized so 1.0 == 10000 nits. */
|
||||
@@ -3210,9 +3172,9 @@ bool dxgi_hdr_readback_to_bgr24(
|
||||
const uint16_t* src = (const uint16_t*)src_row + src_x * 4;
|
||||
for (x = 0; x < width; x++)
|
||||
{
|
||||
float r = dxgi_half_to_float(src[4 * x + 0]) * inv_scale;
|
||||
float g = dxgi_half_to_float(src[4 * x + 1]) * inv_scale;
|
||||
float b = dxgi_half_to_float(src[4 * x + 2]) * inv_scale;
|
||||
float r = retro_half_to_float(src[4 * x + 0]) * inv_scale;
|
||||
float g = retro_half_to_float(src[4 * x + 1]) * inv_scale;
|
||||
float b = retro_half_to_float(src[4 * x + 2]) * inv_scale;
|
||||
/* scRGB carries legal negative values for out-of-gamut
|
||||
* colours; clamping in LinearToSRGB handles them. */
|
||||
dst[3 * x + 0] = dxgi_float_to_unorm8(dxgi_linear_to_srgb(b));
|
||||
@@ -3309,9 +3271,9 @@ bool dxgi_hdr_readback_to_rgb16(
|
||||
* scale to absolute nits, PQ-encode into 16 bit. Extended
|
||||
* range (negatives, >10000 nits) clamps -- unavoidable for
|
||||
* a UNORM PNG. */
|
||||
float r = dxgi_half_to_float(src[x * 4 + 0]);
|
||||
float g = dxgi_half_to_float(src[x * 4 + 1]);
|
||||
float b = dxgi_half_to_float(src[x * 4 + 2]);
|
||||
float r = retro_half_to_float(src[x * 4 + 0]);
|
||||
float g = retro_half_to_float(src[x * 4 + 1]);
|
||||
float b = retro_half_to_float(src[x * 4 + 2]);
|
||||
float lvl;
|
||||
float nr = r * (80.0f / 10000.0f);
|
||||
float ng = g * (80.0f / 10000.0f);
|
||||
|
||||
+4
-32
@@ -30,6 +30,7 @@
|
||||
#include "../common/vulkan_video_import.h"
|
||||
#include <retro_inline.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <math/float16.h>
|
||||
#include <queues/mpsc_stack.h>
|
||||
#include <queues/task_queue.h>
|
||||
#include <retro_math.h>
|
||||
@@ -17375,35 +17376,6 @@ static bool vulkan_read_viewport_bgrx(void *data, uint8_t *buffer,
|
||||
}
|
||||
|
||||
#ifdef VULKAN_HDR_SWAPCHAIN
|
||||
/* IEEE-754 half (binary16) -> float, for reading an RGBA16F scRGB backbuffer
|
||||
* on the CPU. Handles normals, subnormals, sign and inf/nan. */
|
||||
static float vulkan_half_to_float(uint16_t h)
|
||||
{
|
||||
uint32_t sign = (uint32_t)(h & 0x8000) << 16;
|
||||
uint32_t exp = (h >> 10) & 0x1F;
|
||||
uint32_t mant = h & 0x3FF;
|
||||
uint32_t f;
|
||||
float out;
|
||||
if (exp == 0)
|
||||
{
|
||||
if (mant == 0)
|
||||
f = sign;
|
||||
else
|
||||
{
|
||||
exp = 127 - 15 + 1;
|
||||
while (!(mant & 0x400)) { mant <<= 1; exp--; }
|
||||
mant &= 0x3FF;
|
||||
f = sign | (exp << 23) | (mant << 13);
|
||||
}
|
||||
}
|
||||
else if (exp == 0x1F)
|
||||
f = sign | 0x7F800000 | (mant << 13);
|
||||
else
|
||||
f = sign | ((exp - 15 + 127) << 23) | (mant << 13);
|
||||
memcpy(&out, &f, sizeof(out));
|
||||
return out;
|
||||
}
|
||||
|
||||
/* ST.2084 (PQ) inverse-EOTF: normalised linear (v = nits/10000, [0,1]) ->
|
||||
* PQ code [0,1]. Same constants as the forward HDR pipeline. */
|
||||
static float vulkan_pq_encode(float v)
|
||||
@@ -17589,9 +17561,9 @@ static bool vulkan_read_viewport_hdr(void *data, uint16_t *buffer,
|
||||
int x;
|
||||
for (x = 0; x < (int)vp_width; x++)
|
||||
{
|
||||
float r = vulkan_half_to_float(row[x * 4 + 0]);
|
||||
float g = vulkan_half_to_float(row[x * 4 + 1]);
|
||||
float b = vulkan_half_to_float(row[x * 4 + 2]);
|
||||
float r = retro_half_to_float(row[x * 4 + 0]);
|
||||
float g = retro_half_to_float(row[x * 4 + 1]);
|
||||
float b = retro_half_to_float(row[x * 4 + 2]);
|
||||
float lvl;
|
||||
buffer[x * 3 + 0] = vulkan_scrgb_channel_to_pq16(r);
|
||||
buffer[x * 3 + 1] = vulkan_scrgb_channel_to_pq16(g);
|
||||
|
||||
+4
-1292
File diff suppressed because it is too large.
Load diff
@@ -582,6 +582,9 @@ VIDEO IMAGE
|
||||
#ifdef HAVE_RTGA
|
||||
#include "../libretro-common/formats/tga/rtga.c"
|
||||
#endif
|
||||
#ifdef HAVE_RGIF
|
||||
#include "../libretro-common/formats/gif/rgif.c"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_IMAGEVIEWER
|
||||
#include "../cores/libretro-imageviewer/image_core.c"
|
||||
@@ -776,6 +779,10 @@ VIDEO DRIVER
|
||||
#include "../gfx/drivers/d3d12.c"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_MF_AUDIO
|
||||
#include "../record/record_mf_audio.c"
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_D3D10)
|
||||
#include "../gfx/drivers/d3d10.c"
|
||||
#endif
|
||||
@@ -905,6 +912,7 @@ FONTS
|
||||
|
||||
#include "../gfx/font_driver.c"
|
||||
|
||||
#include "../libretro-common/gfx/font/rtt.c"
|
||||
#include "../gfx/drivers_font_renderer/stb.c"
|
||||
|
||||
#if defined(HAVE_FREETYPE)
|
||||
@@ -1787,6 +1795,7 @@ NETPLAY
|
||||
#include "../network/netplay/netplay_room_parse.c"
|
||||
#include "../libretro-common/net/net_compat.c"
|
||||
#include "../libretro-common/net/net_socket.c"
|
||||
#include "../libretro-common/net/net_resolve.c"
|
||||
#include "../libretro-common/net/net_http.c"
|
||||
#ifdef HAVE_RTMP
|
||||
#include "../libretro-common/net/net_rtmp_chunk.c"
|
||||
|
||||
@@ -1441,6 +1441,42 @@ void aes_ctr_crypt(const struct aes_ctx *ctx, uint8_t *counter,
|
||||
crypto_memzero(ks, sizeof(ks));
|
||||
}
|
||||
|
||||
/* The register holds ciphertext: encrypting it gives the next
|
||||
* keystream block, and each ciphertext octet goes back into it. */
|
||||
void aes_cfb128_encrypt(const struct aes_ctx *ctx, uint8_t *iv,
|
||||
unsigned *num, const uint8_t *in, uint8_t *out, size_t len)
|
||||
{
|
||||
unsigned n = *num & 15;
|
||||
while (len)
|
||||
{
|
||||
if (!n)
|
||||
aes_encrypt_block(ctx, iv, iv);
|
||||
iv[n] ^= *in++;
|
||||
*out++ = iv[n];
|
||||
n = (n + 1) & 15;
|
||||
len--;
|
||||
}
|
||||
*num = n;
|
||||
}
|
||||
|
||||
void aes_cfb128_decrypt(const struct aes_ctx *ctx, uint8_t *iv,
|
||||
unsigned *num, const uint8_t *in, uint8_t *out, size_t len)
|
||||
{
|
||||
unsigned n = *num & 15;
|
||||
while (len)
|
||||
{
|
||||
uint8_t c;
|
||||
if (!n)
|
||||
aes_encrypt_block(ctx, iv, iv);
|
||||
c = *in++;
|
||||
*out++ = (uint8_t)(iv[n] ^ c);
|
||||
iv[n] = c;
|
||||
n = (n + 1) & 15;
|
||||
len--;
|
||||
}
|
||||
*num = n;
|
||||
}
|
||||
|
||||
void aes_cbc_encrypt(const struct aes_ctx *ctx, uint8_t *iv,
|
||||
const uint8_t *in, uint8_t *out, size_t len)
|
||||
{
|
||||
|
||||
@@ -407,6 +407,11 @@ static uint32_t rd_adler32_update(uint32_t adler,
|
||||
return rd_adler32_on(adler, buf, len, 16);
|
||||
}
|
||||
|
||||
uint32_t encoding_adler32(uint32_t adler, const uint8_t *buf, size_t len)
|
||||
{
|
||||
return rd_adler32_update(adler, buf, len);
|
||||
}
|
||||
|
||||
/* Test entry point for tools/encodings/adler32_test.c: the kernel above is
|
||||
* static, and driving it only through rinflate/rdeflate would exercise
|
||||
* whatever lengths and alignments a stream happens to produce rather than
|
||||
@@ -2220,6 +2225,7 @@ struct rdeflate
|
||||
uint32_t crc; /* gzip: running crc32 of the input */
|
||||
uint32_t total_in; /* gzip: mod 2^32, for ISIZE */
|
||||
int final_in; /* caller signalled end of input */
|
||||
int flush_req; /* sync flush asked for: 1 pending, 2 marker out */
|
||||
int done;
|
||||
int error;
|
||||
|
||||
@@ -2944,6 +2950,14 @@ void rdeflate_finish(void *data)
|
||||
s->final_in = 1;
|
||||
}
|
||||
|
||||
/* write out everything taken so far, ending on a byte boundary */
|
||||
void rdeflate_flush(void *data)
|
||||
{
|
||||
struct rdeflate *s = (struct rdeflate*)data;
|
||||
if (!s->flush_req)
|
||||
s->flush_req = 1;
|
||||
}
|
||||
|
||||
/* ------- the parser: consume win[block_start..win_len) into symbols ------- */
|
||||
/* Fills the symbol buffer using greedy/lazy matching. Leaves s->pos at the
|
||||
* first unprocessed byte. Stops early if the symbol buffer is near full.
|
||||
@@ -3141,8 +3155,10 @@ static void rd_parse(struct rdeflate *s)
|
||||
s->prev_lit = prev_lit;
|
||||
}
|
||||
|
||||
/* flush a deferred match/literal at true end of input */
|
||||
if (s->final_in && s->pos >= end && s->have_prev
|
||||
/* flush a deferred match/literal at true end of input, or at a sync
|
||||
* flush once all the input given so far is in */
|
||||
if ((s->final_in || (s->flush_req && s->in_pos >= s->in_size))
|
||||
&& s->pos >= end && s->have_prev
|
||||
&& s->nsyms < RD_BLOCK_SYMS - 2)
|
||||
{
|
||||
s->have_prev = 0;
|
||||
@@ -3886,6 +3902,33 @@ int rdeflate_process(void *data, size_t *read, size_t *wrote)
|
||||
block_ready = 1;
|
||||
is_final = 1;
|
||||
}
|
||||
/* sync flush: once all the input given is parsed, the pending
|
||||
* symbols go out as a block, then an empty stored block ends
|
||||
* the output on a byte boundary (zlib's Z_SYNC_FLUSH). The
|
||||
* window is kept, so matches still reach across it. */
|
||||
if (!block_ready && s->flush_req && !s->final_in
|
||||
&& s->in_pos >= s->in_size && s->pos >= s->win_len
|
||||
&& !s->have_prev)
|
||||
{
|
||||
if (s->nsyms)
|
||||
block_ready = 1;
|
||||
else
|
||||
{
|
||||
if (s->flush_req == 1)
|
||||
{
|
||||
rd_putbits(s, 0, 3); /* BFINAL=0, BTYPE=00 */
|
||||
if (s->bitcnt & 7)
|
||||
rd_putbits(s, 0, 8 - (s->bitcnt & 7));
|
||||
rd_putbits(s, 0x0000, 16);
|
||||
rd_putbits(s, 0xffff, 16);
|
||||
s->flush_req = 2;
|
||||
}
|
||||
if (!rd_flush_bytes(s))
|
||||
goto suspend;
|
||||
s->flush_req = 0;
|
||||
goto suspend;
|
||||
}
|
||||
}
|
||||
if (!block_ready) /* need more input */
|
||||
goto suspend;
|
||||
s->block_final = is_final;
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (encoding_shiftjis.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 <encodings/shiftjis.h>
|
||||
|
||||
#include "encoding_shiftjis_tables.h"
|
||||
|
||||
/* The row of sjis_decode for lead octet @b, or -1 */
|
||||
static int sjis_row(unsigned b)
|
||||
{
|
||||
if (b >= 0x81 && b <= 0x9F)
|
||||
return (int)(b - 0x81);
|
||||
if (b >= 0xE0 && b <= 0xEF)
|
||||
return (int)(b - 0xE0 + 31);
|
||||
if (b >= 0xFA && b <= 0xFC)
|
||||
return (int)(b - 0xFA + 47);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Puts @cp at @out + *pos as UTF-8 if it fits in @cap octets */
|
||||
static int sjis_put_utf8(char *out, size_t cap, size_t *pos, uint32_t cp)
|
||||
{
|
||||
size_t n = cp < 0x80 ? 1 : cp < 0x800 ? 2 : 3;
|
||||
char *p = out + *pos;
|
||||
if (cap - *pos < n)
|
||||
return 0;
|
||||
if (n == 1)
|
||||
p[0] = (char)cp;
|
||||
else if (n == 2)
|
||||
{
|
||||
p[0] = (char)(0xC0 | (cp >> 6));
|
||||
p[1] = (char)(0x80 | (cp & 0x3F));
|
||||
}
|
||||
else
|
||||
{
|
||||
p[0] = (char)(0xE0 | (cp >> 12));
|
||||
p[1] = (char)(0x80 | ((cp >> 6) & 0x3F));
|
||||
p[2] = (char)(0x80 | (cp & 0x3F));
|
||||
}
|
||||
*pos += n;
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t sjis_to_utf8(char *out, size_t out_size, const char *in, size_t in_len)
|
||||
{
|
||||
const unsigned char *s = (const unsigned char*)in;
|
||||
size_t i = 0, pos = 0;
|
||||
|
||||
if (!out_size)
|
||||
return 0;
|
||||
while (i < in_len)
|
||||
{
|
||||
unsigned b = s[i];
|
||||
uint32_t cp;
|
||||
int row;
|
||||
if (b < 0x80)
|
||||
{
|
||||
cp = b;
|
||||
i++;
|
||||
}
|
||||
else if (b >= 0xA1 && b <= 0xDF)
|
||||
{
|
||||
/* half-width katakana */
|
||||
cp = 0xFF61 + (b - 0xA1);
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned t;
|
||||
if ((row = sjis_row(b)) < 0 || i + 1 >= in_len)
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
t = s[i + 1];
|
||||
if (t < 0x40 || t > 0xFC || !(cp = sjis_decode[row][t - 0x40]))
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
if (!sjis_put_utf8(out, out_size - 1, &pos, cp))
|
||||
break;
|
||||
}
|
||||
out[pos] = '\0';
|
||||
return pos;
|
||||
}
|
||||
|
||||
/* The two-octet code for @cp, or 0 */
|
||||
static unsigned sjis_lookup(uint32_t cp)
|
||||
{
|
||||
size_t lo = 0, hi = SJIS_ENCODE_COUNT;
|
||||
while (lo < hi)
|
||||
{
|
||||
size_t mid = lo + (hi - lo) / 2;
|
||||
if (sjis_encode[mid][0] < cp)
|
||||
lo = mid + 1;
|
||||
else
|
||||
hi = mid;
|
||||
}
|
||||
return lo < SJIS_ENCODE_COUNT && sjis_encode[lo][0] == cp ? sjis_encode[lo][1] : 0;
|
||||
}
|
||||
|
||||
/* The code point UTF-8 starts at @s (@len octets left), its length at
|
||||
* @n; 0 at @n where the octet starts no valid character */
|
||||
static uint32_t sjis_utf8_next(const unsigned char *s, size_t len, size_t *n)
|
||||
{
|
||||
unsigned b = s[0];
|
||||
uint32_t cp, min;
|
||||
size_t k, more;
|
||||
|
||||
*n = 0;
|
||||
if (b < 0x80)
|
||||
{
|
||||
*n = 1;
|
||||
return b;
|
||||
}
|
||||
if (b >= 0xC2 && b <= 0xDF)
|
||||
{
|
||||
more = 1;
|
||||
cp = b & 0x1F;
|
||||
min = 0x80;
|
||||
}
|
||||
else if (b >= 0xE0 && b <= 0xEF)
|
||||
{
|
||||
more = 2;
|
||||
cp = b & 0x0F;
|
||||
min = 0x800;
|
||||
}
|
||||
else if (b >= 0xF0 && b <= 0xF4)
|
||||
{
|
||||
more = 3;
|
||||
cp = b & 0x07;
|
||||
min = 0x10000;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
if (len <= more)
|
||||
return 0;
|
||||
for (k = 1; k <= more; k++)
|
||||
{
|
||||
if ((s[k] & 0xC0) != 0x80)
|
||||
return 0;
|
||||
cp = (cp << 6) | (s[k] & 0x3F);
|
||||
}
|
||||
if (cp < min || cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF))
|
||||
return 0;
|
||||
*n = more + 1;
|
||||
return cp;
|
||||
}
|
||||
|
||||
size_t utf8_to_sjis(char *out, size_t out_size, const char *in, size_t in_len)
|
||||
{
|
||||
const unsigned char *s = (const unsigned char*)in;
|
||||
size_t i = 0, pos = 0;
|
||||
|
||||
if (!out_size)
|
||||
return 0;
|
||||
while (i < in_len)
|
||||
{
|
||||
size_t n;
|
||||
uint32_t cp = sjis_utf8_next(s + i, in_len - i, &n);
|
||||
unsigned code;
|
||||
if (!n)
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
i += n;
|
||||
if (cp < 0x80)
|
||||
code = cp;
|
||||
else if (cp >= 0xFF61 && cp <= 0xFF9F)
|
||||
code = 0xA1 + (cp - 0xFF61);
|
||||
else if (!(code = sjis_lookup(cp)))
|
||||
continue;
|
||||
if (code > 0xFF)
|
||||
{
|
||||
if (out_size - 1 - pos < 2)
|
||||
break;
|
||||
out[pos++] = (char)(code >> 8);
|
||||
out[pos++] = (char)(code & 0xFF);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (out_size - 1 - pos < 1)
|
||||
break;
|
||||
out[pos++] = (char)code;
|
||||
}
|
||||
}
|
||||
out[pos] = '\0';
|
||||
return pos;
|
||||
}
|
||||
|
||||
/* Code page 1252's 0x80-0x9F; 0 where it has nothing */
|
||||
static const uint16_t cp1252_high[32] = {
|
||||
0x20AC, 0, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,
|
||||
0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0, 0x017D, 0,
|
||||
0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
|
||||
0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0, 0x017E, 0x0178
|
||||
};
|
||||
|
||||
size_t cp1252_to_utf8(char *out, size_t out_size, const char *in, size_t in_len)
|
||||
{
|
||||
const unsigned char *s = (const unsigned char*)in;
|
||||
size_t i, pos = 0;
|
||||
|
||||
if (!out_size)
|
||||
return 0;
|
||||
for (i = 0; i < in_len; i++)
|
||||
{
|
||||
uint32_t cp = s[i];
|
||||
if (cp >= 0x80 && cp <= 0x9F && !(cp = cp1252_high[cp - 0x80]))
|
||||
continue;
|
||||
if (!sjis_put_utf8(out, out_size - 1, &pos, cp))
|
||||
break;
|
||||
}
|
||||
out[pos] = '\0';
|
||||
return pos;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,61 @@
|
||||
# python3 gen_sjis_tables.py > encoding_shiftjis_tables.h
|
||||
#
|
||||
# Writes encoding_shiftjis_tables.h: Shift-JIS as Windows code page 932
|
||||
# has it (JIS X 0208 with the NEC and IBM extensions), from Python's
|
||||
# cp932 codec. The user-defined area F040-F9FC and the single octets
|
||||
# 0x80, 0xA0 and 0xFD-0xFF, which code page 932 sends to the Private Use
|
||||
# Area, are left out.
|
||||
import sys
|
||||
|
||||
def lead_ok(l):
|
||||
return 0x81 <= l <= 0x9F or 0xE0 <= l <= 0xEF or 0xFA <= l <= 0xFC
|
||||
|
||||
leads = [l for l in range(0x81, 0xFD) if lead_ok(l)]
|
||||
decode = []
|
||||
pairs = {}
|
||||
for l in leads:
|
||||
row = []
|
||||
for t in range(0x40, 0xFD):
|
||||
u = 0
|
||||
try:
|
||||
s = bytes([l, t]).decode('cp932')
|
||||
if len(s) == 1 and not (0xE000 <= ord(s) <= 0xF8FF):
|
||||
u = ord(s)
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
row.append(u)
|
||||
decode.append(row)
|
||||
|
||||
for cp in range(0x80, 0x10000):
|
||||
if 0xD800 <= cp < 0xE000 or 0xE000 <= cp <= 0xF8FF:
|
||||
continue
|
||||
try:
|
||||
e = chr(cp).encode('cp932')
|
||||
except UnicodeEncodeError:
|
||||
continue
|
||||
if len(e) == 2 and lead_ok(e[0]):
|
||||
pairs[cp] = (e[0] << 8) | e[1]
|
||||
|
||||
out = sys.stdout
|
||||
out.write('/* Generated by gen_sjis_tables.py from code page 932, as Python\'s\n'
|
||||
' * cp932 codec has it; do not edit. */\n'
|
||||
'#ifndef LIBRETRO_ENCODING_SHIFTJIS_TABLES_H\n'
|
||||
'#define LIBRETRO_ENCODING_SHIFTJIS_TABLES_H\n\n'
|
||||
'#include <stdint.h>\n\n')
|
||||
out.write('/* Unicode for lead octet L (rows: 0x81-0x9F, 0xE0-0xEF, 0xFA-0xFC)\n'
|
||||
' * and trail octet 0x40-0xFC; 0 where nothing is mapped. */\n')
|
||||
out.write('static const uint16_t sjis_decode[%d][189] = {\n' % len(leads))
|
||||
for l, row in zip(leads, decode):
|
||||
out.write(' /* 0x%02X */ {\n' % l)
|
||||
for i in range(0, len(row), 10):
|
||||
out.write(' ' + ','.join('0x%04X' % v for v in row[i:i+10]) + ',\n')
|
||||
out.write(' },\n')
|
||||
out.write('};\n\n')
|
||||
keys = sorted(pairs)
|
||||
out.write('/* Unicode code points with their two-octet Shift-JIS codes, sorted\n'
|
||||
' * by code point for a binary search. */\n')
|
||||
out.write('#define SJIS_ENCODE_COUNT %d\n' % len(keys))
|
||||
out.write('static const uint16_t sjis_encode[SJIS_ENCODE_COUNT][2] = {\n')
|
||||
for i in range(0, len(keys), 6):
|
||||
out.write(' ' + ','.join('{0x%04X,0x%04X}' % (k, pairs[k]) for k in keys[i:i+6]) + ',\n')
|
||||
out.write('};\n\n#endif\n')
|
||||
File diff suppressed because it is too large.
Load diff
@@ -66,6 +66,7 @@
|
||||
#include <limits.h>
|
||||
|
||||
#include <retro_inline.h>
|
||||
#include <retro_endianness.h>
|
||||
|
||||
#include <formats/image.h>
|
||||
#include <formats/rdds.h>
|
||||
@@ -90,7 +91,20 @@
|
||||
( (unsigned long long)RDDS_RL32((const unsigned char*)(p)) \
|
||||
| ((unsigned long long)RDDS_RL32((const unsigned char*)(p) + 4) << 32))
|
||||
|
||||
static void rdds_bcdec__color_block(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int onlyOpaqueMode) {
|
||||
/* Inlined into each of its callers whatever the compiler would choose, so
|
||||
* that a decoder taking a constant order (RGBA or BGRA) is built for it
|
||||
* alone, with no test of the order per block. */
|
||||
#if defined(_MSC_VER)
|
||||
#define RDDS_FORCE_INLINE static __forceinline
|
||||
#elif defined(__GNUC__)
|
||||
#define RDDS_FORCE_INLINE static INLINE __attribute__((always_inline))
|
||||
#else
|
||||
#define RDDS_FORCE_INLINE static INLINE
|
||||
#endif
|
||||
|
||||
/* bgra: B,G,R,A in memory instead of R,G,B,A (red and blue swapped in
|
||||
* the reference colours) */
|
||||
RDDS_FORCE_INLINE void rdds_bcdec__color_block_order(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int onlyOpaqueMode, int bgra) {
|
||||
unsigned short c0, c1;
|
||||
unsigned int refColors[4]; /* 0xAABBGGRR */
|
||||
unsigned char* dstColors;
|
||||
@@ -146,6 +160,12 @@ static void rdds_bcdec__color_block(const void* compressedBlock, void* decompres
|
||||
|
||||
colorIndices = RDDS_RL32((const unsigned char*)compressedBlock + 4);
|
||||
|
||||
if (bgra) {
|
||||
for (i = 0; i < 4; ++i)
|
||||
refColors[i] = (refColors[i] & 0xFF00FF00u) | ((refColors[i] >> 16) & 0xFFu)
|
||||
| ((refColors[i] & 0xFFu) << 16);
|
||||
}
|
||||
|
||||
/* Fill out the decompressed color block */
|
||||
dstColors = (unsigned char*)decompressedBlock;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
@@ -162,6 +182,14 @@ static void rdds_bcdec__color_block(const void* compressedBlock, void* decompres
|
||||
}
|
||||
}
|
||||
|
||||
static void rdds_bcdec__color_block(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int onlyOpaqueMode) {
|
||||
rdds_bcdec__color_block_order(compressedBlock, decompressedBlock, destinationPitch, onlyOpaqueMode, 0);
|
||||
}
|
||||
|
||||
static void rdds_bcdec__color_block_bgra(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int onlyOpaqueMode) {
|
||||
rdds_bcdec__color_block_order(compressedBlock, decompressedBlock, destinationPitch, onlyOpaqueMode, 1);
|
||||
}
|
||||
|
||||
static void rdds_bcdec__sharp_alpha_block(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
|
||||
const unsigned char* alpha;
|
||||
unsigned char* decompressed;
|
||||
@@ -390,6 +418,21 @@ static void rdds_bcdec_bc3(const void* compressedBlock, void* decompressedBlock,
|
||||
rdds_bcdec__smooth_alpha_block(compressedBlock, ((char*)decompressedBlock) + 3, destinationPitch, 4);
|
||||
}
|
||||
|
||||
/* The same, B,G,R,A in memory: alpha stays the fourth byte */
|
||||
static void rdds_bcdec_bc1_bgra(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
|
||||
rdds_bcdec__color_block_bgra(compressedBlock, decompressedBlock, destinationPitch, 0);
|
||||
}
|
||||
|
||||
static void rdds_bcdec_bc2_bgra(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
|
||||
rdds_bcdec__color_block_bgra(((char*)compressedBlock) + 8, decompressedBlock, destinationPitch, 1);
|
||||
rdds_bcdec__sharp_alpha_block(compressedBlock, ((char*)decompressedBlock) + 3, destinationPitch);
|
||||
}
|
||||
|
||||
static void rdds_bcdec_bc3_bgra(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
|
||||
rdds_bcdec__color_block_bgra(((char*)compressedBlock) + 8, decompressedBlock, destinationPitch, 1);
|
||||
rdds_bcdec__smooth_alpha_block(compressedBlock, ((char*)decompressedBlock) + 3, destinationPitch, 4);
|
||||
}
|
||||
|
||||
#ifndef BCDEC_BC4BC5_PRECISE
|
||||
static void rdds_bcdec_bc4(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
|
||||
rdds_bcdec__smooth_alpha_block(compressedBlock, decompressedBlock, destinationPitch, 1);
|
||||
@@ -1891,7 +1934,7 @@ static const unsigned char rdds_d6[190] = {
|
||||
* stores the compiler already coalesces, so it needs no fused variant
|
||||
* and no punch-through handling here.) */
|
||||
static INLINE void rdds_bc_refcolors(const unsigned char *cp,
|
||||
unsigned int ref[4])
|
||||
unsigned int ref[4], int bgra)
|
||||
{
|
||||
unsigned c0 = RDDS_RL16(cp), c1 = RDDS_RL16(cp + 2);
|
||||
unsigned r0 = (c0 >> 11) & 0x1F, g0 = (c0 >> 5) & 0x3F, b0 = c0 & 0x1F;
|
||||
@@ -1905,11 +1948,19 @@ static INLINE void rdds_bc_refcolors(const unsigned char *cp,
|
||||
| ((unsigned)rdds_d6[2 * g0 + g1] << 8) | rdds_d5[2 * r0 + r1];
|
||||
ref[3] = 0xFF000000u | ((unsigned)rdds_d5[b0 + 2 * b1] << 16)
|
||||
| ((unsigned)rdds_d6[g0 + 2 * g1] << 8) | rdds_d5[r0 + 2 * r1];
|
||||
if (bgra) /* A<<24|R<<16|G<<8|B, for a BGRA surface */
|
||||
{
|
||||
int k;
|
||||
for (k = 0; k < 4; k++)
|
||||
ref[k] = (ref[k] & 0xFF00FF00u) | ((ref[k] >> 16) & 0xFFu)
|
||||
| ((ref[k] & 0xFFu) << 16);
|
||||
}
|
||||
}
|
||||
|
||||
/* BC2: opaque colour + explicit 4-bit alpha, fused into one store. */
|
||||
static void rdds_bcdec_bc2_direct(const void *compressedBlock,
|
||||
void *decompressedBlock, int destinationPitch)
|
||||
/* BC2: opaque colour + explicit 4-bit alpha, fused into one store.
|
||||
* bgra: the uint32 is A<<24|R<<16|G<<8|B instead of A<<24|B<<16|G<<8|R. */
|
||||
RDDS_FORCE_INLINE void rdds_bcdec_bc2_fused(const void *compressedBlock,
|
||||
void *decompressedBlock, int destinationPitch, int bgra)
|
||||
{
|
||||
const unsigned char *p = (const unsigned char*)compressedBlock;
|
||||
unsigned char *dst = (unsigned char*)decompressedBlock;
|
||||
@@ -1917,7 +1968,7 @@ static void rdds_bcdec_bc2_direct(const void *compressedBlock,
|
||||
unsigned int ci = RDDS_RL32(p + 12);
|
||||
int i, j;
|
||||
|
||||
rdds_bc_refcolors(p + 8, ref);
|
||||
rdds_bc_refcolors(p + 8, ref, bgra);
|
||||
for (i = 0; i < 4; ++i)
|
||||
{
|
||||
unsigned int arow = RDDS_RL16(p + 2 * i); /* four 4-bit alphas */
|
||||
@@ -1934,9 +1985,11 @@ static void rdds_bcdec_bc2_direct(const void *compressedBlock,
|
||||
}
|
||||
}
|
||||
|
||||
/* BC3: opaque colour + smooth (interpolated) 8-value alpha, one store. */
|
||||
static void rdds_bcdec_bc3_direct(const void *compressedBlock,
|
||||
void *decompressedBlock, int destinationPitch)
|
||||
/* BC3: opaque colour + smooth (interpolated) 8-value alpha, one store.
|
||||
* The alpha ramp rounds as rdds_bcdec__smooth_alpha_block does, so this
|
||||
* path and the scratch one agree for every texel. */
|
||||
RDDS_FORCE_INLINE void rdds_bcdec_bc3_fused(const void *compressedBlock,
|
||||
void *decompressedBlock, int destinationPitch, int bgra)
|
||||
{
|
||||
const unsigned char *p = (const unsigned char*)compressedBlock;
|
||||
unsigned char *dst = (unsigned char*)decompressedBlock;
|
||||
@@ -1951,24 +2004,24 @@ static void rdds_bcdec_bc3_direct(const void *compressedBlock,
|
||||
al[1] = (unsigned char)((blk >> 8) & 0xFF);
|
||||
if (al[0] > al[1])
|
||||
{
|
||||
al[2] = (unsigned char)((6 * al[0] + al[1]) / 7);
|
||||
al[3] = (unsigned char)((5 * al[0] + 2 * al[1]) / 7);
|
||||
al[4] = (unsigned char)((4 * al[0] + 3 * al[1]) / 7);
|
||||
al[5] = (unsigned char)((3 * al[0] + 4 * al[1]) / 7);
|
||||
al[6] = (unsigned char)((2 * al[0] + 5 * al[1]) / 7);
|
||||
al[7] = (unsigned char)(( al[0] + 6 * al[1]) / 7);
|
||||
al[2] = (unsigned char)((6 * al[0] + al[1] + 3) / 7);
|
||||
al[3] = (unsigned char)((5 * al[0] + 2 * al[1] + 3) / 7);
|
||||
al[4] = (unsigned char)((4 * al[0] + 3 * al[1] + 3) / 7);
|
||||
al[5] = (unsigned char)((3 * al[0] + 4 * al[1] + 3) / 7);
|
||||
al[6] = (unsigned char)((2 * al[0] + 5 * al[1] + 3) / 7);
|
||||
al[7] = (unsigned char)(( al[0] + 6 * al[1] + 3) / 7);
|
||||
}
|
||||
else
|
||||
{
|
||||
al[2] = (unsigned char)((4 * al[0] + al[1]) / 5);
|
||||
al[3] = (unsigned char)((3 * al[0] + 2 * al[1]) / 5);
|
||||
al[4] = (unsigned char)((2 * al[0] + 3 * al[1]) / 5);
|
||||
al[5] = (unsigned char)(( al[0] + 4 * al[1]) / 5);
|
||||
al[2] = (unsigned char)((4 * al[0] + al[1] + 2) / 5);
|
||||
al[3] = (unsigned char)((3 * al[0] + 2 * al[1] + 2) / 5);
|
||||
al[4] = (unsigned char)((2 * al[0] + 3 * al[1] + 2) / 5);
|
||||
al[5] = (unsigned char)(( al[0] + 4 * al[1] + 2) / 5);
|
||||
al[6] = 0x00;
|
||||
al[7] = 0xFF;
|
||||
}
|
||||
|
||||
rdds_bc_refcolors(p + 8, ref);
|
||||
rdds_bc_refcolors(p + 8, ref, bgra);
|
||||
ci = RDDS_RL32(p + 12);
|
||||
idx = blk >> 16; /* 16 * 3-bit indices */
|
||||
for (i = 0; i < 4; ++i)
|
||||
@@ -1985,6 +2038,15 @@ static void rdds_bcdec_bc3_direct(const void *compressedBlock,
|
||||
}
|
||||
}
|
||||
|
||||
static void rdds_bcdec_bc2_direct(const void *b, void *o, int pitch)
|
||||
{ rdds_bcdec_bc2_fused(b, o, pitch, 0); }
|
||||
static void rdds_bcdec_bc3_direct(const void *b, void *o, int pitch)
|
||||
{ rdds_bcdec_bc3_fused(b, o, pitch, 0); }
|
||||
static void rdds_bcdec_bc2_direct_bgra(const void *b, void *o, int pitch)
|
||||
{ rdds_bcdec_bc2_fused(b, o, pitch, 1); }
|
||||
static void rdds_bcdec_bc3_direct_bgra(const void *b, void *o, int pitch)
|
||||
{ rdds_bcdec_bc3_fused(b, o, pitch, 1); }
|
||||
|
||||
/* Emit an edge (partially out-of-range) block from a decoded RGBA8
|
||||
* scratch into the surface with per-texel bounds tests. Shared by every
|
||||
* path so the hot interior loops stay branch-free. */
|
||||
@@ -2060,6 +2122,17 @@ RDDS_GEN_ROWS_DIRECT(rdds_rows_bc1_rgba, rdds_bcdec_bc1, 8u)
|
||||
RDDS_GEN_ROWS_DIRECT(rdds_rows_bc2_rgba, rdds_bcdec_bc2_direct, 16u)
|
||||
RDDS_GEN_ROWS_DIRECT(rdds_rows_bc3_rgba, rdds_bcdec_bc3_direct, 16u)
|
||||
|
||||
#ifndef MSB_FIRST
|
||||
/* The same for a BGRA video driver, little-endian only: there the
|
||||
* A<<24|R<<16|G<<8|B pixel is the bytes B,G,R,A, which the BGRA block
|
||||
* decoders write, so the edge scratch passes through rdds_emit_edge's
|
||||
* byte order unchanged (as with RGBA). Big-endian keeps the scratch
|
||||
* path below. */
|
||||
RDDS_GEN_ROWS_DIRECT(rdds_rows_bc1_bgra, rdds_bcdec_bc1_bgra, 8u)
|
||||
RDDS_GEN_ROWS_DIRECT(rdds_rows_bc2_bgra, rdds_bcdec_bc2_direct_bgra, 16u)
|
||||
RDDS_GEN_ROWS_DIRECT(rdds_rows_bc3_bgra, rdds_bcdec_bc3_direct_bgra, 16u)
|
||||
#endif
|
||||
|
||||
/* Decode block rows [by0, by1) of a BCn surface into 'out'. The caller
|
||||
* has already validated that the payload holds every block. */
|
||||
static void rdds_rows_compressed(const rdds_info_t *info,
|
||||
@@ -2093,6 +2166,22 @@ static void rdds_rows_compressed(const rdds_info_t *info,
|
||||
break; /* BC4/BC5/BC7 fall through to the scratch loop */
|
||||
}
|
||||
}
|
||||
#ifndef MSB_FIRST
|
||||
else if (!premul)
|
||||
{
|
||||
switch (info->fmt)
|
||||
{
|
||||
case RDDS_FMT_BC1:
|
||||
rdds_rows_bc1_bgra(info, data, out, by0, by1); return;
|
||||
case RDDS_FMT_BC2:
|
||||
rdds_rows_bc2_bgra(info, data, out, by0, by1); return;
|
||||
case RDDS_FMT_BC3:
|
||||
rdds_rows_bc3_bgra(info, data, out, by0, by1); return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Generic scratch path: BGRA output order, premultiplied alpha,
|
||||
* BC4/BC5 channel expansion, and BC7 (whose decode dominates, so the
|
||||
@@ -2722,3 +2811,6 @@ void rdds_decode_block_bc1(const void *block, void *out, int pitch) { rdds_bcdec
|
||||
void rdds_decode_block_bc2(const void *block, void *out, int pitch) { rdds_bcdec_bc2(block, out, pitch); }
|
||||
void rdds_decode_block_bc3(const void *block, void *out, int pitch) { rdds_bcdec_bc3(block, out, pitch); }
|
||||
void rdds_decode_block_bc7(const void *block, void *out, int pitch) { rdds_bcdec_bc7(block, out, pitch); }
|
||||
void rdds_decode_block_bc1_bgra(const void *block, void *out, int pitch) { rdds_bcdec_bc1_bgra(block, out, pitch); }
|
||||
void rdds_decode_block_bc2_bgra(const void *block, void *out, int pitch) { rdds_bcdec_bc2_bgra(block, out, pitch); }
|
||||
void rdds_decode_block_bc3_bgra(const void *block, void *out, int pitch) { rdds_bcdec_bc3_bgra(block, out, pitch); }
|
||||
@@ -277,6 +277,18 @@ bool rflv_mux_get(rflv_mux_t *m, const rflv_span_t **spans, size_t *count)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rflv_mux_get_tag(rflv_mux_t *m, rflv_tag_t *tag)
|
||||
{
|
||||
if (!m || !tag || !m->pending || m->output == FLV_HEADER)
|
||||
return false;
|
||||
tag->spans[0].data = m->prefix + 11;
|
||||
tag->spans[0].size = m->spans[0].size - 11;
|
||||
tag->spans[1] = m->spans[1];
|
||||
tag->timestamp = m->timestamp;
|
||||
tag->type = m->prefix[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rflv_mux_release(rflv_mux_t *m)
|
||||
{
|
||||
if (!m || !m->pending)
|
||||
|
||||
@@ -0,0 +1,463 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (rgif.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 <retro_endianness.h>
|
||||
#include <formats/image.h>
|
||||
#include <formats/rgif.h>
|
||||
|
||||
#define RG_CODES 4096 /* 12-bit LZW codes */
|
||||
|
||||
struct rgif
|
||||
{
|
||||
/* the LZW table: each code is its prefix code and one last index */
|
||||
int16_t prefix[RG_CODES];
|
||||
uint8_t first[RG_CODES]; /* the first index of the code's string */
|
||||
uint8_t suffix[RG_CODES];
|
||||
uint8_t stack[RG_CODES]; /* a string, last index first */
|
||||
|
||||
uint8_t pal[256][4]; /* the global table: R, G, B, alpha */
|
||||
uint8_t lpal[256][4]; /* the image's own */
|
||||
uint8_t px[256][4]; /* each index as written: in @order, or
|
||||
0 where it is transparent */
|
||||
|
||||
/* the image_transfer interface's file */
|
||||
const uint8_t *buf;
|
||||
size_t len;
|
||||
size_t avail;
|
||||
};
|
||||
|
||||
/* The file, read as stb_image reads it: past the end, every byte is 0 */
|
||||
struct rg_in
|
||||
{
|
||||
const uint8_t *p;
|
||||
size_t at, len;
|
||||
};
|
||||
|
||||
static unsigned rg_get8(struct rg_in *in)
|
||||
{
|
||||
return in->at < in->len ? in->p[in->at++] : 0;
|
||||
}
|
||||
|
||||
static unsigned rg_get16(struct rg_in *in)
|
||||
{
|
||||
unsigned lo = rg_get8(in);
|
||||
return lo | (rg_get8(in) << 8);
|
||||
}
|
||||
|
||||
static void rg_skip(struct rg_in *in, size_t n)
|
||||
{
|
||||
in->at = n < in->len - in->at ? in->at + n : in->len;
|
||||
}
|
||||
|
||||
static void rg_put(uint8_t *d, unsigned order, unsigned r, unsigned g,
|
||||
unsigned b, unsigned a)
|
||||
{
|
||||
switch (order)
|
||||
{
|
||||
case RGIF_ARGB:
|
||||
d[0] = (uint8_t)a; d[1] = (uint8_t)r; d[2] = (uint8_t)g; d[3] = (uint8_t)b;
|
||||
break;
|
||||
case RGIF_BGRA:
|
||||
d[0] = (uint8_t)b; d[1] = (uint8_t)g; d[2] = (uint8_t)r; d[3] = (uint8_t)a;
|
||||
break;
|
||||
case RGIF_ABGR:
|
||||
d[0] = (uint8_t)a; d[1] = (uint8_t)b; d[2] = (uint8_t)g; d[3] = (uint8_t)r;
|
||||
break;
|
||||
default:
|
||||
d[0] = (uint8_t)r; d[1] = (uint8_t)g; d[2] = (uint8_t)b; d[3] = (uint8_t)a;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void rg_colours(struct rg_in *in, uint8_t pal[256][4], unsigned n,
|
||||
int transparent)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
pal[i][0] = (uint8_t)rg_get8(in);
|
||||
pal[i][1] = (uint8_t)rg_get8(in);
|
||||
pal[i][2] = (uint8_t)rg_get8(in);
|
||||
pal[i][3] = (int)i == transparent ? 0 : 255;
|
||||
}
|
||||
}
|
||||
|
||||
static int rg_header(struct rg_in *in, unsigned *w, unsigned *h)
|
||||
{
|
||||
unsigned v;
|
||||
if (rg_get8(in) != 'G' || rg_get8(in) != 'I' || rg_get8(in) != 'F'
|
||||
|| rg_get8(in) != '8')
|
||||
return 0;
|
||||
v = rg_get8(in);
|
||||
if ((v != '7' && v != '9') || rg_get8(in) != 'a')
|
||||
return 0;
|
||||
*w = rg_get16(in);
|
||||
*h = rg_get16(in);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool rgif_size(const uint8_t *data, size_t len,
|
||||
unsigned *width, unsigned *height)
|
||||
{
|
||||
struct rg_in in;
|
||||
in.p = data; in.at = 0; in.len = data ? len : 0;
|
||||
return rg_header(&in, width, height) != 0;
|
||||
}
|
||||
|
||||
rgif_t *rgif_new(void)
|
||||
{
|
||||
return (rgif_t*)calloc(1, sizeof(rgif_t));
|
||||
}
|
||||
|
||||
void rgif_free(rgif_t *g)
|
||||
{
|
||||
free(g);
|
||||
}
|
||||
|
||||
/* Where the image's pixels go, in the order its LZW stream gives them */
|
||||
struct rg_raster
|
||||
{
|
||||
uint8_t *out;
|
||||
size_t stride;
|
||||
unsigned x0, x1; /* the image's columns, x0 to x1 - 1 */
|
||||
unsigned y0, y1; /* and rows */
|
||||
unsigned x, y; /* the next pixel */
|
||||
unsigned step; /* rows from one to the next in this pass */
|
||||
unsigned parse; /* interlace passes still to come */
|
||||
uint8_t *row; /* out + y * stride */
|
||||
};
|
||||
|
||||
/* A code's string, out to the image; the rows of an interlaced image in
|
||||
* GIF's four passes */
|
||||
static void rg_emit(rgif_t *g, struct rg_raster *r, unsigned code)
|
||||
{
|
||||
unsigned n = 0;
|
||||
|
||||
do
|
||||
{
|
||||
g->stack[n++] = g->suffix[code];
|
||||
code = (unsigned)g->prefix[code];
|
||||
} while (code < RG_CODES);
|
||||
|
||||
while (n)
|
||||
{
|
||||
if (r->y >= r->y1)
|
||||
return; /* past the image: the rest of the stream is dropped */
|
||||
memcpy(r->row + (size_t)r->x * 4, g->px[g->stack[--n]], 4);
|
||||
if (++r->x >= r->x1)
|
||||
{
|
||||
r->x = r->x0;
|
||||
r->y += r->step;
|
||||
while (r->y >= r->y1 && r->parse)
|
||||
{
|
||||
r->step = 1u << r->parse; /* 8, 4, 2: from row 4, 2, 1 */
|
||||
r->y = r->y0 + r->step / 2;
|
||||
r->parse--;
|
||||
}
|
||||
if (r->y < r->y1)
|
||||
r->row = r->out + (size_t)r->y * r->stride;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The image's LZW stream, as stb_image reads it; 0 where it is broken */
|
||||
static int rg_raster(rgif_t *g, struct rg_in *in, struct rg_raster *r)
|
||||
{
|
||||
unsigned lzw_cs = rg_get8(in);
|
||||
unsigned clear, codesize, codemask, avail, bits = 0, valid = 0, len = 0;
|
||||
int oldcode = -1, first = 1;
|
||||
unsigned i;
|
||||
|
||||
if (lzw_cs > 12)
|
||||
return 0;
|
||||
clear = 1u << lzw_cs;
|
||||
codesize = lzw_cs + 1;
|
||||
codemask = (1u << codesize) - 1;
|
||||
for (i = 0; i < clear; i++)
|
||||
{
|
||||
g->prefix[i] = -1;
|
||||
g->first[i] = (uint8_t)i;
|
||||
g->suffix[i] = (uint8_t)i;
|
||||
}
|
||||
avail = clear + 2;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
unsigned code;
|
||||
if (valid < codesize)
|
||||
{
|
||||
if (!len)
|
||||
{
|
||||
len = rg_get8(in); /* the next sub-block */
|
||||
if (!len)
|
||||
return 1;
|
||||
}
|
||||
len--;
|
||||
bits |= rg_get8(in) << valid;
|
||||
valid += 8;
|
||||
continue;
|
||||
}
|
||||
code = bits & codemask;
|
||||
bits >>= codesize;
|
||||
valid -= codesize;
|
||||
|
||||
if (code == clear)
|
||||
{
|
||||
codesize = lzw_cs + 1;
|
||||
codemask = (1u << codesize) - 1;
|
||||
avail = clear + 2;
|
||||
oldcode = -1;
|
||||
first = 0;
|
||||
}
|
||||
else if (code == clear + 1)
|
||||
{
|
||||
/* the end: what is left of the sub-blocks is skipped */
|
||||
rg_skip(in, len);
|
||||
while ((len = rg_get8(in)) > 0)
|
||||
rg_skip(in, len);
|
||||
return 1;
|
||||
}
|
||||
else if (code <= avail)
|
||||
{
|
||||
if (first)
|
||||
return 0; /* stb_image wants a clear code first */
|
||||
if (oldcode >= 0)
|
||||
{
|
||||
/* past 4096 a code can never be named; stb_image still
|
||||
* counts them, up to 8192 */
|
||||
if (avail < RG_CODES)
|
||||
{
|
||||
g->prefix[avail] = (int16_t)oldcode;
|
||||
g->first[avail] = g->first[oldcode];
|
||||
g->suffix[avail] = code == avail ? g->first[oldcode]
|
||||
: g->first[code];
|
||||
}
|
||||
if (++avail > 8192)
|
||||
return 0;
|
||||
}
|
||||
else if (code == avail)
|
||||
return 0;
|
||||
|
||||
rg_emit(g, r, code);
|
||||
|
||||
if ((avail & codemask) == 0 && avail <= 0x0fff)
|
||||
{
|
||||
codesize++;
|
||||
codemask = (1u << codesize) - 1;
|
||||
}
|
||||
oldcode = (int)code;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool rgif_decode(rgif_t *g, const uint8_t *data, size_t len,
|
||||
uint8_t *out, size_t stride, unsigned order)
|
||||
{
|
||||
struct rg_in in;
|
||||
unsigned w, h, flags, bgindex, eflags = 0, i;
|
||||
int transparent = -1;
|
||||
uint8_t bg[4];
|
||||
|
||||
in.p = data; in.at = 0; in.len = data ? len : 0;
|
||||
if (!g || !rg_header(&in, &w, &h))
|
||||
return false;
|
||||
flags = rg_get8(&in);
|
||||
bgindex = rg_get8(&in);
|
||||
rg_get8(&in); /* aspect ratio */
|
||||
|
||||
memset(g->pal, 0, sizeof(g->pal));
|
||||
memset(g->lpal, 0, sizeof(g->lpal));
|
||||
if (flags & 0x80)
|
||||
rg_colours(&in, g->pal, 2u << (flags & 7), -1);
|
||||
|
||||
/* Where the image does not reach: the background, opaque, unless the
|
||||
* file names entry 0 (none of it, in stb_image's reading) */
|
||||
if (bgindex > 0)
|
||||
rg_put(bg, order, g->pal[bgindex][0], g->pal[bgindex][1],
|
||||
g->pal[bgindex][2], 255);
|
||||
else
|
||||
memset(bg, 0, sizeof(bg));
|
||||
{
|
||||
unsigned y, x;
|
||||
for (y = 0; y < h; y++)
|
||||
{
|
||||
uint8_t *row = out + (size_t)y * stride;
|
||||
if (!bgindex)
|
||||
memset(row, 0, (size_t)w * 4);
|
||||
else
|
||||
for (x = 0; x < w; x++)
|
||||
memcpy(row + (size_t)x * 4, bg, 4);
|
||||
}
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
unsigned tag = rg_get8(&in);
|
||||
|
||||
if (tag == 0x2c) /* an image */
|
||||
{
|
||||
struct rg_raster r;
|
||||
unsigned x = rg_get16(&in), y = rg_get16(&in);
|
||||
unsigned iw = rg_get16(&in), ih = rg_get16(&in);
|
||||
unsigned lflags;
|
||||
uint8_t (*table)[4];
|
||||
|
||||
if (x + iw > w || y + ih > h)
|
||||
return false;
|
||||
lflags = rg_get8(&in);
|
||||
if (lflags & 0x80)
|
||||
{
|
||||
rg_colours(&in, g->lpal, 2u << (lflags & 7),
|
||||
(eflags & 1) ? transparent : -1);
|
||||
table = g->lpal;
|
||||
}
|
||||
else if (flags & 0x80)
|
||||
table = g->pal;
|
||||
else
|
||||
return false;
|
||||
|
||||
/* a transparent index, or one past the table, leaves the pixel
|
||||
* as stb_image's first frame has it: transparent black */
|
||||
for (i = 0; i < 256; i++)
|
||||
{
|
||||
if (table[i][3] > 128)
|
||||
rg_put(g->px[i], order, table[i][0], table[i][1], table[i][2],
|
||||
table[i][3]);
|
||||
else
|
||||
memset(g->px[i], 0, 4);
|
||||
}
|
||||
|
||||
r.out = out;
|
||||
r.stride = stride;
|
||||
r.x0 = x;
|
||||
r.x1 = x + iw;
|
||||
r.y0 = y;
|
||||
r.y1 = y + ih;
|
||||
r.x = x;
|
||||
r.y = iw ? y : r.y1; /* no columns: nothing to draw */
|
||||
if (lflags & 0x40)
|
||||
{
|
||||
r.step = 8;
|
||||
r.parse = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
r.step = 1;
|
||||
r.parse = 0;
|
||||
}
|
||||
r.row = out + (size_t)y * stride;
|
||||
return rg_raster(g, &in, &r) != 0;
|
||||
}
|
||||
else if (tag == 0x21) /* an extension */
|
||||
{
|
||||
unsigned n;
|
||||
if (rg_get8(&in) == 0xf9) /* graphic control */
|
||||
{
|
||||
n = rg_get8(&in);
|
||||
if (n != 4)
|
||||
{
|
||||
/* stb_image skips it and reads on from there */
|
||||
rg_skip(&in, n);
|
||||
continue;
|
||||
}
|
||||
eflags = rg_get8(&in);
|
||||
rg_get16(&in); /* delay */
|
||||
if (transparent >= 0)
|
||||
g->pal[transparent][3] = 255;
|
||||
if (eflags & 1)
|
||||
{
|
||||
transparent = (int)rg_get8(&in);
|
||||
g->pal[transparent][3] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
rg_skip(&in, 1);
|
||||
transparent = -1;
|
||||
}
|
||||
}
|
||||
while ((n = rg_get8(&in)) != 0)
|
||||
rg_skip(&in, n);
|
||||
}
|
||||
else
|
||||
return false; /* the trailer before any image, or no GIF block */
|
||||
}
|
||||
}
|
||||
|
||||
bool rgif_header_ready(const uint8_t *data, size_t len)
|
||||
{
|
||||
unsigned w, h;
|
||||
return data && len >= 13 && rgif_size(data, len, &w, &h);
|
||||
}
|
||||
|
||||
bool rgif_set_buf_ptr(rgif_t *g, void *data, size_t len)
|
||||
{
|
||||
if (!g)
|
||||
return false;
|
||||
g->buf = (const uint8_t*)data;
|
||||
g->len = len;
|
||||
g->avail = len;
|
||||
return true;
|
||||
}
|
||||
|
||||
void rgif_set_avail(rgif_t *g, size_t avail)
|
||||
{
|
||||
if (g)
|
||||
g->avail = avail < g->len ? avail : g->len;
|
||||
}
|
||||
|
||||
int rgif_process_image(rgif_t *g, void **buf, size_t size,
|
||||
unsigned *width, unsigned *height, bool supports_rgba)
|
||||
{
|
||||
unsigned w, h, order;
|
||||
size_t len;
|
||||
uint8_t *px;
|
||||
|
||||
if (!g || !g->buf || !buf)
|
||||
return IMAGE_PROCESS_ERROR;
|
||||
len = g->len ? g->len : size;
|
||||
if (g->avail < len)
|
||||
return IMAGE_PROCESS_WAIT; /* nothing taken: the whole file first */
|
||||
if (!rgif_size(g->buf, len, &w, &h) || !w || !h)
|
||||
return IMAGE_PROCESS_ERROR;
|
||||
if (!(px = (uint8_t*)malloc((size_t)w * h * 4)))
|
||||
return IMAGE_PROCESS_ERROR;
|
||||
/* the 32-bit pixel, ARGB or ABGR from the high byte, as it lies in
|
||||
* memory */
|
||||
if (is_little_endian())
|
||||
order = supports_rgba ? RGIF_RGBA : RGIF_BGRA;
|
||||
else
|
||||
order = supports_rgba ? RGIF_ABGR : RGIF_ARGB;
|
||||
if (!rgif_decode(g, g->buf, len, px, (size_t)w * 4, order))
|
||||
{
|
||||
free(px);
|
||||
return IMAGE_PROCESS_ERROR;
|
||||
}
|
||||
*buf = px;
|
||||
*width = w;
|
||||
*height = h;
|
||||
return IMAGE_PROCESS_END;
|
||||
}
|
||||
@@ -100,6 +100,12 @@ enum image_type_enum image_texture_get_type(const char *path)
|
||||
(ext[2] | 0x20) == 'a')
|
||||
return IMAGE_TYPE_TGA;
|
||||
#endif
|
||||
#ifdef HAVE_RGIF
|
||||
if ((ext[0] | 0x20) == 'g' &&
|
||||
(ext[1] | 0x20) == 'i' &&
|
||||
(ext[2] | 0x20) == 'f')
|
||||
return IMAGE_TYPE_GIF;
|
||||
#endif
|
||||
#ifdef HAVE_RDDS
|
||||
if ((ext[0] | 0x20) == 'd' &&
|
||||
(ext[1] | 0x20) == 'd' &&
|
||||
@@ -107,6 +113,10 @@ enum image_type_enum image_texture_get_type(const char *path)
|
||||
return IMAGE_TYPE_DDS;
|
||||
#endif
|
||||
#ifdef HAVE_RMP4
|
||||
if ((ext[0] | 0x20) == 'm' &&
|
||||
(ext[1] | 0x20) == 'o' &&
|
||||
(ext[2] | 0x20) == 'v')
|
||||
return IMAGE_TYPE_MP4;
|
||||
if ((ext[0] | 0x20) == 'm' &&
|
||||
ext[1] == '4' &&
|
||||
(ext[2] | 0x20) == 'v')
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
#ifdef HAVE_RTGA
|
||||
#include <formats/rtga.h>
|
||||
#endif
|
||||
#ifdef HAVE_RGIF
|
||||
#include <formats/rgif.h>
|
||||
#endif
|
||||
#ifdef HAVE_RBMP
|
||||
#include <formats/rbmp.h>
|
||||
#endif
|
||||
@@ -68,27 +71,27 @@
|
||||
* Y = forwarded to the backend, . = returns the unsupported answer,
|
||||
* s = constant stub (see the notes below):
|
||||
*
|
||||
* PNG JPG BMP TGA WBP DDS WBM MP4
|
||||
* new / free / set_buf_ptr Y Y Y Y Y Y Y Y
|
||||
* start Y Y s s s s s s
|
||||
* is_valid Y Y s s s s s s
|
||||
* process Y Y Y Y Y Y Y Y
|
||||
* process slices (returns NEXT) Y Y Y Y Y Y Y Y
|
||||
* iterate Y Y . . . . . .
|
||||
* need_more Y Y . . . . . .
|
||||
* set_avail Y Y . . . . Y Y
|
||||
* get_gpu_layout . . . . . Y . .
|
||||
* set_want_10bit / is_10bit Y . . . . . Y Y
|
||||
* detach_anim_stream . . . . . . Y Y
|
||||
* anim_* (whole buffer) . . . . Y . . .
|
||||
* anim_stream_new Y . . . Y . Y Y
|
||||
* anim_stream_new_avail Y . . . . . Y Y
|
||||
* PNG JPG BMP TGA WBP DDS WBM MP4 GIF
|
||||
* new / free / set_buf_ptr Y Y Y Y Y Y Y Y Y
|
||||
* start Y Y s s s s s s s
|
||||
* is_valid Y Y s s s s s s s
|
||||
* process Y Y Y Y Y Y Y Y Y
|
||||
* process slices (returns NEXT) Y Y Y Y Y Y Y Y .
|
||||
* iterate Y Y . . . . . . .
|
||||
* need_more Y Y . . . . . . .
|
||||
* set_avail Y Y . . . . Y Y Y
|
||||
* get_gpu_layout . . . . . Y . . .
|
||||
* set_want_10bit / is_10bit Y . . . . . Y Y .
|
||||
* detach_anim_stream . . . . . . Y Y .
|
||||
* anim_* (whole buffer) . . . . Y . . . .
|
||||
* anim_stream_new Y . . . Y . Y Y .
|
||||
* anim_stream_new_avail Y . . . . . Y Y .
|
||||
* anim_stream_free / _get_info /
|
||||
* _next / _rewind / _set_argb Y . . . Y . Y Y
|
||||
* anim_stream_set_avail Y . . . . . Y Y
|
||||
* _next / _rewind / _set_argb Y . . . Y . Y Y .
|
||||
* anim_stream_set_avail Y . . . . . Y Y .
|
||||
* anim_stream_media_floor /
|
||||
* _consumed . . . . . . Y Y
|
||||
* anim_stream_complete_scan . . . . . . Y .
|
||||
* _consumed . . . . . . Y Y .
|
||||
* anim_stream_complete_scan . . . . . . Y . .
|
||||
*
|
||||
* The gaps, in the order a caller runs into them:
|
||||
*
|
||||
@@ -115,7 +118,8 @@
|
||||
* - set_avail (the still-image byte wall) is honoured by PNG and JPEG,
|
||||
* where it surfaces as need_more(), and by TGA, WEBM and MP4, where
|
||||
* it surfaces as IMAGE_PROCESS_WAIT out of process(), and by BMP the
|
||||
* same way. WEBP and DDS have no partial-buffer decode and must be
|
||||
* same way. GIF waits the same way, but for the whole file: it
|
||||
* decodes its first image in one process() call once it is all in. WEBP and DDS have no partial-buffer decode and must be
|
||||
* handed fully resident data: a WEBP still is a VP8 keyframe, which
|
||||
* cannot be decoded in part at all.
|
||||
*
|
||||
@@ -168,6 +172,11 @@ void image_transfer_free(void *data, enum image_type_enum type)
|
||||
case IMAGE_TYPE_TGA:
|
||||
#ifdef HAVE_RTGA
|
||||
rtga_free((rtga_t*)data);
|
||||
#endif
|
||||
break;
|
||||
case IMAGE_TYPE_GIF:
|
||||
#ifdef HAVE_RGIF
|
||||
rgif_free((rgif_t*)data);
|
||||
#endif
|
||||
break;
|
||||
case IMAGE_TYPE_PNG:
|
||||
@@ -235,6 +244,12 @@ void *image_transfer_new(enum image_type_enum type)
|
||||
return rtga_alloc();
|
||||
#else
|
||||
break;
|
||||
#endif
|
||||
case IMAGE_TYPE_GIF:
|
||||
#ifdef HAVE_RGIF
|
||||
return rgif_new();
|
||||
#else
|
||||
break;
|
||||
#endif
|
||||
case IMAGE_TYPE_BMP:
|
||||
#ifdef HAVE_RBMP
|
||||
@@ -299,6 +314,12 @@ bool image_transfer_start(void *data, enum image_type_enum type)
|
||||
return true;
|
||||
#else
|
||||
break;
|
||||
#endif
|
||||
case IMAGE_TYPE_GIF:
|
||||
#ifdef HAVE_RGIF
|
||||
return true;
|
||||
#else
|
||||
break;
|
||||
#endif
|
||||
case IMAGE_TYPE_BMP:
|
||||
#ifdef HAVE_RBMP
|
||||
@@ -360,6 +381,12 @@ bool image_transfer_is_valid(
|
||||
return true;
|
||||
#else
|
||||
break;
|
||||
#endif
|
||||
case IMAGE_TYPE_GIF:
|
||||
#ifdef HAVE_RGIF
|
||||
return true;
|
||||
#else
|
||||
break;
|
||||
#endif
|
||||
case IMAGE_TYPE_BMP:
|
||||
#ifdef HAVE_RBMP
|
||||
@@ -419,6 +446,11 @@ void image_transfer_set_buffer_ptr(
|
||||
case IMAGE_TYPE_TGA:
|
||||
#ifdef HAVE_RTGA
|
||||
rtga_set_buf_ptr((rtga_t*)data, (uint8_t*)ptr);
|
||||
#endif
|
||||
break;
|
||||
case IMAGE_TYPE_GIF:
|
||||
#ifdef HAVE_RGIF
|
||||
rgif_set_buf_ptr((rgif_t*)data, (uint8_t*)ptr, len);
|
||||
#endif
|
||||
break;
|
||||
case IMAGE_TYPE_BMP:
|
||||
@@ -486,6 +518,14 @@ int image_transfer_process(
|
||||
break;
|
||||
#else
|
||||
break;
|
||||
#endif
|
||||
case IMAGE_TYPE_GIF:
|
||||
#ifdef HAVE_RGIF
|
||||
ret = rgif_process_image((rgif_t*)data,
|
||||
(void**)buf, len, width, height, supports_rgba);
|
||||
break;
|
||||
#else
|
||||
break;
|
||||
#endif
|
||||
case IMAGE_TYPE_BMP:
|
||||
#ifdef HAVE_RBMP
|
||||
@@ -703,6 +743,7 @@ bool image_transfer_iterate(void *data, enum image_type_enum type)
|
||||
case IMAGE_TYPE_DDS:
|
||||
case IMAGE_TYPE_WEBM:
|
||||
case IMAGE_TYPE_MP4:
|
||||
case IMAGE_TYPE_GIF:
|
||||
case IMAGE_TYPE_NONE:
|
||||
break;
|
||||
}
|
||||
@@ -746,6 +787,11 @@ void image_transfer_set_avail(void *data, enum image_type_enum type,
|
||||
case IMAGE_TYPE_TGA:
|
||||
#ifdef HAVE_RTGA
|
||||
rtga_set_avail((rtga_t*)data, avail);
|
||||
#endif
|
||||
break;
|
||||
case IMAGE_TYPE_GIF:
|
||||
#ifdef HAVE_RGIF
|
||||
rgif_set_avail((rgif_t*)data, avail);
|
||||
#endif
|
||||
break;
|
||||
case IMAGE_TYPE_BMP:
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
* memory-resident files. Leading tags are stepped over by the
|
||||
* frame-sync scan rather than parsed.
|
||||
*
|
||||
* What it does not implement: ID3/APE tag parsing, Xing/Info/LAME
|
||||
* header interpretation (VBR duration estimates and gapless
|
||||
* delay/padding trimming are the caller's concern), and encoding.
|
||||
* The Xing/Info and VBRI tag frames are read by rmp3_parse_vbr_tag; the
|
||||
* pull interface uses them for gapless playback, dropping the tag frame,
|
||||
* the encoder delay and padding the LAME extension gives, and the
|
||||
* decoder's own delay. The stream interface leaves that to its caller.
|
||||
*
|
||||
* What it does not implement: ID3/APE tag parsing, and encoding.
|
||||
*
|
||||
* Two interfaces share the decode core. The original pull interface
|
||||
* (rmp3_init_memory / rmp3_read_s16 / rmp3_read_f32 /
|
||||
@@ -2624,8 +2627,12 @@ static uint32_t rmp3_decode_next_frame(rmp3* pMP3)
|
||||
|
||||
if (samples != 0)
|
||||
{
|
||||
pMP3->framesConsumed = 0;
|
||||
pMP3->framesRemaining = samples;
|
||||
/* The start the encoder and the synthesis added is dropped. */
|
||||
uint32_t skip = pMP3->skipLeft < samples
|
||||
? (uint32_t)pMP3->skipLeft : samples;
|
||||
pMP3->skipLeft -= skip;
|
||||
pMP3->framesConsumed = skip;
|
||||
pMP3->framesRemaining = samples - skip;
|
||||
pMP3->frameChannels = (uint32_t)info.channels;
|
||||
if (pMP3->sampleRate == 0)
|
||||
{
|
||||
@@ -2633,6 +2640,8 @@ static uint32_t rmp3_decode_next_frame(rmp3* pMP3)
|
||||
pMP3->channels = (uint32_t)info.channels;
|
||||
pMP3->sampleRate = (uint32_t)info.hz;
|
||||
}
|
||||
if (pMP3->framesRemaining == 0)
|
||||
continue;
|
||||
return 1;
|
||||
}
|
||||
/* samples == 0 with frame_bytes > 0: junk was skipped; continue. */
|
||||
@@ -2726,6 +2735,108 @@ static void rmp3_set_output_mode(rmp3* pMP3, uint32_t f32)
|
||||
* pMP3->frameChannels, f32);
|
||||
}
|
||||
|
||||
static uint32_t rmp3_be32(const uint8_t *p)
|
||||
{
|
||||
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16)
|
||||
| ((uint32_t)p[2] << 8) | p[3];
|
||||
}
|
||||
|
||||
int rmp3_parse_vbr_tag(const uint8_t *frame, size_t frame_bytes,
|
||||
rmp3_vbr_tag *tag)
|
||||
{
|
||||
const uint8_t *p;
|
||||
const uint8_t *end;
|
||||
uint32_t flags;
|
||||
size_t side;
|
||||
|
||||
if (!frame || !tag || frame_bytes < RMP3_HDR_SIZE
|
||||
|| !rmp3_hdr_valid(frame))
|
||||
return 0;
|
||||
memset(tag, 0, sizeof(*tag));
|
||||
tag->frame_samples = rmp3_hdr_frame_samples(frame);
|
||||
end = frame + frame_bytes;
|
||||
|
||||
/* VBRI sits 32 bytes past the header, whatever the mode. */
|
||||
p = frame + RMP3_HDR_SIZE + 32;
|
||||
if (p + 18 <= end && !memcmp(p, "VBRI", 4))
|
||||
{
|
||||
tag->is_vbri = 1;
|
||||
tag->bytes = rmp3_be32(p + 10);
|
||||
tag->frames = rmp3_be32(p + 14);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Xing and Info follow the side information. */
|
||||
if (RMP3_HDR_TEST_MPEG1(frame))
|
||||
side = RMP3_HDR_IS_MONO(frame) ? 17 : 32;
|
||||
else
|
||||
side = RMP3_HDR_IS_MONO(frame) ? 9 : 17;
|
||||
p = frame + RMP3_HDR_SIZE + (RMP3_HDR_IS_CRC(frame) ? 2 : 0) + side;
|
||||
if (p + 8 > end || (memcmp(p, "Xing", 4) && memcmp(p, "Info", 4)))
|
||||
return 0;
|
||||
flags = rmp3_be32(p + 4);
|
||||
p += 8;
|
||||
if (flags & 1)
|
||||
{
|
||||
if (p + 4 > end)
|
||||
return 1;
|
||||
tag->frames = rmp3_be32(p);
|
||||
p += 4;
|
||||
}
|
||||
if (flags & 2)
|
||||
{
|
||||
if (p + 4 > end)
|
||||
return 1;
|
||||
tag->bytes = rmp3_be32(p);
|
||||
p += 4;
|
||||
}
|
||||
if (flags & 4)
|
||||
p += 100; /* seek table */
|
||||
if (flags & 8)
|
||||
p += 4; /* quality */
|
||||
|
||||
/* LAME's extension: a 9-byte encoder name, then the delay and the
|
||||
* padding as two 12-bit fields 21 bytes in. */
|
||||
if (p + 24 <= end && (!memcmp(p, "LAME", 4)
|
||||
|| !memcmp(p, "Lavf", 4) || !memcmp(p, "Lavc", 4)))
|
||||
{
|
||||
tag->encoder_delay = ((uint32_t)p[21] << 4) | (p[22] >> 4);
|
||||
tag->encoder_padding = ((uint32_t)(p[22] & 0x0F) << 8) | p[23];
|
||||
tag->has_delay = 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Reads the tag frame at the stream's first frame, if there is one, and
|
||||
* sets the reads to skip it and trim what the encoder added. */
|
||||
static void rmp3_setup_gapless(rmp3 *pMP3)
|
||||
{
|
||||
rmp3_vbr_tag tag;
|
||||
int free_format_bytes = 0;
|
||||
int frame_bytes = 0;
|
||||
int avail = pMP3->dataSize > (size_t)INT_MAX
|
||||
? INT_MAX : (int)pMP3->dataSize;
|
||||
int i = rmp3d_find_frame(pMP3->pData, avail,
|
||||
&free_format_bytes, &frame_bytes);
|
||||
|
||||
if (!frame_bytes || i + frame_bytes > avail
|
||||
|| !rmp3_parse_vbr_tag(pMP3->pData + i, (size_t)frame_bytes, &tag))
|
||||
return;
|
||||
pMP3->dataStart = (size_t)i + (size_t)frame_bytes;
|
||||
pMP3->readPos = pMP3->dataStart;
|
||||
if (!tag.has_delay)
|
||||
return;
|
||||
pMP3->startSkip = (uint64_t)tag.encoder_delay + RMP3_DECODER_DELAY;
|
||||
pMP3->skipLeft = pMP3->startSkip;
|
||||
if (tag.frames)
|
||||
{
|
||||
uint64_t samples = (uint64_t)tag.frames * tag.frame_samples;
|
||||
uint64_t added = (uint64_t)tag.encoder_delay + tag.encoder_padding;
|
||||
if (samples > added)
|
||||
pMP3->totalFrames = samples - added;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t rmp3_init_memory(rmp3* pMP3, const void* pData, size_t dataSize)
|
||||
{
|
||||
if (!pMP3)
|
||||
@@ -2758,6 +2869,7 @@ uint32_t rmp3_init_memory(rmp3* pMP3, const void* pData, size_t dataSize)
|
||||
pMP3->sampleRate = (uint32_t)info.hz;
|
||||
}
|
||||
|
||||
rmp3_setup_gapless(pMP3);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -2777,6 +2889,15 @@ uint64_t rmp3_read_f32(rmp3* pMP3, uint64_t framesToRead, float* pBufferOut)
|
||||
|
||||
rmp3_set_output_mode(pMP3, 1);
|
||||
|
||||
/* Nothing past the track's end: the encoder's padding. */
|
||||
if (pMP3->totalFrames)
|
||||
{
|
||||
uint64_t left = pMP3->totalFrames > pMP3->framesRead
|
||||
? pMP3->totalFrames - pMP3->framesRead : 0;
|
||||
if (framesToRead > left)
|
||||
framesToRead = left;
|
||||
}
|
||||
|
||||
while (framesToRead > 0)
|
||||
{
|
||||
/* Drain the currently decoded frame (native float samples). */
|
||||
@@ -2820,6 +2941,7 @@ uint64_t rmp3_read_f32(rmp3* pMP3, uint64_t framesToRead, float* pBufferOut)
|
||||
break;
|
||||
}
|
||||
|
||||
pMP3->framesRead += totalFramesRead;
|
||||
return totalFramesRead;
|
||||
}
|
||||
|
||||
@@ -2835,6 +2957,15 @@ uint64_t rmp3_read_s16(rmp3* pMP3, uint64_t framesToRead, int16_t* pBufferOut)
|
||||
|
||||
rmp3_set_output_mode(pMP3, 0);
|
||||
|
||||
/* Nothing past the track's end: the encoder's padding. */
|
||||
if (pMP3->totalFrames)
|
||||
{
|
||||
uint64_t left = pMP3->totalFrames > pMP3->framesRead
|
||||
? pMP3->totalFrames - pMP3->framesRead : 0;
|
||||
if (framesToRead > left)
|
||||
framesToRead = left;
|
||||
}
|
||||
|
||||
while (framesToRead > 0)
|
||||
{
|
||||
while (pMP3->framesRemaining > 0 && framesToRead > 0)
|
||||
@@ -2873,6 +3004,7 @@ uint64_t rmp3_read_s16(rmp3* pMP3, uint64_t framesToRead, int16_t* pBufferOut)
|
||||
break;
|
||||
}
|
||||
|
||||
pMP3->framesRead += totalFramesRead;
|
||||
return totalFramesRead;
|
||||
}
|
||||
|
||||
@@ -2884,9 +3016,11 @@ uint32_t rmp3_seek_to_frame(rmp3* pMP3, uint64_t frameIndex)
|
||||
/* Rewind to the start of the stream and decode-skip forward. MP3
|
||||
* frames depend on the bit reservoir of their predecessors, so
|
||||
* decoding from the top is the only sample-accurate way in. */
|
||||
pMP3->readPos = 0;
|
||||
pMP3->readPos = pMP3->dataStart;
|
||||
pMP3->framesConsumed = 0;
|
||||
pMP3->framesRemaining = 0;
|
||||
pMP3->framesRead = 0;
|
||||
pMP3->skipLeft = pMP3->startSkip;
|
||||
pMP3->atEnd = 0;
|
||||
pMP3->decoder.header[0] = 0;
|
||||
|
||||
|
||||
@@ -0,0 +1,744 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (rpng_rows.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.
|
||||
*/
|
||||
|
||||
/* A PNG a row at a time; see <formats/rpng_rows.h>. */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <encodings/crc32.h>
|
||||
#include <encodings/deflate.h>
|
||||
#include <formats/rpng_rows.h>
|
||||
|
||||
/* Where the decoder is in the file */
|
||||
enum
|
||||
{
|
||||
RR_SIG = 0, /* the 8-byte signature */
|
||||
RR_HDR, /* a chunk's length and type */
|
||||
RR_DATA, /* its data */
|
||||
RR_CRC, /* its CRC */
|
||||
RR_END, /* after IEND */
|
||||
RR_FAILED
|
||||
};
|
||||
|
||||
/* What is done with a chunk's data */
|
||||
enum
|
||||
{
|
||||
RR_KEEP = 0, /* copied to keep[], read at its end */
|
||||
RR_SKIP, /* checked and dropped */
|
||||
RR_IMAGE /* IDAT: inflated into rows */
|
||||
};
|
||||
|
||||
#define RR_KEEP_MAX 768 /* the biggest chunk read: PLTE */
|
||||
#define RR_DIM_MAX 0x7fffffffu
|
||||
|
||||
#define RR_TYPE(a, b, c, d) \
|
||||
(((uint32_t)(a) << 24) | ((uint32_t)(b) << 16) | ((uint32_t)(c) << 8) | (uint32_t)(d))
|
||||
|
||||
struct rpng_rows
|
||||
{
|
||||
struct rpng_rows_info info;
|
||||
|
||||
int stage;
|
||||
int mode; /* RR_KEEP, RR_SKIP or RR_IMAGE */
|
||||
uint32_t type; /* the chunk's */
|
||||
uint32_t left; /* bytes of it to go */
|
||||
uint32_t crc; /* of its type and data so far */
|
||||
uint32_t keep_len;
|
||||
uint8_t head[8]; /* signature, chunk header or CRC */
|
||||
unsigned head_fill;
|
||||
uint8_t keep[RR_KEEP_MAX];
|
||||
|
||||
int have_ihdr;
|
||||
int have_plte;
|
||||
unsigned colour_n; /* colour chunks taken */
|
||||
int header_sent;
|
||||
int idat_done; /* a chunk other than IDAT came after IDATs */
|
||||
int image_done; /* every row is out */
|
||||
int inflate_done;
|
||||
|
||||
/* the image data */
|
||||
void *inflate;
|
||||
uint8_t *rows; /* two rows, each with its filter byte */
|
||||
size_t rows_cap;
|
||||
uint8_t *cur; /* the row being inflated */
|
||||
uint8_t *prev; /* the row before it in this pass */
|
||||
size_t fill; /* bytes of cur so far */
|
||||
size_t rowbytes; /* of this pass's rows, without the filter byte */
|
||||
unsigned bpp; /* bytes a pixel, at least 1, for the filters */
|
||||
unsigned pass; /* 0..6 for Adam7, 0 otherwise */
|
||||
uint32_t pass_width;
|
||||
uint32_t pass_height;
|
||||
uint32_t pass_row; /* rows of the pass out so far */
|
||||
int pass_end; /* the last row out ended its pass */
|
||||
};
|
||||
|
||||
static const uint8_t rr_x0[7] = { 0, 4, 0, 2, 0, 1, 0 };
|
||||
static const uint8_t rr_y0[7] = { 0, 0, 4, 0, 2, 0, 1 };
|
||||
static const uint8_t rr_dx[7] = { 8, 8, 4, 4, 2, 2, 1 };
|
||||
static const uint8_t rr_dy[7] = { 8, 8, 8, 4, 4, 2, 2 };
|
||||
|
||||
static uint32_t rr_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 unsigned rr_bits(const struct rpng_rows_info *info)
|
||||
{
|
||||
return (unsigned)info->depth * info->channels;
|
||||
}
|
||||
|
||||
size_t rpng_rows_rowbytes(const struct rpng_rows_info *info, uint32_t width)
|
||||
{
|
||||
return ((size_t)width * rr_bits(info) + 7) / 8;
|
||||
}
|
||||
|
||||
rpng_rows_t *rpng_rows_new(void)
|
||||
{
|
||||
return (rpng_rows_t*)calloc(1, sizeof(rpng_rows_t));
|
||||
}
|
||||
|
||||
void rpng_rows_free(rpng_rows_t *z)
|
||||
{
|
||||
if (!z)
|
||||
return;
|
||||
rinflate_free(z->inflate);
|
||||
free(z->rows);
|
||||
free(z);
|
||||
}
|
||||
|
||||
void rpng_rows_reset(rpng_rows_t *z)
|
||||
{
|
||||
void *inflate = z->inflate;
|
||||
uint8_t *rows = z->rows;
|
||||
size_t rows_cap = z->rows_cap;
|
||||
memset(z, 0, sizeof(*z));
|
||||
z->inflate = inflate;
|
||||
z->rows = rows;
|
||||
z->rows_cap = rows_cap;
|
||||
}
|
||||
|
||||
const struct rpng_rows_info *rpng_rows_info(const rpng_rows_t *z)
|
||||
{
|
||||
return &z->info;
|
||||
}
|
||||
|
||||
/* The next pass with any pixels, from z->pass on; returns 0 when there is
|
||||
* none left */
|
||||
static int rr_pass_begin(rpng_rows_t *z)
|
||||
{
|
||||
const struct rpng_rows_info *info = &z->info;
|
||||
for (; z->pass < 7; z->pass++)
|
||||
{
|
||||
if (!info->interlace)
|
||||
{
|
||||
z->pass_width = info->width;
|
||||
z->pass_height = info->height;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned p = z->pass;
|
||||
z->pass_width = info->width > rr_x0[p]
|
||||
? (info->width - rr_x0[p] + rr_dx[p] - 1) / rr_dx[p] : 0;
|
||||
z->pass_height = info->height > rr_y0[p]
|
||||
? (info->height - rr_y0[p] + rr_dy[p] - 1) / rr_dy[p] : 0;
|
||||
}
|
||||
if (z->pass_width && z->pass_height)
|
||||
{
|
||||
z->rowbytes = rpng_rows_rowbytes(info, z->pass_width);
|
||||
z->pass_row = 0;
|
||||
z->fill = 0;
|
||||
/* the row above the first is all zeroes */
|
||||
memset(z->prev, 0, z->rowbytes + 1);
|
||||
return 1;
|
||||
}
|
||||
if (!info->interlace)
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rr_paeth(int a, int b, int c)
|
||||
{
|
||||
int p = a + b - c;
|
||||
int pa = p > a ? p - a : a - p;
|
||||
int pb = p > b ? p - b : b - p;
|
||||
int pc = p > c ? p - c : c - p;
|
||||
if (pa <= pb && pa <= pc)
|
||||
return a;
|
||||
return pb <= pc ? b : c;
|
||||
}
|
||||
|
||||
/* Undoes cur's filter against prev; returns 0 for a filter that is not one */
|
||||
static int rr_unfilter(rpng_rows_t *z)
|
||||
{
|
||||
uint8_t *r = z->cur + 1;
|
||||
const uint8_t *p = z->prev + 1;
|
||||
size_t n = z->rowbytes;
|
||||
size_t bpp = z->bpp;
|
||||
size_t i;
|
||||
|
||||
switch (z->cur[0])
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
for (i = bpp; i < n; i++)
|
||||
r[i] = (uint8_t)(r[i] + r[i - bpp]);
|
||||
break;
|
||||
case 2:
|
||||
for (i = 0; i < n; i++)
|
||||
r[i] = (uint8_t)(r[i] + p[i]);
|
||||
break;
|
||||
case 3:
|
||||
for (i = 0; i < bpp && i < n; i++)
|
||||
r[i] = (uint8_t)(r[i] + (p[i] >> 1));
|
||||
for (; i < n; i++)
|
||||
r[i] = (uint8_t)(r[i] + ((r[i - bpp] + p[i]) >> 1));
|
||||
break;
|
||||
case 4:
|
||||
for (i = 0; i < bpp && i < n; i++)
|
||||
r[i] = (uint8_t)(r[i] + p[i]);
|
||||
for (; i < n; i++)
|
||||
r[i] = (uint8_t)(r[i] + rr_paeth(r[i - bpp], p[i], p[i - bpp]));
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* A colour chunk, from keep[]: dropped where libpng drops it */
|
||||
static void rr_colour(rpng_rows_t *z)
|
||||
{
|
||||
struct rpng_rows_info *info = &z->info;
|
||||
const uint8_t *k = z->keep;
|
||||
uint32_t len = z->keep_len;
|
||||
uint32_t bit;
|
||||
uint8_t *at;
|
||||
unsigned i;
|
||||
|
||||
switch (z->type)
|
||||
{
|
||||
case RR_TYPE('g', 'A', 'M', 'A'):
|
||||
if (len != 4 || (k[0] & 0x80u))
|
||||
return;
|
||||
bit = RPNG_ROWS_GAMA;
|
||||
at = &info->gama_at;
|
||||
if (info->chunks & bit)
|
||||
return;
|
||||
info->gama = rr_be32(k);
|
||||
break;
|
||||
case RR_TYPE('c', 'H', 'R', 'M'):
|
||||
if (len != 32)
|
||||
return;
|
||||
for (i = 0; i < 8; i++)
|
||||
if (rr_be32(k + i * 4) == 0x80000000u)
|
||||
return; /* no 32-bit value is its negative */
|
||||
bit = RPNG_ROWS_CHRM;
|
||||
at = &info->chrm_at;
|
||||
if (info->chunks & bit)
|
||||
return;
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
uint32_t v = rr_be32(k + i * 4);
|
||||
info->chrm[i] = (v & 0x80000000u)
|
||||
? -(int32_t)(~v + 1u) : (int32_t)v;
|
||||
}
|
||||
break;
|
||||
case RR_TYPE('s', 'R', 'G', 'B'):
|
||||
if (len != 1 || k[0] > 3)
|
||||
return;
|
||||
bit = RPNG_ROWS_SRGB;
|
||||
at = &info->srgb_at;
|
||||
if (info->chunks & bit)
|
||||
return;
|
||||
info->srgb = k[0];
|
||||
break;
|
||||
case RR_TYPE('s', 'B', 'I', 'T'):
|
||||
{
|
||||
unsigned n = info->color_type == 3 ? 3u : info->channels;
|
||||
unsigned max = info->color_type == 3 ? 8u : info->depth;
|
||||
if (len != n)
|
||||
return;
|
||||
for (i = 0; i < n; i++)
|
||||
if (!k[i] || k[i] > max)
|
||||
return;
|
||||
bit = RPNG_ROWS_SBIT;
|
||||
at = &info->sbit_at;
|
||||
if (info->chunks & bit)
|
||||
return;
|
||||
memcpy(info->sbit, k, n);
|
||||
break;
|
||||
}
|
||||
case RR_TYPE('c', 'I', 'C', 'P'):
|
||||
if (len != 4)
|
||||
return;
|
||||
bit = RPNG_ROWS_CICP;
|
||||
at = &info->cicp_at;
|
||||
if (info->chunks & bit)
|
||||
return;
|
||||
memcpy(info->cicp, k, 4);
|
||||
break;
|
||||
default: /* mDCV */
|
||||
if (len != 24)
|
||||
return;
|
||||
bit = RPNG_ROWS_MDCV;
|
||||
at = &info->mdcv_at;
|
||||
if (info->chunks & bit)
|
||||
return;
|
||||
for (i = 0; i < 8; i++)
|
||||
info->mdcv[i] = (uint16_t)((k[i * 2] << 8) | k[i * 2 + 1]);
|
||||
info->mdcv_luminance[0] = rr_be32(k + 16);
|
||||
info->mdcv_luminance[1] = rr_be32(k + 20);
|
||||
break;
|
||||
}
|
||||
info->chunks |= bit;
|
||||
*at = (uint8_t)++z->colour_n;
|
||||
}
|
||||
|
||||
/* IHDR, from keep[] */
|
||||
static int rr_ihdr(rpng_rows_t *z)
|
||||
{
|
||||
struct rpng_rows_info *info = &z->info;
|
||||
const uint8_t *d = z->keep;
|
||||
unsigned bits;
|
||||
|
||||
if (z->have_ihdr || z->keep_len != 13)
|
||||
return 0;
|
||||
info->width = rr_be32(d);
|
||||
info->height = rr_be32(d + 4);
|
||||
info->depth = d[8];
|
||||
info->color_type = d[9];
|
||||
info->interlace = d[12];
|
||||
if (!info->width || !info->height || info->width > RR_DIM_MAX
|
||||
|| info->height > RR_DIM_MAX || d[10] || d[11] || d[12] > 1)
|
||||
return 0;
|
||||
switch (info->color_type)
|
||||
{
|
||||
case 0:
|
||||
info->channels = 1;
|
||||
bits = 1 | 2 | 4 | 8 | 16;
|
||||
break;
|
||||
case 3:
|
||||
info->channels = 1;
|
||||
bits = 1 | 2 | 4 | 8;
|
||||
break;
|
||||
case 2:
|
||||
info->channels = 3;
|
||||
bits = 8 | 16;
|
||||
break;
|
||||
case 4:
|
||||
info->channels = 2;
|
||||
bits = 8 | 16;
|
||||
break;
|
||||
case 6:
|
||||
info->channels = 4;
|
||||
bits = 8 | 16;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
if ((info->depth & (info->depth - 1)) || !(bits & info->depth))
|
||||
return 0;
|
||||
/* a row's bytes, and two of them with their filter bytes, must fit */
|
||||
if ((size_t)info->width > ((size_t)-1 - 16) / 2 / 8 / rr_bits(info))
|
||||
return 0;
|
||||
z->bpp = rr_bits(info) >= 8 ? rr_bits(info) / 8 : 1;
|
||||
z->have_ihdr = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* The end of a chunk the decoder kept or checked, its CRC right: what it
|
||||
* says goes into info. Returns 0 for a broken critical chunk. */
|
||||
static int rr_chunk_end(rpng_rows_t *z)
|
||||
{
|
||||
struct rpng_rows_info *info = &z->info;
|
||||
uint32_t bit = 0;
|
||||
int before_plte = 0;
|
||||
|
||||
switch (z->type)
|
||||
{
|
||||
case RR_TYPE('I', 'H', 'D', 'R'):
|
||||
return rr_ihdr(z);
|
||||
case RR_TYPE('P', 'L', 'T', 'E'):
|
||||
if (z->have_plte || z->header_sent || !z->keep_len || z->keep_len % 3
|
||||
|| z->keep_len / 3 > (1u << (info->color_type == 3 ? info->depth : 8)))
|
||||
return info->color_type != 3; /* only an error where it is needed */
|
||||
if (info->color_type == 0 || info->color_type == 4)
|
||||
return 1; /* a palette on a gray image is ignored */
|
||||
memcpy(info->palette, z->keep, z->keep_len);
|
||||
info->palette_len = (uint16_t)(z->keep_len / 3);
|
||||
z->have_plte = 1;
|
||||
info->chunks |= RPNG_ROWS_PLTE;
|
||||
return 1;
|
||||
case RR_TYPE('t', 'R', 'N', 'S'):
|
||||
if (z->header_sent || (info->chunks & RPNG_ROWS_TRNS))
|
||||
return 1;
|
||||
if (info->color_type == 3)
|
||||
{
|
||||
if (!z->have_plte || !z->keep_len || z->keep_len > info->palette_len)
|
||||
return 1;
|
||||
}
|
||||
else if (z->keep_len != (info->color_type == 0 ? 2u
|
||||
: info->color_type == 2 ? 6u : 0u) || !z->keep_len)
|
||||
return 1;
|
||||
memcpy(info->trns, z->keep, z->keep_len);
|
||||
info->trns_len = (uint16_t)z->keep_len;
|
||||
info->chunks |= RPNG_ROWS_TRNS;
|
||||
return 1;
|
||||
case RR_TYPE('t', 'E', 'X', 't'):
|
||||
case RR_TYPE('z', 'T', 'X', 't'):
|
||||
case RR_TYPE('i', 'T', 'X', 't'):
|
||||
info->text_chunks++;
|
||||
return 1;
|
||||
case RR_TYPE('g', 'A', 'M', 'A'):
|
||||
case RR_TYPE('c', 'H', 'R', 'M'):
|
||||
case RR_TYPE('s', 'R', 'G', 'B'):
|
||||
case RR_TYPE('s', 'B', 'I', 'T'):
|
||||
case RR_TYPE('c', 'I', 'C', 'P'):
|
||||
case RR_TYPE('m', 'D', 'C', 'V'):
|
||||
/* before PLTE and the image data */
|
||||
if (!z->header_sent && !z->have_plte)
|
||||
rr_colour(z);
|
||||
return 1;
|
||||
case RR_TYPE('i', 'C', 'C', 'P'): bit = RPNG_ROWS_ICCP; before_plte = 1; break;
|
||||
case RR_TYPE('b', 'K', 'G', 'D'): bit = RPNG_ROWS_BKGD; break;
|
||||
case RR_TYPE('h', 'I', 'S', 'T'):
|
||||
if (!z->have_plte)
|
||||
return 1;
|
||||
bit = RPNG_ROWS_HIST;
|
||||
break;
|
||||
case RR_TYPE('p', 'H', 'Y', 's'): bit = RPNG_ROWS_PHYS; break;
|
||||
case RR_TYPE('o', 'F', 'F', 's'): bit = RPNG_ROWS_OFFS; break;
|
||||
case RR_TYPE('p', 'C', 'A', 'L'): bit = RPNG_ROWS_PCAL; break;
|
||||
case RR_TYPE('s', 'C', 'A', 'L'): bit = RPNG_ROWS_SCAL; break;
|
||||
case RR_TYPE('s', 'P', 'L', 'T'): bit = RPNG_ROWS_SPLT; break;
|
||||
case RR_TYPE('t', 'I', 'M', 'E'):
|
||||
info->chunks |= RPNG_ROWS_TIME;
|
||||
return 1;
|
||||
/* known, not reported */
|
||||
case RR_TYPE('e', 'X', 'I', 'f'):
|
||||
case RR_TYPE('c', 'L', 'L', 'I'):
|
||||
return 1;
|
||||
default:
|
||||
/* an unknown chunk; critical ones fail at their header */
|
||||
if (z->type & 0x20u)
|
||||
info->unknown_chunks++;
|
||||
return 1;
|
||||
}
|
||||
/* ancillary chunks with a place: before PLTE, or before IDAT */
|
||||
if (z->header_sent || (before_plte && z->have_plte))
|
||||
return 1;
|
||||
info->chunks |= bit;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* A chunk header in head[]; sets up how its data is taken */
|
||||
static int rr_chunk_begin(rpng_rows_t *z)
|
||||
{
|
||||
uint32_t len = rr_be32(z->head);
|
||||
uint32_t type = rr_be32(z->head + 4);
|
||||
unsigned i;
|
||||
|
||||
if (len > RR_DIM_MAX)
|
||||
return 0;
|
||||
for (i = 4; i < 8; i++)
|
||||
{
|
||||
uint8_t c = z->head[i];
|
||||
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')))
|
||||
return 0;
|
||||
}
|
||||
z->type = type;
|
||||
z->left = len;
|
||||
z->keep_len = 0;
|
||||
z->crc = encoding_crc32(0, z->head + 4, 4);
|
||||
|
||||
if (!z->have_ihdr && type != RR_TYPE('I', 'H', 'D', 'R'))
|
||||
return 0;
|
||||
|
||||
if (type == RR_TYPE('I', 'D', 'A', 'T'))
|
||||
{
|
||||
if (z->idat_done)
|
||||
return 0; /* IDATs are consecutive */
|
||||
z->mode = RR_IMAGE;
|
||||
return 1;
|
||||
}
|
||||
if (z->header_sent)
|
||||
z->idat_done = 1;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case RR_TYPE('I', 'H', 'D', 'R'):
|
||||
case RR_TYPE('P', 'L', 'T', 'E'):
|
||||
case RR_TYPE('t', 'R', 'N', 'S'):
|
||||
z->mode = len <= RR_KEEP_MAX ? RR_KEEP : RR_SKIP;
|
||||
if (z->mode == RR_SKIP && type != RR_TYPE('t', 'R', 'N', 'S'))
|
||||
return 0;
|
||||
return 1;
|
||||
/* the colour chunks: one too long to keep is the wrong length */
|
||||
case RR_TYPE('g', 'A', 'M', 'A'):
|
||||
case RR_TYPE('c', 'H', 'R', 'M'):
|
||||
case RR_TYPE('s', 'R', 'G', 'B'):
|
||||
case RR_TYPE('s', 'B', 'I', 'T'):
|
||||
case RR_TYPE('c', 'I', 'C', 'P'):
|
||||
case RR_TYPE('m', 'D', 'C', 'V'):
|
||||
z->mode = len <= RR_KEEP_MAX ? RR_KEEP : RR_SKIP;
|
||||
return 1;
|
||||
case RR_TYPE('I', 'E', 'N', 'D'):
|
||||
z->mode = RR_SKIP;
|
||||
return z->header_sent && !len;
|
||||
default:
|
||||
/* an unknown critical chunk cannot be skipped */
|
||||
if (!(type & 0x20000000u))
|
||||
return 0;
|
||||
z->mode = RR_SKIP;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Image setup at the first IDAT */
|
||||
static int rr_image_begin(rpng_rows_t *z)
|
||||
{
|
||||
size_t need = 2 * (rpng_rows_rowbytes(&z->info, z->info.width) + 1);
|
||||
|
||||
if (z->info.color_type == 3 && !z->have_plte)
|
||||
return 0;
|
||||
if (need > z->rows_cap)
|
||||
{
|
||||
uint8_t *rows = (uint8_t*)malloc(need);
|
||||
if (!rows)
|
||||
return 0;
|
||||
free(z->rows);
|
||||
z->rows = rows;
|
||||
z->rows_cap = need;
|
||||
}
|
||||
z->cur = z->rows;
|
||||
z->prev = z->rows + need / 2;
|
||||
if (z->inflate)
|
||||
rinflate_reset(z->inflate, 15);
|
||||
else if (!(z->inflate = rinflate_new(15)))
|
||||
return 0;
|
||||
z->pass = 0;
|
||||
if (!rr_pass_begin(z))
|
||||
return 0;
|
||||
z->info.chunks |= RPNG_ROWS_IDAT;
|
||||
z->header_sent = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int rpng_rows_step(rpng_rows_t *z, const uint8_t *in, size_t len,
|
||||
size_t *consumed, struct rpng_rows_row *row)
|
||||
{
|
||||
static const uint8_t sig[8] = { 0x89, 'P', 'N', 'G', 0x0d, 0x0a, 0x1a, 0x0a };
|
||||
size_t at = 0;
|
||||
int ev = RPNG_ROWS_NEXT;
|
||||
|
||||
if (z->pass_end)
|
||||
{
|
||||
z->pass_end = 0;
|
||||
z->pass++;
|
||||
if (!z->info.interlace || !rr_pass_begin(z))
|
||||
z->image_done = 1;
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (z->stage == RR_FAILED)
|
||||
{
|
||||
ev = RPNG_ROWS_ERROR;
|
||||
break;
|
||||
}
|
||||
if (z->stage == RR_END)
|
||||
{
|
||||
ev = RPNG_ROWS_END;
|
||||
break;
|
||||
}
|
||||
|
||||
if (z->stage == RR_SIG || z->stage == RR_HDR || z->stage == RR_CRC)
|
||||
{
|
||||
unsigned want = z->stage == RR_CRC ? 4 : 8;
|
||||
while (z->head_fill < want && at < len)
|
||||
z->head[z->head_fill++] = in[at++];
|
||||
if (z->head_fill < want)
|
||||
break;
|
||||
z->head_fill = 0;
|
||||
|
||||
if (z->stage == RR_SIG)
|
||||
{
|
||||
if (memcmp(z->head, sig, 8))
|
||||
goto fail;
|
||||
z->stage = RR_HDR;
|
||||
continue;
|
||||
}
|
||||
if (z->stage == RR_CRC)
|
||||
{
|
||||
int crc_ok = rr_be32(z->head) == z->crc;
|
||||
z->stage = RR_HDR;
|
||||
/* critical chunks must be right; ancillary ones are dropped */
|
||||
if (!crc_ok && !(z->type & 0x20000000u))
|
||||
goto fail;
|
||||
if (z->type == RR_TYPE('I', 'D', 'A', 'T'))
|
||||
continue;
|
||||
if (z->type == RR_TYPE('I', 'E', 'N', 'D'))
|
||||
{
|
||||
if (!z->image_done)
|
||||
goto fail;
|
||||
z->stage = RR_END;
|
||||
continue;
|
||||
}
|
||||
if (crc_ok && !rr_chunk_end(z))
|
||||
goto fail;
|
||||
continue;
|
||||
}
|
||||
/* a chunk header */
|
||||
if (!rr_chunk_begin(z))
|
||||
goto fail;
|
||||
z->stage = RR_DATA;
|
||||
if (z->mode == RR_IMAGE && !z->header_sent)
|
||||
{
|
||||
if (!rr_image_begin(z))
|
||||
goto fail;
|
||||
ev = RPNG_ROWS_HEADER;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* RR_DATA. Image rows first: inflate can hold a row's worth of
|
||||
* output for input it has taken already, also at a chunk's end */
|
||||
if (z->mode == RR_IMAGE && !z->image_done && !z->inflate_done)
|
||||
{
|
||||
size_t n = len - at < z->left ? len - at : z->left;
|
||||
size_t taken = 0, wrote = 0;
|
||||
int r;
|
||||
|
||||
rinflate_set_in(z->inflate, in + at, n);
|
||||
rinflate_set_out(z->inflate, z->cur + z->fill,
|
||||
z->rowbytes + 1 - z->fill);
|
||||
r = rinflate_process(z->inflate, &taken, &wrote);
|
||||
z->crc = encoding_crc32(z->crc, in + at, taken);
|
||||
at += taken;
|
||||
z->left -= (uint32_t)taken;
|
||||
z->fill += wrote;
|
||||
if (r == RDEFLATE_PROCESS_ERROR)
|
||||
goto fail;
|
||||
if (r == RDEFLATE_PROCESS_END)
|
||||
z->inflate_done = 1;
|
||||
|
||||
if (z->fill == z->rowbytes + 1)
|
||||
{
|
||||
uint8_t *t;
|
||||
if (!rr_unfilter(z))
|
||||
goto fail;
|
||||
row->data = z->cur + 1;
|
||||
row->width = z->pass_width;
|
||||
if (z->info.interlace)
|
||||
{
|
||||
row->y = rr_y0[z->pass] + z->pass_row * rr_dy[z->pass];
|
||||
row->x0 = rr_x0[z->pass];
|
||||
row->dx = rr_dx[z->pass];
|
||||
row->pass = (uint8_t)(z->pass + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
row->y = z->pass_row;
|
||||
row->x0 = 0;
|
||||
row->dx = 1;
|
||||
row->pass = 0;
|
||||
}
|
||||
t = z->prev;
|
||||
z->prev = z->cur;
|
||||
z->cur = t;
|
||||
z->fill = 0;
|
||||
/* the next pass starts in the next call: this row is in prev
|
||||
* until then */
|
||||
if (++z->pass_row == z->pass_height)
|
||||
z->pass_end = 1;
|
||||
ev = RPNG_ROWS_ROW;
|
||||
break;
|
||||
}
|
||||
if (z->inflate_done)
|
||||
goto fail; /* the stream ended before the image did */
|
||||
if (taken || wrote)
|
||||
continue;
|
||||
/* nothing more without more input */
|
||||
}
|
||||
|
||||
if (!z->left)
|
||||
{
|
||||
z->stage = RR_CRC;
|
||||
continue;
|
||||
}
|
||||
if (at >= len)
|
||||
break;
|
||||
|
||||
if (z->mode != RR_IMAGE)
|
||||
{
|
||||
size_t n = len - at < z->left ? len - at : z->left;
|
||||
z->crc = encoding_crc32(z->crc, in + at, n);
|
||||
if (z->mode == RR_KEEP)
|
||||
{
|
||||
memcpy(z->keep + z->keep_len, in + at, n);
|
||||
z->keep_len += (uint32_t)n;
|
||||
}
|
||||
at += n;
|
||||
z->left -= (uint32_t)n;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* image data past the last row: the zlib trailer, or data libpng
|
||||
* warns about and ignores */
|
||||
{
|
||||
size_t n = len - at < z->left ? len - at : z->left;
|
||||
size_t taken = 0, wrote = 0;
|
||||
if (!z->inflate_done)
|
||||
{
|
||||
uint8_t sink[64];
|
||||
int r;
|
||||
rinflate_set_in(z->inflate, in + at, n);
|
||||
rinflate_set_out(z->inflate, sink, sizeof(sink));
|
||||
r = rinflate_process(z->inflate, &taken, &wrote);
|
||||
if (r == RDEFLATE_PROCESS_END || r == RDEFLATE_PROCESS_ERROR)
|
||||
z->inflate_done = 1;
|
||||
else if (!taken && !wrote)
|
||||
taken = n; /* nothing it would take */
|
||||
}
|
||||
else
|
||||
taken = n;
|
||||
z->crc = encoding_crc32(z->crc, in + at, taken);
|
||||
at += taken;
|
||||
z->left -= (uint32_t)taken;
|
||||
}
|
||||
}
|
||||
|
||||
if (consumed)
|
||||
*consumed = at;
|
||||
return ev;
|
||||
|
||||
fail:
|
||||
z->stage = RR_FAILED;
|
||||
if (consumed)
|
||||
*consumed = at;
|
||||
return RPNG_ROWS_ERROR;
|
||||
}
|
||||
@@ -170,12 +170,14 @@ struct rvz
|
||||
uint64_t rd_off;
|
||||
size_t rd_len;
|
||||
size_t rd_done;
|
||||
int rd_part; /* a decrypted read's partition, or -1 */
|
||||
bool rd_armed;
|
||||
uint32_t n_regions;
|
||||
uint32_t n_parts;
|
||||
uint32_t n_groups;
|
||||
uint32_t chunk;
|
||||
uint32_t compression;
|
||||
int32_t level; /* as the file records it */
|
||||
uint32_t n_slots;
|
||||
uint32_t block_part; /* the hash block held: partition, */
|
||||
uint32_t block_index; /* and block, or UINT32_MAX */
|
||||
@@ -863,12 +865,65 @@ static const struct rvz_region *rvz_region_at(const rvz_t *z, uint64_t off,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* The partition whose data starts at disc offset @pdo, or -1 */
|
||||
static int rvz_part_at(const rvz_t *z, uint64_t pdo)
|
||||
{
|
||||
uint32_t i;
|
||||
for (i = 0; i < z->n_parts; i++)
|
||||
if ((uint64_t)z->parts[i].base * RVZ_SECTOR == pdo)
|
||||
return (int)i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Runs an armed decrypted read on: each sector's data as its group
|
||||
* holds it, with no hashes worked out and nothing encrypted */
|
||||
static int rvz_read_decrypted_run(rvz_t *z)
|
||||
{
|
||||
const struct rvz_part *pt = &z->parts[z->rd_part];
|
||||
unsigned n_lists = z->chunk >= RVZ_BLOCK ? z->chunk / RVZ_BLOCK : 1;
|
||||
int e;
|
||||
|
||||
while (z->rd_done < z->rd_len)
|
||||
{
|
||||
uint64_t pos = z->rd_off + z->rd_done;
|
||||
size_t in_s = (size_t)(pos % RVZ_SECTOR_DATA);
|
||||
size_t take = z->rd_len - z->rd_done, avail;
|
||||
uint32_t s, g, g_first, g_count, in_g, k;
|
||||
struct rvz_slot *slot = NULL;
|
||||
|
||||
if (pos / RVZ_SECTOR_DATA >= pt->sectors)
|
||||
return RVZ_ERROR_DATA;
|
||||
s = (uint32_t)(pos / RVZ_SECTOR_DATA);
|
||||
if (!rvz_part_locate(z, pt, s, &g, &g_first, &g_count, &in_g))
|
||||
return RVZ_ERROR_DATA;
|
||||
/* a slot holding the group already, else the first */
|
||||
for (k = 0; k < z->n_slots && !slot; k++)
|
||||
if (z->slots[k].group == g)
|
||||
slot = &z->slots[k];
|
||||
if (!slot)
|
||||
slot = &z->slots[0];
|
||||
if ((e = rvz_group_load(z, slot, g, (size_t)g_count * RVZ_SECTOR_DATA,
|
||||
n_lists, (uint64_t)g_first * RVZ_SECTOR_DATA, NULL)) != RVZ_OK)
|
||||
return e;
|
||||
avail = (size_t)(g_count - in_g) * RVZ_SECTOR_DATA - in_s;
|
||||
if (take > avail)
|
||||
take = avail;
|
||||
memcpy(z->rd_dst + z->rd_done,
|
||||
slot->data + (size_t)in_g * RVZ_SECTOR_DATA + in_s, take);
|
||||
z->rd_done += take;
|
||||
}
|
||||
return RVZ_OK;
|
||||
}
|
||||
|
||||
/* Runs the armed read on from where it stands */
|
||||
static int rvz_read_run(rvz_t *z)
|
||||
{
|
||||
uint8_t *out = z->rd_dst;
|
||||
int e;
|
||||
|
||||
if (z->rd_part >= 0)
|
||||
return rvz_read_decrypted_run(z);
|
||||
|
||||
while (z->rd_done < z->rd_len)
|
||||
{
|
||||
uint64_t pos = z->rd_off + z->rd_done, next;
|
||||
@@ -976,6 +1031,45 @@ int rvz_read_begin(rvz_t *z, uint64_t off, void *dst, size_t len)
|
||||
z->rd_off = off;
|
||||
z->rd_len = len;
|
||||
z->rd_done = 0;
|
||||
z->rd_part = -1;
|
||||
z->rd_armed = true;
|
||||
return RVZ_OK;
|
||||
}
|
||||
|
||||
bool rvz_decrypted_supported(const rvz_t *z, uint64_t partition_data,
|
||||
uint64_t off, uint64_t len)
|
||||
{
|
||||
const struct rvz_part *pt;
|
||||
unsigned k;
|
||||
int pi;
|
||||
if (!z || z->stage != RVZ_STAGE_OPEN
|
||||
|| (pi = rvz_part_at(z, partition_data)) < 0)
|
||||
return false;
|
||||
pt = &z->parts[pi];
|
||||
for (k = 0; k < 2; k++)
|
||||
if (pt->count[k] && off + len <= (uint64_t)(pt->first[k] - pt->base
|
||||
+ pt->count[k]) * RVZ_SECTOR_DATA)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int rvz_read_decrypted_begin(rvz_t *z, uint64_t partition_data,
|
||||
uint64_t off, void *dst, size_t len)
|
||||
{
|
||||
int pi;
|
||||
if (!z || (!dst && len))
|
||||
return RVZ_ERROR_PARAM;
|
||||
if (z->stage != RVZ_STAGE_OPEN)
|
||||
return RVZ_ERROR_STATE;
|
||||
if ((pi = rvz_part_at(z, partition_data)) < 0)
|
||||
return RVZ_ERROR_PARAM;
|
||||
z->fetching = 0;
|
||||
z->failed = false;
|
||||
z->rd_dst = (uint8_t*)dst;
|
||||
z->rd_off = off;
|
||||
z->rd_len = len;
|
||||
z->rd_done = 0;
|
||||
z->rd_part = pi;
|
||||
z->rd_armed = true;
|
||||
return RVZ_OK;
|
||||
}
|
||||
@@ -1065,6 +1159,18 @@ int rvz_disc_type(const rvz_t *z)
|
||||
return z ? z->disc_type : 0;
|
||||
}
|
||||
|
||||
int rvz_compression(const rvz_t *z, int *level)
|
||||
{
|
||||
if (level)
|
||||
*level = z && z->stage == RVZ_STAGE_OPEN ? (int)z->level : 0;
|
||||
return z && z->stage == RVZ_STAGE_OPEN ? (int)z->compression : -1;
|
||||
}
|
||||
|
||||
uint32_t rvz_chunk_size(const rvz_t *z)
|
||||
{
|
||||
return z && z->stage == RVZ_STAGE_OPEN ? z->chunk : 0;
|
||||
}
|
||||
|
||||
uint64_t rvz_disc_size(const rvz_t *z)
|
||||
{
|
||||
return z ? z->disc_size : 0;
|
||||
@@ -1272,6 +1378,7 @@ static int rvz_open_run(rvz_t *z)
|
||||
d = z->disc;
|
||||
z->disc_type = (int)rvz_be32(d + 0x00);
|
||||
z->compression = rvz_be32(d + 0x04);
|
||||
z->level = (int32_t)rvz_be32(d + 0x08);
|
||||
z->chunk = rvz_be32(d + 0x0c);
|
||||
memcpy(z->dhead, d + 0x10, RVZ_DHEAD);
|
||||
z->n_parts = rvz_be32(d + 0x90);
|
||||
|
||||
@@ -90,6 +90,8 @@ struct ryaml_node
|
||||
int32_t next_sibling;
|
||||
int32_t parent;
|
||||
uint32_t num_children;
|
||||
int32_t alias; /* *name: the anchored node it stands for */
|
||||
uint32_t off; /* where in the document it starts */
|
||||
uint8_t type;
|
||||
};
|
||||
|
||||
@@ -104,10 +106,21 @@ struct ryaml
|
||||
{
|
||||
struct ryaml_node *nodes;
|
||||
struct ryaml_chunk *chunks;
|
||||
const char *doc; /* borrowed, for ryaml_location() */
|
||||
size_t doc_len;
|
||||
size_t count;
|
||||
size_t cap;
|
||||
};
|
||||
|
||||
/* &name, while the document is read: aliases are resolved as they are met,
|
||||
* so the names do not outlive the parse */
|
||||
struct ryaml_anchor
|
||||
{
|
||||
const char *name;
|
||||
uint32_t len;
|
||||
int32_t node;
|
||||
};
|
||||
|
||||
struct ryaml_frame
|
||||
{
|
||||
int32_t node;
|
||||
@@ -123,6 +136,9 @@ struct ryaml_parser
|
||||
struct ryaml_frame *stk;
|
||||
int sp;
|
||||
int stk_cap;
|
||||
struct ryaml_anchor *anc;
|
||||
int anc_count;
|
||||
int anc_cap;
|
||||
int32_t pending;
|
||||
int32_t pending_indent;
|
||||
int err;
|
||||
@@ -164,7 +180,8 @@ static char *ryaml_arena_alloc(ryaml_t *y, size_t n)
|
||||
/* nodes */
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
static int32_t ryaml_node_new(struct ryaml_parser *ps, int32_t parent, int type)
|
||||
static int32_t ryaml_node_new(struct ryaml_parser *ps, int32_t parent, int type,
|
||||
const char *at)
|
||||
{
|
||||
ryaml_t *y = ps->y;
|
||||
struct ryaml_node *n;
|
||||
@@ -195,6 +212,8 @@ static int32_t ryaml_node_new(struct ryaml_parser *ps, int32_t parent, int type)
|
||||
n->next_sibling = RYAML_NONE;
|
||||
n->parent = parent;
|
||||
n->num_children = 0;
|
||||
n->alias = RYAML_NONE;
|
||||
n->off = (uint32_t)(at - ps->doc);
|
||||
n->type = (uint8_t)type;
|
||||
|
||||
if (parent >= 0)
|
||||
@@ -392,6 +411,56 @@ static int ryaml_quoted(struct ryaml_parser *ps, const char *s, const char *e,
|
||||
case '\\': *w++ = '\\'; break;
|
||||
case '"': *w++ = '"'; break;
|
||||
case '\'': *w++ = '\''; break;
|
||||
case ' ': *w++ = ' '; break;
|
||||
case 'x':
|
||||
case 'u':
|
||||
case 'U':
|
||||
{
|
||||
/* \xNN, \uNNNN, \UNNNNNNNN: a code point, as UTF-8 */
|
||||
int digits = (c == 'x') ? 2 : (c == 'u') ? 4 : 8;
|
||||
uint32_t cp = 0;
|
||||
int i;
|
||||
for (i = 0; i < digits; i++)
|
||||
{
|
||||
char h = (r < q) ? *r : 0;
|
||||
int d = (h >= '0' && h <= '9') ? h - '0'
|
||||
: (h >= 'a' && h <= 'f') ? h - 'a' + 10
|
||||
: (h >= 'A' && h <= 'F') ? h - 'A' + 10 : -1;
|
||||
if (d < 0)
|
||||
{
|
||||
ps->err = 1;
|
||||
return 0;
|
||||
}
|
||||
cp = (cp << 4) | (uint32_t)d;
|
||||
r++;
|
||||
}
|
||||
if (c == 'x' || cp < 0x80)
|
||||
*w++ = (char)cp;
|
||||
else if (cp < 0x800)
|
||||
{
|
||||
*w++ = (char)(0xC0 | (cp >> 6));
|
||||
*w++ = (char)(0x80 | (cp & 0x3F));
|
||||
}
|
||||
else if (cp < 0x10000)
|
||||
{
|
||||
*w++ = (char)(0xE0 | (cp >> 12));
|
||||
*w++ = (char)(0x80 | ((cp >> 6) & 0x3F));
|
||||
*w++ = (char)(0x80 | (cp & 0x3F));
|
||||
}
|
||||
else if (cp < 0x110000)
|
||||
{
|
||||
*w++ = (char)(0xF0 | (cp >> 18));
|
||||
*w++ = (char)(0x80 | ((cp >> 12) & 0x3F));
|
||||
*w++ = (char)(0x80 | ((cp >> 6) & 0x3F));
|
||||
*w++ = (char)(0x80 | (cp & 0x3F));
|
||||
}
|
||||
else
|
||||
{
|
||||
ps->err = 1;
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: *w++ = c; break;
|
||||
}
|
||||
continue;
|
||||
@@ -621,6 +690,81 @@ static int ryaml_block_scalar(struct ryaml_parser *ps, const char *hdr,
|
||||
static const char *ryaml_flow(struct ryaml_parser *ps, int32_t parent,
|
||||
const char *s, const char *e);
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
/* anchors and aliases */
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
/* The name after a '&' or '*' at @s: up to a blank, the line end, or
|
||||
* (in a flow collection) a ',', ']' or '}'. Returns its end, or NULL
|
||||
* when there is no name. */
|
||||
static const char *ryaml_anchor_name(const char *s, const char *e)
|
||||
{
|
||||
const char *q = s + 1;
|
||||
while (q < e && !RYAML_IS_SPACE(*q) && !RYAML_IS_BREAK(*q)
|
||||
&& *q != ',' && *q != ']' && *q != '}')
|
||||
q++;
|
||||
return (q > s + 1) ? q : NULL;
|
||||
}
|
||||
|
||||
/* &name on @node: a later *name stands for it. A name given again
|
||||
* names the newer node from there on, as YAML has it. */
|
||||
static const char *ryaml_anchor(struct ryaml_parser *ps, int32_t node,
|
||||
const char *s, const char *e)
|
||||
{
|
||||
const char *q = ryaml_anchor_name(s, e);
|
||||
if (!q)
|
||||
{
|
||||
ps->err = 1;
|
||||
return NULL;
|
||||
}
|
||||
if (ps->anc_count == ps->anc_cap)
|
||||
{
|
||||
int ncap = ps->anc_cap ? (ps->anc_cap * 2) : 16;
|
||||
struct ryaml_anchor *tmp = (struct ryaml_anchor*)realloc(ps->anc,
|
||||
(size_t)ncap * sizeof(struct ryaml_anchor));
|
||||
if (!tmp)
|
||||
{
|
||||
ps->err = 1;
|
||||
return NULL;
|
||||
}
|
||||
ps->anc = tmp;
|
||||
ps->anc_cap = ncap;
|
||||
}
|
||||
ps->anc[ps->anc_count].name = s + 1;
|
||||
ps->anc[ps->anc_count].len = (uint32_t)(q - s - 1);
|
||||
ps->anc[ps->anc_count].node = node;
|
||||
ps->anc_count++;
|
||||
return q;
|
||||
}
|
||||
|
||||
/* *name as @node's value: @node stands for the anchored node, which
|
||||
* has to come before it in the document. */
|
||||
static const char *ryaml_alias(struct ryaml_parser *ps, int32_t node,
|
||||
const char *s, const char *e)
|
||||
{
|
||||
const char *q = ryaml_anchor_name(s, e);
|
||||
int i;
|
||||
if (q)
|
||||
{
|
||||
uint32_t len = (uint32_t)(q - s - 1);
|
||||
for (i = ps->anc_count - 1; i >= 0; i--)
|
||||
{
|
||||
if (ps->anc[i].len == len && !memcmp(ps->anc[i].name, s + 1, len))
|
||||
{
|
||||
int32_t target = ps->anc[i].node;
|
||||
struct ryaml_node *n = &ps->y->nodes[node];
|
||||
if (ps->y->nodes[target].alias >= 0)
|
||||
target = ps->y->nodes[target].alias;
|
||||
n->alias = target;
|
||||
n->type = ps->y->nodes[target].type;
|
||||
return q;
|
||||
}
|
||||
}
|
||||
}
|
||||
ps->err = 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *ryaml_flow_skip_ws(const char *s, const char *e)
|
||||
{
|
||||
while (s < e && RYAML_IS_SPACE(*s))
|
||||
@@ -690,7 +834,7 @@ static const char *ryaml_flow(struct ryaml_parser *ps, int32_t parent,
|
||||
if (*s == close)
|
||||
return s + 1;
|
||||
|
||||
child = ryaml_node_new(ps, parent, RYAML_TYPE_SCALAR);
|
||||
child = ryaml_node_new(ps, parent, RYAML_TYPE_SCALAR, s);
|
||||
if (child < 0)
|
||||
return NULL;
|
||||
|
||||
@@ -716,7 +860,26 @@ static const char *ryaml_flow(struct ryaml_parser *ps, int32_t parent,
|
||||
}
|
||||
}
|
||||
|
||||
if (*s == '{' || *s == '[')
|
||||
if (*s == '&')
|
||||
{
|
||||
s = ryaml_anchor(ps, child, s, e);
|
||||
if (!s)
|
||||
return NULL;
|
||||
s = ryaml_flow_skip_ws(s, e);
|
||||
if (s >= e)
|
||||
{
|
||||
ps->err = 1;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (*s == '*')
|
||||
{
|
||||
s = ryaml_alias(ps, child, s, e);
|
||||
if (!s)
|
||||
return NULL;
|
||||
}
|
||||
else if (*s == '{' || *s == '[')
|
||||
{
|
||||
s = ryaml_flow(ps, child, s, e);
|
||||
if (!s)
|
||||
@@ -770,12 +933,36 @@ static int ryaml_value(struct ryaml_parser *ps, int32_t node, int indent,
|
||||
while (v < le && RYAML_IS_SPACE(*v))
|
||||
v++;
|
||||
|
||||
/* &name ahead of the value, or of the lines that make it up */
|
||||
if (v < le && *v == '&')
|
||||
{
|
||||
if (!(v = ryaml_anchor(ps, node, v, le)))
|
||||
return 0;
|
||||
while (v < le && RYAML_IS_SPACE(*v))
|
||||
v++;
|
||||
}
|
||||
|
||||
if (v >= le || *v == '\r' || *v == '#')
|
||||
{
|
||||
*deferred = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* *name, and nothing but a comment after it */
|
||||
if (*v == '*')
|
||||
{
|
||||
if (!(v = ryaml_alias(ps, node, v, le)))
|
||||
return 0;
|
||||
while (v < le && RYAML_IS_SPACE(*v))
|
||||
v++;
|
||||
if (v < le && *v != '\r' && *v != '#')
|
||||
{
|
||||
ps->err = 1;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
n = &ps->y->nodes[node];
|
||||
|
||||
if (*v == '|' || *v == '>')
|
||||
@@ -895,7 +1082,7 @@ static const char *ryaml_find_key_colon(const char *s, const char *le)
|
||||
|
||||
static int ryaml_run(struct ryaml_parser *ps)
|
||||
{
|
||||
int32_t root = ryaml_node_new(ps, RYAML_NONE, RYAML_TYPE_NONE);
|
||||
int32_t root = ryaml_node_new(ps, RYAML_NONE, RYAML_TYPE_NONE, ps->p);
|
||||
|
||||
if (root < 0)
|
||||
return 0;
|
||||
@@ -929,6 +1116,25 @@ static int ryaml_run(struct ryaml_parser *ps)
|
||||
* up, or a sequence sitting at the key's own indent. */
|
||||
if (ps->pending >= 0)
|
||||
{
|
||||
/* The value itself on the line below its key: a scalar (quoted
|
||||
* or plain), a flow collection or an alias, rather than the
|
||||
* entries of a mapping */
|
||||
if (!is_seq_item && indent > ps->pending_indent
|
||||
&& (*c == '[' || *c == '{' || *c == '*'
|
||||
|| !ryaml_find_key_colon(c, le)))
|
||||
{
|
||||
int32_t node = ps->pending;
|
||||
int deferred;
|
||||
int r;
|
||||
ps->pending = RYAML_NONE;
|
||||
r = ryaml_value(ps, node, ps->pending_indent, c, le, &deferred);
|
||||
if (r == 0 || deferred)
|
||||
return 0;
|
||||
if (r < 0)
|
||||
continue;
|
||||
ps->p = ryaml_next_line(le, ps->end);
|
||||
continue;
|
||||
}
|
||||
if (indent > ps->pending_indent
|
||||
|| (is_seq_item && indent == ps->pending_indent))
|
||||
{
|
||||
@@ -968,7 +1174,7 @@ static int ryaml_run(struct ryaml_parser *ps)
|
||||
if (ps->y->nodes[cur].type != RYAML_TYPE_SEQ)
|
||||
return 0;
|
||||
|
||||
item = ryaml_node_new(ps, cur, RYAML_TYPE_SCALAR);
|
||||
item = ryaml_node_new(ps, cur, RYAML_TYPE_SCALAR, c);
|
||||
if (item < 0)
|
||||
return 0;
|
||||
|
||||
@@ -993,7 +1199,7 @@ static int ryaml_run(struct ryaml_parser *ps)
|
||||
if (!ryaml_push(ps, item, kind))
|
||||
return 0;
|
||||
|
||||
kv = ryaml_node_new(ps, item, RYAML_TYPE_SCALAR);
|
||||
kv = ryaml_node_new(ps, item, RYAML_TYPE_SCALAR, rest);
|
||||
if (kv < 0)
|
||||
return 0;
|
||||
|
||||
@@ -1030,6 +1236,12 @@ static int ryaml_run(struct ryaml_parser *ps)
|
||||
int r = ryaml_value(ps, item, indent, rest, le, &deferred);
|
||||
if (r == 0)
|
||||
return 0;
|
||||
if (deferred)
|
||||
{
|
||||
/* "- &name" with the item on the lines below */
|
||||
ps->pending = item;
|
||||
ps->pending_indent = indent;
|
||||
}
|
||||
if (r < 0)
|
||||
continue;
|
||||
}
|
||||
@@ -1057,7 +1269,7 @@ static int ryaml_run(struct ryaml_parser *ps)
|
||||
if (ps->y->nodes[cur].type != RYAML_TYPE_MAP)
|
||||
return 0;
|
||||
|
||||
kv = ryaml_node_new(ps, cur, RYAML_TYPE_SCALAR);
|
||||
kv = ryaml_node_new(ps, cur, RYAML_TYPE_SCALAR, c);
|
||||
if (kv < 0)
|
||||
return 0;
|
||||
|
||||
@@ -1113,6 +1325,8 @@ ryaml_t *ryaml_parse_ex(const char *buf, size_t len,
|
||||
return NULL;
|
||||
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
y->doc = buf;
|
||||
y->doc_len = len;
|
||||
ps.y = y;
|
||||
ps.doc = buf;
|
||||
ps.p = buf;
|
||||
@@ -1154,11 +1368,13 @@ ryaml_t *ryaml_parse_ex(const char *buf, size_t len,
|
||||
*err_col = (size_t)(ps.p - ls) + 1;
|
||||
}
|
||||
free(ps.stk);
|
||||
free(ps.anc);
|
||||
ryaml_free(y);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
free(ps.stk);
|
||||
free(ps.anc);
|
||||
return y;
|
||||
}
|
||||
|
||||
@@ -1191,6 +1407,10 @@ void ryaml_free(ryaml_t *y)
|
||||
|
||||
#define RYAML_VALID(y, n) ((y) && (n) >= 0 && (size_t)(n) < (y)->count)
|
||||
|
||||
/* What @node holds: the anchored node for an alias, else itself. A node
|
||||
* keeps its own key, siblings and parent either way. */
|
||||
#define RYAML_HELD(y, n) ((y)->nodes[n].alias >= 0 ? (y)->nodes[n].alias : (n))
|
||||
|
||||
int ryaml_root(const ryaml_t *y)
|
||||
{
|
||||
return (y && y->count) ? 0 : RYAML_NONE;
|
||||
@@ -1203,12 +1423,12 @@ size_t ryaml_count(const ryaml_t *y)
|
||||
|
||||
int ryaml_first_child(const ryaml_t *y, int node)
|
||||
{
|
||||
return RYAML_VALID(y, node) ? y->nodes[node].first_child : RYAML_NONE;
|
||||
return RYAML_VALID(y, node) ? y->nodes[RYAML_HELD(y, node)].first_child : RYAML_NONE;
|
||||
}
|
||||
|
||||
int ryaml_last_child(const ryaml_t *y, int node)
|
||||
{
|
||||
return RYAML_VALID(y, node) ? y->nodes[node].last_child : RYAML_NONE;
|
||||
return RYAML_VALID(y, node) ? y->nodes[RYAML_HELD(y, node)].last_child : RYAML_NONE;
|
||||
}
|
||||
|
||||
int ryaml_next_sibling(const ryaml_t *y, int node)
|
||||
@@ -1223,7 +1443,7 @@ int ryaml_parent(const ryaml_t *y, int node)
|
||||
|
||||
size_t ryaml_num_children(const ryaml_t *y, int node)
|
||||
{
|
||||
return RYAML_VALID(y, node) ? (size_t)y->nodes[node].num_children : 0;
|
||||
return RYAML_VALID(y, node) ? (size_t)y->nodes[RYAML_HELD(y, node)].num_children : 0;
|
||||
}
|
||||
|
||||
int ryaml_find_child_len(const ryaml_t *y, int node,
|
||||
@@ -1234,7 +1454,7 @@ int ryaml_find_child_len(const ryaml_t *y, int node,
|
||||
if (!RYAML_VALID(y, node) || !key)
|
||||
return RYAML_NONE;
|
||||
|
||||
for (n = y->nodes[node].first_child; n >= 0; n = y->nodes[n].next_sibling)
|
||||
for (n = y->nodes[RYAML_HELD(y, node)].first_child; n >= 0; n = y->nodes[n].next_sibling)
|
||||
{
|
||||
const struct ryaml_node *c = &y->nodes[n];
|
||||
if (c->key_len == (uint32_t)key_len && c->key
|
||||
@@ -1256,7 +1476,7 @@ int ryaml_has_child(const ryaml_t *y, int node, const char *key)
|
||||
|
||||
int ryaml_type(const ryaml_t *y, int node)
|
||||
{
|
||||
return RYAML_VALID(y, node) ? (int)y->nodes[node].type : RYAML_TYPE_NONE;
|
||||
return RYAML_VALID(y, node) ? (int)y->nodes[RYAML_HELD(y, node)].type : RYAML_TYPE_NONE;
|
||||
}
|
||||
|
||||
int ryaml_is_map(const ryaml_t *y, int node)
|
||||
@@ -1271,12 +1491,12 @@ int ryaml_is_seq(const ryaml_t *y, int node)
|
||||
|
||||
int ryaml_has_children(const ryaml_t *y, int node)
|
||||
{
|
||||
return RYAML_VALID(y, node) && y->nodes[node].first_child >= 0;
|
||||
return RYAML_VALID(y, node) && y->nodes[RYAML_HELD(y, node)].first_child >= 0;
|
||||
}
|
||||
|
||||
int ryaml_has_val(const ryaml_t *y, int node)
|
||||
{
|
||||
return RYAML_VALID(y, node) && y->nodes[node].val != NULL;
|
||||
return RYAML_VALID(y, node) && y->nodes[RYAML_HELD(y, node)].val != NULL;
|
||||
}
|
||||
|
||||
int ryaml_has_key(const ryaml_t *y, int node)
|
||||
@@ -1299,6 +1519,8 @@ const char *ryaml_key(const ryaml_t *y, int node, size_t *len)
|
||||
|
||||
const char *ryaml_val(const ryaml_t *y, int node, size_t *len)
|
||||
{
|
||||
if (RYAML_VALID(y, node))
|
||||
node = RYAML_HELD(y, node);
|
||||
if (!RYAML_VALID(y, node) || !y->nodes[node].val)
|
||||
{
|
||||
if (len)
|
||||
@@ -1432,3 +1654,32 @@ int ryaml_val_equals(const ryaml_t *y, int node, const char *str)
|
||||
slen = strlen(str);
|
||||
return (slen == len) && (memcmp(v, str, len) == 0);
|
||||
}
|
||||
|
||||
int ryaml_is_alias(const ryaml_t *y, int node)
|
||||
{
|
||||
return RYAML_VALID(y, node) && y->nodes[node].alias >= 0;
|
||||
}
|
||||
|
||||
int ryaml_location(const ryaml_t *y, int node, size_t *line, size_t *col)
|
||||
{
|
||||
const char *p, *end, *ls;
|
||||
size_t l = 1;
|
||||
if (!RYAML_VALID(y, node) || !y->doc)
|
||||
return 0;
|
||||
p = y->doc;
|
||||
end = y->doc + y->nodes[node].off;
|
||||
ls = p;
|
||||
while (p < end)
|
||||
{
|
||||
const char *nl = (const char*)memchr(p, '\n', (size_t)(end - p));
|
||||
if (!nl)
|
||||
break;
|
||||
l++;
|
||||
p = ls = nl + 1;
|
||||
}
|
||||
if (line)
|
||||
*line = l;
|
||||
if (col)
|
||||
*col = (size_t)(end - ls) + 1;
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,515 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (ryaml_writer.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 <formats/ryaml_writer.h>
|
||||
|
||||
/* Output is staged in one buffer: written out through the callback when
|
||||
* it fills, or grown and kept when there is no callback. Nothing else is
|
||||
* allocated per call; the stack of open collections grows only when the
|
||||
* document nests deeper than it has before. */
|
||||
#define RYW_STAGE 4096
|
||||
#define RYW_DEPTH_MIN 16
|
||||
|
||||
enum ryw_kind { RYW_MAP, RYW_SEQ, RYW_FLOW };
|
||||
|
||||
/* How a collection began, which decides where its first entry goes */
|
||||
enum ryw_open
|
||||
{
|
||||
RYW_ROOT, /* the document: at the start of the first line */
|
||||
RYW_AFTER_KEY, /* "key:" written: on the next line, indented */
|
||||
RYW_AFTER_DASH /* "- " written: on the same line */
|
||||
};
|
||||
|
||||
struct ryw_frame
|
||||
{
|
||||
unsigned indent; /* column of the entries */
|
||||
unsigned count; /* entries written */
|
||||
unsigned char kind;
|
||||
unsigned char open;
|
||||
};
|
||||
|
||||
struct ryaml_writer
|
||||
{
|
||||
ryaml_writer_io_t io;
|
||||
void *user;
|
||||
char *buf;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
struct ryw_frame *stk;
|
||||
int sp; /* -1: nothing open */
|
||||
int stk_cap;
|
||||
int after_key; /* a key waits for its value */
|
||||
int done; /* the document has its value */
|
||||
int err;
|
||||
};
|
||||
|
||||
static void ryw_flush(ryaml_writer_t *w)
|
||||
{
|
||||
if (w->io && w->len)
|
||||
{
|
||||
if (!w->io(w->buf, w->len, w->user))
|
||||
w->err = 1;
|
||||
w->len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void ryw_put(ryaml_writer_t *w, const char *s, size_t n)
|
||||
{
|
||||
if (w->err)
|
||||
return;
|
||||
if (w->len + n + 1 > w->cap)
|
||||
{
|
||||
if (w->io)
|
||||
{
|
||||
ryw_flush(w);
|
||||
if (n + 1 > w->cap)
|
||||
{
|
||||
/* larger than the stage: straight through */
|
||||
if (!w->io(s, n, w->user))
|
||||
w->err = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t ncap = w->cap * 2;
|
||||
char *tmp;
|
||||
while (ncap < w->len + n + 1)
|
||||
ncap *= 2;
|
||||
if (!(tmp = (char*)realloc(w->buf, ncap)))
|
||||
{
|
||||
w->err = 1;
|
||||
return;
|
||||
}
|
||||
w->buf = tmp;
|
||||
w->cap = ncap;
|
||||
}
|
||||
}
|
||||
memcpy(w->buf + w->len, s, n);
|
||||
w->len += n;
|
||||
}
|
||||
|
||||
static void ryw_spaces(ryaml_writer_t *w, unsigned n)
|
||||
{
|
||||
static const char sp[] = " ";
|
||||
while (n)
|
||||
{
|
||||
unsigned k = n < 32 ? n : 32;
|
||||
ryw_put(w, sp, k);
|
||||
n -= k;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
/* scalars */
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
/* Whether the reader would take @s back as it is, unquoted */
|
||||
static int ryw_plain_ok(const char *s, size_t len, int in_flow, int is_key)
|
||||
{
|
||||
size_t i;
|
||||
if (!len)
|
||||
return 0;
|
||||
/* "-1" is a scalar; "- " or "-" alone would be a sequence item */
|
||||
if (s[0] == '-' && len > 1 && s[1] != ' ' && s[1] != '\t')
|
||||
;
|
||||
else switch (s[0])
|
||||
{
|
||||
case '-': case '?': case ':': case ',': case '[': case ']':
|
||||
case '{': case '}': case '#': case '&': case '*': case '!':
|
||||
case '|': case '>': case '\'': case '"': case '%': case '@':
|
||||
case '`': case ' ': case '\t':
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (s[len - 1] == ' ' || s[len - 1] == '\t' || s[len - 1] == ':')
|
||||
return 0;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
unsigned char c = (unsigned char)s[i];
|
||||
if (c < 0x20 || c == 0x7f)
|
||||
return 0;
|
||||
if (c == ':' && i + 1 < len && (s[i + 1] == ' ' || s[i + 1] == '\t'))
|
||||
return 0;
|
||||
if (c == '#' && i && (s[i - 1] == ' ' || s[i - 1] == '\t'))
|
||||
return 0;
|
||||
if ((in_flow || is_key) && (c == ',' || c == '[' || c == ']'
|
||||
|| c == '{' || c == '}'))
|
||||
return 0;
|
||||
if (in_flow && c == ':')
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void ryw_text(ryaml_writer_t *w, const char *s, size_t len,
|
||||
int in_flow, int is_key)
|
||||
{
|
||||
const char *run;
|
||||
size_t i;
|
||||
|
||||
if (ryw_plain_ok(s, len, in_flow, is_key))
|
||||
{
|
||||
ryw_put(w, s, len);
|
||||
return;
|
||||
}
|
||||
|
||||
/* double-quoted, escaping only what the reader unescapes */
|
||||
ryw_put(w, "\"", 1);
|
||||
run = s;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
unsigned char c = (unsigned char)s[i];
|
||||
const char *esc = NULL;
|
||||
char xb[4];
|
||||
size_t elen = 2;
|
||||
switch (c)
|
||||
{
|
||||
case '"': esc = "\\\""; break;
|
||||
case '\\': esc = "\\\\"; break;
|
||||
case '\n': esc = "\\n"; break;
|
||||
case '\r': esc = "\\r"; break;
|
||||
case '\t': esc = "\\t"; break;
|
||||
case '\0': esc = "\\0"; break;
|
||||
default:
|
||||
if (c < 0x20 || c == 0x7f)
|
||||
{
|
||||
/* any other control byte as \xNN */
|
||||
xb[0] = '\\';
|
||||
xb[1] = 'x';
|
||||
xb[2] = hex[c >> 4];
|
||||
xb[3] = hex[c & 15];
|
||||
esc = xb;
|
||||
elen = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (esc)
|
||||
{
|
||||
ryw_put(w, run, (size_t)(s + i - run));
|
||||
ryw_put(w, esc, elen);
|
||||
run = s + i + 1;
|
||||
}
|
||||
}
|
||||
ryw_put(w, run, (size_t)(s + len - run));
|
||||
ryw_put(w, "\"", 1);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
/* layout */
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
/* Where the next entry of @f starts: its first entry after a key goes on
|
||||
* a new line, after a dash on the same one (a sequence's first item on
|
||||
* a new line too, as "- - a" does not read back), and every later entry
|
||||
* at the start of its own line. */
|
||||
static void ryw_entry(ryaml_writer_t *w, struct ryw_frame *f)
|
||||
{
|
||||
if (!f->count && (f->open == RYW_AFTER_KEY
|
||||
|| (f->open == RYW_AFTER_DASH && f->kind == RYW_SEQ)))
|
||||
ryw_put(w, "\n", 1);
|
||||
else if (!f->count && f->open != RYW_ROOT)
|
||||
return;
|
||||
ryw_spaces(w, f->indent);
|
||||
}
|
||||
|
||||
static struct ryw_frame *ryw_top(ryaml_writer_t *w)
|
||||
{
|
||||
return w->sp >= 0 ? &w->stk[w->sp] : NULL;
|
||||
}
|
||||
|
||||
/* Where a value goes in the current collection, before it is written:
|
||||
* @what is 0 for a scalar or flow sequence, 1 for a block mapping, 2 for
|
||||
* a block sequence (whose first item goes on the line below the dash).
|
||||
* Returns 0 when no value is due there. */
|
||||
static int ryw_value_slot(ryaml_writer_t *w, int what)
|
||||
{
|
||||
struct ryw_frame *f = ryw_top(w);
|
||||
if (w->err)
|
||||
return 0;
|
||||
if (!f)
|
||||
{
|
||||
if (w->done)
|
||||
return 0;
|
||||
w->done = 1;
|
||||
return 1;
|
||||
}
|
||||
switch (f->kind)
|
||||
{
|
||||
case RYW_MAP:
|
||||
if (!w->after_key)
|
||||
return 0;
|
||||
w->after_key = 0;
|
||||
if (!what)
|
||||
ryw_put(w, " ", 1);
|
||||
return 1;
|
||||
case RYW_SEQ:
|
||||
ryw_entry(w, f);
|
||||
ryw_put(w, "- ", what == 2 ? 1 : 2);
|
||||
f->count++;
|
||||
return 1;
|
||||
default:
|
||||
if (f->count)
|
||||
ryw_put(w, ", ", 2);
|
||||
f->count++;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int ryw_push(ryaml_writer_t *w, int kind, int open, unsigned indent)
|
||||
{
|
||||
if (w->sp + 1 >= w->stk_cap)
|
||||
{
|
||||
int ncap = w->stk_cap * 2;
|
||||
struct ryw_frame *tmp = (struct ryw_frame*)realloc(w->stk,
|
||||
(size_t)ncap * sizeof(*tmp));
|
||||
if (!tmp)
|
||||
{
|
||||
w->err = 1;
|
||||
return 0;
|
||||
}
|
||||
w->stk = tmp;
|
||||
w->stk_cap = ncap;
|
||||
}
|
||||
w->sp++;
|
||||
w->stk[w->sp].indent = indent;
|
||||
w->stk[w->sp].count = 0;
|
||||
w->stk[w->sp].kind = (unsigned char)kind;
|
||||
w->stk[w->sp].open = (unsigned char)open;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void ryw_begin_block(ryaml_writer_t *w, int kind)
|
||||
{
|
||||
struct ryw_frame *f = ryw_top(w);
|
||||
int open;
|
||||
unsigned indent;
|
||||
|
||||
if (f && f->kind == RYW_FLOW)
|
||||
{
|
||||
w->err = 1; /* no block collection inside a flow one */
|
||||
return;
|
||||
}
|
||||
if (!ryw_value_slot(w, kind == RYW_SEQ ? 2 : 1))
|
||||
{
|
||||
w->err = 1;
|
||||
return;
|
||||
}
|
||||
if (!f)
|
||||
{
|
||||
open = RYW_ROOT;
|
||||
indent = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
open = (f->kind == RYW_MAP) ? RYW_AFTER_KEY : RYW_AFTER_DASH;
|
||||
indent = f->indent + 2;
|
||||
}
|
||||
ryw_push(w, kind, open, indent);
|
||||
}
|
||||
|
||||
static void ryw_end(ryaml_writer_t *w, int seq)
|
||||
{
|
||||
struct ryw_frame *f = ryw_top(w);
|
||||
struct ryw_frame *parent;
|
||||
|
||||
if (w->err)
|
||||
return;
|
||||
if (!f || w->after_key || (seq ? f->kind == RYW_MAP : f->kind != RYW_MAP))
|
||||
{
|
||||
w->err = 1;
|
||||
return;
|
||||
}
|
||||
if (f->kind == RYW_FLOW)
|
||||
ryw_put(w, "]", 1);
|
||||
else if (!f->count)
|
||||
{
|
||||
/* after "key:", or after the "-" a block sequence is opened with */
|
||||
if (f->open == RYW_AFTER_KEY
|
||||
|| (f->open == RYW_AFTER_DASH && f->kind == RYW_SEQ))
|
||||
ryw_put(w, " ", 1);
|
||||
ryw_put(w, seq ? "[]" : "{}", 2);
|
||||
}
|
||||
w->sp--;
|
||||
parent = ryw_top(w);
|
||||
/* a block collection's entries end their own lines; a flow one or an
|
||||
* empty one ends its line here, unless it sits in a flow sequence */
|
||||
if ((f->kind == RYW_FLOW || !f->count) && (!parent || parent->kind != RYW_FLOW))
|
||||
ryw_put(w, "\n", 1);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
/* public */
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
ryaml_writer_t *ryaml_writer_new(ryaml_writer_io_t io, void *user_data)
|
||||
{
|
||||
ryaml_writer_t *w = (ryaml_writer_t*)calloc(1, sizeof(*w));
|
||||
if (!w)
|
||||
return NULL;
|
||||
w->io = io;
|
||||
w->user = user_data;
|
||||
w->cap = RYW_STAGE;
|
||||
w->buf = (char*)malloc(w->cap);
|
||||
w->stk_cap = RYW_DEPTH_MIN;
|
||||
w->stk = (struct ryw_frame*)malloc((size_t)w->stk_cap * sizeof(*w->stk));
|
||||
w->sp = -1;
|
||||
if (!w->buf || !w->stk)
|
||||
{
|
||||
free(w->buf);
|
||||
free(w->stk);
|
||||
free(w);
|
||||
return NULL;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
int ryaml_writer_free(ryaml_writer_t *w)
|
||||
{
|
||||
int ok;
|
||||
if (!w)
|
||||
return 0;
|
||||
ryw_flush(w);
|
||||
ok = !w->err && w->sp < 0 && !w->after_key;
|
||||
free(w->buf);
|
||||
free(w->stk);
|
||||
free(w);
|
||||
return ok;
|
||||
}
|
||||
|
||||
const char *ryaml_writer_memory(ryaml_writer_t *w, size_t *len)
|
||||
{
|
||||
if (!w || w->io || w->err)
|
||||
{
|
||||
if (len)
|
||||
*len = 0;
|
||||
return NULL;
|
||||
}
|
||||
w->buf[w->len] = '\0';
|
||||
if (len)
|
||||
*len = w->len;
|
||||
return w->buf;
|
||||
}
|
||||
|
||||
int ryaml_writer_error(const ryaml_writer_t *w)
|
||||
{
|
||||
return !w || w->err;
|
||||
}
|
||||
|
||||
void ryaml_writer_begin_map(ryaml_writer_t *w)
|
||||
{
|
||||
if (w)
|
||||
ryw_begin_block(w, RYW_MAP);
|
||||
}
|
||||
|
||||
void ryaml_writer_end_map(ryaml_writer_t *w)
|
||||
{
|
||||
if (w)
|
||||
ryw_end(w, 0);
|
||||
}
|
||||
|
||||
void ryaml_writer_begin_seq(ryaml_writer_t *w, int flow)
|
||||
{
|
||||
struct ryw_frame *f;
|
||||
if (!w)
|
||||
return;
|
||||
if (!flow)
|
||||
{
|
||||
ryw_begin_block(w, RYW_SEQ);
|
||||
return;
|
||||
}
|
||||
f = ryw_top(w);
|
||||
if (!ryw_value_slot(w, 0))
|
||||
{
|
||||
w->err = 1;
|
||||
return;
|
||||
}
|
||||
ryw_put(w, "[", 1);
|
||||
ryw_push(w, RYW_FLOW, RYW_AFTER_DASH, f ? f->indent : 0);
|
||||
}
|
||||
|
||||
void ryaml_writer_end_seq(ryaml_writer_t *w)
|
||||
{
|
||||
if (w)
|
||||
ryw_end(w, 1);
|
||||
}
|
||||
|
||||
void ryaml_writer_key(ryaml_writer_t *w, const char *key, size_t len)
|
||||
{
|
||||
struct ryw_frame *f;
|
||||
if (!w || w->err)
|
||||
return;
|
||||
f = ryw_top(w);
|
||||
if (!f || f->kind != RYW_MAP || w->after_key || !key)
|
||||
{
|
||||
w->err = 1;
|
||||
return;
|
||||
}
|
||||
ryw_entry(w, f);
|
||||
ryw_text(w, key, len, 0, 1);
|
||||
ryw_put(w, ":", 1);
|
||||
f->count++;
|
||||
w->after_key = 1;
|
||||
}
|
||||
|
||||
void ryaml_writer_scalar(ryaml_writer_t *w, const char *val, size_t len)
|
||||
{
|
||||
struct ryw_frame *f;
|
||||
if (!w)
|
||||
return;
|
||||
f = ryw_top(w);
|
||||
if (!ryw_value_slot(w, 0))
|
||||
{
|
||||
w->err = 1;
|
||||
return;
|
||||
}
|
||||
ryw_text(w, val ? val : "", val ? len : 0, f && f->kind == RYW_FLOW, 0);
|
||||
if (!f || f->kind != RYW_FLOW)
|
||||
ryw_put(w, "\n", 1);
|
||||
}
|
||||
|
||||
void ryaml_writer_blank_line(ryaml_writer_t *w)
|
||||
{
|
||||
struct ryw_frame *f;
|
||||
if (!w || w->err)
|
||||
return;
|
||||
f = ryw_top(w);
|
||||
if (w->after_key || (f && f->kind == RYW_FLOW))
|
||||
{
|
||||
w->err = 1;
|
||||
return;
|
||||
}
|
||||
/* the next entry starts a line of its own, so this one can be empty;
|
||||
* a collection's first entry after a key starts with its line break,
|
||||
* so it gets its own here */
|
||||
if (f && !f->count && f->open == RYW_AFTER_KEY)
|
||||
{
|
||||
w->err = 1;
|
||||
return;
|
||||
}
|
||||
ryw_put(w, "\n", 1);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -85,6 +85,7 @@ struct rslc_pr
|
||||
#ifdef RSLC_EXTENDED
|
||||
bool msl_boolread; /* inside the bool(...) of a block read */
|
||||
bool msl_dual; /* a dual-source output: every color names its index */
|
||||
const struct rslc_var *fb_out; /* GLSL: the output an input attachment reads */
|
||||
#endif
|
||||
uint8_t *active[RSLC_MAX_BLOCKS];
|
||||
unsigned version;
|
||||
@@ -417,6 +418,9 @@ static void pr_type_full(struct rslc_pr *p, const struct rslc_type *t)
|
||||
pr_array_suffix(p, t);
|
||||
}
|
||||
|
||||
/* GLSL ES: of the opaque types only sampler2D and samplerCube have a
|
||||
* default precision (and int and uint sampler2D the statements the stage
|
||||
* opens with); any other takes its own, highp where none is stated. */
|
||||
static void pr_precision(struct rslc_pr *p, unsigned prec, const struct rslc_type *t)
|
||||
{
|
||||
const struct rslc_type *bt = t;
|
||||
@@ -426,6 +430,17 @@ static void pr_precision(struct rslc_pr *p, unsigned prec, const struct rslc_typ
|
||||
bt = bt->elem;
|
||||
if (bt->kind == RSLC_TY_STRUCT || bt->kind == RSLC_TY_VOID || bt->base == RSLC_BASE_BOOL)
|
||||
return;
|
||||
if (!prec && (RSLC_TY_IS_SAMPLER(bt->kind) || RSLC_TY_IS_IMAGE(bt->kind)))
|
||||
{
|
||||
if (bt->kind == RSLC_TY_SAMPLER2D)
|
||||
return;
|
||||
#ifdef RSLC_EXTENDED
|
||||
if ( bt->kind == RSLC_TY_SAMPLERX && bt->rows == RSLC_SDIM_CUBE
|
||||
&& !bt->cols && bt->base == RSLC_BASE_FLOAT)
|
||||
return;
|
||||
#endif
|
||||
prec = RSLC_PREC_HIGH;
|
||||
}
|
||||
switch (prec)
|
||||
{
|
||||
case RSLC_PREC_LOW: pr_s(p, "lowp "); break;
|
||||
@@ -1970,6 +1985,17 @@ static void pr_builtin(struct rslc_pr *p, const struct rslc_node *n)
|
||||
pr_msl_builtin(p, n);
|
||||
return;
|
||||
}
|
||||
#ifdef RSLC_EXTENDED
|
||||
/* the framebuffer's value: the inout output, or ARM's built-in */
|
||||
if (bfn == RSLC_BFN_SUBPASSLOAD)
|
||||
{
|
||||
if (p->fb_out)
|
||||
pr_var_name(p, p->fb_out);
|
||||
else
|
||||
pr_s(p, "gl_LastFragColorARM");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
/* fma() needs GLSL 4.00 / ES 3.20; below that it is written out */
|
||||
if (bfn == RSLC_BFN_FMA && ((!p->es && p->version < 400) || (p->es && p->version < 320)))
|
||||
{
|
||||
@@ -4490,6 +4516,32 @@ static void pr_ioblock_decl(struct rslc_pr *p, const struct rslc_var *v, bool is
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef RSLC_EXTENDED
|
||||
/* GLSL, GL_EXT_shader_framebuffer_fetch: the colour output an input
|
||||
* attachment reads, the one at its index (an output without a location
|
||||
* is at 0); it prints inout. Every attachment must find one. */
|
||||
static bool pr_fb_fetch_output(struct rslc_pr *p, const struct rslc_var *in)
|
||||
{
|
||||
const struct rslc_prog *pr = &p->c->prog;
|
||||
const struct rslc_var *out = NULL;
|
||||
uint32_t i;
|
||||
if (p->c->opts->fb_fetch != RSLC_FB_FETCH_EXT)
|
||||
return true;
|
||||
for (i = 0; i < pr->noutputs && !out; i++)
|
||||
{
|
||||
const struct rslc_var *v = pr->outputs[i];
|
||||
if (v->index)
|
||||
continue;
|
||||
if (v->location == in->member || (v->location == RSLC_UNSET && in->member == 0))
|
||||
out = v;
|
||||
}
|
||||
if (!out || (p->fb_out && p->fb_out != out))
|
||||
return false;
|
||||
p->fb_out = out;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void pr_io_decl(struct rslc_pr *p, const struct rslc_var *v, bool is_in)
|
||||
{
|
||||
bool layout_ok = p->es ? p->version >= 300 : p->version >= 330;
|
||||
@@ -4528,6 +4580,11 @@ static void pr_io_decl(struct rslc_pr *p, const struct rslc_var *v, bool is_in)
|
||||
pr_s(p, "centroid ");
|
||||
if (v->quals & RSLC_VQ_SAMPLE)
|
||||
pr_s(p, "sample ");
|
||||
#ifdef RSLC_EXTENDED
|
||||
if (v == p->fb_out)
|
||||
pr_s(p, "inout ");
|
||||
else
|
||||
#endif
|
||||
pr_s(p, is_in ? "in " : "out ");
|
||||
pr_precision(p, v->precision, v->type);
|
||||
pr_type(p, v->type);
|
||||
@@ -4719,6 +4776,31 @@ static bool pr_glsl(struct rslc_pr *p, struct rslc_result *result)
|
||||
pr_s(p, "#extension GL_ARB_shader_bit_encoding : require");
|
||||
pr_nl(p);
|
||||
}
|
||||
#ifdef RSLC_EXTENDED
|
||||
for (i = 1; i < pr->nvars; i++)
|
||||
if (pr->vars[i]->storage == RSLC_ST_SUBPASS)
|
||||
{
|
||||
pr_s(p, c->opts->fb_fetch == RSLC_FB_FETCH_ARM
|
||||
? "#extension GL_ARM_shader_framebuffer_fetch : require"
|
||||
: "#extension GL_EXT_shader_framebuffer_fetch : require");
|
||||
pr_nl(p);
|
||||
break;
|
||||
}
|
||||
if (pr->early_fragment_tests && c->opts->early_z_depth_unchanged
|
||||
&& (p->es || p->version < 420))
|
||||
{
|
||||
pr_s(p, p->es ? "#extension GL_EXT_conservative_depth : require"
|
||||
: "#extension GL_ARB_conservative_depth : require");
|
||||
pr_nl(p);
|
||||
}
|
||||
if (pr->subgroup)
|
||||
{
|
||||
pr_s(p, "#extension GL_KHR_shader_subgroup_basic : require");
|
||||
pr_nl(p);
|
||||
pr_s(p, "#extension GL_KHR_shader_subgroup_arithmetic : require");
|
||||
pr_nl(p);
|
||||
}
|
||||
#endif
|
||||
if (!p->es && p->version < 400 && p->uses_gather)
|
||||
{
|
||||
pr_s(p, "#extension GL_ARB_texture_gather : require");
|
||||
@@ -4774,6 +4856,7 @@ static bool pr_glsl(struct rslc_pr *p, struct rslc_result *result)
|
||||
for (i = 0; i < pr->nsamplers; i++)
|
||||
{
|
||||
pr_s(p, "uniform ");
|
||||
pr_precision(p, pr->samplers[i]->precision, pr->samplers[i]->type);
|
||||
pr_type(p, pr->samplers[i]->type);
|
||||
pr_c(p, ' ');
|
||||
pr_var_name(p, pr->samplers[i]);
|
||||
@@ -4783,6 +4866,13 @@ static bool pr_glsl(struct rslc_pr *p, struct rslc_result *result)
|
||||
}
|
||||
if (pr->early_fragment_tests)
|
||||
{
|
||||
#ifdef RSLC_EXTENDED
|
||||
/* the driver may then test depth early, as the stage does not
|
||||
* change it */
|
||||
if (c->opts->early_z_depth_unchanged)
|
||||
pr_s(p, "layout(depth_unchanged) out float gl_FragDepth;");
|
||||
else
|
||||
#endif
|
||||
pr_s(p, "layout(early_fragment_tests) in;");
|
||||
pr_nl(p);
|
||||
}
|
||||
@@ -4834,6 +4924,7 @@ static bool pr_glsl(struct rslc_pr *p, struct rslc_result *result)
|
||||
pr_s(p, "readonly ");
|
||||
if (v->quals & RSLC_VQ_WRITEONLY)
|
||||
pr_s(p, "writeonly ");
|
||||
pr_precision(p, v->precision, v->type);
|
||||
}
|
||||
else if (v->storage == RSLC_ST_SHARED)
|
||||
pr_s(p, "shared ");
|
||||
@@ -6687,12 +6778,11 @@ bool rslc_emit_text(struct rslc_ctx *c, struct rslc_result *result)
|
||||
rslc_err(c, p->hlsl ? "hlsl: no geometry stage yet" : "msl: no geometry stage", NULL, 0);
|
||||
return false;
|
||||
}
|
||||
/* subgroup operations have a form in SPIR-V and MSL alone; HLSL's
|
||||
* shader models here have no helper invocation either */
|
||||
if (!p->msl && pr->subgroup)
|
||||
/* subgroup operations have no form in HLSL's shader models here,
|
||||
* which have no helper invocation either */
|
||||
if (p->hlsl && pr->subgroup)
|
||||
{
|
||||
rslc_err(c, p->hlsl ? "hlsl: no form of subgroup operations"
|
||||
: "glsl: no form of subgroup operations", NULL, 0);
|
||||
rslc_err(c, "hlsl: no form of subgroup operations", NULL, 0);
|
||||
return false;
|
||||
}
|
||||
if (p->hlsl)
|
||||
@@ -6703,8 +6793,19 @@ bool rslc_emit_text(struct rslc_ctx *c, struct rslc_result *result)
|
||||
rslc_err(c, "hlsl: no form of gl_HelperInvocation", NULL, 0);
|
||||
return false;
|
||||
}
|
||||
/* an input attachment has a form in MSL alone */
|
||||
if (!p->msl)
|
||||
/* an input attachment has a form in MSL, and in GLSL where the caller
|
||||
* names the framebuffer fetch extension */
|
||||
if (!p->msl && !p->hlsl && c->opts->fb_fetch != RSLC_FB_FETCH_NONE)
|
||||
{
|
||||
for (k = 1; k < pr->nvars; k++)
|
||||
if (pr->vars[k]->storage == RSLC_ST_SUBPASS && !pr_fb_fetch_output(p, pr->vars[k]))
|
||||
{
|
||||
rslc_err(c, "glsl: no colour output for the input attachment", NULL,
|
||||
pr->vars[k]->line);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!p->msl)
|
||||
for (k = 1; k < pr->nvars; k++)
|
||||
if (pr->vars[k]->storage == RSLC_ST_SUBPASS)
|
||||
{
|
||||
|
||||
@@ -176,6 +176,23 @@ int crypto_aes_hw(void);
|
||||
void aes_ctr_crypt(const struct aes_ctx *ctx, uint8_t *counter,
|
||||
const uint8_t *in, uint8_t *out, size_t len);
|
||||
|
||||
/**
|
||||
* aes_cfb128_encrypt:
|
||||
* @iv : 16 octets; left holding the feedback register,
|
||||
* so a caller can continue the stream.
|
||||
* @num : octets of the register's keystream already
|
||||
* used, 0 at the start of a stream; updated.
|
||||
* @len : any length.
|
||||
*
|
||||
* CFB mode with 128-bit feedback (SP 800-38A), as OpenSSL's
|
||||
* AES_cfb128_encrypt: a stream split across calls gives the same
|
||||
* octets as one call. @in and @out may alias.
|
||||
**/
|
||||
void aes_cfb128_encrypt(const struct aes_ctx *ctx, uint8_t *iv,
|
||||
unsigned *num, const uint8_t *in, uint8_t *out, size_t len);
|
||||
void aes_cfb128_decrypt(const struct aes_ctx *ctx, uint8_t *iv,
|
||||
unsigned *num, const uint8_t *in, uint8_t *out, size_t len);
|
||||
|
||||
/**
|
||||
* aes_cbc_encrypt:
|
||||
* @iv : 16 octets; left holding the last ciphertext
|
||||
|
||||
@@ -185,9 +185,34 @@ void rdeflate_set_out(void *stream, uint8_t *out, size_t out_size);
|
||||
/* Signal that no more input will be supplied after the current input buffer
|
||||
* is consumed, so the final block and trailer can be emitted. */
|
||||
void rdeflate_finish(void *stream);
|
||||
/**
|
||||
* rdeflate_flush:
|
||||
* @stream : stream returned by rdeflate_new()
|
||||
*
|
||||
* Sync flush, zlib's Z_SYNC_FLUSH: once the current input is consumed,
|
||||
* everything given so far is written out, ending with an empty stored
|
||||
* block on a byte boundary, so a reader of the output up to there
|
||||
* decodes all of it - a log compressed as it is written stays readable
|
||||
* up to its last line after a crash. Call rdeflate_process() until it
|
||||
* returns with room left in the output; the flush is then done and the
|
||||
* stream goes on, its window kept. A no-op once rdeflate_finish() was
|
||||
* called.
|
||||
*/
|
||||
void rdeflate_flush(void *stream);
|
||||
|
||||
int rdeflate_process(void *stream, size_t *read, size_t *wrote);
|
||||
|
||||
/**
|
||||
* encoding_adler32:
|
||||
* @adler : the checksum so far, 1 to start
|
||||
* @buf : the data
|
||||
* @len : its length
|
||||
*
|
||||
* Adler-32 (RFC 1950) of @buf continued from @adler, as zlib's adler32()
|
||||
* gives it: the checksum rinflate and rdeflate keep for the zlib wrapper.
|
||||
*/
|
||||
uint32_t encoding_adler32(uint32_t adler, const uint8_t *buf, size_t len);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,50 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (shiftjis.h).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef _LIBRETRO_ENCODINGS_SHIFTJIS_H
|
||||
#define _LIBRETRO_ENCODINGS_SHIFTJIS_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <retro_common_api.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
/* Shift-JIS here is Windows code page 932: JIS X 0208 with the NEC and
|
||||
* IBM extensions, without the user-defined area. Each conversion skips
|
||||
* input it cannot convert, an octet at a time, writes at most
|
||||
* @out_size - 1 octets and a NUL, stopping before a character that does
|
||||
* not fit, and returns the octets written, the NUL not counted. */
|
||||
|
||||
/* An @out_size of 3 * @in_len + 1 always suffices. */
|
||||
size_t sjis_to_utf8(char *out, size_t out_size, const char *in, size_t in_len);
|
||||
|
||||
/* An @out_size of @in_len + 1 always suffices. */
|
||||
size_t utf8_to_sjis(char *out, size_t out_size, const char *in, size_t in_len);
|
||||
|
||||
/* Windows code page 1252; an @out_size of 3 * @in_len + 1 always
|
||||
* suffices. */
|
||||
size_t cp1252_to_utf8(char *out, size_t out_size, const char *in, size_t in_len);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -98,7 +98,8 @@ enum image_type_enum
|
||||
IMAGE_TYPE_WEBP,
|
||||
IMAGE_TYPE_DDS,
|
||||
IMAGE_TYPE_WEBM,
|
||||
IMAGE_TYPE_MP4
|
||||
IMAGE_TYPE_MP4,
|
||||
IMAGE_TYPE_GIF
|
||||
};
|
||||
|
||||
#define IMAGE_MAX_MIPS 16
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (ratrac9.h).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __LIBRETRO_SDK_FORMAT_RATRAC9_H__
|
||||
#define __LIBRETRO_SDK_FORMAT_RATRAC9_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <retro_common_api.h>
|
||||
|
||||
#include <boolean.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
/* Decode errors (negative). */
|
||||
#define RATRAC9_ERR_CONFIG -1 /* bad configuration word */
|
||||
#define RATRAC9_ERR_SUPERFRAME -2 /* first-in-superframe block mid superframe */
|
||||
#define RATRAC9_ERR_REUSE_PARAMS -3 /* band parameter reuse in a superframe's first frame */
|
||||
#define RATRAC9_ERR_BAND_PARAMS -4 /* band parameters out of range */
|
||||
#define RATRAC9_ERR_GRAD_BOUNDARY -5 /* gradient boundary above the unit count */
|
||||
#define RATRAC9_ERR_GRAD_START_UNIT -6 /* gradient start unit out of range */
|
||||
#define RATRAC9_ERR_GRAD_END_UNIT -7 /* gradient end unit out of range */
|
||||
#define RATRAC9_ERR_GRAD_START_VALUE -8 /* gradient start value out of range */
|
||||
#define RATRAC9_ERR_GRAD_END_VALUE -9 /* gradient end value out of range */
|
||||
#define RATRAC9_ERR_GRAD_ORDER -10 /* gradient start unit after end unit */
|
||||
#define RATRAC9_ERR_SF_MODE -11 /* scale factor mode needs a previous frame */
|
||||
#define RATRAC9_ERR_SF_RANGE -12 /* scale factor out of range */
|
||||
#define RATRAC9_ERR_EXT_LENGTH -13 /* extension data longer than declared */
|
||||
|
||||
typedef struct ratrac9 ratrac9_t;
|
||||
|
||||
/* The 4 configuration bytes. NULL for a bad config or no memory. */
|
||||
ratrac9_t *ratrac9_open(const uint8_t config[4]);
|
||||
void ratrac9_close(ratrac9_t *d);
|
||||
/* Back to the state just after ratrac9_open, without allocating. */
|
||||
void ratrac9_reset(ratrac9_t *d);
|
||||
|
||||
unsigned ratrac9_channels(const ratrac9_t *d);
|
||||
unsigned ratrac9_sample_rate(const ratrac9_t *d);
|
||||
unsigned ratrac9_frame_samples(const ratrac9_t *d); /* per channel */
|
||||
unsigned ratrac9_frame_bytes(const ratrac9_t *d);
|
||||
unsigned ratrac9_frames_per_superframe(const ratrac9_t *d);
|
||||
unsigned ratrac9_superframe_bytes(const ratrac9_t *d);
|
||||
unsigned ratrac9_channel_config(const ratrac9_t *d); /* channel_config_index */
|
||||
|
||||
/* Decode one frame from in[0..len): frame_samples per channel into out, interleaved,
|
||||
* or planar when planar is true. *used gets the bytes the frame took (it can exceed
|
||||
* len for a frame cut short; the decoder never reads past len).
|
||||
* Returns 0, or one of the negative RATRAC9_ERR_* codes. */
|
||||
int ratrac9_decode_s16(ratrac9_t *d, const uint8_t *in, size_t len,
|
||||
int16_t *out, bool planar, size_t *used);
|
||||
int ratrac9_decode_s32(ratrac9_t *d, const uint8_t *in, size_t len,
|
||||
int32_t *out, bool planar, size_t *used);
|
||||
int ratrac9_decode_f32(ratrac9_t *d, const uint8_t *in, size_t len,
|
||||
float *out, bool planar, size_t *used);
|
||||
|
||||
/* The decoder's history as bytes, for a savestate: ratrac9_state_size bytes
|
||||
* out, and back into a decoder opened with the same config. Load refuses any
|
||||
* other size, a state from another config, or out-of-range fields (and then
|
||||
* leaves the decoder unchanged). Fixed-width little-endian fields. */
|
||||
size_t ratrac9_state_size(const ratrac9_t *d);
|
||||
void ratrac9_save_state(const ratrac9_t *d, void *data);
|
||||
bool ratrac9_load_state(ratrac9_t *d, const void *data, size_t size);
|
||||
|
||||
/* Reads a WAVE 'fmt ' chunk's body: format tag 0xFFFE, 52 bytes, the ATRAC9
|
||||
* sub-format GUID at bytes 24..39, the config bytes at 44..47. Copies the
|
||||
* config bytes out; false if the chunk is not that. */
|
||||
bool ratrac9_parse_wave_fmt(const uint8_t *fmt, size_t len, uint8_t config[4]);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -73,6 +73,16 @@ void rdds_decode_block_bc2(const void *block, void *out, int pitch);
|
||||
void rdds_decode_block_bc3(const void *block, void *out, int pitch);
|
||||
void rdds_decode_block_bc7(const void *block, void *out, int pitch);
|
||||
|
||||
/**
|
||||
* rdds_decode_block_bc1_bgra / bc2_bgra / bc3_bgra:
|
||||
*
|
||||
* The same, B,G,R,A in memory (a BGRA8 texture). The same values as the
|
||||
* RGBA decoders, red and blue swapped.
|
||||
*/
|
||||
void rdds_decode_block_bc1_bgra(const void *block, void *out, int pitch);
|
||||
void rdds_decode_block_bc2_bgra(const void *block, void *out, int pitch);
|
||||
void rdds_decode_block_bc3_bgra(const void *block, void *out, int pitch);
|
||||
|
||||
rdds_t *rdds_alloc(void);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
@@ -40,6 +40,13 @@ typedef struct rflv_span
|
||||
size_t size;
|
||||
} rflv_span_t;
|
||||
|
||||
typedef struct rflv_tag
|
||||
{
|
||||
rflv_span_t spans[2];
|
||||
uint32_t timestamp;
|
||||
unsigned type;
|
||||
} rflv_tag_t;
|
||||
|
||||
enum rflv_result
|
||||
{
|
||||
RFLV_ERROR = -1, RFLV_BUSY, RFLV_PROGRESS,
|
||||
@@ -61,6 +68,10 @@ int rflv_mux_step(rflv_mux_t *mux);
|
||||
/* Up to three borrowed spans. No payload copy, allocation, I/O or wait. */
|
||||
bool rflv_mux_get(rflv_mux_t *mux,
|
||||
const rflv_span_t **spans, size_t *count);
|
||||
/* Ready codec payload for streaming, without the file tag header/trailer.
|
||||
* False for the initial file header. Borrows last until mux output release.
|
||||
* A streaming consumer must return its borrow before releasing mux output. */
|
||||
bool rflv_mux_get_tag(rflv_mux_t *mux, rflv_tag_t *tag);
|
||||
bool rflv_mux_release(rflv_mux_t *mux);
|
||||
/* Requires acknowledged output. No seek or file trailer. */
|
||||
bool rflv_mux_finish(rflv_mux_t *mux);
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (rgif.h).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __LIBRETRO_SDK_FORMAT_RGIF_H__
|
||||
#define __LIBRETRO_SDK_FORMAT_RGIF_H__
|
||||
|
||||
/* A GIF's first image, decoded where the caller wants it.
|
||||
*
|
||||
* The file is read where it lies, nothing copied; the pixels go straight
|
||||
* into the caller's memory at the caller's row pitch, in the byte order
|
||||
* asked for, eight bits a channel. The decoder's own memory (the LZW
|
||||
* table, about 20 KB) is taken by rgif_new() and kept, so decoding one
|
||||
* image after another takes nothing more.
|
||||
*
|
||||
* The picture is the GIF's whole logical screen. Where the first image
|
||||
* does not cover it, the screen's background colour shows, opaque, if
|
||||
* the file names one other than entry 0 of its colour table; otherwise
|
||||
* those pixels are transparent black, as are the image's own transparent
|
||||
* pixels and indices past its colour table - as stb_image gives the first
|
||||
* frame. A file cut short gives what was decoded before the cut. */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <retro_common_api.h>
|
||||
#include <boolean.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
/* The byte order of each pixel written */
|
||||
enum rgif_order
|
||||
{
|
||||
RGIF_RGBA = 0,
|
||||
RGIF_ARGB,
|
||||
RGIF_BGRA,
|
||||
RGIF_ABGR
|
||||
};
|
||||
|
||||
typedef struct rgif rgif_t;
|
||||
|
||||
rgif_t *rgif_new(void);
|
||||
void rgif_free(rgif_t *g);
|
||||
|
||||
/**
|
||||
* rgif_size:
|
||||
* @data, @len : the file
|
||||
*
|
||||
* The logical screen's size from the header, without decoding.
|
||||
*
|
||||
* Returns: false if it is not a GIF.
|
||||
*/
|
||||
bool rgif_size(const uint8_t *data, size_t len,
|
||||
unsigned *width, unsigned *height);
|
||||
|
||||
/**
|
||||
* rgif_decode:
|
||||
* @data, @len : the file
|
||||
* @out : rgif_size()'s width x height pixels of 4 bytes, @stride
|
||||
* bytes from one row to the next (at least width * 4)
|
||||
* @order : RGIF_RGBA, RGIF_ARGB, RGIF_BGRA or RGIF_ABGR
|
||||
*
|
||||
* Decodes the first image into @out.
|
||||
*
|
||||
* Returns: false if it is not a GIF, or is broken before its first
|
||||
* image's pixels: an image past the screen, no colour table, a bad LZW
|
||||
* stream, a block that is no GIF block. @out may be partly written.
|
||||
*/
|
||||
bool rgif_decode(rgif_t *g, const uint8_t *data, size_t len,
|
||||
uint8_t *out, size_t stride, unsigned order);
|
||||
|
||||
/* The image_transfer interface (formats/image.h), as the other image
|
||||
* decoders have it: the first image as a 32-bit texture of its own,
|
||||
* ARGB32 or, with @supports_rgba, ABGR32, in native byte order.
|
||||
*
|
||||
* rgif_set_avail() says how much of the buffer is resident yet; until
|
||||
* the whole file is, rgif_process_image() returns IMAGE_PROCESS_WAIT and
|
||||
* takes nothing (a GIF decodes in one go). rgif_header_ready() says
|
||||
* whether @len bytes hold the header. */
|
||||
bool rgif_header_ready(const uint8_t *data, size_t len);
|
||||
bool rgif_set_buf_ptr(rgif_t *g, void *data, size_t len);
|
||||
void rgif_set_avail(rgif_t *g, size_t avail);
|
||||
int rgif_process_image(rgif_t *g, void **buf, size_t size,
|
||||
unsigned *width, unsigned *height, bool supports_rgba);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -116,9 +116,41 @@ typedef struct
|
||||
size_t dataSize;
|
||||
size_t readPos; /* Read cursor into pData. */
|
||||
uint32_t atEnd;
|
||||
/* Gapless playback, from the Xing/Info tag frame when there is one:
|
||||
* the tag frame is not decoded, the encoder delay and the decoder's
|
||||
* own RMP3_DECODER_DELAY are dropped from the start, and output
|
||||
* stops at the track's end, before the encoder padding. */
|
||||
uint64_t totalFrames; /* PCM frames the reads give in all; 0 when not known. */
|
||||
uint64_t framesRead; /* PCM frames returned so far. Internal. */
|
||||
uint64_t startSkip; /* PCM frames dropped at the start. Internal. */
|
||||
uint64_t skipLeft; /* Of those, still to drop. Internal. */
|
||||
size_t dataStart; /* First byte after a tag frame. Internal. */
|
||||
} rmp3;
|
||||
|
||||
|
||||
/* The samples a Layer III decoder outputs before the first one the
|
||||
* encoder was given: the synthesis filter's delay. */
|
||||
#define RMP3_DECODER_DELAY 529
|
||||
|
||||
/* What the tag frame at the start of an encoded stream says: a Xing or
|
||||
* Info tag (LAME and most encoders since), with LAME's extension where
|
||||
* the encoder wrote one, or a Fraunhofer VBRI tag. The tag frame decodes
|
||||
* to silence and is not part of the track. */
|
||||
typedef struct
|
||||
{
|
||||
uint32_t frames; /* MPEG frames after the tag frame; 0 when not given. */
|
||||
uint32_t bytes; /* Stream bytes; 0 when not given. */
|
||||
uint32_t encoder_delay; /* Samples the encoder put before the audio (LAME extension). */
|
||||
uint32_t encoder_padding; /* Samples the encoder put after it (LAME extension). */
|
||||
uint32_t frame_samples; /* Samples a channel in each frame: 1152, 576 or 384. */
|
||||
uint8_t has_delay; /* The LAME extension was there. */
|
||||
uint8_t is_vbri;
|
||||
} rmp3_vbr_tag;
|
||||
|
||||
/* Reads the tag in @frame, the @frame_bytes of one whole MPEG audio
|
||||
* frame starting at its header. Returns 1 when the frame is a tag frame. */
|
||||
int rmp3_parse_vbr_tag(const uint8_t *frame, size_t frame_bytes, rmp3_vbr_tag *tag);
|
||||
|
||||
/* Initializes an MP3 decoder from a block of memory.
|
||||
*
|
||||
* This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for
|
||||
@@ -145,7 +177,8 @@ uint64_t rmp3_read_s16(rmp3* pMP3, uint64_t framesToRead, int16_t* pBufferOut);
|
||||
|
||||
/* Seeks to a specific frame.
|
||||
*
|
||||
* Note that this is _not_ an MP3 frame, but rather a PCM frame.
|
||||
* Note that this is _not_ an MP3 frame, but rather a PCM frame, counted
|
||||
* as the reads count them: after the trimmed start.
|
||||
*/
|
||||
uint32_t rmp3_seek_to_frame(rmp3* pMP3, uint64_t frameIndex);
|
||||
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (rpng_rows.h).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __LIBRETRO_SDK_FORMAT_RPNG_ROWS_H__
|
||||
#define __LIBRETRO_SDK_FORMAT_RPNG_ROWS_H__
|
||||
|
||||
/* A PNG decoded a row at a time, from input handed over a piece at a time,
|
||||
* each row as the PNG stores it: the filter undone, nothing converted -
|
||||
* a palette index, gray at 1, 2, 4, 8 or 16 bits, RGB or RGBA at 8 or 16
|
||||
* bits a sample, big-endian. What is done with the samples is the
|
||||
* caller's: rpng decodes to 32-bit pixels, this is for a caller that has
|
||||
* conversions of its own to apply (a console's PNG decoder library,
|
||||
* emulated), or that wants rows as the data arrives.
|
||||
*
|
||||
* rpng_rows_step() takes what input it is given, as much as it needs for
|
||||
* the next thing to report, and says how much it took; the rest is given
|
||||
* to it again in the next call, with whatever followed. It reports:
|
||||
*
|
||||
* RPNG_ROWS_HEADER once, at the first IDAT: the header and every chunk
|
||||
* before the image data have been read (the palette,
|
||||
* tRNS, which ancillary chunks there were)
|
||||
* RPNG_ROWS_ROW a row, valid until the next call
|
||||
* RPNG_ROWS_END IEND: the image is complete and its chunks counted
|
||||
* RPNG_ROWS_NEXT the input is all taken; more is wanted
|
||||
* RPNG_ROWS_ERROR the file is not a PNG, or is broken
|
||||
*
|
||||
* An interlaced (Adam7) image comes out pass by pass, each row of a pass
|
||||
* naming the image row it belongs to and the columns its pixels go to.
|
||||
*
|
||||
* The decoder takes its memory when the header is read - two rows and an
|
||||
* inflate state - and nothing after. Every chunk's CRC is checked; a bad
|
||||
* one in a critical chunk is an error, in an ancillary chunk it drops the
|
||||
* chunk, as libpng does.
|
||||
*
|
||||
* The colour chunks (gAMA, cHRM, sRGB, sBIT, cICP, mDCV) are kept as the
|
||||
* file states them, for a caller that converts colour as libpng would:
|
||||
* one of the wrong length, out of place (after PLTE or the image data),
|
||||
* a second of a kind, or one holding a value the specification forbids
|
||||
* is dropped, as libpng drops it. */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <retro_common_api.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
enum
|
||||
{
|
||||
RPNG_ROWS_ERROR = -1,
|
||||
RPNG_ROWS_NEXT = 0,
|
||||
RPNG_ROWS_HEADER = 1,
|
||||
RPNG_ROWS_ROW = 2,
|
||||
RPNG_ROWS_END = 3
|
||||
};
|
||||
|
||||
/* The ancillary chunks seen, as bits of rpng_rows_info.chunks. A chunk
|
||||
* where the PNG specification does not allow it (gAMA after PLTE, bKGD
|
||||
* after IDAT, hIST without a palette, tRNS on an image with alpha) is
|
||||
* dropped, as libpng drops it. Bits 0 to 15 are in the order console PNG
|
||||
* libraries report them in. */
|
||||
enum
|
||||
{
|
||||
RPNG_ROWS_GAMA = 1 << 0,
|
||||
RPNG_ROWS_SBIT = 1 << 1,
|
||||
RPNG_ROWS_CHRM = 1 << 2,
|
||||
RPNG_ROWS_PLTE = 1 << 3,
|
||||
RPNG_ROWS_TRNS = 1 << 4,
|
||||
RPNG_ROWS_BKGD = 1 << 5,
|
||||
RPNG_ROWS_HIST = 1 << 6,
|
||||
RPNG_ROWS_PHYS = 1 << 7,
|
||||
RPNG_ROWS_OFFS = 1 << 8,
|
||||
RPNG_ROWS_TIME = 1 << 9,
|
||||
RPNG_ROWS_PCAL = 1 << 10,
|
||||
RPNG_ROWS_SRGB = 1 << 11,
|
||||
RPNG_ROWS_ICCP = 1 << 12,
|
||||
RPNG_ROWS_SPLT = 1 << 13,
|
||||
RPNG_ROWS_SCAL = 1 << 14,
|
||||
RPNG_ROWS_IDAT = 1 << 15,
|
||||
RPNG_ROWS_CICP = 1 << 16,
|
||||
RPNG_ROWS_MDCV = 1 << 17
|
||||
};
|
||||
|
||||
struct rpng_rows_info
|
||||
{
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t chunks; /* RPNG_ROWS_* */
|
||||
uint32_t text_chunks; /* tEXt, zTXt and iTXt */
|
||||
uint32_t unknown_chunks; /* ancillary, safe to copy, not known here */
|
||||
uint16_t palette_len; /* PLTE entries */
|
||||
uint16_t trns_len; /* tRNS: palette alphas, or 2 (gray) or 6 (RGB) */
|
||||
uint8_t depth; /* bits a sample: 1, 2, 4, 8 or 16 */
|
||||
uint8_t color_type; /* 0 gray, 2 RGB, 3 palette, 4 gray+alpha, 6 RGBA */
|
||||
uint8_t channels; /* 1 to 4 */
|
||||
uint8_t interlace; /* 1: Adam7 */
|
||||
uint8_t palette[256 * 3];/* RGB triples */
|
||||
uint8_t trns[256]; /* palette alphas, or the key colour as stored */
|
||||
|
||||
/* The colour chunks, each valid where its bit is in chunks */
|
||||
uint32_t gama; /* gAMA: the image's gamma times 100000 */
|
||||
int32_t chrm[8]; /* cHRM: white, red, green and blue x and y,
|
||||
times 100000 */
|
||||
uint16_t mdcv[8]; /* mDCV: red, green, blue and white x and y,
|
||||
times 50000 */
|
||||
uint32_t mdcv_luminance[2];/* mDCV: maximum and minimum, 0.0001 cd/m2 */
|
||||
uint8_t cicp[4]; /* cICP: primaries, transfer function, matrix,
|
||||
full range */
|
||||
uint8_t sbit[4]; /* sBIT: significant bits of each channel,
|
||||
R, G, B for a palette */
|
||||
uint8_t srgb; /* sRGB: the rendering intent */
|
||||
/* Where each came among the colour chunks, from 1: for rules that go
|
||||
* by which came first (libpng's gamma: sRGB's over gAMA's, unless a
|
||||
* cICP came before both) */
|
||||
uint8_t gama_at, chrm_at, srgb_at, sbit_at, cicp_at, mdcv_at;
|
||||
};
|
||||
|
||||
struct rpng_rows_row
|
||||
{
|
||||
const uint8_t *data; /* the row's pixels, packed as the PNG stores them */
|
||||
uint32_t y; /* the image row it is */
|
||||
uint32_t x0; /* the column of its first pixel */
|
||||
uint32_t dx; /* columns from one pixel to the next */
|
||||
uint32_t width; /* pixels in it */
|
||||
uint8_t pass; /* Adam7 pass 1 to 7; 0 for a plain image */
|
||||
};
|
||||
|
||||
typedef struct rpng_rows rpng_rows_t;
|
||||
|
||||
rpng_rows_t *rpng_rows_new(void);
|
||||
void rpng_rows_free(rpng_rows_t *z);
|
||||
|
||||
/* Back to the state rpng_rows_new() left, for the next file, keeping the
|
||||
* memory the last one took where the next fits in it. */
|
||||
void rpng_rows_reset(rpng_rows_t *z);
|
||||
|
||||
/**
|
||||
* rpng_rows_step:
|
||||
* @in : the next bytes of the file
|
||||
* @len : how many
|
||||
* @consumed : receives how many it took
|
||||
* @row : receives the row for RPNG_ROWS_ROW
|
||||
*
|
||||
* Returns: one of the RPNG_ROWS_* events, as above.
|
||||
*/
|
||||
int rpng_rows_step(rpng_rows_t *z, const uint8_t *in, size_t len,
|
||||
size_t *consumed, struct rpng_rows_row *row);
|
||||
|
||||
/* What has been read so far: complete for the header once
|
||||
* RPNG_ROWS_HEADER is reported, for the chunk counts at RPNG_ROWS_END. */
|
||||
const struct rpng_rows_info *rpng_rows_info(const rpng_rows_t *z);
|
||||
|
||||
/* Bytes in a row of @width pixels of the image's format */
|
||||
size_t rpng_rows_rowbytes(const struct rpng_rows_info *info, uint32_t width);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <retro_common_api.h>
|
||||
#include <boolean.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
@@ -116,6 +117,21 @@ int rvz_disc_type(const rvz_t *rvz);
|
||||
/* the size of the disc image */
|
||||
uint64_t rvz_disc_size(const rvz_t *rvz);
|
||||
|
||||
/* How the file's groups are compressed */
|
||||
#define RVZ_COMPRESSION_NONE 0
|
||||
#define RVZ_COMPRESSION_PURGE 1
|
||||
#define RVZ_COMPRESSION_BZIP2 2
|
||||
#define RVZ_COMPRESSION_LZMA 3
|
||||
#define RVZ_COMPRESSION_LZMA2 4
|
||||
#define RVZ_COMPRESSION_ZSTD 5
|
||||
|
||||
/* The open file's RVZ_COMPRESSION_* method, -1 before it is open; and
|
||||
* at @level (may be NULL) the compression level the file records */
|
||||
int rvz_compression(const rvz_t *rvz, int *level);
|
||||
|
||||
/* the octets of the disc a group holds, 0 before the file is open */
|
||||
uint32_t rvz_chunk_size(const rvz_t *rvz);
|
||||
|
||||
/**
|
||||
* rvz_read_begin:
|
||||
*
|
||||
@@ -139,6 +155,35 @@ int rvz_read_step(rvz_t *rvz, rvz_request_t *req);
|
||||
/* octets the armed read has put at its @dst so far */
|
||||
size_t rvz_read_progress(const rvz_t *rvz);
|
||||
|
||||
/**
|
||||
* rvz_decrypted_supported:
|
||||
* @partition_data : where the Wii partition's data starts on the disc
|
||||
* @off : an offset in that data, decrypted: 0x7c00 octets a
|
||||
* sector, with no hashes
|
||||
* @len : octets from there
|
||||
*
|
||||
* Whether the range is one rvz_read_decrypted_begin() reads: it ends
|
||||
* within one of the partition's data segments.
|
||||
**/
|
||||
bool rvz_decrypted_supported(const rvz_t *rvz, uint64_t partition_data,
|
||||
uint64_t off, uint64_t len);
|
||||
|
||||
/**
|
||||
* rvz_read_decrypted_begin:
|
||||
*
|
||||
* Arms a read of @len octets of a Wii partition's data at @off as it
|
||||
* is decrypted, 0x7c00 octets a sector, into @dst, run with
|
||||
* rvz_read_step() as rvz_read_begin()'s is. The file holds that data
|
||||
* decrypted, so it comes as it is stored: no hashes are worked out and
|
||||
* nothing is encrypted, as reading the disc image would, only to be
|
||||
* decrypted again. A range past the partition's data fails the read.
|
||||
*
|
||||
* Returns: RVZ_OK, RVZ_ERROR_PARAM when no partition's data starts at
|
||||
* @partition_data, or RVZ_ERROR_STATE before the image is open.
|
||||
**/
|
||||
int rvz_read_decrypted_begin(rvz_t *rvz, uint64_t partition_data,
|
||||
uint64_t off, void *dst, size_t len);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -55,9 +55,14 @@
|
||||
* nested block mappings, block sequences, compact mappings written on a
|
||||
* sequence dash, plain and quoted scalars, literal and folded block
|
||||
* scalars with the three chomping modes, {} and [] flow collections,
|
||||
* and comments. Anchors, aliases, tags, directives, multi-document
|
||||
* streams and flow collections spanning several lines are not read; a
|
||||
* document using them is rejected rather than half-parsed. Tabs are
|
||||
* comments, and anchors and aliases: "&name" ahead of a value (or of the
|
||||
* lines that make it up) and "*name" as a value, in block and flow
|
||||
* collections alike. An alias is a node of its own, with its own key,
|
||||
* that holds what the anchored node holds: its value, its type and its
|
||||
* children, as every call below reads them. Tags, directives, merge keys,
|
||||
* multi-document streams and flow collections spanning several lines
|
||||
* are not read; a document using them, or an alias to a name not
|
||||
* anchored before it, is rejected rather than half-parsed. Tabs are
|
||||
* never indentation, which is what the specification says and what a
|
||||
* hand-written file usually means anyway.
|
||||
*/
|
||||
@@ -274,6 +279,28 @@ int ryaml_val_bool(const ryaml_t *y, int node, int *out);
|
||||
*/
|
||||
int ryaml_val_equals(const ryaml_t *y, int node, const char *str);
|
||||
|
||||
/**
|
||||
* ryaml_is_alias:
|
||||
* @y : tree
|
||||
* @node : node to ask about
|
||||
* Returns: 1 when @node is a "*name", standing for the node anchored as
|
||||
* name, 0 otherwise.
|
||||
*/
|
||||
int ryaml_is_alias(const ryaml_t *y, int node);
|
||||
|
||||
/**
|
||||
* ryaml_location:
|
||||
* @y : tree
|
||||
* @node : node to find
|
||||
* @line : filled with its 1-based line, may be NULL
|
||||
* @col : filled with its 1-based column, may be NULL
|
||||
* Where @node starts in the document: its key, its sequence dash, or its
|
||||
* value in a flow collection. For messages about a document; it walks
|
||||
* the document up to the node, so it is not for a lookup loop.
|
||||
* Returns: 1 on success, 0 for an invalid node.
|
||||
*/
|
||||
int ryaml_location(const ryaml_t *y, int node, size_t *line, size_t *col);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,118 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (ryaml_writer.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.
|
||||
*/
|
||||
|
||||
/* YAML writer: the counterpart of ryaml's reader. The caller walks its
|
||||
* data and says what comes next - a mapping, a key, a scalar, a sequence
|
||||
* - and the writer lays it out as block YAML, two spaces a level:
|
||||
*
|
||||
* Core:
|
||||
* PPU Threads: 2
|
||||
* Libraries: []
|
||||
* List:
|
||||
* - one
|
||||
* - key: value
|
||||
* other: value
|
||||
* Patch:
|
||||
* - [be32, 0x10000, 0x60000000]
|
||||
*
|
||||
* A sequence may instead be written in flow style on one line, as the
|
||||
* last entry shows. An empty mapping or sequence comes out as {} or [].
|
||||
* A key or scalar is quoted, with escapes, only where the reader would
|
||||
* not read it back as it is: empty, with blanks at either end, starting
|
||||
* with an indicator, holding ": " or " #" or a line break, or (in a flow
|
||||
* sequence) a comma or bracket. Whatever is written reads back with
|
||||
* ryaml_parse() to the same keys, values and structure.
|
||||
*
|
||||
* Output goes through a callback, a buffer at a time, or into a buffer
|
||||
* the writer grows and hands over.
|
||||
*/
|
||||
|
||||
#ifndef __LIBRETRO_SDK_FORMAT_RYAML_WRITER_H
|
||||
#define __LIBRETRO_SDK_FORMAT_RYAML_WRITER_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <retro_common_api.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct ryaml_writer ryaml_writer_t;
|
||||
|
||||
/* Takes @len bytes; returns 0 on failure, which the writer keeps and
|
||||
* reports from then on. */
|
||||
typedef int (*ryaml_writer_io_t)(const void *buf, size_t len, void *user_data);
|
||||
|
||||
/**
|
||||
* ryaml_writer_new:
|
||||
* @io : where output goes, NULL to keep it in memory
|
||||
* @user_data : passed to @io
|
||||
* Returns: the writer, or NULL when out of memory.
|
||||
*/
|
||||
ryaml_writer_t *ryaml_writer_new(ryaml_writer_io_t io, void *user_data);
|
||||
|
||||
/**
|
||||
* ryaml_writer_free:
|
||||
* @w : writer, may be NULL
|
||||
* Writes out what is left and frees the writer.
|
||||
* Returns: 1 when everything was written and well formed, 0 otherwise.
|
||||
*/
|
||||
int ryaml_writer_free(ryaml_writer_t *w);
|
||||
|
||||
/**
|
||||
* ryaml_writer_memory:
|
||||
* @w : writer made with a NULL @io
|
||||
* @len : filled with the length
|
||||
* The document so far, NUL-terminated, owned by the writer: valid until
|
||||
* the next call on it. NULL for a writer with a callback, or after a
|
||||
* failure.
|
||||
*/
|
||||
const char *ryaml_writer_memory(ryaml_writer_t *w, size_t *len);
|
||||
|
||||
/**
|
||||
* ryaml_writer_error:
|
||||
* Returns: 1 once a write failed or the calls did not make a document
|
||||
* (a scalar where a key was due, an end without a begin, ...).
|
||||
*/
|
||||
int ryaml_writer_error(const ryaml_writer_t *w);
|
||||
|
||||
/* A mapping: keys and their values until ryaml_writer_end_map(). As the
|
||||
* document itself, a value of a key, or an item of a block sequence. */
|
||||
void ryaml_writer_begin_map(ryaml_writer_t *w);
|
||||
void ryaml_writer_end_map(ryaml_writer_t *w);
|
||||
|
||||
/* A sequence: items until ryaml_writer_end_seq(). @flow writes it on one
|
||||
* line, [a, b]; its items are then scalars or flow sequences. */
|
||||
void ryaml_writer_begin_seq(ryaml_writer_t *w, int flow);
|
||||
void ryaml_writer_end_seq(ryaml_writer_t *w);
|
||||
|
||||
/* A key in the current mapping; its value comes next. */
|
||||
void ryaml_writer_key(ryaml_writer_t *w, const char *key, size_t len);
|
||||
|
||||
/* A scalar: the value of the last key, an item, or the document. */
|
||||
void ryaml_writer_scalar(ryaml_writer_t *w, const char *val, size_t len);
|
||||
|
||||
/* An empty line, between two entries of a block mapping or sequence. */
|
||||
void ryaml_writer_blank_line(ryaml_writer_t *w);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,241 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (rtt.h).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __LIBRETRO_SDK_GFX_FONT_RTT_H__
|
||||
#define __LIBRETRO_SDK_GFX_FONT_RTT_H__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <retro_common_api.h>
|
||||
|
||||
/* rtt - a clean-room TrueType glyph loader and rasterizer.
|
||||
*
|
||||
* Implements, from the OpenType/TrueType specification:
|
||||
* - sfnt / TTC directory parsing with full bounds checking
|
||||
* - cmap formats 0, 4, 6, 12 (codepoint -> glyph index)
|
||||
* - hmtx / hhea / head / maxp metrics
|
||||
* - glyf outlines (simple + composite) and CFF outlines (Type 2
|
||||
* charstrings, CID-keyed fonts included), flattened and filled by an
|
||||
* exact-coverage anti-aliased rasterizer (signed-area delta
|
||||
* accumulation + per-row prefix sum, SSE2/NEON resolve kernels)
|
||||
* - vhea/vmtx vertical metrics and 'kern' format 0 pair kerning
|
||||
*
|
||||
* Coverage is accumulated in float and quantized only in the final
|
||||
* per-row resolve step, so a future higher-bit-depth output path
|
||||
* (e.g. 10-bit HDR) only needs an alternate resolve routine.
|
||||
*
|
||||
* There is no hinting: outlines are drawn as designed.
|
||||
*
|
||||
* The font is read in place from the caller's bytes, which must stay
|
||||
* valid for as long as the rtt_font_t is used: nothing is copied and
|
||||
* no file is opened. Rendering allocates only to grow the caller's
|
||||
* rtt_scratch_t, which is kept from glyph to glyph.
|
||||
*/
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
/* Up to this many CID font dicts have their local subroutines found at
|
||||
* init; a font with more finds the rest per glyph. */
|
||||
#define RTT_CFF_MAX_FDS 32
|
||||
|
||||
/* A CFF INDEX: count items, offsets of off_size bytes at offsets (the
|
||||
* first is 1), item data from data - 1 + offset. */
|
||||
typedef struct rtt_cff_index
|
||||
{
|
||||
uint32_t count;
|
||||
uint32_t offsets;
|
||||
uint32_t data;
|
||||
uint32_t off_size;
|
||||
} rtt_cff_index_t;
|
||||
|
||||
typedef struct rtt_font
|
||||
{
|
||||
const uint8_t *data;
|
||||
size_t size;
|
||||
|
||||
uint32_t glyf;
|
||||
uint32_t loca;
|
||||
uint32_t head;
|
||||
uint32_t hhea;
|
||||
uint32_t hmtx;
|
||||
uint32_t cmap_sub;
|
||||
|
||||
uint32_t glyf_len;
|
||||
uint32_t loca_len;
|
||||
uint32_t hmtx_len;
|
||||
uint32_t cmap_sub_len;
|
||||
|
||||
int num_glyphs;
|
||||
int num_hmetrics;
|
||||
int units_per_em;
|
||||
int loca_long;
|
||||
int cmap_format;
|
||||
/* maxp.maxPoints: the largest simple-glyph point count in the font,
|
||||
* which sizes the outline scratch carved into each render's block
|
||||
* (0 when maxp is the CFF short form, leaving per-glyph fallback). */
|
||||
int max_points;
|
||||
|
||||
uint32_t fontstart;
|
||||
/* How far a flattened curve may stray from the true one, in pixels;
|
||||
* 0 for 0.35 */
|
||||
float flatness;
|
||||
|
||||
/* Vertical metrics and kerning; 0 when the font has none */
|
||||
uint32_t vhea;
|
||||
uint32_t vmtx;
|
||||
uint32_t vmtx_len;
|
||||
uint32_t kern;
|
||||
uint32_t kern_len;
|
||||
int num_vmetrics;
|
||||
|
||||
/* CFF outlines, for a font with no glyf table */
|
||||
int is_cff;
|
||||
int cff_num_fds;
|
||||
uint32_t cff;
|
||||
uint32_t cff_len;
|
||||
uint32_t cff_fdselect; /* 0 for a font that is not CID-keyed */
|
||||
rtt_cff_index_t cff_charstrings;
|
||||
rtt_cff_index_t cff_gsubrs;
|
||||
rtt_cff_index_t cff_subrs; /* the Private DICT's, when not CID-keyed */
|
||||
rtt_cff_index_t cff_fdarray;
|
||||
rtt_cff_index_t cff_fd_subrs[RTT_CFF_MAX_FDS];
|
||||
uint32_t cff_fdarray_off;
|
||||
} rtt_font_t;
|
||||
|
||||
/* Receives an outline in font units, y up. Every contour begins with
|
||||
* move_to and is closed back to that point. */
|
||||
typedef struct rtt_outline_sink
|
||||
{
|
||||
void *user;
|
||||
void (*move_to)(void *user, float x, float y);
|
||||
void (*line_to)(void *user, float x, float y);
|
||||
void (*quad_to)(void *user, float cx, float cy, float x, float y);
|
||||
void (*cubic_to)(void *user, float c1x, float c1y, float c2x, float c2y,
|
||||
float x, float y);
|
||||
} rtt_outline_sink;
|
||||
|
||||
/* A face's rasterizer memory, kept from glyph to glyph */
|
||||
typedef struct rtt_scratch
|
||||
{
|
||||
float *mem;
|
||||
size_t size;
|
||||
/* The cell the zeroed layout is for, packed; 0 for none */
|
||||
unsigned dims;
|
||||
} rtt_scratch_t;
|
||||
|
||||
/* Byte offset of face @index in @data (a TrueType file or a TTC
|
||||
* collection), or -1. */
|
||||
int rtt_font_offset_for_index(const uint8_t *data, size_t size,
|
||||
int index);
|
||||
/* Parses the face at @fontstart. Returns 0 when it is not a usable
|
||||
* TrueType face. */
|
||||
int rtt_init(rtt_font_t *f, const uint8_t *data, size_t size,
|
||||
int fontstart);
|
||||
/* Glyph index for a Unicode code point; 0 when there is none. */
|
||||
int rtt_find_glyph(const rtt_font_t *f, uint32_t cp);
|
||||
void rtt_glyph_hmetrics(const rtt_font_t *f, int gi,
|
||||
int *advance, int *lsb);
|
||||
int rtt_glyph_box(const rtt_font_t *f, int gi,
|
||||
int *x0, int *y0, int *x1, int *y1);
|
||||
void rtt_vmetrics(const rtt_font_t *f, int *ascent, int *descent,
|
||||
int *line_gap);
|
||||
float rtt_scale_for_em_to_pixels(const rtt_font_t *f, float px);
|
||||
/* Renders glyph @gi, whose box has xmin @bx0 and ymax @by1, into @dst
|
||||
* (@out_w x @out_h, @stride elements a row) at scale @sx, @sy: the
|
||||
* bitmap's origin is (floor(xmin * sx), floor(-ymax * sy)). The whole
|
||||
* region is written, as 8-bit coverage, or native 16-bit when @fmt16. */
|
||||
void rtt_render_glyph(const rtt_font_t *f, rtt_scratch_t *sc,
|
||||
void *dst, int out_w, int out_h, int stride, float sx, float sy,
|
||||
int gi, int bx0, int by1, int fmt16);
|
||||
/* As rtt_render_glyph with the device origin given directly: a font
|
||||
* unit point (x, y) lands at (x * sx - ox, -y * sy - oy) in the bitmap,
|
||||
* so a fractional origin places the glyph at a sub-pixel offset. */
|
||||
void rtt_render_glyph_at(const rtt_font_t *f, rtt_scratch_t *sc,
|
||||
void *dst, int out_w, int out_h, int stride, float sx, float sy,
|
||||
float ox, float oy, int gi, int fmt16);
|
||||
|
||||
/* Sets how far rendering may flatten a curve from its true shape, in
|
||||
* pixels: the default, 0.35, suits small text; finer costs more line
|
||||
* segments per curve. 0 restores the default. */
|
||||
void rtt_set_flatness(rtt_font_t *f, float px);
|
||||
|
||||
/* Walks glyph @gi's outline into @sink, in font units. @sc lends point
|
||||
* storage (TrueType); it may be NULL. Returns 0 for no outline. */
|
||||
int rtt_glyph_outline(const rtt_font_t *f, rtt_scratch_t *sc, int gi,
|
||||
const rtt_outline_sink *sink);
|
||||
|
||||
/* A component of a TrueType composite glyph, as stored: @arg1/@arg2 are
|
||||
* an x/y offset in font units when flags has ARGS_ARE_XY_VALUES (0x0002),
|
||||
* else the point numbers to match; the matrix is in 2.14. */
|
||||
typedef struct rtt_component
|
||||
{
|
||||
int glyph;
|
||||
unsigned flags;
|
||||
int arg1, arg2;
|
||||
int xx, xy, yx, yy;
|
||||
} rtt_component_t;
|
||||
|
||||
/* For a caller that places composite glyphs itself, as a hinting or
|
||||
* fixed-point engine must: 0 for a simple glyph, -1 for none or a CFF
|
||||
* font, else how many components glyph @gi has, with up to @max of them
|
||||
* written to @out. */
|
||||
int rtt_glyph_components(const rtt_font_t *f, int gi,
|
||||
rtt_component_t *out, int max);
|
||||
|
||||
/* A simple TrueType glyph's points as stored, in font units: @on is 1
|
||||
* for an on-curve point, @ends the last point of each contour. With any
|
||||
* array NULL it returns the point count (and *@num_contours) alone.
|
||||
* Returns the points written, 0 for an empty glyph, -1 for a composite,
|
||||
* a CFF font, bad data or arrays too small. */
|
||||
int rtt_glyph_points(const rtt_font_t *f, int gi, int32_t *xs, int32_t *ys,
|
||||
uint8_t *on, uint16_t *ends, int max_points, int max_contours,
|
||||
int *num_contours);
|
||||
|
||||
/* The control box of glyph @gi's outline, in font units: the box of
|
||||
* all its points, curve controls and lone points included. Returns 0
|
||||
* for an empty glyph. */
|
||||
int rtt_glyph_cbox(const rtt_font_t *f, rtt_scratch_t *sc, int gi,
|
||||
float *x0, float *y0, float *x1, float *y1);
|
||||
|
||||
/* Vertical advance and top side bearing from vmtx; returns 0 (and
|
||||
* sets neither) when the font has no vertical metrics. */
|
||||
int rtt_glyph_vmetrics(const rtt_font_t *f, int gi, int *advance, int *tsb);
|
||||
|
||||
/* vhea's ascent, descent and line gap; returns 0 when there is none. */
|
||||
int rtt_vhea_metrics(const rtt_font_t *f, int *ascent, int *descent,
|
||||
int *line_gap);
|
||||
|
||||
/* Horizontal kerning between two glyphs from 'kern' format 0 tables,
|
||||
* in font units; 0 when there is none. */
|
||||
int rtt_kerning(const rtt_font_t *f, int left, int right);
|
||||
|
||||
/* A table of the face, read in place; NULL when absent. */
|
||||
const uint8_t *rtt_table(const rtt_font_t *f, const char *tag,
|
||||
uint32_t *len);
|
||||
|
||||
/* Frees a scratch's memory; it may be used again after. */
|
||||
void rtt_scratch_free(rtt_scratch_t *sc);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -319,6 +319,18 @@ struct rslc_names
|
||||
* version: GLSL #version, HLSL shader model or MSL version per dst.
|
||||
* es / fixup_clipspace / flip_vert_y: the flags slang_process passes
|
||||
* into the printers today; honoured at print time, never in the IR. */
|
||||
/* RSLC_EXTENDED, GLSL: how an input attachment, the colour attachment's
|
||||
* value on entry, is read */
|
||||
enum rslc_fb_fetch
|
||||
{
|
||||
RSLC_FB_FETCH_NONE = 0, /* refused */
|
||||
RSLC_FB_FETCH_EXT, /* GL_EXT_shader_framebuffer_fetch: the colour
|
||||
output at the attachment's index is inout
|
||||
and read where the input is */
|
||||
RSLC_FB_FETCH_ARM /* GL_ARM_shader_framebuffer_fetch:
|
||||
gl_LastFragColorARM */
|
||||
};
|
||||
|
||||
struct rslc_opts
|
||||
{
|
||||
void *(*alloc)(void *ud, size_t len);
|
||||
@@ -336,6 +348,10 @@ struct rslc_opts
|
||||
bool es;
|
||||
bool fixup_clipspace;
|
||||
bool flip_vert_y;
|
||||
unsigned fb_fetch; /* enum rslc_fb_fetch */
|
||||
/* RSLC_EXTENDED, GLSL: early fragment tests as conservative depth's
|
||||
* depth_unchanged, for a context without image load / store */
|
||||
bool early_z_depth_unchanged;
|
||||
};
|
||||
|
||||
#define RSLC_ARENA_CHUNK (64u * 1024u)
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (float16.h).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef __LIBRETRO_SDK_MATH_FLOAT16_H__
|
||||
#define __LIBRETRO_SDK_MATH_FLOAT16_H__
|
||||
|
||||
/* IEEE 754 binary16 ("half") to and from binary32, exactly.
|
||||
*
|
||||
* Integer arithmetic only, so the result does not depend on the
|
||||
* FPU's flush-to-zero or denormals-are-zero modes: subnormal halves
|
||||
* widen to normal floats, and a float too small for a half rounds to
|
||||
* a subnormal half or a signed zero. Narrowing rounds to nearest,
|
||||
* ties to even, as F16C and GPUs do; past the largest half it gives
|
||||
* infinity. Infinities keep their sign, and a NaN stays a NaN with
|
||||
* its payload's high bits (a quiet bit is set where narrowing would
|
||||
* otherwise lose them all). */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <retro_inline.h>
|
||||
|
||||
static INLINE float retro_half_to_float(uint16_t h)
|
||||
{
|
||||
uint32_t sign = (uint32_t)(h & 0x8000u) << 16;
|
||||
uint32_t exp = (h >> 10) & 0x1Fu;
|
||||
uint32_t mant = h & 0x3FFu;
|
||||
uint32_t bits;
|
||||
float f;
|
||||
|
||||
if (exp == 0x1Fu) /* infinity, NaN */
|
||||
bits = sign | 0x7F800000u | (mant << 13);
|
||||
else if (exp != 0) /* normal */
|
||||
bits = sign | ((exp + (127 - 15)) << 23) | (mant << 13);
|
||||
else if (mant == 0) /* signed zero */
|
||||
bits = sign;
|
||||
else
|
||||
{
|
||||
/* Subnormal: shift the leading 1 up to the implicit bit. */
|
||||
exp = 127 - 15 + 1;
|
||||
while (!(mant & 0x400u))
|
||||
{
|
||||
mant <<= 1;
|
||||
exp--;
|
||||
}
|
||||
bits = sign | (exp << 23) | ((mant & 0x3FFu) << 13);
|
||||
}
|
||||
memcpy(&f, &bits, sizeof(f));
|
||||
return f;
|
||||
}
|
||||
|
||||
static INLINE uint16_t retro_float_to_half(float value)
|
||||
{
|
||||
uint32_t bits;
|
||||
uint32_t sign, exp, mant;
|
||||
int32_t e;
|
||||
|
||||
memcpy(&bits, &value, sizeof(bits));
|
||||
sign = (bits >> 16) & 0x8000u;
|
||||
exp = (bits >> 23) & 0xFFu;
|
||||
mant = bits & 0x7FFFFFu;
|
||||
|
||||
if (exp == 0xFFu) /* infinity, NaN */
|
||||
{
|
||||
if (!mant)
|
||||
return (uint16_t)(sign | 0x7C00u);
|
||||
return (uint16_t)(sign | 0x7C00u | 0x200u | (mant >> 13));
|
||||
}
|
||||
|
||||
e = (int32_t)exp - 127 + 15;
|
||||
if (e >= 0x1F) /* too large: infinity */
|
||||
return (uint16_t)(sign | 0x7C00u);
|
||||
|
||||
if (e <= 0)
|
||||
{
|
||||
/* Subnormal half or zero. A float subnormal is far below the
|
||||
* smallest half and rounds to zero, as does anything below half
|
||||
* of the smallest half subnormal. */
|
||||
uint32_t shift, half_bit, rest;
|
||||
if (e < -10 || exp == 0)
|
||||
return (uint16_t)sign;
|
||||
mant |= 0x800000u; /* the implicit bit */
|
||||
shift = (uint32_t)(14 - e); /* 14..24 */
|
||||
half_bit = 1u << (shift - 1);
|
||||
rest = mant & ((1u << shift) - 1);
|
||||
mant >>= shift;
|
||||
if (rest > half_bit || (rest == half_bit && (mant & 1u)))
|
||||
mant++; /* may carry into the smallest normal */
|
||||
return (uint16_t)(sign | mant);
|
||||
}
|
||||
|
||||
{
|
||||
/* Normal: round the 23-bit mantissa to 10. A carry out of the
|
||||
* mantissa moves to the next exponent, and past the largest
|
||||
* exponent gives infinity, both by plain addition. */
|
||||
uint32_t half = ((uint32_t)e << 10) | (mant >> 13);
|
||||
uint32_t rest = mant & 0x1FFFu;
|
||||
if (rest > 0x1000u || (rest == 0x1000u && (half & 1u)))
|
||||
half++;
|
||||
return (uint16_t)(sign | half);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -49,8 +49,15 @@ void net_http_connection_free(struct http_connection_t *conn);
|
||||
|
||||
void net_http_connection_set_user_agent(struct http_connection_t *conn, const char *user_agent);
|
||||
|
||||
/* Pre-formatted "Name: value\r\n" lines. An Accept-Encoding or
|
||||
* User-Agent among them replaces net_http's own. */
|
||||
void net_http_connection_set_headers(struct http_connection_t *conn, const char *headers);
|
||||
|
||||
/* false: a 3xx response is returned as it is (status, headers and
|
||||
* body), for a caller applying its own redirect rules. Followed by
|
||||
* default. */
|
||||
void net_http_connection_set_redirects(struct http_connection_t *conn, bool follow);
|
||||
|
||||
struct ssl_socket_retro_config;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: MIT */
|
||||
#ifndef NET_RESOLVE_H
|
||||
#define NET_RESOLVE_H
|
||||
|
||||
#include <retro_common_api.h>
|
||||
#include <boolean.h>
|
||||
|
||||
typedef struct net_resolve net_resolve_t;
|
||||
typedef bool (*net_resolve_dispatch_t)(void (*work)(void*), void *job,
|
||||
void *userdata);
|
||||
|
||||
enum net_resolve_result
|
||||
{
|
||||
NET_RESOLVE_ERROR = -1,
|
||||
NET_RESOLVE_PENDING,
|
||||
NET_RESOLVE_READY
|
||||
};
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
/* Networking must already be initialized. Copies the raw hostname (up to 1023
|
||||
* bytes, no IPv6 brackets) since a canceled lookup can outlive its caller.
|
||||
* dispatch must queue work exactly once on a resolver worker, or return
|
||||
* false without invoking it. Never run it inline on the main/frame thread.
|
||||
* Four live requests maximum, including canceled jobs not yet retired.
|
||||
* NULL also reports capacity/contended admission; caller schedules any retry.
|
||||
* The executor owns worker lifetime; this module never starts or joins threads. */
|
||||
net_resolve_t *net_resolve_new(const char *hostname, unsigned port,
|
||||
net_resolve_dispatch_t dispatch, void *userdata);
|
||||
/* One caller owns each request; serialize poll/free. No work/wait.
|
||||
* On READY, addresses is a borrowed addrinfo chain, compatible
|
||||
* with socket_connect_begin. It stays immutable until free; never freeaddrinfo
|
||||
* it or modify its links. PENDING/ERROR set addresses to NULL. */
|
||||
int net_resolve_poll(net_resolve_t *request, void **addresses);
|
||||
/* Drops the caller's ownership immediately, even during a system lookup.
|
||||
* A queued worker skips resolution after cancellation. An executing resolver
|
||||
* retains the job and reclaims its addresses after returning. Call once. */
|
||||
void net_resolve_free(net_resolve_t *request);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -47,6 +47,9 @@ bool net_rtmp_chunk_push(net_rtmp_chunk_t *chunk, unsigned chunk_id,
|
||||
* No allocation, copy of payload, I/O, loop over chunks or wait. */
|
||||
bool net_rtmp_chunk_get(net_rtmp_chunk_t *chunk, net_rtmp_span_t *span);
|
||||
bool net_rtmp_chunk_consume(net_rtmp_chunk_t *chunk, size_t size);
|
||||
/* True before a header starts, or after the message ends. No live output
|
||||
* borrow may be interrupted; the transport owner tracks that borrow. */
|
||||
bool net_rtmp_chunk_boundary(const net_rtmp_chunk_t *chunk);
|
||||
void net_rtmp_chunk_free(net_rtmp_chunk_t *chunk);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -44,15 +44,28 @@ bool net_rtmp_session_feed(net_rtmp_session_t *session,
|
||||
/* One output retirement, command dispatch or reply-parser step. True means
|
||||
* local progress; false requires transport activity or reports FAILED. */
|
||||
bool net_rtmp_session_step(net_rtmp_session_t *session);
|
||||
/* Borrow one output span. Partial consumption keeps its remaining bytes live. */
|
||||
/* Borrow one output span, capped to peer credit. Partial consumption keeps
|
||||
* the grant live, even if a later peer message reduces its window. */
|
||||
bool net_rtmp_session_get(net_rtmp_session_t *session, net_rtmp_span_t *span);
|
||||
bool net_rtmp_session_consume(net_rtmp_session_t *session, size_t size);
|
||||
/* READY and no media borrow: announce a new outgoing chunk size. Media
|
||||
* admission stays busy until the announcement has left the transport. */
|
||||
bool net_rtmp_session_chunk_size(net_rtmp_session_t *session, unsigned size);
|
||||
/* READY only: one audio(8), video(9) or AMF0 data(18) message, up to three
|
||||
* borrowed spans. False takes nothing. Payload includes its FLV codec header,
|
||||
* without an FLV tag header/trailer. Timestamp is milliseconds, modulo 2^32.
|
||||
* The producer keeps bytes immutable until media_release succeeds. */
|
||||
bool net_rtmp_session_media(net_rtmp_session_t *session, unsigned type,
|
||||
uint32_t timestamp, const net_rtmp_span_t *spans, size_t count);
|
||||
/* Retires a fully sent message, or a canceled message after transport discard.
|
||||
* True returns the producer's borrow. No allocation or wait. */
|
||||
bool net_rtmp_session_media_release(net_rtmp_session_t *session);
|
||||
enum net_rtmp_session_state net_rtmp_session_status(
|
||||
const net_rtmp_session_t *session, uint32_t *stream, uint64_t *sent);
|
||||
void net_rtmp_session_cancel(net_rtmp_session_t *session);
|
||||
/* After cancellation, retire a transport borrow without reporting it sent. */
|
||||
bool net_rtmp_session_discard(net_rtmp_session_t *session);
|
||||
/* Refuses a live transport borrow. Releases internal borrows before freeing. */
|
||||
/* Refuses live transport/media borrows. Releases internal borrows before freeing. */
|
||||
bool net_rtmp_session_free(net_rtmp_session_t *session);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -35,6 +35,15 @@ RETRO_BEGIN_DECLS
|
||||
* nonblocking; neither name resolution nor connection setup occurs here.
|
||||
* This worker is the session's sole I/O owner until free. */
|
||||
net_rtmp_socket_t *net_rtmp_socket_new(net_rtmp_session_t *session, int fd);
|
||||
#ifdef HAVE_SSL
|
||||
/* Borrows a completed TLS connection on fd. Caller closes/frees TLS after
|
||||
* retiring the pump. Handshake scheduling remains with the caller. */
|
||||
net_rtmp_socket_t *net_rtmp_socket_new_tls(net_rtmp_session_t *session,
|
||||
int fd, void *ssl);
|
||||
#endif
|
||||
/* Drains bytes already accepted by TLS, not unconsumed session output.
|
||||
* 1: drained, 0: needs writable readiness, -1: failed. Plain sockets return 1. */
|
||||
int net_rtmp_socket_flush(net_rtmp_socket_t *socket);
|
||||
/* One bounded receive/feed/send pass. No allocation, poll, wait or clock. */
|
||||
int net_rtmp_socket_step(net_rtmp_socket_t *socket, uint32_t receive_time);
|
||||
/* Retires this pump's session borrows and cancels it. Caller then closes the
|
||||
|
||||
@@ -52,6 +52,13 @@ enum socket_protocol
|
||||
SOCKET_PROTOCOL_UDP
|
||||
};
|
||||
|
||||
enum socket_connect_result
|
||||
{
|
||||
SOCKET_CONNECT_ERROR = -1,
|
||||
SOCKET_CONNECT_PENDING,
|
||||
SOCKET_CONNECT_READY
|
||||
};
|
||||
|
||||
typedef struct socket_target
|
||||
{
|
||||
unsigned port;
|
||||
@@ -101,6 +108,13 @@ bool socket_bind(int fd, void *data);
|
||||
|
||||
int socket_connect(int fd, void *data);
|
||||
|
||||
/* A resolved addrinfo, never a hostname. Begin once on a fresh descriptor;
|
||||
* step only after PENDING, and stop after a terminal result. A successful
|
||||
* begin leaves it nonblocking. Caller holds the address and closes to cancel.
|
||||
* No resolution, clock, retry loop or wait; caller schedules/deadlines it. */
|
||||
int socket_connect_begin(int fd, void *data);
|
||||
int socket_connect_step(int fd, void *data);
|
||||
|
||||
bool socket_connect_with_timeout(int fd, void *data, int timeout);
|
||||
|
||||
int socket_create(
|
||||
|
||||
@@ -43,6 +43,13 @@ void* ssl_socket_init(int fd, const char *domain);
|
||||
|
||||
int ssl_socket_connect(void *state_data, void *data, bool timeout_enable, bool nonblock);
|
||||
|
||||
/* TLS only, on an already-connected descriptor. Enables nonblocking I/O.
|
||||
* 1: complete, -1: failed, 0: progress; read/write name the next readiness
|
||||
* interests. Neither means internal work remains: schedule another step.
|
||||
* The caller owns scheduling, deadlines and cancellation (close then free).
|
||||
* Do not mix this with ssl_socket_connect() on the same state. */
|
||||
int ssl_socket_handshake_step(void *state_data, bool *read, bool *write);
|
||||
|
||||
/* The TLS library's own code for the most recent failure in
|
||||
* ssl_socket_init()/ssl_socket_connect(), as the library reports it
|
||||
* (mbedtls: negative, so -0x7780 style; BearSSL: its BR_ERR_* value),
|
||||
@@ -68,8 +75,12 @@ int ssl_socket_flush_nonblocking(void *state_data);
|
||||
|
||||
int ssl_socket_receive_all_blocking(void *state_data, void *data_, size_t len);
|
||||
|
||||
/* Positive plaintext bytes, zero yields, negative sets error. Native partial
|
||||
* records/control work use ssl_socket_retro_pending for readiness. */
|
||||
ssize_t ssl_socket_receive_all_nonblocking(void *state_data, bool *error, void *data_, size_t len);
|
||||
|
||||
/* Closes immediately; flush pending application bytes before closing when
|
||||
* delivery matters. The close notification is best effort and never waits. */
|
||||
void ssl_socket_close(void *state_data);
|
||||
|
||||
void ssl_socket_free(void *state_data);
|
||||
@@ -104,11 +115,16 @@ unsigned ssl_socket_retro_version(void *state_data);
|
||||
* handshake. */
|
||||
unsigned ssl_socket_retro_suite(void *state_data);
|
||||
|
||||
/* After handshake, native backend: buffered plaintext/control work is ready.
|
||||
* Otherwise include *write in the socket wait interests, alongside reading. */
|
||||
bool ssl_socket_retro_pending(void *state_data, bool *write);
|
||||
|
||||
struct rsa_private_key;
|
||||
|
||||
/* net_socket_ssl_retro.c only: one connection's own settings, for
|
||||
* clients that pin their own CAs or present a certificate. Every
|
||||
* pointer is borrowed until ssl_socket_connect() returns. */
|
||||
* pointer is borrowed until ssl_socket_connect() returns, or until the
|
||||
* incremental handshake completes or the connection is freed. */
|
||||
struct ssl_socket_retro_config
|
||||
{
|
||||
/* Trust anchors, DER, in place of the built-in bundle; NULL for
|
||||
@@ -135,6 +151,15 @@ struct ssl_socket_retro_config
|
||||
#define SSL_RETRO_NO_VERIFY (1 << 0)
|
||||
/* Check the chain but not that it names the host. */
|
||||
#define SSL_RETRO_NO_HOSTNAME (1 << 1)
|
||||
/* Full duplex: once the handshake is done, one thread may receive
|
||||
* (ssl_socket_receive_all_nonblocking, ssl_socket_retro_pending) while
|
||||
* another sends (ssl_socket_send_all_nonblocking,
|
||||
* ssl_socket_flush_nonblocking) on this connection, with no lock. The
|
||||
* receiving side then sends nothing itself: a KeyUpdate the server asks
|
||||
* for is sent by the next send or flush, before its data. Nonblocking
|
||||
* calls only after the handshake; close and free once neither thread
|
||||
* uses the connection. */
|
||||
#define SSL_RETRO_DUPLEX (1 << 2)
|
||||
|
||||
/**
|
||||
* ssl_socket_retro_configure:
|
||||
|
||||
@@ -43,6 +43,7 @@ struct net_upnp_gateway
|
||||
char control_url[NET_UPNP_URL_MAX]; /* WAN connection service control */
|
||||
char service_type[128]; /* its urn:...:WANIPConnection:n */
|
||||
char lan_addr[64]; /* this host's address towards it */
|
||||
char desc_url[NET_UPNP_URL_MAX]; /* its device description */
|
||||
};
|
||||
|
||||
/* Searches for a gateway for up to timeout_ms. Returns true and fills
|
||||
@@ -50,6 +51,13 @@ struct net_upnp_gateway
|
||||
* service. */
|
||||
bool net_upnp_discover(struct net_upnp_gateway *gw, int timeout_ms);
|
||||
|
||||
/* The gateway whose device description is at desc_url, as discovery
|
||||
* found it before (gw->desc_url), without searching again. Returns true
|
||||
* and fills gw when the description still names a WAN connection
|
||||
* service. */
|
||||
bool net_upnp_gateway_from_url(struct net_upnp_gateway *gw,
|
||||
const char *desc_url, int timeout_ms);
|
||||
|
||||
/* The gateway's external IPv4 address, dotted, into s. */
|
||||
bool net_upnp_external_address(const struct net_upnp_gateway *gw,
|
||||
char *s, size_t len, int timeout_ms);
|
||||
@@ -58,6 +66,11 @@ bool net_upnp_external_address(const struct net_upnp_gateway *gw,
|
||||
* TCP, without a lease time. */
|
||||
bool net_upnp_add_port_mapping(const struct net_upnp_gateway *gw,
|
||||
unsigned port, bool udp, const char *description, int timeout_ms);
|
||||
/* As net_upnp_add_port_mapping, from external_port to internal_port,
|
||||
* for a caller that tries other external ports when one is taken. */
|
||||
bool net_upnp_add_port_mapping_ext(const struct net_upnp_gateway *gw,
|
||||
unsigned external_port, unsigned internal_port, bool udp,
|
||||
const char *description, int timeout_ms);
|
||||
bool net_upnp_delete_port_mapping(const struct net_upnp_gateway *gw,
|
||||
unsigned port, bool udp, int timeout_ms);
|
||||
|
||||
|
||||
@@ -296,6 +296,8 @@ struct http_t
|
||||
/* The request has already been replayed once on a fresh
|
||||
* connection; it will not be replayed again. */
|
||||
bool retried;
|
||||
/* 3xx responses are handed back as they are, not followed. */
|
||||
bool no_redirect;
|
||||
unsigned redirects;
|
||||
|
||||
/* For the stall bound: when the transfer last moved and how far it
|
||||
@@ -331,6 +333,7 @@ struct http_connection_t
|
||||
const struct ssl_socket_retro_config *tls;
|
||||
int port;
|
||||
bool ssl;
|
||||
bool no_redirect;
|
||||
};
|
||||
|
||||
/* The OS error left by the socket call that just failed, for
|
||||
@@ -968,6 +971,12 @@ void net_http_connection_set_user_agent(
|
||||
conn->useragent = user_agent ? strdup(user_agent) : NULL;
|
||||
}
|
||||
|
||||
void net_http_connection_set_redirects(
|
||||
struct http_connection_t *conn, bool follow)
|
||||
{
|
||||
conn->no_redirect = !follow;
|
||||
}
|
||||
|
||||
void net_http_connection_set_headers(
|
||||
struct http_connection_t *conn, const char *headers)
|
||||
{
|
||||
@@ -1468,6 +1477,7 @@ struct http_t *net_http_new(struct http_connection_t *conn)
|
||||
state->request.headers = conn->headers ? strdup(conn->headers) : NULL;
|
||||
state->request.auth = conn->auth ? strdup(conn->auth) : NULL;
|
||||
state->request.port = conn->port;
|
||||
state->no_redirect = conn->no_redirect;
|
||||
|
||||
state->response.status = -1;
|
||||
state->sink = conn->sink;
|
||||
@@ -1903,6 +1913,22 @@ static void net_http_send_reset(struct http_t *state)
|
||||
state->send_phase = SEND_NONE;
|
||||
}
|
||||
|
||||
/* Whether the caller's pre-formatted headers carry @name (with its
|
||||
* colon), at the start of a line, in any case. */
|
||||
static bool net_http_has_header(const char *headers, const char *name)
|
||||
{
|
||||
size_t len = strlen(name);
|
||||
const char *line;
|
||||
for (line = headers; line && *line; )
|
||||
{
|
||||
const char *next = strchr(line, '\n');
|
||||
if (!strncasecmp(line, name, len))
|
||||
return true;
|
||||
line = next ? next + 1 : NULL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static size_t net_http_put(char *dst, size_t at, const char *src)
|
||||
{
|
||||
size_t n = strlen(src);
|
||||
@@ -1917,7 +1943,10 @@ static bool net_http_build_head(struct http_t *state)
|
||||
{
|
||||
struct request *request = &state->request;
|
||||
const char *method = request->method ? request->method : "GET";
|
||||
bool body_method = request->method && request->method[0] == 'P';
|
||||
/* Content-Length goes with a POST/PUT/PATCH, empty or not, and with
|
||||
* any other method carrying a body, which is sent */
|
||||
bool body_method = (request->method && request->method[0] == 'P')
|
||||
|| ((request->postdata || request->source) && request->contentlength);
|
||||
size_t cap = 192
|
||||
+ STRLEN_CONST("Accept-Encoding: identity\r\n")
|
||||
+ strlen(method) + strlen(request->path) + strlen(request->domain)
|
||||
@@ -1968,11 +1997,17 @@ static bool net_http_build_head(struct http_t *state)
|
||||
#endif
|
||||
}
|
||||
/* The body reaches callers as sent: no content coding is decoded,
|
||||
* so none is accepted. Without this header a server may pick any. */
|
||||
n = net_http_put(h, n, "Accept-Encoding: identity\r\n");
|
||||
n = net_http_put(h, n, "User-Agent: ");
|
||||
n = net_http_put(h, n, request->useragent ? request->useragent : "libretro");
|
||||
n = net_http_put(h, n, "\r\n\r\n");
|
||||
* so none is accepted. Without this header a server may pick any.
|
||||
* A caller sending its own takes on decoding what it accepts. */
|
||||
if (!net_http_has_header(request->headers, "Accept-Encoding:"))
|
||||
n = net_http_put(h, n, "Accept-Encoding: identity\r\n");
|
||||
if (!net_http_has_header(request->headers, "User-Agent:"))
|
||||
{
|
||||
n = net_http_put(h, n, "User-Agent: ");
|
||||
n = net_http_put(h, n, request->useragent ? request->useragent : "libretro");
|
||||
n = net_http_put(h, n, "\r\n");
|
||||
}
|
||||
n = net_http_put(h, n, "\r\n");
|
||||
|
||||
state->out_head = h;
|
||||
state->out_head_len = n;
|
||||
@@ -2289,6 +2324,7 @@ static ssize_t net_http_receive_header(struct http_t *state, ssize_t len)
|
||||
response->conn_close = true;
|
||||
if ( status >= 300 && status < 400
|
||||
&& response->location
|
||||
&& !state->no_redirect
|
||||
&& state->redirects < NET_HTTP_MAX_REDIRECTS)
|
||||
response->discard = true;
|
||||
if (status >= 100 && status < 200 && status != 101)
|
||||
@@ -3274,7 +3310,14 @@ bool net_http_wait(struct http_t *state, int timeout_ms)
|
||||
return true;
|
||||
|
||||
if (state->conn->connected && state->request_sent)
|
||||
{
|
||||
rd = true;
|
||||
#ifdef HAVE_RETROSSL
|
||||
if (state->ssl && state->conn->ssl_ctx
|
||||
&& ssl_socket_retro_pending(state->conn->ssl_ctx, &wr))
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
wr = true;
|
||||
|
||||
@@ -3560,16 +3603,9 @@ bool net_http_update_budget(struct http_t *state,
|
||||
if (response->part == P_DONE || state->err)
|
||||
break;
|
||||
|
||||
/* _len == 0 is EAGAIN: the socket is drained for now.
|
||||
* _len < 0 past the header stage is a close, which the body
|
||||
* parser above has already turned into P_DONE for T_FULL and
|
||||
* into an error otherwise; either way we are finished here.
|
||||
*
|
||||
* This is the one exit that leaves nothing buffered anywhere,
|
||||
* including inside the TLS layer: the SSL read reports EAGAIN
|
||||
* only once it has handed over every decrypted byte it holds.
|
||||
* Breaking on the budget below leaves bytes waiting, so only
|
||||
* this exit marks the transfer as blocked. */
|
||||
/* Zero yields: net_http_wait checks TLS-local work as well as
|
||||
* socket readiness. A negative result ends a close-delimited
|
||||
* body, or fails any other unfinished body above. */
|
||||
if (_len <= 0)
|
||||
{
|
||||
state->blocked = (_len == 0);
|
||||
@@ -3612,7 +3648,8 @@ bool net_http_update_budget(struct http_t *state,
|
||||
state->conn = NULL;
|
||||
|
||||
if ( response->status >= 300 && response->status < 400
|
||||
&& response->location)
|
||||
&& response->location
|
||||
&& !state->no_redirect)
|
||||
{
|
||||
if (state->redirects >= NET_HTTP_MAX_REDIRECTS)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: MIT */
|
||||
#include <retro_posix_source.h>
|
||||
#include <net/net_resolve.h>
|
||||
#include <net/net_socket.h>
|
||||
#include <retro_atomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <compat/msvc.h>
|
||||
|
||||
struct net_resolve
|
||||
{
|
||||
struct addrinfo *addresses;
|
||||
char *hostname;
|
||||
retro_atomic_int_t references;
|
||||
retro_atomic_int_t canceled;
|
||||
retro_atomic_int_t result;
|
||||
unsigned port;
|
||||
};
|
||||
|
||||
static retro_atomic_int_t net_resolve_live;
|
||||
|
||||
static void net_resolve_release(net_resolve_t *request)
|
||||
{
|
||||
if (retro_atomic_fetch_sub_int(&request->references, 1) == 1)
|
||||
{
|
||||
if (request->addresses)
|
||||
freeaddrinfo_retro(request->addresses);
|
||||
free(request);
|
||||
retro_atomic_fetch_sub_int(&net_resolve_live, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void net_resolve_work(void *data)
|
||||
{
|
||||
net_resolve_t *request = (net_resolve_t*)data;
|
||||
struct addrinfo hints = {0};
|
||||
struct addrinfo *addresses = NULL;
|
||||
char service[6];
|
||||
int result = NET_RESOLVE_ERROR;
|
||||
if (!retro_atomic_load_acquire_int(&request->canceled))
|
||||
{
|
||||
#if defined(HAVE_SOCKET_LEGACY) || defined(WIIU)
|
||||
hints.ai_family = AF_INET;
|
||||
#else
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
#endif
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_NUMERICSERV;
|
||||
snprintf(service, sizeof(service), "%u", request->port);
|
||||
if (!getaddrinfo_retro(request->hostname, service, &hints, &addresses)
|
||||
&& addresses)
|
||||
result = NET_RESOLVE_READY;
|
||||
}
|
||||
request->addresses = addresses;
|
||||
retro_atomic_store_release_int(&request->result, result);
|
||||
net_resolve_release(request);
|
||||
}
|
||||
|
||||
net_resolve_t *net_resolve_new(const char *hostname, unsigned port,
|
||||
net_resolve_dispatch_t dispatch, void *userdata)
|
||||
{
|
||||
net_resolve_t *request;
|
||||
size_t length;
|
||||
int live;
|
||||
if (!hostname || !port || port > 65535 || !dispatch)
|
||||
return NULL;
|
||||
for (length = 0; length < 1024 && hostname[length]; length++)
|
||||
{
|
||||
}
|
||||
if (!length || length == 1024)
|
||||
return NULL;
|
||||
live = retro_atomic_load_acquire_int(&net_resolve_live);
|
||||
if (live >= 4 || !retro_atomic_cas_int(&net_resolve_live, live, live + 1))
|
||||
return NULL;
|
||||
request = (net_resolve_t*)calloc(1, sizeof(*request) + length + 1);
|
||||
if (!request)
|
||||
{
|
||||
retro_atomic_fetch_sub_int(&net_resolve_live, 1);
|
||||
return NULL;
|
||||
}
|
||||
request->port = port;
|
||||
request->hostname = (char*)(request + 1);
|
||||
memcpy(request->hostname, hostname, length + 1);
|
||||
retro_atomic_store_release_int(&request->references, 2);
|
||||
if (!dispatch(net_resolve_work, request, userdata))
|
||||
{
|
||||
net_resolve_release(request);
|
||||
net_resolve_release(request);
|
||||
return NULL;
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
int net_resolve_poll(net_resolve_t *request, void **addresses)
|
||||
{
|
||||
int result;
|
||||
if (!addresses)
|
||||
return NET_RESOLVE_ERROR;
|
||||
*addresses = NULL;
|
||||
if (!request)
|
||||
return NET_RESOLVE_ERROR;
|
||||
result = retro_atomic_load_acquire_int(&request->result);
|
||||
if (result == NET_RESOLVE_READY)
|
||||
*addresses = request->addresses;
|
||||
return result;
|
||||
}
|
||||
|
||||
void net_resolve_free(net_resolve_t *request)
|
||||
{
|
||||
if (!request)
|
||||
return;
|
||||
retro_atomic_store_release_int(&request->canceled, 1);
|
||||
net_resolve_release(request);
|
||||
}
|
||||
@@ -173,6 +173,11 @@ bool net_rtmp_chunk_consume(net_rtmp_chunk_t *c, size_t size)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool net_rtmp_chunk_boundary(const net_rtmp_chunk_t *c)
|
||||
{
|
||||
return c && (!c->active || !c->header_offset);
|
||||
}
|
||||
|
||||
void net_rtmp_chunk_free(net_rtmp_chunk_t *c)
|
||||
{
|
||||
free(c);
|
||||
|
||||
@@ -22,19 +22,27 @@
|
||||
#include <net/net_rtmp_session.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
enum session_output { SESSION_OUTPUT_NONE, SESSION_OUTPUT_CONTROL, SESSION_OUTPUT_PUBLISH };
|
||||
enum session_output
|
||||
{
|
||||
SESSION_OUTPUT_NONE, SESSION_OUTPUT_CONTROL,
|
||||
SESSION_OUTPUT_PUBLISH, SESSION_OUTPUT_SIZE
|
||||
};
|
||||
struct net_rtmp_session
|
||||
{
|
||||
net_rtmp_handshake_t *handshake;
|
||||
net_rtmp_publish_t *publish;
|
||||
net_rtmp_control_t *control;
|
||||
net_rtmp_receive_t *receive;
|
||||
net_rtmp_chunk_t *chunk;
|
||||
net_rtmp_chunk_t *chunk, *media;
|
||||
net_rtmp_span_t view;
|
||||
uint64_t sent;
|
||||
uint64_t sent, acknowledged;
|
||||
uint32_t peer_ack;
|
||||
unsigned chunk_size;
|
||||
enum net_rtmp_session_state state;
|
||||
enum session_output owner;
|
||||
bool borrowed, handshake_view, reply;
|
||||
uint8_t size_bytes[4];
|
||||
bool borrowed, handshake_view, reply, media_active, media_complete;
|
||||
bool media_wire;
|
||||
};
|
||||
|
||||
net_rtmp_session_t *net_rtmp_session_new(const net_rtmp_publish_config_t *config,
|
||||
@@ -48,7 +56,9 @@ net_rtmp_session_t *net_rtmp_session_new(const net_rtmp_publish_config_t *config
|
||||
s->control = net_rtmp_control_new();
|
||||
s->receive = net_rtmp_receive_new(max_reply, streams);
|
||||
s->chunk = net_rtmp_chunk_new(128);
|
||||
if (!s->handshake || !s->publish || !s->control || !s->receive || !s->chunk)
|
||||
s->media = net_rtmp_chunk_new(128);
|
||||
if (!s->handshake || !s->publish || !s->control || !s->receive
|
||||
|| !s->chunk || !s->media)
|
||||
{
|
||||
net_rtmp_session_free(s);
|
||||
return NULL;
|
||||
@@ -82,6 +92,7 @@ bool net_rtmp_session_step(net_rtmp_session_t *s)
|
||||
net_rtmp_span_t span;
|
||||
net_rtmp_message_t m;
|
||||
int result;
|
||||
bool progress = false;
|
||||
if (!s || s->state == NET_RTMP_SESSION_FAILED)
|
||||
return false;
|
||||
if (s->state == NET_RTMP_SESSION_HANDSHAKE)
|
||||
@@ -96,13 +107,26 @@ bool net_rtmp_session_step(net_rtmp_session_t *s)
|
||||
}
|
||||
if (s->owner != SESSION_OUTPUT_NONE && !net_rtmp_chunk_get(s->chunk, &span))
|
||||
{
|
||||
result = s->owner == SESSION_OUTPUT_CONTROL
|
||||
? net_rtmp_control_ack(s->control) : net_rtmp_publish_ack(s->publish);
|
||||
if (s->owner == SESSION_OUTPUT_SIZE)
|
||||
{
|
||||
result = net_rtmp_chunk_size(s->chunk, s->chunk_size)
|
||||
&& net_rtmp_chunk_size(s->media, s->chunk_size);
|
||||
s->chunk_size = 0;
|
||||
}
|
||||
else
|
||||
result = s->owner == SESSION_OUTPUT_CONTROL
|
||||
? net_rtmp_control_ack(s->control) : net_rtmp_publish_ack(s->publish);
|
||||
s->owner = SESSION_OUTPUT_NONE;
|
||||
if (!result)
|
||||
goto invalid;
|
||||
return true;
|
||||
}
|
||||
if (s->media_active && !s->media_complete
|
||||
&& !net_rtmp_chunk_get(s->media, &span))
|
||||
{
|
||||
s->media_complete = true;
|
||||
return true;
|
||||
}
|
||||
if (s->reply)
|
||||
{
|
||||
result = net_rtmp_publish_step(s->publish);
|
||||
@@ -127,9 +151,23 @@ bool net_rtmp_session_step(net_rtmp_session_t *s)
|
||||
goto invalid;
|
||||
if (result == NET_RTMP_CONTROL_HANDLED)
|
||||
{
|
||||
if (m.type == 3)
|
||||
{
|
||||
net_rtmp_control_status_t status;
|
||||
uint32_t delta;
|
||||
if (!net_rtmp_control_status(s->control, &status))
|
||||
goto invalid;
|
||||
delta = status.peer_ack - s->peer_ack;
|
||||
if (delta <= s->sent - s->acknowledged)
|
||||
{
|
||||
s->acknowledged += delta;
|
||||
s->peer_ack = status.peer_ack;
|
||||
}
|
||||
}
|
||||
if (!net_rtmp_receive_release(s->receive))
|
||||
goto invalid;
|
||||
return true;
|
||||
progress = true;
|
||||
goto dispatch;
|
||||
}
|
||||
if (result == NET_RTMP_CONTROL_OTHER)
|
||||
{
|
||||
@@ -159,16 +197,26 @@ bool net_rtmp_session_step(net_rtmp_session_t *s)
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatch:
|
||||
if (s->owner != SESSION_OUTPUT_NONE)
|
||||
return false;
|
||||
return progress;
|
||||
if (net_rtmp_control_get(s->control, &m))
|
||||
s->owner = SESSION_OUTPUT_CONTROL;
|
||||
else if (s->chunk_size)
|
||||
{
|
||||
s->owner = SESSION_OUTPUT_SIZE;
|
||||
m.chunk_id = 2;
|
||||
m.stream_id = m.timestamp = 0;
|
||||
m.type = 1;
|
||||
m.payload.data = s->size_bytes;
|
||||
m.payload.size = sizeof(s->size_bytes);
|
||||
}
|
||||
else if (net_rtmp_publish_get(s->publish, &m))
|
||||
s->owner = SESSION_OUTPUT_PUBLISH;
|
||||
else if (net_rtmp_publish_status(s->publish, NULL, NULL) == NET_RTMP_PUBLISH_FAILED)
|
||||
goto invalid;
|
||||
else
|
||||
return false;
|
||||
return progress;
|
||||
if (!net_rtmp_chunk_push(s->chunk, m.chunk_id, m.stream_id,
|
||||
m.type, m.timestamp, &m.payload, 1))
|
||||
goto invalid;
|
||||
@@ -187,9 +235,30 @@ bool net_rtmp_session_get(net_rtmp_session_t *s, net_rtmp_span_t *span)
|
||||
if (s->state == NET_RTMP_SESSION_FAILED)
|
||||
return false;
|
||||
s->handshake_view = s->state == NET_RTMP_SESSION_HANDSHAKE;
|
||||
if (!(s->handshake_view ? net_rtmp_handshake_get(s->handshake, &s->view)
|
||||
: net_rtmp_chunk_get(s->chunk, &s->view)))
|
||||
return false;
|
||||
if (s->handshake_view)
|
||||
{
|
||||
if (!net_rtmp_handshake_get(s->handshake, &s->view))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
net_rtmp_control_status_t status;
|
||||
uint64_t outstanding = s->sent - s->acknowledged;
|
||||
if (!net_rtmp_control_status(s->control, &status)
|
||||
|| (status.bandwidth && outstanding >= status.bandwidth))
|
||||
return false;
|
||||
if (!s->media_wire && !net_rtmp_chunk_get(s->chunk, &s->view))
|
||||
{
|
||||
if (!s->media_active || s->media_complete
|
||||
|| !net_rtmp_chunk_get(s->media, &s->view))
|
||||
return false;
|
||||
s->media_wire = true;
|
||||
}
|
||||
else if (s->media_wire && !net_rtmp_chunk_get(s->media, &s->view))
|
||||
return false;
|
||||
if (status.bandwidth && s->view.size > status.bandwidth - outstanding)
|
||||
s->view.size = (size_t)(status.bandwidth - outstanding);
|
||||
}
|
||||
s->borrowed = true;
|
||||
}
|
||||
*span = s->view;
|
||||
@@ -202,13 +271,55 @@ bool net_rtmp_session_consume(net_rtmp_session_t *s, size_t size)
|
||||
|| (uint64_t)size > (uint64_t)-1 - s->sent)
|
||||
return false;
|
||||
if (!(s->handshake_view ? net_rtmp_handshake_consume(s->handshake, size)
|
||||
: net_rtmp_chunk_consume(s->chunk, size)))
|
||||
: net_rtmp_chunk_consume(s->media_wire ? s->media : s->chunk, size)))
|
||||
return false;
|
||||
s->sent += size;
|
||||
s->view.data = (const uint8_t*)s->view.data + size;
|
||||
s->view.size -= size;
|
||||
if (!s->view.size)
|
||||
{
|
||||
s->borrowed = false;
|
||||
if (s->media_wire && net_rtmp_chunk_boundary(s->media))
|
||||
s->media_wire = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool net_rtmp_session_chunk_size(net_rtmp_session_t *s, unsigned size)
|
||||
{
|
||||
if (!s || s->state != NET_RTMP_SESSION_READY || s->media_active
|
||||
|| s->chunk_size || !size || size > 0x7fffffff)
|
||||
return false;
|
||||
s->chunk_size = size;
|
||||
s->size_bytes[0] = (uint8_t)(size >> 24);
|
||||
s->size_bytes[1] = (uint8_t)(size >> 16);
|
||||
s->size_bytes[2] = (uint8_t)(size >> 8);
|
||||
s->size_bytes[3] = (uint8_t)size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool net_rtmp_session_media(net_rtmp_session_t *s, unsigned type,
|
||||
uint32_t timestamp, const net_rtmp_span_t *spans, size_t count)
|
||||
{
|
||||
uint32_t stream;
|
||||
unsigned id = type == 8 ? 4 : type == 9 ? 6 : 5;
|
||||
if (!s || s->state != NET_RTMP_SESSION_READY || s->media_active || s->chunk_size
|
||||
|| (type != 8 && type != 9 && type != 18) || !count
|
||||
|| net_rtmp_publish_status(s->publish, &stream, NULL)
|
||||
!= NET_RTMP_PUBLISH_READY
|
||||
|| !net_rtmp_chunk_push(s->media, id, stream, type, timestamp, spans, count))
|
||||
return false;
|
||||
s->media_active = true;
|
||||
s->media_complete = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool net_rtmp_session_media_release(net_rtmp_session_t *s)
|
||||
{
|
||||
if (!s || !s->media_active || s->borrowed
|
||||
|| (!s->media_complete && s->state != NET_RTMP_SESSION_FAILED))
|
||||
return false;
|
||||
s->media_active = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -240,9 +351,10 @@ bool net_rtmp_session_free(net_rtmp_session_t *s)
|
||||
{
|
||||
if (!s)
|
||||
return true;
|
||||
if (s->borrowed)
|
||||
if (s->borrowed || s->media_active)
|
||||
return false;
|
||||
net_rtmp_chunk_free(s->chunk);
|
||||
net_rtmp_chunk_free(s->media);
|
||||
net_rtmp_publish_cancel(s->publish);
|
||||
if (s->reply)
|
||||
net_rtmp_publish_step(s->publish);
|
||||
|
||||
@@ -21,15 +21,24 @@
|
||||
*/
|
||||
#include <net/net_rtmp_socket.h>
|
||||
#include <net/net_socket.h>
|
||||
#ifdef HAVE_SSL
|
||||
#include <net/net_socket_ssl.h>
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
|
||||
struct net_rtmp_socket
|
||||
{
|
||||
net_rtmp_session_t *session;
|
||||
#ifdef HAVE_SSL
|
||||
void *ssl;
|
||||
#endif
|
||||
size_t offset, size;
|
||||
int fd;
|
||||
uint8_t input[4096];
|
||||
bool failed;
|
||||
#ifdef HAVE_SSL
|
||||
bool tls_pending;
|
||||
#endif
|
||||
};
|
||||
|
||||
net_rtmp_socket_t *net_rtmp_socket_new(net_rtmp_session_t *session, int fd)
|
||||
@@ -51,6 +60,42 @@ net_rtmp_socket_t *net_rtmp_socket_new(net_rtmp_session_t *session, int fd)
|
||||
return s;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SSL
|
||||
net_rtmp_socket_t *net_rtmp_socket_new_tls(net_rtmp_session_t *session,
|
||||
int fd, void *ssl)
|
||||
{
|
||||
net_rtmp_socket_t *s;
|
||||
if (!ssl)
|
||||
return NULL;
|
||||
s = net_rtmp_socket_new(session, fd);
|
||||
if (s)
|
||||
s->ssl = ssl;
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
|
||||
int net_rtmp_socket_flush(net_rtmp_socket_t *s)
|
||||
{
|
||||
int result = 1;
|
||||
if (!s || s->failed)
|
||||
return -1;
|
||||
#ifdef HAVE_SSL
|
||||
if (s->ssl && s->tls_pending)
|
||||
{
|
||||
result = ssl_socket_flush_nonblocking(s->ssl);
|
||||
if (result >= 0)
|
||||
s->tls_pending = result == 0;
|
||||
}
|
||||
#endif
|
||||
if (result < 0)
|
||||
{
|
||||
s->failed = true;
|
||||
net_rtmp_session_cancel(s->session);
|
||||
net_rtmp_session_discard(s->session);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int net_rtmp_socket_step(net_rtmp_socket_t *s, uint32_t time)
|
||||
{
|
||||
net_rtmp_span_t span;
|
||||
@@ -62,7 +107,13 @@ int net_rtmp_socket_step(net_rtmp_socket_t *s, uint32_t time)
|
||||
progress = net_rtmp_session_step(s->session);
|
||||
if (s->offset == s->size)
|
||||
{
|
||||
count = socket_receive_all_nonblocking(s->fd, &error, s->input, sizeof(s->input));
|
||||
#ifdef HAVE_SSL
|
||||
if (s->ssl)
|
||||
count = ssl_socket_receive_all_nonblocking(s->ssl, &error,
|
||||
s->input, sizeof(s->input));
|
||||
else
|
||||
#endif
|
||||
count = socket_receive_all_nonblocking(s->fd, &error, s->input, sizeof(s->input));
|
||||
if (count < 0 || error || (size_t)count > sizeof(s->input))
|
||||
goto failed;
|
||||
s->offset = 0;
|
||||
@@ -83,15 +134,37 @@ int net_rtmp_socket_step(net_rtmp_socket_t *s, uint32_t time)
|
||||
progress = true;
|
||||
if (net_rtmp_session_status(s->session, NULL, NULL) == NET_RTMP_SESSION_FAILED)
|
||||
goto failed;
|
||||
#ifdef HAVE_SSL
|
||||
if (s->tls_pending)
|
||||
{
|
||||
int result = net_rtmp_socket_flush(s);
|
||||
if (result < 0)
|
||||
return NET_RTMP_SOCKET_ERROR;
|
||||
if (!result)
|
||||
return progress ? NET_RTMP_SOCKET_PROGRESS : NET_RTMP_SOCKET_IDLE;
|
||||
progress = true;
|
||||
}
|
||||
#endif
|
||||
if (net_rtmp_session_get(s->session, &span))
|
||||
{
|
||||
limit = span.size < 4096 ? span.size : 4096;
|
||||
count = socket_send_all_nonblocking(s->fd, span.data, limit, true);
|
||||
#ifdef HAVE_SSL
|
||||
if (s->ssl)
|
||||
count = ssl_socket_send_all_nonblocking(s->ssl, span.data, limit, true);
|
||||
else
|
||||
#endif
|
||||
count = socket_send_all_nonblocking(s->fd, span.data, limit, true);
|
||||
if (count < 0 || (size_t)count > limit
|
||||
|| !net_rtmp_session_consume(s->session, (size_t)count))
|
||||
goto failed;
|
||||
if (count)
|
||||
{
|
||||
#ifdef HAVE_SSL
|
||||
if (s->ssl)
|
||||
s->tls_pending = true;
|
||||
#endif
|
||||
progress = true;
|
||||
}
|
||||
}
|
||||
return progress ? NET_RTMP_SOCKET_PROGRESS : NET_RTMP_SOCKET_IDLE;
|
||||
failed:
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
@@ -380,8 +381,13 @@ done:
|
||||
int socket_poll(struct pollfd *fds, unsigned nfds, int timeout)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
/* select() exists on 9x, XP, Vista+, and Xbox XTL. WSAPoll does not. */
|
||||
fd_set rfds, wfds, efds;
|
||||
/* select() exists on 9x, XP, Vista+, and Xbox XTL. WSAPoll does not.
|
||||
* A Winsock fd_set is a count and an array that select() reads up to
|
||||
* the count, and FD_SET drops what passes FD_SETSIZE (64), so more
|
||||
* sockets than that get sets sized for them. */
|
||||
fd_set sets[3];
|
||||
fd_set *rfds = &sets[0], *wfds = &sets[1], *efds = &sets[2];
|
||||
void *big = NULL;
|
||||
struct timeval tv, *ptv = NULL;
|
||||
unsigned i;
|
||||
int ret;
|
||||
@@ -390,9 +396,18 @@ int socket_poll(struct pollfd *fds, unsigned nfds, int timeout)
|
||||
if (nfds && !fds)
|
||||
return -1;
|
||||
|
||||
FD_ZERO(&rfds);
|
||||
FD_ZERO(&wfds);
|
||||
FD_ZERO(&efds);
|
||||
if (nfds > FD_SETSIZE)
|
||||
{
|
||||
size_t one = offsetof(fd_set, fd_array) + nfds * sizeof(SOCKET);
|
||||
if (!(big = malloc(3 * one)))
|
||||
return -1;
|
||||
rfds = (fd_set*)big;
|
||||
wfds = (fd_set*)((char*)big + one);
|
||||
efds = (fd_set*)((char*)big + 2 * one);
|
||||
}
|
||||
rfds->fd_count = 0;
|
||||
wfds->fd_count = 0;
|
||||
efds->fd_count = 0;
|
||||
|
||||
for (i = 0; i < nfds; i++)
|
||||
{
|
||||
@@ -408,10 +423,10 @@ int socket_poll(struct pollfd *fds, unsigned nfds, int timeout)
|
||||
}
|
||||
|
||||
if (fds[i].events & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI))
|
||||
FD_SET(s, &rfds);
|
||||
rfds->fd_array[rfds->fd_count++] = s;
|
||||
if (fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND))
|
||||
FD_SET(s, &wfds);
|
||||
FD_SET(s, &efds);
|
||||
wfds->fd_array[wfds->fd_count++] = s;
|
||||
efds->fd_array[efds->fd_count++] = s;
|
||||
}
|
||||
|
||||
if (timeout >= 0)
|
||||
@@ -422,9 +437,12 @@ int socket_poll(struct pollfd *fds, unsigned nfds, int timeout)
|
||||
}
|
||||
|
||||
/* Winsock ignores nfds and walks the fd_set. */
|
||||
ret = socket_select(0, &rfds, &wfds, &efds, ptv);
|
||||
ret = socket_select(0, rfds, wfds, efds, ptv);
|
||||
if (ret < 0)
|
||||
{
|
||||
free(big);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < nfds; i++)
|
||||
{
|
||||
@@ -433,18 +451,19 @@ int socket_poll(struct pollfd *fds, unsigned nfds, int timeout)
|
||||
if (fds[i].revents & POLLNVAL)
|
||||
continue;
|
||||
|
||||
if (FD_ISSET(s, &rfds))
|
||||
if (FD_ISSET(s, rfds))
|
||||
fds[i].revents |= (short)(fds[i].events &
|
||||
(POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI));
|
||||
if (FD_ISSET(s, &wfds))
|
||||
if (FD_ISSET(s, wfds))
|
||||
fds[i].revents |= (short)(fds[i].events &
|
||||
(POLLOUT | POLLWRNORM | POLLWRBAND));
|
||||
if (FD_ISSET(s, &efds))
|
||||
if (FD_ISSET(s, efds))
|
||||
fds[i].revents |= POLLERR;
|
||||
if (fds[i].revents)
|
||||
nready++;
|
||||
}
|
||||
|
||||
free(big);
|
||||
return nready;
|
||||
#elif defined(VITA)
|
||||
int i, j;
|
||||
@@ -801,6 +820,87 @@ static void socket_set_last_error(int err)
|
||||
}
|
||||
#endif
|
||||
|
||||
int socket_connect_begin(int fd, void *data)
|
||||
{
|
||||
int result;
|
||||
if (fd < 0 || !data || !socket_nonblock(fd))
|
||||
return SOCKET_CONNECT_ERROR;
|
||||
result = socket_connect(fd, data);
|
||||
if (!result)
|
||||
return SOCKET_CONNECT_READY;
|
||||
return isinprogress(result) || isagain(result)
|
||||
? SOCKET_CONNECT_PENDING : SOCKET_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
int socket_connect_step(int fd, void *data)
|
||||
{
|
||||
int result;
|
||||
if (fd < 0 || !data)
|
||||
return SOCKET_CONNECT_ERROR;
|
||||
#ifdef NETWORK_HAVE_POLL
|
||||
{
|
||||
struct pollfd fds = {0};
|
||||
NET_POLL_FD(fd, &fds);
|
||||
NET_POLL_EVENT(POLLOUT, &fds);
|
||||
result = socket_poll(&fds, 1, 0);
|
||||
if (result < 0)
|
||||
return SOCKET_CONNECT_ERROR;
|
||||
if (!result || !NET_POLL_HAS_EVENT(POLLOUT | POLLERR | POLLHUP | POLLNVAL, &fds))
|
||||
return SOCKET_CONNECT_PENDING;
|
||||
}
|
||||
#else
|
||||
{
|
||||
fd_set writable, errors;
|
||||
struct timeval timeout;
|
||||
#ifndef _WIN32
|
||||
if (fd >= FD_SETSIZE)
|
||||
return SOCKET_CONNECT_ERROR;
|
||||
#endif
|
||||
FD_ZERO(&writable);
|
||||
FD_ZERO(&errors);
|
||||
FD_SET(fd, &writable);
|
||||
FD_SET(fd, &errors);
|
||||
timeout.tv_sec = timeout.tv_usec = 0;
|
||||
result = socket_select(fd + 1, NULL, &writable, &errors, &timeout);
|
||||
if (result < 0)
|
||||
return SOCKET_CONNECT_ERROR;
|
||||
if (!result || (!FD_ISSET(fd, &writable) && !FD_ISSET(fd, &errors)))
|
||||
return SOCKET_CONNECT_PENDING;
|
||||
}
|
||||
#endif
|
||||
#if defined(GEKKO) && !defined(GEKKO_NATIVE)
|
||||
result = socket_connect(fd, data);
|
||||
if (!result || -result == EISCONN)
|
||||
return SOCKET_CONNECT_READY;
|
||||
#elif defined(_3DS) || defined(GEKKO_NATIVE)
|
||||
result = socket_connect(fd, data);
|
||||
if (!result || errno == EISCONN)
|
||||
return SOCKET_CONNECT_READY;
|
||||
#elif defined(WIIU)
|
||||
result = socket_connect(fd, data);
|
||||
if (!result || socketlasterr() == SO_EISCONN)
|
||||
return SOCKET_CONNECT_READY;
|
||||
#else
|
||||
{
|
||||
int error = -1;
|
||||
socklen_t size = sizeof(error);
|
||||
result = getsockopt(fd, SOL_SOCKET, SO_ERROR, (char*)&error, &size);
|
||||
if (result < 0 || size != sizeof(error))
|
||||
return SOCKET_CONNECT_ERROR;
|
||||
if (!error)
|
||||
return SOCKET_CONNECT_READY;
|
||||
#ifdef SOCKET_CONNECT_SETS_ERROR
|
||||
socket_set_last_error(error);
|
||||
#endif
|
||||
return SOCKET_CONNECT_ERROR;
|
||||
}
|
||||
#endif
|
||||
#if defined(GEKKO) || defined(GEKKO_NATIVE) || defined(_3DS) || defined(WIIU)
|
||||
return isinprogress(result) || isagain(result)
|
||||
? SOCKET_CONNECT_PENDING : SOCKET_CONNECT_ERROR;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool socket_connect_with_timeout(int fd, void *data, int timeout)
|
||||
{
|
||||
int res;
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
struct ssl_state
|
||||
{
|
||||
int fd;
|
||||
int handshake_state;
|
||||
char domain[256];
|
||||
br_ssl_client_context sc;
|
||||
br_x509_minimal_context xc;
|
||||
@@ -280,7 +281,7 @@ void* ssl_socket_init(int fd, const char *domain)
|
||||
|
||||
static bool process_inner(struct ssl_state *state, bool blocking)
|
||||
{
|
||||
bool dummy;
|
||||
bool dummy = false;
|
||||
size_t buflen;
|
||||
ssize_t bytes;
|
||||
uint8_t *buf = br_ssl_engine_sendrec_buf(&state->sc.eng, &buflen);
|
||||
@@ -321,7 +322,7 @@ static bool process_inner(struct ssl_state *state, bool blocking)
|
||||
bytes = socket_receive_all_nonblocking(state->fd, &dummy, buf, buflen);
|
||||
if (bytes > 0)
|
||||
br_ssl_engine_recvrec_ack(&state->sc.eng, bytes);
|
||||
if (bytes < 0)
|
||||
if (bytes < 0 || dummy)
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -381,44 +382,73 @@ static void ssl_bear_report_verify(struct ssl_state *state)
|
||||
ssl_socket_log_verify_fail(1, state->domain, info);
|
||||
}
|
||||
|
||||
int ssl_socket_handshake_step(void *state_data, bool *read, bool *write)
|
||||
{
|
||||
struct ssl_state *state = (struct ssl_state*)state_data;
|
||||
unsigned status;
|
||||
if (read)
|
||||
*read = false;
|
||||
if (write)
|
||||
*write = false;
|
||||
if (!state || state->handshake_state < 0)
|
||||
return -1;
|
||||
if (state->handshake_state == 2)
|
||||
return 1;
|
||||
if (!state->handshake_state)
|
||||
{
|
||||
if (!socket_set_block(state->fd, false))
|
||||
goto error;
|
||||
state->handshake_state = 1;
|
||||
}
|
||||
if (!process_inner(state, false))
|
||||
goto error;
|
||||
status = br_ssl_engine_current_state(&state->sc.eng);
|
||||
if (status & BR_SSL_CLOSED)
|
||||
goto error;
|
||||
if (status & BR_SSL_SENDAPP)
|
||||
{
|
||||
state->handshake_state = 2;
|
||||
return 1;
|
||||
}
|
||||
if (read)
|
||||
*read = (status & BR_SSL_RECVREC) != 0;
|
||||
if (write)
|
||||
*write = (status & BR_SSL_SENDREC) != 0;
|
||||
return 0;
|
||||
error:
|
||||
state->handshake_state = -1;
|
||||
ssl_bear_report_verify(state);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ssl_socket_connect(void *state_data,
|
||||
void *data, bool timeout_enable, bool nonblock)
|
||||
{
|
||||
struct ssl_state *state = (struct ssl_state*)state_data;
|
||||
unsigned bearstate;
|
||||
|
||||
int result;
|
||||
bool read, write;
|
||||
(void)nonblock;
|
||||
if (!state)
|
||||
return -1;
|
||||
if (timeout_enable)
|
||||
{
|
||||
if (!socket_connect_with_timeout(state->fd, data, 5000))
|
||||
return -1;
|
||||
/* socket_connect_with_timeout makes the socket non-blocking. */
|
||||
if (!socket_set_block(state->fd, true))
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
else if (socket_connect(state->fd, data))
|
||||
return -1;
|
||||
while ((result = ssl_socket_handshake_step(state, &read, &write)) == 0)
|
||||
{
|
||||
if (socket_connect(state->fd, data))
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (!process_inner(state, true))
|
||||
if ((read || write)
|
||||
&& (!socket_wait(state->fd, &read, &write, SSL_SOCKET_IO_TIMEOUT_MS)
|
||||
|| !(read || write)))
|
||||
{
|
||||
ssl_bear_report_verify(state);
|
||||
state->handshake_state = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bearstate = br_ssl_engine_current_state(&state->sc.eng);
|
||||
if (bearstate & BR_SSL_SENDAPP)
|
||||
break; /* handshake done */
|
||||
if (bearstate & BR_SSL_CLOSED)
|
||||
{
|
||||
ssl_bear_report_verify(state);
|
||||
return -1; /* failed */
|
||||
}
|
||||
}
|
||||
|
||||
if (result < 0 || !socket_set_block(state->fd, true))
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -574,12 +604,16 @@ int ssl_socket_flush_nonblocking(void *state_data)
|
||||
void ssl_socket_close(void *state_data)
|
||||
{
|
||||
struct ssl_state *state = (struct ssl_state*)state_data;
|
||||
|
||||
br_ssl_engine_close(&state->sc.eng);
|
||||
process_inner(state, false); /* send close notification */
|
||||
socket_close(state->fd); /* but immediately close socket
|
||||
and don't worry about recipient
|
||||
getting our message */
|
||||
if (!state || state->fd < 0)
|
||||
return;
|
||||
if (state->handshake_state == 2 && socket_set_block(state->fd, false))
|
||||
{
|
||||
br_ssl_engine_close(&state->sc.eng);
|
||||
process_inner(state, false);
|
||||
}
|
||||
socket_close(state->fd);
|
||||
state->fd = -1;
|
||||
state->handshake_state = -1;
|
||||
}
|
||||
|
||||
void ssl_socket_free(void *state_data)
|
||||
|
||||
@@ -43,6 +43,11 @@
|
||||
#include <mbedtls/platform.h>
|
||||
#include <mbedtls/net_sockets.h>
|
||||
#include <mbedtls/ssl.h>
|
||||
#if MBEDTLS_VERSION_MAJOR == 3 && MBEDTLS_VERSION_MINOR == 0
|
||||
#define SSL_MBED_FD(ctx) ((ctx)->MBEDTLS_PRIVATE(fd))
|
||||
#else
|
||||
#define SSL_MBED_FD(ctx) ((ctx)->fd)
|
||||
#endif
|
||||
#if MBEDTLS_VERSION_MAJOR >= 4
|
||||
#include <psa/crypto.h>
|
||||
#else
|
||||
@@ -108,6 +113,7 @@ struct ssl_state
|
||||
* holds that record half-sent and must be called again with the same
|
||||
* buffer and length to finish it. */
|
||||
size_t wpend;
|
||||
int handshake_state; /* 0: setup, 1: active, 2: complete, -1: failed */
|
||||
};
|
||||
|
||||
static void ssl_debug(void *ctx, int level,
|
||||
@@ -180,7 +186,7 @@ void* ssl_socket_init(int fd, const char *domain)
|
||||
mbedtls_entropy_init(&state->entropy);
|
||||
#endif
|
||||
|
||||
state->net_ctx.fd = fd;
|
||||
SSL_MBED_FD(&state->net_ctx) = fd;
|
||||
|
||||
#if SSL_MBED_LEGACY_RNG
|
||||
if (mbedtls_ctr_drbg_seed(&state->ctr_drbg,
|
||||
@@ -247,7 +253,7 @@ error:
|
||||
* Mirrors the normal-teardown path in ssl_socket_free below
|
||||
* (lines 355-361) in reverse of init order. mbedtls_net_free
|
||||
* is intentionally not called for net_ctx (line 111):
|
||||
* - init sets state->net_ctx.fd from the caller's fd
|
||||
* - init borrows the caller's descriptor
|
||||
* parameter at line 120, and the caller still owns that
|
||||
* fd on this error exit.
|
||||
* - ssl_socket_free similarly never calls mbedtls_net_free;
|
||||
@@ -319,27 +325,9 @@ void ssl_socket_log_verify_disabled(const char *domain)
|
||||
}
|
||||
#endif
|
||||
|
||||
int ssl_socket_connect(void *state_data,
|
||||
void *data, bool timeout_enable, bool nonblock)
|
||||
static int ssl_mbed_start(struct ssl_state *state)
|
||||
{
|
||||
int ret, flags;
|
||||
int authmode;
|
||||
struct ssl_state *state = (struct ssl_state*)state_data;
|
||||
|
||||
if (timeout_enable)
|
||||
{
|
||||
if (!socket_connect_with_timeout(state->net_ctx.fd, data, 5000))
|
||||
return -1;
|
||||
/* socket_connect_with_timeout makes the socket non-blocking. */
|
||||
if (!socket_set_block(state->net_ctx.fd, true))
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (socket_connect(state->net_ctx.fd, data))
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret, authmode;
|
||||
state->last_err = 0;
|
||||
|
||||
if ((ret = mbedtls_ssl_config_defaults(&state->conf,
|
||||
@@ -386,55 +374,113 @@ int ssl_socket_connect(void *state_data,
|
||||
|
||||
mbedtls_ssl_set_bio(&state->ctx, &state->net_ctx, mbedtls_net_send, mbedtls_net_recv, NULL);
|
||||
|
||||
/* The handshake runs on a non-blocking socket, each wait for the
|
||||
* server bounded: on a blocking one, a server that goes silent holds
|
||||
* recv() for good. */
|
||||
if (!socket_set_block(state->net_ctx.fd, false))
|
||||
if (!socket_set_block(SSL_MBED_FD(&state->net_ctx), false))
|
||||
return -1;
|
||||
while ((ret = mbedtls_ssl_handshake(&state->ctx)) != 0)
|
||||
state->handshake_state = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ssl_mbed_report_verify(struct ssl_state *state, bool required)
|
||||
{
|
||||
char info[512];
|
||||
uint32_t flags = mbedtls_ssl_get_verify_result(&state->ctx);
|
||||
mbedtls_x509_crt_verify_info(info, sizeof(info), " ! ", flags);
|
||||
ssl_socket_log_verify_fail(required ? 1 : 0, state->domain, info);
|
||||
}
|
||||
|
||||
int ssl_socket_handshake_step(void *state_data, bool *read, bool *write)
|
||||
{
|
||||
struct ssl_state *state = (struct ssl_state*)state_data;
|
||||
int result;
|
||||
bool complete;
|
||||
if (read)
|
||||
*read = false;
|
||||
if (write)
|
||||
*write = false;
|
||||
if (!state || state->handshake_state < 0)
|
||||
return -1;
|
||||
if (state->handshake_state == 2)
|
||||
return 1;
|
||||
if (!state->handshake_state && ssl_mbed_start(state) != 0)
|
||||
{
|
||||
if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE)
|
||||
state->handshake_state = -1;
|
||||
return -1;
|
||||
}
|
||||
result = mbedtls_ssl_handshake_step(&state->ctx);
|
||||
if (result == MBEDTLS_ERR_SSL_WANT_READ)
|
||||
{
|
||||
if (read)
|
||||
*read = true;
|
||||
return 0;
|
||||
}
|
||||
if (result == MBEDTLS_ERR_SSL_WANT_WRITE)
|
||||
{
|
||||
if (write)
|
||||
*write = true;
|
||||
return 0;
|
||||
}
|
||||
#ifdef MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS
|
||||
if (result == MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS)
|
||||
return 0;
|
||||
#endif
|
||||
#ifdef MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS
|
||||
if (result == MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS)
|
||||
return 0;
|
||||
#endif
|
||||
if (result)
|
||||
{
|
||||
state->last_err = result;
|
||||
state->handshake_state = -1;
|
||||
if (result == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED)
|
||||
ssl_mbed_report_verify(state, true);
|
||||
return -1;
|
||||
}
|
||||
#if MBEDTLS_VERSION_NUMBER >= 0x03020000
|
||||
complete = mbedtls_ssl_is_handshake_over(&state->ctx) != 0;
|
||||
#elif MBEDTLS_VERSION_MAJOR >= 3
|
||||
/* 3.0/3.1 predate the completion accessor. */
|
||||
complete = state->ctx.MBEDTLS_PRIVATE(state) == MBEDTLS_SSL_HANDSHAKE_OVER;
|
||||
#else
|
||||
complete = state->ctx.state == MBEDTLS_SSL_HANDSHAKE_OVER;
|
||||
#endif
|
||||
if (!complete)
|
||||
return 0;
|
||||
state->handshake_state = 2;
|
||||
if (mbedtls_ssl_get_verify_result(&state->ctx))
|
||||
ssl_mbed_report_verify(state, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ssl_socket_connect(void *state_data,
|
||||
void *data, bool timeout_enable, bool nonblock)
|
||||
{
|
||||
struct ssl_state *state = (struct ssl_state*)state_data;
|
||||
int result;
|
||||
bool read, write;
|
||||
(void)nonblock;
|
||||
if (!state)
|
||||
return -1;
|
||||
if (timeout_enable)
|
||||
{
|
||||
if (!socket_connect_with_timeout(SSL_MBED_FD(&state->net_ctx), data, 5000))
|
||||
return -1;
|
||||
}
|
||||
else if (socket_connect(SSL_MBED_FD(&state->net_ctx), data))
|
||||
return -1;
|
||||
while ((result = ssl_socket_handshake_step(state, &read, &write)) == 0)
|
||||
{
|
||||
if ((read || write)
|
||||
&& (!socket_wait(SSL_MBED_FD(&state->net_ctx), &read, &write, SSL_SOCKET_IO_TIMEOUT_MS)
|
||||
|| !(read || write)))
|
||||
{
|
||||
bool rd = (ret == MBEDTLS_ERR_SSL_WANT_READ);
|
||||
bool wr = !rd;
|
||||
if ( !socket_wait(state->net_ctx.fd, &rd, &wr, SSL_SOCKET_IO_TIMEOUT_MS)
|
||||
|| !(rd || wr))
|
||||
{
|
||||
state->last_err = MBEDTLS_ERR_SSL_TIMEOUT;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state->last_err = ret;
|
||||
/* Fail-closed: under REQUIRED a bad certificate makes the
|
||||
* handshake return here (MBEDTLS_ERR_X509_CERT_VERIFY_FAILED)
|
||||
* before we reach the verify-result block below. Surface the
|
||||
* reason, then bail. */
|
||||
if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED)
|
||||
{
|
||||
char vrfy_buf[512];
|
||||
uint32_t vflags = mbedtls_ssl_get_verify_result(&state->ctx);
|
||||
mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), " ! ", vflags);
|
||||
ssl_socket_log_verify_fail(1, state->domain, vrfy_buf);
|
||||
}
|
||||
state->last_err = MBEDTLS_ERR_SSL_TIMEOUT;
|
||||
state->handshake_state = -1;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (!socket_set_block(state->net_ctx.fd, true))
|
||||
if (result < 0 || !socket_set_block(SSL_MBED_FD(&state->net_ctx), true))
|
||||
return -1;
|
||||
|
||||
if ((flags = mbedtls_ssl_get_verify_result(&state->ctx)) != 0)
|
||||
{
|
||||
char vrfy_buf[512];
|
||||
mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), " ! ", flags);
|
||||
/* Reached only under OPTIONAL/DISABLED: the handshake succeeded
|
||||
* despite a verification failure. Log the soft-fail and let the
|
||||
* connection proceed (the mode's documented behaviour). */
|
||||
ssl_socket_log_verify_fail(0, state->domain, vrfy_buf);
|
||||
}
|
||||
|
||||
return state->net_ctx.fd;
|
||||
return SSL_MBED_FD(&state->net_ctx);
|
||||
}
|
||||
|
||||
int ssl_socket_last_error(void *state_data)
|
||||
@@ -617,10 +663,13 @@ int ssl_socket_flush_nonblocking(void *state_data)
|
||||
void ssl_socket_close(void *state_data)
|
||||
{
|
||||
struct ssl_state *state = (struct ssl_state*)state_data;
|
||||
|
||||
mbedtls_ssl_close_notify(&state->ctx);
|
||||
|
||||
socket_close(state->net_ctx.fd);
|
||||
if (!state || SSL_MBED_FD(&state->net_ctx) < 0)
|
||||
return;
|
||||
if (state->handshake_state == 2 && socket_set_block(SSL_MBED_FD(&state->net_ctx), false))
|
||||
mbedtls_ssl_close_notify(&state->ctx);
|
||||
socket_close(SSL_MBED_FD(&state->net_ctx));
|
||||
SSL_MBED_FD(&state->net_ctx) = -1;
|
||||
state->handshake_state = -1;
|
||||
}
|
||||
|
||||
void ssl_socket_free(void *state_data)
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -316,9 +316,13 @@ bool net_upnp_discover(struct net_upnp_gateway *gw, int timeout_ms)
|
||||
"ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n"
|
||||
"MX: 2\r\n"
|
||||
"\r\n";
|
||||
/* an SSDP reply is a datagram, one Ethernet frame at most; on the
|
||||
* heap, as this can run on an 8 KiB thread stack (PSP, GX) */
|
||||
enum { NET_UPNP_SSDP_MAX = 1536 };
|
||||
retro_time_t deadline = net_upnp_deadline(timeout_ms);
|
||||
struct addrinfo *group = NULL;
|
||||
struct addrinfo hints;
|
||||
char *buf;
|
||||
int fd;
|
||||
bool found = false;
|
||||
|
||||
@@ -329,8 +333,14 @@ bool net_upnp_discover(struct net_upnp_gateway *gw, int timeout_ms)
|
||||
hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
|
||||
if (getaddrinfo_retro("239.255.255.250", "1900", &hints, &group) || !group)
|
||||
return false;
|
||||
if (!(buf = (char*)malloc(NET_UPNP_SSDP_MAX)))
|
||||
{
|
||||
freeaddrinfo_retro(group);
|
||||
return false;
|
||||
}
|
||||
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
|
||||
{
|
||||
free(buf);
|
||||
freeaddrinfo_retro(group);
|
||||
return false;
|
||||
}
|
||||
@@ -351,14 +361,13 @@ bool net_upnp_discover(struct net_upnp_gateway *gw, int timeout_ms)
|
||||
{
|
||||
while (!found)
|
||||
{
|
||||
char buf[1536];
|
||||
char location[NET_UPNP_URL_MAX];
|
||||
bool rd = true;
|
||||
int left = net_upnp_remaining_ms(deadline);
|
||||
ssize_t got;
|
||||
if (!left || !socket_wait(fd, &rd, NULL, left) || !rd)
|
||||
break;
|
||||
got = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
|
||||
got = recvfrom(fd, buf, NET_UPNP_SSDP_MAX, 0, NULL, NULL);
|
||||
if (got <= 0)
|
||||
continue;
|
||||
if (net_upnp_ssdp_location(buf, (size_t)got, location,
|
||||
@@ -370,18 +379,49 @@ bool net_upnp_discover(struct net_upnp_gateway *gw, int timeout_ms)
|
||||
location, gw)
|
||||
&& net_upnp_lan_addr(gw->control_url, gw->lan_addr,
|
||||
sizeof(gw->lan_addr)))
|
||||
{
|
||||
strlcpy(gw->desc_url, location, sizeof(gw->desc_url));
|
||||
found = true;
|
||||
}
|
||||
free(xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
socket_close(fd);
|
||||
free(buf);
|
||||
freeaddrinfo_retro(group);
|
||||
if (!found)
|
||||
memset(gw, 0, sizeof(*gw));
|
||||
return found;
|
||||
}
|
||||
|
||||
bool net_upnp_gateway_from_url(struct net_upnp_gateway *gw,
|
||||
const char *desc_url, int timeout_ms)
|
||||
{
|
||||
int status;
|
||||
bool found = false;
|
||||
char *xml;
|
||||
|
||||
memset(gw, 0, sizeof(*gw));
|
||||
if (!desc_url || !string_starts_with_case_insensitive(desc_url, "http://")
|
||||
|| strlcpy(gw->desc_url, desc_url, sizeof(gw->desc_url))
|
||||
>= sizeof(gw->desc_url))
|
||||
{
|
||||
memset(gw, 0, sizeof(*gw));
|
||||
return false;
|
||||
}
|
||||
xml = net_upnp_http(desc_url, NULL, NULL, net_upnp_deadline(timeout_ms),
|
||||
&status);
|
||||
if (xml && status == 200 && net_upnp_parse_description(xml, desc_url, gw)
|
||||
&& net_upnp_lan_addr(gw->control_url, gw->lan_addr,
|
||||
sizeof(gw->lan_addr)))
|
||||
found = true;
|
||||
free(xml);
|
||||
if (!found)
|
||||
memset(gw, 0, sizeof(*gw));
|
||||
return found;
|
||||
}
|
||||
|
||||
bool net_upnp_external_address(const struct net_upnp_gateway *gw,
|
||||
char *s, size_t len, int timeout_ms)
|
||||
{
|
||||
@@ -412,6 +452,14 @@ static void net_upnp_escape(char *s, size_t len, const char *text)
|
||||
|
||||
bool net_upnp_add_port_mapping(const struct net_upnp_gateway *gw,
|
||||
unsigned port, bool udp, const char *description, int timeout_ms)
|
||||
{
|
||||
return net_upnp_add_port_mapping_ext(gw, port, port, udp, description,
|
||||
timeout_ms);
|
||||
}
|
||||
|
||||
bool net_upnp_add_port_mapping_ext(const struct net_upnp_gateway *gw,
|
||||
unsigned external_port, unsigned internal_port, bool udp,
|
||||
const char *description, int timeout_ms)
|
||||
{
|
||||
char args[640];
|
||||
char desc[128];
|
||||
@@ -427,7 +475,8 @@ bool net_upnp_add_port_mapping(const struct net_upnp_gateway *gw,
|
||||
"<NewEnabled>1</NewEnabled>"
|
||||
"<NewPortMappingDescription>%s</NewPortMappingDescription>"
|
||||
"<NewLeaseDuration>0</NewLeaseDuration>",
|
||||
port, udp ? "UDP" : "TCP", port, gw->lan_addr, desc);
|
||||
external_port, udp ? "UDP" : "TCP", internal_port, gw->lan_addr,
|
||||
desc);
|
||||
xml = net_upnp_soap(gw, "AddPortMapping", args,
|
||||
net_upnp_deadline(timeout_ms), &status);
|
||||
free(xml);
|
||||
|
||||
@@ -2,6 +2,8 @@ include ../../sample_objdir.mk
|
||||
|
||||
TARGET := rinflate_window_test
|
||||
TARGET_TEST := rdeflate_hash_test
|
||||
TARGET_TEST2 := rdeflate_flush_test
|
||||
TARGET_TEST3 := encoding_adler32_test
|
||||
|
||||
LIBRETRO_COMM_DIR := ../../..
|
||||
|
||||
@@ -39,7 +41,11 @@ ifneq ($(SANITIZER),)
|
||||
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
all: $(TARGET) $(TARGET_TEST)
|
||||
# The flush test links what the window test does, and so does the adler32 one
|
||||
FLUSH_SOURCES := rdeflate_flush_test.c $(filter-out rinflate_window_test.c,$(SOURCES))
|
||||
ADLER_SOURCES := encoding_adler32_test.c $(filter-out rinflate_window_test.c,$(SOURCES))
|
||||
|
||||
all: $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
@@ -54,12 +60,20 @@ $(TARGET): $(OBJS)
|
||||
$(TARGET_TEST): $(HASH_SOURCES) $(LIBRETRO_COMM_DIR)/encodings/encoding_deflate.c
|
||||
$(CC) $(CFLAGS) $(HASH_ARCH) -o $@ $(HASH_SOURCES) $(LDFLAGS)
|
||||
|
||||
$(TARGET_TEST2): $(FLUSH_SOURCES)
|
||||
$(CC) $(CFLAGS) -o $@ $(FLUSH_SOURCES) $(LDFLAGS)
|
||||
|
||||
$(TARGET_TEST3): $(ADLER_SOURCES)
|
||||
$(CC) $(CFLAGS) -o $@ $(ADLER_SOURCES) $(LDFLAGS)
|
||||
|
||||
check: all
|
||||
./$(TARGET)
|
||||
./$(TARGET_TEST)
|
||||
./$(TARGET_TEST2)
|
||||
./$(TARGET_TEST3)
|
||||
|
||||
clean:
|
||||
rm -rf $(SAMPLE_OBJDIR)
|
||||
rm -f $(TARGET) $(TARGET_TEST) $(TARGET).exe $(TARGET_TEST).exe $(OBJS)
|
||||
rm -f $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) $(TARGET).exe $(TARGET_TEST).exe $(TARGET_TEST2).exe $(TARGET_TEST3).exe $(OBJS)
|
||||
|
||||
.PHONY: all check clean
|
||||
@@ -0,0 +1,110 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (encoding_adler32_test.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* encoding_adler32: known values, a checksum continued across pieces
|
||||
* equal to the one taken whole, and long runs against the textbook loop
|
||||
* at the lengths that matter to the vector kernel (block edges, the
|
||||
* 5552-octet bound, all 0xff from the largest seed). */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <encodings/deflate.h>
|
||||
|
||||
static int failures = 0;
|
||||
|
||||
static void check(uint32_t got, uint32_t want, const char *what, size_t n)
|
||||
{
|
||||
if (got != want)
|
||||
{
|
||||
printf("FAIL %s (%u): got %08x, want %08x\n", what, (unsigned)n,
|
||||
(unsigned)got, (unsigned)want);
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t ref_adler32(uint32_t adler, const uint8_t *buf, size_t len)
|
||||
{
|
||||
uint32_t a = adler & 0xffff;
|
||||
uint32_t b = adler >> 16;
|
||||
size_t i;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
a = (a + buf[i]) % 65521;
|
||||
b = (b + a) % 65521;
|
||||
}
|
||||
return (b << 16) | a;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
static const size_t lens[] = { 0, 1, 15, 16, 17, 31, 32, 63, 64, 65,
|
||||
127, 128, 129, 5551, 5552, 5553, 11104, 65536, 100003 };
|
||||
const size_t big = 100003;
|
||||
uint8_t *buf = (uint8_t*)malloc(big);
|
||||
size_t i, k;
|
||||
|
||||
if (!buf)
|
||||
return 1;
|
||||
|
||||
check(encoding_adler32(1, NULL, 0), 1, "empty", 0);
|
||||
check(encoding_adler32(1, (const uint8_t*)"Wikipedia", 9), 0x11e60398,
|
||||
"Wikipedia", 9);
|
||||
|
||||
srand(1);
|
||||
for (i = 0; i < big; i++)
|
||||
buf[i] = (uint8_t)rand();
|
||||
for (k = 0; k < sizeof(lens) / sizeof(lens[0]); k++)
|
||||
{
|
||||
size_t off;
|
||||
for (off = 0; off < 4; off++)
|
||||
if (lens[k] + off <= big)
|
||||
check(encoding_adler32(1, buf + off, lens[k]),
|
||||
ref_adler32(1, buf + off, lens[k]), "random", lens[k]);
|
||||
}
|
||||
|
||||
/* continued across pieces of every size from 1 to 97 */
|
||||
for (k = 1; k < 98; k++)
|
||||
{
|
||||
uint32_t a = 1;
|
||||
for (i = 0; i < big; i += k)
|
||||
a = encoding_adler32(a, buf + i, i + k > big ? big - i : k);
|
||||
check(a, ref_adler32(1, buf, big), "pieces", k);
|
||||
}
|
||||
|
||||
/* the worst case for the 32-bit sums: all 0xff from the largest seed */
|
||||
memset(buf, 0xff, big);
|
||||
for (k = 0; k < sizeof(lens) / sizeof(lens[0]); k++)
|
||||
check(encoding_adler32(0xfff0fff0u, buf, lens[k]),
|
||||
ref_adler32(0xfff0fff0u, buf, lens[k]), "0xff", lens[k]);
|
||||
|
||||
free(buf);
|
||||
if (failures)
|
||||
{
|
||||
printf("%d failure(s)\n", failures);
|
||||
return 1;
|
||||
}
|
||||
printf("encoding_adler32: ok\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (rdeflate_flush_test.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* rdeflate_flush: a gzip stream compressed a piece at a time with a sync
|
||||
* flush after each piece, as a log written while it grows. After every
|
||||
* flush the output so far must end with the empty stored block
|
||||
* (00 00 ff ff) and decode to exactly the input so far; at the end the
|
||||
* whole stream decodes, trailer checked. Every level, and an output a
|
||||
* few bytes at a time so the flush itself suspends. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <encodings/deflate.h>
|
||||
|
||||
#define TOTAL (300 * 1024)
|
||||
#define PIECES 37
|
||||
|
||||
static int failures = 0;
|
||||
|
||||
#define CHECK(c) do { if (!(c)) { \
|
||||
fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, #c); failures++; } } while (0)
|
||||
|
||||
static void gen(uint8_t *p, size_t n)
|
||||
{
|
||||
static const char text[] = "Log line: the emulator says something. ";
|
||||
uint32_t x = 7;
|
||||
size_t i;
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
if ((i / 3000) % 3)
|
||||
p[i] = (uint8_t)text[i % (sizeof(text) - 1)];
|
||||
else
|
||||
{
|
||||
x = x * 1103515245u + 12345u;
|
||||
p[i] = (uint8_t)(x >> 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Decodes @len bytes of a gzip stream; returns the bytes it gave, or
|
||||
* (size_t)-1 on an error. @complete: whether the stream must end. */
|
||||
static size_t decode(const uint8_t *in, size_t len, uint8_t *out,
|
||||
size_t out_len, int complete)
|
||||
{
|
||||
void *z = rinflate_new(31);
|
||||
size_t rd = 0, wr = 0;
|
||||
int r;
|
||||
if (!z)
|
||||
return (size_t)-1;
|
||||
rinflate_set_in(z, in, len);
|
||||
rinflate_set_out(z, out, out_len);
|
||||
r = rinflate_process(z, &rd, &wr);
|
||||
rinflate_free(z);
|
||||
if (r == RDEFLATE_PROCESS_ERROR || (complete && r != RDEFLATE_PROCESS_END)
|
||||
|| (!complete && r == RDEFLATE_PROCESS_END))
|
||||
return (size_t)-1;
|
||||
return wr;
|
||||
}
|
||||
|
||||
/* Runs the encoder until it stops with room left, @step bytes of output
|
||||
* room at a time */
|
||||
static int pump(void *z, uint8_t *out, size_t cap, size_t *len, size_t step,
|
||||
int *ended)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
size_t room = cap - *len < step ? cap - *len : step;
|
||||
size_t rd = 0, wr = 0;
|
||||
int r;
|
||||
if (!room)
|
||||
return 0;
|
||||
rdeflate_set_out(z, out + *len, room);
|
||||
r = rdeflate_process(z, &rd, &wr);
|
||||
*len += wr;
|
||||
if (r == RDEFLATE_PROCESS_ERROR)
|
||||
return 0;
|
||||
if (r == RDEFLATE_PROCESS_END)
|
||||
{
|
||||
*ended = 1;
|
||||
return 1;
|
||||
}
|
||||
if (wr < room)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
static const int levels[] = { 0, 1, 3, 6, 9 };
|
||||
static const size_t steps[] = { 1 << 20, 5 };
|
||||
uint8_t *src = (uint8_t*)malloc(TOTAL);
|
||||
uint8_t *out = (uint8_t*)malloc(TOTAL * 2);
|
||||
uint8_t *back = (uint8_t*)malloc(TOTAL);
|
||||
unsigned li, si;
|
||||
|
||||
if (!src || !out || !back)
|
||||
return 1;
|
||||
gen(src, TOTAL);
|
||||
|
||||
for (li = 0; li < sizeof(levels) / sizeof(levels[0]); li++)
|
||||
{
|
||||
for (si = 0; si < sizeof(steps) / sizeof(steps[0]); si++)
|
||||
{
|
||||
void *z = rdeflate_new(levels[li], 31);
|
||||
size_t len = 0, given = 0;
|
||||
int ended = 0, p;
|
||||
|
||||
CHECK(z != NULL);
|
||||
if (!z)
|
||||
continue;
|
||||
for (p = 0; p < PIECES; p++)
|
||||
{
|
||||
size_t n = TOTAL / PIECES + (p == PIECES - 1 ? TOTAL % PIECES : 0);
|
||||
size_t got;
|
||||
|
||||
rdeflate_set_in(z, src + given, n);
|
||||
given += n;
|
||||
rdeflate_flush(z);
|
||||
CHECK(pump(z, out, TOTAL * 2, &len, steps[si], &ended));
|
||||
CHECK(!ended);
|
||||
CHECK(len >= 4 && !memcmp(out + len - 4, "\x00\x00\xff\xff", 4));
|
||||
|
||||
got = decode(out, len, back, TOTAL, 0);
|
||||
CHECK(got == given);
|
||||
CHECK(got == given && !memcmp(back, src, given));
|
||||
}
|
||||
|
||||
rdeflate_set_in(z, NULL, 0);
|
||||
rdeflate_finish(z);
|
||||
CHECK(pump(z, out, TOTAL * 2, &len, steps[si], &ended));
|
||||
CHECK(ended);
|
||||
CHECK(decode(out, len, back, TOTAL, 1) == TOTAL);
|
||||
CHECK(!memcmp(back, src, TOTAL));
|
||||
rdeflate_free(z);
|
||||
}
|
||||
}
|
||||
|
||||
free(src);
|
||||
free(out);
|
||||
free(back);
|
||||
if (failures)
|
||||
{
|
||||
fprintf(stderr, "rdeflate_flush_test: %d failure(s)\n", failures);
|
||||
return 1;
|
||||
}
|
||||
printf("rdeflate_flush_test: ok\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
include ../../sample_objdir.mk
|
||||
|
||||
TARGET := shiftjis_test
|
||||
|
||||
LIBRETRO_COMM_DIR := ../../..
|
||||
|
||||
SOURCES := \
|
||||
shiftjis_test.c \
|
||||
$(LIBRETRO_COMM_DIR)/encodings/encoding_shiftjis.c
|
||||
|
||||
CFLAGS += -Wall -pedantic -std=gnu99 -g1 -O2 -I$(LIBRETRO_COMM_DIR)/include
|
||||
|
||||
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
|
||||
|
||||
ifneq ($(SANITIZER),)
|
||||
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
|
||||
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
|
||||
@mkdir -p $(@D)
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(CC) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(SAMPLE_OBJDIR)
|
||||
rm -f $(TARGET) $(OBJS)
|
||||
|
||||
.PHONY: clean
|
||||
@@ -0,0 +1,158 @@
|
||||
/* Shift-JIS (code page 932) and code page 1252 conversions: known
|
||||
* characters each way, every two-octet code through UTF-8 and back,
|
||||
* the octets skipped, and output that stops at a character it has no
|
||||
* room for. */
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <encodings/shiftjis.h>
|
||||
|
||||
static int failures = 0;
|
||||
|
||||
static void check(int ok, const char *what)
|
||||
{
|
||||
if (!ok)
|
||||
{
|
||||
fprintf(stderr, "FAIL: %s\n", what);
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
static void check_bytes(const char *got, size_t got_len, const char *want, size_t want_len,
|
||||
const char *what)
|
||||
{
|
||||
if (got_len != want_len || memcmp(got, want, want_len) || got[got_len] != '\0')
|
||||
{
|
||||
size_t i;
|
||||
fprintf(stderr, "FAIL: %s: got", what);
|
||||
for (i = 0; i < got_len; i++)
|
||||
fprintf(stderr, " %02X", (unsigned char)got[i]);
|
||||
fprintf(stderr, "\n");
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
#define CHECK_SJIS(sjis, utf8, what) \
|
||||
do { \
|
||||
char out[64]; \
|
||||
size_t n = sjis_to_utf8(out, sizeof(out), sjis, sizeof(sjis) - 1); \
|
||||
check_bytes(out, n, utf8, sizeof(utf8) - 1, what " (to UTF-8)"); \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_BOTH(sjis, utf8, what) \
|
||||
do { \
|
||||
char out[64]; \
|
||||
size_t n; \
|
||||
CHECK_SJIS(sjis, utf8, what); \
|
||||
n = utf8_to_sjis(out, sizeof(out), utf8, sizeof(utf8) - 1); \
|
||||
check_bytes(out, n, sjis, sizeof(sjis) - 1, what " (to Shift-JIS)"); \
|
||||
} while (0)
|
||||
|
||||
static void check_known(void)
|
||||
{
|
||||
CHECK_BOTH("abc\\~", "abc\\~", "ASCII, backslash and tilde as they are");
|
||||
CHECK_BOTH("\x82\xA0", "\xE3\x81\x82", "hiragana A");
|
||||
CHECK_BOTH("\x81\x40", "\xE3\x80\x80", "ideographic space");
|
||||
CHECK_BOTH("\x81\x5F", "\xEF\xBC\xBC", "full-width backslash");
|
||||
CHECK_BOTH("\x81\x60", "\xEF\xBD\x9E", "wave dash as code page 932 has it");
|
||||
CHECK_BOTH("\x88\x9F", "\xE4\xBA\x9C", "first level 1 kanji");
|
||||
CHECK_BOTH("\xEA\xA4", "\xE7\x86\x99", "last level 2 kanji");
|
||||
CHECK_BOTH("\x87\x40", "\xE2\x91\xA0", "NEC circled one");
|
||||
CHECK_SJIS("\xFA\x40", "\xE2\x85\xB0", "IBM small roman one");
|
||||
CHECK_BOTH("\xEE\xEF", "\xE2\x85\xB0", "NEC-selected IBM small roman one");
|
||||
CHECK_BOTH("\xB1", "\xEF\xBD\xB1", "half-width katakana A");
|
||||
CHECK_BOTH("\x93\xFA\x96\x7B", "\xE6\x97\xA5\xE6\x9C\xAC", "two kanji");
|
||||
/* the NEC duplicate of a kanji decodes to it and comes back as the
|
||||
* code page's own code for it */
|
||||
CHECK_SJIS("\xED\x40", "\xE7\xBA\x8A", "NEC-selected IBM kanji");
|
||||
}
|
||||
|
||||
static void check_skipped(void)
|
||||
{
|
||||
CHECK_SJIS("a\x80" "b\xA0" "c\xFD\xFE\xFF" "d", "abcd", "octets code page 932 sends to the PUA");
|
||||
CHECK_SJIS("a\x82", "a", "a lead octet at the end");
|
||||
CHECK_SJIS("\x82\x20" "b", " b", "a lead octet before a bad trail");
|
||||
CHECK_SJIS("\xF0\x40" "z", "@z", "the user-defined area");
|
||||
CHECK_SJIS("\x85\x40" "z", "@z", "an unassigned code");
|
||||
{
|
||||
char out[16];
|
||||
size_t n;
|
||||
/* an overlong slash, a surrogate, an emoji and a lone
|
||||
* continuation octet */
|
||||
static const char bad[] = "a\xC0\xAF" "b\xED\xA0\x80" "c\xF0\x9F\x98\x80" "d\x80" "e";
|
||||
n = utf8_to_sjis(out, sizeof(out), bad, sizeof(bad) - 1);
|
||||
check_bytes(out, n, "abcde", 5, "UTF-8 it cannot convert");
|
||||
}
|
||||
}
|
||||
|
||||
static void check_room(void)
|
||||
{
|
||||
char out[8];
|
||||
size_t n;
|
||||
/* three octets free: the second kanji does not fit */
|
||||
n = sjis_to_utf8(out, 5, "\x93\xFA\x96\x7B", 4);
|
||||
check_bytes(out, n, "\xE6\x97\xA5", 3, "stops before a character with no room");
|
||||
n = utf8_to_sjis(out, 4, "\xE6\x97\xA5\xE6\x9C\xAC", 6);
|
||||
check_bytes(out, n, "\x93\xFA", 2, "stops before a two-octet code with no room");
|
||||
n = cp1252_to_utf8(out, 3, "\x80", 1);
|
||||
check_bytes(out, n, "", 0, "stops before the euro sign with no room");
|
||||
check(sjis_to_utf8(out, 0, "a", 1) == 0, "no room at all");
|
||||
}
|
||||
|
||||
static void check_cp1252(void)
|
||||
{
|
||||
char out[64];
|
||||
size_t n = cp1252_to_utf8(out, sizeof(out), "\x80\x81\x8A\x9F\xE9\xFF" "z", 7);
|
||||
check_bytes(out, n, "\xE2\x82\xAC\xC5\xA0\xC5\xB8\xC3\xA9\xC3\xBF" "z", 12, "code page 1252");
|
||||
}
|
||||
|
||||
/* Every two-octet code that decodes comes back, through UTF-8, as a
|
||||
* code that decodes to the same character. */
|
||||
static void check_round_trip(void)
|
||||
{
|
||||
unsigned lead, trail, mapped = 0;
|
||||
for (lead = 0x81; lead <= 0xFC; lead++)
|
||||
{
|
||||
for (trail = 0x40; trail <= 0xFC; trail++)
|
||||
{
|
||||
char in[2], utf8[8], lone[8], back[4], again[8];
|
||||
size_t n, m, k;
|
||||
in[0] = (char)lead;
|
||||
in[1] = (char)trail;
|
||||
/* a lead octet alone is skipped; anything else is a character */
|
||||
if (sjis_to_utf8(lone, sizeof(lone), in, 1))
|
||||
break;
|
||||
n = sjis_to_utf8(utf8, sizeof(utf8), in, 2);
|
||||
/* nothing, or the lead skipped and the trail read alone */
|
||||
if (n == sjis_to_utf8(lone, sizeof(lone), in + 1, 1) && !memcmp(utf8, lone, n))
|
||||
continue;
|
||||
mapped++;
|
||||
m = utf8_to_sjis(back, sizeof(back), utf8, n);
|
||||
k = sjis_to_utf8(again, sizeof(again), back, m);
|
||||
if (m != 2 || k != n || memcmp(utf8, again, n))
|
||||
{
|
||||
fprintf(stderr, "FAIL: %02X %02X does not come back\n", lead, trail);
|
||||
failures++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
check(mapped == 7724, "the number of two-octet codes");
|
||||
printf("%u two-octet codes through UTF-8 and back\n", mapped);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
check_known();
|
||||
check_skipped();
|
||||
check_room();
|
||||
check_cp1252();
|
||||
check_round_trip();
|
||||
if (failures)
|
||||
{
|
||||
fprintf(stderr, "FAIL: %d checks\n", failures);
|
||||
return 1;
|
||||
}
|
||||
printf("ok\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -125,6 +125,14 @@ int main(void)
|
||||
/ (double)freq.QuadPart;
|
||||
if (ms > worst)
|
||||
worst = ms;
|
||||
/* The first read starts the read-ahead thread. Let it take up
|
||||
* that read's hint before reading on: a reader that gets to
|
||||
* the end of the file before the thread first runs (a slow
|
||||
* start under Wine) leaves it only the last hint to bring in,
|
||||
* and one hint shows nothing of the read-ahead keeping up. */
|
||||
if (off == 0)
|
||||
for (i = 0; i < 500 && hook_calls < 1; i++)
|
||||
Sleep(10);
|
||||
}
|
||||
/* the thread runs on its own: wait until it has gone a few of
|
||||
* its sleeps without starting another */
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Alex Barney
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,39 @@
|
||||
include ../../sample_objdir.mk
|
||||
|
||||
TARGETS := ratrac9_test ratrac9_vectors_test
|
||||
|
||||
LIBRETRO_COMM_DIR := ../../..
|
||||
|
||||
OBJS_ratrac9_test := $(call sample_objs,ratrac9_test.o)
|
||||
OBJS_ratrac9_vectors_test := $(call sample_objs,ratrac9_vectors_test.o \
|
||||
$(LIBRETRO_COMM_DIR)/formats/atrac9/ratrac9.o)
|
||||
|
||||
CFLAGS += -Wall -pedantic -std=gnu99 -g1 -O1 -I$(LIBRETRO_COMM_DIR)/include
|
||||
|
||||
ifneq ($(SANITIZER),)
|
||||
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
|
||||
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
LDLIBS += -lm
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
|
||||
@mkdir -p $(@D)
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
ratrac9_test: $(OBJS_ratrac9_test)
|
||||
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
|
||||
|
||||
ratrac9_vectors_test: $(OBJS_ratrac9_vectors_test)
|
||||
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -rf $(SAMPLE_OBJDIR)
|
||||
rm -f $(TARGETS) $(OBJS_ratrac9_test) $(OBJS_ratrac9_vectors_test)
|
||||
|
||||
.PHONY: all clean
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render the independent LibAtrac9 codebooks supplied as a local file.
|
||||
|
||||
Source: Thealexbarney/LibAtrac9, commit
|
||||
efca2e3af35562a09a9bb6deed90e45b4b824dc4,
|
||||
CSharp/LibAtrac9/HuffmanCodebooks.cs (MIT, Alex Barney 2018).
|
||||
No decoder-under-test tables are read.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
source = Path(sys.argv[1]).read_bytes()
|
||||
expected = '2bdbe2e2e1a7f1b28851187e382069866b0abfd042a853691c1591aba4dfde90'
|
||||
if hashlib.sha256(source).hexdigest() != expected:
|
||||
raise ValueError('unexpected reference source revision')
|
||||
arrays = {}
|
||||
for name, body in re.findall(
|
||||
r'(?:byte|short)\[\]\s+(\w+)\s*=\s*\{([^}]+)\}', source.decode()):
|
||||
arrays[name] = [int(n, 16 if n.startswith('0x') else 10)
|
||||
for n in re.findall(r'0x[0-9A-Fa-f]+|\d+', body)]
|
||||
rows = []
|
||||
|
||||
def emit(name, reference, power):
|
||||
if reference is None:
|
||||
rows.append('codebook ' + name + ' none')
|
||||
return
|
||||
bits, codes = arrays[reference + 'Bits'], arrays[reference + 'Codes']
|
||||
assert len(bits) == len(codes)
|
||||
values = 1 << power
|
||||
value_bits = (len(codes).bit_length() - 1) >> power
|
||||
rows.append('codebook %s symbols %d values_per_symbol %d value_bits %d '
|
||||
'max_code_bits %d' %
|
||||
(name, len(bits), values, value_bits, max(bits)))
|
||||
rows.extend(' symbol %d length %d code 0x%x' % (i, bit, code)
|
||||
for i, (bit, code) in enumerate(zip(bits, codes)))
|
||||
|
||||
for bits in range(1, 7):
|
||||
emit('sf_unsigned[%d]' % bits, 'ScaleFactorsA%d' % bits, 0)
|
||||
for bits in range(2, 6):
|
||||
emit('sf_signed[%d]' % bits, 'ScaleFactorsB%d' % bits, 0)
|
||||
for family, letter in enumerate('AB'):
|
||||
for precision in range(2, 8):
|
||||
for table in range(4):
|
||||
reference = 'Spectrum%s%d%d' % (letter, precision, table + 1)
|
||||
if family == 1 and table == 0:
|
||||
reference = None
|
||||
power = (1 if table == 0 else 2) if precision == 2 else (
|
||||
1 if precision in (3, 4) else 0)
|
||||
emit('spectrum[%d][%d][%d]' % (family, precision, table),
|
||||
reference, power)
|
||||
Path(__file__).with_name('codebooks.txt').write_text(
|
||||
'\n'.join(rows) + '\n', encoding='ascii', newline='\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,183 @@
|
||||
/* ratrac9 against a reference decoder's output: generated streams covering every
|
||||
* channel layout, low and high sample rates and every superframe size, decoded to
|
||||
* 16-bit, 32-bit, float and planar 16-bit and compared by CRC-32 (of the samples as
|
||||
* little-endian bytes). Also: a reset and a savestate continue exactly. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <formats/ratrac9.h>
|
||||
|
||||
struct at9_vector
|
||||
{
|
||||
const char *name;
|
||||
const uint8_t *data;
|
||||
size_t size;
|
||||
unsigned frames;
|
||||
unsigned used;
|
||||
uint32_t crc_s16, crc_s32, crc_f32, crc_planar;
|
||||
};
|
||||
|
||||
#include "ratrac9_vectors.h"
|
||||
|
||||
#define MAX_PCM (8 * 256)
|
||||
|
||||
static int fails;
|
||||
|
||||
static void check(int ok, const char *name, const char *what)
|
||||
{
|
||||
if (!ok)
|
||||
{
|
||||
printf("FAIL: %s: %s\n", name, what);
|
||||
fails++;
|
||||
}
|
||||
}
|
||||
|
||||
/* CRC-32 (zlib's), bit by bit */
|
||||
static uint32_t crc32_bytes(uint32_t crc, const uint8_t *p, size_t len)
|
||||
{
|
||||
crc = ~crc;
|
||||
while (len--)
|
||||
{
|
||||
int k;
|
||||
crc ^= *p++;
|
||||
for (k = 0; k < 8; k++)
|
||||
crc = (crc >> 1) ^ (0xEDB88320u & (0u - (crc & 1)));
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
static uint32_t crc_le(uint32_t crc, const void *pcm, unsigned count,
|
||||
unsigned width)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t*)pcm;
|
||||
uint8_t le[4];
|
||||
unsigned i, k;
|
||||
for (i = 0; i < count; i++, p += width)
|
||||
{
|
||||
uint32_t v = 0;
|
||||
/* the value in host order, written out little-endian */
|
||||
if (width == 2)
|
||||
{
|
||||
uint16_t s;
|
||||
memcpy(&s, p, 2);
|
||||
v = s;
|
||||
}
|
||||
else
|
||||
memcpy(&v, p, 4);
|
||||
for (k = 0; k < width; k++)
|
||||
le[k] = (uint8_t)(v >> (8 * k));
|
||||
crc = crc32_bytes(crc, le, width);
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
/* Decodes frames [from, to) of v with d in the given output form. */
|
||||
static uint32_t run(ratrac9_t *d, const struct at9_vector *v, int form,
|
||||
unsigned from, unsigned to, uint32_t crc, unsigned *used_sum)
|
||||
{
|
||||
static int16_t s16[MAX_PCM];
|
||||
static int32_t s32[MAX_PCM];
|
||||
static float f32[MAX_PCM];
|
||||
unsigned fb = ratrac9_frame_bytes(d);
|
||||
unsigned cnt = ratrac9_channels(d) * ratrac9_frame_samples(d);
|
||||
unsigned f;
|
||||
for (f = from; f < to; f++)
|
||||
{
|
||||
const uint8_t *in = v->data + 4 + (size_t)f * fb;
|
||||
size_t used = 0;
|
||||
int ret;
|
||||
switch (form)
|
||||
{
|
||||
case 1:
|
||||
ret = ratrac9_decode_s32(d, in, fb, s32, false, &used);
|
||||
crc = crc_le(crc, s32, cnt, 4);
|
||||
break;
|
||||
case 2:
|
||||
ret = ratrac9_decode_f32(d, in, fb, f32, false, &used);
|
||||
crc = crc_le(crc, f32, cnt, 4);
|
||||
break;
|
||||
case 3:
|
||||
ret = ratrac9_decode_s16(d, in, fb, s16, true, &used);
|
||||
crc = crc_le(crc, s16, cnt, 2);
|
||||
break;
|
||||
default:
|
||||
ret = ratrac9_decode_s16(d, in, fb, s16, false, &used);
|
||||
crc = crc_le(crc, s16, cnt, 2);
|
||||
break;
|
||||
}
|
||||
if (ret != 0)
|
||||
return 0;
|
||||
if (used_sum)
|
||||
*used_sum += (unsigned)used;
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < sizeof(at9_vectors) / sizeof(at9_vectors[0]); i++)
|
||||
{
|
||||
const struct at9_vector *v = &at9_vectors[i];
|
||||
uint32_t want[4];
|
||||
unsigned half = v->frames / 2;
|
||||
unsigned used = 0;
|
||||
int form;
|
||||
ratrac9_t *d = ratrac9_open(v->data);
|
||||
ratrac9_t *e;
|
||||
|
||||
want[0] = v->crc_s16;
|
||||
want[1] = v->crc_s32;
|
||||
want[2] = v->crc_f32;
|
||||
want[3] = v->crc_planar;
|
||||
check(d != NULL, v->name, "open");
|
||||
if (!d)
|
||||
continue;
|
||||
check(4 + (size_t)ratrac9_frame_bytes(d) * v->frames == v->size,
|
||||
v->name, "frame size");
|
||||
|
||||
for (form = 0; form < 4; form++)
|
||||
{
|
||||
ratrac9_reset(d);
|
||||
check(run(d, v, form, 0, v->frames, 0, form ? NULL : &used)
|
||||
== want[form], v->name,
|
||||
form == 0 ? "16-bit" : form == 1 ? "32-bit"
|
||||
: form == 2 ? "float" : "planar");
|
||||
}
|
||||
check(used == v->used, v->name, "bytes used");
|
||||
|
||||
/* a reset mid-stream starts over */
|
||||
ratrac9_reset(d);
|
||||
run(d, v, 0, 0, half, 0, NULL);
|
||||
ratrac9_reset(d);
|
||||
check(run(d, v, 0, 0, v->frames, 0, NULL) == want[0], v->name,
|
||||
"after a reset");
|
||||
|
||||
/* a savestate mid-stream, loaded into another decoder, goes on */
|
||||
e = ratrac9_open(v->data);
|
||||
if (e)
|
||||
{
|
||||
size_t size = ratrac9_state_size(d);
|
||||
static uint8_t state[65536];
|
||||
uint32_t crc;
|
||||
ratrac9_reset(d);
|
||||
crc = run(d, v, 0, 0, half, 0, NULL);
|
||||
check(size <= sizeof(state), v->name, "state size");
|
||||
if (size <= sizeof(state))
|
||||
{
|
||||
ratrac9_save_state(d, state);
|
||||
check(ratrac9_load_state(e, state, size), v->name, "state load");
|
||||
check(!ratrac9_load_state(e, state, size - 1), v->name,
|
||||
"state of the wrong size");
|
||||
check(run(e, v, 0, half, v->frames, crc, NULL) == want[0],
|
||||
v->name, "continued from a savestate");
|
||||
}
|
||||
ratrac9_close(e);
|
||||
}
|
||||
ratrac9_close(d);
|
||||
}
|
||||
printf("ratrac9_vectors_test: %s (%d failures)\n", fails ? "FAIL" : "PASS",
|
||||
fails);
|
||||
return fails ? 1 : 0;
|
||||
}
|
||||
@@ -16,12 +16,126 @@
|
||||
* own use of them -- the same static functions -- which the linkage
|
||||
* itself guarantees. BC7 is therefore checked only to run and to write
|
||||
* every pixel (a sentinel pattern is overwritten).
|
||||
* BGRA: the _bgra block decoders give the RGBA ones' bytes with red and
|
||||
* blue swapped, over many random blocks.
|
||||
* Whole images: random BC1/BC2/BC3 DDS files, with edges (sizes not a
|
||||
* multiple of 4), decoded by rdds_process_image for an RGBA and for a
|
||||
* BGRA driver, each pixel checked against the block decoders. The
|
||||
* loader's fast paths have their own fused decoders; they must agree
|
||||
* with these texel for texel (a BC3 alpha ramp once truncated on the
|
||||
* RGBA path and rounded on the BGRA one).
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <formats/rdds.h>
|
||||
#include <formats/image.h>
|
||||
|
||||
typedef void (*block_fn)(const void *block, void *out, int pitch);
|
||||
|
||||
static uint32_t rnd_state = 0x12345678u;
|
||||
static uint8_t rnd8(void)
|
||||
{
|
||||
rnd_state = rnd_state * 1664525u + 1013904223u;
|
||||
return (uint8_t)(rnd_state >> 24);
|
||||
}
|
||||
|
||||
static void put32(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);
|
||||
}
|
||||
|
||||
/* A DDS file of random blocks: a 128-byte header, then the blocks */
|
||||
static uint8_t *make_dds(unsigned w, unsigned h, const char *fourcc,
|
||||
unsigned block_size, size_t *len)
|
||||
{
|
||||
size_t n = (size_t)((w + 3) / 4) * ((h + 3) / 4) * block_size, i;
|
||||
uint8_t *d = (uint8_t*)calloc(1, 128 + n);
|
||||
memcpy(d, "DDS ", 4);
|
||||
put32(d + 4, 124);
|
||||
put32(d + 8, 0x1007 | 0x80000); /* caps, height, width, pixelformat, linear size */
|
||||
put32(d + 12, h);
|
||||
put32(d + 16, w);
|
||||
put32(d + 20, (uint32_t)n);
|
||||
put32(d + 28, 1);
|
||||
put32(d + 76, 32);
|
||||
put32(d + 80, 4); /* DDPF_FOURCC */
|
||||
memcpy(d + 84, fourcc, 4);
|
||||
put32(d + 108, 0x1000); /* DDSCAPS_TEXTURE */
|
||||
for (i = 0; i < n; i++)
|
||||
d[128 + i] = rnd8();
|
||||
*len = 128 + n;
|
||||
return d;
|
||||
}
|
||||
|
||||
/* rdds_process_image to the end; NULL on failure */
|
||||
static uint32_t *decode_image(uint8_t *dds, size_t len, bool rgba,
|
||||
unsigned *w, unsigned *h)
|
||||
{
|
||||
rdds_t *r = rdds_alloc();
|
||||
void *buf = NULL;
|
||||
int ret;
|
||||
rdds_set_buf_ptr(r, dds);
|
||||
do
|
||||
ret = rdds_process_image(r, &buf, len, w, h, rgba);
|
||||
while (ret == IMAGE_PROCESS_NEXT);
|
||||
rdds_free(r);
|
||||
if (ret != IMAGE_PROCESS_END)
|
||||
{
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
return (uint32_t*)buf;
|
||||
}
|
||||
|
||||
/* Every pixel of the image, decoded for an RGBA or a BGRA driver, is the
|
||||
* block decoder's texel: A<<24|B<<16|G<<8|R, or A<<24|R<<16|G<<8|B */
|
||||
static int check_image(const char *name, const char *fourcc,
|
||||
unsigned block_size, block_fn dec, unsigned w, unsigned h)
|
||||
{
|
||||
size_t len;
|
||||
uint8_t *dds = make_dds(w, h, fourcc, block_size, &len);
|
||||
unsigned bx, by, bw = (w + 3) / 4, bh = (h + 3) / 4, iw, ih;
|
||||
uint32_t *img[2];
|
||||
int mode, bad = 0;
|
||||
|
||||
img[0] = decode_image(dds, len, true, &iw, &ih);
|
||||
img[1] = decode_image(dds, len, false, &iw, &ih);
|
||||
if (!img[0] || !img[1] || iw != w || ih != h)
|
||||
bad = -1;
|
||||
for (by = 0; bad >= 0 && by < bh; by++)
|
||||
for (bx = 0; bx < bw; bx++)
|
||||
{
|
||||
uint8_t t[64];
|
||||
unsigned x, y;
|
||||
dec(dds + 128 + ((size_t)by * bw + bx) * block_size, t, 16);
|
||||
for (y = 0; y < 4; y++)
|
||||
for (x = 0; x < 4; x++)
|
||||
{
|
||||
unsigned px = bx * 4 + x, py = by * 4 + y;
|
||||
const uint8_t *p = t + (y * 4 + x) * 4;
|
||||
uint32_t want[2];
|
||||
if (px >= w || py >= h)
|
||||
continue;
|
||||
want[0] = ((uint32_t)p[3] << 24) | ((uint32_t)p[2] << 16)
|
||||
| ((uint32_t)p[1] << 8) | p[0];
|
||||
want[1] = ((uint32_t)p[3] << 24) | ((uint32_t)p[0] << 16)
|
||||
| ((uint32_t)p[1] << 8) | p[2];
|
||||
for (mode = 0; mode < 2; mode++)
|
||||
if (img[mode][(size_t)py * w + px] != want[mode])
|
||||
bad++;
|
||||
}
|
||||
}
|
||||
printf(" %s: %s %ux%u image, RGBA and BGRA, as the block decoder (%d wrong)\n",
|
||||
bad == 0 ? "ok" : "FAIL", name, w, h, bad);
|
||||
free(img[0]);
|
||||
free(img[1]);
|
||||
free(dds);
|
||||
return bad == 0;
|
||||
}
|
||||
|
||||
static int px(const uint8_t *out, int i, int r, int g, int b, int a)
|
||||
{
|
||||
@@ -104,6 +218,34 @@ int main(void)
|
||||
good = wide[0] == 255 && wide[16] == 0x11 && wide[32] == 255 && wide[3 * 32] == 255;
|
||||
printf(" %s: pitch 32 leaves the gap between rows untouched\n", good ? "ok" : "FAIL"); ok &= good;
|
||||
}
|
||||
/* BGRA block decoders: the RGBA bytes, red and blue swapped */
|
||||
{
|
||||
static const block_fn rgba_fn[3] = { rdds_decode_block_bc1, rdds_decode_block_bc2, rdds_decode_block_bc3 };
|
||||
static const block_fn bgra_fn[3] = { rdds_decode_block_bc1_bgra, rdds_decode_block_bc2_bgra, rdds_decode_block_bc3_bgra };
|
||||
int f, n, i, bad = 0;
|
||||
for (f = 0; f < 3; f++)
|
||||
for (n = 0; n < 20000; n++)
|
||||
{
|
||||
uint8_t blk[16], a[64], b[64];
|
||||
for (i = 0; i < 16; i++)
|
||||
blk[i] = rnd8();
|
||||
rgba_fn[f](blk, a, 16);
|
||||
bgra_fn[f](blk, b, 16);
|
||||
for (i = 0; i < 16; i++)
|
||||
bad += a[i * 4] != b[i * 4 + 2] || a[i * 4 + 1] != b[i * 4 + 1]
|
||||
|| a[i * 4 + 2] != b[i * 4] || a[i * 4 + 3] != b[i * 4 + 3];
|
||||
}
|
||||
printf(" %s: BC1/BC2/BC3 BGRA = RGBA with red and blue swapped, 60000 random blocks (%d wrong)\n",
|
||||
bad ? "FAIL" : "ok", bad);
|
||||
ok &= !bad;
|
||||
}
|
||||
/* Whole images, edges included */
|
||||
ok &= check_image("BC1", "DXT1", 8, rdds_decode_block_bc1, 64, 64);
|
||||
ok &= check_image("BC1", "DXT1", 8, rdds_decode_block_bc1, 37, 29);
|
||||
ok &= check_image("BC2", "DXT3", 16, rdds_decode_block_bc2, 64, 64);
|
||||
ok &= check_image("BC2", "DXT3", 16, rdds_decode_block_bc2, 37, 29);
|
||||
ok &= check_image("BC3", "DXT5", 16, rdds_decode_block_bc3, 64, 64);
|
||||
ok &= check_image("BC3", "DXT5", 16, rdds_decode_block_bc3, 37, 29);
|
||||
printf(ok ? "rdds blocks: ok\n" : "rdds blocks: FAILED\n");
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -56,10 +56,12 @@ static unsigned be(const void *data, unsigned count)
|
||||
|
||||
static void headers(rflv_mux_t *m, unsigned flags)
|
||||
{
|
||||
rflv_tag_t tag;
|
||||
const rflv_span_t *s;
|
||||
const unsigned char *p;
|
||||
size_t n;
|
||||
assert(rflv_mux_get(m, &s, &n) && n == 1);
|
||||
assert(!rflv_mux_get_tag(m, &tag));
|
||||
p = (const unsigned char*)s[0].data;
|
||||
assert(s[0].size == 13 && !memcmp(p, "FLV\1", 4) && p[4] == flags);
|
||||
assert(be(p + 5, 4) == 9 && be(p + 9, 4) == 0);
|
||||
@@ -71,6 +73,9 @@ static void headers(rflv_mux_t *m, unsigned flags)
|
||||
assert(rflv_mux_get(m, &s, &n) && n == 3);
|
||||
p = (const unsigned char*)s[0].data;
|
||||
assert(p[0] == 9 && p[11] == 23 && p[12] == 0);
|
||||
assert(rflv_mux_get_tag(m, &tag) && tag.type == 9 && !tag.timestamp);
|
||||
assert(tag.spans[0].data == p + 11 && tag.spans[0].size == 5);
|
||||
assert(tag.spans[1].data == s[1].data && tag.spans[1].size == sizeof(avcc));
|
||||
assert(be(p + 1, 3) == sizeof(avcc) + 5);
|
||||
assert(s[1].size == sizeof(avcc) && !memcmp(s[1].data, avcc, sizeof(avcc)));
|
||||
assert(be(s[2].data, 4) == sizeof(avcc) + 16);
|
||||
@@ -81,14 +86,19 @@ static void headers(rflv_mux_t *m, unsigned flags)
|
||||
assert(rflv_mux_get(m, &s, &n) && n == 3);
|
||||
p = (const unsigned char*)s[0].data;
|
||||
assert(p[0] == 8 && p[11] == 175 && p[12] == 0);
|
||||
assert(rflv_mux_get_tag(m, &tag) && tag.type == 8 && !tag.timestamp);
|
||||
assert(tag.spans[0].data == p + 11 && tag.spans[0].size == 2);
|
||||
assert(tag.spans[1].data == s[1].data && tag.spans[1].size == sizeof(asc));
|
||||
assert(s[1].size == sizeof(asc) && !memcmp(s[1].data, asc, sizeof(asc)));
|
||||
assert(rflv_mux_release(m));
|
||||
}
|
||||
assert(rflv_mux_step(m) == RFLV_BUSY);
|
||||
assert(!rflv_mux_get_tag(m, &tag));
|
||||
}
|
||||
|
||||
static void lifetime(void)
|
||||
{
|
||||
rflv_tag_t tag;
|
||||
rflv_config_t c = config();
|
||||
unsigned char owned[sizeof(avcc)];
|
||||
rflv_mux_t *m;
|
||||
@@ -99,6 +109,7 @@ static void lifetime(void)
|
||||
c.avcc = owned;
|
||||
m = rflv_mux_new(&c);
|
||||
assert(m);
|
||||
assert(!rflv_mux_get_tag(NULL, &tag) && !rflv_mux_get_tag(m, NULL));
|
||||
memset(owned, 0, sizeof(owned));
|
||||
headers(m, 5);
|
||||
assert(rflv_mux_push(m, 1, predicted, sizeof(predicted), 0, 0, false) == RFLV_ERROR);
|
||||
@@ -110,6 +121,9 @@ static void lifetime(void)
|
||||
assert(rflv_mux_get(m, &s, &n) && n == 3 && s[1].data == idr);
|
||||
p = (const unsigned char*)s[0].data;
|
||||
assert(be(p + 4, 3) == 1000 && be(p + 13, 3) == 0xffff9c);
|
||||
assert(rflv_mux_get_tag(m, &tag) && tag.timestamp == 1000 && tag.type == 9);
|
||||
assert(tag.spans[0].data == p + 11 && tag.spans[0].size == 5);
|
||||
assert(tag.spans[1].data == idr && tag.spans[1].size == sizeof(idr));
|
||||
assert(rflv_mux_step(m) == RFLV_PACKET);
|
||||
assert(rflv_mux_push(m, 2, asc, sizeof(asc), 0, 0, false) == RFLV_BUSY);
|
||||
assert(be(p + 13, 3) == 0xffff9c);
|
||||
@@ -119,6 +133,8 @@ static void lifetime(void)
|
||||
assert(rflv_mux_get(m, &s, &n) && s[1].data == asc);
|
||||
p = (const unsigned char*)s[0].data;
|
||||
assert(be(p + 4, 3) == 1000 && p[12] == 1);
|
||||
assert(rflv_mux_get_tag(m, &tag) && tag.timestamp == 1000 && tag.type == 8);
|
||||
assert(tag.spans[1].data == asc && tag.spans[1].size == sizeof(asc));
|
||||
assert(rflv_mux_release(m));
|
||||
assert(rflv_mux_finish(m) && rflv_mux_finish(m));
|
||||
assert(rflv_mux_step(m) == RFLV_END);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
include ../../sample_objdir.mk
|
||||
|
||||
TARGET := rgif_test
|
||||
|
||||
LIBRETRO_COMM_DIR := ../../..
|
||||
|
||||
SOURCES := \
|
||||
rgif_test.c \
|
||||
$(LIBRETRO_COMM_DIR)/formats/gif/rgif.c
|
||||
|
||||
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
|
||||
|
||||
CFLAGS += -Wall -pedantic -std=gnu99 -g1 -I$(LIBRETRO_COMM_DIR)/include
|
||||
|
||||
# The samples workflow passes SANITIZER=address,undefined to every
|
||||
# sample dir. Without this block the variable was accepted and
|
||||
# ignored, so these targets built unsanitized while the workflow
|
||||
# reported them as checked.
|
||||
ifneq ($(SANITIZER),)
|
||||
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
|
||||
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
|
||||
@mkdir -p $(@D)
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(CC) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(SAMPLE_OBJDIR)
|
||||
rm -f $(TARGET) $(OBJS)
|
||||
|
||||
.PHONY: clean
|
||||
@@ -0,0 +1,525 @@
|
||||
/* rgif: GIFs written here - every colour table size, plain and
|
||||
* interlaced, a global or the image's own table, transparency, an image
|
||||
* that does not fill the screen with and without a background colour,
|
||||
* LZW tables that fill and are cleared or left full - decoded into rows
|
||||
* of any pitch in each byte order and checked pixel for pixel; files cut
|
||||
* short decoded as far as they go; broken files refused. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <formats/image.h>
|
||||
#include <formats/rgif.h>
|
||||
|
||||
static int fails;
|
||||
|
||||
#define CHECK(c, what) do { if (!(c)) { printf("FAIL: %s (%s:%d)\n", what, __FILE__, __LINE__); fails++; } } while (0)
|
||||
|
||||
/* ---- a small GIF writer ---- */
|
||||
|
||||
struct out
|
||||
{
|
||||
uint8_t *p;
|
||||
size_t n, cap;
|
||||
};
|
||||
|
||||
static void put(struct out *o, const void *d, size_t n)
|
||||
{
|
||||
if (o->n + n > o->cap)
|
||||
{
|
||||
o->cap = (o->n + n) * 2 + 64;
|
||||
o->p = (uint8_t*)realloc(o->p, o->cap);
|
||||
}
|
||||
memcpy(o->p + o->n, d, n);
|
||||
o->n += n;
|
||||
}
|
||||
|
||||
static void put8(struct out *o, unsigned v)
|
||||
{
|
||||
uint8_t b = (uint8_t)v;
|
||||
put(o, &b, 1);
|
||||
}
|
||||
|
||||
static void put16(struct out *o, unsigned v)
|
||||
{
|
||||
put8(o, v & 0xff);
|
||||
put8(o, v >> 8);
|
||||
}
|
||||
|
||||
/* LZW, packed into sub-blocks; the code size steps as a decoder's does */
|
||||
struct lzw
|
||||
{
|
||||
struct out *o;
|
||||
uint8_t block[255];
|
||||
unsigned block_n;
|
||||
uint32_t bits;
|
||||
unsigned nbits;
|
||||
unsigned min, cs, next, avail;
|
||||
int first;
|
||||
int16_t *dict; /* [4096][256]: the code for a string and an index */
|
||||
};
|
||||
|
||||
static void lzw_byte(struct lzw *z, unsigned b)
|
||||
{
|
||||
z->block[z->block_n++] = (uint8_t)b;
|
||||
if (z->block_n == 255)
|
||||
{
|
||||
put8(z->o, 255);
|
||||
put(z->o, z->block, 255);
|
||||
z->block_n = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void lzw_bits(struct lzw *z, unsigned code)
|
||||
{
|
||||
z->bits |= (uint32_t)code << z->nbits;
|
||||
z->nbits += z->cs;
|
||||
while (z->nbits >= 8)
|
||||
{
|
||||
lzw_byte(z, z->bits & 0xff);
|
||||
z->bits >>= 8;
|
||||
z->nbits -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
static void lzw_reset(struct lzw *z)
|
||||
{
|
||||
z->cs = z->min + 1;
|
||||
z->next = (1u << z->min) + 2;
|
||||
z->avail = z->next;
|
||||
z->first = 1;
|
||||
memset(z->dict, 0xff, sizeof(int16_t) * 4096 * 256);
|
||||
}
|
||||
|
||||
static void lzw_clear(struct lzw *z)
|
||||
{
|
||||
lzw_bits(z, 1u << z->min);
|
||||
lzw_reset(z);
|
||||
}
|
||||
|
||||
static void lzw_code(struct lzw *z, unsigned code)
|
||||
{
|
||||
unsigned mask;
|
||||
lzw_bits(z, code);
|
||||
if (!z->first)
|
||||
z->avail++;
|
||||
z->first = 0;
|
||||
mask = (1u << z->cs) - 1;
|
||||
if ((z->avail & mask) == 0 && z->avail <= 0x0fff)
|
||||
z->cs++;
|
||||
}
|
||||
|
||||
/* @full: 1 clears a full table, 0 goes on with it full */
|
||||
static void lzw_encode(struct out *o, const uint8_t *idx, size_t n,
|
||||
unsigned min, int full)
|
||||
{
|
||||
struct lzw z;
|
||||
size_t i;
|
||||
unsigned w;
|
||||
|
||||
memset(&z, 0, sizeof(z));
|
||||
z.o = o;
|
||||
z.min = min;
|
||||
z.dict = (int16_t*)malloc(sizeof(int16_t) * 4096 * 256);
|
||||
put8(o, min);
|
||||
lzw_reset(&z);
|
||||
lzw_clear(&z);
|
||||
w = idx[0];
|
||||
for (i = 1; i < n; i++)
|
||||
{
|
||||
unsigned k = idx[i];
|
||||
int16_t c = z.dict[w * 256 + k];
|
||||
if (c >= 0)
|
||||
{
|
||||
w = (unsigned)c;
|
||||
continue;
|
||||
}
|
||||
lzw_code(&z, w);
|
||||
if (z.next < 4096)
|
||||
{
|
||||
z.dict[w * 256 + k] = (int16_t)z.next++;
|
||||
if (z.next == 4096 && full)
|
||||
lzw_clear(&z);
|
||||
}
|
||||
w = k;
|
||||
}
|
||||
lzw_code(&z, w);
|
||||
lzw_bits(&z, (1u << min) + 1);
|
||||
if (z.nbits)
|
||||
lzw_byte(&z, z.bits & 0xff);
|
||||
if (z.block_n)
|
||||
{
|
||||
put8(o, z.block_n);
|
||||
put(o, z.block, z.block_n);
|
||||
}
|
||||
put8(o, 0);
|
||||
free(z.dict);
|
||||
}
|
||||
|
||||
struct gif_desc
|
||||
{
|
||||
unsigned w, h; /* the screen */
|
||||
unsigned x, y, iw, ih; /* the image */
|
||||
unsigned bits; /* colour table: 2 << (bits - 1) entries */
|
||||
int local; /* the image's own table, no global one */
|
||||
int bgindex;
|
||||
int transparent; /* -1: none */
|
||||
int interlace;
|
||||
int full_clear;
|
||||
const uint8_t *idx; /* iw x ih indices */
|
||||
};
|
||||
|
||||
static void table(uint8_t *pal, unsigned n, unsigned seed)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < n * 3; i++)
|
||||
pal[i] = (uint8_t)(i * 37 + seed * 11 + 5);
|
||||
}
|
||||
|
||||
static void write_gif(struct out *o, const struct gif_desc *d)
|
||||
{
|
||||
unsigned n = 2u << (d->bits - 1);
|
||||
uint8_t pal[768];
|
||||
uint8_t *order = (uint8_t*)malloc((size_t)d->iw * d->ih + 1);
|
||||
size_t k = 0;
|
||||
|
||||
put(o, "GIF89a", 6);
|
||||
put16(o, d->w);
|
||||
put16(o, d->h);
|
||||
put8(o, d->local ? 0 : 0x80 | (d->bits - 1));
|
||||
put8(o, d->bgindex);
|
||||
put8(o, 0);
|
||||
if (!d->local)
|
||||
{
|
||||
table(pal, n, 1);
|
||||
put(o, pal, n * 3);
|
||||
}
|
||||
put(o, "\x21\xfe\x03" "abc" "\0", 7); /* a comment */
|
||||
if (d->transparent >= 0)
|
||||
{
|
||||
put(o, "\x21\xf9\x04\x01\x00\x00", 6);
|
||||
put8(o, d->transparent);
|
||||
put8(o, 0);
|
||||
}
|
||||
put8(o, 0x2c);
|
||||
put16(o, d->x);
|
||||
put16(o, d->y);
|
||||
put16(o, d->iw);
|
||||
put16(o, d->ih);
|
||||
put8(o, (d->local ? 0x80 | (d->bits - 1) : 0) | (d->interlace ? 0x40 : 0));
|
||||
if (d->local)
|
||||
{
|
||||
table(pal, n, 2);
|
||||
put(o, pal, n * 3);
|
||||
}
|
||||
/* the rows in the order they are stored */
|
||||
if (d->interlace)
|
||||
{
|
||||
static const unsigned start[4] = { 0, 4, 2, 1 }, step[4] = { 8, 8, 4, 2 };
|
||||
unsigned p, y;
|
||||
for (p = 0; p < 4; p++)
|
||||
for (y = start[p]; y < d->ih; y += step[p])
|
||||
{
|
||||
memcpy(order + k, d->idx + (size_t)y * d->iw, d->iw);
|
||||
k += d->iw;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(order, d->idx, (size_t)d->iw * d->ih);
|
||||
k = (size_t)d->iw * d->ih;
|
||||
}
|
||||
lzw_encode(o, order, k ? k : 1, d->bits < 2 ? 2 : d->bits, d->full_clear);
|
||||
put8(o, 0x3b);
|
||||
free(order);
|
||||
}
|
||||
|
||||
/* what the screen should hold, RGBA */
|
||||
static void expect(const struct gif_desc *d, uint8_t *e)
|
||||
{
|
||||
unsigned n = 2u << (d->bits - 1);
|
||||
uint8_t gpal[768], lpal[768];
|
||||
const uint8_t *pal;
|
||||
unsigned x, y;
|
||||
|
||||
memset(gpal, 0, sizeof(gpal));
|
||||
if (!d->local)
|
||||
table(gpal, n, 1);
|
||||
table(lpal, n, 2);
|
||||
pal = d->local ? lpal : gpal;
|
||||
for (y = 0; y < d->h; y++)
|
||||
for (x = 0; x < d->w; x++)
|
||||
{
|
||||
uint8_t *p = e + ((size_t)y * d->w + x) * 4;
|
||||
if (d->bgindex > 0)
|
||||
{
|
||||
p[0] = gpal[d->bgindex * 3];
|
||||
p[1] = gpal[d->bgindex * 3 + 1];
|
||||
p[2] = gpal[d->bgindex * 3 + 2];
|
||||
p[3] = 255;
|
||||
}
|
||||
else
|
||||
memset(p, 0, 4);
|
||||
}
|
||||
for (y = 0; y < d->ih; y++)
|
||||
for (x = 0; x < d->iw; x++)
|
||||
{
|
||||
unsigned i = d->idx[(size_t)y * d->iw + x];
|
||||
uint8_t *p = e + ((size_t)(d->y + y) * d->w + d->x + x) * 4;
|
||||
if ((int)i == d->transparent || i >= n)
|
||||
memset(p, 0, 4);
|
||||
else
|
||||
{
|
||||
p[0] = pal[i * 3];
|
||||
p[1] = pal[i * 3 + 1];
|
||||
p[2] = pal[i * 3 + 2];
|
||||
p[3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void check_gif(rgif_t *g, const struct gif_desc *d, const char *what)
|
||||
{
|
||||
static const unsigned orders[3] = { RGIF_RGBA, RGIF_ARGB, RGIF_BGRA };
|
||||
struct out o;
|
||||
size_t stride = (size_t)d->w * 4 + 12;
|
||||
uint8_t *e = (uint8_t*)malloc((size_t)d->w * d->h * 4 + 4);
|
||||
uint8_t *got = (uint8_t*)malloc(stride * d->h + 4);
|
||||
unsigned w, h, k;
|
||||
|
||||
memset(&o, 0, sizeof(o));
|
||||
write_gif(&o, d);
|
||||
expect(d, e);
|
||||
CHECK(rgif_size(o.p, o.n, &w, &h) && w == d->w && h == d->h, what);
|
||||
for (k = 0; k < 3; k++)
|
||||
{
|
||||
unsigned y, x;
|
||||
int ok = 1;
|
||||
memset(got, 0xa5, stride * d->h + 4);
|
||||
CHECK(rgif_decode(g, o.p, o.n, got, stride, orders[k]), what);
|
||||
for (y = 0; y < d->h; y++)
|
||||
{
|
||||
const uint8_t *row = got + y * stride;
|
||||
for (x = 0; x < d->w; x++)
|
||||
{
|
||||
const uint8_t *s = e + ((size_t)y * d->w + x) * 4;
|
||||
const uint8_t *p = row + x * 4;
|
||||
uint8_t want[4];
|
||||
if (orders[k] == RGIF_ARGB)
|
||||
{
|
||||
want[0] = s[3]; want[1] = s[0]; want[2] = s[1]; want[3] = s[2];
|
||||
}
|
||||
else if (orders[k] == RGIF_BGRA)
|
||||
{
|
||||
want[0] = s[2]; want[1] = s[1]; want[2] = s[0]; want[3] = s[3];
|
||||
}
|
||||
else
|
||||
memcpy(want, s, 4);
|
||||
if (memcmp(p, want, 4))
|
||||
ok = 0;
|
||||
}
|
||||
/* the pitch's padding is the caller's */
|
||||
for (x = d->w * 4; x < stride; x++)
|
||||
if (row[x] != 0xa5)
|
||||
ok = 0;
|
||||
}
|
||||
CHECK(ok, what);
|
||||
}
|
||||
|
||||
/* cut short: what came before the cut, without reading past it */
|
||||
if (o.n > 40)
|
||||
{
|
||||
uint8_t *cut = (uint8_t*)malloc(o.n / 2);
|
||||
memcpy(cut, o.p, o.n / 2);
|
||||
rgif_decode(g, cut, o.n / 2, got, stride, RGIF_RGBA);
|
||||
free(cut);
|
||||
}
|
||||
|
||||
free(o.p);
|
||||
free(e);
|
||||
free(got);
|
||||
}
|
||||
|
||||
static uint8_t *noise(unsigned n, unsigned colours, unsigned seed, int smooth)
|
||||
{
|
||||
uint8_t *p = (uint8_t*)malloc(n + 1);
|
||||
uint32_t x = seed * 2654435761u + 1;
|
||||
unsigned i;
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
x = x * 1103515245u + 12345u;
|
||||
/* runs, so the table grows with long strings too */
|
||||
p[i] = (uint8_t)(smooth && i && (x >> 28) < 12 ? p[i - 1]
|
||||
: (x >> 16) % colours);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static void broken(rgif_t *g)
|
||||
{
|
||||
static const uint8_t pal[6] = { 0, 0, 0, 255, 255, 255 };
|
||||
static const uint8_t lzw[] = { 2, 2, 0x44, 0x0a, 0 };
|
||||
uint8_t px[64];
|
||||
struct out o;
|
||||
unsigned w, h;
|
||||
|
||||
/* a 2x1 screen, one image: index 0 then 1 */
|
||||
memset(&o, 0, sizeof(o));
|
||||
put(&o, "GIF87a\x02\x00\x01\x00\x80\x00\x00", 13);
|
||||
put(&o, pal, 6);
|
||||
put(&o, "\x2c\0\0\0\0\x02\0\x01\0\0", 10);
|
||||
put(&o, lzw, sizeof(lzw));
|
||||
put8(&o, 0x3b);
|
||||
CHECK(rgif_decode(g, o.p, o.n, px, 8, RGIF_RGBA), "a hand-made GIF87a");
|
||||
CHECK(!memcmp(px, "\0\0\0\xff\xff\xff\xff\xff", 8), "a hand-made GIF87a: its pixels");
|
||||
|
||||
o.p[3] = '7'; o.p[4] = '8';
|
||||
CHECK(!rgif_size(o.p, o.n, &w, &h) && !rgif_decode(g, o.p, o.n, px, 8, RGIF_RGBA),
|
||||
"not a GIF");
|
||||
o.p[3] = '8'; o.p[4] = '7';
|
||||
|
||||
o.p[13 + 6 + 5] = 3; /* the image 3 wide on a screen 2 wide */
|
||||
CHECK(!rgif_decode(g, o.p, o.n, px, 8, RGIF_RGBA), "an image past the screen");
|
||||
o.p[13 + 6 + 5] = 2;
|
||||
|
||||
o.p[10] = 0; /* no global table, and none of its own */
|
||||
CHECK(!rgif_decode(g, o.p, o.n, px, 8, RGIF_RGBA), "no colour table");
|
||||
o.p[10] = 0x80;
|
||||
|
||||
o.p[13 + 6 + 10] = 13; /* LZW codes over 12 bits */
|
||||
CHECK(!rgif_decode(g, o.p, o.n, px, 8, RGIF_RGBA), "an LZW code size of 13");
|
||||
o.p[13 + 6 + 10] = 2;
|
||||
|
||||
o.p[13 + 6 + 12] = 0x40; /* the first code is not a clear */
|
||||
CHECK(!rgif_decode(g, o.p, o.n, px, 8, RGIF_RGBA), "no clear code first");
|
||||
o.p[13 + 6 + 12] = 0x44;
|
||||
|
||||
o.p[13 + 6] = 0x2b; /* no such block */
|
||||
CHECK(!rgif_decode(g, o.p, o.n, px, 8, RGIF_RGBA), "an unknown block");
|
||||
o.p[13 + 6] = 0x3b; /* the trailer, and no image */
|
||||
CHECK(!rgif_decode(g, o.p, o.n, px, 8, RGIF_RGBA), "no image");
|
||||
CHECK(!rgif_decode(g, o.p, 5, px, 8, RGIF_RGBA), "a header cut short");
|
||||
CHECK(!rgif_decode(g, NULL, 0, px, 8, RGIF_RGBA), "no file");
|
||||
free(o.p);
|
||||
}
|
||||
|
||||
/* The image_transfer interface: a 32-bit texture of its own, ARGB32 or
|
||||
* ABGR32 in native order; nothing until the whole file is resident */
|
||||
static void texture(rgif_t *g)
|
||||
{
|
||||
struct gif_desc d;
|
||||
struct out o;
|
||||
uint8_t *idx = noise(13 * 9, 16, 77, 1);
|
||||
uint8_t *ref = (uint8_t*)malloc(13 * 9 * 4);
|
||||
void *px = NULL;
|
||||
unsigned w = 0, h = 0, k;
|
||||
uint32_t one = 1;
|
||||
int le = *(uint8_t*)&one == 1;
|
||||
|
||||
memset(&d, 0, sizeof(d));
|
||||
memset(&o, 0, sizeof(o));
|
||||
d.w = d.iw = 13; d.h = d.ih = 9; d.bits = 4; d.transparent = 3;
|
||||
d.idx = idx;
|
||||
write_gif(&o, &d);
|
||||
|
||||
CHECK(rgif_header_ready(o.p, o.n) && !rgif_header_ready(o.p, 12),
|
||||
"header_ready");
|
||||
for (k = 0; k < 2; k++)
|
||||
{
|
||||
int rgba = (int)k;
|
||||
unsigned order = le ? (rgba ? RGIF_RGBA : RGIF_BGRA)
|
||||
: (rgba ? RGIF_ABGR : RGIF_ARGB);
|
||||
rgif_set_buf_ptr(g, o.p, o.n);
|
||||
rgif_set_avail(g, o.n - 1);
|
||||
CHECK(rgif_process_image(g, &px, o.n, &w, &h, rgba != 0)
|
||||
== IMAGE_PROCESS_WAIT && !px, "a file not all in: wait");
|
||||
rgif_set_avail(g, o.n);
|
||||
CHECK(rgif_process_image(g, &px, o.n, &w, &h, rgba != 0)
|
||||
== IMAGE_PROCESS_END && px && w == 13 && h == 9,
|
||||
"the texture");
|
||||
rgif_decode(g, o.p, o.n, ref, 13 * 4, order);
|
||||
CHECK(px && !memcmp(px, ref, 13 * 9 * 4),
|
||||
"the texture: ARGB32 / ABGR32 in native order");
|
||||
free(px);
|
||||
px = NULL;
|
||||
}
|
||||
o.p[0] = 'X';
|
||||
rgif_set_buf_ptr(g, o.p, o.n);
|
||||
CHECK(rgif_process_image(g, &px, o.n, &w, &h, true) == IMAGE_PROCESS_ERROR
|
||||
&& !px, "the texture of no GIF");
|
||||
free(o.p);
|
||||
free(idx);
|
||||
free(ref);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
static const unsigned sizes[][2] = { { 1, 1 }, { 7, 5 }, { 33, 17 }, { 160, 120 } };
|
||||
rgif_t *g = rgif_new();
|
||||
unsigned s, bits, il, var;
|
||||
char what[128];
|
||||
|
||||
for (s = 0; s < sizeof(sizes) / sizeof(sizes[0]); s++)
|
||||
for (bits = 1; bits <= 8; bits++)
|
||||
for (il = 0; il < 2; il++)
|
||||
for (var = 0; var < 4; var++)
|
||||
{
|
||||
struct gif_desc d;
|
||||
unsigned colours = 2u << (bits - 1);
|
||||
memset(&d, 0, sizeof(d));
|
||||
d.w = sizes[s][0]; d.h = sizes[s][1];
|
||||
d.iw = d.w; d.ih = d.h;
|
||||
d.bits = bits;
|
||||
d.interlace = (int)il;
|
||||
d.transparent = -1;
|
||||
d.full_clear = 1;
|
||||
switch (var)
|
||||
{
|
||||
case 1: /* its own table and a transparent index */
|
||||
d.local = 1;
|
||||
d.transparent = (int)(colours / 2);
|
||||
break;
|
||||
case 2: /* not the whole screen, a background colour */
|
||||
d.x = d.w / 3; d.y = d.h / 4;
|
||||
d.iw = d.w - d.x; d.ih = d.h - d.y - (d.h > 2);
|
||||
d.bgindex = (int)(colours - 1);
|
||||
d.transparent = 0;
|
||||
break;
|
||||
case 3: /* not the whole screen, background entry 0: none */
|
||||
d.x = d.w > 1; d.iw = d.w - d.x;
|
||||
d.ih = d.h / 2 + 1 > d.h ? d.h : d.h / 2 + 1;
|
||||
break;
|
||||
}
|
||||
/* every index the LZW codes can name: past a 2-entry table too */
|
||||
d.idx = noise(d.iw * d.ih, bits < 2 ? 4 : colours,
|
||||
s * 64 + bits * 8 + il * 4 + var, var & 1);
|
||||
sprintf(what, "%ux%u, %u colours, %s, case %u", d.w, d.h, colours,
|
||||
il ? "interlaced" : "plain", var);
|
||||
check_gif(g, &d, what);
|
||||
free((void*)d.idx);
|
||||
}
|
||||
|
||||
/* a full table: cleared, or kept as it is */
|
||||
{
|
||||
struct gif_desc d;
|
||||
memset(&d, 0, sizeof(d));
|
||||
d.w = d.iw = 300; d.h = d.ih = 200; d.bits = 8; d.transparent = -1;
|
||||
d.idx = noise(d.iw * d.ih, 256, 7, 0);
|
||||
d.full_clear = 1;
|
||||
check_gif(g, &d, "a table that fills, cleared");
|
||||
free((void*)d.idx);
|
||||
d.w = d.iw = 70; d.h = d.ih = 40; d.full_clear = 0;
|
||||
d.idx = noise(d.iw * d.ih, 256, 9, 0);
|
||||
check_gif(g, &d, "a table that fills and stays full");
|
||||
free((void*)d.idx);
|
||||
}
|
||||
|
||||
broken(g);
|
||||
texture(g);
|
||||
rgif_free(g);
|
||||
|
||||
printf("rgif_test: %s (%d failures)\n", fails ? "FAIL" : "PASS", fails);
|
||||
return fails ? 1 : 0;
|
||||
}
|
||||
Loaded 100 of 204 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user