257 Commits
Author SHA1 Message Date
davey a14c02e2bd libretro: enable the Vulkan device extensions the frontend requires
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.
2026-11-04 18:34:34 +01:00
Flyinghead 802874e79d arm64 linux: get page size at runtime. Upload symbols to sentry 2026-11-02 14:59:56 +01:00
raph 1fac3d3fef libretro build fix 2026-11-01 18:38:49 +01:00
James 800d0ec2d8 Added arm64-linux build to CI (#2596)
* Added linux-arm64 build (aarch64) to CI
* Run unit tests on linux-arm64 build
2026-11-01 18:04:39 +01:00
Alex Stapleton 6a6038f910 picotcp: make room for more than ~1,000 timers (#2595)
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]
2026-11-01 18:04:12 +01:00
Alex Stapleton f86e4c6ea4 picotcp: don't connect a socket whose quad is already in use (#2594)
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]
2026-11-01 18:03:52 +01:00
Alex Stapleton 7b1df94c8e picoppp: don't terminate when shutting down a disconnected host socket (#2593)
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]
2026-11-01 18:03:33 +01:00
Alex Stapleton 973e569a7d picotcp: fix double free racing pico_enqueue() with the pico thread (#2592)
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]
2026-11-01 18:03:11 +01:00
flyinghead 13cfe43858 Crowdin Translations
[ci skip]
2026-11-01 18:02:41 +01:00
Flyinghead e778e529be save to temp file then rename over previous one when possible
[ci skip]
2026-11-01 18:00:35 +01:00
Flyinghead 2c83abcce4 libretro: add libjuice to combined lib for switch 2026-10-28 14:41:20 +01:00
Flyinghead 07535d22e8 ci: enable macOS build 2026-10-28 14:39:11 +01:00
Flyinghead 98ceceed1e aica: decrease the aica DMA xfer rate from 25 MB/s to 16 MiB/s
Fixes NCAA College Football 2K2, NBA 2K and NFL 2K right music channel
getting louder.
Issue #2525
2026-10-28 14:37:54 +01:00
Flyinghead 23d212f4f0 ci: disable macOS build 2026-10-27 11:26:55 +01:00
Flyinghead 917a7552f9 disable openmp on macos 2026-10-26 14:20:23 +01:00
Flyinghead 697d15a133 naomi: drop support for .lst files 2026-10-26 13:08:42 +01:00
Flyinghead 34e1211871 mem: return non-null value when reading ext. device (area 5)
Fixes NHL 2K2 freeze when pressing DPad right on Credits menu.
Issue #2535
2026-10-26 12:38:16 +01:00
Flyinghead cd89ae96ab aica: decrease the aica DMA xfer rate from 50 to 25 MiB/s
Fixes NCAA College Football 2K2 right music channel getting louder.
Issue #2525
Small ADPCM decoding optimization
2026-10-26 12:29:50 +01:00
scribam 01db8a203e ci: use openbsd 8.0 (#2587)
[ci skip]
2026-10-26 10:27:20 +01:00
Joe Mattiello 6d49796a15 flashrom: write the flash and nvmem saves atomically (#2579)
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]
2026-10-26 10:26:57 +01:00
Joe Mattiello 907d15155e rend: NEON for the 0888 and 8888 framebuffer write paths on arm64 (#2557)
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]
2026-10-26 10:26:27 +01:00
Givo e2f1484571 changed achievement draw shortcut to return false rather than true 2026-10-24 17:24:35 +02:00
Will fe58ee8c20 add the ability to hide achievement popups 2026-10-24 17:24:35 +02:00
Joe Mattiello 7c22066aa3 tests: build and link the unit test binary on macOS (#2575)
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]
2026-10-24 17:24:11 +02:00
Flyinghead 3a498b1cb5 initialize all scheduler ids to -1
[ci skip]
2026-10-24 17:18:03 +02:00
Flyinghead c52e6ccf56 bba: move frame queueing to net services. Call service to rx frame
New Service::sendEthFrame function to send frame to bba
Let net services implement queuing and synchronization
capcom: don't interfere when not in use
2026-10-22 16:32:34 +02:00
Flyinghead de7597f0bc picotcp: fix race condition in enqueue/dequeue 2026-10-22 15:01:18 +02:00
Flyinghead 5f35ec7d72 Merge remote-tracking branch 'origin/master' into dev 2026-10-22 14:13:58 +02:00
Flyinghead fa9d39a355 bba: skip deserialization if device doesn't exist
regression from 5543a95c4f
2026-10-22 12:36:23 +02:00
Flyinghead db065c5700 audio: put the RingBuffer cursors on separate cache lines 2026-10-21 18:24:07 +02:00
Flyinghead 620699865b Merge remote-tracking branch 'origin/master' into dev 2026-10-21 17:47:06 +02:00
Flyinghead a9bd1c74fd empty commit 2026-10-21 17:09:21 +02:00
Flyinghead 5543a95c4f bba: start polling only when needed. Increase poll interval to 1 ms 2026-10-21 16:54:14 +02:00
Flyinghead 78d5d09c1e ci: fix sentry-cli on android builds 2026-10-21 15:59:34 +02:00
Joseph MattielloandClaude Fable 5.1 31697241b7 sdl: initialize the axis direction in SDLControllerMappingParser
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]>
2026-10-21 12:09:24 +02:00
Joe Mattiello e9f4020c56 maplelink: keep the link registry alive through static destruction (#2577)
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]
2026-10-21 12:08:48 +02:00
Joe MattielloandClaude Fable 5.1 5eb52090ad arm7: keep the V flag across logical ops in the arm64 recompiler (#2576)
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]>
2026-10-21 12:07:57 +02:00
jutler 128e6ce29c addrspace: mask the shift count taken from the tagged pointer (#2574)
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]
2026-10-21 12:07:14 +02:00
Rich Stokes 38a3d83c77 bba: hand Ethernet frames over on the threads that own them (#2569)
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]
2026-10-21 12:05:47 +02:00
Joe Mattiello 19ed307f74 rend: rotate sorted triangle indices for flat shading instead of copying colors (#2553)
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]
2026-10-21 12:04:35 +02:00
Joe Mattiello d207cd9198 addrspace: clear the address map when its backing memory is freed (#2546)
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]
2026-10-21 11:19:28 +02:00
Flyinghead 1d356d5e55 android: fix swappyvk crash if activity is null
[ci skip]
2026-10-21 11:06:06 +02:00
scribam 32c2304459 ci: maintenance updates
- 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
2026-10-21 10:59:14 +02:00
davey 34ad8c8262 libretro: avoid exporting symbols on linux, android and the bsds
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.
2026-10-20 16:33:04 +02:00
Chris Dixon abfc411392 libretro: report a fixed savestate size for each system (#2561)
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]
2026-10-20 16:32:32 +02:00
Flyinghead 9981e84912 breakpad: dwarf5 reader fix 2026-10-20 15:51:14 +02:00
Flyinghead b5acd1b533 wccf: card reader fix. Player names and images for 331j and 400j
Fix card reader error on reinsertion with 116, 212e and 322e.
Add support for player names and images for 331j and 400j
2026-10-20 15:50:07 +02:00
github-actions[bot] 6bb0fc3e18 Fetch translations & Recreate libretro_core_options_intl.h 2026-10-17 00:25:56 +00:00
Joseph MattielloandClaude Opus 5.5 08afcbe6b9 rend: clamp xBRZ texture upscale factor to 2..6
xbrz::scale() only implements factors 2 to 6. Any other value hits an
assert that's compiled out in release builds, and scale() returns
without writing the destination buffer. The texture is then uploaded
from uninitialized memory at the requested size.

