Compare commits

..
Author SHA1 Message Date
Codex 122da7195a record: add borrowed native Windows AAC encoding
Lend native PCM input and retain raw AAC output until its consumer returns the packet. Keep native calls on one recording I/O owner, bound input admission, preserve source timestamps and load optional Media Foundation entry points at runtime.

Use private interface IDs to avoid bulk SDK GUID collisions. Verify real Windows encoding against an independent decoder, including mono/stereo, supported rates and bitrates, partial blocks, large clocks, cancellation and owner checks.
2026-11-10 22:23:07 +01:00
LibretroAdmin a5d7f8b5bd encodings: Shift-JIS (code page 932) and code page 1252 conversions
sjis_to_utf8(), utf8_to_sjis() and cp1252_to_utf8(), table driven and
allocation free, for cores that read Japanese and western text from
game data (GameCube and Wii banners, saves and memory cards) and today
pull in iconv for it. Shift-JIS is code page 932: JIS X 0208 with the
NEC and IBM extensions. Octets that do not form a character are
skipped one at a time.

The tables are generated by gen_sjis_tables.py from Python's cp932
codec. The sample checks known characters, skipping, truncation and a
round trip of every two-octet code.
2026-11-10 22:21:28 +01:00
LibretroAdmin 183ba70c15 net/retrossl: set the socket's blocking mode only when it changes
Every ssl_socket_receive_all_nonblocking / send_all_nonblocking /
flush_nonblocking call, and every blocking one, set the socket's mode
again: socket_set_block(), two fcntl() calls (an ioctlsocket on
Windows), however many times in a row the mode was already right. A
connection keeps the mode it last gave its socket and calls
socket_set_block() only when that changes, or once more after
socket_connect_with_timeout(), which leaves the socket nonblocking
behind its back. The socket is the connection's own: nothing else in
libretro-common or RetroArch sets the mode of a socket handed to it.

A duplex connection is made nonblocking as its handshake completes, so
neither of its two threads changes the mode afterwards (and they share
no write).

tls_duplex, 60 lines sent while 2 are read over TLS 1.3: 170 fcntl
calls before, 6 after. The local server matrix passes, and the duplex
check under ThreadSanitizer.
2026-11-10 21:56:06 +01:00
LibretroAdmin 1a6a5c2e91 formats/yaml: values below their keys, and \x, \u, \U escapes
Read against RPCS3's patch.yml (3 MB, 216803 nodes), ryaml stopped at
two things the file uses and YAML allows:

- A key's value on the line below it, more indented - a quoted or plain
  scalar, a flow collection or an alias - often with the key anchored:
      DeS_FPS_Notes: &DeS_FPS_Notes
        "Patch version 2.0 and above ..."
  A line there that is itself a key (quoted ones included) still makes
  the key a mapping, as before.
- \xNN, \uNNNN and \UNNNNNNNN in double-quoted scalars, as bytes and as
  UTF-8 ("shiv   \x00" in the patch data), and "\ " for a space. A bad
  digit is refused rather than read as something else.

With these, every key, scalar, sequence and mapping of that patch.yml
reads as yaml-cpp reads it, aliases included, and so do 3000 random
documents written by yaml-cpp's emitter (checked with a comparison
built against yaml-cpp, which RetroArch does not carry).

ryaml_writer writes any other control byte as \xNN instead of as it is.

ryaml_test covers values below their keys and the escapes, and refuses
a bad \x; ryaml_writer_test's random scalars now hold control bytes.
2026-11-10 21:43:36 +01:00
Codex 65f332c151 formats/flv: lend codec payloads to RTMP transports
Expose ready codec headers and packet spans without FLV file framing or a payload copy. Streaming consumers keep mux output and encoder data borrowed until the RTMP session returns them.

Exercise the mux-to-session path with independent decoded H.264/AAC output over TCP and TLS, including B-frame composition offsets, peer credit and cancellation of a live packet borrow.
2026-11-10 21:29:27 +01:00
LibretroAdmin 1b87a9af45 formats/yaml: ryaml_writer, the reader's counterpart
libretro-common could read YAML but not write it, so a core that keeps
its settings, patch configuration or caches as YAML (RPCS3) still needed
yaml-cpp for the writing.

ryaml_writer takes the document as calls - begin a mapping, a key, a
scalar, begin a sequence (block, or flow on one line), end - and lays it
out as block YAML, two spaces a level, the way yaml-cpp's emitter does
for such documents: a sequence under a key indented below it, a mapping
in a sequence item starting on the dash's line, empty collections as {}
and [], and blank lines between entries on request. A key or scalar is
quoted, double, with the escapes ryaml reads, only where ryaml would not
read it back as it is. Output goes through a callback a 4 KiB stage at
a time, or into a buffer the writer grows and hands over to read in
place; nothing else is allocated per call.

