mirror of
https://github.com/ayozetr/bloodborne_linux.git
synced 2026-11-08 17:03:48 -10:00
Compare commits
111
Commits
main
...
0.51-linux.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5c2611f72 | ||
|
|
73f3bb512a | ||
|
|
5e86d0bde7 | ||
|
|
30b3409674 | ||
|
|
b3c8b1dadd | ||
|
|
697af8936f | ||
|
|
576a214b5f | ||
|
|
8d1db30f9e | ||
|
|
16a08695c3 | ||
|
|
b4d74c88f7 | ||
|
|
bb3a9d126d | ||
|
|
3d2b8b3067 | ||
|
|
186a342750 | ||
|
|
2bcc093bd9 | ||
|
|
8937a721ca | ||
|
|
4f3187843f | ||
|
|
da5561d6c7 | ||
|
|
f9186dbb8d | ||
|
|
1b93552dca | ||
|
|
d7a94e5dfc | ||
|
|
e2b3f73e71 | ||
|
|
1ce06532c2 | ||
|
|
49a37b4459 | ||
|
|
25cdef7482 | ||
|
|
9413c82bfb | ||
|
|
f4a12043f6 | ||
|
|
7e02e06139 | ||
|
|
7135aa9288 | ||
|
|
48de7625b5 | ||
|
|
1ca5a39b3a | ||
|
|
34ca9d1340 | ||
|
|
b18055557b | ||
|
|
ebb79d3774 | ||
|
|
479e964d90 | ||
|
|
fb25a78b5d | ||
|
|
5bf966b85a | ||
|
|
e7f5b9f575 | ||
|
|
51927fb018 | ||
|
|
c5c983c248 | ||
|
|
a80e1ed7d5 | ||
|
|
a07910eb89 | ||
|
|
2e4a55f8bb | ||
|
|
f1d897ea0d | ||
|
|
9779cbbc70 | ||
|
|
e36717374e | ||
|
|
542e9ada19 | ||
|
|
9e629d2efd | ||
|
|
74584d57b5 | ||
|
|
40b67fc784 | ||
|
|
82923859db | ||
|
|
2ea5b61641 | ||
|
|
0ca95492c4 | ||
|
|
4fd797f7e9 | ||
|
|
1ebe35c2e8 | ||
|
|
17e7741934 | ||
|
|
97a60dd497 | ||
|
|
a3a8a3af6e | ||
|
|
f1091cc0b1 | ||
|
|
c496667284 | ||
|
|
a8b7a8eaa1 | ||
|
|
4d934b7529 | ||
|
|
442c2b66bd | ||
|
|
d7a42fde94 | ||
|
|
d0318b1ab5 | ||
|
|
1bc4f37504 | ||
|
|
44a6b7c3ce | ||
|
|
efdf7b176e | ||
|
|
4f7df3de5c | ||
|
|
e3c51c48aa | ||
|
|
33393185b9 | ||
|
|
c294942db4 | ||
|
|
6c6974a742 | ||
|
|
83246710d7 | ||
|
|
b71eb7cfee | ||
|
|
b6c25e1fb4 | ||
|
|
0e2b626358 | ||
|
|
b060b9fa2b | ||
|
|
18a7dd7e39 | ||
|
|
28a9233034 | ||
|
|
9c056c437f | ||
|
|
2683273a17 | ||
|
|
4d297b96bb | ||
|
|
917c4bdd20 | ||
|
|
8e24cfdcf5 | ||
|
|
cfede387ed | ||
|
|
f44390da19 | ||
|
|
c89f4d432c | ||
|
|
4fd1cd6ee6 | ||
|
|
cfe7af67a5 | ||
|
|
124384eac1 | ||
|
|
5227432b1b | ||
|
|
81db68f552 | ||
|
|
f723f6a972 | ||
|
|
82a96e0d25 | ||
|
|
71717863d5 | ||
|
|
c4281227da | ||
|
|
ee20c8b147 | ||
|
|
a39a0073de | ||
|
|
09a1e4981a | ||
|
|
992fc95ed1 | ||
|
|
851712537b | ||
|
|
e289b722b5 | ||
|
|
c7cae2a585 | ||
|
|
7d53c5e9d6 | ||
|
|
7dfbf49367 | ||
|
|
52a6daf288 | ||
|
|
912641c298 | ||
|
|
dca76e386f | ||
|
|
0b465f8ee1 | ||
|
|
82782d8155 | ||
|
|
89ea3deb70 |
No files matched your search
@@ -0,0 +1 @@
|
||||
ko_fi: ayozetr
|
||||
@@ -0,0 +1,84 @@
|
||||
name: Bug report
|
||||
description: The game or the launcher does something wrong, crashes or does not start.
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for reporting. Before you start, [docs/TROUBLESHOOTING.md](https://github.com/ayozetr/bloodborne_linux/blob/main/docs/TROUBLESHOOTING.md) covers the common problems.
|
||||
|
||||
Only your own dump of the game with the 1.09 update is supported. Do not ask for or link game files.
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: The launcher's **About** page shows it.
|
||||
placeholder: 0.51-linux.2
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: install
|
||||
attributes:
|
||||
label: How you run it
|
||||
options:
|
||||
- AppImage
|
||||
- From source (run.sh / the launcher in a checkout)
|
||||
- Steam Deck (AppImage)
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: system
|
||||
attributes:
|
||||
label: Distribution, GPU and driver
|
||||
placeholder: CachyOS, RTX 3060, NVIDIA 580.95 / Fedora 43, RX 7800 XT, Mesa 25.2
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: graphics
|
||||
attributes:
|
||||
label: Upscaler, preset and output resolution
|
||||
description: From the launcher's **Settings** page or the in-game menu (Insert / L3+R3).
|
||||
placeholder: FSR 3.1, Quality, 2560x1440
|
||||
- type: textarea
|
||||
id: what
|
||||
attributes:
|
||||
label: What happened
|
||||
description: What you saw, and what you expected instead. Screenshots or a short video help.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: How to make it happen
|
||||
description: Where in the game, and what you did right before. Say if it happens every time.
|
||||
placeholder: |
|
||||
1. Load the save at the Cathedral Ward lamp
|
||||
2. Walk towards Old Yharnam
|
||||
3. The game closes during the load
|
||||
- type: dropdown
|
||||
id: online
|
||||
attributes:
|
||||
label: Online
|
||||
options:
|
||||
- Offline
|
||||
- Online, shadPS4's public server
|
||||
- Online, my own server
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: Log
|
||||
description: |
|
||||
In the launcher's **Log** page, **Export log…** and drag the file here (or **Copy** and paste it).
|
||||
Do not paste your shadNet password or your trophy key anywhere in the issue.
|
||||
placeholder: Drag the exported log here.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: Checks
|
||||
options:
|
||||
- label: I use my own dump of Bloodborne with the 1.09 update.
|
||||
required: true
|
||||
- label: I searched the existing issues for the same problem.
|
||||
required: true
|
||||
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Troubleshooting
|
||||
url: https://github.com/ayozetr/bloodborne_linux/blob/main/docs/TROUBLESHOOTING.md
|
||||
about: The common problems and what to do, before opening an issue.
|
||||
- name: Online play and your own server
|
||||
url: https://github.com/ayozetr/bloodborne_linux/blob/main/docs/ONLINE.md
|
||||
about: Accounts, the connection test, summons and the private server guide.
|
||||
@@ -0,0 +1,21 @@
|
||||
name: Feature request
|
||||
description: Something the port or the launcher could do.
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: textarea
|
||||
id: what
|
||||
attributes:
|
||||
label: What you would like
|
||||
description: What it should do, and when you would use it.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: why
|
||||
attributes:
|
||||
label: Why
|
||||
description: What it solves for you, or what you do today instead.
|
||||
- type: textarea
|
||||
id: elsewhere
|
||||
attributes:
|
||||
label: Seen elsewhere
|
||||
description: Another port, fork or mod that already has it (a link helps a lot).
|
||||
@@ -23,6 +23,8 @@ perf.data
|
||||
/bloodborne_upscalers_*.md
|
||||
mods.json
|
||||
patches.json
|
||||
# The mods folder of runs from source (the launcher's default, DATA_DIR/mods)
|
||||
/mods/
|
||||
# Local: game logs, messages and archives from users, internal plans
|
||||
/logs/
|
||||
/message*.txt
|
||||
@@ -30,3 +32,10 @@ patches.json
|
||||
/docs/ISSUE_ACTION_PLAN_*.md
|
||||
# AMD FidelityFX DLLs (FSR 4.1.1 is built from the user's own): never in the repository
|
||||
*.dll
|
||||
# Game files and dumps: never in the repository
|
||||
eboot.bin
|
||||
param.sfo
|
||||
*.pkg
|
||||
*.dcx
|
||||
*.7z
|
||||
/CUSA*/
|
||||
@@ -1,6 +1,8 @@
|
||||
[submodule "gpu/third_party/fsr-vulkan"]
|
||||
path = gpu/third_party/fsr-vulkan
|
||||
url = https://github.com/FireBurn/FSR-Vulkan.git
|
||||
# build.sh applies gpu/patches/fsr-vulkan to its working tree
|
||||
ignore = dirty
|
||||
[submodule "gpu/third_party/imgui"]
|
||||
path = gpu/third_party/imgui
|
||||
url = https://github.com/ocornut/imgui.git
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
THIS PROJECT IS NOT RELATED TO SHADPS4. ALL QUESTIONS RELATED TO THIS PROJECT SHOULD BE SENT TO THE DISCORD SERVER https://discord.gg/KYZRKk9CB, NOT TO THE SHADPS4 SERVER.
|
||||
|
||||
|
||||
# bbport — a native Linux port of Bloodborne
|
||||
|
||||
> This is the README of the original project (deadinside28/bloodborne_pc), kept for its
|
||||
> technical details. The fork's own README is [README.md](README.md).
|
||||
|
||||
**English** · [Русский](BBPORT.ru.md)
|
||||
|
||||
bbport is the counterpart of Wine + DXVK for a single game: *Bloodborne* for PlayStation 4
|
||||
(CUSA03173, game version 1.09) on an x86-64 Linux PC. The game's original executable runs
|
||||
directly on the PC:
|
||||
|
||||
- **as in Wine**, the game's x86-64 code runs on the CPU directly, and a runtime written for
|
||||
this one game replaces the PS4 system libraries;
|
||||
- **as in DXVK**, the game's graphics are translated to Vulkan — by a renderer derived from
|
||||
[shadPS4](https://github.com/shadps4-emu/shadPS4) and heavily extended for this game, including
|
||||
temporal upscaling with AMD FSR 3.1, FSR 4 and FSR 4.1.1;
|
||||
- memory and the GPU's work are moving to the PC model: the launcher has an experimental
|
||||
*New memory and translation model* mode; without it the game runs on the old
|
||||
memory model, as in 0.3.
|
||||
|
||||
One step remains to the full Wine + DXVK model: make the new mode the default and remove the old
|
||||
memory model (see below).
|
||||
|
||||
> **No game files are included.** You need your own dump of Bloodborne with update 1.09: any retail
|
||||
> release (CUSA00900 in the US, CUSA03173 for the European Game of the Year edition, ...), as they
|
||||
> share the same executable.
|
||||
> This project is not affiliated with Sony Interactive Entertainment, FromSoftware or AMD.
|
||||
|
||||
**Status: experimental, playable.** The game boots, loads saves and plays (the Hunter's Dream
|
||||
and several areas of Yharnam were played with it) with sound, gamepad and saving.
|
||||
A full play-through has not been verified, and only one machine (Linux, AMD Radeon RX 7800 XT,
|
||||
Mesa/RADV) has been tested thoroughly.
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Native execution.** The eboot is converted offline into a flat memory image; PS4 libc and
|
||||
libSceFios2 are linked into it as native code. No CPU emulation and no per-instruction
|
||||
translation: the game code runs at full speed.
|
||||
- **Two modes** (launcher → *Mode*):
|
||||
- **Switch off (the default) — as in 0.3.** The old memory model: VRAM copies of the game's
|
||||
memory, writes tracked through page protection. With every fix made since 0.3 (motion
|
||||
vectors and the upscaler, flicker with DoF on, a damaged shader cache).
|
||||
- **New memory and translation model — experimental.** The game's memory lives
|
||||
in system RAM and the GPU reads it where it is, as a PC game's buffers; data it reads often
|
||||
is kept in VRAM and given back when unused (textures after 20 s, buffers after 60 s). The
|
||||
layer's memory module tracks CPU writes to mirrored blocks to keep those copies coherent;
|
||||
it does not copy the whole GPU-visible memory. The PS4 command processor's work
|
||||
is translated into Vulkan commands rather than emulated on the CPU: memory writes
|
||||
(`WRITE_DATA`, DMA) are done by the GPU in command-stream order, and fences are written by the
|
||||
GPU itself at the end of the pipeline, at the same point of the stream as the PS4's command
|
||||
processor writes them. **It may crash**; hardware testing has been performed on an
|
||||
RX 7800 XT, and performance depends on the scene and settings. On AMD the game's memory is kept
|
||||
in a sparse buffer (the arena). **On NVIDIA, Intel and others** it goes through the layer's
|
||||
memory module: the game's memory is bound in place and VRAM copies are separate buffers, with
|
||||
no sparse rebinding. This avoids the sparse-binding path associated with reported freezes
|
||||
on a GTX 1660 Ti. On NVIDIA the module runs on a tester's GTX 1660 Ti, its speed is still
|
||||
being checked; not tested on Intel. Without the launcher: `BB_PC_MODEL=1`; to choose the memory
|
||||
path: `BB_GUEST_MEMORY=host|dmabuf`; the memory module: `BB_LAYER_MEMORY=1/0`.
|
||||
|
||||
[0.5-pre4 changes](docs/CHANGES_0.5-pre4.md): no stutter of the memory module (NVIDIA) while areas
|
||||
stream in, shader compilation in the background, VRAM on 4–6 GB cards, damaged game extractions
|
||||
reported, the settings menu as in 0.3. [0.5-pre3](docs/CHANGES_0.5-pre3.md): the memory module on
|
||||
NVIDIA — VRAM copies, one VRAM budget with the texture cache, watched blocks. [0.5-pre2](docs/CHANGES_0.5-pre2.md): the memory
|
||||
module, native GPU copy shader, portable GPU completion labels, and shader-cache migration.
|
||||
|
||||
Unused textures are freed in both modes, so VRAM no longer grows with every area visited.
|
||||
- **Unlocked frame rate.** Community patches (`patches/Bloodborne.xml`) make the simulation
|
||||
use the real frame time; ~90 FPS at 4K with FSR 4 Balanced on an RX 7800 XT, ~150 FPS at
|
||||
1440p with FSR 4 Quality. Also 30/60/90 FPS modes.
|
||||
- **Temporal upscaling built for this game.** Bloodborne has no velocity buffer, so bbport
|
||||
computes motion vectors itself: camera motion from depth and the scene matrices, and object
|
||||
motion (characters, cloth, weapons) from the vertex positions of the previous frame. The
|
||||
scene is jittered sub-pixel (Halton) and rendered at a reduced resolution; the upscaler fills
|
||||
the output (720p for the Steam Deck, 1080p, 1440p or 2160p) and the UI is drawn natively at the output resolution.
|
||||
- **FSR 3.1** (FireBurn/FSR-Vulkan).
|
||||
- **FSR 4 (INT8, model v07)** on GPUs exposing the required Vulkan shader features —
|
||||
RDNA2/3 included (see Requirements).
|
||||
- **FSR 4.1.1**: AMD's 4.1.1 DLL is recorded once under vkd3d-proton and its passes are
|
||||
replayed natively on Vulkan; the output is **bit-exact** with the DLL. Two variants, as in
|
||||
the DLL: INT8 on any GPU with the required shader features, and FP8 matrices on RDNA4 (RX
|
||||
9000; picked automatically). The assets are built on your machine from one DLL of your own
|
||||
(4.1.x): the launcher's *FSR 4.1.1 from your own AMD DLL → Choose DLL…* button (2–5 minutes,
|
||||
twice that on RDNA4; needs a recent Proton), or `tools/fsr4cap`.
|
||||
- Faster than AMD's own shaders on RDNA3: the final passes of FSR 4 and 4.1.1 were rewritten
|
||||
to store through workgroup memory (3.5× and 2.3× faster, bit-exact); FSR 4 costs ~4 ms at
|
||||
4K on an RX 7800 XT instead of ~6 ms.
|
||||
- **Multi-threaded GPU command processing.** The PS4 command stream is decoded on one thread
|
||||
and draws are bound and recorded on another (two-stage pipeline), with a Vulkan recording
|
||||
thread and helper threads for memory copies. Early on the single GPU thread capped the game
|
||||
at ~26 FPS; now it runs at 90–150 FPS depending on resolution and scene.
|
||||
- **Settings inside the game's own menu:** *System → Display / Game effects / Game patches*
|
||||
(after *Screen/Sound*), built and drawn by the game itself — output resolution, upscaler,
|
||||
preset, sharpness, FPS counter, model detail and the effect patches, in the game's language.
|
||||
- **Overlay menu** (Insert or L3+R3), styled like the game's dialogs: everything above plus the
|
||||
advanced settings, with gamepad, keyboard and mouse.
|
||||
- **GTK4 launcher** and an **AppImage** for the Steam Deck.
|
||||
|
||||
## How it differs from shadPS4
|
||||
|
||||
| | shadPS4 | bbport |
|
||||
|---|---|---|
|
||||
| Scope | General PS4 emulator, many games | One game: Bloodborne v1.09 |
|
||||
| Loading | Its own ELF loader and kernel emulation at run time | The eboot is converted offline (`scripts/`) into an image with PS4 libc/Fios2 linked in; a C loader maps it and jumps into the game (loader and runtime: ~5k lines) |
|
||||
| Memory | The GPU's view of PS4 memory is kept in VRAM copies, synchronized through page-protection write tracking | By default the same model (as in 0.3); in the *New memory and translation model* mode: the game's memory in system RAM, used by the GPU in place, frequently read data in VRAM, freed when unused |
|
||||
| Command processor | Emulated: memory writes, DMA and fences are done by the CPU while decoding | By default the same; in the new mode translated into Vulkan commands that the GPU runs in stream order, fences written after the work has really finished |
|
||||
| System libraries | Broad HLE of the PS4 OS | A small runtime (`src/runtime_*.c`) that implements exactly what Bloodborne calls: memory, threads, sync, files, audio (incl. ATRAC9), pad, saves, AppContent |
|
||||
| GPU | shadPS4 video core and shader recompiler | The same core (vendored, GPL) with ~200 marked changes (`bbport:`) plus new modules: two-stage draw pipeline, render-state and texture-set memoization, render-scale proxies, motion vectors, FSR 3.1/4/4.1.1, frame capture and GPU profiler |
|
||||
| GPU thread | One thread processes the whole command stream (the bottleneck in Bloodborne) | Decode and draw recording run on separate threads; the work scales with the hardware threads (Steam Deck included) |
|
||||
| Upscaling | — | Temporal (FSR 3.1, FSR 4, FSR 4.1.1) with the game's own motion vectors and jitter |
|
||||
| Game patches | Patch files applied by the emulator | The same community patches, compiled at start (`scripts/patches.py`); render resolution, effects and FPS from the launcher |
|
||||
|
||||
Without shadPS4 there would be no bbport: its renderer and shader recompiler are the base of
|
||||
the graphics side.
|
||||
|
||||
### Wine + DXVK for one game
|
||||
|
||||
- **CPU.** The PS4 CPU is x86-64, so the game's code runs directly on the PC's CPU, with no
|
||||
emulation and no instruction translation.
|
||||
- **System libraries.** As Wine replaces the Windows API, the bbport runtime (`src/runtime_*.c`)
|
||||
implements exactly the PS4 OS functions Bloodborne calls: memory, threads, files, audio, pad,
|
||||
saves.
|
||||
- **Memory.** In the new mode, as in a PC game: the game's data in system RAM, VRAM used the way
|
||||
a PC game uses it. The mode is still experimental.
|
||||
- **Graphics.** The PS4 GPU's command stream (PM4) is a recording of the game's graphics API
|
||||
calls: it is written by 99 functions of the statically linked libGnm, so decoding the stream
|
||||
and translating the calls themselves come to the same thing. GCN shaders are translated to
|
||||
SPIR-V. In the new mode the command processor's work (memory writes, DMA, fences) is
|
||||
translated into Vulkan commands as well; fences are written by the GPU itself. Resource
|
||||
descriptors (textures, buffers) are read by the translator on the CPU, as DXVK reads resource
|
||||
bindings: a PS4 texture has to be converted from the PS4 tiling into a Vulkan image before the
|
||||
draw runs, so the CPU must know its descriptor beforehand — translation, not emulation. The
|
||||
descriptor tables (the constant engine's dumps) are read only by the translator, so the CPU
|
||||
performs them. The game's occlusion queries (~60 a frame) are
|
||||
Vulkan occlusion queries; predication, `COPY_DATA` and `COND_EXEC` are translated too (check:
|
||||
`BB_PM4_SELFTEST=1`). Details:
|
||||
[docs/EMULATION_REMOVAL_PLAN.ru.md](docs/EMULATION_REMOVAL_PLAN.ru.md), "Шаг 4" (Russian).
|
||||
|
||||
"Port" here means a build for this one game, not a rewrite of its source code, which the project
|
||||
neither has nor includes.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Linux x86-64, a Vulkan 1.3 GPU. Tested: AMD RX 7800 XT with Mesa 26 (RADV).
|
||||
The *New memory and translation model* mode is not tested on NVIDIA yet.
|
||||
FSR 4 / 4.1.1 require shader Float16, Int8/Int16, integer dot products, linear compute
|
||||
derivatives and extended storage image formats; FSR 4.1.1 additionally requires
|
||||
`VK_VALVE_shader_mixed_float_dot_product`. Unsupported choices fall back to FSR 3.1
|
||||
before the first frame and are disabled in the in-game menu.
|
||||
- Your decrypted game dump: the `CUSA03173` folder (eboot.bin, sce_module, ...), version 1.09.
|
||||
The US release (`CUSA00900`) runs too: its 1.09 executable is the same as this European
|
||||
Game of the Year edition's.
|
||||
A dumped update is a separate folder: copy it over the base game, replacing files. The base
|
||||
game alone (1.00) crashes at start (guest offset 0x20348b8); the launcher and `run.sh` check
|
||||
the executable and say what is missing (`BB_SKIP_GAME_CHECK=1` skips the check). The other
|
||||
retail releases (the US `CUSA00900`, the Asian `CUSA03023`, ...) run the same 1.09 executable. Some dumps ship it
|
||||
with Lance McDonald's 60 fps patch baked in, which crashes when the gestures menu opens: the
|
||||
check asks for a clean eboot.bin then (bbport has its own 60 fps).
|
||||
- To build: GCC, CMake, Ninja, Python 3, glslang, SDL3, Vulkan headers and the libraries in
|
||||
`shell.nix`. With [Nix](https://nixos.org) everything comes from `shell.nix` automatically.
|
||||
|
||||
## Build and run
|
||||
|
||||
```bash
|
||||
git clone --recursive https://github.com/deadinside28/bloodborne_pc.git bbport && cd bbport
|
||||
bash build.sh # builds out/bb-probe and out/gpu/libbbgpu.so
|
||||
BB_GAME_DIR=/path/to/CUSA03173 bash run.sh
|
||||
```
|
||||
|
||||
or the launcher (pick the game folder, settings, *Start*):
|
||||
|
||||
```bash
|
||||
bash launcher/bb-launcher.sh # launcher/install-desktop.sh adds it to the app menu
|
||||
```
|
||||
|
||||
By default the game folder is expected next to the repository (`../CUSA03173`). Saves and the
|
||||
shader cache go to `user/` (the launcher lets you choose another folder); settings to
|
||||
`bbport.ini`. A gamepad is used through SDL3 (the launcher's *Controls → Controller* picks one
|
||||
when several are connected; `BB_GAMEPAD=<GUID or part of the name>`). The keyboard works too,
|
||||
also next to a connected gamepad (the Steam Deck always has one); both are remapped in the
|
||||
launcher (*Controls*). The character name is typed on the keyboard in a box over the game.
|
||||
The touchpad: its left half (Tab, Back/Select) opens the gestures, the right half (Backspace)
|
||||
the key items. There is no PSN: the *Play online / offline* screen is skipped and the game opens
|
||||
its main menu offline (a game patch; the launcher's *Game effects → Skip the “play online / offline” choice*,
|
||||
`BB_SKIP_NETWORK_CHOICE=0` shows it). With several monitors, *Screen → Monitor* in the launcher
|
||||
(`BB_DISPLAY=<number or part of the name>`) picks the one for the game.
|
||||
|
||||
Add-ons: `BB_DLC_DIR=/path/to/dlc` (the launcher's *DLC folder*) holds the dumped add-ons of
|
||||
the game, one folder each named by its entitlement label (`SPEXPANSIONDLC03/sce_sys/...`).
|
||||
Their data ships with the 1.09 update, so a folder with only `sce_sys` is enough. `run.sh` links
|
||||
it as `user/addcont/<title id>`, where the runtime finds add-ons (see **DLC** below); a real
|
||||
folder already there is kept. The GOTY edition (CUSA03173) needs none.
|
||||
|
||||
**Resolution and preset changes:** for outputs other than 1080p (720p on the Steam Deck,
|
||||
1440p, 4K) the whole game renders at the preset's resolution, set by a patch at start — the
|
||||
fastest path. Changing the output or the preset in the in-game menu then needs *Apply and
|
||||
restart the game*. The *Live resolution changes* setting (launcher, in-game menu,
|
||||
`bbport.ini` `live_resolution=0|1|auto`; **off by default**) instead keeps the game at
|
||||
1080p internally and scales its render targets at run time, so 720p/1080p/1440p/4K and the
|
||||
presets switch without a restart. It costs more: the game then believes it renders 1080p
|
||||
and draws more (e.g. ~8× more small lights), and some targets are copied between sizes —
|
||||
use it on strong desktop GPUs only (`auto` turns it on for discrete GPUs with 8+ GB that are
|
||||
not pre-Turing NVIDIA). 1080p output and TAA always use the live path.
|
||||
|
||||
**TAA:** a separate native-resolution temporal AA mode in the launcher and overlay,
|
||||
switchable live without an FSR model. The saved FSR preset is restored when returning
|
||||
to FSR. FSR Native AA adds reconstruction on top of full-resolution rendering and can
|
||||
be slower than disabling AA. TAA also adds work compared with no temporal AA.
|
||||
The RCAS switch and the 0–2 sharpness control also work with TAA. Sharpening runs after
|
||||
temporal accumulation and leaves its history and HUD unchanged.
|
||||
|
||||
**Mods:** the launcher accepts separate loose-file mod folders (with `dvdroot_ps4/`, an extra
|
||||
wrapper folder, or the game folders such as `chr/` directly; file name case does not matter),
|
||||
with enable switches and load order. A sibling `CUSA03173-mods/` overlay also works.
|
||||
The original game is preserved; later mods override conflicting files.
|
||||
**Third-party patches:** shadPS4-format XML patch files in the data directory's `patches/`,
|
||||
switched on and off in the launcher. See [mods and patches](docs/MODS.md).
|
||||
**DLC:** put your add-on dumps in `user/addcont/<title id>/<entitlement label>/` (shadPS4's
|
||||
layout, e.g. `user/addcont/CUSA03173/SPEXPANSIONDLC03` for The Old Hunters): each folder there is
|
||||
reported to the game as an installed add-on.
|
||||
|
||||
**Launcher language:** Russian, English, Brazilian Portuguese, Spanish, Italian, French, German or Simplified Chinese (follows the system language by default).
|
||||
**Game language:** the PS4 system language, *System language* in the launcher; by default it
|
||||
follows the desktop's locale (Japanese, English, French, Spanish, German, Italian or Russian,
|
||||
English for the rest). `run.sh` takes it as `BB_LANGUAGE` (PS4 code: 0 Japanese, 1 English,
|
||||
2 French, 3 Spanish, 4 German, 5 Italian, 8 Russian; without it, the desktop's locale as above).
|
||||
**Game region:** *Game region* in the launcher, `BB_REGION=eu|na` for `run.sh`: the regional text
|
||||
of a language, such as Spanish from Spain (`eu`) or Latin American Spanish (`na`). It sets the
|
||||
game's `USER_DEFINED_PARAM_1` (13 Europe, 2 the Americas) for the launch; by default the dump's
|
||||
`param.sfo` decides.
|
||||
|
||||
**Free camera and game debug menu** (v1.09): enable the corresponding switches in the
|
||||
launcher or in-game menu and restart. Free camera uses Lance McDonald's
|
||||
[GoldHEN patch](https://github.com/GoldHEN/GoldHEN_Patch_Repository/blob/main/patches/xml/Bloodborne-Orbis.xml):
|
||||
hold Cross and press L3 to cycle modes (keyboard: hold Space and press Z). It needs no fonts
|
||||
and conflicts with *Enemy Control*.
|
||||
For the game debug menu, install `DbgFont14h.ccm` and `DbgFont14h.tpf` from
|
||||
[Debug Menu and XML Patch](https://www.nexusmods.com/bloodborne/mods/253) into the game's
|
||||
`dvdroot_ps4/font/` first. Startup rejects missing or empty font files instead of launching
|
||||
the unsafe patch. Open it with the left touchpad / Tab (with the debug menu on, the left half
|
||||
no longer opens the gestures); Backspace is the right touchpad.
|
||||
Touch coordinates are forwarded from SDL gamepads; Back/Select emulates a left click on
|
||||
pads without a touch surface. The port's settings menu remains Insert / L3+R3.
|
||||
|
||||
GPU occlusion queries still use synthetic pixel counters (`PixelPipeStatDump`), and
|
||||
`IT_SET_PREDICATION` is unimplemented. Free camera allows visual investigation; it does
|
||||
not implement GPU occlusion culling.
|
||||
|
||||
**Upscaler assets** (not included; FSR 3.1 needs none):
|
||||
|
||||
```bash
|
||||
bash tools/fetch_fsr4_assets.sh # FSR 4 v07 (MIT, built from AMD's source by Q2RTX)
|
||||
# FSR 4.1.1, from your own AMD DLLs (e.g. OptiScaler's FSR4_LATEST), needs GE-Proton 10 or newer:
|
||||
bash tools/fsr4cap/build_assets.sh <amd_fidelityfx_upscaler_dx12.dll>
|
||||
```
|
||||
|
||||
**FSR 4.1.1 from your own DLL.** The easiest way is the launcher: *Upscaler → FSR 4.1.1 from your
|
||||
own AMD DLL → Choose DLL…*, then pick `amd_fidelityfx_upscaler_dx12.dll` version 4.1.x: from
|
||||
OptiScaler's `FSR4_LATEST` folder or from a game with FSR 4.1. That one file is enough: AMD's DLL
|
||||
exports the FidelityFX API itself, so no loader is needed (only a DLL without these exports would
|
||||
be recorded through `amd_fidelityfx_loader_dx12.dll`, and the launcher would ask for it). The DLL is checked at once,
|
||||
without a long capture; these do not fit: AMD's official FSR 4.0.x (e.g. Pragmata's 4.0.3 —
|
||||
AMD enables it on RDNA4 only, and under Proton it does not start on other GPUs) and community
|
||||
builds (4.0.2b, 4.1.1b and similar OptiScaler INT8 builds: another model and pass count). Then
|
||||
the DLL runs under Proton and is recorded at 20 sizes (progress in the row and in the *Log* tab;
|
||||
on RDNA4 a second time, for the FP8 variant),
|
||||
the passes are translated to SPIR-V, and FSR 4.1.1 is selected. The Proton build is picked
|
||||
automatically — the first under which the DLL enables FSR 4.1: GE-Proton 10 or newer,
|
||||
Proton-CachyOS, Proton Experimental (tested: GE-Proton 11, Proton-CachyOS 11, Experimental of
|
||||
October 2026; Steam's Proton 11.0 and GE-Proton 9 do not). It runs in the Steam runtime it
|
||||
requires (Steam installs it the first time any game runs with that Proton), or through
|
||||
umu-launcher when installed. On NixOS umu-launcher comes from `nix-shell` (downloaded the first
|
||||
time, ~1.7 GB); the AppImage runs the recording on the system itself, through the user's systemd
|
||||
(`systemd-run --user`), since the system's `/nix` is out of its sight. Recording on the Steam Deck is not verified yet; if the DLL does not
|
||||
enable FSR 4.1 there, build on a PC and copy the `fsr4_411` folder. The same from the command
|
||||
line: the launcher's (and the AppImage's) `--build-fsr411 <DLL>`.
|
||||
|
||||
**DLSS (NVIDIA GeForce RTX).** NVIDIA's DLSS SDK is not included: check out
|
||||
[github.com/NVIDIA/DLSS](https://github.com/NVIDIA/DLSS) (`include/` and `lib/Linux_x86_64` are
|
||||
enough) and build with `DLSS_SDK_ROOT=/path/to/DLSS bash build.sh`. That also builds the bridge
|
||||
`out/libbbport_dlss.so` (`gpu/dlss_bridge`, the only code that uses the SDK) and copies NVIDIA's
|
||||
`libnvidia-ngx-dlss.so.<version>` next to `out/bb-probe`; the driver provides the rest. Then pick
|
||||
*DLSS* in the launcher or the in-game menu: the preset sets the render size, Native AA is DLAA.
|
||||
DLSS gets the inputs FSR 3.1 gets (scene color, depth, motion vectors, jitter). Without the
|
||||
libraries, on other GPUs or with `BB_DLSS=0` it is listed as unavailable, and a DLSS setting falls
|
||||
back to FSR 3.1.
|
||||
|
||||
On Linux, DLSS also needs NVIDIA's CUDA/UVM runtime. If device creation fails with the DLSS
|
||||
extensions, the renderer retries without them and uses FSR 3.1. If `/dev/nvidia-uvm` is missing,
|
||||
load the installed driver module with `sudo modprobe nvidia_uvm` and restart the game. Installing
|
||||
the distribution's `nvidia-modprobe` package lets NVIDIA's libraries load the module when needed.
|
||||
If `modprobe` reports `Key was rejected by service` with Secure Boot enabled, enroll the
|
||||
module's signing key using the distribution's [MOK procedure](https://documentation.ubuntu.com/security/security-features/platform-protections/secure-boot/#mok-generation-and-signing-process).
|
||||
|
||||
The AppImage built with `DLSS_SDK_ROOT` carries the bridge and NVIDIA's library (NVIDIA's RTX SDK
|
||||
license allows that in an application; its text is `NVIDIA-DLSS-LICENSE.txt` in the package). A
|
||||
newer `libnvidia-ngx-dlss.so.<version>` (from the SDK's `lib/Linux_x86_64/rel`) is chosen in the
|
||||
launcher: *Upscaler → DLSS: NVIDIA's library → Choose a file…*, as the FSR 4.1.1 DLL; it is
|
||||
copied to `<user>/dlss/`, which the game prefers. A game's `nvngx_dlss.dll` is the Windows
|
||||
library: NVIDIA's Linux driver does not load it.
|
||||
|
||||
From source the script takes its tools from the system (MinGW GCC, CMake, Ninja, Python 3,
|
||||
SPIRV-Tools, Git) or from Nix; the AppImage has them prebuilt.
|
||||
|
||||
**FP8 on RDNA4.** When vkd3d-proton offers FP8 cooperative matrices (RDNA4), the DLL runs
|
||||
another variant of its passes: other shaders and weights, other dispatch sizes. The build records
|
||||
both, INT8 into `fsr4_411/` and FP8 into `fsr4_411/fp8/`, and the game picks FP8 itself when the
|
||||
GPU has FP8 matrices (`VK_EXT_shader_float8`); the log says `Upscaler: FSR 4.1.1 replay, FP8 …`.
|
||||
`BB_FSR411_VARIANT=int8` forces INT8. Checked on an RX 7800 XT through vkd3d-proton's FP16
|
||||
emulation of FP8 (`BB_FSR4CAP_FP8=1` records it there too, `fp8emu/`): bit-exact with the DLL;
|
||||
**not yet run on RDNA4 hardware.**
|
||||
|
||||
**AppImage** (Steam Deck): `bash build.sh && bash packaging/appimage.sh` →
|
||||
`dist/Bloodborne-bbport-x86_64.AppImage`; data in `~/.local/share/bbport`, `--play` starts the
|
||||
game without the launcher window (Game Mode). FSR 4.1.1 models are not packaged: build them with
|
||||
the launcher's button (see above); they go to `~/.local/share/bbport/fsr4_411`
|
||||
(`BB_PACKAGE_FSR411=1` bundles a local `fsr4_411` into an AppImage for your own devices). On the Steam Deck pick the 1280×720 output (the
|
||||
game is 16:9; on the 1280×800 screen it gets thin bars).
|
||||
|
||||
**Adding the AppImage to Steam** (*Add a Non-Steam Game*) needs no options; the compatibility tool
|
||||
does not matter. (Steam preloads its overlay into every non-Steam game; the AppImage removes it
|
||||
before its own programs start, so the Steam overlay is not shown in the game.) Where Steam
|
||||
runs games without FUSE (NixOS: Steam's FHS sandbox; the AppImage then exits with *Cannot mount
|
||||
AppImage*), set the launch options to
|
||||
|
||||
```
|
||||
TMPDIR=$HOME/.cache APPIMAGE_EXTRACT_AND_RUN=1 NO_CLEANUP=1 %command%
|
||||
```
|
||||
|
||||
The AppImage then unpacks itself (~2 GB, `~/.cache/appimage_extracted_*`) on the first start
|
||||
(~10 s) and reuses that copy afterwards; a new AppImage version gets a new copy, the old one can
|
||||
be deleted. Without `TMPDIR` it would unpack into Steam's `/tmp`, which is in RAM there. Add
|
||||
` --play` after `%command%` to skip the launcher.
|
||||
|
||||
**NVIDIA in the AppImage:** startup discovers the host's installed 64-bit NVIDIA Vulkan ICD
|
||||
and exposes its vendor libraries alongside the bundled AMD/Intel drivers. This keeps the
|
||||
NVIDIA userspace driver matched to the host kernel module. Standard Linux distributions keep
|
||||
these libraries under `/usr/lib*`; on NixOS the AppImage's internal `/nix/store` may hide them.
|
||||
In that case copy the NVIDIA libraries into an accessible directory and set
|
||||
`BB_NVIDIA_LIB_DIR=/path/to/libraries` (the NVIDIA manifest must also be accessible).
|
||||
Explicit `VK_DRIVER_FILES`/`VK_ICD_FILENAMES` overrides are preserved. Diagnose drivers inside
|
||||
the package with:
|
||||
|
||||
```bash
|
||||
./Bloodborne-bbport-x86_64.AppImage --vulkan-info 2>&1 | tee bbport-vulkan.log
|
||||
```
|
||||
|
||||
A user reported successful startup with FSR 3 on a GTX 1060 6GB (Fedora 44, NVIDIA
|
||||
580.178.04); selecting FSR 4 caused a black window. Use FSR 3 on this configuration.
|
||||
|
||||
MangoHud is bundled in the AppImage; enable its checkbox in the launcher. If MangoHud is also
|
||||
installed system-wide, or Steam's performance overlay is on (Steam Deck game mode), only one
|
||||
overlay is drawn (two drew doubled, offset text).
|
||||
When running from source, install MangoHud separately. A diagnostic launch with
|
||||
`VK_LOADER_LAYERS_DISABLE=~implicit~` also disables MangoHud.
|
||||
|
||||
Useful variables: `BB_FRAME_STATS=1` (frame statistics, including a `Memory:` line: VRAM, GTT,
|
||||
RSS, images and guest blocks in VRAM), `BB_PC_MODEL=1` (the new memory and translation model,
|
||||
0, the old model as in 0.3, is the default), `BB_ANISO=N` (anisotropic filtering of scene textures; 16 by
|
||||
default, 0 = the game's own),
|
||||
`BB_GC_IDLE_SECONDS=N` / `BB_VRAM_IDLE_SECONDS=N` (how long unused textures / buffers stay in VRAM;
|
||||
20 / 60), `BB_BREADCRUMBS=0` (no GPU breadcrumbs; with them a GPU hang names the draw or dispatch
|
||||
it is stuck in), `BB_GPU_PROFILE=1` (GPU time per
|
||||
pass), `BB_FSR4_PROFILE=1` (GPU time per FSR 4 pass), `BB_UPSCALER=taa|fsr3|fsr4|fsr411|off|none`,
|
||||
`BB_FRAMES_AHEAD=N` (how many frames the GPU command thread may run ahead of the GPU; 1 by default,
|
||||
0 = unbounded), `BB_PRESENT_THREAD=0` (present on the vblank thread, as before),
|
||||
`BB_LIVE_RES=1` (live resolution changes instead of the startup patch for outputs other than 1080p),
|
||||
`BB_PAD_RECORD=file` / `BB_PAD_REPLAY=file` (record a route with F9, replay it in scripted tests),
|
||||
`BB_GC_BUDGET_MB=N` (texture cache budget, as on integrated GPUs), `BB_VRAM_LIMIT_MB=N` (the VRAM
|
||||
budget taken as at most N MiB: checks the behaviour of cards with little VRAM), `BB_ASYNC_PIPELINES=0`
|
||||
(new pipelines compile at once and the game waits; by default in the background), `BB_PRESENT_DUMP_TRIGGER=file`
|
||||
with `BB_PRESENT_DUMP_COUNT=N` (dump N consecutive presented frames),
|
||||
`BB_FSR411_VARIANT=int8|fp8|fp8emu` (FSR 4.1.1 variant; by default FP8 where the GPU has FP8
|
||||
matrices), `BB_READBACKS=0|1|2` (reads of GPU-written memory by the game: 1 by default, 2
|
||||
precise and slow, 0 off).
|
||||
More in [docs/](docs); recent changes: [docs/CHANGES_0.4.md](docs/CHANGES_0.4.md) (in Russian),
|
||||
[docs/CHANGES_2026-10-06.md](docs/CHANGES_2026-10-06.md).
|
||||
|
||||
**Online play (shadNet).** A separate module, `out/gpu/libbbnet.so` (`gpu/bbnet`: shadPS4's network
|
||||
and PSN libraries from shadp2p, a shadPS4 fork, with the shadNet client), built when protobuf,
|
||||
nlohmann_json, OpenSSL, zlib and miniupnpc are there (`shell.nix`) and included in the AppImage.
|
||||
The launcher's *Online* page turns it on (`BB_ONLINE=1`): messages, bloodstains and ghosts come
|
||||
from The Hunter's Dream, bells and summons go through a shadNet server (`srv.shadps4.net:31313` by
|
||||
default) with a shadNet account (Online ID and password). Offline the module is never loaded; a
|
||||
build without it plays offline only.
|
||||
|
||||
## Repository layout
|
||||
|
||||
| Path | Contents |
|
||||
|---|---|
|
||||
| `src/` | Loader (`probe.c`) and the HLE runtime |
|
||||
| `scripts/` | Offline preparation of the game image, module linking, patch compiler |
|
||||
| `gpu/` | Renderer library: vendored shadPS4 video core with this port's changes (`gpu/VENDOR.txt`), shims, ImGui menu, FSR 4.1.1 runtime (`gpu/shadps4/video_core/renderer_vulkan/fsr411`) |
|
||||
| `launcher/`, `packaging/` | GTK4 launcher; Nix package and AppImage |
|
||||
| `patches/` | Community patches for Bloodborne |
|
||||
| `tools/` | Developer tools: scripted runs, A/B toggles, FSR benchmark helpers, FSR 4 shader rewrites, `fsr4cap` (FSR 4.1.1 recording/extraction) |
|
||||
| `tests/` | Loader, runtime, patch and renderer tests |
|
||||
| `docs/` | Design notes and measurements ([upscaler](docs/upscaler.md), [parallel GPU](docs/parallel_gpu.md), [motion vectors](docs/motion_vectors.md), [roadmap](docs/ROADMAP.md)) |
|
||||
|
||||
Tests: `bash build.sh --test`, `python3 -m unittest discover -s tests`, and
|
||||
`ninja -C out/gpu motion-history-test ui-composition-test scene-resolution-test motion-shader-test settings-test`.
|
||||
|
||||
## Roadmap
|
||||
|
||||
- The new memory and translation model on by default once it is stable and tested on NVIDIA;
|
||||
the old model is removed after that.
|
||||
- Textures as objects created at load time (less guessing by address, uploads outside the
|
||||
frame).
|
||||
- Shaders translated ahead of time, at install, not during play.
|
||||
- More CPU parallelism in GPU command processing (split the draw-recording stage further),
|
||||
scaling to all hardware threads — most important for the Steam Deck.
|
||||
- Async compute for the upscaler (the frame is GPU-bound at 4K).
|
||||
- XeSS (super resolution) and XeFG frame generation through a Wine helper sharing Vulkan
|
||||
memory (a memory-bridge prototype is in `tools/bridge_helper`); inputs exposed so that OptiScaler-style mapping works.
|
||||
- Frame generation (FSR 3.1 FG first), reactive and transparency masks for particles and fog.
|
||||
- Fix the races in AMD's FSR 4.1.1 shaders at output widths that are not multiples of 64
|
||||
(e.g. 1600×900), as already done for the left-edge race in FSR 4 v07 at 1080p.
|
||||
- Steam Deck validation of the AppImage; HDR output.
|
||||
|
||||
## Credits and licenses
|
||||
|
||||
bbport is licensed under the **GNU GPL v2 or later** ([LICENSE](LICENSE)) — it contains code
|
||||
from shadPS4 (GPL-2.0-or-later). Third-party components keep their licenses:
|
||||
[shadPS4](https://github.com/shadps4-emu/shadPS4) video core and shader recompiler (GPL-2.0+),
|
||||
[sirit](https://github.com/shadps4-emu/sirit), [half](https://half.sourceforge.net/),
|
||||
[FSR-Vulkan](https://github.com/FireBurn/FSR-Vulkan) by FireBurn (MIT; FSR 3.1 on Vulkan and the
|
||||
FSR 4 v07 provider), AMD FidelityFX SDK (MIT), [LibAtrac9](https://github.com/Thealexbarney/LibAtrac9)
|
||||
(MIT), [Dear ImGui](https://github.com/ocornut/imgui) (MIT), DejaVu fonts,
|
||||
[dxil-spirv](https://github.com/HansKristian-Work/dxil-spirv) (MIT, used to build the
|
||||
FSR 4.1.1 assets). Game patches by Kyo, Lance McDonald, auser1337, illusion, emoose and other
|
||||
community members (`patches/Bloodborne.xml`). The DLSS bridge (`gpu/dlss_bridge`, MIT) and
|
||||
loader come from [Supermedo's Windows port](https://github.com/Supermedo/bloodborne_pc), adapted
|
||||
from [IFreemz/shadPS4-Bloodborne-DLSS-FSR](https://github.com/IFreemz/shadPS4-Bloodborne-DLSS-FSR).
|
||||
AMD's FSR 4 DLLs and model data and NVIDIA's DLSS SDK and libraries are not distributed here.
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
# bbport — нативный порт Bloodborne для Linux
|
||||
|
||||
[English](README.md) · **Русский**
|
||||
> README оригинального проекта (deadinside28/bloodborne_pc), сохранён ради технических
|
||||
> подробностей. README этого форка — [README.md](README.md) (на английском).
|
||||
|
||||
[English](BBPORT.md) · **Русский**
|
||||
|
||||
bbport — это аналог Wine + DXVK, только для одной игры: *Bloodborne* для PlayStation 4
|
||||
(CUSA03173, версия игры 1.09) на ПК с Linux x86-64. Оригинальный исполняемый файл игры
|
||||
@@ -256,6 +259,14 @@ Experimental от октября 2026; Proton 11.0 из Steam и GE-Proton 9 —
|
||||
векторы движения, jitter). Без библиотек, на других видеокартах или с `BB_DLSS=0` он недоступен,
|
||||
а выбранный DLSS заменяется на FSR 3.1.
|
||||
|
||||
На Linux DLSS также нужен CUDA/UVM из драйвера NVIDIA. Если устройство не создаётся с
|
||||
расширениями DLSS, рендерер повторяет попытку без них и использует FSR 3.1. Если отсутствует
|
||||
`/dev/nvidia-uvm`, загрузи установленный модуль командой `sudo modprobe nvidia_uvm` и перезапусти
|
||||
игру. Пакет `nvidia-modprobe` из репозитория дистрибутива позволяет библиотекам NVIDIA загружать
|
||||
модуль по мере необходимости.
|
||||
Если при включённом Secure Boot команда `modprobe` сообщает `Key was rejected by service`,
|
||||
зарегистрируй ключ подписи модуля по [процедуре MOK своего дистрибутива](https://documentation.ubuntu.com/security/security-features/platform-protections/secure-boot/#mok-generation-and-signing-process).
|
||||
|
||||
AppImage, собранный с `DLSS_SDK_ROOT`, содержит мост и библиотеку NVIDIA (лицензия RTX SDK от NVIDIA
|
||||
это разрешает в составе приложения; её текст — `NVIDIA-DLSS-LICENSE.txt` в пакете). Более новая
|
||||
`libnvidia-ngx-dlss.so.<версия>` (из `lib/Linux_x86_64/rel` в SDK) выбирается в лаунчере:
|
||||
@@ -1,408 +1,173 @@
|
||||
THIS PROJECT IS NOT RELATED TO SHADPS4. ALL QUESTIONS RELATED TO THIS PROJECT SHOULD BE SENT TO THE DISCORD SERVER https://discord.gg/KYZRKk9CB, NOT TO THE SHADPS4 SERVER.
|
||||
# Bloodborne for Linux
|
||||
|
||||
[](https://github.com/ayozetr/bloodborne_linux/releases/latest)
|
||||
[](https://github.com/ayozetr/bloodborne_linux/releases/latest)
|
||||
[](LICENSE)
|
||||
[](#features)
|
||||
[](https://ko-fi.com/ayozetr)
|
||||
|
||||
# bbport — a native Linux port of Bloodborne
|
||||
The PlayStation 4 version of *Bloodborne* running natively on Linux. The game's own x86-64 code
|
||||
runs directly on the CPU and its graphics are translated to Vulkan. It is not an emulator.
|
||||
|
||||
**English** · [Русский](README.ru.md)
|
||||
This fork builds on [bbport](https://github.com/deadinside28/bloodborne_pc), the native Linux port
|
||||
by deadinside28. It adds the features of [Supermedo's Windows port](https://github.com/Supermedo/bloodborne_pc)
|
||||
(online play, DLSS, cheats) brought to Linux, plus a few of its own.
|
||||
|
||||
bbport is the counterpart of Wine + DXVK for a single game: *Bloodborne* for PlayStation 4
|
||||
(CUSA03173, game version 1.09) on an x86-64 Linux PC. The game's original executable runs
|
||||
directly on the PC:
|
||||
> **No game files are included.** You need your own decrypted dump of Bloodborne with update
|
||||
> 1.09, from a retail release: [which ones, and which have been checked](docs/COMPATIBILITY.md).
|
||||
> This project is not affiliated with Sony Interactive Entertainment or FromSoftware.
|
||||
|
||||
- **as in Wine**, the game's x86-64 code runs on the CPU directly, and a runtime written for
|
||||
this one game replaces the PS4 system libraries;
|
||||
- **as in DXVK**, the game's graphics are translated to Vulkan — by a renderer derived from
|
||||
[shadPS4](https://github.com/shadps4-emu/shadPS4) and heavily extended for this game, including
|
||||
temporal upscaling with AMD FSR 3.1, FSR 4 and FSR 4.1.1;
|
||||
- memory and the GPU's work follow the PC model on AMD GPUs by default (the new memory and
|
||||
translation model); NVIDIA and other GPUs keep the old memory model of 0.3 for now. The
|
||||
launcher's *Memory model* switches either way.
|
||||
## Features
|
||||
|
||||
One step remains to the full Wine + DXVK model: the new model on NVIDIA as well, then removing the
|
||||
old memory model (see below).
|
||||
|
||||
> **No game files are included.** You need your own dump of Bloodborne with update 1.09: any retail
|
||||
> release (CUSA00900 in the US, CUSA03173 for the European Game of the Year edition, ...), as they
|
||||
> share the same executable.
|
||||
> This project is not affiliated with Sony Interactive Entertainment, FromSoftware or AMD.
|
||||
|
||||
**Status: experimental, playable.** The game boots, loads saves and plays (the Hunter's Dream
|
||||
and several areas of Yharnam were played with it) with sound, gamepad and saving.
|
||||
A full play-through has not been verified, and only one machine (Linux, AMD Radeon RX 7800 XT,
|
||||
Mesa/RADV) has been tested thoroughly.
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Native execution.** The eboot is converted offline into a flat memory image; PS4 libc and
|
||||
libSceFios2 are linked into it as native code. No CPU emulation and no per-instruction
|
||||
translation: the game code runs at full speed.
|
||||
- **Two memory models** (launcher → *Mode → Memory and translation model*; *Auto*, the default,
|
||||
picks by GPU):
|
||||
- **The old memory model, as in 0.3 — the default on NVIDIA, Intel and others.** VRAM copies
|
||||
of the game's memory, writes tracked through page protection. With every fix made since 0.3
|
||||
(motion vectors and the upscaler, flicker with DoF on, a damaged shader cache).
|
||||
- **The new memory and translation model — the default on AMD.** The game's memory lives
|
||||
in system RAM and the GPU reads it where it is, as a PC game's buffers; data it reads often
|
||||
is kept in VRAM and given back when unused (textures after 20 s, buffers after 60 s). The
|
||||
layer's memory module tracks CPU writes to mirrored blocks to keep those copies coherent;
|
||||
it does not copy the whole GPU-visible memory. The PS4 command processor's work
|
||||
is translated into Vulkan commands rather than emulated on the CPU: memory writes
|
||||
(`WRITE_DATA`, DMA) are done by the GPU in command-stream order, and fences are written by the
|
||||
GPU itself at the end of the pipeline, at the same point of the stream as the PS4's command
|
||||
processor writes them. On AMD the game's memory is kept in a sparse buffer (the arena); on
|
||||
NVIDIA, Intel and others it goes through the layer's memory module: bound in place, VRAM
|
||||
copies as separate buffers, no sparse rebinding (NVIDIA's sparse binding froze the GPU for
|
||||
seconds); `BB_LAYER_MEMORY=1/0` chooses by hand. Tested on an RX 7800 XT. On NVIDIA it can be chosen by hand (*New*); testers' results differ by scene,
|
||||
some scenes are slower than with the old model, so it is not the default there yet. Not
|
||||
tested on Intel. Without the launcher: `BB_PC_MODEL=1/0` (unset: by GPU, as *Auto*).
|
||||
|
||||
[Changes in 0.5](docs/CHANGES_0.5.md); the prereleases: [pre4](docs/CHANGES_0.5-pre4.md),
|
||||
[pre3](docs/CHANGES_0.5-pre3.md), [pre2](docs/CHANGES_0.5-pre2.md).
|
||||
|
||||
Unused textures are freed in both modes, so VRAM no longer grows with every area visited.
|
||||
- **Unlocked frame rate.** Community patches (`patches/Bloodborne.xml`) make the simulation
|
||||
use the real frame time; ~90 FPS at 4K with FSR 4 Balanced on an RX 7800 XT, ~150 FPS at
|
||||
1440p with FSR 4 Quality. Also 30/60/90 FPS modes.
|
||||
- **Temporal upscaling built for this game.** Bloodborne has no velocity buffer, so bbport
|
||||
computes motion vectors itself: camera motion from depth and the scene matrices, and object
|
||||
motion (characters, cloth, weapons) from the vertex positions of the previous frame. The
|
||||
scene is jittered sub-pixel (Halton) and rendered at a reduced resolution; the upscaler fills
|
||||
the output (720p for the Steam Deck, 1080p, 1440p or 2160p) and the UI is drawn natively at the output resolution.
|
||||
- **FSR 3.1** (FireBurn/FSR-Vulkan).
|
||||
- **FSR 4 (INT8, model v07)** on GPUs exposing the required Vulkan shader features —
|
||||
RDNA2/3 included (see Requirements).
|
||||
- **FSR 4.1.1**: AMD's 4.1.1 DLL is recorded once under vkd3d-proton and its passes are
|
||||
replayed natively on Vulkan; the output is **bit-exact** with the DLL. Two variants, as in
|
||||
the DLL: INT8 on any GPU with the required shader features, and FP8 matrices on RDNA4 (RX
|
||||
9000; picked automatically). The assets are built on your machine from one DLL of your own
|
||||
(4.1.x): the launcher's *FSR 4.1.1 from your own AMD DLL → Choose DLL…* button (2–5 minutes,
|
||||
twice that on RDNA4; needs a recent Proton), or `tools/fsr4cap`.
|
||||
- Faster than AMD's own shaders on RDNA3: the final passes of FSR 4 and 4.1.1 were rewritten
|
||||
to store through workgroup memory (3.5× and 2.3× faster, bit-exact); FSR 4 costs ~4 ms at
|
||||
4K on an RX 7800 XT instead of ~6 ms.
|
||||
- **Multi-threaded GPU command processing.** The PS4 command stream is decoded on one thread
|
||||
and draws are bound and recorded on another (two-stage pipeline), with a Vulkan recording
|
||||
thread and helper threads for memory copies. Early on the single GPU thread capped the game
|
||||
at ~26 FPS; now it runs at 90–150 FPS depending on resolution and scene.
|
||||
- **Settings inside the game's own menu:** *System → Display / Effects* (after *Screen/Sound*),
|
||||
built and drawn by the game itself — output resolution, upscaler, preset, sharpness, FPS
|
||||
counter, model detail and the game's effects, in the game's language.
|
||||
- **Overlay menu** (Insert or L3+R3), styled like the game's dialogs: everything above plus the
|
||||
advanced settings, with gamepad, keyboard and mouse.
|
||||
- **GTK4 launcher** and an **AppImage** for the Steam Deck.
|
||||
|
||||
## How it differs from shadPS4
|
||||
|
||||
| | shadPS4 | bbport |
|
||||
|---|---|---|
|
||||
| Scope | General PS4 emulator, many games | One game: Bloodborne v1.09 |
|
||||
| Loading | Its own ELF loader and kernel emulation at run time | The eboot is converted offline (`scripts/`) into an image with PS4 libc/Fios2 linked in; a C loader maps it and jumps into the game (loader and runtime: ~5k lines) |
|
||||
| Memory | The GPU's view of PS4 memory is kept in VRAM copies, synchronized through page-protection write tracking | By default the same model (as in 0.3); in the *New memory and translation model* mode: the game's memory in system RAM, used by the GPU in place, frequently read data in VRAM, freed when unused |
|
||||
| Command processor | Emulated: memory writes, DMA and fences are done by the CPU while decoding | By default the same; in the new mode translated into Vulkan commands that the GPU runs in stream order, fences written after the work has really finished |
|
||||
| System libraries | Broad HLE of the PS4 OS | A small runtime (`src/runtime_*.c`) that implements exactly what Bloodborne calls: memory, threads, sync, files, audio (incl. ATRAC9), pad, saves, AppContent |
|
||||
| GPU | shadPS4 video core and shader recompiler | The same core (vendored, GPL) with ~200 marked changes (`bbport:`) plus new modules: two-stage draw pipeline, render-state and texture-set memoization, render-scale proxies, motion vectors, FSR 3.1/4/4.1.1, frame capture and GPU profiler |
|
||||
| GPU thread | One thread processes the whole command stream (the bottleneck in Bloodborne) | Decode and draw recording run on separate threads; the work scales with the hardware threads (Steam Deck included) |
|
||||
| Upscaling | — | Temporal (FSR 3.1, FSR 4, FSR 4.1.1) with the game's own motion vectors and jitter |
|
||||
| Game patches | Patch files applied by the emulator | The same community patches, compiled at start (`scripts/patches.py`); render resolution, effects and FPS from the launcher |
|
||||
|
||||
Without shadPS4 there would be no bbport: its renderer and shader recompiler are the base of
|
||||
the graphics side.
|
||||
|
||||
### Wine + DXVK for one game
|
||||
|
||||
- **CPU.** The PS4 CPU is x86-64, so the game's code runs directly on the PC's CPU, with no
|
||||
emulation and no instruction translation.
|
||||
- **System libraries.** As Wine replaces the Windows API, the bbport runtime (`src/runtime_*.c`)
|
||||
implements exactly the PS4 OS functions Bloodborne calls: memory, threads, files, audio, pad,
|
||||
saves.
|
||||
- **Memory.** In the new mode, as in a PC game: the game's data in system RAM, VRAM used the way
|
||||
a PC game uses it. The mode is still experimental.
|
||||
- **Graphics.** The PS4 GPU's command stream (PM4) is a recording of the game's graphics API
|
||||
calls: it is written by 99 functions of the statically linked libGnm, so decoding the stream
|
||||
and translating the calls themselves come to the same thing. GCN shaders are translated to
|
||||
SPIR-V. In the new mode the command processor's work (memory writes, DMA, fences) is
|
||||
translated into Vulkan commands as well; fences are written by the GPU itself. Resource
|
||||
descriptors (textures, buffers) are read by the translator on the CPU, as DXVK reads resource
|
||||
bindings: a PS4 texture has to be converted from the PS4 tiling into a Vulkan image before the
|
||||
draw runs, so the CPU must know its descriptor beforehand — translation, not emulation. The
|
||||
descriptor tables (the constant engine's dumps) are read only by the translator, so the CPU
|
||||
performs them. The game's occlusion queries (~60 a frame) are
|
||||
Vulkan occlusion queries; predication, `COPY_DATA` and `COND_EXEC` are translated too (check:
|
||||
`BB_PM4_SELFTEST=1`). Details:
|
||||
[docs/EMULATION_REMOVAL_PLAN.ru.md](docs/EMULATION_REMOVAL_PLAN.ru.md), "Шаг 4" (Russian).
|
||||
|
||||
"Port" here means a build for this one game, not a rewrite of its source code, which the project
|
||||
neither has nor includes.
|
||||
- **Online play (beta)** through [The Hunter's Dream](https://thehuntersdream.com) and shadNet:
|
||||
messages, bloodstains, ghosts, bells and summons. Linux and Windows players can play together,
|
||||
through shadPS4's public server or [one of your own](docs/SERVER.md).
|
||||
- **Upscaling:** AMD FSR 3.1, FSR 4 and FSR 4.1.1, **NVIDIA DLSS** on GeForce RTX, and
|
||||
native-resolution TAA.
|
||||
- **Unlocked frame rate**, capped at your display's refresh rate up to 120 FPS (above that the
|
||||
game's own movement timing breaks), or 30/60/90 FPS. Output from 720p to 4K.
|
||||
- **Launcher** with every setting in one window, in English, Spanish, Italian, French, German,
|
||||
Portuguese, Russian and Simplified Chinese, like the in-game menu (Insert or L3+R3).
|
||||
- **Settings in the game's own System menu** as well (Display and Effects pages), in the game's
|
||||
language.
|
||||
- **Two memory models:** the new one, where the GPU works with the game's memory as in a PC game
|
||||
(the default on AMD), and the one of 0.3 (the default elsewhere); the launcher switches them.
|
||||
- **Xbox, Switch and keyboard button prompts**, picked by the controller in use, and a gestures
|
||||
menu without the motion-control icons.
|
||||
- **Cheats and gameplay tweaks:** never die, stealth, silent steps, enemy control, camera
|
||||
distance, ragdoll physics and more.
|
||||
- **Trophies:** a notice in the game, in the style of the PS4's, and a Trophies page in the
|
||||
launcher with every trophy, from your own dump. Their names and descriptions need the PS4's
|
||||
trophy key (shadPS4's TrophyKey), which you enter in the launcher; without it, only the icons.
|
||||
- **Game effects** on and off, **mods** and community **patches**, loaded without changing the
|
||||
game files ([how](docs/MODS.en.md)).
|
||||
- **Add to Steam** in one click (native or Flatpak Steam), and **Discord Rich Presence** with the
|
||||
area you are in and your co-op partners.
|
||||
- **AppImage:** one file with everything inside, for any distribution and the Steam Deck. It
|
||||
updates itself: the launcher's **About** page checks this repository's releases and installs
|
||||
a new AppImage over the old one.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Linux x86-64, a Vulkan 1.3 GPU. Tested: AMD RX 7800 XT with Mesa 26 (RADV).
|
||||
The *New memory and translation model* mode is not tested on NVIDIA yet.
|
||||
FSR 4 / 4.1.1 require shader Float16, Int8/Int16, integer dot products, linear compute
|
||||
derivatives and extended storage image formats; FSR 4.1.1 additionally requires
|
||||
`VK_VALVE_shader_mixed_float_dot_product`. Unsupported choices fall back to FSR 3.1
|
||||
before the first frame and are disabled in the in-game menu.
|
||||
- Your decrypted game dump: the `CUSA03173` folder (eboot.bin, sce_module, ...), version 1.09.
|
||||
A dumped update is a separate folder: copy it over the base game, replacing files. The base
|
||||
game alone (1.00) crashes at start (guest offset 0x20348b8); the launcher and `run.sh` check
|
||||
the executable and say what is missing (`BB_SKIP_GAME_CHECK=1` skips the check). The other
|
||||
retail releases (the US `CUSA00900`, the Asian `CUSA03023`, ...) run the same 1.09 executable. Some dumps ship it
|
||||
with Lance McDonald's 60 fps patch baked in, which crashes when the gestures menu opens: the
|
||||
check asks for a clean eboot.bin then (bbport has its own 60 fps).
|
||||
- To build: GCC, CMake, Ninja, Python 3, glslang, SDL3, Vulkan headers and the libraries in
|
||||
`shell.nix`. With [Nix](https://nixos.org) everything comes from `shell.nix` automatically.
|
||||
- Linux x86-64 and a GPU with Vulkan 1.3: AMD or Intel with Mesa, or NVIDIA with NVIDIA's driver
|
||||
(open kernel modules included). Mesa's NVK has not been tested.
|
||||
- DLSS needs a GeForce RTX (20 series or newer) and NVIDIA's driver; it does not work with NVK.
|
||||
- About 6 GB of free memory, 10 GB for 1440p or 4K output.
|
||||
|
||||
## Build and run
|
||||
## Getting started
|
||||
|
||||
### AppImage
|
||||
|
||||
Build it with `bash build.sh && bash packaging/appimage-sharun.sh`, then run
|
||||
`dist/Bloodborne-bbport-x86_64.AppImage`. Choose your game folder (the one with `eboot.bin`) and
|
||||
press **Play**. Saves and settings go to `~/.local/share/bbport`.
|
||||
|
||||
### From source
|
||||
|
||||
On Arch Linux (or CachyOS, Manjaro…):
|
||||
|
||||
```bash
|
||||
git clone --recursive https://github.com/deadinside28/bloodborne_pc.git bbport && cd bbport
|
||||
bash build.sh # builds out/bb-probe and out/gpu/libbbgpu.so
|
||||
BB_GAME_DIR=/path/to/CUSA03173 bash run.sh
|
||||
sudo pacman -S --needed base-devel cmake ninja python python-gobject gtk4 libadwaita sdl3 \
|
||||
vulkan-headers vulkan-icd-loader glslang spirv-headers spirv-cross ffmpeg boost fmt \
|
||||
robin-map xxhash zydis miniz protobuf nlohmann-json openssl miniupnpc python-pillow ttf-dejavu
|
||||
paru -S magic_enum vulkan-memory-allocator xbyak # from the AUR
|
||||
|
||||
git clone --recursive https://github.com/ayozetr/bloodborne_linux
|
||||
cd bloodborne_linux
|
||||
bash build.sh
|
||||
python3 launcher/bbport_launcher.py # or launcher/install-desktop.sh for a menu entry
|
||||
```
|
||||
|
||||
or the launcher (pick the game folder, settings, *Start*):
|
||||
With [Nix](https://nixos.org), `bash build.sh` gets every dependency from `shell.nix` by itself.
|
||||
|
||||
```bash
|
||||
bash launcher/bb-launcher.sh # launcher/install-desktop.sh adds it to the app menu
|
||||
```
|
||||
### Online play
|
||||
|
||||
By default the game folder is expected next to the repository (`../CUSA03173`). Saves and the
|
||||
shader cache go to `user/` (the launcher lets you choose another folder); settings to
|
||||
`bbport.ini`. A gamepad is used through SDL3 (the launcher's *Controls → Controller* picks one
|
||||
when several are connected; `BB_GAMEPAD=<GUID or part of the name>`). The keyboard works too,
|
||||
also next to a connected gamepad (the Steam Deck always has one); both are remapped in the
|
||||
launcher (*Controls*). The character name is typed on the keyboard in a box over the game.
|
||||
The touchpad: its left half (Tab, Back/Select) opens the gestures, the right half (Backspace)
|
||||
the key items. There is no PSN: the *Play online / offline* screen is skipped and the game opens
|
||||
its main menu offline (a game patch; the launcher's *Game effects → Skip the “play online / offline” choice*,
|
||||
`BB_SKIP_NETWORK_CHOICE=0` shows it). With several monitors, *Screen → Monitor* in the launcher
|
||||
(`BB_DISPLAY=<number or part of the name>`) picks the one for the game.
|
||||
1. Register a shadNet account at [shadnet.shadps4.net](https://shadnet.shadps4.net), or get one
|
||||
from the owner of your group's own server.
|
||||
2. In the launcher, open **Online**, turn on **Play online**, choose the shadNet server and enter
|
||||
your account name (not your email) and password. **Test the connection**: every line should
|
||||
be green.
|
||||
3. Start the game and choose online at the title screen.
|
||||
|
||||
**Resolution and preset changes:** for outputs other than 1080p (720p on the Steam Deck,
|
||||
1440p, 4K) the whole game renders at the preset's resolution, set by a patch at start — the
|
||||
fastest path. Changing the output or the preset in the in-game menu then needs *Apply and
|
||||
restart the game*. The *Live resolution changes* setting (launcher, in-game menu,
|
||||
`bbport.ini` `live_resolution=0|1|auto`; **off by default**) instead keeps the game at
|
||||
1080p internally and scales its render targets at run time, so 720p/1080p/1440p/4K and the
|
||||
presets switch without a restart. It costs more: the game then believes it renders 1080p
|
||||
and draws more (e.g. ~8× more small lights), and some targets are copied between sizes —
|
||||
use it on strong desktop GPUs only (`auto` turns it on for discrete GPUs with 8+ GB that are
|
||||
not pre-Turing NVIDIA). 1080p output and TAA always use the live path.
|
||||
Everyone in a session needs game version 1.09 and the same servers. Turn cheats off when you
|
||||
play with others.
|
||||
|
||||
**TAA:** a separate native-resolution temporal AA mode in the launcher and overlay,
|
||||
switchable live without an FSR model. The saved FSR preset is restored when returning
|
||||
to FSR. FSR Native AA adds reconstruction on top of full-resolution rendering and can
|
||||
be slower than disabling AA. TAA also adds work compared with no temporal AA.
|
||||
The RCAS switch and the 0–2 sharpness control also work with TAA. Sharpening runs after
|
||||
temporal accumulation and leaves its history and HUD unchanged.
|
||||
**Co-op needs your own server for now:** shadPS4's public server does not answer on its UDP port,
|
||||
so summons do not connect through it (messages, bloodstains and ghosts do).
|
||||
[docs/SERVER.md](docs/SERVER.md) sets one up with Docker and Tailscale; it has been tested with a
|
||||
Linux and a Windows player.
|
||||
|
||||
**Mods:** the launcher accepts separate loose-file mod folders (with `dvdroot_ps4/`, an extra
|
||||
wrapper folder, or the game folders such as `chr/` directly; file name case does not matter),
|
||||
with enable switches and load order. A sibling `CUSA03173-mods/` overlay also works.
|
||||
The original game is preserved; later mods override conflicting files.
|
||||
**Third-party patches:** shadPS4-format XML patch files in the data directory's `patches/`,
|
||||
switched on and off in the launcher. See [mods and patches](docs/MODS.md).
|
||||
**DLC:** put your add-on dumps in `user/addcont/<title id>/<entitlement label>/` (shadPS4's
|
||||
layout, e.g. `user/addcont/CUSA03173/SPEXPANSIONDLC03` for The Old Hunters): each folder there is
|
||||
reported to the game as an installed add-on.
|
||||
Co-op connects the players directly over UDP. UPnP opens the port on the router by itself; with
|
||||
ufw or firewalld on, the launcher's **Online** page says so and gives the commands: pin the
|
||||
**Co-op port** (for example 3658), open it (`sudo ufw allow 3658/udp`), and for UPnP let the
|
||||
router's answers in (`sudo ufw allow from <router> proto udp`). [More in docs/ONLINE.md](docs/ONLINE.md).
|
||||
|
||||
**Launcher language:** Russian, English, Brazilian Portuguese or Simplified Chinese (follows the system language by default).
|
||||
### DLSS
|
||||
|
||||
**Free camera and game debug menu** (v1.09): enable the corresponding switches in the
|
||||
launcher or in-game menu and restart. Free camera uses Lance McDonald's
|
||||
[GoldHEN patch](https://github.com/GoldHEN/GoldHEN_Patch_Repository/blob/main/patches/xml/Bloodborne-Orbis.xml):
|
||||
hold Cross and press L3 to cycle modes (keyboard: hold Space and press Z). It needs no fonts
|
||||
and conflicts with *Enemy Control*.
|
||||
For the game debug menu, install `DbgFont14h.ccm` and `DbgFont14h.tpf` from
|
||||
[Debug Menu and XML Patch](https://www.nexusmods.com/bloodborne/mods/253) into the game's
|
||||
`dvdroot_ps4/font/` first. Startup rejects missing or empty font files instead of launching
|
||||
the unsafe patch. Open it with the left touchpad / Tab (with the debug menu on, the left half
|
||||
no longer opens the gestures); Backspace is the right touchpad.
|
||||
Touch coordinates are forwarded from SDL gamepads; Back/Select emulates a left click on
|
||||
pads without a touch surface. The port's settings menu remains Insert / L3+R3.
|
||||
On a PC with a GeForce RTX, run `bash tools/build_dlss.sh` once after `build.sh`. It downloads
|
||||
what it needs from the NVIDIA DLSS SDK and builds the bridge. Then choose **DLSS** as the
|
||||
upscaler.
|
||||
|
||||
GPU occlusion queries still use synthetic pixel counters (`PixelPipeStatDump`), and
|
||||
`IT_SET_PREDICATION` is unimplemented. Free camera allows visual investigation; it does
|
||||
not implement GPU occlusion culling.
|
||||
### DLC
|
||||
|
||||
**Upscaler assets** (not included; FSR 3.1 needs none):
|
||||
The Game of the Year edition (`CUSA03173`) already includes The Old Hunters. With the base game
|
||||
(such as the US `CUSA00900`), dump your add-ons too: each one is a folder (`SPEXPANSIONDLC03`,
|
||||
`SPDLCMESSENGER00`…) with only `sce_sys` inside, because their data ships with the 1.09 update.
|
||||
Put them in one folder and pick it in the launcher (**Game → DLC folder**), or set
|
||||
`BB_DLC_DIR=/path/to/dlc` for `run.sh`; the launcher shows how many it found. Each folder must
|
||||
keep the add-on's name (its entitlement label, such as `SPEXPANSIONDLC03`). Copying them to
|
||||
`user/addcont/<title id>/`, shadPS4's layout, works too.
|
||||
|
||||
```bash
|
||||
bash tools/fetch_fsr4_assets.sh # FSR 4 v07 (MIT, built from AMD's source by Q2RTX)
|
||||
# FSR 4.1.1, from your own AMD DLLs (e.g. OptiScaler's FSR4_LATEST), needs GE-Proton 10 or newer:
|
||||
bash tools/fsr4cap/build_assets.sh <amd_fidelityfx_upscaler_dx12.dll>
|
||||
```
|
||||
### Add to Steam
|
||||
|
||||
**FSR 4.1.1 from your own DLL.** The easiest way is the launcher: *Upscaler → FSR 4.1.1 from your
|
||||
own AMD DLL → Choose DLL…*, then pick `amd_fidelityfx_upscaler_dx12.dll` version 4.1.x: from
|
||||
OptiScaler's `FSR4_LATEST` folder or from a game with FSR 4.1. That one file is enough: AMD's DLL
|
||||
exports the FidelityFX API itself, so no loader is needed (only a DLL without these exports would
|
||||
be recorded through `amd_fidelityfx_loader_dx12.dll`, and the launcher would ask for it). The DLL is checked at once,
|
||||
without a long capture; these do not fit: AMD's official FSR 4.0.x (e.g. Pragmata's 4.0.3 —
|
||||
AMD enables it on RDNA4 only, and under Proton it does not start on other GPUs) and community
|
||||
builds (4.0.2b, 4.1.1b and similar OptiScaler INT8 builds: another model and pass count). Then
|
||||
the DLL runs under Proton and is recorded at 20 sizes (progress in the row and in the *Log* tab;
|
||||
on RDNA4 a second time, for the FP8 variant),
|
||||
the passes are translated to SPIR-V, and FSR 4.1.1 is selected. The Proton build is picked
|
||||
automatically — the first under which the DLL enables FSR 4.1: GE-Proton 10 or newer,
|
||||
Proton-CachyOS, Proton Experimental (tested: GE-Proton 11, Proton-CachyOS 11, Experimental of
|
||||
October 2026; Steam's Proton 11.0 and GE-Proton 9 do not). It runs in the Steam runtime it
|
||||
requires (Steam installs it the first time any game runs with that Proton), or through
|
||||
umu-launcher when installed. On NixOS umu-launcher comes from `nix-shell` (downloaded the first
|
||||
time, ~1.7 GB); the AppImage runs the recording on the system itself, through the user's systemd
|
||||
(`systemd-run --user`), since the system's `/nix` is out of its sight. Recording on the Steam Deck is not verified yet; if the DLL does not
|
||||
enable FSR 4.1 there, build on a PC and copy the `fsr4_411` folder. The same from the command
|
||||
line: the launcher's (and the AppImage's) `--build-fsr411 <DLL>`.
|
||||
**Game → Add to Steam** puts Bloodborne in your Steam library; it starts with the launcher's
|
||||
settings. Steam has to be closed while the game is added, and the launcher offers to do that.
|
||||
Flatpak Steam runs games inside its sandbox, which has neither the game nor your drivers, so
|
||||
the shortcut starts the game outside it with `flatpak-spawn --host`. That needs the
|
||||
`org.freedesktop.Flatpak` permission for Steam, which the launcher asks for once: it lets Steam
|
||||
start programs on your system.
|
||||
|
||||
**DLSS (NVIDIA GeForce RTX).** NVIDIA's DLSS SDK is not included: check out
|
||||
[github.com/NVIDIA/DLSS](https://github.com/NVIDIA/DLSS) (`include/` and `lib/Linux_x86_64` are
|
||||
enough) and build with `DLSS_SDK_ROOT=/path/to/DLSS bash build.sh`. That also builds the bridge
|
||||
`out/libbbport_dlss.so` (`gpu/dlss_bridge`, the only code that uses the SDK) and copies NVIDIA's
|
||||
`libnvidia-ngx-dlss.so.<version>` next to `out/bb-probe`; the driver provides the rest. Then pick
|
||||
*DLSS* in the launcher or the in-game menu: the preset sets the render size, Native AA is DLAA.
|
||||
DLSS gets the inputs FSR 3.1 gets (scene color, depth, motion vectors, jitter). Without the
|
||||
libraries, on other GPUs or with `BB_DLSS=0` it is listed as unavailable, and a DLSS setting falls
|
||||
back to FSR 3.1.
|
||||
### Button prompts
|
||||
|
||||
The AppImage built with `DLSS_SDK_ROOT` carries the bridge and NVIDIA's library (NVIDIA's RTX SDK
|
||||
license allows that in an application; its text is `NVIDIA-DLSS-LICENSE.txt` in the package). A
|
||||
newer `libnvidia-ngx-dlss.so.<version>` (from the SDK's `lib/Linux_x86_64/rel`) is chosen in the
|
||||
launcher: *Upscaler → DLSS: NVIDIA's library → Choose a file…*, as the FSR 4.1.1 DLL; it is
|
||||
copied to `<user>/dlss/`, which the game prefers. A game's `nvngx_dlss.dll` is the Windows
|
||||
library: NVIDIA's Linux driver does not load it.
|
||||
The game shows PlayStation buttons. In the launcher, **Controls → Button prompts** switches them to
|
||||
Xbox, Nintendo Switch or keyboard ones; *Automatic* follows the controller in use, and the
|
||||
keyboard's when none is connected. Keyboard prompts show the keys bound in **Controls →
|
||||
Keyboard**. **Gestures menu without motion-control icons** cleans up the gestures menu. The
|
||||
prompts are put into your own game's menu files at each start, and nothing in the game folder
|
||||
changes.
|
||||
|
||||
From source the script takes its tools from the system (MinGW GCC, CMake, Ninja, Python 3,
|
||||
SPIRV-Tools, Git) or from Nix; the AppImage has them prebuilt.
|
||||
## Support
|
||||
|
||||
**FP8 on RDNA4.** When vkd3d-proton offers FP8 cooperative matrices (RDNA4), the DLL runs
|
||||
another variant of its passes: other shaders and weights, other dispatch sizes. The build records
|
||||
both, INT8 into `fsr4_411/` and FP8 into `fsr4_411/fp8/`, and the game picks FP8 itself when the
|
||||
GPU has FP8 matrices (`VK_EXT_shader_float8`); the log says `Upscaler: FSR 4.1.1 replay, FP8 …`.
|
||||
`BB_FSR411_VARIANT=int8` forces INT8. Checked on an RX 7800 XT through vkd3d-proton's FP16
|
||||
emulation of FP8 (`BB_FSR4CAP_FP8=1` records it there too, `fp8emu/`): bit-exact with the DLL;
|
||||
**not yet run on RDNA4 hardware.**
|
||||
If this fork is useful to you and you would like to support it, you can on
|
||||
[Ko-fi](https://ko-fi.com/ayozetr). The port itself is deadinside28's work (see below).
|
||||
|
||||
**AppImage** (Steam Deck): `bash build.sh && bash packaging/appimage.sh` →
|
||||
`dist/Bloodborne-bbport-x86_64.AppImage`; data in `~/.local/share/bbport`, `--play` starts the
|
||||
game without the launcher window (Game Mode). FSR 4.1.1 models are not packaged: build them with
|
||||
the launcher's button (see above); they go to `~/.local/share/bbport/fsr4_411`
|
||||
(`BB_PACKAGE_FSR411=1` bundles a local `fsr4_411` into an AppImage for your own devices). On the Steam Deck pick the 1280×720 output (the
|
||||
game is 16:9; on the 1280×800 screen it gets thin bars).
|
||||
## Credits
|
||||
|
||||
**Adding the AppImage to Steam** (*Add a Non-Steam Game*) needs no options; the compatibility tool
|
||||
does not matter. (Steam preloads its overlay into every non-Steam game; the AppImage removes it
|
||||
before its own programs start, so the Steam overlay is not shown in the game.) Where Steam
|
||||
runs games without FUSE (NixOS: Steam's FHS sandbox; the AppImage then exits with *Cannot mount
|
||||
AppImage*), set the launch options to
|
||||
- [deadinside28/bloodborne_pc](https://github.com/deadinside28/bloodborne_pc): **bbport**, the
|
||||
native Linux port this fork is built on, and almost all of the work behind it. Its README, with
|
||||
the technical details, is in [BBPORT.md](BBPORT.md).
|
||||
- [Supermedo/bloodborne_pc](https://github.com/Supermedo/bloodborne_pc): the Windows port, whose
|
||||
online play, DLSS support, cheats page, Add to Steam and icon were brought to Linux here.
|
||||
- [shadPS4](https://github.com/shadps4-emu/shadPS4): the renderer and shader recompiler.
|
||||
- [shadp2p](https://github.com/Wozzardman/shadp2p) by Wozzardman: the network and PSN libraries
|
||||
for online play. Servers by the [shadPS4](https://shadps4.net) team and
|
||||
[The Hunter's Dream](https://thehuntersdream.com) by droogie and its team.
|
||||
- [IFreemz/shadPS4-Bloodborne-DLSS-FSR](https://github.com/IFreemz/shadPS4-Bloodborne-DLSS-FSR):
|
||||
the DLSS bridge, and the [NVIDIA DLSS SDK](https://github.com/NVIDIA/DLSS).
|
||||
- [FSR-Vulkan](https://github.com/FireBurn/FSR-Vulkan) by FireBurn and the AMD FidelityFX SDK.
|
||||
- **Dommo**: the Xbox and Switch button prompts (*DS3 Style - Modern Xbox prompts*, *DS3 Style -
|
||||
Nintendo Switch prompts*) and the *Stripped down gesture menu*, from
|
||||
[Nexus Mods (Bloodborne, mod 30)](https://www.nexusmods.com/bloodborne/mods/30), uploaded by
|
||||
goomab. Only their drawn prompts are in this repository ([assets/menu_prompts](assets/menu_prompts)).
|
||||
- **Abken**: the [KBM Icons Generator](https://www.nexusmods.com/bloodborne/mods/537), which showed
|
||||
which textures keyboard prompts need (after the idea of
|
||||
[mod 460](https://www.nexusmods.com/bloodborne/mods/460)); bbport draws its own.
|
||||
- [quick-sharun](https://github.com/pkgforge-dev/Anylinux-AppImages) for the AppImage.
|
||||
- Game patches by Kyo, Lance McDonald, auser1337, illusion, emoose and the Bloodborne community.
|
||||
|
||||
```
|
||||
TMPDIR=$HOME/.cache APPIMAGE_EXTRACT_AND_RUN=1 NO_CLEANUP=1 %command%
|
||||
```
|
||||
Full list of components and licenses: [BBPORT.md](BBPORT.md#credits-and-licenses).
|
||||
|
||||
The AppImage then unpacks itself (~2 GB, `~/.cache/appimage_extracted_*`) on the first start
|
||||
(~10 s) and reuses that copy afterwards; a new AppImage version gets a new copy, the old one can
|
||||
be deleted. Without `TMPDIR` it would unpack into Steam's `/tmp`, which is in RAM there. Add
|
||||
` --play` after `%command%` to skip the launcher.
|
||||
## License
|
||||
|
||||
**NVIDIA in the AppImage:** startup discovers the host's installed 64-bit NVIDIA Vulkan ICD
|
||||
and exposes its vendor libraries alongside the bundled AMD/Intel drivers. This keeps the
|
||||
NVIDIA userspace driver matched to the host kernel module. Standard Linux distributions keep
|
||||
these libraries under `/usr/lib*`; on NixOS the AppImage's internal `/nix/store` may hide them.
|
||||
In that case copy the NVIDIA libraries into an accessible directory and set
|
||||
`BB_NVIDIA_LIB_DIR=/path/to/libraries` (the NVIDIA manifest must also be accessible).
|
||||
With the host's NVIDIA driver the AppImage also passes the host's EGL vendor files, NVIDIA's
|
||||
first (`__EGL_VENDOR_LIBRARY_FILENAMES`; without them NVIDIA's ICD gave no `vkCreateInstance` on
|
||||
some hosts, #107; `BB_NVIDIA_EGL=0`: off). Explicit `VK_DRIVER_FILES`/`VK_ICD_FILENAMES` overrides are preserved. Diagnose drivers inside
|
||||
the package with:
|
||||
|
||||
```bash
|
||||
./Bloodborne-bbport-x86_64.AppImage --vulkan-info 2>&1 | tee bbport-vulkan.log
|
||||
```
|
||||
|
||||
A user reported successful startup with FSR 3 on a GTX 1060 6GB (Fedora 44, NVIDIA
|
||||
580.178.04); selecting FSR 4 caused a black window. Use FSR 3 on this configuration.
|
||||
|
||||
MangoHud is bundled in the AppImage; enable its checkbox in the launcher. If MangoHud is also
|
||||
installed system-wide, or Steam's performance overlay is on (Steam Deck game mode), only one
|
||||
overlay is drawn (two drew doubled, offset text).
|
||||
When running from source, install MangoHud separately. A diagnostic launch with
|
||||
`VK_LOADER_LAYERS_DISABLE=~implicit~` also disables MangoHud.
|
||||
|
||||
Useful variables: `BB_FRAME_STATS=1` (frame statistics, including a `Memory:` line: VRAM, GTT,
|
||||
RSS, images and guest blocks in VRAM), `BB_PC_MODEL=1` (the new memory and translation model,
|
||||
0, the old model as in 0.3, is the default), `BB_ANISO=N` (anisotropic filtering of scene textures; 16 by
|
||||
default, 0 = the game's own),
|
||||
`BB_GC_IDLE_SECONDS=N` / `BB_VRAM_IDLE_SECONDS=N` (how long unused textures / buffers stay in VRAM;
|
||||
20 / 60), `BB_BREADCRUMBS=0` (no GPU breadcrumbs; with them a GPU hang names the draw or dispatch
|
||||
it is stuck in), `BB_GPU_PROFILE=1` (GPU time per
|
||||
pass), `BB_FSR4_PROFILE=1` (GPU time per FSR 4 pass), `BB_UPSCALER=taa|fsr3|fsr4|fsr411|off|none`,
|
||||
`BB_FRAMES_AHEAD=N` (how many frames the GPU command thread may run ahead of the GPU; 1 by default,
|
||||
0 = unbounded), `BB_PRESENT_THREAD=0` (present on the vblank thread, as before),
|
||||
`BB_LIVE_RES=1` (live resolution changes instead of the startup patch for outputs other than 1080p),
|
||||
`BB_PAD_RECORD=file` / `BB_PAD_REPLAY=file` (record a route with F9, replay it in scripted tests),
|
||||
`BB_GC_BUDGET_MB=N` (texture cache budget, as on integrated GPUs), `BB_VRAM_LIMIT_MB=N` (the VRAM
|
||||
budget taken as at most N MiB: checks the behaviour of cards with little VRAM), `BB_ASYNC_PIPELINES=0`
|
||||
(new pipelines compile at once and the game waits; by default in the background), `BB_PRESENT_DUMP_TRIGGER=file`
|
||||
with `BB_PRESENT_DUMP_COUNT=N` (dump N consecutive presented frames),
|
||||
`BB_FSR411_VARIANT=int8|fp8|fp8emu` (FSR 4.1.1 variant; by default FP8 where the GPU has FP8
|
||||
matrices), `BB_READBACKS=0|1|2` (reads of GPU-written memory by the game: 1 by default, 2
|
||||
precise and slow, 0 off).
|
||||
More in [docs/](docs); recent changes: [docs/CHANGES_0.4.md](docs/CHANGES_0.4.md) (in Russian),
|
||||
[docs/CHANGES_2026-10-06.md](docs/CHANGES_2026-10-06.md).
|
||||
|
||||
**Online play (shadNet).** A separate module, `out/gpu/libbbnet.so` (`gpu/bbnet`: shadPS4's network
|
||||
and PSN libraries from shadp2p, a shadPS4 fork, with the shadNet client), built when protobuf,
|
||||
nlohmann_json, OpenSSL, zlib and miniupnpc are there (`shell.nix`) and included in the AppImage.
|
||||
The launcher's *Online* page turns it on (`BB_ONLINE=1`): messages, bloodstains and ghosts come
|
||||
from The Hunter's Dream, bells and summons go through a shadNet server (`srv.shadps4.net:31313` by
|
||||
default) with a shadNet account (Online ID and password). Offline the module is never loaded; a
|
||||
build without it plays offline only.
|
||||
|
||||
## Repository layout
|
||||
|
||||
| Path | Contents |
|
||||
|---|---|
|
||||
| `src/` | Loader (`probe.c`) and the HLE runtime |
|
||||
| `scripts/` | Offline preparation of the game image, module linking, patch compiler |
|
||||
| `gpu/` | Renderer library: vendored shadPS4 video core with this port's changes (`gpu/VENDOR.txt`), shims, ImGui menu, FSR 4.1.1 runtime (`gpu/shadps4/video_core/renderer_vulkan/fsr411`) |
|
||||
| `launcher/`, `packaging/` | GTK4 launcher; Nix package and AppImage |
|
||||
| `patches/` | Community patches for Bloodborne |
|
||||
| `tools/` | Developer tools: scripted runs, A/B toggles, FSR benchmark helpers, FSR 4 shader rewrites, `fsr4cap` (FSR 4.1.1 recording/extraction) |
|
||||
| `tests/` | Loader, runtime, patch and renderer tests |
|
||||
| `docs/` | Design notes and measurements ([upscaler](docs/upscaler.md), [parallel GPU](docs/parallel_gpu.md), [motion vectors](docs/motion_vectors.md), [roadmap](docs/ROADMAP.md)) |
|
||||
|
||||
Tests: `bash build.sh --test`, `python3 -m unittest discover -s tests`, and
|
||||
`ninja -C out/gpu motion-history-test ui-composition-test scene-resolution-test motion-shader-test settings-test`.
|
||||
|
||||
## Roadmap
|
||||
|
||||
- The new memory and translation model on by default on NVIDIA too, once it is as fast there;
|
||||
the old model is removed after that.
|
||||
- Textures as objects created at load time (less guessing by address, uploads outside the
|
||||
frame).
|
||||
- Shaders translated ahead of time, at install, not during play.
|
||||
- More CPU parallelism in GPU command processing (split the draw-recording stage further),
|
||||
scaling to all hardware threads — most important for the Steam Deck.
|
||||
- Async compute for the upscaler (the frame is GPU-bound at 4K).
|
||||
- XeSS (super resolution) and XeFG frame generation through a Wine helper sharing Vulkan
|
||||
memory (a memory-bridge prototype is in `tools/bridge_helper`); inputs exposed so that OptiScaler-style mapping works.
|
||||
- Frame generation (FSR 3.1 FG first), reactive and transparency masks for particles and fog.
|
||||
- Fix the races in AMD's FSR 4.1.1 shaders at output widths that are not multiples of 64
|
||||
(e.g. 1600×900), as already done for the left-edge race in FSR 4 v07 at 1080p.
|
||||
- Steam Deck validation of the AppImage; HDR output.
|
||||
|
||||
## Credits and licenses
|
||||
|
||||
bbport is licensed under the **GNU GPL v2 or later** ([LICENSE](LICENSE)) — it contains code
|
||||
from shadPS4 (GPL-2.0-or-later). Third-party components keep their licenses:
|
||||
[shadPS4](https://github.com/shadps4-emu/shadPS4) video core and shader recompiler (GPL-2.0+),
|
||||
[sirit](https://github.com/shadps4-emu/sirit), [half](https://half.sourceforge.net/),
|
||||
[FSR-Vulkan](https://github.com/FireBurn/FSR-Vulkan) by FireBurn (MIT; FSR 3.1 on Vulkan and the
|
||||
FSR 4 v07 provider), AMD FidelityFX SDK (MIT), [LibAtrac9](https://github.com/Thealexbarney/LibAtrac9)
|
||||
(MIT), [Dear ImGui](https://github.com/ocornut/imgui) (MIT), DejaVu fonts,
|
||||
[dxil-spirv](https://github.com/HansKristian-Work/dxil-spirv) (MIT, used to build the
|
||||
FSR 4.1.1 assets). Game patches by Kyo, Lance McDonald, auser1337, illusion, emoose and other
|
||||
community members (`patches/Bloodborne.xml`). The DLSS bridge (`gpu/dlss_bridge`, MIT) and
|
||||
loader come from [Supermedo's Windows port](https://github.com/Supermedo/bloodborne_pc), adapted
|
||||
from [IFreemz/shadPS4-Bloodborne-DLSS-FSR](https://github.com/IFreemz/shadPS4-Bloodborne-DLSS-FSR).
|
||||
AMD's FSR 4 DLLs and model data and NVIDIA's DLSS SDK and libraries are not distributed here.
|
||||
GNU GPL v2 or later ([LICENSE](LICENSE)). Third-party components keep their own licenses. NVIDIA
|
||||
and DLSS are trademarks of NVIDIA Corporation.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Button prompts and gestures menu
|
||||
|
||||
By **Dommo**, from [Nexus Mods: Bloodborne, mod 30](https://www.nexusmods.com/bloodborne/mods/30)
|
||||
(uploaded by goomab): *DS3 Style - Modern Xbox prompts*, *DS3 Style - Nintendo Switch prompts* and
|
||||
*Stripped down gesture menu*. The mod page allows using its assets and uploading them elsewhere
|
||||
with credit to the author, but not in anything sold.
|
||||
|
||||
Only what the mods drew is here, never the game's own files:
|
||||
|
||||
- `xbox/`, `switch/`: the 20 button icons (`KG_*.dds`, BC7 exactly as in the mods) and the strip
|
||||
of big prompts from the shared UI atlas (`atlas.dds`, scaled down to the game's atlas size).
|
||||
- `gesturetop.json`: the edits that turn the game's `menu/gesturetop.gfx` into the mod's.
|
||||
|
||||
`scripts/menu_prompts.py` puts them into the player's own `menu/common.tpf.dcx` and
|
||||
`menu/gesturetop.gfx` at each start; `tools/extract_prompt_assets.py` rebuilds this folder from
|
||||
the mods' ZIPs. Keyboard prompts are drawn by `scripts/menu_prompts.py` itself.
|
||||
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"original_sha256": "5d7bae1451d97296e88a6add62b1fd9d1425901acc5c907e90f52354bc0b624e",
|
||||
"result_sha256": "c77f7398c02613bd3d1a9613ce773fa8b6b74143053c6092b52a3c8d959dd868",
|
||||
"edits": [
|
||||
[
|
||||
4,
|
||||
1,
|
||||
"2f"
|
||||
],
|
||||
[
|
||||
2548,
|
||||
1,
|
||||
"18"
|
||||
],
|
||||
[
|
||||
2556,
|
||||
1,
|
||||
"8e"
|
||||
],
|
||||
[
|
||||
2563,
|
||||
0,
|
||||
"c9"
|
||||
],
|
||||
[
|
||||
2564,
|
||||
0,
|
||||
"0034bca0480238"
|
||||
],
|
||||
[
|
||||
3189,
|
||||
1,
|
||||
"92"
|
||||
],
|
||||
[
|
||||
3229,
|
||||
1,
|
||||
"92"
|
||||
],
|
||||
[
|
||||
3242,
|
||||
5,
|
||||
"207b2673a5e0"
|
||||
],
|
||||
[
|
||||
3268,
|
||||
1,
|
||||
"98"
|
||||
],
|
||||
[
|
||||
3281,
|
||||
5,
|
||||
"2077b30a7b80"
|
||||
],
|
||||
[
|
||||
3519,
|
||||
1,
|
||||
"41"
|
||||
],
|
||||
[
|
||||
3529,
|
||||
1,
|
||||
"33"
|
||||
],
|
||||
[
|
||||
3539,
|
||||
5,
|
||||
"c900004000207d11920000"
|
||||
],
|
||||
[
|
||||
5795,
|
||||
1,
|
||||
"ab"
|
||||
],
|
||||
[
|
||||
5817,
|
||||
1,
|
||||
"1a"
|
||||
],
|
||||
[
|
||||
5826,
|
||||
5,
|
||||
"c900004000207a015deba0"
|
||||
],
|
||||
[
|
||||
6076,
|
||||
12,
|
||||
""
|
||||
]
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,48 @@
|
||||
# Game compatibility
|
||||
|
||||
bbport runs Bloodborne for PS4 with **update 1.09**. Its hooks and patches use the addresses of
|
||||
one executable: the launcher hashes the loaded `eboot.bin` image and runs only that one 1.09
|
||||
executable (`scripts/game_check.py`). A dump that passes the check runs; one that does not is
|
||||
told why (no 1.09 update, another executable, or an `eboot.bin` with Lance McDonald's 60 fps
|
||||
patch baked in).
|
||||
|
||||
## Releases the launcher accepts
|
||||
|
||||
Every retail release of Bloodborne. Title and content IDs as listed in
|
||||
[deadinside28/bloodborne_pc#117](https://github.com/deadinside28/bloodborne_pc/pull/117).
|
||||
|
||||
| Edition | Region | Title ID | Content ID | 1.09 executable checked |
|
||||
|---|---|---|---|---|
|
||||
| Bloodborne | US | `CUSA00900` | `UP9000-CUSA00900_00-BLOODBORNE000000` | Yes |
|
||||
| Bloodborne | Europe | `CUSA00207` | `EP9000-CUSA00207_00-BLOODBORNE0000EU` | Not yet |
|
||||
| Bloodborne | UK | `CUSA00208` | `EP9000-CUSA00208_00-BLOODBORNE0000UK` | Not yet |
|
||||
| Bloodborne | Japan | `CUSA00299` | `JP9000-CUSA00299_00-BLOODBORNE0000JP` | Not yet |
|
||||
| Bloodborne | Asia | `CUSA01363` | `HP9000-CUSA01363_00-BLOODBORNE0000AS` | Not yet |
|
||||
| The Old Hunters Edition | US | `CUSA03179` | `UP9000-CUSA03179_00-BLOODBORNE000000` | Not yet |
|
||||
| Game of the Year Edition | Europe | `CUSA03173` | `EP9000-CUSA03173_00-BLOODBORNE0000EU` | Yes |
|
||||
| The Old Hunters Edition | Japan | `CUSA03014` | `JP9000-CUSA03014_00-BLOODBORNE0000JP` | Not yet |
|
||||
| The Old Hunters Edition | Asia | `CUSA03023` | `HP9000-CUSA03023_00-BLOODBORNE0000AS` | Not yet |
|
||||
|
||||
*Checked* means a dump of that release with update 1.09 had exactly the executable bbport runs.
|
||||
The others are accepted on the expectation that every release shares the same 1.09 executable;
|
||||
if one does not, the launcher says so instead of starting a game that would crash. A report of
|
||||
how yours goes is welcome either way.
|
||||
|
||||
The alpha tests (`CUSA01318`, `CUSA01322`, `CUSA01333`, `CUSA01335`) are not supported.
|
||||
|
||||
The Game of the Year and The Old Hunters editions include the DLC. With a base game, dump the
|
||||
add-ons too and choose their folder in the launcher (see the README's *DLC* section).
|
||||
|
||||
## Tested with this fork
|
||||
|
||||
On Linux (CachyOS, NVIDIA GeForce RTX 3060), with the US release (`CUSA00900`):
|
||||
|
||||
- The base game with update 1.09 and the add-ons dumped separately (The Old Hunters and the two
|
||||
messenger add-ons), from the launcher's DLC folder.
|
||||
- A dump whose `eboot.bin` had Lance McDonald's 60 fps patch baked in: the launcher reports it,
|
||||
and the clean 1.09 `eboot.bin` runs.
|
||||
- Both regional texts through *Game region*: Spanish from Spain (Europe) and Latin American
|
||||
Spanish (the Americas).
|
||||
|
||||
The European Game of the Year edition (`CUSA03173`) is the release the original bbport is
|
||||
developed and tested with.
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
# Mods and third-party patches
|
||||
|
||||
Both are on the launcher's **Settings** page and apply at the next start. The game folder itself
|
||||
is never changed.
|
||||
|
||||
## File mods
|
||||
|
||||
The **Mods** group has the folder, a **Load mods** switch, and the list of mods: each one can be
|
||||
turned on or off and moved up or down. The lowest mod that is on wins: when two mods replace the
|
||||
same file, the log says which one replaced the other.
|
||||
|
||||
The mods folder is `~/.local/share/bbport/mods/` with the AppImage (`mods/` in the data
|
||||
directory when running from the repository). Extract each mod into a folder of its own:
|
||||
|
||||
```text
|
||||
mods/
|
||||
Mod name/
|
||||
dvdroot_ps4/
|
||||
chr/...
|
||||
param/...
|
||||
menu/...
|
||||
```
|
||||
|
||||
These layouts work as well:
|
||||
|
||||
- `Mod name/app0/dvdroot_ps4/...`, and `Mod name/<title ID>/dvdroot_ps4/...` with whichever
|
||||
release the mod was made for (`CUSA03173`, `CUSA00900`, ...).
|
||||
- One extra wrapper folder, as extracting an archive into a folder of its name leaves it:
|
||||
`Mod name/Mod name v1.2/dvdroot_ps4/...` (readme files and pictures may sit next to it).
|
||||
- The game's folders without `dvdroot_ps4`: `Mod name/chr/...`, `Mod name/parts/...` (`chr`,
|
||||
`parts`, `map`, `menu`, `msg`, `param`, `sfx`, `sound`, `font` and the other folders of
|
||||
`dvdroot_ps4`).
|
||||
|
||||
Upper or lower case does not matter: a mod made on Windows with
|
||||
`DVDROOT_PS4/Chr/C0000.chrbnd.dcx` replaces the game's `dvdroot_ps4/chr/c0000.chrbnd.dcx`.
|
||||
The log shows how many game files the mods replaced and added, e.g.
|
||||
`Mods: 12 game files replaced, 0 added`. A mod meant to replace files that says
|
||||
`0 game files replaced` has its files in the wrong place.
|
||||
|
||||
What works: ready-made replacements of `dvdroot_ps4` files (textures, models, params,
|
||||
interface, sound, fonts). What does not:
|
||||
|
||||
- ZIP or 7z archives: extract them first.
|
||||
- Two mods changing different parts of the same `.dcx` or `.bnd` file: the last one replaces
|
||||
it whole, the files are not merged.
|
||||
- Mods that replace `eboot.bin`, `sce_module` or `sce_sys`, DLL injectors and script plugins.
|
||||
Changes to the game's code are patches (below).
|
||||
- Symbolic links inside a mod.
|
||||
|
||||
Whether a mod works with Bloodborne 1.09 is up to its author.
|
||||
|
||||
A shadPS4-style folder next to the game folder, `<game folder>-mods/dvdroot_ps4/...` (for
|
||||
example `CUSA03173-mods` next to `CUSA03173`), is loaded too, before the mods in the list. You
|
||||
can also choose a folder that holds `dvdroot_ps4` directly as the mods folder: it is then loaded
|
||||
as one mod. **Load mods** turns all of them off. The launcher's button prompts and gestures
|
||||
menu work the same way, as files laid under the mods; they are settings of their own and stay
|
||||
on when mods are off.
|
||||
|
||||
### How it works
|
||||
|
||||
For each start, the data directory gets a merged tree of links, `out/mod-game-*`: the game sees
|
||||
the mods' files where they replace its own and the original files everywhere else. `/app0` is
|
||||
read-only, and saves go to the usual folder. The tree is removed when the game exits normally
|
||||
or the launcher stops it; one left behind by a crash is not reused.
|
||||
|
||||
The order and the mods turned off are kept in `mods.json` in the data directory:
|
||||
|
||||
```json
|
||||
{"order": ["First", "Second"], "disabled": ["First"]}
|
||||
```
|
||||
|
||||
New folders are turned on and added at the end, in alphabetical order.
|
||||
|
||||
From a terminal:
|
||||
|
||||
```sh
|
||||
BB_GAME_DIR=/path/to/game BB_MODS_DIR=/path/to/mods bash run.sh
|
||||
BB_GAME_DIR=/path/to/game BB_MODS_ENABLED=0 bash run.sh
|
||||
```
|
||||
|
||||
`BB_MODS_CONFIG` sets another `mods.json`. `BB_MOD_TRACE=1` (with `BB_MODS_DIR`) logs the
|
||||
first reads of files that come from mods, to see whether a mod is being used.
|
||||
|
||||
## Third-party patches
|
||||
|
||||
The **Third-party patches** group loads patches in the shadPS4/GoldHEN XML format from the
|
||||
patches folder: `~/.local/share/bbport/patches/` with the AppImage, or another folder chosen in
|
||||
the launcher.
|
||||
|
||||
- Only patches for version `01.09` of `eboot.bin` are listed. Files for other games are
|
||||
skipped: a file applies when it lists any retail release of Bloodborne, or none.
|
||||
- Each patch starts on or off as the file says (`isEnabled`); the choice in the launcher is
|
||||
kept in `patches.json` in the data directory:
|
||||
|
||||
```json
|
||||
{"enabled": ["file.xml/Patch name"], "disabled": ["file.xml/Other patch"]}
|
||||
```
|
||||
|
||||
- Lines of type `bytes`, `bytes16`, `bytes32`, `bytes64`, `float32`, `float64`, `utf8` and
|
||||
`utf16` work. A patch with a `mask` line (a pattern search) or an address outside the
|
||||
executable is skipped whole, and the log says so.
|
||||
- They apply after bbport's own patches (`patches/Bloodborne.xml`), so where both change the
|
||||
same bytes, the third-party patch wins.
|
||||
- Patches that replace pointers in tables, such as *60 FPS++*, are moved to where the game is
|
||||
loaded.
|
||||
|
||||
From a terminal: `BB_PATCHES_DIR=/path BB_PATCHES_CONFIG=/path/patches.json bash run.sh`.
|
||||
|
||||
This guide is based on bbport's [MODS.md](MODS.md) (Russian), by deadinside28.
|
||||
@@ -0,0 +1,77 @@
|
||||
# Online play and co-op
|
||||
|
||||
Online play is a beta. Messages, bloodstains and ghosts come from the community game server,
|
||||
[The Hunter's Dream](https://thehuntersdream.com). Bells and summons go through a shadNet server,
|
||||
and then the players' games connect to each other directly. Linux and Windows players can play
|
||||
together: everyone needs the game at version 1.09 and the same servers.
|
||||
|
||||
> **Co-op does not connect through shadPS4's public server.** It does not answer the UDP ping
|
||||
> that tells it each player's address, so a summon times out
|
||||
> ([deadinside28/bloodborne_pc#121](https://github.com/deadinside28/bloodborne_pc/issues/121));
|
||||
> the shadPS4 team does not support forks. Messages, bloodstains and ghosts work. For co-op, a
|
||||
> group runs its own server: [SERVER.md](SERVER.md). *Test the connection* tells which case
|
||||
> yours is: the shadNet server's UDP line is red on the public server.
|
||||
|
||||
## Account
|
||||
|
||||
1. On shadPS4's public server, register at [shadnet.shadps4.net](https://shadnet.shadps4.net).
|
||||
On your own server, its owner creates the account ([SERVER.md](SERVER.md)).
|
||||
2. In the launcher's **Online** page, turn on **Play online**, choose the **shadNet server**
|
||||
(shadPS4's public one or your own, with its address), and enter the **account name** (not the
|
||||
email) and the password. The password is kept on this computer with the launcher's settings.
|
||||
3. **Test the connection**: the game server, the shadNet server, its UDP port and its WebAPI
|
||||
should all answer, and with the account filled in, the sign-in should work. A wrong name or
|
||||
password shows there (the server does not say which of the two).
|
||||
|
||||
If the game says you are not signed in to PlayStation Network, the shadNet login failed: the log
|
||||
then has a shadNet login error (code 8 is a wrong name or password). If the server did not answer
|
||||
when the game started, the game keeps trying in the background.
|
||||
|
||||
## Summoning in the game
|
||||
|
||||
- The host rings the **Beckoning Bell** (one Insight each time); the helper rings the **Small
|
||||
Resonant Bell**.
|
||||
- Use the same **password** in the game's network settings, so you find each other whatever
|
||||
your levels.
|
||||
- Be in the same area, close to each other, with the area's boss still alive in the host's
|
||||
world.
|
||||
|
||||
## Ports
|
||||
|
||||
Co-op traffic is UDP, straight between the players' computers. With your own server on
|
||||
Tailscale ([SERVER.md](SERVER.md)) it goes through Tailscale: turn **UPnP** off and skip this
|
||||
section. Otherwise each player has to be reachable from the Internet:
|
||||
|
||||
1. **The router.** With **UPnP** on (the default), the game opens its port on the router itself:
|
||||
the log says `UPNP: mapped UDP port <port>`. A router without UPnP needs a port forward by
|
||||
hand (UDP, the pinned port below, to your computer's LAN address).
|
||||
2. **Pin the port** when you open it by hand or in a firewall: **Co-op port (UDP)** in the
|
||||
Online page, for example 3658 (0, the default, is a random port each time). The log then says
|
||||
`P2P transport ready: UDP physical_port=3658`.
|
||||
3. **Open it in the firewall.** UPnP opens the port on the router only, not in this computer's
|
||||
firewall. With ufw or firewalld on, the launcher's **Online** page says
|
||||
so and gives the commands. For ufw:
|
||||
```sh
|
||||
sudo ufw allow 3658/udp
|
||||
sudo ufw allow from <router IP> proto udp # the router's UPnP answers
|
||||
```
|
||||
On Windows, in a terminal as administrator:
|
||||
```
|
||||
netsh advfirewall firewall add rule name="Bloodborne co-op" dir=in action=allow protocol=UDP localport=3658
|
||||
```
|
||||
|
||||
A router whose public address is shared by many customers (CG-NAT) cannot forward ports: the
|
||||
log's `UPNP: IGD found - external IP=...` should be your real public address. Tailscale works
|
||||
behind CG-NAT too.
|
||||
|
||||
## Reading the log
|
||||
|
||||
Run with `BB_GPU_LOG=info` in the extra variables for the online details. In a summon:
|
||||
|
||||
| Line | Meaning |
|
||||
|---|---|
|
||||
| `Logged in npid='...'` | The shadNet login worked. |
|
||||
| `summon_messenger/create` | Your sign is placed (helper). |
|
||||
| `summon_messenger/get`, `sceNpMatching2CreateJoinRoom`, `summon_messenger/request` | The host found the sign, made the room and sent the summon. |
|
||||
| `peer '...' endpoint unresolved` | shadNet has no address for the other player: its UDP port did not answer (the public server, above). |
|
||||
| `event=0(DEAD)` | The connection gave up. |
|
||||
@@ -0,0 +1,40 @@
|
||||
# Documentation
|
||||
|
||||
## For players
|
||||
|
||||
- [COMPATIBILITY.md](COMPATIBILITY.md): which releases of Bloodborne run, and which have been
|
||||
checked.
|
||||
- [MODS.en.md](MODS.en.md): file mods and third-party patches.
|
||||
- [ONLINE.md](ONLINE.md): online play and co-op: the account, the bells, the ports.
|
||||
- [SERVER.md](SERVER.md): hosting your own co-op server (shadnet-p2p) for a group of friends.
|
||||
- [TROUBLESHOOTING.md](TROUBLESHOOTING.md): common problems and what to do.
|
||||
|
||||
## For this fork
|
||||
|
||||
- [RELEASE_TEMPLATE.md](RELEASE_TEMPLATE.md): the format of every release's notes.
|
||||
|
||||
## bbport's development notes
|
||||
|
||||
Everything else here comes from [bbport](https://github.com/deadinside28/bloodborne_pc) by
|
||||
deadinside28, kept as it is so that its updates merge cleanly. Many are in Russian (`.ru.md`, or
|
||||
a Russian title).
|
||||
|
||||
- **What changed in each version:** [CHANGES_0.5.md](CHANGES_0.5.md),
|
||||
[CHANGES_0.5-pre4.md](CHANGES_0.5-pre4.md), [CHANGES_0.5-pre3.md](CHANGES_0.5-pre3.md),
|
||||
[CHANGES_0.5-pre2.md](CHANGES_0.5-pre2.md), [CHANGES_after_0.4.md](CHANGES_after_0.4.md),
|
||||
[CHANGES_0.4.md](CHANGES_0.4.md), [CHANGES_2026-10-06.md](CHANGES_2026-10-06.md) (0.3),
|
||||
[CHANGES_2026-10-03.md](CHANGES_2026-10-03.md), [CHANGES_2026-10-02.md](CHANGES_2026-10-02.md).
|
||||
- **Where the port is going:** [ROADMAP.md](ROADMAP.md),
|
||||
[EMULATION_REMOVAL_PLAN.ru.md](EMULATION_REMOVAL_PLAN.ru.md),
|
||||
[TRANSLATOR_ARCHITECTURE.ru.md](TRANSLATOR_ARCHITECTURE.ru.md).
|
||||
- **Memory and GPU commands:** [MEMORY_MODULE_PLAN.ru.md](MEMORY_MODULE_PLAN.ru.md),
|
||||
[NVIDIA_MEMORY_MODEL.ru.md](NVIDIA_MEMORY_MODEL.ru.md),
|
||||
[TRANSLATION_GPU_COPY_LABELS.ru.md](TRANSLATION_GPU_COPY_LABELS.ru.md),
|
||||
[parallel_gpu.md](parallel_gpu.md).
|
||||
- **Upscaling, TAA and motion vectors:** [upscaler.md](upscaler.md),
|
||||
[DEPTH_ADAPTIVE_TAA.md](DEPTH_ADAPTIVE_TAA.md),
|
||||
[TEMPORAL_REVIEW_2026-10-01.md](TEMPORAL_REVIEW_2026-10-01.md),
|
||||
[motion_vectors.md](motion_vectors.md).
|
||||
- **Mods, patches and TAA sharpness** (the original of MODS.en.md): [MODS.md](MODS.md).
|
||||
- **The first experiments:** [DEVELOPMENT_LOG.ru.md](DEVELOPMENT_LOG.ru.md), and the session
|
||||
notes in [history/](history/).
|
||||
@@ -0,0 +1,46 @@
|
||||
# Release notes template
|
||||
|
||||
Every release uses this format: same sections, same order. A section with nothing to say keeps
|
||||
its heading and says "None.". Notes are in English. "New", "Improved" and "Fixed" cover the
|
||||
changes since the previous release. Third-party work is credited in the line that uses it.
|
||||
|
||||
- Tag: `<upstream bbport version>-linux.<n>` (e.g. `0.4-linux.1`).
|
||||
- Title: `bbport Linux <tag>`.
|
||||
- Asset: `dist/Bloodborne-bbport-x86_64.AppImage` (`bash build.sh && bash packaging/appimage-sharun.sh`).
|
||||
|
||||
```markdown
|
||||
<One or two sentences: what this release is and which upstream bbport it is based on.>
|
||||
|
||||
**Requirements:** your own decrypted dump of Bloodborne with update 1.09, any retail release (US,
|
||||
Europe, UK, Japan, Asia, and the editions with The Old Hunters). No game files are included.
|
||||
Linux x86-64 and a Vulkan 1.3 GPU.
|
||||
|
||||
## Download
|
||||
|
||||
| File | For |
|
||||
|---|---|
|
||||
| `Bloodborne-bbport-x86_64.AppImage` | Any x86-64 distribution and the Steam Deck |
|
||||
|
||||
Make it executable, run it, choose your game folder and press **Play**. Saves and settings go
|
||||
to `~/.local/share/bbport`.
|
||||
|
||||
SHA-256: `<sha256sum of the AppImage>`
|
||||
|
||||
## New
|
||||
- **<Feature>**: <what it does>.
|
||||
|
||||
## Improved
|
||||
- <Change to something that existed>.
|
||||
|
||||
## Fixed
|
||||
- <Bug fixed>.
|
||||
|
||||
## Upgrading
|
||||
- <What someone coming from the previous release has to do, or "None.">
|
||||
|
||||
## Known issues
|
||||
- <Limitations and known bugs.>
|
||||
|
||||
**Full changes:** https://github.com/ayozetr/bloodborne_linux/compare/<previous tag>...<tag>
|
||||
**Credits and licenses:** https://github.com/ayozetr/bloodborne_linux#credits
|
||||
```
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
# Hosting your own co-op server
|
||||
|
||||
Bells and summons go through a shadNet server. Players who connect through shadPS4's public one
|
||||
(`srv.shadps4.net`) get no co-op at the moment: it does not answer on its UDP port, so a summon
|
||||
never connects ([deadinside28/bloodborne_pc#121](https://github.com/deadinside28/bloodborne_pc/issues/121)).
|
||||
A group of friends can run their own server instead:
|
||||
[shadnet-p2p](https://github.com/Wozzardman/shadnet-p2p) by Wozzardman, the shadNet server with
|
||||
that UDP port working and a summon service of its own.
|
||||
|
||||
Messages, bloodstains and ghosts still come from The Hunter's Dream, whichever shadNet server you
|
||||
use. Windows players of [Supermedo's port](https://github.com/Supermedo/bloodborne_pc) can join
|
||||
the same server.
|
||||
|
||||
Tested with two players, one on this Linux port and one on Supermedo's Windows port, over
|
||||
Tailscale, with the server below on a home server (Ubuntu 26.04): summons, a boss fight together
|
||||
and summoning again after the helper died.
|
||||
|
||||
## How it fits together
|
||||
|
||||
- **One always-on computer runs the server:** a VPS, a home server, or one of the players' PCs
|
||||
(then it has to be on while you play).
|
||||
- **Everyone joins one [Tailscale](https://tailscale.com) network** (a *tailnet*): the server and
|
||||
every player. Tailscale gives each computer an address starting with `100.` and encrypts the
|
||||
traffic between them. This matters: shadNet's login on TCP 31313 is not encrypted, so it should
|
||||
not be open to the Internet. And the games then reach each other over Tailscale too, so nobody
|
||||
has to open ports on their router.
|
||||
|
||||
| Protocol | Port | Used for |
|
||||
|---|---|---|
|
||||
| TCP | 31313 | Login and matchmaking |
|
||||
| UDP | 31314 | Each player's address, for the other player of a summon |
|
||||
| TCP | 31315 | WebAPI and the summon service |
|
||||
|
||||
## 1. Tailscale
|
||||
|
||||
On the server and on every player's computer, install Tailscale
|
||||
([download](https://tailscale.com/download); on Linux `curl -fsSL https://tailscale.com/install.sh | sh`)
|
||||
and sign in:
|
||||
|
||||
```sh
|
||||
sudo tailscale up
|
||||
```
|
||||
|
||||
The server's owner invites the players from the
|
||||
[admin console](https://login.tailscale.com/admin/users) (**Users → Invite external users**),
|
||||
and each player accepts the invitation and signs in on the computer they play on. Then, on the
|
||||
server:
|
||||
|
||||
```sh
|
||||
tailscale ip -4 # the server's address, 100.x.y.z: the players need it
|
||||
tailscale status # every player's computer should be listed
|
||||
```
|
||||
|
||||
## 2. The server
|
||||
|
||||
### With Docker (any Linux)
|
||||
|
||||
This repository has a Docker image for it: [tools/shadnet-server](../tools/shadnet-server). It
|
||||
builds shadnet-p2p on Debian 13, because shadnet-p2p needs Qt 6.8 or newer and some
|
||||
distributions are older (Ubuntu 24.04 has Qt 6.4). It runs on any Linux with Docker, Ubuntu 24.04
|
||||
included.
|
||||
|
||||
```sh
|
||||
mkdir shadnet-server && cd shadnet-server
|
||||
for f in Dockerfile entrypoint.sh; do
|
||||
curl -fsSLO "https://raw.githubusercontent.com/ayozetr/bloodborne_linux/main/tools/shadnet-server/$f"
|
||||
done
|
||||
docker build -t bbport-shadnet .
|
||||
```
|
||||
|
||||
Then start it, with the server's Tailscale address and a signup code of your own (letters,
|
||||
digits, `-` and `_`; the players never need it, only you, to create their accounts):
|
||||
|
||||
```sh
|
||||
docker run -d --name shadnet --restart unless-stopped --network host \
|
||||
-v shadnet-data:/data \
|
||||
-e SHADNET_HOST=100.x.y.z \
|
||||
-e SHADNET_REGISTRATION_KEY=a-long-code-of-your-own \
|
||||
bbport-shadnet
|
||||
docker logs shadnet
|
||||
```
|
||||
|
||||
The log should end with the three listeners on the Tailscale address:
|
||||
|
||||
```text
|
||||
Unsecured TCP listener on: 100.x.y.z:31313. No encryption, use it only on trusted networks
|
||||
STUN UDP listener on: 100.x.y.z:31314
|
||||
WebApiServer listening on: 100.x.y.z:31315
|
||||
```
|
||||
|
||||
`SHADNET_HOST` keeps the server off the computer's other networks. After a reboot, it waits until
|
||||
Tailscale is up. The configuration (`shadnet.cfg`) and the accounts (`db/shadnet.db`) live in the
|
||||
`shadnet-data` volume. To stop or update the server:
|
||||
|
||||
```sh
|
||||
docker stop shadnet && docker rm shadnet # the volume, with the accounts, stays
|
||||
docker build -t bbport-shadnet . # after downloading a newer Dockerfile
|
||||
```
|
||||
|
||||
Then run the same `docker run` again.
|
||||
|
||||
### Without Docker
|
||||
|
||||
Follow [shadnet-p2p's README](https://github.com/Wozzardman/shadnet-p2p#readme). It needs Qt 6.8
|
||||
or newer: Arch Linux and its derivatives, Debian 13, Ubuntu 26.04, Fedora 41 or newer. On Debian
|
||||
and Ubuntu, install `qt6-websockets-dev` along with the packages it lists, or CMake does not find
|
||||
Qt's HTTP server module (checked on Debian 13 and Ubuntu 26.04). In `build/shadnet.cfg`, set `Host` to the server's Tailscale address (or
|
||||
`0.0.0.0`), `Matching2Enabled=true` and `RegistrationSecretKey` to your signup code.
|
||||
|
||||
### A firewall on the server
|
||||
|
||||
With ufw, let the tailnet in:
|
||||
|
||||
```sh
|
||||
sudo ufw allow in on tailscale0
|
||||
```
|
||||
|
||||
## 3. Accounts
|
||||
|
||||
One account per player, created on the server with the signup code. The name is 3 to 16
|
||||
letters, digits, `-` or `_`:
|
||||
|
||||
```sh
|
||||
docker exec shadnet shadnet-sample 127.0.0.1 31313 register <name> <password> <email> <signup code>
|
||||
```
|
||||
|
||||
`127.0.0.1` works only when the server listens on every address (`SHADNET_HOST` unset or
|
||||
`0.0.0.0`). With `SHADNET_HOST` set, use that address instead. The tool answers
|
||||
`Account created successfully.`, or `FAILED: Unauthorized` when the code is wrong. Give each player
|
||||
their own name and password.
|
||||
|
||||
To back up the accounts, stop the server and copy the volume's `db/shadnet.db`.
|
||||
|
||||
## 4. Each player's launcher
|
||||
|
||||
In the **Online** page:
|
||||
|
||||
| Setting | Value |
|
||||
|---|---|
|
||||
| Play online | On |
|
||||
| shadNet server | **Your own server** |
|
||||
| Server address | `100.x.y.z` (the port, 31313, is added) |
|
||||
| WebAPI address | Empty (the server's port 31315) |
|
||||
| Online ID / Password | The player's own account |
|
||||
| UPnP | Off (Tailscale needs no router ports) |
|
||||
|
||||
Then **Test the connection**. Every line should be green, the shadNet server's UDP among them: it
|
||||
shows the address the server sees for you, a `100.` one through Tailscale. A red UDP line means
|
||||
summons cannot connect: check that the server is running and that its log lists the UDP listener.
|
||||
|
||||
Summons go through the server's own summon service: the log of a game says
|
||||
`Co-op: summon signs through http://100.x.y.z:31315`.
|
||||
|
||||
Windows players set the same server, WebAPI and account in the Windows port's launcher.
|
||||
|
||||
## Seamless co-op (experimental)
|
||||
|
||||
shadnet-p2p can also summon between different areas: the guest is moved to the host's area. To
|
||||
use it, the server and every player must have it on:
|
||||
|
||||
- The server: add `-e SHADNET_BLOODBORNE_SEAMLESS_COOP=1` to `docker run`, or set
|
||||
`BloodborneSeamlessCoop=true` in `shadnet.cfg`. Its log then says `seamless co-op enabled`.
|
||||
- Each player: **Seamless co-op (experimental)** in the launcher's Online page.
|
||||
|
||||
Ring the host's Beckoning Bell first, then the guest's Small Resonant Bell. Travelling together
|
||||
by lantern once the session has started does not work yet.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **The log shows `127.0.0.1` or another address in the listeners:** check `SHADNET_HOST`, or
|
||||
`Host` in the `shadnet.cfg` beside the server's executable.
|
||||
- **`Waiting for 100.x.y.z` in the log:** Tailscale is not up on the server, or the address is
|
||||
wrong (`tailscale ip -4`).
|
||||
- **The game says it is not signed in to PlayStation Network:** a wrong name or password (code 8 in
|
||||
the log), or two players using one account.
|
||||
- **The test is green but summons fail:** every player's computer must be in the tailnet, not only
|
||||
the server; `tailscale ping <player>` from each side. Everyone needs the game at version 1.09
|
||||
and the same co-op mode.
|
||||
|
||||
See also [ONLINE.md](ONLINE.md) for the bells, passwords and log lines of a summon.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Troubleshooting
|
||||
|
||||
When asking for help, attach the log: in the launcher's **Log** page, **Export log…** saves it to
|
||||
a file and **Copy** puts it on the clipboard.
|
||||
|
||||
## The launcher rejects the game
|
||||
|
||||
The launcher checks the game folder before starting. What it says, and what to do:
|
||||
|
||||
| Message | What to do |
|
||||
|---|---|
|
||||
| bbport needs the 1.09 update merged into the game folder | Copy everything from the dumped 1.09 update into the game folder, replacing files (`eboot.bin` and `sce_sys` too). |
|
||||
| param.sfo is from 1.09 but eboot.bin is not | Copy `eboot.bin` from the dumped 1.09 update over the old one. |
|
||||
| This eboot.bin has the 60 fps patch by Lance McDonald applied | Use a clean `eboot.bin` from the 1.09 update. bbport has its own 60 FPS. |
|
||||
| This is not a retail release of Bloodborne | Alpha tests and other games do not run. See [COMPATIBILITY.md](COMPATIBILITY.md). |
|
||||
| eboot.bin could not be read as a decrypted PS4 executable | Dump the game and the update again. |
|
||||
| The game files are damaged | The extraction tool broke compressed files (LibOrbisPkg PkgTool on .NET 6 or newer); the game would hang while loading. Extract the game and the update again with a fixed tool. |
|
||||
|
||||
## The game shows *New Game* instead of my save
|
||||
|
||||
Saves live in the data directory, and each way of running the port has its own:
|
||||
|
||||
- The AppImage: `~/.local/share/bbport/user/savedata/1/<title ID>/`.
|
||||
- From the repository (`run.sh`): `user/savedata/1/<title ID>/` in the repository.
|
||||
|
||||
To move a save, close the game, back up both folders, and copy the `savedata` folder over.
|
||||
|
||||
## DLSS is "not supported by this GPU"
|
||||
|
||||
The log says why, on a line starting with `DLSS:`.
|
||||
|
||||
- DLSS needs a GeForce RTX (20 series or newer) and NVIDIA's own driver; it does not work with
|
||||
NVK.
|
||||
- The 0.4-linux.1 AppImage ships a modified copy of NVIDIA's DLSS library, which the driver
|
||||
refuses (`Feature requirement query failed: 0xbad00012`): update to 0.5-linux.1 or newer.
|
||||
- From the repository, DLSS needs `bash tools/build_dlss.sh` once after `build.sh`.
|
||||
- If the log says `Device creation with DLSS extensions failed`, NVIDIA's CUDA/UVM
|
||||
module is missing: the game then uses FSR 3.1. Load it with `sudo modprobe nvidia_uvm` (the
|
||||
`nvidia-modprobe` package loads it when needed); with Secure Boot, a module signing key that
|
||||
is not enrolled stops it (`Key was rejected by service`).
|
||||
|
||||
## Spanish shows the Latin American text (or the other way round)
|
||||
|
||||
The game picks a language's regional text by the dump's region. Choose it in **Settings → Game
|
||||
region**: *Europe* for Spanish from Spain, *The Americas* for Latin American Spanish.
|
||||
|
||||
## The game crashes after playing a while, on a card with little VRAM
|
||||
|
||||
On a 6 GB card the texture cache can run out of video memory: the log then repeats
|
||||
`Texture cache: memory pressure, ... (critical ...)`. Lower what uses VRAM: a 60 FPS limit,
|
||||
a lower output resolution, lower **Model detail** in the game's *Display* settings, and close
|
||||
other programs that use the GPU (browsers, video players, other games).
|
||||
|
||||
## Summons never connect
|
||||
|
||||
Run **Test the connection** in the **Online** page. A red line for the shadNet server's UDP means
|
||||
the server never learns your address, and no summon can connect: shadPS4's public server does
|
||||
this. Use your own server: [SERVER.md](SERVER.md).
|
||||
|
||||
## Other online problems
|
||||
|
||||
See [ONLINE.md](ONLINE.md): the account, the bells, the ports and the log lines of a summon.
|
||||
@@ -168,6 +168,9 @@ target_link_libraries(bbgpu PUBLIC Vulkan::Headers fmt::fmt Boost::headers magic
|
||||
add_executable(motion-history-test EXCLUDE_FROM_ALL ../tests/test_motion_history.cpp)
|
||||
target_compile_options(motion-history-test PRIVATE -UNDEBUG -Wall -Wextra -Werror)
|
||||
target_include_directories(motion-history-test PRIVATE ..)
|
||||
add_executable(pm4-resync-test EXCLUDE_FROM_ALL ../tests/test_pm4_resync.cpp)
|
||||
target_compile_options(pm4-resync-test PRIVATE -UNDEBUG -Wall -Wextra -Werror)
|
||||
target_include_directories(pm4-resync-test PRIVATE ..)
|
||||
add_executable(motion-shader-test EXCLUDE_FROM_ALL ../tests/test_motion_shaders.cpp ../tests/test_gpu_runtime_stubs.cpp)
|
||||
target_include_directories(motion-shader-test PRIVATE shim shadps4 third_party/gcn/include)
|
||||
target_link_libraries(motion-shader-test PRIVATE bbgpu)
|
||||
|
||||
@@ -30,6 +30,9 @@ int bbgpu_text_input_begin(const char *initial_utf8, const char *prompt_utf8);
|
||||
int bbgpu_text_input_poll(char *out_utf8, uint64_t size);
|
||||
/* 1 while the in-game settings menu is open: the game's pad input is held neutral. */
|
||||
int bbgpu_overlay_captures_input(void);
|
||||
/* A trophy the game unlocked (runtime_services.c): the overlay's notice. grade: P, G, S, B or '-';
|
||||
* icon: a TROPxxx.rgba file of scripts/trophies.py (u32 width, u32 height, RGBA), or NULL. */
|
||||
void bbgpu_trophy_notice(int id, char grade, const char *name_utf8, const char *icon_path);
|
||||
/* Mouse look (runtime_pad.c): the window takes the mouse when it is clicked (relative mode) and
|
||||
* lets it go on F1, losing focus or the menu opening. take: motion (pixels) and wheel notches
|
||||
* since the last call, reset; captured: 1 while the window holds it; enable: mouse_look=0 in
|
||||
|
||||
@@ -1,416 +1,427 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#ifdef _WIN32
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#include <Ws2tcpip.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <winsock2.h>
|
||||
typedef SOCKET net_socket;
|
||||
typedef int socklen_t;
|
||||
#else
|
||||
#include <cerrno>
|
||||
#include <arpa/inet.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
typedef int net_socket;
|
||||
#endif
|
||||
#if defined(__APPLE__)
|
||||
#include <net/if_dl.h>
|
||||
#include <net/route.h>
|
||||
#endif
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <string.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "net.h"
|
||||
#include "net_error.h"
|
||||
#include "net_util.h"
|
||||
|
||||
namespace NetUtil {
|
||||
|
||||
const std::array<u8, 6>& NetUtilInternal::GetEthernetAddr() const {
|
||||
return ether_address;
|
||||
}
|
||||
|
||||
bool NetUtilInternal::RetrieveEthernetAddr() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
#ifdef _WIN32
|
||||
std::vector<u8> adapter_infos(sizeof(IP_ADAPTER_INFO));
|
||||
ULONG size_infos = sizeof(IP_ADAPTER_INFO);
|
||||
|
||||
if (GetAdaptersInfo(reinterpret_cast<PIP_ADAPTER_INFO>(adapter_infos.data()), &size_infos) ==
|
||||
ERROR_BUFFER_OVERFLOW)
|
||||
adapter_infos.resize(size_infos);
|
||||
|
||||
if (GetAdaptersInfo(reinterpret_cast<PIP_ADAPTER_INFO>(adapter_infos.data()), &size_infos) ==
|
||||
NO_ERROR &&
|
||||
size_infos) {
|
||||
PIP_ADAPTER_INFO info = reinterpret_cast<PIP_ADAPTER_INFO>(adapter_infos.data());
|
||||
memcpy(ether_address.data(), info[0].Address, 6);
|
||||
return true;
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
ifaddrs* ifap;
|
||||
|
||||
if (getifaddrs(&ifap) == 0) {
|
||||
ifaddrs* p;
|
||||
for (p = ifap; p; p = p->ifa_next) {
|
||||
if (p->ifa_addr->sa_family == AF_LINK) {
|
||||
sockaddr_dl* sdp = reinterpret_cast<sockaddr_dl*>(p->ifa_addr);
|
||||
memcpy(ether_address.data(), sdp->sdl_data + sdp->sdl_nlen, 6);
|
||||
freeifaddrs(ifap);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
freeifaddrs(ifap);
|
||||
}
|
||||
#elif defined(__FreeBSD__)
|
||||
// todo
|
||||
#else
|
||||
ifreq ifr;
|
||||
ifconf ifc;
|
||||
char buf[1024];
|
||||
int success = 0;
|
||||
|
||||
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (sock == -1)
|
||||
return false;
|
||||
|
||||
ifc.ifc_len = sizeof(buf);
|
||||
ifc.ifc_buf = buf;
|
||||
if (ioctl(sock, SIOCGIFCONF, &ifc) == -1)
|
||||
return false;
|
||||
|
||||
ifreq* it = ifc.ifc_req;
|
||||
const ifreq* const end = it + (ifc.ifc_len / sizeof(ifreq));
|
||||
|
||||
for (; it != end; ++it) {
|
||||
strcpy(ifr.ifr_name, it->ifr_name);
|
||||
if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) {
|
||||
if (!(ifr.ifr_flags & IFF_LOOPBACK)) {
|
||||
if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) {
|
||||
success = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(sock);
|
||||
|
||||
if (success) {
|
||||
memcpy(ether_address.data(), ifr.ifr_hwaddr.sa_data, 6);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& NetUtilInternal::GetDefaultGateway() const {
|
||||
return default_gateway;
|
||||
}
|
||||
|
||||
bool NetUtilInternal::RetrieveDefaultGateway() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
#ifdef _WIN32
|
||||
ULONG flags = GAA_FLAG_INCLUDE_GATEWAYS;
|
||||
ULONG family = AF_INET; // Only IPv4
|
||||
ULONG buffer_size = 15000;
|
||||
|
||||
std::vector<BYTE> buffer(buffer_size);
|
||||
PIP_ADAPTER_ADDRESSES adapter_addresses =
|
||||
reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data());
|
||||
|
||||
DWORD result = GetAdaptersAddresses(family, flags, nullptr, adapter_addresses, &buffer_size);
|
||||
if (result != NO_ERROR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (PIP_ADAPTER_ADDRESSES adapter = adapter_addresses; adapter != nullptr;
|
||||
adapter = adapter->Next) {
|
||||
if (adapter->OperStatus != IfOperStatusUp)
|
||||
continue;
|
||||
|
||||
IP_ADAPTER_GATEWAY_ADDRESS_LH* gateway = adapter->FirstGatewayAddress;
|
||||
while (gateway) {
|
||||
sockaddr* sa = gateway->Address.lpSockaddr;
|
||||
if (sa->sa_family == AF_INET) {
|
||||
char str[INET_ADDRSTRLEN];
|
||||
sockaddr_in* sa_in = reinterpret_cast<sockaddr_in*>(sa);
|
||||
if (inet_ntop(AF_INET, &sa_in->sin_addr, str, sizeof(str))) {
|
||||
this->default_gateway = str;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
gateway = gateway->Next;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
#elif defined(__APPLE__)
|
||||
// adapted from
|
||||
// https://github.com/seladb/PcapPlusPlus/blob/a49a79e0b67b402ad75ffa96c1795def36df75c8/Pcap%2B%2B/src/PcapLiveDevice.cpp#L1236
|
||||
// route message struct for communication in APPLE device
|
||||
struct BSDRoutingMessage {
|
||||
struct rt_msghdr header;
|
||||
char messageSpace[512];
|
||||
};
|
||||
|
||||
struct BSDRoutingMessage routingMessage;
|
||||
// It creates a raw socket that can be used for routing-related operations
|
||||
int sockfd = socket(PF_ROUTE, SOCK_RAW, 0);
|
||||
if (sockfd < 0) {
|
||||
return false;
|
||||
}
|
||||
memset(reinterpret_cast<char*>(&routingMessage), 0, sizeof(routingMessage));
|
||||
routingMessage.header.rtm_msglen = sizeof(struct rt_msghdr);
|
||||
routingMessage.header.rtm_version = RTM_VERSION;
|
||||
routingMessage.header.rtm_type = RTM_GET;
|
||||
routingMessage.header.rtm_addrs = RTA_DST | RTA_NETMASK;
|
||||
routingMessage.header.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;
|
||||
routingMessage.header.rtm_msglen += 2 * sizeof(sockaddr_in);
|
||||
|
||||
if (write(sockfd, reinterpret_cast<char*>(&routingMessage), routingMessage.header.rtm_msglen) <
|
||||
0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the response from the route socket
|
||||
if (read(sockfd, reinterpret_cast<char*>(&routingMessage), sizeof(routingMessage)) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct in_addr* gateAddr = nullptr;
|
||||
struct sockaddr* sa = nullptr;
|
||||
char* spacePtr = (reinterpret_cast<char*>(&routingMessage.header + 1));
|
||||
auto rtmAddrs = routingMessage.header.rtm_addrs;
|
||||
int index = 1;
|
||||
auto roundUpClosestMultiple = [](int multiple, int num) {
|
||||
return ((num + multiple - 1) / multiple) * multiple;
|
||||
};
|
||||
while (rtmAddrs) {
|
||||
if (rtmAddrs & 1) {
|
||||
sa = reinterpret_cast<sockaddr*>(spacePtr);
|
||||
if (index == RTA_GATEWAY) {
|
||||
gateAddr = &((sockaddr_in*)sa)->sin_addr;
|
||||
break;
|
||||
}
|
||||
spacePtr += sa->sa_len > 0 ? roundUpClosestMultiple(sizeof(uint32_t), sa->sa_len)
|
||||
: sizeof(uint32_t);
|
||||
}
|
||||
index++;
|
||||
rtmAddrs >>= 1;
|
||||
}
|
||||
|
||||
if (gateAddr == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, gateAddr, str, sizeof(str));
|
||||
this->default_gateway = str;
|
||||
return true;
|
||||
#elif defined(__FreeBSD__)
|
||||
return true;
|
||||
#else
|
||||
std::ifstream route{"/proc/net/route"};
|
||||
std::string line;
|
||||
|
||||
std::getline(route, line);
|
||||
while (std::getline(route, line)) {
|
||||
std::istringstream iss{line};
|
||||
std::string iface, destination, gateway;
|
||||
int flags;
|
||||
|
||||
iss >> iface >> destination >> gateway >> std::hex >> flags;
|
||||
|
||||
if (destination == "00000000") {
|
||||
u64 default_gateway{};
|
||||
std::stringstream ss;
|
||||
ss << std::hex << gateway;
|
||||
ss >> default_gateway;
|
||||
|
||||
char str[INET_ADDRSTRLEN];
|
||||
in_addr addr;
|
||||
addr.s_addr = default_gateway;
|
||||
inet_ntop(AF_INET, &addr, str, sizeof(str));
|
||||
this->default_gateway = str;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& NetUtilInternal::GetNetmask() const {
|
||||
return netmask;
|
||||
}
|
||||
|
||||
bool NetUtilInternal::RetrieveNetmask() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
char netmaskStr[INET_ADDRSTRLEN];
|
||||
auto success = false;
|
||||
|
||||
#ifdef _WIN32
|
||||
ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
|
||||
ULONG family = AF_INET; // Only IPv4
|
||||
ULONG buffer_size = 15000;
|
||||
|
||||
std::vector<BYTE> buffer(buffer_size);
|
||||
auto adapter_addresses = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data());
|
||||
|
||||
DWORD result = GetAdaptersAddresses(family, flags, nullptr, adapter_addresses, &buffer_size);
|
||||
if (result == ERROR_BUFFER_OVERFLOW) {
|
||||
buffer.resize(buffer_size);
|
||||
adapter_addresses = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data());
|
||||
result = GetAdaptersAddresses(family, flags, nullptr, adapter_addresses, &buffer_size);
|
||||
}
|
||||
|
||||
if (result != NO_ERROR)
|
||||
return false;
|
||||
|
||||
for (auto adapter = adapter_addresses; adapter != nullptr; adapter = adapter->Next) {
|
||||
// Skip loopback and down interfaces
|
||||
if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK || adapter->OperStatus != IfOperStatusUp)
|
||||
continue;
|
||||
|
||||
for (auto unicast = adapter->FirstUnicastAddress; unicast != nullptr;
|
||||
unicast = unicast->Next) {
|
||||
if (unicast->Address.lpSockaddr->sa_family == AF_INET) {
|
||||
ULONG prefix_length = unicast->OnLinkPrefixLength;
|
||||
ULONG mask = prefix_length == 0 ? 0 : 0xFFFFFFFF << (32 - prefix_length);
|
||||
|
||||
in_addr mask_addr{};
|
||||
mask_addr.S_un.S_addr = htonl(mask);
|
||||
|
||||
if (inet_ntop(AF_INET, &mask_addr, netmaskStr, INET_ADDRSTRLEN)) {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
ifaddrs* ifap;
|
||||
|
||||
if (getifaddrs(&ifap) == 0) {
|
||||
ifaddrs* p;
|
||||
for (p = ifap; p; p = p->ifa_next) {
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET) {
|
||||
auto sa = reinterpret_cast<sockaddr_in*>(p->ifa_netmask);
|
||||
inet_ntop(AF_INET, &sa->sin_addr, netmaskStr, INET_ADDRSTRLEN);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeifaddrs(ifap);
|
||||
#endif
|
||||
|
||||
if (success) {
|
||||
netmask = netmaskStr;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
const std::string& NetUtilInternal::GetIp() const {
|
||||
return ip;
|
||||
}
|
||||
|
||||
u32 NetUtilInternal::GetExternalIp() const {
|
||||
return external_ip;
|
||||
}
|
||||
|
||||
void NetUtilInternal::SetExternalIp(u32 addr) {
|
||||
external_ip = addr;
|
||||
}
|
||||
|
||||
u32 NetUtilInternal::GetNatType() const {
|
||||
return nat_type;
|
||||
}
|
||||
|
||||
bool NetUtilInternal::RetrieveIp() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
auto sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sockfd == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sockaddr_in sa{};
|
||||
socklen_t sa_len{sizeof(sa)};
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_addr.s_addr = inet_addr("1.1.1.1");
|
||||
sa.sin_port = htons(80);
|
||||
|
||||
#ifdef _WIN32
|
||||
#define close closesocket
|
||||
#endif
|
||||
|
||||
if (connect(sockfd, (sockaddr*)&sa, sa_len) == -1) {
|
||||
close(sockfd);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getsockname(sockfd, (struct sockaddr*)&sa, &sa_len) == -1) {
|
||||
close(sockfd);
|
||||
return false;
|
||||
}
|
||||
|
||||
char netmaskStr[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &sa.sin_addr, netmaskStr, INET_ADDRSTRLEN);
|
||||
ip = netmaskStr;
|
||||
|
||||
close(sockfd);
|
||||
|
||||
#ifdef _WIN32
|
||||
#undef close
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int NetUtilInternal::ResolveHostname(const char* hostname, Libraries::Net::OrbisNetInAddr* addr) {
|
||||
const addrinfo hints = {
|
||||
.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG,
|
||||
.ai_family = AF_INET,
|
||||
};
|
||||
|
||||
addrinfo* info = nullptr;
|
||||
auto gai_result = getaddrinfo(hostname, nullptr, &hints, &info);
|
||||
|
||||
auto ret = ORBIS_OK;
|
||||
if (gai_result != 0) {
|
||||
// handle more errors
|
||||
LOG_ERROR(Lib_Net, "address resolution for {} failed: {}", hostname, gai_result);
|
||||
ret = ORBIS_NET_ERETURN;
|
||||
} else {
|
||||
ASSERT(info && info->ai_addr);
|
||||
in_addr resolved_addr = ((sockaddr_in*)info->ai_addr)->sin_addr;
|
||||
LOG_DEBUG(Lib_Net, "resolved address for {}: {}", hostname, inet_ntoa(resolved_addr));
|
||||
addr->inaddr_addr = resolved_addr.s_addr;
|
||||
}
|
||||
|
||||
freeaddrinfo(info);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace NetUtil
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#ifdef _WIN32
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#include <Ws2tcpip.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <winsock2.h>
|
||||
typedef SOCKET net_socket;
|
||||
typedef int socklen_t;
|
||||
#else
|
||||
#include <cerrno>
|
||||
#include <arpa/inet.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
typedef int net_socket;
|
||||
#endif
|
||||
#if defined(__APPLE__)
|
||||
#include <net/if_dl.h>
|
||||
#include <net/route.h>
|
||||
#endif
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <string.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "net.h"
|
||||
#include "net_error.h"
|
||||
#include "net_util.h"
|
||||
|
||||
namespace NetUtil {
|
||||
|
||||
const std::array<u8, 6>& NetUtilInternal::GetEthernetAddr() const {
|
||||
return ether_address;
|
||||
}
|
||||
|
||||
bool NetUtilInternal::RetrieveEthernetAddr() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
#ifdef _WIN32
|
||||
std::vector<u8> adapter_infos(sizeof(IP_ADAPTER_INFO));
|
||||
ULONG size_infos = sizeof(IP_ADAPTER_INFO);
|
||||
|
||||
if (GetAdaptersInfo(reinterpret_cast<PIP_ADAPTER_INFO>(adapter_infos.data()), &size_infos) ==
|
||||
ERROR_BUFFER_OVERFLOW)
|
||||
adapter_infos.resize(size_infos);
|
||||
|
||||
if (GetAdaptersInfo(reinterpret_cast<PIP_ADAPTER_INFO>(adapter_infos.data()), &size_infos) ==
|
||||
NO_ERROR &&
|
||||
size_infos) {
|
||||
PIP_ADAPTER_INFO info = reinterpret_cast<PIP_ADAPTER_INFO>(adapter_infos.data());
|
||||
memcpy(ether_address.data(), info[0].Address, 6);
|
||||
return true;
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
ifaddrs* ifap;
|
||||
|
||||
if (getifaddrs(&ifap) == 0) {
|
||||
ifaddrs* p;
|
||||
for (p = ifap; p; p = p->ifa_next) {
|
||||
if (p->ifa_addr->sa_family == AF_LINK) {
|
||||
sockaddr_dl* sdp = reinterpret_cast<sockaddr_dl*>(p->ifa_addr);
|
||||
memcpy(ether_address.data(), sdp->sdl_data + sdp->sdl_nlen, 6);
|
||||
freeifaddrs(ifap);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
freeifaddrs(ifap);
|
||||
}
|
||||
#elif defined(__FreeBSD__)
|
||||
// todo
|
||||
#else
|
||||
ifreq ifr;
|
||||
ifconf ifc;
|
||||
char buf[1024];
|
||||
int success = 0;
|
||||
|
||||
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (sock == -1)
|
||||
return false;
|
||||
|
||||
ifc.ifc_len = sizeof(buf);
|
||||
ifc.ifc_buf = buf;
|
||||
if (ioctl(sock, SIOCGIFCONF, &ifc) == -1)
|
||||
return false;
|
||||
|
||||
ifreq* it = ifc.ifc_req;
|
||||
const ifreq* const end = it + (ifc.ifc_len / sizeof(ifreq));
|
||||
|
||||
for (; it != end; ++it) {
|
||||
strcpy(ifr.ifr_name, it->ifr_name);
|
||||
if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) {
|
||||
if (!(ifr.ifr_flags & IFF_LOOPBACK)) {
|
||||
if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) {
|
||||
success = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(sock);
|
||||
|
||||
if (success) {
|
||||
memcpy(ether_address.data(), ifr.ifr_hwaddr.sa_data, 6);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& NetUtilInternal::GetDefaultGateway() const {
|
||||
return default_gateway;
|
||||
}
|
||||
|
||||
bool NetUtilInternal::RetrieveDefaultGateway() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
#ifdef _WIN32
|
||||
ULONG flags = GAA_FLAG_INCLUDE_GATEWAYS;
|
||||
ULONG family = AF_INET; // Only IPv4
|
||||
ULONG buffer_size = 15000;
|
||||
|
||||
std::vector<BYTE> buffer(buffer_size);
|
||||
PIP_ADAPTER_ADDRESSES adapter_addresses =
|
||||
reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data());
|
||||
|
||||
DWORD result = GetAdaptersAddresses(family, flags, nullptr, adapter_addresses, &buffer_size);
|
||||
if (result != NO_ERROR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (PIP_ADAPTER_ADDRESSES adapter = adapter_addresses; adapter != nullptr;
|
||||
adapter = adapter->Next) {
|
||||
if (adapter->OperStatus != IfOperStatusUp)
|
||||
continue;
|
||||
|
||||
IP_ADAPTER_GATEWAY_ADDRESS_LH* gateway = adapter->FirstGatewayAddress;
|
||||
while (gateway) {
|
||||
sockaddr* sa = gateway->Address.lpSockaddr;
|
||||
if (sa->sa_family == AF_INET) {
|
||||
char str[INET_ADDRSTRLEN];
|
||||
sockaddr_in* sa_in = reinterpret_cast<sockaddr_in*>(sa);
|
||||
if (inet_ntop(AF_INET, &sa_in->sin_addr, str, sizeof(str))) {
|
||||
this->default_gateway = str;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
gateway = gateway->Next;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
#elif defined(__APPLE__)
|
||||
// adapted from
|
||||
// https://github.com/seladb/PcapPlusPlus/blob/a49a79e0b67b402ad75ffa96c1795def36df75c8/Pcap%2B%2B/src/PcapLiveDevice.cpp#L1236
|
||||
// route message struct for communication in APPLE device
|
||||
struct BSDRoutingMessage {
|
||||
struct rt_msghdr header;
|
||||
char messageSpace[512];
|
||||
};
|
||||
|
||||
struct BSDRoutingMessage routingMessage;
|
||||
// It creates a raw socket that can be used for routing-related operations
|
||||
int sockfd = socket(PF_ROUTE, SOCK_RAW, 0);
|
||||
if (sockfd < 0) {
|
||||
return false;
|
||||
}
|
||||
memset(reinterpret_cast<char*>(&routingMessage), 0, sizeof(routingMessage));
|
||||
routingMessage.header.rtm_msglen = sizeof(struct rt_msghdr);
|
||||
routingMessage.header.rtm_version = RTM_VERSION;
|
||||
routingMessage.header.rtm_type = RTM_GET;
|
||||
routingMessage.header.rtm_addrs = RTA_DST | RTA_NETMASK;
|
||||
routingMessage.header.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;
|
||||
routingMessage.header.rtm_msglen += 2 * sizeof(sockaddr_in);
|
||||
|
||||
if (write(sockfd, reinterpret_cast<char*>(&routingMessage), routingMessage.header.rtm_msglen) <
|
||||
0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the response from the route socket
|
||||
if (read(sockfd, reinterpret_cast<char*>(&routingMessage), sizeof(routingMessage)) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct in_addr* gateAddr = nullptr;
|
||||
struct sockaddr* sa = nullptr;
|
||||
char* spacePtr = (reinterpret_cast<char*>(&routingMessage.header + 1));
|
||||
auto rtmAddrs = routingMessage.header.rtm_addrs;
|
||||
int index = 1;
|
||||
auto roundUpClosestMultiple = [](int multiple, int num) {
|
||||
return ((num + multiple - 1) / multiple) * multiple;
|
||||
};
|
||||
while (rtmAddrs) {
|
||||
if (rtmAddrs & 1) {
|
||||
sa = reinterpret_cast<sockaddr*>(spacePtr);
|
||||
if (index == RTA_GATEWAY) {
|
||||
gateAddr = &((sockaddr_in*)sa)->sin_addr;
|
||||
break;
|
||||
}
|
||||
spacePtr += sa->sa_len > 0 ? roundUpClosestMultiple(sizeof(uint32_t), sa->sa_len)
|
||||
: sizeof(uint32_t);
|
||||
}
|
||||
index++;
|
||||
rtmAddrs >>= 1;
|
||||
}
|
||||
|
||||
if (gateAddr == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, gateAddr, str, sizeof(str));
|
||||
this->default_gateway = str;
|
||||
return true;
|
||||
#elif defined(__FreeBSD__)
|
||||
return true;
|
||||
#else
|
||||
std::ifstream route{"/proc/net/route"};
|
||||
std::string line;
|
||||
|
||||
std::getline(route, line);
|
||||
while (std::getline(route, line)) {
|
||||
std::istringstream iss{line};
|
||||
std::string iface, destination, gateway;
|
||||
int flags;
|
||||
|
||||
iss >> iface >> destination >> gateway >> std::hex >> flags;
|
||||
|
||||
if (destination == "00000000") {
|
||||
u64 default_gateway{};
|
||||
std::stringstream ss;
|
||||
ss << std::hex << gateway;
|
||||
ss >> default_gateway;
|
||||
|
||||
char str[INET_ADDRSTRLEN];
|
||||
in_addr addr;
|
||||
addr.s_addr = default_gateway;
|
||||
inet_ntop(AF_INET, &addr, str, sizeof(str));
|
||||
this->default_gateway = str;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& NetUtilInternal::GetNetmask() const {
|
||||
return netmask;
|
||||
}
|
||||
|
||||
bool NetUtilInternal::RetrieveNetmask() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
char netmaskStr[INET_ADDRSTRLEN];
|
||||
auto success = false;
|
||||
|
||||
#ifdef _WIN32
|
||||
ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
|
||||
ULONG family = AF_INET; // Only IPv4
|
||||
ULONG buffer_size = 15000;
|
||||
|
||||
std::vector<BYTE> buffer(buffer_size);
|
||||
auto adapter_addresses = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data());
|
||||
|
||||
DWORD result = GetAdaptersAddresses(family, flags, nullptr, adapter_addresses, &buffer_size);
|
||||
if (result == ERROR_BUFFER_OVERFLOW) {
|
||||
buffer.resize(buffer_size);
|
||||
adapter_addresses = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data());
|
||||
result = GetAdaptersAddresses(family, flags, nullptr, adapter_addresses, &buffer_size);
|
||||
}
|
||||
|
||||
if (result != NO_ERROR)
|
||||
return false;
|
||||
|
||||
for (auto adapter = adapter_addresses; adapter != nullptr; adapter = adapter->Next) {
|
||||
// Skip loopback and down interfaces
|
||||
if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK || adapter->OperStatus != IfOperStatusUp)
|
||||
continue;
|
||||
|
||||
for (auto unicast = adapter->FirstUnicastAddress; unicast != nullptr;
|
||||
unicast = unicast->Next) {
|
||||
if (unicast->Address.lpSockaddr->sa_family == AF_INET) {
|
||||
ULONG prefix_length = unicast->OnLinkPrefixLength;
|
||||
ULONG mask = prefix_length == 0 ? 0 : 0xFFFFFFFF << (32 - prefix_length);
|
||||
|
||||
in_addr mask_addr{};
|
||||
mask_addr.S_un.S_addr = htonl(mask);
|
||||
|
||||
if (inet_ntop(AF_INET, &mask_addr, netmaskStr, INET_ADDRSTRLEN)) {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
ifaddrs* ifap;
|
||||
|
||||
if (getifaddrs(&ifap) == 0) {
|
||||
ifaddrs* p;
|
||||
for (p = ifap; p; p = p->ifa_next) {
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET) {
|
||||
auto sa = reinterpret_cast<sockaddr_in*>(p->ifa_netmask);
|
||||
inet_ntop(AF_INET, &sa->sin_addr, netmaskStr, INET_ADDRSTRLEN);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeifaddrs(ifap);
|
||||
#endif
|
||||
|
||||
if (success) {
|
||||
netmask = netmaskStr;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
const std::string& NetUtilInternal::GetIp() const {
|
||||
return ip;
|
||||
}
|
||||
|
||||
u32 NetUtilInternal::GetExternalIp() const {
|
||||
return external_ip;
|
||||
}
|
||||
|
||||
void NetUtilInternal::SetExternalIp(u32 addr) {
|
||||
external_ip = addr;
|
||||
}
|
||||
|
||||
u32 NetUtilInternal::GetNatType() const {
|
||||
return nat_type;
|
||||
}
|
||||
|
||||
bool NetUtilInternal::RetrieveIp() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
auto sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sockfd == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sockaddr_in sa{};
|
||||
socklen_t sa_len{sizeof(sa)};
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_addr.s_addr = inet_addr("1.1.1.1");
|
||||
sa.sin_port = htons(80);
|
||||
|
||||
#ifdef _WIN32
|
||||
#define close closesocket
|
||||
#endif
|
||||
|
||||
if (connect(sockfd, (sockaddr*)&sa, sa_len) == -1) {
|
||||
close(sockfd);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getsockname(sockfd, (struct sockaddr*)&sa, &sa_len) == -1) {
|
||||
close(sockfd);
|
||||
return false;
|
||||
}
|
||||
|
||||
char netmaskStr[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &sa.sin_addr, netmaskStr, INET_ADDRSTRLEN);
|
||||
ip = netmaskStr;
|
||||
|
||||
close(sockfd);
|
||||
|
||||
#ifdef _WIN32
|
||||
#undef close
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int NetUtilInternal::ResolveHostname(const char* hostname, Libraries::Net::OrbisNetInAddr* addr) {
|
||||
// bbport: the game's "Playlog" telemetry (FromSoftware's bb-playlog-prod / bb-playlog-test
|
||||
// buckets on Amazon S3) is not sent anywhere: its host does not resolve, as with no network.
|
||||
if (std::string_view(hostname).find("bb-playlog") != std::string_view::npos) {
|
||||
static std::atomic_flag told = ATOMIC_FLAG_INIT;
|
||||
if (!told.test_and_set()) {
|
||||
LOG_INFO(Lib_Net, "{}: the game's play log upload is blocked", hostname);
|
||||
}
|
||||
return ORBIS_NET_ERETURN;
|
||||
}
|
||||
const addrinfo hints = {
|
||||
.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG,
|
||||
.ai_family = AF_INET,
|
||||
};
|
||||
|
||||
addrinfo* info = nullptr;
|
||||
auto gai_result = getaddrinfo(hostname, nullptr, &hints, &info);
|
||||
|
||||
auto ret = ORBIS_OK;
|
||||
if (gai_result != 0) {
|
||||
// handle more errors
|
||||
LOG_ERROR(Lib_Net, "address resolution for {} failed: {}", hostname, gai_result);
|
||||
ret = ORBIS_NET_ERETURN;
|
||||
} else {
|
||||
ASSERT(info && info->ai_addr);
|
||||
in_addr resolved_addr = ((sockaddr_in*)info->ai_addr)->sin_addr;
|
||||
LOG_DEBUG(Lib_Net, "resolved address for {}: {}", hostname, inet_ntoa(resolved_addr));
|
||||
addr->inaddr_addr = resolved_addr.s_addr;
|
||||
}
|
||||
|
||||
freeaddrinfo(info);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace NetUtil
|
||||
File diff suppressed because it is too large.
Load diff
@@ -303,6 +303,10 @@ private:
|
||||
std::chrono::milliseconds backoff{0};
|
||||
};
|
||||
std::unordered_map<s32, ReconnectState> m_reconnect;
|
||||
// bbport: how each user's latest connection attempt ended (ConnectUser), so a failure
|
||||
// retrying cannot fix (wrong name or password, an incompatible server) is not retried.
|
||||
std::unordered_map<s32, ShadNet::ShadNetState> m_last_connect_state;
|
||||
bool RetryableFailure(s32 user_id) const;
|
||||
|
||||
mutable std::mutex m_mutex_pending_invites;
|
||||
std::unordered_map<s32, std::vector<PendingInvitation>> m_pending_invites;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+395
-385
@@ -1,385 +1,395 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2019-2026 rpcs3 Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <semaphore>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "common/types.h"
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
using ShadSocketHandle = SOCKET;
|
||||
static constexpr ShadSocketHandle SHAD_INVALID_SOCK = INVALID_SOCKET;
|
||||
#define SHAD_CLOSE(s) ::closesocket(s)
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
using ShadSocketHandle = int;
|
||||
static constexpr ShadSocketHandle SHAD_INVALID_SOCK = -1;
|
||||
#define SHAD_CLOSE(s) ::close(s)
|
||||
#endif
|
||||
|
||||
namespace ShadNet {
|
||||
|
||||
// Protocol constants
|
||||
static constexpr u32 SHAD_HEADER_SIZE = 15;
|
||||
static constexpr u32 SHAD_CONNECT_TIMEOUT_MS = 10000; // 10 second connect/handshake timeout
|
||||
// MAX_ATTEMPTS * CONNECT_TIMEOUT + sum(backoffs).
|
||||
static constexpr u32 SHAD_CONNECT_MAX_ATTEMPTS = 4;
|
||||
static constexpr u32 SHAD_CONNECT_RETRY_BACKOFF_MS =
|
||||
1000; // base backoff, doubled per retry (cap 8s)
|
||||
static constexpr u32 SHAD_PROTOCOL_VERSION = 1;
|
||||
static constexpr u32 SHAD_MAX_PACKET_SIZE = 0x800000; // 8 MiB
|
||||
|
||||
// Protocol enumerations (must match shadnet server protocol.h)
|
||||
enum class PacketType : u8 {
|
||||
Request = 0,
|
||||
Reply = 1,
|
||||
Notification = 2,
|
||||
ServerInfo = 3,
|
||||
};
|
||||
|
||||
enum class CommandType : u16 {
|
||||
Login = 0,
|
||||
Terminate = 1,
|
||||
Create = 2,
|
||||
Delete = 3,
|
||||
SendToken = 4,
|
||||
SendResetToken = 5,
|
||||
ResetPassword = 6,
|
||||
ResetState = 7,
|
||||
AddFriend = 8,
|
||||
RemoveFriend = 9,
|
||||
AddBlock = 10,
|
||||
RemoveBlock = 11,
|
||||
GetServerFeatures = 12,
|
||||
GetBoardInfos = 30,
|
||||
RecordScore = 31,
|
||||
RecordScoreData = 32,
|
||||
GetScoreData = 33,
|
||||
GetScoreRange = 34,
|
||||
GetScoreFriends = 35,
|
||||
GetScoreNpid = 36,
|
||||
GetScoreAccountId = 37,
|
||||
GetScoreGameDataByAccId = 38,
|
||||
GetToken = 39,
|
||||
SetAppearOffline = 40,
|
||||
// Matchmaking
|
||||
ContextStart = 100,
|
||||
CreateRoom = 101,
|
||||
JoinRoom = 102,
|
||||
LeaveRoom = 103,
|
||||
SearchRoom = 104,
|
||||
RequestSignalingInfos = 105,
|
||||
ContextStop = 106,
|
||||
SetUserInfo = 107,
|
||||
SetRoomDataInternal = 108,
|
||||
SetRoomDataExternal = 109,
|
||||
KickoutRoomMember = 110,
|
||||
GetWorldInfoList = 111,
|
||||
GetRoomDataExternalList = 112,
|
||||
GetUserInfoList = 113,
|
||||
GetRoomMemberDataExternalList = 114,
|
||||
SendRoomMessage = 115,
|
||||
};
|
||||
|
||||
enum class NotificationType : u16 {
|
||||
FriendQuery = 5,
|
||||
FriendNew = 6,
|
||||
FriendLost = 7,
|
||||
FriendStatus = 8,
|
||||
RoomEvent = 10,
|
||||
RoomMessage = 11,
|
||||
WebApiPushEvent = 17, // Generic NP WebApi push event
|
||||
};
|
||||
|
||||
enum class ErrorType : uint8_t {
|
||||
NoError = 0,
|
||||
Malformed = 1,
|
||||
Invalid = 2,
|
||||
InvalidInput = 3,
|
||||
TooSoon = 4,
|
||||
LoginError = 5,
|
||||
LoginAlreadyLoggedIn = 6,
|
||||
LoginInvalidUsername = 7,
|
||||
LoginInvalidPassword = 8,
|
||||
LoginInvalidToken = 9,
|
||||
CreationError = 10,
|
||||
CreationExistingUsername = 11,
|
||||
CreationBannedEmailProvider = 12,
|
||||
CreationExistingEmail = 13,
|
||||
RoomMissing = 14,
|
||||
RoomAlreadyJoined = 15,
|
||||
RoomFull = 16,
|
||||
RoomPasswordMismatch = 17,
|
||||
RoomPasswordMissing = 18,
|
||||
RoomGroupNoJoinLabel = 19,
|
||||
RoomGroupFull = 20,
|
||||
RoomGroupJoinLabelNotFound = 21,
|
||||
RoomGroupMaxSlotMismatch = 22,
|
||||
Unauthorized = 23,
|
||||
DbFail = 24,
|
||||
EmailFail = 25,
|
||||
NotFound = 26,
|
||||
Blocked = 27,
|
||||
AlreadyFriend = 28,
|
||||
ScoreNotBest = 29,
|
||||
ScoreInvalid = 30,
|
||||
ScoreHasData = 31,
|
||||
CondFail = 32,
|
||||
Unsupported = 33,
|
||||
};
|
||||
|
||||
enum class ShadNetState {
|
||||
Ok,
|
||||
FailureInput,
|
||||
FailureResolve,
|
||||
FailureConnect,
|
||||
FailureServerInfo,
|
||||
FailureAuth,
|
||||
FailureAlreadyIn,
|
||||
FailureUsername,
|
||||
FailurePassword,
|
||||
FailureToken,
|
||||
FailureProtocol,
|
||||
FailureOther,
|
||||
};
|
||||
|
||||
// Callback data structures
|
||||
|
||||
struct FriendEntry {
|
||||
std::string npid;
|
||||
bool online = false;
|
||||
};
|
||||
|
||||
struct LoginResult {
|
||||
ErrorType error = ErrorType::Malformed;
|
||||
std::string avatarUrl;
|
||||
u64 userId = 0;
|
||||
std::vector<FriendEntry> friends;
|
||||
std::vector<std::string> requestsSent;
|
||||
std::vector<std::string> requestsReceived;
|
||||
std::vector<std::string> blocked;
|
||||
};
|
||||
|
||||
struct NotifyFriendQuery {
|
||||
std::string fromNpid;
|
||||
};
|
||||
struct NotifyFriendNew {
|
||||
std::string npid;
|
||||
bool online = false;
|
||||
};
|
||||
struct NotifyFriendLost {
|
||||
std::string npid;
|
||||
};
|
||||
struct NotifyFriendStatus {
|
||||
std::string npid;
|
||||
bool online = false;
|
||||
u64 timestamp = 0;
|
||||
};
|
||||
struct NotifyWebApiPushEvent {
|
||||
std::string npServiceName; // may be empty (catch-all listeners match any)
|
||||
u32 npServiceLabel = 0;
|
||||
std::string dataType; // e.g. "np:service:..."
|
||||
std::string data; // raw event body (typically PSN-format JSON)
|
||||
std::string fromNpid; // may be empty
|
||||
std::string toNpid; // may be empty
|
||||
// Optional extended-data (key,value) pairs (e.g. friendlist trigger/additionalTrigger,
|
||||
// presence gameStatus/gameData). Empty when the server sends none / is older.
|
||||
std::vector<std::pair<std::string, std::string>> extdData;
|
||||
};
|
||||
|
||||
struct MatchingBinAttr {
|
||||
u32 attr_id = 0;
|
||||
u64 update_date = 0;
|
||||
u32 update_member_id = 0;
|
||||
std::vector<u8> data;
|
||||
};
|
||||
|
||||
struct NotifyRoomEvent {
|
||||
u32 ctx_id = 0;
|
||||
u64 room_id = 0;
|
||||
u32 event = 0;
|
||||
u32 event_cause = 0;
|
||||
s32 error_code = 0;
|
||||
u32 flags = 0;
|
||||
bool has_passwd_mask = false;
|
||||
u64 passwd_slot_mask = 0;
|
||||
|
||||
std::string member_npid;
|
||||
u64 member_account_id = 0;
|
||||
u32 member_platform = 0;
|
||||
u32 member_id = 0;
|
||||
u32 member_team_id = 0;
|
||||
bool member_is_owner = false;
|
||||
u64 member_join_date = 0;
|
||||
u32 member_nat_type = 0;
|
||||
u32 member_flag_attr = 0;
|
||||
u32 member_group_id = 0;
|
||||
std::string member_addr;
|
||||
u32 member_port = 0;
|
||||
std::vector<MatchingBinAttr> member_bin_attrs;
|
||||
|
||||
std::vector<MatchingBinAttr> bin_attrs;
|
||||
};
|
||||
|
||||
struct NotifyRoomMessage {
|
||||
u32 ctx_id = 0;
|
||||
u64 room_id = 0;
|
||||
u32 src_member_id = 0;
|
||||
u32 event = 0;
|
||||
u32 cast_type = 0;
|
||||
std::vector<u32> dst_member_ids;
|
||||
std::string src_npid;
|
||||
u64 src_account_id = 0;
|
||||
u32 src_platform = 0;
|
||||
std::vector<u8> msg;
|
||||
};
|
||||
|
||||
// ShadNetClient
|
||||
|
||||
class ShadNetClient {
|
||||
public:
|
||||
ShadNetClient();
|
||||
~ShadNetClient();
|
||||
ShadNetClient(const ShadNetClient&) = delete;
|
||||
ShadNetClient& operator=(const ShadNetClient&) = delete;
|
||||
|
||||
void Start(const std::string& host, u16 port, const std::string& npid,
|
||||
const std::string& password, const std::string& token = {});
|
||||
void Stop();
|
||||
|
||||
ShadNetState WaitForConnection();
|
||||
ShadNetState WaitForAuthenticated();
|
||||
|
||||
bool IsConnected() const;
|
||||
bool IsAuthenticated() const;
|
||||
ShadNetState GetState() const;
|
||||
|
||||
const std::string& GetAvatarUrl() const;
|
||||
u64 GetUserId() const;
|
||||
u32 GetServerProtocolVersion() const {
|
||||
return m_server_protocol_version.load();
|
||||
}
|
||||
u32 GetAddrLocal() const;
|
||||
u32 GetAddrServer() const;
|
||||
bool IsMatching2Enabled() const;
|
||||
u32 GetNumFriends() const;
|
||||
std::optional<std::string> GetFriendNpid(u32 index) const;
|
||||
|
||||
std::string GetBearerToken() const;
|
||||
|
||||
// Callbacks
|
||||
std::function<void(const LoginResult&)> onLoginResult;
|
||||
std::function<void(const NotifyFriendQuery&)> onFriendQuery;
|
||||
std::function<void(const NotifyFriendNew&)> onFriendNew;
|
||||
std::function<void(const NotifyFriendLost&)> onFriendLost;
|
||||
std::function<void(const NotifyFriendStatus&)> onFriendStatus;
|
||||
std::function<void(const NotifyRoomEvent&)> onRoomEvent;
|
||||
std::function<void(const NotifyRoomMessage&)> onRoomMessage;
|
||||
std::function<void(const NotifyWebApiPushEvent&)> onWebApiPushEvent;
|
||||
// Async reply callback.
|
||||
// cmd —command this reply is for (matches the request's cmd)
|
||||
// pkt_id —packet id echoed back from the original request header
|
||||
// error —ErrorType byte that prefixes every reply body
|
||||
// body —reply payload AFTER the error byte (may be empty)
|
||||
std::function<void(CommandType cmd, u64 pkt_id, ErrorType error, const std::vector<u8>& body)>
|
||||
onAsyncReply;
|
||||
|
||||
// Submit a Request packet for async processing.
|
||||
// Allocates a packet id, builds the packet, pushes it to the writer queue.
|
||||
// Returns the packet id so callers can correlate the eventual reply.
|
||||
u64 SubmitRequest(CommandType cmd, const std::vector<u8>& payload);
|
||||
|
||||
// Friend / block commands.
|
||||
u64 AddFriend(const std::string& npid);
|
||||
u64 RemoveFriend(const std::string& npid);
|
||||
u64 AddBlock(const std::string& npid);
|
||||
u64 RemoveBlock(const std::string& npid);
|
||||
// Set the Appear-Offline preference mid-session (server handles us as offline while set).
|
||||
u64 SetAppearOffline(bool enable);
|
||||
|
||||
private:
|
||||
void ConnectThread();
|
||||
void ReaderThread();
|
||||
void WriterThread();
|
||||
bool DoConnect();
|
||||
void DoDisconnect();
|
||||
bool RecvN(u8* buf, u32 n);
|
||||
bool SendAll(const std::vector<u8>& data);
|
||||
std::vector<u8> BuildPacket(CommandType cmd, u64 id, const std::vector<u8>& payload) const;
|
||||
void DispatchPacket(PacketType type, u16 cmd_raw, u64 pkt_id, const std::vector<u8>& payload);
|
||||
void HandleLoginReply(const std::vector<u8>& payload);
|
||||
void HandleGetTokenReply(const std::vector<u8>& payload);
|
||||
void HandleServerFeaturesReply(const std::vector<u8>& payload);
|
||||
void HandleNotification(u16 cmd_raw, const std::vector<u8>& payload);
|
||||
bool RequestServerFeatures();
|
||||
|
||||
// Helper: read a u32-LE-prefixed proto blob from a byte vector at pos.
|
||||
static std::string ExtractBlob(const std::vector<u8>& p, int pos);
|
||||
|
||||
static void PutLE16(std::vector<u8>& b, size_t off, u16 v);
|
||||
static void PutLE32(std::vector<u8>& b, size_t off, u32 v);
|
||||
static void PutLE64(std::vector<u8>& b, size_t off, u64 v);
|
||||
static u16 GetLE16(const u8* p);
|
||||
static u32 GetLE32(const u8* p);
|
||||
static u64 GetLE64(const u8* p);
|
||||
|
||||
ShadSocketHandle m_sock = SHAD_INVALID_SOCK;
|
||||
std::string m_host;
|
||||
u16 m_port = 31313;
|
||||
std::string m_npid;
|
||||
std::string m_password;
|
||||
std::string m_token;
|
||||
bool m_appear_offline = false; // user's Appear-Offline preference (sent at login)
|
||||
|
||||
std::atomic<bool> m_terminate{false};
|
||||
std::atomic<bool> m_connected{false};
|
||||
std::atomic<bool> m_authenticated{false};
|
||||
std::atomic<ShadNetState> m_state{ShadNetState::Ok};
|
||||
|
||||
std::binary_semaphore m_sem_connected{0};
|
||||
std::binary_semaphore m_sem_authenticated{0};
|
||||
std::mutex m_mutex_connected;
|
||||
std::mutex m_mutex_authenticated;
|
||||
|
||||
std::thread m_thread_connect;
|
||||
std::thread m_thread_reader;
|
||||
std::thread m_thread_writer;
|
||||
|
||||
std::mutex m_mutex_send_direct;
|
||||
std::mutex m_mutex_send_queue;
|
||||
std::condition_variable m_cv_send_queue;
|
||||
std::vector<std::vector<u8>> m_send_queue;
|
||||
|
||||
std::string m_avatar_url;
|
||||
u64 m_user_id = 0;
|
||||
mutable std::mutex m_mutex_bearer;
|
||||
std::string m_bearer_token;
|
||||
std::atomic<u32> m_addr_local{0};
|
||||
std::atomic<u32> m_addr_server{0};
|
||||
std::atomic<u32> m_server_protocol_version{0};
|
||||
std::atomic<bool> m_matching2_enabled{false};
|
||||
std::atomic<bool> m_server_features_received{false};
|
||||
|
||||
mutable std::mutex m_mutex_friends;
|
||||
std::vector<FriendEntry> m_friends;
|
||||
|
||||
std::atomic<u64> m_pkt_counter{1};
|
||||
};
|
||||
|
||||
} // namespace ShadNet
|
||||
// SPDX-FileCopyrightText: Copyright 2019-2026 rpcs3 Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <semaphore>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "common/types.h"
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
using ShadSocketHandle = SOCKET;
|
||||
static constexpr ShadSocketHandle SHAD_INVALID_SOCK = INVALID_SOCKET;
|
||||
#define SHAD_CLOSE(s) ::closesocket(s)
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
using ShadSocketHandle = int;
|
||||
static constexpr ShadSocketHandle SHAD_INVALID_SOCK = -1;
|
||||
#define SHAD_CLOSE(s) ::close(s)
|
||||
#endif
|
||||
|
||||
namespace ShadNet {
|
||||
|
||||
// Protocol constants
|
||||
static constexpr u32 SHAD_HEADER_SIZE = 15;
|
||||
static constexpr u32 SHAD_CONNECT_TIMEOUT_MS = 10000; // 10 second connect/handshake timeout
|
||||
// MAX_ATTEMPTS * CONNECT_TIMEOUT + sum(backoffs).
|
||||
static constexpr u32 SHAD_CONNECT_MAX_ATTEMPTS = 4;
|
||||
static constexpr u32 SHAD_CONNECT_RETRY_BACKOFF_MS =
|
||||
1000; // base backoff, doubled per retry (cap 8s)
|
||||
static constexpr u32 SHAD_PROTOCOL_VERSION = 1;
|
||||
static constexpr u32 SHAD_MAX_PACKET_SIZE = 0x800000; // 8 MiB
|
||||
|
||||
// Protocol enumerations (must match shadnet server protocol.h)
|
||||
enum class PacketType : u8 {
|
||||
Request = 0,
|
||||
Reply = 1,
|
||||
Notification = 2,
|
||||
ServerInfo = 3,
|
||||
};
|
||||
|
||||
enum class CommandType : u16 {
|
||||
Login = 0,
|
||||
Terminate = 1,
|
||||
Create = 2,
|
||||
Delete = 3,
|
||||
SendToken = 4,
|
||||
SendResetToken = 5,
|
||||
ResetPassword = 6,
|
||||
ResetState = 7,
|
||||
AddFriend = 8,
|
||||
RemoveFriend = 9,
|
||||
AddBlock = 10,
|
||||
RemoveBlock = 11,
|
||||
GetServerFeatures = 12,
|
||||
SetClientVersion = 13,
|
||||
GetBoardInfos = 30,
|
||||
RecordScore = 31,
|
||||
RecordScoreData = 32,
|
||||
GetScoreData = 33,
|
||||
GetScoreRange = 34,
|
||||
GetScoreFriends = 35,
|
||||
GetScoreNpid = 36,
|
||||
GetScoreAccountId = 37,
|
||||
GetScoreGameDataByAccId = 38,
|
||||
GetToken = 39,
|
||||
SetAppearOffline = 40,
|
||||
// Matchmaking
|
||||
ContextStart = 100,
|
||||
CreateRoom = 101,
|
||||
JoinRoom = 102,
|
||||
LeaveRoom = 103,
|
||||
SearchRoom = 104,
|
||||
RequestSignalingInfos = 105,
|
||||
ContextStop = 106,
|
||||
SetUserInfo = 107,
|
||||
SetRoomDataInternal = 108,
|
||||
SetRoomDataExternal = 109,
|
||||
KickoutRoomMember = 110,
|
||||
GetWorldInfoList = 111,
|
||||
GetRoomDataExternalList = 112,
|
||||
GetUserInfoList = 113,
|
||||
GetRoomMemberDataExternalList = 114,
|
||||
SendRoomMessage = 115,
|
||||
};
|
||||
|
||||
enum class NotificationType : u16 {
|
||||
FriendQuery = 5,
|
||||
FriendNew = 6,
|
||||
FriendLost = 7,
|
||||
FriendStatus = 8,
|
||||
RoomEvent = 10,
|
||||
RoomMessage = 11,
|
||||
WebApiPushEvent = 17, // Generic NP WebApi push event
|
||||
ClientRejected = 19, // The server is signing this client out and says why
|
||||
};
|
||||
|
||||
enum class ErrorType : uint8_t {
|
||||
NoError = 0,
|
||||
Malformed = 1,
|
||||
Invalid = 2,
|
||||
InvalidInput = 3,
|
||||
TooSoon = 4,
|
||||
LoginError = 5,
|
||||
LoginAlreadyLoggedIn = 6,
|
||||
LoginInvalidUsername = 7,
|
||||
LoginInvalidPassword = 8,
|
||||
LoginInvalidToken = 9,
|
||||
CreationError = 10,
|
||||
CreationExistingUsername = 11,
|
||||
CreationBannedEmailProvider = 12,
|
||||
CreationExistingEmail = 13,
|
||||
RoomMissing = 14,
|
||||
RoomAlreadyJoined = 15,
|
||||
RoomFull = 16,
|
||||
RoomPasswordMismatch = 17,
|
||||
RoomPasswordMissing = 18,
|
||||
RoomGroupNoJoinLabel = 19,
|
||||
RoomGroupFull = 20,
|
||||
RoomGroupJoinLabelNotFound = 21,
|
||||
RoomGroupMaxSlotMismatch = 22,
|
||||
Unauthorized = 23,
|
||||
DbFail = 24,
|
||||
EmailFail = 25,
|
||||
NotFound = 26,
|
||||
Blocked = 27,
|
||||
AlreadyFriend = 28,
|
||||
ScoreNotBest = 29,
|
||||
ScoreInvalid = 30,
|
||||
ScoreHasData = 31,
|
||||
CondFail = 32,
|
||||
Unsupported = 33,
|
||||
};
|
||||
|
||||
enum class ShadNetState {
|
||||
Ok,
|
||||
FailureInput,
|
||||
FailureResolve,
|
||||
FailureConnect,
|
||||
FailureServerInfo,
|
||||
FailureAuth,
|
||||
FailureAlreadyIn,
|
||||
FailureUsername,
|
||||
FailurePassword,
|
||||
FailureToken,
|
||||
FailureProtocol,
|
||||
FailureOther,
|
||||
};
|
||||
|
||||
// Callback data structures
|
||||
|
||||
struct FriendEntry {
|
||||
std::string npid;
|
||||
bool online = false;
|
||||
};
|
||||
|
||||
struct LoginResult {
|
||||
ErrorType error = ErrorType::Malformed;
|
||||
std::string avatarUrl;
|
||||
u64 userId = 0;
|
||||
std::vector<FriendEntry> friends;
|
||||
std::vector<std::string> requestsSent;
|
||||
std::vector<std::string> requestsReceived;
|
||||
std::vector<std::string> blocked;
|
||||
};
|
||||
|
||||
struct NotifyFriendQuery {
|
||||
std::string fromNpid;
|
||||
};
|
||||
struct NotifyFriendNew {
|
||||
std::string npid;
|
||||
bool online = false;
|
||||
};
|
||||
struct NotifyFriendLost {
|
||||
std::string npid;
|
||||
};
|
||||
struct NotifyFriendStatus {
|
||||
std::string npid;
|
||||
bool online = false;
|
||||
u64 timestamp = 0;
|
||||
};
|
||||
struct NotifyWebApiPushEvent {
|
||||
std::string npServiceName; // may be empty (catch-all listeners match any)
|
||||
u32 npServiceLabel = 0;
|
||||
std::string dataType; // e.g. "np:service:..."
|
||||
std::string data; // raw event body (typically PSN-format JSON)
|
||||
std::string fromNpid; // may be empty
|
||||
std::string toNpid; // may be empty
|
||||
// Optional extended-data (key,value) pairs (e.g. friendlist trigger/additionalTrigger,
|
||||
// presence gameStatus/gameData). Empty when the server sends none / is older.
|
||||
std::vector<std::pair<std::string, std::string>> extdData;
|
||||
};
|
||||
|
||||
struct MatchingBinAttr {
|
||||
u32 attr_id = 0;
|
||||
u64 update_date = 0;
|
||||
u32 update_member_id = 0;
|
||||
std::vector<u8> data;
|
||||
};
|
||||
|
||||
struct NotifyRoomEvent {
|
||||
u32 ctx_id = 0;
|
||||
u64 room_id = 0;
|
||||
u32 event = 0;
|
||||
u32 event_cause = 0;
|
||||
s32 error_code = 0;
|
||||
u32 flags = 0;
|
||||
bool has_passwd_mask = false;
|
||||
u64 passwd_slot_mask = 0;
|
||||
|
||||
std::string member_npid;
|
||||
u64 member_account_id = 0;
|
||||
u32 member_platform = 0;
|
||||
u32 member_id = 0;
|
||||
u32 member_team_id = 0;
|
||||
bool member_is_owner = false;
|
||||
u64 member_join_date = 0;
|
||||
u32 member_nat_type = 0;
|
||||
u32 member_flag_attr = 0;
|
||||
u32 member_group_id = 0;
|
||||
std::string member_addr;
|
||||
u32 member_port = 0;
|
||||
std::vector<MatchingBinAttr> member_bin_attrs;
|
||||
|
||||
std::vector<MatchingBinAttr> bin_attrs;
|
||||
};
|
||||
|
||||
struct NotifyRoomMessage {
|
||||
u32 ctx_id = 0;
|
||||
u64 room_id = 0;
|
||||
u32 src_member_id = 0;
|
||||
u32 event = 0;
|
||||
u32 cast_type = 0;
|
||||
std::vector<u32> dst_member_ids;
|
||||
std::string src_npid;
|
||||
u64 src_account_id = 0;
|
||||
u32 src_platform = 0;
|
||||
std::vector<u8> msg;
|
||||
};
|
||||
|
||||
// ShadNetClient
|
||||
|
||||
class ShadNetClient {
|
||||
public:
|
||||
ShadNetClient();
|
||||
~ShadNetClient();
|
||||
ShadNetClient(const ShadNetClient&) = delete;
|
||||
ShadNetClient& operator=(const ShadNetClient&) = delete;
|
||||
|
||||
void Start(const std::string& host, u16 port, const std::string& npid,
|
||||
const std::string& password, const std::string& token = {});
|
||||
void Stop();
|
||||
|
||||
ShadNetState WaitForConnection();
|
||||
ShadNetState WaitForAuthenticated();
|
||||
|
||||
bool IsConnected() const;
|
||||
/// The server refused this client's version (ClientRejected): signing in again cannot help.
|
||||
bool WasRejected() const {
|
||||
return m_rejected.load();
|
||||
}
|
||||
bool IsAuthenticated() const;
|
||||
ShadNetState GetState() const;
|
||||
|
||||
const std::string& GetAvatarUrl() const;
|
||||
u64 GetUserId() const;
|
||||
u32 GetServerProtocolVersion() const {
|
||||
return m_server_protocol_version.load();
|
||||
}
|
||||
u32 GetAddrLocal() const;
|
||||
u32 GetAddrServer() const;
|
||||
bool IsMatching2Enabled() const;
|
||||
u32 GetNumFriends() const;
|
||||
std::optional<std::string> GetFriendNpid(u32 index) const;
|
||||
|
||||
std::string GetBearerToken() const;
|
||||
|
||||
// Callbacks
|
||||
std::function<void(const LoginResult&)> onLoginResult;
|
||||
std::function<void(const NotifyFriendQuery&)> onFriendQuery;
|
||||
std::function<void(const NotifyFriendNew&)> onFriendNew;
|
||||
std::function<void(const NotifyFriendLost&)> onFriendLost;
|
||||
std::function<void(const NotifyFriendStatus&)> onFriendStatus;
|
||||
std::function<void(const NotifyRoomEvent&)> onRoomEvent;
|
||||
std::function<void(const NotifyRoomMessage&)> onRoomMessage;
|
||||
std::function<void(const NotifyWebApiPushEvent&)> onWebApiPushEvent;
|
||||
// Async reply callback.
|
||||
// cmd —command this reply is for (matches the request's cmd)
|
||||
// pkt_id —packet id echoed back from the original request header
|
||||
// error —ErrorType byte that prefixes every reply body
|
||||
// body —reply payload AFTER the error byte (may be empty)
|
||||
std::function<void(CommandType cmd, u64 pkt_id, ErrorType error, const std::vector<u8>& body)>
|
||||
onAsyncReply;
|
||||
|
||||
// Submit a Request packet for async processing.
|
||||
// Allocates a packet id, builds the packet, pushes it to the writer queue.
|
||||
// Returns the packet id so callers can correlate the eventual reply.
|
||||
u64 SubmitRequest(CommandType cmd, const std::vector<u8>& payload);
|
||||
|
||||
// Friend / block commands.
|
||||
u64 AddFriend(const std::string& npid);
|
||||
u64 RemoveFriend(const std::string& npid);
|
||||
u64 AddBlock(const std::string& npid);
|
||||
u64 RemoveBlock(const std::string& npid);
|
||||
// Set the Appear-Offline preference mid-session (server handles us as offline while set).
|
||||
u64 SetAppearOffline(bool enable);
|
||||
|
||||
private:
|
||||
void ConnectThread();
|
||||
void ReaderThread();
|
||||
void WriterThread();
|
||||
bool DoConnect();
|
||||
void DoDisconnect();
|
||||
bool RecvN(u8* buf, u32 n);
|
||||
bool SendAll(const std::vector<u8>& data);
|
||||
std::vector<u8> BuildPacket(CommandType cmd, u64 id, const std::vector<u8>& payload) const;
|
||||
void DispatchPacket(PacketType type, u16 cmd_raw, u64 pkt_id, const std::vector<u8>& payload);
|
||||
void HandleLoginReply(const std::vector<u8>& payload);
|
||||
void HandleGetTokenReply(const std::vector<u8>& payload);
|
||||
void HandleServerFeaturesReply(const std::vector<u8>& payload);
|
||||
void HandleNotification(u16 cmd_raw, const std::vector<u8>& payload);
|
||||
bool RequestServerFeatures();
|
||||
// Tell the server which client this is; a shadNet server may deny clients that stay silent.
|
||||
u64 ReportClientVersion();
|
||||
static std::string BuildVersionString();
|
||||
|
||||
// Helper: read a u32-LE-prefixed proto blob from a byte vector at pos.
|
||||
static std::string ExtractBlob(const std::vector<u8>& p, int pos);
|
||||
|
||||
static void PutLE16(std::vector<u8>& b, size_t off, u16 v);
|
||||
static void PutLE32(std::vector<u8>& b, size_t off, u32 v);
|
||||
static void PutLE64(std::vector<u8>& b, size_t off, u64 v);
|
||||
static u16 GetLE16(const u8* p);
|
||||
static u32 GetLE32(const u8* p);
|
||||
static u64 GetLE64(const u8* p);
|
||||
|
||||
ShadSocketHandle m_sock = SHAD_INVALID_SOCK;
|
||||
std::string m_host;
|
||||
u16 m_port = 31313;
|
||||
std::string m_npid;
|
||||
std::string m_password;
|
||||
std::string m_token;
|
||||
bool m_appear_offline = false; // user's Appear-Offline preference (sent at login)
|
||||
|
||||
std::atomic<bool> m_terminate{false};
|
||||
std::atomic<bool> m_connected{false};
|
||||
std::atomic<bool> m_rejected{false};
|
||||
std::atomic<bool> m_authenticated{false};
|
||||
std::atomic<ShadNetState> m_state{ShadNetState::Ok};
|
||||
|
||||
std::binary_semaphore m_sem_connected{0};
|
||||
std::binary_semaphore m_sem_authenticated{0};
|
||||
std::mutex m_mutex_connected;
|
||||
std::mutex m_mutex_authenticated;
|
||||
|
||||
std::thread m_thread_connect;
|
||||
std::thread m_thread_reader;
|
||||
std::thread m_thread_writer;
|
||||
|
||||
std::mutex m_mutex_send_direct;
|
||||
std::mutex m_mutex_send_queue;
|
||||
std::condition_variable m_cv_send_queue;
|
||||
std::vector<std::vector<u8>> m_send_queue;
|
||||
|
||||
std::string m_avatar_url;
|
||||
u64 m_user_id = 0;
|
||||
mutable std::mutex m_mutex_bearer;
|
||||
std::string m_bearer_token;
|
||||
std::atomic<u32> m_addr_local{0};
|
||||
std::atomic<u32> m_addr_server{0};
|
||||
std::atomic<u32> m_server_protocol_version{0};
|
||||
std::atomic<bool> m_matching2_enabled{false};
|
||||
std::atomic<bool> m_server_features_received{false};
|
||||
|
||||
mutable std::mutex m_mutex_friends;
|
||||
std::vector<FriendEntry> m_friends;
|
||||
|
||||
std::atomic<u64> m_pkt_counter{1};
|
||||
};
|
||||
|
||||
} // namespace ShadNet
|
||||
@@ -29,6 +29,22 @@ message SetAppearOfflineRequest {
|
||||
bool appear_offline = 1;
|
||||
}
|
||||
|
||||
// Sent right after signing in. A server with clientversions.cfg rules signs out a client
|
||||
// that reports nothing, or a version it denies.
|
||||
message SetClientVersionRequest {
|
||||
string version = 1;
|
||||
}
|
||||
|
||||
// Notification 19: the server is signing this client out, then disconnects.
|
||||
// reason: 1 = client version not accepted
|
||||
// message: text for the player, from the rule that matched
|
||||
// version: the version the server checked ("" = none reported)
|
||||
message NotifyClientRejected {
|
||||
uint32 reason = 1;
|
||||
string message = 2;
|
||||
string version = 3;
|
||||
}
|
||||
|
||||
message ServerFeaturesReply {
|
||||
bool matching2_enabled = 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
From: bbport
|
||||
Subject: [PATCH] FSR 4 v07: aspect mask for external image barriers
|
||||
|
||||
FfxFsr4VkExternalImageState gets an aspectMask (0 = color). The layout
|
||||
barriers of a registered external image used VK_IMAGE_ASPECT_COLOR_BIT, which
|
||||
is invalid for the D32_SFLOAT_S8_UINT depth image (VUID-VkImageMemoryBarrier-
|
||||
image-03319, subresourceRange-09601).
|
||||
|
||||
---
|
||||
diff --git a/include/ffx_vk_fsr4_v07.h b/include/ffx_vk_fsr4_v07.h
|
||||
index e856c0c..c698735 100644
|
||||
--- a/include/ffx_vk_fsr4_v07.h
|
||||
+++ b/include/ffx_vk_fsr4_v07.h
|
||||
@@ -61,6 +61,11 @@ typedef struct FfxFsr4VkShaderBlob {
|
||||
* GENERAL-to-GENERAL record is valid and still establishes the documented
|
||||
* compute visibility boundary. The record may be updated every frame; it is
|
||||
* matched by `view` and does not transfer image/view ownership.
|
||||
+ *
|
||||
+ * bbport: `aspectMask` names the aspects of `image` that the layout barriers
|
||||
+ * cover. 0 means VK_IMAGE_ASPECT_COLOR_BIT; a depth/stencil image needs all
|
||||
+ * of its aspects (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT for
|
||||
+ * D32_SFLOAT_S8_UINT).
|
||||
*/
|
||||
typedef struct FfxFsr4VkExternalImageState {
|
||||
uint32_t structSize;
|
||||
@@ -72,6 +77,7 @@ typedef struct FfxFsr4VkExternalImageState {
|
||||
VkImageLayout restoreLayout;
|
||||
VkPipelineStageFlags restoreStageMask;
|
||||
VkAccessFlags restoreAccessMask;
|
||||
+ VkImageAspectFlags aspectMask;
|
||||
} FfxFsr4VkExternalImageState;
|
||||
|
||||
// FSR4 has: pass0 (pre), pass1-12 (model), pass13 (post), rcas, spd_auto_exposure
|
||||
diff --git a/src/ffx_vk_fsr4_v07.c b/src/ffx_vk_fsr4_v07.c
|
||||
index eb42c16..4bb049c 100644
|
||||
--- a/src/ffx_vk_fsr4_v07.c
|
||||
+++ b/src/ffx_vk_fsr4_v07.c
|
||||
@@ -104,6 +104,7 @@ typedef struct {
|
||||
VkImageLayout restoreLayout;
|
||||
VkPipelineStageFlags restoreStageMask;
|
||||
VkAccessFlags restoreAccessMask;
|
||||
+ VkImageAspectFlags aspectMask; /* external images: aspects the barriers cover */
|
||||
int externalStateKnown;
|
||||
int externalTouched;
|
||||
} VkRes;
|
||||
@@ -1049,6 +1050,8 @@ static FfxErrorCode cbRegisterRes(FfxInterface* I,
|
||||
r->restoreLayout = state->restoreLayout;
|
||||
r->restoreStageMask = state->restoreStageMask;
|
||||
r->restoreAccessMask = state->restoreAccessMask;
|
||||
+ r->aspectMask = state->aspectMask ? state->aspectMask
|
||||
+ : VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
r->externalStateKnown = 1;
|
||||
} else if (in->description.type == FFX_RESOURCE_TYPE_BUFFER) {
|
||||
if (!in->description.size) return FFX_ERROR_INVALID_SIZE;
|
||||
@@ -1268,7 +1271,8 @@ static FfxErrorCode cbScheduleJob(FfxInterface* I, const FfxGpuJobDescription* j
|
||||
|
||||
// ── image layout barrier ──────────────────────────────────────────────────────
|
||||
|
||||
-static void imgBarrier(VkCommandBuffer cmd, VkImage img,
|
||||
+static void imgBarrierAspect(VkCommandBuffer cmd, VkImage img,
|
||||
+ VkImageAspectFlags aspect,
|
||||
VkImageLayout from, VkImageLayout to,
|
||||
VkAccessFlags sa, VkAccessFlags da,
|
||||
VkPipelineStageFlags ss, VkPipelineStageFlags ds)
|
||||
@@ -1280,10 +1284,18 @@ static void imgBarrier(VkCommandBuffer cmd, VkImage img,
|
||||
b.oldLayout=from; b.newLayout=to;
|
||||
b.srcQueueFamilyIndex=b.dstQueueFamilyIndex=VK_QUEUE_FAMILY_IGNORED;
|
||||
b.image=img;
|
||||
- b.subresourceRange=(VkImageSubresourceRange){VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1};
|
||||
+ b.subresourceRange=(VkImageSubresourceRange){aspect,0,1,0,1};
|
||||
vkCmdPipelineBarrier(cmd,ss,ds,0,0,NULL,0,NULL,1,&b);
|
||||
}
|
||||
|
||||
+static void imgBarrier(VkCommandBuffer cmd, VkImage img,
|
||||
+ VkImageLayout from, VkImageLayout to,
|
||||
+ VkAccessFlags sa, VkAccessFlags da,
|
||||
+ VkPipelineStageFlags ss, VkPipelineStageFlags ds)
|
||||
+{
|
||||
+ imgBarrierAspect(cmd, img, VK_IMAGE_ASPECT_COLOR_BIT, from, to, sa, da, ss, ds);
|
||||
+}
|
||||
+
|
||||
static void transitionExternalImagesToCompute(FfxFsr4VkContext* c,
|
||||
VkCommandBuffer cmd)
|
||||
{
|
||||
@@ -1296,7 +1308,7 @@ static void transitionExternalImagesToCompute(FfxFsr4VkContext* c,
|
||||
? VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT : r->stageMask;
|
||||
VkAccessFlags srcAccess = r->layout == VK_IMAGE_LAYOUT_UNDEFINED
|
||||
? 0u : r->accessMask;
|
||||
- imgBarrier(cmd, r->img, r->layout, VK_IMAGE_LAYOUT_GENERAL,
|
||||
+ imgBarrierAspect(cmd, r->img, r->aspectMask, r->layout, VK_IMAGE_LAYOUT_GENERAL,
|
||||
srcAccess, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
|
||||
srcStage, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
||||
r->layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
@@ -1315,7 +1327,7 @@ static FfxErrorCode restoreExternalImages(FfxFsr4VkContext* c,
|
||||
if (!r->external || r->kind != RES_IMAGE || !r->img ||
|
||||
!r->externalStateKnown || !r->externalTouched)
|
||||
continue;
|
||||
- imgBarrier(cmd, r->img, r->layout, r->restoreLayout,
|
||||
+ imgBarrierAspect(cmd, r->img, r->aspectMask, r->layout, r->restoreLayout,
|
||||
r->accessMask, r->restoreAccessMask,
|
||||
r->stageMask, r->restoreStageMask);
|
||||
r->layout = r->restoreLayout;
|
||||
@@ -5,3 +5,6 @@ patches here to its working tree when they are not applied yet:
|
||||
|
||||
- `0001-...`: `BB_FSR4_PROFILE` (GPU time per FSR 4 pass) and `BB_FSR4_STATS` (driver
|
||||
statistics of each pass) in the FSR 4 v07 provider.
|
||||
- `0002-...`: `aspectMask` in `FfxFsr4VkExternalImageState` (0 = color). The layout barriers
|
||||
of a registered external image named the color aspect, which is invalid for the
|
||||
D32_SFLOAT_S8_UINT depth image.
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
template <typename Func>
|
||||
void ForEachItemBelow(TickType tick, Func&& func) {
|
||||
static constexpr bool RETURNS_BOOL =
|
||||
std::is_same_v<std::invoke_result<Func, ObjectType>, bool>;
|
||||
std::is_same_v<std::invoke_result_t<Func, ObjectType>, bool>;
|
||||
Item* iterator = first_item;
|
||||
while (iterator) {
|
||||
if (static_cast<s64>(tick) - static_cast<s64>(iterator->tick) < 0) {
|
||||
|
||||
@@ -70,10 +70,12 @@ public:
|
||||
}
|
||||
|
||||
std::optional<T> Pop() {
|
||||
if (Size() == 0) {
|
||||
// bbport: checked under the lock: a Clear() from another thread (the player stopping,
|
||||
// the game closing) between the check and front() read an empty queue.
|
||||
std::lock_guard guard(m_mutex);
|
||||
if (m_queue.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::lock_guard guard(m_mutex);
|
||||
auto result = std::move(m_queue.front());
|
||||
m_queue.pop();
|
||||
return result;
|
||||
|
||||
@@ -387,6 +387,9 @@ bool AvPlayerSource::GetVideoData(AvPlayerFrameInfoEx& video_info) {
|
||||
}
|
||||
|
||||
auto frame = m_video_frames.Pop();
|
||||
if (!frame.has_value()) {
|
||||
return false; // bbport: cleared since the size check (the player stopped)
|
||||
}
|
||||
video_info = frame->info;
|
||||
|
||||
m_current_video_frame = std::move(frame);
|
||||
@@ -409,6 +412,9 @@ bool AvPlayerSource::GetAudioData(AvPlayerFrameInfo& audio_info) {
|
||||
}
|
||||
|
||||
auto frame = m_audio_frames.Pop();
|
||||
if (!frame.has_value()) {
|
||||
return false; // bbport: cleared since the size check (the player stopped)
|
||||
}
|
||||
m_last_audio_ts = frame->info.timestamp;
|
||||
m_last_data_time = std::chrono::high_resolution_clock::now();
|
||||
m_pause_duration = std::chrono::high_resolution_clock::duration(0);
|
||||
|
||||
@@ -378,6 +378,29 @@ void VideoOutDriver::Flip(const Request& req) {
|
||||
|
||||
// bbport: BB_FRAME_STATS=1 prints flip rate and frame time spread every 5 seconds.
|
||||
static const bool frame_stats = EmulatorSettingsImpl::Flag("BB_FRAME_STATS", false);
|
||||
// bbport: without it, one short line every 30 seconds, so a player's last_run.log says how the
|
||||
// game ran: game frames per second, the slowest 1% of frames, the worst frame and the
|
||||
// shader/pipeline compiles of the window.
|
||||
if (!frame_stats) {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
static Clock::time_point window_start = Clock::now(), last = window_start;
|
||||
static std::vector<float> intervals;
|
||||
const auto now = Clock::now();
|
||||
intervals.push_back(std::chrono::duration<float, std::milli>(now - last).count());
|
||||
last = now;
|
||||
const double window = std::chrono::duration<double>(now - window_start).count();
|
||||
if (window >= 30.0) {
|
||||
std::sort(intervals.begin(), intervals.end());
|
||||
const size_t n = intervals.size();
|
||||
const double slow_ms = intervals[n - 1 - n / 100];
|
||||
std::printf("Perf: %.1f FPS over %.0f s, 1%% low %.1f FPS, worst frame %.1f ms, %u "
|
||||
"shader/pipeline compiles\n",
|
||||
n / window, window, 1000.0 / std::max(slow_ms, 0.001),
|
||||
static_cast<double>(intervals.back()), Vulkan::g_bb_compiles.exchange(0));
|
||||
intervals.clear();
|
||||
window_start = now;
|
||||
}
|
||||
}
|
||||
if (frame_stats) {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
static Clock::time_point window_start = Clock::now(), last = window_start;
|
||||
|
||||
@@ -218,6 +218,10 @@ struct Info : InfoPersistent {
|
||||
std::memcpy(&base, &ud[ptr_index], sizeof(base));
|
||||
base = reinterpret_cast<const u32*>(VAddr(base) & 0xFFFFFFFFFFFFULL);
|
||||
}
|
||||
// bbport: an unbound descriptor table must reach the existing empty-sharp handling.
|
||||
if (!base) {
|
||||
return T{};
|
||||
}
|
||||
std::memcpy(&data, base + dword_offset, sizeof(T));
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <queue>
|
||||
@@ -22,6 +23,7 @@
|
||||
#include "shader_recompiler/ir/reg.h"
|
||||
#include "shader_recompiler/ir/srt_gvn_table.h"
|
||||
#include "shader_recompiler/ir/value.h"
|
||||
#include "bbport_toggles.h"
|
||||
|
||||
#ifdef ARCH_X86_64
|
||||
|
||||
@@ -30,9 +32,14 @@ using namespace Xbyak::util;
|
||||
static Xbyak::CodeGenerator g_srt_codegen(32_MB);
|
||||
static const u8* g_srt_codegen_start = nullptr;
|
||||
|
||||
namespace {
|
||||
void EnsureSrtFaultHandler();
|
||||
}
|
||||
|
||||
namespace Shader {
|
||||
|
||||
PFN_SrtWalker RegisterWalkerCode(const u8* ptr, size_t size) {
|
||||
EnsureSrtFaultHandler();
|
||||
const auto func_addr = (PFN_SrtWalker)g_srt_codegen.getCurr();
|
||||
g_srt_codegen.db(ptr, size);
|
||||
g_srt_codegen.ready();
|
||||
@@ -77,6 +84,26 @@ static bool SrtWalkerSignalHandler(void* context, void* fault_address) {
|
||||
return false; // Not in SRT code range
|
||||
}
|
||||
|
||||
// bbport: walks ahead of the game (draw preparation workers, the recording thread's table
|
||||
// check) may follow pointers into memory the game has reused since. They go back to their
|
||||
// recovery point (probe.c) and the GPU thread walks the tables itself: patching here made one
|
||||
// stale read zero that load for the rest of the session. This also keeps a failed check
|
||||
// below from leaving through a recovery point with the lock held (common/assert.cpp).
|
||||
if (runtime_fault_recover) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// bbport: draw-preparation workers run the same walker concurrently. A thread that faulted
|
||||
// on the instruction before another one patched it finds the patch here and retries; decoding
|
||||
// it as the original MOV would trip the ASSERT below (exit 23).
|
||||
static std::mutex patch_mutex;
|
||||
std::scoped_lock patch_lock{patch_mutex};
|
||||
const u8* bytes = reinterpret_cast<const u8*>(code);
|
||||
if ((bytes[0] == 0x48 && bytes[1] == 0x31 && bytes[2] == 0xFF) ||
|
||||
(bytes[0] == 0x45 && bytes[1] == 0x31 && bytes[2] == 0xD2)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Patch instruction to zero register
|
||||
ZydisDecodedInstruction instruction;
|
||||
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||
@@ -90,29 +117,37 @@ static bool SrtWalkerSignalHandler(void* context, void* fault_address) {
|
||||
size_t len = instruction.length;
|
||||
const size_t patch_size = 3;
|
||||
u8* code_patch = const_cast<u8*>(reinterpret_cast<const u8*>(code));
|
||||
u8 patch[patch_size];
|
||||
|
||||
// We can only encounter rdi or r10d as the first operand in a
|
||||
// fault memory access for SRT walker.
|
||||
switch (operands[0].reg.value) {
|
||||
case ZYDIS_REGISTER_RDI:
|
||||
// mov rdi, [rdi + (off_dw << 2)] -> xor rdi, rdi
|
||||
code_patch[0] = 0x48;
|
||||
code_patch[1] = 0x31;
|
||||
code_patch[2] = 0xFF;
|
||||
patch[0] = 0x48;
|
||||
patch[1] = 0x31;
|
||||
patch[2] = 0xFF;
|
||||
break;
|
||||
case ZYDIS_REGISTER_R10D:
|
||||
// mov r10d, [rdi + (off_dw << 2)] -> xor r10d, r10d
|
||||
code_patch[0] = 0x45;
|
||||
code_patch[1] = 0x31;
|
||||
code_patch[2] = 0xD2;
|
||||
patch[0] = 0x45;
|
||||
patch[1] = 0x31;
|
||||
patch[2] = 0xD2;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unsupported register for SRT walker patch");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fill nops
|
||||
memset(code_patch + patch_size, 0x90, len - patch_size);
|
||||
// bbport: other threads may be running this code. Written from the end, every state in
|
||||
// between is a load into the same register or a register move into it: nops over the
|
||||
// displacement, then the ModRM byte (mov reg, reg), the REX prefix, the xor opcode last.
|
||||
for (size_t i = len; i-- > patch_size;) {
|
||||
__atomic_store_n(&code_patch[i], u8{0x90}, __ATOMIC_RELEASE);
|
||||
}
|
||||
for (const size_t i : {size_t{2}, size_t{0}, size_t{1}}) {
|
||||
__atomic_store_n(&code_patch[i], patch[i], __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
LOG_WARNING(Render_Recompiler, "Patched SRT walker at {}, fault address {}", code,
|
||||
fault_address);
|
||||
@@ -120,6 +155,20 @@ static bool SrtWalkerSignalHandler(void* context, void* fault_address) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Registers the walker fault handler once, whichever walker is added first (generated here or
|
||||
// loaded from the pipeline cache through RegisterWalkerCode, which before left it unregistered
|
||||
// when every walker came from the cache). The buffer is still empty then, so its current
|
||||
// position is its start.
|
||||
void EnsureSrtFaultHandler() {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [] {
|
||||
g_srt_codegen_start = g_srt_codegen.getCurr();
|
||||
// Call after the memory invalidation handler
|
||||
constexpr u32 priority = 2;
|
||||
Core::Signals::Instance()->RegisterAccessViolationHandler(SrtWalkerSignalHandler, priority);
|
||||
});
|
||||
}
|
||||
|
||||
using namespace Shader;
|
||||
|
||||
struct PassInfo {
|
||||
@@ -641,13 +690,7 @@ static void GenerateSrtProgram(Info& info, PassInfo& pass_info) {
|
||||
}
|
||||
|
||||
// Register the signal handler for SRT walker, if not already registered
|
||||
if (g_srt_codegen_start == nullptr) {
|
||||
g_srt_codegen_start = c.getCurr();
|
||||
auto* signals = Core::Signals::Instance();
|
||||
// Call after the memory invalidation handler
|
||||
constexpr u32 priority = 2;
|
||||
signals->RegisterAccessViolationHandler(SrtWalkerSignalHandler, priority);
|
||||
}
|
||||
EnsureSrtFaultHandler();
|
||||
|
||||
info.srt_info.walker_func = c.getCurr<PFN_SrtWalker>();
|
||||
pass_info.dst_off_dw = NUM_USER_DATA_REGS;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <pthread.h>
|
||||
@@ -34,6 +35,7 @@
|
||||
#include "bbport_write_log.h"
|
||||
#include "bbport_free_check.h"
|
||||
#include "video_core/amdgpu/pm4_cmds.h"
|
||||
#include "video_core/amdgpu/pm4_resync.h"
|
||||
#include "video_core/amdgpu/pm4_selftest.h"
|
||||
#include "cp_decoder.h"
|
||||
#include "video_core/renderdoc.h"
|
||||
@@ -1460,14 +1462,27 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
|
||||
|
||||
switch (type) {
|
||||
default:
|
||||
ReportBadPacket(base_addr, dcb_dwords, dcb.data(), seq, dcb_depth);
|
||||
UNREACHABLE_MSG("Wrong PM4 type {}", type);
|
||||
break;
|
||||
case 0:
|
||||
ReportBadPacket(base_addr, dcb_dwords, dcb.data(), seq, dcb_depth);
|
||||
UNREACHABLE_MSG("Unimplemented PM4 type 0, base reg: {}, size: {}",
|
||||
header->type0.base.Value(), header->type0.NumWords());
|
||||
break;
|
||||
case 0: {
|
||||
// bbport: an invalid header ended the game (UNREACHABLE: "Unimplemented PM4 type
|
||||
// 0"). Seen at area loads: a SET_SH_REG cut short, whose declared body holds the
|
||||
// next packets, leaves the decoder inside them. Decoding resumes at the next dword
|
||||
// that can start a type 3 packet, so the rest of the buffer and its fences still
|
||||
// run (dropping them would leave the guest waiting for them). The draw preparation
|
||||
// scanner skips by the same rule (pm4_resync.h).
|
||||
static std::atomic<int> reports{0};
|
||||
if (reports.fetch_add(1, std::memory_order_relaxed) < 8) {
|
||||
ReportBadPacket(base_addr, dcb_dwords, dcb.data(), seq, dcb_depth);
|
||||
}
|
||||
const std::size_t skip = ResyncSkip(dcb);
|
||||
std::fprintf(stderr,
|
||||
"PM4: invalid header %#x at dword %zu of the buffer, resynced %zu dwords "
|
||||
"later\n",
|
||||
header->raw, std::size_t((reinterpret_cast<uintptr_t>(dcb.data()) -
|
||||
base_addr) / sizeof(u32)),
|
||||
skip);
|
||||
dcb = NextPacket(dcb, skip);
|
||||
continue;
|
||||
}
|
||||
case 2:
|
||||
// Type-2 packet are used for padding purposes
|
||||
dcb = NextPacket(dcb, 1);
|
||||
|
||||
@@ -356,7 +356,10 @@ constexpr NumberConversion MapNumberConversion(const NumberFormat num_fmt,
|
||||
case DataFormat::Format16_16_16_16:
|
||||
return NumberConversion::Sint16ToSnormNz;
|
||||
default:
|
||||
UNREACHABLE_MSG("data_fmt = {}", u32(data_fmt));
|
||||
// bbport: was UNREACHABLE. Seen on macOS with data format 45 (FMASK 8 S4 F1) and
|
||||
// SnormNz, a combination no real resource has (a stale or unused descriptor); it
|
||||
// ended the game. No conversion is the harmless answer.
|
||||
return NumberConversion::None;
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// bbport: one rule for stepping over an invalid PM4 packet header.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
namespace AmdGpu {
|
||||
|
||||
/// A dword that can start a type 3 packet: type 3, reserved bits clear, and the packet within
|
||||
/// the `remaining` dwords (same bits as PM4Type3Header).
|
||||
inline bool PlausibleType3(std::uint32_t dword, std::size_t remaining) {
|
||||
const std::uint32_t num_words = (((dword >> 16) & 0x3fff) + 1) & 0x3fff;
|
||||
return (dword >> 30) == 3 && (dword & 0xfc) == 0 && num_words < remaining;
|
||||
}
|
||||
|
||||
/// Dwords from the invalid header at the front of `remaining` to the next dword that can start
|
||||
/// a type 3 packet. Liverpool::ProcessGraphics and the draw preparation scanner both skip by
|
||||
/// this: when the scanner stopped at the header instead, its register state fell out of step
|
||||
/// with the GPU thread's for the rest of the session and no prepared draw was used again.
|
||||
inline std::size_t ResyncSkip(std::span<const std::uint32_t> remaining) {
|
||||
std::size_t skip = 1;
|
||||
while (skip < remaining.size() && !PlausibleType3(remaining[skip], remaining.size() - skip)) {
|
||||
++skip;
|
||||
}
|
||||
return skip;
|
||||
}
|
||||
|
||||
} // namespace AmdGpu
|
||||
@@ -95,9 +95,16 @@ void main() {
|
||||
// Both depths are full precision. Do not accept an old foreground vector merely
|
||||
// because perspective depth compresses distant surfaces into a small interval.
|
||||
float depth_threshold = max(2e-7, (proj.z - depth) * 0.01);
|
||||
// bbport: an object vector far from the camera's at the same pixel read history that is
|
||||
// not this object's: still bushes behind the Hunter's Dream workshop got 100-650 px a
|
||||
// frame while the camera vectors around them were right. Real object motion beyond 5% of
|
||||
// the frame width per frame (32 px at low render sizes) is rarer than such history, so
|
||||
// the camera vector stays then.
|
||||
const float max_object_delta = max(0.05 * size.x, 32.0);
|
||||
if (object_motion.b > 0.99 && abs(object_motion.a - depth) < depth_threshold &&
|
||||
!any(isnan(object_motion.rg)) &&
|
||||
!any(isinf(object_motion.rg))) {
|
||||
!any(isinf(object_motion.rg)) &&
|
||||
distance(object_motion.rg, motion) < max_object_delta) {
|
||||
result = object_motion.rg;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
@@ -39,8 +40,22 @@ VertexRange IndexedRange(std::span<const Index, Extent> indices, int32_t base_ve
|
||||
// Vertex shader constant buffers (stride 16) of Bloodborne's G-buffer shaders: the 864-byte
|
||||
// scene constants (camera), 416-byte model constants and palettes of 3x4 bone matrices
|
||||
// (48 bytes per bone). Characters have 656+ bytes; weapons and props 2-8 bones (96-384).
|
||||
enum class BufferRole { Other, Skeleton, SmallSkeleton };
|
||||
// bbport: palettes of 16-64 KiB in whole bones (24576, 49152: 512-1024 bones, seen in Central
|
||||
// Yharnam where the large beasts smeared under FSR) are LargeSkeleton: gated like small ones
|
||||
// (history only while the palette changes), since instance data of static props may look the
|
||||
// same. BB_MOTION_LARGE=0 leaves them to camera vectors as before.
|
||||
enum class BufferRole { Other, Skeleton, SmallSkeleton, LargeSkeleton };
|
||||
inline bool LargePalettesEnabled() {
|
||||
static const bool enabled = [] {
|
||||
const char* env = std::getenv("BB_MOTION_LARGE");
|
||||
return !env || env[0] != '0';
|
||||
}();
|
||||
return enabled;
|
||||
}
|
||||
inline BufferRole ClassifyBuffer(uint32_t size) {
|
||||
if (size > 16384 && size <= 65536 && size % 48 == 0 && LargePalettesEnabled()) {
|
||||
return BufferRole::LargeSkeleton;
|
||||
}
|
||||
if (size == 864 || size > 16384) {
|
||||
return BufferRole::Other;
|
||||
}
|
||||
|
||||
@@ -65,6 +65,13 @@ void Init(const Instance& instance) {
|
||||
"located\n");
|
||||
return;
|
||||
}
|
||||
// deadinside28#177: a build against other Vulkan headers left the extension's function null
|
||||
// although the extension was on; the first marker then crashed.
|
||||
if (!VULKAN_HPP_DEFAULT_DISPATCHER.vkCmdWriteBufferMarkerAMD) {
|
||||
std::printf("GPU breadcrumbs: VK_AMD_buffer_marker has no function, GPU hangs will not be "
|
||||
"located\n");
|
||||
return;
|
||||
}
|
||||
const VkBufferCreateInfo buffer_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = ArgsOffset + ArgSlots * 16,
|
||||
|
||||
@@ -14,10 +14,24 @@
|
||||
// Define assert-on-result to nothing to instead return the result for our handling.
|
||||
#define VULKAN_HPP_ASSERT_ON_RESULT
|
||||
|
||||
// vulkan.hpp adds fields to DispatchLoaderBase when NDEBUG is undefined, so the layout of
|
||||
// vk::detail::DispatchLoaderDynamic depends on it. libbbgpu is built with NDEBUG while the
|
||||
// renderer tests build with -UNDEBUG (their asserts must run); the inline init() copies the
|
||||
// linker keeps would then read the dispatcher at offsets another translation unit did not write
|
||||
// (a call through a null vkGetInstanceProcAddr). Include it with NDEBUG defined everywhere.
|
||||
#ifndef NDEBUG
|
||||
#define NDEBUG
|
||||
#define BB_VK_COMMON_RESTORE_ASSERTS
|
||||
#endif
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-value"
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#pragma clang diagnostic pop
|
||||
#ifdef BB_VK_COMMON_RESTORE_ASSERTS
|
||||
#undef BB_VK_COMMON_RESTORE_ASSERTS
|
||||
#undef NDEBUG
|
||||
#include <cassert> // vulkan.hpp's own include of it just turned assert() off
|
||||
#endif
|
||||
|
||||
#define VMA_STATIC_VULKAN_FUNCTIONS 0
|
||||
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstdio>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include "common/serdes.h"
|
||||
@@ -115,8 +116,13 @@ ComputePipeline::ComputePipeline(const Instance& instance, Scheduler& scheduler,
|
||||
throw Serialization::CorruptData{"cached compute pipeline rejected by the driver: " +
|
||||
vk::to_string(pipeline_result)};
|
||||
}
|
||||
ASSERT_MSG(pipeline_result == vk::Result::eSuccess, "Failed to create compute pipeline: {}",
|
||||
vk::to_string(pipeline_result));
|
||||
// bbport: as for graphics pipelines, one the driver cannot build skips its dispatches.
|
||||
if (pipeline_result != vk::Result::eSuccess) {
|
||||
std::printf("GPU: the driver could not create compute pipeline %s (%s): its dispatches "
|
||||
"are skipped\n",
|
||||
debug_str.c_str(), vk::to_string(pipeline_result).c_str());
|
||||
return;
|
||||
}
|
||||
pipeline = std::move(pipe);
|
||||
SetObjectName(device, *pipeline, "Compute Pipeline {}", debug_str);
|
||||
}
|
||||
|
||||
@@ -58,11 +58,15 @@ void* FindSymbol(Module module, const char* name) {
|
||||
}
|
||||
#else
|
||||
// bbport (Linux): libbbport_dlss.so (gpu/dlss_bridge) and NVIDIA's libnvidia-ngx-dlss.so.<version>
|
||||
// next to bb-probe; NGX finds the latter through the bridge's path list.
|
||||
// next to bb-probe, or in BB_DLSS_DIR (packaged builds whose /proc/self/exe is the dynamic
|
||||
// loader, such as an AppImage); NGX finds the latter through the bridge's path list.
|
||||
constexpr char BridgeName[] = "libbbport_dlss.so";
|
||||
using Module = void*;
|
||||
|
||||
std::filesystem::path ExecutableDirectory() {
|
||||
if (const char* dir = std::getenv("BB_DLSS_DIR"); dir && dir[0]) {
|
||||
return dir;
|
||||
}
|
||||
std::error_code error;
|
||||
return std::filesystem::read_symlink("/proc/self/exe", error).parent_path();
|
||||
}
|
||||
@@ -256,6 +260,10 @@ void Dlss::AppendDeviceExtensions(vk::Instance instance, vk::PhysicalDevice phys
|
||||
impl->device_ready = true;
|
||||
}
|
||||
|
||||
void Dlss::Disable(std::string_view reason) {
|
||||
impl->Disable(reason);
|
||||
}
|
||||
|
||||
void Dlss::Initialize(vk::Instance instance, vk::PhysicalDevice physical, vk::Device device) {
|
||||
if (!impl->eligible || !impl->device_ready || impl->initialized) {
|
||||
return;
|
||||
@@ -343,6 +351,7 @@ Dlss* Dlss::Get() {
|
||||
void Dlss::AppendInstanceExtensions(std::vector<const char*>&) {}
|
||||
void Dlss::AppendDeviceExtensions(vk::Instance, vk::PhysicalDevice, std::vector<const char*>&) {}
|
||||
void Dlss::Initialize(vk::Instance, vk::PhysicalDevice, vk::Device) {}
|
||||
void Dlss::Disable(std::string_view) {}
|
||||
void Dlss::Shutdown() {}
|
||||
bool Dlss::Available() const {
|
||||
return false;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "common/types.h"
|
||||
@@ -31,6 +32,8 @@ public:
|
||||
std::vector<const char*>& enabled);
|
||||
/// After the device exists.
|
||||
void Initialize(vk::Instance instance, vk::PhysicalDevice physical, vk::Device device);
|
||||
/// Device creation can fail despite advertised NGX extensions (e.g. missing CUDA/UVM).
|
||||
void Disable(std::string_view reason);
|
||||
void Shutdown();
|
||||
|
||||
[[nodiscard]] bool Available() const;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "common/thread.h"
|
||||
#include "video_core/amdgpu/liverpool.h"
|
||||
#include "video_core/amdgpu/pm4_cmds.h"
|
||||
#include "video_core/amdgpu/pm4_resync.h"
|
||||
#include "video_core/renderer_vulkan/vk_draw_prep.h"
|
||||
#include "video_core/renderer_vulkan/liverpool_to_vk.h"
|
||||
#include <xxhash.h>
|
||||
@@ -26,7 +27,8 @@ bool IsDirectDraw(AmdGpu::PM4ItOpcode opcode) {
|
||||
opcode == PM4ItOpcode::DrawIndexAuto;
|
||||
}
|
||||
|
||||
/// Walks the type-3 packets of a command buffer (type-2 padding skipped).
|
||||
/// Walks the type-3 packets of a command buffer (type-2 padding skipped, an invalid header
|
||||
/// stepped over exactly as the GPU thread does, so both register states stay equal).
|
||||
template <typename Func>
|
||||
void ForEachPacket(std::span<const u32> commands, Func&& func) {
|
||||
for (size_t at = 0; at < commands.size();) {
|
||||
@@ -36,7 +38,8 @@ void ForEachPacket(std::span<const u32> commands, Func&& func) {
|
||||
continue;
|
||||
}
|
||||
if (header->type != 3) {
|
||||
return;
|
||||
at += AmdGpu::ResyncSkip(commands.subspan(at));
|
||||
continue;
|
||||
}
|
||||
const size_t words = header->type3.NumWords() + 1;
|
||||
if (at + words > commands.size()) {
|
||||
|
||||
@@ -37,18 +37,18 @@ bool ReadFile(const std::string& path, std::vector<u8>& data) {
|
||||
return bool(file.read(reinterpret_cast<char*>(data.data()), std::streamsize(data.size())));
|
||||
}
|
||||
|
||||
// The model for the menu's preset. bbport: Performance and Ultra Performance (2x and 3x) use the
|
||||
// balanced model too: the v07 performance and ultra performance models leave trails behind moving
|
||||
// characters in Bloodborne, and the balanced one handles those ratios cleanly (IFreemz's
|
||||
// shadPS4-Bloodborne-DLSS-FSR, 9b2766e). The render size still follows the preset.
|
||||
FfxFsr4ModelPreset ModelPreset(int preset) {
|
||||
switch (preset) {
|
||||
case 0:
|
||||
return FFX_FSR4_MODEL_PRESET_NATIVE_AA;
|
||||
case 1:
|
||||
return FFX_FSR4_MODEL_PRESET_QUALITY;
|
||||
case 2:
|
||||
return FFX_FSR4_MODEL_PRESET_BALANCED;
|
||||
case 3:
|
||||
return FFX_FSR4_MODEL_PRESET_PERFORMANCE;
|
||||
default:
|
||||
return FFX_FSR4_MODEL_PRESET_ULTRA_PERFORMANCE;
|
||||
return FFX_FSR4_MODEL_PRESET_BALANCED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +218,8 @@ struct Fsr4Upscaler::Impl {
|
||||
}
|
||||
}
|
||||
|
||||
VkResult Register(const Image& image, VkAccessFlags access) {
|
||||
VkResult Register(const Image& image, VkAccessFlags access,
|
||||
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT) {
|
||||
const FfxFsr4VkExternalImageState state{
|
||||
.structSize = sizeof(FfxFsr4VkExternalImageState),
|
||||
.image = image.image,
|
||||
@@ -229,6 +230,7 @@ struct Fsr4Upscaler::Impl {
|
||||
.restoreLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.restoreStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
.restoreAccessMask = access,
|
||||
.aspectMask = aspect,
|
||||
};
|
||||
return ffxFsr4VkSetExternalImageState(&backend, &state);
|
||||
}
|
||||
@@ -316,15 +318,20 @@ struct Fsr4Upscaler::Impl {
|
||||
// or every later BeginFrame fails (VK_ERROR_VALIDATION_FAILED_EXT).
|
||||
const auto abandon = [&] { in_flight.emplace_back(frame_id, scheduler.CurrentTick()); };
|
||||
constexpr VkAccessFlags read = VK_ACCESS_SHADER_READ_BIT;
|
||||
const std::array<std::pair<const Image*, VkAccessFlags>, 4> images{{
|
||||
{&f.color, read},
|
||||
{&f.depth, read},
|
||||
{&f.motion, read},
|
||||
{&f.output, read | VK_ACCESS_SHADER_WRITE_BIT},
|
||||
// The depth image is D32_SFLOAT_S8_UINT: its barriers name both aspects.
|
||||
constexpr VkImageAspectFlags color_aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
constexpr VkImageAspectFlags depth_aspect =
|
||||
VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
const std::array<std::tuple<const Image*, VkAccessFlags, VkImageAspectFlags>, 4> images{{
|
||||
{&f.color, read, color_aspect},
|
||||
{&f.depth, read, depth_aspect},
|
||||
{&f.motion, read, color_aspect},
|
||||
{&f.output, read | VK_ACCESS_SHADER_WRITE_BIT, color_aspect},
|
||||
}};
|
||||
static constexpr const char* names[] = {"color", "depth", "motion", "output"};
|
||||
for (u32 i = 0; i < images.size(); ++i) {
|
||||
if (const VkResult result = Register(*images[i].first, images[i].second);
|
||||
if (const VkResult result = Register(*std::get<0>(images[i]), std::get<1>(images[i]),
|
||||
std::get<2>(images[i]));
|
||||
result != VK_SUCCESS) {
|
||||
Fail(std::string{"external image registration failed ("} + names[i] + ", " +
|
||||
std::to_string(int(result)) + ")",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
@@ -429,8 +430,15 @@ GraphicsPipeline::GraphicsPipeline(
|
||||
throw Serialization::CorruptData{"cached graphics pipeline rejected by the driver: " +
|
||||
vk::to_string(pipeline_result)};
|
||||
}
|
||||
ASSERT_MSG(pipeline_result == vk::Result::eSuccess, "Failed to create graphics pipeline: {}",
|
||||
vk::to_string(pipeline_result));
|
||||
// bbport: a driver that cannot build one pipeline ("ErrorUnknown" on some AMD and older
|
||||
// drivers) stopped the game here (code 23). The pipeline stays empty instead, and the draws
|
||||
// that use it are skipped (PipelineCache): a missing effect rather than a closed game.
|
||||
if (pipeline_result != vk::Result::eSuccess) {
|
||||
std::printf("GPU: the driver could not create graphics pipeline %s (%s): its draws are "
|
||||
"skipped\n",
|
||||
debug_str.c_str(), vk::to_string(pipeline_result).c_str());
|
||||
return;
|
||||
}
|
||||
pipeline = std::move(pipe);
|
||||
SetObjectName(device, *pipeline, "Graphics Pipeline {}", debug_str);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,25 @@ struct Skipped {
|
||||
s64 NowNs() {
|
||||
return std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
}
|
||||
|
||||
// BB_INDIRECT_GUARD_MAX: the most workgroups one indirect dispatch may launch. A value that is not
|
||||
// a positive number keeps the default: 0 would make the shader skip every indirect dispatch.
|
||||
u32 MaxTotal() {
|
||||
constexpr u32 Default = 1u << 22;
|
||||
const char* text = std::getenv("BB_INDIRECT_GUARD_MAX");
|
||||
if (!text || !*text) {
|
||||
return Default;
|
||||
}
|
||||
char* end = nullptr;
|
||||
const unsigned long long value = std::strtoull(text, &end, 0);
|
||||
if (*end != '\0' || value == 0 || value > 0xFFFFFFFFull) {
|
||||
std::printf("Indirect dispatches: BB_INDIRECT_GUARD_MAX=\"%s\" is not a number from 1 to "
|
||||
"4294967295, keeping the default %u\n",
|
||||
text, Default);
|
||||
return Default;
|
||||
}
|
||||
return static_cast<u32>(value);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool IndirectGuard::Enabled() {
|
||||
@@ -57,8 +76,7 @@ IndirectGuard::IndirectGuard(const Instance& instance_, Scheduler& scheduler_)
|
||||
for (u32 i = 0; i < 3; ++i) {
|
||||
max_count[i] = limits.maxComputeWorkGroupCount[i];
|
||||
}
|
||||
const char* max = std::getenv("BB_INDIRECT_GUARD_MAX");
|
||||
max_total = max && *max ? static_cast<u32>(std::strtoul(max, nullptr, 0)) : 1u << 22;
|
||||
max_total = MaxTotal();
|
||||
|
||||
std::array<vk::DescriptorSetLayoutBinding, 3> bindings{};
|
||||
for (u32 i = 0; i < bindings.size(); ++i) {
|
||||
|
||||
@@ -196,7 +196,7 @@ Instance::Instance(const Frontend::WindowSystemInfo& window_info, s32 physical_d
|
||||
VK_VERSION_MAJOR(TargetVulkanApiVersion), VK_VERSION_MINOR(TargetVulkanApiVersion),
|
||||
VK_VERSION_MAJOR(properties.apiVersion), VK_VERSION_MINOR(properties.apiVersion));
|
||||
|
||||
CreateDevice();
|
||||
ASSERT_MSG(CreateDevice(), "Unable to initialize Vulkan device");
|
||||
CollectPhysicalMemoryInfo();
|
||||
CollectImageFormatInfo();
|
||||
CollectToolingInfo();
|
||||
@@ -459,6 +459,7 @@ bool Instance::CreateDevice() {
|
||||
const auto vk11_features = feature_chain.get<vk::PhysicalDeviceVulkan11Features>();
|
||||
vk12_features = feature_chain.get<vk::PhysicalDeviceVulkan12Features>();
|
||||
vk13_features = feature_chain.get<vk::PhysicalDeviceVulkan13Features>();
|
||||
const auto renderer_extension_count = enabled_extensions.size();
|
||||
// bbport: DLSS (optional bridge library) needs its own device extensions on NVIDIA GPUs.
|
||||
std::vector<const char*> dlss_extensions;
|
||||
if (Dlss* dlss = Dlss::Get()) {
|
||||
@@ -701,6 +702,24 @@ bool Instance::CreateDevice() {
|
||||
}
|
||||
|
||||
auto [device_result, dev] = physical_device.createDeviceUnique(device_chain.get());
|
||||
if (device_result != vk::Result::eSuccess &&
|
||||
enabled_extensions.size() > renderer_extension_count) {
|
||||
// NGX can advertise its extensions even when their runtime dependencies are missing
|
||||
// (e.g. NVIDIA UVM/CUDA on Linux). DLSS is optional: retry the renderer's original
|
||||
// extension set, and keep NGX from initializing against a device without its extensions.
|
||||
LOG_WARNING(Render_Vulkan, "Device creation with DLSS extensions failed: {}; "
|
||||
"retrying without DLSS", vk::to_string(device_result));
|
||||
if (Dlss* dlss = Dlss::Get()) {
|
||||
dlss->Disable(fmt::format("Vulkan device creation failed with DLSS extensions: {} "
|
||||
"(check NVIDIA CUDA/UVM availability)",
|
||||
vk::to_string(device_result)));
|
||||
}
|
||||
enabled_extensions.resize(renderer_extension_count);
|
||||
device_chain.get().enabledExtensionCount = static_cast<u32>(enabled_extensions.size());
|
||||
auto retry = physical_device.createDeviceUnique(device_chain.get());
|
||||
device_result = retry.result;
|
||||
dev = std::move(retry.value);
|
||||
}
|
||||
if (device_result != vk::Result::eSuccess) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Failed to create device: {}", vk::to_string(device_result));
|
||||
return false;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
@@ -37,8 +38,70 @@
|
||||
#include "bbport_guest_memory.h"
|
||||
#include "bbport_settings.h"
|
||||
#include "game_profile.h"
|
||||
#include "bbport_shader_activity.h"
|
||||
#include "bbport_toggles.h"
|
||||
|
||||
// bbport: common/assert.cpp calls it before stopping on a failed check (SpeculativeAssertExit).
|
||||
extern void (*g_assert_speculative_exit)();
|
||||
extern "C" int runtime_memory_vma_info(uintptr_t address, int* prot, int* type, uintptr_t* end);
|
||||
extern "C" const uint64_t* runtime_memory_generation(void);
|
||||
|
||||
namespace {
|
||||
// bbport: a stage's program address points at readable memory. A malformed command buffer (a
|
||||
// register packet whose values are the next packets) set a shader address of 0x4c0012d0000 and
|
||||
// reading the program there ended the game; such a draw or dispatch is skipped instead.
|
||||
template <typename Program>
|
||||
bool ProgramReadable(const Program& pgm) {
|
||||
const auto address = reinterpret_cast<uintptr_t>(pgm.template Address<u32*>());
|
||||
if (!address) {
|
||||
return false;
|
||||
}
|
||||
// Checked for every stage of every draw: the last readable mapping found is remembered for
|
||||
// as long as the mapping table keeps its generation (an odd one is a change in progress).
|
||||
thread_local uintptr_t known_start = 0, known_end = 0;
|
||||
thread_local uint64_t known_generation = 1;
|
||||
const uint64_t generation = __atomic_load_n(runtime_memory_generation(), __ATOMIC_ACQUIRE);
|
||||
if (generation == known_generation && address >= known_start && address < known_end) {
|
||||
return true;
|
||||
}
|
||||
int prot = 0, type = -1;
|
||||
uintptr_t end = 0;
|
||||
if (!runtime_memory_vma_info(address, &prot, &type, &end)) {
|
||||
return true; // not a guest mapping (the executable's image)
|
||||
}
|
||||
if ((prot & 1) == 0) {
|
||||
static std::atomic<int> reports{0};
|
||||
if (reports.fetch_add(1, std::memory_order_relaxed) < 8) {
|
||||
std::fprintf(stderr, "Pipeline: shader program address %#llx is not readable; the draw "
|
||||
"is skipped\n", (unsigned long long)address);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if ((generation & 1) == 0) {
|
||||
known_start = address; // the mapping's start is not known: from here on
|
||||
known_end = end;
|
||||
known_generation = generation;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// bbport: a check that fails while a speculative read has its recovery point armed (draw
|
||||
// preparation ahead of the GPU thread specializing a shader from descriptors the game may still
|
||||
// be rewriting) ends that attempt like a fault does, and the GPU thread prepares the draw in
|
||||
// order. Anywhere else the assertion still stops.
|
||||
void SpeculativeAssertExit() {
|
||||
sigjmp_buf* const recover = runtime_fault_recover;
|
||||
if (!recover) {
|
||||
return;
|
||||
}
|
||||
runtime_fault_recover = nullptr;
|
||||
std::fputs("GPU: speculative draw preparation abandoned after the check above; the GPU "
|
||||
"thread prepares this draw in order\n",
|
||||
stderr);
|
||||
siglongjmp(*recover, 1);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
using Shader::HwStage;
|
||||
@@ -292,6 +355,7 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
||||
: instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_},
|
||||
desc_heap{instance, scheduler.GetWorkSemaphore(), DescriptorHeapSizes} {
|
||||
sel.regs = &liverpool->regs;
|
||||
g_assert_speculative_exit = SpeculativeAssertExit;
|
||||
const auto& vk12_props = instance.GetVk12Properties();
|
||||
profile = Shader::Profile{
|
||||
.max_viewport_width = instance.GetMaxViewportWidth(),
|
||||
@@ -434,7 +498,11 @@ std::atomic<u32> g_bb_compiles;
|
||||
namespace {
|
||||
struct CompileTimer {
|
||||
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
|
||||
CompileTimer() {
|
||||
BbShaderActivity::Begin();
|
||||
}
|
||||
~CompileTimer() {
|
||||
BbShaderActivity::End();
|
||||
g_bb_compile_ns += u64(std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now() - start)
|
||||
.count());
|
||||
@@ -463,7 +531,7 @@ const GraphicsPipeline* PipelineCache::TryPreparedPipeline(const PreparedDraw& p
|
||||
for (u32 i = 0; i < prepared.num_stages; ++i) {
|
||||
const auto& stage = prepared.stages[i];
|
||||
const auto* pgm = regs.ProgramForStage(static_cast<u32>(stage.hw_stage));
|
||||
if (!pgm || !pgm->Address<u32*>()) {
|
||||
if (!pgm || !ProgramReadable(*pgm)) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto params = AmdGpu::GetParams(*pgm);
|
||||
@@ -606,6 +674,13 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline(const DrawIndirectPar
|
||||
it.value() = std::make_unique<GraphicsPipeline>(
|
||||
instance, scheduler, desc_heap, profile, sel.graphics_key, *pipeline_cache, sel.infos,
|
||||
sel.runtime_infos, sel.fetch_shader, sel.modules, sdata, false);
|
||||
// bbport: the driver could not create it. The empty entry keeps the key from compiling
|
||||
// again on every draw (they are skipped); it is not saved, so the next start tries anew.
|
||||
if (!it.value()->IsValid()) {
|
||||
it.value().reset();
|
||||
sel.fetch_shader.reset();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RegisterPipelineData(sel.graphics_key, pipeline_hash, sdata);
|
||||
++num_new_pipelines;
|
||||
@@ -625,6 +700,10 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline(const DrawIndirectPar
|
||||
|
||||
const GraphicsPipeline* PipelineCache::FinishPipeline(const GraphicsPipelineKey& key,
|
||||
PendingPipeline& job) {
|
||||
if (!job.pipeline || !job.pipeline->IsValid()) {
|
||||
graphics_pipelines[key].reset(); // bbport: as GetGraphicsPipeline, skipped and not saved
|
||||
return nullptr;
|
||||
}
|
||||
const auto pipeline_hash = std::hash<GraphicsPipelineKey>{}(key);
|
||||
RegisterPipelineData(key, pipeline_hash, job.sdata);
|
||||
++num_new_pipelines;
|
||||
@@ -706,6 +785,10 @@ const ComputePipeline* PipelineCache::GetComputePipeline() {
|
||||
it.value() = std::make_unique<ComputePipeline>(instance, scheduler, desc_heap, profile,
|
||||
*pipeline_cache, compute_key, *sel.infos[0],
|
||||
sel.modules[0], sdata, false);
|
||||
if (!it.value()->IsValid()) { // bbport: as for graphics pipelines
|
||||
it.value().reset();
|
||||
return nullptr;
|
||||
}
|
||||
RegisterPipelineData(compute_key, sdata);
|
||||
++num_new_pipelines;
|
||||
|
||||
@@ -914,7 +997,7 @@ bool PipelineCache::RefreshGraphicsStages(PipelineSelection& sel) {
|
||||
}
|
||||
|
||||
const auto* pgm = regs.ProgramForStage(stage_in_idx);
|
||||
if (!pgm || !pgm->Address<u32*>()) {
|
||||
if (!pgm || !ProgramReadable(*pgm)) {
|
||||
key.stage_hashes[stage_out_idx] = 0;
|
||||
sel.infos[stage_out_idx] = nullptr;
|
||||
return false;
|
||||
@@ -1025,6 +1108,9 @@ bool PipelineCache::RefreshGraphicsStages(PipelineSelection& sel) {
|
||||
bool PipelineCache::RefreshComputeKey() {
|
||||
Shader::Backend::Bindings binding{};
|
||||
const auto& cs_pgm = liverpool->GetCsRegs();
|
||||
if (!ProgramReadable(cs_pgm)) {
|
||||
return false;
|
||||
}
|
||||
const auto cs_params = AmdGpu::GetParams(cs_pgm);
|
||||
std::tie(sel.infos[0], sel.modules[0], sel.fetch_shader, compute_key.value) =
|
||||
GetProgram(sel, HwStage::Compute, SwStage::Compute, cs_params, binding);
|
||||
@@ -1094,10 +1180,25 @@ PipelineCache::Result PipelineCache::GetProgram(PipelineSelection& sel, HwStage
|
||||
return {};
|
||||
}
|
||||
runtime_fault_recover = &recover;
|
||||
info.RefreshFlatBuf();
|
||||
auto spec = Shader::StageSpecialization(info, runtime_info, profile, binding);
|
||||
// A C++ exception from the same data (a container over its capacity, an empty optional)
|
||||
// escaped the worker and ended the process through std::terminate: it is treated like
|
||||
// a fault. A fault's jump out of the try block does not unwind, so it is unaffected.
|
||||
std::optional<Shader::StageSpecialization> spec;
|
||||
try {
|
||||
info.RefreshFlatBuf();
|
||||
spec.emplace(info, runtime_info, profile, binding);
|
||||
} catch (const std::exception& e) {
|
||||
runtime_fault_recover = nullptr;
|
||||
static std::atomic<u32> logged{0};
|
||||
if (logged.fetch_add(1, std::memory_order_relaxed) < 8) {
|
||||
LOG_WARNING(Render_Vulkan, "Draw preparation ahead of the GPU thread stopped: {}",
|
||||
e.what());
|
||||
}
|
||||
worker.failed = true;
|
||||
return {};
|
||||
}
|
||||
runtime_fault_recover = nullptr;
|
||||
const auto it = std::ranges::find(program->modules, spec, &Program::Module::spec);
|
||||
const auto it = std::ranges::find(program->modules, *spec, &Program::Module::spec);
|
||||
if (it == program->modules.end()) {
|
||||
worker.failed = true;
|
||||
return {};
|
||||
|
||||
@@ -36,6 +36,11 @@ public:
|
||||
return *pipeline;
|
||||
}
|
||||
|
||||
/// bbport: false when the driver could not create the pipeline (its draws are skipped).
|
||||
bool IsValid() const noexcept {
|
||||
return static_cast<bool>(pipeline);
|
||||
}
|
||||
|
||||
vk::PipelineLayout GetLayout() const noexcept {
|
||||
return *pipeline_layout;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <csetjmp>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
@@ -95,6 +96,30 @@ bool CanBlitToSwapchain(const vk::PhysicalDevice physical_device, vk::Format for
|
||||
return MakeImageBlit(frame_width, frame_height, swapchain_width, swapchain_height, 0, 0);
|
||||
}
|
||||
|
||||
// bbport: a frame's fence, waited for in 10 s slices. Past the first slice the log says so, once
|
||||
// per wait, with the breadcrumbs (which command the GPU has not finished): without it a GPU that
|
||||
// never finishes the frame froze the game with nothing in the log (issue #5).
|
||||
static vk::Result WaitFrameFence(vk::Device device, vk::Fence fence, const char* where) {
|
||||
constexpr u64 slice = 10'000'000'000ULL;
|
||||
bool told = false;
|
||||
while (true) {
|
||||
const vk::Result result = device.waitForFences(fence, false, slice);
|
||||
if (result != vk::Result::eTimeout) {
|
||||
if (told) {
|
||||
std::printf("Presenter: %s finished after all\n", where);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (!told) {
|
||||
told = true;
|
||||
std::printf("Presenter: %s not finished after 10 s: the GPU is stuck on it\n", where);
|
||||
std::fflush(stdout);
|
||||
Breadcrumbs::ReportStuck(where);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static vk::Rect2D FitImage(s32 frame_width, s32 frame_height, s32 swapchain_width,
|
||||
s32 swapchain_height) {
|
||||
float frame_aspect = static_cast<float>(frame_width) / frame_height;
|
||||
@@ -251,14 +276,11 @@ Frame* Presenter::PrepareLastFrame() {
|
||||
Frame* frame = last_submit_frame;
|
||||
|
||||
while (true) {
|
||||
vk::Result result = instance.GetDevice().waitForFences(frame->present_done, false,
|
||||
std::numeric_limits<u64>::max());
|
||||
vk::Result result =
|
||||
WaitFrameFence(instance.GetDevice(), frame->present_done, "the last frame");
|
||||
if (result == vk::Result::eSuccess) {
|
||||
break;
|
||||
}
|
||||
if (result == vk::Result::eTimeout) {
|
||||
continue;
|
||||
}
|
||||
if (result == vk::Result::eErrorDeviceLost) {
|
||||
Breadcrumbs::ReportDeviceLost("waiting for a frame");
|
||||
}
|
||||
@@ -760,7 +782,7 @@ Frame* Presenter::GetRenderFrame() {
|
||||
vk::Result result{};
|
||||
|
||||
const auto wait = [&]() {
|
||||
result = device.waitForFences(frame->present_done, false, std::numeric_limits<u64>::max());
|
||||
result = WaitFrameFence(device, frame->present_done, "a frame to draw into");
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
@@ -1701,7 +1701,8 @@ void Rasterizer::DrawRecord(const GraphicsPipeline* pipeline, const PreparedDraw
|
||||
break;
|
||||
}
|
||||
const VAddr address = buffer.base_address;
|
||||
if (role == Motion::BufferRole::SmallSkeleton &&
|
||||
if ((role == Motion::BufferRole::SmallSkeleton ||
|
||||
role == Motion::BufferRole::LargeSkeleton) &&
|
||||
memory->IsValidGpuMapping(address, 0) &&
|
||||
memory->ClampRangeSize(address, size) == size) {
|
||||
palette = XXH3_64bits_withSeed(reinterpret_cast<const void*>(address), size,
|
||||
|
||||
@@ -17,6 +17,17 @@
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
/// bbport: records `regions` as one command per slice. A deferred command's data shares a
|
||||
/// recording chunk with it (128 KiB), and a download of the buffer cache can carry far more
|
||||
/// regions than that (178563 copies, 4.3 MB, loading an area on NVIDIA in 0.5).
|
||||
template <typename T, typename Record>
|
||||
static void RecordInSlices(Scheduler& scheduler, std::span<const T> regions, Record&& record) {
|
||||
constexpr std::size_t PerCommand = 64 * 1024 / sizeof(T);
|
||||
for (std::size_t at = 0; at < regions.size(); at += PerCommand) {
|
||||
record(scheduler.RecordData(regions.subspan(at, std::min(PerCommand, regions.size() - at))));
|
||||
}
|
||||
}
|
||||
|
||||
static vk::ImageType ConvertImageType(AmdGpu::ImageType type) noexcept {
|
||||
switch (type) {
|
||||
case AmdGpu::ImageType::Color1D:
|
||||
@@ -142,9 +153,11 @@ void Runtime::CopyBuffer(const VideoCore::Buffer* src, const VideoCore::Buffer*
|
||||
FlushBarriers();
|
||||
}
|
||||
|
||||
scheduler.Record([src_handle = src->Handle(), dst_handle = dst->Handle(),
|
||||
regions = scheduler.RecordData(copies)](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.copyBuffer(src_handle, dst_handle, regions.size(), regions.data());
|
||||
RecordInSlices(scheduler, copies, [&](std::span<const vk::BufferCopy> regions) {
|
||||
scheduler.Record([src_handle = src->Handle(), dst_handle = dst->Handle(),
|
||||
regions](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.copyBuffer(src_handle, dst_handle, regions.size(), regions.data());
|
||||
});
|
||||
});
|
||||
|
||||
if (bounded) {
|
||||
@@ -190,9 +203,10 @@ void Runtime::CopyFromGuestChunk(vk::Buffer src, const VideoCore::Buffer* dst,
|
||||
if (needs_flush) {
|
||||
FlushBarriers();
|
||||
}
|
||||
scheduler.Record([src, dst_handle = dst->Handle(),
|
||||
regions = scheduler.RecordData(copies)](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.copyBuffer(src, dst_handle, regions.size(), regions.data());
|
||||
RecordInSlices(scheduler, copies, [&](std::span<const vk::BufferCopy> regions) {
|
||||
scheduler.Record([src, dst_handle = dst->Handle(), regions](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.copyBuffer(src, dst_handle, regions.size(), regions.data());
|
||||
});
|
||||
});
|
||||
for (const auto& copy : copies) {
|
||||
AccessBuffer(dst, copy.dstOffset, copy.size, vk::PipelineStageFlagBits2::eCopy,
|
||||
@@ -286,10 +300,13 @@ void Runtime::UploadImage(VideoCore::Image* dst, const VideoCore::Buffer* src,
|
||||
FlushBarriers();
|
||||
}
|
||||
|
||||
scheduler.Record([src_handle = src->Handle(), image = dst->GetImage(),
|
||||
regions = scheduler.RecordData(upload_copies)](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.copyBufferToImage(src_handle, image, vk::ImageLayout::eTransferDstOptimal,
|
||||
regions.size(), regions.data());
|
||||
RecordInSlices(scheduler, std::span<const vk::BufferImageCopy>{upload_copies},
|
||||
[&](std::span<const vk::BufferImageCopy> regions) {
|
||||
scheduler.Record([src_handle = src->Handle(), image = dst->GetImage(),
|
||||
regions](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.copyBufferToImage(src_handle, image, vk::ImageLayout::eTransferDstOptimal,
|
||||
regions.size(), regions.data());
|
||||
});
|
||||
});
|
||||
|
||||
for (const auto& copy : upload_copies) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <limits>
|
||||
#include <cstdio>
|
||||
#include "video_core/renderer_vulkan/vk_breadcrumbs.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_semaphore.h"
|
||||
@@ -10,7 +10,8 @@
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
constexpr u64 WAIT_TIMEOUT = std::numeric_limits<u64>::max();
|
||||
// bbport: waits go in 10 s slices so a GPU that never finishes is reported, not waited on silently.
|
||||
constexpr u64 WAIT_TIMEOUT = 10'000'000'000ULL;
|
||||
|
||||
Semaphore::Semaphore(const Instance& instance_) : instance{instance_} {
|
||||
const vk::StructureChain semaphore_chain = {
|
||||
@@ -66,12 +67,28 @@ void Semaphore::Wait(u64 tick) {
|
||||
.pValues = &tick,
|
||||
};
|
||||
|
||||
// bbport: a lost device ends the wait with a report instead of a silent endless loop.
|
||||
// bbport: a lost device ends the wait with a report instead of a silent endless loop, and a
|
||||
// submission still not finished after 10 s says so once with the GPU breadcrumbs.
|
||||
bool told = false;
|
||||
while (true) {
|
||||
const vk::Result result = instance.GetDevice().waitSemaphores(&wait_info, WAIT_TIMEOUT);
|
||||
if (result == vk::Result::eSuccess) {
|
||||
if (told) {
|
||||
std::printf("GPU: submission %llu finished after all\n", (unsigned long long)tick);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (result == vk::Result::eTimeout) {
|
||||
if (!told) {
|
||||
told = true;
|
||||
std::printf("GPU: submission %llu not finished after 10 s: the GPU is stuck on it\n",
|
||||
(unsigned long long)tick);
|
||||
std::fflush(stdout);
|
||||
Breadcrumbs::ReportStuck("waiting for a submission");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (result == vk::Result::eErrorDeviceLost) {
|
||||
Breadcrumbs::ReportDeviceLost("waiting for a submission");
|
||||
ASSERT_MSG(false, "Device lost while waiting for a submission");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -120,9 +121,25 @@ void Swapchain::SetHDR(bool hdr) {
|
||||
|
||||
bool Swapchain::AcquireNextImage() {
|
||||
vk::Device device = instance.GetDevice();
|
||||
vk::Result result =
|
||||
device.acquireNextImageKHR(swapchain, std::numeric_limits<u64>::max(),
|
||||
image_acquired[frame_index], VK_NULL_HANDLE, &image_index);
|
||||
// bbport: in 10 s slices, saying so once when the compositor holds every image that long
|
||||
// (issue #5: a frozen game with nothing in the log).
|
||||
vk::Result result;
|
||||
bool told = false;
|
||||
while ((result = device.acquireNextImageKHR(swapchain, 10'000'000'000ULL,
|
||||
image_acquired[frame_index], VK_NULL_HANDLE,
|
||||
&image_index)) == vk::Result::eTimeout ||
|
||||
result == vk::Result::eNotReady) {
|
||||
if (!told) {
|
||||
told = true;
|
||||
std::printf("Swapchain: no image from the compositor in 10 s (it holds all %zu)\n",
|
||||
images.size());
|
||||
std::fflush(stdout);
|
||||
}
|
||||
}
|
||||
if (told) {
|
||||
std::printf("Swapchain: the compositor gave an image back\n");
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
switch (result) {
|
||||
case vk::Result::eSuccess:
|
||||
|
||||
@@ -1471,6 +1471,22 @@ void TemporalUpscaler::OnDraw(u64 vs_hash, VideoCore::ImageId color,
|
||||
}
|
||||
return;
|
||||
}
|
||||
// bbport: a UI draw without a color target (a Scaleform mask: color writes off) writes the
|
||||
// stencil that the UI's next draws test. It must go into the UI's output-size depth like
|
||||
// them: drawn into the guest depth, the mask was lost when the next UI draw switched to the
|
||||
// UI depth (PrepareUiDepth clears its stencil), and masked images (the loading screen's item
|
||||
// picture, cut out of a sheet of item pictures) vanished.
|
||||
if (ui_phase && !color && depth && depth != ui_depth && ui_color) {
|
||||
const auto& depth_image = texture_cache.GetImage(depth);
|
||||
const auto& ui_target = texture_cache.GetImage(ui_color);
|
||||
if (depth_image.info.size.width == ui_target.info.size.width &&
|
||||
depth_image.info.size.height == ui_target.info.size.height) {
|
||||
EnsureUiResources(ui_width, ui_height, ui_format, depth_image.info.pixel_format);
|
||||
PrepareUiDepth(depth);
|
||||
ui_depth = depth;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!Scaled() || !color) {
|
||||
return;
|
||||
}
|
||||
|
||||
Loaded 100 of 171 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user