rend.TextureUpscale2 can be set to 8 or 16 through emu.cfg or Lua.
Clamp the factor once and use it for the buffer size, the scale call
and the upscaled dimensions so they always agree.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-16 20:14:45 +02:00
Joe Mattiello e9dd6187cb cdipsr: make temp_value a local (#2549)
temp_value was a file-scope global shared by every CDI parse. The boxart
scraper opens discs on its own thread, so it can parse a CDI while a
game is loading and corrupt the other parse's reads.

Use a local in each function. It's a uint32_t initialized to 0 because
every read into it is 4 bytes; a local unsigned long would leave the top
half uninitialized on LP64.

Co-authored-by: Claude Opus 5.5 <[email protected]>
[ci skip]
2026-10-16 20:13:29 +02:00
Joe Mattiello a5dc811814 savestate: reject invalid broadcast/cable/region instead of aborting (#2555)
dc_deserialize() read the broadcast, cable and region settings straight
into the config and checked them with verify(), so a corrupt or
incompatible state killed the process, after already writing the bad
values into the settings.

Read them into locals and throw Deserializer::Exception when one is out
of range. dc_loadstate() and the libretro core already catch it and
report the state as unusable. Valid states load exactly as before.

Co-authored-by: Claude Opus 5.5 <[email protected]>
[ci skip]
2026-10-16 20:12:36 +02:00
Joe Mattiello 5c86428c04 input: stop the emulator while saving an in-RAM savestate (#2554)
The in-RAM save hotkey called gui_saveState(false, true), which runs
dc_serialize() without stopping the emulator. With threaded rendering
on (the default), the SH4 and AICA keep running on the emulator thread
while the input thread serializes, so the state can be torn: memory and
registers from different moments.

Stop and restart around the save like the disk save hotkey and the
in-RAM load hotkey already do.

Co-authored-by: Claude Opus 5.5 <[email protected]>
[ci skip]
2026-10-16 20:11:22 +02:00
Chris Dixon 258f5c11d4 libretro: forget the LED callback on deinit (#2560)
The callback was only ever taken once. A frontend that loads a fresh
instance of itself for the next game, as Kodi does, keeps the core
loaded, so the next GD-ROM light change called into the unloaded one.
[ci skip]
2026-10-16 20:09:21 +02:00
Flyinghead 91c3084d52 imgread: add 2336 to 2340 bytes sector conversion as used by Bleemcast 2026-10-14 13:26:57 +02:00
Flyinghead 506b4efd04 capcom: use peer-to-peer raw mode for in-game communication
Let peers exchange V.42 frames instead of implementing the protocol
locally so that error correction works end to end.
2026-10-14 13:20:12 +02:00
Garrett Brown 80471d57b0 libretro: define the unique texture dumping option
Add the missing dump_unique_textures core-option definition beside the
existing texture-dump options. This prevents findDefinition() from
returning null when the frontend supplies a value for this option.

Default to disabled to match the implicit false config default, and use
the existing setting description for skipping video and animated textures.
2026-10-13 11:28:52 +02:00
Chris Dixon ce8689101f libretro: look for the NAOMI BIOS through the frontend VFS (#2542)
findNaomiBios checked for the BIOS archive with access(), so a frontend
watching its VFS never saw it being looked for, unlike findFlash, which
already uses path_is_valid. Kodi uses that to name the file a core is
missing.
[ci skip]
2026-10-13 11:28:29 +02:00
Flyinghead 518c496251 sh4: set Sh4Cntx address on Reset
RAM base and Sh4Cntx address might change on libretro without Init being
called. So set the correct Sh4Cntx address on Reset and propagate to
dynarec, cycle counter and caches.
[ci skip]
2026-10-13 11:25:52 +02:00
Flyinghead 804edb00d7 dx11,gl: fix clear framebuffer and scissor regressions
dx11: don't use scissor rect for final A-buffer render
gl4: clear geom_fbo[1] (color pass) not geom_fbo[0] (depth pass) when
needed
Issue #1197
2026-10-13 11:14:45 +02:00
Flyinghead f03faffd2f libretro: reset disk_index to 0 when loading a game
Don't use the static disk_index in read_m3u()
Issue #2541
2026-10-12 09:46:45 +02:00
Flyinghead fa23eef593 multiboard: avoid crash when raw input is enabled
Fixes MINIDUMP-A9F
2026-10-12 09:44:10 +02:00
Flyinghead 658abb386a maple: don't crash on invalid maple device. Just ignore it
Fixes MINIDUMP-A9C
2026-10-12 09:42:20 +02:00
Jordi Serra fa3c526e4f ggpo: resolve the peer host name (#2514)
GGPO parses the peer address with inet_pton() only, so a host name in
the Peer field silently becomes 0.0.0.0 and the session never syncs,
without any error. Resolve non-numeric addresses with getaddrinfo()
before handing them to GGPO, and fail with a clear message when the
name cannot be resolved.

Co-authored-by: unjordi <[email protected]>
[ci skip]
2026-10-12 09:34:50 +02:00
Jordi Serra e6d3365c0b Fix MD5Sum hashing hostfs::File pointers instead of file contents (#2513)
Since the switch to hostfs::File (#2299), MD5Sum::add(hostfs::File *)
resolved to the generic add(const T&) template, which hashes the pointer
value. The resulting digests depend on the heap address, so they differ
between peers and between runs. This breaks GGPO peer verification when
a .state.net savestate is used (stateMD5), and with CDI, GDI and CUE
images (gameMD5).

Add an MD5Sum::add(hostfs::File *) overload that hashes the whole file
content, like the std::FILE overload, and reject pointer types in the
template with a static_assert so this cannot happen again silently.

Co-authored-by: unjordi <[email protected]>
[ci skip]
2026-10-12 09:33:48 +02:00
Flyinghead 7422d1b831 wccf: rename ENTER button to PRESSURE 2026-10-12 09:32:19 +02:00
Flyinghead 6dafd41eaa capcom: wrong battle code on win32. fix spawn and hmgeo end of match
An overflow was setting the short battle code to 7fffffff on win32 in
50% of cases.
Fix spawn and hmgeo end of match freeze by returning 0 if the connection
is closed.
2026-10-12 09:25:01 +02:00
github-actions[bot] ed953fcbfd Fetch translations & Recreate libretro_core_options_intl.h 2026-10-10 01:44:50 +00:00
Flyinghead ec56b5f260 sh4: remove penalty cycles for MMU memory access
Issue #1058
2026-10-09 18:49:02 +02:00
Flyinghead bc3b8c71da capcom: support for sf3 MF 2000 v1 and v2. modem hang up
net service throw when connection is closed. Modem catches it and hang
up.
netservice: call custom service start() and stop() like others.
ice: init http client before using it
capcom: send stop message when one end is closed. detect hanged resync
and retry them.
capcom: support sf3 M.F 2000 v1 and v2
2026-10-09 18:46:18 +02:00
Flyinghead 0d9853df9a dcnet: use ipv4 address to select AP 2026-10-07 13:34:54 +02:00
Flyinghead df7aedf506 cue: recheck track file handle
Fixes MINIDUMP-A8T MINIDUMP-A8V
2026-10-07 13:32:45 +02:00
바닷빛andClaude Opus 5 df5feec55c build: pass /utf-8 to tinygettext so non-UTF-8 locales can build (#2532)
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>

[ci skip]
2026-10-07 13:30:36 +02:00
바닷빛andClaude Opus 5.5 bbdef06a09 sh4: clear the FPSCR cause field when the guest writes FPSCR (#2531)
On the SH4 every FPU instruction clears the FPSCR cause field before it
executes, and sets a cause bit only if it raises an FPU exception.
Flycast doesn't update the cause field in FPU instructions, so a cause
bit that the guest writes back with lds/lds.l stays set forever.

Kita He. White Illumination (T14301M) resets to the BIOS when a karaoke
song starts (issue #1058). TRFDRAW.DLL inverts a 4x4 matrix with an LU
decomposition whose pivot comparison helper saves FPSCR, runs fcmp/gt and
reads FPSCR back: a set cause.V means an unordered (NaN) comparison. With
a stale cause.V every comparison looks unordered, no pivot row is chosen,
and the uninitialized pivot index makes the routine read a wild pointer.
The access violation is caught by the game's __except handler, which
calls ResetToFirmware().

Clearing the cause field whenever FPSCR is written matches what the
hardware shows to code that reads FPSCR after an FPU instruction.

Co-authored-by: Claude Opus 5.5 <[email protected]>

[ci skip]
2026-10-07 13:29:53 +02:00
Rich Stokes b4c38d652e input: rebase absolute mouse motion after relative input (#2528)
[ci skip]
2026-10-07 13:27:42 +02:00
Flyinghead 4dca1988da capcom: fix battle code mem location in several games 2026-10-05 14:45:15 +02:00
Flyinghead 552a86086b win32 and switch build fixes 2026-10-04 12:44:39 +02:00
Flyinghead 9ec8b7e658 Merge remote-tracking branch 'origin/master' into dev 2026-10-04 11:56:16 +02:00
Flyinghead 7d69c9cfd4 capcom: use libjuice for in-game p2p connection
fix capcom vs SNK pro battle code address
2026-10-04 11:51:01 +02:00
Nejsplitter 5ff3f974d9 Add stretch to fill screen option (#2515)
[ci skip]
2026-10-04 11:45:46 +02:00
flyinghead f83fda7069 New translations strings.xml (Spanish)
[ci skip]
2026-10-03 11:12:01 +02:00
github-actions[bot] 59ed35a7ea Fetch translations & Recreate libretro_core_options_intl.h 2026-10-03 01:09:55 +00:00
scribam 04669ebe41 ci: fix libusb1 installation on netbsd 2026-10-01 11:32:26 +02:00
nixxou 98ce9fb5d7 Keep spaces inside -config section names (#2516)
Game ids can have spaces, like "INITIAL D Ver.2". The parser removed all
spaces in the section name so it became "INITIALDVer.2". Now only the
spaces before and after the name are removed.

Co-authored-by: nixxou <[email protected]>
[ci skip]
2026-10-01 11:31:59 +02:00
Acts1631 2a57c9ffd9 savestate: validate fixed buffer lengths (#2512)
[ci skip]
2026-10-01 11:31:15 +02:00
Acts1631 2ea7920fdc network: bound DCNet Ethernet frame sizes (#2511)
[ci skip]
2026-10-01 11:31:00 +02:00
Acts1631 9d4c337349 network: validate GGPO application data packets (#2510)
[ci skip]
2026-10-01 11:30:37 +02:00
vkedwardli 89b72a8e3e windows: fix HTTP URL parsing race and GET status handling (#2509)
Use per-request URL buffers for GET and POST to prevent concurrent requests from overwriting each other's host and path.

Return the actual HTTP status for GET requests, retaining an error status when sending the request or querying its status fails
[ci skip]
2026-10-01 11:30:16 +02:00
Flyinghead fe04dd6503 modem: answer mode. network: capcom direct support
modem: implement V42 answerer mode
modem: add callbacks for dial out, answer mode, start handshake, etc.
network: detect capcom direct games and simulate the peer-to-peer connection
2026-09-29 22:08:43 +02:00
flyinghead b1b80d8849 Crowdin Translations (Chinese Simplified)
[ci skip]
2026-09-29 22:07:45 +02:00
Richard Stokes e36e9df2dc vk oit: fix synchronization hazards between render passes and frames
The OIT color and depth/stencil attachments, a-buffers and pixel counter
are shared by consecutive render passes and by all frames in flight, but
the render pass had no external dependency covering them, and the pixel
counter reset wasn't synchronized with the fragment shaders using it.

With MoltenVK using Metal 3 argument buffers and residency sets (macOS 15+),
Metal no longer tracks hazards on resources accessed through argument
buffers, so input attachment reads and storage buffer accesses of a frame
may race with the attachment clears/loads and a-buffer writes of the next
one. This results in sporadic one-frame glitches with per-pixel alpha
sorting.

Issue #2495
2026-09-28 13:49:06 +02:00
github-actions[bot] ea087b9140 Fetch translations & Recreate libretro_core_options_intl.h 2026-09-26 00:36:31 +00:00
Flyinghead 869038f40a scif: use scheduler only after it has been deserialized
Calling sh4_sched_request before will not use the correct current time.
Fixes barcode reader malfunctioning after loading a state (mushiking,
dinoking, etc.)
Issue #2501
2026-09-23 12:01:56 +02:00
flyinghead 0a304f1396 Crowdin Translations (Simplified Chinese, Swedish) 2026-09-22 15:42:21 +02:00
Richard Stokes 628bd3dbb1 macOS: queue document opens with an SDL user event
sdl2-compat (what Homebrew installs as sdl2) doesn't convert the file
name of an SDL_DROPFILE event pushed by the application, then crashes
in strdup(NULL) when converting the event back during window creation.
Carry the path in a registered user event instead.
2026-09-20 20:35:12 +02:00
Flyinghead 2ac5c772f0 vk: ends rendering before clearing texture cache
Fixes validation errors when deleting textures used by the current
command buffer.
2026-09-19 15:24:45 +02:00
Flyinghead fff68b3f57 gdrom: fix regressions
Pause wasn't working. Bust-a-Move 4 puzzle collection music was missing.
Issue #786
2026-09-19 15:21:25 +02:00
Flyinghead e6707469d2 gdrom: improve SecNumber.Status handling with timer
Fixes NFL Blitz 2001 freeze on matchup screen. Issue #2488
Fixes Ms. Pac-Man spurious CDDA pause. Issue #786
Fixes Cool Boarders Burrrn load freeze with real BIOS. Issue #1073

Move scheduler state serialization to respective modules.
2026-09-18 16:45:41 +02:00
Flyinghead a99c8667b4 vk: invalid attachment 1 initial layout when rttCopy2vram is on 2026-09-18 15:19:52 +02:00
Flyinghead 49ad26a7e5 change default log level from debug to info. compilation warnings 2026-09-18 15:15:47 +02:00
Flyinghead da5be1a0a7 Merge remote-tracking branch 'origin/master' into dev 2026-09-18 14:56:43 +02:00
Flyinghead dd7a5f0620 target android 16. Fix Back invoked handler 2026-09-18 12:12:07 +02:00
Flyinghead 6d73f7191c minor changes (error logging) 2026-09-18 12:09:27 +02:00
Ryan McClelland 215a5af165 vmem: ignore the heap pointer tag when matching a fault address
On arm64 Android, apps targeting API 30 or later get tagged heap pointers:
malloc returns addresses with a tag in the top byte, while the fault address
a signal handler receives has that byte cleared. Without nvmem, RAM, VRAM,
AICA RAM and Elan RAM are malloc'd, so the range checks in bm_getRamOffset(),
getVramOffset() and the memwatch watchers never matched a fault on a
protected page, and the first write to a page holding compiled code killed
the core as an unhandled SIGSEGV.

Compare untagged addresses instead. The vmem branches are unchanged: that
memory is mmap'd and carries no tag.

The libandroid link from 8933f9fe0 is still wanted, since it is what lets
ASharedMemory_create resolve and nvmem work, but its subject blamed the
dynarec; this was the crash.
2026-09-16 16:01:28 +02:00
Rich Stokes c0fb036d60 Fix stalled uploads in the picoTCP proxy (#2497)
[ci skip]
2026-09-16 16:01:05 +02:00
Ryan McClelland 8933f9fe0c libretro: link libandroid, or the dynarec dies on the first memory access
flycast's libretro build has never run on Android 11 or later. It loads,
reaches frame one, and then takes a SIGSEGV its own handler cannot place:

    [VMEM] Virtual memory file allocation failed: errno 13
    [VMEM] Warning! nvmem is DISABLED
    [COMMON] SIGSEGV @ 0x71d0c87618 invalid access to 0x71ce6cba00

errno 13 is EACCES. posix_vmem.cpp asks ASharedMemory_create first and
falls back to opening /dev/ashmem when the symbol is null -- and Android
11 took that device away from apps. So the fallback is refused, nvmem is
disabled, and the dynarec's fastmem accesses have no mapping behind
them. rewrite() cannot place the fault, and the handler falls through to
die("segfault").

The good path was there all along and was dead code, because the symbol
is declared weak:

    extern "C" int __attribute__((weak)) ASharedMemory_create(const char*, size_t);

and nothing in the LIBRETRO branch links libandroid, so it resolved to
null every time. readelf on the built core shows only libm, libdl and
libc in NEEDED, and nm shows ASharedMemory_create as an undefined weak.
The app build links `android log` in its own ANDROID block further down;
the libretro build never did.
2026-09-15 11:29:00 +02:00
Rich Stokes 4509ee903c macOS: defer document loading until emulator initialization finishes (#2492)
Route document opens through SDL's event queue
[ci skip]
2026-09-15 11:28:32 +02:00
Flyinghead 04b74c3a11 gdrom: stop cdda when reading a sector
Revert the revert (9869ea8852) to fix
Hydro Thunder. Also needed for NFL Blitz 2001.
Issue #2488
[ci skip]
2026-09-15 11:24:09 +02:00
Edward Li 36c975e391 ci: package artifacts before upload and verify macOS signatures 2026-09-13 15:22:03 +02:00
Flyinghead bcad2d76d0 naomi network: actively refuse connections when max nodes is reached
Issue #2402
2026-09-13 15:19:45 +02:00
Flyinghead 9869ea8852 gdrom: don't stop cdda when reading a sector
Revert dcf5444acc
Issue #2488
2026-09-13 15:12:12 +02:00
chyyran ea22ee8723 lua: expose openGdrom and insertGdrom as lua functions 2026-09-12 13:37:53 +02:00
vkedwardli b777c15fdc docs: restore Homebrew installation via the Flycast tap (#2486)
Document install commands for stable, master, and nightly dev channels.
Link to tap instructions and adjust installation section formatting.
[ci skip]
2026-09-12 13:15:43 +02:00
vkedwardli 93379c489f macOS: backport MoltenVK fixes and verify packaged artifacts (#2485)
[ci skip]
2026-09-12 13:14:52 +02:00
Flyinghead b03335bca9 pvr: swap interval detector improvements
Don't switch to swap interval 2 if actual interval is 3 or more. Fixes
ECCO switching to swap 2 during loading.
Switch to interval 1 when slightly above 30 FPS for too long. Helps
variable refresh rate games (Unreal) and those rendering close to 30 FPS
(ECCO, Armada).
Issue #2467
[ci skip]
2026-09-12 13:14:20 +02:00
github-actions[bot] 604ebd58fe Fetch translations & Recreate libretro_core_options_intl.h 2026-09-12 00:14:46 +00:00
Flyinghead eddf263586 windows: fix http request with path > 256 chars
The path of http requests was limited to 256 chars on Windows, which
isn't enough for some TGDB gets.
Issue #2455
Issue #2470

Only save the database when needed. Better handling of TGDB allowance
limit. Pause if no progress.
2026-09-11 13:37:37 +02:00
Flyinghead 3f70e47adb aica: store last sample and Q at loop start even with ADPCM stream
When reaching LSA, the last sample and Q values should be stored, even
with ADPCM long stream (which doesn't use them). NBA 2K switches a
looping sound from ADPCM long stream to ADPCM and relies on the loop
start values to be restored.
Issue #2469
2026-09-11 10:39:31 +02:00
Romain TISSERANDandClaude Opus 5 0abac3465d chd: MIL-CD support for images with pregaps
CHDs converted from Redump MIL-CD dumps were rejected with "Unsupported
subtype or pre/postgap": the audio tracks of the first session carry their
pregap inside their own .bin file, which chdman records as PREGAP:n
PGTYPE:V<type> in the CDROM_TRACK_METADATA2 entry.

Handle these tracks the way MAME does:
- when PGTYPE is prefixed with 'V', the pregap sectors are part of the track
  data and counted in FRAMES, so INDEX 01 sits `pregap` frames after the
  previous track and the track data starts `pregap` frames into the CHD
- otherwise the pregap only takes up space on the disc
- subcode data doesn't change the sector layout, and postgap sectors are
  never stored: warn about both instead of bailing out

Also:
- audio tracks of CD-ROM CHDs are stored big-endian, just like the ones of
  GD-ROM CHDs using the CHGD metadata tag. Not byte-swapping them was the
  cause of the audio distortion reported with CHD MIL-CDs
- the 11400 frame session gap includes the pregap of the second session's
  first track. Don't count those sectors twice when they're in the image
- only treat a multi-track disc as a MIL-CD when its last track is a data
  track. A mixed mode CD is a single session disc and its data track is the
  first one
- report the lead-out as the first FAD after the last sector, like the CUE
  and GDI loaders do, instead of one frame earlier

Tested on a dozen homebrew MIL-CDs, from 2 to 17 tracks: the CUE and CHD
loaders now return the same tracks, sessions and sector data for each of
them, and they all boot.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01NfFd8rJc9aTQB7au8yLrqG

* cue: don't count the session pregap twice in MIL-CDs

The 11400 frame gap added at REM SESSION 02 is the lead-out of the first
session (01:30:00), the lead-in of the second one (01:00:00) and the pregap
of its first track (00:02:00). When that track has an INDEX 00 of its own,
its pregap sectors are stored in the image and were counted a second time,
placing the data track 150 frames after the session start reported in the
TOC, i.e. after the FAD the boot file is looked up at.

Redump lays MIL-CDs out with no INDEX 00 on the second session's track, so
none of the images I have hits this. It only shows up with images written by
other tools, and is included here because the CHD loader needs the same
adjustment to agree with this one.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01NfFd8rJc9aTQB7au8yLrqG
2026-09-10 11:14:28 +02:00
Flyinghead a9b451c6f2 ini: check if string empty before accessing 1st element
This is UB with c++11.
[ci skip]
2026-09-10 11:12:12 +02:00
Flyinghead 6838fbd642 naomi: fix wccf card reader state when ejecting/loading card 2026-09-08 16:12:53 +02:00
Flyinghead 34cd222e12 vk: use FIFO present mode for all gpus and platfoms
Mailbox mode was used on desktop with NVidia GPUs before, which may
cause some frames to be lost.
Issue #732
2026-09-08 16:06:33 +02:00
WizzardSK fb286f777c cmake: keep the Switch archive's extracted objects apart
Since #2477 put zstd in the combined archive, the libnx job fails at the
final link with `undefined reference to XXH32` - the symbol went missing
from an archive that extracts xxHash first.

ar names a member after the basename of its object, and zstd builds its
own vendored copy of xxhash.c, namespaced to ZSTD_. Both archives
therefore carry a member called xxhash.c.o, and extracting them into one
directory leaves only the second: zstd's, which defines ZSTD_XXH32 and
nothing the core calls.

Extracting each archive into a directory of its own keeps every object,
whatever it is called. Reproduced and checked with a CMake project of the
same shape, two static libraries whose members collide:

  flat, as now:        core_main, ZSTD_XXH32
  a directory each:    core_main, ZSTD_XXH32, XXH32

Not run on the buildbot - the Switch toolchain is not something I have
here.
2026-09-08 08:46:23 +02:00
Flyinghead 0ff538115f vk: fix deprecation warning 2026-09-07 20:24:29 +02:00
flyinghead 5aca173e80 New translations flycast.pot (Chinese Simplified)
[ci skip]
New translations for {languageName}
2026-09-07 19:17:02 +02:00
Flyinghead 2f0917bb7b ui: change Chinese (Traditional) language name
[ci skip]
2026-09-07 19:15:59 +02:00
Flyinghead f2013c652a pvr: reset PCW::Texture if texture is invalid. Fix Xtreme Sports shadows
Handle the case where MVO list are shared between OP and TR but the TR
list is the one to use. Fixes Xtreme Sports shadows. Issue #2479
Reset PCW::Texture to 0 if texture is invalid (hence null) so renderers
don't have to worry about it.
2026-09-07 19:12:47 +02:00
Flyinghead 230d83654f vk: use dynamic rendering local read to implement 2ndary accumulator
Secondary accumulator fixes BANG! Gunship Elite, Evil Dead, Space
Channel 5 part 2 and WCCF in non-OIT mode.
Bind dummy imageView/buffer if a descriptor set doesn't use a slot. This
avoids referencing old resources and fixes validation errors when those
resources are deleted.
oit: lighter headers. Also bind dummy imageView/buffer where needed.
2026-09-07 18:58:08 +02:00
WizzardSK fa04b20837 cmake: the Switch archive was missing zstd and tinygettext 2026-09-07 14:46:25 +02:00
WizzardSK 52c8ecef1e gitlab-ci: the libnx image's cmake is too old for this tree 2026-09-06 16:03:29 +02:00
flyinghead d3c90b0f84 Crowdin Translations (Chinese Simplified) 2026-09-06 15:00:19 +02:00
kihato fe4cf1d586 Traditional Chinese was missing from the UI language drop-down menu (#2473)
[ci skip]
2026-09-06 14:59:53 +02:00
kihato 08ffbe6cf1 Additional games that do not support RGB output (#2472)
[ci skip]
2026-09-06 14:59:30 +02:00
Rubén Tomás b35b829a12 rend: optimize ARM64 framebuffer paths (#2452)
Add NEON framebuffer conversion and VRAM write paths, and validate the physical VRAM footprint before vector loads so both 8 MB and 16 MB configurations remain in bounds.
[ci skip]
2026-09-06 14:59:02 +02:00
WizzardSK a75d1cff53 gitlab-ci: build the Switch core's combined archive (#2471)
The libnx template builds --target ${CORENAME}_libretro, and on Switch that
target is flycast's own objects and nothing else - 251 members. The
dependencies (xxhash, chdr, zip, elf, the resources) are folded in by the
"combined" target that CMakeLists.txt defines for exactly this case, and
that is the archive RetroArch has to link: 409 members.
[ci skip]
2026-09-05 16:36:55 +02:00
flyinghead 62d7a0432a Crowdin Translations (Chinese Traditional) 2026-09-05 10:19:51 +02:00
Flyinghead 066c82ddfd ui: fix dreamcast bios displayed on disk eject. Game filter field
Fix dreamcast BIOS boxart displayed when ejecting a disk. Issue #2468
Show game filter field on all platforms. Issue #2417
[ci skip]
2026-09-05 10:19:07 +02:00
Flyinghead 1145248aa7 save states: drop support for old save states
First version supported is now V42 (release v2.2 2023/10/23)
netdimm: add full network dump (disabled)
2026-09-05 09:28:35 +02:00
WizzardSK daa46ec63e fix for switch (#2466)
uncomment build-static
[ci skip]
2026-09-05 09:11:55 +02:00
github-actions[bot] 9814e2fe40 Fetch translations & Recreate libretro_core_options_intl.h 2026-09-05 00:01:10 +00:00
Aldo Borrero d8a76809e5 OpenGL: fix per-frame VAO leak in the ImGui GL3 backend
ImGui_ImplOpenGL3_SetupRenderState generated the VAO into its
`vertex_array_object` parameter, which is passed by value, so the name never
reached ImGui_ImplOpenGL3_RenderDrawData. There the local was still 0, the
`if (vertex_array_object != 0)` guard was false, and glDeleteVertexArrays never
ran — a VAO was leaked every frame. The OSD is drawn every frame, so on Mesa
(radeonsi) host memory grows unbounded until OOM; Vulkan is unaffected.

Move the glGenVertexArrays back into RenderDrawData and pass the handle to
SetupRenderState for binding only, so the existing glDeleteVertexArrays runs
and the VAO is recreated each frame (staying valid across GL context changes),
matching the upstream ImGui backend.
2026-09-04 09:48:47 +02:00
WizzardSK fd9d39b127 ci: build the Switch core here, with CMake (#2465)
The libretro Switch core on the buildbot still comes from the old
libretro/flycast tree, because the job here was written against the
Makefile-based libnx-static template and this tree has no Makefile. There
is a CMake version of that template, and the CMakeLists already knows how
to build for Switch (NINTENDO_SWITCH + LIBRETRO produces
flycast_libretro_libnx), so point the job at that instead.
[ci skip]
2026-09-04 09:47:52 +02:00
Flyinghead cc4a59b1f2 mmu: restrict wince hacks to wince games. Fix full mmu !wince states
Add windowsCE flag to gate windows CE-specific hacks: system calls
interceptions, page fault handler, memory slot #0 flush.
Detect mmu usage when loading a state and add UTLB entries to cache.
Fixes mario 64 savestates.
2026-09-03 18:32:14 +02:00
Flyinghead 8f511f8960 Remove macOS Homebrew from Install section
Issue #2461
[ci skip]
2026-09-03 16:11:23 +02:00
Flyinghead 9bdb0ad323 fix test 2026-09-03 15:58:04 +02:00
Stefan Schlosser 5c96b69ff0 Turn USE_DREAMLINK_DEVICES into build option
Turning the USE_DREAMLINK_DEVICES variable into a build option (enabled
by default) provides a convenient way to exclude building the
DreamPicoPort-Api dependency, which could use newer libusb APIs than are
available on platforms which provide their own libusb implementation,
i.e. mostly the BSDs.

For example, the newest DreamPicoPort-Api uses libusb_wrap_sys_device,
which isn't available on FreeBSD < 15. Building flycast with an updated
DreamPicoPort-Api on the still supported legacy FreeBSD 14 would need to
disable this option.

The build option needs to be forced off when targeting Android, iOS,
Switch or LibRetro, to keep the same build behavior from before this
change.
This also allows to unnest the configuration block to the top-level.
2026-09-03 15:23:35 +02:00
Flyinghead 7db29cc2fd pvr: improved framebuffer writes detection
Fixes Sega Rally (JP) network menus.
Fixes Ikaruga and Armada loading screens. Issue #1189.
Fixes VF3tb history mode videos in NTSC and VGA mode.
Don't check for framebuffer writes when writing the framebuffer to vram
(Full FBE)
[ci skip]
2026-09-03 15:23:17 +02:00
Flyinghead 162bc02007 minor changes
Catch/rethrow xbyak exceptions in arm7 and sh4 dynarecs. Fixes
MINIDUMP-8P9
naomi: code to generate random main board serial id (not used yet)
Don't log warning for sh4 trapa op.
texcache: get rid of unused FBLineWriter argument.
2026-09-03 14:54:11 +02:00
ShinMikite 12bb43652f Fix Monkey Ball minimap depth masking 2026-09-02 18:10:29 +02:00
Flyinghead 71b739faee android: tentative fix for ANR when calling eglDestroyContext
Flush and finish the GL pipeline.
Release the ANativeWindow for both GL and VK.
[ci skip]
2026-09-02 18:09:57 +02:00
Flyinghead 57e33d323a ui: fix regression where dpad won't scroll up/down the game list
Likely caused by an imgui change
Issue #2455
2026-09-02 17:52:39 +02:00
Florin9doi b4d7e75977 Re-connect the jump pack for the fishing controller 2026-09-01 14:26:21 +02:00
Flyinghead 58b283fc02 wccf improvements
Act as left projector and start right instance when MultiboardSlaves=2
netdimm: fix vf4/vf4efo regression. Get rid of final tuned sync socket
hack not needed anymore. Ignore netdimm server reboot.
Set node number in eeprom for left and right projectors.
ccd cam: don't crash if http server can't start
[ci skip]
2026-09-01 14:25:56 +02:00
Flyinghead 149e651d24 android: don't start swappy if g_activity is null
Probably not the right fix but should mitigate the issue
2026-08-31 08:17:01 +02:00
Flyinghead c37be698db android: fix start up crash on android <= 8 2026-08-31 08:10:46 +02:00
Flyinghead 453dcefc12 wccf: fix card expiration/resign. Manager data transfer support
Emulate 2 cards in reader.
Save and update card write counter.
Detect expired card and insert a blank card with it to allow data
transfer. Replace existing card with new one when saving.
2026-08-30 15:33:41 +02:00
Flyinghead ce3abe5a0b Merge remote-tracking branch 'origin/master' into dev 2026-08-30 15:27:20 +02:00
Flyinghead e2722869ff The Grinch (US) doesn't support RGB output
Issue #2447
2026-08-30 09:24:17 +02:00
RTA bbe606c8f7 gles: enable OIT with OpenGL ES 3.1
Request and propagate explicit OpenGL ES 3.1 contexts in libretro. Add the image-atomic and shader-storage support needed by OIT, validate per-pixel resource limits, and improve A-buffer synchronization and framebuffer buffer reuse.
2026-08-28 14:55:30 +02:00
Flyinghead 5803d8aa14 wccf cam: fix card selection (comm menu). 2k1/2k2/2k4 support
Moving a card now registers as a selection in communication menu.
Changed pitch coordinates to match real hw.
Support 2k1, 2k2 (eu) and 2k4 (eu) for player names and pictures.
[ci skip]
2026-08-28 14:55:03 +02:00
Acts1631 180e670605 Reject unterminated quoted disc filenames
CUE and GDI descriptor parsing continued appending after a quoted
filename reached end of input. A malformed descriptor could consume
CPU and memory until Flycast stopped responding.

Treat failed character extraction as an invalid descriptor while
skipping whitespace and while scanning quoted filenames.
2026-08-27 19:05:33 +02:00
flyinghead f831592f64 Translations: Spanish, Simplified Chinese 2026-08-24 14:42:11 +02:00
Flyinghead ba04273a25 Add turkish and chinese simplified language choices
[ci skip]
2026-08-24 14:40:54 +02:00
Flyinghead 804fcb4a9e texconv: get rid of vq and pal globals. wccf: player card pics endpoint
texconv: Get rid of ::vq_codebook and ::palette_index globals.
wccf: New http endpoint to download player cards.
[ci skip]
2026-08-24 14:34:45 +02:00
Flyinghead c3763d8fc4 sdl: in multiboard mode, handle fullscreen switches locally
Switching to fullscreen and back should be handled by the slave, not
routed to the master like other key presses.
Issue #2439
2026-08-23 13:29:03 +02:00
Flyinghead 9473c0377a android: clear knownDevices when InputDeviceManager starts
Issue #2440
2026-08-23 13:20:05 +02:00
Flyinghead 9c6c8e59d6 atomiswave: reduce bios size to 64KB as the rest is writable
Fixes regression introduced by 8412d3d8a3
that overwrites the loaded .nvmem data with the bios second half.
2026-08-23 13:18:08 +02:00
Flyinghead d367284ddf another msvc build fix 2026-08-22 18:22:09 +02:00
Flyinghead 105f760613 switch and msvc build fixes 2026-08-22 18:11:20 +02:00
Flyinghead 8c5446706e wccf: team pitch api web server
Run REST api to manage player card positions with web server.
Load/save player positions in wccf420e.zip-team.json file.
2026-08-22 17:20:44 +02:00
github-actions[bot] 7fcd6976fa Fetch translations & Recreate libretro_core_options_intl.h 2026-08-21 22:38:44 +00:00
Flyinghead 982f26a0da netdimm: always return back the correct dimm command
Tentative fix for WCCF freezes
2026-08-21 17:17:38 +02:00
Flyinghead d5e74ad756 vk: fix RTT initial color attachment layout
Fixes Miss Moonlight color attachment layout mismatch error.
2026-08-20 15:18:20 +02:00
Flyinghead 9dbca96328 pvr: tile clipping is used for framebuffer size calculation in RTT
Fixes Miss Moonlight vk exception (width=0, height=0)
2026-08-20 15:14:43 +02:00
flyinghead 44e4c7b50d empty commit to trigger CI builds 2026-08-19 13:56:45 +02:00
Bruceleeto 643374c91e load elf sections that were being ignored. 2026-08-18 15:35:02 +02:00
scribam 33d5b38163 ci: remove sentry-cli login (#2435)
[ci skip]
2026-08-18 15:33:31 +02:00
scribam caa55c0297 ci: fix sentry-cli on android builds 2026-08-17 20:45:26 +02:00
Flyinghead a6242fd653 win32 build fix 2026-08-17 15:11:04 +02:00
scribam 02c779ddc1 ci: maintenance updates
- 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
2026-08-17 14:52:14 +02:00
MetalSlugandMetalSlug b0284ba9ba add pregap support for CHD and CUE (#2429)
Co-authored-by: MetalSlug <>

[ci skip]
2026-08-17 14:51:40 +02:00
Flyinghead 29a54e36ce rend: support secondary accumlator for !OIT and !autsort modes
Secondary accumulator was only supported by OIT renderers in autosort
mode. This adds support for missing renderers (dx9, dx11 and gles) and
modes (dx11 OIT and gl4 OIT in !autosort mode).
Vulkan support will come in a later commit.

Fixes WCCF black pitch issue. Evil Dead and BANG! Gunship Elite now
playable in non-OIT mode.

[ci skip]
2026-08-17 14:50:56 +02:00
Flyinghead 7cc58de0c3 netdimm: reduce the poll time to help with wccf freezes
error out if socket is already busy
2026-08-17 14:32:39 +02:00
Flyinghead 2550d10902 network: support for Sega Rally 2 (JP) online play 2026-08-16 18:18:01 +02:00
vkedwardli 565f5174f7 Add native compressed custom textures and video-memory preloading (#2408) 2026-08-15 19:18:39 +02:00
James Smith f58719d9b7 Fixed issue with single device mode but installed on B, C, or D port 2026-08-02 18:44:15 +02:00
James Smith 8041f02984 Don't check joystick name for serial number on Windows - this name gets cached and reused for similar VID/PID 2026-08-02 18:44:15 +02:00
Flyinghead 41406fb31a wccf: fix card corruption and player card dispenser
Club card format changed. Cmd 24 and 25 are single block read and write.
Card dispenser correctly reports that the player card has been dispensed
and picked up.
2026-07-24 16:58:28 +02:00
Flyinghead e293832bef netdimm: select() return code was overwritten and lost
Fixes wccf countdown stuck at 20 s.
2026-07-23 18:20:52 +02:00
Flyinghead 253970d8e5 wccf ccd cam emulation
Work in progress
2026-07-22 16:35:29 +02:00
Flyinghead 63aecfd491 Merge remote-tracking branch 'origin/master' into dev 2026-07-21 14:45:21 +02:00
Flyinghead 670b5001d4 netdimm: set wccf server IP. Close sockets in destructor
Added option naomi:WCCFServer to specify the server IP address (or
domain name). Replace wccf harcoded server IP address with the
configured one at runtime.
2026-07-20 14:12:50 +02:00
Flyinghead 2805df5484 Merge remote-tracking branch 'origin/master' into dev 2026-07-20 11:44:06 +02:00
Flyinghead 0cd66cea5c wccf preliminary support
netdimm: more complete network implementation. Add netdimm server.
wccf ic card reader emulation
wccf inputs
2026-07-18 17:40:47 +02:00
scribam d93612734b core: small optimization for reg alloc 2026-07-17 16:18:46 +02:00
James Smith 95e28f412f Handle unknown keys for manual mapping in Android 2026-07-13 17:21:39 +02:00
scribam a749e51559 tests: use cross-platform sleep function (#2400)
[ci skip]
2026-07-13 17:21:20 +02:00
scribam e82f38a8cc core: headers cleanup (#2399)
[ci skip]
2026-07-13 17:20:55 +02:00
Edward Li 89bf160a9d macOS: use release MoltenVK dylib directly 2026-07-08 16:58:59 +02:00
Flyinghead e64b28a759 aica: fix x86 DSP dynarec
Regression introduced by 8176646c5a
Issue #2249
2026-07-03 23:43:34 +02:00
shinyquagsire23 675ce1b63b arm7: Implement MSR fully in AICA dynarec
This fixes audio in tip-of-branch KallistiOS versions, due to an FIQ
enable being no-op'd.
2026-07-02 16:15:55 +02:00
Flyinghead 63d19693bc ui: show game ID on pause menu
Issue #2389
2026-07-01 13:09:43 +02:00
Edward Li 8b5cb7513b Fix content grid layout during UI scale changes 2026-06-30 11:18:33 +02:00
Edward Li 940f5bb911 SDL: refresh display metrics on monitor changes 2026-06-30 11:18:33 +02:00
Edward Li d70a265804 Delay OIT PVR resource allocation until game start 2026-06-24 13:16:07 +02:00
jpovixwm fc7b8c247d vk oit: honor maxStorageBufferRange and use BDA if available 2026-06-23 19:45:52 +02:00
Flyinghead 526b52b141 alienfnt: online watch support. use original dns name for server
Use original www.alienfrontserver.com domain name in default nvmem
picotcp: Open UDP port 7980 to receive watched game state
2026-06-21 19:15:55 +02:00
Immersion95 bcbc43fad6 sh4: end the current timeslice on Stop()
When Stop() is requested, also clear the SH4 cycle counter so the current
timeslice ends as soon as possible.

This prevents execution from continuing past a requested stop point.
2026-06-18 12:45:10 +02:00
david.schkalee d7c59e9ab5 achievements: retry login when host override is set and login previously failed 2026-06-16 14:47:12 +02:00
David Schkalee 14487d96ec android: allow cleartext HTTP to 127.0.0.1 for local proxy image loading (#2379)
[ci skip]
2026-06-16 14:46:52 +02:00
scribam f52c275c90 vk: use enums from vulkan-hpp (#2376)
[ci skip]
2026-06-16 14:46:18 +02:00
Flyinghead a27a81ba18 Merge remote-tracking branch 'origin/master' into dev 2026-06-14 10:56:38 +02:00
Flyinghead d1d7373d43 naomi: add clubkrt eeprom. Fix clubkrt network config 2026-06-14 10:56:12 +02:00
flyinghead 80cc71952e Crowdin Translations (#2374)
[ci skip]
2026-06-14 10:55:09 +02:00
Flyinghead 8c155b02c8 libretro build fix 2026-06-13 14:50:42 +02:00
Flyinghead 5ff2bc0c42 i18n: update flycast.pot 2026-06-13 14:33:56 +02:00
Flyinghead e435d72aa8 android: use gamesdk frame-pacing library
Use games-frame-pacing library (aka Swappy) for OpenGL and Vulkan.
Implements proper frame pacing for 30 FPS games and/or 120+ Hz displays.
Don't duplicate Vulkan frames.
2026-06-13 14:27:59 +02:00
Flyinghead f68e60fe92 wsi: get rid of static graphics contexts 2026-06-11 17:41:20 +02:00
Flyinghead 1e60ef78c7 vk: detect crash when pipeline file is corrupted and ignore on next run
Use emu.cfg flag to detect crash. Ignore pipeline cache if flag remains
set from previous run.
Use assert() instead of verify()
2026-06-11 10:03:27 +02:00
david.schkalee a9c5ef8b41 Add RetroAchievements host override via broadcast 2026-06-11 09:42:53 +02:00
Flyinghead d234bcf7d9 github actions: stick to windows 2022 for now
[ci skip]
2026-06-11 09:38:31 +02:00
Flyinghead 1e68c83356 rend: avoid verify() 2026-06-04 23:29:38 +02:00
Flyinghead 436e125d59 naomi: disable audio on sega strike fighter slaves
slave audio is connected to a "bass shaker" and shouldn't be sent to
regular speakers.
Issue #1816
2026-06-04 23:17:33 +02:00
flyinghead 444635372e Crowdin Translations (#2363) 2026-06-03 12:48:14 +02:00
Flyinghead abf284fc2d ui: rename Disabled network mode to Native to avoid confusion
[ci skip]
2026-06-03 12:47:26 +02:00
Flyinghead b4f89c9926 vk: fix regression on android due to eSuboptimalKHR handling
presentKHR() returns eSuboptimalKHR on Android if not rendering at the
native resolution and orientation of the screen (often vertical).
2026-06-03 12:45:01 +02:00
Flyinghead b043043568 i18n: update flycast.pot 2026-06-02 16:29:51 +02:00
Flyinghead 8542ff2dc4 naomi network QoL improvements. fix jvs eeprom regression
Support for satellite nodes (vonot, club kart)
Start network game when known max of nodes is reached.
Add Start Now button to network sync wait window.

Ignore MultiboardSlaves option if not active when loading eeprom. Rely
on game id instead.
2026-06-02 16:24:14 +02:00
Flyinghead f6dead8c39 naomi: derbyoc2 support. fix eeprom mismatch with multiboard slaves.
Save multiboard eeprom under a different name, same as nvmem
derbyoc2 uses the same card reader as WE
Disable M3Comm on slaves
Use non-free play default eeproms with proper DOC coin settings
Slave's eeprom was configured as satellite in some cases
2026-06-01 14:59:42 +02:00
Flyinghead 6de588bf57 sdl: ignore hdpi scaling less than 1.2 2026-05-31 19:41:42 +02:00
Flyinghead 1536d588f6 naomi: load flash after rom so game id is known (multiboard)
Fixes loading of correct DOC main flash at boot (instead of loading the
satellite one).
2026-05-31 13:37:57 +02:00
scribam d3c64bf1f5 ci: update cross-platform-actions and openbsd (#2358)
[ci skip]
2026-05-31 13:35:02 +02:00
scribam 2f10d39910 ios: remove [super dealloc] when using arc (#2356)
[ci skip]
2026-05-31 13:34:38 +02:00
Isaac Marovitz 7e917bc53a Restructure core/rend CMakeLists (#2354)
[ci skip]
2026-05-31 13:34:13 +02:00
Flyinghead 4a9ca07838 naomi: support for dinokich and loveber3cn 2026-05-30 14:44:42 +02:00
scribam 3e81e56499 rend: rename gl.quad to gl.quadDrawer (#2355)
Prevent confusion with the macro "quad" from newlib (devkitpro, vitasdk)

[ci skip]
2026-05-30 14:35:30 +02:00
mehlis 39c991671f add HiDPI/scale factor support for Linux (#2326)
Respects GNOME scale settings via:
  1. GDK_SCALE environment variable
  2. GNOME settings (gsettings)
  3. SDL DPI detection fallback

[ci skip]
2026-05-30 14:31:54 +02:00
Joe Mattiello 10dee60cda dx9: route renderer teardown on vsync switch through rend_term_renderer (#2325)
Made-with: Cursor

[ci skip]
2026-05-30 14:30:00 +02:00
Joe Mattiello 6925586fa2 vk: enable portability enumeration on Apple platforms (#2324)
Since Vulkan loader 1.3.216 the loader no longer enumerates portability ICDs unless the application
sets VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR and enables the
VK_KHR_portability_enumeration instance extension. Without this,
vkCreateInstance returns VK_ERROR_INCOMPATIBLE_DRIVER on macOS / iOS
where MoltenVK is the only available ICD, and Flycast fails to start
the Vulkan renderer entirely.

Made-with: Cursor

[ci skip]
2026-05-30 14:28:31 +02:00
Joe Mattiello d447d3d2db vk: recreate swapchain on suboptimal present result (#2323)
vulkan-hpp lists eSuboptimalKHR as a success code for presentKHR, so
the call returns the value rather than throwing. The existing code
ignores it via (void)res and the surface stays stuck in a sub-optimal
configuration until something else triggers a resize.

In practice this shows up across DPI / window-resize / display-mode
changes and on MoltenVK, where the driver more aggressively reports
the swapchain as suboptimal but still presents. Set resized so the
swapchain is rebuilt on the next frame, matching the existing
out-of-date handling that already comes through vk::SystemError.

Made-with: Cursor

[ci skip]
2026-05-30 14:24:20 +02:00
Joe Mattiello 55a8d6094b pvr: bound render-message queue and drain it on renderer term to fix libretro audio drift (#2322)
pvr: bound PvrMessageQueue depth to prevent producer/consumer drift
pvr: drain queue before destroying the renderer in rend_term_renderer

Made-with: Cursor

[ci skip]
2026-05-30 14:13:06 +02:00
Flyinghead 1370b74fb9 fix non-multiboard build 2026-05-29 12:58:01 +02:00
Flyinghead 46deb1dd7a naomi: use smart ptrs for multiboard and M3 comm. Refactoring 2026-05-29 10:03:13 +02:00
Flyinghead 7baf5c0a5b win32: escape double quotes in cmd line args with backslash 2026-05-29 09:57:03 +02:00
Flyinghead ce19baebc5 naomi: fix derbyoc[w] network slots. rename some comm board registers
derbyoc and derbyocw expect the slave count, not the total node count,
when the M3 board connects.
2026-05-29 09:55:41 +02:00
Flyinghead 00892506d7 compile warnings
Issue #2347
2026-05-28 16:24:50 +02:00
flyinghead 34bb486e54 New translations flycast.pot (Spanish) (#2339)
[ci skip]
2026-05-28 16:24:10 +02:00
Flyinghead 1583b75dd7 multiboard: derby owners club support. route keyboard events to master
WIP support for derby owners club (except derbyoc2)
Slaves now send keyboard events to master, which handles them and sets
input focus to its own window, and attempts to raise it.
Half the sync interval for F355 to fix sync regression.
2026-05-28 14:41:03 +02:00
Flyinghead 569dc1cd1a aica: properly align RAM reads/writes from sh4. Avoid crash on last byte 2026-05-27 17:46:45 +02:00
Flyinghead 8c0da03a15 jvs: disable eeprom load for slaves. nvmem: fix doc sat/main conflict
Save derby owners club flash file (.nvmem) under a different name for
the main screen.
2026-05-27 17:44:07 +02:00
Flyinghead 3153ac762e support quoted config values on command line 2026-05-27 17:40:23 +02:00
Flyinghead b49dd9d184 win32 build fix 2026-05-25 21:44:30 +02:00
Flyinghead 4941fdf934 libretro build fix 2026-05-25 20:32:36 +02:00
Flyinghead 81d4756fe9 Merge remote-tracking branch 'origin/master' into dev 2026-05-25 19:59:53 +02:00
Flyinghead f35f0d51ac naomi: multiboard support for alpilot and sstrkfgt 2026-05-25 19:43:32 +02:00
Flyinghead db8147e91f naomi: move multiboard code to .cpp. less log 2026-05-25 15:25:31 +02:00
Flyinghead 91d1430b42 maple: create MIE device on multiboard slaves 2026-05-25 14:47:25 +02:00
Flyinghead cc1f7ca1c1 naomi: reset M3 comm board data when disconnecting 2026-05-25 14:13:16 +02:00
318 changed files with 20110 additions and 7859 deletions

No files matched your search

+19 -27
View File
@@ -11,13 +11,16 @@ jobs:
name: Android
runs-on: ubuntu-latest
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
steps:
- name: Set up build environment
run: |
sudo apt-get update
sudo apt-get -y install ccache cmake libcurl4-openssl-dev ninja-build
sudo apt-get -y install ccache libcurl4-openssl-dev
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
@@ -28,21 +31,18 @@ jobs:
distribution: 'temurin'
cache: 'gradle'
- uses: actions/cache@v5
- uses: actions/cache@v6
with:
path: ~/.ccache
key: android-ccache-${{ github.sha }}
restore-keys: android-ccache-
path: ${{ env.CCACHE_DIR }}
key: ccache-android-${{ github.sha }}
restore-keys: ccache-android-
- name: Disable release signing
run: sed -i 's/signingConfigs.getByName("release")/signingConfigs.getByName("debug")/g' shell/android-studio/flycast/build.gradle.kts
run: sed -i 's/signingConfigs.getByName("release")/signingConfigs.getByName("debug")/' shell/android-studio/flycast/build.gradle.kts
if: github.repository != 'flyinghead/flycast' || github.event_name != 'push'
- name: Bump version code
uses: chkfung/[email protected]
with:
gradlePath: shell/android-studio/flycast/build.gradle.kts
versionCode: ${{ github.run_number }}
run: sed -i 's/versionCode = 8/versionCode = ${{ github.run_number }}/' shell/android-studio/flycast/build.gradle.kts
- name: Gradle
working-directory: shell/android-studio
@@ -51,7 +51,7 @@ jobs:
SENTRY_UPLOAD_URL: ${{ secrets.SENTRY_UPLOAD_URL }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v7
with:
name: flycast-release.apk
path: shell/android-studio/flycast/build/outputs/apk/release/flycast-release.apk
@@ -85,18 +85,8 @@ jobs:
rclone copy shell/android-studio/flycast/build/outputs/apk/release scaleway:flycast-builds/android/${GITHUB_REF#refs/}-$GITHUB_SHA --exclude '*.json'
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push'
- name: Setup Sentry CLI
uses: mathieu-bour/setup-sentry-cli@v2
env:
SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }}
with:
url: https://sentry.io
token: ${{ env.SENTRY_TOKEN }}
organization: flycast
project: minidump
version: 2.21.2
if: ${{ env.SENTRY_TOKEN != '' }}
- uses: getsentry/action-setup-cli@v1
- name: Upload symbols to Sentry
run: |
VERSION=$(git describe --tags --always)
@@ -105,5 +95,7 @@ jobs:
sentry-cli debug-files upload symbols
shell: bash
env:
SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }}
if: ${{ env.SENTRY_TOKEN != '' && github.event_name == 'push' }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }}
SENTRY_ORG: flycast
SENTRY_PROJECT: minidump
if: ${{ env.SENTRY_AUTH_TOKEN != '' && github.event_name == 'push' }}
+10 -10
View File
@@ -18,28 +18,28 @@ jobs:
libretro: [ OFF, ON ]
include:
- operating_system: freebsd
version: '15.0'
pkginstall: sudo pkg update && sudo pkg install -y alsa-lib ccache cmake evdev-proto git libao libcdio libevdev libudev-devd libzip lua54 miniupnpc ninja pkgconf pulseaudio sdl2
version: '15.1'
pkginstall: sudo pkg upgrade -y && sudo pkg install -y alsa-lib ccache cmake evdev-proto git libao libcdio libevdev libudev-devd libzip lua54 miniupnpc ninja pkgconf pulseaudio sdl2
- operating_system: netbsd
version: '10.1'
pkginstall: sudo pkgin update && sudo pkgin -y install alsa-lib ccache clang cmake git libao libcdio libusb1-1.0.30 libzip lua54 miniupnpc ninja-build pkgconf pulseaudio SDL2
version: '11.0'
pkginstall: sudo pkgin update && sudo pkgin -y install alsa-lib ccache clang cmake git libao libcdio libusb1 libzip lua54 miniupnpc ninja-build pkgconf pulseaudio SDL2
- operating_system: openbsd
version: '7.9'
pkginstall: sudo pkg_add -u && sudo pkg_add ccache cmake git libao libcdio libusb1-1.0.29 libzip lua-5.4.8p0 miniupnpc ninja pulseaudio sdl2
version: '8.0'
pkginstall: sudo pkg_add -u && sudo pkg_add ccache cmake git libao libcdio libusb1-1.0.30 libzip lua-5.4.8p2 miniupnpc ninja pulseaudio sdl2
exclude:
- architecture: arm64
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
- uses: actions/cache@v5
- uses: actions/cache@v6
with:
path: ${{ env.CCACHE_DIR }}
key: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ github.sha }}
restore-keys: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-
key: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ matrix.libretro }}-${{ github.sha }}
restore-keys: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ matrix.libretro }}-
- name: Start VM
uses: cross-platform-actions/action@v1
+51 -37
View File
@@ -21,13 +21,14 @@ jobs:
strategy:
matrix:
config:
- {name: i686-pc-windows-msvc, os: windows-2022, shell: cmd, arch: x86, cmakeArgs: -G Ninja, buildType: Release}
- {name: apple-darwin, os: macos-latest, shell: sh, cmakeArgs: -G Xcode -DUSE_DISCORD=ON, destDir: osx, buildType: RelWithDebInfo}
- {name: x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: linux, buildType: RelWithDebInfo}
- {name: x86_64-pc-windows-msvc, os: windows-2022, shell: cmd, arch: x64, cmakeArgs: -G Ninja -DUSE_DISCORD=ON, buildType: Release}
- {name: x86_64-w64-mingw32, os: windows-2022, shell: 'msys2 {0}', cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: win, buildType: RelWithDebInfo}
- {name: libretro-x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -G Ninja, buildType: Release}
- {name: libretro-x86_64-w64-mingw32, os: windows-2022, shell: 'msys2 {0}', cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -G Ninja, buildType: Release}
- { name: i686-pc-windows-msvc, os: windows-2022, shell: cmd, arch: x86, cmakeArgs: -G Ninja, buildType: Release }
- { name: apple-darwin, os: macos-latest, shell: sh, cmakeArgs: -G Xcode -DUSE_DISCORD=ON -DMOLTENVK_DYLIB=$MOLTENVK_DYLIB, destDir: osx, buildType: RelWithDebInfo, packagePath: artifact/flycast-apple-darwin.zip }
- { name: x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: linux, buildType: RelWithDebInfo }
- { name: arm64-pc-linux-gnu, os: ubuntu-22.04-arm, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: linux, buildType: RelWithDebInfo }
- { name: x86_64-pc-windows-msvc, os: windows-2022, shell: cmd, arch: x64, cmakeArgs: -G Ninja -DUSE_DISCORD=ON, buildType: Release }
- { name: x86_64-w64-mingw32, os: windows-2022, shell: 'msys2 {0}', cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: win, buildType: RelWithDebInfo, packagePath: artifact/flycast-x86_64-w64-mingw32.zip }
- { name: libretro-x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -G Ninja, buildType: Release }
- { name: libretro-x86_64-w64-mingw32, os: windows-2022, shell: 'msys2 {0}', cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -G Ninja, buildType: Release }
steps:
- name: Set up build environment (macOS)
@@ -36,20 +37,26 @@ jobs:
brew list -1 | grep python | while read formula; do brew unlink $formula; brew link --overwrite $formula; done
brew update || :
brew install libao ldid pulseaudio
brew uninstall --ignore-dependencies zstd
VULKAN_VER=1.4.341.1 && aria2c https://sdk.lunarg.com/sdk/download/$VULKAN_VER/mac/vulkan_sdk.zip?Human=true -o vulkan_sdk.zip
unzip -q vulkan_sdk.zip
./vulkansdk-macOS-$VULKAN_VER.app/Contents/MacOS/vulkansdk-macOS-$VULKAN_VER --root $HOME/VulkanSDK --accept-licenses --default-answer --confirm-command install copy_only=1
rm -f vulkan_sdk.zip
rm -rf ./vulkansdk-macOS-$VULKAN_VER.app
echo "VULKAN_SDK=$HOME/VulkanSDK/macOS" >> $GITHUB_ENV
moltenvk_tag="v1.4.2"
moltenvk_root="$RUNNER_TEMP/MoltenVK-$moltenvk_tag"
rm -rf "$moltenvk_root"
mkdir -p "$moltenvk_root"
aria2c "https://github.com/KhronosGroup/MoltenVK/releases/download/$moltenvk_tag/MoltenVK-macos.tar" -d "$RUNNER_TEMP" -o "MoltenVK-macos-$moltenvk_tag.tar"
tar -xf "$RUNNER_TEMP/MoltenVK-macos-$moltenvk_tag.tar" -C "$moltenvk_root"
moltenvk_dylib="$(find "$moltenvk_root" -type f -path '*/macOS/*' -name libMoltenVK.dylib | head -n 1)"
if [ -z "$moltenvk_dylib" ]; then
echo "MoltenVK $moltenvk_tag was downloaded, but macOS libMoltenVK.dylib was not found." >&2
exit 1
fi
echo "MOLTENVK_DYLIB=$moltenvk_dylib" >> $GITHUB_ENV
if: runner.os == 'macOS'
- name: Set up build environment (Linux)
run: |
sudo add-apt-repository ppa:christianrauch/libdecoration
sudo apt-get update
sudo apt-get -y install ccache libao-dev libasound2-dev libevdev-dev libgl1-mesa-dev liblua5.3-dev libminiupnpc-dev libpulse-dev libsdl2-dev libudev-dev libzip-dev ninja-build libcurl4-openssl-dev libcdio-dev libfuse2 locales
sudo apt-get -y install ccache libao-dev libasound2-dev libevdev-dev libgl1-mesa-dev liblua5.3-dev libminiupnpc-dev libpulse-dev libsdl2-dev libudev-dev libzip-dev libcurl4-openssl-dev libcdio-dev libfuse2 locales
sudo apt-get -y install libwayland-dev libdecor-0-dev libaudio-dev libjack-dev libsndio-dev libsamplerate0-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxkbcommon-dev libdrm-dev libgbm-dev libgles2-mesa-dev libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev
sudo sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/g' /etc/locale.gen
sudo locale-gen
@@ -59,12 +66,13 @@ jobs:
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
install: git make mingw-w64-x86_64-ccache mingw-w64-x86_64-cmake mingw-w64-x86_64-lua mingw-w64-x86_64-ninja mingw-w64-x86_64-SDL2 mingw-w64-x86_64-toolchain mingw-w64-x86_64-libcdio
install: git make
pacboy: ccache:p cmake:p libcdio:p lua:p ninja:p SDL2:p toolchain:p
if: matrix.config.shell == 'msys2 {0}'
- name: Set up build environment (Windows, Visual Studio)
run: |
choco install ccache directx-sdk ninja
choco install ccache directx-sdk
echo DXSDK_DIR=C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)>> %GITHUB_ENV%
if: matrix.config.shell == 'cmd'
@@ -73,23 +81,23 @@ jobs:
arch: ${{ matrix.config.arch }}
if: matrix.config.shell == 'cmd'
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
- name: Compile universal dependencies (macOS)
run: |
for formula in ./shell/apple/libomp.rb ./shell/apple/libpng-flycast.rb; do
for formula in ./shell/apple/libpng-flycast.rb; do
libname=$(basename "$formula" .rb)
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 HOMEBREW_DEVELOPER=1 brew install --build-from-source --formula "$formula" || \
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 HOMEBREW_DEVELOPER=1 brew reinstall --build-from-source --formula "$formula"
prefix=$(brew --prefix $libname)
# Find and verify the static library
libfile=$(find "$prefix/lib" -name "*.a" | head -n1)
lipo -info "$libfile"
# Export prefix for subsequent CMake steps
if [ -z "$CMAKE_PREFIX_PATH" ]; then
CMAKE_PREFIX_PATH="$prefix"
@@ -98,9 +106,11 @@ jobs:
fi
done
echo "CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH" >> $GITHUB_ENV
# After the last brew download: brew's own curl links brew's zstd
brew uninstall --ignore-dependencies zstd
if: matrix.config.name == 'apple-darwin'
- uses: actions/cache@v5
- uses: actions/cache@v6
with:
path: ${{ env.CCACHE_DIR }}
key: ccache-${{ matrix.config.name }}-${{ github.sha }}
@@ -119,7 +129,7 @@ jobs:
cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.config.buildType }} -DCMAKE_INSTALL_PREFIX=artifact -DENABLE_CTEST=ON ${{ matrix.config.cmakeArgs }}
cmake --build build --config ${{ matrix.config.buildType }}
./build/flycast
if: matrix.config.name == 'x86_64-pc-linux-gnu'
if: matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu'
- name: Dump symbols
run: |
@@ -134,7 +144,7 @@ jobs:
./configure
make
sudo make install
if: matrix.config.name == 'x86_64-pc-linux-gnu'
if: matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu'
- name: Dump symbols and make AppImage (linux)
run: |
@@ -142,29 +152,34 @@ jobs:
dump_syms artifact/bin/flycast > symbols/flycast.sym 2>/dev/null
strip artifact/bin/flycast
shell/linux/make-appimage.sh . artifact/bin appimage
mv flycast-x86_64.AppImage artifact/bin
ARCH=$(uname -m)
mv flycast-${ARCH}.AppImage artifact/bin
rm artifact/bin/flycast
if: matrix.config.name == 'x86_64-pc-linux-gnu'
- uses: actions/upload-artifact@v6
with:
name: flycast-${{ matrix.config.name }}
path: |
artifact/bin
artifact/lib/libretro
if: matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu'
- name: Package app (macos)
run: |
cd artifact/bin
codesign --verify --deep --strict --all-architectures --verbose=4 Flycast.app
zip -rm flycast.app.zip Flycast.app
cp flycast.app.zip "../../${{ matrix.config.packagePath }}"
if: matrix.config.name == 'apple-darwin'
- name: Package app (windows)
run: |
powershell Compress-Archive artifact/bin/flycast.exe artifact/bin/flycast.zip
powershell Compress-Archive artifact/bin/flycast.exe artifact/bin/flycast.zip
rm artifact/bin/flycast.exe
cp artifact/bin/flycast.zip "${{ matrix.config.packagePath }}"
if: matrix.config.name == 'x86_64-w64-mingw32'
- uses: actions/upload-artifact@v7
with:
name: flycast-${{ matrix.config.name }}
archive: ${{ !matrix.config.packagePath }}
path: |
${{ matrix.config.packagePath || 'artifact/bin' }}
${{ !matrix.config.packagePath && 'artifact/lib/libretro' || '' }}
- name: Setup Rclone
uses: AnimMouse/setup-rclone@v1
with:
@@ -186,7 +201,7 @@ jobs:
project: minidump
version: 2.21.2
if: ${{ env.SENTRY_TOKEN != '' }}
- name: Upload symbols to Sentry (Windows, macOS, Linux)
run: |
VERSION=$(git describe --tags --always)
@@ -196,5 +211,4 @@ jobs:
shell: bash
env:
SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }}
if: ${{ env.SENTRY_TOKEN != '' && (matrix.config.name == 'x86_64-w64-mingw32' || matrix.config.name == 'apple-darwin' || matrix.config.name == 'x86_64-pc-linux-gnu') && github.event_name == 'push' }}
if: ${{ env.SENTRY_TOKEN != '' && (matrix.config.name == 'x86_64-w64-mingw32' || matrix.config.name == 'apple-darwin' || matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu') && github.event_name == 'push' }}
+10 -8
View File
@@ -12,19 +12,22 @@ jobs:
runs-on: ubuntu-latest
container: devkitpro/devkita64:latest
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
strategy:
matrix:
config:
- {name: standalone, destDir: switch}
- {name: libretro, cmakeArgs: -DLIBRETRO=ON}
- { name: standalone, destDir: switch }
- { name: libretro, cmakeArgs: -DLIBRETRO=ON }
steps:
- name: Set up build environment
run: |
sudo apt-get update
sudo apt-get -y install ccache cmake
sudo apt-get -y install ccache gh
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
@@ -32,9 +35,9 @@ jobs:
- name: Mark git repository as safe
run: git config --global --add safe.directory $PWD
- uses: actions/cache@v5
- uses: actions/cache@v6
with:
path: ~/.ccache
path: ${{ env.CCACHE_DIR }}
key: ccache-switch-${{ matrix.config.name }}-${{ github.sha }}
restore-keys: ccache-switch-${{ matrix.config.name }}-
@@ -43,7 +46,7 @@ jobs:
$DEVKITPRO/portlibs/switch/bin/aarch64-none-elf-cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=artifact -G Ninja ${{ matrix.config.cmakeArgs }}
cmake --build build --config Release --target install
- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v7
with:
name: flycast-switch-${{ matrix.config.name }}
path: |
@@ -58,7 +61,6 @@ jobs:
uses: AnimMouse/setup-rclone@v1
with:
rclone_config: ${{ secrets.RCLONE_CONFIG }}
version: v1.69.1
- name: Upload to S3
run: rclone copy artifact/bin scaleway:flycast-builds/${{ matrix.config.destDir }}/${GITHUB_REF#refs/}-$GITHUB_SHA
+3 -3
View File
@@ -16,7 +16,7 @@ jobs:
arch: [ Win32, x64, ARM64 ]
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
@@ -35,7 +35,7 @@ jobs:
cd build\AppPackages\flycast\flycast_*_Test
mkdir tmp
ren *.msix flycast.msix
makeappx.exe unpack /p .\flycast.msix /d tmp
makeappx unpack /p .\flycast.msix /d tmp
copy ..\..\..\Release\*.dll tmp
makeappx pack /d tmp /p ..\..\..\artifact\flycast.appx
@@ -43,7 +43,7 @@ jobs:
run: signtool sign /f shell\uwp\sign_cert.pfx /p '${{ secrets.SIGN_CERT_PWD }}' /v /fd SHA256 build\artifact\flycast.appx
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push'
- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v7
with:
name: flycast-uwp-${{ matrix.arch }}
path: build/artifact
+35 -7
View File
@@ -37,6 +37,11 @@
IOS_MINVER: 13.0
MINVER: 13.0
.core-defs-libnx:
extends: .core-defs
variables:
CORE_ARGS: -DLIBRETRO=ON -DUSE_OPENMP=OFF
.core-defs-android:
extends: .core-defs
script:
@@ -80,8 +85,8 @@ include:
################################## CONSOLES ################################
# Nintendo Switch
# - project: 'libretro-infrastructure/ci-templates'
# file: '/libnx-static.yml'
- project: 'libretro-infrastructure/ci-templates'
file: '/libnx-static-cmake.yml'
# tvOS arm64
- project: 'libretro-infrastructure/ci-templates'
@@ -96,7 +101,7 @@ include:
stages:
- build-prepare
- build-shared
# - build-static
- build-static
##############################################################################
#################################### STAGES ##################################
@@ -182,10 +187,33 @@ libretro-build-ios-arm64:
################################### CONSOLES #################################
# Nintendo Switch
#libretro-build-libnx-aarch64:
# extends:
# - .libretro-libnx-static-retroarch-master
# - .core-defs
libretro-build-libnx-aarch64:
extends:
- .libretro-libnx-static-cmake-retroarch-master
- .core-defs-libnx
# The libnx image ships CMake 3.18.4 and this tree asks for 3.22.1, so the
# job stops at configure. Put a newer one in front of it - in /tmp, since
# the job does not run as a user who can write /opt.
before_script:
- !reference [.libretro-libnx-static-cmake, before_script]
- |
CMAKE_VER=3.31.6
curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VER}/cmake-${CMAKE_VER}-linux-x86_64.tar.gz" | tar -xz -C /tmp
export PATH="/tmp/cmake-${CMAKE_VER}-linux-x86_64/bin:$PATH"
# The template's script, with one word changed. It builds --target
# ${CORENAME}_libretro, and on Switch that target is flycast's own objects
# and nothing else. `combined` is the target CMakeLists.txt defines for
# exactly this case: it folds in xxhash, chdr, zip, elf and the resources,
# which is what RetroArch links against afterwards.
script:
- cmake $CORE_ARGS "$CMAKE_SOURCE_ROOT" -B$BUILD_DIR -DCMAKE_BUILD_TYPE=Release -DLIBRETRO_STATIC=ON -DLIBRETRO_SUFFIX=_libnx -DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/Switch.cmake -DNSWITCH=ON -DCFLAGS=-DHAVE_LIBNX=1 -DCXXFLAGS=-DHAVE_LIBNX=1
- cmake --build $BUILD_DIR --target combined --config Release -- -j $NUMPROC
- mv $BUILD_DIR/$EXTRA_PATH/${CORENAME}_libretro_libnx.a retroarch-precompiled/libretro_libnx.a
- cd retroarch-precompiled/
- make -f Makefile.libnx -j10
- mv retroarch_switch.nro ../${CORENAME}_libretro_libnx.nro
- mv retroarch_switch.elf ../${CORENAME}_libretro_libnx.elf
- cd ../
# tvOS arm64
libretro-build-tvos-arm64:
+4 -1
View File
@@ -49,7 +49,7 @@
url = https://github.com/herumi/xbyak.git
[submodule "core/deps/libjuice"]
path = core/deps/libjuice
url = https://github.com/paullouisageneau/libjuice.git
url = https://github.com/flyinghead/libjuice.git
[submodule "core/deps/websocketpp"]
path = core/deps/websocketpp
url = https://github.com/zaphoyd/websocketpp.git
@@ -65,3 +65,6 @@
[submodule "core/deps/gamesdk"]
path = core/deps/gamesdk
url = https://github.com/flyinghead/gamesdk.git
[submodule "core/deps/basis_universal"]
path = core/deps/basis_universal
url = https://github.com/BinomialLLC/basis_universal.git
+160 -72
View File
@@ -87,8 +87,10 @@ option(USE_HOST_GLSLANG "Use host glslang" OFF)
option(USE_HOST_LIBZIP "Use host libzip" ON)
option(USE_HOST_SDL "Use host SDL library" ${USE_HOST_SDL_DEFAULT})
option(USE_HOST_LIBCHDR "Use host libchdr" OFF)
option(USE_HOST_LIBJUICE "Use host libjuice" OFF)
option(USE_OPENMP "Use OpenMP if available" ON)
option(USE_VULKAN "Build with Vulkan support" ON)
set(MOLTENVK_DYLIB "" CACHE FILEPATH "Path to macOS MoltenVK dylib for macOS Vulkan builds")
option(USE_DX9 "Build with Direct3D 9 support" ON)
option(USE_DX11 "Build with Direct3D 11 support" ON)
option(LIBRETRO "Build libretro core" OFF)
@@ -105,6 +107,7 @@ option(ENABLE_DC_PROFILER "Build with support for target machine (SH4) profiler"
option(ENABLE_FC_PROFILER "Build with support for host app (Flycast) profiler" OFF)
option(USE_DISCORD "Use Discord Presence API" OFF)
option(USE_LIBCDIO "Use libcdio for CDROM access" OFF)
option(USE_DREAMLINK_DEVICES "Use DreamLink devices" ON)
if(IOS AND NOT LIBRETRO)
set(USE_VULKAN OFF CACHE BOOL "Force vulkan off" FORCE)
@@ -120,6 +123,11 @@ if(WEBOS)
set(USE_VULKAN OFF CACHE BOOL "Force vulkan off" FORCE)
endif()
if(ANDROID OR IOS OR NINTENDO_SWITCH OR LIBRETRO)
# DreamLink only available for non-mobile Linux, MacOS, and Windows
set(USE_DREAMLINK_DEVICES OFF CACHE BOOL "Force DreamLink devices off" FORCE)
endif()
include(GNUInstallDirs)
include(CMakeRC)
@@ -205,6 +213,10 @@ elseif(LIBRETRO)
if(NOT CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
endif()
if(NOT WIN32)
# keep bundled libs like glslang from binding to the frontend's copies
target_link_options(${PROJECT_NAME} PRIVATE "-Wl,--version-script,${CMAKE_CURRENT_SOURCE_DIR}/shell/libretro/link.T")
endif()
endif()
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "flycast_libretro"
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
@@ -218,7 +230,10 @@ elseif(LIBRETRO)
target_link_libraries(${PROJECT_NAME} PRIVATE ${FOUNDATION})
endif()
if(ANDROID OR USE_GLES)
target_compile_definitions(${PROJECT_NAME} PRIVATE GLES GLES3 HAVE_OPENGLES HAVE_OPENGLES3 HAVE_OIT)
target_compile_definitions(${PROJECT_NAME} PRIVATE GLES GLES3 HAVE_OPENGLES HAVE_OPENGLES3 HAVE_OPENGLES_3_1 HAVE_OIT)
if(ANDROID)
target_link_libraries(${PROJECT_NAME} PRIVATE android)
endif()
elseif(IOS)
target_compile_definitions(${PROJECT_NAME} PRIVATE GLES GLES3 HAVE_OPENGLES HAVE_OPENGLES3)
find_library(OPENGLES OpenGLES)
@@ -271,6 +286,7 @@ elseif(WIN32)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
endif()
elseif(APPLE)
set(USE_OPENMP OFF CACHE BOOL "Force openmp off" FORCE)
add_executable(${PROJECT_NAME} MACOSX_BUNDLE core/emulator.cpp)
else()
add_executable(${PROJECT_NAME} core/emulator.cpp)
@@ -280,6 +296,25 @@ set_target_properties(${PROJECT_NAME} PROPERTIES
CMAKE_CXX_STANDARD 20
CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(flycast_basis_transcoder STATIC
core/deps/basis_universal/transcoder/basisu_transcoder.cpp
core/deps/basis_universal/zstd/zstddeclib.c)
target_compile_features(flycast_basis_transcoder PRIVATE cxx_std_17)
target_compile_definitions(flycast_basis_transcoder PRIVATE
BASISD_SUPPORT_KTX2=1
BASISD_SUPPORT_KTX2_ZSTD=1
BASISD_SUPPORT_DXT1=1
BASISD_SUPPORT_DXT5A=1
BASISD_SUPPORT_ETC2_EAC_A8=1
BASISD_SUPPORT_XUASTC=1)
target_include_directories(flycast_basis_transcoder PUBLIC core/deps)
set_target_properties(flycast_basis_transcoder PROPERTIES POSITION_INDEPENDENT_CODE ON)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(flycast_basis_transcoder PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:-fno-strict-aliasing>)
endif()
target_link_libraries(${PROJECT_NAME} PRIVATE flycast_basis_transcoder)
if(WINDOWS_STORE)
set(USE_OPENGL OFF)
set(USE_VULKAN OFF)
@@ -428,10 +463,6 @@ if(USE_BREAKPAD AND NOT LIBRETRO)
endif()
if(USE_OPENMP)
if(APPLE AND IS_DIRECTORY "/opt/homebrew/opt/libomp")
list(PREPEND CMAKE_PREFIX_PATH "/opt/homebrew/opt/libomp")
message(STATUS "Found and using Homebrew libomp hint.")
endif()
find_package(OpenMP)
if(OpenMP_CXX_FOUND)
if(MINGW)
@@ -472,6 +503,37 @@ if(USE_VULKAN)
target_link_libraries(${PROJECT_NAME} PRIVATE glslang::glslang-default-resource-limits glslang::SPIRV)
endif()
if(USE_DREAMLINK_DEVICES)
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_DREAMLINK_DEVICES=1)
if (WINDOWS_STORE)
# Use winrt implementation of the DreamPicoPort-API
option(DREAMPICOPORT_WITH_LIBUSB "Use libusb to access DreamPicoPort-API" OFF)
elseif (CMAKE_SYSTEM_NAME MATCHES "DragonFly|FreeBSD|NetBSD|OpenBSD|Haiku")
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBUSB REQUIRED libusb-1.0)
set(DREAMPICOPORT_LIBUSB_LIBRARIES "${LIBUSB_LIBRARIES}" CACHE STRING "Name of the libusb library to link against")
set(DREAMPICOPORT_LIBUSB_INCLUDE_DIRS "${LIBUSB_INCLUDE_DIRS}" CACHE STRING "Path to libusb include directory, when applicable")
else()
if(MINGW)
# Shim 'windowsapp' to prevent libusb from linking against the UWP library
# and link against standard desktop libraries instead for Windows 7 support.
if(NOT TARGET windowsapp)
add_library(windowsapp INTERFACE)
target_link_libraries(windowsapp INTERFACE advapi32 ole32 shell32 user32)
endif()
endif()
# libusb
add_subdirectory(core/deps/DreamPicoPort-API/ext/libusb-cmake)
endif()
# DreamPicoPort-API
option(DREAMPICOPORT_ADD_LIBUSB "Add internal libusb library" OFF) # Already included above, when applicable
add_subdirectory(core/deps/DreamPicoPort-API EXCLUDE_FROM_ALL)
target_link_libraries(${PROJECT_NAME} PRIVATE dream_pico_port_api)
endif()
if(NOT LIBRETRO)
if(USE_OSS)
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_OSS)
@@ -491,39 +553,6 @@ if(NOT LIBRETRO)
endif()
if(NOT ANDROID AND NOT IOS)
if (NOT NINTENDO_SWITCH)
# DreamLink enabled for non-mobile Linux, MacOS, and Windows
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_DREAMLINK_DEVICES=1)
set(USE_DREAMLINK_DEVICES ON) # Must be set before adding core/sdl
if (WINDOWS_STORE)
# Use winrt implementation of the DreamPicoPort-API
option(DREAMPICOPORT_WITH_LIBUSB "Use libusb to access DreamPicoPort-API" OFF)
elseif (CMAKE_SYSTEM_NAME MATCHES "DragonFly|FreeBSD|NetBSD|OpenBSD|Haiku")
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBUSB REQUIRED libusb-1.0)
set(DREAMPICOPORT_LIBUSB_LIBRARIES "${LIBUSB_LIBRARIES}" CACHE STRING "Name of the libusb library to link against")
set(DREAMPICOPORT_LIBUSB_INCLUDE_DIRS "${LIBUSB_INCLUDE_DIRS}" CACHE STRING "Path to libusb include directory, when applicable")
else()
if(MINGW)
# Shim 'windowsapp' to prevent libusb from linking against the UWP library
# and link against standard desktop libraries instead for Windows 7 support.
if(NOT TARGET windowsapp)
add_library(windowsapp INTERFACE)
target_link_libraries(windowsapp INTERFACE advapi32 ole32 shell32 user32)
endif()
endif()
# libusb
add_subdirectory(core/deps/DreamPicoPort-API/ext/libusb-cmake)
endif()
# DreamPicoPort-API
option(DREAMPICOPORT_ADD_LIBUSB "Add internal libusb library" OFF) # Already included above, when applicable
add_subdirectory(core/deps/DreamPicoPort-API EXCLUDE_FROM_ALL)
target_link_libraries(${PROJECT_NAME} PRIVATE dream_pico_port_api)
endif()
if(USE_HOST_SDL)
find_package(SDL2 2.0.18)
endif()
@@ -550,11 +579,6 @@ if(NOT LIBRETRO)
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_SDL USE_SDL_AUDIO)
add_subdirectory(core/sdl)
if((UNIX AND NOT APPLE) OR NINTENDO_SWITCH)
find_package(CURL REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl)
endif()
endif()
find_package(ZLIB)
@@ -574,6 +598,18 @@ if(NOT LIBRETRO)
endif()
endif()
# http
if((UNIX AND NOT APPLE AND NOT ANDROID) OR NINTENDO_SWITCH)
find_package(CURL REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl)
elseif(LIBRETRO AND ANDROID)
# libretro needs to enable prefab and add dependency 'implementation("io.github.vvb2060.ndk:curl:8.18.0")'
find_package(curl REQUIRED CONFIG)
target_link_libraries(${PROJECT_NAME} PRIVATE curl::curl_static)
elseif(LIBRETRO AND WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE wininet)
endif()
find_package(PkgConfig)
if(NOT WITH_SYSTEM_ZLIB)
@@ -789,18 +825,27 @@ if(WIN32)
target_include_directories(${PROJECT_NAME} PRIVATE core/deps/dirent)
endif()
if(NOT LIBRETRO AND NOT NINTENDO_SWITCH)
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_ICE)
# libjuice
# libjuice
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_ICE)
if (USE_HOST_LIBJUICE AND UNIX AND NOT APPLE AND NOT ANDROID)
find_package(LibJuice)
endif()
if(NOT TARGET LibJuice::LibJuiceStatic)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
option(NO_SERVER "Disable server support" ON)
option(NO_TESTS "Disable tests build" ON)
add_subdirectory(core/deps/libjuice EXCLUDE_FROM_ALL)
target_link_libraries(${PROJECT_NAME} PRIVATE LibJuice::LibJuiceStatic)
if(NINTENDO_SWITCH)
target_include_directories(juice-static SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/shell/switch)
target_compile_definitions(juice-static PRIVATE getnameinfo=getnameinfo_fixed)
endif()
endif()
target_link_libraries(${PROJECT_NAME} PRIVATE LibJuice::LibJuiceStatic)
# websocket++
# websocket++
if(NOT LIBRETRO AND NOT NINTENDO_SWITCH)
target_include_directories(${PROJECT_NAME} PRIVATE core/deps/websocketpp)
target_compile_definitions(${PROJECT_NAME} PRIVATE ASIO_STANDALONE)
target_compile_definitions(${PROJECT_NAME} PRIVATE ASIO_STANDALONE USE_WEBSOCKET)
if (MSVC)
target_compile_definitions(${PROJECT_NAME} PRIVATE
_WEBSOCKETPP_CPP11_FUNCTIONAL_
@@ -815,8 +860,16 @@ if(NOT LIBRETRO AND NOT NINTENDO_SWITCH)
endif()
endif()
# tinygettext
set(TINYGETTEXT_WO_ICONV ON)
add_subdirectory(core/deps/tinygettext EXCLUDE_FROM_ALL)
if(MSVC)
# language.cpp holds the native language names as non-ASCII literals. MSVC
# decodes sources without a BOM using the active code page, so on a non-UTF-8
# system locale those literals break the build (C2001/C2064). The main target
# already passes /utf-8; this dependency needs it too.
target_compile_options(tinygettext PRIVATE /utf-8)
endif()
target_include_directories(${PROJECT_NAME} PRIVATE core/deps/tinygettext/include)
target_link_libraries(${PROJECT_NAME} PRIVATE tinygettext::tinygettext)
@@ -972,7 +1025,9 @@ if(LIBRETRO)
shell/libretro/storage.cpp
shell/libretro/vmu_xhair.cpp)
if(APPLE)
target_sources(${PROJECT_NAME} PRIVATE shell/libretro/oslib_apple.mm)
target_sources(${PROJECT_NAME} PRIVATE
shell/libretro/oslib_apple.mm
shell/apple/common/http_client.mm)
endif()
if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE mswsock)
@@ -1361,19 +1416,24 @@ if(NOT LIBRETRO)
COMMAND ${CMAKE_COMMAND} -E tar "cfv" "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>-${CMAKE_OSX_SYSROOT}/Flycast.ipa" --format=zip ${CMAKE_CURRENT_BINARY_DIR}/Payload)
else()
add_subdirectory(core/deps/Syphon)
target_link_libraries(${PROJECT_NAME} PRIVATE Syphon)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${SYPHON_METALLIB}
$<TARGET_FILE_DIR:flycast>/../Resources/)
target_compile_definitions(${PROJECT_NAME} PRIVATE VIDEO_ROUTING)
target_sources(${PROJECT_NAME} PRIVATE
shell/apple/common/http_client.mm
shell/apple/common/util.mm
shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.h
shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.mm
shell/apple/emulator-osx/emulator-osx/osx-main.mm)
shell/apple/common/util.mm)
# The app's main() lives in SDLApplicationDelegate.mm; the test binary
# gets its main() from gtest_main and stubs the rest (tests/src/test_stubs.cpp).
if(NOT BUILD_TESTING)
add_subdirectory(core/deps/Syphon)
target_link_libraries(${PROJECT_NAME} PRIVATE Syphon)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${SYPHON_METALLIB}
$<TARGET_FILE_DIR:flycast>/../Resources/)
target_compile_definitions(${PROJECT_NAME} PRIVATE VIDEO_ROUTING)
target_sources(${PROJECT_NAME} PRIVATE
shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.h
shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.mm
shell/apple/emulator-osx/emulator-osx/osx-main.mm)
endif()
set(ASSETS shell/apple/emulator-osx/emulator-osx/Images.xcassets)
target_sources(${PROJECT_NAME} PRIVATE ${ASSETS})
source_group("Resources" FILES ${ASSETS})
@@ -1416,9 +1476,15 @@ if(NOT LIBRETRO)
${IOSURFACE_LIBRARY}
${MULTITOUCH_SUPPORT_LIBRARY})
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "$ENV{VULKAN_SDK}/lib/libMoltenVK.dylib"
$<TARGET_FILE_DIR:flycast>/../Frameworks/libvulkan.dylib)
if(USE_VULKAN)
set(MOLTENVK_DYLIB_PATH "${MOLTENVK_DYLIB}")
if(NOT MOLTENVK_DYLIB_PATH OR NOT EXISTS "${MOLTENVK_DYLIB_PATH}")
message(FATAL_ERROR "A macOS MoltenVK dylib is required for macOS Vulkan builds. Run shell/apple/generate_xcode_project.command to download the configured MoltenVK release, or pass -DMOLTENVK_DYLIB=/path/to/libMoltenVK.dylib.")
endif()
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${MOLTENVK_DYLIB_PATH}"
$<TARGET_FILE_DIR:flycast>/../Frameworks/libMoltenVK.dylib)
endif()
endif()
elseif(UNIX)
if(NOT BUILD_TESTING)
@@ -1478,16 +1544,38 @@ endif()
if(NINTENDO_SWITCH)
if(LIBRETRO)
# ar names a member after the basename of the object, so extracting every
# archive into one directory loses whatever two of them happen to share:
# zstd carries its own copy of xxhash.c, whose object lands on top of the
# real xxHash one and takes XXH32 with it. A directory per archive keeps
# them apart.
add_custom_target(combined ALL
COMMAND ${CMAKE_AR} -x $<TARGET_FILE:xxHash::xxhash>
COMMAND ${CMAKE_AR} -x $<TARGET_FILE:chdr-static>
COMMAND ${CMAKE_AR} -x $<TARGET_FILE:zip>
COMMAND ${CMAKE_AR} -x $<TARGET_FILE:elf>
COMMAND ${CMAKE_AR} -x $<TARGET_FILE:flycast::res>
COMMAND ${CMAKE_AR} -rs flycast_libretro_libnx.a *.o
COMMAND rm *.o
COMMAND ${CMAKE_COMMAND} -E rm -rf combined-objs
COMMAND ${CMAKE_COMMAND} -E make_directory
combined-objs/xxhash combined-objs/chdr combined-objs/zip
combined-objs/zstd combined-objs/elf combined-objs/tinygettext
combined-objs/res combined-objs/libjuice
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/xxhash
${CMAKE_AR} -x $<TARGET_FILE:xxHash::xxhash>
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/chdr
${CMAKE_AR} -x $<TARGET_FILE:chdr-static>
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/zip
${CMAKE_AR} -x $<TARGET_FILE:zip>
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/zstd
${CMAKE_AR} -x $<TARGET_FILE:libzstd_static>
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/elf
${CMAKE_AR} -x $<TARGET_FILE:elf>
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/tinygettext
${CMAKE_AR} -x $<TARGET_FILE:tinygettext::tinygettext>
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/res
${CMAKE_AR} -x $<TARGET_FILE:flycast::res>
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/libjuice
${CMAKE_AR} -x $<TARGET_FILE:LibJuice::LibJuiceStatic>
COMMAND ${CMAKE_AR} -rs flycast_libretro_libnx.a combined-objs/*/*.o
COMMAND ${CMAKE_COMMAND} -E rm -rf combined-objs
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}
DEPENDS xxHash::xxhash chdr-static zip ${PROJECT_NAME})
DEPENDS xxHash::xxhash chdr-static zip libzstd_static
tinygettext::tinygettext ${PROJECT_NAME})
else()
nx_generate_nacp(flycast.nacp NAME "Flycast" AUTHOR "flyinghead, M4xw" VERSION "${GIT_VERSION}")
nx_create_nro(flycast NACP flycast.nacp ICON "${CMAKE_SOURCE_DIR}/shell/switch/flycast.jpeg")
+20 -12
View File
@@ -28,7 +28,7 @@ Automated test results are available from the builds page as well.
### Android ![android](https://flyinghead.github.io/flycast-builds/android.jpg)
Install Flycast from [**Google Play**](https://play.google.com/store/apps/details?id=com.flycast.emulator).
&emsp;Install Flycast from [**Google Play**](https://play.google.com/store/apps/details?id=com.flycast.emulator).
### Flatpak (Linux ![ubuntu logo](https://flyinghead.github.io/flycast-builds/ubuntu.png))
@@ -36,41 +36,49 @@ Install Flycast from [**Google Play**](https://play.google.com/store/apps/detail
2. Install Flycast from [Flathub](https://flathub.org/apps/details/org.flycast.Flycast):
`flatpak install -y org.flycast.Flycast`
&emsp;`flatpak install -y org.flycast.Flycast`
3. Run Flycast:
`flatpak run org.flycast.Flycast`
&emsp;`flatpak run org.flycast.Flycast`
### Homebrew (macOS ![apple logo](https://flyinghead.github.io/flycast-builds/apple.png))
1. [Set up Homebrew](https://brew.sh).
1. [Set up Homebrew](https://brew.sh) or run `brew update` if already installed.
2. Install Flycast via Homebrew:
2. Choose one channel:
`brew install --cask flycast`
| Channel | Install command |
| -------------------- | ------------------------------------------------------- |
| Master (recommended) | `brew install --cask flyinghead/flycast/flycast@master` |
| Stable | `brew install --cask flyinghead/flycast/flycast` |
| Nightly dev | `brew install --cask flyinghead/flycast/flycast@dev` |
3. Run Flycast from your Application folder
&emsp;See the <a href="https://github.com/flyinghead/homebrew-flycast#readme">Flycast tap</a> for updating, uninstalling, and switching channels.
### iOS
Due to persistent harassment from an iOS user, support for this platform has been dropped.
&emsp;Due to persistent harassment from an iOS user, support for this platform has been dropped.
### Xbox One/Series ![xbox logo](https://flyinghead.github.io/flycast-builds/xbox.png)
Grab the latest build from [**the builds page**](https://flyinghead.github.io/flycast-builds/), or the [**GitHub Actions**](https://github.com/flyinghead/flycast/actions/workflows/uwp.yml). Then install it using the **Xbox Device Portal**.
&emsp;Grab the latest build from [**the builds page**](https://flyinghead.github.io/flycast-builds/), or the [**GitHub Actions**](https://github.com/flyinghead/flycast/actions/workflows/uwp.yml). Then install it using the **Xbox Device Portal**.
## Build from source
### macOS
Right-click the bootstrap script and choose **Open**:
&emsp;Right-click the bootstrap script and choose **Open**:
`shell/apple/generate_xcode_project.command`
&emsp;`shell/apple/generate_xcode_project.command`
### Windows
Double-click the bootstrap script:
&emsp;Double-click the bootstrap script:
`shell\windows\generate_vs_project.bat`
&emsp;`shell\windows\generate_vs_project.bat`
### Linux
+4 -2
View File
@@ -73,8 +73,10 @@ constexpr u32 SAMPLE_COUNT = 512; // AudioBackend::push() is always called with
class RingBuffer
{
std::vector<u8> buffer;
std::atomic_int readCursor { 0 };
std::atomic_int writeCursor { 0 };
// The producer and the audio callback each update one cursor: keep them on
// separate cache lines.
alignas(64) std::atomic_int readCursor { 0 };
alignas(64) std::atomic_int writeCursor { 0 };
u32 readSize() {
return (u32)((writeCursor - readCursor + buffer.size()) % buffer.size());
+1 -1
View File
@@ -1,5 +1,5 @@
#pragma once
#include <stdint.h>
#include <cstdint>
#include <string>
#include <vector>
+9 -1
View File
@@ -57,6 +57,7 @@ static void parseConfigOption(const std::string& str)
switch (step)
{
case 0:
section = trim_trailing_ws(section);
if (section.empty()) {
WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str());
return;
@@ -78,6 +79,7 @@ static void parseConfigOption(const std::string& str)
WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str());
return;
case 1:
key = trim_trailing_ws(key);
if (key.empty()) {
WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str());
return;
@@ -138,8 +140,14 @@ static void parseConfigOption(const std::string& str)
switch (step)
{
case 0:
// Ignore leading spaces. Inner spaces are kept (game ids such as "INITIAL D Ver.2")
// and trailing ones are trimmed at the ':' and '=' separators
if (!section.empty())
section += c;
break;
case 1:
// Ignore
if (!key.empty())
key += c;
break;
case 2:
value += c;
+16 -5
View File
@@ -20,6 +20,8 @@
#include "types.h"
#include "stdclass.h"
#include <cctype>
namespace config {
const IniFile::Entry *IniFile::getEntry(const std::string& sectionName, const std::string& name) const
@@ -71,7 +73,7 @@ bool IniFile::getBool(const std::string& section, const std::string& entry, bool
static bool hasHexPrefix(const std::string& s)
{
size_t pos = 0;
while (pos < s.length() - 1 && std::isspace((int8_t)s[pos]))
while (pos + 1 < s.length() && std::isspace(static_cast<unsigned char>(s[pos])))
pos++;
if (pos + 1 >= s.length())
return false;
@@ -89,9 +91,18 @@ int IniFile::getInt(const std::string& section, const std::string& entry, int de
ss.setf(std::ios_base::hex, std::ios_base::basefield);
else
ss.setf(std::ios_base::dec, std::ios_base::basefield);
unsigned value = defaultValue;
unsigned value = static_cast<unsigned>(defaultValue);
ss >> value;
return (int)value;
if (ss)
return static_cast<int>(value);
// Compatibility for options migrated from Option<bool> to Option<int>.
std::string token = *pValue;
string_tolower(token);
if (token == "yes" || token == "true" || token == "on")
return 1;
if (token == "no" || token == "false" || token == "off")
return 0;
return defaultValue;
}
int64_t IniFile::getInt64(const std::string& section, const std::string& entry, int64_t defaultValue) const
@@ -217,9 +228,9 @@ void IniFile::load(const std::string& data, bool cEscape)
}
std::string value = line.substr(pos + 1);
// Space characters before and after the equal sign are for readability, and not part of the entry name or value
if (value[0] == ' ')
if (!value.empty() && value[0] == ' ')
value = value.substr(1);
if (value[0] == '"')
if (!value.empty() && value[0] == '"')
{
// quoted value
size_t end = value.rfind('"');
-1
View File
@@ -21,7 +21,6 @@
#include <cstdio>
#include <map>
#include <string>
#include <cstdint>
#include <vector>
#include <locale>
#include <sstream>
+17 -1
View File
@@ -77,11 +77,12 @@ Option<int> TextureUpscale("rend.TextureUpscale2", 1);
Option<int> MaxFilteredTextureSize("rend.MaxFilteredTextureSize", 256);
Option<float> ExtraDepthScale("rend.ExtraDepthScale", 1.f);
Option<bool> CustomTextures("rend.CustomTextures");
Option<bool> PreloadCustomTextures("rend.PreloadCustomTextures");
Option<int> PreloadCustomTextures("rend.PreloadCustomTextures", 0);
Option<bool> DumpTextures("rend.DumpTextures");
Option<bool> DumpUniqueTextures("rend.DumpUniqueTextures");
Option<bool> DumpReplacedTextures("rend.DumpReplacedTextures");
Option<int> ScreenStretching("rend.ScreenStretching", 100);
Option<bool> StretchToFill("rend.StretchToFill");
Option<bool> Fog("rend.Fog", true);
Option<bool> FloatVMUs("rend.FloatVMUs");
Option<bool> Rotate90("rend.Rotate90");
@@ -121,6 +122,20 @@ Option<bool> EmulateFramebuffer("rend.EmulateFramebuffer", false);
Option<bool> FixUpscaleBleedingEdge("rend.FixUpscaleBleedingEdge", true);
Option<bool> CustomGpuDriver("rend.CustomGpuDriver", false);
Option<bool> FramePacing("rend.FramePacing", true);
CustomTexturePreloadMode customTexturePreloadMode()
{
const auto mode = static_cast<CustomTexturePreloadMode>(PreloadCustomTextures.get());
switch (mode)
{
case CustomTexturePreloadMode::Off:
case CustomTexturePreloadMode::SystemMemory:
case CustomTexturePreloadMode::VideoMemory:
return mode;
default:
return CustomTexturePreloadMode::Off;
}
}
#ifdef VIDEO_ROUTING
Option<bool, false> VideoRouting("rend.VideoRouting", false);
Option<bool, false> VideoRoutingScale("rend.VideoRoutingScale", false);
@@ -244,6 +259,7 @@ Option<std::string, false> LuaFileName("LuaFileName", "flycast.lua");
// RetroAchievements
Option<bool> EnableAchievements("Enabled", false, "achievements");
Option<bool> ShowAchievementPopups("Show", true, "achievements");
Option<bool> AchievementsHardcoreMode("HardcoreMode", false, "achievements");
OptionString AchievementsUserName("UserName", "", "achievements");
OptionString AchievementsToken("Token", "", "achievements");
+15 -6
View File
@@ -17,16 +17,16 @@
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <string>
#include <vector>
#include <array>
#include <cmath>
#include <type_traits>
#include "cfg.h"
#include "hw/maple/maple_cfg.h"
#ifdef LIBRETRO
#include <libretro.h>
#endif
#include <array>
#include <cmath>
#include <string>
#include <type_traits>
#include <vector>
namespace config {
@@ -447,11 +447,19 @@ extern Option<int> PerPixelLayers;
#endif
extern Option<float> ExtraDepthScale;
extern Option<bool> CustomTextures;
extern Option<bool> PreloadCustomTextures;
enum class CustomTexturePreloadMode : int
{
Off = 0,
SystemMemory = 1,
VideoMemory = 2,
};
extern Option<int> PreloadCustomTextures;
CustomTexturePreloadMode customTexturePreloadMode();
extern Option<bool> DumpTextures;
extern Option<bool> DumpUniqueTextures;
extern Option<bool> DumpReplacedTextures;
extern Option<int> ScreenStretching; // in percent. 150 means stretch from 4/3 to 6/3
extern Option<bool> StretchToFill;
extern Option<bool> Fog;
extern Option<bool> FloatVMUs;
extern Option<bool> Rotate90;
@@ -571,6 +579,7 @@ extern Option<std::string, false> LuaFileName;
// RetroAchievements
extern Option<bool> EnableAchievements;
extern Option<bool> ShowAchievementPopups;
extern Option<bool> AchievementsHardcoreMode;
extern OptionString AchievementsUserName;
extern OptionString AchievementsToken;
+4
View File
@@ -29,6 +29,10 @@
#include "emulator.h"
#include "oslib/storage.h"
#include "oslib/i18n.h"
#include <string>
#include <vector>
using namespace i18n;
const WidescreenCheat CheatManager::widescreen_cheats[] =
+1 -1
View File
@@ -24,7 +24,7 @@
#include "hw/sh4/dyna/shil.h"
#include "cfg/option.h"
#include <array>
#include <signal.h>
#include <csignal>
#include <map>
#ifndef SIGTRAP
+2 -5
View File
@@ -4,11 +4,6 @@
#include <errno.h>
#include "cdipsr.h"
// Global variables
unsigned long temp_value;
/////////////////////////////////////////////////////////////////////////////
unsigned long ask_type(hostfs::File *fsource, long header_position)
@@ -16,6 +11,7 @@ unsigned long ask_type(hostfs::File *fsource, long header_position)
unsigned char filename_length;
unsigned long track_mode;
uint32_t temp_value = 0;
fsource->seek(header_position, SEEK_SET);
fsource->read(&temp_value, 4, 1);
@@ -43,6 +39,7 @@ bool CDI_read_track (hostfs::File *fsource, image_s *image, track_s *track)
unsigned char TRACK_START_MARK[10] = { 0, 0, 0x01, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF };
unsigned char current_start_mark[10];
uint32_t temp_value = 0;
fsource->read(&temp_value, 4, 1);
if (temp_value != 0)
@@ -13,6 +13,15 @@
static const int UDP_HEADER_SIZE = 28; /* Size of IP + UDP headers */
static const int NUM_SYNC_PACKETS = 5;
static const int SYNC_RETRY_INTERVAL = 2000;
static bool IsValidAppData(const UdpMsg *msg, int len)
{
const int header_size = (const uint8 *)msg->u.app_data.data - (const uint8 *)msg;
if (len < header_size)
return false;
return msg->u.app_data.size <= MAX_APPDATA_SIZE
&& msg->u.app_data.size <= len - header_size;
}
static const int SYNC_FIRST_RETRY_INTERVAL = 500;
static const int RUNNING_RETRY_INTERVAL = 200;
static const int KEEP_ALIVE_INTERVAL = 200;
@@ -320,6 +329,13 @@ UdpProtocol::HandlesMsg(sockaddr_in &from,
void
UdpProtocol::OnMsg(UdpMsg *msg, int len)
{
if (len < (int)sizeof(msg->hdr)) {
return;
}
if (msg->hdr.type == UdpMsg::AppData && !IsValidAppData(msg, len)) {
return;
}
bool handled = false;
typedef bool (UdpProtocol::*DispatchFn)(UdpMsg *msg, int len);
static const DispatchFn table[] = {
@@ -791,6 +807,8 @@ UdpProtocol::ClearSendQueue()
void UdpProtocol::SendAppData(const void *data, int len, bool spectators)
{
if (len < 0 || len > MAX_APPDATA_SIZE || (data == nullptr && len != 0))
return;
if (_udp == nullptr)
return;
if (_current_state != Synchronzied && _current_state != Running)
@@ -806,6 +824,8 @@ void UdpProtocol::SendAppData(const void *data, int len, bool spectators)
bool UdpProtocol::OnAppData(UdpMsg *msg, int len)
{
if (!IsValidAppData(msg, len))
return false;
UdpProtocol::Event evt(UdpProtocol::Event::AppData);
evt.u.app_data.spectators = msg->u.app_data.spectators != 0;
evt.u.app_data.size = msg->u.app_data.size;
@@ -278,15 +278,9 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid
if (bd->HasBindSampler && glBindSampler != NULL)
glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise.
#endif
vertex_array_object = 0;
#ifndef GLES2
if (GLGraphicsContext::Instance()->getMajorVersion() >= 3)
{
// Recreate the VAO every time
// (This is to easily allow multiple GL contexts. VAO are not shared among GL contexts, and we don't track creation/deletion of windows so we don't have an obvious key to use to cache them.)
glGenVertexArrays(1, &vertex_array_object);
glBindVertexArray(vertex_array_object);
}
#endif
glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle);
@@ -309,8 +303,6 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
if (fb_width <= 0 || fb_height <= 0)
return;
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
@@ -325,6 +317,10 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
// Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts)
// The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound.
GLuint vertex_array_object = 0;
#ifndef GLES2
if (GLGraphicsContext::Instance()->getMajorVersion() >= 3)
glGenVertexArrays(1, &vertex_array_object);
#endif
ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
// Will project scissor/clipping rectangles into framebuffer space
@@ -657,6 +653,10 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects()
void ImGui_ImplOpenGL3_DestroyDeviceObjects()
{
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
if (bd == nullptr) {
WARN_LOG(RENDERER, "ImGui_ImplOpenGL3_DestroyDeviceObjects: backend data is null");
return;
}
if (bd->VboHandle) { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; }
if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; }
if (bd->ShaderHandle) { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; }
+4 -1
View File
@@ -3008,7 +3008,10 @@ static bool glsm_state_ctx_init(glsm_ctx_params_t *params)
return false;
#ifdef HAVE_OPENGLES
hw_render.context_type = RETRO_HW_CONTEXT_OPENGLES2;
hw_render.context_type = params->context_type != RETRO_HW_CONTEXT_NONE
? params->context_type : RETRO_HW_CONTEXT_OPENGLES2;
hw_render.version_major = params->major;
hw_render.version_minor = params->minor;
#else
hw_render.context_type = RETRO_HW_CONTEXT_OPENGL;
if (params->context_type != RETRO_HW_CONTEXT_NONE)
+1 -1
View File
@@ -4,7 +4,7 @@
*********************************************************************/
#define MAX_BLOCK_SIZE 1600
#define MAX_BLOCK_COUNT 16
#define MAX_BLOCK_COUNT 1024 /* 16 blocks (~1,000 timers) ran out with a few dozen connections a second */
#define DECLARE_HEAP(type, orderby) \
struct heap_ ## type { \
+23 -11
View File
@@ -69,6 +69,8 @@ static void debug_q(struct pico_queue *q)
static inline int32_t pico_enqueue(struct pico_queue *q, struct pico_frame *p)
{
uint32_t size;
if ((q->max_frames) && (q->max_frames <= q->frames))
return -1;
@@ -98,18 +100,19 @@ static inline int32_t pico_enqueue(struct pico_queue *q, struct pico_frame *p)
q->size += p->buffer_len + q->overhead;
q->frames++;
debug_q(q);
/* Read under the lock: once unlocked, another thread may dequeue (and free) the frame,
* and a size of 0 would make the caller free it again. */
size = q->size;
if (q->shared)
PICOTCP_MUTEX_UNLOCK(q->mutex);
return (int32_t)q->size;
return (int32_t)size;
}
static inline struct pico_frame *pico_dequeue(struct pico_queue *q)
{
struct pico_frame *p = q->head;
if (!p)
return NULL;
struct pico_frame *p;
if (q->frames < 1)
return NULL;
@@ -117,15 +120,19 @@ static inline struct pico_frame *pico_dequeue(struct pico_queue *q)
if (q->shared)
PICOTCP_MUTEX_LOCK(q->mutex);
q->head = p->next;
q->frames--;
q->size -= p->buffer_len - q->overhead;
if (q->head == NULL)
q->tail = NULL;
p = q->head;
if (p)
{
q->head = p->next;
q->frames--;
q->size -= p->buffer_len - q->overhead;
if (q->head == NULL)
q->tail = NULL;
debug_q(q);
debug_q(q);
p->next = NULL;
p->next = NULL;
}
if (q->shared)
PICOTCP_MUTEX_UNLOCK(q->mutex);
@@ -160,6 +167,11 @@ static inline void pico_queue_empty(struct pico_queue *q)
static inline void pico_queue_protect(struct pico_queue *q)
{
#ifdef PICO_SUPPORT_MUTEX
/* Create the mutex now: creating it lazily on first use races between threads */
if (q->mutex == NULL)
q->mutex = pico_mutex_init();
#endif
q->shared = 1;
}
+6 -1
View File
@@ -1710,7 +1710,12 @@ int pico_socket_connect(struct pico_socket *s, const void *remote_addr, uint16_t
return -1;
}
pico_socket_alter_state(s, PICO_SOCKET_STATE_BOUND, 0, 0);
/* A socket with the same quad is already in the tree: carrying on would leave this one
* untracked, and deleting it later would unlink (and free) the other one. */
if (pico_socket_alter_state(s, PICO_SOCKET_STATE_BOUND, 0, 0) != 0) {
pico_err = PICO_ERR_EADDRINUSE;
return -1;
}
#ifdef PICO_SUPPORT_UDP
if (PROTO(s) == PICO_PROTO_UDP) {
+43 -16
View File
@@ -25,6 +25,7 @@
#include "hw/naomi/naomi_cart.h"
#include "reios/reios.h"
#include "hw/sh4/modules/mmu.h"
#include "hw/sh4/modules/modules.h"
#include "hw/sh4/sh4_if.h"
#include "hw/sh4/sh4_mem.h"
#include "hw/sh4/sh4_sched.h"
@@ -42,15 +43,19 @@
#include "hw/pvr/pvr.h"
#include "profiler/fc_profiler.h"
#include "oslib/storage.h"
#include "wsi/context.h"
#include <chrono>
#ifndef LIBRETRO
#include "ui/gui.h"
#endif
#include "hw/sh4/sh4_interpreter.h"
#include "hw/sh4/dyna/ngen.h"
#include "oslib/i18n.h"
#include <algorithm>
#include <chrono>
#include <exception>
#include <future>
#include <mutex>
#include <string>
#include <utility>
settings_t settings;
constexpr char const *BIOS_TITLE = "Dreamcast BIOS";
@@ -258,7 +263,34 @@ static void loadSpecialSettings()
|| prod_id == "T1208M" // Gigawing (JP)
|| prod_id == "T1235M" // Vampire Chronicle for Matching Service
|| prod_id == "T22901N" // Roadsters (US)
|| prod_id == "T28202M"))// Shin Nihon Pro Wrestling 4
|| prod_id == "T28202M" // Shin Nihon Pro Wrestling 4
|| prod_id == "T9512N" // The Grinch (US)
|| prod_id == "51034" // Generator Vol.1
|| prod_id == "MK-51057" // Generator Vol.2
|| prod_id == "T15128N" // Coaster Works
|| prod_id == "T9505N" // ESPN-NBA 2 Night
|| prod_id == "T-9703N" // NFL Blitz 2000
|| prod_id == "T-8106N" // Shadow Man
|| prod_id == "T15106N" // Slave Zero
|| prod_id == "T40402N" // Tom Clancy's Rainbow Six - Rouge Spear
|| prod_id == "T15125N" // Unreal Tournament
|| prod_id == "6107140" // Dorimaga GD Vol.1
|| prod_id == "T41202M" // Boku no tennis jinsei
|| prod_id == "T9503M" // Eisei Meijin III
|| prod_id == "T41201M" // Jet Coaster Dream
|| prod_id == "T40902M" // Jet Coaster Dream 2
|| prod_id == "T20105M" // Kanon
|| prod_id == "T19702M" // Memories Off Complete
|| prod_id == "T17001M" // Pen Pen Tri Iceron (JP)
|| prod_id == "T1301M" // Revive - Sosei
|| prod_id == "T15003M" // Seitai Heiki Expendable
|| prod_id == "T38805M" // Sengoku Turb FID
|| prod_id == "T20104M" // Sentimental Graffiti 2
|| prod_id == "T16601M" // Shin Honkaku Hanafuda
|| prod_id == "T1236M" // Super Street Fighter IIX For Matching Service
|| prod_id == "T36501M" // Vigilante 8 (JP)
|| prod_id == "T5301M" // World Neverland Plus - Olerud Kingdom Story
|| prod_id == "HDR-0198")) // World Series Baseball 2K2 (JP)
{
NOTICE_LOG(BOOT, "Game doesn't support RGB. Using TV Composite instead");
config::Cable.override(3);
@@ -278,8 +310,7 @@ static void loadSpecialSettings()
}
else if (prod_id == "T17708N" // Stupid Invaders (US)
|| prod_id == "T17711D" // Stupid Invaders (EU)
|| prod_id == "T46509M" // Suika (JP)
|| prod_id == "T36901M") // Cool Boarders Burrrn (JP)
|| prod_id == "T46509M") // Suika (JP)
{
NOTICE_LOG(BOOT, "Forcing HLE BIOS");
config::UseReios.override(true);
@@ -396,7 +427,8 @@ static void loadSpecialSettings()
}
else if (settings.platform.isArcade())
{
if (prod_id == "COSMIC SMASH IN JAPAN")
if (prod_id == "COSMIC SMASH IN JAPAN"
|| prod_id == "MONKEY BALL JAPAN VERSION")
{
INFO_LOG(BOOT, "Enabling translucent depth multipass for game %s", prod_id.c_str());
config::TranslucentPolygonDepthMask.override(true);
@@ -553,7 +585,7 @@ int getGamePlatform(const std::string& filename)
return DC_PLATFORM_DREAMCAST; // unknown
if (extension == "zip" || extension == "7z")
return naomi_cart_GetPlatform(filename.c_str());
if (extension == "bin" || extension == "dat" || extension == "lst")
if (extension == "bin" || extension == "dat")
return DC_PLATFORM_NAOMI;
return DC_PLATFORM_DREAMCAST;
@@ -865,7 +897,6 @@ void loadGameSpecificSettings()
loadSpecialSettings();
config::Settings::instance().setGameId(settings.content.gameId);
custom_texture.init();
// Reload per-game settings
config::Settings::instance().load(true);
@@ -897,11 +928,8 @@ void Emulator::stepRange(u32 from, u32 to)
void Emulator::loadstate(Deserializer& deser)
{
if (!custom_texture.preloaded())
{
custom_texture.terminate();
custom_texture.init();
}
if (config::customTexturePreloadMode() == config::CustomTexturePreloadMode::Off)
custom_texture.refresh();
#if FEAT_AREC == DYNAREC_JIT
aica::arm::recompiler::flush();
#endif
@@ -1123,7 +1151,6 @@ void Emulator::diskChange()
{
config::Settings::instance().reset();
config::Settings::instance().load(false);
custom_texture.terminate();
if (!settings.content.path.empty())
{
hostfs::FileInfo info = hostfs::storage().getFileInfo(settings.content.path);
+1 -1
View File
@@ -28,7 +28,7 @@
#include <mutex>
#include <utility>
#include <vector>
#include <time.h>
#include <ctime>
void loadGameSpecificSettings();
void SaveSettings();
+11 -9
View File
@@ -402,7 +402,8 @@ static void Write_SB_ADST(u32 addr, u32 data)
SB_ADSUSP &= ~0x10;
// Schedule the end of DMA transfer interrupt
int cycles = len * (SH4_MAIN_CLOCK / 2 / G2_BUS_CLOCK); // 16 bits @ 25 MHz
// theoretical: 16 bits @ 25 MHz, actual: 11.3 MiB/s, here: 15.9 MiB/s
int cycles = len * (SH4_MAIN_CLOCK / 2 / G2_BUS_CLOCK) * 3;
if (cycles <= 512)
dma_end_sched(0, 0, 0, nullptr);
else
@@ -544,6 +545,9 @@ void serialize(Serializer& ser)
ser << aica_reg;
sgc::serialize(ser);
sh4_sched_serialize(ser, aica_schid);
sh4_sched_serialize(ser, rtc_schid);
sh4_sched_serialize(ser, dma_sched_id);
}
void deserialize(Deserializer& deser)
@@ -559,10 +563,7 @@ void deserialize(Deserializer& deser)
deser >> arm::armFiqEnable;
deser >> arm::armMode;
deser >> arm::Arm7Enabled;
if (deser.version() >= Deserializer::V19)
deser >> arm::arm7ClockTicks;
else
arm::arm7ClockTicks = 0;
deser >> arm::arm7ClockTicks;
dsp::state.deserialize(deser);
@@ -573,11 +574,7 @@ void deserialize(Deserializer& deser)
}
if (!deser.rollback())
{
aica_ram.deserialize(deser);
if (settings.platform.isAtomiswave())
deser.skip(6_MB, Deserializer::V30);
}
deser >> VREG;
deser >> ARMRST;
deser >> rtc_EN;
@@ -586,6 +583,11 @@ void deserialize(Deserializer& deser)
deser >> aica_reg;
sgc::deserialize(deser);
if (deser.version() >= Deserializer::V62) {
sh4_sched_deserialize(deser, aica_schid);
sh4_sched_deserialize(deser, rtc_schid);
sh4_sched_deserialize(deser, dma_sched_id);
}
}
} // namespace aica
+3
View File
@@ -6,6 +6,9 @@ namespace aica
{
extern RamRegion aica_ram;
extern int aica_schid;
extern int rtc_schid;
extern int dma_sched_id;
u32 GetRTC_now();
template<typename T> T readRtcReg(u32 addr);
template<typename T> void writeRtcReg(u32 addr, T data);
-4
View File
@@ -41,16 +41,12 @@ struct DSPState
void deserialize(Deserializer& deser)
{
deser.skip(4096 * 8, Deserializer::V18); // DynCode
deser >> TEMP;
deser >> MEMS;
deser >> MIXS;
deser >> RBP;
deser >> RBL;
deser.skip(44, Deserializer::V18);
deser >> MDEC_CT;
deser.skip(33596 - 4096 * 8 - sizeof(TEMP) - sizeof(MEMS) - sizeof(MIXS) - 4 * 3 - 44,
Deserializer::V18); // other dsp stuff
if (!deser.rollback())
dirty = true;
}
+27 -35
View File
@@ -95,15 +95,14 @@ static const char* stream_names[]=
};
//x.8 format
static const s32 adpcm_qs[8] =
static constexpr s32 adpcm_qs[8] =
{
0x0e6, 0x0e6, 0x0e6, 0x0e6, 0x133, 0x199, 0x200, 0x266,
};
//x.3 format
static const s32 adpcm_scale[16] =
static constexpr s32 adpcm_scale[8] =
{
1,3,5,7,9,11,13,15,
-1,-3,-5,-7,-9,-11,-13,-15,
1, 3, 5, 7, 9, 11, 13, 15,
};
static const s32 qtable[32] = {
@@ -203,12 +202,9 @@ public:
{
if (deser.version() < Deserializer::V49)
{
if (deser.version() >= Deserializer::V22)
{
deser.skip<int>(); // beepOn
deser.skip<int>(); // beepPeriod
deser.skip<int>(); // beepCounter
}
deser.skip<int>(); // beepOn
deser.skip<int>(); // beepPeriod
deser.skip<int>(); // beepCounter
init();
}
else
@@ -431,6 +427,7 @@ struct ChannelEx
{
u32 LSA;
u32 LEA;
u32 alignedLEA;
u8 looped;
} loop;
@@ -800,6 +797,8 @@ struct ChannelEx
{
loop.LSA = ccd->LSA;
loop.LEA = ccd->LEA;
// ADPCM long stream needs 4-sample alignment for LEA
loop.alignedLEA = (loop.LEA + 3) & ~3;
}
u32 EG_EffRate(u32 rate)
@@ -1062,11 +1061,13 @@ static T readAicaRam(u32 addr) {
template<PCMSType PCMS, bool Last>
void ChannelEx::StepDecodeSample(u32 CA)
{
// Only ADPCM needs all intermediate samples to be decoded
if constexpr (!Last && (PCMS == PCM16 || PCMS == PCM8 || PCMS == NOISE))
return;
u32 next_addr = CA + 1;
if (next_addr >= loop.LEA && loop.LEA > loop.LSA)
const u32 lea = PCMS == ADPCM_STREAM ? loop.alignedLEA : loop.LEA;
if (next_addr >= lea && lea > loop.LSA)
next_addr = loop.LSA;
SampleType s0, s1;
@@ -1095,16 +1096,11 @@ void ChannelEx::StepDecodeSample(u32 CA)
case ADPCM_STREAM:
{
u8 ad1 = readAicaRam<u8>(SA + (CA >> 1));
u8 ad2 = readAicaRam<u8>(SA + (next_addr >> 1));
ad1 >>= (CA & 1) * 4;
ad2 >>= (next_addr & 1) * 4;
ad1 &= 0xF;
ad2 &= 0xF;
s32 q = adpcm.last_quant;
if (PCMS == ADPCM && CA == loop.LSA)
if (CA == loop.LSA)
{
if (!adpcm.in_loop)
{
@@ -1112,7 +1108,7 @@ void ChannelEx::StepDecodeSample(u32 CA)
adpcm.loopstart_quant = q;
adpcm.loopstart_prev_sample = this->s0;
}
else
else if (PCMS == ADPCM)
{
q = adpcm.loopstart_quant;
this->s0 = adpcm.loopstart_prev_sample;
@@ -1122,6 +1118,9 @@ void ChannelEx::StepDecodeSample(u32 CA)
adpcm.last_quant = q;
if constexpr (Last)
{
u8 ad2 = readAicaRam<u8>(SA + (next_addr >> 1));
ad2 >>= (next_addr & 1) * 4;
ad2 &= 0xF;
SampleType prev = s0;
if (PCMS == ADPCM && next_addr == loop.LSA && adpcm.in_loop)
{
@@ -1153,11 +1152,7 @@ void ChannelEx::StreamStep()
sp.ip--;
u32 CA = this->CA + 1;
u32 ca_t = CA;
if constexpr (PCMS == ADPCM_STREAM)
ca_t &= ~3; // in adpcm "stream" mode, LEA and LSA are supposed to be 4-sample aligned
// but some games don't respect this rule
const u32 lea = PCMS == ADPCM_STREAM ? loop.alignedLEA : loop.LEA;
if constexpr (LPSLNK)
{
@@ -1168,13 +1163,13 @@ void ChannelEx::StreamStep()
}
}
if (ca_t >= loop.LEA)
if (CA >= lea)
{
if (loop.LSA > loop.LEA)
if (loop.LSA > lea)
{
// When LSA > LEA, aica won't stop when reaching LEA but will continue until LSA.
// It will then reset CA to 0 and stop playing, even for looping sounds.
if (ca_t >= loop.LSA)
if (CA >= loop.LSA)
{
loop.looped = 1;
CA = 0;
@@ -1721,16 +1716,13 @@ void deserialize(Deserializer& deser)
deser >> cdda_sector;
deser >> cdda_index;
midiSendBuffer.clear();
if (deser.version() >= Deserializer::V28)
u32 size;
deser >> size;
for (u32 i = 0; i < size; i++)
{
u32 size;
deser >> size;
for (u32 i = 0; i < size; i++)
{
u8 b;
deser >> b;
midiSendBuffer.push_back(b);
}
u8 b;
deser >> b;
midiSendBuffer.push_back(b);
}
}
+13 -23
View File
@@ -427,32 +427,11 @@ class Arm7Compiler : public MacroAssembler
Add(rd, w0, op2);
break;
case 8: // TST
#ifdef STRICT_MODE
// In armv7, TST and TEQ do not affect the V flag.
// This isn't the case in armv8 so we need to save
// and restore it.
// Since this is a bit complicated/slow, let's assume nobody
// relies on this.
Cset(w3, vs);
#endif
Tst(rn, op2);
#ifdef STRICT_MODE
Mrs(x0, NZCV);
Bfi(x0, x3, 28, 1); // V is bit 28
Msr(NZCV, x0);
#endif
break;
case 9: // TEQ
Eor(w0, rn, op2);
#ifdef STRICT_MODE
Cset(w3, vs);
#endif
Tst(w0, w0);
#ifdef STRICT_MODE
Mrs(x0, NZCV);
Bfi(x0, x3, 28, 1); // V is bit 28
Msr(NZCV, x0);
#endif
break;
case 10: // CMP
Cmp(rn, op2);
@@ -474,10 +453,21 @@ class Arm7Compiler : public MacroAssembler
die("invalid op");
break;
}
if (set_carry_bit)
if (set_carry_bit || logical_op_set_flags)
{
Mrs(x0, NZCV);
Bfi(x0, x14, 29, 1); // C is bit 29 in NZCV
if (set_carry_bit)
Bfi(x0, x14, 29, 1); // C is bit 29 in NZCV
if (logical_op_set_flags)
{
// ARMv7 logical ops (AND, EOR, ORR, BIC, MOV, MVN, TST, TEQ) leave V alone.
// The AArch64 flag-setting equivalents (ANDS, BICS, TST) clear it, so put
// back the V the op started with. loadFlags() ran before the op, so the
// saved PSR still holds it.
Ldr(w3, arm_reg_operand(RN_PSR_FLAGS));
Ubfx(w3, w3, 28, 1); // w3 = V
Bfi(x0, x3, 28, 1); // V is bit 28 in NZCV
}
Msr(NZCV, x0);
}
}
+17 -6
View File
@@ -28,6 +28,7 @@ using namespace Xbyak::util;
#include "arm7_rec.h"
#include "oslib/unwind_info.h"
#include "oslib/virtmem.h"
#include "stdclass.h"
namespace aica::arm
{
@@ -45,11 +46,11 @@ static UnwindInfo unwinder;
class Arm7Compiler;
#ifdef _WIN32
static const std::array<Xbyak::Reg32, 8> alloc_regs {
static constexpr std::array<Xbyak::Reg32, 8> alloc_regs {
ebx, ebp, edi, esi, r12d, r13d, r14d, r15d
};
#else
static const std::array<Xbyak::Reg32, 6> alloc_regs {
static constexpr std::array<Xbyak::Reg32, 6> alloc_regs {
ebx, ebp, r12d, r13d, r14d, r15d
};
#endif
@@ -1003,8 +1004,13 @@ void arm7backend_compile(const std::vector<ArmOp>& block_ops, u32 cycles)
virtmem::jit_set_exec(protStart, protSize, false);
Arm7Compiler assembler;
assembler.compile(block_ops, cycles);
try {
assembler.compile(block_ops, cycles);
} catch (const Xbyak::Error& e) {
virtmem::jit_set_exec(protStart, protSize, true);
ERROR_LOG(AICA_ARM, "Xbyak error: %s", e.what());
throw FlycastException(strprintf("ARM recompilation error: %s", e.what()));
}
virtmem::jit_set_exec(protStart, protSize, true);
}
@@ -1016,8 +1022,13 @@ void arm7backend_flush()
unwinder.clear();
Arm7Compiler assembler;
assembler.generateMainLoop();
try {
assembler.generateMainLoop();
} catch (const Xbyak::Error& e) {
virtmem::jit_set_exec(protStart, protSize, true);
ERROR_LOG(AICA_ARM, "Xbyak error: %s", e.what());
throw FlycastException(strprintf("ARM recompilation error: %s", e.what()));
}
virtmem::jit_set_exec(protStart, protSize, true);
}
+28 -11
View File
@@ -21,6 +21,8 @@
#include "hw/holly/holly_intc.h"
#include "network/netservice.h"
#include "serialize.h"
#include "hw/sh4/sh4_sched.h"
#include "cfg/option.h"
static RTL8139State *rtl8139device;
@@ -49,6 +51,24 @@ static u8 GAPS_ram[GAPSPCI_RAM_SIZE];
static u8 GAPS_regs[GAPSPCI_REGS_SIZE];
static u32 dmaOffset;
static bool interruptPending;
static constexpr u32 MAX_ETH_FRAME_SIZE = 1514;
static int rxSchedId = -1;
constexpr int RX_POLL_CYCLES = 5_sh4ms;
static int rxDeliver(int tag, int cycles, int jitter, void *arg)
{
u8 frame[MAX_ETH_FRAME_SIZE];
while (rtl8139_can_receive(rtl8139device))
{
const u32 size = net::modbba::sendEthFrame(frame);
if (size == 0)
break;
if (size < 6 || size > MAX_ETH_FRAME_SIZE)
continue;
rtl8139_receive(rtl8139device, frame, size);
}
return RX_POLL_CYCLES;
}
static void setInterrupt()
{
@@ -64,7 +84,7 @@ void pci_set_irq(PCIDevice *pci_dev, int level)
setInterrupt();
}
void bba_Init()
static void bba_Init()
{
NICConf nicConf = { 0xc, 0xa, 0xf, 0xe, 0, 0 };
rtl8139device = rtl8139_init(&nicConf);
@@ -73,6 +93,9 @@ void bba_Init()
memset(&GAPS_regs[0], 0, sizeof(GAPS_regs));
memcpy(&GAPS_regs[0], "GAPSPCI_BRIDGE_2", 0x10);
memcpy(&GAPS_regs[0x1c], "SEGA", 4);
rxSchedId = sh4_sched_register(0, rxDeliver);
sh4_sched_request(rxSchedId, RX_POLL_CYCLES);
}
void bba_Term()
@@ -83,6 +106,8 @@ void bba_Term()
rtl8139_destroy(rtl8139device);
rtl8139device = nullptr;
}
sh4_sched_unregister(rxSchedId);
rxSchedId = -1;
}
void bba_Reset(bool hard)
@@ -90,7 +115,8 @@ void bba_Reset(bool hard)
if (hard)
{
bba_Term();
bba_Init();
if (config::EmulateBBA && (settings.platform.isConsole() || settings.platform.isAtomiswave()))
bba_Init();
}
}
@@ -213,15 +239,6 @@ ssize_t qemu_send_packet(RTL8139State *s, const uint8_t *buf, int size)
return size;
}
int bba_recv_frame(const u8 *data, u32 len)
{
if (!rtl8139_can_receive(rtl8139device))
return 0;
rtl8139_receive(rtl8139device, data, len);
return 1;
}
void pci_dma_read(PCIDevice *dev, dma_addr_t addr, void *buf, dma_addr_t len)
{
memcpy(buf, &GAPS_ram[addr & GAPSPCI_RAM_MASK], len);
-2
View File
@@ -19,11 +19,9 @@
#pragma once
#include "types.h"
void bba_Init();
void bba_Term();
void bba_Reset(bool hard);
u32 bba_ReadMem(u32 addr, u32 sz);
void bba_WriteMem(u32 addr, u32 data, u32 sz);
void bba_Serialize(Serializer& ser);
void bba_Deserialize(Deserializer& deser);
int bba_recv_frame(const u8 *data, u32 len);
+20 -2
View File
@@ -2411,6 +2411,25 @@ void rtl8139_serialize(RTL8139State *s, Serializer& ser)
bool rtl8139_deserialize(RTL8139State *s, Deserializer& deser)
{
if (s == nullptr)
{
deser.skip(256 * 3 + sizeof(PCIDevice::io_regions)
+ 8 + 8 + 4 * 4 + 4 * 4
+ 4 + 4 + 4 + 4
+ 2 + 2
+ 4 + 4 + 4 + 2
+ 6
+ 2
+ 2
+ 5 * 2
+ sizeof(MACAddr)
+ 4
+ sizeof(EEprom9346::contents) + sizeof(EEprom9346::mode) + sizeof(EEprom9346::tick)
+ sizeof(EEprom9346::address) + sizeof(EEprom9346::input) + sizeof(EEprom9346::output) + 4
+ 4 + 4 + 8
+ 12);
return false;
}
deser.deserialize(s->parent_obj.config, 256);
deser.deserialize(s->parent_obj.cmask, 256);
deser.deserialize(s->parent_obj.wmask, 256);
@@ -2472,8 +2491,7 @@ bool rtl8139_deserialize(RTL8139State *s, Deserializer& deser)
deser >> s->TimerInt;
deser >> s->TCTR_base;
if (deser.version() >= Deserializer::V38)
sh4_sched_deserialize(deser, s->schedId);
sh4_sched_deserialize(deser, s->schedId);
return s->bChipCmdState & CmdRxEnb;
}
+3 -11
View File
@@ -18,6 +18,7 @@
#include "oslib/oslib.h"
#include "stdclass.h"
#include "oslib/storage.h"
#include "oslib/directory.h"
bool MemChip::Load(const std::string& file)
{
@@ -40,17 +41,8 @@ void MemChip::Load(const u8 *data, size_t size)
memcpy(this->data + write_protect_size, data, this->size - write_protect_size);
}
void WritableChip::Save(const std::string& file)
{
FILE *f = nowide::fopen(file.c_str(), "wb");
if (f == nullptr)
{
ERROR_LOG(FLASHROM, "Cannot save flash/nvmem to file '%s'", file.c_str());
return;
}
if (std::fwrite(data + write_protect_size, 1, size - write_protect_size, f) != size - write_protect_size)
ERROR_LOG(FLASHROM, "Failed or truncated write to flash file '%s'", file.c_str());
std::fclose(f);
void WritableChip::Save(const std::string& file) {
safeFullSave(file, data + write_protect_size, size - write_protect_size);
}
bool MemChip::Load(const std::string &prefix, const std::string &names_ro, const std::string &title)
+1 -1
View File
@@ -8,7 +8,7 @@
*/
#pragma once
#include "types.h"
#include <string.h>
#include <cstring>
class X76F100SerialFlash
{
+73 -66
View File
@@ -15,7 +15,9 @@
#include "imgread/common.h"
#include "serialize.h"
int gdrom_schid;
int gdrom_schid = -1;
static int statusSchid = -1;
static int nextStatus = -1;
//Sense: ASC - ASCQ - Key
int sns_asc;
@@ -32,15 +34,6 @@ cdda_t cdda;
static gd_states gd_state;
static DiscType gd_disk_type;
/*
GD rom reset -> GDS_WAITCMD
GDS_WAITCMD -> ATA/SPI command [Command code is on ata_command]
SPI Command -> GDS_WAITPACKET -> GDS_SPI_* , depending on input
GDS_SPI_READSECTOR -> Depending on features , it can do quite a few things
*/
static u32 data_write_mode=0;
//Registers
static u32 DriveSel;
@@ -154,6 +147,8 @@ void DmaBuffer::deserialize(Deserializer& deser)
{
index = 0;
deser >> size;
if (size > sizeof(cache))
throw Deserializer::Exception("Invalid GD-ROM DMA buffer size");
deser.deserialize(&cache[0], size);
}
}
@@ -171,12 +166,25 @@ void PioBuffer::deserialize(Deserializer& deser)
deser >> next_state;
deser >> index;
deser >> size;
if (size > Capacity / sizeof(_data[0]) || index > size)
throw Deserializer::Exception("Invalid GD-ROM PIO buffer state");
if (deser.version() < Deserializer::V54)
deser >> _data;
else
deser.deserialize(&_data[0], size);
}
static void cddaTransition(int state, u64 duration = 20_sh4ms, int transientState = GD_BUSY)
{
SecNumber.Status = transientState;
nextStatus = state;
sh4_sched_request(statusSchid, duration);
}
static void cddaCancelTransition() {
sh4_sched_request(statusSchid, -1);
}
static void gd_set_state(gd_states state)
{
gd_states prev=gd_state;
@@ -257,6 +265,9 @@ static void gd_set_state(gd_states state)
sector_count = maxSectors;
next_state = gds_readsector_pio;
}
else {
cddaTransition(GD_PAUSE, 67_sh4ms, GD_PLAY);
}
u16 *buffer = pio_buff.fill(sector_count * read_params.sector_type);
libGDR_ReadSector((u8*)buffer, read_params.start_sector, sector_count, read_params.sector_type);
@@ -345,6 +356,7 @@ void gd_setdisc()
gd_disk_type = newd;
SecNumber.DiscFormat = gd_disk_type >> 4;
cddaCancelTransition();
}
static void gd_reset()
@@ -440,6 +452,7 @@ static void gd_process_ata_cmd()
Error.full = 1;
sns_key = 0;
SecNumber.Status = GD_PAUSE;
cddaCancelTransition();
IntReason.full = 1;
// DC Checker expects these values
ByteCount.low = 0x14;
@@ -749,7 +762,9 @@ static void gd_process_spi_cmd()
{
#define readcmd packet_cmd.GDReadBlock
SecNumber.Status = GD_PLAY;
cdda.status = cdda_t::NoInfo;
cddaCancelTransition();
u32 sector_type = 2048;
if (readcmd.head == 1 && readcmd.subh == 1 && readcmd.data == 1 && readcmd.expdtype == 3 && readcmd.other == 0)
sector_type = 2340;
@@ -911,7 +926,6 @@ static void gd_process_spi_cmd()
case SPI_CD_PLAY:
{
const u32 param_type = packet_cmd.data_8[1] & 7;
printf_spicmd("SPI_CD_PLAY param_type=%d", param_type);
if (param_type == 1 || param_type == 2)
{
@@ -927,30 +941,24 @@ static void gd_process_spi_cmd()
cdda.EndAddr.FAD = ses_inf[3] << 16 | ses_inf[4] << 8 | ses_inf[5];
}
cdda.repeats = packet_cmd.data_8[6] & 0xF;
cdda.status = cdda_t::Playing;
SecNumber.Status = GD_PLAY;
GDStatus.DSC = 1;
printf_spicmd("SPI_CD_PLAY %d -> %d repeat %d", cdda.CurrAddr.FAD, cdda.EndAddr.FAD, cdda.repeats);
cddaTransition(GD_PLAY, 5_sh4ms, SecNumber.Status);
}
else if (param_type == 7)
{
printf_spicmd("SPI_CD_PLAY pause");
if (cdda.status == cdda_t::Paused)
{
// Resume from previous pos unless we're at the end
if (cdda.CurrAddr.FAD > cdda.EndAddr.FAD)
{
cdda.status = cdda_t::Terminated;
SecNumber.Status = GD_STANDBY;
}
cddaTransition(GD_STANDBY);
else
{
cdda.status = cdda_t::Playing;
SecNumber.Status = GD_PLAY;
}
cddaTransition(GD_PLAY);
}
}
else
else {
die("SPI_CD_PLAY: unknown parameter");
}
DEBUG_LOG(GDROM, "CDDA StartAddr=%d EndAddr=%d repeats=%d status=%d CurrAddr=%d",cdda.StartAddr.FAD,
cdda.EndAddr.FAD, cdda.repeats, cdda.status, cdda.CurrAddr.FAD);
@@ -964,42 +972,30 @@ static void gd_process_spi_cmd()
const u32 param_type = packet_cmd.data_8[1] & 7;
printf_spicmd("SPI_CD_SEEK param_type=%d", param_type);
SecNumber.Status = GD_PAUSE;
if (cdda.status == cdda_t::Playing)
cdda.status = cdda_t::Paused;
if (param_type == 1 || param_type == 2)
{
bool min_sec_frame = param_type == 2;
cdda.StartAddr.FAD = cdda.CurrAddr.FAD = GetFAD(&packet_cmd.data_8[2], min_sec_frame);
#ifdef STRICT_MODE
SecNumber.Status = GD_SEEK;
GDStatus.DSC = 0;
sh4_sched_request(gdrom_schid, SH4_MAIN_CLOCK / 50); // 20 ms
#else
GDStatus.DSC = 1;
#endif
cddaTransition(GD_PAUSE, 20_sh4ms, GD_SEEK);
}
else if (param_type == 3)
{
//stop audio , goto home
cdda.StartAddr.FAD = cdda.CurrAddr.FAD = 150;
cdda.status = cdda_t::NoInfo;
#ifdef STRICT_MODE
SecNumber.Status = GD_BUSY;
GDStatus.DSC = 0;
sh4_sched_request(gdrom_schid, SH4_MAIN_CLOCK / 50); // 20 ms
#else
SecNumber.Status = GD_STANDBY;
GDStatus.DSC = 1;
#endif
cddaTransition(GD_STANDBY, 5_sh4ms);
}
else if (param_type == 4)
{
//pause audio -- nothing more
SecNumber.Status = GD_PAUSE;
cdda.status = cdda_t::Paused;
cddaCancelTransition();
}
else
else {
die("SPI_CD_SEEK : not known parameter..");
}
DEBUG_LOG(GDROM, "CDDA StartAddr=%d EndAddr=%d repeats=%d status=%d CurrAddr=%d",cdda.StartAddr.FAD,
cdda.EndAddr.FAD, cdda.repeats, cdda.status, cdda.CurrAddr.FAD);
@@ -1017,7 +1013,7 @@ static void gd_process_spi_cmd()
case SPI_GET_SCD:
{
printf_spicmd("SPI_GET_SCD");
printf_spicmd("SPI_GET_SCD %d", packet_cmd.data_8[1] & 0xF);
const u32 format = packet_cmd.data_8[1] & 0xF;
const u32 alloc_len = (packet_cmd.data_8[3] << 8) | packet_cmd.data_8[4];
@@ -1230,16 +1226,6 @@ static int getGDROMTicks()
//is this needed ?
static int GDRomschd(int tag, int cycles, int jitter, void *arg)
{
if (SecNumber.Status == GD_SEEK)
{
SecNumber.Status = GD_PAUSE;
GDStatus.DSC = 1;
}
else if (SecNumber.Status == GD_BUSY)
{
SecNumber.Status = GD_STANDBY;
GDStatus.DSC = 1;
}
if (!(SB_GDST & 1) || !(SB_GDEN & 1) || (dma_buff.isEmpty() && read_params.remaining_sectors == 0))
return 0;
@@ -1298,13 +1284,33 @@ static int GDRomschd(int tag, int cycles, int jitter, void *arg)
asic_RaiseInterrupt(holly_GDROM_DMA);
}
// Read ALL sectors and all buffer
if (read_params.remaining_sectors == 0 && dma_buff.isEmpty())
//verify(!SB_GDST&1) -> dc can do multi read dma
if (read_params.remaining_sectors == 0 && dma_buff.isEmpty()) {
gd_set_state(gds_procpacketdone);
cddaTransition(GD_PAUSE, 67_sh4ms, GD_PLAY);
}
return getGDROMTicks();
}
static int statusSched(int tag, int cycles, int jitter, void *arg)
{
if (nextStatus != -1)
{
GDStatus.DSC = 1;
int prevCddaStatus = cdda.status;
if (nextStatus == GD_PAUSE && cdda.status == cdda_t::Playing)
cdda.status = cdda_t::Paused;
else if (nextStatus == GD_STANDBY)
cdda.status = cdda_t::Terminated;
else if (nextStatus == GD_PLAY)
cdda.status = cdda_t::Playing;
DEBUG_LOG(GDROM, "statusSched: Status %d -> %d, cdda status %d -> %d", SecNumber.Status, nextStatus, prevCddaStatus, cdda.status);
SecNumber.Status = nextStatus;
nextStatus = -1;
}
return 0;
}
//DMA Start
static void GDROM_DmaStart(u32 addr, u32 data)
{
@@ -1346,6 +1352,7 @@ static void GDROM_DmaEnable(u32 addr, u32 data)
void gdrom_reg_Init()
{
gdrom_schid = sh4_sched_register(0, &GDRomschd);
statusSchid = sh4_sched_register(0, &statusSched);
gd_disc_change();
}
@@ -1353,6 +1360,8 @@ void gdrom_reg_Term()
{
sh4_sched_unregister(gdrom_schid);
gdrom_schid = -1;
sh4_sched_unregister(statusSchid);
statusSchid = -1;
}
void gdrom_reg_Reset(bool hard)
@@ -1390,7 +1399,6 @@ void gdrom_reg_Reset(bool hard)
cdda = {};
gd_disk_type = NoDisk;
data_write_mode = 0;
DriveSel = 0xa0;
Error = {};
IntReason = {};
@@ -1423,7 +1431,6 @@ void serialize(Serializer& ser)
ser << cdda;
ser << gd_state;
ser << gd_disk_type;
ser << data_write_mode;
ser << DriveSel;
ser << Error;
@@ -1433,6 +1440,8 @@ void serialize(Serializer& ser)
ser << SecNumber;
ser << GDStatus;
ser << ByteCount;
sh4_sched_serialize(ser, gdrom_schid);
sh4_sched_serialize(ser, statusSchid);
}
void deserialize(Deserializer& deser)
@@ -1446,20 +1455,14 @@ void deserialize(Deserializer& deser)
deser >> packet_cmd;
deser >> set_mode_offset;
deser >> read_params;
if (deser.version() >= Deserializer::V17) {
dma_buff.deserialize(deser);
}
else {
deser >> packet_cmd;
dma_buff.clear();
}
dma_buff.deserialize(deser);
pio_buff.deserialize(deser);
deser.skip<u32>(Deserializer::V44); // set_mode_offset (repeat)
deser >> ata_command;
deser >> cdda;
deser >> gd_state;
deser >> gd_disk_type;
deser >> data_write_mode;
deser.skip<u32>(Deserializer::V62); // data_write_mode
deser >> DriveSel;
deser >> Error;
@@ -1469,6 +1472,10 @@ void deserialize(Deserializer& deser)
deser >> SecNumber;
deser >> GDStatus;
deser >> ByteCount;
if (deser.version() >= Deserializer::V62) {
sh4_sched_deserialize(deser, gdrom_schid);
sh4_sched_deserialize(deser, statusSchid);
}
}
}
+1 -16
View File
@@ -651,7 +651,6 @@ void sb_Init()
maple_Init();
aica::sbInit();
bba_Init();
ModemInit();
}
@@ -706,19 +705,5 @@ void sb_serialize(Serializer& ser)
void sb_deserialize(Deserializer& deser)
{
deser >> sb_regs;
if (deser.version() < Deserializer::V33)
deser >> SB_ISTNRM;
if (deser.version() >= Deserializer::V24)
deser >> SB_ISTNRM1;
else
SB_ISTNRM1 = 0;
if (deser.version() < Deserializer::V33)
{
if (deser.version() < Deserializer::V30)
{
deser.skip<u32>(); // SB_FFST_rc;
deser.skip<u32>(); // SB_FFST;
}
deser >> SB_ADST;
}
deser >> SB_ISTNRM1;
}
+2 -1
View File
@@ -7,4 +7,5 @@ target_sources(${PROJECT_NAME} PRIVATE
maple_helper.h
maple_if.cpp
maple_if.h
maple_jvs.cpp)
maple_jvs.cpp
wccf_cam.cpp)
+24 -22
View File
@@ -4,6 +4,7 @@
#include "hw/naomi/naomi_cart.h"
#include "hw/naomi/card_reader.h"
#include "hw/sh4/modules/modules.h"
#include "hw/sh4/sh4_sched.h"
#include "cfg/option.h"
#include "stdclass.h"
#include "serialize.h"
@@ -14,6 +15,7 @@ MapleInputState mapleInputState[4];
extern bool maple_ddt_pending_reset;
extern std::vector<std::pair<u32, std::vector<u32>>> mapleDmaOut;
extern bool SDCKBOccupied;
extern int maple_schid;
void (*MapleConfigMap::UpdateVibration)(u32 port, float power, float inclination, u32 duration_ms);
@@ -227,7 +229,8 @@ static void mcfg_Create(MapleDeviceType type, u32 bus, u32 port, s32 player_num
}
}
std::shared_ptr<maple_device> dev = maple_Create(type);
dev->Setup(bus, port, player_num);
if (dev != nullptr)
dev->Setup(bus, port, player_num);
}
static void createNaomiDevices()
@@ -257,6 +260,12 @@ static void createNaomiDevices()
insertRfidCard(1);
}
}
else if (gameId.substr(0, 4) == "WCCF")
{
if (config::MultiboardSlaves <= 1)
// CCD cam
mcfg_Create(MDT_WccfCamera, 1, 5, 0);
}
else if (gameId == "THE KING OF ROUTE66")
{
mcfg_Create(MDT_SegaController, 1, 5);
@@ -502,6 +511,8 @@ void mcfg_SerializeDevices(Serializer& ser)
{
ser << maple_ddt_pending_reset;
ser << SDCKBOccupied;
sh4_sched_serialize(ser, maple_schid);
ser << (u32)mapleDmaOut.size();
for (const auto& pair : mapleDmaOut)
{
@@ -526,30 +537,23 @@ void mcfg_DeserializeDevices(Deserializer& deser)
{
if (!deser.rollback())
mcfg_DestroyDevices(false);
u8 eeprom[128];
if (deser.version() < Deserializer::V23)
{
deser >> eeprom;
deser.skip(128); // Unused eeprom space
deser.skip<bool>(); // EEPROM_loaded
}
deser >> maple_ddt_pending_reset;
if (deser.version() >= Deserializer::V47)
deser >> SDCKBOccupied;
if (deser.version() >= Deserializer::V62)
sh4_sched_deserialize(deser, maple_schid);
mapleDmaOut.clear();
if (deser.version() >= Deserializer::V23)
u32 size;
deser >> size;
for (u32 i = 0; i < size; i++)
{
u32 size;
deser >> size;
for (u32 i = 0; i < size; i++)
{
u32 address;
deser >> address;
u32 dataSize;
deser >> dataSize;
mapleDmaOut.emplace_back(address, std::vector<u32>(dataSize));
deser.deserialize(mapleDmaOut.back().second.data(), dataSize);
}
u32 address;
deser >> address;
u32 dataSize;
deser >> dataSize;
mapleDmaOut.emplace_back(address, std::vector<u32>(dataSize));
deser.deserialize(mapleDmaOut.back().second.data(), dataSize);
}
for (int i = 0; i < MAPLE_PORTS; i++)
@@ -564,8 +568,6 @@ void mcfg_DeserializeDevices(Deserializer& deser)
MapleDevices[i][j]->deserialize(deser);
}
}
if (deser.version() < Deserializer::V23 && EEPROM != nullptr)
memcpy(EEPROM, eeprom, sizeof(eeprom));
}
std::shared_ptr<MIE> getMieDevice()
+9 -1
View File
@@ -25,6 +25,7 @@ enum MapleDeviceType
MDT_Dreameye = 17,
MDT_SegaControllerXL = 18,
MDT_DreamParaParaController = 19,
MDT_WccfCamera = 20,
MDT_Count
};
@@ -48,6 +49,11 @@ enum PlainJoystickTriggerId
PJTI_Count = 4
};
enum class MaplePortType {
user_accessible,
built_in,
};
struct PlainJoystickState
{
PlainJoystickState()
@@ -118,10 +124,12 @@ void mcfg_DestroyDevices(bool full = true);
void mcfg_SerializeDevices(Serializer& ser);
void mcfg_DeserializeDevices(Deserializer& deser);
constexpr int maple_getPortCount(MapleDeviceType type)
constexpr int maple_getPortCount(MapleDeviceType type, MaplePortType port)
{
switch (type)
{
case MDT_FishingController:
return port == MaplePortType::user_accessible ? 0 : 1;
case MDT_SegaController:
case MDT_SegaControllerXL:
return 2;
+1 -2
View File
@@ -841,7 +841,6 @@ struct maple_microphone: maple_base
deser >> gain;
deser >> sampling;
deser >> eight_khz;
deser.skip(480 - sizeof(u32) - sizeof(bool) * 2, Deserializer::V23);
if (sampling)
StartAudioRecording(eight_khz);
}
@@ -1888,10 +1887,10 @@ std::shared_ptr<maple_device> maple_Create(MapleDeviceType type)
case MDT_SegaControllerXL: return std::make_shared<FullController>();
case MDT_DreamParaParaController: return std::make_shared<maple_dreamparapara_controller>();
case MDT_RFIDReaderWriter: return RFIDReaderWriter::Create();
case MDT_WccfCamera: return WccfCamera::Create();
default:
ERROR_LOG(MAPLE, "Invalid device type %d", type);
die("Invalid maple device type");
break;
}
return nullptr;
+8 -4
View File
@@ -2,13 +2,12 @@
#include "types.h"
#include "maple_cfg.h"
#include "maple_helper.h"
#include <cmath>
#include "input/gamepad.h"
#include "serialize.h"
#include "hw/hwreg.h"
#include <cmath>
#include <memory>
#include <vector>
enum MapleFunctionID
{
@@ -204,8 +203,6 @@ void limit_joystick_magnitude(s8& joyx, s8& joyy)
}
}
extern u8 *EEPROM;
#define SWAP32(a) ((((a) & 0xff) << 24) | (((a) & 0xff00) << 8) | (((a) >> 8) & 0xff00) | (((a) >> 24) & 0xff))
const char *GetCurrentGameButtonName(DreamcastKey key);
@@ -305,6 +302,8 @@ struct BaseMIE : public maple_base
u32 dma(u32 cmd) override;
void reply(u8 code, u8 sizew = 0);
virtual u8 getExtDeviceMap() const { return maple_GetAttachedDevices(bus_id); }
virtual void handle_86_subcommand();
virtual void firmwareLoaded(u32 hash) {}
};
@@ -318,3 +317,8 @@ struct RFIDReaderWriter : public BaseMIE
{
static std::shared_ptr<maple_device> Create();
};
struct WccfCamera : public BaseMIE
{
static std::shared_ptr<maple_device> Create();
};
+2 -2
View File
@@ -21,7 +21,7 @@ enum MaplePattern
std::shared_ptr<maple_device> MapleDevices[MAPLE_PORTS][6];
int maple_schid;
int maple_schid = -1;
/*
Maple host controller
@@ -219,7 +219,7 @@ static void maple_DoDma()
// and send everything to the main device.
auto pDevice = MapleDevices[bus][5];
if (pDevice != nullptr
&& maple_getPortCount(pDevice->get_device_type()) != 0)
&& maple_getPortCount(pDevice->get_device_type(), MaplePortType::built_in) != 0)
{
port = getPort(reci);
if (port != 5)
+88 -45
View File
@@ -22,11 +22,15 @@
#include <xxhash.h>
#include "oslib/oslib.h"
#include "oslib/i18n.h"
#include "oslib/directory.h"
#include "stdclass.h"
#include "cfg/option.h"
#include "network/output.h"
#include "hw/naomi/printer.h"
#include "hw/naomi/card_reader.h"
#include "hw/sh4/sh4_sched.h"
#include "input/haptic.h"
#include "hw/sh4/sh4_mem.h"
#include <algorithm>
#include <array>
@@ -635,10 +639,7 @@ public:
}
void deserialize(Deserializer& deser) override
{
if (deser.version() >= Deserializer::V31)
deser >> out;
else
out = 0xff;
deser >> out;
jvs_837_13844::deserialize(deser);
}
@@ -756,10 +757,7 @@ public:
}
void deserialize(Deserializer& deser) override
{
if (deser.version() >= Deserializer::V31)
deser >> testMode;
else
testMode = false;
deser >> testMode;
if (deser.version() >= Deserializer::V51)
{
deser >> damper_high;
@@ -942,8 +940,7 @@ public:
}
void deserialize(Deserializer& deser) override
{
if (deser.version() >= Deserializer::V31)
deser >> gear;
deser >> gear;
jvs_837_13844_racing::deserialize(deser);
}
@@ -1022,6 +1019,69 @@ public:
}
};
class jvs_837_13844_wccf : public jvs_837_13844
{
public:
jvs_837_13844_wccf(u8 node_id, MIEImpl *parent, int first_player = 0)
: jvs_837_13844(node_id, parent, first_player)
{}
void write_digital_out(int count, const u8 *data) override
{
jvs_837_13844::write_digital_out(count, data);
if (count >= 3)
{
if (data[2] & 0x80) {
// solenoid is on
solenoidOn = true;
solenoidWait = 0;
}
else
{
// solenoid is off
if (solenoidOn)
{
// wait 30 frames before ejecting the card
if (++solenoidWait >= 30) {
card_reader::ejectCard();
solenoidOn = false;
}
}
}
if (data[0] & 1)
{
// Player card dispenser
if (cardDispenser == 0)
NOTICE_LOG(JVS, "WCCF player card dispensed");
cardDispenser = sh4_sched_now64();
}
}
}
void read_digital_in(const u32 *buttons, u32 *v) override
{
jvs_837_13844::read_digital_in(buttons, v);
v[1] |= NAOMI_RIGHT_KEY | NAOMI_BTN1_KEY; // card dispenser sensor off, standby on
if (cardDispenser != 0)
{
// idle: wait for owed cards > 0 && standby=on
// payout: assert card vendor payout
// payoutwait: wait for !sensor on, decrement owed cards
// recvwait: wait for !sensor off -> idle
u64 now = sh4_sched_now64();
if (now - cardDispenser > 1000_sh4ms)
cardDispenser = 0;
else
v[1] &= ~(NAOMI_RIGHT_KEY | NAOMI_BTN1_KEY); // card dispenser sensor on, standby off
}
}
private:
bool solenoidOn = false;
int solenoidWait = 0;
u64 cardDispenser = 0;
};
// Ninja assault
class jvs_namco_jyu : public jvs_io_board
{
@@ -1309,8 +1369,11 @@ u32 BaseMIE::RawDma(const u32 *buffer_in, u32 buffer_in_len, u32 *buffer_out)
u32 resp = dma(cmd);
if (resp != MDRE_UnknownCmd)
{
const u32 reci = (buffer_in[0] >> 8) & 0xFF;
u32 reci = (buffer_in[0] >> 8) & 0xFF;
const u32 sender = (buffer_in[0] >> 16) & 0xFF;
if (reci & 0x20)
reci |= getExtDeviceMap();
buffer_out[0] = (resp << 0 ) | (sender << 8) | (reci << 16) | ((out_len / 4) << 24);
return out_len + 4;
}
@@ -1383,7 +1446,7 @@ u32 BaseMIE::RawDma(const u32 *buffer_in, u32 buffer_in_len, u32 *buffer_out)
case MDC_JVSGetId:
{
DEBUG_LOG(JVS, "bus[%d] JVS Get Id", bus_id);
LOGJVS("bus[%d] JVS Get Id", bus_id);
static const char ID[56] = "315-6149 COPYRIGHT SEGA ENTERPRISES CO,LTD. 1998";
reply(MDRS_JVSGetIdReply, 7);
wptr(ID, 28);
@@ -1395,7 +1458,7 @@ u32 BaseMIE::RawDma(const u32 *buffer_in, u32 buffer_in_len, u32 *buffer_out)
break;
default:
INFO_LOG(MAPLE, "BaseMIE: Unknown Maple command %x", cmd);
INFO_LOG(MAPLE, "bus[%d] BaseMIE: Unknown Maple command %x", bus_id, cmd);
reply(MDRE_UnknownCmd);
break;;
}
@@ -1598,6 +1661,10 @@ MIEImpl::MIEImpl()
{
io_boards.push_back(std::make_unique<jvs_837_13844_samba>(1, this));
}
else if (gameId.substr(0, 4) == "WCCF" && config::MultiboardSlaves <= 1)
{
io_boards.push_back(std::make_unique<jvs_837_13844_wccf>(1, this));
}
else
{
if (gameId == "POKASUKA GHOST (JAPANESE)" // Manic Panic Ghosts
@@ -1894,18 +1961,7 @@ void MIEImpl::handle_86_subcommand()
std::string eeprom_file = getEepromPath();
if (!eeprom_file.empty())
{
FILE* f = nowide::fopen(eeprom_file.c_str(), "wb");
if (f)
{
std::fwrite(eeprom, 1, sizeof(eeprom), f);
std::fclose(f);
INFO_LOG(MAPLE, "Saved EEPROM to %s", eeprom_file.c_str());
}
else {
WARN_LOG(MAPLE, "EEPROM SAVE FAILED to %s", eeprom_file.c_str());
}
}
safeFullSave(eeprom_file, eeprom, sizeof(eeprom));
reply(MDRS_JVSReply, 1);
memcpy(dma_buffer_out, eeprom, 4);
@@ -2038,18 +2094,13 @@ void MIEImpl::deserialize(Deserializer& deser)
{
maple_base::deserialize(deser);
deser >> crazy_mode;
if (deser.version() >= Deserializer::V35)
deser >> hotd2p;
else
hotd2p = settings.content.gameId == "hotd2p";
deser >> hotd2p;
deser >> jvs_repeat_request;
deser >> jvs_receive_length;
deser >> jvs_receive_buffer;
if (deser.version() >= Deserializer::V23)
deser >> eeprom;
deser >> eeprom;
u32 board_count;
deser >> board_count;
deser.skip(sizeof(size_t) - sizeof(u32), Deserializer::V23);
for (u32 i = 0; i < board_count; i++)
io_boards[i]->deserialize(deser);
}
@@ -2638,7 +2689,7 @@ u32 RFIDReaderWriterImpl::dma(u32 cmd)
return (MapleDeviceRV)0xfe;
case 0xA1: // read card data
DEBUG_LOG(JVS, "RFID card read (data? %d)", d4Seen);
LOGJVS("RFID card read (data? %d)", d4Seen);
w32(getStatus());
if (!d4Seen)
// serial0 and serial1 only
@@ -2667,7 +2718,7 @@ u32 RFIDReaderWriterImpl::dma(u32 cmd)
u32 offset = r8() * 4;
size_t size = r8() * 4;
skip(2);
DEBUG_LOG(JVS, "RFID card write: offset 0x%x len %d", offset, (int)size);
LOGJVS("RFID card write: offset 0x%x len %d", offset, (int)size);
rptr(cardData + offset, std::min(size, sizeof(cardData) - offset));
saveCard();
return (MapleDeviceRV)0xfe;
@@ -2694,7 +2745,7 @@ u32 RFIDReaderWriterImpl::dma(u32 cmd)
counter = 0;
break;
}
DEBUG_LOG(JVS, "RFID decrement %d", counter);
LOGJVS("RFID decrement %d", counter);
cardData[19 - counter]--;
saveCard();
w32(getStatus());
@@ -2802,17 +2853,9 @@ void RFIDReaderWriterImpl::saveCard() const
{
if (transientData)
return;
std::string path = getCardPath();
FILE *fp = nowide::fopen(path.c_str(), "wb");
if (fp == nullptr)
{
WARN_LOG(NAOMI, "Can't create card file %s: errno %d", path.c_str(), errno);
return;
}
const std::string path = getCardPath();
INFO_LOG(NAOMI, "Saving card file to %s", path.c_str());
if (fwrite(cardData, 1, sizeof(cardData), fp) != sizeof(cardData))
WARN_LOG(NAOMI, "Truncated write to file: %s", path.c_str());
fclose(fp);
safeFullSave(path, cardData, sizeof(cardData));
}
void RFIDReaderWriterImpl::serialize(Serializer& ser) const
File diff suppressed because it is too large. Load diff
+26 -11
View File
@@ -61,8 +61,11 @@ void *readConst(u32 addr, bool& ismem, u32 sz)
else
{
ismem = true;
addr <<= iirf;
addr >>= iirf;
// Only the low bits of iirf hold the mask shift. Shifting a 32-bit value by the
// whole tagged pointer is undefined, and GCC 16 on 32-bit ARM drops the
// address entirely (every access hits offset 0).
addr <<= (iirf & HANDLER_MAX);
addr >>= (iirf & HANDLER_MAX);
return &(((u8 *)ptr)[addr]);
}
@@ -94,8 +97,11 @@ void *writeConst(u32 addr, bool& ismem, u32 sz)
else
{
ismem = true;
addr <<= iirf;
addr >>= iirf;
// Only the low bits of iirf hold the mask shift. Shifting a 32-bit value by the
// whole tagged pointer is undefined, and GCC 16 on 32-bit ARM drops the
// address entirely (every access hits offset 0).
addr <<= (iirf & HANDLER_MAX);
addr >>= (iirf & HANDLER_MAX);
return &(((u8 *)ptr)[addr]);
}
@@ -112,8 +118,11 @@ T DYNACALL readt(u32 addr)
if (likely(ptr != nullptr))
{
addr <<= iirf;
addr >>= iirf;
// Only the low bits of iirf hold the mask shift. Shifting a 32-bit value by the
// whole tagged pointer is undefined, and GCC 16 on 32-bit ARM drops the
// address entirely (every access hits offset 0).
addr <<= (iirf & HANDLER_MAX);
addr >>= (iirf & HANDLER_MAX);
return *(T *)&((u8 *)ptr)[addr];
}
@@ -156,8 +165,11 @@ void DYNACALL writet(u32 addr, T data)
if (likely(ptr != nullptr))
{
addr <<= iirf;
addr >>= iirf;
// Only the low bits of iirf hold the mask shift. Shifting a 32-bit value by the
// whole tagged pointer is undefined, and GCC 16 on 32-bit ARM drops the
// address entirely (every access hits offset 0).
addr <<= (iirf & HANDLER_MAX);
addr >>= (iirf & HANDLER_MAX);
*(T *)&((u8 *)ptr)[addr] = data;
}
@@ -398,6 +410,9 @@ static void termMappings()
aica::aica_ram.free();
free_pages(elan::RAM);
elan::RAM = nullptr;
// Don't leave the address map pointing at freed memory. A null entry
// routes accesses to handler 0 (not mapped), which touches nothing.
memset(memInfo_ptr, 0, sizeof(memInfo_ptr));
}
}
@@ -407,7 +422,7 @@ void initMappings()
// Fallback to statically allocated buffers, this results in slow-ops being generated.
if (ram_base == nullptr)
{
WARN_LOG(VMEM, "Warning! nvmem is DISABLED (due to failure or not being built-in");
WARN_LOG(VMEM, "Warning! nvmem is DISABLED (due to failure or not being built-in)");
// Allocate it all and initialize it.
p_sh4rcb = (Sh4RCB*)malloc_pages(sizeof(Sh4RCB));
@@ -535,8 +550,8 @@ u32 getVramOffset(void *addr)
else
#endif
{
ptrdiff_t offset = (u8*)addr - &vram[0];
if (offset < 0 || offset >= VRAM_SIZE)
uintptr_t offset = virtmem::untag(addr) - virtmem::untag(&vram[0]);
if (offset >= VRAM_SIZE)
return -1;
return (u32)offset;
+6 -4
View File
@@ -41,9 +41,10 @@ void AicaRamWatcher::unprotectMem(u32 addr, u32 size)
u32 AicaRamWatcher::getMemOffset(void *p)
{
if ((u8 *)p < &aica::aica_ram[0] || (u8 *)p >= &aica::aica_ram[ARAM_SIZE])
uintptr_t offset = virtmem::untag(p) - virtmem::untag(&aica::aica_ram[0]);
if (offset >= ARAM_SIZE)
return -1;
return (u32)((u8 *)p - &aica::aica_ram[0]);
return (u32)offset;
}
void ElanRamWatcher::protectMem(u32 addr, u32 size)
@@ -69,9 +70,10 @@ void ElanRamWatcher::unprotectMem(u32 addr, u32 size)
u32 ElanRamWatcher::getMemOffset(void *p)
{
using namespace elan;
if ((u8 *)p < RAM || (u8 *)p >= &RAM[ERAM_SIZE])
uintptr_t offset = virtmem::untag(p) - virtmem::untag(RAM);
if (offset >= ERAM_SIZE)
return -1;
return (u32)((u8 *)p - RAM);
return (u32)offset;
}
}
+177 -36
View File
@@ -59,7 +59,7 @@ static u8 dspram[0x1000];
static_assert(sizeof(regs_write_mask) == sizeof(modem_regs));
static_assert(sizeof(por_dspram) == sizeof(dspram));
static int modem_sched;
static int modem_sched = -1;
enum ModemStates
{
@@ -82,13 +82,24 @@ enum ConnectState
HANDSHAKING,
PRE_CONNECTED,
CONNECTED,
NEGO_COMPLETE
NEGO_COMPLETE,
RINGING_IN,
};
static ConnectState connect_state = DISCONNECTED;
static float txFifoSize = 0.f;
static std::deque<u8> rxFifo;
static int cyclesPerByte;
static float txRxRatio = 1.f;
static std::string numberDialed;
static std::function<void()> onAnswerCallback;
static std::function<void()> onHandshakeCallback;
static std::function<void(const std::string&)> onDialCallback;
static std::function<void()> periodicCallback;
static u32 periodicCycles;
static u64 lastDSPRamWrite;
static u64 ringStart;
static bool peer2peerRawMode;
static bool keepRinging;
static void schedule_callback(int ms);
static const char *getDSPRamLabel(u32 addr);
@@ -166,6 +177,7 @@ static InStream *curInput = &netIn;
static OutStream *curOutput = &netOut;
static V42Protocol v42Proto { netIn, netOut };
static HdlcSyncAsync hdlcProto { netIn, netOut };
static bool v8bis;
static V8bisProtocol v8bisProto;
@@ -196,9 +208,16 @@ static void configureStreams()
curInput = &v8bisProto;
curOutput = &v8bisProto;
}
else if (modem_regs.reg08.ASYN == 0) {
curInput = &v42Proto;
curOutput = &v42Proto;
else if (modem_regs.reg08.ASYN == 0)
{
if (peer2peerRawMode) {
curInput = &hdlcProto;
curOutput = &hdlcProto;
}
else {
curInput = &v42Proto;
curOutput = &v42Proto;
}
}
else {
curInput = &netIn;
@@ -334,6 +353,16 @@ static bool setModemSpeedFromCONF()
return speed != 0;
}
static void hangUp()
{
INFO_LOG(MODEM, "Hanging up modem");
modem_regs.ABCODE = 0x96;
modem_regs.reg1f.NEWS = 1;
modem_regs.CONF = 0xC0;
dspram[0x2e4] = 0;
connect_state = DISCONNECTED;
}
static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
{
#ifndef NDEBUG
@@ -362,6 +391,11 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
break;
case MS_END_DSP:
DSPTestEnd();
if (ringStart != 0) {
// The game resets the modem before answering a call so restore the correct state
connect_state = RINGING;
ringStart = 0;
}
break;
case MS_NORMAL:
modem_regs.reg1f.NEWC = 0; // Not needed when state is CONNECTED but who cares
@@ -369,18 +403,24 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
switch (connect_state)
{
case DIALING:
if (last_dial_time != 0 && sh4_sched_now64() - last_dial_time >= SH4_MAIN_CLOCK)
if (last_dial_time == 0)
last_dial_time = sh4_sched_now64();
if (sh4_sched_now64() - last_dial_time >= SH4_MAIN_CLOCK)
{
LOG("Switching to RINGING state");
connect_state = RINGING;
schedule_callback(100);
if (!numberDialed.empty())
{
NOTICE_LOG(MODEM, "Dialing %s", numberDialed.c_str());
if (onDialCallback)
onDialCallback(numberDialed);
numberDialed.clear();
}
}
else
{
last_dial_time = sh4_sched_now64();
else {
modem_regs.reg1e.TDBE = 1;
schedule_callback(1000); // To switch to Ringing state
callback_cycles = 1000_sh4ms; // To switch to Ringing state
}
break;
case RINGING:
@@ -388,11 +428,8 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
last_dial_time = 0;
LOG("\t\t *** %s STATE ***", connect_state == RINGING ? "RINGING" : "NEGO COMPLETE");
modem_regs.reg1f.NEWS = 1;
if (!modem_regs.reg09.DATA && connect_state == RINGING)
{
SET_STATUS_BIT(0x0f, modem_regs.reg0f.RI, 1);
if (!modem_regs.reg09.DATA && connect_state == RINGING && modem_regs.reg09.ORG)
SET_STATUS_BIT(0x0b, modem_regs.reg0b.ATV25, 1);
}
break;
case HANDSHAKING:
LOG("\t\t *** HANDSHAKING STATE ***");
@@ -410,8 +447,7 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
break;
case PRE_CONNECTED:
if (modem_regs.reg03.RLSDE)
SET_STATUS_BIT(0x0f, modem_regs.reg0f.RLSD, 1);
SET_STATUS_BIT(0x0f, modem_regs.reg0f.RLSD, 1);
bool validConfig;
if (modem_regs.CONF == 0xAA)
{
@@ -493,8 +529,11 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
dspram[0x208] = 0xff; // 2.4 - 19.2 kpbs supported
dspram[0x209] = 0xbf; // 21.6 - 33.6 kpbs supported, asymmetric supported
callback_cycles = SH4_MAIN_CLOCK / 1000000 * 238; // 238 us
callback_cycles = 238_sh4us;
v42Proto.reset();
v42Proto.setOriginator(modem_regs.reg09.ORG);
hdlcProto.reset();
hdlcProto.setOriginator(modem_regs.reg09.ORG);
break;
@@ -519,14 +558,46 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
}
if (rxFifo.size() < 16)
{
int c = curInput->read();
if (c >= 0) {
rxFifo.push_back(c);
updateRxFifoStatus();
try {
int c = curInput->read();
if (c >= 0) {
rxFifo.push_back(c);
updateRxFifoStatus();
}
} catch (const net::modbba::ConnectionClosed& e) {
hangUp();
}
}
break;
case DISCONNECTED:
if (onAnswerCallback)
{
callback_cycles = SH4_MAIN_CLOCK;
if (lastDSPRamWrite != 0
&& sh4_sched_now64() - lastDSPRamWrite >= (u64)5 * SH4_MAIN_CLOCK)
{
INFO_LOG(MODEM, "Answer mode detected");
onAnswerCallback();
lastDSPRamWrite = 0;
callback_cycles = 0;
}
}
break;
case RINGING_IN:
{
// Japan standard: 1 s on, 2 s off
u64 v = (sh4_sched_now64() - ringStart) % ((u64)3 * SH4_MAIN_CLOCK);
bool ri = v < SH4_MAIN_CLOCK;
if (ri != modem_regs.reg0f.RI) {
modem_regs.reg1f.NEWS = 1;
SET_STATUS_BIT(0x0f, modem_regs.reg0f.RI, ri);
}
callback_cycles = SH4_MAIN_CLOCK;
break;
}
default:
break;
}
@@ -537,6 +608,13 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
}
update_interrupt();
if (periodicCallback)
{
periodicCallback();
if (callback_cycles == 0 || callback_cycles > (int)periodicCycles)
callback_cycles = periodicCycles;
}
return callback_cycles;
}
@@ -548,6 +626,15 @@ void ModemInit()
void ModemReset()
{
net::modbba::stop();
onAnswerCallback = {};
onHandshakeCallback = {};
onDialCallback = {};
periodicCallback = {};
periodicCycles = 0;
lastDSPRamWrite = 0;
ringStart = 0;
peer2peerRawMode = false;;
keepRinging = false;
}
void ModemTerm()
@@ -660,6 +747,8 @@ static void modem_reset(u32 v)
state = MS_RESET;
LOG("Modem reset start ...");
net::modbba::stop();
lastDSPRamWrite = 0;
keepRinging = false;
}
else
{
@@ -678,11 +767,14 @@ static void check_start_handshake()
{
if (modem_regs.reg09.DTR
&& (modem_regs.reg09.DATA || modem_regs.reg15.AUTO)
&& (connect_state == RINGING || connect_state == NEGO_COMPLETE))
&& (connect_state == RINGING || connect_state == NEGO_COMPLETE)
&& !keepRinging)
{
LOG("DTR asserted. starting handshaking");
connect_state = HANDSHAKING;
schedule_callback(1);
if (onHandshakeCallback)
onHandshakeCallback();
}
}
@@ -748,7 +840,7 @@ static void ModemNormalWrite(u32 reg, u32 data)
{
download_crc = (download_crc << 1) + ((download_crc & 0x80) >> 7) + (data & 0xFF);
}
else if (connect_state == DISCONNECTED || connect_state == DIALING)
else if ((connect_state == DISCONNECTED || connect_state == DIALING) && !modem_regs.reg09.DATA)
{
//LOG("ModemNormalWrite : TBUFFER = %X", data);
if (connect_state == DISCONNECTED)
@@ -756,7 +848,10 @@ static void ModemNormalWrite(u32 reg, u32 data)
INFO_LOG(MODEM, "MODEM Dialing");
connect_state = DIALING;
modem_regs.SECRXB = 0;
numberDialed.clear();
}
if (data <= 9)
numberDialed += '0' + data;
schedule_callback(100);
}
else if (connect_state == CONNECTED && modem_regs.reg08.RTS)
@@ -765,7 +860,12 @@ static void ModemNormalWrite(u32 reg, u32 data)
#ifndef NDEBUG
sent_bytes++;
#endif
curOutput->write(data);
try {
curOutput->write(data);
} catch (const net::modbba::ConnectionClosed& e) {
hangUp();
break;
}
modem_regs.reg1e.TDBE = 0;
txFifoSize += 1.f;
@@ -860,6 +960,10 @@ static void ModemNormalWrite(u32 reg, u32 data)
}
if ((dspram_addr == 0x26B || dspram_addr == 0x26c) && (modem_regs.MEDA & 0xFF) != 0)
dspram[0x26F] = 1; // Saved Filtered EQM
if (connect_state == DISCONNECTED && onAnswerCallback) {
lastDSPRamWrite = sh4_sched_now64();
schedule_callback(1000);
}
}
else
{
@@ -1048,19 +1152,17 @@ void ModemSerialize(Serializer& ser)
}
void ModemDeserialize(Deserializer& deser)
{
if (!config::EmulateBBA || deser.version() > Deserializer::V31)
sh4_sched_deserialize(deser, modem_sched);
if (deser.version() >= Deserializer::V20)
{
deser >> modem_regs;
deser >> dspram;
deser >> state;
deser >> connect_state;
deser >> last_dial_time;
deser.skip<bool>(Deserializer::V58);
}
sh4_sched_deserialize(deser, modem_sched);
deser >> modem_regs;
deser >> dspram;
deser >> state;
deser >> connect_state;
deser >> last_dial_time;
deser.skip<bool>(Deserializer::V58);
rxFifo.clear();
v42Proto.reset();
hdlcProto.reset();
v8bis = false;
v8bisProto.reset();
configureStreams();
@@ -1211,3 +1313,42 @@ static const char *getDSPRamLabel(u32 addr)
else
return it->second;
}
void modemOnAnswerMode(std::function<void()> callback) {
onAnswerCallback = callback;
}
void modemOnDial(std::function<void(const std::string&)> callback) {
onDialCallback = callback;
}
void modemOnHandshake(std::function<void()> callback) {
onHandshakeCallback = callback;
}
void modemPeriodicCallback(u32 cycles, std::function<void()> callback)
{
periodicCallback = callback;
periodicCycles = cycles;
if (cycles != 0 && !sh4_sched_is_scheduled(modem_sched))
sh4_sched_request(modem_sched, cycles);
}
void modemPeer2Peer(bool enabled) {
peer2peerRawMode = enabled;
configureStreams();
}
void modemKeepRinging(bool enabled)
{
keepRinging = enabled;
if (!enabled)
check_start_handshake();
}
void modemIncomingCall()
{
connect_state = RINGING_IN;
modem_regs.reg1f.NEWS = 1;
SET_STATUS_BIT(0x0f, modem_regs.reg0f.RI, 1);
ringStart = sh4_sched_now64();
schedule_callback(1000);
}
+8
View File
@@ -23,6 +23,7 @@
#pragma once
#include "types.h"
#include <functional>
void ModemInit();
void ModemReset();
@@ -31,3 +32,10 @@ u32 ModemReadMem_A0_006(u32 addr,u32 size);
void ModemWriteMem_A0_006(u32 addr,u32 data,u32 size);
void ModemSerialize(Serializer& ser);
void ModemDeserialize(Deserializer& deser);
void modemOnAnswerMode(std::function<void()> callback);
void modemOnHandshake(std::function<void()> callback);
void modemOnDial(std::function<void(const std::string&)> callback);
void modemPeer2Peer(bool enabled);
void modemKeepRinging(bool enabled);
void modemIncomingCall();
void modemPeriodicCallback(u32 cycles, std::function<void()> callback);
+369 -47
View File
@@ -151,7 +151,7 @@ static u16 calcCrc16(const std::vector<u8>& data, size_t len = 0)
return crc ^ 0xffff;
}
void HdlcEncoder::sendFrame(const std::vector<u8>& data)
void HdlcEncoder::sendFrame(const std::vector<u8>& data, int crc)
{
// opening flag
sendFlag();
@@ -159,7 +159,8 @@ void HdlcEncoder::sendFrame(const std::vector<u8>& data)
for (u8 b : data)
sendByte(b);
// crc
u16 crc = calcCrc16(data);
if (crc < 0)
crc = calcCrc16(data);
sendByte(crc & 0xff);
sendByte(crc >> 8);
// closing flag
@@ -221,7 +222,7 @@ void HdlcDecoder::write(u8 v)
if (rxOnes == 6)
{
// we have a flag
if (!rxFrame.empty())
if (rxFrame.size() >= 4)
{
u16 crc = calcCrc16(rxFrame, rxFrame.size() - 2);
if (crc != *(u16 *)(&rxFrame.back() - 1)) {
@@ -229,12 +230,18 @@ void HdlcDecoder::write(u8 v)
}
else
{
rxFrame.pop_back();
rxFrame.pop_back();
if (!keepCrc) {
rxFrame.pop_back();
rxFrame.pop_back();
}
frames.push_back(std::move(rxFrame));
}
rxFrame.clear();
}
else if (!rxFrame.empty()) {
WARN_LOG(MODEM, "Invalid ~empty frame: size %zd [%02x]", rxFrame.size(), rxFrame[0]);
rxFrame.clear();
}
rxCurByte = 0;
rxPosition = 0;
}
@@ -359,6 +366,8 @@ void V42Protocol::handleFrame()
handleSabme(rxFrame[0], rxFrame[1]);
break;
case 0x0f: // DM
WARN_LOG(MODEM, "Received DM");
phase = Release;
break;
case 0x03: // UI
WARN_LOG(MODEM, "Received UI");
@@ -367,7 +376,17 @@ void V42Protocol::handleFrame()
handleDisc(rxFrame[0]);
break;
case 0x63: // UA
receiverReady = true;
DEBUG_LOG(MODEM, "Received UA");
if (!originator && phase == Establish) {
phase = Connected;
}
else if (phase == DiscSent) {
INFO_LOG(MODEM, "Received UA of DISC");
phase = Release;
}
else {
receiverReady = true;
}
break;
case 0x87: // FRMR
WARN_LOG(MODEM, "Received FRMR");
@@ -393,6 +412,18 @@ void V42Protocol::handleFrame()
}
}
void V42Protocol::sendSabme() {
std::vector<u8> sabme { 3, 0x7f };
sendFrame(sabme);
}
void V42Protocol::sendDisc()
{
std::vector<u8> disc { (u8)(1 | (!originator << 1)), 0x53 };
sendFrame(disc);
phase = DiscSent;
}
void V42Protocol::handleSabme(u8 address, u8 control)
{
if (phase == Connected)
@@ -442,10 +473,7 @@ void V42Protocol::handleIFrame(const std::vector<u8>& rxFrame)
outputStream.write(c);
}
} catch (const v42b::Exception& e) {
// send DISC
std::vector<u8> disc { 1, 0x43 };
sendFrame(disc);
phase = Release;
sendDisc();
sentIFrames.clear();
return;
}
@@ -464,6 +492,29 @@ void V42Protocol::handleIFrame(const std::vector<u8>& rxFrame)
}
}
void V42Protocol::sendXid()
{
// 03 af 82
// [
// gid, sz_msb, sz_lsb
// [
// paramId, valueSize, value...
// ]
// ]
// As sent when originator
std::vector<u8> xid { 0x03, 0xaf, 0x82,
0x80, 0x00, 0x13,
0x03, 0x03, 0x8a, 0x89, 0x00,
0x05, 0x02, 0x02, 0x00,
0x06, 0x02, 0x02, 0x00,
0x07, 0x01, 0x0f,
0x08, 0x01, 0x0f,
0xff, 0x00, 0x05,
0x01, 0x03, 0x4d, 0x4d, 0x41
};
sendFrame(xid);
}
void V42Protocol::handleXid(std::vector<u8> rxFrame)
{
u32 maxCodeWords = 512;
@@ -634,10 +685,12 @@ void V42Protocol::handleXid(std::vector<u8> rxFrame)
i += paramSize;
}
}
if (userDataOffset != 0)
rxFrame.resize(userDataOffset);
sendFrame(rxFrame);
if (originator)
{
if (userDataOffset != 0)
rxFrame.resize(userDataOffset);
sendFrame(rxFrame);
}
if (compressionEnabled)
{
NOTICE_LOG(MODEM, "V.42bis compression enabled: max dictionary size %d", maxCodeWords);
@@ -722,8 +775,24 @@ void V42Protocol::sendIFrame()
int V42Protocol::read()
{
if (phase == None || phase == Detection) {
v14Encoder.flush();
if (phase == None || phase == Detection)
{
if (originator) {
v14Encoder.flush();
}
else if (!v14Encoder.available())
{
if (odpCount <= 20)
{
// send ODP: DC1
v14Encoder.setStopBits(9);
v14Encoder.write(0x91);
v14Encoder.write(0x11);
v14Encoder.setStopBits(1);
v14Encoder.flush();
}
v14Encoder.flush();
}
return v14Encoder.read();
}
else if (phase == V14)
@@ -734,7 +803,7 @@ int V42Protocol::read()
}
if (hdlcEncoder.available() == 0)
{
if (phase == Connected)
if (phase == Connected || phase == DiscSent)
{
sendIFrame();
if (hdlcEncoder.available() != 0)
@@ -748,7 +817,17 @@ int V42Protocol::read()
return hdlcEncoder.read();
}
}
sendFlag();
else if (phase == Establish && !originator)
{
for (int i = 0; i < 16; i++)
sendFlag();
sendXid();
sendSabme();
if (hdlcEncoder.available() != 0)
return hdlcEncoder.read();
}
if (phase != Release)
sendFlag();
}
return hdlcEncoder.read();
}
@@ -757,12 +836,19 @@ void V42Protocol::write(u8 v)
{
if (phase == None || phase == Detection)
{
if (detectionTimer.expired()) {
if (originator && detectionTimer.expired())
{
// V14 isn't currently used in answering mode. If needed, the detectionTimer should probably be reset
// on first write since it's already expired by then.
INFO_LOG(MODEM, "Switching to V.14 mode");
phase = V14;
}
else
{
if (!originator && phase == None) {
phase = Detection;
odpCount = 0;
}
v14Decoder.write(v);
int c = v14Decoder.read();
if (c == -1)
@@ -770,49 +856,81 @@ void V42Protocol::write(u8 v)
v = c;
}
}
if (phase == None)
switch (phase)
{
if ((v == 0x11 || v == 0x91)
&& (v14Decoder.receivedStopBits() == 9 || v14Decoder.receivedStopBits() == 17))
case None:
if (originator)
{
phase = Detection;
lastRx = v;
odpCount = 1;
if ((v == 0x11 || v == 0x91)
&& (v14Decoder.receivedStopBits() >= 9 && v14Decoder.receivedStopBits() <= 17))
{
phase = Detection;
lastRx = v;
odpCount = 1;
}
}
}
else if (phase == Detection)
{
// V.42 ODP: DC1 with alternating parity followed by 8+1 or 16+1 ones
if ((v == 0x11 || v == 0x91)
&& (v14Decoder.receivedStopBits() == 9 || v14Decoder.receivedStopBits() == 17)
&& v != lastRx)
break;
case Detection:
if (originator)
{
odpCount++;
if (odpCount == 4)
// V.42 ODP: DC1 with alternating parity followed by 8+1 or 16+1 ones
if ((v == 0x11 || v == 0x91)
&& (v14Decoder.receivedStopBits() >= 9 && v14Decoder.receivedStopBits() <= 17)
&& v != lastRx)
{
odpCount++;
if (odpCount == 4)
{
odpCount = 0;
// send ADP
v14Encoder.setStopBits(9);
v14Encoder.write('E');
v14Encoder.write('C');
//v14Encoder.write(0); // switches to v14 only and start ppp nego
v14Encoder.setStopBits(1);
}
}
else
{
odpCount = 0;
// send ADP
v14Encoder.setStopBits(9);
v14Encoder.write('E');
v14Encoder.write('C');
//v14Encoder.write(0); // switches to v14 only and start ppp nego
v14Encoder.setStopBits(1);
if (v == 0x7e)
phase = Establish;
}
}
else
{
odpCount = 0;
if (v == 0x7e)
phase = Establish;
// Answerer
if ((v == 'E' || v == 'C')
&& (v14Decoder.receivedStopBits() >= 9 && v14Decoder.receivedStopBits() <= 17)
&& v != lastRx)
{
++odpCount;
}
else
{
if (odpCount >= 10)
phase = Establish;
else
odpCount = 0;
}
}
lastRx = v;
}
else if (phase == Establish || phase == Connected) {
break;
case Establish:
case Connected:
case DiscSent:
hdlcDecoder.write(v);
handleFrame();
}
else if (phase == V14) {
break;
case V14:
v14PipeOut.write(v);
break;
default:
break;
}
}
@@ -850,6 +968,210 @@ void V42Protocol::ackIFrame(int seqNum)
}
}
int HdlcSyncAsync::read()
{
if (phase == None || phase == Detection)
{
if (originator) {
v14Encoder.flush();
}
else if (!v14Encoder.available())
{
if (odpCount <= 20)
{
// send ODP: DC1
v14Encoder.setStopBits(9);
v14Encoder.write(0x91);
v14Encoder.write(0x11);
v14Encoder.setStopBits(1);
v14Encoder.flush();
}
v14Encoder.flush();
}
return v14Encoder.read();
}
if (hdlcEncoder.available() == 0)
{
if (phase == Connected)
{
if (hdlcEncoder.available() == 0)
handleAsyncFrame();
if (hdlcEncoder.available() != 0)
return hdlcEncoder.read();
}
if (phase != Release)
hdlcEncoder.sendFlag();
}
return hdlcEncoder.read();
}
void HdlcSyncAsync::write(u8 v)
{
if (phase == None || phase == Detection)
{
if (!originator && phase == None) {
phase = Detection;
odpCount = 0;
}
v14Decoder.write(v);
int c = v14Decoder.read();
if (c == -1)
return;
v = c;
}
switch (phase)
{
case None:
if (originator)
{
if ((v == 0x11 || v == 0x91)
&& (v14Decoder.receivedStopBits() >= 9 && v14Decoder.receivedStopBits() <= 17))
{
phase = Detection;
lastRx = v;
odpCount = 1;
}
}
break;
case Detection:
if (originator)
{
// V.42 ODP: DC1 with alternating parity followed by 8+1 or 16+1 ones
if ((v == 0x11 || v == 0x91)
&& (v14Decoder.receivedStopBits() >= 9 && v14Decoder.receivedStopBits() <= 17)
&& v != lastRx)
{
odpCount++;
if (odpCount == 4)
{
odpCount = 0;
// send ADP
v14Encoder.setStopBits(9);
v14Encoder.write('E');
v14Encoder.write('C');
v14Encoder.setStopBits(1);
}
}
else
{
odpCount = 0;
if (v == 0x7e)
phase = Connected;
}
}
else
{
// Answerer
if ((v == 'E' || v == 'C')
&& (v14Decoder.receivedStopBits() >= 9 && v14Decoder.receivedStopBits() <= 17)
&& v != lastRx)
{
++odpCount;
}
else
{
if (odpCount >= 10)
phase = Connected;
else
odpCount = 0;
}
}
lastRx = v;
break;
case Connected:
hdlcDecoder.write(v);
handleSyncFrame();
break;
default:
break;
}
}
void HdlcSyncAsync::handleSyncFrame()
{
if (!hdlcDecoder.frameAvailable())
return;
outputStream.write(FLAG);
std::vector<u8> frame = hdlcDecoder.getFrame();
for (u8 b : frame)
{
if (b == FLAG || b == ESCAPE) {
outputStream.write(ESCAPE);
b ^= 0x20;
}
outputStream.write(b);
}
outputStream.write(FLAG);
}
void HdlcSyncAsync::handleAsyncFrame()
{
if (inputStream.available() == 0)
return;
bool frameComplete = false;
for (;;)
{
int c = inputStream.read();
if (c == -1)
break;
if (c == FLAG)
{
if (inFrame.empty())
continue;
frameComplete = true;
break;
}
else if (c == ESCAPE) {
escapeSeen = true;
continue;
}
if (escapeSeen) {
c ^= 0x20;
escapeSeen = false;
}
inFrame.push_back(c & 0xff);
}
if (!frameComplete)
return;
if (inFrame.size() >= 4)
{
u16 crc = *(u16 *)&inFrame[inFrame.size() - 2];
inFrame.pop_back();
inFrame.pop_back();
/*
if ((inFrame[0] & 0xfd) == 1)
{
if (inFrame[1] == 9)
WARN_LOG(MODEM, "[%d] Received REJ %d", originator, inFrame[2] >> 1);
else if (inFrame[1] == 0xd)
WARN_LOG(MODEM, "[%d] Received SREJ %d", originator, inFrame[2] >> 1);
}
*/
hdlcEncoder.sendFrame(inFrame, crc);
}
else if (!inFrame.empty()) {
WARN_LOG(MODEM, "Small frame (%d) received", (int)inFrame.size());
}
inFrame.clear();
}
void HdlcSyncAsync::reset()
{
phase = None;
lastRx = 0;
odpCount = 0;
v14Encoder.reset();
v14Decoder.reset();
hdlcEncoder.reset();
hdlcDecoder.reset();
inFrame.clear();
escapeSeen = false;
}
u8 V8bisProtocol::detectTone()
{
switch (toneState)
+67 -2
View File
@@ -20,7 +20,6 @@
#include "types.h"
#include "internal.h"
#include "v42bis.h"
#include "network/netservice.h"
#include <vector>
#include <deque>
#include <unordered_map>
@@ -71,6 +70,9 @@ private:
class HdlcDecoder : public OutStream
{
public:
HdlcDecoder(bool keepCrc = false)
: keepCrc(keepCrc)
{}
void write(u8 v);
bool frameAvailable() const { return !frames.empty(); }
std::vector<u8> getFrame();
@@ -82,12 +84,13 @@ private:
int rxOnes = 0;
int rxPosition = 0;
u8 rxCurByte = 0;
bool keepCrc;
};
class HdlcEncoder : public BufferedTransformer
{
public:
void sendFrame(const std::vector<u8>& data);
void sendFrame(const std::vector<u8>& data, int crc = -1);
void sendFlag();
void reset() override;
@@ -114,15 +117,21 @@ public:
return 1;
}
void setOriginator(bool originator) {
this->originator = originator;
}
private:
InStream& inputStream;
OutStream& outputStream;
bool originator = true;
enum Phase {
None,
Detection,
Establish,
Connected,
DiscSent,
Release,
V14 // Bomberman Online, Power Smash, Pro Yakyu Team de asobou net!
};
@@ -181,10 +190,66 @@ private:
void sendIFrame();
void ackIFrame(int seqNum);
void sendDisc();
// Answerer
void sendSabme();
void sendXid();
friend class V42Test;
};
//
// Converts between synchronous HDLC and HDLC-like asynchronous frames
// as used by ppp. This is used for Capcom peer-to-peer communication.
// Upper layer protocols such as V.42 (and potentially V.42bis) are implemented
// natively by each peer. This way error correction works end to end.
//
class HdlcSyncAsync : public InStream, public OutStream
{
public:
HdlcSyncAsync(InStream& in, OutStream& out)
: inputStream(in), outputStream(out)
{}
int read() override;
void write(u8 v) override;
void reset();
int available() override {
return 1;
}
void setOriginator(bool originator) {
this->originator = originator;
}
private:
void handleSyncFrame();
void handleAsyncFrame();
InStream& inputStream;
OutStream& outputStream;
bool originator = true;
enum Phase {
None,
Detection,
Connected,
Release,
};
Phase phase = None;
u8 lastRx = 0;
int odpCount = 0;
V14Encoder v14Encoder;
V14Decoder v14Decoder;
HdlcEncoder hdlcEncoder;
HdlcDecoder hdlcDecoder {true};
std::vector<uint8_t> inFrame;
bool escapeSeen = false;
static constexpr uint8_t FLAG = 0x7e;
static constexpr uint8_t ESCAPE = 0x7d;
};
//
// V.8 bis is used by older browsers (Planet Web 1.125, DreamKey 1.0/1.5 and the many games that include them)
// and Windows CE games.
-1
View File
@@ -19,7 +19,6 @@
#pragma once
#include "types.h"
#include "internal.h"
#include <deque>
#include <map>
#include <vector>
#include <stdexcept>
+2 -4
View File
@@ -154,10 +154,8 @@ void serialize(Serializer& ser)
void deserialize(Deserializer& deser)
{
deser >> aw_maple_devs;
if (deser.version() >= Deserializer::V20) {
deser >> coin_chute_time;
deser >> aw_ram_test_skipped;
}
deser >> coin_chute_time;
deser >> aw_ram_test_skipped;
}
} // namespace atomiswave
+353 -9
View File
@@ -18,10 +18,13 @@
*/
#include "card_reader.h"
#include "oslib/oslib.h"
#include "oslib/directory.h"
#include "hw/sh4/modules/modules.h"
#include "hw/maple/maple_cfg.h"
#include "hw/maple/maple_devs.h"
#include "oslib/i18n.h"
#include "stdclass.h"
#include "input/gamepad_device.h"
#include <deque>
#include <memory>
#include <cerrno>
@@ -40,6 +43,8 @@ public:
INFO_LOG(NAOMI, "Card inserted");
}
virtual void ejectCard() {}
protected:
virtual bool loadCard() = 0;
@@ -61,16 +66,8 @@ protected:
void saveCard(const u8 *cardData, u32 len)
{
std::string path = hostfs::getArcadeFlashPath() + ".card";
FILE *fp = nowide::fopen(path.c_str(), "wb");
if (fp == nullptr)
{
WARN_LOG(NAOMI, "Can't create card file %s: errno %d", path.c_str(), errno);
return;
}
INFO_LOG(NAOMI, "Saving card file to %s", path.c_str());
if (fwrite(cardData, 1, len, fp) != len)
WARN_LOG(NAOMI, "Truncated write to file: %s", path.c_str());
fclose(fp);
safeFullSave(path, cardData, len);
}
template<typename T>
@@ -655,6 +652,343 @@ const u8 ClubKartCardReader::CommandBytes[][2]
static std::unique_ptr<CardReaderWriter> cardReader;
#define HWLOG(...) DEBUG_LOG(NAOMI, __VA_ARGS__)
// WCCF, Dragon Treasure IC card reader/writer (Saxa HW210), hooked to the SH4 SCIF port
class SaxaHW210 : public CardReaderWriter, public SerialPort::Pipe
{
public:
SaxaHW210() {
SCIFSerialPort::Instance().setPipe(this);
}
~SaxaHW210() override {
SCIFSerialPort::Instance().setPipe(nullptr);
kcode[1] |= DC_DPAD_UP;
}
void write(u8 b) override
{
inBuffer.push_back(b);
if (inBuffer.size() < 6)
return;
u32 size = (inBuffer[2] << 8) + inBuffer[3] + 5;
if (inBuffer.size() < size)
return;
u8 crc = calcCrc(inBuffer.begin(), inBuffer.begin() + size - 1);
if (crc != inBuffer[size - 1]) {
WARN_LOG(NAOMI, "Invalid CRC for command %02x size %d", inBuffer[1], size);
}
else
{
dumpPkt();
size_t outStart = outBuffer.size();
const u16 arg = (inBuffer[4] << 8) | inBuffer[5];
switch (inBuffer[1])
{
case 0x10: // sz 0 arg 1000
sendCmd(0x10); // returned arg should be != 0x20
break;
case 0x11: // sz 8 arg 6: 00 00 00 00 00 00
sendCmd(0x11);
break;
case 0x14: // NFC field status?
// sz 0 arg 1400:
// called after returning arg=0x20 in cmd 10
sendCmd(0x14, 0, cardInserted);
break;
case 0x20: // Select card slot
// sz 8 arg 1: 00 00 00 00 00 00
// arg is selected card slot#. Normally 1 but set to 0 to access other card
// expects arg=0, no payload
if (!cardInserted || (!cardExpired && arg == 0)) {
sendCmd(0x20, 0, 0x8000);
}
else {
cardIndex = arg;
sendCmd(0x20);
}
break;
case 0x21: // sz 10 arg 0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00
// Returning arg=1 indicates that 2 cards are in the reader
// payload is some card identifier, sent back in cmd 22
sendCmd(0x21, 8, cardExpired && cardIndex == 1);
outBuffer.push_back(0);
outBuffer.push_back(cardIndex);
outBuffer.push_back(0);
outBuffer.push_back(cardIndex);
for (int i = 0; i < 4; i++)
outBuffer.push_back(0);
break;
case 0x22: // sz 8 arg 0: 00 00 00 00 00 00
// expects arg=0
// payload[0-1] is used as cmd arg for all operations below
sendCmd(0x22, 8);
outBuffer.push_back(0);
outBuffer.push_back(arg);
for (int i = 0; i < 6; i++)
outBuffer.push_back(0);
break;
case 0x24: // Read single block
{
// sz 8 arg 0: 00 04 00 00 00 00
if (!cardInserted) {
sendCmd(0x24, 0, 0x8000);
break;
}
sendCmd(0x24, 8);
u32 offset = inBuffer[7] * 8;
for (int i = 0; i < 8; i++)
{
if (offset + i < CARD_SZ)
outBuffer.push_back(cardData[arg][offset + i]);
else
outBuffer.push_back(0);
}
break;
}
case 0x25: // Write single block
// sz 10 arg 0: 00 00 00 04 95 71 25 76 12 34 56 78 00 00
if (!cardInserted) {
sendCmd(0x25, 0, 0x8000);
break;
}
if (inBuffer[9] < 6) {
// write-protected
HWLOG("HW210: Block %d write-protected", inBuffer[9]);
sendCmd(0x25, 0, 0x8001);
}
else {
writeCard(arg, inBuffer[9], 1, &inBuffer[10]);
sendCmd(0x25);
}
break;
case 0x26: // Decrement remaining writes
// sz 8 arg 800: 00 05 00 01 00 00
// sz 8 arg 800: 00 05 ff fd 00 00 after resigning
{
if (!cardInserted) {
sendCmd(0x26, 0, 0x8000);
break;
}
u8 *p = &cardData[arg][inBuffer[7] * 8];
u16 remain = (p[0] << 8) | p[1];
const u16 count = (inBuffer[8] << 8) | inBuffer[9];
remain -= count;
p[0] = remain >> 8;
p[1] = remain & 0xff;
// After transferring an expired club card, the game will decrease
// the source card write count to 0. This prevents the card from being
// transferred again. In the Flycast case, the old card doesn't exist anymore
// so we ignore it.
saveCard(arg);
sendCmd(0x26);
break;
}
case 0x27: // sz 8 arg 800: 00 00 00 00 00 00
sendCmd(0x27);
break;
case 0x31: // sz 8 arg 800: 00 00 00 06 00 00
sendCmd(0x31);
break;
case 0x33: // Get remaining writes
{
// sz 8 arg 0: 00 05 00 00 00 00
// expects arg=8008 or 0
if (!cardInserted) {
sendCmd(0x33, 0, 0x8000);
break;
}
sendCmd(0x33, 8);
// must be ffff for blank cards
const u8 *p = &cardData[arg][inBuffer[7] * 8];
outBuffer.push_back(p[0]);
outBuffer.push_back(p[1]);
for (int i = 0; i < 6; i++)
outBuffer.push_back(0);
break;
}
case 0x34: // Read card
{
// sz 8 arg 0: 00 08 00 1e 00 00
// offset size (in 8-byte blocks)
if (!cardInserted) {
sendCmd(0x34, 0, 0x8000);
break;
}
const u32 offset = ((inBuffer[6] << 8) | inBuffer[7]) * 8;
u32 size = ((inBuffer[8] << 8) | inBuffer[9]) * 8;
size = std::min(size, (u32)CARD_SZ - offset);
sendCmd(0x34, size);
for (u32 i = 0; i < size; i++)
outBuffer.push_back(cardData[arg][offset + i]);
break;
}
case 0x35: // Write card
{
if (!cardInserted) {
sendCmd(0x35, 0, 0x8000);
break;
}
const u32 block = (inBuffer[6] << 8) | inBuffer[7];
if (block < 6) {
// write-protected
HWLOG("HW210: Block %d write-protected", block);
sendCmd(0x35, 0, 0x8001);
}
else {
writeCard(arg, block, (inBuffer[8] << 8) | inBuffer[9],
&inBuffer[10]);
sendCmd(0x35);
}
break;
}
default:
WARN_LOG(NAOMI, "HW210: unhandled command %02x", inBuffer[1]);
break;
}
if (outBuffer.size() != outStart)
outBuffer.push_back(calcCrc(outBuffer.begin() + outStart, outBuffer.end()));
}
inBuffer.erase(inBuffer.begin(), inBuffer.begin() + size);
}
int available() override {
return outBuffer.size();
}
u8 read() override
{
if (outBuffer.empty())
return 0;
u8 b = outBuffer.front();
outBuffer.pop_front();
return b;
}
void ejectCard() override
{
if (!cardInserted)
return;
NOTICE_LOG(NAOMI, "Card ejected");
os_notify(i18n::T("Card ejected"), 2000);
cardInserted = false;
cardExpired = false;
memset(cardData[0], 0, CARD_SZ);
memset(cardData[1], 0, CARD_SZ);
kcode[1] |= DC_DPAD_UP;
}
protected:
bool loadCard() override
{
memset(cardData[0], 0, CARD_SZ);
memset(cardData[1], 0, CARD_SZ);
bool ret = CardReaderWriter::loadCard(cardData[1], CARD_SZ);
if (!ret) {
initCard(1);
}
else
{
cardExpired = cardData[1][0x28] == 0 && cardData[1][0x29] == 1;
if (cardExpired) {
memcpy(cardData[0], cardData[1], CARD_SZ);
initCard(1);
}
}
kcode[1] &= ~DC_DPAD_UP; // card sensor
return true;
}
private:
void dumpPkt()
{
std::string payload;
u32 size = (inBuffer[2] << 8) + inBuffer[3];
for (u32 i = 2; i < size; i++)
payload += strprintf("%02x ", inBuffer[4 + i]);
if (inBuffer[1] != 0x24 || inBuffer[6] != 0 || inBuffer[7] != 0)
HWLOG("HW210: cmd %02x sz %x arg %x: %s", inBuffer[1], size, (inBuffer[4] << 8) + inBuffer[5], payload.c_str());
}
void sendCmd(u8 cmd, u16 payloadSz = 0, u16 arg = 0)
{
outBuffer.push_back(0x10); // status = OK? FF seems to be accepted too
outBuffer.push_back(cmd);
outBuffer.push_back((payloadSz + 2) >> 8);
outBuffer.push_back(payloadSz + 2);
outBuffer.push_back(arg >> 8);
outBuffer.push_back(arg);
}
void initCard(int index)
{
u8 *card = cardData[index];
memset(card, 0, CARD_SZ);
// Block 0: factory-set UID
for (int i = 0; i < 8; i++)
card[i] = rand() & 0xff;
// Block 4 must start with 95 71 25 7x ?? ?? ?? ??
// ? can be any digit [0-9]
// x must be the sum of all ? digits % 10
static constexpr u8 UID[] = { 0x95, 0x71, 0x25, 0x70 };
const u32 offset = 4 * 8;
memcpy(&card[offset], UID, sizeof(UID));
int sum = 0;
srand(time(nullptr) + index * 2);
for (int i = 0; i < 4; i++)
{
const u8 n = rand() % 100;
card[i + offset + 4] = ((n / 10) << 4) | (n % 10);
sum += n / 10 + n % 10;
}
card[offset + 3] = (card[offset + 3] & 0xf0) | (sum % 10);
// Block 5: write counter
card[0x28] = 0xff;
card[0x29] = 0xff;
// Block 6: copy of block #0 UID
memcpy(card + 0x30, card, 8);
NOTICE_LOG(NAOMI, "WCCF IC card %d created: %02x %02x %02x %02x %02x", index, card[offset + 3],
card[offset + 4], card[offset + 5],
card[offset + 6], card[offset + 7]);
}
void writeCard(int cardIndex, u32 offset, u32 blocks, const u8 *data)
{
offset *= 8; // in bytes
if (offset >= CARD_SZ)
return;
u32 size = blocks * 8;
size = std::min(size, (u32)CARD_SZ - offset);
if (memcmp(&cardData[cardIndex][offset], data, size) == 0)
return;
memcpy(&cardData[cardIndex][offset], data, size);
saveCard(cardIndex);
}
void saveCard(int cardIndex)
{
if (cardIndex == 1)
CardReaderWriter::saveCard(cardData[1], CARD_SZ);
else
WARN_LOG(NAOMI, "Card #0 not saved");
}
static constexpr size_t CARD_SZ = 0x270;
u8 cardData[2][CARD_SZ] {};
int cardIndex = 1;
bool cardExpired = false;
};
void initdInit() {
term();
cardReader = std::make_unique<InitialDCardReader>();
@@ -674,6 +1008,11 @@ void clubkInit() {
cardReader = std::make_unique<ClubKartCardReader>();
}
void wccfInit() {
term();
cardReader = std::make_unique<SaxaHW210>();
}
void term() {
cardReader.reset();
}
@@ -765,6 +1104,11 @@ void insertCard(int playerNum)
else
insertRfidCard(playerNum);
}
void ejectCard()
{
if (cardReader != nullptr)
cardReader->ejectCard();
}
bool readerAvailable()
{
+2
View File
@@ -24,6 +24,7 @@ namespace card_reader {
void initdInit();
void derbyInit();
void clubkInit();
void wccfInit();
void term();
void barcodeInit();
@@ -33,6 +34,7 @@ std::string barcodeGetCard();
void barcodeSetCard(const std::string& card);
void insertCard(int playerNum);
void ejectCard();
bool readerAvailable();
}
+5 -16
View File
@@ -23,6 +23,7 @@
#include "dinokich_card.h"
#include "cfg/option.h"
#include "oslib/oslib.h"
#include "oslib/directory.h"
#include <algorithm>
namespace systemsp
@@ -385,16 +386,7 @@ DinokichCardReader::Reply DinokichCardReader::on_63(const std::vector<u8>& f) {
void DinokichCardReader::saveCard(int slotId)
{
Slot& slot = slots_[slotId];
FILE *f = nowide::fopen(getCardDataPath(index + slotId).c_str(), "wb");
if (f != nullptr)
{
if (fwrite(slot.eeprom.data(), 1, slot.eeprom.size(), f) != slot.eeprom.size())
WARN_LOG(NAOMI, "Rfid card %d: truncated write", index);
fclose(f);
}
else {
WARN_LOG(NAOMI, "Card saved failed to %s", getCardDataPath(index).c_str());
}
safeFullSave(getCardDataPath(index + slotId), slot.eeprom.data(), slot.eeprom.size());
}
void DinokichCardReader::serialize(Serializer& ser) const
@@ -445,12 +437,9 @@ void DinokichCardReader::deserialize(Deserializer& deser)
deser.skip<u8>(); // expectedBytes
deser >> size;
deser.skip(size);
if (deser.version() >= Deserializer::V41)
{
deser.skip<int>(); // state
deser.skip<int>(); // rowCounter
deser.skip(128); // cardData
}
deser.skip<int>(); // state
deser.skip<int>(); // rowCounter
deser.skip(128); // cardData
}
}
}
+36 -16
View File
@@ -23,6 +23,7 @@
#include "naomi.h"
#include "oslib/i18n.h"
#include <algorithm>
#include <zlib.h>
/*
@@ -539,26 +540,28 @@ void GDCartridge::device_start(LoadProgress *progress, std::vector<u8> *digest)
// directory
u8 dir_sector[2048];
// find data of file
u32 file_size;
if (netpic == 0) {
// find data of file
if (netpic == 0)
{
u32 dir = ((buffer[0x2 + 0] << 0) |
(buffer[0x2 + 1] << 8) |
(buffer[0x2 + 2] << 16) |
(buffer[0x2 + 3] << 24));
read_gdrom(gdrom.get(), dir, dir_sector);
find_file(name, dir_sector, file_start, file_size);
find_file(name, dir_sector, file_start, fileSize);
if (file_start && (file_size == 0x100)) {
if (file_start && fileSize == 0x100) {
// read file
read_gdrom(gdrom.get(), file_start, buffer);
// get "rom" file name
memset(name, '\0', 128);
memcpy(name, buffer + 0xc0, FILENAME_LENGTH - 1);
}
} else {
}
else
{
bool found = false;
u32 i = 0;
while (i < 2048 && buffer[i] != 0)
@@ -583,15 +586,15 @@ void GDCartridge::device_start(LoadProgress *progress, std::vector<u8> *digest)
}
}
find_file(name, dir_sector, file_start, file_size);
find_file(name, dir_sector, file_start, fileSize);
if (file_start == 0)
// mj1: filename in the pic is incorrect, probably because the disk isn't supposed to be run like this?
// so grab the first .BIN and load it.
find_file("*.BIN", dir_sector, file_start, file_size);
find_file("*.BIN", dir_sector, file_start, fileSize);
if (file_start != 0)
{
u32 file_rounded_size = (file_size + 2047) & ~2048;
u32 file_rounded_size = (fileSize + 2047) & ~2048;
for (dimm_data_size = 4096; dimm_data_size < file_rounded_size; dimm_data_size <<= 1)
;
dimm_data = (u8 *)malloc(dimm_data_size);
@@ -605,6 +608,20 @@ void GDCartridge::device_start(LoadProgress *progress, std::vector<u8> *digest)
loadedSegments.end(), true);
des_generate_subkeys(rev64(key), des_subkeys);
if (fullLoad)
{
for (unsigned offset = 0; offset < fileSize; offset += SEGMENT_SIZE)
{
if (progress != nullptr)
{
if (progress->cancelled)
throw LoadCancelledException();
progress->progress = (float)offset / fileSize;
}
loadSegments(offset, SEGMENT_SIZE);
}
}
}
if (!dimm_data)
@@ -620,13 +637,16 @@ void GDCartridge::loadSegments(u32 offset, u32 size)
if (loadedSegments[segment])
continue;
DEBUG_LOG(NAOMI, "Loading segment %d", segment);
const u32 start = segment * SEGMENT_SIZE;
// load data
read_gdrom(gdrom.get(), file_start + (segment * SEGMENT_SIZE) / 2048,
dimm_data + segment * SEGMENT_SIZE,
read_gdrom(gdrom.get(), file_start + start / 2048,
dimm_data + start,
SEGMENT_SIZE / 2048,
nullptr);
if (start < fileSize)
crc = crc32(crc, dimm_data + start, std::min(SEGMENT_SIZE, fileSize - start));
// decrypt loaded data
u64 *pData = (u64 *)(dimm_data + segment * SEGMENT_SIZE);
u64 *pData = (u64 *)(dimm_data + start);
for (u32 i = 0; i < SEGMENT_SIZE; i += 8, pData++)
*pData = des_encrypt_decrypt<true>(*pData, des_subkeys);
loadedSegments[segment] = true;
@@ -759,18 +779,18 @@ void GDCartridge::process()
break;
case 1: // network commands
WARN_LOG(NAOMI, "Network command received cmd %x. Need full NetDIMM?", cmd);
returnToNaomi(true, 0, -1);
returnToNaomi(dimm_command & 0x7e00, true, 0, -1);
break;
default:
WARN_LOG(NAOMI, "Unknown DIMM command group %d cmd %x", cmdGroup, cmd);
returnToNaomi(true, 0, -1);
returnToNaomi(dimm_command & 0x7e00, true, 0, -1);
break;
}
}
void GDCartridge::returnToNaomi(bool failed, u16 offsetl, u32 parameter)
void GDCartridge::returnToNaomi(u16 cmd, bool failed, u16 offsetl, u32 parameter)
{
dimm_command = ((dimm_command & 0x7e00) + 0x400) | (failed ? 0xff : 0x4);
dimm_command = (cmd + 0x400) | (failed ? 0xff : 0x4);
dimm_offsetl = offsetl;
dimm_parameterh = parameter >> 16;
dimm_parameterl = parameter;
+7 -1
View File
@@ -33,10 +33,13 @@ public:
void Serialize(Serializer &ser) const override;
void Deserialize(Deserializer &deser) override;
u32 getFileSize() const { return fileSize; }
u32 getCrc() const { return ~crc; }
protected:
virtual void process();
virtual int schedCallback();
void returnToNaomi(bool failed, u16 offsetl, u32 parameter);
void returnToNaomi(u16 cmd, bool failed, u16 offsetl, u32 parameter);
template<typename T>
void peek(u32 address)
@@ -69,6 +72,9 @@ protected:
u16 dimm_parameterh;
static constexpr u16 DIMM_STATUS = 0x111;
int schedId;
u32 fileSize = 0;
u32 crc = 0;
bool fullLoad = false;
private:
const char *gdrom_name = nullptr;
+4 -12
View File
@@ -323,18 +323,10 @@ private:
deser >> maxBet;
deser >> minBet;
deser >> addBet;
if (deser.version() >= Deserializer::V39)
{
deser >> paidAmount;
deser >> wonAmount;
deser >> betButton;
deser >> curBase;
}
else
{
paidAmount = 0;
wonAmount = 0;
}
deser >> paidAmount;
deser >> wonAmount;
deser >> betButton;
deser >> curBase;
}
u32 expectedBytes = 0;
+10 -22
View File
@@ -269,30 +269,18 @@ void serialize(Serializer& ser)
void deserialize(Deserializer& deser)
{
if (deser.version() >= Deserializer::V27)
{
if (initialized) {
deser >> midiTxBuf;
deser >> midiTxBufIndex;
}
else if (deser.version() < Deserializer::V51) {
deser.skip(4); // midiTxBuf
deser.skip<u32>(); // midiTxBufIndex
}
if (initialized) {
deser >> midiTxBuf;
deser >> midiTxBufIndex;
}
else {
midiTxBufIndex = 0;
}
if (deser.version() >= Deserializer::V34)
{
if (initialized)
deser >> calibrating;
else if (deser.version() < Deserializer::V51)
deser.skip<bool>(); // calibrating
}
else {
calibrating = false;
else if (deser.version() < Deserializer::V51) {
deser.skip(4); // midiTxBuf
deser.skip<u32>(); // midiTxBufIndex
}
if (initialized)
deser >> calibrating;
else if (deser.version() < Deserializer::V51)
deser.skip<bool>(); // calibrating
if (initialized)
{
maxSpring = 0x7f;
+30 -41
View File
@@ -25,6 +25,7 @@
#include "naomi_cart.h"
#include "naomi_regs.h"
#include "naomi_m3comm.h"
#include "naomi_flashrom.h"
#include "multiboard.h"
#include "serialize.h"
#include "network/output.h"
@@ -126,12 +127,21 @@ static int naomiDmaSched(int tag, int sch_cycl, int jitter, void *arg)
void* ptr = CurrentCartridge->GetDmaPtr(block_len);
if (block_len == 0)
{
INFO_LOG(NAOMI, "Aborted DMA transfer. Read past end of cart?");
for (u32 i = 0; i < len; i += 8, start += 8)
addrspace::write64(start, 0);
if (SB_GDDIR == 1)
{
INFO_LOG(NAOMI, "Aborted DMA transfer. Read past end of cart?");
for (u32 i = 0; i < len; i += 8, start += 8)
addrspace::write64(start, 0);
}
break;
}
WriteMemBlock_nommu_ptr(start, (u32*)ptr, block_len);
if (SB_GDDIR == 1) {
WriteMemBlock_nommu_ptr(start, (u32*)ptr, block_len);
}
else {
const void *src = GetMemPtr(start, block_len);
memcpy(ptr, src, block_len);
}
CurrentCartridge->AdvancePtr(block_len);
len -= block_len;
start += block_len;
@@ -165,7 +175,6 @@ static void Naomi_DmaStart(u32 addr, u32 data)
else if ((m3comm == nullptr || !m3comm->DmaStart(addr, data)) && CurrentCartridge != nullptr)
{
DEBUG_LOG(NAOMI, "NAOMI-DMA start addr %08X len %x", SB_GDSTAR, SB_GDLEN);
verify(1 == SB_GDDIR);
SB_GDST = 1;
SB_GDSTARD = SB_GDSTAR & 0x1FFFFFE0;
SB_GDLEND = 0;
@@ -215,7 +224,19 @@ void naomi_reg_Init()
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'
};
romSerialId.setData(romSerialData);
#if 0
// Generate random main board serial id
u8 mainSerialData[sizeof(romSerialData)];
memcpy(mainSerialData, romSerialData, sizeof(romSerialData));
for (unsigned i = 52; i < 52 + 16; i++)
mainSerialData[i] = (rand() % 10) + '0';
u16 crc = eeprom_crc(&mainSerialData[22], 46);
mainSerialData[20] = crc >> 8;
mainSerialData[21] = crc;
mainSerialId.setData(mainSerialData);
#else
mainSerialId.setData(romSerialData);
#endif
if (dmaSchedId == -1)
dmaSchedId = sh4_sched_register(0, naomiDmaSched);
}
@@ -236,11 +257,11 @@ const u8 *getGameSerialId()
void naomi_reg_Term()
{
naomi_cart_Close();
multiboard.reset();
m3comm.reset();
networkOutput.term();
if (dmaSchedId != -1)
sh4_sched_unregister(dmaSchedId);
sh4_sched_unregister(dmaSchedId);
dmaSchedId = -1;
midiffb::term();
}
@@ -289,40 +310,8 @@ void naomi_Serialize(Serializer& ser)
}
void naomi_Deserialize(Deserializer& deser)
{
if (deser.version() < Deserializer::V40)
{
deser.skip<u32>(); // GSerialBuffer
deser.skip<u32>(); // BSerialBuffer
deser.skip<int>(); // GBufPos
deser.skip<int>(); // BBufPos
deser.skip<int>(); // GState
deser.skip<int>(); // BState
deser.skip<int>(); // GOldClk
deser.skip<int>(); // BOldClk
deser.skip<int>(); // BControl
deser.skip<int>(); // BCmd
deser.skip<int>(); // BLastCmd
deser.skip<int>(); // GControl
deser.skip<int>(); // GCmd
deser.skip<int>(); // GLastCmd
deser.skip<int>(); // SerStep
deser.skip<int>(); // SerStep2
deser.skip(69); // BSerial
deser.skip(69); // GSerial
}
else
{
mainSerialId.deserialize(deser);
romSerialId.deserialize(deser);
}
if (deser.version() < Deserializer::V36)
{
deser.skip<u32>(); // reg_dimm_command;
deser.skip<u32>(); // reg_dimm_offsetl;
deser.skip<u32>(); // reg_dimm_parameterl;
deser.skip<u32>(); // reg_dimm_parameterh;
deser.skip<u32>(); // reg_dimm_status;
}
mainSerialId.deserialize(deser);
romSerialId.deserialize(deser);
atomiswave::deserialize(deser);
midiffb::deserialize(deser);
if (deser.version() >= Deserializer::V45)
+79 -122
View File
@@ -278,7 +278,8 @@ static void loadMameRom(const std::string& path, const std::string& fileName, Lo
GDCartridge *gdcart;
if (strncmp(game->name, "vf4", 3) == 0
|| strcmp(game->name, "mj1") == 0
|| strncmp(game->name, "wccf", 4) == 0)
|| strncmp(game->name, "wccf", 4) == 0
|| strncmp(game->name, "dragntr", 7) == 0)
gdcart = new NetDimm(game->size);
else
gdcart = new GDCartridge(game->size);
@@ -466,143 +467,59 @@ static void loadDecryptedRom(const std::string& path, const std::string& fileNam
}
}
std::string folder;
std::vector<std::string> files;
std::vector<u32> fstart;
std::vector<u32> fsize;
u32 romSize = 0;
// BIN/DAT loading
hostfs::File* fp = hostfs::storage().openFile(path, "rb");
if (fp == nullptr)
throw FlycastException(strprintf(T("Error: can't open %s"), path.c_str()));
std::string extension = get_file_extension(fileName);
if (extension == "lst")
{
// LST file
hostfs::File *fl = hostfs::storage().openFile(path, "r");
if (!fl)
throw FlycastException(strprintf(T("Error: can't open %s"), path.c_str()));
folder = hostfs::storage().getParentPath(path);
fp->seek(0, SEEK_END);
const u32 romSize = (u32)fp->tell();
fp->seek(0, SEEK_SET);
char t[512];
char* line = fl->gets(t, sizeof(t));
if (!line)
{
delete fl;
throw FlycastException(Ts("Error: Invalid LST file"));
}
char* eon = strstr(line, "\n");
if (!eon)
DEBUG_LOG(NAOMI, "+Loading naomi rom that has no name");
else
{
*eon = 0;
DEBUG_LOG(NAOMI, "+Loading naomi rom : %s", line);
}
line = fl->gets(t, sizeof(t));
if (!line)
{
delete fl;
throw FlycastException(Ts("Error: Invalid LST file"));
}
while (line)
{
char filename[512];
u32 addr, sz;
if (sscanf(line, "\"%[^\"]\",%x,%x", filename, &addr, &sz) == 3)
{
files.emplace_back(filename);
fstart.push_back(addr);
fsize.push_back(sz);
romSize = std::max(romSize, (addr + sz));
}
else if (line[0] != 0 && line[0] != '\n' && line[0] != '\r')
WARN_LOG(NAOMI, "Warning: invalid line in .lst file: %s", line);
line = fl->gets(t, sizeof(t));
}
delete fl;
}
else
{
// BIN loading
hostfs::File* fp = hostfs::storage().openFile(path, "rb");
if (fp == nullptr)
throw FlycastException(strprintf(T("Error: can't open %s"), path.c_str()));
fp->seek(0, SEEK_END);
u32 file_size = (u32)fp->tell();
INFO_LOG(NAOMI, "raw: %.2f MB set address space", romSize / 1024.f / 1024.f);
if (romSize == 0) {
delete fp;
files.emplace_back(path);
fstart.push_back(0);
fsize.push_back(file_size);
romSize = file_size;
}
INFO_LOG(NAOMI, "+%zd romfiles, %.2f MB set address space", files.size(), romSize / 1024.f / 1024.f);
if (romSize == 0)
throw FlycastException(Ts("Invalid empty ROM"));
MD5Sum md5;
}
// Allocate space for the rom
u8 *romBase = (u8 *)malloc(romSize);
if (romBase == nullptr)
if (romBase == nullptr) {
delete fp;
throw FlycastException(Ts("Out of memory"));
bool load_error = false;
for (size_t i = 0; i<files.size(); i++)
{
hostfs::File *fp = nullptr;
if (files[i] != "null")
{
std::string filePath;
if (folder.empty())
filePath = files[i];
else
filePath = hostfs::storage().getSubPath(folder, files[i]);
fp = hostfs::storage().openFile(filePath, "rb");
if (fp == nullptr)
{
ERROR_LOG(NAOMI, "Unable to open file %s: error %d", filePath.c_str(), errno);
load_error = true;
break;
}
}
u8* romDest = romBase + fstart[i];
if (fp == nullptr)
{
//printf("-Reserving ram at 0x%08X, size 0x%08X\n", fstart[i], fsize[i]);
memset(romDest, -1, fsize[i]);
}
else
{
//printf("-Mapping \"%s\" at 0x%08X, size 0x%08X\n", files[i].c_str(), fstart[i], fsize[i]);
bool mapped = fp->read(romDest, 1, fsize[i]) == fsize[i];
if (config::GGPOEnable)
md5.add(fp);
delete fp;
if (!mapped)
{
ERROR_LOG(NAOMI, "Unable to read file %s @ %08x size %x", files[i].c_str(),
fstart[i], fsize[i]);
load_error = true;
break;
}
}
}
if (load_error)
u32 offset = 0;
bool error = false;
while (offset < romSize && !error)
{
const u32 len = std::min(8192u, romSize - offset);
error = fp->read(romBase + offset, len, 1) != 1;
offset += len;
if (progress != nullptr)
{
if (progress->cancelled)
break;
progress->progress = (float)offset / romSize;
}
}
delete fp;
if (progress->cancelled) {
free(romBase);
throw LoadCancelledException();
}
if (error)
{
ERROR_LOG(NAOMI, "Unable to read file %s size %d", path.c_str(), romSize);
free(romBase);
throw FlycastException(Ts("Error: Failed to load BIN/DAT file"));
}
if (config::GGPOEnable)
{
MD5Sum md5;
md5.add(romBase, romSize);
md5.getDigest(settings.network.md5.game);
}
DEBUG_LOG(NAOMI, "Legacy ROM loaded successfully");
@@ -705,6 +622,11 @@ void naomi_cart_LoadRom(const std::string& path, const std::string& fileName, Lo
{
serialModemInit();
}
else if ((gameId.substr(0, 4) == "WCCF" && config::MultiboardSlaves <= 1)
|| gameId.substr(0, 15) == "DRAGON TREASURE")
{
card_reader::wccfInit();
}
if (gameId == " TOUCH DE UNOH -------------"
|| gameId == " TOUCH DE UNOH 2 -----------"
|| (gameId == "F355 CHALLENGE JAPAN" && (config::MultiboardSlaves == 2 || romName == "f355"))
@@ -753,6 +675,41 @@ void naomi_cart_LoadRom(const std::string& path, const std::string& fileName, Lo
}
}
}
else if (gameId.substr(0, 4) == "WCCF" && config::MultiboardSlaves > 1)
{
// Main projectors are directly linked by a null-modem cable
config::BattleCableEnable.override(true);
if (!config::loadBool("naomi", "WCCFSlave"))
{
config::ActAsServer.override(true);
config::NetworkServer.override("localhost:37392");
config::LocalPort.override(37391);
int x = config::loadInt("window", "left", (1920 - 640) / 2);
int w = config::loadInt("window", "width", 640);
std::string region = "config:Dreamcast.Region=" + std::to_string(config::Region);
std::string left = "window:left=" + std::to_string(x + w);
std::string title = "window:title=\"" + Ts("Right")
+ " - " + settings.content.title + '"';
std::string server = "naomi:WCCFServer=" + config::loadStr("naomi", "WCCFServer");
const char *args[] = {
"-config", "naomi:WCCFSlave=yes",
"-config", server.c_str(),
"-config", region.c_str(),
"-config", left.c_str(),
"-config", title.c_str(),
"-config", "network:MultiboardSlaves=2",
settings.content.path.c_str()
};
os_RunInstance(std::size(args), args);
}
else
{
config::ActAsServer.override(false);
config::NetworkServer.override("localhost:37391");
config::LocalPort.override(37392);
}
}
#endif
}
else
+6 -2
View File
@@ -20,11 +20,14 @@
*/
#include "naomi_flashrom.h"
#include "hw/flashrom/nvmem.h"
#include "hw/maple/maple_devs.h"
#include "cfg/option.h"
static u16 eeprom_crc(const u8 *buf, int size)
extern u8 *EEPROM;
// CRC-16-CCITT variant
u16 eeprom_crc(const u8 *buf, int size)
{
// custom seed
int n = 0xdebdeb00;
for (int i = 0; i < size; i++)
@@ -40,6 +43,7 @@ static u16 eeprom_crc(const u8 *buf, int size)
n <<= 1;
}
}
// extra round with a virtual null trailing byte
for (int c = 0; c < 8; c++)
{
if (n & 0x80000000)
+1
View File
@@ -27,3 +27,4 @@ u8 read_naomi_eeprom(u32 offset);
void write_naomi_eeprom(u32 offset, u8 value);
void configure_naomi_eeprom(const RomBootID *bootId);
void configure_maxspeed_flash(bool enableNetwork, bool master);
u16 eeprom_crc(const u8 *buf, int size);
+13 -3
View File
@@ -39,11 +39,11 @@ const BIOS_t BIOS[] =
"awbios",
{
// Sammy version
{ 0, "bios0.ic23", 0x000000, 0x020000, 0x719b2b0b },
{ 0, "bios0.ic23", 0x000000, 0x010000, 0x719b2b0b },
//or
//{ 0, "bios1.ic23", 0x000000, 0x020000, 0xd3e80a9f },
//{ 0, "bios1.ic23", 0x000000, 0x010000, 0xd3e80a9f },
// Sega version
//{ 0, "fpr-24363.ic48", 0x000000, 0x020000, 0x82a105f0 },
//{ 0, "fpr-24363.ic48", 0x000000, 0x010000, 0x82a105f0 },
}
},
{
@@ -6141,6 +6141,7 @@ const Game Games[] =
{ "317-0329-jpn.pic", 0, 0x4000, 0x097f5f92 },
},
"cdp-10001c",
&wccf_inputs,
},
// World Club Champion Football Serie A 2001-2002 DIMM FIRM Ver.3.03 (CDP-10003)
{
@@ -6173,6 +6174,7 @@ const Game Games[] =
// SH4-based camera sensor board connected to MAPLE CN7
},
"cdv-10002",
&wccf_inputs,
},
// World Club Champion Football Serie A 2002-2003 Ver.2.34 (Japan) (CDV-10008)
{
@@ -6188,6 +6190,7 @@ const Game Games[] =
{ "317-0369-jpn.pic", 0, 0x4000, 0xfb3672ba },
},
"cdv-10008",
&wccf_inputs,
},
// World Club Champion Football European Clubs 2004-2005 (Asia) (CDV-10013)
{
@@ -6203,6 +6206,7 @@ const Game Games[] =
{ "317-0419-jpn.pic", 0, 0x4000, 0x04fae86b },
},
"cdv-10013",
&wccf_inputs,
},
// World Club Champion Football European Clubs 2004-2005 Ver.1.1 (Export) (CDV-10015)
{
@@ -6218,6 +6222,7 @@ const Game Games[] =
{ "317-0419-exp.pic", 0, 0x4000, 0x3f5e1445 },
},
"cdv-10015",
&wccf_inputs,
},
// World Club Champion Football European Clubs 2004-2005 Ver.3.22 (Export) (CDV-10015P)
{
@@ -6233,6 +6238,7 @@ const Game Games[] =
{ "317-0419-exp.pic", 0, 0x4000, 0x3f5e1445 },
},
"cdv-10015p",
&wccf_inputs,
},
// World Club Champion Football European Clubs 2004-2005 Ver.1.1 (Japan) (CDV-10020)
{
@@ -6248,6 +6254,7 @@ const Game Games[] =
{ "317-unk-jpn.pic", 0, 0x4000, 0xa4abd76b },
},
"cdv-10020",
&wccf_inputs,
},
// World Club Champion Football European Clubs 2004-2005 Ver.1.2 (Japan) (CDV-10021)
{
@@ -6263,6 +6270,7 @@ const Game Games[] =
{ "317-unk-jpn.pic", 0, 0x4000, 0xa4abd76b },
},
"cdv-10021",
&wccf_inputs,
},
// World Club Champion Football European Clubs 2005-2006 (Japan) (CDV-10025)
{
@@ -6278,6 +6286,7 @@ const Game Games[] =
{ "317-0456-jpn.pic", 0, 0x4000, 0xcf3bd834 },
},
"cdv-10025",
&wccf_inputs,
},
// World Club Champion Football European Clubs 2005-2006 (Export) (CDV-10027)
{
@@ -6293,6 +6302,7 @@ const Game Games[] =
{ "317-0456-exp.pic", 0, 0x4000, 0xfb7dd592 },
},
"cdv-10027",
&wccf_inputs,
},
// AtomisWave Roms
+27
View File
@@ -628,6 +628,33 @@ static InputDescriptors kick4csh_inputs = {
NAO_BASE_BTN_DESC
}
};
static InputDescriptors wccf_inputs = {
{
// UDLR START
// NAOMI_BTN0_KEY "PRESSURE"
// NAOMI_BTN1_KEY "SHOOT"
// NAOMI_BTN2_KEY card vendor error
// NAOMI_BTN3_KEY card vendor empty
// P2:
// NAOMI_BTN0_KEY "KEEPER"
// NAOMI_BTN1_KEY card vendor standby
// NAOMI_BTN2_KEY "DATA"
// UP club team card
// DOWN SW1
// LEFT SW2
// RIGHT !card vendor sensor
{ NAOMI_BTN0_KEY, "PRESSURE" },
{ NAOMI_BTN1_KEY, "SHOOT" },
{ NAOMI_BTN2_KEY, "SW1", 0, NAOMI_DOWN_KEY },
{ NAOMI_BTN3_KEY, "KEEPER", 0, NAOMI_BTN0_KEY },
{ NAOMI_BTN4_KEY, "DATA", 0, NAOMI_BTN2_KEY },
NAO_DPAD_DESC
NAO_START_DESC
NAO_BASE_BTN_DESC
}
};
//
// AtomisWave games
//
+1055 -122
View File
File diff suppressed because it is too large. Load diff
+38 -25
View File
@@ -19,11 +19,21 @@
#pragma once
#include "gdcartridge.h"
#include "network/net_platform.h"
#include "util/tsqueue.h"
#include <memory>
#include <functional>
//#define NET_TRACE
class NetDimmServer;
class NetDimm : public GDCartridge
{
public:
enum ServerMsg { ControlRead, Reboot };
NetDimm(u32 size);
~NetDimm();
void Init(LoadProgress *progress = nullptr, std::vector<u8> *digest = nullptr) override;
@@ -31,6 +41,15 @@ public:
void Deserialize(Deserializer &deser) override;
u32 getDimmSize() const { return dimm_data_size; }
u8 *getDimmData(u32 offset) {
return &dimm_data[offset & (dimm_data_size - 1)];
}
void controlRead(std::function<void(u32)> callback);
void reboot();
protected:
void process() override;
int schedCallback() override;
@@ -62,12 +81,7 @@ private:
dimm_parameterh = value >> 16;
}
sock_t getSocket(int idx)
{
if (idx < 1 || idx > (int)sockets.size())
return INVALID_SOCKET;
return sockets[idx - 1].fd;
}
sock_t getSocket(int idx);
bool isBusy() const
{
@@ -81,23 +95,7 @@ private:
Socket() = default;
Socket(sock_t fd) : fd(fd) {}
int close()
{
int rc = 0;
if (fd != INVALID_SOCKET)
rc = ::closesocket(fd);
fd = INVALID_SOCKET;
connecting = false;
receiving = false;
sending = false;
connectTimeout = 0;
connectTime = 0;
sendTimeout = 0;
sendTime = 0;
recvTimeout = 0;
recvTime = 0;
return rc;
}
int close();
bool isClosed() const {
return fd == INVALID_SOCKET;
@@ -122,13 +120,28 @@ private:
u64 recvTimeout = 0;
u64 recvTime = 0;
int lastError = 0;
int port = 0;
sockaddr *srcAddr = nullptr;
socklen_t *addrLen = nullptr;
#ifdef NET_TRACE
void openTrace();
void closeTrace();
void traceRecv(const u8 *data, size_t len);
void traceSend(const u8 *data, size_t len);
FILE *trcFile = nullptr;
#endif
};
std::vector<Socket> sockets;
int lastError = 0; // for socket() and select()
bool dnsInProgress = false;
u32 serverIp = 0; //0x0100007f for testing only
bool finalTuned = false;
bool wccf = false;
u32 dimmBufferOffset = 0x0f000000;
std::unique_ptr<NetDimmServer> server;
std::function<void(u32)> controlReadCallback;
TsQueue<ServerMsg> serverQueue;
static constexpr int POLL_CYCLES = SH4_MAIN_CLOCK / 60;
static constexpr int POLL_CYCLES = SH4_MAIN_CLOCK / 120;
};
+1 -6
View File
@@ -1203,12 +1203,7 @@ void serialize(Serializer& ser)
void deserialize(Deserializer& deser)
{
if (printer != nullptr)
{
if (deser.version() >= Deserializer::V35)
printer->deserialize(deser);
else
init();
}
printer->deserialize(deser);
}
#endif
+6 -19
View File
@@ -30,6 +30,7 @@
#include "hw/holly/sb.h"
#include "oslib/storage.h"
#include "oslib/oslib.h"
#include "oslib/directory.h"
#include "cfg/option.h"
#include "card_reader.h"
#include "naomi_roms.h"
@@ -342,17 +343,9 @@ public:
deser >> size;
recvBuffer.resize(size);
deser.deserialize(recvBuffer.data(), recvBuffer.size());
if (deser.version() >= Deserializer::V41)
{
deser >> state;
deser >> rowCounter;
deser.deserialize(cardData.data(), cardData.size());
}
else
{
state = Off;
rowCounter = 0;
}
deser >> state;
deser >> rowCounter;
deser.deserialize(cardData.data(), cardData.size());
}
private:
@@ -432,13 +425,7 @@ private:
void saveData()
{
FILE *f = nowide::fopen(getCardDataPath().c_str(), "wb");
if (f == nullptr) {
WARN_LOG(NAOMI, "Can't save RFID card: error %x", errno);
return;
}
fwrite(cardData.data(), 1, cardData.size(), f);
fclose(f);
safeFullSave(getCardDataPath(), cardData.data(), cardData.size());
}
u32 dinoShuffle(u32 v) {
@@ -683,7 +670,7 @@ private:
SerialPort *port;
std::deque<u8> toSend;
std::vector<u8> recvBuffer;
int schedId = 0;
int schedId;
u32 lastPosX = ~0;
u32 lastPosY = ~0;
bool lastButton = false;
+3
View File
@@ -26,6 +26,7 @@
#include "serialize.h"
#include "network/net_platform.h"
#include <memory>
#include <vector>
namespace systemsp
{
@@ -171,6 +172,8 @@ public:
void Serialize(Serializer &ser) const override;
void Deserialize(Deserializer &deser) override;
// The media is loaded into memory-backed RAM, so a savestate holds all of it
u32 getSerializedMediaSize() const { return mediaName != nullptr ? RomSize : 0; }
bool GetBootId(RomBootID *bootId) override
{
+94 -41
View File
@@ -1,7 +1,9 @@
#include "Renderer_if.h"
#include "spg.h"
#include "rend/CustomTexture.h"
#include "rend/texconv.h"
#include "rend/transform_matrix.h"
#include "pvr_mem.h"
#include "cfg/option.h"
#include "emulator.h"
#include "serialize.h"
@@ -12,6 +14,7 @@
#include "profiler/fc_profiler.h"
#include "network/ggpo.h"
#include <cassert>
#include <mutex>
#include <deque>
@@ -30,9 +33,6 @@ static cResetEvent vramRollback;
// direct framebuffer write detection
static bool render_called = false;
u32 fb_watch_addr_start;
u32 fb_watch_addr_end;
bool fb_dirty;
static bool pend_rend;
static bool rendererEnabled = true;
@@ -199,7 +199,10 @@ private:
TA_context *taContext = DequeueRender();
if (taContext == nullptr)
return;
renderer->processGpuCleanupOperations();
// tile clipping is used to calculate framebuffer size in RTT below
setTileClipping(taContext->rend);
int width, height;
getScaledFramebufferSize(taContext->rend, width, height);
taContext->rend.framebufferWidth = width;
@@ -250,6 +253,7 @@ private:
void renderFramebuffer(const FramebufferInfo& config)
{
FC_PROFILE_SCOPE;
renderer->processGpuCleanupOperations();
#ifdef LIBRETRO
int w, h;
@@ -328,7 +332,7 @@ public:
{
// force 60/50 FPS now
lastInterval = 1;
stability = std::max(stability, 10);
currentInterval = 1;
return;
}
}
@@ -336,37 +340,46 @@ public:
rendersFullSpeed = 0;
}
const float refreshRate = SPG_CONTROL.isPAL() ? 20_sh4ms : 16667_sh4us;
int interval = std::round(avgRenderInterval / refreshRate);
float frac = std::abs(avgRenderInterval / refreshRate - interval);
const float vblankPerRender = avgRenderInterval / (SPG_CONTROL.isPAL() ? 20_sh4ms : 16667_sh4us);
int interval = std::round(vblankPerRender);
float frac = std::abs(vblankPerRender - interval);
if (frac <= .05f || (interval == 1 && frac <= .2f))
if ((interval == 2 && frac <= .05f)
|| (interval == 1 && frac <= .2f))
{
if (lastInterval == (int)interval) {
stability++;
if (lastInterval == (int)interval)
{
if (++stable >= 10)
currentInterval = std::min(lastInterval, 2);
unstable = 0;
}
else {
stability = 0;
else
{
stable = 0;
lastInterval = interval;
unstable++;
}
}
else {
stability = 0;
stable = 0;
unstable++;
}
if (unstable >= 30 && vblankPerRender < 2.f)
// Force swap interval to 1 if the frame rate is off over 30 frames
// Helps with games that render slightly above 30 FPS (ECCO 33 FPS, Armada ~40 FPS)
currentInterval = 1;
}
int swapInterval()
{
if (stability < 10)
return -1;
else
return std::min(lastInterval, 2);
int swapInterval() const {
return currentInterval;
}
void reset()
{
lastInterval = 1;
stability = 0;
stable = 0;
unstable = 0;
currentInterval = 1;
lastRender = 0;
renderInterval = 0;
@@ -383,7 +396,9 @@ private:
}
int lastInterval;
int stability;
int stable;
int unstable;
int currentInterval;
u64 lastRender;
u64 renderInterval;
@@ -474,12 +489,65 @@ void rend_term_renderer()
if (renderer != nullptr)
{
custom_texture.invalidateGpuPreloads();
renderer->Term();
delete renderer;
renderer = nullptr;
}
}
void rend_process_custom_texture_preloads()
{
if (renderer != nullptr)
{
renderer->processGpuCleanupOperations();
renderer->processCustomTexturePreloads();
}
}
bool rend_supports_gpu_texture_preload()
{
return renderer != nullptr && renderer->supportsGpuTexturePreload();
}
std::shared_ptr<GpuPreloadedTexture> Renderer::findGpuPreloadedTexture(
u32 currentHash, u32 oldVqHash, u32 oldHash) const
{
const auto find = [this](u32 hash) -> std::shared_ptr<GpuPreloadedTexture> {
const auto found = gpuPreloadedTextures.find(hash);
return found == gpuPreloadedTextures.end() ? nullptr : found->second;
};
if (auto texture = find(currentHash))
return texture;
if (oldVqHash != 0)
if (auto texture = find(oldVqHash))
return texture;
if (oldHash != 0)
return find(oldHash);
return nullptr;
}
void Renderer::addGpuPreloadedTexture(u32 hash, std::shared_ptr<GpuPreloadedTexture> texture)
{
assert(texture);
gpuPreloadedTextures.emplace(hash, std::move(texture));
}
void Renderer::clearGpuPreloadedTextures()
{
gpuPreloadedTextures.clear();
}
void Renderer::processGpuCleanupOperations()
{
if (custom_texture.consumeGpuCleanupOperations())
{
clearTextureCache();
clearGpuPreloadedTextures();
resetTextureCache = false;
}
}
void rend_reset()
{
FinishRender(DequeueRender());
@@ -598,7 +666,7 @@ int rend_end_render(int tag, int cycles, int jitter, void *arg)
void rend_vblank()
{
if (config::EmulateFramebuffer
|| (!render_called && fb_dirty && FB_R_CTRL.fb_enable))
|| (!render_called && FB_R_CTRL.fb_enable && FramebufferWatcher::Instance().isDirty()))
{
if (rend_is_enabled())
{
@@ -609,21 +677,13 @@ void rend_vblank()
if (!config::EmulateFramebuffer)
DEBUG_LOG(PVR, "Direct framebuffer write detected");
}
fb_dirty = false;
}
render_called = false;
check_framebuffer_write();
emu.vblank();
swapIntervalDetector.vblank();
}
void check_framebuffer_write()
{
u32 fb_size = (FB_R_SIZE.fb_y_size + 1) * (FB_R_SIZE.fb_x_size + FB_R_SIZE.fb_modulus) * 4;
fb_watch_addr_start = (SPG_CONTROL.interlace ? FB_R_SOF2 : FB_R_SOF1) & VRAM_MASK;
fb_watch_addr_end = fb_watch_addr_start + fb_size;
}
void rend_cancel_emu_wait()
{
if (config::ThreadedRendering)
@@ -680,20 +740,13 @@ void rend_serialize(Serializer& ser)
{
ser << fb_w_cur;
ser << render_called;
ser << fb_dirty;
ser << fb_watch_addr_start;
ser << fb_watch_addr_end;
FramebufferWatcher::Instance().serialize(ser);
}
void rend_deserialize(Deserializer& deser)
{
deser >> fb_w_cur;
if (deser.version() >= Deserializer::V20)
{
deser >> render_called;
deser >> fb_dirty;
deser >> fb_watch_addr_start;
deser >> fb_watch_addr_end;
}
deser >> render_called;
FramebufferWatcher::Instance().deserialize(deser);
pend_rend = false;
fbAddrHistory[0] = 1;
fbAddrHistory[1] = 1;
+17 -5
View File
@@ -2,8 +2,13 @@
#include "types.h"
#include "ta_ctx.h"
#include "emulator.h"
#include <string>
#include <memory>
#include <unordered_map>
#include <vector>
struct GpuPreloadedTexture;
extern u32 FrameCount;
bool rend_init_renderer();
@@ -21,6 +26,8 @@ void rend_start_rollback();
void rend_allow_rollback();
void rend_enable_renderer(bool enabled);
bool rend_is_enabled();
void rend_process_custom_texture_preloads();
bool rend_supports_gpu_texture_preload();
void rend_serialize(Serializer& ser);
void rend_deserialize(Deserializer& deser);
static void rend_updatePalette();
@@ -75,10 +82,20 @@ struct Renderer
virtual bool GetLastFrame(std::vector<u8>& data, int& width, int& height) { return false; }
virtual bool Present() { return true; }
virtual void processCustomTexturePreloads() {}
virtual bool supportsGpuTexturePreload() const { return false; }
virtual BaseTextureCacheData *GetTexture(TSP tsp, TCW tcw, int area = 0) { return nullptr; }
std::shared_ptr<GpuPreloadedTexture> findGpuPreloadedTexture(
u32 currentHash, u32 oldVqHash, u32 oldHash) const;
void processGpuCleanupOperations();
protected:
virtual void clearTextureCache() {}
void addGpuPreloadedTexture(u32 hash, std::shared_ptr<GpuPreloadedTexture> texture);
void clearGpuPreloadedTextures();
std::unordered_map<u32, std::shared_ptr<GpuPreloadedTexture>> gpuPreloadedTextures;
bool resetTextureCache = false;
bool clearLastFrame = false;
bool updatePalette = true;
@@ -100,11 +117,6 @@ private:
extern Renderer* renderer;
extern u32 fb_watch_addr_start;
extern u32 fb_watch_addr_end;
extern bool fb_dirty;
void check_framebuffer_write();
static inline void rend_updatePalette() {
if (renderer != nullptr)
renderer->updatePalette = true;
+3 -18
View File
@@ -514,26 +514,13 @@ struct State
void deserialize(Deserializer& deser)
{
projMatrixIdx = -1;
if (deser.version() < Deserializer::V24)
{
reset();
resetProjectionMatrix();
return;
}
ta_parse_reset();
u32 listType;
deser >> listType;
ta_set_list_type(listType);
deser >> gmp;
deser >> instance;
if (deser.version() < Deserializer::V40)
{
deser.skip<u32>(); // projMatrix address
resetProjectionMatrix();
}
else {
deser >> projMatrix;
}
deser >> projMatrix;
u32 tileclip;
deser >> tileclip;
ta_set_tileclip(tileclip);
@@ -1795,10 +1782,8 @@ void reset(bool hard)
void term()
{
if (schedId != -1) {
sh4_sched_unregister(schedId);
schedId = -1;
}
sh4_sched_unregister(schedId);
schedId = -1;
}
void vmem_init()
+1 -4
View File
@@ -97,10 +97,7 @@ void deserialize(Deserializer& deser)
deser >> ta_fsm[2048];
deser >> ta_fsm_cl;
if (deser.version() >= Deserializer::V29)
deser >> taRenderPass;
else
taRenderPass = 0;
deser >> taRenderPass;
DeserializeTAContext(deser);
if (!deser.rollback())
+93 -26
View File
@@ -11,8 +11,6 @@
#include "hw/holly/holly_intc.h"
#include "serialize.h"
static u32 pvr_map32(u32 offset32);
RamRegion vram;
// YUV converter code
@@ -226,11 +224,14 @@ void DYNACALL pvr_write32p(u32 addr, T data)
return;
}
addr &= ~(sizeof(T) - 1);
u32 vaddr = addr & VRAM_MASK;
if (vaddr >= fb_watch_addr_start && vaddr < fb_watch_addr_end)
fb_dirty = true;
*(T *)&vram[pvr_map32(addr)] = data;
T *pVram = (T *)&vram[pvr_map32(addr)];
if constexpr (!Internal)
{
// check needed to avoid spurious FB write detections with VF3tb history videos
if (data != *pVram)
FramebufferWatcher::Instance().vramCheck(addr & VRAM_MASK);
}
*pVram = data;
}
template void pvr_write32p<u8, false>(u32 addr, u8 data);
template void pvr_write32p<u8, true>(u32 addr, u8 data);
@@ -284,25 +285,6 @@ void DYNACALL TAWriteSQ(u32 address, const SQBuffer *sqb)
//Misc interface
#define VRAM_BANK_BIT 0x400000
static u32 pvr_map32(u32 offset32)
{
//64b wide bus is achieved by interleaving the banks every 32 bits
const u32 static_bits = VRAM_MASK - (VRAM_BANK_BIT * 2 - 1) + 3;
const u32 offset_bits = (VRAM_BANK_BIT - 1) & ~3;
u32 bank = (offset32 & VRAM_BANK_BIT) / VRAM_BANK_BIT;
u32 rv = offset32 & static_bits;
rv |= (offset32 & offset_bits) * 2;
rv |= bank * 4;
return rv;
}
template<typename T, bool upper>
T DYNACALL pvr_read_area4(u32 addr)
{
@@ -334,3 +316,88 @@ template void pvr_write_area4<u32, false>(u32 addr, u32 data);
template void pvr_write_area4<u8, true>(u32 addr, u8 data);
template void pvr_write_area4<u16, true>(u32 addr, u16 data);
template void pvr_write_area4<u32, true>(u32 addr, u32 data);
FramebufferWatcher::FramebufferWatcher() {
fbSizeUpdated();
fbSofUpdated(FB_R_SOF1);
}
void FramebufferWatcher::fbSizeUpdated()
{
size = (FB_R_SIZE.fb_y_size + 1) * (FB_R_SIZE.fb_x_size + FB_R_SIZE.fb_modulus) * 4;
fbs[0].end = fbs[0].start + size;
fbs[1].end = fbs[1].start + size;
}
void FramebufferWatcher::fbSofUpdated(u32 sof)
{
u32 waddr = sof & VRAM_MASK;
if (waddr == fbs[0].start || waddr == fbs[1].start)
return;
fbs[1] = fbs[0];
fbs[0].start = waddr;
fbs[0].end = waddr + size;
fbs[0].dirty = false;
DEBUG_LOG(PVR, "FramebufferWatcher: watching new %x old %x SOF1 %x", fbs[0].start, fbs[1].start, sof);
}
void FramebufferWatcher::vramCheck(u32 vramOffset)
{
for (Framebuffer& fb : fbs)
{
if (vramOffset >= fb.start && vramOffset < fb.end)
{
if (!fb.dirty)
DEBUG_LOG(PVR, "FB Write detected in [%x - %x]: %x", fb.start, fb.end, vramOffset);
fb.dirty = true;
break;
}
}
}
bool FramebufferWatcher::isDirty()
{
for (Framebuffer& fb : fbs)
if (fb.dirty) {
fb.dirty = false;
return true;
}
return false;
}
void FramebufferWatcher::serialize(Serializer& ser)
{
for (const Framebuffer& fb : fbs)
{
ser << fb.dirty;
ser << fb.start;
ser << fb.end;
}
}
void FramebufferWatcher::deserialize(Deserializer& deser)
{
if (deser.version() >= Deserializer::V61)
{
for (Framebuffer& fb : fbs)
{
deser >> fb.dirty;
deser >> fb.start;
deser >> fb.end;
}
}
else
{
deser >> fbs[0].dirty;
deser >> fbs[0].start;
deser >> fbs[0].end;
fbs[1] = {};
}
fbSizeUpdated();
}
FramebufferWatcher& FramebufferWatcher::Instance() {
static FramebufferWatcher instance;
return instance;
}
+37
View File
@@ -19,8 +19,45 @@ void YUV_deserialize(Deserializer& deser);
void YUV_reset();
// 32-bit vram path handlers
// The 32-bit bus interleaves the two VRAM banks every 32 bits.
constexpr u32 VRAM_32_BANK_BIT = 0x400000;
inline u32 pvr_map32(u32 offset32)
{
const u32 static_bits = VRAM_MASK - (VRAM_32_BANK_BIT * 2 - 1) + 3;
const u32 offset_bits = (VRAM_32_BANK_BIT - 1) & ~3;
const u32 bank = (offset32 & VRAM_32_BANK_BIT) / VRAM_32_BANK_BIT;
u32 rv = offset32 & static_bits;
rv |= (offset32 & offset_bits) * 2;
rv |= bank * 4;
return rv;
}
template<typename T> T DYNACALL pvr_read32p(u32 addr);
template<typename T, bool Internal = false> void DYNACALL pvr_write32p(u32 addr, T data);
// Area 4 handlers
template<typename T, bool upper> T DYNACALL pvr_read_area4(u32 addr);
template<typename T, bool upper> void DYNACALL pvr_write_area4(u32 addr, T data);
class FramebufferWatcher
{
FramebufferWatcher();
struct Framebuffer {
u32 start = 0;
u32 end = 0;
bool dirty = false;
};
Framebuffer fbs[2];
u32 size = 0;
public:
void fbSizeUpdated();
void fbSofUpdated(u32 sof);
void vramCheck(u32 vramOffset);
bool isDirty();
void serialize(Serializer& ser);
void deserialize(Deserializer& deser);
static FramebufferWatcher& Instance();
};
+4 -4
View File
@@ -171,11 +171,9 @@ void pvr_WriteReg(u32 paddr,u32 data)
return;
case FB_R_SIZE_addr:
if (PvrReg(addr, u32) != data)
{
if (PvrReg(addr, u32) != data) {
PvrReg(addr, u32) = data;
fb_dirty = false;
check_framebuffer_write();
FramebufferWatcher::Instance().fbSizeUpdated();
}
return;
@@ -192,6 +190,8 @@ void pvr_WriteReg(u32 paddr,u32 data)
case FB_R_SOF1_addr:
case FB_R_SOF2_addr:
data &= 0x00fffffc;
if (addr == FB_R_SOF1_addr)
FramebufferWatcher::Instance().fbSofUpdated(data);
rend_swap_frame(data);
break;
+9 -4
View File
@@ -28,8 +28,8 @@ static u64 last_fps;
constexpr int PIXEL_CLOCK = 27 * 1000 * 1000;
static u32 Line_Cycles;
static u32 Frame_Cycles;
int render_end_schid;
int vblank_schid;
int render_end_schid = -1;
int vblank_schid = -1;
static std::array<double, 4> real_times;
static std::array<u64, 4> cpu_cycles;
@@ -319,11 +319,12 @@ void spg_Serialize(Serializer& ser)
ser << Frame_Cycles;
ser << lightgun_line;
ser << lightgun_hpos;
sh4_sched_serialize(ser, vblank_schid);
sh4_sched_serialize(ser, render_end_schid);
}
void spg_Deserialize(Deserializer& deser)
{
if (deser.version() < Deserializer::V30)
deser.skip<u32>(); // in_vblank
deser >> clc_pvr_scanline;
deser >> maple_int_pending;
deser >> pvr_numscanlines;
@@ -332,4 +333,8 @@ void spg_Deserialize(Deserializer& deser)
deser >> Frame_Cycles;
deser >> lightgun_line;
deser >> lightgun_hpos;
if (deser.version() >= Deserializer::V62) {
sh4_sched_deserialize(deser, vblank_schid);
sh4_sched_deserialize(deser, render_end_schid);
}
}
+15 -31
View File
@@ -237,18 +237,14 @@ static void deserializeContext(Deserializer& deser, TA_context **pctx)
*pctx = nullptr;
return;
}
*pctx = tactx_Find(address, true);
u32 size;
deser >> size;
if (size > TA_DATA_SIZE)
throw Deserializer::Exception("Invalid TA context size");
*pctx = tactx_Find(address, true);
tad_context& tad = (*pctx)->tad;
deser.deserialize(tad.thd_root, size);
tad.thd_data = tad.thd_root + size;
if (deser.version() < Deserializer::V26)
{
u32 render_pass_count;
deser >> render_pass_count;
deser.skip(sizeof(u32) * render_pass_count);
}
}
void SerializeTAContext(Serializer& ser)
@@ -268,30 +264,18 @@ void DeserializeTAContext(Deserializer& deser)
{
if (::ta_ctx != nullptr)
SetCurrentTARC(TACTX_NONE);
if (deser.version() >= Deserializer::V25)
u32 listSize;
deser >> listSize;
for (const auto& ctx : ctx_list)
tactx_Recycle(ctx);
ctx_list.clear();
for (u32 i = 0; i < listSize; i++)
{
u32 listSize;
deser >> listSize;
for (const auto& ctx : ctx_list)
tactx_Recycle(ctx);
ctx_list.clear();
for (u32 i = 0; i < listSize; i++)
{
TA_context *ctx;
deserializeContext(deser, &ctx);
}
int curCtx;
deser >> curCtx;
if (curCtx >= 0 && curCtx < (int)ctx_list.size())
SetCurrentTARC(ctx_list[curCtx]->Address);
}
else
{
TA_context *ta_cur_ctx;
deserializeContext(deser, &ta_cur_ctx);
if (ta_cur_ctx != nullptr)
SetCurrentTARC(ta_cur_ctx->Address);
if (deser.version() >= Deserializer::V20)
deserializeContext(deser, &ta_cur_ctx);
TA_context *ctx;
deserializeContext(deser, &ctx);
}
int curCtx;
deser >> curCtx;
if (curCtx >= 0 && curCtx < (int)ctx_list.size())
SetCurrentTARC(ctx_list[curCtx]->Address);
}
+1
View File
@@ -415,6 +415,7 @@ u32 ta_get_list_type();
void ta_set_list_type(u32 listType);
void ta_parse_reset();
void getRegionTileAddrAndSize(u32& address, u32& size);
void setTileClipping(rend_context& ctx);
void sortTriangles(rend_context& ctx, RenderPass& pass, const RenderPass& previousPass);
void sortPolyParams(std::vector<PolyParam>& polys, int first, int end, rend_context& ctx);
+58 -32
View File
@@ -36,6 +36,31 @@ static f32 f16(u16 v)
return *(f32*)&z;
}
static void loadTexture(PolyParam *pp, int index)
{
if (pp->pcw.Texture == 0)
return;
if (index == 0)
{
pp->texture = renderer->GetTexture(pp->tsp, pp->tcw, 0);
if (pp->texture == nullptr)
pp->pcw.Texture = 0;
}
else
{
pp->texture1 = renderer->GetTexture(pp->tsp1, pp->tcw1, 1);
if (pp->texture1 == nullptr)
pp->pcw.Texture = 0;
}
}
static void loadTextures(PolyParam *pp)
{
loadTexture(pp, 0);
if (pp->tsp1.full != (u32)-1)
loadTexture(pp, 1);
}
class BaseTAParser
{
static Ta_Dma *DYNACALL NullVertexData(Ta_Dma *data, Ta_Dma *data_end)
@@ -549,8 +574,8 @@ private:
d_pp->pcw = pp->pcw;
d_pp->tileclip = tileclip_val;
if (d_pp->pcw.Texture && fetchTextures)
d_pp->texture = renderer->GetTexture(d_pp->tsp, d_pp->tcw);
if (fetchTextures)
loadTexture(d_pp, 0);
}
#define glob_param_bdc(pp) glob_param_bdc_( (TA_PolyParam0*)pp)
@@ -609,8 +634,8 @@ private:
CurrentPP->tsp1.full = pp->tsp1.full;
CurrentPP->tcw1.full = pp->tcw1.full;
if (pp->pcw.Texture && fetchTextures)
CurrentPP->texture1 = renderer->GetTexture(pp->tsp1, pp->tcw1, 1);
if (fetchTextures)
loadTexture(CurrentPP, 1);
}
// Intensity, with Two Volumes
@@ -622,8 +647,8 @@ private:
CurrentPP->tsp1.full = pp->tsp1.full;
CurrentPP->tcw1.full = pp->tcw1.full;
if (pp->pcw.Texture && fetchTextures)
CurrentPP->texture1 = renderer->GetTexture(pp->tsp1, pp->tcw1, 1);
if (fetchTextures)
loadTexture(CurrentPP, 1);
}
static void TACALL AppendPolyParam4B(void* vpp)
@@ -981,8 +1006,8 @@ private:
d_pp->pcw = spr->pcw;
d_pp->tileclip = tileclip_val;
if (d_pp->pcw.Texture && fetchTextures)
d_pp->texture = renderer->GetTexture(d_pp->tsp, d_pp->tcw);
if (fetchTextures)
loadTexture(d_pp, 0);
SFaceBaseColor = spr->BaseCol;
SFaceOffsColor = spr->OffsCol;
@@ -1207,7 +1232,7 @@ static void parseRenderPass(RenderPass& pass, const RenderPass& previousPass, re
}
}
static void setTileClipping(rend_context& ctx)
void setTileClipping(rend_context& ctx)
{
u32 xmax, ymax;
getRegionTileClipping((u32&)ctx.tileClip.origin.x, xmax, (u32&)ctx.tileClip.origin.y, ymax);
@@ -1226,8 +1251,7 @@ static void ta_parse_vdrc(TA_context* ctx, bool primRestart)
ta_parse_reset();
PolyParam *bgpp = &vd_rc.global_param_op.front();
if (bgpp->pcw.Texture)
bgpp->texture = renderer->GetTexture(bgpp->tsp, bgpp->tcw);
loadTexture(bgpp, 0);
TA_context *childCtx = ctx;
int pass = 0;
@@ -1276,34 +1300,38 @@ static void ta_parse_vdrc(TA_context* ctx, bool primRestart)
childCtx = childCtx->nextContext;
pass++;
}
if (vd_rc.global_param_mvo_tr.size() > vd_rc.global_param_mvo.size())
{
// Hack for Xtreme Sports. This game shares the same MVO list between opaque and translucent.
// BUT the translucent list is the one to use, contrary to other games. Detect this here and
// swap the arrays, which also allows non-oit renderers to display them.
bool swapMVO = false;
for (const RenderPass& renderPass : vd_rc.render_passes)
{
if (renderPass.mv_op_tr_shared) {
swapMVO = true;
break;
}
}
if (swapMVO)
{
std::swap(vd_rc.global_param_mvo, vd_rc.global_param_mvo_tr);
for (RenderPass& renderPass : vd_rc.render_passes)
std::swap(renderPass.mvo_count, renderPass.mvo_tr_count);
}
}
setTileClipping(ctx->rend);
vd_ctx = nullptr;
}
static void ta_parse_naomi2(TA_context* ctx, bool primRestart)
{
for (PolyParam& pp : ctx->rend.global_param_op)
{
if (pp.pcw.Texture)
pp.texture = renderer->GetTexture(pp.tsp, pp.tcw, 0);
if (pp.tsp1.full != (u32)-1)
pp.texture1 = renderer->GetTexture(pp.tsp1, pp.tcw1, 1);
}
loadTextures(&pp);
for (PolyParam& pp : ctx->rend.global_param_pt)
{
if (pp.pcw.Texture)
pp.texture = renderer->GetTexture(pp.tsp, pp.tcw, 0);
if (pp.tsp1.full != (u32)-1)
pp.texture1 = renderer->GetTexture(pp.tsp1, pp.tcw1, 1);
}
loadTextures(&pp);
for (PolyParam& pp : ctx->rend.global_param_tr)
{
if (pp.pcw.Texture)
pp.texture = renderer->GetTexture(pp.tsp, pp.tcw, 0);
if (pp.tsp1.full != (u32)-1)
pp.texture1 = renderer->GetTexture(pp.tsp1, pp.tcw1, 1);
}
loadTextures(&pp);
ctx->rend.newRenderPass();
RenderPass previousPass{};
@@ -1321,8 +1349,6 @@ static void ta_parse_naomi2(TA_context* ctx, bool primRestart)
}
previousPass = pass;
}
setTileClipping(ctx->rend);
}
void ta_parse(TA_context *ctx, bool primRestart)
+74 -67
View File
@@ -61,82 +61,88 @@ DynarecCodeEntryPtr DYNACALL bm_GetCodeByVAddr(u32 addr)
if (addr & 1)
{
switch (addr)
if (!settings.content.windowsCE) {
Do_Exception(addr, Sh4Ex_AddressErrorRead);
}
else
{
switch (addr)
{
#ifdef USE_WINCE_HACK
case 0xfffffde7: // GetTickCount
// This should make this syscall faster
Sh4cntx.r[0] = sh4_sched_now64() * 1000 / SH4_MAIN_CLOCK;
Sh4cntx.pc = Sh4cntx.pr;
Sh4cntx.cycle_counter -= 100;
break;
case 0xfffffde7: // GetTickCount
// This should make this syscall faster
Sh4cntx.r[0] = sh4_sched_now64() * 1000 / SH4_MAIN_CLOCK;
Sh4cntx.pc = Sh4cntx.pr;
Sh4cntx.cycle_counter -= 100;
break;
case 0xfffffd05: // QueryPerformanceCounter(u64 *)
{
bool isRam;
u64 *ptr;
u32 paddr;
if (rdv_writeMemImmediate(Sh4cntx.r[4], sizeof(u64), (void*&)ptr, isRam, paddr) && isRam)
case 0xfffffd05: // QueryPerformanceCounter(u64 *)
{
*ptr = sh4_sched_now64() >> 4;
Sh4cntx.r[0] = 1;
Sh4cntx.pc = Sh4cntx.pr;
Sh4cntx.cycle_counter -= 100;
}
else
{
Do_Exception(addr, Sh4Ex_AddressErrorRead);
}
}
break;
case 0xfffffded: // GetProcAddressW
{
// Intercept the dricas authentication function and replace it with a stub (Hundred Swords, Rune Jade)
static constexpr u16 Function[] = { 'U', 'A', '_', 'Q', 'u', 'e', 'r', 'y', 'D', '\0' };
bool success = false;
u32 vaddr = Sh4cntx.r[5];
if ((vaddr & 0xffff0000) != 0) // not an ordinal
{
u32 pa = 0;
for (unsigned i = 0; ; i++)
bool isRam;
u64 *ptr;
u32 paddr;
if (rdv_writeMemImmediate(Sh4cntx.r[4], sizeof(u64), (void*&)ptr, isRam, paddr) && isRam)
{
if (pa == 0 && mmu_full_lookup(vaddr, nullptr, pa) != MmuError::NONE)
break;
u16 c = ReadMem16_nommu(pa);
if (c != Function[i])
break;
if (c == 0) {
success = true;
break;
}
vaddr += 2;
if ((vaddr & 0xfff) == 0)
pa = 0;
else
pa += 2;
*ptr = sh4_sched_now64() >> 4;
Sh4cntx.r[0] = 1;
Sh4cntx.pc = Sh4cntx.pr;
Sh4cntx.cycle_counter -= 100;
}
else
{
Do_Exception(addr, Sh4Ex_AddressErrorRead);
}
}
if (success)
break;
case 0xfffffded: // GetProcAddressW
{
INFO_LOG(DYNAREC, "GetProcAddressW('UA_QueryD') intercepted");
Sh4cntx.r[0] = 0xffff0001;
Sh4cntx.pc = Sh4cntx.pr;
// Intercept the dricas authentication function and replace it with a stub (Hundred Swords, Rune Jade)
static constexpr u16 Function[] = { 'U', 'A', '_', 'Q', 'u', 'e', 'r', 'y', 'D', '\0' };
bool success = false;
u32 vaddr = Sh4cntx.r[5];
if ((vaddr & 0xffff0000) != 0) // not an ordinal
{
u32 pa = 0;
for (unsigned i = 0; ; i++)
{
if (pa == 0 && mmu_full_lookup(vaddr, nullptr, pa) != MmuError::NONE)
break;
u16 c = ReadMem16_nommu(pa);
if (c != Function[i])
break;
if (c == 0) {
success = true;
break;
}
vaddr += 2;
if ((vaddr & 0xfff) == 0)
pa = 0;
else
pa += 2;
}
}
if (success)
{
INFO_LOG(DYNAREC, "GetProcAddressW('UA_QueryD') intercepted");
Sh4cntx.r[0] = 0xffff0001;
Sh4cntx.pc = Sh4cntx.pr;
}
else {
Do_Exception(addr, Sh4Ex_AddressErrorRead);
}
}
else {
Do_Exception(addr, Sh4Ex_AddressErrorRead);
}
}
break;
case 0xffff0001: // UA_QueryD stub
DEBUG_LOG(DYNAREC, "UA_QueryD called");
Sh4cntx.r[0] = 0;
Sh4cntx.pc = Sh4cntx.pr;
break;
break;
case 0xffff0001: // UA_QueryD stub
DEBUG_LOG(DYNAREC, "UA_QueryD called");
Sh4cntx.r[0] = 0;
Sh4cntx.pc = Sh4cntx.pr;
break;
#endif
default:
Do_Exception(addr, Sh4Ex_AddressErrorRead);
break;
default:
Do_Exception(addr, Sh4Ex_AddressErrorRead);
break;
}
}
addr = Sh4cntx.pc;
}
@@ -544,9 +550,10 @@ u32 bm_getRamOffset(void *p)
else
#endif
{
if ((u8 *)p < &mem_b[0] || (u8 *)p >= &mem_b[RAM_SIZE])
uintptr_t offset = virtmem::untag(p) - virtmem::untag(&mem_b[0]);
if (offset >= RAM_SIZE)
return -1;
return (u32)((u8 *)p - &mem_b[0]);
return (u32)offset;
}
}
+3
View File
@@ -334,6 +334,9 @@ void* DYNACALL rdv_LinkBlock(u8* code,u32 dpc)
void Sh4Recompiler::Reset(bool hard)
{
super::Reset(hard);
if (hard)
// in case ram base has changed
sh4Dynarec->init(*getContext(), codeBuffer);
ResetCache();
if (hard)
bm_Reset();
+5 -5
View File
@@ -24,6 +24,7 @@
#include "hw/sh4/modules/mmu.h"
#include "ssa.h"
#include <array>
#include <deque>
#include <map>
#include <vector>
@@ -37,19 +38,18 @@ public:
RegAlloc() = default;
virtual ~RegAlloc() = default;
void DoAlloc(RuntimeBlockInfo* block, const nreg_t* regs_avail, const nregf_t* regsf_avail)
template<std::size_t N, std::size_t NF>
void DoAlloc(RuntimeBlockInfo* block, const std::array<nreg_t, N>& regs_avail, const std::array<nregf_t, NF>& regsf_avail)
{
this->block = block;
SSAOptimizer optim(block);
optim.AddVersionPass();
verify(host_gregs.empty());
while (*regs_avail != (nreg_t)-1)
host_gregs.push_back(*regs_avail++);
host_gregs.insert(host_gregs.end(), regs_avail.begin(), regs_avail.end());
verify(host_fregs.empty());
while (*regsf_avail != (nregf_t)-1)
host_fregs.push_back(*regsf_avail++);
host_fregs.insert(host_fregs.end(), regsf_avail.begin(), regsf_avail.end());
}
void OpBegin(shil_opcode* op, int opid)
+5
View File
@@ -98,10 +98,15 @@ void Sh4Interpreter::Step()
void Sh4Interpreter::Reset(bool hard)
{
ctx = &p_sh4rcb->cntx;
verify(!ctx->CpuRunning);
if (hard)
{
// in case ram base has changed
sh4cycles.init(ctx);
icache.init(ctx);
ocache.init(ctx);
int schedNext = ctx->sh4_sched_next;
memset(ctx, 0, sizeof(*ctx));
ctx->sh4_sched_next = schedNext;
+1 -1
View File
@@ -926,7 +926,7 @@ sh4op(i1011_iiii_iiii_iiii)
// trapa #<imm>
sh4op(i1100_0011_iiii_iiii)
{
WARN_LOG(INTERPRETER, "TRAP #%X", GetImm8(op));
INFO_LOG(INTERPRETER, "TRAP #%X", GetImm8(op));
debugger::debugTrap(Sh4Ex_Trap);
CCN_TRA = (GetImm8(op) << 2);
Do_Exception(ctx->pc, Sh4Ex_Trap);
+54 -20
View File
@@ -176,9 +176,35 @@ int main(int argc, char *argv[])
}
#endif
bool UTLB_Sync(u32 entry)
bool fullMmuNeeded(int index)
{
const unsigned start = index < 0 ? 0 : index;
const unsigned end = index < 0 ? std::size(UTLB) : index + 1;
for (unsigned i = start; i < end; i++)
{
const TLB_Entry& entry = UTLB[i];
if (entry.Data.V == 1
// ignore many arcade and Visual Concepts games presumably bogus mappings
&& entry.Address.VPN != 0x30040 && entry.Address.VPN != 0x30000
// ignore identity mapping
&& entry.Address.VPN != entry.Data.PPN
// ignore store queue mapping
&& (entry.Address.VPN & (0xFC000000 >> 10)) != (0xE0000000 >> 10))
{
INFO_LOG(SH4, "fullMmuNeeded[%d]: VPN %x PPN %x ASID %d sz %d", i,
entry.Address.VPN << 10, entry.Data.PPN << 10,
entry.Address.ASID, entry.Data.SZ1 * 2 + entry.Data.SZ0);
return true;
}
}
return false;
}
void UTLB_Sync(u32 entry)
{
TLB_Entry& tlb_entry = UTLB[entry];
if (tlb_entry.Data.V == 0)
return;
u32 sz = tlb_entry.Data.SZ1 * 2 + tlb_entry.Data.SZ0;
tlb_entry.Address.VPN &= mmu_mask[sz] >> 10;
@@ -190,6 +216,8 @@ bool UTLB_Sync(u32 entry)
cache_entry(tlb_entry);
DEBUG_LOG(SH4, "UTLB_Sync[%d]: VPN %x PPN %x ASID %d sz %d", entry, tlb_entry.Address.VPN << 10, tlb_entry.Data.PPN << 10,
tlb_entry.Address.ASID, sz);
if (!mmu_enabled())
{
if ((tlb_entry.Address.VPN & (0xFC000000 >> 10)) == (0xE0000000 >> 10))
@@ -198,20 +226,18 @@ bool UTLB_Sync(u32 entry)
u32 vpn_sq = ((tlb_entry.Address.VPN & 0x7FFFF) >> 10) & 0x3F;//upper bits are always known [0xE0/E1/E2/E3]
sq_remap[vpn_sq] = tlb_entry.Data.PPN << 10;
}
else if (CCN_MMUCR.AT == 1 && tlb_entry.Address.VPN != 0x30040 && tlb_entry.Address.VPN != 0x30000 && tlb_entry.Data.V == 1)
else if (CCN_MMUCR.AT == 1 && fullMmuNeeded(entry))
{
// Enable Full MMU if not an expected store queue mapping
// VPN checks to ignore many arcade and Visual Concepts games presumably bogus mappings
mmuOn = true;
NOTICE_LOG(SH4, "Enabling on-demand Full MMU support");
mmu_set_state();
}
}
return true;
}
void ITLB_Sync(u32 entry)
{
DEBUG_LOG(SH4, "ITLB_Sync[%d]", entry);
}
//Do a full lookup on the UTLB entry's
@@ -249,28 +275,31 @@ MmuError mmu_full_lookup(u32 va, const TLB_Entry** tlb_entry_ret, u32& rv)
}
#ifdef USE_WINCE_HACK
// WinCE hack
TLB_Entry& entry = UTLB[CCN_MMUCR.URC];
if (wince_resolve_address(va, entry))
if (settings.content.windowsCE)
{
CCN_PTEL.reg_data = entry.Data.reg_data;
CCN_PTEA.reg_data = entry.Assistance.reg_data;
CCN_PTEH.reg_data = entry.Address.reg_data;
// WinCE hack to avoid calling the page fault handler
TLB_Entry& entry = UTLB[CCN_MMUCR.URC];
if (wince_resolve_address(va, entry))
{
CCN_PTEL.reg_data = entry.Data.reg_data;
CCN_PTEA.reg_data = entry.Assistance.reg_data;
CCN_PTEH.reg_data = entry.Address.reg_data;
lru_entry = *tlb_entry_ret = &entry;
lru_entry = *tlb_entry_ret = &entry;
u32 sz = entry.Data.SZ1 * 2 + entry.Data.SZ0;
lru_mask = mmu_mask[sz];
lru_address = va & mmu_mask[sz];
entry.Data.PPN &= mmu_mask[sz] >> 10;
u32 sz = entry.Data.SZ1 * 2 + entry.Data.SZ0;
lru_mask = mmu_mask[sz];
lru_address = va & mmu_mask[sz];
entry.Data.PPN &= mmu_mask[sz] >> 10;
rv = (entry.Data.PPN << 10) | (va & ~mmu_mask[sz]);
rv = (entry.Data.PPN << 10) | (va & ~mmu_mask[sz]);
cache_entry(entry);
cache_entry(entry);
p_sh4rcb->cntx.cycle_counter -= 164;
p_sh4rcb->cntx.cycle_counter -= 164;
return MmuError::NONE;
return MmuError::NONE;
}
}
#endif
@@ -333,5 +362,10 @@ void mmu_flush_table()
lru_entry = nullptr;
flush_cache();
mmuAddressLUTFlush(true);
for (TLB_Entry& entry : ITLB)
entry.Data.V = 0;
for (TLB_Entry& entry : UTLB)
entry.Data.V = 0;
}
#endif // FAST_MMU
+25 -17
View File
@@ -55,24 +55,18 @@ constexpr u32 ITLB_LRU_AND[4] =
#ifndef FAST_MMU
//sync mem mapping to mmu , suspend compiled blocks if needed.entry is a UTLB entry # , -1 is for full sync
bool UTLB_Sync(u32 entry)
void UTLB_Sync(u32 entry)
{
printf_mmu("UTLB MEM remap %d : 0x%X to 0x%X : %d asid %d size %d", entry, UTLB[entry].Address.VPN << 10, UTLB[entry].Data.PPN << 10, UTLB[entry].Data.V,
UTLB[entry].Address.ASID, UTLB[entry].Data.SZ0 + UTLB[entry].Data.SZ1 * 2);
if (UTLB[entry].Data.V == 0)
return true;
return;
if ((UTLB[entry].Address.VPN & (0xFC000000 >> 10)) == (0xE0000000 >> 10))
{
// Used when FullMMU is off
u32 vpn_sq = ((UTLB[entry].Address.VPN & 0x7FFFF) >> 10) & 0x3F;//upper bits are always known [0xE0/E1/E2/E3]
sq_remap[vpn_sq] = UTLB[entry].Data.PPN << 10;
return true;
}
else
{
return false;
}
}
//sync mem mapping to mmu , suspend compiled blocks if needed.entry is a ITLB entry # , -1 is for full sync
@@ -441,18 +435,27 @@ void mmu_set_state()
{
if (CCN_MMUCR.AT == 1)
{
// Detect if we're running Windows CE
static const char magic[] = { 'S', 0, 'H', 0, '-', 0, '4', 0, ' ', 0, 'K', 0, 'e', 0, 'r', 0, 'n', 0, 'e', 0, 'l', 0 };
if (memcmp(GetMemPtr(0x8c0110a8, 4), magic, sizeof(magic)) == 0
|| memcmp(GetMemPtr(0x8c011118, 4), magic, sizeof(magic)) == 0)
if (!settings.content.windowsCE)
{
mmuOn = true;
NOTICE_LOG(SH4, "Enabling Full MMU support");
// Detect if we're running Windows CE
static const char magic[] = { 'S', 0, 'H', 0, '-', 0, '4', 0, ' ', 0, 'K', 0, 'e', 0, 'r', 0, 'n', 0, 'e', 0, 'l', 0 };
if (memcmp(GetMemPtr(0x8c0110a8, 4), magic, sizeof(magic)) == 0
|| memcmp(GetMemPtr(0x8c011118, 4), magic, sizeof(magic)) == 0)
{
mmuOn = true;
settings.content.windowsCE = true;
NOTICE_LOG(SH4, "Enabling Windows CE MMU support");
}
else if (fullMmuNeeded()) {
mmuOn = true;
NOTICE_LOG(SH4, "Enabling Full MMU support");
}
}
}
else
{
mmuOn = false;
settings.content.windowsCE = false;
}
SetMemoryHandlers();
@@ -586,11 +589,16 @@ void mmu_serialize(Serializer& ser)
void mmu_deserialize(Deserializer& deser)
{
deser.skip(8, Deserializer::V33); // CCN_QACR_TR
deser >> UTLB;
deser >> ITLB;
deser >> sq_remap;
deser.skip(64 * 4, Deserializer::V23); // ITLB_LRU_USE
if (!settings.content.windowsCE)
{
for (unsigned i = 0; i < std::size(UTLB); i++)
UTLB_Sync(i);
for (unsigned i = 0; i < std::size(ITLB); i++)
ITLB_Sync(i);
}
}
+6 -4
View File
@@ -55,13 +55,14 @@ constexpr u32 mmu_mask[4] =
((0xFFFFFFFF) >> 20) << 20 //1 MB page
};
bool UTLB_Sync(u32 entry);
void UTLB_Sync(u32 entry);
void ITLB_Sync(u32 entry);
bool mmu_match(u32 va, CCN_PTEH_type Address, CCN_PTEL_type Data);
void mmu_set_state();
void mmu_flush_table();
[[noreturn]] void mmu_raise_exception(MmuError mmu_error, u32 address, u32 am);
bool fullMmuNeeded(int index = -1);
static inline bool mmu_enabled()
{
@@ -122,12 +123,13 @@ extern u32 mmuAddressLUT[0x100000];
static inline void mmuAddressLUTFlush(bool full)
{
if (full)
memset(mmuAddressLUT, 0, sizeof(mmuAddressLUT) / 2); // flush user memory
if (full || !settings.content.windowsCE) {
memset(mmuAddressLUT, 0, sizeof(mmuAddressLUT) / 2); // flush user memory (P0)
}
else
{
constexpr u32 slotPages = (32 * 1024 * 1024) >> 12;
memset(mmuAddressLUT, 0, slotPages * sizeof(u32)); // flush slot 0
memset(mmuAddressLUT, 0, slotPages * sizeof(u32)); // flush slot 0 (windows CE)
}
}
#endif
+3
View File
@@ -110,6 +110,9 @@ public:
void reset();
void serialize(Serializer& ser);
void deserialize(Deserializer& deser);
void postDeserialize() {
updateBaudRate();
}
u8 SCFRDR2_read();
void SCFTDR2_write(u8 data);
+4 -9
View File
@@ -375,14 +375,10 @@ void SCIFSerialPort::init()
void SCIFSerialPort::term()
{
if (schedId != -1) {
sh4_sched_unregister(schedId);
schedId = -1;
}
if (brkSchedId != -1) {
sh4_sched_unregister(brkSchedId);
brkSchedId = -1;
}
sh4_sched_unregister(schedId);
schedId = -1;
sh4_sched_unregister(brkSchedId);
brkSchedId = -1;
}
void SCIFSerialPort::reset()
@@ -441,7 +437,6 @@ void SCIFSerialPort::deserialize(Deserializer& deser)
transmitting = false;
}
SCIF_SCBRR2 &= 0xff; // work around previous issues with dynarecs
updateBaudRate();
}
struct PTYPipe : public SerialPort::Pipe
+1 -1
View File
@@ -17,7 +17,7 @@ static u64 tmu_mask[3];
static u32 old_mode[3] = { 0xFFFF, 0xFFFF, 0xFFFF};
static constexpr InterruptID tmu_intID[3] = { sh4_TMU0_TUNI0, sh4_TMU1_TUNI1, sh4_TMU2_TUNI2 };
int tmu_sched[3];
int tmu_sched[3] { -1, -1, -1 };
static s64 tmu_ch_base[3];
inline static u64 now() {
Loaded 100 of 318 files, more files were not shown because too many files have changed in this diff. Show more