Commit Graph

229 Commits

Author SHA1 Message Date
ParantezTech
9eb021c7d1 [CI] fix check 2026-07-15 16:01:54 +03:00
ParantezTech
4ac29f340a [CI] Update for cross-platform support 2026-07-15 15:52:15 +03:00
kuba
fa2616d224 Linux and macOS support (#47)
* [macos/linux] Cross-platform host memory, TLS, and ABI layer for POSIX

Introduces the foundation for running SharpEmu on macOS (osx-x64 under
Rosetta 2) and Linux (linux-x64). The CPU backend executes guest x86-64
code natively, so these targets run the whole process as x86-64; this
commit replaces the Windows-only host primitives with platform-dispatched
equivalents so the guest boots and services HLE calls off Windows.

Memory (HostMemory.cs, new): a Win32-semantics facade over
mmap/mprotect/munmap with a shadow region table answering VirtualQuery.
PhysicalVirtualMemory, DirectExecutionBackend, StubManager, and the two
Kernel*CompatExports now go through it instead of kernel32 P/Invokes.
Exact-address requests use MAP_FIXED_NOREPLACE (Linux) / guarded
MAP_FIXED (macOS) so they match Win32 "map there or fail" semantics.

TLS + host helpers (PosixHostStubs.cs, new): pthread-backed TLS and
Win64-ABI-compatible stubs for the kernel32 helpers the backend embeds
into emitted x86-64 code (TlsGetValue, QueryPerformanceCounter,
SwitchToThread, Sleep). A Win64->SysV thunk wraps managed callbacks,
since .NET on POSIX compiles them for the SysV ABI while the emitted
call sites use Win64.

Guest address layout: the 0x7FFx window is Windows-only (dyld shared
cache / Rosetta runtime live there on POSIX), so stack/TLS/stub regions
relocate to 0x6FFx off Windows.

Vectored exception handling is gated off on POSIX for now (guest faults
are not yet recovered) — the signal-based bridge is the next step. Also
adds osx-x64 to the RID list and a Docker-based Linux smoke-test script.

Status: on both macOS (Rosetta) and Linux (amd64), the guest now boots,
runs native x86-64 code, and dispatches HLE imports. macOS stops at a
Rosetta translation-cache issue; Linux runs ~252 imports through C++
static-init before hitting the missing fault handler (SIGSEGV).

* [posix] Bridge the vectored exception handler to sigaction(SIGSEGV/SIGBUS/SIGILL)

Guest faults on macOS/Linux previously terminated the process because the
recovery logic in DirectExecutionBackend.Exceptions.cs was Windows-only.
This adds a POSIX front-end that reuses the existing handler bodies:

- DirectExecutionBackend.PosixSignals.cs installs SA_SIGINFO handlers via
  an [UnmanagedCallersOnly] entry, rebuilds the Win64 EXCEPTION_POINTERS /
  CONTEXT view from the platform mcontext (Darwin __ss thread state via
  the mcontext pointer at ucontext+48, Linux glibc gregs at ucontext+40 --
  offsets verified against the headers on both platforms), runs the same
  chain as the VEH path (TryRecoverUnresolvedSentinel trap-sentinel
  recovery, TryHandleLazyCommittedPage demand paging, VectoredHandler
  diagnostics incl. FS/GS TLS-fault detection), and writes register
  changes back into the mcontext so sigreturn resumes the repaired guest.
  Unrecovered faults chain to the previously installed handler so the
  .NET runtime keeps mapping its own faults to managed exceptions.

- The whole recovery path is warmed up with fabricated inputs before the
  handlers are installed. This is required under Rosetta 2: the signal
  trampoline cannot enter x86 code that has never been executed (and so
  never translated) -- a cold handler is silently never invoked and the
  faulting instruction retries forever (reproduced and verified in an
  isolated .NET test under Rosetta for Linux). It also keeps first-fault
  JIT work out of the signal frame.

- Handlers run without SA_ONSTACK: the runtime's alternate stacks are too
  small for the diagnostic path, while guest (2MB) and host thread stacks
  match where Windows dispatches exceptions anyway.

- The raw reads in the shared fault diagnostics (stack qwords, RBP walk,
  code bytes at RIP) now probe the region table on POSIX before touching
  memory, since a nested SIGSEGV inside the handler would kill the
  process before diagnostics finish. Windows keeps its try/catch reads.

- Escape hatches: SHARPEMU_DISABLE_POSIX_SIGNALS=1 skips installation,
  SHARPEMU_DISABLE_RAW_HANDLER=1 disables sentinel recovery (parity with
  Windows), SHARPEMU_LOG_POSIX_SIGNALS=1 traces every delivery (first 16
  and every 1024th are always traced).

Verified with the test game: Linux (amd64 container) previously died with
SIGSEGV right after import #252; it now recovers/diagnoses signals and the
run proceeds to the real next blocker, an unpatched negative-offset guest
TLS read (fault at TLS base - 0x1708), which gets the full NATIVE
EXCEPTION dump before terminating. macOS is unchanged: the bridge installs
and the game still stops at the known Rosetta translation-cache error at
import 12, which is the next work item.

* [posix] Fix guest memory layout faults: TLS prefix, exact mmap, map search base

Three fixes that take the test game from dying during libc init to running
its full main loop on macOS and Linux:

- Static TLS blocks live below the TCB (FreeBSD amd64 variant II) and
  libc.prx reaches past -0x1700, but only a 4KB prefix was mapped below
  the TLS base. The prefix is now 64KB on POSIX (Windows keeps 4KB); the
  fault was a read at TLS base - 0x1708 during libc init.

- HostMemory exact allocation on macOS used MAP_FIXED, which silently
  maps over untracked host memory. The direct-memory allocator's address
  scan walked into the .NET runtime's JIT heap and replaced live code,
  which under Rosetta 2 surfaced as "no code fragment associated with
  the given arm pc". Exact placement now passes the address as a hint
  and fails on relocation, like MAP_FIXED_NOREPLACE does on Linux.

- sceKernelMapDirectMemory/MapFlexibleMemory searched for free space
  starting at 4GB, which is the Mach-O image base on macOS. The default
  search base is 0x20_0000_0000 on POSIX, and TryAllocateAtOrAbove now
  asks the kernel for a placement instead of page-stepping through host-
  owned address space (Rosetta ignores mmap hints for whole VA windows),
  over-allocating when the caller needs more than page alignment.

Windows behavior is unchanged; all divergences are platform-guarded.

* [macos] Video presenter on the main thread, MoltenVK support, window keyboard input

Gets the test game from a headless loop to a playable window on macOS:

- AppKit traps with SIGILL ("NSUpdateCycleInitialize() is called off the
  main thread") when GLFW runs on a worker thread. The CLI now moves
  emulation onto a worker thread on macOS and parks the real main thread
  in HostMainThread.Pump(); the presenter posts its whole window loop
  there instead of spawning a thread, and a shutdown handler asks the
  render loop to close the window so the pump unwinds on guest exit.

- MoltenVK: enable VK_KHR_portability_enumeration (+ the portability
  instance flag) and VK_KHR_portability_subset when advertised, and gate
  robustBufferAccess2 on the device actually supporting it (Metal does
  not; the old code keyed it off robustImageAccess2 and vkCreateDevice
  failed with ErrorFeatureNotPresent).

- Input: pad exports polled user32 GetAsyncKeyState, so POSIX hosts threw
  DllNotFoundException per scePadReadState call. The presenter now
  attaches the window's keyboard via Silk.NET.Input into HostWindowInput,
  and the pad exports map the existing VK-code layout onto it off
  Windows. Headless hosts (Linux containers) report a disconnected
  keyboard and fall back to neutral pad data silently.

GLFW needs an x86-64 Vulkan loader under Rosetta: place a universal
libMoltenVK.dylib next to SharpEmu named libvulkan.1.dylib (Homebrew's
arm64-only copy cannot load into the x86-64 process) and export
DYLD_LIBRARY_PATH to that directory.

Verified: Dreaming Sarah boots to a MoltenVK-backed 2560x1440 window on
macOS (Apple M4, Rosetta 2), renders the intro, title, and menus, and
keyboard input drives it into gameplay. Linux (amd64 container) runs the
same build headless through millions of imports with no faults. Windows
paths unchanged; arm64 and x64 builds clean.

* [posix] CoreAudio playback, self-contained MoltenVK loading, input/log polish

- Audio: sceAudioOut ports now play through an AudioQueue backend on macOS
  (stereo PCM16 with the same 32KB backpressure pacing as the WinMM path).
  The WinMM port and the new CoreAudio port share an IHostAudioPort
  interface and sample converter; hosts without a backend (Linux
  containers) keep the silent fallback.

- MoltenVK: GLFW resolves Vulkan with dlopen("libvulkan.1.dylib"), which
  cannot see the app-local universal MoltenVK build, so the presenter now
  feeds vkGetInstanceProcAddr straight into glfwInitVulkanLoader (GLFW
  3.4) before creating the window. No DYLD_LIBRARY_PATH needed; the CLI
  also preloads the dylib for Silk.NET and prints setup hints when it is
  missing. scripts/fetch-macos-moltenvk.sh stages the official universal
  dylib next to a build.

- The virtual-range allocator's failure trace now names the address and
  length instead of "AllocateAt invocation threw".

Investigated and documented (not port defects): the savedata transaction
failure is identical on Linux and macOS (HLE argument-register mapping for
sceSaveDataCreateTransactionResource), and the in-game tile speckling has
no platform-specific code in its path - the one macOS-only delta is that
MoltenVK lacks robustBufferAccess2, so out-of-bounds shader reads return
garbage instead of zeros.

Verified on macOS: window, audio backend, and keyboard input all come up
with zero environment configuration; the game runs to gameplay. Linux
headless run unchanged (silent audio, no faults). Windows paths untouched;
arm64 and x64 builds clean.

* [cpu] Preserve guest registers and flags across patched TLS accesses

The TLS patch handler replaces guest `mov reg, fs:[...]` instructions,
which preserve every other register and the flags - but the handler
loaded the TLS index into ecx and called TlsGetValue (Win64: clobbers
rcx/rdx/r8-r11) with `sub/add rsp` trashing the arithmetic flags. Guest
code that keeps live values or comparison results across a TLS access
computed garbage deterministically. The handler now saves rcx, rdx,
r8-r11, and the flags around the call, keeping the same inner stack
alignment. This applies to the load patches and both store-helper stubs,
on every platform.

Also in this change, from the rendering-artifact investigation:

- The present blit picks linear filtering for any fractional scale
  (nearest only for integer upscales): a 3840x2160 guest frame blitted
  into a 2560x1440 swapchain with nearest silently dropped every third
  row/column.
- ClampViewport no longer trims the guest viewport rectangle to the
  render target; trimming changed the guest's scale/offset and skewed
  texel addressing. Vulkan permits viewports beyond the framebuffer
  (the scissor confines rendering), so only spec bounds are enforced.
- Env-gated diagnostics grown during the investigation: guest texture
  dumps (SHARPEMU_TEXTURE_DUMP_DIR), aliased guest-image readback dumps
  (SHARPEMU_TRACE_GUEST_IMAGES=alias), small-render-target write movies
  (SHARPEMU_TRACE_GUEST_WRITES=small), unattended input injection
  (SHARPEMU_AUTO_CROSS=secs,...), viewport nudging
  (SHARPEMU_VIEWPORT_EPSILON), chunked-draw toggle
  (SHARPEMU_DISABLE_CHUNKED_DRAWS), and rect-list/draw vertex traces.

Known remaining issue (root cause narrowed, not yet fixed): the game's
terrain texture pages are corrupted in guest memory before any GPU work
- the mound's solid-fill 32x32 tiles decode to fully transparent texels
and the grass page has deterministic gaps, byte-identical across runs.
Ruled out: memcpy/memmove/memset/realloc HLE semantics, sampler wrap
modes, texel-boundary rounding, chunked draws, viewport handling. Next
step is auditing the Chowdren asset decode path (custom compressed
images) against the emulator's import surface.

* [linux] ALSA playback backend for sceAudioOut

sceAudioOut ports on Linux now play through libasound instead of the
silent fallback. The PCM device opens in blocking mode with ~170ms of
device buffer (the time-equivalent of the 32KB queue the WinMM and
CoreAudio ports keep), so snd_pcm_writei provides the same backpressure
pacing without a managed queue. Underruns and suspend/resume go through
snd_pcm_recover with one retry per submit; anything else drops the
buffer rather than stalling the guest.

The "default" device routes through PulseAudio/PipeWire on desktops
and straight to hardware on bare ALSA; SHARPEMU_ALSA_DEVICE overrides
it (the null device makes the path testable in containers). A missing
libasound or device fails port creation and lands in the existing
silent fallback.

Verified in an amd64 container: the test game opens the port
(backend=alsa, 48kHz stereo float32) and streams sceAudioOutOutput
through the null device for a full run; without a usable device the
port logs a warning and falls back to silent. Playback on real Linux
audio hardware has not been tested.

* [fixes] Address review feedback: commit bounds, CoreAudio shutdown, dump errors

- HostMemory: a MEM_COMMIT that runs past its reservation now fails like
  Win32 instead of committing a prefix and reporting success. All current
  callers already clamp their ranges to the region, so this only guards
  future callers.

- CoreAudioPort: Dispose wakes a submitter waiting on backpressure and
  the wait treats ObjectDisposedException as a timed-out wait, so closing
  a port during playback can no longer throw. A failed AudioQueueStart
  tears the queue down and fails fast instead of leaving an undrainable
  queue that stalls every later submit on its timeout.

- AgcExports: texture dumping catches all write failures (bad path,
  permissions), logging a warning instead of crashing when
  SHARPEMU_TEXTURE_DUMP_DIR points somewhere unusable.

Verified with the Linux container run: game boots and streams audio with
the stricter commit check, and a dump dir under /proc produces warnings
instead of taking the process down.

* [ci] Build linux-x64 and osx-x64 archives

Adds a build-posix matrix job (ubuntu-latest / macos-latest) mirroring
the Windows build: locked restore, Release build, self-contained CLI
publish, and a tar.gz artifact per RID (tar keeps the executable bit).
The macOS archive also stages the universal MoltenVK dylib via
scripts/fetch-macos-moltenvk.sh so the artifact runs without any manual
Vulkan setup. The release job still only ships the Windows archive.

* [cli] Keep POSIX glfw natives outside the single-file bundle

The KeepGlfwOutsideSingleFile target only matched filenames starting
with 'glfw', which covers Windows (glfw3.dll) but not libglfw.3.dylib /
libglfw.so.3. Those got embedded into the single-file bundle, and
Silk.NET's library loader does not probe the bundle extraction
directory, so a published build died with "Couldn't find a suitable
window platform" (and the glfwInitVulkanLoader wiring, which loads the
library from AppContext.BaseDirectory, could not run either). Keeping
the POSIX names loose next to the executable fixes both, the same way
the Windows build already handled it.

Found by running the CI-built osx-x64 archive: video failed while local
loose-file builds worked. With the fix the published single-file build
opens the MoltenVK window, wires the loader, and reaches gameplay.

* [ci] Publish linux-x64 and osx-x64 release archives

The build-posix artifacts now ship as per-RID GitHub releases on main
pushes and manual dispatches, tagged the same way as the win64 ones
(<rid>-<ref>-<sha>). Archives stay tar.gz so the executable bit
survives extraction.

* [cli] Fail early on non-x86-64 host processes

The CPU backend executes guest x86-64 code natively, so the process
must be x86-64 (win-x64/linux-x64 on x64 hardware, osx-x64 under
Rosetta 2 on Apple Silicon). An arm64 process previously failed deep
inside emulation startup, indistinguishable from MoltenVK, signal
handler, or guest memory problems. CLI mode now checks the process
architecture up front and exits with a message naming the supported
execution model (and the Rosetta install command on macOS). The
GUI-only path stays usable on arm64.

* [video] Log the selected Vulkan device name and API version

The presenter never named the GPU it picked, so a 'no video' report
could not be told apart from a real windowing failure without guessing.
It now logs the device name, type, and API version right after
selection. A software rasterizer (llvmpipe/lavapipe/SwiftShader) shows
up here and typically lacks the device features the translated shaders
need, which is the likely cause when a window opens and presents frames
but nothing draws.

* [video] Steer GLFW to XWayland on Wayland sessions

GLFW's native Wayland backend does not reliably map the Vulkan window
with some drivers (NVIDIA in particular): frames present but the window
never becomes visible, so the game runs with audio and no picture. A
report on an RTX 5080 showed exactly this — all device features present,
frames presenting, but the log had 'libdecor-gtk.so failed to init' and
a 1.4x-scaled window, both Wayland tells.

On a Wayland session that also exposes an X server (DISPLAY set), the
presenter now clears WAYLAND_DISPLAY for its own process before GLFW
initializes, so GLFW selects its dependable X11/XWayland backend.
SHARPEMU_ENABLE_WAYLAND=1 opts back into native Wayland. Headless
(no DISPLAY) and non-Linux hosts are unaffected.

* [video] Force GLFW X11 backend via the platform init hint, log the platform

The previous attempt cleared WAYLAND_DISPLAY to steer GLFW off Wayland,
but a reporter still hit the native-Wayland path (the Wayland-only
libdecor error persisted), so that env trick doesn't switch GLFW.

Use GLFW's supported mechanism instead: glfwInitHint(GLFW_PLATFORM,
GLFW_PLATFORM_X11) before GLFW initializes, called into the same libglfw
GLFW itself loads (the pattern InitializeMacVulkanLoader already uses).
Still gated on a Wayland session with an X server present (DISPLAY set)
so we never force X11 where XWayland can't catch it, and still
overridable with SHARPEMU_ENABLE_WAYLAND=1.

