Compare commits

..
7 Commits
Author SHA1 Message Date
ayozetr c1202bab21 Launcher: version 0.70-linux.6 2026-11-12 00:39:59 +00:00
ayozetr c435733825 Launcher: a shortcut in the applications menu or on the desktop
The AppImage had no menu entry unless an AppImage manager (Gear Lever,
AppImageLauncher) made one, and a checkout had only
launcher/install-desktop.sh. The Setup page now has a Shortcut row, under
Add to Steam, with two buttons: Applications menu writes a desktop entry
to $XDG_DATA_HOME/applications, Desktop to the desktop folder
(xdg-user-dir DESKTOP), executable and marked trusted for GNOME's file
manager. The entry starts the AppImage itself, or the checkout's
bb-launcher.sh, with the icon kept in the data folder as Add to Steam
keeps it. In every launcher language.

bbport_shortcut.py holds the entry (Exec quoted as the Desktop Entry
Specification wants) and tests/test_shortcut.py covers it.
getting-started.md mentions the row.
2026-11-12 00:39:52 +00:00
ayozetr 64acb09154 Credits: the reporters of #50 and #51
In docs/CREDITS.md and the launcher's About page, in every language:
brenki1 for FSR 4 stopping after lamp travels (#50), and shaneo88's entry
gains the camera that did not follow the character on a controller (#51).
2026-11-12 00:38:21 +00:00
ayozetrandAsanilo 5f4ae0ac12 Upscaler: the preset file's upscaler with a fourth field too
From Asanilo's pull request to bbport (deadinside28/bloodborne_pc#438,
fixing #437). A BB_PRESET_FILE request with a fourth field (the aspect
ratio) kept the previous upscaler: the provider was taken only when
exactly three fields were read. It is now taken from three on, with the
same range check. tests/test_preset_file.py covers three- and four-field
requests, invalid and partial input and the polling cadence.

Credits (docs/CREDITS.md and the launcher's About page, in every
language): The-Fool-cmd for the NixOS shells, and Asanilo's entry gains
this fix.

Co-Authored-By: Asanilo <[email protected]>
2026-11-12 00:37:38 +00:00
ayozetrandThe-Fool-cmd 79b71e6b16 Nix shells: DLSS and the launcher's folder chooser on NixOS
From The-Fool-cmd's pull request to bbport (deadinside28/bloodborne_pc#452),
for running from source with nix-shell on NixOS:

- shell.nix: NixOS keeps the driver's libraries in /run/opengl-driver/lib,
  where the DLSS bridge's dlopen of libnvidia-ngx.so.1 did not look, so DLSS
  never turned on ("Feature requirement query failed: 0xbad00012"). The
  directory joins LD_LIBRARY_PATH; it holds only driver libraries.
- launcher/shell.nix: the launcher gets the XDG_DATA_DIRS the packaged one
  has (packaging/default.nix): the icons for hosts without an Adwaita theme,
  and GTK4's schemas, without which the folder chooser aborted.

The AppImage and the packaged launcher already handled both.

Co-Authored-By: The-Fool-cmd <[email protected]>
2026-11-12 00:36:51 +00:00
ayozetr 93a8f8e2a5 Pad: keep the camera following the character on a controller
With mouse look on, the game's own camera auto-rotation (the camera
swinging behind the character as it moves) is switched off while the
window holds the mouse, because it fights a mouse turning the camera
(#352). The window takes the mouse whenever it has the focus, so a
player on a controller lost it too, in the sessions where the mouse had
been taken (#51, a PS4 controller with mouse look on).

It is now switched off only while the keyboard and mouse are what is
being played with: the last of the two to be used, as the button prompts
already tell them apart. Back on the controller, the camera follows the
character again.
2026-11-12 00:35:26 +00:00
ayozetr 2433441711 FSR 4: keep registering images after many area loads
The FSR 4 provider (FSR-Vulkan's v07 backend) keeps the images the port
registers each frame (color, depth, motion, output) in a table of eight
records matched by view, and never let one go. The port makes new views
whenever it recreates those images, at an area load or a resolution
change, so after a few lamp travels every registration failed with
VK_ERROR_OUT_OF_POOL_MEMORY ("external image registration failed") and
the upscaler stopped until it was turned off and on again (#50, an RX
9060 XT with FSR 4).

gpu/patches/fsr-vulkan/0005 keeps the table in order of use, a
registration moving its record to the end, and a full table gives up the
record unused the longest: the images of the frame being recorded are the
ones registered last. The five patches apply in order to a clean
submodule.
2026-11-12 00:33:23 +00:00
15 changed files with 421 additions and 14 deletions

No files matched your search

+4 -2
View File
@@ -50,9 +50,10 @@ Obrikash and noelmiller. What each one did:
- **[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, fast loading, and the L3 run-jump patch (Kyo's) as bbhost carries it.
- **[Asanilo](https://github.com/Asanilo)**: two GPU fixes: work submission counts, and samplers freed after the frame that uses them.
- **[Asanilo](https://github.com/Asanilo)**: two GPU fixes: work submission counts, and samplers freed after the frame that uses them; and the preset file's upscaler with four fields.
- **[AlexanderDzhoganov](https://github.com/AlexanderDzhoganov)**: BBSC: the guest no longer stays a spectator after a boss wipe.
- **[nullsumlabs](https://github.com/nullsumlabs)**: an unknown system value (sysctl) no longer stops the game.
- **[The-Fool-cmd](https://github.com/The-Fool-cmd)**: DLSS and the launcher's folder chooser when running from source on NixOS.
- **[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.
@@ -98,7 +99,8 @@ Obrikash and noelmiller. What each one did:
- **[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.
- **[shaneo88](https://github.com/shaneo88)**: shadPS4's patch file in the patches folder, and the camera not following the character on a controller.
- **[brenki1](https://github.com/brenki1)**: FSR 4 stopping after lamp travels.
- **[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.
+4
View File
@@ -18,6 +18,10 @@
3. In the launcher, choose your **game folder**: the one with `eboot.bin` in it.
4. Press **Play**.
The launcher can put itself in your applications menu or on your desktop: **Shortcut**, under
**Add to Steam** in its settings (an AppImage manager such as Gear Lever does the same for the
menu).
!!! 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.
@@ -0,0 +1,46 @@
From: bbport
Subject: [PATCH] FSR 4 v07: a full external image table gives up its least used record
External image records are matched by view and were never removed, and the
table holds eight. The caller makes new views whenever it recreates its
images (an area load, a resolution change), so after a few of those every
ffxFsr4VkSetExternalImageState failed with VK_ERROR_OUT_OF_POOL_MEMORY and
the upscaler stopped (bbport Linux #50). The table is now kept in order of
use, a registration moving its record to the end, and a full table drops
the record unused the longest: the images of the frame being recorded are
the ones registered last.
---
diff --git a/src/ffx_vk_fsr4_v07.c b/src/ffx_vk_fsr4_v07.c
--- a/src/ffx_vk_fsr4_v07.c
+++ b/src/ffx_vk_fsr4_v07.c
@@ -2013,14 +2013,26 @@
return VK_ERROR_VALIDATION_FAILED_EXT;
FfxFsr4VkContext* c = (FfxFsr4VkContext*)iface->device;
+ /* bbport: the table is kept in order of use, the record registered last at
+ * its end, and a full table gives up the record unused the longest.
+ * Records are matched by view and were never removed, and the caller makes
+ * new views whenever it recreates its images (an area load, a resolution
+ * change): after a few of those every registration failed with
+ * VK_ERROR_OUT_OF_POOL_MEMORY and the upscaler stopped. The images of the
+ * frame being recorded are the ones registered last. */
for (uint32_t i = 0; i < c->externalImageCount; ++i) {
if (c->externalImages[i].state.view == state->view) {
- c->externalImages[i].state = *state;
+ memmove(&c->externalImages[i], &c->externalImages[i + 1],
+ (c->externalImageCount - i - 1) * sizeof(c->externalImages[0]));
+ c->externalImages[c->externalImageCount - 1].state = *state;
return VK_SUCCESS;
}
}
- if (c->externalImageCount >= MAX_EXTERNAL_IMAGES)
- return VK_ERROR_OUT_OF_POOL_MEMORY;
+ if (c->externalImageCount >= MAX_EXTERNAL_IMAGES) {
+ memmove(&c->externalImages[0], &c->externalImages[1],
+ (MAX_EXTERNAL_IMAGES - 1) * sizeof(c->externalImages[0]));
+ --c->externalImageCount;
+ }
c->externalImages[c->externalImageCount++].state = *state;
return VK_SUCCESS;
}
+4
View File
@@ -16,3 +16,7 @@ patches here to its working tree when they are not applied yet:
- `0004-...`: `aspectMask` in `FfxFsr4VkExternalImageState` (0 = color). The layout barriers of a
registered external image named the color aspect, which is invalid for a depth image (the
Linux fork's 0002).
- `0005-...`: the FSR 4 provider's table of external images (eight, matched by view) never let
a record go, and the port makes new views whenever it recreates its images: after a few area
loads every registration failed with `VK_ERROR_OUT_OF_POOL_MEMORY` and FSR 4 stopped (#50).
The table is kept in order of use and a full one drops the record unused the longest.
+1 -1
View File
@@ -210,7 +210,7 @@ bool TemporalUpscaler::OnFrameStart() {
if (fields >= 2 && output >= 0 && output < outputs.count) {
BbSettings::Get().output_res.store(BbSettings::PackSize(outputs.sizes[output]));
}
if (fields == 3 && provider >= 0 && provider < BbSettings::UpscalerCount) {
if (fields >= 3 && provider >= 0 && provider < BbSettings::UpscalerCount) {
BbSettings::Get().upscaler.store(provider);
}
std::fclose(f);
+70 -7
View File
@@ -633,6 +633,13 @@ EN = {
"Steam не закрылся. Закройте его сами и попробуйте ещё раз": "Steam did not close. Close it yourself and try again",
"Не удалось добавить игру в Steam: {}": "Couldn't add the game to Steam: {}",
"Bloodborne добавлен в библиотеку Steam": "Bloodborne was added to your Steam library",
'Ярлык': 'Shortcut',
'Лаунчер в меню приложений или на рабочем столе': 'The launcher in the applications menu or on the desktop',
'Меню приложений': 'Applications menu',
'Рабочий стол': 'Desktop',
'Не удалось создать ярлык: {}': 'Could not create the shortcut: {}',
'Ярлык создан на рабочем столе': 'Shortcut created on the desktop',
'Bloodborne добавлен в меню приложений': 'Bloodborne added to the applications menu',
# Online page
"Играть онлайн": "Play online",
"Игровой сервер (сообщения, пятна крови, призраки)": "Game server (messages, bloodstains, ghosts)",
@@ -870,6 +877,7 @@ EN = {
'Два исправления GPU: счётчики отправленной работы и сэмплеры, освобождаемые после их кадра': 'Two GPU fixes: work submission counts, and samplers freed after their frame',
'BBSC: гость больше не остаётся зрителем после гибели группы у босса': 'BBSC: the guest no longer stays a spectator after a boss wipe',
'Неизвестный системный параметр (sysctl) больше не останавливает игру': 'An unknown system value (sysctl) no longer stops the game',
'DLSS и выбор папки в лаунчере при запуске из исходников на NixOS': "DLSS and the launcher's folder chooser when running from source on NixOS",
'Сетевые библиотеки игры по сети и сервер для своего кооператива': 'The online play libraries and the server for your own co-op',
'Основа поддержки DLSS и модель FSR 4 без шлейфов за персонажами': 'The groundwork for DLSS, and the FSR 4 model without trails behind characters',
'Апскейлер FSR 3.1': 'The FSR 3.1 upscaler',
@@ -891,7 +899,8 @@ EN = {
'Журнал, показавший, почему Steam Input не доходил до игры': 'The log that showed why Steam Input did not reach the game',
'Чёрный Сон Охотника после долгой игры': "The Hunter's Dream going black after long sessions",
'«Выход на рабочий стол» при игре по сети и идеи для лаунчера': 'Quit to Desktop with online play on, and ideas for the launcher',
'Файл патчей shadPS4 в папке патчей': "shadPS4's patch file in the patches folder",
'Файл патчей shadPS4 в папке патчей и камера, которая не следовала за персонажем с геймпадом': "shadPS4's patch file in the patches folder, and the camera not following the character on a controller",
'FSR 4, переставший работать после перемещений у ламп': 'FSR 4 stopping after lamp travels',
'Вылет «Transaction failed» на Seamless Hunt': 'The "Transaction failed" crash on Seamless Hunt',
'Вылет «Transaction failed» на Seamless Hunt, журналы, по которым гость BBSC перестал следовать за чужим хозяином, и проверки BBSC 0.0.2 с игроком на Windows': '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',
'Страница трофеев, не находившая файлы дампа': "The Trophies page not finding the dump's files",
@@ -1552,6 +1561,13 @@ PT_BR = {
"Steam не закрылся. Закройте его сами и попробуйте ещё раз": "O Steam não fechou. Feche-o você mesmo e tente de novo",
"Не удалось добавить игру в Steam: {}": "Não foi possível adicionar o jogo ao Steam: {}",
"Bloodborne добавлен в библиотеку Steam": "Bloodborne foi adicionado à sua biblioteca do Steam",
'Ярлык': 'Atalho',
'Лаунчер в меню приложений или на рабочем столе': 'O launcher no menu de aplicativos ou na área de trabalho',
'Меню приложений': 'Menu de aplicativos',
'Рабочий стол': 'Área de trabalho',
'Не удалось создать ярлык: {}': 'Não foi possível criar o atalho: {}',
'Ярлык создан на рабочем столе': 'Atalho criado na área de trabalho',
'Bloodborne добавлен в меню приложений': 'Bloodborne adicionado ao menu de aplicativos',
# Online page
"Играть онлайн": "Jogar online",
"Игровой сервер (сообщения, пятна крови, призраки)": "Servidor do jogo (mensagens, manchas de sangue, fantasmas)",
@@ -1965,6 +1981,7 @@ PT_BR = {
'Два исправления GPU: счётчики отправленной работы и сэмплеры, освобождаемые после их кадра': 'Duas correções da GPU: contagem do trabalho enviado e samplers liberados após o seu quadro',
'BBSC: гость больше не остаётся зрителем после гибели группы у босса': 'BBSC: o convidado não fica mais como espectador após uma derrota no chefe',
'Неизвестный системный параметр (sysctl) больше не останавливает игру': 'Um valor de sistema desconhecido (sysctl) não para mais o jogo',
'DLSS и выбор папки в лаунчере при запуске из исходников на NixOS': 'DLSS e o seletor de pastas do launcher ao rodar do código-fonte no NixOS',
'Сетевые библиотеки игры по сети и сервер для своего кооператива': 'As bibliotecas do jogo online e o servidor para o seu próprio cooperativo',
'Основа поддержки DLSS и модель FSR 4 без шлейфов за персонажами': 'A base do suporte a DLSS e o modelo do FSR 4 sem rastros atrás dos personagens',
'Апскейлер FSR 3.1': 'O upscaler FSR 3.1',
@@ -1986,7 +2003,8 @@ PT_BR = {
'Журнал, показавший, почему Steam Input не доходил до игры': 'O log que mostrou por que o Steam Input não chegava ao jogo',
'Чёрный Сон Охотника после долгой игры': 'O Sonho do Caçador ficando preto depois de sessões longas',
'«Выход на рабочий стол» при игре по сети и идеи для лаунчера': '"Sair para a área de trabalho" com o jogo online e ideias para o launcher',
'Файл патчей shadPS4 в папке патчей': 'O arquivo de patches do shadPS4 na pasta de patches',
'Файл патчей shadPS4 в папке патчей и камера, которая не следовала за персонажем с геймпадом': 'O arquivo de patches do shadPS4 na pasta de patches e a câmera que não seguia o personagem no controle',
'FSR 4, переставший работать после перемещений у ламп': 'O FSR 4 parando após viagens pelas lâmpadas',
'Вылет «Transaction failed» на Seamless Hunt': 'O travamento "Transaction failed" no Seamless Hunt',
'Вылет «Transaction failed» на Seamless Hunt, журналы, по которым гость BBSC перестал следовать за чужим хозяином, и проверки BBSC 0.0.2 с игроком на Windows': 'O crash "Transaction failed" no Seamless Hunt, os logs que fizeram o convidado do BBSC parar de seguir o mundo de um estranho e os testes do BBSC 0.0.2 com um jogador no Windows',
'Страница трофеев, не находившая файлы дампа': 'A página de troféus que não encontrava os arquivos do dump',
@@ -2652,6 +2670,13 @@ ES = {
"Steam не закрылся. Закройте его сами и попробуйте ещё раз": "Steam no se ha cerrado. Ciérralo tú y vuelve a intentarlo",
"Не удалось добавить игру в Steam: {}": "No se pudo añadir el juego a Steam: {}",
"Bloodborne добавлен в библиотеку Steam": "Bloodborne se ha añadido a tu biblioteca de Steam",
'Ярлык': 'Acceso directo',
'Лаунчер в меню приложений или на рабочем столе': 'El launcher en el menú de aplicaciones o en el escritorio',
'Меню приложений': 'Menú de aplicaciones',
'Рабочий стол': 'Escritorio',
'Не удалось создать ярлык: {}': 'No se pudo crear el acceso directo: {}',
'Ярлык создан на рабочем столе': 'Acceso directo creado en el escritorio',
'Bloodborne добавлен в меню приложений': 'Bloodborne añadido al menú de aplicaciones',
# Online page
"Играть онлайн": "Jugar online",
"Игровой сервер (сообщения, пятна крови, призраки)": "Servidor del juego (mensajes, manchas de sangre, fantasmas)",
@@ -2975,6 +3000,7 @@ ES = {
'Два исправления GPU: счётчики отправленной работы и сэмплеры, освобождаемые после их кадра': 'Dos arreglos de la GPU: el recuento del trabajo enviado y los samplers liberados tras su fotograma',
'BBSC: гость больше не остаётся зрителем после гибели группы у босса': 'BBSC: el invitado ya no se queda de espectador tras caer todos ante un jefe',
'Неизвестный системный параметр (sysctl) больше не останавливает игру': 'Un valor del sistema desconocido (sysctl) ya no detiene el juego',
'DLSS и выбор папки в лаунчере при запуске из исходников на NixOS': 'DLSS y el selector de carpetas del launcher al ejecutar desde el código en NixOS',
'Сетевые библиотеки игры по сети и сервер для своего кооператива': 'Las librerías del juego online y el servidor para el cooperativo propio',
'Основа поддержки DLSS и модель FSR 4 без шлейфов за персонажами': 'La base del soporte de DLSS y el modelo de FSR 4 sin estelas tras los personajes',
'Апскейлер FSR 3.1': 'El escalado FSR 3.1',
@@ -2996,7 +3022,8 @@ ES = {
'Журнал, показавший, почему Steam Input не доходил до игры': 'El log que mostró por qué Steam Input no llegaba al juego',
'Чёрный Сон Охотника после долгой игры': 'El Sueño del Cazador en negro tras sesiones largas',
'«Выход на рабочий стол» при игре по сети и идеи для лаунчера': '«Salir al escritorio» con el juego online e ideas para el launcher',
'Файл патчей shadPS4 в папке патчей': 'El archivo de parches de shadPS4 en la carpeta de parches',
'Файл патчей shadPS4 в папке патчей и камера, которая не следовала за персонажем с геймпадом': 'El archivo de parches de shadPS4 en la carpeta de parches y la cámara que no seguía al personaje con mando',
'FSR 4, переставший работать после перемещений у ламп': 'FSR 4 dejando de funcionar tras viajar entre lámparas',
'Вылет «Transaction failed» на Seamless Hunt': 'El cierre «Transaction failed» en Seamless Hunt',
'Вылет «Transaction failed» на Seamless Hunt, журналы, по которым гость BBSC перестал следовать за чужим хозяином, и проверки BBSC 0.0.2 с игроком на Windows': 'El cierre «Transaction failed» en Seamless Hunt, los registros que hicieron que el invitado de BBSC dejara de seguir el mundo de un desconocido y las pruebas de BBSC 0.0.2 con un jugador en Windows',
'Страница трофеев, не находившая файлы дампа': 'La página de trofeos que no encontraba los archivos del volcado',
@@ -3662,6 +3689,13 @@ IT = {
"Steam не закрылся. Закройте его сами и попробуйте ещё раз": "Steam non si è chiuso. Chiudilo tu e riprova",
"Не удалось добавить игру в Steam: {}": "Impossibile aggiungere il gioco a Steam: {}",
"Bloodborne добавлен в библиотеку Steam": "Bloodborne è stato aggiunto alla tua libreria di Steam",
'Ярлык': 'Collegamento',
'Лаунчер в меню приложений или на рабочем столе': 'Il launcher nel menu delle applicazioni o sul desktop',
'Меню приложений': 'Menu applicazioni',
'Рабочий стол': 'Desktop',
'Не удалось создать ярлык: {}': 'Impossibile creare il collegamento: {}',
'Ярлык создан на рабочем столе': 'Collegamento creato sul desktop',
'Bloodborne добавлен в меню приложений': 'Bloodborne aggiunto al menu delle applicazioni',
# Online page
"Играть онлайн": "Gioca online",
"Игровой сервер (сообщения, пятна крови, призраки)": "Server di gioco (messaggi, macchie di sangue, fantasmi)",
@@ -3985,6 +4019,7 @@ IT = {
'Два исправления GPU: счётчики отправленной работы и сэмплеры, освобождаемые после их кадра': 'Due correzioni della GPU: il conteggio del lavoro inviato e i sampler liberati dopo il loro fotogramma',
'BBSC: гость больше не остаётся зрителем после гибели группы у босса': "BBSC: l'ospite non resta più spettatore dopo una sconfitta contro un boss",
'Неизвестный системный параметр (sysctl) больше не останавливает игру': 'Un valore di sistema sconosciuto (sysctl) non ferma più il gioco',
'DLSS и выбор папки в лаунчере при запуске из исходников на NixOS': 'DLSS e la scelta della cartella nel launcher eseguendo dai sorgenti su NixOS',
'Сетевые библиотеки игры по сети и сервер для своего кооператива': 'Le librerie del gioco online e il server per la propria cooperativa',
'Основа поддержки DLSS и модель FSR 4 без шлейфов за персонажами': 'La base del supporto DLSS e il modello FSR 4 senza scie dietro i personaggi',
'Апскейлер FSR 3.1': 'L’upscaler FSR 3.1',
@@ -4006,7 +4041,8 @@ IT = {
'Журнал, показавший, почему Steam Input не доходил до игры': 'Il log che ha mostrato perché Steam Input non arrivava al gioco',
'Чёрный Сон Охотника после долгой игры': 'Il Sogno del Cacciatore nero dopo sessioni lunghe',
'«Выход на рабочий стол» при игре по сети и идеи для лаунчера': '"Esci al desktop" con il gioco online e idee per il launcher',
'Файл патчей shadPS4 в папке патчей': 'Il file di patch di shadPS4 nella cartella delle patch',
'Файл патчей shadPS4 в папке патчей и камера, которая не следовала за персонажем с геймпадом': 'Il file delle patch di shadPS4 nella cartella delle patch e la telecamera che non seguiva il personaggio col controller',
'FSR 4, переставший работать после перемещений у ламп': 'FSR 4 che smetteva di funzionare dopo i viaggi tra le lanterne',
'Вылет «Transaction failed» на Seamless Hunt': 'La chiusura "Transaction failed" su Seamless Hunt',
'Вылет «Transaction failed» на Seamless Hunt, журналы, по которым гость BBSC перестал следовать за чужим хозяином, и проверки BBSC 0.0.2 с игроком на Windows': "Il crash «Transaction failed» su Seamless Hunt, i log che hanno fatto smettere all'ospite di BBSC di seguire il mondo di uno sconosciuto e i test di BBSC 0.0.2 con un giocatore su Windows",
'Страница трофеев, не находившая файлы дампа': 'La pagina dei trofei che non trovava i file del dump',
@@ -4672,6 +4708,13 @@ FR = {
"Steam не закрылся. Закройте его сами и попробуйте ещё раз": "Steam ne s'est pas fermé. Fermez-le vous-même et réessayez",
"Не удалось добавить игру в Steam: {}": "Impossible d'ajouter le jeu à Steam : {}",
"Bloodborne добавлен в библиотеку Steam": "Bloodborne a été ajouté à votre bibliothèque Steam",
'Ярлык': 'Raccourci',
'Лаунчер в меню приложений или на рабочем столе': 'Le launcher dans le menu des applications ou sur le bureau',
'Меню приложений': 'Menu des applications',
'Рабочий стол': 'Bureau',
'Не удалось создать ярлык: {}': 'Impossible de créer le raccourci : {}',
'Ярлык создан на рабочем столе': 'Raccourci créé sur le bureau',
'Bloodborne добавлен в меню приложений': 'Bloodborne ajouté au menu des applications',
# Online page
"Играть онлайн": "Jouer en ligne",
"Игровой сервер (сообщения, пятна крови, призраки)": "Serveur de jeu (messages, taches de sang, fantômes)",
@@ -4995,6 +5038,7 @@ FR = {
'Два исправления GPU: счётчики отправленной работы и сэмплеры, освобождаемые после их кадра': 'Deux correctifs GPU : le décompte du travail soumis et les samplers libérés après leur image',
'BBSC: гость больше не остаётся зрителем после гибели группы у босса': "BBSC : l'invité ne reste plus spectateur après une défaite face à un boss",
'Неизвестный системный параметр (sysctl) больше не останавливает игру': 'Une valeur système inconnue (sysctl) n’arrête plus le jeu',
'DLSS и выбор папки в лаунчере при запуске из исходников на NixOS': 'DLSS et le sélecteur de dossier du launcher en lançant depuis les sources sous NixOS',
'Сетевые библиотеки игры по сети и сервер для своего кооператива': 'Les bibliothèques du jeu en ligne et le serveur pour votre propre coopération',
'Основа поддержки DLSS и модель FSR 4 без шлейфов за персонажами': 'La base de la prise en charge de DLSS et le modèle FSR 4 sans traînées derrière les personnages',
'Апскейлер FSR 3.1': 'L’upscaler FSR 3.1',
@@ -5016,7 +5060,8 @@ FR = {
'Журнал, показавший, почему Steam Input не доходил до игры': "Le journal qui a montré pourquoi Steam Input n'atteignait pas le jeu",
'Чёрный Сон Охотника после долгой игры': 'Le Rêve du chasseur noir après de longues sessions',
'«Выход на рабочий стол» при игре по сети и идеи для лаунчера': '« Quitter vers le bureau » en jeu en ligne, et des idées pour le lanceur',
'Файл патчей shadPS4 в папке патчей': 'Le fichier de patchs de shadPS4 dans le dossier des patchs',
'Файл патчей shadPS4 в папке патчей и камера, которая не следовала за персонажем с геймпадом': 'Le fichier de patchs de shadPS4 dans le dossier des patchs, et la caméra qui ne suivait pas le personnage à la manette',
'FSR 4, переставший работать после перемещений у ламп': 'FSR 4 qui cessait de fonctionner après des voyages entre lanternes',
'Вылет «Transaction failed» на Seamless Hunt': 'Le plantage « Transaction failed » sur Seamless Hunt',
'Вылет «Transaction failed» на Seamless Hunt, журналы, по которым гость BBSC перестал следовать за чужим хозяином, и проверки BBSC 0.0.2 с игроком на Windows': "Le plantage « Transaction failed » sur Seamless Hunt, les journaux qui ont empêché l'invité de BBSC de suivre le monde d'un inconnu, et les tests de BBSC 0.0.2 avec un joueur sous Windows",
'Страница трофеев, не находившая файлы дампа': 'La page des trophées qui ne trouvait pas les fichiers du dump',
@@ -5682,6 +5727,13 @@ DE = {
"Steam не закрылся. Закройте его сами и попробуйте ещё раз": "Steam wurde nicht geschlossen. Schließe es selbst und versuche es erneut",
"Не удалось добавить игру в Steam: {}": "Spiel konnte nicht zu Steam hinzugefügt werden: {}",
"Bloodborne добавлен в библиотеку Steam": "Bloodborne wurde deiner Steam-Bibliothek hinzugefügt",
'Ярлык': 'Verknüpfung',
'Лаунчер в меню приложений или на рабочем столе': 'Der Launcher im Anwendungsmenü oder auf dem Desktop',
'Меню приложений': 'Anwendungsmenü',
'Рабочий стол': 'Desktop',
'Не удалось создать ярлык: {}': 'Die Verknüpfung konnte nicht erstellt werden: {}',
'Ярлык создан на рабочем столе': 'Verknüpfung auf dem Desktop erstellt',
'Bloodborne добавлен в меню приложений': 'Bloodborne zum Anwendungsmenü hinzugefügt',
# Online page
"Играть онлайн": "Online spielen",
"Игровой сервер (сообщения, пятна крови, призраки)": "Spielserver (Nachrichten, Blutflecken, Geister)",
@@ -6005,6 +6057,7 @@ DE = {
'Два исправления GPU: счётчики отправленной работы и сэмплеры, освобождаемые после их кадра': 'Zwei GPU-Fehlerbehebungen: die Zählung der übermittelten Arbeit und Sampler, die nach ihrem Frame freigegeben werden',
'BBSC: гость больше не остаётся зрителем после гибели группы у босса': 'BBSC: der Gast bleibt nach einer Niederlage gegen einen Boss nicht mehr Zuschauer',
'Неизвестный системный параметр (sysctl) больше не останавливает игру': 'Ein unbekannter Systemwert (sysctl) hält das Spiel nicht mehr an',
'DLSS и выбор папки в лаунчере при запуске из исходников на NixOS': 'DLSS und die Ordnerauswahl des Launchers beim Start aus dem Quellcode unter NixOS',
'Сетевые библиотеки игры по сети и сервер для своего кооператива': 'Die Bibliotheken für das Online-Spiel und der Server für den eigenen Koop',
'Основа поддержки DLSS и модель FSR 4 без шлейфов за персонажами': 'Die Grundlage für DLSS und das FSR-4-Modell ohne Schlieren hinter Figuren',
'Апскейлер FSR 3.1': 'Der FSR-3.1-Upscaler',
@@ -6026,7 +6079,8 @@ DE = {
'Журнал, показавший, почему Steam Input не доходил до игры': 'Das Log, das zeigte, warum Steam Input das Spiel nicht erreichte',
'Чёрный Сон Охотника после долгой игры': 'Der schwarze Traum des Jägers nach langen Sitzungen',
'«Выход на рабочий стол» при игре по сети и идеи для лаунчера': '„Zum Desktop beenden“ beim Onlinespiel und Ideen für den Launcher',
'Файл патчей shadPS4 в папке патчей': 'Die Patch-Datei von shadPS4 im Patch-Ordner',
'Файл патчей shadPS4 в папке патчей и камера, которая не следовала за персонажем с геймпадом': 'Die Patch-Datei von shadPS4 im Patch-Ordner und die Kamera, die der Figur mit Controller nicht folgte',
'FSR 4, переставший работать после перемещений у ламп': 'FSR 4, das nach Laternenreisen ausfiel',
'Вылет «Transaction failed» на Seamless Hunt': 'Der Absturz „Transaction failed“ auf Seamless Hunt',
'Вылет «Transaction failed» на Seamless Hunt, журналы, по которым гость BBSC перестал следовать за чужим хозяином, и проверки BBSC 0.0.2 с игроком на Windows': 'Der Absturz „Transaction failed“ auf Seamless Hunt, die Logs, durch die ein BBSC-Gast nicht mehr der Welt eines Fremden folgt, und Tests von BBSC 0.0.2 mit einem Windows-Spieler',
'Страница трофеев, не находившая файлы дампа': 'Die Trophäenseite, die die Dateien des Dumps nicht fand',
@@ -6843,6 +6897,13 @@ ZH_CN = {
", основной": ",主显示器",
"0: случайный при каждом запуске. Закрепите его, чтобы открыть в брандмауэре или на роутере вручную": "0:每次使用随机端口。如需在防火墙或路由器上手动开放,请固定一个端口",
"Bloodborne добавлен в библиотеку Steam": "Bloodborne 已添加到你的 Steam 库",
'Ярлык': '快捷方式',
'Лаунчер в меню приложений или на рабочем столе': '在应用菜单或桌面上放置启动器',
'Меню приложений': '应用菜单',
'Рабочий стол': '桌面',
'Не удалось создать ярлык: {}': '无法创建快捷方式:{}',
'Ярлык создан на рабочем столе': '已在桌面创建快捷方式',
'Bloodborne добавлен в меню приложений': '已将 Bloodborne 添加到应用菜单',
"Online ID — это имя учётной записи (NPID), зарегистрированное на сервере, а не адрес email": "Online ID 是你在服务器上注册的账号名(NPID),而不是邮箱地址",
"PlayStation (оригинал)": "PlayStation(原版)",
"Steam не закрылся. Закройте его сами и попробуйте ещё раз": "Steam 没有关闭。请手动关闭后重试",
@@ -6984,6 +7045,7 @@ ZH_CN = {
'Два исправления GPU: счётчики отправленной работы и сэмплеры, освобождаемые после их кадра': '两项 GPU 修复:已提交工作的计数,以及在其帧结束后才释放的采样器',
'BBSC: гость больше не остаётся зрителем после гибели группы у босса': 'BBSC:首领战全灭后,客人不再一直处于观战状态',
'Неизвестный системный параметр (sysctl) больше не останавливает игру': '未知的系统值(sysctl)不再导致游戏停止',
'DLSS и выбор папки в лаунчере при запуске из исходников на NixOS': '在 NixOS 上从源码运行时的 DLSS 与启动器文件夹选择',
'Сетевые библиотеки игры по сети и сервер для своего кооператива': '联机库与自建合作服务器',
'Основа поддержки DLSS и модель FSR 4 без шлейфов за персонажами': 'DLSS 支持的基础,以及角色身后无拖影的 FSR 4 模型',
'Апскейлер FSR 3.1': 'FSR 3.1 超分辨率',
@@ -7005,7 +7067,8 @@ ZH_CN = {
'Журнал, показавший, почему Steam Input не доходил до игры': '指出 Steam Input 为何无法传到游戏的日志',
'Чёрный Сон Охотника после долгой игры': '长时间游玩后猎人梦境变黑',
'«Выход на рабочий стол» при игре по сети и идеи для лаунчера': '联机时的“退出到桌面”以及对启动器的建议',
'Файл патчей shadPS4 в папке патчей': '补丁文件夹中的 shadPS4 补丁文件',
'Файл патчей shadPS4 в папке патчей и камера, которая не следовала за персонажем с геймпадом': '补丁文件夹中的 shadPS4 补丁文件,以及使用手柄时镜头不跟随角色的问题',
'FSR 4, переставший работать после перемещений у ламп': '在灯之间传送后 FSR 4 停止工作的问题',
'Вылет «Transaction failed» на Seamless Hunt': 'Seamless Hunt 上的“Transaction failed”崩溃',
'Вылет «Transaction failed» на Seamless Hunt, журналы, по которым гость BBSC перестал следовать за чужим хозяином, и проверки BBSC 0.0.2 с игроком на Windows': 'Seamless Hunt 上的“Transaction failed”崩溃、让 BBSC 访客不再跟随陌生人世界的日志,以及与 Windows 玩家一起测试 BBSC 0.0.2',
'Страница трофеев, не находившая файлы дампа': '找不到转储文件的奖杯页面',
+38 -1
View File
@@ -28,6 +28,7 @@ from bbport_i18n import language, set_language, system_game_language, tr
import bbport_cache
import bbport_atlas_maps
import bbport_coop
import bbport_shortcut
import bbport_steam
import bbport_update
@@ -1042,6 +1043,7 @@ THANKS = (
"освобождаемые после их кадра"),
_github("AlexanderDzhoganov", "BBSC: гость больше не остаётся зрителем после гибели группы у босса"),
_github("nullsumlabs", "Неизвестный системный параметр (sysctl) больше не останавливает игру"),
_github("The-Fool-cmd", "DLSS и выбор папки в лаунчере при запуске из исходников на NixOS"),
_github("Wozzardman", "Сервер для своего кооператива и бесшовный призыв BBSC"),
_github("IFreemz", "Модель FSR 4 без шлейфов за персонажами"),
_github("FireBurn", "Апскейлер FSR 3.1"),
@@ -1078,7 +1080,9 @@ THANKS = (
_github("shauryasagar", "Чёрный Сон Охотника после долгой игры"),
_github("ZeroSTF", "Чёрный Сон Охотника после долгой игры"),
_github("popcar2", "Идеи для лаунчера"),
_github("shaneo88", "Файл патчей shadPS4 в папке патчей"),
_github("shaneo88", "Файл патчей shadPS4 в папке патчей и камера, которая не следовала за "
"персонажем с геймпадом"),
_github("brenki1", "FSR 4, переставший работать после перемещений у ламп"),
_github("n3vrs0ft", "Вылет «Transaction failed» на Seamless Hunt, журналы, по которым гость "
"BBSC перестал следовать за чужим хозяином, и проверки BBSC 0.0.2 с "
"игроком на Windows"),
@@ -1355,6 +1359,16 @@ class LauncherWindow(Adw.ApplicationWindow):
steam.add_prefix(themed_icon("steam-symbolic"))
steam.add_suffix(steam_button)
game.add(steam)
# A menu entry (and one on the desktop) for the AppImage or this checkout: an AppImage
# has none unless an AppImage manager makes it (bbport_shortcut.py).
shortcut = Adw.ActionRow(title=tr("Ярлык"),
subtitle=tr("Лаунчер в меню приложений или на рабочем столе"))
shortcut.add_prefix(themed_icon("application-x-executable-symbolic"))
for label, where in ((tr("Меню приложений"), "menu"), (tr("Рабочий стол"), "desktop")):
button = Gtk.Button(label=label, valign=Gtk.Align.CENTER)
button.connect("clicked", self.on_create_shortcut, where)
shortcut.add_suffix(button)
game.add(shortcut)
self.steam_overlay_row = Adw.SwitchRow(
title=tr("Оверлей Steam"),
subtitle=tr("Кнопка Steam, счётчик FPS и снимки экрана Steam, когда игра запущена из Steam"),
@@ -2630,6 +2644,29 @@ class LauncherWindow(Adw.ApplicationWindow):
return False
GLib.timeout_add(500, poll)
def on_create_shortcut(self, _button, where):
appimage = os.environ.get("APPIMAGE")
target = appimage or str(PORT_DIR / "launcher" / "bb-launcher.sh")
icon = PORT_DIR / "launcher" / "bloodborne.png"
if appimage:
# The AppImage's mount is gone once it closes: the icon is kept in the data folder.
try:
kept = DATA_DIR / "bloodborne.png"
kept.write_bytes(icon.read_bytes())
icon = kept
except OSError:
pass
desktop = where == "desktop"
folder = bbport_shortcut.desktop_dir() if desktop else bbport_shortcut.applications_dir()
try:
path = bbport_shortcut.write_entry(folder, target, str(icon), trusted=desktop)
except OSError as error:
self.alert(tr("Ярлык"), tr("Не удалось создать ярлык: {}").format(error))
return
message = tr("Ярлык создан на рабочем столе") if desktop else tr("Bloodborne добавлен в меню приложений")
print(f"Launcher: shortcut written to {path}")
self.toasts.add_toast(Adw.Toast(title=message, timeout=6))
def add_to_steam(self, steam, configs, restart):
# Packaged: the AppImage itself; from a checkout: the launcher script.
appimage = os.environ.get("APPIMAGE")
+72
View File
@@ -0,0 +1,72 @@
# SPDX-FileCopyrightText: Copyright 2026 bbport contributors
# SPDX-License-Identifier: GPL-3.0-or-later
"""A desktop entry for the launcher: in the applications menu and on the desktop.
The AppImage has no menu entry of its own unless an AppImage manager (Gear Lever, AppImageLauncher)
makes one, and a checkout has launcher/install-desktop.sh. The launcher's "Create shortcut" writes
one for whichever it is: the AppImage itself, or the checkout's bb-launcher.sh.
"""
import os
import subprocess
from pathlib import Path
ENTRY_NAME = "io.github.ayozetr.bloodborne_linux.desktop"
def exec_quote(path):
"""`path` as one Exec argument (Desktop Entry Specification: double quotes, with ", `, $ and \\
escaped)."""
escaped = "".join("\\" + c if c in '"`$\\' else c for c in str(path))
return f'"{escaped}"'
def entry_text(target, icon):
"""The desktop entry that starts `target` (the AppImage or bb-launcher.sh) with `icon`."""
return ("[Desktop Entry]\n"
"Type=Application\n"
"Name=Bloodborne (bbport)\n"
"Comment=Native Linux port launcher for your own copy of Bloodborne\n"
f"Exec={exec_quote(target)}\n"
f"Icon={icon}\n"
"Terminal=false\n"
"Categories=Game;\n"
"StartupWMClass=io.github.bbport.Launcher\n")
def applications_dir(env=os.environ):
"""Where the user's own menu entries go ($XDG_DATA_HOME/applications)."""
data = env.get("XDG_DATA_HOME") or str(Path.home() / ".local" / "share")
return Path(data) / "applications"
def desktop_dir():
"""The desktop folder (xdg-user-dir DESKTOP: "Escritorio", "Bureau"...), else ~/Desktop."""
try:
out = subprocess.run(["xdg-user-dir", "DESKTOP"], capture_output=True, text=True,
timeout=5).stdout.strip()
if out and Path(out).is_dir() and Path(out) != Path.home():
return Path(out)
except (OSError, subprocess.SubprocessError):
pass
return Path.home() / "Desktop"
def write_entry(folder, target, icon, trusted=False):
"""Writes the entry into `folder` and returns its path. On the desktop (`trusted`) it is made
executable and marked trusted for GNOME's file manager, which otherwise will not start it."""
folder.mkdir(parents=True, exist_ok=True)
path = folder / ENTRY_NAME
path.write_text(entry_text(target, icon), encoding="utf-8")
path.chmod(0o755 if trusted else 0o644)
if trusted:
try:
subprocess.run(["gio", "set", str(path), "metadata::trusted", "true"],
capture_output=True, timeout=5)
except (OSError, subprocess.SubprocessError):
pass
else:
try:
subprocess.run(["update-desktop-database", str(folder)], capture_output=True, timeout=10)
except (OSError, subprocess.SubprocessError):
pass
return path
+1 -1
View File
@@ -14,7 +14,7 @@ import re
import tempfile
import urllib.request
VERSION = "0.70-linux.5"
VERSION = "0.70-linux.6"
def _build():
+5
View File
@@ -5,4 +5,9 @@ pkgs.mkShell {
(python3.withPackages (ps: [ ps.pygobject3 ]))
gtk4 libadwaita gobject-introspection
];
# The same data directories packaging/default.nix gives the packaged launcher: icons for hosts
# without an Adwaita theme, and GTK4's schemas, which the file chooser aborts without.
shellHook = ''
export XDG_DATA_DIRS=${pkgs.adwaita-icon-theme}/share:${pkgs.hicolor-icon-theme}/share:${pkgs.gtk4}/share/gsettings-schemas/${pkgs.gtk4.name}''${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}
'';
}
+6
View File
@@ -26,4 +26,10 @@ pkgs.mkShell {
# Online module (gpu/bbnet): shadNet client, HTTPS, UPnP
protobuf nlohmann_json openssl zlib miniupnpc
];
# NixOS keeps the driver's libraries in /run/opengl-driver/lib, where the DLSS bridge's dlopen of
# libnvidia-ngx.so.1 does not look (the AppImage links them in: launcher/bbport_vulkan.py).
# Only driver libraries live there, so nothing the shell provides is replaced.
shellHook = ''
export LD_LIBRARY_PATH=''${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}${pkgs.addDriverRunpath.driverLink}/lib
'';
}
+5 -2
View File
@@ -559,7 +559,10 @@ static uint8_t look_axis(float value, uint8_t stick, int deadzone) {
/* While the mouse turns the camera, the game's own camera auto-rotation (it swings the camera
* behind the character on strafes, made for a stick) fights it: its three stores (1.09) are NOPs
* then, put back when the mouse is let go (#352). Only where the bytes are the game's own.
* BB_MOUSE_AUTO_ROTATE=1: left as the game does it. */
* BB_MOUSE_AUTO_ROTATE=1: left as the game does it. Not while the gamepad is what is being played
* with (gamepad_playing, sample.c): the window holds the mouse whenever it has the focus, and a
* player on a controller lost the camera following the character (#51). */
static int gamepad_playing; /* the gamepad was used last, not the keyboard or the mouse */
static void mouse_auto_rotation_hold(int hold) {
static int held=-1, off=-1;
if (off<0) { const char *e=bb_env("BB_MOUSE_AUTO_ROTATE"); off=e && e[0]=='1'; }
@@ -588,7 +591,7 @@ static void mouse_auto_rotation_hold(int hold) {
held=hold;
}
static void apply_mouse_look(PadData *d, double dx, double dy, uint64_t now, int captured) {
mouse_auto_rotation_hold(mouse_look && captured && !bbgpu_free_camera());
mouse_auto_rotation_hold(mouse_look && captured && !gamepad_playing && !bbgpu_free_camera());
/* The free camera (patch) takes the mouse through the right stick below. */
if (captured && (dx!=0 || dy!=0) && !bbgpu_free_camera()) {
const float k=MOUSE_RAD_PER_PIXEL*mouse_sensitivity;
+2
View File
@@ -190,6 +190,8 @@ static void sample_host(PadData *d) {
(mouse_buttons!=0));
for (int i=0;i<IN_COUNT && !keyboard_active;++i) keyboard_active=key_down(k,i);
prompt_note(g,pad_active,keyboard_active);
if (keyboard_active) gamepad_playing=0;
else if (pad_active) gamepad_playing=1;
apply_keyboard(d,k); /* the bound mouse buttons too */
video_skip(d,k);
if (motion_on()) {
+123
View File
@@ -0,0 +1,123 @@
# SPDX-FileCopyrightText: Copyright 2026 bbport contributors
# SPDX-License-Identifier: GPL-2.0-or-later
"""Exercise the production preset-file reader without a Vulkan device.
Compile the unchanged OnFrameStart polling/reading block with the real settings
implementation. Only the remaining frame/GPU work is omitted from the harness.
"""
import os
from pathlib import Path
import shlex
import shutil
import subprocess
import tempfile
import unittest
from paths import ROOT
class PresetFileTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
compiler = shlex.split(os.environ.get('CXX', 'c++'))
if not compiler or not shutil.which(compiler[0]):
raise unittest.SkipTest('a C++23 compiler is required')
cls.tmp = tempfile.TemporaryDirectory()
cls.addClassCleanup(cls.tmp.cleanup)
cls.folder = Path(cls.tmp.name)
source = (ROOT / 'gpu/upscale/vk_temporal_upscaler.cpp').read_text(encoding='utf-8')
start = source.index('static const char* preset_file',
source.index('bool TemporalUpscaler::OnFrameStart() {'))
end = source.index('const auto& settings = BbSettings::Get();', start)
reader = source[start:end]
harness = cls.folder / 'preset_file.cpp'
harness.write_text('''#include <cstdio>
#include <cstdlib>
#include "shim/bb_env.h"
#include "shim/bbport_settings.h"
void Poll() {
static unsigned preset_file_frames = 0;
''' + reader + '''}
int main(int argc, char** argv) {
auto& s = BbSettings::Get();
s.preset = BbSettings::Balanced;
s.upscaler = BbSettings::UpscalerDlss;
s.aspect = BbSettings::Aspect16x9;
s.output_res = BbSettings::PackSize({1920, 1080});
const int frames = argc > 1 ? std::atoi(argv[1]) : 64;
for (int i = 0; i < frames; ++i) Poll();
const auto output = BbSettings::UnpackSize(s.output_res.load());
std::printf("%d %d %d %d %d\\n", s.preset.load(), s.upscaler.load(),
s.aspect.load(), output.width, output.height);
std::fprintf(stderr, "%d\\n", int(BbSettings::UpscalerCount));
}
''', encoding='utf-8')
cls.exe = cls.folder / 'preset-file-test'
result = subprocess.run(compiler + ['-std=c++23', '-Wall', '-Wextra', '-Werror',
'-I', str(ROOT / 'gpu'), str(harness),
str(ROOT / 'gpu/shim/bbport_settings.cpp'),
'-o', str(cls.exe)], capture_output=True, text=True, timeout=60)
if result.returncode:
raise RuntimeError(result.stdout + result.stderr)
# The provider list is the real one, not a copy: a provider added later is covered too.
probe = subprocess.run([str(cls.exe), '0'], capture_output=True, text=True, check=True)
cls.providers = int(probe.stderr)
def read(self, text, frames=64):
request = self.folder / 'request.txt'
if text is None:
request.unlink(missing_ok=True)
else:
request.write_text(text, encoding='utf-8')
env = dict(os.environ, BB_PRESET_FILE=str(request))
result = subprocess.run([str(self.exe), str(frames)], env=env,
capture_output=True, text=True, timeout=5, check=True)
return tuple(map(int, result.stdout.split()))
def test_four_fields_apply_provider_and_aspect(self):
self.assertEqual(self.read('1 0 0 3'), (1, 0, 3, 640, 360))
def test_three_and_four_fields_accept_every_provider(self):
for provider in range(self.providers):
for suffix in ('', ' 3'):
with self.subTest(provider=provider, suffix=suffix):
self.assertEqual(self.read(f'1 0 {provider}{suffix}'),
(1, provider, 3, 640, 360))
def test_one_and_two_fields_keep_provider(self):
self.assertEqual(self.read('1'), (1, 5, 3, 1920, 1080))
self.assertEqual(self.read('1 0'), (1, 5, 3, 640, 360))
def test_invalid_provider_keeps_previous_provider(self):
for provider in (-1, self.providers, 999):
for suffix in ('', ' 2'):
with self.subTest(provider=provider, suffix=suffix):
aspect, size = (2, (640, 400)) if suffix else (3, (640, 360))
self.assertEqual(self.read(f'1 0 {provider}{suffix}'), (1, 5, aspect, *size))
def test_invalid_other_fields_do_not_block_valid_provider(self):
for text in ('-1 -1 0 -1', '5 999 0 6'):
with self.subTest(text=text):
self.assertEqual(self.read(text), (2, 0, 3, 1920, 1080))
def test_aspect_is_applied_before_output_lookup(self):
self.assertEqual(self.read('1 0 0 2'), (1, 0, 2, 640, 400))
def test_missing_empty_and_non_numeric_requests(self):
for text in (None, '', 'bad', 'bad 0 0 3'):
with self.subTest(text=text):
self.assertEqual(self.read(text), (2, 5, 3, 1920, 1080))
def test_partial_requests_apply_only_scanned_fields(self):
self.assertEqual(self.read('1 0 bad 2'), (1, 5, 3, 640, 360))
self.assertEqual(self.read('1 0 0 bad'), (1, 0, 3, 640, 360))
def test_polling_interval_is_unchanged(self):
self.assertEqual(self.read('1 0 0 3', frames=63), (2, 5, 3, 1920, 1080))
self.assertEqual(self.read('1 0 0 3', frames=64), (1, 0, 3, 640, 360))
if __name__ == '__main__':
unittest.main()
+40
View File
@@ -0,0 +1,40 @@
# SPDX-FileCopyrightText: Copyright 2026 bbport contributors
# SPDX-License-Identifier: GPL-3.0-or-later
import sys
import tempfile
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "launcher"))
import bbport_shortcut # noqa: E402
class ShortcutTest(unittest.TestCase):
def test_exec_quotes_spaces_and_specials(self):
self.assertEqual(bbport_shortcut.exec_quote("/a b/Bloodborne.AppImage"),
'"/a b/Bloodborne.AppImage"')
self.assertEqual(bbport_shortcut.exec_quote('/x/"$`\\'), '"/x/\\"\\$\\`\\\\"')
def test_entry_fields(self):
text = bbport_shortcut.entry_text("/games/Bloodborne.AppImage", "/data/bloodborne.png")
lines = text.splitlines()
self.assertEqual(lines[0], "[Desktop Entry]")
self.assertIn('Exec="/games/Bloodborne.AppImage"', lines)
self.assertIn("Icon=/data/bloodborne.png", lines)
self.assertIn("StartupWMClass=io.github.bbport.Launcher", lines)
def test_applications_dir_follows_xdg_data_home(self):
self.assertEqual(bbport_shortcut.applications_dir({"XDG_DATA_HOME": "/d"}),
Path("/d/applications"))
def test_write_entry_modes(self):
with tempfile.TemporaryDirectory() as tmp:
menu = bbport_shortcut.write_entry(Path(tmp) / "apps", "/a", "/i")
self.assertEqual(menu.stat().st_mode & 0o777, 0o644)
desk = bbport_shortcut.write_entry(Path(tmp) / "desk", "/a", "/i", trusted=True)
self.assertEqual(desk.stat().st_mode & 0o777, 0o755)
self.assertEqual(desk.read_text(), bbport_shortcut.entry_text("/a", "/i"))
if __name__ == "__main__":
unittest.main()