mirror of
https://github.com/ayozetr/bloodborne_linux.git
synced 2026-11-08 01:08:25 -10:00
Compare commits
7
Commits
0.70-linux.2
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98750d0687 | ||
|
|
0b3dac11d7 | ||
|
|
5041586f9a | ||
|
|
e77bce746f | ||
|
|
d1dea1b8e4 | ||
|
|
6796fbbd13 | ||
|
|
badd460277 |
No files matched your search
@@ -59,6 +59,17 @@ and the HUD keeps to the screen's edges; it changes live, in the game's *Display
|
||||
**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
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
|
||||
#include "bbgpu.h"
|
||||
#include "bbport_guest_code_hooks.h"
|
||||
#include "bbport_atlas_menu.h"
|
||||
#include "bbport_overlay.h"
|
||||
#include "bbport_toggles.h"
|
||||
#include "common/types.h"
|
||||
@@ -520,24 +521,35 @@ void PS4_SYSV_ABI OnListUpdate(std::uint64_t, const Core::GuestRegisterSnapshot*
|
||||
}
|
||||
return;
|
||||
}
|
||||
static const bool trace = bb_env("BB_MENU_POINTER_FILE") != nullptr;
|
||||
if ((!moved && !clicked) || pad_steering.load(std::memory_order_relaxed)) {
|
||||
if (trace && clicked) {
|
||||
std::printf("Menu pointer: list %p click waits (pad steering)\n",
|
||||
static_cast<void*>(component));
|
||||
}
|
||||
return;
|
||||
}
|
||||
std::uint64_t slot = 0;
|
||||
const int index = list.ItemAt(x, y, slot);
|
||||
if (index < 0) {
|
||||
if (trace && clicked) {
|
||||
std::printf("Menu pointer: list %p click at %.0f,%.0f, on none of its items\n",
|
||||
static_cast<void*>(component), x, y);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (clicked) {
|
||||
click.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
static const bool trace = bb_env("BB_MENU_POINTER_FILE") != nullptr;
|
||||
if (trace) {
|
||||
std::printf("Menu pointer: list %p item %d (cursor %d)%s\n", static_cast<void*>(component),
|
||||
index, list.CursorIndex(), clicked ? " click" : "");
|
||||
}
|
||||
if (list.MoveTo(index, slot) && clicked) {
|
||||
const std::uint32_t button = list.ClickButton(slot, x, y, ConfirmButton());
|
||||
if (trace) {
|
||||
std::printf("Menu pointer: list %p presses %#x\n", static_cast<void*>(component), button);
|
||||
}
|
||||
presses.fetch_or(button, std::memory_order_relaxed);
|
||||
// A held click on a slider's arrow keeps moving it.
|
||||
if (button == Left || button == Right) {
|
||||
@@ -602,27 +614,41 @@ int OnEvent(const void* sdl_event, void* sdl_window) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const bool ours = !bbgpu_mouse_captured() && !BbOverlay::MenuOpen() && InMenu();
|
||||
// Not BbOverlay::MenuOpen(): that is also true while Hunter's Atlas's tile shows in the game's
|
||||
// quick menu, and then no click reached the game's own items (only the tile's Map opened).
|
||||
// A click on the tile itself is left to it (BbAtlasMenu::HandleMouse, after this filter),
|
||||
// and so is its release, on which it opens the map.
|
||||
const bool atlas_tile = BbAtlasMenu::Visible() && BbAtlasMenu::Hovered();
|
||||
const bool ours =
|
||||
!bbgpu_mouse_captured() && !BbOverlay::CapturesInput() && !atlas_tile && InMenu();
|
||||
static const bool trace = bb_env("BB_MENU_POINTER_FILE") != nullptr;
|
||||
if (trace && event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
|
||||
std::printf("Menu pointer: button %d down (taken %d: captured %d, overlay %d, Atlas tile %d, "
|
||||
"menu %d)\n",
|
||||
int(event.button.button), ours ? 1 : 0, bbgpu_mouse_captured() ? 1 : 0,
|
||||
BbOverlay::CapturesInput() ? 1 : 0, atlas_tile ? 1 : 0, InMenu() ? 1 : 0);
|
||||
}
|
||||
static bool left_taken = false; ///< the left button's press was the pointer's: so is its release
|
||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT) {
|
||||
left_held.store(false, std::memory_order_relaxed);
|
||||
repeat_press.store(0, std::memory_order_relaxed);
|
||||
return ours ? 1 : 0;
|
||||
const bool taken = left_taken;
|
||||
left_taken = false;
|
||||
return taken ? 1 : 0;
|
||||
}
|
||||
if (!ours) {
|
||||
return 0;
|
||||
}
|
||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
|
||||
// Only the left button is the pointer's. The others keep their bindings in a menu too
|
||||
// (runtime_pad/sample.c), as the game's own prompts show them: the right button is L2, which
|
||||
// switches the storage box's pages (#44); it used to be the way back, and with it nothing
|
||||
// could press L2 in a menu. Escape is the way back.
|
||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_LEFT) {
|
||||
SetPointer(window, event.button.x, event.button.y);
|
||||
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||
left_held.store(true, std::memory_order_relaxed);
|
||||
click_ms.store(NowMs(), std::memory_order_relaxed);
|
||||
click.store(true, std::memory_order_relaxed);
|
||||
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
||||
presses.fetch_or(BackButton(), std::memory_order_relaxed);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_UP) {
|
||||
left_held.store(true, std::memory_order_relaxed);
|
||||
click_ms.store(NowMs(), std::memory_order_relaxed);
|
||||
click.store(true, std::memory_order_relaxed);
|
||||
left_taken = true;
|
||||
return 1;
|
||||
}
|
||||
if (event.type == SDL_EVENT_MOUSE_WHEEL) {
|
||||
@@ -636,7 +662,7 @@ int OnEvent(const void* sdl_event, void* sdl_window) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// BB_MENU_POINTER_FILE=<file> (tests): "x y [click|right|up|down]" in stage pixels, applied
|
||||
/// BB_MENU_POINTER_FILE=<file> (tests): "x y [click|back|up|down]" in stage pixels, applied
|
||||
/// when the file changes, as the mouse would.
|
||||
void ReadTestFile() {
|
||||
static const char* path = bb_env("BB_MENU_POINTER_FILE");
|
||||
@@ -668,7 +694,7 @@ void ReadTestFile() {
|
||||
if (!std::strcmp(action, "click")) {
|
||||
click_ms.store(now, std::memory_order_relaxed);
|
||||
click.store(true, std::memory_order_relaxed);
|
||||
} else if (!std::strcmp(action, "right")) {
|
||||
} else if (!std::strcmp(action, "back")) {
|
||||
presses.fetch_or(BackButton(), std::memory_order_relaxed);
|
||||
} else if (!std::strcmp(action, "up") || !std::strcmp(action, "down")) {
|
||||
presses.fetch_or(action[0] == 'u' ? Up : Down, std::memory_order_relaxed);
|
||||
|
||||
@@ -71,6 +71,7 @@ bool Interacting(){return visible || BbMap::IsOpen() || runtime_map_menu_open();
|
||||
bool UsesControllerGlyphs(){const char* qa=bb_env("BB_ATLAS_QA_DEVICE");return bb_env("BB_ATLAS_QA")&&qa?std::strcmp(qa,"controller")==0:bool(controller);}
|
||||
bool UsesPlayStationGlyphs(){const auto type=SDL_GetGamepadTypeForID(last_controller.load());return type==SDL_GAMEPAD_TYPE_PS3||type==SDL_GAMEPAD_TYPE_PS4||type==SDL_GAMEPAD_TYPE_PS5;}
|
||||
bool Focused(){return focused;}
|
||||
bool Hovered(){return hovered;}
|
||||
void Suppress(bool value){suppressed=value;if(value){visible=false;state.Reset();}}
|
||||
void Reset(){state.Reset();focused=false;visible=false;hovered=false;mouse_held=false;map_previous=0;}
|
||||
void Tick(){Refresh();if(BbMap::IsOpen()&&!runtime_map_menu_open()&&state.focused){BbMap::Close();Reset();}}
|
||||
|
||||
@@ -8,6 +8,8 @@ bool Interacting();
|
||||
bool UsesControllerGlyphs();
|
||||
bool UsesPlayStationGlyphs();
|
||||
bool Focused();
|
||||
/// The pointer is over the tile (the game's own menu pointer leaves its clicks to it).
|
||||
bool Hovered();
|
||||
void Suppress(bool value);
|
||||
void Tick();
|
||||
void Draw();
|
||||
|
||||
@@ -60,6 +60,11 @@ private:
|
||||
u64 last_mouse_motion_ms{}; ///< SDL_GetTicks of the last mouse motion (UpdateCursor)
|
||||
bool cursor_hidden{};
|
||||
u64 capture_settle_ms{}; ///< mouse motion before this (SDL_GetTicks) is not looking around
|
||||
/// Issue #39: fullscreen asked again on Wayland when the compositor did not keep it (FixFullscreen).
|
||||
bool fullscreen_wanted{};
|
||||
u64 fullscreen_since_ms{}, fullscreen_tried_ms{};
|
||||
int fullscreen_tries{};
|
||||
void FixFullscreen();
|
||||
bool pointer_shown{}; ///< a game module's pointer menu is open (bbgpu_pointer_wanted)
|
||||
bool released_for_pointer{}; ///< the mouse was let go for it: taken back after
|
||||
void CaptureMouse(bool capture);
|
||||
|
||||
+30
-2
@@ -223,8 +223,8 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, const char* title) : width{width_}
|
||||
if (gamescope && !(fullscreen && fullscreen[0] == '1')) {
|
||||
std::printf("Display: gamescope: fullscreen\n");
|
||||
}
|
||||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN,
|
||||
!placed && (gamescope || (fullscreen && fullscreen[0] == '1')));
|
||||
fullscreen_wanted = !placed && (gamescope || (fullscreen && fullscreen[0] == '1'));
|
||||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, fullscreen_wanted);
|
||||
// Issue #234: the swapchain at the display's pixels on a scaled desktop (Wayland 200%), not at
|
||||
// the logical size stretched by the compositor. Sizes below are read in pixels already.
|
||||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
|
||||
@@ -249,6 +249,7 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, const char* title) : width{width_}
|
||||
window_info.render_surface = reinterpret_cast<void*>(SDL_GetNumberProperty(wp, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0));
|
||||
} else if (driver && !std::strcmp(driver, "wayland")) {
|
||||
window_info.type = WindowSystemType::Wayland;
|
||||
fullscreen_since_ms = SDL_GetTicks();
|
||||
window_info.display_connection = SDL_GetPointerProperty(wp, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr);
|
||||
window_info.render_surface = SDL_GetPointerProperty(wp, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr);
|
||||
} else {
|
||||
@@ -519,9 +520,36 @@ bool WindowSDL::PollEvents() {
|
||||
}
|
||||
pointer_shown = pointer;
|
||||
UpdateCursor();
|
||||
FixFullscreen();
|
||||
return is_open;
|
||||
}
|
||||
|
||||
// Issue #39: on Wayland a program cannot place its window. With several monitors KWin can map
|
||||
// it on another one first (the one SDL calls primary, not KDE's) and move it to the monitor
|
||||
// asked for, and the window is left there as a plain window. Asked again, fullscreen holds (as
|
||||
// KDE's own shortcut did). Only in the first seconds and a few times: there is no fullscreen
|
||||
// toggle in the game, but the desktop's own shortcuts must keep working after that.
|
||||
void WindowSDL::FixFullscreen() {
|
||||
constexpr u64 FirstMs = 10000, BetweenMs = 1000;
|
||||
constexpr int MaxTries = 3;
|
||||
if (!fullscreen_wanted || !fullscreen_since_ms || fullscreen_tries >= MaxTries) {
|
||||
return;
|
||||
}
|
||||
const u64 now = SDL_GetTicks();
|
||||
if (now - fullscreen_since_ms > FirstMs) {
|
||||
fullscreen_since_ms = 0; // past the start: the desktop decides from here
|
||||
return;
|
||||
}
|
||||
if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) || now - fullscreen_tried_ms < BetweenMs) {
|
||||
return;
|
||||
}
|
||||
fullscreen_tried_ms = now;
|
||||
++fullscreen_tries;
|
||||
std::printf("Display: the compositor left the window out of fullscreen, asked again (%d of %d)\n",
|
||||
fullscreen_tries, MaxTries);
|
||||
SDL_SetWindowFullscreen(window, true);
|
||||
}
|
||||
|
||||
// Issue #3: the OS cursor over the game. Hidden in fullscreen, and in a window after 3 s without
|
||||
// moving the mouse; always shown while the settings menu or a game's pointer menu is open.
|
||||
void WindowSDL::UpdateCursor() {
|
||||
|
||||
@@ -251,9 +251,23 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_fmt) {
|
||||
if (exact_fmt && info.pixel_format != resolved.guest_info.pixel_format) {
|
||||
image_id = {}; // the very format is needed
|
||||
} else if (resolved.guest_info.resources < info.resources) {
|
||||
// bbport (Linux fork): which images, and whether the dropped one held what the GPU
|
||||
// drew (then lost: the new image is read from guest memory). Issue #25: a texture
|
||||
// garbled until the game was restarted.
|
||||
const ImageInfo& had = resolved.guest_info;
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Image overlap resolve failed: wanted {:#x}+{:#x} {}x{} {} levels {} layers {}; "
|
||||
"dropped {:#x}+{:#x} {}x{} {} levels {} layers {}{}",
|
||||
info.guest_address, info.guest_size, info.size.width, info.size.height,
|
||||
vk::to_string(info.pixel_format), info.resources.levels, info.resources.layers,
|
||||
had.guest_address, had.guest_size, had.size.width, had.size.height,
|
||||
vk::to_string(had.pixel_format), had.resources.levels, had.resources.layers,
|
||||
True(resolved.flags & ImageFlagBits::GpuModified)
|
||||
? (resolved.SafeToDownload() ? ", drawn by the GPU (its contents are lost)"
|
||||
: ", drawn by the GPU, then written")
|
||||
: "");
|
||||
FreeImage(image_id); // picked up wrongly
|
||||
image_id = {};
|
||||
LOG_WARNING(Render_Vulkan, "Image overlap resolve failed");
|
||||
}
|
||||
}
|
||||
if (!image_id) {
|
||||
|
||||
@@ -14,7 +14,7 @@ import re
|
||||
import tempfile
|
||||
import urllib.request
|
||||
|
||||
VERSION = "0.70-linux.2"
|
||||
VERSION = "0.70-linux.3"
|
||||
|
||||
|
||||
def _build():
|
||||
|
||||
@@ -89,6 +89,43 @@ EOF
|
||||
chmod +x "$appdir/bin/bloodborne"
|
||||
sed -e 's|@LAUNCHER@|bloodborne|' -e 's|@ICON@|bloodborne|' \
|
||||
launcher/io.github.bbport.Launcher.desktop.in > "$work/bloodborne.desktop"
|
||||
# The version and a description for AppImage managers (Gear Lever showed "UNKNOWN", #49): the
|
||||
# desktop entry's X-AppImage-Version, which appimagetool writes from VERSION (it replaces one in
|
||||
# the entry), and AppStream metainfo named after the desktop entry. The version is the
|
||||
# launcher's own (bbport_update.py), so it is kept in one place.
|
||||
version=$(sed -n 's/^VERSION = "\(.*\)"$/\1/p' launcher/bbport_update.py)
|
||||
[[ -n $version ]] || { echo "No VERSION in launcher/bbport_update.py" >&2; exit 1; }
|
||||
export VERSION=$version
|
||||
mkdir -p "$appdir/share/metainfo"
|
||||
cat > "$appdir/share/metainfo/bloodborne.appdata.xml" <<METAINFO
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>io.github.ayozetr.bloodborne_linux</id>
|
||||
<name>Bloodborne (bbport)</name>
|
||||
<developer id="io.github.ayozetr">
|
||||
<name>bloodborne_linux contributors</name>
|
||||
</developer>
|
||||
<summary>Native Linux port launcher for your own copy of Bloodborne</summary>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0-or-later</project_license>
|
||||
<description>
|
||||
<p>Runs Bloodborne natively on Linux from your own decrypted dump of the PS4 game with update
|
||||
1.09. No game files are included. Upscaling (DLSS, FSR), online play, mods and the community
|
||||
patches are set up from the launcher, which also updates itself.</p>
|
||||
</description>
|
||||
<launchable type="desktop-id">bloodborne.desktop</launchable>
|
||||
<url type="homepage">https://github.com/ayozetr/bloodborne_linux</url>
|
||||
<url type="bugtracker">https://github.com/ayozetr/bloodborne_linux/issues</url>
|
||||
<categories><category>Game</category></categories>
|
||||
<content_rating type="oars-1.1">
|
||||
<content_attribute id="violence-fantasy">intense</content_attribute>
|
||||
<content_attribute id="violence-bloodshed">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="$version" date="$(date +%F)"/>
|
||||
</releases>
|
||||
</component>
|
||||
METAINFO
|
||||
|
||||
# GTK, libadwaita and their typelibs are loaded by PyGObject at run time (not linked): named here.
|
||||
# So are Python's ssl and hashlib modules, which quick-sharun leaves out by default: the launcher's
|
||||
|
||||
@@ -101,7 +101,12 @@ static void sample_host(PadData *d) {
|
||||
* it) and sample_atlas() gives the game the neutral input. */
|
||||
if (bbgpu_overlay_captures_input() && !atlas_on()) { apply_mouse_look(d,0,0,d->timestamp,0); return; }
|
||||
const bool *k=SDL_WasInit(SDL_INIT_VIDEO) ? SDL_GetKeyboardState(NULL) : NULL;
|
||||
mouse_buttons=captured && SDL_WasInit(SDL_INIT_VIDEO) ? SDL_GetMouseState(NULL,NULL) : 0;
|
||||
/* In a game's pointer menu (the mouse released) the buttons count too, but the left one, which
|
||||
* is the pointer's click (gpu/games/bloodborne_menu_pointer.cpp): the right one is L2 there as
|
||||
* the game's prompts show it (the storage box's pages, #44). */
|
||||
const int pointer_menu=!captured && bbgpu_pointer_wanted();
|
||||
mouse_buttons=(captured || pointer_menu) && SDL_WasInit(SDL_INIT_VIDEO) ? SDL_GetMouseState(NULL,NULL) : 0;
|
||||
if (!captured) mouse_buttons&=~SDL_BUTTON_LMASK;
|
||||
const int pad_only=steam_pad_only(g) || !keyboard_enabled();
|
||||
atomic_store_explicit(&steam_only_now,pad_only,memory_order_relaxed);
|
||||
if (pad_only) {
|
||||
|
||||
Reference in new issue
Block a user