diff --git a/CMakeLists.txt b/CMakeLists.txt index 43e8d7cab..cd3894719 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -425,6 +425,8 @@ set(NP_LIBS src/core/libraries/np_manager/np_manager.cpp set(MISC_LIBS src/core/libraries/screenshot/screenshot.cpp src/core/libraries/screenshot/screenshot.h + src/core/libraries/move/move.cpp + src/core/libraries/move/move.h ) set(DEV_TOOLS src/core/devtools/layer.cpp diff --git a/README.md b/README.md index 7ba13ad47..fd40d2d63 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,11 @@ R3 | M | | - [**skmp**](https://github.com/skmp) - [**wheremyfoodat**](https://github.com/wheremyfoodat) - [**raziel1000**](https://github.com/raziel1000) +- [**viniciuslrangel**](https://github.com/viniciuslrangel) +- [**roamic**](https://github.com/vladmikhalin) +- [**poly**](https://github.com/polybiusproxy) +- [**squidbus**](https://github.com/squidbus) +- [**frodo**](https://github.com/baggins183) Logo is done by [**Xphalnos**](https://github.com/Xphalnos) diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index 75c61a188..a2fd2c0a4 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -97,6 +97,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, Http) \ SUB(Lib, Ssl) \ SUB(Lib, SysModule) \ + SUB(Lib, Move) \ SUB(Lib, NpManager) \ SUB(Lib, NpScore) \ SUB(Lib, NpTrophy) \ diff --git a/src/common/logging/types.h b/src/common/logging/types.h index a0e7d021f..5b496d175 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -57,8 +57,9 @@ enum class Class : u8 { Lib_MsgDlg, ///< The LibSceMsgDialog implementation. Lib_AudioOut, ///< The LibSceAudioOut implementation. Lib_AudioIn, ///< The LibSceAudioIn implementation. + Lib_Move, ///< The LibSceMove implementation. Lib_Net, ///< The LibSceNet implementation. - Lib_NetCtl, ///< The LibSecNetCtl implementation. + Lib_NetCtl, ///< The LibSceNetCtl implementation. Lib_SaveData, ///< The LibSceSaveData implementation. Lib_SaveDataDialog, ///< The LibSceSaveDataDialog implementation. Lib_Ssl, ///< The LibSceSsl implementation. diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index 24f5e9f87..2b7331cbd 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -26,7 +26,7 @@ asm(".zerofill GUEST_SYSTEM,GUEST_SYSTEM,__guest_system,0xFBFC00000"); namespace Core { -static constexpr size_t BackingSize = SCE_KERNEL_MAIN_DMEM_SIZE_PRO; +static constexpr size_t BackingSize = SCE_KERNEL_TOTAL_MEM_PRO; #ifdef _WIN32 diff --git a/src/core/libraries/ajm/ajm_batch.h b/src/core/libraries/ajm/ajm_batch.h index 65110ee73..3c586b773 100644 --- a/src/core/libraries/ajm/ajm_batch.h +++ b/src/core/libraries/ajm/ajm_batch.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/src/core/libraries/audio/cubeb_audio.cpp b/src/core/libraries/audio/cubeb_audio.cpp index ca0a4c3b6..e1195558a 100644 --- a/src/core/libraries/audio/cubeb_audio.cpp +++ b/src/core/libraries/audio/cubeb_audio.cpp @@ -5,7 +5,7 @@ #include #include -#include "common/assert.h" +#include "common/logging/log.h" #include "common/ringbuffer.h" #include "core/libraries/audio/audioout.h" #include "core/libraries/audio/audioout_backend.h" @@ -58,6 +58,8 @@ public: } if (const auto ret = cubeb_stream_start(stream); ret != CUBEB_OK) { LOG_ERROR(Lib_AudioOut, "Failed to start cubeb stream: {}", ret); + cubeb_stream_destroy(stream); + stream = nullptr; return; } } @@ -74,6 +76,9 @@ public: } void Output(void* ptr, size_t size) override { + if (!stream) { + return; + } auto* data = static_cast(ptr); std::unique_lock lock{buffer_mutex}; diff --git a/src/core/libraries/audio/sdl_audio.cpp b/src/core/libraries/audio/sdl_audio.cpp index 7d7a7cee5..598941ba7 100644 --- a/src/core/libraries/audio/sdl_audio.cpp +++ b/src/core/libraries/audio/sdl_audio.cpp @@ -2,9 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include - #include -#include #include "common/logging/log.h" #include "core/libraries/audio/audioout.h" @@ -26,18 +24,28 @@ public: SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, nullptr, nullptr); if (stream == nullptr) { LOG_ERROR(Lib_AudioOut, "Failed to create SDL audio stream: {}", SDL_GetError()); + return; + } + if (!SDL_ResumeAudioStreamDevice(stream)) { + LOG_ERROR(Lib_AudioOut, "Failed to resume SDL audio stream: {}", SDL_GetError()); + SDL_DestroyAudioStream(stream); + stream = nullptr; + return; } - SDL_ResumeAudioStreamDevice(stream); } ~SDLPortBackend() override { - if (stream) { - SDL_DestroyAudioStream(stream); - stream = nullptr; + if (!stream) { + return; } + SDL_DestroyAudioStream(stream); + stream = nullptr; } void Output(void* ptr, size_t size) override { + if (!stream) { + return; + } SDL_PutAudioStreamData(stream, ptr, static_cast(size)); while (SDL_GetAudioStreamAvailable(stream) > AUDIO_STREAM_BUFFER_THRESHOLD) { // Yield to allow the stream to drain. @@ -46,7 +54,15 @@ public: } void SetVolume(const std::array& ch_volumes) override { - // TODO: Not yet implemented + if (!stream) { + return; + } + // SDL does not have per-channel volumes, for now just take the maximum of the channels. + const auto vol = *std::ranges::max_element(ch_volumes); + if (!SDL_SetAudioStreamGain(stream, static_cast(vol) / SCE_AUDIO_OUT_VOLUME_0DB)) { + LOG_WARNING(Lib_AudioOut, "Failed to change SDL audio stream volume: {}", + SDL_GetError()); + } } private: diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index 566f8ce1f..91a1329e5 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -513,10 +513,14 @@ void PS4_SYSV_ABI sceGnmDingDong(u32 gnm_vqid, u32 next_offs_dw) { auto vqid = gnm_vqid - 1; auto& asc_queue = liverpool->asc_queues[{vqid}]; - const auto& offs_dw = asc_next_offs_dw[vqid]; + auto& offs_dw = asc_next_offs_dw[vqid]; - if (next_offs_dw < offs_dw) { - ASSERT_MSG(next_offs_dw == 0, "ACB submission is split at the end of ring buffer"); + if (next_offs_dw < offs_dw && next_offs_dw != 0) { + // For cases if a submission is split at the end of the ring buffer, we need to submit it in + // two parts to handle the wrap + liverpool->SubmitAsc(gnm_vqid, {reinterpret_cast(asc_queue.map_addr) + offs_dw, + asc_queue.ring_size_dw - offs_dw}); + offs_dw = 0; } const auto* acb_ptr = reinterpret_cast(asc_queue.map_addr) + offs_dw; diff --git a/src/core/libraries/kernel/memory.h b/src/core/libraries/kernel/memory.h index 2d19ceb49..400b6c3fc 100644 --- a/src/core/libraries/kernel/memory.h +++ b/src/core/libraries/kernel/memory.h @@ -6,9 +6,11 @@ #include "common/bit_field.h" #include "common/types.h" -constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE = 5056_MB; // ~ 5GB -// TODO: Confirm this value on hardware. -constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE_PRO = 5568_MB; // ~ 5.5GB +constexpr u64 SCE_KERNEL_TOTAL_MEM = 5248_MB; +constexpr u64 SCE_KERNEL_TOTAL_MEM_PRO = 5888_MB; + +constexpr u64 SCE_FLEXIBLE_MEMORY_BASE = 64_MB; +constexpr u64 SCE_FLEXIBLE_MEMORY_SIZE = 512_MB; namespace Core::Loader { class SymbolsResolver; @@ -129,10 +131,6 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags); int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len); -void* Malloc(size_t size); - -void Free(void* ptr); - void RegisterMemory(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Kernel diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 66cdd5b87..c30c2d7c3 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -18,6 +18,7 @@ #include "core/libraries/libc_internal/libc_internal.h" #include "core/libraries/libpng/pngdec.h" #include "core/libraries/libs.h" +#include "core/libraries/move/move.h" #include "core/libraries/network/http.h" #include "core/libraries/network/net.h" #include "core/libraries/network/netctl.h" @@ -91,6 +92,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::Remoteplay::RegisterlibSceRemoteplay(sym); Libraries::Videodec::RegisterlibSceVideodec(sym); Libraries::RazorCpu::RegisterlibSceRazorCpu(sym); + Libraries::Move::RegisterlibSceMove(sym); } } // namespace Libraries diff --git a/src/core/libraries/move/move.cpp b/src/core/libraries/move/move.cpp new file mode 100644 index 000000000..626fed9b4 --- /dev/null +++ b/src/core/libraries/move/move.cpp @@ -0,0 +1,44 @@ +// 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 "move.h" + +namespace Libraries::Move { + +int PS4_SYSV_ABI sceMoveOpen() { + LOG_ERROR(Lib_Move, "(STUBBED) called"); + return ORBIS_FAIL; +} + +int PS4_SYSV_ABI sceMoveGetDeviceInfo() { + LOG_ERROR(Lib_Move, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceMoveReadStateRecent() { + LOG_TRACE(Lib_Move, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceMoveTerm() { + LOG_ERROR(Lib_Move, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceMoveInit() { + LOG_ERROR(Lib_Move, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterlibSceMove(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("HzC60MfjJxU", "libSceMove", 1, "libSceMove", 1, 1, sceMoveOpen); + LIB_FUNCTION("GWXTyxs4QbE", "libSceMove", 1, "libSceMove", 1, 1, sceMoveGetDeviceInfo); + LIB_FUNCTION("f2bcpK6kJfg", "libSceMove", 1, "libSceMove", 1, 1, sceMoveReadStateRecent); + LIB_FUNCTION("tsZi60H4ypY", "libSceMove", 1, "libSceMove", 1, 1, sceMoveTerm); + LIB_FUNCTION("j1ITE-EoJmE", "libSceMove", 1, "libSceMove", 1, 1, sceMoveInit); +}; + +} // namespace Libraries::Move \ No newline at end of file diff --git a/src/core/libraries/move/move.h b/src/core/libraries/move/move.h new file mode 100644 index 000000000..2d7adaba7 --- /dev/null +++ b/src/core/libraries/move/move.h @@ -0,0 +1,21 @@ +// 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::Move { + +int PS4_SYSV_ABI sceMoveOpen(); +int PS4_SYSV_ABI sceMoveGetDeviceInfo(); +int PS4_SYSV_ABI sceMoveReadStateRecent(); +int PS4_SYSV_ABI sceMoveTerm(); +int PS4_SYSV_ABI sceMoveInit(); + +void RegisterlibSceMove(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Move \ No newline at end of file diff --git a/src/core/libraries/np_trophy/np_trophy.cpp b/src/core/libraries/np_trophy/np_trophy.cpp index 9324ed6bb..ccd8ab710 100644 --- a/src/core/libraries/np_trophy/np_trophy.cpp +++ b/src/core/libraries/np_trophy/np_trophy.cpp @@ -498,7 +498,7 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, OrbisNpTrophyFlagArray* flags, u32* count) { - LOG_INFO(Lib_NpTrophy, "GetTrophyUnlockState called"); + LOG_INFO(Lib_NpTrophy, "called"); if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; @@ -519,8 +519,9 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context, pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str()); if (!result) { - LOG_ERROR(Lib_NpTrophy, "Failed to open trophy xml : {}", result.description()); - return -1; + LOG_ERROR(Lib_NpTrophy, "Failed to open trophy XML: {}", result.description()); + *count = 0; + return ORBIS_OK; } int num_trophies = 0; diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 9cf4198ae..28d2eea7b 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -5,6 +5,7 @@ #include "common/arch.h" #include "common/assert.h" #include "common/config.h" +#include "common/elf_info.h" #include "common/logging/log.h" #include "common/path_util.h" #include "common/string_util.h" @@ -65,21 +66,41 @@ void Linker::Execute() { Relocate(m.get()); } - // Configure used flexible memory size. - if (const auto* proc_param = GetProcParam()) { - if (proc_param->size >= - offsetof(OrbisProcParam, mem_param) + sizeof(OrbisKernelMemParam*)) { - if (const auto* mem_param = proc_param->mem_param) { - if (mem_param->size >= - offsetof(OrbisKernelMemParam, flexible_memory_size) + sizeof(u64*)) { - if (const auto* flexible_size = mem_param->flexible_memory_size) { - memory->SetupMemoryRegions(*flexible_size); - } + // Configure the direct and flexible memory regions. + u64 fmem_size = SCE_FLEXIBLE_MEMORY_SIZE; + bool use_extended_mem1 = true, use_extended_mem2 = true; + + const auto* proc_param = GetProcParam(); + ASSERT(proc_param); + + Core::OrbisKernelMemParam mem_param{}; + if (proc_param->size >= offsetof(OrbisProcParam, mem_param) + sizeof(OrbisKernelMemParam*)) { + if (proc_param->mem_param) { + mem_param = *proc_param->mem_param; + if (mem_param.size >= + offsetof(OrbisKernelMemParam, flexible_memory_size) + sizeof(u64*)) { + if (const auto* flexible_size = mem_param.flexible_memory_size) { + fmem_size = *flexible_size + SCE_FLEXIBLE_MEMORY_BASE; } } } } + if (mem_param.size < offsetof(OrbisKernelMemParam, extended_memory_1) + sizeof(u64*)) { + mem_param.extended_memory_1 = nullptr; + } + if (mem_param.size < offsetof(OrbisKernelMemParam, extended_memory_2) + sizeof(u64*)) { + mem_param.extended_memory_2 = nullptr; + } + + const u64 sdk_ver = proc_param->sdk_version; + if (sdk_ver < Common::ElfInfo::FW_50) { + use_extended_mem1 = mem_param.extended_memory_1 ? *mem_param.extended_memory_1 : false; + use_extended_mem2 = mem_param.extended_memory_2 ? *mem_param.extended_memory_2 : false; + } + + memory->SetupMemoryRegions(fmem_size, use_extended_mem1, use_extended_mem2); + main_thread.Run([this, module](std::stop_token) { Common::SetCurrentThreadName("GAME_MainThread"); LoadSharedLibraries(); diff --git a/src/core/linker.h b/src/core/linker.h index d6b5d648a..7ef13ae56 100644 --- a/src/core/linker.h +++ b/src/core/linker.h @@ -22,8 +22,9 @@ struct OrbisKernelMemParam { u8* extended_memory_1; u64* extended_gpu_page_table; u8* extended_memory_2; - u64* exnteded_cpu_page_table; + u64* extended_cpu_page_table; }; +static_assert(sizeof(OrbisKernelMemParam) == 0x38); struct OrbisProcParam { u64 size; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index aa116fa3d..0a69ad773 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -12,12 +12,7 @@ namespace Core { -constexpr u64 SCE_DEFAULT_FLEXIBLE_MEMORY_SIZE = 448_MB; - MemoryManager::MemoryManager() { - // Set up the direct and flexible memory regions. - SetupMemoryRegions(SCE_DEFAULT_FLEXIBLE_MEMORY_SIZE); - // Insert a virtual memory area that covers the entire area we manage. const VAddr system_managed_base = impl.SystemManagedVirtualBase(); const size_t system_managed_size = impl.SystemManagedVirtualSize(); @@ -38,10 +33,17 @@ MemoryManager::MemoryManager() { MemoryManager::~MemoryManager() = default; -void MemoryManager::SetupMemoryRegions(u64 flexible_size) { - const auto total_size = - Config::isNeoMode() ? SCE_KERNEL_MAIN_DMEM_SIZE_PRO : SCE_KERNEL_MAIN_DMEM_SIZE; - total_flexible_size = flexible_size; +void MemoryManager::SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1, + bool use_extended_mem2) { + const bool is_neo = Config::isNeoMode(); + auto total_size = is_neo ? SCE_KERNEL_TOTAL_MEM_PRO : SCE_KERNEL_TOTAL_MEM; + if (!use_extended_mem1 && is_neo) { + total_size -= 256_MB; + } + if (!use_extended_mem2 && !is_neo) { + total_size -= 128_MB; + } + total_flexible_size = flexible_size - SCE_FLEXIBLE_MEMORY_BASE; total_direct_size = total_size - flexible_size; // Insert an area that covers direct memory physical block. diff --git a/src/core/memory.h b/src/core/memory.h index a9f2df322..615ecc3eb 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -166,7 +166,7 @@ public: bool TryWriteBacking(void* address, const void* data, u32 num_bytes); - void SetupMemoryRegions(u64 flexible_size); + void SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1, bool use_extended_mem2); PAddr PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment); diff --git a/src/main.cpp b/src/main.cpp index 17b5c11fe..bdbab89c9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,11 +4,14 @@ #include "functional" #include "iostream" #include "string" +#include "system_error" #include "unordered_map" #include #include "common/config.h" #include "common/memory_patcher.h" +#include "common/path_util.h" +#include "core/file_sys/fs.h" #include "emulator.h" #ifdef _WIN32 @@ -20,6 +23,10 @@ int main(int argc, char* argv[]) { SetConsoleOutputCP(CP_UTF8); #endif + // Load configurations + const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + Config::load(user_dir / "config.toml"); + bool has_game_argument = false; std::string game_path; @@ -33,6 +40,7 @@ int main(int argc, char* argv[]) { " -p, --patch Apply specified patch file\n" " -f, --fullscreen Specify window initial fullscreen " "state. Does not overwrite the config file.\n" + " --add-game-folder Adds a new game folder to the config.\n" " -h, --help Display this help message\n"; exit(0); }}, @@ -81,6 +89,25 @@ int main(int argc, char* argv[]) { Config::setFullscreenMode(is_fullscreen); }}, {"--fullscreen", [&](int& i) { arg_map["-f"](i); }}, + {"--add-game-folder", + [&](int& i) { + if (++i >= argc) { + std::cerr << "Error: Missing argument for --add-game-folder\n"; + exit(1); + } + std::string config_dir(argv[i]); + std::filesystem::path config_path = std::filesystem::path(config_dir); + std::error_code discard; + if (!std::filesystem::exists(config_path, discard)) { + std::cerr << "Error: File does not exist: " << config_path << "\n"; + exit(1); + } + + Config::addGameInstallDir(config_path); + Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); + std::cout << "Game folder successfully saved.\n"; + exit(0); + }}, }; if (argc == 1) { @@ -105,20 +132,41 @@ int main(int argc, char* argv[]) { } } + // If no game directory is set and no command line argument, prompt for it + if (Config::getGameInstallDirs().empty()) { + std::cout << "Warning: No game folder set, please set it by calling shadps4" + " with the --add-game-folder argument"; + } + if (!has_game_argument) { std::cerr << "Error: Please provide a game path or ID.\n"; exit(1); } // Check if the game path or ID exists - if (!std::filesystem::exists(game_path)) { - std::cerr << "Error: Game file not found\n"; - return -1; + std::filesystem::path eboot_path(game_path); + + // Check if the provided path is a valid file + if (!std::filesystem::exists(eboot_path)) { + // If not a file, treat it as a game ID and search in install directories + bool game_found = false; + for (const auto& install_dir : Config::getGameInstallDirs()) { + const auto candidate_path = install_dir / game_path / "eboot.bin"; + if (std::filesystem::exists(candidate_path)) { + eboot_path = candidate_path; + game_found = true; + break; + } + } + if (!game_found) { + std::cerr << "Error: Game ID or file path not found: " << game_path << std::endl; + return 1; + } } - // Run the emulator with the specified game + // Run the emulator with the resolved eboot path Core::Emulator emulator; - emulator.Run(game_path); + emulator.Run(eboot_path); return 0; } diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 6c96ab37f..bbc84c4fc 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -347,9 +347,8 @@ public: if (selected == deleteUpdate) { if (!std::filesystem::exists(Common::FS::PathFromQString(game_update_path))) { - QMessageBox::critical( - nullptr, tr("Error"), - QString(tr("This game has no separate update to delete!"))); + QMessageBox::critical(nullptr, tr("Error"), + QString(tr("This game has no update to delete!"))); error = true; } else { folder_path = game_update_path; diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index 318245053..ac731fdce 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "iostream" +#include "system_error" #include "unordered_map" #include "common/config.h" @@ -31,7 +32,7 @@ int main(int argc, char* argv[]) { bool has_command_line_argument = argc > 1; bool show_gui = false, has_game_argument = false; - std::string gamePath; + std::string game_path; // Map of argument strings to lambda functions std::unordered_map> arg_map = { @@ -46,6 +47,7 @@ int main(int argc, char* argv[]) { " -s, --show-gui Show the GUI\n" " -f, --fullscreen Specify window initial fullscreen " "state. Does not overwrite the config file.\n" + " --add-game-folder Adds a new game folder to the config.\n" " -h, --help Display this help message\n"; exit(0); }}, @@ -57,7 +59,7 @@ int main(int argc, char* argv[]) { {"-g", [&](int& i) { if (i + 1 < argc) { - gamePath = argv[++i]; + game_path = argv[++i]; has_game_argument = true; } else { std::cerr << "Error: Missing argument for -g/--game\n"; @@ -98,6 +100,25 @@ int main(int argc, char* argv[]) { Config::setFullscreenMode(is_fullscreen); }}, {"--fullscreen", [&](int& i) { arg_map["-f"](i); }}, + {"--add-game-folder", + [&](int& i) { + if (++i >= argc) { + std::cerr << "Error: Missing argument for --add-game-folder\n"; + exit(1); + } + std::string config_dir(argv[i]); + std::filesystem::path config_path = std::filesystem::path(config_dir); + std::error_code discard; + if (!std::filesystem::is_directory(config_path, discard)) { + std::cerr << "Error: Directory does not exist: " << config_path << "\n"; + exit(1); + } + + Config::addGameInstallDir(config_path); + Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); + std::cout << "Game folder successfully saved.\n"; + exit(0); + }}, }; // Parse command-line arguments using the map @@ -106,6 +127,10 @@ int main(int argc, char* argv[]) { auto it = arg_map.find(cur_arg); if (it != arg_map.end()) { it->second(i); // Call the associated lambda function + } else if (i == argc - 1 && !has_game_argument) { + // Assume the last argument is the game file if not specified via -g/--game + game_path = argv[i]; + has_game_argument = true; } else { std::cerr << "Unknown argument: " << cur_arg << ", see --help for info.\n"; return 1; @@ -134,14 +159,14 @@ int main(int argc, char* argv[]) { // Process game path or ID if provided if (has_game_argument) { - std::filesystem::path game_file_path(gamePath); + std::filesystem::path game_file_path(game_path); // Check if the provided path is a valid file if (!std::filesystem::exists(game_file_path)) { // If not a file, treat it as a game ID and search in install directories bool game_found = false; for (const auto& install_dir : Config::getGameInstallDirs()) { - auto potential_game_path = install_dir / gamePath / "eboot.bin"; + auto potential_game_path = install_dir / game_path / "eboot.bin"; if (std::filesystem::exists(potential_game_path)) { game_file_path = potential_game_path; game_found = true; @@ -149,7 +174,7 @@ int main(int argc, char* argv[]) { } } if (!game_found) { - std::cerr << "Error: Game ID or file path not found: " << gamePath << std::endl; + std::cerr << "Error: Game ID or file path not found: " << game_path << std::endl; return 1; } } diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 326fe9e10..42389084b 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -115,6 +115,7 @@ void MainWindow::CreateActions() { m_theme_act_group->addAction(ui->setThemeBlue); m_theme_act_group->addAction(ui->setThemeViolet); m_theme_act_group->addAction(ui->setThemeGruvbox); + m_theme_act_group->addAction(ui->setThemeTokyoNight); m_theme_act_group->addAction(ui->setThemeSystem); } @@ -572,6 +573,14 @@ void MainWindow::CreateConnects() { isIconBlack = false; } }); + connect(ui->setThemeTokyoNight, &QAction::triggered, &m_window_themes, [this]() { + m_window_themes.SetWindowTheme(Theme::TokyoNight, ui->mw_searchbar); + Config::setMainWindowTheme(static_cast(Theme::TokyoNight)); + if (isIconBlack) { + SetUiIcons(false); + isIconBlack = false; + } + }); connect(ui->setThemeSystem, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::System, ui->mw_searchbar); Config::setMainWindowTheme(static_cast(Theme::System)); @@ -980,6 +989,11 @@ void MainWindow::SetLastUsedTheme() { isIconBlack = false; SetUiIcons(false); break; + case Theme::TokyoNight: + ui->setThemeTokyoNight->setChecked(true); + isIconBlack = false; + SetUiIcons(false); + break; case Theme::System: ui->setThemeSystem->setChecked(true); bool isSystemDarkMode; diff --git a/src/qt_gui/main_window_themes.cpp b/src/qt_gui/main_window_themes.cpp index 645b0a734..12d810bde 100644 --- a/src/qt_gui/main_window_themes.cpp +++ b/src/qt_gui/main_window_themes.cpp @@ -151,6 +151,28 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { themePalette.setColor(QPalette::HighlightedText, Qt::black); qApp->setPalette(themePalette); break; + case Theme::TokyoNight: + mw_searchbar->setStyleSheet( + "QLineEdit {" + "background-color: #1a1b26; color: #9d7cd8; border: 1px solid #9d7cd8; " + "border-radius: 4px; padding: 5px; }" + "QLineEdit:focus {" + "border: 1px solid #7aa2f7; }"); + themePalette.setColor(QPalette::Window, QColor(31, 35, 53)); + themePalette.setColor(QPalette::WindowText, QColor(192, 202, 245)); + themePalette.setColor(QPalette::Base, QColor(25, 28, 39)); + themePalette.setColor(QPalette::AlternateBase, QColor(36, 40, 59)); + themePalette.setColor(QPalette::ToolTipBase, QColor(192, 202, 245)); + themePalette.setColor(QPalette::ToolTipText, QColor(192, 202, 245)); + themePalette.setColor(QPalette::Text, QColor(192, 202, 245)); + themePalette.setColor(QPalette::Button, QColor(30, 30, 41)); + themePalette.setColor(QPalette::ButtonText, QColor(192, 202, 245)); + themePalette.setColor(QPalette::BrightText, QColor(197, 59, 83)); + themePalette.setColor(QPalette::Link, QColor(79, 214, 190)); + themePalette.setColor(QPalette::Highlight, QColor(79, 214, 190)); + themePalette.setColor(QPalette::HighlightedText, Qt::black); + qApp->setPalette(themePalette); + break; case Theme::System: mw_searchbar->setStyleSheet("QLineEdit {" "border: 1px solid;" diff --git a/src/qt_gui/main_window_themes.h b/src/qt_gui/main_window_themes.h index d60cadb6d..4389f5d34 100644 --- a/src/qt_gui/main_window_themes.h +++ b/src/qt_gui/main_window_themes.h @@ -9,7 +9,7 @@ #include #include -enum class Theme : int { Dark, Light, Green, Blue, Violet, Gruvbox, System }; +enum class Theme : int { Dark, Light, Green, Blue, Violet, Gruvbox, TokyoNight, System }; class WindowThemes : public QObject { Q_OBJECT diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 7cbe0bb4b..46513df9e 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -37,6 +37,7 @@ public: QAction* setThemeBlue; QAction* setThemeViolet; QAction* setThemeGruvbox; + QAction* setThemeTokyoNight; QAction* setThemeSystem; QWidget* centralWidget; QLineEdit* mw_searchbar; @@ -163,6 +164,9 @@ public: setThemeGruvbox = new QAction(MainWindow); setThemeGruvbox->setObjectName("setThemeGruvbox"); setThemeGruvbox->setCheckable(true); + setThemeTokyoNight = new QAction(MainWindow); + setThemeTokyoNight->setObjectName("setThemeTokyoNight"); + setThemeTokyoNight->setCheckable(true); setThemeSystem = new QAction(MainWindow); setThemeSystem->setObjectName("setThemeSystem"); setThemeSystem->setCheckable(true); @@ -291,6 +295,7 @@ public: menuThemes->addAction(setThemeBlue); menuThemes->addAction(setThemeViolet); menuThemes->addAction(setThemeGruvbox); + menuThemes->addAction(setThemeTokyoNight); menuThemes->addAction(setThemeSystem); menuGame_List_Icons->addAction(setIconSizeTinyAct); menuGame_List_Icons->addAction(setIconSizeSmallAct); @@ -379,6 +384,7 @@ public: setThemeBlue->setText(QCoreApplication::translate("MainWindow", "Blue", nullptr)); setThemeViolet->setText(QCoreApplication::translate("MainWindow", "Violet", nullptr)); setThemeGruvbox->setText("Gruvbox"); + setThemeTokyoNight->setText("Tokyo Night"); setThemeSystem->setText(QCoreApplication::translate("MainWindow", "System", nullptr)); toolBar->setWindowTitle(QCoreApplication::translate("MainWindow", "toolBar", nullptr)); } // retranslateUi diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index f6868a123..b82f41bcf 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -200,7 +200,6 @@ SettingsDialog::SettingsDialog(std::span physical_devices, ui->fullscreenCheckBox->installEventFilter(this); ui->separateUpdatesCheckBox->installEventFilter(this); ui->showSplashCheckBox->installEventFilter(this); - ui->ps4proCheckBox->installEventFilter(this); ui->discordRPCCheckbox->installEventFilter(this); ui->userName->installEventFilter(this); ui->logTypeGroupBox->installEventFilter(this); @@ -297,7 +296,6 @@ void SettingsDialog::LoadValuesFromConfig() { ui->separateUpdatesCheckBox->setChecked( toml::find_or(data, "General", "separateUpdateEnabled", false)); ui->showSplashCheckBox->setChecked(toml::find_or(data, "General", "showSplash", false)); - ui->ps4proCheckBox->setChecked(toml::find_or(data, "General", "isPS4Pro", false)); ui->logTypeComboBox->setCurrentText( QString::fromStdString(toml::find_or(data, "General", "logType", "async"))); ui->logFilterLineEdit->setText( @@ -414,8 +412,6 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("separateUpdatesCheckBox"); } else if (elementName == "showSplashCheckBox") { text = tr("showSplashCheckBox"); - } else if (elementName == "ps4proCheckBox") { - text = tr("ps4proCheckBox"); } else if (elementName == "discordRPCCheckbox") { text = tr("discordRPCCheckbox"); } else if (elementName == "userName") { @@ -528,11 +524,9 @@ void SettingsDialog::UpdateSettings() { const QVector TouchPadIndex = {"left", "center", "right", "none"}; Config::setBackButtonBehavior(TouchPadIndex[ui->backButtonBehaviorComboBox->currentIndex()]); - Config::setNeoMode(ui->ps4proCheckBox->isChecked()); Config::setFullscreenMode(ui->fullscreenCheckBox->isChecked()); Config::setisTrophyPopupDisabled(ui->disableTrophycheckBox->isChecked()); Config::setPlayBGM(ui->playBGMCheckBox->isChecked()); - Config::setNeoMode(ui->ps4proCheckBox->isChecked()); Config::setLogType(ui->logTypeComboBox->currentText().toStdString()); Config::setLogFilter(ui->logFilterLineEdit->text().toStdString()); Config::setUserName(ui->userNameLineEdit->text().toStdString()); @@ -609,4 +603,4 @@ void SettingsDialog::ResetInstallFolders() { } Config::setGameInstallDirs(settings_install_dirs_config); } -} +} \ No newline at end of file diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index c55206dab..44d776beb 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -246,13 +246,6 @@ - - - - Is PS4 Pro - - - diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 4ceb91315..32b838fac 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -2,7 +2,7 @@ + SPDX-License-Identifier: GPL-2.0-or-later --> AboutDialog @@ -18,7 +18,7 @@ shadPS4 is an experimental open-source emulator for the PlayStation 4. - shadPS4 是一款实验性质的开源 PlayStation 4模拟器软件。 + shadPS4 是一款实验性质的开源 PlayStation 4 模拟器软件。 @@ -103,7 +103,7 @@ Cheats / Patches - 作弊码 / 补丁 + 作弊码/补丁 @@ -113,7 +113,7 @@ Trophy Viewer - Trophy 查看器 + 奖杯查看器 @@ -128,7 +128,7 @@ Open Save Data Folder - 打开保存数据文件夹 + 打开存档数据文件夹 @@ -173,27 +173,27 @@ Delete DLC - 删除DLC + 删除 DLC Compatibility... - Compatibility... + 兼容性... Update database - Update database + 更新数据库 View report - View report + 查看报告 Submit a report - Submit a report + 提交报告 @@ -203,7 +203,7 @@ Shortcut created successfully! - 创建快捷方式成功! + 创建快捷方式成功! @@ -213,7 +213,7 @@ Error creating shortcut! - 创建快捷方式出错! + 创建快捷方式出错! @@ -228,7 +228,7 @@ requiresEnableSeparateUpdateFolder_MSG - 这个功能需要‘启用单独的更新目录’配置选项才能正常运行,如果你想要使用这个功能,请启用它。 + 这个功能需要“启用单独的更新目录”配置选项才能正常运行,如果你想要使用这个功能,请启用它。 @@ -243,7 +243,7 @@ This game has no DLC to delete! - 这个游戏没有DLC可以删除! + 这个游戏没有 DLC 可以删除! @@ -258,7 +258,7 @@ Are you sure you want to delete %1's %2 directory? - 你确定要删除 %1 的 %2 目录? + 你确定要删除 %1 的%2目录? @@ -266,7 +266,7 @@ Open/Add Elf Folder - 打开/添加Elf文件夹 + 打开/添加 Elf 文件夹 @@ -316,7 +316,7 @@ Exit the application. - 退出应用程序. + 退出应用程序。 @@ -341,12 +341,12 @@ Medium - 中等 + Large - 巨大 + @@ -376,7 +376,7 @@ Dump Game List - 转储游戏列表 + 导出游戏列表 @@ -472,7 +472,7 @@ Trophy Viewer - Trophy 查看器 + 奖杯查看器 @@ -485,7 +485,7 @@ General - 通用 + 常规 @@ -520,12 +520,12 @@ Show Splash - 显示Splash + 显示启动画面 Is PS4 Pro - 是否是 PS4 Pro + 模拟 PS4 Pro @@ -570,17 +570,17 @@ Hide Cursor Idle Timeout - 光标空闲超时隐藏 + 光标隐藏闲置时长 s - s + Controller - 控制器 + 手柄 @@ -595,7 +595,7 @@ Graphics Device - 图像设备 + 图形设备 @@ -700,7 +700,7 @@ Disable Trophy Pop-ups - Disable Trophy Pop-ups + 禁止弹出奖杯 @@ -710,22 +710,22 @@ Update Compatibility Database On Startup - Update Compatibility Database On Startup + 启动时更新兼容性数据库 Game Compatibility - Game Compatibility + 游戏兼容性 Display Compatibility Data - Display Compatibility Data + 显示兼容性数据 Update Compatibility Database - Update Compatibility Database + 更新兼容性数据库 @@ -735,7 +735,7 @@ Audio Backend - Audio Backend + 音频后端 @@ -778,12 +778,12 @@ All Patches available for all games have been downloaded. - 所有游戏的所有补丁都已下载。 + 所有游戏的可用补丁都已下载。 Games: - 游戏: + 游戏: @@ -798,7 +798,7 @@ Game Boot - 游戏启动 + 启动游戏 @@ -818,7 +818,7 @@ PKG and Game versions match: - PKG 和游戏版本匹配: + PKG 和游戏版本匹配: @@ -828,17 +828,17 @@ PKG Version %1 is older than installed version: - PKG 版本 %1 比已安装版本更旧: + PKG 版本 %1 比已安装版本更旧: Game is installed: - 游戏已安装: + 游戏已安装: Would you like to install Patch: - 您想安装补丁吗: + 您想安装补丁吗: @@ -848,12 +848,12 @@ Would you like to install DLC: %1? - 您想安装 DLC: %1 吗? + 您想安装 DLC:%1 吗? DLC already installed: - DLC 已经安装: + DLC 已经安装: @@ -896,42 +896,42 @@ Cheats / Patches for - Cheats / Patches for + 作弊码/补丁: defaultTextEdit_MSG - 作弊/补丁是实验性的。\n请小心使用。\n\n通过选择存储库并点击下载按钮,单独下载作弊程序。\n在“补丁”选项卡中,您可以一次性下载所有补丁,选择要使用的补丁并保存选择。\n\n由于我们不开发作弊程序/补丁,\n请将问题报告给作弊程序的作者。\n\n创建了新的作弊程序?访问:\nhttps://github.com/shadps4-emu/ps4_cheats + 作弊码/补丁是实验性的。\n请小心使用。\n\n通过选择存储库并点击下载按钮,下载该游戏的作弊码。\n在“补丁”选项卡中,您可以一次性下载所有补丁,选择要使用的补丁并保存选择。\n\n由于我们不开发作弊码/补丁,\n请将问题报告给作弊码/补丁的作者。\n\n创建了新的作弊码/补丁?欢迎提交到我们的仓库:\nhttps://github.com/shadps4-emu/ps4_cheats No Image Available - 没有可用的图像 + 没有可用的图片 Serial: - 序列号: + 序列号: Version: - 版本: + 版本: Size: - 大小: + 大小: Select Cheat File: - 选择作弊码文件: + 选择作弊码文件: Repository: - 存储库: + 存储库: @@ -961,7 +961,7 @@ Select Patch File: - 选择补丁文件: + 选择补丁文件: @@ -1016,7 +1016,7 @@ Failed to parse XML: - 解析 XML 失败: + 解析 XML 失败: @@ -1046,17 +1046,17 @@ File already exists. Do you want to replace it? - 文件已存在。您要替换它吗? + 文件已存在,您要替换它吗? Failed to save file: - 保存文件失败: + 保存文件失败: Failed to download file: - 下载文件失败: + 下载文件失败: @@ -1076,17 +1076,17 @@ CheatsDownloadedSuccessfully_MSG - 您已成功下载了该游戏版本的作弊码 从所选存储库中。如果有,您还可以尝试从其他存储库下载,或通过从列表中选择文件来使用它们。 + 您已从所选存储库中成功下载了该游戏版本的作弊码。您还可以尝试从其他存储库下载,或通过从列表中选择文件来使用它们。 Failed to save: - 保存失败: + 保存失败: Failed to download: - 下载失败: + 下载失败: @@ -1096,7 +1096,7 @@ DownloadComplete_MSG - 补丁下载成功!所有可用的补丁已下载完成,无需像作弊码那样单独下载每个游戏的补丁。如果补丁没有出现,可能是该补丁不存在于特定的序列号和游戏版本中。 + 补丁下载成功!所有可用的补丁已下载完成,无需像作弊码那样单独下载每个游戏的补丁。如果补丁没有出现,可能是该补丁不适用于当前游戏的序列号和版本。 @@ -1111,12 +1111,12 @@ The game is in version: %1 - 游戏版本: %1 + 游戏版本:%1 The downloaded patch only works on version: %1 - 下载的补丁仅适用于版本: %1 + 下载的补丁仅适用于版本:%1 @@ -1131,12 +1131,12 @@ Failed to open file: - 无法打开文件: + 无法打开文件: XML ERROR: - XML 错误: + XML 错误: @@ -1146,12 +1146,12 @@ Author: - 作者: + 作者: Directory does not exist: - 目录不存在: + 目录不存在: @@ -1161,12 +1161,12 @@ Name: - 名称: + 名称: Can't apply cheats before the game is started - 在游戏开始之前无法应用作弊。 + 在游戏启动之前无法应用作弊码。 @@ -1199,17 +1199,17 @@ consoleLanguageGroupBox - 控制台语言:\n设置 PS4 游戏中使用的语言。\n建议设置为支持的语言,因为可能因地区而异。 + 主机语言:\n设置 PS4 游戏中使用的语言。\n建议设置为支持的语言,这将因地区而异。 emulatorLanguageGroupBox - 模拟器语言:\n设置模拟器用户界面的语言。 + 模拟器语言:\n设置模拟器用户界面的语言。 fullscreenCheckBox - 启用全屏模式:\n自动将游戏窗口设置为全屏模式。\n您可以按 F11 键禁用此选项。 + 启用全屏:\n以全屏模式启动游戏。\n您可以按 F11 键切换回窗口模式。 @@ -1219,77 +1219,77 @@ showSplashCheckBox - 显示启动画面:\n在游戏启动时显示游戏的启动画面(特殊图像)。 + 显示启动画面:\n在游戏启动时显示游戏的启动画面(特殊图像)。 ps4proCheckBox - 这是 PS4 Pro:\n使模拟器作为 PS4 PRO 运行,可以在支持的游戏中激活特殊功能。 + 模拟 PS4 Pro:\n使模拟器作为 PS4 Pro 运行,可以在支持的游戏中激活特殊功能。 discordRPCCheckbox - 启用 Discord Rich Presence:\n在您的 Discord 个人资料上显示仿真器图标和相关信息。 + 启用 Discord Rich Presence:\n在您的 Discord 个人资料上显示模拟器图标和相关信息。 userName - 用户名:\n设置 PS4 帐户的用户名。某些游戏中可能会显示此名称。 + 用户名:\n设置 PS4 帐户的用户名,某些游戏中可能会显示此名称。 logTypeGroupBox - 日志类型:\n设置是否同步日志窗口的输出以提高性能。这可能会对模拟产生负面影响。 + 日志类型:\n设置日志窗口输出的同步方式以提高性能。可能会对模拟产生不良影响。 logFilter - 日志过滤器:\n过滤日志,仅打印特定信息。\n例如:"Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" 级别: Trace, Debug, Info, Warning, Error, Critical - 按此顺序,特定级别将静默列表中所有先前的级别,并记录所有后续级别。 + 日志过滤器:\n过滤日志,仅打印特定信息。\n例如:"Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" 级别: Trace, Debug, Info, Warning, Error, Critical - 按此顺序,特定级别将静默列表中所有先前的级别,并记录所有后续级别。 updaterGroupBox - 更新:\nRelease: 官方版本,可能非常旧,并且每月发布,但更可靠且经过测试。\nNightly: 开发版本,包含所有最新功能和修复,但可能包含错误且不够稳定。 + 更新:\nRelease:每月发布的官方版本可能非常过时,但更可靠且经过测试。\nNightly:包含所有最新功能和修复的开发版本,但可能包含错误且稳定性较低。 GUIgroupBox - 播放标题音乐:\n如果游戏支持,在图形界面选择游戏时启用播放特殊音乐。 + 播放标题音乐:\n如果游戏支持,在图形界面选择游戏时播放特殊音乐。 disableTrophycheckBox - Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + 禁止弹出奖杯:\n禁用游戏内奖杯通知。可以在奖杯查看器中继续跟踪奖杯进度(在主窗口中右键点击游戏)。 hideCursorGroupBox - 隐藏光标:\n选择光标何时消失:\n从不: 您将始终看到鼠标。\n空闲: 设置光标在空闲后消失的时间。\n始终: 您将永远看不到鼠标。 + 隐藏光标:\n选择光标何时消失:\n从不: 始终显示光标。\闲置: 光标在闲置若干秒后消失。\n始终: 始终显示光标。 idleTimeoutGroupBox - 设置鼠标在空闲后消失的时间。 + 光标隐藏闲置时长:\n光标自动隐藏之前的闲置时长。 backButtonBehaviorGroupBox - 返回按钮行为:\n设置控制器的返回按钮以模拟在 PS4 触控板上指定位置的点击。 + 返回按钮行为:\n设置手柄的返回按钮模拟在 PS4 触控板上指定位置的点击。 enableCompatibilityCheckBox - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + 显示兼容性数据:\n在列表视图中显示游戏兼容性信息。启用“启动时更新兼容性数据库”以获取最新信息。 checkCompatibilityOnStartupCheckBox - Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + 启动时更新兼容性数据库:\n当 shadPS4 启动时自动更新兼容性数据库。 updateCompatibilityButton - Update Compatibility Database:\nImmediately update the compatibility database. + 更新兼容性数据库:\n立即更新兼容性数据库。 @@ -1299,7 +1299,7 @@ Idle - 空闲 + 闲置 @@ -1329,62 +1329,62 @@ graphicsAdapterGroupBox - 图形设备:\n在具有多个 GPU 的系统中,从下拉列表中选择要使用的 GPU,\n或者选择“自动检测”以自动确定。 + 图形设备:\n在具有多个 GPU 的系统中,从下拉列表中选择要使用的 GPU,\n或者选择“自动选择”由模拟器决定。 resolutionLayout - 宽度/高度:\n设置启动时模拟器的窗口大小,该大小可以在游戏中更改。\n这与游戏中的分辨率不同。 + 宽度/高度:\n设置启动游戏时的窗口大小,游戏过程中可以调整。\n这与游戏内的分辨率不同。 heightDivider - Vblank 除数:\n模拟器更新的帧速率乘以此数字。改变此项可能会导致游戏速度加快,或破坏游戏中不期望此变化的关键功能! + Vblank Divider:\n模拟器刷新的帧率会乘以此数字。改变此项可能会导致游戏速度加快,或破坏游戏中不期望此变化的关键功能! dumpShadersCheckBox - 启用着色器转储:\n为了技术调试,在渲染期间将游戏着色器保存到文件夹中。 + 启用着色器转储:\n用于技术调试,在渲染期间将游戏着色器保存到文件夹中。 nullGpuCheckBox - 启用空 GPU:\n为了技术调试,将游戏渲染禁用,仿佛没有图形卡。 + 启用 NULL GPU:\n用于技术调试,禁用游戏渲染,就像没有显卡一样。 gameFoldersBox - 游戏文件夹:\n检查已安装游戏的文件夹列表。 + 游戏文件夹:\n检查已安装游戏的文件夹列表。 addFolderButton - 添加:\n将文件夹添加到列表。 + 添加:\n将文件夹添加到列表。 removeFolderButton - 移除:\n从列表中移除文件夹。 + 移除:\n从列表中移除文件夹。 debugDump - 启用调试转储:\n将当前正在运行的 PS4 程序的导入和导出符号及文件头信息保存到目录中。 + 启用调试转储:\n将当前正在运行的 PS4 程序的导入和导出符号及文件头信息保存到目录中。 vkValidationCheckBox - 启用 Vulkan 验证层:\n启用验证 Vulkan 渲染器状态并记录内部状态信息的系统。这可能会降低性能,并可能更改模拟行为。 + 启用 Vulkan 验证层:\n启用一个系统来验证 Vulkan 渲染器的状态并记录其内部状态的信息。\n这将降低性能并可能改变模拟的行为。 vkSyncValidationCheckBox - 启用 Vulkan 同步验证:\n启用验证 Vulkan 渲染任务时间的系统。这可能会降低性能,并可能更改模拟行为。 + 启用 Vulkan 同步验证:\n启用一个系统来验证 Vulkan 渲染任务的时间。\n这将降低性能并可能改变模拟的行为。 rdocCheckBox - 启用 RenderDoc 调试:\n如果启用,模拟器将提供与 Renderdoc 的兼容性,允许在渲染过程中捕获和分析当前渲染的帧。 + 启用 RenderDoc 调试:\n启用后模拟器将提供与 Renderdoc 的兼容性,允许在渲染过程中捕获和分析当前渲染的帧。 @@ -1407,7 +1407,7 @@ Compatibility - Compatibility + 兼容性 @@ -1442,52 +1442,52 @@ Never Played - Never Played + 未玩过 h - h + 小时 m - m + 分钟 s - s + Compatibility is untested - Compatibility is untested + 兼容性未经测试 Game does not initialize properly / crashes the emulator - Game does not initialize properly / crashes the emulator + 游戏无法正确初始化/模拟器崩溃 Game boots, but only displays a blank screen - Game boots, but only displays a blank screen + 游戏启动,但只显示白屏 Game displays an image but does not go past the menu - Game displays an image but does not go past the menu + 游戏显示图像但无法通过菜单页面 Game has game-breaking glitches or unplayable performance - Game has game-breaking glitches or unplayable performance + 游戏有严重的 Bug 或太卡无法游玩 Game can be completed with playable performance and no major glitches - Game can be completed with playable performance and no major glitches + 游戏能在可玩的性能下完成且没有重大 Bug @@ -1525,7 +1525,7 @@ No download URL found for the specified asset. - 未找到指定资产的下载 URL。 + 未找到指定资源的下载地址。 @@ -1560,7 +1560,7 @@ Show Changelog - 显示变更日志 + 显示更新日志 @@ -1580,17 +1580,17 @@ Hide Changelog - 隐藏变更日志 + 隐藏更新日志 Changes - 变更 + 更新日志 Network error occurred while trying to access the URL - 尝试访问 URL 时发生网络错误 + 尝试访问网址时发生网络错误 @@ -1646,4 +1646,4 @@ TB - \ No newline at end of file + diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 0ce9eea7c..f0cf15af0 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -24,9 +24,11 @@ static constexpr spv::ExecutionMode GetInputPrimitiveType(AmdGpu::PrimitiveType case AmdGpu::PrimitiveType::PointList: return spv::ExecutionMode::InputPoints; case AmdGpu::PrimitiveType::LineList: + case AmdGpu::PrimitiveType::LineStrip: return spv::ExecutionMode::InputLines; case AmdGpu::PrimitiveType::TriangleList: case AmdGpu::PrimitiveType::TriangleStrip: + case AmdGpu::PrimitiveType::RectList: return spv::ExecutionMode::Triangles; case AmdGpu::PrimitiveType::AdjTriangleList: return spv::ExecutionMode::InputTrianglesAdjacency; diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index d8bafccd9..575bf91f7 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -43,9 +43,11 @@ static constexpr u32 NumVertices(AmdGpu::PrimitiveType type) { case AmdGpu::PrimitiveType::PointList: return 1u; case AmdGpu::PrimitiveType::LineList: + case AmdGpu::PrimitiveType::LineStrip: return 2u; case AmdGpu::PrimitiveType::TriangleList: case AmdGpu::PrimitiveType::TriangleStrip: + case AmdGpu::PrimitiveType::RectList: return 3u; case AmdGpu::PrimitiveType::AdjTriangleList: return 6u; diff --git a/src/shader_recompiler/frontend/format.cpp b/src/shader_recompiler/frontend/format.cpp index 4f0922e2e..9677be3e5 100644 --- a/src/shader_recompiler/frontend/format.cpp +++ b/src/shader_recompiler/frontend/format.cpp @@ -3565,8 +3565,8 @@ constexpr std::array InstructionFormatMIMG = {{ {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Float32, ScalarType::Float32}, // 64 = IMAGE_GATHER4 - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, // 65 = IMAGE_GATHER4_CL {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, ScalarType::Undefined}, @@ -3603,10 +3603,10 @@ constexpr std::array InstructionFormatMIMG = {{ ScalarType::Undefined}, // 79 = IMAGE_GATHER4_C_LZ {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, - ScalarType::Uint32}, + ScalarType::Float32}, // 80 = IMAGE_GATHER4_O - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Float32}, // 81 = IMAGE_GATHER4_CL_O {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, ScalarType::Undefined}, diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 48cb79610..7c3db9551 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -144,8 +144,10 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { return IMAGE_SAMPLE(inst); // Image gather operations + case Opcode::IMAGE_GATHER4: case Opcode::IMAGE_GATHER4_LZ: case Opcode::IMAGE_GATHER4_C: + case Opcode::IMAGE_GATHER4_O: case Opcode::IMAGE_GATHER4_C_O: case Opcode::IMAGE_GATHER4_C_LZ: case Opcode::IMAGE_GATHER4_LZ_O: diff --git a/src/video_core/amdgpu/resource.h b/src/video_core/amdgpu/resource.h index 58b286e9c..6bbe1fb7e 100644 --- a/src/video_core/amdgpu/resource.h +++ b/src/video_core/amdgpu/resource.h @@ -447,7 +447,7 @@ struct Sampler { } float MaxAniso() const { - switch (max_aniso) { + switch (max_aniso.Value()) { case AnisoRatio::One: return 1.0f; case AnisoRatio::Two: diff --git a/src/video_core/texture_cache/sampler.cpp b/src/video_core/texture_cache/sampler.cpp index 5ce10ff0e..8dbdd2912 100644 --- a/src/video_core/texture_cache/sampler.cpp +++ b/src/video_core/texture_cache/sampler.cpp @@ -18,7 +18,8 @@ Sampler::Sampler(const Vulkan::Instance& instance, const AmdGpu::Sampler& sample (AmdGpu::IsAnisoFilter(sampler.xy_mag_filter) || AmdGpu::IsAnisoFilter(sampler.xy_min_filter)); const float maxAnisotropy = - std::clamp(sampler.MaxAniso(), 1.0f, instance.MaxSamplerAnisotropy()); + anisotropyEnable ? std::clamp(sampler.MaxAniso(), 1.0f, instance.MaxSamplerAnisotropy()) + : 1.0f; const vk::SamplerCreateInfo sampler_ci = { .magFilter = LiverpoolToVK::Filter(sampler.xy_mag_filter), .minFilter = LiverpoolToVK::Filter(sampler.xy_min_filter),