Merge branch 'shadps4-emu:main' into sceSystemServiceReceiveEvent

This commit is contained in:
Stephen Miller 2024-09-20 11:11:46 -05:00 committed by GitHub
commit 6f4152674a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
83 changed files with 6812 additions and 2744 deletions

View file

@ -25,10 +25,10 @@ jobs:
run: >
sudo apt-get update && sudo apt install libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev
- name: Cache CMake dependency source code
- name: Cache CMake Configuration
uses: actions/cache@v4
env:
cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-sources
cache-name: ${{ runner.os }}-qt-cache-cmake-configuration
with:
path: |
${{github.workspace}}/build
@ -36,16 +36,16 @@ jobs:
restore-keys: |
${{ env.cache-name }}-
- name: Cache CMake dependency build objects
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.14
env:
cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-builds
cache-name: ${{ runner.os }}-qt-cache-cmake-build
with:
append-timestamp: false
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel

View file

@ -25,10 +25,10 @@ jobs:
run: >
sudo apt-get update && sudo apt install libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential
- name: Cache CMake dependency source code
- name: Cache CMake Configuration
uses: actions/cache@v4
env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-sources
cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration
with:
path: |
${{github.workspace}}/build
@ -36,16 +36,16 @@ jobs:
restore-keys: |
${{ env.cache-name }}-
- name: Cache CMake dependency build objects
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.14
env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-builds
cache-name: ${{ runner.os }}-sdl-cache-cmake-build
with:
append-timestamp: false
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel

View file

@ -40,10 +40,10 @@ jobs:
arch: clang_64
archives: qtbase qttools
- name: Cache CMake dependency source code
- name: Cache CMake Configuration
uses: actions/cache@v4
env:
cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-sources
cache-name: ${{ runner.os }}-qt-cache-cmake-configuration
with:
path: |
${{github.workspace}}/build
@ -51,10 +51,10 @@ jobs:
restore-keys: |
${{ env.cache-name }}-
- name: Cache CMake dependency build objects
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.14
env:
cache-name: ${{runner.os}}-qt-cache-cmake-dependency-builds
cache-name: ${{runner.os}}-qt-cache-cmake-build
with:
append-timestamp: false
create-symlink: true
@ -62,7 +62,7 @@ jobs:
variant: sccache
- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache
run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu)

View file

@ -31,10 +31,10 @@ jobs:
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
arch -x86_64 /usr/local/bin/brew install molten-vk
- name: Cache CMake dependency source code
- name: Cache CMake Configuration
uses: actions/cache@v4
env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-sources
cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration
with:
path: |
${{github.workspace}}/build
@ -42,10 +42,10 @@ jobs:
restore-keys: |
${{ env.cache-name }}-
- name: Cache CMake dependency build objects
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.14
env:
cache-name: ${{runner.os}}-sdl-cache-cmake-dependency-builds
cache-name: ${{runner.os}}-sdl-cache-cmake-build
with:
append-timestamp: false
create-symlink: true
@ -53,7 +53,7 @@ jobs:
variant: sccache
- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache
run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu)

View file

@ -30,10 +30,10 @@ jobs:
arch: win64_msvc2019_64
archives: qtbase qttools
- name: Cache CMake dependency source code
- name: Cache CMake Configuration
uses: actions/cache@v4
env:
cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-sources
cache-name: ${{ runner.os }}-qt-ninja-cache-cmake-configuration
with:
path: |
${{github.workspace}}/build
@ -41,8 +41,21 @@ jobs:
restore-keys: |
${{ env.cache-name }}-
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.14
env:
cache-name: ${{ runner.os }}-qt-cache-cmake-build
with:
append-timestamp: false
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
- name: Setup VS Environment
uses: ilammy/msvc-dev-cmd@v1.13.0
with:
arch: amd64
- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL -DENABLE_QT_GUI=ON
run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel
@ -50,8 +63,8 @@ jobs:
- name: Deploy
run: |
mkdir upload
move build/Release/shadPS4.exe upload
windeployqt --dir upload upload/shadPS4.exe
move build/shadPS4.exe upload
windeployqt --no-compiler-runtime --no-system-d3d-compiler --no-system-dxc-compiler --dir upload upload/shadPS4.exe
- name: Get date and git hash
id: vars

View file

@ -20,10 +20,10 @@ jobs:
with:
submodules: recursive
- name: Cache CMake dependency source code
- name: Cache CMake Configuration
uses: actions/cache@v4
env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-sources
cache-name: ${{ runner.os }}-sdl-ninja-cache-cmake-configuration
with:
path: |
${{github.workspace}}/build
@ -31,8 +31,21 @@ jobs:
restore-keys: |
${{ env.cache-name }}-
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.14
env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-build
with:
append-timestamp: false
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
- name: Setup VS Environment
uses: ilammy/msvc-dev-cmd@v1.13.0
with:
arch: amd64
- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL
run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel
@ -49,4 +62,4 @@ jobs:
with:
name: shadps4-win64-sdl-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.shorthash }}
path: |
${{github.workspace}}/build/Release/shadPS4.exe
${{github.workspace}}/build/shadPS4.exe

View file

@ -47,6 +47,11 @@ else()
message(FATAL_ERROR "Unsupported CPU architecture: ${BASE_ARCHITECTURE}")
endif()
if (APPLE AND ARCHITECTURE STREQUAL "x86_64")
# Exclude ARM homebrew path to avoid conflicts when cross compiling.
list(APPEND CMAKE_IGNORE_PREFIX_PATH "/opt/homebrew")
endif()
# This function should be passed a list of all files in a target. It will automatically generate file groups
# following the directory hierarchy, so that the layout of the files in IDEs matches the one in the filesystem.
function(create_target_directory_groups target_name)
@ -227,11 +232,18 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
src/core/libraries/system/msgdialog_ui.cpp
src/core/libraries/system/posix.cpp
src/core/libraries/system/posix.h
src/core/libraries/save_data/error_codes.h
src/core/libraries/save_data/save_backup.cpp
src/core/libraries/save_data/save_backup.h
src/core/libraries/save_data/save_instance.cpp
src/core/libraries/save_data/save_instance.h
src/core/libraries/save_data/save_memory.cpp
src/core/libraries/save_data/save_memory.h
src/core/libraries/save_data/savedata.cpp
src/core/libraries/save_data/savedata.h
src/core/libraries/system/savedatadialog.cpp
src/core/libraries/system/savedatadialog.h
src/core/libraries/save_data/dialog/savedatadialog.cpp
src/core/libraries/save_data/dialog/savedatadialog.h
src/core/libraries/save_data/dialog/savedatadialog_ui.cpp
src/core/libraries/save_data/dialog/savedatadialog_ui.h
src/core/libraries/system/sysmodule.cpp
src/core/libraries/system/sysmodule.h
src/core/libraries/system/systemservice.cpp
@ -344,6 +356,7 @@ set(COMMON src/common/logging/backend.cpp
src/common/concepts.h
src/common/config.cpp
src/common/config.h
src/common/cstring.h
src/common/debug.h
src/common/disassembler.cpp
src/common/disassembler.h
@ -602,6 +615,7 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
set(IMGUI src/imgui/imgui_config.h
src/imgui/imgui_layer.h
src/imgui/imgui_std.h
src/imgui/imgui_texture.h
src/imgui/layer/video_info.cpp
src/imgui/layer/video_info.h
src/imgui/renderer/imgui_core.cpp
@ -610,6 +624,8 @@ set(IMGUI src/imgui/imgui_config.h
src/imgui/renderer/imgui_impl_sdl3.h
src/imgui/renderer/imgui_impl_vulkan.cpp
src/imgui/renderer/imgui_impl_vulkan.h
src/imgui/renderer/texture_manager.cpp
src/imgui/renderer/texture_manager.h
)
set(INPUT src/input/controller.cpp

View file

@ -12,7 +12,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
</h1>
<h1 align="center">
<a href="https://discord.gg/MyZRaBngxA">
<a href="https://discord.gg/bFJxfftGW6">
<img src="https://img.shields.io/discord/1080089157554155590?color=5865F2&label=shadPS4 Discord&logo=Discord&logoColor=white" width="240">
<a href="https://github.com/shadps4-emu/shadPS4/releases/latest">
<img src="https://img.shields.io/github/downloads/shadps4-emu/shadPS4/total.svg" width="140">

View file

@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
import std.io;
import std.sys;
struct Header {
u32 magic;
u32 version;
u32 key_table_offset;
u32 data_table_offset;
u32 index_table_entries;
};
struct KeyEntry {
char name[];
} [[inline]];
struct DataEntry<auto fmt, auto size> {
if (fmt == 0x0404) {
u32 int_value;
} else if(fmt == 0x0004) {
char bin_value[size];
} else if(fmt == 0x0204) {
char str_value[size];
} else {
std::warning("unknown fmt type");
}
} [[inline]];
struct IndexEntry {
u16 key_offset;
u16 param_fmt;
u32 param_len;
u32 param_max_len;
u32 data_offset;
};
struct Entry<auto KeyTableOffset, auto DataTableOffset> {
u64 begin = $;
IndexEntry index;
KeyEntry key @ KeyTableOffset + index.key_offset;
DataEntry<index.param_fmt, index.param_len> data @ DataTableOffset + index.data_offset;
u8 data_empty[index.param_max_len - index.param_len] @ DataTableOffset + index.data_offset + index.param_len;
$ = begin + sizeof(IndexEntry);
};
Header header @ 0;
std::assert(header.magic == 0x46535000, "Miss match magic");
std::assert(header.version == 0x00000101, "Miss match version");
Entry<header.key_table_offset, header.data_table_offset> list[header.index_table_entries] @ 0x14;

133
src/common/cstring.h Normal file
View file

@ -0,0 +1,133 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string_view>
#include "assert.h"
namespace Common {
/**
* @brief A null-terminated string with a fixed maximum length
* This class is not meant to be used as a general-purpose string class
* It is meant to be used as `char[N]` where memory layout is fixed
* @tparam N Maximum length of the string
* @tparam T Type of character
*/
template <size_t N, typename T = char>
class CString {
T data[N]{};
public:
class Iterator;
CString() = default;
template <size_t M>
explicit CString(const CString<M>& other)
requires(M <= N)
{
std::ranges::copy(other.begin(), other.end(), data);
}
void FromString(const std::basic_string_view<T>& str) {
size_t p = str.copy(data, N - 1);
data[p] = '\0';
}
void Zero() {
std::ranges::fill(data, 0);
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtautological-undefined-compare"
explicit(false) operator std::basic_string_view<T>() const {
if (this == nullptr) {
return {};
}
return std::basic_string_view<T>{data};
}
explicit operator std::basic_string<T>() const {
if (this == nullptr) {
return {};
}
return std::basic_string<T>{data};
}
std::basic_string<T> to_string() const {
if (this == nullptr) {
return {};
}
return std::basic_string<T>{data};
}
std::basic_string_view<T> to_view() const {
if (this == nullptr) {
return {};
}
return std::basic_string_view<T>{data};
}
#pragma clang diagnostic pop
char* begin() {
return data;
}
const char* begin() const {
return data;
}
char* end() {
return data + N;
}
const char* end() const {
return data + N;
}
T& operator[](size_t idx) {
return data[idx];
}
const T& operator[](size_t idx) const {
return data[idx];
}
class Iterator {
T* ptr;
T* end;
public:
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
using iterator_category = std::random_access_iterator_tag;
Iterator() = default;
explicit Iterator(T* ptr) : ptr(ptr), end(ptr + N) {}
Iterator& operator++() {
++ptr;
return *this;
}
Iterator operator++(int) {
Iterator tmp = *this;
++ptr;
return tmp;
}
operator T*() {
ASSERT_MSG(ptr >= end, "CString iterator out of bounds");
return ptr;
}
};
};
static_assert(sizeof(CString<13>) == sizeof(char[13])); // Ensure size still matches a simple array
static_assert(std::weakly_incrementable<CString<13>::Iterator>);
} // namespace Common

View file

@ -396,4 +396,18 @@ s64 IOFile::Tell() const {
return ftello(file);
}
u64 GetDirectorySize(const std::filesystem::path& path) {
if (!fs::exists(path)) {
return 0;
}
u64 total = 0;
for (const auto& entry : fs::recursive_directory_iterator(path)) {
if (fs::is_regular_file(entry.path())) {
total += fs::file_size(entry.path());
}
}
return total;
}
} // namespace Common::FS

View file

@ -219,4 +219,6 @@ private:
uintptr_t file_mapping = 0;
};
u64 GetDirectorySize(const std::filesystem::path& path);
} // namespace Common::FS

View file

@ -144,6 +144,10 @@ public:
initialization_in_progress_suppress_logging = false;
}
static bool IsActive() {
return instance != nullptr;
}
static void Start() {
instance->StartBackendThread();
}
@ -275,6 +279,10 @@ void Initialize(std::string_view log_file) {
Impl::Initialize(log_file.empty() ? LOG_FILE : log_file);
}
bool IsActive() {
return Impl::IsActive();
}
void Start() {
Impl::Start();
}

View file

@ -13,6 +13,8 @@ class Filter;
/// Initializes the logging system. This should be the first thing called in main.
void Initialize(std::string_view log_file = "");
bool IsActive();
/// Starts the logging threads.
void Start();

View file

