diff --git a/.github/linux-appimage-qt.sh b/.github/linux-appimage-qt.sh index 06d5cbc11..776eed61e 100755 --- a/.github/linux-appimage-qt.sh +++ b/.github/linux-appimage-qt.sh @@ -27,7 +27,7 @@ chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh cp -a "$GITHUB_WORKSPACE/build/translations" AppDir/usr/bin -./linuxdeploy-x86_64.AppImage --appdir AppDir -d "$GITHUB_WORKSPACE"/.github/shadps4.desktop -e "$GITHUB_WORKSPACE"/build/shadps4 -i "$GITHUB_WORKSPACE"/.github/shadps4.png --plugin qt +./linuxdeploy-x86_64.AppImage --appdir AppDir -d "$GITHUB_WORKSPACE"/dist/net.shadps4.shadPS4.desktop -e "$GITHUB_WORKSPACE"/build/shadps4 -i "$GITHUB_WORKSPACE"/src/images/net.shadps4.shadPS4.svg --plugin qt rm AppDir/usr/plugins/multimedia/libgstreamermediaplugin.so ./linuxdeploy-x86_64.AppImage --appdir AppDir --output appimage -mv Shadps4-x86_64.AppImage Shadps4-qt.AppImage +mv shadPS4-x86_64.AppImage Shadps4-qt.AppImage diff --git a/.github/linux-appimage-sdl.sh b/.github/linux-appimage-sdl.sh index cc0e9f5fa..7961f5312 100755 --- a/.github/linux-appimage-sdl.sh +++ b/.github/linux-appimage-sdl.sh @@ -17,5 +17,5 @@ chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh # Build AppImage ./linuxdeploy-x86_64.AppImage --appdir AppDir ./linuxdeploy-plugin-checkrt-x86_64.sh --appdir AppDir -./linuxdeploy-x86_64.AppImage --appdir AppDir -d "$GITHUB_WORKSPACE"/.github/shadps4.desktop -e "$GITHUB_WORKSPACE"/build/shadps4 -i "$GITHUB_WORKSPACE"/.github/shadps4.png --output appimage -mv Shadps4-x86_64.AppImage Shadps4-sdl.AppImage +./linuxdeploy-x86_64.AppImage --appdir AppDir -d "$GITHUB_WORKSPACE"/dist/net.shadps4.shadPS4.desktop -e "$GITHUB_WORKSPACE"/build/shadps4 -i "$GITHUB_WORKSPACE"/src/images/net.shadps4.shadPS4.svg --output appimage +mv shadPS4-x86_64.AppImage Shadps4-sdl.AppImage diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 44aa3dd37..a3b3ee8ec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -143,7 +143,7 @@ jobs: arch: amd64 - name: Configure CMake - run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel @@ -162,7 +162,7 @@ jobs: path: upload/ macos-sdl: - runs-on: macos-latest + runs-on: macos-15 needs: get-info steps: - uses: actions/checkout@v4 @@ -218,7 +218,7 @@ jobs: path: shadps4-macos-sdl.tar.gz macos-qt: - runs-on: macos-latest + runs-on: macos-15 needs: get-info steps: - uses: actions/checkout@v4 @@ -265,7 +265,7 @@ jobs: variant: sccache - name: Configure CMake - run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu) @@ -368,7 +368,7 @@ jobs: 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_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON -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}} --parallel3 diff --git a/CMakeLists.txt b/CMakeLists.txt index d9479e851..d320d49e3 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ endif() option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF) option(ENABLE_DISCORD_RPC "Enable the Discord RPC integration" ON) +option(ENABLE_UPDATER "Enables the options to updater" ON) # First, determine whether to use CMAKE_OSX_ARCHITECTURES or CMAKE_SYSTEM_PROCESSOR. if (APPLE AND CMAKE_OSX_ARCHITECTURES) @@ -365,6 +366,10 @@ set(VDEC_LIB src/core/libraries/videodec/videodec2_impl.cpp src/core/libraries/videodec/videodec2.cpp src/core/libraries/videodec/videodec2.h src/core/libraries/videodec/videodec2_avc.h + src/core/libraries/videodec/videodec.cpp + src/core/libraries/videodec/videodec.h + src/core/libraries/videodec/videodec_impl.cpp + src/core/libraries/videodec/videodec_impl.h ) set(NP_LIBS src/core/libraries/np_manager/np_manager.cpp @@ -732,6 +737,12 @@ set(EMULATOR src/emulator.cpp if(ENABLE_QT_GUI) qt_add_resources(RESOURCE_FILES src/shadps4.qrc) +if (ENABLE_UPDATER) + set(UPDATER src/qt_gui/check_update.cpp + src/qt_gui/check_update.h + ) +endif() + set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/about_dialog.h src/qt_gui/about_dialog.ui @@ -739,8 +750,6 @@ set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/background_music_player.h src/qt_gui/cheats_patches.cpp src/qt_gui/cheats_patches.h - src/qt_gui/check_update.cpp - src/qt_gui/check_update.h src/qt_gui/main_window_ui.h src/qt_gui/main_window.cpp src/qt_gui/main_window.h @@ -771,6 +780,7 @@ set(QT_GUI src/qt_gui/about_dialog.cpp ${EMULATOR} ${RESOURCE_FILES} ${TRANSLATIONS} + ${UPDATER} ) endif() @@ -843,8 +853,11 @@ else() endif() if (ENABLE_QT_GUI) - target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia) - add_definitions(-DENABLE_QT_GUI) + target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia) + add_definitions(-DENABLE_QT_GUI) + if (ENABLE_UPDATER) + add_definitions(-DENABLE_UPDATER) + endif() endif() if (WIN32) @@ -930,6 +943,9 @@ endif() install(TARGETS shadps4 BUNDLE DESTINATION .) if (ENABLE_QT_GUI AND CMAKE_SYSTEM_NAME STREQUAL "Linux") - install(FILES ".github/shadps4.desktop" DESTINATION "share/applications") - install(FILES ".github/shadps4.png" DESTINATION "share/icons/hicolor/512x512/apps") + install(FILES "dist/net.shadps4.shadPS4.desktop" DESTINATION "share/applications") + install(FILES "dist/net.shadps4.shadPS4.releases.xml" DESTINATION "share/metainfo/releases") + install(FILES "dist/net.shadps4.shadPS4.metainfo.xml" DESTINATION "share/metainfo") + install(FILES ".github/shadps4.png" DESTINATION "share/icons/hicolor/512x512/apps" RENAME "net.shadps4.shadPS4.png") + install(FILES "src/images/net.shadps4.shadPS4.svg" DESTINATION "share/icons/hicolor/scalable/apps") endif() diff --git a/REUSE.toml b/REUSE.toml index e1a266030..405156231 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -5,9 +5,12 @@ path = [ "REUSE.toml", "CMakeSettings.json", ".github/FUNDING.yml", - ".github/shadps4.desktop", ".github/shadps4.png", ".gitmodules", + "dist/net.shadps4.shadPS4.desktop", + "dist/net.shadps4.shadPS4_metadata.pot", + "dist/net.shadps4.shadPS4.metainfo.xml", + "dist/net.shadps4.shadPS4.releases.xml", "documents/changelog.txt", "documents/Quickstart/2.png", "documents/Screenshots/*", @@ -35,6 +38,7 @@ path = [ "src/images/stop_icon.png", "src/images/shadPS4.icns", "src/images/shadps4.ico", + "src/images/net.shadps4.shadPS4.svg", "src/images/themes_icon.png", "src/images/update_icon.png", "src/shadps4.qrc", diff --git a/.github/shadps4.desktop b/dist/net.shadps4.shadPS4.desktop similarity index 59% rename from .github/shadps4.desktop rename to dist/net.shadps4.shadPS4.desktop index 095acb787..fbefa0566 100644 --- a/.github/shadps4.desktop +++ b/dist/net.shadps4.shadPS4.desktop @@ -1,9 +1,9 @@ [Desktop Entry] -Name=Shadps4 +Name=shadPS4 Exec=shadps4 Terminal=false Type=Application -Icon=shadps4 -Comment=shadps4 emulator +Icon=net.shadps4.shadPS4 +Comment=PlayStation 4 emulator Categories=Game; StartupWMClass=shadps4; diff --git a/dist/net.shadps4.shadPS4.metainfo.xml b/dist/net.shadps4.shadPS4.metainfo.xml new file mode 100644 index 000000000..bef0bec4c --- /dev/null +++ b/dist/net.shadps4.shadPS4.metainfo.xml @@ -0,0 +1,63 @@ + + + net.shadps4.shadPS4 + shadPS4 + + shadPS4 Contributors + https://github.com/shadps4-emu/shadps4/graphs/contributors + + PS4 Emulator + CC0-1.0 + GPL-2.0 + net.shadps4.shadPS4.desktop + https://shadps4.net/ + + shadPS4 is an early PlayStation 4 emulator for Windows, Linux and macOS written in C++. + The emulator is still early in development, so don't expect a flawless experience. Nonetheless, the emulator can already run a number of commercial games. + + + + https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/1.png + Bloodborne + + + https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/2.png + Hatsune Miku: Project DIVA Future Tone + + + https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/3.png + Yakuza Kiwami + + + https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/4.png + Persona 4 Golden + + + + Game + + + + + + + + + keyboard + + + gamepad + + + offline-only + + + shadps4 + + + emulator + emulation + playstation + ps4 + + diff --git a/dist/net.shadps4.shadPS4.releases.xml b/dist/net.shadps4.shadPS4.releases.xml new file mode 100644 index 000000000..8da203fe4 --- /dev/null +++ b/dist/net.shadps4.shadPS4.releases.xml @@ -0,0 +1,23 @@ + + + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.4.0 + + + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.3.0 + + + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.2.0 + + + https://github.com/shadps4-emu/shadPS4/releases/tag/0.1.0 + + + https://github.com/shadps4-emu/shadPS4/releases/tag/v0.0.3 + + + https://github.com/shadps4-emu/shadPS4/releases/tag/v0.0.2 + + + https://github.com/shadps4-emu/shadPS4/releases/tag/v0.0.1 + + diff --git a/dist/net.shadps4.shadPS4_metadata.pot b/dist/net.shadps4.shadPS4_metadata.pot new file mode 100644 index 000000000..d77947b7a --- /dev/null +++ b/dist/net.shadps4.shadPS4_metadata.pot @@ -0,0 +1,65 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2024-11-08 09:07+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. (itstool) path: component/name +#: ./net.shadps4.shadPS4.metainfo.xml:4 +msgid "shadPS4" +msgstr "" + +#. (itstool) path: developer/name +#: ./net.shadps4.shadPS4.metainfo.xml:6 +msgid "shadPS4 Contributors" +msgstr "" + +#. (itstool) path: component/summary +#: ./net.shadps4.shadPS4.metainfo.xml:9 +msgid "PS4 Emulator" +msgstr "" + +#. (itstool) path: description/p +#: ./net.shadps4.shadPS4.metainfo.xml:16 +msgid "shadPS4 is an early PlayStation 4 emulator for Windows, Linux and macOS written in C++." +msgstr "" + +#. (itstool) path: description/p +#: ./net.shadps4.shadPS4.metainfo.xml:17 +msgid "The emulator is still early in development, so don't expect a flawless experience. Nonetheless, the emulator can already run a number of commercial games." +msgstr "" + +#. (itstool) path: screenshot/caption +#: ./net.shadps4.shadPS4.metainfo.xml:22 +msgid "Bloodborne" +msgstr "" + +#. (itstool) path: screenshot/caption +#: ./net.shadps4.shadPS4.metainfo.xml:26 +msgid "Hatsune Miku: Project DIVA Future Tone" +msgstr "" + +#. (itstool) path: screenshot/caption +#: ./net.shadps4.shadPS4.metainfo.xml:30 +msgid "Yakuza Kiwami" +msgstr "" + +#. (itstool) path: screenshot/caption +#: ./net.shadps4.shadPS4.metainfo.xml:34 +msgid "Persona 4 Golden" +msgstr "" + +#. (itstool) path: keywords/keyword +#: ./net.shadps4.shadPS4.metainfo.xml:59 +msgid "emulator" +msgstr "" + +#. (itstool) path: keywords/keyword +#: ./net.shadps4.shadPS4.metainfo.xml:60 +msgid "emulation" +msgstr "" diff --git a/src/common/config.cpp b/src/common/config.cpp index 1dde7223c..e97a46005 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -658,7 +658,7 @@ void save(const std::filesystem::path& path) { // TODO Migration code, after a major release this should be removed. data.at("GUI").as_table().erase("installDir"); - std::ofstream file(path, std::ios::out); + std::ofstream file(path, std::ios::binary); file << data; file.close(); } diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index a76c472a7..5ca594bf7 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -120,6 +120,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, SharePlay) \ SUB(Lib, Fiber) \ SUB(Lib, Vdec2) \ + SUB(Lib, Videodec) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Vulkan) \ diff --git a/src/common/logging/types.h b/src/common/logging/types.h index fd7dcfd1a..2821729d4 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -87,6 +87,7 @@ enum class Class : u8 { Lib_SharePlay, ///< The LibSceSharePlay implemenation Lib_Fiber, ///< The LibSceFiber implementation. Lib_Vdec2, ///< The LibSceVideodec2 implementation. + Lib_Videodec, ///< The LibSceVideodec implementation. Frontend, ///< Emulator UI Render, ///< Video Core Render_Vulkan, ///< Vulkan backend diff --git a/src/core/libraries/ajm/ajm_at9.cpp b/src/core/libraries/ajm/ajm_at9.cpp index 7fff0ff0c..936929ae8 100644 --- a/src/core/libraries/ajm/ajm_at9.cpp +++ b/src/core/libraries/ajm/ajm_at9.cpp @@ -40,23 +40,11 @@ void AjmAt9Decoder::Initialize(const void* buffer, u32 buffer_size) { const auto params = reinterpret_cast(buffer); std::memcpy(m_config_data, params->config_data, ORBIS_AT9_CONFIG_DATA_SIZE); AjmAt9Decoder::Reset(); - m_pcm_buffer.resize(m_codec_info.frameSamples * m_codec_info.channels * GetPointCodeSize(), 0); + m_pcm_buffer.resize(m_codec_info.frameSamples * m_codec_info.channels * GetPCMSize(m_format), + 0); } -u8 AjmAt9Decoder::GetPointCodeSize() { - switch (m_format) { - case AjmFormatEncoding::S16: - return sizeof(s16); - case AjmFormatEncoding::S32: - return sizeof(s32); - case AjmFormatEncoding::Float: - return sizeof(float); - default: - UNREACHABLE(); - } -} - -void AjmAt9Decoder::GetInfo(void* out_info) { +void AjmAt9Decoder::GetInfo(void* out_info) const { auto* info = reinterpret_cast(out_info); info->super_frame_size = m_codec_info.superframeSize; info->frames_in_super_frame = m_codec_info.framesInSuperframe; @@ -65,8 +53,7 @@ void AjmAt9Decoder::GetInfo(void* out_info) { } std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf, SparseOutputBuffer& output, - AjmSidebandGaplessDecode& gapless, - std::optional max_samples_per_channel) { + AjmInstanceGapless& gapless) { int ret = 0; int bytes_used = 0; switch (m_format) { @@ -91,32 +78,37 @@ std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf, SparseOut m_superframe_bytes_remain -= bytes_used; - u32 skipped_samples = 0; - if (gapless.skipped_samples < gapless.skip_samples) { - skipped_samples = std::min(u32(m_codec_info.frameSamples), - u32(gapless.skip_samples - gapless.skipped_samples)); - gapless.skipped_samples += skipped_samples; + u32 skip_samples = 0; + if (gapless.current.skip_samples > 0) { + skip_samples = std::min(u16(m_codec_info.frameSamples), gapless.current.skip_samples); + gapless.current.skip_samples -= skip_samples; } - const auto max_samples = max_samples_per_channel.has_value() - ? max_samples_per_channel.value() * m_codec_info.channels - : std::numeric_limits::max(); + const auto max_pcm = gapless.init.total_samples != 0 + ? gapless.current.total_samples * m_codec_info.channels + : std::numeric_limits::max(); - size_t samples_written = 0; + size_t pcm_written = 0; switch (m_format) { case AjmFormatEncoding::S16: - samples_written = WriteOutputSamples(output, skipped_samples, max_samples); + pcm_written = WriteOutputSamples(output, skip_samples, max_pcm); break; case AjmFormatEncoding::S32: - samples_written = WriteOutputSamples(output, skipped_samples, max_samples); + pcm_written = WriteOutputSamples(output, skip_samples, max_pcm); break; case AjmFormatEncoding::Float: - samples_written = WriteOutputSamples(output, skipped_samples, max_samples); + pcm_written = WriteOutputSamples(output, skip_samples, max_pcm); break; default: UNREACHABLE(); } + const auto samples_written = pcm_written / m_codec_info.channels; + gapless.current.skipped_samples += m_codec_info.frameSamples - samples_written; + if (gapless.init.total_samples != 0) { + gapless.current.total_samples -= samples_written; + } + m_num_frames += 1; if ((m_num_frames % m_codec_info.framesInSuperframe) == 0) { if (m_superframe_bytes_remain) { @@ -126,18 +118,28 @@ std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf, SparseOut m_num_frames = 0; } - return {1, samples_written / m_codec_info.channels}; + return {1, samples_written}; } -AjmSidebandFormat AjmAt9Decoder::GetFormat() { +AjmSidebandFormat AjmAt9Decoder::GetFormat() const { return AjmSidebandFormat{ .num_channels = u32(m_codec_info.channels), .channel_mask = GetChannelMask(u32(m_codec_info.channels)), .sampl_freq = u32(m_codec_info.samplingRate), .sample_encoding = m_format, - .bitrate = u32(m_codec_info.samplingRate * GetPointCodeSize() * 8), + .bitrate = u32((m_codec_info.samplingRate * m_codec_info.superframeSize * 8) / + (m_codec_info.framesInSuperframe * m_codec_info.frameSamples)), .reserved = 0, }; } +u32 AjmAt9Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const { + const auto max_samples = + gapless.init.total_samples != 0 + ? std::min(gapless.current.total_samples, u32(m_codec_info.frameSamples)) + : m_codec_info.frameSamples; + const auto skip_samples = std::min(u32(gapless.current.skip_samples), max_samples); + return (max_samples - skip_samples) * m_codec_info.channels * GetPCMSize(m_format); +} + } // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_at9.h b/src/core/libraries/ajm/ajm_at9.h index e5f65db93..689681dec 100644 --- a/src/core/libraries/ajm/ajm_at9.h +++ b/src/core/libraries/ajm/ajm_at9.h @@ -33,15 +33,13 @@ struct AjmAt9Decoder final : AjmCodec { void Reset() override; void Initialize(const void* buffer, u32 buffer_size) override; - void GetInfo(void* out_info) override; - AjmSidebandFormat GetFormat() override; + void GetInfo(void* out_info) const override; + AjmSidebandFormat GetFormat() const override; + u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override; std::tuple ProcessData(std::span& input, SparseOutputBuffer& output, - AjmSidebandGaplessDecode& gapless, - std::optional max_samples) override; + AjmInstanceGapless& gapless) override; private: - u8 GetPointCodeSize(); - template size_t WriteOutputSamples(SparseOutputBuffer& output, u32 skipped_samples, u32 max_samples) { std::span pcm_data{reinterpret_cast(m_pcm_buffer.data()), diff --git a/src/core/libraries/ajm/ajm_batch.cpp b/src/core/libraries/ajm/ajm_batch.cpp index 5c76beae8..b1cec88b3 100644 --- a/src/core/libraries/ajm/ajm_batch.cpp +++ b/src/core/libraries/ajm/ajm_batch.cpp @@ -135,7 +135,10 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) { case Identifier::AjmIdentInputControlBuf: { ASSERT_MSG(!input_control_buffer.has_value(), "Only one instance of input control buffer is allowed per job"); - input_control_buffer = batch_buffer.Consume(); + const auto& buffer = batch_buffer.Consume(); + if (buffer.p_address != nullptr && buffer.size != 0) { + input_control_buffer = buffer; + } break; } case Identifier::AjmIdentControlFlags: @@ -155,19 +158,27 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) { case Identifier::AjmIdentInlineBuf: { ASSERT_MSG(!output_control_buffer.has_value(), "Only one instance of inline buffer is allowed per job"); - inline_buffer = batch_buffer.Consume(); + const auto& buffer = batch_buffer.Consume(); + if (buffer.p_address != nullptr && buffer.size != 0) { + inline_buffer = buffer; + } break; } case Identifier::AjmIdentOutputRunBuf: { auto& buffer = batch_buffer.Consume(); u8* p_begin = reinterpret_cast(buffer.p_address); - job.output.buffers.emplace_back(std::span(p_begin, p_begin + buffer.size)); + if (p_begin != nullptr && buffer.size != 0) { + job.output.buffers.emplace_back(std::span(p_begin, p_begin + buffer.size)); + } break; } case Identifier::AjmIdentOutputControlBuf: { ASSERT_MSG(!output_control_buffer.has_value(), "Only one instance of output control buffer is allowed per job"); - output_control_buffer = batch_buffer.Consume(); + const auto& buffer = batch_buffer.Consume(); + if (buffer.p_address != nullptr && buffer.size != 0) { + output_control_buffer = buffer; + } break; } default: diff --git a/src/core/libraries/ajm/ajm_instance.cpp b/src/core/libraries/ajm/ajm_instance.cpp index 85a6f54a1..4e04eea74 100644 --- a/src/core/libraries/ajm/ajm_instance.cpp +++ b/src/core/libraries/ajm/ajm_instance.cpp @@ -22,6 +22,19 @@ constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200; constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000; constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000; +u8 GetPCMSize(AjmFormatEncoding format) { + switch (format) { + case AjmFormatEncoding::S16: + return sizeof(s16); + case AjmFormatEncoding::S32: + return sizeof(s32); + case AjmFormatEncoding::Float: + return sizeof(float); + default: + UNREACHABLE(); + } +} + AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) { switch (codec_type) { case AjmCodecType::At9Dec: { @@ -30,7 +43,8 @@ AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_fl break; } case AjmCodecType::Mp3Dec: { - m_codec = std::make_unique(AjmFormatEncoding(flags.format)); + m_codec = std::make_unique(AjmFormatEncoding(flags.format), + AjmMp3CodecFlags(flags.codec)); break; } default: @@ -45,7 +59,6 @@ void AjmInstance::ExecuteJob(AjmJob& job) { m_format = {}; m_gapless = {}; m_resample_parameters = {}; - m_gapless_samples = 0; m_total_samples = 0; m_codec->Reset(); } @@ -64,27 +77,47 @@ void AjmInstance::ExecuteJob(AjmJob& job) { } if (job.input.gapless_decode.has_value()) { auto& params = job.input.gapless_decode.value(); - m_gapless.total_samples = params.total_samples; - m_gapless.skip_samples = params.skip_samples; + if (params.total_samples != 0) { + const auto max = std::max(params.total_samples, m_gapless.init.total_samples); + m_gapless.current.total_samples += max - m_gapless.init.total_samples; + m_gapless.init.total_samples = max; + } + if (params.skip_samples != 0) { + const auto max = std::max(params.skip_samples, m_gapless.init.skip_samples); + m_gapless.current.skip_samples += max - m_gapless.init.skip_samples; + m_gapless.init.skip_samples = max; + } } if (!job.input.buffer.empty() && !job.output.buffers.empty()) { - u32 frames_decoded = 0; std::span in_buf(job.input.buffer); SparseOutputBuffer out_buf(job.output.buffers); + u32 frames_decoded = 0; auto in_size = in_buf.size(); auto out_size = out_buf.Size(); - while (!in_buf.empty() && !out_buf.IsEmpty() && !IsGaplessEnd()) { - const auto samples_remain = - m_gapless.total_samples != 0 - ? std::optional{m_gapless.total_samples - m_gapless_samples} - : std::optional{}; - const auto [nframes, nsamples] = - m_codec->ProcessData(in_buf, out_buf, m_gapless, samples_remain); + while (!in_buf.empty() && !out_buf.IsEmpty() && !m_gapless.IsEnd()) { + if (!HasEnoughSpace(out_buf)) { + if (job.output.p_mframe == nullptr || frames_decoded == 0) { + job.output.p_result->result = ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM; + break; + } + } + + const auto [nframes, nsamples] = m_codec->ProcessData(in_buf, out_buf, m_gapless); frames_decoded += nframes; m_total_samples += nsamples; - m_gapless_samples += nsamples; + + if (False(job.flags.run_flags & AjmJobRunFlags::MultipleFrames)) { + break; + } + } + + if (m_gapless.IsEnd()) { + in_buf = in_buf.subspan(in_buf.size()); + m_gapless.current.total_samples = m_gapless.init.total_samples; + m_gapless.current.skip_samples = m_gapless.init.skip_samples; + m_codec->Reset(); } if (job.output.p_mframe) { job.output.p_mframe->num_frames = frames_decoded; @@ -96,25 +129,19 @@ void AjmInstance::ExecuteJob(AjmJob& job) { } } - if (m_flags.gapless_loop && m_gapless.total_samples != 0 && - m_gapless_samples >= m_gapless.total_samples) { - m_gapless_samples = 0; - m_gapless.skipped_samples = 0; - m_codec->Reset(); - } if (job.output.p_format != nullptr) { *job.output.p_format = m_codec->GetFormat(); } if (job.output.p_gapless_decode != nullptr) { - *job.output.p_gapless_decode = m_gapless; + *job.output.p_gapless_decode = m_gapless.current; } if (job.output.p_codec_info != nullptr) { m_codec->GetInfo(job.output.p_codec_info); } } -bool AjmInstance::IsGaplessEnd() { - return m_gapless.total_samples != 0 && m_gapless_samples >= m_gapless.total_samples; +bool AjmInstance::HasEnoughSpace(const SparseOutputBuffer& output) const { + return output.Size() >= m_codec->GetNextFrameSize(m_gapless); } } // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_instance.h b/src/core/libraries/ajm/ajm_instance.h index d1d398ae8..9d0f6b9f3 100644 --- a/src/core/libraries/ajm/ajm_instance.h +++ b/src/core/libraries/ajm/ajm_instance.h @@ -14,6 +14,8 @@ namespace Libraries::Ajm { +u8 GetPCMSize(AjmFormatEncoding format); + class SparseOutputBuffer { public: SparseOutputBuffer(std::span> chunks) @@ -33,14 +35,17 @@ public: ++m_current; } } + if (!pcm.empty()) { + LOG_ERROR(Lib_Ajm, "Could not write {} samples", pcm.size()); + } return samples_written; } - bool IsEmpty() { + bool IsEmpty() const { return m_current == m_chunks.end(); } - size_t Size() { + size_t Size() const { size_t result = 0; for (auto it = m_current; it != m_chunks.end(); ++it) { result += it->size(); @@ -53,17 +58,26 @@ private: std::span>::iterator m_current; }; +struct AjmInstanceGapless { + AjmSidebandGaplessDecode init{}; + AjmSidebandGaplessDecode current{}; + + bool IsEnd() const { + return init.total_samples != 0 && current.total_samples == 0; + } +}; + class AjmCodec { public: virtual ~AjmCodec() = default; virtual void Initialize(const void* buffer, u32 buffer_size) = 0; virtual void Reset() = 0; - virtual void GetInfo(void* out_info) = 0; - virtual AjmSidebandFormat GetFormat() = 0; + virtual void GetInfo(void* out_info) const = 0; + virtual AjmSidebandFormat GetFormat() const = 0; + virtual u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const = 0; virtual std::tuple ProcessData(std::span& input, SparseOutputBuffer& output, - AjmSidebandGaplessDecode& gapless, - std::optional max_samples_per_channel) = 0; + AjmInstanceGapless& gapless) = 0; }; class AjmInstance { @@ -73,16 +87,14 @@ public: void ExecuteJob(AjmJob& job); private: - bool IsGaplessEnd(); + bool HasEnoughSpace(const SparseOutputBuffer& output) const; + std::optional GetNumRemainingSamples() const; AjmInstanceFlags m_flags{}; AjmSidebandFormat m_format{}; - AjmSidebandGaplessDecode m_gapless{}; + AjmInstanceGapless m_gapless{}; AjmSidebandResampleParameters m_resample_parameters{}; - - u32 m_gapless_samples{}; u32 m_total_samples{}; - std::unique_ptr m_codec; }; diff --git a/src/core/libraries/ajm/ajm_mp3.cpp b/src/core/libraries/ajm/ajm_mp3.cpp index eb65fe2a8..3b464238d 100644 --- a/src/core/libraries/ajm/ajm_mp3.cpp +++ b/src/core/libraries/ajm/ajm_mp3.cpp @@ -15,20 +15,50 @@ extern "C" { namespace Libraries::Ajm { // Following tables have been reversed from AJM library -static constexpr std::array, 3> SamplerateTable = {{ - {0x5622, 0x5DC0, 0x3E80}, - {0xAC44, 0xBB80, 0x7D00}, - {0x2B11, 0x2EE0, 0x1F40}, -}}; +static constexpr std::array, 4> Mp3SampleRateTable = { + std::array{11025, 12000, 8000, 0}, + std::array{0, 0, 0, 0}, + std::array{22050, 24000, 16000, 0}, + std::array{44100, 48000, 32000, 0}, +}; -static constexpr std::array, 2> BitrateTable = {{ - {0, 0x20, 0x28, 0x30, 0x38, 0x40, 0x50, 0x60, 0x70, 0x80, 0xA0, 0xC0, 0xE0, 0x100, 0x140}, - {0, 0x8, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0}, -}}; +static constexpr std::array, 4> Mp3BitRateTable = { + std::array{0, 8, 16, 24, 32, 40, 48, 56, 64, 0, 0, 0, 0, 0, 0, 0}, + std::array{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + std::array{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, + std::array{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0}, +}; -static constexpr std::array UnkTable = {0x48, 0x90}; +enum class Mp3AudioVersion : u32 { + V2_5 = 0, + Reserved = 1, + V2 = 2, + V1 = 3, +}; -SwrContext* swr_context{}; +enum class Mp3ChannelMode : u32 { + Stereo = 0, + JointStereo = 1, + DualChannel = 2, + SingleChannel = 3, +}; + +struct Mp3Header { + u32 emphasis : 2; + u32 original : 1; + u32 copyright : 1; + u32 mode_ext_idx : 2; + Mp3ChannelMode channel_mode : 2; + u32 : 1; + u32 padding : 1; + u32 sampling_rate_idx : 2; + u32 bitrate_idx : 4; + u32 protection_type : 1; + u32 layer_type : 2; + Mp3AudioVersion version : 2; + u32 sync : 11; +}; +static_assert(sizeof(Mp3Header) == sizeof(u32)); static AVSampleFormat AjmToAVSampleFormat(AjmFormatEncoding format) { switch (format) { @@ -49,64 +79,81 @@ AVFrame* AjmMp3Decoder::ConvertAudioFrame(AVFrame* frame) { return frame; } - auto pcm16_frame = av_frame_clone(frame); - pcm16_frame->format = format; + AVFrame* new_frame = av_frame_alloc(); + new_frame->pts = frame->pts; + new_frame->pkt_dts = frame->pkt_dts < 0 ? 0 : frame->pkt_dts; + new_frame->format = format; + new_frame->ch_layout = frame->ch_layout; + new_frame->sample_rate = frame->sample_rate; - if (swr_context) { - swr_free(&swr_context); - swr_context = nullptr; - } AVChannelLayout in_ch_layout = frame->ch_layout; - AVChannelLayout out_ch_layout = pcm16_frame->ch_layout; - swr_alloc_set_opts2(&swr_context, &out_ch_layout, AVSampleFormat(pcm16_frame->format), + AVChannelLayout out_ch_layout = new_frame->ch_layout; + swr_alloc_set_opts2(&m_swr_context, &out_ch_layout, AVSampleFormat(new_frame->format), frame->sample_rate, &in_ch_layout, AVSampleFormat(frame->format), frame->sample_rate, 0, nullptr); - swr_init(swr_context); - const auto res = swr_convert_frame(swr_context, pcm16_frame, frame); + swr_init(m_swr_context); + const auto res = swr_convert_frame(m_swr_context, new_frame, frame); if (res < 0) { - LOG_ERROR(Lib_AvPlayer, "Could not convert to S16: {}", av_err2str(res)); + LOG_ERROR(Lib_AvPlayer, "Could not convert frame: {}", av_err2str(res)); + av_frame_free(&new_frame); + av_frame_free(&frame); return nullptr; } av_frame_free(&frame); - return pcm16_frame; + return new_frame; } -AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format) - : m_format(format), m_codec(avcodec_find_decoder(AV_CODEC_ID_MP3)), - m_parser(av_parser_init(m_codec->id)) { - AjmMp3Decoder::Reset(); -} - -AjmMp3Decoder::~AjmMp3Decoder() { - avcodec_free_context(&m_codec_context); -} - -void AjmMp3Decoder::Reset() { - if (m_codec_context) { - avcodec_free_context(&m_codec_context); - } - m_codec_context = avcodec_alloc_context3(m_codec); - ASSERT_MSG(m_codec_context, "Could not allocate audio m_codec context"); +AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags) + : m_format(format), m_flags(flags), m_codec(avcodec_find_decoder(AV_CODEC_ID_MP3)), + m_codec_context(avcodec_alloc_context3(m_codec)), m_parser(av_parser_init(m_codec->id)) { int ret = avcodec_open2(m_codec_context, m_codec, nullptr); ASSERT_MSG(ret >= 0, "Could not open m_codec"); } -void AjmMp3Decoder::GetInfo(void* out_info) { +AjmMp3Decoder::~AjmMp3Decoder() { + swr_free(&m_swr_context); + av_parser_close(m_parser); + avcodec_free_context(&m_codec_context); +} + +void AjmMp3Decoder::Reset() { + avcodec_flush_buffers(m_codec_context); + m_header.reset(); + m_frame_samples = 0; +} + +void AjmMp3Decoder::GetInfo(void* out_info) const { auto* info = reinterpret_cast(out_info); + if (m_header.has_value()) { + auto* header = reinterpret_cast(&m_header.value()); + info->header = std::byteswap(m_header.value()); + info->has_crc = header->protection_type; + info->channel_mode = static_cast(header->channel_mode); + info->mode_extension = header->mode_ext_idx; + info->copyright = header->copyright; + info->original = header->original; + info->emphasis = header->emphasis; + } } std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf, SparseOutputBuffer& output, - AjmSidebandGaplessDecode& gapless, - std::optional max_samples_per_channel) { + AjmInstanceGapless& gapless) { AVPacket* pkt = av_packet_alloc(); + if ((!m_header.has_value() || m_frame_samples == 0) && in_buf.size() >= 4) { + m_header = std::byteswap(*reinterpret_cast(in_buf.data())); + AjmDecMp3ParseFrame info{}; + ParseMp3Header(in_buf.data(), in_buf.size(), false, &info); + m_frame_samples = info.samples_per_channel; + } + int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(), in_buf.size(), AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); ASSERT_MSG(ret >= 0, "Error while parsing {}", ret); in_buf = in_buf.subspan(ret); u32 frames_decoded = 0; - u32 samples_decoded = 0; + u32 samples_written = 0; if (pkt->size) { // Send the packet with the compressed data to the decoder @@ -121,6 +168,7 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf, SparseOut AVFrame* frame = av_frame_alloc(); ret = avcodec_receive_frame(m_codec_context, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + av_frame_free(&frame); break; } else if (ret < 0) { UNREACHABLE_MSG("Error during decoding"); @@ -128,73 +176,260 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf, SparseOut frame = ConvertAudioFrame(frame); frames_decoded += 1; - u32 skipped_samples = 0; - if (gapless.skipped_samples < gapless.skip_samples) { - skipped_samples = std::min(u32(frame->nb_samples), - u32(gapless.skip_samples - gapless.skipped_samples)); - gapless.skipped_samples += skipped_samples; + u32 skip_samples = 0; + if (gapless.current.skip_samples > 0) { + skip_samples = std::min(u16(frame->nb_samples), gapless.current.skip_samples); + gapless.current.skip_samples -= skip_samples; } - const auto max_samples = - max_samples_per_channel.has_value() - ? max_samples_per_channel.value() * frame->ch_layout.nb_channels + const auto max_pcm = + gapless.init.total_samples != 0 + ? gapless.current.total_samples * m_codec_context->ch_layout.nb_channels : std::numeric_limits::max(); + u32 pcm_written = 0; switch (m_format) { case AjmFormatEncoding::S16: - samples_decoded += - WriteOutputSamples(frame, output, skipped_samples, max_samples); + pcm_written = WriteOutputPCM(frame, output, skip_samples, max_pcm); break; case AjmFormatEncoding::S32: - samples_decoded += - WriteOutputSamples(frame, output, skipped_samples, max_samples); + pcm_written = WriteOutputPCM(frame, output, skip_samples, max_pcm); break; case AjmFormatEncoding::Float: - samples_decoded += - WriteOutputSamples(frame, output, skipped_samples, max_samples); + pcm_written = WriteOutputPCM(frame, output, skip_samples, max_pcm); break; default: UNREACHABLE(); } + const auto samples = pcm_written / m_codec_context->ch_layout.nb_channels; + samples_written += samples; + gapless.current.skipped_samples += frame->nb_samples - samples; + if (gapless.init.total_samples != 0) { + gapless.current.total_samples -= samples; + } + av_frame_free(&frame); } } av_packet_free(&pkt); - return {frames_decoded, samples_decoded}; + return {frames_decoded, samples_written}; } -int AjmMp3Decoder::ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl, +u32 AjmMp3Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const { + const auto max_samples = gapless.init.total_samples != 0 + ? std::min(gapless.current.total_samples, m_frame_samples) + : m_frame_samples; + const auto skip_samples = std::min(u32(gapless.current.skip_samples), max_samples); + return (max_samples - skip_samples) * m_codec_context->ch_layout.nb_channels * + GetPCMSize(m_format); +} + +class BitReader { +public: + BitReader(const u8* data) : m_data(data) {} + + template + T Read(u32 const nbits) { + T accumulator = 0; + for (unsigned i = 0; i < nbits; ++i) { + accumulator = (accumulator << 1) + GetBit(); + } + return accumulator; + } + + void Skip(size_t nbits) { + m_bit_offset += nbits; + } + + size_t GetCurrentOffset() { + return m_bit_offset; + } + +private: + u8 GetBit() { + const auto bit = (m_data[m_bit_offset / 8] >> (7 - (m_bit_offset % 8))) & 1; + m_bit_offset += 1; + return bit; + } + + const u8* m_data; + size_t m_bit_offset = 0; +}; + +int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_ofl, AjmDecMp3ParseFrame* frame) { LOG_INFO(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl); - if (buf == nullptr || stream_size < 4 || frame == nullptr) { - return ORBIS_AJM_ERROR_INVALID_PARAMETER; - } - if ((buf[0] & SYNCWORDH) != SYNCWORDH || (buf[1] & SYNCWORDL) != SYNCWORDL) { + + if (p_begin == nullptr || stream_size < 4 || frame == nullptr) { return ORBIS_AJM_ERROR_INVALID_PARAMETER; } - const u32 unk_idx = buf[1] >> 3 & 1; - const s32 version_idx = (buf[1] >> 3 & 3) ^ 2; - const s32 sr_idx = buf[2] >> 2 & 3; - const s32 br_idx = (buf[2] >> 4) & 0xf; - const s32 padding_bit = (buf[2] >> 1) & 0x1; + const auto* p_current = p_begin; + + auto bytes = std::byteswap(*reinterpret_cast(p_current)); + p_current += 4; + auto header = reinterpret_cast(&bytes); + if (header->sync != 0x7FF) { + return ORBIS_AJM_ERROR_INVALID_PARAMETER; + } + + frame->sample_rate = Mp3SampleRateTable[u32(header->version)][header->sampling_rate_idx]; + frame->bitrate = Mp3BitRateTable[u32(header->version)][header->bitrate_idx] * 1000; + frame->num_channels = header->channel_mode == Mp3ChannelMode::SingleChannel ? 1 : 2; + if (header->version == Mp3AudioVersion::V1) { + frame->frame_size = (144 * frame->bitrate) / frame->sample_rate + header->padding; + frame->samples_per_channel = 1152; + } else { + frame->frame_size = (72 * frame->bitrate) / frame->sample_rate + header->padding; + frame->samples_per_channel = 576; + } - frame->sample_rate = SamplerateTable[version_idx][sr_idx]; - frame->bitrate = BitrateTable[version_idx != 1][br_idx] * 1000; - frame->num_channels = (buf[3] < 0xc0) + 1; - frame->frame_size = (UnkTable[unk_idx] * frame->bitrate) / frame->sample_rate + padding_bit; - frame->samples_per_channel = UnkTable[unk_idx] * 8; frame->encoder_delay = 0; + frame->num_frames = 0; + frame->total_samples = 0; + frame->ofl_type = AjmDecMp3OflType::None; + + if (!parse_ofl) { + return ORBIS_OK; + } + + BitReader reader(p_current); + if (header->protection_type == 0) { + reader.Skip(16); // crc = reader.Read(16); + } + + if (header->version == Mp3AudioVersion::V1) { + // main_data_begin = reader.Read(9); + // if (header->channel_mode == Mp3ChannelMode::SingleChannel) { + // private_bits = reader.Read(5); + // } else { + // private_bits = reader.Read(3); + // } + // for (u32 ch = 0; ch < frame->num_channels; ++ch) { + // for (u8 band = 0; band < 4; ++band) { + // scfsi[ch][band] = reader.Read(1); + // } + // } + if (header->channel_mode == Mp3ChannelMode::SingleChannel) { + reader.Skip(18); + } else { + reader.Skip(20); + } + } else { + // main_data_begin = reader.Read(8); + // if (header->channel_mode == Mp3ChannelMode::SingleChannel) { + // private_bits = reader.Read(1); + // } else { + // private_bits = reader.Read(2); + // } + if (header->channel_mode == Mp3ChannelMode::SingleChannel) { + reader.Skip(9); + } else { + reader.Skip(10); + } + } + + u32 part2_3_length = 0; + // Number of granules (18x32 sub-band samples) + const u8 ngr = header->version == Mp3AudioVersion::V1 ? 2 : 1; + for (u8 gr = 0; gr < ngr; ++gr) { + for (u32 ch = 0; ch < frame->num_channels; ++ch) { + // part2_3_length[gr][ch] = reader.Read(12); + part2_3_length += reader.Read(12); + // big_values[gr][ch] = reader.Read(9); + // global_main[gr][ch] = reader.Read(8); + // if (header->version == Mp3AudioVersion::V1) { + // scalefac_compress[gr][ch] = reader.Read(4); + // } else { + // scalefac_compress[gr][ch] = reader.Read(9); + // } + // window_switching_flag = reader.Read(1); + // if (window_switching_flag) { + // block_type[gr][ch] = reader.Read(2); + // mixed_block_flag[gr][ch] = reader.Read(1); + // for (u8 region = 0; region < 2; ++region) { + // table_select[gr][ch][region] = reader.Read(5); + // } + // for (u8 window = 0; window < 3; ++window) { + // subblock_gain[gr][ch][window] = reader.Read(3); + // } + // } else { + // for (u8 region = 0; region < 3; ++region) { + // table_select[gr][ch][region] = reader.Read(5); + // } + // region0_count[gr][ch] = reader.Read(4); + // region1_count[gr][ch] = reader.Read(3); + // } + // if (header->version == Mp3AudioVersion::V1) { + // preflag[gr][ch] = reader.Read(1); + // } + // scalefac_scale[gr][ch] = reader.Read(1); + // count1table_select[gr][ch] = reader.Read(1); + if (header->version == Mp3AudioVersion::V1) { + reader.Skip(47); + } else { + reader.Skip(51); + } + } + } + reader.Skip(part2_3_length); + + p_current += ((reader.GetCurrentOffset() + 7) / 8); + + const auto* p_end = p_begin + frame->frame_size; + if (memcmp(p_current, "Xing", 4) == 0 || memcmp(p_current, "Info", 4) == 0) { + // TODO: Parse Xing/Lame header + LOG_ERROR(Lib_Ajm, "Xing/Lame header is not implemented."); + } else if (memcmp(p_current, "VBRI", 4) == 0) { + // TODO: Parse VBRI header + LOG_ERROR(Lib_Ajm, "VBRI header is not implemented."); + } else { + // Parse FGH header + constexpr auto fgh_indicator = 0xB4; + while ((p_current + 9) < p_end && *p_current != fgh_indicator) { + ++p_current; + } + auto p_fgh = p_current; + if ((p_current + 9) < p_end && *p_current == fgh_indicator) { + u8 crc = 0xFF; + auto crc_func = [](u8 c, u8 v, u8 s) { + if (((c >> 7) & 1) != ((v >> s) & 1)) { + return c * 2; + } + return (c * 2) ^ 0x45; + }; + for (u8 i = 0; i < 9; ++i, ++p_current) { + for (u8 j = 0; j < 8; ++j) { + crc = crc_func(crc, *p_current, 7 - j); + } + } + if (p_fgh[9] == crc) { + frame->encoder_delay = std::byteswap(*reinterpret_cast(p_fgh + 1)); + frame->total_samples = std::byteswap(*reinterpret_cast(p_fgh + 3)); + frame->ofl_type = AjmDecMp3OflType::Fgh; + } else { + LOG_ERROR(Lib_Ajm, "FGH header CRC is incorrect."); + } + } else { + LOG_ERROR(Lib_Ajm, "Could not find vendor header."); + } + } return ORBIS_OK; } -AjmSidebandFormat AjmMp3Decoder::GetFormat() { - LOG_ERROR(Lib_Ajm, "Unimplemented"); - return AjmSidebandFormat{}; +AjmSidebandFormat AjmMp3Decoder::GetFormat() const { + return AjmSidebandFormat{ + .num_channels = u32(m_codec_context->ch_layout.nb_channels), + .channel_mask = GetChannelMask(u32(m_codec_context->ch_layout.nb_channels)), + .sampl_freq = u32(m_codec_context->sample_rate), + .sample_encoding = m_format, + .bitrate = u32(m_codec_context->bit_rate), + .reserved = 0, + }; }; } // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_mp3.h b/src/core/libraries/ajm/ajm_mp3.h index 0ae956d61..70c949550 100644 --- a/src/core/libraries/ajm/ajm_mp3.h +++ b/src/core/libraries/ajm/ajm_mp3.h @@ -8,11 +8,24 @@ extern "C" { #include +struct SwrContext; } namespace Libraries::Ajm { -enum class AjmDecMp3OflType : u32 { None = 0, Lame = 1, Vbri = 2, Fgh = 3, VbriAndFgh = 4 }; +enum class AjmDecMp3OflType : u32 { + None = 0, + Lame = 1, + Vbri = 2, + Fgh = 3, + VbriAndFgh = 4, +}; + +enum AjmMp3CodecFlags : u32 { + IgnoreOfl = 1 << 0, + VlcRewind = 1 << 8, +}; +DECLARE_ENUM_FLAG_OPERATORS(AjmMp3CodecFlags) // 11-bit syncword if MPEG 2.5 extensions are enabled static constexpr u8 SYNCWORDH = 0xff; @@ -50,38 +63,40 @@ struct AjmSidebandDecMp3CodecInfo { class AjmMp3Decoder : public AjmCodec { public: - explicit AjmMp3Decoder(AjmFormatEncoding format); + explicit AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags); ~AjmMp3Decoder() override; void Reset() override; void Initialize(const void* buffer, u32 buffer_size) override {} - void GetInfo(void* out_info) override; - AjmSidebandFormat GetFormat() override; + void GetInfo(void* out_info) const override; + AjmSidebandFormat GetFormat() const override; + u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override; std::tuple ProcessData(std::span& input, SparseOutputBuffer& output, - AjmSidebandGaplessDecode& gapless, - std::optional max_samples_per_channel) override; + AjmInstanceGapless& gapless) override; static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl, AjmDecMp3ParseFrame* frame); private: template - size_t WriteOutputSamples(AVFrame* frame, SparseOutputBuffer& output, u32 skipped_samples, - u32 max_samples) { - const auto size = frame->ch_layout.nb_channels * frame->nb_samples * sizeof(T); - std::span pcm_data(reinterpret_cast(frame->data[0]), size >> 1); + size_t WriteOutputPCM(AVFrame* frame, SparseOutputBuffer& output, u32 skipped_samples, + u32 max_pcm) { + std::span pcm_data(reinterpret_cast(frame->data[0]), + frame->nb_samples * frame->ch_layout.nb_channels); pcm_data = pcm_data.subspan(skipped_samples * frame->ch_layout.nb_channels); - const auto pcm_size = std::min(u32(pcm_data.size()), max_samples); - const auto samples_written = output.Write(pcm_data.subspan(0, pcm_size)); - return samples_written / frame->ch_layout.nb_channels; + return output.Write(pcm_data.subspan(0, std::min(u32(pcm_data.size()), max_pcm))); } AVFrame* ConvertAudioFrame(AVFrame* frame); const AjmFormatEncoding m_format; + const AjmMp3CodecFlags m_flags; const AVCodec* m_codec = nullptr; AVCodecContext* m_codec_context = nullptr; AVCodecParserContext* m_parser = nullptr; + SwrContext* m_swr_context = nullptr; + std::optional m_header; + u32 m_frame_samples = 0; }; } // namespace Libraries::Ajm diff --git a/src/core/libraries/error_codes.h b/src/core/libraries/error_codes.h index 66ad5f8b6..6bbdc3080 100644 --- a/src/core/libraries/error_codes.h +++ b/src/core/libraries/error_codes.h @@ -590,4 +590,29 @@ constexpr int ORBIS_VIDEODEC2_ERROR_NEW_SEQUENCE = 0x811D0300; constexpr int ORBIS_VIDEODEC2_ERROR_ACCESS_UNIT = 0x811D0301; constexpr int ORBIS_VIDEODEC2_ERROR_OVERSIZE_DECODE = 0x811D0302; constexpr int ORBIS_VIDEODEC2_ERROR_INVALID_SEQUENCE = 0x811D0303; -constexpr int ORBIS_VIDEODEC2_ERROR_FATAL_STREAM = 0x811D0304; \ No newline at end of file +constexpr int ORBIS_VIDEODEC2_ERROR_FATAL_STREAM = 0x811D0304; + +// Videodec library + +constexpr int ORBIS_VIDEODEC_ERROR_API_FAIL = 0x80C10000; +constexpr int ORBIS_VIDEODEC_ERROR_CODEC_TYPE = 0x80C10001; +constexpr int ORBIS_VIDEODEC_ERROR_STRUCT_SIZE = 0x80C10002; +constexpr int ORBIS_VIDEODEC_ERROR_HANDLE = 0x80C10003; +constexpr int ORBIS_VIDEODEC_ERROR_CPU_MEMORY_SIZE = 0x80C10004; +constexpr int ORBIS_VIDEODEC_ERROR_CPU_MEMORY_POINTER = 0x80C10005; +constexpr int ORBIS_VIDEODEC_ERROR_CPU_GPU_MEMORY_SIZE = 0x80C10006; +constexpr int ORBIS_VIDEODEC_ERROR_CPU_GPU_MEMORY_POINTER = 0x80C10007; +constexpr int ORBIS_VIDEODEC_ERROR_SHADER_CONTEXT_POINTER = 0x80C10008; +constexpr int ORBIS_VIDEODEC_ERROR_AU_SIZE = 0x80C10009; +constexpr int ORBIS_VIDEODEC_ERROR_AU_POINTER = 0x80C1000A; +constexpr int ORBIS_VIDEODEC_ERROR_FRAME_BUFFER_SIZE = 0x80C1000B; +constexpr int ORBIS_VIDEODEC_ERROR_FRAME_BUFFER_POINTER = 0x80C1000C; +constexpr int ORBIS_VIDEODEC_ERROR_FRAME_BUFFER_ALIGNMENT = 0x80C1000D; +constexpr int ORBIS_VIDEODEC_ERROR_CONFIG_INFO = 0x80C1000E; +constexpr int ORBIS_VIDEODEC_ERROR_ARGUMENT_POINTER = 0x80C1000F; +constexpr int ORBIS_VIDEODEC_ERROR_NEW_SEQUENCE = 0x80C10010; +constexpr int ORBIS_VIDEODEC_ERROR_DECODE_AU = 0x80C10011; +constexpr int ORBIS_VIDEODEC_ERROR_MISMATCH_SPEC = 0x80C10012; +constexpr int ORBIS_VIDEODEC_ERROR_INVALID_SEQUENCE = 0x80C10013; +constexpr int ORBIS_VIDEODEC_ERROR_FATAL_STREAM = 0x80C10014; +constexpr int ORBIS_VIDEODEC_ERROR_FATAL_STATE = 0x80C10015; diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index b0365435b..23d751622 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -41,6 +41,7 @@ #include "core/libraries/system/systemservice.h" #include "core/libraries/system/userservice.h" #include "core/libraries/usbd/usbd.h" +#include "core/libraries/videodec/videodec.h" #include "core/libraries/videodec/videodec2.h" #include "core/libraries/videoout/video_out.h" @@ -87,6 +88,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::GameLiveStreaming::RegisterlibSceGameLiveStreaming(sym); Libraries::SharePlay::RegisterlibSceSharePlay(sym); Libraries::Remoteplay::RegisterlibSceRemoteplay(sym); + Libraries::Videodec::RegisterlibSceVideodec(sym); } } // namespace Libraries diff --git a/src/core/libraries/videodec/videodec.cpp b/src/core/libraries/videodec/videodec.cpp new file mode 100644 index 000000000..8d6ea4988 --- /dev/null +++ b/src/core/libraries/videodec/videodec.cpp @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "videodec.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" +#include "videodec_impl.h" + +namespace Libraries::Videodec { + +static constexpr u64 kMinimumMemorySize = 32_MB; ///> Fake minimum memory size for querying + +int PS4_SYSV_ABI sceVideodecCreateDecoder(const OrbisVideodecConfigInfo* pCfgInfoIn, + const OrbisVideodecResourceInfo* pRsrcInfoIn, + OrbisVideodecCtrl* pCtrlOut) { + LOG_INFO(Lib_Videodec, "called"); + + if (!pCfgInfoIn || !pRsrcInfoIn || !pCtrlOut) { + return ORBIS_VIDEODEC_ERROR_ARGUMENT_POINTER; + } + if (pCfgInfoIn->thisSize != sizeof(OrbisVideodecConfigInfo) || + pRsrcInfoIn->thisSize != sizeof(OrbisVideodecResourceInfo)) { + return ORBIS_VIDEODEC_ERROR_STRUCT_SIZE; + } + + VdecDecoder* decoder = new VdecDecoder(*pCfgInfoIn, *pRsrcInfoIn); + pCtrlOut->thisSize = sizeof(OrbisVideodecCtrl); + pCtrlOut->handle = decoder; + pCtrlOut->version = 1; //??? + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceVideodecDecode(OrbisVideodecCtrl* pCtrlIn, + const OrbisVideodecInputData* pInputDataIn, + OrbisVideodecFrameBuffer* pFrameBufferInOut, + OrbisVideodecPictureInfo* pPictureInfoOut) { + LOG_TRACE(Lib_Videodec, "called"); + if (!pCtrlIn || !pInputDataIn || !pPictureInfoOut) { + return ORBIS_VIDEODEC_ERROR_ARGUMENT_POINTER; + } + if (pCtrlIn->thisSize != sizeof(OrbisVideodecCtrl) || + pFrameBufferInOut->thisSize != sizeof(OrbisVideodecFrameBuffer)) { + return ORBIS_VIDEODEC_ERROR_STRUCT_SIZE; + } + + VdecDecoder* decoder = (VdecDecoder*)pCtrlIn->handle; + if (!decoder) { + return ORBIS_VIDEODEC_ERROR_HANDLE; + } + return decoder->Decode(*pInputDataIn, *pFrameBufferInOut, *pPictureInfoOut); +} + +int PS4_SYSV_ABI sceVideodecDeleteDecoder(OrbisVideodecCtrl* pCtrlIn) { + LOG_INFO(Lib_Videodec, "(STUBBED) called"); + + VdecDecoder* decoder = (VdecDecoder*)pCtrlIn->handle; + if (!decoder) { + return ORBIS_VIDEODEC_ERROR_HANDLE; + } + delete decoder; + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceVideodecFlush(OrbisVideodecCtrl* pCtrlIn, + OrbisVideodecFrameBuffer* pFrameBufferInOut, + OrbisVideodecPictureInfo* pPictureInfoOut) { + LOG_INFO(Lib_Videodec, "called"); + + if (!pFrameBufferInOut || !pPictureInfoOut) { + return ORBIS_VIDEODEC_ERROR_ARGUMENT_POINTER; + } + if (pFrameBufferInOut->thisSize != sizeof(OrbisVideodecFrameBuffer) || + pPictureInfoOut->thisSize != sizeof(OrbisVideodecPictureInfo)) { + return ORBIS_VIDEODEC_ERROR_STRUCT_SIZE; + } + + VdecDecoder* decoder = (VdecDecoder*)pCtrlIn->handle; + if (!decoder) { + return ORBIS_VIDEODEC_ERROR_HANDLE; + } + return decoder->Flush(*pFrameBufferInOut, *pPictureInfoOut); +} + +int PS4_SYSV_ABI sceVideodecMapMemory() { + LOG_ERROR(Lib_Videodec, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceVideodecQueryResourceInfo(const OrbisVideodecConfigInfo* pCfgInfoIn, + OrbisVideodecResourceInfo* pRsrcInfoOut) { + LOG_INFO(Lib_Videodec, "called"); + + if (!pCfgInfoIn || !pRsrcInfoOut) { + return ORBIS_VIDEODEC_ERROR_ARGUMENT_POINTER; + } + if (pCfgInfoIn->thisSize != sizeof(OrbisVideodecConfigInfo) || + pRsrcInfoOut->thisSize != sizeof(OrbisVideodecResourceInfo)) { + return ORBIS_VIDEODEC_ERROR_STRUCT_SIZE; + } + + pRsrcInfoOut->thisSize = sizeof(OrbisVideodecResourceInfo); + pRsrcInfoOut->pCpuMemory = nullptr; + pRsrcInfoOut->pCpuGpuMemory = nullptr; + + pRsrcInfoOut->cpuGpuMemorySize = kMinimumMemorySize; + pRsrcInfoOut->cpuMemorySize = kMinimumMemorySize; + + pRsrcInfoOut->maxFrameBufferSize = kMinimumMemorySize; + pRsrcInfoOut->frameBufferAlignment = 0x100; + + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceVideodecReset(OrbisVideodecCtrl* pCtrlIn) { + LOG_INFO(Lib_Videodec, "(STUBBED) called"); + + VdecDecoder* decoder = (VdecDecoder*)pCtrlIn->handle; + decoder->Reset(); + return ORBIS_OK; +} + +void RegisterlibSceVideodec(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("qkgRiwHyheU", "libSceVideodec", 1, "libSceVideodec", 1, 1, + sceVideodecCreateDecoder); + LIB_FUNCTION("q0W5GJMovMs", "libSceVideodec", 1, "libSceVideodec", 1, 1, sceVideodecDecode); + LIB_FUNCTION("U0kpGF1cl90", "libSceVideodec", 1, "libSceVideodec", 1, 1, + sceVideodecDeleteDecoder); + LIB_FUNCTION("jeigLlKdp5I", "libSceVideodec", 1, "libSceVideodec", 1, 1, sceVideodecFlush); + LIB_FUNCTION("kg+lH0V61hM", "libSceVideodec", 1, "libSceVideodec", 1, 1, sceVideodecMapMemory); + LIB_FUNCTION("leCAscipfFY", "libSceVideodec", 1, "libSceVideodec", 1, 1, + sceVideodecQueryResourceInfo); + LIB_FUNCTION("f8AgDv-1X8A", "libSceVideodec", 1, "libSceVideodec", 1, 1, sceVideodecReset); +}; + +} // namespace Libraries::Videodec \ No newline at end of file diff --git a/src/core/libraries/videodec/videodec.h b/src/core/libraries/videodec/videodec.h new file mode 100644 index 000000000..45c5a6924 --- /dev/null +++ b/src/core/libraries/videodec/videodec.h @@ -0,0 +1,108 @@ +// 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::Videodec { + +struct OrbisVideodecConfigInfo { + u64 thisSize; + u32 codecType; + u32 profile; + u32 maxLevel; + s32 maxFrameWidth; + s32 maxFrameHeight; + s32 maxDpbFrameCount; + u64 videodecFlags; +}; + +struct OrbisVideodecResourceInfo { + u64 thisSize; + u64 cpuMemorySize; + void* pCpuMemory; + u64 cpuGpuMemorySize; + void* pCpuGpuMemory; + u64 maxFrameBufferSize; + u32 frameBufferAlignment; +}; + +struct OrbisVideodecCtrl { + u64 thisSize; + void* handle; + u64 version; +}; + +struct OrbisVideodecFrameBuffer { + u64 thisSize; + void* pFrameBuffer; + u64 frameBufferSize; +}; + +struct OrbisVideodecAvcInfo { + u32 numUnitsInTick; + u32 timeScale; + u8 fixedFrameRateFlag; + u8 aspectRatioIdc; + u16 sarWidth; + u16 sarHeight; + u8 colourPrimaries; + u8 transferCharacteristics; + u8 matrixCoefficients; + u8 videoFullRangeFlag; + u32 frameCropLeftOffset; + u32 frameCropRightOffset; + u32 frameCropTopOffset; + u32 frameCropBottomOffset; +}; + +union OrbisVideodecCodecInfo { + u8 reserved[64]; + OrbisVideodecAvcInfo avc; +}; + +struct OrbisVideodecPictureInfo { + u64 thisSize; + u32 isValid; + u32 codecType; + u32 frameWidth; + u32 framePitch; + u32 frameHeight; + u32 isErrorPic; + u64 ptsData; + u64 attachedData; + OrbisVideodecCodecInfo codec; +}; + +struct OrbisVideodecInputData { + u64 thisSize; + void* pAuData; + u64 auSize; + u64 ptsData; + u64 dtsData; + u64 attachedData; +}; + +int PS4_SYSV_ABI sceVideodecCreateDecoder(const OrbisVideodecConfigInfo* pCfgInfoIn, + const OrbisVideodecResourceInfo* pRsrcInfoIn, + OrbisVideodecCtrl* pCtrlOut); +int PS4_SYSV_ABI sceVideodecDecode(OrbisVideodecCtrl* pCtrlIn, + const OrbisVideodecInputData* pInputDataIn, + OrbisVideodecFrameBuffer* pFrameBufferInOut, + OrbisVideodecPictureInfo* pPictureInfoOut); +int PS4_SYSV_ABI sceVideodecDeleteDecoder(OrbisVideodecCtrl* pCtrlIn); +int PS4_SYSV_ABI sceVideodecFlush(OrbisVideodecCtrl* pCtrlIn, + OrbisVideodecFrameBuffer* pFrameBufferInOut, + OrbisVideodecPictureInfo* pPictureInfoOut); +int PS4_SYSV_ABI sceVideodecMapMemory(); +int PS4_SYSV_ABI sceVideodecQueryResourceInfo(const OrbisVideodecConfigInfo* pCfgInfoIn, + OrbisVideodecResourceInfo* pRsrcInfoOut); +int PS4_SYSV_ABI sceVideodecReset(OrbisVideodecCtrl* pCtrlIn); + +void RegisterlibSceVideodec(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Videodec \ No newline at end of file diff --git a/src/core/libraries/videodec/videodec_impl.cpp b/src/core/libraries/videodec/videodec_impl.cpp new file mode 100644 index 000000000..a244c4a61 --- /dev/null +++ b/src/core/libraries/videodec/videodec_impl.cpp @@ -0,0 +1,222 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "videodec_impl.h" + +#include "common/alignment.h" +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" + +// The av_err2str macro in libavutil/error.h does not play nice with C++ +#ifdef av_err2str +#undef av_err2str +#include +av_always_inline std::string av_err2string(int errnum) { + char errbuf[AV_ERROR_MAX_STRING_SIZE]; + return av_make_error_string(errbuf, AV_ERROR_MAX_STRING_SIZE, errnum); +} +#define av_err2str(err) av_err2string(err).c_str() +#endif // av_err2str + +namespace Libraries::Videodec { + +static inline void CopyNV12Data(u8* dst, const AVFrame& src) { + u32 width = Common::AlignUp((u32)src.width, 16); + u32 height = Common::AlignUp((u32)src.height, 16); + std::memcpy(dst, src.data[0], src.width * src.height); + std::memcpy(dst + src.width * height, src.data[1], (src.width * src.height) / 2); +} + +VdecDecoder::VdecDecoder(const OrbisVideodecConfigInfo& pCfgInfoIn, + const OrbisVideodecResourceInfo& pRsrcInfoIn) { + + const AVCodec* codec = avcodec_find_decoder(AV_CODEC_ID_H264); + ASSERT(codec); + + mCodecContext = avcodec_alloc_context3(codec); + ASSERT(mCodecContext); + mCodecContext->width = pCfgInfoIn.maxFrameWidth; + mCodecContext->height = pCfgInfoIn.maxFrameHeight; + + avcodec_open2(mCodecContext, codec, nullptr); +} + +VdecDecoder::~VdecDecoder() { + avcodec_free_context(&mCodecContext); + sws_freeContext(mSwsContext); +} + +s32 VdecDecoder::Decode(const OrbisVideodecInputData& pInputDataIn, + OrbisVideodecFrameBuffer& pFrameBufferInOut, + OrbisVideodecPictureInfo& pPictureInfoOut) { + pPictureInfoOut.thisSize = sizeof(OrbisVideodecPictureInfo); + pPictureInfoOut.isValid = false; + pPictureInfoOut.isErrorPic = true; + + if (!pInputDataIn.pAuData) { + return ORBIS_VIDEODEC_ERROR_AU_POINTER; + } + if (pInputDataIn.auSize == 0) { + return ORBIS_VIDEODEC_ERROR_AU_SIZE; + } + + AVPacket* packet = av_packet_alloc(); + if (!packet) { + LOG_ERROR(Lib_Videodec, "Failed to allocate packet"); + return ORBIS_VIDEODEC_ERROR_API_FAIL; + } + + packet->data = (u8*)pInputDataIn.pAuData; + packet->size = pInputDataIn.auSize; + packet->pts = pInputDataIn.ptsData; + packet->dts = pInputDataIn.dtsData; + + int ret = avcodec_send_packet(mCodecContext, packet); + if (ret < 0) { + LOG_ERROR(Lib_Videodec, "Error sending packet to decoder: {}", ret); + av_packet_free(&packet); + return ORBIS_VIDEODEC_ERROR_API_FAIL; + } + + AVFrame* frame = av_frame_alloc(); + if (frame == nullptr) { + LOG_ERROR(Lib_Videodec, "Failed to allocate frame"); + av_packet_free(&packet); + return ORBIS_VIDEODEC_ERROR_API_FAIL; + } + int frameCount = 0; + while (true) { + ret = avcodec_receive_frame(mCodecContext, frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } else if (ret < 0) { + LOG_ERROR(Lib_Videodec, "Error receiving frame from decoder: {}", ret); + av_packet_free(&packet); + av_frame_free(&frame); + return ORBIS_VIDEODEC_ERROR_API_FAIL; + } + + if (frame->format != AV_PIX_FMT_NV12) { + AVFrame* nv12_frame = ConvertNV12Frame(*frame); + ASSERT(nv12_frame); + av_frame_free(&frame); + frame = nv12_frame; + } + + CopyNV12Data((u8*)pFrameBufferInOut.pFrameBuffer, *frame); + + pPictureInfoOut.codecType = 0; + pPictureInfoOut.frameWidth = Common::AlignUp((u32)frame->width, 16); + pPictureInfoOut.frameHeight = Common::AlignUp((u32)frame->height, 16); + pPictureInfoOut.framePitch = frame->linesize[0]; + + pPictureInfoOut.isValid = true; + pPictureInfoOut.isErrorPic = false; + frameCount++; + if (frameCount > 1) { + LOG_WARNING(Lib_Videodec, "We have more than 1 frame"); + } + } + + av_packet_free(&packet); + av_frame_free(&frame); + return ORBIS_OK; +} + +s32 VdecDecoder::Flush(OrbisVideodecFrameBuffer& pFrameBufferInOut, + OrbisVideodecPictureInfo& pPictureInfoOut) { + pPictureInfoOut.thisSize = sizeof(pPictureInfoOut); + pPictureInfoOut.isValid = false; + pPictureInfoOut.isErrorPic = true; + + AVFrame* frame = av_frame_alloc(); + if (!frame) { + LOG_ERROR(Lib_Videodec, "Failed to allocate frame"); + return ORBIS_VIDEODEC_ERROR_API_FAIL; + } + + int frameCount = 0; + while (true) { + int ret = avcodec_receive_frame(mCodecContext, frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } else if (ret < 0) { + LOG_ERROR(Lib_Videodec, "Error receiving frame from decoder: {}", ret); + av_frame_free(&frame); + return ORBIS_VIDEODEC_ERROR_API_FAIL; + } + + if (frame->format != AV_PIX_FMT_NV12) { + AVFrame* nv12_frame = ConvertNV12Frame(*frame); + ASSERT(nv12_frame); + av_frame_free(&frame); + frame = nv12_frame; + } + + CopyNV12Data((u8*)pFrameBufferInOut.pFrameBuffer, *frame); + + pPictureInfoOut.codecType = 0; + pPictureInfoOut.frameWidth = Common::AlignUp((u32)frame->width, 16); + pPictureInfoOut.frameHeight = Common::AlignUp((u32)frame->height, 16); + pPictureInfoOut.framePitch = frame->linesize[0]; + + pPictureInfoOut.isValid = true; + pPictureInfoOut.isErrorPic = false; + + u32 width = Common::AlignUp((u32)frame->width, 16); + u32 height = Common::AlignUp((u32)frame->height, 16); + pPictureInfoOut.codec.avc.frameCropLeftOffset = u32(frame->crop_left); + pPictureInfoOut.codec.avc.frameCropRightOffset = + u32(frame->crop_right + (width - frame->width)); + pPictureInfoOut.codec.avc.frameCropTopOffset = u32(frame->crop_top); + pPictureInfoOut.codec.avc.frameCropBottomOffset = + u32(frame->crop_bottom + (height - frame->height)); + // TODO maybe more avc? + + if (frameCount > 1) { + LOG_WARNING(Lib_Videodec, "We have more than 1 frame"); + } + } + + av_frame_free(&frame); + return ORBIS_OK; +} + +s32 VdecDecoder::Reset() { + avcodec_flush_buffers(mCodecContext); + return ORBIS_OK; +} + +AVFrame* VdecDecoder::ConvertNV12Frame(AVFrame& frame) { + AVFrame* nv12_frame = av_frame_alloc(); + nv12_frame->pts = frame.pts; + nv12_frame->pkt_dts = frame.pkt_dts < 0 ? 0 : frame.pkt_dts; + nv12_frame->format = AV_PIX_FMT_NV12; + nv12_frame->width = frame.width; + nv12_frame->height = frame.height; + nv12_frame->sample_aspect_ratio = frame.sample_aspect_ratio; + nv12_frame->crop_top = frame.crop_top; + nv12_frame->crop_bottom = frame.crop_bottom; + nv12_frame->crop_left = frame.crop_left; + nv12_frame->crop_right = frame.crop_right; + + av_frame_get_buffer(nv12_frame, 0); + + if (mSwsContext == nullptr) { + mSwsContext = sws_getContext(frame.width, frame.height, AVPixelFormat(frame.format), + nv12_frame->width, nv12_frame->height, AV_PIX_FMT_NV12, + SWS_FAST_BILINEAR, nullptr, nullptr, nullptr); + } + + const auto res = sws_scale(mSwsContext, frame.data, frame.linesize, 0, frame.height, + nv12_frame->data, nv12_frame->linesize); + if (res < 0) { + LOG_ERROR(Lib_Videodec, "Could not convert to NV12: {}", av_err2str(res)); + return nullptr; + } + + return nv12_frame; +} + +} // namespace Libraries::Videodec diff --git a/src/core/libraries/videodec/videodec_impl.h b/src/core/libraries/videodec/videodec_impl.h new file mode 100644 index 000000000..3d48db608 --- /dev/null +++ b/src/core/libraries/videodec/videodec_impl.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "videodec.h" + +extern "C" { +#include +#include +#include +} + +namespace Libraries::Videodec { + +class VdecDecoder { +public: + VdecDecoder(const OrbisVideodecConfigInfo& pCfgInfoIn, + const OrbisVideodecResourceInfo& pRsrcInfoIn); + ~VdecDecoder(); + s32 Decode(const OrbisVideodecInputData& pInputDataIn, + OrbisVideodecFrameBuffer& pFrameBufferInOut, + OrbisVideodecPictureInfo& pPictureInfoOut); + s32 Flush(OrbisVideodecFrameBuffer& pFrameBufferInOut, + OrbisVideodecPictureInfo& pPictureInfoOut); + s32 Reset(); + +private: + AVFrame* ConvertNV12Frame(AVFrame& frame); + +private: + AVCodecContext* mCodecContext = nullptr; + SwsContext* mSwsContext = nullptr; +}; + +} // namespace Libraries::Videodec \ No newline at end of file diff --git a/src/images/net.shadps4.shadPS4.svg b/src/images/net.shadps4.shadPS4.svg new file mode 100644 index 000000000..2d954b12c --- /dev/null +++ b/src/images/net.shadps4.shadPS4.svg @@ -0,0 +1,2 @@ + + diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp index 230d396f0..60b440c24 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.cpp +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -11,6 +11,7 @@ #include #if defined(__APPLE__) #include +#include #endif #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN @@ -71,7 +72,14 @@ static void PlatformSetImeData(ImGuiContext*, ImGuiViewport* viewport, ImGuiPlat auto window_id = (SDL_WindowID)(intptr_t)viewport->PlatformHandle; SDL_Window* window = SDL_GetWindowFromID(window_id); if ((!data->WantVisible || bd->ime_window != window) && bd->ime_window != nullptr) { - SDL_StopTextInput(bd->ime_window); + auto stop_input = [&bd] { SDL_StopTextInput(bd->ime_window); }; +#ifdef __APPLE__ + dispatch_sync(dispatch_get_main_queue(), ^{ + stop_input(); + }); +#else + stop_input(); +#endif bd->ime_window = nullptr; } if (data->WantVisible) { @@ -80,8 +88,17 @@ static void PlatformSetImeData(ImGuiContext*, ImGuiViewport* viewport, ImGuiPlat r.y = (int)data->InputPos.y; r.w = 1; r.h = (int)data->InputLineHeight; - SDL_SetTextInputArea(window, &r, 0); - SDL_StartTextInput(window); + const auto start_input = [&window, &r] { + SDL_SetTextInputArea(window, &r, 0); + SDL_StartTextInput(window); + }; +#ifdef __APPLE__ + dispatch_sync(dispatch_get_main_queue(), ^{ + start_input(); + }); +#else + start_input(); +#endif bd->ime_window = window; } } diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 8d7018522..823ad921c 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -44,20 +44,31 @@ public: // Setup menu. QMenu menu(widget); + + // "Open Folder..." submenu + QMenu* openFolderMenu = new QMenu(tr("Open Folder..."), widget); + QAction* openGameFolder = new QAction(tr("Open Game Folder"), widget); + QAction* openSaveDataFolder = new QAction(tr("Open Save Data Folder"), widget); + QAction* openLogFolder = new QAction(tr("Open Log Folder"), widget); + + openFolderMenu->addAction(openGameFolder); + openFolderMenu->addAction(openSaveDataFolder); + openFolderMenu->addAction(openLogFolder); + + menu.addMenu(openFolderMenu); + QAction createShortcut(tr("Create Shortcut"), widget); - QAction openFolder(tr("Open Game Folder"), widget); QAction openCheats(tr("Cheats / Patches"), widget); QAction openSfoViewer(tr("SFO Viewer"), widget); QAction openTrophyViewer(tr("Trophy Viewer"), widget); - menu.addAction(&openFolder); menu.addAction(&createShortcut); menu.addAction(&openCheats); menu.addAction(&openSfoViewer); menu.addAction(&openTrophyViewer); // "Copy" submenu. - QMenu* copyMenu = new QMenu(tr("Copy info"), widget); + QMenu* copyMenu = new QMenu(tr("Copy info..."), widget); QAction* copyName = new QAction(tr("Copy Name"), widget); QAction* copySerial = new QAction(tr("Copy Serial"), widget); QAction* copyNameAll = new QAction(tr("Copy All"), widget); @@ -86,12 +97,29 @@ public: return; } - if (selected == &openFolder) { + if (selected == openGameFolder) { QString folderPath; Common::FS::PathToQString(folderPath, m_games[itemID].path); QDesktopServices::openUrl(QUrl::fromLocalFile(folderPath)); } + if (selected == openSaveDataFolder) { + QString userPath; + Common::FS::PathToQString(userPath, + Common::FS::GetUserPath(Common::FS::PathType::UserDir)); + QString saveDataPath = + userPath + "/savedata/1/" + QString::fromStdString(m_games[itemID].serial); + QDir(saveDataPath).mkpath(saveDataPath); + QDesktopServices::openUrl(QUrl::fromLocalFile(saveDataPath)); + } + + if (selected == openLogFolder) { + QString userPath; + Common::FS::PathToQString(userPath, + Common::FS::GetUserPath(Common::FS::PathType::UserDir)); + QDesktopServices::openUrl(QUrl::fromLocalFile(userPath + "/log")); + } + if (selected == &openSfoViewer) { PSF psf; QString game_update_path; diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 8e56a6e9d..959b055c8 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -7,7 +7,9 @@ #include "about_dialog.h" #include "cheats_patches.h" +#ifdef ENABLE_UPDATER #include "check_update.h" +#endif #include "common/io_file.h" #include "common/path_util.h" #include "common/scm_rev.h" @@ -59,8 +61,10 @@ bool MainWindow::Init() { this->show(); // load game list LoadGameLists(); +#ifdef ENABLE_UPDATER // Check for update CheckUpdateMain(true); +#endif auto end = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast(end - start); @@ -183,6 +187,7 @@ void MainWindow::LoadGameLists() { } } +#ifdef ENABLE_UPDATER void MainWindow::CheckUpdateMain(bool checkSave) { if (checkSave) { if (!Config::autoUpdate()) { @@ -192,6 +197,7 @@ void MainWindow::CheckUpdateMain(bool checkSave) { auto checkUpdate = new CheckUpdate(false); checkUpdate->exec(); } +#endif void MainWindow::GetPhysicalDevices() { Vulkan::Instance instance(false, false); @@ -254,10 +260,12 @@ void MainWindow::CreateConnects() { settingsDialog->exec(); }); +#ifdef ENABLE_UPDATER connect(ui->updaterAct, &QAction::triggered, this, [this]() { auto checkUpdate = new CheckUpdate(true); checkUpdate->exec(); }); +#endif connect(ui->aboutAct, &QAction::triggered, this, [this]() { auto aboutDialog = new AboutDialog(this); @@ -933,7 +941,9 @@ void MainWindow::SetUiIcons(bool isWhite) { ui->bootInstallPkgAct->setIcon(RecolorIcon(ui->bootInstallPkgAct->icon(), isWhite)); ui->bootGameAct->setIcon(RecolorIcon(ui->bootGameAct->icon(), isWhite)); ui->exitAct->setIcon(RecolorIcon(ui->exitAct->icon(), isWhite)); +#ifdef ENABLE_UPDATER ui->updaterAct->setIcon(RecolorIcon(ui->updaterAct->icon(), isWhite)); +#endif ui->downloadCheatsPatchesAct->setIcon( RecolorIcon(ui->downloadCheatsPatchesAct->icon(), isWhite)); ui->dumpGameListAct->setIcon(RecolorIcon(ui->dumpGameListAct->icon(), isWhite)); diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index 6264978aa..7dc85f0a7 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -56,7 +56,9 @@ private: void CreateDockWindows(); void GetPhysicalDevices(); void LoadGameLists(); +#ifdef ENABLE_UPDATER void CheckUpdateMain(bool checkSave); +#endif void CreateConnects(); void SetLastUsedTheme(); void SetLastIconSizeBullet(); diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 373b2924e..a51e37d1e 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -26,7 +26,9 @@ public: QAction* downloadCheatsPatchesAct; QAction* dumpGameListAct; QAction* pkgViewerAct; +#ifdef ENABLE_UPDATER QAction* updaterAct; +#endif QAction* aboutAct; QAction* configureAct; QAction* setThemeDark; @@ -130,9 +132,11 @@ public: pkgViewerAct = new QAction(MainWindow); pkgViewerAct->setObjectName("pkgViewer"); pkgViewerAct->setIcon(QIcon(":images/file_icon.png")); +#ifdef ENABLE_UPDATER updaterAct = new QAction(MainWindow); updaterAct->setObjectName("updaterAct"); updaterAct->setIcon(QIcon(":images/update_icon.png")); +#endif aboutAct = new QAction(MainWindow); aboutAct->setObjectName("aboutAct"); aboutAct->setIcon(QIcon(":images/about_icon.png")); @@ -291,7 +295,9 @@ public: menuUtils->addAction(downloadCheatsPatchesAct); menuUtils->addAction(dumpGameListAct); menuUtils->addAction(pkgViewerAct); +#ifdef ENABLE_UPDATER menuHelp->addAction(updaterAct); +#endif menuHelp->addAction(aboutAct); retranslateUi(MainWindow); @@ -306,8 +312,10 @@ public: bootInstallPkgAct->setText( QCoreApplication::translate("MainWindow", "Install Packages (PKG)", nullptr)); bootGameAct->setText(QCoreApplication::translate("MainWindow", "Boot Game", nullptr)); +#ifdef ENABLE_UPDATER updaterAct->setText( QCoreApplication::translate("MainWindow", "Check for Updates", nullptr)); +#endif aboutAct->setText(QCoreApplication::translate("MainWindow", "About shadPS4", nullptr)); configureAct->setText(QCoreApplication::translate("MainWindow", "Configure...", nullptr)); #if QT_CONFIG(tooltip) diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 84dc5011e..83582663a 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -6,7 +6,9 @@ #include #include +#ifdef ENABLE_UPDATER #include "check_update.h" +#endif #include "common/logging/backend.h" #include "common/logging/filter.h" #include "main_window.h" @@ -29,7 +31,7 @@ QStringList languageNames = {"Arabic", "Italian", "Japanese", "Korean", - "Norwegian", + "Norwegian (Bokmaal)", "Polish", "Portuguese (Brazil)", "Portuguese (Portugal)", @@ -143,6 +145,7 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge connect(ui->logFilterLineEdit, &QLineEdit::textChanged, this, [](const QString& text) { Config::setLogFilter(text.toStdString()); }); +#ifdef ENABLE_UPDATER connect(ui->updateCheckBox, &QCheckBox::stateChanged, this, [](int state) { Config::setAutoUpdate(state == Qt::Checked); }); @@ -153,6 +156,10 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge auto checkUpdate = new CheckUpdate(true); checkUpdate->exec(); }); +#else + ui->updaterGroupBox->setVisible(false); + ui->GUIgroupBox->setMaximumSize(265, 16777215); +#endif connect(ui->playBGMCheckBox, &QCheckBox::stateChanged, this, [](int val) { Config::setPlayBGM(val); @@ -278,7 +285,9 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge ui->userName->installEventFilter(this); ui->logTypeGroupBox->installEventFilter(this); ui->logFilter->installEventFilter(this); +#ifdef ENABLE_UPDATER ui->updaterGroupBox->installEventFilter(this); +#endif ui->GUIgroupBox->installEventFilter(this); // Input @@ -340,6 +349,7 @@ void SettingsDialog::LoadValuesFromConfig() { ui->vkSyncValidationCheckBox->setChecked(Config::vkValidationSyncEnabled()); ui->rdocCheckBox->setChecked(Config::isRdocEnabled()); +#ifdef ENABLE_UPDATER ui->updateCheckBox->setChecked(Config::autoUpdate()); std::string updateChannel = Config::getUpdateChannel(); if (updateChannel != "Release" && updateChannel != "Nightly") { @@ -350,6 +360,7 @@ void SettingsDialog::LoadValuesFromConfig() { } } ui->updateComboBox->setCurrentText(QString::fromStdString(updateChannel)); +#endif for (const auto& dir : Config::getGameInstallDirs()) { QString path_string; @@ -451,8 +462,10 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("logTypeGroupBox"); } else if (elementName == "logFilter") { text = tr("logFilter"); +#ifdef ENABLE_UPDATER } else if (elementName == "updaterGroupBox") { text = tr("updaterGroupBox"); +#endif } else if (elementName == "GUIgroupBox") { text = tr("GUIgroupBox"); } @@ -535,4 +548,4 @@ bool SettingsDialog::eventFilter(QObject* obj, QEvent* event) { } } return QDialog::eventFilter(obj, event); -} \ No newline at end of file +} diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts index 457d84ef8..25e215183 100644 --- a/src/qt_gui/translations/ar.ts +++ b/src/qt_gui/translations/ar.ts @@ -100,11 +100,6 @@ Create Shortcut إنشاء اختصار - - - Open Game Folder - فتح مجلد اللعبة - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer عارض الجوائز + + + Open Folder... + فتح المجلد... + + + + Open Game Folder + فتح مجلد اللعبة + + + + Open Save Data Folder + فتح مجلد بيانات الحفظ + + + + Open Log Folder + فتح مجلد السجل + - Copy info - نسخ المعلومات + Copy info... + ...نسخ المعلومات diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index e14826725..14c42f1d9 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -100,11 +100,6 @@ Create Shortcut Create Shortcut - - - Open Game Folder - Open Game Folder - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Trophy Viewer + + + Open Folder... + Åbn Mappe... + + + + Open Game Folder + Åbn Spilmappe + + + + Open Save Data Folder + Åbn Gem Data Mappe + + + + Open Log Folder + Åbn Log Mappe + - Copy info - Copy info + Copy info... + Copy info... diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts index 77b6a01ac..64a6c6480 100644 --- a/src/qt_gui/translations/de.ts +++ b/src/qt_gui/translations/de.ts @@ -100,11 +100,6 @@ Create Shortcut Verknüpfung erstellen - - - Open Game Folder - Spieleordner öffnen - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Trophäen anzeigen + + + Open Folder... + Ordner öffnen... + + + + Open Game Folder + Spielordner öffnen + + + + Open Save Data Folder + Speicherordner öffnen + + + + Open Log Folder + Protokollordner öffnen + - Copy info - Infos kopieren + Copy info... + Infos kopieren... diff --git a/src/qt_gui/translations/el.ts b/src/qt_gui/translations/el.ts index a738bf90a..e064f8c26 100644 --- a/src/qt_gui/translations/el.ts +++ b/src/qt_gui/translations/el.ts @@ -100,11 +100,6 @@ Create Shortcut Create Shortcut - - - Open Game Folder - Open Game Folder - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Trophy Viewer + + + Open Folder... + Άνοιγμα Φακέλου... + + + + Open Game Folder + Άνοιγμα Φακέλου Παιχνιδιού + + + + Open Save Data Folder + Άνοιγμα Φακέλου Αποθηκευμένων Δεδομένων + + + + Open Log Folder + Άνοιγμα Φακέλου Καταγραφής + - Copy info - Copy info + Copy info... + Copy info... diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index 9f25fc836..9bf7c7188 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -100,11 +100,6 @@ Create Shortcut Create Shortcut - - - Open Game Folder - Open Game Folder - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Trophy Viewer + + + Open Folder... + Open Folder... + + + + Open Game Folder + Open Game Folder + + + + Open Save Data Folder + Open Save Data Folder + + + + Open Log Folder + Open Log Folder + - Copy info - Copy info + Copy info... + Copy info... diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index b0a6e4335..5d637249e 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -100,11 +100,6 @@ Create Shortcut Crear acceso directo - - - Open Game Folder - Abrir carpeta del juego - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Ver trofeos + + + Open Folder... + Abrir Carpeta... + + + + Open Game Folder + Abrir Carpeta del Juego + + + + Open Save Data Folder + Abrir Carpeta de Datos Guardados + + + + Open Log Folder + Abrir Carpeta de Registros + - Copy info - Copiar información + Copy info... + Copiar información... diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 15f5d6193..55a2fdf53 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -100,11 +100,6 @@ Create Shortcut ساخت شورتکات - - - Open Game Folder - بازکردن محل نصب بازی - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer مشاهده تروفی ها + + + Open Folder... + باز کردن پوشه... + - Copy info - کپی کردن اطلاعات + Open Game Folder + باز کردن پوشه بازی + + + + Open Save Data Folder + پوشه ذخیره داده را باز کنید + + + + Open Log Folder + باز کردن پوشه لاگ + + + + Copy info... + ...کپی کردن اطلاعات diff --git a/src/qt_gui/translations/fi.ts b/src/qt_gui/translations/fi.ts index cb7426e01..4d160bf6b 100644 --- a/src/qt_gui/translations/fi.ts +++ b/src/qt_gui/translations/fi.ts @@ -100,11 +100,6 @@ Create Shortcut Create Shortcut - - - Open Game Folder - Open Game Folder - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Trophy Viewer + + + Open Folder... + Avaa Kansio... + + + + Open Game Folder + Avaa Pelikansio + + + + Open Save Data Folder + Avaa Tallennustiedostokansio + + + + Open Log Folder + Avaa Lokikansio + - Copy info - Copy info + Copy info... + Copy info... diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index 4c2d5cbdd..39cd11bf6 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -100,11 +100,6 @@ Create Shortcut Créer un raccourci - - - Open Game Folder - Ouvrir le dossier du jeu - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Visionneuse de trophées + + + Open Folder... + Ouvrir le Dossier... + + + + Open Game Folder + Ouvrir le Dossier du Jeu + + + + Open Save Data Folder + Ouvrir le Dossier des Données de Sauvegarde + + + + Open Log Folder + Ouvrir le Dossier des Logs + - Copy info - Copier infos + Copy info... + Copier infos... diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 633ba9810..a43b8d371 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -62,7 +62,7 @@ Select which directory you want to install to. - Select which directory you want to install to. + Válassza ki a mappát a játékok telepítésére. @@ -90,7 +90,7 @@ The value for location to install games is not valid. - A játékok telepítéséhez megadott érték nem érvényes. + A játékok telepítéséhez megadott útvonal nem érvényes. @@ -100,11 +100,6 @@ Create Shortcut Parancsikon Létrehozása - - - Open Game Folder - Játék Mappa Megnyitása - Cheats / Patches @@ -113,17 +108,37 @@ SFO Viewer - SFO Néző + SFO Nézegető Trophy Viewer Trófeák Megtekintése + + + Open Folder... + Mappa megnyitása... + - Copy info - Információ Másolása + Open Game Folder + Játék Mappa Megnyitása + + + + Open Save Data Folder + Mentési adatok mappa megnyitása + + + + Open Log Folder + Napló mappa megnyitása + + + + Copy info... + Információ Másolása... @@ -143,22 +158,22 @@ Delete... - Delete... + Törlés... Delete Game - Delete Game + Játék törlése Delete Update - Delete Update + Frissítések törlése Delete DLC - Delete DLC + DLC-k törlése @@ -188,27 +203,27 @@ Game - Game + Játék requiresEnableSeparateUpdateFolder_MSG - This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + Ehhez a funkcióhoz szükséges a 'Külön Frissítési Mappa Engedélyezése' opció, hogy működjön. Ha használni akarja, először engedélyezze azt. This game has no update to delete! - This game has no update to delete! + Ehhez a játékhoz nem tartozik törlendő frissítés! Update - Update + Frissítés This game has no DLC to delete! - This game has no DLC to delete! + Ehhez a játékhoz nem tartozik törlendő DLC! @@ -223,7 +238,7 @@ Are you sure you want to delete %1's %2 directory? - Are you sure you want to delete %1's %2 directory? + Biztosan törölni akarja a %1's %2 mappát? @@ -231,7 +246,7 @@ Open/Add Elf Folder - Efl Mappa Megnyitása/Hozzáadása + ELF Mappa Megnyitása/Hozzáadása @@ -241,7 +256,7 @@ Boot Game - Játék Bootolása + Játék Indítása @@ -276,12 +291,12 @@ Exit shadPS4 - Kilépés a shadPS4 + Kilépés a shadPS4-ből Exit the application. - Lépjen ki az programból. + Lépjen ki a programból. @@ -326,7 +341,7 @@ Elf Viewer - Elf Néző + Elf Nézegető @@ -341,12 +356,12 @@ Dump Game List - Játék Lista Dumpolása + Játéklista Dumpolása PKG Viewer - PKG Néző + PKG Nézegető @@ -361,17 +376,17 @@ View - Megnézés + Nézet Game List Icons - Játék Könyvtár Ikonok + Játékkönyvtár Ikonok Game List Mode - Játék Könyvtár Mód + Játékkönyvtár Nézet @@ -475,12 +490,12 @@ Enable Fullscreen - Teljesképernyő Engedélyezése + Teljes Képernyő Engedélyezése Enable Separate Update Folder - Enable Separate Update Folder + Külön Frissítési Mappa Engedélyezése @@ -490,12 +505,12 @@ Is PS4 Pro - PS4 Pro + PS4 Pro mód Enable Discord Rich Presence - Engedélyezze a Discord Rich Presence-t + A Discord Rich Presence engedélyezése @@ -545,7 +560,7 @@ Back Button Behavior - Vissza gomb viselkedése + Vissza gomb Viselkedése @@ -678,17 +693,17 @@ * Unsupported Vulkan Version - * Támogatott Vulkan verzió hiányzik + * Nem támogatott Vulkan verzió Download Cheats For All Installed Games - Letöltés csalások minden telepített játékhoz + Csalások letöltése minden telepített játékhoz Download Patches For All Games - Frissítések letöltése minden játékhoz + Javítások letöltése minden játékhoz @@ -698,17 +713,17 @@ You have downloaded cheats for all the games you have installed. - Csalásokat töltöttél le az összes telepített játékhoz. + Minden elérhető csalás letöltődött az összes telepített játékhoz. Patches Downloaded Successfully! - Frissítések sikeresen letöltve! + Javítások sikeresen letöltve! All Patches available for all games have been downloaded. - Az összes játékhoz elérhető frissítés letöltésre került. + Az összes játékhoz elérhető javítás letöltésre került. @@ -758,7 +773,7 @@ PKG Version %1 is older than installed version: - A %1-es PKG verzió régebbi, mint a telepített verzió: + A(z) %1-es PKG verzió régebbi, mint a telepített verzió: @@ -778,7 +793,7 @@ Would you like to install DLC: %1? - Szeretné telepíteni a DLC-t: %1? + Szeretné telepíteni a %1 DLC-t? @@ -831,7 +846,7 @@ defaultTextEdit_MSG - A csalások/patchek kísérleti jellegűek.\nHasználja őket óvatosan.\n\nTöltse le a csalásokat egyesével a repository kiválasztásával és a letöltés gombra kattintással.\nA Patches fül alatt egyszerre letöltheti az összes patchet, választhat, melyeket szeretné használni, és elmentheti a választását.\n\nMivel nem fejlesztjük a csalásokat/patch-eket,\nkérjük, jelentse a problémákat a csalás szerzőjének.\n\nKészített egy új csalást? Látogasson el ide:\nhttps://github.com/shadps4-emu/ps4_cheats + A csalások/javítások kísérleti jellegűek.\nHasználja őket óvatosan.\n\nTöltse le a csalásokat egyesével a tároló kiválasztásával és a letöltés gombra kattintással.\nA Javítások fül alatt egyszerre letöltheti az összes javítást, majd választhat, melyeket szeretné használni, és elmentheti a választását.\n\nMivel nem mi fejlesztjük a csalásokat/patch-eket,\nkérjük, jelentse a problémákat a csalás szerzőjének.\n\nKészített egy új csalást? Látogasson el ide:\nhttps://github.com/shadps4-emu/ps4_cheats @@ -996,7 +1011,7 @@ CheatsNotFound_MSG - Nincs található csalás ezen a játékverzión ebben a kiválasztott tárolóban,próbálj meg egy másik tárolót vagy a játék egy másik verzióját. + Nincs található csalás ezen a játékverzión ebben a kiválasztott tárolóban, próbálj meg egy másik tárolót vagy a játék egy másik verzióját. @@ -1026,7 +1041,7 @@ DownloadComplete_MSG - Frissítések sikeresen letöltve! Minden elérhető frissítés letöltésre került, nem szükséges egyesével letölteni őket minden játékhoz, mint a csalások esetében. Ha a javítás nem jelenik meg, lehet, hogy nem létezik a játék adott sorozatszámához és verziójához. + Frissítések sikeresen letöltve! Minden elérhető frissítés letöltésre került, nem szükséges egyesével letölteni őket minden játékhoz, mint a csalások esetében. Ha a javítások nem jelennek meg, lehet, hogy nem léteznek a játék adott sorozatszámához és verziójához. @@ -1046,7 +1061,7 @@ The downloaded patch only works on version: %1 - A letöltött javítás csak a(z) %1 verzión működik + A letöltött javításhoz a(z) %1 verzió működik @@ -1081,7 +1096,7 @@ Directory does not exist: - A könyvtár nem létezik: + A mappa nem létezik: @@ -1144,22 +1159,22 @@ separateUpdatesCheckBox - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + Külön Frissítéi Mappa Engedélyezése:\nEngedélyezi a frissítések külön mappába helyezését, a könnyű kezelésük érdekében. showSplashCheckBox - Indító képernyő megjelenítése:\nMegjeleníti a játék indító képernyőjét (különleges képet) a játék elindításakor. + Indítóképernyő megjelenítése:\nMegjeleníti a játék indítóképernyőjét (különleges képet) a játék elindításakor. ps4proCheckBox - PS4 Pro:\nAz emulátort PS4 PRO-ként kezeli, ami engedélyezheti a speciális funkciókat olyan játékokban, amelyek támogatják. + PS4 Pro:\nAz emulátort PS4 PRO-ként kezeli, ami engedélyezhet speciális funkciókat olyan játékokban, amelyek támogatják azt. discordRPCCheckbox - Engedélyezze a Discord Rich Presence-t:\nMegjeleníti az emulator ikonját és a kapcsolódó információkat a Discord profilján. + A Discord Rich Presence engedélyezése:\nMegjeleníti az emulator ikonját és a kapcsolódó információkat a Discord profilján. @@ -1184,17 +1199,17 @@ GUIgroupBox - Játék címzene lejátszása:\nHa a játék támogatja, engedélyezze a speciális zene lejátszását, amikor a játékot kiválasztja a GUI-ban. + Játék címzene lejátszása:\nHa a játék támogatja, engedélyezze egy speciális zene lejátszását, amikor a játékot kiválasztja a GUI-ban. hideCursorGroupBox - Akurátor elrejtése:\nVálassza ki, mikor tűnjön el az egérkurzor:\nSoha: Az egér mindig látható.\nInaktív: Állítson be egy időt, amikor inaktív állapotban eltűnik.\nMindig: soha nem látja az egeret. + Kurzor elrejtése:\nVálassza ki, mikor tűnjön el az egérmutató:\nSoha: Az egér mindig látható.\nInaktív: Állítson be egy időt, amennyi idő mozdulatlanság után eltűnik.\nMindig: az egér mindig el lesz rejtve. idleTimeoutGroupBox - Állítson be egy időt, ameddig az egér inaktív állapot után eltűnik. + Állítson be egy időt, ami után egér inaktív állapotban eltűnik. @@ -1219,17 +1234,17 @@ Touchpad Left - Érintőpad Balra + Érintőpad Bal Touchpad Right - Érintőpad Jobbra + Érintőpad Jobb Touchpad Center - Érintőpad Középen + Érintőpad Közép @@ -1239,7 +1254,7 @@ graphicsAdapterGroupBox - Grafikus eszköz:\nTöbb GPU-s rendszereken válassza ki a GPU-t, amelyet az emulátor használ a legördülő listából,\nvagy válassza az "Auto Select" lehetőséget, hogy automatikusan meghatározza azt. + Grafikus eszköz:\nTöbb GPU-s rendszereken válassza ki, melyik GPU-t használja az emulátor a legördülő listából,\nvagy válassza az "Auto Select" lehetőséget, hogy automatikusan kiválassza azt. @@ -1249,7 +1264,7 @@ heightDivider - Vblank osztó:\nAz emulátor frissítési sebessége e számot megszorozva működik. Ennek megváltoztatása kedvezőtlen hatásokat okozhat, például növelheti a játék sebességét, vagy megszakíthat kritikus játékfunkciókat, amelyek nem számítanak arra, hogy ez megváltozik! + Vblank elosztó:\nAz emulátor frissítési sebessége e számot megszorozva működik. Ennek megváltoztatása kedvezőtlen hatásokat okozhat, például növelheti a játék sebességét, vagy megszakíthat kritikus játékfunkciókat, amelyek nem számítanak arra, hogy ez megváltozik! @@ -1264,7 +1279,7 @@ gameFoldersBox - Játék mappák:\nA mappák listája az telepített játékok ellenőrzésére. + Játék mappák:\nA mappák listája, ahol telepített játékok vannak. @@ -1405,7 +1420,7 @@ Latest Version - Legújabb verzió + Új verzió @@ -1415,7 +1430,7 @@ Show Changelog - Módosítások megjelenítése + Változások megjelenítése @@ -1430,17 +1445,17 @@ No - Nem + Mégse Hide Changelog - Módosítások elrejtése + Változások elrejtése Changes - Módosítások + Változások @@ -1473,4 +1488,4 @@ A frissítési szkript fájl létrehozása nem sikerült - \ No newline at end of file + diff --git a/src/qt_gui/translations/id.ts b/src/qt_gui/translations/id.ts index f841ad3a8..d616f1cf3 100644 --- a/src/qt_gui/translations/id.ts +++ b/src/qt_gui/translations/id.ts @@ -100,11 +100,6 @@ Create Shortcut Create Shortcut - - - Open Game Folder - Open Game Folder - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Trophy Viewer + + + Open Folder... + Buka Folder... + + + + Open Game Folder + Buka Folder Game + + + + Open Save Data Folder + Buka Folder Data Simpanan + + + + Open Log Folder + Buka Folder Log + - Copy info - Copy info + Copy info... + Copy info... diff --git a/src/qt_gui/translations/it.ts b/src/qt_gui/translations/it.ts index b6eb13240..c59289314 100644 --- a/src/qt_gui/translations/it.ts +++ b/src/qt_gui/translations/it.ts @@ -100,11 +100,6 @@ Create Shortcut Crea scorciatoia - - - Open Game Folder - Apri cartella del gioco - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Visualizzatore Trofei + + + Open Folder... + Apri Cartella... + + + + Open Game Folder + Apri Cartella del Gioco + + + + Open Save Data Folder + Apri Cartella dei Dati di Salvataggio + + + + Open Log Folder + Apri Cartella dei Log + - Copy info - Copia informazioni + Copy info... + Copia informazioni... diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index a79b34e2a..f4a4b15ad 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -100,11 +100,6 @@ Create Shortcut ショートカットを作成 - - - Open Game Folder - ゲームフォルダを開く - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer トロフィービューワー + + + Open Folder... + フォルダを開く... + + + + Open Game Folder + ゲームフォルダを開く + + + + Open Save Data Folder + セーブデータフォルダを開く + + + + Open Log Folder + ログフォルダを開く + - Copy info - 情報をコピー + Copy info... + 情報をコピー... diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 6ef89ea24..2fa3ee153 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -100,11 +100,6 @@ Create Shortcut Create Shortcut - - - Open Game Folder - Open Game Folder - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Trophy Viewer + + + Open Folder... + Open Folder... + + + + Open Game Folder + Open Game Folder + + + + Open Save Data Folder + Open Save Data Folder + + + + Open Log Folder + Open Log Folder + - Copy info - Copy info + Copy info... + Copy info... diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index d7fc6e844..16aaf5d86 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -100,11 +100,6 @@ Create Shortcut Create Shortcut - - - Open Game Folder - Open Game Folder - Apgaulės / Pleistrai @@ -120,10 +115,30 @@ Trophy Viewer Trophy Viewer + + + Open Folder... + Atidaryti Katalogą... + + + + Open Game Folder + Atidaryti Žaidimo Katalogą + + + + Open Save Data Folder + Atidaryti Išsaugotų Duomenų Katalogą + + + + Open Log Folder + Atidaryti Žurnalų Katalogą + - Copy info - Copy info + Copy info... + Copy info... diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb_NO.ts similarity index 80% rename from src/qt_gui/translations/nb.ts rename to src/qt_gui/translations/nb_NO.ts index cdcf4d5fb..e02f24182 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -8,7 +8,7 @@ About shadPS4 - About shadPS4 + Om shadPS4 @@ -18,12 +18,12 @@ shadPS4 is an experimental open-source emulator for the PlayStation 4. - shadPS4 is an experimental open-source emulator for the PlayStation 4. + shadPS4 er en eksperimentell åpen kildekode-etterligner for PlayStation 4. This software should not be used to play games you have not legally obtained. - This software should not be used to play games you have not legally obtained. + Denne programvaren skal ikke brukes til å spille spill du ikke har fått lovlig. @@ -31,7 +31,7 @@ Open Folder - Open Folder + Åpne mappe @@ -39,17 +39,17 @@ Loading game list, please wait :3 - Loading game list, please wait :3 + Laster spill-liste, vennligst vent :3 Cancel - Cancel + Avbryt Loading... - Loading... + Laster... @@ -57,12 +57,12 @@ shadPS4 - Choose directory - shadPS4 - Choose directory + shadPS4 - Velg mappe Select which directory you want to install to. - Select which directory you want to install to. + Velg hvilken mappe du vil installere til. @@ -70,27 +70,27 @@ shadPS4 - Choose directory - shadPS4 - Choose directory + shadPS4 - Velg mappe Directory to install games - Directory to install games + Mappe for å installere spill Browse - Browse + Bla gjennom Error - Error + Feil The value for location to install games is not valid. - The value for location to install games is not valid. + Verdien for mappen for å installere spill er ikke gyldig. @@ -98,117 +98,132 @@ Create Shortcut - Create Shortcut - - - - Open Game Folder - Open Game Folder + Lag snarvei Cheats / Patches - Juks / Oppdateringer + Juks / Programrettelse SFO Viewer - SFO Viewer + SFO Viser Trophy Viewer - Trophy Viewer + Trofé Viser + + + + Open Folder... + Åpne Mappen... + + + + Open Game Folder + Åpne Spillmappe + + + + Open Save Data Folder + Åpne Lagrede Data-mappen + + + + Open Log Folder + Åpne Loggmappen - Copy info - Copy info + Copy info... + Kopier info... Copy Name - Copy Name + Kopier Navn Copy Serial - Copy Serial + Kopier Serienummer Copy All - Copy All + Kopier Alle Delete... - Delete... + Slett... Delete Game - Delete Game + Slett Spill Delete Update - Delete Update + Slett Oppdatering Delete DLC - Delete DLC + Slett DLC Shortcut creation - Shortcut creation + Snarvei opprettelse Shortcut created successfully!\n %1 - Shortcut created successfully!\n %1 + Snarvei opprettet!\n %1 Error - Error + Feil Error creating shortcut!\n %1 - Error creating shortcut!\n %1 + Feil ved opprettelse av snarvei!\n %1 Install PKG - Install PKG + Installer PKG Game - Game + Spill requiresEnableSeparateUpdateFolder_MSG - This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + Denne funksjonen krever 'Aktiver seperat oppdateringsmappe' konfigurasjonsalternativet. Hvis du vil bruke denne funksjonen, vennligst aktiver den. This game has no update to delete! - This game has no update to delete! + Dette spillet har ingen oppdatering å slette! Update - Update + Oppdater This game has no DLC to delete! - This game has no DLC to delete! + Dette spillet har ingen DLC å slette! @@ -218,12 +233,12 @@ Delete %1 - Delete %1 + Slett %1 Are you sure you want to delete %1's %2 directory? - Are you sure you want to delete %1's %2 directory? + Er du sikker på at du vil slette %1's %2 directory? @@ -231,17 +246,17 @@ Open/Add Elf Folder - Open/Add Elf Folder + Åpne/Legg til Elf-mappe Install Packages (PKG) - Install Packages (PKG) + Installer Pakker (PKG) Boot Game - Boot Game + Start Spill @@ -251,57 +266,57 @@ About shadPS4 - About shadPS4 + Om shadPS4 Configure... - Configure... + Konfigurer... Install application from a .pkg file - Install application from a .pkg file + Installer fra en .pkg fil Recent Games - Recent Games + Nylige Spill Exit - Exit + Avslutt Exit shadPS4 - Exit shadPS4 + Avslutt shadPS4 Exit the application. - Exit the application. + Avslutt programmet. Show Game List - Show Game List + Vis Spill-listen Game List Refresh - Game List Refresh + Oppdater Spill-listen Tiny - Tiny + Bitteliten Small - Small + Liten @@ -311,82 +326,82 @@ Large - Large + Stor List View - List View + Liste-visning Grid View - Grid View + Rute-visning Elf Viewer - Elf Viewer + Elf-visning Game Install Directory - Game Install Directory + Spillinstallasjons-mappe Download Cheats/Patches - Last ned Juks / Oppdateringer + Last ned Juks /Programrettelse Dump Game List - Dump Game List + Dump Spill-liste PKG Viewer - PKG Viewer + PKG Viser Search... - Search... + Søk... File - File + Fil View - View + Oversikt Game List Icons - Game List Icons + Spill-liste Ikoner Game List Mode - Game List Mode + Spill-liste Modus Settings - Settings + Innstillinger Utils - Utils + Verktøy Themes - Themes + Tema @@ -396,32 +411,32 @@ Dark - Dark + Mørk Light - Light + Lys Green - Green + Grønn Blue - Blue + Blå Violet - Violet + Lilla toolBar - toolBar + Verktøylinje @@ -429,7 +444,7 @@ Open Folder - Open Folder + Åpne Mappe @@ -437,7 +452,7 @@ Trophy Viewer - Trophy Viewer + Trofé Viser @@ -445,12 +460,12 @@ Settings - Settings + Innstillinger General - General + Generell @@ -460,37 +475,37 @@ Console Language - Console Language + Konsollspråk Emulator Language - Emulator Language + Etterlignerspråk Emulator - Emulator + Etterligner Enable Fullscreen - Enable Fullscreen + Aktiver Fullskjerm Enable Separate Update Folder - Enable Separate Update Folder + Aktiver Seperat Oppdateringsmappe Show Splash - Show Splash + Vis Velkomst Is PS4 Pro - Is PS4 Pro + Er PS4 Pro @@ -500,7 +515,7 @@ Username - Username + Brukernavn @@ -525,17 +540,17 @@ Cursor - Markør + Musepeker Hide Cursor - Skjul markør + Skjul musepeker Hide Cursor Idle Timeout - Timeout for å skjule markør ved inaktivitet + Skjul musepeker ved inaktivitet @@ -545,47 +560,47 @@ Back Button Behavior - Tilbakeknapp atferd + Tilbakeknapp Atferd Graphics - Graphics + Grafikk Graphics Device - Graphics Device + Grafikkenhet Width - Width + Bredde Height - Height + Høyde Vblank Divider - Vblank Divider + Vblank Skillelinje Advanced - Advanced + Avansert Enable Shaders Dumping - Enable Shaders Dumping + Aktiver Skyggelegger Dumping Enable NULL GPU - Enable NULL GPU + Aktiver NULL GPU @@ -610,27 +625,27 @@ Debug - Debug + Feilretting Enable Debug Dumping - Enable Debug Dumping + Aktiver Feilretting Dumping Enable Vulkan Validation Layers - Enable Vulkan Validation Layers + Aktiver Vulkan Valideringslag Enable Vulkan Synchronization Validation - Enable Vulkan Synchronization Validation + Aktiver Vulkan Synkroniseringslag Enable RenderDoc Debugging - Enable RenderDoc Debugging + Aktiver RenderDoc Feilretting @@ -673,17 +688,17 @@ Game List - Spilliste + Spill-liste * Unsupported Vulkan Version - * Ikke støttet Vulkan-versjon + * Ustøttet Vulkan-versjon Download Cheats For All Installed Games - Last ned jukser for alle installerte spill + Last ned juks for alle installerte spill @@ -698,17 +713,17 @@ You have downloaded cheats for all the games you have installed. - Du har lastet ned jukser for alle spillene du har installert. + Du har lastet ned juks for alle spillene du har installert. Patches Downloaded Successfully! - Oppdateringer lastet ned vellykket! + Programrettelser lastet ned vellykket! All Patches available for all games have been downloaded. - Alle oppdateringer tilgjengelige for alle spillene har blitt lastet ned. + Programrettelser tilgjengelige for alle spill har blitt lastet ned. @@ -743,7 +758,7 @@ Patch detected! - Oppdatering oppdaget! + Programrettelse oppdaget! @@ -768,7 +783,7 @@ Would you like to install Patch: - Ønsker du å installere oppdateringen: + Ønsker du å installere programrettelsen: @@ -803,12 +818,12 @@ Extracting PKG %1/%2 - Ekstraherer PKG %1/%2 + Pakker ut PKG %1/%2 Extraction Finished - Ekstrahering fullført + Utpakking fullført @@ -818,7 +833,7 @@ File doesn't appear to be a valid PKG file - Fil ser ikke ut til å være en gyldig PKG-fil + Filen ser ikke ut til å være en gyldig PKG-fil @@ -826,12 +841,12 @@ Cheats / Patches - Jukser / Oppdateringer + Juks / Programrettelse defaultTextEdit_MSG - Cheats/Patches er eksperimentelle.\nBruk med forsiktighet.\n\nLast ned cheats individuelt ved å velge depotet og klikke på nedlastingsknappen.\nPå fanen Patches kan du laste ned alle patches samtidig, velge hvilke du ønsker å bruke, og lagre valget ditt.\n\nSiden vi ikke utvikler Cheats/Patches,\nvær vennlig å rapportere problemer til cheat-utvikleren.\n\nHar du laget en ny cheat? Besøk:\nhttps://github.com/shadps4-emu/ps4_cheats + Juks/programrettelse er eksperimentelle.\nBruk med forsiktighet.\n\nLast ned juks individuelt ved å velge pakkebrønn og klikke på nedlastingsknappen.\nPå fanen programrettelse kan du laste ned alle programrettelser samtidig, velge hvilke du ønsker å bruke, og lagre valget ditt.\n\nSiden vi ikke utvikler Juksene/Programrettelsene,\nvær vennlig å rapportere problemer til juks-utvikleren.\n\nHar du laget en ny juks? Besøk:\nhttps://github.com/shadps4-emu/ps4_cheats @@ -861,12 +876,12 @@ Repository: - Depot: + Pakkebrønn: Download Cheats - Last ned jukser + Last ned juks @@ -891,12 +906,12 @@ Select Patch File: - Velg oppdateringsfil: + Velg programrettelse-filen: Download Patches - Last ned oppdateringer + Last ned programrettelse @@ -911,7 +926,7 @@ Patches - Oppdateringer + Programrettelse @@ -921,7 +936,7 @@ No patch selected. - Ingen oppdatering valgt. + Ingen programrettelse valgt. @@ -931,7 +946,7 @@ No patch file found for the current serial. - Ingen oppdateringsfil funnet for det aktuelle serienummeret. + Ingen programrettelse-fil funnet for det aktuelle serienummeret. @@ -946,7 +961,7 @@ Failed to parse XML: - Feil ved parsing av XML: + Feil ved tolkning av XML: @@ -971,12 +986,12 @@ File Exists - Fil eksisterer + Filen eksisterer File already exists. Do you want to replace it? - Fil eksisterer allerede. Ønsker du å erstatte den? + Filen eksisterer allerede. Ønsker du å erstatte den? @@ -996,7 +1011,7 @@ CheatsNotFound_MSG - Ingen jukser funnet for dette spillet i denne versjonen av det valgte depotet,prøv et annet depot eller en annen versjon av spillet. + Ingen juks funnet for dette spillet i denne versjonen av den valgte pakkebrønnen,prøv en annen pakkebrønn eller en annen versjon av spillet. @@ -1006,7 +1021,7 @@ CheatsDownloadedSuccessfully_MSG - Du har lastet ned jukser vellykket for denne versjonen av spillet fra det valgte depotet. Du kan prøve å laste ned fra et annet depot, hvis det er tilgjengelig, vil det også være mulig å bruke det ved å velge filen fra listen. + Du har lastet ned jukser vellykket for denne versjonen av spillet fra den valgte pakkebrønnen. Du kan prøve å laste ned fra en annen pakkebrønn, hvis det er tilgjengelig, vil det også være mulig å bruke det ved å velge filen fra listen. @@ -1026,7 +1041,7 @@ DownloadComplete_MSG - Oppdateringer lastet ned vellykket! Alle oppdateringer tilgjengelige for alle spill har blitt lastet ned, det er ikke nødvendig å laste dem ned individuelt for hvert spill som skjer med jukser. Hvis oppdateringen ikke vises, kan det hende at den ikke finnes for den spesifikke serienummeret og versjonen av spillet. + Oppdateringer lastet ned vellykket! Alle programrettelsene tilgjengelige for alle spill har blitt lastet ned, det er ikke nødvendig å laste dem ned individuelt for hvert spill som skjer med jukser. Hvis programrettelsen ikke vises, kan det hende at den ikke finnes for den spesifikke serienummeret og versjonen av spillet. @@ -1046,7 +1061,7 @@ The downloaded patch only works on version: %1 - Den nedlastede patchen fungerer bare på versjon: %1 + Den nedlastede programrettelsen fungerer bare på versjon: %1 @@ -1056,7 +1071,7 @@ Incompatibility Notice - Inkompatibilitetsvarsel + Inkompatibilitets-varsel @@ -1081,7 +1096,7 @@ Directory does not exist: - Direktory eksisterer ikke: + Mappen eksisterer ikke: @@ -1104,7 +1119,7 @@ Save - Lag + Lagre @@ -1124,7 +1139,7 @@ Point your mouse at an option to display its description. - Hold musen over et valg for at vise beskrivelsen. + Pek musen over et alternativ for å vise beskrivelsen. @@ -1134,7 +1149,7 @@ emulatorLanguageGroupBox - Emulatorspråk:\nAngir språket for emulatorens brukergrensesnitt. + Etterlignerspråket:\nAngir språket for etterlignerens brukergrensesnitt. @@ -1144,7 +1159,7 @@ separateUpdatesCheckBox - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + Aktiver separat oppdateringsmappe:\nAktiverer installering av spill i en egen mappe for enkel administrasjon. @@ -1154,12 +1169,12 @@ ps4proCheckBox - Er PS4 Pro:\nFår emulatoren til å fungere som en PS4 PRO, noe som kan aktivere spesielle funksjoner i spill som støtter det. + Er PS4 Pro:\nFår emulatoren til å fungere som en PS4 PRO, noe som kan aktivere spesielle funksjoner i spill som støtter dette. discordRPCCheckbox - Aktiver Discord Rich Presence:\nViser emulatorikonet og relevant informasjon på Discord-profilen din. + Aktiver Discord Rich Presence:\nViser etterlignerikonet og relevant informasjon på Discord-profilen din. @@ -1169,7 +1184,7 @@ logTypeGroupBox - Logtype:\nAngir om loggvinduets utdata skal synkroniseres for ytelse. Kan ha negative effekter på emulering. + Logtype:\nAngir om loggvinduets utdata skal synkroniseres for ytelse. Kan ha negative effekter for etterligneren. @@ -1184,17 +1199,17 @@ GUIgroupBox - Spille tittelmusikk:\nHvis et spill støtter det, aktiverer det å spille spesiell musikk når du velger spillet i GUI. + Spille tittelmusikk:\nHvis et spill støtter det, aktiverer spesiell musikk når du velger spillet i menyen. hideCursorGroupBox - Skjul musepeker:\nVelg når musepekeren skal forsvinne:\nAldri: Du vil alltid se musen.\nInaktiv: Sett en tid for at den skal forsvinne etter å ha vært inaktiv.\nAlltid: du vil aldri se musen. + Skjul musepeker:\nVelg når musepekeren skal forsvinne:\nAldri: Du vil alltid se musepekeren.\nInaktiv: Sett en tid for at den skal forsvinne etter å ha vært inaktiv.\nAlltid: du vil aldri se musepekeren. idleTimeoutGroupBox - Sett en tid for når musen forsvinner etter å ha vært inaktiv. + Sett en tid for når musepekeren forsvinner etter å ha vært inaktiv. @@ -1239,27 +1254,27 @@ graphicsAdapterGroupBox - Grafikkdevice:\nI systemer med flere GPU-er, velg GPU-en emulatoren skal bruke fra rullegardinlisten,\neller velg "Auto Select" for å bestemme det automatisk. + Grafikkenhet:\nI systemer med flere GPU-er, velg GPU-en etterligneren skal bruke fra rullegardinlisten,\neller velg "Auto Select" for å bestemme det automatisk. resolutionLayout - Bredde/Høyde:\nAngir størrelsen på emulatorkvinduet ved oppstart, som kan endres under spillingen.\nDette er forskjellig fra oppløsningen i spillet. + Bredde/Høyde:\nAngir størrelsen på etterlignerkvinduet ved oppstart, som kan endres under spillingen.\nDette er forskjellig fra oppløsningen i spillet. heightDivider - Vblank divider:\nBilderaten som emulatoren oppdaterer ved, multipliseres med dette tallet. Endring av dette kan ha negative effekter, som å øke hastigheten på spillet, eller ødelegge kritisk spillfunksjonalitet som ikke forventer at dette endres! + Vblank Skillelinje:\nBildehastigheten som etterligneren oppdaterer ved, multipliseres med dette tallet. Endring av dette kan ha negative effekter, som å øke hastigheten på spillet, eller ødelegge kritisk spillfunksjonalitet som ikke forventer at dette endres! dumpShadersCheckBox - Aktiver shaderdumping:\nFor teknisk feilsøking lagrer shaderne fra spillet i en mappe mens de gjengis. + Aktiver skyggelegger-dumping:\nFor teknisk feilsøking lagrer skyggeleggene fra spillet i en mappe mens de gjengis. nullGpuCheckBox - Aktiver Null GPU:\nFor teknisk feilsøking deaktiverer spillrendering som om det ikke var noe grafikkort. + Aktiver Null GPU:\nFor teknisk feilsøking deaktiverer spillgjengivelse som om det ikke var noe grafikkort. @@ -1279,22 +1294,22 @@ debugDump - Aktiver feilsøking dumping:\nLagrer import- og eksport-symbolene og filoverskriftsinformasjonen til det nåværende kjørende PS4-programmet i en katalog. + Aktiver dumping av feilsøking:\nLagrer import- og eksport-symbolene og filoverskriftsinformasjonen til det nåværende kjørende PS4-programmet i en katalog. vkValidationCheckBox - Aktiver Vulkan valideringslag:\nAktiverer et system som validerer tilstanden til Vulkan-rendereren og logger informasjon om dens indre tilstand. Dette vil redusere ytelsen og sannsynligvis endre emuleringens oppførsel. + Aktiver Vulkan valideringslag:\nAktiverer et system som validerer tilstanden til Vulkan-gjengiveren og logger informasjon om dens indre tilstand. Dette vil redusere ytelsen og sannsynligvis endre etterlignerens oppførsel. vkSyncValidationCheckBox - Aktiver Vulkan synkronisering validering:\nAktiverer et system som validerer timingen av Vulkan-renderingsoppgaver. Dette vil redusere ytelsen og sannsynligvis endre emuleringens oppførsel. + Aktiver Vulkan synkronisering validering:\nAktiverer et system som validerer frekvens tiden av Vulkan-gjengivelsensoppgaver. Dette vil redusere ytelsen og sannsynligvis endre etterlignerens oppførsel. rdocCheckBox - Aktiver RenderDoc feilsøking:\nHvis aktivert, vil emulatoren gi kompatibilitet med Renderdoc for å tillate opptak og analyse av det nåværende renderte bildet. + Aktiver RenderDoc feilsøking:\nHvis aktivert, vil etterligneren gi kompatibilitet med Renderdoc for å tillate opptak og analyse av det nåværende gjengitte bildet. @@ -1322,7 +1337,7 @@ Firmware - Firmware + Fastvare @@ -1473,4 +1488,4 @@ Kunne ikke opprette oppdateringsskriptfilen - \ No newline at end of file + diff --git a/src/qt_gui/translations/nl.ts b/src/qt_gui/translations/nl.ts index 380d90705..b0cfaff5e 100644 --- a/src/qt_gui/translations/nl.ts +++ b/src/qt_gui/translations/nl.ts @@ -100,11 +100,6 @@ Create Shortcut Create Shortcut - - - Open Game Folder - Open Game Folder - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Trophy Viewer + + + Open Folder... + Map openen... + + + + Open Game Folder + Open Spelmap + + + + Open Save Data Folder + Open Map voor Opslagdata + + + + Open Log Folder + Open Logmap + - Copy info - Copy info + Copy info... + Copy info... diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 5d211734e..4d11c13f6 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -100,11 +100,6 @@ Create Shortcut Utwórz skrót - - - Open Game Folder - Otwórz katalog gry - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Menedżer trofeów + + + Open Folder... + Otwórz Folder... + + + + Open Game Folder + Otwórz Katalog Gry + + + + Open Save Data Folder + Otwórz Folder Danych Zapisów + + + + Open Log Folder + Otwórz Folder Dziennika + - Copy info - Kopiuj informacje + Copy info... + Kopiuj informacje... diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index eb79fade4..f1d3631d8 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -100,11 +100,6 @@ Create Shortcut Criar Atalho - - - Open Game Folder - Abrir Pasta do Jogo - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Visualizador de Troféu + + + Open Folder... + Abrir Pasta... + + + + Open Game Folder + Abrir Pasta do Jogo + + + + Open Save Data Folder + Abrir Pasta de Save + + + + Open Log Folder + Abrir Pasta de Log + - Copy info - Copiar informação + Copy info... + Copiar informação... diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 603cd3a24..fff0bcddb 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -100,11 +100,6 @@ Create Shortcut Create Shortcut - - - Open Game Folder - Open Game Folder - Trapaças / Patches @@ -120,10 +115,30 @@ Trophy Viewer Trophy Viewer + + + Open Folder... + Deschide Folder... + + + + Open Game Folder + Deschide Folder Joc + + + + Open Save Data Folder + Deschide Folder Date Salvate + + + + Open Log Folder + Deschide Folder Jurnal + - Copy info - Copy info + Copy info... + Copy info... diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 4c58786c4..052623235 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -100,11 +100,6 @@ Create Shortcut Создать ярлык - - - Open Game Folder - Открыть папку с игрой - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Просмотр трофеев + + + Open Folder... + Открыть Папку... + + + + Open Game Folder + Открыть папку с игрой + + + + Open Save Data Folder + Открыть Папку Сохранений + + + + Open Log Folder + Открыть Папку Логов + - Copy info - Копировать информацию + Copy info... + Копировать информацию... diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts index 00fd5cb48..5715371bf 100644 --- a/src/qt_gui/translations/sq.ts +++ b/src/qt_gui/translations/sq.ts @@ -100,11 +100,6 @@ Create Shortcut Krijo Shkurtore - - - Open Game Folder - Hap Dosjen e Lojës - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Shikuesi i Trofeve + + + Open Folder... + Hapni Dosjen... + + + + Open Game Folder + Hapni Dosjen e Lojës + + + + Open Save Data Folder + Hapni Dosjen e të Dhënave të Ruajtura + + + + Open Log Folder + Hapni Dosjen e Regjistrimeve + - Copy info - Kopjo informacionin + Copy info... + Kopjo informacionin... diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 6c4913603..335465778 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -100,11 +100,6 @@ Create Shortcut Kısayol Oluştur - - - Open Game Folder - Oyun Klasörünü Aç - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Kupa Görüntüleyici + + + Open Folder... + Klasörü Aç... + + + + Open Game Folder + Oyun Klasörünü Aç + + + + Open Save Data Folder + Kaydetme Verileri Klasörünü Aç + + + + Open Log Folder + Log Klasörünü Aç + - Copy info - Bilgiyi Kopyala + Copy info... + Bilgiyi Kopyala... diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 61c884986..31bfe9dba 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -100,11 +100,6 @@ Create Shortcut Створити Ярлик - - - Open Game Folder - Відкрити папку з грою - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Перегляд трофеїв + + + Open Folder... + Відкрити Папку... + + + + Open Game Folder + Відкрити папку з грою + + + + Open Save Data Folder + Відкрити Папку Збережених Даних + + + + Open Log Folder + Відкрити Папку Логів + - Copy info - Копіювати інформацію + Copy info... + Копіювати інформацію... diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 5fca6b6b5..223cb9ed0 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -100,11 +100,6 @@ Create Shortcut Create Shortcut - - - Open Game Folder - Open Game Folder - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Trophy Viewer + + + Open Folder... + Mở Thư Mục... + + + + Open Game Folder + Mở Thư Mục Trò Chơi + + + + Open Save Data Folder + Mở Thư Mục Dữ Liệu Lưu + + + + Open Log Folder + Mở Thư Mục Nhật Ký + - Copy info - Copy info + Copy info... + Copy info... diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index bfcbbaa98..4fe1f7c42 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -100,11 +100,6 @@ Create Shortcut 创建快捷方式 - - - Open Game Folder - 打开游戏文件夹 - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Trophy 查看器 + + + Open Folder... + 打开文件夹... + + + + Open Game Folder + 打开游戏文件夹 + + + + Open Save Data Folder + 打开保存数据文件夹 + + + + Open Log Folder + 打开日志文件夹 + - Copy info - 复制信息 + Copy info... + 复制信息... diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 84b32b7a5..4db00775d 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -100,11 +100,6 @@ Create Shortcut Create Shortcut - - - Open Game Folder - Open Game Folder - Cheats / Patches @@ -120,10 +115,30 @@ Trophy Viewer Trophy Viewer + + + Open Folder... + 打開資料夾... + + + + Open Game Folder + 打開遊戲資料夾 + + + + Open Save Data Folder + 打開存檔資料夾 + + + + Open Log Folder + 打開日誌資料夾 + - Copy info - Copy info + Copy info... + Copy info... diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index 586785e6a..7d29c845d 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -690,9 +690,9 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip IR::Inst* body = inst.Arg(1).InstRecursive(); const auto [coords, arg] = [&] -> std::pair { switch (image.GetType()) { - case AmdGpu::ImageType::Color1D: // x + case AmdGpu::ImageType::Color1D: // x, [lod] return {body->Arg(0), body->Arg(1)}; - case AmdGpu::ImageType::Color1DArray: // x, slice + case AmdGpu::ImageType::Color1DArray: // x, slice, [lod] [[fallthrough]]; case AmdGpu::ImageType::Color2D: // x, y, [lod] [[fallthrough]]; @@ -703,9 +703,9 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip case AmdGpu::ImageType::Color2DMsaaArray: // x, y, slice. (sample is passed on different // argument) [[fallthrough]]; - case AmdGpu::ImageType::Color3D: // x, y, z + case AmdGpu::ImageType::Color3D: // x, y, z, [lod] return {ir.CompositeConstruct(body->Arg(0), body->Arg(1), body->Arg(2)), body->Arg(3)}; - case AmdGpu::ImageType::Cube: // x, y, face + case AmdGpu::ImageType::Cube: // x, y, face, [lod] return {PatchCubeCoord(ir, body->Arg(0), body->Arg(1), body->Arg(2), is_storage, inst_info.is_array), body->Arg(3)}; @@ -717,8 +717,8 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip if (inst_info.has_lod) { ASSERT(inst.GetOpcode() == IR::Opcode::ImageFetch); - ASSERT(image.GetType() == AmdGpu::ImageType::Color2D || - image.GetType() == AmdGpu::ImageType::Color2DArray); + ASSERT(image.GetType() != AmdGpu::ImageType::Color2DMsaa && + image.GetType() != AmdGpu::ImageType::Color2DMsaaArray); inst.SetArg(3, arg); } else if (image.GetType() == AmdGpu::ImageType::Color2DMsaa || image.GetType() == AmdGpu::ImageType::Color2DMsaaArray) { diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index c368f2101..da098fa37 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -322,7 +322,7 @@ bool PipelineCache::RefreshGraphicsKey() { switch (regs.stage_enable.raw) { case Liverpool::ShaderStageEnable::VgtStages::EsGs: { if (!instance.IsGeometryStageSupported() || !IsGsFeaturesSupported()) { - break; + return false; } if (!TryBindStageRemap(Shader::Stage::Export, Shader::Stage::Vertex)) { return false;
shadPS4 is an early PlayStation 4 emulator for Windows, Linux and macOS written in C++.
The emulator is still early in development, so don't expect a flawless experience. Nonetheless, the emulator can already run a number of commercial games.