samples/formats/yaml_writer/ryaml_writer_test: a configuration and a
patch file byte for byte against their expected layout; 36 scalars that
need care (indicators, ": ", " #", blanks at the ends, line breaks,
quotes, backslashes, flow punctuation, UTF-8) as keys, values, items and
flow items, read back the same; 3000 random documents read back by
ryaml to the same tree, through memory and through a callback; and the
calls that make no document refused. With " #" left unquoted it fails.
2026-11-10 21:28:47 +01:00
LibretroAdmin bbbc50f1f6 formats/yaml: anchors and aliases, and where a node is
ryaml read neither "&name" nor "*name", and a document that used them
came out wrong rather than refused. RPCS3's patch files need them: an
entry [ load, *name, modifier ] stands for the sequence of patches
anchored as name under "Anchors:".

- "&name" ahead of a value, or of the lines that make it up, anchors
  the node; on a block mapping value, a sequence item or a flow item.
- "*name" as a value is a node of its own, with its own key, siblings
  and parent, that holds what the anchored node holds: every call that
  reads a value, a type or children reads the anchored node's.
  ryaml_is_alias() tells one apart. Aliases are resolved as they are
  read, so the names are gone with the parse; an alias of an alias
  holds the first node, and a name anchored again names the newer one.
- An alias to a name not anchored before it, an anchor with no name,
  and anything but a comment after an alias are refused.
- ryaml_location(): a node's line and column in the document, for
  messages about it (where its key, its dash or its flow item starts).

Nodes grow by eight bytes (the alias and the offset); a lookup costs
one test more. Nothing changes for a document without anchors.