Also logs 'GLFW windowing platform in use: <backend>' after init via
glfwGetPlatform, so a 'no window' report shows X11 vs Wayland outright.
Verified on macOS: the readback correctly reports Cocoa and the
presenter is unaffected (the fix is a no-op off Linux).

* [video] Run the GLFW window on the main thread on Linux too

GLFW requires window creation and event processing on the process main
thread on every platform: initialization, window creation, and
glfwPollEvents are main-thread-only, and X11 in particular has a single
event queue that must be serviced there. A window created and polled on
another thread may never map — which is why the game ran (audio, imports,
even Vulkan present) with no visible window on Linux.

macOS already routed the window loop to the main-thread pump (AppKit
needs it); Windows is fine because it has a per-thread event queue. Linux
was the gap: it spawned a background thread for the presenter. Extend the
existing HostMainThread pattern to Linux — emulation runs on a worker,
the main thread pumps the window work the presenter posts.

Refs GLFW intro guide (thread-safety): init, window creation, and event
processing are restricted to the main thread.

Verified: macOS still boots to its window unchanged; the Linux headless
container runs to millions of imports with no deadlock or regression.
On-screen confirmation on a real Linux desktop is still pending, but this
is the documented root cause for a windowless-but-running Linux session.

* [posix] Skip Win32 native guest workers