@ -14,12 +14,17 @@
namespace Common {
std::string ToLower(std::string str) {
std::transform(str.begin(), str.end(), str.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
std::string ToLower(std::string_view input) {
std::string str;
str.resize(input.size());
std::ranges::transform(input, str.begin(), tolower);
return str;
}
void ToLowerInPlace(std::string& str) {
std::ranges::transform(str, str.begin(), tolower);
}
std::vector<std::string> SplitString(const std::string& str, char delimiter) {
std::istringstream iss(str);
std::vector<std::string> output(1);

View file

@ -10,7 +10,9 @@
namespace Common {
/// Make a string lowercase
[[nodiscard]] std::string ToLower(std::string str);
[[nodiscard]] std::string ToLower(std::string_view str);
void ToLowerInPlace(std::string& str);
std::vector<std::string> SplitString(const std::string& str, char delimiter);

View file

@ -9,6 +9,7 @@
#include "common/thread.h"
#ifdef __APPLE__
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <pthread.h>
#elif defined(_WIN32)
#include <windows.h>
@ -31,6 +32,48 @@
namespace Common {
#ifdef __APPLE__
void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) {
// CPU time to grant.
const std::chrono::nanoseconds computation_ns = period_ns / 2;
// Determine the timebase for converting time to ticks.
struct mach_timebase_info timebase {};
mach_timebase_info(&timebase);
const auto ticks_per_ns =
static_cast<double>(timebase.denom) / static_cast<double>(timebase.numer);
const auto period_ticks =
static_cast<u32>(static_cast<double>(period_ns.count()) * ticks_per_ns);
const auto computation_ticks =
static_cast<u32>(static_cast<double>(computation_ns.count()) * ticks_per_ns);
thread_time_constraint_policy policy = {
.period = period_ticks,
.computation = computation_ticks,
// Should not matter since preemptible is false, but needs to be >= computation regardless.
.constraint = computation_ticks,
.preemptible = false,
};
int ret = thread_policy_set(
pthread_mach_thread_np(pthread_self()), THREAD_TIME_CONSTRAINT_POLICY,
reinterpret_cast<thread_policy_t>(&policy), THREAD_TIME_CONSTRAINT_POLICY_COUNT);
if (ret != KERN_SUCCESS) {
LOG_ERROR(Common, "Could not set thread to real-time with period {} ns: {}",
period_ns.count(), ret);
}
}
#else
void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) {
// Not implemented
}
#endif
#ifdef _WIN32
void SetCurrentThreadPriority(ThreadPriority new_priority) {

View file

@ -4,6 +4,7 @@
#pragma once
#include <chrono>
#include "common/types.h"
namespace Common {
@ -16,6 +17,8 @@ enum class ThreadPriority : u32 {
Critical = 4,
};
void SetCurrentThreadRealtime(std::chrono::nanoseconds period_ns);
void SetCurrentThreadPriority(ThreadPriority new_priority);
void SetCurrentThreadName(const char* name);

View file

@ -658,26 +658,15 @@ static PatchModule* GetModule(const void* ptr) {
return &(std::prev(upper_bound)->second);
}
static bool TryPatch(void* code_address) {
auto* code = static_cast<u8*>(code_address);
auto* module = GetModule(code);
if (module == nullptr) {
return false;
}
std::unique_lock lock{module->mutex};
// Return early if already patched, in case multiple threads signaled at the same time.
if (std::ranges::find(module->patched, code) != module->patched.end()) {
return true;
}
/// Returns a boolean indicating whether the instruction was patched, and the offset to advance past
/// whatever is at the current code pointer.
static std::pair<bool, u64> TryPatch(u8* code, PatchModule* module) {
ZydisDecodedInstruction instruction;
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
const auto status =
ZydisDecoderDecodeFull(&instr_decoder, code, module->end - code, &instruction, operands);
if (!ZYAN_SUCCESS(status)) {
return false;
return std::make_pair(false, 1);
}
if (Patches.contains(instruction.mnemonic)) {
@ -717,20 +706,52 @@ static bool TryPatch(void* code_address) {
module->patched.insert(code);
LOG_DEBUG(Core, "Patched instruction '{}' at: {}",
ZydisMnemonicGetString(instruction.mnemonic), fmt::ptr(code));
return true;
return std::make_pair(true, instruction.length);
}
}
}
return false;
return std::make_pair(false, instruction.length);
}
static bool TryPatchJit(void* code_address) {
auto* code = static_cast<u8*>(code_address);
auto* module = GetModule(code);
if (module == nullptr) {
return false;
}
std::unique_lock lock{module->mutex};
// Return early if already patched, in case multiple threads signaled at the same time.
if (std::ranges::find(module->patched, code) != module->patched.end()) {
return true;
}
return TryPatch(code, module).first;
}
static void TryPatchAot(void* code_address, u64 code_size) {
auto* code = static_cast<u8*>(code_address);
auto* module = GetModule(code);
if (module == nullptr) {
return;
}
std::unique_lock lock{module->mutex};
const auto* end = code + code_size;
while (code < end) {
code += TryPatch(code, module).second;
}
}
static bool PatchesAccessViolationHandler(void* code_address, void* fault_address, bool is_write) {
return TryPatch(code_address);
return TryPatchJit(code_address);
}
static bool PatchesIllegalInstructionHandler(void* code_address) {
return TryPatch(code_address);
return TryPatchJit(code_address);
}
static void PatchesInit() {
@ -757,17 +778,23 @@ void RegisterPatchModule(void* module_ptr, u64 module_size, void* trampoline_are
}
void PrePatchInstructions(u64 segment_addr, u64 segment_size) {
#ifdef __APPLE__
#if defined(__APPLE__)
// HACK: For some reason patching in the signal handler at the start of a page does not work
// under Rosetta 2. Patch any instructions at the start of a page ahead of time.
if (!Patches.empty()) {
auto* code_page = reinterpret_cast<u8*>(Common::AlignUp(segment_addr, 0x1000));
const auto* end_page = code_page + Common::AlignUp(segment_size, 0x1000);
while (code_page < end_page) {
TryPatch(code_page);
TryPatchJit(code_page);
code_page += 0x1000;
}
}
#elif !defined(_WIN32)
// Linux and others have an FS segment pointing to valid memory, so continue to do full
// ahead-of-time patching for now until a better solution is worked out.
if (!Patches.empty()) {
TryPatchAot(reinterpret_cast<void*>(segment_addr), segment_size);
}
#endif
}

View file

@ -2,61 +2,275 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "common/assert.h"
#include "common/io_file.h"
#include "common/logging/log.h"
#include "core/file_format/psf.h"
PSF::PSF() = default;
static const std::unordered_map<std::string_view, u32> psf_known_max_sizes = {
{"ACCOUNT_ID", 8}, {"CATEGORY", 4}, {"DETAIL", 1024}, {"FORMAT", 4},
{"MAINTITLE", 128}, {"PARAMS", 1024}, {"SAVEDATA_BLOCKS", 8}, {"SAVEDATA_DIRECTORY", 32},
{"SUBTITLE", 128}, {"TITLE_ID", 12},
};
static inline u32 get_max_size(std::string_view key, u32 default_value) {
if (const auto& v = psf_known_max_sizes.find(key); v != psf_known_max_sizes.end()) {
return v->second;
}
return default_value;
}
PSF::~PSF() = default;
bool PSF::open(const std::string& filepath, const std::vector<u8>& psfBuffer) {
if (!psfBuffer.empty()) {
psf.resize(psfBuffer.size());
psf = psfBuffer;
} else {
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
return false;
}
const u64 psfSize = file.GetSize();
psf.resize(psfSize);
file.Seek(0);
file.Read(psf);
file.Close();
bool PSF::Open(const std::filesystem::path& filepath) {
if (std::filesystem::exists(filepath)) {
last_write = std::filesystem::last_write_time(filepath);
}
// Parse file contents
PSFHeader header;
std::memcpy(&header, psf.data(), sizeof(header));
for (u32 i = 0; i < header.index_table_entries; i++) {
PSFEntry entry;
std::memcpy(&entry, &psf[sizeof(PSFHeader) + i * sizeof(PSFEntry)], sizeof(entry));
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
return false;
}
const std::string key = (char*)&psf[header.key_table_offset + entry.key_offset];
if (entry.param_fmt == PSFEntry::Fmt::TextRaw ||
entry.param_fmt == PSFEntry::Fmt::TextNormal) {
map_strings[key] = (char*)&psf[header.data_table_offset + entry.data_offset];
}
if (entry.param_fmt == PSFEntry::Fmt::Integer) {
u32 value;
std::memcpy(&value, &psf[header.data_table_offset + entry.data_offset], sizeof(value));
map_integers[key] = value;
const u64 psfSize = file.GetSize();
std::vector<u8> psf(psfSize);
file.Seek(0);
file.Read(psf);
file.Close();
return Open(psf);
}
bool PSF::Open(const std::vector<u8>& psf_buffer) {
const u8* psf_data = psf_buffer.data();
entry_list.clear();
map_binaries.clear();
map_strings.clear();
map_integers.clear();
// Parse file contents
PSFHeader header{};
std::memcpy(&header, psf_data, sizeof(header));
if (header.magic != PSF_MAGIC) {
LOG_ERROR(Core, "Invalid PSF magic number");
return false;
}
if (header.version != PSF_VERSION_1_1 && header.version != PSF_VERSION_1_0) {
LOG_ERROR(Core, "Unsupported PSF version: 0x{:08x}", header.version);
return false;
}
for (u32 i = 0; i < header.index_table_entries; i++) {
PSFRawEntry raw_entry{};
std::memcpy(&raw_entry, psf_data + sizeof(PSFHeader) + i * sizeof(PSFRawEntry),
sizeof(raw_entry));
Entry& entry = entry_list.emplace_back();
entry.key = std::string{(char*)(psf_data + header.key_table_offset + raw_entry.key_offset)};
entry.param_fmt = static_cast<PSFEntryFmt>(raw_entry.param_fmt.Raw());
entry.max_len = raw_entry.param_max_len;
const u8* data = psf_data + header.data_table_offset + raw_entry.data_offset;
switch (entry.param_fmt) {
case PSFEntryFmt::Binary: {
std::vector<u8> value(raw_entry.param_len);
std::memcpy(value.data(), data, raw_entry.param_len);
map_binaries.emplace(i, std::move(value));
} break;
case PSFEntryFmt::Text: {
std::string c_str{reinterpret_cast<const char*>(data)};
map_strings.emplace(i, std::move(c_str));
} break;
case PSFEntryFmt::Integer: {
ASSERT_MSG(raw_entry.param_len == sizeof(s32), "PSF integer entry size mismatch");
s32 integer = *(s32*)data;
map_integers.emplace(i, integer);
} break;
default:
UNREACHABLE_MSG("Unknown PSF entry format");
}
}
return true;
}
std::string PSF::GetString(const std::string& key) {
if (map_strings.find(key) != map_strings.end()) {
return map_strings.at(key);
bool PSF::Encode(const std::filesystem::path& filepath) const {
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Write);
if (!file.IsOpen()) {
return false;
}
return "";
last_write = std::filesystem::file_time_type::clock::now();
const auto psf_buffer = Encode();
return file.Write(psf_buffer) == psf_buffer.size();
}
u32 PSF::GetInteger(const std::string& key) {
if (map_integers.find(key) != map_integers.end()) {
return map_integers.at(key);
}
return -1;
std::vector<u8> PSF::Encode() const {
std::vector<u8> psf_buffer;
Encode(psf_buffer);
return psf_buffer;
}
void PSF::Encode(std::vector<u8>& psf_buffer) const {
psf_buffer.resize(sizeof(PSFHeader) + sizeof(PSFRawEntry) * entry_list.size());
{
auto& header = *(PSFHeader*)psf_buffer.data();
header.magic = PSF_MAGIC;
header.version = PSF_VERSION_1_1;
header.index_table_entries = entry_list.size();
}
const size_t key_table_offset = psf_buffer.size();
((PSFHeader*)psf_buffer.data())->key_table_offset = key_table_offset;
for (size_t i = 0; i < entry_list.size(); i++) {
auto& raw_entry = ((PSFRawEntry*)(psf_buffer.data() + sizeof(PSFHeader)))[i];
const Entry& entry = entry_list[i];
raw_entry.key_offset = psf_buffer.size() - key_table_offset;
raw_entry.param_fmt.FromRaw(static_cast<u16>(entry.param_fmt));
raw_entry.param_max_len = entry.max_len;
std::ranges::copy(entry.key, std::back_inserter(psf_buffer));
psf_buffer.push_back(0); // NULL terminator
}
const size_t data_table_offset = psf_buffer.size();
((PSFHeader*)psf_buffer.data())->data_table_offset = data_table_offset;
for (size_t i = 0; i < entry_list.size(); i++) {
if (psf_buffer.size() % 4 != 0) {
std::ranges::fill_n(std::back_inserter(psf_buffer), 4 - psf_buffer.size() % 4, 0);
}
auto& raw_entry = ((PSFRawEntry*)(psf_buffer.data() + sizeof(PSFHeader)))[i];
const Entry& entry = entry_list[i];
raw_entry.data_offset = psf_buffer.size() - data_table_offset;
s32 additional_padding = s32(raw_entry.param_max_len);
switch (entry.param_fmt) {
case PSFEntryFmt::Binary: {
const auto& value = map_binaries.at(i);
raw_entry.param_len = value.size();
additional_padding -= s32(raw_entry.param_len);
std::ranges::copy(value, std::back_inserter(psf_buffer));
} break;
case PSFEntryFmt::Text: {
const auto& value = map_strings.at(i);
raw_entry.param_len = value.size() + 1;
additional_padding -= s32(raw_entry.param_len);
std::ranges::copy(value, std::back_inserter(psf_buffer));
psf_buffer.push_back(0); // NULL terminator
} break;
case PSFEntryFmt::Integer: {
const auto& value = map_integers.at(i);
raw_entry.param_len = sizeof(s32);
additional_padding -= s32(raw_entry.param_len);
const auto value_bytes = reinterpret_cast<const u8*>(&value);
std::ranges::copy(value_bytes, value_bytes + sizeof(s32),
std::back_inserter(psf_buffer));
} break;
default:
UNREACHABLE_MSG("Unknown PSF entry format");
}
ASSERT_MSG(additional_padding >= 0, "PSF entry max size mismatch");
std::ranges::fill_n(std::back_inserter(psf_buffer), additional_padding, 0);
}
}
std::optional<std::span<const u8>> PSF::GetBinary(std::string_view key) const {
const auto& [it, index] = FindEntry(key);
if (it == entry_list.end()) {
return {};
}
ASSERT(it->param_fmt == PSFEntryFmt::Binary);
return std::span{map_binaries.at(index)};
}
std::optional<std::string_view> PSF::GetString(std::string_view key) const {
const auto& [it, index] = FindEntry(key);
if (it == entry_list.end()) {
return {};
}
ASSERT(it->param_fmt == PSFEntryFmt::Text);
return std::string_view{map_strings.at(index)};
}
std::optional<s32> PSF::GetInteger(std::string_view key) const {
const auto& [it, index] = FindEntry(key);
if (it == entry_list.end()) {
return {};
}
ASSERT(it->param_fmt == PSFEntryFmt::Integer);
return map_integers.at(index);
}
void PSF::AddBinary(std::string key, std::vector<u8> value, bool update) {
auto [it, index] = FindEntry(key);
bool exist = it != entry_list.end();
if (exist && !update) {
LOG_ERROR(Core, "PSF: Tried to add binary key that already exists: {}", key);
return;
}
if (exist) {
ASSERT_MSG(it->param_fmt == PSFEntryFmt::Binary, "PSF: Change format is not supported");
it->max_len = get_max_size(key, value.size());
map_binaries.at(index) = std::move(value);
return;
}
Entry& entry = entry_list.emplace_back();
entry.max_len = get_max_size(key, value.size());
entry.key = std::move(key);
entry.param_fmt = PSFEntryFmt::Binary;
map_binaries.emplace(entry_list.size() - 1, std::move(value));
}
void PSF::AddString(std::string key, std::string value, bool update) {
auto [it, index] = FindEntry(key);
bool exist = it != entry_list.end();
if (exist && !update) {
LOG_ERROR(Core, "PSF: Tried to add string key that already exists: {}", key);
return;
}
if (exist) {
ASSERT_MSG(it->param_fmt == PSFEntryFmt::Text, "PSF: Change format is not supported");
it->max_len = get_max_size(key, value.size() + 1);
map_strings.at(index) = std::move(value);
return;
}
Entry& entry = entry_list.emplace_back();
entry.max_len = get_max_size(key, value.size() + 1);
entry.key = std::move(key);
entry.param_fmt = PSFEntryFmt::Text;
map_strings.emplace(entry_list.size() - 1, std::move(value));
}
void PSF::AddInteger(std::string key, s32 value, bool update) {
auto [it, index] = FindEntry(key);
bool exist = it != entry_list.end();
if (exist && !update) {
LOG_ERROR(Core, "PSF: Tried to add integer key that already exists: {}", key);
return;
}
if (exist) {
ASSERT_MSG(it->param_fmt == PSFEntryFmt::Integer, "PSF: Change format is not supported");
it->max_len = sizeof(s32);
map_integers.at(index) = value;
return;
}
Entry& entry = entry_list.emplace_back();
entry.key = std::move(key);
entry.param_fmt = PSFEntryFmt::Integer;
entry.max_len = sizeof(s32);
map_integers.emplace(entry_list.size() - 1, value);
}
std::pair<std::vector<PSF::Entry>::iterator, size_t> PSF::FindEntry(std::string_view key) {
auto entry =
std::ranges::find_if(entry_list, [&](const auto& entry) { return entry.key == key; });
return {entry, std::distance(entry_list.begin(), entry)};
}
std::pair<std::vector<PSF::Entry>::const_iterator, size_t> PSF::FindEntry(
std::string_view key) const {
auto entry =
std::ranges::find_if(entry_list, [&](const auto& entry) { return entry.key == key; });
return {entry, std::distance(entry_list.begin(), entry)};
}

View file

@ -3,11 +3,18 @@
#pragma once
#include <filesystem>
#include <span>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
#include "common/endian.h"
constexpr u32 PSF_MAGIC = 0x00505346;
constexpr u32 PSF_VERSION_1_1 = 0x00000101;
constexpr u32 PSF_VERSION_1_0 = 0x00000100;
struct PSFHeader {
u32_be magic;
u32_le version;
@ -15,34 +22,72 @@ struct PSFHeader {
u32_le data_table_offset;
u32_le index_table_entries;
};
static_assert(sizeof(PSFHeader) == 0x14);
struct PSFEntry {
enum Fmt : u16 {
TextRaw = 0x0400, // String in UTF-8 format and not NULL terminated
TextNormal = 0x0402, // String in UTF-8 format and NULL terminated
Integer = 0x0404, // Unsigned 32-bit integer
};
struct PSFRawEntry {
u16_le key_offset;
u16_be param_fmt;
u32_le param_len;
u32_le param_max_len;
u32_le data_offset;
};
static_assert(sizeof(PSFRawEntry) == 0x10);
enum class PSFEntryFmt : u16 {
Binary = 0x0004, // Binary data
Text = 0x0204, // String in UTF-8 format and NULL terminated
Integer = 0x0404, // Signed 32-bit integer
};
class PSF {
struct Entry {
std::string key;
PSFEntryFmt param_fmt;
u32 max_len;
};
public:
PSF();
~PSF();
PSF() = default;
~PSF() = default;
bool open(const std::string& filepath, const std::vector<u8>& psfBuffer);
PSF(const PSF& other) = default;
PSF(PSF&& other) noexcept = default;
PSF& operator=(const PSF& other) = default;
PSF& operator=(PSF&& other) noexcept = default;
std::string GetString(const std::string& key);
u32 GetInteger(const std::string& key);
bool Open(const std::filesystem::path& filepath);
bool Open(const std::vector<u8>& psf_buffer);
std::unordered_map<std::string, std::string> map_strings;
std::unordered_map<std::string, u32> map_integers;
[[nodiscard]] std::vector<u8> Encode() const;
void Encode(std::vector<u8>& buf) const;
bool Encode(const std::filesystem::path& filepath) const;
std::optional<std::span<const u8>> GetBinary(std::string_view key) const;
std::optional<std::string_view> GetString(std::string_view key) const;
std::optional<s32> GetInteger(std::string_view key) const;
void AddBinary(std::string key, std::vector<u8> value, bool update = false);
void AddString(std::string key, std::string value, bool update = false);
void AddInteger(std::string key, s32 value, bool update = false);
[[nodiscard]] std::filesystem::file_time_type GetLastWrite() const {
return last_write;
}
[[nodiscard]] const std::vector<Entry>& GetEntries() const {
return entry_list;
}
private:
std::vector<u8> psf;
mutable std::filesystem::file_time_type last_write;
std::vector<Entry> entry_list;
std::unordered_map<size_t, std::vector<u8>> map_binaries;
std::unordered_map<size_t, std::string> map_strings;
std::unordered_map<size_t, s32> map_integers;
[[nodiscard]] std::pair<std::vector<Entry>::iterator, size_t> FindEntry(std::string_view key);
[[nodiscard]] std::pair<std::vector<Entry>::const_iterator, size_t> FindEntry(
std::string_view key) const;
};

View file

@ -9,12 +9,14 @@ namespace Core::FileSys {
constexpr int RESERVED_HANDLES = 3; // First 3 handles are stdin,stdout,stderr
void MntPoints::Mount(const std::filesystem::path& host_folder, const std::string& guest_folder) {
void MntPoints::Mount(const std::filesystem::path& host_folder, const std::string& guest_folder,
bool read_only) {
std::scoped_lock lock{m_mutex};
m_mnt_pairs.emplace_back(host_folder, guest_folder);
m_mnt_pairs.emplace_back(host_folder, guest_folder, read_only);
}
void MntPoints::Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder) {
std::scoped_lock lock{m_mutex};
auto it = std::remove_if(m_mnt_pairs.begin(), m_mnt_pairs.end(),
[&](const MntPair& pair) { return pair.mount == guest_folder; });
m_mnt_pairs.erase(it, m_mnt_pairs.end());
@ -25,7 +27,7 @@ void MntPoints::UnmountAll() {
m_mnt_pairs.clear();
}
std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory) {
std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory, bool* is_read_only) {
// Evil games like Turok2 pass double slashes e.g /app0//game.kpf
std::string corrected_path(guest_directory);
size_t pos = corrected_path.find("//");
@ -39,6 +41,10 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory) {
return "";
}
if (is_read_only) {
*is_read_only = mount->read_only;
}
// Nothing to do if getting the mount itself.
if (corrected_path == mount->mount) {
return mount->host_path;

View file

@ -22,18 +22,22 @@ public:
struct MntPair {
std::filesystem::path host_path;
std::string mount; // e.g /app0/
bool read_only;
};
explicit MntPoints() = default;
~MntPoints() = default;
void Mount(const std::filesystem::path& host_folder, const std::string& guest_folder);
void Mount(const std::filesystem::path& host_folder, const std::string& guest_folder,
bool read_only = false);
void Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder);
void UnmountAll();
std::filesystem::path GetHostPath(std::string_view guest_directory);
std::filesystem::path GetHostPath(std::string_view guest_directory,
bool* is_read_only = nullptr);
const MntPair* GetMount(const std::string& guest_path) {
std::scoped_lock lock{m_mutex};
const auto it = std::ranges::find_if(
m_mnt_pairs, [&](const auto& mount) { return guest_path.starts_with(mount.mount); });
return it == m_mnt_pairs.end() ? nullptr : &*it;

View file

@ -90,37 +90,32 @@ int PS4_SYSV_ABI sceAppContentAddcontUnmount() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceAppContentAppParamGetInt(OrbisAppContentAppParamId paramId, s32* value) {
if (value == nullptr)
int PS4_SYSV_ABI sceAppContentAppParamGetInt(OrbisAppContentAppParamId paramId, s32* out_value) {
if (out_value == nullptr)
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
auto* param_sfo = Common::Singleton<PSF>::Instance();
std::optional<s32> value;
switch (paramId) {
case ORBIS_APP_CONTENT_APPPARAM_ID_SKU_FLAG:
*value = ORBIS_APP_CONTENT_APPPARAM_SKU_FLAG_FULL;
value = ORBIS_APP_CONTENT_APPPARAM_SKU_FLAG_FULL;
break;
case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_1:
*value = param_sfo->GetInteger("USER_DEFINED_PARAM_1");
value = param_sfo->GetInteger("USER_DEFINED_PARAM_1");
break;
case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_2:
*value = param_sfo->GetInteger("USER_DEFINED_PARAM_2");
value = param_sfo->GetInteger("USER_DEFINED_PARAM_2");
break;
case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_3:
*value = param_sfo->GetInteger("USER_DEFINED_PARAM_3");
value = param_sfo->GetInteger("USER_DEFINED_PARAM_3");
break;
case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_4:
*value = param_sfo->GetInteger("USER_DEFINED_PARAM_4");
value = param_sfo->GetInteger("USER_DEFINED_PARAM_4");
break;
default:
LOG_ERROR(Lib_AppContent, " paramId = {}, value = {} paramId is not valid", paramId,
*value);
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
}
if (*value == -1) {
LOG_ERROR(Lib_AppContent,
" paramId = {}, value = {} value is not valid can't read param.sfo?", paramId,
*value);
LOG_ERROR(Lib_AppContent, " paramId = {} paramId is not valid", paramId);
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
}
*out_value = value.value_or(0);
return ORBIS_OK;
}
@ -251,7 +246,7 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar
auto* param_sfo = Common::Singleton<PSF>::Instance();
const auto addons_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir);
title_id = param_sfo->GetString("TITLE_ID");
title_id = *param_sfo->GetString("TITLE_ID");
auto addon_path = addons_dir / title_id;
if (std::filesystem::exists(addon_path)) {
for (const auto& entry : std::filesystem::directory_iterator(addon_path)) {

View file

@ -40,8 +40,6 @@ public:
bool SetLooping(bool is_looping);
private:
using ScePthreadMutex = Kernel::ScePthreadMutex;
// Memory Replacement
static void* PS4_SYSV_ABI Allocate(void* handle, u32 alignment, u32 size);
static void PS4_SYSV_ABI Deallocate(void* handle, void* memory);

View file

@ -7,6 +7,8 @@
#include "common/alignment.h"
#include "common/singleton.h"
#include "common/thread.h"
#include "core/file_sys/fs.h"
#include "core/libraries/kernel/time_management.h"
@ -432,6 +434,8 @@ void AvPlayerSource::ReleaseAVFormatContext(AVFormatContext* context) {
void AvPlayerSource::DemuxerThread(std::stop_token stop) {
using namespace std::chrono;
Common::SetCurrentThreadName("shadPS4:AvDemuxer");
if (!m_audio_stream_index.has_value() && !m_video_stream_index.has_value()) {
LOG_WARNING(Lib_AvPlayer, "Could not start DEMUXER thread. No streams enabled.");
return;
@ -439,7 +443,8 @@ void AvPlayerSource::DemuxerThread(std::stop_token stop) {
LOG_INFO(Lib_AvPlayer, "Demuxer Thread started");
while (!stop.stop_requested()) {
if (m_video_packets.Size() > 30 && m_audio_packets.Size() > 8) {
if (m_video_packets.Size() > 30 &&
(!m_audio_stream_index.has_value() || m_audio_packets.Size() > 8)) {
std::this_thread::sleep_for(milliseconds(5));
continue;
}
@ -498,7 +503,7 @@ void AvPlayerSource::DemuxerThread(std::stop_token stop) {
}
m_state.OnEOF();
LOG_INFO(Lib_AvPlayer, "Demuxer Thread exited normaly");
LOG_INFO(Lib_AvPlayer, "Demuxer Thread exited normally");
}
AvPlayerSource::AVFramePtr AvPlayerSource::ConvertVideoFrame(const AVFrame& frame) {
@ -597,6 +602,8 @@ Frame AvPlayerSource::PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame
void AvPlayerSource::VideoDecoderThread(std::stop_token stop) {
using namespace std::chrono;
Common::SetCurrentThreadName("shadPS4:AvVideoDecoder");
LOG_INFO(Lib_AvPlayer, "Video Decoder Thread started");
while ((!m_is_eof || m_video_packets.Size() != 0) && !stop.stop_requested()) {
if (!m_video_packets_cv.Wait(stop,
@ -652,7 +659,7 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) {
}
}
LOG_INFO(Lib_AvPlayer, "Video Decoder Thread exited normaly");
LOG_INFO(Lib_AvPlayer, "Video Decoder Thread exited normally");
}
AvPlayerSource::AVFramePtr AvPlayerSource::ConvertAudioFrame(const AVFrame& frame) {
@ -717,6 +724,8 @@ Frame AvPlayerSource::PrepareAudioFrame(FrameBuffer buffer, const AVFrame& frame
void AvPlayerSource::AudioDecoderThread(std::stop_token stop) {
using namespace std::chrono;
Common::SetCurrentThreadName("shadPS4:AvAudioDecoder");
LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread started");
while ((!m_is_eof || m_audio_packets.Size() != 0) && !stop.stop_requested()) {
if (!m_audio_packets_cv.Wait(stop,
@ -772,7 +781,7 @@ void AvPlayerSource::AudioDecoderThread(std::stop_token stop) {
}
}
LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread exited normaly");
LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread exited normally");
}
bool AvPlayerSource::HasRunningThreads() const {

View file

@ -5,6 +5,8 @@
#include "avplayer_source.h"
#include "avplayer_state.h"
#include "common/thread.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/time_management.h"
@ -178,6 +180,7 @@ bool AvPlayerState::Start() {
void AvPlayerState::AvControllerThread(std::stop_token stop) {
using std::chrono::milliseconds;
Common::SetCurrentThreadName("shadPS4:AvController");
while (!stop.stop_requested()) {
if (m_event_queue.Size() != 0) {

View file

@ -38,9 +38,6 @@ public:
bool SetLooping(bool is_looping);
private:
using ScePthreadMutex = Kernel::ScePthreadMutex;
using ScePthread = Kernel::ScePthread;
// Event Replacement
static void PS4_SYSV_ABI AutoPlayEventCallback(void* handle, s32 event_id, s32 source_id,
void* event_data);

View file

@ -1272,8 +1272,12 @@ int PS4_SYSV_ABI sceGnmRequestMipStatsReportAndReset() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceGnmResetVgtControl() {
LOG_ERROR(Lib_GnmDriver, "(STUBBED) called");
s32 PS4_SYSV_ABI sceGnmResetVgtControl(u32* cmdbuf, u32 size) {
LOG_TRACE(Lib_GnmDriver, "called");
if (cmdbuf == nullptr || size != 3) {
return -1;
}
PM4CmdSetData::SetContextReg(cmdbuf, 0x2aau, 0xffu); // IA_MULTI_VGT_PARAM
return ORBIS_OK;
}

View file

@ -134,7 +134,7 @@ s32 PS4_SYSV_ABI sceGnmRegisterResource(void* res_handle, void* owner_handle, co
int PS4_SYSV_ABI sceGnmRequestFlipAndSubmitDone();
int PS4_SYSV_ABI sceGnmRequestFlipAndSubmitDoneForWorkload();
int PS4_SYSV_ABI sceGnmRequestMipStatsReportAndReset();
int PS4_SYSV_ABI sceGnmResetVgtControl();
s32 PS4_SYSV_ABI sceGnmResetVgtControl(u32* cmdbuf, u32 size);
int PS4_SYSV_ABI sceGnmSdmaClose();
int PS4_SYSV_ABI sceGnmSdmaConstFill();
int PS4_SYSV_ABI sceGnmSdmaCopyLinear();

View file

@ -179,11 +179,16 @@ int PS4_SYSV_ABI sceKernelUnlink(const char* path) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
const auto host_path = mnt->GetHostPath(path);
bool ro = false;
const auto host_path = mnt->GetHostPath(path, &ro);
if (host_path.empty()) {
return SCE_KERNEL_ERROR_EACCES;
}
if (ro) {
return SCE_KERNEL_ERROR_EROFS;
}
if (std::filesystem::is_directory(host_path)) {
return SCE_KERNEL_ERROR_EPERM;
}
@ -270,11 +275,18 @@ int PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) {
return SCE_KERNEL_ERROR_EINVAL;
}
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
const auto dir_name = mnt->GetHostPath(path);
bool ro = false;
const auto dir_name = mnt->GetHostPath(path, &ro);
if (std::filesystem::exists(dir_name)) {
return SCE_KERNEL_ERROR_EEXIST;
}
if (ro) {
return SCE_KERNEL_ERROR_EROFS;
}
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
if (dir_name.empty() || !std::filesystem::create_directory(dir_name)) {
return SCE_KERNEL_ERROR_EIO;
@ -299,7 +311,8 @@ int PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
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();
const auto path_name = mnt->GetHostPath(path);
bool ro = false;
const auto path_name = mnt->GetHostPath(path, &ro);
std::memset(sb, 0, sizeof(OrbisKernelStat));
const bool is_dir = std::filesystem::is_directory(path_name);
const bool is_file = std::filesystem::is_regular_file(path_name);
@ -319,6 +332,10 @@ int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) {
sb->st_blocks = (sb->st_size + 511) / 512;
// TODO incomplete
}
if (ro) {
sb->st_mode &= ~0000555u;
}
return ORBIS_OK;
}
@ -500,11 +517,18 @@ s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) {
s32 PS4_SYSV_ABI sceKernelRename(const char* from, const char* to) {
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
const auto src_path = mnt->GetHostPath(from);
bool ro = false;
const auto src_path = mnt->GetHostPath(from, &ro);
if (!std::filesystem::exists(src_path)) {
return ORBIS_KERNEL_ERROR_ENOENT;
}
const auto dst_path = mnt->GetHostPath(to);
if (ro) {
return SCE_KERNEL_ERROR_EROFS;
}
const auto dst_path = mnt->GetHostPath(to, &ro);
if (ro) {
return SCE_KERNEL_ERROR_EROFS;
}
const bool src_is_dir = std::filesystem::is_directory(src_path);
const bool dst_is_dir = std::filesystem::is_directory(dst_path);
if (src_is_dir && !dst_is_dir) {

View file

@ -244,7 +244,7 @@ int PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time,
int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver) {
auto* param_sfo = Common::Singleton<PSF>::Instance();
int version = param_sfo->GetInteger("SYSTEM_VER");
int version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000);
LOG_INFO(Kernel, "returned system version = {:#x}", version);
*ver = version;
return (version > 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL;
@ -425,6 +425,7 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("WFcfL2lzido", "libkernel", 1, "libkernel", 1, 1, sceKernelQueryMemoryProtection);
LIB_FUNCTION("BHouLQzh0X0", "libkernel", 1, "libkernel", 1, 1, sceKernelDirectMemoryQuery);
LIB_FUNCTION("MBuItvba6z8", "libkernel", 1, "libkernel", 1, 1, sceKernelReleaseDirectMemory);
LIB_FUNCTION("PGhQHd-dzv8", "libkernel", 1, "libkernel", 1, 1, sceKernelMmap);
LIB_FUNCTION("cQke9UuBQOk", "libkernel", 1, "libkernel", 1, 1, sceKernelMunmap);
LIB_FUNCTION("mL8NDH86iQI", "libkernel", 1, "libkernel", 1, 1, sceKernelMapNamedFlexibleMemory);
LIB_FUNCTION("aNz11fnnzi4", "libkernel", 1, "libkernel", 1, 1,

View file

@ -32,6 +32,10 @@ public:
return ORBIS_KERNEL_ERROR_EBUSY;
}
if (timeout && *timeout == 0) {
return SCE_KERNEL_ERROR_ETIMEDOUT;
}
// Create waiting thread object and add it into the list of waiters.
WaitingThread waiter{need_count, is_fifo};
const auto it = AddWaiter(&waiter);

View file

@ -27,12 +27,12 @@
#include "core/libraries/playgo/playgo.h"
#include "core/libraries/random/random.h"
#include "core/libraries/rtc/rtc.h"
#include "core/libraries/save_data/dialog/savedatadialog.h"
#include "core/libraries/save_data/savedata.h"
#include "core/libraries/screenshot/screenshot.h"
#include "core/libraries/system/commondialog.h"
#include "core/libraries/system/msgdialog.h"
#include "core/libraries/system/posix.h"
#include "core/libraries/system/savedatadialog.h"
#include "core/libraries/system/sysmodule.h"
#include "core/libraries/system/systemservice.h"
#include "core/libraries/system/userservice.h"
@ -57,11 +57,11 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
Libraries::Net::RegisterlibSceNet(sym);
Libraries::NetCtl::RegisterlibSceNetCtl(sym);
Libraries::SaveData::RegisterlibSceSaveData(sym);
Libraries::SaveData::Dialog::RegisterlibSceSaveDataDialog(sym);
Libraries::Ssl::RegisterlibSceSsl(sym);
Libraries::SysModule::RegisterlibSceSysmodule(sym);
Libraries::Posix::Registerlibsceposix(sym);
Libraries::AudioIn::RegisterlibSceAudioIn(sym);
Libraries::SaveDataDialog::RegisterlibSceSaveDataDialog(sym);
Libraries::NpManager::RegisterlibSceNpManager(sym);
Libraries::NpScore::RegisterlibSceNpScore(sym);
Libraries::NpTrophy::RegisterlibSceNpTrophy(sym);

View file

@ -31,10 +31,6 @@ public:
void Finish();
void Draw() override;
bool ShouldGrabGamepad() override {
return false;
}
};
}; // namespace Libraries::NpTrophy

View file

@ -0,0 +1,163 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/libs.h"
#include "core/libraries/system/commondialog.h"
#include "magic_enum.hpp"
#include "savedatadialog.h"
#include "savedatadialog_ui.h"
namespace Libraries::SaveData::Dialog {
using CommonDialog::Error;
using CommonDialog::Result;
using CommonDialog::Status;
static auto g_status = Status::NONE;
static SaveDialogState g_state{};
static SaveDialogResult g_result{};
static SaveDialogUi g_save_dialog_ui;
Error PS4_SYSV_ABI sceSaveDataDialogClose() {
LOG_DEBUG(Lib_SaveDataDialog, "called");
if (g_status != Status::RUNNING) {
return Error::NOT_RUNNING;
}
g_save_dialog_ui.Finish(ButtonId::INVALID);
g_save_dialog_ui = SaveDialogUi{};
return Error::OK;
}
Error PS4_SYSV_ABI sceSaveDataDialogGetResult(OrbisSaveDataDialogResult* result) {
LOG_DEBUG(Lib_SaveDataDialog, "called");
if (g_status != Status::FINISHED) {
return Error::NOT_FINISHED;
}
if (result == nullptr) {
return Error::ARG_NULL;
}
g_result.CopyTo(*result);
return Error::OK;
}
Status PS4_SYSV_ABI sceSaveDataDialogGetStatus() {
LOG_TRACE(Lib_SaveDataDialog, "called status={}", magic_enum::enum_name(g_status));
return g_status;
}
Error PS4_SYSV_ABI sceSaveDataDialogInitialize() {
LOG_DEBUG(Lib_SaveDataDialog, "called");
if (!CommonDialog::g_isInitialized) {
return Error::NOT_SYSTEM_INITIALIZED;
}
if (g_status != Status::NONE) {
return Error::ALREADY_INITIALIZED;
}
if (CommonDialog::g_isUsed) {
return Error::BUSY;
}
g_status = Status::INITIALIZED;
CommonDialog::g_isUsed = true;
return Error::OK;
}
s32 PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay() {
return 1;
}
Error PS4_SYSV_ABI sceSaveDataDialogOpen(const OrbisSaveDataDialogParam* param) {
if (g_status != Status::INITIALIZED && g_status != Status::FINISHED) {
LOG_INFO(Lib_SaveDataDialog, "called without initialize");
return Error::INVALID_STATE;
}
if (param == nullptr) {
LOG_DEBUG(Lib_SaveDataDialog, "called param:(NULL)");
return Error::ARG_NULL;
}
LOG_DEBUG(Lib_SaveDataDialog, "called param->mode: {}", magic_enum::enum_name(param->mode));
ASSERT(param->size == sizeof(OrbisSaveDataDialogParam));
ASSERT(param->baseParam.size == sizeof(CommonDialog::BaseParam));
g_result = {};
g_state = SaveDialogState{*param};
g_status = Status::RUNNING;
g_save_dialog_ui = SaveDialogUi(&g_state, &g_status, &g_result);
return Error::OK;
}
Error PS4_SYSV_ABI sceSaveDataDialogProgressBarInc(OrbisSaveDataDialogProgressBarTarget target,
u32 delta) {
LOG_DEBUG(Lib_SaveDataDialog, "called");
if (g_status != Status::RUNNING) {
return Error::NOT_RUNNING;
}
if (g_state.GetMode() != SaveDataDialogMode::PROGRESS_BAR) {
return Error::NOT_SUPPORTED;
}
if (target != OrbisSaveDataDialogProgressBarTarget::DEFAULT) {
return Error::PARAM_INVALID;
}
g_state.GetState<SaveDialogState::ProgressBarState>().progress += delta;
return Error::OK;
}
Error PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue(OrbisSaveDataDialogProgressBarTarget target,
u32 rate) {
LOG_DEBUG(Lib_SaveDataDialog, "called");
if (g_status != Status::RUNNING) {
return Error::NOT_RUNNING;
}
if (g_state.GetMode() != SaveDataDialogMode::PROGRESS_BAR) {
return Error::NOT_SUPPORTED;
}
if (target != OrbisSaveDataDialogProgressBarTarget::DEFAULT) {
return Error::PARAM_INVALID;
}
g_state.GetState<SaveDialogState::ProgressBarState>().progress = rate;
return Error::OK;
}
Error PS4_SYSV_ABI sceSaveDataDialogTerminate() {
LOG_DEBUG(Lib_SaveDataDialog, "called");
if (g_status == Status::RUNNING) {
sceSaveDataDialogClose();
}
if (g_status == Status::NONE) {
return Error::NOT_INITIALIZED;
}
g_save_dialog_ui = SaveDialogUi{};
g_status = Status::NONE;
CommonDialog::g_isUsed = false;
return Error::OK;
}
Status PS4_SYSV_ABI sceSaveDataDialogUpdateStatus() {
LOG_TRACE(Lib_SaveDataDialog, "called status={}", magic_enum::enum_name(g_status));
return g_status;
}
void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("fH46Lag88XY", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogClose);
LIB_FUNCTION("yEiJ-qqr6Cg", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogGetResult);
LIB_FUNCTION("ERKzksauAJA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogGetStatus);
LIB_FUNCTION("s9e3+YpRnzw", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogInitialize);
LIB_FUNCTION("en7gNVnh878", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogIsReadyToDisplay);
LIB_FUNCTION("4tPhsP6FpDI", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogOpen);
LIB_FUNCTION("V-uEeFKARJU", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogProgressBarInc);
LIB_FUNCTION("hay1CfTmLyA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogProgressBarSetValue);
LIB_FUNCTION("YuH2FA7azqQ", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogTerminate);
LIB_FUNCTION("KK3Bdg1RWK0", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogUpdateStatus);
};
} // namespace Libraries::SaveData::Dialog

View file

@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/system/commondialog.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::SaveData::Dialog {
struct OrbisSaveDataDialogParam;
struct OrbisSaveDataDialogResult;
enum class OrbisSaveDataDialogProgressBarTarget : u32;
CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogClose();
CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogGetResult(OrbisSaveDataDialogResult* result);
CommonDialog::Status PS4_SYSV_ABI sceSaveDataDialogGetStatus();
CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogInitialize();
s32 PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay();
CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogOpen(const OrbisSaveDataDialogParam* param);
CommonDialog::Error PS4_SYSV_ABI
sceSaveDataDialogProgressBarInc(OrbisSaveDataDialogProgressBarTarget target, u32 delta);
CommonDialog::Error PS4_SYSV_ABI
sceSaveDataDialogProgressBarSetValue(OrbisSaveDataDialogProgressBarTarget target, u32 rate);
CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogTerminate();
CommonDialog::Status PS4_SYSV_ABI sceSaveDataDialogUpdateStatus();
void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::SaveData::Dialog

View file

@ -0,0 +1,802 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/chrono.h>
#include <imgui.h>
#include <magic_enum.hpp>
#include "common/singleton.h"
#include "core/file_sys/fs.h"
#include "core/libraries/save_data/save_instance.h"
#include "imgui/imgui_std.h"
#include "savedatadialog_ui.h"
using namespace ImGui;
using namespace Libraries::CommonDialog;
constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB
constexpr auto SAVE_ICON_SIZE = ImVec2{152.0f, 85.0f};
constexpr auto SAVE_ICON_PADDING = ImVec2{8.0f, 2.0f};
static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f};
constexpr auto FOOTER_HEIGHT = BUTTON_SIZE.y + 15.0f;
static constexpr float PROGRESS_BAR_WIDTH{0.8f};
static ::Core::FileSys::MntPoints* g_mnt =
Common::Singleton<::Core::FileSys::MntPoints>::Instance();
static std::string SpaceSizeToString(size_t size) {
std::string size_str;
if (size > 1024 * 1024 * 1024) { // > 1GB
size_str = fmt::format("{:.2f} GB", double(size / 1024 / 1024) / 1024.0f);
} else if (size > 1024 * 1024) { // > 1MB
size_str = fmt::format("{:.2f} MB", double(size / 1024) / 1024.0f);
} else if (size > 1024) { // > 1KB
size_str = fmt::format("{:.2f} KB", double(size) / 1024.0f);
} else {
size_str = fmt::format("{} B", size);
}
return size_str;
}
namespace Libraries::SaveData::Dialog {
void SaveDialogResult::CopyTo(OrbisSaveDataDialogResult& result) const {
result.mode = this->mode;
result.result = this->result;
result.buttonId = this->button_id;
if (result.dirName != nullptr) {
result.dirName->data.FromString(this->dir_name);
}
if (result.param != nullptr && this->param.GetString(SaveParams::MAINTITLE).has_value()) {
result.param->FromSFO(this->param);
}
result.userData = this->user_data;
}
SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) {
this->mode = param.mode;
this->type = param.dispType;
this->user_data = param.userData;
if (param.optionParam != nullptr) {
this->enable_back = {param.optionParam->back == OptionBack::ENABLE};
}
static std::string game_serial{*Common::Singleton<PSF>::Instance()->GetString("CONTENT_ID"), 7,
9};
const auto item = param.items;
this->user_id = item->userId;
if (item->titleId == nullptr) {
this->title_id = game_serial;
} else {
this->title_id = item->titleId->data.to_string();
}
for (u32 i = 0; i < item->dirNameNum; i++) {
const auto dir_name = item->dirName[i].data.to_view();
if (dir_name.empty()) {
continue;
}
auto dir_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name);
auto param_sfo_path = dir_path / "sce_sys" / "param.sfo";
if (!std::filesystem::exists(param_sfo_path)) {
continue;
}
PSF param_sfo;
param_sfo.Open(param_sfo_path);
auto last_write = param_sfo.GetLastWrite();
#ifdef _WIN32
auto utc_time = std::chrono::file_clock::to_utc(last_write);
#else
auto utc_time = std::chrono::file_clock::to_sys(last_write);
#endif
std::string date_str = fmt::format("{:%d %b, %Y %R}", utc_time);
size_t size = Common::FS::GetDirectorySize(dir_path);
std::string size_str = SpaceSizeToString(size);
auto icon_path = dir_path / "sce_sys" / "icon0.png";
RefCountedTexture icon;
if (std::filesystem::exists(icon_path)) {
icon = RefCountedTexture::DecodePngFile(icon_path);
}
bool is_corrupted = std::filesystem::exists(dir_path / "sce_sys" / "corrupted");
this->save_list.emplace_back(Item{
.dir_name = std::string{dir_name},
.icon = icon,
.title = std::string{*param_sfo.GetString(SaveParams::MAINTITLE)},
.subtitle = std::string{*param_sfo.GetString(SaveParams::SUBTITLE)},
.details = std::string{*param_sfo.GetString(SaveParams::DETAIL)},
.date = date_str,
.size = size_str,
.last_write = param_sfo.GetLastWrite(),
.pfo = param_sfo,
.is_corrupted = is_corrupted,
});
}
if (type == DialogType::SAVE) {
RefCountedTexture icon;
std::string title{"New Save"};
const auto new_item = item->newItem;
if (new_item != nullptr && new_item->iconBuf && new_item->iconSize) {
auto buf = (u8*)new_item->iconBuf;
icon = RefCountedTexture::DecodePngTexture({buf, buf + new_item->iconSize});
} else {
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
if (std::filesystem::exists(src_icon)) {
icon = RefCountedTexture::DecodePngFile(src_icon);
}
}
if (new_item != nullptr && new_item->title != nullptr) {
title = std::string{new_item->title};
}
this->new_item = Item{
.dir_name = "",
.icon = icon,
.title = title,
};
}
if (item->focusPos != FocusPos::DIRNAME) {
this->focus_pos = item->focusPos;
} else {
this->focus_pos = item->focusPosDirName->data.to_string();
}
this->style = item->itemStyle;
switch (mode) {
case SaveDataDialogMode::USER_MSG: {
this->state = UserState{param};
} break;
case SaveDataDialogMode::SYSTEM_MSG:
this->state = SystemState{*this, param};
break;
case SaveDataDialogMode::ERROR_CODE: {
this->state = ErrorCodeState{param};
} break;
case SaveDataDialogMode::PROGRESS_BAR: {
this->state = ProgressBarState{*this, param};
} break;
default:
break;
}
}
SaveDialogState::UserState::UserState(const OrbisSaveDataDialogParam& param) {
auto& user = *param.userMsgParam;
this->type = user.buttonType;
this->msg_type = user.msgType;
this->msg = user.msg != nullptr ? std::string{user.msg} : std::string{};
}
SaveDialogState::SystemState::SystemState(const SaveDialogState& state,
const OrbisSaveDataDialogParam& param) {
#define M(save, load, del) \
if (type == DialogType::SAVE) \
this->msg = save; \
else if (type == DialogType::LOAD) \
this->msg = load; \
else if (type == DialogType::DELETE) \
this->msg = del; \
else \
UNREACHABLE()
auto type = param.dispType;
auto& sys = *param.sysMsgParam;
switch (sys.msgType) {
case SystemMessageType::NODATA: {
this->msg = "There is no saved data";
} break;
case SystemMessageType::CONFIRM:
show_no = true;
M("Do you want to save?", "Do you want to load this saved data?",
"Do you want to delete this saved data?");
break;
case SystemMessageType::OVERWRITE:
show_no = true;
M("Do you want to overwrite the existing saved data?", "##UNKNOWN##", "##UNKNOWN##");
break;
case SystemMessageType::NOSPACE:
M(fmt::format(
"There is not enough space to save the data. To continue {} free space is required.",
SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)),
"##UNKNOWN##", "##UNKNOWN##");
break;
case SystemMessageType::PROGRESS:
hide_ok = true;
show_cancel = state.enable_back.value_or(false);
M("Saving...", "Loading...", "Deleting...");
break;
case SystemMessageType::FILE_CORRUPTED:
this->msg = "The saved data is corrupted.";
break;
case SystemMessageType::FINISHED:
M("Saved successfully.", "Loading complete.", "Deletion complete.");
break;
case SystemMessageType::NOSPACE_CONTINUABLE:
M(fmt::format("There is not enough space to save the data. {} free space is required.",
SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)),
"##UNKNOWN##", "##UNKNOWN##");
break;
case SystemMessageType::CORRUPTED_AND_DELETED: {
show_cancel = state.enable_back.value_or(true);
const char* msg1 = "The saved data is corrupted and will be deleted.";
M(msg1, msg1, "##UNKNOWN##");
} break;
case SystemMessageType::CORRUPTED_AND_CREATED: {
show_cancel = state.enable_back.value_or(true);
const char* msg2 = "The saved data is corrupted. This saved data will be deleted and a new "
"one will be created.";
M(msg2, msg2, "##UNKNOWN##");
} break;
case SystemMessageType::CORRUPTED_AND_RESTORE: {
show_cancel = state.enable_back.value_or(true);
const char* msg3 =
"The saved data is corrupted. The data that was backed up by the system will be "
"restored.";
M(msg3, msg3, "##UNKNOWN##");
} break;
case SystemMessageType::TOTAL_SIZE_EXCEEDED:
M("Cannot create more saved data", "##UNKNOWN##", "##UNKNOWN##");
break;
default:
msg = fmt::format("Unknown message type: {}", magic_enum::enum_name(sys.msgType));
break;
}
#undef M
}
SaveDialogState::ErrorCodeState::ErrorCodeState(const OrbisSaveDataDialogParam& param) {
auto& err = *param.errorCodeParam;
constexpr auto NOT_FOUND = 0x809F0008;
constexpr auto BROKEN = 0x809F000F;
switch (err.errorCode) {
case NOT_FOUND:
this->error_msg = "There is not saved data.";
break;
case BROKEN:
this->error_msg = "The data is corrupted.";
break;
default:
this->error_msg = fmt::format("An error has occurred. ({:X})", err.errorCode);
break;
}
}
SaveDialogState::ProgressBarState::ProgressBarState(const SaveDialogState& state,
const OrbisSaveDataDialogParam& param) {
this->progress = 0;
auto& bar = *param.progressBarParam;
switch (bar.sysMsgType) {
case ProgressSystemMessageType::INVALID:
this->msg = bar.msg != nullptr ? std::string{bar.msg} : std::string{};
break;
case ProgressSystemMessageType::PROGRESS:
switch (state.type) {
case DialogType::SAVE:
this->msg = "Saving...";
break;
case DialogType::LOAD:
this->msg = "Loading...";
break;
case DialogType::DELETE:
this->msg = "Deleting...";
break;
}
break;
case ProgressSystemMessageType::RESTORE:
this->msg = "Restoring saved data...";
break;
}
}
SaveDialogUi::SaveDialogUi(SaveDialogState* state, Status* status, SaveDialogResult* result)
: state(state), status(status), result(result) {
if (status && *status == Status::RUNNING) {
first_render = true;
AddLayer(this);
}
}
SaveDialogUi::~SaveDialogUi() {
Finish(ButtonId::INVALID);
}
SaveDialogUi::SaveDialogUi(SaveDialogUi&& other) noexcept
: Layer(other), state(other.state), status(other.status), result(other.result) {
std::scoped_lock lock(draw_mutex, other.draw_mutex);
other.state = nullptr;
other.status = nullptr;
other.result = nullptr;
if (status && *status == Status::RUNNING) {
first_render = true;
AddLayer(this);
}
}
SaveDialogUi& SaveDialogUi::operator=(SaveDialogUi other) {
std::scoped_lock lock(draw_mutex, other.draw_mutex);
using std::swap;
swap(state, other.state);
swap(status, other.status);
swap(result, other.result);
if (status && *status == Status::RUNNING) {
first_render = true;
AddLayer(this);
}
return *this;
}
void SaveDialogUi::Finish(ButtonId buttonId, Result r) {
std::unique_lock lock(draw_mutex);
if (result) {
result->mode = this->state->mode;
result->result = r;
result->button_id = buttonId;
result->user_data = this->state->user_data;
if (state && state->mode != SaveDataDialogMode::LIST && !state->save_list.empty()) {
result->dir_name = state->save_list.front().dir_name;
}
}
if (status) {
*status = Status::FINISHED;
}
RemoveLayer(this);
}
void SaveDialogUi::Draw() {
std::unique_lock lock{draw_mutex};
if (status == nullptr || *status != Status::RUNNING || state == nullptr) {
return;
}
const auto& ctx = *GetCurrentContext();
const auto& io = ctx.IO;
ImVec2 window_size;
if (state->GetMode() == SaveDataDialogMode::LIST) {
window_size = ImVec2{
std::min(io.DisplaySize.x - 200.0f, 1100.0f),
std::min(io.DisplaySize.y - 100.0f, 700.0f),
};
} else {
window_size = ImVec2{
std::min(io.DisplaySize.x, 500.0f),
std::min(io.DisplaySize.y, 300.0f),
};
}
CentralizeWindow();
SetNextWindowSize(window_size);
SetNextWindowCollapsed(false);
if (first_render || !io.NavActive) {
SetNextWindowFocus();
}
if (Begin("Save Data Dialog##SaveDataDialog", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
DrawPrettyBackground();
Separator();
// Draw title bigger
SetWindowFontScale(1.7f);
switch (state->type) {
case DialogType::SAVE:
TextUnformatted("Save");
break;
case DialogType::LOAD:
TextUnformatted("Load");
break;
case DialogType::DELETE:
TextUnformatted("Delete");
break;
}
SetWindowFontScale(1.0f);
Separator();
BeginGroup();
switch (state->GetMode()) {
case SaveDataDialogMode::LIST:
DrawList();
break;
case SaveDataDialogMode::USER_MSG:
DrawUser();
break;
case SaveDataDialogMode::SYSTEM_MSG:
DrawSystemMessage();
break;
case SaveDataDialogMode::ERROR_CODE:
DrawErrorCode();
break;
case SaveDataDialogMode::PROGRESS_BAR:
DrawProgressBar();
break;
default:
TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "!!!Unknown dialog mode!!!");
}
EndGroup();
}
End();
first_render = false;
if (*status == Status::FINISHED) {
if (state) {
*state = SaveDialogState{};
}
state = nullptr;
status = nullptr;
result = nullptr;
}
}
void SaveDialogUi::DrawItem(int _id, const SaveDialogState::Item& item, bool clickable) {
constexpr auto text_spacing = 1.2f;
auto& ctx = *GetCurrentContext();
auto& window = *ctx.CurrentWindow;
auto content_region_avail = GetContentRegionAvail();
const auto outer_pos = window.DC.CursorPos;
const auto pos = outer_pos + SAVE_ICON_PADDING;
const ImVec2 size = {content_region_avail.x - SAVE_ICON_PADDING.x,
SAVE_ICON_SIZE.y + SAVE_ICON_PADDING.y};
const ImRect bb{outer_pos, outer_pos + size + SAVE_ICON_PADDING};
const ImGuiID id = GetID(_id);
ItemSize(size + ImVec2{0.0f, SAVE_ICON_PADDING.y * 2.0f});
if (!ItemAdd(bb, id)) {
return;
}
window.DrawList->AddRectFilled(bb.Min + SAVE_ICON_PADDING, bb.Max - SAVE_ICON_PADDING,
GetColorU32(ImVec4{0.3f}));
bool hovered = false;
if (clickable) {
bool held;
bool pressed = ButtonBehavior(bb, id, &hovered, &held);
if (pressed) {
result->dir_name = item.dir_name;
result->param = item.pfo;
Finish(ButtonId::INVALID);
}
RenderNavHighlight(bb, id);
}
if (item.icon) {
auto texture = item.icon.GetTexture();
window.DrawList->AddImage(texture.im_id, pos, pos + SAVE_ICON_SIZE);
} else {
// placeholder
window.DrawList->AddRectFilled(pos, pos + SAVE_ICON_SIZE, GetColorU32(ImVec4{0.7f}));
}
auto pos_x = SAVE_ICON_SIZE.x + 5.0f;
auto pos_y = 2.0f;
if (!item.title.empty()) {
const char* begin = &item.title.front();
const char* end = &item.title.back() + 1;
SetWindowFontScale(2.0f);
RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false);
if (item.is_corrupted) {
float width = CalcTextSize(begin, end).x + 10.0f;
PushStyleColor(ImGuiCol_Text, 0xFF0000FF);
RenderText(pos + ImVec2{pos_x + width, pos_y}, "- Corrupted", nullptr, false);
PopStyleColor();
}
pos_y += ctx.FontSize * text_spacing;
}
SetWindowFontScale(1.3f);
if (state->style == ItemStyle::TITLE_SUBTITLE_DATESIZE) {
if (!item.subtitle.empty()) {
const char* begin = &item.subtitle.front();
const char* end = &item.subtitle.back() + 1;
RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false);
}
pos_y += ctx.FontSize * text_spacing;
}
{
float width = 0.0f;
if (!item.date.empty()) {
const char* d_begin = &item.date.front();
const char* d_end = &item.date.back() + 1;
width = CalcTextSize(d_begin, d_end).x + 15.0f;
RenderText(pos + ImVec2{pos_x, pos_y}, d_begin, d_end, false);
}
if (!item.size.empty()) {
const char* s_begin = &item.size.front();
const char* s_end = &item.size.back() + 1;
RenderText(pos + ImVec2{pos_x + width, pos_y}, s_begin, s_end, false);
}
pos_y += ctx.FontSize * text_spacing;
}
if (state->style == ItemStyle::TITLE_DATASIZE_SUBTITLE && !item.subtitle.empty()) {
const char* begin = &item.subtitle.front();
const char* end = &item.subtitle.back() + 1;
RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false);
}
SetWindowFontScale(1.0f);
if (hovered) {
window.DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, 0, 2.0f);
}
}
void SaveDialogUi::DrawList() {
auto availableSize = GetContentRegionAvail();
constexpr auto footerHeight = 30.0f;
availableSize.y -= footerHeight + 1.0f;
BeginChild("##ScrollingRegion", availableSize, ImGuiChildFlags_NavFlattened);
int i = 0;
if (state->new_item.has_value()) {
DrawItem(i++, state->new_item.value());
}
for (const auto& item : state->save_list) {
DrawItem(i++, item);
}
if (first_render) { // Make the initial focus
if (std::holds_alternative<FocusPos>(state->focus_pos)) {
auto pos = std::get<FocusPos>(state->focus_pos);
if (pos == FocusPos::LISTHEAD || pos == FocusPos::DATAHEAD) {
SetItemCurrentNavFocus(GetID(0));
} else if (pos == FocusPos::LISTTAIL || pos == FocusPos::DATATAIL) {
SetItemCurrentNavFocus(GetID(std::max(i - 1, 0)));
} else { // Date
int idx = 0;
int max_idx = 0;
bool is_min = pos == FocusPos::DATAOLDEST;
std::filesystem::file_time_type max_write{};
if (state->new_item.has_value()) {
idx++;
}
for (const auto& item : state->save_list) {
if (item.last_write > max_write ^ is_min) {
max_write = item.last_write;
max_idx = idx;
}
idx++;
}
SetItemCurrentNavFocus(GetID(max_idx));
}
} else if (std::holds_alternative<std::string>(state->focus_pos)) {
auto dir_name = std::get<std::string>(state->focus_pos);
if (dir_name.empty()) {
SetItemCurrentNavFocus(GetID(0));
} else {
int idx = 0;
if (state->new_item.has_value()) {
if (dir_name == state->new_item->dir_name) {
SetItemCurrentNavFocus(GetID(idx));
}
idx++;
}
for (const auto& item : state->save_list) {
if (item.dir_name == dir_name) {
SetItemCurrentNavFocus(GetID(idx));
break;
}
idx++;
}
}
}
}
EndChild();
Separator();
if (state->enable_back.value_or(true)) {
constexpr auto back = "Back";
constexpr float pad = 7.0f;
const auto txt_size = CalcTextSize(back);
const auto button_size = ImVec2{
std::max(txt_size.x, 100.0f) + pad * 2.0f,
footerHeight - pad,
};
SetCursorPosX(GetContentRegionAvail().x - button_size.x);
if (Button(back, button_size)) {
result->dir_name.clear();
Finish(ButtonId::INVALID);
}
if (IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
SetItemCurrentNavFocus();
}
}
}
void SaveDialogUi::DrawUser() {
const auto& user_state = state->GetState<SaveDialogState::UserState>();
const auto btn_type = user_state.type;
const auto ws = GetWindowSize();
if (!state->save_list.empty()) {
DrawItem(0, state->save_list.front(), false);
}
auto has_btn = btn_type != ButtonType::NONE;
ImVec2 btn_space;
if (has_btn) {
btn_space = ImVec2{0.0f, FOOTER_HEIGHT};
}
const auto& msg = user_state.msg;
if (!msg.empty()) {
const char* begin = &msg.front();
const char* end = &msg.back() + 1;
if (user_state.msg_type == UserMessageType::ERROR) {
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f));
// Maybe make the text bold?
}
DrawCenteredText(begin, end, GetContentRegionAvail() - btn_space);
if (user_state.msg_type == UserMessageType::ERROR) {
PopStyleColor();
}
}
if (has_btn) {
int count = 1;
if (btn_type == ButtonType::YESNO || btn_type == ButtonType::ONCANCEL) {
++count;
}
SetCursorPos({
ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast<float>(count),
ws.y - FOOTER_HEIGHT + 5.0f,
});
BeginGroup();
if (btn_type == ButtonType::YESNO) {
if (Button("Yes", BUTTON_SIZE)) {
Finish(ButtonId::YES);
}
SameLine();
if (Button("No", BUTTON_SIZE)) {
Finish(ButtonId::NO);
}
if (first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
SetItemCurrentNavFocus();
}
} else {
if (Button("OK", BUTTON_SIZE)) {
Finish(ButtonId::OK);
}
if (first_render) {
SetItemCurrentNavFocus();
}
if (btn_type == ButtonType::ONCANCEL) {
SameLine();
if (Button("Cancel", BUTTON_SIZE)) {
Finish(ButtonId::INVALID, Result::USER_CANCELED);
}
if (IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
SetItemCurrentNavFocus();
}
}
}
EndGroup();
}
}
void SaveDialogUi::DrawSystemMessage() {
const auto& sys_state = state->GetState<SaveDialogState::SystemState>();
if (!state->save_list.empty()) {
DrawItem(0, state->save_list.front(), false);
}
const auto ws = GetWindowSize();
const auto& msg = sys_state.msg;
if (!msg.empty()) {
const char* begin = &msg.front();
const char* end = &msg.back() + 1;
DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT});
}
int count = 1;
if (sys_state.hide_ok) {
--count;
}
if (sys_state.show_no || sys_state.show_cancel) {
++count;
}
SetCursorPos({
ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast<float>(count),
ws.y - FOOTER_HEIGHT + 5.0f,
});
BeginGroup();
if (Button(sys_state.show_no ? "Yes" : "OK", BUTTON_SIZE)) {
Finish(ButtonId::YES);
}
SameLine();
if (sys_state.show_no) {
if (Button("No", BUTTON_SIZE)) {
Finish(ButtonId::NO);
}
} else if (sys_state.show_cancel) {
if (Button("Cancel", BUTTON_SIZE)) {
Finish(ButtonId::INVALID, Result::USER_CANCELED);
}
}
if (first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
SetItemCurrentNavFocus();
}
EndGroup();
}
void SaveDialogUi::DrawErrorCode() {
const auto& err_state = state->GetState<SaveDialogState::ErrorCodeState>();
if (!state->save_list.empty()) {
DrawItem(0, state->save_list.front(), false);
}
const auto ws = GetWindowSize();
const auto& msg = err_state.error_msg;
if (!msg.empty()) {
const char* begin = &msg.front();
const char* end = &msg.back() + 1;
DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT});
}
SetCursorPos({
ws.x / 2.0f - BUTTON_SIZE.x / 2.0f,
ws.y - FOOTER_HEIGHT + 5.0f,
});
if (Button("OK", BUTTON_SIZE)) {
Finish(ButtonId::OK);
}
if (first_render) {
SetItemCurrentNavFocus();
}
}
void SaveDialogUi::DrawProgressBar() {
const auto& bar_state = state->GetState<SaveDialogState::ProgressBarState>();
const auto ws = GetWindowSize();
if (!state->save_list.empty()) {
DrawItem(0, state->save_list.front(), false);
}
const auto& msg = bar_state.msg;
if (!msg.empty()) {
const char* begin = &msg.front();
const char* end = &msg.back() + 1;
DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT});
}
SetCursorPos({
ws.x * ((1 - PROGRESS_BAR_WIDTH) / 2.0f),
ws.y - FOOTER_HEIGHT + 5.0f,
});
ProgressBar(static_cast<float>(bar_state.progress) / 100.0f,
{PROGRESS_BAR_WIDTH * ws.x, BUTTON_SIZE.y});
}
}; // namespace Libraries::SaveData::Dialog

View file

@ -0,0 +1,317 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include <variant>
#include <vector>
#include "core/file_format/psf.h"
#include "core/libraries/save_data/savedata.h"
#include "core/libraries/system/commondialog.h"
#include "imgui/imgui_layer.h"
#include "imgui/imgui_texture.h"
namespace Libraries::SaveData::Dialog {
using OrbisUserServiceUserId = s32;
enum class SaveDataDialogMode : u32 {
INVALID = 0,
LIST = 1,
USER_MSG = 2,
SYSTEM_MSG = 3,
ERROR_CODE = 4,
PROGRESS_BAR = 5,
};
enum class DialogType : u32 {
SAVE = 1,
LOAD = 2,
DELETE = 3,
};
enum class DialogAnimation : u32 {
ON = 0,
OFF = 1,
};
enum class ButtonId : u32 {
INVALID = 0,
OK = 1,
YES = 1,
NO = 2,
};
enum class ButtonType : u32 {
OK = 0,
YESNO = 1,
NONE = 2,
ONCANCEL = 3,
};
enum class UserMessageType : u32 {
NORMAL = 0,
ERROR = 1,
};
enum class FocusPos : u32 {
LISTHEAD = 0,
LISTTAIL = 1,
DATAHEAD = 2,
DATATAIL = 3,
DATALTATEST = 4,
DATAOLDEST = 5,
DIRNAME = 6,
};
enum class ItemStyle : u32 {
TITLE_DATASIZE_SUBTITLE = 0,
TITLE_SUBTITLE_DATESIZE = 1,
TITLE_DATESIZE = 2,
};
enum class SystemMessageType : u32 {
NODATA = 1,
CONFIRM = 2,
OVERWRITE = 3,
NOSPACE = 4,
PROGRESS = 5,
FILE_CORRUPTED = 6,
FINISHED = 7,
NOSPACE_CONTINUABLE = 8,
CORRUPTED_AND_DELETED = 10,
CORRUPTED_AND_CREATED = 11,
CORRUPTED_AND_RESTORE = 13,
TOTAL_SIZE_EXCEEDED = 14,
};
enum class ProgressBarType : u32 {
PERCENTAGE = 0,
};
enum class ProgressSystemMessageType : u32 {
INVALID = 0,
PROGRESS = 1,
RESTORE = 2,
};
enum class OptionBack : u32 {
ENABLE = 0,
DISABLE = 1,
};
enum class OrbisSaveDataDialogProgressBarTarget : u32 {
DEFAULT = 0,
};
struct AnimationParam {
DialogAnimation userOK;
DialogAnimation userCancel;
std::array<u8, 32> _reserved;
};
struct SaveDialogNewItem {
const char* title;
void* iconBuf;
size_t iconSize;
std::array<u8, 32> _reserved;
};
struct SaveDialogItems {
OrbisUserServiceUserId userId;
s32 : 32;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
u32 dirNameNum;
s32 : 32;
const SaveDialogNewItem* newItem;
FocusPos focusPos;
s32 : 32;
const OrbisSaveDataDirName* focusPosDirName;
ItemStyle itemStyle;
std::array<u8, 32> _reserved;
};
struct UserMessageParam {
ButtonType buttonType;
UserMessageType msgType;
const char* msg;
std::array<u8, 32> _reserved;
};
struct SystemMessageParam {
SystemMessageType msgType;
s32 : 32;
u64 value;
std::array<u8, 32> _reserved;
};
struct ErrorCodeParam {
u32 errorCode;
std::array<u8, 32> _reserved;
};
struct ProgressBarParam {
ProgressBarType barType;
s32 : 32;
const char* msg;
ProgressSystemMessageType sysMsgType;
std::array<u8, 28> _reserved;
};
struct OptionParam {
OptionBack back;
std::array<u8, 32> _reserved;
};
struct OrbisSaveDataDialogParam {
CommonDialog::BaseParam baseParam;
s32 size;
SaveDataDialogMode mode;
DialogType dispType;
s32 : 32;
AnimationParam* animParam;
SaveDialogItems* items;
UserMessageParam* userMsgParam;
SystemMessageParam* sysMsgParam;
ErrorCodeParam* errorCodeParam;
ProgressBarParam* progressBarParam;
void* userData;
OptionParam* optionParam;
std::array<u8, 24> _reserved;
};
struct OrbisSaveDataDialogResult {
SaveDataDialogMode mode{};
CommonDialog::Result result{};
ButtonId buttonId{};
s32 : 32;
OrbisSaveDataDirName* dirName;
OrbisSaveDataParam* param;
void* userData;
std::array<u8, 32> _reserved;
};
struct SaveDialogResult {
SaveDataDialogMode mode{};
CommonDialog::Result result{CommonDialog::Result::OK};
ButtonId button_id{ButtonId::INVALID};
std::string dir_name{};
PSF param{};
void* user_data{};
void CopyTo(OrbisSaveDataDialogResult& result) const;
};
class SaveDialogState {
friend class SaveDialogUi;
public:
struct UserState {
ButtonType type{};
UserMessageType msg_type{};
std::string msg{};
UserState(const OrbisSaveDataDialogParam& param);
};
struct SystemState {
std::string msg{};
bool hide_ok{};
bool show_no{}; // Yes instead of OK
bool show_cancel{};
SystemState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param);
};
struct ErrorCodeState {
std::string error_msg{};
ErrorCodeState(const OrbisSaveDataDialogParam& param);
};
struct ProgressBarState {
std::string msg{};
u32 progress{};
ProgressBarState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param);
};
struct Item {
std::string dir_name{};
ImGui::RefCountedTexture icon{};
std::string title{};
std::string subtitle{};
std::string details{};
std::string date{};
std::string size{};
std::filesystem::file_time_type last_write{};
PSF pfo{};
bool is_corrupted{};
};
private:
SaveDataDialogMode mode{};
DialogType type{};
void* user_data{};
std::optional<bool> enable_back{};
OrbisUserServiceUserId user_id{};
std::string title_id{};
std::vector<Item> save_list{};
std::variant<FocusPos, std::string, std::monostate> focus_pos{std::monostate{}};
ItemStyle style{};
std::optional<Item> new_item{};
std::variant<UserState, SystemState, ErrorCodeState, ProgressBarState, std::monostate> state{
std::monostate{}};
public:
explicit SaveDialogState(const OrbisSaveDataDialogParam& param);
SaveDialogState() = default;
[[nodiscard]] SaveDataDialogMode GetMode() const {
return mode;
}
template <typename T>
[[nodiscard]] T& GetState() {
return std::get<T>(state);
}
};
class SaveDialogUi final : public ImGui::Layer {
bool first_render{false};
SaveDialogState* state{};
CommonDialog::Status* status{};
SaveDialogResult* result{};
std::recursive_mutex draw_mutex{};
public:
explicit SaveDialogUi(SaveDialogState* state = nullptr, CommonDialog::Status* status = nullptr,
SaveDialogResult* result = nullptr);
~SaveDialogUi() override;
SaveDialogUi(const SaveDialogUi& other) = delete;
SaveDialogUi(SaveDialogUi&& other) noexcept;
SaveDialogUi& operator=(SaveDialogUi other);
void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK);
void Draw() override;
private:
void DrawItem(int id, const SaveDialogState::Item& item, bool clickable = true);
void DrawList();
void DrawUser();
void DrawSystemMessage();
void DrawErrorCode();
void DrawProgressBar();
};
}; // namespace Libraries::SaveData::Dialog

View file

@ -1,28 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
constexpr int ORBIS_SAVE_DATA_ERROR_PARAMETER = 0x809F0000;
constexpr int ORBIS_SAVE_DATA_ERROR_NOT_INITIALIZED = 0x809F0001;
constexpr int ORBIS_SAVE_DATA_ERROR_OUT_OF_MEMORY = 0x809F0002;
constexpr int ORBIS_SAVE_DATA_ERROR_BUSY = 0x809F0003;
constexpr int ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED = 0x809F0004;
constexpr int ORBIS_SAVE_DATA_ERROR_NO_PERMISSION = 0x809F0005;
constexpr int ORBIS_SAVE_DATA_ERROR_FINGERPRINT_MISMATCH = 0x809F0006;
constexpr int ORBIS_SAVE_DATA_ERROR_EXISTS = 0x809F0007;
constexpr int ORBIS_SAVE_DATA_ERROR_NOT_FOUND = 0x809F0008;
constexpr int ORBIS_SAVE_DATA_ERROR_NO_SPACE_FS = 0x809F000A;
constexpr int ORBIS_SAVE_DATA_ERROR_INTERNAL = 0x809F000B;
constexpr int ORBIS_SAVE_DATA_ERROR_MOUNT_FULL = 0x809F000C;
constexpr int ORBIS_SAVE_DATA_ERROR_BAD_MOUNTED = 0x809F000D;
constexpr int ORBIS_SAVE_DATA_ERROR_FILE_NOT_FOUND = 0x809F000E;
constexpr int ORBIS_SAVE_DATA_ERROR_BROKEN = 0x809F000F;
constexpr int ORBIS_SAVE_DATA_ERROR_INVALID_LOGIN_USER = 0x809F0011;
constexpr int ORBIS_SAVE_DATA_ERROR_MEMORY_NOT_READY = 0x809F0012;
constexpr int ORBIS_SAVE_DATA_ERROR_BACKUP_BUSY = 0x809F0013;
constexpr int ORBIS_SAVE_DATA_ERROR_NOT_REGIST_CALLBACK = 0x809F0015;
constexpr int ORBIS_SAVE_DATA_ERROR_BUSY_FOR_SAVING = 0x809F0016;
constexpr int ORBIS_SAVE_DATA_ERROR_LIMITATION_OVER = 0x809F0017;
constexpr int ORBIS_SAVE_DATA_ERROR_EVENT_BUSY = 0x809F0018;
constexpr int ORBIS_SAVE_DATA_ERROR_PARAMSFO_TRANSFER_TITLE_ID_NOT_FOUND = 0x809F0019;

View file

@ -0,0 +1,207 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <deque>
#include <mutex>
#include <semaphore>
#include <magic_enum.hpp>
#include "save_backup.h"
#include "save_instance.h"
#include "common/logging/log.h"
#include "common/logging/log_entry.h"
#include "common/polyfill_thread.h"
#include "common/thread.h"
constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save
constexpr std::string_view backup_dir = "sce_backup"; // backup folder
constexpr std::string_view backup_dir_tmp = "sce_backup_tmp"; // in-progress backup folder
namespace fs = std::filesystem;
namespace Libraries::SaveData::Backup {
static std::jthread g_backup_thread;
static std::counting_semaphore g_backup_thread_semaphore{0};
static std::mutex g_backup_queue_mutex;
static std::deque<BackupRequest> g_backup_queue;
static std::deque<BackupRequest> g_result_queue;
static std::atomic_int g_backup_progress = 0;
static std::atomic g_backup_status = WorkerStatus::NotStarted;
static void backup(const std::filesystem::path& dir_name) {
if (!fs::exists(dir_name)) {
return;
}
std::vector<std::filesystem::path> backup_files;
for (const auto& entry : fs::directory_iterator(dir_name)) {
const auto filename = entry.path().filename();
if (filename != backup_dir && filename != backup_dir_tmp) {
backup_files.push_back(entry.path());
}
}
const auto backup_dir = dir_name / ::backup_dir;
const auto backup_dir_tmp = dir_name / ::backup_dir_tmp;
g_backup_progress = 0;
int total_count = static_cast<int>(backup_files.size());
int current_count = 0;
fs::remove_all(backup_dir_tmp);
fs::create_directory(backup_dir_tmp);
for (const auto& file : backup_files) {
fs::copy(file, backup_dir_tmp / file.filename(), fs::copy_options::recursive);
current_count++;
g_backup_progress = current_count * 100 / total_count;
}
bool has_existing = fs::exists(backup_dir);
if (has_existing) {
fs::rename(backup_dir, dir_name / "sce_backup_old");
}
fs::rename(backup_dir_tmp, backup_dir);
if (has_existing) {
fs::remove_all(dir_name / "sce_backup_old");
}
}
static void BackupThreadBody() {
Common::SetCurrentThreadName("SaveData_BackupThread");
while (true) {
g_backup_status = WorkerStatus::Waiting;
g_backup_thread_semaphore.acquire();
BackupRequest req;
{
std::scoped_lock lk{g_backup_queue_mutex};
req = g_backup_queue.front();
}
if (req.save_path.empty()) {
break;
}
g_backup_status = WorkerStatus::Running;
LOG_INFO(Lib_SaveData, "Backing up the following directory: {}", req.save_path.string());
backup(req.save_path);
LOG_DEBUG(Lib_SaveData, "Backing up the following directory: {} finished",
req.save_path.string());
{
std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.pop_front();
g_result_queue.push_back(std::move(req));
if (g_result_queue.size() > 20) {
g_result_queue.pop_front();
}
}
}
g_backup_status = WorkerStatus::NotStarted;
}
void StartThread() {
if (g_backup_status != WorkerStatus::NotStarted) {
return;
}
LOG_DEBUG(Lib_SaveData, "Starting backup thread");
g_backup_thread = std::jthread{BackupThreadBody};
g_backup_status = WorkerStatus::Waiting;
}
void StopThread() {
if (g_backup_status == WorkerStatus::NotStarted || g_backup_status == WorkerStatus::Stopping) {
return;
}
LOG_DEBUG(Lib_SaveData, "Stopping backup thread");
{
std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.emplace_back(BackupRequest{});
}
g_backup_thread_semaphore.release();
g_backup_status = WorkerStatus::Stopping;
}
bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,
std::string_view dir_name, OrbisSaveDataEventType origin) {
auto save_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name);
if (g_backup_status != WorkerStatus::Waiting && g_backup_status != WorkerStatus::Running) {
LOG_ERROR(Lib_SaveData, "Called backup while status is {}. Backup request to {} ignored",
magic_enum::enum_name(g_backup_status.load()), save_path.string());
return false;
}
{
std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.push_back(BackupRequest{
.user_id = user_id,
.title_id = std::string{title_id},
.dir_name = std::string{dir_name},
.origin = origin,
.save_path = std::move(save_path),
});
}
g_backup_thread_semaphore.release();
return true;
}
bool Restore(const std::filesystem::path& save_path) {
LOG_INFO(Lib_SaveData, "Restoring backup for {}", save_path.string());
if (!fs::exists(save_path) || !fs::exists(save_path / backup_dir)) {
return false;
}
for (const auto& entry : fs::directory_iterator(save_path)) {
const auto filename = entry.path().filename();
if (filename != backup_dir) {
fs::remove_all(entry.path());
}
}
for (const auto& entry : fs::directory_iterator(save_path / backup_dir)) {
const auto filename = entry.path().filename();
fs::copy(entry.path(), save_path / filename, fs::copy_options::recursive);
}
return true;
}
WorkerStatus GetWorkerStatus() {
return g_backup_status;
}
bool IsBackupExecutingFor(const std::filesystem::path& save_path) {
std::scoped_lock lk{g_backup_queue_mutex};
return std::ranges::find(g_backup_queue, save_path,
[](const auto& v) { return v.save_path; }) != g_backup_queue.end();
}
std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path) {
return save_path / backup_dir;
}
std::optional<BackupRequest> PopLastEvent() {
std::scoped_lock lk{g_backup_queue_mutex};
if (g_result_queue.empty()) {
return std::nullopt;
}
auto req = std::move(g_result_queue.front());
g_result_queue.pop_front();
return req;
}
void PushBackupEvent(BackupRequest&& req) {
std::scoped_lock lk{g_backup_queue_mutex};
g_result_queue.push_back(std::move(req));
if (g_result_queue.size() > 20) {
g_result_queue.pop_front();
}
}
float GetProgress() {
return static_cast<float>(g_backup_progress) / 100.0f;
}
void ClearProgress() {
g_backup_progress = 0;
}
} // namespace Libraries::SaveData::Backup

View file

@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include "common/types.h"
namespace Libraries::SaveData {
using OrbisUserServiceUserId = s32;
namespace Backup {
enum class WorkerStatus {
NotStarted,
Waiting,
Running,
Stopping,
};
enum class OrbisSaveDataEventType : u32 {
UMOUNT_BACKUP = 1,
BACKUP = 2,
SAVE_DATA_MEMORY_SYNC = 3,
};
struct BackupRequest {
OrbisUserServiceUserId user_id{};
std::string title_id{};
std::string dir_name{};
OrbisSaveDataEventType origin{};
std::filesystem::path save_path{};
};
// No problem calling this function if the backup thread is already running
void StartThread();
void StopThread();
bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,
std::string_view dir_name, OrbisSaveDataEventType origin);
bool Restore(const std::filesystem::path& save_path);
WorkerStatus GetWorkerStatus();
bool IsBackupExecutingFor(const std::filesystem::path& save_path);
std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path);
std::optional<BackupRequest> PopLastEvent();
void PushBackupEvent(BackupRequest&& req);
float GetProgress();
void ClearProgress();
} // namespace Backup
} // namespace Libraries::SaveData

View file

@ -0,0 +1,228 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <iostream>
#include <magic_enum.hpp>
#include "common/assert.h"
#include "common/config.h"
#include "common/path_util.h"
#include "common/singleton.h"
#include "core/file_sys/fs.h"
#include "save_instance.h"
constexpr u32 OrbisSaveDataBlocksMax = 32768; // 1 GiB
constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save
constexpr std::string_view max_block_file_name = "max_block.txt";
static Core::FileSys::MntPoints* g_mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
namespace fs = std::filesystem;
// clang-format off
static const std::unordered_map<std::string, std::string> default_title = {
{"ja_JP", "セーブデータ"},
{"en", "Saved Data"},
{"fr", "Données sauvegardées"},
{"es_ES", "Datos guardados"},
{"de", "Gespeicherte Daten"},
{"it", "Dati salvati"},
{"nl", "Opgeslagen data"},
{"pt_PT", "Dados guardados"},
{"ru", "Сохраненные данные"},
{"ko_KR", "저장 데이터"},
{"zh_CN", "保存数据"},
{"fi", "Tallennetut tiedot"},
{"sv_SE", "Sparade data"},
{"da_DK", "Gemte data"},
{"no_NO", "Lagrede data"},
{"pl_PL", "Zapisane dane"},
{"pt_BR", "Dados salvos"},
{"tr_TR", "Kayıtlı Veriler"},
};
// clang-format on
namespace Libraries::SaveData {
std::filesystem::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id,
std::string_view game_serial) {
return Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(user_id) /
game_serial;
}
std::filesystem::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id,
std::string_view game_serial,
std::string_view dir_name) {
return Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(user_id) /
game_serial / dir_name;
}
int SaveInstance::GetMaxBlocks(const std::filesystem::path& save_path) {
Common::FS::IOFile max_blocks_file{save_path / sce_sys / max_block_file_name,
Common::FS::FileAccessMode::Read};
int max_blocks = 0;
if (max_blocks_file.IsOpen()) {
max_blocks = std::atoi(max_blocks_file.ReadString(16).c_str());
}
if (max_blocks <= 0) {
max_blocks = OrbisSaveDataBlocksMax;
}
return max_blocks;
}
std::filesystem::path SaveInstance::GetParamSFOPath(const std::filesystem::path& dir_path) {
return dir_path / sce_sys / "param.sfo";
}
void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name,
std::string game_serial) {
std::string locale = Config::getEmulatorLanguage();
if (!default_title.contains(locale)) {
locale = "en";
}
#define P(type, key, ...) param_sfo.Add##type(std::string{key}, __VA_ARGS__)
// TODO Link with user service
P(Binary, SaveParams::ACCOUNT_ID, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
P(String, SaveParams::MAINTITLE, default_title.at(locale));
P(String, SaveParams::SUBTITLE, "");
P(String, SaveParams::DETAIL, "");
P(String, SaveParams::SAVEDATA_DIRECTORY, std::move(dir_name));
P(Integer, SaveParams::SAVEDATA_LIST_PARAM, 0);
P(String, SaveParams::TITLE_ID, std::move(game_serial));
#undef P
}
SaveInstance::SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string _game_serial,
std::string_view _dir_name, int max_blocks)
: slot_num(slot_num), user_id(user_id), game_serial(std::move(_game_serial)),
dir_name(_dir_name), max_blocks(max_blocks) {
ASSERT(slot_num >= 0 && slot_num < 16);
save_path = MakeDirSavePath(user_id, game_serial, dir_name);
const auto sce_sys_path = save_path / sce_sys;
param_sfo_path = sce_sys_path / "param.sfo";
corrupt_file_path = sce_sys_path / "corrupted";
mount_point = "/savedata" + std::to_string(slot_num);
this->exists = fs::exists(param_sfo_path);
this->mounted = g_mnt->GetMount(mount_point) != nullptr;
}
SaveInstance::~SaveInstance() {
if (mounted) {
Umount();
}
}
SaveInstance::SaveInstance(SaveInstance&& other) noexcept {
if (this == &other)
return;
*this = std::move(other);
}
SaveInstance& SaveInstance::operator=(SaveInstance&& other) noexcept {
if (this == &other)
return *this;
slot_num = other.slot_num;
user_id = other.user_id;
game_serial = std::move(other.game_serial);
dir_name = std::move(other.dir_name);
save_path = std::move(other.save_path);
param_sfo_path = std::move(other.param_sfo_path);
corrupt_file_path = std::move(other.corrupt_file_path);
corrupt_file = std::move(other.corrupt_file);
param_sfo = std::move(other.param_sfo);
mount_point = std::move(other.mount_point);
max_blocks = other.max_blocks;
exists = other.exists;
mounted = other.mounted;
read_only = other.read_only;
other.mounted = false;
return *this;
}
void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_corrupt) {
if (mounted) {
UNREACHABLE_MSG("Save instance is already mounted");
}
this->exists = fs::exists(param_sfo_path); // check again just in case
if (!exists) {
CreateFiles();
if (copy_icon) {
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
if (fs::exists(src_icon)) {
fs::copy_file(src_icon, GetIconPath());
}
}
exists = true;
} else {
if (!ignore_corrupt && fs::exists(corrupt_file_path)) {
throw std::filesystem::filesystem_error(
"Corrupted save data", corrupt_file_path,
std::make_error_code(std::errc::illegal_byte_sequence));
}
if (!param_sfo.Open(param_sfo_path)) {
throw std::filesystem::filesystem_error(
"Failed to read param.sfo", param_sfo_path,
std::make_error_code(std::errc::illegal_byte_sequence));
}
}
if (!ignore_corrupt && !read_only) {
int err = corrupt_file.Open(corrupt_file_path, Common::FS::FileAccessMode::Write);
if (err != 0) {
throw std::filesystem::filesystem_error(
"Failed to open corrupted file", corrupt_file_path,
std::make_error_code(std::errc::illegal_byte_sequence));
}
}
max_blocks = GetMaxBlocks(save_path);
g_mnt->Mount(save_path, mount_point, read_only);
mounted = true;
this->read_only = read_only;
}
void SaveInstance::Umount() {
if (!mounted) {
UNREACHABLE_MSG("Save instance is not mounted");
return;
}
mounted = false;
const bool ok = param_sfo.Encode(param_sfo_path);
if (!ok) {
throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path,
std::make_error_code(std::errc::permission_denied));
}
param_sfo = PSF();
corrupt_file.Close();
fs::remove(corrupt_file_path);
g_mnt->Unmount(save_path, mount_point);
}
void SaveInstance::CreateFiles() {
const auto sce_sys_dir = save_path / sce_sys;
fs::create_directories(sce_sys_dir);
SetupDefaultParamSFO(param_sfo, dir_name, game_serial);
const bool ok = param_sfo.Encode(param_sfo_path);
if (!ok) {
throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path,
std::make_error_code(std::errc::permission_denied));
}
Common::FS::IOFile max_block{sce_sys_dir / max_block_file_name,
Common::FS::FileAccessMode::Write};
max_block.WriteString(std::to_string(max_blocks == 0 ? OrbisSaveDataBlocksMax : max_blocks));
}
} // namespace Libraries::SaveData

View file

@ -0,0 +1,138 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include "common/io_file.h"
#include "core/file_format/psf.h"
namespace Core::FileSys {
class MntPoints;
}
namespace Libraries::SaveData {
// Used constexpr to easily use as string
namespace SaveParams {
constexpr std::string_view ACCOUNT_ID = "ACCOUNT_ID";
constexpr std::string_view ATTRIBUTE = "ATTRIBUTE";
constexpr std::string_view CATEGORY = "CATEGORY";
constexpr std::string_view DETAIL = "DETAIL";
constexpr std::string_view FORMAT = "FORMAT";
constexpr std::string_view MAINTITLE = "MAINTITLE";
constexpr std::string_view PARAMS = "PARAMS";
constexpr std::string_view SAVEDATA_BLOCKS = "SAVEDATA_BLOCKS";
constexpr std::string_view SAVEDATA_DIRECTORY = "SAVEDATA_DIRECTORY";
constexpr std::string_view SAVEDATA_LIST_PARAM = "SAVEDATA_LIST_PARAM";
constexpr std::string_view SUBTITLE = "SUBTITLE";
constexpr std::string_view TITLE_ID = "TITLE_ID";
} // namespace SaveParams
using OrbisUserServiceUserId = s32;
class SaveInstance {
int slot_num{};
int user_id{};
std::string game_serial;
std::string dir_name;
std::filesystem::path save_path;
std::filesystem::path param_sfo_path;
std::filesystem::path corrupt_file_path;
Common::FS::IOFile corrupt_file;
PSF param_sfo;
std::string mount_point;
int max_blocks{};
bool exists{};
bool mounted{};
bool read_only{};
public:
// Location of all save data for a title
static std::filesystem::path MakeTitleSavePath(OrbisUserServiceUserId user_id,
std::string_view game_serial);
// Location of a specific save data directory
static std::filesystem::path MakeDirSavePath(OrbisUserServiceUserId user_id,
std::string_view game_serial,
std::string_view dir_name);
static int GetMaxBlocks(const std::filesystem::path& save_path);
// Get param.sfo path from a dir_path generated by MakeDirSavePath
static std::filesystem::path GetParamSFOPath(const std::filesystem::path& dir_path);
static void SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, std::string game_serial);
explicit SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string game_serial,
std::string_view dir_name, int max_blocks = 0);
~SaveInstance();
SaveInstance(const SaveInstance& other) = delete;
SaveInstance(SaveInstance&& other) noexcept;
SaveInstance& operator=(const SaveInstance& other) = delete;
SaveInstance& operator=(SaveInstance&& other) noexcept;
void SetupAndMount(bool read_only = false, bool copy_icon = false, bool ignore_corrupt = false);
void Umount();
[[nodiscard]] std::filesystem::path GetIconPath() const noexcept {
return save_path / "sce_sys" / "icon0.png";
}
[[nodiscard]] bool Exists() const noexcept {
return exists;
}
[[nodiscard]] OrbisUserServiceUserId GetUserId() const noexcept {
return user_id;
}
[[nodiscard]] std::string_view GetTitleId() const noexcept {
return game_serial;
}
[[nodiscard]] const std::string& GetDirName() const noexcept {
return dir_name;
}
[[nodiscard]] const std::filesystem::path& GetSavePath() const noexcept {
return save_path;
}
[[nodiscard]] const PSF& GetParamSFO() const noexcept {
return param_sfo;
}
[[nodiscard]] PSF& GetParamSFO() noexcept {
return param_sfo;
}
[[nodiscard]] const std::string& GetMountPoint() const noexcept {
return mount_point;
}
[[nodiscard]] int GetMaxBlocks() const noexcept {
return max_blocks;
}
[[nodiscard]] bool Mounted() const noexcept {
return mounted;
}
[[nodiscard]] bool IsReadOnly() const noexcept {
return read_only;
}
void CreateFiles();
};
} // namespace Libraries::SaveData

View file

@ -0,0 +1,287 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "save_memory.h"
#include <condition_variable>
#include <filesystem>
#include <mutex>
#include <utility>
#include <fmt/format.h>
#include <core/libraries/system/msgdialog_ui.h>
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/polyfill_thread.h"
#include "common/singleton.h"
#include "common/thread.h"
#include "core/file_sys/fs.h"
#include "save_instance.h"
using Common::FS::IOFile;
namespace fs = std::filesystem;
constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save
constexpr std::string_view DirnameSaveDataMemory = "sce_sdmemory";
constexpr std::string_view FilenameSaveDataMemory = "memory.dat";
namespace Libraries::SaveData::SaveMemory {
static Core::FileSys::MntPoints* g_mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
static OrbisUserServiceUserId g_user_id{};
static std::string g_game_serial{};
static std::filesystem::path g_save_path{};
static std::filesystem::path g_param_sfo_path{};
static PSF g_param_sfo;
static bool g_save_memory_initialized = false;
static std::mutex g_saving_memory_mutex;
static std::vector<u8> g_save_memory;
static std::filesystem::path g_icon_path;
static std::vector<u8> g_icon_memory;
static std::condition_variable g_trigger_save_memory;
static std::atomic_bool g_saving_memory = false;
static std::atomic_bool g_save_event = false;
static std::jthread g_save_memory_thread;
static std::atomic_bool g_memory_dirty = false;
static std::atomic_bool g_param_dirty = false;
static std::atomic_bool g_icon_dirty = false;
static void SaveFileSafe(void* buf, size_t count, const std::filesystem::path& path) {
const auto& dir = path.parent_path();
const auto& name = path.filename();
const auto tmp_path = dir / (name.string() + ".tmp");
IOFile file(tmp_path, Common::FS::FileAccessMode::Write);
file.WriteRaw<u8>(buf, count);
file.Close();
fs::remove(path);
fs::rename(tmp_path, path);
}
[[noreturn]] void SaveThreadLoop() {
Common::SetCurrentThreadName("SaveData_SaveDataMemoryThread");
std::mutex mtx;
while (true) {
{
std::unique_lock lk{mtx};
g_trigger_save_memory.wait(lk);
}
// Save the memory
g_saving_memory = true;
std::scoped_lock lk{g_saving_memory_mutex};
try {
LOG_DEBUG(Lib_SaveData, "Saving save data memory {}", g_save_path.string());
if (g_memory_dirty) {
g_memory_dirty = false;
SaveFileSafe(g_save_memory.data(), g_save_memory.size(),
g_save_path / FilenameSaveDataMemory);
}
if (g_param_dirty) {
g_param_dirty = false;
static std::vector<u8> buf;
g_param_sfo.Encode(buf);
SaveFileSafe(buf.data(), buf.size(), g_param_sfo_path);
}
if (g_icon_dirty) {
g_icon_dirty = false;
SaveFileSafe(g_icon_memory.data(), g_icon_memory.size(), g_icon_path);
}
if (g_save_event) {
Backup::PushBackupEvent(Backup::BackupRequest{
.user_id = g_user_id,
.title_id = g_game_serial,
.dir_name = std::string{DirnameSaveDataMemory},
.origin = Backup::OrbisSaveDataEventType::SAVE_DATA_MEMORY_SYNC,
.save_path = g_save_path,
});
g_save_event = false;
}
} catch (const fs::filesystem_error& e) {
LOG_ERROR(Lib_SaveData, "Failed to save save data memory: {}", e.what());
MsgDialog::ShowMsgDialog(MsgDialog::MsgDialogState{
MsgDialog::MsgDialogState::UserState{
.type = MsgDialog::ButtonType::OK,
.msg = fmt::format("Failed to save save data memory.\nCode: <{}>\n{}",
e.code().message(), e.what()),
},
});
}
g_saving_memory = false;
}
}
void SetDirectories(OrbisUserServiceUserId user_id, std::string _game_serial) {
g_user_id = user_id;
g_game_serial = std::move(_game_serial);
g_save_path = SaveInstance::MakeDirSavePath(user_id, g_game_serial, DirnameSaveDataMemory);
g_param_sfo_path = SaveInstance::GetParamSFOPath(g_save_path);
g_param_sfo = PSF();
g_icon_path = g_save_path / sce_sys / "icon0.png";
}
const std::filesystem::path& GetSavePath() {
return g_save_path;
}
size_t CreateSaveMemory(size_t memory_size) {
size_t existed_size = 0;
static std::once_flag init_save_thread_flag;
std::call_once(init_save_thread_flag,
[] { g_save_memory_thread = std::jthread{SaveThreadLoop}; });
g_save_memory.resize(memory_size);
SaveInstance::SetupDefaultParamSFO(g_param_sfo, std::string{DirnameSaveDataMemory},
g_game_serial);
g_save_memory_initialized = true;
if (!fs::exists(g_param_sfo_path)) {
// Create save memory
fs::create_directories(g_save_path / sce_sys);
IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Write};
bool ok = memory_file.SetSize(memory_size);
if (!ok) {
LOG_ERROR(Lib_SaveData, "Failed to set memory size");
throw std::filesystem::filesystem_error(
"Failed to set save memory size", g_save_path / FilenameSaveDataMemory,
std::make_error_code(std::errc::no_space_on_device));
}
memory_file.Close();
} else {
// Load save memory
bool ok = g_param_sfo.Open(g_param_sfo_path);
if (!ok) {
LOG_ERROR(Lib_SaveData, "Failed to open SFO at {}", g_param_sfo_path.string());
throw std::filesystem::filesystem_error(
"failed to open SFO", g_param_sfo_path,
std::make_error_code(std::errc::illegal_byte_sequence));
}
IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Read};
if (!memory_file.IsOpen()) {
LOG_ERROR(Lib_SaveData, "Failed to open save memory");
throw std::filesystem::filesystem_error(
"failed to open save memory", g_save_path / FilenameSaveDataMemory,
std::make_error_code(std::errc::permission_denied));
}
size_t save_size = memory_file.GetSize();
existed_size = save_size;
memory_file.Seek(0);
memory_file.ReadRaw<u8>(g_save_memory.data(), std::min(save_size, memory_size));
memory_file.Close();
}
return existed_size;
}
void SetIcon(void* buf, size_t buf_size) {
if (buf == nullptr) {
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
if (fs::exists(src_icon)) {
fs::copy_file(src_icon, g_icon_path);
}
IOFile file(g_icon_path, Common::FS::FileAccessMode::Read);
size_t size = file.GetSize();
file.Seek(0);
g_icon_memory.resize(size);
file.ReadRaw<u8>(g_icon_memory.data(), size);
file.Close();
} else {
g_icon_memory.resize(buf_size);
std::memcpy(g_icon_memory.data(), buf, buf_size);
IOFile file(g_icon_path, Common::FS::FileAccessMode::Append);
file.Seek(0);
file.WriteRaw<u8>(g_icon_memory.data(), buf_size);
file.Close();
}
}
void WriteIcon(void* buf, size_t buf_size) {
if (buf_size != g_icon_memory.size()) {
g_icon_memory.resize(buf_size);
}
std::memcpy(g_icon_memory.data(), buf, buf_size);
g_icon_dirty = true;
}
bool IsSaveMemoryInitialized() {
return g_save_memory_initialized;
}
PSF& GetParamSFO() {
return g_param_sfo;
}
std::span<u8> GetIcon() {
return {g_icon_memory};
}
void SaveSFO(bool sync) {
if (!sync) {
g_param_dirty = true;
return;
}
const bool ok = g_param_sfo.Encode(g_param_sfo_path);
if (!ok) {
LOG_ERROR(Lib_SaveData, "Failed to encode param.sfo");
throw std::filesystem::filesystem_error("Failed to write param.sfo", g_param_sfo_path,
std::make_error_code(std::errc::permission_denied));
}
}
bool IsSaving() {
return g_saving_memory;
}
bool TriggerSaveWithoutEvent() {
if (g_saving_memory) {
return false;
}
g_trigger_save_memory.notify_one();
return true;
}
bool TriggerSave() {
if (g_saving_memory) {
return false;
}
g_save_event = true;
g_trigger_save_memory.notify_one();
return true;
}
void ReadMemory(void* buf, size_t buf_size, int64_t offset) {
std::scoped_lock lk{g_saving_memory_mutex};
if (offset > g_save_memory.size()) {
UNREACHABLE_MSG("ReadMemory out of bounds");
}
if (offset + buf_size > g_save_memory.size()) {
UNREACHABLE_MSG("ReadMemory out of bounds");
}
std::memcpy(buf, g_save_memory.data() + offset, buf_size);
}
void WriteMemory(void* buf, size_t buf_size, int64_t offset) {
std::scoped_lock lk{g_saving_memory_mutex};
if (offset > g_save_memory.size()) {
UNREACHABLE_MSG("WriteMemory out of bounds");
}
if (offset + buf_size > g_save_memory.size()) {
UNREACHABLE_MSG("WriteMemory out of bounds");
}
std::memcpy(g_save_memory.data() + offset, buf, buf_size);
g_memory_dirty = true;
}
} // namespace Libraries::SaveData::SaveMemory

