diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5c4a34469..c36c026fc 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -376,6 +376,78 @@ jobs:
name: shadps4-linux-qt-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
path: Shadps4-qt.AppImage
+ linux-sdl-gcc:
+ runs-on: ubuntu-24.04
+ needs: get-info
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Install dependencies
+ run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev
+
+ - name: Cache CMake Configuration
+ uses: actions/cache@v4
+ env:
+ cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration
+ with:
+ path: |
+ ${{github.workspace}}/build
+ key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+ restore-keys: |
+ ${{ env.cache-name }}-
+
+ - name: Cache CMake Build
+ uses: hendrikmuhs/ccache-action@v1.2.14
+ env:
+ cache-name: ${{ runner.os }}-sdl-cache-cmake-build
+ with:
+ append-timestamp: false
+ key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+
+ - name: Configure CMake
+ run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
+
+ - name: Build
+ run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc)
+
+ linux-qt-gcc:
+ runs-on: ubuntu-24.04
+ needs: get-info
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Install dependencies
+ run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev
+
+ - name: Cache CMake Configuration
+ uses: actions/cache@v4
+ env:
+ cache-name: ${{ runner.os }}-qt-cache-cmake-configuration
+ with:
+ path: |
+ ${{github.workspace}}/build
+ key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+ restore-keys: |
+ ${{ env.cache-name }}-
+
+ - name: Cache CMake Build
+ uses: hendrikmuhs/ccache-action@v1.2.14
+ env:
+ cache-name: ${{ runner.os }}-qt-cache-cmake-build
+ with:
+ append-timestamp: false
+ key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+
+ - name: Configure CMake
+ run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
+
+ - name: Build
+ run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc)
+
pre-release:
if: github.ref == 'refs/heads/main' && github.repository == 'shadps4-emu/shadPS4' && github.event_name == 'push'
needs: [get-info, windows-sdl, windows-qt, macos-sdl, macos-qt, linux-sdl, linux-qt]
diff --git a/dist/net.shadps4.shadPS4.metainfo.xml b/dist/net.shadps4.shadPS4.metainfo.xml
index d5ad992e3..384cf75e8 100644
--- a/dist/net.shadps4.shadPS4.metainfo.xml
+++ b/dist/net.shadps4.shadPS4.metainfo.xml
@@ -62,7 +62,6 @@
https://github.com/shadps4-emu/shadPS4/releases/tag/v0.0.1
-
keyboard
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 4350948b7..4ce5636d8 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -213,9 +213,7 @@ endif()
# Discord RPC
if (ENABLE_DISCORD_RPC)
- set(BUILD_EXAMPLES OFF)
add_subdirectory(discord-rpc)
- target_include_directories(discord-rpc INTERFACE discord-rpc/include)
endif()
# GCN Headers
diff --git a/externals/discord-rpc b/externals/discord-rpc
index 4ec218155..51b09d426 160000
--- a/externals/discord-rpc
+++ b/externals/discord-rpc
@@ -1 +1 @@
-Subproject commit 4ec218155d73bcb8022f8f7ca72305d801f84beb
+Subproject commit 51b09d426a4a1bcfa6ee6d4894e57d669f4a2e65
diff --git a/src/common/elf_info.h b/src/common/elf_info.h
index 02eefbb7a..cb32679bb 100644
--- a/src/common/elf_info.h
+++ b/src/common/elf_info.h
@@ -111,7 +111,7 @@ public:
return raw_firmware_ver;
}
- [[nodiscard]] const PSFAttributes& PSFAttributes() const {
+ [[nodiscard]] const PSFAttributes& GetPSFAttributes() const {
ASSERT(initialized);
return psf_attributes;
}
diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp
index bf340e9e3..7d456780b 100644
--- a/src/core/file_sys/fs.cpp
+++ b/src/core/file_sys/fs.cpp
@@ -40,7 +40,8 @@ void MntPoints::UnmountAll() {
m_mnt_pairs.clear();
}
-std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_read_only) {
+std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_read_only,
+ bool force_base_path) {
// Evil games like Turok2 pass double slashes e.g /app0//game.kpf
std::string corrected_path(path);
size_t pos = corrected_path.find("//");
@@ -72,7 +73,7 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
patch_path /= rel_path;
if ((corrected_path.starts_with("/app0") || corrected_path.starts_with("/hostapp")) &&
- std::filesystem::exists(patch_path)) {
+ !force_base_path && std::filesystem::exists(patch_path)) {
return patch_path;
}
@@ -132,8 +133,10 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
return std::optional(current_path);
};
- if (const auto path = search(patch_path)) {
- return *path;
+ if (!force_base_path) {
+ if (const auto path = search(patch_path)) {
+ return *path;
+ }
}
if (const auto path = search(host_path)) {
return *path;
@@ -144,6 +147,39 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
return host_path;
}
+// TODO: Does not handle mount points inside mount points.
+void MntPoints::IterateDirectory(std::string_view guest_directory,
+ const IterateDirectoryCallback& callback) {
+ const auto base_path = GetHostPath(guest_directory, nullptr, true);
+ const auto patch_path = GetHostPath(guest_directory, nullptr, false);
+ // Only need to consider patch path if it exists and does not resolve to the same as base.
+ const auto apply_patch = base_path != patch_path && std::filesystem::exists(patch_path);
+
+ // Pass 1: Any files that existed in the base directory, using patch directory if needed.
+ if (std::filesystem::exists(base_path)) {
+ for (const auto& entry : std::filesystem::directory_iterator(base_path)) {
+ if (apply_patch) {
+ const auto patch_entry_path = patch_path / entry.path().filename();
+ if (std::filesystem::exists(patch_entry_path)) {
+ callback(patch_entry_path, !std::filesystem::is_directory(patch_entry_path));
+ continue;
+ }
+ }
+ callback(entry.path(), !entry.is_directory());
+ }
+ }
+
+ // Pass 2: Any files that exist only in the patch directory.
+ if (apply_patch) {
+ for (const auto& entry : std::filesystem::directory_iterator(patch_path)) {
+ const auto base_entry_path = base_path / entry.path().filename();
+ if (!std::filesystem::exists(base_entry_path)) {
+ callback(entry.path(), !entry.is_directory());
+ }
+ }
+ }
+}
+
int HandleTable::CreateHandle() {
std::scoped_lock lock{m_mutex};
diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h
index 56df32ad0..6638b48e8 100644
--- a/src/core/file_sys/fs.h
+++ b/src/core/file_sys/fs.h
@@ -36,7 +36,11 @@ public:
void UnmountAll();
std::filesystem::path GetHostPath(std::string_view guest_directory,
- bool* is_read_only = nullptr);
+ bool* is_read_only = nullptr, bool force_base_path = false);
+ using IterateDirectoryCallback =
+ std::function;
+ void IterateDirectory(std::string_view guest_directory,
+ const IterateDirectoryCallback& callback);
const MntPair* GetMountFromHostPath(const std::string& host_path) {
std::scoped_lock lock{m_mutex};
diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp
index 57efbb631..2eb5d1621 100644
--- a/src/core/libraries/kernel/file_system.cpp
+++ b/src/core/libraries/kernel/file_system.cpp
@@ -46,17 +46,6 @@ static std::map available_device = {
namespace Libraries::Kernel {
-auto GetDirectoryEntries(const std::filesystem::path& path) {
- std::vector files;
- for (const auto& entry : std::filesystem::directory_iterator(path)) {
- auto& dir_entry = files.emplace_back();
- dir_entry.name = entry.path().filename().string();
- dir_entry.isFile = !std::filesystem::is_directory(entry.path().string());
- }
-
- return files;
-}
-
int PS4_SYSV_ABI sceKernelOpen(const char* raw_path, int flags, u16 mode) {
LOG_INFO(Kernel_Fs, "path = {} flags = {:#x} mode = {}", raw_path, flags, mode);
auto* h = Common::Singleton::Instance();
@@ -115,7 +104,12 @@ int PS4_SYSV_ABI sceKernelOpen(const char* raw_path, int flags, u16 mode) {
if (create) {
return handle; // dir already exists
} else {
- file->dirents = GetDirectoryEntries(file->m_host_name);
+ mnt->IterateDirectory(file->m_guest_name,
+ [&file](const auto& ent_path, const auto ent_is_file) {
+ auto& dir_entry = file->dirents.emplace_back();
+ dir_entry.name = ent_path.filename().string();
+ dir_entry.isFile = ent_is_file;
+ });
file->dirents_index = 0;
}
}
@@ -695,66 +689,12 @@ static int GetDents(int fd, char* buf, int nbytes, s64* basep) {
return sizeof(OrbisKernelDirent);
}
-static int HandleSeparateUpdateDents(int fd, char* buf, int nbytes, s64* basep) {
- int dir_entries = 0;
-
- auto* h = Common::Singleton::Instance();
- auto* mnt = Common::Singleton::Instance();
- auto* file = h->GetFile(fd);
- auto update_dir_name = std::string{fmt::UTF(file->m_host_name.u8string()).data};
- auto mount = mnt->GetMountFromHostPath(update_dir_name);
- auto suffix = std::string{fmt::UTF(mount->host_path.u8string()).data};
-
- size_t pos = update_dir_name.find("-UPDATE");
- if (pos != std::string::npos) {
- update_dir_name.erase(pos, 7);
- auto guest_name = mount->mount + "/" + update_dir_name.substr(suffix.size() + 1);
- int descriptor;
-
- auto existent_folder = h->GetFile(update_dir_name);
- if (!existent_folder) {
- u32 handle = h->CreateHandle();
- auto* new_file = h->GetFile(handle);
- new_file->type = Core::FileSys::FileType::Directory;
- new_file->m_guest_name = guest_name;
- new_file->m_host_name = update_dir_name;
- if (!std::filesystem::is_directory(new_file->m_host_name)) {
- h->DeleteHandle(handle);
- return dir_entries;
- } else {
- new_file->dirents = GetDirectoryEntries(new_file->m_host_name);
- new_file->dirents_index = 0;
- }
- new_file->is_opened = true;
- descriptor = h->GetFileDescriptor(new_file);
- } else {
- descriptor = h->GetFileDescriptor(existent_folder);
- }
-
- dir_entries = GetDents(descriptor, buf, nbytes, basep);
- if (dir_entries == ORBIS_OK && existent_folder) {
- existent_folder->dirents_index = 0;
- file->dirents_index = 0;
- }
- }
-
- return dir_entries;
-}
-
int PS4_SYSV_ABI sceKernelGetdents(int fd, char* buf, int nbytes) {
- int a = GetDents(fd, buf, nbytes, nullptr);
- if (a == ORBIS_OK) {
- return HandleSeparateUpdateDents(fd, buf, nbytes, nullptr);
- }
- return a;
+ return GetDents(fd, buf, nbytes, nullptr);
}
int PS4_SYSV_ABI sceKernelGetdirentries(int fd, char* buf, int nbytes, s64* basep) {
- int a = GetDents(fd, buf, nbytes, basep);
- if (a == ORBIS_OK) {
- return HandleSeparateUpdateDents(fd, buf, nbytes, basep);
- }
- return a;
+ return GetDents(fd, buf, nbytes, basep);
}
s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) {
diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp
index 791a98a36..c21257c50 100644
--- a/src/core/libraries/kernel/process.cpp
+++ b/src/core/libraries/kernel/process.cpp
@@ -15,7 +15,7 @@ namespace Libraries::Kernel {
int PS4_SYSV_ABI sceKernelIsNeoMode() {
LOG_DEBUG(Kernel_Sce, "called");
return Config::isNeoModeConsole() &&
- Common::ElfInfo::Instance().PSFAttributes().support_neo_mode;
+ Common::ElfInfo::Instance().GetPSFAttributes().support_neo_mode;
}
int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver) {
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 1327ede5f..619941000 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -171,10 +171,11 @@ int MemoryManager::PoolReserve(void** out_addr, VAddr virtual_addr, size_t size,
// Fixed mapping means the virtual address must exactly match the provided one.
if (True(flags & MemoryMapFlags::Fixed)) {
- const auto& vma = FindVMA(mapped_addr)->second;
+ auto& vma = FindVMA(mapped_addr)->second;
// If the VMA is mapped, unmap the region first.
if (vma.IsMapped()) {
UnmapMemoryImpl(mapped_addr, size);
+ vma = FindVMA(mapped_addr)->second;
}
const size_t remaining_size = vma.base + vma.size - mapped_addr;
ASSERT_MSG(vma.type == VMAType::Free && remaining_size >= size);
@@ -208,10 +209,11 @@ int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, Mem
// Fixed mapping means the virtual address must exactly match the provided one.
if (True(flags & MemoryMapFlags::Fixed)) {
- const auto& vma = FindVMA(mapped_addr)->second;
+ auto& vma = FindVMA(mapped_addr)->second;
// If the VMA is mapped, unmap the region first.
if (vma.IsMapped()) {
UnmapMemoryImpl(mapped_addr, size);
+ vma = FindVMA(mapped_addr)->second;
}
const size_t remaining_size = vma.base + vma.size - mapped_addr;
ASSERT_MSG(vma.type == VMAType::Free && remaining_size >= size);
@@ -393,14 +395,18 @@ s32 MemoryManager::UnmapMemoryImpl(VAddr virtual_addr, size_t size) {
ASSERT_MSG(vma_base.Contains(virtual_addr, size),
"Existing mapping does not contain requested unmap range");
+ const auto type = vma_base.type;
+ if (type == VMAType::Free) {
+ return ORBIS_OK;
+ }
+
const auto vma_base_addr = vma_base.base;
const auto vma_base_size = vma_base.size;
const auto phys_base = vma_base.phys_base;
const bool is_exec = vma_base.is_exec;
const auto start_in_vma = virtual_addr - vma_base_addr;
- const auto type = vma_base.type;
const bool has_backing = type == VMAType::Direct || type == VMAType::File;
- if (type == VMAType::Direct) {
+ if (type == VMAType::Direct || type == VMAType::Pooled) {
rasterizer->UnmapMemory(virtual_addr, size);
}
if (type == VMAType::Flexible) {
@@ -418,10 +424,12 @@ s32 MemoryManager::UnmapMemoryImpl(VAddr virtual_addr, size_t size) {
MergeAdjacent(vma_map, new_it);
bool readonly_file = vma.prot == MemoryProt::CpuRead && type == VMAType::File;
- // Unmap the memory region.
- impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + size, phys_base, is_exec,
- has_backing, readonly_file);
- TRACK_FREE(virtual_addr, "VMEM");
+ if (type != VMAType::Reserved && type != VMAType::PoolReserved) {
+ // Unmap the memory region.
+ impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + size, phys_base,
+ is_exec, has_backing, readonly_file);
+ TRACK_FREE(virtual_addr, "VMEM");
+ }
return ORBIS_OK;
}
diff --git a/src/emulator.cpp b/src/emulator.cpp
index 5d037e26c..dbe693340 100644
--- a/src/emulator.cpp
+++ b/src/emulator.cpp
@@ -217,41 +217,15 @@ void Emulator::Run(const std::filesystem::path& file) {
linker->LoadModule(eboot_path);
// check if we have system modules to load
- LoadSystemModules(eboot_path, game_info.game_serial);
+ LoadSystemModules(game_info.game_serial);
// Load all prx from game's sce_module folder
- std::vector modules_to_load;
- std::filesystem::path game_module_folder = file.parent_path() / "sce_module";
- if (std::filesystem::is_directory(game_module_folder)) {
- for (const auto& entry : std::filesystem::directory_iterator(game_module_folder)) {
- if (entry.is_regular_file()) {
- modules_to_load.push_back(entry.path());
- }
+ mnt->IterateDirectory("/app0/sce_module", [this](const auto& path, const auto is_file) {
+ if (is_file) {
+ LOG_INFO(Loader, "Loading {}", fmt::UTF(path.u8string()));
+ linker->LoadModule(path);
}
- }
-
- // Load all prx from separate update's sce_module folder
- std::filesystem::path game_patch_folder = game_folder;
- game_patch_folder += "-UPDATE";
- std::filesystem::path update_module_folder = game_patch_folder / "sce_module";
- if (std::filesystem::is_directory(update_module_folder)) {
- for (const auto& entry : std::filesystem::directory_iterator(update_module_folder)) {
- auto it = std::find_if(modules_to_load.begin(), modules_to_load.end(),
- [&entry](const std::filesystem::path& p) {
- return p.filename() == entry.path().filename();
- });
- if (it != modules_to_load.end()) {
- *it = entry.path();
- } else {
- modules_to_load.push_back(entry.path());
- }
- }
- }
-
- for (const auto& module_path : modules_to_load) {
- LOG_INFO(Loader, "Loading {}", fmt::UTF(module_path.u8string()));
- linker->LoadModule(module_path);
- }
+ });
#ifdef ENABLE_DISCORD_RPC
// Discord RPC
@@ -278,7 +252,7 @@ void Emulator::Run(const std::filesystem::path& file) {
std::exit(0);
}
-void Emulator::LoadSystemModules(const std::filesystem::path& file, std::string game_serial) {
+void Emulator::LoadSystemModules(const std::string& game_serial) {
constexpr std::array ModulesToLoad{
{{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterlibSceNgs2},
{"libSceUlt.sprx", nullptr},
diff --git a/src/emulator.h b/src/emulator.h
index e973e9022..a08ab43c3 100644
--- a/src/emulator.h
+++ b/src/emulator.h
@@ -29,7 +29,7 @@ public:
void UpdatePlayTime(const std::string& serial);
private:
- void LoadSystemModules(const std::filesystem::path& file, std::string game_serial);
+ void LoadSystemModules(const std::string& game_serial);
Core::MemoryManager* memory;
Input::GameController* controller;