Working Linux Build - #45
Open
BMagnu wants to merge 1 commit into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is a fork with most fixes and enhancements from master working on linux.
Closes #4, closes #6, closes #12
I've tested this as far as the end of Mission 1 so far, with everything working as expected so far.
Note: Heavily vibecoded, so some of the code is going to be less pretty than it should be.
First, a list of the features I have not yet ported, with no immediate plan due to the amount of effort needed to make them work on linux:
Also, I did NOT test this on windows. Someone with a windows machine should definitely give this a run before anyone thinks about merging this.
Now, an AI-gen'd list of changes for the main build system and OS-dependent code:
1. Build the Vulkan backend and the AC6 backend fixes on Linux
ac6_widescreen.cppandac6_fullres_effects.cppwere Win32-only —__try/__except,VirtualQuery/VirtualProtect,_byteswap_*,CreateThread. The platform-independent logic(the camera signature predicate, the aspect poke, the static-default patch) is now factored out and
shared, with only the memory-probing primitives behind the platform split.
CMakeLists.txtmoves the texture-swap cvar definitions intosrc/ac6_texture_swap_cvars.cppsothey exist on every platform while
ac6_texture_overrides.cpp— which needs the D3D12/DXGI formatenums — stays Windows-only, and declares the
rexsystem→rexgraphicslink back-edge that aVulkan-only build needs (on Windows the symbol resolves by accident through an earlier reference).
2. Guest-memory probing that cannot fault
Guest memory is an shm mapping, so a page can be readable and still unbacked: a plain dereference
raises SIGBUS, for which no handler is installed. Every probing read now goes through
process_vm_readv, which performs the access in the kernel and reportsEFAULTinstead ofsignalling. Writes stay direct (after a probing read validates the mapping) so a write to a
GPU-write-watched page still faults into the SDK's recovery handler and is tracked like any other
guest write.
The widescreen memory sweep has no
VirtualQueryto walk regions with, and parsing/proc/self/mapsper sweep is far too slow, so it strides the guest range in 1 MiB chunks readthrough the same call — an unmapped range simply fails — and scans the copy, taking writability
from the SDK's own page tracking.
3.
QueryProtectprotection shadowQueryProtectanswered by parsing/proc/self/maps: a file open plus a ~1000-line scan. The MMIOhandler calls it on every access violation, while holding the global critical region, and guest
write-watches fault constantly — this measured at 39% of total process CPU with the guest making
no forward progress at all. Windows has no equivalent problem,
VirtualQuerybeing a cheap syscall.AllocFixed,ProtectandDeallocFixedare the only writers of this process's own protections,so they now record them in a coalesced, page-aligned range map answered in O(1). A lookup that
misses still falls back to the maps file, so addresses this module never handed out (host
allocations, the stack) answer exactly as before.
4. NT event semantics for POSIX waits
The Win32 dispatcher hands an auto-reset release to one specific waiter, in FIFO order. Publishing a
flag that every waiter races for is not equivalent in two ways that both strand the guest: two
releases delivered before any waiter runs collapse into one, and no waiter is guaranteed to ever
win. Signallers now grant a token to the longest-waiting thread instead
(
posix_event_fifo_handoff, on by default — off restores the old flag behaviour so a suspectedregression can be A/B'd without a rebuild).
Alongside it: a
log_long_waits_mswatchdog that names the stuck thread, the object and the waitkind from a fixed-address table readable out of a core or a live process, and a
sync_testsbinarycovering the primitives (kept separate from
unit_testsso it needs onlyrexcore). The watchdoglogs at error level, because
ac6_performance_modepins the log level to error and that is exactlythe configuration a hang gets reproduced in.
5. POSIX file handles
O_RDONLY/O_WRONLY/O_RDWRare an enumeration in the low two bits (0/1/2), not bit flags, sothey cannot be OR-ed together: a read+write open OR-ed to
O_WRONLYand every read on thatdescriptor then failed with
EBADF. The title opens its save read+write, so the reads silentlyreturned nothing and it wrote back whatever its buffer already held. Windows has no equivalent
problem —
GENERIC_READ|GENERIC_WRITEreally are bit flags.Also:
pread/pwritereturning-1was assigned straight into asize_tout-parameter, reportingSIZE_MAXbytes transferred to any caller that checked the count rather than the bool.6. Crash and fault diagnostics
An unclaimed
SIGSEGVreturned from the handler, which re-executes the faulting instruction, whichfaults again — the thread spins inside the signal handler forever, burning a core, with no
diagnostic anywhere. A guest null dereference therefore appeared as an unexplained "deadlock"
instead of a crash. It is now reported (translated back to a guest address), handed to a
guest-aware reporter, and then allowed to die under
SIG_DFL, producing a core that points at thereal faulting instruction.
diag_crash_handler.cppgains its POSIX half. Rather than installing a competingSIGSEGVhandler —
ExceptionHandlerowns that signal — it registers as that reporter and prints what ahost backtrace cannot give: the guest call chain walked from the saved stack backchain, all 32
GPRs, and the memory behind every register that looks like a live guest pointer. It runs on a
thread that is about to die, so it takes no locks and probes every read first.
7. Guest sockets no longer block forever
XNet is not implemented, so nothing can ever deliver to a guest socket and a blocking
recvon onewaits forever — the title does not get past its network init.
guest_socket_recv_timeout_us(2 ms default) bounds the wait. This is a stopgap: it makes a socket the guest expects to block
return
EAGAINinstead, and should be removed if real Xbox 360 socket semantics land.8. We were injecting RenderDoc into ourselves
RenderDocAPI::CreateIfConnected()is meant to hand back the API only when RenderDoc has alreadyattached. On POSIX it probed with a plain
dlopen("librenderdoc.so", RTLD_LAZY), which loadsthe library when it is not already mapped.
librenderdoc.solives in a system library directory onany machine with RenderDoc installed, so every run pulled RenderDoc in, initialised it, and got its
capture overlay drawn over the game — and, through
IsGpuDebugMarkersEnabled()'s "auto-enable whenRenderDoc is detected" path, silently turned
gpu_debug_markerson as well.Windows has the same shape but not the same outcome:
renderdoc.dllis not on the default DLLsearch path unless RenderDoc genuinely injected it. The probe now uses a new
DynamicLibrary::LoadIfAlreadyLoaded, which isRTLD_NOLOADon POSIX and unchangedLoadLibraryWon Windows, so it observes RenderDoc rather than causing it. Launching underRenderDoc still works exactly as before.
Vulkan backend
9. AC6's backend hooks were D3D12-only
A whole family of AC6 fixes was wired into the D3D12 command processor only. On Vulkan they
reported themselves enabled in the log and did nothing. Ported:
it
AreTimingHooksActive()fails closed forever and the entire FPS-unlock / physics-dt family issilently dead.
that produced the 2×2 ghost planes).
(radar window, PiP inset) viewport and scissor shrink about the render-target centre, target
marker quad narrowing, and the mode-classified swap-source notification that decides fill vs.
letterbox presentation.
fetch-constant patch.
buffer residency when the guest rewrites its trail history ring in place, and the forced RT0
colour mask for the trail point-list pass (whose register state writes no colour components, so
the draw was dropped entirely).
10. Texture result exponent bias read from the wrong fetch-constant word
The SPIR-V translator took the result exponent bias from bits 13:18 of fetch constant word 4.
Those bits fall inside
lod_bias(dword_4 +12, 10 bits); the actualexp_adjustfield is atdword_3 +13. A non-zero LOD bias was therefore applied as an exponent bias, scaling every fetchedtexel by a power of two — for AC6 by 2⁻⁸, crushing the composite to black. The DXBC translator
reads word 3, which is why this only ever affected Vulkan.
11.
execute_unclipped_draw_vs_on_cpurestored to its upstream defaultWith it off, unclipped draws get no vertex extent estimate, so
height_usedfalls back to the fullrender-target height. A single depth-only unclipped draw then claims all 2048 EDRAM tiles and takes
ownership of every range, and anything resolving from those tiles afterwards inherits depth data
instead of colour.
12. Diagnostics on the silent failure paths
Several Vulkan paths returned
falseorcontinued without a word, leaving only a black result towork back from. Once-only error logs added for: a failed texture upload, a texture with no load
shader or a null load pipeline, a transfer with no buildable pipeline, and a render target that
would transfer to itself.
VulkanPipelineCachealso honoursdump_shadersnow — only the D3D12pipeline cache dumped its translated binary, so the setting produced no SPIR-V at all.
Audio
13. SDL output sized to the real device period
The driver hardcoded a queue target of 3 render-driver frames while SDL opens a 1024-frame period
at 48 kHz — four 256-sample guest frames. The host therefore asked for more audio than the runtime
was ever allowed to queue, and the shortfall was filled with silence on every callback.
The period is now queried from the device and the queue target derived from it, with two frames of
headroom for worker wake jitter (the POSIX multi-handle wait is a 1 ms poll, not a real blocking
wait). Supporting changes:
with a thread inside the allocator; on exhaustion the stalest queued frame is recycled rather
than blocking, and the drop is counted in telemetry.
wrote — the guest render-driver tic is derived entirely from
ReportSamplesConsumedForClient,so over-reporting ran the guest's audio clock ahead of real playback.
audio_max_queue_depthraised to 16, and a clamp below what the driver needs now warns insteadof silently guaranteeing an underrun.
so a starved host cannot wedge startup indefinitely.
AudioTraceBuffer::Recordearly-outs when tracing is off. It is reached from the host audiocallback on every consumed frame, and took a mutex and churned a deque there for data nobody
reads.
Input
14. Sticks the kernel binds to
hid-genericwere silently invisibleThis one is technically an enhancement because I'm not sure how windows handles joystick to gamepad for rexglue.
A Thrustmaster T.Flight Hotas One (USB
044f:b68d, the AC7-branded flight stick) produced no input at all and, worse, produced no log line either — it was indistinguishable from a device that was never plugged in. Five things hadto line up for that:
The kernel's
xpadtable has no entry forb68d, so the stick binds tohid-generic. That driverdescribes it with joystick-class button codes (
BTN_TRIGGER…) rather than the gamepad-classcodes (
BTN_GAMEPAD/BTN_SOUTH…) anxpaddevice would report. SDL3's Linux backend willsynthesise a gamepad mapping for an unknown device, but
LINUX_JoystickGetGamepadMappinggives upimmediately on
!has_key[BTN_GAMEPAD]— "not a gamepad according to the specs". Neither SDL'sbuilt-in mapping table nor the upstream community
SDL_GameControllerDBhas an entry for this GUID.And
SDLInputDriverreaches devices exclusively through the SDL gamepad API: it inited onlySDL_INIT_GAMEPADand listened only forSDL_EVENT_GAMEPAD_ADDED, which an unmapped joystick neverraises. There is no
SDL_Joystickfallback anywhere in the runtime.Three changes, only the first of which is device-specific:
gamecontrollerdb.txtnow ships at the repo root and is copied next to the executable on everybuild, carrying a hand-authored entry for this stick. Its throttle is split across both triggers,
its yaw twist across the shoulder buttons, and its Trim control is an axis rather than a pair of
buttons, so it drives
dpup/dpdownas half-axis bindings. The coolie hat is the right stick,which deliberately leaves
dpleft/dprightunbound — AC6 does not need them, at the cost ofUpdateXCapabilitiesreportingX_INPUT_CAPS_NO_NAVIGATION.hid_mappings_fileresolves against the executable directory first, falling back to the CWD.Previously it was a bare relative name resolved against the CWD alone, and since no such file
shipped, that path only ever logged
file '...' does not exist.Mappings also load beforeSDL_INIT_GAMEPADnow, so the initial device-added burst is already mapping-aware.SDL_INIT_JOYSTICKis requested alongsideSDL_INIT_GAMEPADpurely so unmapped sticks stillraise
SDL_EVENT_JOYSTICK_ADDED. A new handler warns with the device name, VID/PID, and the GUIDa database line has to be keyed on. The next unrecognised device is a log line rather than a
hardware investigation.
Incidentally fixed in the same file:
OnControllerDeviceAdded/Removedreadevent.cdevice.which,the camera device union member, where they meant
event.gdevice.which. The two structs arelayout-identical so it worked, but only by accident.
Some more fixes were also incorporated and inspired from a different seemingly WIP fork of this repo targeting linux
A second, independent Linux port exists at
The four fixes in this section are that fork's work, ported here — the analysis and
the original implementations are theirs.
15. The >1x mosaic, and arming the scaling fixes for
resolution_scale("Fix the >1x mosaic on Vulkan, and arm the scaling fixes for resolution_scale").
param_gen_integer_guest_positionandparam_gen_host_subpixel_restoreexisted as cvars and wereread by the DXBC translator, but nothing on the SPIR-V side read them — so the scaling fixes
were inert on Vulkan and AC6's deferred EDRAM restore / de-swizzle passes scrambled into a mosaic
at any draw scale above 1x. Two halves, both mirroring the DXBC path:
StartFragmentShaderInMainnow floors the reverted PsParamGen position to the integer guest-pixelindex. Reverting the resolution scale leaves a sub-guest-pixel fraction that is only correct for
shaders feeding PsParamGen straight to a
tfetch; shaders doing integer pixel-address maths on itsee a multiplied period in their
frac()-based bit extraction and scramble the sample coordinate.ProcessTextureFetchInstructionre-adds the host sub-pixel after the coordinate is normalized, sothose passes regain full host resolution instead of sampling at guest resolution. It runs before
the de-swizzle identity override, which overwrites the coordinate outright — the same precedence
the DXBC translator uses.
Separately,
ApplyAc6FixDefaultsgated the whole family ondraw_resolution_scale_x/y, but thecombined
resolution_scalecvar — what the settings menu writes — leaves those at 1. It now asksTextureCache::GetConfigDrawResolutionScalefor the effective scale, so the fixes engage foreveryone who scaled that way; the effective scale is also logged on the config support line.
16. A POSIX read at end of file must fail
("Report a POSIX read at end of file as a failure, like the Win32 handle").
Win32
ReadFilereportsERROR_HANDLE_EOF, whichHostPathFileturns intoX_STATUS_END_OF_FILE.preadjust returns 0, which reached the guest as "success, zero bytes,position unchanged" — a loader reading a file to its end then never terminates. A short or
zero-length write is likewise a failure now, so the atomic-write path cannot commit a truncated
temp over a good file. (This sits on top of our own fix to the same function, item 5.)
17.
getCompTexLDcube face id and negated Z("Produce the cube face id and negated Z whenever they are used").
Two component-mask bugs in the SPIR-V translator's cube coordinate lowering. Negated Z feeds both
the X-major
sc(component 1) and the Y-majortc(component 0), but was only created forcomponent 1; and the Y-major face
idwas computed inside thetcguard, so it went unset wheneverthe id was wanted without the coordinate. Wrong cube-map sampling on Vulkan for any shader
requesting only some components.
18. Stencil-bit transfers killed no samples
("Kill stencil-bit transfer samples whose source bit is clear").
A stencil-bit transfer from a depth/stencil source binds only the stencil texture — the depth is
not needed and deliberately not bound — so neither packing branch ran and
packedwas left unset.The sample kill is skipped entirely when
packedisNoResult, so every sample kept its bit andthe destination stencil came out
0xFFeverywhere regardless of the source.