View file

@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "save_backup.h"
class PSF;
namespace Libraries::SaveData {
using OrbisUserServiceUserId = s32;
}
namespace Libraries::SaveData::SaveMemory {
void SetDirectories(OrbisUserServiceUserId user_id, std::string game_serial);
[[nodiscard]] const std::filesystem::path& GetSavePath();
// returns the size of the existed save memory
size_t CreateSaveMemory(size_t memory_size);
// Initialize the icon. Set buf to null to read the standard icon.
void SetIcon(void* buf, size_t buf_size);
// Update the icon
void WriteIcon(void* buf, size_t buf_size);
[[nodiscard]] bool IsSaveMemoryInitialized();
[[nodiscard]] PSF& GetParamSFO();
[[nodiscard]] std::span<u8> GetIcon();
// Save now or wait for the background thread to save
void SaveSFO(bool sync = false);
[[nodiscard]] bool IsSaving();
bool TriggerSaveWithoutEvent();
bool TriggerSave();
void ReadMemory(void* buf, size_t buf_size, int64_t offset);
void WriteMemory(void* buf, size_t buf_size, int64_t offset);
} // namespace Libraries::SaveData::SaveMemory

File diff suppressed because it is too large Load diff

View file

@ -3,259 +3,81 @@
#pragma once
#include "common/cstring.h"
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
class PSF;
namespace Libraries::SaveData {
constexpr int ORBIS_SAVE_DATA_DIRNAME_DATA_MAXSIZE =
32; // Maximum size for a save data directory name
constexpr int ORBIS_SAVE_DATA_MOUNT_POINT_DATA_MAXSIZE = 16; // Maximum size for a mount point name
constexpr size_t OrbisSaveDataTitleMaxsize = 128; // Maximum title name size
constexpr size_t OrbisSaveDataSubtitleMaxsize = 128; // Maximum subtitle name size
constexpr size_t OrbisSaveDataDetailMaxsize = 1024; // Maximum detail name size
enum class Error : u32;
enum class OrbisSaveDataParamType : u32;
using OrbisUserServiceUserId = s32;
// Maximum size for a title ID (4 uppercase letters + 5 digits)
constexpr int OrbisSaveDataTitleIdDataSize = 10;
// Maximum save directory name size
constexpr int OrbisSaveDataDirnameDataMaxsize = 32;
struct OrbisSaveDataTitleId {
Common::CString<OrbisSaveDataTitleIdDataSize> data;
std::array<char, 6> _pad;
};
struct OrbisSaveDataDirName {
char data[ORBIS_SAVE_DATA_DIRNAME_DATA_MAXSIZE];
Common::CString<OrbisSaveDataDirnameDataMaxsize> data;
};
struct OrbisSaveDataMount2 {
s32 user_id;
s32 unk1;
const OrbisSaveDataDirName* dir_name;
u64 blocks;
u32 mount_mode;
u8 reserved[32];
s32 unk2;
};
struct OrbisSaveDataMountPoint {
char data[ORBIS_SAVE_DATA_MOUNT_POINT_DATA_MAXSIZE];
};
struct OrbisSaveDataMountResult {
OrbisSaveDataMountPoint mount_point;
u64 required_blocks;
u32 unused;
u32 mount_status;
u8 reserved[28];
s32 unk1;
};
constexpr int ORBIS_SAVE_DATA_TITLE_ID_DATA_SIZE = 10;
struct OrbisSaveDataTitleId {
char data[ORBIS_SAVE_DATA_TITLE_ID_DATA_SIZE];
char padding[6];
};
constexpr int ORBIS_SAVE_DATA_FINGERPRINT_DATA_SIZE = 65;
struct OrbisSaveDataFingerprint {
char data[ORBIS_SAVE_DATA_FINGERPRINT_DATA_SIZE];
char padding[15];
};
struct OrbisSaveDataMount {
s32 user_id;
s32 pad;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dir_name;
const OrbisSaveDataFingerprint* fingerprint;
u64 blocks;
u32 mount_mode;
u8 reserved[32];
};
typedef u32 OrbisSaveDataParamType;
constexpr int ORBIS_SAVE_DATA_TITLE_MAXSIZE = 128;
constexpr int ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE = 128;
constexpr int ORBIS_SAVE_DATA_DETAIL_MAXSIZE = 1024;
struct OrbisSaveDataParam {
char title[ORBIS_SAVE_DATA_TITLE_MAXSIZE];
char subTitle[ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE];
char detail[ORBIS_SAVE_DATA_DETAIL_MAXSIZE];
Common::CString<OrbisSaveDataTitleMaxsize> title;
Common::CString<OrbisSaveDataSubtitleMaxsize> subTitle;
Common::CString<OrbisSaveDataDetailMaxsize> detail;
u32 userParam;
int : 32;
time_t mtime;
u8 reserved[32];
std::array<u8, 32> _reserved;
void FromSFO(const PSF& sfo);
void ToSFO(PSF& sfo) const;
};
struct OrbisSaveDataIcon {
void* buf;
size_t bufSize;
size_t dataSize;
u8 reserved[32];
};
typedef u32 OrbisSaveDataSaveDataMemoryOption;
#define ORBIS_SAVE_DATA_MEMORY_OPTION_NONE (0x00000000)
#define ORBIS_SAVE_DATA_MEMORY_OPTION_SET_PARAM (0x00000001 << 0)
#define ORBIS_SAVE_DATA_MEMORY_OPTION_DOUBLE_BUFFER (0x00000001 << 1)
struct OrbisSaveDataMemorySetup2 {
OrbisSaveDataSaveDataMemoryOption option;
s32 userId;
size_t memorySize;
size_t iconMemorySize;
const OrbisSaveDataParam* initParam;
const OrbisSaveDataIcon* initIcon;
u32 slotId;
u8 reserved[20];
};
struct OrbisSaveDataMemorySetupResult {
size_t existedMemorySize;
u8 reserved[16];
};
typedef u32 OrbisSaveDataEventType;
#define SCE_SAVE_DATA_EVENT_TYPE_INVALID (0)
#define SCE_SAVE_DATA_EVENT_TYPE_UMOUNT_BACKUP_END (1)
#define SCE_SAVE_DATA_EVENT_TYPE_BACKUP_END (2)
#define SCE_SAVE_DATA_EVENT_TYPE_SAVE_DATA_MEMORY_SYNC_END (3)
struct OrbisSaveDataEvent {
OrbisSaveDataEventType type;
s32 errorCode;
s32 userId;
u8 padding[4];
OrbisSaveDataTitleId titleId;
OrbisSaveDataDirName dirName;
u8 reserved[40];
};
struct OrbisSaveDataMemoryData {
void* buf;
size_t bufSize;
off_t offset;
u8 reserved[40];
};
struct OrbisSaveDataMemoryGet2 {
s32 userId;
u8 padding[4];
OrbisSaveDataMemoryData* data;
OrbisSaveDataParam* param;
OrbisSaveDataIcon* icon;
u32 slotId;
u8 reserved[28];
};
struct OrbisSaveDataMemorySet2 {
s32 userId;
u8 padding[4];
const OrbisSaveDataMemoryData* data;
const OrbisSaveDataParam* param;
const OrbisSaveDataIcon* icon;
u32 dataNum;
u8 slotId;
u8 reserved[24];
};
struct OrbisSaveDataCheckBackupData {
s32 userId;
int : 32;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
OrbisSaveDataParam* param;
OrbisSaveDataIcon* icon;
u8 reserved[32];
};
struct OrbisSaveDataMountInfo {
u64 blocks;
u64 freeBlocks;
u8 reserved[32];
};
#define ORBIS_SAVE_DATA_BLOCK_SIZE (32768)
#define ORBIS_SAVE_DATA_BLOCKS_MIN2 (96)
#define ORBIS_SAVE_DATA_BLOCKS_MAX (32768)
// savedataMount2 mountModes (ORed values)
constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY = 1;
constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_RDWR = 2;
constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_CREATE = 4;
constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF = 8;
constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON = 16;
constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 = 32;
typedef struct _OrbisSaveDataEventParam OrbisSaveDataEventParam;
typedef u32 OrbisSaveDataSortKey;
#define ORBIS_SAVE_DATA_SORT_KEY_DIRNAME (0)
#define ORBIS_SAVE_DATA_SORT_KEY_USER_PARAM (1)
#define ORBIS_SAVE_DATA_SORT_KEY_BLOCKS (2)
#define ORBIS_SAVE_DATA_SORT_KEY_MTIME (3)
#define ORBIS_SAVE_DATA_SORT_KEY_FREE_BLOCKS (5)
typedef u32 OrbisSaveDataSortOrder;
#define ORBIS_SAVE_DATA_SORT_ORDER_ASCENT (0)
#define ORBIS_SAVE_DATA_SORT_ORDER_DESCENT (1)
struct OrbisSaveDataDirNameSearchCond {
s32 userId;
int : 32;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
OrbisSaveDataSortKey key;
OrbisSaveDataSortOrder order;
u8 reserved[32];
};
struct OrbisSaveDataSearchInfo {
u64 blocks;
u64 freeBlocks;
u8 reserved[32];
};
struct OrbisSaveDataDirNameSearchResult {
u32 hitNum;
int : 32;
OrbisSaveDataDirName* dirNames;
u32 dirNamesNum;
u32 setNum;
OrbisSaveDataParam* params;
OrbisSaveDataSearchInfo* infos;
u8 reserved[12];
int : 32;
};
struct OrbisSaveDataDelete {
s32 userId;
int : 32;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
u32 unused;
u8 reserved[32];
int : 32;
};
typedef u32 OrbisSaveDataMemorySyncOption;
#define SCE_SAVE_DATA_MEMORY_SYNC_OPTION_NONE (0x00000000)
#define SCE_SAVE_DATA_MEMORY_SYNC_OPTION_BLOCKING (0x00000001 << 0)
struct OrbisSaveDataMemorySync {
s32 userId;
u32 slotId;
OrbisSaveDataMemorySyncOption option;
u8 reserved[28];
};
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_ALL = 0;
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_TITLE = 1;
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE = 2;
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL = 3;
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM = 4;
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_MTIME = 5;
struct OrbisSaveDataBackup;
struct OrbisSaveDataCheckBackupData;
struct OrbisSaveDataDelete;
struct OrbisSaveDataDirNameSearchCond;
struct OrbisSaveDataDirNameSearchResult;
struct OrbisSaveDataEvent;
struct OrbisSaveDataEventParam;
struct OrbisSaveDataIcon;
struct OrbisSaveDataMemoryGet2;
struct OrbisSaveDataMemorySet2;
struct OrbisSaveDataMemorySetup2;
struct OrbisSaveDataMemorySetupResult;
struct OrbisSaveDataMemorySync;
struct OrbisSaveDataMount2;
struct OrbisSaveDataMount;
struct OrbisSaveDataMountInfo;
struct OrbisSaveDataMountPoint;
struct OrbisSaveDataMountResult;
struct OrbisSaveDataRestoreBackupData;
int PS4_SYSV_ABI sceSaveDataAbort();
int PS4_SYSV_ABI sceSaveDataBackup();
Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup);
int PS4_SYSV_ABI sceSaveDataBindPsnAccount();
int PS4_SYSV_ABI sceSaveDataBindPsnAccountForSystemBackup();
int PS4_SYSV_ABI sceSaveDataChangeDatabase();
int PS4_SYSV_ABI sceSaveDataChangeInternal();
int PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check);
Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check);
int PS4_SYSV_ABI sceSaveDataCheckBackupDataForCdlg();
int PS4_SYSV_ABI sceSaveDataCheckBackupDataInternal();
int PS4_SYSV_ABI sceSaveDataCheckCloudData();
@ -263,7 +85,7 @@ int PS4_SYSV_ABI sceSaveDataCheckIpmiIfSize();
int PS4_SYSV_ABI sceSaveDataCheckSaveDataBroken();
int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersion();
int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest();
int PS4_SYSV_ABI sceSaveDataClearProgress();
Error PS4_SYSV_ABI sceSaveDataClearProgress();
int PS4_SYSV_ABI sceSaveDataCopy5();
int PS4_SYSV_ABI sceSaveDataCreateUploadData();
int PS4_SYSV_ABI sceSaveDataDebug();
@ -273,13 +95,13 @@ int PS4_SYSV_ABI sceSaveDataDebugCreateSaveDataRoot();
int PS4_SYSV_ABI sceSaveDataDebugGetThreadId();
int PS4_SYSV_ABI sceSaveDataDebugRemoveSaveDataRoot();
int PS4_SYSV_ABI sceSaveDataDebugTarget();
int PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del);
Error PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del);
int PS4_SYSV_ABI sceSaveDataDelete5();
int PS4_SYSV_ABI sceSaveDataDeleteAllUser();
int PS4_SYSV_ABI sceSaveDataDeleteCloudData();
int PS4_SYSV_ABI sceSaveDataDeleteUser();
int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond,
OrbisSaveDataDirNameSearchResult* result);
Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond,
OrbisSaveDataDirNameSearchResult* result);
int PS4_SYSV_ABI sceSaveDataDirNameSearchInternal();
int PS4_SYSV_ABI sceSaveDataDownload();
int PS4_SYSV_ABI sceSaveDataGetAllSize();
@ -292,70 +114,70 @@ int PS4_SYSV_ABI sceSaveDataGetClientThreadPriority();
int PS4_SYSV_ABI sceSaveDataGetCloudQuotaInfo();
int PS4_SYSV_ABI sceSaveDataGetDataBaseFilePath();
int PS4_SYSV_ABI sceSaveDataGetEventInfo();
int PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam,
OrbisSaveDataEvent* event);
Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam,
OrbisSaveDataEvent* event);
int PS4_SYSV_ABI sceSaveDataGetFormat();
int PS4_SYSV_ABI sceSaveDataGetMountedSaveDataCount();
int PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint,
OrbisSaveDataMountInfo* info);
int PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint,
const OrbisSaveDataParamType paramType, void* paramBuf,
const size_t paramBufSize, size_t* gotSize);
int PS4_SYSV_ABI sceSaveDataGetProgress();
Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint,
OrbisSaveDataMountInfo* info);
Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint,
OrbisSaveDataParamType paramType, void* paramBuf,
size_t paramBufSize, size_t* gotSize);
Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress);
int PS4_SYSV_ABI sceSaveDataGetSaveDataCount();
int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const size_t bufSize,
const int64_t offset);
int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam);
Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
size_t bufSize, int64_t offset);
Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam);
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir();
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootPath();
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootUsbPath();
int PS4_SYSV_ABI sceSaveDataGetSavePoint();
int PS4_SYSV_ABI sceSaveDataGetUpdatedDataCount();
int PS4_SYSV_ABI sceSaveDataInitialize();
int PS4_SYSV_ABI sceSaveDataInitialize2();
int PS4_SYSV_ABI sceSaveDataInitialize3();
Error PS4_SYSV_ABI sceSaveDataInitialize(void*);
Error PS4_SYSV_ABI sceSaveDataInitialize2(void*);
Error PS4_SYSV_ABI sceSaveDataInitialize3(void*);
int PS4_SYSV_ABI sceSaveDataInitializeForCdlg();
int PS4_SYSV_ABI sceSaveDataIsDeletingUsbDb();
int PS4_SYSV_ABI sceSaveDataIsMounted();
int PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint,
OrbisSaveDataIcon* icon);
int PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount,
OrbisSaveDataMountResult* mount_result);
s32 PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount,
OrbisSaveDataMountResult* mount_result);
Error PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint,
OrbisSaveDataIcon* icon);
Error PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount,
OrbisSaveDataMountResult* mount_result);
Error PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount,
OrbisSaveDataMountResult* mount_result);
int PS4_SYSV_ABI sceSaveDataMount5();
int PS4_SYSV_ABI sceSaveDataMountInternal();
int PS4_SYSV_ABI sceSaveDataMountSys();
int PS4_SYSV_ABI sceSaveDataPromote5();
int PS4_SYSV_ABI sceSaveDataRebuildDatabase();
int PS4_SYSV_ABI sceSaveDataRegisterEventCallback();
int PS4_SYSV_ABI sceSaveDataRestoreBackupData();
Error PS4_SYSV_ABI sceSaveDataRestoreBackupData(const OrbisSaveDataRestoreBackupData* restore);
int PS4_SYSV_ABI sceSaveDataRestoreBackupDataForCdlg();
int PS4_SYSV_ABI sceSaveDataRestoreLoadSaveDataMemory();
int PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint,
const OrbisSaveDataIcon* icon);
Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint,
const OrbisSaveDataIcon* icon);
int PS4_SYSV_ABI sceSaveDataSetAutoUploadSetting();
int PS4_SYSV_ABI sceSaveDataSetEventInfo();
int PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint,
OrbisSaveDataParamType paramType, const void* paramBuf,
size_t paramBufSize);
Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint,
OrbisSaveDataParamType paramType, const void* paramBuf,
size_t paramBufSize);
int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser();
int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf,
const size_t bufSize, const int64_t offset);
int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam);
int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(u32 userId, size_t memorySize,
OrbisSaveDataParam* param);
int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
OrbisSaveDataMemorySetupResult* result);
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
size_t bufSize, int64_t offset);
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam);
int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(/*u32 userId, size_t memorySize,
OrbisSaveDataParam* param*/);
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
OrbisSaveDataMemorySetupResult* result);
int PS4_SYSV_ABI sceSaveDataShutdownStart();
int PS4_SYSV_ABI sceSaveDataSupportedFakeBrokenStatus();
int PS4_SYSV_ABI sceSaveDataSyncCloudList();
int PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam);
int PS4_SYSV_ABI sceSaveDataTerminate();
Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam);
Error PS4_SYSV_ABI sceSaveDataTerminate();
int PS4_SYSV_ABI sceSaveDataTransferringMount();
int PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint);
Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint);
int PS4_SYSV_ABI sceSaveDataUmountSys();
int PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint);
Error PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint);
int PS4_SYSV_ABI sceSaveDataUnregisterEventCallback();
int PS4_SYSV_ABI sceSaveDataUpload();
int PS4_SYSV_ABI Func_02E4C4D201716422();

