mirror of
https://github.com/ayozetr/bloodborne_linux.git
synced 2026-11-08 01:08:25 -10:00
Compare commits
136
Commits
0.51-linux.2
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98750d0687 | ||
|
|
0b3dac11d7 | ||
|
|
5041586f9a | ||
|
|
e77bce746f | ||
|
|
d1dea1b8e4 | ||
|
|
6796fbbd13 | ||
|
|
badd460277 | ||
|
|
1d0bbec978 | ||
|
|
843424de13 | ||
|
|
21e1ce4224 | ||
|
|
498a37d24b | ||
|
|
e8c7eaaf9c | ||
|
|
74b1a30870 | ||
|
|
62dbeefa6b | ||
|
|
7c6b25a560 | ||
|
|
43ffa68201 | ||
|
|
0f90125d4a | ||
|
|
02dc648c96 | ||
|
|
c16b6d3d58 | ||
|
|
b4f75ec15c | ||
|
|
c8373033ad | ||
|
|
42e8b4c606 | ||
|
|
afac1a4f39 | ||
|
|
4c1e2e3061 | ||
|
|
ab3088469a | ||
|
|
dbc374fcec | ||
|
|
8e9dbc20e1 | ||
|
|
027e834a79 | ||
|
|
0219c34163 | ||
|
|
f0306a62a1 | ||
|
|
5fd9e2ccb5 | ||
|
|
311aeb36ff | ||
|
|
3963108624 | ||
|
|
afef07c90f | ||
|
|
aae3f0566f | ||
|
|
dff4079ef6 | ||
|
|
e0543751fb | ||
|
|
76ebf406f3 | ||
|
|
c27d8fb36b | ||
|
|
2719bce35f | ||
|
|
8cfcdc1f76 | ||
|
|
a68b6a75c8 | ||
|
|
2bdee3185a | ||
|
|
6621a1f0a7 | ||
|
|
e2ac43b725 | ||
|
|
614602cf36 | ||
|
|
f0068ecb34 | ||
|
|
1f98a632b5 | ||
|
|
4e0aa911d2 | ||
|
|
312d119b5a | ||
|
|
0d34da1577 | ||
|
|
28eac08195 | ||
|
|
f0aa0fa2ae | ||
|
|
983c89fa9a | ||
|
|
da3ff0ecb0 | ||
|
|
d698ad290f | ||
|
|
e5fde7ed25 | ||
|
|
7c4a740338 | ||
|
|
91f63ff553 | ||
|
|
7331bb9410 | ||
|
|
2399b7a708 | ||
|
|
cefd12304c | ||
|
|
e1239378d6 | ||
|
|
7f8cbf12a4 | ||
|
|
7f69b82128 | ||
|
|
edfb811965 | ||
|
|
4a6177cf06 | ||
|
|
a238c6937b | ||
|
|
1441367174 | ||
|
|
8c6aeeb1c5 | ||
|
|
ae6c8728f0 | ||
|
|
5c8bf041d7 | ||
|
|
c93a1f915c | ||
|
|
b2a3990de2 | ||
|
|
4e715f48d5 | ||
|
|
5b8ac8efa5 | ||
|
|
d96d384538 | ||
|
|
683b593f45 | ||
|
|
a0ec0efaf3 | ||
|
|
90ba06f7fc | ||
|
|
456532bfa7 | ||
|
|
59da2f9a6f | ||
|
|
47085f688c | ||
|
|
10732fb349 | ||
|
|
e4d879ef3c | ||
|
|
c8bfa7f54c | ||
|
|
61824c2b2a | ||
|
|
a9dcce1eac | ||
|
|
96bd219320 | ||
|
|
c0cde157e3 | ||
|
|
fcca90e6fa | ||
|
|
0fd636c7f0 | ||
|
|
4bac966821 | ||
|
|
32383ee5d6 | ||
|
|
8cf70f6492 | ||
|
|
1bd6314b69 | ||
|
|
39a2174f47 | ||
|
|
a5e89fd8ac | ||
|
|
e64655f7f6 | ||
|
|
570ee9b579 | ||
|
|
fc17a2d7f4 | ||
|
|
afadddfaf9 | ||
|
|
63be02c67f | ||
|
|
3ed9330a6a | ||
|
|
f47ef822ee | ||
|
|
2824ea7ab2 | ||
|
|
a48a131590 | ||
|
|
8cff10a374 | ||
|
|
b902cbc5ff | ||
|
|
cddb964ebd | ||
|
|
cfc63a6766 | ||
|
|
64f7962cac | ||
|
|
1cb0fd24ed | ||
|
|
b2a6130b42 | ||
|
|
ed9d812b85 | ||
|
|
bdd7994cca | ||
|
|
4247abab8e | ||
|
|
fa0d7232d9 | ||
|
|
b2f516f0e0 | ||
|
|
773368a150 | ||
|
|
0d091c9335 | ||
|
|
95c548e6f1 | ||
|
|
55eb72e933 | ||
|
|
40031dd622 | ||
|
|
1eadf67c8c | ||
|
|
b8bb54a791 | ||
|
|
f760e22ebf | ||
|
|
c61dc69ae7 | ||
|
|
05807030a2 | ||
|
|
64660056b4 | ||
|
|
c2a45f36e6 | ||
|
|
47b276b389 | ||
|
|
71e6cc9592 | ||
|
+24 |
0fc2921c45 | ||
|
|
8185b88d80 | ||
|
|
72e5c8ce22 |
No files matched your search
@@ -0,0 +1,3 @@
|
||||
# GitHub's language bar: this C++ table of game texts (its <?name?> placeholders) is not PHP.
|
||||
gpu/bbnet20/bbsc/core/bbsc_messages.inc linguist-language=C++
|
||||
gpu/bbnet20/bbsc/core/bbsc_translations.inc linguist-language=C++
|
||||
@@ -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, with the build of a test build; so does the first line of the log.
|
||||
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).
|
||||
+8
-2
@@ -23,8 +23,6 @@ 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
|
||||
@@ -32,6 +30,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
|
||||
# The mods folder of runs from source (the launcher's default, DATA_DIR/mods), and the list of the
|
||||
# last run's mods
|
||||
/mods/
|
||||
/mods.last
|
||||
# Game files and dumps: never in the repository
|
||||
eboot.bin
|
||||
param.sfo
|
||||
@@ -39,3 +41,7 @@ param.sfo
|
||||
*.dcx
|
||||
*.7z
|
||||
/CUSA*/
|
||||
# The launcher's icon (shipped)
|
||||
!launcher/bloodborne.png
|
||||
# The players' guide, built by mkdocs (mkdocs.yml)
|
||||
_site/
|
||||
@@ -1,28 +1,23 @@
|
||||
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
|
||||
# bbport — a native Linux and Windows 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)
|
||||
> This is the README of the original project (deadinside28/bloodborne_pc, version 0.7), kept for
|
||||
> its technical details. The Linux fork's own README is [README.md](README.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:
|
||||
(CUSA03173, game version 1.09) on an x86-64 PC with Linux or Windows 10/11 (since 0.7, see
|
||||
[Windows](#windows)). 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).
|
||||
- **as in DXVK**, the game's graphics are translated to Vulkan — by the port's own renderer and
|
||||
shader translator (they started from [shadPS4](https://github.com/shadps4-emu/shadPS4)'s and were
|
||||
rewritten), including temporal upscaling with AMD FSR 3.1, FSR 4, FSR 4.1.1 and NVIDIA DLSS,
|
||||
and DLSS Frame Generation;
|
||||
- memory and the GPU's work follow the PC model on every GPU: the new memory and translation
|
||||
model is the only one (the default since 0.6; the old model of 0.3 was removed in 0.7).
|
||||
|
||||
> **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
|
||||
@@ -31,19 +26,17 @@ memory model (see below).
|
||||
|
||||
**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.
|
||||
A full play-through has not been verified. Tested thoroughly: AMD Radeon RX 7800 XT (Linux with
|
||||
Mesa/RADV, and Windows 11), the Steam Deck; on Windows also two laptops with NVIDIA GTX 1050 and
|
||||
GeForce 930MX; other NVIDIA and Intel GPUs by players.
|
||||
|
||||
## 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
|
||||
- **One memory and translation model** (the model of 0.3 is gone):
|
||||
- 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;
|
||||
@@ -51,25 +44,37 @@ Mesa/RADV) has been tested thoroughly.
|
||||
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`.
|
||||
processor writes them. On every GPU the game's memory goes through the layer's memory module:
|
||||
bound in place, VRAM copies as separate buffers, no sparse binding (NVIDIA's sparse binding
|
||||
froze the GPU for seconds; on AMD the module is faster than the former sparse arena too);
|
||||
`BB_LAYER_MEMORY=0` gives AMD's sparse arena. Tested thoroughly on an RX 7800 XT; on NVIDIA
|
||||
by testers. A driver that fails the startup checks stops the game with a line beginning
|
||||
`STOP: the GPU driver cannot use the game's memory in place`.
|
||||
|
||||
[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.
|
||||
The graphics heaps of the game grow as it takes memory and give it back when freed.
|
||||
|
||||
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.
|
||||
[Changes in 0.7](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/CHANGES_0.7.md), [0.6](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/CHANGES_0.6.md), [0.6-pre1](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/CHANGES_0.6-pre1.md),
|
||||
[0.51](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/CHANGES_0.51.md), [0.5](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/CHANGES_0.5.md).
|
||||
|
||||
Unused textures are freed, so VRAM no longer grows with every area visited; *Texture quality*
|
||||
(full, half, quarter, auto = half on cards with up to 3 GB) skips the top levels of big
|
||||
textures on small cards.
|
||||
- **Unlocked frame rate.** Hooks in the game's code make the simulation use the real frame time
|
||||
(the community patches in `patches/Bloodborne.xml` remain for comparison, `BB_FRAME_HOOKS=0`);
|
||||
~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; **F3** switches a 30 FPS limit on and off while playing (some
|
||||
cinematics, e.g. Laurence's, are safest at 30).
|
||||
- **Any aspect ratio, live.** 4:3, 16:10, 16:9, 21:9 and 32:9 with the HUD at the screen's edges,
|
||||
through hooks (the game's code is not patched); cinematics are wide too.
|
||||
- **Keyboard and mouse.** Mouse look turns the game's camera directly; a mouse pointer in the
|
||||
game's menus (F1 releases the mouse); a layout close to Dark Souls III's (Space dodge, Left
|
||||
Shift run, Shift+Space jump); movement keys ramp up like a stick; button prompts show the bound
|
||||
key, or the buttons of the gamepad in use (Xbox and others), PlayStation icons only with a
|
||||
PlayStation pad.
|
||||
- **Online play and seamless co-op.** Two pools on shadPS4 0.20's network stack: **Seamless
|
||||
Hunt** (default; bells, summons, messages, bloodstains and seamless co-op, BBSC, up to five
|
||||
hunters; an account from seamlesshunt.com) and **The Hunter's Dream** (sign-in with Discord; no
|
||||
BBSC).
|
||||
- **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
|
||||
@@ -84,6 +89,18 @@ Mesa/RADV) has been tested thoroughly.
|
||||
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`.
|
||||
- **DLSS Super Resolution and DLSS Frame Generation** (NVIDIA GeForce RTX) through an
|
||||
optional bridge built against NVIDIA's SDK (see Building). Frame generation makes the frames
|
||||
between two of the game's (2x on RTX 40, up to 4x on RTX 50) from the finished frame, the
|
||||
frame before its UI, the scene depth, the motion vectors and the game's camera matrices;
|
||||
*Overlay menu → Frame generation*, or the launcher.
|
||||
- **FSR 4.1.2's models from the AMD driver** (INT8 on RDNA2/3 and the *Lite* model), taken
|
||||
from the driver's `amdxcffx64.dll` with the upscaler DLL (*FSR 4 model* in the menus).
|
||||
- **FSR 1** (spatial, no motion vectors) for weak GPUs, and **FSR 3.1 frame generation**
|
||||
(experimental, every GPU; the port's own optical-flow and interpolation shaders).
|
||||
- **Anti-aliasing** separate from the upscalers: *auto* (the game's AA only without an
|
||||
upscaler), the game's, FXAA (Kyo's *Force FXAA2*), SMAA 1x (the port's own, before the UI), off;
|
||||
sharpening (RCAS) also without an upscaler. **GT-VBAO** can replace the game's SSAO.
|
||||
- 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.
|
||||
@@ -91,12 +108,19 @@ Mesa/RADV) has been tested thoroughly.
|
||||
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
|
||||
- **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. Chromatic aberration,
|
||||
depth of field, motion blur, SSAO, the game's AA and dynamic-light shadows switch at once;
|
||||
screen-space reflections, the intro, free camera and the debug menu need a restart.
|
||||
- **Overlay menu** (Insert, or hold 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.
|
||||
- **Shaders before you play.** A pack of shader recipes from players' caches (655 programs, 1149
|
||||
pipelines) is compiled in the background at start, and the cache survives updates: a repeat
|
||||
session compiles nothing.
|
||||
- **Launchers:** GTK4 on Linux, WPF in the Windows 11 style on Windows (both with sections and
|
||||
descriptions); an **AppImage** for Linux and the Steam Deck, the same as a **folder**
|
||||
(`tar.zst`, `Bloodborne.sh`), a **zip** for Windows.
|
||||
|
||||
## How it differs from shadPS4
|
||||
|
||||
@@ -104,16 +128,17 @@ Mesa/RADV) has been tested thoroughly.
|
||||
|---|---|---|
|
||||
| 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 |
|
||||
| Memory | The GPU's view of PS4 memory is kept in VRAM copies, synchronized through page-protection write tracking | 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 | 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 | shadPS4 video core and shader recompiler | Its own command processor, renderer, caches and shader translator (they started from shadPS4's, `gpu/VENDOR.txt`): two-stage draw pipeline, render-state and texture-set memoization, render-scale proxies, motion vectors, FSR 3.1/4/4.1.1, DLSS, 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 |
|
||||
| Upscaling | — | Temporal (FSR 3.1, FSR 4, FSR 4.1.1/4.1.2, DLSS) with motion vectors computed for the game and jitter; frame generation (DLSS, FSR 3.1) |
|
||||
| Platforms | Linux, Windows, macOS | Linux and Windows (x86-64) |
|
||||
| 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.
|
||||
Without shadPS4 there would be no bbport: its renderer and shader recompiler were the starting
|
||||
point of the graphics side.
|
||||
|
||||
### Wine + DXVK for one game
|
||||
|
||||
@@ -122,8 +147,8 @@ the graphics side.
|
||||
- **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.
|
||||
- **Memory.** As in a PC game (the default model): the game's data in system RAM, VRAM used the
|
||||
way a PC game uses it.
|
||||
- **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
|
||||
@@ -136,22 +161,20 @@ the graphics side.
|
||||
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).
|
||||
[docs/EMULATION_REMOVAL_PLAN.ru.md](https://github.com/deadinside28/bloodborne_pc/blob/master/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.
|
||||
- Linux x86-64 or Windows 10/11 x86-64, a Vulkan 1.3 GPU. Tested: AMD RX 7800 XT with Mesa 26
|
||||
(RADV) and with AMD's Windows driver; NVIDIA and Intel by players (please report how they run).
|
||||
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
|
||||
@@ -187,22 +210,13 @@ its main menu offline (a game patch; the launcher's *Game effects → Skip the
|
||||
`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.
|
||||
**Resolution and preset changes:** the game starts at the scene size of the chosen output
|
||||
and preset (a patch at start, the fastest path); the output is the monitor's resolution by
|
||||
default (*Auto*). With *Live resolution changes* (on by default; `bbport.ini`
|
||||
`live_resolution=0|1`) the preset, the output resolution and the aspect ratio then switch while
|
||||
playing, to smaller and to larger sizes, at the same FPS as a start at that size; only another
|
||||
upscaler kind needs a restart. GPUs that cannot give the game's depth a proxy (no D32S8 blits
|
||||
and no shader stencil export, e.g. GTX 10xx) apply a change at the next start instead.
|
||||
|
||||
**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
|
||||
@@ -214,27 +228,24 @@ 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.
|
||||
The original game is preserved; later mods override conflicting files. Texture packs with
|
||||
upscaled map textures (from the mods folder or already in the game folder) get larger graphics
|
||||
heaps by themselves (`BB_LARGE_HEAPS=1/0` forces it on or off).
|
||||
**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).
|
||||
switched on and off in the launcher. See [mods and patches](https://github.com/deadinside28/bloodborne_pc/blob/master/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.
|
||||
**Launcher language:** Russian, English, Brazilian Portuguese, Simplified Chinese or French (follows the system language by default).
|
||||
The game's own text language is chosen separately, from all 21 languages the game has.
|
||||
**GPU choice:** *Mode → GPU* picks the graphics card on machines with two (*Auto* prefers the
|
||||
discrete one; stored by name; `BB_GPU_ID=N` from the shell wins).
|
||||
|
||||
**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
|
||||
hold Cross and press L3 to cycle modes (keyboard: hold F and press Left Ctrl). 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
|
||||
@@ -242,11 +253,11 @@ For the game debug menu, install `DbgFont14h.ccm` and `DbgFont14h.tpf` from
|
||||
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.
|
||||
pads without a touch surface. The port's settings menu remains Insert / holding 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.
|
||||
With the *Restore Debug Camera* patch the mouse turns the free camera. The game's occlusion
|
||||
queries are Vulkan occlusion queries in the default model (`BB_OCCLUSION_QUERIES=0`: everything
|
||||
counts as visible, as in the old model).
|
||||
|
||||
**Upscaler assets** (not included; FSR 3.1 needs none):
|
||||
|
||||
@@ -267,7 +278,8 @@ builds (4.0.2b, 4.1.1b and similar OptiScaler INT8 builds: another model and pas
|
||||
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,
|
||||
automatically — the first under which the DLL enables FSR 4.1: GE-Proton 10 or newer (also the
|
||||
AUR's `proton-ge-custom` in `/usr/share/steam/compatibilitytools.d`),
|
||||
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
|
||||
@@ -287,13 +299,6 @@ DLSS gets the inputs FSR 3.1 gets (scene color, depth, motion vectors, jitter).
|
||||
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
|
||||
@@ -301,6 +306,31 @@ launcher: *Upscaler → DLSS: NVIDIA's library → Choose a file…*, as the FSR
|
||||
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.
|
||||
|
||||
**DLSS Frame Generation (GeForce RTX 40 and newer).** The same SDK (310.7 or newer) has NVIDIA's
|
||||
Linux frame generation library, `lib/Linux_x86_64/rel/libnvidia-ngx-dlssg.so.<version>`;
|
||||
`build.sh` copies it next to `bb-probe` with the Super Resolution one, and the launcher's
|
||||
*DLSS: NVIDIA's library → Choose a file…* takes it too (into `<user>/dlss/`, beside or instead of
|
||||
the package's). Then *Frame generation → DLSS* in the overlay menu (Insert, or hold L3+R3) or the
|
||||
launcher: between each two of the game's frames one generated frame is shown (RTX 40), or up to
|
||||
three (*Multiplier*, RTX 50); the display needs the refresh rate for them. It works with any
|
||||
upscaler, DLSS or FSR; the inputs are the frame as shown, the frame before its UI, the scene depth,
|
||||
the motion vectors bbport computes and the game's cameras as clip-space matrices. Without the
|
||||
library, on an RTX 30 or older or with `BB_DLSS=0` the entry says why it is unavailable, and the
|
||||
game runs without generated frames. `BB_FRAME_GEN=dlss` and `BB_FRAME_GEN_MULTIPLIER=2..4` set it
|
||||
from the command line. FSR 3.1 frame generation is offered on every GPU as experimental
|
||||
(*Frame generation → FSR 3.1*).
|
||||
|
||||
**Low latency (NVIDIA Reflex).** On NVIDIA's driver (550 or newer) the port uses the Vulkan
|
||||
form of Reflex, `VK_NV_low_latency2`, the same mechanism Proton gives Windows games: the game's
|
||||
thread waits for the driver before each frame so that frames do not queue up in front of the GPU,
|
||||
and the frame's simulation, submissions and present are marked for the driver. On by default where
|
||||
the driver has it: the overlay menu (*Output → Low latency (NVIDIA Reflex)*) and the launcher
|
||||
offer Off, On and On + Boost (`low_latency=off|on|boost` in `bbport.ini`, `BB_LOW_LATENCY`);
|
||||
on AMD the same setting uses **Anti-Lag 2** (`VK_AMD_anti_lag`: AMD's Windows driver, or Mesa's
|
||||
anti-lag layer on Linux; `BB_ANTI_LAG=0` leaves it out). With DLSS frame generation it is always
|
||||
at least On, as NVIDIA requires of games: the generated frames add latency that Reflex takes back.
|
||||
Reflex and DLSS frame generation came from PR #260 by adds39939.
|
||||
|
||||
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.
|
||||
|
||||
@@ -320,8 +350,9 @@ the launcher's button (see above); they go to `~/.local/share/bbport/fsr4_411`
|
||||
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
|
||||
does not matter. (Steam preloads its overlay into every non-Steam game; the AppImage keeps it out
|
||||
of the launcher and gives it to the game only, so it can open in the game with Shift+Tab; `BB_STEAM_OVERLAY=0`
|
||||
leaves it out.) Where Steam
|
||||
runs games without FUSE (NixOS: Steam's FHS sandbox; the AppImage then exits with *Cannot mount
|
||||
AppImage*), set the launch options to
|
||||
|
||||
@@ -340,7 +371,9 @@ NVIDIA userspace driver matched to the host kernel module. Standard Linux distri
|
||||
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
|
||||
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
|
||||
@@ -357,8 +390,7 @@ 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
|
||||
RSS, images and guest blocks in VRAM), `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
|
||||
@@ -374,17 +406,72 @@ budget taken as at most N MiB: checks the behaviour of cards with little VRAM),
|
||||
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).
|
||||
precise and slow, 0 off), `BB_RENDER_RESOURCE_CACHE_MB=160` (the game's render resource cache
|
||||
at its own size instead of 512 MiB of the port's memory; `BB_DMEM_EXTRA_MB`, 512 by default, is
|
||||
the direct memory the port keeps past the game's own). All of them, each with what it does:
|
||||
[docs/ENVIRONMENT.md](docs/ENVIRONMENT.md); the log's `Environment:` line names those set and any
|
||||
unknown `BB_*` name (a typo). More in [docs/](docs); recent changes: [docs/CHANGES_0.7.md](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/CHANGES_0.7.md),
|
||||
[docs/CHANGES_0.6.md](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/CHANGES_0.6.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.
|
||||
**Online play.** A separate module, `out/gpu/libbbnet20.so` (`gpu/bbnet20`: shadPS4 v0.20's
|
||||
network, NP and shadNet code, the port's BBSC and The Hunter's Dream backend), 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`) and offers two pools:
|
||||
- **Seamless Hunt** (default): a shadNet server with BBSC's game server
|
||||
(`play.seamlesshunt.com:31313`, WebAPI `:31315`); bells, summons, messages, bloodstains and
|
||||
**seamless co-op (BBSC)**: the session survives deaths, lamps, area changes, bosses and the
|
||||
Hunter's Dream, up to five hunters (*Seamless co-op (BBSC)*, on by default; every player needs
|
||||
it and the same server). The account (online id and password) is made on
|
||||
https://seamlesshunt.com. Details: [docs/BBSC.md](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/BBSC.md).
|
||||
- **The Hunter's Dream** (thehuntersdream.com): sign in with Discord from the launcher (a code is
|
||||
confirmed on the site); no BBSC.
|
||||
|
||||
On Windows the module is `online\bbnet20.dll` (Seamless Hunt with BBSC; the launcher's *Online*
|
||||
section). The Linux folder package keeps it open as `online/libbbnet20.so`, and `BB_NET_MODULE`
|
||||
points the port at any other build of it (a newer BBSC, say).
|
||||
|
||||
The former module on shadp2p (`libbbnet.so`) was removed (0.7). shadPS4's
|
||||
public server (`srv.shadps4.net`) accepts only shadPS4's own builds. Offline the module is never
|
||||
loaded; a build without it plays offline only.
|
||||
|
||||
## Windows
|
||||
|
||||
Since 0.7 bbport runs natively on Windows 10/11 (x86-64, a Vulkan 1.3 GPU with a current driver:
|
||||
AMD Adrenalin, NVIDIA GeForce, Intel Arc). The same code as on Linux: the game's x86-64 code runs
|
||||
directly, the runtime's POSIX layer is implemented on Win32 (`gpu/platform/win32`), and the
|
||||
game's memory is shared with Vulkan through `VK_EXT_external_memory_host`.
|
||||
|
||||
**Package:** `Bloodborne-bbport-<version>-windows-x64.zip`. Unpack it anywhere (not into *Program Files*), start
|
||||
`Bloodborne.exe`, choose the game folder (the one with `eboot.bin`), press *Play*. Insert (or
|
||||
holding L3+R3) opens the port's menu in the game. Everything the port writes stays in that folder:
|
||||
`bbport.ini` (settings), `user\` (saves, shader cache), `out\` (prepared game files), `logs\` (one
|
||||
log per run: for a report send the newest one). `Bloodborne.exe --play` starts the game without
|
||||
the launcher window (through `bbport-run.exe`).
|
||||
|
||||
**Launcher:** WPF in the Windows 11 style, following the system's light or dark theme, in five
|
||||
languages; the same settings as on Linux (sections with descriptions), monitor, controller and
|
||||
GPU choice, the log of the running game, *Stop*.
|
||||
|
||||
**Upscalers:** FSR 3.1 and FSR 4 as on Linux. FSR 4.1.1: choose *Build from an AMD DLL…* and pick
|
||||
`amd_fidelityfx_upscaler_dx12.dll` 4.1.x; the recording runs on vkd3d-proton and DXVK from the
|
||||
package (`tools\fsr4cap\bin`), so any GPU with Vulkan can build the set (a minute or two, into
|
||||
`fsr4_411\`). FSR 4.1.2's models (INT8 for RDNA2/3, *Lite*): pick the driver's `amdxcffx64.dll`
|
||||
(Adrenalin 26.10.1 or newer, `C:\Windows\System32\DriverStore\FileRepository\u0*\`) together with
|
||||
the upscaler DLL. DLSS and DLSS Frame Generation (GeForce RTX): with `bin\bbport_dlss.dll` and
|
||||
NVIDIA's `nvngx_dlss.dll` / `nvngx_dlssg.dll` in the package.
|
||||
|
||||
**NVIDIA:** at start the port adds a *prefer maximum performance* power profile for
|
||||
`bb-probe.exe` to the driver, unless you chose a mode in NVIDIA's Control Panel yourself
|
||||
(`BB_NV_POWER=0`: leave it alone); presentation uses FIFO (`BB_NVIDIA_MAILBOX=1`: Mailbox).
|
||||
|
||||
**Online play:** the launcher's *Online* section, the same two pools as on Linux (Seamless Hunt
|
||||
with BBSC, The Hunter's Dream with Discord sign-in), UPnP and a connection check; the module is
|
||||
`online\bbnet20.dll`. **Mods:** the launcher's *Mods and patches* section, as on Linux.
|
||||
|
||||
**Building:** in MSYS2's CLANG64 shell (`mingw-w64-clang-x86_64` toolchain, CMake, Ninja, Python,
|
||||
SDL3, glslang), or cross-built from Linux with llvm-mingw; the package
|
||||
is made by `packaging/windows/package.sh` (the launcher needs the .NET 9 SDK). DLSS:
|
||||
`DLSS_SDK_ROOT=<NVIDIA's DLSS SDK for Windows>` (`sdk/DLSS-windows`).
|
||||
|
||||
## Repository layout
|
||||
|
||||
@@ -392,45 +479,71 @@ build without it plays offline only.
|
||||
|---|---|
|
||||
| `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 |
|
||||
| `gpu/` | Renderer library, the port's own (`gpu/VENDOR.txt`): command processor, shader translator, renderer, caches, ImGui menu, FSR 4.1.1 runtime (`gpu/upscale/fsr411`); the online module (`gpu/bbnet20`) |
|
||||
| `launcher/`, `packaging/` | GTK4 launcher (Linux), WPF launcher (`launcher/windows`), the shared settings schema; Nix package, AppImage, Windows package |
|
||||
| `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)) |
|
||||
| `docs/` | Design notes and measurements ([upscaler](docs/upscaler.md), [parallel GPU](docs/parallel_gpu.md), [motion vectors](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/motion_vectors.md), [roadmap](https://github.com/deadinside28/bloodborne_pc/blob/master/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`.
|
||||
`ninja -C out/gpu motion-history-test ui-composition-test scene-resolution-test motion-shader-test settings-test camera-matrices-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.
|
||||
- NVIDIA on Windows: more reports from RTX cards in the heaviest views.
|
||||
- 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.
|
||||
- All of the game's shaders translated ahead of time (today: recipes from players' caches).
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
## Related projects
|
||||
|
||||
- [ducvd89/Bloodborne-Android](https://github.com/ducvd89/Bloodborne-Android): Bloodborne on
|
||||
Android, a fork of bbport (GSR2 / FSR 3.1 and LSFG, an on-screen controller; tested on
|
||||
Snapdragon 8 Gen 2 and 8 Elite).
|
||||
- [bloodborne-native/bloodborne-mac](https://github.com/bloodborne-native/bloodborne-mac):
|
||||
Bloodborne running natively on Apple Silicon, a macOS app built on bbport and shadPS4.
|
||||
|
||||
## 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+),
|
||||
from shadPS4 (GPL-2.0-or-later). Reusing bbport's code: keep its copyright and license notices
|
||||
(`SPDX-FileCopyrightText: Copyright 2026 bbport contributors` at the top of its files) and
|
||||
[NOTICE](NOTICE), and say where the code comes from, with a link to this repository. Third-party components keep their licenses:
|
||||
[shadPS4](https://github.com/shadps4-emu/shadPS4) (GPL-2.0+: the renderer and shader translator were derived from it and rewritten; the online module keeps its network and NP code),
|
||||
[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,
|
||||
[BBSC](https://github.com/CLSAMG22/BBSC) seamless co-op (GPL-2.0+, in the online module), the
|
||||
online module's network code from [shadPS4 v0.20](https://github.com/shadps4-emu/shadPS4/tree/v.0.20.0),
|
||||
The Hunter's Dream backend from
|
||||
[droogie/bbhost](https://github.com/droogie/bbhost) (GPL-3.0-or-later, so `libbbnet20.so` as a
|
||||
whole is GPL-3.0-or-later),
|
||||
[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
|
||||
FSR 4.1.1 assets), [wepoll](https://github.com/piscisaureus/wepoll) (BSD-2-Clause, the Windows
|
||||
online module). Game patches by Kyo, Lance McDonald, auser1337, illusion, emoose and other
|
||||
community members (`patches/Bloodborne.xml`). The DLSS bridge (`gpu/dlss_bridge`, MIT) is 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.
|
||||
|
||||
Contributions merged into bbport (thank you; the full list with each release is in
|
||||
[docs/CONTRIBUTORS_0.7.md](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/CONTRIBUTORS_0.7.md)): SrDeTs (#10), IgorWarzocha (#36), itachi-re
|
||||
(#43), Bainianzzz (#71), hideoneric (#78), guitaripod (#80), unTanana (#83, #84, #89), andrR89
|
||||
(#93), SaeedShow (#108), AndiMajore (#112, #113), ayozetr (#117), JuanraCM (#120), Supermedo and Awaken (the online
|
||||
module's network stack, from Supermedo's Windows fork), wheat32 (#360 larger heaps for texture packs),
|
||||
Sarxzer (#347 French), whp199 (#210, #211, #214, #342, #343), DominickCobb-rs (#346), maiconjsv
|
||||
(#339), iannitram (#293), adds39939 (#260 DLSS Frame Generation and Reflex), AndiMajore (#166,
|
||||
#224, #225), Qiming-Liu (#75, #157 Chinese), AyrtonSanabio (#153, #154), axpico (#206), msisovic (#193),
|
||||
noelmiller (#235), Obrikash (#140), Aryxa (#137), ArenaMMORPG (#352/#353), guutol (trophies),
|
||||
redditor69 (GT-VBAO), SrShadowy and Teefpyre (engine and BBSC fixes from SrShadowy's fork), and
|
||||
the players who sent their shader caches and logs.
|
||||
-410
@@ -1,410 +0,0 @@
|
||||
ДАННЫЙ ПРОЕКТ НЕ ИМЕЕТ ОТНОШЕНИЯ К SHADPS4. ВСЕ ВОПРОСЫ, СВЯЗАННЫЕ С ЭТИМ ПРОЕКТОМ - ПИШИТЕ НА ДИСКОРД СЕРВЕР https://discord.gg/KYZRKk9CB, А НЕ НА СЕРВЕР SHADPS4
|
||||
|
||||
# bbport — нативный порт Bloodborne для Linux
|
||||
|
||||
> README оригинального проекта (deadinside28/bloodborne_pc), сохранён ради технических
|
||||
> подробностей. README этого форка — [README.md](README.md) (на английском).
|
||||
|
||||
[English](BBPORT.md) · **Русский**
|
||||
|
||||
bbport — это аналог Wine + DXVK, только для одной игры: *Bloodborne* для PlayStation 4
|
||||
(CUSA03173, версия игры 1.09) на ПК с Linux x86-64. Оригинальный исполняемый файл игры
|
||||
запускается прямо на ПК:
|
||||
|
||||
- **как в Wine**, x86-64-код игры выполняется на процессоре напрямую, а системные библиотеки PS4
|
||||
заменяет собственный рантайм, написанный под эту игру;
|
||||
- **как в DXVK**, графика игры переводится в Vulkan — рендером на основе
|
||||
[shadPS4](https://github.com/shadps4-emu/shadPS4), сильно доработанным под Bloodborne, вплоть
|
||||
до временного апскейлинга AMD FSR 3.1, FSR 4 и FSR 4.1.1;
|
||||
- память и работа видеочипа на AMD по умолчанию устроены как у игры для ПК (новая модель памяти
|
||||
и трансляции); NVIDIA и другие видеокарты пока работают со старой моделью памяти, как в 0.3.
|
||||
В лаунчере *Модель памяти* переключается в обе стороны.
|
||||
|
||||
До полной модели Wine + DXVK остался один шаг: новая модель и на NVIDIA, затем удаление старой
|
||||
модели памяти (см. ниже).
|
||||
|
||||
> **Файлов игры в репозитории нет.** Нужен свой дамп Bloodborne с обновлением 1.09: любое издание
|
||||
> из магазина (CUSA00900 в США, CUSA03173 — европейское Game of the Year, ...): у них один и тот же
|
||||
> исполняемый файл.
|
||||
> Проект не связан с Sony Interactive Entertainment, FromSoftware и AMD.
|
||||
|
||||
**Состояние: экспериментальный, играбельный.** Игра запускается, загружает сохранения и
|
||||
проходится (Сон Охотника и несколько локаций Ярнама) со звуком,
|
||||
геймпадом и сохранением. Полное прохождение не проверено; основательно проверена одна машина
|
||||
(Linux, AMD Radeon RX 7800 XT, Mesa/RADV).
|
||||
|
||||
## Что сделано
|
||||
|
||||
- **Нативное исполнение.** eboot заранее превращается в плоский образ памяти, в который как
|
||||
нативный код прилинкованы libc и libSceFios2 из PS4. Никакой эмуляции процессора и
|
||||
трансляции инструкций: код игры работает на полной скорости.
|
||||
- **Две модели памяти** (лаунчер → *Режим работы → Модель памяти и трансляции*; *Авто*, по
|
||||
умолчанию, выбирает по видеокарте):
|
||||
- **Старая модель памяти, как в 0.3, — по умолчанию на NVIDIA, Intel и других.** Копии памяти
|
||||
игры в VRAM, записи отслеживаются защитой страниц. Со всеми исправлениями после 0.3
|
||||
(векторы движения и апскейлер, мерцание при включённом DoF, повреждённый кэш шейдеров).
|
||||
- **Новая модель памяти и трансляции — по умолчанию на AMD.** Память
|
||||
игры находится в оперативной памяти, и GPU читает её на месте, как буферы игры для ПК; то,
|
||||
что он читает часто, держится в видеопамяти и освобождается, когда перестаёт использоваться
|
||||
(текстуры — через 20 с, буферы — через 60 с). Модуль памяти прослойки отслеживает записи CPU
|
||||
в блоки с зеркалами для согласования данных; вся память, видимая GPU, не копируется.
|
||||
Работа командного процессора PS4 переводится в команды Vulkan, а не
|
||||
эмулируется на процессоре: записи в память (`WRITE_DATA`, DMA) выполняет видеочип в порядке
|
||||
потока команд, метки (fences) пишет сам видеочип в конце конвейера, в той же точке потока,
|
||||
что и командный процессор PS4.
|
||||
На AMD память игры держится в sparse-буфере (арене); на NVIDIA, Intel и других — через модуль
|
||||
памяти прослойки: привязывается на месте, копии в VRAM — отдельные буферы, без
|
||||
sparse-перепривязок (на NVIDIA sparse binding держал видеочип секундами); `BB_LAYER_MEMORY=1/0`
|
||||
— выбрать вручную. Проверено на RX 7800 XT. На NVIDIA её можно выбрать
|
||||
вручную (*Новая*); у тестеров результат зависит от сцены, в части сцен медленнее старой
|
||||
модели, поэтому там она пока не по умолчанию. На Intel не проверялась. Без лаунчера —
|
||||
`BB_PC_MODEL=1/0` (не задано — по видеокарте, как *Авто*).
|
||||
|
||||
[Изменения в 0.5](docs/CHANGES_0.5.md); пререлизы: [pre4](docs/CHANGES_0.5-pre4.md),
|
||||
[pre3](docs/CHANGES_0.5-pre3.md), [pre2](docs/CHANGES_0.5-pre2.md).
|
||||
|
||||
Неиспользуемые текстуры освобождаются в обоих режимах, поэтому видеопамять больше не растёт
|
||||
с каждой локацией.
|
||||
- **Разблокированная частота кадров.** Патчи сообщества (`patches/Bloodborne.xml`) переводят
|
||||
симуляцию на реальное время кадра; ~90 FPS в 4K с FSR 4 Balanced на RX 7800 XT, ~150 FPS в
|
||||
1440p с FSR 4 Quality. Есть режимы 30/60/90 FPS.
|
||||
- **Временной апскейлинг под эту игру.** Буфера скоростей в Bloodborne нет, поэтому порт
|
||||
считает векторы движения сам: движение камеры — по глубине и матрицам сцены, движение
|
||||
объектов (персонажи, ткань, оружие) — по позициям вершин прошлого кадра. Сцена сдвигается
|
||||
субпиксельным jitter (Halton) и рисуется в уменьшенном разрешении, апскейлер достраивает
|
||||
вывод (720p для Steam Deck, 1080p, 1440p или 2160p), интерфейс рисуется сразу в выходном разрешении.
|
||||
- **FSR 3.1** (FireBurn/FSR-Vulkan).
|
||||
- **FSR 4 (INT8, модель v07)** на видеокартах с нужными возможностями шейдеров Vulkan,
|
||||
включая RDNA2/3 (см. требования).
|
||||
- **FSR 4.1.1**: DLL AMD 4.1.1 один раз записывается под vkd3d-proton, а её проходы
|
||||
воспроизводятся нативно в Vulkan; результат **побитово совпадает** с DLL. Два варианта, как
|
||||
в самой DLL: INT8 на любой видеокарте с нужными возможностями шейдеров и матрицы FP8 на
|
||||
RDNA4 (RX 9000; выбирается сам). Ассеты собираются на твоей машине из одной твоей DLL
|
||||
4.1.x: в лаунчере кнопка *FSR 4.1.1 из своей DLL AMD → Выбрать DLL…* (2–5 минут, на RDNA4
|
||||
вдвое дольше; нужен свежий Proton), или `tools/fsr4cap`.
|
||||
- Быстрее шейдеров самой AMD на RDNA3: финальные проходы FSR 4 и 4.1.1 переписаны с записью
|
||||
через shared-память (в 3.5 и 2.3 раза быстрее, побитово так же); FSR 4 в 4K на RX 7800 XT
|
||||
стоит ~4 мс вместо ~6.
|
||||
- **Многопоточная обработка GPU-команд.** Поток команд PS4 разбирается в одном потоке, а
|
||||
привязка ресурсов и запись draw — в другом (двухстадийный конвейер), плюс отдельный поток
|
||||
записи команд Vulkan и вспомогательные потоки для копий памяти. В начале проекта единственный
|
||||
поток GPU ограничивал игру ~26 FPS; сейчас 90–150 FPS в зависимости от разрешения и сцены.
|
||||
- **Настройки в меню самой игры:** *Система → Изображение / Эффекты* (после *Экран/звук*) — их
|
||||
строит и рисует сама игра: разрешение вывода, апскейлер, пресет, резкость, счётчик FPS,
|
||||
детализация моделей и эффекты игры, на языке игры.
|
||||
- **Меню поверх игры** (Insert или L3+R3) в стиле диалогов игры: всё то же и расширенные
|
||||
настройки; геймпад, клавиатура и мышь.
|
||||
- **Лаунчер на GTK4** и **AppImage** для Steam Deck.
|
||||
|
||||
## Чем отличается от shadPS4
|
||||
|
||||
| | shadPS4 | bbport |
|
||||
|---|---|---|
|
||||
| Охват | Универсальный эмулятор PS4, много игр | Одна игра: Bloodborne v1.09 |
|
||||
| Загрузка | Свой ELF-загрузчик и эмуляция ядра во время работы | eboot заранее превращается (`scripts/`) в образ с прилинкованными libc/Fios2 PS4; загрузчик на C отображает его и передаёт управление игре (загрузчик и рантайм — ~5 тыс. строк) |
|
||||
| Память | Вид памяти PS4 со стороны GPU хранится копиями в VRAM, синхронизация через отслеживание записей защитой страниц | По умолчанию та же модель (как в 0.3); в режиме *Новая модель памяти и трансляции*: память игры в оперативной памяти, GPU использует её на месте, часто читаемое — в VRAM, освобождается, когда не нужно |
|
||||
| Командный процессор | Эмулируется: записи в память, DMA и метки выполняет процессор во время разбора команд | По умолчанию так же; в новом режиме переводится в команды Vulkan, их выполняет видеочип в порядке потока, метки — после реального завершения работы |
|
||||
| Системные библиотеки | Широкий HLE ОС PS4 | Небольшой рантайм (`src/runtime_*.c`) ровно под то, что вызывает Bloodborne: память, потоки, синхронизация, файлы, звук (включая ATRAC9), геймпад, сохранения, AppContent |
|
||||
| GPU | Видеоядро и рекомпилятор шейдеров shadPS4 | То же ядро (вложено, GPL) с ~200 помеченными правками (`bbport:`) и новыми модулями: двухстадийный конвейер draw, мемоизация состояния прохода и наборов текстур, уменьшенные цели сцены, векторы движения, FSR 3.1/4/4.1.1, захват кадра, профайлер GPU |
|
||||
| Поток GPU | Один поток обрабатывает весь поток команд (узкое место в Bloodborne) | Разбор и запись draw идут в разных потоках; работа масштабируется под число потоков (в том числе Steam Deck) |
|
||||
| Апскейлинг | — | Временной (FSR 3.1, FSR 4, FSR 4.1.1) на собственных векторах движения и jitter игры |
|
||||
| Патчи игры | Патч-файлы применяет эмулятор | Те же патчи сообщества компилируются при запуске (`scripts/patches.py`); разрешение рендера, эффекты и FPS — из лаунчера |
|
||||
|
||||
Без shadPS4 bbport бы не было: его рендерер и рекомпилятор шейдеров — основа графической части.
|
||||
|
||||
### Wine + DXVK для одной игры
|
||||
|
||||
- **Процессор.** Процессор PS4 — x86-64, поэтому код игры выполняется прямо на процессоре ПК, без
|
||||
эмуляции и перевода инструкций.
|
||||
- **Системные библиотеки.** Как Wine подменяет Windows API, так рантайм bbport (`src/runtime_*.c`)
|
||||
реализует ровно те функции ОС PS4, которые вызывает Bloodborne: память, потоки, файлы, звук,
|
||||
геймпад, сохранения.
|
||||
- **Память.** В новом режиме — как у игры для ПК: данные игры в оперативной памяти, видеопамять
|
||||
используется так, как её использует игра для ПК. Пока режим экспериментальный.
|
||||
- **Графика.** Поток команд видеочипа PS4 (PM4) — это записанные вызовы графического API игры:
|
||||
его пишут 99 функций статически слинкованной libGnm, так что разбор потока и перевод самих
|
||||
вызовов дают одно и то же. Шейдеры GCN переводятся в SPIR-V. В новом режиме работа командного
|
||||
процессора (записи в память, DMA, метки) тоже переводится в команды Vulkan; метки пишет сам
|
||||
видеочип. Дескрипторы ресурсов (текстур, буферов) транслятор читает на процессоре, как DXVK
|
||||
читает привязки ресурсов: текстуру PS4 нужно перевести из раскладки PS4 в образ Vulkan до
|
||||
отрисовки, поэтому процессор должен знать её дескриптор заранее — это трансляция, а не
|
||||
эмуляция. Таблицы дескрипторов (дампы движка констант) читает только транслятор, поэтому их
|
||||
выполняет процессор. Occlusion-запросы игры (~60 за кадр) — настоящие запросы Vulkan; предикация, `COPY_DATA`,
|
||||
`COND_EXEC` тоже переводятся (проверка — `BB_PM4_SELFTEST=1`).
|
||||
Подробно — [docs/EMULATION_REMOVAL_PLAN.ru.md](docs/EMULATION_REMOVAL_PLAN.ru.md), «Шаг 4».
|
||||
|
||||
«Порт» здесь — сборка под одну игру, а не переписанный исходный код: исходного кода игры у
|
||||
проекта нет, и он его не содержит.
|
||||
|
||||
## Требования
|
||||
|
||||
- Linux x86-64, видеокарта с Vulkan 1.3. Проверено: AMD RX 7800 XT, Mesa 26 (RADV).
|
||||
Режим *Новая модель памяти и трансляции* на NVIDIA пока не проверен.
|
||||
FSR 4 / 4.1.1 требуют shader Float16, Int8/Int16, целочисленных dot-произведений,
|
||||
linear compute derivatives и extended storage image formats; FSR 4.1.1 дополнительно
|
||||
требует `VK_VALVE_shader_mixed_float_dot_product`. Неподдерживаемый вариант заменяется
|
||||
на FSR 3.1 до первого кадра и недоступен в игровом меню.
|
||||
- Свой расшифрованный дамп игры: папка `CUSA03173` (eboot.bin, sce_module, ...), версия 1.09.
|
||||
Дамп обновления — отдельная папка: скопируйте её поверх базовой игры с заменой файлов. Одна
|
||||
базовая игра (1.00) падает при запуске (guest offset 0x20348b8); лаунчер и `run.sh` проверяют
|
||||
исполняемый файл и пишут, чего не хватает (`BB_SKIP_GAME_CHECK=1` отключает проверку).
|
||||
Другие издания (американское `CUSA00900`, азиатское `CUSA03023`, ...) запускают тот же
|
||||
исполняемый файл 1.09. В некоторых дампах в него уже вшит патч 60 FPS от Lance McDonald, из-за которого игра
|
||||
падает при открытии меню жестов: проверка тогда просит чистый eboot.bin (у bbport свои 60 FPS).
|
||||
- Для сборки: GCC, CMake, Ninja, Python 3, glslang, SDL3, заголовки Vulkan и библиотеки из
|
||||
`shell.nix`. С [Nix](https://nixos.org) всё берётся из `shell.nix` автоматически.
|
||||
|
||||
## Сборка и запуск
|
||||
|
||||
```bash
|
||||
git clone --recursive https://github.com/deadinside28/bloodborne_pc.git bbport && cd bbport
|
||||
bash build.sh # собирает out/bb-probe и out/gpu/libbbgpu.so
|
||||
BB_GAME_DIR=/путь/к/CUSA03173 bash run.sh
|
||||
```
|
||||
|
||||
или через лаунчер (выбрать папку игры, настройки, «Запустить»):
|
||||
|
||||
```bash
|
||||
bash launcher/bb-launcher.sh # launcher/install-desktop.sh добавит его в меню приложений
|
||||
```
|
||||
|
||||
По умолчанию папка игры ищется рядом с репозиторием (`../CUSA03173`). Сохранения и кэш шейдеров
|
||||
— в `user/` (в лаунчере можно выбрать другую папку), настройки — в `bbport.ini`. Геймпад через
|
||||
SDL3 (если их несколько, нужный выбирается в лаунчере: *Управление → Контроллер*;
|
||||
`BB_GAMEPAD=<GUID или часть имени>`). Клавиатура тоже работает, и при подключённом геймпаде
|
||||
(на Steam Deck он есть всегда); обе раскладки меняются в лаунчере (*Управление*). Имя персонажа
|
||||
вводится с клавиатуры в окне поверх игры. Тачпад: левая половина (Tab, Back/Select) открывает
|
||||
жесты, правая (Backspace) — личные вещи. PSN нет: экран «Играть по сети / вне сети» пропускается,
|
||||
игра сразу открывает главное меню вне сети (патч игры; в лаунчере *Эффекты игры → Пропускать
|
||||
выбор «по сети / вне сети»*, `BB_SKIP_NETWORK_CHOICE=0` — показывать). При нескольких мониторах нужный выбирается в
|
||||
лаунчере: *Экран → Монитор* (`BB_DISPLAY=<номер или часть имени>`).
|
||||
|
||||
**Смена разрешения и пресетов:** для вывода не 1080p (720p на Steam Deck, 1440p, 4K) вся игра
|
||||
рисуется в разрешении пресета, которое задаёт патч при запуске, — это самый быстрый путь.
|
||||
Смена вывода или пресета в игровом меню тогда требует «Применить и перезапустить игру».
|
||||
Настройка «Смена разрешения на лету» (лаунчер, игровое меню, `bbport.ini`
|
||||
`live_resolution=0|1|auto`; **по умолчанию выключена**) вместо этого оставляет игре 1080p и
|
||||
масштабирует её цели на ходу: 720p/1080p/1440p/4K и пресеты меняются без перезапуска. Это
|
||||
дороже: игра считает, что рисует 1080p, и рисует больше (например, мелких источников света
|
||||
примерно в 8 раз больше), часть целей копируется между размерами — включай только на мощных
|
||||
настольных видеокартах (`auto` включает её на дискретных GPU с 8+ ГБ, кроме NVIDIA старше
|
||||
Turing). Вывод 1080p и TAA всегда идут живым путём.
|
||||
|
||||
**TAA:** отдельный пункт сглаживания в лаунчере и игровом меню, переключается на ходу.
|
||||
Работает в разрешении вывода без модели FSR; пресет FSR сохраняется для обратного
|
||||
переключения. Native AA у FSR выполняет реконструкцию поверх полного разрешения и
|
||||
может быть тяжелее выключенного сглаживания. TAA тоже добавляет обработку кадра.
|
||||
|
||||
**Моды:** лаунчер подключает распакованные моды из отдельных папок (с `dvdroot_ps4/`, с лишней
|
||||
папкой-обёрткой или сразу с папками игры вроде `chr/`; регистр имён не важен), с переключателями
|
||||
и порядком загрузки. **Сторонние патчи:** XML-файлы в формате shadPS4 в папке `patches/` каталога
|
||||
данных, включаются в лаунчере. Подробнее — [моды и патчи](docs/MODS.md).
|
||||
**DLC:** дампы дополнений клади в `user/addcont/<title id>/<метка>/` (как в shadPS4, например
|
||||
`user/addcont/CUSA03173/SPEXPANSIONDLC03` для The Old Hunters): каждая такая папка сообщается
|
||||
игре как установленное дополнение.
|
||||
|
||||
**Язык лаунчера:** русский, английский, бразильский португальский или упрощённый китайский (по умолчанию — как в системе).
|
||||
|
||||
**Свободная камера и debug menu игры** (v1.09): включи соответствующие галочки в лаунчере
|
||||
или игровом меню и перезапусти игру. Для камеры используется
|
||||
[патч Lance McDonald из GoldHEN](https://github.com/GoldHEN/GoldHEN_Patch_Repository/blob/main/patches/xml/Bloodborne-Orbis.xml):
|
||||
удерживай Cross и нажимай L3 для переключения режимов (клавиатура: Space + Z).
|
||||
Камере шрифты не нужны; с патчем Enemy Control она несовместима.
|
||||
Для debug menu сначала установи `DbgFont14h.ccm` и `DbgFont14h.tpf` из
|
||||
[Debug Menu and XML Patch](https://www.nexusmods.com/bloodborne/mods/253) в
|
||||
`dvdroot_ps4/font/` внутри папки игры. Если файлов нет или они пустые, запуск остановится
|
||||
с объяснением. Меню открывается левым touchpad / Tab (с включённым debug menu левая половина
|
||||
больше не открывает жесты); правый touchpad — Backspace.
|
||||
Координаты касания передаются с SDL-геймпадов; Back/Select заменяет левый клик на геймпадах
|
||||
без сенсорной поверхности. Меню настроек порта по-прежнему открывается Insert / L3+R3.
|
||||
|
||||
GPU-запросы occlusion пока возвращают искусственные счётчики пикселей
|
||||
(`PixelPipeStatDump`), а `IT_SET_PREDICATION` не реализован. Свободная камера позволяет
|
||||
проверять видимость, но не добавляет GPU occlusion culling.
|
||||
|
||||
**Ассеты апскейлеров** (в репозитории их нет; FSR 3.1 они не нужны):
|
||||
|
||||
```bash
|
||||
bash tools/fetch_fsr4_assets.sh # FSR 4 v07 (MIT, собраны Q2RTX из исходников AMD)
|
||||
# FSR 4.1.1 — из своих DLL AMD (например, FSR4_LATEST у OptiScaler), нужен GE-Proton 10 или новее:
|
||||
bash tools/fsr4cap/build_assets.sh <amd_fidelityfx_upscaler_dx12.dll>
|
||||
```
|
||||
|
||||
**FSR 4.1.1 из своей DLL.** Проще всего — в лаунчере: *Апскейлер → FSR 4.1.1 из своей DLL AMD →
|
||||
Выбрать DLL…* и указать `amd_fidelityfx_upscaler_dx12.dll` версии 4.1.x: из папки `FSR4_LATEST`
|
||||
OptiScaler или из игры с FSR 4.1. Этого одного файла достаточно: DLL AMD сама экспортирует
|
||||
FidelityFX API, загрузчик не нужен (через `amd_fidelityfx_loader_dx12.dll` записывалась бы только
|
||||
DLL без этих функций, и тогда лаунчер его спросит). DLL проверяется сразу, без долгой записи, и не подходят: официальная FSR 4.0.x
|
||||
(например, 4.0.3 из Pragmata — AMD включает её только на RDNA4, под Proton на других видеокартах
|
||||
она не запускается) и сборки сообщества (4.0.2b, 4.1.1b и подобные INT8-сборки OptiScaler:
|
||||
другая модель и число проходов). Дальше DLL запускается под Proton и записывается в 20
|
||||
разрешениях (ход виден в строке и во вкладке «Журнал»; на RDNA4 — второй раз, для варианта FP8), проходы переводятся в SPIR-V, и FSR 4.1.1
|
||||
сразу выбирается. Proton выбирается сам — первый, под которым DLL включает FSR 4.1: GE-Proton 10
|
||||
или новее, Proton-CachyOS, Proton Experimental (проверено: GE-Proton 11, Proton-CachyOS 11,
|
||||
Experimental от октября 2026; Proton 11.0 из Steam и GE-Proton 9 — нет). Он запускается в
|
||||
рантайме Steam, который требует (Steam ставит его при первом запуске любой игры с этим Proton),
|
||||
или через umu-launcher, если он установлен. На NixOS umu-launcher берётся из `nix-shell` (в первый
|
||||
раз скачивается, ~1,7 ГБ); AppImage запускает запись на самой системе, через systemd пользователя
|
||||
(`systemd-run --user`), так как системный `/nix` ей не виден. Запись на Steam Deck пока не проверена; если DLL не включит
|
||||
там FSR 4.1, собери на ПК и скопируй папку `fsr4_411`. То же из командной строки:
|
||||
`--build-fsr411 <DLL>` у лаунчера (и у AppImage).
|
||||
|
||||
**DLSS (NVIDIA GeForce RTX).** SDK DLSS от NVIDIA не входит в репозиторий: скачай
|
||||
[github.com/NVIDIA/DLSS](https://github.com/NVIDIA/DLSS) (достаточно `include/` и
|
||||
`lib/Linux_x86_64`) и собери с `DLSS_SDK_ROOT=/путь/к/DLSS bash build.sh`. Тогда собирается и мост
|
||||
`out/libbbport_dlss.so` (`gpu/dlss_bridge`, единственный код, использующий SDK), а
|
||||
`libnvidia-ngx-dlss.so.<версия>` от NVIDIA копируется рядом с `out/bb-probe`; остальное даёт
|
||||
драйвер. Затем выбери *DLSS* в лаунчере или во внутриигровом меню: пресет задаёт разрешение
|
||||
рендера, Native AA — это DLAA. DLSS получает те же входы, что и FSR 3.1 (цвет сцены, глубину,
|
||||
векторы движения, 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) выбирается в лаунчере:
|
||||
*Апскейлер → DLSS: библиотека NVIDIA → Выбрать файл…*, как DLL для FSR 4.1.1; она копируется в
|
||||
`<user>/dlss/`, и игра берёт её первой. `nvngx_dlss.dll` из игр — библиотека для Windows: драйвер
|
||||
NVIDIA на Linux её не загружает.
|
||||
|
||||
Скрипт из исходников берёт инструменты из системы (MinGW GCC, CMake, Ninja, Python 3,
|
||||
SPIRV-Tools, Git) или из Nix; в AppImage они уже собраны.
|
||||
|
||||
**FP8 на RDNA4.** Когда vkd3d-proton даёт кооперативные матрицы FP8 (RDNA4), DLL запускает
|
||||
другой вариант своих проходов: другие шейдеры и веса, другие размеры диспатчей. Сборка
|
||||
записывает оба: INT8 в `fsr4_411/`, FP8 в `fsr4_411/fp8/`, а игра сама выбирает FP8, если у
|
||||
видеокарты есть матрицы FP8 (`VK_EXT_shader_float8`); в журнале — `Upscaler: FSR 4.1.1 replay,
|
||||
FP8 …`. `BB_FSR411_VARIANT=int8` — принудительно INT8. Проверено на RX 7800 XT через эмуляцию FP8
|
||||
на FP16 в vkd3d-proton (`BB_FSR4CAP_FP8=1` записывает его и там, `fp8emu/`): побитово как DLL;
|
||||
**на самой RDNA4 ещё не запускалось.**
|
||||
|
||||
**AppImage** (Steam Deck): `bash build.sh && bash packaging/appimage.sh` →
|
||||
`dist/Bloodborne-bbport-x86_64.AppImage`; данные в `~/.local/share/bbport`, `--play` запускает
|
||||
игру без окна лаунчера (игровой режим). Модели FSR 4.1.1 в пакет не входят: собери их кнопкой
|
||||
лаунчера (см. выше), они лягут в `~/.local/share/bbport/fsr4_411` (`BB_PACKAGE_FSR411=1` кладёт
|
||||
локальную `fsr4_411` в AppImage для своих устройств). На Steam Deck выбирай вывод 1280×720 (игра 16:9, на
|
||||
экране 1280×800 будут узкие полосы).
|
||||
|
||||
**Добавление AppImage в Steam** («Добавить стороннюю игру»): ничего настраивать не нужно,
|
||||
инструмент совместимости не важен. (Steam подгружает свой оверлей во все сторонние игры; AppImage
|
||||
убирает его до запуска своих программ, поэтому оверлея Steam в игре нет.) Если Steam запускает игры без FUSE (NixOS: FHS-песочница Steam; AppImage тогда завершается с
|
||||
*Cannot mount AppImage*), в параметрах запуска укажи
|
||||
|
||||
```
|
||||
TMPDIR=$HOME/.cache APPIMAGE_EXTRACT_AND_RUN=1 NO_CLEANUP=1 %command%
|
||||
```
|
||||
|
||||
Тогда AppImage при первом запуске распакуется (~2 ГБ, `~/.cache/appimage_extracted_*`, ~10 с),
|
||||
а дальше будет запускать эту копию; новая версия AppImage распакуется заново, старую копию можно
|
||||
удалить. Без `TMPDIR` распаковка шла бы в `/tmp` Steam, а он там в оперативной памяти.
|
||||
` --play` после `%command%` — запуск без лаунчера.
|
||||
|
||||
**NVIDIA в AppImage:** пакет автоматически ищет установленный на машине 64-битный NVIDIA
|
||||
Vulkan-драйвер и подключает его вместе с комплектными AMD/Intel-драйверами. Используются
|
||||
библиотеки NVIDIA с этой машины, чтобы их версия совпадала с модулем ядра. На обычных Linux
|
||||
дистрибутивах они доступны в `/usr/lib*`; на NixOS внутренний `/nix/store` AppImage может скрывать
|
||||
драйвер хоста. В этом случае библиотеки NVIDIA нужно скопировать в доступную папку и указать
|
||||
`BB_NVIDIA_LIB_DIR=/путь/к/библиотекам` (манифест NVIDIA тоже должен быть доступен).
|
||||
С драйвером NVIDIA хоста AppImage передаёт и файлы EGL-вендоров хоста, NVIDIA первым
|
||||
(`__EGL_VENDOR_LIBRARY_FILENAMES`; без них ICD NVIDIA на некоторых системах не отдавал
|
||||
`vkCreateInstance`, #107; `BB_NVIDIA_EGL=0` — выключить). Явные `VK_DRIVER_FILES`/`VK_ICD_FILENAMES` сохраняются. Проверка драйвера внутри пакета:
|
||||
|
||||
```bash
|
||||
./Bloodborne-bbport-x86_64.AppImage --vulkan-info 2>&1 | tee bbport-vulkan.log
|
||||
```
|
||||
|
||||
Пользователь подтвердил запуск с FSR 3 на GTX 1060 6GB (Fedora 44, NVIDIA 580.178.04);
|
||||
выбор FSR 4 приводил к чёрному окну. На этой конфигурации используй FSR 3.
|
||||
|
||||
MangoHud включён в AppImage; включи его галочку в лаунчере. Если MangoHud установлен ещё и в
|
||||
системе или включён оверлей производительности Steam (игровой режим Steam Deck), рисуется только
|
||||
один оверлей (два давали двоящийся текст со сдвигом).
|
||||
При запуске из исходников MangoHud нужно установить отдельно. Диагностический запуск
|
||||
с `VK_LOADER_LAYERS_DISABLE=~implicit~` отключает и MangoHud.
|
||||
|
||||
Полезные переменные: `BB_FRAME_STATS=1` (статистика кадров, в том числе строка `Memory:` —
|
||||
VRAM, GTT, RSS, текстуры и блоки памяти игры в VRAM), `BB_PC_MODEL=1` (новая модель памяти и трансляции;
|
||||
по умолчанию 0 — старая модель, как в 0.3), `BB_ANISO=N` (анизотропная фильтрация текстур сцены; по умолчанию
|
||||
16, 0 — как в игре),
|
||||
`BB_GC_IDLE_SECONDS=N` / `BB_VRAM_IDLE_SECONDS=N` (сколько неиспользуемые текстуры / буферы
|
||||
остаются в VRAM; 20 / 60), `BB_BREADCRUMBS=0` (без меток GPU; с ними зависание видеокарты
|
||||
называет отрисовку или dispatch, на которой она встала), `BB_GPU_PROFILE=1` (время GPU по
|
||||
проходам), `BB_FSR4_PROFILE=1` (время каждого прохода FSR 4), `BB_UPSCALER=taa|fsr3|fsr4|fsr411|off|none`,
|
||||
`BB_FRAMES_AHEAD=N` (на сколько кадров поток команд может опережать GPU; по умолчанию 1, 0 — без
|
||||
ограничения), `BB_PRESENT_THREAD=0` (показ в потоке vblank, как раньше), `BB_LIVE_RES=1` (смена
|
||||
разрешения без перезапуска вместо патча при запуске для вывода не 1080p),
|
||||
`BB_PAD_RECORD=файл` / `BB_PAD_REPLAY=файл` (запись маршрута по F9 и его повтор в тестах),
|
||||
`BB_GC_BUDGET_MB=N` (бюджет кэша текстур, как на встроенных GPU), `BB_VRAM_LIMIT_MB=N` (бюджет
|
||||
VRAM считается не больше N МиБ: проверка поведения карт с малой VRAM), `BB_ASYNC_PIPELINES=0`
|
||||
(новые конвейеры компилируются сразу, игра ждёт; по умолчанию — в фоне), `BB_PRESENT_DUMP_TRIGGER=файл`
|
||||
с `BB_PRESENT_DUMP_COUNT=N` (снимок N показанных кадров подряд),
|
||||
`BB_FSR411_VARIANT=int8|fp8|fp8emu` (вариант FSR 4.1.1; по умолчанию FP8, где у видеокарты есть
|
||||
матрицы FP8), `BB_READBACKS=0|1|2` (чтение игрой памяти, записанной GPU: по умолчанию 1, 2 —
|
||||
точно и медленно, 0 — выключено).
|
||||
Подробнее — в [docs/](docs); последние изменения — [docs/CHANGES_0.4.md](docs/CHANGES_0.4.md),
|
||||
[docs/CHANGES_2026-10-06.md](docs/CHANGES_2026-10-06.md).
|
||||
|
||||
**Игра по сети (shadNet).** Отдельный модуль `out/gpu/libbbnet.so` (`gpu/bbnet`: сетевые и PSN-библиотеки
|
||||
shadPS4 из shadp2p, форка shadPS4, с клиентом shadNet); собирается, когда есть protobuf, nlohmann_json,
|
||||
OpenSSL, zlib и miniupnpc (`shell.nix`), входит в AppImage. Включается на странице лаунчера *Онлайн*
|
||||
(`BB_ONLINE=1`): сообщения, пятна крови и призраки — с The Hunter's Dream, колокола и призывы — через
|
||||
сервер shadNet (по умолчанию `srv.shadps4.net:31313`) с учётной записью shadNet (Online ID и пароль).
|
||||
Офлайн модуль не загружается; сборка без него играет только офлайн.
|
||||
|
||||
## Устройство репозитория
|
||||
|
||||
| Путь | Содержимое |
|
||||
|---|---|
|
||||
| `src/` | Загрузчик (`probe.c`) и HLE-рантайм |
|
||||
| `scripts/` | Подготовка образа игры, линковка модулей, компилятор патчей |
|
||||
| `gpu/` | Библиотека рендерера: видеоядро shadPS4 с правками порта (`gpu/VENDOR.txt`), прослойки, меню ImGui, рантайм FSR 4.1.1 (`gpu/shadps4/video_core/renderer_vulkan/fsr411`) |
|
||||
| `launcher/`, `packaging/` | Лаунчер GTK4; пакет Nix и AppImage |
|
||||
| `patches/` | Патчи сообщества для Bloodborne |
|
||||
| `tools/` | Утилиты разработчика: скриптовые запуски, A/B-переключатели, бенчмарк FSR, переписывание шейдеров FSR 4, `fsr4cap` (запись и извлечение FSR 4.1.1) |
|
||||
| `tests/` | Тесты загрузчика, рантайма, патчей и рендерера |
|
||||
| `docs/` | Заметки и замеры ([апскейлер](docs/upscaler.md), [многопоточный GPU](docs/parallel_gpu.md), [векторы движения](docs/motion_vectors.md), [план](docs/ROADMAP.md), [журнал разработки](docs/DEVELOPMENT_LOG.ru.md)) |
|
||||
|
||||
Тесты: `bash build.sh --test`, `python3 -m unittest discover -s tests` и
|
||||
`ninja -C out/gpu motion-history-test ui-composition-test scene-resolution-test motion-shader-test settings-test`.
|
||||
|
||||
## Что будет дальше
|
||||
|
||||
- Новая модель памяти и трансляции по умолчанию и на NVIDIA, когда она станет там не медленнее;
|
||||
после этого старая модель удаляется.
|
||||
- Текстуры — объекты, которые создаются при загрузке (меньше угадывания по адресам, загрузка
|
||||
вне кадра).
|
||||
- Шейдеры переводятся заранее, при установке, а не во время игры.
|
||||
- Больше параллельности CPU в обработке GPU-команд (дальнейшее разделение стадии записи draw)
|
||||
с масштабированием на все потоки — важнее всего для Steam Deck.
|
||||
- Async compute для апскейлера (в 4K кадр упирается в GPU).
|
||||
- XeSS (суперразрешение) и генерация кадров XeFG через помощник под Wine с общей памятью Vulkan
|
||||
(прототип моста памяти — `tools/bridge_helper`); входы апскейлера
|
||||
в виде, совместимом с подходом OptiScaler.
|
||||
- Генерация кадров (сначала FSR 3.1 FG), маски реактивности и прозрачности для частиц и тумана.
|
||||
- Исправление гонок в шейдерах FSR 4.1.1 от AMD при ширине вывода, не кратной 64 (например,
|
||||
1600×900) — так же, как уже исправлена гонка у левого края в FSR 4 v07 при 1080p.
|
||||
- Проверка AppImage на Steam Deck; вывод HDR.
|
||||
|
||||
## Благодарности и лицензии
|
||||
|
||||
bbport распространяется по **GNU GPL v2 или новее** ([LICENSE](LICENSE)) — в нём код shadPS4
|
||||
(GPL-2.0-or-later). Сторонние компоненты сохраняют свои лицензии:
|
||||
видеоядро и рекомпилятор шейдеров [shadPS4](https://github.com/shadps4-emu/shadPS4) (GPL-2.0+),
|
||||
[sirit](https://github.com/shadps4-emu/sirit), [half](https://half.sourceforge.net/),
|
||||
[FSR-Vulkan](https://github.com/FireBurn/FSR-Vulkan) от FireBurn (MIT; FSR 3.1 на Vulkan и
|
||||
провайдер FSR 4 v07), AMD FidelityFX SDK (MIT), [LibAtrac9](https://github.com/Thealexbarney/LibAtrac9)
|
||||
(MIT), [Dear ImGui](https://github.com/ocornut/imgui) (MIT), шрифты DejaVu,
|
||||
[dxil-spirv](https://github.com/HansKristian-Work/dxil-spirv) (MIT, для сборки ассетов FSR 4.1.1).
|
||||
Патчи игры — Kyo, Lance McDonald, auser1337, illusion, emoose и другие участники сообщества
|
||||
(`patches/Bloodborne.xml`). Мост DLSS (`gpu/dlss_bridge`, MIT) и загрузчик взяты из
|
||||
[Windows-порта Supermedo](https://github.com/Supermedo/bloodborne_pc), адаптированного из
|
||||
[IFreemz/shadPS4-Bloodborne-DLSS-FSR](https://github.com/IFreemz/shadPS4-Bloodborne-DLSS-FSR).
|
||||
DLL и данные модели FSR 4 от AMD, а также SDK и библиотеки DLSS от NVIDIA здесь не
|
||||
распространяются.
|
||||
@@ -1,281 +1,622 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
<https://fsf.org/>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
0. Definitions.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
1. Source Code.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
2. Basic Permissions.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
14. Revised Versions of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
NO WARRANTY
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
@@ -287,15 +628,15 @@ free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
@@ -303,36 +644,31 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Moe Ghoul>, 1 April 1989
|
||||
Moe Ghoul, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,36 @@
|
||||
bbport — a native PC port of Bloodborne (CUSA03173, 1.09)
|
||||
https://github.com/deadinside28/bloodborne_pc
|
||||
|
||||
This copy is the Linux fork, https://github.com/ayozetr/bloodborne_linux. bbport's own files stay
|
||||
GPL-2.0-or-later, as below. The fork as a whole is distributed under the GNU General Public
|
||||
License, version 3 or (at your option) any later version (LICENSE has its text): it includes code
|
||||
under GPL-3.0-or-later (the online module's BBSC and The Hunter's Dream parts), and everything
|
||||
else in it allows that. Files from other projects keep their own notices.
|
||||
|
||||
Copyright 2026 bbport contributors (the authors of this repository's commits and merged pull
|
||||
requests). Licensed under the GNU General Public License, version 2 or (at your option) any later
|
||||
version (the fork's LICENSE has version 3's text). No game code or data is part of this project.
|
||||
|
||||
Files of this project carry, in their first lines,
|
||||
SPDX-FileCopyrightText: Copyright 2026 bbport contributors
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
(or the license and copyright holders of the code they came from). The GPL requires these notices
|
||||
to be kept intact in copies and in works based on them (GPL-2.0, sections 1 and 2): if you reuse
|
||||
bbport's code — the runtime in src/, the GPU layer in gpu/, the scripts, the launchers — keep
|
||||
them, keep this file, and say in your own documentation that the code comes from bbport, with a
|
||||
link to the repository above. Code reused from a fork of bbport is bbport's code too.
|
||||
|
||||
Code bbport is built on, with its own notices kept in its files:
|
||||
|
||||
- shadPS4 (https://github.com/shadps4-emu/shadPS4, GPL-2.0-or-later, Copyright shadPS4 Emulator
|
||||
Project): the GPU layer and the shader translator started from its video core and shader
|
||||
recompiler and were rewritten part by part (gpu/VENDOR.txt). The online module keeps shadPS4
|
||||
v0.20.0's network, NP and shadNet code (gpu/bbnet20/vendor/shadps4, gpu/bbnet20/SHADNET-PORT.md).
|
||||
- The online module's other parts (gpu/bbnet20: BBSC, The Hunter's Dream pool; GPL-3.0-or-later
|
||||
where their files say so) keep their authors' copyright (gpu/bbnet20/SHADNET-PORT.md).
|
||||
- The DLSS bridge (gpu/dlss_bridge, MIT), adapted from IFreemz's shadPS4-Bloodborne-DLSS-FSR
|
||||
(https://github.com/IFreemz/shadPS4-Bloodborne-DLSS-FSR).
|
||||
- AMD FidelityFX shaders and headers (MIT) in gpu/upscale/shaders/ffx, gpu/third_party and
|
||||
tools/fsr4cap/ffx, with AMD's notices.
|
||||
- The libraries in gpu/third_party (sirit, imgui, half, httplib, fsr-vulkan and others), each under
|
||||
its own license in its folder.
|
||||
@@ -1,62 +1,104 @@
|
||||
# Bloodborne for Linux
|
||||
|
||||
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.
|
||||
[](https://github.com/ayozetr/bloodborne_linux/releases/latest)
|
||||
[](https://github.com/ayozetr/bloodborne_linux/releases/latest)
|
||||
[](LICENSE)
|
||||
[](#features)
|
||||
[](https://ko-fi.com/ayozetr)
|
||||
|
||||
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.
|
||||
The PlayStation 4 version of *Bloodborne* running natively on Linux. The game's own code runs
|
||||
directly on the CPU and its graphics are translated to Vulkan: it is not an emulator.
|
||||
|
||||
This fork is built on [bbport](https://github.com/deadinside28/bloodborne_pc) by deadinside28. It
|
||||
adds improvements of its own, and others from the community, taken from pull requests and other
|
||||
projects ([credits](docs/CREDITS.md)).
|
||||
|
||||
> **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.
|
||||
> 1.09, from a retail release ([which ones](docs/COMPATIBILITY.md)). This project is not
|
||||
> affiliated with Sony Interactive Entertainment or FromSoftware.
|
||||
|
||||
**[The players' guide](https://ayozetr.github.io/bloodborne_linux/)**: installing, playing online and
|
||||
seamless co-op, the Steam Deck, and what to do when something goes wrong.
|
||||
|
||||
## Features
|
||||
|
||||
- **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 and Russian, 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.
|
||||
- **Multiplayer**: online messages, bloodstains, ghosts, bells and summons, on
|
||||
[The Hunter's Dream](https://thehuntersdream.com) or a server of your own; **Seamless Co-op**
|
||||
(experimental), co-op that lasts through deaths, lamps and areas, for up to 5 hunters; and
|
||||
**local co-op** (split screen), 2 to 4 players on one PC, each with their own controller.
|
||||
- **In-game map** (Hunter's Atlas): a fullscreen map with where you are, lamps and items, and fast
|
||||
travel to the lamps you've found.
|
||||
- **Frame rate unlocked** (up to 120 FPS recommended; the game's movement can break above it)
|
||||
or capped at **30, 60 or 90**, and changed while you play from the in-game menu. Resolutions from
|
||||
**720p to 4K**, and any **aspect ratio** (4:3 to 32:9 ultrawide, [how](docs/TROUBLESHOOTING.md#the-picture-is-stretched-on-an-ultrawide-screen)).
|
||||
- **Upscaling:** AMD **FSR 3.1** and **FSR 4**, NVIDIA **DLSS** (GeForce RTX) and TAA, and
|
||||
**frame generation** (FSR 3.1, or DLSS on GeForce RTX 40 and newer).
|
||||
- **Any controller:** PlayStation, Xbox and Switch pads with their own **button prompts in the
|
||||
game**, and **keyboard and mouse**, with keys you can rebind (bbport 0.7's Dark Souls
|
||||
III-style layout, or the classic one of earlier Linux versions), movement keys that keep a
|
||||
sprint through turns (the last of two opposite keys wins; `keyboard_movement=0.7` in
|
||||
`bbport.ini` for bbport 0.7's ramped stick) and the mouse as a pointer in the game's menus.
|
||||
- **Exit to desktop** from the game's title and System menus.
|
||||
- **Cheats and gameplay tweaks:** never die, stealth, camera distance, ragdoll physics and more.
|
||||
- **Trophies and the platinum**, with a notice in the game like on the PS4.
|
||||
- **Add to Steam** in one click, and **Discord Rich Presence** with your area, your co-op
|
||||
partners and a button to this repository.
|
||||
- **Launcher and in-game menu** (Insert, or hold L3 + R3) in English, Spanish, French, German, Italian,
|
||||
Portuguese, Russian and Simplified Chinese.
|
||||
- **AppImage** ready for any distribution and the Steam Deck, which **updates itself** from the
|
||||
launcher (and can go back to an earlier release).
|
||||
- **Mods and community patches**, without touching the game files ([how](docs/MODS.en.md)).
|
||||
|
||||
## Requirements
|
||||
|
||||
- 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.
|
||||
- Linux x86-64 and a GPU with Vulkan 1.3: AMD or Intel with Mesa, or NVIDIA with NVIDIA's driver.
|
||||
- About 6 GB of free memory, 10 GB for 1440p or 4K.
|
||||
|
||||
## Getting started
|
||||
|
||||
### AppImage
|
||||
1. Download `Bloodborne-bbport-x86_64.AppImage` from the
|
||||
[latest release](https://github.com/ayozetr/bloodborne_linux/releases/latest).
|
||||
2. Make it executable and run it.
|
||||
3. Choose your game folder (the one with `eboot.bin`) and press **Play**.
|
||||
|
||||
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`.
|
||||
Saves and settings go to `~/.local/share/bbport`; **Setup → Saves** exports your saves into a
|
||||
.zip and imports them back, or from shadPS4, and can keep a copy of them from before each game
|
||||
(off by default). The Game of the Year edition already has The Old
|
||||
Hunters; with the base game, its DLC goes in **Setup → DLC folder** ([how](docs/COMPATIBILITY.md)).
|
||||
|
||||
### From source
|
||||
## Online play
|
||||
|
||||
In the launcher's **Multiplayer → Online** tab, turn on **Play online**, choose a server and press **Test**
|
||||
in the **Status** row (every line should get a check mark):
|
||||
|
||||
- **Seamless Hunt** (the default): the community server of Seamless Co-op (BBSC), with players on
|
||||
the Windows port. Make an account from the launcher with **Create** ([docs/ONLINE.md](docs/ONLINE.md#seamless-hunt)).
|
||||
- **The Hunter's Dream**: sign in with Discord. You play with bbhost's players
|
||||
([docs/ONLINE.md](docs/ONLINE.md#the-hunters-dream-with-bbhosts-players)).
|
||||
- **Your own server**: for your group, with Docker and Tailscale ([docs/SERVER.md](docs/SERVER.md)).
|
||||
- **Seamless Co-op (BBSC)**, in the same tab: every player needs it, and summons across areas need
|
||||
its game server, which your own server can run ([docs/ONLINE.md](docs/ONLINE.md#seamless-co-op-bbsc)).
|
||||
|
||||
With online play on, the game starts online by itself, as PLAY ONLINE on a PS4. If you turned off
|
||||
*Skip the “play online / offline” choice* and pick offline, choose **Log In** on the title screen later
|
||||
for online play; until then the co-op bells are greyed out.
|
||||
|
||||
Everyone in a session needs update 1.09. Turn cheats off when you play with others. Ports,
|
||||
firewalls and the rest: [docs/ONLINE.md](docs/ONLINE.md).
|
||||
|
||||
## Local co-op (experimental)
|
||||
|
||||
Several games on one PC, one per player, each in its part of the screen with its own controller
|
||||
(the launcher's **Multiplayer → Local** tab): choose the players (2 to 4), the layout and each player's
|
||||
controller, then press **Play together**. Player 1 plays with the launcher's settings and saves;
|
||||
players 2 to 4 get their own saves and settings in `~/.local/share/bbport/coop/player<N>` (copied
|
||||
from player 1's the first time, about 400 MB each), without online play or Discord. One player can
|
||||
use the keyboard and mouse. Only player 1 has sound unless you turn that off. The windows can go
|
||||
to a monitor of their own (**Monitor**), and **Copy player 1's saves** gives another player the
|
||||
same characters (their own saves are backed up first). The windows are placed through X11, which
|
||||
works in a Wayland session too (XWayland).
|
||||
|
||||
## Building from source
|
||||
|
||||
On Arch Linux (or CachyOS, Manjaro…):
|
||||
|
||||
@@ -69,99 +111,42 @@ 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
|
||||
python3 launcher/bbport_launcher.py
|
||||
```
|
||||
|
||||
With [Nix](https://nixos.org), `bash build.sh` gets every dependency from `shell.nix` by itself.
|
||||
With [Nix](https://nixos.org), `bash build.sh` gets every dependency by itself. DLSS needs
|
||||
`bash tools/build_dlss.sh` once; the AppImage is `bash packaging/appimage-sharun.sh`, which bundles
|
||||
the libraries of the PC it runs on. Releases are built with `bash packaging/appimage-in-arch.sh`
|
||||
(Docker, plain Arch Linux): on distributions built for newer processors (CachyOS's x86-64-v3
|
||||
repositories, for example) the AppImage would otherwise not start on CPUs without AVX2. Environment
|
||||
variables for the launcher's **Advanced → Developer → Extra variables**: [docs/ENVIRONMENT.md](docs/ENVIRONMENT.md).
|
||||
Problems: [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md).
|
||||
|
||||
### Online play
|
||||
## On Windows
|
||||
|
||||
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.
|
||||
|
||||
Everyone in a session needs game version 1.09 and the same servers. Turn cheats off when you
|
||||
play with others.
|
||||
|
||||
**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.
|
||||
|
||||
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).
|
||||
|
||||
### DLSS
|
||||
|
||||
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.
|
||||
|
||||
### DLC
|
||||
|
||||
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.
|
||||
|
||||
### Add to Steam
|
||||
|
||||
**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.
|
||||
|
||||
### Button prompts
|
||||
|
||||
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.
|
||||
If you need a Windows version, I recommend
|
||||
[Supermedo's Windows port](https://github.com/Supermedo/bloodborne_pc) and
|
||||
[Awakenchan's LanternEngine](https://github.com/Awakenchan/LanternEngine).
|
||||
|
||||
## Support
|
||||
|
||||
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).
|
||||
This fork is built on the port deadinside28 created, but it has a lot of hours of work behind it,
|
||||
and above all hours and hours of testing things and hunting down fixes so it runs as well as it
|
||||
can. I always try to fix every report as soon as possible, because I know how much this game means
|
||||
to some people. If it's useful to you and you'd like to support it, you can on
|
||||
[Ko-fi](https://ko-fi.com/ayozetr).
|
||||
|
||||
## Credits
|
||||
|
||||
- [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.
|
||||
|
||||
Full list of components and licenses: [BBPORT.md](BBPORT.md#credits-and-licenses).
|
||||
Built on [bbport](https://github.com/deadinside28/bloodborne_pc) by deadinside28, with
|
||||
[shadPS4](https://github.com/shadps4-emu/shadPS4)'s renderer, and with work from many people and
|
||||
projects: everyone is in [docs/CREDITS.md](docs/CREDITS.md). It includes code from
|
||||
Lantern Engine by Awaken (https://github.com/Awakenchan/LanternEngine). Components and their licenses:
|
||||
[BBPORT.md](BBPORT.md#credits-and-licenses).
|
||||
|
||||
## License
|
||||
|
||||
GNU GPL v2 or later ([LICENSE](LICENSE)). Third-party components keep their own licenses. NVIDIA
|
||||
GNU GPL v3 or later ([LICENSE](LICENSE)). Files taken from other projects keep their own notices:
|
||||
most are GPL-2.0-or-later (bbport, shadPS4), which allows them to be distributed under
|
||||
GPL-3.0-or-later. Third-party components keep their own licenses. NVIDIA
|
||||
and DLSS are trademarks of NVIDIA Corporation.
|
||||
@@ -11,6 +11,8 @@ Only what the mods drew is here, never the game's own files:
|
||||
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.
|
||||
The port draws its button prompts live (`gpu/shim/bbport_button_prompts.cpp`): `scripts/prompt_art.py`
|
||||
turns the 20 icons into the raw images it uses for the Xbox and Switch styles, and the keyboard's
|
||||
are its own. `scripts/gestures_menu.py` applies `gesturetop.json` to the player's own
|
||||
`menu/gesturetop.gfx` when the launcher's *Gestures menu without motion-control icons* is on.
|
||||
The Linux fork's `tools/extract_prompt_assets.py` rebuilt this folder from the mods' ZIPs.
|
||||
@@ -28,6 +28,12 @@ pgo=${BB_PGO:-}
|
||||
if [[ -z $pgo ]]; then
|
||||
if [[ -n $(find pgo -name '*.gcda' -print -quit 2>/dev/null) ]]; then pgo=use; else pgo=off; fi
|
||||
fi
|
||||
# A profile only applies when its files are named as this build names them (relative to out/gpu,
|
||||
# -fprofile-prefix-path): one recorded elsewhere matched nothing and PGO was silently off.
|
||||
if [[ $pgo == use && -z $(find pgo -name 'CMakeFiles#bbgpu.dir#*.gcda' -print -quit 2>/dev/null) ]]; then
|
||||
echo 'PGO: the profile in pgo/ is from another build layout (rebuild it: BB_PGO=generate); built without it' >&2
|
||||
pgo=off
|
||||
fi
|
||||
mkdir -p pgo
|
||||
# Submodules (git clone --recursive, or: git submodule update --init) and this port's changes
|
||||
# to FSR-Vulkan (gpu/patches/fsr-vulkan), applied to its working tree once.
|
||||
@@ -50,25 +56,47 @@ if ! fsr_patches_apply --check 2>/dev/null; then
|
||||
fi
|
||||
fsr_patches_apply
|
||||
cmake -S gpu -B out/gpu -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBB_PGO="$pgo" \
|
||||
-DBB_LTO="${BB_LTO:-ON}" -DBB_PGO_DIR="$PWD/pgo" >/dev/null
|
||||
-DBB_LTO="${BB_LTO:-ON}" -DBB_PGO_DIR="$PWD/pgo" > out/gpu-cmake.log
|
||||
grep '^-- Online module' out/gpu-cmake.log || true
|
||||
echo "GPU library: PGO $pgo, LTO ${BB_LTO:-ON}"
|
||||
# A failed GPU build must stop here: an older libbbgpu.so would otherwise be used silently.
|
||||
if ! ninja -C out/gpu bbgpu $(grep -q "^build bbnet:" out/gpu/build.ninja && echo bbnet) > out/gpu-build.log 2>&1; then
|
||||
if ! ninja -C out/gpu bbgpu $(grep -q "^build bbnet20:" out/gpu/build.ninja && echo bbnet20) > out/gpu-build.log 2>&1; then
|
||||
grep -v '^\[' out/gpu-build.log | tail -40 >&2
|
||||
echo 'GPU library build failed (full log: out/gpu-build.log)' >&2; exit 1
|
||||
fi
|
||||
# Optional DLSS (NVIDIA RTX): DLSS_SDK_ROOT=<github.com/NVIDIA/DLSS checkout> builds the bridge
|
||||
# (gpu/dlss_bridge, the only code using NVIDIA's SDK) and puts it next to bb-probe with NVIDIA's
|
||||
# libnvidia-ngx-dlss.so. Without them the DLSS upscaler is listed as unavailable.
|
||||
# libnvidia-ngx-dlss.so (Super Resolution) and libnvidia-ngx-dlssg.so (Frame Generation, GeForce
|
||||
# RTX 40 and newer; SDK 310.7 or newer has it). Without them the DLSS upscaler and DLSS Frame
|
||||
# Generation are listed as unavailable.
|
||||
if [[ -n ${DLSS_SDK_ROOT:-} ]]; then
|
||||
cmake -S gpu/dlss_bridge -B out/dlss-bridge -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DDLSS_SDK_ROOT="$DLSS_SDK_ROOT" >/dev/null
|
||||
ninja -C out/dlss-bridge >/dev/null
|
||||
rm -f out/libnvidia-ngx-dlss.so.*
|
||||
rm -f out/libnvidia-ngx-dlss.so.* out/libnvidia-ngx-dlssg.so.*
|
||||
cp out/dlss-bridge/libbbport_dlss.so "$DLSS_SDK_ROOT"/lib/Linux_x86_64/rel/libnvidia-ngx-dlss.so.* out/
|
||||
if ls "$DLSS_SDK_ROOT"/lib/Linux_x86_64/rel/libnvidia-ngx-dlssg.so.* >/dev/null 2>&1; then
|
||||
cp "$DLSS_SDK_ROOT"/lib/Linux_x86_64/rel/libnvidia-ngx-dlssg.so.* out/
|
||||
echo "DLSS Frame Generation library: $(ls out/libnvidia-ngx-dlssg.so.*)"
|
||||
else
|
||||
echo "DLSS Frame Generation library not in this SDK (lib/Linux_x86_64/rel/libnvidia-ngx-dlssg.so.*): DLSS Super Resolution only"
|
||||
fi
|
||||
cp "$DLSS_SDK_ROOT/LICENSE.txt" out/NVIDIA-DLSS-LICENSE.txt
|
||||
echo "DLSS bridge: out/libbbport_dlss.so"
|
||||
fi
|
||||
# Installing from PS4 packages (launcher: Install from PKG, scripts/pkg_install.py): pkgextract
|
||||
# (github.com/paulomanrique/ps4-pkg-extractor, Unlicense) at a reviewed revision, with its
|
||||
# Cargo.lock. Needs cargo; without it the launcher asks for a game folder only.
|
||||
pkgextract_rev=f430ce1634fc7953834b545a3946bfbd5fa216fa
|
||||
if [[ ! -x out/pkgextract/bin/pkgextract ]] && command -v cargo >/dev/null; then
|
||||
if cargo install --quiet --locked --root out/pkgextract \
|
||||
--git https://github.com/paulomanrique/ps4-pkg-extractor --rev "$pkgextract_rev" pkgextract \
|
||||
>out/pkgextract-build.log 2>&1; then
|
||||
echo 'PKG extractor: out/pkgextract/bin/pkgextract'
|
||||
else
|
||||
echo 'pkgextract build failed (out/pkgextract-build.log): Install from PKG is not offered' >&2
|
||||
fi
|
||||
fi
|
||||
# $ORIGIN/gpu: packaged copies keep the library next to the binary without patching it.
|
||||
gpu=(-Lout/gpu -lbbgpu -Wl,-rpath,'$ORIGIN/gpu' -Wl,-rpath,"$PWD/out/gpu" -rdynamic)
|
||||
runtime=(src/runtime*.c)
|
||||
@@ -84,11 +112,15 @@ echo "Built $PWD/out/bb-probe"
|
||||
# GPU check for run.sh (live_resolution=auto) and the launcher's gamepad list (--gamepads).
|
||||
"$CC" -std=c11 -O2 -Wall -Wextra -Werror "${includes[@]}" tools/gpu_capabilities.c "${libraries[@]}" -o out/bb-gpu-capabilities
|
||||
if [[ ${1:-} == --test ]]; then
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread "${includes[@]}" -I. -Isrc tests/test_pad.c "${libraries[@]}" -o out/pad-test
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread "${includes[@]}" -I. -Isrc tests/test_pad.c "${libraries[@]}" -lm -o out/pad-test
|
||||
out/pad-test
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread "${includes[@]}" -I. -Isrc tests/test_pad_atlas.c "${libraries[@]}" -lm -o out/pad-atlas-test
|
||||
out/pad-atlas-test
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread -I. -Isrc tests/test_runtime.c "${runtime[@]}" out/libatrac9.a -lm "${gpu[@]}" "${libraries[@]}" -o out/runtime-test
|
||||
out/runtime-test
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread -Isrc tests/test_file_mods.c -o out/file-mods-test
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -Isrc tests/test_movbe.c src/runtime_movbe.c -o out/movbe-test
|
||||
out/movbe-test
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread -Isrc tests/test_file_mods.c src/runtime_remote.c -o out/file-mods-test
|
||||
out/file-mods-test
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread -I. -Isrc tests/test_sema.c "${runtime[@]}" out/libatrac9.a -lm "${gpu[@]}" "${libraries[@]}" -o out/sema-test
|
||||
out/sema-test
|
||||
@@ -96,4 +128,25 @@ if [[ ${1:-} == --test ]]; then
|
||||
out/online-test
|
||||
"$CC" -std=c11 -D_GNU_SOURCE -O2 -g -Wall -Wextra -Werror -I. -Isrc tests/test_content.c src/runtime_content.c -o out/content-test
|
||||
out/content-test
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread -Isrc tests/test_trophy.c src/runtime_trophy.c -o out/trophy-test
|
||||
out/trophy-test out/trophy_test_tmp
|
||||
# Hunter's Atlas: its guarded game-memory readers on synthetic images.
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -Isrc tests/test_map_location.c src/runtime_map.c -lm -o out/map-location-test
|
||||
out/map-location-test
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread -Isrc tests/test_atlas_state.c src/runtime_atlas_state.c -lm -o out/atlas-state-test
|
||||
out/atlas-state-test
|
||||
# Hunter's Atlas: its tile's state machine, map styles, floor interiors, controller shortcut.
|
||||
# (-Wno-misleading-indentation: Atlas's compact one-line style.) bbport (Linux fork):
|
||||
# nlohmann/json from the system, as gpu/CMakeLists.txt.
|
||||
atlas_cxx=(-std=c++20 -O2 -g -Wall -Wextra -Werror -Wno-misleading-indentation -Igpu/shim -Igpu/third_party/imgui "${includes[@]}")
|
||||
for test in test_bbport_atlas_menu_state test_bbport_atlas_style test_bbport_atlas_interiors; do
|
||||
"${CXX:-c++}" "${atlas_cxx[@]}" "tests/$test.cpp" -o "out/$test" || exit 1
|
||||
(cd out && "./$test") || exit 1
|
||||
done
|
||||
"${CXX:-c++}" "${atlas_cxx[@]}" tests/test_map_controller.cpp gpu/shim/bbport_map_controller.cpp "${libraries[@]}" -o out/map-controller-test
|
||||
out/map-controller-test
|
||||
"${CXX:-c++}" "${atlas_cxx[@]}" tests/test_atlas_features.cpp gpu/shim/bbport_map_features.cpp "${libraries[@]}" -o out/atlas-features-test
|
||||
rm -rf out/atlas-features && out/atlas-features-test out/atlas-features
|
||||
"${CXX:-c++}" "${atlas_cxx[@]}" tests/test_atlas_i18n.cpp -o out/atlas-i18n-test
|
||||
out/atlas-i18n-test
|
||||
fi
|
||||
@@ -1,273 +0,0 @@
|
||||
# Изменения в 0.4 (после 0.3, с 2026-10-06)
|
||||
|
||||
Замеры — RX 7800 XT / RADV, Yahar'gul стоя, 1080p без лимита FPS; «4 ядра» — `CPUS=0-3,8-11`
|
||||
(4 ядра / 8 потоков, как у Steam Deck).
|
||||
|
||||
## Модель памяти ПК — снова по умолчанию, и для NVIDIA
|
||||
|
||||
- `BB_GUEST_IN_PLACE=1` по умолчанию (`run.sh`). В лаунчере вместо «Новой модели памяти» —
|
||||
переключатель *Старая модель памяти* (ключ `legacy_memory_model`): модель 0.2 как запасной вариант.
|
||||
- NVIDIA отображает dma-buf только со смещения 0. Раньше порт тогда возвращался к старой модели;
|
||||
теперь у таких драйверов каждое выделение прямой памяти игры — отдельный кусок памяти Vulkan
|
||||
(игра делает 14 выделений и каждое отображает один раз целиком, со смещения 0). Проверка при
|
||||
запуске пишет «the driver's dma-buf maps at offset 0 only: one chunk per direct memory allocation».
|
||||
`BB_GUEST_WHOLE_CHUNKS=1` включает этот режим и на других драйверах (проверка без NVIDIA):
|
||||
на RX 7800 XT 188–190 FPS, картинка в порядке. **На NVIDIA не проверено.**
|
||||
|
||||
## Уровень API: libGnm игры
|
||||
|
||||
Игра собрана со статической libGnm: пакеты PM4 пишет не драйвер, а сама игра, через функции
|
||||
`sce::Gnm::DrawCommandBuffer` / `DispatchCommandBuffer` / `ConstantCommandBuffer` в
|
||||
`0x1068000–0x10a0000` и `0x11fd000–0x11fe000`. Это и есть графический API игры — уровень, на
|
||||
котором работают слои вроде DXVK.
|
||||
|
||||
- `tools/re/`: поиск пакетов PM4 в коде (`pm4_scan.py`), граф вызовов по границам функций из
|
||||
`.eh_frame` (`callgraph.py`), сигнатуры функций по регистрам (`gnm_sigs.py`), таблица перехватов
|
||||
(`gen_gnm_table.py` → `gpu/shim/bbport_gnm_table.inc`), имена функций (`gnm_names.txt`).
|
||||
- `gpu/shim/bbport_gnm_hooks.cpp`: перехват 513 функций libGnm в начале (jmp на заглушку рядом с
|
||||
образом, перенесённый пролог), до запуска кода игры; каждая точка сверяется с eboot 1.09.
|
||||
- `BB_GNM_CENSUS=1` — счёт вызовов (`BB_GNM_CENSUS_FILE` — полный список в файл).
|
||||
- `BB_GNM_OBSERVE=1` — каждый метод командного буфера отмечает пакеты, которые записал;
|
||||
при отправке проверяется, откуда пришёл каждый пакет. Итог: **100,0 % пакетов (99,9 % слов)
|
||||
пишут известные вызовы API** — командный поток игры целиком описывается вызовами Gnm.
|
||||
- `BB_GNM_TRACE=адрес[:число]` — аргументы, вызывающий код и записанные пакеты.
|
||||
- Найдено: игра вызывает 99 методов командных буферов; обратный вызов резервирования
|
||||
`0x26af230` начинает новый кусок буфера (список кусков уходит в отправку отдельными DCB);
|
||||
`writeDataInline` (`0x1073b80`) из GX обнуляет 192 байта по разу на проход; метки EOP
|
||||
(`writeAtEndOfPipe`, значение 1) ставит распределитель памяти GPU, `waitOnAddress` ждёт их
|
||||
же — это барьер в порядке очереди, а не ожидание CPU.
|
||||
|
||||
## Быстрее на слабых процессорах
|
||||
|
||||
- `WRITE_DATA` и DMA ждали все отложенные копии хоста (22 мкс на каждый `WRITE_DATA`,
|
||||
~35 за кадр, 12 % времени потока записи отрисовок на 4 ядрах). Теперь ждут только копии,
|
||||
которые читают записываемый диапазон (`Scheduler::WaitHostCopiesFor`, таблица источников по
|
||||
64 КиБ); командный процессор PS4 тоже не ждёт предыдущих отрисовок перед такой записью.
|
||||
Ожидания: 37 → 2 за кадр. `BB_HOST_COPY_WAITS=all` — прежнее поведение.
|
||||
- Проверка одновременной записи в планировщик (`ProducerScope`) включается только с
|
||||
`BB_PRODUCER_CHECK=1` (лаунчер: *Диагностика вылетов*).
|
||||
- 4 ядра: 158 → 167 FPS.
|
||||
- `BB_SECTIONS=1` показывает и задачи стадии B по функциям.
|
||||
|
||||
## Исправления
|
||||
|
||||
- Свёрнутое окно (Win+D, «Показать рабочий стол») больше не завершает игру (issue #31):
|
||||
поверхность 0×0 не попадает ни в размер кадра, ни в swapchain; кадры, пока окно свёрнуто,
|
||||
не выводятся. Проверено только, что обычный вывод не изменился: в Hyprland окно не
|
||||
сворачивается до 0×0.
|
||||
|
||||
## Сравнение с 0.3
|
||||
|
||||
Лаунчер, раздел «Для разработчика»: *Синхронизация как в 0.3* (`BB_AS_0_3=1` в `run.sh`)
|
||||
возвращает то, что изменилось после релиза: старая модель памяти, `WRITE_DATA`/DMA ждут все
|
||||
копии хоста (`BB_HOST_COPY_WAITS=all`), проверка одновременной записи (`BB_PRODUCER_CHECK=1`).
|
||||
Yahar'gul стоя, 1080p: 123 FPS против ~186 с новой моделью памяти и трансляции. Из командной
|
||||
строки: `bash tools/play_ab.sh v03 BB_AS_0_3=1`.
|
||||
|
||||
## Глубина резкости (DoF) снова работает (issue #44)
|
||||
|
||||
С DoF и любым апскейлером (FSR 3/4/4.1.1, TAA) в дальнем размытии — небо, туман — появлялись
|
||||
тёмные прямоугольники с резкими краями, полосы и перевёрнутая копия сцены; в 0.2 этого не было.
|
||||
Причина — прицельные ожидания дампов констант из 0.3 (`DumpConstRam` ждёт поток записи отрисовок,
|
||||
только если пересекается с их чтениями): в список чтений попадали лишь константные буферы, а
|
||||
вершины и индексы поток записи тоже читает из памяти игры. Игра пишет мелкие данные отрисовок
|
||||
(в том числе полноэкранные квады DoF) через движок констант, и дамп для следующих отрисовок
|
||||
перезаписывал их раньше, чем их успевали прочитать. Окно открывалось, когда поток записи
|
||||
задерживался прямо перед проходами DoF — на проходах апскейлера. Теперь диапазоны вершин и
|
||||
индексов тоже в списке (неподготовленные отрисовки ждут целиком). Ожиданий стало ~850/с вместо
|
||||
~150/с, FPS в проверенных местах не изменился. `BB_CONSTRAM_PIPE=0` — полное ожидание на каждом
|
||||
дампе (A/B).
|
||||
|
||||
Возможно, это же чинит мерцающие значки и глифы интерфейса (issue #11) — не проверено.
|
||||
|
||||
## Кэш шейдеров больше не ломает запуск (issues #28, #38, #39)
|
||||
|
||||
- Файлы кэша пишутся под временным именем и переименовываются: вылет или отключение питания
|
||||
оставляют старый файл или никакого, а не обрезанный.
|
||||
- Обрезанная запись при чтении больше не останавливает игру (`Invalid or corrupted
|
||||
deserialization container`, код 23): повреждённые записи считаются, кэш пересобирается.
|
||||
То же для SPIR-V с неверным заголовком и для пайплайнов, которые драйвер отверг при
|
||||
предзагрузке. Код SRT walker из кэша регистрируется только после проверки длины.
|
||||
|
||||
## Прочее
|
||||
|
||||
- Процессор без AVX, BMI1, MOVBE, LZCNT, POPCNT или SSE4.2 (Intel до Haswell) — понятное
|
||||
сообщение при запуске вместо `Guest fault (signal 4)`, код 132 (issue #26).
|
||||
`BB_SKIP_CPU_CHECK=1` — запустить всё равно.
|
||||
- `tools/fsr4cap/build_assets.sh` молча завершался, если в одной из папок Steam не было
|
||||
GE-Proton (issue #40).
|
||||
- Копии памяти игры, поставленные в прямом режиме записи (отрисовка после проходов апскейлера,
|
||||
`BB_VK_RECORD_THREAD=0`), выполняются сразу, а не со следующей отправкой.
|
||||
- Внутриигровое меню на английском или русском — переключатель «Language» в меню (PR #36);
|
||||
по умолчанию язык лаунчера. Лаунчер на бразильском португальском (PR #10); строки без перевода
|
||||
— на английском. README: адрес репозитория в команде клонирования (PR #43).
|
||||
- Переключатель `BB_TOGGLE_FILE`: бит 31 отключает сборщик текстур; второе число в файле —
|
||||
временные биты экспериментов (`BbToggle::Experiment`).
|
||||
|
||||
## Векторы движения (ночь на 2026-10-07, проверяется)
|
||||
|
||||
- Знак оси Y векторов камеры берётся из вьюпорта G-buffer каждый кадр (`BB_CAMERA_Y=up/down` —
|
||||
принудительно), а не задан константой: 0.2 и 0.3 ошибались каждая в своих местах
|
||||
(issues #41, #42, #47, #18).
|
||||
- Если в кадре несколько проходов G-buffer, камера берётся у того, чья глубина идёт в векторы.
|
||||
- FSR 4 на пресетах с апскейлом получает кадр в линейном свете (`BB_FSR4_LINEAR=0` — как раньше).
|
||||
- Маска реактивности работает и с FSR 4: его результат смешивается с текущим кадром на
|
||||
эффектах (туман, свет, частицы).
|
||||
|
||||
## Поток записи отрисовок не ждёт потоки записи команд (2026-10-07)
|
||||
|
||||
- Презентер (барьер кадра, FSR 1, постобработка) записывается так же, как отрисовки, на потоках
|
||||
записи команд. Раньше он брал командный буфер напрямую: поток отрисовок ждал, пока потоки
|
||||
записи закончат всё переданное (~90 мкс каждый кадр), и сам отправлял кадр (~45 мкс).
|
||||
- Где команды всё же пишутся напрямую (проходы апскейлера: векторы движения, TAA, FSR 3/4,
|
||||
резкость), они идут в отдельный сегмент со своим пулом команд, после переданных сегментов,
|
||||
без ожидания (`BB_DIRECT_SEGMENT=0` — ожидание, как раньше). Сигналы и упорядоченные задачи
|
||||
в таком сегменте остаются в очереди, чтобы не обогнать более ранние метки.
|
||||
- Поток показа ждёт, пока отправка кадра (с потока записи) ушла в очередь, прежде чем ставить
|
||||
своё ожидание на неё: иначе одна очередь ждала бы более позднюю отправку (зависание GPU при
|
||||
запуске, найдено на этой работе).
|
||||
- DMA в память, которую GPU использует на месте (модель ПК) или которую писал GPU, — это копия
|
||||
на GPU в порядке потока; поток отрисовок больше не ждёт перед ней копии и отложенные сигналы
|
||||
(~16 мкс на каждую, ~900 в секунду: 4-байтовые счётчики GDS).
|
||||
- Исправлены два давних редких случая записи в уже отправленный командный буфер: смена размера
|
||||
при резкости больше 1 (`Finish()` посреди прохода апскейлера) и смена пресета FSR 4 в меню.
|
||||
- `BB_FINAL_DUMP_TRIGGER` (`KIND=final tools/frame.sh`): снимок кадра после FSR и постобработки.
|
||||
|
||||
Замеры (Hunter's Dream, стоя, 4 ядра / 8 потоков, модель ПК): 143–144 → 146 FPS; поток
|
||||
отрисовок больше не блокируется на записи команд (было 1,8–3,3 % времени). 16 потоков: модель ПК
|
||||
~186, старая модель 144–151 (разброс между запусками ±3 %, без разницы с прежней сборкой).
|
||||
|
||||
## Режим работы в лаунчере (2026-10-07)
|
||||
|
||||
- Группа *Режим работы*, переключатель *Новая модель памяти и трансляции* (`BB_PC_MODEL=1`),
|
||||
по умолчанию выключен. Выключен — старая модель памяти, как в 0.3, со всеми исправлениями
|
||||
после неё. Включён — память игры используется видеочипом на месте, работа командного
|
||||
процессора переводится в команды Vulkan (экспериментально).
|
||||
- Только видеокарты AMD: лаунчер находит их по `/sys/class/drm/card*/device/vendor` (0x1002) и
|
||||
без AMD делает переключатель недоступным; библиотека GPU сама держит режим выключенным, если
|
||||
выбранное устройство Vulkan не AMD (гибридные ноутбуки). `BB_PC_MODEL_ANY_GPU=1` — включить
|
||||
всё равно, для проверки.
|
||||
- Прежний переключатель *Как в версии 0.3* переехал в раздел «Для разработчика» под именем
|
||||
*Синхронизация как в 0.3*: это откат для поиска регрессий, а не один из двух режимов.
|
||||
- README (оба) описывают оба режима, таблицу отличий от shadPS4 и планы.
|
||||
|
||||
## FSR 4.1.1 из своей DLL — кнопкой в лаунчере (2026-10-07)
|
||||
|
||||
- *Апскейлер → FSR 4.1.1 из своей DLL AMD → Выбрать DLL…*: выбираешь
|
||||
`amd_fidelityfx_upscaler_dx12.dll` 4.1.x, лаунчер собирает ассеты (`tools/fsr4cap/build_assets.sh`)
|
||||
в фоне, ход — в подписи строки и во вкладке «Журнал», в конце FSR 4.1.1 выбирается. Отмена той
|
||||
же кнопкой. Без окна: `--build-fsr411 <DLL> [загрузчик]`.
|
||||
- DLL проверяется до записи (`tools/fsr4cap/dll_info.py`: версия из свойств файла, модели FSR 4
|
||||
по именам шейдеров). Сборки сообщества OptiScaler (4.0.2b/c: модель `v07_i8`, 16 проходов вместо
|
||||
29, другие веса) пока не подходят — об этом сразу сообщение, без записи. Загрузчик ищется рядом
|
||||
с DLL и папкой выше под обоими именами (`amd_fidelityfx_loader_dx12.dll`, у OptiScaler —
|
||||
`amd_fidelityfx_dx12.dll`), иначе лаунчер его спрашивает.
|
||||
- Proton запускается через umu-run, если он установлен, иначе в контейнере Steam, который требует
|
||||
этот Proton (`toolmanifest.vdf`: Steam Linux Runtime 4 для GE-Proton 11) — umu больше не нужен.
|
||||
- AppImage содержит собранные заранее dxil-spirv и fsr4cap.exe (Nix), SPIRV-Tools: компиляторы и
|
||||
сеть не нужны, только GE-Proton. Рабочая папка (префикс Proton ~300 МБ) удаляется после сборки.
|
||||
- Заголовки FidelityFX API (MIT) лежат в `tools/fsr4cap/ffx`: SDK больше не клонируется.
|
||||
- Новый набор заменяет старый только целиком; коды выхода скрипта (2–7) лаунчер показывает
|
||||
понятным текстом. Собранная ранее dxil-spirv, которая не запускается (дерево переехало),
|
||||
собирается заново.
|
||||
|
||||
Проверено: из исходников (Nix, umu) и пакет (Steam Linux Runtime 4 в FHS-окружении) — результат
|
||||
побайтно совпадает с прежним `fsr4_411`; кнопка в лаунчере (headless-сессия): ход «N из 20»,
|
||||
отказ для 4.0.2c. Не проверено: AppImage на обычном дистрибутиве / Steam Deck (на NixOS AppImage
|
||||
скрывает системный `/nix` и FHS-ссылки).
|
||||
- После первой проверки (DLL из Pragmata): это официальная FSR 4.0.3 с той же моделью
|
||||
`v07_fp8_no_scale`, но её провайдер AMD показывает только на RDNA4 — на RX 7800 XT под любым
|
||||
Proton DLL предлагает лишь FSR 3.1.5 и 2.3.4. Теперь 4.0.x отклоняется сразу с объяснением.
|
||||
Отклоняется и «загрузчик», который на самом деле DLL апскейлера (так получилось в видео:
|
||||
загрузчика рядом не было, и в окне выбора оказалась та же DLL).
|
||||
- Proton выбирается перебором: первый, под которым DLL включает FSR 4.1 (GE-Proton 10+,
|
||||
Proton-CachyOS, Proton Experimental, затем Proton 11.0/10.0 из Steam), у каждого свой префикс.
|
||||
Проверено на RX 7800 XT: GE-Proton 11-6, Proton-CachyOS 11 (июль), Experimental (октябрь) —
|
||||
да; Proton 11.0-2c и GE-Proton 9 — нет. На Steam Deck был только GE-Proton 9.
|
||||
- На NixOS AppImage сразу говорит, что рантайм Steam там не запустить (код 8), вместо
|
||||
непонятного «не запустился под Proton».
|
||||
|
||||
## FSR 4.1.1: одна DLL и вариант FP8 для RDNA4 (2026-10-07, день)
|
||||
|
||||
- **Загрузчик больше не нужен.** DLL апскейлера AMD сама экспортирует FidelityFX API
|
||||
(`ffxCreateContext` …), и `fsr4cap.exe` загружает её напрямую; `dll_info.py` проверяет экспорт
|
||||
по таблице PE. Загрузчик спрашивается только у DLL без этих функций (таких пока не встречалось).
|
||||
Запись с DLL напрямую совпадает с записью через загрузчик (кроме двух байт неинициализированной
|
||||
константы в кадре 0), набор — побайтно.
|
||||
- Повторная сборка из AppImage падала: `cp: cannot create regular file …/fsr4cap.exe: Permission
|
||||
denied` — копия файла из AppImage только для чтения, и вторая копия поверх неё не создаётся.
|
||||
Теперь старый файл удаляется перед копированием (и DLL, если она только для чтения).
|
||||
- **FP8 на RDNA4.** На RDNA4 vkd3d-proton даёт DLL матрицы FP8, и она запускает другой вариант
|
||||
проходов (issue #12): те же 29 проходов, константы и буферы, но шейдеры на WMMA FP8, свои веса и
|
||||
своя сетка групп (у каждого прохода свой тайл: 16×8, 32×1, 16×4 …; `_post` — ширина тензора
|
||||
выровнена до 32, а не до 4). Сборка после INT8 пробует FP8 на первом размере и, если DLL
|
||||
отдаёт другие шейдеры (RDNA4), записывает все 20 размеров во второй раз (`fp8/capture_*`).
|
||||
`extract.py` проверяет правила обоих вариантов и пишет `fsr4_411/fp8/` (матрицы FP8,
|
||||
`VK_EXT_shader_float8`) и `fsr4_411/fp8emu/` (FP8 через FP16-матрицы, как vkd3d-proton на
|
||||
RDNA3 — только для проверки). Игра выбирает FP8 сама, если у видеокарты есть матрицы FP8 и
|
||||
набор собран; `BB_FSR411_VARIANT=int8|fp8|fp8emu` — принудительно. В строке
|
||||
`Upscaler: FSR 4.1.1 replay, …` теперь указан вариант.
|
||||
- Проходы с матрицами запускаются с подгруппой 32 и полными подгруппами, как у vkd3d-proton
|
||||
(эвристика размера волны у них совпадает с использованием матриц — `extract.py` это проверяет).
|
||||
Устройство игры включает `VK_KHR_cooperative_matrix`, `VK_EXT_shader_float8`,
|
||||
`vulkanMemoryModel`, `computeFullSubgroups`, если они есть.
|
||||
- Проверено на RX 7800 XT: FP8 с эмуляцией (GE-Proton 11-6, `BB_FSR4CAP_FP8=1`) — DLL и повтор
|
||||
**побайтно одинаковы** на 7 размерах, обе модели, 4 кадра (`VARIANT=fp8emu verify.sh`; с
|
||||
dxil-spirv той же версии, что в vkd3d-proton GE-Proton 11-6 — наша закреплённая новее и
|
||||
иначе округляет преобразования FP8 в эмуляции). INT8 — как прежде (1600×900 расходится у
|
||||
правого края из-за гонки в самой DLL). На RDNA4 вживую **не проверено**: проходы модели FP8
|
||||
от обеих версий dxil-spirv одинаковы, различаются только `_post`.
|
||||
- На RDNA3 FP8 по умолчанию не записывается: эмуляция почти вдвое медленнее INT8 (1440p Balanced,
|
||||
RX 7800 XT: 3,35 мс против 1,82 мс). `BB_FSR4CAP_FP8=1` —
|
||||
записать и там, `BB_FSR4CAP_FP8=0` — не записывать нигде.
|
||||
|
||||
## Управление и игровое меню (2026-10-07)
|
||||
|
||||
- **Меню жестов с тачпада.** Касания тачпада приходили с id 0 и без времени удержания, и игра
|
||||
их не принимала: ни тачпад, ни Back, ни Tab не открывали жесты. Теперь каждое новое касание
|
||||
получает свой id (1–127), как у DualShock 4, и время удержания. Проверено в игре: левая половина
|
||||
тачпада — «Жесты», правая — «Личные вещи» (с включённым debug menu левая половина открывает его).
|
||||
В лаунчере это подписано у привязок тачпада.
|
||||
- **Клавиатура работает и при подключённом геймпаде.** Раньше она управляла игрой, только когда
|
||||
геймпада нет (а на Steam Deck он есть всегда); работали лишь Tab и Backspace. Теперь кнопки
|
||||
клавиатуры добавляются к кнопкам геймпада, а зажатая клавиша движения/камеры отклоняет стик до
|
||||
конца. Вживую с клавиатурой не проверено (только сборка и тесты).
|
||||
- **Меню настроек (Insert / L3+R3) помнит своё место** — и между открытиями, и между запусками
|
||||
(`menu_pos` в bbport.ini, доля экрана: переживает смену разрешения). Раньше оно каждый раз
|
||||
открывалось у левого края.
|
||||
- **Курсор мыши над меню.** Пока меню открыто, показывается системный курсор (в полноэкранном
|
||||
режиме и после 3 с без движения он скрыт), а меню сразу знает, где мышь, а не после первого
|
||||
движения.
|
||||
- **Настройки из меню больше не стирают назначения кнопок.** Меню записывало bbport.ini
|
||||
целиком, только со своими ключами: назначения клавиатуры и геймпада из лаунчера (`key.*`,
|
||||
`pad.*`) и другие его ключи пропадали после любого изменения в игре. Теперь меню меняет только
|
||||
свои строки, остальное оставляет, файл заменяется целиком (обрыв записи оставляет старый).
|
||||
Тест: `ninja -C out/gpu settings-test`.
|
||||
|
||||
## BB_READBACKS=2 больше не зависает при запуске
|
||||
|
||||
Точный режим обратного чтения защищает страницы, записанные GPU, и от чтения. Их читали и
|
||||
потоки копирования порта (выгрузка в VRAM: `bb:Copy*`, `bb:VkRecorder`): чтение падало в
|
||||
обработчик сбоев, тот ставил обратное чтение в очередь потоку GPU и ждал, а поток GPU в это время
|
||||
ждал поток записи отрисовок, который ждал эти копии. Теперь копии порта в точном режиме читают
|
||||
память игры через второе отображение тех же страниц, которое защита не трогает. Проверено: с
|
||||
`BB_READBACKS=2` игра доходит до титульного экрана и работает (раньше вставала на загрузке).
|
||||
В самой игре в этом режиме не проверено. `BB_PROBE=<обёртка>` в run.sh — запуск под gdb и т. п.
|
||||
и из исходников.
|
||||
|
||||
## FSR 4.1.1 из AppImage на NixOS (2026-10-07, вечер)
|
||||
|
||||
AppImage работает в своём пространстве монтирования с chroot, где её `/nix/store` заменяет
|
||||
системный: изнутри не видны ни `umu-run`, ни `nix-shell`, ни FHS-окружение Steam, и кнопка
|
||||
сборки на NixOS заканчивалась ошибкой 8. Теперь `capture_all.sh` на NixOS без `umu-run`:
|
||||
- если есть `nix-shell` (из исходников, на самой системе) — перезапускается в
|
||||
`nix-shell -p umu-launcher`;
|
||||
- в AppImage — копирует себя и `proton.sh` в рабочую папку (`~/.local/share/bbport/fsr4cap`,
|
||||
её видят обе стороны) и запускает запись на системе через `systemd-run --user --wait --pipe`
|
||||
в login-shell (PATH и NIX_PATH пользователя); ход и код выхода приходят обратно, отмена
|
||||
останавливает службу. Перевод в SPIR-V — уже внутри AppImage. `systemd-run` из
|
||||
systemd-minimal (он и так был в пакете) добавлен в PATH лаунчера.
|
||||
|
||||
Проверено на этом ПК (NixOS): кнопка без окна (`--build-fsr411`) из AppImage собирает FSR 4.1.1
|
||||
до конца, набор побайтно как эталонный, рабочая папка удалена; отмена посреди записи
|
||||
останавливает службу на системе.
|
||||
@@ -1,107 +0,0 @@
|
||||
# 0.5-pre2 — pre-release for testing
|
||||
|
||||
Second 0.5 prerelease, built from the `testing` branch. This release focuses on the experimental
|
||||
memory and GPU command translation path. Changes are listed relative to
|
||||
[0.5-pre1](https://github.com/deadinside28/bloodborne_pc/releases/tag/0.5-pre1).
|
||||
|
||||
## English
|
||||
|
||||
- **New memory module for NVIDIA and other non-AMD GPUs.** When the experimental mode is enabled,
|
||||
the layer binds chunks of game memory directly and keeps frequently used data in separate VRAM
|
||||
buffers. It does not use `vkQueueBindSparse`. The launcher now exposes the mode on all GPU
|
||||
vendors; the startup capability check is retained. AMD still selects the sparse arena by
|
||||
default within this mode; `BB_LAYER_MEMORY=1` selects the new module manually.
|
||||
- **Memory coherence fixes.** CPU writes to mirrored blocks are tracked selectively. Buffer
|
||||
bindings and command-processor copies use the current GPU data, CPU writes invalidate stale
|
||||
GPU ownership, and mixed ranges can use refreshed VRAM fringes. Promotion respects the VRAM
|
||||
budget, and cached binding decisions reduce repeated checks and locking.
|
||||
- **Original GPU copy shader enabled by default in the new model.** The game's copy shader is
|
||||
translated to SPIR-V and reads its copy list on the GPU. Its writes update both the current
|
||||
GPU view and game memory, keeping VRAM mirrors and CPU consumers consistent.
|
||||
`BB_COPY_SHADER_NATIVE=0` retains the HLE path for comparison.
|
||||
- **Portable GPU completion labels.** Supported EOP/EOS/RELEASE_MEM labels can be written by
|
||||
standard Vulkan commands on GPUs without `VK_AMD_buffer_marker`. Prior GPU results become
|
||||
visible before the completion label; the AMD fast path remains available. The portable path
|
||||
can be forced for testing with `BB_GPU_LABELS_PORTABLE=1`.
|
||||
- **Synchronization and bounds fixes.** Corrected update/inline-data stage dependencies and
|
||||
index-buffer read tracking. Paged buffer accesses reject invalid ranges before accessing the
|
||||
page table. Vulkan initialization also enables the required surface extension for headless
|
||||
validation.
|
||||
- **Shader-cache migration.** The shader binary format is now version 9. Binary, metadata and
|
||||
pipeline versions are checked together before loading the cache, preventing old and new
|
||||
shader variants from being mixed. AppImage startup chooses the GPU cache directory before
|
||||
loading the GPU library, following the selected user-data directory.
|
||||
- **Regression checks.** Added GPU tests for paged copies, mirror/guest writes, range guards and
|
||||
32/64-bit portable labels. The PM4 occlusion check now spans multiple submissions to avoid
|
||||
false failures when a single submission contains no draws.
|
||||
|
||||
**How to test:** enable *New memory and translation model* in the launcher, or set
|
||||
`BB_PC_MODEL=1`. The experimental mode remains **off by default**. For testing the new module
|
||||
on AMD, also set `BB_LAYER_MEMORY=1`.
|
||||
|
||||
**Validation and limits:** runtime tests, packaged Vulkan-driver discovery tests, the new GPU
|
||||
tests and all eight in-game PM4 checks passed. GPU and AppImage checks used an RX 7800 XT/RADV;
|
||||
the portable label path was forced on AMD. Physical NVIDIA/Intel hardware and NVIDIA host-memory
|
||||
import have not been tested. Full-game Vulkan validation still reports graphics-pipeline and
|
||||
image-layout errors. A consistent FPS improvement over the previous copy path has not been
|
||||
established, and long gameplay sessions still need testing.
|
||||
|
||||
The earlier 0.5 changes remain included: settings in the game's System menu, display/monitor
|
||||
selection, effect and patch controls, the updated overlay, launcher log export, Chinese
|
||||
translation, DLC add-on folders and atomic save-file writes. See the
|
||||
[0.5-pre1 notes](https://github.com/deadinside28/bloodborne_pc/releases/tag/0.5-pre1) for details.
|
||||
|
||||
The AppImage contains no game files. Proprietary FSR 4 model assets and NVIDIA's DLSS SDK/libraries
|
||||
are not bundled. `SHA256SUMS` accompanies the download.
|
||||
|
||||
## Русский
|
||||
|
||||
- **Новый модуль памяти для NVIDIA и остальных не-AMD GPU.** При включённом экспериментальном
|
||||
режиме прослойка привязывает куски памяти игры напрямую и держит часто используемые данные
|
||||
в отдельных буферах VRAM. `vkQueueBindSparse` не используется. Переключатель в лаунчере
|
||||
доступен для всех производителей GPU, стартовая проверка возможностей драйвера сохранена.
|
||||
На AMD внутри нового режима по умолчанию остаётся sparse-арена; новый модуль можно выбрать
|
||||
через `BB_LAYER_MEMORY=1`.
|
||||
- **Согласование памяти CPU и GPU.** Записи CPU в блоки с зеркалами отслеживаются выборочно.
|
||||
Привязки буферов и копии командного процессора используют актуальные данные GPU; новые записи
|
||||
CPU снимают устаревшие пометки GPU. Смешанные диапазоны могут использовать обновляемые края
|
||||
зеркал VRAM. Переносы соблюдают бюджет видеопамяти, кэш решений уменьшает повторные проверки
|
||||
и блокировки.
|
||||
- **Оригинальный GPU copy shader по умолчанию в новой модели.** Шейдер игры транслируется
|
||||
в SPIR-V и сам читает список копий на видеокарте. Результат записывается и в актуальное
|
||||
GPU-представление, и в память игры: зеркала VRAM и потребители на CPU видят согласованные
|
||||
данные. `BB_COPY_SHADER_NATIVE=0` оставляет HLE для сравнения.
|
||||
- **Переносимые метки завершения GPU.** Поддерживаемые метки EOP/EOS/RELEASE_MEM записываются
|
||||
стандартными командами Vulkan на GPU без `VK_AMD_buffer_marker`. Предшествующие результаты
|
||||
GPU становятся видимыми до метки завершения. Быстрый путь AMD сохранён;
|
||||
`BB_GPU_LABELS_PORTABLE=1` принудительно выбирает переносимый путь для проверки.
|
||||
- **Синхронизация и границы доступа.** Исправлены зависимости update/inline data и учёт чтения
|
||||
index buffer. Недопустимые диапазоны отклоняются до обращения к таблице страниц. Для
|
||||
headless-проверки Vulkan включается необходимое расширение surface.
|
||||
- **Миграция кэша шейдеров.** Формат shader binary поднят до версии 9. Версии binary, metadata
|
||||
и pipeline проверяются вместе до загрузки кэша, чтобы старые и новые варианты шейдеров
|
||||
не смешивались. AppImage выбирает GPU-каталог до загрузки GPU-библиотеки, с учётом выбранного
|
||||
каталога пользовательских данных.
|
||||
- **Проверки регрессий.** Добавлены GPU-тесты копирования через таблицу страниц, записей
|
||||
в зеркало и память игры, границ доступа и переносимых 32/64-битных меток. PM4-проверка
|
||||
occlusion теперь охватывает несколько отправок команд, чтобы отправка без отрисовок
|
||||
не давала ложный FAIL.
|
||||
|
||||
**Как проверить:** включить *Новую модель памяти и трансляции* в лаунчере или задать
|
||||
`BB_PC_MODEL=1`. Экспериментальный режим по умолчанию **выключен**. Для нового модуля на AMD
|
||||
также нужен `BB_LAYER_MEMORY=1`.
|
||||
|
||||
**Проверки и ограничения:** пройдены тесты рантайма, поиска Vulkan-драйвера в пакете, новые
|
||||
GPU-тесты и все восемь PM4-проверок в игре. GPU и AppImage проверялись на RX 7800 XT/RADV;
|
||||
переносимые метки принудительно включались на AMD. Физические NVIDIA/Intel и импорт host memory
|
||||
на NVIDIA пока не проверены. Полная Vulkan-валидация игры ещё сообщает об ошибках графических
|
||||
конвейеров и раскладок изображений. Стабильный прирост FPS относительно предыдущего пути
|
||||
копирования не подтверждён; длительное прохождение также требует проверки.
|
||||
|
||||
Все изменения первого prerelease 0.5 включены: настройки в меню System самой игры, выбор
|
||||
дисплея/монитора, управление эффектами и патчами, обновлённый оверлей, экспорт логов лаунчера,
|
||||
китайский перевод, папки DLC и атомарная запись сохранений. Подробности — в
|
||||
[описании 0.5-pre1](https://github.com/deadinside28/bloodborne_pc/releases/tag/0.5-pre1).
|
||||
|
||||
Игровых файлов в AppImage нет. Проприетарные модели FSR 4 и SDK/библиотеки DLSS не включены.
|
||||
К загрузке приложен `SHA256SUMS`.
|
||||
@@ -1,103 +0,0 @@
|
||||
# 0.5-pre3 — pre-release for testing
|
||||
|
||||
Third 0.5 prerelease, built from the `testing` branch. It addresses the first hardware logs of the
|
||||
new memory module on NVIDIA (GTX 1660 Ti laptop, 6 GB, driver 615.71). Changes are listed relative
|
||||
to [0.5-pre2](https://github.com/deadinside28/bloodborne_pc/releases/tag/0.5-pre2). Details and
|
||||
measurements: [NVIDIA_MEMORY_MODEL.ru.md](NVIDIA_MEMORY_MODEL.ru.md).
|
||||
|
||||
## English
|
||||
|
||||
- **VRAM copies were disabled on NVIDIA.** The fixed-arena workaround for NVIDIA's sparse binding
|
||||
was also applied to the memory module, which has no sparse arena: it made no VRAM copies of the
|
||||
game's memory at all (10–15 FPS on the GTX 1660 Ti). The workaround no longer applies to the
|
||||
module; `BB_FIXED_ARENA=0` is no longer needed.
|
||||
- **One VRAM budget for textures and copies of the game's memory.** The texture collector counts
|
||||
all of our VRAM. On cards below ~9 GB its critical mark (3.9 GB on the 6 GB card) was below what
|
||||
the copies and the textures need together, so it kept evicting textures in use (155–1255 every
|
||||
5 s, stutters) while the copies kept growing up to 90 % of the driver's budget. Copies now stop
|
||||
384 MiB short of that mark; close to it the module frees room itself, first the unused room of
|
||||
its copies, then copies not used for 3 s (`BB_VRAM_PRESSURE_IDLE_SECONDS`).
|
||||
- **Fewer reallocations of VRAM copies.** A copy that met newly loaded data was replaced by a
|
||||
bigger one each time: loading a level allocated 15–27 GB for 1.3 GB of data, with up to 7.4 GB
|
||||
of VRAM in use at once, and streaming areas did the same while playing. A growing copy now gets
|
||||
room to grow (up to 64 MiB, `BB_LAYER_MIRROR_GROWTH_MB`): 5–10 GB, and the worst loading frame of
|
||||
a test route went from 555 to 87 ms (RX 7800 XT). Replaced copies are freed as soon as the GPU is
|
||||
done with them.
|
||||
- **Command-processor writes (WRITE_DATA)** update the VRAM copy as well, instead of sending the
|
||||
block back to system memory (with watched blocks, below, up to 2 GB a minute went back and forth).
|
||||
- **Watched blocks** (`BB_LAYER_WATCH`, on by default). Memory the CPU writes without announcing
|
||||
it was always read by the GPU from system memory, over the bus on NVIDIA. It now gets a VRAM
|
||||
copy for reading bindings too; a write trap catches the next CPU write and the block is uploaded
|
||||
again before its next use. This also replaces most volatile-block refreshes (RX 7800 XT:
|
||||
1.3 GB/s of copies → 36 MB/s; the NVIDIA log showed 455 MB/s). Blocks the CPU writes nearly every
|
||||
frame still stay in system memory: uploading them before draws ended render passes (GPU frame
|
||||
4.8 → 7.2 ms on the RX 7800 XT); `BB_LAYER_WATCH_HOT=1` turns that on for experiments.
|
||||
- **File reads retry on EFAULT** (a page protected again between the runtime's touch and the
|
||||
kernel's copy).
|
||||
- **Testing aids:** `BB_VRAM_LIMIT_MB=N` takes the driver's VRAM budget as at most N MiB (5600
|
||||
gives the texture-collector marks of a 6 GB card); `BB_RESIDENCY_MIX=1` also says why blocks are
|
||||
not watched; the `Layer memory:` lines report watched blocks and write traps by kind.
|
||||
|
||||
**How to test:** enable *New memory and translation model* in the launcher, or set
|
||||
`BB_PC_MODEL=1`; the mode remains **off by default**. Logs with `BB_FRAME_STATS=1
|
||||
BB_GPU_PROFILE=1` at one place, once as is, once with `BB_LAYER_WATCH=0` and once with the mode off,
|
||||
show where the time goes.
|
||||
|
||||
**Validation and limits:** RX 7800 XT/RADV (PCIe 3.0) with the memory module forced on and
|
||||
`BB_VRAM_LIMIT_MB=5600`: model of 0.3 ~195 FPS, module ~195–213 FPS (GPU 4.6–5.2 ms a frame; runs
|
||||
vary by about ±10 %), VRAM kept under the collector's critical mark, a recorded route without
|
||||
frames over 25 ms, a 3-minute walk without errors, unchanged images; runtime, unit (101) and GPU
|
||||
tests passed. Not tested on physical NVIDIA or Intel hardware: on AMD reads of the game's memory
|
||||
in place cost little, so whether NVIDIA now reaches the speed of the 0.3 model is not established.
|
||||
|
||||
The AppImage contains no game files. Proprietary FSR 4 model assets and NVIDIA's DLSS SDK/libraries
|
||||
are not bundled. `SHA256SUMS` accompanies the download.
|
||||
|
||||
## Русский
|
||||
|
||||
- **На NVIDIA не было копий в VRAM.** Обход sparse binding на NVIDIA («фиксированная арена»)
|
||||
применялся и к модулю памяти, у которого sparse-арены нет: модуль вообще не копировал память
|
||||
игры в VRAM (10–15 FPS на GTX 1660 Ti). Теперь обход на модуль не действует,
|
||||
`BB_FIXED_ARENA=0` больше не нужен.
|
||||
- **Общий бюджет VRAM для текстур и копий памяти игры.** Сборщик кэша текстур считает всю нашу
|
||||
VRAM. На картах меньше ~9 ГБ его критическая отметка (3,9 ГБ на 6 ГБ) оказалась ниже того, что
|
||||
нужно копиям и текстурам вместе: он постоянно выселял используемые текстуры (155–1255 за 5 с,
|
||||
фризы), а копии росли до 90 % бюджета драйвера. Теперь копии встают за 384 МиБ до отметки, а
|
||||
рядом с ней модуль сам освобождает место: сначала неиспользуемый запас своих копий, затем
|
||||
копии, не нужные 3 с (`BB_VRAM_PRESSURE_IDLE_SECONDS`).
|
||||
- **Меньше перевыделений копий.** Копия, к которой примыкали новые данные, каждый раз заменялась
|
||||
большей: загрузка уровня выделяла 15–27 ГБ на 1,3 ГБ данных, до 7,4 ГБ VRAM одновременно; то же
|
||||
при подгрузке районов во время игры. Растущая копия теперь получает запас под рост (до 64 МиБ,
|
||||
`BB_LAYER_MIRROR_GROWTH_MB`): 5–10 ГБ, худший кадр загрузки в тестовом маршруте 555 → 87 мс
|
||||
(RX 7800 XT). Заменённые копии освобождаются сразу, как только видеочип с ними закончил.
|
||||
- **Записи командного процессора (WRITE_DATA)** обновляют и копию в VRAM, а не возвращают блок
|
||||
в системную память (с наблюдаемыми блоками, ниже, туда и обратно ходило до 2 ГБ в минуту).
|
||||
- **Наблюдаемые блоки** (`BB_LAYER_WATCH`, по умолчанию включено). Память, в которую процессор
|
||||
пишет без предупреждения, видеочип всегда читал из системной памяти, на NVIDIA через шину.
|
||||
Теперь для читающих привязок у неё тоже есть копия в VRAM: ловушка записи ловит следующую
|
||||
запись процессора, и блок перезаливается перед следующим использованием. Это же заменило
|
||||
большую часть обновлений летучих блоков (RX 7800 XT: 1,3 ГБ/с копий → 36 МБ/с; в логе с NVIDIA
|
||||
было 455 МБ/с). Блоки, которые процессор пишет почти каждый кадр, остаются в системной памяти:
|
||||
их заливка перед отрисовками завершала render pass (кадр видеочипа 4,8 → 7,2 мс на RX 7800 XT);
|
||||
`BB_LAYER_WATCH_HOT=1` включает это для экспериментов.
|
||||
- **Чтение файлов повторяется при EFAULT** (страница снова защищена между касанием рантайма и
|
||||
копированием ядром).
|
||||
- **Для проверок:** `BB_VRAM_LIMIT_MB=N` — бюджет VRAM драйвера считается не больше N МиБ (5600
|
||||
даёт пороги сборщика текстур карты на 6 ГБ); `BB_RESIDENCY_MIX=1` объясняет, почему блоки не
|
||||
наблюдаются; строки `Layer memory:` показывают наблюдаемые блоки и срабатывания ловушек по видам.
|
||||
|
||||
**Как проверить:** включить *Новую модель памяти и трансляции* в лаунчере или задать
|
||||
`BB_PC_MODEL=1`; режим по-прежнему **выключен по умолчанию**. Логи с `BB_FRAME_STATS=1
|
||||
BB_GPU_PROFILE=1` в одном месте — как есть, с `BB_LAYER_WATCH=0` и с выключенным режимом —
|
||||
покажут, куда уходит время.
|
||||
|
||||
**Проверки и ограничения:** RX 7800 XT/RADV (PCIe 3.0), модуль памяти включён вручную,
|
||||
`BB_VRAM_LIMIT_MB=5600`: модель 0.3 ~195 FPS, модуль ~195–213 FPS (видеочип 4,6–5,2 мс за кадр;
|
||||
разброс между запусками около ±10 %), VRAM держится ниже критической отметки сборщика,
|
||||
записанный маршрут без кадров дольше 25 мс, 3 минуты прогулки без ошибок, картинка не
|
||||
изменилась; тесты рантайма, юнит-тесты (101) и GPU-тесты пройдены. На физических NVIDIA и Intel
|
||||
не проверялось: на AMD чтения памяти игры на месте почти бесплатны, поэтому достигает ли NVIDIA
|
||||
теперь скорости модели 0.3, не установлено.
|
||||
|
||||
Игровых файлов в AppImage нет. Проприетарные модели FSR 4 и SDK/библиотеки DLSS не включены.
|
||||
К загрузке приложен `SHA256SUMS`.
|
||||
@@ -1,147 +0,0 @@
|
||||
# 0.5-pre4 — pre-release for testing
|
||||
|
||||
Fourth 0.5 prerelease, built from the `testing` branch, after the first tests of 0.5-pre3 on
|
||||
NVIDIA (GTX 1060 6 GB, GTX 1660 Ti 6 GB and a 4 GB card). Changes are listed relative to
|
||||
[0.5-pre3](https://github.com/deadinside28/bloodborne_pc/releases/tag/0.5-pre3). Details and
|
||||
measurements: [NVIDIA_MEMORY_MODEL.ru.md](https://github.com/deadinside28/bloodborne_pc/blob/testing/docs/NVIDIA_MEMORY_MODEL.ru.md).
|
||||
|
||||
## English
|
||||
|
||||
- **The new memory model on NVIDIA no longer stutters while areas stream in.** On NVIDIA (and
|
||||
any GPU other than AMD) the new model keeps VRAM copies of the game's memory itself. After
|
||||
each run of blocks it copied to VRAM, it rewrote the page table entries of the whole copy (up
|
||||
to 160 000 entries in one frame), looking up every block in the runtime's memory map under a
|
||||
lock the game's threads hold while they map memory: the GPU thread waited 30–70 ms in frames
|
||||
where an area streamed in. Only the entries that changed are written now, with one lookup per
|
||||
mapping. On a recorded route (RX 7800 XT forced into this path, five runs) the 99th percentile
|
||||
frame went from 11–13 ms to 5.1–7.5 ms (9.4 ms in a run where the GPU itself ran slower), the
|
||||
worst frame from 32–57 ms to 9–16 ms, close to AMD's default path (5.2–6.0 ms).
|
||||
- **No more copying the same memory to VRAM and back every frame.** Where the GPU writes
|
||||
memory the CPU also wrote, the write goes to the game's memory; a reading binding then copied
|
||||
the range to VRAM again, the next writing binding sent it back, every frame (seen at one
|
||||
spot with 42 FPS: about 200 MB/s each way). A range sent
|
||||
back for the GPU's writes a second time now stays where it is. Write traps on VRAM copies are
|
||||
set and cleared with one call per run of blocks instead of one per 64 KiB.
|
||||
- **Fewer shader-compilation hitches.** A new graphics pipeline of a pass that draws into a
|
||||
render target used in each of the last 8 frames is compiled on worker threads while its draws
|
||||
go without it for a frame or two. Translating a new shader and compute pipelines still happen at once, and so
|
||||
do passes rendered once and loading screens. `BB_ASYNC_PIPELINES=0` restores the old
|
||||
behaviour, `BB_ASYNC_PIPELINE_THREADS=N` sets the worker count. The drivers' own shader caches
|
||||
are kept between sessions (NVIDIA: no cleanup, 10 GB; Mesa: 4 GB). As before, the first start
|
||||
of a new build compiles its pipeline cache again.
|
||||
- **4 GB cards: the new memory model no longer collapses to ~5 FPS.** 0.5-pre3 stopped copying
|
||||
the game's memory to VRAM short of the texture collector's critical mark; on a 4 GB card the
|
||||
textures alone held the usage there, so no copy was made again and everything was read over
|
||||
the bus after a few minutes. VRAM copies now have their own share (25 % of the driver's
|
||||
budget, `BB_LAYER_MIRROR_FLOOR_PERCENT`) that the texture collector's mark does not take.
|
||||
Cards with 4 GB are still faster with the 0.3 memory model.
|
||||
- **No VRAM left behind after travelling.** Copies of an area left behind stayed in VRAM (large
|
||||
bindings over all memory marked every copy as in use), and the copies of the new area waited,
|
||||
read over the bus (about 10 FPS less after a lamp travel on a GTX 1060). When VRAM is short,
|
||||
copies not bound directly for 3 s now go back first, and copies with memory left without
|
||||
valid data are made again over their valid data only, giving that memory back.
|
||||
- **Damaged game extractions are reported instead of hanging (#81).** Some extraction tools
|
||||
(LibOrbisPkg PkgTool on .NET 6 or newer) leave parts of compressed sectors stale without an
|
||||
error; the game then hung while loading shaders (`shaderBinarySize … is not equal to Program
|
||||
size`). The launcher and the game's preparation now unpack the shader files (under a second)
|
||||
and say that the extraction is damaged. `scripts/game_check.py GAME_DIR` checks every file.
|
||||
- **NVIDIA driver diagnostics (#107).** `--vulkan-info` now lists the NVIDIA libraries linked
|
||||
for the package, the kernel module, the `/dev/nvidia*` nodes, hybrid-graphics variables and
|
||||
the NVIDIA libraries the dynamic linker searched for and did not load: one log shows why the
|
||||
host driver gave no `vkCreateInstance` inside the AppImage.
|
||||
- **A crash window in command recording closed.** Handing recorded commands to the recording
|
||||
threads left the current command chunk empty for a moment; anything recording on that thread
|
||||
then (a fault handler) read a null chunk. The same crash was reported for a Windows build
|
||||
(#100). The replacement chunk is now swapped in before the old one leaves.
|
||||
- **The in-game settings menu (Insert, L3+R3) looks as in 0.3 and 0.4 again:** one movable window
|
||||
with sections, check boxes, sliders and `(?)` hints, keyboard and gamepad navigation. Its
|
||||
content is that of 0.5 (DLSS, debug views, current hints).
|
||||
|
||||
**How to test:** enable *New memory and translation model* in the launcher, or set
|
||||
`BB_PC_MODEL=1`; the mode remains **off by default**. On AMD it uses the sparse arena;
|
||||
`BB_LAYER_MEMORY=1` forces NVIDIA's path there. Logs with `BB_FRAME_STATS=1` show compiles per 5 s,
|
||||
a line `GPU: N pipelines compiled in the background, M draws went without theirs`, and for
|
||||
frames over 40 ms where the new model's VRAM copies spent their time (`layer memory ms`).
|
||||
|
||||
**Validation and limits:** RX 7800 XT/RADV, a recorded 25 s route: AMD's default path p99
|
||||
5.2–6.0 ms; NVIDIA's path forced, before/after as above; no frames over 20 ms in either. A 4-minute
|
||||
session on NVIDIA's path with the VRAM budget limited to 4.5 GB (`BB_VRAM_LIMIT_MB=4608`): 177 FPS
|
||||
on average, p99 9.7 ms, VRAM at most 3.0 GB; 11 of the 12 frames over 40 ms were compiles or the
|
||||
loading screen. Runtime, unit (106) and GPU tests passed. NVIDIA's own path (the game's memory imported as
|
||||
host memory) cannot run on AMD: the gains there are not measured yet, logs from NVIDIA testers
|
||||
are welcome. Physical Intel GPUs are not tested.
|
||||
|
||||
The AppImage contains no game files. Proprietary FSR 4 model assets and NVIDIA's DLSS SDK/libraries
|
||||
are not bundled. `SHA256SUMS` accompanies the download.
|
||||
|
||||
## Русский
|
||||
|
||||
- **Новая модель памяти на NVIDIA больше не дёргается при подгрузке локаций.** На NVIDIA (и
|
||||
любой видеокарте, кроме AMD) новая модель сама держит копии памяти игры в VRAM. После каждого
|
||||
куска, скопированного в VRAM, она переписывала записи таблицы страниц по всей копии (до 160 000
|
||||
записей за кадр) и для каждого блока искала его в карте памяти рантайма под блокировкой,
|
||||
которую держат потоки игры, когда отображают память: поток видеокарты ждал по 30–70 мс в
|
||||
кадрах, где подгружалась локация. Теперь пишутся только изменившиеся записи, с одним поиском
|
||||
на отображение. На записанном маршруте (RX 7800 XT, принудительно на этом пути, пять прогонов)
|
||||
99-й процентиль кадра — 5,1–7,5 мс вместо 11–13 (9,4 мс в прогоне, где медленнее работала сама
|
||||
видеокарта), худший кадр — 9–16 мс вместо 32–57, близко к обычному пути AMD (5,2–6,0 мс).
|
||||
- **Память больше не гоняется в VRAM и обратно каждый кадр.** Где видеокарта пишет в память,
|
||||
в которую писал и процессор, запись идёт в память игры; привязка на чтение снова копировала
|
||||
этот диапазон в VRAM, следующая привязка на запись отправляла обратно — и так каждый кадр
|
||||
(замечено в одном месте при 42 FPS: около 200 МБ/с в каждую сторону). Диапазон, который вернулся из-за записи видеокарты второй раз, теперь
|
||||
остаётся на месте. Ловушки записи на копиях в VRAM ставятся и снимаются одним вызовом на
|
||||
участок, а не на каждые 64 КБ.
|
||||
- **Меньше рывков от компиляции шейдеров.** Новый графический конвейер прохода, который рисует
|
||||
в цель рендера, использованную в каждом из последних 8 кадров, собирается в фоновых потоках, а
|
||||
его отрисовки кадр-другой идут без него. Перевод нового шейдера и compute-конвейеры
|
||||
по-прежнему компилируются сразу, как и разовые проходы и экраны загрузки.
|
||||
`BB_ASYNC_PIPELINES=0` — как раньше, `BB_ASYNC_PIPELINE_THREADS=N` — число потоков. Дисковые
|
||||
кэши шейдеров драйверов сохраняются между сессиями (NVIDIA — без очистки, 10 ГБ; Mesa — 4 ГБ).
|
||||
Как и раньше, при первом запуске новой сборки кэш конвейеров собирается заново.
|
||||
- **Карты на 4 ГБ: новая модель памяти больше не падает до ~5 FPS.** В 0.5-pre3 копии памяти
|
||||
игры в VRAM останавливались, не доходя до критической отметки сборщика текстур; на карте 4 ГБ
|
||||
одни текстуры держали память там, копии больше не создавались, и через несколько минут всё
|
||||
читалось через шину. Теперь у копий есть своя доля VRAM (25 % бюджета драйвера,
|
||||
`BB_LAYER_MIRROR_FLOOR_PERCENT`), которую отметка сборщика текстур не забирает. На картах
|
||||
4 ГБ модель памяти 0.3 по-прежнему быстрее.
|
||||
- **После перемещений VRAM не остаётся занятой.** Копии покинутой локации оставались в VRAM
|
||||
(большие привязки на всю память помечали каждую копию как используемую), а копии новой ждали
|
||||
и читались через шину (около 10 FPS меньше после телепорта к лампе на GTX 1060). Теперь при
|
||||
нехватке VRAM сначала уходят копии, к которым напрямую не привязывались 3 с, а копии, в
|
||||
которых осталась память без действительных данных, пересоздаются только поверх
|
||||
действительных данных и отдают эту память.
|
||||
- **Повреждённая распаковка игры теперь называется прямо, а не приводит к зависанию (#81).**
|
||||
Некоторые распаковщики (LibOrbisPkg PkgTool на .NET 6 и новее) без ошибки оставляют части
|
||||
сжатых секторов устаревшими; игра зависала на загрузке шейдеров (`shaderBinarySize … is not
|
||||
equal to Program size`). Лаунчер и подготовка игры теперь распаковывают файлы шейдеров (меньше
|
||||
секунды) и сообщают, что распаковка повреждена. `scripts/game_check.py ПАПКА_ИГРЫ` проверяет
|
||||
все файлы.
|
||||
- **Диагностика драйвера NVIDIA (#107).** `--vulkan-info` теперь показывает библиотеки NVIDIA,
|
||||
подключённые для пакета, модуль ядра, узлы `/dev/nvidia*`, переменные гибридной графики и
|
||||
библиотеки NVIDIA, которые динамический компоновщик искал и не загрузил: по одному логу видно,
|
||||
почему драйвер хоста не отдал `vkCreateInstance` внутри AppImage.
|
||||
- **Закрыто окно для вылета при записи команд.** При передаче записанных команд потокам записи
|
||||
текущий кусок команд на мгновение оставался пустым; если в этот момент на том же потоке
|
||||
что-то записывало (обработчик ошибки доступа), оно читало пустой указатель. Такой же вылет был
|
||||
у сборки для Windows (#100). Теперь новый кусок подставляется до того, как уходит старый.
|
||||
- **Меню настроек в игре (Insert, L3+R3) снова выглядит как в 0.3 и 0.4:** одно перемещаемое
|
||||
окно с разделами, флажками, ползунками и подсказками `(?)`, управление клавиатурой и
|
||||
геймпадом. Содержимое — как в 0.5 (DLSS, отладочные виды, актуальные подсказки).
|
||||
|
||||
**Как проверить:** включить *Новую модель памяти и трансляции* в лаунчере или задать
|
||||
`BB_PC_MODEL=1`; режим по-прежнему **выключен по умолчанию**. На AMD он работает через
|
||||
sparse-арену; `BB_LAYER_MEMORY=1` включает на AMD путь NVIDIA. В логе с `BB_FRAME_STATS=1` —
|
||||
число компиляций за 5 с, строка `GPU: N pipelines compiled in the background, M draws went
|
||||
without theirs` и для кадров дольше 40 мс — на что ушло время копий новой модели в VRAM
|
||||
(`layer memory ms`).
|
||||
|
||||
**Проверки и ограничения:** RX 7800 XT/RADV, записанный маршрут на 25 с: обычный путь AMD —
|
||||
p99 5,2–6,0 мс; путь NVIDIA, принудительно, — до и после, как выше; ни одного кадра дольше
|
||||
20 мс. Сессия на 4 минуты на пути NVIDIA с бюджетом VRAM 4,5 ГБ (`BB_VRAM_LIMIT_MB=4608`):
|
||||
в среднем 177 FPS, p99 9,7 мс, VRAM не больше 3,0 ГБ; 11 из 12 кадров дольше 40 мс — компиляция
|
||||
или экран загрузки. Тесты рантайма, юнит-тесты (106) и GPU-тесты пройдены. Собственный путь NVIDIA (память
|
||||
игры импортируется как память хоста) на AMD не запустить: выигрыш там ещё не измерен, логи от
|
||||
тестеров с NVIDIA приветствуются. Физические Intel не проверялись.
|
||||
|
||||
Игровых файлов в AppImage нет. Проприетарные модели FSR 4 и SDK/библиотеки DLSS не включены.
|
||||
К загрузке приложен `SHA256SUMS`.
|
||||
@@ -1,172 +0,0 @@
|
||||
**English** · [Русский](#русский)
|
||||
|
||||
bbport is the counterpart of Wine + DXVK for a single game: Bloodborne (CUSA03173, v1.09) on Linux.
|
||||
The game's x86-64 code runs directly on the CPU, a runtime written for this game replaces the PS4
|
||||
system libraries, and the graphics are translated to Vulkan.
|
||||
|
||||
Questions about this project: Discord https://discord.gg/KYZRKk9CB — not the shadPS4 server.
|
||||
|
||||
### What's new since 0.4
|
||||
|
||||
Details and measurements of each step are in the prerelease notes:
|
||||
[pre1](https://github.com/deadinside28/bloodborne_pc/releases/tag/0.5-pre1),
|
||||
[pre2](https://github.com/deadinside28/bloodborne_pc/releases/tag/0.5-pre2),
|
||||
[pre3](https://github.com/deadinside28/bloodborne_pc/releases/tag/0.5-pre3),
|
||||
[pre4](https://github.com/deadinside28/bloodborne_pc/releases/tag/0.5-pre4).
|
||||
|
||||
**The new memory and translation model — the default on AMD**
|
||||
- Launcher: *Mode → Memory and translation model*. *Auto* (the default) gives AMD GPUs the new
|
||||
model and NVIDIA, Intel and others the memory model of 0.3 with all its fixes; *New* and *Old*
|
||||
choose by hand. Without the launcher: `BB_PC_MODEL=1/0` (unset: as *Auto*).
|
||||
- The GPU uses the game's memory where it is, as a PC game's buffers; what it reads often is kept
|
||||
in VRAM — on AMD in a sparse buffer (the arena), on other GPUs by the layer's memory module
|
||||
without sparse rebinding (`BB_LAYER_MEMORY=1/0` by hand). The PS4 command processor's work is
|
||||
translated rather than emulated: memory writes and fences are done by the GPU in stream order,
|
||||
`COPY_DATA`, `COND_EXEC`, predication, `MEM_SEMAPHORE` and the GPU clock (100 MHz, as on the
|
||||
PS4) follow the GPU's timeline.
|
||||
- The game's occlusion queries as Vulkan queries are available with `BB_OCCLUSION_QUERIES=1`, off
|
||||
by default: with an upscaler the scene is drawn smaller and jittered, and a lamp's glow then
|
||||
flashed grey over the whole frame now and then. Off, everything counts as visible, as in the
|
||||
0.3 model.
|
||||
- The layer's memory module (NVIDIA and others): some launches no longer run the GPU 1.3–1.7 times
|
||||
slower (copies that broke render passes up to ~100 times a frame).
|
||||
- On NVIDIA the new model can be chosen by hand; testers' results differ by scene (some scenes are
|
||||
slower than with the old model), so it is not the default there yet. Logs are welcome.
|
||||
|
||||
**Settings inside the game's own menu**
|
||||
- *System* has *Display* and *Effects*, real pages of the game's menu, on the title screen and
|
||||
in the pause menu: output resolution, quality preset and sharpness as sliders, FPS counter,
|
||||
model detail and upscaler; chromatic aberration, depth of field, motion blur, SSAO, dynamic
|
||||
shadows and SSR. The game's patches and its own AA are in the overlay menu and the launcher.
|
||||
- The overlay menu (Insert / L3+R3) looks as in 0.3 again.
|
||||
- No "Play online / offline" screen: there is no PSN, the game opens its main menu offline.
|
||||
|
||||
**Smoother play**
|
||||
- New graphics pipelines of passes drawn every frame compile in the background: fewer hitches.
|
||||
Only geometry goes without its pipeline meanwhile (it shows a frame later); full-screen passes
|
||||
(lighting, fog, post-processing) wait for theirs — skipping one left whole frames grey. The
|
||||
drivers' shader caches are kept between sessions. In the game's menu the output resolution and
|
||||
preset sliders apply once they rest for 0.4 s, not at every step.
|
||||
- Cards with little VRAM: FPS no longer falls the longer you play (the texture collector evicted
|
||||
textures used a moment ago and read them again over the bus: 32 915 in ~7 minutes in an NVIDIA
|
||||
log); the game no longer stops when VRAM for its memory copies runs out; VRAM is given back
|
||||
after travelling.
|
||||
|
||||
**New**
|
||||
- DLC: add-on folders under `user/addcont/` are reported as installed; saves with The Old Hunters
|
||||
load (#33, PR #84).
|
||||
- DLSS on Linux for NVIDIA RTX when built with NVIDIA's DLSS SDK (PR #83; not in the AppImage).
|
||||
- Launcher: monitor choice (#69), *Copy* and *Export log…* (PR #78), Simplified Chinese (PR #75).
|
||||
- Damaged game extractions are reported instead of hanging while loading shaders (#81);
|
||||
`scripts/game_check.py GAME_DIR` checks every file.
|
||||
- NVIDIA in the AppImage: the host's EGL vendor files are passed along (without them NVIDIA's
|
||||
driver gave no `vkCreateInstance` on some hosts, #107; `BB_NVIDIA_EGL=0`: off); `--vulkan-info`
|
||||
prints the driver report together with the summary.
|
||||
- AMD Polaris, Vega and their APUs under RADV: `RADV_DEBUG=nohiz` is set automatically (black
|
||||
halos and vanishing geometry, #54, #99; your own `RADV_DEBUG` or `BB_AUTO_NOHIZ=0` keeps it off).
|
||||
- The FSR 4.1.1 build also finds Proton installed system-wide (PR #112). The bundled Dear ImGui
|
||||
is private to the GPU library: Vulkan layers with their own copy no longer crash the game
|
||||
(PR #113).
|
||||
|
||||
**Fixes**
|
||||
- Saves are no longer damaged by a crash during saving (#64).
|
||||
- FSR 4.1.1 build: Proton runs without Steam's container where it cannot start (#61).
|
||||
- The item picture on the loading screen at outputs other than 1080p (#67).
|
||||
- GPU hangs from garbage indirect dispatch sizes (#34); a crash window while recording commands
|
||||
(the same crash was reported for a Windows build, #100).
|
||||
- Small mips of tiled textures are detiled the way the GPU stores them (from shadPS4 #5196).
|
||||
- Chinese/Japanese/Korean Windows locales in the game preparation (#55); an `eboot.bin` dumped as a
|
||||
plain ELF (#49); stick calibration for Switch Pro clones (#70, PR #71); `BB_FSR4_STATS=1`
|
||||
(PR #80).
|
||||
|
||||
**Known limits:** one machine was tested thoroughly (RX 7800 XT, Mesa/RADV). Banding in dark
|
||||
gradients is not confirmed fixed. The Windows build is a separate port.
|
||||
|
||||
The AppImage contains no game files. Proprietary FSR 4 model assets and NVIDIA's DLSS SDK/libraries
|
||||
are not bundled. `SHA256SUMS` accompanies the download.
|
||||
|
||||
## Русский
|
||||
|
||||
bbport — аналог Wine + DXVK для одной игры: Bloodborne (CUSA03173, v1.09) на Linux. Код игры x86-64
|
||||
выполняется прямо на процессоре, системные библиотеки PS4 заменяет рантайм, написанный под эту игру,
|
||||
графика переводится в Vulkan.
|
||||
|
||||
Вопросы по проекту: Discord https://discord.gg/KYZRKk9CB — не сервер shadPS4.
|
||||
|
||||
### Что нового после 0.4
|
||||
|
||||
Подробности и замеры каждого шага — в описаниях пререлизов:
|
||||
[pre1](https://github.com/deadinside28/bloodborne_pc/releases/tag/0.5-pre1),
|
||||
[pre2](https://github.com/deadinside28/bloodborne_pc/releases/tag/0.5-pre2),
|
||||
[pre3](https://github.com/deadinside28/bloodborne_pc/releases/tag/0.5-pre3),
|
||||
[pre4](https://github.com/deadinside28/bloodborne_pc/releases/tag/0.5-pre4).
|
||||
|
||||
**Новая модель памяти и трансляции — по умолчанию на AMD**
|
||||
- Лаунчер: *Режим работы → Модель памяти и трансляции*. *Авто* (по умолчанию) даёт видеокартам
|
||||
AMD новую модель, а NVIDIA, Intel и остальным — модель памяти 0.3 со всеми исправлениями;
|
||||
*Новая* и *Старая* — вручную. Без лаунчера — `BB_PC_MODEL=1/0` (не задано — как *Авто*).
|
||||
- Видеокарта работает с памятью игры на месте, как с буферами игры для ПК; то, что она читает
|
||||
часто, держится в VRAM — на AMD в sparse-буфере (арене), на остальных видеокартах модулем
|
||||
памяти прослойки без sparse-перепривязок (`BB_LAYER_MEMORY=1/0` — вручную). Работа командного
|
||||
процессора PS4 переводится, а не эмулируется: записи в память и метки делает видеочип в порядке
|
||||
потока, `COPY_DATA`, `COND_EXEC`, предикация, `MEM_SEMAPHORE` и часы видеочипа (100 МГц, как у
|
||||
PS4) — во времени видеочипа.
|
||||
- Occlusion-запросы игры как запросы Vulkan — по `BB_OCCLUSION_QUERIES=1`, по умолчанию выключены:
|
||||
с апскейлером сцена рисуется меньше и со сдвигом, и свечение лампы время от времени вспыхивало
|
||||
серым на весь кадр. Без них всё считается видимым, как в модели 0.3.
|
||||
- Модуль памяти прослойки (NVIDIA и другие): некоторые запуски больше не гоняют видеокарту в
|
||||
1,3–1,7 раза медленнее (копирования, разрывавшие проходы рендера до ~100 раз за кадр).
|
||||
- На NVIDIA новую модель можно выбрать вручную; у тестеров результат зависит от сцены (в части
|
||||
сцен медленнее старой модели), поэтому там она пока не по умолчанию. Логи приветствуются.
|
||||
|
||||
**Настройки — в меню самой игры**
|
||||
- В *Системе* — *Изображение* и *Эффекты*, настоящие страницы меню игры, и на титульном экране,
|
||||
и в паузе: разрешение вывода, пресет качества и резкость ползунками, счётчик FPS, детализация
|
||||
моделей и апскейлер; хроматическая аберрация, глубина резкости, размытие в движении, SSAO,
|
||||
динамические тени и SSR. Патчи игры и её собственное сглаживание — в меню оверлея и лаунчере.
|
||||
- Меню оверлея (Insert / L3+R3) снова выглядит как в 0.3.
|
||||
- Без экрана «Играть онлайн / офлайн»: PSN нет, игра сразу открывает главное меню офлайн.
|
||||
|
||||
**Ровнее**
|
||||
- Новые графические конвейеры проходов, которые рисуются каждый кадр, собираются в фоне: меньше
|
||||
рывков. Без своего конвейера в это время остаётся только геометрия (она появляется кадром
|
||||
позже); полноэкранные проходы (освещение, туман, постобработка) ждут свой — пропуск такого
|
||||
прохода давал целые серые кадры. Дисковые кэши шейдеров драйверов сохраняются между сессиями. В
|
||||
меню игры ползунки разрешения вывода и пресета применяются, когда простоят 0,4 с, а не на
|
||||
каждом шаге.
|
||||
- Карты с малым объёмом VRAM: FPS больше не падает со временем (сборщик текстур выгружал только что
|
||||
использованные текстуры и читал их снова через шину: 32 915 за ~7 минут в логе с NVIDIA); игра
|
||||
не останавливается, когда кончается VRAM для копий её памяти; после перемещений VRAM
|
||||
освобождается.
|
||||
|
||||
**Новое**
|
||||
- DLC: папки дополнений в `user/addcont/` сообщаются игре как установленные, сохранения с The Old
|
||||
Hunters загружаются (#33, PR #84).
|
||||
- DLSS на Linux для NVIDIA RTX при сборке с SDK DLSS от NVIDIA (PR #83; в AppImage не входит).
|
||||
- Лаунчер: выбор монитора (#69), *Копировать* и *Экспорт лога…* (PR #78), упрощённый китайский
|
||||
(PR #75).
|
||||
- Повреждённая распаковка игры называется прямо, а не приводит к зависанию на загрузке шейдеров
|
||||
(#81); `scripts/game_check.py ПАПКА_ИГРЫ` проверяет все файлы.
|
||||
- NVIDIA в AppImage: передаются файлы EGL-вендоров хоста (без них драйвер NVIDIA на некоторых
|
||||
системах не отдавал `vkCreateInstance`, #107; `BB_NVIDIA_EGL=0` — выключить); `--vulkan-info`
|
||||
печатает отчёт о драйвере вместе со сводкой.
|
||||
- AMD Polaris, Vega и их APU под RADV: `RADV_DEBUG=nohiz` ставится сам (чёрные ореолы и пропадающая
|
||||
геометрия, #54, #99; свой `RADV_DEBUG` или `BB_AUTO_NOHIZ=0` — не ставить).
|
||||
- Сборка FSR 4.1.1 находит и Proton, установленный в систему (PR #112). Встроенный Dear ImGui
|
||||
скрыт внутри библиотеки GPU: слои Vulkan со своей копией больше не роняют игру (PR #113).
|
||||
|
||||
**Исправления**
|
||||
- Сохранения не портятся при вылете во время сохранения (#64).
|
||||
- Сборка FSR 4.1.1: Proton запускается без контейнера Steam там, где тот не стартует (#61).
|
||||
- Картинка предмета на экране загрузки при выводе не в 1080p (#67).
|
||||
- Зависания видеочипа от мусорных размеров непрямых запусков (#34); окно для вылета при записи
|
||||
команд (такой же вылет был у сборки для Windows, #100).
|
||||
- Мелкие мипы тайловых текстур раскладываются так, как их хранит видеокарта (из shadPS4 #5196).
|
||||
- Китайская/японская/корейская локали Windows при подготовке игры (#55); `eboot.bin`, снятый как
|
||||
обычный ELF (#49); калибровка стиков у клонов Switch Pro (#70, PR #71); `BB_FSR4_STATS=1` (PR #80).
|
||||
|
||||
**Ограничения:** подробно проверена одна машина (RX 7800 XT, Mesa/RADV). Полосы в тёмных
|
||||
градиентах — исправление не подтверждено. Сборка для Windows — отдельный порт.
|
||||
|
||||
Игровых файлов в AppImage нет. Проприетарные модели FSR 4 и SDK/библиотеки DLSS не включены.
|
||||
К загрузке приложен `SHA256SUMS`.
|
||||
@@ -1,109 +0,0 @@
|
||||
**English** · [Русский](#русский)
|
||||
|
||||
bbport is the counterpart of Wine + DXVK for a single game: Bloodborne (v1.09) on Linux.
|
||||
Changes since [0.5](https://github.com/deadinside28/bloodborne_pc/releases/tag/0.5); details and
|
||||
measurements: [CHANGES_after_0.5.md](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/CHANGES_after_0.5.md) (Russian).
|
||||
|
||||
Questions about this project: Discord https://discord.gg/KYZRKk9CB — not the shadPS4 server.
|
||||
|
||||
### New
|
||||
|
||||
- **Online play (shadNet)**, as a separate module (`libbbnet.so`, loaded only when online play is
|
||||
on): messages, bloodstains and ghosts from The Hunter's Dream, bells and summons through a
|
||||
shadNet server (`srv.shadps4.net:31313` by default) with a shadNet account. Launcher → *Online*:
|
||||
servers, account, UPnP, connection test. The router port is opened by UPnP (#121). The same
|
||||
servers as the Windows port. The network and PSN libraries come from shadp2p (a shadPS4 fork,
|
||||
GPL-2.0) by way of the Linux and Windows forks of bbport.
|
||||
- **Mouse look**: click into the game to take the mouse, F1 releases it; mouse buttons and the
|
||||
wheel can be bound like keys (Mouse Left = R1, Mouse Right = L2, Middle = R3, side buttons
|
||||
R2/L1, wheel = quick items). Launcher → *Controls*: mouse look, sensitivity, invert Y (#51, #52).
|
||||
- **DLSS in the AppImage** (NVIDIA GeForce RTX): the bridge and NVIDIA's DLSS 310.9.1 library are
|
||||
included (NVIDIA's RTX SDK license allows it within an application; its text is in the package).
|
||||
A newer `libnvidia-ngx-dlss.so.*` can be chosen in the launcher (*Upscaler → DLSS: NVIDIA's
|
||||
library*), like the FSR 4.1.1 DLL.
|
||||
- **Every retail release** of Bloodborne with update 1.09 is accepted (US CUSA00900, EU, UK, JP,
|
||||
Asia and The Old Hunters editions; PR #117); an `eboot.bin` with a 60 fps patch baked in is
|
||||
reported.
|
||||
- **Quit the game** from the overlay (Insert / L3+R3; press twice) — #74, #116.
|
||||
|
||||
### Fixes
|
||||
|
||||
- The US release (and other regions) crashed with code 139 on entering the world in the new
|
||||
memory model: they ran without the game's profile (#119).
|
||||
- Motion blur smeared the whole frame while the camera turned in the new memory model (#122).
|
||||
- Cards with 4 GB (NVIDIA): "Failed to allocate 64 MiB for guest memory copies" stopped the game;
|
||||
now it frees textures sooner and uses other memory instead.
|
||||
- The main menu flickered with output resolutions above 1080p (live resolution).
|
||||
- Online, "Could not get information from the Bloodborne server" in the AppImage on NixOS (HTTPS
|
||||
certificates).
|
||||
- NVIDIA on Wayland: the swapchain is recreated through `oldSwapchain` (PR #93).
|
||||
- Building from git after updating from 0.4: "patch failed: src/ffx_vk_fsr4_v07.c" (#124).
|
||||
- `bb-launcher.sh --play` through nix-shell (PR #120); FSR 4.1.1 recording from the AppImage on
|
||||
SteamOS (PR #89).
|
||||
|
||||
### Smaller download
|
||||
|
||||
The AppImage is **399 MB instead of 925 MB**: only Mesa's Vulkan drivers (RADV without LLVM and
|
||||
ANV) instead of all of Mesa, no debug info in the game binary, MangoHud without its 32-bit half,
|
||||
the launcher's GTK without GStreamer.
|
||||
|
||||
**Known limits:** online login and summons were not tested by the author (no shadNet account);
|
||||
the UDP side of the public shadNet server did not answer in #121. Mouse look and DLSS need
|
||||
testing on more systems.
|
||||
|
||||
The AppImage contains no game files. AMD's FSR 4 model assets are not bundled. `SHA256SUMS`
|
||||
accompanies the download.
|
||||
|
||||
## Русский
|
||||
|
||||
bbport — аналог Wine + DXVK для одной игры: Bloodborne (v1.09) на Linux. Изменения после
|
||||
[0.5](https://github.com/deadinside28/bloodborne_pc/releases/tag/0.5); подробности и замеры —
|
||||
[CHANGES_after_0.5.md](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/CHANGES_after_0.5.md).
|
||||
|
||||
Вопросы по проекту: Discord https://discord.gg/KYZRKk9CB — не сервер shadPS4.
|
||||
|
||||
### Новое
|
||||
|
||||
- **Игра по сети (shadNet)** — отдельным модулем (`libbbnet.so`, загружается, только когда онлайн
|
||||
включён): сообщения, пятна крови и призраки — с The Hunter's Dream, колокола и призывы — через
|
||||
сервер shadNet (по умолчанию `srv.shadps4.net:31313`) с учётной записью shadNet. Лаунчер →
|
||||
*Онлайн*: серверы, учётная запись, UPnP, проверка соединения. Порт на роутере открывается по UPnP
|
||||
(#121). Серверы те же, что у Windows-версии. Сетевые и PSN-библиотеки — из shadp2p (форк shadPS4,
|
||||
GPL-2.0), через Linux- и Windows-форки bbport.
|
||||
- **Камера мышью**: щелчок в окне игры захватывает мышь, F1 — отпускает; кнопки мыши и колесо
|
||||
назначаются как клавиши (левая — R1, правая — L2, средняя — R3, боковые — R2/L1, колесо —
|
||||
быстрые предметы). Лаунчер → *Управление*: камера мышью, чувствительность, инверсия (#51, #52).
|
||||
- **DLSS в AppImage** (NVIDIA GeForce RTX): мост и библиотека DLSS 310.9.1 от NVIDIA в пакете
|
||||
(лицензия RTX SDK разрешает это в составе приложения; её текст — в пакете). Более новую
|
||||
`libnvidia-ngx-dlss.so.*` можно выбрать в лаунчере (*Апскейлер → DLSS: библиотека NVIDIA*), как DLL
|
||||
для FSR 4.1.1.
|
||||
- **Любое розничное издание** Bloodborne с обновлением 1.09 (США CUSA00900, Европа, Великобритания,
|
||||
Япония, Азия и издания с The Old Hunters; PR #117); о `eboot.bin` со вшитым патчем 60 FPS
|
||||
сообщается.
|
||||
- **Выход из игры** из оверлея (Insert / L3+R3; нажать дважды) — #74, #116.
|
||||
|
||||
### Исправления
|
||||
|
||||
- Американское и другие издания падали с кодом 139 при входе в мир в новой модели памяти: они шли
|
||||
без профиля игры (#119).
|
||||
- Размытие в движении размазывало весь кадр при повороте камеры в новой модели памяти (#122).
|
||||
- Карты на 4 ГБ (NVIDIA): «Failed to allocate 64 MiB for guest memory copies» останавливало игру;
|
||||
теперь текстуры освобождаются раньше, используется другая память.
|
||||
- Мерцание главного меню при выводе выше 1080p (живая смена разрешения).
|
||||
- Онлайн: «Не удалось получить информацию от сервера Bloodborne» в AppImage на NixOS (сертификаты
|
||||
HTTPS).
|
||||
- NVIDIA на Wayland: swapchain пересоздаётся через `oldSwapchain` (PR #93).
|
||||
- Сборка из git после обновления с 0.4: «patch failed: src/ffx_vk_fsr4_v07.c» (#124).
|
||||
- `bb-launcher.sh --play` через nix-shell (PR #120); запись FSR 4.1.1 из AppImage на SteamOS (PR #89).
|
||||
|
||||
### Меньше скачивать
|
||||
|
||||
AppImage — **399 МБ вместо 925 МБ**: из Mesa только Vulkan-драйверы (RADV без LLVM и ANV), без
|
||||
отладочной информации в бинарнике игры, MangoHud без 32-битной части, GTK лаунчера без GStreamer.
|
||||
|
||||
**Ограничения:** вход в shadNet и призыв автор не проверял (нет учётной записи); UDP-часть
|
||||
публичного сервера shadNet в #121 не отвечала. Камеру мышью и DLSS нужно проверить на большем числе
|
||||
систем.
|
||||
|
||||
Файлов игры в AppImage нет. Ассеты моделей FSR 4 от AMD не входят. К загрузке прилагается
|
||||
`SHA256SUMS`.
|
||||
@@ -1,160 +0,0 @@
|
||||
# Изменения 2026-10-02 (локально, не опубликовано)
|
||||
|
||||
Что сделано, как проверить, и что из этого подтверждено замерами. Все замеры — на RX 7800 XT /
|
||||
RADV; «слабый CPU» — та же машина, ограниченная `taskset` до 4 ядер / 8 потоков.
|
||||
|
||||
## Производительность и плавность
|
||||
|
||||
### Steam Deck и слабые GPU: путь из релиза 0.1 снова по умолчанию
|
||||
|
||||
Для вывода не 1080p (720p на Деке, 1440p, 2160p) вся игра снова рисуется в разрешении пресета
|
||||
(патч при запуске), как в 0.1. Живое масштабирование оставляло постобработку в 1080p и каждый
|
||||
кадр копировало цели сцены туда-обратно: на Деке и GTX 1060 это давало 7–8 FPS.
|
||||
|
||||
- Выбор пути — настройка «Смена разрешения на лету» (лаунчер, меню, `bbport.ini`
|
||||
`live_resolution=auto|0|1`, применяется после перезапуска). **Авто** (было по умолчанию; с 03.10 по умолчанию «выключена») включает
|
||||
живую смену на дискретных GPU с 8+ ГБ памяти, кроме NVIDIA старше Turing: на RX 7800 XT
|
||||
разрешение и пресеты снова меняются в меню на лету, а Steam Deck и GTX 1060 получают быстрый
|
||||
путь. Решение пишется в журнал (`GPU: … live resolution changes on/off`); `BB_LIVE_RES=0/1`
|
||||
перекрывает всё.
|
||||
- Смена пресета, разрешения вывода или переход на TAA в меню при этом пути — через кнопку
|
||||
«Применить и перезапустить игру».
|
||||
- Глубина/stencil уменьшенных целей на GPU без `VK_EXT_shader_stencil_export` (Pascal) снова
|
||||
в полном размере, без 9-проходного копирования.
|
||||
|
||||
### Рваный фреймтайм при упоре в GPU (NVIDIA)
|
||||
|
||||
Две причины, обе исправлены:
|
||||
|
||||
1. Показ кадра (`vkAcquireNextImage`, `vkQueuePresent`) выполнялся в потоке vblank игры. Когда
|
||||
GPU загружен, эти вызовы блокируются (на NVIDIA особенно), и vblank-события игры приходили
|
||||
неравномерно. Теперь показ — в отдельном потоке `bb:Present` (`BB_PRESENT_THREAD=0` — как
|
||||
раньше).
|
||||
2. Поток команд GPU убегал вперёд и упирался в случайные ресурсы, флипы шли пачками. Теперь он
|
||||
опережает GPU не больше чем на `BB_FRAMES_AHEAD` кадров (по умолчанию 1; 0 — без ограничения).
|
||||
|
||||
4K без апскейлера, ~78 FPS, медленный поворот камеры (`Frame pacing` в журнале, окна по 5 с):
|
||||
|
||||
| | FPS | разброс кадра | p99 | кадров > 1.5× медианы |
|
||||
|---|---|---|---|---|
|
||||
| было | 79.5 | 9.9 мс | 25.8 мс | ~125 |
|
||||
| отдельный поток показа | 79.4 | 5.9 мс | 25.0 мс | ~80 |
|
||||
| + опережение 1 кадр | 77.4 | **0.9 мс** | **14.6 мс** | **0** |
|
||||
|
||||
На слабом CPU (720p FSR 3 Performance, без лимита) опережение на FPS не влияет (166 FPS при 0, 1
|
||||
и 2), а отдельный поток показа дал +3.5% (160 → 166 FPS).
|
||||
|
||||
Новая строка `Frame pacing:` в `BB_FRAME_STATS=1`: медиана, разброс, p99 и число всплесков.
|
||||
|
||||
### Кэш шейдеров отключался навсегда (фризы при входе в каждую новую область)
|
||||
|
||||
Кэш пайплайнов при запуске сравнивает сохранённый `Shader::Profile` с текущим. В исходном
|
||||
shadPS4 при несовпадении кэш **закрывается на всю сессию** и ничего не записывает; файл профиля
|
||||
так и остаётся старым, поэтому дальше кэш не работает никогда, и каждая сессия компилирует все
|
||||
шейдеры заново. Сегодня профиль изменился (флаг для Pascal), и с ~12:16 кэш у всех запусков был
|
||||
выключен — включая забег у арены Людвига: в окне фриза 64 компиляции на 114 мс.
|
||||
|
||||
Теперь несовместимый кэш очищается и строится заново (одна пересборка, как при обновлении). В
|
||||
журнале: `Pipeline cache isn't compatible with current system: rebuilding it`. Проверка: забег
|
||||
по маршруту дважды подряд — 241 компиляция в первом, 28 во втором.
|
||||
|
||||
Тот же фриз на выходе с арены (115 мс, забег игрока): кроме компиляций — регистрация 1057
|
||||
текстур и загрузка 220 МБ изображений и 245 МБ буферов за один кадр (защита страниц 17 мс,
|
||||
резидентность 10.5 мс, создание изображений 7.9 мс, остальное — копирование). Эту часть ещё
|
||||
предстоит разнести по кадрам или потокам.
|
||||
|
||||
### CPU на слабых процессорах
|
||||
|
||||
Профиль (`perf`, 4 ядра / 8 потоков, 720p FSR 3 Performance, без лимита, ходьба): ~166 FPS,
|
||||
предел — второй этап конвейера (`bb:DrawRec`, занят 88–90%; первый этап ждёт его ~40%).
|
||||
Время второго этапа размазано по привязке ресурсов и записи draw, единственного горячего места
|
||||
нет. Для Деки и Haswell (ядра примерно вдвое медленнее) это порядка 85–100 FPS — выше 60, то
|
||||
есть средний FPS там упирается не в CPU. Главная проблема слабых CPU — фризы подгрузки (см.
|
||||
«Инструменты»).
|
||||
|
||||
- Поток записи Vulkan выбирал время активного ожидания по числу всех потоков машины, а не
|
||||
доступных процессу: под ограничением ядер он крутился 200 мкс вместо 20.
|
||||
- Ожидание второго этапа теперь тоже зависит от числа ядер (`BB_PIPE_SPIN_US`); на FPS не
|
||||
влияет (166.3–166.8 при 0, 50 и 200 мкс), оставлено короче для машин с 8 потоками.
|
||||
- Ридбэки (Relaxed, защищают лица от «взрыва» вершин): в той же сцене 166.4 FPS против 166.3 с
|
||||
`BB_READBACKS=0` — ничего не стоят, выключать их незачем.
|
||||
|
||||
## Стабильность
|
||||
|
||||
### Вылет `Guest fault … 0x263b8e7` с двухэтапным конвейером
|
||||
|
||||
`sceGnmSubmitDone` не блокировал игру, если поток команд уже **разобрал** все буферы
|
||||
(`num_submits == 0`), хотя второй этап конвейера и отложенные метки fence ещё не записали память.
|
||||
Игра освобождала объекты с метками, поздняя запись портила кучу. Добавлен признак
|
||||
`work_retired`: GPU считается свободным только после опустошения конвейера и записи меток.
|
||||
|
||||
Проверка: 40 минут на 4 ядрах / 8 потоках без падения. Вылет редкий (старая сборка на моей машине
|
||||
тоже прожила 40 минут), поэтому подтверждением будет только отсутствие вылета у вас.
|
||||
|
||||
## Изображение
|
||||
|
||||
- **Резкость до 2.0** (лаунчер, меню). До 1 — RCAS самого апскейлера, выше — ещё один проход
|
||||
RCAS по готовому кадру (FSR 3, FSR 4, FSR 4.1.1, в том числе вывод 720p на Деке; у TAA — свой).
|
||||
- **Ctrl+клик по ползунку в меню** теперь позволяет ввести число (раньше клавиатура только
|
||||
стирала: в меню не передавался текстовый ввод SDL).
|
||||
- **Мерцающие объекты на NVIDIA GTX 10xx (Pascal)** — гробы на мосту и т. п. (shadPS4 PR #1644
|
||||
чинит это только на Turing и новее через барицентрики). Для GPU NVIDIA без
|
||||
`VK_KHR_fragment_shader_barycentric` рекомпилятор теперь умножает интерполированное значение
|
||||
на 1 + 2⁻¹⁶ перед переводом в целое: 0.99999994 снова даёт 1. Нашёл этот приём в 27 шейдерах
|
||||
Bloodborne; на AMD с принудительным включением (`BB_INTERP_INT_FIX=1`) картинка та же.
|
||||
**Нужна проверка на GTX 10xx.** Кэш шейдеров один раз пересоберётся у всех (новая версия).
|
||||
- **Исправления рекомпилятора шейдеров из свежего shadPS4** (вышли после версии, от которой взят
|
||||
код порта): `V_CVT_PK_U8_F32`, `V_BFM_B32`, `V_ALIGNBIT/ALIGNBYTE_B32` (сдвиг на 32 при нулевом
|
||||
выравнивании давал неопределённый результат), перенос SCC в `S_ADDC/S_SUBB_U32`, 32-битные
|
||||
литералы в 64-битных числах, режим плавающей точки compute-шейдеров, селекторы P10/P20 у
|
||||
`V_INTERP_MOV_F32`. Список — в `gpu/VENDOR.txt`. Такие инструкции встречаются в упаковке
|
||||
глубины/теней; связь с багами теней (shadPS4 #4384, #3465) не подтверждена — проверьте те места.
|
||||
- **TAA.** Пробовал: смешивание в тонмапленном пространстве, обрезку истории в YCoCg, обрезку
|
||||
по дисперсии, восстановление текущего кадра фильтром 3×3. Сравнение в одном запуске с
|
||||
переключением на лету (неподвижная камера и поворот): ни одна техника не дала выигрыша сверх
|
||||
разброса между замерами, обрезка YCoCg и фильтр 3×3 — хуже. По умолчанию остался прежний
|
||||
алгоритм; техники доступны для экспериментов (`BB_TOGGLE_FILE`, биты 51–54). Нужен пример,
|
||||
где именно TAA нестабилен (место, что видно), чтобы чинить конкретно это.
|
||||
|
||||
## Моды, патчи, лаунчер
|
||||
|
||||
- **Моды проверены в игре**: мод, подменяющий тексты меню, работает (пути в верхнем регистре,
|
||||
папка-обёртка). Новое: регистр имён не важен, принимаются `Мод/CUSA03173/dvdroot_ps4`,
|
||||
папка-обёртка архива и моды без `dvdroot_ps4` (сразу `chr/`, `parts/`…). В журнале:
|
||||
`Mods: N game files replaced, M added`.
|
||||
- **Сторонние XML-патчи shadPS4**: папка `patches/` в каталоге данных, группа «Сторонние патчи» в
|
||||
лаунчере, выбор в `patches.json`. Подробности — [MODS.md](MODS.md).
|
||||
- **60/90 FPS** больше не падают с `patch overlaps a relocation`: патчи, заменяющие указатели,
|
||||
загрузчик переносит на реальную базу образа.
|
||||
- **Лаунчер на английском** (по умолчанию — язык системы; переключатель вверху страницы).
|
||||
- Подпись «Ограничение FPS»: 0 — по частоте экрана, не выше 120 Гц.
|
||||
|
||||
## Память
|
||||
|
||||
15 циклов «Выйти из игры → Продолжить» (перезагрузка уровня), 720p FSR 3:
|
||||
|
||||
| циклы | RSS процесса | VRAM |
|
||||
|---|---|---|
|
||||
| 0 → 7 | +179 МБ | +751 МБ |
|
||||
| 11 → 15 | +8 МБ | +15 МБ |
|
||||
|
||||
Рост затухает: при повторных загрузках это прогрев кэшей (пайплайны, текстуры), а не
|
||||
неограниченная утечка. `heaptrack` на 8 таких циклах тоже не нашёл источников, растущих от
|
||||
загрузки к загрузке (крупные — один раз при запуске: таблица страниц 279 МБ, пул текстурного
|
||||
кэша 92 МБ). Кэш текстур по замыслу освобождает память только у бюджета VRAM. 40-минутный
|
||||
прогон в одной локации: RSS ~2.64 ГБ, VRAM ~4.29 ГБ всё время, без роста. Не проверено:
|
||||
долгий проход через много разных локаций (рост кэша до бюджета там ожидаем) — для этого нужен
|
||||
записанный маршрут.
|
||||
|
||||
## Инструменты для тестов
|
||||
|
||||
- **Запись маршрута**: `BB_PAD_RECORD=/путь/route.txt` — F9 в игре начинает и останавливает запись
|
||||
геймпада/клавиатуры. `BB_PAD_REPLAY=/путь/route.txt` + слово `replay` в `BB_PAD_FILE` —
|
||||
воспроизведение с того же момента. Так можно один раз пробежать маршрут (от лампы у Людвига к
|
||||
другой лампе), а потом повторять его в замерах фризов. Воспроизведение проверено; запуск записи
|
||||
клавишей F9 автоматически проверить нельзя — проверьте, пожалуйста.
|
||||
|
||||
Как записать: отдохнуть у лампы у Людвига, выйти в меню и «Продолжить» (персонаж появится у
|
||||
лампы), в лаунчере в «Доп. переменные» указать `BB_PAD_RECORD=/home/<вы>/route.txt`, после
|
||||
появления нажать F9, пробежать до другой лампы, F9. Сохранение тоже понадобится (папка
|
||||
`user/savedata`), чтобы старт был в той же точке.
|
||||
@@ -1,107 +0,0 @@
|
||||
# Изменения 2026-10-03 (локально, не опубликовано)
|
||||
|
||||
Замеры — RX 7800 XT / RADV, ограничение `taskset` до 4 ядер / 8 потоков, настройки Steam Deck
|
||||
(вывод 1280×720, FSR 3 Performance, сцена 640×360), без лимита FPS. Стенд:
|
||||
`out/bb-probe` из исходников, сохранение `docs/savedata_route`, скриптовый вход в уровень.
|
||||
|
||||
## Смена разрешения на лету — по умолчанию выключена
|
||||
|
||||
`live_resolution` без значения (новая установка, лаунчер, меню) теперь означает «выключена»:
|
||||
для вывода не 1080p — патч разрешения при старте, как в 0.1. `auto` (проверка видеокарты) и `1`
|
||||
выбираются явно. Тесты `test_run_settings`.
|
||||
|
||||
## AppImage без моделей FSR 4.1.1
|
||||
|
||||
Модели извлекаются из DLL AMD и в публичный пакет не входят (как в 0.1): `packaging/default.nix`
|
||||
кладёт `fsr4_411` только с `BB_PACKAGE_FSR411=1`; иначе их ищут в каталоге данных.
|
||||
|
||||
## Steam Deck: запуск и сборщик текстур
|
||||
|
||||
- **AppImage не запускала игру на NixOS** (код 127 без сообщения): `run.sh` читал
|
||||
`live_resolution` через `sed`, которого нет в окружении AppImage. Теперь только встроенные
|
||||
команды bash; тест `test_live_resolution_without_sed_or_grep`.
|
||||
- **Сборщик текстур на встроенных GPU** (`TextureCache::GarbageCollectImages`). Бюджет считался
|
||||
как «все кучи минус 8 ГБ для системы» (на Деке ~1 ГБ), а занятость — по кучам GTT, где лежат и
|
||||
буферы с памятью игры (~4 ГБ). Сборщик постоянно работал в «агрессивном» режиме: на каждой
|
||||
отправке команд (~66 за кадр) выкидывал до 40 текстур, не использованных 2–3 кадра, и снова
|
||||
их загружал, а изменённые GPU асинхронно записывал обратно в память игры. Теперь на встроенных
|
||||
GPU пороги — доля текущего бюджета драйвера (`VK_EXT_memory_budget`): сбор с 70%, нехватка с
|
||||
85%, критично с 95%. В журнале при нехватке: `Texture cache: memory pressure, …`.
|
||||
`BB_GC_BUDGET_MB=N` — то же правило с заданным бюджетом на любом GPU (проверка на ПК).
|
||||
- **Запись вытесненной текстуры обратно в память игры теперь синхронная**, пока страницы ещё
|
||||
защищены. Отложенная запись приходила после снятия защиты — поверх того, что игра успела туда
|
||||
записать (например, кучу после выгрузки локации). Возможная причина `Guest fault 0x263b8e7` на
|
||||
Деке при подгрузке карт; **на ПК этот вылет тоже был один раз за ~10 прогонов, его причина не
|
||||
найдена** (порог нехватки памяти на ПК ~11 ГБ, сборщик туда не попадает).
|
||||
|
||||
## Живая смена разрешения дешевле
|
||||
|
||||
Раньше уменьшались только цели 1920×1080; постобработка игры в полуразрешении (960×540, в том
|
||||
числе цепочка bloom с mip-уровнями) шла в полном размере и каждый кадр пересэмплировала цвет
|
||||
сцены. На Деке один такой проход стоил ~8.4 мс (против ~1.4 мс при патче разрешения).
|
||||
|
||||
- Цели 960×540 уменьшаются тем же коэффициентом, что сцена (ProxySize). Пересчёт FragCoord в
|
||||
шейдерах не меняется: 960/320 = 1920/640.
|
||||
- Mip-цепочки полуразрешения: отдельная уменьшенная копия на каждый уровень (ключ — изображение и
|
||||
уровень); выборка одного уровня читает копию, выборка всей цепочки — родное изображение после
|
||||
пересэмплирования.
|
||||
- Переключатель `BB_TOGGLE_FILE` бит 50 (`SceneHalfRes`) — прежнее поведение.
|
||||
- `BB_SCENE_DEBUG`: ещё печатает пересэмплирования (`Scene resolve/fill`) и чтения уменьшенных
|
||||
целей в полном размере с причиной (`Scene native read`).
|
||||
- Тест: `scene-resolution-test` — копия mip-уровня 2 цепочки 960×540 и её возврат в родной уровень.
|
||||
|
||||
Результат на ПК (4c/8t): занятость GPU живого пути 3.41 → 3.23 мс на кадр, проход `5a619b89` —
|
||||
320×180 вместо 960×540. Картинка совпадает с режимом патча (bloom и туман считаются в
|
||||
пониженном разрешении, как при патче). Осталось: ~9 пересэмплирований и ~7 обратных заливок
|
||||
цвета сцены 1080p за кадр (копирования, `9a9cf8a9`, композиция UI).
|
||||
|
||||
**Почему живой путь всё равно дороже на CPU:** игра считает экран 1080p и рисует в проходе
|
||||
освещения (`vs 8747a367`) 335 источников света вместо 42 при патче (мелкие источники
|
||||
отбрасываются по размеру на экране): 975 draw-вызовов на кадр против 679. Это решение самой игры.
|
||||
|
||||
## TAA: мерцание тонких объектов вдали
|
||||
|
||||
Прутья окон, перила и края обломков мерцали: в фазе jitter, где тонкий объект не попадает в
|
||||
центр пикселя, проверка глубины отбрасывала историю (в ней объект ближе текущего фона). Теперь
|
||||
при движении меньше пикселя такая история остаётся и ограничивается цветом соседей (как в TAA без
|
||||
отбрасывания по глубине). Переключатель бит 55 — прежнее правило.
|
||||
|
||||
Метрика: 30 кадров подряд при неподвижной камере (`BB_PRESENT_DUMP_COUNT=30`), средняя вторая
|
||||
разность яркости по времени (мерцание кадр-к-кадру, плавная анимация почти не влияет), ×1e-3:
|
||||
|
||||
| | среднее | p99.9 |
|
||||
|---|---|---|
|
||||
| было (2 прогона) | 0.367 / 0.381 | 23.2 / 22.0 |
|
||||
| стало (2 прогона) | 0.266 / 0.293 | 12.2 / 13.1 |
|
||||
| без TAA и jitter | 0.428 | 16.8 |
|
||||
|
||||
Мягкое ограничение по цвету для неподвижных пикселей и обрезка по дисперсии мерцание не
|
||||
уменьшили (дисперсия — увеличила). Тесты `taa-shader-test`: добавлены «thin line missed by this
|
||||
frame keeps history», «static disocclusion is clipped», «moving disocclusion is rejected».
|
||||
|
||||
## Апскейлеры: сверка с аудитом от 2026-10-02
|
||||
|
||||
Аудит смотрел версию с GitHub (`cc001f9`); по локальному коду:
|
||||
|
||||
- Native AA вне 1080p (2.1) — уже исправлено (`w > ow`).
|
||||
- FSR 4 v07 и HDR-флаг (2.2): провайдер сохраняет флаги контекста, но не читает их — флаг на
|
||||
результат не влияет. На пути с патчем вход действительно LDR (после тонмаппинга) — ограничение
|
||||
самого пути.
|
||||
- Небо (4.1) и порог глубины объектных векторов (4.4) — уже исправлены (поворот без переноса,
|
||||
относительный порог, полная точность глубины).
|
||||
- **Mip bias (4.7) — сделано:** в проходах G-buffer (5+ целей) при уменьшенной сцене смещение
|
||||
LOD сэмплеров log2(рендер / вывод) (на Деке −1); тени, постобработка и UI без изменений. В
|
||||
журнале `Upscaler: scene texture LOD bias -1.00`. Переключатель бит 57.
|
||||
- Не сделано: классификация частиц для jitter (4.2), сброс истории на смену камеры (4.8).
|
||||
|
||||
## CPU на слабых процессорах: замер
|
||||
|
||||
Режим патча, 4c/8t, 209 FPS. Потоки (% одного ядра, пользователь + ядро): `bb:DrawRec` 72 + 14,
|
||||
поток команд GPU 56 + 20, `bb:VkRecorder` 72 + 1 (в основном время RADV), основной поток игры
|
||||
46 + 10, четыре потока подготовки draw по ~20. Всего занято ~545% из 800%: предел —
|
||||
последовательная цепочка «поток команд → запись draw → запись Vulkan». Дальнейший рост требует
|
||||
распараллелить запись Vulkan (несколько командных буферов) — крупная переделка, не начата.
|
||||
|
||||
## Инструменты
|
||||
|
||||
- `BB_PRESENT_DUMP_COUNT=N` — `BB_PRESENT_DUMP_TRIGGER` снимает N кадров подряд.
|
||||
@@ -1,145 +0,0 @@
|
||||
# Изменения 2026-10-04 — 2026-10-06 (версия 0.3)
|
||||
|
||||
Замеры — RX 7800 XT / RADV, Mesa 26, вывод 1080p без лимита FPS, если не сказано иначе.
|
||||
|
||||
## Модель памяти ПК — экспериментальная, по умолчанию выключена
|
||||
|
||||
Включается переключателем лаунчера «Новая модель памяти (экспериментально)» или
|
||||
`BB_GUEST_IN_PLACE=1`; по умолчанию работает модель 0.2. Проверена только на RX 7800 XT и Steam
|
||||
Deck, может вылетать; на NVIDIA dma-buf не отображается со смещением, и порт возвращается к
|
||||
старой модели. Прямая память игры выделяется через Vulkan
|
||||
(dma-buf, видимая GPU системная память), и GPU читает её на месте, как буферы игры для ПК. Копий
|
||||
всей памяти в VRAM и отслеживания записей через защиту страниц больше нет. Метки готовности
|
||||
(EOP/EOS) пишутся, когда GPU действительно выполнил работу.
|
||||
|
||||
- Кадры перекрываются: первые две отправки команд кадра (`BB_SUBMIT_AHEAD=2`) не ждут конца
|
||||
предыдущего кадра. При K ≥ 8 куча игры течёт (~2 объекта за кадр), поэтому есть сторож: рост
|
||||
живых выделений на 100 000 выключает перекрытие до конца сессии.
|
||||
- Отправка команд асинхронная (`BB_ASYNC_SUBMIT`): command buffer'ы сегментов закрывают их потоки
|
||||
записи, сама отправка — упорядоченная задача на них же.
|
||||
- Сигналы GPU копятся на стадии записи draw и публикуются пачкой; ожидания ConstRam — точечные.
|
||||
- Замеры, Yahar'gul, стоя: старая модель 138,7 → 143,6 FPS, новая 101 → 152–155 FPS. Тяжёлое
|
||||
место (~2000 draw за кадр): 95,5 → 115–124 FPS; там теперь упор в стадию записи draw.
|
||||
|
||||
Ключи настроек `pc_memory` и `old_memory_model` больше не читаются: новый ключ `new_memory_model`
|
||||
по умолчанию выключен у всех.
|
||||
|
||||
## Видеопамять больше не растёт от локации к локации
|
||||
|
||||
До исправлений VRAM за 13 минут игры вырос с 3,8 до 7,3 ГБ, теперь держится на 4–5,5 ГБ.
|
||||
|
||||
- **Сборщик текстур не освобождал ничего.** Десять самых старых текстур, в которые рисовал
|
||||
GPU, расходовали весь лимит прохода. Теперь такие текстуры пропускаются и уходят в конец
|
||||
очереди; без нехватки памяти выгружаются текстуры, не использованные `BB_GC_IDLE_SECONDS`
|
||||
(20) секунд. Возраст считается в секундах: прежние 16 отправок команд (~3 кадра) выкидывали
|
||||
всё, что ненадолго ушло с экрана.
|
||||
- **Блоки памяти игры в VRAM никогда не освобождались.** Теперь блоки, не использованные
|
||||
`BB_VRAM_IDLE_SECONDS` (60) секунд, возвращаются в память игры и снова переносятся в VRAM при
|
||||
обращении (без этого просадка 90 → 55 FPS: GPU читал их по PCIe). Блоки памяти, которую игра
|
||||
снимает с отображения, освобождаются. Свободные слоты используются и многоблочными запросами;
|
||||
новые блоки занимают самые заполненные куски по 64 МБ, почти пустые выселяются, пустые
|
||||
возвращаются драйверу.
|
||||
- VMA берёт память блоками по 64 МБ вместо 256 (`BB_VMA_BLOCK_MB`), текстуры от 4 МБ получают
|
||||
отдельное выделение (`BB_VMA_DEDICATED_MB`): удалённые текстуры не держат большие блоки.
|
||||
- Статистика кадров: строка `Memory:` — VRAM и GTT игры по данным ядра, RSS, блоки VMA, все
|
||||
изображения Vulkan, кэш текстур, блоки памяти игры в VRAM, работа сборщика.
|
||||
`tools/load_watch.sh` пишет VRAM/GTT/RSS каждые 5 с.
|
||||
|
||||
## Зависания GPU: найдено место
|
||||
|
||||
Метки GPU (`vk_breadcrumbs.*`, `VK_AMD_buffer_marker`; `BB_BREADCRUMBS=0` — выключить): до и
|
||||
после каждой отрисовки, dispatch и прохода порта GPU пишет номер команды. При потере устройства
|
||||
журнал называет команду, на которой GPU встал, её шейдеры и параметры.
|
||||
|
||||
Найдено (сессия ~20 минут): зависание в непрямом dispatch шейдера частиц `cs 2da7fe60` сразу после
|
||||
`cs 4ca76892` (1×1×1, генератор случайных чисел эмиттеров, счётчик в GDS). Число итераций циклов
|
||||
`2da7fe60` берётся из памяти. Аргументы непрямых dispatch теперь копируются GPU в буфер, видимый
|
||||
процессору: отчёт показывает, что GPU прочитал, а статистика — самые большие непрямые dispatch.
|
||||
**Причина ещё не устранена**: нужен следующий случай с этими данными.
|
||||
|
||||
Зависание больше не выглядит как тихая заморозка картинки при играющем звуке (Steam Deck, бой с
|
||||
Германом): ожидания GPU замечают потерю устройства, а сторож через 10 с без прогресса GPU пишет
|
||||
в журнал отчёт меток и останавливает игру с понятной ошибкой.
|
||||
|
||||
## Падения и запуск
|
||||
|
||||
- Гостевой сбой `0x263b8e7` (частый на Steam Deck): метки EOP писались через `memcpy`, который
|
||||
записывает 4–7 байт двумя перекрывающимися словами. Игра успевала освободить блок между ними.
|
||||
Теперь метки пишутся одной атомарной записью (`store_once`, `StoreLabel`). На Deck не проверено.
|
||||
- NVIDIA с новой моделью памяти падала при запуске: драйвер не отображает dma-buf со смещением.
|
||||
Перед включением модели порт проверяет это и при неудаче остаётся на старой модели.
|
||||
- Проверка файлов игры (`scripts/game_check.py`, issue #7, #13, #14): хеш загруженного образа
|
||||
eboot сравнивается с CUSA03173 1.09; без обновления, со старым eboot или другим изданием запуск
|
||||
останавливается с объяснением, лаунчер показывает то же. `BB_SKIP_GAME_CHECK=1` — обход.
|
||||
|
||||
## Лаунчер и ввод
|
||||
|
||||
- Выпадающие списки с длинными вариантами («Фоновая загрузка в видеопамять», конвейер GPU,
|
||||
чтение данных GPU, контроллер) показывают короткие названия, а пояснение к выбранному —
|
||||
строкой под заголовком: текст больше не обрезается.
|
||||
|
||||
- Выбор контроллера (issue #15): *Управление → Контроллер* (`BB_GAMEPAD`, GUID или часть
|
||||
имени); выбранный берётся, как только подключится. Список — `bb-gpu-capabilities --gamepads`.
|
||||
- Ввод имени персонажа в окне поверх игры (issue #19, патч из issue); ввод в игру на это время
|
||||
нейтрален (Backspace не нажимает тачпад).
|
||||
- Курсор скрыт в полноэкранном режиме и через 3 с без движения мыши в окне (issue #3).
|
||||
- **Переназначение клавиатуры и геймпада**: *Управление → Клавиатура / Геймпад*, кнопка
|
||||
«Назначить» ждёт нажатия. В `bbport.ini` — строки `key.<кнопка>=` и `pad.<кнопка>=` с именами
|
||||
SDL; нет строки — назначение по умолчанию.
|
||||
|
||||
## Патчи игры
|
||||
|
||||
- **Sprint Fix (High FPS)**: выше ~147 FPS (а иногда и выше ~90) бег
|
||||
падал до половины скорости — детектор стены игры мерил расстояние за кадр. Патч включается
|
||||
всегда вместе с режимами 90 FPS и «без ограничения».
|
||||
- **Чёрный тонмаппинг на процессорах Intel** (темнее, чем на PS4; сильнее всего в DLC): код игры
|
||||
выполняется нативно, а приближённые float-инструкции Intel дают другие значения. Патч
|
||||
исправления включается сам на процессорах Intel; `BB_INTEL_TONEMAP_FIX=0/1` — принудительно.
|
||||
|
||||
## Сборка ассетов FSR 4.1.1
|
||||
|
||||
- Новейший GE-Proton выбирается по версии (`ls | tail -1` брал 9-x вместо 10-x; issue #4), также
|
||||
`~/.steam/root` и Flatpak; захват без кадров останавливает сборку с объяснением.
|
||||
- На RDNA4 DLL, вероятно, выбирает FP8-вариант шейдеров (issue #12): захват скрывает от
|
||||
vkd3d-proton `VK_KHR_cooperative_matrix`, `VK_NV_cooperative_matrix2`, `VK_EXT_shader_float8`. На
|
||||
RX 7800 XT захват совпадает побайтно; на RDNA4 не проверено. `extract.py` при нарушении правил
|
||||
ничего не пишет и объясняет причину.
|
||||
- Инструменты из системы без Nix; Nix без канала — `channel:nixos-unstable` (issue #22).
|
||||
|
||||
## Апскейлеры: мыло и полосы на лестницах
|
||||
|
||||
- **Векторы камеры: перевёрнутая вертикаль.** `camera_motion.comp` и проверка глубины в
|
||||
`taa.comp` считали, что ось +y вида направлена вверх по экрану; у игры она вниз (по строкам).
|
||||
Ошибка затрагивала наклон камеры и вертикальное движение, поворот влево-вправо был верным.
|
||||
Поэтому видно было там, где камера покачивается вверх-вниз: на лестнице к собору от гробницы
|
||||
Идон и в канализации. FSR 4 и 4.1.1 давали полосатое мыло, FSR 3.1 — общее размытие, а персонаж
|
||||
с векторами объектов оставался чётким. Проверка: на статичных мешах, у которых есть и векторы
|
||||
объектов (позиции вершин с GPU), новые векторы камеры совпадают с ними в пределах 0,05 px на
|
||||
17 кадрах бега по лестнице, прежние расходились на 1,3–3,7 px. В игре на FSR 4.1.1 1080p
|
||||
Performance «стало сильно лучше».
|
||||
- **Анизотропная фильтрация 16x** для текстур отрисовок сцены (`BB_ANISO`, по умолчанию 16;
|
||||
0 — фильтры игры). Игра ставит большинству текстур 1x, а при апскейле поверхности под острым
|
||||
углом теряют детали. Переключатель для A/B на лету — бит 61 в `BB_TOGGLE_FILE`.
|
||||
- Дамп апскейлера (`BB_DUMP_TRIGGER`) работает и на HDR-пути, пишет матрицы камеры кадра;
|
||||
`tools/dump_view.py` переводит дамп в PNG.
|
||||
|
||||
## MangoHud: двойной оверлей
|
||||
|
||||
Текст MangoHud двоился со сдвигом и разными значениями: рисовались два оверлея. Встроенный в
|
||||
AppImage слой называется `VK_LAYER_MANGOHUD_overlay_64_x86_64` (nixpkgs), системный — иначе,
|
||||
и загрузчик Vulkan грузил оба. На Steam Deck в игровом режиме то же дают оверлей
|
||||
производительности Steam (mangoapp) и MangoHud из лаунчера. Теперь `run.sh` при `MANGOHUD=1`
|
||||
не включает MangoHud в игре, если запущен mangoapp, и отключает встроенный слой, если в системе
|
||||
есть свой (`VK_LOADER_LAYERS_DISABLE`). На NixOS без системного MangoHud не воспроизводилось.
|
||||
|
||||
## Окклюжн куллинг (ветка rainmakerv3/shadPS4 `opts`)
|
||||
|
||||
Коммит «correct queries» заменяет фальшивый счётчик ZPASS_DONE настоящими occlusion queries
|
||||
Vulkan. В Соборном округе Bloodborne делает ~0–8 таких запросов за 5 с и не использует
|
||||
предикацию (`SET_PREDICATION` — 0), то есть отрисовки по ним не отсекает: переносить нечего.
|
||||
Сетевой поток в офлайне (коммит про epoll в той же ветке) у нас ядро не занимает.
|
||||
|
||||
## Тесты
|
||||
|
||||
`tests/test_game_check.py`; `build.sh --test` снова собирается (заглушка
|
||||
`runtime_memory_note_write` в `tests/test_file_mods.c`).
|
||||
@@ -1,434 +0,0 @@
|
||||
# Изменения после 0.4 (с 2026-10-08)
|
||||
|
||||
Замеры — RX 7800 XT / RADV, Кошмар охотника у лампы (сохранение пользователя), 1080p без лимита
|
||||
FPS, новая модель (`BB_PC_MODEL=1`); «4 ядра» — `CPUS=0-3,8-11` (4 ядра / 8 потоков).
|
||||
Подробности и что осталось — [EMULATION_REMOVAL_PLAN.ru.md](EMULATION_REMOVAL_PLAN.ru.md),
|
||||
«Шаг 3».
|
||||
|
||||
## Метки EOP/EOS пишет видеочип (новая модель)
|
||||
|
||||
- `EVENT_WRITE_EOP` без прерывания, `EVENT_WRITE_EOS` и `RELEASE_MEM` очередей compute:
|
||||
буферный маркер Vulkan (`VK_AMD_buffer_marker`, конец конвейера) прямо в память игры, в той же
|
||||
точке потока, где их пишет командный процессор PS4. Раньше каждую метку писал поток процессора
|
||||
после завершения всей отправки Vulkan — ~330 меток за кадр, 61 тыс. в секунду.
|
||||
- 64-битная метка — две 32-битные записи, старшая половина первой (кто видит новую младшую,
|
||||
видит и новую старшую).
|
||||
- Метки с прерыванием, а также без `VK_AMD_buffer_marker` (NVIDIA) и с `BB_FREE_CHECK=1` — как
|
||||
раньше. `BB_GPU_LABELS=0` — как раньше везде.
|
||||
- EOS с записью из GDS: копия видеочипом в порядке потока вместо ожидания всего видеочипа
|
||||
(`Finish()`) и записи процессором.
|
||||
- Статистика: строка `EOP fences` показывает, сколько меток за кадр записал видеочип.
|
||||
- FPS тот же (16 потоков: 233; 4 ядра: ~217 против ~215), картинка та же; 8 минут ходьбы — см.
|
||||
ниже.
|
||||
|
||||
## Перехваты кода игры без ловушек
|
||||
|
||||
- Точки, где игра выделяет память GPU (аллокатор диапазонов, три места кучи) и где загрузчики
|
||||
копируют ресурсы (`memcpy`), раньше были `int3`: каждый вызов — сигнал ядра (SIGTRAP), ~1200
|
||||
в секунду только у аллокатора. Теперь переход в заглушку рядом с образом игры: заглушка
|
||||
вызывает обработчик и выполняет перекрытые инструкции; вызов `memcpy` идёт через трамплин.
|
||||
- Байты переписываются поверх уже стоящего `int3` (поток, наткнувшийся на него в этот момент,
|
||||
уходит в заглушку). `BB_GUEST_HOOK_TRAPS=1` — ловушки, как раньше. Диагностические точки
|
||||
(`BB_FRAME_STATS`, `BB_HEAP_SITES`) остались ловушками.
|
||||
|
||||
## Новая модель памяти на NVIDIA
|
||||
|
||||
- Новый способ отдать память игры видеочипу — импорт памяти хоста
|
||||
(`VK_EXT_external_memory_host`): кусок прямой памяти остаётся в memfd рантайма, видеочип
|
||||
импортирует его второе отображение. Процессор работает с обычной кэшируемой памятью, видеочип —
|
||||
через шину, как с upload heap игры для ПК. Освобождённая память в таких кусках обнуляется, а не
|
||||
выбрасывается из memfd (видеочип держит эти страницы).
|
||||
- При запуске самопроверка в точности как в работе: импорт куска memfd, привязка к sparse-буферу,
|
||||
запись видеочипом — чтение процессором через другое отображение, запись процессором — копия
|
||||
видеочипом; sparse-буфер для такой памяти должен принимать и VRAM. Не прошла — dma-buf (на
|
||||
NVIDIA — «кусок на выделение»), для которого теперь меряется скорость чтения процессором через
|
||||
отображение драйвера (меньше 1 ГБ/с — не используется: код игры работает на этой памяти). Не
|
||||
прошло и это — старая модель. В журнале: `Guest memory: host memory imported by the GPU`,
|
||||
`host memory import does not work here: …`, `CPU reads through the driver's dma-buf: N MB/s`.
|
||||
- Порядок: на AMD — dma-buf (как раньше; RADV не импортирует memfd —
|
||||
`ErrorInvalidExternalHandle`, проверено), на остальных — сначала импорт. `BB_GUEST_MEMORY=host`
|
||||
или `dmabuf` выбирает сам.
|
||||
- Арена (sparse-буфер памяти игры) создаётся с тем типом внешней памяти, который выбран; если
|
||||
драйвер с ним не пускает в арену VRAM — старая модель с сообщением в журнале.
|
||||
- Лаунчер: переключатель *Новая модель памяти и трансляции* доступен на AMD и NVIDIA (тексты на
|
||||
трёх языках). Без лаунчера — `BB_PC_MODEL=1`.
|
||||
- **На NVIDIA не проверено** (у автора только AMD): нужен журнал с NVIDIA.
|
||||
|
||||
## Диагностика
|
||||
|
||||
- `BB_DCB_STATS=1`: кроме структуры буферов команд — пакеты PM4 в секунду по типам, события
|
||||
`EVENT_WRITE` по типам, EOP по источнику данных и прерыванию, EOS по команде, `WAIT_REG_MEM` по
|
||||
месту и движку, и чем закончились ожидания (метка VO, метка в порядке потока, выполнено сразу,
|
||||
ждали — и сколько мс в секунду).
|
||||
|
||||
## Шейдер копирования игры: записи остаются в памяти игры; трансляция вместо подмены — по выбору
|
||||
|
||||
- Вычислительный шейдер копирования игры (`cs fefebf9f`, 2–50 запусков за кадр) выполнялся
|
||||
подменой: процессор при записи команд читал список копий из памяти игры и делал копии своим
|
||||
шейдером. Теперь его можно транслировать как есть (`BB_COPY_SHADER_NATIVE=1`, только новая
|
||||
модель): список читает видеочип во время выполнения, как на PS4.
|
||||
- Найдено: рядом с тем, куда он копирует, лежат код шейдеров и таблицы описателей, которые наш
|
||||
транслятор читает процессором. Пока эти блоки в памяти игры, всё верно; копия в VRAM (правило
|
||||
«видеочип пишет это 3 кадра из 60») давала транслятору устаревший код — чёрная сцена. Подмена
|
||||
держала их в памяти игры случайно (её диапазоны каждый раз разные). Теперь записи этого шейдера
|
||||
в обоих режимах остаются в памяти игры (`force_writes_in_place`).
|
||||
- По умолчанию — подмена: родной шейдер на 4 ядрах стоит потоку записи отрисовок ~0,3 мкс на
|
||||
пакет (+10 %), видеочипу +2 %. Бит 2 второго числа `BB_TOGGLE_FILE` переключает на ходу.
|
||||
|
||||
## Константы на месте (по выбору) и привязка буферов
|
||||
|
||||
- `BB_CONSTANTS_IN_PLACE=1` (новая модель): маленькие буферы только для чтения (константы)
|
||||
видеочип читает прямо из памяти игры, а не из копии, сделанной при разборе (кольцо констант) или
|
||||
при записи (стрим-буфер). Копии защищали отрисовку от записей командного процессора, которые
|
||||
процессор выполнял «наперёд»; в новой модели эти записи выполняет видеочип в порядке потока,
|
||||
так что копия больше не нужна для правильности. Замер (4 ядра, у лампы): разбор −0,8 мкс на
|
||||
отрисовку, видеочип +3 % (константы идут по PCIe), FPS тот же — поэтому выключено по умолчанию.
|
||||
Бит 1 второго числа `BB_TOGGLE_FILE` переключает на ходу.
|
||||
- Привязка буфера только для чтения, уже лежащего в памяти игры, запоминается до изменения
|
||||
резидентности (без обхода блоков на каждую привязку); проверки «на месте ли буфер» для
|
||||
диагностики `BB_TRACE_SHADER` больше не выполняются, когда она выключена (были две на привязку).
|
||||
|
||||
## Исправление
|
||||
|
||||
- Реестр обработчиков ошибок доступа к памяти терял обработчик с тем же приоритетом, что у уже
|
||||
зарегистрированного (множество упорядочивалось только по приоритету). Порядок теперь явный:
|
||||
диагностическая ловушка VRAM, менеджер страниц, SRT walker.
|
||||
|
||||
## Диагностика (новая)
|
||||
|
||||
- `BB_RES_STATS=1`: откуда шейдеры берут описатели ресурсов — из регистров или из таблиц в памяти
|
||||
(SRT walker), по видам, на привязку.
|
||||
- `BB_VRAM_ACCESS_TRAP=1` (+ `BB_VRAM_TRAP_RANGE=адрес,размер`): блоки памяти игры, перенесённые
|
||||
в VRAM, закрываются от процессора; печатаются места кода, которые к ним обращаются (кто читает
|
||||
устаревшую копию).
|
||||
- `BB_WATCH=адрес,размер`: что транслятор читает из диапазона (указатели в пользовательских
|
||||
данных, буферы, код шейдеров). `BB_TRACE_ADDR=адрес,размер` (с `BB_TRACE_SIZE`): привязки над
|
||||
диапазоном с самого начала. `BB_TILE_BACK_LOG=1`: образы, записанные обратно в память, потому
|
||||
что их читают как текстурный буфер. `BB_COPY_SHADER_TRACE=1`: буферы шейдера копирования.
|
||||
|
||||
## Occlusion-запросы игры — настоящие запросы Vulkan (новая модель)
|
||||
|
||||
- Bloodborne делает около 60 occlusion-запросов за кадр (`ZPASS_DONE`); раньше они получали
|
||||
фиктивную последовательность «всё видно». Теперь сэмплы считают запросы Vulkan, а результаты
|
||||
записывает видеочип в память игры в формате PS4 (счётчики блоков глубины с битом готовности).
|
||||
Проходы рендера не разрываются: запрос режется на границах проходов и буферов команд, запись
|
||||
результатов откладывается до ближайшей точки вне прохода и делается одним запуском шейдера. На
|
||||
4 ядрах разница с фиктивным счётчиком — в пределах шума. `BB_OCCLUSION_QUERIES=0` — как раньше.
|
||||
- `COPY_DATA` (константы, регистры, часы, память, GDS) — записи и копии видеочипа в порядке потока;
|
||||
`COND_EXEC` — условие по метке в порядке потока или после работы видеочипа до пакета;
|
||||
`SET_PREDICATION` (ZPass, Bool64/32) — отрисовки и запуски с битом предиката пропускаются, если
|
||||
предикат не выполнен. Bloodborne последние три не использует; это для переноса других игр.
|
||||
- `BB_PM4_SELFTEST=1`: самопроверка всего этого в работающей игре (8 проверок, печатает PASS/FAIL).
|
||||
- Исправлена взаимоблокировка при чтении процессором данных видеочипа из потока разбора.
|
||||
|
||||
## Шейдер копирования игры — по умолчанию как есть
|
||||
|
||||
- Шейдер копирования игры (`cs fefebf9f`, ~36 запусков за кадр) теперь по умолчанию переводится и
|
||||
выполняется сам: список копий читает видеочип. `BB_COPY_SHADER_NATIVE=0` — прежняя подмена (список
|
||||
читает процессор). Цена на 4 ядрах — +6 % к потоку записи отрисовок, видеочип и FPS те же.
|
||||
- Чтобы цена была такой: запись видеочипа в память ищет затронутую текстуру только по первому байту
|
||||
(нужна лишь текстура, начинающаяся с этого адреса; раньше обходился весь диапазон под мьютексом), а
|
||||
запись поверх страниц, которые процессор не менял и которые уже помечены как данные видеочипа,
|
||||
проходит без блокировки трекера.
|
||||
- Подмены «копирование текстуры через буферы» и «очистка текстуры через буфер» оставлены: путь через
|
||||
память (выгрузить отрисованный кадр в память, выполнить шейдер игры, перезалить текстуру) сейчас
|
||||
даёт застывшую картинку — содержимое целей рендера не доходит до памяти игры, а при рендере сцены в
|
||||
пониженном разрешении его там и нет. `BB_HLE_OFF=1|2|4` — опыт с отключением подмен,
|
||||
`BB_HLE_STATS=1` — сколько раз они срабатывают (~4, ~15 и ~10 за кадр).
|
||||
- Опыт `BB_GUEST_VRAM=1`: вся память игры в VRAM через Resizable BAR — 16 FPS уже в меню (процессор
|
||||
читает её через PCIe), не годится.
|
||||
|
||||
## Сохранения не портятся при вылете (#64)
|
||||
|
||||
- Bloodborne сохраняется примерно раз в секунду: удаляет `backupN`, переименовывает `userdataN` в
|
||||
`backupN` и пишет новый `userdataN` (1,3 МБ). Вылет или снятие процесса посреди записи оставляли
|
||||
обрезанный `userdataN`.
|
||||
- Теперь файл сохранения (`/savedataN/...`), открытый на запись, пишется во временную копию
|
||||
(`<имя>.<n>.bbtmp` рядом), а при закрытии она сбрасывается на диск (`fsync`) и одной операцией
|
||||
`rename` встаёт на место файла. Вылет посреди записи оставляет прежний файл (или его отсутствие,
|
||||
если игра уже переименовала его в `backupN`: тогда игра сама читает `backupN` — проверено).
|
||||
- Пока файл открыт, операции по пути (`stat`, `access`, `truncate`, повторное открытие) видят копию:
|
||||
игра проверяет размер записываемого файла по пути.
|
||||
- Копия не попадает в списки каталога; оставшиеся после вылета копии удаляются при монтировании.
|
||||
- `param.bin` и `icon0.png` записываются так же (временный файл, `fsync`, `rename`).
|
||||
- `BB_SAVE_TRACE=1` — все операции игры с файлами сохранений в журнал.
|
||||
|
||||
## Подготовка файлов игры и геймпад
|
||||
|
||||
- `prepare.py` и остальные скрипты, лаунчер: текстовые файлы читаются и пишутся в UTF-8 явно, вывод в
|
||||
консоль не падает на символах вне её кодировки (#55: китайская, японская, корейская локаль Windows,
|
||||
`™` в названии игры).
|
||||
- `eboot.bin` (и модули `sce_module`) принимаются и обычным ELF, без обёртки SELF: так их пишут
|
||||
некоторые программы дампа (#49). Проверено: ELF, извлечённый из нашего eboot, даёт тот же `boot.bin`
|
||||
байт в байт.
|
||||
- Стики (PR #71; #70): клон Switch Pro, у которого все четыре оси в покое смещены на
|
||||
~±16380 и ход вдвое меньше, калибруется — нейтраль берётся из спокойного окна после подключения,
|
||||
ход каждой оси — по её собственному диапазону. Мёртвая зона как в shadPS4: `BB_PAD_DEADZONE`
|
||||
(по умолчанию 5 из 127) и `BB_PAD_DEADZONE_OUTER` (127). `BB_PAD_CENTER=lx,ly,rx,ry` — нейтраль
|
||||
вручную, `BB_PAD_CENTER_CAL=0` — без замера.
|
||||
Отличие от PR: нейтраль принимается, только если смещены все четыре оси (иначе обычный геймпад,
|
||||
подключённый с зажатым стиком, запомнил бы этот наклон как нейтраль до переподключения); пока
|
||||
нейтраль не известна, стики читаются как раньше, а не стоят на месте.
|
||||
- README.ru.md: адрес репозитория в команде `git clone` (PR #43; в английском README он уже был).
|
||||
|
||||
## Зависания видеочипа: непрямые запуски, VRAM, сведения о видеокарте
|
||||
|
||||
- Непрямые запуски (#34, комментарий к #32; новая модель памяти): дважды игра теряла устройство на
|
||||
симуляции частиц, получившей вместо числа групп мусор (1,2 и 3,8 млрд групп; у нас в логах от
|
||||
2026-10-06 такое тоже было — 1,4 млрд и 6,4 млн, обошлось). Настоящие числа у Bloodborne — не больше
|
||||
нескольких сотен. Теперь перед каждым непрямым запуском шейдер из одного потока
|
||||
(`indirect_guard.comp`) копирует числа групп в наш буфер, обнуляя неправдоподобные (больше
|
||||
пределов устройства или `BB_INDIRECT_GUARD_MAX` групп всего, по умолчанию 4M), и запуск идёт из
|
||||
этой копии. Худшее, что теперь бывает, — пропущенный шаг частиц вместо вылета; пропуски
|
||||
печатаются строкой `Indirect dispatches: N skipped ...`. `BB_INDIRECT_GUARD=0` — как раньше.
|
||||
Причина мусора в памяти игры не найдена (только новая модель, редко). Цена: в пределах шума
|
||||
(183–184 FPS, видеочип 5,42–5,47 мс в одной сцене). Проверено с порогом 10 (пропуски
|
||||
срабатывают, игра идёт) и под слоем валидации (новых ошибок нет).
|
||||
- Перенос блоков памяти игры в VRAM теперь учитывает бюджет видеопамяти драйвера: если занято больше
|
||||
`BB_VRAM_PROMOTE_PERCENT` (по умолчанию 90 %), новые копии не создаются — блоки читаются на месте
|
||||
(медленнее, но без переполнения VRAM на картах с 8 ГБ), а перенос возобновляется, когда память
|
||||
освободится. Строка в логе при переключении. Число «MiB of loaded blocks moved ... to VRAM» —
|
||||
накопительное (сколько перенесено за сессию), а не занятое место; занятое — в строке `Memory:`.
|
||||
- В начале лога теперь строка с видеокартой, объёмом VRAM, драйвером и версией Vulkan (в отчётах
|
||||
о зависаниях, как #56, её не было).
|
||||
- #56 (старая модель, зависание при загрузке карты): два лога — два разных места остановки (обычная
|
||||
отрисовка из 5 индексов и копирования между проходами), шейдер из первого у нас работает 4–12 раз
|
||||
за кадр без проблем, те же патчи (Disable DoF, Motion Blur) у нас включены. Воспроизвести не
|
||||
удалось; без модели видеокарты и версии Mesa дальше не продвинуться (теперь они будут в логе).
|
||||
|
||||
## Баги из issues (2026-10-08, день)
|
||||
|
||||
- **Сборка FSR 4.1.1 там, где контейнер Steam не запускается (#61).** Bazzite + 9070 XT: ни один
|
||||
Proton «не запустил FSR 4.1», причина в списке была пустой, а в конце лога — `bwrap: No permissions
|
||||
to create a new namespace`: контейнер Steam (pressure-vessel, и под umu-run тоже) не может создать
|
||||
пространство имён — так бывает, когда сам лаунчер запущен в песочнице (Flatpak, ярлык Steam с
|
||||
рантаймом) или ядро это запрещает. Теперь при такой ошибке Proton запускается напрямую, без
|
||||
контейнера (сообщение `Steam's container could not start here (...): running Proton without it`,
|
||||
дальше до конца сборки так же); `BB_FSR4CAP_RUNNER=direct` — сразу так.
|
||||
Причина отказа каждого Proton — последняя непустая строка журнала записи, иначе ошибка контейнера
|
||||
или Wine (раньше — последняя строка, часто пустая). Один и тот же Proton больше не пробуется дважды
|
||||
по путям `/home/...` и `/var/home/...` (Fedora Atomic): одинаковые папки узнаются по устройству и
|
||||
inode, а не по `realpath`.
|
||||
Проверено: запись внутри `bwrap --disable-userns` (та же ошибка контейнера) — запасной путь
|
||||
записал кадры FSR 4.1.1 под GE-Proton 11-6, результат совпадает с обычной записью побайтно (кроме
|
||||
адресов указателей в `trace.txt`).
|
||||
- **MangoHud снова работает с системным MangoHud (#66).** С 0.3 `run.sh` при системном MangoHud
|
||||
отключал встроенный в AppImage слой по имени (`VK_LOADER_LAYERS_DISABLE`), но у системного слоя
|
||||
обычно то же имя (`VK_LAYER_MANGOHUD_overlay_64_x86_64`) — загрузчик Vulkan отключал оба. Теперь
|
||||
встроенный MangoHud убирается из путей поиска слоёв (`XDG_DATA_DIRS`), а системный остаётся.
|
||||
Проверено загрузчиком Vulkan (`VK_LOADER_DEBUG=layer`): раньше «forced disabled», теперь слой
|
||||
вставляется.
|
||||
- **Картинка предмета на экране загрузки не пропадает (#67, патч bmy).** Маски Scaleform рисуются
|
||||
без записи цвета; при выводе не 1080p они попадали в глубину игры, а следующая отрисовка UI
|
||||
переключалась на глубину UI размера вывода с очищенным трафаретом — картинка не проходила тест.
|
||||
Теперь отрисовки UI без цели цвета тоже идут в глубину UI. В игре не проверено (на macOS у автора —
|
||||
да).
|
||||
- **Выбор монитора (#69).** Окно и полноэкранный режим шли на монитор, который SDL считает основным.
|
||||
*Экран → Монитор* в лаунчере (`BB_DISPLAY`: номер или часть имени; список —
|
||||
`bb-gpu-capabilities --displays`), в логе при запуске — строки `Display N: ...` с отметкой
|
||||
выбранного. Проверено на Hyprland с двумя мониторами: полноэкранное окно открывается на выбранном
|
||||
(обычное окно на Wayland размещает композитор).
|
||||
- **Дебаг-меню: шрифты из мода находятся.** Патч дебаг-меню читает шрифты из
|
||||
`dvdroot_ps4/adhoc/font/DbgFont14h.ccm` и `.tpf` (и шейдеры `adhoc/FontShader/debugFont_*`),
|
||||
причём игра запрашивает имена в нижнем регистре (`dbgfont14h.ccm`, найдено с новым
|
||||
`BB_FILE_TRACE=1`). Проверка в `patches.py` искала их в `dvdroot_ps4/font` с точным регистром, а
|
||||
рантайм на Linux различал регистр — шрифты не находились ни модом, ни в дампе, и запуск падал с
|
||||
трассировкой Python. Теперь: рантайм, не найдя файл игры, ищет его без учёта регистра (как PS4 и
|
||||
shadPS4; только при промахе, без затрат для найденных файлов); проверка смотрит в `adhoc/`
|
||||
без учёта регистра; без шрифтов патч дебаг-меню не применяется (строка `Patches: debug menu off:
|
||||
...`, подсказка, если шрифты лежат в старом `dvdroot_ps4/font`), игра запускается; папка `adhoc`
|
||||
в моде узнаётся как папка игры. Проверено: мод `Adhoc/Font/DbgFont14h.*` открывается игрой
|
||||
(`Mods: open /app0/dvdroot_ps4/adhoc/font/dbgfont14h.ccm -> .../Adhoc/Font/DbgFont14h.ccm`).
|
||||
С настоящими шрифтами меню не проверено (их нет).
|
||||
- `BB_FILE_TRACE=1`: каждый путь, который игра открывает, проверяет или читает (`File trace: ...`).
|
||||
- **Экрана «Играть по сети / вне сети» больше нет.** У порта нет PSN; теперь игра сразу открывает
|
||||
главное меню в режиме вне сети — при запуске и после «Выйти из игры», с сохранением и без. Патч игры
|
||||
«Skip Online/Offline Choice» (`patches/Bloodborne.xml`): функция, которая строит этот диалог
|
||||
(`0x1b39030`, найдена по ID строк 401010/401011 из `menu.msgbnd`), возвращает то же, что вернул бы
|
||||
её пункт «ИГРАТЬ ВНЕ СЕТИ» (вызов его обработчика `0x1b49cd0`, vtable `0x533be30`): следующий
|
||||
диалог цепочки, так что выбор не показывается. Включается `patches.py` по умолчанию (лаунчер:
|
||||
*Эффекты игры → Пропускать выбор*, `BB_SKIP_NETWORK_CHOICE=0` — показывать). Прежний вариант
|
||||
(порт нажимал «крест», экран мелькал) удалён. `tools/restart.sh` по-прежнему показывает выбор
|
||||
(его сценарий сам нажимает на титуле). Проверено скриншотами: запуск без ввода — сразу главное
|
||||
меню; «Продолжить» → «Выйти из игры» — сразу главное меню.
|
||||
- Память при повторной загрузке локации (6 циклов «Продолжить → выйти», новая модель): RSS
|
||||
707 → 733 МиБ, VRAM выходит на ~3,7 ГБ; старая модель (4 цикла): RSS 3735 → 3795 МиБ, VRAM ~3 ГБ.
|
||||
Утечки на гигабайты (сообщение из Discord про телепорты) так не видно; телепорт между разными
|
||||
локациями не проверен.
|
||||
- **Нехватка видеопамяти у копий памяти игры не роняет игру.** Старая модель держит копии памяти
|
||||
игры в блоках VRAM по 64 МиБ; на картах с 4 ГБ (RTX 3050 mobile, сообщение из Discord) выделение
|
||||
блока могло вернуть `ErrorOutOfDeviceMemory`, и игра останавливалась (`vk_platform.h:73`, код 23).
|
||||
Теперь такой блок берётся из системной памяти, которую GPU читает по шине (строка `Guest memory:
|
||||
VRAM is full ...`): медленнее, но без вылета. `BB_RESIDENCY_VRAM_LIMIT_MB=N` — проверка (VRAM
|
||||
«кончается» после N МиБ блоков). Проверено с 32 МиБ: игра идёт, картинка верна, 28 FPS (всё в
|
||||
системной памяти; в жизни туда уходит только излишек). То ли это место, где упал отчёт из
|
||||
Discord, не доказано — строки перед остановкой в нём нет.
|
||||
- **«Чтение данных GPU: Precise» больше не замораживает первую катсцену.** Новая игра → катсцена
|
||||
стояла на первом кадре (звук шёл): видеоплеер писал кадр ролика в память, которую GPU писал
|
||||
раньше; обработчик попросил поток GPU о чтении, а тот, снимая копию байтов игры для «двойников»
|
||||
(`BB_GPU_WRITE_TWINS`) под их мьютексом, сам попал на защищённую от чтения страницу и в
|
||||
обработчике ждал тот же мьютекс. Найдено по стекам потоков (SIGUSR2). Теперь байты игры для
|
||||
«двойников» читаются через вид без защиты (`ReadBacking`), а с `BB_READBACKS=2` «двойники»
|
||||
выключены: страница с байтами GPU, в которую игре можно писать, была бы «только для записи»
|
||||
(следующей остановкой был `page_manager.cpp: ... write-only`). Проверено: новая игра с
|
||||
`BB_READBACKS=2` и без — катсцена идёт до создания персонажа.
|
||||
- **Меню настроек — как в ПК-играх, в стиле Bloodborne.** Вместо окна ImGui поверх игры
|
||||
(Insert / L3+R3): игра затемнена, по центру панель с орнаментными линиями, как у диалогов самой
|
||||
игры (шрифт с засечками DejaVu Serif, встроен рядом с DejaVu Sans), заголовок и вкладки
|
||||
«Изображение / Апскейлер / Эффекты игры / Дополнительно» (L1/R1, Q/E, PageUp/PageDown, мышь).
|
||||
Каждая настройка — строка «название ◀ значение ▶»: вверх/вниз (крестовина, стик, стрелки) —
|
||||
выбор строки, влево/вправо — значение, Крест/Enter — следующее значение, мышь — клик по
|
||||
стрелкам или строке, ползунки тянутся мышью. Описание выбранной настройки — внизу (вместо «(?)»),
|
||||
там же предупреждение о перезапуске; строка «Применить и перезапустить игру» — когда нужно.
|
||||
Подсказки кнопок — в подвале; Esc / Круг / Insert / L3+R3 — закрыть. Недоступные значения
|
||||
(FSR 4 без поддержки) пропускаются, зависимые строки гаснут (пресет при TAA, сила резкости без
|
||||
резкости, маска реактивности). Фокус — свой (навигация ImGui до строк не доходила).
|
||||
`BB_MENU_KEYS_FILE=<файл>` — сценарное управление меню для проверок (`toggle up down left right
|
||||
enter back l1 r1 kup kdown`).
|
||||
- **Настройки порта — родные страницы в меню игры «Система».** После «Экран/звук» появились пункты
|
||||
«Изображение», «Эффекты игры» и «Патчи игры»: их строит и рисует сама игра (её строки, списки,
|
||||
ползунок, подсказка внизу, «По умолчанию» с подтверждением), и на титульном экране, и в паузе.
|
||||
«Изображение»: разрешение вывода, апскейлер (в списке только то, что эта видеокарта и сборка
|
||||
умеют: без NVIDIA нет DLSS), пресет, резкость (ползунок 0–10, 0 — выкл, 10 = 2.0), счётчик FPS —
|
||||
меняются сразу и пишутся в `bbport.ini`. «Эффекты игры» / «Патчи игры»: детализация моделей и
|
||||
патчи-эффекты (после перезапуска). «По умолчанию» ставит значения порта по умолчанию. Тексты на
|
||||
языке самой игры (русский — если её тексты на кириллице, иначе английский). Как устроено
|
||||
(`gpu/shim/bbport_game_menu.cpp`): перехват поиска текстов игры (свои id 0x7F0000+) и добавления
|
||||
пункта в меню (0x1b4e2a0, после «Экран/звук»); страница — макет «ControllSetting» со своими
|
||||
строками и заголовком; последний аргумент строк игры — указатель на значение по умолчанию.
|
||||
Каждая перехваченная функция сверяется побайтно, `BB_GAME_MENU=0` — выключить. Проверено: 16
|
||||
открытий страниц — рост памяти как у родных страниц игры; оверлей (Insert / L3+R3) остался.
|
||||
- Ровность 60 FPS со временем (сообщение из Discord: «через некоторое время кадры неровные»):
|
||||
6 минут стоя, `BB_FPS=60` — каждую минуту 16,67 мс, разброс 0,00–0,01 мс, ни одного кадра
|
||||
дольше 20 мс (кроме загрузки в первой минуте). На RX 7800 XT не воспроизводится.
|
||||
|
||||
## Влитые PR (2026-10-08, вечер)
|
||||
|
||||
- **PR #84: DLC.** Папки `<user>/addcont/<title>/<метка>` (как у shadPS4) сообщаются игре
|
||||
как установленные дополнения: сохранения с The Old Hunters загружаются, области DLC доступны
|
||||
(#33). Данные дополнений не монтируются — The Old Hunters нужна только метка, данные в патче
|
||||
1.09. При вливании: `_GNU_SOURCE` под `#ifndef` (тест собирает с `-D_GNU_SOURCE` и `-Werror`), в
|
||||
тест AppContent — заглушка папки пользователя и проверка: папка `addcont/CUSA00900/SPEXPANSIONDLC03`
|
||||
даёт одну запись «установлено».
|
||||
- **PR #80:** `BB_FSR4_STATS=1` больше не ломает пайплайны FSR 4 v07 без
|
||||
`VK_EXT_pipeline_executable_statistics`. Патч к подмодулю FSR-Vulkan изменился: рабочую копию
|
||||
подмодуля нужно вернуть (`git -C gpu/third_party/fsr-vulkan checkout -- src/ffx_vk_fsr4_v07.c`), и
|
||||
`build.sh` наложит новую версию.
|
||||
- **PR #78:** лаунчер — кнопки «Копировать» и «Экспорт лога…» на вкладке «Журнал».
|
||||
- **PR #75:** лаунчер на упрощённом китайском (строки, добавленные сегодня, — на
|
||||
английском до перевода).
|
||||
- **PR #83: DLSS на Linux.** Мост `gpu/dlss_bridge` (MIT, единственный код с SDK NVIDIA)
|
||||
собирается с `DLSS_SDK_ROOT=<github.com/NVIDIA/DLSS> bash build.sh` в `out/libbbport_dlss.so`,
|
||||
рядом копируется библиотека NVIDIA; игра загружает мост при запуске. Новый апскейлер `dlss`
|
||||
(лаунчер, меню, `bbport.ini`): те же входы, что у FSR 3.1, Native AA = DLAA, при ошибке — FSR 3.1.
|
||||
Без моста и библиотеки, на не-NVIDIA или с `BB_DLSS=0` ничего не меняется (DLSS в меню недоступен).
|
||||
При вливании: пункт DLSS и его описание перенесены в новое меню, строка в списке апскейлеров
|
||||
лаунчера — после строки языков PR #75. Проверено здесь: сборка без SDK, игра на AMD (FSR 4.1.1,
|
||||
FSR 4 v07 со статистикой). С DLSS не проверено (нет NVIDIA); автор — RTX 4090. В AppImage мост и
|
||||
библиотека NVIDIA не входят.
|
||||
|
||||
## После 0.5-pre1 (2026-10-09)
|
||||
|
||||
- **Новая модель памяти снова только для AMD.** Лог с GTX 1660 Ti (драйвер 615.71, Hyprland):
|
||||
импорт памяти хоста прошёл проверку при запуске, но после первого переноса блоков в VRAM
|
||||
(`84 MiB of loaded blocks moved`) — `GPU watchdog: submission 4114 not finished after 10 s`:
|
||||
видеочип закончил шейдер копирования и не начинал следующий запуск. Отправка ждёт семафор
|
||||
перепривязки памяти (`vkQueueBindSparse` арены), и на NVIDIA каждая такая перепривязка держала
|
||||
видеочип 10 секунд и дольше; рабочий стол вставал вместе с ним, со стороны — «зависает вся
|
||||
система». Проверка при запуске этого не ловит (там одна маленькая привязка). Теперь на NVIDIA
|
||||
переключатель недоступен, у кого он был включён — старая модель (лаунчер передаёт
|
||||
`BB_PC_MODEL=0`, библиотека GPU тоже не включает её на NVIDIA); `BB_PC_MODEL_ANY_GPU=1` — всё же
|
||||
попробовать. README и тексты лаунчера исправлены.
|
||||
- `BB_RESIDENCY_REPORT=<файл>` (диагностика новой модели): когда файл появляется, в журнал —
|
||||
блоки памяти игры, которые видеочип недавно читал на месте, а не из VRAM, и почему они не
|
||||
перенесены (процессор пишет часто; байты, о записи которых мы не знаем; ждут переноса).
|
||||
У лампы в Кошмаре охотника: ~1,2 ГБ таких блоков памяти Garlic целиком «неизвестны».
|
||||
- Сообщение «первая загрузка медленнее, после телепорта 90 FPS» (RX 7900 XTX, новая модель): здесь
|
||||
не воспроизвелось — первая и повторная загрузка (выход в титул, «Продолжить») 131,7 и 131,9 FPS
|
||||
на RX 7800 XT; переносы в VRAM после загрузки заканчиваются за секунды. Нужен лог тестера.
|
||||
|
||||
## Новая модель на NVIDIA: разбор (2026-10-09, продолжение)
|
||||
|
||||
- **Причина зависания найдена.** Это известная регрессия проприетарного драйвера NVIDIA на Linux
|
||||
(с 555 и до сих пор, у тестера 615): время `vkQueueBindSparse` растёт с числом страниц, **уже**
|
||||
привязанных во всех sparse-ресурсах, и вызов держит глобальную блокировку (форум разработчиков
|
||||
NVIDIA, «Sparse texture binding is painfully slow»: 9200 страниц — 3,6 с; исправления нет).
|
||||
В арене новой модели — гигабайты страниц по 64 КиБ (у тестера 863 МиБ привязано на месте ещё
|
||||
до первых переносов), так что каждый перенос блоков в VRAM стоил секунды. Игра сама заметила
|
||||
остановку видеочипа ещё до первого переноса («Stall during rendering at flush 1»).
|
||||
- **Почему не обойти «одной правкой».** Замер `BB_RESIDENCY_MIX=1` (новая диагностика): ~17 тыс.
|
||||
привязок в секунду (~85 за кадр) — диапазоны в сотни МБ, где одновременно блоки в VRAM и на
|
||||
месте (шейдеры, привязанные ко всей памяти; треть — поверх данных, записанных видеочипом).
|
||||
Одна sparse-арена смешивает их бесплатно; схема «VRAM-арена + отдельные буферы памяти игры»
|
||||
их не обслужит. Без перепривязок вообще (`BB_FIXED_ARENA=1`, всё читается на месте через шину):
|
||||
**48,6 FPS против ~205** на RX 7800 XT — хуже старой модели.
|
||||
- Итог: новая модель остаётся только для AMD. Для NVIDIA нужна либо починка драйвера (или
|
||||
Mesa NVK, где sparse работает нормально), либо программная подкачка страниц в транслированных
|
||||
шейдерах вместо sparse — многодневная переделка.
|
||||
- Добавлено: `BB_FIXED_ARENA=1/0` (арена держит первые привязки: без переносов в VRAM, блоки вне
|
||||
кусков памяти игры — в системной памяти; по умолчанию — при импорте памяти хоста, т. е. на NVIDIA
|
||||
с `BB_PC_MODEL_ANY_GPU=1`); строка в журнале `vkQueueBindSparse of N ranges took X ms` (дольше
|
||||
50 мс, в обеих моделях); в отчёте сторожа видеочипа — `Arena binds: ...` (ждут ли отправки
|
||||
незавершённую привязку); `BB_RESIDENCY_MIX=1` — привязки «на месте / VRAM / смешанные» за 2 с.
|
||||
|
||||
## Новая модель на всех видеокартах: модуль памяти прослойки (2026-10-09)
|
||||
|
||||
- **Новая модель включается на любой видеокарте.** На AMD — как раньше, sparse-арена. На NVIDIA,
|
||||
Intel и остальных — модуль памяти прослойки (`gpu/layer`, docs/MEMORY_MODULE_PLAN.ru.md): память
|
||||
игры привязывается на месте через буферы её кусков, копии в VRAM — отдельные буферы («зеркала»),
|
||||
перенос — копия плюс учёт в модуле. `vkQueueBindSparse`, из-за которого на NVIDIA зависали игра
|
||||
и рабочий стол, в этом режиме не вызывается ни разу. Вручную: `BB_LAYER_MEMORY=1/0`. Лаунчер
|
||||
больше не прячет переключатель на не-AMD; README и подсказка (три языка) исправлены. **На
|
||||
NVIDIA не проверено** — нужен лог тестера.
|
||||
- Замеры на RX 7800 XT, стоя: было 48–60 FPS в режиме модуля, стало ~205 (sparse ~205–238, старая
|
||||
модель ~220). В другом месте: модуль 167,5, sparse 164,5, старая модель 146. 5 минут ходьбы с
|
||||
подгрузкой областей — без ошибок, 166–240 FPS.
|
||||
- Что мешало (найдено по профилю видеочипа):
|
||||
- **HLE копий и очисток образов не срабатывало.** Детекторы `IsComputeImageCopy` /
|
||||
`IsComputeImageClear` требуют у шейдера ровно два буфера, а в режиме модуля каждому шейдеру
|
||||
с буферами игры добавлялся третий — таблица страниц. Копия памяти целей рендеринга
|
||||
(`cs 3d5ebf4e`) и очистка (`cs 8b355b5a`, 31 запуск за кадр) шли настоящими шейдерами через
|
||||
память, а образы перезагружались из неё каждый кадр (~16 образов 1080p вместо ~2,6).
|
||||
Теперь таблица страниц не считается (`ShaderBufferCount`): 60 → 136 FPS.
|
||||
- **Смешанные диапазоны.** Диапазон, у которого все блоки в VRAM, кроме одного с «неизвестными»
|
||||
байтами (край, промежуток между образами), целиком читался через PCIe (8 МБ на привязку).
|
||||
Теперь такой блок «летучий» в зеркале: обновляется из памяти игры перед каждой привязкой, то,
|
||||
что запуск записал, копируется обратно после запуска. 139 → 207 FPS. `BB_LAYER_VOLATILE=0` —
|
||||
выключить.
|
||||
- Перенос в VRAM при первой привязке диапазона известных данных (как делала арена); он
|
||||
соблюдает `BB_GARLIC_VRAM=0` и бюджет VRAM (`BB_VRAM_PROMOTE_PERCENT`, важно для карт с 6 ГБ).
|
||||
- Стартовая проверка импорта памяти хоста в режиме модуля проверяет запись видеочипа и процессора
|
||||
через сам импортированный буфер, без sparse-буфера.
|
||||
- Диагностика: `BB_RESIDENCY_MIX=2` — ещё и самые тяжёлые привязки на месте / смешанные с
|
||||
разбором блоков (VRAM, подходят, «динамические», неизвестные байты); в строке `Layer memory:` —
|
||||
число вызовов `vkQueueBindSparse` и байты, скопированные обратно.
|
||||
|
||||
## Модуль памяти: взрывы вершин и скорость (2026-10-09, день)
|
||||
|
||||
Итог работы над новой моделью для NVIDIA — в `docs/NVIDIA_MEMORY_MODEL.ru.md`: ловушки записи на
|
||||
блоках с копией в VRAM, чтения через модуль везде, где есть копия, данные процессора больше не
|
||||
затираются данными видеочипа, запись видеочипа в VRAM только в блоки, которые процессор не трогал
|
||||
(`BB_LAYER_GPU_WRITES`), шейдер копирования в режиме модуля через HLE, кэши ответов модуля.
|
||||
|
||||
## 0.5-pre2: оригинальное GPU-копирование и переносимые метки
|
||||
|
||||
В итоговой сборке шейдер копирования в новой модели теперь нативный по умолчанию: его записи
|
||||
согласованы между памятью игры и зеркалами VRAM. Добавлены стандартные Vulkan-метки завершения
|
||||
для GPU без `VK_AMD_buffer_marker`, исправлены зависимости записи/чтения буферов и миграция
|
||||
всего кэша при изменении формата. Подробное описание релиза и проверок:
|
||||
[CHANGES_0.5-pre2.md](CHANGES_0.5-pre2.md).
|
||||
|
||||
## 0.5-pre3: модуль памяти на NVIDIA
|
||||
|
||||
По первым логам с GTX 1660 Ti: копии в VRAM снова включены для модуля (их выключал обход
|
||||
sparse-арены), общий бюджет VRAM с кэшем текстур, запас роста копий вместо их перевыделения,
|
||||
записи WRITE_DATA в копию, наблюдаемые блоки (`BB_LAYER_WATCH`), повтор чтения файлов при EFAULT.
|
||||
Подробности: [CHANGES_0.5-pre3.md](CHANGES_0.5-pre3.md) и
|
||||
[NVIDIA_MEMORY_MODEL.ru.md](NVIDIA_MEMORY_MODEL.ru.md).
|
||||
|
||||
## 0.5-pre4: компиляция шейдеров в фоне, VRAM на картах 4–6 ГБ, меню как в 0.3
|
||||
|
||||
Новые конвейеры проходов, рисуемых каждый кадр, компилируются в фоновых потоках
|
||||
(`BB_ASYNC_PIPELINES`); у копий памяти игры в VRAM своя доля бюджета (регрессия pre3 на картах
|
||||
4 ГБ); после перемещений VRAM освобождается от копий покинутой локации; меню настроек в игре —
|
||||
снова одно окно, как в 0.3 и 0.4. Подробности: [CHANGES_0.5-pre4.md](CHANGES_0.5-pre4.md).
|
||||
@@ -1,159 +0,0 @@
|
||||
# Изменения после 0.5 (с 2026-10-10)
|
||||
|
||||
Замеры — RX 7800 XT / RADV, сохранение пользователя (Соборный округ), 1080p.
|
||||
|
||||
## Все розничные издания получают профиль игры (#119)
|
||||
|
||||
- Профиль Bloodborne (`gpu/games/bloodborne.cpp`) выбирался только для CUSA03173. Американское
|
||||
издание (CUSA00900) и остальные регионы запускают тот же исполняемый файл 1.09, но шли без
|
||||
профиля, и новая модель падала при входе в мир: `Guest fault ... at guest offset 0x2085xxx,
|
||||
address (nil)` в аллокаторе игры (#119, RX 7600, US-издание).
|
||||
- Воспроизведено у себя на CUSA03173 с `BB_GAME_PROFILE=none`: то же падение (`0x208596a`) при
|
||||
входе в мир. С профилем, но без перехватов (`BB_GUEST_HOOKS=0`) — не падает: спасает
|
||||
`DynamicWriters` — код аллокатора, чьи служебные данные лежат в памяти GPU; без этой отметки
|
||||
их блоки уходят в VRAM-копию.
|
||||
- Профиль, проверка игры (`scripts/game_check.py`), патчи (`scripts/patches.py`), «звуковой
|
||||
фикс» сохранений и папки модов теперь знают все издания: CUSA00900, CUSA00207, CUSA00208,
|
||||
CUSA00299, CUSA01363 и издания с The Old Hunters CUSA03179, CUSA03173, CUSA03014, CUSA03023.
|
||||
- Проверка игры: другие регионы базовой игры без обновления просят 1.09 вместо «поддерживается
|
||||
только CUSA03173»; eboot.bin со вшитым патчем 60 FPS (падает в меню жестов) — отдельное
|
||||
сообщение (PR #117).
|
||||
- Осталось: общий путь без профиля должен не падать сам (служебные данные аллокатора в
|
||||
VRAM-копии). Сейчас он нужен только для чужих игр.
|
||||
|
||||
## Размытие в движении на весь кадр в новой модели (#122)
|
||||
|
||||
- С включённым размытием в движении (по умолчанию) кадр при повороте камеры размазывался целиком,
|
||||
в старой модели — нет (#122, RX 9060 XT). Воспроизведено: резкость кадра во время поворота
|
||||
(стандартное отклонение лапласиана) 6,9 против 8,5–12 в старой модели.
|
||||
- Поиск: `BB_GARLIC_VRAM_RANGE=начало,конец` (новый диагностический ключ: VRAM-копии только в
|
||||
этом диапазоне) и деление диапазона пополам — блок 64 КиБ `0x104ff00000`. Его читает только
|
||||
видеочип (буфер 11,4 МиБ каждый кадр), записей буферами и процессором нет: это память
|
||||
рендер-таргета, который игра потом читает как буфер.
|
||||
- Причина: выгрузка изображения в память игры шла через staging и отложенную запись процессора,
|
||||
когда у этой памяти была VRAM-копия; копия обновлялась лишь после той записи (инвалидацией) —
|
||||
буфер читал данные прошлых кадров. Теперь видеочип копирует изображение и в арену (VRAM-копию)
|
||||
в порядке потока; в память игры — как раньше. И `IsRegionGpuModified` считает изменённым то,
|
||||
что видеочип записал в VRAM-копию (`gpu_written_bytes`, не на месте): без этого обновление после
|
||||
записи процессора поверх затирало копию старыми байтами (`BB_GPU_DATA_CHECK=0` — как раньше).
|
||||
Нужны обе правки. После: 11,9–13,0, как в старой модели; FPS тот же (127–131 в обоих
|
||||
вариантах, по три запуска).
|
||||
|
||||
## Нехватка VRAM на 4 ГБ больше не останавливает игру
|
||||
|
||||
- RTX 3050 4 ГБ, старая модель (Авто на NVIDIA), 0.5: три падения за вечер —
|
||||
`Failed to allocate 64 MiB for guest memory copies: ErrorOutOfDeviceMemory`. Запасного типа
|
||||
памяти (системной) для арены нет: разреженные буферы NVIDIA берут только VRAM. В pre5
|
||||
сборщик текстур стал держать текстуры по 5 с (FPS не падал со временем), и на 4 ГБ VRAM
|
||||
заканчивалась.
|
||||
- Теперь: блок поменьше (ровно запрошенный), затем любой другой тип памяти, к которому можно
|
||||
привязать арену; и 30 с сборщик под давлением снова выселяет по отправкам, как до 0.5. Он же
|
||||
делает так сам, когда занято больше 92 % бюджета драйвера (`VK_EXT_memory_budget`, раз в
|
||||
секунду). Проверить можно только на такой карте.
|
||||
|
||||
## Выход из игры с геймпада (#74, #116)
|
||||
|
||||
- Оверлей (Insert или L3+R3): кнопка «Выйти из игры», второе нажатие подтверждает; игра
|
||||
закрывается как по кнопке окна.
|
||||
|
||||
## Мышь
|
||||
|
||||
- Камера мышью: щелчок в окне игры захватывает мышь (относительный режим SDL), F1 или уход из
|
||||
окна — отпускает; меню настроек и ввод имени тоже отпускают. Движение мыши — правый стик:
|
||||
отклонение по скорости мыши (~1000 пикселей/с — полный поворот при чувствительности 1), за
|
||||
60 мс спадает; движущаяся мышь сразу проходит мёртвую зону игры (`BB_MOUSE_DEADZONE`, 16 из
|
||||
127), так что медленное прицеливание тоже поворачивает камеру. Одинаково при любом FPS.
|
||||
- Кнопки мыши и колесо — те же «клавиши» в привязках: `Mouse Left` R1, `Mouse Right` L2 (огнестрел),
|
||||
`Mouse Middle` R3 (захват цели), `Mouse X1` R2, `Mouse X2` L1, колесо — крестовина вверх/вниз
|
||||
(быстрые предметы); каждое деление колеса — короткое нажатие. Кнопки работают только при
|
||||
захваченной мыши: щелчок, который её захватывает, не атакует.
|
||||
- bbport.ini: `mouse_look=0/1`, `mouse_sensitivity` (0.05–20), `mouse_invert_y`; лаунчер —
|
||||
*Управление*: «Камера мышью», чувствительность, инверсия; «Клавиатура и мышь» — назначение
|
||||
кнопок, «Назначить» принимает и кнопку мыши/колесо. Основа — PR #108 (виртуальный стик от
|
||||
движения мыши); раскладка клавиатуры не менялась.
|
||||
|
||||
## Прочее
|
||||
|
||||
- Swapchain пересоздаётся через `oldSwapchain` (PR #93): на NVIDIA под Wayland пересоздание
|
||||
падало с «Surface already has a tearing controller».
|
||||
- `launcher/bb-launcher.sh` передаёт аргументы (`--play`) и через nix-shell (PR #120).
|
||||
- Запись FSR 4.1.1 из AppImage на SteamOS и других дистрибутивах (PR #89): запись на хосте
|
||||
через systemd, повтор с GPU, представленным как RX 7800 XT, если DLL не предлагает FSR 4.1.
|
||||
- Лаунчер: у DLSS — подсказка, есть ли он в этой сборке (в AppImage его нет: DLSS SDK от NVIDIA
|
||||
не распространяется, игра включает FSR 3.1).
|
||||
|
||||
## DLSS в AppImage и своя библиотека NVIDIA
|
||||
|
||||
- Сборка с `DLSS_SDK_ROOT` (DLSS SDK 310.9.1 с github.com/NVIDIA/DLSS) кладёт в пакет мост
|
||||
`libbbport_dlss.so`, `libnvidia-ngx-dlss.so.310.9.1` и `NVIDIA-DLSS-LICENSE.txt` (лицензия RTX
|
||||
SDK разрешает распространение в составе приложения, не отдельно). Тестовый AppImage:
|
||||
`dist/…-test-1010-dlss.AppImage`.
|
||||
- Лаунчер, *Апскейлер → DLSS: библиотека NVIDIA → Выбрать файл…* — как DLL для FSR 4.1.1:
|
||||
`libnvidia-ngx-dlss.so.<версия>` копируется в `<user>/dlss/`, игра берёт её раньше встроенной
|
||||
(лог: `DLSS: NVIDIA's library from …`). Выбранная `nvngx_dlss.dll` — подсказка, что нужна
|
||||
библиотека для Linux. Мост ищется рядом с `bb-probe`, затем в `<user>/dlss/`.
|
||||
- Мост собирается и с защитными флагами Nix (`-Werror=format-security`).
|
||||
- Проверено на RX 7800 XT только до «not an NVIDIA GPU»: путь к библиотеке (встроенной и
|
||||
своей) выбирается верно. Сам DLSS — на карте NVIDIA RTX.
|
||||
|
||||
## Сборка из git после обновления с 0.4 (#124)
|
||||
|
||||
- `build.sh` останавливался: «patch failed: src/ffx_vk_fsr4_v07.c:35» — в подмодуле FSR-Vulkan
|
||||
оставался применённым патч прошлой версии, а новый не применялся ни вперёд, ни назад. Теперь
|
||||
подмодуль в этом случае сбрасывается и патчи применяются заново (проверено на копии в таком
|
||||
состоянии).
|
||||
|
||||
## Онлайн (shadNet) — отдельным модулем
|
||||
|
||||
- `gpu/bbnet` → `out/gpu/libbbnet.so`: сетевые и PSN-библиотеки shadPS4 из shadp2p (форк shadPS4,
|
||||
GPL-2.0) с клиентом shadNet, перенесены из Linux-форка bbport с онлайном (а тот — из Windows-порта).
|
||||
Модуль собирается отдельной целью, его C++ скрыт (`-fvisibility=hidden`, `-Bsymbolic`), наружу —
|
||||
только `bbnet_init` и `bbnet_resolve` (`gpu/bbnet/bbnet.h`). Свои заголовки модуля
|
||||
(`gpu/bbnet/include`) только дополняют общие: в `gpu/shim` и `gpu/shadps4` ничего не менялось.
|
||||
- Рантайм (`src/runtime_net.c`): с `BB_ONLINE=1` на первом сетевом/PSN-импорте загружает модуль
|
||||
(`dlopen`, `RTLD_LOCAL`; `BB_NET_MODULE` — другой путь), отдаёт ему данные игры и гостевые потоки
|
||||
для обратных вызовов NP; трофеи, голос и диалоги магазина остаются в рантайме. Без `BB_ONLINE`
|
||||
модуль не загружается; без модуля или если он не стартовал — офлайн, как раньше.
|
||||
- Сборка: protobuf, nlohmann_json, OpenSSL, zlib, miniupnpc (`shell.nix`); без них модуль не
|
||||
собирается (`BB_NET=OFF` — вручную), порт собирается как раньше. AppImage содержит модуль.
|
||||
- `run.sh`: перенаправления адресов (`scripts/online.py`: сервер игры → The Hunter's Dream,
|
||||
знаки призыва → WebAPI shadNet), сервер shadNet по умолчанию `srv.shadps4.net:31313`.
|
||||
- Лаунчер: страница *Онлайн* (сервер игры, сервер кооператива и WebAPI, учётная запись shadNet,
|
||||
UPnP, проверка соединения); `settings.json` — только для владельца (0600, там пароль).
|
||||
- UPnP: порт P2P теперь открывается на роутере, когда транспорт готов, и закрывается при выходе
|
||||
(#121: `AddMapping` не вызывался нигде).
|
||||
- Проверено: `tests/test_online.c` (импорты сети и NP → модуль, трофеи → рантайм); игра с
|
||||
`BB_ONLINE=1` без учётной записи доходит до уровня (модуль: 1399 функций; «no users connected
|
||||
to shadNet»). Вход в shadNet и призыв не проверены: нет учётной записи, а UDP-часть публичного
|
||||
сервера, по #121, не отвечает.
|
||||
- HTTPS в AppImage: «Не удалось получить информацию от сервера Bloodborne» — проверка сертификата
|
||||
The Hunter's Dream проваливалась (`SSL server verification failed`): OpenSSL из Nix берёт корневые
|
||||
сертификаты из `/etc/ssl/certs/ca-certificates.crt`, а на NixOS это ссылка в `/nix/store` хоста,
|
||||
которого внутри AppImage не видно. Обёртки пакета теперь задают `SSL_CERT_FILE` на свой пакет
|
||||
сертификатов (`nss-cacert`), если указанный не читается; `run.sh` из исходников берёт системный.
|
||||
Лаунчер: текст ошибки в проверке соединения не терялся из-за разметки (`<urlopen error …>`), значки
|
||||
— из темы Adwaita.
|
||||
|
||||
## Мерцание главного меню при выводе выше 1080p
|
||||
|
||||
- С живой сменой разрешения (вывод 3840×2160 на мониторе 2560×1080) главное меню мерцало: часть
|
||||
кадров показывалась в родных 1920×1080 игры, остальные — с интерфейсом в разрешении вывода
|
||||
(4 из 50 снимков). `BB_UI_TRACE=1` (новая диагностика: отрисовки кадров без интерфейса в
|
||||
разрешении вывода) показал: пока меню анимирует подсветку, игра рисует его другими шейдерами
|
||||
Scaleform (`bb4d5f8c`, `e94b5b06`, `9951d199`, `44072322`, `a334a845`), а не `09957251`; их не было в
|
||||
списке шейдеров интерфейса (`ui_composition.h`), и такие кадры не собирались в разрешении
|
||||
вывода. После: 0 из 80; в уровне картинка та же.
|
||||
|
||||
## AppImage меньше
|
||||
|
||||
- В пакете были весь Mesa с LLVM (~800 МБ: нужны только Vulkan-драйверы RADV и ANV), полный GCC
|
||||
и заголовки Vulkan (их пути в отладочной информации `bb-probe`), 32-битная половина MangoHud
|
||||
(вторые glibc, gtk3, icu, perl) и GStreamer у GTK4 лаунчера. Теперь: свои RADV (без LLVM, ACO)
|
||||
и ANV без остального Mesa, `bb-probe` без отладочной информации, MangoHud 64-битный, GTK4 без
|
||||
GStreamer, печати и tracker.
|
||||
- Ещё: SDL3 без зашитого zenity (тот тянул вторую GTK4 с GStreamer; теперь zenity хоста, если
|
||||
есть), libdecor без плагина GTK 3 (у SDL3 и у glfw MangoHud; рамки рисует его cairo-плагин),
|
||||
MangoHud без xdg-utils (Perl; `xdg-open` хоста). Сборка своей Mesa включает Dozen только потому,
|
||||
что рецепт nixpkgs ждёт его выход `spirv2dxil`; в пакет он не попадает.
|
||||
- Итог: AppImage 925 → 399 МБ, пакет без сжатия 2,85 → 1,06 ГБ. Проверено: лаунчер открывается,
|
||||
игра из пакета стартует на встроенном RADV до титульного экрана.
|
||||
@@ -30,8 +30,13 @@ 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).
|
||||
The Game of the Year and The Old Hunters editions include the DLC. With a 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 choose it in the launcher (**Setup → 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.
|
||||
|
||||
## Tested with this fork
|
||||
|
||||
@@ -41,8 +46,8 @@ On Linux (CachyOS, NVIDIA GeForce RTX 3060), with the US release (`CUSA00900`):
|
||||
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).
|
||||
- Every text and voice language the files have, through *Game text language* and *Voice
|
||||
language*: Spanish from Spain and Latin American Spanish among them.
|
||||
|
||||
The European Game of the Year edition (`CUSA03173`) is the release the original bbport is
|
||||
developed and tested with.
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
# Credits
|
||||
|
||||
Everyone whose work is in this fork, and every project it took something from. Everyone listed
|
||||
under *People* and *Help and testing* also appears in the launcher's **About** section. Components and their licenses: [bbport's notes](../BBPORT.md#credits-and-licenses).
|
||||
|
||||
## Projects
|
||||
|
||||
| Project | What this fork takes from it |
|
||||
|---|---|
|
||||
| [deadinside28/bloodborne_pc](https://github.com/deadinside28/bloodborne_pc) | **bbport**, the native Linux port everything here is built on. Its README is [here](../BBPORT.md). |
|
||||
| [Supermedo/bloodborne_pc](https://github.com/Supermedo/bloodborne_pc) | The Windows port: online play, the cheats page, Add to Steam, the field of view, the game's audio at full level and many fixes. |
|
||||
| [Awakenchan/LanternEngine](https://github.com/Awakenchan/LanternEngine) (formerly BloodBorneButPc) | Lantern Engine by Awaken (https://github.com/Awakenchan/LanternEngine) (GPL-3.0-or-later): crash fixes, stick options, the controller in use, save import, the version selector, and renderer fixes (vertex inputs, index buffers, camera motion vectors). |
|
||||
| [shadPS4](https://github.com/shadps4-emu/shadPS4) | The renderer and shader recompiler they started from, and the online code: shadPS4 0.20's network, NP and shadNet libraries, put together for bbport by Awaken ([gpu/bbnet20](../gpu/bbnet20)). |
|
||||
| [Wozzardman/shadnet-p2p](https://github.com/Wozzardman/shadnet-p2p) | The shadNet server for a group's own co-op server ([hosting your own server](SERVER.md)). |
|
||||
| [Classmg22/BBSC](https://github.com/Classmg22/BBSC) | Seamless Co-op, by CLS_AMG, with Wozzardman's seamless summon ([gpu/bbnet20/bbsc](../gpu/bbnet20/bbsc), its game server in [scripts/bbsc_gameserver](../scripts/bbsc_gameserver)). |
|
||||
| [fhody125-web/NoDreamForHunter](https://github.com/fhody125-web/NoDreamForHunter) | The base of BBSC's game server. |
|
||||
| [MikeTat1988/HuntersAtlas](https://github.com/MikeTat1988/HuntersAtlas) | Hunter's Atlas: the fullscreen in-game map, its quick-menu tile, lamp travel and the map generator ([tools/hunters-atlas](../tools/hunters-atlas)), which runs with Grimrukh's [soulstruct](https://github.com/Grimrukh/soulstruct). |
|
||||
| [The Hunter's Dream](https://thehuntersdream.com) | The online server, by droogie and its team; droogie's [bbhost](https://github.com/droogie/bbhost) is the base of its backend in the online module ([gpu/bbnet20/thd](../gpu/bbnet20/thd)) and brought fast loading after a death or travel. |
|
||||
| [IFreemz/shadPS4-Bloodborne-DLSS-FSR](https://github.com/IFreemz/shadPS4-Bloodborne-DLSS-FSR) | The FSR 4 model without trails. |
|
||||
| [NVIDIA DLSS SDK](https://github.com/NVIDIA/DLSS) | DLSS. |
|
||||
| [FireBurn/FSR-Vulkan](https://github.com/FireBurn/FSR-Vulkan) | FSR 3.1, with the AMD FidelityFX SDK. |
|
||||
| [pkgforge-dev/Anylinux-AppImages](https://github.com/pkgforge-dev/Anylinux-AppImages) | quick-sharun, which builds the AppImage. |
|
||||
| [paulomanrique/ps4-pkg-extractor](https://github.com/paulomanrique/ps4-pkg-extractor) | The launcher's *Install from PKG*, on [orbis-pkg-util](https://crates.io/crates/orbis-pkg-util). |
|
||||
| [herumi/xbyak](https://github.com/herumi/xbyak), [fmtlib/fmt](https://github.com/fmtlib/fmt) | Used by the shader translator and the online module. |
|
||||
| [GoldHEN_Patch_Repository](https://github.com/GoldHEN/GoldHEN_Patch_Repository) | Game patches by Kyo, Lance McDonald, auser1337, illusion, emoose and the Bloodborne community: high frame rates, resolution, ultrawide and more. |
|
||||
| [Nexus Mods: mod 30](https://www.nexusmods.com/bloodborne/mods/30) | Dommo's Xbox and Switch button prompts and stripped-down gestures menu, uploaded by goomab ([assets/menu_prompts](../assets/menu_prompts)). |
|
||||
|
||||
## bbport 0.7's contributors
|
||||
|
||||
This fork is built on bbport 0.7, which took pull requests and outside work from Qiming-Liu,
|
||||
AndiMajore, adds39939, iannitram, maiconjsv, whp199, DominickCobb-rs, Sarxzer, wheat32,
|
||||
ArenaMMORPG, guutol (Luís Gustavo P. Bomfim), redditor69, SrShadowy and Teefpyre, Supermedo and
|
||||
Awaken, and in earlier releases SrDeTs, IgorWarzocha, itachi-re, Bainianzzz, hideoneric,
|
||||
guitaripod, unTanana, andrR89, JuanraCM, SaeedShow, msisovic, AyrtonSanabio, axpico, Aryxa,
|
||||
Obrikash and noelmiller. What each one did:
|
||||
[bbport's list](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/CONTRIBUTORS_0.7.md).
|
||||
|
||||
## People
|
||||
|
||||
- **[deadinside28](https://github.com/deadinside28)**: bbport, the port this whole project is built on,
|
||||
and 0.6's menu pointer and keyboard layout.
|
||||
- **[Supermedo](https://github.com/Supermedo)**: online play, the cheats page, Add to Steam, and many
|
||||
fixes from the Windows port.
|
||||
- **[0xCydral](https://github.com/0xCydral)**: gathered and adapted dozens of fixes (crashes while
|
||||
loading, black images, the upscaler, the performance line in the log).
|
||||
- **[Awakenchan](https://github.com/Awakenchan)**: crash fixes (flips, descriptors, page faults),
|
||||
stick options, the controller in use, save import, the version selector, vertex shaders with
|
||||
the right bindings, stale vertex inputs read again, index buffers of indirect draws, and camera
|
||||
motion vectors without NaN.
|
||||
- **[Classmg22](https://github.com/Classmg22)** (CLS_AMG): BBSC, Seamless Co-op.
|
||||
- **[Wozzardman](https://github.com/Wozzardman)**: the server for a group's own co-op, and BBSC's seamless summon.
|
||||
- **[droogie](https://github.com/droogie)**: The Hunter's Dream server, and fast loading (bbhost).
|
||||
- **[fhody125-web](https://github.com/fhody125-web)**: NoDreamForHunter, the base of BBSC's game server.
|
||||
- **[MikeTat1988](https://github.com/MikeTat1988)**: Hunter's Atlas, the fullscreen in-game map.
|
||||
- **[IFreemz](https://github.com/IFreemz)**: the FSR 4 model without trails.
|
||||
- **[FireBurn](https://github.com/FireBurn)**: FSR 3.1.
|
||||
- **[Jhony-Jhansen](https://github.com/Jhony-Jhansen)**: keyboard movement (the last of two opposite keys wins).
|
||||
- **[cassioKenji](https://github.com/cassioKenji)**: the keyboard sprint measurements.
|
||||
- **[msisovic](https://github.com/msisovic)**: why loading hung after a lamp travel (guest thread handles above 40 bits).
|
||||
- **[axpico](https://github.com/axpico)**: with VRAM full, the game waits for evicted textures to be freed instead of closing.
|
||||
- **[emurphy04](https://github.com/emurphy04)**: a video player crash when it stops, the field of
|
||||
view, and an online crash guard.
|
||||
- **[yumlevi](https://github.com/yumlevi)**: the black character preview, the loading screen's
|
||||
picture, and a crash on an unreadable shader program.
|
||||
- **[GoncaloLobo0](https://github.com/GoncaloLobo0)**: crashes while loading areas.
|
||||
- **[bmy](https://github.com/bmy)**: motion vectors for the large beasts, and a memory fault.
|
||||
- **[Mrsuss60](https://github.com/Mrsuss60)**: the gestures menu from the keyboard, a deleted
|
||||
image's slot, and the log of failed memory reservations.
|
||||
- **[SrShadowy](https://github.com/SrShadowy)**: the on-screen keyboard for the character's name,
|
||||
and the shader compiling notice.
|
||||
- **[Qiming-Liu](https://github.com/Qiming-Liu)**: the Simplified Chinese translation.
|
||||
- **[andrecabralgit](https://github.com/andrecabralgit)**: the game's audio at full level.
|
||||
- **[Jgamrot](https://github.com/Jgamrot)**: the in-game menu's checkboxes and sliders.
|
||||
- **[GerasimosKan](https://github.com/GerasimosKan)**: trophy names without the key, and the
|
||||
sound when one is earned.
|
||||
- **[artemkaVG](https://github.com/artemkaVG)**: a stencil buffer fix.
|
||||
- **[parsifal295](https://github.com/parsifal295)**: the touchpad halves on Xbox pads.
|
||||
- **[AyrtonSanabio](https://github.com/AyrtonSanabio)**: the in-game menu's language without the launcher.
|
||||
- **[Aryxa](https://github.com/Aryxa)**: DLSS in the AppImage on NVIDIA.
|
||||
- **[AndiMajore](https://github.com/AndiMajore)**: crash debugging (`BB_FAULT_WAIT`).
|
||||
- **[iannitram](https://github.com/iannitram)**: FSR 3.1 without colour noise on the Ryzen Z1 (ROG Ally, Legion Go).
|
||||
- **[whp199](https://github.com/whp199)**: the GX occlusion query guard against the 0x2169edc crash.
|
||||
- **[nick24051998](https://github.com/nick24051998)**: the SRT walker without faults on null tables.
|
||||
|
||||
## Help and testing
|
||||
|
||||
- **[willthehuman](https://github.com/willthehuman)**: detailed reports with logs, and testing the fixes.
|
||||
- **[nr1971](https://github.com/nr1971)**: the game not starting on some graphics cards, the
|
||||
camera speeding up on diagonals, and a crash on quitting.
|
||||
- **[Joomzie](https://github.com/Joomzie)**: swapping A and B on Nintendo-style controllers.
|
||||
- **[Kaka556Kevin](https://github.com/Kaka556Kevin)**: testing the video memory fix.
|
||||
- **[segasonicye](https://github.com/segasonicye)**: why the game did not start in the Steam Deck's Game Mode.
|
||||
- **[Monochrome0907](https://github.com/Monochrome0907)**: which effects freeze loading screens on NVIDIA.
|
||||
- **[bruxis](https://github.com/bruxis)**: the log that showed why Steam Input did not reach the game.
|
||||
- **[shauryasagar](https://github.com/shauryasagar)** and **[ZeroSTF](https://github.com/ZeroSTF)**: the Hunter's Dream going black after long sessions.
|
||||
- **[popcar2](https://github.com/popcar2)**: ideas for the launcher.
|
||||
- **[shaneo88](https://github.com/shaneo88)**: shadPS4's patch file in the patches folder.
|
||||
- **[n3vrs0ft](https://github.com/n3vrs0ft)**: the "Transaction failed" crash on Seamless Hunt, the logs that stopped a BBSC guest from following a stranger's world, and testing BBSC 0.0.2 with a Windows player.
|
||||
- **[cappp](https://github.com/cappp)**: the "Transaction failed" crash on Seamless Hunt.
|
||||
- **[simo1730](https://github.com/simo1730)**: the Trophies page not finding the dump's files.
|
||||
- **[Nahnahchi](https://github.com/Nahnahchi)**: logs and tests of a GPU freeze in a boss fight on NVIDIA, a crash in Yahar'gul, stretched models, a crash on the Steam Deck, effects turning black, rainbow textures and crashes in the game's memory manager.
|
||||
- **[thomasgarafano1](https://github.com/thomasgarafano1)**: Hunter's Atlas not preparing the map with a GOTY dump, and testing the fix.
|
||||
- **[TacticalBill](https://github.com/TacticalBill)**: marking trophies already earned on the PS4.
|
||||
- **[holydingus](https://github.com/holydingus)**: BBSC's items not given to a new character.
|
||||
- **[OPGZ2](https://github.com/OPGZ2)**: the launcher's blank window at start.
|
||||
- **[kuday248](https://github.com/kuday248)**: settings from the in-game menu that the launcher overwrote.
|
||||
- **[BlazinKarp](https://github.com/BlazinKarp)**: the Steam overlay in the game.
|
||||
- **[molnar-a](https://github.com/molnar-a)**: a crash on the Steam Deck after lamp travel, and testing the fix.
|
||||
- **[Aman9das](https://github.com/Aman9das)**: asking for bbport 0.6's keyboard layout.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Catching a crash with a debugger
|
||||
|
||||
By default a fatal fault or a GPU-library assertion ends the process at once: the log keeps
|
||||
one line (`Host fault`, `Guest fault`, `STOP: GPU library assertion failed`) and nothing else.
|
||||
`BB_FAULT_WAIT=1` keeps the process alive at that point, stopped inside its handler, so a
|
||||
debugger can look at the faulting thread with its registers, stack and memory still there.
|
||||
|
||||
1. Launcher → *Advanced → Developer → Extra variables*: `BB_FAULT_WAIT=1` (or export it before `run.sh`).
|
||||
Nothing changes while the game runs; it costs nothing.
|
||||
2. When it crashes, the game freezes, a desktop notification says so, and the log ends with
|
||||
`bbport: ... the process stays alive for a debugger: bash tools/debug_attach.sh <pid>`.
|
||||
3. Run that line. gdb attaches, writes `logs/crash-<time>-<pid>.txt` and kills the process.
|
||||
With `--keep` it detaches instead, so `gdb -p <pid>` can be used by hand afterwards.
|
||||
|
||||
The report has every thread's stack, and for the faulting thread its registers, the
|
||||
instructions around `pc` and the top of its stack; for a GPU-library assertion the locals of
|
||||
the asserting thread (`bt full`). bbport's own frames carry file and line (the build is
|
||||
RelWithDebInfo); libc's come from debuginfod when it is reachable. Game code shows as a
|
||||
*guest offset* (pc minus the image base, as in the log's `Guest fault` line); the report gives
|
||||
an `objdump` command that disassembles `out/boot-linked.bin` around it.
|
||||
|
||||
GPU hangs are different: the device is lost and the GPU breadcrumbs in the log name the stuck
|
||||
draw or dispatch; the assertion that follows is caught the same way, but the state of interest
|
||||
is on the GPU.
|
||||
@@ -1,188 +0,0 @@
|
||||
# Depth-Adaptive TAA Improvements
|
||||
|
||||
**Дата:** 2026-10-01
|
||||
**Статус:** Исторический эксперимент, заменён исправлением temporal reprojection.
|
||||
|
||||
Описанные ниже пороги и веса больше не используются. Уменьшение веса истории вдали
|
||||
усиливало мерцание; сравнение device depth между разными камерами и хранение глубины
|
||||
в FP16 не позволяли корректно отличать дальние поверхности. Актуальный разбор,
|
||||
изменения и результаты проверок: [TEMPORAL_REVIEW_2026-10-01.md](TEMPORAL_REVIEW_2026-10-01.md).
|
||||
|
||||
## Проблема
|
||||
|
||||
Текущая реализация TAA использует фиксированные пороги для:
|
||||
1. Определения disocclusion (раскрытие новых поверхностей): `0.0005`
|
||||
2. Временного веса накопления: `0.9` → `0.65` в зависимости от движения
|
||||
|
||||
Эти параметры не учитывают глубину пикселя, что приводит к проблемам:
|
||||
- **Далёкие объекты (depth → 1.0)**: временная нестабильность, мерцание
|
||||
- **Близкие объекты (depth → 0.0)**: чрезмерное размытие при быстром движении
|
||||
|
||||
## Решение: Адаптивные пороги по глубине
|
||||
|
||||
### 1. Depth-Adaptive Disocclusion Threshold
|
||||
|
||||
**Файл:** `gpu/shadps4/video_core/host_shaders/taa.comp`
|
||||
|
||||
```glsl
|
||||
float depthThreshold(float depth) {
|
||||
// Near: 0.001, Far (>0.95): 0.0002
|
||||
return mix(0.001, 0.0002, smoothstep(0.7, 0.95, depth));
|
||||
}
|
||||
```
|
||||
|
||||
**Логика:**
|
||||
- Близкие поверхности (depth < 0.7): порог `0.001` — допускает небольшие расхождения
|
||||
- Дальние поверхности (depth > 0.95): порог `0.0002` — строже, меньше false positives
|
||||
- Плавный переход через `smoothstep(0.7, 0.95, depth)`
|
||||
|
||||
**Применение:**
|
||||
```glsl
|
||||
float depthDiff = abs(history.a - depth);
|
||||
float threshold = depthThreshold(depth);
|
||||
if (depthDiff < threshold && ...) {
|
||||
// Accept history
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Depth-Adaptive Temporal Weight
|
||||
|
||||
```glsl
|
||||
float temporalWeight(float depth, float motionLength) {
|
||||
// Base weight decreases for distant objects: 0.9 near, 0.8 far
|
||||
float baseWeight = mix(0.9, 0.8, smoothstep(0.7, 0.95, depth));
|
||||
// Motion reduces weight more aggressively for distant pixels
|
||||
float motionFactor = mix(0.65, 0.5, smoothstep(0.7, 0.95, depth));
|
||||
return mix(baseWeight, motionFactor, clamp(motionLength / 16.0, 0.0, 1.0));
|
||||
}
|
||||
```
|
||||
|
||||
**Логика:**
|
||||
- **Базовый вес без движения:**
|
||||
- Близко: `0.9` (агрессивное накопление истории)
|
||||
- Далеко: `0.8` (консервативнее, меньше мерцания)
|
||||
|
||||
- **При быстром движении (motionLength > 16px):**
|
||||
- Близко: снижается до `0.65`
|
||||
- Далеко: снижается до `0.5` (ещё консервативнее)
|
||||
|
||||
**Применение:**
|
||||
```glsl
|
||||
float motionLength = length(motion);
|
||||
float weight = temporalWeight(depth, motionLength);
|
||||
result = mix(current, clipped, weight);
|
||||
```
|
||||
|
||||
### 3. Motion Vector Validation (camera_motion.comp)
|
||||
|
||||
**Файл:** `gpu/shadps4/video_core/host_shaders/camera_motion.comp`
|
||||
|
||||
```glsl
|
||||
// Depth-adaptive validation: distant surfaces need tighter depth matching.
|
||||
float depth_threshold = mix(0.001, 0.0003, smoothstep(0.7, 0.95, depth));
|
||||
if (object_motion.b > 0.99 && abs(object_motion.a - depth) < depth_threshold &&
|
||||
!any(isnan(object_motion.rg)) && !any(isinf(object_motion.rg))) {
|
||||
result = object_motion.rg;
|
||||
}
|
||||
```
|
||||
|
||||
**Что изменено:**
|
||||
- Старый порог: фиксированный `0.001`
|
||||
- Новый порог: `0.001` → `0.0003` для далёких поверхностей
|
||||
- Предотвращает использование object motion vectors от неправильных пикселей
|
||||
|
||||
### 4. Улучшенная визуализация debug режимов
|
||||
|
||||
**Файл:** `gpu/shadps4/video_core/host_shaders/camera_motion.comp`
|
||||
|
||||
Режимы `BB_DEBUG_MOTION=1` теперь структурированы:
|
||||
```glsl
|
||||
if (mode == 1) {
|
||||
// Raw depth visualization
|
||||
imageStore(color_img, pixel, vec4(fract(depth * 4.0), depth, depth < 0.0 ? 1.0 : 0.0, 1.0));
|
||||
} else if (mode == 2) {
|
||||
// View-space depth visualization (z / 50)
|
||||
const float v = z / 50.0;
|
||||
imageStore(color_img, pixel, vec4(fract(v * 4.0), v, v < 0.0 ? 1.0 : 0.0, 1.0));
|
||||
}
|
||||
```
|
||||
|
||||
## Ожидаемые улучшения
|
||||
|
||||
### Дальние объекты
|
||||
- ✅ Меньше мерцания на далёких стенах и небе
|
||||
- ✅ Более стабильная история при небольших движениях камеры
|
||||
- ✅ Меньше ложных disocclusion от численных ошибок глубины
|
||||
|
||||
### Близкие объекты
|
||||
- ✅ Меньше размытия при быстрых движениях (персонаж, оружие)
|
||||
- ✅ Более точные object motion vectors
|
||||
- ✅ Лучший баланс между накоплением и отзывчивостью
|
||||
|
||||
### Адаптивная зона (depth 0.7-0.95)
|
||||
- Плавный переход между режимами
|
||||
- Предотвращает резкие артефакты на границе
|
||||
|
||||
## Тестирование
|
||||
|
||||
### Визуальная проверка
|
||||
```bash
|
||||
# TAA в нативном разрешении
|
||||
BB_UPSCALER=taa bash run.sh
|
||||
|
||||
# Проверить:
|
||||
# 1. Мерцание далёких стен (Hunter's Dream, верхние этажи)
|
||||
# 2. Размытие оружия при быстром повороте
|
||||
# 3. Стабильность при медленном движении камеры
|
||||
# 4. Disocclusion при обходе углов
|
||||
```
|
||||
|
||||
### Debug режимы
|
||||
```bash
|
||||
# Визуализация глубины
|
||||
BB_DEBUG_MOTION=1 bash run.sh
|
||||
# Toggle: (1<<21) = raw depth, (1<<22) = view-space z
|
||||
|
||||
# Проверка motion vectors
|
||||
# Toggle: (1<<20) = blended motion, (1<<23) = reprojection, (1<<24) = difference
|
||||
```
|
||||
|
||||
### A/B сравнение
|
||||
```bash
|
||||
# Создать файл для runtime-переключения
|
||||
echo "0" > /tmp/bb_toggle
|
||||
|
||||
# В игре переключать биты для сравнения
|
||||
# Потребуется временный механизм для старых/новых весов
|
||||
```
|
||||
|
||||
## Потенциальные риски
|
||||
|
||||
1. **Слишком консервативные веса на далёких объектах**
|
||||
- Может увеличить смазывание при быстрых поворотах
|
||||
- Решение: настроить `baseWeight` (0.8 → 0.85?)
|
||||
|
||||
2. **Слишком строгий порог глубины для object motion**
|
||||
- Может отбрасывать валидные векторы
|
||||
- Решение: `0.0003` → `0.0005` для depth > 0.95
|
||||
|
||||
3. **Smoothstep диапазон 0.7-0.95 может быть неоптимальным**
|
||||
- Bloodborne: near=0.05, far=3000
|
||||
- Большинство геометрии в диапазоне 0.1-0.9
|
||||
- Решение: профилирование распределения глубины в типичных сценах
|
||||
|
||||
## Следующие шаги
|
||||
|
||||
1. ✅ Реализовать depth-adaptive пороги
|
||||
2. ⏳ Собрать и протестировать в игре
|
||||
3. ⏳ Собрать статистику распределения глубины
|
||||
4. ⏳ Настроить пороги на основе реальных данных
|
||||
5. ⏳ Добавить runtime-переключение для A/B тестов
|
||||
6. ⏳ Документировать финальные параметры
|
||||
|
||||
## Ссылки
|
||||
|
||||
- [upscaler.md](upscaler.md) — общая архитектура апскейлера
|
||||
- [motion_vectors.md](motion_vectors.md) — векторы движения
|
||||
- TAA shader: `gpu/shadps4/video_core/host_shaders/taa.comp`
|
||||
- Camera motion: `gpu/shadps4/video_core/host_shaders/camera_motion.comp`
|
||||
@@ -1,447 +0,0 @@
|
||||
# Bloodborne: первый эксперимент нативного запуска
|
||||
|
||||
## Частота кадров, звук, диагностика (2026-09-25)
|
||||
|
||||
- **FPS**: `BB_FPS=uncap` (по умолчанию) применяет патч «Uncap FPS++»
|
||||
(Lance McDonald, Kyo) из `patches/Bloodborne.xml`: интервал флипа 1 и
|
||||
шаги симуляции от реального дельтатайма кадра. Vblank при этом следует за
|
||||
частотой монитора (`BB_VBLANK_HZ=0`). `BB_FPS=60`/`90` — патчи с
|
||||
фиксированным шагом, `BB_FPS=30` — без патчей. Дополнительные патчи из XML:
|
||||
`BB_PATCHES="Skip Intro;Disable Motion Blur (perf increase)"`.
|
||||
`patches.py` компилирует их в `out/patches.bin`; адреса в XML — это
|
||||
виртуальные адреса PS4 (база eboot 0x400000), загрузчик пишет их по
|
||||
смещению `адрес-0x400000` после релокаций (`--patches`).
|
||||
- **Звук**: `sceAudioOutOutput` теперь возвращает управление строго раз в
|
||||
период буфера (как железо). Раньше вызовы шли пачками раз в квант PipeWire
|
||||
(21 мс), поток вывода FMOD обгонял микшер и проигрывал незаписанные половины
|
||||
512-кадровых блоков — щелчок каждые 10.7 мс.
|
||||
- **Производительность потока GPU** (`shadPS4:GpuCommandProcessor` — узкое
|
||||
место: ~97% одного ядра). Замер в одной сцене: 26.5 → 47 FPS.
|
||||
- `runtime_memory.c`: таблица регионов под rwlock вместо рекурсивного
|
||||
мьютекса (18% времени поток GPU ждал его), плюс потоковый кеш регионов,
|
||||
проверяемый по счётчику поколений таблицы, — запросы видеоядра на каждый
|
||||
draw идут без блокировки.
|
||||
- `vk_scheduler.cpp`: `PopPendingOperations` на каждом draw делал ioctl
|
||||
опроса timeline-семафора; теперь только при непустой очереди и не чаще
|
||||
раза в 250 мкс.
|
||||
- `fetch_shader.cpp`: разбор fetch-шейдера кешируется по адресу кода с
|
||||
проверкой копии кода.
|
||||
- `region_manager.h`: без изменённых страниц в диапазоне защита не
|
||||
пересчитывается.
|
||||
- Запись команд Vulkan в отдельном потоке `bb:VkRecorder`
|
||||
(`Scheduler::Record`, команды и их данные в блоках по 128 КиБ; draw,
|
||||
dispatch, дескрипторы, барьеры, копирования, dynamic state, детайлинг).
|
||||
`CommandBuffer()` для непереведённого кода синхронизируется и включает
|
||||
прямую запись до конца draw. `BB_VK_RECORD_THREAD=0` — старое поведение.
|
||||
- Кеш описаний текстур (`ImageDesc`) по T# и флагам ресурса, кеш
|
||||
`FindImage`, проверка изменённых страниц без блокировки для привязок
|
||||
только на чтение, загрузка гостевых данных буферов в потоке записи.
|
||||
- Замер в одной сцене: все оптимизации 64 FPS, без них 40 FPS (×1.6).
|
||||
- `BB_TOGGLE_FILE=<файл>`: маска выключенных оптимизаций, меняется во время
|
||||
игры (1 кеш регионов, 2 кеш fetch-шейдеров, 4 ранний выход трекинга
|
||||
страниц, 8 лимит опроса драйвера, 16 поток записи, 32 кеш текстур,
|
||||
64 проверка страниц без блокировки, 128 кеш FindImage, 256 загрузка
|
||||
буферов в потоке записи, 512 мемо трекера барьеров) — для поиска
|
||||
регрессий без перезапуска.
|
||||
- **Звук**: при старте для Bloodborne выставляется флаг `0x204E` в
|
||||
`SPRJ0005/userdata0010` (хак rainvmaker из форка Diegolix29): с нулевым
|
||||
флагом часть звуков, например оружие игрока, не проигрывается.
|
||||
`BB_SOUND_HACK=0` отключает.
|
||||
Профилирование без perf: загрузчик по SIGUSR2 печатает RIP, `rdi` (адрес
|
||||
мьютекса при ожидании futex) и цепочку кадров потока; GPU-библиотека
|
||||
собирается с `-fno-omit-frame-pointer`.
|
||||
- **Диагностика**: `BB_FRAME_STATS=1` — FPS, худший кадр и время компиляции
|
||||
шейдеров/пайплайнов раз в 5 с; `BB_AUDIO_STATS=1` — ритм и заполнение
|
||||
очереди звука; `BB_AUDIO_DUMP=путь` — сырой PCM каждого порта.
|
||||
|
||||
## Текущее состояние (2026-09-25)
|
||||
|
||||
Оригинальный x86-64 код `eboot.bin` выполняется напрямую на Linux и проходит
|
||||
глобальные конструкторы, инициализацию памяти и запуск собственной системы
|
||||
потоков игры: создано 13 гостевых потоков (`FD4JobWorker:0`, `EzWorkPool_*` ×9,
|
||||
`MOMainThread`, логгер). Игра выделяет ~4.8 ГиБ direct- и ~337 МиБ
|
||||
flexible-памяти. Текущая остановка — `sceAjmInitialize` (аудиокодек ATRAC9),
|
||||
caller `0xd18a6a`.
|
||||
|
||||
Изменения этого этапа:
|
||||
|
||||
- `runtime_memory.c` переписан: единый sparse memfd на весь direct-пул
|
||||
(5056 МиБ, как `sceKernelGetDirectMemorySize` на PS4), таблица регионов
|
||||
с разбиением, flexible memory (448 МиБ), `ReserveVirtualRange`, `Mprotect`,
|
||||
`QueryMemoryProtection`, `VirtualQuery`, `GetDirectMemoryType`, `BatchMap(2)`,
|
||||
частичные unmap/release, `MAP_FIXED`/`NO_OVERWRITE`, GPU-биты защиты.
|
||||
- **Все видимые гостю адреса размещаются ниже 1 ТиБ**: образ, стеки потоков,
|
||||
главный стек, трамплины (`runtime_low_map`), гостевая память
|
||||
(`0x10_0000_0000..0xfc_0000_0000`), кучи host-объектов (non-PIE + `mallopt`).
|
||||
Игра упаковывает указатели в 40 бит: например, lock по адресу `0x20368c0`
|
||||
хранит владельца как `scePthreadSelf() & 0xffffffffff` и сравнивает с полным
|
||||
значением. С хэндлами `0x7f…` это давало вечную взаимоблокировку.
|
||||
- Watchdog (`--timeout`) печатает RIP и цепочку кадров всех потоков
|
||||
(смещения относительно образа и `bb-probe` для `addr2line`).
|
||||
`BB_TRACE_SEMA=1` печатает блокирующие ожидания семафоров.
|
||||
`got_import.py <адрес GOT>` называет импорт за PLT-заглушкой.
|
||||
- Сеть в режиме «кабель отключён»: HTTP-шаблоны/соединения/запросы создаются,
|
||||
отправка возвращает сетевую ошибку; NpWebApi/Matching2/Signaling/Score —
|
||||
«не в PSN»; голосовой чат — порты без звука.
|
||||
|
||||
Следующие подсистемы по порядку появления: Ajm (ATRAC9 через ffmpeg),
|
||||
Fios2, SaveData, Rtc, Pad, AudioOut, AvPlayer, VideoOut и GnmDriver.
|
||||
|
||||
## Предыдущее описание
|
||||
|
||||
|
||||
**Оригинальный x86-64 код `eboot.bin` выполняется непосредственно процессором
|
||||
Linux и проходит начальный участок конструкторов.** Теперь он возвращается из
|
||||
`_init_env`, регистрирует три обработчика `atexit` и 279 C++-деструкторов,
|
||||
проходит 1416 guard инициализации, создаёт 1422 mutex, один rwlock и 17 семафоров,
|
||||
выделяет/отображает 116 МиБ для игрового аллокатора.
|
||||
Оригинальная `libc.prx` загружается вместе с eboot, её инициализатор возвращает 0,
|
||||
конструктор `std::ios_base::Init` пройден. Следующая контролируемая остановка —
|
||||
`sceSysmoduleLoadModule(0xb4)` (`g8cM39EUZ6o#M#N`, caller `0x2026149`).
|
||||
Отдельный тест Vulkan выполняет команду заполнения буфера и проверяет
|
||||
возвращённые 4096 байт. shadPS4 в этом эксперименте не запускается.
|
||||
|
||||
**Это ещё не рекомпиляция всей игры и не играбельный порт.** Нет окна игры,
|
||||
меню, отрисовки игровых ресурсов или работающего игрового цикла. Vulkan пока
|
||||
не соединён с графическими командами игры. Контролируемая остановка на очередном
|
||||
неподдерживаемом импорте — измеримая граница прототипа; запуск игры не завершён.
|
||||
|
||||
## Запуск в текущем окружении
|
||||
|
||||
Из `game_files`:
|
||||
|
||||
```bash
|
||||
bash native_probe/run.sh --software
|
||||
```
|
||||
|
||||
Скрипт находит локальные Python, GCC и Vulkan SDK, подготавливает образ,
|
||||
собирает загрузчик и запускает его. На этой машине инструменты найдены
|
||||
в `/nix/store`; ничего скачивать или устанавливать не понадобилось.
|
||||
`--software` выбирает Lavapipe и отключает неявные Vulkan layers для теста.
|
||||
В проверенном окружении `/dev/dri` отсутствует, поэтому реальный GPU не проверен.
|
||||
|
||||
Ожидаемый результат:
|
||||
|
||||
```text
|
||||
Vulkan: llvmpipe (LLVM 21.1.8, 256 bits); command submission + 4096-byte readback PASS
|
||||
Mapped 91973152 bytes, 4 segments; applied 237083 relocations
|
||||
Starting native libc.prx at image offset 0x56e0000; fallback exports=202
|
||||
Runtime: application heap API registered
|
||||
Native libc module initializer returned 0
|
||||
Entering original x86-64 code at guest offset 0xa0
|
||||
Runtime: _init_env returned (verified libc implementation: RET)
|
||||
Runtime: direct allocation offset=0x0, size=121634816 bytes
|
||||
Runtime: mapped direct memory, size=121634816, alignment=0x200000
|
||||
Runtime: semaphore id=1, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=2, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=3, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=4, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=5, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=6, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=7, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=8, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=9, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=10, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=11, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=12, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=13, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=14, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=15, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=16, attr=2, initial=0, max=1
|
||||
Runtime: semaphore id=17, attr=2, initial=0, max=1
|
||||
STOP: first unsupported PS4 import: g8cM39EUZ6o#M#N (index 249)
|
||||
API: sceSysmoduleLoadModule
|
||||
Caller return offset: 0x2026149; first argument: 0xb4
|
||||
Runtime: _init_env=1, atexit=3, __cxa_atexit=279, registered handlers=282
|
||||
Runtime: static guards acquired=1416, released=1416
|
||||
Runtime: mutexes created=1422, locks=3208, unlocks=3208
|
||||
Runtime: rwlocks created=1, reads=21, writes=1, unlocks=22
|
||||
Runtime: semaphores created=17, deleted=0, acquired=0, signals=0, timeouts=0
|
||||
Runtime: direct memory allocations=1, maps=1, live=121634816, budget=536870912
|
||||
Runtime: memory operations=9884
|
||||
Original guest entry instructions executed; game initialization is incomplete.
|
||||
```
|
||||
|
||||
**Код завершения 20 означает ожидаемую остановку на отсутствующей реализации
|
||||
PS4 API.** Он намеренно отличается от 0. Ошибки загрузки возвращают 1;
|
||||
явно неподдерживаемые режимы runtime — 21, срабатывание stack protector — 22.
|
||||
Linux-сигналы — 128 + номер сигнала. На выполнение гостевого кода в Linux
|
||||
установлен таймер 10 секунд.
|
||||
|
||||
На системе с доступным GPU можно попробовать `bash native_probe/run.sh`.
|
||||
Это использует обычный выбор устройства Vulkan; аппаратный результат здесь
|
||||
не проверен. Для повторного запуска уже собранных файлов:
|
||||
|
||||
```bash
|
||||
native_probe/out/bb-probe native_probe/out/boot-libc.bin --cpu-only
|
||||
native_probe/out/bb-probe --vulkan-only
|
||||
```
|
||||
|
||||
Базовый образ без нативной libc сохраняется в `out/boot.bin` для сравнения
|
||||
с прежней остановкой на `std::ios_base::Init`.
|
||||
|
||||
Из каталога `native_probe` команда остаётся прежней: `bash run.sh --software`.
|
||||
Для воспроизведения самой ранней остановки на `_init_env` используйте
|
||||
`out/bb-probe out/boot.bin --cpu-only --strict-imports` из этого каталога.
|
||||
|
||||
Последняя команда использует обычные настройки Vulkan. Для программного
|
||||
драйвера задайте `VK_DRIVER_FILES` на его JSON и
|
||||
`VK_LOADER_LAYERS_DISABLE='~implicit~'`, как делает `run.sh --software`.
|
||||
|
||||
## Что найдено в файлах
|
||||
|
||||
| Параметр | Результат |
|
||||
|---|---|
|
||||
| Игра | Bloodborne, CUSA03173 |
|
||||
| Версия из `param.sfo` | APP_VER 01.09, VERSION 01.00 |
|
||||
| Контейнер | SELF, 8 записей сегментов |
|
||||
| CPU | x86-64, ELF64 little-endian, PS4/FreeBSD ABI |
|
||||
| Тип ELF | ET_SCE_DYNEXEC (`0xfe10`) |
|
||||
| Точка входа | `0xa0` относительно базы загрузки |
|
||||
| Загружаемые сегменты | 2: код/данные только для чтения и изменяемые данные/BSS |
|
||||
| Образ в памяти | 91 042 036 байт |
|
||||
| Импорты | 686 уникальных импортированных символов |
|
||||
| Зависимости DT_NEEDED | 42 модуля |
|
||||
| Перемещения адресов | 231 478 RELATIVE, 2 838 ABS64, 23 GLOB_DAT, 661 JUMP_SLOT |
|
||||
| Ресурсы `dvdroot_ps4` | 31 322 968 005 байт |
|
||||
|
||||
Среди зависимостей: libc, libkernel, GnmDriver, VideoOut, Pad, Fios2,
|
||||
AudioOut, SaveData, Sysmodule, сетевые и диалоговые API. В `sce_module`
|
||||
имеются только семь файлов; наличие файлов модулей не заменяет поддержку их ABI.
|
||||
|
||||
Используемые сегменты SELF доступны открытым текстом. Извлечение не выполняет
|
||||
расшифровку. Неподдерживаемые сжатые/зашифрованные сегменты отклоняются.
|
||||
Один незагружаемый сегмент метаданных (`program header 9`, тип `0x6fffff01`)
|
||||
не представлен в доступных SELF-сегментах. В диагностическом `eboot.elf` его
|
||||
диапазон заполнен нулями: **это не побайтово полное восстановление ELF**.
|
||||
Все сегменты, нужные этому эксперименту, проверены на наличие.
|
||||
|
||||
Подробности, SHA-256 исходного eboot, список зависимостей, NID, сегментов
|
||||
и количество ресурсов по каталогам находятся в `out/analysis.json`.
|
||||
Имена нескольких импортов проверены вычислением NID из предполагаемых имён;
|
||||
результаты записаны в `import_name_hints`. Для первого вызова `_init_env`
|
||||
получен NID `bzQExy189ZI`, следующий вызов соответствует `atexit`.
|
||||
|
||||
## Как устроен прототип
|
||||
|
||||
`prepare.py` читает SELF и SFO, проверяет границы, восстанавливает доступные
|
||||
сегменты, разбирает PS4 dynamic/symbol/RELA-таблицы и формирует `out/boot.bin`.
|
||||
Исходные файлы открываются только для чтения. Все результаты — в `out/`.
|
||||
|
||||
Дополнительно проверяется фактический экспорт `_init_env` в `sce_module/libc.prx`:
|
||||
NID `bzQExy189ZI`, адрес `0x5fef0`, размер 1, байт `c3` (RET).
|
||||
SHA-256 libc и доказательство записываются в `analysis.json/libc_evidence`.
|
||||
Первоначальное предположение о сложной инициализации внутри `_init_env`
|
||||
для этого дампа не подтвердилось. Возврат соответствует реальному коду функции.
|
||||
Формат `BBPROBE2` содержит флаг этой проверки; без флага runtime отключён.
|
||||
Старые образы `BBPROBE1` читаются с отключённым runtime.
|
||||
|
||||
`link_libc.py` создаёт отдельный `out/boot-libc.bin` формата `BBPROBE3`:
|
||||
добавляет два сегмента оригинальной libc, её перемещения адресов и 202
|
||||
резервные привязки экспортов. Сопоставляет NID вместе с именами и версиями
|
||||
библиотеки/модуля: локальные суффиксы libc `#C#A` и eboot `#q#q` различаются.
|
||||
Реализованные host-контракты имеют приоритет. Остальные совпавшие импорты
|
||||
ссылаются на настоящий код/данные libc, включая объекты и таблицы C++.
|
||||
Исходные файлы игры не изменяются; сведения о привязках и SHA-256 libc
|
||||
записываются в `out/libc-link.json`.
|
||||
|
||||
Перед входом в eboot выполняется `DT_INIT` libc. Поддержан TLS этой библиотеки:
|
||||
модуль 2, шаблон 544 байта, блок 1184 байта на host-поток, обнулённый остаток;
|
||||
14 перемещений `DTPMOD64` записывают номер модуля, а не указатель на образ.
|
||||
`__tls_get_addr` проверяет модуль и границы. Полный TLS всех модулей PS4,
|
||||
его освобождение при завершении гостевых потоков и прямой доступ через FS
|
||||
не реализованы. Для текущего одного гостевого потока блок живёт до выхода.
|
||||
`sceKernelGetProcParam` возвращает исходный перемещённый procparam eboot;
|
||||
`_sceKernelRtldSetApplicationHeapAPI` сохраняет таблицу callbacks аллокатора.
|
||||
|
||||
`runtime_thread.c` реализует self и запросы init/get/getaffinity/destroy
|
||||
атрибутов текущего гостевого потока. Для этого прототипа существует один
|
||||
виртуальный CPU (маска 1); это не маска host CPU и не настройка его affinity.
|
||||
Создание гостевых потоков и изменение атрибутов остаются неподдержанными.
|
||||
|
||||
`probe.c` выделяет память, копирует сегменты и применяет перемещения адресов.
|
||||
Неизвестные функции направляются в исполняемые перехватчики, печатающие NID
|
||||
и завершающие процесс. Неподдерживаемые импортированные данные направляются в недоступные
|
||||
страницы: их использование вызывает ошибку, а не возвращает фиктивные данные.
|
||||
После загрузки устанавливаются права страниц по ELF, код получает права RX.
|
||||
Затем выполняется прямой вызов оригинальной точки входа по SysV ABI.
|
||||
Подготовки аргументов достаточно для наблюдаемого участка; полноценный стек
|
||||
запуска ядра PS4, исключения и общий механизм загрузки зависимых модулей
|
||||
не реализованы; пока специально подключена только libc.
|
||||
|
||||
`runtime.c` реализует проверенный возврат из `_init_env`, реестр `atexit`/
|
||||
`__cxa_atexit` с обратным порядком и фильтрацией DSO, `__cxa_finalize`,
|
||||
`exit`, guard acquire/release/abort, случайный process-local stack canary,
|
||||
аварийный обработчик повреждения стека, четыре функции работы с памятью и `strlen`.
|
||||
Сопоставление выполняется по полному NID с library/module suffix именно
|
||||
этого eboot, без автоматического «вернуть 0» для неизвестных импортов.
|
||||
Реестр завершения и guards рассчитаны на текущий один гостевой поток;
|
||||
рекурсивная/конкурентная инициализация guard останавливает выполнение с кодом 21.
|
||||
|
||||
`runtime_mutex.c` связывает PS4 mutex/атрибуты с Linux pthread, преобразует
|
||||
типы и коды ошибок, поддерживает рекурсивные и статически созданные mutex.
|
||||
Приоритетные протоколы, PS4-планировщик и гостевые потоки не реализованы.
|
||||
Opaque handles предназначены для реализованных API; двоичный layout
|
||||
внутренних PS4 mutex не воспроизводится. Наблюдаемый участок libc работает
|
||||
через эти API; прямой доступ к внутренностям mutex потребует отдельной реализации.
|
||||
|
||||
POSIX-импорты mutex из `libScePosix` используют тот же backend через
|
||||
отдельные wrappers: возвращают положительные PS4 errno, а не отрицательные
|
||||
коды `scePthread`. Двухаргументный `pthread_mutex_init` не читает отсутствующий
|
||||
третий аргумент. Linux errno не передаётся гостю напрямую.
|
||||
|
||||
`runtime_sema.c` реализует kernel create/delete, wait/poll, signal/cancel.
|
||||
Handles — уникальные 32-битные ID; удалённые ID не переиспользуются.
|
||||
Проверены счётчики и переполнение, таймаут в микросекундах с монотонными
|
||||
часами, резервирование токенов до пробуждения, отмена и удаление с ожидающими
|
||||
host-потоками. Объект освобождается после возврата всех ожидающих вызовов.
|
||||
В режиме FIFO обслуживаются подходящие запросы в порядке очереди; запрос
|
||||
меньшего размера может пройти ожидающий запрос, для которого токенов ещё нет.
|
||||
Это соответствует использованной справочной модели shadPS4; сопоставление
|
||||
на реальной PS4 пока не выполнено.
|
||||
Атрибуты 0/2 допускают создание и неблокирующие операции, но блокирующее
|
||||
ожидание явно останавливает прототип (21): приоритетный PS4-планировщик ещё
|
||||
отсутствует. Ненулевые option parameters также не реализованы. В текущем
|
||||
запуске игра создала 17 семафоров с attr=2, initial=0, max=1, но пока не
|
||||
выполняла wait/signal; эти операции проверены отдельными C-тестами.
|
||||
|
||||
`runtime_time.c` поддерживает POSIX `gettimeofday`: время берётся у host,
|
||||
поля копируются в явные PS4 timeval/timezone. Проверены границы полей и
|
||||
диапазон микросекунд. Неожиданная ошибка host API останавливает прототип:
|
||||
полный POSIX errno/TLS bridge ещё не реализован.
|
||||
|
||||
`runtime_rwlock.c` поддерживает init/destroy, read/write, try-read/try-write,
|
||||
timed-read/timed-write и unlock поверх Linux pthread. Реестр синхронизирует
|
||||
инициализацию статических handles, отслеживает владельцев, читателей и
|
||||
ожидающие вызовы: уничтожение занятой блокировки возвращает EBUSY,
|
||||
чужой unlock — EPERM, таймаут — Orbis ETIMEDOUT. Ожидание не удерживает
|
||||
mutex реестра. Проверена одновременная работа нескольких host-потоков,
|
||||
но создание гостевых потоков пока отсутствует. Поддержаны только атрибуты
|
||||
по умолчанию; нестандартные атрибуты останавливают прототип с кодом 21.
|
||||
|
||||
`runtime_memory.c` даёт **ограниченный CPU-пул 512 МиБ для исследования**.
|
||||
Это выбранный бюджет прототипа, не объём RAM PS4. Выделение возвращает
|
||||
виртуальный физический offset; Linux memfd обеспечивает реальное общее
|
||||
хранилище для нескольких отображений и выравнивание 16 КиБ/2 МиБ.
|
||||
Реализованы allocate/map, полное unmap и полное release. Поддержаны типы
|
||||
памяти 0/3 для CPU, permissions read/write, flags=0, автоматический адрес;
|
||||
иной режим останавливается с диагностикой. Частичные release/unmap пока
|
||||
возвращают EINVAL. GPU coherency, резервирование фиксированных адресов,
|
||||
полное адресное пространство PS4 и подключение этого пула к Vulkan отсутствуют.
|
||||
|
||||
`vulkan_smoke.c` создаёт instance/device/queue, отправляет `vkCmdFillBuffer`,
|
||||
ставит барьер для чтения CPU, ожидает завершения и сравнивает все слова буфера.
|
||||
Это проверка будущего графического backend, не переводчик GNM.
|
||||
|
||||
## Сборка на других системах
|
||||
|
||||
Linux x86-64: Python 3, GCC/Clang, Vulkan headers и loader. Обычно достаточно:
|
||||
|
||||
```bash
|
||||
python3 native_probe/prepare.py CUSA03173
|
||||
python3 native_probe/link_libc.py CUSA03173
|
||||
bash native_probe/build.sh
|
||||
```
|
||||
|
||||
Скрипт поддерживает `CC`, `VULKAN_INCLUDE`, `VULKAN_LIB`;
|
||||
при наличии `pkg-config vulkan` использует его настройки.
|
||||
|
||||
Для Windows в исходнике есть ветка `VirtualAlloc`/`VirtualProtect` и явный
|
||||
SysV ABI. Предполагается **MinGW-w64 GCC/Clang x86-64**, не MSVC.
|
||||
Эта ветка **не собрана и не проверена** в текущем Linux-окружении.
|
||||
Пример ручной сборки в окружении MinGW с установленным Vulkan SDK:
|
||||
|
||||
```text
|
||||
python native_probe/prepare.py CUSA03173
|
||||
gcc -std=c11 -O2 -Wall -Wextra native_probe/probe.c native_probe/runtime.c native_probe/runtime_mutex.c native_probe/runtime_thread.c native_probe/runtime_rwlock.c native_probe/runtime_sema.c native_probe/runtime_time.c native_probe/runtime_memory.c native_probe/vulkan_smoke.c -I<SDK>/Include -L<SDK>/Lib -lvulkan-1 -o native_probe/out/bb-probe.exe
|
||||
native_probe/out/bb-probe.exe native_probe/out/boot.bin
|
||||
```
|
||||
|
||||
Пути SDK заменяются фактическими; бинарник Linux непереносим на Windows.
|
||||
Диагностика сигналов и таймер в текущем исходнике реализованы только для Linux.
|
||||
Новые mutex/rwlock/semaphore/time/direct-memory backends также реализованы только для Linux;
|
||||
Windows дойдёт до более раннего неподдерживаемого импорта.
|
||||
|
||||
## Проверки
|
||||
|
||||
```bash
|
||||
bash native_probe/build.sh --test
|
||||
python3 -m unittest discover -s native_probe -v
|
||||
```
|
||||
|
||||
Пройдено 35 Python-тестов, включая запуск набора C-проверок runtime:
|
||||
границы SELF/образа, проверка инструкции `_init_env`, прямое исполнение
|
||||
x86-64, перемещения адресов, режим strict, отключение неподтверждённого runtime,
|
||||
диагностика сбоев. C-проверки охватывают обратный порядок деструкторов,
|
||||
DSO и повторную финализацию, регистрацию при финализации и рост реестра,
|
||||
guard acquire/abort/release, ошибки и рекурсивность mutex, выравнивание,
|
||||
непересечение физических выделений, shared aliasing и повторное выделение
|
||||
очищенной памяти. Stack protector и рекурсивный guard проверены в subprocess.
|
||||
Дополнительно проверены жизненный цикл rwlock, повторные read-захваты,
|
||||
чужой unlock, занятое уничтожение, несколько одновременных читателей,
|
||||
ожидающий writer, преобразование таймаутов и `strlen` с NUL внутри строки.
|
||||
Добавлены проверки нативного инициализатора/экспорта, приоритета host API,
|
||||
отключения libc в strict-режиме, прав исполнения и границ новых metadata,
|
||||
совпадения идентичности при разных локальных ID, отказа при другой версии
|
||||
библиотеки и обработки TLS-релокаций. C-тест проверяет отдельные TLS-блоки
|
||||
в host-потоках, шаблон/BSS, запросы атрибутов и регистрацию heap API.
|
||||
Отдельный `sema-test` проверяет 32-битную запись handle без порчи соседнего
|
||||
поля, устаревшие ID, переполнение, таймаут, очередь из нескольких host-потоков,
|
||||
резервирование токенов, отмену/удаление с ожидающими и явный отказ от
|
||||
приоритетного блокирующего ожидания. Проверены положительные ошибки POSIX
|
||||
mutex, рекурсивный захват и выходные структуры `gettimeofday`.
|
||||
Linux-сборка выполнена с `-Wall -Wextra -Werror`.
|
||||
Актуальный интеграционный запуск настоящего eboot записан в `out/run.log`;
|
||||
результат старого режима — в `out/strict-run.log`.
|
||||
|
||||
## Следующие этапы настоящего порта
|
||||
|
||||
1. Реализовать загрузку/реестр системных модулей и необходимые контракты
|
||||
AppContent. Игра вызвала `sceSysmoduleLoadModule(0xb4)` из `0x2026149`;
|
||||
по справочной таблице этот ID означает `libSceAppContent`. Такого файла
|
||||
среди семи локальных `sce_module/*.prx` нет. Потребуется реализация
|
||||
используемых API и состояния модуля; простой возврат успеха не добавлен.
|
||||
2. Продолжить конструкторы, дополнить память/синхронизацию, затем реализовать
|
||||
TLS остальных модулей, запуск потоков и загрузку необходимых модулей.
|
||||
3. Дойти до `main`, добавить файловую систему (`/app0` → каталог ресурсов)
|
||||
и фиксировать каждый следующий неподдерживаемый контракт.
|
||||
4. Перевести GNM/PM4-команды, форматы ресурсов и AMD GCN-шейдеры в Vulkan/SPIR-V;
|
||||
затем связать VideoOut с окном и swapchain. Наличие Vulkan само по себе
|
||||
не делает графический код PS4 совместимым.
|
||||
5. Для статической рекомпиляции восстанавливать функции и контрольный поток,
|
||||
таблицы виртуальных вызовов, исключения и ABI; проверять поведение сравнением
|
||||
с исходным машинным кодом. На x86-64 прямое исполнение с совместимым runtime
|
||||
уже позволяет исследовать запуск без предварительного перевода всего CPU-кода.
|
||||
|
||||
Приоритет следующего изменения — Sysmodule/AppContent и следующие конструкторы.
|
||||
|
||||
## Справочные источники
|
||||
|
||||
Формат SELF и правила соотнесения сегментов сверены с
|
||||
[заголовками загрузчика shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/loader/elf.h)
|
||||
и [загрузкой сегментов](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/loader/elf.cpp).
|
||||
PS4 dynamic-таблицы и вычисление NID сверены с
|
||||
[module.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/module.cpp),
|
||||
контекст запуска — с [linker.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/linker.cpp).
|
||||
Сигнатуры mutex и значения типов сверены с
|
||||
[mutex.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/threads/mutex.cpp)
|
||||
и [pthread.h](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/threads/pthread.h),
|
||||
rwlock — с [rwlock.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/threads/rwlock.cpp),
|
||||
сигнатуры direct memory — с
|
||||
[memory.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/memory.cpp).
|
||||
Семафоры сверены с
|
||||
[semaphore.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/threads/semaphore.cpp),
|
||||
структуры времени — с
|
||||
[time.h](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/time.h).
|
||||
Идентификатор AppContent — с
|
||||
[sysmodule_table.h](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/sysmodule/sysmodule_table.h).
|
||||
Новые контракты регистрации heap API и procparam сверены с
|
||||
[linker.h](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/linker.h)
|
||||
и [process.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/process.cpp),
|
||||
атрибуты потоков — с
|
||||
[pthread_attr.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/threads/pthread_attr.cpp).
|
||||
Тело `_init_env` и устройство guards проверены дизассемблированием локальной libc.
|
||||
Реализация прототипа написана отдельно; зависимости на исполняемый shadPS4 нет.
|
||||
|
||||
Каталог `out/` содержит производные данные игры, имеет `.gitignore` и не нужен
|
||||
для распространения исходников инструмента.
|
||||
@@ -1,355 +0,0 @@
|
||||
# Уход от эмуляции: что сделано и что дальше (2026-10-06)
|
||||
|
||||
Исторический план и замеры по этапам разработки. В `0.5-pre2` новая модель остаётся
|
||||
экспериментальной и выключена по умолчанию; для NVIDIA и других не-AMD GPU добавлен модуль
|
||||
памяти без sparse-перепривязок. Актуальное состояние и проверки:
|
||||
[CHANGES_0.5-pre2.md](CHANGES_0.5-pre2.md).
|
||||
|
||||
## Где проходит граница API
|
||||
|
||||
Bloodborne собран со статической libGnm. Пакеты PM4 пишет не драйвер, а сама игра, функциями
|
||||
`sce::Gnm::*CommandBuffer` (`0x1068000–0x10a0000`, `0x11fd000–0x11fe000`). Перехват всех таких
|
||||
функций (`BB_GNM_OBSERVE=1`) показал: 100 % отправляемых пакетов пишут известные вызовы API, всего
|
||||
99 методов. Gnm — API, который пишет регистры: `setDepthStencilControl` = один `SET_CONTEXT_REG`.
|
||||
Поэтому разбор PM4 от этих 99 функций и трансляция самих вызовов дают одно и то же; PM4 здесь —
|
||||
формат записи вызовов, как командный список. Он уже разбирается параллельно (DrawPreparation).
|
||||
|
||||
Эмуляция железа — не разбор пакетов, а то, что делается вместо GPU:
|
||||
|
||||
| Что | Состояние |
|
||||
|---|---|
|
||||
| Модель памяти 0.2 (копии в VRAM, защита страниц, обратное чтение, ранние метки) | Запасной вариант. Модель ПК — по умолчанию, для NVIDIA режим «кусок на выделение». Удалить код 0.2 после проверки на NVIDIA. |
|
||||
| Метки EOP/EOS | Модель ПК: пишутся после завершения работы GPU (честно). |
|
||||
| `WaitRegMem` на метки той же очереди | Транслируется как выполненное ожидание (порядок очереди); ждать CPU не нужно. |
|
||||
| `WRITE_DATA`/DMA ждали все копии хоста | Ждут только пересекающиеся копии (как командный процессор, который не ждёт шейдеров). |
|
||||
| Constant Engine (CE RAM, дампы, счётчики) | Выполняется на CPU в порядке потока: наш разборщик читает описатели из памяти. |
|
||||
| SRT walker (код шейдера GCN, исполняемый на CPU) | Нужен по той же причине. |
|
||||
| Учёт записей GPU для разборщика (`PendingWriteOverlaps`, `IsRegionGpuModified`) | ~15 % потока GpuComm, та же причина. |
|
||||
| Кэш текстур по адресу + хеш | Та же причина плюс отсутствие объектов «текстура» в PM4. |
|
||||
|
||||
Почти вся оставшаяся эмуляция существует потому, что **транслятор читает описатели ресурсов
|
||||
(T#/V#/S#) из памяти игры на CPU во время разбора**. Драйвер ПК и DXVK так не делают: память
|
||||
приложения читает GPU во время выполнения.
|
||||
|
||||
## Дальше, по порядку
|
||||
|
||||
1. **NVIDIA.** Тестовая сборка `dist/Bloodborne-bbport-x86_64-test-1006.AppImage` (модель ПК по
|
||||
умолчанию, режим «кусок на выделение»). Если работает — удалить модель 0.2 целиком
|
||||
(`page_manager`, `fault_manager`, обратное чтение, BB_UFFD, предзагрузка, ранние метки).
|
||||
2. **Распараллелить стадию B** (запись отрисовок; на 4 ядрах занята на 99 %, ~3,3 мкс на
|
||||
отрисовку, равномерно по привязке буферов/текстур, началу прохода, вершинам). Варианты:
|
||||
(а) несколько потоков стадии B по диапазонам отрисовок внутри прохода рендеринга (у каждого
|
||||
свой сегмент команд; общие кэши уже под блокировками; порядок барьеров — по проходам);
|
||||
(б) разрешение привязок (буферы/текстуры → Vulkan) на стадии A, где 28 % простоя, а B только
|
||||
записывает. Нужны прогулочные тесты.
|
||||
3. **Описатели на GPU (главный шаг к «как DXVK»).** Шейдер читает T#/V#/S# сам (буферы на месте
|
||||
и BDA уже есть); текстуры — bindless-массив, T# → индекс через таблицу на GPU, которую
|
||||
ведёт кэш текстур при создании образов. Тогда не нужны SRT walker, CE на CPU, учёт ожидающих
|
||||
записей; `WRITE_DATA`/DMA/дампы CE становятся записями GPU в порядке потока. Большой проект.
|
||||
4. **Текстуры как объекты.** Перехват `Gnm::Texture::init*` и загрузчика TPF
|
||||
(`CSGXTextureCreateFragment`): образ создаётся при загрузке, а не угадывается по адресу;
|
||||
хеши и наложения адресов уходят. Ассеты можно заранее перевести в линейный вид (детайлинг
|
||||
один раз при установке).
|
||||
5. **Шейдеры заранее.** Все шейдеры GCN из файлов игры → SPIR-V при установке; без компиляции
|
||||
во время игры.
|
||||
|
||||
Что остаётся трансляцией, как у DXVK: шейдеры GCN → SPIR-V, описатели и форматы → Vulkan.
|
||||
|
||||
## Шаг 1 (2026-10-07): движок констант больше не синхронизируется с записью отрисовок
|
||||
|
||||
Замеры (`BB_CE_STATS=1`, Яр'гул): ~14 000 `DumpConstRam` в секунду, ~4–5 МБ/с, всего 25 страниц
|
||||
(кольцо ~100 КБ). Каждый дамп читается ровно одним указателем из user data — это таблицы
|
||||
описателей для SRT walker. Константы, вершины, индексы, большие буферы из этих страниц не
|
||||
читаются; шейдеров, читающих память самостоятельно (`uses_dma`), нет. То есть дампы CE нужны
|
||||
только транслятору — это построение таблиц описателей, а не данные для GPU.
|
||||
|
||||
Ожидания стадии A на дампах (`WaitForPendingReads`) были нужны только потому, что стадия B для
|
||||
отрисовок без подготовки сама перечитывала таблицы V# (описатели вершинных буферов) — к тому
|
||||
времени перезаписанные следующим дампом (~15 % V# оказывались чужими: баг DoF, issue #44).
|
||||
|
||||
Сделано:
|
||||
- стадия A снимает V# в пакет отрисовки, стадия B берёт их оттуда и таблицы не читает;
|
||||
- источник DMA отмечается точным диапазоном, а не «читает неизвестно что».
|
||||
|
||||
Итог: ожиданий на дампах ~850/с → ~46/с, стадия A ждёт 1,5 % времени вместо ~30 %
|
||||
(16 потоков). На 4 ядрах / 8 потоках FPS тот же (~170): там узкое место — стадия B
|
||||
(3,4 мкс на пакет; больше всего — `ObtainBuffer` 0,57 мкс: сопоставление адресов памяти игры с
|
||||
буферами GPU).
|
||||
|
||||
Записи командного процессора (`BB_CE_STATS`): `WRITE_DATA` ~8 400/с в 2 страницы (читаются как
|
||||
константы ~240/с), DMA ~1 000/с в 15 страниц. Они пишут данные для GPU, и их перевод — команды
|
||||
GPU в порядке потока (`vkCmdUpdateBuffer`, у DMA для памяти на месте уже так). Для `WRITE_DATA`
|
||||
это упирается в правило переноса часто записываемых GPU блоков в VRAM — делать вместе с
|
||||
упрощением модели памяти.
|
||||
|
||||
## Шаг 2 (2026-10-07): синхронизации стадии B
|
||||
|
||||
Стадия B (запись отрисовок) — узкое место на 4 ядрах (занята 99 %). Задачи кадра на ней:
|
||||
- сигнал flip = подготовка кадра презентером; 600 тыс. тактов на кадр, из них работы ~40 тыс.,
|
||||
остальное — ожидание потоков записи команд (`CommandBuffer()`) и синхронная отправка. Теперь
|
||||
~50 тыс.: презентер записывается через `Record`, отправка асинхронная;
|
||||
- проходы апскейлера пишут команды напрямую — для них отдельный сегмент без ожидания;
|
||||
- DMA (~900/с, 4 байта) ждали копии и сигналы даже когда запись делает GPU — ждут только для
|
||||
записей CPU.
|
||||
|
||||
Что осталось на стадии B (мкс на пакет, 3,5 всего): привязка ресурсов 1,0 (буферы 0,4, текстуры
|
||||
0,45), `ObtainBuffer` 0,6, начало прохода 0,26, вершины 0,34, задачи 0,2. Дальше —
|
||||
разделение стадии B или привязка ресурсов на стадии A (см. п. 2 выше).
|
||||
|
||||
## Шаг 3 (2026-10-08): метки пишет видеочип, перехваты без сигналов, NVIDIA
|
||||
|
||||
Что игра реально отправляет (новый счётчик `BB_DCB_STATS=1`: пакеты PM4 по типам, события,
|
||||
метки, исходы `WAIT_REG_MEM`; Кошмар охотника, у лампы, ~185 FPS): `EVENT_WRITE_EOP` 61 тыс./с
|
||||
(~330 за кадр, все без прерывания, 32- и 64-битные метки), `WAIT_REG_MEM` 31 тыс./с (все по
|
||||
памяти и все выполнены по меткам, ожидающим в порядке потока — процессор не ждёт ни разу),
|
||||
`EVENT_WRITE_EOS` 371/с, `WRITE_DATA` 3,5 тыс./с, `DUMP_CONST_RAM` 6,6 тыс./с, сбросы кэшей
|
||||
(`EVENT_WRITE`, `ACQUIRE_MEM`) — около 70 тыс./с. Не встречаются: occlusion-запросы
|
||||
(`ZPASS_DONE`), `SET_PREDICATION`, `COND_EXEC`, `MEM_SEMAPHORE`, `REWIND`, `COPY_DATA`, метки
|
||||
со временем GPU, отправки в очереди compute.
|
||||
|
||||
Сделано:
|
||||
- **Метки EOP/EOS пишет видеочип** (`Rasterizer::WriteLabelOnGpu`, `BB_GPU_LABELS=0` — как
|
||||
раньше): буферный маркер Vulkan (`VK_AMD_buffer_marker`, конец конвейера) прямо в память игры
|
||||
(кусок гостевой памяти), в той же точке потока, где метку пишет командный процессор PS4.
|
||||
64-битная метка — две 32-битные записи, старшая половина первой. Раньше метку писал поток
|
||||
процессора после завершения всей отправки Vulkan (61 тыс. записей в секунду). Метки с
|
||||
прерыванием по-прежнему сигналятся с процессора (прерывание — оттуда). То же для меток
|
||||
`RELEASE_MEM` очередей compute. EOS с записью из GDS — копия видеочипом в порядке потока вместо
|
||||
`Finish()` и чтения процессором.
|
||||
- **Перехваты кода игры без ловушек** (`gpu/shim/bbport_guest_hooks.cpp`, `BB_GUEST_HOOK_TRAPS=1`
|
||||
— ловушки `int3`): точки загрузчиков, аллокатора диапазонов и кучи GPU переходят в заглушки
|
||||
рядом с образом, которые вызывают обработчик и выполняют перекрытые инструкции; вызовы `memcpy`
|
||||
загрузчиков идут через трамплин. Раньше каждый вызов был сигналом ядра (SIGTRAP), ~1200 в
|
||||
секунду только у аллокатора.
|
||||
- **Новая модель на NVIDIA** (`gpu/shim/bbport_guest_memory.cpp`): кроме кусков dma-buf (AMD) —
|
||||
режим «память хоста, импортированная видеочипом» (`VK_EXT_external_memory_host`): кусок
|
||||
остаётся в memfd рантайма, видеочип импортирует его второе отображение. При запуске
|
||||
самопроверка в точности как в работе: импорт memfd, привязка к sparse-буферу, запись
|
||||
видеочипом и чтение процессором через другое отображение, и обратно. Не прошла — dma-buf
|
||||
(у NVIDIA «кусок на выделение»), для которого теперь меряется скорость чтения процессором
|
||||
(некэшируемое отображение замедлило бы код игры; меньше 1 ГБ/с — не используется). Не прошло
|
||||
и это — старая модель. Выбор: `BB_GUEST_MEMORY=host|dmabuf`. Переключатель в лаунчере доступен
|
||||
на AMD и NVIDIA. На AMD (RADV) импорт memfd драйвер не принимает — там dma-buf, как раньше.
|
||||
На NVIDIA не проверено: в журнале видно, какой путь выбран и почему.
|
||||
|
||||
Замеры (у лампы): 16 потоков — 233 FPS и с метками видеочипа, и без; 4 ядра / 8 потоков —
|
||||
~217 против ~215 FPS (в пределах шума; выигрыш — свободный поток сигналов).
|
||||
|
||||
### Что ещё осталось и почему
|
||||
|
||||
Всё, что осталось от эмуляции, держится на одном: **транслятор читает описатели ресурсов
|
||||
(V#/T#/S#) и таблицы с ними из памяти игры во время разбора**. Из-за этого:
|
||||
- `DUMP_CONST_RAM` выполняет процессор (дампы читает только транслятор — это его таблицы
|
||||
описателей, видеочип их не читает ни разу);
|
||||
- DMA и `WRITE_DATA` в память не «на месте» выполняет процессор (туда приходят код шейдеров и
|
||||
описатели, которые транслятор читает сразу);
|
||||
- SRT walker (код шейдера, переведённый в x86) обходит таблицы на процессоре;
|
||||
- шейдер специализируется по полям описателей (шаг, формат, перестановка каналов, тип
|
||||
изображения), конвейер выбирается по регистрам;
|
||||
- текстуры узнаются по адресу и формату из T#.
|
||||
|
||||
Перенос на видеочип — новый режим рекомпилятора: шейдер сам читает V# (адрес через BDA-арену,
|
||||
формат и шаг — во время выполнения), изображения через bindless-массив с таблицей T# → индекс,
|
||||
барьеры из сбросов кэшей самой игры (`ACQUIRE_MEM`/`EVENT_WRITE`), резидентность всей памяти
|
||||
сразу при отображении. Это несколько дней работы с риском для картинки: не начато.
|
||||
|
||||
Остальное — не эмуляция в смысле «вместо видеочипа»: перевод GCN → SPIR-V во время игры (как у
|
||||
DXVK), синтетический vblank (HLE VideoOut), заглушки для неиспользуемых игрой пакетов
|
||||
(occlusion — фиктивный счётчик, `SET_PREDICATION`, `COND_EXEC`, `MEM_SEMAPHORE`).
|
||||
|
||||
## Шаг 4 (2026-10-08): где проходит граница между эмуляцией и трансляцией
|
||||
|
||||
Замер (`BB_RES_STATS=1`, Кошмар охотника у лампы): ~97 тыс. привязок ресурсов в секунду; почти у
|
||||
каждой (0,96 стадии на привязку) описатели лежат в таблицах в памяти, а не в регистрах: на одну
|
||||
привязку буферы — 1,3 из регистров и 2,8 из памяти, текстуры — 0,6 и 3,3, сэмплеры — 1,0 и 0,9.
|
||||
|
||||
**Описатели на видеочипе для текстур сделать правильно нельзя.** Текстура PS4 лежит в памяти в
|
||||
раскладке PS4 (tiling), которой нет в Vulkan: её нужно перевести в образ Vulkan, а для этого
|
||||
процессор должен знать T# (адрес, формат, размеры) до того, как отрисовка выполнится. Если шейдер
|
||||
будет сам читать T# из памяти и искать образ в таблице на видеочипе, то при первом использовании
|
||||
текстуры образа ещё нет — правильного запасного пути у видеочипа нет (только кадр с неверной
|
||||
текстурой). Поэтому чтение описателей процессором (SRT walker, таблицы, которые заполняет движок
|
||||
констант) — не эмуляция, а трансляция привязки ресурсов, как у DXVK и vkd3d, которые тоже читают
|
||||
привязки на процессоре и создают по ним объекты Vulkan. Для буферов описатели на видеочипе
|
||||
возможны (BDA), но выигрыш — только запись дескрипторов (~0,1 мкс на пакет), если не переделать
|
||||
резидентность и барьеры целиком; не начато.
|
||||
|
||||
Что из эмуляции на самом деле осталось, и используется ли это Bloodborne:
|
||||
|
||||
| Что | Bloodborne |
|
||||
|---|---|
|
||||
| Ожидание флипа (метка VideoOut) блокирует поток разбора | да — это синхронизация с дисплеем, как ожидание в Present у DXVK |
|
||||
| Счётчик occlusion-запросов — фиктивный | **да**, ~60 событий `ZPASS_DONE` за кадр (в счётчике пакетов их не было видно: они во вложенных буферах); переведено — «Шаг 5» |
|
||||
| `SET_PREDICATION`, `COND_EXEC`, `COPY_DATA` не транслируются | нет; переведено — «Шаг 5» |
|
||||
| `MEM_SEMAPHORE`, `REWIND` — ожидание на процессоре | нет |
|
||||
| Очереди compute (ASC) — корутины в одном потоке | нет (0 отправок за кадр) |
|
||||
| Запись процессором в память, видимую видеочипу, «объявляется» (обёртки `memcpy`, перехваты загрузчиков, кучи, аллокатора, чтение файлов) | да — для копий в VRAM и текстур; `BB_WRITE_VERIFY` на маршруте не нашёл необъявленных записей, кроме ожидаемых |
|
||||
| Шейдер копирования игры — подмена (список читается процессором) | да; теперь можно транслировать (`BB_COPY_SHADER_NATIVE=1`) |
|
||||
|
||||
Строки «нет» нужны только при переносе на другие игры (Driveclub, inFAMOUS: там асинхронный
|
||||
compute и occlusion есть) — проверить их на Bloodborne нельзя.
|
||||
|
||||
Шейдер копирования (`cs fefebf9f`): подмену можно заменить трансляцией самого шейдера. Она нашла
|
||||
настоящую опасность новой модели: рядом с тем, куда он копирует, лежат код шейдеров и таблицы
|
||||
описателей, которые транслятор читает процессором; если эти блоки уходят в VRAM («видеочип пишет
|
||||
это 3 кадра из 60»), транслятор читает устаревший код (чёрная сцена). Подмена обходила это
|
||||
случайно. Теперь записи этого шейдера всегда остаются в памяти игры. Общее правило «то, что
|
||||
видеочип пишет, но сам не читает, остаётся в памяти игры» (как readback heap) пробовалось: ломает
|
||||
производительность (видеочип 4,2 → 6,1 мс за кадр — так попадают в оперативную память и области
|
||||
размером с цели рендера), убрано.
|
||||
|
||||
Константы: копирование в кольцо при разборе было нужно, пока записи командного процессора
|
||||
выполнялись процессором «наперёд». В новой модели оно больше не нужно для правильности —
|
||||
`BB_CONSTANTS_IN_PLACE=1` читает их на месте; по умолчанию выключено (видеочип +3 % из-за PCIe,
|
||||
FPS не выше). То есть кольцо констант теперь — оптимизация (копия в VRAM), а не эмуляция.
|
||||
|
||||
Дальше по трансляции для Bloodborne делать почти нечего; дальнейшая работа — производительность
|
||||
(стадия B на слабых процессорах) и перенос: настоящие запросы occlusion (`vkCmdBeginQuery` +
|
||||
копия результата видеочипом в память игры), предикация (`VK_EXT_conditional_rendering`),
|
||||
очереди compute как очередь Vulkan с семафорами — когда появится игра, которая их использует.
|
||||
|
||||
## Шаг 5 (2026-10-08): occlusion-запросы, COPY_DATA, COND_EXEC, предикация
|
||||
|
||||
Поправка к шагам 3 и 4: Bloodborne **использует** occlusion-запросы — около 60 событий
|
||||
`ZPASS_DONE` за кадр (~14 тыс. в секунду у лампы в Кошмаре охотника). Счётчик пакетов их не
|
||||
показал: он смотрел только буферы команд верхнего уровня. До сих пор игра получала фиктивную
|
||||
последовательность «всё видно».
|
||||
|
||||
Сделано (только новая модель; `BB_OCCLUSION_QUERIES=0` — как раньше, бит 4 второго числа
|
||||
`BB_TOGGLE_FILE` — переключение на ходу):
|
||||
|
||||
- **Occlusion-запросы → запросы Vulkan** (`vk_occlusion.{h,cpp}`, `occlusion_resolve.comp`). На
|
||||
PS4 каждый блок глубины ведёт счётчик сэмплов, прошедших тест глубины; событие `ZPASS_DONE`
|
||||
записывает счётчики (16 байт между блоками, бит готовности 63), игра берёт разность «после −
|
||||
до». Здесь с первого события всегда активен запрос Vulkan; он режется на отрезки на каждом
|
||||
событии и на каждой границе, через которую запрос нельзя протянуть (начало и конец прохода
|
||||
рендера, буфер команд — `Scheduler::CarriedScope`, Suspend/Resume). Событие запоминает, сколько
|
||||
отрезков закончилось до него. В ближайшей точке вне прохода результаты отрезков копируются, и
|
||||
один запуск шейдера складывает их по порядку и пишет каждому ожидающему событию его накопленное
|
||||
значение в память игры через адрес видеочипа (BDA). Проходы рендера не разрываются; цена на
|
||||
4 ядрах — в пределах шума (220 против 219 FPS с фиктивным счётчиком, видеочип 4,54 против
|
||||
4,56 мс). Результаты появляются чуть позже, чем на PS4, до этого бит готовности снят — игра
|
||||
проверяет его, как на PS4. Счётчики всегда остаются в памяти игры (их читает процессор).
|
||||
- **COPY_DATA**: значения, известные при разборе (константа, регистр, часы), записывает видеочип
|
||||
в порядке потока, как `WRITE_DATA`; копии из памяти и GDS — копии видеочипа в порядке потока,
|
||||
как `DMA_DATA`. Запись в регистр — пока предупреждение.
|
||||
- **COND_EXEC**: условие берётся из ожидающей метки в порядке потока, иначе — после того, как
|
||||
видеочип выполнил работу до пакета (раньше читалось при разборе, впереди видеочипа).
|
||||
- **SET_PREDICATION** (ZPass, Bool64, Bool32; CONTINUE — ИЛИ с предыдущим): пропускаются
|
||||
отрисовки и запуски, у которых в заголовке стоит бит предиката. Предикат вычисляется при
|
||||
первом таком пакете, после того как видеочип выполнил работу до него (правильно, но с
|
||||
ожиданием видеочипа; Bloodborne предикацию не использует). `VK_EXT_conditional_rendering` не
|
||||
подошёл: он пропускал бы и наши внутренние запуски (детайлинг текстур, копии), и не переходит
|
||||
через границы буферов команд.
|
||||
|
||||
Проверка: `BB_PM4_SELFTEST=1` — через ~40 с игры в отправки игры подмешиваются тестовые пакеты
|
||||
(в память, выделенную самой проверкой), через несколько секунд результаты сверяются: occlusion
|
||||
по отправке с отрисовками (> 0 сэмплов) и пустой интервал (0), `COPY_DATA` (32 и 64 бита,
|
||||
память → память в порядке, часы), `COND_EXEC` (ложь пропускает запись, истина выполняет),
|
||||
предикация (ZPass и Bool32: 2 из 4 пустых предикатных отрисовок пропущены). Все 8 — PASS. Слой
|
||||
валидации Vulkan на 67 тыс. событий не нашёл нарушений правил запросов.
|
||||
|
||||
Попутно найдена и исправлена взаимоблокировка: чтение памяти для процессора из потока разбора
|
||||
(`ReadMemory`) отправляло задачу этому же потоку и ждало её.
|
||||
|
||||
Что осталось без перевода: очереди compute (ASC) по-прежнему выполняются в том же потоке, что и
|
||||
графика, и отправляются в ту же очередь Vulkan (это допустимая сериализация); `MEM_SEMAPHORE`
|
||||
меняет счётчик процессором при разборе; время видеочипа в метках и `COPY_DATA` — часы процессора.
|
||||
Bloodborne ничего из этого не использует.
|
||||
|
||||
## Дополнение 2026-10-09: copy shader в модуле памяти и GPU-метки без AMD-расширения
|
||||
|
||||
В `BB_PC_MODEL=1 BB_LAYER_MEMORY=1` оригинальный `fefebf9f` теперь исполняется по умолчанию: paged stores обновляют память игры и актуальные зеркала. HLE доступен через `BB_COPY_SHADER_NATIVE=0`.
|
||||
|
||||
Без `VK_AMD_buffer_marker` метки EOP/EOS и применимый RELEASE_MEM пишет GPU стандартными Vulkan updates с барьерами. AMD сохраняет быстрые markers; `BB_GPU_LABELS_PORTABLE=1` позволяет проверить переносимый вариант. Подробности и аппаратные ограничения: [TRANSLATION_GPU_COPY_LABELS.ru.md](TRANSLATION_GPU_COPY_LABELS.ru.md).
|
||||
|
||||
## План от 2026-10-10: что осталось и как перевести на трансляцию
|
||||
|
||||
Требование: одинаково для AMD и NVIDIA и без опоры на код именно Bloodborne (адреса функций игры,
|
||||
хеши её шейдеров) там, где модель трансляции отвечает за правильность.
|
||||
|
||||
| Что осталось | Где | Почему это эмуляция или привязка |
|
||||
|---|---|---|
|
||||
| Модель памяти 0.3 (копии в VRAM с защитой страниц, обратное чтение, `fault_manager`, BB_UFFD, предзагрузка) | **по умолчанию** | делает вместо видеочипа то, чего у ПК нет: одна память |
|
||||
| Два пути новой модели: sparse-арена (AMD) и модуль памяти (остальные) | новая модель | sparse-перепривязки — путь одного производителя (на NVIDIA держат видеочип секундами) |
|
||||
| Записи загрузчиков в текстуры и буферы видны видеочипу через перехваты кода Bloodborne (`memcpy` игры в 0x216d420/0x2171d60, аллокатор 0x26aa070, куча TLSF) | новая модель | в другой игре этих адресов нет: записи остались бы незамеченными |
|
||||
| `MEM_SEMAPHORE` меняется при разборе (впереди видеочипа), время видеочипа в метках и `COPY_DATA` — часы процессора, очереди compute — в одном потоке с графикой | командный процессор | Bloodborne не использует; нужно для других игр |
|
||||
| CE и SRT walker читают описатели на процессоре, кэш текстур по адресу, синтетический vblank | — | трансляция привязок и платформенный слой, как у DXVK/vkd3d (шаг 4): остаётся |
|
||||
|
||||
Этапы:
|
||||
|
||||
1. **Один путь памяти для всех GPU.** Модуль памяти — по умолчанию и на AMD (на маршруте 2026-10-09
|
||||
вровень со sparse-ареной: p99 5,1–7,5 мс против 5,2–6,0). Sparse-арена — запасной вариант
|
||||
`BB_LAYER_MEMORY=0`. Заодно на AMD по умолчанию исполняется сам шейдер копирования игры вместо
|
||||
подмены по его хешу.
|
||||
2. **Согласование памяти без перехватов игры.** Записи процессора в память, которую видеочип
|
||||
держит как образ или копию, ловятся общим механизмом (ловушки записи по 64 КиБ, как у модуля),
|
||||
перехваты Bloodborne остаются ускорением (меньше срабатываний ловушек). Проверка: прогон с
|
||||
`BB_GUEST_HOOKS=0` и `BB_WRITE_VERIFY=1` (необъявленные записи), картинка и время кадра.
|
||||
3. **Остатки командного процессора — на видеочип.** `MEM_SEMAPHORE`: сигнал — запись видеочипа в
|
||||
порядке потока; время видеочипа — метки времени Vulkan, переведённые в часы PS4 (100 МГц) и
|
||||
записанные видеочипом. Проверка — `BB_PM4_SELFTEST` (Bloodborne их не использует). Очереди
|
||||
compute как отдельная очередь Vulkan — после, когда будет игра для проверки.
|
||||
4. **Новая модель по умолчанию** (после логов с NVIDIA на 0.5-pre4), затем удаление модели 0.3 и
|
||||
sparse-арены.
|
||||
|
||||
### Ход работ (2026-10-10)
|
||||
|
||||
- **Этап 1 сделан:** модуль памяти по умолчанию на всех GPU (на AMD тоже; sparse-арена —
|
||||
`BB_LAYER_MEMORY=0`), 0 вызовов `vkQueueBindSparse`.
|
||||
- **Модульность:** профили игр `gpu/games/` (`game_profile.h`, `bloodborne.cpp`): перехваты кода
|
||||
игры, её код, пишущий данные каждый кадр, хеш шейдера копирования — только там. Профиль
|
||||
выбирается по TITLE_ID; `BB_GAME_PROFILE=none` — игра как незнакомая, `BB_GUEST_HOOKS=0` — без
|
||||
перехватов.
|
||||
- **Этап 2, запись:** общий учёт ловушек записи в рантайме (`runtime_memory_trap`, причины по
|
||||
страницам; `gpu/layer/bblayer_write_traps.h`): копии модуля в VRAM и текстуры ловят записи на
|
||||
одних страницах, не мешая друг другу; `mprotect` игры ловушки сохраняет, новое отображение
|
||||
начинается без них (тест в `tests/test_runtime.c`). Страницы под текстурами под ловушками по
|
||||
умолчанию (`BB_IMAGE_TRAPS=0` — как раньше), записи libc и перехваченных загрузчиков
|
||||
объявляются до записи (без срабатываний). На маршруте с профилем — как без ловушек (4,18 мс
|
||||
видеочипа), 4–29 срабатываний за 5 с.
|
||||
- **Найдено попутно — разброс «некоторые запуски в 1,3–1,7 раза медленнее»:** «летучие» блоки
|
||||
модуля (блоки, в которые пишет процессор, в общей копии диапазона в VRAM) обновлялись копией
|
||||
перед каждой привязкой на чтение; копия разрывает проход рендера, и объёмы света (по отрисовке на
|
||||
каждый) рвали свой проход до ~100 раз за кадр (`BB_PASS_BREAK_TRACE=1`: 92–109 тыс. разрывов за
|
||||
5 с из `Runtime::CopyBuffer` против ~700). Летучие блоки выключены по умолчанию
|
||||
(`BB_LAYER_VOLATILE=1` — включить; тогда чтения внутри открытого прохода идут на месте): 10 из 10
|
||||
прогонов быстрые. Порядок «результаты occlusion до последующих меток»
|
||||
(`OcclusionQueries::WriteBeforeLabel`) оставлен как поведение PS4, но причиной не был: игра читает
|
||||
результаты через 2–10 мс и находит их записанными (`BB_OCCLUSION_READ_TRACE=1`, ловушки на
|
||||
чтение в рантайме — причины от 16 делают страницу без доступа).
|
||||
- **Незнакомая игра (этап 2, чтение):** прежний вывод «без профиля — снова медленный режим» был
|
||||
тем же разбросом из-за летучих блоков (один прогон). Без профиля маршрут идёт так же быстро
|
||||
(3 из 3, 4,29–4,30 мс) и выглядит так же. Ловушки на чтение для данных, которые есть только в
|
||||
VRAM (копия обратно при чтении процессором), остаются нужны для правильности в других играх;
|
||||
механизм причин «без доступа» в рантайме уже есть и проверен тестом.
|
||||
- **Этап 3 сделан (кроме отдельной очереди compute):**
|
||||
- `MEM_SEMAPHORE`: сигнал — в порядке потока, после завершения работы видеочипа перед ним
|
||||
(`RunMemSemaphoreSignal`, как метки EOP); ожидание сбрасывает конвейер записи и отправляет
|
||||
записанное, как `WAIT_REG_MEM`. `BB_MEM_SEMAPHORE_ORDER=0` — сигнал при разборе (как раньше).
|
||||
- Часы видеочипа: у PS4 100 МГц; теперь одна шкала для процессора и видеочипа — монотонные часы
|
||||
хоста в тиках по 10 нс (`GetGpuClock64`, раньше — настенные наносекунды). Метки EOP/RELEASE_MEM
|
||||
с часами и `COPY_DATA` из часов пишет сам видеочип: метка времени Vulkan → копия из запроса →
|
||||
короткий шейдер (`gpu_timestamp.comp`) переводит в шкалу PS4 и пишет в память игры
|
||||
(`vk_timestamps.cpp`). Привязка тиков к часам хоста — один раз, на простаивающем видеочипе
|
||||
(на RX 7800 XT точность 152 мкс). `BB_GPU_TIMESTAMPS=0` — пишет процессор.
|
||||
- Проверка — `BB_PM4_SELFTEST=1`: 11 из 11; новые проверки «сигнал не раньше предшествующей
|
||||
метки» (со старым порядком — FAIL), «ожидание и списание», «часы 100 МГц в шкале процессора»
|
||||
(2 с между метками: 1,961–1,999 с). Bloodborne ни то, ни другое не использует.
|
||||
- **Этап 2, чтение — готово, включается `BB_LAYER_READ_TRAPS=1`:** то, что видеочип записал только в
|
||||
копию в VRAM (запись привязки в зеркало), под ловушкой «без доступа» (причина `VramData` в
|
||||
рантайме); первое касание процессора (игра, транслятор, libc рантайма) копирует данные блока
|
||||
назад через очередь обратного чтения (`FastReadback`, как в модели 0.3: ждёт только тик этой
|
||||
записи) и снимает ловушку. Запись в файл из такой страницы сначала её читает (`touch_for_read`),
|
||||
чтение файла в неё — уже касалось (`touch_for_write`). На маршруте: 60 МиБ под ловушками, 0–1
|
||||
касание за 10 с, время видеочипа то же (4,28–4,30 мс). Bloodborne такие данные процессором не
|
||||
читает, поэтому само копирование назад здесь не проверено — по умолчанию выключено до игры,
|
||||
которой это нужно.
|
||||
- **Пересмотрено перед 0.5 (вечер 2026-10-10):** на AMD по умолчанию снова sparse-арена
|
||||
(`BB_LAYER_MEMORY=1` — модуль). Модуль шёл вровень с ареной на маршруте в Ярнаме, но в Сне
|
||||
охотника держал вне VRAM ~1 ГБ данных области (диапазоны с несколькими «неизвестными» блоками
|
||||
без летучих блоков остаются на месте): 100 FPS против 154 с ареной; с летучими блоками — 120, но
|
||||
с ~225 тыс. срабатываний ловушек в секунду. Нужен доступ по блокам для смешанных диапазонов.
|
||||
Occlusion-запросы как запросы Vulkan — по выбору (`BB_OCCLUSION_QUERIES=1`): с апскейлером
|
||||
(сцена меньше и со сдвигом) свечение лампы вспыхивало серым на весь кадр; счёт нужно сделать
|
||||
устойчивым к этому. Фоновая компиляция больше не пропускает полноэкранные проходы (серые кадры).
|
||||
@@ -0,0 +1,775 @@
|
||||
# Environment variables
|
||||
|
||||
The port reads a few hundred `BB_*` environment variables. Some are for players (upscaler, frame
|
||||
rate, controls); most are diagnostics and developer switches. The list below is every one the code
|
||||
reads, from the port's own registry (`gpu/shim/bb_env_vars.def`); `tests/test_env_vars.py` fails
|
||||
when the code and the registry disagree, so it stays complete. Everything from "Every `BB_*`
|
||||
variable" on is the output of `python3 scripts/env_vars.py --doc` (without its title); the sections
|
||||
above it are written by hand.
|
||||
|
||||
## How to set them
|
||||
|
||||
**In the launcher:** *Advanced → Developer → Extra variables (NAME=value, space-separated)*. The
|
||||
settings key is `extra_env` in the launcher's `settings.json`, read by `game_environment()` in
|
||||
`launcher/bbport_launcher.py`:
|
||||
|
||||
- The field is split on whitespace, and each piece at its **first** `=`:
|
||||
`BB_UPSCALER=fsr4 BB_AUDIO_GAIN=1.5 RADV_DEBUG=nohiz`.
|
||||
- **There is no quoting.** A value cannot contain spaces, and quotes stay part of the value. Use
|
||||
separators the variable itself accepts, such as `;` or `,`.
|
||||
- A piece without `=` is ignored. If a name appears twice, the last one wins.
|
||||
- Any name is accepted, third-party ones included (`RADV_DEBUG`, `MANGOHUD`, `VK_*`, `__GL_*`).
|
||||
- The result goes to `run.sh`. Both the launcher's *Play* button and `--play` (a Steam shortcut,
|
||||
the Steam Deck's Game Mode) use it.
|
||||
|
||||
**From a terminal (source checkout):** `BB_GAME_DIR=/path/to/CUSA03173 BB_FPS=60 bash run.sh`.
|
||||
Without the launcher, nothing sets the launcher's variables. `run.sh` then uses its own defaults
|
||||
(game folder `../CUSA03173`, uncapped frame rate, and so on).
|
||||
|
||||
**From a terminal (AppImage):** `BB_UPSCALER=fsr4 ./Bloodborne-bbport-x86_64.AppImage --play`
|
||||
starts the game with the launcher's saved settings. Mind the precedence below: the launcher's own
|
||||
settings replace a variable of the same name from the shell. Extra variables replace both. The
|
||||
AppImage keeps its data (bbport.ini, saves, logs) in `BB_DATA_DIR`, by default
|
||||
`~/.local/share/bbport`.
|
||||
|
||||
**bbport.ini:** many graphics options live in `bbport.ini` and the in-game menu, not in the
|
||||
environment. A few variables override those keys at start: `BB_UPSCALER`, `BB_UPSCALE_PRESET`,
|
||||
`BB_FSR_SHARPNESS`, `BB_JITTER`, `BB_REACTIVE*` and `BB_OBJECT_MOTION`
|
||||
(`gpu/shim/bbport_settings.cpp`). Values set this way stay in force until the next start.
|
||||
|
||||
## Precedence
|
||||
|
||||
The environment is built in this order; a later step wins:
|
||||
|
||||
1. The environment the launcher (or the AppImage) was started with.
|
||||
2. The launcher's own settings (`game_environment()`): some are always written (the game folder,
|
||||
mods, languages, frame rate, fullscreen and the like) and replace a shell value; others only
|
||||
when the setting is on.
|
||||
3. The launcher's **Extra variables**.
|
||||
4. `run.sh`, which fills in what is still unset (the data folder, the online module, Discord).
|
||||
|
||||
Every `BB_*` variable the port reads, by kind, from `gpu/shim/bb_env_vars.def`
|
||||
(`scripts/env_vars.py --doc`). The game prints the variables set
|
||||
at its start (`Environment:` in the log) and warns about `BB_*` names it does not know.
|
||||
|
||||
`=1: ...` / `=0: ...` / `=N: ...` say what that value does; `set: ...` means any value
|
||||
turns it on (even `0`).
|
||||
|
||||
## Settings (107)
|
||||
|
||||
What players and the launchers set. The menu, bbport.ini and the launcher write most of them; set one by hand to override them.
|
||||
|
||||
| Variable | What it does |
|
||||
|---|---|
|
||||
| `BB_ANISO` | =N: anisotropic filtering of scene textures (default 16) |
|
||||
| `BB_ASYNC_PIPELINES` | =0: new pipelines of passes drawn every frame compiled at once instead of skipping a draw for a frame or two |
|
||||
| `BB_ATLAS_ENABLED` | =1 / 0: Hunter's Atlas, the in-game map, on or off (the launcher's Map switch; unset: the map's own settings.ini) |
|
||||
| `BB_AUTO_RENDER_RES` | =1: render resolution picked from the measured frame rate |
|
||||
| `BB_BBSC` | =1: seamless co-op (BBSC), the launcher's co-op switch |
|
||||
| `BB_BBSC_LOCAL_SERVER` | =1 / auto (default) / 0: the BBSC game server on this PC (scripts/bbsc_gameserver) |
|
||||
| `BB_BUTTON_PROMPTS` | =0: the game's PlayStation button icons always |
|
||||
| `BB_CJK_FONT` | =path: a font file with Chinese characters for the menu in Simplified Chinese (default: fontconfig's choice for zh-cn) |
|
||||
| `BB_COMMUNITY_SERVER` | URL of the online pool server (default: the community server) |
|
||||
| `BB_CONFIG` | =path: the controls file (default bbport.ini) |
|
||||
| `BB_CONTENT_SKU` | =full / ...: the content profile run.py prepares |
|
||||
| `BB_CURSOR` | =0: the system's mouse cursor instead of the port's |
|
||||
| `BB_DECK_DESKTOP_INPUT` | =1: keep keyboard and mouse on a Steam Deck in game mode |
|
||||
| `BB_DISCORD` | =0: no Discord Rich Presence (the launcher's Discord switch) |
|
||||
| `BB_DISPLAY` | =N: the monitor to open the window on |
|
||||
| `BB_DLC_DIR` | =path: the dumped add-ons of this title, one folder each, linked as <user>/addcont/<title id> (the launcher's DLC folder) |
|
||||
| `BB_DLSS` | =0: no DLSS even on NVIDIA RTX |
|
||||
| `BB_DMEM_EXTRA_MB` | =N (0-2048, default 512): direct memory beyond the PS4's for the port's extras |
|
||||
| `BB_DMEM_MB` | =N: the game's direct memory size in MiB (PS4: 5056; raised for high resolutions and large heaps) |
|
||||
| `BB_EVDEV_TOUCHPAD` | =0: no DualSense/DualShock touchpad through evdev |
|
||||
| `BB_FFX_DIR` | the folder with AMD's FSR DLLs for Vulkan, amd_fidelityfx_loader_vk.dll and amd_fidelityfx_upscaler_vk.dll (upscaler fsr_amd; default <user>/fsr, then beside the game) |
|
||||
| `BB_FFX_FG_ML` | =1: AMD's ML frame generation (4.0.x) accepts any GPU (AMD ships it for RDNA4); =0: refuses it (FSR 3.1) |
|
||||
| `BB_FFX_FG_VERSION` | =<part of a name>: the frame generation AMD's DLL runs, from those the log lists (default: theirs) |
|
||||
| `BB_FFX_VERSION` | =<part of a name>: the upscaler AMD's FSR DLLs run, from those the log lists (default: theirs) |
|
||||
| `BB_FPS` | =uncap (default) / 90 / 60 / 30: the frame rate (the launcher's) |
|
||||
| `BB_FPS_LIMIT` | =N: frame rate limit (the launcher's) |
|
||||
| `BB_FRAME_GEN` | =fsr3 / dlss / fsr_amd (1: fsr3): frame generation |
|
||||
| `BB_FRAME_GEN_MULTIPLIER` | =2..4: frames shown per rendered one with frame generation |
|
||||
| `BB_FRAME_STATS` | set: frame statistics in the log every 5 s and slow frames (the launcher's statistics switch) |
|
||||
| `BB_FSR411_DIR` | the FSR 4.1.1 assets folder (default fsr4_411) |
|
||||
| `BB_FSR411_VARIANT` | =int8 / fp8 / fp8emu / navi2 / lite: FSR 4.1.1 model variant (default: the menu's model) |
|
||||
| `BB_FSR4_DIR` | the FSR 4 v07 assets folder (default fsr4_shaders) |
|
||||
| `BB_FSR_SHARPNESS` | =0..1: RCAS sharpening (the menu's) |
|
||||
| `BB_FULLSCREEN` | =0 / 1 / exclusive: window mode |
|
||||
| `BB_GAMEPAD` | =SDL GUID or part of the name: the controller to use (the launcher's) |
|
||||
| `BB_GAME_DIR` | the game's folder (with eboot.bin) |
|
||||
| `BB_GAME_MENU` | =0: no port pages in the game's System menu |
|
||||
| `BB_GESTURES_NO_MOTION` | =1: the gestures menu without the motion-control icons (Dommo's mod; the launcher's switch) |
|
||||
| `BB_GPU_ID` | =N: the GPU to use, by Vulkan index (the launcher's) |
|
||||
| `BB_GPU_PROFILE` | =1: GPU profiler: time per labelled pass in the log (the launcher's) |
|
||||
| `BB_HDR` | =1: HDR output allowed |
|
||||
| `BB_HUD_ANCHOR` | =0: on wide screens the HUD stays in the 16:9 middle |
|
||||
| `BB_INTEL_TONEMAP_FIX` | =0 / 1: Intel CPU tone mapping fix patch off / on (default on on Intel) |
|
||||
| `BB_JITTER` | =0 / 1: upscaler camera jitter (the menu's) |
|
||||
| `BB_JUMP_GAP_MS` | =N (default 70): pause around a keyboard jump while running |
|
||||
| `BB_JUMP_TAP_MS` | =N (default 70): press length of a keyboard jump while running |
|
||||
| `BB_KEYBOARD` | =0: local co-op, this game ignores the keyboard and the mouse |
|
||||
| `BB_KEY_FALL_MS` | =N (default 45): keyboard stick release time |
|
||||
| `BB_KEY_RISE_MS` | =N (default 70): keyboard stick rise time |
|
||||
| `BB_KEY_TURN_DPS` | =N (default 1100): keyboard stick turn speed in degrees a second |
|
||||
| `BB_LANGUAGE` | =N: the game's language, PS4 number (1 English, 8 Russian; the launcher's) |
|
||||
| `BB_LARGE_HEAPS` | =1 / 0: larger graphics heaps for texture packs on / off (default: detected) |
|
||||
| `BB_LAYER_MEMORY` | =1 / 0: the layer's memory module (default on non-AMD GPUs) |
|
||||
| `BB_LOW_LATENCY` | =off / on / boost: NVIDIA Reflex / AMD Anti-Lag (the menu's) |
|
||||
| `BB_MAP_ART_DIR` | folder: the map's pictures (the launcher's) |
|
||||
| `BB_MAP_CATALOG` | file: the names of the map's places and items (the launcher's) |
|
||||
| `BB_MAP_CONFIG` | file: the map's settings (the launcher's) |
|
||||
| `BB_MAP_DIR` | folder: the maps Hunter's Atlas generated (the launcher's) |
|
||||
| `BB_MAP_FOG_ENABLED` | =1: areas not visited yet stay hidden on the map |
|
||||
| `BB_MAP_FONT` | file: the font of the map's names (the launcher's) |
|
||||
| `BB_MAP_ITEMS_ENABLED` | =1: items on the map (the launcher's) |
|
||||
| `BB_MAP_ITEM_CATEGORIES` | =list: the items the map shows (healing,insight,equipment,echoes) |
|
||||
| `BB_MAP_NPC_MODE` | =all / new_dialogue / ...: the NPCs the map shows |
|
||||
| `BB_MAP_SOUND_GAIN` | =0-2: the level of the map's cursor sounds (0.024, the game's own quick-menu cursor) |
|
||||
| `BB_MAP_TRAVEL_ENABLED` | =1: travel to lamps from the map (the launcher's) |
|
||||
| `BB_MENU_LANGUAGE` | =ru / en / ...: the port's menu language (default: the launcher's, else the system's) |
|
||||
| `BB_MENU_POINTER` | =0: no mouse pointer in the game's menus |
|
||||
| `BB_MODS_ENABLED` | =0: no mods |
|
||||
| `BB_MOUSE_AUTO_ROTATE` | =1: the game's camera auto rotation also with the mouse |
|
||||
| `BB_MOUSE_DEADZONE` | =N (default 16 of 127): mouse look dead zone |
|
||||
| `BB_NATIVE_MOUSE` | =0: mouse look through the right stick instead of the camera hook |
|
||||
| `BB_NET_BACKEND` | =thd: The Hunter's Dream online pool (the launcher's online pool) |
|
||||
| `BB_OBJECT_MOTION` | =0 / 1: motion vectors of moving objects (the menu's) |
|
||||
| `BB_ONLINE` | =1: online play (the launcher's) |
|
||||
| `BB_P2P_PORT` | =N: the UDP port for P2P online traffic (default 3658, 0: any) |
|
||||
| `BB_PAD_BACKGROUND` | =1: local co-op, the controller keeps driving the game and its sound plays while another window has the focus |
|
||||
| `BB_PAD_DEADZONE` | =N (default 5): stick inner dead zone |
|
||||
| `BB_PAD_DEADZONE_OUTER` | =N (default 127): stick outer limit |
|
||||
| `BB_PAD_LIGHTBAR` | =RRGGBB: the light bar colour of DualShock 4 and DualSense pads (the launcher's Controls) |
|
||||
| `BB_PAD_MOTION` | =0 / 1: motion sensor emulation (over bbport.ini pad_motion) |
|
||||
| `BB_PATCHES` | =name,...: extra patches by name (patches.py) |
|
||||
| `BB_PROCESS_PRIORITY` | =normal / above / high: process priority on Windows (default above) |
|
||||
| `BB_REACTIVE` | =1: FSR reactive mask (the menu's) |
|
||||
| `BB_REACTIVE_MAX` | reactive mask maximum (the menu's) |
|
||||
| `BB_REACTIVE_SCALE` | reactive mask scale (the menu's) |
|
||||
| `BB_REACTIVE_THRESHOLD` | reactive mask threshold (the menu's) |
|
||||
| `BB_RENDER_RES` | =WxH: render resolution through the startup patch (compatibility) |
|
||||
| `BB_SAVE_LOG` | =1: the log and frame statistics also kept in logs/ |
|
||||
| `BB_SEAMLESSHUNT_TOMBS` | =1: keep Seamless Hunt's bloodstains and death visions (they crash the game; scripts/online.py) |
|
||||
| `BB_SHADER_PROGRESS` | =0: no shader compilation progress on screen |
|
||||
| `BB_SHADNET_NPID` | the shadNet account name (the launcher's) |
|
||||
| `BB_SHADNET_PASSWORD` | the shadNet account password (the launcher's) |
|
||||
| `BB_SHADNET_SERVER` | the shadNet server |
|
||||
| `BB_SHADNET_TOKEN` | the shadNet account token (the launcher's) |
|
||||
| `BB_SHADNET_WEBAPI` | the shadNet web API server |
|
||||
| `BB_SKIP_CPU_CHECK` | =1: skip the check of the processor's instructions (AVX, BMI1, LZCNT...) before the game starts |
|
||||
| `BB_SKIP_GAME_CHECK` | =1: start with game files that did not pass the check |
|
||||
| `BB_STEAM_INPUT_X11` | =0: keep the native Wayland window when Steam made a virtual controller for the game (default: Xwayland, so Steam Input reaches it) |
|
||||
| `BB_TEXTURE_QUALITY` | texture quality over bbport.ini's texture_quality |
|
||||
| `BB_THD_ONLINE_ID` | The Hunter's Dream online name (the launcher's) |
|
||||
| `BB_THD_TOKEN` | The Hunter's Dream account token (the launcher's) |
|
||||
| `BB_TROPHY_KEY` | the trophy data key (from the environment or the port's .env) |
|
||||
| `BB_UPNP` | =1: UPnP port forwarding for online play |
|
||||
| `BB_UPSCALER` | =none / fsr3 / fsr4 / dlss / ...: the upscaler over the menu's (scripts, A/B) |
|
||||
| `BB_UPSCALE_PRESET` | the upscaler quality preset (the menu's) |
|
||||
| `BB_USER_NAME` | the PS4 user name (default Hunter) |
|
||||
| `BB_WINDOW_RECT` | =x,y,w,h: local co-op, the window's place and size as fractions of its display, without borders |
|
||||
|
||||
## Tuning (235)
|
||||
|
||||
Defaults to change on a machine where they misbehave, workarounds, and switches that turn a fix off for comparison.
|
||||
|
||||
| Variable | What it does |
|
||||
|---|---|
|
||||
| `BB_ADB` | path of adb for BB_APP0_ADB (default: adb) |
|
||||
| `BB_ANTI_LAG` | =1: AMD Anti-Lag 2 through Mesa's layer (measured ~0 gain, #289) |
|
||||
| `BB_APP0_ADB` | =serial: the game's files fetched from a phone over adb on first open |
|
||||
| `BB_ASYNC_PIPELINE_THREADS` | =N: threads compiling pipelines in the background |
|
||||
| `BB_ASYNC_SUBMIT` | =0: the presenter submits the frame itself instead of a submit thread |
|
||||
| `BB_AUDIO` | =none: no audio device (headless runs) |
|
||||
| `BB_AUDIO_GAIN` | =N (0-8, default 1): the game's sound level on top of the fold to the device's channels |
|
||||
| `BB_AUTO_NOHIZ` | =0 (run.sh): no automatic RADV_DEBUG=nohiz on the GPUs that need it |
|
||||
| `BB_AVPLAYER_ASYNC_READY` | =1: movie StateReady on the controller thread, as before (crashed at 200+ FPS) |
|
||||
| `BB_BDA_DEDICATED_MB` | =N: buffers with GPU addresses get dedicated memory from N MiB |
|
||||
| `BB_BIND_MEMO` | =0: every resource binding takes the full path (no memo) |
|
||||
| `BB_BREADCRUMBS` | =0: no GPU breadcrumbs (what the GPU was drawing at a hang) |
|
||||
| `BB_CHUNK_TEXTURES` | =0: texture data copied instead of read in place from guest memory chunks |
|
||||
| `BB_CHUNK_UPLOADS` | =0: uploads through CPU staging instead of binding guest memory in place |
|
||||
| `BB_COMMIT_IMPORT_LOCKED` | =1: guest memory commits import under the lock |
|
||||
| `BB_COMPUTE_RELEASE_FLUSH` | =0: work before a compute release waits for the rest of the frame |
|
||||
| `BB_CONSTANTS_IN_PLACE` | =1: shader constants bound in place from guest memory instead of copied |
|
||||
| `BB_CONSTANT_RING_MB` | =N: size of the constant ring in MiB |
|
||||
| `BB_CONSTRAM_GPU_WAIT` | =0: a constant RAM dump does not wait for queued draws reading it (DoF artifacts #44) |
|
||||
| `BB_CONSTRAM_PIPE` | =0: a constant RAM dump waits for all queued draws |
|
||||
| `BB_COPY_DEST_GAP` | =bytes: merge gap of copy shader destinations (default 256) |
|
||||
| `BB_COPY_DEST_PRECISE` | =0: the whole bound destination of a copy shader counts as written |
|
||||
| `BB_COPY_FILTER_GATE` | =1 with BB_COPY_SKIP_SAME: the copy filter off while the draw recording thread is over 92% busy |
|
||||
| `BB_COPY_GPU_BUFFERS` | =0: command buffers decoded from guest memory instead of a copy per submission |
|
||||
| `BB_COPY_MERGE_KB` | =N: copy shader ranges closer than N KiB merged (default 64) |
|
||||
| `BB_COPY_PAGED_RANGES` | =0: copy shader ranges whole instead of by page |
|
||||
| `BB_COPY_SHADER_NATIVE` | =0: the copy shader's list read on the CPU |
|
||||
| `BB_COPY_SKIP_SAME` | =0: off (default on): of the game's copy shader calls only the copies that change their destinations run (checked on thread bb:CopyFilter) |
|
||||
| `BB_COPY_THREADS` | =N (0-16): threads for large guest memory copies |
|
||||
| `BB_CORE_RESERVE` | =0 / 1: keep the game's threads off a reserved core (Linux, SMT CPUs with 6+ cores) |
|
||||
| `BB_CRASH_GUARDS` | =0: without the guards of the game's own unchecked pointers that crashed players (scripts/crash_guards.py) |
|
||||
| `BB_DEAD_IMAGES` | =0: images of freed memory left to the collector instead of dropped on the GPU thread |
|
||||
| `BB_DEAD_IMAGES_LATE` | set: dead images dropped at the frame's end only, as before |
|
||||
| `BB_DETILE_SKIPPED` | =1: texture levels the quality setting leaves out are still detiled and read |
|
||||
| `BB_DIRECT_IMPORT_LOCKED` | =1: direct memory imports under the lock |
|
||||
| `BB_DIRECT_MEMORY_ACCESS` | =1: shadPS4's direct memory access mode of the shader translator |
|
||||
| `BB_DIRECT_SEGMENT` | =0: the presenter waits for the recording threads instead of taking its own command segment |
|
||||
| `BB_DISABLE_HTTPS` | =1: the game's HTTPS requests go out as plain HTTP (online) |
|
||||
| `BB_DRAW_PIPE` | =0 / N: the draw pipeline off, or its depth |
|
||||
| `BB_FAST_WRITER_FAULTS` | =1: fast path for guest write faults (off: a device loss on a GTX 1050) |
|
||||
| `BB_FAULT_WINDOW` | =KiB: window unprotected around a guest write fault (default 256) |
|
||||
| `BB_FFX_OWN_SHADERS` | =0: FSR frame generation with the packaged shaders instead of the port's own |
|
||||
| `BB_FG_ASYNC` | =1: FSR 3.1 frame generation on a second compute queue (no gain measured) |
|
||||
| `BB_FG_EARLY_RETIRE` | =0: with frame generation the frame counts as finished only after it |
|
||||
| `BB_FIXED_ARENA` | =1 / 0: force the fixed arena on or off (tests) |
|
||||
| `BB_FIX_CHARA_ID` | =0: without the character id guard fix |
|
||||
| `BB_FIX_HIT_COLLECTOR` | =0: without the hit collector overflow fix |
|
||||
| `BB_FIX_PLAY_TIME` | =0: without the play time fix at high frame rates |
|
||||
| `BB_FLIP_AHEAD` | =N (default 1, 0 off): the buffer shown before a flip goes back to the game that many flips early |
|
||||
| `BB_FLIP_RATE` | =N: the flip rate the game asks for replaced (0: every vblank; games without frame rate hooks) |
|
||||
| `BB_FRAMES_AHEAD` | =N (default 1): guest frames the command processor may run ahead of the GPU |
|
||||
| `BB_FRAME_HOOKS` | =0: the community's frame rate patches instead of the port's hooks |
|
||||
| `BB_FSR3_FP32` | =1: FSR 3.1 without its FP16 shader variants (#85) |
|
||||
| `BB_FSR3_WAVE32` | =1: FSR 3.1 without its wave64 shader variants (#85) |
|
||||
| `BB_FSR4_LINEAR` | =0: FSR 4 gets the encoded frame as before |
|
||||
| `BB_FSR4_OPT` | =0: FSR 4 v07 passes without the optimized set (opt/) |
|
||||
| `BB_FX_INIT_CLEAR` | =0: the effect buffers' initialization clears through the game's path |
|
||||
| `BB_GARLIC_VRAM` | =0 / 1: VRAM copies of the game's GPU memory off / on (default: on discrete GPUs) |
|
||||
| `BB_GC_BUDGET_MB` | =N: texture cache budget in MiB, as on integrated GPUs |
|
||||
| `BB_GC_IDLE_SECONDS` | =N (default 20): unused textures evicted after N seconds |
|
||||
| `BB_GC_KEEP_GPU_WRITTEN` | =0: the collector downloads and frees linear images the GPU wrote under mere memory pressure; default only past the critical mark and unused for a minute (#30) |
|
||||
| `BB_GC_LIVE_BUDGET` | =0: texture cache marks from the startup budget instead of the driver's current one |
|
||||
| `BB_GC_PRESSURE_IDLE_SECONDS` | =N (default 5): the same under memory pressure |
|
||||
| `BB_GC_SMALL_MARKS` | =P,C: pressure and critical marks in percent on small cards |
|
||||
| `BB_GPU_COMMAND_WRITES` | =0: GPU command writes not done in place |
|
||||
| `BB_GPU_DATA_CHECK` | =0: GPU-written data not checked before CPU reads (#122) |
|
||||
| `BB_GPU_LABELS` | =1 / 0: GPU-written labels on / off (default off on AMD GFX8) |
|
||||
| `BB_GPU_LABELS_PORTABLE` | =1: portable GPU label writes |
|
||||
| `BB_GPU_SPIN_US` | =N: microseconds the GPU thread spins before sleeping (default 200 with 12+ threads) |
|
||||
| `BB_GPU_TIMESTAMPS` | =0: timestamps written by the CPU instead of the GPU |
|
||||
| `BB_GPU_WRITES_IN_PLACE` | =1: GPU-written buffers in place with write tracking, as before |
|
||||
| `BB_GPU_WRITE_TWINS` | =1: VRAM twins of GPU-written guest pages |
|
||||
| `BB_GPU_WRITE_TWINS_MAX` | =N: pages with at most N GPU-written ranges get twins (default 1024) |
|
||||
| `BB_GUEST_HOOKS` | =0: no guest code hooks at all (aspect ratio, HUD, mouse, fixes) |
|
||||
| `BB_GUEST_LAZY` | =1: lazy guest memory with the layer's memory module (off: broke buffers) |
|
||||
| `BB_GUEST_MEMORY` | =dmabuf / host / private: how guest memory is shared with the GPU (default per vendor) |
|
||||
| `BB_GUEST_UNCACHED` | =1: guest memory only in memory types the GPU reads past its caches |
|
||||
| `BB_GUEST_VRAM` | =1 (experiment): direct memory in VRAM mapped through the PCI BAR |
|
||||
| `BB_GUEST_VRAM_RANGES` | =lo-hi,...: physical ranges for BB_GUEST_VRAM (default: the graphics heaps) |
|
||||
| `BB_GX_COMMAND_HEAP` | =0: without the larger GX command heap patch (patches.py) |
|
||||
| `BB_GX_QUERY_GUARD` | =0: without the GX query creation guard |
|
||||
| `BB_HEAP_GROW` | =0: the graphics heap whole from the start instead of growing on demand |
|
||||
| `BB_HEAP_GROW_AHEAD` | =N (default 2, 0 off): heap pieces made ahead |
|
||||
| `BB_HEAP_GROW_INITIAL_MB` | =N (default 512): heap memory backed from the start |
|
||||
| `BB_HEAP_GROW_PIECE_MB` | =N (default 320, at least 258): heap growth piece |
|
||||
| `BB_HEAP_SHRINK` | =0: the heap only grows |
|
||||
| `BB_HEAP_SHRINK_DELAY` | =seconds (default 60): a piece free this long is given back |
|
||||
| `BB_HEAP_TOP_HOOK` | =0: without the hook on allocations from the heap's top |
|
||||
| `BB_HIT_COLLECTOR_CAP` | =N (default 65536): hits per call before the overflow fix takes over |
|
||||
| `BB_HONEST_DRAIN_FLUSH` | =0: no flush when the GPU is on its last submission |
|
||||
| `BB_HONEST_FLIPS` | =1 (experiment): a flip completes once the GPU has finished the frame |
|
||||
| `BB_HONEST_FLUSH_US` | =N (default 2000): submissions batched over N microseconds while the GPU has work |
|
||||
| `BB_HONEST_IDLE_BACKLOG` | =bytes (default 64 KiB): queued draws that make an idle GPU get a flush |
|
||||
| `BB_HONEST_IDLE_FLUSH` | =0: no flush for an idle GPU |
|
||||
| `BB_HONEST_IDLE_US` | =N (default 200): microseconds of GPU idle before the guest may be waiting |
|
||||
| `BB_HOST_COPY_WAITS` | =all: host copies always wait, as before |
|
||||
| `BB_HOST_ONLY_DEFAULT` | =0: none of the game profile's allocations in host memory only |
|
||||
| `BB_HOT_EFFECTS` | =0: effect settings applied as patches at start instead of live |
|
||||
| `BB_HOT_PAGES` | =1: hot page tracking |
|
||||
| `BB_HOT_VERTEX_COPY` | =1: vertex data bound in place copied into VRAM once per submission (no reads over the bus per pass) |
|
||||
| `BB_IMAGE_RECYCLE_MB` | =N (default 256, 0 off): memory of freed images kept for reuse |
|
||||
| `BB_IMAGE_RECYCLE_SECONDS` | =N (default 90): recycled images older than this go |
|
||||
| `BB_IMAGE_SPARES` | =1 / 0: spare images made ahead on a thread (default on on Windows) |
|
||||
| `BB_IMAGE_SPARES_MB` | =N: memory for spare images |
|
||||
| `BB_IMAGE_TRAPS` | =0: no write traps on image memory |
|
||||
| `BB_IMPORT_GRANULE_MB` | =N (2..64, default 32): lazy guest memory import granule |
|
||||
| `BB_IMPORT_OVERLAP_MB` | =N: overlap budget of lazy imports |
|
||||
| `BB_INDIRECT_GUARD` | =0: indirect draw and dispatch counts used as the game gives them |
|
||||
| `BB_INDIRECT_GUARD_MAX` | =N (default 4M): indirect dispatch groups allowed in all |
|
||||
| `BB_INDIRECT_GUARD_MAX_VERTICES` | =N (default 64M): indirect draws over this run nothing |
|
||||
| `BB_INPLACE_TEXTURES` | =1: all texture data read in place (off: looked smeared) |
|
||||
| `BB_INTERP_INT_FIX` | =0 / 1: flat integer interpolation fix off / on (default: on where barycentrics exist) |
|
||||
| `BB_JOB_SLOT_FIX` | =0: without the render job slot leak fix |
|
||||
| `BB_KEEP_DECOMPRESS_DRAWS` | =1: the game's depth decompress passes are drawn |
|
||||
| `BB_KERNEL_CLAMP` | =0: without the effect kernel clamp fix |
|
||||
| `BB_KIT_RESTORE` | =0: the online kit's code changes are not restored |
|
||||
| `BB_LAYER_CPU_MARK_SECONDS` | =N (default 5, 0: until handed out): CPU marks of layer blocks expire after N seconds |
|
||||
| `BB_LAYER_DOMAINS` | =0: mirrors not per game allocation |
|
||||
| `BB_LAYER_GPU_WRITES` | =0 / 1 / 2: where GPU writes may land in VRAM copies (0 nowhere, 1 default) |
|
||||
| `BB_LAYER_MIRROR_FLOOR_PERCENT` | =N (max 90): VRAM budget share kept for mirrors |
|
||||
| `BB_LAYER_MIRROR_GROWTH_MB` | =N (default 64, 0 none): room a growing mirror gets |
|
||||
| `BB_LAYER_MIRROR_HEADROOM_MB` | =N (0: the whole floor): mirror headroom |
|
||||
| `BB_LAYER_READ_TRAPS` | =1: data the GPU wrote into a VRAM copy only is trapped for CPU reads |
|
||||
| `BB_LAYER_RELOAD_RESET` | =0: blocks reloaded in place keep their state, as before |
|
||||
| `BB_LAYER_STITCH` | =0: bindings across heap pieces from the arena, as before (exploding vertices) |
|
||||
| `BB_LAYER_UNMAP_RESET` | =0: unmapped blocks keep their state, as before |
|
||||
| `BB_LAYER_VOLATILE` | =1: volatile blocks |
|
||||
| `BB_LAYER_VOLATILE_IN_PASS` | =0: volatile blocks refreshed in render passes too, as before |
|
||||
| `BB_LAYER_WATCH` | =0: blocks with unannounced bytes get no watched VRAM copy |
|
||||
| `BB_LAYER_WRITE_TRAPS` | =0: no write traps on layer blocks |
|
||||
| `BB_LIGHT_GRID_HOOK` | =0: without the light grid hook |
|
||||
| `BB_LIGHT_GRID_OPT` | =1 / 0: light grid optimization on / off, over the setting (tests) |
|
||||
| `BB_LIVE_HYBRID` | =1: the startup resolution patch with live presets over it |
|
||||
| `BB_MEM_SEMAPHORE_ORDER` | =0: memory semaphores without ordering |
|
||||
| `BB_MIRROR_ALLOC_PERCENT` | =N (0 off): a mirror that would take device memory past N percent is refused |
|
||||
| `BB_MIRROR_COMPACT_AGE` | =N (default 30): mirrors younger than N seconds are left alone |
|
||||
| `BB_MIRROR_COMPACT_MB` | =N (default 64): mirror memory without valid blocks over this is given back |
|
||||
| `BB_MIRROR_COMPACT_SECONDS` | =N (default 1): mirror compaction interval |
|
||||
| `BB_MIRROR_FREE_BACKLOG_MB` | =N (default 128): retired mirror memory allowed to wait |
|
||||
| `BB_MIRROR_FREE_MB_PER_FRAME` | =N (default 64): mirror memory freed per frame |
|
||||
| `BB_MIRROR_MERGE_GROWTH` | =0: a mirror made over neighbours grows at the top only, as before |
|
||||
| `BB_MIRROR_POOL_MB` | =N: retired mirrors kept for new ones |
|
||||
| `BB_MIRROR_REPROMOTE_SECONDS` | =N (default 0): blocks sent back are not copied again that soon |
|
||||
| `BB_MIRROR_UNBOUND_SECONDS` | =N (0 never): blocks no binding named for N seconds leave VRAM |
|
||||
| `BB_MIRROR_VRAM_ONLY` | =1: mirrors in VRAM or not at all |
|
||||
| `BB_MOTION_LARGE` | =0: bone palettes of 16-64 KiB (the large beasts) get camera motion vectors only, not object motion |
|
||||
| `BB_MOTION_VERTICES_MI` | =N (1-8): object motion vertex buffer in Mi vertices |
|
||||
| `BB_NET_MODULE` | =path: the online module to load (default gpu/libbbnet20.so beside bb-probe) |
|
||||
| `BB_NP_COUNTRY` | =code: the online user's country (default us) |
|
||||
| `BB_NP_LANGUAGE` | =code: the online user's language (default en) |
|
||||
| `BB_NVIDIA_EGL` | =0: the launcher leaves the EGL vendor files as they are (#107) |
|
||||
| `BB_NVIDIA_LIB_DIR` | =dir: extra folder with NVIDIA's libraries for the package |
|
||||
| `BB_NVIDIA_MAILBOX` | set: keep Mailbox presentation on NVIDIA Windows instead of FIFO |
|
||||
| `BB_NV_POWER` | =0: leave the NVIDIA driver profile of bb-probe.exe alone (Windows; by default its power management mode is set to prefer maximum performance unless the user chose one) |
|
||||
| `BB_OCCLUSION_INTERVALS` | =0: occlusion samples counted all the time, not only while a query is open |
|
||||
| `BB_OCCLUSION_LABEL_ORDER` | =0: occlusion labels may go ahead |
|
||||
| `BB_OCCLUSION_QUERIES` | =0: a fixed occlusion result sequence instead of real queries |
|
||||
| `BB_OF_FAST` | =1: optical flow without a search on the finest level |
|
||||
| `BB_PACE_CATCHUP` | =1: frame pacing catches up after a late frame |
|
||||
| `BB_PAD_CENTER` | =lx,ly,rx,ry: the sticks' neutral position |
|
||||
| `BB_PAD_CENTER_CAL` | =0: no measurement of the sticks' neutral position |
|
||||
| `BB_PAD_DEADZONE_RADIAL` | =0: the sticks' dead zone per axis instead of round |
|
||||
| `BB_PAD_WORLD_DRAWS` | =N (default 150): a frame with this many draws counts as the world for pad timing |
|
||||
| `BB_PAGED_CROSSING` | =0: paged bindings only in windows |
|
||||
| `BB_PAGED_FORMATTED` | =0: formatted buffers never paged |
|
||||
| `BB_PAGED_HOLES` | =1 (experiment): paged reads skip memory the game never wrote |
|
||||
| `BB_PAGED_MIN_MB` | =N (default 64): bindings from this size are paged |
|
||||
| `BB_PENDING_SUMMARY` | =0: pending checks without the summary level |
|
||||
| `BB_PIPELINE_CACHE` | =0: no pipeline cache on disk |
|
||||
| `BB_PIPE_SIGNALS` | =0: the frame's cache work on the GPU thread instead of the recording thread |
|
||||
| `BB_PIPE_SPIN_US` | =N: microseconds the draw pipe's threads spin before sleeping |
|
||||
| `BB_PM4_RECOVER` | =0: a command buffer the game rewrote while read stops decoding, as before |
|
||||
| `BB_PM4_REPAIR` | =0: corrupt submitted command buffers decoded as they are |
|
||||
| `BB_PM4_WAIT_US` | =N (default 20000): command processor wait timeout in microseconds |
|
||||
| `BB_PRELOAD_BACKGROUND` | =0: cached pipelines loaded before the game starts instead of in the background |
|
||||
| `BB_PREP_WORKERS` | =N (0-16): draw preparation threads |
|
||||
| `BB_PRESENT_MODE` | =Mailbox / Fifo / Immediate: swapchain present mode (default Mailbox) |
|
||||
| `BB_PRESENT_THREAD` | =0: present on the vblank thread instead of its own |
|
||||
| `BB_PROMOTE_MB_PER_FRAME` | =N (0 none; default 32 on cards up to 4 GiB, else 256): VRAM copies per frame |
|
||||
| `BB_READBACKS` | =0 / 1 / 2: GPU readbacks off / relaxed (default) / precise |
|
||||
| `BB_READBACK_LINEAR` | =1: linear images read back to guest memory |
|
||||
| `BB_READBACK_QUEUE` | =0: no separate queue for readbacks |
|
||||
| `BB_REGS_FULL_RESET` | set: the whole register file reset, as before |
|
||||
| `BB_RENDER_RESOURCE_CACHE_MB` | =N (160: the game's own): the game's render resource cache size |
|
||||
| `BB_REPAIRED_DRAWS` | =1: repaired command buffers still draw |
|
||||
| `BB_REPAIRED_WAIT_MS` | =N (default 500): waits of repaired command buffers give up after this |
|
||||
| `BB_RESOLVE_WORKERS` | =N (0-4): threads that check and copy draw resources (stage 2) |
|
||||
| `BB_RING_DEDUP` | =0: off (default on): constants already in the constant ring with the same bytes bound there again, not copied over the bus once more |
|
||||
| `BB_SCENE_DEPTH_PROXIES` | =0: depth images read at the native size, not as reduced proxies |
|
||||
| `BB_SCENE_STENCIL_ZERO` | =0: no depth proxies where stencil export is missing, the scene stays native |
|
||||
| `BB_SCENE_UPSIZE` | =0: a scene larger than native drawn at the native size |
|
||||
| `BB_SCENE_WITHOUT_UI` | =0: cinematics without a UI pass are not finished as scenes |
|
||||
| `BB_SHADOWS` | =1: shadow copies of written buffers on discrete GPUs |
|
||||
| `BB_SKIP_EMPTY_DRAWS` | =1 (experiment): draws that write nothing are left out |
|
||||
| `BB_SKIP_NOOP_DRAWS` | =1: draws without effect left out (#352, no gain measured) |
|
||||
| `BB_SOUND_HACK` | =0: saves left untouched (no sound settings fix) |
|
||||
| `BB_SPEC_MATCH` | =0: shader specializations always built and compared |
|
||||
| `BB_STAGE_PRIORITY` | =0 (default) / 1 / 2: draw stage thread priority off / above normal / highest |
|
||||
| `BB_STAGING_KEEP_MB` | =N (default 512): upload staging kept when idle |
|
||||
| `BB_STAGING_VRAM_IDLE_MS` | =N (default 3000): VRAM staging given back after this long unused |
|
||||
| `BB_STREAM_BUFFER_MB` | =N: stream buffer size |
|
||||
| `BB_SUBMIT_AHEAD` | =N (default 2): a frame's first submissions go straight to the GPU |
|
||||
| `BB_SUBMIT_LOCK` | =frame: a frame's last submission decoded under the lock |
|
||||
| `BB_SWAPCHAIN_IMAGES` | =N: swapchain images |
|
||||
| `BB_TEXTURE_HELPER` | =0 / 1: spinning texture helper thread (default: with cores to spare) |
|
||||
| `BB_TEXTURE_UPLOAD_MB_PER_FRAME` | =N (default 32, 0 off): texture bytes uploaded a frame before new textures start small |
|
||||
| `BB_THD_AUTH` | The Hunter's Dream account service URL |
|
||||
| `BB_THD_P2P_ADDR` | our address for The Hunter's Dream peers (default: detected) |
|
||||
| `BB_THD_RULESET` | The Hunter's Dream ruleset (default vanilla) |
|
||||
| `BB_THD_SERVER` | The Hunter's Dream session service URL |
|
||||
| `BB_THD_STUN` | =host[:port] / off: STUN server (default: the server's host on 3478) |
|
||||
| `BB_THD_VERIFY_TLS` | =0: The Hunter's Dream TLS certificates not checked |
|
||||
| `BB_TILE_BIND_VRAM` | =0: tiled data read in place over the bus instead of from a VRAM copy |
|
||||
| `BB_TIMELINE_POLL_US` | =N (default 50): timeline semaphore poll interval |
|
||||
| `BB_UNIFIED_MEMORY` | =1 / 0: treat the GPU as unified memory or not (over the device type) |
|
||||
| `BB_VBAO_BLUR` | =N (default 2, 0: raw): VBAO denoise taps per side |
|
||||
| `BB_VBAO_BLUR_DEPTH` | =f (default 0.05): relative depth difference that stops the VBAO blur |
|
||||
| `BB_VBAO_BLUR_NORMAL` | =f (default 8, 0 ignores normals): how sharply the VBAO blur stops at normal changes |
|
||||
| `BB_VBAO_NORMALS` | =0: VBAO from depth only |
|
||||
| `BB_VBAO_POWER` | =f (default 1.5): VBAO strength |
|
||||
| `BB_VBAO_RADIUS` | =metres (default 1.5): VBAO radius |
|
||||
| `BB_VBAO_THICKNESS` | =metres (default 0.4): VBAO assumed thickness |
|
||||
| `BB_VERTEX_READ_COPY` | =1 (experimental): vertex data bound in place copied into VRAM for the vertices each draw reads (once per guest submission) |
|
||||
| `BB_VK_RECORD_THREAD` | =0: Vulkan commands recorded on the calling thread |
|
||||
| `BB_VK_RECORD_THREADS` | =N (1-8): Vulkan recording threads (default 2 from 8 hardware threads) |
|
||||
| `BB_VK_SEGMENT_KB` | =N (default 64): recording segment size |
|
||||
| `BB_VMA_BLOCK_MB` | =N (default 64): VMA block size |
|
||||
| `BB_VMA_DEDICATED_MB` | =N (default 4): images from this size get dedicated memory |
|
||||
| `BB_VRAM_FLOOR_IDLE_SECONDS` | =N (default 30): mirror floor idle time before textures make room |
|
||||
| `BB_VRAM_IDLE_SECONDS` | =N (default 60): VRAM copies unused this long are given back |
|
||||
| `BB_VRAM_LIMIT_MB` | =N: the driver's VRAM budget taken as at most N MiB |
|
||||
| `BB_VRAM_PRESSURE_IDLE_SECONDS` | =N (default 10): under pressure, VRAM groups unused this long go |
|
||||
| `BB_VRAM_PROMOTE_PERCENT` | =N (default 90): no new VRAM copies past this share of the budget |
|
||||
| `BB_VULKANINFO` | path of vulkaninfo for the launcher |
|
||||
| `BB_WORK_RETIRED` | =0: the recording thread and fences do not wait for retired work |
|
||||
|
||||
## Diagnostics (282)
|
||||
|
||||
Logs, dumps, traces, probes and experiments, for finding bugs. Most cost frame time; none is meant for playing.
|
||||
|
||||
| Variable | What it does |
|
||||
|---|---|
|
||||
| `BB_ARENA_VRAM_ONLY` | =1 (tests): the arena in VRAM only, as NVIDIA's sparse buffers |
|
||||
| `BB_ATLAS_DEBUG` | =1: Hunter's Atlas logs what its game-state readers find |
|
||||
| `BB_ATLAS_QA` | =1: Hunter's Atlas's test commands (BB_MAP_TEST_CONTROL) may also send mouse, key and resize events |
|
||||
| `BB_ATLAS_QA_DEVICE` | =controller / keyboard: with BB_ATLAS_QA, the prompts Hunter's Atlas shows |
|
||||
| `BB_AUDIO_DUMP` | =dir: raw converted PCM per audio port |
|
||||
| `BB_AUDIO_STATS` | set: audio port statistics |
|
||||
| `BB_AUDIO_TRACE` | =1: Ajm instance configurations and jobs that fail |
|
||||
| `BB_AVPLAYER_TRACE` | =1: the game's movie player calls with results and times |
|
||||
| `BB_BARRIER_STATS` | =1: images whose subresource states cost the most |
|
||||
| `BB_BBSC_MEMSCAN` | set: BBSC finds its sites by scanning memory instead of the known addresses |
|
||||
| `BB_BBSC_PROBE` | =address: BBSC probe of that guest address |
|
||||
| `BB_BDA_LOG` | set: GPU addresses of buffer sources, to place a GPUVM fault |
|
||||
| `BB_BIG_ALLOC_STACKS` | =1: call stacks of device memory allocations of 64 MiB and more |
|
||||
| `BB_BREADCRUMB_READS` | =1 / 0: breadcrumbs of T# and V# reads everywhere / nowhere (default: NVIDIA only) |
|
||||
| `BB_BUFFER_STATS` | =1: how many frames behind the GPU is when a frame starts |
|
||||
| `BB_CAMERA_FRAME_LOG` | set: projection of every G-buffer pass's camera with its GPU frame |
|
||||
| `BB_CAMERA_INVERSE` | =own: motion vectors from the inverse of the view matrix instead of the game's stored one |
|
||||
| `BB_CAMERA_LOG` | set: camera position every 100 ms |
|
||||
| `BB_CAMERA_Y` | =up / down: force the camera's y direction for motion vectors |
|
||||
| `BB_CAMLOG` | set: camera yaw when it changes (#325) |
|
||||
| `BB_CAPTURE_DIR` | folder for frame captures (BB_CAPTURE_TRIGGER) |
|
||||
| `BB_CAPTURE_TRIGGER` | =file: creating that file records the next frame for the frame analyzer |
|
||||
| `BB_CE_STATS` | =1: what the constant engine's dumps feed |
|
||||
| `BB_CE_WAIT_LOG` | set: constant engine waits that were not satisfied |
|
||||
| `BB_CE_WATCH` | =hex address: constant RAM dumps and WRITE_DATA packets over that address |
|
||||
| `BB_CLOTH_STIFFNESS` | =value: cloth stiffness bound at any frame rate |
|
||||
| `BB_COMPILE_TRACE` | =1: every shader and pipeline compiled during the session |
|
||||
| `BB_CONSTRAM_WAIT_LOG` | set: constant RAM dumps that waited for the GPU, sampled |
|
||||
| `BB_COPY_SHADER_TRACE` | set: the copy shader's buffers every 2 s |
|
||||
| `BB_COPY_WHERE` | =1: the copy shader's sources and destinations per frame, VRAM or in place (log lines) |
|
||||
| `BB_DCB_STATS` | =1: structure of the graphics command buffers |
|
||||
| `BB_DEBUG_MOTION` | =1: motion vectors blended over the picture |
|
||||
| `BB_DISPATCH_LOG` | =1: the shape of each kind of compute dispatch, once |
|
||||
| `BB_DISPATCH_START_LOG` | set: dispatches with a nonzero thread group start |
|
||||
| `BB_DISPLAY_WATCH` | set: every draw into a display buffer with its sizes and state |
|
||||
| `BB_DISPLAY_WATCH_TARGET` | =hex address: also every draw into that color target |
|
||||
| `BB_DLSSG_CAMERA_MOTION` | =0: DLSS-G told the vectors have no camera motion |
|
||||
| `BB_DLSSG_DEPTH_INVERTED` | =1: DLSS-G told the depth is the other way round |
|
||||
| `BB_DLSSG_HUDLESS` | =0: DLSS-G gets no hudless frame |
|
||||
| `BB_DLSSG_MATRICES` | =0: DLSS-G gets no camera matrices |
|
||||
| `BB_DLSSG_MVEC_SCALE` | =f: DLSS-G motion vector scale (default 1) |
|
||||
| `BB_DLSSG_PREFILL` | =0: DLSS-G outputs not filled with the real frame first (black frames when it skips) |
|
||||
| `BB_DLSSG_SUBRECTS` | =0: DLSS-G gets no input sub-rectangles |
|
||||
| `BB_DRAW_WATCH` | =1: draws whose viewport, targets or textures differ from the previous frame's |
|
||||
| `BB_DRAW_WATCH_TEXTURES` | set: BB_DRAW_WATCH also lists bound textures |
|
||||
| `BB_DUMP_DIR` | =dir: where upscaler dumps go (default out/dump) |
|
||||
| `BB_DUMP_EVERY` | set: dump every frame while the trigger file exists |
|
||||
| `BB_DUMP_FRAMES` | =N: frames per upscaler dump (default 8) |
|
||||
| `BB_DUMP_GOT` | =offset: which host function a guest GOT slot calls |
|
||||
| `BB_DUMP_ONLY` | =name,name: dump only those upscaler images |
|
||||
| `BB_DUMP_SHADERS` | =1: translated shader programs written to the dump folder |
|
||||
| `BB_DUMP_TRIGGER` | =file: creating it dumps the upscaler's inputs and outputs |
|
||||
| `BB_DYNAMIC_STATE_ALWAYS` | =1: all dynamic state set again at every draw |
|
||||
| `BB_DYNSTATE_CHECK` | =1: the scissor each command buffer really has, and who set it |
|
||||
| `BB_EFFECT_DUMP` | set: the game's effect options objects every 10 s |
|
||||
| `BB_EXPERIMENT_CYCLE` | =bits,bits,...:seconds: the experiment bits step through the list, over and over (test builds) |
|
||||
| `BB_EXP_BARRIER_ALL` | set (experiment): a full memory barrier before every draw and dispatch |
|
||||
| `BB_EXP_BARRIER_TARGET` | =hex address (experiment): a full barrier before each draw into that target |
|
||||
| `BB_EXP_CONSTRAM_FINISH` | =1 (experiment): every constant RAM dump waits for all earlier GPU work |
|
||||
| `BB_EXP_COPY_SRC_SNAPSHOT` | set (experiment): copy shader sources snapshotted as with the old memory model |
|
||||
| `BB_EXP_CPU_READS_GPU_WAIT` | set (experiment): CPU-side reads also wait for the GPU, as before |
|
||||
| `BB_EXP_DMA_UNTRACKED` | set (experiment): stages reading memory through pointers count as unlisted reads |
|
||||
| `BB_EXP_EXTRA_BREAKS` | =N (experiment): the render pass ended before every Nth draw, to measure breaks |
|
||||
| `BB_EXP_FINISH_BEFORE_TARGET` | =hex address (experiment): the GPU finishes everything before a draw into that target |
|
||||
| `BB_EXP_FINISH_FRAMES` | set (experiment): the GPU finishes each frame before the next |
|
||||
| `BB_EXP_FLIP_FINISH` | set (experiment): the flip is signalled once the GPU has run the frame |
|
||||
| `BB_EXP_FLIP_LAG` | set (experiment): at most one frame in flight |
|
||||
| `BB_EXP_NOTED_GPU_WAIT` | set (experiment): constant RAM waits cover every read noted on stage A, as before |
|
||||
| `BB_EXP_NO_MEMORY_HOOKS` | set (experiment): no guest hooks for GPU allocations and copies |
|
||||
| `BB_EXP_NO_STREAM` | set (experiment): nothing goes through the stream buffer |
|
||||
| `BB_EXP_PAGED_IN_PLACE` | =1 (experiment): paged buffers bound in place |
|
||||
| `BB_EXP_TRANSIT_TARGET` | =hex address (experiment): that target goes through TransferSrcOptimal |
|
||||
| `BB_FAULT_WAIT` | =1: a fatal fault or a GPU library assertion leaves the process alive for a debugger (tools/debug_attach.sh) |
|
||||
| `BB_FENCE_DELAY_TEST` | =1 (test): every 200th fence comes 80 ms late |
|
||||
| `BB_FFX_FG_DEBUG` | =1: AMD's frame generation draws its tear lines and reset marks |
|
||||
| `BB_FG_DUMP_TRIGGER` | =file: creating it saves real and generated frames, raw |
|
||||
| `BB_FG_LUT` | =0: generated frames without the game's display LUT |
|
||||
| `BB_FG_PROFILE` | =1: GPU time of each FSR 3.1 frame generation pass |
|
||||
| `BB_FILE_TRACE` | =1: every guest path the game opens or checks (mods, missing files) |
|
||||
| `BB_FINAL_DUMP_TRIGGER` | =file: creating it saves the presented frame |
|
||||
| `BB_FI_CHECK` | =1: frame interpolation samples drawn as their difference to the SDK's |
|
||||
| `BB_FPS_PRINT` | =1: the present rate every 5 s, without BB_FRAME_STATS's cost |
|
||||
| `BB_FRAME_DT_SMOOTH` | =a (0..1): the game's frame step follows the measured frame time smoothed by a |
|
||||
| `BB_FRAME_GEN_ONLY` | set: only the generated frames are shown |
|
||||
| `BB_FRAME_LOG` | =path (with BB_FRAME_STATS): one line per flip |
|
||||
| `BB_FREE_CHECK` | =1: guest heap corruption diagnostic around the free list |
|
||||
| `BB_FSR411_POSTPASS` | =lds / orig: FSR 4.1.1 post pass variant |
|
||||
| `BB_FSR4_MODEL_CYCLE` | =N: the FSR 4 model switched to the next one every N frames |
|
||||
| `BB_FSR4_PROFILE` | =1: FSR 4.1.1 GPU time per pass every 300 frames |
|
||||
| `BB_FSR4_STATS` | =1: driver statistics of every FSR 4 pass (fsr4-bench --stats) |
|
||||
| `BB_GAME_ASSERT_STOP` | =1: the game's known harmless asserts stop it, as before |
|
||||
| `BB_GAME_HUGE` | =0 (tests): no huge pages for the game's image |
|
||||
| `BB_GAME_MENU_TRACE` | =1: how often the game asks for the port's menu texts |
|
||||
| `BB_GAME_PROFILE` | =none: no game profile (hooks, fixes), the plain runtime |
|
||||
| `BB_GARLIC_VRAM_RANGE` | =begin:end: VRAM copies only for guest addresses in that range |
|
||||
| `BB_GC_TRIGGER_MB` | =N: the texture collector starts at N MiB (tests) |
|
||||
| `BB_GNM_CENSUS` | =1: calls per Gnm function, printed |
|
||||
| `BB_GNM_CENSUS_FILE` | =path: every Gnm function's total calls written there |
|
||||
| `BB_GNM_CENSUS_RANGE` | =first:last: census only for those table indices (bisecting) |
|
||||
| `BB_GNM_NATIVE_ONLY` | =1: native Gnm observers without checksum analysis |
|
||||
| `BB_GNM_OBSERVE` | =1: submitted command buffers checked against the hooked API calls' packets |
|
||||
| `BB_GNM_RACE_STACKS` | =1: call stacks of racing Gnm command buffer writers |
|
||||
| `BB_GNM_TRACE` | =offset[:count]: arguments, caller and packets of that Gnm function |
|
||||
| `BB_GNM_WRITES_TRACE` | =1: recent Gnm builders overlapping a bad submitted buffer |
|
||||
| `BB_GPU_LOG` | =trace / debug / info / warning / error: GPU layer log level |
|
||||
| `BB_GPU_PROFILE_DRAWS` | =1: the GPU profiler times every draw |
|
||||
| `BB_GPU_PROFILE_TOP` | =N: the GPU profiler prints the N heaviest labels (default 30) |
|
||||
| `BB_GPU_WRITE_WATCH` | =hex address: GPU writes noted over the 64 KiB page holding it |
|
||||
| `BB_GUEST_HOOK_TRAPS` | =1: guest hooks stay int3 traps instead of native detours |
|
||||
| `BB_GUEST_WHOLE_CHUNKS` | =1 (tests): as if offset mappings failed (the NVIDIA path) |
|
||||
| `BB_GX_QUERY_FAIL` | =N: every Nth GX query creation fails as with a full heap |
|
||||
| `BB_HEAP_DUMP` | =addr,...: those guest objects and the ones they point to |
|
||||
| `BB_HEAP_FIND` | =hex value: once, 60 s in, where the game's data holds that value |
|
||||
| `BB_HEAP_HOOKS` | =0 (bisecting): the heap's ranges not reported |
|
||||
| `BB_HEAP_SITES` | =1: the game's malloc replacement sites |
|
||||
| `BB_HEAP_TRACE` | =1: which 16 MiB ranges the game's GPU allocators hand out, every 60 s |
|
||||
| `BB_HEAP_WALK` | =core,...: walk of the game's range allocator cores |
|
||||
| `BB_HLE_OFF` | =mask (experiment): 1 image copy, 2 metadata clear, 4 image clear run as real shaders |
|
||||
| `BB_HLE_STATS` | set: how often the compute shader substitutions fire |
|
||||
| `BB_HOST_ONLY` | =phys,... (experiment): those direct allocations in host memory only |
|
||||
| `BB_HUD_DX` | =f: another HUD shift |
|
||||
| `BB_IMAGE_CLEAR_LOG` | =1: why an image clear went to the real shader, once per reason |
|
||||
| `BB_IMAGE_COPY_LOG` | set: also why a large copy shader was not substituted |
|
||||
| `BB_IMAGE_SYNC_LOG` | set: images refreshed most per 5 s, with their flags |
|
||||
| `BB_IMAGE_WATCH` | =address: what happens to the image at that address |
|
||||
| `BB_IME_TEXT` | =text: the on-screen keyboard answers with that text at once (tests) |
|
||||
| `BB_IMITATE_NVIDIA` | =1 (tests on AMD): the port treats this GPU as NVIDIA's |
|
||||
| `BB_IMPORT_LOG` | set: lazy import requests per 10 s |
|
||||
| `BB_IMPORT_TRACE` | =lo-hi (MiB): who asked for imports starting there |
|
||||
| `BB_IMPORT_WRITTEN_BUDGET` | =1 (tests): written bindings under the import budget too, as before |
|
||||
| `BB_INPLACE_READ_CHECK` | =1: checks of the GPU's in-place reads of game memory |
|
||||
| `BB_LABEL_TRAP` | =1 / 2: with BB_FREE_CHECK, read-only label pages first |
|
||||
| `BB_LANG_TABLES` | set: the sell regions' language tables (#269) |
|
||||
| `BB_LAYER_CHECK` | =1: stop on a stale page table entry of the layer |
|
||||
| `BB_LAYER_MARK_LOG` | =lo-hi (hex): who marks layer blocks there as the CPU's |
|
||||
| `BB_LAYER_PAGED` | =0 (tests): huge bindings cut in place instead of the page table |
|
||||
| `BB_LAYER_PAGED_STATS` | set: paged bindings per shader every 5 s |
|
||||
| `BB_LAYER_PROBE` | =1: GPU writes kept in place because the CPU once wrote the block |
|
||||
| `BB_LAYER_VERIFY` | =1 / 2: verify the layer's mirrors (2: also those a paged binding reads) |
|
||||
| `BB_LAYER_WATCH_HOT` | =1 (experiment): often written blocks watched and uploaded after every write |
|
||||
| `BB_LAYER_WRITE_LOG` | set: large GPU writes kept in place, and the images near them |
|
||||
| `BB_LIST_UNRESOLVED` | =1 (porting another game): every unresolved import, once |
|
||||
| `BB_LOW_INFO_TEST` | set: test of the memory queries for the game's statics (#282) |
|
||||
| `BB_MAP_TEST_CONTROL` | =file: one command at a time for Hunter's Atlas's automated tests (open, close, zoom-in, area <id>, ...), removed once read |
|
||||
| `BB_MAP_TEST_TRACE` | =file: with BB_MAP_TEST_CONTROL, Hunter's Atlas writes its state there as JSON |
|
||||
| `BB_MEMORY_LOCK_REPORT_MS` | =N (default 20, 0 off): memory map lock holds longer than this, with the code |
|
||||
| `BB_MENU_KEYS_FILE` | =file (scripted tests): menu key tokens read from it |
|
||||
| `BB_MENU_POINTER_FILE` | =file (tests): menu pointer moves and clicks read from it |
|
||||
| `BB_MENU_POINTER_LOG` | =1: the signals of the in-menu check, with every change |
|
||||
| `BB_META_FIXED_BASE` | set: shader prediction from metadata at a fixed program base |
|
||||
| `BB_META_NO_SYNTH` | set: shader prediction without synthesized inputs |
|
||||
| `BB_META_RECIPE_FETCH` | set: shader prediction without generated fetch shaders |
|
||||
| `BB_MIRROR_TRACE` | set: mirror creation and growth |
|
||||
| `BB_MOD_TRACE` | =1: every file a mod replaces |
|
||||
| `BB_MOTION_SELECT_LOG` | =1: each G-buffer vertex shader once with the motion vector selection |
|
||||
| `BB_MOTION_TRACE` | =file: the frame after the file appears prints its motion draws |
|
||||
| `BB_MOVBE_TRAP` | =file: the movbe at the image offsets listed there (tools/movbe_sites.py) made ud2 and run by the port, as on a processor without MOVBE |
|
||||
| `BB_MSG_TRACE` | =1: the game's text lookups per 2 s by category (#255) |
|
||||
| `BB_NGX_LOG` | =1 / 2: NVIDIA NGX log into the port's (2: verbose) |
|
||||
| `BB_NULL_GPU` | =1: no GPU work at all (tests) |
|
||||
| `BB_OBJECT_MOTION_ALL` | =1: the old broad object motion selection (A/B) |
|
||||
| `BB_OCCLUSION_READ_TRACE` | =1: the game's occlusion result reads |
|
||||
| `BB_OCCLUSION_SAMPLES` | =N: samples the game read, for N queries |
|
||||
| `BB_OCCLUSION_TRACE` | =N: the first N occlusion events with frame and render pass |
|
||||
| `BB_OVERLAY_OPEN` | set (tests): the port's menu open from the start, unfolded |
|
||||
| `BB_OVERLAY_TRACE` | =1: the menu's mouse buttons as SDL and ImGui see them |
|
||||
| `BB_PAD_CLOCK` | =wall: pad timestamps from the wall clock, as before |
|
||||
| `BB_PAD_FILE` | =file: scripted pad input for automated runs |
|
||||
| `BB_PAD_RECORD` | =file: F9 starts and stops recording the pad state into it |
|
||||
| `BB_PAD_REPLAY` | =file: plays a BB_PAD_RECORD recording |
|
||||
| `BB_PAD_TAP_TEST` | =ms: the right stick fully right for that long (#325) |
|
||||
| `BB_PAD_TRACE` | =1 / 2: gamepad buttons and touches as read (2: every sample) |
|
||||
| `BB_PAGED_COPY_CROSSING` | =0 (tests): copy programs paged for size alone are not paged |
|
||||
| `BB_PAGED_VERIFY` | =1: verify every block a paged binding reads |
|
||||
| `BB_PARTIAL_TRACE` | set: partial image refreshes |
|
||||
| `BB_PASS_BREAK_TRACE` | =1: what ended each render pass |
|
||||
| `BB_PASS_DUMP` | =addr,...: rows of those render targets at each display pass (with BB_PASS_DUMP_TRIGGER) |
|
||||
| `BB_PASS_DUMP_AT` | =hex address: BB_PASS_DUMP's images also before the first draw into that target |
|
||||
| `BB_PASS_DUMP_STEP` | =N (default 8): BB_PASS_DUMP every Nth row |
|
||||
| `BB_PASS_DUMP_TRIGGER` | =file: creating it starts BB_PASS_DUMP |
|
||||
| `BB_PENDING_STATS` | set: pending notes and granules a second, by level |
|
||||
| `BB_PIN_CLEAR_LOG` | set: large releases of GPU-pinned memory that were cleared |
|
||||
| `BB_PIPE_VERIFY` | =N: verify the draw pipe's results every Nth frame |
|
||||
| `BB_PM4_SELFTEST` | =1: tests of command processor packets Bloodborne never sends |
|
||||
| `BB_PM4_SELFTEST_STEPS` | =mask: the self test steps to run |
|
||||
| `BB_POOL_WATCH` | =1: watch of the game's pool corruption (#32) |
|
||||
| `BB_PRESENT_DUMP_COUNT` | =N: BB_PRESENT_DUMP_TRIGGER saves N consecutive frames |
|
||||
| `BB_PRESENT_DUMP_TRIGGER` | =file: creating it saves the presented frame with the UI |
|
||||
| `BB_PRESET_FILE` | =file: 'preset [output [upscaler [aspect]]]' read about once a second (scripted tests) |
|
||||
| `BB_PRODUCER_CHECK` | =1: command producer checks (the launcher's crash diagnostics) |
|
||||
| `BB_PROMPT_TRACE` | =1: key guide icon textures found for button prompts |
|
||||
| `BB_READBACK_LOG` | =path: the newest GPU-written bindings behind readbacks |
|
||||
| `BB_READ_SAMPLE` | =spec: sampling of which guest memory the CPU reads |
|
||||
| `BB_RECORDER_TRACE` | =1: most frequent command buffer callers every 2000 calls |
|
||||
| `BB_REGS_CHECK` | set: registers compared with the defaults after a reset |
|
||||
| `BB_RESIDENCY_MIX` | =1 / 2: bindings per kind every 2 s |
|
||||
| `BB_RESIDENCY_REPORT` | =file: when it appears, blocks bound in place and why they have no VRAM copy |
|
||||
| `BB_RESIDENCY_VRAM_LIMIT_MB` | =N (tests): pretend VRAM ends at N MiB |
|
||||
| `BB_RES_STATS` | =1: where bound shaders read their resource descriptors from |
|
||||
| `BB_RE_ADVANCE` | =1: probe of Scaleform's MovieImpl::Advance |
|
||||
| `BB_RE_ANALOG` | =1: probe of the analog getter |
|
||||
| `BB_RE_CAMERA` | =1: camera probe |
|
||||
| `BB_RE_CAMSTICK` | =1: probe of the camera stick getter |
|
||||
| `BB_RE_CAM_ADD` | =radians: turns the camera by that much (probe) |
|
||||
| `BB_RE_ENTRY` | =offset,...: entry probes at those guest offsets |
|
||||
| `BB_RE_GX_QUERY` | =1: first GX query creations and guard events with their thread |
|
||||
| `BB_RE_VIEWPORT` | =1: the game's viewport calls |
|
||||
| `BB_RE_WATCH` | =offset: hardware watchpoint on that guest offset |
|
||||
| `BB_RE_WATCH_ADDR` | =address: hardware watchpoint on that address |
|
||||
| `BB_RE_WATCH_AT` | =N (default 3000): watchpoint set after that many pad reads |
|
||||
| `BB_RE_WATCH_CAMANGLE` | =1: watch who writes the camera angle |
|
||||
| `BB_RE_WATCH_CAMIN` | =1: watchpoint on the camera input |
|
||||
| `BB_RE_WATCH_CAMSTATE` | =1: watch who reads the camera's aspect ratio |
|
||||
| `BB_RE_WATCH_FILE` | =file: watch the hex address written in it once it appears |
|
||||
| `BB_RE_WATCH_FOLLOW` | =N: follow the watched bytes through host copies N times |
|
||||
| `BB_RE_WATCH_INDEX` | =N (default 62): virtual input index for BB_RE_WATCH_VIRTUAL |
|
||||
| `BB_RE_WATCH_VIRTUAL` | =1: watchpoint on the virtual right stick X |
|
||||
| `BB_RE_ZERO_VIRTUAL` | =1: virtual inputs fed from the right stick's X get 0 |
|
||||
| `BB_RING_REASONS` | set: why small read-only bindings were not copied, per 5 s |
|
||||
| `BB_ROBUSTNESS` | =0 (experiment): buffer and image robustness off |
|
||||
| `BB_SAMPLE_ALL` | =1: every sampled address as module+offset |
|
||||
| `BB_SAMPLE_RANGE` | =lo-hi: guest offsets the sampler reports |
|
||||
| `BB_SAMPLE_REPORT_SECONDS` | =N: thread samples every N seconds |
|
||||
| `BB_SAMPLE_THREAD` | =name: where that thread runs, sampled |
|
||||
| `BB_SAVE_TRACE` | =1: every operation on save files |
|
||||
| `BB_SCENE_DEBUG` | =file: when it appears, where the scene image is accessed |
|
||||
| `BB_SCENE_NO_RESOLVE` | =file:line,...: those accesses read the native scene image |
|
||||
| `BB_SCENE_SAMPLED_RESOLVE` | =0: shader reads take the native scene image as it is |
|
||||
| `BB_SCENE_STENCIL_BITS` | =1 (tests): the eight-draw stencil reconstruction |
|
||||
| `BB_SECTIONS` | =1: timing of the draw recording thread's sections |
|
||||
| `BB_SECTIONS_SLOW` | =1 (with BB_SECTIONS): slow frames' sections at each flip |
|
||||
| `BB_SEQUENCE_DUMP_COUNT` | =N (default 16): images per BB_SEQUENCE_DUMP_TRIGGER |
|
||||
| `BB_SEQUENCE_DUMP_TRIGGER` | =file: creating it saves the next images shown (frame generation checks) |
|
||||
| `BB_SHADER_CAPTURE` | =dir: shader inputs captured there |
|
||||
| `BB_SKIP_PS` | =hash,...: draws with these pixel shaders left out |
|
||||
| `BB_SLOW_BIND_LOG` | =microseconds: image memory binds slower than this |
|
||||
| `BB_SPEC_MATCH_CHECK` | set: the built specialization must find the same one |
|
||||
| `BB_STALL_REPORT` | =0: no report when the queues stop making progress |
|
||||
| `BB_STALL_THREADS` | =0: a stall report covers the command thread only |
|
||||
| `BB_STREAM_STATS` | =1: what a frame streams |
|
||||
| `BB_TAA_DIAGNOSTICS` | set: TAA diagnostic image dumps |
|
||||
| `BB_TEST_VPAD` | =1 (tests): a virtual Xbox-like gamepad |
|
||||
| `BB_THD_TRACE` | =1: every The Hunter's Dream datagram and callback |
|
||||
| `BB_TICK_LOG` | set: ranges marked written by the GPU most often, per 5 s |
|
||||
| `BB_TILE_BACK_LOG` | set: which shader a 'Tile back' line is for |
|
||||
| `BB_TIMELINE` | =file: a timeline of frames on their way to the GPU |
|
||||
| `BB_TIMELINE_START` | =seconds (default 30): BB_TIMELINE starts recording this late |
|
||||
| `BB_TLS_WATCH` | =offset: who writes that static TLS variable of the main thread |
|
||||
| `BB_TOGGLE_FILE` | =file: experiment bits re-read every 250 ms |
|
||||
| `BB_TRACE_ADDR` | =addr:size: that range traced from the start (with BB_TRACE_SIZE) |
|
||||
| `BB_TRACE_CB` | =hash: how that shader's constant buffers were bound |
|
||||
| `BB_TRACE_CB_LIST` | =hash,...: BB_TRACE_CB for several shaders |
|
||||
| `BB_TRACE_CB_RANGE` | =lo-hi: constant buffer bindings over that range |
|
||||
| `BB_TRACE_GPU_INPUTS` | =vs hash: the traced draw's buffer ranges, dumped at the display pass |
|
||||
| `BB_TRACE_PIPELINE` | =vs hash: that draw's pipeline handle and push data |
|
||||
| `BB_TRACE_SEMA` | set: blocking semaphore waits (deadlocks) |
|
||||
| `BB_TRACE_SHADER` | =vs hash: that draw's counts and streams once a second |
|
||||
| `BB_TRACE_SIZE` | =bytes: bindings over GPU-written buffers of that size, counted |
|
||||
| `BB_TRACE_VERTEX_DATA` | set (with BB_TRACE_SHADER): the stream bytes whenever they change |
|
||||
| `BB_TROPHY_PREVIEW` | =id: at the game's first trophy call, that trophy's notice without recording it |
|
||||
| `BB_TROPHY_TEST` | =id: that trophy's announcement once, nothing recorded |
|
||||
| `BB_UI_DRAWS` | =file: while it exists, one frame's UI draws (another aspect ratio, or the upscaler's UI phase) logged with their targets, stencil, blend, clip rects and redirection |
|
||||
| `BB_UI_DRAWS_PS` | =hex pixel shader hash: BB_UI_DRAWS also dumps that shader's draws' vertex streams, first constant buffer and indices |
|
||||
| `BB_UI_TEXTURE_LOG` | =file: when it appears, the textures the UI binds over the next 2 s |
|
||||
| `BB_UI_TRACE` | set: draws of frames that ended without the output-size UI |
|
||||
| `BB_UI_TRIGGER_VS` | =hex hash: the vertex shader that starts the UI phase |
|
||||
| `BB_UPSCALE_BEFORE_CS` | =hex hash: upscale before that compute shader |
|
||||
| `BB_VERTEX_IN_PLACE` | =1: vertex data bound in place per frame, by why it has no VRAM copy, and what draws read of it (log lines) |
|
||||
| `BB_VK_MARKERS` | =1: the game's debug labels as Vulkan markers |
|
||||
| `BB_VK_VALIDATION` | =1: Vulkan validation layers |
|
||||
| `BB_VK_VALIDATION_SYNC` | =1: Vulkan synchronization validation |
|
||||
| `BB_VRAM_ACCESS_TRAP` | set: blocks moved to VRAM are closed to the CPU |
|
||||
| `BB_VRAM_TRAP_RANGE` | =addr:size: BB_VRAM_ACCESS_TRAP only over that range |
|
||||
| `BB_WAIT_LOG` | =file: the game's kernel waits written there |
|
||||
| `BB_WAIT_TRACE` | =1: where the time waiting for GPU ticks goes |
|
||||
| `BB_WARN_META_TEXTURE` | set: warn about textures read from metadata memory |
|
||||
| `BB_WATCH` | =addr:size: what the translator reads from that range |
|
||||
| `BB_WRITE_LOG` | =1: GPU writes to guest memory near a fault |
|
||||
| `BB_WRITE_SHADOWS` | =1: ranges the game's copy shader writes in place copied into VRAM after it, small read-only bindings read there (no gain measured) |
|
||||
| `BB_WRITE_SHADOW_TRACE` | =1: why BB_WRITE_SHADOWS lookups miss and who drops the copies (log lines) |
|
||||
| `BB_WRITE_VERIFY` | =1: page protection to catch writes nothing announced |
|
||||
| `BB_ZERO_SCAN` | =seconds: that long after the first mapping, how much direct memory is zero |
|
||||
|
||||
## Internal (44)
|
||||
|
||||
Passed between the launchers, run.py / run.sh and the game. Not meant to be set by hand.
|
||||
|
||||
| Variable | What it does |
|
||||
|---|---|
|
||||
| `BB_BUNDLED_VK_DRIVER_FILES` | the package's own Vulkan driver manifests (set by the launcher) |
|
||||
| `BB_BUNDLED_VK_LAYER_PATH` | the package's own Vulkan layers, found before the host's (set by the launcher) |
|
||||
| `BB_CLIENT_VERSION` | the online client's name for the server (bbport VERSION, set by run.py) |
|
||||
| `BB_DATA_DIR` | the port's data folder (set by run.py) |
|
||||
| `BB_DEBUG_KEEP` | =1: tools/debug_attach.sh leaves the process stopped after writing its report (--keep) |
|
||||
| `BB_DEBUG_OUT` | where tools/debug_attach.sh writes its report (set by the script) |
|
||||
| `BB_DLSS_DIR` | the DLSS bridge and NVIDIA's DLSS libraries' folder in place of bb-probe's (set by the AppImage without Nix) |
|
||||
| `BB_FSR411_OUT` | where the launcher's FSR 4.1.1 build puts its assets |
|
||||
| `BB_FSR4CAP_CLEAN` | =1: the FSR 4.1.1 build removes its work folder afterwards (set by the launcher) |
|
||||
| `BB_FSR4CAP_TOOLS` | the FSR 4.1.1 build's tools folder (set by the launcher) |
|
||||
| `BB_FSR4CAP_WORK` | the FSR 4.1.1 build's work folder (set by the launcher) |
|
||||
| `BB_GAMEPAD_STRICT` | =1 (local co-op): only the chosen gamepad, never another one instead |
|
||||
| `BB_GPU_USER_DIR` | the user folder as the GPU library and the online module see it (set by the runtime) |
|
||||
| `BB_HOST_VK_ADD_DRIVER_FILES` | the host's VK_ADD_DRIVER_FILES under Steam's pressure-vessel, kept for the log while the AppImage without Nix drops it |
|
||||
| `BB_HOST_VK_ADD_LAYER_PATH` | the host's VK_ADD_LAYER_PATH under Steam's pressure-vessel, kept for the log while the AppImage without Nix drops it |
|
||||
| `BB_HOST_VK_DRIVER_FILES` | the host's VK_DRIVER_FILES under Steam's pressure-vessel, kept for the log while the AppImage without Nix drops it |
|
||||
| `BB_HOST_VK_ICD_FILENAMES` | the host's VK_ICD_FILENAMES under Steam's pressure-vessel, kept for the log while the AppImage without Nix drops it |
|
||||
| `BB_HOST_VK_IMPLICIT_LAYER_PATH` | the host's VK_IMPLICIT_LAYER_PATH under Steam's pressure-vessel, kept for the log while the AppImage without Nix drops it |
|
||||
| `BB_HOST_VK_LAYER_PATH` | the host's VK_LAYER_PATH under Steam's pressure-vessel, kept for the log while the AppImage without Nix drops it |
|
||||
| `BB_IMAGE_SHAPES` | the package's image shapes list for spare images (set by run.py) |
|
||||
| `BB_INTERNAL_HEIGHT` | the game's internal screen height (default 1080; set by run.py) |
|
||||
| `BB_INTERNAL_WIDTH` | the game's internal screen width (default 1920; set by run.py) |
|
||||
| `BB_LIVE_RES` | =1: render resolution changes live (set by run.py) |
|
||||
| `BB_MODS_CONFIG` | the mods list (default <data>/mods.json) |
|
||||
| `BB_MODS_DIR` | the folder of enabled mods' files (set by run.py) |
|
||||
| `BB_MONITOR_SIZE` | =WxH: the monitor's size the startup patch was made for (set by the launcher) |
|
||||
| `BB_OUTPUT_RES` | =WxH: the output the startup patch was made for (set by run.py) |
|
||||
| `BB_PATCHES_CONFIG` | the patch choice file (default <data>/patches.json) |
|
||||
| `BB_PATCHES_DIR` | the patches folder (default <data>/patches) |
|
||||
| `BB_PKGEXTRACT` | pkgextract for the launcher's Install from PKG (the AppImage's copy) |
|
||||
| `BB_PREBUILT` | =1: packaged build, run.sh builds nothing (set by the AppImage) |
|
||||
| `BB_PRESENCE_DIR` | Discord Rich Presence's folder for this start (run.sh; scripts/discord_presence.py) |
|
||||
| `BB_PROBE` | path of bb-probe for run.py |
|
||||
| `BB_PROMPT_ART` | the Xbox and Switch button art for the live prompts, KG_*.rgba by style (scripts/prompt_art.py; set by run.sh) |
|
||||
| `BB_SAVEDATA_TITLE` | the saves' title id (set by the runtime), for BBSC's save folder before the game has written one |
|
||||
| `BB_SHADER_GAME_DIR` | the game's files the shader recipes' programs come from (set by run.py) |
|
||||
| `BB_SHADER_RECIPES` | the shader recipes pack (set by run.py) |
|
||||
| `BB_SKIP_NETWORK_CHOICE` | =1: no online choice at start (the launcher's) |
|
||||
| `BB_STALL_THREADS_DONE` | set by the port once all threads were reported |
|
||||
| `BB_STEAM_OVERLAY` | the Steam overlay's preload kept for the game (=0: off; set by the AppImage entry) |
|
||||
| `BB_TIMEOUT` | =seconds: run.py stops the game after this long (tests) |
|
||||
| `BB_TROPHY_DIR` | the trophy list and icons scripts/trophies.py prepared, where the earned ones are recorded (set by run.sh) |
|
||||
| `BB_USER_DIR` | the user folder (saves, settings; set by run.py) |
|
||||
| `BB_VBLANK_HZ` | =N (default 60, 0: 480): the guest vblank rate (1000 for uncapped; set by run.py) |
|
||||
|
||||
## Build (15)
|
||||
|
||||
Read by the build and packaging scripts only.
|
||||
|
||||
| Variable | What it does |
|
||||
|---|---|
|
||||
| `BB_FSR4CAP_CHECKED` | internal to tools/fsr4cap: the tools were checked |
|
||||
| `BB_FSR4CAP_FP8` | =1: also the FP8 FSR 4.1.1 variant through Proton's emulation; 0: never |
|
||||
| `BB_FSR4CAP_ON_HOST` | set: the FSR 4.1.1 capture runs without systemd-run |
|
||||
| `BB_FSR4CAP_RUNNER` | =steam: the FSR 4.1.1 capture uses Steam's runtime |
|
||||
| `BB_FSR4CAP_SHELL` | internal to tools/fsr4cap: already inside the nix shell with the tools |
|
||||
| `BB_FSR4CAP_SPOOF` | =0: the FSR 4.1.1 capture does not present an RDNA4 GPU to AMD's DLL |
|
||||
| `BB_FSR4CAP_UMU_SHELL` | internal to tools/fsr4cap: already inside the umu nix shell |
|
||||
| `BB_FSR4_DRIVER_MODELS` | FSR 4 driver models to build (default: navi2 lite) |
|
||||
| `BB_FSR4_TIERS` | FSR 4 v07 asset tiers to download (tools/fetch_fsr4_assets.sh) |
|
||||
| `BB_IN_NIX_SHELL` | internal to run.sh: already inside the nix shell |
|
||||
| `BB_LTO` | =1: link-time optimization (packaging) |
|
||||
| `BB_PACKAGE_FSR411` | =1: the AppImage carries FSR 4.1.1 models (never in public packages) |
|
||||
| `BB_PGO` | =generate / use: profile-guided optimization (CMake option) |
|
||||
| `BB_PGO_DIR` | the PGO profile folder (CMake option) |
|
||||
| `BB_PUBLISH_BRANCH` | =1: tools/publish_guide.sh publishes the players' guide from a branch other than main |
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# Layer memory module: PS4 GPU addresses without sparse (plan, 2026-10-09)
|
||||
|
||||
> Design notes from when this work was planned, kept for their reasoning. bbport 0.7 has one
|
||||
> memory model and its own renderer, and some switches named here (`BB_PC_MODEL`) no longer exist; the
|
||||
> current list is in [ENVIRONMENT.md](ENVIRONMENT.md).
|
||||
|
||||
## Why
|
||||
|
||||
The new memory model keeps game memory in a single sparse arena and moves blocks between game
|
||||
memory and VRAM by rebinding (`vkQueueBindSparse`). On NVIDIA (Linux, drivers ≥ 555), the bind
|
||||
time grows with the number of pages already bound, and the call holds a global lock: each
|
||||
move to VRAM cost seconds, and the game and the desktop froze (GTX 1660 Ti, 615.71). Without moves
|
||||
(`BB_FIXED_ARENA=1`) it runs at 48.6 FPS versus ~205 on an RX 7800 XT. We need a model that works the
|
||||
same on AMD, NVIDIA and Intel and is not tied to the shadPS4 buffer cache: part of a future layer
|
||||
for running PS4 games.
|
||||
|
||||
## Idea
|
||||
|
||||
The PS4 GPU has its own virtual memory (GPUVM) on top of shared memory; every V#, T# and pointer in
|
||||
user data is a virtual address. On PC we do the same with Vulkan 1.2 means (BDA):
|
||||
|
||||
- **Memory sources**: ordinary (non-sparse) Vulkan buffers:
|
||||
- chunks of game memory: dma-buf (AMD) or host memory import (NVIDIA, Intel), one chunk per
|
||||
game direct memory allocation (allocations are mapped whole, so a range inside one
|
||||
mapping is always contiguous within one chunk);
|
||||
- VRAM mirrors: segments aligned to game addresses (game page X sits in the segment at the
|
||||
same offset), allocated when the first VRAM page appears in the segment.
|
||||
- **Page table** (a buffer on the GPU): page of a PS4 address → device address (BDA) of its
|
||||
current source. It is changed by a copy in the command stream, in order with draws, without semaphores.
|
||||
- **Direct path**: the whole V# range is in one source (almost all bindings) → an ordinary
|
||||
descriptor (source buffer, offset, size). The shader translator does not change.
|
||||
- **Paged path**: the range is mixed (≈85 huge bindings per frame: shaders bound
|
||||
to all of memory) → the descriptor points to a record {PS4 address, size}, and the shader
|
||||
translates each address through the table. A pipeline variant only for such bindings
|
||||
(specialization key).
|
||||
- **Vertices, indices, indirect arguments, copies, texture uploads**: the direct path; otherwise,
|
||||
assembly into a contiguous temporary buffer (read-only) or per-page copies (the CPU knows the table).
|
||||
- **Moving to VRAM and back**: the policy of the current new model (data whose writes we
|
||||
know about: loaders, the GPU; unknown writers stay in place), but the execution is a copy + a
|
||||
table write + choosing the source at bind time. Bindings recorded before a move read the old
|
||||
source, which is still valid; VRAM is freed once the GPU has passed those commands.
|
||||
|
||||
## Order of work
|
||||
|
||||
1. **Module core** (`gpu/layer/`, no dependencies on shadPS4): sources, pages, table,
|
||||
`Resolve(address, size)` → a direct source or "mixed", walking over sources for copies.
|
||||
The command recording interface is abstract (copy, table write), so it does not pull in the scheduler.
|
||||
2. **Hook-up without VRAM**: the new model returns ranges in place through chunk buffers, not
|
||||
through the arena; the new model does not create the sparse arena. Expected: ~48 FPS on AMD (like
|
||||
`BB_FIXED_ARENA`), no `vkQueueBindSparse` at all. Check the image, then make a build for NVIDIA.
|
||||
3. **VRAM mirrors + moves**: the policy from `BufferCache`, the page table, the paged path in
|
||||
shaders for mixed bindings. Goal: parity with the sparse arena on AMD (~205 FPS).
|
||||
4. **NVIDIA/Intel**: a test build, a tester log; then remove sparse from the new model entirely.
|
||||
|
||||
Measurements: RX 7800 XT, standing still (`tools/bench.sh`), `BB_PC_MODEL=1`; diagnostics `BB_RESIDENCY_MIX=1`.
|
||||
|
||||
## Status (2026-10-09, morning)
|
||||
|
||||
- Steps 1–3 are done. On the RX 7800 XT, standing still: module ~205 FPS, sparse arena ~205–238, old model
|
||||
~220 (at the lamp in the Hunter's Nightmare); in another spot (1170 draw calls): 167.5 / 164.5 / 146.
|
||||
`vkQueueBindSparse` is never called in module mode (counter in the `Layer memory:` line).
|
||||
- Mixed bindings are solved without the paged path: "volatile" blocks (`layer_volatile`). A block with
|
||||
unknown bytes (the edge of a range, a gap between images) goes into the mirror together with
|
||||
the rest of the range, is refreshed from game memory before each binding, and whatever a dispatch
|
||||
wrote into it is copied back after the dispatch (`LayerAfterBindings`). The paged path remains for
|
||||
bindings of 64 MiB and larger. `BB_LAYER_VOLATILE=0` turns it off (for comparison).
|
||||
- Default: AMD uses the sparse arena, other GPUs use the module (`BB_LAYER_MEMORY=1/0` to
|
||||
choose manually). Step 4 (NVIDIA): needs a tester log; the host memory import path cannot be
|
||||
checked on AMD (RADV does not import memfd).
|
||||
|
||||
## Addendum: original copying and portable labels (2026-10-09)
|
||||
|
||||
In the new module, the paged path of the original copy shader is fixed: writes update game memory and the current mirror; the native shader is now selected by default. For GPU labels, a standard Vulkan path without AMD buffer marker was added. The implementation, switches and verification limits are described in [TRANSLATION_GPU_COPY_LABELS.ru.md](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/TRANSLATION_GPU_COPY_LABELS.ru.md). This removes the dependency of label writes on the AMD extension, but does not replace hardware testing on NVIDIA.
|
||||
@@ -1,68 +0,0 @@
|
||||
# Модуль памяти прослойки: адреса видеочипа PS4 без sparse (план, 2026-10-09)
|
||||
|
||||
## Зачем
|
||||
|
||||
Новая модель памяти держит память игры в одной sparse-арене и переносит блоки между памятью
|
||||
игры и VRAM перепривязкой (`vkQueueBindSparse`). На NVIDIA (Linux, драйверы ≥ 555) время
|
||||
привязки растёт с числом уже привязанных страниц, вызов держит глобальную блокировку: каждый
|
||||
перенос в VRAM стоил секунды, игра и рабочий стол вставали (GTX 1660 Ti, 615.71). Без переносов
|
||||
(`BB_FIXED_ARENA=1`) — 48,6 FPS против ~205 на RX 7800 XT. Нужна модель, работающая одинаково на
|
||||
AMD, NVIDIA и Intel, и не привязанная к буферному кэшу shadPS4: часть будущей прослойки запуска
|
||||
игр PS4.
|
||||
|
||||
## Идея
|
||||
|
||||
У видеочипа PS4 своя виртуальная память (GPUVM) над общей памятью; любой V#, T#, указатель в
|
||||
user data — виртуальный адрес. На ПК то же самое делаем средствами Vulkan 1.2 (BDA):
|
||||
|
||||
- **Источники памяти** — обычные (не sparse) буферы Vulkan:
|
||||
- куски памяти игры: dma-buf (AMD) или импорт памяти хоста (NVIDIA, Intel), по куску на
|
||||
выделение прямой памяти игры (выделения отображаются целиком — диапазон внутри одного
|
||||
отображения всегда непрерывен в одном куске);
|
||||
- зеркала в VRAM: сегменты, выровненные по адресам игры (страница игры X лежит в сегменте по
|
||||
тому же смещению), выделяются, когда в сегменте появляется первая страница в VRAM.
|
||||
- **Таблица страниц** (буфер на видеочипе): страница адреса PS4 → адрес устройства (BDA) её
|
||||
текущего источника. Меняется копией в потоке команд — в порядке с отрисовками, без семафоров.
|
||||
- **Прямой путь**: диапазон V# целиком в одном источнике (почти все привязки) → обычный
|
||||
дескриптор (буфер источника, смещение, размер). Транслятор шейдеров не меняется.
|
||||
- **Страничный путь**: диапазон смешанный (≈85 огромных привязок за кадр: шейдеры, привязанные
|
||||
ко всей памяти) → дескриптор указывает на запись {адрес PS4, размер}, шейдер переводит каждый
|
||||
адрес через таблицу. Вариант пайплайна только для таких привязок (ключ специализации).
|
||||
- **Вершины, индексы, аргументы indirect, копии, загрузка текстур**: прямой путь, иначе сборка
|
||||
в непрерывный временный буфер (только чтение) или копии по страницам (процессор знает таблицу).
|
||||
- **Перенос в VRAM и обратно** — политика нынешней новой модели (данные, о записи которых мы
|
||||
знаем: загрузчики, видеочип; неизвестные писатели — на месте), но исполнение — копия + запись в
|
||||
таблицу + выбор источника при привязке. Привязки, записанные до переноса, читают старый
|
||||
источник — он ещё верен; освобождение VRAM — когда видеочип прошёл эти команды.
|
||||
|
||||
## Порядок работ
|
||||
|
||||
1. **Ядро модуля** (`gpu/layer/`, без зависимостей от shadPS4): источники, страницы, таблица,
|
||||
`Resolve(адрес, размер)` → прямой источник или «смешанный», обход по источникам для копий.
|
||||
Интерфейс записи команд — абстрактный (копия, запись таблицы), чтобы не тянуть планировщик.
|
||||
2. **Подключение без VRAM**: новая модель отдаёт диапазоны на месте через буферы кусков, а не
|
||||
через арену; sparse-арена в новой модели не создаётся. Ожидание: ~48 FPS на AMD (как
|
||||
`BB_FIXED_ARENA`), никаких `vkQueueBindSparse`. Проверка картинки, затем сборка для NVIDIA.
|
||||
3. **Зеркала в VRAM + перенос**: политика из `BufferCache`, таблица страниц, страничный путь в
|
||||
шейдерах для смешанных привязок. Цель — паритет с sparse-ареной на AMD (~205 FPS).
|
||||
4. **NVIDIA/Intel**: тестовая сборка, лог тестера; затем убрать sparse из новой модели совсем.
|
||||
|
||||
Замеры — RX 7800 XT, стоя (`tools/bench.sh`), `BB_PC_MODEL=1`; диагностика `BB_RESIDENCY_MIX=1`.
|
||||
|
||||
## Состояние (2026-10-09, утро)
|
||||
|
||||
- Шаги 1–3 сделаны. На RX 7800 XT стоя: модуль ~205 FPS, sparse-арена ~205–238, старая модель
|
||||
~220 (у лампы в Кошмаре охотника); в другом месте (1170 вызовов отрисовки) — 167,5 / 164,5 / 146.
|
||||
`vkQueueBindSparse` в режиме модуля не вызывается ни разу (счётчик в строке `Layer memory:`).
|
||||
- Смешанные привязки решены без страничного пути: «летучие» блоки (`layer_volatile`) — блок с
|
||||
неизвестными байтами (край диапазона, промежуток между образами) кладётся в зеркало вместе с
|
||||
остальным диапазоном, обновляется из памяти игры перед каждой привязкой, а то, что запуск в него
|
||||
записал, копируется обратно после запуска (`LayerAfterBindings`). Страничный путь остаётся для
|
||||
привязок от 64 МиБ. `BB_LAYER_VOLATILE=0` — выключить (для сравнения).
|
||||
- По умолчанию: AMD — sparse-арена, остальные видеокарты — модуль (`BB_LAYER_MEMORY=1/0` —
|
||||
вручную). Шаг 4 (NVIDIA): нужен лог тестера; путь импорта памяти хоста на AMD не проверить
|
||||
(RADV не импортирует memfd).
|
||||
|
||||
## Дополнение: оригинальное копирование и переносимые метки (2026-10-09)
|
||||
|
||||
В новом модуле исправлен paged-путь оригинального copy shader: записи обновляют память игры и актуальное зеркало; нативный шейдер теперь выбран по умолчанию. Для GPU-меток добавлен стандартный Vulkan-путь без AMD buffer marker. Реализация, переключатели и границы проверки описаны в [TRANSLATION_GPU_COPY_LABELS.ru.md](TRANSLATION_GPU_COPY_LABELS.ru.md). Это закрывает зависимость записи меток от AMD-расширения, но не заменяет аппаратную проверку NVIDIA.
|
||||
+15
-8
@@ -1,6 +1,6 @@
|
||||
# Mods and third-party patches
|
||||
|
||||
Both are on the launcher's **Settings** page and apply at the next start. The game folder itself
|
||||
Both are in the launcher's **Mods** section and apply at the next start. The game folder itself
|
||||
is never changed.
|
||||
|
||||
## File mods
|
||||
@@ -25,11 +25,16 @@ 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).
|
||||
- Up to three extra wrapper folders, as extracting an archive into a folder of its name leaves it:
|
||||
`Mod name/Mod name v1.2/dvdroot_ps4/...` (notes, pictures and links may sit next to them:
|
||||
`README`, `.txt`, `.pdf`, `.png`, `.webp`, `.url`, ...).
|
||||
- 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`).
|
||||
`dvdroot_ps4`), also next to other folders such as `Screenshots` or `Optional`, which stay out
|
||||
of the game.
|
||||
|
||||
A folder in the mods folder that is not a mod is listed in the launcher under the mods with the
|
||||
reason: still an archive, several mods in one folder, no game files, or empty.
|
||||
|
||||
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`.
|
||||
@@ -52,9 +57,8 @@ 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.
|
||||
as one mod. **Load mods** turns all of them off. The field of view works the same way, as files
|
||||
laid under the mods; it is a setting of its own and stays on when mods are off.
|
||||
|
||||
### How it works
|
||||
|
||||
@@ -101,9 +105,12 @@ the launcher.
|
||||
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.
|
||||
- shadPS4's patch file can stay named `Bloodborne.xml`. In a file of that name, the patches
|
||||
named as one of bbport's own are left out (bbport's apply), and the rest start off until you
|
||||
turn them on, whatever the file says. An older package's copy of bbport's file adds nothing.
|
||||
- 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.
|
||||
This guide is based on bbport's [MODS.md](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/MODS.md) (Russian), by deadinside28.
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
# Файловые моды
|
||||
|
||||
В лаунчере появилась группа **Моды**: выбор папки, общий переключатель,
|
||||
включение каждого мода и стрелки порядка загрузки. Изменения применяются при
|
||||
следующем запуске. Нижний включённый мод имеет больший приоритет; при совпадении
|
||||
файлов в журнале выводится, какой мод заменил предыдущий.
|
||||
|
||||
По умолчанию используется `bbport/mods/`, а в AppImage —
|
||||
`~/.local/share/bbport/mods/`. Распакуйте скачанный мод так:
|
||||
|
||||
```text
|
||||
mods/
|
||||
Название мода/
|
||||
dvdroot_ps4/
|
||||
chr/...
|
||||
param/...
|
||||
menu/...
|
||||
```
|
||||
|
||||
Также принимаются:
|
||||
|
||||
- `Название мода/app0/dvdroot_ps4/...` и `Название мода/CUSA03173/dvdroot_ps4/...`;
|
||||
- лишняя папка-обёртка, как после распаковки архива в папку с его именем:
|
||||
`Название мода/Название мода v1.2/dvdroot_ps4/...` (рядом могут лежать readme/картинки);
|
||||
- мод без `dvdroot_ps4`, в котором сразу лежат папки игры: `Название мода/chr/...`,
|
||||
`Название мода/parts/...` (`chr`, `parts`, `map`, `menu`, `msg`, `param`, `sfx`,
|
||||
`sound`, `font` и другие папки `dvdroot_ps4`).
|
||||
|
||||
Регистр имён в моде не важен: `DVDROOT_PS4/Chr/C0000.chrbnd.dcx` из мода, собранного
|
||||
под Windows, заменяет игровой `dvdroot_ps4/chr/c0000.chrbnd.dcx`. Раньше такой файл
|
||||
добавлялся рядом под своим именем, и игра его не видела. В журнале запуска видно, сколько
|
||||
файлов игры мод заменил и сколько добавил: `Mods: 12 game files replaced, 0 added`.
|
||||
Если мод должен заменять файлы, а пишет `0 game files replaced`, проверьте пути внутри мода.
|
||||
Архивы ZIP/7z нужно предварительно распаковать. Поддерживаются готовые замены
|
||||
файлов `dvdroot_ps4`: текстуры, модели, параметры, интерфейс, звук и шрифты.
|
||||
Менеджер не объединяет содержимое двух одинаковых `.dcx`/`.bnd` файлов: последняя
|
||||
замена выигрывает целиком.
|
||||
|
||||
Для уже подготовленной папки shadPS4 работает соседний каталог
|
||||
`CUSA03173-mods/dvdroot_ps4/...` рядом с `CUSA03173/`. Он подключается автоматически
|
||||
перед модами из списка. Общий переключатель выключает и его. Можно также выбрать
|
||||
папку, непосредственно содержащую `dvdroot_ps4`, как папку модов; такая папка
|
||||
подключается целиком, без списка отдельных модов.
|
||||
Соглашение `-mods` проверено по
|
||||
[исходникам shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/file_sys/fs.cpp).
|
||||
|
||||
Оригиналы игры не перезаписываются. На время запуска создаётся отдельное
|
||||
объединённое дерево ссылок в каталоге данных `out/mod-game-*`; чтение, stat и
|
||||
перечисление каталогов видят выбранные замены и остальные оригинальные файлы.
|
||||
Игровые `/app0` и `/hostapp` доступны только для чтения. Сохранения остаются
|
||||
в прежней папке и доступны для записи. При обычном завершении или остановке
|
||||
лаунчером временное дерево удаляется. После SIGKILL/аварийного отключения оно
|
||||
может остаться; такие каталоги не используются в следующем запуске.
|
||||
|
||||
Моды с заменой `eboot.bin`, `sce_module` или `sce_sys`, инжекторы DLL и скриптовые
|
||||
плагины этим загрузчиком не поддерживаются. Патчи исполняемого кода остаются
|
||||
в существующей системе `patches/` / `BB_PATCHES`. Символьные ссылки внутри мода
|
||||
не принимаются. Совместимость конкретного мода с CUSA03173 1.09 определяется
|
||||
его автором.
|
||||
|
||||
Для запуска из терминала:
|
||||
|
||||
```sh
|
||||
BB_GAME_DIR=/path/to/CUSA03173 BB_MODS_DIR=/path/to/mods bash run.sh
|
||||
BB_GAME_DIR=/path/to/CUSA03173 BB_MODS_ENABLED=0 bash run.sh
|
||||
```
|
||||
|
||||
Порядок и отключения сохраняются в каталоге данных `mods.json`:
|
||||
|
||||
```json
|
||||
{"order": ["Первый", "Второй"], "disabled": ["Первый"]}
|
||||
```
|
||||
|
||||
Новые папки включаются автоматически и добавляются в конец по алфавиту.
|
||||
`BB_MODS_CONFIG` меняет путь профиля. `BB_MOD_TRACE=1` с `BB_MODS_DIR` выводит
|
||||
первые чтения реально подключённых файлов для диагностики.
|
||||
|
||||
# Сторонние патчи (XML shadPS4)
|
||||
|
||||
Папка `patches/` в каталоге данных (в AppImage — `~/.local/share/bbport/patches/`, в лаунчере
|
||||
группа **Сторонние патчи**, там же можно выбрать другую папку). Подходят XML-файлы патчей в
|
||||
формате shadPS4/GoldHEN: берутся `Metadata` для `AppVer="01.09"` и `eboot.bin`, файлы с
|
||||
`TitleID` других игр пропускаются. Включение по умолчанию — из `isEnabled` файла, выбор в
|
||||
лаунчере сохраняется в `patches.json` каталога данных:
|
||||
|
||||
```json
|
||||
{"enabled": ["файл.xml/Имя патча"], "disabled": ["файл.xml/Другой патч"]}
|
||||
```
|
||||
|
||||
Поддерживаются строки `bytes`, `bytes16/32/64`, `float32/64`, `utf8`, `utf16`.
|
||||
Патч со строками `mask` (поиск по шаблону) или с адресом вне eboot пропускается целиком,
|
||||
с сообщением в журнале. Сторонние патчи применяются после встроенных
|
||||
(`patches/Bloodborne.xml`): при пересечении побеждает сторонний. Из терминала:
|
||||
`BB_PATCHES_DIR=/путь BB_PATCHES_CONFIG=/путь/patches.json bash run.sh`.
|
||||
|
||||
Патчи, которые заменяют указатели в таблицах (как `60 FPS++` и `90 FPS++`), загрузчик
|
||||
переносит на реальный адрес образа. Раньше такие патчи останавливали запуск ошибкой
|
||||
`patch overlaps a relocation`.
|
||||
|
||||
# Резкость TAA
|
||||
|
||||
Настройки `sharpen` и `sharpness` теперь работают также для TAA, в лаунчере и
|
||||
меню игры. Диапазон — **0…1**; при 0 или выключенном переключателе дополнительного
|
||||
прохода нет. RCAS применяется к готовой сцене перед HUD. В историю TAA сохраняется
|
||||
цвет до повышения резкости, чтобы она не усиливалась при повторном накоплении.
|
||||
Ровные участки сохраняют исходный цвет, включая HDR; дополнительный буфер не нужен.
|
||||
@@ -1,232 +0,0 @@
|
||||
# Новая модель памяти на NVIDIA: итог работы (2026-10-09)
|
||||
|
||||
Замеры ниже относятся к этапу разработки модуля. Актуальные изменения `0.5-pre2` и результаты
|
||||
проверки оригинального copy shader: [CHANGES_0.5-pre2.md](CHANGES_0.5-pre2.md) и
|
||||
[TRANSLATION_GPU_COPY_LABELS.ru.md](TRANSLATION_GPU_COPY_LABELS.ru.md).
|
||||
|
||||
## Задача
|
||||
|
||||
Новая модель памяти и трансляции (`BB_PC_MODEL=1`) держала память игры в одном sparse-буфере
|
||||
(«арене») и переносила горячие блоки в VRAM перепривязкой страниц (`vkQueueBindSparse`). На
|
||||
проприетарном драйвере NVIDIA для Linux это не работает: начиная с версии 555 время
|
||||
`vkQueueBindSparse` растёт с числом уже привязанных страниц, а сам вызов держит глобальную
|
||||
блокировку. У тестера (GTX 1660 Ti, драйвер 615.71) каждый перенос держал видеочип 10 секунд и
|
||||
дольше, и вместе с игрой вставал рабочий стол. Поэтому новая модель была доступна только на AMD.
|
||||
|
||||
Цель: новая модель памяти на всех видеокартах.
|
||||
|
||||
## Решение: модуль памяти прослойки
|
||||
|
||||
Вместо sparse-арены работает собственный модуль памяти (`gpu/layer/bblayer_gpu_memory.*`,
|
||||
`gpu/shadps4/video_core/buffer_cache/buffer_cache_layer.cpp`, план — `MEMORY_MODULE_PLAN.ru.md`):
|
||||
|
||||
- память игры привязывается **на месте**, через буферы её кусков (dma-buf на AMD, импорт памяти
|
||||
хоста `VK_EXT_external_memory_host` на NVIDIA);
|
||||
- часто читаемое копируется в VRAM в отдельные буферы-«зеркала». Перенос сводится к копии и
|
||||
учёту в модуле, без перепривязки страниц;
|
||||
- привязка берёт зеркало, если диапазон целиком в VRAM, иначе память игры на месте. Диапазоны от
|
||||
64 МиБ идут через таблицу страниц в транслированных шейдерах.
|
||||
|
||||
**`vkQueueBindSparse` в этом режиме не вызывается ни разу**: счётчик выводится в строке журнала
|
||||
`Layer memory: ... vkQueueBindSparse: 0 calls`.
|
||||
|
||||
### Когда включается
|
||||
|
||||
| Видеокарта | По умолчанию |
|
||||
|---|---|
|
||||
| AMD | sparse-арена, как раньше |
|
||||
| NVIDIA, Intel и остальные | модуль памяти, автоматически |
|
||||
|
||||
`BB_LAYER_MEMORY=1` / `=0` выбирает вручную. Лаунчер больше не прячет переключатель «Новая модель
|
||||
памяти и трансляции» на видеокартах не от AMD. Стартовая проверка импорта памяти хоста в режиме
|
||||
модуля проверяет запись видеочипа и процессора через сам импортированный буфер, без sparse-буфера.
|
||||
|
||||
## Что пришлось исправить, чтобы модуль заработал правильно и быстро
|
||||
|
||||
### Скорость: с 48–60 FPS до ~205 FPS на AMD (стоя у лампы)
|
||||
|
||||
1. **Подмена копий и очисток образов не срабатывала.** Детекторы `IsComputeImageCopy` и
|
||||
`IsComputeImageClear` ждали у шейдера ровно два буфера, а модуль добавлял каждому шейдеру
|
||||
третий — таблицу страниц. Копия целей рендеринга (`cs 3d5ebf4e`) и очистка (`cs 8b355b5a`,
|
||||
31 раз за кадр) выполнялись настоящими шейдерами через память, а образы 1080p
|
||||
перезагружались каждый кадр. Теперь таблица страниц не считается (`ShaderBufferCount`):
|
||||
60 → 136 FPS.
|
||||
2. **Смешанные диапазоны.** Если у диапазона все блоки в VRAM, кроме одного-двух крайних с
|
||||
«неизвестными» байтами (промежуток между образами, чужие данные), весь диапазон читался через
|
||||
шину. Такие блоки теперь «летучие»: лежат в зеркале и обновляются из памяти игры перед каждой
|
||||
привязкой на чтение. 139 → 207 FPS. Для привязок на запись летучие блоки не используются.
|
||||
3. **Перенос в VRAM при первой привязке** диапазона известных данных (как делала арена). Он
|
||||
соблюдает `BB_GARLIC_VRAM=0` и бюджет VRAM (`BB_VRAM_PROMOTE_PERCENT`, важно для карт на 6 ГБ).
|
||||
4. **Нагрузка на процессор.** Ответы модуля кэшируются по поколению размещения; повторные
|
||||
проверки «можно ли перенести» — не чаще раза за кадр на диапазон; проверка «есть ли у
|
||||
диапазона копия в VRAM» — без блокировок, по счётчику версий модуля. Это сняло около 20%
|
||||
времени привязок и почти все блокировки в потоке командного процессора.
|
||||
|
||||
### Правильность: взрывы вершин и мусорные текстуры
|
||||
|
||||
Причина была одна: **копия в VRAM расходилась с памятью игры**. Найдено и закрыто несколько
|
||||
путей:
|
||||
|
||||
1. **Код игры пишет в память без нашего ведома**: заголовки её кучи (`0x2085a60` / `0x20858a0`),
|
||||
часть загрузчика ресурсов (`0x216d420`). Блоки с копией в VRAM теперь защищены от записи
|
||||
собственными ловушками модуля. Запись проходит сразу, а блок возвращается на место перед
|
||||
следующей привязкой. Часто записываемые блоки остаются на месте навсегда (`NoteCpuWrite`).
|
||||
2. **Чтения мимо модуля.** Мелкие привязки, кольцо констант и копирования командного процессора
|
||||
брали данные из памяти игры, хотя новые данные видеочипа лежали в VRAM: в новой модели
|
||||
`IsRegionGpuModified` знает только о незавершённых записях. Теперь всё, что задевает блок с
|
||||
копией в VRAM, идёт через модуль (`LayerMirrored`).
|
||||
3. **Данные видеочипа затирали новые данные процессора.** При возврате блока на место и при
|
||||
повторной выдаче памяти игре модуль копировал «изменённое видеочипом» обратно, в том числе
|
||||
поверх того, что процессор успел записать позже: пометки ставились слишком широко. Теперь
|
||||
заявленные записи процессора снимают пометки со своих байтов (`LayerForgetGpuData`), выдача
|
||||
памяти заново снимает пометки со своего диапазона, а после незаявленной записи данные видеочипа
|
||||
в блоке считаются мёртвыми и назад не копируются.
|
||||
4. **Куда видеочипу можно писать в VRAM** (`BB_LAYER_GPU_WRITES`): по умолчанию (`1`) — только в
|
||||
блоки, которые процессор не трогал с момента выделения: цели рендеринга и другие данные одного
|
||||
видеочипа. `0` — никогда (правильно, но 60–70 FPS: записи идут через шину), `2` — везде (давало
|
||||
взрывы).
|
||||
5. **Шейдер копирования игры** в `0.5-pre2` по умолчанию транслируется в SPIR-V и выполняется
|
||||
на GPU. Таблица страниц содержит актуальное GPU-представление и адреса памяти игры;
|
||||
copy shader записывает результат в оба представления. Это устраняет расхождение зеркала
|
||||
VRAM и памяти игры без подмены оригинального списка копий на CPU.
|
||||
`BB_COPY_SHADER_NATIVE=0` оставляет HLE для сравнения.
|
||||
|
||||
На предыдущем этапе проверено в игре на AMD (модуль включён вручную, шейдер копирования через HLE): с
|
||||
`BB_LAYER_GPU_WRITES=0` и с правилом «видеочип пишет в VRAM только внутри образов» — Сон
|
||||
Охотника, собаки и трупы в Ярнаме чистые. На последней сборке (правило «блоки, которые процессор
|
||||
не трогал») артефактов при игре не замечено, но отдельной проверки всех мест ещё не было.
|
||||
|
||||
## Замеры (RX 7800 XT, AMD, модуль включён вручную)
|
||||
|
||||
| Место | Модуль | Sparse-арена | Старая модель |
|
||||
|---|---|---|---|
|
||||
| У лампы, утром (до исправлений правильности) | ~205 FPS | ~205–238 | ~220 |
|
||||
| Сон Охотника, сейчас | 130–144 FPS | — | — |
|
||||
|
||||
В Сне Охотника упираемся то в поток команд видеочипа на процессоре (~3,9 мкс на вызов при
|
||||
~1500 вызовах за кадр), то в сам видеочип: проходы G-буфера 960×540, FSR ~1,07 мс.
|
||||
|
||||
**Открытый вопрос:** в части запусков на том же месте видеочип работает заметно медленнее
|
||||
(70–95 FPS, FSR до 3,2 мс вместо 1,07). Причина не найдена; версии с компиляцией шейдеров,
|
||||
поучастковым профилем `BB_SECTIONS` и обновлением летучих блоков не подтвердились. Сравнение
|
||||
запусков осложняет то, что игра рисует разные проходы в зависимости от ракурса и сцены.
|
||||
|
||||
## Что не проверено
|
||||
|
||||
- **Сама NVIDIA.** На AMD путь NVIDIA (импорт памяти хоста + модуль) не воспроизвести: RADV не
|
||||
импортирует memfd. Нужен лог тестера.
|
||||
- Карты с малым объёмом VRAM (6 ГБ): поведение при заполненной VRAM проверено только
|
||||
искусственно (`BB_VRAM_PROMOTE_PERCENT=1`: копий нет, ~38 FPS, стабильно).
|
||||
- Intel.
|
||||
|
||||
## Что попросить у тестера на NVIDIA
|
||||
|
||||
Сборку с включённой в лаунчере «Новой моделью памяти и трансляции». В журнале должны быть строки:
|
||||
|
||||
- `Guest memory: host memory imported by the GPU (VK_EXT_external_memory_host ...)`;
|
||||
- `Guest memory: the new memory model through the layer's memory module on this GPU (vendor 0x10de)`;
|
||||
- `Layer memory: ... vkQueueBindSparse: 0 calls, 0 ranges; write traps hit N`;
|
||||
- `Frame stats: ... FPS`.
|
||||
|
||||
Если видна строка `vkQueueBindSparse of N ranges took X ms`, значит, sparse где-то всё же
|
||||
используется — это ошибка.
|
||||
|
||||
## Первые логи с NVIDIA и исправления (вечер 2026-10-09, `0.5-pre3`)
|
||||
|
||||
Тестер: GTX 1660 Ti (ноутбук, 6 ГБ, PCIe 3.0), драйвер 615.71, сборка 0.5-pre2. Импорт памяти
|
||||
хоста и модуль включились, `vkQueueBindSparse: 0`.
|
||||
|
||||
1. **Зеркал не было вовсе** (10–15 FPS, мерцание фона на титульном экране): `FixedArena()` по
|
||||
умолчанию включалась при импорте памяти хоста и через `GarlicInVramForLayer()` запрещала модулю
|
||||
переносы. Теперь в режиме модуля она всегда выключена. До новой сборки обход — `BB_FIXED_ARENA=0`:
|
||||
с ним у тестера 30–35 FPS (видео), против 60–70 в старой модели.
|
||||
2. **Кэш текстур и зеркала делили VRAM без общего бюджета.** Сборщик кэша текстур сравнивает с
|
||||
порогами всю VRAM процесса, зеркала тоже, а пороги на картах меньше ~9 ГБ — 40 и 80 %
|
||||
стартового бюджета (у тестера давление 1963 МиБ, критическая отметка 3927 МиБ). Переносы же
|
||||
вставали только на 90 % бюджета драйвера. Итог — 3,9–4,0 ГБ, сборщик постоянно за критической
|
||||
отметкой: 155–1255 выселенных образов за 5 с против 0–29 на AMD. Теперь:
|
||||
- переносы в VRAM встают за 384 МиБ до критической отметки (`VramPromotionsPaused`, строка
|
||||
`copies of loaded blocks wait`);
|
||||
- за 128 МиБ до неё модуль сам освобождает место (`LayerYieldToImages`): сначала обрезает запас
|
||||
роста зеркал, затем возвращает на место целые зеркала, не использовавшиеся 3 с
|
||||
(`BB_VRAM_PRESSURE_IDLE_SECONDS`).
|
||||
3. **Перестройка зеркал.** Участок, примыкающий к зеркалу, создавал новое зеркало целиком с
|
||||
копией старого. При загрузке уровня это 15–27 ГБ выделений на 1,3 ГБ данных и всплеск VRAM до
|
||||
7,4 ГБ (у тестера в логе 5,6 ГБ при физических 6). Во время игры, при подгрузке районов,
|
||||
то же самое. Теперь растущее вверх зеркало получает запас под рост, равный своему размеру, но
|
||||
не больше 64 МиБ (`BB_LAYER_MIRROR_GROWTH_MB`): 5–10 ГБ выделений, худший кадр загрузки 555 →
|
||||
87 мс. Заменённые зеркала освобождаются на каждой отправке, а не раз в секунду.
|
||||
4. **Записи `WRITE_DATA` в зеркало.** Командный процессор пишет на видеочипе в память игры; блок
|
||||
с зеркалом раньше уходил на место. Если диапазон целиком в одном зеркале, запись идёт в оба
|
||||
представления (`CommandWriteMirror`).
|
||||
5. **Наблюдаемые блоки** (`BB_LAYER_WATCH`, по умолчанию включено). Блоки, в которых процессор
|
||||
когда-то писал без предупреждения, раньше всегда читались на месте, а значит на NVIDIA через
|
||||
шину. Теперь для читающих привязок они тоже получают зеркало под ловушкой записи. Запись
|
||||
процессора не возвращает блок на место: он перезаливается перед следующей привязкой, а
|
||||
ловушка взводится снова. Видеочип в такие блоки пишет на месте; блок, куда он записал,
|
||||
больше не наблюдается. Наблюдение почти полностью заменило летучие блоки (RX 7800 XT:
|
||||
1,3 ГБ/с перезаливок → 36 МБ/с; у тестера летучих было 455 МБ/с). Блоки, которые процессор
|
||||
пишет часто (3 кадра из 60), по-прежнему уходят на место: перезаливка их перед привязками
|
||||
завершала render pass (видеочип 4,8 → 7,2 мс за кадр). `BB_LAYER_WATCH_HOT=1` — эксперимент
|
||||
с ними.
|
||||
6. Чтения файлов рантайма (`read`/`pread`) повторяются, если ядро вернуло `EFAULT`: ловушку могли
|
||||
взвести снова между касанием страниц и чтением.
|
||||
|
||||
Проверка на RX 7800 XT с `BB_VRAM_LIMIT_MB=5600` (пороги сборщика как у тестера: 1960/3920):
|
||||
старая модель ~195 FPS; модуль ~200–213 FPS, видеочип 4,6–5,2 мс; VRAM держится ниже
|
||||
критической отметки; маршрут `docs/route2.txt` без кадров дольше 25 мс; 3 минуты прогулки без
|
||||
ошибок; картинка в Сне Охотника и в конце маршрута как без наблюдения. Сама NVIDIA этими
|
||||
замерами не проверена: на AMD чтения на месте почти бесплатны.
|
||||
|
||||
## Отзывы на 0.5-pre3 и исправления (`0.5-pre4`)
|
||||
|
||||
- **GTX 1060 6 ГБ:** новая модель вровень со старой (41–48 FPS при ~1500 вызовах за кадр в обеих,
|
||||
видеочип 21–24 мс). Ранние просадки — компиляция шейдеров в первой сессии (160–241 компиляция
|
||||
за 5 с, рывки 1–3,7 с): теперь новые конвейеры проходов, рисуемых каждый кадр, собираются в
|
||||
фоне (`BB_ASYNC_PIPELINES`), а их отрисовки кадр-другой пропускаются.
|
||||
- **Карта на 4 ГБ:** через ~4,5 минуты 5 FPS — регрессия pre3. Переносы в VRAM вставали за
|
||||
384 МиБ до критической отметки сборщика текстур (у карты 4 ГБ — нижние значения порогов,
|
||||
1536/3072 МиБ), а текстуры и служебные выделения уже держали 2808 МиБ: копий стало 124 МиБ на
|
||||
1,9 ГБ данных, видеочип ~200 мс на кадр. Теперь у зеркал своя доля (25 % бюджета драйвера,
|
||||
`BB_LAYER_MIRROR_FLOOR_PERCENT`): ниже неё пауза из-за сборщика не действует.
|
||||
- **После телепорта ~10 FPS меньше:** большие привязки на всю память (через таблицу страниц)
|
||||
помечали каждые 2 МиБ как используемые, поэтому в игре ничего не простаивало, и зеркала
|
||||
покинутой локации держали VRAM. Теперь учитываются только прямые привязки
|
||||
(`layer_group_use`): при нехватке VRAM сначала уходят данные, к которым напрямую не
|
||||
привязывались 3 с, а зеркала с памятью без действительных блоков пересоздаются только поверх
|
||||
действительных (`LayerCompactMirrors`) — иначе частично вернувшееся на место зеркало держало
|
||||
всю свою память. На RX 7800 XT с `BB_VRAM_LIMIT_MB=4200–4600` это освобождало 160–490 МиБ за
|
||||
раз, картинка не менялась. На NVIDIA не проверено.
|
||||
|
||||
## Переключатели и диагностика
|
||||
|
||||
| Переменная | Что делает |
|
||||
|---|---|
|
||||
| `BB_LAYER_MEMORY=1/0` | модуль памяти вкл/выкл (по умолчанию: на не-AMD вкл) |
|
||||
| `BB_LAYER_GPU_WRITES=0/1/2` | где видеочипу можно писать в VRAM (см. выше), по умолчанию 1 |
|
||||
| `BB_LAYER_WRITE_TRAPS=0` | без ловушек записи (только для сравнения: вернутся взрывы) |
|
||||
| `BB_LAYER_VOLATILE=1` | с летучими блоками (с 2026-10-10 выключены по умолчанию: их копии разрывали проходы рендера) |
|
||||
| `BB_LAYER_PAGED=0` | без таблицы страниц для привязок от 64 МиБ |
|
||||
| `BB_COPY_SHADER_NATIVE=0/1` | HLE / нативный шейдер копирования игры; в новой модели по умолчанию нативный |
|
||||
| `BB_RESIDENCY_MIX=1/2` | привязки на месте / в VRAM / смешанные; `2` — ещё и самые тяжёлые с разбором блоков |
|
||||
| `BB_LAYER_WRITE_LOG=1` | крупные записи видеочипа, оставленные на месте, и образы рядом |
|
||||
| `BB_TOGGLE_FILE`, бит эксперимента 8 | чтение на месте вместо копий в VRAM (проверка устаревших копий) |
|
||||
| `BB_TOGGLE_FILE`, бит эксперимента 16 | без обновлений летучих блоков |
|
||||
| `BB_LAYER_WATCH=0` | без наблюдаемых блоков (блоки, где писал процессор, читаются на месте) |
|
||||
| `BB_LAYER_WATCH_HOT=1` | наблюдать и часто записываемые процессором блоки (эксперимент) |
|
||||
| `BB_LAYER_MIRROR_GROWTH_MB=N` | запас роста зеркала, по умолчанию 64; `0` — без запаса |
|
||||
| `BB_VRAM_PRESSURE_IDLE_SECONDS=N` | при нехватке VRAM на место уходят данные, к которым напрямую не привязывались N с (3; `0` — нет) |
|
||||
| `BB_LAYER_MIRROR_FLOOR_PERCENT=N` | доля бюджета VRAM, которую сборщик текстур не забирает у зеркал (25) |
|
||||
| `BB_ASYNC_PIPELINES=0` | новые конвейеры компилируются сразу, игра ждёт (по умолчанию — в фоне для проходов, рисуемых каждый кадр) |
|
||||
| `BB_ASYNC_PIPELINE_THREADS=N` | число потоков фоновой компиляции (по умолчанию четверть потоков процессора, 1–4) |
|
||||
| `BB_VRAM_LIMIT_MB=N` | тест: бюджет VRAM драйвера считается не больше N МиБ (имитация карты на 6 ГБ: 5600) |
|
||||
| `BB_RESIDENCY_MIX=1` | строка `Layer in place` теперь также говорит, почему блоки не наблюдаются |
|
||||
|
||||
## Что дальше
|
||||
|
||||
1. Лог с NVIDIA.
|
||||
2. Найти причину медленных запусков (или доказать, что это разная сцена).
|
||||
3. Летучие блоки обновляются на 0,9–1,6 ГБ/с — дешевле было бы обновлять их только после
|
||||
записи процессора (ловушки уже есть).
|
||||
4. Если модуль на NVIDIA подтвердится, перевести на него и AMD и убрать sparse-арену из новой
|
||||
модели.
|
||||
+111
-36
@@ -1,35 +1,113 @@
|
||||
# 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.
|
||||
Online play is a beta. Messages, bloodstains and ghosts come from a community game server:
|
||||
Seamless Hunt's (the default) or [The Hunter's Dream](https://thehuntersdream.com). Bells and summons go through a co-op pool,
|
||||
and then the players' games connect to each other. Linux and Windows players can play together:
|
||||
everyone needs the game at version 1.09 and the same pool.
|
||||
|
||||
> **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.
|
||||
> **shadPS4's public shadNet server no longer accepts bbport builds**: it signs the game out
|
||||
> right after the login ("The shadNet server doesn't accept this shadPS4 build" in the log), and
|
||||
> the launcher says so. The pools for bbport are Seamless Hunt (the default), The Hunter's Dream's and a server of your own.
|
||||
|
||||
## Account
|
||||
## Seamless Hunt
|
||||
|
||||
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.
|
||||
[Seamless Hunt](https://install.seamlesshunt.com) is a community server for Seamless Co-op (BBSC),
|
||||
where Windows players join with BBSC's package for the Windows port.
|
||||
|
||||
If the game says you are not signed in to PlayStation Network, the shadNet login failed. The
|
||||
usual cause is the password: the log then has a shadNet login error (code 8 is a wrong
|
||||
password). *Test the connection* only checks that the servers answer, not the account.
|
||||
1. In the launcher's **Multiplayer → Online** tab, turn on **Play online** and choose **Seamless Hunt**: its
|
||||
shadNet server, WebAPI and game server come with it.
|
||||
2. Type a name and a password under **Account** and press **Create** (once). The server wants an
|
||||
email for each account; the launcher makes one from the name, as Seamless Hunt's own tool does.
|
||||
3. Turn on BBSC in the **Seamless co-op (BBSC)** group and press **Test** in the **Status** row.
|
||||
|
||||
Its bloodstains and death visions crash the game with "Transaction failed", on Windows too: they
|
||||
are off by default, as in Seamless Hunt's package (**Bloodstains on Seamless Hunt** brings them
|
||||
back). Messages, ghosts and summon signs are not affected.
|
||||
|
||||
## The Hunter's Dream (with bbhost's players)
|
||||
|
||||
Choose **The Hunter's Dream (with bbhost players)** in the launcher's **Multiplayer → Online** tab.
|
||||
The game's online module talks to The Hunter's Dream itself, the way bbhost does (from bbport 0.7):
|
||||
nothing else runs next to the game. Its account is your Discord sign-in, the same one bbhost uses:
|
||||
|
||||
1. Press **Sign in with Discord**. The launcher shows a code, copies it and opens The Hunter's
|
||||
Dream's page.
|
||||
2. On that page, press *Sign in with Discord to link* and sign in; back on the page, enter the code
|
||||
in the code field (not in *Sign in with a code from the game*).
|
||||
|
||||
No password is kept: the launcher keeps the sign-in's token with its settings. **Sign out** forgets
|
||||
it. **Test** checks The Hunter's Dream's game server and its co-op service. Seamless co-op (BBSC)
|
||||
is not available there.
|
||||
|
||||
Report problems here ([issues](https://github.com/ayozetr/bloodborne_linux/issues)), not to The
|
||||
Hunter's Dream's support.
|
||||
|
||||
## Your own server
|
||||
|
||||
Its owner creates the account ([hosting your own server](SERVER.md)), or you make it with **Create** under
|
||||
**Account** when the server lets anyone register.
|
||||
|
||||
1. In the launcher's **Multiplayer → Online** tab, turn on **Play online**, choose **Your own server** 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.
|
||||
2. Press **Test** in the **Status** row: 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.
|
||||
|
||||
## Seamless co-op (BBSC)
|
||||
|
||||
[BBSC](https://github.com/Classmg22/BBSC) (Bloodborne Seamless Co-op, by CLS_AMG) keeps a co-op
|
||||
session going through deaths, lamps, areas, bosses and the Hunter's Dream, for up to 5 hunters:
|
||||
the guest follows the host through lamps, sees the host's world (doors, bosses, lifts), keeps the
|
||||
items and echoes it gets, and enemies get stronger for every guest. It runs inside the game's
|
||||
online module (`gpu/bbnet20/bbsc`, BBSC 0.0.2 from bbport 0.7). It is new, and its own author
|
||||
marks much of it as untested.
|
||||
|
||||
1. In the launcher's **Multiplayer → Online** tab, **Turn on BBSC** in the **Seamless co-op (BBSC)**
|
||||
group (on by default). While it is on, BBSC's bells replace the game's; turned off, the game's
|
||||
come back the next time the character is loaded (the save is copied to `save-backups` first).
|
||||
**BBSC settings**, folded under it, has BBSC's own options: what the guest shares with the host,
|
||||
five hunters, enemy scaling and its percentages per guest, shared progress and the rest; only
|
||||
the ones changed from BBSC's defaults are passed to the game.
|
||||
2. Every player of the session needs BBSC, the same version: on Linux this port, on Windows
|
||||
BBSC's package for the Windows port (its *BBport* release). A player without it plays the
|
||||
normal game.
|
||||
3. The server decides how much of it works:
|
||||
- **BBSC's game server** (Seamless Hunt's, which comes with it, or your group's,
|
||||
[hosted with it](SERVER.md#seamless-co-op-bbsc)): everything, summons across areas and 5 hunters included. Set it as the
|
||||
**Game server** of the Online tab.
|
||||
- **The Hunter's Dream** has no BBSC: the normal game's co-op, with bbhost's players.
|
||||
|
||||
With BBSC and BBSC's game server, two players who cannot reach each other directly (CGNAT,
|
||||
strict routers, two games on one PC) are connected through that server's UDP relay (port
|
||||
3478); `SHADPS4_P2P_RELAY=0` turns it off.
|
||||
|
||||
BBSC gives every character its own items, which cost no Insight: the **Hunt Bell** (call other
|
||||
players), the **Kin Bell** (join another player), the **Bloodlust Bell** (invade), the **Duel Bell**
|
||||
(PvP on and off during co-op) and the **Parting Blank** (end co-op). It takes the game's Beckoning
|
||||
Bell, Small Resonant Bell and Silencing Blank out of the inventory, and they do not come back when
|
||||
BBSC is turned off; BBSC's own items stay, with their names (scripts/bbsc_kit_texts.py), and work again
|
||||
when it is back on. Before changing anything it backs the saves up to `user/bbsc/save-backups/`:
|
||||
copy a character's save from there to play it without BBSC with its old bells. Its texts follow the
|
||||
language the game shows its text in (*Game text language* in the launcher, else the system's):
|
||||
Spanish, French, German, Italian, Portuguese and Russian; English otherwise. **F8** puts you back
|
||||
on the ground if you get stuck inside another player.
|
||||
|
||||
**Not yet tested with more than one player on Linux:** one player alone works (its items, deaths,
|
||||
lamps, the Hunter's Dream). Report what you find with the log.
|
||||
|
||||
Its log is in `user/bbsc/logs/` (in the AppImage, `~/.local/share/bbport/user/bbsc/logs/`), and
|
||||
the game's log has its warnings (`BBSC:` lines). Not in the Linux build: BBSC's cinematic camera
|
||||
(L3 + R3 opens bbport's menu) and its own Discord status (bbport has one).
|
||||
|
||||
## Summoning in the game
|
||||
|
||||
- The host rings the **Beckoning Bell** (one Insight each time); the helper rings the **Small
|
||||
Resonant Bell**.
|
||||
Resonant Bell**. With BBSC: the **Hunt Bell** and the **Kin Bell**, at no Insight cost.
|
||||
- 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
|
||||
@@ -38,26 +116,24 @@ password). *Test the connection* only checks that the servers answer, not the ac
|
||||
## 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
|
||||
Tailscale ([hosting your own server](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`.
|
||||
the log says `P2P on UDP port <port>: asking the router to forward it (UPnP)`. A router
|
||||
without UPnP needs a port forward by hand (UDP, the port below, to your computer's LAN
|
||||
address).
|
||||
2. **The port** to open by hand or in a firewall is UDP 3658, the PS4's own, which the game uses
|
||||
by default (**Co-op port (UDP)** in the Online tab: 0 keeps it). The log says
|
||||
`P2P transport on UDP 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
|
||||
firewall. With ufw or firewalld on, the launcher's **Multiplayer → Online** tab 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
|
||||
```
|
||||
Windows players open the same UDP port in their own firewall.
|
||||
|
||||
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
|
||||
@@ -69,8 +145,7 @@ Run with `BB_GPU_LOG=info` in the extra variables for the online details. In a s
|
||||
|
||||
| Line | Meaning |
|
||||
|---|---|
|
||||
| `Logged in npid='...'` | The shadNet login worked. |
|
||||
| `signed 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. |
|
||||
| `signed out by the server` | The server refuses this build (shadPS4's public one): use Seamless Hunt, The Hunter's Dream or your own server. |
|
||||
+26
-27
@@ -2,39 +2,38 @@
|
||||
|
||||
## For players
|
||||
|
||||
The same documents, with a few pages written for players, make up the
|
||||
[players' guide](https://ayozetr.github.io/bloodborne_linux/) (built from this folder: `mkdocs.yml`; published with `tools/publish_guide.sh`).
|
||||
|
||||
- [getting-started.md](getting-started.md): what you need, the first launch, saves and updates.
|
||||
- [steam-deck.md](steam-deck.md): Add to Steam, the Steam Deck's Game Mode, Steam Input and the overlay.
|
||||
- [logs.md](logs.md): getting a log, also from Game Mode, and sending it.
|
||||
- [faq.md](faq.md): the questions players ask most.
|
||||
- [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.
|
||||
- [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 for a group of friends.
|
||||
- [MODS.en.md](MODS.en.md): file mods and third-party patches.
|
||||
|
||||
## For this fork
|
||||
## For developers
|
||||
|
||||
- [ENVIRONMENT.md](ENVIRONMENT.md): every environment variable the port reads, with its default
|
||||
and where in the code it is read.
|
||||
- [DEBUGGING.md](DEBUGGING.md): catching a crash with a debugger (`BB_FAULT_WAIT`).
|
||||
- [RELEASE_TEMPLATE.md](RELEASE_TEMPLATE.md): the format of every release's notes.
|
||||
- [CREDITS.md](CREDITS.md): whose work is in the port, and its licenses.
|
||||
|
||||
## bbport's development notes
|
||||
## Design notes from upstream bbport
|
||||
|
||||
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).
|
||||
The port is built on [bbport](https://github.com/deadinside28/bloodborne_pc) by deadinside28. These
|
||||
notes explain parts of its engine that this port still runs, and the code points to them. Some
|
||||
describe plans or measurements at the date in their 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/).
|
||||
- **GPU commands in parallel:** [parallel_gpu.md](parallel_gpu.md).
|
||||
- **Upscaling, TAA and frame generation:** [upscaler.md](upscaler.md).
|
||||
- **The translator that replaces emulation:** [TRANSLATOR_ARCHITECTURE.md](TRANSLATOR_ARCHITECTURE.md).
|
||||
- **The GPU layer's memory module:** [MEMORY_MODULE_PLAN.md](MEMORY_MODULE_PLAN.md).
|
||||
|
||||
Upstream's release notes, roadmap, session logs and its other notes (most in Russian) stay in
|
||||
[its docs folder](https://github.com/deadinside28/bloodborne_pc/tree/master/docs).
|
||||
@@ -43,4 +43,5 @@ SHA-256: `<sha256sum of the AppImage>`
|
||||
|
||||
**Full changes:** https://github.com/ayozetr/bloodborne_linux/compare/<previous tag>...<tag>
|
||||
**Credits and licenses:** https://github.com/ayozetr/bloodborne_linux#credits
|
||||
Includes Lantern Engine by Awaken (https://github.com/Awakenchan/LanternEngine).
|
||||
```
|
||||
-206
@@ -1,206 +0,0 @@
|
||||
# Нативный порт Bloodborne: что сделано и что дальше
|
||||
|
||||
Основной текст — состояние на 2026-09-26; ниже первым разделом — что изменилось к 30.09.
|
||||
Самый подробный срез: [CURRENT_STATUS_2026-09-29.md](history/CURRENT_STATUS_2026-09-29.md).
|
||||
Отчёт о работе 30.09–01.10 (конвейер GPU, апскейлер, падение, лаунчер, AppImage):
|
||||
[SESSION_2026-09-30.md](history/SESSION_2026-09-30.md).
|
||||
Подробности по отдельным темам:
|
||||
[parallel_gpu.md](parallel_gpu.md) (многопоточность GPU, подтормаживания при подгрузке),
|
||||
[upscaler.md](upscaler.md) (анализ кадра игры, константы сцены).
|
||||
|
||||
## Обновление 30.09
|
||||
|
||||
- Сделано после 26.09: FSR 4 INT8 (v07), векторы движения объектов, смена пресета без
|
||||
перезапуска (уменьшенные цели сцены; глубина D32S8 копируется шейдером), LTO и PGO,
|
||||
масштабирование подготовки draw под число потоков (Steam Deck тоже).
|
||||
- Проблемы из раздела «Известные проблемы» ниже: пункт 3 (перезапуск при смене пресета)
|
||||
решён; пункт 1 (векторы объектов) реализован, у FSR 4 остаётся гостинг на персонаже при
|
||||
Ultra Performance; пункт 2 (мыльный интерфейс) — UI рисуется в 1920×1080.
|
||||
- Ограничение: уменьшенный рендер сцены (`SceneTargets::Eligible`) требует цель 1920×1080,
|
||||
то есть работает только при выводе 1080p.
|
||||
- Распараллеливание потока GPU-команд (шаг 2 [parallel_gpu.md](parallel_gpu.md)): текстуры на
|
||||
отдельном потоке параллельно с буферами работают корректно, но прироста не дают —
|
||||
данные каждого draw переезжают между ядрами. Узкое место — поток GPU-команд: 91% на CPU,
|
||||
~9% ожидания мелких копий гостевой памяти перед fence. Дальше — двухстадийный конвейер
|
||||
(второй поток владеет текстурным кешем и записью draw, копия регистров из дельт сканера)
|
||||
или мемоизация привязок текстур между кадрами; см. «What would scale» в parallel_gpu.md.
|
||||
- **Двухстадийный конвейер draw** (`vk_draw_pipe.h`): поток GPU-команд декодирует PM4 и
|
||||
выбирает пайплайн, второй поток (`bb:DrawRec`) со своей копией регистров привязывает ресурсы
|
||||
и записывает draw. +19% FPS на 16 потоках, +18% на 8 (как Steam Deck). Включён по умолчанию
|
||||
при 8+ потоках (`BB_DRAW_PIPE=0/1`). Узкое место теперь — вторая стадия; подробности и план —
|
||||
в parallel_gpu.md.
|
||||
- Пресеты и нагрузка GPU: сцена на RX 7800 XT стоит мало, поэтому пресет меняет время GPU
|
||||
лишь на ~0.5 мс, а сам FSR 4 при выводе 1080p — ~1.4 мс (FSR 4 Ultra Performance дороже
|
||||
нативного рендера без апскейлера). Исправлено: проходы освещения оставались в 1920×1080 при
|
||||
любом пресете (проверка смотрела на чужие слоты целей); уменьшенные цели теперь сэмплируются
|
||||
напрямую, без ресэмплинга в натив (кэш шейдеров пересобирается один раз). Следующее для
|
||||
Steam Deck — гостевая копия памяти целей рендеринга `3d5ebf4e` (см. parallel_gpu.md).
|
||||
- **Исправлено падение с конвейером** (порча гостевой кучи, guest offset `0x263b8e7`, через
|
||||
13–17 минут): прерывание «GPU простаивает» уходило раньше меток EOP, отложенных в поток записи
|
||||
Vulkan, и игра освобождала объекты с этими метками. Теперь перед ним конвейер и отложенные
|
||||
метки дожидаются; 2 прогона по 30 минут без падений (до исправления — 7 из 7 падений).
|
||||
- **Разрешение вывода 1440p/2160p** (меню «Разрешение вывода», bbport.ini `output_res`, после
|
||||
перезапуска): сцена рисуется в разрешении пресета от выхода, апскейлер (FSR 3 или FSR 4)
|
||||
дорисовывает кадр, интерфейс растеризуется в выходном разрешении. Эффекты игры (аберрация,
|
||||
DoF, размытие, SSAO, сглаживание игры, динамические тени, SSR, заставки, LOD) — там же.
|
||||
- **Лаунчер** (`launcher/bb-launcher.sh`, GTK4/libadwaita) и **AppImage** для Steam Deck
|
||||
(`bash packaging/appimage.sh` → `dist/`; данные в `~/.local/share/bbport`, `--play` —
|
||||
запуск без окна лаунчера).
|
||||
- Инструменты: `BB_PAD_FILE` (скриптовый ввод геймпада), `ab.sh` (A/B битов
|
||||
`BB_TOGGLE_FILE` в запущенной игре), в `Frame stats` — простой и блокировки потока GPU.
|
||||
|
||||
## Как устроен порт
|
||||
|
||||
- Оригинальный eboot (CUSA03173 v01.09) исполняется напрямую на x86-64. Системные библиотеки
|
||||
PS4 заменены своим HLE-рантаймом на C (`runtime_*.c`, `probe.c`).
|
||||
- Графика: видеоядро и рекомпилятор шейдеров shadPS4 (GPL), собранные в `libbbgpu.so`
|
||||
(`gpu/`), с собственными доработками. Эмулятор shadPS4 целиком не используется.
|
||||
- Патчи игры (FPS, разрешение и др.) — XML сообщества (`patches/Bloodborne.xml`),
|
||||
компилируются `patches.py` и накладываются загрузчиком при старте.
|
||||
|
||||
## Сделано
|
||||
|
||||
### Производительность и плавность
|
||||
|
||||
| Что | Результат |
|
||||
|---|---|
|
||||
| Запись команд Vulkan в отдельном потоке, кеши текстур/буферов, rwlock вместо мьютекса | 26 → 64 FPS в одной сцене |
|
||||
| Подготовка draw-вызовов на 4 потоках (`bb:DrawPrep`), проверка на потоке GPU | +11.5% |
|
||||
| Окно снятия защиты 256 КиБ при записи игры в память (меньше page fault) | +22% |
|
||||
| Кеш описаний текстур, быстрый путь для тех же render target | до ~94 FPS на улице |
|
||||
| Копирования гостевой памяти на пуле потоков, пул staging-буферов | фризы при подгрузке 60–170 мс → 40–50 мс |
|
||||
| Ожидание копий перед видимыми игре fence | убрано мерцание интерфейса |
|
||||
| Режим «Readbacks Relaxed» | нет «взрывов вершин» (FaceGen) |
|
||||
| Vblank 480 Гц + лимит кадров до min(монитор, 120), вывод сразу по флипу | ровная подача кадров; бег не замедляется выше 120 FPS |
|
||||
|
||||
Всё переключается во время игры через `BB_TOGGLE_FILE` (битовая маска) для A/B-сравнений.
|
||||
Статистика: `BB_FRAME_STATS=1`.
|
||||
|
||||
### Звук, ввод
|
||||
|
||||
- Вывод звука с правильным темпом (раньше щелчки каждые 10.7 мс).
|
||||
- Геймпад через SDL3, запасная раскладка клавиатуры.
|
||||
|
||||
### Анализ кадра
|
||||
|
||||
- `BB_CAPTURE_TRIGGER=<файл>` записывает один кадр: проходы, цели, шейдеры, текстуры,
|
||||
константы, режимы смешивания.
|
||||
- Разобрано устройство кадра Bloodborne (G-buffer, свет, цвет сцены RGBA16F, туман,
|
||||
пост-обработка, тонмаппинг, сглаживание игры, интерфейс) и 864-байтные константы сцены
|
||||
(матрицы вида и проекции).
|
||||
|
||||
### Временной апскейлер (FSR 3.1)
|
||||
|
||||
- **Векторы движения камеры**: из глубины сцены и матриц текущего и прошлого кадра
|
||||
(`vk_camera_motion.cpp`, `camera_motion.comp`). Проверены перепроецированием прошлого
|
||||
кадра: статичная геометрия совпадает.
|
||||
- **Jitter**: субпиксельный сдвиг геометрии сцены через viewport, последовательность
|
||||
Halton(2,3); число фаз 8·(вывод/рендер)². Интерфейс и пост-обработка не сдвигаются.
|
||||
- **FSR 3.1** (FireBurn/FSR-Vulkan, нативный Vulkan, подмодуль `gpu/third_party/fsr-vulkan`):
|
||||
- *Native AA*: на HDR-цвете сцены перед пост-обработкой, 1:1.
|
||||
- *Пресеты Quality/Balanced/Performance/Ultra Performance* (x1.5/1.7/2/3): игра рендерит в
|
||||
пониженном разрешении (патч разрешения, который `patches.py` выводит из патча 720p
|
||||
сообщества), FSR увеличивает готовый кадр до 1920x1080 перед первым проходом интерфейса.
|
||||
Интерфейс и проход вывода перенаправлены в изображения полного размера, презентер
|
||||
показывает их вместо буфера игры.
|
||||
- **Маска реактивности** (снимок сцены до прозрачных отрисовок, сравнение перед наложением
|
||||
тумана): работает, но на дымке Bloodborne даёт дрожание — по умолчанию выключена.
|
||||
- **Внутриигровое меню** (Dear ImGui, Insert или L3+R3): апскейлер, пресет, резкость,
|
||||
jitter, маска, счётчик FPS. Настройки в `bbport.ini`. Смена пресета — кнопка
|
||||
«Применить и перезапустить игру».
|
||||
|
||||
## Известные проблемы
|
||||
|
||||
Состояние последнего коммита. В рабочем дереве подготовлен перенос пресетов на уменьшенные
|
||||
цели рендера при сохранении гостевых буферов, постобработки и UI в 1920×1080. Переходы
|
||||
между пресетами и копирование цвета/глубины/stencil прошли Vulkan-тест на Lavapipe.
|
||||
RX 7800 XT не поддерживает blit D32S8, поэтому новый путь оставлял FSR вход 1920×1080
|
||||
и снижал FPS. Для таких GPU запуск теперь автоматически выбирает проверенный путь с патчем
|
||||
разрешения игры и UI 1920×1080; смена пресета снова требует перезапуска. На GPU с нужной
|
||||
поддержкой остаётся переключение без перезапуска. Пункты 2–3 ниже относятся к последнему
|
||||
коммиту, до этих изменений рабочего дерева.
|
||||
|
||||
Общий проход векторов движения объектов был слишком дорогим: Performance давал около
|
||||
49 FPS с ним и 77–78 FPS без него. Теперь дополнительная цель и шейдеры включаются только
|
||||
для G-buffer отрисовок с буфером позы/костей; остальные используют векторы камеры.
|
||||
В пробном запуске выборочный путь держал около 81–87 FPS, без пропусков из-за ёмкости
|
||||
истории. Векторы объектов включены по умолчанию; `BB_OBJECT_MOTION=0` отключает их, а
|
||||
`BB_OBJECT_MOTION_ALL=1` возвращает старый общий путь для сравнения. Выбор по размеру
|
||||
буфера пока эвристический: качество оружия и одежды в движении требует проверки в игре.
|
||||
|
||||
1. **Движущиеся объекты «рассыпаются»** (оружие на спине, персонаж, ткань, враги): есть
|
||||
только векторы движения камеры. Камера от третьего лица следует за персонажем, поэтому
|
||||
векторы камеры для него неверны, и апскейлер берёт историю не из того места. Это касается
|
||||
любого временного апскейлера (FSR 3/4, DLSS, XeSS).
|
||||
2. **Интерфейс в пресетах мыльный**: с патчем разрешения игра считает себя 540p-игрой и,
|
||||
судя по всему, готовит шрифты и элементы интерфейса под это разрешение. Меню
|
||||
(титульное, пауза) вообще не проходят через апскейлер.
|
||||
3. **Смена пресета требует перезапуска**: разрешение задаётся патчем кода при старте, а
|
||||
буферы игра выделяет один раз.
|
||||
|
||||
## План
|
||||
|
||||
### 1. Векторы движения объектов
|
||||
|
||||
Нужны всем апскейлерам и генерации кадров. Игра их не считает, поэтому считаем сами.
|
||||
|
||||
Как:
|
||||
- Для каждой отрисовки в G-buffer (проходы с 5+ целями) рекомпилятор делает второй вариант
|
||||
вершинного шейдера, который выдаёт позицию дважды: с текущими константами и с константами
|
||||
прошлого кадра. Разница проекций — вектор движения, пишется во вспомогательную цель
|
||||
RG16F как дополнительный выход пиксельного шейдера.
|
||||
- Константы прошлого кадра: для каждого draw запоминаем содержимое константных буферов
|
||||
(матрица объекта, кости скелета) по ключу «шейдер + номер draw в проходе» или по адресу
|
||||
данных. Нужно выяснить, как игра хранит матрицы скелета: в буферах, которые перезаписываются
|
||||
каждый кадр (тогда копируем прошлую версию), или в кольцевом буфере (тогда достаточно
|
||||
адреса прошлого кадра).
|
||||
- Где объект не найден (первый кадр, новый объект) — векторы камеры, как сейчас.
|
||||
- Проверка: отладочный вид векторов (`BB_DEBUG_MOTION`), перепроецирование прошлого кадра
|
||||
по полным векторам, визуально — пила на спине при беге.
|
||||
|
||||
### 2. Масштабирование на уровне рендерера (вместо патча разрешения)
|
||||
|
||||
Игра работает в 1920x1080 (или в разрешении монитора), а в пониженном разрешении рисуются
|
||||
только проходы 3D-сцены. Интерфейс и меню остаются чёткими, пресет меняется на лету.
|
||||
|
||||
Как:
|
||||
- Кеш текстур выделяет для целей сцены (G-buffer, глубина, свет, цвет сцены, эффекты) копию
|
||||
уменьшенного размера; гостевая память и описания игры не меняются.
|
||||
- Viewport и scissor проходов в эти цели масштабируются (как уже сделано для интерфейса,
|
||||
включая проходы с отключённым отсечением).
|
||||
- Шейдеры, которые считают по пиксельным координатам (`gl_FragCoord`, `texelFetch`,
|
||||
compute-шейдеры по пикселям), получают коэффициент масштаба через push-константы
|
||||
рекомпилятора. Выборки по нормированным координатам работают без изменений.
|
||||
- FSR ставится туда же, где Native AA: цвет сцены перед пост-обработкой (HDR, лучшее
|
||||
качество); пост-обработка и интерфейс идут в полном разрешении.
|
||||
- Риски: проходы, читающие цели сцены по пиксельным координатам в пост-обработке, и
|
||||
чтения из гостевой памяти этих целей (их нужно запретить или масштабировать обратно).
|
||||
|
||||
### 3. Другие апскейлеры
|
||||
|
||||
Все используют одни и те же входы (цвет, глубина, векторы движения, jitter, маски).
|
||||
|
||||
- **FSR 4 (INT8)**: в FSR-Vulkan есть экспериментальный провайдер FSR 4 v07 INT8 — добавить
|
||||
как вариант в меню и сравнить с FSR 3.1 после шага 1.
|
||||
- **DLSS**: нативный Linux Vulkan SDK (NGX). На RX 7800 XT не работает, но нужен для
|
||||
пользователей NVIDIA.
|
||||
- **XeSS / XeFG и OptiScaler**: Intel поставляет их только как Windows DLL. Варианты:
|
||||
загрузчик Windows DLL (PE) внутри процесса либо запуск всей сборки под Proton с
|
||||
OptiScaler, которому отдаём входы через интерфейс DLSS.
|
||||
Проверено 29.09: libxess.dll (одинаковая у DOOM: The Dark Ages, PRAGMATA и др.) имеет
|
||||
Vulkan-путь (`xessVKCreateContext`, `xessVKExecute`), но импортирует ~300 функций:
|
||||
MSVCP140 (iostream), VCRUNTIME140 (C++-исключения), UCRT, kernel32, setupapi, dxgi.
|
||||
Решено отложить; самый реалистичный путь — хелпер под Wine с общей памятью Vulkan.
|
||||
|
||||
### 4. Генерация кадров
|
||||
|
||||
- FSR 3.1 Frame Generation (есть в FSR-Vulkan): промежуточный кадр между двумя готовыми,
|
||||
по тем же векторам движения; интерфейс нужно рисовать поверх отдельно (есть перенаправление
|
||||
интерфейса — его можно рендерить в отдельный слой).
|
||||
- XeFG — после решения с Windows DLL (см. выше).
|
||||
- Требование: подача кадров через презентер (очередь и VRR уже работают).
|
||||
|
||||
### 5. Прочее
|
||||
|
||||
- Steam Deck: портативная сборка (patchelf, без Nix), проверка пресетов на 1280x800.
|
||||
- Дальнейшая разгрузка потока GPU (шаг 2 из [parallel_gpu.md](parallel_gpu.md)).
|
||||
+46
-20
@@ -1,14 +1,14 @@
|
||||
# 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:
|
||||
Bells and summons go through a shadNet server. shadPS4's public one (`srv.shadps4.net`) no longer
|
||||
accepts bbport builds, and the public pools for bbport are Seamless Hunt (the default) and The
|
||||
Hunter's Dream's (the launcher's **Multiplayer → Online** tab; [README](../README.md#online-play)). A group of friends
|
||||
can also run their own server:
|
||||
[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
|
||||
With your own shadNet server, messages, bloodstains and ghosts come from the **Game server** field
|
||||
of the Online tab: The Hunter's Dream by default, or BBSC's game server ([below](#seamless-co-op-bbsc)). 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
|
||||
@@ -133,7 +133,7 @@ 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:
|
||||
In **Multiplayer → Online**:
|
||||
|
||||
| Setting | Value |
|
||||
|---|---|
|
||||
@@ -144,26 +144,52 @@ In the **Online** page:
|
||||
| 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.
|
||||
Then press **Test** in the **Status** row. Every line should get a check mark, the shadNet server's UDP among them: it
|
||||
shows the address the server sees for you, a `100.` one through Tailscale. An error mark on the 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)
|
||||
## Seamless co-op (BBSC)
|
||||
|
||||
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:
|
||||
For [BBSC](ONLINE.md#seamless-co-op-bbsc) with everything on (summons across areas, 5 hunters),
|
||||
the group also runs BBSC's game server next to shadNet. It is in this repository,
|
||||
`scripts/bbsc_gameserver/bbsc_gameserver.py` (Python 3, nothing to install), and it takes the
|
||||
place of The Hunter's Dream for messages, bloodstains and the server list.
|
||||
|
||||
- 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.
|
||||
1. Turn on shadNet's seamless mode: run the shadNet container again with
|
||||
`-e SHADNET_BLOODBORNE_SEAMLESS_COOP=1` (it is kept in its `shadnet.cfg`).
|
||||
2. Build and start the game server on the same computer, with the same Tailscale address:
|
||||
|
||||
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.
|
||||
```sh
|
||||
mkdir bbsc-server && cd bbsc-server
|
||||
for f in Dockerfile entrypoint.sh; do
|
||||
curl -fsSLO "https://raw.githubusercontent.com/ayozetr/bloodborne_linux/main/tools/bbsc-server/$f"
|
||||
done
|
||||
docker build -t bbport-bbsc-server .
|
||||
docker run -d --name bbsc-server --restart unless-stopped --network host \
|
||||
-v bbsc-data:/data -e BBSC_HOST=100.x.y.z bbport-bbsc-server
|
||||
docker logs bbsc-server
|
||||
```
|
||||
|
||||
`BBSC_SHADNET` is the shadNet server's address when it is on another computer. Messages,
|
||||
bloodstains and its logs live in the `bbsc-data` volume. Without Docker:
|
||||
`python3 scripts/bbsc_gameserver/bbsc_gameserver.py --shadnet 100.x.y.z --host 100.x.y.z`.
|
||||
|
||||
| Port | Protocol | What |
|
||||
|---|---|---|
|
||||
| 20443 | TCP | The game server (messages, bloodstains, ghosts) |
|
||||
| 20444 | TCP | BBSC's relay (warps, world sync, scaling) |
|
||||
| 18671 | TCP | Its bbhost-compatible service (`BBSC_BBHOST=0`, `--no-bbhost`, turns it off) |
|
||||
| 3478 | UDP | The relay BBSC players fall back to when they cannot reach each other directly |
|
||||
|
||||
Each player then sets **Game server** to `http://100.x.y.z:20443` in the launcher's
|
||||
**Multiplayer → Online** tab, keeps the same shadNet server and account, and turns on BBSC in the **Seamless co-op (BBSC)** group.
|
||||
BBSC finds its relay at the game server's address (or `BBSC_RELAY`, the relay address in BBSC's
|
||||
settings).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -173,8 +199,8 @@ by lantern once the session has started does not work yet.
|
||||
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 test passes 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.
|
||||
See also [Online play and co-op](ONLINE.md) for the bells, passwords and log lines of a summon.
|
||||
@@ -1,134 +0,0 @@
|
||||
# Проверка TAA и входов временных апскейлеров — 2026-10-01
|
||||
|
||||
## Состояние проекта
|
||||
|
||||
bbport — специализированный Linux-рантайм для оригинального x86-64 кода Bloodborne
|
||||
CUSA03173 1.09 с переводом GPU-команд в Vulkan на основе shadPS4. Это экспериментальный
|
||||
порт: загрузчик, HLE системных вызовов, звук, ввод, сохранения, игровой интерфейс настроек
|
||||
и temporal upscaling уже реализованы. В этой рабочей копии доступны исходники, собранная
|
||||
библиотека, игровой дамп и сохранение. Git-метаданных проекта нет, поэтому историю
|
||||
изменений и чистоту рабочего дерева проверить нельзя.
|
||||
|
||||
Полное прохождение, переносимость на другие драйверы и Steam Deck этой проверкой не
|
||||
подтверждаются. В документации остаются ограничения: искусственные occlusion query,
|
||||
неполный predication, прозрачные эффекты без собственных векторов движения и гонки
|
||||
в модели FSR 4.1.1 для некоторых нестандартных размеров вывода.
|
||||
|
||||
## Видео и причины
|
||||
|
||||
Исходная запись `video_2026-10-01_21-21-02.mp4`: 94.89 с, 2560×1080, 60 FPS.
|
||||
В ней переключаются TAA, FSR 4.1.1, FSR 4 и FSR 3.1. На архитектуре, тонких контурах
|
||||
и движущемся оружии видна временная нестабильность. В меню включён игровой motion blur;
|
||||
его вклад нельзя приписывать TAA. Файл `video_fixed_taa.mp4` имеет ту же длительность,
|
||||
но другой размер кадра (2560×1082); сама эта запись не подтверждает исправление рендерера.
|
||||
Кадры и последовательности для просмотра лежат в `out/dev/temporal-review/`.
|
||||
|
||||
Выявлены ошибки в коде, независимо от субъективной оценки видео:
|
||||
|
||||
1. **FP16 depth в TAA history.** Шаг half около 1 составляет примерно 0.000488.
|
||||
При near ≈ 0.05 дальние поверхности становятся неразличимыми. Порог проверки истории
|
||||
был меньше этого шага, поэтому история могла как ошибочно отбрасываться, так и смешиваться
|
||||
с другой поверхностью. История теперь RGBA32F.
|
||||
2. **Несопоставимые камеры.** Старая проверка сравнивала глубину прошлого кадра с глубиной
|
||||
текущего. Теперь текущая точка переводится в систему предыдущей камеры, и сравнение
|
||||
выполняется с ожидаемой глубиной там. Допуск масштабируется по перспективной глубине.
|
||||
3. **Обнуление дальних векторов.** `depth >= 0.99999` считалось небом и получало нулевое
|
||||
движение. Для проекции с near ≈ 0.05, far 3000 этот порог затрагивает геометрию примерно
|
||||
от 1875 единиц. Теперь только clear depth 1 считается бесконечным фоном; фон получает
|
||||
вращательное движение камеры без переноса. Дальняя геометрия получает обычную репроекцию.
|
||||
4. **FP16 depth в object motion.** В RGBA16F терялась глубина, используемая для проверки,
|
||||
не перекрыт ли вектор более поздним draw. Буфер и формат графического pipeline теперь
|
||||
RGBA32F; проверка глубины больше не принимает другую дальнюю поверхность с широким
|
||||
фиксированным допуском. Финальные векторы для FSR по-прежнему RG16F.
|
||||
5. **Проверка смешанной глубины на краях.** TAA интерполировал глубину истории между
|
||||
разными поверхностями. Теперь четыре bilinear tap проверяются независимо, цвет собирается
|
||||
только из согласованных tap; при частичном покрытии вес истории уменьшается.
|
||||
6. **Слишком слабое накопление вдали.** Прежнее depth-adaptive снижение веса до 0.8/0.5
|
||||
заменено весом 0.98…0.85 в зависимости от движения при валидной истории (нижний
|
||||
вес достигается при 8 px/frame). Диагностический прогон с весом 0.94 показал
|
||||
остаточные колебания цвета между фазами jitter даже с принятой историей. RGB истории
|
||||
ограничивается диапазоном соседей текущего кадра.
|
||||
7. **Jitter глубины на склонах и тонких деталях.** Глубина теперь переносится в
|
||||
стабильную позицию пикселя по локальному одностороннему градиенту, с защитой от
|
||||
разрывов поверхности. Для motion/depth выбирается ближайшая поверхность в 3×3;
|
||||
цвет и диапазон clipping берутся вокруг исходного цветового пикселя. Это позволяет
|
||||
тонким контурам сохранять историю при чередовании foreground/background под jitter.
|
||||
8. **Нестабильная адресация истории при почти нулевом движении — основная причина
|
||||
обнаруженного дребезга TAA.** Раньше целая и дробная части bilinear-координаты
|
||||
вычислялись после сложения большого номера пикселя с очень маленьким motion.
|
||||
На GPU это давало несогласованные части и выбор соседнего texel. Теперь сначала
|
||||
разделяется сам motion: `base = p + ivec2(floor(motion))`,
|
||||
`fraction = motion - floor(motion)`. Регрессионная проверка на пикселе x=2010
|
||||
с движением около −0.00003 px воспроизводит ошибку старой формулы:
|
||||
цвет 0.250000 вместо ожидаемого 0.739985. Исправленная формула проходит проверку.
|
||||
|
||||
Исправления 3–4 относятся к общим входам TAA/FSR 3.1/FSR 4/FSR 4.1.1. Алгоритмы и
|
||||
ML-ассеты AMD не изменялись. Проверка ABI shader/pipeline и диагностический dump object
|
||||
motion обновлены вместе с форматом ресурса.
|
||||
|
||||
## Проверки и ограничения
|
||||
|
||||
Сборка `bbgpu`, `taa-shader-test`, `camera-motion-test` через `shell.nix`.
|
||||
На Vulkan GPU прошли 18 случаев production TAA shader: reset с NaN, unjitter, накопление,
|
||||
clipping, раскрытие поверхности, выход за кадр, NaN history, точная дальняя глубина,
|
||||
отказ другой дальней поверхности, перенос камеры, край с частично валидной историей,
|
||||
небо и граница небо/геометрия, наклонная глубина при обоих знаках jitter, тонкий контур,
|
||||
снижение persistence при движении и очень малое отрицательное движение на большой координате.
|
||||
Прошли 6 проверок production camera-motion shader:
|
||||
дальняя геометрия, вращение неба, отсутствие переноса неба, исключение jitter,
|
||||
валидный и устаревший object vector. Лог: `out/dev/temporal-review/tests.log`.
|
||||
Дополнительная проверка unjitter: `out/dev/temporal-review/taa-final-tests.log`.
|
||||
Воспроизведение регрессии старой формулы и 18 успешных проверок новой:
|
||||
`out/dev/temporal-review/regression-proof.log`.
|
||||
Финальная проверка игры: `out/dev/temporal-review/subpixel-validation.log`.
|
||||
Все 64 существующих Python-теста прошли (`out/dev/temporal-review/python-tests.log`).
|
||||
|
||||
Live smoke: копия сохранения загружена в Сне Охотника. В одном процессе проверены
|
||||
TAA → Off → FSR 3.1 → FSR 4 v07 → FSR 4.1.1 → TAA, вывод 2560×1440,
|
||||
по шесть финальных кадров на режим и движение камеры между переключениями.
|
||||
FSR работают с входом 1706×960; TAA — с нативным 2560×1440. Контексты AMD
|
||||
подтверждены логом, подмены на FSR 3.1 не было. Процесс завершился штатно;
|
||||
после компиляции pipeline выдерживал лимит 60 FPS. Логи и raw-кадры:
|
||||
`out/dev/temporal-review/subpixel-validation.log`, `out/dev/temporal-review/far-refined/`.
|
||||
Игровые motion blur и AA отключены в тестовой конфигурации.
|
||||
|
||||
При неподвижной камере на участке дальней башни (x=2010…2099, y=400…779)
|
||||
среднее абсолютное изменение RGB между шестью кадрами TAA снизилось с 1.892 до
|
||||
0.299 по шкале 0…255, примерно в 6.3 раза. После возврата к TAA — 0.279.
|
||||
В финальном прогоне FSR 3.1: 0.328, FSR 4: 0.278, FSR 4.1.1: 0.364.
|
||||
Это результат выбранной сцены, а не универсальная оценка качества: разрешение входа
|
||||
FSR отличается от TAA, а до/после здесь сравнивается именно последняя правка
|
||||
адресации истории. Данные: `out/dev/temporal-review/far-refined/stability.json`;
|
||||
предыдущий прогон: `out/dev/temporal-review/final-validation.log`.
|
||||
|
||||
Для диагностики добавлен `BB_TAA_DIAGNOSTICS=1`: альфа выхода TAA содержит вес принятой
|
||||
истории вместо 1. Его можно прочитать в `BB_DUMP_TRIGGER` raw RGBA16F. RGB не меняется,
|
||||
альфа истории по-прежнему содержит depth. Обычный режим сохраняет непрозрачный выход.
|
||||
Дополнительные режимы 2 и 3 выводят текущий цвет или ограниченную историю;
|
||||
диагностический образ истории позволяет сравнить прямое чтение с репроекцией.
|
||||
Эти сравнения выявили ошибку адресации даже при принятой истории и почти нулевом motion.
|
||||
|
||||
Исходники и `out/gpu/libbbgpu.so` обновлены. Пересобран
|
||||
`dist/Bloodborne-bbport-x86_64.AppImage` (832 MiB); пакетный `--vulkan-info`
|
||||
успешно определил RX 7800 XT через RADV. Логи: `appimage-build.log` и
|
||||
`appimage-vulkan.log` в `out/dev/temporal-review/`. Полный игровой прогон выше
|
||||
выполнен с локальной библиотекой; отдельный полный игровой прогон AppImage не выполнялся.
|
||||
|
||||
Эти тесты подтверждают конкретные исправления, но не доказывают исчезновение любого
|
||||
мерцания в игре. Движение объектов по истории вершин, прозрачности, анимированные
|
||||
материалы, выбор LOD и субпиксельная геометрия при сильном уменьшении разрешения
|
||||
по-прежнему требуют проверки в отдельных сценах.
|
||||
|
||||
Цена полной точности: две истории TAA занимают 253 MiB вместо 127 MiB в 4K;
|
||||
object motion — 127 MiB вместо 63 MiB при 4K render. Это также повышает расход
|
||||
пропускной способности памяти. Возможная следующая оптимизация — отдельный R32F depth
|
||||
при сохранении цвета и motion в FP16, с теми же тестами корректности.
|
||||
|
||||
Для регрессий:
|
||||
|
||||
```sh
|
||||
nix-shell shell.nix --run 'ninja -C out/gpu taa-shader-test camera-motion-test && out/gpu/taa-shader-test && out/gpu/camera-motion-test'
|
||||
```
|
||||
|
||||
Подход с движением для всех входных пикселей согласуется с рекомендациями
|
||||
[AMD FSR 3.1 integration](https://gpuopen.com/presentations/2024/FidelityFX_Super_Resolution_3-1_Release-Overview_and_Integration.pdf).
|
||||
@@ -1,42 +0,0 @@
|
||||
# Оригинальное GPU-копирование и переносимые метки
|
||||
|
||||
Срез 09.10.2026. Область проверки: `BB_PC_MODEL=1 BB_LAYER_MEMORY=1`; новая модель, без оценки старой модели памяти.
|
||||
|
||||
## Оригинальный copy shader
|
||||
|
||||
В режиме памяти игры на месте шейдер `fefebf9f` теперь по умолчанию транслируется в SPIR-V и исполняется на GPU. Его список копий читает сам шейдер при исполнении; CPU не заменяет его списком Vulkan-копий. `BB_COPY_SHADER_NATIVE=0` сохраняет HLE для сравнения. Эксперимент 2 по-прежнему инвертирует выбор.
|
||||
|
||||
Для больших привязок таблица BDA содержит два представления: в первой половине — актуальные адреса (зеркало или память игры), во второй — адреса памяти игры. Существующая адресация первой половины сохранена для DMA/ReadConst. Описание привязки содержит флаг записи насквозь и смещение второй таблицы.
|
||||
|
||||
Оригинальный copy shader записывает результат в актуальный GPU-адрес и в память игры. Поэтому CPU-потребители видят результат после синхронизации, а GPU-потребители не читают устаревшее зеркало. Когда обе записи адресуют память игры, они дают тот же результат. Остальные вычислительные шейдеры сохраняют свой учёт GPU-владения зеркалами. Записи насквозь не помечают всё зеркало огромной привязки как требующее обратного копирования; ранее накопленные GPU-записи при этом сохраняются в учёте.
|
||||
|
||||
Проверка диапазона не допускает переполнения `offset + size`; отвергнутый доступ не индексирует произвольную страницу таблицы. Версия shader binary cache поднята до 9. Профиль кэша теперь содержит версии binary/meta/pipeline: при их смене весь кэш перестраивается до загрузки модулей. Старые метаданные и SPIR-V с совпадающими именами индексов вариаций не смешиваются с новыми.
|
||||
|
||||
`run.sh` задаёт `BB_GPU_USER_DIR` до запуска процесса, из `BB_USER_DIR` или `$BB_DATA_DIR/user`. GPU-каталоги создаются при загрузке библиотеки, поэтому установка этого значения внутри `bbgpu_init` была слишком поздней. Это обеспечивает правильный выбор кэша для AppImage и отдельных профилей сохранений; явный `BB_GPU_USER_DIR` сохраняется.
|
||||
|
||||
## EOP/EOS и RELEASE_MEM
|
||||
|
||||
Если `VK_AMD_buffer_marker` доступно, сохраняется быстрый путь AMD. Без расширения метку пишет GPU через стандартный `vkCmdUpdateBuffer`. `BB_GPU_LABELS_PORTABLE=1` принудительно выбирает этот путь и позволяет проверить его на AMD. `BB_GPU_LABELS=0` оставляет прежний callback CPU после завершения GPU.
|
||||
|
||||
Переносимый путь завершает текущий render pass, публикует предшествующие GPU-записи для CPU, записывает старшую половину 64-битной метки, ставит зависимость и записывает младшую половину. Следующая зависимость обеспечивает видимость метки для GPU и CPU. Уведомление планировщику и обработка WAIT_REG_MEM сохраняются. Неподходящие адреса и метки с прерыванием сохраняют соответствующие запасные пути.
|
||||
|
||||
Стандартная команда разрешена вне render pass: [vkCmdUpdateBuffer](https://docs.vulkan.org/refpages/latest/refpages/source/vkCmdUpdateBuffer.html). Маски стадий и доступа соответствуют [правилам Vulkan synchronization](https://docs.vulkan.org/spec/latest/chapters/synchronization.html). Завершение проходов и барьеры имеют стоимость; принудительный переносимый путь на AMD не выбран по умолчанию.
|
||||
|
||||
При проверке также исправлены две зависимости соседних путей: update/inline data учитываются на стадии CLEAR, а диапазон index buffer учитывается после draw, чтобы последующая загрузка в зеркало ожидала чтения индексов.
|
||||
|
||||
## Проверки
|
||||
|
||||
`translation-memory-test` исполняет SPIR-V производственного backend и производственную функцию переносимых меток на Vulkan GPU. Проверяются чтение актуального источника, запись в зеркало и память игры, граница страниц, unmapped/out-of-range store, отключённая запись насквозь, 32/64-битные метки, порядок половин, видимость предшествующего GPU-результата при опросе CPU, последующий GPU-потребитель и нулевое значение. Диагностические AMD breadcrumbs отключены, чтобы тест не записывал AMD-маркеры.
|
||||
|
||||
```sh
|
||||
nix-shell shell.nix --run 'ninja -C out/gpu translation-memory-test'
|
||||
# VK_LAYER_PATH при необходимости указывает каталог установленного Khronos validation layer.
|
||||
BB_VK_VALIDATION_SYNC=1 out/gpu/translation-memory-test out/translation-shaders
|
||||
spirv-val --target-env vulkan1.3 out/translation-shaders/paged-copy.spv
|
||||
```
|
||||
|
||||
PM4 self-test измеряет положительный occlusion-интервал через несколько отправок. Большая отдельная отправка могла содержать только compute/state и давать ложный FAIL. Требования positive samples, valid bits, нулевой интервал и predication сохранены.
|
||||
|
||||
Аппаратная проверка выполнена на RX 7800 XT / RADV. Переносимый путь проверен принудительно на AMD; выполнение на настоящей NVIDIA, импорт её host memory и длительное прохождение игры требуют отдельного аппаратного прогона.
|
||||
|
||||
Локальные журналы, копии исходников перед изменениями, замеры и снимки: `out/dev/translation-fixes-20261009/`. Игровые проверки используют копии сохранений и настроек. Исходные сохранения и настройки не заменяются. Журналы и игровые данные в релиз не включены.
|
||||
@@ -0,0 +1,99 @@
|
||||
# Our own translator instead of shadPS4: structure and order of work (2026-10-10)
|
||||
|
||||
> Design notes from when this work was planned, kept for their reasoning. bbport 0.7 has one
|
||||
> memory model and its own renderer, and some switches named here (`BB_CP_SHADOW` and `BB_CP_DECODER`) no longer exist; the
|
||||
> current list is in [ENVIRONMENT.md](ENVIRONMENT.md).
|
||||
|
||||
Goal: move away from emulation and from shadPS4 code. We want our own PS4 command translator and
|
||||
our own graphics renderer that scale to other games (everything specific to one game lives in its
|
||||
profile, `gpu/games/`) and behave the same on AMD and NVIDIA. After that comes shadnet (online play).
|
||||
|
||||
## Current state
|
||||
|
||||
Borrowed from shadPS4 (`gpu/VENDOR.txt`, commit b4e7ae7 from 2026-09-24): about 116k lines.
|
||||
|
||||
| Part | Lines | What it does | Our work in it |
|
||||
|---|---|---|---|
|
||||
| `video_core/amdgpu` | 8.3k | PM4 parsing (command processor `liverpool.cpp`), registers, tiling | GPU-side labels, occlusion, predication, PM4 self-test |
|
||||
| `video_core/renderer_vulkan` | 28.6k | Vulkan renderer: pipelines, rasterizer, scheduler, presenter | draw pipeline (stages A/B), recording threads, upscalers, background compilation |
|
||||
| `video_core/buffer_cache` | 7.4k | game memory for the GPU | almost all rewritten (memory module, VRAM copies, traps) |
|
||||
| `video_core/texture_cache` | 4.8k | textures by address, detiling | time-based collector, uploads |
|
||||
| `shader_recompiler` | 38.6k | GCN → IR → SPIR-V | paged buffers (BDA), fixes |
|
||||
| `core`, `common` | 21.8k | VideoOut, GnmDriver, event queues, utilities | vblank, labels |
|
||||
|
||||
Our own code, outside shadPS4: `gpu/layer/` (memory module `BbLayer::GpuMemory`), `gpu/games/` (game
|
||||
profiles), `gpu/shim/` (overlay, settings, hooks), the runtime in `src/` (kernel, libc, files, saves).
|
||||
|
||||
## Target structure
|
||||
|
||||
Modules with narrow interfaces. None of them knows addresses in the game's code (that belongs to the
|
||||
profile), and none does the GPU's work on the CPU (that is emulation):
|
||||
|
||||
1. **memory**: game memory in place, VRAM copies as a cache, shared tracking of page protection
|
||||
(write traps for copies and textures). Exists: `layer/` + the module in `buffer_cache_layer.cpp`.
|
||||
2. **cp**: command processor. Parses PM4 into the translator's command stream (state, draws,
|
||||
dispatches, synchronization) in the order the GPU executes it. Replaces `liverpool.cpp`.
|
||||
3. **state**: context registers → pipeline keys and Vulkan dynamic state. Replaces
|
||||
`liverpool_to_vk` and the key building in `vk_pipeline_cache`.
|
||||
4. **resources**: V#/T#/S# descriptors → buffers, images, samplers (caches, detiling). Replaces
|
||||
`buffer_cache`, `texture_cache` (the buffer part is already almost ours).
|
||||
5. **render**: Vulkan. Command recording per thread, barriers, submission, presentation. Replaces
|
||||
`renderer_vulkan` (the scheduler and stages A/B are already ours).
|
||||
6. **shaders**: GCN → SPIR-V. The largest part; it is replaced last, stage by stage (decoder,
|
||||
IR, passes, code generation).
|
||||
7. **platform**: VideoOut, GnmDriver, kernel events (`core/libraries`), next to the runtime.
|
||||
8. **games**: game profiles (exists).
|
||||
|
||||
## Order
|
||||
|
||||
One module is replaced at a time. First an interface in our code with the shadPS4 code behind it
|
||||
as is, then our own implementation behind the same interface. After each step: the route (a route
|
||||
benchmark script (not in this repository), frames and snapshots), the PM4 self-test
|
||||
(`BB_PM4_SELFTEST=1`), unit tests, the game on AMD and (from tester logs) on NVIDIA.
|
||||
|
||||
| Step | What | Why in this order |
|
||||
|---|---|---|
|
||||
| 1 | memory: shared tracking of page protection; write traps under textures by default; model 0.3 and the sparse arena are removed | removes the rest of memory emulation and the dependency on game hooks |
|
||||
| 2 | cp: the translator's command stream; `MEM_SEMAPHORE`, GPU time, compute queues done by the GPU | the last emulation left is in the command processor |
|
||||
| 3 | state + resources | they build on 1 and 2; the buffer part is already ours |
|
||||
| 4 | render | the scheduler and stages are already ours; the rasterizer and pipelines remain |
|
||||
| 5 | shaders | the largest and riskiest; by then everything else is already ours |
|
||||
| 6 | shadnet | networking, once the graphics are our own |
|
||||
|
||||
Steps 1 and 2 are the plan to move away from emulation
|
||||
([`EMULATION_REMOVAL_PLAN.ru.md`](https://github.com/deadinside28/bloodborne_pc/blob/master/docs/EMULATION_REMOVAL_PLAN.ru.md),
|
||||
section dated 2026-10-10).
|
||||
|
||||
## Progress
|
||||
|
||||
### 2026-10-10: our own PM4 decoder (step 2, start)
|
||||
|
||||
- `gpu/cp/`: our own code, without shadPS4. Packet format and opcodes (`cp_pm4.h`), register file and
|
||||
decoder (`cp_decoder.{h,cpp}`). The decoder walks the buffer in stream order, applies register
|
||||
packets (`SET_*_REG`, `CLEAR_STATE`, registers written by draw packets), descends into
|
||||
nested buffers and hands each packet to a "sink" (`BbCp::Sink`) with the state already applied:
|
||||
draws and dispatches as typed calls, the rest as packets. The sink decides `COND_EXEC`. The
|
||||
decoder knows nothing about Vulkan, the game or where execution happens.
|
||||
- Unit test `tests/test_cp_decoder.cpp` (target `cp-decoder-test`): registers, partial writes,
|
||||
draws, dispatches, nested buffers, `COND_EXEC`, `CLEAR_STATE`, corrupt buffers.
|
||||
- Register addresses are checked against the shadPS4 layout at build time (`static_assert`). This
|
||||
found a bug: on CIK, `CP_STRMOUT_CNTL` is in uconfig (`0xC03F`), not in config.
|
||||
- Shadow check in the game (`BB_CP_SHADOW=1`): the new decoder parses every top-level buffer
|
||||
from the same state, and the register files are compared at the end of the buffer. On the route,
|
||||
all buffers matched (~800k buffers, ~200 million packets, 0 mismatches, 0 parse errors).
|
||||
Bloodborne does not use nested buffers (they are covered by the unit test).
|
||||
- Next: the decoder as a cursor (packet by packet, so the command processor loop can yield to
|
||||
other queues while waiting), the register file as shared state storage (its layout matches
|
||||
shadPS4 `Regs` word for word), then moving packet handling from `liverpool.cpp` into the
|
||||
translator's sink one packet type at a time, each time with the shadow check and the route.
|
||||
|
||||
### 2026-10-10: the new decoder is the real parsing path
|
||||
|
||||
- The decoder became a cursor (`BbCp::Cursor`: packet by packet, nested buffers at the
|
||||
caller's choice). The register file can live in the owner's storage (for the command processor,
|
||||
its `regs`, with the same word layout). A write observer (`RegisterObserver`, one call per
|
||||
packet) provides what draw preparation needs: the written blocks and the packet checksum.
|
||||
- `ProcessGraphics` takes packets and register writes from the cursor (`BB_CP_DECODER=0` selects
|
||||
the old walk). What each packet does is still the old code. PM4 self-test: 11 of 11. The share of
|
||||
prepared draws is the same (747k used, 19k not, versus 755k/24k), so the checksums
|
||||
match. The image is the same.
|
||||
@@ -1,92 +0,0 @@
|
||||
# Свой транслятор вместо shadPS4: устройство и порядок работ (2026-10-10)
|
||||
|
||||
Цель: уйти от эмуляции и от кода shadPS4 — собственный транслятор команд PS4 и собственный
|
||||
графический рендер, которые масштабируются на другие игры (всё, что про одну игру, — в её
|
||||
профиле, `gpu/games/`), одинаковые для AMD и NVIDIA. Потом — shadnet (сетевая игра).
|
||||
|
||||
## Что сейчас
|
||||
|
||||
Заимствовано из shadPS4 (`gpu/VENDOR.txt`, коммит b4e7ae7 от 2026-09-24) около 116 тыс. строк:
|
||||
|
||||
| Часть | Строк | Что делает | Наше в ней |
|
||||
|---|---|---|---|
|
||||
| `video_core/amdgpu` | 8,3 тыс. | разбор PM4 (командный процессор `liverpool.cpp`), регистры, тайлинг | метки видеочипом, occlusion, предикация, самотест PM4 |
|
||||
| `video_core/renderer_vulkan` | 28,6 тыс. | рендер Vulkan: конвейеры, растеризатор, планировщик, презентер | конвейер отрисовок (стадии A/B), потоки записи, апскейлеры, фоновая компиляция |
|
||||
| `video_core/buffer_cache` | 7,4 тыс. | память игры для видеочипа | почти всё переписано (модуль памяти, копии в VRAM, ловушки) |
|
||||
| `video_core/texture_cache` | 4,8 тыс. | текстуры по адресу, детайлинг | сборщик по времени, загрузки |
|
||||
| `shader_recompiler` | 38,6 тыс. | GCN → IR → SPIR-V | постраничные буферы (BDA), исправления |
|
||||
| `core`, `common` | 21,8 тыс. | VideoOut, GnmDriver, очереди событий, утилиты | vblank, метки |
|
||||
|
||||
Своё, вне кода shadPS4: `gpu/layer/` (модуль памяти `BbLayer::GpuMemory`), `gpu/games/` (профили
|
||||
игр), `gpu/shim/` (оверлей, настройки, перехваты), рантайм `src/` (ядро, libc, файлы, сохранения).
|
||||
|
||||
## Устройство, к которому идём
|
||||
|
||||
Модули с узкими интерфейсами; ни один не знает адресов кода игры (это профиль) и не делает
|
||||
работу видеочипа на процессоре (это эмуляция):
|
||||
|
||||
1. **memory** — память игры на месте, копии в VRAM как кэш, общий учёт защиты страниц (ловушки
|
||||
записи для копий и текстур). Есть: `layer/` + модуль в `buffer_cache_layer.cpp`.
|
||||
2. **cp** — командный процессор: разбор PM4 в поток команд транслятора (состояние, отрисовки,
|
||||
запуски, синхронизация) в порядке исполнения видеочипом. Заменяет `liverpool.cpp`.
|
||||
3. **state** — регистры контекста → ключи конвейеров и динамическое состояние Vulkan. Заменяет
|
||||
`liverpool_to_vk`, сборку ключей в `vk_pipeline_cache`.
|
||||
4. **resources** — описатели V#/T#/S# → буферы, образы, сэмплеры (кэши, детайлинг). Заменяет
|
||||
`buffer_cache`, `texture_cache` (буферная часть уже почти наша).
|
||||
5. **render** — Vulkan: запись команд по потокам, барьеры, отправка, вывод. Заменяет
|
||||
`renderer_vulkan` (планировщик и стадии A/B уже наши).
|
||||
6. **shaders** — GCN → SPIR-V. Самая большая часть; заменяется последней, по стадиям (декодер,
|
||||
IR, проходы, генерация).
|
||||
7. **platform** — VideoOut, GnmDriver, события ядра (`core/libraries`), рядом с рантаймом.
|
||||
8. **games** — профили игр (есть).
|
||||
|
||||
## Порядок
|
||||
|
||||
Замена по одному модулю: сначала интерфейс в нашем коде и за ним — код shadPS4 как есть, потом
|
||||
наша реализация за тем же интерфейсом. После каждого шага: маршрут (`route_bench.sh`, кадры и
|
||||
снимки), самотест PM4 (`BB_PM4_SELFTEST=1`), юнит-тесты, игра на AMD и (по логам тестеров) NVIDIA.
|
||||
|
||||
| Шаг | Что | Почему в этом порядке |
|
||||
|---|---|---|
|
||||
| 1 | memory: общий учёт защиты страниц; ловушки под текстурами по умолчанию; модель 0.3 и sparse-арена удаляются | убирает остаток эмуляции памяти и привязку к перехватам игры |
|
||||
| 2 | cp: поток команд транслятора; `MEM_SEMAPHORE`, время видеочипа, очереди compute — видеочипом | в командном процессоре осталась последняя эмуляция |
|
||||
| 3 | state + resources | опираются на 1 и 2; буферная часть уже наша |
|
||||
| 4 | render | планировщик и стадии уже наши; остаются растеризатор и конвейеры |
|
||||
| 5 | shaders | самый большой и рискованный; к этому времени остальное уже наше |
|
||||
| 6 | shadnet | сеть, когда графика своя |
|
||||
|
||||
Шаги 1 и 2 — это план ухода от эмуляции (`EMULATION_REMOVAL_PLAN.ru.md`, раздел от 2026-10-10).
|
||||
|
||||
## Ход работ
|
||||
|
||||
### 2026-10-10: свой декодер PM4 (шаг 2, начало)
|
||||
|
||||
- `gpu/cp/` — свой код, без shadPS4: формат пакетов и опкоды (`cp_pm4.h`), файл регистров и
|
||||
декодер (`cp_decoder.{h,cpp}`). Декодер идёт по буферу в порядке потока, применяет пакеты
|
||||
регистров (`SET_*_REG`, `CLEAR_STATE`, регистры, которые пишут пакеты отрисовки), спускается во
|
||||
вложенные буферы и отдаёт каждый пакет «приёмнику» (`BbCp::Sink`) с уже применённым состоянием:
|
||||
отрисовки и запуски — типизированно, остальное — пакетами. `COND_EXEC` решает приёмник. Про
|
||||
Vulkan, игру и место исполнения декодер не знает.
|
||||
- Юнит-тест `tests/test_cp_decoder.cpp` (цель `cp-decoder-test`): регистры, частичные записи,
|
||||
отрисовки, запуски, вложенные буферы, `COND_EXEC`, `CLEAR_STATE`, битые буферы.
|
||||
- Адреса регистров сверены с раскладкой shadPS4 на этапе сборки (`static_assert`): так нашлась
|
||||
ошибка — на CIK `CP_STRMOUT_CNTL` в uconfig (`0xC03F`), не в config.
|
||||
- Теневая проверка в игре (`BB_CP_SHADOW=1`): новый декодер разбирает каждый буфер верхнего уровня
|
||||
с того же состояния, в конце буфера файлы регистров сравниваются. На маршруте — все буферы
|
||||
совпали (~800 тыс. буферов, ~200 млн пакетов, 0 расхождений, 0 ошибок разбора). Вложенных
|
||||
буферов Bloodborne не использует (проверены юнит-тестом).
|
||||
- Дальше: декодер как курсор (пакет за пакетом, чтобы цикл командного процессора мог уступать
|
||||
другим очередям на ожиданиях), файл регистров — общее хранилище состояния (раскладка совпадает
|
||||
с `Regs` shadPS4 по словам), затем перенос обработки пакетов из `liverpool.cpp` в приёмник
|
||||
транслятора по одному виду пакетов, каждый раз с теневой проверкой и маршрутом.
|
||||
|
||||
### 2026-10-10: новый декодер — настоящий путь разбора
|
||||
|
||||
- Декодер стал курсором (`BbCp::Cursor`: пакет за пакетом, вложенные буферы — по желанию
|
||||
вызывающего), файл регистров может жить в хранилище владельца (у командного процессора — его
|
||||
`regs`, раскладка по словам та же), наблюдатель записей (`RegisterObserver`, один вызов на
|
||||
пакет) даёт то, что нужно подготовке отрисовок: записанные блоки и контрольную сумму пакетов.
|
||||
- `ProcessGraphics` берёт пакеты и запись регистров у курсора (`BB_CP_DECODER=0` — старый обход);
|
||||
что делает каждый пакет — пока прежний код. PM4-самопроверка — 11 из 11; доля подготовленных
|
||||
отрисовок та же (747 тыс. использовано, 19 тыс. нет, против 755/24 тыс.) — контрольные суммы
|
||||
сходятся; картинка та же.
|
||||
+75
-16
@@ -1,6 +1,6 @@
|
||||
# Troubleshooting
|
||||
|
||||
When asking for help, attach the log: in the launcher's **Log** page, **Export log…** saves it to
|
||||
When asking for help, attach the log: in the launcher's **Log** section, **Export log…** saves it to
|
||||
a file and **Copy** puts it on the clipboard.
|
||||
|
||||
## The launcher rejects the game
|
||||
@@ -12,7 +12,7 @@ The launcher checks the game folder before starting. What it says, and what to d
|
||||
| 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). |
|
||||
| This is not a retail release of Bloodborne | Alpha tests and other games do not run. See [supported releases](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. |
|
||||
|
||||
@@ -27,22 +27,22 @@ To move a save, close the game, back up both folders, and copy the `savedata` fo
|
||||
|
||||
## DLSS is "not supported by this GPU"
|
||||
|
||||
The log says why, on a line starting with `DLSS:`.
|
||||
The log says why, on a line `DLSS: unavailable (...)`; the game then uses another upscaler.
|
||||
|
||||
- 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.
|
||||
NVK (`not an NVIDIA GPU`, or `this GPU or driver does not support DLSS`).
|
||||
- `NVIDIA NGX initialization failed`: update the driver, and check that NVIDIA's CUDA/UVM module
|
||||
is loaded (`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`).
|
||||
- DLSS Frame Generation needs a GeForce RTX 40 or newer; on older cards the log says so and DLSS
|
||||
itself still works.
|
||||
- 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 picks a language's regional text by the dump's region and the system language. Choose the
|
||||
text you want in **Setup → Game text language**: *Español* for Spanish from Spain, *Español
|
||||
(Latinoamérica)* for Latin American Spanish. **Voice language** does the same for the voices.
|
||||
|
||||
## The game crashes after playing a while, on a card with little VRAM
|
||||
|
||||
@@ -51,12 +51,71 @@ On a 6 GB card the texture cache can run out of video memory: the log then repea
|
||||
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).
|
||||
|
||||
## The picture is stretched on an ultrawide screen
|
||||
|
||||
Choose the screen's shape in **Graphics → Display → Aspect ratio** (*Auto* follows the output
|
||||
resolution): 4:3, 16:10, 16:9, 21:9 or 32:9. The view gets wider (or taller) instead of stretched,
|
||||
and the HUD keeps to the screen's edges; it changes live, in the game's *Display* menu too.
|
||||
**Output resolution** *Auto* is the monitor's own; any other size written as `output_res=WxH` in
|
||||
bbport.ini works too.
|
||||
|
||||
## Fullscreen opens on the wrong monitor, or drops back to a window
|
||||
|
||||
With several monitors, choose the one to play on in **Display → Monitor**: the port's *Primary*
|
||||
is the one the system reports first, which on Wayland is not always the one KDE or GNOME show as
|
||||
primary. On Wayland a program cannot place its own window, so the compositor may open it on
|
||||
another monitor first and then move it; when that leaves it as a plain window, the port asks for
|
||||
fullscreen again during the first seconds (the log says
|
||||
`Display: the compositor left the window out of fullscreen, asked again`). If it still ends up
|
||||
windowed, add `SDL_VIDEO_DRIVER=x11` under **Advanced → Developer → Extra variables**: through
|
||||
Xwayland the window places itself.
|
||||
|
||||
## Exit to desktop
|
||||
|
||||
**Exit to desktop** is in the game's System menu and on the title screen, from bbport 0.7. The
|
||||
*Quit to Desktop* switch of earlier Linux versions is gone with it, and a third-party *Exit Game*
|
||||
patch is no longer needed. In the world it asks first and goes through the game's own Quit Game,
|
||||
so your progress is saved, and the game closes once it is back at the title.
|
||||
|
||||
## The port's menu opens by accident with L3 + R3
|
||||
|
||||
From bbport 0.7, L3 + R3 opens the port's menu only when both are held for 0.8 seconds, so pressing
|
||||
them in a fight (lock-on and camera reset) no longer opens it. **Controls → Port menu from the
|
||||
controller** can make it 2 seconds, or turn it off so that only Insert on the keyboard opens the
|
||||
menu. Closing it with L3 + R3 always works.
|
||||
|
||||
## The controller does nothing when the game is started from Steam
|
||||
|
||||
With Steam Input on, Steam hides the controller and the game gets Steam's virtual one, whose input
|
||||
Steam sends only to the window it sees focused. On a Wayland desktop Steam sees only X11 windows,
|
||||
so the game now opens its window through Xwayland when Steam made a virtual controller for it (the
|
||||
log says `Window: X11 (Xwayland), so Steam Input reaches the game`). On older releases, put
|
||||
`SDL_VIDEO_DRIVER=x11 %command% --play` in the shortcut's launch options (keep `--play` after
|
||||
`%command%`), or turn Steam Input off for the shortcut: the game reads the controller itself.
|
||||
|
||||
## The Steam overlay does not open in the game
|
||||
|
||||
Steam's overlay (Shift+Tab, its FPS counter, F12 screenshots) needs the game started from Steam,
|
||||
with **Steam overlay** on under **Add to Steam** in the launcher (on by default). The log then says
|
||||
`Steam overlay: …/ubuntu12_64/gameoverlayrenderer.so`. Without that line, the game was not started
|
||||
by Steam.
|
||||
|
||||
## The camera turns much faster on diagonals
|
||||
|
||||
With the right stick pushed all the way, the game switches to its fast turn (about twice the
|
||||
speed), and it does so on diagonals too, not only straight sideways. That is how the game turns on
|
||||
a PS4 as well, with any controller. **Controls → Even camera speed on diagonals** keeps diagonals at
|
||||
the normal speed; the fast turn stays when the stick is pushed straight sideways. With it on the
|
||||
camera can feel heavier: the game's own *Camera speed* setting makes up for it.
|
||||
|
||||
## 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).
|
||||
Press **Test** in the **Status** row of **Multiplayer → Online**. An error mark on the shadNet server's UDP line means
|
||||
the server never learns your address, and no summon can connect: check that the server is running
|
||||
and that its log lists the UDP listener, and that no firewall blocks UDP 31314 on its side
|
||||
([hosting your own server](SERVER.md)). shadPS4's public server does not accept bbport builds at all: use The
|
||||
Hunter's Dream, Seamless Hunt or your own server.
|
||||
|
||||
## Other online problems
|
||||
|
||||
See [ONLINE.md](ONLINE.md): the account, the bells, the ports and the log lines of a summon.
|
||||
See [Online play and co-op](ONLINE.md): the account, the bells, the ports and the log lines of a summon.
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
# FAQ
|
||||
|
||||
## Is this an emulator?
|
||||
|
||||
No. The game's own code runs directly on the CPU, and its graphics are translated to Vulkan. It is
|
||||
built on [bbport](https://github.com/deadinside28/bloodborne_pc) by deadinside28.
|
||||
|
||||
## Where do I get the game?
|
||||
|
||||
From your own PS4: a dump of your copy with update 1.09. No game files are included, and none can
|
||||
be shared here.
|
||||
|
||||
## Can I play with Windows players?
|
||||
|
||||
Yes. On Seamless Hunt or The Hunter's Dream, Linux and Windows players play together; for seamless
|
||||
co-op everyone needs the same BBSC version (0.0.2). [Online play](ONLINE.md).
|
||||
|
||||
## Summons take a long time
|
||||
|
||||
Some waiting is normal: the game checks for summon signs about every 35 seconds, as the PS4's
|
||||
servers had it, so a sign can take over half a minute to show up.
|
||||
|
||||
## My friend never gets summoned with the Kin Bell
|
||||
|
||||
It depends on the server:
|
||||
|
||||
- On **Seamless Hunt** (or your own server with BBSC's game server), seamless co-op works fully:
|
||||
summons across areas and up to 5 hunters.
|
||||
- On **The Hunter's Dream** there is no seamless co-op: summons are the normal game's, with the
|
||||
Beckoning Bell and the Small Resonant Bell. Both hunters must be in the **same area**, the area's
|
||||
boss must still be alive in the host's world, and your levels must be within range (or use the
|
||||
same password).
|
||||
|
||||
[Seamless co-op on each server](ONLINE.md#seamless-co-op-bbsc).
|
||||
|
||||
## Do I need to open ports on my router?
|
||||
|
||||
Usually not: with **UPnP** on (the default) the game opens its port on the router by itself, and
|
||||
with seamless co-op (BBSC) on Seamless Hunt relays players who cannot reach each other directly. A
|
||||
router without UPnP, or a firewall on your computer, needs UDP 3658 opened
|
||||
([ports](ONLINE.md#ports)). With [your own server](SERVER.md), Tailscale takes care of it.
|
||||
|
||||
## How do I use the in-game map?
|
||||
|
||||
In the launcher, **Mods → Map (Hunter's Atlas)**: turn on **In-game map** and press **Prepare**
|
||||
once. It makes the maps from your own game files (it downloads about 200 MB of tools the first
|
||||
time). In the game, choose **Map** in the quick menu (its fourth tile), press **M**, or with a
|
||||
controller open the game's menu and click the right stick (R3). The port's menu (Insert, or hold
|
||||
L3 + R3) changes the key and the controller shortcut.
|
||||
**Travel to lamps from the map** and **Items on the map** are in the same launcher group.
|
||||
|
||||
## How do I get FSR 4.1.2 (INT8, for RDNA2 and the Steam Deck)?
|
||||
|
||||
FSR 4.1.2's models are built from AMD's own files, which can't be shipped here. In
|
||||
**Graphics → Upscaler**, on **FSR 4.1.2 from AMD's driver**, press **Choose a file…** and pick
|
||||
AMD's Adrenalin 26.10.1 or newer installer (the `.exe`) or the `amdxcffx64.dll` inside it. It also
|
||||
needs AMD's FSR 4.1.x upscaler DLL (`amd_fidelityfx_upscaler_dx12.dll`, from OptiScaler or a game
|
||||
with FSR 4.1): if you built FSR 4.1.1 before, it uses that one, otherwise it asks for it. It takes
|
||||
about 4 minutes. In the game, the port's menu chooses the model (INT8 RDNA2 or INT8 Lite).
|
||||
|
||||
## How do I get a log on the Steam Deck?
|
||||
|
||||
Turn on *Advanced → Developer → Save the log and statistics to a file* once in Desktop Mode; Game
|
||||
Mode sessions then save theirs too. [Getting a log](logs.md).
|
||||
|
||||
## The game updated and my Steam shortcut stopped working
|
||||
|
||||
Press **Add to Steam** again in the launcher: it updates the shortcut.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Getting started
|
||||
|
||||
## What you need
|
||||
|
||||
- **Your own dump of Bloodborne with update 1.09**, from your PS4: any retail release (US, Europe,
|
||||
UK, Japan, Asia, and the editions with The Old Hunters). No game files are included, and other
|
||||
versions of the game's executable start and then crash. [Which releases work](COMPATIBILITY.md).
|
||||
- **Linux x86-64** and a GPU with **Vulkan 1.3**: AMD or Intel with Mesa, or NVIDIA with NVIDIA's
|
||||
driver. The Steam Deck works too.
|
||||
- About **6 GB of free memory**, 10 GB for 1440p or 4K.
|
||||
|
||||
## First launch
|
||||
|
||||
1. Download `Bloodborne-bbport-x86_64.AppImage` from the
|
||||
[latest release](https://github.com/ayozetr/bloodborne_linux/releases/latest).
|
||||
2. Make it executable (in a file manager: *Properties → Permissions → Allow executing*, or
|
||||
`chmod +x Bloodborne-bbport-x86_64.AppImage`) and run it.
|
||||
3. In the launcher, choose your **game folder**: the one with `eboot.bin` in it.
|
||||
4. Press **Play**.
|
||||
|
||||
!!! tip "The first minutes"
|
||||
The first time, the game builds its shaders as it meets them, so it can stutter for a few
|
||||
minutes. The next sessions start with them ready.
|
||||
|
||||
## Saves, settings and the DLC
|
||||
|
||||
- Saves and settings go to `~/.local/share/bbport`.
|
||||
- **Setup → Saves** exports your saves into a `.zip` and imports them back, also from shadPS4.
|
||||
- The Game of the Year edition already has *The Old Hunters*. With the base game, put its DLC
|
||||
folder in **Setup → DLC folder**.
|
||||
|
||||
## Updating
|
||||
|
||||
The launcher's **About** page says when a new release is out and installs it; it can also go back
|
||||
to an earlier one. Your saves and settings stay as they are.
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Play online](ONLINE.md), alone or in seamless co-op.
|
||||
- [Add the game to Steam](steam-deck.md), or play on the Steam Deck.
|
||||
- Something wrong? [Troubleshooting](TROUBLESHOOTING.md) and [getting a log](logs.md).
|
||||
@@ -1,67 +0,0 @@
|
||||
# Исправления — 2026-09-27
|
||||
|
||||
## ✅ Что исправлено
|
||||
|
||||
### 1. Маска реактивности не сохранялась
|
||||
- **Файл:** `gpu/shim/bbport_settings.cpp`
|
||||
- **Проблема:** Чекбокс "Показать маску (отладка)" не сохранялся в `bbport.ini`
|
||||
- **Решение:** Добавлена загрузка/сохранение параметра `debug_view`
|
||||
|
||||
### 2. 🔥 ДИНАМИЧЕСКОЕ ПЕРЕКЛЮЧЕНИЕ ПРЕСЕТОВ на RX 7800 XT
|
||||
- **Файлы:**
|
||||
- `gpu/shadps4/video_core/renderer_vulkan/vk_temporal_upscaler.cpp`
|
||||
- `gpu/shim/bbport_overlay.cpp`
|
||||
- **Проблема:** RX 7800 XT не поддерживает `blit D32S8`, поэтому требовался перезапуск
|
||||
- **Решение:**
|
||||
- Fallback: UI depth очищается вместо копирования (работает корректно)
|
||||
- Убрана блокировка `BB_LIVE_SCALING_UNSUPPORTED`
|
||||
- Обновлено меню — убрано требование перезапуска для пресетов
|
||||
|
||||
### 3. Правильная конфигурация
|
||||
- **Файл:** `bbport.ini`
|
||||
- **Критично:** `jitter=1` (БЫЛО ВЫКЛЮЧЕНО!)
|
||||
- Оптимизированы параметры резкости и маски
|
||||
|
||||
---
|
||||
|
||||
## 🎮 Что теперь работает БЕЗ перезапуска
|
||||
|
||||
- ✅ **Смена пресетов** (Native AA / Quality / Balanced / Performance / Ultra Performance)
|
||||
- ✅ Включение/выключение апскейлера
|
||||
- ✅ Резкость
|
||||
- ✅ Jitter
|
||||
- ✅ Маска реактивности
|
||||
- ✅ Отладочные режимы
|
||||
|
||||
**Перезапуск нужен ТОЛЬКО для:** векторов движения объектов (`object_motion`)
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Тестирование
|
||||
|
||||
### Сборка
|
||||
✅ Успешно
|
||||
|
||||
### Проверить в игре:
|
||||
|
||||
1. **Динамическое переключение:**
|
||||
- Открыть меню (Insert или L3+R3)
|
||||
- Сменить пресет
|
||||
- **Должно применится СРАЗУ**
|
||||
|
||||
2. **Шлейфы на оружии:**
|
||||
- С `jitter=1` должны стать НАМНОГО меньше
|
||||
- Сравнить с `object_motion=0`
|
||||
|
||||
3. **Маска реактивности:**
|
||||
- Должна отображаться (красный цвет)
|
||||
- Помечены прозрачные эффекты
|
||||
|
||||
---
|
||||
|
||||
## 📝 Изменённые файлы
|
||||
|
||||
- `gpu/shim/bbport_settings.cpp`
|
||||
- `gpu/shadps4/video_core/renderer_vulkan/vk_temporal_upscaler.cpp`
|
||||
- `gpu/shim/bbport_overlay.cpp`
|
||||
- `bbport.ini`
|
||||
@@ -1,248 +0,0 @@
|
||||
# Bloodborne на ПК: фактическое состояние порта и апскейлера
|
||||
|
||||
Срез на **29 сентября 2026 года**. Источник — файлы в `game_files`, история Git
|
||||
`native_probe` до `ac9fe90`, текущие незакоммиченные изменения, сохранённые логи
|
||||
запусков и локальные тесты. В документе «есть в коде», «запускалось» и «проверено
|
||||
по изображению» не считаются одним и тем же.
|
||||
|
||||
## Краткий итог
|
||||
|
||||
Это работающий экспериментальный **нативный Linux-порт**, а не запуск через
|
||||
полный эмулятор: исходный x86-64 `CUSA03173/eboot.bin` исполняется на CPU ПК,
|
||||
системные вызовы PS4 обрабатывает собственный HLE-рантайм, графику выводит
|
||||
Vulkan-ядро на основе shadPS4. Игра доходит до сцены с персонажем, принимает
|
||||
ввод, работает со звуком и сохранениями; это подтверждают логи запуска,
|
||||
снимки и видео в корне `native_probe`. Прохождение всей игры, все локации и
|
||||
отсутствие сбоев этими материалами **не подтверждены**.
|
||||
|
||||
**FSR 3.1 temporal upscaling реально работает по проверенному пути с разрешением
|
||||
сцены, заданным при старте**: лог `логи.md` показывает `960×540 → 1920×1080`,
|
||||
компоновку UI в 1920×1080 и запуск на RX 7800 XT. Текущий путь смены пресета
|
||||
без перезапуска остаётся проблемным на этой видеокарте: последний
|
||||
`last_run.log` показывает переключение запрошенных размеров сцены, но FSR-контекст
|
||||
`1920×1080 → 1920×1080`. По коду уменьшенные цели здесь не выбираются, когда
|
||||
`D32S8` не поддерживает blit. Следовательно, лог переключения пресетов **не
|
||||
доказывает реальное снижение разрешения 3D и выигрыш FPS**.
|
||||
|
||||
## Состав проекта и развитие
|
||||
|
||||
| Путь | Роль |
|
||||
|---|---|
|
||||
| `../CUSA03173/` | Локальные файлы игры, `eboot.bin`, ресурсы и модули PS4. |
|
||||
| `../np_base/` | Снимок ранней базы порта; рабочая разработка идёт в `native_probe`. |
|
||||
| `prepare.py`, `link_libc.py`, `link_modules.py`, `probe.c`, `runtime_*.c` | Подготовка исполняемого образа, привязка модулей, загрузчик и HLE. |
|
||||
| `gpu/` | Vulkan-видеоядро, рекомпилятор шейдеров, доработки для игры, FSR и ImGui. Заимствованное ядро shadPS4 описано в `gpu/VENDOR.txt`. |
|
||||
| `patches/Bloodborne.xml`, `patches.py` | Патчи FPS и разрешения, применяемые к гостевому коду при старте. |
|
||||
| `bbport.ini`, `run.sh`, `build.sh` | Настройки, полный запуск и сборка. |
|
||||
| `out/`, `out/dev/`, `last_run.log`, `логи.md` | Собранные бинарники и следы проверок; часть логов относится к более ранним версиям кода. |
|
||||
|
||||
Развитие по Git: 25 сентября добавлены загрузчик, HLE, графика, звук и
|
||||
оптимизации CPU/GPU; 26 сентября — анализ кадра, векторы камеры, jitter,
|
||||
FSR 3.1, пресеты и меню; 27 сентября последний коммит `ac9fe90` исправил
|
||||
переключение пресетов и отображение маски. После него в рабочем дереве есть
|
||||
незакоммиченные доработки векторов объектов, уменьшенных целей рендера,
|
||||
разделения разрешений сцены/UI, скриптов и тестов. Это **текущее рабочее
|
||||
дерево**, не готовый релиз. Старые разделы `README.md`, `docs/upscaler.md`
|
||||
и `docs/ROADMAP.md` описывают разные этапы и местами уже устарели.
|
||||
|
||||
## Что реализовано
|
||||
|
||||
### Запуск и производительность
|
||||
|
||||
- Гостевой `eboot` и часть оригинальных PRX запускаются вместе с реализациями
|
||||
памяти, потоков, файлов, времени, контроллера, аудио, сохранений и сервисов.
|
||||
Сеть работает в офлайн-режиме; PSN не реализован.
|
||||
- SDL3 обслуживает геймпад и запасное управление с клавиатуры. Звук выводится
|
||||
через host-аудиосистему; в коде есть исправление темпа `AudioOut` и флаг
|
||||
сохранения для отсутствовавших звуков.
|
||||
- Vulkan-рендерер получил отдельный поток записи команд, подготовку draw на
|
||||
четырёх потоках, кеши текстур и буферов, копирование гостевой памяти и пул
|
||||
staging-буферов. `docs/parallel_gpu.md` и `docs/ROADMAP.md` содержат
|
||||
замеры в отдельных сценах; их нельзя переносить на все конфигурации.
|
||||
- `BB_FPS=uncap` по умолчанию применяет патч с шагом симуляции по времени
|
||||
кадра; доступны `30`, `60`, `90`. Частота вывода ограничивается монитором
|
||||
и максимумом 120 FPS. Для диагностики есть `BB_FRAME_STATS=1`,
|
||||
`BB_TOGGLE_FILE` и захват структуры кадра через `BB_CAPTURE_TRIGGER`.
|
||||
|
||||
### Апскейлер
|
||||
|
||||
- В игре нет готового буфера скорости. Порт вычисляет векторы камеры из
|
||||
глубины и матриц двух кадров; для геометрии сцены делает субпиксельный
|
||||
jitter Halton. Перепроекция статичной геометрии проверялась ранее
|
||||
(`docs/upscaler.md`).
|
||||
- Интегрирован FSR 3.1 через `gpu/third_party/fsr-vulkan`. Native AA работает
|
||||
1:1 на HDR-цвете сцены перед постобработкой. Проверенный режим с пониженным
|
||||
разрешением использует патч гостевого разрешения, FSR после тонмаппинга и
|
||||
отдельную компоновку интерфейса в 1920×1080. Патч UI в текущем дереве
|
||||
оставляет его координаты нативными. Явный `BB_RENDER_RES=ШxВ` включает
|
||||
этот путь; например, лог `логи.md` подтверждает `960x540`.
|
||||
- Пресеты в коде: Native AA `1920×1080`, Quality `1280×720`, Balanced
|
||||
`1130×636`, Performance `960×540`, Ultra Performance `640×360` для вывода
|
||||
1920×1080. Меню ImGui открывается `Insert` или `L3+R3`. В `bbport.ini`
|
||||
сохраняются FSR, пресет, резкость, jitter, маска реактивности, режим
|
||||
отладки, векторы объектов и счётчик FPS. Текущий конфиг содержит
|
||||
`upscaler=fsr3`, `preset=4`, `jitter=1`, `reactive=1`,
|
||||
`object_motion=1`, `debug_view=0`.
|
||||
- Маска реактивности формируется сравнением цвета до и после прозрачных
|
||||
проходов. Она опциональна: прежние наблюдения отмечают дрожание тонкой
|
||||
дымки при её включении. Настройка по умолчанию в коде выключена, но
|
||||
в текущем `bbport.ini` включена.
|
||||
- В рабочем дереве есть экспериментальные векторы анимированных объектов:
|
||||
прошлые позиции вершин, дополнительная цель G-buffer, сопоставление
|
||||
отрисовок между кадрами и ограничение на подходящие буферы позы/костей.
|
||||
Изменение `object_motion` требует перезапуска. По
|
||||
`out/dev/motion-selective.log` история в выбранных draws часто находится,
|
||||
без пропусков по ёмкости в показанных интервалах. Это метрика draw, **не
|
||||
оценка покрытия пикселей или визуального качества**.
|
||||
|
||||
## Текущий дефект динамических пресетов
|
||||
|
||||
В `vk_scene_resolution.cpp` функция `SceneTargets::Eligible` требует
|
||||
`BlitSrc|BlitDst` у формата каждого кандидата. В `vk_rasterizer.cpp`
|
||||
уменьшенный проход отключается, если неподходящая глубина; от неё же зависит
|
||||
выбор уменьшенного входа FSR в `vk_temporal_upscaler.cpp`. Сохранённый лог
|
||||
для RX 7800 XT сообщает об отсутствии `blit` у `D32S8`. Позднее добавлен
|
||||
fallback, который **очищает глубину UI** вместо её копирования, но он не
|
||||
устраняет требование `blit` у глубины **3D-сцены**.
|
||||
|
||||
Итог на этой машине: в `last_run.log` 28 сентября перечислены переключения
|
||||
`640×360`, `1280×720`, `960×540` и других пресетов, однако созданный
|
||||
FSR-контекст остался `1920×1080 → 1920×1080 (HDR scene color)`. Сообщение
|
||||
`Scene resolution: raster ...` отражает запрошенный размер proxy-целей;
|
||||
оно не измеряет фактически использованный размер каждого draw. Заявления
|
||||
`README_FIXES.md` и подсказки меню о работающем на всех GPU динамическом
|
||||
масштабировании сильнее, чем позволяют код и последний лог. `логи.md`
|
||||
относится к более раннему запуску с автоматическим выбором стартового патча,
|
||||
который затем изменили в `run.sh`.
|
||||
|
||||
## Проверки и границы уверенности
|
||||
|
||||
| Проверка | Результат |
|
||||
|---|---|
|
||||
| Логи игрового запуска и кадры от 27–28 сентября | Загрузка в игровую сцену и вывод изображения подтверждены. Сквозной тест прохождения не проводился. |
|
||||
| `логи.md` на RX 7800 XT | Подтверждены патч сцены `960×540`, UI `1920×1080`, FSR-контекст `960×540 → 1920×1080`. |
|
||||
| `last_run.log` | Меню меняло пресеты без перезапуска, но FSR был 1:1; FPS-статистики этого запуска нет. |
|
||||
| `out/dev/motion-selective.log` | Для одной серии сцен с выборочными векторами объектов видны интервалы примерно 72–100 FPS, без `capacity skips` в приведённых сводках; это не контролируемое сравнение пресетов. |
|
||||
| `out/gpu/motion-history-test` и `out/gpu/ui-composition-test` | Повторно выполнены 29 сентября: оба `PASS`. |
|
||||
| `nix-shell shell.nix --run 'python3 -m unittest -v test_patches'` | Повторно выполнено 29 сентября: 5 тестов `OK`. |
|
||||
| `out/dev/scene-validation.log` | Vulkan-тест уменьшенных целей на Lavapipe **завершился assertion** из-за несовпадения версии заголовков Vulkan и диспетчера. Полной успешной проверки этого пути лог не даёт. |
|
||||
|
||||
Векторы объектов в игре и их эффект на шлейфы оружия и одежды визуально не
|
||||
подтверждены систематическим A/B-тестом. Ранние замеры около 49 FPS со
|
||||
всеми draws против 77–78 FPS без них и около 81–87 FPS на более узкой
|
||||
выборке относятся к разным запускам; это ориентир для дальнейшего теста,
|
||||
а не гарантированная производительность текущего конфига. Снимок
|
||||
`image_2026-09-28_00-44-14.png` показывает игровую сцену и оверлей около
|
||||
59 FPS, но не раскрывает условия замера.
|
||||
|
||||
## Что ещё не сделано / что проверять следующим
|
||||
|
||||
1. Исправить и подтвердить реальный уменьшенный 3D-рендер в динамическом
|
||||
режиме на RX 7800 XT: проверить фактический размер color/depth attachments
|
||||
и FSR-вход на каждом пресете. Если путь остаётся несовместимым с `D32S8`,
|
||||
явно выбирать стартовый патч разрешения и показывать в меню необходимость
|
||||
перезапуска.
|
||||
2. После исправления провести одинаковую игровую сцену с Native AA,
|
||||
Quality и Performance: зафиксировать вход/выход FSR, FPS, время кадра,
|
||||
чёткость UI и изображение в движении.
|
||||
3. Визуально сравнить векторы объектов включено/выключено при беге, повороте
|
||||
камеры и в бою; проверить шлейфы, покрытие истории и стоимость GPU.
|
||||
4. Разобраться с assertion в `scene-resolution-test`; остальные тесты не
|
||||
заменяют игровой Vulkan-тест. Генерации кадров, FSR 4, DLSS и XeSS в
|
||||
текущем порте нет — они перечислены в `docs/ROADMAP.md` как планы.
|
||||
|
||||
## Как воспроизвести подтверждённый путь
|
||||
|
||||
Из `game_files` на этой конфигурации:
|
||||
|
||||
```bash
|
||||
BB_RENDER_RES=960x540 BB_FRAME_STATS=1 bash native_probe/run.sh
|
||||
```
|
||||
|
||||
`BB_RENDER_RES` выбирает патч стартового разрешения сцены, а текущий
|
||||
`patches.py` сохраняет UI в 1920×1080. Смена разрешения таким способом
|
||||
требует нового запуска. Без `BB_RENDER_RES` включается экспериментальный
|
||||
динамический путь, описанный выше. Запуск пересобирает порт и использует
|
||||
локальные файлы `CUSA03173`; наличие исходников и бинарника само по себе
|
||||
не подтверждает воспроизводимость на другом ПК.
|
||||
|
||||
## Исправление 29 сентября: живая смена пресета
|
||||
|
||||
Причин, по которым смена пресета в меню не меняла картинку, было две:
|
||||
|
||||
1. `SceneTargets` копировал цели между нативным и уменьшенным размером только
|
||||
через `vkCmdBlitImage`, а RADV не даёт blit для `D32S8`. Поэтому глубина
|
||||
сцены считалась неподходящей и уменьшенный рендер не включался. Теперь для
|
||||
depth/stencil-форматов без blit копирование идёт полноэкранной отрисовкой
|
||||
(`host_shaders/depth_resample.frag`): глубина пишется через `gl_FragDepth`,
|
||||
трафарет через `VK_EXT_shader_stencil_export`.
|
||||
2. Маска реактивности (`reactive=1`) снимала цвет в 1920×1080 и пересоздавала
|
||||
FSR-контекст 1920→1920, а основной проход сразу пересоздавал его под
|
||||
уменьшенный вход. Контекст пересоздавался каждый кадр. Теперь маска
|
||||
читает уменьшенную цель сцены.
|
||||
|
||||
Проверка на RX 7800 XT (`out/dyn-res-test.log`): переключения в меню дают
|
||||
`FSR 3 context 1280x720 / 1130x636 / 960x540 / 640x360 -> 1920x1080`,
|
||||
по одному пересозданию на смену пресета, без сбоев. FPS в этой сцене около
|
||||
54–56 на всех пресетах, то есть узкое место здесь не в разрешении 3D.
|
||||
|
||||
## 30 сентября: FSR 4 INT8 и производительность
|
||||
|
||||
### FSR 4 (v07 INT8/DOT4)
|
||||
|
||||
- `vk_fsr4.cpp/.h`: провайдер FSR-Vulkan `ffxFsr4V07*` с моделью под пресет (native,
|
||||
quality, balanced, performance, ultraperf), тир 1080. Входы те же, что у FSR 3.1.
|
||||
В меню «Апскейлер → FSR 4 (INT8)», в ini `upscaler=fsr4`, env `BB_UPSCALER=fsr4`.
|
||||
Если нет ассетов или возможностей GPU, автоматически включается FSR 3.1.
|
||||
- Ассеты: `fetch_fsr4_assets.sh` скачивает набор, собранный Q2RTX из MIT-исходников
|
||||
FidelityFX SDK (`fsr4_shaders/`, в `.gitignore`, 98 SHA-256 сверены с манифестами).
|
||||
`BB_FSR4_DIR` задаёт другой каталог.
|
||||
- Устройство: включены `shaderIntegerDotProduct` и `VK_KHR_compute_shader_derivatives`
|
||||
(`Instance::IsFsr4Int8Supported`).
|
||||
- Проверено на RX 7800 XT: модели quality/ultraperf создаются, при смене пресета
|
||||
фриз ~6,5 с (компиляция). Пользователь: картинка чётче FSR 3.1, но сильный гостинг
|
||||
на персонаже и оружии. Векторы проверены отладочным видом, они корректны:
|
||||
персонаж получает векторы объекта. В SPIR-V pre-прохода глубина выбирается
|
||||
по минимуму (как у нас), векторы трактуются как «прошлая − текущая». Для A/B
|
||||
в меню: «FSR 4: авто-экспозиция», «FSR 4: обратный знак jitter».
|
||||
|
||||
### Производительность (perf, поток GPU-команд)
|
||||
|
||||
- `SceneTargets::Eligible` спрашивал у драйвера свойства формата на каждую отрисовку:
|
||||
~13–15% времени потока GPU. Теперь кеш по формату.
|
||||
- `ObjectMotion::Attach`, снимок цвета и маска реактивности вызывали
|
||||
`Scheduler::CommandBuffer()`. Это переводило запись всего остатка сабмита (почти
|
||||
весь G-buffer) на поток GPU. Теперь они идут через `Scheduler::Record`.
|
||||
Остались два прямых вызова в конце кадра (`TemporalUpscaler::Run`, презентер).
|
||||
- Итог: поток GPU ~74% вместо ~94% одного ядра. В игровых сценах 61–98 FPS против
|
||||
53–87 до исправлений (сцены не совпадают один в один, это ориентир).
|
||||
|
||||
### Профиль с DWARF-стеком (perf, поток GPU-команд, 30.09)
|
||||
|
||||
- `Motion::IndexedRange` (скан и хеш индексов для векторов объектов) занимал ~10% потока
|
||||
GPU. Добавлен `Motion::IndexRangeCache`: результат переиспользуется и полностью
|
||||
перепроверяется раз в 32 кадра. В игре ~98% диапазонов из кеша (260 записей).
|
||||
- `SceneTargets`: плоская таблица свойств форматов и память последних восьми целей;
|
||||
вместе с Attachment/Get/View/Eligible ~4% вместо ~7%.
|
||||
- После этого поток GPU загружен работой без заметных ожиданий. Поток записи Vulkan
|
||||
~40% работы и ~57% ожидания. GPU (RX 7800 XT) в среднем ~65% занятости.
|
||||
Ограничивает кадр поток GPU-команд. Главные статьи — базовые затраты видеоядра:
|
||||
FindView ~5,6%, BindTextures ~5,6%, memmove ~4,6%, SmallGuestCopy ~3,5%,
|
||||
CachedImageDescEntry ~3,2%, BindBuffers/BindVertexBuffers ~3% каждая.
|
||||
Следующий шаг — перенос этих вычислений на рабочие потоки (шаг 2 parallel_gpu.md).
|
||||
|
||||
### FSR 4: гостинг на персонаже и оружии (30.09, повторная проверка)
|
||||
|
||||
Видео `video_2026-09-30_03-43-12.mp4`: FSR 4, Ultra Performance (640×360 → 1920×1080). За
|
||||
движущимся диском пилы остаётся тёмный отпечаток на открывшемся фоне. Проверено:
|
||||
|
||||
- отладочный вид векторов (FSR 4): персонаж и оружие получают векторы объекта;
|
||||
- кеш диапазонов индексов не устаревает (0 изменений при перепроверках);
|
||||
- последовательность jitter совпадает с провайдером (Halton 2/3, ceil(8·ratio²) фаз);
|
||||
- A/B в одном запуске с выключенными оптимизациями после 29.09 (биты 4096, 8192, 1<<30):
|
||||
шлейфы одинаковые в обеих фазах, то есть это не регрессия от них. Попутно: эти
|
||||
оптимизации вместе дают 84,4 против 70,8 FPS при беге (+19%).
|
||||
|
||||
Модель v07 определяет открывшийся фон по цвету (глубина используется только для расширения
|
||||
векторов), и при входе 640×360 ей мало информации. FSR 3.1 отбрасывает такую историю по
|
||||
глубине, поэтому на Ultra Performance шлейфов у него меньше.
|
||||
@@ -1,248 +0,0 @@
|
||||
# Исправления и анализ проблем
|
||||
|
||||
## Дата: 2026-09-27
|
||||
|
||||
### Проблемы из тестирования
|
||||
|
||||
1. **Маска реактивности не отображается в отладочном режиме**
|
||||
2. **Шлейфы на оружии при движении**
|
||||
3. **Изменение настроек требует перезапуск игры**
|
||||
|
||||
---
|
||||
|
||||
## 1. Маска реактивности не сохраняется ✅ ИСПРАВЛЕНО
|
||||
|
||||
### Проблема
|
||||
Чекбокс "Показать маску (отладка)" в меню работал, но настройка `debug_view` не сохранялась в `bbport.ini`, поэтому после перезапуска маска не отображалась.
|
||||
|
||||
### Причина
|
||||
В `bbport_settings.cpp`:
|
||||
- Функция `Set()` не обрабатывала ключ `debug_view`
|
||||
- Функция `Save()` не записывала значение `debug_view` в файл
|
||||
|
||||
### Исправление
|
||||
**Файл:** `native_probe/gpu/shim/bbport_settings.cpp`
|
||||
|
||||
1. Добавлена обработка в `Set()`:
|
||||
```cpp
|
||||
} else if (key == "debug_view") {
|
||||
v.debug_view = std::clamp(i, 0, DebugViewCount - 1);
|
||||
}
|
||||
```
|
||||
|
||||
2. Добавлена запись в `Save()`:
|
||||
```cpp
|
||||
"debug_view=%d\nshow_fps=%d\n",
|
||||
// ...
|
||||
v.debug_view.load(), int(v.show_fps.load())
|
||||
```
|
||||
|
||||
### Как проверить
|
||||
1. Запустить игру
|
||||
2. Открыть меню (Insert или L3+R3)
|
||||
3. Включить "Маска реактивности" → "Показать маску (отладка)"
|
||||
4. Закрыть меню
|
||||
5. Маска должна отображаться красным цветом поверх затемнённого кадра
|
||||
|
||||
---
|
||||
|
||||
## 2. Шлейфы на оружии
|
||||
|
||||
### Анализ
|
||||
Из логов видно:
|
||||
```
|
||||
Object motion: on (4194304 vertices per frame)
|
||||
```
|
||||
|
||||
Векторы движения объектов включены и работают. Шлейфы могут быть из-за:
|
||||
|
||||
#### Возможные причины:
|
||||
|
||||
**A. Проблема FSR 3.1**
|
||||
- FSR 3.1 может неправильно обрабатывать быстро движущиеся объекты
|
||||
- Нужно сравнить с FSR 4 (когда будет реализован)
|
||||
|
||||
**B. Векторы движения объектов неточные**
|
||||
- Выборочный путь включает векторы только для отрисовок с буферами поз/костей
|
||||
- Возможно, оружие на спине использует другой тип анимации
|
||||
|
||||
**C. Маска реактивности не работает для оружия**
|
||||
- Маска помечает только прозрачные эффекты (частицы, дымка)
|
||||
- Оружие — непрозрачный объект, поэтому маска на него не влияет
|
||||
|
||||
**D. Jitter выключен**
|
||||
Из `bbport.ini`:
|
||||
```
|
||||
jitter=0
|
||||
```
|
||||
Субпиксельный сдвиг **выключен**, что ухудшает качество темпорального апскейлера!
|
||||
|
||||
### Рекомендации для тестирования
|
||||
|
||||
1. **Включить jitter:**
|
||||
```ini
|
||||
jitter=1
|
||||
```
|
||||
Jitter критически важен для FSR 3.1!
|
||||
|
||||
2. **Проверить маску реактивности:**
|
||||
- Включить `debug_view=1` и `reactive=1`
|
||||
- Посмотреть, какие области помечены как реактивные
|
||||
- Оружие НЕ должно быть в маске (оно непрозрачное)
|
||||
|
||||
3. **Попробовать увеличить reactive_scale:**
|
||||
```ini
|
||||
reactive_scale=2.00
|
||||
```
|
||||
(хотя это для прозрачности, не для оружия)
|
||||
|
||||
4. **Отключить векторы объектов для сравнения:**
|
||||
```ini
|
||||
object_motion=0
|
||||
```
|
||||
Перезапустить и сравнить — станет хуже или лучше?
|
||||
|
||||
5. **Проверить разные пресеты:**
|
||||
- Native AA (preset=0) — без масштабирования
|
||||
- Quality (preset=1) — x1.5
|
||||
- Performance (preset=3) — x2.0
|
||||
|
||||
Шлейфы должны быть меньше на более высоких разрешениях рендера.
|
||||
|
||||
### Логи для отладки
|
||||
|
||||
Включить отладку векторов движения:
|
||||
```bash
|
||||
BB_DEBUG_MOTION=1 ./run.sh
|
||||
```
|
||||
|
||||
Должны появиться опции:
|
||||
- Toggle 1<<22: перепроецирование прошлого кадра
|
||||
- Toggle 1<<23: карта ошибок
|
||||
|
||||
---
|
||||
|
||||
## 3. Перезапуск для применения настроек
|
||||
|
||||
### Текущая ситуация
|
||||
|
||||
Из логов:
|
||||
```
|
||||
GPU scene scaling: AMD Radeon RX 7800 XT (RADV NAVI32) lacks blit support for D32S8
|
||||
GPU scene scaling: using startup resolution 960x540 for this driver
|
||||
```
|
||||
|
||||
Твоя RX 7800 XT **не поддерживает blit D32S8** (копирование depth+stencil буфера), поэтому используется старый путь:
|
||||
- Разрешение задаётся патчем при запуске
|
||||
- Смена пресета требует перезапуска
|
||||
|
||||
### Новый путь (в рабочем дереве, не закоммичен)
|
||||
|
||||
Согласно ROADMAP.md, в рабочем дереве есть код для:
|
||||
- Динамического масштабирования на уровне рендерера
|
||||
- Переключения пресетов без перезапуска
|
||||
- Автоматического fallback для GPU без blit D32S8
|
||||
|
||||
Но этот код **ещё не закоммичен** — автор ждёт тестирования на реальном GPU.
|
||||
|
||||
### Что работает без перезапуска
|
||||
|
||||
Эти настройки применяются **сразу**:
|
||||
- ✅ Включение/выключение апскейлера
|
||||
- ✅ Резкость (sharpness)
|
||||
- ✅ Jitter (если не включён BB_LIVE_SCALING_UNSUPPORTED)
|
||||
- ✅ Маска реактивности и её параметры
|
||||
- ✅ Отладочные режимы
|
||||
|
||||
### Что требует перезапуск
|
||||
|
||||
На твоём GPU:
|
||||
- ❌ Смена пресета (Native AA ↔ Quality ↔ Performance)
|
||||
- ❌ Включение/выключение векторов движения объектов
|
||||
|
||||
### Обходной путь
|
||||
|
||||
Для тестирования разных пресетов без перезапуска игры:
|
||||
```bash
|
||||
# Редактировать bbport.ini
|
||||
preset=1 # Quality
|
||||
# Перезапустить только процесс (Ctrl+C, затем ./run.sh)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Дополнительные находки
|
||||
|
||||
### Конфигурация из bbport.ini
|
||||
|
||||
```ini
|
||||
upscaler=fsr3
|
||||
preset=3 # Performance (960x540 → 1920x1080, x2.0)
|
||||
sharpen=1
|
||||
sharpness=0.81 # Высокая резкость
|
||||
jitter=0 # ⚠️ ВЫКЛЮЧЕН — это плохо для FSR!
|
||||
reactive=1
|
||||
object_motion=1
|
||||
reactive_scale=1.00
|
||||
reactive_threshold=0.20
|
||||
reactive_max=0.90
|
||||
show_fps=1
|
||||
```
|
||||
|
||||
### Рекомендованные настройки для теста
|
||||
|
||||
```ini
|
||||
upscaler=fsr3
|
||||
preset=3 # Performance
|
||||
sharpen=1
|
||||
sharpness=0.50 # Немного снизить
|
||||
jitter=1 # ⚠️ ВКЛЮЧИТЬ обязательно!
|
||||
reactive=1
|
||||
object_motion=1
|
||||
reactive_scale=1.50 # Увеличить для теста
|
||||
reactive_threshold=0.15
|
||||
reactive_max=0.90
|
||||
debug_view=1 # Показывать маску
|
||||
show_fps=1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Следующие шаги
|
||||
|
||||
### Для пользователя (тестирование)
|
||||
|
||||
1. ✅ Перезапустить игру с исправлениями
|
||||
2. Включить `jitter=1` и `debug_view=1`
|
||||
3. Протестировать отображение маски
|
||||
4. Записать видео с маской и без неё
|
||||
5. Сравнить шлейфы с `object_motion=0` и `object_motion=1`
|
||||
|
||||
### Для разработчика (следующие фичи)
|
||||
|
||||
1. **Закоммитить код динамического масштабирования**
|
||||
- Уже работает в рабочем дереве
|
||||
- Нужно тестирование на RX 7800 XT
|
||||
|
||||
2. **Улучшить векторы движения объектов**
|
||||
- Проверить, почему оружие оставляет шлейфы
|
||||
- Возможно, нужен более широкий критерий для "движущихся отрисовок"
|
||||
|
||||
3. **Добавить FSR 4 INT8**
|
||||
- Уже есть в FSR-Vulkan как experimental
|
||||
- Сравнить качество с FSR 3.1
|
||||
|
||||
4. **Добавить DLSS**
|
||||
- Нативный Linux Vulkan SDK (NGX)
|
||||
- Не будет работать на AMD, но нужен для пользователей NVIDIA
|
||||
|
||||
---
|
||||
|
||||
## Коммит
|
||||
|
||||
Изменённые файлы:
|
||||
- `native_probe/gpu/shim/bbport_settings.cpp` — добавлено сохранение/загрузка `debug_view`
|
||||
|
||||
Тестирование:
|
||||
- Сборка: ✅ успешно
|
||||
- Требуется проверка в игре
|
||||
@@ -1,147 +0,0 @@
|
||||
# 🎮 Исправления для Bloodborne Native Port
|
||||
|
||||
## ✅ Что сделано
|
||||
|
||||
### 1. 🔧 Исправлена маска реактивности
|
||||
- Параметр `debug_view` теперь сохраняется в `bbport.ini`
|
||||
- Маска отображается после перезапуска
|
||||
|
||||
### 2. 🚀 ДИНАМИЧЕСКОЕ ПЕРЕКЛЮЧЕНИЕ ПРЕСЕТОВ на RX 7800 XT
|
||||
**Главное исправление!** Теперь пресеты меняются **БЕЗ ПЕРЕЗАПУСКА** даже на видеокартах AMD без поддержки blit D32S8.
|
||||
|
||||
**Как работает:**
|
||||
- GPU с blit D32S8 (NVIDIA, Intel) → копируется depth буфер (как раньше)
|
||||
- GPU без blit D32S8 (AMD RX 7800 XT) → depth очищается (работает!)
|
||||
- UI и глубина работают корректно в обоих случаях
|
||||
|
||||
### 3. ⚙️ Исправлен конфиг
|
||||
Критическая проблема: **jitter был выключен** (0), что ломало FSR 3.1!
|
||||
- `jitter=1` — включён (обязательно для темпорального апскейлера)
|
||||
- `debug_view=1` — включена маска
|
||||
- Оптимизированы параметры резкости и маски
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Что теперь работает БЕЗ перезапуска
|
||||
|
||||
- ✅ **Смена пресетов** (Native AA / Quality / Balanced / Performance / Ultra Performance)
|
||||
- ✅ Включение/выключение апскейлера
|
||||
- ✅ Резкость (sharpen/sharpness)
|
||||
- ✅ Jitter (субпиксельный сдвиг)
|
||||
- ✅ Маска реактивности (reactive) и её параметры
|
||||
- ✅ Отладочные режимы (debug_view)
|
||||
|
||||
**Перезапуск нужен ТОЛЬКО для:** векторов движения объектов (`object_motion`)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Как запустить
|
||||
|
||||
### Вариант 1: Быстрый старт
|
||||
```bash
|
||||
./start_fixed.sh
|
||||
```
|
||||
|
||||
### Вариант 2: Обычный запуск
|
||||
```bash
|
||||
bash run.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Что проверить
|
||||
|
||||
### 1. Динамическое переключение пресетов
|
||||
1. Запустить игру
|
||||
2. Открыть меню: **Insert** или **L3+R3** на геймпаде
|
||||
3. Сменить пресет (например, с Performance на Quality)
|
||||
4. Закрыть меню
|
||||
5. ✅ **Разрешение должно измениться СРАЗУ**
|
||||
6. Проверить FPS и качество
|
||||
|
||||
### 2. Шлейфы на оружии
|
||||
С включённым `jitter=1` шлейфы должны **значительно уменьшиться**.
|
||||
|
||||
**Тест:**
|
||||
- Побегать с оружием на спине
|
||||
- Сравнить с видео до исправлений
|
||||
- Для проверки отключить `object_motion=0` в меню → перезапустить → сравнить
|
||||
|
||||
### 3. Маска реактивности
|
||||
- Должна отображаться (красный цвет на затемнённом кадре)
|
||||
- Помечены прозрачные эффекты (дымка, частицы)
|
||||
- Оружие НЕ должно быть в маске (оно непрозрачное)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Производительность
|
||||
|
||||
Проверь разные пресеты:
|
||||
|
||||
| Пресет | Разрешение рендера | Масштаб | Качество | FPS |
|
||||
|--------|-------------------|---------|----------|-----|
|
||||
| Native AA | 1920×1080 | x1.0 | Максимум | ~60-70 |
|
||||
| Quality | 1280×720 | x1.5 | Высокое | ~80-90 |
|
||||
| Balanced | ~1130×635 | x1.7 | Среднее | ~90-100 |
|
||||
| **Performance** | **960×540** | **x2.0** | **Хорошее** | **~100-110** |
|
||||
| Ultra Perf | 640×360 | x3.0 | Базовое | ~120+ |
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Технические детали
|
||||
|
||||
### Fallback для UI depth на AMD
|
||||
```cpp
|
||||
// Если GPU поддерживает blit D32S8:
|
||||
if (depth_blit) {
|
||||
// Копируем depth сцены в UI depth
|
||||
cmd.blitImage(scene_depth, ui_depth, ...);
|
||||
}
|
||||
// Иначе (RX 7800 XT):
|
||||
else {
|
||||
// Просто очищаем UI depth
|
||||
cmd.clearDepthStencilImage(ui_depth, ...);
|
||||
}
|
||||
// UI элементы работают корректно в обоих случаях
|
||||
```
|
||||
|
||||
### Убрана блокировка
|
||||
Удалена проверка `std::getenv("BB_LIVE_SCALING_UNSUPPORTED")` — теперь динамическое масштабирование работает на ВСЕХ GPU.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Изменённые файлы
|
||||
|
||||
- `gpu/shim/bbport_settings.cpp` — сохранение `debug_view`
|
||||
- `gpu/shadps4/video_core/renderer_vulkan/vk_temporal_upscaler.cpp` — fallback UI depth, убрана блокировка
|
||||
- `gpu/shim/bbport_overlay.cpp` — обновлено меню
|
||||
- `bbport.ini` — правильная конфигурация
|
||||
- `CHANGELOG.md` — описание изменений
|
||||
- `start_fixed.sh` — скрипт быстрого запуска
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Известные проблемы
|
||||
|
||||
1. **Шлейфы на оружии** — может быть проблема FSR 3.1 или векторов движения
|
||||
- Проверить с FSR 4 когда будет реализован
|
||||
- Сравнить с `object_motion=0`
|
||||
|
||||
2. **Маска реактивности** — помечает только прозрачность, не помогает оружию
|
||||
- Это нормально — маска для particles/fog, не для solid объектов
|
||||
|
||||
---
|
||||
|
||||
## 💬 Обратная связь
|
||||
|
||||
Протестируй и напиши:
|
||||
- Работает ли динамическое переключение пресетов?
|
||||
- Стали ли меньше шлейфы с `jitter=1`?
|
||||
- Отображается ли маска реактивности?
|
||||
- Какой FPS на разных пресетах?
|
||||
|
||||
---
|
||||
|
||||
✅ **Сборка протестирована:** успешно
|
||||
✅ **Коммит:** `ac9fe90`
|
||||
📅 **Дата:** 2026-09-27
|
||||
@@ -1,240 +0,0 @@
|
||||
# Работа 30.09–01.10.2026: многопоточный GPU, апскейлер, стабильность, лаунчер, AppImage
|
||||
|
||||
Отчёт о большой сессии разработки нативного порта. Коммиты — от `37c8eac` до `9d0cf7f`
|
||||
(35 штук, `git log 37c8eac^..HEAD`). Технические подробности на английском — в
|
||||
[parallel_gpu.md](parallel_gpu.md) (разделы «Two-stage draw pipeline», «Second round»,
|
||||
«Third round», «Fixed: guest heap corruption»); краткая сводка — в [ROADMAP.md](ROADMAP.md).
|
||||
|
||||
Все замеры — RX 7800 XT, 16 потоков, вход в локацию после загрузки, персонаж стоит, FSR 4
|
||||
Ultra Performance, если не сказано иное. Между перезапусками FPS гуляет на ±2–5, поэтому
|
||||
решения о каждой оптимизации принимались по A/B в одном процессе (`ab.sh` и биты
|
||||
`BB_TOGGLE_FILE`).
|
||||
|
||||
## Итог в цифрах
|
||||
|
||||
| Что | Было | Стало |
|
||||
|---|---|---|
|
||||
| FPS на входе в локацию | ~86 | ~145–150 |
|
||||
| Время GPU на кадр, FSR 4 Ultra Performance | 5.11 мс | 4.82 мс |
|
||||
| Разброс времени GPU между пресетами | ~0.5 мс | ~0.9 мс |
|
||||
| Падение через 13–17 минут игры | в 7 из 7 прогонов | 0 из 2 прогонов по 30 минут |
|
||||
| Разрешение вывода | только 1080p | 1080p / 1440p / 2160p |
|
||||
| Запуск | `run.sh` из терминала | GTK4-лаунчер и AppImage |
|
||||
|
||||
## 1. Двухстадийный конвейер обработки GPU-команд
|
||||
|
||||
**Проблема.** Поток GPU-команд (`shadPS4:GpuComm`) делал всё сам: разбирал PM4, выбирал
|
||||
пайплайны, привязывал ресурсы и записывал draw. Остальные ядра простаивали.
|
||||
|
||||
**Решение** (`vk_draw_pipe.h`, `vk_rasterizer.cpp`, `liverpool.cpp`):
|
||||
- **Stage A** (`GpuComm`) разбирает PM4, ведёт регистры, выбирает пайплайн, копирует мелкие
|
||||
константы в кольцевой буфер и кладёт пакет в очередь.
|
||||
- **Stage B** (`bb:DrawRec`) держит копию регистров (по 32-словным блокам изменений), привязывает
|
||||
ресурсы и записывает draw и dispatch.
|
||||
- Между стадиями работает очередь без блокировок на 16 МБ.
|
||||
- Операции с памятью (EOP/EOS, WriteData, DmaData, сигнал flip) выполняются на stage B строго
|
||||
по порядку с draw.
|
||||
- Дренажи (stage A ждёт, пока B опустеет) остались только там, где без них нельзя.
|
||||
|
||||
Включается автоматически при 8+ потоках (`BB_DRAW_PIPE=0/1`). На 16 потоках: 110.9 против
|
||||
83.9 FPS (+32%); на 8 потоках (как Steam Deck): 83.8 против 71.0.
|
||||
|
||||
**Дальнейшие оптимизации:**
|
||||
|
||||
| Оптимизация | Тогл-бит | Эффект |
|
||||
|---|---|---|
|
||||
| Кольцо констант на stage A | 36 | +11% |
|
||||
| Memo состояния рендер-прохода (90% draw продолжают открытый проход) | 44 | +9% |
|
||||
| Memo наборов текстур по хешам T# (32768 слотов) | 45 | +5.6%, затем ещё +1.3% |
|
||||
| Конвейерные indirect-draw и indirect-dispatch | 46 | +1.4% |
|
||||
| Дешёвая проверка отложенных GPU-записей (слияние диапазонов, редкая чистка) | — | ~+4% |
|
||||
| Copy-shader HLE одним compute-диспатчем, слияние в пределах 64 КБ | 43 | GPU: 2.1 → 0.6 мс |
|
||||
| Таблицы истории движения без аллокаций | — | без изменения FPS |
|
||||
|
||||
Что выяснилось:
|
||||
- Узкое место теперь stage B: она занята ~85% времени, около 2.8 мкс на пакет, профиль плоский.
|
||||
- Stage A ждёт ~25% времени — на первом дренаже после длинной серии draw. В статистике кадров
|
||||
теперь видно ожидание по каждому месту дренажа.
|
||||
- Векторы движения объектов стоят около 10% FPS.
|
||||
- Не дали выигрыша и откачены:
|
||||
- ослабление барьера при передаче пакетов (+0.4%, риск пропущенного пробуждения);
|
||||
- memo динамического состояния: context-регистры меняются почти на каждом draw.
|
||||
- PGO-профиль пересобран под новый код.
|
||||
|
||||
## 2. Почему пресеты апскейлера мало меняли нагрузку на GPU
|
||||
|
||||
Выяснилось, что на RX 7800 XT отрисовка самой сцены стоит дёшево. Нативный 1080p без
|
||||
апскейлера — 3.67 мс GPU, а сам FSR 4 при выводе 1080p — около 1.4 мс. Поэтому FSR 4 Ultra
|
||||
Performance нагружает GPU сильнее, чем нативный рендер. При ~145 FPS упор в CPU, и GPU около
|
||||
четверти времени простаивает.
|
||||
|
||||
Найдены и исправлены две реальные проблемы:
|
||||
1. **Проходы освещения всегда рисовались в 1920×1080.** Проверка «можно ли рисовать проход в
|
||||
уменьшенном разрешении» смотрела на все восемь слотов целей, а не только на цели самого
|
||||
прохода. В лишних слотах лежали G-buffer-текстуры, которые проход читает. Тогл 47.
|
||||
2. **Лишний ресэмплинг.** Каждый проход, читавший уменьшенную цель, сначала растягивал её до
|
||||
1080p — около 25 раз за кадр, 0.4 мс GPU. Теперь рекомпилятор шейдеров помечает образы,
|
||||
которые читаются только нормализованным сэмплированием, и такие проходы читают уменьшенную
|
||||
копию напрямую. Тогл 48. Из-за изменения формата метаданных кэш шейдеров один раз
|
||||
пересобирается при первом запуске.
|
||||
|
||||
Время GPU по пресетам после исправлений (один процесс, `BB_PRESET_FILE`):
|
||||
|
||||
| Пресет | GPU, мс/кадр |
|
||||
|---|---|
|
||||
| Native AA | 5.72 |
|
||||
| Quality | 5.35 |
|
||||
| Balanced | 5.21 |
|
||||
| Performance | 4.99 |
|
||||
| Ultra Performance | 4.82 |
|
||||
|
||||
Следующий кандидат для Steam Deck — гостевая компьют-копия `3d5ebf4e`. Она копирует память
|
||||
целей рендеринга (глубина 1080p — 12 МБ) и тянет за собой выгрузки и загрузки образов. Её
|
||||
замена на копирование между образами описана в parallel_gpu.md.
|
||||
|
||||
## 3. Падение через 13–17 минут (испорченная гостевая куча)
|
||||
|
||||
**Симптом.** `Guest fault … at guest offset 0x263b8e7, address 0x5300000000`: в free-list
|
||||
гостевого аллокатора лежал испорченный указатель. Падало только с конвейером.
|
||||
|
||||
**Как искали.**
|
||||
- Стресс-тесты по 15–40 минут (`tools/soak.sh`) и бисекция тоглами конвейера.
|
||||
- Журнал записей в гостевую память `BB_WRITE_LOG=1` гонку маскировал: даже наносекунды
|
||||
задержки на записи меток её прятали.
|
||||
- Помог режим `BB_WRITE_LOG=2`: он записывает адреса меток в момент разбора команд, то есть вне
|
||||
гонки. Дамп при падении показал, что метки конца кадра лежат внутри освобождённых
|
||||
48-байтовых объектов кучи.
|
||||
|
||||
**Причина.** Stage A посылал прерывание «GPU простаивает», как только разобрал все командные
|
||||
буферы. По нему `sceGnmSubmitDone` отпускает игру, и та считает работу кадра завершённой. Но
|
||||
метки конца кадра (EOP) в этот момент ещё ждали в потоке записи Vulkan (режим
|
||||
RecorderFences). Игра освобождала объекты с этими метками, а другой её поток продолжал их
|
||||
обновлять.
|
||||
|
||||
**Исправление.** Перед этим прерыванием stage A дренирует конвейер и ждёт все отложенные
|
||||
метки. То же сделано перед WriteData/ReleaseMem вычислительных очередей и перед редкими
|
||||
неконвейерными командами графики. Результат: до исправления — 7 падений из 7 прогонов, после —
|
||||
2 прогона по 30 минут без падений, FPS не изменился.
|
||||
|
||||
## 4. Разрешение вывода 1440p/2160p и эффекты игры
|
||||
|
||||
Новый раздел в меню игры (Insert или L3+R3) — «Разрешение и эффекты (после перезапуска)».
|
||||
Кнопка «Применить и перезапустить игру» перезапускает игру через run.sh.
|
||||
|
||||
**Разрешение вывода** (`bbport.ini`: `output_res=2560x1440` или `3840x2160`):
|
||||
- Игра патчем рисует сцену в разрешении пресета от выхода. Например, 4K Performance — это
|
||||
1916×1078: ровно 1920×1080 нельзя, такой размер порт принимает за пространство UI.
|
||||
- FSR 3 или FSR 4 дорисовывает кадр до выходного размера. FSR 4 добавлен в этот режим: его
|
||||
HDR-выход копируется в образ UI.
|
||||
- Интерфейс остаётся в координатах 1920×1080 игры, а порт растеризует его сразу в выходном
|
||||
разрешении, поэтому он чёткий. Первая попытка патчить UI до размера вывода растягивала его в
|
||||
1.5 раза — исправлено.
|
||||
- Для сцены больше 1080p добавляется патч кучи сообщества и увеличивается бюджет direct memory
|
||||
(`BB_DMEM_MB`, по умолчанию 5056 МБ, run.sh поднимает до 9152).
|
||||
- Проверено:
|
||||
- 1440p FSR 3 и FSR 4 Quality — около 150 FPS;
|
||||
- 4K FSR 4 Performance — около 230 FPS.
|
||||
|
||||
HUD в обоих случаях на месте.
|
||||
- Для FSR 4 при выводе 1440p и 2160p нужны модели уровня «2160». `fetch_fsr4_assets.sh`
|
||||
теперь скачивает их по умолчанию.
|
||||
|
||||
**Эффекты.** Включаются и выключаются патчами сообщества из `patches/Bloodborne.xml`:
|
||||
- хроматическая аберрация;
|
||||
- глубина резкости (DoF);
|
||||
- размытие в движении;
|
||||
- SSAO;
|
||||
- собственное сглаживание игры;
|
||||
- тени от динамических источников;
|
||||
- отражения SSR (по умолчанию выключены);
|
||||
- пропуск заставок;
|
||||
- детализация моделей (LOD -2…2).
|
||||
|
||||
Ключи в bbport.ini: `effect_*`, `skip_intro`, `model_lod`.
|
||||
|
||||
## 5. Лаунчер (GTK4 / libadwaita)
|
||||
|
||||
Запуск: `launcher/bb-launcher.sh` (при отсутствии PyGObject/GTK4 поднимается через
|
||||
`nix-shell launcher/shell.nix`).
|
||||
|
||||
Что в нём есть:
|
||||
- **Игра:** путь к папке игры (проверяется наличие `eboot.bin`), папка сохранений, язык системы.
|
||||
- **Экран:** разрешение вывода, полный экран, режим показа кадров, HDR.
|
||||
- **Апскейлер:** выбор, пресет, резкость, векторы движения объектов, счётчик FPS — это те же
|
||||
ключи bbport.ini, что и в меню игры.
|
||||
- **Эффекты игры** и детализация моделей.
|
||||
- **Частота кадров:** патч (uncap/60/90/30) и ограничение FPS.
|
||||
- **Производительность:** конвейер GPU (авто/вкл/выкл), режим readbacks.
|
||||
- **Для разработчика:** MangoHud, статистика кадров, профиль GPU, валидация Vulkan,
|
||||
произвольные переменные окружения.
|
||||
- Вкладка «Журнал» с выводом игры, кнопки «Запустить» и «Остановить» (игра и её помощники
|
||||
останавливаются как одна группа процессов).
|
||||
- `--play` запускает игру с сохранёнными настройками без окна — для игрового режима Steam Deck.
|
||||
|
||||
Настройки самого лаунчера хранятся в `~/.config/bbport-launcher/settings.json`.
|
||||
|
||||
## 6. AppImage для Steam Deck
|
||||
|
||||
Сборка: `bash build.sh && bash packaging/appimage.sh`. Результат —
|
||||
`dist/Bloodborne-bbport-x86_64.AppImage`, около 700 МБ.
|
||||
|
||||
Как устроено:
|
||||
- `packaging/default.nix` собирает пакет: готовые бинарники, скрипты подготовки, патчи, модели
|
||||
FSR 4, лаунчер с Python, GTK4, libadwaita, темами значков и Vulkan-драйверами Mesa.
|
||||
- Бандлер nix-appimage монтирует внутренний `/nix/store` через user namespaces — они есть в
|
||||
SteamOS. Поэтому библиотеки SteamOS не конфликтуют с glibc пакета.
|
||||
- Драйвер хоста в процесс игры не загрузить, поэтому используется radv из пакета
|
||||
(`VK_DRIVER_FILES`).
|
||||
- Данные хранятся в `~/.local/share/bbport`: сгенерированные файлы, `user/` (сохранения и кэш
|
||||
шейдеров) и `bbport.ini`. Сохранения отдельные от `native_probe/user`, их нужно скопировать.
|
||||
- run.sh получил режимы `BB_PREBUILT=1` (готовые бинарники, без сборки и nix-shell),
|
||||
`BB_DATA_DIR` (каталог данных) и `BB_GAME_DIR` (папка игры).
|
||||
|
||||
Ошибки, найденные при первом запуске на живой системе:
|
||||
1. «no version information available / undefined symbol: , version». patchelf в фазе fixup nix
|
||||
испортил версии символов не-PIE бинарника игры. Теперь бинарник ищет свою библиотеку через
|
||||
`$ORIGIN/gpu`, а patchelf к нему не применяется.
|
||||
2. «libSDL3.so.0: cannot open». Nix находит зависимости только среди входов деривации, а
|
||||
бинарники собраны снаружи. Теперь `appimage.sh` выписывает store-пути из RUNPATH в
|
||||
`packaging/runtime-paths.nix`, и пакет берёт их как входы.
|
||||
3. «FSR 4 недоступен: missing …_2160_pre.spv»: не хватало моделей уровня «2160» (см. раздел 4).
|
||||
|
||||
После этих исправлений игра из AppImage запускается (`--play`), FSR 4 при выводе 4K работает.
|
||||
|
||||
## 7. Инструменты для разработки
|
||||
|
||||
| Инструмент | Назначение |
|
||||
|---|---|
|
||||
| `tools/restart.sh` | перезапуск игры со статистикой и вход в локацию (крест, крест) |
|
||||
| `tools/press.sh` | ввод геймпада из скрипта через `BB_PAD_FILE` |
|
||||
| `tools/soak.sh <сек>` | стресс-тест с поворотами камеры, сообщает о падении |
|
||||
| `ab.sh` | A/B бита `BB_TOGGLE_FILE` в запущенной игре |
|
||||
| `BB_GPU_PROFILE=1` | время GPU по проходам, диспатчам, апскейлеру, загрузкам и выгрузкам образов, ресэмплингу прокси |
|
||||
| `BB_FRAME_STATS=1` | статистика кадров: ожидание stage A по причинам и местам дренажа, memo, чтение прокси |
|
||||
| `BB_SCENE_DEBUG=<файл>` | после создания файла печатает, почему проходы кадра остались в полном разрешении |
|
||||
| `BB_PRESET_FILE=<файл>` | смена пресета из файла (раз в секунду) |
|
||||
| `BB_WRITE_LOG=1/2` | журнал записей эмуляции GPU в гостевую память и дамп при гостевом падении |
|
||||
| `BB_DMEM_MB` | бюджет direct memory |
|
||||
|
||||
Биты `BB_TOGGLE_FILE` (выключают оптимизацию при установке бита):
|
||||
|
||||
| Бит | Что выключает | Бит | Что выключает |
|
||||
|---|---|---|---|
|
||||
| 36 | кольцо констант | 43 | multi-copy шейдер |
|
||||
| 37 | конвейер целиком | 44 | memo состояния прохода |
|
||||
| 38 | задачи в конвейере | 45 | memo наборов текстур |
|
||||
| 39 | ожидание отложенных меток | 46 | конвейерные indirect |
|
||||
| 40 | конвейерные dispatch | 47 | только свои слоты целей |
|
||||
| 41 | метки через поток записи | 48 | чтение уменьшенных копий |
|
||||
| 42 | записи в память на stage B | 49 | порядок гостевых записей |
|
||||
|
||||
## 8. Что дальше
|
||||
|
||||
- **Stage B — узкое место CPU.** Дальнейший рост требует разделить её работу: кэши текстур и
|
||||
буферов против подготовки дескрипторов и записи.
|
||||
- **Steam Deck (GPU):** HLE компьют-копии `3d5ebf4e` как копирования между образами;
|
||||
проходы в половинном разрешении (960×540) тоже уменьшать.
|
||||
- **Масштабируемый режим (1440p/2160p):** пресет меняется только с перезапуском; объединить его
|
||||
с живым режимом уменьшенных целей.
|
||||
- Проверить AppImage на самом Steam Deck: radv из пакета на Van Gogh, звук через PipeWire хоста,
|
||||
игровой режим с `--play`.
|
||||
@@ -1,10 +0,0 @@
|
||||
Первая версия векторов движения объектов собрана, игра запущена, ошибок при старте нет. Но в самой игре я её ещё не проверял: шейдеры G-buffer с новым кодом компилируются только при загрузке уровня, так что главная проверка впереди.
|
||||
|
||||
Как это работает:
|
||||
- Вершинный шейдер каждой отрисовки в G-buffer сохраняет итоговые позиции вершин и читает позиции тех же вершин из прошлого кадра.
|
||||
- Пиксельный шейдер пишет разницу в дополнительную цель G-buffer.
|
||||
- Где объект двигался, FSR получает этот вектор, в остальных местах — вектор камеры.
|
||||
- Позиции сохраняются только для движущихся отрисовок: тех, у которых изменились константы вершинного шейдера (матрица объекта, кости). Статичная геометрия памяти не тратит.
|
||||
- Это учитывает любую анимацию, которую считает вершинный шейдер: скиннинг, оружие, ткань, врагов.
|
||||
|
||||
Загрузись, пожалуйста, в игру и напиши «готов». Я проверю лог: сколько отрисовок движется и у скольких нашлись позиции прошлого кадра. Потом побегай с пилой на спине. Для сравнения я смогу выключать векторы объектов на ходу, остаются только векторы камеры.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
template: home.html
|
||||
title: Home
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
---
|
||||
|
||||
# Where to begin
|
||||
|
||||
The PlayStation 4 version of *Bloodborne*, running natively on Linux and the Steam Deck: the
|
||||
game's own code runs on the CPU and its graphics are translated to Vulkan. You need your own dump
|
||||
of the game with update 1.09; no game files are included.
|
||||
|
||||
<div class="hd-cards">
|
||||
<a class="hd-card" href="getting-started/"><strong>Getting started</strong><span>Your dump, the AppImage and the first launch.</span></a>
|
||||
<a class="hd-card" href="ONLINE/"><strong>Playing online</strong><span>Seamless Hunt, The Hunter's Dream or your own server, and seamless co-op.</span></a>
|
||||
<a class="hd-card" href="steam-deck/"><strong>Steam and the Steam Deck</strong><span>Add to Steam, Game Mode, controllers and the Steam overlay.</span></a>
|
||||
<a class="hd-card" href="TROUBLESHOOTING/"><strong>When something breaks</strong><span>Common problems, and how to send a log.</span></a>
|
||||
</div>
|
||||
|
||||
## What you get
|
||||
|
||||
- **Online and co-op**: messages, bloodstains, bells and summons, and **seamless co-op** that
|
||||
lasts through deaths, lamps and areas, with Linux and Windows players together.
|
||||
- **Local co-op**: 2 to 4 players on one PC, each with their own controller.
|
||||
- **Hunter's Atlas**: a fullscreen in-game map with fast travel to the lamps you have found.
|
||||
- **Frame rate unlocked** or capped at 30, 60 or 90, from **720p to 4K**, and ultrawide screens.
|
||||
- **FSR 3.1, FSR 4 and DLSS** upscaling.
|
||||
- **Any controller**, with its own button prompts in the game, and **keyboard and mouse**.
|
||||
- **Trophies**, **Discord** status and **Add to Steam** in one click.
|
||||
- An **AppImage** for any distribution and the Steam Deck, which **updates itself**.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Getting a log
|
||||
|
||||
A log is the first thing needed to fix a problem: it says what the game and the GPU were doing.
|
||||
|
||||
## From the launcher
|
||||
|
||||
1. Turn on *Advanced → Developer → Save the log and statistics to a file*. It stays on.
|
||||
2. Play until the problem happens, then close the game.
|
||||
3. The log is in `~/.local/share/bbport/logs`, named after the time the game started
|
||||
(`20261026_183438.log`). The launcher's **Log** page shows the last one too.
|
||||
|
||||
This also works when the game is started from Steam or from the Steam Deck's Game Mode: turn the
|
||||
option on once in the launcher, and every session saves its log.
|
||||
|
||||
The log's first line says which version ran, and for a test build which one
|
||||
(`bbport Linux 0.70-linux.1, build 74b1a308 HEAD 2026-11-03 (bbport 0.7)`).
|
||||
|
||||
## Sending it
|
||||
|
||||
Open an [issue](https://github.com/ayozetr/bloodborne_linux/issues/new/choose) and attach the
|
||||
`.log` file. It helps a lot to say:
|
||||
|
||||
- what happened, and roughly when (an area, a boss, a menu, after a lamp travel);
|
||||
- your distribution, GPU and driver;
|
||||
- the upscaler, preset and output resolution;
|
||||
- for online problems, which server and whether seamless co-op (BBSC) was on, and the other
|
||||
player's log too if you can.
|
||||
|
||||
!!! note "Crashes"
|
||||
*Advanced → Developer → Crash diagnostics* adds checks that point at what went wrong in some
|
||||
crashes. The game runs a bit slower with it on: turn it on when asked in an issue.
|
||||
@@ -1,131 +0,0 @@
|
||||
# Векторы движения: доработка после видео 2026-09-26
|
||||
|
||||
Исходный материал: `video_2026-09-26_04-19-53.mp4`, FSR 3.1 Performance,
|
||||
рендер 960×540 → вывод 1920×1080, и `out/dev/t71.log`.
|
||||
Это описание текущих изменений рабочего дерева, а не подтверждение качества в игре.
|
||||
|
||||
Общий проход для всех G-buffer draws оказался слишком дорогим: Performance работал примерно
|
||||
при 49 FPS с ним и 77–78 FPS без него. Теперь векторы объектов включены по умолчанию только
|
||||
для шейдеров с буфером позы/костей (размер 640–16384 байт, кроме общего буфера сцены
|
||||
864 байта; шаг 16 байт). В пробном запуске с более узким порогом 1024 байта
|
||||
это сократило число обработанных draws примерно до 140 за кадр, устранило переполнение
|
||||
истории и дало около 81–87 FPS. Остальные draws сохраняют дешёвые векторы камеры.
|
||||
`BB_OBJECT_MOTION=0` выключает всю функцию, `BB_OBJECT_MOTION_ALL=1` возвращает общий
|
||||
проход для A/B. Альфа-канал вспомогательной цели хранит глубину: если поздний обычный
|
||||
draw перекрыл объект, его вектор не используется. Качество персонажа и оружия в движении
|
||||
после выборки ещё нужно проверить визуально.
|
||||
|
||||
## Что обнаружено
|
||||
|
||||
- В конце исходного лога за 600 кадров: 257400 G-buffer draws, 256200 объявлены
|
||||
движущимися, только 30000 получили предыдущие позиции (~11,7% всех draws).
|
||||
Это покрытие отрисовок, не процент пикселей. Причина каждого пропуска старым
|
||||
счётчиком не различалась.
|
||||
- Под историю резервировался размер целого vertex buffer, умноженный на число
|
||||
экземпляров **вместе с firstInstance**. Для подмешей общих буферов это лишние
|
||||
позиции; бюджет составлял 2097152 позиции на кадр.
|
||||
- История не имела отдельного барьера между записями/чтениями vertex shaders;
|
||||
переиспользование кольца host-параметров не ожидало GPU.
|
||||
- Выключенные записи всех вершин направлялись в один scratch-элемент.
|
||||
- `CameraMotion::OnDisplayPass` сбрасывал Depth() перед расчётом jitter phases:
|
||||
Performance получал 8 фаз вместо 32. В восстановлении позиции из глубины
|
||||
также не учитывался сдвиг viewport.
|
||||
|
||||
## Изменения
|
||||
|
||||
- Диапазон истории определяется min/max фактических индексов с учётом baseVertex
|
||||
и primitive restart. FirstInstance вычитается в шейдере, а не расходует память.
|
||||
- Сохраняются все подходящие прямые G-buffer draws, начиная с первого появления.
|
||||
Эвристика изменения констант удалена: она считала движущимися почти все draws
|
||||
и пропускала начало движения. Чтение возможно только при совпадении с прошлым кадром.
|
||||
- Ключ включает все описания vertex streams, шейдер, адрес и хеш содержимого
|
||||
индексов, диапазон вершин, экземпляры и номер среди одинаковых draws.
|
||||
Смена топологии или пропуск кадра сбрасывает соответствующую историю.
|
||||
- Бюджет — 4194304 позиции/кадр; два массива вместе занимают 128 МиБ вместо 64 МиБ.
|
||||
При нехватке памяти остаются векторы камеры. Лог отдельно показывает сохранения,
|
||||
совпадения, отсутствие истории, нехватку памяти и недопустимые диапазоны.
|
||||
- Добавлены vertex-to-vertex barriers, ожидание GPU перед повторным использованием
|
||||
host-параметров, coherent host memory и независимый учёт layout изображения движения.
|
||||
Повторное чтение изображения не стирает признак наличия векторов.
|
||||
- Выключенные обращения обходятся ветвлением. Для повторных invocation одной
|
||||
индексированной вершины используются атомарные записи компонентов позиции.
|
||||
Позиции за камерой и нечисловые/бесконечные векторы не используются.
|
||||
- Performance получает 32 фазы jitter после создания FSR context. Векторы камеры
|
||||
восстанавливаются из unjittered координат; векторы объектов уже получались
|
||||
из clip positions до сдвига viewport.
|
||||
|
||||
## Проверки
|
||||
|
||||
Сборка и CPU-тесты:
|
||||
|
||||
```bash
|
||||
cd native_probe
|
||||
nix-shell shell.nix --run 'bash build.sh && ninja -C out/gpu motion-history-test motion-shader-test && out/gpu/motion-history-test && out/gpu/motion-shader-test out/motion-shaders'
|
||||
```
|
||||
|
||||
`test_motion_history.cpp` проверяет диапазоны u16/u32, restart, отрицательный baseVertex,
|
||||
firstInstance, повторные draws, смену топологии, пропущенный кадр, исчерпание бюджета,
|
||||
переполнение и число фаз. Также пройден запуск с AddressSanitizer/UBSan.
|
||||
|
||||
`test_motion_shaders.cpp` использует настоящий SPIR-V backend и создаёт VS/PS с
|
||||
векторами и без них. Все четыре результата прошли `spirv-val --target-env vulkan1.3`.
|
||||
Для повторения нужен `spirv-val` из SPIRV-Tools:
|
||||
|
||||
```bash
|
||||
for shader in out/motion-shaders/*.spv; do
|
||||
spirv-val --target-env vulkan1.3 "$shader" || exit
|
||||
done
|
||||
```
|
||||
|
||||
Логи сборки/тестов: `out/dev/motion-validation.log`. Доступа к `/dev/dri` в среде
|
||||
проверки нет: качество на RX 7800 XT, FPS и процент покрытия после изменений
|
||||
ещё не проверены. Успешная валидация SPIR-V не доказывает правильность картинки.
|
||||
|
||||
## Следующий запуск в игре
|
||||
|
||||
Из `game_files`:
|
||||
|
||||
```bash
|
||||
BB_FRAME_STATS=1 BB_TOGGLE_FILE=out/dev/motion-toggles bash native_probe/run.sh 2>&1 | tee native_probe/out/dev/motion-run.log
|
||||
```
|
||||
|
||||
Бит 29 во временном toggle-файле выключает только запись истории. Для корректного
|
||||
сравнения производительности надо запускать игру с `BB_OBJECT_MOTION=0` и без него:
|
||||
выбор пайплайна и дополнительная цель задаются при старте процесса.
|
||||
После переключения дать FSR несколько кадров на обновление истории.
|
||||
Сравнивать одну сцену: персонаж/оружие при беге, поворот камеры, бой, неподвижная камера.
|
||||
Через ~600 кадров проверить `Object motion:`: `with history`, `capacity skips`, `unmatched`.
|
||||
|
||||
Оставшиеся ограничения: одинаковые меши сопоставляются по порядку draws, который
|
||||
может меняться при отсечении объектов; смена адресов динамических vertex buffers
|
||||
теряет совпадение. Indirect draws, tessellation/geometry shaders, MSAA и проходы
|
||||
вне G-buffer остаются на векторах камеры. Прозрачность/частицы, мыльный UI и перенос
|
||||
масштабирования на уровень рендерера этим изменением не решены. Стоимость сканирования
|
||||
индексов и атомарных записей нужно измерить на реальном запуске.
|
||||
|
||||
## 29 сентября: оружие и маленькие скелеты (гостинг пилы-вертушки)
|
||||
|
||||
Видео `video_2026-09-29_23-08-26.mp4`: персонаж стоит, диск пилы вращается и оставляет
|
||||
шлейф. Лог `BB_MOTION_SELECT_LOG=1` показал причину: второй буфер вершинного шейдера —
|
||||
палитра костей по 48 байт (3x4), и у оружия/реквизита она 96–384 байта (2–8 костей).
|
||||
Правило «от 640 байт» такие отрисовки не включало, диск получал только векторы камеры,
|
||||
то есть для FSR был неподвижным.
|
||||
|
||||
Изменения:
|
||||
|
||||
- `Motion::ClassifyBuffer` (`motion_history.h`): 864 — сцена, ≥640 — скелет персонажа,
|
||||
96..639 кратно 48 — маленький скелет. Пайплайны с любым скелетом получают вариант
|
||||
с векторами.
|
||||
- Маленьких скелетов около тысячи на кадр, большинство — статичные части карты.
|
||||
Поэтому они «гейтятся»: хеш палитры сравнивается с прошлым кадром по дешёвому
|
||||
ключу (`History::Moving`) до сканирования индексов. Совпала — вектор камеры точен,
|
||||
ничего не пишется. Изменилась — позиции сохраняются, со следующего кадра вектор
|
||||
объекта. Хеш с буфером 416 байт не подходит: он меняется вместе с камерой почти у всех
|
||||
(проверено: переполнение истории и падение FPS до ~40).
|
||||
- Меню: «Показать векторы движения (отладка)». Красный/зелёный — |x|/|y|, синий —
|
||||
пиксель получил вектор объекта. `BB_MOTION_SELECT_LOG=1` печатает шейдеры G-buffer
|
||||
с размерами буферов и решением.
|
||||
|
||||
Замер `out/motion-gated.log`: ~225 сохранённых отрисовок и ~600 «неподвижных» на кадр,
|
||||
без переполнения, 58–80 FPS в игровых сценах. Визуально исчезновение шлейфа
|
||||
на пиле ещё нужно подтвердить.
|
||||
@@ -1,5 +1,9 @@
|
||||
# Parallel GPU command processing — design notes
|
||||
|
||||
> Design notes from when this work was planned, kept for their reasoning. bbport 0.7 has one
|
||||
> memory model and its own renderer, and some switches named here (`BB_UFFD`) no longer exist; the
|
||||
> current list is in [ENVIRONMENT.md](ENVIRONMENT.md).
|
||||
|
||||
Goal: remove the single-core bottleneck of the emulated GPU command processor
|
||||
(`shadPS4:GpuCommandProcessor`, ~100% of one core while the rest of the CPU idles).
|
||||
|
||||
@@ -268,10 +272,10 @@ copies queued in the recording stream, i.e. until the recording thread has recor
|
||||
command before them, plus ~4% for the copy threads (`BbCopy::WaitAsync`). The recording thread
|
||||
itself works ~40% (the rest is its spin); it is not the limit.
|
||||
|
||||
### Texture binding on a helper thread (opt-in: `BB_TEXTURE_HELPER=1`, toggle 1 << 22)
|
||||
### Texture binding on a helper thread (opt-in: `BB_TEXTURE_HELPER=1`, toggle 1 << 34)
|
||||
|
||||
`bb:TexBind` binds a draw's textures while the GPU thread binds its buffers and resolves its
|
||||
vertex/index buffers (split into Resolve/Emit, toggle 1 << 23); the GPU thread joins before
|
||||
vertex/index buffers (split into Resolve/Emit, toggle 1 << 35); the GPU thread joins before
|
||||
`BeginRendering`. The helper takes only the memoized path (FindImage memo valid, image clean,
|
||||
no scene-target proxy, no upscaler redirect, no storage images, no mip arrays, no storage
|
||||
buffers in the pipeline); anything else is bound after the join. The GPU thread joins before
|
||||
@@ -291,7 +295,7 @@ not pay on this CPU; the helper stays opt-in for other CPUs.
|
||||
|
||||
| change | A/B | kept |
|
||||
|---|---|---|
|
||||
| FindView memo in the image description cache (1 << 21) + write prefetch in record chunks (1 << 20) | 81.5 vs 80.8 FPS | yes |
|
||||
| FindView memo in the image description cache (1 << 33) + write prefetch in record chunks (1 << 32) | 81.5 vs 80.8 FPS | yes |
|
||||
| format check by table instead of `magic_enum::enum_contains` (a linear scan per texture per draw); hot `Image` fields (flags, binding, tracking range, backing, ticks) moved in front of `ImageInfo` | within noise | yes (no downside) |
|
||||
| host copy queue: small copies in a lock-free MPMC queue, run by the idle recording thread, the rest by the GPU thread at the wait | blocked 9.5% → 4.8%, but 86.5 vs 87.2 FPS: the copying moved onto the GPU thread | no |
|
||||
| no barrier tracking for read-only stream buffer ranges | 85.9 vs 87.4 FPS, both phase orders (cause not found) | no |
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Steam and the Steam Deck
|
||||
|
||||
## Add to Steam
|
||||
|
||||
**Setup → Game → Add to Steam** puts *Bloodborne* in your Steam library as a non-Steam game. It
|
||||
starts the game straight away, with the launcher's settings, without opening the launcher. Steam
|
||||
has to close for it, and the launcher offers to do that.
|
||||
|
||||
After updating from an older release, press **Add to Steam** once more so the shortcut points to
|
||||
the right place.
|
||||
|
||||
## On the Steam Deck
|
||||
|
||||
1. In Desktop Mode, download the AppImage and set the game up in the launcher, as in
|
||||
[Getting started](getting-started.md). A resolution of **720p** with an upscaler (FSR) suits the
|
||||
Deck's screen.
|
||||
2. Press **Add to Steam**.
|
||||
3. Back in Game Mode, start *Bloodborne* from your library.
|
||||
|
||||
To change a setting later, go back to Desktop Mode and open the launcher: what you change there is
|
||||
what Game Mode uses.
|
||||
|
||||
!!! tip "Logs in Game Mode"
|
||||
Turn on *Advanced → Developer → Save the log and statistics to a file* once in Desktop Mode:
|
||||
every session played from Game Mode then saves its log too. [Getting a log](logs.md).
|
||||
|
||||
## Controllers and Steam Input
|
||||
|
||||
With Steam Input on, the game gets Steam's virtual controller. On a Wayland desktop the game then
|
||||
opens its window through Xwayland, so Steam's input reaches it. With Steam Input off, the game reads
|
||||
the controller itself. [More](TROUBLESHOOTING.md#the-controller-does-nothing-when-the-game-is-started-from-steam).
|
||||
|
||||
## The Steam overlay
|
||||
|
||||
Shift+Tab, Steam's FPS counter and F12 screenshots need **Steam overlay**, under *Add to Steam* in
|
||||
the launcher (on by default), and the game started from Steam.
|
||||
[More](TROUBLESHOOTING.md#the-steam-overlay-does-not-open-in-the-game).
|
||||
@@ -0,0 +1,148 @@
|
||||
/* The players' guide: "Hunter's Dream" theme over MkDocs Material's dark scheme. Moonlit
|
||||
blue-grey; IM Fell English for headings and menus, EB Garamond for reading (IM Fell has only
|
||||
old-style figures: 1 and 0 read as ı and o in versions and values). No artwork or typeface from
|
||||
the game. */
|
||||
@import url("https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;0,600;1,400&family=IM+Fell+English:ital@0;1&family=IM+Fell+English+SC&family=JetBrains+Mono:wght@400&display=swap");
|
||||
|
||||
[data-md-color-scheme="slate"] {
|
||||
--hd-bg: #0f1218;
|
||||
--hd-bg-raised: #12161e;
|
||||
--hd-line: #2a3140;
|
||||
--hd-text: #d4d9e2;
|
||||
--hd-muted: #a9b3c4;
|
||||
--hd-bright: #f2f4f8;
|
||||
--hd-moon: #e4eaf5;
|
||||
--hd-blood: #e0a0a0;
|
||||
|
||||
--md-default-bg-color: var(--hd-bg);
|
||||
--md-default-fg-color: var(--hd-text);
|
||||
--md-default-fg-color--light: var(--hd-muted);
|
||||
--md-default-fg-color--lighter: #6f7a8c;
|
||||
--md-default-fg-color--lightest: var(--hd-line);
|
||||
--md-primary-fg-color: #141924;
|
||||
--md-primary-fg-color--dark: #1a2030;
|
||||
--md-primary-fg-color--light: #2a3140;
|
||||
--md-primary-bg-color: var(--hd-bright);
|
||||
--md-accent-fg-color: #ffffff;
|
||||
--md-typeset-a-color: #b9c8e6;
|
||||
--md-code-bg-color: #0a0d12;
|
||||
--md-code-fg-color: #c8d3e6;
|
||||
--md-footer-bg-color: #0b0d12;
|
||||
--md-footer-bg-color--dark: #08090d;
|
||||
--md-text-font-family: "EB Garamond", Georgia, serif;
|
||||
--md-code-font-family: "JetBrains Mono", monospace;
|
||||
}
|
||||
|
||||
body, input { font-family: "EB Garamond", Georgia, serif; font-variant-numeric: lining-nums; }
|
||||
.md-typeset { font-size: .92rem; line-height: 1.65; }
|
||||
.md-typeset code, .md-typeset pre { font-family: "JetBrains Mono", monospace; }
|
||||
|
||||
/* Headings, the header and the navigation in the small-capitals cut. */
|
||||
.md-typeset h1, .md-typeset h2, .md-typeset h3, .md-typeset h4,
|
||||
.md-header__title, .md-nav__title, .md-tabs__link, .md-nav__item--section > .md-nav__link {
|
||||
font-family: "IM Fell English SC", Georgia, serif;
|
||||
font-weight: 400;
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
.md-typeset h1 { color: var(--hd-bright); font-family: "IM Fell English", Georgia, serif; }
|
||||
.md-typeset h2 { color: var(--hd-bright); border-bottom: 1px solid var(--hd-line); padding-bottom: .2em; }
|
||||
.md-typeset h3 { color: #c4cfe2; }
|
||||
|
||||
.md-header { background: rgba(15, 18, 24, .85); backdrop-filter: blur(6px); border-bottom: 1px solid var(--hd-line); box-shadow: none; }
|
||||
.md-sidebar--primary .md-nav__link--active { color: #fff; }
|
||||
/* The phone drawer: its repository strip and title in the theme's colours, not Material's blue. */
|
||||
.md-nav__source { background-color: #1a2030; }
|
||||
.md-nav--primary .md-nav__title[for="__drawer"] { background-color: #141924; }
|
||||
.md-sidebar--primary .md-nav__link--active::before { content: "✦ "; color: #9fb0d0; }
|
||||
.md-nav__link:hover, .md-typeset a:hover { color: #fff; }
|
||||
|
||||
/* Admonitions: quiet frames instead of Material's bright ones. */
|
||||
.md-typeset .admonition, .md-typeset details {
|
||||
background: rgba(160, 180, 215, .06);
|
||||
border: 1px solid #2f3a4f !important;
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
font-size: .85rem;
|
||||
}
|
||||
.md-typeset .admonition-title, .md-typeset summary {
|
||||
background: transparent !important;
|
||||
font-family: "IM Fell English SC", Georgia, serif;
|
||||
color: #c4cfe2;
|
||||
}
|
||||
.md-typeset .admonition-title::before, .md-typeset summary::before { background-color: #9fb0d0 !important; }
|
||||
.md-typeset .warning, .md-typeset .danger { border-color: #5a3a3a !important; background: rgba(170, 80, 80, .08); }
|
||||
.md-typeset .warning > .admonition-title, .md-typeset .danger > .admonition-title { color: var(--hd-blood); }
|
||||
.md-typeset .warning > .admonition-title::before, .md-typeset .danger > .admonition-title::before { background-color: var(--hd-blood) !important; }
|
||||
|
||||
.md-typeset table:not([class]) { border: 1px solid var(--hd-line); font-size: .8rem; }
|
||||
/* Reference tables (ENVIRONMENT.md): names whole, and every column wide enough to read. */
|
||||
.md-typeset table:not([class]) td:first-child code { white-space: nowrap; word-break: normal; }
|
||||
.md-typeset table:not([class]) td:nth-child(2) { min-width: 6rem; }
|
||||
.md-typeset table:not([class]) td:nth-child(3) { min-width: 15rem; }
|
||||
.md-typeset table:not([class]) td:nth-child(4) { min-width: 7rem; }
|
||||
/* Wider pages than Material's 61rem: the reference tables have four columns. */
|
||||
.md-grid { max-width: 90rem; }
|
||||
.md-typeset table:not([class]) td code { word-break: break-word; }
|
||||
.md-typeset table:not([class]) th { background: var(--hd-bg-raised); font-family: "IM Fell English SC", Georgia, serif; font-weight: 400; }
|
||||
|
||||
/* Home page: the moon and the mist (overrides/home.html). */
|
||||
.hd-hero {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
padding: 7rem 1rem 6.5rem;
|
||||
background:
|
||||
radial-gradient(circle at 78% 22%, #eef2f9 0 42px, rgba(220, 228, 244, .35) 43px, rgba(200, 212, 236, .10) 110px, transparent 210px),
|
||||
radial-gradient(ellipse 70% 40% at 50% 100%, rgba(160, 175, 205, .22), transparent 70%),
|
||||
linear-gradient(#1c2335, var(--hd-bg) 75%);
|
||||
}
|
||||
.hd-hero::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: auto 0 0 0;
|
||||
height: 160px;
|
||||
background: linear-gradient(transparent, rgba(190, 200, 220, .10) 60%, var(--hd-bg));
|
||||
}
|
||||
.hd-hero__content { position: relative; z-index: 1; }
|
||||
.hd-hero h1 { margin: 0; font: 400 clamp(2.4rem, 6vw, 4.4rem) "IM Fell English", Georgia, serif; color: var(--hd-bright); }
|
||||
.hd-hero p { margin: .9rem 0 0; color: #b8c2d3; font-style: italic; font-size: 1.3rem; }
|
||||
.hd-buttons { display: flex; gap: .9rem; justify-content: center; flex-wrap: wrap; margin-top: 2rem; }
|
||||
.hd-button {
|
||||
padding: .75rem 1.75rem;
|
||||
font: 1.1rem "IM Fell English SC", Georgia, serif;
|
||||
color: #e9edf3 !important;
|
||||
border: 1px solid rgba(220, 228, 240, .45);
|
||||
border-radius: 2px;
|
||||
background: rgba(15, 18, 24, .55);
|
||||
transition: background .2s, border-color .2s;
|
||||
}
|
||||
.hd-button:hover { background: rgba(255, 255, 255, .1); border-color: rgba(240, 244, 250, .8); }
|
||||
.hd-button--primary { background: rgba(228, 234, 245, .92); color: #141822 !important; border-color: transparent; }
|
||||
.hd-button--primary:hover { background: #fff; }
|
||||
|
||||
.hd-home { max-width: 54rem; margin: 0 auto; }
|
||||
.hd-cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; margin: 1.2rem 0 2rem; }
|
||||
.hd-card {
|
||||
display: block;
|
||||
border: 1px solid var(--hd-line);
|
||||
background: rgba(160, 180, 215, .05);
|
||||
padding: 1.1rem 1.2rem;
|
||||
border-radius: 4px;
|
||||
color: var(--hd-text) !important;
|
||||
transition: border-color .2s, background .2s;
|
||||
}
|
||||
.hd-card:hover { border-color: #5b6a86; background: rgba(160, 180, 215, .09); }
|
||||
.hd-card strong { display: block; font: 1.25rem "IM Fell English SC", Georgia, serif; font-weight: 400; color: #dfe5ef; margin-bottom: .3rem; }
|
||||
.hd-card span { color: var(--hd-muted); font-size: .85rem; }
|
||||
|
||||
@media screen and (max-width: 44.9375em) {
|
||||
/* A smaller moon in the corner: in the middle it covered the title. */
|
||||
.hd-hero {
|
||||
padding: 4.5rem 1rem 4rem;
|
||||
background:
|
||||
radial-gradient(circle at 86% 11%, #eef2f9 0 22px, rgba(220, 228, 244, .35) 23px, rgba(200, 212, 236, .10) 60px, transparent 120px),
|
||||
radial-gradient(ellipse 70% 40% at 50% 100%, rgba(160, 175, 205, .22), transparent 70%),
|
||||
linear-gradient(#1c2335, var(--hd-bg) 75%);
|
||||
}
|
||||
.hd-cards { grid-template-columns: 1fr; }
|
||||
}
|
||||
+133
-131
@@ -140,162 +140,164 @@ The previous frame's matrices are not there; the port keeps them itself.
|
||||
- Missing: motion of animated objects (characters, cloth, foliage), reactive/transparency
|
||||
masks for particles and fog, render-resolution scaling, frame generation.
|
||||
|
||||
## 2026-10-01: FSR 4 при выводе 1440p/2160p не работал (мерцание и дрожание)
|
||||
## 2026-10-01: FSR 4 did not work at 1440p/2160p output (flicker and shimmer)
|
||||
|
||||
Видео `video_2026-10-01_03-58-59.mp4` (вывод 3840x2160, FSR 4 Performance): мерцание и дрожание
|
||||
всех объектов. Причина — апскейлер в этом режиме вообще не выполнялся, а jitter оставался
|
||||
включённым: на экран шёл растянутый кадр сцены, каждый кадр сдвинутый на свою фазу Halton.
|
||||
Video `video_2026-10-01_03-58-59.mp4` (3840x2160 output, FSR 4 Performance): flicker and shimmer
|
||||
on all objects. Cause: the upscaler did not run at all in this mode, while jitter stayed
|
||||
enabled. The screen showed a stretched scene frame, each frame shifted by its own Halton phase.
|
||||
|
||||
1. Проход UI опознавался по точному размеру цели `BB_RENDER_RES` (1916x1078), а игра выделяет
|
||||
цели с выровненной высотой (1916x1080; в константах сцены тоже 1916x1080). `RunScaled` не
|
||||
вызывался ни разу (в логе не было `UI: native composition`). Теперь допускается выравнивание
|
||||
до 8 пикселей (`RenderTarget`), а размер сцены для FSR берётся из констант сцены
|
||||
(`CameraMotion::RenderSize`, `SceneSize`).
|
||||
2. После этого FSR 4 падал с `external image registration failed (-1000069000)`: `RunScaled`
|
||||
создавал новые image view каждый кадр, а реестр FSR 4 вмещает восемь. Теперь те же
|
||||
`CachedView`, что и в пути Native AA.
|
||||
1. The UI pass was identified by the exact `BB_RENDER_RES` target size (1916x1078), but the game
|
||||
allocates targets with an aligned height (1916x1080; the scene constants also say 1916x1080).
|
||||
`RunScaled` was never called (the log had no `UI: native composition`). Alignment up to
|
||||
8 pixels is now accepted (`RenderTarget`), and the scene size for FSR comes from the scene
|
||||
constants (`CameraMotion::RenderSize`, `SceneSize`).
|
||||
2. After that FSR 4 failed with `external image registration failed (-1000069000)`: `RunScaled`
|
||||
created new image views every frame, and the FSR 4 registry holds eight. It now uses the same
|
||||
`CachedView` as the Native AA path.
|
||||
|
||||
Проверка дампом (`BB_DUMP_TRIGGER=<файл> BB_DUMP_DIR=<каталог>`, `BB_DUMP_FRAMES`, по умолчанию 8:
|
||||
вход FSR, векторы движения и выход, raw): при неподвижной камере PSNR соседних кадров выхода
|
||||
~45 дБ против ~28 дБ у входа с jitter; при повороте камеры и ходьбе шлейфов нет. FPS в этом
|
||||
режиме ~107 вместо ~220 — раньше FSR 4 просто не выполнялся.
|
||||
Dump check (`BB_DUMP_TRIGGER=<file> BB_DUMP_DIR=<dir>`, `BB_DUMP_FRAMES`, default 8:
|
||||
FSR input, motion vectors and output, raw): with a static camera the PSNR of adjacent output frames
|
||||
is ~45 dB against ~28 dB for the jittered input; no trails when turning the camera or walking. FPS in
|
||||
this mode is ~107 instead of ~220; before, FSR 4 simply did not run.
|
||||
|
||||
Осталось: спрайты (по 4 индекса) в цвет сцены со сценической глубиной — свечения, огоньки —
|
||||
по-прежнему не сдвигаются jitter (правило «≤ 6 индексов = полноэкранный проход»).
|
||||
Remaining: sprites (4 indices each) drawn into scene color with scene depth (glows, small lights)
|
||||
are still not shifted by jitter (the rule "≤ 6 indices = full-screen pass").
|
||||
|
||||
## 2026-10-01: FSR 4 быстрее — проход post (2.9 → 0.8 мс в 4K)
|
||||
## 2026-10-01: Faster FSR 4 — the post pass (2.9 → 0.8 ms at 4K)
|
||||
|
||||
Замеры: `BB_FSR4_PROFILE=1` (время каждого прохода FSR 4, патч провайдера в субмодуле,
|
||||
см. `gpu/patches/fsr-vulkan`), бенчмарк вне игры `out/gpu/fsr4-bench` (`ninja -C out/gpu
|
||||
fsr4-bench`; `--stats` — регистры и инструкции от RADV). 4K Balanced (2260x1272 → 3840x2160),
|
||||
RX 7800 XT: весь FSR 4 — 5.8 мс, из них **post 2.5–2.9 мс** (не нейросеть: последние слои,
|
||||
pixel shuffle 2x2 и смешивание с историей), 12 проходов модели — 2.4 мс, pre 0.55.
|
||||
Measurements: `BB_FSR4_PROFILE=1` (time of each FSR 4 pass, a provider patch in the submodule,
|
||||
see `gpu/patches/fsr-vulkan`), the out-of-game benchmark `out/gpu/fsr4-bench` (`ninja -C out/gpu
|
||||
fsr4-bench`; `--stats` prints registers and instructions from RADV). 4K Balanced (2260x1272 → 3840x2160),
|
||||
RX 7800 XT: all of FSR 4 takes 5.8 ms, of which **post is 2.5–2.9 ms** (not the neural network: the last layers,
|
||||
2x2 pixel shuffle and blending with history), the 12 model passes 2.4 ms, pre 0.55.
|
||||
|
||||
Причина: каждый поток считает блок 2x2 выходных пикселей и пишет их по одному в три образа
|
||||
(рекуррентное состояние, история, выход) — каждая инструкция записи волны пишет пиксели через
|
||||
один. Убрав любую из трёх записей, проход ускорялся в 1.3–4 раза при том же коде.
|
||||
Cause: each thread computes a 2x2 block of output pixels and writes them one by one into three images
|
||||
(recurrent state, history, output), so each wave store instruction writes every other
|
||||
pixel. Removing any one of the three stores made the pass 1.3–4 times faster with the same code.
|
||||
|
||||
Решение: `tools/fsr4_optimize.sh` декомпилирует post (spirv-cross), `tools/fsr4_post_lds.pl`
|
||||
собирает блок 16x16 рабочей группы в shared memory и пишет сплошными строками, glslang
|
||||
компилирует обратно в `fsr4_shaders/opt/`; `vk_fsr4.cpp` берёт его оттуда (`BB_FSR4_OPT=0` —
|
||||
оригинал). Две тонкости, найденные сравнением выходов:
|
||||
- spirv-cross переводит знаковую распаковку int8 (`OpBitcast` в `i8vec4`) как `unpack8(uint)`
|
||||
(беззнаковую) — без исправления результат совсем другой (PSNR 17 дБ);
|
||||
- значения в shared memory должны оставаться float: half из shared memory компилятор
|
||||
превращает в 16-битную запись, а она иначе округляет в unorm8.
|
||||
Fix: `tools/fsr4_optimize.sh` decompiles post (spirv-cross), `tools/fsr4_post_lds.pl`
|
||||
gathers the workgroup's 16x16 block in shared memory and writes contiguous rows, and glslang
|
||||
compiles it back into `fsr4_shaders/opt/`; `vk_fsr4.cpp` loads it from there (`BB_FSR4_OPT=0` selects the
|
||||
original). Two subtleties, found by comparing outputs:
|
||||
- spirv-cross translates the signed int8 unpack (`OpBitcast` to `i8vec4`) as `unpack8(uint)`
|
||||
(unsigned); without a fix the result is completely different (PSNR 17 dB);
|
||||
- values in shared memory must stay float: the compiler turns a half from shared memory
|
||||
into a 16-bit store, which rounds differently to unorm8.
|
||||
|
||||
`tools/fsr4_verify.sh` сравнивает оригинал и оптимизированный post для всех пресетов при выводе
|
||||
1080p/1440p/4K на псевдослучайных входах: 1440p и 4K — побитово одинаково; post 4K 2.1–3.5 →
|
||||
0.81–0.87 мс, 1440p 1.2–1.5 → 0.36–0.38 мс, 1080p 0.37–0.69 → 0.20–0.22 мс. В игре (4K
|
||||
Balanced): FSR 4 5.8 → 4.0 мс, кадр GPU 13.1 → ~11.6 мс; дальше FPS упирается в CPU (поток
|
||||
GPU-команд ждёт копии гостевой памяти, «host copies» ~20%).
|
||||
`tools/fsr4_verify.sh` compares the original and the optimized post for all presets at
|
||||
1080p/1440p/4K output on pseudo-random inputs: 1440p and 4K are bit-identical; post at 4K 2.1–3.5 →
|
||||
0.81–0.87 ms, 1440p 1.2–1.5 → 0.36–0.38 ms, 1080p 0.37–0.69 → 0.20–0.22 ms. In game (4K
|
||||
Balanced): FSR 4 5.8 → 4.0 ms, GPU frame 13.1 → ~11.6 ms; beyond that FPS is CPU-bound (the
|
||||
GPU command thread waits for guest memory copies, "host copies" ~20%).
|
||||
|
||||
Найдено попутно: **оригинальный FSR 4 на уровне 1080 (вывод 1920x1080) недетерминирован** — от
|
||||
запуска к запуску на одинаковых входах меняется полоса у левого края (столбцы 0–132), то есть
|
||||
где-то гонка или чтение неинициализированной памяти в модели/провайдере. При выводе 1080p это
|
||||
может давать мерцание у левого края кадра. Не исследовано.
|
||||
Found along the way: **the original FSR 4 at the 1080 tier (1920x1080 output) is nondeterministic**: from
|
||||
run to run on identical inputs a strip at the left edge changes (columns 0–132), so there is
|
||||
a race or a read of uninitialized memory somewhere in the model/provider. At 1080p output this
|
||||
can cause flicker at the left edge of the frame. Not investigated.
|
||||
|
||||
Маска реактивности при FSR 4 больше не считается (FSR 4 её не принимает).
|
||||
The reactive mask is no longer computed with FSR 4 (FSR 4 does not accept it).
|
||||
|
||||
### Что ещё проверено (там же, 4K Balanced)
|
||||
### What else was checked (same setup, 4K Balanced)
|
||||
|
||||
- **A/B в одном процессе** (`ab.sh`, бит 24 — FSR выключен, UI-копия без апскейла): 86.5 FPS с
|
||||
FSR 4 против 87.7 без него. После оптимизации post кадр упирается в CPU (поток GPU-команд,
|
||||
ожидание копий гостевой памяти), а не в GPU. Async compute для FSR (перекрыть его с началом
|
||||
следующего кадра) дал бы не больше этого ~1% — отложено до ускорения CPU-части; к тому же он
|
||||
требует переставлять команды UI и вывода кадра N после работы кадра N+1.
|
||||
- **WMMA (`VK_KHR_cooperative_matrix`, RADV на RDNA3 поддерживает)**: прототип прохода 1
|
||||
модели (остаточный блок 16 каналов: 3x3 16→16, 1x1 16→32 ReLU, 1x1 32→16) — 0.85 мс в первом
|
||||
варианте, 0.49 мс со словной раскладкой в shared memory, против 0.30 мс у исходного dot4.
|
||||
Арифметика на WMMA заняла бы ~0.1 мс, но при 16 каналах выкладка тайлов, эпилоги через shared
|
||||
memory и занятость (6 волн/SIMD, предел по LDS) съедают выигрыш. Потолок — около 1 мс на
|
||||
все 12 проходов при тонкой ручной переработке каждого; не начато.
|
||||
- `RADV_PERFTEST=cswave32` (wave32 для compute): FSR 4 медленнее, 4.2 → 5.4 мс.
|
||||
- Проход pre (0.54 мс) запись не ограничивает (0.50 мс без записи).
|
||||
- **A/B in one process** (`ab.sh`, bit 24 turns FSR off, UI copy without upscaling): 86.5 FPS with
|
||||
FSR 4 against 87.7 without it. After the post optimization the frame is CPU-bound (GPU command thread,
|
||||
waiting for guest memory copies), not GPU-bound. Async compute for FSR (overlapping it with the start
|
||||
of the next frame) would gain no more than this ~1%; postponed until the CPU side is faster. It also
|
||||
requires reordering the UI and present commands of frame N after the work of frame N+1.
|
||||
- **WMMA (`VK_KHR_cooperative_matrix`, supported by RADV on RDNA3)**: a prototype of model pass 1
|
||||
(16-channel residual block: 3x3 16→16, 1x1 16→32 ReLU, 1x1 32→16): 0.85 ms in the first
|
||||
version, 0.49 ms with a word layout in shared memory, against 0.30 ms for the original dot4.
|
||||
The arithmetic on WMMA would take ~0.1 ms, but with 16 channels the tile layout, epilogues through shared
|
||||
memory and occupancy (6 waves/SIMD, limited by LDS) eat the gain. The ceiling is about 1 ms for
|
||||
all 12 passes with careful manual rework of each; not started.
|
||||
- `RADV_PERFTEST=cswave32` (wave32 for compute): FSR 4 is slower, 4.2 → 5.4 ms.
|
||||
- The pre pass (0.54 ms) is not limited by stores (0.50 ms without stores).
|
||||
|
||||
## 2026-10-01: мерцание FSR 4 у левого края при выводе 1080p — гонка в проходе 11 модели
|
||||
## 2026-10-01: FSR 4 flicker at the left edge at 1080p output — a race in model pass 11
|
||||
|
||||
Недетерминизм уровня 1080 (см. выше) — ошибка в шейдере модели v07, проход 11 (декодер,
|
||||
1/4 → 1/2 разрешения). Каждый поток — пиксель входа 1/4 разрешения и пишет блок 2x2 выхода
|
||||
1/2 разрешения. Диспатч округляет ширину до 64 потоков, а потоки за шириной входа не
|
||||
останавливаются: на уровне 1080 (вход 480 в ширину) потоки 480..511 пишут пиксели выхода
|
||||
960..1023, то есть первые пиксели следующей строки (строка любого тензора — 15360 байт), и
|
||||
гоняются с их настоящими авторами. При выводе 4K ширина 960 делится на 64, ошибки нет.
|
||||
The 1080-tier nondeterminism (see above) is a bug in the v07 model shader, pass 11 (decoder,
|
||||
1/4 → 1/2 resolution). Each thread is one pixel of the 1/4-resolution input and writes a 2x2 block of the
|
||||
1/2-resolution output. The dispatch rounds the width up to 64 threads, and threads beyond the input width do not
|
||||
exit: at the 1080 tier (input 480 wide) threads 480..511 write output pixels
|
||||
960..1023, which are the first pixels of the next row (a row of any tensor is 15360 bytes), and
|
||||
race with their real writers. At 4K output the width 960 is divisible by 64, so there is no bug.
|
||||
|
||||
`tools/fsr4_pass11_guard.pl` добавляет ранний выход для потоков за границей (размер берётся из
|
||||
проверки соседей самого прохода); `tools/fsr4_optimize.sh` собирает его в `fsr4_shaders/opt`
|
||||
для всех пресетов вместе с post; `vk_fsr4.cpp` и бенчмарк берут из `opt/` любой проход.
|
||||
`tools/fsr4_verify.sh`: 1440p/4K — побитово как оригинал (защита там не срабатывает); 1080p —
|
||||
одинаковый результат от запуска к запуску, отличия от оригинала после первого кадра только в
|
||||
бывшей полосе у левого края.
|
||||
`tools/fsr4_pass11_guard.pl` adds an early exit for out-of-bounds threads (the size comes from
|
||||
the pass's own neighbor check); `tools/fsr4_optimize.sh` builds it into `fsr4_shaders/opt`
|
||||
for all presets together with post; `vk_fsr4.cpp` and the benchmark load any pass from `opt/`.
|
||||
`tools/fsr4_verify.sh`: 1440p/4K are bit-identical to the original (the guard does not trigger there); 1080p gives
|
||||
the same result from run to run, and after the first frame it differs from the original only in
|
||||
the former strip at the left edge.
|
||||
|
||||
Найдено при этом: spirv-cross переводит все знаковые распаковки int8 (`OpBitcast` в `v4char`)
|
||||
как беззнаковый `unpack8(uint)`; в pass 11 их девять разных форм. Исправление общее для
|
||||
скриптов — `tools/Fsr4SpirvCrossFixes.pm` (все `unpack8` через знаковый помощник).
|
||||
Found in the process: spirv-cross translates all signed int8 unpacks (`OpBitcast` to `v4char`)
|
||||
as unsigned `unpack8(uint)`; pass 11 has nine different forms of them. The fix is shared by the
|
||||
scripts: `tools/Fsr4SpirvCrossFixes.pm` (all `unpack8` go through a signed helper).
|
||||
|
||||
## 2026-10-01: FSR 4.1.1 — что внутри и как его запустить в Vulkan (разведка)
|
||||
## 2026-10-01: FSR 4.1.1 — what is inside and how to run it in Vulkan (investigation)
|
||||
|
||||
Источник: `amd_fidelityfx_upscaler_dx12.dll` 4.1.1.2740 из OptiScaler (`FSR4_LATEST`).
|
||||
- В DLL есть провайдер `ffxProvider_FSR4_Int8` / `Fsr4Int8UpscalerModel` (версия «FSR4-i8 4.1.1»).
|
||||
1028 DXIL-контейнеров (25.7 МБ): проходы модели `fsr4_model_v07_fp8_no_scale_pass1..12` (+ `_post`),
|
||||
270 вариантов `prepass`, 144 `postpass`, `fsr_rcas_pass` и запасной FSR 3.1/2. Сжатых данных нет.
|
||||
- Несмотря на «fp8» в имени, проходы модели — обычный DXIL с `dot4AddPacked` (int8 dot4), без
|
||||
расширений AMD (WMMA). Архитектура та же v07 (12 проходов), но веса и смещения не зашиты в
|
||||
шейдер, а читаются из `InitializerBuffer` (t18, typed buffer), размеры тензоров — из cbuffer
|
||||
`CsTensorSizes`, scratch — `u11`. У каждого прохода 9 вариантов: сетка зашита (уровни 1080 / 4K /
|
||||
8K: 480x270, 960x540, 1920x1080 для прохода 5) и, видимо, пресеты.
|
||||
- `amdxcffx64.dll` (загрузчик 2.3.0.2913, его подгружает Proton) — те же FP8-проходы FSR 4 и ML-
|
||||
генерация кадров (`mlfi_*`), отдельной INT8-модели нет. `FSR4_INT8` 4.0.2 — это `v07_i8`, то есть
|
||||
то, что порт уже использует.
|
||||
- dxil-spirv (HansKristian-Work/dxil-spirv, собирается из исходников) переводит проходы в
|
||||
корректный SPIR-V: `dxil-spirv <pass> --enable-shader-i8-dot --ssbo-uav --use-reflection-names`.
|
||||
Source: `amd_fidelityfx_upscaler_dx12.dll` 4.1.1.2740 from OptiScaler (`FSR4_LATEST`).
|
||||
- The DLL contains the provider `ffxProvider_FSR4_Int8` / `Fsr4Int8UpscalerModel` (version "FSR4-i8 4.1.1").
|
||||
1028 DXIL containers (25.7 MB): model passes `fsr4_model_v07_fp8_no_scale_pass1..12` (+ `_post`),
|
||||
270 `prepass` variants, 144 `postpass`, `fsr_rcas_pass` and the FSR 3.1/2 fallback. There is no compressed data.
|
||||
- Despite "fp8" in the name, the model passes are plain DXIL with `dot4AddPacked` (int8 dot4), without
|
||||
AMD extensions (WMMA). The architecture is the same v07 (12 passes), but weights and biases are not baked into
|
||||
the shader; they are read from `InitializerBuffer` (t18, typed buffer), tensor sizes from the cbuffer
|
||||
`CsTensorSizes`, scratch is `u11`. Each pass has 9 variants: the grid is baked in (1080 / 4K /
|
||||
8K tiers: 480x270, 960x540, 1920x1080 for pass 5) and, apparently, the presets.
|
||||
- `amdxcffx64.dll` (loader 2.3.0.2913, loaded by Proton) has the same FP8 FSR 4 passes and ML
|
||||
frame generation (`mlfi_*`), with no separate INT8 model. `FSR4_INT8` 4.0.2 is `v07_i8`, that is,
|
||||
what the port already uses.
|
||||
- dxil-spirv (HansKristian-Work/dxil-spirv, built from source) translates the passes into
|
||||
correct SPIR-V: `dxil-spirv <pass> --enable-shader-i8-dot --ssbo-uav --use-reflection-names`.
|
||||
|
||||
Чего не хватает для своего провайдера 4.1.1: соответствие контейнеров (пресет, уровень, проход),
|
||||
содержимое `InitializerBuffer`, значения `CsTensorSizes`, перестановки prepass/postpass, константы и
|
||||
размеры диспатчей. Самый надёжный путь — не разбирать x86-код провайдера, а записать один кадр:
|
||||
маленькая тестовая программа на FFX API (DX12, MinGW) под Wine/Proton с vkd3d-proton, явный выбор
|
||||
провайдера «FSR4-i8» через override версии, и Vulkan-слой, который пишет пайплайны (SPIR-V от
|
||||
dxil-spirv), содержимое буферов и размеры диспатчей. Затем — воспроизведение своим провайдером
|
||||
через тот же бенчмарк (`fsr4-bench`), с проверкой против записанного выхода.
|
||||
What is missing for an own 4.1.1 provider: the container mapping (preset, tier, pass),
|
||||
the contents of `InitializerBuffer`, the `CsTensorSizes` values, the prepass/postpass permutations, constants and
|
||||
dispatch sizes. The most reliable way is not to reverse the provider's x86 code but to record one frame:
|
||||
a small FFX API (DX12, MinGW) test program under Wine/Proton with vkd3d-proton, explicit selection of
|
||||
the "FSR4-i8" provider through a version override, and a Vulkan layer that writes the pipelines (SPIR-V from
|
||||
dxil-spirv), buffer contents and dispatch sizes. Then replay with an own provider
|
||||
through the same benchmark (`fsr4-bench`), checked against the recorded output.
|
||||
|
||||
## 2026-10-01: FSR 4.1.1 в порту (upscaler=fsr411)
|
||||
## 2026-10-01: FSR 4.1.1 in the port (upscaler=fsr411)
|
||||
|
||||
Модель AMD 4.1.1 (INT8) работает нативно в Vulkan и совпадает с DLL до бита.
|
||||
The AMD 4.1.1 (INT8) model runs natively in Vulkan and matches the DLL bit for bit.
|
||||
|
||||
**Как получено.** `tools/fsr4cap/fsr4cap.exe` (C, MinGW) вызывает FidelityFX API 2.3 из
|
||||
`amd_fidelityfx_loader_dx12.dll` с явной версией 4.1.1 и через подмену таблиц методов D3D12
|
||||
записывает всё, что делает DLL под vkd3d-proton: пайплайны (DXIL), корневые сигнатуры,
|
||||
дескрипторы, загрузки, константы, диспатчи. Кадр — 29 диспатчей: SPD авто-экспозиции, prepass,
|
||||
pass0_post, проходы модели 1..12 (за каждым `_post`, обнуление рамки тензора), postpass, RCAS.
|
||||
- Моделей две: m0 (Native..Performance) и m1 (Ultra Performance, соотношение 3.0), веса — 128 КБ
|
||||
(`InitializerBuffer`). Уровни: t1080 (вывод до 1920x1080), t2160 (больше, вкл. 2560x1080).
|
||||
- Тензоры считаются от вывода, выровненного до 8; таблица `CsTensorSizes` (17 размеров), число
|
||||
групп и константы (MLSR как у v07, но width/height не выровнены и `inv_scale = 1 / scale`;
|
||||
SPD; RCAS) проверяются `extract.py` на 20 записях.
|
||||
- dxil-spirv переводит DXIL так же, как vkd3d-proton: `--mixed-float-dot-product`
|
||||
(`VK_VALVE_shader_mixed_float_dot_product`, dot2 по half с накоплением во float) — без него
|
||||
PSNR 40 дБ вместо точного совпадения. `--class-bindings` (патч) раскладывает регистры SRV/UAV/
|
||||
CBV/сэмплеров по биндингам +0/+32/+64/+96, рантайм привязывает ресурсы по именам.
|
||||
- `fsr411/fsr411.cpp` — независимое ядро на Vulkan (игра и `fsr4-bench --fsr411`).
|
||||
- `verify.sh`: DLL и воспроизведение на одинаковых входах, 8 кадров — побитово одинаково для
|
||||
1080p (обе модели), 1440p, 2560x1080, 4K (обе модели). 1600x900 расходится только у правого
|
||||
края, где сама DLL недетерминирована (гонка в её шейдерах при ширине тензора не кратной 64).
|
||||
**How it was obtained.** `tools/fsr4cap/fsr4cap.exe` (C, MinGW) calls the FidelityFX API 2.3 from
|
||||
`amd_fidelityfx_loader_dx12.dll` with an explicit version 4.1.1 and, by replacing D3D12 method tables,
|
||||
records everything the DLL does under vkd3d-proton: pipelines (DXIL), root signatures,
|
||||
descriptors, uploads, constants, dispatches. (Since then fsr4cap.exe loads AMD's upscaler DLL directly,
|
||||
which exports the API itself; the loader is only needed for a DLL that does not.) A frame is 29 dispatches: auto-exposure SPD, prepass,
|
||||
pass0_post, model passes 1..12 (each followed by `_post`, which clears the tensor border), postpass, RCAS.
|
||||
- There are two models: m0 (Native..Performance) and m1 (Ultra Performance, ratio 3.0); weights are 128 KB
|
||||
(`InitializerBuffer`). Tiers: t1080 (output up to 1920x1080), t2160 (larger, incl. 2560x1080).
|
||||
- Tensors are computed from the output aligned to 8; the `CsTensorSizes` table (17 sizes), the group
|
||||
counts and the constants (MLSR as in v07, but width/height are not aligned and `inv_scale = 1 / scale`;
|
||||
SPD; RCAS) are checked by `extract.py` on 20 recordings.
|
||||
- dxil-spirv translates DXIL the same way as vkd3d-proton: `--mixed-float-dot-product`
|
||||
(`VK_VALVE_shader_mixed_float_dot_product`, half dot2 with float accumulation); without it
|
||||
PSNR is 40 dB instead of an exact match. `--class-bindings` (a patch) lays out SRV/UAV/
|
||||
CBV/sampler registers at bindings +0/+32/+64/+96; the runtime binds resources by name.
|
||||
- `fsr411/fsr411.cpp` is an independent Vulkan core (the game and `fsr4-bench --fsr411`).
|
||||
- `verify.sh`: DLL and replay on identical inputs, 8 frames, bit-identical for
|
||||
1080p (both models), 1440p, 2560x1080, 4K (both models). 1600x900 differs only at the right
|
||||
edge, where the DLL itself is nondeterministic (a race in its shaders when the tensor width is not a multiple of 64).
|
||||
|
||||
**Ускорение.** postpass переписан через workgroup memory на уровне SPIR-V-ассемблера
|
||||
(`postpass_lds.py`; перевод через GLSL неточен: теряются `DenormPreserve` и точная расстановка
|
||||
`RelaxedPrecision`, и ещё что-то меняет результат): 2.18 → 0.95 мс в 4K, побитово как DLL.
|
||||
Весь FSR 4.1.1: 1.09 мс при 1080p (1280x720), 4.2 мс при 4K Balanced. В игре (4K Balanced)
|
||||
апскейлер 4.12 мс против 4.32 мс у ускоренного v07.
|
||||
**Speedup.** postpass is rewritten to use workgroup memory at the SPIR-V assembly level
|
||||
(`postpass_lds.py`; going through GLSL is inexact: `DenormPreserve` and the exact placement of
|
||||
`RelaxedPrecision` are lost, and something else changes the result too): 2.18 → 0.95 ms at 4K, bit-identical to the DLL.
|
||||
All of FSR 4.1.1: 1.09 ms at 1080p (1280x720), 4.2 ms at 4K Balanced. In game (4K Balanced)
|
||||
the upscaler takes 4.12 ms against 4.32 ms for the accelerated v07.
|
||||
|
||||
**Сборка ассетов** (пользователем, из своих DLL; в репозитории ничего от AMD нет):
|
||||
**Building the assets** (by the user, from their own DLLs; the repository contains nothing from AMD):
|
||||
|
||||
bash tools/fsr4cap/build_assets.sh <amd_fidelityfx_upscaler_dx12.dll 4.1.x> <amd_fidelityfx_loader_dx12.dll 2.3.x>
|
||||
bash tools/fsr4cap/build_assets.sh <amd_fidelityfx_upscaler_dx12.dll 4.1.x> [<amd_fidelityfx_loader_dx12.dll 2.3.x>]
|
||||
|
||||
Нужны nix-shell (или MinGW, cmake, ninja, python3, SPIRV-Tools, umu-run), сеть для dxil-spirv и
|
||||
заголовков FidelityFX SDK и Proton (GE-Proton). DLL есть, например, у OptiScaler
|
||||
(`FSR4_LATEST/`) и в играх с FSR 3.1/4. Сборка с нуля даёт те же файлы; `VERIFY=1` в конце
|
||||
сравнивает с DLL.
|
||||
Needs nix-shell (or MinGW, cmake, ninja, python3, SPIRV-Tools, git), network access for dxil-spirv, and
|
||||
Proton (PROTONPATH, or GE-Proton 10+ / Proton-CachyOS / Proton Experimental; umu-run or Steam's runtime).
|
||||
In the AppImage the tools are prebuilt (`BB_FSR4CAP_TOOLS`). The DLLs are available, for example, from OptiScaler
|
||||
(`FSR4_LATEST/`) and in games with FSR 3.1/4. A build from scratch produces the same files; `VERIFY=1` at the end
|
||||
compares against the DLL.
|
||||
|
||||
**Поправка к A/B выше («кадр упирается в CPU»).** Те замеры шли, пока 12 зависших процессов
|
||||
shadPS4 занимали по ядру. На свободной системе: «host copies» 1.6%, ожидание GPU ~41% — кадр
|
||||
упирается в GPU, так что async compute для FSR снова имеет смысл.
|
||||
**Correction to the A/B above ("the frame is CPU-bound").** Those measurements ran while 12 hung shadPS4
|
||||
processes each occupied a core. On an idle system: "host copies" 1.6%, waiting for the GPU ~41%; the frame
|
||||
is GPU-bound, so async compute for FSR makes sense again.
|
||||
+263
-75
@@ -27,9 +27,22 @@ if (BB_LTO)
|
||||
message(WARNING "LTO unavailable: ${bb_ipo_error}")
|
||||
endif()
|
||||
endif()
|
||||
if (BB_PGO STREQUAL "generate")
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND BB_PGO STREQUAL "generate")
|
||||
# Clang (the Windows build, llvm-mingw): instrumented, the profile written to BB_PGO_FILE's
|
||||
# directory as bbport-%p.profraw (merged with llvm-profdata into BB_PGO_FILE for "use").
|
||||
add_compile_options(-fprofile-instr-generate -fprofile-update=atomic)
|
||||
add_link_options(-fprofile-instr-generate)
|
||||
add_compile_definitions(BB_PGO_GENERATE BB_PGO_CLANG)
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND BB_PGO STREQUAL "use")
|
||||
set(BB_PGO_FILE "${BB_PGO_DIR}/bbport.profdata" CACHE FILEPATH "Merged Clang profile")
|
||||
add_compile_options(-fprofile-instr-use=${BB_PGO_FILE} -Wno-profile-instr-out-of-date
|
||||
-Wno-profile-instr-unprofiled -Wno-backend-plugin)
|
||||
elseif (BB_PGO STREQUAL "generate")
|
||||
# Atomic counters: the GPU, recording, preparation and copy threads run the same code.
|
||||
add_compile_options(-fprofile-generate=${BB_PGO_DIR} -fprofile-update=atomic)
|
||||
# Profile files named relative to the build directory (not its absolute path): moving the
|
||||
# checkout (game_files/native_probe -> bbport) left a profile nothing matched, silently.
|
||||
add_compile_options(-fprofile-generate=${BB_PGO_DIR} -fprofile-update=atomic
|
||||
-fprofile-prefix-path=${CMAKE_BINARY_DIR})
|
||||
add_link_options(-fprofile-generate=${BB_PGO_DIR})
|
||||
add_compile_definitions(BB_PGO_GENERATE)
|
||||
elseif (BB_PGO STREQUAL "use")
|
||||
@@ -37,24 +50,57 @@ elseif (BB_PGO STREQUAL "use")
|
||||
# threads can be slightly inconsistent (correction). Functions changed since the profile
|
||||
# (or built differently for it, bbgpu_init) are compiled without it.
|
||||
add_compile_options(-fprofile-use=${BB_PGO_DIR} -fprofile-partial-training
|
||||
-fprofile-prefix-path=${CMAKE_BINARY_DIR}
|
||||
-fprofile-correction -Wno-missing-profile -Wno-coverage-mismatch)
|
||||
endif()
|
||||
|
||||
find_package(Vulkan REQUIRED)
|
||||
find_package(fmt REQUIRED)
|
||||
find_package(Boost REQUIRED)
|
||||
find_package(magic_enum REQUIRED)
|
||||
find_package(magic_enum QUIET)
|
||||
if(NOT magic_enum_FOUND)
|
||||
# Header-only; packaged on few systems (the Windows cross build's MSYS2 has none).
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(magic_enum GIT_REPOSITORY https://github.com/Neargye/magic_enum.git GIT_TAG v0.9.7)
|
||||
FetchContent_MakeAvailable(magic_enum)
|
||||
endif()
|
||||
find_package(tsl-robin-map REQUIRED)
|
||||
find_package(VulkanMemoryAllocator CONFIG REQUIRED)
|
||||
find_package(VulkanMemoryAllocator CONFIG QUIET)
|
||||
if (NOT TARGET GPUOpen::VulkanMemoryAllocator)
|
||||
# The header alone, without its CMake package (MSYS2: include/vma/vk_mem_alloc.h).
|
||||
find_path(VMA_INCLUDE_DIR vk_mem_alloc.h PATH_SUFFIXES vma REQUIRED)
|
||||
add_library(GPUOpen::VulkanMemoryAllocator INTERFACE IMPORTED)
|
||||
target_include_directories(GPUOpen::VulkanMemoryAllocator INTERFACE ${VMA_INCLUDE_DIR})
|
||||
endif()
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(XXHASH REQUIRED IMPORTED_TARGET libxxhash)
|
||||
pkg_check_modules(SDL3 REQUIRED IMPORTED_TARGET sdl3)
|
||||
pkg_check_modules(X11 REQUIRED IMPORTED_TARGET x11)
|
||||
find_package(miniz REQUIRED)
|
||||
if (NOT WIN32)
|
||||
pkg_check_modules(X11 REQUIRED IMPORTED_TARGET x11)
|
||||
endif()
|
||||
find_package(miniz QUIET)
|
||||
if(NOT miniz_FOUND)
|
||||
# Distributions without a miniz package (Debian, Ubuntu: #136): built from its release.
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(miniz GIT_REPOSITORY https://github.com/richgel999/miniz.git GIT_TAG 3.1.2)
|
||||
FetchContent_MakeAvailable(miniz)
|
||||
if(NOT TARGET miniz::miniz)
|
||||
add_library(miniz::miniz ALIAS miniz)
|
||||
endif()
|
||||
endif()
|
||||
find_package(Zydis REQUIRED)
|
||||
find_program(GLSLANG_COMPILER NAMES glslang glslangValidator REQUIRED)
|
||||
|
||||
set(SIRIT_USE_SYSTEM_SPIRV_HEADERS OFF CACHE BOOL "" FORCE)
|
||||
if (WIN32)
|
||||
# Every target, the third-party ones too: what glibc and MSVC have and MinGW does not.
|
||||
include_directories(BEFORE platform/win32/include)
|
||||
enable_language(RC)
|
||||
# Bit-fields laid out as on Linux (GCC's way), not MSVC's: the GPU registers and the PS4's
|
||||
# structures are declared with them.
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mno-ms-bitfields>)
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:C,CXX>:SHELL:-include ${CMAKE_CURRENT_SOURCE_DIR}/platform/win32/include/bbport_win32.h>")
|
||||
endif()
|
||||
add_subdirectory(third_party/sirit EXCLUDE_FROM_ALL)
|
||||
|
||||
# bbport: FSR 3.1 temporal upscaling / frame generation, native Vulkan (MIT, FireBurn/FSR-Vulkan).
|
||||
@@ -62,77 +108,129 @@ set(FFX_VK_PORTABLE_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(FFX_VK_PORTABLE_BUILD_PROBE OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(third_party/fsr-vulkan EXCLUDE_FROM_ALL)
|
||||
|
||||
# Host shaders -> headers (same scheme as shadPS4's host_shaders/CMakeLists.txt).
|
||||
set(SHADER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/shadps4/video_core/host_shaders)
|
||||
# Host shaders -> headers: SPIR-V arrays from glslang, in bbgpu/shaders/.
|
||||
set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
|
||||
set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders)
|
||||
set(SHADER_HEADERS "")
|
||||
function(add_host_shader FILENAME SHADER_NAME)
|
||||
set(OUT ${SHADER_DIR}/${SHADER_NAME}.h)
|
||||
function(add_shader_header SOURCE OUT_DIR SHADER_NAME)
|
||||
set(OUT ${OUT_DIR}/${SHADER_NAME}.h)
|
||||
string(TOUPPER ${SHADER_NAME} SYMBOL)
|
||||
set(DEFINES ${ARGN})
|
||||
list(TRANSFORM DEFINES PREPEND "-D")
|
||||
add_custom_command(OUTPUT ${OUT}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADER_DIR}
|
||||
COMMAND ${GLSLANG_COMPILER} --target-env vulkan1.3 -o ${OUT} --vn ${SYMBOL} ${DEFINES} ${SHADER_SRC}/${FILENAME}
|
||||
COMMAND ${CMAKE_COMMAND} "-DHEADER_FILE=${OUT}" -P ${SHADER_SRC}/AddShaderHeaderInclude.cmake
|
||||
DEPENDS ${SHADER_SRC}/${FILENAME} VERBATIM)
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${OUT_DIR}
|
||||
COMMAND ${GLSLANG_COMPILER} --target-env vulkan1.3 -o ${OUT} --vn ${SYMBOL} ${DEFINES}
|
||||
-I${CMAKE_CURRENT_SOURCE_DIR}/third_party/amd-fsr1 ${SOURCE}
|
||||
COMMAND ${CMAKE_COMMAND} "-DHEADER_FILE=${OUT}" -P ${CMAKE_CURRENT_SOURCE_DIR}/render/shaders/header.cmake
|
||||
DEPENDS ${SOURCE} VERBATIM)
|
||||
set(SHADER_HEADERS ${SHADER_HEADERS} ${OUT} PARENT_SCOPE)
|
||||
endfunction()
|
||||
add_host_shader(color_to_ms_depth.frag color_to_ms_depth_frag)
|
||||
add_host_shader(ms_image_blit.frag ms_image_blit_frag)
|
||||
add_host_shader(ms_image_blit.frag ms_image_blit_msaa_frag SRC_MSAA=1)
|
||||
add_host_shader(fault_buffer_process.comp fault_buffer_process_comp)
|
||||
add_host_shader(fs_tri.vert fs_tri_vert)
|
||||
add_host_shader(fsr.comp fsr_easu_comp SAMPLE_EASU=1)
|
||||
add_host_shader(fsr.comp fsr_rcas_comp SAMPLE_RCAS=1)
|
||||
add_host_shader(post_process.frag post_process_frag)
|
||||
add_host_shader(camera_motion.comp camera_motion_comp)
|
||||
add_host_shader(upscale_merge.comp upscale_merge_comp)
|
||||
add_host_shader(upscale_reactive.comp upscale_reactive_comp)
|
||||
add_host_shader(taa.comp taa_comp)
|
||||
add_host_shader(taa_sharpen.comp taa_sharpen_comp)
|
||||
add_host_shader(taa_sharpen.comp taa_sharpen_ldr_comp LDR_OUTPUT=1)
|
||||
add_host_shader(fsr4_color.comp fsr4_decode_comp)
|
||||
add_host_shader(fsr4_color.comp fsr4_encode_comp ENCODE=1)
|
||||
add_host_shader(fsr4_reactive.comp fsr4_reactive_comp)
|
||||
add_host_shader(buffer_multi_copy.comp buffer_multi_copy_comp)
|
||||
add_host_shader(occlusion_resolve.comp occlusion_resolve_comp)
|
||||
add_host_shader(gpu_timestamp.comp gpu_timestamp_comp)
|
||||
add_host_shader(indirect_guard.comp indirect_guard_comp)
|
||||
add_host_shader(depth_resample.frag depth_resample_frag)
|
||||
add_host_shader(depth_resample.frag depth_stencil_resample_frag HAS_STENCIL=1)
|
||||
add_host_shader(depth_resample.frag depth_stencil_bits_frag STENCIL_BITS=1)
|
||||
add_host_shader(camera_motion.comp camera_motion_debug_comp DEBUG_OVERLAY=1)
|
||||
set(OWN_SHADER_DIR ${SHADER_INCLUDE}/bbgpu/shaders)
|
||||
function(add_own_shader FILENAME SHADER_NAME)
|
||||
add_shader_header(${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME} ${OWN_SHADER_DIR} ${SHADER_NAME} ${ARGN})
|
||||
set(SHADER_HEADERS ${SHADER_HEADERS} PARENT_SCOPE)
|
||||
endfunction()
|
||||
add_own_shader(render/shaders/depth_from_color.frag depth_from_color_frag)
|
||||
add_own_shader(render/shaders/texel_copy.frag texel_copy_frag)
|
||||
add_own_shader(render/shaders/texel_copy.frag texel_copy_msaa_frag MSAA_SOURCE=1)
|
||||
add_own_shader(buffers/shaders/fault_scan.comp fault_scan_comp)
|
||||
add_own_shader(render/shaders/fullscreen.vert fullscreen_vert)
|
||||
add_own_shader(render/shaders/present_encode.frag present_encode_frag)
|
||||
add_own_shader(upscale/shaders/camera_motion.comp camera_motion_comp)
|
||||
# GT-VBAO in place of the game's SSAO (upscale/vk_vbao.cpp, docs/vbao.md; contributed by redditor69).
|
||||
add_own_shader(upscale/shaders/vbao.comp vbao_comp)
|
||||
add_own_shader(upscale/shaders/vbao_blur.comp vbao_blur_comp)
|
||||
add_own_shader(upscale/shaders/vbao_blur.comp vbao_blur_rgba_comp OUTPUT_RGBA=1)
|
||||
add_own_shader(upscale/shaders/vbao_view.comp vbao_view_comp)
|
||||
add_own_shader(upscale/shaders/upscale_merge.comp upscale_merge_comp)
|
||||
add_own_shader(upscale/shaders/upscale_reactive.comp upscale_reactive_comp)
|
||||
add_own_shader(upscale/shaders/taa.comp taa_comp)
|
||||
add_own_shader(upscale/shaders/taa_sharpen.comp taa_sharpen_comp)
|
||||
add_own_shader(upscale/shaders/fsr1_easu.comp fsr1_easu_comp)
|
||||
add_own_shader(upscale/shaders/taa_sharpen.comp taa_sharpen_ldr_comp LDR_OUTPUT=1)
|
||||
add_own_shader(upscale/shaders/fsr4_color.comp fsr4_decode_comp)
|
||||
add_own_shader(upscale/shaders/fsr4_color.comp fsr4_encode_comp ENCODE=1)
|
||||
add_own_shader(upscale/shaders/fsr4_reactive.comp fsr4_reactive_comp)
|
||||
add_own_shader(upscale/shaders/fg_display_lut.comp fg_display_lut_comp)
|
||||
# SMAA 1x for anti_aliasing=smaa (upscale/vk_smaa.cpp; third_party/smaa, MIT).
|
||||
add_own_shader(upscale/shaders/smaa.comp smaa_edges_comp PASS=0)
|
||||
add_own_shader(upscale/shaders/smaa.comp smaa_weights_comp PASS=1)
|
||||
add_own_shader(upscale/shaders/smaa.comp smaa_blend_comp PASS=2)
|
||||
# bbport: FSR 3.1 frame generation passes compiled here (ffx_vk.cpp ffxVkShaderOverride): the SDK's
|
||||
# GLSL with its own headers, as FidelityFX_SC builds them (-Os, Vulkan 1.2), changed where faster.
|
||||
set(FFX_GPU_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/fsr-vulkan/upstream/ffx-1.1.4/sdk/include/FidelityFX/gpu)
|
||||
# The port's changed copies of SDK headers: a change to one rebuilds every pass.
|
||||
file(GLOB_RECURSE FFX_OWN_HEADERS CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/upscale/shaders/ffx/*.h)
|
||||
function(add_ffx_shader FILENAME SHADER_NAME)
|
||||
set(OUT ${OWN_SHADER_DIR}/${SHADER_NAME}.h)
|
||||
string(TOUPPER ${SHADER_NAME} SYMBOL)
|
||||
set(DEFINES ${ARGN})
|
||||
list(TRANSFORM DEFINES PREPEND "-D")
|
||||
add_custom_command(OUTPUT ${OUT}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${OWN_SHADER_DIR}
|
||||
COMMAND ${GLSLANG_COMPILER} --target-env vulkan1.2 -S comp -Os -o ${OUT} --vn ${SYMBOL}
|
||||
-DFFX_GPU=1 -DFFX_GLSL=1 ${DEFINES} -I${CMAKE_CURRENT_SOURCE_DIR}/upscale/shaders/ffx -I${FFX_GPU_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME}
|
||||
COMMAND ${CMAKE_COMMAND} "-DHEADER_FILE=${OUT}" -P ${CMAKE_CURRENT_SOURCE_DIR}/render/shaders/header.cmake
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME} ${FFX_OWN_HEADERS} VERBATIM)
|
||||
set(SHADER_HEADERS ${SHADER_HEADERS} ${OUT} PARENT_SCOPE)
|
||||
endfunction()
|
||||
add_ffx_shader(upscale/shaders/ffx/of_v5.comp ffx_of_v5 FFX_HALF=0 FFX_OPTICALFLOW_OPTION_HDR_COLOR_INPUT=0)
|
||||
add_ffx_shader(upscale/shaders/ffx/of_v5.comp ffx_of_v5_hdr FFX_HALF=0 FFX_OPTICALFLOW_OPTION_HDR_COLOR_INPUT=1)
|
||||
add_ffx_shader(upscale/shaders/ffx/of_v5.comp ffx_of_v5_16 FFX_HALF=1 FFX_OPTICALFLOW_OPTION_HDR_COLOR_INPUT=0)
|
||||
add_ffx_shader(upscale/shaders/ffx/of_v5.comp ffx_of_v5_16_hdr FFX_HALF=1 FFX_OPTICALFLOW_OPTION_HDR_COLOR_INPUT=1)
|
||||
# BB_OF_FAST: the finest optical flow level takes the coarser one's vectors (no search).
|
||||
add_ffx_shader(upscale/shaders/ffx/of_v5.comp ffx_of_v5_fast FFX_HALF=0 FFX_OPTICALFLOW_OPTION_HDR_COLOR_INPUT=0 BB_OF_LEVEL0_PREDICT=1)
|
||||
add_ffx_shader(upscale/shaders/ffx/of_v5.comp ffx_of_v5_hdr_fast FFX_HALF=0 FFX_OPTICALFLOW_OPTION_HDR_COLOR_INPUT=1 BB_OF_LEVEL0_PREDICT=1)
|
||||
add_ffx_shader(upscale/shaders/ffx/of_v5.comp ffx_of_v5_16_fast FFX_HALF=1 FFX_OPTICALFLOW_OPTION_HDR_COLOR_INPUT=0 BB_OF_LEVEL0_PREDICT=1)
|
||||
add_ffx_shader(upscale/shaders/ffx/of_v5.comp ffx_of_v5_16_hdr_fast FFX_HALF=1 FFX_OPTICALFLOW_OPTION_HDR_COLOR_INPUT=1 BB_OF_LEVEL0_PREDICT=1)
|
||||
add_ffx_shader(upscale/shaders/ffx/fi_interpolation.comp ffx_fi_interpolation FFX_HALF=0)
|
||||
add_ffx_shader(upscale/shaders/ffx/fi_interpolation.comp ffx_fi_interpolation_check FFX_HALF=0 BB_FI_HARDWARE_BILINEAR=2)
|
||||
add_own_shader(render/shaders/buffer_multi_copy.comp buffer_multi_copy_comp)
|
||||
add_own_shader(render/shaders/atlas_decode.comp atlas_decode_comp)
|
||||
add_own_shader(render/shaders/occlusion_resolve.comp occlusion_resolve_comp)
|
||||
add_own_shader(render/shaders/gpu_timestamp.comp gpu_timestamp_comp)
|
||||
add_own_shader(render/shaders/indirect_guard.comp indirect_guard_comp)
|
||||
add_own_shader(render/shaders/indirect_draw_guard.comp indirect_draw_guard_comp)
|
||||
add_own_shader(upscale/shaders/depth_resample.frag depth_resample_frag)
|
||||
add_own_shader(upscale/shaders/depth_resample.frag depth_stencil_resample_frag HAS_STENCIL=1)
|
||||
add_own_shader(upscale/shaders/depth_resample.frag depth_stencil_bits_frag STENCIL_BITS=1)
|
||||
add_own_shader(upscale/shaders/camera_motion.comp camera_motion_debug_comp DEBUG_OVERLAY=1)
|
||||
foreach(BPP IN ITEMS 8 16 32 64 96 128)
|
||||
add_host_shader(tiling.comp tiling_micro_${BPP}_comp BITS_PER_PIXEL=${BPP} IS_MACRO_TILED=0)
|
||||
add_host_shader(tiling.comp tiling_macro_${BPP}_comp BITS_PER_PIXEL=${BPP} IS_MACRO_TILED=1)
|
||||
add_own_shader(textures/shaders/detile.comp detile_micro_${BPP}_comp BITS_PER_PIXEL=${BPP} IS_MACRO_TILED=0)
|
||||
add_own_shader(textures/shaders/detile.comp detile_macro_${BPP}_comp BITS_PER_PIXEL=${BPP} IS_MACRO_TILED=1)
|
||||
endforeach()
|
||||
add_custom_target(host_shaders DEPENDS ${SHADER_HEADERS})
|
||||
|
||||
file(GLOB_RECURSE VIDEO_CORE CONFIGURE_DEPENDS shadps4/video_core/*.cpp)
|
||||
file(GLOB_RECURSE RECOMPILER CONFIGURE_DEPENDS shadps4/shader_recompiler/*.cpp)
|
||||
list(FILTER VIDEO_CORE EXCLUDE REGEX "renderdoc\\.cpp$")
|
||||
set(COMMON
|
||||
shadps4/common/decoder.cpp shadps4/common/error.cpp
|
||||
shadps4/common/io_file.cpp shadps4/common/native_clock.cpp shadps4/common/number_utils.cpp
|
||||
shadps4/common/path_util.cpp shadps4/common/rdtsc.cpp shadps4/common/spin_lock.cpp shadps4/common/recursive_lock.cpp
|
||||
shadps4/common/string_util.cpp shadps4/common/thread.cpp shadps4/common/signal_context.cpp
|
||||
shim/common/logging/log.cpp)
|
||||
file(GLOB AVPLAYER CONFIGURE_DEPENDS shadps4/core/libraries/avplayer/*.cpp)
|
||||
file(GLOB_RECURSE RECOMPILER CONFIGURE_DEPENDS shader/*.cpp)
|
||||
# Own utilities (types, logging, paths, locks, containers): common/.
|
||||
file(GLOB COMMON CONFIGURE_DEPENDS common/*.cpp common/logging/*.cpp)
|
||||
# Own system libraries (Gnm, VideoOut, AvPlayer, event queues, interrupts): platform/.
|
||||
file(GLOB_RECURSE PLATFORM_SOURCES CONFIGURE_DEPENDS platform/*.cpp)
|
||||
# platform/win32: the POSIX subset over Win32 (with its headers in platform/win32/include).
|
||||
if (NOT WIN32)
|
||||
list(FILTER PLATFORM_SOURCES EXCLUDE REGEX "/platform/win32/")
|
||||
endif()
|
||||
pkg_check_modules(FFMPEG REQUIRED IMPORTED_TARGET libavformat libavcodec libavutil libswscale libswresample)
|
||||
file(GLOB SHIM_SOURCES CONFIGURE_DEPENDS shim/*.cpp shim/*/*.cpp)
|
||||
# The layer's own modules (independent of the shadPS4 code): layer/.
|
||||
file(GLOB LAYER_SOURCES CONFIGURE_DEPENDS layer/*.cpp)
|
||||
# Game profiles (what the translator knows about one game, out of its core): games/.
|
||||
file(GLOB GAME_SOURCES CONFIGURE_DEPENDS games/*.cpp)
|
||||
file(GLOB GAME_SOURCES CONFIGURE_DEPENDS games/*.cpp games/*/*.cpp)
|
||||
# The translator's own command processor (docs/TRANSLATOR_ARCHITECTURE.ru.md, step 2): cp/.
|
||||
file(GLOB CP_SOURCES CONFIGURE_DEPENDS cp/*.cpp)
|
||||
add_library(bbgpu SHARED ${VIDEO_CORE} ${RECOMPILER} ${COMMON} ${SHIM_SOURCES} ${LAYER_SOURCES} ${GAME_SOURCES} ${CP_SOURCES}
|
||||
shadps4/core/libraries/gnmdriver/gnmdriver.cpp
|
||||
shadps4/core/libraries/videoout/driver.cpp shadps4/core/libraries/videoout/video_out.cpp
|
||||
shadps4/core/libraries/kernel/equeue.cpp ${AVPLAYER}
|
||||
shadps4/core/libraries/videodec/video_utils.cpp)
|
||||
file(GLOB CP_SOURCES CONFIGURE_DEPENDS cp/*.cpp memory/*.cpp gcn/*.cpp textures/*.cpp buffers/*.cpp vulkan/*.cpp)
|
||||
# The translator's rendering pieces (labels, timestamps, occlusion, draw preparation, profiling,
|
||||
# frame capture, prompts): render/. Temporal upscaling, motion vectors, scene resolution: upscale/.
|
||||
file(GLOB RENDER_SOURCES CONFIGURE_DEPENDS render/*.cpp)
|
||||
file(GLOB UPSCALE_SOURCES CONFIGURE_DEPENDS upscale/*.cpp upscale/fsr411/*.cpp)
|
||||
# Windows: linked into bb-probe.exe (a DLL cannot leave the runtime's symbols to the executable
|
||||
# that loads it, as -Wl,--allow-shlib-undefined does on Linux).
|
||||
if (WIN32)
|
||||
set(BBGPU_LIBRARY_TYPE STATIC)
|
||||
else()
|
||||
set(BBGPU_LIBRARY_TYPE SHARED)
|
||||
endif()
|
||||
add_library(bbgpu ${BBGPU_LIBRARY_TYPE} ${RECOMPILER} ${COMMON} ${PLATFORM_SOURCES} ${SHIM_SOURCES} ${LAYER_SOURCES} ${GAME_SOURCES} ${CP_SOURCES}
|
||||
${RENDER_SOURCES} ${UPSCALE_SOURCES})
|
||||
add_dependencies(bbgpu host_shaders)
|
||||
# In-game settings menu: Dear ImGui (third_party/imgui, MIT) with its Vulkan backend, and an
|
||||
# embedded Cyrillic font (third_party/fonts).
|
||||
@@ -152,24 +250,71 @@ target_compile_definitions(bbgpu PRIVATE IMGUI_IMPL_VULKAN_NO_PROTOTYPES
|
||||
BB_FONT_PATH="${CMAKE_CURRENT_SOURCE_DIR}/third_party/fonts/DejaVuSans.ttf")
|
||||
set_source_files_properties(shim/bbport_overlay.cpp PROPERTIES
|
||||
OBJECT_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/fonts/DejaVuSans.ttf")
|
||||
target_include_directories(bbgpu PRIVATE shim layer games cp shadps4 ${SHADER_INCLUDE} third_party/gcn/include third_party/half/include)
|
||||
target_include_directories(bbgpu PRIVATE . shim layer games cp ${SHADER_INCLUDE} third_party/gcn/include third_party/half/include)
|
||||
if (WIN32)
|
||||
target_include_directories(bbgpu PUBLIC platform/win32/include)
|
||||
endif()
|
||||
# Hunter's Atlas (shim/bbport_map*.cpp) reads its map packs with nlohmann/json (header only, MIT).
|
||||
# bbport (Linux fork): the system package the online module (bbnet20) builds with.
|
||||
find_package(nlohmann_json CONFIG REQUIRED)
|
||||
target_link_libraries(bbgpu PRIVATE nlohmann_json::nlohmann_json)
|
||||
target_compile_definitions(bbgpu PRIVATE BOOST_ASIO_STANDALONE NDEBUG)
|
||||
target_compile_options(bbgpu PRIVATE -Wno-deprecated-declarations -Wno-missing-field-initializers)
|
||||
# Loaded with bb-probe at startup (DT_NEEDED, never dlopen): thread_local access without a
|
||||
# TLS descriptor call (_dl_tlsdesc_return was ~2% of the draw recording thread).
|
||||
target_compile_options(bbgpu PRIVATE -ftls-model=initial-exec)
|
||||
# Runtime symbols (runtime_memory_*, runtime_process_*) resolve against bb-probe.
|
||||
target_link_options(bbgpu PRIVATE -Wl,--allow-shlib-undefined)
|
||||
if (NOT WIN32)
|
||||
target_compile_options(bbgpu PRIVATE -ftls-model=initial-exec)
|
||||
# Runtime symbols (runtime_memory_*, runtime_process_*) resolve against bb-probe.
|
||||
target_link_options(bbgpu PRIVATE -Wl,--allow-shlib-undefined)
|
||||
endif()
|
||||
target_link_libraries(bbgpu PRIVATE ffx-vulkan::portable ffx-vulkan::fsr4-v07-vulkan)
|
||||
# Trophy names: trophy00.trp decrypted with the user's trophy key (shim/bbport_trophies.cpp).
|
||||
# Without OpenSSL the unlocks are kept and shown by number.
|
||||
find_package(OpenSSL QUIET COMPONENTS Crypto)
|
||||
if (OpenSSL_FOUND)
|
||||
target_link_libraries(bbgpu PRIVATE OpenSSL::Crypto)
|
||||
target_compile_definitions(bbgpu PRIVATE BB_TROPHY_DECRYPT=1)
|
||||
else()
|
||||
message(STATUS "Trophy names: off (OpenSSL's libcrypto missing; unlocks shown by number)")
|
||||
endif()
|
||||
target_link_libraries(bbgpu PUBLIC Vulkan::Headers fmt::fmt Boost::headers magic_enum::magic_enum
|
||||
tsl::robin_map GPUOpen::VulkanMemoryAllocator PkgConfig::XXHASH PkgConfig::SDL3 PkgConfig::X11 PkgConfig::FFMPEG miniz::miniz Zydis::Zydis sirit)
|
||||
tsl::robin_map GPUOpen::VulkanMemoryAllocator PkgConfig::XXHASH PkgConfig::SDL3 PkgConfig::FFMPEG miniz::miniz Zydis::Zydis sirit)
|
||||
if (NOT WIN32)
|
||||
target_link_libraries(bbgpu PUBLIC PkgConfig::X11)
|
||||
endif()
|
||||
|
||||
# Windows: the runtime (src/, C) and this library in one bb-probe.exe (on Linux build.sh links the
|
||||
# runtime against libbbgpu.so). Below 1 TiB stay the host heap and thread stacks the game packs
|
||||
# into 40-bit fields: no high-entropy address randomization.
|
||||
if (WIN32)
|
||||
file(GLOB BB_RUNTIME_SOURCES CONFIGURE_DEPENDS ../src/runtime*.c)
|
||||
file(GLOB BB_ATRAC9_SOURCES CONFIGURE_DEPENDS ../third_party/LibAtrac9/C/src/*.c)
|
||||
add_library(bbatrac9 STATIC ${BB_ATRAC9_SOURCES})
|
||||
target_compile_options(bbatrac9 PRIVATE -w)
|
||||
add_executable(bb-probe ../src/probe.c ../src/vulkan_smoke.c ${BB_RUNTIME_SOURCES}
|
||||
platform/win32/bb-probe.rc)
|
||||
set_target_properties(bb-probe PROPERTIES C_STANDARD 11 C_EXTENSIONS ON)
|
||||
target_include_directories(bb-probe PRIVATE .. ../src)
|
||||
target_compile_options(bb-probe PRIVATE $<$<COMPILE_LANGUAGE:C>:-Wall -Wextra>)
|
||||
target_link_libraries(bb-probe PRIVATE bbgpu bbatrac9 pthread ws2_32 bcrypt dbghelp winmm onecore
|
||||
${Vulkan_LIBRARIES})
|
||||
# Plain -l names in the packages' CMake files (Zydis: Zycore) are found in the sysroot.
|
||||
foreach(root ${CMAKE_FIND_ROOT_PATH})
|
||||
target_link_directories(bb-probe PRIVATE ${root}/lib)
|
||||
endforeach()
|
||||
target_link_options(bb-probe PRIVATE -Wl,--disable-high-entropy-va
|
||||
-Wl,--stack,16777216 -Wl,--error-limit=0)
|
||||
endif()
|
||||
|
||||
# Explicit test targets; neither launches the game nor needs a Vulkan device.
|
||||
add_executable(trophy-test EXCLUDE_FROM_ALL ../tests/test_trophies.cpp shim/bbport_trophy_data.cpp)
|
||||
target_compile_options(trophy-test PRIVATE -UNDEBUG -Wall -Wextra -Werror)
|
||||
target_include_directories(trophy-test PRIVATE shim)
|
||||
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(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_include_directories(motion-shader-test PRIVATE . shim ${SHADER_INCLUDE} third_party/gcn/include)
|
||||
target_link_libraries(motion-shader-test PRIVATE bbgpu)
|
||||
add_executable(ui-composition-test EXCLUDE_FROM_ALL ../tests/test_ui_composition.cpp)
|
||||
target_compile_options(ui-composition-test PRIVATE -UNDEBUG -Wall -Wextra -Werror)
|
||||
@@ -177,12 +322,23 @@ target_include_directories(ui-composition-test PRIVATE ..)
|
||||
add_executable(cp-decoder-test EXCLUDE_FROM_ALL ../tests/test_cp_decoder.cpp cp/cp_decoder.cpp)
|
||||
target_compile_options(cp-decoder-test PRIVATE -UNDEBUG -Wall -Wextra -Werror)
|
||||
target_include_directories(cp-decoder-test PRIVATE cp)
|
||||
add_executable(fx-init-clear-test EXCLUDE_FROM_ALL ../tests/test_fx_init_clear.cpp cp/cp_decoder.cpp)
|
||||
target_compile_options(fx-init-clear-test PRIVATE -UNDEBUG -Wall -Wextra -Werror)
|
||||
target_include_directories(fx-init-clear-test PRIVATE . shim)
|
||||
add_executable(effect-flags-test EXCLUDE_FROM_ALL ../tests/test_effect_flags.cpp)
|
||||
target_compile_options(effect-flags-test PRIVATE -UNDEBUG -Wall -Wextra -Werror)
|
||||
target_include_directories(effect-flags-test PRIVATE shim)
|
||||
add_executable(settings-test EXCLUDE_FROM_ALL ../tests/test_settings.cpp shim/bbport_settings.cpp)
|
||||
target_compile_options(settings-test PRIVATE -UNDEBUG -Wall -Wextra -Werror)
|
||||
target_include_directories(settings-test PRIVATE .. shim)
|
||||
|
||||
# The clip-space matrices DLSS Frame Generation gets (upscale/camera_matrices.h): no GPU.
|
||||
add_executable(camera-matrices-test EXCLUDE_FROM_ALL ../tests/test_camera_matrices.cpp)
|
||||
target_compile_options(camera-matrices-test PRIVATE -UNDEBUG -Wall -Wextra -Werror)
|
||||
target_include_directories(camera-matrices-test PRIVATE ..)
|
||||
|
||||
add_executable(scene-resolution-test EXCLUDE_FROM_ALL ../tests/test_scene_resolution.cpp ../tests/test_gpu_runtime_stubs.cpp)
|
||||
target_include_directories(scene-resolution-test PRIVATE shim shadps4 third_party/gcn/include)
|
||||
target_include_directories(scene-resolution-test PRIVATE . shim ${SHADER_INCLUDE} third_party/gcn/include)
|
||||
# The test's Vulkan-Hpp inline methods have a different set of platform extensions
|
||||
# from vk_platform.cpp. Keep them local and use an explicit dispatcher in the test.
|
||||
target_compile_options(scene-resolution-test PRIVATE -UNDEBUG -fPIC)
|
||||
@@ -190,7 +346,7 @@ set_source_files_properties(../tests/test_scene_resolution.cpp PROPERTIES
|
||||
COMPILE_OPTIONS "-fvisibility=hidden;-fvisibility-inlines-hidden")
|
||||
target_link_libraries(scene-resolution-test PRIVATE bbgpu)
|
||||
add_executable(taa-shader-test EXCLUDE_FROM_ALL ../tests/test_taa_shader.cpp ../tests/test_gpu_runtime_stubs.cpp)
|
||||
target_include_directories(taa-shader-test PRIVATE shim shadps4 ${SHADER_INCLUDE} third_party/gcn/include)
|
||||
target_include_directories(taa-shader-test PRIVATE . shim ${SHADER_INCLUDE} third_party/gcn/include)
|
||||
target_compile_options(taa-shader-test PRIVATE -UNDEBUG -fPIC)
|
||||
set_source_files_properties(../tests/test_taa_shader.cpp PROPERTIES
|
||||
COMPILE_OPTIONS "-fvisibility=hidden;-fvisibility-inlines-hidden")
|
||||
@@ -198,19 +354,27 @@ target_link_libraries(taa-shader-test PRIVATE bbgpu)
|
||||
add_dependencies(taa-shader-test host_shaders)
|
||||
|
||||
add_executable(camera-motion-test EXCLUDE_FROM_ALL ../tests/test_camera_motion.cpp ../tests/test_gpu_runtime_stubs.cpp)
|
||||
target_include_directories(camera-motion-test PRIVATE shim shadps4 ${SHADER_INCLUDE} third_party/gcn/include)
|
||||
target_include_directories(camera-motion-test PRIVATE . shim ${SHADER_INCLUDE} third_party/gcn/include)
|
||||
target_compile_options(camera-motion-test PRIVATE -UNDEBUG -fPIC)
|
||||
target_link_libraries(camera-motion-test PRIVATE bbgpu)
|
||||
add_dependencies(camera-motion-test host_shaders)
|
||||
|
||||
add_executable(translation-memory-test EXCLUDE_FROM_ALL ../tests/test_translation_memory.cpp
|
||||
../tests/test_gpu_runtime_stubs.cpp)
|
||||
target_include_directories(translation-memory-test PRIVATE shim shadps4 third_party/gcn/include)
|
||||
target_include_directories(translation-memory-test PRIVATE . shim ${SHADER_INCLUDE} third_party/gcn/include)
|
||||
target_compile_options(translation-memory-test PRIVATE -UNDEBUG -fPIC)
|
||||
set_source_files_properties(../tests/test_translation_memory.cpp PROPERTIES
|
||||
COMPILE_OPTIONS "-fvisibility=hidden;-fvisibility-inlines-hidden")
|
||||
target_link_libraries(translation-memory-test PRIVATE bbgpu)
|
||||
|
||||
# Spare images on a real GPU (textures/image_spares.h): the time a texture costs the recording
|
||||
# thread with and without them, the GPU busy.
|
||||
add_executable(image-spares-test EXCLUDE_FROM_ALL ../tests/test_image_spares.cpp
|
||||
textures/image_spares.cpp vulkan/vk_common.cpp)
|
||||
target_include_directories(image-spares-test PRIVATE . shim)
|
||||
target_compile_options(image-spares-test PRIVATE -UNDEBUG)
|
||||
target_link_libraries(image-spares-test PRIVATE Vulkan::Headers GPUOpen::VulkanMemoryAllocator)
|
||||
|
||||
# bbport: FSR 4 benchmark outside the game (tools/fsr4_bench.cpp): ninja -C out/gpu fsr4-bench.
|
||||
add_executable(upscaler-support-test EXCLUDE_FROM_ALL ../tests/test_upscaler_support.cpp
|
||||
shim/bbport_settings.cpp)
|
||||
@@ -218,11 +382,11 @@ target_include_directories(upscaler-support-test PRIVATE shim)
|
||||
target_compile_options(upscaler-support-test PRIVATE -UNDEBUG)
|
||||
|
||||
add_executable(fsr4-bench EXCLUDE_FROM_ALL tools/fsr4_bench.cpp
|
||||
shadps4/video_core/renderer_vulkan/fsr411/fsr411.cpp)
|
||||
target_include_directories(fsr4-bench PRIVATE shadps4/video_core/renderer_vulkan/fsr411)
|
||||
upscale/fsr411/fsr411.cpp)
|
||||
target_include_directories(fsr4-bench PRIVATE upscale/fsr411)
|
||||
target_link_libraries(fsr4-bench PRIVATE ffx-vulkan::fsr4-v07-vulkan ffx-vulkan::fsr4-v07-assets Vulkan::Vulkan)
|
||||
|
||||
# bbport online module (gpu/bbnet, libbbnet.so): built when its libraries are there (shell.nix);
|
||||
# bbport online module (gpu/bbnet20, libbbnet20.so): built when its libraries are there (shell.nix);
|
||||
# BB_NET=OFF leaves it out. Without it the port plays offline.
|
||||
option(BB_NET "Build the online module (shadNet)" ON)
|
||||
if (BB_NET)
|
||||
@@ -234,9 +398,33 @@ if (BB_NET)
|
||||
find_library(MINIUPNPC_LIBRARY NAMES miniupnpc)
|
||||
if (Protobuf_FOUND AND nlohmann_json_FOUND AND OpenSSL_FOUND AND ZLIB_FOUND AND
|
||||
MINIUPNPC_INCLUDE AND MINIUPNPC_LIBRARY)
|
||||
add_subdirectory(bbnet)
|
||||
message(STATUS "Online module: on (libbbnet.so)")
|
||||
add_subdirectory(bbnet20)
|
||||
message(STATUS "Online module: on (libbbnet20.so)")
|
||||
else()
|
||||
message(STATUS "Online module: off (protobuf, nlohmann_json, OpenSSL, zlib or miniupnpc missing)")
|
||||
# Named one by one (#145: "the build has no online module" with no hint why).
|
||||
set(missing "")
|
||||
foreach(dep Protobuf nlohmann_json OpenSSL ZLIB)
|
||||
if (NOT ${dep}_FOUND)
|
||||
list(APPEND missing ${dep})
|
||||
endif()
|
||||
endforeach()
|
||||
if (NOT MINIUPNPC_INCLUDE OR NOT MINIUPNPC_LIBRARY)
|
||||
list(APPEND missing miniupnpc)
|
||||
endif()
|
||||
list(JOIN missing ", " missing)
|
||||
message(STATUS "Online module: off (missing: ${missing}; shell.nix lists the packages)")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# bbport: the shader corpus replay (render/shader_capture.h): ninja -C out/gpu shader-corpus-test.
|
||||
add_executable(shader-corpus-test EXCLUDE_FROM_ALL ../tests/test_shader_corpus.cpp
|
||||
../tests/test_gpu_runtime_stubs.cpp)
|
||||
target_include_directories(shader-corpus-test PRIVATE . shim ${SHADER_INCLUDE} third_party/gcn/include third_party/half/include)
|
||||
target_compile_options(shader-corpus-test PRIVATE -UNDEBUG -fPIC)
|
||||
target_link_libraries(shader-corpus-test PRIVATE bbgpu)
|
||||
|
||||
add_executable(srt-walker-fault-test EXCLUDE_FROM_ALL ../tests/test_srt_walker_fault.cpp
|
||||
../tests/test_gpu_runtime_stubs.cpp)
|
||||
target_include_directories(srt-walker-fault-test PRIVATE . shim ${SHADER_INCLUDE} third_party/gcn/include third_party/half/include)
|
||||
target_compile_options(srt-walker-fault-test PRIVATE -UNDEBUG -Wall -Wextra)
|
||||
target_link_libraries(srt-walker-fault-test PRIVATE bbgpu)
|
||||
+14
-10
@@ -1,10 +1,14 @@
|
||||
Vendored from https://github.com/shadps4-emu/shadPS4 commit b4e7ae776071ceb1dd35004577a8719481e1d258 (GPL-2.0-or-later).
|
||||
Directories: src/video_core, src/shader_recompiler, src/common, src/core/libraries/{gnmdriver,videoout}, externals/gcn, externals/sirit, externals/half.
|
||||
Also: src/core/libraries/kernel/{orbis_error,posix_error,time,process,equeue}.h, equeue.cpp, src/core/libraries/error_codes.h, src/core/platform.h, src/core/libraries/system/{userservice,systemservice}.h.
|
||||
Also: src/core/libraries/avplayer (without the handle streamer), src/core/libraries/videodec/video_utils.{h,cpp}.
|
||||
Local changes are marked with "bbport:" comments.
|
||||
Backported from upstream after b4e7ae7 (2026-10-02), recompiler correctness fixes only:
|
||||
233562929 V_INTERP_MOV_F32 P10/P20 selectors (#5087), d96278e1e compute shader floating-point
|
||||
mode (#5126), e518a6515 S_SUBB_U32 SCC wrap (#5031), 282e5c792 S_ADDC_U32 SCC carry-out (#5030),
|
||||
fb6367d29 32-bit literals in the high dword of 64-bit floats (#5114), 94e217781 V_BFM_B32 (#5185),
|
||||
d1cb90dbb ADDC (#5200), dd43d1cac V_CVT_PK_U8_F32 (#5194), ef08b1e1f V_ALIGNBIT/ALIGNBYTE (#5186).
|
||||
The GPU layer (gpu/) is the port's own code: command processor (cp/), GCN hardware definitions
|
||||
(gcn/), shader translator GCN -> SPIR-V (shader/), renderer (render/), buffer and texture caches
|
||||
(buffers/, textures/), memory tracking (memory/), platform libraries (platform/), upscalers
|
||||
(upscale/). It started from shadPS4 (https://github.com/shadps4-emu/shadPS4, commit
|
||||
b4e7ae776071ceb1dd35004577a8719481e1d258, GPL-2.0-or-later) and was rewritten part by part;
|
||||
gpu/shadps4 was removed on 2026-10-13. The shader translator was checked against the shadPS4
|
||||
translator it replaced on a corpus of the game's shaders (byte-identical SPIR-V,
|
||||
tests/test_shader_corpus.cpp).
|
||||
|
||||
The online module (gpu/bbnet20) keeps shadPS4 v0.20.0's network, NP and shadNet code
|
||||
(bbnet20/vendor/shadps4, SHADNET-PORT.md).
|
||||
|
||||
Third-party libraries in third_party/: sirit (SPIR-V emitter), gcn (AMD GCN headers), half,
|
||||
httplib, imgui, fonts, amd-fsr1 (FidelityFX headers used by shaders), fsr-vulkan.
|
||||
+69
-1
@@ -1,4 +1,6 @@
|
||||
/* C interface of the GPU library (gpu/): shadPS4's Liverpool/Vulkan video core,
|
||||
// SPDX-FileCopyrightText: Copyright 2026 bbport contributors
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/* C interface of the GPU library (gpu/): the command processor and Vulkan renderer,
|
||||
* GnmDriver, VideoOut and kernel event queues, adapted to the native loader. */
|
||||
#ifndef BBGPU_H
|
||||
#define BBGPU_H
|
||||
@@ -30,6 +32,12 @@ 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);
|
||||
/* Hunter's Atlas: the pad's buttons with what its tile or map took out (the game gets the rest);
|
||||
* the buttons unchanged while it is off or not in use. */
|
||||
uint32_t bbgpu_atlas_route_pad(uint32_t buttons, uint8_t lx, uint8_t ly, uint8_t rx, uint8_t ry);
|
||||
/* 1 while the game should be silent: bbport.ini mute_unfocused is on and the window does not
|
||||
* have focus. Safe from any thread (the audio ports read it for every buffer). */
|
||||
int bbgpu_audio_muted(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);
|
||||
@@ -39,7 +47,67 @@ void bbgpu_trophy_notice(int id, char grade, const char *name_utf8, const char *
|
||||
* bbport.ini keeps the window from taking it. */
|
||||
void bbgpu_mouse_take(double *dx, double *dy, int *wheel_up, int *wheel_down);
|
||||
int bbgpu_mouse_captured(void);
|
||||
/* Asks the window to take the mouse (relative mode, as a click in the game does); the window
|
||||
* thread does it. */
|
||||
void bbgpu_mouse_capture(void);
|
||||
void bbgpu_mouse_look_enable(int enabled);
|
||||
/* A module's look at the window's events first (an SDL_Event and the SDL_Window, on the window
|
||||
* thread): nonzero takes the event (the port and the game do not see it). Up to 4. */
|
||||
typedef int (*BbGpuEventFilter)(const void *sdl_event, void *sdl_window);
|
||||
void bbgpu_add_event_filter(BbGpuEventFilter filter);
|
||||
/* A module's menu pointer: nonzero while the game shows a menu the mouse points into. The window
|
||||
* then lets the mouse go and shows the cursor, and takes the mouse back after (as it had it).
|
||||
* Up to 4; pointer_wanted: any of them says so. */
|
||||
/* A video (AvPlayer) playing now; bbgpu_video_skip stops it (1 when one was stopped). */
|
||||
int bbgpu_video_active(void);
|
||||
/* The game shows its world (not a loading screen): the clock of pad recordings runs. */
|
||||
int bbgpu_world_active(void);
|
||||
/* Microseconds the world has been shown (the pad recording clock). */
|
||||
unsigned long long bbgpu_world_us(void);
|
||||
int bbgpu_video_skip(void);
|
||||
typedef int (*BbGpuPointerQuery)(void);
|
||||
void bbgpu_add_pointer_query(BbGpuPointerQuery query);
|
||||
int bbgpu_pointer_wanted(void);
|
||||
/* A module's changes to the pad state the game reads (runtime_pad.c, after the host's input and
|
||||
* the scripted one): the buttons (PS4 bits); the sticks (lx ly rx ry) as they are. Up to 4. */
|
||||
typedef void (*BbGpuPadFilter)(uint32_t *buttons, const uint8_t *sticks);
|
||||
void bbgpu_add_pad_filter(BbGpuPadFilter filter);
|
||||
void bbgpu_filter_pad(uint32_t *buttons, const uint8_t *sticks);
|
||||
/* The shown frame's width / height (the display buffer's; the window letterboxes it). */
|
||||
float bbgpu_frame_aspect(void);
|
||||
/* A line of text over the game, near the bottom, until called with NULL (any thread). */
|
||||
void bbgpu_overlay_banner(const char *utf8);
|
||||
/* Trophies (gpu/shim/bbport_trophies.cpp): setup takes the game's sce_sys folder and the user
|
||||
* folder (once); unlock keeps the trophy and announces it over the frame the first time, and
|
||||
* returns the platinum unlocked along with it, else -1. */
|
||||
void bbgpu_trophy_setup(const char *sce_sys_host_dir, const char *user_dir);
|
||||
int bbgpu_trophy_unlock(int id);
|
||||
/* Native mouse look: turns the game's camera (radians, applied at its next update); 0 when the
|
||||
* camera hook is not in (another game version, BB_NATIVE_MOUSE=0) or the camera does not take the
|
||||
* turns now (lock-on, menus): drive the right stick then. */
|
||||
int bbgpu_camera_look(float yaw, float pitch);
|
||||
/* The pipeline caches written by an earlier build, translated again for this one; called before
|
||||
* the runtime reserves the guest's memory (render/shader_rebuild.h). */
|
||||
int bbgpu_rebuild_shader_caches(void);
|
||||
/* One cache directory (RebuildCaches' child where there is no fork: bb-probe --rebuild-shader-cache). */
|
||||
int bbgpu_rebuild_shader_cache(const char *dir);
|
||||
int bbgpu_predict_shaders_test(const char *cache_dir, const char *pack, const char *game_dir,
|
||||
const char *out_dir, int jobs);
|
||||
int bbgpu_merge_shader_metas(const char *cache_dir, const char *pack, const char *game_dir,
|
||||
const char *meta_dir, const char *out_pack, const char *work_dir, int jobs);
|
||||
int bbgpu_meta_reconstruct_test(const char *cache_dir, const char *pack, const char *game_dir);
|
||||
int bbgpu_merge_shader_packs(const char *cache_dir, const char *pack_a, const char *pack_b,
|
||||
const char *game_dir, const char *out_pack, const char *work_dir, int jobs);
|
||||
/* Shader recipes (render/shader_rebuild.h): bb-probe --export-shader-recipes CACHE_DIR GAME_DIR OUT
|
||||
* and --import-shaders CACHE_DIR PACK GAME_DIR MOTION_PARAMS MOTION_POSITIONS (run by the GPU
|
||||
* library itself, before the guest's memory exists in that process). */
|
||||
int bbgpu_export_shader_recipes(const char *cache_dir, const char *game_dir, const char *out_path);
|
||||
int bbgpu_import_shader_recipes(const char *cache_dir, const char *pack, const char *game_dir,
|
||||
unsigned long long motion_params, unsigned long long motion_positions);
|
||||
/* 1 while the free camera (the community patch) is on: the mouse drives it through the right stick. */
|
||||
int bbgpu_free_camera(void);
|
||||
/* The game's current camera state (its projection parameters at +0x50), 0 until known. */
|
||||
uint64_t bbgpu_camera_state(void);
|
||||
/* Patches the loaded image before the game runs (image still writable): libGnm entry hooks. */
|
||||
void bbgpu_patch_image(unsigned char *image, uint64_t size);
|
||||
/* Number of symbols registered by the vendored libraries (diagnostics). */
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# bbport online module: libbbnet.so, the PS4 network and PSN libraries with the shadNet client
|
||||
# (shadp2p, a shadPS4 fork, GPL-2.0), loaded by the runtime only for online play
|
||||
# (src/runtime_net.c). Its headers (include/) extend the port's shim for these libraries alone;
|
||||
# everything in it is hidden and bound to itself (-Bsymbolic) except bbnet.h's two calls, so its
|
||||
# copies of shadPS4's kernel shims and symbol table never meet libbbgpu's.
|
||||
# Needs protobuf, nlohmann_json, OpenSSL, zlib and miniupnpc (shell.nix); without them the port
|
||||
# builds without it and plays offline (gpu/CMakeLists.txt: BB_NET).
|
||||
|
||||
set(SHADNET_PROTO_OUT "${CMAKE_CURRENT_BINARY_DIR}/shadnet_proto_gen")
|
||||
file(MAKE_DIRECTORY "${SHADNET_PROTO_OUT}")
|
||||
add_custom_command(
|
||||
OUTPUT "${SHADNET_PROTO_OUT}/shadnet.pb.cc" "${SHADNET_PROTO_OUT}/shadnet.pb.h"
|
||||
COMMAND protobuf::protoc "--proto_path=${CMAKE_CURRENT_SOURCE_DIR}/shadps4/shadnet"
|
||||
"--cpp_out=${SHADNET_PROTO_OUT}" "${CMAKE_CURRENT_SOURCE_DIR}/shadps4/shadnet/shadnet.proto"
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/shadps4/shadnet/shadnet.proto")
|
||||
file(GLOB_RECURSE BBNET_SOURCES CONFIGURE_DEPENDS shadps4/core/libraries/network/*.cpp
|
||||
shadps4/core/libraries/np/*.cpp shadps4/shadnet/*.cpp)
|
||||
# Trophies and the system dialogs stay in the C runtime; their shadPS4 versions need the
|
||||
# emulator's UI layers.
|
||||
list(FILTER BBNET_SOURCES EXCLUDE REGEX
|
||||
"np_trophy|trophy_ui|np_sns_facebook_dialog|np_profile_dialog/|np_commerce/")
|
||||
|
||||
add_library(bbnet SHARED ${BBNET_SOURCES} "${SHADNET_PROTO_OUT}/shadnet.pb.cc" bbnet_glue.cpp)
|
||||
# The module's own headers first, then the port's shim and shadPS4 (common/, logging).
|
||||
target_include_directories(bbnet PRIVATE include shadps4 shadps4/shadnet "${SHADNET_PROTO_OUT}"
|
||||
${CMAKE_CURRENT_SOURCE_DIR} ../shim ../shadps4 ../third_party/imgui ../third_party/httplib)
|
||||
target_compile_definitions(bbnet PRIVATE BOOST_ASIO_STANDALONE NDEBUG
|
||||
CPPHTTPLIB_OPENSSL_SUPPORT CPPHTTPLIB_ZLIB_SUPPORT)
|
||||
target_compile_options(bbnet PRIVATE -fvisibility=hidden -fvisibility-inlines-hidden
|
||||
-Wno-deprecated-declarations -Wno-missing-field-initializers)
|
||||
target_link_options(bbnet PRIVATE -Wl,-Bsymbolic -Wl,--exclude-libs,ALL)
|
||||
# libbbgpu (already in the process) provides shadPS4's common code: logging, threads' names.
|
||||
set_target_properties(bbnet PROPERTIES BUILD_RPATH "\$ORIGIN" INSTALL_RPATH "\$ORIGIN"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") # beside libbbgpu.so, where the runtime looks
|
||||
target_link_libraries(bbnet PRIVATE bbgpu nlohmann_json::nlohmann_json protobuf::libprotobuf
|
||||
OpenSSL::SSL OpenSSL::Crypto ZLIB::ZLIB "${MINIUPNPC_LIBRARY}" Vulkan::Headers fmt::fmt
|
||||
Boost::headers magic_enum::magic_enum)
|
||||
target_include_directories(bbnet PRIVATE "${MINIUPNPC_INCLUDE}")
|
||||
@@ -1,14 +0,0 @@
|
||||
// bbport co-op: the Bloodborne summon helpers from shadp2p's bloodborne_re.h. Seamless
|
||||
// co-op (host placement) is not ported yet; these keep the header value between requests.
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace Core::Bloodborne {
|
||||
std::optional<std::string> GetSeamlessHostPlacementHeader();
|
||||
bool SetSeamlessHostPlacementHeader(std::string_view value);
|
||||
void ClearSeamlessHostPlacementHeader();
|
||||
void TraceMatching2LeaveRoom(std::uintptr_t return_address, std::uint64_t room_id);
|
||||
} // namespace Core::Bloodborne
|
||||
@@ -1,93 +0,0 @@
|
||||
// bbport: shadPS4 settings used by the video core, read once from BB_* environment
|
||||
// variables (defaults match shadPS4 except the pipeline cache, which is on).
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include "common/types.h"
|
||||
|
||||
enum GpuReadbacksMode : int { Disabled, Relaxed, Precise };
|
||||
|
||||
class EmulatorSettingsImpl {
|
||||
public:
|
||||
static EmulatorSettingsImpl* GetInstance() { static EmulatorSettingsImpl s; return &s; }
|
||||
static bool Flag(const char* name, bool fallback) {
|
||||
const char* v = std::getenv(name);
|
||||
return v ? (v[0] == '1' || v[0] == 'y' || v[0] == 't') : fallback;
|
||||
}
|
||||
static long Number(const char* name, long fallback) {
|
||||
const char* v = std::getenv(name);
|
||||
return v ? std::strtol(v, nullptr, 10) : fallback;
|
||||
}
|
||||
s32 GetGpuId() { static const auto value = s32(Number("BB_GPU_ID", -1)); return value; }
|
||||
u32 GetInternalScreenWidth() { static const auto value = u32(Number("BB_INTERNAL_WIDTH", 1920)); return value; }
|
||||
u32 GetInternalScreenHeight() { static const auto value = u32(Number("BB_INTERNAL_HEIGHT", 1080)); return value; }
|
||||
std::string GetPresentMode() { const char* v = std::getenv("BB_PRESENT_MODE"); return v ? v : "Mailbox"; }
|
||||
int GetRcasAttenuation() { static const auto value = int(Number("BB_RCAS_ATTENUATION", 250)); return value; }
|
||||
// bbport: Relaxed by default: without readbacks FaceGen reads stale GPU-written vertices
|
||||
// (vertex explosions); in Hunter's Nightmare it costs no measurable frame rate.
|
||||
u32 GetReadbacksMode() { static const auto value = u32(Number("BB_READBACKS", GpuReadbacksMode::Relaxed)); return value; }
|
||||
// bbport: uncapped presets (run.sh: BB_VBLANK_HZ=480; 0 means the same): vblank runs at
|
||||
// 480 Hz, and a finished frame is presented as soon as it arrives (IsUncappedVblank), not on
|
||||
// the next vblank tick (at the display rate frames alternated 10/20 ms at 100 Hz: judder; at
|
||||
// 480 Hz they snap to ~2.1 ms steps). The frame rate is not limited unless BB_FPS_LIMIT
|
||||
// (launcher: "FPS limit") asks for it. Other values are used as given (60/90: the
|
||||
// fixed-timestep presets, flips on vblank ticks).
|
||||
u32 GetVblankFrequency() {
|
||||
static const u32 value = [] {
|
||||
const long hz = Number("BB_VBLANK_HZ", 60);
|
||||
return hz > 0 ? u32(hz) : 480u;
|
||||
}();
|
||||
return value;
|
||||
}
|
||||
/// Vblank faster than any fixed-timestep preset (above 120 Hz, or 0): flips at once.
|
||||
bool IsUncappedVblank() {
|
||||
static const bool value = [] {
|
||||
const long hz = Number("BB_VBLANK_HZ", 60);
|
||||
return hz <= 0 || hz > 120;
|
||||
}();
|
||||
return value;
|
||||
}
|
||||
/// Frames per second the present thread lets through; 0 = no limit (BB_FPS_LIMIT).
|
||||
u32 GetFrameLimit() {
|
||||
static const u32 value = u32(std::max(0L, Number("BB_FPS_LIMIT", 0)));
|
||||
return value;
|
||||
}
|
||||
bool IsCopyGpuBuffers() { static const auto value = Flag("BB_COPY_GPU_BUFFERS", false); return value; }
|
||||
bool IsDirectMemoryAccessEnabled() { static const auto value = Flag("BB_DIRECT_MEMORY_ACCESS", false); return value; }
|
||||
bool IsDumpShaders() { static const auto value = Flag("BB_DUMP_SHADERS", false); return value; }
|
||||
bool IsFsrEnabled() { static const auto value = Flag("BB_FSR1", false); return value; }
|
||||
bool IsHdrAllowed() { static const auto value = Flag("BB_HDR", false); return value; }
|
||||
bool IsNullGPU() { static const auto value = Flag("BB_NULL_GPU", false); return value; }
|
||||
bool IsPatchShaders() { return false; }
|
||||
bool IsPipelineCacheArchived() { return false; }
|
||||
bool IsPipelineCacheEnabled() { static const auto value = Flag("BB_PIPELINE_CACHE", true); return value; }
|
||||
bool IsRcasEnabled() { static const auto value = Flag("BB_RCAS", true); return value; }
|
||||
bool IsReadbackLinearImagesEnabled() { static const auto value = Flag("BB_READBACK_LINEAR", false); return value; }
|
||||
bool IsRenderdocEnabled() { return false; }
|
||||
bool IsShaderCollect() { return false; }
|
||||
bool IsUserfaultfdTracking() { return false; }
|
||||
bool IsVkCrashDiagnosticEnabled() { return false; }
|
||||
bool IsVkGuestMarkersEnabled() { static const auto value = Flag("BB_VK_MARKERS", false); return value; }
|
||||
bool IsVkHostMarkersEnabled() { static const auto value = Flag("BB_VK_MARKERS", false); return value; }
|
||||
bool IsVkValidationCoreEnabled() { return true; }
|
||||
bool IsVkValidationEnabled() { static const auto value = Flag("BB_VK_VALIDATION", false); return value; }
|
||||
bool IsVkValidationGpuEnabled() { return false; }
|
||||
bool IsVkValidationSyncEnabled() { static const auto value = Flag("BB_VK_VALIDATION_SYNC", false); return value; }
|
||||
// bbport co-op: online play through a shadNet server (BB_ONLINE=1, set by the launcher).
|
||||
static std::string Text(const char* name, const char* fallback) {
|
||||
const char* v = std::getenv(name);
|
||||
return v && v[0] ? v : fallback;
|
||||
}
|
||||
bool IsConnectedToNetwork() { static const auto value = Flag("BB_ONLINE", false); return value; }
|
||||
bool IsShadNetEnabled() { return IsConnectedToNetwork() && !shadnet_session_disabled.load(); }
|
||||
void SetShadNetSessionDisabled(bool v) { shadnet_session_disabled.store(v); }
|
||||
std::string GetShadNetServer() { return Text("BB_SHADNET_SERVER", "127.0.0.1:31313"); }
|
||||
std::string GetShadNetWebApiServer() { return Text("BB_SHADNET_WEBAPI", "http://127.0.0.1:31315"); }
|
||||
bool IsUPnPEnabled() { static const auto value = Flag("BB_UPNP", true); return value; }
|
||||
|
||||
private:
|
||||
std::atomic<bool> shadnet_session_disabled{false};
|
||||
};
|
||||
#define EmulatorSettings (*EmulatorSettingsImpl::GetInstance())
|
||||
@@ -1,40 +0,0 @@
|
||||
// bbport: host threads that may call guest code (AvPlayer allocator callbacks).
|
||||
// Each thread gets a guest TCB (GS base, TLS) from the C runtime before running.
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/kernel/threads/pthread.h"
|
||||
|
||||
extern "C" void runtime_thread_attach_host(const char* name);
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
class Thread {
|
||||
public:
|
||||
Thread() = default;
|
||||
~Thread() { Stop(); }
|
||||
void Run(std::function<void(std::stop_token)>&& func) {
|
||||
thread = std::jthread([func = std::move(func)](std::stop_token stop) {
|
||||
runtime_thread_attach_host("bb:hle");
|
||||
func(stop);
|
||||
});
|
||||
}
|
||||
// A thread may stop its own Thread object (AvPlayer does); it detaches instead of joining.
|
||||
void Join() {
|
||||
if (!thread.joinable()) return;
|
||||
if (thread.get_id() == std::this_thread::get_id()) thread.detach();
|
||||
else thread.join();
|
||||
}
|
||||
bool Joinable() const { return thread.joinable(); }
|
||||
void Stop() {
|
||||
if (thread.joinable()) {
|
||||
thread.request_stop();
|
||||
Join();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::jthread thread;
|
||||
};
|
||||
} // namespace Libraries::Kernel
|
||||
@@ -1,22 +0,0 @@
|
||||
// bbport: shadPS4 registers HLE functions with LIB_FUNCTION(nid, library,
|
||||
// version, module, fn). Here the registrations fill a table that the C
|
||||
// loader queries by NID (see bbgpu.cpp: bbgpu_resolve).
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include "common/assert.h"
|
||||
#include "common/types.h"
|
||||
#include "core/emulator_settings.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
enum class SymbolType { Function, Object };
|
||||
class SymbolsResolver {
|
||||
public:
|
||||
void AddSymbol(const char* nid, const char* library, const char* module, SymbolType type, u64 address);
|
||||
};
|
||||
} // namespace Core::Loader
|
||||
|
||||
#define LIB_FUNCTION(nid, lib, libversion, mod, function) \
|
||||
sym->AddSymbol(nid, lib, mod, Core::Loader::SymbolType::Function, \
|
||||
reinterpret_cast<u64>(function))
|
||||
#define LIB_OBJ(nid, lib, libversion, mod, obj) \
|
||||
sym->AddSymbol(nid, lib, mod, Core::Loader::SymbolType::Object, reinterpret_cast<u64>(obj))
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,495 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/types.h"
|
||||
#include "netctl.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
// Define our own htonll and ntohll because its not available in some systems/platforms
|
||||
#ifndef HTONLL
|
||||
#define HTONLL(x) (((uint64_t)htonl((x) & 0xFFFFFFFFUL)) << 32) | htonl((uint32_t)((x) >> 32))
|
||||
#endif
|
||||
#ifndef NTOHLL
|
||||
#define NTOHLL(x) (((uint64_t)ntohl((x) & 0xFFFFFFFFUL)) << 32) | ntohl((uint32_t)((x) >> 32))
|
||||
#endif
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
static int ConvertFamilies(int family);
|
||||
|
||||
enum OrbisNetFamily : u32 {
|
||||
ORBIS_NET_AF_UNIX = 1,
|
||||
ORBIS_NET_AF_INET = 2,
|
||||
ORBIS_NET_AF_INET6 = 28,
|
||||
};
|
||||
enum OrbisNetSocketType : u32 {
|
||||
ORBIS_NET_SOCK_STREAM = 1,
|
||||
ORBIS_NET_SOCK_DGRAM = 2,
|
||||
ORBIS_NET_SOCK_RAW = 3,
|
||||
ORBIS_NET_SOCK_DGRAM_P2P = 6,
|
||||
ORBIS_NET_SOCK_STREAM_P2P = 10
|
||||
};
|
||||
|
||||
enum OrbisNetProtocol : u32 {
|
||||
ORBIS_NET_IPPROTO_IP = 0,
|
||||
ORBIS_NET_IPPROTO_ICMP = 1,
|
||||
ORBIS_NET_IPPROTO_IGMP = 2,
|
||||
ORBIS_NET_IPPROTO_TCP = 6,
|
||||
ORBIS_NET_IPPROTO_UDP = 17,
|
||||
ORBIS_NET_IPPROTO_IPV6 = 41,
|
||||
ORBIS_NET_SOL_SOCKET = 0xFFFF
|
||||
};
|
||||
|
||||
constexpr std::string_view NameOf(OrbisNetProtocol p) {
|
||||
switch (p) {
|
||||
case ORBIS_NET_IPPROTO_IP:
|
||||
return "ORBIS_NET_IPPROTO_IP";
|
||||
case ORBIS_NET_IPPROTO_ICMP:
|
||||
return "ORBIS_NET_IPPROTO_ICMP";
|
||||
case ORBIS_NET_IPPROTO_IGMP:
|
||||
return "ORBIS_NET_IPPROTO_IGMP";
|
||||
case ORBIS_NET_IPPROTO_TCP:
|
||||
return "ORBIS_NET_IPPROTO_TCP";
|
||||
case ORBIS_NET_IPPROTO_UDP:
|
||||
return "ORBIS_NET_IPPROTO_UDP";
|
||||
case ORBIS_NET_IPPROTO_IPV6:
|
||||
return "ORBIS_NET_IPPROTO_IPV6";
|
||||
case ORBIS_NET_SOL_SOCKET:
|
||||
return "ORBIS_NET_SOL_SOCKET";
|
||||
default:
|
||||
UNREACHABLE_MSG("{}", (u32)p);
|
||||
}
|
||||
}
|
||||
|
||||
enum OrbisNetSocketIpOption : u32 {
|
||||
ORBIS_NET_IP_HDRINCL = 2,
|
||||
ORBIS_NET_IP_TOS = 3,
|
||||
ORBIS_NET_IP_TTL = 4,
|
||||
ORBIS_NET_IP_MULTICAST_IF = 9,
|
||||
ORBIS_NET_IP_MULTICAST_TTL = 10,
|
||||
ORBIS_NET_IP_MULTICAST_LOOP = 11,
|
||||
ORBIS_NET_IP_ADD_MEMBERSHIP = 12,
|
||||
ORBIS_NET_IP_DROP_MEMBERSHIP = 13,
|
||||
ORBIS_NET_IP_TTLCHK = 23,
|
||||
ORBIS_NET_IP_MAXTTL = 24,
|
||||
};
|
||||
|
||||
enum OrbisNetSocketTcpOption : u32 {
|
||||
ORBIS_NET_TCP_NODELAY = 1,
|
||||
ORBIS_NET_TCP_MAXSEG = 2,
|
||||
ORBIS_NET_TCP_MSS_TO_ADVERTISE = 3,
|
||||
};
|
||||
|
||||
enum OrbisNetSocketSoOption : u32 {
|
||||
ORBIS_NET_SO_REUSEADDR = 0x00000004,
|
||||
ORBIS_NET_SO_KEEPALIVE = 0x00000008,
|
||||
ORBIS_NET_SO_BROADCAST = 0x00000020,
|
||||
ORBIS_NET_SO_LINGER = 0x00000080,
|
||||
ORBIS_NET_SO_REUSEPORT = 0x00000200,
|
||||
ORBIS_NET_SO_ONESBCAST = 0x00010000,
|
||||
ORBIS_NET_SO_USECRYPTO = 0x00020000,
|
||||
ORBIS_NET_SO_USESIGNATURE = 0x00040000,
|
||||
ORBIS_NET_SO_SNDBUF = 0x1001,
|
||||
ORBIS_NET_SO_RCVBUF = 0x1002,
|
||||
ORBIS_NET_SO_ERROR = 0x1007,
|
||||
ORBIS_NET_SO_TYPE = 0x1008,
|
||||
ORBIS_NET_SO_SNDTIMEO = 0x1105,
|
||||
ORBIS_NET_SO_RCVTIMEO = 0x1106,
|
||||
ORBIS_NET_SO_ERROR_EX = 0x1107,
|
||||
ORBIS_NET_SO_ACCEPTTIMEO = 0x1108,
|
||||
ORBIS_NET_SO_CONNECTTIMEO = 0x1109,
|
||||
ORBIS_NET_SO_NBIO = 0x1200,
|
||||
ORBIS_NET_SO_POLICY = 0x1201,
|
||||
ORBIS_NET_SO_NAME = 0x1202,
|
||||
ORBIS_NET_SO_PRIORITY = 0x1203
|
||||
};
|
||||
|
||||
enum OrbisNetFlags : u32 {
|
||||
ORBIS_NET_MSG_PEEK = 0x00000002,
|
||||
ORBIS_NET_MSG_WAITALL = 0x00000040,
|
||||
ORBIS_NET_MSG_DONTWAIT = 0x00000080,
|
||||
ORBIS_NET_MSG_USECRYPTO = 0x00100000,
|
||||
ORBIS_NET_MSG_USESIGNATURE = 0x00200000,
|
||||
ORBIS_NET_MSG_PEEKLEN = (0x00400000 | ORBIS_NET_MSG_PEEK)
|
||||
};
|
||||
|
||||
constexpr std::string_view NameOf(OrbisNetSocketSoOption o) {
|
||||
switch (o) {
|
||||
case ORBIS_NET_SO_REUSEADDR:
|
||||
return "ORBIS_NET_SO_REUSEADDR";
|
||||
case ORBIS_NET_SO_KEEPALIVE:
|
||||
return "ORBIS_NET_SO_KEEPALIVE";
|
||||
case ORBIS_NET_SO_BROADCAST:
|
||||
return "ORBIS_NET_SO_BROADCAST";
|
||||
case ORBIS_NET_SO_LINGER:
|
||||
return "ORBIS_NET_SO_LINGER";
|
||||
case ORBIS_NET_SO_REUSEPORT:
|
||||
return "ORBIS_NET_SO_REUSEPORT";
|
||||
case ORBIS_NET_SO_ONESBCAST:
|
||||
return "ORBIS_NET_SO_ONESBCAST";
|
||||
case ORBIS_NET_SO_USECRYPTO:
|
||||
return "ORBIS_NET_SO_USECRYPTO";
|
||||
case ORBIS_NET_SO_USESIGNATURE:
|
||||
return "ORBIS_NET_SO_USESIGNATURE";
|
||||
case ORBIS_NET_SO_SNDBUF:
|
||||
return "ORBIS_NET_SO_SNDBUF";
|
||||
case ORBIS_NET_SO_RCVBUF:
|
||||
return "ORBIS_NET_SO_RCVBUF";
|
||||
case ORBIS_NET_SO_ERROR:
|
||||
return "ORBIS_NET_SO_ERROR";
|
||||
case ORBIS_NET_SO_TYPE:
|
||||
return "ORBIS_NET_SO_TYPE";
|
||||
case ORBIS_NET_SO_SNDTIMEO:
|
||||
return "ORBIS_NET_SO_SNDTIMEO";
|
||||
case ORBIS_NET_SO_RCVTIMEO:
|
||||
return "ORBIS_NET_SO_RCVTIMEO";
|
||||
case ORBIS_NET_SO_ERROR_EX:
|
||||
return "ORBIS_NET_SO_ERROR_EX";
|
||||
case ORBIS_NET_SO_ACCEPTTIMEO:
|
||||
return "ORBIS_NET_SO_ACCEPTTIMEO";
|
||||
case ORBIS_NET_SO_CONNECTTIMEO:
|
||||
return "ORBIS_NET_SO_CONNECTTIMEO";
|
||||
case ORBIS_NET_SO_NBIO:
|
||||
return "ORBIS_NET_SO_NBIO";
|
||||
case ORBIS_NET_SO_POLICY:
|
||||
return "ORBIS_NET_SO_POLICY";
|
||||
case ORBIS_NET_SO_NAME:
|
||||
return "ORBIS_NET_SO_NAME";
|
||||
case ORBIS_NET_SO_PRIORITY:
|
||||
return "ORBIS_NET_SO_PRIORITY";
|
||||
default:
|
||||
UNREACHABLE_MSG("{}", (u32)o);
|
||||
}
|
||||
}
|
||||
|
||||
enum OrbisNetEpollFlag : u32 {
|
||||
ORBIS_NET_EPOLL_CTL_ADD = 1,
|
||||
ORBIS_NET_EPOLL_CTL_MOD = 2,
|
||||
ORBIS_NET_EPOLL_CTL_DEL = 3,
|
||||
};
|
||||
|
||||
enum OrbisNetEpollEvents : u32 {
|
||||
ORBIS_NET_EPOLLIN = 0x1,
|
||||
ORBIS_NET_EPOLLOUT = 0x2,
|
||||
ORBIS_NET_EPOLLERR = 0x8,
|
||||
ORBIS_NET_EPOLLHUP = 0x10,
|
||||
ORBIS_NET_EPOLLDESCID = 0x10000,
|
||||
};
|
||||
|
||||
enum OrbisNetResolverFlag : u32 {
|
||||
ORBIS_NET_RESOLVER_ASYNC = 0x1,
|
||||
ORBIS_NET_RESOLVER_START_NTOA_DISABLE_IPADDRESS = 0x10000,
|
||||
};
|
||||
|
||||
using OrbisNetId = s32;
|
||||
|
||||
struct OrbisNetSockaddr {
|
||||
u8 sa_len;
|
||||
u8 sa_family;
|
||||
char sa_data[14];
|
||||
};
|
||||
|
||||
struct OrbisNetSockaddrIn {
|
||||
u8 sin_len;
|
||||
u8 sin_family;
|
||||
u16 sin_port;
|
||||
u32 sin_addr;
|
||||
u16 sin_vport;
|
||||
char sin_zero[6];
|
||||
};
|
||||
|
||||
using OrbisNetInAddr_t = u32;
|
||||
|
||||
struct OrbisNetInAddr {
|
||||
OrbisNetInAddr_t inaddr_addr;
|
||||
};
|
||||
|
||||
struct OrbisNetSockaddrUn {
|
||||
u8 sun_len;
|
||||
u8 sun_family; // AF_UNIX
|
||||
char sun_path[104];
|
||||
};
|
||||
|
||||
struct OrbisNetIovec {
|
||||
void* iov_base;
|
||||
u64 iov_len;
|
||||
};
|
||||
|
||||
struct OrbisNetMsghdr {
|
||||
void* msg_name;
|
||||
u32 msg_namelen;
|
||||
OrbisNetIovec* msg_iov;
|
||||
int msg_iovlen;
|
||||
void* msg_control;
|
||||
u32 msg_controllen;
|
||||
int msg_flags;
|
||||
};
|
||||
|
||||
union OrbisNetEpollData {
|
||||
void* ptr;
|
||||
u32 data_u32;
|
||||
int fd;
|
||||
u64 data_u64;
|
||||
};
|
||||
|
||||
struct OrbisNetEpollEvent {
|
||||
u32 events;
|
||||
u32 pad;
|
||||
u64 ident;
|
||||
OrbisNetEpollData data;
|
||||
};
|
||||
|
||||
union OrbisNetAddrUnion {
|
||||
OrbisNetInAddr addr;
|
||||
u8 addr6[16];
|
||||
};
|
||||
|
||||
struct OrbisNetResolverAddr {
|
||||
OrbisNetAddrUnion u;
|
||||
u32 af;
|
||||
u32 pad[3];
|
||||
};
|
||||
|
||||
struct OrbisNetResolverInfo {
|
||||
OrbisNetResolverAddr addrs[10];
|
||||
u32 records;
|
||||
u32 recordsv4;
|
||||
u32 pad[14];
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI in6addr_any();
|
||||
int PS4_SYSV_ABI in6addr_loopback();
|
||||
int PS4_SYSV_ABI sce_net_dummy();
|
||||
int PS4_SYSV_ABI sce_net_in6addr_any();
|
||||
int PS4_SYSV_ABI sce_net_in6addr_linklocal_allnodes();
|
||||
int PS4_SYSV_ABI sce_net_in6addr_linklocal_allrouters();
|
||||
int PS4_SYSV_ABI sce_net_in6addr_loopback();
|
||||
int PS4_SYSV_ABI sce_net_in6addr_nodelocal_allnodes();
|
||||
OrbisNetId PS4_SYSV_ABI sceNetAccept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
|
||||
int PS4_SYSV_ABI sceNetAddrConfig6GetInfo();
|
||||
int PS4_SYSV_ABI sceNetAddrConfig6Start();
|
||||
int PS4_SYSV_ABI sceNetAddrConfig6Stop();
|
||||
int PS4_SYSV_ABI sceNetAllocateAllRouteInfo();
|
||||
int PS4_SYSV_ABI sceNetBandwidthControlGetDataTraffic();
|
||||
int PS4_SYSV_ABI sceNetBandwidthControlGetDefaultParam();
|
||||
int PS4_SYSV_ABI sceNetBandwidthControlGetIfParam();
|
||||
int PS4_SYSV_ABI sceNetBandwidthControlGetPolicy();
|
||||
int PS4_SYSV_ABI sceNetBandwidthControlSetDefaultParam();
|
||||
int PS4_SYSV_ABI sceNetBandwidthControlSetIfParam();
|
||||
int PS4_SYSV_ABI sceNetBandwidthControlSetPolicy();
|
||||
int PS4_SYSV_ABI sceNetBind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
|
||||
int PS4_SYSV_ABI sceNetClearDnsCache();
|
||||
int PS4_SYSV_ABI sceNetConfigAddArp();
|
||||
int PS4_SYSV_ABI sceNetConfigAddArpWithInterface();
|
||||
int PS4_SYSV_ABI sceNetConfigAddIfaddr();
|
||||
int PS4_SYSV_ABI sceNetConfigAddMRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigAddRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigAddRoute6();
|
||||
int PS4_SYSV_ABI sceNetConfigAddRouteWithInterface();
|
||||
int PS4_SYSV_ABI sceNetConfigCleanUpAllInterfaces();
|
||||
int PS4_SYSV_ABI sceNetConfigDelArp();
|
||||
int PS4_SYSV_ABI sceNetConfigDelArpWithInterface();
|
||||
int PS4_SYSV_ABI sceNetConfigDelDefaultRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigDelDefaultRoute6();
|
||||
int PS4_SYSV_ABI sceNetConfigDelIfaddr();
|
||||
int PS4_SYSV_ABI sceNetConfigDelIfaddr6();
|
||||
int PS4_SYSV_ABI sceNetConfigDelMRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigDelRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigDelRoute6();
|
||||
int PS4_SYSV_ABI sceNetConfigDownInterface();
|
||||
int PS4_SYSV_ABI sceNetConfigEtherGetLinkMode();
|
||||
int PS4_SYSV_ABI sceNetConfigEtherPostPlugInOutEvent();
|
||||
int PS4_SYSV_ABI sceNetConfigEtherSetLinkMode();
|
||||
int PS4_SYSV_ABI sceNetConfigFlushRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigGetDefaultRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigGetDefaultRoute6();
|
||||
int PS4_SYSV_ABI sceNetConfigGetIfaddr();
|
||||
int PS4_SYSV_ABI sceNetConfigGetIfaddr6();
|
||||
int PS4_SYSV_ABI sceNetConfigRoutingShowRoutingConfig();
|
||||
int PS4_SYSV_ABI sceNetConfigRoutingShowtCtlVar();
|
||||
int PS4_SYSV_ABI sceNetConfigRoutingStart();
|
||||
int PS4_SYSV_ABI sceNetConfigRoutingStop();
|
||||
int PS4_SYSV_ABI sceNetConfigSetDefaultRoute();
|
||||
int PS4_SYSV_ABI sceNetConfigSetDefaultRoute6();
|
||||
int PS4_SYSV_ABI sceNetConfigSetDefaultScope();
|
||||
int PS4_SYSV_ABI sceNetConfigSetIfaddr();
|
||||
int PS4_SYSV_ABI sceNetConfigSetIfaddr6();
|
||||
int PS4_SYSV_ABI sceNetConfigSetIfaddr6WithFlags();
|
||||
int PS4_SYSV_ABI sceNetConfigSetIfFlags();
|
||||
int PS4_SYSV_ABI sceNetConfigSetIfLinkLocalAddr6();
|
||||
int PS4_SYSV_ABI sceNetConfigSetIfmtu();
|
||||
int PS4_SYSV_ABI sceNetConfigUnsetIfFlags();
|
||||
int PS4_SYSV_ABI sceNetConfigUpInterface();
|
||||
int PS4_SYSV_ABI sceNetConfigUpInterfaceWithFlags();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocClearWakeOnWlan();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocCreate();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocGetWakeOnWlanInfo();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocJoin();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocLeave();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuClearWakeOnWlan();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuSetWakeOnWlan();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocScanJoin();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocSetExtInfoElement();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanAdhocSetWakeOnWlan();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanApStart();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanApStop();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanQuery();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStart();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStop();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanDiagGetDeviceInfo();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanDiagSetAntenna();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanDiagSetTxFixedRate();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanGetDeviceConfig();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanInfraGetRssiInfo();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanInfraLeave();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanInfraScanJoin();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanScan();
|
||||
int PS4_SYSV_ABI sceNetConfigWlanSetDeviceConfig();
|
||||
int PS4_SYSV_ABI sceNetConnect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
|
||||
int PS4_SYSV_ABI sceNetControl();
|
||||
int PS4_SYSV_ABI sceNetDhcpdStart();
|
||||
int PS4_SYSV_ABI sceNetDhcpdStop();
|
||||
int PS4_SYSV_ABI sceNetDhcpGetAutoipInfo();
|
||||
int PS4_SYSV_ABI sceNetDhcpGetInfo();
|
||||
int PS4_SYSV_ABI sceNetDhcpGetInfoEx();
|
||||
int PS4_SYSV_ABI sceNetDhcpStart();
|
||||
int PS4_SYSV_ABI sceNetDhcpStop();
|
||||
int PS4_SYSV_ABI sceNetDumpAbort();
|
||||
int PS4_SYSV_ABI sceNetDumpCreate();
|
||||
int PS4_SYSV_ABI sceNetDumpDestroy();
|
||||
int PS4_SYSV_ABI sceNetDumpRead();
|
||||
int PS4_SYSV_ABI sceNetDuplicateIpStart();
|
||||
int PS4_SYSV_ABI sceNetDuplicateIpStop();
|
||||
int PS4_SYSV_ABI sceNetEpollAbort();
|
||||
int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, OrbisNetId id,
|
||||
OrbisNetEpollEvent* event);
|
||||
int PS4_SYSV_ABI sceNetEpollCreate(const char* name, int flags);
|
||||
int PS4_SYSV_ABI sceNetEpollDestroy(OrbisNetId epollid);
|
||||
int PS4_SYSV_ABI sceNetEpollWait(OrbisNetId epollid, OrbisNetEpollEvent* events, int maxevents,
|
||||
int timeout);
|
||||
int* PS4_SYSV_ABI sceNetErrnoLoc();
|
||||
int PS4_SYSV_ABI sceNetEtherNtostr();
|
||||
int PS4_SYSV_ABI sceNetEtherStrton();
|
||||
int PS4_SYSV_ABI sceNetEventCallbackCreate();
|
||||
int PS4_SYSV_ABI sceNetEventCallbackDestroy();
|
||||
int PS4_SYSV_ABI sceNetEventCallbackGetError();
|
||||
int PS4_SYSV_ABI sceNetEventCallbackWaitCB();
|
||||
int PS4_SYSV_ABI sceNetFreeAllRouteInfo();
|
||||
int PS4_SYSV_ABI sceNetGetArpInfo();
|
||||
int PS4_SYSV_ABI sceNetGetDns6Info();
|
||||
int PS4_SYSV_ABI sceNetGetDnsInfo();
|
||||
int PS4_SYSV_ABI sceNetGetIfList();
|
||||
int PS4_SYSV_ABI sceNetGetIfListOnce();
|
||||
int PS4_SYSV_ABI sceNetGetIfName();
|
||||
int PS4_SYSV_ABI sceNetGetIfnameNumList();
|
||||
int PS4_SYSV_ABI sceNetGetMacAddress(Libraries::NetCtl::OrbisNetEtherAddr* addr, int flags);
|
||||
int PS4_SYSV_ABI sceNetGetMemoryPoolStats();
|
||||
int PS4_SYSV_ABI sceNetGetNameToIndex();
|
||||
int PS4_SYSV_ABI sceNetGetpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
|
||||
int PS4_SYSV_ABI sceNetGetRandom();
|
||||
int PS4_SYSV_ABI sceNetGetRouteInfo();
|
||||
int PS4_SYSV_ABI sceNetGetSockInfo();
|
||||
int PS4_SYSV_ABI sceNetGetSockInfo6();
|
||||
int PS4_SYSV_ABI sceNetGetsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
|
||||
int PS4_SYSV_ABI sceNetGetsockopt(OrbisNetId s, int level, int optname, void* optval, u32* optlen);
|
||||
int PS4_SYSV_ABI sceNetGetStatisticsInfo();
|
||||
int PS4_SYSV_ABI sceNetGetStatisticsInfoInternal();
|
||||
int PS4_SYSV_ABI sceNetGetSystemTime();
|
||||
u32 PS4_SYSV_ABI sceNetHtonl(u32 host32);
|
||||
u64 PS4_SYSV_ABI sceNetHtonll(u64 host64);
|
||||
u16 PS4_SYSV_ABI sceNetHtons(u16 host16);
|
||||
const char* PS4_SYSV_ABI sceNetInetNtop(int af, const void* src, char* dst, u32 size);
|
||||
int PS4_SYSV_ABI sceNetInetNtopWithScopeId();
|
||||
int PS4_SYSV_ABI sceNetInetPton(int af, const char* src, void* dst);
|
||||
int PS4_SYSV_ABI sceNetInetPtonEx(int af, const char* src, void* dst, int flags);
|
||||
int PS4_SYSV_ABI sceNetInetPtonWithScopeId();
|
||||
int PS4_SYSV_ABI sceNetInfoDumpStart();
|
||||
int PS4_SYSV_ABI sceNetInfoDumpStop();
|
||||
int PS4_SYSV_ABI sceNetInit();
|
||||
int PS4_SYSV_ABI sceNetInitParam();
|
||||
int PS4_SYSV_ABI sceNetIoctl();
|
||||
int PS4_SYSV_ABI sceNetListen(OrbisNetId s, int backlog);
|
||||
int PS4_SYSV_ABI sceNetMemoryAllocate();
|
||||
int PS4_SYSV_ABI sceNetMemoryFree();
|
||||
u32 PS4_SYSV_ABI sceNetNtohl(u32 net32);
|
||||
int PS4_SYSV_ABI sceNetNtohll();
|
||||
u16 PS4_SYSV_ABI sceNetNtohs(u16 net16);
|
||||
int PS4_SYSV_ABI sceNetPoolCreate(const char* name, int size, int flags);
|
||||
int PS4_SYSV_ABI sceNetPoolDestroy();
|
||||
int PS4_SYSV_ABI sceNetPppoeStart();
|
||||
int PS4_SYSV_ABI sceNetPppoeStop();
|
||||
int PS4_SYSV_ABI sceNetRecv(OrbisNetId s, void* buf, u64 len, int flags);
|
||||
int PS4_SYSV_ABI sceNetRecvfrom(OrbisNetId s, void* buf, u64 len, int flags, OrbisNetSockaddr* addr,
|
||||
u32* paddrlen);
|
||||
int PS4_SYSV_ABI sceNetRecvmsg(OrbisNetId s, OrbisNetMsghdr* msg, int flags);
|
||||
int PS4_SYSV_ABI sceNetResolverAbort();
|
||||
int PS4_SYSV_ABI sceNetResolverConnect();
|
||||
int PS4_SYSV_ABI sceNetResolverConnectAbort();
|
||||
int PS4_SYSV_ABI sceNetResolverConnectCreate();
|
||||
int PS4_SYSV_ABI sceNetResolverConnectDestroy();
|
||||
int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags);
|
||||
int PS4_SYSV_ABI sceNetResolverDestroy(OrbisNetId resolverid);
|
||||
int PS4_SYSV_ABI sceNetResolverGetError(OrbisNetId resolverid, s32* status);
|
||||
int PS4_SYSV_ABI sceNetResolverStartAton();
|
||||
int PS4_SYSV_ABI sceNetResolverStartAton6();
|
||||
int PS4_SYSV_ABI sceNetResolverStartNtoa(OrbisNetId resolverid, const char* hostname,
|
||||
OrbisNetInAddr* addr, int timeout, int retry, int flags);
|
||||
int PS4_SYSV_ABI sceNetResolverStartNtoa6();
|
||||
int PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecords(OrbisNetId resolverid, const char* hostname,
|
||||
OrbisNetResolverInfo* info, int timeout,
|
||||
int retry, int flags);
|
||||
int PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecordsEx();
|
||||
int PS4_SYSV_ABI sceNetSend(OrbisNetId s, const void* buf, u64 len, int flags);
|
||||
int PS4_SYSV_ABI sceNetSendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, int flags);
|
||||
int PS4_SYSV_ABI sceNetSendto(OrbisNetId s, const void* buf, u64 len, int flags,
|
||||
const OrbisNetSockaddr* addr, u32 addrlen);
|
||||
int PS4_SYSV_ABI sceNetSetDns6Info();
|
||||
int PS4_SYSV_ABI sceNetSetDns6InfoToKernel();
|
||||
int PS4_SYSV_ABI sceNetSetDnsInfo();
|
||||
int PS4_SYSV_ABI sceNetSetDnsInfoToKernel();
|
||||
int PS4_SYSV_ABI sceNetSetsockopt(OrbisNetId s, int level, int optname, const void* optval,
|
||||
u32 optlen);
|
||||
int PS4_SYSV_ABI sceNetShowIfconfig();
|
||||
int PS4_SYSV_ABI sceNetShowIfconfigForBuffer();
|
||||
int PS4_SYSV_ABI sceNetShowIfconfigWithMemory();
|
||||
int PS4_SYSV_ABI sceNetShowNetstat();
|
||||
int PS4_SYSV_ABI sceNetShowNetstatEx();
|
||||
int PS4_SYSV_ABI sceNetShowNetstatExForBuffer();
|
||||
int PS4_SYSV_ABI sceNetShowNetstatForBuffer();
|
||||
int PS4_SYSV_ABI sceNetShowNetstatWithMemory();
|
||||
int PS4_SYSV_ABI sceNetShowPolicy();
|
||||
int PS4_SYSV_ABI sceNetShowPolicyWithMemory();
|
||||
int PS4_SYSV_ABI sceNetShowRoute();
|
||||
int PS4_SYSV_ABI sceNetShowRoute6();
|
||||
int PS4_SYSV_ABI sceNetShowRoute6ForBuffer();
|
||||
int PS4_SYSV_ABI sceNetShowRoute6WithMemory();
|
||||
int PS4_SYSV_ABI sceNetShowRouteForBuffer();
|
||||
int PS4_SYSV_ABI sceNetShowRouteWithMemory();
|
||||
int PS4_SYSV_ABI sceNetShutdown(OrbisNetId s, int how);
|
||||
OrbisNetId PS4_SYSV_ABI sceNetSocket(const char* name, int family, int type, int protocol);
|
||||
int PS4_SYSV_ABI sceNetSocketAbort(OrbisNetId s, int flags);
|
||||
int PS4_SYSV_ABI sceNetSocketClose(OrbisNetId s);
|
||||
int PS4_SYSV_ABI sceNetSyncCreate();
|
||||
int PS4_SYSV_ABI sceNetSyncDestroy();
|
||||
int PS4_SYSV_ABI sceNetSyncGet();
|
||||
int PS4_SYSV_ABI sceNetSyncSignal();
|
||||
int PS4_SYSV_ABI sceNetSyncWait();
|
||||
int PS4_SYSV_ABI sceNetSysctl();
|
||||
int PS4_SYSV_ABI sceNetTerm();
|
||||
int PS4_SYSV_ABI sceNetThreadCreate();
|
||||
int PS4_SYSV_ABI sceNetThreadExit();
|
||||
int PS4_SYSV_ABI sceNetThreadJoin();
|
||||
int PS4_SYSV_ABI sceNetUsleep();
|
||||
int PS4_SYSV_ABI Func_0E707A589F751C68();
|
||||
int PS4_SYSV_ABI sceNetEmulationGet();
|
||||
int PS4_SYSV_ABI sceNetEmulationSet();
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Net
|
||||
@@ -1,79 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/types.h"
|
||||
#include "net_epoll.h"
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
u32 ConvertEpollEventsIn(u32 orbis_events) {
|
||||
u32 ret = 0;
|
||||
|
||||
#ifndef __FreeBSD__
|
||||
if ((orbis_events & ORBIS_NET_EPOLLIN) != 0) {
|
||||
ret |= EPOLLIN;
|
||||
}
|
||||
if ((orbis_events & ORBIS_NET_EPOLLOUT) != 0) {
|
||||
ret |= EPOLLOUT;
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
u32 ConvertEpollEventsOut(u32 epoll_events) {
|
||||
u32 ret = 0;
|
||||
|
||||
#ifndef __FreeBSD__
|
||||
if ((epoll_events & EPOLLIN) != 0) {
|
||||
ret |= ORBIS_NET_EPOLLIN;
|
||||
}
|
||||
if ((epoll_events & EPOLLOUT) != 0) {
|
||||
ret |= ORBIS_NET_EPOLLOUT;
|
||||
}
|
||||
if ((epoll_events & EPOLLERR) != 0) {
|
||||
ret |= ORBIS_NET_EPOLLERR;
|
||||
}
|
||||
if ((epoll_events & EPOLLHUP) != 0) {
|
||||
ret |= ORBIS_NET_EPOLLHUP;
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int EpollTable::CreateHandle(const char* name) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
if (auto it = std::ranges::find_if(epolls, [](const Epoll& e) { return e.Destroyed(); });
|
||||
it != epolls.end()) {
|
||||
*it = Epoll(name);
|
||||
const auto ret = std::distance(epolls.begin(), it);
|
||||
LOG_DEBUG(Lib_Net, "epollid = {}", ret);
|
||||
return ret;
|
||||
} else {
|
||||
epolls.emplace_back(name);
|
||||
const auto ret = epolls.size() - 1;
|
||||
LOG_DEBUG(Lib_Net, "epollid = {}", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
void EpollTable::DeleteHandle(int d) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
Epoll* EpollTable::GetEpoll(int epollid) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (epollid >= epolls.size() || epolls[epollid].Destroyed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &epolls[epollid];
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -1,86 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/network/net.h"
|
||||
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <wepoll.h>
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
|
||||
// ADD libepoll-shim if using freebsd!
|
||||
#include <sys/epoll.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
#ifdef _WIN32
|
||||
using epoll_handle = HANDLE;
|
||||
#else
|
||||
using epoll_handle = int;
|
||||
#endif
|
||||
|
||||
struct Epoll {
|
||||
std::vector<std::pair<u32 /*netId*/, OrbisNetEpollEvent>> events{};
|
||||
std::string name;
|
||||
epoll_handle epoll_fd;
|
||||
std::deque<u32> async_resolutions{};
|
||||
|
||||
explicit Epoll(const char* name_) : name(name_), epoll_fd(epoll_create1(0)) {
|
||||
#ifdef _WIN32
|
||||
ASSERT(epoll_fd != nullptr);
|
||||
#else
|
||||
ASSERT(epoll_fd != -1);
|
||||
#endif
|
||||
if (name_ == nullptr) {
|
||||
name = "anon";
|
||||
}
|
||||
}
|
||||
|
||||
bool Destroyed() const noexcept {
|
||||
return destroyed;
|
||||
}
|
||||
|
||||
void Destroy() noexcept {
|
||||
events.clear();
|
||||
#ifdef _WIN32
|
||||
epoll_close(epoll_fd);
|
||||
epoll_fd = nullptr;
|
||||
#else
|
||||
close(epoll_fd);
|
||||
epoll_fd = -1;
|
||||
#endif
|
||||
name = "";
|
||||
destroyed = true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool destroyed{};
|
||||
};
|
||||
|
||||
u32 ConvertEpollEventsIn(u32 orbis_events);
|
||||
u32 ConvertEpollEventsOut(u32 epoll_events);
|
||||
|
||||
class EpollTable {
|
||||
public:
|
||||
EpollTable() = default;
|
||||
virtual ~EpollTable() = default;
|
||||
|
||||
int CreateHandle(const char* name);
|
||||
void DeleteHandle(int d);
|
||||
Epoll* GetEpoll(int d);
|
||||
|
||||
private:
|
||||
std::vector<Epoll> epolls;
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Net
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -1,48 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/types.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "net_error.h"
|
||||
#include "net_resolver.h"
|
||||
#include "net_util.h"
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
int Resolver::ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeout, int retry,
|
||||
int flags) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
if (async_resolution) {
|
||||
*sceNetErrnoLoc() = ORBIS_NET_RESOLVER_EBUSY;
|
||||
return ORBIS_NET_ERROR_RESOLVER_EBUSY;
|
||||
}
|
||||
|
||||
async_resolution = AsyncResolution{hostname, addr, timeout, retry, flags};
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void Resolver::Resolve() {
|
||||
if (!EmulatorSettings.IsConnectedToNetwork()) {
|
||||
resolution_error = ORBIS_NET_ERROR_RESOLVER_ENODNS;
|
||||
return;
|
||||
}
|
||||
|
||||
if (async_resolution) {
|
||||
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
|
||||
auto ret = netinfo->ResolveHostname(async_resolution->hostname, async_resolution->addr);
|
||||
resolution_error = ret;
|
||||
if (ret != ORBIS_OK) {
|
||||
// Resolver errors are stored as ORBIS_NET_ERROR values.
|
||||
resolution_error = -ret | ORBIS_NET_ERROR_BASE;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Lib_Net, "async resolution has not been set-up");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -1,39 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/network/net.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
struct Resolver {
|
||||
public:
|
||||
Resolver(const char* name, int poolid, int flags) : name(name), poolid(poolid), flags(flags) {}
|
||||
|
||||
int ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeout, int retry, int flags);
|
||||
void Resolve();
|
||||
|
||||
int resolution_error = ORBIS_OK;
|
||||
|
||||
private:
|
||||
struct AsyncResolution {
|
||||
const char* hostname;
|
||||
OrbisNetInAddr* addr;
|
||||
int timeout;
|
||||
int retry;
|
||||
int flags;
|
||||
};
|
||||
|
||||
std::string name;
|
||||
int poolid;
|
||||
int flags;
|
||||
std::optional<AsyncResolution> async_resolution{};
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -1,150 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <upnperrors.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "net_upnp.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#endif
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
UPnPClient& UPnPClient::Instance() {
|
||||
static UPnPClient instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
UPnPClient::~UPnPClient() {
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
if (m_available.load())
|
||||
FreeUPNPUrls(&m_urls);
|
||||
}
|
||||
|
||||
void UPnPClient::Start() {
|
||||
if (!m_p2p_features_enabled.load()) {
|
||||
LOG_INFO(Lib_Net, "UPNP: skipped because Matching2 is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
bool expected = false;
|
||||
if (!m_started.compare_exchange_strong(expected, true))
|
||||
return;
|
||||
LOG_INFO(Lib_Net, "UPNP: starting discovery thread");
|
||||
m_thread = std::thread(&UPnPClient::DiscoverThread, this);
|
||||
}
|
||||
|
||||
void UPnPClient::SetP2PFeaturesEnabled(bool enabled) {
|
||||
m_p2p_features_enabled.store(enabled);
|
||||
}
|
||||
|
||||
bool UPnPClient::WaitReady(int timeout_ms) {
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this] { return m_done.load(); });
|
||||
return m_available.load();
|
||||
}
|
||||
|
||||
bool UPnPClient::IsAvailable() const {
|
||||
return m_available.load();
|
||||
}
|
||||
|
||||
u32 UPnPClient::GetExternalIp() const {
|
||||
return m_external_ip_nbo.load();
|
||||
}
|
||||
|
||||
std::string UPnPClient::GetExternalIpString() const {
|
||||
return m_available.load() ? std::string(m_external_ip_str) : std::string{};
|
||||
}
|
||||
|
||||
u16 UPnPClient::GetExternalPort() const {
|
||||
return m_external_port.load();
|
||||
}
|
||||
|
||||
void UPnPClient::DiscoverThread() {
|
||||
int error = 0;
|
||||
UPNPDev* devlist = upnpDiscover(2000, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, 0, 2, &error);
|
||||
|
||||
if (!devlist) {
|
||||
LOG_INFO(Lib_Net, "UPNP: no IGD found (error={})", error);
|
||||
m_done = true;
|
||||
m_cv.notify_all();
|
||||
return;
|
||||
}
|
||||
|
||||
char wan_addr[40]{};
|
||||
const int ret = UPNP_GetValidIGD(devlist, &m_urls, &m_data, m_lan_addr, sizeof(m_lan_addr),
|
||||
wan_addr, sizeof(wan_addr));
|
||||
freeUPNPDevlist(devlist);
|
||||
|
||||
if (ret != 1) {
|
||||
LOG_INFO(Lib_Net, "UPNP: no valid IGD (ret={})", ret);
|
||||
m_done = true;
|
||||
m_cv.notify_all();
|
||||
return;
|
||||
}
|
||||
|
||||
char external_ip[40]{};
|
||||
if (UPNP_GetExternalIPAddress(m_urls.controlURL, m_data.first.servicetype, external_ip) !=
|
||||
UPNPCOMMAND_SUCCESS) {
|
||||
LOG_WARNING(Lib_Net, "UPNP: IGD found but failed to get external IP");
|
||||
FreeUPNPUrls(&m_urls);
|
||||
m_done = true;
|
||||
m_cv.notify_all();
|
||||
return;
|
||||
}
|
||||
|
||||
u32 ip_nbo = 0;
|
||||
inet_pton(AF_INET, external_ip, &ip_nbo);
|
||||
m_external_ip_nbo.store(ip_nbo);
|
||||
std::strncpy(m_external_ip_str, external_ip, sizeof(m_external_ip_str) - 1);
|
||||
m_available = true;
|
||||
|
||||
LOG_INFO(Lib_Net, "UPNP: IGD found - external IP={} lan={} (port mapping available)",
|
||||
external_ip, m_lan_addr);
|
||||
|
||||
m_done = true;
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
void UPnPClient::AddMapping(u16 port) {
|
||||
if (!m_p2p_features_enabled.load())
|
||||
return;
|
||||
|
||||
if (!m_available.load())
|
||||
return;
|
||||
|
||||
const std::string port_str = std::to_string(port);
|
||||
const int ret =
|
||||
UPNP_AddPortMapping(m_urls.controlURL, m_data.first.servicetype, port_str.c_str(),
|
||||
port_str.c_str(), m_lan_addr, "shadPS4", "UDP", nullptr, "0");
|
||||
|
||||
if (ret == UPNPCOMMAND_SUCCESS) {
|
||||
m_external_port.store(port);
|
||||
LOG_INFO(Lib_Net, "UPNP: mapped UDP port {}", port);
|
||||
} else {
|
||||
// Store the requested port as best-guess even on failure so callers still get a value.
|
||||
m_external_port.store(port);
|
||||
LOG_WARNING(Lib_Net, "UPNP: failed to map port {} ({})", port, strupnperror(ret));
|
||||
}
|
||||
}
|
||||
|
||||
void UPnPClient::RemoveMapping(u16 port) {
|
||||
if (!m_available.load())
|
||||
return;
|
||||
|
||||
const std::string port_str = std::to_string(port);
|
||||
UPNP_DeletePortMapping(m_urls.controlURL, m_data.first.servicetype, port_str.c_str(), "UDP",
|
||||
nullptr);
|
||||
|
||||
LOG_INFO(Lib_Net, "UPNP: removed UDP port mapping {}", port);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,712 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <vector>
|
||||
#include <common/assert.h>
|
||||
#include "common/error.h"
|
||||
#include "core/libraries/kernel/file_system.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "net.h"
|
||||
#ifndef _WIN32
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
#include "net_error.h"
|
||||
#include "sockets.h"
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
#ifdef _WIN32
|
||||
#define ERROR_CASE(errname) \
|
||||
case (WSA##errname): \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
|
||||
return -1;
|
||||
#else
|
||||
#define ERROR_CASE(errname) \
|
||||
case (errname): \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
static int ConvertReturnErrorCode(int retval) {
|
||||
if (retval < 0) {
|
||||
#ifdef _WIN32
|
||||
switch (WSAGetLastError()) {
|
||||
#else
|
||||
switch (errno) {
|
||||
#endif
|
||||
#ifndef _WIN32 // These errorcodes don't exist in WinSock
|
||||
ERROR_CASE(EPERM)
|
||||
ERROR_CASE(ENOENT)
|
||||
// ERROR_CASE(ESRCH)
|
||||
// ERROR_CASE(EIO)
|
||||
// ERROR_CASE(ENXIO)
|
||||
// ERROR_CASE(E2BIG)
|
||||
// ERROR_CASE(ENOEXEC)
|
||||
// ERROR_CASE(EDEADLK)
|
||||
ERROR_CASE(ENOMEM)
|
||||
// ERROR_CASE(ECHILD)
|
||||
// ERROR_CASE(EBUSY)
|
||||
ERROR_CASE(EEXIST)
|
||||
// ERROR_CASE(EXDEV)
|
||||
ERROR_CASE(ENODEV)
|
||||
// ERROR_CASE(ENOTDIR)
|
||||
// ERROR_CASE(EISDIR)
|
||||
ERROR_CASE(ENFILE)
|
||||
// ERROR_CASE(ENOTTY)
|
||||
// ERROR_CASE(ETXTBSY)
|
||||
// ERROR_CASE(EFBIG)
|
||||
ERROR_CASE(ENOSPC)
|
||||
// ERROR_CASE(ESPIPE)
|
||||
// ERROR_CASE(EROFS)
|
||||
// ERROR_CASE(EMLINK)
|
||||
ERROR_CASE(EPIPE)
|
||||
// ERROR_CASE(EDOM)
|
||||
// ERROR_CASE(ERANGE)
|
||||
// ERROR_CASE(ENOLCK)
|
||||
// ERROR_CASE(ENOSYS)
|
||||
// ERROR_CASE(EIDRM)
|
||||
// ERROR_CASE(EOVERFLOW)
|
||||
// ERROR_CASE(EILSEQ)
|
||||
// ERROR_CASE(ENOTSUP)
|
||||
ERROR_CASE(ECANCELED)
|
||||
// ERROR_CASE(EBADMSG)
|
||||
ERROR_CASE(ENODATA)
|
||||
// ERROR_CASE(ENOSR)
|
||||
// ERROR_CASE(ENOSTR)
|
||||
// ERROR_CASE(ETIME)
|
||||
#endif
|
||||
ERROR_CASE(EINTR)
|
||||
ERROR_CASE(EBADF)
|
||||
ERROR_CASE(EACCES)
|
||||
ERROR_CASE(EFAULT)
|
||||
ERROR_CASE(EINVAL)
|
||||
ERROR_CASE(EMFILE)
|
||||
ERROR_CASE(EWOULDBLOCK)
|
||||
ERROR_CASE(EINPROGRESS)
|
||||
ERROR_CASE(EALREADY)
|
||||
ERROR_CASE(ENOTSOCK)
|
||||
ERROR_CASE(EDESTADDRREQ)
|
||||
ERROR_CASE(EMSGSIZE)
|
||||
ERROR_CASE(EPROTOTYPE)
|
||||
ERROR_CASE(ENOPROTOOPT)
|
||||
ERROR_CASE(EPROTONOSUPPORT)
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
ERROR_CASE(EOPNOTSUPP)
|
||||
#endif
|
||||
ERROR_CASE(EAFNOSUPPORT)
|
||||
ERROR_CASE(EADDRINUSE)
|
||||
ERROR_CASE(EADDRNOTAVAIL)
|
||||
ERROR_CASE(ENETDOWN)
|
||||
ERROR_CASE(ENETUNREACH)
|
||||
ERROR_CASE(ENETRESET)
|
||||
ERROR_CASE(ECONNABORTED)
|
||||
ERROR_CASE(ECONNRESET)
|
||||
ERROR_CASE(ENOBUFS)
|
||||
ERROR_CASE(EISCONN)
|
||||
ERROR_CASE(ENOTCONN)
|
||||
ERROR_CASE(ETIMEDOUT)
|
||||
ERROR_CASE(ECONNREFUSED)
|
||||
ERROR_CASE(ELOOP)
|
||||
ERROR_CASE(ENAMETOOLONG)
|
||||
ERROR_CASE(EHOSTUNREACH)
|
||||
ERROR_CASE(ENOTEMPTY)
|
||||
}
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EINTERNAL;
|
||||
return -1;
|
||||
}
|
||||
// if it is 0 or positive return it as it is
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int ConvertLevels(int level) {
|
||||
switch (level) {
|
||||
case ORBIS_NET_SOL_SOCKET:
|
||||
return SOL_SOCKET;
|
||||
case ORBIS_NET_IPPROTO_IP:
|
||||
return IPPROTO_IP;
|
||||
case ORBIS_NET_IPPROTO_TCP:
|
||||
return IPPROTO_TCP;
|
||||
case ORBIS_NET_IPPROTO_IPV6:
|
||||
return IPPROTO_IPV6;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void convertOrbisNetSockaddrToPosix(const OrbisNetSockaddr* src, sockaddr* dst) {
|
||||
if (src == nullptr || dst == nullptr)
|
||||
return;
|
||||
memset(dst, 0, sizeof(sockaddr));
|
||||
const OrbisNetSockaddrIn* src_in = (const OrbisNetSockaddrIn*)src;
|
||||
sockaddr_in* dst_in = (sockaddr_in*)dst;
|
||||
dst_in->sin_family = src_in->sin_family;
|
||||
dst_in->sin_port = src_in->sin_port;
|
||||
memcpy(&dst_in->sin_addr, &src_in->sin_addr, 4);
|
||||
}
|
||||
|
||||
static void convertPosixSockaddrToOrbis(sockaddr* src, OrbisNetSockaddr* dst) {
|
||||
if (src == nullptr || dst == nullptr)
|
||||
return;
|
||||
memset(dst, 0, sizeof(OrbisNetSockaddr));
|
||||
OrbisNetSockaddrIn* dst_in = (OrbisNetSockaddrIn*)dst;
|
||||
sockaddr_in* src_in = (sockaddr_in*)src;
|
||||
dst_in->sin_family = static_cast<unsigned char>(src_in->sin_family);
|
||||
dst_in->sin_port = src_in->sin_port;
|
||||
memcpy(&dst_in->sin_addr, &src_in->sin_addr, 4);
|
||||
}
|
||||
|
||||
bool PosixSocket::IsValid() const {
|
||||
#ifdef _WIN32
|
||||
return sock != INVALID_SOCKET;
|
||||
#else
|
||||
return sock != -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
int PosixSocket::Close() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
#ifdef _WIN32
|
||||
auto out = closesocket(sock);
|
||||
#else
|
||||
auto out = ::close(sock);
|
||||
#endif
|
||||
return ConvertReturnErrorCode(out);
|
||||
}
|
||||
|
||||
int PosixSocket::Bind(const OrbisNetSockaddr* addr, u32 addrlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
sockaddr addr2;
|
||||
convertOrbisNetSockaddrToPosix(addr, &addr2);
|
||||
return ConvertReturnErrorCode(::bind(sock, &addr2, sizeof(sockaddr_in)));
|
||||
}
|
||||
|
||||
int PosixSocket::Listen(int backlog) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return ConvertReturnErrorCode(::listen(sock, backlog));
|
||||
}
|
||||
|
||||
static int convertOrbisFlagsToPosix(int sock_type, int sce_flags) {
|
||||
int posix_flags = 0;
|
||||
|
||||
if (sce_flags & ORBIS_NET_MSG_PEEK)
|
||||
posix_flags |= MSG_PEEK;
|
||||
#ifndef _WIN32
|
||||
if (sce_flags & ORBIS_NET_MSG_DONTWAIT)
|
||||
posix_flags |= MSG_DONTWAIT;
|
||||
#endif
|
||||
// MSG_WAITALL is only valid for stream sockets
|
||||
if ((sce_flags & ORBIS_NET_MSG_WAITALL) &&
|
||||
((sock_type == ORBIS_NET_SOCK_STREAM) || (sock_type == ORBIS_NET_SOCK_STREAM_P2P)))
|
||||
posix_flags |= MSG_WAITALL;
|
||||
|
||||
return posix_flags;
|
||||
}
|
||||
|
||||
// On Windows, MSG_DONTWAIT is not handled natively by recv/send.
|
||||
// This function uses select() with zero timeout to simulate non-blocking behavior.
|
||||
static int socket_is_ready(int sock, bool is_read = true) {
|
||||
fd_set fds{};
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(sock, &fds);
|
||||
timeval timeout{0, 0};
|
||||
int res =
|
||||
select(sock + 1, is_read ? &fds : nullptr, is_read ? nullptr : &fds, nullptr, &timeout);
|
||||
if (res == 0) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EWOULDBLOCK;
|
||||
return -1;
|
||||
} else if (res < 0) {
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int PosixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
#ifdef _WIN32
|
||||
int totalSent = 0;
|
||||
bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0;
|
||||
bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0;
|
||||
|
||||
// stream socket with multiple buffers
|
||||
bool use_wsamsg =
|
||||
(socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) &&
|
||||
msg->msg_iovlen > 1;
|
||||
|
||||
for (int i = 0; i < msg->msg_iovlen; ++i) {
|
||||
char* buf = (char*)msg->msg_iov[i].iov_base;
|
||||
size_t remaining = msg->msg_iov[i].iov_len;
|
||||
|
||||
while (remaining > 0) {
|
||||
if (dontWait) {
|
||||
int ready = socket_is_ready(sock, false);
|
||||
if (ready <= 0)
|
||||
return ready;
|
||||
}
|
||||
|
||||
int sent = 0;
|
||||
if (use_wsamsg) {
|
||||
// only call WSASendMsg if we have multiple buffers
|
||||
LPFN_WSASENDMSG wsasendmsg = nullptr;
|
||||
GUID guid = WSAID_WSASENDMSG;
|
||||
DWORD bytes = 0;
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
|
||||
&wsasendmsg, sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
// fallback to send()
|
||||
sent = ::send(sock, buf, remaining, 0);
|
||||
} else {
|
||||
DWORD bytesSent = 0;
|
||||
int res = wsasendmsg(
|
||||
sock, reinterpret_cast<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(msg)), 0,
|
||||
&bytesSent, nullptr, nullptr);
|
||||
if (res == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
sent = bytesSent;
|
||||
}
|
||||
} else {
|
||||
sent = ::send(sock, buf, remaining, 0);
|
||||
if (sent == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
}
|
||||
|
||||
totalSent += sent;
|
||||
remaining -= sent;
|
||||
buf += sent;
|
||||
|
||||
if (!waitAll)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return totalSent;
|
||||
|
||||
#else
|
||||
int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
int res = sendmsg(sock, reinterpret_cast<const msghdr*>(msg), native_flags);
|
||||
return ConvertReturnErrorCode(res);
|
||||
#endif
|
||||
}
|
||||
|
||||
int PosixSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
|
||||
u32 tolen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
int res = 0;
|
||||
#ifdef _WIN32
|
||||
if (flags & ORBIS_NET_MSG_DONTWAIT) {
|
||||
res = socket_is_ready(sock, false);
|
||||
if (res <= 0)
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
if (to == nullptr) {
|
||||
res = send(sock, (const char*)msg, len, posix_flags);
|
||||
} else {
|
||||
sockaddr addr{};
|
||||
convertOrbisNetSockaddrToPosix(to, &addr);
|
||||
res = sendto(sock, (const char*)msg, len, posix_flags, &addr, tolen);
|
||||
}
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
int PosixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
|
||||
std::scoped_lock lock{receive_mutex};
|
||||
|
||||
#ifdef _WIN32
|
||||
int totalReceived = 0;
|
||||
bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0;
|
||||
bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0;
|
||||
|
||||
// stream socket with multiple buffers
|
||||
bool use_wsarecvmsg =
|
||||
(socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) &&
|
||||
msg->msg_iovlen > 1;
|
||||
|
||||
for (int i = 0; i < msg->msg_iovlen; ++i) {
|
||||
char* buf = (char*)msg->msg_iov[i].iov_base;
|
||||
size_t remaining = msg->msg_iov[i].iov_len;
|
||||
|
||||
while (remaining > 0) {
|
||||
// emulate DONTWAIT
|
||||
if (dontWait) {
|
||||
int ready = socket_is_ready(sock, true);
|
||||
if (ready <= 0)
|
||||
return ready; // returns ORBIS_NET_ERROR_EWOULDBLOCK or error
|
||||
}
|
||||
|
||||
int received = 0;
|
||||
if (use_wsarecvmsg) {
|
||||
// only call WSARecvMsg if multiple buffers + stream
|
||||
LPFN_WSARECVMSG wsarecvmsg = nullptr;
|
||||
GUID guid = WSAID_WSARECVMSG;
|
||||
DWORD bytes = 0;
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
|
||||
&wsarecvmsg, sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
// fallback to recv()
|
||||
received = ::recv(sock, buf, remaining, 0);
|
||||
if (received == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
} else {
|
||||
DWORD bytesReceived = 0;
|
||||
int res = wsarecvmsg(sock, reinterpret_cast<LPWSAMSG>(msg), &bytesReceived,
|
||||
nullptr, nullptr);
|
||||
if (res == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
received = bytesReceived;
|
||||
}
|
||||
} else {
|
||||
// fallback to recv() for UDP or single-buffer
|
||||
received = ::recv(sock, buf, remaining, 0);
|
||||
if (received == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
}
|
||||
|
||||
totalReceived += received;
|
||||
remaining -= received;
|
||||
buf += received;
|
||||
|
||||
// stop after first receive if WAITALL is not set
|
||||
if (!waitAll)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return totalReceived;
|
||||
|
||||
#else
|
||||
int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
int res = recvmsg(sock, reinterpret_cast<msghdr*>(msg), native_flags);
|
||||
return ConvertReturnErrorCode(res);
|
||||
#endif
|
||||
}
|
||||
|
||||
int PosixSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from,
|
||||
u32* fromlen) {
|
||||
std::scoped_lock lock{receive_mutex};
|
||||
int res = 0;
|
||||
#ifdef _WIN32
|
||||
if (flags & ORBIS_NET_MSG_DONTWAIT) {
|
||||
res = socket_is_ready(sock);
|
||||
if (res <= 0)
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
if (from == nullptr) {
|
||||
res = recv(sock, (char*)buf, len, posix_flags);
|
||||
} else {
|
||||
sockaddr addr{};
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
res = recvfrom(sock, (char*)buf, len, posix_flags, &addr,
|
||||
(fromlen && *fromlen <= sizeof(addr) ? (socklen_t*)fromlen : &addrlen));
|
||||
if (res > 0)
|
||||
convertPosixSockaddrToOrbis(&addr, from);
|
||||
}
|
||||
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
SocketPtr PosixSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
sockaddr addr2;
|
||||
socklen_t len = sizeof(addr2);
|
||||
net_socket new_socket = ::accept(sock, &addr2, &len);
|
||||
#ifdef _WIN32
|
||||
if (new_socket != INVALID_SOCKET) {
|
||||
#else
|
||||
if (new_socket >= 0) {
|
||||
#endif
|
||||
if (addr && addrlen) {
|
||||
convertPosixSockaddrToOrbis(&addr2, addr);
|
||||
*addrlen = sizeof(OrbisNetSockaddrIn);
|
||||
}
|
||||
return std::make_shared<PosixSocket>(new_socket);
|
||||
}
|
||||
ConvertReturnErrorCode(new_socket);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int PosixSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
sockaddr addr2;
|
||||
convertOrbisNetSockaddrToPosix(addr, &addr2);
|
||||
int result = ::connect(sock, &addr2, sizeof(sockaddr_in));
|
||||
#ifdef _WIN32
|
||||
// Winsock returns EWOULDBLOCK where real hardware returns EINPROGRESS
|
||||
// Step in here on errors to address this.
|
||||
if (result == -1) {
|
||||
if (WSAGetLastError() == WSAEWOULDBLOCK) {
|
||||
WSASetLastError(WSAEINPROGRESS);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
LOG_DEBUG(Lib_Net, "raw connect result = {}, errno = {}", result,
|
||||
result == -1 ? Common::GetLastErrorMsg() : "none");
|
||||
return ConvertReturnErrorCode(result);
|
||||
}
|
||||
|
||||
int PosixSocket::GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
sockaddr addr;
|
||||
convertOrbisNetSockaddrToPosix(name, &addr);
|
||||
if (name != nullptr) {
|
||||
*namelen = sizeof(sockaddr_in);
|
||||
}
|
||||
int res = getsockname(sock, &addr, (socklen_t*)namelen);
|
||||
if (res >= 0) {
|
||||
convertPosixSockaddrToOrbis(&addr, name);
|
||||
*namelen = sizeof(OrbisNetSockaddrIn);
|
||||
}
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
#define CASE_SETSOCKOPT(opt) \
|
||||
case ORBIS_NET_##opt: \
|
||||
return ConvertReturnErrorCode( \
|
||||
setsockopt(sock, native_level, opt, (const char*)optval, optlen))
|
||||
|
||||
#define CASE_SETSOCKOPT_VALUE(opt, value) \
|
||||
case opt: \
|
||||
if (optlen != sizeof(*value)) { \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
|
||||
return -1; \
|
||||
} \
|
||||
memcpy(value, optval, optlen); \
|
||||
return 0
|
||||
|
||||
int PosixSocket::SetSocketOptions(int level, int optname, const void* optval, u32 optlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
s32 native_level = ConvertLevels(level);
|
||||
::linger native_linger;
|
||||
if (native_level == SOL_SOCKET) {
|
||||
switch (optname) {
|
||||
CASE_SETSOCKOPT(SO_REUSEADDR);
|
||||
CASE_SETSOCKOPT(SO_KEEPALIVE);
|
||||
CASE_SETSOCKOPT(SO_BROADCAST);
|
||||
CASE_SETSOCKOPT(SO_SNDBUF);
|
||||
CASE_SETSOCKOPT(SO_RCVBUF);
|
||||
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_CONNECTTIMEO, &sockopt_so_connecttimeo);
|
||||
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_REUSEPORT, &sockopt_so_reuseport);
|
||||
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_USECRYPTO, &sockopt_so_usecrypto);
|
||||
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_USESIGNATURE, &sockopt_so_usesignature);
|
||||
case ORBIS_NET_SO_SNDTIMEO:
|
||||
case ORBIS_NET_SO_RCVTIMEO: {
|
||||
if (optlen != sizeof(int)) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_ERROR_EFAULT;
|
||||
return -1;
|
||||
}
|
||||
std::vector<char> val;
|
||||
const auto optname_nat = (optname == ORBIS_NET_SO_SNDTIMEO) ? SO_SNDTIMEO : SO_RCVTIMEO;
|
||||
int timeout_us = *(const int*)optval;
|
||||
#ifdef _WIN32
|
||||
DWORD timeout = timeout_us / 1000;
|
||||
#else
|
||||
timeval timeout{.tv_sec = timeout_us / 1000000, .tv_usec = timeout_us % 1000000};
|
||||
#endif
|
||||
val.insert(val.end(), (char*)&timeout, (char*)&timeout + sizeof(timeout));
|
||||
optlen = sizeof(timeout);
|
||||
return ConvertReturnErrorCode(
|
||||
setsockopt(sock, native_level, optname_nat, val.data(), optlen));
|
||||
}
|
||||
case ORBIS_NET_SO_ONESBCAST: {
|
||||
|
||||
if (optlen != sizeof(sockopt_so_onesbcast)) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_ERROR_EFAULT;
|
||||
return -1;
|
||||
}
|
||||
memcpy(&sockopt_so_onesbcast, optval, optlen);
|
||||
return ConvertReturnErrorCode(
|
||||
setsockopt(sock, native_level, SO_BROADCAST, (const char*)optval, optlen));
|
||||
}
|
||||
case ORBIS_NET_SO_TYPE:
|
||||
case ORBIS_NET_SO_ERROR: {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_ENOPROTOOPT;
|
||||
return -1;
|
||||
}
|
||||
case ORBIS_NET_SO_LINGER: {
|
||||
if (socket_type != ORBIS_NET_SOCK_STREAM) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EPROCUNAVAIL;
|
||||
return -1;
|
||||
}
|
||||
if (optlen < sizeof(OrbisNetLinger)) {
|
||||
LOG_ERROR(Lib_Net, "size missmatched! optlen = {} OrbisNetLinger={}", optlen,
|
||||
sizeof(OrbisNetLinger));
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const void* native_val = &native_linger;
|
||||
u32 native_len = sizeof(native_linger);
|
||||
native_linger.l_onoff = reinterpret_cast<const OrbisNetLinger*>(optval)->l_onoff;
|
||||
native_linger.l_linger = reinterpret_cast<const OrbisNetLinger*>(optval)->l_linger;
|
||||
return ConvertReturnErrorCode(
|
||||
setsockopt(sock, native_level, SO_LINGER, (const char*)native_val, native_len));
|
||||
}
|
||||
|
||||
case ORBIS_NET_SO_NAME:
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
|
||||
return -1; // don't support set for name
|
||||
case ORBIS_NET_SO_NBIO: {
|
||||
if (optlen < sizeof(sockopt_so_nbio)) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
|
||||
return -1;
|
||||
} else {
|
||||
memcpy(&sockopt_so_nbio, optval, sizeof(sockopt_so_nbio));
|
||||
}
|
||||
#ifdef _WIN32
|
||||
static_assert(sizeof(u_long) == sizeof(sockopt_so_nbio),
|
||||
"type used for ioctlsocket value does not have the expected size");
|
||||
return ConvertReturnErrorCode(ioctlsocket(sock, FIONBIO, (u_long*)&sockopt_so_nbio));
|
||||
#else
|
||||
return ConvertReturnErrorCode(ioctl(sock, FIONBIO, &sockopt_so_nbio));
|
||||
#endif
|
||||
}
|
||||
case ORBIS_NET_SO_ACCEPTTIMEO:
|
||||
LOG_ERROR(Lib_Net, "Unhandled option ORBIS_NET_SO_ACCEPTTIMEO");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
} else if (native_level == IPPROTO_IP) {
|
||||
switch (optname) {
|
||||
// CASE_SETSOCKOPT(IP_HDRINCL);
|
||||
CASE_SETSOCKOPT(IP_TOS);
|
||||
CASE_SETSOCKOPT(IP_TTL);
|
||||
CASE_SETSOCKOPT(IP_MULTICAST_IF);
|
||||
CASE_SETSOCKOPT(IP_MULTICAST_TTL);
|
||||
CASE_SETSOCKOPT(IP_MULTICAST_LOOP);
|
||||
CASE_SETSOCKOPT(IP_ADD_MEMBERSHIP);
|
||||
CASE_SETSOCKOPT(IP_DROP_MEMBERSHIP);
|
||||
CASE_SETSOCKOPT_VALUE(ORBIS_NET_IP_TTLCHK, &sockopt_ip_ttlchk);
|
||||
CASE_SETSOCKOPT_VALUE(ORBIS_NET_IP_MAXTTL, &sockopt_ip_maxttl);
|
||||
case ORBIS_NET_IP_HDRINCL: {
|
||||
if (socket_type != ORBIS_NET_SOCK_RAW) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EPROCUNAVAIL;
|
||||
return -1;
|
||||
}
|
||||
return ConvertReturnErrorCode(
|
||||
setsockopt(sock, native_level, optname, (const char*)optval, optlen));
|
||||
}
|
||||
}
|
||||
} else if (native_level == IPPROTO_TCP) {
|
||||
switch (optname) {
|
||||
CASE_SETSOCKOPT(TCP_NODELAY);
|
||||
CASE_SETSOCKOPT(TCP_MAXSEG);
|
||||
CASE_SETSOCKOPT_VALUE(ORBIS_NET_TCP_MSS_TO_ADVERTISE, &sockopt_tcp_mss_to_advertise);
|
||||
}
|
||||
}
|
||||
|
||||
UNREACHABLE_MSG("Unknown level ={} optname ={}", level, optname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CASE_GETSOCKOPT(opt) \
|
||||
case ORBIS_NET_##opt: { \
|
||||
socklen_t optlen_temp = *optlen; \
|
||||
auto retval = ConvertReturnErrorCode( \
|
||||
getsockopt(sock, native_level, opt, (char*)optval, &optlen_temp)); \
|
||||
*optlen = optlen_temp; \
|
||||
return retval; \
|
||||
}
|
||||
#define CASE_GETSOCKOPT_VALUE(opt, value) \
|
||||
case opt: \
|
||||
if (*optlen < sizeof(value)) { \
|
||||
*optlen = sizeof(value); \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
|
||||
return -1; \
|
||||
} \
|
||||
*optlen = sizeof(value); \
|
||||
*(decltype(value)*)optval = value; \
|
||||
return 0;
|
||||
|
||||
int PosixSocket::GetSocketOptions(int level, int optname, void* optval, u32* optlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
s32 native_level = ConvertLevels(level);
|
||||
if (native_level == SOL_SOCKET) {
|
||||
switch (optname) {
|
||||
CASE_GETSOCKOPT(SO_REUSEADDR);
|
||||
CASE_GETSOCKOPT(SO_KEEPALIVE);
|
||||
CASE_GETSOCKOPT(SO_BROADCAST);
|
||||
CASE_GETSOCKOPT(SO_LINGER);
|
||||
CASE_GETSOCKOPT(SO_SNDBUF);
|
||||
CASE_GETSOCKOPT(SO_RCVBUF);
|
||||
CASE_GETSOCKOPT(SO_SNDTIMEO);
|
||||
CASE_GETSOCKOPT(SO_RCVTIMEO);
|
||||
CASE_GETSOCKOPT(SO_ERROR);
|
||||
CASE_GETSOCKOPT(SO_TYPE);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_NBIO, sockopt_so_nbio);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_CONNECTTIMEO, sockopt_so_connecttimeo);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_REUSEPORT, sockopt_so_reuseport);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_ONESBCAST, sockopt_so_onesbcast);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_USECRYPTO, sockopt_so_usecrypto);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_USESIGNATURE, sockopt_so_usesignature);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_NAME,
|
||||
(char)0); // writes an empty string to the output buffer
|
||||
case ORBIS_NET_SO_ERROR_EX: {
|
||||
socklen_t optlen_temp = *optlen;
|
||||
auto retval = ConvertReturnErrorCode(
|
||||
getsockopt(sock, level, SO_ERROR, (char*)optval, &optlen_temp));
|
||||
*optlen = optlen_temp;
|
||||
if (retval < 0) {
|
||||
s32 r = *Libraries::Kernel::__Error();
|
||||
*Libraries::Kernel::__Error() = 0;
|
||||
return r;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
} else if (native_level == IPPROTO_IP) {
|
||||
switch (optname) {
|
||||
CASE_GETSOCKOPT(IP_HDRINCL);
|
||||
CASE_GETSOCKOPT(IP_TOS);
|
||||
CASE_GETSOCKOPT(IP_TTL);
|
||||
CASE_GETSOCKOPT(IP_MULTICAST_IF);
|
||||
CASE_GETSOCKOPT(IP_MULTICAST_TTL);
|
||||
CASE_GETSOCKOPT(IP_MULTICAST_LOOP);
|
||||
CASE_GETSOCKOPT(IP_ADD_MEMBERSHIP);
|
||||
CASE_GETSOCKOPT(IP_DROP_MEMBERSHIP);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_IP_TTLCHK, sockopt_ip_ttlchk);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_IP_MAXTTL, sockopt_ip_maxttl);
|
||||
}
|
||||
} else if (native_level == IPPROTO_TCP) {
|
||||
switch (optname) {
|
||||
CASE_GETSOCKOPT(TCP_NODELAY);
|
||||
CASE_GETSOCKOPT(TCP_MAXSEG);
|
||||
CASE_GETSOCKOPT_VALUE(ORBIS_NET_TCP_MSS_TO_ADVERTISE, sockopt_tcp_mss_to_advertise);
|
||||
}
|
||||
}
|
||||
UNREACHABLE_MSG("Unknown level ={} optname ={}", level, optname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PosixSocket::GetPeerName(OrbisNetSockaddr* name, u32* namelen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
sockaddr addr;
|
||||
convertOrbisNetSockaddrToPosix(name, &addr);
|
||||
if (name != nullptr) {
|
||||
*namelen = sizeof(sockaddr_in);
|
||||
}
|
||||
int res = ::getpeername(sock, &addr, (socklen_t*)namelen);
|
||||
if (res >= 0) {
|
||||
convertPosixSockaddrToOrbis(&addr, name);
|
||||
*namelen = sizeof(OrbisNetSockaddrIn);
|
||||
}
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
int PosixSocket::fstat(Libraries::Kernel::OrbisKernelStat* sb) {
|
||||
#ifdef _WIN32
|
||||
LOG_ERROR(Lib_Net, "(STUBBED) called");
|
||||
sb->st_mode = 0000777u | 0140000u;
|
||||
return 0;
|
||||
#else
|
||||
struct stat st{};
|
||||
int result = ::fstat(sock, &st);
|
||||
sb->st_mode = 0000777u | 0140000u;
|
||||
sb->st_size = st.st_size;
|
||||
sb->st_blocks = st.st_blocks;
|
||||
sb->st_blksize = st.st_blksize;
|
||||
// sb->st_flags = st.st_flags;
|
||||
return ConvertReturnErrorCode(result);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -1,242 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
// clang-format off
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <afunix.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <mstcpip.h>
|
||||
// clang-format on
|
||||
typedef SOCKET net_socket;
|
||||
typedef int socklen_t;
|
||||
#ifndef LPFN_WSASENDMSG
|
||||
typedef INT(PASCAL* LPFN_WSASENDMSG)(SOCKET s, LPWSAMSG lpMsg, DWORD dwFlags,
|
||||
LPDWORD lpNumberOfBytesSent, LPWSAOVERLAPPED lpOverlapped,
|
||||
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
|
||||
#endif
|
||||
#ifndef WSAID_WSASENDMSG
|
||||
static const GUID WSAID_WSASENDMSG = {
|
||||
0xa441e712, 0x754f, 0x43ca, {0x84, 0xa7, 0x0d, 0xee, 0x44, 0xcf, 0x60, 0x6d}};
|
||||
#endif
|
||||
#ifndef LPFN_WSARECVMSG
|
||||
typedef INT(PASCAL* LPFN_WSARECVMSG)(SOCKET s, LPWSAMSG lpMsg, LPDWORD lpdwNumberOfBytesRecvd,
|
||||
LPWSAOVERLAPPED lpOverlapped,
|
||||
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
|
||||
#endif
|
||||
|
||||
#ifndef WSAID_WSARECVMSG
|
||||
static const GUID WSAID_WSARECVMSG = {
|
||||
0xf689d7c8, 0x6f1f, 0x436b, {0x8a, 0x53, 0xe5, 0x4f, 0xe3, 0x51, 0xc3, 0x22}};
|
||||
#endif
|
||||
#else
|
||||
#include <cerrno>
|
||||
#include <arpa/inet.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
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include "net.h"
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
struct OrbisKernelStat;
|
||||
}
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
struct Socket;
|
||||
|
||||
typedef std::shared_ptr<Socket> SocketPtr;
|
||||
|
||||
struct OrbisNetLinger {
|
||||
s32 l_onoff;
|
||||
s32 l_linger;
|
||||
};
|
||||
struct Socket {
|
||||
explicit Socket(int domain, int type, int protocol) : socket_type(type) {}
|
||||
virtual ~Socket() = default;
|
||||
virtual bool IsValid() const = 0;
|
||||
virtual int Close() = 0;
|
||||
virtual int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) = 0;
|
||||
virtual int GetSocketOptions(int level, int optname, void* optval, u32* optlen) = 0;
|
||||
virtual int Bind(const OrbisNetSockaddr* addr, u32 addrlen) = 0;
|
||||
virtual int Listen(int backlog) = 0;
|
||||
virtual int SendMessage(const OrbisNetMsghdr* msg, int flags) = 0;
|
||||
virtual int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
|
||||
u32 tolen) = 0;
|
||||
virtual SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) = 0;
|
||||
virtual int ReceiveMessage(OrbisNetMsghdr* msg, int flags) = 0;
|
||||
virtual int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from,
|
||||
u32* fromlen) = 0;
|
||||
virtual int Connect(const OrbisNetSockaddr* addr, u32 namelen) = 0;
|
||||
virtual int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) = 0;
|
||||
virtual int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) = 0;
|
||||
virtual int fstat(Libraries::Kernel::OrbisKernelStat* stat) = 0;
|
||||
virtual std::optional<net_socket> Native() = 0;
|
||||
std::mutex m_mutex;
|
||||
std::mutex receive_mutex;
|
||||
int socket_type;
|
||||
};
|
||||
|
||||
struct PosixSocket : public Socket {
|
||||
net_socket sock;
|
||||
int sockopt_so_connecttimeo = 0;
|
||||
int sockopt_so_reuseport = 0;
|
||||
int sockopt_so_onesbcast = 0;
|
||||
int sockopt_so_usecrypto = 0;
|
||||
int sockopt_so_usesignature = 0;
|
||||
int sockopt_so_nbio = 0;
|
||||
int sockopt_ip_ttlchk = 0;
|
||||
int sockopt_ip_maxttl = 0;
|
||||
int sockopt_tcp_mss_to_advertise = 0;
|
||||
int socket_type;
|
||||
explicit PosixSocket(int domain, int type, int protocol)
|
||||
: Socket(domain, type, protocol), sock(socket(domain, type, protocol)) {
|
||||
socket_type = type;
|
||||
}
|
||||
explicit PosixSocket(net_socket sock) : Socket(0, 0, 0), sock(sock) {}
|
||||
bool IsValid() const override;
|
||||
int Close() override;
|
||||
int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) override;
|
||||
int GetSocketOptions(int level, int optname, void* optval, u32* optlen) override;
|
||||
int Bind(const OrbisNetSockaddr* addr, u32 addrlen) override;
|
||||
int Listen(int backlog) override;
|
||||
int SendMessage(const OrbisNetMsghdr* msg, int flags) override;
|
||||
int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
|
||||
u32 tolen) override;
|
||||
int ReceiveMessage(OrbisNetMsghdr* msg, int flags) override;
|
||||
int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) override;
|
||||
SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) override;
|
||||
int Connect(const OrbisNetSockaddr* addr, u32 namelen) override;
|
||||
int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override;
|
||||
int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override;
|
||||
int fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
|
||||
std::optional<net_socket> Native() override {
|
||||
return sock;
|
||||
}
|
||||
};
|
||||
|
||||
struct P2PDatagram {
|
||||
u32 source_addr{};
|
||||
u16 source_port{};
|
||||
u16 source_vport{};
|
||||
std::vector<u8> payload;
|
||||
};
|
||||
|
||||
struct P2PSocket : public Socket {
|
||||
explicit P2PSocket(int domain, int type, int protocol);
|
||||
~P2PSocket() override;
|
||||
bool IsValid() const override {
|
||||
return valid;
|
||||
}
|
||||
int Close() override;
|
||||
int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) override;
|
||||
int GetSocketOptions(int level, int optname, void* optval, u32* optlen) override;
|
||||
int Bind(const OrbisNetSockaddr* addr, u32 addrlen) override;
|
||||
int Listen(int backlog) override;
|
||||
int SendMessage(const OrbisNetMsghdr* msg, int flags) override;
|
||||
int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
|
||||
u32 tolen) override;
|
||||
int ReceiveMessage(OrbisNetMsghdr* msg, int flags) override;
|
||||
int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) override;
|
||||
SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) override;
|
||||
int Connect(const OrbisNetSockaddr* addr, u32 namelen) override;
|
||||
int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override;
|
||||
int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override;
|
||||
int fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
|
||||
std::optional<net_socket> Native() override {
|
||||
#ifdef _WIN32
|
||||
if (readiness_fd != INVALID_SOCKET) {
|
||||
return readiness_fd;
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
if (readiness_fd >= 0) {
|
||||
return readiness_fd;
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
// Called by the shared UDP transport after it demultiplexes a virtual port.
|
||||
void EnqueuePacket(P2PDatagram packet);
|
||||
|
||||
private:
|
||||
bool valid{true};
|
||||
bool bound{false};
|
||||
bool connected{false};
|
||||
OrbisNetSockaddrIn local_addr{};
|
||||
OrbisNetSockaddrIn peer_addr{};
|
||||
|
||||
int sockopt_so_nbio{0};
|
||||
int sockopt_so_usecrypto{0};
|
||||
int sockopt_so_usesignature{0};
|
||||
std::map<int, int> integer_socket_options;
|
||||
|
||||
std::condition_variable receive_cv;
|
||||
std::deque<P2PDatagram> receive_queue;
|
||||
|
||||
#ifdef _WIN32
|
||||
// wepoll requires a Winsock handle. A private loopback pair carries readiness bytes only.
|
||||
net_socket readiness_fd{INVALID_SOCKET};
|
||||
net_socket readiness_signal_fd{INVALID_SOCKET};
|
||||
#elif defined(__linux__)
|
||||
// eventfd carries readiness only; network data stays on the shared UDP transport.
|
||||
int readiness_fd{-1};
|
||||
#endif
|
||||
};
|
||||
|
||||
struct UnixSocket : public Socket {
|
||||
net_socket sock;
|
||||
int socket_type;
|
||||
explicit UnixSocket(int domain, int type, int protocol)
|
||||
: Socket(domain, type, protocol), sock(socket(domain, type, protocol)) {
|
||||
socket_type = type;
|
||||
}
|
||||
explicit UnixSocket(net_socket sock) : Socket(0, 0, 0), sock(sock) {}
|
||||
bool IsValid() const override;
|
||||
int Close() override;
|
||||
int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) override;
|
||||
int GetSocketOptions(int level, int optname, void* optval, u32* optlen) override;
|
||||
int Bind(const OrbisNetSockaddr* addr, u32 addrlen) override;
|
||||
int Listen(int backlog) override;
|
||||
int SendMessage(const OrbisNetMsghdr* msg, int flags) override;
|
||||
int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
|
||||
u32 tolen) override;
|
||||
int ReceiveMessage(OrbisNetMsghdr* msg, int flags) override;
|
||||
int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) override;
|
||||
SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) override;
|
||||
int Connect(const OrbisNetSockaddr* addr, u32 namelen) override;
|
||||
int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override;
|
||||
int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override;
|
||||
int fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
|
||||
std::optional<net_socket> Native() override {
|
||||
return sock;
|
||||
}
|
||||
};
|
||||
|
||||
u16 GetP2PConfiguredPort();
|
||||
u32 GetP2PAdvertisedAddr();
|
||||
bool EnsureP2PTransport();
|
||||
bool P2PTransportIsReady();
|
||||
int P2PSignalingSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
|
||||
int P2PSignalingRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
|
||||
int P2PControlSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
|
||||
int P2PControlRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
|
||||
int P2PMatching2SendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
|
||||
int P2PMatching2RecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -1,440 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <common/assert.h>
|
||||
#include <common/logging/log.h>
|
||||
#include <core/libraries/kernel/kernel.h>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#include "common/error.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "net_error.h"
|
||||
#include "sockets.h"
|
||||
#include "sys_net.h"
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
using FDTable = Common::Singleton<Core::FileSys::HandleTable>;
|
||||
|
||||
int PS4_SYSV_ABI sys_connect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
|
||||
int returncode = file->socket->Connect(addr, addrlen);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
u32 error = *Libraries::Kernel::__Error();
|
||||
// Don't log EINPROGRESS or EISCONN, these are normal to see from non-blocking communication.
|
||||
if (error != ORBIS_NET_EINPROGRESS && error != ORBIS_NET_EISCONN) {
|
||||
LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_bind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
|
||||
int returncode = file->socket->Bind(addr, addrlen);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_accept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
|
||||
auto new_sock = file->socket->Accept(addr, paddrlen);
|
||||
if (!new_sock) {
|
||||
u32 error = *Libraries::Kernel::__Error();
|
||||
// Don't log EWOULDBLOCK, this is normal to see from non-blocking communication.
|
||||
if (error != ORBIS_NET_EWOULDBLOCK) {
|
||||
LOG_ERROR(Lib_Net,
|
||||
"s = {} ({}) returned error code creating new socket for accepting: {}", s,
|
||||
file->m_guest_name, error);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
auto fd = FDTable::Instance()->CreateHandle();
|
||||
auto* new_file = FDTable::Instance()->GetFile(fd);
|
||||
new_file->is_opened = true;
|
||||
new_file->type = Core::FileSys::FileType::Socket;
|
||||
new_file->socket = new_sock;
|
||||
return fd;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_getpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
|
||||
int returncode = file->socket->GetPeerName(addr, paddrlen);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_getsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
|
||||
int returncode = file->socket->GetSocketAddress(addr, paddrlen);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_getsockopt(OrbisNetId s, int level, int optname, void* optval, u32* optlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
auto name = [&] -> std::string_view {
|
||||
switch (level) {
|
||||
case ORBIS_NET_SOL_SOCKET:
|
||||
return NameOf((OrbisNetSocketSoOption)optname);
|
||||
case ORBIS_NET_IPPROTO_IP:
|
||||
return magic_enum::enum_name((OrbisNetSocketIpOption)optname);
|
||||
case ORBIS_NET_IPPROTO_TCP:
|
||||
return magic_enum::enum_name((OrbisNetSocketTcpOption)optname);
|
||||
default:
|
||||
return std::string_view{"unknown"};
|
||||
}
|
||||
}();
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({}), level = {}, optname = {}", s, file->m_guest_name,
|
||||
NameOf((OrbisNetProtocol)level), name);
|
||||
int returncode = file->socket->GetSocketOptions(level, optname, optval, optlen);
|
||||
if (returncode >= 0) {
|
||||
if (optname == ORBIS_NET_SO_ERROR) {
|
||||
LOG_DEBUG(Lib_Net, "so_error = {}", *reinterpret_cast<s32*>(optval));
|
||||
}
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_listen(OrbisNetId s, int backlog) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({}), backlog = {}", s, file->m_guest_name, backlog);
|
||||
int returncode = file->socket->Listen(backlog);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_setsockopt(OrbisNetId s, int level, int optname, const void* optval,
|
||||
u32 optlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
auto name = [&] -> std::string_view {
|
||||
switch (level) {
|
||||
case ORBIS_NET_SOL_SOCKET:
|
||||
return NameOf((OrbisNetSocketSoOption)optname);
|
||||
case ORBIS_NET_IPPROTO_IP:
|
||||
return magic_enum::enum_name((OrbisNetSocketIpOption)optname);
|
||||
case ORBIS_NET_IPPROTO_TCP:
|
||||
return magic_enum::enum_name((OrbisNetSocketTcpOption)optname);
|
||||
default:
|
||||
return std::string_view{"unknown"};
|
||||
}
|
||||
}();
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({}), level = {}, optname = {}", s, file->m_guest_name,
|
||||
NameOf((OrbisNetProtocol)level), name);
|
||||
int returncode = file->socket->SetSocketOptions(level, optname, optval, optlen);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_shutdown(OrbisNetId s, int how) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_socketex(const char* name, int family, int type, int protocol) {
|
||||
auto sname = name ? name : "anon";
|
||||
LOG_INFO(Lib_Net, "name = {} family = {} type = {} protocol = {}", std::string(sname),
|
||||
magic_enum::enum_name((OrbisNetFamily)family),
|
||||
magic_enum::enum_name((OrbisNetSocketType)type), protocol);
|
||||
SocketPtr socket;
|
||||
switch (type) {
|
||||
case ORBIS_NET_SOCK_STREAM:
|
||||
case ORBIS_NET_SOCK_DGRAM:
|
||||
if (family == ORBIS_NET_AF_UNIX) {
|
||||
socket = std::make_shared<UnixSocket>(family, type, protocol);
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case ORBIS_NET_SOCK_RAW:
|
||||
socket = std::make_shared<PosixSocket>(family, type, protocol);
|
||||
break;
|
||||
case ORBIS_NET_SOCK_DGRAM_P2P:
|
||||
case ORBIS_NET_SOCK_STREAM_P2P:
|
||||
socket = std::make_shared<P2PSocket>(family, type, protocol);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown type {}", type);
|
||||
}
|
||||
if (!socket->IsValid()) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto fd = FDTable::Instance()->CreateHandle();
|
||||
auto* sock = FDTable::Instance()->GetFile(fd);
|
||||
sock->is_opened = true;
|
||||
sock->type = Core::FileSys::FileType::Socket;
|
||||
sock->socket = socket;
|
||||
sock->m_guest_name = sname;
|
||||
return fd;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_socket(int family, int type, int protocol) {
|
||||
return sys_socketex(nullptr, family, type, protocol);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int socketpair(int family, int type, int protocol, net_socket fd[2]) {
|
||||
if (family != AF_UNIX && family != AF_INET) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
|
||||
return -1;
|
||||
}
|
||||
if (type != SOCK_STREAM) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
SOCKET listener = INVALID_SOCKET;
|
||||
SOCKET sock1 = INVALID_SOCKET;
|
||||
SOCKET sock2 = INVALID_SOCKET;
|
||||
sockaddr_in addr{};
|
||||
int addrlen = sizeof(addr);
|
||||
|
||||
listener = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listener == INVALID_SOCKET)
|
||||
goto fail1;
|
||||
|
||||
ZeroMemory(&addr, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
addr.sin_port = 0;
|
||||
|
||||
if (bind(listener, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR)
|
||||
goto fail1;
|
||||
if (listen(listener, 1) == SOCKET_ERROR)
|
||||
goto fail1;
|
||||
|
||||
if (getsockname(listener, (sockaddr*)&addr, &addrlen) == SOCKET_ERROR)
|
||||
goto fail1;
|
||||
|
||||
sock1 = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock1 == INVALID_SOCKET)
|
||||
goto fail2;
|
||||
|
||||
if (connect(sock1, (sockaddr*)&addr, addrlen) == SOCKET_ERROR)
|
||||
goto fail2;
|
||||
|
||||
sock2 = accept(listener, nullptr, nullptr);
|
||||
if (sock2 == INVALID_SOCKET)
|
||||
goto fail3;
|
||||
|
||||
closesocket(listener);
|
||||
fd[0] = sock1;
|
||||
fd[1] = sock2;
|
||||
return 0;
|
||||
|
||||
fail3:
|
||||
closesocket(sock2);
|
||||
fail2:
|
||||
closesocket(sock1);
|
||||
fail1:
|
||||
closesocket(listener);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int PS4_SYSV_ABI sys_socketpair(int family, int type, int protocol, int sv[2]) {
|
||||
if (sv == nullptr) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (family != ORBIS_NET_AF_UNIX) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
net_socket fd[2];
|
||||
if (socketpair(family, type, protocol, fd) != 0) {
|
||||
LOG_ERROR(Lib_Net, "socketpair failed with {}", Common::GetLastErrorMsg());
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto fd1 = FDTable::Instance()->CreateHandle();
|
||||
auto fd2 = FDTable::Instance()->CreateHandle();
|
||||
auto* sock = FDTable::Instance()->GetFile(fd1);
|
||||
sock->is_opened = true;
|
||||
sock->type = Core::FileSys::FileType::Socket;
|
||||
sock->socket = std::make_shared<UnixSocket>(fd[0]);
|
||||
sock->m_guest_name = "anon_sock0";
|
||||
sock = FDTable::Instance()->GetFile(fd2);
|
||||
sock->is_opened = true;
|
||||
sock->type = Core::FileSys::FileType::Socket;
|
||||
sock->socket = std::make_shared<UnixSocket>(fd[1]);
|
||||
sock->m_guest_name = "anon_sock1";
|
||||
sv[0] = fd1;
|
||||
sv[1] = fd2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_netabort(OrbisNetId s, int flags) {
|
||||
LOG_ERROR(Lib_Net, "(STUBBED) called");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_socketclose(OrbisNetId s) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
|
||||
int returncode = file->socket->Close();
|
||||
if (returncode >= 0) {
|
||||
FDTable::Instance()->DeleteHandle(s);
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_send(OrbisNetId s, const void* buf, u64 len, int flags) {
|
||||
return sys_sendto(s, buf, len, flags, nullptr, 0);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_sendto(OrbisNetId s, const void* buf, u64 len, int flags,
|
||||
const OrbisNetSockaddr* addr, u32 addrlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({}), len = {}, flags = {:#x}", s, file->m_guest_name, len, flags);
|
||||
int returncode = file->socket->SendPacket(buf, len, flags, addr, addrlen);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_sendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, int flags) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({}), flags = {:#x}", s, file->m_guest_name, flags);
|
||||
int returncode = file->socket->SendMessage(msg, flags);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
s64 PS4_SYSV_ABI sys_recv(OrbisNetId s, void* buf, u64 len, int flags) {
|
||||
return sys_recvfrom(s, buf, len, flags, nullptr, nullptr);
|
||||
}
|
||||
|
||||
s64 PS4_SYSV_ABI sys_recvfrom(OrbisNetId s, void* buf, u64 len, int flags, OrbisNetSockaddr* addr,
|
||||
u32* paddrlen) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({}), len = {}, flags = {:#x}", s, file->m_guest_name, len, flags);
|
||||
int returncode = file->socket->ReceivePacket(buf, len, flags, addr, paddrlen);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
// Don't log EWOULDBLOCK, this is normal to see from non-blocking communication.
|
||||
u32 error = *Libraries::Kernel::__Error();
|
||||
if (error != ORBIS_NET_EWOULDBLOCK) {
|
||||
LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
s64 PS4_SYSV_ABI sys_recvmsg(OrbisNetId s, OrbisNetMsghdr* msg, int flags) {
|
||||
auto file = FDTable::Instance()->GetSocket(s);
|
||||
if (!file) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
|
||||
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
|
||||
return -1;
|
||||
}
|
||||
LOG_DEBUG(Lib_Net, "s = {} ({}), flags = {:#x}", s, file->m_guest_name, flags);
|
||||
int returncode = file->socket->ReceiveMessage(msg, flags);
|
||||
if (returncode >= 0) {
|
||||
return returncode;
|
||||
}
|
||||
LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name,
|
||||
(u32)*Libraries::Kernel::__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_htons(u16 v) {
|
||||
return ((v << 8) & 0xff00) | ((v >> 8) & 0x00ff);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sys_htonl(u32 v) {
|
||||
return (v << 24) | ((v & 0xff00) << 8) | ((v >> 8) & 0xff00) | (v >> 24);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
@@ -1,393 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <common/assert.h>
|
||||
#include "common/error.h"
|
||||
#include "core/libraries/kernel/file_system.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "net.h"
|
||||
#ifndef _WIN32
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#endif
|
||||
#include "net_error.h"
|
||||
#include "sockets.h"
|
||||
|
||||
namespace Libraries::Net {
|
||||
|
||||
#ifdef _WIN32
|
||||
#define ERROR_CASE(errname) \
|
||||
case (WSA##errname): \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
|
||||
return -1;
|
||||
#else
|
||||
#define ERROR_CASE(errname) \
|
||||
case (errname): \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
static int ConvertReturnErrorCode(int retval) {
|
||||
if (retval < 0) {
|
||||
#ifdef _WIN32
|
||||
switch (WSAGetLastError()) {
|
||||
#else
|
||||
switch (errno) {
|
||||
#endif
|
||||
#ifndef _WIN32 // These errorcodes don't exist in WinSock
|
||||
ERROR_CASE(EPERM)
|
||||
ERROR_CASE(ENOENT)
|
||||
// ERROR_CASE(ESRCH)
|
||||
// ERROR_CASE(EIO)
|
||||
// ERROR_CASE(ENXIO)
|
||||
// ERROR_CASE(E2BIG)
|
||||
// ERROR_CASE(ENOEXEC)
|
||||
// ERROR_CASE(EDEADLK)
|
||||
ERROR_CASE(ENOMEM)
|
||||
// ERROR_CASE(ECHILD)
|
||||
// ERROR_CASE(EBUSY)
|
||||
ERROR_CASE(EEXIST)
|
||||
// ERROR_CASE(EXDEV)
|
||||
ERROR_CASE(ENODEV)
|
||||
// ERROR_CASE(ENOTDIR)
|
||||
// ERROR_CASE(EISDIR)
|
||||
ERROR_CASE(ENFILE)
|
||||
// ERROR_CASE(ENOTTY)
|
||||
// ERROR_CASE(ETXTBSY)
|
||||
// ERROR_CASE(EFBIG)
|
||||
ERROR_CASE(ENOSPC)
|
||||
// ERROR_CASE(ESPIPE)
|
||||
// ERROR_CASE(EROFS)
|
||||
// ERROR_CASE(EMLINK)
|
||||
ERROR_CASE(EPIPE)
|
||||
// ERROR_CASE(EDOM)
|
||||
// ERROR_CASE(ERANGE)
|
||||
// ERROR_CASE(ENOLCK)
|
||||
// ERROR_CASE(ENOSYS)
|
||||
// ERROR_CASE(EIDRM)
|
||||
// ERROR_CASE(EOVERFLOW)
|
||||
// ERROR_CASE(EILSEQ)
|
||||
// ERROR_CASE(ENOTSUP)
|
||||
ERROR_CASE(ECANCELED)
|
||||
// ERROR_CASE(EBADMSG)
|
||||
ERROR_CASE(ENODATA)
|
||||
// ERROR_CASE(ENOSR)
|
||||
// ERROR_CASE(ENOSTR)
|
||||
// ERROR_CASE(ETIME)
|
||||
#endif
|
||||
ERROR_CASE(EINTR)
|
||||
ERROR_CASE(EBADF)
|
||||
ERROR_CASE(EACCES)
|
||||
ERROR_CASE(EFAULT)
|
||||
ERROR_CASE(EINVAL)
|
||||
ERROR_CASE(EMFILE)
|
||||
ERROR_CASE(EWOULDBLOCK)
|
||||
ERROR_CASE(EINPROGRESS)
|
||||
ERROR_CASE(EALREADY)
|
||||
ERROR_CASE(ENOTSOCK)
|
||||
ERROR_CASE(EDESTADDRREQ)
|
||||
ERROR_CASE(EMSGSIZE)
|
||||
ERROR_CASE(EPROTOTYPE)
|
||||
ERROR_CASE(ENOPROTOOPT)
|
||||
ERROR_CASE(EPROTONOSUPPORT)
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
ERROR_CASE(EOPNOTSUPP)
|
||||
#endif
|
||||
ERROR_CASE(EAFNOSUPPORT)
|
||||
ERROR_CASE(EADDRINUSE)
|
||||
ERROR_CASE(EADDRNOTAVAIL)
|
||||
ERROR_CASE(ENETDOWN)
|
||||
ERROR_CASE(ENETUNREACH)
|
||||
ERROR_CASE(ENETRESET)
|
||||
ERROR_CASE(ECONNABORTED)
|
||||
ERROR_CASE(ECONNRESET)
|
||||
ERROR_CASE(ENOBUFS)
|
||||
ERROR_CASE(EISCONN)
|
||||
ERROR_CASE(ENOTCONN)
|
||||
ERROR_CASE(ETIMEDOUT)
|
||||
ERROR_CASE(ECONNREFUSED)
|
||||
ERROR_CASE(ELOOP)
|
||||
ERROR_CASE(ENAMETOOLONG)
|
||||
ERROR_CASE(EHOSTUNREACH)
|
||||
ERROR_CASE(ENOTEMPTY)
|
||||
}
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EINTERNAL;
|
||||
return -1;
|
||||
}
|
||||
// if it is 0 or positive return it as it is
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int ConvertLevels(int level) {
|
||||
switch (level) {
|
||||
case ORBIS_NET_SOL_SOCKET:
|
||||
return SOL_SOCKET;
|
||||
case ORBIS_NET_IPPROTO_IP:
|
||||
return IPPROTO_IP;
|
||||
case ORBIS_NET_IPPROTO_TCP:
|
||||
return IPPROTO_TCP;
|
||||
case ORBIS_NET_IPPROTO_IPV6:
|
||||
return IPPROTO_IPV6;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void convertOrbisNetSockaddrToUnix(const OrbisNetSockaddr* src, sockaddr_un* dst) {
|
||||
if (src == nullptr || dst == nullptr)
|
||||
return;
|
||||
memset(dst, 0, sizeof(sockaddr_un));
|
||||
const OrbisNetSockaddrUn* src_in = (const OrbisNetSockaddrUn*)src;
|
||||
sockaddr_un* dst_in = (sockaddr_un*)dst;
|
||||
dst_in->sun_family = src_in->sun_family;
|
||||
memcpy(&dst_in->sun_path, &src_in->sun_path, src_in->sun_len);
|
||||
}
|
||||
|
||||
static void convertUnixSockaddrToOrbis(sockaddr* src, OrbisNetSockaddr* dst) {
|
||||
if (src == nullptr || dst == nullptr)
|
||||
return;
|
||||
memset(dst, 0, sizeof(OrbisNetSockaddrUn));
|
||||
OrbisNetSockaddrUn* dst_in = (OrbisNetSockaddrUn*)dst;
|
||||
sockaddr_un* src_in = (sockaddr_un*)src;
|
||||
dst_in->sun_len = strnlen(src_in->sun_path, 108);
|
||||
dst_in->sun_family = src_in->sun_family;
|
||||
memcpy(&dst_in->sun_path, &src_in->sun_path, dst_in->sun_len);
|
||||
}
|
||||
|
||||
bool UnixSocket::IsValid() const {
|
||||
#ifdef _WIN32
|
||||
return sock != INVALID_SOCKET;
|
||||
#else
|
||||
return sock != -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
int UnixSocket::Close() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
#ifdef _WIN32
|
||||
auto out = closesocket(sock);
|
||||
#else
|
||||
auto out = ::close(sock);
|
||||
#endif
|
||||
return ConvertReturnErrorCode(out);
|
||||
}
|
||||
|
||||
int UnixSocket::Bind(const OrbisNetSockaddr* addr, u32 addrlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
sockaddr_un addr2;
|
||||
convertOrbisNetSockaddrToUnix(addr, &addr2);
|
||||
return ConvertReturnErrorCode(::bind(sock, (const sockaddr*)&addr2, sizeof(sockaddr_un)));
|
||||
}
|
||||
|
||||
int UnixSocket::Listen(int backlog) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return ConvertReturnErrorCode(::listen(sock, backlog));
|
||||
}
|
||||
|
||||
int UnixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
#ifdef _WIN32
|
||||
DWORD bytesSent = 0;
|
||||
LPFN_WSASENDMSG wsasendmsg = nullptr;
|
||||
GUID guid = WSAID_WSASENDMSG;
|
||||
DWORD bytes = 0;
|
||||
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsasendmsg,
|
||||
sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
|
||||
int res = wsasendmsg(sock, reinterpret_cast<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(msg)), flags,
|
||||
&bytesSent, nullptr, nullptr);
|
||||
|
||||
if (res == SOCKET_ERROR) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
return static_cast<int>(bytesSent);
|
||||
#else
|
||||
int res = sendmsg(sock, reinterpret_cast<const msghdr*>(msg), flags);
|
||||
return ConvertReturnErrorCode(res);
|
||||
#endif
|
||||
}
|
||||
|
||||
int UnixSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
|
||||
u32 tolen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
LOG_ERROR(Lib_Net, "called");
|
||||
if (to != nullptr) {
|
||||
sockaddr_un addr;
|
||||
convertOrbisNetSockaddrToUnix(to, &addr);
|
||||
return ConvertReturnErrorCode(
|
||||
sendto(sock, (const char*)msg, len, flags, (sockaddr*)&addr, sizeof(sockaddr_un)));
|
||||
} else {
|
||||
return ConvertReturnErrorCode(send(sock, (const char*)msg, len, flags));
|
||||
}
|
||||
}
|
||||
|
||||
int UnixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
|
||||
std::scoped_lock lock{receive_mutex};
|
||||
#ifdef _WIN32
|
||||
LPFN_WSARECVMSG wsarecvmsg = nullptr;
|
||||
GUID guid = WSAID_WSARECVMSG;
|
||||
DWORD bytes = 0;
|
||||
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsarecvmsg,
|
||||
sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
|
||||
DWORD bytesReceived = 0;
|
||||
int res = wsarecvmsg(sock, reinterpret_cast<LPWSAMSG>(msg), &bytesReceived, nullptr, nullptr);
|
||||
|
||||
if (res == SOCKET_ERROR) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
return static_cast<int>(bytesReceived);
|
||||
#else
|
||||
int res = recvmsg(sock, reinterpret_cast<msghdr*>(msg), flags);
|
||||
return ConvertReturnErrorCode(res);
|
||||
#endif
|
||||
}
|
||||
|
||||
int UnixSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) {
|
||||
std::scoped_lock lock{receive_mutex};
|
||||
LOG_ERROR(Lib_Net, "called");
|
||||
if (from != nullptr) {
|
||||
sockaddr_un addr;
|
||||
int res = recvfrom(sock, (char*)buf, len, flags, (sockaddr*)&addr, (socklen_t*)fromlen);
|
||||
convertUnixSockaddrToOrbis((sockaddr*)&addr, from);
|
||||
*fromlen = sizeof(OrbisNetSockaddrUn);
|
||||
return ConvertReturnErrorCode(res);
|
||||
} else {
|
||||
return ConvertReturnErrorCode(recv(sock, (char*)buf, len, flags));
|
||||
}
|
||||
}
|
||||
|
||||
SocketPtr UnixSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
sockaddr_un addr2;
|
||||
socklen_t len = sizeof(addr2);
|
||||
net_socket new_socket = ::accept(sock, (sockaddr*)&addr2, &len);
|
||||
#ifdef _WIN32
|
||||
if (new_socket != INVALID_SOCKET) {
|
||||
#else
|
||||
if (new_socket >= 0) {
|
||||
#endif
|
||||
if (addr && addrlen) {
|
||||
convertUnixSockaddrToOrbis((sockaddr*)&addr2, addr);
|
||||
*addrlen = sizeof(OrbisNetSockaddrUn);
|
||||
}
|
||||
return std::make_shared<UnixSocket>(new_socket);
|
||||
}
|
||||
ConvertReturnErrorCode(new_socket);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int UnixSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
sockaddr_un addr2;
|
||||
convertOrbisNetSockaddrToUnix(addr, &addr2);
|
||||
int result = 0;
|
||||
do {
|
||||
result = ::connect(sock, (sockaddr*)&addr2, sizeof(sockaddr_un));
|
||||
LOG_DEBUG(Lib_Net, "raw connect result = {}, errno = {}", result,
|
||||
result == -1 ? Common::GetLastErrorMsg() : "none");
|
||||
} while (result == -1 && (errno == EINTR || errno == EINPROGRESS));
|
||||
return ConvertReturnErrorCode(result);
|
||||
}
|
||||
|
||||
int UnixSocket::GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
sockaddr_un addr;
|
||||
convertOrbisNetSockaddrToUnix(name, &addr);
|
||||
if (name != nullptr) {
|
||||
*namelen = sizeof(sockaddr_un);
|
||||
}
|
||||
int res = getsockname(sock, (sockaddr*)&addr, (socklen_t*)namelen);
|
||||
if (res >= 0) {
|
||||
convertUnixSockaddrToOrbis((sockaddr*)&addr, name);
|
||||
*namelen = sizeof(OrbisNetSockaddrUn);
|
||||
}
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
#define CASE_SETSOCKOPT(opt) \
|
||||
case ORBIS_NET_##opt: \
|
||||
return ConvertReturnErrorCode( \
|
||||
setsockopt(sock, native_level, opt, (const char*)optval, optlen))
|
||||
|
||||
#define CASE_SETSOCKOPT_VALUE(opt, value) \
|
||||
case opt: \
|
||||
if (optlen != sizeof(*value)) { \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
|
||||
return -1; \
|
||||
} \
|
||||
memcpy(value, optval, optlen); \
|
||||
return 0
|
||||
|
||||
int UnixSocket::SetSocketOptions(int level, int optname, const void* optval, u32 optlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
LOG_ERROR(Lib_Net, "Unknown level = {} optname = {}", level, optname);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define CASE_GETSOCKOPT(opt) \
|
||||
case ORBIS_NET_##opt: { \
|
||||
socklen_t optlen_temp = *optlen; \
|
||||
auto retval = ConvertReturnErrorCode( \
|
||||
getsockopt(sock, native_level, opt, (char*)optval, &optlen_temp)); \
|
||||
*optlen = optlen_temp; \
|
||||
return retval; \
|
||||
}
|
||||
#define CASE_GETSOCKOPT_VALUE(opt, value) \
|
||||
case opt: \
|
||||
if (*optlen < sizeof(value)) { \
|
||||
*optlen = sizeof(value); \
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
|
||||
return -1; \
|
||||
} \
|
||||
*optlen = sizeof(value); \
|
||||
*(decltype(value)*)optval = value; \
|
||||
return 0;
|
||||
|
||||
int UnixSocket::GetSocketOptions(int level, int optname, void* optval, u32* optlen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
LOG_ERROR(Lib_Net, "Unknown level = {} optname = {}", level, optname);
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
|
||||
int UnixSocket::GetPeerName(OrbisNetSockaddr* name, u32* namelen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
LOG_DEBUG(Lib_Net, "called");
|
||||
|
||||
sockaddr_un addr;
|
||||
convertOrbisNetSockaddrToUnix(name, &addr);
|
||||
if (name != nullptr) {
|
||||
*namelen = sizeof(sockaddr_un);
|
||||
}
|
||||
int res = ::getpeername(sock, (sockaddr*)&addr, (socklen_t*)namelen);
|
||||
if (res >= 0) {
|
||||
convertUnixSockaddrToOrbis((sockaddr*)&addr, name);
|
||||
*namelen = sizeof(OrbisNetSockaddrUn);
|
||||
}
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
int UnixSocket::fstat(Libraries::Kernel::OrbisKernelStat* sb) {
|
||||
#ifdef _WIN32
|
||||
LOG_ERROR(Lib_Net, "(STUBBED) called");
|
||||
sb->st_mode = 0000777u | 0140000u;
|
||||
return 0;
|
||||
#else
|
||||
struct stat st{};
|
||||
int result = ::fstat(sock, &st);
|
||||
sb->st_mode = 0000777u | 0140000u;
|
||||
sb->st_size = st.st_size;
|
||||
sb->st_blocks = st.st_blocks;
|
||||
sb->st_blksize = st.st_blksize;
|
||||
// sb->st_flags = st.st_flags;
|
||||
return ConvertReturnErrorCode(result);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Libraries::Net
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,325 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2019-2026 rpcs3 Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/np/np_manager.h"
|
||||
#include "core/libraries/np/np_score/np_score.h"
|
||||
#include "core/libraries/np/np_score/np_score_ctx.h"
|
||||
#include "core/libraries/rtc/rtc.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "shadnet/client.h"
|
||||
|
||||
namespace Libraries::Np {
|
||||
|
||||
class NpHandler {
|
||||
public:
|
||||
static NpHandler& GetInstance();
|
||||
|
||||
NpHandler(const NpHandler&) = delete;
|
||||
NpHandler& operator=(const NpHandler&) = delete;
|
||||
|
||||
// Connect every currently-logged-in user that has shadNet credentials.
|
||||
void Initialize();
|
||||
|
||||
// Disconnect all clients, stop worker threads, fire SignedOut for each.
|
||||
void Shutdown();
|
||||
|
||||
// Connect/disconnect a single user in response to a user service login/logout event.
|
||||
void OnUserLoggedIn(s32 user_id);
|
||||
void OnUserLoggedOut(s32 user_id);
|
||||
|
||||
// True if this specific user is authenticated to the shadNet server.
|
||||
bool IsPsnSignedIn(s32 user_id) const;
|
||||
|
||||
/// True if any user is currently signed in
|
||||
bool IsAnySignedIn() const;
|
||||
|
||||
// Set the Appear-Offline preference for all signed-in users (and future logins). While
|
||||
// enabled, shadNet handles the user as offline for everyone else. Call from the UI/config.
|
||||
void SetAppearOffline(bool enable);
|
||||
bool IsAppearOffline() const {
|
||||
return m_appear_offline.load();
|
||||
}
|
||||
|
||||
/// Full NP ID for this user, built once from shadnet_npid after login.
|
||||
OrbisNpId GetNpId(s32 user_id) const;
|
||||
|
||||
/// The Online ID embedded in the NP ID (npid.handle).
|
||||
OrbisNpOnlineId GetOnlineId(s32 user_id) const;
|
||||
|
||||
// Avatar URL returned by the server.
|
||||
std::string GetAvatarUrl(s32 user_id) const;
|
||||
|
||||
// 64-bit account ID assigned by the server.
|
||||
OrbisNpAccountId GetAccountId(s32 user_id) const;
|
||||
|
||||
// WebAPI bearer token
|
||||
std::string GetBearerToken(s32 user_id) const;
|
||||
|
||||
// Sends a session invitation via the WebAPI (POST /v1/sessions/<id>/invitations). Each 'to'
|
||||
// entry is an online ID (npid) or a decimal account ID
|
||||
bool SendSessionInvitation(s32 user_id, const std::string& session_id,
|
||||
const std::vector<std::string>& to, const std::string& message);
|
||||
|
||||
// Raises ORBIS_SYSTEM_SERVICE_EVENT_SESSION_INVITATION (0x10000002) so titles that watch the
|
||||
// system-service event learn about the invite.
|
||||
void PostSessionInvitationEvent(const std::string& session_id, const std::string& invitation_id,
|
||||
const std::string& accepter_online_id);
|
||||
|
||||
// A session invitation surfaced from a shadNet push, stashed until the user acts on it.
|
||||
struct PendingInvitation {
|
||||
std::string session_id;
|
||||
std::string invitation_id;
|
||||
std::string from_npid; // sender (for display)
|
||||
std::string to_npid; // local recipient / accepter
|
||||
int64_t valid_until = 0; // ms since epoch; 0 = never expires
|
||||
};
|
||||
// Pending invitations stashed for a local user (newest last), populated on arrival.
|
||||
std::vector<PendingInvitation> GetPendingInvitations(s32 user_id) const;
|
||||
// Consumes an invitation server-side (PUT usedFlag=true) and drops it from the stash. Returns
|
||||
// true if the invite was found and the consume succeeded.
|
||||
bool AcceptSessionInvitation(s32 user_id, const std::string& invitation_id);
|
||||
// Dismisses an invitation locally by dropping it from the stash. No server call (a declined
|
||||
// invite is left unconsumed and simply ages out server-side).
|
||||
void DeclineSessionInvitation(s32 user_id, const std::string& invitation_id);
|
||||
|
||||
// Local IP address (network byte order) as seen at connect time.
|
||||
u32 GetLocalIpAddr(s32 user_id) const;
|
||||
|
||||
// Reverse lookup: server account_id to local user_id.
|
||||
// Returns -1 if no connected user owns that account_id.
|
||||
s32 GetUserIdByAccountId(u64 account_id) const;
|
||||
|
||||
// Reverse lookup: OrbisNpOnlineId to local user_id.
|
||||
// Scans m_np_ids for a matching handle.data string.
|
||||
// Returns -1 if no connected user has that Online ID.
|
||||
s32 GetUserIdByOnlineId(const OrbisNpOnlineId& online_id) const;
|
||||
|
||||
// Friend list
|
||||
u32 GetNumFriends(s32 user_id) const;
|
||||
std::optional<std::string> GetFriendNpid(s32 user_id, u32 index) const;
|
||||
|
||||
// ---- Friend list / requests / blocked (shadNet) ----
|
||||
struct FriendInfo {
|
||||
std::string npid;
|
||||
bool online = false;
|
||||
};
|
||||
struct FriendListSnapshot {
|
||||
std::vector<FriendInfo> friends;
|
||||
std::vector<std::string> requests_received;
|
||||
std::vector<std::string> requests_sent;
|
||||
std::vector<std::string> blocked;
|
||||
};
|
||||
|
||||
// Local users that currently have an authenticated shadNet session.
|
||||
std::vector<s32> GetConnectedUsers() const;
|
||||
|
||||
// user's friend state for UI display.
|
||||
FriendListSnapshot GetFriendList(s32 user_id) const;
|
||||
|
||||
// Friend/block actions
|
||||
s32 SendFriendRequest(s32 user_id, const std::string& npid);
|
||||
s32 RemoveFriend(s32 user_id, const std::string& npid);
|
||||
s32 BlockUser(s32 user_id, const std::string& npid);
|
||||
s32 UnblockUser(s32 user_id, const std::string& npid);
|
||||
|
||||
// Submit a RecordScore request to the shadNet server.
|
||||
s32 RecordScore(s32 user_id, s32 service_label, u32 boardId, s32 pcId, s64 score,
|
||||
const char* comment, size_t commentLen, const u8* gameInfoData,
|
||||
size_t gameInfoSize, std::shared_ptr<NpScore::ScoreRequestCtx> req);
|
||||
|
||||
s32 RecordGameData(s32 user_id, s32 service_label, u32 boardId, s32 pcId, s64 score,
|
||||
const u8* data, size_t size, std::shared_ptr<NpScore::ScoreRequestCtx> req);
|
||||
|
||||
s32 GetGameData(s32 user_id, s32 service_label, u32 boardId, const std::string& npId, s32 pcId,
|
||||
void* dataOut, u64 recvSize, u64* totalSizeOut,
|
||||
std::shared_ptr<NpScore::ScoreRequestCtx> req);
|
||||
|
||||
s32 GetGameDataByAccountId(s32 user_id, s32 service_label, u32 boardId, u64 accountId, s32 pcId,
|
||||
void* dataOut, u64 recvSize, u64* totalSizeOut,
|
||||
std::shared_ptr<NpScore::ScoreRequestCtx> req);
|
||||
|
||||
s32 GetBoardInfo(s32 user_id, s32 service_label, u32 boardId,
|
||||
NpScore::OrbisNpScoreBoardInfo* boardInfo,
|
||||
std::shared_ptr<NpScore::ScoreRequestCtx> req);
|
||||
|
||||
// Submit a GetRankingByNpId request to the shadNet server.
|
||||
s32 GetRankingByNpId(s32 user_id, s32 service_label, u32 boardId,
|
||||
const std::vector<std::string>& npIds, const std::vector<s32>& pcIds,
|
||||
NpScore::OrbisNpScorePlayerRankData* rankArray,
|
||||
NpScore::OrbisNpScoreComment* commentArray,
|
||||
NpScore::OrbisNpScoreGameInfo* infoArray,
|
||||
Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord,
|
||||
std::shared_ptr<NpScore::ScoreRequestCtx> req);
|
||||
|
||||
// Submit a GetRankingByRange request to the shadNet server.
|
||||
s32 GetRankingByRange(s32 user_id, s32 service_label, u32 boardId, u32 startSerialRank,
|
||||
u32 arrayNum, NpScore::OrbisNpScoreRankData* rankArray,
|
||||
NpScore::OrbisNpScoreComment* commentArray,
|
||||
NpScore::OrbisNpScoreGameInfo* infoArray,
|
||||
Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord,
|
||||
std::shared_ptr<NpScore::ScoreRequestCtx> req);
|
||||
|
||||
// A-variant of GetRankingByRange.
|
||||
s32 GetRankingByRangeA(s32 user_id, s32 service_label, u32 boardId, u32 startSerialRank,
|
||||
u32 arrayNum, NpScore::OrbisNpScoreRankDataA* rankArray,
|
||||
NpScore::OrbisNpScoreComment* commentArray,
|
||||
NpScore::OrbisNpScoreGameInfo* infoArray,
|
||||
Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord,
|
||||
std::shared_ptr<NpScore::ScoreRequestCtx> req);
|
||||
|
||||
s32 GetRankingByAccountId(s32 user_id, s32 service_label, u32 boardId,
|
||||
const std::vector<u64>& accountIds, const std::vector<s32>& pcIds,
|
||||
NpScore::OrbisNpScorePlayerRankDataA* rankArray,
|
||||
NpScore::OrbisNpScoreComment* commentArray,
|
||||
NpScore::OrbisNpScoreGameInfo* infoArray,
|
||||
Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord,
|
||||
std::shared_ptr<NpScore::ScoreRequestCtx> req);
|
||||
|
||||
// Submit a GetFriendsRanking request to the shadNet server.
|
||||
s32 GetFriendsRanking(s32 user_id, s32 service_label, u32 boardId, bool includeSelf,
|
||||
u32 arrayNum, NpScore::OrbisNpScoreRankData* rankArray,
|
||||
NpScore::OrbisNpScoreComment* commentArray,
|
||||
NpScore::OrbisNpScoreGameInfo* infoArray,
|
||||
Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord,
|
||||
std::shared_ptr<NpScore::ScoreRequestCtx> req);
|
||||
|
||||
// A-variant of GetFriendsRanking
|
||||
s32 GetFriendsRankingA(s32 user_id, s32 service_label, u32 boardId, bool includeSelf,
|
||||
u32 arrayNum, NpScore::OrbisNpScoreRankDataA* rankArray,
|
||||
NpScore::OrbisNpScoreComment* commentArray,
|
||||
NpScore::OrbisNpScoreGameInfo* infoArray,
|
||||
Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord,
|
||||
std::shared_ptr<NpScore::ScoreRequestCtx> req);
|
||||
|
||||
// State callbacks
|
||||
using StateCallback = std::function<void(Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
NpManager::OrbisNpState state)>;
|
||||
|
||||
s32 RegisterStateCallback(StateCallback cb, void* userdata);
|
||||
void UnregisterStateCallback(s32 handle);
|
||||
|
||||
private:
|
||||
NpHandler() = default;
|
||||
~NpHandler() = default;
|
||||
|
||||
/// Connect one user. Blocks until connected+authenticated or failed.
|
||||
bool ConnectUser(s32 user_id, const std::string& host, u16 port, const std::string& npid,
|
||||
const std::string& password, const std::string& token);
|
||||
|
||||
// Connect a single logged-in user by id (looks up credentials, parses server).
|
||||
bool ConnectUserById(s32 user_id);
|
||||
|
||||
// Parse the configured shadNet server "host:port" (default port 31313).
|
||||
std::pair<std::string, u16> ParseServerAddress() const;
|
||||
|
||||
// Start the health-monitor worker thread if not already running (idempotent).
|
||||
void StartWorker();
|
||||
|
||||
// Disconnect and remove one user's client.
|
||||
void DisconnectUser(s32 user_id);
|
||||
|
||||
void WorkerThread();
|
||||
// Transparent reconnect after a network drop
|
||||
void MarkForReconnect(s32 user_id);
|
||||
void TryReconnect();
|
||||
void FireStateCallback(s32 user_id, NpManager::OrbisNpState state);
|
||||
|
||||
// Notification forwarders wired into each client
|
||||
void OnFriendQuery(s32 user_id, const ShadNet::NotifyFriendQuery& n);
|
||||
void OnFriendNew(s32 user_id, const ShadNet::NotifyFriendNew& n);
|
||||
void OnFriendLost(s32 user_id, const ShadNet::NotifyFriendLost& n);
|
||||
void OnFriendStatus(s32 user_id, const ShadNet::NotifyFriendStatus& n);
|
||||
void OnWebApiPushEvent(s32 user_id, const ShadNet::NotifyWebApiPushEvent& n);
|
||||
void OnLoginResult(s32 user_id, const ShadNet::LoginResult& res);
|
||||
|
||||
// General async reply dispatch. Routes a reply to the owning subsystem by
|
||||
// command. Called from the per-user ShadNetClient on the reader thread.
|
||||
void OnAsyncReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, ShadNet::ErrorType error,
|
||||
const std::vector<u8>& body);
|
||||
|
||||
void OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, ShadNet::ErrorType error,
|
||||
const std::vector<u8>& body);
|
||||
|
||||
// 12-byte NP Communication ID
|
||||
std::string GetNpCommId(s32 service_label) const;
|
||||
|
||||
// Appear-Offline preference, applied to every client at login and on change.
|
||||
std::atomic<bool> m_appear_offline{false};
|
||||
|
||||
// Per-user client map
|
||||
mutable std::mutex m_mutex_clients;
|
||||
std::map<s32, std::shared_ptr<ShadNet::ShadNetClient>> m_clients;
|
||||
// Per-user NP ID built once from shadnet_npid after login.
|
||||
std::map<s32, OrbisNpId> m_np_ids;
|
||||
|
||||
// Score requests awaiting a reply, keyed by the submit packet id.
|
||||
struct PendingScoreRequest {
|
||||
std::shared_ptr<NpScore::ScoreRequestCtx> req;
|
||||
ShadNet::CommandType cmd;
|
||||
std::vector<std::string> requestedNpIds;
|
||||
NpScore::OrbisNpScorePlayerRankData* rankArray = nullptr;
|
||||
NpScore::OrbisNpScoreRankData* plainRankArray = nullptr;
|
||||
NpScore::OrbisNpScoreRankDataA* aRankArray = nullptr;
|
||||
NpScore::OrbisNpScorePlayerRankDataA* aPlayerRankArray = nullptr;
|
||||
NpScore::OrbisNpScoreComment* commentArray = nullptr;
|
||||
NpScore::OrbisNpScoreGameInfo* infoArray = nullptr;
|
||||
NpScore::OrbisNpScoreBoardInfo* boardInfo = nullptr;
|
||||
// GetGameData / GetGameDataByAccountId output buffers.
|
||||
void* dataOut = nullptr;
|
||||
u64 recvSize = 0;
|
||||
u64* totalSizeOut = nullptr;
|
||||
Libraries::Rtc::OrbisRtcTick* lastSortDate = nullptr;
|
||||
u32* totalRecord = nullptr;
|
||||
u64 arrayNum = 0;
|
||||
};
|
||||
mutable std::mutex m_mutex_pending_score;
|
||||
std::map<u64, PendingScoreRequest> m_pending_score;
|
||||
|
||||
// Worker thread
|
||||
std::atomic<bool> m_initialized{false};
|
||||
std::atomic<bool> m_worker_running{false};
|
||||
std::thread m_worker_thread;
|
||||
|
||||
// Users dropped by a network error and awaiting transparent reconnect
|
||||
struct ReconnectState {
|
||||
std::chrono::steady_clock::time_point next_attempt{};
|
||||
std::chrono::milliseconds backoff{0};
|
||||
};
|
||||
std::unordered_map<s32, ReconnectState> m_reconnect;
|
||||
|
||||
mutable std::mutex m_mutex_pending_invites;
|
||||
std::unordered_map<s32, std::vector<PendingInvitation>> m_pending_invites;
|
||||
|
||||
// State callbacks
|
||||
struct CbEntry {
|
||||
s32 handle;
|
||||
StateCallback cb;
|
||||
void* userdata;
|
||||
};
|
||||
mutable std::mutex m_mutex_cbs;
|
||||
std::vector<CbEntry> m_state_cbs;
|
||||
s32 m_next_handle{1};
|
||||
|
||||
// Friend state per user, seeded from LoginReply and updated by notifications/actions.
|
||||
mutable std::mutex m_mutex_friend_state;
|
||||
std::map<s32, FriendListSnapshot> m_friend_state;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Np
|
||||
@@ -1,23 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/np/np_types2.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::Np::NpMatching2 {
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* param);
|
||||
int PS4_SYSV_ABI sceNpMatching2Terminate();
|
||||
int PS4_SYSV_ABI sceNpMatching2CreateContext(const OrbisNpMatching2CreateContextParameter* param,
|
||||
OrbisNpMatching2ContextId* ctxId);
|
||||
int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContextParameterA* param,
|
||||
OrbisNpMatching2ContextId* ctxId);
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Np::NpMatching2
|
||||
@@ -1,617 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/network/net.h"
|
||||
#include "core/libraries/network/sockets.h"
|
||||
#include "core/libraries/np/np_error.h"
|
||||
#include "core/libraries/np/np_matching2/np_matching2_internal.h"
|
||||
#include "core/libraries/np/np_matching2/np_matching2_mm.h"
|
||||
#include "core/libraries/np/np_matching2/np_matching2_signaling.h"
|
||||
#include "core/libraries/np/np_signaling/np_signaling_stubs.h"
|
||||
|
||||
namespace Libraries::Np::NpMatching2 {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr s32 kMatching2ConnInactive = 0;
|
||||
constexpr s32 kMatching2ConnPending = 1;
|
||||
constexpr s32 kMatching2ConnActive = 2;
|
||||
constexpr auto kMatching2HandshakeRetry = std::chrono::milliseconds(350);
|
||||
constexpr auto kMatching2HandshakeTimeout = std::chrono::seconds(10);
|
||||
constexpr auto kMatching2StunPingInterval = std::chrono::seconds(5);
|
||||
|
||||
std::atomic<bool> g_matching2_stop{false};
|
||||
std::mutex g_matching2_thread_mutex;
|
||||
std::thread g_matching2_thread;
|
||||
|
||||
enum class Matching2HandshakeKind : u8 {
|
||||
Offer = 1,
|
||||
Accept = 2,
|
||||
Check = 3,
|
||||
CheckAck = 4,
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct Matching2HandshakePacket {
|
||||
u8 magic[4] = {'S', 'H', 'A', 'D'};
|
||||
u8 type = 0x21;
|
||||
u8 kind = 0;
|
||||
u64 room_id = 0;
|
||||
u16 from_member_id = 0;
|
||||
u16 to_member_id = 0;
|
||||
u8 online_id_from[16]{};
|
||||
u32 mapped_addr = 0;
|
||||
u16 mapped_port = 0;
|
||||
u16 reserved = 0;
|
||||
u64 nonce = 0;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
static_assert(sizeof(Matching2HandshakePacket) == 0x32);
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct Matching2StunPing {
|
||||
u8 cmd = 0x01;
|
||||
u8 online_id[ORBIS_NP_ONLINEID_MAX_LENGTH]{};
|
||||
u32 local_ip = 0;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
static_assert(sizeof(Matching2StunPing) == 21);
|
||||
|
||||
bool HasMatching2Magic(const Matching2HandshakePacket& pkt) {
|
||||
return pkt.magic[0] == 'S' && pkt.magic[1] == 'H' && pkt.magic[2] == 'A' &&
|
||||
pkt.magic[3] == 'D' && pkt.type == 0x21;
|
||||
}
|
||||
|
||||
std::string OnlineIdToString(const Libraries::Np::OrbisNpOnlineId& online_id) {
|
||||
char buf[ORBIS_NP_ONLINEID_MAX_LENGTH + 1]{};
|
||||
std::memcpy(buf, online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
bool ShouldConnectToPeer(const RoomCache& room, OrbisNpMatching2RoomMemberId self,
|
||||
OrbisNpMatching2RoomMemberId peer) {
|
||||
if (peer == 0 || peer == self) {
|
||||
return false;
|
||||
}
|
||||
if (room.signaling_type == ORBIS_NP_MATCHING2_SIGNALING_TYPE_NONE) {
|
||||
return false;
|
||||
}
|
||||
if (room.signaling_type == ORBIS_NP_MATCHING2_SIGNALING_TYPE_STAR &&
|
||||
room.signaling_main_member != 0) {
|
||||
return self == room.signaling_main_member || peer == room.signaling_main_member;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResolvePeerEndpoint(const MemberCache& member, PeerInfo& peer) {
|
||||
if (peer.addr != 0 && peer.port != 0) {
|
||||
return true;
|
||||
}
|
||||
if (member.addr != 0 && member.port != 0) {
|
||||
peer.addr = member.addr;
|
||||
peer.port = member.port;
|
||||
if (peer.online_id.data[0] == 0) {
|
||||
peer.online_id = member.np_id.handle;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string online_id(member.np_id.handle.data);
|
||||
u32 resolved_addr = 0;
|
||||
u16 resolved_port = 0;
|
||||
if (!online_id.empty() && RequestSignalingInfos(online_id, &resolved_addr, &resolved_port)) {
|
||||
peer.addr = resolved_addr;
|
||||
peer.port = resolved_port;
|
||||
}
|
||||
if (peer.online_id.data[0] == 0) {
|
||||
peer.online_id = member.np_id.handle;
|
||||
}
|
||||
return peer.addr != 0 && peer.port != 0;
|
||||
}
|
||||
|
||||
void MarkMatching2PeerActive(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
|
||||
OrbisNpMatching2RoomMemberId member_id, u32 addr, u16 port) {
|
||||
if (member_id == 0 || member_id == ctx.my_member_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
PeerInfo& peer = ctx.peers[member_id];
|
||||
peer.member_id = member_id;
|
||||
if (addr != 0) {
|
||||
peer.addr = addr;
|
||||
}
|
||||
if (port != 0) {
|
||||
peer.port = port;
|
||||
}
|
||||
const bool first_active = peer.status != kMatching2ConnActive;
|
||||
peer.status = kMatching2ConnActive;
|
||||
peer.handshake_started = true;
|
||||
|
||||
if (first_active && !peer.sent_established) {
|
||||
peer.sent_established = true;
|
||||
QueueMatching2SignalingEvent(ctx, room_id, member_id,
|
||||
ORBIS_NP_MATCHING2_SIGNALING_EVENT_ESTABLISHED, ORBIS_OK);
|
||||
LOG_INFO(Lib_NpMatching2,
|
||||
"Matching2 signaling established: ctx={} room={} member={} addr={:#x}:{}",
|
||||
ctx.ctx_id, room_id, member_id, peer.addr, Libraries::Net::sceNetNtohs(peer.port));
|
||||
}
|
||||
}
|
||||
|
||||
bool SendMatching2Handshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
|
||||
OrbisNpMatching2RoomMemberId member_id, Matching2HandshakeKind kind,
|
||||
u64 nonce) {
|
||||
auto room_it = ctx.room_cache.find(room_id);
|
||||
if (room_it == ctx.room_cache.end()) {
|
||||
return false;
|
||||
}
|
||||
auto member_it = room_it->second.members.find(member_id);
|
||||
if (member_it == room_it->second.members.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PeerInfo& peer = ctx.peers[member_id];
|
||||
peer.member_id = member_id;
|
||||
if (!ResolvePeerEndpoint(member_it->second, peer)) {
|
||||
LOG_WARNING(Lib_NpMatching2, "Matching2 signaling: unresolved endpoint room={} member={}",
|
||||
room_id, member_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
Matching2HandshakePacket pkt{};
|
||||
pkt.kind = static_cast<u8>(kind);
|
||||
pkt.room_id = room_id;
|
||||
pkt.from_member_id = ctx.my_member_id;
|
||||
pkt.to_member_id = member_id;
|
||||
std::memcpy(pkt.online_id_from, ctx.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
|
||||
pkt.mapped_addr = Net::GetP2PAdvertisedAddr();
|
||||
pkt.mapped_port = Net::GetP2PConfiguredPort() != 0
|
||||
? Libraries::Net::sceNetHtons(Net::GetP2PConfiguredPort())
|
||||
: 0;
|
||||
pkt.nonce = nonce;
|
||||
|
||||
const int rc = Net::P2PMatching2SendTo(&pkt, sizeof(pkt), peer.addr, peer.port);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
peer.last_send = now;
|
||||
if (kind == Matching2HandshakeKind::Check) {
|
||||
peer.last_check_send = now;
|
||||
}
|
||||
LOG_DEBUG(Lib_NpMatching2,
|
||||
"Matching2 handshake send kind={} ctx={} room={} {}->{} dst={:#x}:{} rc={}",
|
||||
static_cast<u8>(kind), ctx.ctx_id, room_id, ctx.my_member_id, member_id, peer.addr,
|
||||
Libraries::Net::sceNetNtohs(peer.port), rc);
|
||||
return rc >= 0;
|
||||
}
|
||||
|
||||
ContextObject* FindContextForMatching2Packet(const Matching2HandshakePacket& pkt) {
|
||||
for (u32 id = 1; id <= ContextManager::kMaxContexts; ++id) {
|
||||
ContextObject* ctx =
|
||||
ContextManager::Instance().Get(static_cast<OrbisNpMatching2ContextId>(id));
|
||||
if (!ctx || ctx->room_id != pkt.room_id || ctx->my_member_id != pkt.to_member_id) {
|
||||
continue;
|
||||
}
|
||||
if (ctx->room_cache.find(static_cast<OrbisNpMatching2RoomId>(pkt.room_id)) !=
|
||||
ctx->room_cache.end()) {
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void HandleMatching2HandshakePacket(u32 from_addr, u16 from_port,
|
||||
const Matching2HandshakePacket& pkt) {
|
||||
if (!HasMatching2Magic(pkt)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContextObject* ctx = FindContextForMatching2Packet(pkt);
|
||||
if (!ctx) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "Matching2 handshake: no ctx for room={} to_member={}",
|
||||
pkt.room_id, pkt.to_member_id);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto room_id = static_cast<OrbisNpMatching2RoomId>(pkt.room_id);
|
||||
const auto member_id = static_cast<OrbisNpMatching2RoomMemberId>(pkt.from_member_id);
|
||||
auto room_it = ctx->room_cache.find(room_id);
|
||||
if (room_it == ctx->room_cache.end() ||
|
||||
!ShouldConnectToPeer(room_it->second, ctx->my_member_id, member_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PeerInfo& peer = ctx->peers[member_id];
|
||||
peer.member_id = member_id;
|
||||
// The UDP source endpoint is what this peer's NAT actually exposed to us.
|
||||
// Prefer it over the self-reported mapping, because a NAT can translate
|
||||
// the sender's local UDP port.
|
||||
peer.addr = from_addr != 0 ? from_addr : pkt.mapped_addr;
|
||||
peer.port = from_port != 0 ? from_port : pkt.mapped_port;
|
||||
peer.status =
|
||||
peer.status == kMatching2ConnActive ? kMatching2ConnActive : kMatching2ConnPending;
|
||||
peer.handshake_started = true;
|
||||
SetNpOnlineId(peer.online_id,
|
||||
std::string_view(reinterpret_cast<const char*>(pkt.online_id_from),
|
||||
ORBIS_NP_ONLINEID_MAX_LENGTH));
|
||||
|
||||
const auto kind = static_cast<Matching2HandshakeKind>(pkt.kind);
|
||||
switch (kind) {
|
||||
case Matching2HandshakeKind::Offer:
|
||||
SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::Accept, 0);
|
||||
break;
|
||||
case Matching2HandshakeKind::Accept:
|
||||
peer.sent_check = true;
|
||||
SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::Check, peer.nonce);
|
||||
break;
|
||||
case Matching2HandshakeKind::Check:
|
||||
SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::CheckAck,
|
||||
pkt.nonce);
|
||||
MarkMatching2PeerActive(*ctx, room_id, member_id, peer.addr, peer.port);
|
||||
break;
|
||||
case Matching2HandshakeKind::CheckAck:
|
||||
if (pkt.nonce == 0 || pkt.nonce == peer.nonce) {
|
||||
if (peer.last_check_send.time_since_epoch().count() != 0) {
|
||||
const auto rtt = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now() - peer.last_check_send);
|
||||
peer.ping_us =
|
||||
static_cast<u32>(std::min<u64>(static_cast<u64>(std::max<s64>(0, rtt.count())),
|
||||
std::numeric_limits<u32>::max()));
|
||||
peer.last_check_send = {};
|
||||
}
|
||||
MarkMatching2PeerActive(*ctx, room_id, member_id, peer.addr, peer.port);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Matching2HandshakeThreadMain() {
|
||||
auto last_stun_ping = std::chrono::steady_clock::time_point{};
|
||||
while (!g_matching2_stop.load(std::memory_order_relaxed)) {
|
||||
Matching2HandshakePacket pkt{};
|
||||
u32 from_addr = 0;
|
||||
u16 from_port = 0;
|
||||
const int rc = Net::P2PMatching2RecvFrom(&pkt, sizeof(pkt), &from_addr, &from_port);
|
||||
if (rc == sizeof(pkt)) {
|
||||
HandleMatching2HandshakePacket(from_addr, from_port, pkt);
|
||||
}
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const bool should_stun_ping = last_stun_ping.time_since_epoch().count() == 0 ||
|
||||
now - last_stun_ping >= kMatching2StunPingInterval;
|
||||
for (u32 id = 1; id <= ContextManager::kMaxContexts; ++id) {
|
||||
ContextObject* ctx =
|
||||
ContextManager::Instance().Get(static_cast<OrbisNpMatching2ContextId>(id));
|
||||
if (!ctx) {
|
||||
continue;
|
||||
}
|
||||
if (should_stun_ping) {
|
||||
SendMatching2StunPing(*ctx);
|
||||
}
|
||||
if (ctx->room_id == 0) {
|
||||
continue;
|
||||
}
|
||||
auto room_it = ctx->room_cache.find(ctx->room_id);
|
||||
if (room_it == ctx->room_cache.end()) {
|
||||
continue;
|
||||
}
|
||||
for (auto& [member_id, peer] : ctx->peers) {
|
||||
if (peer.status != kMatching2ConnPending || !peer.handshake_started) {
|
||||
continue;
|
||||
}
|
||||
if (peer.last_send.time_since_epoch().count() != 0 &&
|
||||
now - peer.last_send > kMatching2HandshakeTimeout) {
|
||||
peer.status = kMatching2ConnInactive;
|
||||
QueueMatching2SignalingEvent(*ctx, ctx->room_id, member_id,
|
||||
ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD,
|
||||
ORBIS_NP_MATCHING2_SIGNALING_ERROR_TIMEOUT);
|
||||
continue;
|
||||
}
|
||||
if (peer.last_send.time_since_epoch().count() == 0 ||
|
||||
now - peer.last_send >= kMatching2HandshakeRetry) {
|
||||
SendMatching2Handshake(*ctx, ctx->room_id, member_id,
|
||||
peer.sent_check ? Matching2HandshakeKind::Check
|
||||
: Matching2HandshakeKind::Offer,
|
||||
peer.sent_check ? peer.nonce : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (should_stun_ping) {
|
||||
last_stun_ping = now;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool SendMatching2StunPing(const ContextObject& ctx) {
|
||||
if (!NpSignaling::Stubs::Matching2Enabled()) {
|
||||
return false;
|
||||
}
|
||||
if (!ctx.started || ctx.online_id.data[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
if (!NpSignaling::Stubs::EnsureTransport()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const u32 server_addr = NpSignaling::Stubs::MmServerAddr();
|
||||
const u16 server_udp = NpSignaling::Stubs::MmServerUdpPort();
|
||||
if (server_addr == 0 || server_udp == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Matching2StunPing ping{};
|
||||
ping.cmd = 0x01;
|
||||
std::memcpy(ping.online_id, ctx.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
|
||||
ping.local_ip = NpSignaling::Stubs::AdvertisedAddr();
|
||||
|
||||
const int rc =
|
||||
NpSignaling::Stubs::SignalingSendTo(&ping, sizeof(ping), server_addr, server_udp);
|
||||
LOG_DEBUG(Lib_NpMatching2,
|
||||
"Matching2 STUN ping: ctx={} online_id='{}' server={:#x}:{} local_ip={:#x} rc={}",
|
||||
ctx.ctx_id, OnlineIdToString(ctx.online_id), server_addr,
|
||||
Libraries::Net::sceNetNtohs(server_udp), ping.local_ip, rc);
|
||||
return rc >= 0;
|
||||
}
|
||||
|
||||
void QueueMatching2SignalingEvent(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
|
||||
OrbisNpMatching2RoomMemberId member_id,
|
||||
OrbisNpMatching2Event event, s32 error_code) {
|
||||
PendingEvent ev{};
|
||||
ev.type = PendingEvent::SIGNALING_CB;
|
||||
ev.ctx_id = ctx.ctx_id;
|
||||
ev.fire_at = std::chrono::steady_clock::now();
|
||||
ev.room_id = room_id;
|
||||
ev.member_id = member_id;
|
||||
ev.sig_event = event;
|
||||
ev.error_code = error_code;
|
||||
ScheduleEvent(std::move(ev));
|
||||
}
|
||||
|
||||
void StartMatching2PeerHandshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
|
||||
OrbisNpMatching2RoomMemberId member_id) {
|
||||
auto room_it = ctx.room_cache.find(room_id);
|
||||
if (room_it == ctx.room_cache.end() ||
|
||||
!ShouldConnectToPeer(room_it->second, ctx.my_member_id, member_id)) {
|
||||
return;
|
||||
}
|
||||
auto member_it = room_it->second.members.find(member_id);
|
||||
if (member_it == room_it->second.members.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Net::EnsureP2PTransport();
|
||||
|
||||
PeerInfo& peer = ctx.peers[member_id];
|
||||
peer.member_id = member_id;
|
||||
if (peer.status == kMatching2ConnActive) {
|
||||
return;
|
||||
}
|
||||
ResolvePeerEndpoint(member_it->second, peer);
|
||||
peer.status = kMatching2ConnPending;
|
||||
peer.handshake_started = true;
|
||||
peer.sent_check = false;
|
||||
if (peer.nonce == 0) {
|
||||
peer.nonce = static_cast<u64>(std::chrono::steady_clock::now().time_since_epoch().count() ^
|
||||
(static_cast<u64>(ctx.ctx_id) << 48) ^
|
||||
(static_cast<u64>(member_id) << 16));
|
||||
}
|
||||
SendMatching2Handshake(ctx, room_id, member_id, Matching2HandshakeKind::Offer, 0);
|
||||
}
|
||||
|
||||
void StartMatching2SignalingForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id) {
|
||||
const auto room_it = ctx.room_cache.find(room_id);
|
||||
if (room_it == ctx.room_cache.end()) {
|
||||
return;
|
||||
}
|
||||
for (const auto& [member_id, member] : room_it->second.members) {
|
||||
StartMatching2PeerHandshake(ctx, room_id, member_id);
|
||||
}
|
||||
}
|
||||
|
||||
void QueueMatching2DeadForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
|
||||
s32 error_code) {
|
||||
auto room_it = ctx.room_cache.find(room_id);
|
||||
if (room_it == ctx.room_cache.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& [member_id, member] : room_it->second.members) {
|
||||
if (member_id == 0 || member_id == ctx.my_member_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto peer_it = ctx.peers.find(member_id);
|
||||
if (peer_it != ctx.peers.end()) {
|
||||
peer_it->second.status = kMatching2ConnInactive;
|
||||
peer_it->second.handshake_started = false;
|
||||
peer_it->second.sent_check = false;
|
||||
}
|
||||
|
||||
QueueMatching2SignalingEvent(ctx, room_id, member_id,
|
||||
ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD, error_code);
|
||||
LOG_INFO(Lib_NpMatching2, "Matching2 signaling dead: ctx={} room={} member={} reason={:#x}",
|
||||
ctx.ctx_id, room_id, member_id, static_cast<u32>(error_code));
|
||||
}
|
||||
}
|
||||
|
||||
void StartMatching2HandshakeThread() {
|
||||
std::lock_guard lock(g_matching2_thread_mutex);
|
||||
if (g_matching2_thread.joinable()) {
|
||||
return;
|
||||
}
|
||||
g_matching2_stop.store(false, std::memory_order_relaxed);
|
||||
g_matching2_thread = std::thread(Matching2HandshakeThreadMain);
|
||||
}
|
||||
|
||||
void StopMatching2HandshakeThread() {
|
||||
{
|
||||
std::lock_guard lock(g_matching2_thread_mutex);
|
||||
g_matching2_stop.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
if (g_matching2_thread.joinable()) {
|
||||
g_matching2_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
u32 GetRoomPingUs(const ContextObject& ctx, OrbisNpMatching2RoomId roomId) {
|
||||
const auto room_it = ctx.room_cache.find(roomId);
|
||||
if (room_it == ctx.room_cache.end()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 total_ping = 0;
|
||||
u32 ping_count = 0;
|
||||
for (const auto& [member_id, member] : room_it->second.members) {
|
||||
if (member_id == ctx.my_member_id) {
|
||||
continue;
|
||||
}
|
||||
const auto peer_it = ctx.peers.find(member_id);
|
||||
if (peer_it == ctx.peers.end() || peer_it->second.ping_us == 0) {
|
||||
continue;
|
||||
}
|
||||
total_ping += peer_it->second.ping_us;
|
||||
++ping_count;
|
||||
}
|
||||
|
||||
return ping_count == 0 ? 0 : static_cast<u32>(total_ping / ping_count);
|
||||
}
|
||||
|
||||
void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, OrbisNpMatching2RoomId roomId) {
|
||||
CallbackPayload& p =
|
||||
ctx.request_payload_override ? *ctx.request_payload_override : ctx.request_payload;
|
||||
p.Reset();
|
||||
|
||||
const auto room_it = ctx.room_cache.find(roomId);
|
||||
p.ping_info_response = std::make_unique<OrbisNpMatching2SignalingGetPingInfoResponse>();
|
||||
auto& out = *p.ping_info_response;
|
||||
out = {};
|
||||
if (room_it != ctx.room_cache.end()) {
|
||||
out.serverId = room_it->second.server_id;
|
||||
out.worldId = room_it->second.world_id;
|
||||
out.roomId = room_it->second.room_id;
|
||||
} else {
|
||||
out.serverId = ctx.server_id;
|
||||
out.worldId = ctx.world_id;
|
||||
out.roomId = roomId;
|
||||
}
|
||||
out.rtt = GetRoomPingUs(ctx, roomId);
|
||||
|
||||
p.request_data = p.ping_info_response.get();
|
||||
return p.request_data;
|
||||
}
|
||||
|
||||
s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId roomId,
|
||||
OrbisNpMatching2RoomMemberId memberId, u32 infoType, void* connInfo,
|
||||
bool a_variant) {
|
||||
if (!connInfo) {
|
||||
LOG_ERROR(Lib_NpMatching2, "connInfo null");
|
||||
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
const auto room_it = ctx.room_cache.find(roomId);
|
||||
if (room_it == ctx.room_cache.end()) {
|
||||
LOG_INFO(Lib_NpMatching2, "room={} not cached for connection info", roomId);
|
||||
if (a_variant) {
|
||||
*static_cast<OrbisNpMatching2SignalingConnectionInfoA*>(connInfo) = {};
|
||||
} else {
|
||||
*static_cast<OrbisNpMatching2SignalingConnectionInfo*>(connInfo) = {};
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
const auto member_it = room_it->second.members.find(memberId);
|
||||
const MemberCache* member =
|
||||
member_it != room_it->second.members.end() ? &member_it->second : nullptr;
|
||||
const auto peer_it = ctx.peers.find(memberId);
|
||||
const PeerInfo* peer = peer_it != ctx.peers.end() ? &peer_it->second : nullptr;
|
||||
|
||||
if (a_variant) {
|
||||
auto* out = static_cast<OrbisNpMatching2SignalingConnectionInfoA*>(connInfo);
|
||||
*out = {};
|
||||
switch (infoType) {
|
||||
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_RTT:
|
||||
out->rtt = peer ? peer->ping_us : 0;
|
||||
break;
|
||||
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_BANDWIDTH:
|
||||
out->bandwidth = 0;
|
||||
break;
|
||||
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR:
|
||||
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_MAPPED_ADDR:
|
||||
if (peer) {
|
||||
out->address.addr = peer->addr;
|
||||
out->address.port = peer->port;
|
||||
} else if (member) {
|
||||
out->address.addr = member->addr;
|
||||
out->address.port = member->port;
|
||||
}
|
||||
break;
|
||||
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PACKET_LOSS:
|
||||
out->packetLoss = 0;
|
||||
break;
|
||||
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDRESS_A:
|
||||
if (member) {
|
||||
out->peerAddrA.accountId = member->account_id;
|
||||
out->peerAddrA.platform = member->platform;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_WARNING(Lib_NpMatching2, "unsupported connection info A type={}", infoType);
|
||||
return ORBIS_NP_MATCHING2_SIGNALING_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
} else {
|
||||
auto* out = static_cast<OrbisNpMatching2SignalingConnectionInfo*>(connInfo);
|
||||
*out = {};
|
||||
switch (infoType) {
|
||||
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_RTT:
|
||||
out->rtt = peer ? peer->ping_us : 0;
|
||||
break;
|
||||
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_BANDWIDTH:
|
||||
out->bandwidth = 0;
|
||||
break;
|
||||
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_NP_ID:
|
||||
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_NPID:
|
||||
if (member) {
|
||||
out->npId = member->np_id;
|
||||
}
|
||||
break;
|
||||
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR:
|
||||
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_MAPPED_ADDR:
|
||||
if (peer) {
|
||||
out->address.addr = peer->addr;
|
||||
out->address.port = peer->port;
|
||||
} else if (member) {
|
||||
out->address.addr = member->addr;
|
||||
out->address.port = member->port;
|
||||
}
|
||||
break;
|
||||
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PACKET_LOSS:
|
||||
out->packetLoss = 0;
|
||||
break;
|
||||
default:
|
||||
LOG_WARNING(Lib_NpMatching2, "unsupported connection info type={}", infoType);
|
||||
return ORBIS_NP_MATCHING2_SIGNALING_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO(Lib_NpMatching2, "connection info{}: ctx={} room={} member={} type={} rtt={}",
|
||||
a_variant ? "A" : "", ctx.ctx_id, roomId, memberId, infoType,
|
||||
peer ? peer->ping_us : 0);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
} // namespace Libraries::Np::NpMatching2
|
||||
@@ -1,523 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "core/libraries/kernel/memory.h"
|
||||
#include "core/libraries/network/net.h"
|
||||
#include "core/libraries/network/net_util.h"
|
||||
#include "core/libraries/np/np_common.h"
|
||||
#include "core/libraries/np/np_error.h"
|
||||
#include "core/libraries/np/np_signaling/np_signaling_helpers.h"
|
||||
#include "core/libraries/np/np_signaling/np_signaling_state.h"
|
||||
#include "core/libraries/np/np_signaling/np_signaling_stubs.h"
|
||||
|
||||
namespace Libraries::Np::NpSignaling::Helpers {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr s32 InferredNpAppType = 0;
|
||||
|
||||
bool g_signaling_heap_initialized = false;
|
||||
void* g_signaling_heap_base = nullptr;
|
||||
s64 g_signaling_heap_size = 0;
|
||||
SignalingRuntimeHooks g_runtime_hooks{};
|
||||
bool g_runtime_hooks_registered = false;
|
||||
u64 g_echo_probe_word2 = 0;
|
||||
u64 g_echo_probe_word3 = 0;
|
||||
u64 g_echo_thread_handle = static_cast<u64>(-1);
|
||||
s32 g_echo_thread_status = -1;
|
||||
|
||||
s32 GetInferredAppType(s32* app_type) {
|
||||
if (app_type == nullptr) {
|
||||
return ORBIS_NP_INT_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
*app_type = InferredNpAppType;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s16 GetAppTypeStateGate() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SetAppTypeMarker(u16 marker) {
|
||||
LOG_DEBUG(Lib_NpSignaling, "marker={:#x}", marker);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void SetRuntimeHooks(const SignalingRuntimeHooks& hooks) {
|
||||
g_runtime_hooks = hooks;
|
||||
g_runtime_hooks_registered = true;
|
||||
}
|
||||
|
||||
s32 CheckInitializeAppType(u32* is_app_type_4) {
|
||||
s32 app_type = -1;
|
||||
const s32 rc = GetInferredAppType(&app_type);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (is_app_type_4 != nullptr) {
|
||||
*is_app_type_4 = app_type == 4 ? 1u : 0u;
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 InitSignalingHeap(s64 pool_size) {
|
||||
LOG_DEBUG(Lib_NpSignaling, "pool_size={}", pool_size);
|
||||
|
||||
if (pool_size == 0 || g_signaling_heap_initialized) {
|
||||
return ORBIS_NP_SIGNALING_INTERNAL_ERROR_ALLOCATOR;
|
||||
}
|
||||
|
||||
const u64 aligned_size = Common::AlignUp(static_cast<u64>(pool_size), 0x4000);
|
||||
|
||||
void* heap_base = nullptr;
|
||||
const s32 rc = Libraries::Kernel::sceKernelMapNamedFlexibleMemory(&heap_base, aligned_size, 3,
|
||||
0, "SceNpSignaling");
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
g_signaling_heap_initialized = true;
|
||||
g_signaling_heap_base = heap_base;
|
||||
g_signaling_heap_size = static_cast<s64>(aligned_size);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void ShutdownSignalingHeap() {
|
||||
if (!g_signaling_heap_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_signaling_heap_initialized = false;
|
||||
|
||||
if (g_signaling_heap_base != nullptr) {
|
||||
Libraries::Kernel::sceKernelMunmap(g_signaling_heap_base,
|
||||
static_cast<u64>(g_signaling_heap_size));
|
||||
g_signaling_heap_base = nullptr;
|
||||
g_signaling_heap_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
s32 CheckAppType() {
|
||||
s32 app_type = -1;
|
||||
const s32 rc = GetInferredAppType(&app_type);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (app_type == 5) {
|
||||
const s16 gate = GetAppTypeStateGate();
|
||||
if (gate != 0) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
SetAppTypeMarker(0x245a);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 StartMainRuntime(s32 thread_priority, s32 cpu_affinity_mask, s64 thread_stack_size) {
|
||||
LOG_DEBUG(Lib_NpSignaling,
|
||||
"thread_priority={} cpu_affinity_mask={} "
|
||||
"thread_stack_size={}",
|
||||
thread_priority, cpu_affinity_mask, thread_stack_size);
|
||||
|
||||
if (!g_runtime_hooks_registered) {
|
||||
return ORBIS_NP_SIGNALING_INTERNAL_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
const s32 priority = thread_priority;
|
||||
const u64 affinity = static_cast<u64>(static_cast<u32>(cpu_affinity_mask));
|
||||
const u64 stack_size = static_cast<u64>(thread_stack_size);
|
||||
if (g_runtime_hooks.start_dispatch != nullptr) {
|
||||
g_runtime_hooks.start_dispatch(priority, affinity, stack_size);
|
||||
}
|
||||
if (g_runtime_hooks.start_receive != nullptr) {
|
||||
g_runtime_hooks.start_receive(priority, affinity, stack_size);
|
||||
}
|
||||
if (g_runtime_hooks.start_ping != nullptr) {
|
||||
g_runtime_hooks.start_ping(priority, affinity, stack_size);
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 StartEchoRuntime(s32 thread_priority, s32 cpu_affinity_mask) {
|
||||
LOG_DEBUG(Lib_NpSignaling, "thread_priority={} cpu_affinity_mask={}", thread_priority,
|
||||
cpu_affinity_mask);
|
||||
|
||||
auto sock = Libraries::Net::sceNetSocket("SceNpSignalingIoctl", 2, 6, 0);
|
||||
if (sock < 0) {
|
||||
return sock;
|
||||
}
|
||||
|
||||
const s32 ioctl_rc = Libraries::Net::sceNetIoctl();
|
||||
Libraries::Net::sceNetSocketClose(sock);
|
||||
|
||||
g_echo_probe_word2 = 0;
|
||||
g_echo_probe_word3 = 0;
|
||||
g_echo_thread_handle = static_cast<u64>(-1);
|
||||
g_echo_thread_status = -1;
|
||||
return ioctl_rc;
|
||||
}
|
||||
|
||||
void ShutdownRuntime() {
|
||||
if (!g_runtime_hooks_registered) {
|
||||
g_echo_probe_word2 = 0;
|
||||
g_echo_probe_word3 = 0;
|
||||
g_echo_thread_handle = static_cast<u64>(-1);
|
||||
g_echo_thread_status = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_runtime_hooks.stop_ping != nullptr) {
|
||||
g_runtime_hooks.stop_ping();
|
||||
}
|
||||
if (g_runtime_hooks.stop_receive != nullptr) {
|
||||
g_runtime_hooks.stop_receive();
|
||||
}
|
||||
if (g_runtime_hooks.stop_dispatch != nullptr) {
|
||||
g_runtime_hooks.stop_dispatch();
|
||||
}
|
||||
|
||||
g_echo_probe_word2 = 0;
|
||||
g_echo_probe_word3 = 0;
|
||||
g_echo_thread_handle = static_cast<u64>(-1);
|
||||
g_echo_thread_status = -1;
|
||||
}
|
||||
|
||||
} // namespace Libraries::Np::NpSignaling::Helpers
|
||||
|
||||
namespace Libraries::Np::NpSignaling {
|
||||
|
||||
using Libraries::Net::sceNetNtohs;
|
||||
|
||||
static Kernel::PthreadT g_dispatch_thread{};
|
||||
static Kernel::PthreadT g_receive_thread{};
|
||||
static bool g_receive_stop = false;
|
||||
static Kernel::PthreadT g_ping_thread{};
|
||||
static bool g_ping_stop = false;
|
||||
|
||||
static void HandleStunEcho(s32 ctx_id, const StunEcho& echo) {
|
||||
SignalingMutexGuard lock;
|
||||
const auto it = g_contexts.find(ctx_id);
|
||||
if (it == g_contexts.end() || !it->second.active) {
|
||||
return;
|
||||
}
|
||||
NpSignalingContext& ctx = it->second;
|
||||
ctx.ext_addr.store(echo.ext_ip);
|
||||
ctx.ext_port.store(echo.ext_port);
|
||||
|
||||
LOG_DEBUG(Lib_NpSignaling, "STUN echo: ctxId={} ext_addr={:#x} ext_port={}", ctx_id,
|
||||
echo.ext_ip, sceNetNtohs(echo.ext_port));
|
||||
|
||||
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
|
||||
netinfo->SetExternalIp(echo.ext_ip);
|
||||
|
||||
ctx.stun_cv.notify_all();
|
||||
}
|
||||
|
||||
static bool HasSignalingMagic(const u8* buf, size_t nbytes) {
|
||||
return nbytes >= 5 && std::memcmp(buf, kSignalingMagic, sizeof(kSignalingMagic)) == 0;
|
||||
}
|
||||
|
||||
static void DrainControlPackets() {
|
||||
static constexpr u32 kBufSize = 256;
|
||||
u8 buf[kBufSize];
|
||||
for (;;) {
|
||||
u32 from_addr = 0;
|
||||
u16 from_port = 0;
|
||||
const int rc = Stubs::ControlRecvFrom(buf, kBufSize, &from_addr, &from_port);
|
||||
if (rc <= 0) {
|
||||
break;
|
||||
}
|
||||
const auto nbytes = static_cast<size_t>(rc);
|
||||
if (nbytes == sizeof(SignalingControl) && HasSignalingMagic(buf, nbytes) &&
|
||||
buf[4] == static_cast<u8>(SignalingPacketType::Control)) {
|
||||
SignalingControl ctrl{};
|
||||
std::memcpy(&ctrl, buf, sizeof(ctrl));
|
||||
HandleControlPacket(from_addr, from_port, ctrl);
|
||||
} else {
|
||||
LOG_WARNING(Lib_NpSignaling, "ReceiveThread: bad control packet (size={})", nbytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ReceiveThreadMain() {
|
||||
static constexpr u32 kBufSize = 256;
|
||||
u8 buf[kBufSize];
|
||||
|
||||
while (!g_receive_stop) {
|
||||
DrainControlPackets();
|
||||
|
||||
u32 from_addr = 0;
|
||||
u16 from_port = 0;
|
||||
|
||||
const int rc = Stubs::SignalingRecvFrom(buf, kBufSize, &from_addr, &from_port);
|
||||
if (rc <= 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto nbytes = static_cast<size_t>(rc);
|
||||
|
||||
if (nbytes != sizeof(StunEcho)) {
|
||||
LOG_DEBUG(
|
||||
Lib_NpSignaling,
|
||||
"ReceiveThread DATA from {:#x}:{} size={} b0-4={:02x},{:02x},{:02x},{:02x},{:02x}",
|
||||
from_addr, sceNetNtohs(from_port), nbytes, buf[0], buf[1], buf[2], buf[3],
|
||||
nbytes >= 5 ? buf[4] : 0);
|
||||
}
|
||||
|
||||
if (nbytes == sizeof(StunEcho)) {
|
||||
s32 ctx_id = 0;
|
||||
{
|
||||
SignalingMutexGuard lock;
|
||||
for (const auto& [cid, ctx] : g_contexts) {
|
||||
if (ctx.active) {
|
||||
ctx_id = cid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctx_id == 0) {
|
||||
continue;
|
||||
}
|
||||
StunEcho echo{};
|
||||
std::memcpy(&echo, buf, sizeof(echo));
|
||||
HandleStunEcho(ctx_id, echo);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!HasSignalingMagic(buf, nbytes)) {
|
||||
LOG_WARNING(Lib_NpSignaling, "ReceiveThread: dropping non-SHAD packet (size={})",
|
||||
nbytes);
|
||||
continue;
|
||||
}
|
||||
const u8 type = buf[4];
|
||||
|
||||
if (nbytes == sizeof(SignalingEchoPing) &&
|
||||
type == static_cast<u8>(SignalingPacketType::EchoPing)) {
|
||||
SignalingEchoPing ping{};
|
||||
std::memcpy(&ping, buf, sizeof(ping));
|
||||
SignalingEchoPong pong{};
|
||||
pong.conn_id = ping.conn_id;
|
||||
pong.orig_ts_us = ping.send_ts_us;
|
||||
Stubs::SignalingSendTo(&pong, sizeof(pong), from_addr, from_port);
|
||||
continue;
|
||||
}
|
||||
if (nbytes == sizeof(SignalingEchoPong) &&
|
||||
type == static_cast<u8>(SignalingPacketType::EchoPong)) {
|
||||
SignalingEchoPong pong{};
|
||||
std::memcpy(&pong, buf, sizeof(pong));
|
||||
const s64 now = NowUs();
|
||||
s32 rtt_us = static_cast<s32>(now - static_cast<s64>(pong.orig_ts_us));
|
||||
if (rtt_us < 0) {
|
||||
rtt_us = 0;
|
||||
}
|
||||
{
|
||||
SignalingMutexGuard lock;
|
||||
RecordRttSampleLocked(static_cast<s32>(pong.conn_id), rtt_us);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (nbytes == sizeof(SignalingHandshake) &&
|
||||
type == static_cast<u8>(SignalingPacketType::Handshake)) {
|
||||
SignalingHandshake hs{};
|
||||
std::memcpy(&hs, buf, sizeof(hs));
|
||||
HandleHandshakePacket(from_addr, from_port, hs);
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_WARNING(Lib_NpSignaling, "ReceiveThread: unexpected SHAD packet type={} size={}", type,
|
||||
nbytes);
|
||||
}
|
||||
}
|
||||
|
||||
static PS4_SYSV_ABI void* ReceiveThreadFunc(void*) {
|
||||
ReceiveThreadMain();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void StartReceiveThread(s32 priority, u64 affinity_mask, u64 stack_size) {
|
||||
g_receive_stop = false;
|
||||
if (!g_receive_thread) {
|
||||
NpCommon::sceNpCreateThread(&g_receive_thread, ReceiveThreadFunc, nullptr, priority,
|
||||
stack_size, affinity_mask, "SceNpSignalingRecv");
|
||||
}
|
||||
}
|
||||
|
||||
static void StopReceiveThread() {
|
||||
{
|
||||
SignalingMutexGuard lock;
|
||||
g_receive_stop = true;
|
||||
}
|
||||
if (g_receive_thread) {
|
||||
NpCommon::sceNpJoinThread(g_receive_thread, nullptr);
|
||||
g_receive_thread = {};
|
||||
}
|
||||
}
|
||||
|
||||
static void PingThreadMain() {
|
||||
while (!g_ping_stop) {
|
||||
if (!Stubs::EnsureTransport()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(kSigRetryMs));
|
||||
continue;
|
||||
}
|
||||
|
||||
struct CtxSnapshot {
|
||||
s32 ctx_id;
|
||||
OrbisNpOnlineId online_id{};
|
||||
bool resolved;
|
||||
};
|
||||
std::vector<CtxSnapshot> contexts;
|
||||
{
|
||||
SignalingMutexGuard lock;
|
||||
for (const auto& [ctx_id, ctx] : g_contexts) {
|
||||
if (ctx.active) {
|
||||
contexts.push_back({ctx_id, ctx.owner_online_id, ctx.ext_addr.load() != 0});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Stubs::Matching2Enabled()) {
|
||||
const u32 server_addr = Stubs::MmServerAddr();
|
||||
const u16 server_port = Stubs::MmServerUdpPort();
|
||||
|
||||
for (const auto& cs : contexts) {
|
||||
if (server_addr == 0 || server_port == 0) {
|
||||
break;
|
||||
}
|
||||
StunPing ping{};
|
||||
ping.cmd = 0x01;
|
||||
std::memcpy(ping.online_id, cs.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
|
||||
ping.local_ip = Stubs::AdvertisedAddr();
|
||||
|
||||
Stubs::SignalingSendTo(&ping, sizeof(ping), server_addr, server_port);
|
||||
}
|
||||
}
|
||||
|
||||
SendEchoPings();
|
||||
|
||||
const bool any_unresolved = std::any_of(contexts.begin(), contexts.end(),
|
||||
[](const auto& cs) { return !cs.resolved; });
|
||||
const u32 sleep_ms = any_unresolved ? kSigRetryMs : kSigPingMs;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
|
||||
}
|
||||
}
|
||||
|
||||
static PS4_SYSV_ABI void* PingThreadFunc(void*) {
|
||||
PingThreadMain();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void StartPingThread(s32 priority, u64 affinity_mask, u64 stack_size) {
|
||||
g_ping_stop = false;
|
||||
if (!g_ping_thread) {
|
||||
NpCommon::sceNpCreateThread(&g_ping_thread, PingThreadFunc, nullptr, priority, stack_size,
|
||||
affinity_mask, "SceNpSignalingPing");
|
||||
}
|
||||
}
|
||||
|
||||
static void StopPingThread() {
|
||||
g_ping_stop = true;
|
||||
if (g_ping_thread) {
|
||||
NpCommon::sceNpJoinThread(g_ping_thread, nullptr);
|
||||
g_ping_thread = {};
|
||||
}
|
||||
}
|
||||
|
||||
static void DispatchThreadMain() {
|
||||
for (;;) {
|
||||
ProcessPendingActivations();
|
||||
|
||||
QueuedDispatch dispatch;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(g_dispatch_mutex);
|
||||
for (;;) {
|
||||
if (g_dispatch_stop) {
|
||||
return;
|
||||
}
|
||||
if (g_dispatch_queue.empty()) {
|
||||
g_dispatch_cv.wait_for(lock, std::chrono::seconds(1), [] {
|
||||
return g_dispatch_stop || !g_dispatch_queue.empty();
|
||||
});
|
||||
break;
|
||||
}
|
||||
const auto it = g_dispatch_queue.begin();
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (it->first > now) {
|
||||
g_dispatch_cv.wait_until(lock, it->first);
|
||||
break;
|
||||
}
|
||||
dispatch = std::move(it->second);
|
||||
g_dispatch_queue.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dispatch.callback) {
|
||||
LOG_INFO(Lib_NpSignaling, "t={} ctxId={} connId={} event={}({}) delay={}ms", NowMs(),
|
||||
dispatch.ctx_id, dispatch.conn_id, dispatch.event_type,
|
||||
SignalingEventName(dispatch.event_type), dispatch.delay_ms);
|
||||
LOG_DEBUG(Lib_NpSignaling,
|
||||
"INVOKE signaling_cb={} ctxId={} connId={} event={}({}) errorCode={} arg={}",
|
||||
fmt::ptr(reinterpret_cast<void*>(dispatch.callback)), dispatch.ctx_id,
|
||||
dispatch.conn_id, dispatch.event_type,
|
||||
SignalingEventName(dispatch.event_type), dispatch.error_code,
|
||||
fmt::ptr(dispatch.callback_arg));
|
||||
dispatch.callback(static_cast<u32>(dispatch.ctx_id), static_cast<u32>(dispatch.conn_id),
|
||||
dispatch.event_type, dispatch.error_code, dispatch.callback_arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static PS4_SYSV_ABI void* DispatchThreadFunc(void*) {
|
||||
DispatchThreadMain();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void StartDispatchThread(s32 priority, u64 affinity_mask, u64 stack_size) {
|
||||
std::lock_guard<std::mutex> lock(g_dispatch_mutex);
|
||||
g_dispatch_stop = false;
|
||||
if (!g_dispatch_thread) {
|
||||
NpCommon::sceNpCreateThread(&g_dispatch_thread, DispatchThreadFunc, nullptr, priority,
|
||||
stack_size, affinity_mask, "SceNpSignalingMain");
|
||||
}
|
||||
}
|
||||
|
||||
static void StopDispatchThread() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_dispatch_mutex);
|
||||
g_dispatch_stop = true;
|
||||
g_dispatch_queue.clear();
|
||||
}
|
||||
g_dispatch_cv.notify_all();
|
||||
if (g_dispatch_thread) {
|
||||
NpCommon::sceNpJoinThread(g_dispatch_thread, nullptr);
|
||||
g_dispatch_thread = {};
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterRuntimeHooks() {
|
||||
Helpers::SetRuntimeHooks({
|
||||
.start_dispatch = StartDispatchThread,
|
||||
.start_receive = StartReceiveThread,
|
||||
.start_ping = StartPingThread,
|
||||
.stop_ping = StopPingThread,
|
||||
.stop_receive = StopReceiveThread,
|
||||
.stop_dispatch = StopDispatchThread,
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Libraries::Np::NpSignaling
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,87 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/rtc/rtc.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::Np::NpTus {
|
||||
|
||||
using OrbisNpTssSlotId = s32;
|
||||
using OrbisNpTusSlotId = s32;
|
||||
|
||||
struct OrbisNpTusVariable {
|
||||
OrbisNpId npId;
|
||||
int set;
|
||||
Libraries::Rtc::OrbisRtcTick lastChanged;
|
||||
u8 pad1[4];
|
||||
OrbisNpId lastChangedAuthor;
|
||||
s64 variable;
|
||||
s64 oldVariable;
|
||||
OrbisNpAccountId owner;
|
||||
OrbisNpAccountId lastChangedAuthorId;
|
||||
};
|
||||
|
||||
struct OrbisNpTusDataInfo {
|
||||
u64 size;
|
||||
u8 data[384];
|
||||
};
|
||||
|
||||
struct OrbisNpTusDataStatus {
|
||||
OrbisNpId npId;
|
||||
int set;
|
||||
Libraries::Rtc::OrbisRtcTick lastChanged;
|
||||
OrbisNpId lastChangedAuthor;
|
||||
u8 pad2[4];
|
||||
void* data;
|
||||
u64 dataSize;
|
||||
OrbisNpTusDataInfo info;
|
||||
};
|
||||
|
||||
static_assert(sizeof(OrbisNpTusDataStatus) == 0x1F0);
|
||||
|
||||
struct OrbisNpTusDataStatusA {
|
||||
OrbisNpOnlineId onlineId;
|
||||
u8 pad[16];
|
||||
int set;
|
||||
Libraries::Rtc::OrbisRtcTick lastChanged;
|
||||
OrbisNpOnlineId lastChangedAuthor;
|
||||
u8 pad2[20];
|
||||
void* data;
|
||||
u64 dataSize;
|
||||
OrbisNpTusDataInfo info;
|
||||
OrbisNpAccountId owner;
|
||||
OrbisNpAccountId lastChangedAuthorId;
|
||||
u8 pad3[16];
|
||||
};
|
||||
|
||||
static_assert(sizeof(OrbisNpTusDataStatusA) == 0x210);
|
||||
|
||||
enum class OrbisNpTssStatus : int {
|
||||
Ok = 0,
|
||||
Partial = 1,
|
||||
NotModified = 2,
|
||||
};
|
||||
|
||||
struct OrbisNpTssDataStatus {
|
||||
Libraries::Rtc::OrbisRtcTick modified;
|
||||
OrbisNpTssStatus status;
|
||||
u64 contentLength;
|
||||
};
|
||||
|
||||
struct OrbisNpTssGetDataOptParam {
|
||||
u64 size;
|
||||
u64* offset;
|
||||
u64* last;
|
||||
void* param;
|
||||
};
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpTusWaitAsync(int reqId, int* result);
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Np::NpTus
|
||||
@@ -0,0 +1,66 @@
|
||||
# bbport online module, second generation: libbbnet20.so. shadPS4 v0.20.0's network, NP and
|
||||
# shadNet sources (vendor/shadps4, 529ceb3, with vendor-changes.diff) and The Hunter's Dream
|
||||
# backend (thd/, from droogie/bbhost), as Supermedo/Awaken put them together
|
||||
# (SHADNET-PORT.md). The port's only online module (src/runtime_net.c loads it, BB_NET_MODULE
|
||||
# overrides the path; the former shadp2p module gpu/bbnet was removed 2026-10-26). Hidden
|
||||
# symbols and -Bsymbolic: its C++ never meets libbbgpu's.
|
||||
|
||||
set(BBPORT_VERSION "0.6-pre1")
|
||||
set(BBNET_VENDOR "${CMAKE_CURRENT_SOURCE_DIR}/vendor/shadps4")
|
||||
set(SHADNET_PROTO_OUT20 "${CMAKE_CURRENT_BINARY_DIR}/shadnet_proto_gen")
|
||||
file(MAKE_DIRECTORY "${SHADNET_PROTO_OUT20}")
|
||||
# Cross builds (Windows from Linux) run the build machine's protoc, of the sysroot's version.
|
||||
set(BBNET_PROTOC protobuf::protoc)
|
||||
if (CMAKE_CROSSCOMPILING)
|
||||
find_program(BBNET_HOST_PROTOC protoc NO_CMAKE_FIND_ROOT_PATH REQUIRED)
|
||||
set(BBNET_PROTOC "${BBNET_HOST_PROTOC}")
|
||||
endif()
|
||||
add_custom_command(
|
||||
OUTPUT "${SHADNET_PROTO_OUT20}/shadnet.pb.cc" "${SHADNET_PROTO_OUT20}/shadnet.pb.h"
|
||||
COMMAND ${BBNET_PROTOC} "--proto_path=${BBNET_VENDOR}/shadnet"
|
||||
"--cpp_out=${SHADNET_PROTO_OUT20}" "${BBNET_VENDOR}/shadnet/shadnet.proto"
|
||||
DEPENDS "${BBNET_VENDOR}/shadnet/shadnet.proto")
|
||||
file(GLOB_RECURSE BBNET20_SOURCES CONFIGURE_DEPENDS
|
||||
vendor/shadps4/core/libraries/net/*.cpp vendor/shadps4/core/libraries/network/*.cpp
|
||||
vendor/shadps4/core/libraries/np/*.cpp vendor/shadps4/core/net/*.cpp
|
||||
vendor/shadps4/shadnet/*.cpp)
|
||||
# Trophies, commerce and the profile and Facebook dialogs stay in the C runtime.
|
||||
list(FILTER BBNET20_SOURCES EXCLUDE REGEX
|
||||
"np_trophy|trophy_ui|np_sns_facebook_dialog|np_profile_dialog/|np_commerce/")
|
||||
# BBSC, Bloodborne seamless co-op (GPL-2.0+): its game code and the port's stand-ins for
|
||||
# shadPS4's UI (bbsc/bbsc_glue.cpp). Installed only for Bloodborne with BB_BBSC=1.
|
||||
file(GLOB BBNET20_BBSC_SOURCES CONFIGURE_DEPENDS bbsc/core/*.cpp bbsc/bbsc_glue.cpp)
|
||||
file(GLOB BBNET20_THD_SOURCES CONFIGURE_DEPENDS thd/*.cpp compat/common/*.cpp)
|
||||
|
||||
add_library(bbnet20 SHARED ${BBNET20_SOURCES} ${BBNET20_THD_SOURCES} ${BBNET20_BBSC_SOURCES}
|
||||
"${SHADNET_PROTO_OUT20}/shadnet.pb.cc" bbnet_glue.cpp)
|
||||
target_include_directories(bbnet20 PRIVATE include bbsc compat . .. ../shim "${BBNET_VENDOR}"
|
||||
"${BBNET_VENDOR}/shadnet" "${SHADNET_PROTO_OUT20}" ../third_party/imgui ../third_party/httplib)
|
||||
# The version the shadNet client reports to the server (BB_CLIENT_VERSION overrides it).
|
||||
target_compile_definitions(bbnet20 PRIVATE BOOST_ASIO_STANDALONE NDEBUG
|
||||
CPPHTTPLIB_OPENSSL_SUPPORT CPPHTTPLIB_ZLIB_SUPPORT BBNET_PORT_VERSION="${BBPORT_VERSION}")
|
||||
target_compile_options(bbnet20 PRIVATE -fvisibility=hidden -fvisibility-inlines-hidden
|
||||
-Wno-deprecated-declarations -Wno-missing-field-initializers)
|
||||
target_link_libraries(bbnet20 PRIVATE nlohmann_json::nlohmann_json protobuf::libprotobuf
|
||||
OpenSSL::SSL OpenSSL::Crypto ZLIB::ZLIB "${MINIUPNPC_LIBRARY}" Vulkan::Headers fmt::fmt
|
||||
Boost::headers magic_enum::magic_enum)
|
||||
if (WIN32)
|
||||
# Windows: bbgpu is inside bb-probe.exe (static), so bbnet20.dll takes the few functions it
|
||||
# needs (log, paths, guest code hooks) from the executable's import library, not a second copy
|
||||
# of the GPU library. Next to bb-probe.exe as bin/bbnet20.dll (no "lib" prefix).
|
||||
target_include_directories(bbnet20 PRIVATE $<TARGET_PROPERTY:bbgpu,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
target_compile_definitions(bbnet20 PRIVATE $<TARGET_PROPERTY:bbgpu,INTERFACE_COMPILE_DEFINITIONS>)
|
||||
# wepoll (piscisaureus/wepoll v1.5.8, BSD-2-Clause): epoll over AFD for shadPS4's host_net.
|
||||
target_sources(bbnet20 PRIVATE ../third_party/wepoll/wepoll.c)
|
||||
target_include_directories(bbnet20 PRIVATE ../third_party/wepoll)
|
||||
set_target_properties(bb-probe PROPERTIES ENABLE_EXPORTS ON)
|
||||
target_link_options(bb-probe PRIVATE -Wl,--export-all-symbols)
|
||||
target_link_libraries(bbnet20 PRIVATE bb-probe ws2_32 iphlpapi crypt32 PkgConfig::SDL3)
|
||||
set_target_properties(bbnet20 PROPERTIES PREFIX "" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
else()
|
||||
target_link_options(bbnet20 PRIVATE -Wl,-Bsymbolic -Wl,--exclude-libs,ALL)
|
||||
set_target_properties(bbnet20 PROPERTIES BUILD_RPATH "\$ORIGIN" INSTALL_RPATH "\$ORIGIN"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
target_link_libraries(bbnet20 PRIVATE bbgpu)
|
||||
endif()
|
||||
target_include_directories(bbnet20 PRIVATE "${MINIUPNPC_INCLUDE}")
|
||||
@@ -0,0 +1,33 @@
|
||||
# Native shadNet bridge
|
||||
|
||||
The network-only source in `gpu/bbnet20/vendor/shadps4/` is shadPS4's own, unchanged except
|
||||
where marked `bbport`:
|
||||
https://github.com/shadps4-emu/shadPS4/tree/v.0.20.0
|
||||
(529ceb32f5546b820588f194b903cc39d08015fe): `src/core/libraries/net`, `network`, `np`
|
||||
(without trophies, commerce and the profile and Facebook dialogs), `src/core/net` and
|
||||
`src/shadnet`. These portions are GPL-2.0-or-later, with their original notices.
|
||||
`third_party/httplib/httplib.h` and `third_party/wepoll/` retain their embedded
|
||||
third-party license notices.
|
||||
|
||||
The bbport changes: the shadNet session starts once the game is loaded
|
||||
(`NpManager::InitializeRuntime`, called by `bbnet_initialize`), and the client reports
|
||||
itself to the server as this port (`BB_CLIENT_VERSION`), not as shadPS4. A server that does not answer
|
||||
when the game starts (still starting, a VPN not up yet, a short outage) is tried again in the
|
||||
background, every 2 s and up to every 30 s, as after a dropped connection; one that answers and
|
||||
refuses the sign-in is not. An `exit()` with the network worker running no longer aborts.
|
||||
|
||||
The renderer, shaders and upscalers were not imported. `bbnet_glue.cpp` and `include/`
|
||||
implement what these libraries need from the rest of shadPS4 (kernel threads, user
|
||||
service, settings) on this project's runtime. Online play is off unless `BB_ONLINE=1`.
|
||||
|
||||
## The Hunter's Dream backend (`thd/`)
|
||||
|
||||
`thd/` is the co-op pool for The Hunter's Dream (`online_pool=huntersdream`, which sets
|
||||
`BB_NET_BACKEND=thd`). It adapts droogie/bbhost's network side
|
||||
(https://github.com/droogie/bbhost/tree/566184a74f071166299bb656c4af479e5bbe2771):
|
||||
`src/hle/net.cpp`, `src/hle/np_matching2.cpp`, `src/hle/np_signaling.cpp`, the NP parts of
|
||||
`src/hle/system.cpp`, and `src/net/{http,account,session,stun}.cpp`. These portions are
|
||||
GPL-3.0-or-later; copyright remains with the bbhost contributors. With that pool the game's NP and
|
||||
sceNet imports are these, ahead of the shadPS4 ones; the shadNet pool is unchanged. sceHttp stays
|
||||
shadPS4's, with two `bbport` hooks that act only in that pool: `*.example.com` host-override keys
|
||||
and the account token and ruleset headers.
|
||||
@@ -1,4 +1,4 @@
|
||||
/* bbport online module (libbbnet.so, gpu/bbnet): the PS4 network and PSN libraries with the
|
||||
/* bbport online module (libbbnet20.so, gpu/bbnet20): the PS4 network and PSN libraries with the
|
||||
* shadNet client. A separate library the runtime loads only for online play (BB_ONLINE=1,
|
||||
* src/runtime_net.c); offline the port never touches it, and a build without it runs offline.
|
||||
* Its C++ inside is private (hidden symbols, -Bsymbolic): these two calls are all it exports. */
|
||||
@@ -10,7 +10,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define BBNET_ABI_VERSION 1
|
||||
#define BBNET_ABI_VERSION 2
|
||||
|
||||
/* What the runtime gives the module: the game's identity (param.sfo, its folder for
|
||||
* sce_sys/npbind.dat) and guest threads for the NP libraries' callbacks into the game. */
|
||||
@@ -24,12 +24,30 @@ typedef struct BbNetHost {
|
||||
int32_t (*thread_spawn)(void **thread, void *(__attribute__((sysv_abi)) *entry)(void *),
|
||||
void *argument, uint64_t stack_size, const char *name);
|
||||
int32_t (*thread_join)(void *thread, void **result);
|
||||
/* Ends the calling guest thread (pthread_exit of the NP libraries' threads); does not return. */
|
||||
void (*thread_exit)(void *value);
|
||||
/* Nonzero when [address, address+size) is mapped guest memory (safe to read). */
|
||||
int (*memory_is_mapped)(uintptr_t address, uint64_t size);
|
||||
} BbNetHost;
|
||||
|
||||
/* Registers the libraries; 0 on success, else the module cannot run (the game stays offline). */
|
||||
typedef int (*BbNetInitFn)(const BbNetHost *host);
|
||||
int bbnet_init(const BbNetHost *host);
|
||||
|
||||
/* Optional: the game's image is mapped and patched, before its first instruction runs (game
|
||||
* modules of the online module install their hooks here: BBSC for Bloodborne). */
|
||||
typedef void (*BbNetImageReadyFn)(uint64_t image_base, uint64_t image_size);
|
||||
void bbnet_image_ready(uint64_t image_base, uint64_t image_size);
|
||||
|
||||
/* Optional: the game's image is mapped and relocated but not yet patched (modules that read the
|
||||
* original code, such as the Hunter's Dream backend, look at it here). */
|
||||
typedef void (*BbNetImageMappedFn)(uint8_t *image, uint64_t image_size);
|
||||
void bbnet_image_mapped(uint8_t *image, uint64_t image_size);
|
||||
|
||||
/* Optional: the game is loaded, its first instruction is next (online sessions start here). */
|
||||
typedef void (*BbNetStartFn)(void);
|
||||
void bbnet_start(void);
|
||||
|
||||
/* The module's function for an imported NID ("NID#lib#mod"), or 0. */
|
||||
typedef uintptr_t (*BbNetResolveFn)(const char *scoped_nid);
|
||||
uintptr_t bbnet_resolve(const char *scoped_nid);
|
||||
@@ -1,9 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// bbport online module (libbbnet.so): what the network and NP libraries from shadp2p need
|
||||
// from the rest of shadPS4, on top of the bbport runtime (BbNetHost), their registration and the
|
||||
// module's C interface (bbnet.h). From the Linux co-op fork of bbport, which took it from the
|
||||
// Windows port; the libraries are shadp2p's (a shadPS4 fork, GPL-2.0).
|
||||
// bbport co-op: what shadPS4 0.20.0's network and NP libraries need from the rest of
|
||||
// shadPS4, implemented on top of the bbport runtime, and their registration.
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdio>
|
||||
@@ -16,6 +15,7 @@
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -28,10 +28,12 @@
|
||||
#endif
|
||||
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/line.h"
|
||||
#include "common/types.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/bloodborne_re.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/debugger.h"
|
||||
#include "core/bbport_bbsc_kit.h" // Teefpyre (SrShadowy fork)
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "core/libraries/kernel/memory.h"
|
||||
#include "core/libraries/kernel/threads/pthread.h"
|
||||
@@ -45,9 +47,8 @@
|
||||
|
||||
#include "core/libraries/network/http.h"
|
||||
#include "core/libraries/network/http2.h"
|
||||
#include "core/libraries/network/net.h"
|
||||
#include "core/libraries/net/net.h"
|
||||
#include "core/libraries/network/netctl.h"
|
||||
#include "core/libraries/network/ssl.h"
|
||||
#include "core/libraries/network/ssl2.h"
|
||||
#include "core/libraries/np/np_auth.h"
|
||||
#include "core/libraries/np/np_common.h"
|
||||
@@ -57,17 +58,45 @@
|
||||
#include "core/libraries/np/np_party.h"
|
||||
#include "core/libraries/np/np_score/np_score.h"
|
||||
#include "core/libraries/np/np_signaling/np_signaling.h"
|
||||
#include "core/libraries/np/np_tus.h"
|
||||
#include "core/libraries/np/np_tus/np_tus.h"
|
||||
#include "core/libraries/np/np_utility/np_utility.h"
|
||||
#include "core/libraries/np/np_web_api/np_web_api.h"
|
||||
#include "core/libraries/np/np_web_api2/np_web_api2.h"
|
||||
#include <algorithm>
|
||||
#include <sys/mman.h>
|
||||
#include <SDL3/SDL_events.h>
|
||||
|
||||
#include "bbnet.h"
|
||||
#include "bbgpu.h"
|
||||
#include "common/memory_patcher.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/bbsc_cinematic.h"
|
||||
#include "core/bbsc_persistence.h"
|
||||
#include "core/bbsc_relay_client.h"
|
||||
#include "core/bloodborne_re.h"
|
||||
#include "core/cpu_patches.h"
|
||||
#include "core/file_format/psf.h"
|
||||
#include "games/game_profile.h"
|
||||
#include "thd/thd.h"
|
||||
|
||||
#include "bbnet20/bbnet.h"
|
||||
|
||||
namespace {
|
||||
/// The runtime's side (bbnet_init).
|
||||
BbNetHost g_host{};
|
||||
} // namespace
|
||||
|
||||
// The Hunter's Dream backend reads game structures (the player's position for the live map):
|
||||
// only from mapped guest memory (bbport).
|
||||
namespace BbEngine {
|
||||
bool host_read_safe(const void* p, void* out, std::size_t n) {
|
||||
if (!g_host.memory_is_mapped(reinterpret_cast<uintptr_t>(p), n)) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(out, p, n);
|
||||
return true;
|
||||
}
|
||||
} // namespace BbEngine
|
||||
|
||||
// ---- Kernel ---------------------------------------------------------------------------------
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
@@ -286,10 +315,13 @@ int posix_pthread_create_name_np(PthreadT* thread, const PthreadAttrT* attr,
|
||||
int posix_pthread_join(PthreadT thread, void** ret) {
|
||||
return g_host.thread_join(thread, ret);
|
||||
}
|
||||
void posix_pthread_exit(void* status) {
|
||||
g_host.thread_exit(status);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
||||
// ---- User and system service, debugger, Bloodborne helpers -------------------------------------
|
||||
// ---- User and system service, debugger ----------------------------------------------------------
|
||||
|
||||
namespace Libraries::UserService {
|
||||
s32 PS4_SYSV_ABI sceUserServiceGetInitialUser(int* user_id) {
|
||||
@@ -318,38 +350,22 @@ s32 GetCurrentPid() {
|
||||
}
|
||||
} // namespace Core::Debugger
|
||||
|
||||
namespace Core::Bloodborne {
|
||||
namespace {
|
||||
std::mutex placement_mutex;
|
||||
std::optional<std::string> placement;
|
||||
} // namespace
|
||||
std::optional<std::string> GetSeamlessHostPlacementHeader() {
|
||||
std::scoped_lock lock{placement_mutex};
|
||||
return placement;
|
||||
}
|
||||
bool SetSeamlessHostPlacementHeader(std::string_view value) {
|
||||
if (value.empty() || value.size() > 4096) {
|
||||
return false;
|
||||
}
|
||||
std::scoped_lock lock{placement_mutex};
|
||||
placement = std::string{value};
|
||||
return true;
|
||||
}
|
||||
void ClearSeamlessHostPlacementHeader() {
|
||||
std::scoped_lock lock{placement_mutex};
|
||||
placement.reset();
|
||||
}
|
||||
void TraceMatching2LeaveRoom(std::uintptr_t, std::uint64_t) {}
|
||||
} // namespace Core::Bloodborne
|
||||
|
||||
// ---- Notifications (log only for now) ------------------------------------------------------------
|
||||
// ---- Notifications ------------------------------------------------------------------------------
|
||||
// The console and the log get every one; an Info notice (the login failed, the server is down or
|
||||
// refuses this client) also shows for a while on the port's overlay line, over the game.
|
||||
|
||||
namespace ImGui::ShadNetNotify {
|
||||
namespace {
|
||||
std::mutex history_mutex;
|
||||
std::vector<HistoryEntry> history;
|
||||
std::size_t unread{};
|
||||
// Bumped by every user of the overlay line: a notice clears it only while it is still its own.
|
||||
std::atomic<u64> overlay_line_generation{0};
|
||||
constexpr auto NoticeShown = std::chrono::seconds(15);
|
||||
} // namespace
|
||||
void OverlayLineTaken() {
|
||||
++overlay_line_generation;
|
||||
}
|
||||
KindDisplay DisplayOf(Kind kind) {
|
||||
switch (kind) {
|
||||
case Kind::FriendRequest:
|
||||
@@ -360,12 +376,26 @@ KindDisplay DisplayOf(Kind kind) {
|
||||
return {"Friend", {0.9f, 0.4f, 0.4f, 1.0f}};
|
||||
case Kind::Online:
|
||||
return {"Online", {0.4f, 0.8f, 0.9f, 1.0f}};
|
||||
case Kind::Warning:
|
||||
return {"Warning", {0.95f, 0.6f, 0.2f, 1.0f}};
|
||||
default:
|
||||
return {"Info", {0.8f, 0.8f, 0.8f, 1.0f}};
|
||||
}
|
||||
}
|
||||
void Push(Kind kind, std::string text) {
|
||||
std::printf("Co-op: %s: %s\n", DisplayOf(kind).tag, text.c_str());
|
||||
// One write of the whole line: the shadNet threads log at the same moment.
|
||||
Common::Log::WriteLine(stdout,
|
||||
std::string("Co-op: ") + DisplayOf(kind).tag + ": " + text + "\n");
|
||||
if (kind == Kind::Info) {
|
||||
const u64 mine = ++overlay_line_generation;
|
||||
bbgpu_overlay_banner(text.c_str());
|
||||
std::thread([mine] {
|
||||
std::this_thread::sleep_for(NoticeShown);
|
||||
if (overlay_line_generation.load() == mine) {
|
||||
bbgpu_overlay_banner(nullptr);
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
std::scoped_lock lock{history_mutex};
|
||||
history.push_back({kind, std::move(text), {}});
|
||||
++unread;
|
||||
@@ -401,10 +431,10 @@ void Register() {}
|
||||
void Unregister() {}
|
||||
} // namespace ImGui::InvitationPrompt
|
||||
|
||||
// ---- Game identity -------------------------------------------------------------------------------
|
||||
// ---- Game identity -----------------------------------------------------------------------------
|
||||
|
||||
// ElfInfo lets shadPS4's Core::Emulator fill it in; this port has no emulator class, so the
|
||||
// same name is used here for that one job.
|
||||
// ElfInfo is filled in by shadPS4's Core::Emulator; this port has no emulator class, so the
|
||||
// same name is used here for that one job (bbport).
|
||||
namespace Core {
|
||||
class Emulator {
|
||||
public:
|
||||
@@ -445,20 +475,10 @@ private:
|
||||
};
|
||||
} // namespace Core
|
||||
|
||||
namespace {
|
||||
void Configure(const char* app0, const char* serial, const char* title, const char* app_ver) {
|
||||
Core::Emulator::SetGame(app0, serial, title, app_ver);
|
||||
const auto& ids = Common::ElfInfo::Instance().GetNpCommIds();
|
||||
std::printf("Co-op: %s, NP communication id %s%s\n", serial ? serial : "?",
|
||||
ids.empty() ? "missing" : ids.front().c_str(),
|
||||
EmulatorSettings.IsShadNetEnabled() ? "; online play on" : "");
|
||||
}
|
||||
} // namespace
|
||||
// ---- The module's C interface (bbnet.h) -------------------------------------------------------
|
||||
|
||||
// ---- Registration and the C interface ------------------------------------------------------------
|
||||
|
||||
// The module's own table (the port's libbbgpu keeps another one: this library's symbols are its
|
||||
// own, -Bsymbolic).
|
||||
// The module's own symbol table (libbbgpu keeps another one: this library's symbols are its own,
|
||||
// -Bsymbolic).
|
||||
namespace {
|
||||
struct Symbol {
|
||||
std::string nid;
|
||||
@@ -468,12 +488,19 @@ std::vector<Symbol> g_symbols;
|
||||
} // namespace
|
||||
|
||||
namespace Core::Loader {
|
||||
void SymbolsResolver::AddSymbol(const char* nid, const char*, const char*, SymbolType, u64 address) {
|
||||
void SymbolsResolver::AddSymbol(const char* nid, const char*, const char*, SymbolType,
|
||||
u64 address) {
|
||||
g_symbols.push_back({nid, address});
|
||||
}
|
||||
} // namespace Core::Loader
|
||||
|
||||
extern "C" __attribute__((visibility("default"))) uintptr_t bbnet_resolve(const char* scoped_nid) {
|
||||
#ifdef _WIN32
|
||||
#define BBNET_EXPORT extern "C" __declspec(dllexport)
|
||||
#else
|
||||
#define BBNET_EXPORT extern "C" __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
BBNET_EXPORT uintptr_t bbnet_resolve(const char* scoped_nid) {
|
||||
const char* hash = std::strchr(scoped_nid, '#');
|
||||
const size_t length = hash ? size_t(hash - scoped_nid) : std::strlen(scoped_nid);
|
||||
for (const auto& symbol : g_symbols) {
|
||||
@@ -484,32 +511,181 @@ extern "C" __attribute__((visibility("default"))) uintptr_t bbnet_resolve(const
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" __attribute__((visibility("default"))) int bbnet_init(const BbNetHost* host) {
|
||||
if (!host || host->abi != BBNET_ABI_VERSION || !host->thread_spawn || !host->thread_join) {
|
||||
BBNET_EXPORT void bbnet_image_mapped(uint8_t* image, uint64_t size) {
|
||||
// The Hunter's Dream backend reads the game's code before the patches change it.
|
||||
BbThd::SetImage(image, size);
|
||||
}
|
||||
|
||||
BBNET_EXPORT void bbnet_image_ready(uint64_t base, uint64_t size) {
|
||||
MemoryPatcher::g_eboot_address = base;
|
||||
MemoryPatcher::g_eboot_image_size = size;
|
||||
// BBSC (Bloodborne seamless co-op): BB_BBSC=1 (the launcher's
|
||||
// co-op switch). Its game code checks for CUSA03173 01.09; every retail Bloodborne runs the
|
||||
// same 1.09 executable (gpu/games/bloodborne.cpp), and each of its sites is byte-checked.
|
||||
const char* bbsc = std::getenv("BB_BBSC");
|
||||
if (!bbsc || bbsc[0] != '1') {
|
||||
return;
|
||||
}
|
||||
const Game::Profile* profile = Game::Active();
|
||||
if (!profile || std::string_view{profile->name} != "Bloodborne") {
|
||||
std::printf("BBSC: not Bloodborne: seamless co-op off\n");
|
||||
return;
|
||||
}
|
||||
MemoryPatcher::g_game_serial = "CUSA03173";
|
||||
Common::Singleton<PSF>::Instance()->app_ver = "01.09"; // [extra: Teefpyre]
|
||||
extra_bbsc_setup_save_dir(); // [extra: Teefpyre]
|
||||
// BBSC writes its byte patches straight into the game's code, and turns some on and off
|
||||
// while the game runs (session teardown, the host's lamp): its code pages stay writable
|
||||
// (shadPS4 maps them so; the port maps them read and execute).
|
||||
unsigned writable = 0;
|
||||
#ifdef _WIN32
|
||||
// Windows: the image's executable regions from VirtualQuery, made writable through the
|
||||
// runtime's mprotect (it keeps its own record of the guest's protections).
|
||||
for (unsigned long long at = base; at < base + size;) {
|
||||
MEMORY_BASIC_INFORMATION info{};
|
||||
if (!VirtualQuery(reinterpret_cast<void*>(at), &info, sizeof(info)) || !info.RegionSize) {
|
||||
break;
|
||||
}
|
||||
const unsigned long long start = std::max<unsigned long long>(
|
||||
reinterpret_cast<unsigned long long>(info.BaseAddress), base);
|
||||
const unsigned long long end = std::min<unsigned long long>(
|
||||
reinterpret_cast<unsigned long long>(info.BaseAddress) + info.RegionSize, base + size);
|
||||
constexpr DWORD Executable =
|
||||
PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY;
|
||||
if (info.State == MEM_COMMIT && (info.Protect & Executable) && start < end) {
|
||||
writable += mprotect(reinterpret_cast<void*>(start), end - start,
|
||||
PROT_READ | PROT_WRITE | PROT_EXEC) == 0;
|
||||
std::printf("BBSC: code %#llx-%#llx writable\n", start, end);
|
||||
}
|
||||
at = reinterpret_cast<unsigned long long>(info.BaseAddress) + info.RegionSize;
|
||||
}
|
||||
#endif
|
||||
if (FILE* maps = std::fopen("/proc/self/maps", "r")) {
|
||||
char line[512];
|
||||
while (std::fgets(line, sizeof(line), maps)) {
|
||||
unsigned long long start = 0, end = 0;
|
||||
char perms[5] = {};
|
||||
if (std::sscanf(line, "%llx-%llx %4s", &start, &end, perms) != 3 || perms[2] != 'x') {
|
||||
continue;
|
||||
}
|
||||
start = std::max<unsigned long long>(start, base);
|
||||
end = std::min<unsigned long long>(end, base + size);
|
||||
if (start < end) {
|
||||
writable += mprotect(reinterpret_cast<void*>(start), end - start,
|
||||
PROT_READ | PROT_WRITE | PROT_EXEC) == 0;
|
||||
std::printf("BBSC: code %#llx-%#llx writable\n", start, end);
|
||||
}
|
||||
}
|
||||
std::fclose(maps);
|
||||
}
|
||||
std::printf("BBSC: seamless co-op hooks for the image at %#llx (%u code mappings writable)\n",
|
||||
static_cast<unsigned long long>(base), writable);
|
||||
if (const char* probe = std::getenv("BB_BBSC_PROBE")) {
|
||||
const unsigned long long at = std::strtoull(probe, nullptr, 0);
|
||||
if (FILE* maps = std::fopen("/proc/self/maps", "r")) {
|
||||
char line[512];
|
||||
while (std::fgets(line, sizeof(line), maps)) {
|
||||
unsigned long long a = 0, b = 0;
|
||||
if (std::sscanf(line, "%llx-%llx", &a, &b) == 2 && at >= a && at < b) {
|
||||
std::printf("BBSC probe: %s", line);
|
||||
}
|
||||
}
|
||||
std::fclose(maps);
|
||||
}
|
||||
std::fflush(stdout);
|
||||
std::printf("BBSC probe: byte %#x\n", unsigned(*reinterpret_cast<volatile u8*>(at)));
|
||||
}
|
||||
// The cinematic camera (F1, keyboard and mouse): BBSC sees the window's events first.
|
||||
if (Core::Bbsc::SwitchEnabled("SHADPS4_BBSC_CINEMATIC")) {
|
||||
bbgpu_add_event_filter([](const void* event, void* window) -> int {
|
||||
return Core::Bbsc::CinematicHandleEvent(*static_cast<const SDL_Event*>(event),
|
||||
static_cast<SDL_Window*>(window));
|
||||
});
|
||||
}
|
||||
Core::Bloodborne::InstallSeamlessCoopPatches();
|
||||
Core::Bbsc::InstallPersistentSessionPatches();
|
||||
Core::Bloodborne::InstallReverseEngineeringTrace();
|
||||
Core::Bbsc::StartRelayClient(MemoryPatcher::g_game_serial);
|
||||
}
|
||||
|
||||
namespace Libraries::Np::NpManager {
|
||||
void InitializeRuntime();
|
||||
}
|
||||
|
||||
BBNET_EXPORT void bbnet_start(void) {
|
||||
// The online session starts with the game (not at registration: the account and the
|
||||
// game's SDK version are known only now).
|
||||
if (BbThd::Enabled()) {
|
||||
BbThd::Initialize();
|
||||
return;
|
||||
}
|
||||
Libraries::Np::NpManager::InitializeRuntime();
|
||||
}
|
||||
|
||||
BBNET_EXPORT int bbnet_init(const BbNetHost* host) {
|
||||
if (!host || host->abi != BBNET_ABI_VERSION || !host->thread_spawn || !host->thread_join ||
|
||||
!host->thread_exit || !host->memory_is_mapped) {
|
||||
std::printf("Online: the runtime and the online module are from different versions\n");
|
||||
return -1;
|
||||
}
|
||||
g_host = *host;
|
||||
Configure(host->app0, host->serial, host->title, host->app_ver);
|
||||
Core::Emulator::SetGame(host->app0, host->serial, host->title, host->app_ver);
|
||||
// What BBSC reads of the game (param.sfo, the serial: shadPS4's PSF and memory patcher).
|
||||
{
|
||||
auto* psf = Common::Singleton<PSF>::Instance();
|
||||
psf->app_ver = host->app_ver ? host->app_ver : "";
|
||||
psf->title_id = host->serial ? host->serial : "";
|
||||
psf->title = host->title ? host->title : "";
|
||||
MemoryPatcher::g_game_serial = host->serial ? host->serial : "";
|
||||
}
|
||||
// BBSC patches the game's code, some of it while the game runs: from now on the port's own
|
||||
// code patches leave it writable (bbnet_image_ready).
|
||||
if (const char* bbsc = std::getenv("BB_BBSC"); bbsc && bbsc[0] == '1') {
|
||||
Core::KeepGuestCodeWritable();
|
||||
}
|
||||
const auto& ids = Common::ElfInfo::Instance().GetNpCommIds();
|
||||
std::string line = std::string("Co-op: ") + (host->serial ? host->serial : "?") +
|
||||
", NP communication id " +
|
||||
(ids.empty() ? std::string("missing") : ids.front());
|
||||
if (EmulatorSettings.IsConnectedToNetwork()) {
|
||||
line += "; online play on"; // a launcher can look for this
|
||||
}
|
||||
Common::Log::WriteLine(stdout, line + "\n"); // one write (line.h)
|
||||
Core::Loader::SymbolsResolver resolver;
|
||||
Core::Loader::SymbolsResolver* sym = &resolver;
|
||||
Libraries::Net::RegisterLib(sym);
|
||||
Libraries::NetCtl::RegisterLib(sym);
|
||||
Libraries::Http::RegisterLib(sym);
|
||||
Libraries::Http2::RegisterLib(sym);
|
||||
Libraries::Ssl::RegisterLib(sym);
|
||||
Libraries::Ssl2::RegisterLib(sym);
|
||||
Libraries::Np::NpAuth::RegisterLib(sym);
|
||||
Libraries::Np::NpCommon::RegisterLib(sym);
|
||||
Libraries::Np::NpManager::RegisterLib(sym);
|
||||
Libraries::Np::NpMatching2::RegisterLib(sym);
|
||||
Libraries::Np::NpPartner::RegisterLib(sym);
|
||||
Libraries::Np::NpParty::RegisterLib(sym);
|
||||
Libraries::Np::NpScore::RegisterLib(sym);
|
||||
Libraries::Np::NpSignaling::RegisterLib(sym);
|
||||
Libraries::Np::NpTus::RegisterLib(sym);
|
||||
Libraries::Np::NpWebApi::RegisterLib(sym);
|
||||
Libraries::Np::NpWebApi2::RegisterLib(sym);
|
||||
std::printf("Online: %zu network and PSN functions\n", g_symbols.size());
|
||||
if (BbThd::Enabled()) {
|
||||
// The Hunter's Dream: its NP and sceNet imports; the game's HTTP stays sceHttp's.
|
||||
BbThd::Register(sym);
|
||||
Libraries::Http::RegisterLib(sym);
|
||||
Libraries::Http2::RegisterLib(sym);
|
||||
Libraries::Ssl2::RegisterLib(sym);
|
||||
} else {
|
||||
Libraries::Net::RegisterLib(sym);
|
||||
Libraries::NetCtl::RegisterLib(sym);
|
||||
Libraries::Http::RegisterLib(sym);
|
||||
Libraries::Http2::RegisterLib(sym);
|
||||
// libSceSsl: not registered, as in shadPS4 (its loading is behind a title workaround).
|
||||
Libraries::Ssl2::RegisterLib(sym);
|
||||
Libraries::Np::NpAuth::RegisterLib(sym);
|
||||
Libraries::Np::NpCommon::RegisterLib(sym);
|
||||
Libraries::Np::NpManager::RegisterLib(sym);
|
||||
Libraries::Np::NpMatching2::RegisterLib(sym);
|
||||
Libraries::Np::NpPartner::RegisterLib(sym);
|
||||
Libraries::Np::NpParty::RegisterLib(sym);
|
||||
Libraries::Np::NpScore::RegisterLib(sym);
|
||||
Libraries::Np::NpSignaling::RegisterLib(sym);
|
||||
Libraries::Np::NpTus::RegisterLib(sym);
|
||||
Libraries::Np::NpUtility::RegisterLib(sym);
|
||||
Libraries::Np::NpWebApi::RegisterLib(sym);
|
||||
Libraries::Np::NpWebApi2::RegisterLib(sym);
|
||||
}
|
||||
std::printf("Online: %zu network and PSN functions (%s)\n", g_symbols.size(),
|
||||
BbThd::Enabled() ? "The Hunter's Dream" : "shadNet");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The Hunter's Dream backend starts its dispatcher as a guest thread (thd_session.cpp).
|
||||
extern "C" int32_t runtime_thread_spawn(void** out, void* PS4_SYSV_ABI (*entry)(void*),
|
||||
void* argument, uint64_t stack, const char* name) {
|
||||
return g_host.thread_spawn(out, entry, argument, stack, name);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// bbport online module: what BBSC (shadps4/core/bloodborne_re.cpp, GPL-2.0+)
|
||||
// needs from shadPS4's UI. Its loading screen is a line in the port's overlay; its animated title
|
||||
// card (off by default in BBSC too) is not ported.
|
||||
#include <cstdio>
|
||||
#include <string_view>
|
||||
|
||||
#include "bbgpu.h"
|
||||
|
||||
#include "imgui/bbsc_intro_layer.h"
|
||||
#include "imgui/bbsc_loading_layer.h"
|
||||
#include "imgui/shadnet_notifications_layer.h"
|
||||
|
||||
namespace Core::Bbsc {
|
||||
|
||||
void ShowLoadingScreen(LoadingScreenKind kind) {
|
||||
// The port's overlay line (BBSC draws its own loading screen in shadPS4's ImGui layer).
|
||||
ImGui::ShadNetNotify::OverlayLineTaken();
|
||||
bbgpu_overlay_banner(kind == LoadingScreenKind::Summon ? "Joining the host's world..."
|
||||
: "Following the host...");
|
||||
}
|
||||
|
||||
void HideLoadingScreen() {
|
||||
ImGui::ShadNetNotify::OverlayLineTaken();
|
||||
bbgpu_overlay_banner(nullptr);
|
||||
}
|
||||
|
||||
void StartIntro(std::string_view) {}
|
||||
|
||||
} // namespace Core::Bbsc
|
||||
@@ -0,0 +1,241 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "bbport_bbsc_kit.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
extern "C" int runtime_memory_region(std::uintptr_t address, std::uintptr_t* start,
|
||||
std::uintptr_t* end, int* mapped);
|
||||
extern "C" void runtime_memory_read_backing(std::uintptr_t address, void* data,
|
||||
std::uint64_t size);
|
||||
|
||||
extern "C" const char* extra_bbsc_save_root(void) {
|
||||
const char* dir = std::getenv("BB_GPU_USER_DIR");
|
||||
if (dir == nullptr || dir[0] == '\0') {
|
||||
dir = std::getenv("BB_USER_DIR");
|
||||
}
|
||||
return dir != nullptr && dir[0] != '\0' ? dir : nullptr;
|
||||
}
|
||||
|
||||
extern "C" void extra_bbsc_setup_save_dir(void) {
|
||||
if (std::getenv("BBSC_SAVE_DIR")) {
|
||||
return;
|
||||
}
|
||||
const char* user = extra_bbsc_save_root();
|
||||
const std::filesystem::path save_root =
|
||||
std::filesystem::path(user && user[0] ? user : "user") / "savedata";
|
||||
// bbport (Linux fork): the most recently written save folder, not the first one listed (a
|
||||
// second region's copy, an old one); on a first start, with none yet, the one the game will
|
||||
// write (user 1, src/runtime_services.c; BB_SAVEDATA_TITLE from src/runtime_savedata.c) -
|
||||
// without a folder the kit was never given.
|
||||
std::filesystem::path newest;
|
||||
std::filesystem::file_time_type newest_time{};
|
||||
std::error_code error;
|
||||
for (const auto& id : std::filesystem::directory_iterator(save_root, error)) {
|
||||
std::error_code title_ec;
|
||||
for (const auto& title : std::filesystem::directory_iterator(id.path(), title_ec)) {
|
||||
const auto folder = title.path() / "SPRJ0005";
|
||||
std::error_code ec;
|
||||
const auto written = std::filesystem::last_write_time(folder, ec);
|
||||
if (!ec && std::filesystem::is_directory(folder, ec) &&
|
||||
(newest.empty() || written > newest_time)) {
|
||||
newest = folder;
|
||||
newest_time = written;
|
||||
}
|
||||
}
|
||||
}
|
||||
const char* title = std::getenv("BB_SAVEDATA_TITLE");
|
||||
if (newest.empty() && title != nullptr && title[0] != '\0' &&
|
||||
std::strcmp(title, "UNKNOWN") != 0) {
|
||||
newest = save_root / "1" / title / "SPRJ0005";
|
||||
}
|
||||
if (!newest.empty()) {
|
||||
const std::string text = newest.string();
|
||||
setenv("BBSC_SAVE_DIR", text.c_str(), 0);
|
||||
std::printf("BBSC: save folder %s\n", text.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void extra_bbsc_bell_scan_forever(std::atomic<bool>* ready, void (*prepare)(void)) {
|
||||
if (ready == nullptr || prepare == nullptr) {
|
||||
return;
|
||||
}
|
||||
int delay_s = 2;
|
||||
while (!ready->load(std::memory_order_acquire)) {
|
||||
prepare();
|
||||
if (ready->load(std::memory_order_acquire)) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(delay_s));
|
||||
if (delay_s < 4) {
|
||||
++delay_s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" bool extra_bbsc_check_mapping(const char* param, const char* line) {
|
||||
if (param == nullptr || line == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (param[1] != 'w' || std::strstr(line, ".so") != nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" bool extra_safe_read(void* dst, const void* src, size_t size) {
|
||||
if (dst == nullptr || src == nullptr || size == 0) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(dst, src, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" void extra_bbsc_find_in_memory(
|
||||
const std::uint8_t* needle_ptr, std::size_t needle_len,
|
||||
std::uint64_t lo, std::uint64_t hi, std::size_t max_hits,
|
||||
std::vector<std::uint64_t>* hits) {
|
||||
if (!hits || !needle_ptr || needle_len == 0 || lo >= hi) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr std::size_t Piece = 1u << 20;
|
||||
std::vector<std::uint8_t> buffer(Piece + needle_len);
|
||||
constexpr std::uint64_t GuestMin = 0x1000000000ULL;
|
||||
|
||||
auto match_chunk = [&](std::uint64_t base, std::size_t len) {
|
||||
const std::uint8_t* data = buffer.data();
|
||||
const std::uint8_t* last = data + len - needle_len;
|
||||
for (const std::uint8_t* p = data; p <= last && hits->size() < max_hits; ++p) {
|
||||
p = static_cast<const std::uint8_t*>(
|
||||
std::memchr(p, needle_ptr[0], std::size_t(last - p) + 1));
|
||||
if (!p) {
|
||||
break;
|
||||
}
|
||||
if (std::size_t(p - data) < Piece &&
|
||||
std::memcmp(p, needle_ptr, needle_len) == 0) {
|
||||
hits->push_back(base + std::uint64_t(p - data));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 1. Low mappings below GuestMin (e.g. eboot image): readable mappings from /proc/self/maps
|
||||
if (lo < GuestMin) {
|
||||
const std::uint64_t low_hi = std::min<std::uint64_t>(hi, GuestMin);
|
||||
if (FILE* maps = std::fopen("/proc/self/maps", "r")) {
|
||||
char line[512];
|
||||
while (hits->size() < max_hits && std::fgets(line, sizeof(line), maps)) {
|
||||
unsigned long long start = 0, end = 0;
|
||||
char perms[5] = {};
|
||||
if (std::sscanf(line, "%llx-%llx %4s", &start, &end, perms) != 3 || perms[0] != 'r') {
|
||||
continue;
|
||||
}
|
||||
if (!extra_bbsc_check_mapping(perms, line)) {
|
||||
continue;
|
||||
}
|
||||
start = std::max<unsigned long long>(start, lo);
|
||||
end = std::min<unsigned long long>(end, low_hi);
|
||||
for (std::uint64_t at = start; at < end && hits->size() < max_hits; at += Piece) {
|
||||
const std::size_t want =
|
||||
std::size_t(std::min<std::uint64_t>(end - at, Piece + needle_len - 1));
|
||||
iovec local{buffer.data(), want};
|
||||
iovec remote{reinterpret_cast<void*>(at), want};
|
||||
ssize_t got = process_vm_readv(getpid(), &local, 1, &remote, 1, 0);
|
||||
if (got < 0 && extra_safe_read(buffer.data(), reinterpret_cast<const void*>(at), want)) {
|
||||
got = static_cast<ssize_t>(want);
|
||||
}
|
||||
if (got < ssize_t(needle_len)) {
|
||||
continue;
|
||||
}
|
||||
match_chunk(at, std::size_t(got));
|
||||
}
|
||||
}
|
||||
std::fclose(maps);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Guest mappings at or above GuestMin: query runtime_memory
|
||||
std::uint64_t at = std::max<std::uint64_t>(lo, GuestMin);
|
||||
while (at < hi && hits->size() < max_hits) {
|
||||
std::uintptr_t r_start = 0, r_end = 0;
|
||||
int mapped = 0;
|
||||
if (!runtime_memory_region(at, &r_start, &r_end, &mapped) || r_end <= at) {
|
||||
break;
|
||||
}
|
||||
if (!mapped) {
|
||||
at = r_end;
|
||||
continue;
|
||||
}
|
||||
const std::uint64_t start = std::max<std::uint64_t>(r_start, at);
|
||||
const std::uint64_t end = std::min<std::uint64_t>(r_end, hi);
|
||||
for (std::uint64_t chunk = start; chunk < end && hits->size() < max_hits; chunk += Piece) {
|
||||
const std::size_t want =
|
||||
std::size_t(std::min<std::uint64_t>(end - chunk, Piece + needle_len - 1));
|
||||
if (want < needle_len) {
|
||||
break;
|
||||
}
|
||||
runtime_memory_read_backing(chunk, buffer.data(), want);
|
||||
match_chunk(chunk, want);
|
||||
}
|
||||
at = r_end;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" std::uint64_t extra_bbsc_get_solo_param_repo(
|
||||
std::uint64_t image_base, const BbscParamMemory* mem) {
|
||||
if (image_base == 0 || mem == nullptr || mem->read_u64 == nullptr || mem->readable == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
constexpr std::uint64_t kSoloRepoOffset = 0x05540340;
|
||||
constexpr std::uint64_t kBasePtrOffset = 0x05537F58;
|
||||
constexpr std::size_t kRepoMinSize = 64 * 0x48 + 0x38;
|
||||
|
||||
std::uint64_t repo = mem->read_u64(image_base + kSoloRepoOffset, 0);
|
||||
if (!mem->readable(repo, kRepoMinSize)) {
|
||||
const std::uint64_t base_ptr = mem->read_u64(image_base + kBasePtrOffset, 0);
|
||||
if (mem->readable(base_ptr, 0x70)) {
|
||||
repo = mem->read_u64(base_ptr, 0x68);
|
||||
}
|
||||
}
|
||||
return mem->readable(repo, kRepoMinSize) ? repo : 0;
|
||||
}
|
||||
|
||||
extern "C" void extra_bbsc_find_param_tables_direct(
|
||||
std::uint64_t repo, const char* type, std::size_t type_len, std::uint16_t min_rows, std::uint32_t first_id,
|
||||
const BbscParamMemory* mem, std::vector<std::uint64_t>* out_tables) {
|
||||
if (repo == 0 || type == nullptr || mem == nullptr || out_tables == nullptr ||
|
||||
mem->readable == nullptr || mem->read_s32 == nullptr || mem->read_u16 == nullptr ||
|
||||
mem->read_u32 == nullptr || mem->read_u64 == nullptr || mem->match_name == nullptr) {
|
||||
return;
|
||||
}
|
||||
constexpr std::size_t kRepoMinSize = 64 * 0x48 + 0x38;
|
||||
if (!mem->readable(repo, kRepoMinSize)) {
|
||||
return;
|
||||
}
|
||||
for (std::size_t idx = 0; idx < 64; ++idx) {
|
||||
if (mem->read_s32(repo, idx * 0x48 + 0x28) <= 0) {
|
||||
continue;
|
||||
}
|
||||
const std::uint64_t resource = mem->read_u64(repo, idx * 0x48 + 0x30);
|
||||
const std::uint64_t holder = mem->readable(resource, 0x78) ? mem->read_u64(resource, 0x70) : 0;
|
||||
const std::uint64_t table = mem->readable(holder, 0x78) ? mem->read_u64(holder, 0x70) : 0;
|
||||
if (mem->readable(table, 0x60) && mem->match_name(table, type, type_len) &&
|
||||
mem->read_u16(table, 0x0A) > min_rows &&
|
||||
(first_id == 0 || mem->read_u32(table, 0x40) == first_id) &&
|
||||
std::ranges::find(*out_tables, table) == out_tables->end()) {
|
||||
out_tables->push_back(table);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
// Returns $BB_GPU_USER_DIR or $BB_USER_DIR when set and non-empty, else nullptr.
|
||||
extern "C" const char* extra_bbsc_save_root(void);
|
||||
|
||||
// BBSC save directory automatic resolution across title IDs (CUSA00207, CUSA03173, etc.)
|
||||
// [Teefpyre patch: dynamic save folder detection]
|
||||
extern "C" void extra_bbsc_setup_save_dir(void);
|
||||
|
||||
// Hunter's Kit fix: the bell's param rows (goods + SpEffect) are scannable once the game
|
||||
// data is mapped, but upstream's cap of 36 attempts (3 min) could expire if the user stayed
|
||||
// at the title screen. This worker calls prepare every 2 s until ready turns true.
|
||||
extern "C" void extra_bbsc_bell_scan_forever(std::atomic<bool>* ready, void (*prepare)(void));
|
||||
|
||||
// Safely reads memory even on DMABUF or protected pages, preventing SIGSEGV/SIGBUS.
|
||||
extern "C" bool extra_safe_read(void* dst, const void* src, size_t size);
|
||||
|
||||
// Verifies if a memory mapping should be scanned (skips read-only and host .so libraries).
|
||||
extern "C" bool extra_bbsc_check_mapping(const char* param, const char* line);
|
||||
|
||||
// Memory scan for BBSC param tables and symbols [Teefpyre patch / Linux BBSC Fix]:
|
||||
// Handles low mappings (< GuestMin) via /proc/self/maps and guest mappings (>= GuestMin)
|
||||
// via runtime_memory_read_backing.
|
||||
extern "C" void extra_bbsc_find_in_memory(
|
||||
const std::uint8_t* needle_ptr, std::size_t needle_len,
|
||||
std::uint64_t lo, std::uint64_t hi, std::size_t max_hits,
|
||||
std::vector<std::uint64_t>* hits);
|
||||
|
||||
// Memory access callbacks used by BBSC parameter operations
|
||||
struct BbscParamMemory {
|
||||
bool (*readable)(std::uint64_t addr, std::size_t size);
|
||||
std::uint64_t (*read_u64)(std::uint64_t addr, std::size_t offset);
|
||||
std::int32_t (*read_s32)(std::uint64_t addr, std::size_t offset);
|
||||
std::uint16_t (*read_u16)(std::uint64_t addr, std::size_t offset);
|
||||
std::uint32_t (*read_u32)(std::uint64_t addr, std::size_t offset);
|
||||
bool (*match_name)(std::uint64_t table_addr, const char* name, std::size_t name_len);
|
||||
};
|
||||
|
||||
// Returns the SoloParamRepository pointer if initialized and valid, else 0.
|
||||
// Checks image_base + 0x05540340 and [[image_base + 0x05537F58] + 0x68].
|
||||
// [Teefpyre patch: direct SoloParamRepository lookup without memory scanning]
|
||||
extern "C" std::uint64_t extra_bbsc_get_solo_param_repo(
|
||||
std::uint64_t image_base, const BbscParamMemory* mem);
|
||||
|
||||
// Finds param tables directly from SoloParamRepository without memory scanning.
|
||||
extern "C" void extra_bbsc_find_param_tables_direct(
|
||||
std::uint64_t repo, const char* type, std::size_t type_len, std::uint16_t min_rows, std::uint32_t first_id,
|
||||
const BbscParamMemory* mem, std::vector<std::uint64_t>* out_tables);
|
||||
@@ -0,0 +1,222 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 CLS_AMG
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Input side of the BBSC cinematic camera. While it is on, the keyboard and mouse fly the camera
|
||||
// and never reach the game (shadPS4 would otherwise map them to the pad and move the hunter too);
|
||||
// the controller is untouched. Keys:
|
||||
// F1 on/off | hold right mouse: look | WASD move | E / Space up, Q / C down
|
||||
// Shift fast, Alt slow | wheel: speed | Z / X zoom, V reset zoom | , . roll, / reset roll
|
||||
// arrow keys: look | [ ] smoothing (glide) less / more
|
||||
// Dead in co-op it watches a living hunter instead (bloodborne_re.cpp BbscWatchCamera): Tab / E /
|
||||
// left click next hunter, Shift+Tab / Q previous, F fly freely and back; the controller's R1 / L1
|
||||
// and right stick reach it only then (CinematicPadRead - the F1 camera leaves the pad to the hunter).
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <mutex>
|
||||
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <SDL3/SDL_mouse.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/bbsc_cinematic.h"
|
||||
#include "core/bbsc_persistence.h"
|
||||
|
||||
namespace Core::Bbsc {
|
||||
namespace {
|
||||
|
||||
std::mutex cine_mutex;
|
||||
CinematicInput cine_state;
|
||||
bool looking = false;
|
||||
bool watching = false; // the game thread says the camera watches a hunter
|
||||
unsigned pad_last = 0; // pad buttons at the last read
|
||||
int pad_switch = 0; // R1 / L1 presses while watching
|
||||
float pad_rx = 0.0f, pad_ry = 0.0f, pad_ly = 0.0f;
|
||||
|
||||
float PadAxis(unsigned char value) {
|
||||
const float v = (static_cast<float>(value) - 128.0f) / 127.0f;
|
||||
constexpr float Dead = 0.15f;
|
||||
return std::abs(v) < Dead ? 0.0f
|
||||
: std::clamp((v - std::copysign(Dead, v)) / (1.0f - Dead), -1.0f, 1.0f);
|
||||
}
|
||||
|
||||
bool CinematicEnabledBySwitch() {
|
||||
static const bool enabled = SwitchEnabled("SHADPS4_BBSC_CINEMATIC");
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void SetKey(SDL_Keycode key, bool down) {
|
||||
switch (key) {
|
||||
case SDLK_W: cine_state.forward = down; break;
|
||||
case SDLK_S: cine_state.back = down; break;
|
||||
case SDLK_A: cine_state.left = down; break;
|
||||
case SDLK_D: cine_state.right = down; break;
|
||||
case SDLK_E:
|
||||
case SDLK_SPACE: cine_state.up = down; break;
|
||||
case SDLK_Q:
|
||||
case SDLK_C: cine_state.down = down; break;
|
||||
case SDLK_LSHIFT:
|
||||
case SDLK_RSHIFT: cine_state.fast = down; break;
|
||||
case SDLK_LALT:
|
||||
case SDLK_RALT: cine_state.slow = down; break;
|
||||
case SDLK_Z: cine_state.zoom_in = down; break;
|
||||
case SDLK_X: cine_state.zoom_out = down; break;
|
||||
case SDLK_V: cine_state.zoom_reset = cine_state.zoom_reset || down; break;
|
||||
case SDLK_COMMA: cine_state.roll_left = down; break;
|
||||
case SDLK_PERIOD: cine_state.roll_right = down; break;
|
||||
case SDLK_SLASH: cine_state.roll_reset = cine_state.roll_reset || down; break;
|
||||
case SDLK_UP: cine_state.look_up = down; break;
|
||||
case SDLK_DOWN: cine_state.look_down = down; break;
|
||||
case SDLK_LEFT: cine_state.look_left = down; break;
|
||||
case SDLK_RIGHT: cine_state.look_right = down; break;
|
||||
case SDLK_LEFTBRACKET:
|
||||
if (down) --cine_state.smooth_steps;
|
||||
break;
|
||||
case SDLK_RIGHTBRACKET:
|
||||
if (down) ++cine_state.smooth_steps;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void ReleaseAll(SDL_Window* window) {
|
||||
const bool enabled = cine_state.enabled;
|
||||
cine_state = CinematicInput{};
|
||||
cine_state.enabled = enabled;
|
||||
if (looking && window != nullptr) {
|
||||
SDL_SetWindowRelativeMouseMode(window, false);
|
||||
}
|
||||
looking = false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool CinematicHandleEvent(const SDL_Event& event, SDL_Window* window) {
|
||||
if (!CinematicEnabledBySwitch()) {
|
||||
return false;
|
||||
}
|
||||
std::scoped_lock lock{cine_mutex};
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_F1) {
|
||||
if (!event.key.repeat) {
|
||||
cine_state.enabled = !cine_state.enabled;
|
||||
ReleaseAll(window);
|
||||
LOG_INFO(Debug, "BBSC cinematic camera: {} (F1)", cine_state.enabled ? "on" : "off");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (event.type == SDL_EVENT_KEY_UP && event.key.key == SDLK_F1) {
|
||||
return true;
|
||||
}
|
||||
if (!cine_state.enabled) {
|
||||
return false;
|
||||
}
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP: {
|
||||
const SDL_Keycode key = event.key.key;
|
||||
if (key >= SDLK_F2 && key <= SDLK_F12) {
|
||||
return false; // the emulator's own hotkeys (fullscreen, fps, pause...) keep working
|
||||
}
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
|
||||
if (key == SDLK_TAB) {
|
||||
cine_state.switch_target += (event.key.mod & SDL_KMOD_SHIFT) != 0 ? -1 : 1;
|
||||
} else if (key == SDLK_E) {
|
||||
++cine_state.switch_target;
|
||||
} else if (key == SDLK_Q) {
|
||||
--cine_state.switch_target;
|
||||
} else if (key == SDLK_F) {
|
||||
cine_state.toggle_free = true;
|
||||
}
|
||||
}
|
||||
SetKey(key, event.type == SDL_EVENT_KEY_DOWN);
|
||||
return true;
|
||||
}
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
if (event.button.button == SDL_BUTTON_LEFT && event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
|
||||
++cine_state.switch_target;
|
||||
}
|
||||
if (event.button.button == SDL_BUTTON_RIGHT) {
|
||||
looking = event.type == SDL_EVENT_MOUSE_BUTTON_DOWN;
|
||||
if (window != nullptr) {
|
||||
SDL_SetWindowRelativeMouseMode(window, looking);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
if (looking) {
|
||||
cine_state.mouse_dx += event.motion.xrel;
|
||||
cine_state.mouse_dy += event.motion.yrel;
|
||||
}
|
||||
return true;
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
cine_state.wheel += event.wheel.y > 0.0f ? 1 : (event.wheel.y < 0.0f ? -1 : 0);
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_FOCUS_LOST:
|
||||
ReleaseAll(window);
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CinematicInput TakeCinematicInput() {
|
||||
std::scoped_lock lock{cine_mutex};
|
||||
CinematicInput out = cine_state;
|
||||
if (watching) {
|
||||
out.pad_look_x = pad_rx;
|
||||
out.pad_look_y = -pad_ry;
|
||||
out.pad_move_y = -pad_ly; // stick up = closer
|
||||
out.switch_target += pad_switch;
|
||||
}
|
||||
pad_switch = 0;
|
||||
cine_state.switch_target = 0;
|
||||
cine_state.toggle_free = false;
|
||||
cine_state.mouse_dx = 0.0f;
|
||||
cine_state.mouse_dy = 0.0f;
|
||||
cine_state.wheel = 0;
|
||||
cine_state.smooth_steps = 0;
|
||||
cine_state.zoom_reset = false;
|
||||
cine_state.roll_reset = false;
|
||||
return out;
|
||||
}
|
||||
|
||||
void CinematicForceOff() {
|
||||
std::scoped_lock lock{cine_mutex};
|
||||
cine_state = CinematicInput{};
|
||||
}
|
||||
|
||||
void CinematicForceOn() {
|
||||
std::scoped_lock lock{cine_mutex};
|
||||
if (!cine_state.enabled) {
|
||||
cine_state = CinematicInput{};
|
||||
cine_state.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CinematicSetWatching(bool on) {
|
||||
std::scoped_lock lock{cine_mutex};
|
||||
if (watching != on) {
|
||||
pad_switch = 0;
|
||||
pad_rx = pad_ry = pad_ly = 0.0f;
|
||||
}
|
||||
watching = on;
|
||||
}
|
||||
|
||||
void CinematicPadRead(unsigned buttons, unsigned char rx, unsigned char ry, unsigned char lx,
|
||||
unsigned char ly) {
|
||||
(void)lx;
|
||||
std::scoped_lock lock{cine_mutex};
|
||||
constexpr unsigned L1 = 0x400, R1 = 0x800;
|
||||
const unsigned pressed = buttons & ~pad_last;
|
||||
pad_last = buttons;
|
||||
if (!cine_state.enabled || !watching) {
|
||||
return;
|
||||
}
|
||||
pad_switch += ((pressed & R1) != 0 ? 1 : 0) - ((pressed & L1) != 0 ? 1 : 0);
|
||||
pad_rx = PadAxis(rx);
|
||||
pad_ry = PadAxis(ry);
|
||||
pad_ly = PadAxis(ly);
|
||||
}
|
||||
|
||||
} // namespace Core::Bbsc
|
||||
@@ -0,0 +1,58 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 CLS_AMG
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
// BBSC cinematic camera (2026-09-28): a free camera for filming, flown with keyboard and mouse while
|
||||
// the controller keeps playing the hunter. The window side (sdl_window.cpp) feeds it input; the
|
||||
// game side (bloodborne_re.cpp, BbscCinematicCameraHook) turns the game's own debug free camera on
|
||||
// and moves it. Switch SHADPS4_BBSC_CINEMATIC (default on). F1 toggles it.
|
||||
|
||||
union SDL_Event;
|
||||
struct SDL_Window;
|
||||
|
||||
namespace Core::Bbsc {
|
||||
|
||||
// Window thread. True when the event belongs to the cinematic camera and must not reach the game.
|
||||
bool CinematicHandleEvent(const SDL_Event& event, SDL_Window* window);
|
||||
|
||||
struct CinematicInput {
|
||||
bool enabled = false;
|
||||
bool forward = false, back = false, left = false, right = false, up = false, down = false;
|
||||
bool fast = false, slow = false;
|
||||
bool zoom_in = false, zoom_out = false, zoom_reset = false;
|
||||
bool roll_left = false, roll_right = false, roll_reset = false;
|
||||
bool look_up = false, look_down = false, look_left = false, look_right = false;
|
||||
float mouse_dx = 0.0f; // accumulated since the last TakeCinematicInput
|
||||
float mouse_dy = 0.0f;
|
||||
int wheel = 0; // speed steps, accumulated
|
||||
int smooth_steps = 0; // smoothing steps, accumulated
|
||||
// Controller (mod only, -1..1, added to the keys): left stick move, right stick look, L1/R1
|
||||
// down/up, L2/R2 zoom out/in.
|
||||
float pad_move_x = 0.0f, pad_move_y = 0.0f, pad_rise = 0.0f;
|
||||
float pad_look_x = 0.0f, pad_look_y = 0.0f, pad_zoom = 0.0f;
|
||||
// Watching a living hunter while dead (2026-10-20): presses since the last call, + next /
|
||||
// - previous (Tab, E, left click, R1 / Shift+Tab, Q, L1), and F (watch <-> fly freely).
|
||||
int switch_target = 0;
|
||||
bool toggle_free = false;
|
||||
};
|
||||
|
||||
// Game thread: the current keys plus everything accumulated since the last call.
|
||||
CinematicInput TakeCinematicInput();
|
||||
|
||||
// Game thread: the camera could not be switched on (logged); the toggle goes back to off.
|
||||
void CinematicForceOff();
|
||||
|
||||
// Game thread: switches the camera on (you died in co-op); nothing when it is already on.
|
||||
void CinematicForceOn();
|
||||
|
||||
// Game thread: the camera is watching a living hunter (you are dead). Emulator build: only then
|
||||
// does the controller reach the camera (CinematicPadRead) - the F1 camera leaves it to the hunter.
|
||||
void CinematicSetWatching(bool watching);
|
||||
|
||||
// Emulator build, pad thread (scePadReadState): the game's pad data, read only. While watching,
|
||||
// R1 / L1 switch the hunter and the right stick turns the camera.
|
||||
void CinematicPadRead(unsigned buttons, unsigned char rx, unsigned char ry, unsigned char lx,
|
||||
unsigned char ly);
|
||||
|
||||
} // namespace Core::Bbsc
|
||||
Loaded 100 of 1436 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user