diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ceb915f6a..588236b14 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -76,18 +76,13 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
- uses: hendrikmuhs/ccache-action@v1.2.17
+ uses: hendrikmuhs/ccache-action@v1.2.18
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 --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
@@ -111,7 +106,7 @@ jobs:
- name: Setup Qt
uses: jurplel/install-qt-action@v4
with:
- version: 6.9.0
+ version: 6.9.1
host: windows
target: desktop
arch: win64_msvc2022_64
@@ -130,18 +125,13 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
- uses: hendrikmuhs/ccache-action@v1.2.17
+ uses: hendrikmuhs/ccache-action@v1.2.18
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 --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
@@ -186,7 +176,7 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
- uses: hendrikmuhs/ccache-action@v1.2.17
+ uses: hendrikmuhs/ccache-action@v1.2.18
env:
cache-name: ${{runner.os}}-sdl-cache-cmake-build
with:
@@ -228,7 +218,7 @@ jobs:
- name: Setup Qt
uses: jurplel/install-qt-action@v4
with:
- version: 6.9.0
+ version: 6.9.1
host: mac
target: desktop
arch: clang_64
@@ -247,7 +237,7 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
- uses: hendrikmuhs/ccache-action@v1.2.17
+ uses: hendrikmuhs/ccache-action@v1.2.18
env:
cache-name: ${{runner.os}}-qt-cache-cmake-build
with:
@@ -301,7 +291,7 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
- uses: hendrikmuhs/ccache-action@v1.2.17
+ uses: hendrikmuhs/ccache-action@v1.2.18
env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-build
with:
@@ -362,7 +352,7 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
- uses: hendrikmuhs/ccache-action@v1.2.17
+ uses: hendrikmuhs/ccache-action@v1.2.18
env:
cache-name: ${{ runner.os }}-qt-cache-cmake-build
with:
@@ -409,7 +399,7 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
- uses: hendrikmuhs/ccache-action@v1.2.17
+ uses: hendrikmuhs/ccache-action@v1.2.18
env:
cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-build
with:
@@ -445,7 +435,7 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
- uses: hendrikmuhs/ccache-action@v1.2.17
+ uses: hendrikmuhs/ccache-action@v1.2.18
env:
cache-name: ${{ runner.os }}-qt-gcc-cache-cmake-build
with:
@@ -494,7 +484,7 @@ jobs:
with:
token: ${{ secrets.SHADPS4_TOKEN_REPO }}
name: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}"
- tag: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}"
+ tag: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.fullhash }}"
draft: false
prerelease: true
body: "Full Changelog: [${{ env.last_release_tag }}...${{ needs.get-info.outputs.shorthash }}](https://github.com/shadps4-emu/shadPS4/compare/${{ env.last_release_tag }}...${{ needs.get-info.outputs.fullhash }})"
@@ -530,14 +520,14 @@ jobs:
# Check if release already exists and get ID
release_id=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
- "https://api.github.com/repos/$REPO/releases/tags/Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}" | jq -r '.id')
+ "https://api.github.com/repos/$REPO/releases/tags/Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.fullhash }}" | jq -r '.id')
if [[ "$release_id" == "null" ]]; then
echo "Creating release in $REPO for $filename"
release_id=$(curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
-d '{
- "tag_name": "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}",
+ "tag_name": "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.fullhash }}",
"name": "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}",
"draft": false,
"prerelease": true,
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f85d22b6c..20d33ac95 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -296,6 +296,8 @@ set(AJM_LIB src/core/libraries/ajm/ajm.cpp
set(AUDIO_LIB src/core/libraries/audio/audioin.cpp
src/core/libraries/audio/audioin.h
+ src/core/libraries/voice/voice.cpp
+ src/core/libraries/voice/voice.h
src/core/libraries/audio/audioout.cpp
src/core/libraries/audio/audioout.h
src/core/libraries/audio/audioout_backend.h
diff --git a/documents/building-linux.md b/documents/building-linux.md
index cdc8ba12f..bd07b2eff 100644
--- a/documents/building-linux.md
+++ b/documents/building-linux.md
@@ -29,7 +29,7 @@ sudo dnf install clang git cmake libatomic alsa-lib-devel \
openssl-devel libevdev-devel libudev-devel libXext-devel \
qt6-qtbase-devel qt6-qtbase-private-devel \
qt6-qtmultimedia-devel qt6-qtsvg-devel qt6-qttools-devel \
- vulkan-devel vulkan-validation-layers libpng-devel
+ vulkan-devel vulkan-validation-layers libpng-devel libuuid-devel
```
#### Arch Linux
diff --git a/src/common/config.cpp b/src/common/config.cpp
index 6bccd0f37..6565ab82a 100644
--- a/src/common/config.cpp
+++ b/src/common/config.cpp
@@ -69,7 +69,7 @@ static bool vkGuestMarkers = false;
static bool rdocEnable = false;
static bool isFpsColor = true;
static bool isSeparateLogFilesEnabled = false;
-static s16 cursorState = HideCursorState::Idle;
+static int cursorState = HideCursorState::Idle;
static int cursorHideTimeout = 5; // 5 seconds (default)
static double trophyNotificationDuration = 6.0;
static bool useUnifiedInputConfig = true;
@@ -78,6 +78,7 @@ static int controllerCustomColorRGB[3] = {0, 0, 255};
static bool compatibilityData = false;
static bool checkCompatibilityOnStartup = false;
static std::string trophyKey;
+static bool isPSNSignedIn = false;
// Gui
static bool load_game_size = true;
@@ -730,6 +731,14 @@ void setShowBackgroundImage(bool show) {
showBackgroundImage = show;
}
+bool getPSNSignedIn() {
+ return isPSNSignedIn;
+}
+
+void setPSNSignedIn(bool sign) {
+ isPSNSignedIn = sign;
+}
+
void load(const std::filesystem::path& path) {
// If the configuration file does not exist, create it and return
std::error_code error;
@@ -754,6 +763,7 @@ void load(const std::filesystem::path& path) {
isNeo = toml::find_or(general, "isPS4Pro", false);
isDevKit = toml::find_or(general, "isDevKit", false);
+ isPSNSignedIn = toml::find_or(general, "isPSNSignedIn", false);
playBGM = toml::find_or(general, "playBGM", false);
isTrophyPopupDisabled = toml::find_or(general, "isTrophyPopupDisabled", false);
trophyNotificationDuration =
@@ -953,6 +963,7 @@ void save(const std::filesystem::path& path) {
data["General"]["isPS4Pro"] = isNeo;
data["General"]["isDevKit"] = isDevKit;
+ data["General"]["isPSNSignedIn"] = isPSNSignedIn;
data["General"]["isTrophyPopupDisabled"] = isTrophyPopupDisabled;
data["General"]["trophyNotificationDuration"] = trophyNotificationDuration;
data["General"]["playBGM"] = playBGM;
@@ -1098,6 +1109,7 @@ void setDefaultValues() {
isHDRAllowed = false;
isNeo = false;
isDevKit = false;
+ isPSNSignedIn = false;
isFullscreen = false;
isTrophyPopupDisabled = false;
playBGM = false;
diff --git a/src/common/config.h b/src/common/config.h
index aba23621c..404854ae2 100644
--- a/src/common/config.h
+++ b/src/common/config.h
@@ -14,7 +14,7 @@ struct GameInstallDir {
bool enabled;
};
-enum HideCursorState : s16 { Never, Idle, Always };
+enum HideCursorState : int { Never, Idle, Always };
void load(const std::filesystem::path& path);
void save(const std::filesystem::path& path);
@@ -39,6 +39,7 @@ bool getCompatibilityEnabled();
bool getCheckCompatibilityOnStartup();
int getBackgroundImageOpacity();
bool getShowBackgroundImage();
+bool getPSNSignedIn();
std::string getLogFilter();
std::string getLogType();
@@ -111,6 +112,7 @@ void setCompatibilityEnabled(bool use);
void setCheckCompatibilityOnStartup(bool use);
void setBackgroundImageOpacity(int opacity);
void setShowBackgroundImage(bool show);
+void setPSNSignedIn(bool sign);
void setCursorState(s16 cursorState);
void setCursorHideTimeout(int newcursorHideTimeout);
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp
index fd0614e1b..05935fbdc 100644
--- a/src/common/logging/filter.cpp
+++ b/src/common/logging/filter.cpp
@@ -141,6 +141,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Lib, Camera) \
SUB(Lib, CompanionHttpd) \
SUB(Lib, CompanionUtil) \
+ SUB(Lib, Voice) \
CLS(Frontend) \
CLS(Render) \
SUB(Render, Vulkan) \
diff --git a/src/common/logging/types.h b/src/common/logging/types.h
index bbfd7455b..1da84b219 100644
--- a/src/common/logging/types.h
+++ b/src/common/logging/types.h
@@ -98,6 +98,7 @@ enum class Class : u8 {
Lib_Fiber, ///< The LibSceFiber implementation.
Lib_Vdec2, ///< The LibSceVideodec2 implementation.
Lib_Videodec, ///< The LibSceVideodec implementation.
+ Lib_Voice, ///< The LibSceVoice implementation.
Lib_RazorCpu, ///< The LibRazorCpu implementation.
Lib_Mouse, ///< The LibSceMouse implementation
Lib_WebBrowserDialog, ///< The LibSceWebBrowserDialog implementation
diff --git a/src/common/scm_rev.cpp.in b/src/common/scm_rev.cpp.in
index 71c4c2d0a..0b113eb31 100644
--- a/src/common/scm_rev.cpp.in
+++ b/src/common/scm_rev.cpp.in
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include
+
#include "common/scm_rev.h"
namespace Common {
@@ -15,5 +17,26 @@ constexpr char g_scm_remote_name[] = "@GIT_REMOTE_NAME@";
constexpr char g_scm_remote_url[] = "@GIT_REMOTE_URL@";
constexpr char g_scm_date[] = "@BUILD_DATE@";
+const std::string GetRemoteNameFromLink() {
+ std::string remote_url(Common::g_scm_remote_url);
+ std::string remote_host;
+ try {
+ if (remote_url.starts_with("http")) {
+ if (*remote_url.rbegin() == '/') {
+ remote_url.pop_back();
+ }
+ remote_host = remote_url.substr(19, remote_url.rfind('/') - 19);
+ } else if (remote_url.starts_with("git@")) {
+ auto after_comma_pos = remote_url.find(':') + 1, slash_pos = remote_url.find('/');
+ remote_host = remote_url.substr(after_comma_pos, slash_pos - after_comma_pos);
+ } else {
+ remote_host = "unknown";
+ }
+ } catch (...) {
+ remote_host = "unknown";
+ }
+ return remote_host;
+}
+
} // namespace
diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h
index 36b844e94..2f6d770bb 100644
--- a/src/common/scm_rev.h
+++ b/src/common/scm_rev.h
@@ -3,6 +3,8 @@
#pragma once
+#include
+
namespace Common {
extern const char g_version[];
@@ -15,4 +17,6 @@ extern const char g_scm_remote_name[];
extern const char g_scm_remote_url[];
extern const char g_scm_date[];
+const std::string GetRemoteNameFromLink();
+
} // namespace Common
diff --git a/src/core/libraries/kernel/equeue.cpp b/src/core/libraries/kernel/equeue.cpp
index 41acbf49a..76d939c50 100644
--- a/src/core/libraries/kernel/equeue.cpp
+++ b/src/core/libraries/kernel/equeue.cpp
@@ -145,6 +145,8 @@ bool EqueueInternal::TriggerEvent(u64 ident, s16 filter, void* trigger_data) {
if (event.event.ident == ident && event.event.filter == filter) {
if (filter == SceKernelEvent::Filter::VideoOut) {
event.TriggerDisplay(trigger_data);
+ } else if (filter == SceKernelEvent::Filter::User) {
+ event.TriggerUser(trigger_data);
} else {
event.Trigger(trigger_data);
}
diff --git a/src/core/libraries/kernel/equeue.h b/src/core/libraries/kernel/equeue.h
index a0367c66a..e6e3c0c53 100644
--- a/src/core/libraries/kernel/equeue.h
+++ b/src/core/libraries/kernel/equeue.h
@@ -98,6 +98,12 @@ struct EqueueEvent {
event.data = reinterpret_cast(data);
}
+ void TriggerUser(void* data) {
+ is_triggered = true;
+ event.fflags++;
+ event.udata = data;
+ }
+
void TriggerDisplay(void* data) {
is_triggered = true;
if (data != nullptr) {
diff --git a/src/core/libraries/kernel/kernel.cpp b/src/core/libraries/kernel/kernel.cpp
index 180850217..930640d0e 100644
--- a/src/core/libraries/kernel/kernel.cpp
+++ b/src/core/libraries/kernel/kernel.cpp
@@ -273,6 +273,10 @@ void RegisterKernel(Core::Loader::SymbolsResolver* sym) {
Libraries::Net::sceNetInetNtop); // TODO fix it to sys_ ...
LIB_FUNCTION("4n51s0zEf0c", "libScePosix", 1, "libkernel", 1, 1,
Libraries::Net::sceNetInetPton); // TODO fix it to sys_ ...
+ LIB_FUNCTION("XVL8So3QJUk", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_connect);
+ LIB_FUNCTION("3e+4Iv7IJ8U", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_accept);
+ LIB_FUNCTION("aNeavPDNKzA", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_sendmsg);
+ LIB_FUNCTION("pxnCmagrtao", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_listen);
}
} // namespace Libraries::Kernel
diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp
index cb41a664a..5e94199e1 100644
--- a/src/core/libraries/kernel/memory.cpp
+++ b/src/core/libraries/kernel/memory.cpp
@@ -8,7 +8,6 @@
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/singleton.h"
-#include "core/file_sys/fs.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/kernel/memory.h"
#include "core/libraries/kernel/orbis_error.h"
@@ -152,7 +151,8 @@ s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u
const VAddr in_addr = reinterpret_cast(*addr);
const auto map_flags = static_cast(flags);
- s32 result = memory->Reserve(addr, in_addr, len, map_flags, alignment);
+ s32 result = memory->MapMemory(addr, in_addr, len, Core::MemoryProt::NoAccess, map_flags,
+ Core::VMAType::Reserved, "anon", false, -1, alignment);
if (result == 0) {
LOG_INFO(Kernel_Vmm, "out_addr = {}", fmt::ptr(*addr));
}
@@ -222,9 +222,10 @@ s32 PS4_SYSV_ABI sceKernelMapDirectMemory2(void** addr, u64 len, s32 type, s32 p
return ret;
}
-s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, std::size_t len, int prot,
- int flags, const char* name) {
-
+s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags,
+ const char* name) {
+ LOG_INFO(Kernel_Vmm, "in_addr = {}, len = {:#x}, prot = {:#x}, flags = {:#x}, name = '{}'",
+ fmt::ptr(*addr_in_out), len, prot, flags, name);
if (len == 0 || !Common::Is16KBAligned(len)) {
LOG_ERROR(Kernel_Vmm, "len is 0 or not 16kb multiple");
return ORBIS_KERNEL_ERROR_EINVAL;
@@ -243,18 +244,14 @@ s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, std::size_t
const VAddr in_addr = reinterpret_cast(*addr_in_out);
const auto mem_prot = static_cast(prot);
const auto map_flags = static_cast(flags);
- SCOPE_EXIT {
- LOG_INFO(Kernel_Vmm,
- "in_addr = {:#x}, out_addr = {}, len = {:#x}, prot = {:#x}, flags = {:#x}",
- in_addr, fmt::ptr(*addr_in_out), len, prot, flags);
- };
auto* memory = Core::Memory::Instance();
- return memory->MapMemory(addr_in_out, in_addr, len, mem_prot, map_flags,
- Core::VMAType::Flexible, name);
+ const auto ret = memory->MapMemory(addr_in_out, in_addr, len, mem_prot, map_flags,
+ Core::VMAType::Flexible, name);
+ LOG_INFO(Kernel_Vmm, "out_addr = {}", fmt::ptr(*addr_in_out));
+ return ret;
}
-s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len, int prot,
- int flags) {
+s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags) {
return sceKernelMapNamedFlexibleMemory(addr_in_out, len, prot, flags, "anon");
}
@@ -263,13 +260,26 @@ int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void**
return memory->QueryProtection(std::bit_cast(addr), start, end, prot);
}
-int PS4_SYSV_ABI sceKernelMProtect(const void* addr, size_t size, int prot) {
+s32 PS4_SYSV_ABI sceKernelMprotect(const void* addr, u64 size, s32 prot) {
+ LOG_INFO(Kernel_Vmm, "called addr = {}, size = {:#x}, prot = {:#x}", fmt::ptr(addr), size,
+ prot);
Core::MemoryManager* memory_manager = Core::Memory::Instance();
Core::MemoryProt protection_flags = static_cast(prot);
return memory_manager->Protect(std::bit_cast(addr), size, protection_flags);
}
-int PS4_SYSV_ABI sceKernelMTypeProtect(const void* addr, size_t size, int mtype, int prot) {
+s32 PS4_SYSV_ABI posix_mprotect(const void* addr, u64 size, s32 prot) {
+ s32 result = sceKernelMprotect(addr, size, prot);
+ if (result < 0) {
+ ErrSceToPosix(result);
+ return -1;
+ }
+ return result;
+}
+
+s32 PS4_SYSV_ABI sceKernelMtypeprotect(const void* addr, u64 size, s32 mtype, s32 prot) {
+ LOG_INFO(Kernel_Vmm, "called addr = {}, size = {:#x}, prot = {:#x}", fmt::ptr(addr), size,
+ prot);
Core::MemoryManager* memory_manager = Core::Memory::Instance();
Core::MemoryProt protection_flags = static_cast(prot);
return memory_manager->Protect(std::bit_cast(addr), size, protection_flags);
@@ -344,7 +354,7 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn
break;
}
case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_PROTECT: {
- result = sceKernelMProtect(entries[i].start, entries[i].length, entries[i].protection);
+ result = sceKernelMprotect(entries[i].start, entries[i].length, entries[i].protection);
LOG_INFO(Kernel_Vmm, "entry = {}, operation = {}, len = {:#x}, result = {}", i,
entries[i].operation, entries[i].length, result);
break;
@@ -359,7 +369,7 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn
break;
}
case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_TYPE_PROTECT: {
- result = sceKernelMTypeProtect(entries[i].start, entries[i].length, entries[i].type,
+ result = sceKernelMtypeprotect(entries[i].start, entries[i].length, entries[i].type,
entries[i].protection);
LOG_INFO(Kernel_Vmm, "entry = {}, operation = {}, len = {:#x}, result = {}", i,
entries[i].operation, entries[i].length, result);
@@ -380,7 +390,7 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn
return result;
}
-s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, const char* name) {
+s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, u64 len, const char* name) {
if (name == nullptr) {
LOG_ERROR(Kernel_Vmm, "name is invalid!");
return ORBIS_KERNEL_ERROR_EFAULT;
@@ -396,8 +406,8 @@ s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, cons
return ORBIS_OK;
}
-s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_t len,
- size_t alignment, u64* physAddrOut) {
+s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, u64 len, u64 alignment,
+ u64* physAddrOut) {
if (searchStart < 0 || searchEnd <= searchStart) {
LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
return ORBIS_KERNEL_ERROR_EINVAL;
@@ -439,10 +449,10 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_
return ORBIS_OK;
}
-s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t alignment, int flags,
- void** addrOut) {
- LOG_INFO(Kernel_Vmm, "addrIn = {}, len = {:#x}, alignment = {:#x}, flags = {:#x}",
- fmt::ptr(addrIn), len, alignment, flags);
+s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addr_in, u64 len, u64 alignment, s32 flags,
+ void** addr_out) {
+ LOG_INFO(Kernel_Vmm, "addr_in = {}, len = {:#x}, alignment = {:#x}, flags = {:#x}",
+ fmt::ptr(addr_in), len, alignment, flags);
if (len == 0 || !Common::Is2MBAligned(len)) {
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 2MB aligned!");
@@ -456,14 +466,16 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t ali
}
auto* memory = Core::Memory::Instance();
- const VAddr in_addr = reinterpret_cast(addrIn);
+ const VAddr in_addr = reinterpret_cast(addr_in);
const auto map_flags = static_cast(flags);
- memory->PoolReserve(addrOut, in_addr, len, map_flags, alignment);
+ u64 map_alignment = alignment == 0 ? 2_MB : alignment;
- return ORBIS_OK;
+ return memory->MapMemory(addr_out, std::bit_cast(addr_in), len,
+ Core::MemoryProt::NoAccess, map_flags, Core::VMAType::PoolReserved,
+ "anon", false, -1, map_alignment);
}
-s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags) {
+s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, u64 len, s32 type, s32 prot, s32 flags) {
if (addr == nullptr) {
LOG_ERROR(Kernel_Vmm, "Address is invalid!");
return ORBIS_KERNEL_ERROR_EINVAL;
@@ -482,7 +494,7 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int
return memory->PoolCommit(in_addr, len, mem_prot);
}
-s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags) {
+s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, u64 len, s32 flags) {
if (addr == nullptr) {
LOG_ERROR(Kernel_Vmm, "Address is invalid!");
return ORBIS_KERNEL_ERROR_EINVAL;
@@ -523,12 +535,12 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolBatch(const OrbisKernelMemoryPoolBatchEntry*
break;
}
case OrbisKernelMemoryPoolOpcode::Protect: {
- result = sceKernelMProtect(entry.protect_params.addr, entry.protect_params.len,
+ result = sceKernelMprotect(entry.protect_params.addr, entry.protect_params.len,
entry.protect_params.prot);
break;
}
case OrbisKernelMemoryPoolOpcode::TypeProtect: {
- result = sceKernelMTypeProtect(
+ result = sceKernelMtypeprotect(
entry.type_protect_params.addr, entry.type_protect_params.len,
entry.type_protect_params.type, entry.type_protect_params.prot);
break;
@@ -553,30 +565,48 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolBatch(const OrbisKernelMemoryPoolBatchEntry*
return result;
}
-int PS4_SYSV_ABI sceKernelMmap(void* addr, u64 len, int prot, int flags, int fd, size_t offset,
- void** res) {
- LOG_INFO(Kernel_Vmm, "called addr = {}, len = {}, prot = {}, flags = {}, fd = {}, offset = {}",
- fmt::ptr(addr), len, prot, flags, fd, offset);
- auto* h = Common::Singleton::Instance();
+void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, s32 prot, s32 flags, s32 fd, s64 phys_addr) {
+ LOG_INFO(Kernel_Vmm,
+ "called addr = {}, len = {}, prot = {}, flags = {}, fd = {}, phys_addr = {}",
+ fmt::ptr(addr), len, prot, flags, fd, phys_addr);
+
+ void* addr_out;
auto* memory = Core::Memory::Instance();
const auto mem_prot = static_cast(prot);
const auto mem_flags = static_cast(flags);
+
+ s32 result = ORBIS_OK;
if (fd == -1) {
- return memory->MapMemory(res, std::bit_cast(addr), len, mem_prot, mem_flags,
- Core::VMAType::Flexible);
+ result = memory->MapMemory(&addr_out, std::bit_cast(addr), len, mem_prot, mem_flags,
+ Core::VMAType::Flexible);
} else {
- const uintptr_t handle = h->GetFile(fd)->f.GetFileMapping();
- return memory->MapFile(res, std::bit_cast(addr), len, mem_prot, mem_flags, handle,
- offset);
+ result = memory->MapFile(&addr_out, std::bit_cast(addr), len, mem_prot, mem_flags,
+ fd, phys_addr);
}
+
+ if (result != ORBIS_OK) {
+ // If the memory mappings fail, mmap sets errno to the appropriate error code,
+ // then returns (void*)-1;
+ ErrSceToPosix(result);
+ return reinterpret_cast(-1);
+ }
+
+ return addr_out;
}
-void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, int prot, int flags, int fd, u64 offset) {
- void* ptr;
- LOG_INFO(Kernel_Vmm, "posix mmap redirect to sceKernelMmap");
- int result = sceKernelMmap(addr, len, prot, flags, fd, offset, &ptr);
- ASSERT(result == 0);
- return ptr;
+s32 PS4_SYSV_ABI sceKernelMmap(void* addr, u64 len, s32 prot, s32 flags, s32 fd, s64 phys_addr,
+ void** res) {
+ void* addr_out = posix_mmap(addr, len, prot, flags, fd, phys_addr);
+
+ if (addr_out == reinterpret_cast(-1)) {
+ // posix_mmap failed, calculate and return the appropriate kernel error code using errno.
+ LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
+ return ErrnoToSceKernelError(*__Error());
+ }
+
+ // Set the outputted address
+ *res = addr_out;
+ return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelConfiguredFlexibleMemorySize(u64* sizeOut) {
@@ -678,8 +708,9 @@ void RegisterMemory(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("n1-v6FgU7MQ", "libkernel", 1, "libkernel", 1, 1,
sceKernelConfiguredFlexibleMemorySize);
- LIB_FUNCTION("9bfdLIyuwCY", "libkernel", 1, "libkernel", 1, 1, sceKernelMTypeProtect);
- LIB_FUNCTION("vSMAm3cxYTY", "libkernel", 1, "libkernel", 1, 1, sceKernelMProtect);
+ LIB_FUNCTION("vSMAm3cxYTY", "libkernel", 1, "libkernel", 1, 1, sceKernelMprotect);
+ LIB_FUNCTION("YQOfxL4QfeU", "libScePosix", 1, "libkernel", 1, 1, posix_mprotect);
+ LIB_FUNCTION("9bfdLIyuwCY", "libkernel", 1, "libkernel", 1, 1, sceKernelMtypeprotect);
// Memory pool
LIB_FUNCTION("qCSfqDILlns", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolExpand);
diff --git a/src/core/libraries/kernel/memory.h b/src/core/libraries/kernel/memory.h
index 92e158a00..ea42e7546 100644
--- a/src/core/libraries/kernel/memory.h
+++ b/src/core/libraries/kernel/memory.h
@@ -141,15 +141,14 @@ s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchE
s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtualQueryInfo* info,
size_t infoSize);
s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u64 alignment);
-s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addrInOut, std::size_t len, int prot,
- int flags, const char* name);
-s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len, int prot,
- int flags);
+s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags,
+ const char* name);
+s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags);
int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot);
-int PS4_SYSV_ABI sceKernelMProtect(const void* addr, size_t size, int prot);
+s32 PS4_SYSV_ABI sceKernelMprotect(const void* addr, u64 size, s32 prot);
-int PS4_SYSV_ABI sceKernelMTypeProtect(const void* addr, size_t size, int mtype, int prot);
+s32 PS4_SYSV_ABI sceKernelMtypeprotect(const void* addr, u64 size, s32 mtype, s32 prot);
int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info,
size_t infoSize);
@@ -165,14 +164,14 @@ s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, int numEnt
s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEntries,
int* numEntriesOut, int flags);
-s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, const char* name);
+s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, u64 len, const char* name);
-s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_t len,
- size_t alignment, u64* physAddrOut);
-s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t alignment, int flags,
- void** addrOut);
-s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags);
-s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags);
+s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, u64 len, u64 alignment,
+ u64* physAddrOut);
+s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addr_in, u64 len, u64 alignment, s32 flags,
+ void** addr_out);
+s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, u64 len, s32 type, s32 prot, s32 flags);
+s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, u64 len, s32 flags);
s32 PS4_SYSV_ABI sceKernelMemoryPoolBatch(const OrbisKernelMemoryPoolBatchEntry* entries, s32 count,
s32* num_processed, s32 flags);
diff --git a/src/core/libraries/kernel/threads/mutex.cpp b/src/core/libraries/kernel/threads/mutex.cpp
index 956e5ef65..3dbade96a 100644
--- a/src/core/libraries/kernel/threads/mutex.cpp
+++ b/src/core/libraries/kernel/threads/mutex.cpp
@@ -426,6 +426,7 @@ void RegisterMutex(Core::Loader::SymbolsResolver* sym) {
// Posix
LIB_FUNCTION("ttHNfU+qDBU", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_init);
LIB_FUNCTION("7H0iTOciTLo", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_lock);
+ LIB_FUNCTION("Io9+nTKXZtA", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_timedlock);
LIB_FUNCTION("2Z+PpY6CaJg", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_unlock);
LIB_FUNCTION("ltCfaGr2JGE", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_destroy);
LIB_FUNCTION("dQHWEsJtoE4", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutexattr_init);
diff --git a/src/core/libraries/kernel/threads/pthread.cpp b/src/core/libraries/kernel/threads/pthread.cpp
index 61310bfb5..59b427d22 100644
--- a/src/core/libraries/kernel/threads/pthread.cpp
+++ b/src/core/libraries/kernel/threads/pthread.cpp
@@ -576,8 +576,19 @@ int PS4_SYSV_ABI posix_pthread_getaffinity_np(PthreadT thread, size_t cpusetsize
if (thread == nullptr || cpusetp == nullptr) {
return POSIX_EINVAL;
}
+
+ auto* thread_state = ThrState::Instance();
+ if (thread == g_curthread) {
+ g_curthread->lock.lock();
+ } else if (auto ret = thread_state->FindThread(thread, /*include dead*/ 0); ret != 0) {
+ return ret;
+ }
+
auto* attr_ptr = &thread->attr;
- return posix_pthread_attr_getaffinity_np(&attr_ptr, cpusetsize, cpusetp);
+ auto ret = posix_pthread_attr_getaffinity_np(&attr_ptr, cpusetsize, cpusetp);
+
+ thread->lock.unlock();
+ return ret;
}
int PS4_SYSV_ABI posix_pthread_setaffinity_np(PthreadT thread, size_t cpusetsize,
@@ -585,11 +596,23 @@ int PS4_SYSV_ABI posix_pthread_setaffinity_np(PthreadT thread, size_t cpusetsize
if (thread == nullptr || cpusetp == nullptr) {
return POSIX_EINVAL;
}
- auto* attr_ptr = &thread->attr;
- if (const auto ret = posix_pthread_attr_setaffinity_np(&attr_ptr, cpusetsize, cpusetp)) {
+
+ auto* thread_state = ThrState::Instance();
+ if (thread == g_curthread) {
+ g_curthread->lock.lock();
+ } else if (auto ret = thread_state->FindThread(thread, /*include dead*/ 0); ret != 0) {
return ret;
}
- return thread->SetAffinity(thread->attr.cpuset);
+
+ auto* attr_ptr = &thread->attr;
+ auto ret = posix_pthread_attr_setaffinity_np(&attr_ptr, cpusetsize, cpusetp);
+
+ if (ret == ORBIS_OK) {
+ ret = thread->SetAffinity(thread->attr.cpuset);
+ }
+
+ thread->lock.unlock();
+ return ret;
}
int PS4_SYSV_ABI scePthreadGetaffinity(PthreadT thread, u64* mask) {
diff --git a/src/core/libraries/kernel/threads/pthread_attr.cpp b/src/core/libraries/kernel/threads/pthread_attr.cpp
index 71f6438a6..e098b00a4 100644
--- a/src/core/libraries/kernel/threads/pthread_attr.cpp
+++ b/src/core/libraries/kernel/threads/pthread_attr.cpp
@@ -306,6 +306,8 @@ void RegisterThreadAttr(Core::Loader::SymbolsResolver* sym) {
posix_pthread_attr_getdetachstate);
LIB_FUNCTION("JKyG3SWyA10", "libScePosix", 1, "libkernel", 1, 1,
posix_pthread_attr_setguardsize);
+ LIB_FUNCTION("qlk9pSLsUmM", "libScePosix", 1, "libkernel", 1, 1,
+ posix_pthread_attr_getschedparam);
// Orbis
LIB_FUNCTION("4+h9EzwKF4I", "libkernel", 1, "libkernel", 1, 1,
diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp
index 45b32846f..762c1e762 100644
--- a/src/core/libraries/libs.cpp
+++ b/src/core/libraries/libs.cpp
@@ -60,6 +60,7 @@
#include "core/libraries/videodec/videodec.h"
#include "core/libraries/videodec/videodec2.h"
#include "core/libraries/videoout/video_out.h"
+#include "core/libraries/voice/voice.h"
#include "core/libraries/web_browser_dialog/webbrowserdialog.h"
#include "core/libraries/zlib/zlib_sce.h"
#include "fiber/fiber.h"
@@ -128,6 +129,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
Libraries::Camera::RegisterlibSceCamera(sym);
Libraries::CompanionHttpd::RegisterlibSceCompanionHttpd(sym);
Libraries::CompanionUtil::RegisterlibSceCompanionUtil(sym);
+ Libraries::Voice::RegisterlibSceVoice(sym);
}
} // namespace Libraries
diff --git a/src/core/libraries/np_manager/np_manager.cpp b/src/core/libraries/np_manager/np_manager.cpp
index a60dcd86f..bc920b5a9 100644
--- a/src/core/libraries/np_manager/np_manager.cpp
+++ b/src/core/libraries/np_manager/np_manager.cpp
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/config.h"
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
@@ -10,6 +11,8 @@
namespace Libraries::NpManager {
+#define SIGNEDIN_STATUS (Config::getPSNSignedIn() ? ORBIS_OK : ORBIS_NP_ERROR_SIGNED_OUT)
+
int PS4_SYSV_ABI Func_EF4378573542A508() {
LOG_ERROR(Lib_NpManager, "(STUBBED) called");
return ORBIS_OK;
@@ -921,9 +924,16 @@ int PS4_SYSV_ABI sceNpGetAccountCountry() {
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceNpGetAccountCountryA() {
- LOG_ERROR(Lib_NpManager, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceNpGetAccountCountryA(OrbisUserServiceUserId user_id,
+ OrbisNpCountryCode* country_code) {
+ LOG_INFO(Lib_NpManager, "(STUBBED) called, user_id = {}", user_id);
+ if (country_code == nullptr) {
+ return ORBIS_NP_ERROR_INVALID_ARGUMENT;
+ }
+ ::memset(country_code, 0, sizeof(OrbisNpCountryCode));
+ // TODO: get NP country code from config
+ ::memcpy(country_code->country_code, "us", 2);
+ return SIGNEDIN_STATUS;
}
int PS4_SYSV_ABI sceNpGetAccountDateOfBirth() {
@@ -941,8 +951,8 @@ int PS4_SYSV_ABI sceNpGetAccountId(OrbisNpOnlineId* online_id, u64* account_id)
if (online_id == nullptr || account_id == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
}
- *account_id = 0;
- return ORBIS_NP_ERROR_SIGNED_OUT;
+ *account_id = 0xFEEDFACE;
+ return SIGNEDIN_STATUS;
}
int PS4_SYSV_ABI sceNpGetAccountIdA(OrbisUserServiceUserId user_id, u64* account_id) {
@@ -950,8 +960,8 @@ int PS4_SYSV_ABI sceNpGetAccountIdA(OrbisUserServiceUserId user_id, u64* account
if (account_id == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
}
- *account_id = 0;
- return ORBIS_NP_ERROR_SIGNED_OUT;
+ *account_id = 0xFEEDFACE;
+ return SIGNEDIN_STATUS;
}
int PS4_SYSV_ABI sceNpGetAccountLanguage() {
@@ -984,7 +994,9 @@ int PS4_SYSV_ABI sceNpGetNpId(OrbisUserServiceUserId user_id, OrbisNpId* np_id)
if (np_id == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
}
- return ORBIS_NP_ERROR_SIGNED_OUT;
+ memset(np_id, 0, sizeof(OrbisNpId));
+ strncpy(np_id->handle.data, Config::getUserName().c_str(), sizeof(np_id->handle.data));
+ return SIGNEDIN_STATUS;
}
int PS4_SYSV_ABI sceNpGetNpReachabilityState() {
@@ -997,7 +1009,9 @@ int PS4_SYSV_ABI sceNpGetOnlineId(OrbisUserServiceUserId user_id, OrbisNpOnlineI
if (online_id == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
}
- return ORBIS_NP_ERROR_SIGNED_OUT;
+ memset(online_id, 0, sizeof(OrbisNpOnlineId));
+ strncpy(online_id->data, Config::getUserName().c_str(), sizeof(online_id->data));
+ return SIGNEDIN_STATUS;
}
int PS4_SYSV_ABI sceNpGetParentalControlInfo() {
@@ -1014,8 +1028,8 @@ int PS4_SYSV_ABI sceNpGetState(OrbisUserServiceUserId user_id, OrbisNpState* sta
if (state == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
}
- *state = OrbisNpState::SignedOut;
- LOG_DEBUG(Lib_NpManager, "Signed out");
+ *state = Config::getPSNSignedIn() ? OrbisNpState::SignedIn : OrbisNpState::SignedOut;
+ LOG_DEBUG(Lib_NpManager, "Signed {}", Config::getPSNSignedIn() ? "in" : "out");
return ORBIS_OK;
}
diff --git a/src/core/libraries/np_manager/np_manager.h b/src/core/libraries/np_manager/np_manager.h
index 02a1a32f6..1078a9f3e 100644
--- a/src/core/libraries/np_manager/np_manager.h
+++ b/src/core/libraries/np_manager/np_manager.h
@@ -32,6 +32,12 @@ struct OrbisNpId {
u8 reserved[8];
};
+struct OrbisNpCountryCode {
+ char country_code[2];
+ char end;
+ char pad;
+};
+
int PS4_SYSV_ABI Func_EF4378573542A508();
int PS4_SYSV_ABI _sceNpIpcCreateMemoryFromKernel();
int PS4_SYSV_ABI _sceNpIpcCreateMemoryFromPool();
@@ -215,7 +221,8 @@ int PS4_SYSV_ABI sceNpCreateRequest();
int PS4_SYSV_ABI sceNpDeleteRequest(int reqId);
int PS4_SYSV_ABI sceNpGetAccountAge();
int PS4_SYSV_ABI sceNpGetAccountCountry();
-int PS4_SYSV_ABI sceNpGetAccountCountryA();
+int PS4_SYSV_ABI sceNpGetAccountCountryA(OrbisUserServiceUserId user_id,
+ OrbisNpCountryCode* country_code);
int PS4_SYSV_ABI sceNpGetAccountDateOfBirth();
int PS4_SYSV_ABI sceNpGetAccountDateOfBirthA();
int PS4_SYSV_ABI sceNpGetAccountId(OrbisNpOnlineId* online_id, u64* account_id);
diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp
index 5dfc68e90..42582783b 100644
--- a/src/core/libraries/pad/pad.cpp
+++ b/src/core/libraries/pad/pad.cpp
@@ -316,22 +316,79 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
pData[i].angularVelocity.y = states[i].angularVelocity.y;
pData[i].angularVelocity.z = states[i].angularVelocity.z;
pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
- if (engine) {
+ pData[i].acceleration.x = states[i].acceleration.x * 0.098;
+ pData[i].acceleration.y = states[i].acceleration.y * 0.098;
+ pData[i].acceleration.z = states[i].acceleration.z * 0.098;
+ pData[i].angularVelocity.x = states[i].angularVelocity.x;
+ pData[i].angularVelocity.y = states[i].angularVelocity.y;
+ pData[i].angularVelocity.z = states[i].angularVelocity.z;
+
+ if (engine && handle == 1) {
const auto gyro_poll_rate = engine->GetAccelPollRate();
if (gyro_poll_rate != 0.0f) {
- GameController::CalculateOrientation(pData[i].acceleration,
- pData[i].angularVelocity,
- 1.0f / gyro_poll_rate, pData[i].orientation);
+ auto now = std::chrono::steady_clock::now();
+ float deltaTime = std::chrono::duration_cast(
+ now - controller->GetLastUpdate())
+ .count() /
+ 1000000.0f;
+ controller->SetLastUpdate(now);
+ Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
+ Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
+ GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity,
+ deltaTime, lastOrientation, outputOrientation);
+ pData[i].orientation = outputOrientation;
+ controller->SetLastOrientation(outputOrientation);
}
}
+
pData[i].touchData.touchNum =
(states[i].touchpad[0].state ? 1 : 0) + (states[i].touchpad[1].state ? 1 : 0);
+
+ if (handle == 1) {
+ if (controller->GetTouchCount() >= 127) {
+ controller->SetTouchCount(0);
+ }
+
+ if (controller->GetSecondaryTouchCount() >= 127) {
+ controller->SetSecondaryTouchCount(0);
+ }
+
+ if (pData->touchData.touchNum == 1 && controller->GetPreviousTouchNum() == 0) {
+ controller->SetTouchCount(controller->GetTouchCount() + 1);
+ controller->SetSecondaryTouchCount(controller->GetTouchCount());
+ } else if (pData->touchData.touchNum == 2 && controller->GetPreviousTouchNum() == 1) {
+ controller->SetSecondaryTouchCount(controller->GetSecondaryTouchCount() + 1);
+ } else if (pData->touchData.touchNum == 0 && controller->GetPreviousTouchNum() > 0) {
+ if (controller->GetTouchCount() < controller->GetSecondaryTouchCount()) {
+ controller->SetTouchCount(controller->GetSecondaryTouchCount());
+ } else {
+ if (controller->WasSecondaryTouchReset()) {
+ controller->SetTouchCount(controller->GetSecondaryTouchCount());
+ controller->UnsetSecondaryTouchResetBool();
+ }
+ }
+ }
+
+ controller->SetPreviousTouchNum(pData->touchData.touchNum);
+
+ if (pData->touchData.touchNum == 1) {
+ states[i].touchpad[0].ID = controller->GetTouchCount();
+ states[i].touchpad[1].ID = 0;
+ } else if (pData->touchData.touchNum == 2) {
+ states[i].touchpad[0].ID = controller->GetTouchCount();
+ states[i].touchpad[1].ID = controller->GetSecondaryTouchCount();
+ }
+ } else {
+ states[i].touchpad[0].ID = 1;
+ states[i].touchpad[1].ID = 2;
+ }
+
pData[i].touchData.touch[0].x = states[i].touchpad[0].x;
pData[i].touchData.touch[0].y = states[i].touchpad[0].y;
- pData[i].touchData.touch[0].id = 1;
+ pData[i].touchData.touch[0].id = states[i].touchpad[0].ID;
pData[i].touchData.touch[1].x = states[i].touchpad[1].x;
pData[i].touchData.touch[1].y = states[i].touchpad[1].y;
- pData[i].touchData.touch[1].id = 2;
+ pData[i].touchData.touch[1].id = states[i].touchpad[1].ID;
pData[i].connected = connected;
pData[i].timestamp = states[i].time;
pData[i].connectedCount = connected_count;
@@ -376,31 +433,85 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
pData->leftStick.x = state.axes[static_cast(Input::Axis::LeftX)];
pData->leftStick.y = state.axes[static_cast(Input::Axis::LeftY)];
pData->rightStick.x = state.axes[static_cast(Input::Axis::RightX)];
+ pData->rightStick.x = state.axes[static_cast(Input::Axis::RightX)];
pData->rightStick.y = state.axes[static_cast(Input::Axis::RightY)];
pData->analogButtons.l2 = state.axes[static_cast(Input::Axis::TriggerLeft)];
pData->analogButtons.r2 = state.axes[static_cast(Input::Axis::TriggerRight)];
- pData->acceleration.x = state.acceleration.x;
- pData->acceleration.y = state.acceleration.y;
- pData->acceleration.z = state.acceleration.z;
+ pData->acceleration.x = state.acceleration.x * 0.098;
+ pData->acceleration.y = state.acceleration.y * 0.098;
+ pData->acceleration.z = state.acceleration.z * 0.098;
pData->angularVelocity.x = state.angularVelocity.x;
pData->angularVelocity.y = state.angularVelocity.y;
pData->angularVelocity.z = state.angularVelocity.z;
pData->orientation = {0.0f, 0.0f, 0.0f, 1.0f};
- if (engine) {
+
+ // Only do this on handle 1 for now
+ if (engine && handle == 1) {
const auto gyro_poll_rate = engine->GetAccelPollRate();
if (gyro_poll_rate != 0.0f) {
+ auto now = std::chrono::steady_clock::now();
+ float deltaTime = std::chrono::duration_cast(
+ now - controller->GetLastUpdate())
+ .count() /
+ 1000000.0f;
+ controller->SetLastUpdate(now);
+ Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
+ Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity,
- 1.0f / gyro_poll_rate, pData->orientation);
+ deltaTime, lastOrientation, outputOrientation);
+ pData->orientation = outputOrientation;
+ controller->SetLastOrientation(outputOrientation);
}
}
pData->touchData.touchNum =
(state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0);
+
+ // Only do this on handle 1 for now
+ if (handle == 1) {
+ if (controller->GetTouchCount() >= 127) {
+ controller->SetTouchCount(0);
+ }
+
+ if (controller->GetSecondaryTouchCount() >= 127) {
+ controller->SetSecondaryTouchCount(0);
+ }
+
+ if (pData->touchData.touchNum == 1 && controller->GetPreviousTouchNum() == 0) {
+ controller->SetTouchCount(controller->GetTouchCount() + 1);
+ controller->SetSecondaryTouchCount(controller->GetTouchCount());
+ } else if (pData->touchData.touchNum == 2 && controller->GetPreviousTouchNum() == 1) {
+ controller->SetSecondaryTouchCount(controller->GetSecondaryTouchCount() + 1);
+ } else if (pData->touchData.touchNum == 0 && controller->GetPreviousTouchNum() > 0) {
+ if (controller->GetTouchCount() < controller->GetSecondaryTouchCount()) {
+ controller->SetTouchCount(controller->GetSecondaryTouchCount());
+ } else {
+ if (controller->WasSecondaryTouchReset()) {
+ controller->SetTouchCount(controller->GetSecondaryTouchCount());
+ controller->UnsetSecondaryTouchResetBool();
+ }
+ }
+ }
+
+ controller->SetPreviousTouchNum(pData->touchData.touchNum);
+
+ if (pData->touchData.touchNum == 1) {
+ state.touchpad[0].ID = controller->GetTouchCount();
+ state.touchpad[1].ID = 0;
+ } else if (pData->touchData.touchNum == 2) {
+ state.touchpad[0].ID = controller->GetTouchCount();
+ state.touchpad[1].ID = controller->GetSecondaryTouchCount();
+ }
+ } else {
+ state.touchpad[0].ID = 1;
+ state.touchpad[1].ID = 2;
+ }
+
pData->touchData.touch[0].x = state.touchpad[0].x;
pData->touchData.touch[0].y = state.touchpad[0].y;
- pData->touchData.touch[0].id = 1;
+ pData->touchData.touch[0].id = state.touchpad[0].ID;
pData->touchData.touch[1].x = state.touchpad[1].x;
pData->touchData.touch[1].y = state.touchpad[1].y;
- pData->touchData.touch[1].id = 2;
+ pData->touchData.touch[1].id = state.touchpad[1].ID;
pData->timestamp = state.time;
pData->connected = true; // isConnected; //TODO fix me proper
pData->connectedCount = 1; // connectedCount;
diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp
index c5208b6dd..da715b3bf 100644
--- a/src/core/libraries/videoout/video_out.cpp
+++ b/src/core/libraries/videoout/video_out.cpp
@@ -282,7 +282,12 @@ s32 PS4_SYSV_ABI sceVideoOutGetVblankStatus(int handle, SceVideoOutVblankStatus*
s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status) {
LOG_INFO(Lib_VideoOut, "called");
- *status = driver->GetPort(handle)->resolution;
+ auto* port = driver->GetPort(handle);
+ if (!port || !port->is_open) {
+ return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
+ }
+
+ *status = port->resolution;
return ORBIS_OK;
}
diff --git a/src/core/libraries/voice/voice.cpp b/src/core/libraries/voice/voice.cpp
new file mode 100644
index 000000000..caa16431a
--- /dev/null
+++ b/src/core/libraries/voice/voice.cpp
@@ -0,0 +1,203 @@
+// 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/voice/voice.h"
+
+namespace Libraries::Voice {
+
+s32 PS4_SYSV_ABI sceVoiceConnectIPortToOPort() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceCreatePort() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceDeletePort() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceDisconnectIPortFromOPort() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceEnd() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceGetBitRate(u32 port_id, u32* bitrate) {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ *bitrate = 48000;
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceGetMuteFlag() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceGetPortAttr() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceGetPortInfo(u32 port_id, OrbisVoicePortInfo* info) {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ info->port_type = 0;
+ info->state = 3;
+ info->byte_count = 0;
+ info->frame_size = 1;
+ info->edge_count = 0;
+ info->reserved = 0;
+
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceGetResourceInfo() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceGetVolume() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceInit() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceInitHQ() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoicePausePort() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoicePausePortAll() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceReadFromOPort() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceResetPort() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceResumePort() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceResumePortAll() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceSetBitRate() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceSetMuteFlag() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceSetMuteFlagAll() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceSetThreadsParams() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceSetVolume() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceStart() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceStop() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceUpdatePort() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceVADAdjustment() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceVADSetVersion() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceVoiceWriteToIPort() {
+ LOG_ERROR(Lib_Voice, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+void RegisterlibSceVoice(Core::Loader::SymbolsResolver* sym) {
+ LIB_FUNCTION("oV9GAdJ23Gw", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceConnectIPortToOPort);
+ LIB_FUNCTION("nXpje5yNpaE", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceCreatePort);
+ LIB_FUNCTION("b7kJI+nx2hg", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceDeletePort);
+ LIB_FUNCTION("ajVj3QG2um4", "libSceVoice", 1, "libSceVoice", 0, 0,
+ sceVoiceDisconnectIPortFromOPort);
+ LIB_FUNCTION("Oo0S5PH7FIQ", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceEnd);
+ LIB_FUNCTION("cJLufzou6bc", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceGetBitRate);
+ LIB_FUNCTION("Pc4z1QjForU", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceGetMuteFlag);
+ LIB_FUNCTION("elcxZTEfHZM", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceGetPortAttr);
+ LIB_FUNCTION("CrLqDwWLoXM", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceGetPortInfo);
+ LIB_FUNCTION("Z6QV6j7igvE", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceGetResourceInfo);
+ LIB_FUNCTION("jjkCjneOYSs", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceGetVolume);
+ LIB_FUNCTION("9TrhuGzberQ", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceInit);
+ LIB_FUNCTION("IPHvnM5+g04", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceInitHQ);
+ LIB_FUNCTION("x0slGBQW+wY", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoicePausePort);
+ LIB_FUNCTION("Dinob0yMRl8", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoicePausePortAll);
+ LIB_FUNCTION("cQ6DGsQEjV4", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceReadFromOPort);
+ LIB_FUNCTION("udAxvCePkUs", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceResetPort);
+ LIB_FUNCTION("gAgN+HkiEzY", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceResumePort);
+ LIB_FUNCTION("jbkJFmOZ9U0", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceResumePortAll);
+ LIB_FUNCTION("TexwmOHQsDg", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceSetBitRate);
+ LIB_FUNCTION("gwUynkEgNFY", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceSetMuteFlag);
+ LIB_FUNCTION("oUha0S-Ij9Q", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceSetMuteFlagAll);
+ LIB_FUNCTION("clyKUyi3RYU", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceSetThreadsParams);
+ LIB_FUNCTION("QBFoAIjJoXQ", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceSetVolume);
+ LIB_FUNCTION("54phPH2LZls", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceStart);
+ LIB_FUNCTION("Ao2YNSA7-Qo", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceStop);
+ LIB_FUNCTION("jSZNP7xJrcw", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceUpdatePort);
+ LIB_FUNCTION("hg9T73LlRiU", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceVADAdjustment);
+ LIB_FUNCTION("wFeAxEeEi-8", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceVADSetVersion);
+ LIB_FUNCTION("YeJl6yDlhW0", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceWriteToIPort);
+};
+
+} // namespace Libraries::Voice
\ No newline at end of file
diff --git a/src/core/libraries/voice/voice.h b/src/core/libraries/voice/voice.h
new file mode 100644
index 000000000..8f008f2cc
--- /dev/null
+++ b/src/core/libraries/voice/voice.h
@@ -0,0 +1,56 @@
+// 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::Voice {
+
+struct OrbisVoicePortInfo {
+ s32 port_type;
+ s32 state;
+ u32* edge;
+ u32 byte_count;
+ u32 frame_size;
+ u16 edge_count;
+ u16 reserved;
+};
+
+s32 PS4_SYSV_ABI sceVoiceConnectIPortToOPort();
+s32 PS4_SYSV_ABI sceVoiceCreatePort();
+s32 PS4_SYSV_ABI sceVoiceDeletePort();
+s32 PS4_SYSV_ABI sceVoiceDisconnectIPortFromOPort();
+s32 PS4_SYSV_ABI sceVoiceEnd();
+s32 PS4_SYSV_ABI sceVoiceGetBitRate(u32 port_id, u32* bitrate);
+s32 PS4_SYSV_ABI sceVoiceGetMuteFlag();
+s32 PS4_SYSV_ABI sceVoiceGetPortAttr();
+s32 PS4_SYSV_ABI sceVoiceGetPortInfo(u32 port_id, OrbisVoicePortInfo* info);
+s32 PS4_SYSV_ABI sceVoiceGetResourceInfo();
+s32 PS4_SYSV_ABI sceVoiceGetVolume();
+s32 PS4_SYSV_ABI sceVoiceInit();
+s32 PS4_SYSV_ABI sceVoiceInitHQ();
+s32 PS4_SYSV_ABI sceVoicePausePort();
+s32 PS4_SYSV_ABI sceVoicePausePortAll();
+s32 PS4_SYSV_ABI sceVoiceReadFromOPort();
+s32 PS4_SYSV_ABI sceVoiceResetPort();
+s32 PS4_SYSV_ABI sceVoiceResumePort();
+s32 PS4_SYSV_ABI sceVoiceResumePortAll();
+s32 PS4_SYSV_ABI sceVoiceSetBitRate();
+s32 PS4_SYSV_ABI sceVoiceSetMuteFlag();
+s32 PS4_SYSV_ABI sceVoiceSetMuteFlagAll();
+s32 PS4_SYSV_ABI sceVoiceSetThreadsParams();
+s32 PS4_SYSV_ABI sceVoiceSetVolume();
+s32 PS4_SYSV_ABI sceVoiceStart();
+s32 PS4_SYSV_ABI sceVoiceStop();
+s32 PS4_SYSV_ABI sceVoiceUpdatePort();
+s32 PS4_SYSV_ABI sceVoiceVADAdjustment();
+s32 PS4_SYSV_ABI sceVoiceVADSetVersion();
+s32 PS4_SYSV_ABI sceVoiceWriteToIPort();
+
+void RegisterlibSceVoice(Core::Loader::SymbolsResolver* sym);
+} // namespace Libraries::Voice
\ No newline at end of file
diff --git a/src/core/libraries/zlib/zlib.cpp b/src/core/libraries/zlib/zlib.cpp
index 899cb5bf6..b304992ad 100644
--- a/src/core/libraries/zlib/zlib.cpp
+++ b/src/core/libraries/zlib/zlib.cpp
@@ -51,7 +51,7 @@ void ZlibTaskThread(const std::stop_token& stop) {
if (!task_queue_cv.wait(lock, stop, [&] { return !task_queue.empty(); })) {
break;
}
- task = task_queue.back();
+ task = task_queue.front();
task_queue.pop();
}
@@ -136,7 +136,7 @@ s32 PS4_SYSV_ABI sceZlibWaitForDone(u64* request_id, const u32* timeout) {
} else {
done_queue_cv.wait(lock, pred);
}
- *request_id = done_queue.back();
+ *request_id = done_queue.front();
done_queue.pop();
}
return ORBIS_OK;
diff --git a/src/core/linker.cpp b/src/core/linker.cpp
index 3e6d8c22e..1f45caf12 100644
--- a/src/core/linker.cpp
+++ b/src/core/linker.cpp
@@ -117,6 +117,18 @@ void Linker::Execute(const std::vector args) {
Common::SetCurrentThreadName("GAME_MainThread");
LoadSharedLibraries();
+ // Simulate libSceGnmDriver initialization, which maps a chunk of direct memory.
+ // Some games fail without accurately emulating this behavior.
+ s64 phys_addr{};
+ s32 result = Libraries::Kernel::sceKernelAllocateDirectMemory(
+ 0, Libraries::Kernel::sceKernelGetDirectMemorySize(), 0x10000, 0x10000, 3, &phys_addr);
+ if (result == 0) {
+ void* addr{reinterpret_cast(0xfe0000000)};
+ result = Libraries::Kernel::sceKernelMapNamedDirectMemory(
+ &addr, 0x10000, 0x13, 0, phys_addr, 0x10000, "SceGnmDriver");
+ }
+ ASSERT_MSG(result == 0, "Unable to emulate libSceGnmDriver initialization");
+
// Start main module.
EntryParams params{};
params.argc = 1;
@@ -320,21 +332,22 @@ bool Linker::Resolve(const std::string& name, Loader::SymbolType sym_type, Modul
sr.type = sym_type;
const auto* record = m_hle_symbols.FindSymbol(sr);
- if (!record) {
- // Check if it an export function
- const auto* p = FindExportedModule(*module, *library);
- if (p && p->export_sym.GetSize() > 0) {
- record = p->export_sym.FindSymbol(sr);
- }
- }
if (record) {
*return_info = *record;
-
Core::Devtools::Widget::ModuleList::AddModule(sr.library);
-
return true;
}
+ // Check if it an export function
+ const auto* p = FindExportedModule(*module, *library);
+ if (p && p->export_sym.GetSize() > 0) {
+ record = p->export_sym.FindSymbol(sr);
+ if (record) {
+ *return_info = *record;
+ return true;
+ }
+ }
+
const auto aeronid = AeroLib::FindByNid(sr.name.c_str());
if (aeronid) {
return_info->name = aeronid->name;
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index ca6a0d6cd..54cae910b 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/config.h"
#include "common/debug.h"
+#include "core/file_sys/fs.h"
#include "core/libraries/kernel/memory.h"
#include "core/libraries/kernel/orbis_error.h"
#include "core/libraries/kernel/process.h"
@@ -67,7 +68,7 @@ void MemoryManager::SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1
}
u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) {
- static constexpr u64 MinSizeToClamp = 512_MB;
+ static constexpr u64 MinSizeToClamp = 2_MB;
// Dont bother with clamping if the size is small so we dont pay a map lookup on every buffer.
if (size < MinSizeToClamp) {
return size;
@@ -214,90 +215,6 @@ void MemoryManager::Free(PAddr phys_addr, size_t size) {
MergeAdjacent(dmem_map, dmem_area);
}
-int MemoryManager::PoolReserve(void** out_addr, VAddr virtual_addr, size_t size,
- MemoryMapFlags flags, u64 alignment) {
- std::scoped_lock lk{mutex};
- alignment = alignment > 0 ? alignment : 2_MB;
- VAddr min_address = Common::AlignUp(impl.SystemManagedVirtualBase(), alignment);
- VAddr mapped_addr = Common::AlignUp(virtual_addr, alignment);
-
- // Fixed mapping means the virtual address must exactly match the provided one.
- if (True(flags & MemoryMapFlags::Fixed)) {
- // Make sure we're mapping to a valid address
- mapped_addr = mapped_addr > min_address ? mapped_addr : min_address;
- auto vma = FindVMA(mapped_addr)->second;
- size_t remaining_size = vma.base + vma.size - mapped_addr;
- // If the VMA is mapped or there's not enough space, unmap the region first.
- if (vma.IsMapped() || remaining_size < size) {
- UnmapMemoryImpl(mapped_addr, size);
- vma = FindVMA(mapped_addr)->second;
- }
- }
-
- if (False(flags & MemoryMapFlags::Fixed)) {
- // When MemoryMapFlags::Fixed is not specified, and mapped_addr is 0,
- // search from address 0x200000000 instead.
- mapped_addr = mapped_addr == 0 ? 0x200000000 : mapped_addr;
- mapped_addr = SearchFree(mapped_addr, size, alignment);
- if (mapped_addr == -1) {
- // No suitable memory areas to map to
- return ORBIS_KERNEL_ERROR_ENOMEM;
- }
- }
-
- // Add virtual memory area
- const auto new_vma_handle = CarveVMA(mapped_addr, size);
- auto& new_vma = new_vma_handle->second;
- new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
- new_vma.prot = MemoryProt::NoAccess;
- new_vma.name = "anon";
- new_vma.type = VMAType::PoolReserved;
-
- *out_addr = std::bit_cast(mapped_addr);
- return ORBIS_OK;
-}
-
-int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
- u64 alignment) {
- std::scoped_lock lk{mutex};
-
- virtual_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
- alignment = alignment > 0 ? alignment : 16_KB;
- VAddr mapped_addr = alignment > 0 ? Common::AlignUp(virtual_addr, alignment) : virtual_addr;
-
- // Fixed mapping means the virtual address must exactly match the provided one.
- if (True(flags & MemoryMapFlags::Fixed)) {
- auto vma = FindVMA(mapped_addr)->second;
- size_t remaining_size = vma.base + vma.size - mapped_addr;
- // If the VMA is mapped or there's not enough space, unmap the region first.
- if (vma.IsMapped() || remaining_size < size) {
- UnmapMemoryImpl(mapped_addr, size);
- vma = FindVMA(mapped_addr)->second;
- }
- }
-
- // Find the first free area starting with provided virtual address.
- if (False(flags & MemoryMapFlags::Fixed)) {
- mapped_addr = SearchFree(mapped_addr, size, alignment);
- if (mapped_addr == -1) {
- // No suitable memory areas to map to
- return ORBIS_KERNEL_ERROR_ENOMEM;
- }
- }
-
- // Add virtual memory area
- const auto new_vma_handle = CarveVMA(mapped_addr, size);
- auto& new_vma = new_vma_handle->second;
- new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
- new_vma.prot = MemoryProt::NoAccess;
- new_vma.name = "anon";
- new_vma.type = VMAType::Reserved;
- MergeAdjacent(vma_map, new_vma_handle);
-
- *out_addr = std::bit_cast(mapped_addr);
- return ORBIS_OK;
-}
-
int MemoryManager::PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot) {
std::scoped_lock lk{mutex};
@@ -351,7 +268,7 @@ int MemoryManager::PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot)
return ORBIS_OK;
}
-int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
+s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot,
MemoryMapFlags flags, VMAType type, std::string_view name,
bool is_exec, PAddr phys_addr, u64 alignment) {
std::scoped_lock lk{mutex};
@@ -366,17 +283,18 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M
VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
// Fixed mapping means the virtual address must exactly match the provided one.
- if (True(flags & MemoryMapFlags::Fixed)) {
+ // On a PS4, the Fixed flag is ignored if address 0 is provided.
+ if (True(flags & MemoryMapFlags::Fixed) && virtual_addr != 0) {
auto vma = FindVMA(mapped_addr)->second;
- size_t remaining_size = vma.base + vma.size - mapped_addr;
// There's a possible edge case where we're mapping to a partially reserved range.
// To account for this, unmap any reserved areas within this mapping range first.
auto unmap_addr = mapped_addr;
auto unmap_size = size;
+
// If flag NoOverwrite is provided, don't overwrite mapped VMAs.
// When it isn't provided, VMAs can be overwritten regardless of if they're mapped.
while ((False(flags & MemoryMapFlags::NoOverwrite) || !vma.IsMapped()) &&
- unmap_addr < mapped_addr + size && remaining_size < size) {
+ unmap_addr < mapped_addr + size) {
auto unmapped = UnmapBytesFromEntry(unmap_addr, vma, unmap_size);
unmap_addr += unmapped;
unmap_size -= unmapped;
@@ -384,51 +302,65 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M
}
vma = FindVMA(mapped_addr)->second;
- remaining_size = vma.base + vma.size - mapped_addr;
+ auto remaining_size = vma.base + vma.size - mapped_addr;
if (vma.IsMapped() || remaining_size < size) {
LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at address {:#x}", size, mapped_addr);
return ORBIS_KERNEL_ERROR_ENOMEM;
}
- }
-
- // Find the first free area starting with provided virtual address.
- if (False(flags & MemoryMapFlags::Fixed)) {
- // Provided address needs to be aligned before we can map.
+ } else {
+ // When MemoryMapFlags::Fixed is not specified, and mapped_addr is 0,
+ // search from address 0x200000000 instead.
alignment = alignment > 0 ? alignment : 16_KB;
- mapped_addr = SearchFree(Common::AlignUp(mapped_addr, alignment), size, alignment);
+ mapped_addr = virtual_addr == 0 ? 0x200000000 : mapped_addr;
+ mapped_addr = SearchFree(mapped_addr, size, alignment);
if (mapped_addr == -1) {
// No suitable memory areas to map to
return ORBIS_KERNEL_ERROR_ENOMEM;
}
}
- // Perform the mapping.
- *out_addr = impl.Map(mapped_addr, size, alignment, phys_addr, is_exec);
- TRACK_ALLOC(*out_addr, size, "VMEM");
+ // Create a memory area representing this mapping.
+ const auto new_vma_handle = CarveVMA(mapped_addr, size);
+ auto& new_vma = new_vma_handle->second;
- auto& new_vma = CarveVMA(mapped_addr, size)->second;
- new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
- new_vma.prot = prot;
- new_vma.name = name;
- new_vma.type = type;
- new_vma.is_exec = is_exec;
-
- if (type == VMAType::Direct) {
- new_vma.phys_base = phys_addr;
- }
+ // If type is Flexible, we need to track how much flexible memory is used here.
if (type == VMAType::Flexible) {
flexible_usage += size;
}
- if (IsValidGpuMapping(mapped_addr, size)) {
- rasterizer->MapMemory(mapped_addr, size);
+ new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
+ new_vma.prot = prot;
+ new_vma.name = name;
+ new_vma.type = type;
+ new_vma.phys_base = phys_addr == -1 ? 0 : phys_addr;
+ new_vma.is_exec = is_exec;
+
+ if (type == VMAType::Reserved) {
+ // Technically this should be done for direct and flexible mappings too,
+ // But some Windows-specific limitations make that hard to accomplish.
+ MergeAdjacent(vma_map, new_vma_handle);
}
+ if (type == VMAType::Reserved || type == VMAType::PoolReserved) {
+ // For Reserved/PoolReserved mappings, we don't perform any address space allocations.
+ // Just set out_addr to mapped_addr instead.
+ *out_addr = std::bit_cast(mapped_addr);
+ } else {
+ // If this is not a reservation, then map to GPU and address space
+ if (IsValidGpuMapping(mapped_addr, size)) {
+ rasterizer->MapMemory(mapped_addr, size);
+ }
+ *out_addr = impl.Map(mapped_addr, size, alignment, phys_addr, is_exec);
+ }
+
+ TRACK_ALLOC(*out_addr, size, "VMEM");
return ORBIS_OK;
}
-int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
- MemoryMapFlags flags, uintptr_t fd, size_t offset) {
+s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot,
+ MemoryMapFlags flags, s32 fd, s64 phys_addr) {
+ auto* h = Common::Singleton::Instance();
+
VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
const size_t size_aligned = Common::AlignUp(size, 16_KB);
@@ -449,8 +381,19 @@ int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, Mem
vma.base, vma.base + vma.size, virtual_addr, virtual_addr + size);
}
- // Map the file.
- impl.MapFile(mapped_addr, size_aligned, offset, std::bit_cast(prot), fd);
+ // Get the file to map
+ auto file = h->GetFile(fd);
+ if (file == nullptr) {
+ return ORBIS_KERNEL_ERROR_EBADF;
+ }
+
+ const auto handle = file->f.GetFileMapping();
+
+ impl.MapFile(mapped_addr, size_aligned, phys_addr, std::bit_cast(prot), handle);
+
+ if (prot >= MemoryProt::GpuRead) {
+ ASSERT_MSG(false, "Files cannot be mapped to GPU memory");
+ }
// Add virtual memory area
auto& new_vma = CarveVMA(mapped_addr, size_aligned)->second;
@@ -485,14 +428,15 @@ s32 MemoryManager::PoolDecommit(VAddr virtual_addr, size_t size) {
}
if (type == VMAType::Pooled) {
+ // We always map PoolCommitted memory to GPU, so unmap when decomitting.
+ if (IsValidGpuMapping(virtual_addr, size)) {
+ rasterizer->UnmapMemory(virtual_addr, size);
+ }
+
// Track how much pooled memory is decommitted
pool_budget += size;
}
- if (IsValidGpuMapping(virtual_addr, size)) {
- rasterizer->UnmapMemory(virtual_addr, size);
- }
-
// Mark region as free and attempt to coalesce it with neighbours.
const auto new_it = CarveVMA(virtual_addr, size);
auto& vma = new_it->second;
@@ -528,18 +472,16 @@ u64 MemoryManager::UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma
const auto adjusted_size =
vma_base_size - start_in_vma < size ? vma_base_size - start_in_vma : size;
const bool has_backing = type == VMAType::Direct || type == VMAType::File;
+ const auto prot = vma_base.prot;
if (type == VMAType::Free) {
return adjusted_size;
}
+
if (type == VMAType::Flexible) {
flexible_usage -= adjusted_size;
}
- if (IsValidGpuMapping(virtual_addr, adjusted_size)) {
- rasterizer->UnmapMemory(virtual_addr, adjusted_size);
- }
-
// Mark region as free and attempt to coalesce it with neighbours.
const auto new_it = CarveVMA(virtual_addr, adjusted_size);
auto& vma = new_it->second;
@@ -552,6 +494,11 @@ u64 MemoryManager::UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma
auto& post_merge_vma = post_merge_it->second;
bool readonly_file = post_merge_vma.prot == MemoryProt::CpuRead && type == VMAType::File;
if (type != VMAType::Reserved && type != VMAType::PoolReserved) {
+ // If this mapping has GPU access, unmap from GPU.
+ if (IsValidGpuMapping(virtual_addr, size)) {
+ rasterizer->UnmapMemory(virtual_addr, size);
+ }
+
// Unmap the memory region.
impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + adjusted_size,
phys_base, is_exec, has_backing, readonly_file);
@@ -605,20 +552,8 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, size_t s
vma_base.size - start_in_vma < size ? vma_base.size - start_in_vma : size;
if (vma_base.type == VMAType::Free) {
- LOG_ERROR(Kernel_Vmm, "Cannot change protection on free memory region");
- return ORBIS_KERNEL_ERROR_EINVAL;
- }
-
- // Validate protection flags
- constexpr static MemoryProt valid_flags = MemoryProt::NoAccess | MemoryProt::CpuRead |
- MemoryProt::CpuReadWrite | MemoryProt::GpuRead |
- MemoryProt::GpuWrite | MemoryProt::GpuReadWrite;
-
- MemoryProt invalid_flags = prot & ~valid_flags;
- if (u32(invalid_flags) != 0 && u32(invalid_flags) != u32(MemoryProt::NoAccess)) {
- LOG_ERROR(Kernel_Vmm, "Invalid protection flags: prot = {:#x}, invalid flags = {:#x}",
- u32(prot), u32(invalid_flags));
- return ORBIS_KERNEL_ERROR_EINVAL;
+ // On PS4, protecting freed memory does nothing.
+ return adjusted_size;
}
// Change protection
@@ -650,11 +585,25 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, size_t s
s32 MemoryManager::Protect(VAddr addr, size_t size, MemoryProt prot) {
std::scoped_lock lk{mutex};
- s64 protected_bytes = 0;
+ // Validate protection flags
+ constexpr static MemoryProt valid_flags = MemoryProt::NoAccess | MemoryProt::CpuRead |
+ MemoryProt::CpuReadWrite | MemoryProt::GpuRead |
+ MemoryProt::GpuWrite | MemoryProt::GpuReadWrite;
+
+ MemoryProt invalid_flags = prot & ~valid_flags;
+ if (invalid_flags != MemoryProt::NoAccess) {
+ LOG_ERROR(Kernel_Vmm, "Invalid protection flags");
+ return ORBIS_KERNEL_ERROR_EINVAL;
+ }
+
+ // Align addr and size to the nearest page boundary.
auto aligned_addr = Common::AlignDown(addr, 16_KB);
auto aligned_size = Common::AlignUp(size + addr - aligned_addr, 16_KB);
- do {
+
+ // Protect all VMAs between aligned_addr and aligned_addr + aligned_size.
+ s64 protected_bytes = 0;
+ while (protected_bytes < aligned_size) {
auto it = FindVMA(aligned_addr + protected_bytes);
auto& vma_base = it->second;
ASSERT_MSG(vma_base.Contains(addr + protected_bytes, 0), "Address {:#x} is out of bounds",
@@ -667,7 +616,7 @@ s32 MemoryManager::Protect(VAddr addr, size_t size, MemoryProt prot) {
return result;
}
protected_bytes += result;
- } while (protected_bytes < aligned_size);
+ }
return ORBIS_OK;
}
@@ -798,12 +747,31 @@ s32 MemoryManager::SetDirectMemoryType(s64 phys_addr, s32 memory_type) {
return ORBIS_OK;
}
-void MemoryManager::NameVirtualRange(VAddr virtual_addr, size_t size, std::string_view name) {
- auto it = FindVMA(virtual_addr);
+void MemoryManager::NameVirtualRange(VAddr virtual_addr, u64 size, std::string_view name) {
+ // Sizes are aligned up to the nearest 16_KB
+ auto aligned_size = Common::AlignUp(size, 16_KB);
+ // Addresses are aligned down to the nearest 16_KB
+ auto aligned_addr = Common::AlignDown(virtual_addr, 16_KB);
- ASSERT_MSG(it->second.Contains(virtual_addr, size),
- "Range provided is not fully contained in vma");
- it->second.name = name;
+ auto it = FindVMA(aligned_addr);
+ s64 remaining_size = aligned_size;
+ auto current_addr = aligned_addr;
+ while (remaining_size > 0) {
+ // Nothing needs to be done to free VMAs
+ if (!it->second.IsFree()) {
+ if (remaining_size < it->second.size) {
+ // We should split VMAs here, but this could cause trouble for Windows.
+ // Instead log a warning and name the whole VMA.
+ // it = CarveVMA(current_addr, remaining_size);
+ LOG_WARNING(Kernel_Vmm, "Trying to partially name a range");
+ }
+ auto& vma = it->second;
+ vma.name = name;
+ }
+ remaining_size -= it->second.size;
+ current_addr += it->second.size;
+ it = FindVMA(current_addr);
+ }
}
void MemoryManager::InvalidateMemory(const VAddr addr, const u64 size) const {
@@ -824,6 +792,8 @@ VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment)
ASSERT_MSG(virtual_addr <= max_search_address, "Input address {:#x} is out of bounds",
virtual_addr);
+ // Align up the virtual_addr first.
+ virtual_addr = Common::AlignUp(virtual_addr, alignment);
auto it = FindVMA(virtual_addr);
// If the VMA is free and contains the requested mapping we are done.
diff --git a/src/core/memory.h b/src/core/memory.h
index 883b48854..b3ebe3c27 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -183,20 +183,14 @@ public:
void Free(PAddr phys_addr, size_t size);
- int PoolReserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
- u64 alignment = 0);
-
- int Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
- u64 alignment = 0);
-
int PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot);
- int MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
+ s32 MapMemory(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot,
MemoryMapFlags flags, VMAType type, std::string_view name = "anon",
bool is_exec = false, PAddr phys_addr = -1, u64 alignment = 0);
- int MapFile(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
- MemoryMapFlags flags, uintptr_t fd, size_t offset);
+ s32 MapFile(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot,
+ MemoryMapFlags flags, s32 fd, s64 phys_addr);
s32 PoolDecommit(VAddr virtual_addr, size_t size);
@@ -221,7 +215,7 @@ public:
s32 SetDirectMemoryType(s64 phys_addr, s32 memory_type);
- void NameVirtualRange(VAddr virtual_addr, size_t size, std::string_view name);
+ void NameVirtualRange(VAddr virtual_addr, u64 size, std::string_view name);
void InvalidateMemory(VAddr addr, u64 size) const;
diff --git a/src/emulator.cpp b/src/emulator.cpp
index 2ad8446ab..bb50b8686 100644
--- a/src/emulator.cpp
+++ b/src/emulator.cpp
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include
#include
#include
@@ -62,8 +63,13 @@ Emulator::~Emulator() {
Config::saveMainWindow(config_dir / "config.toml");
}
-void Emulator::Run(const std::filesystem::path& file, const std::vector args) {
+void Emulator::Run(std::filesystem::path file, const std::vector args) {
+ if (std::filesystem::is_directory(file)) {
+ file /= "eboot.bin";
+ }
+
const auto eboot_name = file.filename().string();
+
auto game_folder = file.parent_path();
if (const auto game_folder_name = game_folder.filename().string();
game_folder_name.ends_with("-UPDATE") || game_folder_name.ends_with("-patch")) {
@@ -114,6 +120,11 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector", id, title, app_version);
std::string window_title = "";
std::string remote_url(Common::g_scm_remote_url);
- std::string remote_host;
- try {
- if (*remote_url.rbegin() == '/') {
- remote_url.pop_back();
- }
- remote_host = remote_url.substr(19, remote_url.rfind('/') - 19);
- } catch (...) {
- remote_host = "unknown";
- }
+ std::string remote_host = Common::GetRemoteNameFromLink();
if (Common::g_is_release) {
if (remote_host == "shadps4-emu" || remote_url.length() == 0) {
window_title = fmt::format("shadPS4 v{} | {}", Common::g_version, game_title);
@@ -258,7 +261,11 @@ void Emulator::Run(const std::filesystem::path& file, const std::vectorGetHostPath("/app0/" + eboot_name);
- linker->LoadModule(eboot_path);
+ if (linker->LoadModule(eboot_path) == -1) {
+ LOG_CRITICAL(Loader, "Failed to load game's eboot.bin: {}",
+ std::filesystem::absolute(eboot_path).string());
+ std::quick_exit(0);
+ }
// check if we have system modules to load
LoadSystemModules(game_info.game_serial);
diff --git a/src/emulator.h b/src/emulator.h
index 08c2807a1..257ccd694 100644
--- a/src/emulator.h
+++ b/src/emulator.h
@@ -25,7 +25,7 @@ public:
Emulator();
~Emulator();
- void Run(const std::filesystem::path& file, const std::vector args = {});
+ void Run(std::filesystem::path file, const std::vector args = {});
void UpdatePlayTime(const std::string& serial);
private:
diff --git a/src/input/controller.cpp b/src/input/controller.cpp
index bb8db9a7c..42cabb837 100644
--- a/src/input/controller.cpp
+++ b/src/input/controller.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include
@@ -165,69 +165,37 @@ void GameController::Acceleration(int id, const float acceleration[3]) {
AddState(state);
}
-// Stolen from
-// https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs
-float eInt[3] = {0.0f, 0.0f, 0.0f}; // Integral error terms
-const float Kp = 50.0f; // Proportional gain
-const float Ki = 1.0f; // Integral gain
-Libraries::Pad::OrbisFQuaternion o = {1, 0, 0, 0};
void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
Libraries::Pad::OrbisFVector3& angularVelocity,
float deltaTime,
+ Libraries::Pad::OrbisFQuaternion& lastOrientation,
Libraries::Pad::OrbisFQuaternion& orientation) {
- float ax = acceleration.x, ay = acceleration.y, az = acceleration.z;
- float gx = angularVelocity.x, gy = angularVelocity.y, gz = angularVelocity.z;
+ Libraries::Pad::OrbisFQuaternion q = lastOrientation;
+ Libraries::Pad::OrbisFQuaternion ω = {angularVelocity.x, angularVelocity.y, angularVelocity.z,
+ 0.0f};
- float q1 = o.w, q2 = o.x, q3 = o.y, q4 = o.z;
+ Libraries::Pad::OrbisFQuaternion qω = {q.w * ω.x + q.x * ω.w + q.y * ω.z - q.z * ω.y,
+ q.w * ω.y + q.y * ω.w + q.z * ω.x - q.x * ω.z,
+ q.w * ω.z + q.z * ω.w + q.x * ω.y - q.y * ω.x,
+ q.w * ω.w - q.x * ω.x - q.y * ω.y - q.z * ω.z};
- // Normalize accelerometer measurement
- float norm = std::sqrt(ax * ax + ay * ay + az * az);
- if (norm == 0.0f || deltaTime == 0.0f)
- return; // Handle NaN
- norm = 1.0f / norm;
- ax *= norm;
- ay *= norm;
- az *= norm;
+ Libraries::Pad::OrbisFQuaternion qDot = {0.5f * qω.x, 0.5f * qω.y, 0.5f * qω.z, 0.5f * qω.w};
- // Estimated direction of gravity
- float vx = 2.0f * (q2 * q4 - q1 * q3);
- float vy = 2.0f * (q1 * q2 + q3 * q4);
- float vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
+ q.x += qDot.x * deltaTime;
+ q.y += qDot.y * deltaTime;
+ q.z += qDot.z * deltaTime;
+ q.w += qDot.w * deltaTime;
- // Error is cross product between estimated direction and measured direction of gravity
- float ex = (ay * vz - az * vy);
- float ey = (az * vx - ax * vz);
- float ez = (ax * vy - ay * vx);
- if (Ki > 0.0f) {
- eInt[0] += ex * deltaTime; // Accumulate integral error
- eInt[1] += ey * deltaTime;
- eInt[2] += ez * deltaTime;
- } else {
- eInt[0] = eInt[1] = eInt[2] = 0.0f; // Prevent integral wind-up
- }
+ float norm = std::sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
+ q.x /= norm;
+ q.y /= norm;
+ q.z /= norm;
+ q.w /= norm;
- // Apply feedback terms
- gx += Kp * ex + Ki * eInt[0];
- gy += Kp * ey + Ki * eInt[1];
- gz += Kp * ez + Ki * eInt[2];
-
- //// Integrate rate of change of quaternion
- q1 += (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * deltaTime);
- q2 += (q1 * gx + q3 * gz - q4 * gy) * (0.5f * deltaTime);
- q3 += (q1 * gy - q2 * gz + q4 * gx) * (0.5f * deltaTime);
- q4 += (q1 * gz + q2 * gy - q3 * gx) * (0.5f * deltaTime);
-
- // Normalize quaternion
- norm = std::sqrt(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4);
- norm = 1.0f / norm;
- orientation.w = q1 * norm;
- orientation.x = q2 * norm;
- orientation.y = q3 * norm;
- orientation.z = q4 * norm;
- o.w = q1 * norm;
- o.x = q2 * norm;
- o.y = q3 * norm;
- o.z = q4 * norm;
+ orientation.x = q.x;
+ orientation.y = q.y;
+ orientation.z = q.z;
+ orientation.w = q.w;
LOG_DEBUG(Lib_Pad, "Calculated orientation: {:.2f} {:.2f} {:.2f} {:.2f}", orientation.x,
orientation.y, orientation.z, orientation.w);
}
@@ -260,6 +228,69 @@ void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, f
}
}
+u8 GameController::GetTouchCount() {
+ std::scoped_lock lock{m_mutex};
+ return m_touch_count;
+}
+
+void GameController::SetTouchCount(u8 touchCount) {
+ std::scoped_lock lock{m_mutex};
+ m_touch_count = touchCount;
+}
+
+u8 GameController::GetSecondaryTouchCount() {
+ std::scoped_lock lock{m_mutex};
+ return m_secondary_touch_count;
+}
+
+void GameController::SetSecondaryTouchCount(u8 touchCount) {
+ std::scoped_lock lock{m_mutex};
+ m_secondary_touch_count = touchCount;
+ if (touchCount == 0) {
+ m_was_secondary_reset = true;
+ }
+}
+
+u8 GameController::GetPreviousTouchNum() {
+ std::scoped_lock lock{m_mutex};
+ return m_previous_touchnum;
+}
+
+void GameController::SetPreviousTouchNum(u8 touchNum) {
+ std::scoped_lock lock{m_mutex};
+ m_previous_touchnum = touchNum;
+}
+
+bool GameController::WasSecondaryTouchReset() {
+ std::scoped_lock lock{m_mutex};
+ return m_was_secondary_reset;
+}
+
+void GameController::UnsetSecondaryTouchResetBool() {
+ std::scoped_lock lock{m_mutex};
+ m_was_secondary_reset = false;
+}
+
+void GameController::SetLastOrientation(Libraries::Pad::OrbisFQuaternion& orientation) {
+ std::scoped_lock lock{m_mutex};
+ m_orientation = orientation;
+}
+
+Libraries::Pad::OrbisFQuaternion GameController::GetLastOrientation() {
+ std::scoped_lock lock{m_mutex};
+ return m_orientation;
+}
+
+std::chrono::steady_clock::time_point GameController::GetLastUpdate() {
+ std::scoped_lock lock{m_mutex};
+ return m_last_update;
+}
+
+void GameController::SetLastUpdate(std::chrono::steady_clock::time_point lastUpdate) {
+ std::scoped_lock lock{m_mutex};
+ m_last_update = lastUpdate;
+}
+
void GameController::SetEngine(std::unique_ptr engine) {
std::scoped_lock _{m_mutex};
m_engine = std::move(engine);
diff --git a/src/input/controller.h b/src/input/controller.h
index bbaed75ea..f427a55ec 100644
--- a/src/input/controller.h
+++ b/src/input/controller.h
@@ -23,6 +23,7 @@ enum class Axis {
};
struct TouchpadEntry {
+ u8 ID = 0;
bool state{};
u16 x{};
u16 y{};
@@ -82,9 +83,23 @@ public:
Engine* GetEngine();
u32 Poll();
+ u8 GetTouchCount();
+ void SetTouchCount(u8 touchCount);
+ u8 GetSecondaryTouchCount();
+ void SetSecondaryTouchCount(u8 touchCount);
+ u8 GetPreviousTouchNum();
+ void SetPreviousTouchNum(u8 touchNum);
+ bool WasSecondaryTouchReset();
+ void UnsetSecondaryTouchResetBool();
+
+ void SetLastOrientation(Libraries::Pad::OrbisFQuaternion& orientation);
+ Libraries::Pad::OrbisFQuaternion GetLastOrientation();
+ std::chrono::steady_clock::time_point GetLastUpdate();
+ void SetLastUpdate(std::chrono::steady_clock::time_point lastUpdate);
static void CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
Libraries::Pad::OrbisFVector3& angularVelocity,
float deltaTime,
+ Libraries::Pad::OrbisFQuaternion& lastOrientation,
Libraries::Pad::OrbisFQuaternion& orientation);
private:
@@ -98,8 +113,15 @@ private:
int m_connected_count = 0;
u32 m_states_num = 0;
u32 m_first_state = 0;
+ u8 m_touch_count = 0;
+ u8 m_secondary_touch_count = 0;
+ u8 m_previous_touch_count = 0;
+ u8 m_previous_touchnum = 0;
+ bool m_was_secondary_reset = false;
std::array m_states;
std::array m_private;
+ std::chrono::steady_clock::time_point m_last_update = {};
+ Libraries::Pad::OrbisFQuaternion m_orientation = {0.0f, 0.0f, 0.0f, 1.0f};
std::unique_ptr m_engine = nullptr;
};
diff --git a/src/qt_gui/check_update.cpp b/src/qt_gui/check_update.cpp
index 550fdddb5..b0858840a 100644
--- a/src/qt_gui/check_update.cpp
+++ b/src/qt_gui/check_update.cpp
@@ -137,7 +137,7 @@ tr("The Auto Updater allows up to 60 update checks per hour.\\nYou have reached
}
}
- latestRev = latestVersion.right(7);
+ latestRev = latestVersion.right(40);
latestDate = jsonObj["published_at"].toString();
QJsonArray assets = jsonObj["assets"].toArray();
@@ -167,7 +167,7 @@ tr("The Auto Updater allows up to 60 update checks per hour.\\nYou have reached
QDateTime dateTime = QDateTime::fromString(latestDate, Qt::ISODate);
latestDate = dateTime.isValid() ? dateTime.toString("yyyy-MM-dd HH:mm:ss") : "Unknown date";
- if (latestRev == currentRev.left(7)) {
+ if (latestRev == currentRev) {
if (showMessage) {
QMessageBox::information(this, tr("Auto Updater"),
tr("Your version is already up to date!"));
@@ -215,7 +215,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate,
"%3 | "
"(%4) | "
"
")
- .arg(currentRev.left(7), currentDate, latestRev, latestDate);
+ .arg(currentRev.left(7), currentDate, latestRev.left(7), latestDate);
QLabel* updateLabel = new QLabel(updateText, this);
layout->addWidget(updateLabel);
diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp
index 1966aa52b..906a3066e 100644
--- a/src/qt_gui/main_window.cpp
+++ b/src/qt_gui/main_window.cpp
@@ -56,15 +56,7 @@ bool MainWindow::Init() {
setMinimumSize(720, 405);
std::string window_title = "";
std::string remote_url(Common::g_scm_remote_url);
- std::string remote_host;
- try {
- if (*remote_url.rbegin() == '/') {
- remote_url.pop_back();
- }
- remote_host = remote_url.substr(19, remote_url.rfind('/') - 19);
- } catch (...) {
- remote_host = "unknown";
- }
+ std::string remote_host = Common::GetRemoteNameFromLink();
if (Common::g_is_release) {
if (remote_host == "shadps4-emu" || remote_url.length() == 0) {
window_title = fmt::format("shadPS4 v{}", Common::g_version);
@@ -380,16 +372,15 @@ void MainWindow::CreateConnects() {
connect(ui->refreshGameListAct, &QAction::triggered, this, &MainWindow::RefreshGameTable);
connect(ui->refreshButton, &QPushButton::clicked, this, &MainWindow::RefreshGameTable);
connect(ui->showGameListAct, &QAction::triggered, this, &MainWindow::ShowGameList);
- connect(this, &MainWindow::ExtractionFinished, this, &MainWindow::RefreshGameTable);
connect(ui->toggleLabelsAct, &QAction::toggled, this, &MainWindow::toggleLabelsUnderIcons);
connect(ui->fullscreenButton, &QPushButton::clicked, this, &MainWindow::toggleFullscreen);
connect(ui->sizeSlider, &QSlider::valueChanged, this, [this](int value) {
if (isTableList) {
m_game_list_frame->icon_size =
- 36 + value; // 36 is the minimum icon size to use due to text disappearing.
- m_game_list_frame->ResizeIcons(36 + value);
- Config::setIconSize(36 + value);
+ 48 + value; // 48 is the minimum icon size to use due to text disappearing.
+ m_game_list_frame->ResizeIcons(48 + value);
+ Config::setIconSize(48 + value);
Config::setSliderPosition(value);
} else {
m_game_grid_frame->icon_size = 69 + value;
diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h
index a5ec08d36..97c56433d 100644
--- a/src/qt_gui/main_window.h
+++ b/src/qt_gui/main_window.h
@@ -29,7 +29,6 @@ class MainWindow : public QMainWindow {
Q_OBJECT
signals:
void WindowResized(QResizeEvent* event);
- void ExtractionFinished();
public:
explicit MainWindow(QWidget* parent = nullptr);
diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts
index 34d31f240..9f254e272 100644
--- a/src/qt_gui/translations/pt_BR.ts
+++ b/src/qt_gui/translations/pt_BR.ts
@@ -2048,7 +2048,7 @@
* Unsupported Vulkan Version
- * Unsupported Vulkan Version
+ * Versão do Vulkan não suportada
diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts
index e61985e90..c6d641470 100644
--- a/src/qt_gui/translations/tr_TR.ts
+++ b/src/qt_gui/translations/tr_TR.ts
@@ -138,7 +138,7 @@
File Exists
- Dosya mevcut
+ Dosya Mevcut
File already exists. Do you want to replace it?
@@ -1221,7 +1221,7 @@
Exit shadPS4
- shadPS4'ten Çık
+ shadPS4 Çıkış
Exit the application.
@@ -1381,7 +1381,7 @@
Game Boot
- Oyun Başlatma
+ Oyun Başlat
Only one file can be selected!
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
index 09f9732bf..172358866 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
@@ -372,6 +372,7 @@ Id EmitBitCount64(EmitContext& ctx, Id value);
Id EmitBitwiseNot32(EmitContext& ctx, Id value);
Id EmitFindSMsb32(EmitContext& ctx, Id value);
Id EmitFindUMsb32(EmitContext& ctx, Id value);
+Id EmitFindUMsb64(EmitContext& ctx, Id value);
Id EmitFindILsb32(EmitContext& ctx, Id value);
Id EmitFindILsb64(EmitContext& ctx, Id value);
Id EmitSMin32(EmitContext& ctx, Id a, Id b);
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp
index 10bfbb2ab..1a995354d 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp
@@ -229,6 +229,20 @@ Id EmitFindUMsb32(EmitContext& ctx, Id value) {
return ctx.OpFindUMsb(ctx.U32[1], value);
}
+Id EmitFindUMsb64(EmitContext& ctx, Id value) {
+ // Vulkan restricts some bitwise operations to 32-bit only, so decompose into
+ // two 32-bit values and select the correct result.
+ const Id unpacked{ctx.OpBitcast(ctx.U32[2], value)};
+ const Id hi{ctx.OpCompositeExtract(ctx.U32[1], unpacked, 1U)};
+ const Id lo{ctx.OpCompositeExtract(ctx.U32[1], unpacked, 0U)};
+ const Id hi_msb{ctx.OpFindUMsb(ctx.U32[1], hi)};
+ const Id lo_msb{ctx.OpFindUMsb(ctx.U32[1], lo)};
+ const Id found_hi{ctx.OpINotEqual(ctx.U1[1], hi_msb, ctx.ConstU32(u32(-1)))};
+ const Id shifted_hi{ctx.OpIAdd(ctx.U32[1], hi_msb, ctx.ConstU32(32u))};
+ // value == 0 case is checked in IREmitter
+ return ctx.OpSelect(ctx.U32[1], found_hi, shifted_hi, lo_msb);
+}
+
Id EmitFindILsb32(EmitContext& ctx, Id value) {
return ctx.OpFindILsb(ctx.U32[1], value);
}
diff --git a/src/shader_recompiler/frontend/decode.cpp b/src/shader_recompiler/frontend/decode.cpp
index 20b78e869..37e8a0973 100644
--- a/src/shader_recompiler/frontend/decode.cpp
+++ b/src/shader_recompiler/frontend/decode.cpp
@@ -1032,7 +1032,6 @@ void GcnDecodeContext::decodeInstructionMIMG(uint64_t hexInstruction) {
m_instruction.control.mimg = *reinterpret_cast(&hexInstruction);
m_instruction.control.mimg.mod = getMimgModifier(m_instruction.opcode);
- ASSERT(m_instruction.control.mimg.r128 == 0);
}
void GcnDecodeContext::decodeInstructionDS(uint64_t hexInstruction) {
diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp
index 3a8e894ae..7beb594c3 100644
--- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp
+++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp
@@ -114,6 +114,8 @@ void Translator::EmitScalarAlu(const GcnInst& inst) {
return S_FF1_I32_B64(inst);
case Opcode::S_FLBIT_I32_B32:
return S_FLBIT_I32_B32(inst);
+ case Opcode::S_FLBIT_I32_B64:
+ return S_FLBIT_I32_B64(inst);
case Opcode::S_BITSET0_B32:
return S_BITSET_B32(inst, 0);
case Opcode::S_BITSET1_B32:
@@ -686,6 +688,17 @@ void Translator::S_FLBIT_I32_B32(const GcnInst& inst) {
SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, ir.Imm32(~0U))});
}
+void Translator::S_FLBIT_I32_B64(const GcnInst& inst) {
+ const IR::U64 src0{GetSrc64(inst.src[0])};
+ // Gcn wants the MSB position counting from the left, but SPIR-V counts from the rightmost (LSB)
+ // position
+ const IR::U32 msb_pos = ir.FindUMsb(src0);
+ const IR::U32 pos_from_left = ir.ISub(ir.Imm32(63), msb_pos);
+ // Select 0xFFFFFFFF if src0 was 0
+ const IR::U1 cond = ir.INotEqual(src0, ir.Imm64(u64(0u)));
+ SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, ir.Imm32(~0U))});
+}
+
void Translator::S_BITSET_B32(const GcnInst& inst, u32 bit_value) {
const IR::U32 old_value{GetSrc(inst.dst[0])};
const IR::U32 offset{ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0U), ir.Imm32(5U))};
diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp
index e49f95d9a..5675adf3c 100644
--- a/src/shader_recompiler/frontend/translate/translate.cpp
+++ b/src/shader_recompiler/frontend/translate/translate.cpp
@@ -380,7 +380,7 @@ T Translator::GetSrc64(const InstOperand& operand) {
break;
case OperandField::VccLo:
if constexpr (is_float) {
- UNREACHABLE();
+ value = ir.PackDouble2x32(ir.CompositeConstruct(ir.GetVccLo(), ir.GetVccHi()));
} else {
value = ir.PackUint2x32(ir.CompositeConstruct(ir.GetVccLo(), ir.GetVccHi()));
}
diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h
index 68d5e8dc8..15ba8c8d7 100644
--- a/src/shader_recompiler/frontend/translate/translate.h
+++ b/src/shader_recompiler/frontend/translate/translate.h
@@ -121,6 +121,7 @@ public:
void S_FF1_I32_B32(const GcnInst& inst);
void S_FF1_I32_B64(const GcnInst& inst);
void S_FLBIT_I32_B32(const GcnInst& inst);
+ void S_FLBIT_I32_B64(const GcnInst& inst);
void S_BITSET_B32(const GcnInst& inst, u32 bit_value);
void S_GETPC_B64(u32 pc, const GcnInst& inst);
void S_SAVEEXEC_B64(NegateMode negate, bool is_or, const GcnInst& inst);
@@ -183,6 +184,7 @@ public:
void V_READFIRSTLANE_B32(const GcnInst& inst);
void V_CVT_I32_F64(const GcnInst& inst);
void V_CVT_F64_I32(const GcnInst& inst);
+ void V_CVT_F64_U32(const GcnInst& inst);
void V_CVT_F32_I32(const GcnInst& inst);
void V_CVT_F32_U32(const GcnInst& inst);
void V_CVT_U32_F32(const GcnInst& inst);
@@ -203,6 +205,7 @@ public:
void V_EXP_F32(const GcnInst& inst);
void V_LOG_F32(const GcnInst& inst);
void V_RCP_F32(const GcnInst& inst);
+ void V_RCP_LEGACY_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);
diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp
index 6171cca07..3b88e4dec 100644
--- a/src/shader_recompiler/frontend/translate/vector_alu.cpp
+++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp
@@ -110,6 +110,8 @@ void Translator::EmitVectorAlu(const GcnInst& inst) {
return V_CVT_I32_F64(inst);
case Opcode::V_CVT_F64_I32:
return V_CVT_F64_I32(inst);
+ case Opcode::V_CVT_F64_U32:
+ return V_CVT_F64_U32(inst);
case Opcode::V_CVT_F32_I32:
return V_CVT_F32_I32(inst);
case Opcode::V_CVT_F32_U32:
@@ -156,6 +158,8 @@ void Translator::EmitVectorAlu(const GcnInst& inst) {
return V_LOG_F32(inst);
case Opcode::V_RCP_F32:
return V_RCP_F32(inst);
+ case Opcode::V_RCP_LEGACY_F32:
+ return V_RCP_LEGACY_F32(inst);
case Opcode::V_RCP_F64:
return V_RCP_F64(inst);
case Opcode::V_RCP_IFLAG_F32:
@@ -684,6 +688,11 @@ void Translator::V_CVT_F64_I32(const GcnInst& inst) {
SetDst64(inst.dst[0], ir.ConvertSToF(64, 32, src0));
}
+void Translator::V_CVT_F64_U32(const GcnInst& inst) {
+ const IR::U32 src0{GetSrc(inst.src[0])};
+ SetDst64(inst.dst[0], ir.ConvertUToF(64, 32, src0));
+}
+
void Translator::V_CVT_F32_I32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
SetDst(inst.dst[0], ir.ConvertSToF(32, 32, src0));
@@ -791,6 +800,20 @@ void Translator::V_RCP_F32(const GcnInst& inst) {
SetDst(inst.dst[0], ir.FPRecip(src0));
}
+void Translator::V_RCP_LEGACY_F32(const GcnInst& inst) {
+ const IR::F32 src0{GetSrc(inst.src[0])};
+ const auto result = ir.FPRecip(src0);
+ const auto inf = ir.FPIsInf(result);
+
+ const auto raw_result = ir.ConvertFToU(32, result);
+ const auto sign_bit = ir.ShiftRightLogical(raw_result, ir.Imm32(31u));
+ const auto sign_bit_set = ir.INotEqual(sign_bit, ir.Imm32(0u));
+ const IR::F32 inf_result{ir.Select(sign_bit_set, ir.Imm32(-0.0f), ir.Imm32(0.0f))};
+ const IR::F32 val{ir.Select(inf, inf_result, result)};
+
+ SetDst(inst.dst[0], val);
+}
+
void Translator::V_RCP_F64(const GcnInst& inst) {
const IR::F64 src0{GetSrc64(inst.src[0])};
SetDst64(inst.dst[0], ir.FPRecip(src0));
diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp
index 5639bc56a..5c972c607 100644
--- a/src/shader_recompiler/frontend/translate/vector_memory.cpp
+++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp
@@ -152,6 +152,7 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
// Image gather operations
case Opcode::IMAGE_GATHER4:
+ case Opcode::IMAGE_GATHER4_L:
case Opcode::IMAGE_GATHER4_LZ:
case Opcode::IMAGE_GATHER4_C:
case Opcode::IMAGE_GATHER4_O:
@@ -377,6 +378,7 @@ void Translator::IMAGE_LOAD(bool has_mip, const GcnInst& inst) {
IR::TextureInstInfo info{};
info.has_lod.Assign(has_mip);
info.is_array.Assign(mimg.da);
+ info.is_r128.Assign(mimg.r128);
const IR::Value texel = ir.ImageRead(handle, body, {}, {}, info);
for (u32 i = 0; i < 4; i++) {
@@ -426,6 +428,7 @@ void Translator::IMAGE_GET_RESINFO(const GcnInst& inst) {
IR::TextureInstInfo info{};
info.is_array.Assign(mimg.da);
+ info.is_r128.Assign(mimg.r128);
const IR::Value size = ir.ImageQueryDimension(tsharp, lod, ir.Imm1(has_mips), info);
@@ -451,6 +454,7 @@ void Translator::IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst) {
IR::TextureInstInfo info{};
info.is_array.Assign(mimg.da);
+ info.is_r128.Assign(mimg.r128);
const IR::Value value = ir.GetVectorReg(val_reg);
const IR::Value handle = ir.GetScalarReg(tsharp_reg);
@@ -509,6 +513,7 @@ IR::Value EmitImageSample(IR::IREmitter& ir, const GcnInst& inst, const IR::Scal
info.has_lod.Assign(flags.any(MimgModifier::Lod));
info.is_array.Assign(mimg.da);
info.is_unnormalized.Assign(mimg.unrm);
+ info.is_r128.Assign(mimg.r128);
if (gather) {
info.gather_comp.Assign(std::bit_width(mimg.dmask) - 1);
@@ -617,6 +622,7 @@ void Translator::IMAGE_GET_LOD(const GcnInst& inst) {
IR::TextureInstInfo info{};
info.is_array.Assign(mimg.da);
+ info.is_r128.Assign(mimg.r128);
const IR::Value handle = ir.GetScalarReg(tsharp_reg);
const IR::Value body = ir.CompositeConstruct(
diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h
index d349d7827..24e0741c1 100644
--- a/src/shader_recompiler/info.h
+++ b/src/shader_recompiler/info.h
@@ -84,6 +84,7 @@ struct ImageResource {
bool is_atomic{};
bool is_array{};
bool is_written{};
+ bool is_r128{};
[[nodiscard]] constexpr AmdGpu::Image GetSharp(const Info& info) const noexcept;
};
@@ -293,7 +294,13 @@ constexpr AmdGpu::Buffer BufferResource::GetSharp(const Info& info) const noexce
}
constexpr AmdGpu::Image ImageResource::GetSharp(const Info& info) const noexcept {
- const auto image = info.ReadUdSharp(sharp_idx);
+ AmdGpu::Image image{0};
+ if (!is_r128) {
+ image = info.ReadUdSharp(sharp_idx);
+ } else {
+ AmdGpu::Buffer buf = info.ReadUdSharp(sharp_idx);
+ memcpy(&image, &buf, sizeof(buf));
+ }
if (!image.Valid()) {
// Fall back to null image if unbound.
return AmdGpu::Image::Null();
diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp
index 01d945178..dcb734d01 100644
--- a/src/shader_recompiler/ir/ir_emitter.cpp
+++ b/src/shader_recompiler/ir/ir_emitter.cpp
@@ -1546,8 +1546,15 @@ U32 IREmitter::FindSMsb(const U32& value) {
return Inst(Opcode::FindSMsb32, value);
}
-U32 IREmitter::FindUMsb(const U32& value) {
- return Inst(Opcode::FindUMsb32, value);
+U32 IREmitter::FindUMsb(const U32U64& value) {
+ switch (value.Type()) {
+ case Type::U32:
+ return Inst(Opcode::FindUMsb32, value);
+ case Type::U64:
+ return Inst(Opcode::FindUMsb64, value);
+ default:
+ ThrowInvalidType(value.Type());
+ }
}
U32 IREmitter::FindILsb(const U32U64& value) {
diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h
index 8f8a12736..da7adf42b 100644
--- a/src/shader_recompiler/ir/ir_emitter.h
+++ b/src/shader_recompiler/ir/ir_emitter.h
@@ -266,7 +266,7 @@ public:
[[nodiscard]] U32 BitwiseNot(const U32& value);
[[nodiscard]] U32 FindSMsb(const U32& value);
- [[nodiscard]] U32 FindUMsb(const U32& value);
+ [[nodiscard]] U32 FindUMsb(const U32U64& value);
[[nodiscard]] U32 FindILsb(const U32U64& value);
[[nodiscard]] U32 SMin(const U32& a, const U32& b);
[[nodiscard]] U32 UMin(const U32& a, const U32& b);
diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc
index ab6dbfde9..647432bcf 100644
--- a/src/shader_recompiler/ir/opcodes.inc
+++ b/src/shader_recompiler/ir/opcodes.inc
@@ -349,6 +349,7 @@ OPCODE(BitwiseNot32, U32, U32,
OPCODE(FindSMsb32, U32, U32, )
OPCODE(FindUMsb32, U32, U32, )
+OPCODE(FindUMsb64, U32, U64, )
OPCODE(FindILsb32, U32, U32, )
OPCODE(FindILsb64, U32, U64, )
OPCODE(SMin32, U32, U32, U32, )
diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp
index cc0bf83d3..18c77e600 100644
--- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp
+++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp
@@ -411,6 +411,7 @@ void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors&
.is_atomic = IsImageAtomicInstruction(inst),
.is_array = bool(inst_info.is_array),
.is_written = is_written,
+ .is_r128 = bool(inst_info.is_r128),
});
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
diff --git a/src/shader_recompiler/ir/reg.h b/src/shader_recompiler/ir/reg.h
index 622190cf0..82aa436a7 100644
--- a/src/shader_recompiler/ir/reg.h
+++ b/src/shader_recompiler/ir/reg.h
@@ -44,6 +44,7 @@ union TextureInstInfo {
BitField<9, 1, u32> is_array;
BitField<10, 1, u32> is_unnormalized;
BitField<11, 1, u32> is_gather;
+ BitField<12, 1, u32> is_r128;
};
union BufferInstInfo {
diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp
index 4db7648c6..e031d0ebc 100644
--- a/src/video_core/amdgpu/liverpool.cpp
+++ b/src/video_core/amdgpu/liverpool.cpp
@@ -394,7 +394,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span dcb, std::span(header);
- LOG_DEBUG(Render_Vulkan,
- "Encountered EventWrite: event_type = {}, event_index = {}",
+ LOG_DEBUG(Render, "Encountered EventWrite: event_type = {}, event_index = {}",
magic_enum::enum_name(event->event_type.Value()),
magic_enum::enum_name(event->event_index.Value()));
if (event->event_type.Value() == EventType::SoVgtStreamoutFlush) {
@@ -673,6 +672,16 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header);
+ LOG_WARNING(Render,
+ "unhandled IT_COPY_DATA src_sel = {}, dst_sel = {}, "
+ "count_sel = {}, wr_confirm = {}, engine_sel = {}",
+ u32(copy_data->src_sel.Value()), u32(copy_data->dst_sel.Value()),
+ copy_data->count_sel.Value(), copy_data->wr_confirm.Value(),
+ u32(copy_data->engine_sel.Value()));
+ break;
+ }
case PM4ItOpcode::MemSemaphore: {
const auto* mem_semaphore = reinterpret_cast(header);
if (mem_semaphore->IsSignaling()) {
@@ -756,6 +765,19 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header);
+ if (cond_exec->command.Value() != 0) {
+ LOG_WARNING(Render, "IT_COND_EXEC used a reserved command");
+ }
+ const auto skip = *cond_exec->Address() == false;
+ if (skip) {
+ dcb = NextPacket(dcb,
+ header->type3.NumWords() + 1 + cond_exec->exec_count.Value());
+ continue;
+ }
+ break;
+ }
default:
UNREACHABLE_MSG("Unknown PM4 type 3 opcode {:#x} with count {}",
static_cast(opcode), count);
diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h
index a62141099..245e34d35 100644
--- a/src/video_core/amdgpu/liverpool.h
+++ b/src/video_core/amdgpu/liverpool.h
@@ -608,6 +608,16 @@ struct Liverpool {
}
};
+ struct BorderColorBufferBase {
+ u32 base_addr_lo;
+ BitField<0, 8, u32> base_addr_hi;
+
+ template
+ T Address() const {
+ return std::bit_cast(u64(base_addr_hi) << 40 | u64(base_addr_lo) << 8);
+ }
+ };
+
struct IndexBufferBase {
BitField<0, 8, u32> base_addr_hi;
u32 base_addr_lo;
@@ -1299,7 +1309,9 @@ struct Liverpool {
Scissor screen_scissor;
INSERT_PADDING_WORDS(0xA010 - 0xA00C - 2);
DepthBuffer depth_buffer;
- INSERT_PADDING_WORDS(0xA080 - 0xA018);
+ INSERT_PADDING_WORDS(8);
+ BorderColorBufferBase ta_bc_base;
+ INSERT_PADDING_WORDS(0xA080 - 0xA020 - 2);
WindowOffset window_offset;
ViewportScissor window_scissor;
INSERT_PADDING_WORDS(0xA08E - 0xA081 - 2);
@@ -1626,6 +1638,7 @@ static_assert(GFX6_3D_REG_INDEX(depth_htile_data_base) == 0xA005);
static_assert(GFX6_3D_REG_INDEX(screen_scissor) == 0xA00C);
static_assert(GFX6_3D_REG_INDEX(depth_buffer.z_info) == 0xA010);
static_assert(GFX6_3D_REG_INDEX(depth_buffer.depth_slice) == 0xA017);
+static_assert(GFX6_3D_REG_INDEX(ta_bc_base) == 0xA020);
static_assert(GFX6_3D_REG_INDEX(window_offset) == 0xA080);
static_assert(GFX6_3D_REG_INDEX(window_scissor) == 0xA081);
static_assert(GFX6_3D_REG_INDEX(color_target_mask) == 0xA08E);
diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h
index 58ecda93e..23c1b8f21 100644
--- a/src/video_core/amdgpu/pm4_cmds.h
+++ b/src/video_core/amdgpu/pm4_cmds.h
@@ -554,6 +554,61 @@ struct PM4DmaData {
}
};
+enum class CopyDataSrc : u32 {
+ MappedRegister = 0,
+ Memory = 1,
+ TCL2 = 2,
+ Gds = 3,
+ // Reserved = 4,
+ Immediate = 5,
+ Atomic = 6,
+ GdsAtomic0 = 7,
+ GdsAtomic1 = 8,
+ GpuClock = 9,
+};
+
+enum class CopyDataDst : u32 {
+ MappedRegister = 0,
+ MemorySync = 1,
+ TCL2 = 2,
+ Gds = 3,
+ // Reserved = 4,
+ MemoryAsync = 5,
+};
+
+enum class CopyDataEngine : u32 {
+ Me = 0,
+ Pfp = 1,
+ Ce = 2,
+ // Reserved = 3
+};
+
+struct PM4CmdCopyData {
+ PM4Type3Header header;
+ union {
+ BitField<0, 4, CopyDataSrc> src_sel;
+ BitField<8, 4, CopyDataDst> dst_sel;
+ BitField<16, 1, u32> count_sel;
+ BitField<20, 1, u32> wr_confirm;
+ BitField<30, 2, CopyDataEngine> engine_sel;
+ u32 control;
+ };
+ u32 src_addr_lo;
+ u32 src_addr_hi;
+ u32 dst_addr_lo;
+ u32 dst_addr_hi;
+
+ template
+ T SrcAddress() const {
+ return std::bit_cast(src_addr_lo | u64(src_addr_hi) << 32);
+ }
+
+ template
+ T DstAddress() const {
+ return std::bit_cast(dst_addr_lo | u64(dst_addr_hi) << 32);
+ }
+};
+
struct PM4CmdRewind {
PM4Type3Header header;
union {
@@ -1104,4 +1159,25 @@ struct PM4CmdMemSemaphore {
}
};
+struct PM4CmdCondExec {
+ PM4Type3Header header;
+ union {
+ BitField<2, 30, u32> bool_addr_lo; ///< low 32 address bits for the block in memory from
+ ///< where the CP will fetch the condition
+ };
+ union {
+ BitField<0, 16, u32> bool_addr_hi; ///< high address bits for the condition
+ BitField<28, 4, u32> command;
+ };
+ union {
+ BitField<0, 14, u32> exec_count; ///< Number of DWords that the CP will skip
+ ///< if bool pointed to is zero
+ };
+
+ bool* Address() const {
+ return std::bit_cast(u64(bool_addr_hi.Value()) << 32 | u64(bool_addr_lo.Value())
+ << 2);
+ }
+};
+
} // namespace AmdGpu
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 4bdb08bf2..dff4e5a5f 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -716,7 +716,7 @@ void Rasterizer::BindTextures(const Shader::Info& stage, Shader::Backend::Bindin
ssharp.max_aniso.Assign(AmdGpu::AnisoRatio::One);
}
}
- const auto vk_sampler = texture_cache.GetSampler(ssharp);
+ const auto vk_sampler = texture_cache.GetSampler(ssharp, liverpool->regs.ta_bc_base);
image_infos.emplace_back(vk_sampler, VK_NULL_HANDLE, vk::ImageLayout::eGeneral);
set_writes.push_back({
.dstSet = VK_NULL_HANDLE,
diff --git a/src/video_core/texture_cache/sampler.cpp b/src/video_core/texture_cache/sampler.cpp
index 8dbdd2912..e18c79a59 100644
--- a/src/video_core/texture_cache/sampler.cpp
+++ b/src/video_core/texture_cache/sampler.cpp
@@ -9,7 +9,8 @@
namespace VideoCore {
-Sampler::Sampler(const Vulkan::Instance& instance, const AmdGpu::Sampler& sampler) {
+Sampler::Sampler(const Vulkan::Instance& instance, const AmdGpu::Sampler& sampler,
+ const AmdGpu::Liverpool::BorderColorBufferBase& border_color_base) {
if (sampler.force_degamma) {
LOG_WARNING(Render_Vulkan, "Texture requires gamma correction");
}
@@ -20,7 +21,33 @@ Sampler::Sampler(const Vulkan::Instance& instance, const AmdGpu::Sampler& sample
const float maxAnisotropy =
anisotropyEnable ? std::clamp(sampler.MaxAniso(), 1.0f, instance.MaxSamplerAnisotropy())
: 1.0f;
+ auto borderColor = LiverpoolToVK::BorderColor(sampler.border_color_type);
+ if (!instance.IsCustomBorderColorSupported()) {
+ LOG_WARNING(Render_Vulkan, "Custom border color is not supported, falling back to black");
+ borderColor = vk::BorderColor::eFloatOpaqueBlack;
+ }
+
+ const auto customColor = [&]() -> std::optional {
+ if (borderColor == vk::BorderColor::eFloatCustomEXT) {
+ const auto borderColorIndex = sampler.border_color_ptr.Value();
+ const auto borderColorBuffer = border_color_base.Address*>();
+ const auto customBorderColorArray = borderColorBuffer[borderColorIndex];
+
+ const vk::SamplerCustomBorderColorCreateInfoEXT ret{
+ .customBorderColor =
+ vk::ClearColorValue{
+ .float32 = customBorderColorArray,
+ },
+ .format = vk::Format::eR32G32B32A32Sfloat,
+ };
+ return ret;
+ } else {
+ return std::nullopt;
+ }
+ }();
+
const vk::SamplerCreateInfo sampler_ci = {
+ .pNext = customColor ? &*customColor : nullptr,
.magFilter = LiverpoolToVK::Filter(sampler.xy_mag_filter),
.minFilter = LiverpoolToVK::Filter(sampler.xy_min_filter),
.mipmapMode = LiverpoolToVK::MipFilter(sampler.mip_filter),
@@ -34,7 +61,7 @@ Sampler::Sampler(const Vulkan::Instance& instance, const AmdGpu::Sampler& sample
.compareOp = LiverpoolToVK::DepthCompare(sampler.depth_compare_func),
.minLod = sampler.MinLod(),
.maxLod = sampler.MaxLod(),
- .borderColor = LiverpoolToVK::BorderColor(sampler.border_color_type),
+ .borderColor = borderColor,
.unnormalizedCoordinates = false, // Handled in shader due to Vulkan limitations.
};
auto [sampler_result, smplr] = instance.GetDevice().createSamplerUnique(sampler_ci);
diff --git a/src/video_core/texture_cache/sampler.h b/src/video_core/texture_cache/sampler.h
index 856d39763..28ba0f67b 100644
--- a/src/video_core/texture_cache/sampler.h
+++ b/src/video_core/texture_cache/sampler.h
@@ -14,7 +14,8 @@ namespace VideoCore {
class Sampler {
public:
- explicit Sampler(const Vulkan::Instance& instance, const AmdGpu::Sampler& sampler);
+ explicit Sampler(const Vulkan::Instance& instance, const AmdGpu::Sampler& sampler,
+ const AmdGpu::Liverpool::BorderColorBufferBase& border_color_base);
~Sampler();
Sampler(const Sampler&) = delete;
diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp
index ddb4ea799..63cfc4431 100644
--- a/src/video_core/texture_cache/texture_cache.cpp
+++ b/src/video_core/texture_cache/texture_cache.cpp
@@ -636,9 +636,11 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule
image.flags &= ~ImageFlagBits::Dirty;
}
-vk::Sampler TextureCache::GetSampler(const AmdGpu::Sampler& sampler) {
+vk::Sampler TextureCache::GetSampler(
+ const AmdGpu::Sampler& sampler,
+ const AmdGpu::Liverpool::BorderColorBufferBase& border_color_base) {
const u64 hash = XXH3_64bits(&sampler, sizeof(sampler));
- const auto [it, new_sampler] = samplers.try_emplace(hash, instance, sampler);
+ const auto [it, new_sampler] = samplers.try_emplace(hash, instance, sampler, border_color_base);
return it->second.Handle();
}
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index b6bf88958..ccfeb36b2 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -139,7 +139,9 @@ public:
void RefreshImage(Image& image, Vulkan::Scheduler* custom_scheduler = nullptr);
/// Retrieves the sampler that matches the provided S# descriptor.
- [[nodiscard]] vk::Sampler GetSampler(const AmdGpu::Sampler& sampler);
+ [[nodiscard]] vk::Sampler GetSampler(
+ const AmdGpu::Sampler& sampler,
+ const AmdGpu::Liverpool::BorderColorBufferBase& border_color_base);
/// Retrieves the image with the specified id.
[[nodiscard]] Image& GetImage(ImageId id) {