* [vulkan] Synchronize offscreen targets before present

* [vulkan] Transition fresh textures from undefined layout

* [vulkan] Report swapchain pixels before source readback

* [vulkan] Emit requested guest image diagnostics

* [agc] Diagnose guest texture fallbacks

* [linux] Keep guest GPU mappings in low address space

* [video] Reduce diagnostic stalls and drain complete frames

* [memory] Harden packed GPU address handling

* [readme] Document Linux and macOS release support

* [posix] Integrate the host platform abstraction

* [posix] Restore guest thread address window

* [video] Run the performance HUD on POSIX hosts

The FPS/CPU/work HUD bailed out unless the host was Windows; only the
per-thread hottest-thread scan actually needs Windows APIs. Keep that
scan Windows-only (POSIX reports 'idle') and let the rest of the HUD
run everywhere — the title is already set from the render thread, which
owns the window on macOS and Linux.

* [posix] Implement native guest worker threads

Guest entry stubs must not run above CLR-managed frames on CLR-created
threads (see the NativeWorker preamble); the PR previously fell back to
the inline calli path on POSIX, which reproduced the documented
'attempted to call a UnmanagedCallersOnly method from managed code'
fail-fast (observed after Dreaming Sarah's menu select) and left the
runtime's suspension machinery walking guest frames.

Provide the missing POSIX half of the worker loop:
- PosixHostStubs grows Win64-convention WaitForSingleObject/SetEvent/
  ExitThread stubs backed by dispatch semaphores (macOS) / unnamed POSIX
  semaphores (Linux) plus pthread_exit, with EINTR retry in the wait.
- Worker events are creatable/signalable/waitable from managed code too,
  so NativeGuestExecutor.Run keeps its handshake (AutoResetEvent stays
  on Windows byte-for-byte).
- PosixHostThreading implements CreateNativeThread/WaitForThreadExit/
  CloseThreadHandle over pthreads (liveness probed with
  pthread_kill(0), then joined).
- RunPrologue/RunEpilogue are routed through the existing Win64->SysV
  thunks, so the emitted loop stays identical across platforms.

* [macos] Disable concurrent GC under Rosetta's write-watch hazard

Background GC's write-watch revisit (SoftwareWriteWatch::GetDirty ->
FlushProcessWriteBuffers) calls thread_get_register_pointer_values on
every thread; under Rosetta 2 that Mach call stalls indefinitely on
threads executing translated guest code. The background mark phase then
never finishes and every allocating or Monitor-taking thread wedges
behind it — observed as Dreaming Sarah freezing at the menu/loading
screen with FPS 0 in 5 of 7 runs, dispatcher/watchdog parked in
Monitor.Enter and all BGC threads waiting in t_join.

Non-concurrent GC never takes that path; a 5-minute soak now holds
22-31 fps in-game with zero stalls. Windows and Linux keep concurrent
GC.

* [diag] Periodic guest-thread snapshots with gate-owner tracking

SHARPEMU_PERIODIC_SNAPSHOT_SECONDS=N dumps the stall snapshot every N
seconds even while imports are progressing, for soft stalls where the
game stops advancing but threads keep spinning. The periodic dump never
touches the guest-thread gate (it must keep reporting when the gate is
what's wedged): it reads a lock-free owner record — every gate
acquisition now goes through LockGate(site), which notes site/thread —
and walks the thread table without the lock, tolerating torn reads.
SHARPEMU_PERIODIC_SNAPSHOT_FILE redirects the dump to a side file for
the case where the console itself is wedged (frozen log mirror was one
of the observed failure modes).

* [nuget] Add osx-x64 RID targets to lock files

* [cpu] Back off the guest join poll

TryJoinThread polled the host thread at a fixed 1ms; a game main thread
joining a long-lived worker (Dreaming Sarah parks there for the whole
session) burned ~5% of managed CPU in Join/Sleep syscalls. Ramp the
poll interval to 10ms once the join is clearly long-lived — exit
detection latency for long joins moves from ~1ms to at most 10ms, and
short-lived joins still resolve on the first 1ms polls.

* [nuget] Add linux-x64/win-x64 RID targets to lock files

* [posix] Keep guest stacks clear of the import-stub descent

The import-stub region descends from 0x7000_0000_0000 on the same 16MB
grid as the guest thread windows; moving stacks to 0x6FFF_E000_0000 put
them inside the stub region's 64-module descent range (floor
0x6FFF_C000_0000), silently consuming the top ~32 stack slots on hosts
with many loaded modules. Drop the POSIX stack base to 0x6FFF_A000_0000:
512MB below the stub floor, still 2.5GB above the TLS window. Windows
keeps 0x7FFF_E000_0000 (its bands are ~15TB apart).

* [pad] Read window gamepads on POSIX hosts

XInput and the DualSense hid reader are Windows-only, which left
macOS/Linux with keyboard input only. The presenter's Silk/GLFW input
context already enumerates gamepads on both platforms, so track their
state in HostWindowInput (event-driven on the window thread, snapshot
guarded like the key set) translated to ORBIS conventions: GLFW's Xbox
layout maps A/B/X/Y to Cross/Circle/Square/Triangle, sticks bias from
-1..1 to 0..255 with Y growing down, and triggers rescale from GLFW's
-1..1 resting-at--1 range with digital L2/R2 bits past 25%.

The merge into ReadHostInputState is gated to non-Windows so a physical
pad is never sampled twice through both a native reader and GLFW.
Hotplug is handled via ConnectionChanged; with no pad connected the
path is inert.

Untested against a physical controller (none attached to the dev host);
axis conventions follow the GLFW gamepad-mapping contract.

* [nuget] Refresh lock files after cross-RID restores

