Replace naomi:WCCFSlave by naomi:WCCFNode to set node number for
projectors and satellites.
Handle socket to port 59200 locally to allow connecting from the same IP
and bypass wccfd completely.
The context negotiation interface passes the device extensions the
frontend needs, but they were never enabled. Frontends such as RetroArch
pass the extensions an OpenXR runtime needs to share the device, and
without them SteamVR crashes in xrCreateSession.
The timer heap is capped at MAX_BLOCK_COUNT blocks of 1600 bytes, about
1,050 timers. Each TCP socket keeps several (keepalive, stats,
retransmission, linger) and closed sockets linger for 30 seconds, so a few
dozen connections a second fill it. When pico_timer_add() fails,
socket_del() frees the socket at once instead of deferring it, while
pico_tcp_input() is still using it: AddressSanitizer reports a
heap-use-after-free in pico_tcp_input(), and release builds crash.
Raise the cap to 1024 blocks (~67,000 timers). There is a single heap, and
blocks are only allocated as it grows, so this costs 8 KB of pointers.
[ci skip]
pico_socket_connect() ignored the result of pico_socket_alter_state(),
which fails when a socket with the same local/remote address and port is
already in the socket tree. The new socket then carried on untracked, and
deleting it later found and unlinked the other socket instead, which was
then freed while still in use: a double free or heap corruption.
picoppp hits this when it forwards an inbound connection whose client
reused a source port while picotcp still holds the previous connection
(Linux reuses loopback source ports at once). Fail the connect with
EADDRINUSE, and have picoppp retry with a port picotcp picks.
Found by forwarding a few hundred connections a second to the Broadband
Adapter; the crash went away with this.
[ci skip]
TcpSocket called the throwing overloads of asio's socket.shutdown() and
socket.close() when the guest closed its side of a connection. If the
host peer had already disconnected (or reset the connection),
shutdown() throws std::system_error ("Transport endpoint is not
connected"), nothing catches it, and Flycast terminates.
Use the error_code overloads: a peer that is already gone just means the
socket is already closed.
Found forwarding a few hundred connections a second to a KallistiOS
program, where clients and guest close connections in either order:
Flycast terminated within about a thousand connections.
[ci skip]
Since c52e6cc, the emulation thread calls pico_stack_recv() directly,
which enqueues on the device's protected q_in while the pico thread
dequeues it. pico_enqueue() unlocked the queue and then read q->size for
its return value. If the pico thread dequeued and processed the frame in
between, the size could read 0, and pico_stack_recv() took that as a
failed enqueue and freed a frame the pico thread had already freed.
AddressSanitizer: attempting double-free in pico_stack_recv(), called
from qemu_send_packet() on the emulation thread; freed by
pico_ethernet_receive() on the pico thread. It took a few dozen
forwarded TCP connections to crash a release build.
Read the size under the lock. Also create the queue mutex in
pico_queue_protect(): creating it lazily on first lock can give the two
threads different mutexes.
[ci skip]
WritableChip::Save() wrote the image in place. A crash or a kill while
it is being written leaves a truncated file: the Dreamcast then boots
into the date and time setup with its settings gone, and an arcade
board loses its nvmem (service settings, high scores, unlocked content).
Write to a temp file next to the real one, flush it, then rename it
over the real path, so the file on disk is always either the previous
image or the complete new one. On Windows, rename() refuses to replace
an existing file, so flycast::rename() (which had no callers) now uses
MoveFileEx with MOVEFILE_REPLACE_EXISTING, matching POSIX.
Testing: compiles on macOS arm64; the Windows path is not built here.
Co-authored-by: Claude Fable 5.1 <[email protected]>
[ci skip]
0888 KRGB and 8888 ARGB, still went through the scalar per-pixel writer.
One packed pixel is exactly one VRAM word, so they fit the same 8-word
interleaved store as the 16-bit path, 8 pixels per iteration. The store
is factored out of storeFramebuffer16Pixels() so both use it.
888 (24-bit packed) stays scalar: its pixels straddle VRAM words, so the
store pattern only repeats every 4 pixels / 3 words.
The address walk is the same as writeFramebufferLW(), so the output is
identical to the scalar path, including when the padding isn't a
multiple of 4. A harness writing 7,056 frames through both paths (both
channel orders, modes 4 to 6, 6 frame sizes, 7 destination addresses
including bank and VRAM edges, 7 line strides, 4 clip windows) gives
byte-identical VRAM. The same algorithm measured about 2x faster on a
640x480 8888 frame (0.213 ms to 0.105 ms) on Apple Silicon.
tests/src/rend/FramebufferWriteTest.cpp checks both 32-bit modes against
an independent reference (one pvr_write32p per pixel, the scalar walk)
over 3,600 cases: widths around the 8-pixel vector width, padding
including values that aren't a multiple of 4, destinations at the 32-bit
bank boundary and the end of VRAM, and clip windows, for both channel
orders. All of VRAM is compared, so a store past the frame fails too. On
arm64 it exercises the NEON path; elsewhere the scalar one. It fails when
the padding computation is deliberately broken.
Other architectures are unchanged.
Co-authored-by: Claude Opus 5.5 <[email protected]>
[ci skip]
ENABLE_CTEST=ON never produced a test runner on macOS. The app's main()
(shell/apple/emulator-osx/SDLApplicationDelegate.mm) was still compiled
in, so the binary launched the emulator UI instead of gtest_main, and the
link needed libcurl, which HttpTest uses directly while the core only
links curl where it is the HTTP client (not on Apple).
- Leave SDLApplicationDelegate.mm, osx-main.mm and the Syphon video
routing out of the target when BUILD_TESTING is set, like linux-dist
main.cpp and winmain.cpp already are.
- Stub the os_* / darw_printf / hostfs / i18n glue those files provided,
in tests/src/test_stubs.cpp, for macOS.
- Link CURL::libcurl into the test target on Apple.
With this the suite builds with both the Xcode and the Ninja generators
and runs on Apple Silicon: 144 tests, 141 pass. The three that fail there
(AicaArmTest.LogicOpsTest, SDLControllerMappingTest.split_axis and
.reverse_axis) and an abort in ~maple_sega_vmu at process exit are
pre-existing and are not changed here. The CI workflow still runs the
tests only on Linux.
Co-authored-by: Claude Fable 5.1 <[email protected]>
[ci skip]
SDL_GameControllerButtonBind2 is a struct over a union whose first member
is `int button`. `bind{}` zero-initializes that member only, so
value.axis.direction holds whatever was on the stack. getBindForAxis()
assigns it only when the mapping has a '+' or '-' prefix or a '~'
suffix; for a plain "lefttrigger:a4" it is returned as read, and the
full-axis branch at the end tests it too. At -O2 on macOS/arm64 a
lefttrigger lookup came back with direction 1 instead of 0 and
"-lefty:-a1" with 1 instead of -1; SDLControllerMappingTest.split_axis
and .reverse_axis fail there. The value just happened to be 0 on Linux.
Set axis and direction to 0 before parsing the axis.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
MapleDevices[][] in maple_if.cpp holds shared_ptr<maple_device>s with
static storage, so the VMUs are destroyed when the process exits.
~maple_sega_vmu() clears the LCD on any attached maple link, which goes
through MapleLink::GetMapleLink() and locks MapleLink::Mutex, another
object with static storage, in another translation unit, that may
already be gone. With libc++ locking a destroyed mutex throws
("mutex lock failed: Invalid argument") and the process aborts; the
unit test binary exits with SIGABRT on macOS after every test passed.
On glibc the same lock happens to succeed, which is why Linux never
showed it. The Links table is read in the same state.
Give both to function-local statics that are allocated once and never
destroyed, so they outlive everything that can still reach them.
Co-authored-by: Claude Fable 5.1 <[email protected]>
[ci skip]
On ARMv7, AND, EOR, ORR, BIC, MOV, MVN, TST and TEQ with the S bit set
leave V unchanged. The AArch64 instructions the recompiler uses for them
(ANDS, BICS, TST) clear V, and storeFlags() wrote the host NZCV back as
is, so every flag-setting logical op cleared the guest's V. A signed
conditional branch (BVS, BGE, BLT, BGT, BLE) after such an op then went
the wrong way. The x64 recompiler already preserves V for these ops
(saveFlags(save_v_flag)); the interpreter does too.
[ci skip]
TST and TEQ had a save/restore under STRICT_MODE with a note that nobody
probably relies on it. That path was never built, and it did not cover
ANDS, EORS, ORRS, BICS, MOVS or MVNS.
Restore V after the op from the PSR that loadFlags() loaded before it,
in the same place the shifter carry is merged, for every logical op that
sets flags. Three instructions, only on those ops.
AicaArmTest.LogicOpsTest asserted exactly this (teq with C,V set must
keep V) and failed on arm64; it passes now, with the rest of the suite.
Co-authored-by: Claude Fable 5.1 <[email protected]>
readConst, writeConst, readt and writet shift the 32-bit guest address by
the whole tagged memInfo_ptr value (pointer | shift). Shifting a u32 by 32
or more is undefined, and GCC 16 targeting 32-bit ARM uses that: once the
pointer is known to be non-null (iirf > 31) it drops the offset, so
read32/write16 compile to a plain load/store at the region base. Every
memory access then hits offset 0, the BIOS trampoline copied to 8C0000E0
is lost and the console never boots (black screen, no sound).
Mask the shift with HANDLER_MAX, which is what x86 does in hardware and
what the code intends. No change in behaviour where the old code worked.
Co-authored-by: hitenmandalia <[email protected]>
[ci skip]
Received frames were passed to the RTL8139 from the network thread,
raising the ASIC interrupt there and racing SIIDRebuild() on the SH-4
thread. Queue them and deliver them from an SH-4 scheduler callback.
Sent frames were pushed into picoTCP from the SH-4 thread, but picoTCP
is built without PICO_SUPPORT_MUTEX so its device queue is unlocked.
Queue them and feed them to picoTCP from the pico thread before each
stack tick.
[ci skip]
Vulkan without VK_EXT_provoking_vertex, DX9 and DX11 take flat-shaded
colors from the first vertex, while on Dreamcast it's the last. For the
depth-sorted triangle list, setFirstProvokingVertex() copied each
triangle's last vertex colors onto its first vertex. But sortTriangles()
builds these triangles from strips, so neighbors share vertices:
tri0=(P0,P1,P2), tri1=(P2,P1,P3). P2 is tri0's source and tri1's
destination, and the list is processed in depth order, so one
triangle's flat color can overwrite another's before it's read. In
strips of three or more triangles, consecutive triangles even share
their first vertex. Flat-shaded translucent polygons then pick up a
neighbor's color, e.g. the player shadow decals in Tony Hawk's Pro
Skater 2 and Mat Hoffman's Pro BMX turn uniformly dark.
Each sorted triangle has its own three slots in idx, so rotate them
(a,b,c) -> (c,a,b) instead. The winding is unchanged and the provoking
vertex comes first, without touching shared vertices. Strips keep the
color copy, which is safe there.
Adds SorterTest, which fails on the old code and passes with this change.
Co-authored-by: Claude Opus 5.5 <[email protected]>
[ci skip]
When nvmem is disabled, termMappings() frees main RAM, VRAM, ARAM and
the Elan RAM but leaves memInfo_ptr unchanged. Each entry still holds
the old host base, so any guest access before the next remap returns a
pointer into freed memory.
Zero the table, as init() does. A null entry routes to handler 0,
readMemNotMapped/writeMemNotMapped, which only log.
Co-authored-by: Claude Opus 5.5 <[email protected]>
[ci skip]
- Bump action versions
- Improve ccache support
- Reformat files
- Replace chkfung/android-version-actions and mathieu-bour/setup-sentry-cli from android workflow
- Update MoltenVK to use the latest official tag
- Update BSD operating systems
Export only retro_* from the core, as the osx build does with
libretro.osx.def. Symbols the core exports bind to same-named ones
the frontend already loaded: RetroArch builds with their builtin
glslang made the Vulkan renderer crash in GlslangToSpv on its first
shader compile, and with a system glslang loaded (ffmpeg ->
libplacebo -> shaderc) 33 glslang entry points, zstd, zlib and
stb_image still went to those copies.
A frontend may size its savestate buffer once, as libretro.h allows,
but a state grows and shrinks with the devices plugged in and the
frames being drawn, and states then outgrow the buffer. Report the
system's memory regions plus the allowance GGPO makes for everything
else, which is constant for a game.
System SP gets NAOMI's allowance, plus the game's media if it has one:
the media is loaded into memory-backed RAM, so a state holds all of it.
[ci skip]