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/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/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/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; diff --git a/src/shader_recompiler/frontend/control_flow_graph.cpp b/src/shader_recompiler/frontend/control_flow_graph.cpp index 0816ec088..ec5c117f7 100644 --- a/src/shader_recompiler/frontend/control_flow_graph.cpp +++ b/src/shader_recompiler/frontend/control_flow_graph.cpp @@ -47,13 +47,26 @@ static IR::Condition MakeCondition(const GcnInst& inst) { } } -static bool IgnoresExecMask(Opcode opcode) { - switch (opcode) { - case Opcode::V_WRITELANE_B32: +static bool IgnoresExecMask(const GcnInst& inst) { + // EXEC mask does not affect scalar instructions or branches. + switch (inst.category) { + case InstCategory::ScalarALU: + case InstCategory::ScalarMemory: + case InstCategory::FlowControl: return true; default: - return false; + break; } + // Read/Write Lane instructions are not affected either. + switch (inst.opcode) { + case Opcode::V_READLANE_B32: + case Opcode::V_WRITELANE_B32: + case Opcode::V_READFIRSTLANE_B32: + return true; + default: + break; + } + return false; } static constexpr size_t LabelReserveSize = 32; @@ -147,8 +160,7 @@ void CFG::EmitDivergenceLabels() { // If all instructions in the scope ignore exec masking, we shouldn't insert a // scope. const auto start = inst_list.begin() + curr_begin + 1; - if (!std::ranges::all_of(start, inst_list.begin() + index, IgnoresExecMask, - &GcnInst::opcode)) { + if (!std::ranges::all_of(start, inst_list.begin() + index, IgnoresExecMask)) { // Add a label to the instruction right after the open scope call. // It is the start of a new basic block. const auto& save_inst = inst_list[curr_begin];