Merge branch 'shadps4-emu:main' into disable-heap-malloc
5
.github/linux-appimage-qt.sh
vendored
|
@ -19,12 +19,13 @@ chmod a+x linuxdeploy-x86_64.AppImage
|
|||
chmod a+x linuxdeploy-plugin-qt-x86_64.AppImage
|
||||
chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh
|
||||
|
||||
|
||||
# Build AppImage
|
||||
./linuxdeploy-x86_64.AppImage --appdir AppDir
|
||||
./linuxdeploy-plugin-checkrt-x86_64.sh --appdir AppDir
|
||||
|
||||
cp -a "$GITHUB_WORKSPACE/build/translations" AppDir/usr/bin
|
||||
|
||||
./linuxdeploy-x86_64.AppImage --appdir AppDir -d "$GITHUB_WORKSPACE"/.github/shadps4.desktop -e "$GITHUB_WORKSPACE"/build/shadps4 -i "$GITHUB_WORKSPACE"/.github/shadps4.png --plugin qt --output appimage
|
||||
./linuxdeploy-x86_64.AppImage --appdir AppDir -d "$GITHUB_WORKSPACE"/.github/shadps4.desktop -e "$GITHUB_WORKSPACE"/build/shadps4 -i "$GITHUB_WORKSPACE"/.github/shadps4.png --plugin qt
|
||||
rm AppDir/usr/plugins/multimedia/libgstreamermediaplugin.so
|
||||
./linuxdeploy-x86_64.AppImage --appdir AppDir --output appimage
|
||||
mv Shadps4-x86_64.AppImage Shadps4-qt.AppImage
|
||||
|
|
8
.github/workflows/build.yml
vendored
|
@ -9,6 +9,10 @@ on:
|
|||
pull_request:
|
||||
branches: [ "*" ]
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'push' }}
|
||||
|
||||
env:
|
||||
BUILD_TYPE: Release
|
||||
|
||||
|
@ -287,7 +291,7 @@ jobs:
|
|||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential
|
||||
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential libasound2-dev libpulse-dev libopenal-dev
|
||||
|
||||
- name: Cache CMake Configuration
|
||||
uses: actions/cache@v4
|
||||
|
@ -343,7 +347,7 @@ jobs:
|
|||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev
|
||||
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev
|
||||
|
||||
- name: Cache CMake Configuration
|
||||
uses: actions/cache@v4
|
||||
|
|
|
@ -8,7 +8,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED True)
|
|||
|
||||
if(APPLE)
|
||||
enable_language(OBJC)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 11)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 14)
|
||||
endif()
|
||||
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
|
|
37
README.md
|
@ -34,7 +34,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
|
||||
# General information
|
||||
|
||||
shadPS4 is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++.
|
||||
**shadPS4** is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++.
|
||||
|
||||
If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/Quickstart.md).
|
||||
|
||||
|
@ -44,7 +44,7 @@ To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Di
|
|||
|
||||
To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/).
|
||||
|
||||
For those who'd like to donate to the project, we now have a [Kofi page!](https://ko-fi.com/shadps4)
|
||||
For those who'd like to donate to the project, we now have a [**Kofi page**](https://ko-fi.com/shadps4)!
|
||||
|
||||
# Status
|
||||
|
||||
|
@ -72,40 +72,11 @@ Check the build instructions for [**Linux**](https://github.com/shadps4-emu/shad
|
|||
Check the build instructions for [**macOS**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-macos.md).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> macOS users need at least macOS 15 on Apple Silicon-based Mac devices and at least macOS 11 on Intel-based Mac devices.
|
||||
|
||||
## Building status
|
||||
|
||||
<details>
|
||||
<summary><b>Windows</b></summary>
|
||||
|
||||
| Windows | Build status |
|
||||
|--------|--------|
|
||||
|Windows SDL Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows.yml)
|
||||
|Windows Qt Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows-qt.yml)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Linux</b></summary>
|
||||
|
||||
| Linux | Build status |
|
||||
|--------|--------|
|
||||
|Linux SDL Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux.yml)
|
||||
|Linux Qt Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux-qt.yml)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS</b></summary>
|
||||
|
||||
| macOS | Build status |
|
||||
|--------|--------|
|
||||
|macOS SDL Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos.yml)
|
||||
|macOS Qt Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos-qt.yml)
|
||||
</details>
|
||||
> macOS users need at least macOS 15 on Apple Silicon-based Mac devices and at least macOS 14 on Intel-based Mac devices.
|
||||
|
||||
# Debugging and reporting issues
|
||||
|
||||
For more information on how to test, debug and report issues with the emulator or games, read the [Debugging documentation](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md).
|
||||
For more information on how to test, debug and report issues with the emulator or games, read the [**Debugging documentation**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md).
|
||||
|
||||
# Keyboard mapping
|
||||
|
||||
|
|
|
@ -3,28 +3,28 @@ SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
|||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
-->
|
||||
|
||||
## Build shadPS4 for Linux
|
||||
## Build shadPS4 for Linux
|
||||
|
||||
### Install the necessary tools to build shadPS4:
|
||||
|
||||
#### Debian & Ubuntu
|
||||
```
|
||||
sudo apt-get install build-essential libasound2-dev libpulse-dev libopenal-dev zlib1g-dev libedit-dev libvulkan-dev libudev-dev git libevdev-dev libsdl2-2.0 libsdl2-dev libjack-dev libsndio-dev qt6-base-dev qt6-tools-dev
|
||||
sudo apt install build-essential clang git cmake libasound2-dev libpulse-dev libopenal-dev libssl-dev zlib1g-dev libedit-dev libudev-dev libevdev-dev libsdl2-dev libjack-dev libsndio-dev qt6-base-dev qt6-tools-dev qt6-multimedia-dev libvulkan-dev vulkan-validationlayers
|
||||
```
|
||||
|
||||
#### Fedora
|
||||
```
|
||||
sudo dnf install alsa-lib-devel cmake libatomic libevdev-devel libudev-devel openal-devel qt6-qtbase-devel qt6-qtbase-private-devel vulkan-devel pipewire-jack-audio-connection-kit-devel qt6-qtmultimedia-devel qt6-qtsvg-devel
|
||||
sudo dnf install clang git cmake libatomic alsa-lib-devel pipewire-jack-audio-connection-kit-devel openal-devel openssl-devel libevdev-devel libudev-devel libXext-devel qt6-qtbase-devel qt6-qtbase-private-devel qt6-qtmultimedia-devel qt6-qtsvg-devel qt6-qttools-devel vulkan-devel vulkan-validation-layers
|
||||
```
|
||||
|
||||
#### Arch Linux
|
||||
```
|
||||
sudo pacman -S openal cmake vulkan-validation-layers qt6-base qt6-declarative qt6-multimedia sdl2 sndio jack2 base-devel
|
||||
sudo pacman -S base-devel clang git cmake sndio jack2 openal qt6-base qt6-declarative qt6-multimedia sdl2 vulkan-validation-layers
|
||||
```
|
||||
|
||||
#### OpenSUSE
|
||||
```
|
||||
sudo zypper install git cmake libasound2 libpulse-devel openal-soft-devel zlib-devel libedit-devel vulkan-devel libudev-devel libqt6-qtbase-devel libqt6-qtmultimedia-devel libqt6-qtsvg-devel libQt6Gui-private-headers-devel libevdev-devel libsndio7_1 libjack-devel
|
||||
sudo zypper install clang git cmake libasound2 libpulse-devel libsndio7 libjack-devel openal-soft-devel libopenssl-devel zlib-devel libedit-devel systemd-devel libevdev-devel qt6-base-devel qt6-multimedia-devel qt6-svg-devel qt6-linguist-devel qt6-gui-private-devel vulkan-devel vulkan-validationlayers
|
||||
```
|
||||
### Cloning and compiling:
|
||||
|
||||
|
@ -34,9 +34,11 @@ git clone --recursive https://github.com/shadps4-emu/shadPS4.git
|
|||
cd shadPS4
|
||||
```
|
||||
|
||||
Generate the build directory in the shadPS4 directory. To enable the QT GUI, pass the ```-DENABLE_QT_GUI=ON``` flag:
|
||||
Generate the build directory in the shadPS4 directory. To disable the QT GUI, remove the ```-DENABLE_QT_GUI=ON``` flag:
|
||||
|
||||
**Note**: Clang is the compiler used for official builds and CI. If you build with GCC, you might encounter issues—please report any you find. If you choose to use GCC, we recommend building with Clang at least once before submitting a pull request.
|
||||
```
|
||||
cmake -S . -B build/ -DENABLE_QT_GUI=ON
|
||||
cmake -S . -B build/ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
|
||||
```
|
||||
|
||||
Enter the directory:
|
||||
|
|
|
@ -100,15 +100,16 @@ s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) {
|
|||
if (ptr == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
lock.unlock();
|
||||
// TODO mixing channels
|
||||
int result = SDL_PutAudioStreamData(port.stream, ptr,
|
||||
port.samples_num * port.sample_size * port.channels_num);
|
||||
SDL_bool result = SDL_PutAudioStreamData(
|
||||
port.stream, ptr, port.samples_num * port.sample_size * port.channels_num);
|
||||
// TODO find a correct value 8192 is estimated
|
||||
while (SDL_GetAudioStreamAvailable(port.stream) > 65536) {
|
||||
SDL_Delay(0);
|
||||
}
|
||||
|
||||
return result;
|
||||
return result ? ORBIS_OK : -1;
|
||||
}
|
||||
|
||||
bool SDLAudio::AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume) {
|
||||
|
|
|
@ -28,4 +28,16 @@ template <typename T>
|
|||
return (value & 0x3FFF) == 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires std::is_integral_v<T>
|
||||
[[nodiscard]] constexpr bool Is64KBAligned(T value) {
|
||||
return (value & 0xFFFF) == 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires std::is_integral_v<T>
|
||||
[[nodiscard]] constexpr bool Is2MBAligned(T value) {
|
||||
return (value & 0x1FFFFF) == 0;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <fmt/xchar.h> // for wstring support
|
||||
#include <toml.hpp>
|
||||
#include "common/logging/formatter.h"
|
||||
#include "common/path_util.h"
|
||||
#include "config.h"
|
||||
|
||||
namespace toml {
|
||||
|
@ -32,6 +33,7 @@ namespace Config {
|
|||
static bool isNeo = false;
|
||||
static bool isFullscreen = false;
|
||||
static bool playBGM = false;
|
||||
static int BGMvolume = 50;
|
||||
static u32 screenWidth = 1280;
|
||||
static u32 screenHeight = 720;
|
||||
static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select
|
||||
|
@ -58,6 +60,7 @@ static bool vkCrashDiagnostic = false;
|
|||
|
||||
// Gui
|
||||
std::filesystem::path settings_install_dir = {};
|
||||
std::filesystem::path settings_addon_install_dir = {};
|
||||
u32 main_window_geometry_x = 400;
|
||||
u32 main_window_geometry_y = 400;
|
||||
u32 main_window_geometry_w = 1280;
|
||||
|
@ -89,6 +92,10 @@ bool getPlayBGM() {
|
|||
return playBGM;
|
||||
}
|
||||
|
||||
int getBGMvolume() {
|
||||
return BGMvolume;
|
||||
}
|
||||
|
||||
u32 getScreenWidth() {
|
||||
return screenWidth;
|
||||
}
|
||||
|
@ -249,6 +256,10 @@ void setPlayBGM(bool enable) {
|
|||
playBGM = enable;
|
||||
}
|
||||
|
||||
void setBGMvolume(int volume) {
|
||||
BGMvolume = volume;
|
||||
}
|
||||
|
||||
void setLanguage(u32 language) {
|
||||
m_language = language;
|
||||
}
|
||||
|
@ -290,6 +301,9 @@ void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) {
|
|||
void setGameInstallDir(const std::filesystem::path& dir) {
|
||||
settings_install_dir = dir;
|
||||
}
|
||||
void setAddonInstallDir(const std::filesystem::path& dir) {
|
||||
settings_addon_install_dir = dir;
|
||||
}
|
||||
void setMainWindowTheme(u32 theme) {
|
||||
mw_themes = theme;
|
||||
}
|
||||
|
@ -346,6 +360,13 @@ u32 getMainWindowGeometryH() {
|
|||
std::filesystem::path getGameInstallDir() {
|
||||
return settings_install_dir;
|
||||
}
|
||||
std::filesystem::path getAddonInstallDir() {
|
||||
if (settings_addon_install_dir.empty()) {
|
||||
// Default for users without a config file or a config file from before this option existed
|
||||
return Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "addcont";
|
||||
}
|
||||
return settings_addon_install_dir;
|
||||
}
|
||||
u32 getMainWindowTheme() {
|
||||
return mw_themes;
|
||||
}
|
||||
|
@ -412,6 +433,7 @@ void load(const std::filesystem::path& path) {
|
|||
isNeo = toml::find_or<bool>(general, "isPS4Pro", false);
|
||||
isFullscreen = toml::find_or<bool>(general, "Fullscreen", false);
|
||||
playBGM = toml::find_or<bool>(general, "playBGM", false);
|
||||
BGMvolume = toml::find_or<int>(general, "BGMvolume", 50);
|
||||
logFilter = toml::find_or<std::string>(general, "logFilter", "");
|
||||
logType = toml::find_or<std::string>(general, "logType", "sync");
|
||||
userName = toml::find_or<std::string>(general, "userName", "shadPS4");
|
||||
|
@ -472,6 +494,7 @@ void load(const std::filesystem::path& path) {
|
|||
m_window_size_W = toml::find_or<int>(gui, "mw_width", 0);
|
||||
m_window_size_H = toml::find_or<int>(gui, "mw_height", 0);
|
||||
settings_install_dir = toml::find_fs_path_or(gui, "installDir", {});
|
||||
settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {});
|
||||
main_window_geometry_x = toml::find_or<int>(gui, "geometry_x", 0);
|
||||
main_window_geometry_y = toml::find_or<int>(gui, "geometry_y", 0);
|
||||
main_window_geometry_w = toml::find_or<int>(gui, "geometry_w", 0);
|
||||
|
@ -513,6 +536,7 @@ void save(const std::filesystem::path& path) {
|
|||
data["General"]["isPS4Pro"] = isNeo;
|
||||
data["General"]["Fullscreen"] = isFullscreen;
|
||||
data["General"]["playBGM"] = playBGM;
|
||||
data["General"]["BGMvolume"] = BGMvolume;
|
||||
data["General"]["logFilter"] = logFilter;
|
||||
data["General"]["logType"] = logType;
|
||||
data["General"]["userName"] = userName;
|
||||
|
@ -545,6 +569,8 @@ void save(const std::filesystem::path& path) {
|
|||
data["GUI"]["mw_width"] = m_window_size_W;
|
||||
data["GUI"]["mw_height"] = m_window_size_H;
|
||||
data["GUI"]["installDir"] = std::string{fmt::UTF(settings_install_dir.u8string()).data};
|
||||
data["GUI"]["addonInstallDir"] =
|
||||
std::string{fmt::UTF(settings_addon_install_dir.u8string()).data};
|
||||
data["GUI"]["geometry_x"] = main_window_geometry_x;
|
||||
data["GUI"]["geometry_y"] = main_window_geometry_y;
|
||||
data["GUI"]["geometry_w"] = main_window_geometry_w;
|
||||
|
@ -565,6 +591,7 @@ void setDefaultValues() {
|
|||
isNeo = false;
|
||||
isFullscreen = false;
|
||||
playBGM = false;
|
||||
BGMvolume = 50;
|
||||
screenWidth = 1280;
|
||||
screenHeight = 720;
|
||||
logFilter = "";
|
||||
|
|
|
@ -14,6 +14,8 @@ void save(const std::filesystem::path& path);
|
|||
bool isNeoMode();
|
||||
bool isFullscreenMode();
|
||||
bool getPlayBGM();
|
||||
int getBGMvolume();
|
||||
|
||||
std::string getLogFilter();
|
||||
std::string getLogType();
|
||||
std::string getUserName();
|
||||
|
@ -49,6 +51,7 @@ void setScreenWidth(u32 width);
|
|||
void setScreenHeight(u32 height);
|
||||
void setFullscreenMode(bool enable);
|
||||
void setPlayBGM(bool enable);
|
||||
void setBGMvolume(int volume);
|
||||
void setLanguage(u32 language);
|
||||
void setNeoMode(bool enable);
|
||||
void setUserName(const std::string& type);
|
||||
|
@ -73,6 +76,7 @@ bool vkCrashDiagnosticEnabled();
|
|||
// Gui
|
||||
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h);
|
||||
void setGameInstallDir(const std::filesystem::path& dir);
|
||||
void setAddonInstallDir(const std::filesystem::path& dir);
|
||||
void setMainWindowTheme(u32 theme);
|
||||
void setIconSize(u32 size);
|
||||
void setIconSizeGrid(u32 size);
|
||||
|
@ -91,6 +95,7 @@ u32 getMainWindowGeometryY();
|
|||
u32 getMainWindowGeometryW();
|
||||
u32 getMainWindowGeometryH();
|
||||
std::filesystem::path getGameInstallDir();
|
||||
std::filesystem::path getAddonInstallDir();
|
||||
u32 getMainWindowTheme();
|
||||
u32 getIconSize();
|
||||
u32 getIconSizeGrid();
|
||||
|
|
|
@ -192,8 +192,9 @@ int IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, FileS
|
|||
#endif
|
||||
|
||||
if (!IsOpen()) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}",
|
||||
PathToUTF8String(file_path));
|
||||
const auto ec = std::error_code{result, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}, error_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -372,6 +373,18 @@ bool IOFile::Seek(s64 offset, SeekOrigin origin) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
u64 size = GetSize();
|
||||
if (origin == SeekOrigin::CurrentPosition && Tell() + offset > size) {
|
||||
LOG_ERROR(Common_Filesystem, "Seeking past the end of the file");
|
||||
return false;
|
||||
} else if (origin == SeekOrigin::SetOrigin && (u64)offset > size) {
|
||||
LOG_ERROR(Common_Filesystem, "Seeking past the end of the file");
|
||||
return false;
|
||||
} else if (origin == SeekOrigin::End && offset > 0) {
|
||||
LOG_ERROR(Common_Filesystem, "Seeking past the end of the file");
|
||||
return false;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
|
||||
const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0;
|
||||
|
|
|
@ -119,7 +119,6 @@ static auto UserPaths = [] {
|
|||
create_path(PathType::CapturesDir, user_dir / CAPTURES_DIR);
|
||||
create_path(PathType::CheatsDir, user_dir / CHEATS_DIR);
|
||||
create_path(PathType::PatchesDir, user_dir / PATCHES_DIR);
|
||||
create_path(PathType::AddonsDir, user_dir / ADDONS_DIR);
|
||||
create_path(PathType::MetaDataDir, user_dir / METADATA_DIR);
|
||||
|
||||
return paths;
|
||||
|
|
|
@ -26,7 +26,6 @@ enum class PathType {
|
|||
CapturesDir, // Where rdoc captures are stored.
|
||||
CheatsDir, // Where cheats are stored.
|
||||
PatchesDir, // Where patches are stored.
|
||||
AddonsDir, // Where additional content is stored.
|
||||
MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored.
|
||||
};
|
||||
|
||||
|
@ -45,7 +44,6 @@ constexpr auto DOWNLOAD_DIR = "download";
|
|||
constexpr auto CAPTURES_DIR = "captures";
|
||||
constexpr auto CHEATS_DIR = "cheats";
|
||||
constexpr auto PATCHES_DIR = "patches";
|
||||
constexpr auto ADDONS_DIR = "addcont";
|
||||
constexpr auto METADATA_DIR = "game_data";
|
||||
|
||||
// Filenames
|
||||
|
|
|
@ -1057,7 +1057,7 @@ static bool TryExecuteIllegalInstruction(void* ctx, void* code_address) {
|
|||
// Undefined behavior if length + index is bigger than 64 according to the spec,
|
||||
// we'll warn and continue execution.
|
||||
LOG_WARNING(Core,
|
||||
"extrq at {:x} with length {} and index {} is bigger than 64, "
|
||||
"extrq at {} with length {} and index {} is bigger than 64, "
|
||||
"undefined behavior",
|
||||
fmt::ptr(code_address), length, index);
|
||||
}
|
||||
|
@ -1117,7 +1117,7 @@ static bool TryExecuteIllegalInstruction(void* ctx, void* code_address) {
|
|||
// Undefined behavior if length + index is bigger than 64 according to the spec,
|
||||
// we'll warn and continue execution.
|
||||
LOG_WARNING(Core,
|
||||
"insertq at {:x} with length {} and index {} is bigger than 64, "
|
||||
"insertq at {} with length {} and index {} is bigger than 64, "
|
||||
"undefined behavior",
|
||||
fmt::ptr(code_address), length, index);
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ PKG::PKG() = default;
|
|||
|
||||
PKG::~PKG() = default;
|
||||
|
||||
bool PKG::Open(const std::filesystem::path& filepath) {
|
||||
bool PKG::Open(const std::filesystem::path& filepath, std::string& failreason) {
|
||||
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
|
||||
if (!file.IsOpen()) {
|
||||
return false;
|
||||
|
@ -70,7 +70,11 @@ bool PKG::Open(const std::filesystem::path& filepath) {
|
|||
u32 offset = pkgheader.pkg_table_entry_offset;
|
||||
u32 n_files = pkgheader.pkg_table_entry_count;
|
||||
|
||||
file.Seek(offset);
|
||||
if (!file.Seek(offset)) {
|
||||
failreason = "Failed to seek to PKG table entry offset";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_files; i++) {
|
||||
PKGEntry entry{};
|
||||
file.Read(entry.id);
|
||||
|
@ -85,7 +89,10 @@ bool PKG::Open(const std::filesystem::path& filepath) {
|
|||
const auto name = GetEntryNameByType(entry.id);
|
||||
if (name == "param.sfo") {
|
||||
sfo.clear();
|
||||
file.Seek(entry.offset);
|
||||
if (!file.Seek(entry.offset)) {
|
||||
failreason = "Failed to seek to param.sfo offset";
|
||||
return false;
|
||||
}
|
||||
sfo.resize(entry.size);
|
||||
file.ReadRaw<u8>(sfo.data(), entry.size);
|
||||
}
|
||||
|
@ -127,7 +134,11 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
|
|||
std::array<std::array<u8, 256>, 7> key1;
|
||||
std::array<u8, 256> imgkeydata;
|
||||
|
||||
file.Seek(offset);
|
||||
if (!file.Seek(offset)) {
|
||||
failreason = "Failed to seek to PKG table entry offset";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_files; i++) {
|
||||
PKGEntry entry{};
|
||||
file.Read(entry.id);
|
||||
|
@ -149,7 +160,10 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
|
|||
// Just print with id
|
||||
Common::FS::IOFile out(extract_path / "sce_sys" / std::to_string(entry.id),
|
||||
Common::FS::FileAccessMode::Write);
|
||||
file.Seek(entry.offset);
|
||||
if (!file.Seek(entry.offset)) {
|
||||
failreason = "Failed to seek to PKG entry offset";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<u8> data;
|
||||
data.resize(entry.size);
|
||||
|
@ -195,7 +209,10 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
|
|||
}
|
||||
|
||||
Common::FS::IOFile out(extract_path / "sce_sys" / name, Common::FS::FileAccessMode::Write);
|
||||
file.Seek(entry.offset);
|
||||
if (!file.Seek(entry.offset)) {
|
||||
failreason = "Failed to seek to PKG entry offset";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<u8> data;
|
||||
data.resize(entry.size);
|
||||
|
@ -207,7 +224,10 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
|
|||
if (entry.id == 0x400 || entry.id == 0x401 || entry.id == 0x402 ||
|
||||
entry.id == 0x403) { // somehow 0x401 is not decrypting
|
||||
decNp.resize(entry.size);
|
||||
file.Seek(entry.offset);
|
||||
if (!file.Seek(entry.offset)) {
|
||||
failreason = "Failed to seek to PKG entry offset";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<u8> data;
|
||||
data.resize(entry.size);
|
||||
|
@ -237,7 +257,10 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
|
|||
|
||||
// Read the seed
|
||||
std::array<u8, 16> seed;
|
||||
file.Seek(pkgheader.pfs_image_offset + 0x370);
|
||||
if (!file.Seek(pkgheader.pfs_image_offset + 0x370)) {
|
||||
failreason = "Failed to seek to PFS image offset";
|
||||
return false;
|
||||
}
|
||||
file.Read(seed);
|
||||
|
||||
// Get data and tweak keys.
|
||||
|
|
|
@ -103,7 +103,7 @@ public:
|
|||
PKG();
|
||||
~PKG();
|
||||
|
||||
bool Open(const std::filesystem::path& filepath);
|
||||
bool Open(const std::filesystem::path& filepath, std::string& failreason);
|
||||
void ExtractFiles(const int index);
|
||||
bool Extract(const std::filesystem::path& filepath, const std::filesystem::path& extract,
|
||||
std::string& failreason);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
#include "trp.h"
|
||||
|
||||
|
@ -13,7 +14,10 @@ void TRP::GetNPcommID(const std::filesystem::path& trophyPath, int index) {
|
|||
if (!npbindFile.IsOpen()) {
|
||||
return;
|
||||
}
|
||||
npbindFile.Seek(0x84 + (index * 0x180));
|
||||
if (!npbindFile.Seek(0x84 + (index * 0x180))) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Failed to seek to NPbind offset");
|
||||
return;
|
||||
}
|
||||
npbindFile.ReadRaw<u8>(np_comm_id.data(), 12);
|
||||
std::fill(np_comm_id.begin() + 12, np_comm_id.end(), 0); // fill with 0, we need 16 bytes.
|
||||
}
|
||||
|
@ -56,26 +60,38 @@ bool TRP::Extract(const std::filesystem::path& trophyPath) {
|
|||
std::filesystem::create_directory(trpFilesPath / "Xml");
|
||||
|
||||
for (int i = 0; i < header.entry_num; i++) {
|
||||
file.Seek(seekPos);
|
||||
if (!file.Seek(seekPos)) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
|
||||
return false;
|
||||
}
|
||||
seekPos += (s64)header.entry_size;
|
||||
TrpEntry entry;
|
||||
file.Read(entry);
|
||||
std::string_view name(entry.entry_name);
|
||||
if (entry.flag == 0 && name.find("TROP") != std::string::npos) { // PNG
|
||||
file.Seek(entry.entry_pos);
|
||||
if (!file.Seek(entry.entry_pos)) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
|
||||
return false;
|
||||
}
|
||||
std::vector<u8> icon(entry.entry_len);
|
||||
file.Read(icon);
|
||||
Common::FS::IOFile::WriteBytes(trpFilesPath / "Icons" / name, icon);
|
||||
}
|
||||
if (entry.flag == 3 && np_comm_id[0] == 'N' &&
|
||||
np_comm_id[1] == 'P') { // ESFM, encrypted.
|
||||
file.Seek(entry.entry_pos);
|
||||
if (!file.Seek(entry.entry_pos)) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
|
||||
return false;
|
||||
}
|
||||
file.Read(esfmIv); // get iv key.
|
||||
// Skip the first 16 bytes which are the iv key on every entry as we want a
|
||||
// clean xml file.
|
||||
std::vector<u8> ESFM(entry.entry_len - iv_len);
|
||||
std::vector<u8> XML(entry.entry_len - iv_len);
|
||||
file.Seek(entry.entry_pos + iv_len);
|
||||
if (!file.Seek(entry.entry_pos + iv_len)) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry + iv offset");
|
||||
return false;
|
||||
}
|
||||
file.Read(ESFM);
|
||||
crypto.decryptEFSM(np_comm_id, esfmIv, ESFM, XML); // decrypt
|
||||
removePadding(XML);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "app_content.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/io_file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
|
@ -59,8 +60,7 @@ int PS4_SYSV_ABI sceAppContentAddcontMount(u32 service_label,
|
|||
OrbisAppContentMountPoint* mount_point) {
|
||||
LOG_INFO(Lib_AppContent, "called");
|
||||
|
||||
const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) / title_id /
|
||||
entitlement_label->data;
|
||||
const auto& mount_dir = Config::getAddonInstallDir() / title_id / entitlement_label->data;
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
for (int i = 0; i < addcont_count; i++) {
|
||||
|
@ -246,7 +246,7 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar
|
|||
LOG_ERROR(Lib_AppContent, "(DUMMY) called");
|
||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||
|
||||
const auto addons_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir);
|
||||
const auto addons_dir = Config::getAddonInstallDir();
|
||||
if (const auto value = param_sfo->GetString("TITLE_ID"); value.has_value()) {
|
||||
title_id = *value;
|
||||
} else {
|
||||
|
|
|
@ -229,7 +229,10 @@ s64 PS4_SYSV_ABI sceKernelLseek(int d, s64 offset, int whence) {
|
|||
}
|
||||
|
||||
std::scoped_lock lk{file->m_mutex};
|
||||
file->f.Seek(offset, origin);
|
||||
if (!file->f.Seek(offset, origin)) {
|
||||
LOG_CRITICAL(Kernel_Fs, "sceKernelLseek: failed to seek");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
return file->f.Tell();
|
||||
}
|
||||
|
||||
|
@ -290,7 +293,8 @@ int PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) {
|
|||
}
|
||||
|
||||
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
|
||||
if (dir_name.empty() || !std::filesystem::create_directory(dir_name)) {
|
||||
std::error_code ec;
|
||||
if (dir_name.empty() || !std::filesystem::create_directory(dir_name, ec)) {
|
||||
return SCE_KERNEL_ERROR_EIO;
|
||||
}
|
||||
|
||||
|
@ -310,6 +314,58 @@ int PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
|
|||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelRmdir(const char* path) {
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
bool ro = false;
|
||||
|
||||
const std::filesystem::path dir_name = mnt->GetHostPath(path, &ro);
|
||||
|
||||
if (dir_name.empty()) {
|
||||
LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, permission denied",
|
||||
fmt::UTF(dir_name.u8string()));
|
||||
return SCE_KERNEL_ERROR_EACCES;
|
||||
}
|
||||
|
||||
if (ro) {
|
||||
LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, directory is read only",
|
||||
fmt::UTF(dir_name.u8string()));
|
||||
return SCE_KERNEL_ERROR_EROFS;
|
||||
}
|
||||
|
||||
if (!std::filesystem::is_directory(dir_name)) {
|
||||
LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, path is not a directory",
|
||||
fmt::UTF(dir_name.u8string()));
|
||||
return ORBIS_KERNEL_ERROR_ENOTDIR;
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(dir_name)) {
|
||||
LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, no such file or directory",
|
||||
fmt::UTF(dir_name.u8string()));
|
||||
return ORBIS_KERNEL_ERROR_ENOENT;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
int result = std::filesystem::remove_all(dir_name, ec);
|
||||
|
||||
if (!ec) {
|
||||
LOG_DEBUG(Kernel_Fs, "Removed directory: {}", fmt::UTF(dir_name.u8string()));
|
||||
return ORBIS_OK;
|
||||
}
|
||||
LOG_ERROR(Kernel_Fs, "Failed to remove directory: {}, error_code={}",
|
||||
fmt::UTF(dir_name.u8string()), ec.message());
|
||||
return ErrnoToSceKernelError(ec.value());
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_rmdir(const char* path) {
|
||||
int result = sceKernelRmdir(path);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Kernel_Pthread, "posix_rmdir: error = {}", result);
|
||||
ErrSceToPosix(result);
|
||||
return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) {
|
||||
LOG_INFO(Kernel_Fs, "(PARTIAL) path = {}", path);
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
@ -379,7 +435,10 @@ s64 PS4_SYSV_ABI sceKernelPread(int d, void* buf, size_t nbytes, s64 offset) {
|
|||
SCOPE_EXIT {
|
||||
file->f.Seek(pos);
|
||||
};
|
||||
file->f.Seek(offset);
|
||||
if (!file->f.Seek(offset)) {
|
||||
LOG_CRITICAL(Kernel_Fs, "sceKernelPread: failed to seek");
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
return file->f.ReadRaw<u8>(buf, nbytes);
|
||||
}
|
||||
|
||||
|
@ -513,7 +572,10 @@ s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) {
|
|||
SCOPE_EXIT {
|
||||
file->f.Seek(pos);
|
||||
};
|
||||
file->f.Seek(offset);
|
||||
if (!file->f.Seek(offset)) {
|
||||
LOG_CRITICAL(Kernel_Fs, "sceKernelPwrite: failed to seek");
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
return file->f.WriteRaw<u8>(buf, nbytes);
|
||||
}
|
||||
|
||||
|
@ -564,13 +626,19 @@ void fileSystemSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("AqBioC2vF3I", "libScePosix", 1, "libkernel", 1, 1, posix_read);
|
||||
LIB_FUNCTION("1-LFLmRFxxM", "libkernel", 1, "libkernel", 1, 1, sceKernelMkdir);
|
||||
LIB_FUNCTION("JGMio+21L4c", "libScePosix", 1, "libkernel", 1, 1, posix_mkdir);
|
||||
LIB_FUNCTION("JGMio+21L4c", "libkernel", 1, "libkernel", 1, 1, posix_mkdir);
|
||||
LIB_FUNCTION("naInUjYt3so", "libkernel", 1, "libkernel", 1, 1, sceKernelRmdir);
|
||||
LIB_FUNCTION("c7ZnT7V1B98", "libScePosix", 1, "libkernel", 1, 1, posix_rmdir);
|
||||
LIB_FUNCTION("c7ZnT7V1B98", "libkernel", 1, "libkernel", 1, 1, posix_rmdir);
|
||||
LIB_FUNCTION("eV9wAD2riIA", "libkernel", 1, "libkernel", 1, 1, sceKernelStat);
|
||||
LIB_FUNCTION("kBwCPsYX-m4", "libkernel", 1, "libkernel", 1, 1, sceKernelFStat);
|
||||
LIB_FUNCTION("mqQMh1zPPT8", "libScePosix", 1, "libkernel", 1, 1, posix_fstat);
|
||||
LIB_FUNCTION("mqQMh1zPPT8", "libkernel", 1, "libkernel", 1, 1, posix_fstat);
|
||||
LIB_FUNCTION("VW3TVZiM4-E", "libkernel", 1, "libkernel", 1, 1, sceKernelFtruncate);
|
||||
LIB_FUNCTION("52NcYU9+lEo", "libkernel", 1, "libkernel", 1, 1, sceKernelRename);
|
||||
|
||||
LIB_FUNCTION("E6ao34wPw+U", "libScePosix", 1, "libkernel", 1, 1, posix_stat);
|
||||
LIB_FUNCTION("E6ao34wPw+U", "libkernel", 1, "libkernel", 1, 1, posix_stat);
|
||||
LIB_FUNCTION("+r3rMFwItV4", "libkernel", 1, "libkernel", 1, 1, sceKernelPread);
|
||||
LIB_FUNCTION("uWyW3v98sU4", "libkernel", 1, "libkernel", 1, 1, sceKernelCheckReachability);
|
||||
LIB_FUNCTION("fTx66l5iWIA", "libkernel", 1, "libkernel", 1, 1, sceKernelFsync);
|
||||
|
|
|
@ -56,7 +56,7 @@ void KernelSignalRequest() {
|
|||
}
|
||||
|
||||
static void KernelServiceThread(std::stop_token stoken) {
|
||||
Common::SetCurrentThreadName("Kernel_ServiceThread");
|
||||
Common::SetCurrentThreadName("shadPS4:Kernel_ServiceThread");
|
||||
|
||||
while (!stoken.stop_requested()) {
|
||||
HLE_TRACE;
|
||||
|
@ -186,6 +186,16 @@ void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, int prot, int flags, int fd,
|
|||
return ptr;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelConfiguredFlexibleMemorySize(u64* sizeOut) {
|
||||
if (sizeOut == nullptr) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
auto* memory = Core::Memory::Instance();
|
||||
*sizeOut = memory->GetTotalFlexibleSize();
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
static uint64_t g_mspace_atomic_id_mask = 0;
|
||||
static uint64_t g_mstate_table[64] = {0};
|
||||
|
||||
|
@ -403,10 +413,12 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
|
|||
|
||||
// obj
|
||||
LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", 1, 1, &g_stack_chk_guard);
|
||||
|
||||
// misc
|
||||
LIB_FUNCTION("JGfTMBOdUJo", "libkernel", 1, "libkernel", 1, 1, sceKernelGetFsSandboxRandomWord);
|
||||
LIB_FUNCTION("XVL8So3QJUk", "libkernel", 1, "libkernel", 1, 1, posix_connect);
|
||||
LIB_FUNCTION("6xVpy0Fdq+I", "libkernel", 1, "libkernel", 1, 1, _sigprocmask);
|
||||
|
||||
// memory
|
||||
LIB_FUNCTION("OMDRKKAZ8I4", "libkernel", 1, "libkernel", 1, 1, sceKernelDebugRaiseException);
|
||||
LIB_FUNCTION("rTXw65xmLIA", "libkernel", 1, "libkernel", 1, 1, sceKernelAllocateDirectMemory);
|
||||
|
@ -443,6 +455,14 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("2SKEx6bSq-4", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap);
|
||||
LIB_FUNCTION("kBJzF8x4SyE", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap2);
|
||||
LIB_FUNCTION("DGMG3JshrZU", "libkernel", 1, "libkernel", 1, 1, sceKernelSetVirtualRangeName);
|
||||
LIB_FUNCTION("n1-v6FgU7MQ", "libkernel", 1, "libkernel", 1, 1,
|
||||
sceKernelConfiguredFlexibleMemorySize);
|
||||
|
||||
// Memory pool
|
||||
LIB_FUNCTION("qCSfqDILlns", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolExpand);
|
||||
LIB_FUNCTION("pU-QydtGcGY", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolReserve);
|
||||
LIB_FUNCTION("Vzl66WmfLvk", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolCommit);
|
||||
LIB_FUNCTION("LXo1tpFqJGs", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolDecommit);
|
||||
|
||||
// equeue
|
||||
LIB_FUNCTION("D0OdFMjp46I", "libkernel", 1, "libkernel", 1, 1, sceKernelCreateEqueue);
|
||||
|
|
|
@ -347,4 +347,102 @@ s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, cons
|
|||
memory->NameVirtualRange(std::bit_cast<VAddr>(addr), len, name);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_t len,
|
||||
size_t alignment, u64* physAddrOut) {
|
||||
if (searchStart < 0 || searchEnd <= searchStart) {
|
||||
LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
const bool is_in_range = searchEnd - searchStart >= len;
|
||||
if (len <= 0 || !Common::Is64KBAligned(len) || !is_in_range) {
|
||||
LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (alignment != 0 && !Common::Is64KBAligned(alignment)) {
|
||||
LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (physAddrOut == nullptr) {
|
||||
LOG_ERROR(Kernel_Vmm, "Result physical address pointer is null!");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
auto* memory = Core::Memory::Instance();
|
||||
PAddr phys_addr = memory->PoolExpand(searchStart, searchEnd, len, alignment);
|
||||
*physAddrOut = static_cast<s64>(phys_addr);
|
||||
|
||||
LOG_INFO(Kernel_Vmm,
|
||||
"searchStart = {:#x}, searchEnd = {:#x}, len = {:#x}, alignment = {:#x}, physAddrOut "
|
||||
"= {:#x}",
|
||||
searchStart, searchEnd, len, alignment, phys_addr);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t alignment, int flags,
|
||||
void** addrOut) {
|
||||
LOG_INFO(Kernel_Vmm, "addrIn = {}, len = {:#x}, alignment = {:#x}, flags = {:#x}",
|
||||
fmt::ptr(addrIn), len, alignment, flags);
|
||||
|
||||
if (addrIn == nullptr) {
|
||||
LOG_ERROR(Kernel_Vmm, "Address is invalid!");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (len == 0 || !Common::Is2MBAligned(len)) {
|
||||
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 2MB aligned!");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (alignment != 0) {
|
||||
if ((!std::has_single_bit(alignment) && !Common::Is2MBAligned(alignment))) {
|
||||
LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
auto* memory = Core::Memory::Instance();
|
||||
const VAddr in_addr = reinterpret_cast<VAddr>(addrIn);
|
||||
const auto map_flags = static_cast<Core::MemoryMapFlags>(flags);
|
||||
memory->PoolReserve(addrOut, in_addr, len, map_flags, alignment);
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags) {
|
||||
if (addr == nullptr) {
|
||||
LOG_ERROR(Kernel_Vmm, "Address is invalid!");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (len == 0 || !Common::Is64KBAligned(len)) {
|
||||
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 64KB aligned!");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, type = {:#x}, prot = {:#x}, flags = {:#x}",
|
||||
fmt::ptr(addr), len, type, prot, flags);
|
||||
|
||||
const VAddr in_addr = reinterpret_cast<VAddr>(addr);
|
||||
const auto mem_prot = static_cast<Core::MemoryProt>(prot);
|
||||
auto* memory = Core::Memory::Instance();
|
||||
return memory->PoolCommit(in_addr, len, mem_prot);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags) {
|
||||
if (addr == nullptr) {
|
||||
LOG_ERROR(Kernel_Vmm, "Address is invalid!");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (len == 0 || !Common::Is64KBAligned(len)) {
|
||||
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 64KB aligned!");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, flags = {:#x}", fmt::ptr(addr), len, flags);
|
||||
|
||||
const VAddr pool_addr = reinterpret_cast<VAddr>(addr);
|
||||
auto* memory = Core::Memory::Instance();
|
||||
memory->PoolDecommit(pool_addr, len);
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
|
|
@ -114,4 +114,11 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn
|
|||
|
||||
s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, const char* name);
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_t len,
|
||||
size_t alignment, u64* physAddrOut);
|
||||
s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t alignment, int flags,
|
||||
void** addrOut);
|
||||
s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags);
|
||||
s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags);
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
|
|
@ -414,6 +414,7 @@ ScePthreadMutex* createMutex(ScePthreadMutex* addr) {
|
|||
if (addr == nullptr || *addr != nullptr) {
|
||||
return addr;
|
||||
}
|
||||
|
||||
const VAddr vaddr = reinterpret_cast<VAddr>(addr);
|
||||
std::string name = fmt::format("mutex{:#x}", vaddr);
|
||||
scePthreadMutexInit(addr, nullptr, name.c_str());
|
||||
|
@ -515,9 +516,12 @@ int PS4_SYSV_ABI scePthreadMutexattrSettype(ScePthreadMutexattr* attr, int type)
|
|||
ptype = PTHREAD_MUTEX_RECURSIVE;
|
||||
break;
|
||||
case ORBIS_PTHREAD_MUTEX_NORMAL:
|
||||
case ORBIS_PTHREAD_MUTEX_ADAPTIVE:
|
||||
ptype = PTHREAD_MUTEX_NORMAL;
|
||||
break;
|
||||
case ORBIS_PTHREAD_MUTEX_ADAPTIVE:
|
||||
LOG_ERROR(Kernel_Pthread, "Unimplemented adaptive mutex");
|
||||
ptype = PTHREAD_MUTEX_ERRORCHECK;
|
||||
break;
|
||||
default:
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
@ -1620,6 +1624,10 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("upoVrzMHFeE", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexTrylock);
|
||||
LIB_FUNCTION("IafI2PxcPnQ", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexTimedlock);
|
||||
|
||||
// scePthreadMutexInitForInternalLibc, scePthreadMutexattrInitForInternalLibc
|
||||
LIB_FUNCTION("qH1gXoq71RY", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexInit);
|
||||
LIB_FUNCTION("n2MMpvU8igI", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexattrInit);
|
||||
|
||||
// cond calls
|
||||
LIB_FUNCTION("2Tb92quprl0", "libkernel", 1, "libkernel", 1, 1, scePthreadCondInit);
|
||||
LIB_FUNCTION("m5-2bsNfv7s", "libkernel", 1, "libkernel", 1, 1, scePthreadCondattrInit);
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
|
||||
namespace Libraries::NpTrophy {
|
||||
|
||||
static TrophyUI g_trophy_ui;
|
||||
|
||||
std::string game_serial;
|
||||
|
||||
static constexpr auto MaxTrophyHandles = 4u;
|
||||
|
@ -223,6 +221,14 @@ int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTro
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
struct GameTrophyInfo {
|
||||
uint32_t num_groups;
|
||||
uint32_t num_trophies;
|
||||
uint32_t num_trophies_by_rarity[5];
|
||||
uint32_t unlocked_trophies;
|
||||
uint32_t unlocked_trophies_by_rarity[5];
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
|
||||
OrbisNpTrophyGameDetails* details,
|
||||
OrbisNpTrophyGameData* data) {
|
||||
|
@ -240,79 +246,66 @@ int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTro
|
|||
if (details->size != 0x4A0 || data->size != 0x20)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
const auto trophyDir =
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result =
|
||||
doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
|
||||
if (result) {
|
||||
ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description());
|
||||
|
||||
uint32_t numGroups = 0;
|
||||
uint32_t numTrophies = 0;
|
||||
uint32_t numTrophiesByRarity[5];
|
||||
numTrophiesByRarity[1] = 0;
|
||||
numTrophiesByRarity[2] = 0;
|
||||
numTrophiesByRarity[3] = 0;
|
||||
numTrophiesByRarity[4] = 0;
|
||||
uint32_t unlockedTrophies = 0;
|
||||
uint32_t unlockedTrophiesByRarity[5];
|
||||
unlockedTrophiesByRarity[1] = 0;
|
||||
unlockedTrophiesByRarity[2] = 0;
|
||||
unlockedTrophiesByRarity[3] = 0;
|
||||
unlockedTrophiesByRarity[4] = 0;
|
||||
GameTrophyInfo game_info{};
|
||||
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
for (pugi::xml_node_iterator it = trophyconf.children().begin();
|
||||
it != trophyconf.children().end(); ++it) {
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
for (const pugi::xml_node& node : trophyconf.children()) {
|
||||
std::string_view node_name = node.name();
|
||||
|
||||
if (std::string(it->name()) == "title-name") {
|
||||
strncpy(details->title, it->text().as_string(),
|
||||
ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE);
|
||||
}
|
||||
|
||||
if (std::string(it->name()) == "title-detail") {
|
||||
strncpy(details->description, it->text().as_string(),
|
||||
ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE);
|
||||
}
|
||||
|
||||
if (std::string(it->name()) == "group")
|
||||
numGroups++;
|
||||
|
||||
if (std::string(it->name()) == "trophy") {
|
||||
std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
|
||||
std::string currentTrophyGrade = it->attribute("ttype").value();
|
||||
|
||||
numTrophies++;
|
||||
if (!currentTrophyGrade.empty()) {
|
||||
int trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0));
|
||||
numTrophiesByRarity[trophyGrade]++;
|
||||
if (currentTrophyUnlockState == "unlocked") {
|
||||
unlockedTrophies++;
|
||||
unlockedTrophiesByRarity[trophyGrade]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node_name == "title-name") {
|
||||
strncpy(details->title, node.text().as_string(), ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE);
|
||||
}
|
||||
|
||||
details->numGroups = numGroups;
|
||||
details->numTrophies = numTrophies;
|
||||
details->numPlatinum = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
|
||||
details->numGold = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD];
|
||||
details->numSilver = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER];
|
||||
details->numBronze = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
|
||||
data->unlockedTrophies = unlockedTrophies;
|
||||
data->unlockedPlatinum = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
|
||||
data->unlockedGold = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD];
|
||||
data->unlockedSilver = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER];
|
||||
data->unlockedBronze = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
|
||||
if (node_name == "title-detail") {
|
||||
strncpy(details->description, node.text().as_string(),
|
||||
ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE);
|
||||
}
|
||||
|
||||
// maybe this should be 1 instead of 100?
|
||||
data->progressPercentage = 100;
|
||||
if (node_name == "group")
|
||||
game_info.num_groups++;
|
||||
|
||||
} else
|
||||
LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
|
||||
if (node_name == "trophy") {
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
std::string_view current_trophy_grade = node.attribute("ttype").value();
|
||||
|
||||
if (current_trophy_grade.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
game_info.num_trophies++;
|
||||
int trophy_grade = GetTrophyGradeFromChar(current_trophy_grade.at(0));
|
||||
game_info.num_trophies_by_rarity[trophy_grade]++;
|
||||
|
||||
if (current_trophy_unlockstate) {
|
||||
game_info.unlocked_trophies++;
|
||||
game_info.unlocked_trophies_by_rarity[trophy_grade]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
details->num_groups = game_info.num_groups;
|
||||
details->num_trophies = game_info.num_trophies;
|
||||
details->num_platinum = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
|
||||
details->num_gold = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD];
|
||||
details->num_silver = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
|
||||
details->num_bronze = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
|
||||
data->unlocked_trophies = game_info.unlocked_trophies;
|
||||
data->unlocked_platinum = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
|
||||
data->unlocked_gold = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD];
|
||||
data->unlocked_silver = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
|
||||
data->unlocked_bronze = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
|
||||
|
||||
// maybe this should be 1 instead of 100?
|
||||
data->progress_percentage = 100;
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
@ -323,6 +316,13 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(OrbisNpTrophyContext context, OrbisNpTr
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
struct GroupTrophyInfo {
|
||||
uint32_t num_trophies;
|
||||
uint32_t num_trophies_by_rarity[5];
|
||||
uint32_t unlocked_trophies;
|
||||
uint32_t unlocked_trophies_by_rarity[5];
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
|
||||
OrbisNpTrophyGroupId groupId,
|
||||
OrbisNpTrophyGroupDetails* details,
|
||||
|
@ -341,89 +341,75 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTr
|
|||
if (details->size != 0x4A0 || data->size != 0x28)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
const auto trophyDir =
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result =
|
||||
doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
|
||||
if (result) {
|
||||
ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description());
|
||||
|
||||
uint32_t numGroups = 0;
|
||||
uint32_t numTrophies = 0;
|
||||
uint32_t numTrophiesByRarity[5];
|
||||
numTrophiesByRarity[1] = 0;
|
||||
numTrophiesByRarity[2] = 0;
|
||||
numTrophiesByRarity[3] = 0;
|
||||
numTrophiesByRarity[4] = 0;
|
||||
uint32_t unlockedTrophies = 0;
|
||||
uint32_t unlockedTrophiesByRarity[5];
|
||||
unlockedTrophiesByRarity[1] = 0;
|
||||
unlockedTrophiesByRarity[2] = 0;
|
||||
unlockedTrophiesByRarity[3] = 0;
|
||||
unlockedTrophiesByRarity[4] = 0;
|
||||
GroupTrophyInfo group_info{};
|
||||
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
for (pugi::xml_node_iterator it = trophyconf.children().begin();
|
||||
it != trophyconf.children().end(); ++it) {
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
for (const pugi::xml_node& node : trophyconf.children()) {
|
||||
std::string_view node_name = node.name();
|
||||
|
||||
if (std::string(it->name()) == "group") {
|
||||
numGroups++;
|
||||
std::string currentGroupId = it->attribute("id").value();
|
||||
if (!currentGroupId.empty()) {
|
||||
if (std::stoi(currentGroupId) == groupId) {
|
||||
std::string currentGroupName = it->child("name").text().as_string();
|
||||
std::string currentGroupDescription =
|
||||
it->child("detail").text().as_string();
|
||||
if (node_name == "group") {
|
||||
int current_group_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_GROUP_ID);
|
||||
if (current_group_id != ORBIS_NP_TROPHY_INVALID_GROUP_ID) {
|
||||
if (current_group_id == groupId) {
|
||||
std::string_view current_group_name = node.child("name").text().as_string();
|
||||
std::string_view current_group_description =
|
||||
node.child("detail").text().as_string();
|
||||
|
||||
strncpy(details->title, currentGroupName.c_str(),
|
||||
ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE);
|
||||
strncpy(details->description, currentGroupDescription.c_str(),
|
||||
ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data->groupId = groupId;
|
||||
|
||||
if (std::string(it->name()) == "trophy") {
|
||||
std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
|
||||
std::string currentTrophyGrade = it->attribute("ttype").value();
|
||||
std::string currentTrophyGroupID = it->attribute("gid").value();
|
||||
|
||||
if (!currentTrophyGroupID.empty()) {
|
||||
if (std::stoi(currentTrophyGroupID) == groupId) {
|
||||
numTrophies++;
|
||||
if (!currentTrophyGrade.empty()) {
|
||||
int trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0));
|
||||
numTrophiesByRarity[trophyGrade]++;
|
||||
if (currentTrophyUnlockState == "unlocked") {
|
||||
unlockedTrophies++;
|
||||
unlockedTrophiesByRarity[trophyGrade]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
strncpy(details->title, current_group_name.data(),
|
||||
ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE);
|
||||
strncpy(details->description, current_group_description.data(),
|
||||
ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
details->numTrophies = numTrophies;
|
||||
details->numPlatinum = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
|
||||
details->numGold = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD];
|
||||
details->numSilver = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER];
|
||||
details->numBronze = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
|
||||
data->unlockedTrophies = unlockedTrophies;
|
||||
data->unlockedPlatinum = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
|
||||
data->unlockedGold = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD];
|
||||
data->unlockedSilver = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER];
|
||||
data->unlockedBronze = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
|
||||
details->group_id = groupId;
|
||||
data->group_id = groupId;
|
||||
|
||||
// maybe this should be 1 instead of 100?
|
||||
data->progressPercentage = 100;
|
||||
if (node_name == "trophy") {
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
std::string_view current_trophy_grade = node.attribute("ttype").value();
|
||||
int current_trophy_group_id = node.attribute("gid").as_int(-1);
|
||||
|
||||
} else
|
||||
LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
|
||||
if (current_trophy_grade.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current_trophy_group_id == groupId) {
|
||||
group_info.num_trophies++;
|
||||
int trophyGrade = GetTrophyGradeFromChar(current_trophy_grade.at(0));
|
||||
group_info.num_trophies_by_rarity[trophyGrade]++;
|
||||
if (current_trophy_unlockstate) {
|
||||
group_info.unlocked_trophies++;
|
||||
group_info.unlocked_trophies_by_rarity[trophyGrade]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
details->num_trophies = group_info.num_trophies;
|
||||
details->num_platinum = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
|
||||
details->num_gold = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD];
|
||||
details->num_silver = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
|
||||
details->num_bronze = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
|
||||
data->unlocked_trophies = group_info.unlocked_trophies;
|
||||
data->unlocked_platinum =
|
||||
group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
|
||||
data->unlocked_gold = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD];
|
||||
data->unlocked_silver = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
|
||||
data->unlocked_bronze = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
|
||||
|
||||
// maybe this should be 1 instead of 100?
|
||||
data->progress_percentage = 100;
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
@ -454,87 +440,48 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT
|
|||
if (details->size != 0x498 || data->size != 0x18)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
const auto trophyDir =
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result =
|
||||
doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
|
||||
if (result) {
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
for (pugi::xml_node_iterator it = trophyconf.children().begin();
|
||||
it != trophyconf.children().end(); ++it) {
|
||||
ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description());
|
||||
|
||||
if (std::string(it->name()) == "trophy") {
|
||||
std::string currentTrophyId = it->attribute("id").value();
|
||||
if (std::stoi(currentTrophyId) == trophyId) {
|
||||
std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
|
||||
std::string currentTrophyTimestamp = it->attribute("timestamp").value();
|
||||
std::string currentTrophyGrade = it->attribute("ttype").value();
|
||||
std::string currentTrophyGroupID = it->attribute("gid").value();
|
||||
std::string currentTrophyHidden = it->attribute("hidden").value();
|
||||
std::string currentTrophyName = it->child("name").text().as_string();
|
||||
std::string currentTrophyDescription = it->child("detail").text().as_string();
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
|
||||
if (currentTrophyUnlockState == "unlocked") {
|
||||
details->trophyId = trophyId;
|
||||
if (currentTrophyGrade.empty()) {
|
||||
details->trophyGrade = ORBIS_NP_TROPHY_GRADE_UNKNOWN;
|
||||
} else {
|
||||
details->trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0));
|
||||
}
|
||||
if (currentTrophyGroupID.empty()) {
|
||||
details->groupId = ORBIS_NP_TROPHY_BASE_GAME_GROUP_ID;
|
||||
} else {
|
||||
details->groupId = std::stoi(currentTrophyGroupID);
|
||||
}
|
||||
if (currentTrophyHidden == "yes") {
|
||||
details->hidden = true;
|
||||
} else {
|
||||
details->hidden = false;
|
||||
}
|
||||
for (const pugi::xml_node& node : trophyconf.children()) {
|
||||
std::string_view node_name = node.name();
|
||||
|
||||
strncpy(details->name, currentTrophyName.c_str(),
|
||||
ORBIS_NP_TROPHY_NAME_MAX_SIZE);
|
||||
strncpy(details->description, currentTrophyDescription.c_str(),
|
||||
ORBIS_NP_TROPHY_DESCR_MAX_SIZE);
|
||||
if (node_name == "trophy") {
|
||||
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
if (current_trophy_id == trophyId) {
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
std::string_view current_trophy_grade = node.attribute("ttype").value();
|
||||
std::string_view current_trophy_name = node.child("name").text().as_string();
|
||||
std::string_view current_trophy_description =
|
||||
node.child("detail").text().as_string();
|
||||
|
||||
data->trophyId = trophyId;
|
||||
data->unlocked = true;
|
||||
data->timestamp.tick = std::stoull(currentTrophyTimestamp);
|
||||
} else {
|
||||
details->trophyId = trophyId;
|
||||
if (currentTrophyGrade.empty()) {
|
||||
details->trophyGrade = ORBIS_NP_TROPHY_GRADE_UNKNOWN;
|
||||
} else {
|
||||
details->trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0));
|
||||
}
|
||||
if (currentTrophyGroupID.empty()) {
|
||||
details->groupId = ORBIS_NP_TROPHY_BASE_GAME_GROUP_ID;
|
||||
} else {
|
||||
details->groupId = std::stoi(currentTrophyGroupID);
|
||||
}
|
||||
if (currentTrophyHidden == "yes") {
|
||||
details->hidden = true;
|
||||
} else {
|
||||
details->hidden = false;
|
||||
}
|
||||
uint64_t current_trophy_timestamp = node.attribute("timestamp").as_ullong();
|
||||
int current_trophy_groupid = node.attribute("gid").as_int(-1);
|
||||
bool current_trophy_hidden = node.attribute("hidden").as_bool();
|
||||
|
||||
strncpy(details->name, currentTrophyName.c_str(),
|
||||
ORBIS_NP_TROPHY_NAME_MAX_SIZE);
|
||||
strncpy(details->description, currentTrophyDescription.c_str(),
|
||||
ORBIS_NP_TROPHY_DESCR_MAX_SIZE);
|
||||
details->trophy_id = trophyId;
|
||||
details->trophy_grade = GetTrophyGradeFromChar(current_trophy_grade.at(0));
|
||||
details->group_id = current_trophy_groupid;
|
||||
details->hidden = current_trophy_hidden;
|
||||
|
||||
data->trophyId = trophyId;
|
||||
data->unlocked = false;
|
||||
data->timestamp.tick = 0;
|
||||
}
|
||||
}
|
||||
strncpy(details->name, current_trophy_name.data(), ORBIS_NP_TROPHY_NAME_MAX_SIZE);
|
||||
strncpy(details->description, current_trophy_description.data(),
|
||||
ORBIS_NP_TROPHY_DESCR_MAX_SIZE);
|
||||
|
||||
data->trophy_id = trophyId;
|
||||
data->unlocked = current_trophy_unlockstate;
|
||||
data->timestamp.tick = current_trophy_timestamp;
|
||||
}
|
||||
}
|
||||
} else
|
||||
LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
@ -555,35 +502,33 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
|
|||
|
||||
ORBIS_NP_TROPHY_FLAG_ZERO(flags);
|
||||
|
||||
const auto trophyDir =
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophyDir / "trophy00" / "Xml" / "TROP.XML";
|
||||
auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
|
||||
int numTrophies = 0;
|
||||
ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description());
|
||||
|
||||
if (result) {
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
for (pugi::xml_node_iterator it = trophyconf.children().begin();
|
||||
it != trophyconf.children().end(); ++it) {
|
||||
int num_trophies = 0;
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
|
||||
std::string currentTrophyId = it->attribute("id").value();
|
||||
std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
|
||||
for (const pugi::xml_node& node : trophyconf.children()) {
|
||||
std::string_view node_name = node.name();
|
||||
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
|
||||
if (std::string(it->name()) == "trophy") {
|
||||
numTrophies++;
|
||||
}
|
||||
|
||||
if (currentTrophyUnlockState == "unlocked") {
|
||||
ORBIS_NP_TROPHY_FLAG_SET(std::stoi(currentTrophyId), flags);
|
||||
}
|
||||
if (node_name == "trophy") {
|
||||
num_trophies++;
|
||||
}
|
||||
} else
|
||||
LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
|
||||
|
||||
*count = numTrophies;
|
||||
if (current_trophy_unlockstate) {
|
||||
ORBIS_NP_TROPHY_FLAG_SET(current_trophy_id, flags);
|
||||
}
|
||||
}
|
||||
|
||||
*count = num_trophies;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
@ -912,148 +857,116 @@ int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTr
|
|||
if (platinumId == nullptr)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
const auto trophyDir =
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result =
|
||||
doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
|
||||
ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description());
|
||||
|
||||
*platinumId = ORBIS_NP_TROPHY_INVALID_TROPHY_ID;
|
||||
|
||||
int numTrophies = 0;
|
||||
int numTrophiesUnlocked = 0;
|
||||
int num_trophies = 0;
|
||||
int num_trophies_unlocked = 0;
|
||||
pugi::xml_node platinum_node;
|
||||
|
||||
pugi::xml_node_iterator platinumIt;
|
||||
int platinumTrophyGroup = -1;
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
|
||||
if (result) {
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
for (pugi::xml_node_iterator it = trophyconf.children().begin();
|
||||
it != trophyconf.children().end(); ++it) {
|
||||
for (pugi::xml_node& node : trophyconf.children()) {
|
||||
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
const char* current_trophy_name = node.child("name").text().as_string();
|
||||
std::string_view current_trophy_description = node.child("detail").text().as_string();
|
||||
std::string_view current_trophy_type = node.attribute("ttype").value();
|
||||
|
||||
std::string currentTrophyId = it->attribute("id").value();
|
||||
std::string currentTrophyName = it->child("name").text().as_string();
|
||||
std::string currentTrophyDescription = it->child("detail").text().as_string();
|
||||
std::string currentTrophyType = it->attribute("ttype").value();
|
||||
std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
|
||||
if (current_trophy_type == "P") {
|
||||
platinum_node = node;
|
||||
if (trophyId == current_trophy_id) {
|
||||
return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTrophyType == "P") {
|
||||
platinumIt = it;
|
||||
|
||||
if (std::string(platinumIt->attribute("gid").value()).empty()) {
|
||||
platinumTrophyGroup = -1;
|
||||
} else {
|
||||
platinumTrophyGroup =
|
||||
std::stoi(std::string(platinumIt->attribute("gid").value()));
|
||||
}
|
||||
|
||||
if (trophyId == std::stoi(currentTrophyId)) {
|
||||
return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK;
|
||||
if (std::string_view(node.name()) == "trophy") {
|
||||
if (node.attribute("pid").as_int(-1) != ORBIS_NP_TROPHY_INVALID_TROPHY_ID) {
|
||||
num_trophies++;
|
||||
if (current_trophy_unlockstate) {
|
||||
num_trophies_unlocked++;
|
||||
}
|
||||
}
|
||||
|
||||
if (std::string(it->name()) == "trophy") {
|
||||
if (platinumTrophyGroup == -1) {
|
||||
if (std::string(it->attribute("gid").value()).empty()) {
|
||||
numTrophies++;
|
||||
if (currentTrophyUnlockState == "unlocked") {
|
||||
numTrophiesUnlocked++;
|
||||
}
|
||||
}
|
||||
if (current_trophy_id == trophyId) {
|
||||
if (current_trophy_unlockstate) {
|
||||
LOG_INFO(Lib_NpTrophy, "Trophy already unlocked");
|
||||
return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
|
||||
} else {
|
||||
if (!std::string(it->attribute("gid").value()).empty()) {
|
||||
if (std::stoi(std::string(it->attribute("gid").value())) ==
|
||||
platinumTrophyGroup) {
|
||||
numTrophies++;
|
||||
if (currentTrophyUnlockState == "unlocked") {
|
||||
numTrophiesUnlocked++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (std::stoi(currentTrophyId) == trophyId) {
|
||||
LOG_INFO(Lib_NpTrophy, "Found trophy to unlock {} : {}",
|
||||
it->child("name").text().as_string(),
|
||||
it->child("detail").text().as_string());
|
||||
if (currentTrophyUnlockState == "unlocked") {
|
||||
LOG_INFO(Lib_NpTrophy, "Trophy already unlocked");
|
||||
return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
|
||||
if (node.attribute("unlockstate").empty()) {
|
||||
node.append_attribute("unlockstate") = "true";
|
||||
} else {
|
||||
if (std::string(it->attribute("unlockstate").value()).empty()) {
|
||||
it->append_attribute("unlockstate") = "unlocked";
|
||||
} else {
|
||||
it->attribute("unlockstate").set_value("unlocked");
|
||||
}
|
||||
|
||||
Rtc::OrbisRtcTick trophyTimestamp;
|
||||
Rtc::sceRtcGetCurrentTick(&trophyTimestamp);
|
||||
|
||||
if (std::string(it->attribute("timestamp").value()).empty()) {
|
||||
it->append_attribute("timestamp") =
|
||||
std::to_string(trophyTimestamp.tick).c_str();
|
||||
} else {
|
||||
it->attribute("timestamp")
|
||||
.set_value(std::to_string(trophyTimestamp.tick).c_str());
|
||||
}
|
||||
|
||||
g_trophy_ui.AddTrophyToQueue(trophyId, currentTrophyName);
|
||||
node.attribute("unlockstate").set_value("true");
|
||||
}
|
||||
|
||||
Rtc::OrbisRtcTick trophyTimestamp;
|
||||
Rtc::sceRtcGetCurrentTick(&trophyTimestamp);
|
||||
|
||||
if (node.attribute("timestamp").empty()) {
|
||||
node.append_attribute("timestamp") =
|
||||
std::to_string(trophyTimestamp.tick).c_str();
|
||||
} else {
|
||||
node.attribute("timestamp")
|
||||
.set_value(std::to_string(trophyTimestamp.tick).c_str());
|
||||
}
|
||||
|
||||
std::string trophy_icon_file = "TROP";
|
||||
trophy_icon_file.append(node.attribute("id").value());
|
||||
trophy_icon_file.append(".PNG");
|
||||
|
||||
std::filesystem::path current_icon_path =
|
||||
trophy_dir / "trophy00" / "Icons" / trophy_icon_file;
|
||||
|
||||
AddTrophyToQueue(current_icon_path, current_trophy_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (std::string(platinumIt->attribute("unlockstate").value()).empty()) {
|
||||
if ((numTrophies - 2) == numTrophiesUnlocked) {
|
||||
|
||||
platinumIt->append_attribute("unlockstate") = "unlocked";
|
||||
|
||||
Rtc::OrbisRtcTick trophyTimestamp;
|
||||
Rtc::sceRtcGetCurrentTick(&trophyTimestamp);
|
||||
|
||||
if (std::string(platinumIt->attribute("timestamp").value()).empty()) {
|
||||
platinumIt->append_attribute("timestamp") =
|
||||
std::to_string(trophyTimestamp.tick).c_str();
|
||||
} else {
|
||||
platinumIt->attribute("timestamp")
|
||||
.set_value(std::to_string(trophyTimestamp.tick).c_str());
|
||||
}
|
||||
|
||||
std::string platinumTrophyId = platinumIt->attribute("id").value();
|
||||
std::string platinumTrophyName = platinumIt->child("name").text().as_string();
|
||||
|
||||
*platinumId = std::stoi(platinumTrophyId);
|
||||
g_trophy_ui.AddTrophyToQueue(*platinumId, platinumTrophyName);
|
||||
if (!platinum_node.attribute("unlockstate").as_bool()) {
|
||||
if ((num_trophies - 1) == num_trophies_unlocked) {
|
||||
if (platinum_node.attribute("unlockstate").empty()) {
|
||||
platinum_node.append_attribute("unlockstate") = "true";
|
||||
} else {
|
||||
platinum_node.attribute("unlockstate").set_value("true");
|
||||
}
|
||||
} else if (std::string(platinumIt->attribute("unlockstate").value()) == "locked") {
|
||||
if ((numTrophies - 2) == numTrophiesUnlocked) {
|
||||
|
||||
platinumIt->attribute("unlockstate").set_value("unlocked");
|
||||
Rtc::OrbisRtcTick trophyTimestamp;
|
||||
Rtc::sceRtcGetCurrentTick(&trophyTimestamp);
|
||||
|
||||
Rtc::OrbisRtcTick trophyTimestamp;
|
||||
Rtc::sceRtcGetCurrentTick(&trophyTimestamp);
|
||||
|
||||
if (std::string(platinumIt->attribute("timestamp").value()).empty()) {
|
||||
platinumIt->append_attribute("timestamp") =
|
||||
std::to_string(trophyTimestamp.tick).c_str();
|
||||
} else {
|
||||
platinumIt->attribute("timestamp")
|
||||
.set_value(std::to_string(trophyTimestamp.tick).c_str());
|
||||
}
|
||||
|
||||
std::string platinumTrophyId = platinumIt->attribute("id").value();
|
||||
std::string platinumTrophyName = platinumIt->child("name").text().as_string();
|
||||
|
||||
*platinumId = std::stoi(platinumTrophyId);
|
||||
g_trophy_ui.AddTrophyToQueue(*platinumId, platinumTrophyName);
|
||||
if (platinum_node.attribute("timestamp").empty()) {
|
||||
platinum_node.append_attribute("timestamp") =
|
||||
std::to_string(trophyTimestamp.tick).c_str();
|
||||
} else {
|
||||
platinum_node.attribute("timestamp")
|
||||
.set_value(std::to_string(trophyTimestamp.tick).c_str());
|
||||
}
|
||||
|
||||
int platinum_trophy_id =
|
||||
platinum_node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
const char* platinum_trophy_name = platinum_node.child("name").text().as_string();
|
||||
|
||||
std::string platinum_icon_file = "TROP";
|
||||
platinum_icon_file.append(platinum_node.attribute("id").value());
|
||||
platinum_icon_file.append(".PNG");
|
||||
|
||||
std::filesystem::path platinum_icon_path =
|
||||
trophy_dir / "trophy00" / "Icons" / platinum_icon_file;
|
||||
|
||||
*platinumId = platinum_trophy_id;
|
||||
AddTrophyToQueue(platinum_icon_path, platinum_trophy_name);
|
||||
}
|
||||
}
|
||||
|
||||
doc.save_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
|
||||
|
||||
} else
|
||||
LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
|
||||
doc.save_file((trophy_dir.string() + "/trophy00/Xml/TROP.XML").c_str());
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ bool ORBIS_NP_TROPHY_FLAG_ISSET(int32_t trophyId, OrbisNpTrophyFlagArray* p);
|
|||
|
||||
struct OrbisNpTrophyData {
|
||||
size_t size;
|
||||
OrbisNpTrophyId trophyId;
|
||||
OrbisNpTrophyId trophy_id;
|
||||
bool unlocked;
|
||||
uint8_t reserved[3];
|
||||
Rtc::OrbisRtcTick timestamp;
|
||||
|
@ -66,9 +66,9 @@ constexpr int ORBIS_NP_TROPHY_INVALID_GROUP_ID = -2;
|
|||
|
||||
struct OrbisNpTrophyDetails {
|
||||
size_t size;
|
||||
OrbisNpTrophyId trophyId;
|
||||
OrbisNpTrophyGrade trophyGrade;
|
||||
OrbisNpTrophyGroupId groupId;
|
||||
OrbisNpTrophyId trophy_id;
|
||||
OrbisNpTrophyGrade trophy_grade;
|
||||
OrbisNpTrophyGroupId group_id;
|
||||
bool hidden;
|
||||
uint8_t reserved[3];
|
||||
char name[ORBIS_NP_TROPHY_NAME_MAX_SIZE];
|
||||
|
@ -77,46 +77,46 @@ struct OrbisNpTrophyDetails {
|
|||
|
||||
struct OrbisNpTrophyGameData {
|
||||
size_t size;
|
||||
uint32_t unlockedTrophies;
|
||||
uint32_t unlockedPlatinum;
|
||||
uint32_t unlockedGold;
|
||||
uint32_t unlockedSilver;
|
||||
uint32_t unlockedBronze;
|
||||
uint32_t progressPercentage;
|
||||
uint32_t unlocked_trophies;
|
||||
uint32_t unlocked_platinum;
|
||||
uint32_t unlocked_gold;
|
||||
uint32_t unlocked_silver;
|
||||
uint32_t unlocked_bronze;
|
||||
uint32_t progress_percentage;
|
||||
};
|
||||
|
||||
struct OrbisNpTrophyGameDetails {
|
||||
size_t size;
|
||||
uint32_t numGroups;
|
||||
uint32_t numTrophies;
|
||||
uint32_t numPlatinum;
|
||||
uint32_t numGold;
|
||||
uint32_t numSilver;
|
||||
uint32_t numBronze;
|
||||
uint32_t num_groups;
|
||||
uint32_t num_trophies;
|
||||
uint32_t num_platinum;
|
||||
uint32_t num_gold;
|
||||
uint32_t num_silver;
|
||||
uint32_t num_bronze;
|
||||
char title[ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE];
|
||||
char description[ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE];
|
||||
};
|
||||
|
||||
struct OrbisNpTrophyGroupData {
|
||||
size_t size;
|
||||
OrbisNpTrophyGroupId groupId;
|
||||
uint32_t unlockedTrophies;
|
||||
uint32_t unlockedPlatinum;
|
||||
uint32_t unlockedGold;
|
||||
uint32_t unlockedSilver;
|
||||
uint32_t unlockedBronze;
|
||||
uint32_t progressPercentage;
|
||||
OrbisNpTrophyGroupId group_id;
|
||||
uint32_t unlocked_trophies;
|
||||
uint32_t unlocked_platinum;
|
||||
uint32_t unlocked_gold;
|
||||
uint32_t unlocked_silver;
|
||||
uint32_t unlocked_bronze;
|
||||
uint32_t progress_percentage;
|
||||
uint8_t reserved[4];
|
||||
};
|
||||
|
||||
struct OrbisNpTrophyGroupDetails {
|
||||
size_t size;
|
||||
OrbisNpTrophyGroupId groupId;
|
||||
uint32_t numTrophies;
|
||||
uint32_t numPlatinum;
|
||||
uint32_t numGold;
|
||||
uint32_t numSilver;
|
||||
uint32_t numBronze;
|
||||
OrbisNpTrophyGroupId group_id;
|
||||
uint32_t num_trophies;
|
||||
uint32_t num_platinum;
|
||||
uint32_t num_gold;
|
||||
uint32_t num_silver;
|
||||
uint32_t num_bronze;
|
||||
char title[ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE];
|
||||
char description[ORBIS_NP_TROPHY_GROUP_DESCR_MAX_SIZE];
|
||||
};
|
||||
|
|
|
@ -2,15 +2,27 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <imgui.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/singleton.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
#include "trophy_ui.h"
|
||||
|
||||
using namespace ImGui;
|
||||
using namespace Libraries::NpTrophy;
|
||||
namespace Libraries::NpTrophy {
|
||||
|
||||
TrophyUI::TrophyUI() {
|
||||
std::optional<TrophyUI> current_trophy_ui;
|
||||
std::queue<TrophyInfo> trophy_queue;
|
||||
std::mutex queueMtx;
|
||||
|
||||
TrophyUI::TrophyUI(std::filesystem::path trophyIconPath, std::string trophyName)
|
||||
: trophy_name(trophyName) {
|
||||
if (std::filesystem::exists(trophyIconPath)) {
|
||||
trophy_icon = RefCountedTexture::DecodePngFile(trophyIconPath);
|
||||
} else {
|
||||
LOG_ERROR(Lib_NpTrophy, "Couldnt load trophy icon at {}", trophyIconPath.string());
|
||||
}
|
||||
AddLayer(this);
|
||||
}
|
||||
|
||||
|
@ -18,57 +30,65 @@ TrophyUI::~TrophyUI() {
|
|||
Finish();
|
||||
}
|
||||
|
||||
void Libraries::NpTrophy::TrophyUI::AddTrophyToQueue(int trophyId, std::string trophyName) {
|
||||
TrophyInfo newInfo;
|
||||
newInfo.trophyId = trophyId;
|
||||
newInfo.trophyName = trophyName;
|
||||
trophyQueue.push_back(newInfo);
|
||||
}
|
||||
|
||||
void TrophyUI::Finish() {
|
||||
RemoveLayer(this);
|
||||
}
|
||||
|
||||
bool displayingTrophy;
|
||||
std::chrono::steady_clock::time_point trophyStartedTime;
|
||||
|
||||
void TrophyUI::Draw() {
|
||||
const auto& io = GetIO();
|
||||
|
||||
const ImVec2 window_size{
|
||||
std::min(io.DisplaySize.x, 200.f),
|
||||
std::min(io.DisplaySize.y, 75.f),
|
||||
std::min(io.DisplaySize.x, 250.f),
|
||||
std::min(io.DisplaySize.y, 70.f),
|
||||
};
|
||||
|
||||
if (trophyQueue.size() != 0) {
|
||||
if (!displayingTrophy) {
|
||||
displayingTrophy = true;
|
||||
trophyStartedTime = std::chrono::steady_clock::now();
|
||||
SetNextWindowSize(window_size);
|
||||
SetNextWindowCollapsed(false);
|
||||
SetNextWindowPos(ImVec2(io.DisplaySize.x - 250, 50));
|
||||
KeepNavHighlight();
|
||||
|
||||
if (Begin("Trophy Window", nullptr,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_NoInputs)) {
|
||||
if (trophy_icon) {
|
||||
Image(trophy_icon.GetTexture().im_id, ImVec2(50, 50));
|
||||
ImGui::SameLine();
|
||||
} else {
|
||||
// placeholder
|
||||
const auto pos = GetCursorScreenPos();
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(pos, pos + ImVec2{50.0f},
|
||||
GetColorU32(ImVec4{0.7f}));
|
||||
ImGui::Indent(60);
|
||||
}
|
||||
TextWrapped("Trophy earned!\n%s", trophy_name.c_str());
|
||||
}
|
||||
End();
|
||||
|
||||
std::chrono::steady_clock::time_point timeNow = std::chrono::steady_clock::now();
|
||||
std::chrono::seconds duration =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(timeNow - trophyStartedTime);
|
||||
|
||||
if (duration.count() >= 5) {
|
||||
trophyQueue.erase(trophyQueue.begin());
|
||||
displayingTrophy = false;
|
||||
}
|
||||
|
||||
if (trophyQueue.size() != 0) {
|
||||
SetNextWindowSize(window_size);
|
||||
SetNextWindowCollapsed(false);
|
||||
SetNextWindowPos(ImVec2(io.DisplaySize.x - 200, 50));
|
||||
KeepNavHighlight();
|
||||
|
||||
TrophyInfo currentTrophyInfo = trophyQueue[0];
|
||||
if (Begin("Trophy Window", nullptr,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_NoInputs)) {
|
||||
Text("Trophy earned!");
|
||||
TextWrapped("%s", currentTrophyInfo.trophyName.c_str());
|
||||
}
|
||||
End();
|
||||
trophy_timer -= io.DeltaTime;
|
||||
if (trophy_timer <= 0) {
|
||||
queueMtx.lock();
|
||||
if (!trophy_queue.empty()) {
|
||||
TrophyInfo next_trophy = trophy_queue.front();
|
||||
trophy_queue.pop();
|
||||
current_trophy_ui.emplace(next_trophy.trophy_icon_path, next_trophy.trophy_name);
|
||||
} else {
|
||||
current_trophy_ui.reset();
|
||||
}
|
||||
queueMtx.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void AddTrophyToQueue(std::filesystem::path trophyIconPath, std::string trophyName) {
|
||||
queueMtx.lock();
|
||||
if (current_trophy_ui.has_value()) {
|
||||
TrophyInfo new_trophy;
|
||||
new_trophy.trophy_icon_path = trophyIconPath;
|
||||
new_trophy.trophy_name = trophyName;
|
||||
trophy_queue.push(new_trophy);
|
||||
} else {
|
||||
current_trophy_ui.emplace(trophyIconPath, trophyName);
|
||||
}
|
||||
queueMtx.unlock();
|
||||
}
|
||||
|
||||
} // namespace Libraries::NpTrophy
|
|
@ -5,32 +5,36 @@
|
|||
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
|
||||
#include "common/fixed_value.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/np_trophy/np_trophy.h"
|
||||
#include "imgui/imgui_layer.h"
|
||||
#include "imgui/imgui_texture.h"
|
||||
|
||||
namespace Libraries::NpTrophy {
|
||||
|
||||
struct TrophyInfo {
|
||||
int trophyId = -1;
|
||||
std::string trophyName;
|
||||
};
|
||||
|
||||
class TrophyUI final : public ImGui::Layer {
|
||||
std::vector<TrophyInfo> trophyQueue;
|
||||
|
||||
public:
|
||||
TrophyUI();
|
||||
TrophyUI(std::filesystem::path trophyIconPath, std::string trophyName);
|
||||
~TrophyUI() override;
|
||||
|
||||
void AddTrophyToQueue(int trophyId, std::string trophyName);
|
||||
|
||||
void Finish();
|
||||
|
||||
void Draw() override;
|
||||
|
||||
private:
|
||||
std::string trophy_name;
|
||||
float trophy_timer = 5.0f;
|
||||
ImGui::RefCountedTexture trophy_icon;
|
||||
};
|
||||
|
||||
struct TrophyInfo {
|
||||
std::filesystem::path trophy_icon_path;
|
||||
std::string trophy_name;
|
||||
};
|
||||
|
||||
void AddTrophyToQueue(std::filesystem::path trophyIconPath, std::string trophyName);
|
||||
|
||||
}; // namespace Libraries::NpTrophy
|
|
@ -79,7 +79,7 @@ static void backup(const std::filesystem::path& dir_name) {
|
|||
}
|
||||
|
||||
static void BackupThreadBody() {
|
||||
Common::SetCurrentThreadName("SaveData_BackupThread");
|
||||
Common::SetCurrentThreadName("shadPS4:SaveData_BackupThread");
|
||||
while (g_backup_status != WorkerStatus::Stopping) {
|
||||
g_backup_status = WorkerStatus::Waiting;
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ static void SaveFileSafe(void* buf, size_t count, const std::filesystem::path& p
|
|||
}
|
||||
|
||||
[[noreturn]] void SaveThreadLoop() {
|
||||
Common::SetCurrentThreadName("SaveData_SaveDataMemoryThread");
|
||||
Common::SetCurrentThreadName("shadPS4:SaveData_SaveDataMemoryThread");
|
||||
std::mutex mtx;
|
||||
while (true) {
|
||||
{
|
||||
|
|
|
@ -260,7 +260,7 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
|
|||
static constexpr std::chrono::nanoseconds VblankPeriod{16666667};
|
||||
const auto vblank_period = VblankPeriod / Config::vblankDiv();
|
||||
|
||||
Common::SetCurrentThreadName("PresentThread");
|
||||
Common::SetCurrentThreadName("shadPS4:PresentThread");
|
||||
Common::SetCurrentThreadRealtime(vblank_period);
|
||||
|
||||
Common::AccurateTimer timer{vblank_period};
|
||||
|
|
|
@ -204,7 +204,10 @@ void Elf::Open(const std::filesystem::path& file_name) {
|
|||
}
|
||||
|
||||
out.resize(num);
|
||||
m_f.Seek(offset, SeekOrigin::SetOrigin);
|
||||
if (!m_f.Seek(offset, SeekOrigin::SetOrigin)) {
|
||||
LOG_CRITICAL(Loader, "Failed to seek to header tables");
|
||||
return;
|
||||
}
|
||||
m_f.Read(out);
|
||||
};
|
||||
|
||||
|
@ -465,7 +468,10 @@ std::string Elf::ElfPHeaderStr(u16 no) {
|
|||
void Elf::LoadSegment(u64 virtual_addr, u64 file_offset, u64 size) {
|
||||
if (!is_self) {
|
||||
// It's elf file
|
||||
m_f.Seek(file_offset, SeekOrigin::SetOrigin);
|
||||
if (!m_f.Seek(file_offset, SeekOrigin::SetOrigin)) {
|
||||
LOG_CRITICAL(Loader, "Failed to seek to ELF header");
|
||||
return;
|
||||
}
|
||||
m_f.ReadRaw<u8>(reinterpret_cast<u8*>(virtual_addr), size);
|
||||
return;
|
||||
}
|
||||
|
@ -479,7 +485,10 @@ void Elf::LoadSegment(u64 virtual_addr, u64 file_offset, u64 size) {
|
|||
|
||||
if (file_offset >= phdr.p_offset && file_offset < phdr.p_offset + phdr.p_filesz) {
|
||||
auto offset = file_offset - phdr.p_offset;
|
||||
m_f.Seek(offset + seg.file_offset, SeekOrigin::SetOrigin);
|
||||
if (!m_f.Seek(offset + seg.file_offset, SeekOrigin::SetOrigin)) {
|
||||
LOG_CRITICAL(Loader, "Failed to seek to segment");
|
||||
return;
|
||||
}
|
||||
m_f.ReadRaw<u8>(reinterpret_cast<u8*>(virtual_addr), size);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -51,6 +51,35 @@ void MemoryManager::SetupMemoryRegions(u64 flexible_size) {
|
|||
total_flexible_size, total_direct_size);
|
||||
}
|
||||
|
||||
PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment) {
|
||||
std::scoped_lock lk{mutex};
|
||||
|
||||
auto dmem_area = FindDmemArea(search_start);
|
||||
|
||||
const auto is_suitable = [&] {
|
||||
const auto aligned_base = alignment > 0 ? Common::AlignUp(dmem_area->second.base, alignment)
|
||||
: dmem_area->second.base;
|
||||
const auto alignment_size = aligned_base - dmem_area->second.base;
|
||||
const auto remaining_size =
|
||||
dmem_area->second.size >= alignment_size ? dmem_area->second.size - alignment_size : 0;
|
||||
return dmem_area->second.is_free && remaining_size >= size;
|
||||
};
|
||||
while (!is_suitable() && dmem_area->second.GetEnd() <= search_end) {
|
||||
dmem_area++;
|
||||
}
|
||||
ASSERT_MSG(is_suitable(), "Unable to find free direct memory area: size = {:#x}", size);
|
||||
|
||||
// Align free position
|
||||
PAddr free_addr = dmem_area->second.base;
|
||||
free_addr = alignment > 0 ? Common::AlignUp(free_addr, alignment) : free_addr;
|
||||
|
||||
// Add the allocated region to the list and commit its pages.
|
||||
auto& area = CarveDmemArea(free_addr, size)->second;
|
||||
area.is_free = false;
|
||||
area.is_pooled = true;
|
||||
return free_addr;
|
||||
}
|
||||
|
||||
PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment,
|
||||
int memory_type) {
|
||||
std::scoped_lock lk{mutex};
|
||||
|
@ -112,6 +141,43 @@ void MemoryManager::Free(PAddr phys_addr, size_t size) {
|
|||
MergeAdjacent(dmem_map, dmem_area);
|
||||
}
|
||||
|
||||
int MemoryManager::PoolReserve(void** out_addr, VAddr virtual_addr, size_t size,
|
||||
MemoryMapFlags flags, u64 alignment) {
|
||||
std::scoped_lock lk{mutex};
|
||||
|
||||
virtual_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
|
||||
alignment = alignment > 0 ? alignment : 2_MB;
|
||||
VAddr mapped_addr = alignment > 0 ? Common::AlignUp(virtual_addr, alignment) : virtual_addr;
|
||||
|
||||
// Fixed mapping means the virtual address must exactly match the provided one.
|
||||
if (True(flags & MemoryMapFlags::Fixed)) {
|
||||
const auto& vma = FindVMA(mapped_addr)->second;
|
||||
// If the VMA is mapped, unmap the region first.
|
||||
if (vma.IsMapped()) {
|
||||
UnmapMemoryImpl(mapped_addr, size);
|
||||
}
|
||||
const size_t remaining_size = vma.base + vma.size - mapped_addr;
|
||||
ASSERT_MSG(vma.type == VMAType::Free && remaining_size >= size);
|
||||
}
|
||||
|
||||
// Find the first free area starting with provided virtual address.
|
||||
if (False(flags & MemoryMapFlags::Fixed)) {
|
||||
mapped_addr = SearchFree(mapped_addr, size, alignment);
|
||||
}
|
||||
|
||||
// Add virtual memory area
|
||||
const auto new_vma_handle = CarveVMA(mapped_addr, size);
|
||||
auto& new_vma = new_vma_handle->second;
|
||||
new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
|
||||
new_vma.prot = MemoryProt::NoAccess;
|
||||
new_vma.name = "";
|
||||
new_vma.type = VMAType::PoolReserved;
|
||||
MergeAdjacent(vma_map, new_vma_handle);
|
||||
|
||||
*out_addr = std::bit_cast<void*>(mapped_addr);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
|
||||
u64 alignment) {
|
||||
std::scoped_lock lk{mutex};
|
||||
|
@ -149,6 +215,36 @@ int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, Mem
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int MemoryManager::PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot) {
|
||||
std::scoped_lock lk{mutex};
|
||||
|
||||
const u64 alignment = 64_KB;
|
||||
|
||||
// When virtual addr is zero, force it to virtual_base. The guest cannot pass Fixed
|
||||
// flag so we will take the branch that searches for free (or reserved) mappings.
|
||||
virtual_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
|
||||
VAddr mapped_addr = Common::AlignUp(virtual_addr, alignment);
|
||||
|
||||
// This should return SCE_KERNEL_ERROR_ENOMEM but shouldn't normally happen.
|
||||
const auto& vma = FindVMA(mapped_addr)->second;
|
||||
const size_t remaining_size = vma.base + vma.size - mapped_addr;
|
||||
ASSERT_MSG(!vma.IsMapped() && remaining_size >= size);
|
||||
|
||||
// Perform the mapping.
|
||||
void* out_addr = impl.Map(mapped_addr, size, alignment, -1, false);
|
||||
TRACK_ALLOC(out_addr, size, "VMEM");
|
||||
|
||||
auto& new_vma = CarveVMA(mapped_addr, size)->second;
|
||||
new_vma.disallow_merge = false;
|
||||
new_vma.prot = prot;
|
||||
new_vma.name = "";
|
||||
new_vma.type = Core::VMAType::Pooled;
|
||||
new_vma.is_exec = false;
|
||||
new_vma.phys_base = 0;
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
|
||||
MemoryMapFlags flags, VMAType type, std::string_view name,
|
||||
bool is_exec, PAddr phys_addr, u64 alignment) {
|
||||
|
@ -232,6 +328,39 @@ int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, Mem
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void MemoryManager::PoolDecommit(VAddr virtual_addr, size_t size) {
|
||||
std::scoped_lock lk{mutex};
|
||||
|
||||
const auto it = FindVMA(virtual_addr);
|
||||
const auto& vma_base = it->second;
|
||||
ASSERT_MSG(vma_base.Contains(virtual_addr, size),
|
||||
"Existing mapping does not contain requested unmap range");
|
||||
|
||||
const auto vma_base_addr = vma_base.base;
|
||||
const auto vma_base_size = vma_base.size;
|
||||
const auto phys_base = vma_base.phys_base;
|
||||
const bool is_exec = vma_base.is_exec;
|
||||
const auto start_in_vma = virtual_addr - vma_base_addr;
|
||||
const auto type = vma_base.type;
|
||||
|
||||
rasterizer->UnmapMemory(virtual_addr, size);
|
||||
|
||||
// Mark region as free and attempt to coalesce it with neighbours.
|
||||
const auto new_it = CarveVMA(virtual_addr, size);
|
||||
auto& vma = new_it->second;
|
||||
vma.type = VMAType::PoolReserved;
|
||||
vma.prot = MemoryProt::NoAccess;
|
||||
vma.phys_base = 0;
|
||||
vma.disallow_merge = false;
|
||||
vma.name = "";
|
||||
MergeAdjacent(vma_map, new_it);
|
||||
|
||||
// Unmap the memory region.
|
||||
impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + size, phys_base, is_exec,
|
||||
false, false);
|
||||
TRACK_FREE(virtual_addr, "VMEM");
|
||||
}
|
||||
|
||||
void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) {
|
||||
std::scoped_lock lk{mutex};
|
||||
UnmapMemoryImpl(virtual_addr, size);
|
||||
|
|
|
@ -50,15 +50,17 @@ enum class VMAType : u32 {
|
|||
Direct = 2,
|
||||
Flexible = 3,
|
||||
Pooled = 4,
|
||||
Stack = 5,
|
||||
Code = 6,
|
||||
File = 7,
|
||||
PoolReserved = 5,
|
||||
Stack = 6,
|
||||
Code = 7,
|
||||
File = 8,
|
||||
};
|
||||
|
||||
struct DirectMemoryArea {
|
||||
PAddr base = 0;
|
||||
size_t size = 0;
|
||||
int memory_type = 0;
|
||||
bool is_pooled = false;
|
||||
bool is_free = true;
|
||||
|
||||
PAddr GetEnd() const {
|
||||
|
@ -96,7 +98,7 @@ struct VirtualMemoryArea {
|
|||
}
|
||||
|
||||
bool IsMapped() const noexcept {
|
||||
return type != VMAType::Free && type != VMAType::Reserved;
|
||||
return type != VMAType::Free && type != VMAType::Reserved && type != VMAType::PoolReserved;
|
||||
}
|
||||
|
||||
bool CanMergeWith(const VirtualMemoryArea& next) const {
|
||||
|
@ -135,6 +137,10 @@ public:
|
|||
return total_direct_size;
|
||||
}
|
||||
|
||||
u64 GetTotalFlexibleSize() const {
|
||||
return total_flexible_size;
|
||||
}
|
||||
|
||||
u64 GetAvailableFlexibleSize() const {
|
||||
return total_flexible_size - flexible_usage;
|
||||
}
|
||||
|
@ -145,14 +151,21 @@ public:
|
|||
|
||||
void SetupMemoryRegions(u64 flexible_size);
|
||||
|
||||
PAddr PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment);
|
||||
|
||||
PAddr Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment,
|
||||
int memory_type);
|
||||
|
||||
void Free(PAddr phys_addr, size_t size);
|
||||
|
||||
int PoolReserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
|
||||
u64 alignment = 0);
|
||||
|
||||
int Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
|
||||
u64 alignment = 0);
|
||||
|
||||
int PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot);
|
||||
|
||||
int MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
|
||||
MemoryMapFlags flags, VMAType type, std::string_view name = "",
|
||||
bool is_exec = false, PAddr phys_addr = -1, u64 alignment = 0);
|
||||
|
@ -160,6 +173,8 @@ public:
|
|||
int MapFile(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
|
||||
MemoryMapFlags flags, uintptr_t fd, size_t offset);
|
||||
|
||||
void PoolDecommit(VAddr virtual_addr, size_t size);
|
||||
|
||||
void UnmapMemory(VAddr virtual_addr, size_t size);
|
||||
|
||||
int QueryProtection(VAddr addr, void** start, void** end, u32* prot);
|
||||
|
|
Before Width: | Height: | Size: 572 B After Width: | Height: | Size: 324 B |
Before Width: | Height: | Size: 942 B After Width: | Height: | Size: 453 B |
Before Width: | Height: | Size: 371 B After Width: | Height: | Size: 278 B |
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 545 B |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.4 KiB |
|
@ -127,7 +127,7 @@ void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
|
|||
bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
|
||||
if (m_sdl_gamepad != nullptr) {
|
||||
return SDL_RumbleGamepad(m_sdl_gamepad, (smallMotor / 255.0f) * 0xFFFF,
|
||||
(largeMotor / 255.0f) * 0xFFFF, -1) == 0;
|
||||
(largeMotor / 255.0f) * 0xFFFF, -1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,12 @@ BackgroundMusicPlayer::BackgroundMusicPlayer(QObject* parent) : QObject(parent)
|
|||
m_mediaPlayer->setLoops(QMediaPlayer::Infinite);
|
||||
}
|
||||
|
||||
void BackgroundMusicPlayer::setVolume(int volume) {
|
||||
float linearVolume = QAudio::convertVolume(volume / 100.0f, QAudio::LogarithmicVolumeScale,
|
||||
QAudio::LinearVolumeScale);
|
||||
m_audioOutput->setVolume(linearVolume);
|
||||
}
|
||||
|
||||
void BackgroundMusicPlayer::playMusic(const QString& snd0path) {
|
||||
if (snd0path.isEmpty()) {
|
||||
stopMusic();
|
||||
|
|
|
@ -16,6 +16,7 @@ public:
|
|||
return instance;
|
||||
}
|
||||
|
||||
void setVolume(int volume);
|
||||
void playMusic(const QString& snd0path);
|
||||
void stopMusic();
|
||||
|
||||
|
@ -25,4 +26,4 @@ private:
|
|||
QMediaPlayer* m_mediaPlayer;
|
||||
QAudioOutput* m_audioOutput;
|
||||
QUrl m_currentMusic;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -456,10 +456,9 @@ void CheatsPatches::downloadCheats(const QString& source, const QString& gameSer
|
|||
if (source == "GoldHEN") {
|
||||
url = "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/main/json.txt";
|
||||
} else if (source == "wolf2022") {
|
||||
url = "https://wolf2022.ir/trainer/" + gameSerial + "_" + gameVersion + ".json";
|
||||
url = "https://wolf2022.ir/trainer/list.json";
|
||||
} else if (source == "shadPS4") {
|
||||
url = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/main/"
|
||||
"CHEATS_JSON.txt";
|
||||
url = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/main/CHEATS_JSON.txt";
|
||||
} else {
|
||||
QMessageBox::warning(this, tr("Invalid Source"),
|
||||
QString(tr("The selected source is invalid.") + "\n%1").arg(source));
|
||||
|
@ -474,44 +473,32 @@ void CheatsPatches::downloadCheats(const QString& source, const QString& gameSer
|
|||
QByteArray jsonData = reply->readAll();
|
||||
bool foundFiles = false;
|
||||
|
||||
if (source == "GoldHEN" || source == "shadPS4") {
|
||||
QString textContent(jsonData);
|
||||
QRegularExpression regex(
|
||||
QString("%1_%2[^=]*\\.json").arg(gameSerial).arg(gameVersion));
|
||||
QRegularExpressionMatchIterator matches = regex.globalMatch(textContent);
|
||||
QString baseUrl;
|
||||
if (source == "wolf2022") {
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
|
||||
QJsonArray gamesArray = jsonDoc.object().value("games").toArray();
|
||||
|
||||
if (source == "GoldHEN") {
|
||||
baseUrl = "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/"
|
||||
"main/json/";
|
||||
} else {
|
||||
baseUrl = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/"
|
||||
"main/CHEATS/";
|
||||
}
|
||||
foreach (const QJsonValue& value, gamesArray) {
|
||||
QJsonObject gameObject = value.toObject();
|
||||
QString title = gameObject.value("title").toString();
|
||||
QString version = gameObject.value("version").toString();
|
||||
|
||||
while (matches.hasNext()) {
|
||||
QRegularExpressionMatch match = matches.next();
|
||||
QString fileName = match.captured(0);
|
||||
if (title == gameSerial &&
|
||||
(version == gameVersion || version == gameVersion.mid(1))) {
|
||||
QString fileUrl =
|
||||
"https://wolf2022.ir/trainer/" + gameObject.value("url").toString();
|
||||
|
||||
if (!fileName.isEmpty()) {
|
||||
QString newFileName = fileName;
|
||||
int dotIndex = newFileName.lastIndexOf('.');
|
||||
if (dotIndex != -1) {
|
||||
QString localFileName = gameObject.value("url").toString();
|
||||
localFileName =
|
||||
localFileName.left(localFileName.lastIndexOf('.')) + "_wolf2022.json";
|
||||
|
||||
if (source == "GoldHEN") {
|
||||
newFileName.insert(dotIndex, "_GoldHEN");
|
||||
} else {
|
||||
newFileName.insert(dotIndex, "_shadPS4");
|
||||
}
|
||||
}
|
||||
QString fileUrl = baseUrl + fileName;
|
||||
QString localFilePath = dir.filePath(newFileName);
|
||||
QString localFilePath = dir.filePath(localFileName);
|
||||
|
||||
if (QFile::exists(localFilePath) && showMessageBox) {
|
||||
QMessageBox::StandardButton reply;
|
||||
reply = QMessageBox::question(
|
||||
this, tr("File Exists"),
|
||||
tr("File already exists. Do you want to replace it?"),
|
||||
tr("File already exists. Do you want to replace it?") + "\n" +
|
||||
localFileName,
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (reply == QMessageBox::No) {
|
||||
continue;
|
||||
|
@ -549,38 +536,81 @@ void CheatsPatches::downloadCheats(const QString& source, const QString& gameSer
|
|||
if (!foundFiles && showMessageBox) {
|
||||
QMessageBox::warning(this, tr("Cheats Not Found"), tr("CheatsNotFound_MSG"));
|
||||
}
|
||||
} else if (source == "wolf2022") {
|
||||
QString fileName = QFileInfo(QUrl(url).path()).fileName();
|
||||
QString baseFileName = fileName;
|
||||
int dotIndex = baseFileName.lastIndexOf('.');
|
||||
if (dotIndex != -1) {
|
||||
baseFileName.insert(dotIndex, "_wolf2022");
|
||||
} else if (source == "GoldHEN" || source == "shadPS4") {
|
||||
QString textContent(jsonData);
|
||||
QRegularExpression regex(
|
||||
QString("%1_%2[^=]*\\.json").arg(gameSerial).arg(gameVersion));
|
||||
QRegularExpressionMatchIterator matches = regex.globalMatch(textContent);
|
||||
QString baseUrl;
|
||||
|
||||
if (source == "GoldHEN") {
|
||||
baseUrl = "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/"
|
||||
"main/json/";
|
||||
} else {
|
||||
baseUrl = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/"
|
||||
"main/CHEATS/";
|
||||
}
|
||||
QString filePath;
|
||||
Common::FS::PathToQString(filePath,
|
||||
Common::FS::GetUserPath(Common::FS::PathType::CheatsDir));
|
||||
filePath += "/" + baseFileName;
|
||||
if (QFile::exists(filePath) && showMessageBox) {
|
||||
QMessageBox::StandardButton reply2;
|
||||
reply2 =
|
||||
QMessageBox::question(this, tr("File Exists"),
|
||||
tr("File already exists. Do you want to replace it?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (reply2 == QMessageBox::No) {
|
||||
reply->deleteLater();
|
||||
return;
|
||||
|
||||
while (matches.hasNext()) {
|
||||
QRegularExpressionMatch match = matches.next();
|
||||
QString fileName = match.captured(0);
|
||||
|
||||
if (!fileName.isEmpty()) {
|
||||
QString newFileName = fileName;
|
||||
int dotIndex = newFileName.lastIndexOf('.');
|
||||
if (dotIndex != -1) {
|
||||
|
||||
if (source == "GoldHEN") {
|
||||
newFileName.insert(dotIndex, "_GoldHEN");
|
||||
} else {
|
||||
newFileName.insert(dotIndex, "_shadPS4");
|
||||
}
|
||||
}
|
||||
QString fileUrl = baseUrl + fileName;
|
||||
QString localFilePath = dir.filePath(newFileName);
|
||||
|
||||
if (QFile::exists(localFilePath) && showMessageBox) {
|
||||
QMessageBox::StandardButton reply;
|
||||
reply = QMessageBox::question(
|
||||
this, tr("File Exists"),
|
||||
tr("File already exists. Do you want to replace it?") + "\n" +
|
||||
newFileName,
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (reply == QMessageBox::No) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
QNetworkRequest fileRequest(fileUrl);
|
||||
QNetworkReply* fileReply = manager->get(fileRequest);
|
||||
|
||||
connect(fileReply, &QNetworkReply::finished, [=, this]() {
|
||||
if (fileReply->error() == QNetworkReply::NoError) {
|
||||
QByteArray fileData = fileReply->readAll();
|
||||
QFile localFile(localFilePath);
|
||||
if (localFile.open(QIODevice::WriteOnly)) {
|
||||
localFile.write(fileData);
|
||||
localFile.close();
|
||||
} else {
|
||||
QMessageBox::warning(
|
||||
this, tr("Error"),
|
||||
QString(tr("Failed to save file:") + "\n%1")
|
||||
.arg(localFilePath));
|
||||
}
|
||||
} else {
|
||||
QMessageBox::warning(this, tr("Error"),
|
||||
QString(tr("Failed to download file:") +
|
||||
"%1\n\n" + tr("Error:") + "%2")
|
||||
.arg(fileUrl)
|
||||
.arg(fileReply->errorString()));
|
||||
}
|
||||
fileReply->deleteLater();
|
||||
});
|
||||
|
||||
foundFiles = true;
|
||||
}
|
||||
}
|
||||
QFile cheatFile(filePath);
|
||||
if (cheatFile.open(QIODevice::WriteOnly)) {
|
||||
cheatFile.write(jsonData);
|
||||
cheatFile.close();
|
||||
foundFiles = true;
|
||||
populateFileListCheats();
|
||||
} else {
|
||||
QMessageBox::warning(
|
||||
this, tr("Error"),
|
||||
QString(tr("Failed to save file:") + "\n%1").arg(filePath));
|
||||
if (!foundFiles && showMessageBox) {
|
||||
QMessageBox::warning(this, tr("Cheats Not Found"), tr("CheatsNotFound_MSG"));
|
||||
}
|
||||
}
|
||||
if (foundFiles && showMessageBox) {
|
||||
|
@ -910,11 +940,16 @@ void CheatsPatches::addCheatsToLayout(const QJsonArray& modsArray, const QJsonAr
|
|||
void CheatsPatches::populateFileListCheats() {
|
||||
QString cheatsDir;
|
||||
Common::FS::PathToQString(cheatsDir, Common::FS::GetUserPath(Common::FS::PathType::CheatsDir));
|
||||
QString pattern = m_gameSerial + "_" + m_gameVersion + "*.json";
|
||||
|
||||
QString fullGameVersion = m_gameVersion;
|
||||
QString modifiedGameVersion = m_gameVersion.mid(1);
|
||||
|
||||
QString patternWithFirstChar = m_gameSerial + "_" + fullGameVersion + "*.json";
|
||||
QString patternWithoutFirstChar = m_gameSerial + "_" + modifiedGameVersion + "*.json";
|
||||
|
||||
QDir dir(cheatsDir);
|
||||
QStringList filters;
|
||||
filters << pattern;
|
||||
filters << patternWithFirstChar << patternWithoutFirstChar;
|
||||
dir.setNameFilters(filters);
|
||||
|
||||
QFileInfoList fileList = dir.entryInfoList(QDir::Files);
|
||||
|
@ -1248,4 +1283,4 @@ void CheatsPatches::onPatchCheckBoxHovered(QCheckBox* checkBox, bool hovered) {
|
|||
} else {
|
||||
instructionsTextEdit->setText(defaultTextEdit);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@
|
|||
#include <common/path_util.h>
|
||||
#include <common/scm_rev.h>
|
||||
#include <common/version.h>
|
||||
#include <qprogressbar.h>
|
||||
#include "check_update.h"
|
||||
|
||||
using namespace Common::FS;
|
||||
|
@ -313,15 +314,32 @@ void CheckUpdate::requestChangelog(const QString& currentRev, const QString& lat
|
|||
}
|
||||
|
||||
void CheckUpdate::DownloadUpdate(const QString& url) {
|
||||
QProgressBar* progressBar = new QProgressBar(this);
|
||||
progressBar->setRange(0, 100);
|
||||
progressBar->setTextVisible(true);
|
||||
progressBar->setValue(0);
|
||||
|
||||
layout()->addWidget(progressBar);
|
||||
|
||||
QNetworkRequest request(url);
|
||||
QNetworkReply* reply = networkManager->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply, url]() {
|
||||
connect(reply, &QNetworkReply::downloadProgress, this,
|
||||
[progressBar](qint64 bytesReceived, qint64 bytesTotal) {
|
||||
if (bytesTotal > 0) {
|
||||
int percentage = static_cast<int>((bytesReceived * 100) / bytesTotal);
|
||||
progressBar->setValue(percentage);
|
||||
}
|
||||
});
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply, progressBar, url]() {
|
||||
progressBar->setValue(100);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
QMessageBox::warning(this, tr("Error"),
|
||||
tr("Network error occurred while trying to access the URL") +
|
||||
":\n" + url + "\n" + reply->errorString());
|
||||
reply->deleteLater();
|
||||
progressBar->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -348,6 +366,7 @@ void CheckUpdate::DownloadUpdate(const QString& url) {
|
|||
}
|
||||
|
||||
reply->deleteLater();
|
||||
progressBar->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidg
|
|||
this->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
PopulateGameGrid(m_game_info->m_games, false);
|
||||
|
||||
connect(this, &QTableWidget::cellClicked, this, &GameGridFrame::SetGridBackgroundImage);
|
||||
connect(this, &QTableWidget::currentCellChanged, this, &GameGridFrame::onCurrentCellChanged);
|
||||
|
||||
connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this,
|
||||
&GameGridFrame::RefreshGridBackgroundImage);
|
||||
|
@ -31,22 +31,33 @@ GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidg
|
|||
connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) {
|
||||
m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, this, false);
|
||||
});
|
||||
connect(this, &QTableWidget::cellClicked, this, [&]() {
|
||||
cellClicked = true;
|
||||
crtRow = this->currentRow();
|
||||
crtColumn = this->currentColumn();
|
||||
columnCnt = this->columnCount();
|
||||
});
|
||||
}
|
||||
|
||||
void GameGridFrame::PlayBackgroundMusic(QTableWidgetItem* item) {
|
||||
if (!item) {
|
||||
void GameGridFrame::onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
|
||||
int previousColumn) {
|
||||
cellClicked = true;
|
||||
crtRow = currentRow;
|
||||
crtColumn = currentColumn;
|
||||
columnCnt = this->columnCount();
|
||||
|
||||
auto itemID = (crtRow * columnCnt) + currentColumn;
|
||||
if (itemID > m_game_info->m_games.count() - 1) {
|
||||
validCellSelected = false;
|
||||
BackgroundMusicPlayer::getInstance().stopMusic();
|
||||
return;
|
||||
}
|
||||
QString snd0path;
|
||||
Common::FS::PathToQString(snd0path, m_game_info->m_games[item->row()].snd0_path);
|
||||
BackgroundMusicPlayer::getInstance().playMusic(snd0path);
|
||||
validCellSelected = true;
|
||||
SetGridBackgroundImage(crtRow, crtColumn);
|
||||
auto snd0Path = QString::fromStdString(m_game_info->m_games[itemID].snd0_path.string());
|
||||
PlayBackgroundMusic(snd0Path);
|
||||
}
|
||||
|
||||
void GameGridFrame::PlayBackgroundMusic(QString path) {
|
||||
if (path.isEmpty()) {
|
||||
BackgroundMusicPlayer::getInstance().stopMusic();
|
||||
return;
|
||||
}
|
||||
BackgroundMusicPlayer::getInstance().playMusic(path);
|
||||
}
|
||||
|
||||
void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool fromSearch) {
|
||||
|
@ -157,3 +168,7 @@ void GameGridFrame::RefreshGridBackgroundImage() {
|
|||
this->setPalette(palette);
|
||||
}
|
||||
}
|
||||
|
||||
bool GameGridFrame::IsValidCellSelected() {
|
||||
return validCellSelected;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,9 @@ Q_SIGNALS:
|
|||
public Q_SLOTS:
|
||||
void SetGridBackgroundImage(int row, int column);
|
||||
void RefreshGridBackgroundImage();
|
||||
void PlayBackgroundMusic(QTableWidgetItem* item);
|
||||
void PlayBackgroundMusic(QString path);
|
||||
void onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
|
||||
int previousColumn);
|
||||
|
||||
private:
|
||||
QImage backgroundImage;
|
||||
|
@ -28,10 +30,12 @@ private:
|
|||
GuiContextMenus m_gui_context_menus;
|
||||
std::shared_ptr<GameInfoClass> m_game_info;
|
||||
std::shared_ptr<QVector<GameInfo>> m_games_shared;
|
||||
bool validCellSelected = false;
|
||||
|
||||
public:
|
||||
explicit GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent = nullptr);
|
||||
void PopulateGameGrid(QVector<GameInfo> m_games, bool fromSearch);
|
||||
bool IsValidCellSelected();
|
||||
|
||||
bool cellClicked = false;
|
||||
int icon_size;
|
||||
|
|
|
@ -18,6 +18,7 @@ GameInstallDialog::GameInstallDialog() : m_gamesDirectory(nullptr) {
|
|||
auto layout = new QVBoxLayout(this);
|
||||
|
||||
layout->addWidget(SetupGamesDirectory());
|
||||
layout->addWidget(SetupAddonsDirectory());
|
||||
layout->addStretch();
|
||||
layout->addWidget(SetupDialogActions());
|
||||
|
||||
|
@ -27,7 +28,7 @@ GameInstallDialog::GameInstallDialog() : m_gamesDirectory(nullptr) {
|
|||
|
||||
GameInstallDialog::~GameInstallDialog() {}
|
||||
|
||||
void GameInstallDialog::Browse() {
|
||||
void GameInstallDialog::BrowseGamesDirectory() {
|
||||
auto path = QFileDialog::getExistingDirectory(this, tr("Directory to install games"));
|
||||
|
||||
if (!path.isEmpty()) {
|
||||
|
@ -35,6 +36,14 @@ void GameInstallDialog::Browse() {
|
|||
}
|
||||
}
|
||||
|
||||
void GameInstallDialog::BrowseAddonsDirectory() {
|
||||
auto path = QFileDialog::getExistingDirectory(this, tr("Directory to install DLC"));
|
||||
|
||||
if (!path.isEmpty()) {
|
||||
m_addonsDirectory->setText(QDir::toNativeSeparators(path));
|
||||
}
|
||||
}
|
||||
|
||||
QWidget* GameInstallDialog::SetupGamesDirectory() {
|
||||
auto group = new QGroupBox(tr("Directory to install games"));
|
||||
auto layout = new QHBoxLayout(group);
|
||||
|
@ -51,7 +60,30 @@ QWidget* GameInstallDialog::SetupGamesDirectory() {
|
|||
// Browse button.
|
||||
auto browse = new QPushButton(tr("Browse"));
|
||||
|
||||
connect(browse, &QPushButton::clicked, this, &GameInstallDialog::Browse);
|
||||
connect(browse, &QPushButton::clicked, this, &GameInstallDialog::BrowseGamesDirectory);
|
||||
|
||||
layout->addWidget(browse);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
QWidget* GameInstallDialog::SetupAddonsDirectory() {
|
||||
auto group = new QGroupBox(tr("Directory to install DLC"));
|
||||
auto layout = new QHBoxLayout(group);
|
||||
|
||||
// Input.
|
||||
m_addonsDirectory = new QLineEdit();
|
||||
QString install_dir;
|
||||
Common::FS::PathToQString(install_dir, Config::getAddonInstallDir());
|
||||
m_addonsDirectory->setText(install_dir);
|
||||
m_addonsDirectory->setMinimumWidth(400);
|
||||
|
||||
layout->addWidget(m_addonsDirectory);
|
||||
|
||||
// Browse button.
|
||||
auto browse = new QPushButton(tr("Browse"));
|
||||
|
||||
connect(browse, &QPushButton::clicked, this, &GameInstallDialog::BrowseAddonsDirectory);
|
||||
|
||||
layout->addWidget(browse);
|
||||
|
||||
|
@ -70,6 +102,7 @@ QWidget* GameInstallDialog::SetupDialogActions() {
|
|||
void GameInstallDialog::Save() {
|
||||
// Check games directory.
|
||||
auto gamesDirectory = m_gamesDirectory->text();
|
||||
auto addonsDirectory = m_addonsDirectory->text();
|
||||
|
||||
if (gamesDirectory.isEmpty() || !QDir(gamesDirectory).exists() ||
|
||||
!QDir::isAbsolutePath(gamesDirectory)) {
|
||||
|
@ -78,7 +111,22 @@ void GameInstallDialog::Save() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (addonsDirectory.isEmpty() || !QDir::isAbsolutePath(addonsDirectory)) {
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
"The value for location to install DLC is not valid.");
|
||||
return;
|
||||
}
|
||||
QDir addonsDir(addonsDirectory);
|
||||
if (!addonsDir.exists()) {
|
||||
if (!addonsDir.mkpath(".")) {
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
"The DLC install location could not be created.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Config::setGameInstallDir(Common::FS::PathFromQString(gamesDirectory));
|
||||
Config::setAddonInstallDir(Common::FS::PathFromQString(addonsDirectory));
|
||||
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||
Config::save(config_dir / "config.toml");
|
||||
accept();
|
||||
|
|
|
@ -16,13 +16,16 @@ public:
|
|||
~GameInstallDialog();
|
||||
|
||||
private slots:
|
||||
void Browse();
|
||||
void BrowseGamesDirectory();
|
||||
void BrowseAddonsDirectory();
|
||||
|
||||
private:
|
||||
QWidget* SetupGamesDirectory();
|
||||
QWidget* SetupAddonsDirectory();
|
||||
QWidget* SetupDialogActions();
|
||||
void Save();
|
||||
|
||||
private:
|
||||
QLineEdit* m_gamesDirectory;
|
||||
QLineEdit* m_addonsDirectory;
|
||||
};
|
|
@ -41,7 +41,7 @@ GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidg
|
|||
this->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
|
||||
PopulateGameList();
|
||||
|
||||
connect(this, &QTableWidget::itemClicked, this, &GameListFrame::SetListBackgroundImage);
|
||||
connect(this, &QTableWidget::currentCellChanged, this, &GameListFrame::onCurrentCellChanged);
|
||||
connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this,
|
||||
&GameListFrame::RefreshListBackgroundImage);
|
||||
connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this,
|
||||
|
@ -69,6 +69,16 @@ GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidg
|
|||
});
|
||||
}
|
||||
|
||||
void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
|
||||
int previousColumn) {
|
||||
QTableWidgetItem* item = this->item(currentRow, currentColumn);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
SetListBackgroundImage(item);
|
||||
PlayBackgroundMusic(item);
|
||||
}
|
||||
|
||||
void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) {
|
||||
if (!item) {
|
||||
BackgroundMusicPlayer::getInstance().stopMusic();
|
||||
|
|
|
@ -23,6 +23,8 @@ public Q_SLOTS:
|
|||
void SortNameAscending(int columnIndex);
|
||||
void SortNameDescending(int columnIndex);
|
||||
void PlayBackgroundMusic(QTableWidgetItem* item);
|
||||
void onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
|
||||
int previousColumn);
|
||||
|
||||
private:
|
||||
void SetTableItem(int row, int column, QString itemStr);
|
||||
|
@ -43,24 +45,44 @@ public:
|
|||
int icon_size;
|
||||
|
||||
static bool CompareStringsAscending(GameInfo a, GameInfo b, int columnIndex) {
|
||||
if (columnIndex == 1) {
|
||||
switch (columnIndex) {
|
||||
case 1:
|
||||
return a.name < b.name;
|
||||
} else if (columnIndex == 2) {
|
||||
case 2:
|
||||
return a.serial < b.serial;
|
||||
} else if (columnIndex == 3) {
|
||||
case 3:
|
||||
return a.region < b.region;
|
||||
case 4:
|
||||
return a.fw < b.fw;
|
||||
case 5:
|
||||
return a.size < b.size;
|
||||
case 6:
|
||||
return a.version < b.version;
|
||||
case 7:
|
||||
return a.path < b.path;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool CompareStringsDescending(GameInfo a, GameInfo b, int columnIndex) {
|
||||
if (columnIndex == 1) {
|
||||
switch (columnIndex) {
|
||||
case 1:
|
||||
return a.name > b.name;
|
||||
} else if (columnIndex == 2) {
|
||||
case 2:
|
||||
return a.serial > b.serial;
|
||||
} else if (columnIndex == 3) {
|
||||
case 3:
|
||||
return a.region > b.region;
|
||||
case 4:
|
||||
return a.fw > b.fw;
|
||||
case 5:
|
||||
return a.size > b.size;
|
||||
case 6:
|
||||
return a.version > b.version;
|
||||
case 7:
|
||||
return a.path > b.path;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
|
@ -312,10 +312,7 @@ public:
|
|||
|
||||
if (selected == &installPackage) {
|
||||
QStringList pkg_app_ = m_pkg_app_list[itemIndex].split(";;");
|
||||
std::filesystem::path path(pkg_app_[9].toStdString());
|
||||
#ifdef _WIN32
|
||||
path = std::filesystem::path(pkg_app_[9].toStdWString());
|
||||
#endif
|
||||
std::filesystem::path path = Common::FS::PathFromQString(pkg_app_[9]);
|
||||
InstallDragDropPkg(path, 1, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,14 +2,15 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QKeyEvent>
|
||||
#include <QProgressDialog>
|
||||
|
||||
#include <common/scm_rev.h>
|
||||
#include "about_dialog.h"
|
||||
#include "cheats_patches.h"
|
||||
#include "check_update.h"
|
||||
#include "common/io_file.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/version.h"
|
||||
#include "core/file_format/pkg.h"
|
||||
|
@ -21,6 +22,7 @@
|
|||
|
||||
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
|
||||
ui->setupUi(this);
|
||||
installEventFilter(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
}
|
||||
|
||||
|
@ -306,6 +308,7 @@ void MainWindow::CreateConnects() {
|
|||
});
|
||||
// List
|
||||
connect(ui->setlistModeListAct, &QAction::triggered, m_dock_widget.data(), [this]() {
|
||||
BackgroundMusicPlayer::getInstance().stopMusic();
|
||||
m_dock_widget->setWidget(m_game_list_frame.data());
|
||||
m_game_grid_frame->hide();
|
||||
m_elf_viewer->hide();
|
||||
|
@ -322,6 +325,7 @@ void MainWindow::CreateConnects() {
|
|||
});
|
||||
// Grid
|
||||
connect(ui->setlistModeGridAct, &QAction::triggered, m_dock_widget.data(), [this]() {
|
||||
BackgroundMusicPlayer::getInstance().stopMusic();
|
||||
m_dock_widget->setWidget(m_game_grid_frame.data());
|
||||
m_game_grid_frame->show();
|
||||
m_game_list_frame->hide();
|
||||
|
@ -338,6 +342,7 @@ void MainWindow::CreateConnects() {
|
|||
});
|
||||
// Elf
|
||||
connect(ui->setlistElfAct, &QAction::triggered, m_dock_widget.data(), [this]() {
|
||||
BackgroundMusicPlayer::getInstance().stopMusic();
|
||||
m_dock_widget->setWidget(m_elf_viewer.data());
|
||||
m_game_grid_frame->hide();
|
||||
m_game_list_frame->hide();
|
||||
|
@ -512,25 +517,6 @@ void MainWindow::CreateConnects() {
|
|||
isIconBlack = false;
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_game_grid_frame.get(), &QTableWidget::cellClicked, this,
|
||||
&MainWindow::PlayBackgroundMusic);
|
||||
connect(m_game_list_frame.get(), &QTableWidget::cellClicked, this,
|
||||
&MainWindow::PlayBackgroundMusic);
|
||||
}
|
||||
|
||||
void MainWindow::PlayBackgroundMusic() {
|
||||
if (isGameRunning || !Config::getPlayBGM()) {
|
||||
BackgroundMusicPlayer::getInstance().stopMusic();
|
||||
return;
|
||||
}
|
||||
int itemID = isTableList ? m_game_list_frame->currentItem()->row()
|
||||
: m_game_grid_frame->crtRow * m_game_grid_frame->columnCnt +
|
||||
m_game_grid_frame->crtColumn;
|
||||
|
||||
QString snd0path;
|
||||
Common::FS::PathToQString(snd0path, m_game_info->m_games[itemID].snd0_path);
|
||||
BackgroundMusicPlayer::getInstance().playMusic(snd0path);
|
||||
}
|
||||
|
||||
void MainWindow::StartGame() {
|
||||
|
@ -619,6 +605,7 @@ void MainWindow::ConfigureGuiFromSettings() {
|
|||
} else {
|
||||
ui->setlistModeGridAct->setChecked(true);
|
||||
}
|
||||
BackgroundMusicPlayer::getInstance().setVolume(Config::getBGMvolume());
|
||||
}
|
||||
|
||||
void MainWindow::SaveWindowState() const {
|
||||
|
@ -638,10 +625,7 @@ void MainWindow::InstallPkg() {
|
|||
int pkgNum = 0;
|
||||
for (const QString& file : fileNames) {
|
||||
++pkgNum;
|
||||
std::filesystem::path path(file.toStdString());
|
||||
#ifdef _WIN64
|
||||
path = std::filesystem::path(file.toStdWString());
|
||||
#endif
|
||||
std::filesystem::path path = Common::FS::PathFromQString(file);
|
||||
MainWindow::InstallDragDropPkg(path, pkgNum, nPkg);
|
||||
}
|
||||
}
|
||||
|
@ -659,10 +643,7 @@ void MainWindow::BootGame() {
|
|||
QMessageBox::critical(nullptr, tr("Game Boot"),
|
||||
QString(tr("Only one file can be selected!")));
|
||||
} else {
|
||||
std::filesystem::path path(fileNames[0].toStdString());
|
||||
#ifdef _WIN64
|
||||
path = std::filesystem::path(fileNames[0].toStdWString());
|
||||
#endif
|
||||
std::filesystem::path path = Common::FS::PathFromQString(fileNames[0]);
|
||||
Core::Emulator emulator;
|
||||
if (!std::filesystem::exists(path)) {
|
||||
QMessageBox::critical(nullptr, tr("Run Game"),
|
||||
|
@ -676,9 +657,12 @@ void MainWindow::BootGame() {
|
|||
|
||||
void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int nPkg) {
|
||||
if (Loader::DetectFileType(file) == Loader::FileTypes::Pkg) {
|
||||
pkg = PKG();
|
||||
pkg.Open(file);
|
||||
std::string failreason;
|
||||
pkg = PKG();
|
||||
if (!pkg.Open(file, failreason)) {
|
||||
QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason));
|
||||
return;
|
||||
}
|
||||
auto extract_path = Config::getGameInstallDir() / pkg.GetTitleID();
|
||||
QString pkgType = QString::fromStdString(pkg.GetPkgFlags());
|
||||
QString gameDirPath;
|
||||
|
@ -703,8 +687,8 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
|
|||
}
|
||||
std::string entitlement_label = Common::SplitString(content_id, '-')[2];
|
||||
|
||||
auto addon_extract_path = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) /
|
||||
pkg.GetTitleID() / entitlement_label;
|
||||
auto addon_extract_path =
|
||||
Config::getAddonInstallDir() / pkg.GetTitleID() / entitlement_label;
|
||||
QString addonDirPath;
|
||||
Common::FS::PathToQString(addonDirPath, addon_extract_path);
|
||||
QDir addon_dir(addonDirPath);
|
||||
|
@ -784,7 +768,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
|
|||
}
|
||||
}
|
||||
} else {
|
||||
msgBox.setText(QString(tr("Game already installed") + "\n" + addonDirPath + "\n" +
|
||||
msgBox.setText(QString(tr("Game already installed") + "\n" + gameDirPath + "\n" +
|
||||
tr("Would you like to overwrite?")));
|
||||
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
msgBox.setDefaultButton(QMessageBox::No);
|
||||
|
@ -1042,3 +1026,17 @@ void MainWindow::OnLanguageChanged(const std::string& locale) {
|
|||
|
||||
LoadTranslation();
|
||||
}
|
||||
|
||||
bool MainWindow::eventFilter(QObject* obj, QEvent* event) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
|
||||
auto tblMode = Config::getTableMode();
|
||||
if (tblMode != 2 && (tblMode != 1 || m_game_grid_frame->IsValidCellSelected())) {
|
||||
StartGame();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return QMainWindow::eventFilter(obj, event);
|
||||
}
|
||||
|
|
|
@ -94,6 +94,8 @@ private:
|
|||
QTranslator* translator;
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
|
||||
void dragEnterEvent(QDragEnterEvent* event1) override {
|
||||
if (event1->mimeData()->hasUrls()) {
|
||||
event1->acceptProposedAction();
|
||||
|
@ -108,10 +110,7 @@ protected:
|
|||
int nPkg = urlList.size();
|
||||
for (const QUrl& url : urlList) {
|
||||
pkgNum++;
|
||||
std::filesystem::path path(url.toLocalFile().toStdString());
|
||||
#ifdef _WIN64
|
||||
path = std::filesystem::path(url.toLocalFile().toStdWString());
|
||||
#endif
|
||||
std::filesystem::path path = Common::FS::PathFromQString(url.toLocalFile());
|
||||
InstallDragDropPkg(path, pkgNum, nPkg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,11 +104,12 @@ void PKGViewer::ProcessPKGInfo() {
|
|||
m_pkg_patch_list.clear();
|
||||
m_full_pkg_list.clear();
|
||||
for (int i = 0; i < m_pkg_list.size(); i++) {
|
||||
std::filesystem::path path(m_pkg_list[i].toStdString());
|
||||
#ifdef _WIN32
|
||||
path = std::filesystem::path(m_pkg_list[i].toStdWString());
|
||||
#endif
|
||||
package.Open(path);
|
||||
std::filesystem::path path = Common::FS::PathFromQString(m_pkg_list[i]);
|
||||
std::string failreason;
|
||||
if (!package.Open(path, failreason)) {
|
||||
QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason));
|
||||
return;
|
||||
}
|
||||
psf.Open(package.sfo);
|
||||
QString title_name =
|
||||
QString::fromStdString(std::string{psf.GetString("TITLE").value_or("Unknown")});
|
||||
|
|
|
@ -70,7 +70,7 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
|
|||
InitializeEmulatorLanguages();
|
||||
LoadValuesFromConfig();
|
||||
|
||||
defaultTextEdit = tr("Point your mouse at an options to display a description in here");
|
||||
defaultTextEdit = tr("Point your mouse at an option to display its description.");
|
||||
ui->descriptionText->setText(defaultTextEdit);
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close);
|
||||
|
@ -140,8 +140,17 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
|
|||
checkUpdate->exec();
|
||||
});
|
||||
|
||||
connect(ui->playBGMCheckBox, &QCheckBox::stateChanged, this,
|
||||
[](int val) { Config::setPlayBGM(val); });
|
||||
connect(ui->playBGMCheckBox, &QCheckBox::stateChanged, this, [](int val) {
|
||||
Config::setPlayBGM(val);
|
||||
if (val == Qt::Unchecked) {
|
||||
BackgroundMusicPlayer::getInstance().stopMusic();
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->BGMVolumeSlider, &QSlider::valueChanged, this, [](float val) {
|
||||
Config::setBGMvolume(val);
|
||||
BackgroundMusicPlayer::getInstance().setVolume(val);
|
||||
});
|
||||
}
|
||||
|
||||
// GPU TAB
|
||||
|
@ -231,6 +240,7 @@ void SettingsDialog::LoadValuesFromConfig() {
|
|||
ui->nullGpuCheckBox->setChecked(Config::nullGpu());
|
||||
ui->dumpPM4CheckBox->setChecked(Config::dumpPM4());
|
||||
ui->playBGMCheckBox->setChecked(Config::getPlayBGM());
|
||||
ui->BGMVolumeSlider->setValue((Config::getBGMvolume()));
|
||||
ui->fullscreenCheckBox->setChecked(Config::isFullscreenMode());
|
||||
ui->showSplashCheckBox->setChecked(Config::showSplash());
|
||||
ui->ps4proCheckBox->setChecked(Config::isNeoMode());
|
||||
|
@ -371,4 +381,4 @@ bool SettingsDialog::eventFilter(QObject* obj, QEvent* event) {
|
|||
}
|
||||
}
|
||||
return QDialog::eventFilter(obj, event);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>836</width>
|
||||
<height>442</height>
|
||||
<height>446</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -369,7 +369,7 @@
|
|||
<x>10</x>
|
||||
<y>30</y>
|
||||
<width>241</width>
|
||||
<height>41</height>
|
||||
<height>71</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
@ -386,6 +386,55 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Volume</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="BGMVolumeSlider">
|
||||
<property name="toolTip">
|
||||
<string>Set the volume of the background music.</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="invertedAppearance">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="invertedControls">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="tickPosition">
|
||||
<enum>QSlider::TickPosition::NoTicks</enum>
|
||||
</property>
|
||||
<property name="tickInterval">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>تشغيل موسيقى العنوان</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>الصوت</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>وجه مؤشر الفأرة إلى خيار لعرض الوصف هنا</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>وجّه الماوس نحو خيار لعرض وصفه.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>التحديث:\nمستقر: إصدارات رسمية يتم إصدارها شهريًا، قد تكون قديمة جدًا ولكنها أكثر استقرارًا وتم اختبارها.\nغير مستقر: إصدارات التطوير التي تحتوي على أحدث الميزات والإصلاحات، لكنها قد تحتوي على أخطاء وأقل استقرارًا.</translation>
|
||||
<translation>تحديث: Release: إصدارات رسمية تصدر شهريًا، قد تكون قديمة بعض الشيء، لكنها أكثر استقرارًا واختبارًا. Nightly: إصدارات تطوير تحتوي على أحدث الميزات والإصلاحات، لكنها قد تحتوي على أخطاء وأقل استقرارًا.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Afspil titelsang</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Lydstyrke</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Placer musen på en indstilling for at vise en beskrivelse her</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Peg musen over et valg for at vise dets beskrivelse.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Opdatering:\nStabil: Officielle builds, der frigives månedligt, som kan være meget ældre, men mere stabile og testet.\nUstabil: Udviklerbuilds med de nyeste funktioner og rettelser, men som kan indeholde fejl og være mindre stabile.</translation>
|
||||
<translation>Opdatering:\nRelease: Officielle builds, der frigives månedligt, som kan være meget ældre, men mere stabile og testet.\nNightly: Udviklerbuilds med de nyeste funktioner og rettelser, men som kan indeholde fejl og være mindre stabile.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Titelmusik abspielen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Lautstärke</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Zeigen Sie mit der Maus auf eine Option, um hier eine Beschreibung anzuzeigen</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Bewege die Maus über eine Option, um deren Beschreibung anzuzeigen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Update:\nStabil: Offizielle Builds, die monatlich veröffentlicht werden, können viel älter sein, aber stabiler und getestet.\nUnstabil: Entwickler-Builds, die die neuesten Funktionen und Fehlerbehebungen enthalten, aber Fehler enthalten und weniger stabil sein können.</translation>
|
||||
<translation>Update:\nRelease: Offizielle Builds, die monatlich veröffentlicht werden, können viel älter sein, aber stabiler und getestet.\nNightly: Entwickler-Builds, die die neuesten Funktionen und Fehlerbehebungen enthalten, aber Fehler enthalten und weniger stabil sein können.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Αναπαραγωγή μουσικής τίτλου</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>ένταση</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Τοποθετήστε τον κέρσορα πάνω από μια επιλογή για να εμφανιστεί εδώ η περιγραφή</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Τοποθετήστε το ποντίκι σας πάνω σε μια επιλογή για να εμφανίσετε την περιγραφή της.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Ενημερώσεις:\nΣταθερές: Επίσημες εκδόσεις που κυκλοφορούν μηνιαίως, είναι παλαιότερες αλλά πιο σταθερές και δοκιμασμένες.\nΑσταθείς: Εκδόσεις προγραμματιστών με νέες δυνατότητες και διορθώσεις, αλλά μπορεί να περιέχουν σφάλματα και να είναι λιγότερο σταθερές.</translation>
|
||||
<translation>Ενημερώσεις:\nRelease: Επίσημες εκδόσεις που κυκλοφορούν μηνιαίως, είναι παλαιότερες αλλά πιο σταθερές και δοκιμασμένες.\nNightly: Εκδόσεις προγραμματιστών με νέες δυνατότητες και διορθώσεις, αλλά μπορεί να περιέχουν σφάλματα και να είναι λιγότερο σταθερές.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Play title music</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Volume</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Point your mouse at an options to display a description in here</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Point your mouse at an option to display its description.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Update:\nStable: Official versions released every month that may be very outdated, but are more reliable and tested.\nUnstable: Development versions that have all the latest features and fixes, but may contain bugs and are less stable.</translation>
|
||||
<translation>Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
@ -1056,17 +1061,17 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="329"/>
|
||||
<source>debugDump</source>
|
||||
<translation>Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory</translation>
|
||||
<translation>Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="331"/>
|
||||
<source>vkValidationCheckBox</source>
|
||||
<translation>Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about it's internal state. This will reduce performance and likely change the behavior of emulation.</translation>
|
||||
<translation>Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="333"/>
|
||||
<source>vkSyncValidationCheckBox</source>
|
||||
<translation>Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks. This will reduce performance and likely change the behavior of emulation.</translation>
|
||||
<translation>Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="335"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Reproducir la música de apertura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Volumen</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Apunta con el ratón a una opción para mostrar una descripción aquí</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Coloque el mouse sobre una opción para mostrar su descripción.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Actualización:\nEstable: Versiones oficiales lanzadas cada mes que pueden estar muy desactualizadas, pero son más confiables y están probadas.\nInestable: Versiones de desarrollo que tienen todas las últimas funciones y correcciones, pero pueden contener errores y son menos estables.</translation>
|
||||
<translation>Actualización:\nRelease: Versiones oficiales lanzadas cada mes que pueden estar muy desactualizadas, pero son más confiables y están probadas.\nNightly: Versiones de desarrollo que tienen todas las últimas funciones y correcciones, pero pueden contener errores y son menos estables.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>پخش موسیقی عنوان</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>صدا </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Point your mouse at an options to display a description in here</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>ماوس خود را بر روی یک گزینه قرار دهید تا توضیحات آن نمایش داده شود.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Update:\nStable: Official versions released every month that may be very outdated, but are more reliable and tested.\nUnstable: Development versions that have all the latest features and fixes, but may contain bugs and are less stable.</translation>
|
||||
<translation>Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
@ -1056,12 +1061,12 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="329"/>
|
||||
<source>debugDump</source>
|
||||
<translation>Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory</translation>
|
||||
<translation>Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="331"/>
|
||||
<source>vkValidationCheckBox</source>
|
||||
<translation>Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about it's internal state. This will reduce performance and likely change the behavior of emulation.</translation>
|
||||
<translation>Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state. This will reduce performance and likely change the behavior of emulation.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="333"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Soita otsikkomusiikkia</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Äänenvoimakkuus</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Vie hiiri valinnan päälle näyttääksesi kuvauksen tähän</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Siirrä hiiri vaihtoehdon päälle näyttämään sen kuvaus.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Päivitys:\nVakaa: Viralliset versiot, jotka julkaistaan joka kuukausi ja voivat olla hyvin vanhoja, mutta ovat luotettavampia ja testatumpia.\nEpävakaa: Kehitysversiot, joissa on kaikki uusimmat ominaisuudet ja korjaukset, mutta ne voivat sisältää bugeja ja ovat vähemmän vakaita.</translation>
|
||||
<translation>Päivitys:\nRelease: Viralliset versiot, jotka julkaistaan joka kuukausi ja voivat olla hyvin vanhoja, mutta ovat luotettavampia ja testatumpia.\nNightly: Kehitysversiot, joissa on kaikki uusimmat ominaisuudet ja korjaukset, mutta ne voivat sisältää bugeja ja ovat vähemmän vakaita.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Lire la musique du titre</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Volume</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Placez votre souris sur une option pour afficher une description ici</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Pointez votre souris sur une option pour afficher sa description.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Mise à jour :\nStable : versions officielles publiées chaque mois qui peuvent être très anciennes, mais plus fiables et testées.\nInstable : versions de développement avec toutes les dernières fonctionnalités et correctifs, mais pouvant avoir des bogues et être moins stables.</translation>
|
||||
<translation>Mise à jour :\nRelease: versions officielles publiées chaque mois qui peuvent être très anciennes, mais plus fiables et testées.\nNightly: versions de développement avec toutes les dernières fonctionnalités et correctifs, mais pouvant avoir des bogues et être moins stables.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Címzene lejátszása</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Hangerő</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Mutassa a kurzort a lehetőségeken, hogy itt leírást láthasson</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Helyezze az egérmutatót egy lehetőség fölé, hogy megjelenítse annak leírását.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Frissítés:\nStabil: Hivatalos verziók, amelyeket havonta adnak ki, és amelyek nagyon elavultak lehetnek, de megbízhatóbbak és teszteltek.\nInstabil: Fejlesztési verziók, amelyek az összes legújabb funkciót és javítást tartalmazzák, de hibákat tartalmazhatnak és kevésbé stabilak.</translation>
|
||||
<translation>Frissítés:\nRelease: Hivatalos verziók, amelyeket havonta adnak ki, és amelyek nagyon elavultak lehetnek, de megbízhatóbbak és teszteltek.\nNightly: Fejlesztési verziók, amelyek az összes legújabb funkciót és javítást tartalmazzák, de hibákat tartalmazhatnak és kevésbé stabilak.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
@ -1056,7 +1061,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="329"/>
|
||||
<source>debugDump</source>
|
||||
<translation>Debug dumpolás engedélyezése:\nElmenti a futó PS4 program import- és exportszimbólumait, valamint a fájl fejlécinformációit egy könyvtárba</translation>
|
||||
<translation>Debug dumpolás engedélyezése:\nElmenti a futó PS4 program import- és exportszimbólumait, valamint a fájl fejlécinformációit egy könyvtárba.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="331"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Putar musik judul</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Volume</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Letakkan mouse Anda di atas opsi untuk menampilkan deskripsi di sini</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Arahkan mouse Anda pada opsi untuk menampilkan deskripsinya.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Pembaruan:\nStabil: Versi resmi yang dirilis setiap bulan yang mungkin sangat ketinggalan zaman, tetapi lebih dapat diandalkan dan teruji.\nTidak Stabil: Versi pengembangan yang memiliki semua fitur dan perbaikan terbaru, tetapi mungkin mengandung bug dan kurang stabil.</translation>
|
||||
<translation>Pembaruan:\nRelease: Versi resmi yang dirilis setiap bulan yang mungkin sangat ketinggalan zaman, tetapi lebih dapat diandalkan dan teruji.\nNightly: Versi pengembangan yang memiliki semua fitur dan perbaikan terbaru, tetapi mungkin mengandung bug dan kurang stabil.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
@ -1056,7 +1061,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="329"/>
|
||||
<source>debugDump</source>
|
||||
<translation>Aktifkan Pembuangan Debug:\nMenyimpan simbol impor dan ekspor serta informasi header file dari program PS4 yang sedang berjalan ke direktori</translation>
|
||||
<translation>Aktifkan Pembuangan Debug:\nMenyimpan simbol impor dan ekspor serta informasi header file dari program PS4 yang sedang berjalan ke direktori.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="331"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Riproduci musica del titolo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Volume</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Posiziona il mouse su un'opzione per visualizzare una descrizione qui</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Sposta il mouse su un'opzione per visualizzarne la descrizione.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Aggiornamento:\nStabile: Versioni ufficiali rilasciate ogni mese che potrebbero essere molto datate, ma sono più affidabili e testate.\nInstabile: Versioni di sviluppo che hanno tutte le ultime funzionalità e correzioni, ma potrebbero contenere bug e sono meno stabili.</translation>
|
||||
<translation>Aggiornamento:\nRelease: Versioni ufficiali rilasciate ogni mese che potrebbero essere molto datate, ma sono più affidabili e testate.\nNightly: Versioni di sviluppo che hanno tutte le ultime funzionalità e correzioni, ma potrebbero contenere bug e sono meno stabili.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
@ -1056,7 +1061,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="329"/>
|
||||
<source>debugDump</source>
|
||||
<translation>Abilita Pompaggio di Debug:\nSalva i simboli di importazione ed esportazione e le informazioni sull'intestazione del file del programma PS4 attualmente in esecuzione in una directory</translation>
|
||||
<translation>Abilita Pompaggio di Debug:\nSalva i simboli di importazione ed esportazione e le informazioni sull'intestazione del file del programma PS4 attualmente in esecuzione in una directory.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="331"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>タイトル音楽を再生する</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>音量</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>ここに説明を表示するには、オプションにマウスをポイントしてください</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>オプションにマウスをポイントすると、その説明が表示されます。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>更新:\n安定版: 非常に古いかもしれないが、より信頼性が高くテスト済みの公式バージョンを毎月リリースします。\n不安定版: 最新の機能と修正がすべて含まれていますが、バグが含まれている可能性があり、安定性は低いです。</translation>
|
||||
<translation>更新:\nRelease: 非常に古いかもしれないが、より信頼性が高くテスト済みの公式バージョンを毎月リリースします。\nNightly: 最新の機能と修正がすべて含まれていますが、バグが含まれている可能性があり、安定性は低いです。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Play title music</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>음량</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Point your mouse at an options to display a description in here</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Point your mouse at an option to display its description.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Update:\nStable: Official versions released every month that may be very outdated, but are more reliable and tested.\nUnstable: Development versions that have all the latest features and fixes, but may contain bugs and are less stable.</translation>
|
||||
<translation>Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
@ -1056,12 +1061,12 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="329"/>
|
||||
<source>debugDump</source>
|
||||
<translation>Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory</translation>
|
||||
<translation>Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="331"/>
|
||||
<source>vkValidationCheckBox</source>
|
||||
<translation>Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about it's internal state. This will reduce performance and likely change the behavior of emulation.</translation>
|
||||
<translation>Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state. This will reduce performance and likely change the behavior of emulation.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="333"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Groti antraštės muziką</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Garsumas</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Rodykite pelę ant pasirinkimo, kad čia būtų rodoma aprašymas</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Žymeklį nukreipkite ant pasirinkimo, kad pamatytumėte jo aprašymą.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Atnaujinti:\nStabilus: Oficialios versijos, išleidžiamos kiekvieną mėnesį, kurios gali būti labai pasenusios, tačiau yra patikimos ir išbandytos.\nNestabilus: Vystymo versijos, kuriose yra visos naujausios funkcijos ir taisymai, tačiau gali turėti klaidų ir būti mažiau stabilios.</translation>
|
||||
<translation>Atnaujinti:\nRelease: Oficialios versijos, išleidžiamos kiekvieną mėnesį, kurios gali būti labai pasenusios, tačiau yra patikimos ir išbandytos.\nNightly: Vystymo versijos, kuriose yra visos naujausios funkcijos ir taisymai, tačiau gali turėti klaidų ir būti mažiau stabilios.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Spill tittelmusikk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Volum</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Hold musen over et alternativ for å vise en beskrivelse her</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Hold musen over et valg for at vise beskrivelsen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Oppdatering:\nStabil: Offisielle versjoner utgitt hver måned som kan være veldig utdaterte, men er mer pålitelige og testet.\nUstabil: Utviklingsversjoner som har alle de nyeste funksjonene og feilrettingene, men som kan inneholde feil og er mindre stabile.</translation>
|
||||
<translation>Oppdatering:\nRelease: Offisielle versjoner utgitt hver måned som kan være veldig utdaterte, men er mer pålitelige og testet.\nNightly: Utviklingsversjoner som har alle de nyeste funksjonene og feilrettingene, men som kan inneholde feil og er mindre stabile.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Titelmuziek afspelen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Volume</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Beweeg je muis over een optie om hier een beschrijving weer te geven</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Wijzig de muisaanwijzer naar een optie om de beschrijving weer te geven.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Updateren:\nStabiel: Officiële versies die elke maand worden uitgebracht, die zeer verouderd kunnen zijn, maar betrouwbaar en getest zijn.\nOnstabiel: Ontwikkelingsversies die alle nieuwste functies en bugfixes bevatten, maar mogelijk bugs bevatten en minder stabiel zijn.</translation>
|
||||
<translation>Updateren:\nRelease: Officiële versies die elke maand worden uitgebracht, die zeer verouderd kunnen zijn, maar betrouwbaar en getest zijn.\nNightly: Ontwikkelingsversies die alle nieuwste functies en bugfixes bevatten, maar mogelijk bugs bevatten en minder stabiel zijn.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Odtwórz muzykę tytułową</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Głośność</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Najedź myszą na opcję, aby wyświetlić opis tutaj</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Najedź kursorem na opcję, aby wyświetlić jej opis.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Aktualizator:\nStabilny: Oficjalne wersje wydawane co miesiąc, które mogą być bardzo przestarzałe, ale są niezawodne i przetestowane.\nNiestabilny: Wersje rozwojowe, które zawierają wszystkie najnowsze funkcje i poprawki błędów, ale mogą mieć błędy i być mniej stabilne.</translation>
|
||||
<translation>Aktualizator:\nRelease: Oficjalne wersje wydawane co miesiąc, które mogą być bardzo przestarzałe, ale są niezawodne i przetestowane.\nNightly: Wersje rozwojowe, które zawierają wszystkie najnowsze funkcje i poprawki błędów, ale mogą mieć błędy i być mniej stabilne.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Reproduzir música de abertura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Volume</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,13 +975,13 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Passe o mouse sobre uma opção para exibir a descrição aqui</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Passe o mouse sobre uma opção para exibir sua descrição.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
<source>consoleLanguageGroupBox</source>
|
||||
<translation>Idioma do console:\nDefine o idioma usado pelo jogo PS4.\nRecomenda-se configurá-lo para um idioma que o jogo suporte, o que pode variar conforme a região.</translation>
|
||||
<translation>Idioma do console:\nDefine o idioma usado pelo jogo no PS4.\nRecomenda-se configurá-lo para um idioma que o jogo suporte, o que pode variar conforme a região.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="291"/>
|
||||
|
@ -1016,12 +1021,12 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Atualizações:\nStable: versões oficiais que são lançadas todo mês e podem ser bastante antigas, mas são mais confiáveis e testadas.\nUnstable: versões de desenvolvimento que têm todos os novos recursos e correções, mas podem ter bugs e ser instáveis.</translation>
|
||||
<translation>Atualizações:\nRelease: Versões oficiais que são lançadas todo mês e podem ser bastante antigas, mas são mais confiáveis e testadas.\nNightly: Versões de desenvolvimento que têm todos os novos recursos e correções, mas podem ter bugs e ser instáveis.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
<source>GUIgroupBox</source>
|
||||
<translation>Reproduzir música de título:\nSe o jogo suportar, ativa a reprodução de música especial ao selecionar o jogo na interface.</translation>
|
||||
<translation>Reproduzir música de abertura:\nSe o jogo suportar, ativa a reprodução de uma música especial ao selecionar o jogo na interface do menu.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="312"/>
|
||||
|
@ -1061,12 +1066,12 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="331"/>
|
||||
<source>vkValidationCheckBox</source>
|
||||
<translation>Ativar Camadas de Validação do Vulkan:\nAtiva um sistema que valida o estado do renderizador Vulkan e registra informações sobre seu estado interno. Isso diminui o desempenho e pode alterar o comportamento da emulação.</translation>
|
||||
<translation>Ativar Camadas de Validação do Vulkan:\nAtiva um sistema que valida o estado do renderizador Vulkan e registra informações sobre seu estado interno.\nIsso diminui o desempenho e pode alterar o comportamento da emulação.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="333"/>
|
||||
<source>vkSyncValidationCheckBox</source>
|
||||
<translation>Ativar Validação de Sincronização do Vulkan:\nAtiva um sistema que valida o agendamento de tarefas de renderização Vulkan. Isso diminui o desempenho e pode alterar o comportamento da emulação.</translation>
|
||||
<translation>Ativar Validação de Sincronização do Vulkan:\nAtiva um sistema que valida o agendamento de tarefas de renderização Vulkan.\nIsso diminui o desempenho e pode alterar o comportamento da emulação.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="335"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Redă muzica titlului</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Volum</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Pune mouse-ul pe o opțiune pentru a afișa o descriere aici</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Indicați mouse-ul asupra unei opțiuni pentru a afișa descrierea acesteia.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Actualizare:\nStabil: Versiuni oficiale lansate în fiecare lună, care pot fi foarte învechite, dar sunt mai fiabile și testate.\nInstabil: Versiuni de dezvoltare care conțin toate cele mai recente funcții și corecții, dar pot conține erori și sunt mai puțin stabile.</translation>
|
||||
<translation>Actualizare:\nRelease: Versiuni oficiale lansate în fiecare lună, care pot fi foarte învechite, dar sunt mai fiabile și testate.\nNightly: Versiuni de dezvoltare care conțin toate cele mai recente funcții și corecții, dar pot conține erori și sunt mai puțin stabile.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -402,12 +402,12 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.ui" line="122"/>
|
||||
<source>Enable Fullscreen</source>
|
||||
<translation>Включить полноэкранный режим</translation>
|
||||
<translation>Полноэкранный режим</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="129"/>
|
||||
<source>Show Splash</source>
|
||||
<translation>Показать заставку</translation>
|
||||
<translation>Показывать заставку</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="136"/>
|
||||
|
@ -457,7 +457,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.ui" line="405"/>
|
||||
<source>Vblank Divider</source>
|
||||
<translation>Разделитель Vblank</translation>
|
||||
<translation>Делитель Vblank</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="453"/>
|
||||
|
@ -527,12 +527,17 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.ui" line="354"/>
|
||||
<source>GUI Settings</source>
|
||||
<translation>Настройки GUI</translation>
|
||||
<translation>Настройки интерфейса</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="375"/>
|
||||
<source>Play title music</source>
|
||||
<translation>Воспроизвести музыку заголовка</translation>
|
||||
<translation>Воспроизведение заглавной музыки</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Громкость</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -943,7 +948,7 @@
|
|||
<message>
|
||||
<location filename="../cheats_patches.cpp" line="1163"/>
|
||||
<source>Can't apply cheats before the game is started</source>
|
||||
<translation>Невозможно применить читы до начала игрыs</translation>
|
||||
<translation>Невозможно применить читы до начала игры</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -961,7 +966,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="85"/>
|
||||
<source>Restore Defaults</source>
|
||||
<translation>Восстановить умолчания</translation>
|
||||
<translation>По умолчанию</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="86"/>
|
||||
|
@ -970,13 +975,13 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Наведите курсор мыши на опцию, чтобы отобразить описание здесь</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Наведите указатель мыши на опцию, чтобы отобразить ее описание.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
<source>consoleLanguageGroupBox</source>
|
||||
<translation>Язык консоли:\nУстановите язык, который будет использоваться в играх PS4.\nРекомендуется устанавливать язык, поддерживаемый игрой, так как он может отличаться в зависимости от региона.</translation>
|
||||
<translation>Язык консоли:\nУстановите язык, который будет использоваться в играх PS4.\nРекомендуется устанавливать язык который поддерживается игрой, так как он может отличаться в зависимости от региона.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="291"/>
|
||||
|
@ -986,17 +991,17 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="293"/>
|
||||
<source>fullscreenCheckBox</source>
|
||||
<translation>Включить полноэкранный режим:\nАвтоматически переводит игровое окно в полноэкранный режим.\nВы можете отключить это, нажав клавишу F11.</translation>
|
||||
<translation>Полноэкранный режим:\nАвтоматически переводит игровое окно в полноэкранный режим.\nВы можете отключить это, нажав клавишу F11.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="295"/>
|
||||
<source>showSplashCheckBox</source>
|
||||
<translation>Показать заставку:\nОтображает заставку игры (специальное изображение) во время запуска игры.</translation>
|
||||
<translation>Показывать заставку:\nОтображает заставку игры (специальное изображение) во время запуска игры.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="297"/>
|
||||
<source>ps4proCheckBox</source>
|
||||
<translation>Это PS4 Pro:\nЗаставляет эмулятор работать как PS4 PRO, что может включить специальные функции в играх, поддерживающих это.</translation>
|
||||
<translation>Режим PS4 Pro:\nЗаставляет эмулятор работать как PS4 Pro, что может включить специальные функции в играх, поддерживающих это.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="299"/>
|
||||
|
@ -1006,22 +1011,22 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="301"/>
|
||||
<source>logTypeGroupBox</source>
|
||||
<translation>Тип журнала:\nУстановите, синхронизировать ли вывод окна журнала для производительности. Это может негативно сказаться на эмуляции.</translation>
|
||||
<translation>Тип логов:\nУстановите, синхронизировать ли вывод окна логов ради производительности. Это может негативно сказаться на эмуляции.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="303"/>
|
||||
<source>logFilter</source>
|
||||
<translation>Фильтр журнала: Фильтрует журнал, чтобы печатать только определенную информацию. Пример: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Уровни: Trace, Debug, Info, Warning, Error, Critical - в этом порядке, конкретный уровень глушит все предыдущие уровни в списке и регистрирует все последующие уровни.</translation>
|
||||
<translation>Фильтр логов: Фильтрует логи, чтобы показывать только определенную информацию. Примеры: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Уровни: Trace, Debug, Info, Warning, Error, Critical - в этом порядке, конкретный уровень глушит все предыдущие уровни в списке и показывает все последующие уровни.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Обновление:\nСтабильная версия: Официальные версии, которые могут быть очень старыми и выпускаются каждый месяц, но они более надежные и проверенные.\nНестабильная версия: Версии разработки, которые содержат все последние функции и исправления, но могут содержать ошибки и менее стабильны.</translation>
|
||||
<translation>Обновление:\nRelease: Официальные версии, которые выпускаются каждый месяц и могут быть очень старыми, но они более надежные и проверенные.\nNightly: Версии разработки, которые содержат все последние функции и исправления, но могут содержать ошибки и менее стабильны.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
<source>GUIgroupBox</source>
|
||||
<translation>Воспроизведение музыки из заголовка:\nЕсли игра это поддерживает, включает воспроизведение специальной музыки при выборе игры в графическом интерфейсе.</translation>
|
||||
<translation>Воспроизведение заглавной музыки:\nЕсли игра это поддерживает, включает воспроизведение специальной музыки при выборе игры в интерфейсе.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="312"/>
|
||||
|
@ -1036,7 +1041,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="318"/>
|
||||
<source>heightDivider</source>
|
||||
<translation>Делитель Vblank:\nЧастота кадров, с которой обновляется эмулятор, умножается на это число. Изменение этого может иметь негативные последствия, такие как увеличение скорости игры или разрушение критических функций игры, которые не ожидают этого изменения!</translation>
|
||||
<translation>Делитель Vblank:\nЧастота кадров, с которой обновляется эмулятор, умножается на это число. Изменение этого параметра может иметь негативные последствия, такие как увеличение скорости игры или нарушение критических функций игры, которые этого не ожидают!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="320"/>
|
||||
|
@ -1046,7 +1051,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="322"/>
|
||||
<source>nullGpuCheckBox</source>
|
||||
<translation>Включить Null GPU:\nДля технической отладки отключает рендеринг игры так, как будто графической карты нет.</translation>
|
||||
<translation>Включить NULL GPU:\nДля технической отладки отключает рендеринг игры так, как будто графической карты нет.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="323"/>
|
||||
|
@ -1056,12 +1061,12 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="329"/>
|
||||
<source>debugDump</source>
|
||||
<translation>Включить дамп отладки:\nСохраняет символы импорта и экспорта и информацию о заголовке файла текущей исполняемой программы PS4 в каталоге</translation>
|
||||
<translation>Включить отладочные дампы:\nСохраняет символы импорта, экспорта и информацию о заголовке файла текущей исполняемой программы PS4 в папку.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="331"/>
|
||||
<source>vkValidationCheckBox</source>
|
||||
<translation>Включить слои валидации Vulkan:\nВключает систему, которая проверяет состояние рендерера Vulkan и регистрирует информацию о его внутреннем состоянии. Это снизит производительность и, вероятно, изменит поведение эмуляции.</translation>
|
||||
<translation>Включить слои валидации Vulkan:\nВключает систему, которая проверяет состояние рендерера Vulkan и логирует информацию о его внутреннем состоянии. Это снизит производительность и, вероятно, изменит поведение эмуляции.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="333"/>
|
||||
|
@ -1132,7 +1137,7 @@
|
|||
<message>
|
||||
<location filename="../check_update.cpp" line="64"/>
|
||||
<source>Network error:</source>
|
||||
<translation>Ошибка сети:</translation>
|
||||
<translation>Сетевая ошибка:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../check_update.cpp" line="73"/>
|
||||
|
@ -1182,12 +1187,12 @@
|
|||
<message>
|
||||
<location filename="../check_update.cpp" line="179"/>
|
||||
<source>Do you want to update?</source>
|
||||
<translation>Вы хотите обновить?</translation>
|
||||
<translation>Вы хотите обновиться?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../check_update.cpp" line="193"/>
|
||||
<source>Show Changelog</source>
|
||||
<translation>Показать изменения</translation>
|
||||
<translation>Показать журнал изменений</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../check_update.cpp" line="198"/>
|
||||
|
@ -1197,7 +1202,7 @@
|
|||
<message>
|
||||
<location filename="../check_update.cpp" line="199"/>
|
||||
<source>Update</source>
|
||||
<translation>Обновить</translation>
|
||||
<translation>Обновиться</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../check_update.cpp" line="200"/>
|
||||
|
@ -1207,17 +1212,17 @@
|
|||
<message>
|
||||
<location filename="../check_update.cpp" line="223"/>
|
||||
<source>Hide Changelog</source>
|
||||
<translation>Скрыть изменения</translation>
|
||||
<translation>Скрыть журнал изменений</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../check_update.cpp" line="296"/>
|
||||
<source>Changes</source>
|
||||
<translation>Изменения</translation>
|
||||
<translation>Журнал изменений</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../check_update.cpp" line="310"/>
|
||||
<source>Network error occurred while trying to access the URL</source>
|
||||
<translation>Произошла ошибка сети при попытке доступа к URL</translation>
|
||||
<translation>Произошла сетевая ошибка при попытке доступа к URL</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../check_update.cpp" line="330"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Luaj muzikën e titullit</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Volumi</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Vendosni miun në një opsion për të shfaqur një përshkrim këtu</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Hidhni mouse-in mbi një opsion për të shfaqur përshkrimin e tij.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Aktualizimi:\nStabil: Versionet zyrtare të lëshuara çdo muaj që mund të jenë shumë të vjetra, por janë më të besueshme dhe të testuara.\nPanshkuar: Versionet e zhvillimit që kanë të gjitha veçoritë dhe rregullimet më të fundit, por mund të përmbajnë gabime dhe janë më pak të qëndrueshme.</translation>
|
||||
<translation>Aktualizimi:\nRelease: Versionet zyrtare të lëshuara çdo muaj që mund të jenë shumë të vjetra, por janë më të besueshme dhe të testuara.\nNightly: Versionet e zhvillimit që kanë të gjitha veçoritë dhe rregullimet më të fundit, por mund të përmbajnë gabime dhe janë më pak të qëndrueshme.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Başlık müziğini çal</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Ses seviyesi</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Buraya açıklama göstermek için bir seçeneğin üzerine fareyi getirin</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Seçenek üzerinde farenizi tutarak açıklamasını görüntüleyin.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Güncelleme:\nStabil: Her ay yayınlanan resmi sürümler; çok eski olabilirler, ancak daha güvenilirdir ve test edilmiştir.\nKararsız: Tüm en son özellikler ve düzeltmeler ile birlikte geliştirme sürümleri; hatalar içerebilir ve daha az kararlıdırlar.</translation>
|
||||
<translation>Güncelleme:\nRelease: Her ay yayınlanan resmi sürümler; çok eski olabilirler, ancak daha güvenilirdir ve test edilmiştir.\nNightly: Tüm en son özellikler ve düzeltmeler ile birlikte geliştirme sürümleri; hatalar içerebilir ve daha az kararlıdırlar.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>Phát nhạc tiêu đề</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>Âm lượng</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>Di chuột vào tùy chọn để hiển thị mô tả ở đây</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>Di chuyển chuột đến tùy chọn để hiển thị mô tả của nó.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>Cập nhật:\nỔn định: Các phiên bản chính thức được phát hành hàng tháng; có thể khá cũ nhưng đáng tin cậy hơn và đã được thử nghiệm.\nKhông ổn định: Các phiên bản phát triển có tất cả các tính năng và sửa lỗi mới nhất; có thể có lỗi và ít ổn định hơn.</translation>
|
||||
<translation>Cập nhật:\nRelease: Các phiên bản chính thức được phát hành hàng tháng; có thể khá cũ nhưng đáng tin cậy hơn và đã được thử nghiệm.\nNightly: Các phiên bản phát triển có tất cả các tính năng và sửa lỗi mới nhất; có thể có lỗi và ít ổn định hơn.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>播放标题音乐</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>音量</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>将鼠标指针放在选项上以在这里显示说明</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>将鼠标指针指向选项以显示其描述。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>更新:\n稳定版本: 官方版本,可能非常旧,并且每月发布,但更可靠且经过测试。\n不稳定版本: 开发版本,包含所有最新功能和修复,但可能包含错误且不够稳定。</translation>
|
||||
<translation>更新:\nRelease: 官方版本,可能非常旧,并且每月发布,但更可靠且经过测试。\nNightly: 开发版本,包含所有最新功能和修复,但可能包含错误且不够稳定。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -534,6 +534,11 @@
|
|||
<source>Play title music</source>
|
||||
<translation>播放標題音樂</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="394"/>
|
||||
<source>Volume</source>
|
||||
<translation>音量</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -970,8 +975,8 @@
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="72"/>
|
||||
<source>Point your mouse at an options to display a description in here</source>
|
||||
<translation>將鼠標懸停在選項上以在此顯示描述</translation>
|
||||
<source>Point your mouse at an option to display its description.</source>
|
||||
<translation>將鼠標指向選項以顯示其描述。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="289"/>
|
||||
|
@ -1016,7 +1021,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="305"/>
|
||||
<source>updaterGroupBox</source>
|
||||
<translation>更新:\n穩定版: 每月發布的官方版本,可能非常舊,但更可靠且經過測試。\n不穩定: 開發版本,擁有所有最新的功能和修復,但可能包含錯誤,穩定性較差。</translation>
|
||||
<translation>更新:\nRelease: 每月發布的官方版本,可能非常舊,但更可靠且經過測試。\nNightly: 開發版本,擁有所有最新的功能和修復,但可能包含錯誤,穩定性較差。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.cpp" line="306"/>
|
||||
|
|
|
@ -28,10 +28,7 @@ void TrophyViewer::PopulateTrophyWidget(QString title) {
|
|||
|
||||
QDir dir(trophyDirQt);
|
||||
if (!dir.exists()) {
|
||||
std::filesystem::path path(gameTrpPath_.toStdString());
|
||||
#ifdef _WIN64
|
||||
path = std::filesystem::path(gameTrpPath_.toStdWString());
|
||||
#endif
|
||||
std::filesystem::path path = Common::FS::PathFromQString(gameTrpPath_);
|
||||
if (!trp.Extract(path))
|
||||
return;
|
||||
}
|
||||
|
@ -79,7 +76,7 @@ void TrophyViewer::PopulateTrophyWidget(QString title) {
|
|||
trpType.append(reader.attributes().value("ttype").toString());
|
||||
trpPid.append(reader.attributes().value("pid").toString());
|
||||
if (reader.attributes().hasAttribute("unlockstate")) {
|
||||
if (reader.attributes().value("unlockstate").toString() == "unlocked") {
|
||||
if (reader.attributes().value("unlockstate").toString() == "true") {
|
||||
trpUnlocked.append("unlocked");
|
||||
} else {
|
||||
trpUnlocked.append("locked");
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace Frontend {
|
|||
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_,
|
||||
std::string_view window_title)
|
||||
: width{width_}, height{height_}, controller{controller_} {
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
UNREACHABLE_MSG("Failed to initialize SDL video subsystem: {}", SDL_GetError());
|
||||
}
|
||||
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
||||
|
|
|
@ -233,6 +233,7 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) {
|
|||
ctx.AddExecutionMode(main, spv::ExecutionMode::OriginUpperLeft);
|
||||
}
|
||||
if (info.has_discard) {
|
||||
ctx.AddExtension("SPV_EXT_demote_to_helper_invocation");
|
||||
ctx.AddCapability(spv::Capability::DemoteToHelperInvocationEXT);
|
||||
}
|
||||
if (info.stores.Get(IR::Attribute::Depth)) {
|
||||
|
|
|
@ -49,12 +49,13 @@ Id OutputAttrPointer(EmitContext& ctx, IR::Attribute attr, u32 element) {
|
|||
if (info.num_components == 1) {
|
||||
return info.id;
|
||||
} else {
|
||||
return ctx.OpAccessChain(ctx.output_f32, info.id, ctx.ConstU32(element));
|
||||
return ctx.OpAccessChain(info.pointer_type, info.id, ctx.ConstU32(element));
|
||||
}
|
||||
}
|
||||
switch (attr) {
|
||||
case IR::Attribute::Position0: {
|
||||
return ctx.OpAccessChain(ctx.output_f32, ctx.output_position, ctx.ConstU32(element));
|
||||
}
|
||||
case IR::Attribute::Position1:
|
||||
case IR::Attribute::Position2:
|
||||
case IR::Attribute::Position3: {
|
||||
|
@ -70,17 +71,47 @@ Id OutputAttrPointer(EmitContext& ctx, IR::Attribute attr, u32 element) {
|
|||
case IR::Attribute::RenderTarget6:
|
||||
case IR::Attribute::RenderTarget7: {
|
||||
const u32 index = u32(attr) - u32(IR::Attribute::RenderTarget0);
|
||||
if (ctx.frag_num_comp[index] > 1) {
|
||||
return ctx.OpAccessChain(ctx.output_f32, ctx.frag_color[index], ctx.ConstU32(element));
|
||||
const auto& info{ctx.frag_outputs.at(index)};
|
||||
if (info.num_components > 1) {
|
||||
return ctx.OpAccessChain(info.pointer_type, info.id, ctx.ConstU32(element));
|
||||
} else {
|
||||
return ctx.frag_color[index];
|
||||
return info.id;
|
||||
}
|
||||
}
|
||||
case IR::Attribute::Depth:
|
||||
return ctx.frag_depth;
|
||||
default:
|
||||
throw NotImplementedException("Read attribute {}", attr);
|
||||
throw NotImplementedException("Write attribute {}", attr);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<Id, bool> OutputAttrComponentType(EmitContext& ctx, IR::Attribute attr) {
|
||||
if (IR::IsParam(attr)) {
|
||||
const u32 index{u32(attr) - u32(IR::Attribute::Param0)};
|
||||
const auto& info{ctx.output_params.at(index)};
|
||||
return {info.component_type, info.is_integer};
|
||||
}
|
||||
switch (attr) {
|
||||
case IR::Attribute::Position0:
|
||||
case IR::Attribute::Position1:
|
||||
case IR::Attribute::Position2:
|
||||
case IR::Attribute::Position3:
|
||||
case IR::Attribute::Depth:
|
||||
return {ctx.F32[1], false};
|
||||
case IR::Attribute::RenderTarget0:
|
||||
case IR::Attribute::RenderTarget1:
|
||||
case IR::Attribute::RenderTarget2:
|
||||
case IR::Attribute::RenderTarget3:
|
||||
case IR::Attribute::RenderTarget4:
|
||||
case IR::Attribute::RenderTarget5:
|
||||
case IR::Attribute::RenderTarget6:
|
||||
case IR::Attribute::RenderTarget7: {
|
||||
const u32 index = u32(attr) - u32(IR::Attribute::RenderTarget0);
|
||||
const auto& info{ctx.frag_outputs.at(index)};
|
||||
return {info.component_type, info.is_integer};
|
||||
}
|
||||
default:
|
||||
throw NotImplementedException("Write attribute {}", attr);
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
@ -156,17 +187,21 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp) {
|
|||
// Attribute is disabled or varying component is not written
|
||||
return ctx.ConstF32(comp == 3 ? 1.0f : 0.0f);
|
||||
}
|
||||
if (param.is_default) {
|
||||
return ctx.OpCompositeExtract(param.component_type, param.id, comp);
|
||||
}
|
||||
|
||||
if (param.num_components > 1) {
|
||||
Id result;
|
||||
if (param.is_default) {
|
||||
result = ctx.OpCompositeExtract(param.component_type, param.id, comp);
|
||||
} else if (param.num_components > 1) {
|
||||
const Id pointer{
|
||||
ctx.OpAccessChain(param.pointer_type, param.id, ctx.ConstU32(comp))};
|
||||
return ctx.OpLoad(param.component_type, pointer);
|
||||
result = ctx.OpLoad(param.component_type, pointer);
|
||||
} else {
|
||||
return ctx.OpLoad(param.component_type, param.id);
|
||||
result = ctx.OpLoad(param.component_type, param.id);
|
||||
}
|
||||
if (param.is_integer) {
|
||||
result = ctx.OpBitcast(ctx.F32[1], result);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
const auto step_rate = EmitReadStepRate(ctx, param.id.value);
|
||||
const auto offset = ctx.OpIAdd(
|
||||
|
@ -222,7 +257,12 @@ void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, u32 elemen
|
|||
return;
|
||||
}
|
||||
const Id pointer{OutputAttrPointer(ctx, attr, element)};
|
||||
ctx.OpStore(pointer, ctx.OpBitcast(ctx.F32[1], value));
|
||||
const auto component_type{OutputAttrComponentType(ctx, attr)};
|
||||
if (component_type.second) {
|
||||
ctx.OpStore(pointer, ctx.OpBitcast(component_type.first, value));
|
||||
} else {
|
||||
ctx.OpStore(pointer, value);
|
||||
}
|
||||
}
|
||||
|
||||
template <u32 N>
|
||||
|
|
|
@ -120,6 +120,7 @@ void EmitContext::DefineArithmeticTypes() {
|
|||
|
||||
output_f32 = Name(TypePointer(spv::StorageClass::Output, F32[1]), "output_f32");
|
||||
output_u32 = Name(TypePointer(spv::StorageClass::Output, U32[1]), "output_u32");
|
||||
output_s32 = Name(TypePointer(spv::StorageClass::Output, S32[1]), "output_s32");
|
||||
|
||||
full_result_i32x2 = Name(TypeStruct(S32[1], S32[1]), "full_result_i32x2");
|
||||
full_result_u32x2 = Name(TypeStruct(U32[1], U32[1]), "full_result_u32x2");
|
||||
|
@ -151,21 +152,21 @@ const VectorIds& GetAttributeType(EmitContext& ctx, AmdGpu::NumberFormat fmt) {
|
|||
UNREACHABLE_MSG("Invalid attribute type {}", fmt);
|
||||
}
|
||||
|
||||
EmitContext::SpirvAttribute EmitContext::GetAttributeInfo(AmdGpu::NumberFormat fmt, Id id) {
|
||||
EmitContext::SpirvAttribute EmitContext::GetAttributeInfo(AmdGpu::NumberFormat fmt, Id id,
|
||||
bool output) {
|
||||
switch (fmt) {
|
||||
case AmdGpu::NumberFormat::Float:
|
||||
case AmdGpu::NumberFormat::Unorm:
|
||||
case AmdGpu::NumberFormat::Snorm:
|
||||
case AmdGpu::NumberFormat::SnormNz:
|
||||
return {id, input_f32, F32[1], 4};
|
||||
case AmdGpu::NumberFormat::Uint:
|
||||
return {id, input_u32, U32[1], 4};
|
||||
case AmdGpu::NumberFormat::Sint:
|
||||
return {id, input_s32, S32[1], 4};
|
||||
case AmdGpu::NumberFormat::Sscaled:
|
||||
return {id, input_f32, F32[1], 4};
|
||||
case AmdGpu::NumberFormat::Uscaled:
|
||||
return {id, input_f32, F32[1], 4};
|
||||
case AmdGpu::NumberFormat::Srgb:
|
||||
return {id, output ? output_f32 : input_f32, F32[1], 4, false};
|
||||
case AmdGpu::NumberFormat::Uint:
|
||||
return {id, output ? output_u32 : input_u32, U32[1], 4, true};
|
||||
case AmdGpu::NumberFormat::Sint:
|
||||
return {id, output ? output_s32 : input_s32, S32[1], 4, true};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -236,9 +237,13 @@ void EmitContext::DefineInputs() {
|
|||
: 1;
|
||||
// Note that we pass index rather than Id
|
||||
input_params[input.binding] = {
|
||||
rate_idx, input_u32,
|
||||
U32[1], input.num_components,
|
||||
false, input.instance_data_buf,
|
||||
rate_idx,
|
||||
input_u32,
|
||||
U32[1],
|
||||
input.num_components,
|
||||
true,
|
||||
false,
|
||||
input.instance_data_buf,
|
||||
};
|
||||
} else {
|
||||
Id id{DefineInput(type, input.binding)};
|
||||
|
@ -247,7 +252,7 @@ void EmitContext::DefineInputs() {
|
|||
} else {
|
||||
Name(id, fmt::format("vs_in_attr{}", input.binding));
|
||||
}
|
||||
input_params[input.binding] = GetAttributeInfo(input.fmt, id);
|
||||
input_params[input.binding] = GetAttributeInfo(input.fmt, id, false);
|
||||
interfaces.push_back(id);
|
||||
}
|
||||
}
|
||||
|
@ -320,10 +325,12 @@ void EmitContext::DefineOutputs() {
|
|||
continue;
|
||||
}
|
||||
const u32 num_components = info.stores.NumComponents(mrt);
|
||||
frag_color[i] = DefineOutput(F32[num_components], i);
|
||||
frag_num_comp[i] = num_components;
|
||||
Name(frag_color[i], fmt::format("frag_color{}", i));
|
||||
interfaces.push_back(frag_color[i]);
|
||||
const AmdGpu::NumberFormat num_format{runtime_info.fs_info.color_buffers[i].num_format};
|
||||
const Id type{GetAttributeType(*this, num_format)[num_components]};
|
||||
const Id id = DefineOutput(type, i);
|
||||
Name(id, fmt::format("frag_color{}", i));
|
||||
frag_outputs[i] = GetAttributeInfo(num_format, id, true);
|
||||
interfaces.push_back(id);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -166,6 +166,7 @@ public:
|
|||
Id input_s32{};
|
||||
Id output_u32{};
|
||||
Id output_f32{};
|
||||
Id output_s32{};
|
||||
|
||||
boost::container::small_vector<Id, 16> interfaces;
|
||||
|
||||
|
@ -177,8 +178,6 @@ public:
|
|||
Id frag_coord{};
|
||||
Id front_facing{};
|
||||
Id frag_depth{};
|
||||
std::array<Id, 8> frag_color{};
|
||||
std::array<u32, 8> frag_num_comp{};
|
||||
Id clip_distances{};
|
||||
Id cull_distances{};
|
||||
|
||||
|
@ -237,11 +236,13 @@ public:
|
|||
Id pointer_type;
|
||||
Id component_type;
|
||||
u32 num_components;
|
||||
bool is_integer{};
|
||||
bool is_default{};
|
||||
s32 buffer_handle{-1};
|
||||
};
|
||||
std::array<SpirvAttribute, 32> input_params{};
|
||||
std::array<SpirvAttribute, 32> output_params{};
|
||||
std::array<SpirvAttribute, 8> frag_outputs{};
|
||||
|
||||
private:
|
||||
void DefineArithmeticTypes();
|
||||
|
@ -254,7 +255,7 @@ private:
|
|||
void DefineImagesAndSamplers();
|
||||
void DefineSharedMemory();
|
||||
|
||||
SpirvAttribute GetAttributeInfo(AmdGpu::NumberFormat fmt, Id id);
|
||||
SpirvAttribute GetAttributeInfo(AmdGpu::NumberFormat fmt, Id id, bool output);
|
||||
};
|
||||
|
||||
} // namespace Shader::Backend::SPIRV
|
||||
|
|
|
@ -25,7 +25,7 @@ void Translator::EmitExport(const GcnInst& inst) {
|
|||
return comp;
|
||||
}
|
||||
const u32 index = u32(attrib) - u32(IR::Attribute::RenderTarget0);
|
||||
switch (runtime_info.fs_info.mrt_swizzles[index]) {
|
||||
switch (runtime_info.fs_info.color_buffers[index].mrt_swizzle) {
|
||||
case MrtSwizzle::Identity:
|
||||
return comp;
|
||||
case MrtSwizzle::Alt:
|
||||
|
|
|
@ -909,6 +909,8 @@ void Translator::V_CMP_CLASS_F32(const GcnInst& inst) {
|
|||
switch (inst.dst[1].field) {
|
||||
case OperandField::VccLo:
|
||||
return ir.SetVcc(value);
|
||||
case OperandField::ScalarGPR:
|
||||
return ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), value);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
|
|
@ -80,10 +80,16 @@ struct FragmentRuntimeInfo {
|
|||
auto operator<=>(const PsInput&) const noexcept = default;
|
||||
};
|
||||
boost::container::static_vector<PsInput, 32> inputs;
|
||||
std::array<MrtSwizzle, MaxColorBuffers> mrt_swizzles;
|
||||
struct PsColorBuffer {
|
||||
AmdGpu::NumberFormat num_format;
|
||||
MrtSwizzle mrt_swizzle;
|
||||
|
||||
auto operator<=>(const PsColorBuffer&) const noexcept = default;
|
||||
};
|
||||
std::array<PsColorBuffer, MaxColorBuffers> color_buffers;
|
||||
|
||||
bool operator==(const FragmentRuntimeInfo& other) const noexcept {
|
||||
return std::ranges::equal(mrt_swizzles, other.mrt_swizzles) &&
|
||||
return std::ranges::equal(color_buffers, other.color_buffers) &&
|
||||
std::ranges::equal(inputs, other.inputs);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -44,7 +44,7 @@ Liverpool::~Liverpool() {
|
|||
}
|
||||
|
||||
void Liverpool::Process(std::stop_token stoken) {
|
||||
Common::SetCurrentThreadName("GPU_CommandProcessor");
|
||||
Common::SetCurrentThreadName("shadPS4:GPU_CommandProcessor");
|
||||
|
||||
while (!stoken.stop_requested()) {
|
||||
{
|
||||
|
|
|
@ -253,6 +253,13 @@ struct Liverpool {
|
|||
}
|
||||
};
|
||||
|
||||
struct ModeControl {
|
||||
s32 msaa_enable : 1;
|
||||
s32 vport_scissor_enable : 1;
|
||||
s32 line_stripple_enable : 1;
|
||||
s32 send_unlit_stiles_to_pkr : 1;
|
||||
};
|
||||
|
||||
enum class ZOrder : u32 {
|
||||
LateZ = 0,
|
||||
EarlyZLateZ = 1,
|
||||
|
@ -559,29 +566,39 @@ struct Liverpool {
|
|||
s16 top_left_x;
|
||||
s16 top_left_y;
|
||||
};
|
||||
union {
|
||||
BitField<0, 15, u32> bottom_right_x;
|
||||
BitField<16, 15, u32> bottom_right_y;
|
||||
struct {
|
||||
s16 bottom_right_x;
|
||||
s16 bottom_right_y;
|
||||
};
|
||||
|
||||
// From AMD spec: 'Negative numbers clamped to 0'
|
||||
static s16 Clamp(s16 value) {
|
||||
return std::max(s16(0), value);
|
||||
}
|
||||
|
||||
u32 GetWidth() const {
|
||||
return static_cast<u32>(bottom_right_x - top_left_x);
|
||||
return static_cast<u32>(Clamp(bottom_right_x) - Clamp(top_left_x));
|
||||
}
|
||||
|
||||
u32 GetHeight() const {
|
||||
return static_cast<u32>(bottom_right_y - top_left_y);
|
||||
return static_cast<u32>(Clamp(bottom_right_y) - Clamp(top_left_y));
|
||||
}
|
||||
};
|
||||
|
||||
struct WindowOffset {
|
||||
s32 window_x_offset : 16;
|
||||
s32 window_y_offset : 16;
|
||||
};
|
||||
|
||||
struct ViewportScissor {
|
||||
union {
|
||||
BitField<0, 15, s32> top_left_x;
|
||||
BitField<15, 15, s32> top_left_y;
|
||||
BitField<30, 1, s32> window_offset_disable;
|
||||
BitField<16, 15, s32> top_left_y;
|
||||
BitField<31, 1, s32> window_offset_disable;
|
||||
};
|
||||
union {
|
||||
BitField<0, 15, s32> bottom_right_x;
|
||||
BitField<15, 15, s32> bottom_right_y;
|
||||
struct {
|
||||
s16 bottom_right_x;
|
||||
s16 bottom_right_y;
|
||||
};
|
||||
|
||||
u32 GetWidth() const {
|
||||
|
@ -953,10 +970,14 @@ struct Liverpool {
|
|||
Scissor screen_scissor;
|
||||
INSERT_PADDING_WORDS(0xA010 - 0xA00C - 2);
|
||||
DepthBuffer depth_buffer;
|
||||
INSERT_PADDING_WORDS(0xA08E - 0xA018);
|
||||
INSERT_PADDING_WORDS(0xA080 - 0xA018);
|
||||
WindowOffset window_offset;
|
||||
ViewportScissor window_scissor;
|
||||
INSERT_PADDING_WORDS(0xA08E - 0xA081 - 2);
|
||||
ColorBufferMask color_target_mask;
|
||||
ColorBufferMask color_shader_mask;
|
||||
INSERT_PADDING_WORDS(0xA094 - 0xA08E - 2);
|
||||
ViewportScissor generic_scissor;
|
||||
INSERT_PADDING_WORDS(2);
|
||||
std::array<ViewportScissor, NumViewports> viewport_scissors;
|
||||
std::array<ViewportDepth, NumViewports> viewport_depths;
|
||||
INSERT_PADDING_WORDS(0xA103 - 0xA0D4);
|
||||
|
@ -994,7 +1015,9 @@ struct Liverpool {
|
|||
PolygonControl polygon_control;
|
||||
ViewportControl viewport_control;
|
||||
VsOutputControl vs_output_control;
|
||||
INSERT_PADDING_WORDS(0xA29E - 0xA207 - 2);
|
||||
INSERT_PADDING_WORDS(0xA292 - 0xA207 - 1);
|
||||
ModeControl mode_control;
|
||||
INSERT_PADDING_WORDS(0xA29D - 0xA292 - 1);
|
||||
u32 index_size;
|
||||
u32 max_index_size;
|
||||
IndexBufferType index_buffer_type;
|
||||
|
@ -1206,8 +1229,11 @@ static_assert(GFX6_3D_REG_INDEX(depth_htile_data_base) == 0xA005);
|
|||
static_assert(GFX6_3D_REG_INDEX(screen_scissor) == 0xA00C);
|
||||
static_assert(GFX6_3D_REG_INDEX(depth_buffer.z_info) == 0xA010);
|
||||
static_assert(GFX6_3D_REG_INDEX(depth_buffer.depth_slice) == 0xA017);
|
||||
static_assert(GFX6_3D_REG_INDEX(window_offset) == 0xA080);
|
||||
static_assert(GFX6_3D_REG_INDEX(window_scissor) == 0xA081);
|
||||
static_assert(GFX6_3D_REG_INDEX(color_target_mask) == 0xA08E);
|
||||
static_assert(GFX6_3D_REG_INDEX(color_shader_mask) == 0xA08F);
|
||||
static_assert(GFX6_3D_REG_INDEX(generic_scissor) == 0xA090);
|
||||
static_assert(GFX6_3D_REG_INDEX(viewport_scissors) == 0xA094);
|
||||
static_assert(GFX6_3D_REG_INDEX(primitive_restart_index) == 0xA103);
|
||||
static_assert(GFX6_3D_REG_INDEX(stencil_control) == 0xA10B);
|
||||
|
@ -1227,6 +1253,7 @@ static_assert(GFX6_3D_REG_INDEX(color_control) == 0xA202);
|
|||
static_assert(GFX6_3D_REG_INDEX(clipper_control) == 0xA204);
|
||||
static_assert(GFX6_3D_REG_INDEX(viewport_control) == 0xA206);
|
||||
static_assert(GFX6_3D_REG_INDEX(vs_output_control) == 0xA207);
|
||||
static_assert(GFX6_3D_REG_INDEX(mode_control) == 0xA292);
|
||||
static_assert(GFX6_3D_REG_INDEX(index_size) == 0xA29D);
|
||||
static_assert(GFX6_3D_REG_INDEX(index_buffer_type) == 0xA29F);
|
||||
static_assert(GFX6_3D_REG_INDEX(enable_primitive_id) == 0xA2A1);
|
||||
|
|
|
@ -95,7 +95,8 @@ Buffer::Buffer(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
|
|||
// Create buffer object.
|
||||
const vk::BufferCreateInfo buffer_ci = {
|
||||
.size = size_bytes,
|
||||
.usage = flags,
|
||||
// When maintenance5 is not supported, use all flags since we can't add flags to views.
|
||||
.usage = instance->IsMaintenance5Supported() ? flags : AllFlags,
|
||||
};
|
||||
VmaAllocationInfo alloc_info{};
|
||||
buffer.Create(buffer_ci, usage, &alloc_info);
|
||||
|
@ -119,7 +120,7 @@ vk::BufferView Buffer::View(u32 offset, u32 size, bool is_written, AmdGpu::DataF
|
|||
: vk::BufferUsageFlagBits2KHR::eUniformTexelBuffer,
|
||||
};
|
||||
const vk::BufferViewCreateInfo view_ci = {
|
||||
.pNext = &usage_flags,
|
||||
.pNext = instance->IsMaintenance5Supported() ? &usage_flags : nullptr,
|
||||
.buffer = buffer.buffer,
|
||||
.format = Vulkan::LiverpoolToVK::SurfaceFormat(dfmt, nfmt),
|
||||
.offset = offset,
|
||||
|
|
|
@ -114,6 +114,8 @@ bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) {
|
|||
|
||||
std::array<vk::Buffer, NumVertexBuffers> host_buffers;
|
||||
std::array<vk::DeviceSize, NumVertexBuffers> host_offsets;
|
||||
std::array<vk::DeviceSize, NumVertexBuffers> host_sizes;
|
||||
std::array<vk::DeviceSize, NumVertexBuffers> host_strides;
|
||||
boost::container::static_vector<AmdGpu::Buffer, NumVertexBuffers> guest_buffers;
|
||||
|
||||
struct BufferRange {
|
||||
|
@ -193,11 +195,18 @@ bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) {
|
|||
|
||||
host_buffers[i] = host_buffer->vk_buffer;
|
||||
host_offsets[i] = host_buffer->offset + buffer.base_address - host_buffer->base_address;
|
||||
host_sizes[i] = buffer.GetSize();
|
||||
host_strides[i] = buffer.GetStride();
|
||||
}
|
||||
|
||||
if (num_buffers > 0) {
|
||||
const auto cmdbuf = scheduler.CommandBuffer();
|
||||
cmdbuf.bindVertexBuffers(0, num_buffers, host_buffers.data(), host_offsets.data());
|
||||
if (instance.IsVertexInputDynamicState()) {
|
||||
cmdbuf.bindVertexBuffers(0, num_buffers, host_buffers.data(), host_offsets.data());
|
||||
} else {
|
||||
cmdbuf.bindVertexBuffers2EXT(0, num_buffers, host_buffers.data(), host_offsets.data(),
|
||||
host_sizes.data(), host_strides.data());
|
||||
}
|
||||
}
|
||||
|
||||
return has_step_rate;
|
||||
|
|
|
@ -46,28 +46,34 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul
|
|||
|
||||
boost::container::static_vector<vk::VertexInputBindingDescription, 32> vertex_bindings;
|
||||
boost::container::static_vector<vk::VertexInputAttributeDescription, 32> vertex_attributes;
|
||||
const auto& vs_info = stages[u32(Shader::Stage::Vertex)];
|
||||
for (const auto& input : vs_info->vs_inputs) {
|
||||
if (input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate0 ||
|
||||
input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate1) {
|
||||
// Skip attribute binding as the data will be pulled by shader
|
||||
continue;
|
||||
}
|
||||
if (!instance.IsVertexInputDynamicState()) {
|
||||
const auto& vs_info = stages[u32(Shader::Stage::Vertex)];
|
||||
for (const auto& input : vs_info->vs_inputs) {
|
||||
if (input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate0 ||
|
||||
input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate1) {
|
||||
// Skip attribute binding as the data will be pulled by shader
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto buffer = vs_info->ReadUd<AmdGpu::Buffer>(input.sgpr_base, input.dword_offset);
|
||||
vertex_attributes.push_back({
|
||||
.location = input.binding,
|
||||
.binding = input.binding,
|
||||
.format = LiverpoolToVK::SurfaceFormat(buffer.GetDataFmt(), buffer.GetNumberFmt()),
|
||||
.offset = 0,
|
||||
});
|
||||
vertex_bindings.push_back({
|
||||
.binding = input.binding,
|
||||
.stride = buffer.GetStride(),
|
||||
.inputRate = input.instance_step_rate == Shader::Info::VsInput::None
|
||||
? vk::VertexInputRate::eVertex
|
||||
: vk::VertexInputRate::eInstance,
|
||||
});
|
||||
const auto buffer =
|
||||
vs_info->ReadUd<AmdGpu::Buffer>(input.sgpr_base, input.dword_offset);
|
||||
if (buffer.GetSize() == 0) {
|
||||
continue;
|
||||
}
|
||||
vertex_attributes.push_back({
|
||||
.location = input.binding,
|
||||
.binding = input.binding,
|
||||
.format = LiverpoolToVK::SurfaceFormat(buffer.GetDataFmt(), buffer.GetNumberFmt()),
|
||||
.offset = 0,
|
||||
});
|
||||
vertex_bindings.push_back({
|
||||
.binding = input.binding,
|
||||
.stride = buffer.GetStride(),
|
||||
.inputRate = input.instance_step_rate == Shader::Info::VsInput::None
|
||||
? vk::VertexInputRate::eVertex
|
||||
: vk::VertexInputRate::eInstance,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const vk::PipelineVertexInputStateCreateInfo vertex_input_info = {
|
||||
|
@ -82,11 +88,17 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul
|
|||
"Rectangle List primitive type is only supported for embedded VS");
|
||||
}
|
||||
|
||||
auto prim_restart = key.enable_primitive_restart != 0;
|
||||
if (prim_restart && IsPrimitiveListTopology() && !instance.IsListRestartSupported()) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Primitive restart is enabled for list topology but not supported by driver.");
|
||||
prim_restart = false;
|
||||
}
|
||||
const vk::PipelineInputAssemblyStateCreateInfo input_assembly = {
|
||||
.topology = LiverpoolToVK::PrimitiveType(key.prim_type),
|
||||
.primitiveRestartEnable = key.enable_primitive_restart != 0,
|
||||
.primitiveRestartEnable = prim_restart,
|
||||
};
|
||||
ASSERT_MSG(!key.enable_primitive_restart || key.primitive_restart_index == 0xFFFF ||
|
||||
ASSERT_MSG(!prim_restart || key.primitive_restart_index == 0xFFFF ||
|
||||
key.primitive_restart_index == 0xFFFFFFFF,
|
||||
"Primitive restart index other than -1 is not supported yet");
|
||||
|
||||
|
@ -147,6 +159,8 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul
|
|||
}
|
||||
if (instance.IsVertexInputDynamicState()) {
|
||||
dynamic_states.push_back(vk::DynamicState::eVertexInputEXT);
|
||||
} else {
|
||||
dynamic_states.push_back(vk::DynamicState::eVertexInputBindingStrideEXT);
|
||||
}
|
||||
|
||||
const vk::PipelineDynamicStateCreateInfo dynamic_info = {
|
||||
|
@ -273,7 +287,7 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul
|
|||
.pNext = &pipeline_rendering_ci,
|
||||
.stageCount = static_cast<u32>(shader_stages.size()),
|
||||
.pStages = shader_stages.data(),
|
||||
.pVertexInputState = &vertex_input_info,
|
||||
.pVertexInputState = !instance.IsVertexInputDynamicState() ? &vertex_input_info : nullptr,
|
||||
.pInputAssemblyState = &input_assembly,
|
||||
.pViewportState = &viewport_info,
|
||||
.pRasterizationState = &raster_state,
|
||||
|
@ -379,7 +393,7 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs,
|
|||
for (const auto& buffer : stage->buffers) {
|
||||
const auto vsharp = buffer.GetSharp(*stage);
|
||||
const bool is_storage = buffer.IsStorage(vsharp);
|
||||
if (vsharp) {
|
||||
if (vsharp && vsharp.GetSize() > 0) {
|
||||
const VAddr address = vsharp.base_address;
|
||||
if (texture_cache.IsMeta(address)) {
|
||||
LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a PS shader (buffer)");
|
||||
|
|
|
@ -26,6 +26,7 @@ using Liverpool = AmdGpu::Liverpool;
|
|||
struct GraphicsPipelineKey {
|
||||
std::array<size_t, MaxShaderStages> stage_hashes;
|
||||
std::array<vk::Format, Liverpool::NumColorBuffers> color_formats;
|
||||
std::array<AmdGpu::NumberFormat, Liverpool::NumColorBuffers> color_num_formats;
|
||||
std::array<Liverpool::ColorBuffer::SwapMode, Liverpool::NumColorBuffers> mrt_swizzles;
|
||||
vk::Format depth_format;
|
||||
vk::Format stencil_format;
|
||||
|
@ -45,6 +46,7 @@ struct GraphicsPipelineKey {
|
|||
Liverpool::ColorBufferMask cb_shader_mask;
|
||||
std::array<Liverpool::BlendControl, Liverpool::NumColorBuffers> blend_controls;
|
||||
std::array<vk::ColorComponentFlags, Liverpool::NumColorBuffers> write_masks;
|
||||
std::array<vk::Format, MaxVertexBufferCount> vertex_buffer_formats;
|
||||
|
||||
bool operator==(const GraphicsPipelineKey& key) const noexcept {
|
||||
return std::memcmp(this, &key, sizeof(key)) == 0;
|
||||
|
@ -83,6 +85,16 @@ public:
|
|||
return key.depth_stencil.depth_enable.Value();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsPrimitiveListTopology() const {
|
||||
return key.prim_type == Liverpool::PrimitiveType::PointList ||
|
||||
key.prim_type == Liverpool::PrimitiveType::LineList ||
|
||||
key.prim_type == Liverpool::PrimitiveType::TriangleList ||
|
||||
key.prim_type == Liverpool::PrimitiveType::AdjLineList ||
|
||||
key.prim_type == Liverpool::PrimitiveType::AdjTriangleList ||
|
||||
key.prim_type == Liverpool::PrimitiveType::RectList ||
|
||||
key.prim_type == Liverpool::PrimitiveType::QuadList;
|
||||
}
|
||||
|
||||
private:
|
||||
void BuildDescSetLayout();
|
||||
|
||||
|
|
|
@ -260,9 +260,8 @@ bool Instance::CreateDevice() {
|
|||
color_write_en &= add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
|
||||
const bool calibrated_timestamps = add_extension(VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME);
|
||||
const bool robustness = add_extension(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
|
||||
const bool topology_restart =
|
||||
add_extension(VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME);
|
||||
const bool maintenance5 = add_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME);
|
||||
list_restart = add_extension(VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME);
|
||||
maintenance5 = add_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME);
|
||||
|
||||
// These extensions are promoted by Vulkan 1.3, but for greater compatibility we use Vulkan 1.2
|
||||
// with extensions.
|
||||
|
@ -272,6 +271,7 @@ bool Instance::CreateDevice() {
|
|||
add_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
|
||||
add_extension(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME);
|
||||
add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME);
|
||||
add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
|
||||
#ifdef __APPLE__
|
||||
// Required by Vulkan spec if supported.
|
||||
|
@ -414,7 +414,7 @@ bool Instance::CreateDevice() {
|
|||
if (!workgroup_memory_explicit_layout) {
|
||||
device_chain.unlink<vk::PhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR>();
|
||||
}
|
||||
if (!topology_restart) {
|
||||
if (!list_restart) {
|
||||
device_chain.unlink<vk::PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT>();
|
||||
}
|
||||
if (robustness) {
|
||||
|
|
|
@ -138,6 +138,15 @@ public:
|
|||
return null_descriptor;
|
||||
}
|
||||
|
||||
/// Returns true when VK_KHR_maintenance5 is supported.
|
||||
bool IsMaintenance5Supported() const {
|
||||
return maintenance5;
|
||||
}
|
||||
|
||||
bool IsListRestartSupported() const {
|
||||
return list_restart;
|
||||
}
|
||||
|
||||
/// Returns the vendor ID of the physical device
|
||||
u32 GetVendorID() const {
|
||||
return properties.vendorID;
|
||||
|
@ -280,6 +289,8 @@ private:
|
|||
bool color_write_en{};
|
||||
bool vertex_input_dynamic_state{};
|
||||
bool null_descriptor{};
|
||||
bool maintenance5{};
|
||||
bool list_restart{};
|
||||
u64 min_imported_host_pointer_alignment{};
|
||||
u32 subgroup_size{};
|
||||
bool tooling_info{};
|
||||
|
|
|
@ -95,10 +95,6 @@ Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) {
|
|||
case Shader::Stage::Fragment: {
|
||||
info.num_user_data = regs.ps_program.settings.num_user_regs;
|
||||
info.num_allocated_vgprs = regs.ps_program.settings.num_vgprs * 4;
|
||||
std::ranges::transform(graphics_key.mrt_swizzles, info.fs_info.mrt_swizzles.begin(),
|
||||
[](Liverpool::ColorBuffer::SwapMode mode) {
|
||||
return static_cast<Shader::MrtSwizzle>(mode);
|
||||
});
|
||||
const auto& ps_inputs = regs.ps_inputs;
|
||||
for (u32 i = 0; i < regs.num_interp; i++) {
|
||||
info.fs_info.inputs.push_back({
|
||||
|
@ -108,6 +104,12 @@ Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) {
|
|||
.default_value = u8(ps_inputs[i].default_value),
|
||||
});
|
||||
}
|
||||
for (u32 i = 0; i < Shader::MaxColorBuffers; i++) {
|
||||
info.fs_info.color_buffers[i] = {
|
||||
.num_format = graphics_key.color_num_formats[i],
|
||||
.mrt_swizzle = static_cast<Shader::MrtSwizzle>(graphics_key.mrt_swizzles[i]),
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Shader::Stage::Compute: {
|
||||
|
@ -244,9 +246,11 @@ bool PipelineCache::RefreshGraphicsKey() {
|
|||
// attachments. This might be not a case as HW color buffers can be bound in an arbitrary
|
||||
// order. We need to do some arrays compaction at this stage
|
||||
key.color_formats.fill(vk::Format::eUndefined);
|
||||
key.color_num_formats.fill(AmdGpu::NumberFormat::Unorm);
|
||||
key.blend_controls.fill({});
|
||||
key.write_masks.fill({});
|
||||
key.mrt_swizzles.fill(Liverpool::ColorBuffer::SwapMode::Standard);
|
||||
key.vertex_buffer_formats.fill(vk::Format::eUndefined);
|
||||
|
||||
// First pass of bindings check to idenitfy formats and swizzles and pass them to rhe shader
|
||||
// recompiler.
|
||||
|
@ -260,6 +264,7 @@ bool PipelineCache::RefreshGraphicsKey() {
|
|||
const bool is_vo_surface = renderer->IsVideoOutSurface(col_buf);
|
||||
key.color_formats[remapped_cb] = LiverpoolToVK::AdjustColorBufferFormat(
|
||||
base_format, col_buf.info.comp_swap.Value(), false /*is_vo_surface*/);
|
||||
key.color_num_formats[remapped_cb] = col_buf.NumFormat();
|
||||
if (base_format == key.color_formats[remapped_cb]) {
|
||||
key.mrt_swizzles[remapped_cb] = col_buf.info.comp_swap.Value();
|
||||
}
|
||||
|
@ -310,7 +315,26 @@ bool PipelineCache::RefreshGraphicsKey() {
|
|||
std::tie(infos[i], modules[i], key.stage_hashes[i]) = GetProgram(stage, params, binding);
|
||||
}
|
||||
|
||||
const auto* fs_info = infos[u32(Shader::Stage::Fragment)];
|
||||
const auto* vs_info = infos[static_cast<u32>(Shader::Stage::Vertex)];
|
||||
if (vs_info && !instance.IsVertexInputDynamicState()) {
|
||||
u32 vertex_binding = 0;
|
||||
for (const auto& input : vs_info->vs_inputs) {
|
||||
if (input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate0 ||
|
||||
input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate1) {
|
||||
continue;
|
||||
}
|
||||
const auto& buffer =
|
||||
vs_info->ReadUd<AmdGpu::Buffer>(input.sgpr_base, input.dword_offset);
|
||||
if (buffer.GetSize() == 0) {
|
||||
continue;
|
||||
}
|
||||
ASSERT(vertex_binding < MaxVertexBufferCount);
|
||||
key.vertex_buffer_formats[vertex_binding++] =
|
||||
Vulkan::LiverpoolToVK::SurfaceFormat(buffer.GetDataFmt(), buffer.GetNumberFmt());
|
||||
}
|
||||
}
|
||||
|
||||
const auto* fs_info = infos[static_cast<u32>(Shader::Stage::Fragment)];
|
||||
key.mrt_mask = fs_info ? fs_info->mrt_mask : 0u;
|
||||
|
||||
// Second pass to fill remain CB pipeline key data
|
||||
|
|
|
@ -368,11 +368,55 @@ void Rasterizer::UpdateViewportScissorState() {
|
|||
.maxDepth = vp.zscale + vp.zoffset,
|
||||
});
|
||||
}
|
||||
const auto& sc = regs.screen_scissor;
|
||||
scissors.push_back({
|
||||
.offset = {sc.top_left_x, sc.top_left_y},
|
||||
.extent = {sc.GetWidth(), sc.GetHeight()},
|
||||
});
|
||||
|
||||
const bool enable_offset = !regs.window_scissor.window_offset_disable.Value();
|
||||
Liverpool::Scissor scsr{};
|
||||
const auto combined_scissor_value_tl = [](s16 scr, s16 win, s16 gen, s16 win_offset) {
|
||||
return std::max({scr, s16(win + win_offset), s16(gen + win_offset)});
|
||||
};
|
||||
|
||||
scsr.top_left_x = combined_scissor_value_tl(
|
||||
regs.screen_scissor.top_left_x, s16(regs.window_scissor.top_left_x.Value()),
|
||||
s16(regs.generic_scissor.top_left_x.Value()),
|
||||
enable_offset ? regs.window_offset.window_x_offset : 0);
|
||||
|
||||
scsr.top_left_y = combined_scissor_value_tl(
|
||||
regs.screen_scissor.top_left_y, s16(regs.window_scissor.top_left_y.Value()),
|
||||
s16(regs.generic_scissor.top_left_y.Value()),
|
||||
enable_offset ? regs.window_offset.window_y_offset : 0);
|
||||
|
||||
const auto combined_scissor_value_br = [](s16 scr, s16 win, s16 gen, s16 win_offset) {
|
||||
return std::min({scr, s16(win + win_offset), s16(gen + win_offset)});
|
||||
};
|
||||
|
||||
scsr.bottom_right_x = combined_scissor_value_br(
|
||||
regs.screen_scissor.bottom_right_x, regs.window_scissor.bottom_right_x,
|
||||
regs.generic_scissor.bottom_right_x,
|
||||
enable_offset ? regs.window_offset.window_x_offset : 0);
|
||||
|
||||
scsr.bottom_right_y = combined_scissor_value_br(
|
||||
regs.screen_scissor.bottom_right_y, regs.window_scissor.bottom_right_y,
|
||||
regs.generic_scissor.bottom_right_y,
|
||||
enable_offset ? regs.window_offset.window_y_offset : 0);
|
||||
|
||||
for (u32 idx = 0; idx < Liverpool::NumViewports; idx++) {
|
||||
auto vp_scsr = scsr;
|
||||
if (regs.mode_control.vport_scissor_enable) {
|
||||
vp_scsr.top_left_x =
|
||||
std::max(vp_scsr.top_left_x, s16(regs.viewport_scissors[idx].top_left_x.Value()));
|
||||
vp_scsr.top_left_y =
|
||||
std::max(vp_scsr.top_left_y, s16(regs.viewport_scissors[idx].top_left_y.Value()));
|
||||
vp_scsr.bottom_right_x =
|
||||
std::min(vp_scsr.bottom_right_x, regs.viewport_scissors[idx].bottom_right_x);
|
||||
vp_scsr.bottom_right_y =
|
||||
std::min(vp_scsr.bottom_right_y, regs.viewport_scissors[idx].bottom_right_y);
|
||||
}
|
||||
scissors.push_back({
|
||||
.offset = {vp_scsr.top_left_x, vp_scsr.top_left_y},
|
||||
.extent = {vp_scsr.GetWidth(), vp_scsr.GetHeight()},
|
||||
});
|
||||
}
|
||||
|
||||
const auto cmdbuf = scheduler.CommandBuffer();
|
||||
cmdbuf.setViewport(0, viewports);
|
||||
cmdbuf.setScissor(0, scissors);
|
||||
|
|
|
@ -123,7 +123,7 @@ void Swapchain::Present() {
|
|||
};
|
||||
|
||||
auto result = instance.GetPresentQueue().presentKHR(present_info);
|
||||
if (result == vk::Result::eErrorOutOfDateKHR) {
|
||||
if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR) {
|
||||
needs_recreation = true;
|
||||
} else {
|
||||
ASSERT_MSG(result == vk::Result::eSuccess, "Swapchain presentation failed: {}",
|
||||
|
|