View file

@ -39,11 +39,6 @@ Error PS4_SYSV_ABI sceMsgDialogGetResult(DialogResult* result) {
if (result == nullptr) {
return Error::ARG_NULL;
}
for (const auto v : result->reserved) {
if (v != 0) {
return Error::PARAM_INVALID;
}
}
*result = g_result;
return Error::OK;
}

View file

@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include <imgui.h>
#include "common/assert.h"
#include "imgui/imgui_std.h"
@ -31,18 +33,6 @@ struct {
};
static_assert(std::size(user_button_texts) == static_cast<int>(ButtonType::TWO_BUTTONS) + 1);
static void DrawCenteredText(const char* text) {
const auto ws = GetWindowSize();
const auto text_size = CalcTextSize(text, nullptr, false, ws.x - 40.0f);
PushTextWrapPos(ws.x - 30.0f);
SetCursorPos({
(ws.x - text_size.x) / 2.0f,
(ws.y - text_size.y) / 2.0f - 50.0f,
});
Text("%s", text);
PopTextWrapPos();
}
MsgDialogState::MsgDialogState(const OrbisParam& param) {
this->mode = param.mode;
switch (mode) {
@ -81,11 +71,29 @@ MsgDialogState::MsgDialogState(const OrbisParam& param) {
}
}
MsgDialogState::MsgDialogState(UserState mode) {
this->mode = MsgDialogMode::USER_MSG;
this->state = mode;
}
MsgDialogState::MsgDialogState(ProgressState mode) {
this->mode = MsgDialogMode::PROGRESS_BAR;
this->state = mode;
}
MsgDialogState::MsgDialogState(SystemState mode) {
this->mode = MsgDialogMode::SYSTEM_MSG;
this->state = mode;
}
void MsgDialogUi::DrawUser() {
const auto& [button_type, msg, btn_param1, btn_param2] =
state->GetState<MsgDialogState::UserState>();
const auto ws = GetWindowSize();
DrawCenteredText(msg.c_str());
if (!msg.empty()) {
DrawCenteredText(&msg.front(), &msg.back() + 1,
GetContentRegionAvail() - ImVec2{0.0f, 15.0f + BUTTON_SIZE.y});
}
ASSERT(button_type <= ButtonType::TWO_BUTTONS);
auto [count, text1, text2] = user_button_texts[static_cast<u32>(button_type)];
if (count == 0xFF) { // TWO_BUTTONS -> User defined message
@ -115,7 +123,7 @@ void MsgDialogUi::DrawUser() {
break;
}
}
if (first_render && !focus_first) {
if ((first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) && !focus_first) {
SetItemCurrentNavFocus();
}
PopID();
@ -125,7 +133,7 @@ void MsgDialogUi::DrawUser() {
if (Button(text1, BUTTON_SIZE)) {
Finish(ButtonId::BUTTON1);
}
if (first_render && focus_first) {
if ((first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) && focus_first) {
SetItemCurrentNavFocus();
}
PopID();
@ -249,11 +257,13 @@ void MsgDialogUi::Draw() {
CentralizeWindow();
SetNextWindowSize(window_size);
SetNextWindowFocus();
SetNextWindowCollapsed(false);
if (first_render || !io.NavActive) {
SetNextWindowFocus();
}
KeepNavHighlight();
// Hack to allow every dialog to have a unique window
if (Begin("Message Dialog##MessageDialog", nullptr, ImGuiWindowFlags_NoSavedSettings)) {
if (Begin("Message Dialog##MessageDialog", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
switch (state->GetMode()) {
case MsgDialogMode::USER_MSG:
DrawUser();
@ -269,4 +279,16 @@ void MsgDialogUi::Draw() {
End();
first_render = false;
}
}
DialogResult Libraries::MsgDialog::ShowMsgDialog(MsgDialogState state, bool block) {
DialogResult result{};
Status status = Status::RUNNING;
MsgDialogUi dialog(&state, &status, &result);
if (block) {
while (status == Status::RUNNING) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
return result;
}

View file

@ -3,6 +3,7 @@
#pragma once
#include <string>
#include <variant>
#include "common/fixed_value.h"
@ -129,6 +130,11 @@ private:
public:
explicit MsgDialogState(const OrbisParam& param);
explicit MsgDialogState(UserState mode);
explicit MsgDialogState(ProgressState mode);
explicit MsgDialogState(SystemState mode);
MsgDialogState() = default;
[[nodiscard]] OrbisUserServiceUserId GetUserId() const {
@ -165,13 +171,11 @@ public:
void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK);
void SetProgressBarValue(u32 value, bool increment);
void Draw() override;
bool ShouldGrabGamepad() override {
return true;
}
};
// Utility function to show a message dialog
// !!! This function can block !!!
DialogResult ShowMsgDialog(MsgDialogState state, bool block = true);
}; // namespace Libraries::MsgDialog

View file

@ -1,84 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/system/savedatadialog.h"
namespace Libraries::SaveDataDialog {
int PS4_SYSV_ABI sceSaveDataDialogClose() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogGetResult() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogGetStatus() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogInitialize() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return 1;
}
int PS4_SYSV_ABI sceSaveDataDialogOpen() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogProgressBarInc() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogTerminate() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogUpdateStatus() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return 3; // SCE_COMMON_DIALOG_STATUS_FINISHED
}
void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("fH46Lag88XY", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogClose);
LIB_FUNCTION("yEiJ-qqr6Cg", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogGetResult);
LIB_FUNCTION("ERKzksauAJA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogGetStatus);
LIB_FUNCTION("s9e3+YpRnzw", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogInitialize);
LIB_FUNCTION("en7gNVnh878", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogIsReadyToDisplay);
LIB_FUNCTION("4tPhsP6FpDI", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogOpen);
LIB_FUNCTION("V-uEeFKARJU", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogProgressBarInc);
LIB_FUNCTION("hay1CfTmLyA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogProgressBarSetValue);
LIB_FUNCTION("YuH2FA7azqQ", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogTerminate);
LIB_FUNCTION("KK3Bdg1RWK0", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogUpdateStatus);
};
} // namespace Libraries::SaveDataDialog

View file

@ -1,26 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::SaveDataDialog {
int PS4_SYSV_ABI sceSaveDataDialogClose();
int PS4_SYSV_ABI sceSaveDataDialogGetResult();
int PS4_SYSV_ABI sceSaveDataDialogGetStatus();
int PS4_SYSV_ABI sceSaveDataDialogInitialize();
int PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay();
int PS4_SYSV_ABI sceSaveDataDialogOpen();
int PS4_SYSV_ABI sceSaveDataDialogProgressBarInc();
int PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue();
int PS4_SYSV_ABI sceSaveDataDialogTerminate();
int PS4_SYSV_ABI sceSaveDataDialogUpdateStatus();
void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::SaveDataDialog

View file

@ -261,8 +261,11 @@ void VideoOutDriver::SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_
}
void VideoOutDriver::PresentThread(std::stop_token token) {
static constexpr std::chrono::milliseconds VblankPeriod{16};
static constexpr std::chrono::nanoseconds VblankPeriod{16666667};
const auto vblank_period = VblankPeriod / Config::vblankDiv();
Common::SetCurrentThreadName("PresentThread");
Common::SetCurrentThreadRealtime(vblank_period);
const auto receive_request = [this] -> Request {
std::scoped_lock lk{mutex};
@ -274,7 +277,6 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
return {};
};
auto vblank_period = VblankPeriod / Config::vblankDiv();
auto delay = std::chrono::microseconds{0};
while (!token.stop_requested()) {
// Sleep for most of the vblank duration.
@ -291,8 +293,8 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
}
} else {
delay = Flip(request);
FRAME_END;
}
FRAME_END;
}
{

View file

@ -59,7 +59,7 @@ static LONG WINAPI SignalHandler(EXCEPTION_POINTERS* pExp) noexcept {
#endif
#ifndef IS_WRITE_ERROR
#error "Missing IS_WRITE_ERROR() implementation for target OS and CPU architecture.
#error "Missing IS_WRITE_ERROR() implementation for target OS and CPU architecture."
#endif
static std::string DisassembleInstruction(void* code_address) {
@ -120,9 +120,11 @@ SignalDispatch::SignalDispatch() {
ASSERT_MSG(handle = AddVectoredExceptionHandler(0, SignalHandler),
"Failed to register exception handler.");
#else
constexpr struct sigaction action {
.sa_flags = SA_SIGINFO | SA_ONSTACK, .sa_sigaction = SignalHandler, .sa_mask = 0,
};
struct sigaction action {};
action.sa_sigaction = SignalHandler;
action.sa_flags = SA_SIGINFO | SA_ONSTACK;
sigemptyset(&action.sa_mask);
ASSERT_MSG(sigaction(SIGSEGV, &action, nullptr) == 0 &&
sigaction(SIGBUS, &action, nullptr) == 0,
"Failed to register access violation signal handler.");
@ -135,9 +137,11 @@ SignalDispatch::~SignalDispatch() {
#if defined(_WIN32)
ASSERT_MSG(RemoveVectoredExceptionHandler(handle), "Failed to remove exception handler.");
#else
constexpr struct sigaction action {
.sa_flags = 0, .sa_handler = SIG_DFL, .sa_mask = 0,
};
struct sigaction action {};
action.sa_handler = SIG_DFL;
action.sa_flags = 0;
sigemptyset(&action.sa_mask);
ASSERT_MSG(sigaction(SIGSEGV, &action, nullptr) == 0 &&
sigaction(SIGBUS, &action, nullptr) == 0,
"Failed to remove access violation signal handler.");

View file

@ -10,6 +10,7 @@
#ifdef ENABLE_QT_GUI
#include "common/memory_patcher.h"
#endif
#include "common/assert.h"
#include "common/ntapi.h"
#include "common/path_util.h"
#include "common/polyfill_thread.h"
@ -42,9 +43,10 @@ Emulator::Emulator() {
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::load(config_dir / "config.toml");
// Initialize NT API functions
// Initialize NT API functions and set high priority
#ifdef _WIN32
Common::NtApi::Initialize();
SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
#endif
// Start logger.
@ -98,8 +100,9 @@ void Emulator::Run(const std::filesystem::path& file) {
for (const auto& entry : std::filesystem::directory_iterator(sce_sys_folder)) {
if (entry.path().filename() == "param.sfo") {
auto* param_sfo = Common::Singleton<PSF>::Instance();
param_sfo->open(sce_sys_folder.string() + "/param.sfo", {});
id = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9);
const bool success = param_sfo->Open(sce_sys_folder / "param.sfo");
ASSERT_MSG(success, "Failed to open param.sfo");
id = std::string(*param_sfo->GetString("CONTENT_ID"), 7, 9);
Libraries::NpTrophy::game_serial = id;
const auto trophyDir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles";
@ -112,10 +115,10 @@ void Emulator::Run(const std::filesystem::path& file) {
#ifdef ENABLE_QT_GUI
MemoryPatcher::g_game_serial = id;
#endif
title = param_sfo->GetString("TITLE");
title = *param_sfo->GetString("TITLE");
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
u32 fw_version = param_sfo->GetInteger("SYSTEM_VER");
app_version = param_sfo->GetString("APP_VER");
u32 fw_version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000);
app_version = *param_sfo->GetString("APP_VER");
LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version);
} else if (entry.path().filename() == "playgo-chunk.dat") {
auto* playgo = Common::Singleton<PlaygoFile>::Instance();
@ -141,8 +144,8 @@ void Emulator::Run(const std::filesystem::path& file) {
if (Common::isRelease) {
window_title = fmt::format("shadPS4 v{} | {}", Common::VERSION, game_title);
} else {
window_title =
fmt::format("shadPS4 v{} {} | {}", Common::VERSION, Common::g_scm_desc, game_title);
window_title = fmt::format("shadPS4 v{} {} {} | {}", Common::VERSION, Common::g_scm_branch,
Common::g_scm_desc, game_title);
}
window = std::make_unique<Frontend::WindowSDL>(
Config::getScreenWidth(), Config::getScreenHeight(), controller, window_title);

View file

@ -26,4 +26,7 @@ extern void assert_fail_debug_msg(const char* msg);
#define IMGUI_DEFINE_MATH_OPERATORS
#define IM_VEC2_CLASS_EXTRA \
constexpr ImVec2(float _v) : x(_v), y(_v) {}
constexpr ImVec2(float _v) : x(_v), y(_v) {}
#define IM_VEC4_CLASS_EXTRA \
constexpr ImVec4(float _v) : x(_v), y(_v), z(_v), w(_v) {}

View file

@ -12,10 +12,6 @@ public:
static void RemoveLayer(Layer* layer);
virtual void Draw() = 0;
virtual bool ShouldGrabGamepad() {
return false;
}
};
} // namespace ImGui

View file

@ -3,12 +3,25 @@
#pragma once
#include <cmath>
#include <imgui.h>
#include "imgui_internal.h"
#define IM_COL32_GRAY(x) IM_COL32(x, x, x, 0xFF)
namespace ImGui {
namespace Easing {
inline float FastInFastOutCubic(float x) {
constexpr float c4 = 1.587401f; // 4^(1/3)
constexpr float c05 = 0.7937f; // 0.5^(1/3)
return std::pow(c4 * x - c05, 3.0f) + 0.5f;
}
} // namespace Easing
inline void CentralizeWindow() {
const auto display_size = GetIO().DisplaySize;
SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f});
@ -18,10 +31,39 @@ inline void KeepNavHighlight() {
GetCurrentContext()->NavDisableHighlight = false;
}
inline void SetItemCurrentNavFocus() {
inline void SetItemCurrentNavFocus(const ImGuiID id = -1) {
const auto ctx = GetCurrentContext();
SetFocusID(ctx->LastItemData.ID, ctx->CurrentWindow);
SetFocusID(id == -1 ? ctx->LastItemData.ID : id, ctx->CurrentWindow);
ctx->NavInitResult.Clear();
ctx->NavDisableHighlight = false;
}
inline void DrawPrettyBackground() {
const double time = GetTime() / 1.5f;
const float x = ((float)std::cos(time) + 1.0f) / 2.0f;
const float d = Easing::FastInFastOutCubic(x);
u8 top_left = ImLerp(0x13, 0x05, d);
u8 top_right = ImLerp(0x00, 0x07, d);
u8 bottom_right = ImLerp(0x03, 0x27, d);
u8 bottom_left = ImLerp(0x05, 0x00, d);
auto& window = *GetCurrentWindowRead();
auto inner_pos = window.DC.CursorPos - window.WindowPadding;
auto inner_size = GetContentRegionAvail() + window.WindowPadding * 2.0f;
GetWindowDrawList()->AddRectFilledMultiColor(
inner_pos, inner_pos + inner_size, IM_COL32_GRAY(top_left), IM_COL32_GRAY(top_right),
IM_COL32_GRAY(bottom_right), IM_COL32_GRAY(bottom_left));
}
static void DrawCenteredText(const char* text, const char* text_end = nullptr,
ImVec2 content = GetContentRegionAvail()) {
auto pos = GetCursorPos();
const auto text_size = CalcTextSize(text, text_end, false, content.x - 40.0f);
PushTextWrapPos(content.x);
SetCursorPos(pos + (content - text_size) / 2.0f);
TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
PopTextWrapPos();
SetCursorPos(pos + content);
}
} // namespace ImGui

45
src/imgui/imgui_texture.h Normal file
View file

@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <imgui.h>
namespace ImGui {
namespace Core::TextureManager {
struct Inner;
} // namespace Core::TextureManager
class RefCountedTexture {
Core::TextureManager::Inner* inner;
explicit RefCountedTexture(Core::TextureManager::Inner* inner);
public:
struct Image {
ImTextureID im_id;
u32 width;
u32 height;
};
static RefCountedTexture DecodePngTexture(std::vector<u8> data);
static RefCountedTexture DecodePngFile(std::filesystem::path path);
RefCountedTexture();
RefCountedTexture(const RefCountedTexture& other);
RefCountedTexture(RefCountedTexture&& other) noexcept;
RefCountedTexture& operator=(const RefCountedTexture& other);
RefCountedTexture& operator=(RefCountedTexture&& other) noexcept;
virtual ~RefCountedTexture();
[[nodiscard]] Image GetTexture() const;
explicit(false) operator bool() const;
};
}; // namespace ImGui

View file

@ -10,7 +10,7 @@ void ImGui::Layers::VideoInfo::Draw() {
m_show = IsKeyPressed(ImGuiKey_F10, false) ^ m_show;
if (m_show) {
if (Begin("Video Info")) {
if (Begin("Video Info", 0, ImGuiWindowFlags_NoNav)) {
Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
}
End();

View file

@ -9,7 +9,9 @@
#include "imgui_core.h"
#include "imgui_impl_sdl3.h"
#include "imgui_impl_vulkan.h"
#include "imgui_internal.h"
#include "sdl_window.h"
#include "texture_manager.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
static void CheckVkResult(const vk::Result err) {
@ -68,6 +70,8 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w
.check_vk_result_fn = &CheckVkResult,
};
Vulkan::Init(vk_info);
TextureManager::StartWorker();
}
void OnResize() {
@ -77,6 +81,8 @@ void OnResize() {
void Shutdown(const vk::Device& device) {
device.waitIdle();
TextureManager::StopWorker();
const ImGuiIO& io = GetIO();
const auto ini_filename = (void*)io.IniFilename;
const auto log_filename = (void*)io.LogFilename;
@ -92,24 +98,19 @@ void Shutdown(const vk::Device& device) {
bool ProcessEvent(SDL_Event* event) {
Sdl::ProcessEvent(event);
switch (event->type) {
// Don't block release/up events
case SDL_EVENT_MOUSE_MOTION:
case SDL_EVENT_MOUSE_WHEEL:
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
return GetIO().WantCaptureMouse;
case SDL_EVENT_TEXT_INPUT:
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
return GetIO().WantCaptureKeyboard;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
case SDL_EVENT_GAMEPAD_ADDED:
case SDL_EVENT_GAMEPAD_REMOVED:
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
return (GetIO().BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
return GetIO().NavActive;
default:
return false;
}
@ -130,21 +131,11 @@ void NewFrame() {
}
}
Vulkan::NewFrame();
Sdl::NewFrame();
ImGui::NewFrame();
bool capture_gamepad = false;
for (auto* layer : layers) {
layer->Draw();
if (layer->ShouldGrabGamepad()) {
capture_gamepad = true;
}
}
if (capture_gamepad) {
GetIO().BackendFlags |= ImGuiBackendFlags_HasGamepad;
} else {
GetIO().BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
}
}

View file

@ -4,6 +4,8 @@
// Based on imgui_impl_vulkan.cpp from Dear ImGui repository
#include <cstdio>
#include <mutex>
#include <imgui.h>
#include "imgui_impl_vulkan.h"
@ -47,13 +49,15 @@ struct VkData {
vk::ShaderModule shader_module_vert{};
vk::ShaderModule shader_module_frag{};
std::mutex command_pool_mutex;
vk::CommandPool command_pool{};
vk::Sampler simple_sampler{};
// Font data
vk::Sampler font_sampler{};
vk::DeviceMemory font_memory{};
vk::Image font_image{};
vk::ImageView font_view{};
vk::DescriptorSet font_descriptor_set{};
vk::CommandPool font_command_pool{};
vk::CommandBuffer font_command_buffer{};
// Render buffers
@ -222,12 +226,53 @@ static inline vk::DeviceSize AlignBufferSize(vk::DeviceSize size, vk::DeviceSize
return (size + alignment - 1) & ~(alignment - 1);
}
// Register a texture
vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view,
vk::ImageLayout image_layout) {
void UploadTextureData::Upload() {
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
vk::SubmitInfo submit_info{
.commandBufferCount = 1,
.pCommandBuffers = &command_buffer,
};
CheckVkErr(v.queue.submit({submit_info}));
CheckVkErr(v.queue.waitIdle());
v.device.destroyBuffer(upload_buffer, v.allocator);
v.device.freeMemory(upload_buffer_memory, v.allocator);
{
std::unique_lock lk(bd->command_pool_mutex);
v.device.freeCommandBuffers(bd->command_pool, {command_buffer});
}
upload_buffer = VK_NULL_HANDLE;
upload_buffer_memory = VK_NULL_HANDLE;
}
void UploadTextureData::Destroy() {
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
CheckVkErr(v.device.waitIdle());
RemoveTexture(descriptor_set);
descriptor_set = VK_NULL_HANDLE;
v.device.destroyImageView(image_view, v.allocator);
image_view = VK_NULL_HANDLE;
v.device.destroyImage(image, v.allocator);
image = VK_NULL_HANDLE;
v.device.freeMemory(image_memory, v.allocator);
image_memory = VK_NULL_HANDLE;
}
// Register a texture
vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout,
vk::Sampler sampler) {
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
if (sampler == VK_NULL_HANDLE) {
sampler = bd->simple_sampler;
}
// Create Descriptor Set:
vk::DescriptorSet descriptor_set;
{
@ -262,6 +307,166 @@ vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view,
}
return descriptor_set;
}
UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height,
size_t size) {
ImGuiIO& io = GetIO();
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
UploadTextureData info{};
{
std::unique_lock lk(bd->command_pool_mutex);
info.command_buffer =
CheckVkResult(v.device.allocateCommandBuffers(vk::CommandBufferAllocateInfo{
.commandPool = bd->command_pool,
.commandBufferCount = 1,
}))
.front();
CheckVkErr(info.command_buffer.begin(vk::CommandBufferBeginInfo{
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit,
}));
}
// Create Image
{
vk::ImageCreateInfo image_info{
.imageType = vk::ImageType::e2D,
.format = format,
.extent{
.width = width,
.height = height,
.depth = 1,
},
.mipLevels = 1,
.arrayLayers = 1,
.samples = vk::SampleCountFlagBits::e1,
.tiling = vk::ImageTiling::eOptimal,
.usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled,
.sharingMode = vk::SharingMode::eExclusive,
.initialLayout = vk::ImageLayout::eUndefined,
};
info.image = CheckVkResult(v.device.createImage(image_info, v.allocator));
auto req = v.device.getImageMemoryRequirements(info.image);
vk::MemoryAllocateInfo alloc_info{
.allocationSize = IM_MAX(v.min_allocation_size, req.size),
.memoryTypeIndex =
FindMemoryType(vk::MemoryPropertyFlagBits::eDeviceLocal, req.memoryTypeBits),
};
info.image_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator));
CheckVkErr(v.device.bindImageMemory(info.image, info.image_memory, 0));
}
// Create Image View
{
vk::ImageViewCreateInfo view_info{
.image = info.image,
.viewType = vk::ImageViewType::e2D,
.format = format,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.layerCount = 1,
},
};
info.image_view = CheckVkResult(v.device.createImageView(view_info, v.allocator));
}
// Create descriptor set (ImTextureID)
info.descriptor_set = AddTexture(info.image_view, vk::ImageLayout::eShaderReadOnlyOptimal);
// Create Upload Buffer
{
vk::BufferCreateInfo buffer_info{
.size = size,
.usage = vk::BufferUsageFlagBits::eTransferSrc,
.sharingMode = vk::SharingMode::eExclusive,
};
info.upload_buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator));
auto req = v.device.getBufferMemoryRequirements(info.upload_buffer);
auto alignemtn = IM_MAX(bd->buffer_memory_alignment, req.alignment);
vk::MemoryAllocateInfo alloc_info{
.allocationSize = IM_MAX(v.min_allocation_size, req.size),
.memoryTypeIndex =
FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits),
};
info.upload_buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator));
CheckVkErr(v.device.bindBufferMemory(info.upload_buffer, info.upload_buffer_memory, 0));
}
// Upload to Buffer
{
char* map = (char*)CheckVkResult(v.device.mapMemory(info.upload_buffer_memory, 0, size));
memcpy(map, data, size);
vk::MappedMemoryRange range[1]{
{
.memory = info.upload_buffer_memory,
.size = size,
},
};
CheckVkErr(v.device.flushMappedMemoryRanges(range));
v.device.unmapMemory(info.upload_buffer_memory);
}
// Copy to Image
{
vk::ImageMemoryBarrier copy_barrier[1]{
{
.sType = vk::StructureType::eImageMemoryBarrier,
.dstAccessMask = vk::AccessFlagBits::eTransferWrite,
.oldLayout = vk::ImageLayout::eUndefined,
.newLayout = vk::ImageLayout::eTransferDstOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = info.image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.layerCount = 1,
},
},
};
info.command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eHost,
vk::PipelineStageFlagBits::eTransfer, {}, {}, {},
{copy_barrier});
vk::BufferImageCopy region{
.imageSubresource{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.layerCount = 1,
},
.imageExtent{
.width = width,
.height = height,
.depth = 1,
},
};
info.command_buffer.copyBufferToImage(info.upload_buffer, info.image,
vk::ImageLayout::eTransferDstOptimal, {region});
vk::ImageMemoryBarrier use_barrier[1]{{
.sType = vk::StructureType::eImageMemoryBarrier,
.srcAccessMask = vk::AccessFlagBits::eTransferWrite,
.dstAccessMask = vk::AccessFlagBits::eShaderRead,
.oldLayout = vk::ImageLayout::eTransferDstOptimal,
.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = info.image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.layerCount = 1,
},
}};
info.command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {},
{use_barrier});
}
CheckVkErr(info.command_buffer.end());
return info;
}
void RemoveTexture(vk::DescriptorSet descriptor_set) {
VkData* bd = GetBackendData();
@ -517,27 +722,20 @@ static bool CreateFontsTexture() {
DestroyFontsTexture();
}
// Create command pool/buffer
if (bd->font_command_pool == VK_NULL_HANDLE) {
vk::CommandPoolCreateInfo info{
.sType = vk::StructureType::eCommandPoolCreateInfo,
.flags = vk::CommandPoolCreateFlags{},
.queueFamilyIndex = v.queue_family,
};
bd->font_command_pool = CheckVkResult(v.device.createCommandPool(info, v.allocator));
}
// Create command buffer
if (bd->font_command_buffer == VK_NULL_HANDLE) {
vk::CommandBufferAllocateInfo info{
.sType = vk::StructureType::eCommandBufferAllocateInfo,
.commandPool = bd->font_command_pool,
.commandPool = bd->command_pool,
.commandBufferCount = 1,
};
std::unique_lock lk(bd->command_pool_mutex);
bd->font_command_buffer = CheckVkResult(v.device.allocateCommandBuffers(info)).front();
}
// Start command buffer
{
CheckVkErr(v.device.resetCommandPool(bd->font_command_pool, vk::CommandPoolResetFlags{}));
CheckVkErr(bd->font_command_buffer.reset());
vk::CommandBufferBeginInfo begin_info{};
begin_info.sType = vk::StructureType::eCommandBufferBeginInfo;
begin_info.flags |= vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
@ -597,8 +795,7 @@ static bool CreateFontsTexture() {
}
// Create the Descriptor Set:
bd->font_descriptor_set =
AddTexture(bd->font_sampler, bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal);
bd->font_descriptor_set = AddTexture(bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal);
// Create the Upload Buffer:
vk::DeviceMemory upload_buffer_memory{};
@ -956,25 +1153,6 @@ bool CreateDeviceObjects() {
bd->descriptor_pool = CheckVkResult(v.device.createDescriptorPool(pool_info));
}
if (!bd->font_sampler) {
// Bilinear sampling is required by default. Set 'io.Fonts->Flags |=
// ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow
// point/nearest sampling.
vk::SamplerCreateInfo info{
.sType = vk::StructureType::eSamplerCreateInfo,
.magFilter = vk::Filter::eLinear,
.minFilter = vk::Filter::eLinear,
.mipmapMode = vk::SamplerMipmapMode::eLinear,
.addressModeU = vk::SamplerAddressMode::eRepeat,
.addressModeV = vk::SamplerAddressMode::eRepeat,
.addressModeW = vk::SamplerAddressMode::eRepeat,
.maxAnisotropy = 1.0f,
.minLod = -1000,
.maxLod = 1000,
};
bd->font_sampler = CheckVkResult(v.device.createSampler(info, v.allocator));
}
if (!bd->descriptor_set_layout) {
vk::DescriptorSetLayoutBinding binding[1]{
{
@ -1016,6 +1194,35 @@ bool CreateDeviceObjects() {
CreatePipeline(v.device, v.allocator, v.pipeline_cache, nullptr, &bd->pipeline, v.subpass);
if (bd->command_pool == VK_NULL_HANDLE) {
vk::CommandPoolCreateInfo info{
.sType = vk::StructureType::eCommandPoolCreateInfo,
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
.queueFamilyIndex = v.queue_family,
};
std::unique_lock lk(bd->command_pool_mutex);
bd->command_pool = CheckVkResult(v.device.createCommandPool(info, v.allocator));
}
if (!bd->simple_sampler) {
// Bilinear sampling is required by default. Set 'io.Fonts->Flags |=
// ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow
// point/nearest sampling.
vk::SamplerCreateInfo info{
.sType = vk::StructureType::eSamplerCreateInfo,
.magFilter = vk::Filter::eLinear,
.minFilter = vk::Filter::eLinear,
.mipmapMode = vk::SamplerMipmapMode::eLinear,
.addressModeU = vk::SamplerAddressMode::eRepeat,
.addressModeV = vk::SamplerAddressMode::eRepeat,
.addressModeW = vk::SamplerAddressMode::eRepeat,
.maxAnisotropy = 1.0f,
.minLod = -1000,
.maxLod = 1000,
};
bd->simple_sampler = CheckVkResult(v.device.createSampler(info, v.allocator));
}
return true;
}
@ -1026,12 +1233,14 @@ void ImGuiImplVulkanDestroyDeviceObjects() {
DestroyFontsTexture();
if (bd->font_command_buffer) {
v.device.freeCommandBuffers(bd->font_command_pool, {bd->font_command_buffer});
std::unique_lock lk(bd->command_pool_mutex);
v.device.freeCommandBuffers(bd->command_pool, {bd->font_command_buffer});
bd->font_command_buffer = VK_NULL_HANDLE;
}
if (bd->font_command_pool) {
v.device.destroyCommandPool(bd->font_command_pool, v.allocator);
bd->font_command_pool = VK_NULL_HANDLE;
if (bd->command_pool) {
std::unique_lock lk(bd->command_pool_mutex);
v.device.destroyCommandPool(bd->command_pool, v.allocator);
bd->command_pool = VK_NULL_HANDLE;
}
if (bd->shader_module_vert) {
v.device.destroyShaderModule(bd->shader_module_vert, v.allocator);
@ -1041,9 +1250,9 @@ void ImGuiImplVulkanDestroyDeviceObjects() {
v.device.destroyShaderModule(bd->shader_module_frag, v.allocator);
bd->shader_module_frag = VK_NULL_HANDLE;
}
if (bd->font_sampler) {
v.device.destroySampler(bd->font_sampler, v.allocator);
bd->font_sampler = VK_NULL_HANDLE;
if (bd->simple_sampler) {
v.device.destroySampler(bd->simple_sampler, v.allocator);
bd->simple_sampler = VK_NULL_HANDLE;
}
if (bd->descriptor_set_layout) {
v.device.destroyDescriptorSetLayout(bd->descriptor_set_layout, v.allocator);
@ -1095,13 +1304,4 @@ void Shutdown() {
IM_DELETE(bd);
}
void NewFrame() {
VkData* bd = GetBackendData();
IM_ASSERT(bd != nullptr &&
"Context or backend not initialized! Did you call ImGuiImplVulkanInit()?");
if (!bd->font_descriptor_set)
CreateFontsTexture();
}
} // namespace ImGui::Vulkan

View file

@ -6,6 +6,7 @@
#pragma once
#define VULKAN_HPP_NO_EXCEPTIONS
#include "common/types.h"
#include "video_core/renderer_vulkan/vk_common.h"
struct ImDrawData;
@ -29,14 +30,33 @@ struct InitInfo {
void (*check_vk_result_fn)(vk::Result err);
};
vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view,
vk::ImageLayout image_layout);
// Prepare all resources needed for uploading textures
// Caller should clean up the returned data.
struct UploadTextureData {
vk::Image image;
vk::ImageView image_view;
vk::DescriptorSet descriptor_set;
vk::DeviceMemory image_memory;
vk::CommandBuffer command_buffer; // Submit to the queue
vk::Buffer upload_buffer;
vk::DeviceMemory upload_buffer_memory;
void Upload();
void Destroy();
};
vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout,
vk::Sampler sampler = VK_NULL_HANDLE);
UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height,
size_t size);
void RemoveTexture(vk::DescriptorSet descriptor_set);
bool Init(InitInfo info);
void Shutdown();
void NewFrame();
void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer,
vk::Pipeline pipeline = VK_NULL_HANDLE);

View file

@ -0,0 +1,243 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <deque>
#include <utility>
#include <externals/stb_image.h>
#include "common/assert.h"
#include "common/io_file.h"
#include "common/polyfill_thread.h"
#include "imgui_impl_vulkan.h"
#include "texture_manager.h"
namespace ImGui {
namespace Core::TextureManager {
struct Inner {
std::atomic_int count = 0;
ImTextureID texture_id = nullptr;
u32 width = 0;
u32 height = 0;
Vulkan::UploadTextureData upload_data;
~Inner();
};
} // namespace Core::TextureManager
using namespace Core::TextureManager;
RefCountedTexture::RefCountedTexture(Inner* inner) : inner(inner) {
++inner->count;
}
RefCountedTexture RefCountedTexture::DecodePngTexture(std::vector<u8> data) {
const auto core = new Inner;
Core::TextureManager::DecodePngTexture(std::move(data), core);
return RefCountedTexture(core);
}
RefCountedTexture RefCountedTexture::DecodePngFile(std::filesystem::path path) {
const auto core = new Inner;
Core::TextureManager::DecodePngFile(std::move(path), core);
return RefCountedTexture(core);
}
RefCountedTexture::RefCountedTexture() : inner(nullptr) {}
RefCountedTexture::RefCountedTexture(const RefCountedTexture& other) : inner(other.inner) {
if (inner != nullptr) {
++inner->count;
}
}
RefCountedTexture::RefCountedTexture(RefCountedTexture&& other) noexcept : inner(other.inner) {
other.inner = nullptr;
}
RefCountedTexture& RefCountedTexture::operator=(const RefCountedTexture& other) {
if (this == &other)
return *this;
inner = other.inner;
if (inner != nullptr) {
++inner->count;
}
return *this;
}
RefCountedTexture& RefCountedTexture::operator=(RefCountedTexture&& other) noexcept {
if (this == &other)
return *this;
std::swap(inner, other.inner);
return *this;
}
RefCountedTexture::~RefCountedTexture() {
if (inner != nullptr) {
if (inner->count.fetch_sub(1) == 1) {
delete inner;
}
}
}
RefCountedTexture::Image RefCountedTexture::GetTexture() const {
if (inner == nullptr) {
return {};
}
return Image{
.im_id = inner->texture_id,
.width = inner->width,
.height = inner->height,
};
}
RefCountedTexture::operator bool() const {
return inner != nullptr && inner->texture_id != nullptr;
}
struct Job {
Inner* core;
std::vector<u8> data;
std::filesystem::path path;
};
struct UploadJob {
Inner* core = nullptr;
Vulkan::UploadTextureData data;
int tick = 0; // Used to skip the first frame when destroying to await the current frame to draw
};
static bool g_is_worker_running = false;
static std::jthread g_worker_thread;
static std::condition_variable g_worker_cv;
static std::mutex g_job_list_mtx;
static std::deque<Job> g_job_list;
static std::mutex g_upload_mtx;
static std::deque<UploadJob> g_upload_list;
namespace Core::TextureManager {
Inner::~Inner() {
if (upload_data.descriptor_set != nullptr) {
std::unique_lock lk{g_upload_mtx};
g_upload_list.emplace_back(UploadJob{
.data = this->upload_data,
.tick = 2,
});
}
}
void WorkerLoop() {
std::mutex mtx;
while (g_is_worker_running) {
std::unique_lock lk{mtx};
g_worker_cv.wait(lk);
if (!g_is_worker_running) {
break;
}
while (true) {
g_job_list_mtx.lock();
if (g_job_list.empty()) {
g_job_list_mtx.unlock();
break;
}
auto [core, png_raw, path] = std::move(g_job_list.front());
g_job_list.pop_front();
g_job_list_mtx.unlock();
if (!path.empty()) { // Decode PNG from file
Common::FS::IOFile file(path, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
LOG_ERROR(ImGui, "Failed to open PNG file: {}", path.string());
continue;
}
png_raw.resize(file.GetSize());
file.Seek(0);
file.ReadRaw<u8>(png_raw.data(), png_raw.size());
file.Close();
}
int width, height;
const stbi_uc* pixels =
stbi_load_from_memory(png_raw.data(), png_raw.size(), &width, &height, nullptr, 4);
auto texture = Vulkan::UploadTexture(pixels, vk::Format::eR8G8B8A8Unorm, width, height,
width * height * 4 * sizeof(stbi_uc));
core->upload_data = texture;
core->width = width;
core->height = height;
std::unique_lock upload_lk{g_upload_mtx};
g_upload_list.emplace_back(UploadJob{
.core = core,
});
}
}
}
void StartWorker() {
ASSERT(!g_is_worker_running);
g_worker_thread = std::jthread(WorkerLoop);
g_is_worker_running = true;
}
void StopWorker() {
ASSERT(g_is_worker_running);
g_is_worker_running = false;
g_worker_cv.notify_one();
}
void DecodePngTexture(std::vector<u8> data, Inner* core) {
++core->count;
Job job{
.core = core,
.data = std::move(data),
};
std::unique_lock lk{g_job_list_mtx};
g_job_list.push_back(std::move(job));
g_worker_cv.notify_one();
}
void DecodePngFile(std::filesystem::path path, Inner* core) {
++core->count;
Job job{
.core = core,
.path = std::move(path),
};
std::unique_lock lk{g_job_list_mtx};
g_job_list.push_back(std::move(job));
g_worker_cv.notify_one();
}
void Submit() {
UploadJob upload;
{
std::unique_lock lk{g_upload_mtx};
if (g_upload_list.empty()) {
return;
}
// Upload one texture at a time to avoid slow down
upload = g_upload_list.front();
g_upload_list.pop_front();
if (upload.tick > 0) {
--upload.tick;
g_upload_list.emplace_back(upload);
return;
}
}
if (upload.core != nullptr) {
upload.core->upload_data.Upload();
upload.core->texture_id = upload.core->upload_data.descriptor_set;
if (upload.core->count.fetch_sub(1) == 1) {
delete upload.core;
}
} else {
upload.data.Destroy();
}
}
} // namespace Core::TextureManager
} // namespace ImGui

View file

@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <vector>
#include "common/types.h"
#include "imgui/imgui_texture.h"
namespace vk {
class CommandBuffer;
}
namespace ImGui::Core::TextureManager {
struct Inner;
void StartWorker();
void StopWorker();
void DecodePngTexture(std::vector<u8> data, Inner* core);
void DecodePngFile(std::filesystem::path path, Inner* core);
void Submit();
}; // namespace ImGui::Core::TextureManager

View file

@ -10,6 +10,11 @@ int main(int argc, char* argv[]) {
fmt::print("Usage: {} <elf or eboot.bin path>\n", argv[0]);
return -1;
}
// check if eboot file exists
if (!std::filesystem::exists(argv[1])) {
fmt::print("Eboot.bin file not found\n");
return -1;
}
for (int i = 0; i < argc; i++) {
std::string curArg = argv[i];

View file

@ -27,20 +27,21 @@ public:
game.path = filePath;
PSF psf;
if (psf.open(game.path + "/sce_sys/param.sfo", {})) {
if (psf.Open(std::filesystem::path(game.path) / "sce_sys" / "param.sfo")) {
game.icon_path = game.path + "/sce_sys/icon0.png";
QString iconpath = QString::fromStdString(game.icon_path);
game.icon = QImage(iconpath);
game.pic_path = game.path + "/sce_sys/pic1.png";
game.name = psf.GetString("TITLE");
game.serial = psf.GetString("TITLE_ID");
game.region = GameListUtils::GetRegion(psf.GetString("CONTENT_ID").at(0)).toStdString();
u32 fw_int = psf.GetInteger("SYSTEM_VER");
game.name = *psf.GetString("TITLE");
game.serial = *psf.GetString("TITLE_ID");
game.region =
GameListUtils::GetRegion(psf.GetString("CONTENT_ID")->at(0)).toStdString();
u32 fw_int = *psf.GetInteger("SYSTEM_VER");
QString fw = QString::number(fw_int, 16);
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
: fw.left(3).insert(1, '.');
game.fw = (fw_int == 0) ? "0.00" : fw_.toStdString();
game.version = psf.GetString("APP_VER");
game.version = *psf.GetString("APP_VER");
}
return game;
}

View file

@ -80,8 +80,8 @@ public:
if (selected == &openSfoViewer) {
PSF psf;
if (psf.open(m_games[itemID].path + "/sce_sys/param.sfo", {})) {
int rows = psf.map_strings.size() + psf.map_integers.size();
if (psf.Open(std::filesystem::path(m_games[itemID].path) / "sce_sys" / "param.sfo")) {
int rows = psf.GetEntries().size();
QTableWidget* tableWidget = new QTableWidget(rows, 2);
tableWidget->setAttribute(Qt::WA_DeleteOnClose);
connect(widget->parent(), &QWidget::destroyed, tableWidget,
@ -90,23 +90,33 @@ public:
tableWidget->verticalHeader()->setVisible(false); // Hide vertical header
int row = 0;
for (const auto& pair : psf.map_strings) {
for (const auto& entry : psf.GetEntries()) {
QTableWidgetItem* keyItem =
new QTableWidgetItem(QString::fromStdString(pair.first));
QTableWidgetItem* valueItem =
new QTableWidgetItem(QString::fromStdString(pair.second));
new QTableWidgetItem(QString::fromStdString(entry.key));
QTableWidgetItem* valueItem;
switch (entry.param_fmt) {
case PSFEntryFmt::Binary: {
tableWidget->setItem(row, 0, keyItem);
tableWidget->setItem(row, 1, valueItem);
keyItem->setFlags(keyItem->flags() & ~Qt::ItemIsEditable);
valueItem->setFlags(valueItem->flags() & ~Qt::ItemIsEditable);
row++;
}
for (const auto& pair : psf.map_integers) {
QTableWidgetItem* keyItem =
new QTableWidgetItem(QString::fromStdString(pair.first));
QTableWidgetItem* valueItem = new QTableWidgetItem(
QString("0x").append(QString::number(pair.second, 16)));
const auto bin = *psf.GetBinary(entry.key);
std::string text;
text.reserve(bin.size() * 2);
for (const auto& c : bin) {
static constexpr char hex[] = "0123456789ABCDEF";
text.push_back(hex[c >> 4 & 0xF]);
text.push_back(hex[c & 0xF]);
}
valueItem = new QTableWidgetItem(QString::fromStdString(text));
} break;
case PSFEntryFmt::Text: {
auto text = *psf.GetString(entry.key);
valueItem = new QTableWidgetItem(QString::fromStdString(std::string{text}));
} break;
case PSFEntryFmt::Integer: {
auto integer = *psf.GetInteger(entry.key);
valueItem =
new QTableWidgetItem(QString("0x") + QString::number(integer, 16));
} break;
}
tableWidget->setItem(row, 0, keyItem);
tableWidget->setItem(row, 1, valueItem);

View file

@ -509,6 +509,10 @@ void MainWindow::StartGame() {
if (gamePath != "") {
AddRecentFiles(gamePath);
Core::Emulator emulator;
if (!std::filesystem::exists(gamePath.toUtf8().constData())) {
QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found")));
return;
}
emulator.Run(gamePath.toUtf8().constData());
}
}
@ -610,6 +614,11 @@ void MainWindow::BootGame() {
path = std::filesystem::path(fileNames[0].toStdWString());
#endif
Core::Emulator emulator;
if (!std::filesystem::exists(path)) {
QMessageBox::critical(nullptr, tr("Run Game"),
QString(tr("Eboot.bin file not found")));
return;
}
emulator.Run(path);
}
}
@ -627,9 +636,9 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
QMessageBox msgBox;
msgBox.setWindowTitle(tr("PKG Extraction"));
psf.open("", pkg.sfo);
psf.Open(pkg.sfo);
std::string content_id = psf.GetString("CONTENT_ID");
std::string content_id{*psf.GetString("CONTENT_ID")};
std::string entitlement_label = Common::SplitString(content_id, '-')[2];
auto addon_extract_path = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) /
@ -638,9 +647,11 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
auto category = psf.GetString("CATEGORY");
if (pkgType.contains("PATCH")) {
QString pkg_app_version = QString::fromStdString(psf.GetString("APP_VER"));
psf.open(extract_path.string() + "/sce_sys/param.sfo", {});
QString game_app_version = QString::fromStdString(psf.GetString("APP_VER"));
QString pkg_app_version =
QString::fromStdString(std::string{*psf.GetString("APP_VER")});
psf.Open(extract_path / "sce_sys" / "param.sfo");
QString game_app_version =
QString::fromStdString(std::string{*psf.GetString("APP_VER")});
double appD = game_app_version.toDouble();
double pkgD = pkg_app_version.toDouble();
if (pkgD == appD) {
@ -915,6 +926,10 @@ void MainWindow::CreateRecentGameActions() {
QString gamePath = action->text();
AddRecentFiles(gamePath); // Update the list.
Core::Emulator emulator;
if (!std::filesystem::exists(gamePath.toUtf8().constData())) {
QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found")));
return;
}
emulator.Run(gamePath.toUtf8().constData());
});
}

View file

@ -109,12 +109,12 @@ void PKGViewer::ProcessPKGInfo() {
path = std::filesystem::path(m_pkg_list[i].toStdWString());
#endif
package.Open(path);
psf.open("", package.sfo);
QString title_name = QString::fromStdString(psf.GetString("TITLE"));
QString title_id = QString::fromStdString(psf.GetString("TITLE_ID"));
QString app_type = game_list_util.GetAppType(psf.GetInteger("APP_TYPE"));
QString app_version = QString::fromStdString(psf.GetString("APP_VER"));
QString title_category = QString::fromStdString(psf.GetString("CATEGORY"));
psf.Open(package.sfo);
QString title_name = QString::fromStdString(std::string{*psf.GetString("TITLE")});
QString title_id = QString::fromStdString(std::string{*psf.GetString("TITLE_ID")});
QString app_type = game_list_util.GetAppType(*psf.GetInteger("APP_TYPE"));
QString app_version = QString::fromStdString(std::string{*psf.GetString("APP_VER")});
QString title_category = QString::fromStdString(std::string{*psf.GetString("CATEGORY")});
QString pkg_size = game_list_util.FormatSize(package.GetPkgHeader().pkg_size);
pkg_content_flag = package.GetPkgHeader().pkg_content_flags;
QString flagss = "";
@ -126,7 +126,7 @@ void PKGViewer::ProcessPKGInfo() {
}
}
u32 fw_int = psf.GetInteger("SYSTEM_VER");
u32 fw_int = *psf.GetInteger("SYSTEM_VER");
QString fw = QString::number(fw_int, 16);
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
: fw.left(3).insert(1, '.');

View file

@ -33,7 +33,6 @@ private:
PKGHeader pkgheader;
PKGEntry entry;
PSFHeader header;
PSFEntry psfentry;
char pkgTitleID[9];
std::vector<u8> pkg;
u64 pkgSize = 0;

View file

@ -4,6 +4,8 @@
#include <QCompleter>
#include <QDirIterator>
#include "common/logging/backend.h"
#include "common/logging/filter.h"
#include "main_window.h"
#include "settings_dialog.h"
#include "ui_settings_dialog.h"
@ -78,6 +80,11 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
Config::setDefaultValues();
LoadValuesFromConfig();
}
if (Common::Log::IsActive()) {
Common::Log::Filter filter;
filter.ParseFilterString(Config::getLogFilter());
Common::Log::SetGlobalFilter(filter);
}
});
ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save"));

View file

@ -21,7 +21,7 @@
<message>
<location filename="../about_dialog.ui" line="99"/>
<source>This software should not be used to play games you have not legally obtained.</source>
<translation>Este software no debe utilizarse para jugar juegos que no hayas obtenido legalmente.</translation>
<translation>Este software no debe utilizarse para jugar juegos que hayas obtenido ilegalmente.</translation>
</message>
</context>
<context>
@ -118,7 +118,7 @@
<message>
<location filename="../gui_context_menus.h" line="61"/>
<source>Copy Serial</source>
<translation>Copiar serial</translation>
<translation>Copiar número de serie</translation>
</message>
<message>
<location filename="../gui_context_menus.h" line="62"/>
@ -296,7 +296,7 @@
<message>
<location filename="../main_window_ui.h" line="355"/>
<source>Settings</source>
<translation>Configuraciones</translation>
<translation>Configuración</translation>
</message>
<message>
<location filename="../main_window_ui.h" line="356"/>
@ -341,7 +341,7 @@
<message>
<location filename="../main_window_ui.h" line="364"/>
<source>toolBar</source>
<translation>barra de herramientas</translation>
<translation>Barra de herramientas</translation>
</message>
</context>
<context>
@ -365,7 +365,7 @@
<message>
<location filename="../settings_dialog.ui" line="29"/>
<source>Settings</source>
<translation>Configuraciones</translation>
<translation>Configuración</translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="67"/>
@ -538,7 +538,7 @@
<message>
<location filename="../main_window.cpp" line="392"/>
<source>All Patches available for all games have been downloaded.</source>
<translation>Todos los parches disponibles para todos los juegos han sido descargados.</translation>
<translation>Todos los parches disponibles han sido descargados para todos los juegos.</translation>
</message>
<message>
<location filename="../main_window.cpp" line="549"/>
@ -648,7 +648,7 @@
<message>
<location filename="../main_window.cpp" line="725"/>
<source>File doesn't appear to be a valid PKG file</source>
<translation>El archivo no parece ser un archivo PKG válido</translation>
<translation>El archivo parece no ser un archivo PKG válido</translation>
</message>
</context>
<context>
@ -671,7 +671,7 @@
<message>
<location filename="../cheats_patches.cpp" line="79"/>
<source>Serial: </source>
<translation>Serie: </translation>
<translation>Número de serie: </translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="83"/>
@ -711,7 +711,7 @@
<message>
<location filename="../cheats_patches.cpp" line="170"/>
<source>You can delete the cheats you don't want after downloading them.</source>
<translation>Puedes eliminar los trucos que no quieras después de descargarlos.</translation>
<translation>Puedes eliminar los trucos que no quieras una vez descargados.</translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="178"/>
@ -741,7 +741,7 @@
<message>
<location filename="../cheats_patches.cpp" line="257"/>
<source>Patches</source>
<translation>Parche</translation>
<translation>Parches</translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="278"/>
@ -761,7 +761,7 @@
<message>
<location filename="../cheats_patches.cpp" line="316"/>
<source>No patch file found for the current serial.</source>
<translation>No se encontró ningún archivo de parche para la serie actual.</translation>
<translation>No se encontró ningún archivo de parche para el número de serie actual.</translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="323"/>
@ -937,7 +937,7 @@
<message>
<location filename="../game_list_frame.cpp" line="34"/>
<source>Icon</source>
<translation>Ícono</translation>
<translation>Icono</translation>
</message>
<message>
<location filename="../game_list_frame.cpp" line="34"/>
@ -947,7 +947,7 @@
<message>
<location filename="../game_list_frame.cpp" line="34"/>
<source>Serial</source>
<translation>Serie</translation>
<translation>Numero de serie</translation>
</message>
<message>
<location filename="../game_list_frame.cpp" line="34"/>

View file

@ -7,53 +7,157 @@ namespace Shader::Gcn {
void Translator::EmitDataShare(const GcnInst& inst) {
switch (inst.opcode) {
case Opcode::DS_SWIZZLE_B32:
return DS_SWIZZLE_B32(inst);
case Opcode::DS_READ_B32:
return DS_READ(32, false, false, false, inst);
case Opcode::DS_READ2ST64_B32:
return DS_READ(32, false, true, true, inst);
case Opcode::DS_READ_B64:
return DS_READ(64, false, false, false, inst);
case Opcode::DS_READ2_B32:
return DS_READ(32, false, true, false, inst);
case Opcode::DS_READ2_B64:
return DS_READ(64, false, true, false, inst);
case Opcode::DS_WRITE_B32:
return DS_WRITE(32, false, false, false, inst);
case Opcode::DS_WRITE2ST64_B32:
return DS_WRITE(32, false, true, true, inst);
case Opcode::DS_WRITE_B64:
return DS_WRITE(64, false, false, false, inst);
case Opcode::DS_WRITE2_B32:
return DS_WRITE(32, false, true, false, inst);
case Opcode::DS_WRITE2_B64:
return DS_WRITE(64, false, true, false, inst);
// DS
case Opcode::DS_ADD_U32:
return DS_ADD_U32(inst, false);
case Opcode::DS_MIN_U32:
return DS_MIN_U32(inst, false, false);
case Opcode::DS_MIN_I32:
return DS_MIN_U32(inst, true, false);
case Opcode::DS_MAX_U32:
return DS_MAX_U32(inst, false, false);
case Opcode::DS_MAX_I32:
return DS_MAX_U32(inst, true, false);
case Opcode::DS_MIN_U32:
return DS_MIN_U32(inst, false, false);
case Opcode::DS_MAX_U32:
return DS_MAX_U32(inst, false, false);
case Opcode::DS_WRITE_B32:
return DS_WRITE(32, false, false, false, inst);
case Opcode::DS_WRITE2_B32:
return DS_WRITE(32, false, true, false, inst);
case Opcode::DS_WRITE2ST64_B32:
return DS_WRITE(32, false, true, true, inst);
case Opcode::DS_ADD_RTN_U32:
return DS_ADD_U32(inst, true);
case Opcode::DS_MIN_RTN_U32:
return DS_MIN_U32(inst, false, true);
case Opcode::DS_MAX_RTN_U32:
return DS_MAX_U32(inst, false, true);
case Opcode::DS_APPEND:
return DS_APPEND(inst);
case Opcode::DS_SWIZZLE_B32:
return DS_SWIZZLE_B32(inst);
case Opcode::DS_READ_B32:
return DS_READ(32, false, false, false, inst);
case Opcode::DS_READ2_B32:
return DS_READ(32, false, true, false, inst);
case Opcode::DS_READ2ST64_B32:
return DS_READ(32, false, true, true, inst);
case Opcode::DS_CONSUME:
return DS_CONSUME(inst);
case Opcode::DS_APPEND:
return DS_APPEND(inst);
case Opcode::DS_WRITE_B64:
return DS_WRITE(64, false, false, false, inst);
case Opcode::DS_WRITE2_B64:
return DS_WRITE(64, false, true, false, inst);
case Opcode::DS_READ_B64:
return DS_READ(64, false, false, false, inst);
case Opcode::DS_READ2_B64:
return DS_READ(64, false, true, false, inst);
default:
LogMissingOpcode(inst);
}
}
// SOPP
void Translator::S_BARRIER() {
ir.Barrier();
}
// VOP2
void Translator::V_READFIRSTLANE_B32(const GcnInst& inst) {
const IR::ScalarReg dst{inst.dst[0].code};
const IR::U32 value{GetSrc(inst.src[0])};
if (info.stage != Stage::Compute) {
SetDst(inst.dst[0], value);
} else {
SetDst(inst.dst[0], ir.ReadFirstLane(value));
}
}
void Translator::V_READLANE_B32(const GcnInst& inst) {
const IR::ScalarReg dst{inst.dst[0].code};
const IR::U32 value{GetSrc(inst.src[0])};
const IR::U32 lane{GetSrc(inst.src[1])};
ir.SetScalarReg(dst, ir.ReadLane(value, lane));
}
void Translator::V_WRITELANE_B32(const GcnInst& inst) {
const IR::VectorReg dst{inst.dst[0].code};
const IR::U32 value{GetSrc(inst.src[0])};
const IR::U32 lane{GetSrc(inst.src[1])};
const IR::U32 old_value{GetSrc(inst.dst[0])};
ir.SetVectorReg(dst, ir.WriteLane(old_value, value, lane));
}
// DS
void Translator::DS_ADD_U32(const GcnInst& inst, bool rtn) {
const IR::U32 addr{GetSrc(inst.src[0])};
const IR::U32 data{GetSrc(inst.src[1])};
const IR::U32 offset = ir.Imm32(u32(inst.control.ds.offset0));
const IR::U32 addr_offset = ir.IAdd(addr, offset);
const IR::Value original_val = ir.SharedAtomicIAdd(addr_offset, data);
if (rtn) {
SetDst(inst.dst[0], IR::U32{original_val});
}
}
void Translator::DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn) {
const IR::U32 addr{GetSrc(inst.src[0])};
const IR::U32 data{GetSrc(inst.src[1])};
const IR::U32 offset = ir.Imm32(u32(inst.control.ds.offset0));
const IR::U32 addr_offset = ir.IAdd(addr, offset);
const IR::Value original_val = ir.SharedAtomicIMin(addr_offset, data, is_signed);
if (rtn) {
SetDst(inst.dst[0], IR::U32{original_val});
}
}
void Translator::DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn) {
const IR::U32 addr{GetSrc(inst.src[0])};
const IR::U32 data{GetSrc(inst.src[1])};
const IR::U32 offset = ir.Imm32(u32(inst.control.ds.offset0));
const IR::U32 addr_offset = ir.IAdd(addr, offset);
const IR::Value original_val = ir.SharedAtomicIMax(addr_offset, data, is_signed);
if (rtn) {
SetDst(inst.dst[0], IR::U32{original_val});
}
}
void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64,
const GcnInst& inst) {
const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))};
const IR::VectorReg data0{inst.src[1].code};
const IR::VectorReg data1{inst.src[2].code};
if (is_pair) {
const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1);
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj)));
if (bit_size == 32) {
ir.WriteShared(32, ir.GetVectorReg(data0), addr0);
} else {
ir.WriteShared(
64, ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)),
addr0);
}
const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj)));
if (bit_size == 32) {
ir.WriteShared(32, ir.GetVectorReg(data1), addr1);
} else {
ir.WriteShared(
64, ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1)),
addr1);
}
} else if (bit_size == 64) {
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0)));
const IR::Value data =
ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1));
ir.WriteShared(bit_size, data, addr0);
} else {
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0)));
ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0);
}
}
void Translator::DS_SWIZZLE_B32(const GcnInst& inst) {
const u8 offset0 = inst.control.ds.offset0;
const u8 offset1 = inst.control.ds.offset1;
@ -102,103 +206,6 @@ void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride
}
}
void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64,
const GcnInst& inst) {
const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))};
const IR::VectorReg data0{inst.src[1].code};
const IR::VectorReg data1{inst.src[2].code};
if (is_pair) {
const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1);
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj)));
if (bit_size == 32) {
ir.WriteShared(32, ir.GetVectorReg(data0), addr0);
} else {
ir.WriteShared(
64, ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)),
addr0);
}
const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj)));
if (bit_size == 32) {
ir.WriteShared(32, ir.GetVectorReg(data1), addr1);
} else {
ir.WriteShared(
64, ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1)),
addr1);
}
} else if (bit_size == 64) {
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0)));
const IR::Value data =
ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1));
ir.WriteShared(bit_size, data, addr0);
} else {
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0)));
ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0);
}
}
void Translator::DS_ADD_U32(const GcnInst& inst, bool rtn) {
const IR::U32 addr{GetSrc(inst.src[0])};
const IR::U32 data{GetSrc(inst.src[1])};
const IR::U32 offset = ir.Imm32(u32(inst.control.ds.offset0));
const IR::U32 addr_offset = ir.IAdd(addr, offset);
const IR::Value original_val = ir.SharedAtomicIAdd(addr_offset, data);
if (rtn) {
SetDst(inst.dst[0], IR::U32{original_val});
}
}
void Translator::DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn) {
const IR::U32 addr{GetSrc(inst.src[0])};
const IR::U32 data{GetSrc(inst.src[1])};
const IR::U32 offset = ir.Imm32(u32(inst.control.ds.offset0));
const IR::U32 addr_offset = ir.IAdd(addr, offset);
const IR::Value original_val = ir.SharedAtomicIMin(addr_offset, data, is_signed);
if (rtn) {
SetDst(inst.dst[0], IR::U32{original_val});
}
}
void Translator::DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn) {
const IR::U32 addr{GetSrc(inst.src[0])};
const IR::U32 data{GetSrc(inst.src[1])};
const IR::U32 offset = ir.Imm32(u32(inst.control.ds.offset0));
const IR::U32 addr_offset = ir.IAdd(addr, offset);
const IR::Value original_val = ir.SharedAtomicIMax(addr_offset, data, is_signed);
if (rtn) {
SetDst(inst.dst[0], IR::U32{original_val});
}
}
void Translator::S_BARRIER() {
ir.Barrier();
}
void Translator::V_READFIRSTLANE_B32(const GcnInst& inst) {
const IR::ScalarReg dst{inst.dst[0].code};
const IR::U32 value{GetSrc(inst.src[0])};
if (info.stage != Stage::Compute) {
SetDst(inst.dst[0], value);
} else {
SetDst(inst.dst[0], ir.ReadFirstLane(value));
}
}
void Translator::V_READLANE_B32(const GcnInst& inst) {
const IR::ScalarReg dst{inst.dst[0].code};
const IR::U32 value{GetSrc(inst.src[0])};
const IR::U32 lane{GetSrc(inst.src[1])};
ir.SetScalarReg(dst, ir.ReadLane(value, lane));
}
void Translator::V_WRITELANE_B32(const GcnInst& inst) {
const IR::VectorReg dst{inst.dst[0].code};
const IR::U32 value{GetSrc(inst.src[0])};
const IR::U32 lane{GetSrc(inst.src[1])};
const IR::U32 old_value{GetSrc(inst.dst[0])};
ir.SetVectorReg(dst, ir.WriteLane(old_value, value, lane));
}
void Translator::DS_APPEND(const GcnInst& inst) {
const u32 inst_offset = inst.control.ds.offset0;
const IR::U32 gds_offset = ir.IAdd(ir.GetM0(), ir.Imm32(inst_offset));

View file

@ -17,79 +17,83 @@ void Translator::EmitScalarAlu(const GcnInst& inst) {
}
default:
switch (inst.opcode) {
case Opcode::S_MOV_B32:
return S_MOV(inst);
case Opcode::S_MUL_I32:
return S_MUL_I32(inst);
case Opcode::S_AND_SAVEEXEC_B64:
return S_AND_SAVEEXEC_B64(inst);
case Opcode::S_MOV_B64:
return S_MOV_B64(inst);
case Opcode::S_OR_B64:
return S_OR_B64(NegateMode::None, false, inst);
case Opcode::S_NOR_B64:
return S_OR_B64(NegateMode::Result, false, inst);
case Opcode::S_XOR_B64:
return S_OR_B64(NegateMode::None, true, inst);
case Opcode::S_XNOR_B64:
return S_OR_B64(NegateMode::Result, true, inst);
case Opcode::S_ORN2_B64:
return S_OR_B64(NegateMode::Src1, false, inst);
case Opcode::S_AND_B64:
return S_AND_B64(NegateMode::None, inst);
case Opcode::S_NAND_B64:
return S_AND_B64(NegateMode::Result, inst);
case Opcode::S_ANDN2_B64:
return S_AND_B64(NegateMode::Src1, inst);
case Opcode::S_NOT_B64:
return S_NOT_B64(inst);
// SOP2
case Opcode::S_ADD_U32:
return S_ADD_U32(inst);
case Opcode::S_SUB_U32:
return S_SUB_U32(inst);
case Opcode::S_ADD_I32:
return S_ADD_I32(inst);
case Opcode::S_AND_B32:
return S_AND_B32(NegateMode::None, inst);
case Opcode::S_NAND_B32:
return S_AND_B32(NegateMode::Result, inst);
case Opcode::S_ANDN2_B32:
return S_AND_B32(NegateMode::Src1, inst);
case Opcode::S_ASHR_I32:
return S_ASHR_I32(inst);
case Opcode::S_OR_B32:
return S_OR_B32(inst);
case Opcode::S_XOR_B32:
return S_XOR_B32(inst);
case Opcode::S_LSHL_B32:
return S_LSHL_B32(inst);
case Opcode::S_LSHR_B32:
return S_LSHR_B32(inst);
case Opcode::S_SUB_I32:
return S_SUB_U32(inst);
case Opcode::S_ADDC_U32:
return S_ADDC_U32(inst);
case Opcode::S_MIN_I32:
return S_MIN_U32(true, inst);
case Opcode::S_MIN_U32:
return S_MIN_U32(false, inst);
case Opcode::S_MAX_I32:
return S_MAX_U32(true, inst);
case Opcode::S_MAX_U32:
return S_MAX_U32(false, inst);
case Opcode::S_CSELECT_B32:
return S_CSELECT_B32(inst);
case Opcode::S_CSELECT_B64:
return S_CSELECT_B64(inst);
case Opcode::S_BFE_U32:
return S_BFE_U32(inst);
case Opcode::S_AND_B32:
return S_AND_B32(NegateMode::None, inst);
case Opcode::S_AND_B64:
return S_AND_B64(NegateMode::None, inst);
case Opcode::S_OR_B32:
return S_OR_B32(inst);
case Opcode::S_OR_B64:
return S_OR_B64(NegateMode::None, false, inst);
case Opcode::S_XOR_B32:
return S_XOR_B32(inst);
case Opcode::S_XOR_B64:
return S_OR_B64(NegateMode::None, true, inst);
case Opcode::S_ANDN2_B32:
return S_AND_B32(NegateMode::Src1, inst);
case Opcode::S_ANDN2_B64:
return S_AND_B64(NegateMode::Src1, inst);
case Opcode::S_ORN2_B64:
return S_OR_B64(NegateMode::Src1, false, inst);
case Opcode::S_NAND_B32:
return S_AND_B32(NegateMode::Result, inst);
case Opcode::S_NAND_B64:
return S_AND_B64(NegateMode::Result, inst);
case Opcode::S_NOR_B64:
return S_OR_B64(NegateMode::Result, false, inst);
case Opcode::S_XNOR_B64:
return S_OR_B64(NegateMode::Result, true, inst);
case Opcode::S_LSHL_B32:
return S_LSHL_B32(inst);
case Opcode::S_LSHR_B32:
return S_LSHR_B32(inst);
case Opcode::S_ASHR_I32:
return S_ASHR_I32(inst);
case Opcode::S_BFM_B32:
return S_BFM_B32(inst);
case Opcode::S_BREV_B32:
return S_BREV_B32(inst);
case Opcode::S_ADD_U32:
return S_ADD_U32(inst);
case Opcode::S_ADDC_U32:
return S_ADDC_U32(inst);
case Opcode::S_SUB_U32:
case Opcode::S_SUB_I32:
return S_SUB_U32(inst);
case Opcode::S_MIN_U32:
return S_MIN_U32(false, inst);
case Opcode::S_MIN_I32:
return S_MIN_U32(true, inst);
case Opcode::S_MAX_U32:
return S_MAX_U32(false, inst);
case Opcode::S_MAX_I32:
return S_MAX_U32(true, inst);
case Opcode::S_MUL_I32:
return S_MUL_I32(inst);
case Opcode::S_BFE_U32:
return S_BFE_U32(inst);
case Opcode::S_ABSDIFF_I32:
return S_ABSDIFF_I32(inst);
// SOP1
case Opcode::S_MOV_B32:
return S_MOV(inst);
case Opcode::S_MOV_B64:
return S_MOV_B64(inst);
case Opcode::S_NOT_B64:
return S_NOT_B64(inst);
case Opcode::S_WQM_B64:
break;
case Opcode::S_BREV_B32:
return S_BREV_B32(inst);
case Opcode::S_AND_SAVEEXEC_B64:
return S_AND_SAVEEXEC_B64(inst);
default:
LogMissingOpcode(inst);
}
@ -131,6 +135,7 @@ void Translator::EmitSOPC(const GcnInst& inst) {
void Translator::EmitSOPK(const GcnInst& inst) {
switch (inst.opcode) {
// SOPK
case Opcode::S_MOVK_I32:
return S_MOVK(inst);
@ -169,169 +174,78 @@ void Translator::EmitSOPK(const GcnInst& inst) {
}
}
void Translator::S_MOVK(const GcnInst& inst) {
const auto simm16 = inst.control.sopk.simm;
if (simm16 & (1 << 15)) {
// TODO: need to verify the case of imm sign extension
UNREACHABLE();
}
SetDst(inst.dst[0], ir.Imm32(simm16));
// SOP2
void Translator::S_ADD_U32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
SetDst(inst.dst[0], ir.IAdd(src0, src1));
// TODO: Carry out
ir.SetScc(ir.Imm1(false));
}
void Translator::S_ADDK_I32(const GcnInst& inst) {
const s32 simm16 = inst.control.sopk.simm;
SetDst(inst.dst[0], ir.IAdd(GetSrc(inst.dst[0]), ir.Imm32(simm16)));
void Translator::S_SUB_U32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
SetDst(inst.dst[0], ir.ISub(src0, src1));
// TODO: Carry out
ir.SetScc(ir.Imm1(false));
}
void Translator::S_MULK_I32(const GcnInst& inst) {
const s32 simm16 = inst.control.sopk.simm;
SetDst(inst.dst[0], ir.IMul(GetSrc(inst.dst[0]), ir.Imm32(simm16)));
void Translator::S_ADD_I32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
SetDst(inst.dst[0], ir.IAdd(src0, src1));
// TODO: Overflow flag
}
void Translator::S_MOV(const GcnInst& inst) {
SetDst(inst.dst[0], GetSrc(inst.src[0]));
void Translator::S_ADDC_U32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 carry{ir.Select(ir.GetScc(), ir.Imm32(1U), ir.Imm32(0U))};
SetDst(inst.dst[0], ir.IAdd(ir.IAdd(src0, src1), carry));
}
void Translator::S_MUL_I32(const GcnInst& inst) {
SetDst(inst.dst[0], ir.IMul(GetSrc(inst.src[0]), GetSrc(inst.src[1])));
void Translator::S_MIN_U32(bool is_signed, const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 result = ir.IMin(src0, src1, is_signed);
SetDst(inst.dst[0], result);
ir.SetScc(ir.IEqual(result, src0));
}
void Translator::S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst) {
const IR::U32 lhs = GetSrc(inst.src[0]);
const IR::U32 rhs = GetSrc(inst.src[1]);
const IR::U1 result = [&] {
switch (cond) {
case ConditionOp::EQ:
return ir.IEqual(lhs, rhs);
case ConditionOp::LG:
return ir.INotEqual(lhs, rhs);
case ConditionOp::GT:
return ir.IGreaterThan(lhs, rhs, is_signed);
case ConditionOp::GE:
return ir.IGreaterThanEqual(lhs, rhs, is_signed);
case ConditionOp::LT:
return ir.ILessThan(lhs, rhs, is_signed);
case ConditionOp::LE:
return ir.ILessThanEqual(lhs, rhs, is_signed);
default:
UNREACHABLE();
}
}();
ir.SetScc(result);
void Translator::S_MAX_U32(bool is_signed, const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 result = ir.IMax(src0, src1, is_signed);
SetDst(inst.dst[0], result);
ir.SetScc(ir.IEqual(result, src0));
}
void Translator::S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst) {
const s32 simm16 = inst.control.sopk.simm;
const IR::U32 lhs = GetSrc(inst.dst[0]);
const IR::U32 rhs = ir.Imm32(simm16);
const IR::U1 result = [&] {
switch (cond) {
case ConditionOp::EQ:
return ir.IEqual(lhs, rhs);
case ConditionOp::LG:
return ir.INotEqual(lhs, rhs);
case ConditionOp::GT:
return ir.IGreaterThan(lhs, rhs, is_signed);
case ConditionOp::GE:
return ir.IGreaterThanEqual(lhs, rhs, is_signed);
case ConditionOp::LT:
return ir.ILessThan(lhs, rhs, is_signed);
case ConditionOp::LE:
return ir.ILessThanEqual(lhs, rhs, is_signed);
default:
UNREACHABLE();
}
}();
ir.SetScc(result);
void Translator::S_CSELECT_B32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
SetDst(inst.dst[0], IR::U32{ir.Select(ir.GetScc(), src0, src1)});
}
void Translator::S_AND_SAVEEXEC_B64(const GcnInst& inst) {
// This instruction normally operates on 64-bit data (EXEC, VCC, SGPRs)
// However here we flatten it to 1-bit EXEC and 1-bit VCC. For the destination
// SGPR we have a special IR opcode for SPGRs that act as thread masks.
const IR::U1 exec{ir.GetExec()};
const IR::U1 src = [&] {
switch (inst.src[0].field) {
case OperandField::VccLo:
return ir.GetVcc();
case OperandField::ScalarGPR:
return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code));
default:
UNREACHABLE();
}
}();
switch (inst.dst[0].field) {
case OperandField::ScalarGPR:
ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), exec);
break;
case OperandField::VccLo:
ir.SetVcc(exec);
break;
default:
UNREACHABLE();
}
// Update EXEC.
const IR::U1 result = ir.LogicalAnd(exec, src);
ir.SetExec(result);
ir.SetScc(result);
}
void Translator::S_MOV_B64(const GcnInst& inst) {
const IR::U1 src = [&] {
switch (inst.src[0].field) {
void Translator::S_CSELECT_B64(const GcnInst& inst) {
const auto get_src = [&](const InstOperand& operand) {
switch (operand.field) {
case OperandField::VccLo:
return ir.GetVcc();
case OperandField::ExecLo:
return ir.GetExec();
case OperandField::ScalarGPR:
return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code));
return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code));
case OperandField::ConstZero:
return ir.Imm1(false);
default:
UNREACHABLE();
}
}();
switch (inst.dst[0].field) {
case OperandField::ScalarGPR:
ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), src);
break;
case OperandField::ExecLo:
ir.SetExec(src);
break;
case OperandField::VccLo:
ir.SetVcc(src);
break;
default:
UNREACHABLE();
}
}
void Translator::S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst) {
const auto get_src = [&](const InstOperand& operand) {
switch (operand.field) {
case OperandField::ExecLo:
return ir.GetExec();
case OperandField::VccLo:
return ir.GetVcc();
case OperandField::ScalarGPR:
return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code));
default:
UNREACHABLE();
}
};
const IR::U1 src0{get_src(inst.src[0])};
IR::U1 src1{get_src(inst.src[1])};
if (negate == NegateMode::Src1) {
src1 = ir.LogicalNot(src1);
}
IR::U1 result = is_xor ? ir.LogicalXor(src0, src1) : ir.LogicalOr(src0, src1);
if (negate == NegateMode::Result) {
result = ir.LogicalNot(result);
}
ir.SetScc(result);
const IR::U1 src1{get_src(inst.src[1])};
const IR::U1 result{ir.Select(ir.GetScc(), src0, src1)};
switch (inst.dst[0].field) {
case OperandField::VccLo:
ir.SetVcc(result);
@ -344,6 +258,20 @@ void Translator::S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst) {
}
}
void Translator::S_AND_B32(NegateMode negate, const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
IR::U32 src1{GetSrc(inst.src[1])};
if (negate == NegateMode::Src1) {
src1 = ir.BitwiseNot(src1);
}
IR::U32 result{ir.BitwiseAnd(src0, src1)};
if (negate == NegateMode::Result) {
result = ir.BitwiseNot(result);
}
SetDst(inst.dst[0], result);
ir.SetScc(ir.INotEqual(result, ir.Imm32(0)));
}
void Translator::S_AND_B64(NegateMode negate, const GcnInst& inst) {
const auto get_src = [&](const InstOperand& operand) {
switch (operand.field) {
@ -382,35 +310,6 @@ void Translator::S_AND_B64(NegateMode negate, const GcnInst& inst) {
}
}
void Translator::S_ADD_I32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
SetDst(inst.dst[0], ir.IAdd(src0, src1));
// TODO: Overflow flag
}
void Translator::S_AND_B32(NegateMode negate, const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
IR::U32 src1{GetSrc(inst.src[1])};
if (negate == NegateMode::Src1) {
src1 = ir.BitwiseNot(src1);
}
IR::U32 result{ir.BitwiseAnd(src0, src1)};
if (negate == NegateMode::Result) {
result = ir.BitwiseNot(result);
}
SetDst(inst.dst[0], result);
ir.SetScc(ir.INotEqual(result, ir.Imm32(0)));
}
void Translator::S_ASHR_I32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 result{ir.ShiftRightArithmetic(src0, src1)};
SetDst(inst.dst[0], result);
ir.SetScc(ir.INotEqual(result, ir.Imm32(0)));
}
void Translator::S_OR_B32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
@ -419,46 +318,30 @@ void Translator::S_OR_B32(const GcnInst& inst) {
ir.SetScc(ir.INotEqual(result, ir.Imm32(0)));
}
void Translator::S_XOR_B32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 result{ir.BitwiseXor(src0, src1)};
SetDst(inst.dst[0], result);
ir.SetScc(ir.INotEqual(result, ir.Imm32(0)));
}
void Translator::S_LSHR_B32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 result{ir.ShiftRightLogical(src0, src1)};
SetDst(inst.dst[0], result);
ir.SetScc(ir.INotEqual(result, ir.Imm32(0)));
}
void Translator::S_CSELECT_B32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
SetDst(inst.dst[0], IR::U32{ir.Select(ir.GetScc(), src0, src1)});
}
void Translator::S_CSELECT_B64(const GcnInst& inst) {
void Translator::S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst) {
const auto get_src = [&](const InstOperand& operand) {
switch (operand.field) {
case OperandField::VccLo:
return ir.GetVcc();
case OperandField::ExecLo:
return ir.GetExec();
case OperandField::VccLo:
return ir.GetVcc();
case OperandField::ScalarGPR:
return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code));
case OperandField::ConstZero:
return ir.Imm1(false);
default:
UNREACHABLE();
}
};
const IR::U1 src0{get_src(inst.src[0])};
const IR::U1 src1{get_src(inst.src[1])};
const IR::U1 result{ir.Select(ir.GetScc(), src0, src1)};
IR::U1 src1{get_src(inst.src[1])};
if (negate == NegateMode::Src1) {
src1 = ir.LogicalNot(src1);
}
IR::U1 result = is_xor ? ir.LogicalXor(src0, src1) : ir.LogicalOr(src0, src1);
if (negate == NegateMode::Result) {
result = ir.LogicalNot(result);
}
ir.SetScc(result);
switch (inst.dst[0].field) {
case OperandField::VccLo:
ir.SetVcc(result);
@ -471,12 +354,10 @@ void Translator::S_CSELECT_B64(const GcnInst& inst) {
}
}
void Translator::S_BFE_U32(const GcnInst& inst) {
void Translator::S_XOR_B32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 offset{ir.BitwiseAnd(src1, ir.Imm32(0x1F))};
const IR::U32 count{ir.BitFieldExtract(src1, ir.Imm32(16), ir.Imm32(7))};
const IR::U32 result{ir.BitFieldExtract(src0, offset, count)};
const IR::U32 result{ir.BitwiseXor(src0, src1)};
SetDst(inst.dst[0], result);
ir.SetScc(ir.INotEqual(result, ir.Imm32(0)));
}
@ -489,6 +370,22 @@ void Translator::S_LSHL_B32(const GcnInst& inst) {
ir.SetScc(ir.INotEqual(result, ir.Imm32(0)));
}
void Translator::S_LSHR_B32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 result{ir.ShiftRightLogical(src0, src1)};
SetDst(inst.dst[0], result);
ir.SetScc(ir.INotEqual(result, ir.Imm32(0)));
}
void Translator::S_ASHR_I32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 result{ir.ShiftRightArithmetic(src0, src1)};
SetDst(inst.dst[0], result);
ir.SetScc(ir.INotEqual(result, ir.Imm32(0)));
}
void Translator::S_BFM_B32(const GcnInst& inst) {
const IR::U32 src0{ir.BitwiseAnd(GetSrc(inst.src[0]), ir.Imm32(0x1F))};
const IR::U32 src1{ir.BitwiseAnd(GetSrc(inst.src[1]), ir.Imm32(0x1F))};
@ -496,6 +393,110 @@ void Translator::S_BFM_B32(const GcnInst& inst) {
SetDst(inst.dst[0], ir.ShiftLeftLogical(mask, src1));
}
void Translator::S_MUL_I32(const GcnInst& inst) {
SetDst(inst.dst[0], ir.IMul(GetSrc(inst.src[0]), GetSrc(inst.src[1])));
}
void Translator::S_BFE_U32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 offset{ir.BitwiseAnd(src1, ir.Imm32(0x1F))};
const IR::U32 count{ir.BitFieldExtract(src1, ir.Imm32(16), ir.Imm32(7))};
const IR::U32 result{ir.BitFieldExtract(src0, offset, count)};
SetDst(inst.dst[0], result);
ir.SetScc(ir.INotEqual(result, ir.Imm32(0)));
}
void Translator::S_ABSDIFF_I32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 result{ir.IAbs(ir.ISub(src0, src1))};
SetDst(inst.dst[0], result);
ir.SetScc(ir.INotEqual(result, ir.Imm32(0)));
}
// SOPK
void Translator::S_MOVK(const GcnInst& inst) {
const auto simm16 = inst.control.sopk.simm;
if (simm16 & (1 << 15)) {
// TODO: need to verify the case of imm sign extension
UNREACHABLE();
}
SetDst(inst.dst[0], ir.Imm32(simm16));
}
void Translator::S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst) {
const s32 simm16 = inst.control.sopk.simm;
const IR::U32 lhs = GetSrc(inst.dst[0]);
const IR::U32 rhs = ir.Imm32(simm16);
const IR::U1 result = [&] {
switch (cond) {
case ConditionOp::EQ:
return ir.IEqual(lhs, rhs);
case ConditionOp::LG:
return ir.INotEqual(lhs, rhs);
case ConditionOp::GT:
return ir.IGreaterThan(lhs, rhs, is_signed);
case ConditionOp::GE:
return ir.IGreaterThanEqual(lhs, rhs, is_signed);
case ConditionOp::LT:
return ir.ILessThan(lhs, rhs, is_signed);
case ConditionOp::LE:
return ir.ILessThanEqual(lhs, rhs, is_signed);
default:
UNREACHABLE();
}
}();
ir.SetScc(result);
}
void Translator::S_ADDK_I32(const GcnInst& inst) {
const s32 simm16 = inst.control.sopk.simm;
SetDst(inst.dst[0], ir.IAdd(GetSrc(inst.dst[0]), ir.Imm32(simm16)));
}
void Translator::S_MULK_I32(const GcnInst& inst) {
const s32 simm16 = inst.control.sopk.simm;
SetDst(inst.dst[0], ir.IMul(GetSrc(inst.dst[0]), ir.Imm32(simm16)));
}
// SOP1
void Translator::S_MOV(const GcnInst& inst) {
SetDst(inst.dst[0], GetSrc(inst.src[0]));
}
void Translator::S_MOV_B64(const GcnInst& inst) {
const IR::U1 src = [&] {
switch (inst.src[0].field) {
case OperandField::VccLo:
return ir.GetVcc();
case OperandField::ExecLo:
return ir.GetExec();
case OperandField::ScalarGPR:
return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code));
case OperandField::ConstZero:
return ir.Imm1(false);
default:
UNREACHABLE();
}
}();
switch (inst.dst[0].field) {
case OperandField::ScalarGPR:
ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), src);
break;
case OperandField::ExecLo:
ir.SetExec(src);
break;
case OperandField::VccLo:
ir.SetVcc(src);
break;
default:
UNREACHABLE();
}
}
void Translator::S_NOT_B64(const GcnInst& inst) {
const auto get_src = [&](const InstOperand& operand) {
switch (operand.field) {
@ -528,22 +529,6 @@ void Translator::S_BREV_B32(const GcnInst& inst) {
SetDst(inst.dst[0], ir.BitReverse(GetSrc(inst.src[0])));
}
void Translator::S_ADD_U32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
SetDst(inst.dst[0], ir.IAdd(src0, src1));
// TODO: Carry out
ir.SetScc(ir.Imm1(false));
}
void Translator::S_SUB_U32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
SetDst(inst.dst[0], ir.ISub(src0, src1));
// TODO: Carry out
ir.SetScc(ir.Imm1(false));
}
void Translator::S_GETPC_B64(u32 pc, const GcnInst& inst) {
// This only really exists to let resource tracking pass know
// there is an inline cbuf.
@ -552,35 +537,63 @@ void Translator::S_GETPC_B64(u32 pc, const GcnInst& inst) {
ir.SetScalarReg(dst + 1, ir.Imm32(0));
}
void Translator::S_ADDC_U32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 carry{ir.Select(ir.GetScc(), ir.Imm32(1U), ir.Imm32(0U))};
SetDst(inst.dst[0], ir.IAdd(ir.IAdd(src0, src1), carry));
void Translator::S_AND_SAVEEXEC_B64(const GcnInst& inst) {
// This instruction normally operates on 64-bit data (EXEC, VCC, SGPRs)
// However here we flatten it to 1-bit EXEC and 1-bit VCC. For the destination
// SGPR we have a special IR opcode for SPGRs that act as thread masks.
const IR::U1 exec{ir.GetExec()};
const IR::U1 src = [&] {
switch (inst.src[0].field) {
case OperandField::VccLo:
return ir.GetVcc();
case OperandField::ScalarGPR:
return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code));
default:
UNREACHABLE();
}
}();
switch (inst.dst[0].field) {
case OperandField::ScalarGPR:
ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), exec);
break;
case OperandField::VccLo:
ir.SetVcc(exec);
break;
default:
UNREACHABLE();
}
// Update EXEC.
const IR::U1 result = ir.LogicalAnd(exec, src);
ir.SetExec(result);
ir.SetScc(result);
}
void Translator::S_MAX_U32(bool is_signed, const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 result = ir.IMax(src0, src1, is_signed);
SetDst(inst.dst[0], result);
ir.SetScc(ir.IEqual(result, src0));
}
// SOPC
void Translator::S_MIN_U32(bool is_signed, const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 result = ir.IMin(src0, src1, is_signed);
SetDst(inst.dst[0], result);
ir.SetScc(ir.IEqual(result, src0));
}
void Translator::S_ABSDIFF_I32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 result{ir.IAbs(ir.ISub(src0, src1))};
SetDst(inst.dst[0], result);
ir.SetScc(ir.INotEqual(result, ir.Imm32(0)));
void Translator::S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst) {
const IR::U32 lhs = GetSrc(inst.src[0]);
const IR::U32 rhs = GetSrc(inst.src[1]);
const IR::U1 result = [&] {
switch (cond) {
case ConditionOp::EQ:
return ir.IEqual(lhs, rhs);
case ConditionOp::LG:
return ir.INotEqual(lhs, rhs);
case ConditionOp::GT:
return ir.IGreaterThan(lhs, rhs, is_signed);
case ConditionOp::GE:
return ir.IGreaterThanEqual(lhs, rhs, is_signed);
case ConditionOp::LT:
return ir.ILessThan(lhs, rhs, is_signed);
case ConditionOp::LE:
return ir.ILessThanEqual(lhs, rhs, is_signed);
default:
UNREACHABLE();
}
}();
ir.SetScc(result);
}
} // namespace Shader::Gcn