* [posix] Adopt the host audio/input seams from main

Main's #192 abstracted audio output and pad/keyboard input behind
IHostAudioOutput/IHostInput; re-express the POSIX backends behind them:

- CoreAudioPort/AlsaAudioPort move to Host/Posix as
  PosixCoreAudioStream/PosixAlsaAudioStream implementing
  IHostAudioStream. The seam converts to stereo PCM16 before Submit, so
  the ports' own conversion (and IHostAudioPort/AudioSampleConverter)
  is gone; queueing and backpressure are unchanged.
- PosixHostAudio selects CoreAudio (macOS) / ALSA (Linux) as the
  platform's IHostAudioOutput.
- PosixHostInput implements IHostInput over an
  IPosixWindowInputSource that HostWindowInput registers when the
  presenter attaches the window's GLFW input context: keyboard with
  virtual-key translation, the window gamepad snapshot (now in seam
  HostGamepadState/HostGamepadButtons terms), and keyboard-connected as
  the focus signal. Rumble/lightbar no-op (GLFW has no such API).
- PadExports drops its direct HostWindowInput gamepad merge — pads now
  flow through IHostInput.GetGamepadStates like every platform.
- PosixHostThreading.RequestTimerResolution is a documented no-op.

All three RIDs build; SharpEmu.Libs.Tests pass (26/26).

* [nuget] Regenerate GUI lock file for RID-less locked restore

