93 Commits
Author SHA1 Message Date
github-actions[bot] 7adbf3caef Fetch translations & Recreate libretro_core_options_intl.h 2026-10-31 01:33:35 +00:00
Joseph MattielloandClaude Opus 5.5 1b63c8a367 dreamcast: Default broadcast follows the region
Broadcast has a Default entry in the settings (value 4), but
fixUpDCFlash() only writes values in range, so Default left the flash
byte alone: NTSC from the factory block on a fresh flash, or whatever
the last explicit choice had written. A PAL game booted in NTSC with
Broadcast on Default.

Region's own Default is resolved from IP.BIN just above; resolve
Broadcast the same way: PAL for Europe, NTSC otherwise. Applied with
override() at load, like the region, so the saved setting stays Default.

Testing: compiles on macOS arm64.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 11:48:03 +01:00
Chris Dixon 11ba75f843 libretro: add a Vblank Pacing option (#2562)
Without threaded rendering a run ends when a frame is presented, or
after 50 ms of emulated time without one. Frontends that pace retro_run
by the frame rate instead of blocking on audio, Kodi among them, then
get several vblanks per run through loading screens and have to drop
the extra audio. With the option on, every run ends at the next vblank.
[ci skip]
2026-10-27 11:47:43 +01:00
github-actions[bot] e88b0ced22 Fetch translations & Recreate libretro_core_options_intl.h 2026-10-24 00:37:48 +00:00
Flyinghead 6d81b99816 add pull request template. Fix issue templates. 2026-10-23 11:20:53 +02:00
Flyinghead 53efedbbe7 libretro: don't call the frontend on the emulator thread
Fixes crash with many WinCE games. Issue #2442
2026-10-23 11:20:08 +02:00
Flyinghead fa9d39a355 bba: skip deserialization if device doesn't exist
regression from 5543a95c4f
2026-10-22 12:36:23 +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
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
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
github-actions[bot] ed953fcbfd Fetch translations & Recreate libretro_core_options_intl.h 2026-10-10 01:44:50 +00: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
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
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
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 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
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
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
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
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
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 8f511f8960 Remove macOS Homebrew from Install section
Issue #2461
[ci skip]
2026-09-03 16:11:23 +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
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 e2722869ff The Grinch (US) doesn't support RGB output
Issue #2447
2026-08-30 09:24:17 +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 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
github-actions[bot] 7fcd6976fa Fetch translations & Recreate libretro_core_options_intl.h 2026-08-21 22:38:44 +00: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
106 changed files with 5283 additions and 2665 deletions

No files matched your search

+8 -9
View File
@@ -7,8 +7,8 @@ assignees: ''
---
**Please Note: This form is the minimum required information for submitting bugs.**
**Removing this form may lead to your issue being closed until it is completed.**
<!-- Please Note: This form is the minimum required information for submitting bugs. -->
<!-- Removing this form may lead to your issue being closed until it is completed. -->
Platform / OS / Hardware:
@@ -18,24 +18,23 @@ Hardware:
**_Description of the Issue_**
Add a short, concise description of the issue here
<!-- Add a short, concise description of the issue here -->
**_Debugging Steps Tested_**
* Fill in any steps already tried here
* Begin each new line with an asterisk
* If no steps are required, please skip
<!-- Fill in any steps already tried here -->
<!-- Begin each new line with an asterisk -->
<!-- If no steps are required, please skip -->
**_Logs Gathered_**
```
Please paste the contents of the log / logs here
You may leave this field blank if you have none.
Delete this field if you have none.
```
**_Screenshots_**
(Replace this line with any issue screenshots)
<!-- Replace this line with any issue screenshots -->
+4 -4
View File
@@ -8,13 +8,13 @@ assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
<!-- A clear and concise description of what you want to happen. -->
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Additional context**
Add any other context or screenshots about the feature request here.
<!-- Add any other context or screenshots about the feature request here. -->
+11
View File
@@ -0,0 +1,11 @@
## Description
<!-- Summarize the change and its motivation. -->
<!-- Use "Fixes #123" for an issue this PR closes, or "Related to #123" otherwise. -->
## Checklist
- [ ] This PR targets `dev`, unless it is a critical, low-risk bug fix.
- [ ] I linked the relevant issue or discussion, if one exists.
- [ ] I added or updated tests where appropriate.
- [ ] I tested the change on the relevant platform(s) and noted any limitations.
- [ ] For UI changes, I included screenshots or a recording where helpful.
- [ ] I kept the change focused, followed existing project patterns and avoided code duplication.
+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' }}
+8 -8
View File
@@ -18,11 +18,11 @@ 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
@@ -30,16 +30,16 @@ jobs:
- 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
+40 -29
View File
@@ -21,13 +21,13 @@ 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: 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)
@@ -37,19 +37,26 @@ jobs:
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,7 +81,7 @@ jobs:
arch: ${{ matrix.config.arch }}
if: matrix.config.shell == 'cmd'
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
@@ -84,12 +92,12 @@ jobs:
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"
@@ -100,7 +108,7 @@ jobs:
echo "CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH" >> $GITHUB_ENV
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 }}
@@ -146,25 +154,29 @@ jobs:
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
- 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
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 +198,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)
@@ -197,4 +209,3 @@ jobs:
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' }}
+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:
+52 -11
View File
@@ -89,6 +89,7 @@ option(USE_HOST_SDL "Use host SDL library" ${USE_HOST_SDL_DEFAULT})
option(USE_HOST_LIBCHDR "Use host libchdr" 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)
@@ -205,6 +206,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"
@@ -219,6 +224,9 @@ elseif(LIBRETRO)
endif()
if(ANDROID OR USE_GLES)
target_compile_definitions(${PROJECT_NAME} PRIVATE GLES GLES3 HAVE_OPENGLES HAVE_OPENGLES3 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)
@@ -817,6 +825,13 @@ endif()
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)
@@ -1416,9 +1431,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 +1499,36 @@ 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
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_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
+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;
+2 -2
View File
@@ -217,9 +217,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
@@ -110,6 +110,7 @@ Option<int64_t> PixelBufferSize("rend.PixelBufferSize", 512_MB);
Option<int> AnisotropicFiltering("rend.AnisotropicFiltering", 1);
Option<int> TextureFiltering("rend.TextureFiltering", 0); // Default
Option<bool> ThreadedRendering("rend.ThreadedRendering", true);
Option<bool> VblankPacing("");
Option<bool> DupeFrames("rend.DupeFrames", false);
Option<int> PerPixelLayers("rend.PerPixelLayers", 32);
#ifdef TARGET_UWP
+1
View File
@@ -471,6 +471,7 @@ extern Option<int64_t> PixelBufferSize;
extern Option<int> AnisotropicFiltering;
extern Option<int> TextureFiltering; // 0: default, 1: force nearest, 2: force linear
extern Option<bool> ThreadedRendering;
extern Option<bool> VblankPacing;
extern Option<bool> DupeFrames;
extern Option<bool> NativeDepthInterpolation;
extern Option<bool> EmulateFramebuffer;
+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);
@@ -325,6 +319,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 +655,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; }
+12 -3
View File
@@ -238,6 +238,13 @@ static void loadSpecialSettings()
}
else
WARN_LOG(BOOT, "No region specified in IP.BIN");
// Broadcast left on Default follows the region, the way Region's own
// Default follows IP.BIN above: PAL for Europe, NTSC otherwise.
// fixUpDCFlash() skips the out-of-range Default value, which left the
// flash byte at the factory setting (NTSC) or at whatever the last
// explicit choice wrote.
if (config::Broadcast == 4)
config::Broadcast.override(config::Region == 2 ? 1 : 0);
if (config::Cable <= 1 && (!ip_meta.supportsVGA()
|| prod_id == "T-12504N" // Caesar's Palace (NTSC)
|| prod_id == "12502D-50")) // Caesar's Palace (PAL)
@@ -258,7 +265,8 @@ 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)
{
NOTICE_LOG(BOOT, "Game doesn't support RGB. Using TV Composite instead");
config::Cable.override(3);
@@ -396,7 +404,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);
@@ -1086,7 +1095,7 @@ void Emulator::vblank()
EventManager::event(Event::VBlank);
runner.execTasks();
// Time out if a frame hasn't been rendered for 50 ms
if (sh4_sched_now64() - startTime <= 50_sh4ms)
if (!config::VblankPacing && sh4_sched_now64() - startTime <= 50_sh4ms)
return;
if (ggpo::active())
ggpo::endOfFrame();
+15 -15
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] = {
@@ -431,6 +430,7 @@ struct ChannelEx
{
u32 LSA;
u32 LEA;
u32 alignedLEA;
u8 looped;
} loop;
@@ -800,6 +800,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 +1064,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;
@@ -1104,7 +1108,7 @@ void ChannelEx::StepDecodeSample(u32 CA)
ad2 &= 0xF;
s32 q = adpcm.last_quant;
if (PCMS == ADPCM && CA == loop.LSA)
if (CA == loop.LSA)
{
if (!adpcm.in_loop)
{
@@ -1112,7 +1116,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;
@@ -1153,11 +1157,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 +1168,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;
+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);
}
}
+62 -4
View File
@@ -21,9 +21,50 @@
#include "hw/holly/holly_intc.h"
#include "network/netservice.h"
#include "serialize.h"
#include "hw/sh4/sh4_sched.h"
#include "cfg/option.h"
#include <atomic>
#include <deque>
#include <mutex>
#include <vector>
static RTL8139State *rtl8139device;
// The network backends (picotcp, DCNet) deliver frames from their own
// threads, but the RTL8139 model and the SH-4 interrupt controller belong to
// the emulation thread: raising the receive interrupt from another thread
// races with SIIDRebuild() and can leave stray pending interrupts, which
// the guest sees as an unhandled IRQ. Queue the frames here and hand them
// to the chip from the SH-4 scheduler.
static std::mutex rxMutex;
static std::deque<std::vector<u8>> rxQueue;
static std::atomic<bool> rxPending;
static int rxSchedId = -1;
constexpr size_t RX_QUEUE_MAX = 64;
constexpr int RX_POLL_CYCLES = 1_sh4ms;
static int rxDeliver(int tag, int cycles, int jitter, void *arg)
{
if (rxPending.load(std::memory_order_acquire) && rtl8139device != nullptr)
{
std::lock_guard<std::mutex> _(rxMutex);
while (!rxQueue.empty() && rtl8139_can_receive(rtl8139device))
{
rtl8139_receive(rtl8139device, rxQueue.front().data(), rxQueue.front().size());
rxQueue.pop_front();
}
rxPending = !rxQueue.empty();
}
return RX_POLL_CYCLES;
}
static void rxClear()
{
std::lock_guard<std::mutex> _(rxMutex);
rxQueue.clear();
rxPending = false;
}
// 1400 - 1600 GAPSPCI bridge registers
// 1600 - 1700 standard PCI config
// 1700 - 1800 rtl8139c I/O ports
@@ -49,6 +90,7 @@ 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 void setInterrupt()
{
@@ -64,7 +106,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 +115,10 @@ 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);
rxClear();
rxSchedId = sh4_sched_register(0, rxDeliver);
sh4_sched_request(rxSchedId, RX_POLL_CYCLES);
}
void bba_Term()
@@ -83,6 +129,12 @@ void bba_Term()
rtl8139_destroy(rtl8139device);
rtl8139device = nullptr;
}
if (rxSchedId != -1)
{
sh4_sched_unregister(rxSchedId);
rxSchedId = -1;
}
rxClear();
}
void bba_Reset(bool hard)
@@ -90,7 +142,8 @@ void bba_Reset(bool hard)
if (hard)
{
bba_Term();
bba_Init();
if (config::EmulateBBA && (settings.platform.isConsole() || settings.platform.isAtomiswave()))
bba_Init();
}
}
@@ -213,11 +266,16 @@ ssize_t qemu_send_packet(RTL8139State *s, const uint8_t *buf, int size)
return size;
}
// Called from the network threads
int bba_recv_frame(const u8 *data, u32 len)
{
if (!rtl8139_can_receive(rtl8139device))
if (len < 6 || len > MAX_ETH_FRAME_SIZE)
return 0;
rtl8139_receive(rtl8139device, data, len);
std::lock_guard<std::mutex> _(rxMutex);
if (rxQueue.size() >= RX_QUEUE_MAX)
return 0;
rxQueue.emplace_back(data, data + len);
rxPending.store(true, std::memory_order_release);
return 1;
}
-1
View File
@@ -19,7 +19,6 @@
#pragma once
#include "types.h"
void bba_Init();
void bba_Term();
void bba_Reset(bool hard);
u32 bba_ReadMem(u32 addr, u32 sz);
+19
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);
+4
View File
@@ -154,6 +154,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,6 +173,8 @@ 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
-1
View File
@@ -651,7 +651,6 @@ void sb_Init()
maple_Init();
aica::sbInit();
bba_Init();
ModemInit();
}
+2 -1
View File
@@ -227,7 +227,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()
-1
View File
@@ -1891,7 +1891,6 @@ std::shared_ptr<maple_device> maple_Create(MapleDeviceType type)
default:
ERROR_LOG(MAPLE, "Invalid device type %d", type);
die("Invalid maple device type");
break;
}
return nullptr;
+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;
}
}
+3 -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 },
}
},
{
+2
View File
@@ -171,6 +171,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
{
+32 -19
View File
@@ -200,6 +200,8 @@ private:
if (taContext == nullptr)
return;
// 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;
@@ -266,7 +268,7 @@ private:
if (renderer->Present())
{
presented = true;
if (!config::ThreadedRendering && !ggpo::active())
if (!config::ThreadedRendering && !config::VblankPacing && !ggpo::active())
emu.getSh4Executor()->Stop();
#ifdef LIBRETRO
retro_rend_present();
@@ -328,7 +330,7 @@ public:
{
// force 60/50 FPS now
lastInterval = 1;
stability = std::max(stability, 10);
currentInterval = 1;
return;
}
}
@@ -336,37 +338,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 +394,9 @@ private:
}
int lastInterval;
int stability;
int stable;
int unstable;
int currentInterval;
u64 lastRender;
u64 renderInterval;
+2 -2
View File
@@ -42,7 +42,7 @@ static u32 lightgun_hpos;
static bool maple_int_pending;
#ifdef LIBRETRO
extern bool retro_refresh_av_info(double fps);
extern void retro_setfps(double fps);
#endif
void CalculateSync()
@@ -64,7 +64,7 @@ void CalculateSync()
sh4_sched_request(vblank_schid, Line_Cycles);
#ifdef LIBRETRO
retro_refresh_av_info((double)SH4_MAIN_CLOCK / ((double)Line_Cycles * (double)pvr_numscanlines));
retro_setfps((double)SH4_MAIN_CLOCK / ((double)Line_Cycles * (double)pvr_numscanlines));
#endif
}
+3 -1
View File
@@ -237,9 +237,11 @@ 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;
+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);
+1 -4
View File
@@ -1207,7 +1207,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);
@@ -1277,7 +1277,6 @@ static void ta_parse_vdrc(TA_context* ctx, bool primRestart)
pass++;
}
setTileClipping(ctx->rend);
vd_ctx = nullptr;
}
@@ -1321,8 +1320,6 @@ static void ta_parse_naomi2(TA_context* ctx, bool primRestart)
}
previousPass = pass;
}
setTileClipping(ctx->rend);
}
void ta_parse(TA_context *ctx, bool primRestart)
+3 -2
View File
@@ -544,9 +544,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
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;
+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);
-1
View File
@@ -441,7 +441,6 @@ void SCIFSerialPort::deserialize(Deserializer& deser)
transmitting = false;
}
SCIF_SCBRR2 &= 0xff; // work around previous issues with dynarecs
updateBaudRate();
}
struct PTYPipe : public SerialPort::Pipe
+3
View File
@@ -131,6 +131,9 @@ static void setHostRoundingMode(u32 roundingMode, u32 denorm2zero)
//called when fpscr is changed and we must check for reg banks etc..
void DYNACALL Sh4Context::UpdateFPSCR(Sh4Context *ctx)
{
// The FPU exception cause field is cleared by the next FPU instruction on real hardware.
// FPU instructions don't update it here, so a cause bit written by the guest would stick forever.
ctx->fpscr.full &= ~0x3f000u;
if (ctx->fpscr.FR != ctx->old_fpscr.FR)
// FPU bank change
std::swap(ctx->xf, ctx->fr);
+2
View File
@@ -725,6 +725,8 @@ void deserialize(Deserializer& deser)
p_sh4rcb->cntx.cycle_counter = SH4_TIMESLICE;
sh4_sched_deserialize(deser);
// let the serial port reschedule its callback after the scheduler has been deserialized
SCIFSerialPort::Instance().postDeserialize();
}
void serialize2(Serializer& ser)
+47 -15
View File
@@ -8,8 +8,8 @@ struct CHDDisc : Disc
{
// tracks are padded to a multiple of this many frames
static constexpr u32 CD_TRACK_PADDING = 4;
// lead out, lead in and pregap between 2 sessions of MIL-CDs
static constexpr u32 SESSION_GAP = 11400;
// lead out (01:30:00), lead in (01:00:00) and pregap (00:02:00) between 2 sessions of MIL-CDs
static constexpr u32 SESSION_GAP = 6750 + 4500 + 150;
chd_file *chd = nullptr;
u8* hunk_mem = nullptr;
@@ -143,22 +143,30 @@ void CHDDisc::tryOpen(const char* file)
u32 Offset = 0;
bool isGdrom = head->version < 5; // MIL-CDs only supported starting with CHD v5
bool needAudioSwap = false;
u32 lastPregap = 0;
for(;;)
{
char type[16], subtype[16], pgtype[16], pgsub[16];
int tkid=-1, frames=0, pregap=0, postgap=0, padframes=0;
strcpy(subtype, "NONE");
strcpy(pgtype, "NONE");
strcpy(pgsub, "NONE");
err = chd_get_metadata(chd, CDROM_TRACK_METADATA2_TAG, (u32)tracks.size(), temp, sizeof(temp), &temp_len, &tag, &flags);
if (err == CHDERR_NONE)
{
//"TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d PREGAP:%d PGTYPE:%s PGSUB:%s POSTGAP:%d"
sscanf(temp, CDROM_TRACK_METADATA2_FORMAT, &tkid, type, subtype, &frames, &pregap, pgtype, pgsub, &postgap);
// CD-Rom audio tracks are always stored big-endian
needAudioSwap = true;
}
else if (CHDERR_NONE== (err = chd_get_metadata(chd, CDROM_TRACK_METADATA_TAG, (u32)tracks.size(), temp, sizeof(temp), &temp_len, &tag, &flags)) )
{
//CDROM_TRACK_METADATA_FORMAT "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d"
sscanf(temp, CDROM_TRACK_METADATA_FORMAT, &tkid, type, subtype, &frames);
// CD-Rom audio tracks are always stored big-endian
needAudioSwap = true;
}
else
{
@@ -180,18 +188,30 @@ void CHDDisc::tryOpen(const char* file)
if (tkid != (int)tracks.size() + 1)
throw FlycastException(i18n::Ts("Unexpected track number"));
if (strcmp(subtype, "NONE") != 0 || pregap != 0 || postgap != 0)
throw FlycastException(i18n::Ts("Unsupported subtype or pre/postgap"));
// Subcode data is stored in the CHD but we don't use it. It doesn't change the sector layout.
if (strcmp(subtype, "NONE") != 0)
WARN_LOG(GDROM, "chd: track %d has subcode data (%s). Ignoring", tkid, subtype);
// Postgap sectors are never stored in the file
if (postgap != 0)
WARN_LOG(GDROM, "chd: track %d has a %d frame postgap. Ignoring", tkid, postgap);
DEBUG_LOG(GDROM, "%s", temp);
// When PGTYPE is prefixed with 'V', the pregap sectors are included in the track data,
// and thus counted in FRAMES. Otherwise they only take up space on the disc.
const u32 pregapInFile = pregap > 0 && pgtype[0] == 'V' ? (u32)pregap : 0;
if ((int)pregapInFile > frames)
throw FlycastException(i18n::Ts("Invalid CHD: pregap is longer than the track"));
Track t;
t.StartFAD = total_frames;
total_frames += frames;
// StartFAD is the position of INDEX 01, hence after the pregap
t.StartFAD = total_frames + pregap;
lastPregap = pregap;
total_frames = t.StartFAD + frames - pregapInFile;
t.EndFAD = total_frames - 1 - padframes;
t.CTRL = strcmp(type,"AUDIO") == 0 ? 0 : 4;
u32 sectorSize = getSectorSize(type);
t.file = new CHDTrack(this, Offset - t.StartFAD, sectorSize,
t.file = new CHDTrack(this, Offset + pregapInFile - t.StartFAD, sectorSize,
// audio tracks are byteswapped in recent CHDv5+
!t.isDataTrack() && needAudioSwap);
@@ -221,25 +241,37 @@ void CHDDisc::tryOpen(const char* file)
sessions.push_back(ses);
DEBUG_LOG(GDROM, "session 1: FAD %d", ses.StartFAD);
if (tracks.size() > 1)
// MIL-CDs are multisession CD-Roms whose last session holds the bootable data track.
// CHD files don't carry any session information so we assume that a multi-track disc
// ending with a data track is a MIL-CD, and that its last session has a single track.
if (tracks.size() > 1 && tracks.back().isDataTrack())
{
type = CdRom_XA;
ses.FirstTrack = tracks.size();
// session 1 lead-out: 01:30:00, session 2 lead-in: 01:00:00, pregap: 00:02:00
tracks.back().StartFAD += SESSION_GAP;
tracks.back().EndFAD += SESSION_GAP;
((CHDTrack *)tracks.back().file)->Offset -= SESSION_GAP;
// SESSION_GAP includes the pregap of the first track of the second session.
// When these sectors are part of the track data they have already been accounted for.
const u32 gap = SESSION_GAP - std::min(lastPregap, 150u);
tracks.back().StartFAD += gap;
tracks.back().EndFAD += gap;
((CHDTrack *)tracks.back().file)->Offset -= gap;
ses.StartFAD = tracks.back().StartFAD;
sessions.push_back(ses);
DEBUG_LOG(GDROM, "session 2: track %d FAD %d", ses.FirstTrack, ses.StartFAD);
EndFAD = LeadOut.StartFAD = total_frames + SESSION_GAP - 1;
EndFAD = LeadOut.StartFAD = total_frames + gap;
}
else
{
// Single-track CD-ROMs aren't supported with the exception of naomi mj1/cdp-10002b.chd
// Single-session disc: mixed mode or audio CD. Single-track CD-ROMs aren't
// supported with the exception of naomi mj1/cdp-10002b.chd
type = CdRom;
EndFAD = LeadOut.StartFAD = total_frames - 1;
if (tracks.size() > 1)
for (const Track& track : tracks)
if (track.isDataTrack()) {
type = CdRom_XA;
break;
}
EndFAD = LeadOut.StartFAD = total_frames;
}
}
}
+20 -5
View File
@@ -83,6 +83,7 @@ Disc* cue_parse(const char* file, std::vector<u8> *digest)
u32 currentFAD = 150;
// SESSION context
u32 session_number = 0;
bool firstTrackOfSession = false;
// FILE context
std::string track_filename;
u32 fileStartFAD = 0;
@@ -113,9 +114,11 @@ Disc* cue_parse(const char* file, std::vector<u8> *digest)
else if (cur_session != session_number)
{
session_number = cur_session;
if (session_number == 2)
if (session_number == 2) {
// session 1 lead-out: 01:30:00, session 2 lead-in: 01:00:00, pregap: 00:02:00
currentFAD += 11400;
currentFAD += 6750 + 4500 + 150;
firstTrackOfSession = true;
}
Session ses;
ses.FirstTrack = (u8)disc->tracks.size() + 1;
@@ -154,14 +157,16 @@ Disc* cue_parse(const char* file, std::vector<u8> *digest)
track_filename.clear();
char last;
do {
cuesheet >> last;
} while (isspace(last));
if (!(cuesheet >> last))
throw FlycastException(i18n::T("Invalid CUE file"));
} while (isspace((unsigned char)last));
if (last == '"')
{
cuesheet >> std::noskipws;
for (;;) {
cuesheet >> last;
if (!(cuesheet >> last))
throw FlycastException(i18n::T("Invalid CUE file"));
if (last == '"')
break;
track_filename += last;
@@ -227,6 +232,14 @@ Disc* cue_parse(const char* file, std::vector<u8> *digest)
int min = 0, sec = 0, frame = 0;
if (sscanf(token.c_str(), "%d:%d:%d", &min, &sec, &frame) == 3)
indexFAD = frame + 75 * (sec + 60 * min);
if (firstTrackOfSession && indexFAD > 0) {
// The session gap above already includes this track's pregap so don't
// count the sectors stored before INDEX 01 twice.
const u32 pregap = std::min(indexFAD, 150);
fileStartFAD -= pregap;
currentFAD -= pregap;
}
firstTrackOfSession = false;
Track t;
t.StartFAD = fileStartFAD + indexFAD;
t.CTRL = (track_type == "AUDIO" || track_type == "CDG") ? 0 : 4;
@@ -235,6 +248,8 @@ Disc* cue_parse(const char* file, std::vector<u8> *digest)
DEBUG_LOG(GDROM, "file[%zd] \"%s\": session %d type %s FAD:%d -> %d %s", disc->tracks.size() + 1, track_filename.c_str(),
session_number, track_type.c_str(), t.StartFAD, t.EndFAD, t.isrc.empty() ? "" : ("ISRC " + t.isrc).c_str());
hostfs::File *track_file = hostfs::storage().openFile(track_filename, "rb");
if (track_file == nullptr)
throw FlycastException(strprintf(i18n::T("CUE file: cannot open track %s"), track_filename.c_str()));
t.file = new RawTrackFile(track_file, indexFAD * track_secsize, t.StartFAD, track_secsize);
disc->tracks.push_back(t);
if (disc->tracks.size() >= 2) {
+5 -3
View File
@@ -59,14 +59,16 @@ static Disc* load_gdi(const char* file, std::vector<u8> *digest)
char last;
do {
gdi >> last;
} while (isspace(last));
if (!(gdi >> last))
throw FlycastException(i18n::T("Invalid GDI file"));
} while (isspace((unsigned char)last));
if (last == '"')
{
gdi >> std::noskipws;
for(;;) {
gdi >> last;
if (!(gdi >> last))
throw FlycastException(i18n::T("Invalid GDI file"));
if (last == '"')
break;
track_filename += last;
+1 -1
View File
@@ -137,7 +137,7 @@ bool GamepadDevice::handleButtonInput(int port, DreamcastKey key, bool pressed)
break;
case EMU_BTN_SAVESTATE_RAM:
if (pressed)
gui_saveState(false, true);
gui_saveState(true, true);
break;
case EMU_BTN_SCREENSHOT:
if (pressed)
+24 -15
View File
@@ -18,8 +18,17 @@
#include "cfg/option.h"
#include "hw/maple/maple_if.h"
std::array<std::array<std::list<MapleLink::Ptr>, 2>, 4> MapleLink::Links;
std::mutex MapleLink::Mutex;
std::mutex& MapleLink::mutex()
{
static std::mutex *m = new std::mutex();
return *m;
}
MapleLink::LinkTable& MapleLink::links()
{
static LinkTable *l = new LinkTable();
return *l;
}
namespace
{
@@ -44,10 +53,10 @@ GameState GameState::instance;
std::size_t MapleLink::activeLinkCount(int bus) const
{
std::size_t count = 0;
if (bus >= 0 && bus < (int)Links.size())
if (bus >= 0 && bus < (int)links().size())
{
std::lock_guard<std::mutex> _(Mutex);
for (const std::list<MapleLink::Ptr>& list : Links[bus]) {
std::lock_guard<std::mutex> _(mutex());
for (const std::list<MapleLink::Ptr>& list : links()[bus]) {
if (!list.empty() && list.front().get() == this)
++count;
}
@@ -61,22 +70,22 @@ bool MapleLink::isGameStarted() const {
void MapleLink::registerLink(int bus, int port)
{
if (bus >= 0 && bus < (int)Links.size()
&& port >= 0 && port < (int)Links[0].size())
if (bus >= 0 && bus < (int)links().size()
&& port >= 0 && port < (int)links()[0].size())
{
if (this->bus != bus) {
this->bus = bus;
this->ports = 0;
}
this->ports |= 1 << port;
std::lock_guard<std::mutex> _(Mutex);
Links[bus][port].push_front(shared_from_this());
std::lock_guard<std::mutex> _(mutex());
links()[bus][port].push_front(shared_from_this());
}
}
void MapleLink::unregisterLink(int bus, int port)
{
if (bus >= 0 && bus < (int)Links.size()
&& port >= 0 && port < (int)Links[0].size())
if (bus >= 0 && bus < (int)links().size()
&& port >= 0 && port < (int)links()[0].size())
{
if (this->bus != bus) {
this->bus = -1;
@@ -85,15 +94,15 @@ void MapleLink::unregisterLink(int bus, int port)
else {
this->ports &= ~(1 << port);
}
std::lock_guard<std::mutex> _(Mutex);
Links[bus][port].remove_if([this](const Ptr& item) { return item.get() == this; });
std::lock_guard<std::mutex> _(mutex());
links()[bus][port].remove_if([this](const Ptr& item) { return item.get() == this; });
}
}
bool MapleLink::StorageEnabled()
{
std::lock_guard<std::mutex> _(Mutex);
for (const auto& ports : Links)
std::lock_guard<std::mutex> _(mutex());
for (const auto& ports : links())
{
for (const std::list<MapleLink::Ptr>& list : ports)
if (!list.empty() && list.front()->storageEnabled())
+9 -4
View File
@@ -48,8 +48,8 @@ public:
//! Returns the maple link at the given location if any
static Ptr GetMapleLink(int bus, int port) {
std::lock_guard<std::mutex> _(Mutex);
std::list<MapleLink::Ptr>& list = Links[bus][port];
std::lock_guard<std::mutex> _(mutex());
std::list<MapleLink::Ptr>& list = links()[bus][port];
if (list.empty())
return nullptr;
return list.front();
@@ -66,10 +66,15 @@ protected:
u32 ports = 0;
private:
static std::mutex Mutex;
using LinkTable = std::array<std::array<std::list<Ptr>, 2>, 4>;
// Never destroyed: ~maple_sega_vmu() reaches GetMapleLink() from the
// static destructor of MapleDevices[][] at process exit, after objects
// with static storage in other translation units may already be gone.
// Locking a destroyed std::mutex throws (libc++: "mutex lock failed").
static std::mutex& mutex();
//! Indexed by [bus][extension port][priority idx]
//! Multiple links may be registered for a bus/port, and the last added to front takes precedence
static std::array<std::array<std::list<Ptr>, 2>, 4> Links;
static LinkTable& links();
};
class BaseMapleLink : public MapleLink
+4
View File
@@ -146,6 +146,10 @@ void SetRelativeMousePosition(float xrel, float yrel, u32 mouseId)
{
if (mouseId >= std::size(mo_x_delta))
return;
// Relative motion has a different coordinate space. Rebase the next absolute
// position instead of replaying movement since the last absolute event.
mo_x_prev[mouseId] = -1;
mo_y_prev[mouseId] = -1;
int width = mo_width;
int height = mo_height;
if (config::Rotate90)
+20
View File
@@ -428,6 +428,26 @@ static void luaRegister(lua_State *L)
.beginNamespace("emulator")
.addFunction("startGame", gui_start_game) // FIXME threading!
.addFunction("stopGame", std::function<void()>([]() { gui_stop_game(""); }))
.addFunction("openGdrom", std::function<void()>([]() {
bool restart = false;
if (gui_state == GuiState::Closed) {
gui_open_settings();
restart = true;
}
emu.openGdrom();
if (restart)
gui_open_settings();
}))
.addFunction("insertGdrom", std::function<void(const std::string&)>([](const std::string& path) {
bool restart = false;
if (gui_state == GuiState::Closed) {
gui_open_settings();
restart = true;
}
emu.insertGdrom(path);
if (restart)
gui_open_settings();
}))
.addFunction("pause", std::function<void()>([]() {
if (gui_state == GuiState::Closed)
gui_open_settings();
+12 -1
View File
@@ -42,6 +42,7 @@ namespace net::modbba
{
static TsQueue<u8> toModem;
static constexpr size_t MAX_ETH_FRAME_SIZE = 1514;
class DCNetService : public Service
{
@@ -280,6 +281,8 @@ private:
iterator i = begin;
uint16_t len = (uint8_t)*i++;
len |= uint8_t(*i++) << 8;
if (len > MAX_ETH_FRAME_SIZE)
return std::make_pair(begin + 2, true);
len += 2;
if (end - begin < len)
return std::make_pair(begin, false);
@@ -299,6 +302,14 @@ private:
socket.close(ignored);
return;
}
uint16_t frameLen = recvBuffer[0] | recvBuffer[1] << 8;
if (frameLen > MAX_ETH_FRAME_SIZE || len != (size_t)frameLen + 2)
{
ERROR_LOG(NETWORK, "Invalid Ethernet frame size: %d", frameLen);
std::error_code ignored;
socket.close(ignored);
return;
}
/*
verify(len - 2 == *(u16 *)&recvBuffer[0]);
printf("In frame: dest %02x:%02x:%02x:%02x:%02x:%02x "
@@ -422,7 +433,7 @@ public:
this->handler = std::function<void(const std::error_code&, const asio::ip::address&, const std::string&)>(handler);
try {
asio::ip::udp::resolver resolver(io_context);
auto it = resolver.resolve("dcnet.flyca.st", std::to_string(PORT));
auto it = resolver.resolve(asio::ip::udp::v4(), "dcnet.flyca.st", std::to_string(PORT));
if (it.empty()) {
finish();
return;
+20
View File
@@ -25,6 +25,7 @@
#include "cfg/option.h"
#include "oslib/oslib.h"
#include "oslib/i18n.h"
#include "net_platform.h"
#include <algorithm>
namespace ggpo
@@ -595,6 +596,25 @@ void startSession(int localPort, int localPlayerNum)
peerPort = atoi(config::NetworkServer.get().substr(colon + 1).c_str());
}
player.type = GGPO_PLAYERTYPE_REMOTE;
// GGPO only takes a dotted IPv4 address: resolve host names here
in_addr peerAddr;
if (inet_pton(AF_INET, peerIp.c_str(), &peerAddr) != 1)
{
addrinfo hints {};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
addrinfo *result = nullptr;
if (getaddrinfo(peerIp.c_str(), nullptr, &hints, &result) != 0 || result == nullptr)
{
stopSession();
throw FlycastException(strprintf(i18n::T("GGPO: unknown peer address %s"), peerIp.c_str()));
}
char address[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &((sockaddr_in *)result->ai_addr)->sin_addr, address, sizeof(address));
freeaddrinfo(result);
NOTICE_LOG(NETWORK, "GGPO: peer %s resolved to %s", peerIp.c_str(), address);
peerIp = address;
}
strcpy(player.u.remote.ip_address, peerIp.c_str());
player.u.remote.port = peerPort;
player.player_num = (1 - localPlayerNum) + 1;
+37 -3
View File
@@ -54,8 +54,10 @@ extern "C" {
#include "hw/bba/bba.h"
#include <unordered_map>
#include <deque>
#include <mutex>
#include <future>
#include <vector>
#define RESOLVER1_OPENDNS_COM "208.67.222.222"
#define AFO_ORIG_IP 0x83f2fb3f // 63.251.242.131 in network order
@@ -403,7 +405,10 @@ private:
}
else {
DEBUG_LOG(NETWORK, "TcpSocket[%s] outbound %d bytes", name.c_str(), (int)len);
picoCallback(0);
// A read notification can cover more data than fits in sendbuf.
// Resume draining after the asynchronous write releases that buffer,
// even if no new packet (and therefore no new read event) arrives.
picoCallback(pico_sock != nullptr && pico.state != Closed ? PICO_SOCK_EV_RD : 0);
}
}
@@ -1173,14 +1178,40 @@ static void closeDumpFile()
pcapngDump = nullptr;
}
}
// Frames sent by the emulated BBA arrive on the emulation thread. picotcp is
// built without PICO_SUPPORT_MUTEX, so pico_queue_protect() does nothing and
// calling pico_stack_recv() here corrupts the device queue that the pico
// thread is draining. Queue the frames and feed them from the pico thread.
static std::mutex ethInMutex;
static std::deque<std::vector<u8>> ethInQueue;
constexpr size_t ETH_IN_QUEUE_MAX = 256;
static void pico_receive_eth_frame(const u8 *frame, u32 size)
{
if (pico_dev == nullptr) {
start_pico();
}
else {
dumpFrame(frame, size);
pico_stack_recv(pico_dev, (u8 *)frame, size);
std::lock_guard<std::mutex> _(ethInMutex);
if (ethInQueue.size() < ETH_IN_QUEUE_MAX)
ethInQueue.emplace_back(frame, frame + size);
}
}
// Called on the pico thread
static void feed_eth_frames()
{
std::deque<std::vector<u8>> frames;
{
std::lock_guard<std::mutex> _(ethInMutex);
frames.swap(ethInQueue);
}
if (pico_dev == nullptr)
return;
for (auto& frame : frames)
{
dumpFrame(frame.data(), frame.size());
pico_stack_recv(pico_dev, frame.data(), frame.size());
}
}
@@ -1195,6 +1226,7 @@ static void picoTick(const std::error_code& ec, asio::steady_timer *timer)
ERROR_LOG(NETWORK, "picoTick timer error: %s", ec.message().c_str());
return;
}
feed_eth_frames();
pico_stack_tick();
timer->expires_at(timer->expiry() + asio::chrono::milliseconds(PICO_TICK_MS));
timer->async_wait(std::bind(picoTick, asio::placeholders::error, timer));
@@ -1456,6 +1488,8 @@ static void stop_pico()
emu.setNetworkState(false);
pico_thread_running = false;
pico_thread.stop();
std::lock_guard<std::mutex> _(ethInMutex);
ethInQueue.clear();
}
// picotcp mutex implementation
+31 -13
View File
@@ -61,17 +61,25 @@ void init()
}
}
static bool crackUrl(const std::string& url, URL_COMPONENTS& components)
struct CrackedUrl
{
static char scheme[16], host[256], path[256];
char scheme[16];
char host[256];
char path[2048];
URL_COMPONENTS components;
};
static bool crackUrl(const std::string& url, CrackedUrl& cracked)
{
URL_COMPONENTS& components = cracked.components;
components = {};
components.dwStructSize = sizeof(components);
components.lpszScheme = scheme;
components.dwSchemeLength = sizeof(scheme) / sizeof(scheme[0]);
components.lpszHostName = host;
components.dwHostNameLength = sizeof(host) / sizeof(host[0]);
components.lpszUrlPath = path;
components.dwUrlPathLength = sizeof(path) / sizeof(path[0]);
components.lpszScheme = cracked.scheme;
components.dwSchemeLength = sizeof(cracked.scheme) / sizeof(cracked.scheme[0]);
components.lpszHostName = cracked.host;
components.dwHostNameLength = sizeof(cracked.host) / sizeof(cracked.host[0]);
components.lpszUrlPath = cracked.path;
components.dwUrlPathLength = sizeof(cracked.path) / sizeof(cracked.path[0]);
return InternetCrackUrlA(url.c_str(), url.length(), 0, &components);
}
@@ -82,9 +90,10 @@ static HINTERNET connect(const URL_COMPONENTS& comp) {
int get(const std::string& url, std::vector<u8>& content, const Headers *reqHeaders, Headers *respHeaders)
{
URL_COMPONENTS components;
if (!crackUrl(url, components))
CrackedUrl cracked;
if (!crackUrl(url, cracked))
return 500;
const URL_COMPONENTS& components = cracked.components;
bool https = !strcmp(components.lpszScheme, "https");
@@ -111,6 +120,14 @@ int get(const std::string& url, std::vector<u8>& content, const Headers *reqHead
}
else
{
DWORD status;
DWORD size = sizeof(status);
DWORD index = 0;
if (HttpQueryInfo(hreq, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &size, &index))
rc = status;
else
WARN_LOG(NETWORK, "HttpQueryInfo Error %d", GetLastError());
if (respHeaders != nullptr)
{
std::string headers(4096, '\0');
@@ -163,14 +180,15 @@ int get(const std::string& url, std::vector<u8>& content, const Headers *reqHead
InternetCloseHandle(hreq);
InternetCloseHandle(ic);
return 200;
return rc;
}
static int post(const std::string& url, const char *headers, const u8 *payload, u32 payloadSize, std::vector<u8>& reply)
{
URL_COMPONENTS components;
if (!crackUrl(url, components))
CrackedUrl cracked;
if (!crackUrl(url, cracked))
return 500;
const URL_COMPONENTS& components = cracked.components;
bool https = !strcmp(components.lpszScheme, "https");
+11
View File
@@ -48,6 +48,17 @@ void release_jit_block(void *code_area, size_t size);
// Release a jit block previously allocated by prepare_jit_block (with dual RW and RX areas)
void release_jit_block(void *code_area1, void *code_area2, size_t size);
// The address part of a host pointer. On arm64, Android may put a tag in the top
// byte of heap pointers, and a signal handler receives fault addresses without it.
static inline uintptr_t untag(const void *p)
{
#if HOST_CPU == CPU_ARM64 && defined(__ANDROID__)
return (uintptr_t)p & 0x00ffffffffffffffull;
#else
return (uintptr_t)p;
#endif
}
bool region_lock(void *start, std::size_t len);
bool region_unlock(void *start, std::size_t len);
bool region_set_exec(void *start, std::size_t len);
+7 -4
View File
@@ -598,16 +598,19 @@ bool BaseTextureCacheData::Update()
// xBRZ scaling
if (textureUpscaling)
{
// xBRZ only supports scale factors 2 to 6. Out of range, scale() returns
// without writing the destination buffer.
const int factor = std::clamp((int)config::TextureUpscale, 2, 6);
PixelBuffer<u32> tmp_buf;
tmp_buf.init(width * config::TextureUpscale, height * config::TextureUpscale);
tmp_buf.init(width * factor, height * factor);
if (tcw.PixelFmt == Pixel1555 || tcw.PixelFmt == Pixel4444)
// Alpha channel formats. Palettes with alpha are already handled
has_alpha = true;
UpscalexBRZ(config::TextureUpscale, pb32.data(), tmp_buf.data(), width, height, has_alpha);
UpscalexBRZ(factor, pb32.data(), tmp_buf.data(), width, height, has_alpha);
pb32.steal_data(tmp_buf);
upscaled_w *= config::TextureUpscale;
upscaled_h *= config::TextureUpscale;
upscaled_w *= factor;
upscaled_h *= factor;
}
}
temp_tex_buffer = pb32.data();
+10 -1
View File
@@ -455,7 +455,16 @@ struct DX11OITRenderer : public DX11Renderer
deviceContext->PSSetShaderResources(0, 1, &opaqueTextureView.get());
auto sampler = samplers->getSampler(false);
deviceContext->PSSetSamplers(0, 1, &sampler.get());
deviceContext->RSSetScissorRects(1, &scissorRect);
D3D11_RECT rect {};
if (rendContext->isRTT) {
rect.right = rendContext->framebufferWidth;
rect.bottom = rendContext->framebufferHeight;
}
else {
rect.right = width;
rect.bottom = height;
}
deviceContext->RSSetScissorRects(1, &rect);
deviceContext->OMSetDepthStencilState(depthStencilStates.getState(false, false, 0, false), 0);
setCullMode(0);
+2 -2
View File
@@ -486,7 +486,6 @@ void gl4CreateTextures(int width, int height)
void gl4DrawStrips(GLuint output_fbo, int width, int height)
{
checkOverflowAndReset();
glBindFramebuffer(GL_FRAMEBUFFER, geom_fbo[0]);
if (!gl.rendContext->isRTT)
{
glcache.Disable(GL_SCISSOR_TEST);
@@ -494,6 +493,7 @@ void gl4DrawStrips(GLuint output_fbo, int width, int height)
if (gl.rendContext->clearFramebuffer)
{
// Clear framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, geom_fbo[1]);
glcache.ClearColor(VO_BORDER_COL.red(), VO_BORDER_COL.green(), VO_BORDER_COL.blue(), 1.f);
glClear(GL_COLOR_BUFFER_BIT);
}
@@ -504,7 +504,6 @@ void gl4DrawStrips(GLuint output_fbo, int width, int height)
glBindFramebuffer(GL_READ_FRAMEBUFFER, output_fbo);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glCheck();
glBindFramebuffer(GL_FRAMEBUFFER, geom_fbo[0]);
}
if (gl4ShaderUniforms.base_clipping.enabled)
glcache.Enable(GL_SCISSOR_TEST);
@@ -512,6 +511,7 @@ void gl4DrawStrips(GLuint output_fbo, int width, int height)
if (texSamplers[0] == 0)
glGenSamplers(2, texSamplers);
glBindFramebuffer(GL_FRAMEBUFFER, geom_fbo[0]);
glcache.DepthMask(GL_TRUE);
glClearDepthf(0.f);
glcache.StencilMask(0xFF);
+14 -8
View File
@@ -18,8 +18,10 @@
#include <cstring>
// Vulkan and DirectX use the color values of the first vertex for flat shaded triangle strips.
// On Dreamcast the last vertex is the provoking one so we must copy it onto the first.
// Vulkan and DirectX use the color values of the first vertex for flat shaded triangles.
// On Dreamcast the last vertex is the provoking one, so for strips we copy its colors onto the
// first vertex, and for the depth-sorted triangle list we rotate each triangle's indices instead
// (the sorted list shares vertices between triangles, so copying would corrupt neighbors).
void setFirstProvokingVertex(rend_context& rendContext)
{
auto setProvokingVertex = [&rendContext](const std::vector<PolyParam>& list) {
@@ -62,18 +64,22 @@ void setFirstProvokingVertex(rend_context& rendContext)
}
else
{
// Sorted triangles share vertices (tri0=(P0,P1,P2), tri1=(P2,P1,P3)), and
// they're processed in depth order, so copying colors can overwrite a
// source before it's read. Each triangle owns its 3 index slots though:
// rotate (a,b,c) -> (c,a,b), which keeps the winding and puts the
// provoking vertex first.
for (const SortedTriangle& tri : rendContext.sortedTriangles)
{
if (rendContext.global_param_tr[tri.polyIndex].pcw.Gouraud)
continue;
for (u32 i = 0; i + 2 < tri.count; i += 3)
{
Vertex& vertex = rendContext.verts[rendContext.idx[tri.first + i]];
Vertex& lastVertex = rendContext.verts[rendContext.idx[tri.first + i + 2]];
memcpy(vertex.col, lastVertex.col, sizeof(vertex.col));
memcpy(vertex.spc, lastVertex.spc, sizeof(vertex.spc));
memcpy(vertex.col1, lastVertex.col1, sizeof(vertex.col1));
memcpy(vertex.spc1, lastVertex.spc1, sizeof(vertex.spc1));
u32 *idx = &rendContext.idx[tri.first + i];
const u32 last = idx[2];
idx[2] = idx[1];
idx[1] = idx[0];
idx[0] = last;
}
}
}
+10
View File
@@ -84,8 +84,18 @@ public:
void ResetPixelCounter(vk::CommandBuffer commandBuffer)
{
// Previous render passes and frames in flight must be done with the counter
vk::BufferMemoryBarrier barrier(vk::AccessFlagBits::eShaderWrite, vk::AccessFlagBits::eTransferWrite,
vk::QueueFamilyIgnored, vk::QueueFamilyIgnored, *pixelCounter->buffer, 0, vk::WholeSize);
commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eFragmentShader, vk::PipelineStageFlagBits::eTransfer,
{}, nullptr, barrier, nullptr);
vk::BufferCopy copy(0, 0, sizeof(int));
commandBuffer.copyBuffer(*pixelCounterReset->buffer, *pixelCounter->buffer, copy);
// The reset must be visible to the fragment shaders of the next render pass
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader,
{}, nullptr, barrier, nullptr);
}
void Term()
+1 -1
View File
@@ -231,7 +231,7 @@ protected:
vk::CommandBuffer NewFrame() override;
vk::Framebuffer getFramebuffer(int renderPass, int renderPassCount) override;
vk::ImageLayout getAttachmentInitialLayout() override {
return config::RenderToTextureBuffer ? vk::ImageLayout::eTransferSrcOptimal : vk::ImageLayout::eShaderReadOnlyOptimal;
return vk::ImageLayout::eShaderReadOnlyOptimal;
}
private:
+28 -2
View File
@@ -77,11 +77,37 @@ vk::UniqueRenderPass RenderPasses::MakeRenderPass(bool initial, bool last, bool
std::vector<vk::SubpassDependency> dependencies = GetSubpassDependencies();
dependencies.emplace_back(vk::SubpassExternal, 1, vk::PipelineStageFlagBits::eFragmentShader, vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::AccessFlagBits::eInputAttachmentRead, vk::AccessFlagBits::eColorAttachmentWrite, vk::DependencyFlagBits::eByRegion);
// The OP+PT color and depth/stencil attachments and the OIT storage buffers are shared by consecutive
// render passes and by all the frames in flight. Previous reads (input attachments) and writes (color, depth, a-buffers)
// must be done before these attachments are cleared or loaded and the a-buffers are written again.
// Attachments 1 and 2 are first used in subpass 0 and depth attachment 3 in subpass 1.
constexpr vk::PipelineStageFlags oitStages = vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests
| vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eColorAttachmentOutput;
for (u32 subpass : { 0u, 1u })
dependencies.emplace_back(vk::SubpassExternal, subpass, oitStages, oitStages,
vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite | vk::AccessFlagBits::eShaderWrite,
vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite
| vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite
| vk::AccessFlagBits::eInputAttachmentRead | vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite);
// Attachment 0 is first used in subpass 2. With multiple render passes, it was the OP+PT color attachment
// of the previous render pass (written as color and read as input attachment).
dependencies.emplace_back(vk::SubpassExternal, 2,
vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::AccessFlagBits::eColorAttachmentWrite,
vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite);
dependencies.emplace_back(0, 1, vk::PipelineStageFlagBits::eLateFragmentTests, vk::PipelineStageFlagBits::eFragmentShader,
vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
vk::AccessFlagBits::eInputAttachmentRead | vk::AccessFlagBits::eShaderRead, vk::DependencyFlagBits::eByRegion);
dependencies.emplace_back(1, 2, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader,
vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eInputAttachmentRead, vk::DependencyFlagBits::eByRegion);
// Chains the external dependency above with the OP+PT color attachment transition to subpass 2
dependencies.emplace_back(0, 1, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite,
vk::DependencyFlagBits::eByRegion);
// The OP+PT color attachment is stored at the end of subpass 2
dependencies.emplace_back(1, 2, vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eInputAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite,
vk::DependencyFlagBits::eByRegion);
// This dependency is only needed if the render pass isn't the last: it's needed for the depth-only Tr pass
// Unfortunately we want all render passes to be compatible, and that means all attachments must be identical
dependencies.emplace_back(1, 2, vk::PipelineStageFlagBits::eFragmentShader,
+2
View File
@@ -149,6 +149,8 @@ bool VulkanContext::InitInstance(const char** extensions, uint32_t extensions_co
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr;
#if defined(__ANDROID__) && HOST_CPU == CPU_ARM64
vkGetInstanceProcAddr = loadVulkanDriver();
#elif defined(__APPLE__) && defined(USE_SDL)
vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(SDL_Vulkan_GetVkGetInstanceProcAddr());
#else
static vk::detail::DynamicLoader dl;
vkGetInstanceProcAddr = dl.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr");
+61 -21
View File
@@ -323,6 +323,35 @@ static std::shared_ptr<SDLMouse> getMouse(u32 mouseId)
return mouse;
}
static u32 openFileEventType()
{
static const u32 eventType = SDL_RegisterEvents(1);
return eventType;
}
// Queue a game to be started from the main loop.
// A user event is used rather than SDL_DROPFILE because sdl2-compat doesn't carry over
// the file name of drop events pushed by the application, and crashes when converting them back.
bool sdl_queue_open_file(const char *path)
{
if (openFileEventType() == (u32)-1)
return false;
if (SDL_WasInit(SDL_INIT_EVENTS) == 0 && SDL_InitSubSystem(SDL_INIT_EVENTS) != 0)
return false;
SDL_Event event = {};
event.type = openFileEventType();
event.user.data1 = SDL_strdup(path);
if (event.user.data1 == nullptr)
return false;
if (SDL_PushEvent(&event) != 1)
{
SDL_free(event.user.data1);
return false;
}
return true;
}
void input_sdl_handle()
{
SDLGamepad::UpdateRumble();
@@ -330,6 +359,12 @@ void input_sdl_handle()
SDL_Event event;
while (SDL_PollEvent(&event))
{
if (event.type == openFileEventType())
{
gui_start_game((const char *)event.user.data1);
SDL_free(event.user.data1);
continue;
}
switch (event.type)
{
case SDL_QUIT:
@@ -341,6 +376,27 @@ void input_sdl_handle()
checkRawInput();
if (event.key.repeat == 0)
{
// Alt-Return and F11 toggle full screen
if (event.type == SDL_KEYDOWN
&& ((event.key.keysym.sym == SDLK_RETURN && (event.key.keysym.mod & KMOD_ALT))
|| (event.key.keysym.sym == SDLK_F11 && (event.key.keysym.mod & (KMOD_ALT | KMOD_CTRL | KMOD_SHIFT | KMOD_GUI)) == 0)))
{
if (window_fullscreen)
{
SDL_SetWindowFullscreen(window, 0);
if (!gameRunning || !mouseCaptured)
SDL_ShowCursor(SDL_ENABLE);
}
else
{
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
if (gameRunning)
SDL_ShowCursor(SDL_DISABLE);
}
window_fullscreen = !window_fullscreen;
break;
}
// Route other keyboard events to master in multiboard mode
if (settings.naomi.slave) {
Multiboard::keyboardEvent(event.key.keysym.scancode, event.type == SDL_KEYDOWN);
break;
@@ -369,25 +425,6 @@ void input_sdl_handle()
};
if (event.type == SDL_KEYDOWN)
{
// Alt-Return and F11 toggle full screen
if ((event.key.keysym.sym == SDLK_RETURN && (event.key.keysym.mod & KMOD_ALT))
|| (event.key.keysym.sym == SDLK_F11 && (event.key.keysym.mod & (KMOD_ALT | KMOD_CTRL | KMOD_SHIFT | KMOD_GUI)) == 0))
{
if (window_fullscreen)
{
SDL_SetWindowFullscreen(window, 0);
if (!gameRunning || !mouseCaptured)
SDL_ShowCursor(SDL_ENABLE);
}
else
{
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
if (gameRunning)
SDL_ShowCursor(SDL_DISABLE);
}
window_fullscreen = !window_fullscreen;
break;
}
// Left-Alt + Left-CTRL toggles mouse capture
if ((event.key.keysym.mod & KMOD_LALT) && (event.key.keysym.mod & KMOD_LCTRL)
&& !(is_key_mapped(SDL_SCANCODE_LALT) || is_key_mapped(SDL_SCANCODE_LCTRL)))
@@ -569,6 +606,7 @@ void input_sdl_handle()
case SDL_DROPFILE:
gui_start_game(event.drop.file);
SDL_free(event.drop.file);
break;
// Switch touchscreen support
@@ -610,7 +648,9 @@ void input_sdl_handle()
void sdlReceiveSlaveKeyboardEvent(u16 scancode, bool pressed)
{
sdl_keyboard->input((SDL_Scancode)scancode, pressed);
if (sdl_keyboard != nullptr)
// TODO raw input support
sdl_keyboard->input((SDL_Scancode)scancode, pressed);
if (pressed)
{
u32 flags = SDL_GetWindowFlags(window);
@@ -960,7 +1000,7 @@ void sdl_window_create()
die("error initializing SDL Video subsystem");
}
#if defined(__APPLE__) && defined(USE_VULKAN)
SDL_Vulkan_LoadLibrary("libvulkan.dylib");
SDL_Vulkan_LoadLibrary("@executable_path/../Frameworks/libMoltenVK.dylib");
#endif
}
sdlDeInit.initialized = true;
+1
View File
@@ -5,6 +5,7 @@
void input_sdl_init();
void input_sdl_handle();
void input_sdl_quit();
bool sdl_queue_open_file(const char *path);
void sdl_window_create();
void sdl_window_destroy();
bool sdl_recreate_window(u32 flags);
+4
View File
@@ -100,6 +100,10 @@ public:
return bind;
}
// Axis
// bind{} only zeroes the union's first member (button); direction is
// read below even when no sign or '~' sets it.
bind.value.axis.axis = 0;
bind.value.axis.direction = 0;
if (mapping[valuePos] == '+') {
bind.value.axis.direction = 1;
valuePos++;
+12 -6
View File
@@ -75,6 +75,7 @@ void dc_deserialize(Deserializer& deser)
sh4::deserialize(deser);
deser >> config::EmulateBBA.get();
bba_Reset(true);
if (config::EmulateBBA)
bba_Deserialize(deser);
ModemDeserialize(deser);
@@ -85,12 +86,17 @@ void dc_deserialize(Deserializer& deser)
naomi_Deserialize(deser);
deser >> config::Broadcast.get();
verify(config::Broadcast >= 0 && config::Broadcast <= 4);
deser >> config::Cable.get();
verify(config::Cable >= 0 && config::Cable <= 3);
deser >> config::Region.get();
verify(config::Region >= 0 && config::Region <= 3);
// Validate before applying: a corrupt or incompatible state shouldn't
// abort the process or leave invalid settings behind.
int broadcast, cable, region;
deser >> broadcast;
deser >> cable;
deser >> region;
if (broadcast < 0 || broadcast > 4 || cable < 0 || cable > 3 || region < 0 || region > 3)
throw Deserializer::Exception("Invalid broadcast, cable or region");
config::Broadcast.get() = broadcast;
config::Cable.get() = cable;
config::Region.get() = region;
naomi_cart_deserialize(deser);
reios_deserialize(deser);
+10
View File
@@ -264,3 +264,13 @@ std::string timeToShortDateTimeString(time_t time) {
return i18n::formatShortDateTime(time);
}
MD5Sum& MD5Sum::add(hostfs::File *file)
{
file->seek(0, SEEK_SET);
char buf[4096];
size_t len;
while ((len = file->read(buf, 1, sizeof(buf))) > 0)
MD5_Update(&ctx, buf, (unsigned long)len);
return *this;
}
+8
View File
@@ -10,6 +10,7 @@
#include <thread>
#include <vector>
#include <functional>
#include <type_traits>
#include <cassert>
#include <time.h>
@@ -186,6 +187,10 @@ std::string strprintf(const char *format, Args ... args)
return out;
}
namespace hostfs {
class File;
}
class MD5Sum
{
MD5_CTX ctx;
@@ -209,8 +214,11 @@ public:
return *this;
}
MD5Sum& add(hostfs::File *file);
template<typename T>
MD5Sum& add(const T& v) {
static_assert(!std::is_pointer<T>::value, "MD5Sum::add() would hash the pointer value, not the data it points to");
MD5_Update(&ctx, &v, (unsigned long)sizeof(T));
return *this;
}
+15 -3
View File
@@ -87,6 +87,8 @@ void Boxart::fetchBoxart()
return;
if (toFetch.empty())
return;
if (getTimeMs() < pauseUntil)
return;
fetching = std::async(std::launch::async, [this]() {
ThreadName _("BoxArt-scraper");
if (offlineScraper == nullptr)
@@ -119,7 +121,15 @@ void Boxart::fetchBoxart()
toFetch.erase(toFetch.begin(), toFetch.begin() + size);
}
DEBUG_LOG(COMMON, "Scraping %d games", (int)boxart.size());
offlineScraper->scrape(boxart);
for (GameBoxart& b : boxart)
{
if (b.parsed)
continue;
offlineScraper->scrape(b);
if (b.parsed)
databaseDirty = true;
}
if (databaseDirty)
{
std::lock_guard<std::mutex> guard(mutex);
for (GameBoxart& b : boxart)
@@ -128,7 +138,6 @@ void Boxart::fetchBoxart()
if (!config::FetchBoxart || b.scraped)
b.busy = false;
games[b.fileName] = b;
databaseDirty = true;
}
}
if (config::FetchBoxart)
@@ -147,7 +156,7 @@ void Boxart::fetchBoxart()
databaseDirty = true;
} catch (const std::runtime_error& e) {
if (*e.what() != '\0')
INFO_LOG(COMMON, "thegamesdb error: %s", e.what());
WARN_LOG(COMMON, "thegamesdb error: %s", e.what());
{
// put back failed items into toFetch array
std::lock_guard<std::mutex> guard(mutex);
@@ -165,6 +174,9 @@ void Boxart::fetchBoxart()
}
}
}
if (!databaseDirty)
// No progress so pause for 10 s
pauseUntil = getTimeMs() + 10 * 1000;
saveDatabase();
});
}
+1
View File
@@ -59,6 +59,7 @@ private:
std::unique_ptr<Scraper> arcadeScraper;
bool databaseLoaded = false;
bool databaseDirty = false;
u64 pauseUntil = 0;
std::vector<GameBoxart> toFetch;
std::future<void> fetching;
+7 -5
View File
@@ -76,13 +76,15 @@ json TheGamesDb::httpGet(const std::string& url)
std::vector<u8> receivedData;
int status = http::get(url, receivedData);
bool success = http::success(status);
if (status == 403)
// hit rate-limit cap
blackoutPeriod = getTimeMs() + 60 * 1000;
else if (!success)
if (status == 403 || status == 429) {
// monthly allowance reached
blackoutPeriod = getTimeMs() + 24 * 60 * 60 * 1000;
throw std::runtime_error("TheGamesDB monthly allowance reached. Pausing for 24 hours");
}
if (!success)
blackoutPeriod = getTimeMs() + 1000;
if (!success || receivedData.empty())
throw std::runtime_error("http error");
throw std::runtime_error(strprintf("http error %d", status));
std::string content((const char *)&receivedData[0], receivedData.size());
DEBUG_LOG(COMMON, "TheGameDb: received [%s]", content.c_str());
+5 -11
View File
@@ -832,7 +832,8 @@ static void gameTooltip(const std::string& tip)
static bool gameImageButton(ImguiTexture& texture, const std::string& tooltip, ImVec2 size, const std::string& gameName)
{
bool pressed = texture.button("##imagebutton", size, gameName);
gameTooltip(tooltip);
if (ImGui::IsRectVisible(size))
gameTooltip(tooltip);
return pressed;
}
@@ -975,17 +976,10 @@ static void gui_display_content()
if (counter % itemsPerLine != 0)
ImGui::SameLine();
counter++;
if (ImGui::IsRectVisible(responsiveBoxVec2))
{
ImguiFileTexture tex(art.boxartPath);
pressed = gameImageButton(tex, game.name, responsiveBoxVec2, gameName);
}
else {
ImGui::Dummy(responsiveBoxVec2);
}
ImguiFileTexture tex(art.boxartPath);
pressed = gameImageButton(tex, game.name, responsiveBoxVec2, gameName);
}
else
{
else {
pressed = ImGui::Selectable(gameName.c_str());
}
if (pressed)
+12 -9
View File
@@ -518,17 +518,20 @@ void ImguiTexture::draw(ImDrawList *drawList, const ImVec2& pos, const ImVec2& s
bool ImguiTexture::button(const char* str_id, const ImVec2& image_size, const std::string& title,
const ImVec4& bg_col, const ImVec4& tint_col)
{
ImTextureID id = getId();
if (id == ImTextureID{})
return ImGui::Button(title.c_str(), image_size);
else
const std::string label = title + str_id;
if (ImGui::IsRectVisible(image_size))
{
const float ar = imguiDriver->getAspectRatio(id);
const ImVec2 size = image_size - ImGui::GetStyle().FramePadding * 2;
ImVec2 uv0, uv1;
setUV(ar / size.x * size.y, uv0, uv1);
return ImGui::ImageButton(str_id, id, size, uv0, uv1, bg_col, tint_col);
ImTextureID id = getId();
if (id != ImTextureID{})
{
const float ar = imguiDriver->getAspectRatio(id);
const ImVec2 size = image_size - ImGui::GetStyle().FramePadding * 2;
ImVec2 uv0, uv1;
setUV(ar / size.x * size.y, uv0, uv1);
return ImGui::ImageButton(label.c_str(), id, size, uv0, uv1, bg_col, tint_col);
}
}
return ImGui::Button(label.c_str(), image_size);
}
static u8 *loadImage(const std::string& path, int& width, int& height)
+2
View File
@@ -185,6 +185,8 @@ void EGLGraphicsContext::term()
if (display != EGL_NO_DISPLAY)
{
glFlush();
glFinish();
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (context != EGL_NO_CONTEXT)
@@ -20,7 +20,7 @@ android {
defaultConfig {
applicationId = "com.flycast.emulator"
minSdk = 21
targetSdk = 35
targetSdk = 36
versionCode = 8
versionName = gitVersionName()
vectorDrawables.useSupportLibrary = true
@@ -133,20 +133,15 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat.
Log.i("flycast", "Initializing input devices");
InputDeviceManager.getInstance().startListening(getApplicationContext());
register(this);
audioBackend = new AudioBackend();
onConfigurationChanged(getResources().getConfiguration());
// Ignore the Back button on Android 13+.
// This is apparently required on Android 16 since returning true from onKeyDown() is ignored.
/*
// Back button handling on Android 13+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, () -> {
// Ignore the Back button. It is handled in onKeyDown()
onBackKey();
});
}
*/
// When viewing a resource, pass its URI to the native code for opening
Intent intent = getIntent();
@@ -166,7 +161,7 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat.
}
// Called from native code
private void showAlertDialog(String message)
public void showAlertDialog(String message)
{
handler.post(new Runnable() {
@Override
@@ -354,16 +349,20 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat.
return super.onKeyUp(keyCode, event);
}
private void onBackKey() {
if (JNIdc.guiIsContentBrowser()) {
finish();
}
else {
showMenu();
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (event.getRepeatCount() == 0) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (JNIdc.guiIsContentBrowser()) {
finish();
}
else {
showMenu();
}
onBackKey();
return true;
}
InputDeviceManager deviceManager = InputDeviceManager.getInstance();
@@ -43,6 +43,7 @@ public final class InputDeviceManager implements InputManager.InputDeviceListene
public void startListening(Context applicationContext)
{
knownDevices.clear();
maple_port = 0;
hasTouchscreen = applicationContext.getPackageManager().hasSystemFeature("android.hardware.touchscreen");
if (hasTouchscreen)
@@ -62,7 +62,7 @@ void SwappyGLGC::activate(bool enable)
if (this->enabled == enable)
return;
this->enabled = enable;
if (enable)
if (enable && g_activity != nullptr)
{
SwappyGL_init(jni::env(), g_activity);
swappyAvailable = SwappyGL_isEnabled();
@@ -74,7 +74,9 @@ void SwappyGLGC::activate(bool enable)
SwappyGL_setAutoPipelineMode(false);
}
}
else {
else
{
SwappyGL_setWindow(nullptr);
SwappyGL_destroy();
swappyAvailable = false;
}
@@ -52,6 +52,8 @@ static void activate(bool enable)
{
if (settings.input.fastForwardMode || !config::VSync || !googleTimingSupported)
return;
if (g_activity == nullptr)
return;
// Fake call to make swappy use google timing for this device
VkExtensionProperties prop { { VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME } };
@@ -83,9 +85,9 @@ static void activate(bool enable)
SwappyVk_setAutoPipelineMode(false);
SwappyVk_setSwapIntervalNS((VkDevice)device, (VkSwapchainKHR)swapchain, SWAPPY_SWAP_60FPS);
}
else {
if (swapchain)
SwappyVk_destroySwapchain((VkDevice)device, (VkSwapchainKHR)swapchain);
else if (swapchain) {
SwappyVk_setWindow((VkDevice)device, (VkSwapchainKHR)swapchain, nullptr);
SwappyVk_destroySwapchain((VkDevice)device, (VkSwapchainKHR)swapchain);
}
}
@@ -11,6 +11,7 @@
#include <unistd.h>
#include "ui/gui.h"
#include "oslib/oslib.h"
#include "sdl/sdl.h"
#ifdef USE_BREAKPAD
#include "client/mac/handler/exception_handler.h"
@@ -357,31 +358,13 @@ static bool dumpCallback(const char *dump_dir, const char *minidump_id, void *co
return succeeded;
}
#endif
/*
* Catch document open requests...this lets us notice files when the app
* was launched by double-clicking a document, or when a document was
* dragged/dropped on the app's icon. You need to have a
* CFBundleDocumentsType section in your Info.plist to get this message,
* apparently.
*
* Files are added to gArgv, so to the app, they'll look like command line
* arguments. Previously, apps launched from the finder had nothing but
* an argv[0].
*
* This message may be received multiple times to open several docs on launch.
*
* This message is ignored once the app's mainline has been called.
*/
// Queue document opens until the main loop can safely load the game.
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
{
dispatch_async(dispatch_get_main_queue(), ^(){
gui_start_game([filename cStringUsingEncoding:NSUTF8StringEncoding]);
});
return TRUE;
// AppKit may deliver a document before SDL initializes the video subsystem.
return sdl_queue_open_file([filename UTF8String]) ? YES : NO;
}
/* Called when the internal event loop has just started running */
- (void) applicationDidFinishLaunching: (NSNotification *) note
{
+71 -44
View File
@@ -5,6 +5,12 @@ set -euo pipefail
script_dir="$(cd "$(dirname "$0")" && pwd)"
cd "$script_dir/../../"
MOLTENVK_TAG="v1.4.2-pre.973bb08"
MOLTENVK_ROOT="$PWD/build/deps/MoltenVK-${MOLTENVK_TAG}"
moltenvk_root=""
moltenvk_dylib=""
install_homebrew() {
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
if [ -x /opt/homebrew/bin/brew ]; then
@@ -18,6 +24,19 @@ install_homebrew() {
fi
}
ensure_homebrew() {
if command -v brew >/dev/null 2>&1; then
return
fi
read -p $'\033[1mHomebrew is required to install missing build tools.\033[0m Type \033[1;32my\033[0m to install it now: ' b
if [ "$b" = "y" ]; then
install_homebrew
else
exit 1
fi
}
init_submodules() {
if [ -d .git ]; then
if git rev-parse --verify HEAD >/dev/null 2>&1; then
@@ -36,42 +55,50 @@ init_submodules() {
fi
}
install_vulkan_sdk() {
local installer=""
local install_root=""
find_molten_vk() {
local candidate=""
echo 'Downloading the latest Vulkan SDK from LunarG'
curl -fL "https://sdk.lunarg.com/sdk/download/latest/mac/vulkan_sdk.zip?Human=true" -o vulkan_sdk.zip
if [ ! -d "$MOLTENVK_ROOT" ]; then
return 1
fi
echo 'Extracting the Vulkan SDK installer'
unzip -q vulkan_sdk.zip
while IFS= read -r candidate; do
case "$candidate" in
*/macOS/*|*/macos-*) ;;
*) continue ;;
esac
installer="$(find . -type f -path './vulkansdk-macOS-*.app/Contents/MacOS/*' -print | head -n 1)"
install_root="$HOME/VulkanSDK"
moltenvk_root="$MOLTENVK_ROOT"
moltenvk_dylib="$candidate"
return 0
done < <(/usr/bin/find "$MOLTENVK_ROOT" -type f -name libMoltenVK.dylib)
echo "Installing Vulkan SDK into $install_root"
"$installer" --root "$install_root" --accept-licenses --default-answer --confirm-command install copy_only=1
echo >> ~/.zprofile
echo "export VULKAN_SDK=$HOME/VulkanSDK/macOS" >> ~/.zprofile
echo "VULKAN_SDK is now set in .zprofile"
export VULKAN_SDK="$HOME/VulkanSDK/macOS"
rm -f vulkan_sdk.zip
rm -rf ./vulkansdk-macOS-*.app
return 1
}
if ! command -v brew >/dev/null 2>&1; then
read -p $'\033[1mHomebrew is required.\033[0m Type \033[1;32my\033[0m to install it now: ' b
if [ "$b" = "y" ]; then
install_homebrew
else
download_molten_vk() {
local extract_tmp="$MOLTENVK_ROOT.tmp"
local archive="$PWD/build/deps/MoltenVK-macos-${MOLTENVK_TAG}.tar"
local url="https://github.com/vkedwardli/MoltenVK/releases/download/${MOLTENVK_TAG}/MoltenVK-macos.tar"
mkdir -p "$(dirname "$archive")"
curl -fL "$url" -o "$archive"
rm -rf "$extract_tmp"
mkdir -p "$extract_tmp"
tar -xf "$archive" -C "$extract_tmp"
rm -rf "$MOLTENVK_ROOT"
mv "$extract_tmp" "$MOLTENVK_ROOT"
if ! find_molten_vk; then
echo "MoltenVK ${MOLTENVK_TAG} was downloaded, but macOS libMoltenVK.dylib was not found." >&2
exit 1
fi
fi
}
if ! command -v cmake >/dev/null 2>&1; then
read -p $'\033[1mCMake is required.\033[0m Type \033[1;32my\033[0m to install it with Homebrew: ' c
if [ "$c" = "y" ]; then
ensure_homebrew
brew install cmake
else
exit 1
@@ -130,6 +157,7 @@ if [ -z "$xcode_app" ]; then
read -p $'Or type \033[1;32my\033[0m to install Xcode '"$xcode_version"$' with Homebrew (you will be prompted to sign in with your Apple account): ' c
fi
if [ "$c" = "y" ]; then
ensure_homebrew
brew install xcodes
if [ "$xcode_version" = "latest" ]; then
xcodes install --latest --no-superuser
@@ -157,22 +185,6 @@ if [ ! -f core/deps/glslang/CMakeLists.txt ]; then
fi
fi
if [[ -n "${VULKAN_SDK:-}" && -d "${VULKAN_SDK}" ]]; then
echo "Using Vulkan SDK from $VULKAN_SDK"
elif [ -f "$HOME/VulkanSDK/setup-env.sh" ]; then
export VULKAN_SDK="$HOME/VulkanSDK/macOS"
echo "Using Vulkan SDK from $VULKAN_SDK"
else
echo $'\033[1mVulkan SDK is required.\033[0m VULKAN_SDK is not configured.'
read -p $'Type \033[1;32my\033[0m to install the latest Vulkan SDK: ' v
if [ "$v" = "y" ]; then
install_vulkan_sdk
echo "Using Vulkan SDK from $VULKAN_SDK"
else
echo 'Vulkan will be disabled' >&2
fi
fi
echo "1) macOS ($(uname -m))"
echo "2) iOS"
read -p "Choose your target platform: " x
@@ -187,11 +199,26 @@ if [ "${x:-1}" = "2" ]; then
echo 'Building iOS xcodeproj for debugging'
echo 'Remove CODE_SIGNING_ALLOWED=NO in Build Settings if you are using your Apple Developer Certificate for signing'
else
option="-DCMAKE_OSX_ARCHITECTURES=$(uname -m) -DUSE_VULKAN="
if [[ -n "${VULKAN_SDK:-}" ]]; then
option+="ON"
if find_molten_vk; then
echo "Using MoltenVK from $moltenvk_root"
use_vulkan="ON"
else
option+="OFF"
echo $'\033[1mMoltenVK is required for Vulkan.\033[0m'
echo "Download URL: https://github.com/vkedwardli/MoltenVK/releases/download/${MOLTENVK_TAG}/MoltenVK-macos.tar"
read -p $'Type \033[1;32my\033[0m to download MoltenVK from the configured release URL: ' v
if [ "$v" = "y" ]; then
download_molten_vk
echo "Using MoltenVK from $moltenvk_root"
use_vulkan="ON"
else
echo 'Vulkan will be disabled' >&2
use_vulkan="OFF"
fi
fi
option="-DCMAKE_OSX_ARCHITECTURES=$(uname -m) -DUSE_VULKAN=$use_vulkan"
if [ "$use_vulkan" = "ON" ]; then
option+=" -DMOLTENVK_DYLIB=$moltenvk_dylib"
fi
lldbinitfolder="emulator-osx"
echo 'Building macOS xcodeproj for debugging'
+24 -23
View File
@@ -55,6 +55,8 @@
#include "hw/sh4/sh4_sched.h"
#include "keyboard_map.h"
#include "hw/maple/maple_cfg.h"
#include "hw/naomi/systemsp.h"
#include "hw/pvr/elan.h"
#include "hw/maple/maple_if.h"
#include "hw/pvr/pvr_regs.h"
#include "hw/pvr/Renderer_if.h"
@@ -441,6 +443,8 @@ void retro_deinit()
emu.term();
#endif
libretro_supports_bitmasks = false;
led_state_cb = NULL;
memset(retro_led_state, 0, sizeof(retro_led_state));
categoriesSupported = false;
platformIsDreamcast = true;
platformIsArcade = false;
@@ -760,16 +764,19 @@ bool setAVInfo(retro_system_av_info& avinfo)
return true;
}
bool retro_refresh_av_info(double fps)
void retro_setfps(double fps)
{
if (fps > 0 && fps < 100)
fps_spg = fps;
}
static bool retro_refresh_av_info()
{
retro_system_av_info avinfo;
if (first_run || game_data.empty())
return false;
if (fps > 0 && fps < 100)
fps_spg = fps;
if (setAVInfo(avinfo))
{
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avinfo);
@@ -801,7 +808,7 @@ void retro_resize_renderer(int w, int h, float aspectRatio)
else
{
// Check if timing change is needed instead
if (retro_refresh_av_info(0))
if (retro_refresh_av_info())
return;
retro_game_geometry geometry;
@@ -2464,22 +2471,16 @@ size_t retro_get_memory_size(unsigned type)
size_t retro_serialize_size()
{
DEBUG_LOG(SAVESTATE, "retro_serialize_size");
std::lock_guard<std::mutex> lock(mtx_serialization);
if (!first_run)
try {
emu.stop();
} catch (const FlycastException& e) {
ERROR_LOG(COMMON, "%s", e.what());
return 0;
}
Serializer ser;
dc_serialize(ser);
if (!first_run)
emu.start();
return ser.size();
// A frontend may size its buffer once, but a state grows and shrinks with the
// devices plugged in and the frames being drawn. Report the memory regions plus
// the allowance GGPO makes for everything else, Naomi's for System SP too.
size_t size = settings.platform.ram_size + settings.platform.vram_size
+ settings.platform.aram_size;
if (settings.platform.isNaomi2())
size += elan::ERAM_SIZE;
if (settings.platform.isSystemSP() && systemsp::SystemSpCart::Instance != nullptr)
size += systemsp::SystemSpCart::Instance->getSerializedMediaSize();
return size + (settings.platform.isNaomi() || settings.platform.isSystemSP() ? 20_MB : 10_MB);
}
bool retro_serialize(void *data, size_t size)
@@ -2888,7 +2889,7 @@ static void updateLightgunCoordinates(u32 port)
lightgun_params[port].y = mo_y_abs[port];
}
void updateLightgunCoordinatesFromAnalogStick(int port)
static void updateLightgunCoordinatesFromAnalogStick(int port)
{
int x = input_cb(port, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X);
mo_x_abs[port] = 320 + x * 320 / 32767;
@@ -3874,7 +3875,7 @@ static bool read_m3u(const char *file)
log_cb(RETRO_LOG_ERROR, "Could not read file\n");
return false;
}
unsigned disk_index = 0;
while (filestream_gets(f, line, sizeof(line)) && disk_index <= disk_paths.size())
{
if (line[0] == '#')
+28
View File
@@ -644,6 +644,20 @@ struct retro_core_option_v2_definition option_defs_us[] = {
},
"enabled",
},
{
CORE_OPTION_NAME "_vblank_pacing",
"Vblank Pacing",
NULL,
"Each frame the frontend runs emulates exactly one vblank, whether or not the game drew. For frontends that pace the core by its frame rate rather than by blocking on audio; without it, loading screens that draw nothing run several vblanks per frame and their sound arrives in bursts. Needs Threaded Rendering disabled.",
NULL,
"performance",
{
{ "disabled", NULL },
{ "enabled", NULL },
{ NULL, NULL },
},
"disabled",
},
{
CORE_OPTION_NAME "_auto_skip_frame",
"Auto Skip Frame",
@@ -837,6 +851,20 @@ struct retro_core_option_v2_definition option_defs_us[] = {
},
"disabled",
},
{
CORE_OPTION_NAME "_dump_unique_textures",
"Discard Video and Animated Textures",
NULL,
"Skip dumping video (YUV) and already updated textures.",
NULL,
"hacks",
{
{ "disabled", NULL },
{ "enabled", NULL },
{ NULL, NULL },
},
"disabled",
},
{
CORE_OPTION_NAME "_dump_replaced_textures",
"Dump Replaced Textures",
File diff suppressed because it is too large. Load diff
+4
View File
@@ -0,0 +1,4 @@
{
global: retro_*;
local: *;
};
+1
View File
@@ -89,6 +89,7 @@ Option<bool> IntegerScale("");
Option<bool> LinearInterpolation("", true);
Option<bool> VSync("", true);
Option<bool> ThreadedRendering(CORE_OPTION_NAME "_threaded_rendering", true);
Option<bool> VblankPacing(CORE_OPTION_NAME "_vblank_pacing");
Option<int> AnisotropicFiltering(CORE_OPTION_NAME "_anisotropic_filtering");
Option<int> TextureFiltering(CORE_OPTION_NAME "_texture_filtering");
Option<bool> PowerVR2Filter(CORE_OPTION_NAME "_pvr2_filtering");
+1 -1
View File
@@ -59,7 +59,7 @@ private:
std::vector<BaseOption *> options;
std::string gameId;
retro_environment_t retroEnv = nullptr;
const retro_core_option_v2_definition *optionDefs;
const retro_core_option_v2_definition *optionDefs = nullptr;
template<typename T, bool>
friend class Option;
+2 -2
View File
@@ -119,11 +119,11 @@ std::string findNaomiBios(const std::string& name)
{
std::string basepath(game_dir_no_slash);
basepath += PATH_DEFAULT_SLASH() + name;
if (!file_exists(basepath))
if (!path_is_valid(basepath.c_str()))
{
// File not found in system dir, try game dir instead
basepath = g_roms_dir + name;
if (!file_exists(basepath))
if (!path_is_valid(basepath.c_str()))
return "";
}
return basepath;
+4
View File
@@ -15,22 +15,26 @@ target_sources(${PROJECT_NAME} PRIVATE
src/serialize_test.cpp
src/AicaArmTest.cpp
src/Sh4InterpreterTest.cpp
src/SorterTest.cpp
src/MmuTest.cpp
src/HttpTest.cpp
src/IniFileTest.cpp
src/hw/modem/v42Test.cpp
src/hw/modem/v42bisTest.cpp
src/hw/sh4/modules/TimerTest.cpp
src/imgread/ChdTest.cpp
src/imgread/CueTest.cpp
src/imgread/GdiTest.cpp
src/input/ButtonComboTest.cpp
src/input/GamepadInputHandlingTest.cpp
src/input/MultiBindMappingTest.cpp
src/input/MouseTest.cpp
src/input/InputDefTest.cpp
src/input/InputMappingConfigFileTest.cpp
src/input/InputSetTest.cpp
src/input/SDLControllerMappingTest.cpp
src/oslib/I18nTest.cpp
src/util/Md5SumTest.cpp
src/util/PeriodicThreadTest.cpp
src/util/TsQueueTest.cpp
src/util/WorkerThreadTest.cpp)
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
FILE "audiocd01.bin" BINARY
TRACK 01 AUDIO
INDEX 01 00:00:00
FILE "audiocd02.bin" BINARY
TRACK 02 AUDIO
INDEX 01 00:00:00
FILE "audiocd03.bin" BINARY
TRACK 03 AUDIO
INDEX 01 00:00:00
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Regenerates the .cue and .chd test images sitting in this directory.
./make_fixtures.py (needs chdman from mame-tools)
Each sector of the generated .bin files starts with the 8 bytes
{ track, 0x11, frame & 0xff, 0x22, frame >> 8, 0x33, 0x44, 0x55 }
and is zero filled afterwards, so a sector identifies the track and the frame
it belongs to. Swapping the marker byte pairs gives a different value, which is
what lets ChdTest catch a missing or spurious byte swap of the audio tracks.
The .bin files are deleted once chdman has read them: they are 8 MB of filler
and everything needed to rebuild them is here.
"""
import os
import shutil
import subprocess
import sys
# name -> (cue header, [(track number, type, pregap frames, data frames)])
# The pregap frames are stored in the track's own .bin, ie. the track has an
# INDEX 00, unless the layout says otherwise below.
FIXTURES = {
# MIL-CD laid out as Redump dumps them: the audio tracks of the first
# session carry their pregap, the data track of the second one doesn't.
"milcd": [
("REM SESSION 01", 1, "AUDIO", 0, 300),
(None, 2, "AUDIO", 183, 300),
(None, 3, "AUDIO", 210, 200),
("REM SESSION 02", 4, "MODE2/2352", 0, 450),
],
# Same disc, but the pregap of the second session's track is in the image
"milcd_pregap": [
("REM SESSION 01", 1, "AUDIO", 0, 300),
("REM SESSION 02", 2, "MODE2/2352", 150, 300),
],
# Same disc again, with that pregap declared but not stored
"milcd_nogap": [
("REM SESSION 01", 1, "AUDIO", 0, 300),
("REM SESSION 02", 2, "MODE2/2352", -150, 300),
],
# Single session audio CD: no session gap must be inserted
"audiocd": [
(None, 1, "AUDIO", 0, 150),
(None, 2, "AUDIO", 0, 150),
(None, 3, "AUDIO", 0, 150),
],
}
SECTOR = 2352
def msf(frames):
return "%02d:%02d:%02d" % (frames // (75 * 60), frames // 75 % 60, frames % 75)
def write_bin(path, track, frames):
with open(path, "wb") as f:
for i in range(frames):
sector = bytearray(SECTOR)
sector[0:8] = bytes([track, 0x11, i & 0xFF, 0x22, i >> 8, 0x33, 0x44, 0x55])
f.write(sector)
def build(name, tracks, chdman):
bins = []
cue = []
for header, track, track_type, pregap, frames in tracks:
if header:
cue.append(header)
# A negative pregap is declared with a PREGAP command instead of an
# INDEX 00, ie. those sectors take up space on the disc but aren't stored
stored_pregap = pregap if pregap > 0 else 0
binname = "%s%02d.bin" % (name, track)
write_bin(os.path.join(os.path.dirname(__file__) or ".", binname),
track, stored_pregap + frames)
bins.append(binname)
cue.append('FILE "%s" BINARY' % binname)
cue.append(" TRACK %02d %s" % (track, track_type))
if pregap < 0:
cue.append(" PREGAP %s" % msf(-pregap))
if stored_pregap:
cue.append(" INDEX 00 00:00:00")
cue.append(" INDEX 01 %s" % msf(stored_pregap))
here = os.path.dirname(__file__) or "."
cuepath = os.path.join(here, name + ".cue")
with open(cuepath, "w") as f:
f.write("\n".join(cue) + "\n")
chdpath = os.path.join(here, name + ".chd")
subprocess.run([chdman, "createcd", "-i", cuepath, "-o", chdpath, "-f"],
check=True, stdout=subprocess.DEVNULL)
for binname in bins:
os.remove(os.path.join(here, binname))
print("%s: %d tracks, %d bytes" % (name + ".chd", len(tracks), os.path.getsize(chdpath)))
def main():
chdman = shutil.which("chdman")
if chdman is None:
sys.exit("chdman not found (apt install mame-tools)")
for name, tracks in FIXTURES.items():
build(name, tracks, chdman)
if __name__ == "__main__":
main()
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
REM SESSION 01
FILE "milcd01.bin" BINARY
TRACK 01 AUDIO
INDEX 01 00:00:00
FILE "milcd02.bin" BINARY
TRACK 02 AUDIO
INDEX 00 00:00:00
INDEX 01 00:02:33
FILE "milcd03.bin" BINARY
TRACK 03 AUDIO
INDEX 00 00:00:00
INDEX 01 00:02:60
REM SESSION 02
FILE "milcd04.bin" BINARY
TRACK 04 MODE2/2352
INDEX 01 00:00:00
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
REM SESSION 01
FILE "milcd_nogap01.bin" BINARY
TRACK 01 AUDIO
INDEX 01 00:00:00
REM SESSION 02
FILE "milcd_nogap02.bin" BINARY
TRACK 02 MODE2/2352
PREGAP 00:02:00
INDEX 01 00:00:00
Binary file not shown.
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff. Show more