samples/formats/yaml/ryaml_test, the reader's first test: a
configuration, block scalars, compact mappings, a patch file in
patch.yml's shape with load entries through aliases, anchors on
scalars, mappings and sequences, the documents that must be refused,
and node locations. The old reader fails the anchor, alias and location
checks and passes the rest.
2026-11-10 21:23:50 +01:00
LibretroAdmin fed60b7e05 net/retrossl: SSL_RETRO_DUPLEX, one thread receiving while another sends
A connection configured with SSL_RETRO_DUPLEX may be read on one thread
and written on another at the same time, with no lock - what a client
that keeps a reader and a writer thread on one socket needs (RPCS3's
RPCN client, which had wolfSSL's write_dup for it).

What the two sides shared:

- Receiving pushed out pending output (a sealed record the socket had
  not taken, a KeyUpdate owed) before reading. In duplex mode it does
  not: the sending side's buffers, keys and sequence number are the
  sending thread's alone.
- A KeyUpdate the server asks for was answered from the receive path.
  In duplex mode the receive path only raises key_update, now an atomic,
  and the next send or flush sends ours before its data, as RFC 8446
  4.6.3 asks. The blocking receive path does the same in duplex mode.
- ssl_socket_retro_pending() read the sending side's state for its
  write interest; in duplex mode it reports none.
- last_err could be written by both at once when the connection drops;
  it is an atomic, stored and read relaxed.

A duplex connection takes its record buffer for sending when it is
configured, not on its first send. Without the flag nothing changes but
key_update and last_err being atomics (plain loads and stores on x86 and
ARM).

samples/network/tls_retro/tls_duplex reads the lines openssl s_server
sends while a second thread sends 60 of its own, over TLS 1.3 with two
requested KeyUpdates and an unrequested one between the server's lines,
and over TLS 1.2. The server must print all 60 lines and its -msg trace
must show exactly two KeyUpdates from us. CI runs it in the local server
matrix and again built under ThreadSanitizer; with the receive path
still pushing output, TSan reports the races, and with the request not
raised, the server sees no KeyUpdate.
2026-11-10 21:21:04 +01:00
Codex 49582a9383 net: pump RTMP output through borrowed TLS connections
Add a TLS transport constructor and an explicit nonblocking drain to the existing RTMP socket owner. Retain stalled writes and drain accepted ciphertext before borrowing further session output; cancellation retires the session without closing caller-owned TLS state.

Exercise backpressure and drain failures, then compare decoded H.264/AAC from real TLS 1.2 and 1.3 transfers in standard and legacy command modes. Run the oracle natively under sanitizers and as Windows binaries through the recording CI job.
2026-11-10 21:07:43 +01:00
Codex 5f5f2f47ea test: bundle independent ATRAC9 codebook reference data
Run the complete Huffman reference checks from the sample directory without a private /tmp specification checkout. Pin the MIT-licensed LibAtrac9 source and provide an offline deterministic fixture generator; retain the external fixture argument.
2026-11-10 21:07:35 +01:00
LibretroAdmin 1087a448da formats/dds: BGRA block decoders, and one BC3 alpha ramp on every path
rdds_decode_block_bc1_bgra / bc2_bgra / bc3_bgra: the BC1-BC3 block
decoders with B,G,R,A in memory, the RGBA ones' values with red and
blue swapped (in the four reference colours, once per block). For a
core that decodes blocks for a BGRA8 texture itself, as RPCS3 does
when the GPU has no BCn; that core then needs no copy of bcdec.

The DDS loader uses them too: on a little-endian machine a BGRA video
driver now gets the direct row decoders the RGBA one had (interior
blocks written straight into the surface, no scratch block and no
repack per texel), where it went through the scratch path. Big-endian
keeps the scratch path. The order is a constant in each decoder (the
shared bodies are force-inlined), so the RGBA decoders are built as
before. Decoding a 2048x2048 DDS, best of 5 runs on x86-64:

            RGBA before/after    BGRA before/after
  BC1       4.42 / 4.29 ms        8.83 / 5.64 ms
  BC2       5.74 / 5.45 ms       12.22 / 6.70 ms
  BC3       6.83 / 7.01 ms       14.03 / 7.97 ms

The RGBA BC3 row decoder truncated its interpolated alpha while the
block decoder and the scratch path round it (to the D3D reference), so
an RGBA and a BGRA driver decoded the same DDS with alpha off by one on
some texels. It rounds now, as the rest do; the adds are the 3% on
RGBA BC3.

rdds_block_test checks the BGRA decoders against the RGBA ones over
60000 random blocks, and random BC1/BC2/BC3 DDS images, with edges,
decoded for an RGBA and a BGRA driver, texel for texel against the
block decoders (which fails on the old truncating ramp).
2026-11-10 21:02:27 +01:00
Codex 67c4a68f89 menu: admit MOV movies to the internal media player
Include MOV in the builtin browser filter and classify its extension as an MP4-family thumbnail source. This routes selection and external launches through the existing movie reader, including mixed-case extensions.

Cover browser listing and internal routing, and exercise real QuickTime MOV fixtures through browsing and external launch in all four menu drivers.
2026-11-10 20:51:31 +01:00
LibretroAdmin bf5c84a903 gfx/font: rtt hands out TrueType points as stored and composite components
rtt_glyph_points gives a simple TrueType glyph's points as the glyf
table stores them, in font units, with on-curve flags and contour
ends; rtt_glyph_components gives a composite glyph's components, with
their offsets or matched point numbers and their 2.14 matrices.

A caller that places glyphs in its own fixed-point arithmetic needs
them: a FreeType-compatible engine scales each component's points on
their own, then transforms and moves them, and rounds differently
from scaling the composite as a whole. The core of parallel-ps4 uses
them so its font library keeps FreeType's numbers exactly.

rtt_test checks both on its TrueType font: the square's four stored
corners, counts alone, a composite refused, and the composite's two
components with the second's half scale and offset.
2026-11-10 20:24:21 +01:00
Codex 58ccb33e28 net: advance TLS handshakes without blocking socket I/O
Add a TLS-only step API across the native, Mbed TLS and BearSSL backends. Preserve handshake state and pending ciphertext across calls, report readiness interests, and close stalled connections without waiting.

Snapshot resumption keys with the offered session and skip contended cache updates in nonblocking calls. Exercise fragmented records, backpressure, cancellation, cache replacement and contention, plus real server verification through the existing network CI jobs.
2026-11-10 20:07:06 +01:00
LibretroAdmin 798dcd7f33 formats/rvz: read a Wii partition's data decrypted
A Wii emulator reads a game's partition decrypted, and WIA and RVZ
hold that data decrypted already. Through rvz_read_begin() it came back
as the disc has it: every sector's hashes worked out and the sector
encrypted, for the caller to decrypt again at once.

rvz_read_decrypted_begin() arms a read of a partition's data at an
offset in it, 0x7c00 octets a sector, which rvz_read_step() runs as
it does any read: each sector's data is copied from its group as the
file stores it. rvz_decrypted_supported() says whether a range lies
within one of the partition's data segments. A read past them fails.

rvz_test reads the partition's data of each Wii image decrypted, in
order and anywhere, against the data the image was made from, then
the disc image again, and checks the ranges refused.
2026-11-10 20:04:01 +01:00
LibretroAdmin 024dcba88c formats/rvz: the file's compression method, level and chunk size
rvz_compression() gives the open file's RVZ_COMPRESSION_* method and
the compression level its disc struct records, and rvz_chunk_size()
the octets of the disc a group holds. A frontend or core showing what
an image is, or warning that its groups are large for random access,
needs them; until now only the reader itself could see them.

rvz_test writes a different level for each method, one negative as
Zstandard's may be, and checks all three for every image.
2026-11-10 20:03:57 +01:00
LibretroAdmin a86d86a22c HAVE_RGIF: GIF images through image_texture, image_transfer and the tasks
rgif now loads GIFs wherever RetroArch loads images: thumbnails and
backgrounds through task_image, the image viewer core, the file browser
and the image_texture/image_transfer layer underneath them all.

rgif:
- the image_transfer interface the other decoders have:
  rgif_set_buf_ptr, rgif_set_avail, rgif_header_ready and
  rgif_process_image, giving a 32-bit texture of its own, ARGB32 or
  ABGR32 in native byte order
- RGIF_ABGR, for ABGR32 on big-endian targets
- a file not all read yet gives IMAGE_PROCESS_WAIT and is not touched;
  the image is decoded in one call once it is all in

Wiring, as HAVE_RTGA is wired:
- IMAGE_TYPE_GIF (image.h) and NBIO_TYPE_GIF (task_file_transfer.h),
  both appended so no other value moves
- image_texture_get_type: .gif
- image_transfer: new, free, start, is_valid, set_buffer_ptr, process,
  set_avail; the support matrix gains a GIF column
- task_image and task_file_transfer: GIF is an image task
- FILE_TYPE_GIF ("gif", "GIF"): opened as image content
- the file browser's image extensions, the image viewer core's
  extensions, the System Information list ("GIF (RGIF)") and
  --features ("RGIF"); SUPPORTS_RGIF in config.features.h
- griffin.c, Makefile.common (rgif.o), configure (HAVE_RGIF=yes), and
  next to HAVE_RTGA in every platform Makefile, the Android, Apple,
  MSVC, UWP and QNX projects and the CI compile matrix
- tools/settings_check.py and tools/stack_budget.py check with it

Also: menu_displaylist's image viewer extension list wrote "tga"
without moving its length on, so "webp" and "dds" landed on top of it
and TGA dropped out of the list; it moves on now.

Tests: rgif_test checks the texture in both byte orders against
rgif_decode, the wait before the whole file is in, and a refused file;
image_decode_fuzz sweeps GIF too (every byte of a small GIF set to
edge values, and every truncation), under ASan and UBSan. The griffin
unity build with every codec, the touched objects in a configured
build, the MinGW/C89 syntax check, the overlap check and the stack
budget pass.
2026-11-10 20:00:24 +01:00
LibretroAdmin 4c1412cd0f gfx/font: rtt reads CFF fonts, vertical metrics and kerning; faster
rtt now reads OpenType fonts with CFF outlines as well as TrueType
ones, so the stb font renderer can load .otf files:

- Type 2 charstrings with local and global subroutines, hints and
  hintmasks, flex and the arithmetic operators;
- CID-keyed fonts, each glyph's font dict found by FDSelect (formats
  0 and 3), with the first 32 font dicts' subroutines found at init;