View file

@ -9,6 +9,7 @@ static constexpr u32 SQ_SRC_LITERAL = 0xFF;
void Translator::EmitScalarMemory(const GcnInst& inst) {
switch (inst.opcode) {
// SMRD
case Opcode::S_LOAD_DWORDX4:
return S_LOAD_DWORD(4, inst);
case Opcode::S_LOAD_DWORDX8:
@ -30,6 +31,8 @@ void Translator::EmitScalarMemory(const GcnInst& inst) {
}
}
// SMRD
void Translator::S_LOAD_DWORD(int num_dwords, const GcnInst& inst) {
const auto& smrd = inst.control.smrd;
const u32 dword_offset = [&] -> u32 {

View file

@ -61,177 +61,201 @@ public:
// Instruction categories
void EmitPrologue();
void EmitFetch(const GcnInst& inst);
void EmitDataShare(const GcnInst& inst);
void EmitVectorInterpolation(const GcnInst& inst);
void EmitScalarMemory(const GcnInst& inst);
void EmitVectorMemory(const GcnInst& inst);
void EmitExport(const GcnInst& inst);
void EmitFlowControl(u32 pc, const GcnInst& inst);
void EmitScalarAlu(const GcnInst& inst);
void EmitScalarMemory(const GcnInst& inst);
void EmitVectorAlu(const GcnInst& inst);
void EmitVectorInterpolation(const GcnInst& inst);
void EmitDataShare(const GcnInst& inst);
void EmitVectorMemory(const GcnInst& inst);
// Instruction encodings
void EmitSOPC(const GcnInst& inst);
void EmitSOPK(const GcnInst& inst);
// Scalar ALU
void S_MOVK(const GcnInst& inst);
void S_MOV(const GcnInst& inst);
void S_MUL_I32(const GcnInst& inst);
void S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst);
void S_AND_SAVEEXEC_B64(const GcnInst& inst);
void S_MOV_B64(const GcnInst& inst);
void S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst);
void S_AND_B64(NegateMode negate, const GcnInst& inst);
void S_ADD_I32(const GcnInst& inst);
void S_AND_B32(NegateMode negate, const GcnInst& inst);
void S_ASHR_I32(const GcnInst& inst);
void S_OR_B32(const GcnInst& inst);
void S_XOR_B32(const GcnInst& inst);
void S_LSHR_B32(const GcnInst& inst);
void S_CSELECT_B32(const GcnInst& inst);
void S_CSELECT_B64(const GcnInst& inst);
void S_BFE_U32(const GcnInst& inst);
void S_LSHL_B32(const GcnInst& inst);
void S_BFM_B32(const GcnInst& inst);
void S_NOT_B64(const GcnInst& inst);
void S_BREV_B32(const GcnInst& inst);
// SOP2
void S_ADD_U32(const GcnInst& inst);
void S_SUB_U32(const GcnInst& inst);
void S_GETPC_B64(u32 pc, const GcnInst& inst);
void S_ADD_I32(const GcnInst& inst);
void S_ADDC_U32(const GcnInst& inst);
void S_MULK_I32(const GcnInst& inst);
void S_ADDK_I32(const GcnInst& inst);
void S_MAX_U32(bool is_signed, const GcnInst& inst);
void S_MIN_U32(bool is_signed, const GcnInst& inst);
void S_MAX_U32(bool is_signed, const GcnInst& inst);
void S_CSELECT_B32(const GcnInst& inst);
void S_CSELECT_B64(const GcnInst& inst);
void S_AND_B32(NegateMode negate, const GcnInst& inst);
void S_AND_B64(NegateMode negate, const GcnInst& inst);
void S_OR_B32(const GcnInst& inst);
void S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst);
void S_XOR_B32(const GcnInst& inst);
void S_LSHL_B32(const GcnInst& inst);
void S_LSHR_B32(const GcnInst& inst);
void S_ASHR_I32(const GcnInst& inst);
void S_BFM_B32(const GcnInst& inst);
void S_MUL_I32(const GcnInst& inst);
void S_BFE_U32(const GcnInst& inst);
void S_ABSDIFF_I32(const GcnInst& inst);
// SOPK
void S_MOVK(const GcnInst& inst);
void S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst);
void S_ADDK_I32(const GcnInst& inst);
void S_MULK_I32(const GcnInst& inst);
// SOP1
void S_MOV(const GcnInst& inst);
void S_MOV_B64(const GcnInst& inst);
void S_NOT_B64(const GcnInst& inst);
void S_BREV_B32(const GcnInst& inst);
void S_GETPC_B64(u32 pc, const GcnInst& inst);
void S_AND_SAVEEXEC_B64(const GcnInst& inst);
// SOPC
void S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst);
// SOPP
void S_BARRIER();
// Scalar Memory
// SMRD
void S_LOAD_DWORD(int num_dwords, const GcnInst& inst);
void S_BUFFER_LOAD_DWORD(int num_dwords, const GcnInst& inst);
// Vector ALU
void V_MOV(const GcnInst& inst);
void V_SAD(const GcnInst& inst);
void V_MAC_F32(const GcnInst& inst);
void V_CVT_PKRTZ_F16_F32(const GcnInst& inst);
void V_CVT_F32_F16(const GcnInst& inst);
void V_CVT_F16_F32(const GcnInst& inst);
void V_MUL_F32(const GcnInst& inst);
// VOP2
void V_CNDMASK_B32(const GcnInst& inst);
void V_OR_B32(bool is_xor, const GcnInst& inst);
void V_AND_B32(const GcnInst& inst);
void V_LSHLREV_B32(const GcnInst& inst);
void V_READLANE_B32(const GcnInst& inst);
void V_WRITELANE_B32(const GcnInst& inst);
void V_ADD_F32(const GcnInst& inst);
void V_SUB_F32(const GcnInst& inst);
void V_SUBREV_F32(const GcnInst& inst);
void V_MUL_F32(const GcnInst& inst);
void V_MUL_I32_I24(const GcnInst& inst);
void V_MIN_F32(const GcnInst& inst, bool is_legacy = false);
void V_MAX_F32(const GcnInst& inst, bool is_legacy = false);
void V_MIN_I32(const GcnInst& inst);
void V_MIN_U32(const GcnInst& inst);
void V_MAX_U32(bool is_signed, const GcnInst& inst);
void V_LSHR_B32(const GcnInst& inst);
void V_LSHRREV_B32(const GcnInst& inst);
void V_ASHR_I32(const GcnInst& inst);
void V_ASHRREV_I32(const GcnInst& inst);
void V_LSHL_B32(const GcnInst& inst);
void V_LSHL_B64(const GcnInst& inst);
void V_LSHLREV_B32(const GcnInst& inst);
void V_AND_B32(const GcnInst& inst);
void V_OR_B32(bool is_xor, const GcnInst& inst);
void V_BFM_B32(const GcnInst& inst);
void V_MAC_F32(const GcnInst& inst);
void V_MADMK_F32(const GcnInst& inst);
void V_BCNT_U32_B32(const GcnInst& inst);
void V_MBCNT_U32_B32(bool is_low, const GcnInst& inst);
void V_ADD_I32(const GcnInst& inst);
void V_SUB_I32(const GcnInst& inst);
void V_SUBREV_I32(const GcnInst& inst);
void V_ADDC_U32(const GcnInst& inst);
void V_LDEXP_F32(const GcnInst& inst);
void V_CVT_PKRTZ_F16_F32(const GcnInst& inst);
// VOP1
void V_MOV(const GcnInst& inst);
void V_READFIRSTLANE_B32(const GcnInst& inst);
void V_CVT_F32_I32(const GcnInst& inst);
void V_CVT_F32_U32(const GcnInst& inst);
void V_MAD_F32(const GcnInst& inst);
void V_FRACT_F32(const GcnInst& inst);
void V_ADD_F32(const GcnInst& inst);
void V_CVT_OFF_F32_I4(const GcnInst& inst);
void V_MED3_F32(const GcnInst& inst);
void V_MED3_I32(const GcnInst& inst);
void V_FLOOR_F32(const GcnInst& inst);
void V_SUB_F32(const GcnInst& inst);
void V_RCP_F32(const GcnInst& inst);
void V_FMA_F32(const GcnInst& inst);
void V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst);
void V_MAX_F32(const GcnInst& inst, bool is_legacy = false);
void V_MAX_F64(const GcnInst& inst);
void V_MAX_U32(bool is_signed, const GcnInst& inst);
void V_RSQ_F32(const GcnInst& inst);
void V_SIN_F32(const GcnInst& inst);
void V_LOG_F32(const GcnInst& inst);
void V_EXP_F32(const GcnInst& inst);
void V_SQRT_F32(const GcnInst& inst);
void V_MIN_F32(const GcnInst& inst, bool is_legacy = false);
void V_MIN3_F32(const GcnInst& inst);
void V_MIN3_I32(const GcnInst& inst);
void V_MADMK_F32(const GcnInst& inst);
void V_CUBEMA_F32(const GcnInst& inst);
void V_CUBESC_F32(const GcnInst& inst);
void V_CUBETC_F32(const GcnInst& inst);
void V_CUBEID_F32(const GcnInst& inst);
void V_CVT_U32_F32(const GcnInst& inst);
void V_SUBREV_F32(const GcnInst& inst);
void V_SUBREV_I32(const GcnInst& inst);
void V_MAD_U64_U32(const GcnInst& inst);
void V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst);
void V_LSHRREV_B32(const GcnInst& inst);
void V_MUL_HI_U32(bool is_signed, const GcnInst& inst);
void V_SAD_U32(const GcnInst& inst);
void V_BFE_U32(bool is_signed, const GcnInst& inst);
void V_MAD_I32_I24(const GcnInst& inst, bool is_signed = true);
void V_MUL_I32_I24(const GcnInst& inst);
void V_SUB_I32(const GcnInst& inst);
void V_LSHR_B32(const GcnInst& inst);
void V_ASHRREV_I32(const GcnInst& inst);
void V_ASHR_I32(const GcnInst& inst);
void V_MAD_U32_U24(const GcnInst& inst);
void V_RNDNE_F32(const GcnInst& inst);
void V_BCNT_U32_B32(const GcnInst& inst);
void V_COS_F32(const GcnInst& inst);
void V_MAX3_F32(const GcnInst& inst);
void V_MAX3_U32(bool is_signed, const GcnInst& inst);
void V_CVT_I32_F32(const GcnInst& inst);
void V_MIN_I32(const GcnInst& inst);
void V_MUL_LO_U32(const GcnInst& inst);
void V_CVT_F16_F32(const GcnInst& inst);
void V_CVT_F32_F16(const GcnInst& inst);
void V_CVT_FLR_I32_F32(const GcnInst& inst);
void V_CVT_OFF_F32_I4(const GcnInst& inst);
void V_CVT_F32_UBYTE(u32 index, const GcnInst& inst);
void V_FRACT_F32(const GcnInst& inst);
void V_TRUNC_F32(const GcnInst& inst);
void V_CEIL_F32(const GcnInst& inst);
void V_MIN_U32(const GcnInst& inst);
void V_CMP_NE_U64(const GcnInst& inst);
void V_BFI_B32(const GcnInst& inst);
void V_RNDNE_F32(const GcnInst& inst);
void V_FLOOR_F32(const GcnInst& inst);
void V_EXP_F32(const GcnInst& inst);
void V_LOG_F32(const GcnInst& inst);
void V_RCP_F32(const GcnInst& inst);
void V_RCP_F64(const GcnInst& inst);
void V_RSQ_F32(const GcnInst& inst);
void V_SQRT_F32(const GcnInst& inst);
void V_SIN_F32(const GcnInst& inst);
void V_COS_F32(const GcnInst& inst);
void V_NOT_B32(const GcnInst& inst);
void V_CVT_F32_UBYTE(u32 index, const GcnInst& inst);
void V_BFREV_B32(const GcnInst& inst);
void V_LDEXP_F32(const GcnInst& inst);
void V_CVT_FLR_I32_F32(const GcnInst& inst);
void V_CMP_CLASS_F32(const GcnInst& inst);
void V_FFBL_B32(const GcnInst& inst);
void V_MBCNT_U32_B32(bool is_low, const GcnInst& inst);
void V_BFM_B32(const GcnInst& inst);
void V_FFBH_U32(const GcnInst& inst);
void V_MOVRELS_B32(const GcnInst& inst);
void V_FFBL_B32(const GcnInst& inst);
void V_MOVRELD_B32(const GcnInst& inst);
void V_MOVRELS_B32(const GcnInst& inst);
void V_MOVRELSD_B32(const GcnInst& inst);
// Vector Memory
// VOPC
void V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst);
void V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst);
void V_CMP_NE_U64(const GcnInst& inst);
void V_CMP_CLASS_F32(const GcnInst& inst);
// VOP3a
void V_MAD_F32(const GcnInst& inst);
void V_MAD_I32_I24(const GcnInst& inst, bool is_signed = false);
void V_MAD_U32_U24(const GcnInst& inst);
void V_CUBEID_F32(const GcnInst& inst);
void V_CUBESC_F32(const GcnInst& inst);
void V_CUBETC_F32(const GcnInst& inst);
void V_CUBEMA_F32(const GcnInst& inst);
void V_BFE_U32(bool is_signed, const GcnInst& inst);
void V_BFI_B32(const GcnInst& inst);
void V_FMA_F32(const GcnInst& inst);
void V_FMA_F64(const GcnInst& inst);
void V_MIN3_F32(const GcnInst& inst);
void V_MIN3_I32(const GcnInst& inst);
void V_MAX3_F32(const GcnInst& inst);
void V_MAX3_U32(bool is_signed, const GcnInst& inst);
void V_MED3_F32(const GcnInst& inst);
void V_MED3_I32(const GcnInst& inst);
void V_SAD(const GcnInst& inst);
void V_SAD_U32(const GcnInst& inst);
void V_LSHL_B64(const GcnInst& inst);
void V_MUL_F64(const GcnInst& inst);
void V_MAX_F64(const GcnInst& inst);
void V_MUL_LO_U32(const GcnInst& inst);
void V_MUL_HI_U32(bool is_signed, const GcnInst& inst);
void V_MAD_U64_U32(const GcnInst& inst);
// Vector interpolation
// VINTRP
void V_INTERP_P2_F32(const GcnInst& inst);
void V_INTERP_MOV_F32(const GcnInst& inst);
// Data share
// DS
void DS_ADD_U32(const GcnInst& inst, bool rtn);
void DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn);
void DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn);
void DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst);
void DS_SWIZZLE_B32(const GcnInst& inst);
void DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst);
void DS_APPEND(const GcnInst& inst);
void DS_CONSUME(const GcnInst& inst);
// Buffer Memory
// MUBUF / MTBUF
void BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst);
void BUFFER_LOAD_FORMAT(u32 num_dwords, const GcnInst& inst);
void BUFFER_STORE(u32 num_dwords, bool is_typed, const GcnInst& inst);
void BUFFER_STORE_FORMAT(u32 num_dwords, const GcnInst& inst);
void BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst);
// Vector interpolation
void V_INTERP_P2_F32(const GcnInst& inst);
void V_INTERP_MOV_F32(const GcnInst& inst);
// Data share
void DS_SWIZZLE_B32(const GcnInst& inst);
void DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst);
void DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst);
void DS_ADD_U32(const GcnInst& inst, bool rtn);
void DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn);
void DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn);
void V_READFIRSTLANE_B32(const GcnInst& inst);
void V_READLANE_B32(const GcnInst& inst);
void V_WRITELANE_B32(const GcnInst& inst);
void DS_APPEND(const GcnInst& inst);
void DS_CONSUME(const GcnInst& inst);
void S_BARRIER();
// Image Memory
// MIMG
void IMAGE_LOAD(bool has_mip, const GcnInst& inst);
void IMAGE_STORE(const GcnInst& inst);
void IMAGE_GET_RESINFO(const GcnInst& inst);
void IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst);
void IMAGE_SAMPLE(const GcnInst& inst);
void IMAGE_GATHER(const GcnInst& inst);
void IMAGE_STORE(const GcnInst& inst);
void IMAGE_LOAD(bool has_mip, const GcnInst& inst);
void IMAGE_GET_LOD(const GcnInst& inst);
void IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst);
private:
template <typename T = IR::U32>
@ -241,6 +265,7 @@ private:
void SetDst(const InstOperand& operand, const IR::U32F32& value);
void SetDst64(const InstOperand& operand, const IR::U64F64& value_raw);
// Vector ALU Helprers
IR::U32 VMovRelSHelper(u32 src_vgprno, const IR::U32 m0);
void VMovRelDHelper(u32 dst_vgprno, const IR::U32 src_val, const IR::U32 m0);