Local cross-RID builds stamped a win-x64 runtimes section into
SharpEmu.GUI's lock file; the project declares no RuntimeIdentifiers,
so CI's 'dotnet restore --locked-mode' failed with NU1004 on every
platform. Regenerated via a plain solution restore (--force-evaluate),
matching what the workflow validates.
linux-x64-main-fa2616d osx-x64-main-fa2616d win64-main-fa2616d
2026-07-15 15:36:20 +03:00
Pacuka
7b86a91dfa Add files via upload (#202)
Added Hungarian tranlation. -Pacuka
win64-main-7b86a91
2026-07-15 14:39:42 +03:00
Gutemberg Ribeiro
72645cb373 [Host] Abstract audio output and pad/keyboard input behind the host platform seam (#192)
* [Host] Abstract audio output behind IHostAudioOutput

Add IHostAudioOutput (opens streams, names the backend for diagnostics)
and IHostAudioStream (submit interleaved stereo 16-bit PCM, Dispose) to
the host seam, with the winmm waveOut implementation moving whole into
Host/Windows/WindowsWaveOutAudio — same device open, queueing,
32 KB backpressure wait, and buffer lifetime as WinMmAudioPort had. The
DllImports become source-generated LibraryImports in the move, matching
the other Windows backends.

The guest-format conversion (mono/stereo/7.1, s16/float32 -> stereo
PCM16) is platform policy, not device code, so it stays in Libs as
AudioPcmConversion; AudioOutOutput converts into a pooled buffer and
submits the result through the stream. Open failures still degrade to
the silent paced port with the same warning, and the port log line now
takes its backend name from the platform instead of a hardcoded string.

* [Host] Abstract pad and keyboard input behind IHostInput

Add IHostInput to the host seam: gamepad state snapshots, rumble /
trigger-rumble / lightbar sinks, and the keyboard-fallback queries
(window focus, key state). Gamepad state crosses the seam as the new
unmanaged HostGamepadState with HostGamepadButtons flags — named after
the PlayStation layout the guest API exposes but with the seam's own
values, so SCE_PAD_BUTTON bits never leak into host backends and the
per-frame poll can stackalloc its snapshot buffer.

The DualSense raw-HID reader, the XInput reader, and the Win32 HID
interop move whole into Host/Windows (report parsing, hot-plug loops,
rumble/lightbar output reports, and log strings unchanged), translating
to the neutral flags instead of ORBIS bits and converting their
DllImports to source-generated LibraryImports. WindowsHostInput
composes them plus the user32 keyboard queries; rumble still fans out
to both readers, trigger rumble stays XInput-only, lightbar stays
DualSense-only.

PadExports keeps all policy: the keyboard mapping (now via named
OrbisPadButton constants instead of raw hex), the controller-beats-
keyboard-past-deadzone merge, and the new host->ORBIS button
translation. The GUI's source-linked reader copies re-point to the
moved files (it still cannot reference SharpEmu.HLE wholesale), which
requires AllowUnsafeBlocks for the generated marshalling stubs; its
navigation code switches to the neutral flags.

* [Host] Move the timer-resolution request behind IHostThreading

IHostThreading gains RequestTimerResolution (idempotent, best-effort
~1 ms timed-wait granularity; a no-op wherever the platform default is
already fine). The winmm timeBeginPeriod call, its once-only latch, and
both warning strings move from the Libs-level HostTimerResolution
helper into WindowsHostThreading as a source-generated LibraryImport;
the vblank pump requests it through the platform instead.

HostSystemInfo in SharpEmu.Logging keeps its direct user32/kernel32
imports deliberately: Logging sits below HLE in the dependency chain so
it cannot see the host seam, every path is already OS-gated with
fallbacks, and it only runs once for the diagnostics banner.
2026-07-15 13:24:53 +03:00
SamuelEzequias
2ad9836d13 Add Brazilian translation to Environment tab (#196) win64-main-2ad9836 2026-07-15 13:00:30 +03:00
Gutemberg Ribeiro
62e1775c5c [HLE] Remove steady-state allocations from the hot HLE paths (#190)
* [HLE] Stop allocating on the memcpy/memset and trace hot paths

memcpy/memmove no longer allocate a bounce buffer sized to the whole
copy (large copies previously landed on the LOH); they loop through a
single pooled 256 KB rental, copying high-to-low when the destination
overlaps above the source so memmove semantics survive the chunking.
memset reuses a shared zero chunk for the dominant zero-fill case and
rents/fills only min(length, 16K) bytes for non-zero values instead of
allocating and filling a fresh 16 KB array per call; the map-time
zero-fill loop shares the same zero chunk.

SHARPEMU_LOG_SEMA / SHARPEMU_LOG_VIDEOOUT are now read once into cached
bools and every TraceSemaphore/TraceVideoOut call site is guarded, so
trace messages are no longer interpolated (and the env var no longer
queried) on every semaphore op and every flip with tracing off. Trace
output when the flags are set is unchanged.

* [HLE] Remove per-frame allocations from the vblank/flip/equeue plumbing

The 60 Hz vblank pump no longer allocates per edge: PumpVblanks reuses a
pump-thread-only port list instead of a LINQ Where/ToArray, and
SignalVblank/SubmitFlip snapshot their event registrations into pooled
rentals instead of copying the List on every edge and every flip (the
snapshot must still be taken, since triggers run outside _stateGate and
a per-port reusable buffer would race the pump thread against a guest
thread's first-edge signal).

sceKernelWaitEqueue delivery rents the dequeue buffer from the pool
instead of allocating an array per wait, and event-queue wake keys are
formatted once per handle (cached in a ConcurrentDictionary, dropped on
queue delete) instead of building the string on every enqueue. The
semaphore wake key moves onto KernelSemaphoreState at creation, the
same pattern the pthread mutex state already uses, removing the
per-signal/per-wait formatting. SHARPEMU_LOG_EQUEUE is read once into a
cached bool like the sema/videoout flags.

* [HLE] Read guest C-strings without per-call buffer allocations

CpuContext.TryReadNullTerminatedUtf8 allocated a byte[capacity] and
issued one TryRead per byte for every string-argument import. It now
reads through a stack buffer (pooled above 512 bytes) in 128-byte bulk
chunks, falling back to per-byte reads only when a chunk touches an
unreadable range so a terminator sitting just before unmapped memory
still resolves exactly as before. The chunk bound also keeps the
overread past the terminator smaller than the old loop's worst case is
wide, so no fault can appear where the byte loop succeeded.

TryReadAsciiZ (dlsym/symbol resolution) drops its List<byte> + ToArray
round-trip for the same stack/pooled buffer, keeping the byte-by-byte
TryReadByteCompat reads because their Marshal.ReadByte fallback must
probe exactly up to the terminator. Only the final string is allocated
on either path now.

* [HLE] Replace blocking-wait closures with waiter continuation objects

Every wait that actually parked a guest thread allocated two capturing
lambdas (plus their display classes) for the scheduler's resume/wake
callbacks. RequestCurrentThreadBlock and the backend's blocked-thread
state now carry a single IGuestThreadBlockWaiter instead of the
Func<int>/Func<bool> pair: TryWake keeps the run-under-the-scheduler-
gate contract and Resume still produces the guest's RAX on the woken
thread. The waiter stays attached through the wake transition (the old
code nulled only the wake handler there) and is consumed at resume.

The existing waiter objects absorb the captured state as fields, so a
blocking wait now allocates exactly one object: SemaphoreWaiter,
PthreadMutexWaiter, and EventFlagWaiter implement the interface
directly, and the equeue, cond, and rwlock waits get small waiter
classes replacing their closures. Handler bodies delegate to the same
static methods with the same arguments as before; the untimed event
flag wait's mutable captured result becomes a field on its waiter.

* [HLE] Back pending event queues with a ring deque instead of LinkedList

LinkedList<KernelQueuedEvent> allocated a node object on every
non-coalesced enqueue — one per vblank/flip edge per registered queue,
60+ times a second in steady state. KernelEventDeque is a grow-only
ring buffer over a KernelQueuedEvent[] with the three operations the
queue actually uses (AddLast, RemoveFirst, find-and-update-in-place by
ident/filter), so steady-state enqueue/dequeue allocates nothing and
the coalescing update writes the struct back through an indexer instead
of a node reference. All accesses stay under _eventQueueGate, matching
the LinkedList usage it replaces.

* [HLE] Cap memcpy chunk iterations at the requested size, not the rented length

Address Copilot review: ArrayPool.Rent may return a larger array than
requested, so sizing each iteration by chunk.Length let the copy
granularity depend on pool bucketing internals instead of the intended
256 KB chunking. Behavior was already correct for any chunk size (each
iteration re-reads the source, and the overlap ordering is size-
independent), but the loop now mins against the requested chunkLength,
matching what memset already does.

* [HLE] Skip the flip/vblank snapshot rental when no events are registered

Address Copilot review: SignalVblank and SubmitFlip rented (and
returned) a pooled snapshot even with zero registrations — steady
per-frame pool traffic for games that never register flip events and
only poll flip status. Zero-count signals now skip the rental, the
copy, and the trigger loop entirely, which also retires the
Math.Max(count, 1) minimum-rent guard.
win64-main-62e1775
2026-07-15 12:57:40 +03:00
SamuelEzequias
6dacd59a08 [GUI] Add Portuguese (Portugal) translation (#197)
* [GUI] Add Portuguese (Portugal) translation

* [GUI] Add Portuguese (Portugal) translation
2026-07-15 12:56:25 +03:00
Spooks
9d88542efd Fix virtual memory allocation and access (#193)
* Fix virtual memory allocation and access

* Update test dependency lock file
win64-main-9d88542
2026-07-14 21:50:54 -06:00
StealUrKill
373100a6b0 Add 21 missing SysAbi exports and GUI Environment tab for SHARPEMU_* toggles (#189)
Fills NID gaps hit by PS5 titles during boot, controller setup, and
rendering, and surfaces the common runtime switches in the GUI. All
exports are additive (no behavior change to existing exports) and free of
NID and export-name collisions with upstream.

New export libraries:
- libSceBluetoothHid: Init/RegisterDevice/RegisterCallback success stubs so
  titles proceed past Bluetooth controller setup (opt-out via
  SHARPEMU_BTHID_UNAVAILABLE=1).
- libSceNpCppWebApi: Common::initialize no-op success; UE5 online titles
  abort PS5-component startup on a negative SCE error.

Additions to existing libraries:
- libScePad: scePadOpenExt (shared PadOpenCore, accepts special ports 1/2 and
  the ScePadOpenExtParam pointer), scePadClose, scePadGetExtControllerInformation.
- libSceVideoOut: sceVideoOutConfigureOutput, sceVideoOutInitializeOutputOptions.
- libSceAgc: DCB builders sceAgcDcbSetIndexCount, sceAgcDcbJump, DcbSetPredication,
  SetPacketPredication (emit valid skippable packets; full draw processing TODO).
- libSceAmpr: measure and write KernelEventQueueOnCompletion pair.
- libKernel: scePthreadGet/Setschedparam, sceKernelChmod (validate and accept;
  POSIX permission bits have no host equivalent on Windows).
- libSceNetCtl: sceNetCtlRegisterCallbackV6 (delegates to the v4 callback).
- libSceMouse: sceMouseInit.
- libSceUserService: sceUserServiceGetAgeLevel (adult, skips parental gates).

GUI: new Options Environment tab exposing common SHARPEMU_* switches as
toggles (BTHID_UNAVAILABLE, DISABLE_IMPORT_LOOP_GUARD, VK_VALIDATION,
DUMP_SPIRV, LOG_DIRECT_MEMORY, LOG_NP). Persisted in gui-settings.json and
applied to the emulator process environment at launch; localized with
English fallback.
win64-main-373100a
2026-07-15 03:36:15 +03:00
Gutemberg Ribeiro
f23161be9a Host platform abstraction layer for the execution engine (#181)
* [Host] Introduce host platform abstraction with IHostMemory

Add SharpEmu.HLE/Host with IHostPlatform/IHostMemory interfaces, neutral
page-protection/region enums, and a HostPlatform.Current factory that
resolves the Windows backend (or throws PlatformNotSupportedException on
other OSes, matching today's de-facto behavior). WindowsHostMemory wraps
the exact VirtualAlloc/VirtualFree/VirtualProtect/VirtualQuery calls used
across the engine today, with identical MEM_*/PAGE_* constants.

Migrate StubManager as the first consumer: its private kernel32 P/Invokes
and enums are replaced by IHostMemory calls that issue the same two
native operations (RWX commit+reserve of the PLT arena, release on
Dispose). No behavior change.

This is the first step toward supporting non-Windows hosts; subsequent
commits move the remaining direct P/Invokes in Core and Libs behind the
same seam.

* [Host] Route PhysicalVirtualMemory through IHostMemory

Replace the class's private VirtualAlloc/VirtualFree/VirtualProtect/
VirtualQuery P/Invokes with IHostMemory calls. Every site maps 1:1 onto
the exact native call it issued before: MEM_COMMIT|MEM_RESERVE ->
Allocate, MEM_RESERVE -> Reserve, fault-path commits -> Commit, and
MEM_RELEASE -> Free, with identical protection values produced by the
Windows backend.

IHostMemory gains ProtectRaw so the save/restore protection sequences in
TryWriteExclusive and TryTemporarilyProtectForRead round-trip the raw OS
protection word (including modifier bits the neutral enum cannot
represent) exactly as before. Raw PAGE_* constants remain only for the
internal region-classification helpers, which only ever see values this
class itself assigned.

The exact-address free-on-mismatch, lazy reserve-only threshold, prime
loop, and all trace strings are unchanged.

* [Host] Add IGuestAddressSpace and retire the reflection-based allocator lookup

Introduce IGuestAddressSpace in SharpEmu.HLE (fixed-address AllocateAt /
TryAllocateAtOrAbove and guest mprotect via TryProtect) with signatures
copied from PhysicalVirtualMemory, which now implements it. TryProtect
reproduces the read/write/execute decomposition that
KernelMemoryCompatExports.ResolveHostProtection performs, yielding the
same PAGE_* values through the Windows backend.

KernelVirtualRangeAllocator previously located AllocateAt via cached
MethodInfo reflection (because SharpEmu.Libs cannot see Core types) and
walked wrapper memories through an untyped 'Inner' property. Both are
now typed: ICpuMemoryWrapper exposes the decorated memory (implemented
by TrackedCpuMemory, whose Inner property already existed) and the
allocator type-tests for IGuestAddressSpace with the same bounded
unwrap depth. Failure paths keep the exact [LOADER][TRACE] strings.

* [Host] Move Kernel HLE memory exports off direct kernel32 P/Invokes

KernelMemoryCompatExports loses its private VirtualQuery/VirtualProtect/
VirtualAlloc/VirtualFree declarations and MemoryBasicInformation struct:

- Guest mprotect (sceKernelMprotect/sceKernelMtypeprotect) now routes
  through IGuestAddressSpace.TryProtect resolved from ctx.Memory. The
  orbis read/write/execute decomposition moves into a GuestPageProtection
  conversion whose mapping is value-identical to the removed
  ResolveHostProtection.
- The guarded libc heap and host-page accessibility checks go through
  IHostMemory (same commit+reserve/protect/free sequence; guard-page and
  protection-mask checks compare HostRegionInfo.RawProtection against the
  same PAGE_* literals as before).
- HostMemory is exposed as a property so merely loading the type never
  resolves the platform backend on non-Windows hosts.

KernelRuntimeCompatExports' RDTSC stub allocates its 16-byte RWX page via
IHostMemory.Allocate; the OperatingSystem.IsWindows() gate returning null
is unchanged.

* [Host] Abstract thread, TLS, and symbol primitives in the execution backend

Add IHostThreading (native TLS slots, current-thread id, affinity, raw
thread create/join, diagnostic register capture) and IHostSymbolResolver
(enum-keyed host function addresses baked into emitted stubs), with
Windows implementations wrapping the exact kernel32 calls the backend
made directly before.

DirectExecutionBackend takes an optional IHostPlatform (defaulting to
HostPlatform.Current) and routes every TlsAlloc/TlsFree/TlsSet/GetValue,
GetCurrentThreadId, SetThreadAffinityMask, GetModuleHandle/GetProcAddress
and the suspend+GetThreadContext diagnostic snapshot through it. The
snapshot moves wholesale into WindowsHostThreading (including the Win64
CONTEXT size/flags/offsets, which are Windows-specific by nature) and
returns a neutral HostCapturedRegisters.

NativeGuestExecutor resolves WaitForSingleObject/SetEvent/ExitThread via
the symbol resolver — the same addresses end up in the emitted run loop,
so stub bytes are unchanged — and creates/joins its raw worker thread
through IHostThreading with the same stack-reservation semantics. The
run-loop emitter itself does not move.

Marshal.GetLastWin32Error() in the affinity-failure log still observes
SetThreadAffinityMask's error because the wrapper makes no intervening
SetLastError call.

* [Host] Move fault handling and remaining backend memory ops behind the seam

Add IHostFaultHandling (handler-thunk creation, first-chance handler
install/remove, unhandled-filter set) with WindowsFaultHandling in a new
Cpu/Native/Windows/ folder. The exception-handler trampoline emitter
moves there whole — same pre-filtered NTSTATUS codes, same TEB gs:[8]/
gs:[0x10] stack-limit reads, same host-RSP TLS switch — parameterized
only by (managed callback, TLS slot, TlsGetValue address), which is
exactly what SetupExceptionHandler passed it before. Handler
installation order, the AddVectoredExceptionHandler(first=1) flag, the
SHARPEMU_DISABLE_RAW_HANDLER gate, and all install/teardown log strings
are unchanged.

Every remaining VirtualAlloc/VirtualProtect/VirtualFree/VirtualQuery/
FlushInstructionCache in the backend partials routes through IHostMemory
with 1:1 call mapping (RWX emit -> RX downgrade -> flush for stub
emission, reserve/commit for the PRT aperture and lazy-commit fault
path, raw-protection round-trips via ProtectRaw). HostRegionInfo gains
RawState/RawAllocationProtection so the lazy-commit trace lines and
protection-mask checks keep printing and comparing the exact native
values.

Windows semantics leaked as bare literals become named constants with
identical values: NTSTATUS codes (WindowsFaultCodes) and Win64 CONTEXT
byte offsets (Win64ContextOffsets, with the existing CTX_* constants
aliased to it and handler-local numeric offsets replaced by the names).

* [Host] Resolve the host platform explicitly at the composition root

SharpEmuRuntime.CreateDefault() now resolves HostPlatform.Current once
and passes it explicitly to PhysicalVirtualMemory and (via a new
optional CpuDispatcher parameter) to DirectExecutionBackend, replacing
the implicit default-argument fallbacks. On unsupported OSes boot now
fails at the root with PlatformNotSupportedException and a clear
message instead of on the first native call. A future Linux/macOS
backend plugs in by returning a different IHostPlatform here.

* [Host] Convert the platform backends to source-generated P/Invokes

Replace [DllImport] with [LibraryImport] in the four Windows backend
files added by this branch (WindowsHostMemory, WindowsHostThreading,
WindowsHostSymbolResolver, WindowsFaultHandling). Marshalling stubs are
now generated at compile time instead of JIT-emitted at runtime, which
fits the pre-JIT-everything boot model and keeps the backends
NativeAOT/trimming ready.

Interop stays zero-copy: all signatures are blittable, GetModuleHandleW
now pins the managed string via Utf16 marshalling instead of copying,
and GetProcAddress names marshal through a stack-allocated Utf8 buffer.
Implicit contracts become explicit where LibraryImport requires it:
TlsFree/TlsSetValue gain [MarshalAs(UnmanagedType.Bool)] (the 4-byte
Win32 BOOL DllImport assumed silently), and GetModuleHandle targets the
W entry point directly since LibraryImport never probes suffixes.

The CONTEXT snapshot buffer stays a NativeMemory allocation rather than
stackalloc: CONTEXT requires 16-byte alignment, now documented at the
call site. Native call sequences are unchanged.

* [Host] Address Copilot review: harden failure paths, honor injected platform

- Free the handler thunk page when the RX protection downgrade fails
  (the leak predates this branch, but the failure path is boot-fatal so
  releasing the page is unobservable).
- TraceThreadMode and the static diagnostics helpers now resolve host
  primitives through the backend bound to the current thread, falling
  back to HostPlatform.Current only when no run is active (identical on
  supported configs, honors injection everywhere a backend exists).
- HostPlatform.Create additionally requires an x64 process so native
  Windows ARM64 fails with the promised PlatformNotSupportedException
  instead of emitting x86-64 stubs into an ARM64 process.
win64-main-f23161b
2026-07-15 03:15:36 +03:00
Dafenx
081760be3f [AGC/Vulkan] Support multiple render targets (#149)
* [AGC] Support multiple typed pixel outputs

Emit dense float, uint, and sint fragment outputs for sparse guest MRT slots. Preserve disabled components across partial exports, validate dense host locations, and retain the single-output compiler overload for compatibility.

* [Vulkan] Execute translated draws with multiple color attachments

Carry every active color target and its effective shader/register write mask through one Vulkan draw. Add per-attachment blending, independentBlend negotiation, device/format validation, multi-attachment synchronization, and safe image recreation after in-flight work completes.

* [ShaderDump] Add MRT edge-case coverage

Cover sparse mixed-type outputs, partial exports, merged partial exports, independent blend layouts, eight attachments, and invalid host locations. Run the synthetic shader suite in CI.

---------

Co-authored-by: Dafenx <196083014+Dafenxz0@users.noreply.github.com>
win64-main-081760b
2026-07-15 02:41:39 +03:00
José Luis Caravaca Carretero
e604fb606d Fix pak size-collision that crashed Quake right after the intro demo (#187)
* [Tests] Add SharpEmu.Libs.Tests project

Introduce an xunit project for the HLE libs with a minimal ICpuMemory fake,
so library-level exports and helpers can be exercised without a live guest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [Ampr] Disambiguate pak size-collisions by read locality

PakDirectoryTracker resolves a sequential AMPR read (offset -1) back to an
absolute pak offset by matching the requested byte count against the PACK
directory. When several files share that byte count it took the first
unconsumed match in directory order, which mis-resolves out-of-order reads:
progs/h_ogre.mdl and bots/navigation/death32c.nav are both 0x3A34 bytes, and
death32c.nav sits earlier in the directory and is never read during Quake's
intro demo, so requesting h_ogre.mdl returned the nav file's bytes. The engine
then parsed "NAV2" as a brush model, failed the version check and aborted.

Pick the unconsumed same-size entry nearest the running read cursor instead.
id archives cluster related assets and the guest streams them with locality,
so this lands on the intended file; contiguous same-size runs (the
gfx/weapons/ww_*.lmp icons) still resolve in packed order.

Verified against a Quake dump: the abort is gone, h_ogre.mdl reads correctly,
and the intro demo reaches its main loop and renders instead of dying at the
error dialog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
win64-main-e604fb6
2026-07-15 01:49:41 +03:00
José Luis Caravaca Carretero
df53ff59d9 [Json] Implement sce::Json::Value and String (construct / set / destroy) (#169)
* [Json] Implement sce::Json::Value and Json::String construct/set/destroy

libSceJson previously only had the Initializer/MemAllocator setup path.
The Value and String classes themselves were entirely absent, so a
Prospero title that builds a JSON tree (Quake PPSA01880 does, to shape
a web-API request) hit unresolved imports and faulted on the call. The
imports it left unresolved right before its access violation are exactly
these Value ctors/setters and String ctor/dtor.

Model the Value/String payload host-side (JsonObjectHeap), keyed by the
guest `this` pointer, following the handle-shadow pattern already used
by Ngs2Exports. The guest object bytes are deliberately not written:
these objects are usually stack-allocated with an unknown real layout,
and writing a guessed layout risks smashing an adjacent stack canary
(the same hazard the AudioOut2 context-param note in this tree records).
Constructors and setters follow the Itanium ABI and return `this` in rax,
which is correct whether the real setter returns void or Value&.

Covered NIDs (complete-object C1/D1 variants, matching the observed
imports): Value(default/bool/long/ulong/double/ValueType/char*/String),
Value::~Value, Value::set(bool/long/ulong/double/ValueType/char*/String),
Value::clear, String(char*/default/copy), String::~String.

Only the payload the guest can reach through library methods is modelled;
direct guest reads of the object bytes are out of scope and would need
observed layout evidence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [Tests] Add SharpEmu.Libs.Tests covering the Json Value/String exports

First test project for SharpEmu.Libs (xunit), the SharpEmu.Libs.Tests
layout the maintainer already agreed to in issue #36.

- A FakeCpuMemory (single contiguous region) drives the exports at the
  CpuContext level with no live guest.
- Direct-call tests: ctor/setter round-trips for bool/int/uint/double
  (read from xmm0)/char*/String/ValueType, destructor cleanup, and the
  graceful-degradation paths (missing String shadow and a faulting char*
  pointer both fall back to the empty string instead of throwing).
- Registration test: a real ModuleManager scans SharpEmu.Libs and the
  nine NIDs Quake left unresolved now resolve to the libSceJson exports
  and dispatch cleanly (returns `this` in rax).

InternalsVisibleTo exposes JsonObjectHeap to the test assembly. The test
project's packages.lock.json is committed for CI locked-mode restore;
CI does not run tests yet, left as a maintainer decision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [Json] Add Initializer::setGlobalNullAccessCallback

Quake calls it during kexPSNWebAPI::Initialize and treats the
not-found error as fatal for the whole Np Web API bring-up. Store the
guest hook (never invoked by this HLE: shadows degrade to defaults
instead of dereferencing missing members) and return success.

Verified against the dump: the "setGlobalNullAccessCallback failed
(0x80020002)" line is gone and kexPSNWebAPI::Initialize now logs
"Np Web API Initialized"; the next blockers are sceNpAuthCreateRequest
and sceUserServiceInitialize ordering, outside libSceJson.

Also pins both Json test classes to one xunit collection: they share
JsonObjectHeap statics and parallel class execution raced ResetForTests
against a running test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 01:49:33 +03:00
Randomuser8219
4c35831cb8 Add code -1073741819 as an emulation error (#188)
There's currently games that crash on this code due to emulation errors, so it'd make sense to add this error code as an emulation error.
2026-07-15 01:47:44 +03:00
miles
90fdd20f9a Create nl.json (#186) win64-main-90fdd20 2026-07-15 01:36:38 +03:00
Mike Saito
ae5ef0abe7 Add SaveData transaction and NP UDS layout HLE stubs (#168)
* Add SaveData transaction and NP UDS layout HLE stubs

Wire Prepare, Commit, and Umount2 for implicit save transactions,
unregister guest mounts on Umount2, and add NP UDS CreateEvent,
DestroyEvent, and EventPropertyObjectSetString for layout-load imports.

* Add NP UDS SetArray and PostEvent layout HLE stubs

Add sceNpUniversalDataSystemEventPropertyObjectSetArray and
sceNpUniversalDataSystemPostEvent for layout-load imports on PPSA02929.
2026-07-15 01:34:59 +03:00
Mike Saito
5e2c21edf1 Fix historic SysAbi exports bound to wrong symbol names (#167)
Move KMcEa+rHsIo from libKernel MapMemory mislabel to sceAvPlayerAddSource.
Align WV1GwM32NgY ExportName with sceNpWebApi2PushEventCreateHandle. Behavior unchanged.
2026-07-15 01:34:27 +03:00
Deeptanshu Lal
3fb9d4db1c [Tools] Fix ShaderDump reflection invoke against new optional parameters (#166)
TryCompileVertexShader gained an optional scalarRegisterBufferIndex
parameter (#156), and reflection Invoke does not apply C# default
parameter values, so ShaderDump crashed with
TargetParameterCountException. Pad trailing optional parameters with
Type.Missing under BindingFlags.OptionalParamBinding so the declared
defaults are used; only a new required parameter now needs a tool
update, and that fails with a named error instead of a crash.

Verified: all five programs behave as expected (exit 0), all eight
emitted blobs pass spirv-val --target-env vulkan1.3.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 01:34:08 +03:00
José Luis Caravaca Carretero
de13735972 [CommonDialog] Fix dialog state machine and add MsgDialog progress-bar exports (#163)
Rework the sceMsgDialog and sceSaveDataDialog HLE state machines so the full
Initialize -> Open -> poll -> GetResult -> Close/Terminate lifecycle honors the
common-dialog contract, and add the three missing sceMsgDialogProgressBar* exports.

- Fix an unreachable close path: sceSaveDataDialogClose already did a
  RUNNING -> FINISHED compare-exchange, but Open jumped straight to FINISHED, so
  RUNNING never existed and Close could only return NOT_RUNNING. Open now enters
  RUNNING and the first status poll advances it to FINISHED. Same model applied to
  sceMsgDialog.
- Return the real SCE_COMMON_DIALOG_ERROR_* codes (0x80B8xxxx) from sceMsgDialog*
  instead of emulator-internal result codes, with the missing argument/state guards
  (ARG_NULL, NOT_INITIALIZED, BUSY, NOT_FINISHED, NOT_RUNNING).
- GetResult reports buttonId = 1 (affirmative) instead of 0, the invalid sentinel a
  yes/no prompt could mis-branch on.
- Add sceMsgDialogProgressBarSetValue, sceMsgDialogProgressBarInc and
  sceMsgDialogProgressBarSetMsg (NIDs wTpfglkmv34, Gc5k1qcK4fs, 6H-71OdrpXM), gated
  on the service being initialized.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 01:33:48 +03:00
AlexC
fc0efca297 Fixed deutch language file, it had invalid syntax (#162) 2026-07-15 01:31:54 +03:00
anesr5
2a9a261913 loader: support ps5 SELF and validate ELF signatures (#157)
Co-authored-by: anes <anesrachedi@outlook.fr>
2026-07-15 01:31:16 +03:00
Mike Saito
290f5fd3d7 Add SysAbi ExportName name2nid check script (#152)
* Add SysAbi ExportName name2nid check script

* Make SysAbi ExportName check green on tip with catalog skips and one Np rename
2026-07-15 01:29:23 +03:00
tensorcrush
c06c70cad7 [Aerolib] Add ulobjmgr and NpEAAccess symbol names (#150)
Resolves _sceUlobjmgrRegisterObject (BG26hBGiNlw) and
_sceUlobjmgrUnregisterObject (Smf+fUNblPc), reported as unresolved by
testers, plus four sceNpEAAccess exports. Names taken from shadPS4's
NID tables and each verified by recomputing the NID with the repo's
name2nid derivation before inclusion. aerolib.bin regenerated with
scripts/generate_aerolib_binary.py.

Co-authored-by: tensorcrush <tensorcrush@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 01:28:17 +03:00
Deeptanshu Lal
5e54250752 [Tools] Add GPU conformance executor for dumped shader blobs (#127)
SharpEmu.Tools.GpuConformance executes the exec-cs.spv blob produced by
SharpEmu.Tools.ShaderDump on a real Vulkan device (preferring a discrete
GPU) and compares every word of the 64-byte storage buffer against
CPU-computed expectations, bit for bit. Creating the compute pipeline
doubles as a driver-acceptance check for SharpEmu's emitted SPIR-V.

The checks cover the three ALU results, the store attempted with EXEC=0
(its destination must keep the sentinel), the store after EXEC is
restored, and all trailing sentinel words. Any mismatch counts toward the
failure total and makes the tool exit non-zero.

Verified on an RTX 3060 Laptop GPU (NVIDIA) with all values matching, and
the failure path verified to exit 1 by running a non-storing blob.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 01:26:18 +03:00
AlexC
be6a6a5935 [GUI] New box "About" in configuration (#165)
* Added about tab with github and discord

* Added discord & github svgs and svg support

* Changed svg to pngs and localization text in english & spanish
win64-main-be6a6a5
2026-07-15 00:59:13 +03:00
Mike Saito
caf859cc52 Fix guest shutdown when VideoOut window is closed (#184)
Propagate Silk window close to runtime teardown so audio and CPU workers stop instead of continuing after the presentation window is dismissed.
win64-main-caf859c
2026-07-15 00:52:01 +03:00
brbrhuehue-matrix
d2f3511002 Add Brazilian Portuguese translation (#153) win64-main-d2f3511 2026-07-15 00:42:44 +03:00
Nolan
90a5d5176f Add Korean (ko-KR) localization (#154) 2026-07-15 00:42:31 +03:00
Nolan
28a43e09c7 Add Japanese (ja) localization (#160) 2026-07-15 00:42:17 +03:00
AlexC
093cfa1f3e Fallback to english if it doesnt find the string in current language (#161) 2026-07-15 00:42:10 +03:00
Spooks
d8397b022e Performance Improvements and Optimization Tweaks (#156)
* Improve Gen5 rendering performance and compatibility

* Pin .NET SDK for locked restore

---------

Co-authored-by: Spooks4576 <Spooks4576@users.noreply.github.com>
win64-main-d8397b0
2026-07-14 20:22:52 +03:00
anesr5
85cc2b9892 added french support (#147)
Co-authored-by: anes <anesrachedi@outlook.fr>
win64-main-85cc2b9
2026-07-14 18:06:57 +03:00
AlexC
293194c40b [GUI] Add Spanish language (#148)
* Added localization to spanish language

* Changed Options.Strict.Desc because i didnt like the way i localized it first
2026-07-14 18:06:48 +03:00
tensorcrush
1f09de8896 [AGC] Complete gfx10 v_cmpx_f32 decode and emit ordered/unordered float compares (#122)
* [AGC] Complete gfx10 v_cmpx_f32 decode and emit ordered/unordered float compares

Add the missing v_cmpx_*_f32 VOPC decode entries (0x17-0x1C, 0x1F) and
emission for the ordered/unordered predicates: nlg maps to OpFUnordEqual,
while o/u are lowered from OpIsNan (unordered = isnan(a) || isnan(b),
ordered = !unordered) because SPIR-V's OpOrdered/OpUnordered require the
Kernel capability and are invalid in Vulkan shader modules.

Opcode numbers cross-checked against LLVM's llvm-mc regression tests
(llvm/test/MC/AMDGPU/gfx10_asm_vopc.s, gfx10_asm_vopcx.s); emitted
lowering validated with spirv-val --target-env vulkan1.1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AGC] Write VCC only for non-X vector compares

On gfx10 the VCmpx encodings have no sdst and define EXEC only, so the
unconditional VCC store clobbered VCC on every VCmpx. Move the VCC store
to the non-X path; EXEC keeps the existing old-EXEC & condition update.

Addresses review feedback on #122.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: tensorcrush <tensorcrush@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 18:05:33 +03:00
Dafenx
ddc452b4fc [Pad] Approximate trigger vibration on XInput (#140)
Co-authored-by: Dafenx <196083014+Dafenxz0@users.noreply.github.com>
win64-main-ddc452b
2026-07-14 18:02:20 +03:00
Dafenx
61a97baf85 [AGC] Emit Gen5 v_sad_u32 (#138)
Co-authored-by: Dafenx <196083014+Dafenxz0@users.noreply.github.com>
win64-main-61a97ba
2026-07-14 17:11:11 +03:00
Mike Saito
e80f96ecf5 Align SysAbi export names with Aerolib NID catalog (#137) 2026-07-14 17:10:57 +03:00
Dafenx
d49c0f1f10 Emit Gen5 packed-integer and bit-count ops (#135)
Co-authored-by: Dafenx <196083014+Dafenxz0@users.noreply.github.com>
2026-07-14 17:10:40 +03:00
Dafenx
1d33ef90fc Harden param.json metadata parsing (#134)
Co-authored-by: Dafenx <196083014+Dafenxz0@users.noreply.github.com>
2026-07-14 17:10:32 +03:00
j92580498-max
26a570633c [HLE] Add strchr/strrchr/memchr/strcat/strncat/strstr libc exports (#132)
Implement six missing libc string/memory search and concatenation
routines in the kernel compat layer. Titles frequently call these
during startup string handling (path parsing, config lookups, format
string assembly), and without them the loader currently falls through
to unresolved-import handling.

The implementations follow the existing byte-at-a-time compat helpers
(TryReadCompat/TryWriteCompat) already used by strcpy/strncpy/memcmp,
matching native semantics: strchr/strrchr scan through and including
the terminator, memchr is bounded strictly by count, strcat/strncat
overwrite the destination terminator and re-terminate, and strstr
returns the haystack pointer for an empty needle. NIDs are the
libSceLibcInternal/libc symbol hashes for each name.
2026-07-14 17:10:00 +03:00
Deeptanshu Lal
e4f89445b9 [Tools] Add synthetic shader dump tool for the Gen5 translator (#111)
SharpEmu.Tools.ShaderDump feeds hand-assembled Gen5 (gfx10) instruction
words — cross-checked against LLVM's AMDGPU target definitions — through
the real Gen5ShaderTranslator -> Gen5SpirvTranslator pipeline via
reflection (no emulator source changes; the project is not in the main
solution) and dumps the resulting vertex/compute SPIR-V blobs for
inspection with spirv-val / spirv-dis.

Each bundled program carries an expectation: fmac/muls/sopp-hints/exec
must decode and emit both stages, while sopp-mode (s_round_mode,
s_denorm_mode) pins the loud unknown-sopp decode failure those FP MODE
writes must keep producing until their semantics are modeled (#108). Any
unexpected outcome makes the tool exit non-zero, so it can gate scripts
or CI.

The exec program computes real ALU results and stores them with
buffer_store_dword, toggling EXEC off and on around a pair of stores; its
exec-cs.spv blob is designed for numeric verification on a real Vulkan
device (follow-up tool).

All dumped blobs pass spirv-val --target-env vulkan1.3.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 17:09:11 +03:00
Dawid Imbrzykowski
1254cc1564 added german language (#136) win64-main-1254cc1 2026-07-14 17:06:04 +03:00
Hayyan
503b3f4d6b [GUI] Add Arabic language (#142)
* [GUI] Add Arabic language

* [GUI] Add Arabic language

* [GUI] Add Arabic language
2026-07-14 17:05:53 +03:00
Greenz
6b37ab54f2 [GUI] Add Danish language (#143) 2026-07-14 17:05:47 +03:00
Berk
a84d2344fb Deadcell fix (#144)
* [agc] add resource registration

* [libc] use C locale for printf
win64-main-a84d234
2026-07-14 17:01:16 +03:00
Spooks
787d3a1efb Fix Gen5 boot and restore stable AGC rendering (#139)
Co-authored-by: Spooks4576 <Spooks4576@users.noreply.github.com>
win64-main-787d3a1
2026-07-14 16:13:42 +03:00
Kushida
884584da67 fix: restore WaitSema loop guard boundary (#133) win64-main-884584d 2026-07-14 15:07:25 +03:00
Spooks
d43edc865a Agent/fix gen5 thread agc compat (#130)
* Fix Gen5 thread and AGC compatibility

* Trim compatibility comments

* Report selected Vulkan GPU

* Clean up CPU title label

* Improve emulator frame pacing and performance

* Regenerate package locks with pinned SDK

---------

Co-authored-by: Spooks4576 <Spooks4576@users.noreply.github.com>
win64-main-d43edc8
2026-07-14 14:41:42 +03:00
Berk
cf6964710a [emulator] Improve emulator performance by optimizing memory access and reducing unnecessary overhead in kernel and CPU execution paths (#131) win64-main-cf69647 2026-07-14 14:28:44 +03:00