- vmtx/vhea vertical metrics, 'kern' format 0 pair kerning, and
  rtt_table for any table, all read in place;
- rtt_glyph_outline hands a glyph's outline to the caller, and
  rtt_glyph_cbox gives its control box;
- rtt_render_glyph_at takes a fractional origin for sub-pixel
  placement, and rtt_set_flatness a finer tessellation.

Checked against FreeType on Inter (CFF), the three faces of Noto Serif
CJK Bold (CID-keyed CFF, 65535 glyphs each) and DejaVu Sans: the same
glyph for every code point, advances, vertical advances, kerning and
control boxes. The control boxes match exactly for the CFF fonts, and
to within one unit for DejaVu, where FreeType moves a glyph whose
side bearing disagrees with its xMin.

TrueType rendering is byte for byte what it was, and faster: each
point is carried to the device once before the contours are walked,
glyf flags and coordinates are read without a check per byte where the
font holds them all, and curve steps multiply by an exact reciprocal.
Every glyph of DejaVu Sans at 12 to 20 pixels draws 5.5% faster, at 12
to 100 pixels 1.3% faster.

rtt_test, with a TrueType and a CID-keyed CFF font built by
rtt_fonts.py, checks cmaps, metrics, kerning, boxes and outlines, the
exact area a rendered square covers and a circle's to 0.4%, at
sub-pixel offsets, and every truncation and 1500 corruptions of each
font under the sanitizers; it runs on Linux and under Wine.
2026-11-10 19:58:09 +01:00
LibretroAdmin cddfd368da formats/gif: rgif, a GIF's first image straight into the caller's rows
libretro-common had no GIF decoder; cores that show or emulate one
(an emulated console's GIF decoder library, overlay art) carried
stb_image for it. rgif decodes a GIF's first image:

- from the file where it lies, nothing copied
- into the caller's memory at the caller's row pitch, so a core can
  decode straight into the guest's buffer, RGBA, ARGB or BGRA
- with its memory (the LZW table, about 20 KB) taken by rgif_new() and
  kept: one image after another takes nothing more
- strings written out from a stack in the decoder, not by recursion

It gives what stb_image 2.30 gives for the first frame: the logical
screen, the background colour (opaque) where the image does not reach
when the file names one past entry 0, transparent black for transparent
pixels and indices past the colour table, interlaced rows in their
passes, a file cut short decoded as far as it goes, and the same files
refused (no clear code first, codes past 8192, an image past the
screen, no colour table, an unknown block). The one difference is on
purpose: stb_image copies the background from its BGR-ordered table,
so its red and blue come out swapped; here they do not.

Checked against stb_image 2.30 over 2170 files and their truncations:
the same pixels everywhere but that background.

samples/formats/gif/rgif_test writes GIFs of every colour table size,
plain and interlaced, global and local tables, transparency, images
smaller than the screen with and without a background, LZW tables
that fill and are cleared or left full; decodes them in each byte
order into rows with padding (left untouched) and checks every pixel;
cuts them short; and refuses broken ones. Added to the samples
workflow.
2026-11-10 19:32:52 +01:00
LibretroAdmin c1c815b33e CI: retry the MSYS2 setup once when its system upgrade fails
The package installs after setup-msys2 already retry five times across
mirrors, but the action's own system upgrade (update: true) has no
retry: when mirror.msys2.org stalls on the package databases, pacman
gives up after ten seconds below a byte a second, skips the mirror for
the transaction, and the job fails before anything is built.

In the MSYS2 build and the Windows VFS samples the setup step now
continues on error, and a failed attempt is run once more after a
minute; the second run reinstalls over the first. A second failure
still fails the job. The upgrade stays in the action, since an
msys2-runtime update kills every MSYS2 process and a run step's shell
would not survive it. The Vulkan validation job does no upgrade in the
action and is left as it is.
2026-11-10 19:32:04 +01:00
LibretroAdmin 4356847c42 gfx/font: move the clean-room TrueType rasterizer into libretro-common
The TrueType loader and rasterizer the stb font renderer carried
inside gfx/drivers_font_renderer/stb.c moves, unchanged, to
libretro-common/gfx/font/rtt.c with its interface in
<gfx/font/rtt.h>, so cores can use it: sfnt/TTC directories, cmap
formats 0/4/6/12, hmtx/hhea/head/maxp metrics, glyf outlines and the
exact-coverage anti-aliased rasterizer with its SSE2/NEON resolves.

It reads the caller's bytes in place, opens no file, and allocates
only to grow the caller's rtt_scratch_t, as before. The scratch's
cell key is packed locally instead of through video_defines.h.

The stb renderer now calls it. Every glyph of four fonts at 12, 24,
48 and 100 pixels renders byte for byte as before, at the same speed.
2026-11-10 19:30:21 +01:00
Codex afcfce0d67 ozone: retain media previews through settings refreshes
Do not replace the settings preview player in browser selection or thumbnail refresh callbacks while media settings are active. Other thumbnail update paths already preserve this ownership.

Exercise external movie activation, the settings mouse button, stale browser selection and thumbnail refresh notifications, playback progress and Back. Check root and subfolder paths on Windows and rendered preview pixels in the GPU menu jobs.
2026-11-10 19:17:36 +01:00
LibretroAdmin 0580d2d6fc math/float16.h: half floats both ways, exactly
retro_half_to_float and retro_float_to_half convert IEEE 754 binary16
to and from binary32 with integer arithmetic only, so flush-to-zero and
denormals-are-zero modes change nothing. Narrowing rounds to nearest,
ties to even, as F16C and GPUs do; subnormal halves, signed zeros,
infinities and NaNs are kept (a signalling NaN comes back quiet).

The Direct3D and Vulkan HDR screenshot paths each had a private copy
of the widening; both use the header now.

float16_test checks every half against its value and its round trip,
the floats in and near the half range of both signs against F16C's
VCVTPS2PH (a stride elsewhere), a stride against the nearest half by
search, and the same again with FTZ and DAZ set.
2026-11-10 19:17:25 +01:00
LibretroAdmin 378865087c formats/png: rpng_rows keeps the colour chunks
gAMA, cHRM, sRGB, sBIT, cICP and mDCV are now kept as the file states
them, with where each came among them, for a caller that converts colour
as libpng would: an emulated console PNG decoder turning RGB into gray
weights the channels by the file's chromaticities and works in linear
light when the file has a gamma, as the libpng it emulates did.

Each is dropped where libpng 1.6 drops it: the wrong length, out of
place (after PLTE or the image data), a second of a kind, or a value
the specification forbids (a gamma over 31 bits, a cHRM value that is
no 32-bit integer, a rendering intent over 3, an sBIT of none or more
bits than the sample has). Before, gAMA, cHRM, sRGB and sBIT were
counted whatever they held; cICP and mDCV get bits of their own, above
the sixteen console libraries report.

rpng_rows_test covers each kind kept, the order, the broken ones and
the second of a kind dropped, sBIT by colour type, and the chunks after
PLTE.
2026-11-10 18:59:40 +01:00
LibretroAdmin 7600667d1b net_socket: socket_poll past 64 sockets on Windows
socket_poll runs on select() on Windows, and FD_SET drops a socket
once a set holds FD_SETSIZE (64): any socket past the 64th was never
reported ready, so a poll over more sockets waited out its timeout.

A Winsock fd_set is a count and an array that select() reads up to the
count. Up to FD_SETSIZE sockets the sets stay on the stack; past it
they are allocated for the call, sized for the sockets given.

socket_poll_test polls 150 loopback UDP sockets, on Linux and under
Wine: the two given a datagram, the 71st and the last, are the only
readable ones, all are writable, and none ready times out.
2026-11-10 18:59:12 +01:00
LibretroAdmin 86a5c9918b formats/mp3: gapless playback from the Xing/Info tag and LAME's delay
rmp3_parse_vbr_tag reads the tag frame an encoder puts first: Xing or
Info, with LAME's extension (encoder delay and padding) where there is
one, or VBRI. The side-information offset follows the frame's MPEG
version, channel mode and CRC.

The pull API uses it: the tag frame, which decodes to silence, is not
decoded; the encoder delay plus the decoder's own 529 samples
(RMP3_DECODER_DELAY) come off the start; reads stop at the track's
end, before the padding; and rmp3.totalFrames gives the length up
front. Seeks count from the trimmed start. A stream without the tag
decodes as before. The stream API is unchanged; its callers can use
rmp3_parse_vbr_tag.

rmp3_gapless_test checks LAME streams (MPEG-1 CBR and VBR, MPEG-2)
come out exactly as long as they went in and match ffmpeg's trimmed
decode to a rounding step, plus seeks, an untagged stream and tags
built by hand.
2026-11-10 18:49:55 +01:00
LibretroAdmin 0484a6d4f7 formats/png: rpng_rows, a PNG a row at a time as it is stored
A push decoder for callers with conversions of their own to apply, such
as an emulated console PNG library: input is handed over a piece at a
time, and each row comes out with its filter undone and nothing else
done to it - palette indices, gray at 1 to 16 bits, RGB(A) at 8 or 16,
as the file has them. Adam7 images come out pass by pass, each row
naming its image row and the columns its pixels go to. Before the first
row it reports the header, the palette, tRNS and which ancillary chunks
there were; at IEND the text and unknown chunks are counted.

The image data is inflated straight into the row being filled; two rows
and the inflate state are taken at the header, nothing after. Every CRC
is checked: a bad critical chunk is an error, a bad ancillary chunk is
dropped, as are ancillary chunks out of their place, as libpng drops
them.

rpng_rows_test writes every colour type and bit depth, plain and
interlaced, with every filter type and the image data split over
several IDATs, decodes them a byte and 4 KiB at a time, and checks the
pixels, the chunk bookkeeping and that broken files are refused.
2026-11-10 18:38:26 +01:00
LibretroAdmin 095b916b08 libretro-common: encoding_adler32
The Adler-32 rinflate and rdeflate keep for the zlib wrapper, as zlib's
adler32() gives it, under a name of its own: a caller that wants the
checksum alone, a GCZ disc image's block hash say, no longer needs zlib
for it. The kernel is the existing vector one.

encoding_adler32_test checks known values, a checksum continued across
pieces against the whole, and long runs against the textbook loop at
the block edges, the 5552-octet bound and all 0xff from the largest
seed.
2026-11-10 18:38:24 +01:00
LibretroAdmin 97dd672cc6 samples: run rdeflate_flush_test in CI
The samples workflow runs the targets a Makefile names TARGET, TARGETS
or TARGET_TEST<n>; rdeflate_flush_test was TARGET_FLUSH, so it was built
and never run. It is TARGET_TEST2 now.
2026-11-10 18:38:24 +01:00
Codex 8c62b973fd Measure HTTP stall tests with the production monotonic clock
Use the same clock as the timeout being checked so wall-clock adjustments cannot change the elapsed-time assertion. Retain the original bounds and print transfer state when a stall check fails.
2026-11-10 18:29:50 +01:00
Codex dd3a2d0831 Yield on partial native TLS records and control traffic
Retain partial record headers and bodies instead of switching a nonblocking read into a blocking receive. Process one record or buffered post-handshake message per service call, keeping direct application decryption.

Queue KeyUpdate replies in the retired handshake cork behind existing ciphertext and ahead of new application records. Preserve duplex reads under write backpressure and expose buffered work/write interest to HTTP waits.

Test fragmented input, mixed APIs, control ordering, socket failures and authenticated output. Drive OpenSSL HTTP and KeyUpdate acceptance through readiness waits. The handshake remains synchronous.
2026-11-10 18:24:38 +01:00
LibretroAdmin dd8f53271f net_upnp: a gateway from its description URL, and a mapping to another port
net_upnp_gateway_from_url sets a gateway up from the device description
URL discovery found it by, which net_upnp_discover now keeps in
gw->desc_url, so a caller that saved it skips the SSDP search the next
time. net_upnp_add_port_mapping_ext forwards one external port to a
different internal one, for a caller that tries the next external port
when one is taken; net_upnp_add_port_mapping is it with both the same.

The RPCS3 core replaces miniupnpc with these. net_upnp_test covers both
against its fake gateway.
2026-11-10 18:18:45 +01:00
LibretroAdmin 22b987902c net_http: redirects off on request, the caller's own headers; AES-CFB128
For an HTTP client that keeps its own rules (a console's HTTP library
emulated in a core):

- net_http_connection_set_redirects(conn, false) hands a 3xx back as it
  is - status, headers and body - instead of following its Location.
- An Accept-Encoding or User-Agent among the caller's headers replaces
  net_http's own instead of going out next to it.
- A request carrying a body sends its Content-Length whatever the
  method; a DELETE with a body went without one.

crypto: aes_cfb128_encrypt/decrypt, CFB with 128-bit feedback as
OpenSSL's AES_cfb128_encrypt, resumable mid-block. Checked against
SP 800-38A F.3.13 and F.3.17 and against itself split at random points.

samples/net/net_http_options_test checks the three request options
against a loopback server.
2026-11-10 18:17:37 +01:00
Codex 5dd6fbbfc8 Add bounded asynchronous resolver jobs
Dispatch lookups through an existing resolver executor and publish borrowed address chains without main-thread waits. Separate caller and worker references keep cancellation safe during queued and executing lookups; cap live requests instead of accumulating canceled jobs.

Exercise ownership failures, concurrent native DNS and the independent RTMP A/V receiver path. Keep executor thread lifetime outside the SDK request.
2026-11-10 18:03:29 +01:00
Codex d10f7ab438 Add incremental nonblocking socket connection setup
Separate connection admission from zero-time completion probing so streaming setup can yield while TCP connects. Validate SO_ERROR before reporting readiness and retain the platform-specific completion rules.

Exercise poll/select progress and refusal paths, and use the API in the independent RTMP receiver fixture.
2026-11-10 17:48:35 +01:00
LibretroAdmin a27ef55d56 libretro-common: rdeflate_flush, a sync flush
zlib's Z_SYNC_FLUSH: once the input given is consumed, the pending
symbols go out as a block and an empty stored block ends the output on
a byte boundary, so everything compressed so far decodes from the
output up to there. The window is kept. A log compressed as it is
written stays readable to its last line after a crash; the RPCS3 core
writes its .gz log that way.

rdeflate_flush_test compresses a stream a piece at a time with a flush
after each, at every level and with the output a few bytes at a time,
and decodes the output after every flush.
2026-11-10 17:44:37 +01:00
LibretroAdmin c867b2a34a formats/atrac9: ratrac9, a clean-room ATRAC9 decoder
Decodes ATRAC9 (PS4/PS Vita audio) frame by frame from the 4-byte
config: all six channel layouts (mono to 7.1 with LFE), all sixteen
sample rates, superframes, band extension and intensity stereo. Output
is 16-bit, 32-bit or float, interleaved or planar. Also savestates of
the decoder's history and a WAVE 'fmt ' reader for the config.

Written in two steps: a functional specification of the format and its
tables was drawn up from the reference decoder, and the decoder was
then written from that specification alone, without access to any
existing implementation.

Its 16-bit, 32-bit, float and planar output and bytes used are
bit-identical to LibAtrac9's on 384 generated streams covering every
rate, layout and superframe size. It decodes them about 2.8x as fast
(-O2, x86-64): a 64-bit bit reader, one-lookup Huffman tables that
yield scaled coefficients, and a double-precision split-radix DCT-IV
with SSE2 and NEON paths that give the same bits as the C one. One
allocation at open; none while decoding or resetting; no writable
globals. Where the reference reads outside its own tables (extension
with fewer than 13 or more than 20 units, fine precision 16 and 17),
the decoder rejects the frame or extends the table by its formula.

samples/formats/atrac9: ratrac9_vectors_test checks generated streams
against CRCs of the reference decoder's output, a reset and a savestate
mid-stream; ratrac9_test checks the bit reader, every codebook, the
DCT-IV, config and WAVE parsing, savestates and truncated input.
2026-11-10 17:42:23 +01:00
Codex 085bf1bf56 Send borrowed media through RTMP sessions
Admit encoded audio, video and data without copying packet payloads. Retire producer borrows explicitly, schedule controls between chunks, and bound output by acknowledged peer credit.

Announce outgoing chunk-size changes before media admission. Exercise cancellation, short writes, stalled credit and exhausted output, and compare H.264/AAC decoded through an independent RTMP receiver on Linux and MinGW/Wine.
2026-11-10 17:13:45 +01:00
LibretroAdmin 1dbab030d4 rslc: highp on GLSL ES samplers without a default precision
GLSL ES gives only sampler2D and samplerCube a default precision. A
sampler2DArray, sampler2DMSArray, samplerBuffer, an int or uint array
sampler or a storage image, as a uniform or a function parameter,
cannot compile declared without one, and the builder declares its
samplers without one. Such a declaration now prints its own precision,
highp where none is stated; sampler2D prints as it did.
2026-11-10 16:50:48 +01:00
LibretroAdmin ce3ee5174c net_upnp, raac: the stack budget and overlap copy checks
net_upnp_discover() held a 1536-byte SSDP datagram buffer next to a
NET_UPNP_URL_MAX location, a 2384-2512 byte frame against the 2048 the
budget allows for PSP's and GX's 8 KiB thread stacks. The datagram
buffer is allocated once per discovery instead, before the receive
loop, and freed with the socket; a failed allocation fails the
discovery as a failed socket does.

The parametric stereo decoder's six same-array copies are disjoint by
construction and go on the overlap allow list: a parameter row copied
from another row (the copy is guarded by src != num_env), the matrices
of envelope 0 from envelope num_env_old (guarded by num_env_old > 0),
and in_buf[i][32..37] down to [0..5].

tools/stack_budget.py reports no frame over 2048 bytes (Linux gcc and
x86_64 mingw); the overlap copy check is clean; net_upnp_test passes
under ASan and UBSan.
2026-11-10 16:32:31 +01:00
LibretroAdmin 7370c1a268 vfs_prefetch_offthread_test: let the read-ahead thread take its first hint
The reader went through the 16 MiB fixture in 64 KiB reads without
waiting, and under Wine the read-ahead thread that its first read
starts was not scheduled until the reader had reached the end: its
first look found only the last range published, [12.4 MiB, 16 MiB),
which it brought in as its one hint (3735552 bytes, 57 units), and
'a sequential reader is read ahead of' failed on hints > 1. That is
the thread working as meant - a newer range replaces one not yet
started - not a missing hint. After its first read the test now waits,
outside the timed read, until the thread has taken up that read's
hint; the rest of the file is then read while that hint sleeps, and
the thread brings in the latest range after it: two hints, still no
byte asked for twice.

The same job's PS2 build of vfs_implementation.c for
vfs_close_result_test is a mingw build, so retro_vfs_file_rename_impl
takes its Windows branch and rename_aside_path() went unused
(-Wunused-function). The helper is now built exactly where a branch
that calls it is.

Under Wine 10 the test passes five runs of five (2 hints, 7929856
bytes); vfs_readahead_test and vfs_close_result_test pass, and the
three build without warnings; vfs_implementation.c builds clean with
mingw plain, -DPS2, -D_3DS and -DLEGACY_WIN32, and natively.
2026-11-10 16:24:22 +01:00
LibretroAdmin 0803d4be13 rslc: early depth as conservative depth in GLSL
opts.early_z_depth_unchanged prints a stage's early fragment tests as
layout(depth_unchanged) on gl_FragDepth, under
GL_ARB_conservative_depth below GLSL 4.20 and GL_EXT_conservative_depth
in GLSL ES. A GL context without image load / store has no
early_fragment_tests, and conservative depth is how its driver is told
the stage leaves depth alone so the test can run early.

rslc_ext_test prints the dual-source stage that way at three versions;
check runs glslangValidator on the GLSL 4.50 one where installed.
2026-11-10 16:03:33 +01:00
LibretroAdmin 9cbbbcbacf rslc: framebuffer fetch in GLSL
An input attachment, the colour attachment's value on entry, prints in
GLSL where the caller names the extension it has with opts.fb_fetch:
GL_EXT_shader_framebuffer_fetch reads it from the colour output at the
attachment's index, which prints inout, and
GL_ARM_shader_framebuffer_fetch from gl_LastFragColorARM. Without one
it is refused as before. A stage built for Vulkan and Metal with a
subpass input for its blending can then print for a GL ES context that
fetches from the framebuffer.

rslc_ext_test prints the framebuffer fetch stage both ways, and checks
EXT's refusal of an attachment with no colour output at its index.
2026-11-10 16:03:30 +01:00
LibretroAdmin 1f59acacbe rslc: subgroup operations in GLSL
GLSL prints subgroupMin, subgroupMax, subgroupElect and
gl_HelperInvocation as they are, under GL_KHR_shader_subgroup's basic
and arithmetic extensions, where it refused them before. A core that
reduces its bounding box over the subgroup on OpenGL can then build the
stage instead of writing GLSL for the driver. HLSL still refuses them.

rslc_ext_test prints the bounding box stage for GLSL and GLSL ES and
reads the GLSL back; check runs glslangValidator on it where installed.
2026-11-10 16:03:26 +01:00
204 changed files with 32582 additions and 2629 deletions

No files matched your search

+20 -1
View File
@@ -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
+16
View File
@@ -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
+71 -2
View File
@@ -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
+18
View File
@@ -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 }}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+21
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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
+2
View File
@@ -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)
+1
View File
@@ -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
View File
@@ -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
+76 -216
View File
@@ -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);
+8 -25
View File
@@ -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 },
+6
View File
@@ -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
+6 -2
View File
@@ -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
View File
@@ -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:
+7 -45
View File
@@ -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
View File
@@ -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);
File diff suppressed because it is too large. Load diff
+9
View File
@@ -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"
+36
View File
@@ -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)
{
+45 -2
View File
@@ -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
+112 -20
View File
@@ -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); }
+12
View File
@@ -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)
+463
View File
@@ -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;
}
+10
View File
@@ -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')
+66 -20
View File
@@ -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:
+140 -6
View File
@@ -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;
+744
View File
@@ -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;
}
+107
View File
@@ -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);
+265 -14
View File
@@ -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;
}
+515
View File
@@ -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
+108 -7
View File
@@ -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)
{
+17
View File
@@ -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
+2 -1
View File
@@ -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
+92
View File
@@ -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
+10
View File
@@ -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);
+106
View File
@@ -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
+34 -1
View File
@@ -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);
+177
View File
@@ -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
+45
View File
@@ -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
+30 -3
View File
@@ -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
+241
View File
@@ -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)
+122
View File
@@ -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
+7
View File
@@ -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;
/**
+39
View File
@@ -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
+15 -2
View File
@@ -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
+14
View File
@@ -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(
+26 -1
View File
@@ -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:
+13
View File
@@ -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);
+54 -17
View File
@@ -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)
{
+115
View File
@@ -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);
}
+5
View File
@@ -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);
+127 -15
View File
@@ -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);
+75 -2
View File
@@ -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:
+112 -12
View File
@@ -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;
+66 -32
View File
@@ -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)
+117 -68
View File
@@ -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
+52 -3
View File
@@ -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