File diff suppressed because it is too large Load diff

View file

@ -5,6 +5,22 @@
namespace Shader::Gcn {
void Translator::EmitVectorInterpolation(const GcnInst& inst) {
switch (inst.opcode) {
// VINTRP
case Opcode::V_INTERP_P1_F32:
return;
case Opcode::V_INTERP_P2_F32:
return V_INTERP_P2_F32(inst);
case Opcode::V_INTERP_MOV_F32:
return V_INTERP_MOV_F32(inst);
default:
LogMissingOpcode(inst);
}
}
// VINTRP
void Translator::V_INTERP_P2_F32(const GcnInst& inst) {
const IR::VectorReg dst_reg{inst.dst[0].code};
auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr);
@ -19,17 +35,4 @@ void Translator::V_INTERP_MOV_F32(const GcnInst& inst) {
ir.SetVectorReg(dst_reg, ir.GetAttribute(attrib, inst.control.vintrp.chan));
}
void Translator::EmitVectorInterpolation(const GcnInst& inst) {
switch (inst.opcode) {
case Opcode::V_INTERP_P1_F32:
return;
case Opcode::V_INTERP_P2_F32:
return V_INTERP_P2_F32(inst);
case Opcode::V_INTERP_MOV_F32:
return V_INTERP_MOV_F32(inst);
default:
LogMissingOpcode(inst);
}
}
} // namespace Shader::Gcn

View file

@ -7,55 +7,7 @@ namespace Shader::Gcn {
void Translator::EmitVectorMemory(const GcnInst& inst) {
switch (inst.opcode) {
case Opcode::IMAGE_SAMPLE_LZ_O:
case Opcode::IMAGE_SAMPLE_O:
case Opcode::IMAGE_SAMPLE_C:
case Opcode::IMAGE_SAMPLE_C_LZ:
case Opcode::IMAGE_SAMPLE_LZ:
case Opcode::IMAGE_SAMPLE:
case Opcode::IMAGE_SAMPLE_L:
case Opcode::IMAGE_SAMPLE_L_O:
case Opcode::IMAGE_SAMPLE_C_O:
case Opcode::IMAGE_SAMPLE_B:
case Opcode::IMAGE_SAMPLE_C_LZ_O:
case Opcode::IMAGE_SAMPLE_D:
case Opcode::IMAGE_SAMPLE_CD:
return IMAGE_SAMPLE(inst);
case Opcode::IMAGE_GATHER4_LZ:
case Opcode::IMAGE_GATHER4_C:
case Opcode::IMAGE_GATHER4_C_LZ:
case Opcode::IMAGE_GATHER4_LZ_O:
return IMAGE_GATHER(inst);
case Opcode::IMAGE_ATOMIC_ADD:
return IMAGE_ATOMIC(AtomicOp::Add, inst);
case Opcode::IMAGE_ATOMIC_AND:
return IMAGE_ATOMIC(AtomicOp::And, inst);
case Opcode::IMAGE_ATOMIC_OR:
return IMAGE_ATOMIC(AtomicOp::Or, inst);
case Opcode::IMAGE_ATOMIC_XOR:
return IMAGE_ATOMIC(AtomicOp::Xor, inst);
case Opcode::IMAGE_ATOMIC_UMAX:
return IMAGE_ATOMIC(AtomicOp::Umax, inst);
case Opcode::IMAGE_ATOMIC_SMAX:
return IMAGE_ATOMIC(AtomicOp::Smax, inst);
case Opcode::IMAGE_ATOMIC_UMIN:
return IMAGE_ATOMIC(AtomicOp::Umin, inst);
case Opcode::IMAGE_ATOMIC_SMIN:
return IMAGE_ATOMIC(AtomicOp::Smin, inst);
case Opcode::IMAGE_ATOMIC_INC:
return IMAGE_ATOMIC(AtomicOp::Inc, inst);
case Opcode::IMAGE_ATOMIC_DEC:
return IMAGE_ATOMIC(AtomicOp::Dec, inst);
case Opcode::IMAGE_GET_LOD:
return IMAGE_GET_LOD(inst);
case Opcode::IMAGE_STORE:
return IMAGE_STORE(inst);
case Opcode::IMAGE_LOAD_MIP:
return IMAGE_LOAD(true, inst);
case Opcode::IMAGE_LOAD:
return IMAGE_LOAD(false, inst);
case Opcode::IMAGE_GET_RESINFO:
return IMAGE_GET_RESINFO(inst);
// MUBUF / MTBUF
// Buffer load operations
case Opcode::TBUFFER_LOAD_FORMAT_X:
@ -137,239 +89,74 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
case Opcode::BUFFER_ATOMIC_DEC:
return BUFFER_ATOMIC(AtomicOp::Dec, inst);
// MIMG
// Image load operations
case Opcode::IMAGE_LOAD:
return IMAGE_LOAD(false, inst);
case Opcode::IMAGE_LOAD_MIP:
return IMAGE_LOAD(true, inst);
// Buffer store operations
case Opcode::IMAGE_STORE:
return IMAGE_STORE(inst);
// Image misc operations
case Opcode::IMAGE_GET_RESINFO:
return IMAGE_GET_RESINFO(inst);
// Image atomic operations
case Opcode::IMAGE_ATOMIC_ADD:
return IMAGE_ATOMIC(AtomicOp::Add, inst);
case Opcode::IMAGE_ATOMIC_SMIN:
return IMAGE_ATOMIC(AtomicOp::Smin, inst);
case Opcode::IMAGE_ATOMIC_UMIN:
return IMAGE_ATOMIC(AtomicOp::Umin, inst);
case Opcode::IMAGE_ATOMIC_SMAX:
return IMAGE_ATOMIC(AtomicOp::Smax, inst);
case Opcode::IMAGE_ATOMIC_UMAX:
return IMAGE_ATOMIC(AtomicOp::Umax, inst);
case Opcode::IMAGE_ATOMIC_AND:
return IMAGE_ATOMIC(AtomicOp::And, inst);
case Opcode::IMAGE_ATOMIC_OR:
return IMAGE_ATOMIC(AtomicOp::Or, inst);
case Opcode::IMAGE_ATOMIC_XOR:
return IMAGE_ATOMIC(AtomicOp::Xor, inst);
case Opcode::IMAGE_ATOMIC_INC:
return IMAGE_ATOMIC(AtomicOp::Inc, inst);
case Opcode::IMAGE_ATOMIC_DEC:
return IMAGE_ATOMIC(AtomicOp::Dec, inst);
case Opcode::IMAGE_SAMPLE:
case Opcode::IMAGE_SAMPLE_D:
case Opcode::IMAGE_SAMPLE_L:
case Opcode::IMAGE_SAMPLE_B:
case Opcode::IMAGE_SAMPLE_LZ:
case Opcode::IMAGE_SAMPLE_C:
case Opcode::IMAGE_SAMPLE_C_LZ:
case Opcode::IMAGE_SAMPLE_O:
case Opcode::IMAGE_SAMPLE_L_O:
case Opcode::IMAGE_SAMPLE_LZ_O:
case Opcode::IMAGE_SAMPLE_C_O:
case Opcode::IMAGE_SAMPLE_C_LZ_O:
case Opcode::IMAGE_SAMPLE_CD:
return IMAGE_SAMPLE(inst);
// Image gather operations
case Opcode::IMAGE_GATHER4_LZ:
case Opcode::IMAGE_GATHER4_C:
case Opcode::IMAGE_GATHER4_C_LZ:
case Opcode::IMAGE_GATHER4_LZ_O:
return IMAGE_GATHER(inst);
// Image misc operations
case Opcode::IMAGE_GET_LOD:
return IMAGE_GET_LOD(inst);
default:
LogMissingOpcode(inst);
}
}
void Translator::IMAGE_GET_RESINFO(const GcnInst& inst) {
IR::VectorReg dst_reg{inst.dst[0].code};
const IR::ScalarReg tsharp_reg{inst.src[2].code * 4};
const auto flags = ImageResFlags(inst.control.mimg.dmask);
const bool has_mips = flags.test(ImageResComponent::MipCount);
const IR::U32 lod = ir.GetVectorReg(IR::VectorReg(inst.src[0].code));
const IR::Value tsharp = ir.GetScalarReg(tsharp_reg);
const IR::Value size = ir.ImageQueryDimension(tsharp, lod, ir.Imm1(has_mips));
if (flags.test(ImageResComponent::Width)) {
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 0)});
}
if (flags.test(ImageResComponent::Height)) {
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 1)});
}
if (flags.test(ImageResComponent::Depth)) {
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 2)});
}
if (has_mips) {
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 3)});
}
}
void Translator::IMAGE_SAMPLE(const GcnInst& inst) {
const auto& mimg = inst.control.mimg;
IR::VectorReg addr_reg{inst.src[0].code};
IR::VectorReg dest_reg{inst.dst[0].code};
const IR::ScalarReg tsharp_reg{inst.src[2].code * 4};
const IR::ScalarReg sampler_reg{inst.src[3].code * 4};
const auto flags = MimgModifierFlags(mimg.mod);
// Load first dword of T# and S#. We will use them as the handle that will guide resource
// tracking pass where to read the sharps. This will later also get patched to the SPIRV texture
// binding index.
const IR::Value handle =
ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg));
// Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction
// Set Architecture
const IR::U32 offset =
flags.test(MimgModifier::Offset) ? ir.GetVectorReg<IR::U32>(addr_reg++) : IR::U32{};
const IR::F32 bias =
flags.test(MimgModifier::LodBias) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
const IR::F32 dref =
flags.test(MimgModifier::Pcf) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
const IR::Value derivatives = [&] -> IR::Value {
if (!flags.test(MimgModifier::Derivative)) {
return {};
}
addr_reg = addr_reg + 4;
return ir.CompositeConstruct(
ir.GetVectorReg<IR::F32>(addr_reg - 4), ir.GetVectorReg<IR::F32>(addr_reg - 3),
ir.GetVectorReg<IR::F32>(addr_reg - 2), ir.GetVectorReg<IR::F32>(addr_reg - 1));
}();
// Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler
// Since these are at most 4 dwords, we load them into a single uvec4 and place them
// in coords field of the instruction. Then the resource tracking pass will patch the
// IR instruction to fill in lod_clamp field.
const IR::Value body = ir.CompositeConstruct(
ir.GetVectorReg<IR::F32>(addr_reg), ir.GetVectorReg<IR::F32>(addr_reg + 1),
ir.GetVectorReg<IR::F32>(addr_reg + 2), ir.GetVectorReg<IR::F32>(addr_reg + 3));
// Derivatives are tricky because their number depends on the texture type which is located in
// T#. We don't have access to T# though until resource tracking pass. For now assume if
// derivatives are present, that a 2D image is bound.
const bool has_derivatives = flags.test(MimgModifier::Derivative);
const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod);
IR::TextureInstInfo info{};
info.is_depth.Assign(flags.test(MimgModifier::Pcf));
info.has_bias.Assign(flags.test(MimgModifier::LodBias));
info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp));
info.force_level0.Assign(flags.test(MimgModifier::Level0));
info.has_offset.Assign(flags.test(MimgModifier::Offset));
info.explicit_lod.Assign(explicit_lod);
info.has_derivatives.Assign(has_derivatives);
// Issue IR instruction, leaving unknown fields blank to patch later.
const IR::Value texel = [&]() -> IR::Value {
if (has_derivatives) {
return ir.ImageGradient(handle, body, derivatives, offset, {}, info);
}
if (!flags.test(MimgModifier::Pcf)) {
if (explicit_lod) {
return ir.ImageSampleExplicitLod(handle, body, offset, info);
} else {
return ir.ImageSampleImplicitLod(handle, body, bias, offset, info);
}
}
if (explicit_lod) {
return ir.ImageSampleDrefExplicitLod(handle, body, dref, offset, info);
}
return ir.ImageSampleDrefImplicitLod(handle, body, dref, bias, offset, info);
}();
for (u32 i = 0; i < 4; i++) {
if (((mimg.dmask >> i) & 1) == 0) {
continue;
}
IR::F32 value;
if (flags.test(MimgModifier::Pcf)) {
value = i < 3 ? IR::F32{texel} : ir.Imm32(1.0f);
} else {
value = IR::F32{ir.CompositeExtract(texel, i)};
}
ir.SetVectorReg(dest_reg++, value);
}
}
void Translator::IMAGE_GATHER(const GcnInst& inst) {
const auto& mimg = inst.control.mimg;
if (mimg.da) {
LOG_WARNING(Render_Vulkan, "Image instruction declares an array");
}
IR::VectorReg addr_reg{inst.src[0].code};
IR::VectorReg dest_reg{inst.dst[0].code};
const IR::ScalarReg tsharp_reg{inst.src[2].code * 4};
const IR::ScalarReg sampler_reg{inst.src[3].code * 4};
const auto flags = MimgModifierFlags(mimg.mod);
// Load first dword of T# and S#. We will use them as the handle that will guide resource
// tracking pass where to read the sharps. This will later also get patched to the SPIRV texture
// binding index.
const IR::Value handle =
ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg));
// Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction
// Set Architecture
const IR::Value offset =
flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::Value{};
const IR::F32 bias =
flags.test(MimgModifier::LodBias) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
const IR::F32 dref =
flags.test(MimgModifier::Pcf) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
// Derivatives are tricky because their number depends on the texture type which is located in
// T#. We don't have access to T# though until resource tracking pass. For now assume no
// derivatives are present, otherwise we don't know where coordinates are placed in the address
// stream.
ASSERT_MSG(!flags.test(MimgModifier::Derivative), "Derivative image instruction");
// Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler
// Since these are at most 4 dwords, we load them into a single uvec4 and place them
// in coords field of the instruction. Then the resource tracking pass will patch the
// IR instruction to fill in lod_clamp field.
const IR::Value body = ir.CompositeConstruct(
ir.GetVectorReg<IR::F32>(addr_reg), ir.GetVectorReg<IR::F32>(addr_reg + 1),
ir.GetVectorReg<IR::F32>(addr_reg + 2), ir.GetVectorReg<IR::F32>(addr_reg + 3));
const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod);
IR::TextureInstInfo info{};
info.is_depth.Assign(flags.test(MimgModifier::Pcf));
info.has_bias.Assign(flags.test(MimgModifier::LodBias));
info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp));
info.force_level0.Assign(flags.test(MimgModifier::Level0));
info.has_offset.Assign(flags.test(MimgModifier::Offset));
// info.explicit_lod.Assign(explicit_lod);
info.gather_comp.Assign(std::bit_width(mimg.dmask) - 1);
// Issue IR instruction, leaving unknown fields blank to patch later.
const IR::Value texel = [&]() -> IR::Value {
const IR::F32 lod = flags.test(MimgModifier::Level0) ? ir.Imm32(0.f) : IR::F32{};
if (!flags.test(MimgModifier::Pcf)) {
return ir.ImageGather(handle, body, offset, info);
}
ASSERT(mimg.dmask & 1); // should be always 1st (R) component
return ir.ImageGatherDref(handle, body, offset, dref, info);
}();
// For gather4 instructions dmask selects which component to read and must have
// only one bit set to 1
ASSERT_MSG(std::popcount(mimg.dmask) == 1, "Unexpected bits in gather dmask");
for (u32 i = 0; i < 4; i++) {
const IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)};
ir.SetVectorReg(dest_reg++, value);
}
}
void Translator::IMAGE_LOAD(bool has_mip, const GcnInst& inst) {
const auto& mimg = inst.control.mimg;
IR::VectorReg addr_reg{inst.src[0].code};
IR::VectorReg dest_reg{inst.dst[0].code};
const IR::ScalarReg tsharp_reg{inst.src[2].code * 4};
const IR::Value handle = ir.GetScalarReg(tsharp_reg);
const IR::Value body =
ir.CompositeConstruct(ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1),
ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3));
IR::TextureInstInfo info{};
info.explicit_lod.Assign(has_mip);
const IR::Value texel = ir.ImageFetch(handle, body, {}, {}, {}, info);
for (u32 i = 0; i < 4; i++) {
if (((mimg.dmask >> i) & 1) == 0) {
continue;
}
IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)};
ir.SetVectorReg(dest_reg++, value);
}
}
void Translator::IMAGE_STORE(const GcnInst& inst) {
const auto& mimg = inst.control.mimg;
IR::VectorReg addr_reg{inst.src[0].code};
IR::VectorReg data_reg{inst.dst[0].code};
const IR::ScalarReg tsharp_reg{inst.src[2].code * 4};
const IR::Value handle = ir.GetScalarReg(tsharp_reg);
const IR::Value body =
ir.CompositeConstruct(ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1),
ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3));
boost::container::static_vector<IR::F32, 4> comps;
for (u32 i = 0; i < 4; i++) {
if (((mimg.dmask >> i) & 1) == 0) {
comps.push_back(ir.Imm32(0.f));
continue;
}
comps.push_back(ir.GetVectorReg<IR::F32>(data_reg++));
}
const IR::Value value = ir.CompositeConstruct(comps[0], comps[1], comps[2], comps[3]);
ir.ImageWrite(handle, body, value, {});
}
void Translator::BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst) {
const auto& mtbuf = inst.control.mtbuf;
const IR::VectorReg vaddr{inst.src[0].code};
@ -588,19 +375,77 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) {
}
}
void Translator::IMAGE_GET_LOD(const GcnInst& inst) {
// Image Memory
// MIMG
void Translator::IMAGE_LOAD(bool has_mip, const GcnInst& inst) {
const auto& mimg = inst.control.mimg;
IR::VectorReg dst_reg{inst.dst[0].code};
IR::VectorReg addr_reg{inst.src[0].code};
IR::VectorReg dest_reg{inst.dst[0].code};
const IR::ScalarReg tsharp_reg{inst.src[2].code * 4};
const IR::Value handle = ir.GetScalarReg(tsharp_reg);
const IR::Value body = ir.CompositeConstruct(
ir.GetVectorReg<IR::F32>(addr_reg), ir.GetVectorReg<IR::F32>(addr_reg + 1),
ir.GetVectorReg<IR::F32>(addr_reg + 2), ir.GetVectorReg<IR::F32>(addr_reg + 3));
const IR::Value lod = ir.ImageQueryLod(handle, body, {});
ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 0)});
ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 1)});
const IR::Value body =
ir.CompositeConstruct(ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1),
ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3));
IR::TextureInstInfo info{};
info.explicit_lod.Assign(has_mip);
const IR::Value texel = ir.ImageFetch(handle, body, {}, {}, {}, info);
for (u32 i = 0; i < 4; i++) {
if (((mimg.dmask >> i) & 1) == 0) {
continue;
}
IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)};
ir.SetVectorReg(dest_reg++, value);
}
}
void Translator::IMAGE_STORE(const GcnInst& inst) {
const auto& mimg = inst.control.mimg;
IR::VectorReg addr_reg{inst.src[0].code};
IR::VectorReg data_reg{inst.dst[0].code};
const IR::ScalarReg tsharp_reg{inst.src[2].code * 4};
const IR::Value handle = ir.GetScalarReg(tsharp_reg);
const IR::Value body =
ir.CompositeConstruct(ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1),
ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3));
boost::container::static_vector<IR::F32, 4> comps;
for (u32 i = 0; i < 4; i++) {
if (((mimg.dmask >> i) & 1) == 0) {
comps.push_back(ir.Imm32(0.f));
continue;
}
comps.push_back(ir.GetVectorReg<IR::F32>(data_reg++));
}
const IR::Value value = ir.CompositeConstruct(comps[0], comps[1], comps[2], comps[3]);
ir.ImageWrite(handle, body, value, {});
}
void Translator::IMAGE_GET_RESINFO(const GcnInst& inst) {
IR::VectorReg dst_reg{inst.dst[0].code};
const IR::ScalarReg tsharp_reg{inst.src[2].code * 4};
const auto flags = ImageResFlags(inst.control.mimg.dmask);
const bool has_mips = flags.test(ImageResComponent::MipCount);
const IR::U32 lod = ir.GetVectorReg(IR::VectorReg(inst.src[0].code));
const IR::Value tsharp = ir.GetScalarReg(tsharp_reg);
const IR::Value size = ir.ImageQueryDimension(tsharp, lod, ir.Imm1(has_mips));
if (flags.test(ImageResComponent::Width)) {
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 0)});
}
if (flags.test(ImageResComponent::Height)) {
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 1)});
}
if (flags.test(ImageResComponent::Depth)) {
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 2)});
}
if (has_mips) {
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 3)});
}
}
void Translator::IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst) {
@ -647,4 +492,177 @@ void Translator::IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst) {
}
}
void Translator::IMAGE_SAMPLE(const GcnInst& inst) {
const auto& mimg = inst.control.mimg;
IR::VectorReg addr_reg{inst.src[0].code};
IR::VectorReg dest_reg{inst.dst[0].code};
const IR::ScalarReg tsharp_reg{inst.src[2].code * 4};
const IR::ScalarReg sampler_reg{inst.src[3].code * 4};
const auto flags = MimgModifierFlags(mimg.mod);
// Load first dword of T# and S#. We will use them as the handle that will guide resource
// tracking pass where to read the sharps. This will later also get patched to the SPIRV texture
// binding index.
const IR::Value handle =
ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg));
// Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction
// Set Architecture
const IR::U32 offset =
flags.test(MimgModifier::Offset) ? ir.GetVectorReg<IR::U32>(addr_reg++) : IR::U32{};
const IR::F32 bias =
flags.test(MimgModifier::LodBias) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
const IR::F32 dref =
flags.test(MimgModifier::Pcf) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
const IR::Value derivatives = [&] -> IR::Value {
if (!flags.test(MimgModifier::Derivative)) {
return {};
}
addr_reg = addr_reg + 4;
return ir.CompositeConstruct(
ir.GetVectorReg<IR::F32>(addr_reg - 4), ir.GetVectorReg<IR::F32>(addr_reg - 3),
ir.GetVectorReg<IR::F32>(addr_reg - 2), ir.GetVectorReg<IR::F32>(addr_reg - 1));
}();
// Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler
// Since these are at most 4 dwords, we load them into a single uvec4 and place them
// in coords field of the instruction. Then the resource tracking pass will patch the
// IR instruction to fill in lod_clamp field.
const IR::Value body = ir.CompositeConstruct(
ir.GetVectorReg<IR::F32>(addr_reg), ir.GetVectorReg<IR::F32>(addr_reg + 1),
ir.GetVectorReg<IR::F32>(addr_reg + 2), ir.GetVectorReg<IR::F32>(addr_reg + 3));
// Derivatives are tricky because their number depends on the texture type which is located in
// T#. We don't have access to T# though until resource tracking pass. For now assume if
// derivatives are present, that a 2D image is bound.
const bool has_derivatives = flags.test(MimgModifier::Derivative);
const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod);
IR::TextureInstInfo info{};
info.is_depth.Assign(flags.test(MimgModifier::Pcf));
info.has_bias.Assign(flags.test(MimgModifier::LodBias));
info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp));
info.force_level0.Assign(flags.test(MimgModifier::Level0));
info.has_offset.Assign(flags.test(MimgModifier::Offset));
info.explicit_lod.Assign(explicit_lod);
info.has_derivatives.Assign(has_derivatives);
// Issue IR instruction, leaving unknown fields blank to patch later.
const IR::Value texel = [&]() -> IR::Value {
if (has_derivatives) {
return ir.ImageGradient(handle, body, derivatives, offset, {}, info);
}
if (!flags.test(MimgModifier::Pcf)) {
if (explicit_lod) {
return ir.ImageSampleExplicitLod(handle, body, offset, info);
} else {
return ir.ImageSampleImplicitLod(handle, body, bias, offset, info);
}
}
if (explicit_lod) {
return ir.ImageSampleDrefExplicitLod(handle, body, dref, offset, info);
}
return ir.ImageSampleDrefImplicitLod(handle, body, dref, bias, offset, info);
}();
for (u32 i = 0; i < 4; i++) {
if (((mimg.dmask >> i) & 1) == 0) {
continue;
}
IR::F32 value;
if (flags.test(MimgModifier::Pcf)) {
value = i < 3 ? IR::F32{texel} : ir.Imm32(1.0f);
} else {
value = IR::F32{ir.CompositeExtract(texel, i)};
}
ir.SetVectorReg(dest_reg++, value);
}
}
void Translator::IMAGE_GATHER(const GcnInst& inst) {
const auto& mimg = inst.control.mimg;
if (mimg.da) {
LOG_WARNING(Render_Vulkan, "Image instruction declares an array");
}
IR::VectorReg addr_reg{inst.src[0].code};
IR::VectorReg dest_reg{inst.dst[0].code};
const IR::ScalarReg tsharp_reg{inst.src[2].code * 4};
const IR::ScalarReg sampler_reg{inst.src[3].code * 4};
const auto flags = MimgModifierFlags(mimg.mod);
// Load first dword of T# and S#. We will use them as the handle that will guide resource
// tracking pass where to read the sharps. This will later also get patched to the SPIRV texture
// binding index.
const IR::Value handle =
ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg));
// Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction
// Set Architecture
const IR::Value offset =
flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::Value{};
const IR::F32 bias =
flags.test(MimgModifier::LodBias) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
const IR::F32 dref =
flags.test(MimgModifier::Pcf) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
// Derivatives are tricky because their number depends on the texture type which is located in
// T#. We don't have access to T# though until resource tracking pass. For now assume no
// derivatives are present, otherwise we don't know where coordinates are placed in the address
// stream.
ASSERT_MSG(!flags.test(MimgModifier::Derivative), "Derivative image instruction");
// Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler
// Since these are at most 4 dwords, we load them into a single uvec4 and place them
// in coords field of the instruction. Then the resource tracking pass will patch the
// IR instruction to fill in lod_clamp field.
const IR::Value body = ir.CompositeConstruct(
ir.GetVectorReg<IR::F32>(addr_reg), ir.GetVectorReg<IR::F32>(addr_reg + 1),
ir.GetVectorReg<IR::F32>(addr_reg + 2), ir.GetVectorReg<IR::F32>(addr_reg + 3));
const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod);
IR::TextureInstInfo info{};
info.is_depth.Assign(flags.test(MimgModifier::Pcf));
info.has_bias.Assign(flags.test(MimgModifier::LodBias));
info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp));
info.force_level0.Assign(flags.test(MimgModifier::Level0));
info.has_offset.Assign(flags.test(MimgModifier::Offset));
// info.explicit_lod.Assign(explicit_lod);
info.gather_comp.Assign(std::bit_width(mimg.dmask) - 1);
// Issue IR instruction, leaving unknown fields blank to patch later.
const IR::Value texel = [&]() -> IR::Value {
const IR::F32 lod = flags.test(MimgModifier::Level0) ? ir.Imm32(0.f) : IR::F32{};
if (!flags.test(MimgModifier::Pcf)) {
return ir.ImageGather(handle, body, offset, info);
}
ASSERT(mimg.dmask & 1); // should be always 1st (R) component
return ir.ImageGatherDref(handle, body, offset, dref, info);
}();
// For gather4 instructions dmask selects which component to read and must have
// only one bit set to 1
ASSERT_MSG(std::popcount(mimg.dmask) == 1, "Unexpected bits in gather dmask");
for (u32 i = 0; i < 4; i++) {
const IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)};
ir.SetVectorReg(dest_reg++, value);
}
}
void Translator::IMAGE_GET_LOD(const GcnInst& inst) {
const auto& mimg = inst.control.mimg;
IR::VectorReg dst_reg{inst.dst[0].code};
IR::VectorReg addr_reg{inst.src[0].code};
const IR::ScalarReg tsharp_reg{inst.src[2].code * 4};
const IR::Value handle = ir.GetScalarReg(tsharp_reg);
const IR::Value body = ir.CompositeConstruct(
ir.GetVectorReg<IR::F32>(addr_reg), ir.GetVectorReg<IR::F32>(addr_reg + 1),
ir.GetVectorReg<IR::F32>(addr_reg + 2), ir.GetVectorReg<IR::F32>(addr_reg + 3));
const IR::Value lod = ir.ImageQueryLod(handle, body, {});
ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 0)});
ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 1)});
}
} // namespace Shader::Gcn

View file

@ -66,7 +66,7 @@ struct Buffer {
}
u32 GetStride() const noexcept {
return stride == 0 ? 1U : stride;
return stride;
}
u32 NumDwords() const noexcept {
@ -74,7 +74,7 @@ struct Buffer {
}
u32 GetSize() const noexcept {
return GetStride() * num_records;
return stride == 0 ? num_records : (stride * num_records);
}
};
static_assert(sizeof(Buffer) == 16); // 128bits

View file

@ -4,6 +4,7 @@
#include <mutex>
#include "common/assert.h"
#include "common/debug.h"
#include "imgui/renderer/texture_manager.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
@ -190,6 +191,7 @@ void Scheduler::SubmitExecution(SubmitInfo& info) {
};
try {
ImGui::Core::TextureManager::Submit();
instance.GetGraphicsQueue().submit(submit_info, info.fence);
} catch (vk::DeviceLostError& err) {
UNREACHABLE_MSG("Device lost during submit: {}", err.what());