Merge branch 'main' into main

This commit is contained in:
delledev 2024-10-06 18:24:54 +03:00 committed by GitHub
commit e7179fc328
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
158 changed files with 21534 additions and 1337 deletions

View File

@ -14,12 +14,10 @@ export PATH="$Qt6_DIR/bin:$PATH"
wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh
wget -q https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gstreamer/master/linuxdeploy-plugin-gstreamer.sh
chmod a+x linuxdeploy-x86_64.AppImage chmod a+x linuxdeploy-x86_64.AppImage
chmod a+x linuxdeploy-plugin-qt-x86_64.AppImage chmod a+x linuxdeploy-plugin-qt-x86_64.AppImage
chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh
chmod a+x linuxdeploy-plugin-gstreamer.sh
# Build AppImage # Build AppImage
./linuxdeploy-x86_64.AppImage --appdir AppDir ./linuxdeploy-x86_64.AppImage --appdir AppDir
@ -27,5 +25,7 @@ chmod a+x linuxdeploy-plugin-gstreamer.sh
cp -a "$GITHUB_WORKSPACE/build/translations" AppDir/usr/bin 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 --plugin gstreamer --output appimage ./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
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

View File

@ -3,11 +3,11 @@
name: Build and Release name: Build and Release
on: on: [push, pull_request]
push:
branches: [ "main" ] concurrency:
pull_request: group: ci-${{ github.event_name }}-${{ github.ref }}
branches: [ "*" ] cancel-in-progress: ${{ github.event_name == 'push' }}
env: env:
BUILD_TYPE: Release BUILD_TYPE: Release
@ -287,7 +287,7 @@ jobs:
submodules: recursive submodules: recursive
- name: Install dependencies - name: Install dependencies
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential libasound2-dev libpulse-dev libopenal-dev
- name: Cache CMake Configuration - name: Cache CMake Configuration
uses: actions/cache@v4 uses: actions/cache@v4
@ -343,7 +343,7 @@ jobs:
submodules: recursive submodules: recursive
- name: Install dependencies - name: Install dependencies
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev
- name: Cache CMake Configuration - name: Cache CMake Configuration
uses: actions/cache@v4 uses: actions/cache@v4
@ -382,7 +382,7 @@ jobs:
path: Shadps4-qt.AppImage path: Shadps4-qt.AppImage
pre-release: pre-release:
if: github.ref == 'refs/heads/main' && github.event_name == 'push' if: github.ref == 'refs/heads/main' && github.repository == 'shadps4-emu/shadPS4' && github.event_name == 'push'
needs: [get-info, windows-sdl, windows-qt, macos-sdl, macos-qt, linux-sdl, linux-qt] needs: [get-info, windows-sdl, windows-qt, macos-sdl, macos-qt, linux-sdl, linux-qt]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

View File

@ -8,7 +8,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED True)
if(APPLE) if(APPLE)
enable_language(OBJC) enable_language(OBJC)
set(CMAKE_OSX_DEPLOYMENT_TARGET 11) set(CMAKE_OSX_DEPLOYMENT_TARGET 14)
endif() endif()
if (NOT CMAKE_BUILD_TYPE) if (NOT CMAKE_BUILD_TYPE)
@ -338,6 +338,19 @@ set(MISC_LIBS src/core/libraries/screenshot/screenshot.cpp
src/core/libraries/screenshot/screenshot.h src/core/libraries/screenshot/screenshot.h
) )
set(DEV_TOOLS src/core/devtools/layer.cpp
src/core/devtools/layer.h
src/core/devtools/gcn/gcn_context_regs.cpp
src/core/devtools/gcn/gcn_op_names.cpp
src/core/devtools/gcn/gcn_shader_regs.cpp
src/core/devtools/widget/cmd_list.cpp
src/core/devtools/widget/cmd_list.h
src/core/devtools/widget/frame_dump.cpp
src/core/devtools/widget/frame_dump.h
src/core/devtools/widget/frame_graph.cpp
src/core/devtools/widget/frame_graph.h
)
set(COMMON src/common/logging/backend.cpp set(COMMON src/common/logging/backend.cpp
src/common/logging/backend.h src/common/logging/backend.h
src/common/logging/filter.cpp src/common/logging/filter.cpp
@ -451,6 +464,9 @@ set(CORE src/core/aerolib/stubs.cpp
${USBD_LIB} ${USBD_LIB}
${MISC_LIBS} ${MISC_LIBS}
${DIALOGS_LIB} ${DIALOGS_LIB}
${DEV_TOOLS}
src/core/debug_state.cpp
src/core/debug_state.h
src/core/linker.cpp src/core/linker.cpp
src/core/linker.h src/core/linker.h
src/core/memory.cpp src/core/memory.cpp
@ -504,6 +520,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/exception.h
src/shader_recompiler/frontend/translate/data_share.cpp src/shader_recompiler/frontend/translate/data_share.cpp
src/shader_recompiler/frontend/translate/export.cpp src/shader_recompiler/frontend/translate/export.cpp
src/shader_recompiler/frontend/translate/scalar_alu.cpp src/shader_recompiler/frontend/translate/scalar_alu.cpp
src/shader_recompiler/frontend/translate/scalar_flow.cpp
src/shader_recompiler/frontend/translate/scalar_memory.cpp src/shader_recompiler/frontend/translate/scalar_memory.cpp
src/shader_recompiler/frontend/translate/translate.cpp src/shader_recompiler/frontend/translate/translate.cpp
src/shader_recompiler/frontend/translate/translate.h src/shader_recompiler/frontend/translate/translate.h
@ -512,6 +529,8 @@ set(SHADER_RECOMPILER src/shader_recompiler/exception.h
src/shader_recompiler/frontend/translate/vector_memory.cpp src/shader_recompiler/frontend/translate/vector_memory.cpp
src/shader_recompiler/frontend/control_flow_graph.cpp src/shader_recompiler/frontend/control_flow_graph.cpp
src/shader_recompiler/frontend/control_flow_graph.h src/shader_recompiler/frontend/control_flow_graph.h
src/shader_recompiler/frontend/copy_shader.cpp
src/shader_recompiler/frontend/copy_shader.h
src/shader_recompiler/frontend/decode.cpp src/shader_recompiler/frontend/decode.cpp
src/shader_recompiler/frontend/decode.h src/shader_recompiler/frontend/decode.h
src/shader_recompiler/frontend/fetch_shader.cpp src/shader_recompiler/frontend/fetch_shader.cpp
@ -528,6 +547,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/exception.h
src/shader_recompiler/ir/passes/ir_passes.h src/shader_recompiler/ir/passes/ir_passes.h
src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp
src/shader_recompiler/ir/passes/resource_tracking_pass.cpp src/shader_recompiler/ir/passes/resource_tracking_pass.cpp
src/shader_recompiler/ir/passes/ring_access_elimination.cpp
src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp
src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp
src/shader_recompiler/ir/abstract_syntax_list.h src/shader_recompiler/ir/abstract_syntax_list.h
@ -560,6 +580,7 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
src/video_core/amdgpu/pm4_cmds.h src/video_core/amdgpu/pm4_cmds.h
src/video_core/amdgpu/pm4_opcodes.h src/video_core/amdgpu/pm4_opcodes.h
src/video_core/amdgpu/resource.h src/video_core/amdgpu/resource.h
src/video_core/amdgpu/types.h
src/video_core/amdgpu/default_context.cpp src/video_core/amdgpu/default_context.cpp
src/video_core/buffer_cache/buffer.cpp src/video_core/buffer_cache/buffer.cpp
src/video_core/buffer_cache/buffer.h src/video_core/buffer_cache/buffer.h
@ -625,8 +646,6 @@ set(IMGUI src/imgui/imgui_config.h
src/imgui/imgui_layer.h src/imgui/imgui_layer.h
src/imgui/imgui_std.h src/imgui/imgui_std.h
src/imgui/imgui_texture.h src/imgui/imgui_texture.h
src/imgui/layer/video_info.cpp
src/imgui/layer/video_info.h
src/imgui/renderer/imgui_core.cpp src/imgui/renderer/imgui_core.cpp
src/imgui/renderer/imgui_core.h src/imgui/renderer/imgui_core.h
src/imgui/renderer/imgui_impl_sdl3.cpp src/imgui/renderer/imgui_impl_sdl3.cpp
@ -725,7 +744,7 @@ endif()
create_target_directory_groups(shadps4) create_target_directory_groups(shadps4)
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn)
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3 pugixml::pugixml) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3 pugixml::pugixml)
target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h")

View File

@ -72,7 +72,7 @@ Check the build instructions for [**Linux**](https://github.com/shadps4-emu/shad
Check the build instructions for [**macOS**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-macos.md). Check the build instructions for [**macOS**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-macos.md).
> [!IMPORTANT] > [!IMPORTANT]
> macOS users need at least macOS 15 on Apple Silicon-based Mac devices and at least macOS 11 on Intel-based Mac devices. > macOS users need at least macOS 15 on Apple Silicon-based Mac devices and at least macOS 14 on Intel-based Mac devices.
# Debugging and reporting issues # Debugging and reporting issues

View File

@ -73,3 +73,15 @@ path = "src/imgui/renderer/fonts/NotoSansJP-Regular.ttf"
precedence = "aggregate" precedence = "aggregate"
SPDX-FileCopyrightText = "2012 Google Inc. All Rights Reserved." SPDX-FileCopyrightText = "2012 Google Inc. All Rights Reserved."
SPDX-License-Identifier = "OFL-1.1" SPDX-License-Identifier = "OFL-1.1"
[[annotations]]
path = "src/imgui/renderer/fonts/ProggyVector-Regular.ttf"
precedence = "aggregate"
SPDX-FileCopyrightText = "Copyright (c) 2004, 2005 Tristan Grimmer"
SPDX-License-Identifier = "MIT"
[[annotations]]
path = "externals/gcn/include/**"
SPDX-FileCopyrightText = "NONE"
SPDX-License-Identifier = "CC0-1.0"

View File

@ -14,7 +14,7 @@ sudo apt install build-essential clang git cmake libasound2-dev libpulse-dev lib
#### Fedora #### Fedora
``` ```
sudo dnf install clang cmake libatomic alsa-lib-devel pipewire-jack-audio-connection-kit-devel openal-devel openssl-devel libevdev-devel libudev-devel qt6-qtbase-devel qt6-qtbase-private-devel qt6-qtmultimedia-devel qt6-qtsvg-devel qt6-qttools-devel vulkan-devel vulkan-validation-layers sudo dnf install clang git cmake libatomic alsa-lib-devel pipewire-jack-audio-connection-kit-devel openal-devel openssl-devel libevdev-devel libudev-devel libXext-devel qt6-qtbase-devel qt6-qtbase-private-devel qt6-qtmultimedia-devel qt6-qtsvg-devel qt6-qttools-devel vulkan-devel vulkan-validation-layers
``` ```
#### Arch Linux #### Arch Linux

View File

@ -188,3 +188,6 @@ endif()
set(BUILD_EXAMPLES OFF) set(BUILD_EXAMPLES OFF)
add_subdirectory(discord-rpc/) add_subdirectory(discord-rpc/)
target_include_directories(discord-rpc INTERFACE discord-rpc/include) target_include_directories(discord-rpc INTERFACE discord-rpc/include)
# GCN Headers
add_subdirectory(gcn)

8
externals/gcn/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
project(gcn LANGUAGES CXX)
add_library(gcn dummy.cpp)
target_include_directories(gcn INTERFACE include)

2
externals/gcn/dummy.cpp vendored Normal file
View File

@ -0,0 +1,2 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,117 @@
/*
***********************************************************************************************************************
*
* Copyright (c) 2015-2021 Advanced Micro Devices, Inc. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
**********************************************************************************************************************/
#ifndef PM4_IT_OPCODES_H
#define PM4_IT_OPCODES_H
enum IT_OpCodeType {
IT_NOP = 0x10,
IT_SET_BASE = 0x11,
IT_CLEAR_STATE = 0x12,
IT_INDEX_BUFFER_SIZE = 0x13,
IT_DISPATCH_DIRECT = 0x15,
IT_DISPATCH_INDIRECT = 0x16,
IT_ATOMIC_GDS = 0x1D,
IT_ATOMIC = 0x1E,
IT_OCCLUSION_QUERY = 0x1F,
IT_SET_PREDICATION = 0x20,
IT_REG_RMW = 0x21,
IT_COND_EXEC = 0x22,
IT_PRED_EXEC = 0x23,
IT_DRAW_INDIRECT = 0x24,
IT_DRAW_INDEX_INDIRECT = 0x25,
IT_INDEX_BASE = 0x26,
IT_DRAW_INDEX_2 = 0x27,
IT_CONTEXT_CONTROL = 0x28,
IT_INDEX_TYPE = 0x2A,
IT_DRAW_INDIRECT_MULTI = 0x2C,
IT_DRAW_INDEX_AUTO = 0x2D,
IT_NUM_INSTANCES = 0x2F,
IT_DRAW_INDEX_MULTI_AUTO = 0x30,
IT_INDIRECT_BUFFER_CNST = 0x33,
IT_STRMOUT_BUFFER_UPDATE = 0x34,
IT_DRAW_INDEX_OFFSET_2 = 0x35,
IT_WRITE_DATA = 0x37,
IT_DRAW_INDEX_INDIRECT_MULTI = 0x38,
IT_MEM_SEMAPHORE = 0x39,
IT_COPY_DW__SI__CI = 0x3B,
IT_WAIT_REG_MEM = 0x3C,
IT_INDIRECT_BUFFER = 0x3F,
IT_COND_INDIRECT_BUFFER = 0x3F,
IT_COPY_DATA = 0x40,
IT_CP_DMA = 0x41,
IT_PFP_SYNC_ME = 0x42,
IT_SURFACE_SYNC = 0x43,
IT_COND_WRITE = 0x45,
IT_EVENT_WRITE = 0x46,
IT_EVENT_WRITE_EOP = 0x47,
IT_EVENT_WRITE_EOS = 0x48,
IT_PREAMBLE_CNTL = 0x4A,
IT_CONTEXT_REG_RMW = 0x51,
IT_LOAD_SH_REG = 0x5F,
IT_LOAD_CONFIG_REG = 0x60,
IT_LOAD_CONTEXT_REG = 0x61,
IT_SET_CONFIG_REG = 0x68,
IT_SET_CONTEXT_REG = 0x69,
IT_SET_CONTEXT_REG_INDIRECT = 0x73,
IT_SET_SH_REG = 0x76,
IT_SET_SH_REG_OFFSET = 0x77,
IT_SCRATCH_RAM_WRITE = 0x7D,
IT_SCRATCH_RAM_READ = 0x7E,
IT_LOAD_CONST_RAM = 0x80,
IT_WRITE_CONST_RAM = 0x81,
IT_DUMP_CONST_RAM = 0x83,
IT_INCREMENT_CE_COUNTER = 0x84,
IT_INCREMENT_DE_COUNTER = 0x85,
IT_WAIT_ON_CE_COUNTER = 0x86,
IT_WAIT_ON_DE_COUNTER__SI = 0x87,
IT_WAIT_ON_DE_COUNTER_DIFF = 0x88,
IT_SWITCH_BUFFER = 0x8B,
IT_DRAW_PREAMBLE__CI__VI = 0x36,
IT_RELEASE_MEM__CI__VI = 0x49,
IT_DMA_DATA__CI__VI = 0x50,
IT_ACQUIRE_MEM__CI__VI = 0x58,
IT_REWIND__CI__VI = 0x59,
IT_LOAD_UCONFIG_REG__CI__VI = 0x5E,
IT_SET_QUEUE_REG__CI__VI = 0x78,
IT_SET_UCONFIG_REG__CI__VI = 0x79,
IT_INDEX_ATTRIBUTES_INDIRECT__CI__VI = 0x91,
IT_SET_SH_REG_INDEX__CI__VI = 0x9B,
IT_SET_RESOURCES__CI__VI = 0xA0,
IT_MAP_PROCESS__CI__VI = 0xA1,
IT_MAP_QUEUES__CI__VI = 0xA2,
IT_UNMAP_QUEUES__CI__VI = 0xA3,
IT_QUERY_STATUS__CI__VI = 0xA4,
IT_RUN_LIST__CI__VI = 0xA5,
IT_LOAD_SH_REG_INDEX__VI = 0x63,
IT_LOAD_CONTEXT_REG_INDEX__VI = 0x9F,
IT_DUMP_CONST_RAM_OFFSET__VI = 0x9E,
};
#define PM4_TYPE_0 0
#define PM4_TYPE_2 2
#define PM4_TYPE_3 3
#endif

View File

@ -14,7 +14,9 @@
namespace Audio { namespace Audio {
int SDLAudio::AudioOutOpen(int type, u32 samples_num, u32 freq, constexpr int AUDIO_STREAM_BUFFER_THRESHOLD = 65536; // Define constant for buffer threshold
s32 SDLAudio::AudioOutOpen(int type, u32 samples_num, u32 freq,
Libraries::AudioOut::OrbisAudioOutParamFormat format) { Libraries::AudioOut::OrbisAudioOutParamFormat format) {
using Libraries::AudioOut::OrbisAudioOutParamFormat; using Libraries::AudioOut::OrbisAudioOutParamFormat;
std::unique_lock lock{m_mutex}; std::unique_lock lock{m_mutex};
@ -80,7 +82,7 @@ int SDLAudio::AudioOutOpen(int type, u32 samples_num, u32 freq,
SDL_zero(fmt); SDL_zero(fmt);
fmt.format = sampleFormat; fmt.format = sampleFormat;
fmt.channels = port.channels_num; fmt.channels = port.channels_num;
fmt.freq = 48000; fmt.freq = freq; // Set frequency from the argument
port.stream = port.stream =
SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, NULL, NULL); SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, NULL, NULL);
SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(port.stream)); SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(port.stream));
@ -88,7 +90,8 @@ int SDLAudio::AudioOutOpen(int type, u32 samples_num, u32 freq,
} }
} }
return -1; // all ports are used LOG_ERROR(Lib_AudioOut, "Audio ports are full");
return ORBIS_AUDIO_OUT_ERROR_PORT_FULL; // all ports are used
} }
s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) { s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) {
@ -97,27 +100,28 @@ s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) {
if (!port.isOpen) { if (!port.isOpen) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
} }
if (ptr == nullptr) {
return 0; const size_t data_size = port.samples_num * port.sample_size * port.channels_num;
}
// TODO mixing channels SDL_bool result = SDL_PutAudioStreamData(port.stream, ptr, data_size);
SDL_bool result = SDL_PutAudioStreamData(
port.stream, ptr, port.samples_num * port.sample_size * port.channels_num); lock.unlock(); // Unlock only after necessary operations
// TODO find a correct value 8192 is estimated
while (SDL_GetAudioStreamAvailable(port.stream) > 65536) { while (SDL_GetAudioStreamAvailable(port.stream) > AUDIO_STREAM_BUFFER_THRESHOLD) {
SDL_Delay(0); SDL_Delay(0);
} }
return result ? ORBIS_OK : -1; return result ? ORBIS_OK : -1;
} }
bool SDLAudio::AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume) { s32 SDLAudio::AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume) {
using Libraries::AudioOut::OrbisAudioOutParamFormat; using Libraries::AudioOut::OrbisAudioOutParamFormat;
std::shared_lock lock{m_mutex}; std::shared_lock lock{m_mutex};
auto& port = portsOut[handle - 1]; auto& port = portsOut[handle - 1];
if (!port.isOpen) { if (!port.isOpen) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
} }
for (int i = 0; i < port.channels_num; i++, bitflag >>= 1u) { for (int i = 0; i < port.channels_num; i++, bitflag >>= 1u) {
auto bit = bitflag & 0x1u; auto bit = bitflag & 0x1u;
@ -147,16 +151,16 @@ bool SDLAudio::AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume) {
} }
} }
return true; return ORBIS_OK;
} }
bool SDLAudio::AudioOutGetStatus(s32 handle, int* type, int* channels_num) { s32 SDLAudio::AudioOutGetStatus(s32 handle, int* type, int* channels_num) {
std::shared_lock lock{m_mutex}; std::shared_lock lock{m_mutex};
auto& port = portsOut[handle - 1]; auto& port = portsOut[handle - 1];
*type = port.type; *type = port.type;
*channels_num = port.channels_num; *channels_num = port.channels_num;
return true; return ORBIS_OK;
} }
} // namespace Audio } // namespace Audio

View File

@ -14,11 +14,11 @@ public:
SDLAudio() = default; SDLAudio() = default;
virtual ~SDLAudio() = default; virtual ~SDLAudio() = default;
int AudioOutOpen(int type, u32 samples_num, u32 freq, s32 AudioOutOpen(int type, u32 samples_num, u32 freq,
Libraries::AudioOut::OrbisAudioOutParamFormat format); Libraries::AudioOut::OrbisAudioOutParamFormat format);
s32 AudioOutOutput(s32 handle, const void* ptr); s32 AudioOutOutput(s32 handle, const void* ptr);
bool AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume); s32 AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume);
bool AudioOutGetStatus(s32 handle, int* type, int* channels_num); s32 AudioOutGetStatus(s32 handle, int* type, int* channels_num);
private: private:
struct PortOut { struct PortOut {
@ -33,8 +33,7 @@ private:
bool isOpen = false; bool isOpen = false;
}; };
std::shared_mutex m_mutex; std::shared_mutex m_mutex;
std::array<PortOut, 22> portsOut; // main up to 8 ports , BGM 1 port , voice up to 4 ports , std::array<PortOut, Libraries::AudioOut::SCE_AUDIO_OUT_NUM_PORTS> portsOut;
// personal up to 4 ports , padspk up to 5 ports , aux 1 port
}; };
} // namespace Audio } // namespace Audio

View File

@ -8,6 +8,7 @@
#include <fmt/xchar.h> // for wstring support #include <fmt/xchar.h> // for wstring support
#include <toml.hpp> #include <toml.hpp>
#include "common/logging/formatter.h" #include "common/logging/formatter.h"
#include "common/path_util.h"
#include "config.h" #include "config.h"
namespace toml { namespace toml {
@ -49,7 +50,6 @@ static bool isAutoUpdate = false;
static bool isNullGpu = false; static bool isNullGpu = false;
static bool shouldCopyGPUBuffers = false; static bool shouldCopyGPUBuffers = false;
static bool shouldDumpShaders = false; static bool shouldDumpShaders = false;
static bool shouldDumpPM4 = false;
static u32 vblankDivider = 1; static u32 vblankDivider = 1;
static bool vkValidation = false; static bool vkValidation = false;
static bool vkValidationSync = false; static bool vkValidationSync = false;
@ -60,6 +60,7 @@ static bool vkCrashDiagnostic = false;
// Gui // Gui
std::filesystem::path settings_install_dir = {}; std::filesystem::path settings_install_dir = {};
std::filesystem::path settings_addon_install_dir = {};
u32 main_window_geometry_x = 400; u32 main_window_geometry_x = 400;
u32 main_window_geometry_y = 400; u32 main_window_geometry_y = 400;
u32 main_window_geometry_w = 1280; u32 main_window_geometry_w = 1280;
@ -159,10 +160,6 @@ bool dumpShaders() {
return shouldDumpShaders; return shouldDumpShaders;
} }
bool dumpPM4() {
return shouldDumpPM4;
}
bool isRdocEnabled() { bool isRdocEnabled() {
return rdocEnable; return rdocEnable;
} }
@ -231,10 +228,6 @@ void setDumpShaders(bool enable) {
shouldDumpShaders = enable; shouldDumpShaders = enable;
} }
void setDumpPM4(bool enable) {
shouldDumpPM4 = enable;
}
void setVkValidation(bool enable) { void setVkValidation(bool enable) {
vkValidation = enable; vkValidation = enable;
} }
@ -308,6 +301,9 @@ void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) {
void setGameInstallDir(const std::filesystem::path& dir) { void setGameInstallDir(const std::filesystem::path& dir) {
settings_install_dir = dir; settings_install_dir = dir;
} }
void setAddonInstallDir(const std::filesystem::path& dir) {
settings_addon_install_dir = dir;
}
void setMainWindowTheme(u32 theme) { void setMainWindowTheme(u32 theme) {
mw_themes = theme; mw_themes = theme;
} }
@ -364,6 +360,13 @@ u32 getMainWindowGeometryH() {
std::filesystem::path getGameInstallDir() { std::filesystem::path getGameInstallDir() {
return settings_install_dir; return settings_install_dir;
} }
std::filesystem::path getAddonInstallDir() {
if (settings_addon_install_dir.empty()) {
// Default for users without a config file or a config file from before this option existed
return Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "addcont";
}
return settings_addon_install_dir;
}
u32 getMainWindowTheme() { u32 getMainWindowTheme() {
return mw_themes; return mw_themes;
} }
@ -459,7 +462,6 @@ void load(const std::filesystem::path& path) {
isNullGpu = toml::find_or<bool>(gpu, "nullGpu", false); isNullGpu = toml::find_or<bool>(gpu, "nullGpu", false);
shouldCopyGPUBuffers = toml::find_or<bool>(gpu, "copyGPUBuffers", false); shouldCopyGPUBuffers = toml::find_or<bool>(gpu, "copyGPUBuffers", false);
shouldDumpShaders = toml::find_or<bool>(gpu, "dumpShaders", false); shouldDumpShaders = toml::find_or<bool>(gpu, "dumpShaders", false);
shouldDumpPM4 = toml::find_or<bool>(gpu, "dumpPM4", false);
vblankDivider = toml::find_or<int>(gpu, "vblankDivider", 1); vblankDivider = toml::find_or<int>(gpu, "vblankDivider", 1);
} }
@ -492,6 +494,7 @@ void load(const std::filesystem::path& path) {
m_window_size_W = toml::find_or<int>(gui, "mw_width", 0); m_window_size_W = toml::find_or<int>(gui, "mw_width", 0);
m_window_size_H = toml::find_or<int>(gui, "mw_height", 0); m_window_size_H = toml::find_or<int>(gui, "mw_height", 0);
settings_install_dir = toml::find_fs_path_or(gui, "installDir", {}); settings_install_dir = toml::find_fs_path_or(gui, "installDir", {});
settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {});
main_window_geometry_x = toml::find_or<int>(gui, "geometry_x", 0); main_window_geometry_x = toml::find_or<int>(gui, "geometry_x", 0);
main_window_geometry_y = toml::find_or<int>(gui, "geometry_y", 0); main_window_geometry_y = toml::find_or<int>(gui, "geometry_y", 0);
main_window_geometry_w = toml::find_or<int>(gui, "geometry_w", 0); main_window_geometry_w = toml::find_or<int>(gui, "geometry_w", 0);
@ -548,7 +551,6 @@ void save(const std::filesystem::path& path) {
data["GPU"]["nullGpu"] = isNullGpu; data["GPU"]["nullGpu"] = isNullGpu;
data["GPU"]["copyGPUBuffers"] = shouldCopyGPUBuffers; data["GPU"]["copyGPUBuffers"] = shouldCopyGPUBuffers;
data["GPU"]["dumpShaders"] = shouldDumpShaders; data["GPU"]["dumpShaders"] = shouldDumpShaders;
data["GPU"]["dumpPM4"] = shouldDumpPM4;
data["GPU"]["vblankDivider"] = vblankDivider; data["GPU"]["vblankDivider"] = vblankDivider;
data["Vulkan"]["gpuId"] = gpuId; data["Vulkan"]["gpuId"] = gpuId;
data["Vulkan"]["validation"] = vkValidation; data["Vulkan"]["validation"] = vkValidation;
@ -567,6 +569,8 @@ void save(const std::filesystem::path& path) {
data["GUI"]["mw_width"] = m_window_size_W; data["GUI"]["mw_width"] = m_window_size_W;
data["GUI"]["mw_height"] = m_window_size_H; data["GUI"]["mw_height"] = m_window_size_H;
data["GUI"]["installDir"] = std::string{fmt::UTF(settings_install_dir.u8string()).data}; data["GUI"]["installDir"] = std::string{fmt::UTF(settings_install_dir.u8string()).data};
data["GUI"]["addonInstallDir"] =
std::string{fmt::UTF(settings_addon_install_dir.u8string()).data};
data["GUI"]["geometry_x"] = main_window_geometry_x; data["GUI"]["geometry_x"] = main_window_geometry_x;
data["GUI"]["geometry_y"] = main_window_geometry_y; data["GUI"]["geometry_y"] = main_window_geometry_y;
data["GUI"]["geometry_w"] = main_window_geometry_w; data["GUI"]["geometry_w"] = main_window_geometry_w;
@ -606,7 +610,6 @@ void setDefaultValues() {
isAutoUpdate = false; isAutoUpdate = false;
isNullGpu = false; isNullGpu = false;
shouldDumpShaders = false; shouldDumpShaders = false;
shouldDumpPM4 = false;
vblankDivider = 1; vblankDivider = 1;
vkValidation = false; vkValidation = false;
vkValidationSync = false; vkValidationSync = false;

View File

@ -35,7 +35,6 @@ bool autoUpdate();
bool nullGpu(); bool nullGpu();
bool copyGPUCmdBuffers(); bool copyGPUCmdBuffers();
bool dumpShaders(); bool dumpShaders();
bool dumpPM4();
bool isRdocEnabled(); bool isRdocEnabled();
u32 vblankDiv(); u32 vblankDiv();
@ -45,7 +44,6 @@ void setAutoUpdate(bool enable);
void setNullGpu(bool enable); void setNullGpu(bool enable);
void setCopyGPUCmdBuffers(bool enable); void setCopyGPUCmdBuffers(bool enable);
void setDumpShaders(bool enable); void setDumpShaders(bool enable);
void setDumpPM4(bool enable);
void setVblankDiv(u32 value); void setVblankDiv(u32 value);
void setGpuId(s32 selectedGpuId); void setGpuId(s32 selectedGpuId);
void setScreenWidth(u32 width); void setScreenWidth(u32 width);
@ -78,6 +76,7 @@ bool vkCrashDiagnosticEnabled();
// Gui // Gui
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h); void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h);
void setGameInstallDir(const std::filesystem::path& dir); void setGameInstallDir(const std::filesystem::path& dir);
void setAddonInstallDir(const std::filesystem::path& dir);
void setMainWindowTheme(u32 theme); void setMainWindowTheme(u32 theme);
void setIconSize(u32 size); void setIconSize(u32 size);
void setIconSizeGrid(u32 size); void setIconSizeGrid(u32 size);
@ -96,6 +95,7 @@ u32 getMainWindowGeometryY();
u32 getMainWindowGeometryW(); u32 getMainWindowGeometryW();
u32 getMainWindowGeometryH(); u32 getMainWindowGeometryH();
std::filesystem::path getGameInstallDir(); std::filesystem::path getGameInstallDir();
std::filesystem::path getAddonInstallDir();
u32 getMainWindowTheme(); u32 getMainWindowTheme();
u32 getIconSize(); u32 getIconSize();
u32 getIconSizeGrid(); u32 getIconSizeGrid();

View File

@ -205,9 +205,9 @@ public:
return WriteSpan(string); return WriteSpan(string);
} }
static void WriteBytes(const std::filesystem::path path, std::span<const u8> data) { static size_t WriteBytes(const std::filesystem::path path, std::span<const u8> data) {
IOFile out(path, FileAccessMode::Write); IOFile out(path, FileAccessMode::Write);
out.Write(data); return out.Write(data);
} }
private: private:

View File

@ -110,7 +110,6 @@ static auto UserPaths = [] {
create_path(PathType::LogDir, user_dir / LOG_DIR); create_path(PathType::LogDir, user_dir / LOG_DIR);
create_path(PathType::ScreenshotsDir, user_dir / SCREENSHOTS_DIR); create_path(PathType::ScreenshotsDir, user_dir / SCREENSHOTS_DIR);
create_path(PathType::ShaderDir, user_dir / SHADER_DIR); create_path(PathType::ShaderDir, user_dir / SHADER_DIR);
create_path(PathType::PM4Dir, user_dir / PM4_DIR);
create_path(PathType::SaveDataDir, user_dir / SAVEDATA_DIR); create_path(PathType::SaveDataDir, user_dir / SAVEDATA_DIR);
create_path(PathType::GameDataDir, user_dir / GAMEDATA_DIR); create_path(PathType::GameDataDir, user_dir / GAMEDATA_DIR);
create_path(PathType::TempDataDir, user_dir / TEMPDATA_DIR); create_path(PathType::TempDataDir, user_dir / TEMPDATA_DIR);
@ -119,7 +118,6 @@ static auto UserPaths = [] {
create_path(PathType::CapturesDir, user_dir / CAPTURES_DIR); create_path(PathType::CapturesDir, user_dir / CAPTURES_DIR);
create_path(PathType::CheatsDir, user_dir / CHEATS_DIR); create_path(PathType::CheatsDir, user_dir / CHEATS_DIR);
create_path(PathType::PatchesDir, user_dir / PATCHES_DIR); create_path(PathType::PatchesDir, user_dir / PATCHES_DIR);
create_path(PathType::AddonsDir, user_dir / ADDONS_DIR);
create_path(PathType::MetaDataDir, user_dir / METADATA_DIR); create_path(PathType::MetaDataDir, user_dir / METADATA_DIR);
return paths; return paths;

View File

@ -17,7 +17,6 @@ enum class PathType {
LogDir, // Where log files are stored. LogDir, // Where log files are stored.
ScreenshotsDir, // Where screenshots are stored. ScreenshotsDir, // Where screenshots are stored.
ShaderDir, // Where shaders are stored. ShaderDir, // Where shaders are stored.
PM4Dir, // Where command lists are stored.
SaveDataDir, // Where guest save data is stored. SaveDataDir, // Where guest save data is stored.
TempDataDir, // Where game temp data is stored. TempDataDir, // Where game temp data is stored.
GameDataDir, // Where game data is stored. GameDataDir, // Where game data is stored.
@ -26,7 +25,6 @@ enum class PathType {
CapturesDir, // Where rdoc captures are stored. CapturesDir, // Where rdoc captures are stored.
CheatsDir, // Where cheats are stored. CheatsDir, // Where cheats are stored.
PatchesDir, // Where patches are stored. PatchesDir, // Where patches are stored.
AddonsDir, // Where additional content is stored.
MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored. MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored.
}; };
@ -36,7 +34,6 @@ constexpr auto PORTABLE_DIR = "user";
constexpr auto LOG_DIR = "log"; constexpr auto LOG_DIR = "log";
constexpr auto SCREENSHOTS_DIR = "screenshots"; constexpr auto SCREENSHOTS_DIR = "screenshots";
constexpr auto SHADER_DIR = "shader"; constexpr auto SHADER_DIR = "shader";
constexpr auto PM4_DIR = "pm4";
constexpr auto SAVEDATA_DIR = "savedata"; constexpr auto SAVEDATA_DIR = "savedata";
constexpr auto GAMEDATA_DIR = "data"; constexpr auto GAMEDATA_DIR = "data";
constexpr auto TEMPDATA_DIR = "temp"; constexpr auto TEMPDATA_DIR = "temp";
@ -45,7 +42,6 @@ constexpr auto DOWNLOAD_DIR = "download";
constexpr auto CAPTURES_DIR = "captures"; constexpr auto CAPTURES_DIR = "captures";
constexpr auto CHEATS_DIR = "cheats"; constexpr auto CHEATS_DIR = "cheats";
constexpr auto PATCHES_DIR = "patches"; constexpr auto PATCHES_DIR = "patches";
constexpr auto ADDONS_DIR = "addcont";
constexpr auto METADATA_DIR = "game_data"; constexpr auto METADATA_DIR = "game_data";
// Filenames // Filenames

102
src/core/debug_state.cpp Normal file
View File

@ -0,0 +1,102 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/native_clock.h"
#include "common/singleton.h"
#include "debug_state.h"
#include "libraries/kernel/event_queues.h"
#include "libraries/kernel/time_management.h"
#include "libraries/system/msgdialog.h"
using namespace DebugStateType;
DebugStateImpl& DebugState = *Common::Singleton<DebugStateImpl>::Instance();
static ThreadID ThisThreadID() {
#ifdef _WIN32
return GetCurrentThreadId();
#else
return pthread_self();
#endif
}
static void PauseThread(ThreadID id) {
#ifdef _WIN32
auto handle = OpenThread(THREAD_SUSPEND_RESUME, FALSE, id);
SuspendThread(handle);
CloseHandle(handle);
#else
pthread_kill(id, SIGUSR1);
#endif
}
static void ResumeThread(ThreadID id) {
#ifdef _WIN32
auto handle = OpenThread(THREAD_SUSPEND_RESUME, FALSE, id);
ResumeThread(handle);
CloseHandle(handle);
#else
pthread_kill(id, SIGUSR1);
#endif
}
void DebugStateImpl::AddCurrentThreadToGuestList() {
std::lock_guard lock{guest_threads_mutex};
const ThreadID id = ThisThreadID();
guest_threads.push_back(id);
}
void DebugStateImpl::RemoveCurrentThreadFromGuestList() {
std::lock_guard lock{guest_threads_mutex};
const ThreadID id = ThisThreadID();
std::erase_if(guest_threads, [&](const ThreadID& v) { return v == id; });
}
void DebugStateImpl::PauseGuestThreads() {
using namespace Libraries::MsgDialog;
std::unique_lock lock{guest_threads_mutex};
if (is_guest_threads_paused) {
return;
}
if (ShouldPauseInSubmit()) {
waiting_submit_pause = false;
should_show_frame_dump = true;
}
bool self_guest = false;
ThreadID self_id = ThisThreadID();
for (const auto& id : guest_threads) {
if (id == self_id) {
self_guest = true;
} else {
PauseThread(id);
}
}
pause_time = Libraries::Kernel::Dev::GetClock()->GetUptime();
is_guest_threads_paused = true;
lock.unlock();
if (self_guest) {
PauseThread(self_id);
}
}
void DebugStateImpl::ResumeGuestThreads() {
std::lock_guard lock{guest_threads_mutex};
if (!is_guest_threads_paused) {
return;
}
u64 delta_time = Libraries::Kernel::Dev::GetClock()->GetUptime() - pause_time;
Libraries::Kernel::Dev::GetInitialPtc() += delta_time;
for (const auto& id : guest_threads) {
ResumeThread(id);
}
is_guest_threads_paused = false;
}
void DebugStateImpl::RequestFrameDump(s32 count) {
gnm_frame_dump_request_count = count;
frame_dump_list.clear();
frame_dump_list.resize(count);
waiting_submit_pause = true;
}

126
src/core/debug_state.h Normal file
View File

@ -0,0 +1,126 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <mutex>
#include <vector>
#include <queue>
#include "common/types.h"
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
#endif
#include <Windows.h>
using ThreadID = DWORD;
#else
#include <pthread.h>
#include <signal.h>
using ThreadID = pthread_t;
#endif
namespace Core::Devtools {
class Layer;
namespace Widget {
class FrameGraph;
}
} // namespace Core::Devtools
namespace DebugStateType {
enum class QueueType {
acb,
dcb,
ccb,
};
struct QueueDump {
QueueType type;
u32 submit_num;
u32 num2; // acb: queue_num; else: buffer_in_submit
std::vector<u32> data;
};
struct FrameDump {
std::vector<QueueDump> queues;
};
class DebugStateImpl {
friend class Core::Devtools::Layer;
friend class Core::Devtools::Widget::FrameGraph;
std::mutex guest_threads_mutex{};
std::vector<ThreadID> guest_threads{};
std::atomic_bool is_guest_threads_paused = false;
u64 pause_time{};
std::atomic_int32_t flip_frame_count = 0;
std::atomic_int32_t gnm_frame_count = 0;
s32 gnm_frame_dump_request_count = -1;
bool waiting_submit_pause = false;
bool should_show_frame_dump = false;
std::mutex frame_dump_list_mutex;
std::vector<FrameDump> frame_dump_list{};
std::queue<std::string> debug_message_popup;
public:
void AddCurrentThreadToGuestList();
void RemoveCurrentThreadFromGuestList();
void PauseGuestThreads();
void ResumeGuestThreads();
bool IsGuestThreadsPaused() const {
return is_guest_threads_paused;
}
void IncFlipFrameNum() {
++flip_frame_count;
}
void IncGnmFrameNum() {
++gnm_frame_count;
--gnm_frame_dump_request_count;
}
u32 GetFrameNum() const {
return flip_frame_count;
}
bool DumpingCurrentFrame() const {
return gnm_frame_dump_request_count > 0;
}
bool ShouldPauseInSubmit() const {
return waiting_submit_pause && gnm_frame_dump_request_count == 0;
}
void RequestFrameDump(s32 count = 1);
FrameDump& GetFrameDump() {
return frame_dump_list[frame_dump_list.size() - gnm_frame_dump_request_count];
}
void PushQueueDump(QueueDump dump) {
std::unique_lock lock{frame_dump_list_mutex};
GetFrameDump().queues.push_back(std::move(dump));
}
void ShowDebugMessage(std::string message) {
if (message.empty()) {
return;
}
debug_message_popup.push(std::move(message));
}
};
} // namespace DebugStateType
extern DebugStateType::DebugStateImpl& DebugState;

View File

@ -0,0 +1,297 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Credits to https://github.com/psucien/tlg-emu-tools/
#include "common/types.h"
#include "gcn/si_ci_vi_merged_offset.h"
using namespace Pal::Gfx6;
namespace Core::Devtools::Gcn {
const char* GetContextRegName(u32 reg_offset) {
switch (reg_offset) {
case mmDB_SHADER_CONTROL:
return "mmDB_SHADER_CONTROL";
case mmCB_SHADER_MASK:
return "mmCB_SHADER_MASK";
case mmPA_CL_CLIP_CNTL:
return "mmPA_CL_CLIP_CNTL";
case mmVGT_INSTANCE_STEP_RATE_0:
return "mmVGT_INSTANCE_STEP_RATE_0";
case mmVGT_INSTANCE_STEP_RATE_1:
return "mmVGT_INSTANCE_STEP_RATE_1";
case mmVGT_INDX_OFFSET:
return "mmVGT_INDX_OFFSET";
case mmVGT_SHADER_STAGES_EN:
return "mmVGT_SHADER_STAGES_EN";
case mmVGT_GS_MODE:
return "mmVGT_GS_MODE";
case mmVGT_STRMOUT_CONFIG:
return "mmVGT_STRMOUT_CONFIG";
case mmVGT_OUT_DEALLOC_CNTL:
return "mmVGT_OUT_DEALLOC_CNTL";
case mmVGT_VTX_CNT_EN:
return "mmVGT_VTX_CNT_EN";
case mmVGT_MAX_VTX_INDX:
return "mmVGT_MAX_VTX_INDX";
case mmVGT_MULTI_PRIM_IB_RESET_INDX:
return "mmVGT_MULTI_PRIM_IB_RESET_INDX";
case mmVGT_OUTPUT_PATH_CNTL:
return "mmVGT_OUTPUT_PATH_CNTL";
case mmVGT_GS_PER_ES:
return "mmVGT_GS_PER_ES";
case mmVGT_ES_PER_GS:
return "mmVGT_ES_PER_GS";
case mmVGT_GS_PER_VS:
return "mmVGT_GS_PER_VS";
case mmCB_COLOR0_BASE:
return "mmCB_COLOR0_BASE";
case mmCB_COLOR0_INFO:
return "mmCB_COLOR0_INFO";
case mmCB_COLOR0_CMASK_SLICE:
return "mmCB_COLOR0_CMASK_SLICE";
case mmCB_COLOR0_CLEAR_WORD0:
return "mmCB_COLOR0_CLEAR_WORD0";
case mmCB_COLOR0_CLEAR_WORD1:
return "mmCB_COLOR0_CLEAR_WORD1";
case mmCB_COLOR0_PITCH:
return "mmCB_COLOR0_PITCH";
case mmCB_COLOR0_SLICE:
return "mmCB_COLOR0_SLICE";
case mmCB_COLOR0_VIEW:
return "mmCB_COLOR0_VIEW";
case mmCB_COLOR0_DCC_CONTROL__VI:
return "mmCB_COLOR0_DCC_CONTROL";
case mmCB_COLOR0_CMASK:
return "mmCB_COLOR0_CMASK";
case mmCB_COLOR0_FMASK_SLICE:
return "mmCB_COLOR0_FMASK_SLICE";
case mmCB_COLOR0_FMASK:
return "mmCB_COLOR0_FMASK";
case mmCB_COLOR0_DCC_BASE__VI:
return "mmCB_COLOR0_DCC_BASE";
case mmCB_COLOR0_ATTRIB:
return "mmCB_COLOR0_ATTRIB";
case mmCB_COLOR1_BASE:
return "mmCB_COLOR1_BASE";
case mmCB_COLOR1_INFO:
return "mmCB_COLOR1_INFO";
case mmCB_COLOR1_ATTRIB:
return "mmCB_COLOR1_ATTRIB";
case mmCB_COLOR1_CMASK_SLICE:
return "mmCB_COLOR1_CMASK_SLICE";
case mmCB_COLOR1_CLEAR_WORD0:
return "mmCB_COLOR1_CLEAR_WORD0";
case mmCB_COLOR1_CLEAR_WORD1:
return "mmCB_COLOR1_CLEAR_WORD1";
case mmCB_COLOR1_PITCH:
return "mmCB_COLOR1_PITCH";
case mmCB_COLOR1_VIEW:
return "mmCB_COLOR1_VIEW";
case mmCB_COLOR2_INFO:
return "mmCB_COLOR2_INFO";
case mmCB_COLOR2_ATTRIB:
return "mmCB_COLOR2_ATTRIB";
case mmCB_COLOR2_CMASK_SLICE:
return "mmCB_COLOR2_CMASK_SLICE";
case mmCB_COLOR2_CLEAR_WORD0:
return "mmCB_COLOR2_CLEAR_WORD0";
case mmCB_COLOR2_CLEAR_WORD1:
return "mmCB_COLOR2_CLEAR_WORD1";
case mmCB_COLOR2_PITCH:
return "mmCB_COLOR2_PITCH";
case mmCB_COLOR2_VIEW:
return "mmCB_COLOR2_VIEW";
case mmCB_COLOR3_INFO:
return "mmCB_COLOR3_INFO";
case mmCB_COLOR3_CMASK_SLICE:
return "mmCB_COLOR3_CMASK_SLICE";
case mmCB_COLOR4_INFO:
return "mmCB_COLOR4_INFO";
case mmCB_COLOR5_INFO:
return "mmCB_COLOR5_INFO";
case mmCB_COLOR6_INFO:
return "mmCB_COLOR6_INFO";
case mmCB_COLOR7_INFO:
return "mmCB_COLOR7_INFO";
case mmDB_SRESULTS_COMPARE_STATE0:
return "mmDB_SRESULTS_COMPARE_STATE0";
case mmDB_SRESULTS_COMPARE_STATE1:
return "mmDB_SRESULTS_COMPARE_STATE1";
case mmDB_DEPTH_CONTROL:
return "mmDB_DEPTH_CONTROL";
case mmDB_EQAA:
return "mmDB_EQAA";
case mmPA_SU_POINT_SIZE:
return "mmPA_SU_POINT_SIZE";
case mmPA_SU_POINT_MINMAX:
return "mmPA_SU_POINT_MINMAX";
case mmPA_SU_SC_MODE_CNTL:
return "mmPA_SU_SC_MODE_CNTL";
case mmPA_SU_POLY_OFFSET_DB_FMT_CNTL:
return "mmPA_SU_POLY_OFFSET_DB_FMT_CNTL";
case mmPA_SC_CLIPRECT_RULE:
return "mmPA_SC_CLIPRECT_RULE";
case mmPA_SC_MODE_CNTL_0:
return "mmPA_SC_MODE_CNTL_0";
case mmPA_SC_MODE_CNTL_1:
return "mmPA_SC_MODE_CNTL_1";
case mmPA_SC_AA_SAMPLE_LOCS_PIXEL_X0Y0_0:
return "mmPA_SC_AA_SAMPLE_LOCS_PIXEL_X0Y0_0";
case mmPA_SC_AA_SAMPLE_LOCS_PIXEL_X0Y1_0:
return "mmPA_SC_AA_SAMPLE_LOCS_PIXEL_X0Y1_0";
case mmPA_SC_AA_SAMPLE_LOCS_PIXEL_X1Y0_0:
return "mmPA_SC_AA_SAMPLE_LOCS_PIXEL_X1Y0_0";
case mmPA_SC_AA_MASK_X0Y0_X1Y0:
return "mmPA_SC_AA_MASK_X0Y0_X1Y0";
case mmPA_SC_AA_MASK_X0Y1_X1Y1:
return "mmPA_SC_AA_MASK_X0Y1_X1Y1";
case mmPA_SC_CENTROID_PRIORITY_0:
return "mmPA_SC_CENTROID_PRIORITY_0";
case mmPA_SC_CENTROID_PRIORITY_1:
return "mmPA_SC_CENTROID_PRIORITY_1";
case mmPA_SC_AA_CONFIG:
return "mmPA_SC_AA_CONFIG";
case mmDB_RENDER_CONTROL:
return "mmDB_RENDER_CONTROL";
case mmDB_STENCIL_CONTROL:
return "mmDB_STENCIL_CONTROL";
case mmDB_STENCILREFMASK:
return "mmDB_STENCILREFMASK";
case mmDB_STENCILREFMASK_BF:
return "mmDB_STENCILREFMASK_BF";
case mmDB_STENCIL_CLEAR:
return "mmDB_STENCIL_CLEAR";
case mmDB_DEPTH_CLEAR:
return "mmDB_DEPTH_CLEAR";
case mmCB_TARGET_MASK:
return "mmCB_TARGET_MASK";
case mmDB_Z_INFO:
return "mmDB_Z_INFO";
case mmDB_STENCIL_INFO:
return "mmDB_STENCIL_INFO";
case mmDB_Z_READ_BASE:
return "mmDB_Z_READ_BASE";
case mmDB_STENCIL_READ_BASE:
return "mmDB_STENCIL_READ_BASE";
case mmDB_Z_WRITE_BASE:
return "mmDB_Z_WRITE_BASE";
case mmDB_STENCIL_WRITE_BASE:
return "mmDB_STENCIL_WRITE_BASE";
case mmDB_DEPTH_INFO:
return "mmDB_DEPTH_INFO";
case mmDB_DEPTH_VIEW:
return "mmDB_DEPTH_VIEW";
case mmDB_DEPTH_SLICE:
return "mmDB_DEPTH_SLICE";
case mmDB_DEPTH_SIZE:
return "mmDB_DEPTH_SIZE";
case mmTA_BC_BASE_ADDR:
return "mmTA_BC_BASE_ADDR";
case mmCB_BLEND_RED:
return "mmCB_BLEND_RED";
case mmCB_BLEND_GREEN:
return "mmCB_BLEND_GREEN";
case mmCB_BLEND_BLUE:
return "mmCB_BLEND_BLUE";
case mmDB_ALPHA_TO_MASK:
return "mmDB_ALPHA_TO_MASK";
case mmCB_BLEND0_CONTROL:
return "mmCB_BLEND0_CONTROL";
case mmCB_BLEND1_CONTROL:
return "mmCB_BLEND1_CONTROL";
case mmCB_BLEND2_CONTROL:
return "mmCB_BLEND2_CONTROL";
case mmCB_BLEND3_CONTROL:
return "mmCB_BLEND3_CONTROL";
case mmCB_BLEND4_CONTROL:
return "mmCB_BLEND4_CONTROL";
case mmCB_BLEND5_CONTROL:
return "mmCB_BLEND5_CONTROL";
case mmCB_BLEND6_CONTROL:
return "mmCB_BLEND6_CONTROL";
case mmCB_BLEND7_CONTROL:
return "mmCB_BLEND7_CONTROL";
case mmDB_HTILE_DATA_BASE:
return "mmDB_HTILE_DATA_BASE";
case mmDB_HTILE_SURFACE:
return "mmDB_HTILE_SURFACE";
case mmPA_SU_LINE_CNTL:
return "mmPA_SU_LINE_CNTL";
case mmPA_SC_VPORT_ZMIN_0:
return "mmPA_SC_VPORT_ZMIN_0";
case mmPA_SC_VPORT_ZMAX_0:
return "mmPA_SC_VPORT_ZMAX_0";
case mmPA_SC_VPORT_SCISSOR_0_TL:
return "mmPA_SC_VPORT_SCISSOR_0_TL";
case mmPA_SC_VPORT_SCISSOR_0_BR:
return "mmPA_SC_VPORT_SCISSOR_0_BR";
case mmPA_SC_GENERIC_SCISSOR_TL:
return "mmPA_SC_GENERIC_SCISSOR_TL";
case mmPA_SC_GENERIC_SCISSOR_BR:
return "mmPA_SC_GENERIC_SCISSOR_BR";
case mmPA_CL_VPORT_XSCALE:
return "mmPA_CL_VPORT_XSCALE";
case mmPA_CL_VPORT_YSCALE:
return "mmPA_CL_VPORT_YSCALE";
case mmPA_CL_VPORT_ZSCALE:
return "mmPA_CL_VPORT_ZSCALE";
case mmPA_CL_VPORT_XOFFSET:
return "mmPA_CL_VPORT_XOFFSET";
case mmPA_CL_VPORT_YOFFSET:
return "mmPA_CL_VPORT_YOFFSET";
case mmPA_CL_VPORT_ZOFFSET:
return "mmPA_CL_VPORT_ZOFFSET";
case mmPA_CL_VTE_CNTL:
return "mmPA_CL_VTE_CNTL";
case mmPA_SC_SCREEN_SCISSOR_TL:
return "mmPA_SC_SCREEN_SCISSOR_TL";
case mmPA_SC_SCREEN_SCISSOR_BR:
return "mmPA_SC_SCREEN_SCISSOR_BR";
case mmPA_SU_HARDWARE_SCREEN_OFFSET:
return "mmPA_SU_HARDWARE_SCREEN_OFFSET";
case mmPA_SU_VTX_CNTL:
return "mmPA_SU_VTX_CNTL";
case mmPA_CL_GB_VERT_CLIP_ADJ:
return "mmPA_CL_GB_VERT_CLIP_ADJ";
case mmPA_CL_GB_HORZ_CLIP_ADJ:
return "mmPA_CL_GB_HORZ_CLIP_ADJ";
case mmPA_CL_GB_VERT_DISC_ADJ:
return "mmPA_CL_GB_VERT_DISC_ADJ";
case mmPA_CL_GB_HORZ_DISC_ADJ:
return "mmPA_CL_GB_HORZ_DISC_ADJ";
case mmCB_COLOR_CONTROL:
return "mmCB_COLOR_CONTROL";
case mmSPI_SHADER_Z_FORMAT:
return "mmSPI_SHADER_Z_FORMAT";
case mmSPI_SHADER_COL_FORMAT:
return "mmSPI_SHADER_COL_FORMAT";
case mmPA_CL_VS_OUT_CNTL:
return "mmPA_CL_VS_OUT_CNTL";
case mmSPI_VS_OUT_CONFIG:
return "mmSPI_VS_OUT_CONFIG";
case mmSPI_SHADER_POS_FORMAT:
return "mmSPI_SHADER_POS_FORMAT";
case mmSPI_PS_INPUT_ENA:
return "mmSPI_PS_INPUT_ENA";
case mmSPI_PS_INPUT_ADDR:
return "mmSPI_PS_INPUT_ADDR";
case mmSPI_PS_IN_CONTROL:
return "mmSPI_PS_IN_CONTROL";
case mmSPI_BARYC_CNTL:
return "mmSPI_BARYC_CNTL";
case mmSPI_PS_INPUT_CNTL_0:
return "mmSPI_PS_INPUT_CNTL_0";
case mmSPI_PS_INPUT_CNTL_1:
return "mmSPI_PS_INPUT_CNTL_1";
case mmSPI_PS_INPUT_CNTL_2:
return "mmSPI_PS_INPUT_CNTL_2";
case mmSPI_PS_INPUT_CNTL_3:
return "mmSPI_PS_INPUT_CNTL_3";
default:
break;
}
return "<UNK>";
}
} // namespace Core::Devtools::Gcn

View File

@ -0,0 +1,118 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Credits to https://github.com/psucien/tlg-emu-tools/
#include "common/types.h"
#include "gcn/si_ci_vi_merged_pm4_it_opcodes.h"
namespace Core::Devtools::Gcn {
const char* GetOpCodeName(u32 op) {
switch (op) {
case IT_NOP:
return "IT_NOP";
case IT_SET_BASE:
return "IT_SET_BASE";
case IT_INDEX_BUFFER_SIZE:
return "IT_INDEX_BUFFER_SIZE";
case IT_SET_PREDICATION:
return "IT_SET_PREDICATION";
case IT_COND_EXEC:
return "IT_COND_EXEC";
case IT_INDEX_BASE:
return "IT_INDEX_BASE";
case IT_INDEX_TYPE:
return "IT_INDEX_TYPE";
case IT_NUM_INSTANCES:
return "IT_NUM_INSTANCES";
case IT_STRMOUT_BUFFER_UPDATE:
return "IT_STRMOUT_BUFFER_UPDATE";
case IT_WRITE_DATA:
return "IT_WRITE_DATA";
case IT_MEM_SEMAPHORE:
return "IT_MEM_SEMAPHORE";
case IT_WAIT_REG_MEM:
return "IT_WAIT_REG_MEM";
case IT_INDIRECT_BUFFER:
return "IT_INDIRECT_BUFFER";
case IT_PFP_SYNC_ME:
return "IT_PFP_SYNC_ME";
case IT_EVENT_WRITE:
return "IT_EVENT_WRITE";
case IT_EVENT_WRITE_EOP:
return "IT_EVENT_WRITE_EOP";
case IT_EVENT_WRITE_EOS:
return "IT_EVENT_WRITE_EOS";
case IT_DMA_DATA__CI__VI:
return "IT_DMA_DATA";
case IT_ACQUIRE_MEM__CI__VI:
return "IT_ACQUIRE_MEM";
case IT_REWIND__CI__VI:
return "IT_REWIND";
case IT_SET_CONFIG_REG:
return "IT_SET_CONFIG_REG";
case IT_SET_CONTEXT_REG:
return "IT_SET_CONTEXT_REG";
case IT_SET_SH_REG:
return "IT_SET_SH_REG";
case IT_SET_UCONFIG_REG__CI__VI:
return "IT_SET_UCONFIG_REG";
case IT_INCREMENT_DE_COUNTER:
return "IT_INCREMENT_DE_COUNTER";
case IT_WAIT_ON_CE_COUNTER:
return "IT_WAIT_ON_CE_COUNTER";
case IT_DISPATCH_DIRECT:
return "IT_DISPATCH_DIRECT";
case IT_DISPATCH_INDIRECT:
return "IT_DISPATCH_INDIRECT";
case IT_OCCLUSION_QUERY:
return "IT_OCCLUSION_QUERY";
case IT_REG_RMW:
return "IT_REG_RMW";
case IT_PRED_EXEC:
return "IT_PRED_EXEC";
case IT_DRAW_INDIRECT:
return "IT_DRAW_INDIRECT";
case IT_DRAW_INDEX_INDIRECT:
return "IT_DRAW_INDEX_INDIRECT";
case IT_DRAW_INDEX_2:
return "IT_DRAW_INDEX_2";
case IT_DRAW_INDEX_OFFSET_2:
return "IT_DRAW_INDEX_OFFSET_2";
case IT_CONTEXT_CONTROL:
return "IT_CONTEXT_CONTROL";
case IT_DRAW_INDIRECT_MULTI:
return "IT_DRAW_INDIRECT_MULTI";
case IT_DRAW_INDEX_AUTO:
return "IT_DRAW_INDEX_AUTO";
case IT_DRAW_INDEX_MULTI_AUTO:
return "IT_DRAW_INDEX_MULTI_AUTO";
case IT_COPY_DATA:
return "IT_COPY_DATA";
case IT_CP_DMA:
return "IT_CP_DMA";
case IT_SURFACE_SYNC:
return "IT_SURFACE_SYNC";
case IT_COND_WRITE:
return "IT_COND_WRITE";
case IT_RELEASE_MEM__CI__VI:
return "IT_RELEASE_MEM";
case IT_WRITE_CONST_RAM:
return "IT_WRITE_CONST_RAM"; // used in CCB
case IT_WAIT_ON_DE_COUNTER_DIFF:
return "IT_WAIT_ON_DE_COUNTER_DIFF"; // used in CCB
case IT_DUMP_CONST_RAM:
return "IT_DUMP_CONST_RAM"; // used in CCB
case IT_INCREMENT_CE_COUNTER:
return "IT_INCREMENT_CE_COUNTER"; // used in CCB
case IT_CLEAR_STATE:
return "IT_CLEAR_STATE";
case 0xFF:
return "<STUB (TMP)>";
default:
break;
}
return "<UNK>";
}
} // namespace Core::Devtools::Gcn

View File

@ -0,0 +1,171 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Credits to https://github.com/psucien/tlg-emu-tools/
#include "common/types.h"
#include "gcn/si_ci_vi_merged_offset.h"
using namespace Pal::Gfx6;
namespace Core::Devtools::Gcn {
const char* GetShaderRegName(u32 reg_offset) {
switch (reg_offset) {
case mmSPI_SHADER_PGM_LO_VS:
return "mmSPI_SHADER_PGM_LO_VS";
case mmSPI_SHADER_PGM_HI_VS:
return "mmSPI_SHADER_PGM_HI_VS";
case mmSPI_SHADER_PGM_LO_PS:
return "mmSPI_SHADER_PGM_LO_PS";
case mmSPI_SHADER_PGM_HI_PS:
return "mmSPI_SHADER_PGM_HI_PS";
case mmSPI_SHADER_PGM_RSRC1_VS:
return "mmSPI_SHADER_PGM_RSRC1_VS";
case mmSPI_SHADER_PGM_RSRC2_VS:
return "mmSPI_SHADER_PGM_RSRC2_VS";
case mmSPI_SHADER_PGM_RSRC3_VS__CI__VI:
return "mmSPI_SHADER_PGM_RSRC3_VS__CI__VI";
case mmSPI_SHADER_PGM_RSRC1_PS:
return "mmSPI_SHADER_PGM_RSRC1_PS";
case mmSPI_SHADER_PGM_RSRC2_PS:
return "mmSPI_SHADER_PGM_RSRC2_PS";
case mmSPI_SHADER_PGM_RSRC3_PS__CI__VI:
return "mmSPI_SHADER_PGM_RSRC3_PS__CI__VI";
case mmSPI_SHADER_USER_DATA_PS_0:
return "mmSPI_SHADER_USER_DATA_PS_0";
case mmSPI_SHADER_USER_DATA_PS_1:
return "mmSPI_SHADER_USER_DATA_PS_1";
case mmSPI_SHADER_USER_DATA_PS_2:
return "mmSPI_SHADER_USER_DATA_PS_2";
case mmSPI_SHADER_USER_DATA_PS_3:
return "mmSPI_SHADER_USER_DATA_PS_3";
case mmSPI_SHADER_USER_DATA_PS_4:
return "mmSPI_SHADER_USER_DATA_PS_4";
case mmSPI_SHADER_USER_DATA_PS_5:
return "mmSPI_SHADER_USER_DATA_PS_5";
case mmSPI_SHADER_USER_DATA_PS_6:
return "mmSPI_SHADER_USER_DATA_PS_6";
case mmSPI_SHADER_USER_DATA_PS_7:
return "mmSPI_SHADER_USER_DATA_PS_7";
case mmSPI_SHADER_USER_DATA_PS_8:
return "mmSPI_SHADER_USER_DATA_PS_8";
case mmSPI_SHADER_USER_DATA_PS_9:
return "mmSPI_SHADER_USER_DATA_PS_9";
case mmSPI_SHADER_USER_DATA_PS_10:
return "mmSPI_SHADER_USER_DATA_PS_10";
case mmSPI_SHADER_USER_DATA_PS_11:
return "mmSPI_SHADER_USER_DATA_PS_11";
case mmSPI_SHADER_USER_DATA_PS_12:
return "mmSPI_SHADER_USER_DATA_PS_12";
case mmSPI_SHADER_USER_DATA_PS_13:
return "mmSPI_SHADER_USER_DATA_PS_13";
case mmSPI_SHADER_USER_DATA_PS_14:
return "mmSPI_SHADER_USER_DATA_PS_14";
case mmSPI_SHADER_USER_DATA_PS_15:
return "mmSPI_SHADER_USER_DATA_PS_15";
case mmCOMPUTE_TMPRING_SIZE:
return "mmCOMPUTE_TMPRING_SIZE";
case mmCOMPUTE_PGM_LO:
return "mmCOMPUTE_PGM_LO";
case mmCOMPUTE_PGM_HI:
return "mmCOMPUTE_PGM_HI";
case mmCOMPUTE_PGM_RSRC1:
return "mmCOMPUTE_PGM_RSRC1";
case mmCOMPUTE_PGM_RSRC2:
return "mmCOMPUTE_PGM_RSRC2";
case mmCOMPUTE_USER_DATA_0:
return "mmCOMPUTE_USER_DATA_0";
case mmCOMPUTE_USER_DATA_1:
return "mmCOMPUTE_USER_DATA_1";
case mmCOMPUTE_USER_DATA_2:
return "mmCOMPUTE_USER_DATA_2";
case mmCOMPUTE_USER_DATA_3:
return "mmCOMPUTE_USER_DATA_3";
case mmCOMPUTE_USER_DATA_4:
return "mmCOMPUTE_USER_DATA_4";
case mmCOMPUTE_USER_DATA_5:
return "mmCOMPUTE_USER_DATA_5";
case mmCOMPUTE_USER_DATA_6:
return "mmCOMPUTE_USER_DATA_6";
case mmCOMPUTE_USER_DATA_7:
return "mmCOMPUTE_USER_DATA_7";
case mmCOMPUTE_USER_DATA_8:
return "mmCOMPUTE_USER_DATA_8";
case mmCOMPUTE_USER_DATA_9:
return "mmCOMPUTE_USER_DATA_9";
case mmCOMPUTE_USER_DATA_10:
return "mmCOMPUTE_USER_DATA_10";
case mmCOMPUTE_USER_DATA_11:
return "mmCOMPUTE_USER_DATA_11";
case mmCOMPUTE_USER_DATA_12:
return "mmCOMPUTE_USER_DATA_12";
case mmCOMPUTE_USER_DATA_13:
return "mmCOMPUTE_USER_DATA_13";
case mmCOMPUTE_USER_DATA_14:
return "mmCOMPUTE_USER_DATA_14";
case mmCOMPUTE_USER_DATA_15:
return "mmCOMPUTE_USER_DATA_15";
case mmCOMPUTE_NUM_THREAD_X:
return "mmCOMPUTE_NUM_THREAD_X";
case mmCOMPUTE_NUM_THREAD_Y:
return "mmCOMPUTE_NUM_THREAD_Y";
case mmCOMPUTE_NUM_THREAD_Z:
return "mmCOMPUTE_NUM_THREAD_Z";
case mmCOMPUTE_STATIC_THREAD_MGMT_SE0:
return "mmCOMPUTE_STATIC_THREAD_MGMT_SE0";
case mmCOMPUTE_STATIC_THREAD_MGMT_SE1:
return "mmCOMPUTE_STATIC_THREAD_MGMT_SE1";
case mmCOMPUTE_RESOURCE_LIMITS:
return "mmCOMPUTE_RESOURCE_LIMITS";
case mmSPI_SHADER_USER_DATA_VS_0:
return "mmSPI_SHADER_USER_DATA_VS_0";
case mmSPI_SHADER_USER_DATA_VS_1:
return "mmSPI_SHADER_USER_DATA_VS_1";
case mmSPI_SHADER_USER_DATA_VS_2:
return "mmSPI_SHADER_USER_DATA_VS_2";
case mmSPI_SHADER_USER_DATA_VS_3:
return "mmSPI_SHADER_USER_DATA_VS_3";
case mmSPI_SHADER_USER_DATA_VS_4:
return "mmSPI_SHADER_USER_DATA_VS_4";
case mmSPI_SHADER_USER_DATA_VS_5:
return "mmSPI_SHADER_USER_DATA_VS_5";
case mmSPI_SHADER_USER_DATA_VS_6:
return "mmSPI_SHADER_USER_DATA_VS_6";
case mmSPI_SHADER_USER_DATA_VS_7:
return "mmSPI_SHADER_USER_DATA_VS_7";
case mmSPI_SHADER_USER_DATA_VS_8:
return "mmSPI_SHADER_USER_DATA_VS_8";
case mmSPI_SHADER_USER_DATA_VS_9:
return "mmSPI_SHADER_USER_DATA_VS_9";
case mmSPI_SHADER_USER_DATA_VS_10:
return "mmSPI_SHADER_USER_DATA_VS_10";
case mmSPI_SHADER_USER_DATA_VS_11:
return "mmSPI_SHADER_USER_DATA_VS_11";
case mmSPI_SHADER_USER_DATA_VS_12:
return "mmSPI_SHADER_USER_DATA_VS_12";
case mmSPI_SHADER_USER_DATA_VS_13:
return "mmSPI_SHADER_USER_DATA_VS_13";
case mmSPI_SHADER_USER_DATA_VS_14:
return "mmSPI_SHADER_USER_DATA_VS_14";
case mmSPI_SHADER_USER_DATA_VS_15:
return "mmSPI_SHADER_USER_DATA_VS_15";
case mmSPI_SHADER_USER_DATA_HS_0:
return "mmSPI_SHADER_USER_DATA_HS_0";
case mmSPI_SHADER_USER_DATA_HS_1:
return "mmSPI_SHADER_USER_DATA_HS_1";
case mmSPI_SHADER_USER_DATA_HS_9:
return "mmSPI_SHADER_USER_DATA_HS_9";
case mmSPI_SHADER_PGM_RSRC3_GS__CI__VI:
return "mmSPI_SHADER_PGM_RSRC3_GS__CI__VI";
case mmSPI_SHADER_PGM_RSRC3_ES__CI__VI:
return "mmSPI_SHADER_PGM_RSRC3_ES__CI__VI";
case mmSPI_SHADER_PGM_RSRC3_LS__CI__VI:
return "mmSPI_SHADER_PGM_RSRC3_LS__CI__VI";
case mmSPI_SHADER_LATE_ALLOC_VS__CI__VI:
return "mmSPI_SHADER_LATE_ALLOC_VS__CI__VI";
default:
break;
}
return "<UNK>";
}
} // namespace Core::Devtools::Gcn

231
src/core/devtools/layer.cpp Normal file
View File

@ -0,0 +1,231 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <imgui.h>
#include "common/config.h"
#include "common/singleton.h"
#include "common/types.h"
#include "core/debug_state.h"
#include "imgui/imgui_std.h"
#include "imgui_internal.h"
#include "layer.h"
#include "widget/frame_dump.h"
#include "widget/frame_graph.h"
using namespace ImGui;
using namespace Core::Devtools;
using L = Core::Devtools::Layer;
static bool show_simple_fps = false;
static float fps_scale = 1.0f;
static bool show_advanced_debug = false;
static int dump_frame_count = 1;
static Widget::FrameGraph frame_graph;
static std::vector<Widget::FrameDumpViewer> frame_viewers;
static float debug_popup_timing = 3.0f;
void L::DrawMenuBar() {
const auto& ctx = *GImGui;
const auto& io = ctx.IO;
auto isSystemPaused = DebugState.IsGuestThreadsPaused();
if (BeginMainMenuBar()) {
if (BeginMenu("Options")) {
if (MenuItemEx("Emulator Paused", nullptr, nullptr, isSystemPaused)) {
if (isSystemPaused) {
DebugState.ResumeGuestThreads();
} else {
DebugState.PauseGuestThreads();
}
}
ImGui::EndMenu();
}
if (BeginMenu("GPU Tools")) {
MenuItem("Show frame info", nullptr, &frame_graph.is_open);
if (BeginMenu("Dump frames")) {
SliderInt("Count", &dump_frame_count, 1, 5);
if (MenuItem("Dump", "Ctrl+Alt+F9", nullptr, !DebugState.DumpingCurrentFrame())) {
DebugState.RequestFrameDump(dump_frame_count);
}
ImGui::EndMenu();
}
ImGui::EndMenu();
}
EndMainMenuBar();
}
if (IsKeyPressed(ImGuiKey_F9, false)) {
if (io.KeyCtrl && io.KeyAlt) {
if (!DebugState.ShouldPauseInSubmit()) {
DebugState.RequestFrameDump(dump_frame_count);
}
}
if (!io.KeyCtrl && !io.KeyAlt) {
if (isSystemPaused) {
DebugState.ResumeGuestThreads();
} else {
DebugState.PauseGuestThreads();
}
}
}
}
void L::DrawAdvanced() {
DrawMenuBar();
const auto& ctx = *GImGui;
const auto& io = ctx.IO;
auto isSystemPaused = DebugState.IsGuestThreadsPaused();
frame_graph.Draw();
if (isSystemPaused) {
GetForegroundDrawList(GetMainViewport())
->AddText({10.0f, io.DisplaySize.y - 40.0f}, IM_COL32_WHITE, "Emulator paused");
}
if (DebugState.should_show_frame_dump) {
DebugState.should_show_frame_dump = false;
std::unique_lock lock{DebugState.frame_dump_list_mutex};
while (!DebugState.frame_dump_list.empty()) {
auto frame_dump = std::move(DebugState.frame_dump_list.back());
DebugState.frame_dump_list.pop_back();
frame_viewers.emplace_back(frame_dump);
}
}
for (auto it = frame_viewers.begin(); it != frame_viewers.end();) {
if (it->is_open) {
it->Draw();
++it;
} else {
it = frame_viewers.erase(it);
}
}
if (!DebugState.debug_message_popup.empty()) {
if (debug_popup_timing > 0.0f) {
debug_popup_timing -= io.DeltaTime;
if (Begin("##devtools_msg", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove)) {
BringWindowToDisplayFront(GetCurrentWindow());
const auto display_size = io.DisplaySize;
const auto& msg = DebugState.debug_message_popup.front();
const auto padding = GetStyle().WindowPadding;
const auto txt_size = CalcTextSize(&msg.front(), &msg.back() + 1, false, 250.0f);
SetWindowPos({display_size.x - padding.x * 2.0f - txt_size.x, 50.0f});
SetWindowSize({txt_size.x + padding.x * 2.0f, txt_size.y + padding.y * 2.0f});
PushTextWrapPos(250.0f);
TextEx(&msg.front(), &msg.back() + 1);
PopTextWrapPos();
}
End();
} else {
DebugState.debug_message_popup.pop();
debug_popup_timing = 3.0f;
}
}
}
void L::DrawSimple() {
const auto io = GetIO();
Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
}
void L::SetupSettings() {
frame_graph.is_open = true;
ImGuiSettingsHandler handler{};
handler.TypeName = "DevtoolsLayer";
handler.TypeHash = ImHashStr(handler.TypeName);
handler.ReadOpenFn = [](ImGuiContext*, ImGuiSettingsHandler*, const char* name) {
return std::string_view("Data") == name ? (void*)1 : nullptr;
};
handler.ReadLineFn = [](ImGuiContext*, ImGuiSettingsHandler*, void*, const char* line) {
int v;
float f;
if (sscanf(line, "fps_scale=%f", &f) == 1) {
fps_scale = f;
} else if (sscanf(line, "show_advanced_debug=%d", &v) == 1) {
show_advanced_debug = v != 0;
} else if (sscanf(line, "show_frame_graph=%d", &v) == 1) {
frame_graph.is_open = v != 0;
} else if (sscanf(line, "dump_frame_count=%d", &v) == 1) {
dump_frame_count = v;
}
};
handler.WriteAllFn = [](ImGuiContext*, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) {
buf->appendf("[%s][Data]\n", handler->TypeName);
buf->appendf("fps_scale=%f\n", fps_scale);
buf->appendf("show_advanced_debug=%d\n", show_advanced_debug);
buf->appendf("show_frame_graph=%d\n", frame_graph.is_open);
buf->appendf("dump_frame_count=%d\n", dump_frame_count);
buf->append("\n");
};
AddSettingsHandler(&handler);
const ImGuiID dock_id = ImHashStr("FrameDumpDock");
DockBuilderAddNode(dock_id, 0);
DockBuilderSetNodePos(dock_id, ImVec2{50.0, 50.0});
DockBuilderFinish(dock_id);
}
void L::Draw() {
const auto io = GetIO();
PushID("DevtoolsLayer");
if (!DebugState.IsGuestThreadsPaused()) {
const auto fn = DebugState.flip_frame_count.load();
frame_graph.AddFrame(fn, io.DeltaTime);
}
if (IsKeyPressed(ImGuiKey_F10, false)) {
if (io.KeyCtrl) {
show_advanced_debug = !show_advanced_debug;
} else {
show_simple_fps = !show_simple_fps;
}
}
if (show_simple_fps) {
if (Begin("Video Info", nullptr,
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) {
SetWindowPos("Video Info", {999999.0f, 0.0f}, ImGuiCond_FirstUseEver);
if (BeginPopupContextWindow()) {
#define M(label, value) \
if (MenuItem(label, nullptr, fps_scale == value)) \
fps_scale = value
M("0.5x", 0.5f);
M("1.0x", 1.0f);
M("1.5x", 1.5f);
M("2.0x", 2.0f);
M("2.5x", 2.5f);
EndPopup();
#undef M
}
KeepWindowInside();
SetWindowFontScale(fps_scale);
DrawSimple();
}
End();
}
if (show_advanced_debug) {
PushFont(io.Fonts->Fonts[IMGUI_FONT_MONO]);
PushID("DevtoolsLayer");
DrawAdvanced();
PopID();
PopFont();
}
PopID();
}

24
src/core/devtools/layer.h Normal file
View File

@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "imgui/imgui_layer.h"
namespace Core::Devtools {
class Layer final : public ImGui::Layer {
static void DrawMenuBar();
static void DrawAdvanced();
static void DrawSimple();
public:
static void SetupSettings();
void Draw() override;
};
} // namespace Core::Devtools

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Credits to https://github.com/psucien/tlg-emu-tools/
#pragma once
#include <vector>
#include "common/types.h"
#include "video_core/buffer_cache/buffer_cache.h"
namespace AmdGpu {
union PM4Type3Header;
enum class PM4ItOpcode : u32;
} // namespace AmdGpu
namespace Core::Devtools::Widget {
class FrameDumpViewer;
class CmdListViewer {
/*
* Generic PM4 header
*/
union PM4Header {
struct {
u32 reserved : 16;
u32 count : 14;
u32 type : 2; // PM4_TYPE
};
u32 u32All;
};
struct BatchInfo {
std::string marker{};
size_t start_addr;
size_t end_addr;
size_t command_addr;
AmdGpu::PM4ItOpcode type;
bool bypass{false};
};
FrameDumpViewer* parent;
std::vector<BatchInfo> batches{};
uintptr_t cmdb_addr;
size_t cmdb_size;
int batch_bp{-1};
int vqid{255};
void OnNop(AmdGpu::PM4Type3Header const* header, u32 const* body);
void OnSetBase(AmdGpu::PM4Type3Header const* header, u32 const* body);
void OnSetContextReg(AmdGpu::PM4Type3Header const* header, u32 const* body);
void OnSetShReg(AmdGpu::PM4Type3Header const* header, u32 const* body);
void OnDispatch(AmdGpu::PM4Type3Header const* header, u32 const* body);
public:
explicit CmdListViewer(FrameDumpViewer* parent, const std::vector<u32>& cmd_list);
void Draw();
};
} // namespace Core::Devtools::Widget

View File

@ -0,0 +1,191 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstdio>
#include <fmt/chrono.h>
#include <imgui.h>
#include <magic_enum.hpp>
#include "common/io_file.h"
#include "frame_dump.h"
#include "imgui_internal.h"
#include "imgui_memory_editor.h"
using namespace ImGui;
using namespace DebugStateType;
#define C_V(label, value, var, out) \
if (Selectable(label, var == value)) { \
var = value; \
selected_cmd = -1; \
out = true; \
}
// 00 to 99
static std::array<char, 3> small_int_to_str(const s32 i) {
std::array<char, 3> label{};
if (i == -1) {
label[0] = 'N';
label[1] = 'A';
} else {
label[0] = i / 10 + '0';
label[1] = i % 10 + '0';
}
return label;
}
namespace Core::Devtools::Widget {
FrameDumpViewer::FrameDumpViewer(FrameDump _frame_dump) : frame_dump(std::move(_frame_dump)) {
static int unique_id = 0;
id = unique_id++;
selected_queue_type = QueueType::dcb;
selected_submit_num = 0;
selected_queue_num2 = 0;
cmd_list_viewer.reserve(frame_dump.queues.size());
for (const auto& cmd : frame_dump.queues) {
cmd_list_viewer.emplace_back(this, cmd.data);
if (cmd.type == QueueType::dcb && cmd.submit_num == selected_submit_num &&
cmd.num2 == selected_queue_num2) {
selected_cmd = cmd_list_viewer.size() - 1;
}
}
cmdb_view.Open = false;
cmdb_view.ReadOnly = true;
}
FrameDumpViewer::~FrameDumpViewer() {}
void FrameDumpViewer::Draw() {
if (!is_open) {
return;
}
char name[32];
snprintf(name, sizeof(name), "Frame #%d dump", id);
static ImGuiID dock_id = ImHashStr("FrameDumpDock");
SetNextWindowDockID(dock_id, ImGuiCond_Appearing);
if (Begin(name, &is_open, ImGuiWindowFlags_NoSavedSettings)) {
if (IsWindowAppearing()) {
auto window = GetCurrentWindow();
SetWindowSize(window, ImVec2{470.0f, 600.0f});
}
BeginGroup();
TextEx("Queue type");
SameLine();
if (BeginCombo("##select_queue_type", magic_enum::enum_name(selected_queue_type).data(),
ImGuiComboFlags_WidthFitPreview)) {
bool selected = false;
#define COMBO(x) C_V(magic_enum::enum_name(x).data(), x, selected_queue_type, selected)
COMBO(QueueType::acb)
COMBO(QueueType::dcb);
COMBO(QueueType::ccb);
if (selected) {
selected_submit_num = selected_queue_num2 = -1;
}
EndCombo();
}
SameLine();
TextEx("Submit num");
SameLine();
if (BeginCombo("##select_submit_num", small_int_to_str(selected_submit_num).data(),
ImGuiComboFlags_WidthFitPreview)) {
std::array<bool, 32> available_submits{};
for (const auto& cmd : frame_dump.queues) {
if (cmd.type == selected_queue_type) {
available_submits[cmd.submit_num] = true;
}
}
bool selected = false;
for (int i = 0; i < available_submits.size(); ++i) {
if (available_submits[i]) {
char label[3]{};
label[0] = i / 10 + '0';
label[1] = i % 10 + '0';
C_V(label, i, selected_submit_num, selected);
}
}
if (selected) {
selected_queue_num2 = -1;
}
EndCombo();
}
SameLine();
TextEx(selected_queue_type == QueueType::acb ? "Queue num" : "Buffer num");
SameLine();
if (BeginCombo("##select_queue_num2", small_int_to_str(selected_queue_num2).data(),
ImGuiComboFlags_WidthFitPreview)) {
std::array<bool, 32> available_queues{};
for (const auto& cmd : frame_dump.queues) {
if (cmd.type == selected_queue_type && cmd.submit_num == selected_submit_num) {
available_queues[cmd.num2] = true;
}
}
bool selected = false;
for (int i = 0; i < available_queues.size(); ++i) {
if (available_queues[i]) {
char label[3]{};
label[0] = i / 10 + '0';
label[1] = i % 10 + '0';
C_V(label, i, selected_queue_num2, selected);
}
}
if (selected) {
const auto it = std::ranges::find_if(frame_dump.queues, [&](const auto& cmd) {
return cmd.type == selected_queue_type &&
cmd.submit_num == selected_submit_num && cmd.num2 == selected_queue_num2;
});
if (it != frame_dump.queues.end()) {
selected_cmd = std::distance(frame_dump.queues.begin(), it);
}
}
EndCombo();
}
SameLine();
BeginDisabled(selected_cmd == -1);
if (SmallButton("Dump cmd")) {
auto now_time = fmt::localtime(std::time(nullptr));
const auto fname = fmt::format("{:%F %H-%M-%S} {}_{}_{}.bin", now_time,
magic_enum::enum_name(selected_queue_type),
selected_submit_num, selected_queue_num2);
Common::FS::IOFile file(fname, Common::FS::FileAccessMode::Write);
auto& data = frame_dump.queues[selected_cmd].data;
if (file.IsOpen()) {
DebugState.ShowDebugMessage(fmt::format("Dumping cmd as {}", fname));
file.Write(data);
} else {
DebugState.ShowDebugMessage(fmt::format("Failed to save {}", fname));
LOG_ERROR(Core, "Failed to open file {}", fname);
}
}
EndDisabled();
EndGroup();
if (selected_cmd != -1) {
cmd_list_viewer[selected_cmd].Draw();
}
}
End();
if (cmdb_view.Open && selected_cmd != -1) {
auto& cmd = frame_dump.queues[selected_cmd].data;
auto cmd_size = cmd.size() * sizeof(u32);
MemoryEditor::Sizes s;
cmdb_view.CalcSizes(s, cmd_size, (size_t)cmd.data());
SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(s.WindowWidth, FLT_MAX));
char name[64];
snprintf(name, sizeof(name), "[GFX] Command buffer %d###cmdbuf_hex_%d", id, id);
if (Begin(name, &cmdb_view.Open, ImGuiWindowFlags_NoScrollbar)) {
cmdb_view.DrawContents(cmd.data(), cmd_size, (size_t)cmd.data());
}
End();
}
}
} // namespace Core::Devtools::Widget
#undef C_V

View File

@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <vector>
#include "cmd_list.h"
#include "core/debug_state.h"
#include "imgui_memory_editor.h"
namespace Core::Devtools::Widget {
class CmdListViewer;
class FrameDumpViewer {
friend class CmdListViewer;
DebugStateType::FrameDump frame_dump;
int id;
std::vector<CmdListViewer> cmd_list_viewer;
MemoryEditor cmdb_view;
DebugStateType::QueueType selected_queue_type;
s32 selected_submit_num;
s32 selected_queue_num2;
s32 selected_cmd = -1;
public:
bool is_open = true;
explicit FrameDumpViewer(DebugStateType::FrameDump frame_dump);
~FrameDumpViewer();
void Draw();
};
} // namespace Core::Devtools::Widget

View File

@ -0,0 +1,99 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "frame_graph.h"
#include "common/config.h"
#include "common/singleton.h"
#include "core/debug_state.h"
#include "imgui.h"
#include "imgui_internal.h"
using namespace ImGui;
namespace Core::Devtools::Widget {
constexpr float TARGET_FPS = 60.0f;
constexpr float BAR_WIDTH_MULT = 1.4f;
constexpr float BAR_HEIGHT_MULT = 1.25f;
constexpr float FRAME_GRAPH_PADDING_Y = 3.0f;
constexpr static float FRAME_GRAPH_HEIGHT = 50.0f;
void FrameGraph::Draw() {
if (!is_open) {
return;
}
SetNextWindowSize({340.0, 185.0f}, ImGuiCond_FirstUseEver);
if (Begin("Video debug info", &is_open)) {
const auto& ctx = *GImGui;
const auto& io = ctx.IO;
const auto& window = *ctx.CurrentWindow;
auto& draw_list = *window.DrawList;
auto isSystemPaused = DebugState.IsGuestThreadsPaused();
static float deltaTime;
static float frameRate;
if (!isSystemPaused) {
deltaTime = io.DeltaTime * 1000.0f;
frameRate = 1000.0f / deltaTime;
}
Text("Frame time: %.3f ms (%.1f FPS)", deltaTime, frameRate);
Text("Flip frame: %d Gnm submit frame: %d", DebugState.flip_frame_count.load(),
DebugState.gnm_frame_count.load());
SeparatorText("Frame graph");
const float full_width = GetContentRegionAvail().x;
// Frame graph - inspired by
// https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times
auto pos = GetCursorScreenPos();
const ImVec2 size{full_width, FRAME_GRAPH_HEIGHT + FRAME_GRAPH_PADDING_Y * 2.0f};
ItemSize(size);
if (!ItemAdd({pos, pos + size}, GetID("FrameGraph"))) {
return;
}
float target_dt = 1.0f / (TARGET_FPS * (float)Config::vblankDiv());
float cur_pos_x = pos.x + full_width;
pos.y += FRAME_GRAPH_PADDING_Y;
const float final_pos_y = pos.y + FRAME_GRAPH_HEIGHT;
draw_list.AddRectFilled({pos.x, pos.y - FRAME_GRAPH_PADDING_Y},
{pos.x + full_width, final_pos_y + FRAME_GRAPH_PADDING_Y},
IM_COL32(0x33, 0x33, 0x33, 0xFF));
draw_list.PushClipRect({pos.x, pos.y}, {pos.x + full_width, final_pos_y}, true);
for (u32 i = 0; i < FRAME_BUFFER_SIZE; ++i) {
const auto& frame_info = frame_list[(DebugState.GetFrameNum() - i) % FRAME_BUFFER_SIZE];
const float dt_factor = target_dt / frame_info.delta;
const float width = std::ceil(BAR_WIDTH_MULT / dt_factor);
const float height =
std::min(std::log2(BAR_HEIGHT_MULT / dt_factor) / 3.0f, 1.0f) * FRAME_GRAPH_HEIGHT;
ImU32 color;
if (dt_factor >= 0.95f) { // BLUE
color = IM_COL32(0x33, 0x33, 0xFF, 0xFF);
} else if (dt_factor >= 0.5f) { // GREEN <> YELLOW
float t = 1.0f - (dt_factor - 0.5f) * 2.0f;
int r = (int)(0xFF * t);
color = IM_COL32(r, 0xFF, 0, 0xFF);
} else { // YELLOW <> RED
float t = dt_factor * 2.0f;
int g = (int)(0xFF * t);
color = IM_COL32(0xFF, g, 0, 0xFF);
}
draw_list.AddRectFilled({cur_pos_x - width, final_pos_y - height},
{cur_pos_x, final_pos_y}, color);
cur_pos_x -= width;
if (cur_pos_x < width) {
break;
}
}
draw_list.PopClipRect();
}
End();
}
} // namespace Core::Devtools::Widget

View File

@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Devtools::Widget {
class FrameGraph {
static constexpr u32 FRAME_BUFFER_SIZE = 1024;
struct FrameInfo {
u32 num;
float delta;
};
std::array<FrameInfo, FRAME_BUFFER_SIZE> frame_list{};
public:
bool is_open = true;
void Draw();
void AddFrame(u32 num, float delta) {
frame_list[num % FRAME_BUFFER_SIZE] = FrameInfo{num, delta};
}
};
} // namespace Core::Devtools::Widget

View File

@ -0,0 +1,942 @@
// SPDX-FileCopyrightText: 2024 Dear ImGui Club Contributors
// SPDX-License-Identifier: MIT
// Mini memory editor for Dear ImGui (to embed in your game/tools)
// Get latest version at http://www.github.com/ocornut/imgui_club
// Licensed under The MIT License (MIT)
// Right-click anywhere to access the Options menu!
// You can adjust the keyboard repeat delay/rate in ImGuiIO.
// The code assume a mono-space font for simplicity!
// If you don't use the default font, use ImGui::PushFont()/PopFont() to switch to a mono-space font
// before calling this.
//
// Usage:
// // Create a window and draw memory editor inside it:
// static MemoryEditor mem_edit_1;
// static char data[0x10000];
// size_t data_size = 0x10000;
// mem_edit_1.DrawWindow("Memory Editor", data, data_size);
//
// Usage:
// // If you already have a window, use DrawContents() instead:
// static MemoryEditor mem_edit_2;
// ImGui::Begin("MyWindow")
// mem_edit_2.DrawContents(this, sizeof(*this), (size_t)this);
// ImGui::End();
//
// Changelog:
// - v0.10: initial version
// - v0.23 (2017/08/17): added to github. fixed right-arrow triggering a byte write.
// - v0.24 (2018/06/02): changed DragInt("Rows" to use a %d data format (which is desirable since
// imgui 1.61).
// - v0.25 (2018/07/11): fixed wording: all occurrences of "Rows" renamed to "Columns".
// - v0.26 (2018/08/02): fixed clicking on hex region
// - v0.30 (2018/08/02): added data preview for common data types
// - v0.31 (2018/10/10): added OptUpperCaseHex option to select lower/upper casing display
// [@samhocevar]
// - v0.32 (2018/10/10): changed signatures to use void* instead of unsigned char*
// - v0.33 (2018/10/10): added OptShowOptions option to hide all the interactive option setting.
// - v0.34 (2019/05/07): binary preview now applies endianness setting [@nicolasnoble]
// - v0.35 (2020/01/29): using ImGuiDataType available since Dear ImGui 1.69.
// - v0.36 (2020/05/05): minor tweaks, minor refactor.
// - v0.40 (2020/10/04): fix misuse of ImGuiListClipper API, broke with Dear ImGui 1.79. made cursor
// position appears on left-side of edit box. option popup appears on mouse release. fix MSVC
// warnings where _CRT_SECURE_NO_WARNINGS wasn't working in recent versions.
// - v0.41 (2020/10/05): fix when using with keyboard/gamepad navigation enabled.
// - v0.42 (2020/10/14): fix for . character in ASCII view always being greyed out.
// - v0.43 (2021/03/12): added OptFooterExtraHeight to allow for custom drawing at the bottom of the
// editor [@leiradel]
// - v0.44 (2021/03/12): use ImGuiInputTextFlags_AlwaysOverwrite in 1.82 + fix hardcoded width.
// - v0.50 (2021/11/12): various fixes for recent dear imgui versions (fixed misuse of clipper,
// relying on SetKeyboardFocusHere() handling scrolling from 1.85). added default size.
// - v0.51 (2024/02/22): fix for layout change in 1.89 when using IMGUI_DISABLE_OBSOLETE_FUNCTIONS.
// (#34)
// - v0.52 (2024/03/08): removed unnecessary GetKeyIndex() calls, they are a no-op since 1.87.
// - v0.53 (2024/05/27): fixed right-click popup from not appearing when using DrawContents().
// warning fixes. (#35)
// - v0.54 (2024/07/29): allow ReadOnly mode to still select and preview data. (#46) [@DeltaGW2])
// - v0.55 (2024/08/19): added BgColorFn to allow setting background colors independently from
// highlighted selection. (#27) [@StrikerX3]
// added MouseHoveredAddr public readable field. (#47, #27) [@StrikerX3]
// fixed a data preview crash with 1.91.0 WIP. fixed contiguous highlight
// color when using data preview. *BREAKING* added UserData field passed to
// all optional function handlers: ReadFn, WriteFn, HighlightFn, BgColorFn.
// (#50) [@silverweed]
//
// TODO:
// - This is generally old/crappy code, it should work but isn't very good.. to be rewritten some
// day.
// - PageUp/PageDown are not supported because we use _NoNav. This is a good test scenario for
// working out idioms of how to mix natural nav and our own...
// - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text
// cursor appear at position 1 for one frame.
// - Using InputText() is awkward and maybe overkill here, consider implementing something custom.
#pragma once
#include <stdint.h> // uint8_t, etc.
#include <stdio.h> // sprintf, scanf
#if defined(_MSC_VER) || defined(_UCRT)
#define _PRISizeT "I"
#define ImSnprintf _snprintf
#else
#define _PRISizeT "z"
#define ImSnprintf snprintf
#endif
#if defined(_MSC_VER) || defined(_UCRT)
#pragma warning(push)
#pragma warning( \
disable : 4996) // warning C4996: 'sprintf': This function or variable may be unsafe.
#endif
struct MemoryEditor {
enum DataFormat {
DataFormat_Bin = 0,
DataFormat_Dec = 1,
DataFormat_Hex = 2,
DataFormat_COUNT
};
// Settings
bool Open; // = true // set to false when DrawWindow() was closed. ignore if not using
// DrawWindow().
bool ReadOnly; // = false // disable any editing.
int Cols; // = 16 // number of columns to display.
bool OptShowOptions; // = true // display options button/context menu. when disabled, options
// will be locked unless you provide your own UI for them.
bool OptShowDataPreview; // = false // display a footer previewing the decimal/binary/hex/float
// representation of the currently selected bytes.
bool OptShowHexII; // = false // display values in HexII representation instead of regular
// hexadecimal: hide null/zero bytes, ascii values as ".X".
bool OptShowAscii; // = true // display ASCII representation on the right side.
bool OptGreyOutZeroes; // = true // display null/zero bytes using the TextDisabled color.
bool OptUpperCaseHex; // = true // display hexadecimal values as "FF" instead of "ff".
int OptMidColsCount; // = 8 // set to 0 to disable extra spacing between every mid-cols.
int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated
// based on maximum displayed addr).
float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add
// custom widgets
ImU32 HighlightColor; // // background color of highlighted bytes.
// Function handlers
ImU8 (*ReadFn)(const ImU8* mem, size_t off,
void* user_data); // = 0 // optional handler to read bytes.
void (*WriteFn)(ImU8* mem, size_t off, ImU8 d,
void* user_data); // = 0 // optional handler to write bytes.
bool (*HighlightFn)(const ImU8* mem, size_t off,
void* user_data); // = 0 // optional handler to return Highlight
// property (to support non-contiguous highlighting).
ImU32 (*BgColorFn)(const ImU8* mem, size_t off,
void* user_data); // = 0 // optional handler to return custom background
// color of individual bytes.
void* UserData; // = NULL // user data forwarded to the function handlers
// Public read-only data
bool MouseHovered; // set when mouse is hovering a value.
size_t MouseHoveredAddr; // the address currently being hovered if MouseHovered is set.
// [Internal State]
bool ContentsWidthChanged;
size_t DataPreviewAddr;
size_t DataEditingAddr;
bool DataEditingTakeFocus;
char DataInputBuf[32];
char AddrInputBuf[32];
size_t GotoAddr;
size_t HighlightMin, HighlightMax;
int PreviewEndianness;
ImGuiDataType PreviewDataType;
MemoryEditor() {
// Settings
Open = true;
ReadOnly = false;
Cols = 16;
OptShowOptions = true;
OptShowDataPreview = false;
OptShowHexII = false;
OptShowAscii = true;
OptGreyOutZeroes = true;
OptUpperCaseHex = true;
OptMidColsCount = 8;
OptAddrDigitsCount = 0;
OptFooterExtraHeight = 0.0f;
HighlightColor = IM_COL32(255, 255, 255, 50);
ReadFn = nullptr;
WriteFn = nullptr;
HighlightFn = nullptr;
BgColorFn = nullptr;
UserData = nullptr;
// State/Internals
ContentsWidthChanged = false;
DataPreviewAddr = DataEditingAddr = (size_t)-1;
DataEditingTakeFocus = false;
memset(DataInputBuf, 0, sizeof(DataInputBuf));
memset(AddrInputBuf, 0, sizeof(AddrInputBuf));
GotoAddr = (size_t)-1;
MouseHovered = false;
MouseHoveredAddr = 0;
HighlightMin = HighlightMax = (size_t)-1;
PreviewEndianness = 0;
PreviewDataType = ImGuiDataType_S32;
}
void GotoAddrAndHighlight(size_t addr_min, size_t addr_max) {
GotoAddr = addr_min;
HighlightMin = addr_min;
HighlightMax = addr_max;
}
struct Sizes {
int AddrDigitsCount;
float LineHeight;
float GlyphWidth;
float HexCellWidth;
float SpacingBetweenMidCols;
float PosHexStart;
float PosHexEnd;
float PosAsciiStart;
float PosAsciiEnd;
float WindowWidth;
Sizes() {
memset(this, 0, sizeof(*this));
}
};
void CalcSizes(Sizes& s, size_t mem_size, size_t base_display_addr) {
ImGuiStyle& style = ImGui::GetStyle();
s.AddrDigitsCount = OptAddrDigitsCount;
if (s.AddrDigitsCount == 0)
for (size_t n = base_display_addr + mem_size - 1; n > 0; n >>= 4)
s.AddrDigitsCount++;
s.LineHeight = ImGui::GetTextLineHeight();
s.GlyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space
s.HexCellWidth =
(float)(int)(s.GlyphWidth * 2.5f); // "FF " we include trailing space in the width to
// easily catch clicks everywhere
s.SpacingBetweenMidCols =
(float)(int)(s.HexCellWidth *
0.25f); // Every OptMidColsCount columns we add a bit of extra spacing
s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth;
s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols);
s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd;
if (OptShowAscii) {
s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1;
if (OptMidColsCount > 0)
s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) *
s.SpacingBetweenMidCols;
s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth;
}
s.WindowWidth =
s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth;
}
// Standalone Memory Editor window
void DrawWindow(const char* title, void* mem_data, size_t mem_size,
size_t base_display_addr = 0x0000) {
Sizes s;
CalcSizes(s, mem_size, base_display_addr);
ImGui::SetNextWindowSize(ImVec2(s.WindowWidth, s.WindowWidth * 0.60f),
ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(s.WindowWidth, FLT_MAX));
Open = true;
if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar)) {
DrawContents(mem_data, mem_size, base_display_addr);
if (ContentsWidthChanged) {
CalcSizes(s, mem_size, base_display_addr);
ImGui::SetWindowSize(ImVec2(s.WindowWidth, ImGui::GetWindowSize().y));
}
}
ImGui::End();
}
// Memory Editor contents only
void DrawContents(void* mem_data_void, size_t mem_size, size_t base_display_addr = 0x0000) {
if (Cols < 1)
Cols = 1;
ImU8* mem_data = (ImU8*)mem_data_void;
Sizes s;
CalcSizes(s, mem_size, base_display_addr);
ImGuiStyle& style = ImGui::GetStyle();
const ImVec2 contents_pos_start = ImGui::GetCursorScreenPos();
// We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent
// click from moving the window. This is used as a facility since our main click detection
// code doesn't assign an ActiveId so the click would normally be caught as a window-move.
const float height_separator = style.ItemSpacing.y;
float footer_height = OptFooterExtraHeight;
if (OptShowOptions)
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1;
if (OptShowDataPreview)
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 +
ImGui::GetTextLineHeightWithSpacing() * 3;
ImGui::BeginChild("##scrolling", ImVec2(-FLT_MIN, -footer_height), ImGuiChildFlags_None,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
// We are not really using the clipper API correctly here, because we rely on
// visible_start_addr/visible_end_addr for our scrolling function.
const int line_total_count = (int)((mem_size + Cols - 1) / Cols);
ImGuiListClipper clipper;
clipper.Begin(line_total_count, s.LineHeight);
bool data_next = false;
if (DataEditingAddr >= mem_size)
DataEditingAddr = (size_t)-1;
if (DataPreviewAddr >= mem_size)
DataPreviewAddr = (size_t)-1;
size_t preview_data_type_size = OptShowDataPreview ? DataTypeGetSize(PreviewDataType) : 0;
size_t data_editing_addr_next = (size_t)-1;
if (DataEditingAddr != (size_t)-1) {
// Move cursor but only apply on next frame so scrolling with be synchronized (because
// currently we can't change the scrolling while the window is being rendered)
if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) &&
(ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) {
data_editing_addr_next = DataEditingAddr - Cols;
} else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) &&
(ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols) {
data_editing_addr_next = DataEditingAddr + Cols;
} else if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow) &&
(ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) {
data_editing_addr_next = DataEditingAddr - 1;
} else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) &&
(ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) {
data_editing_addr_next = DataEditingAddr + 1;
}
}
// Draw vertical separator
ImVec2 window_pos = ImGui::GetWindowPos();
if (OptShowAscii)
draw_list->AddLine(
ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y),
ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y + 9999),
ImGui::GetColorU32(ImGuiCol_Border));
const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text);
const ImU32 color_disabled =
OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text;
const char* format_address =
OptUpperCaseHex ? "%0*" _PRISizeT "X: " : "%0*" _PRISizeT "x: ";
const char* format_data = OptUpperCaseHex ? "%0*" _PRISizeT "X" : "%0*" _PRISizeT "x";
const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x";
const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x ";
MouseHovered = false;
MouseHoveredAddr = 0;
while (clipper.Step())
for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd;
line_i++) // display only visible lines
{
size_t addr = (size_t)line_i * Cols;
ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr);
// Draw Hexadecimal
for (int n = 0; n < Cols && addr < mem_size; n++, addr++) {
float byte_pos_x = s.PosHexStart + s.HexCellWidth * n;
if (OptMidColsCount > 0)
byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols;
ImGui::SameLine(byte_pos_x);
// Draw highlight or custom background color
const bool is_highlight_from_user_range =
(addr >= HighlightMin && addr < HighlightMax);
const bool is_highlight_from_user_func =
(HighlightFn && HighlightFn(mem_data, addr, UserData));
const bool is_highlight_from_preview =
(addr >= DataPreviewAddr &&
addr < DataPreviewAddr + preview_data_type_size);
ImU32 bg_color = 0;
bool is_next_byte_highlighted = false;
if (is_highlight_from_user_range || is_highlight_from_user_func ||
is_highlight_from_preview) {
is_next_byte_highlighted =
(addr + 1 < mem_size) &&
((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) ||
(HighlightFn && HighlightFn(mem_data, addr + 1, UserData)) ||
(addr + 1 < DataPreviewAddr + preview_data_type_size));
bg_color = HighlightColor;
} else if (BgColorFn != nullptr) {
is_next_byte_highlighted =
(addr + 1 < mem_size) &&
((BgColorFn(mem_data, addr + 1, UserData) & IM_COL32_A_MASK) != 0);
bg_color = BgColorFn(mem_data, addr, UserData);
}
if (bg_color != 0) {
float bg_width = s.GlyphWidth * 2;
if (is_next_byte_highlighted || (n + 1 == Cols)) {
bg_width = s.HexCellWidth;
if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols &&
((n + 1) % OptMidColsCount) == 0)
bg_width += s.SpacingBetweenMidCols;
}
ImVec2 pos = ImGui::GetCursorScreenPos();
draw_list->AddRectFilled(
pos, ImVec2(pos.x + bg_width, pos.y + s.LineHeight), bg_color);
}
if (DataEditingAddr == addr) {
// Display text input on current byte
bool data_write = false;
ImGui::PushID((void*)addr);
if (DataEditingTakeFocus) {
ImGui::SetKeyboardFocusHere(0);
ImSnprintf(AddrInputBuf, 32, format_data, s.AddrDigitsCount,
base_display_addr + addr);
ImSnprintf(DataInputBuf, 32, format_byte,
ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]);
}
struct InputTextUserData {
// FIXME: We should have a way to retrieve the text edit cursor position
// more easily in the API, this is rather tedious. This is such a ugly
// mess we may be better off not using InputText() at all here.
static int Callback(ImGuiInputTextCallbackData* data) {
InputTextUserData* user_data = (InputTextUserData*)data->UserData;
if (!data->HasSelection())
user_data->CursorPos = data->CursorPos;
#if IMGUI_VERSION_NUM < 19102
if (data->Flags & ImGuiInputTextFlags_ReadOnly)
return 0;
#endif
if (data->SelectionStart == 0 &&
data->SelectionEnd == data->BufTextLen) {
// When not editing a byte, always refresh its InputText content
// pulled from underlying memory data (this is a bit tricky,
// since InputText technically "owns" the master copy of the
// buffer we edit it in there)
data->DeleteChars(0, data->BufTextLen);
data->InsertChars(0, user_data->CurrentBufOverwrite);
data->SelectionStart = 0;
data->SelectionEnd = 2;
data->CursorPos = 0;
}
return 0;
}
char CurrentBufOverwrite[3]; // Input
int CursorPos; // Output
};
InputTextUserData input_text_user_data;
input_text_user_data.CursorPos = -1;
ImSnprintf(input_text_user_data.CurrentBufOverwrite, 3, format_byte,
ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]);
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal |
ImGuiInputTextFlags_EnterReturnsTrue |
ImGuiInputTextFlags_AutoSelectAll |
ImGuiInputTextFlags_NoHorizontalScroll |
ImGuiInputTextFlags_CallbackAlways;
if (ReadOnly)
flags |= ImGuiInputTextFlags_ReadOnly;
flags |=
ImGuiInputTextFlags_AlwaysOverwrite; // was
// ImGuiInputTextFlags_AlwaysInsertMode
ImGui::SetNextItemWidth(s.GlyphWidth * 2);
if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf),
flags, InputTextUserData::Callback,
&input_text_user_data))
data_write = data_next = true;
else if (!DataEditingTakeFocus && !ImGui::IsItemActive())
DataEditingAddr = data_editing_addr_next = (size_t)-1;
DataEditingTakeFocus = false;
if (input_text_user_data.CursorPos >= 2)
data_write = data_next = true;
if (data_editing_addr_next != (size_t)-1)
data_write = data_next = false;
unsigned int data_input_value = 0;
if (!ReadOnly && data_write &&
sscanf(DataInputBuf, "%X", &data_input_value) == 1) {
if (WriteFn)
WriteFn(mem_data, addr, (ImU8)data_input_value, UserData);
else
mem_data[addr] = (ImU8)data_input_value;
}
ImGui::PopID();
} else {
// NB: The trailing space is not visible but ensure there's no gap that the
// mouse cannot click on.
ImU8 b = ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr];
if (OptShowHexII) {
if ((b >= 32 && b < 128))
ImGui::Text(".%c ", b);
else if (b == 0xFF && OptGreyOutZeroes)
ImGui::TextDisabled("## ");
else if (b == 0x00)
ImGui::Text(" ");
else
ImGui::Text(format_byte_space, b);
} else {
if (b == 0 && OptGreyOutZeroes)
ImGui::TextDisabled("00 ");
else
ImGui::Text(format_byte_space, b);
}
if (ImGui::IsItemHovered()) {
MouseHovered = true;
MouseHoveredAddr = addr;
if (ImGui::IsMouseClicked(0)) {
DataEditingTakeFocus = true;
data_editing_addr_next = addr;
}
}
}
}
if (OptShowAscii) {
// Draw ASCII values
ImGui::SameLine(s.PosAsciiStart);
ImVec2 pos = ImGui::GetCursorScreenPos();
addr = (size_t)line_i * Cols;
const float mouse_off_x = ImGui::GetIO().MousePos.x - pos.x;
const size_t mouse_addr =
(mouse_off_x >= 0.0f && mouse_off_x < s.PosAsciiEnd - s.PosAsciiStart)
? addr + (size_t)(mouse_off_x / s.GlyphWidth)
: (size_t)-1;
ImGui::PushID(line_i);
if (ImGui::InvisibleButton(
"ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight))) {
DataEditingAddr = DataPreviewAddr = mouse_addr;
DataEditingTakeFocus = true;
}
if (ImGui::IsItemHovered()) {
MouseHovered = true;
MouseHoveredAddr = mouse_addr;
}
ImGui::PopID();
for (int n = 0; n < Cols && addr < mem_size; n++, addr++) {
if (addr == DataEditingAddr) {
draw_list->AddRectFilled(
pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight),
ImGui::GetColorU32(ImGuiCol_FrameBg));
draw_list->AddRectFilled(
pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight),
ImGui::GetColorU32(ImGuiCol_TextSelectedBg));
} else if (BgColorFn) {
draw_list->AddRectFilled(
pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight),
BgColorFn(mem_data, addr, UserData));
}
unsigned char c =
ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr];
char display_c = (c < 32 || c >= 128) ? '.' : c;
draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled,
&display_c, &display_c + 1);
pos.x += s.GlyphWidth;
}
}
}
ImGui::PopStyleVar(2);
const float child_width = ImGui::GetWindowSize().x;
ImGui::EndChild();
// Notify the main window of our ideal child content size (FIXME: we are missing an API to
// get the contents size from the child)
ImGui::SetCursorPosX(s.WindowWidth);
ImGui::Dummy(ImVec2(0.0f, 0.0f));
if (data_next && DataEditingAddr + 1 < mem_size) {
DataEditingAddr = DataPreviewAddr = DataEditingAddr + 1;
DataEditingTakeFocus = true;
} else if (data_editing_addr_next != (size_t)-1) {
DataEditingAddr = DataPreviewAddr = data_editing_addr_next;
DataEditingTakeFocus = true;
}
const bool lock_show_data_preview = OptShowDataPreview;
if (OptShowOptions) {
ImGui::Separator();
DrawOptionsLine(s, mem_data, mem_size, base_display_addr);
}
if (lock_show_data_preview) {
ImGui::Separator();
DrawPreviewLine(s, mem_data, mem_size, base_display_addr);
}
const ImVec2 contents_pos_end(contents_pos_start.x + child_width,
ImGui::GetCursorScreenPos().y);
// ImGui::GetForegroundDrawList()->AddRect(contents_pos_start, contents_pos_end,
// IM_COL32(255, 0, 0, 255));
if (OptShowOptions)
if (ImGui::IsMouseHoveringRect(contents_pos_start, contents_pos_end))
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) &&
ImGui::IsMouseReleased(ImGuiMouseButton_Right))
ImGui::OpenPopup("OptionsPopup");
if (ImGui::BeginPopup("OptionsPopup")) {
ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f);
if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) {
ContentsWidthChanged = true;
if (Cols < 1)
Cols = 1;
}
ImGui::Checkbox("Show Data Preview", &OptShowDataPreview);
ImGui::Checkbox("Show HexII", &OptShowHexII);
if (ImGui::Checkbox("Show Ascii", &OptShowAscii)) {
ContentsWidthChanged = true;
}
ImGui::Checkbox("Grey out zeroes", &OptGreyOutZeroes);
ImGui::Checkbox("Uppercase Hex", &OptUpperCaseHex);
ImGui::EndPopup();
}
}
void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size,
size_t base_display_addr) {
IM_UNUSED(mem_data);
ImGuiStyle& style = ImGui::GetStyle();
const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X"
: "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x";
// Options menu
if (ImGui::Button("Options"))
ImGui::OpenPopup("OptionsPopup");
ImGui::SameLine();
ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount,
base_display_addr + mem_size - 1);
ImGui::SameLine();
ImGui::SetNextItemWidth((s.AddrDigitsCount + 1) * s.GlyphWidth +
style.FramePadding.x * 2.0f);
if (ImGui::InputText("##addr", AddrInputBuf, IM_ARRAYSIZE(AddrInputBuf),
ImGuiInputTextFlags_CharsHexadecimal |
ImGuiInputTextFlags_EnterReturnsTrue)) {
size_t goto_addr;
if (sscanf(AddrInputBuf, "%" _PRISizeT "X", &goto_addr) == 1) {
GotoAddr = goto_addr - base_display_addr;
HighlightMin = HighlightMax = (size_t)-1;
}
}
if (GotoAddr != (size_t)-1) {
if (GotoAddr < mem_size) {
ImGui::BeginChild("##scrolling");
ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y +
(GotoAddr / Cols) * ImGui::GetTextLineHeight());
ImGui::EndChild();
DataEditingAddr = DataPreviewAddr = GotoAddr;
DataEditingTakeFocus = true;
}
GotoAddr = (size_t)-1;
}
// if (MouseHovered)
//{
// ImGui::SameLine();
// ImGui::Text("Hovered: %p", MouseHoveredAddr);
// }
}
void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size,
size_t base_display_addr) {
IM_UNUSED(base_display_addr);
ImU8* mem_data = (ImU8*)mem_data_void;
ImGuiStyle& style = ImGui::GetStyle();
ImGui::AlignTextToFramePadding();
ImGui::Text("Preview as:");
ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f +
style.ItemInnerSpacing.x);
static const ImGuiDataType supported_data_types[] = {
ImGuiDataType_S8, ImGuiDataType_U8, ImGuiDataType_S16, ImGuiDataType_U16,
ImGuiDataType_S32, ImGuiDataType_U32, ImGuiDataType_S64, ImGuiDataType_U64,
ImGuiDataType_Float, ImGuiDataType_Double};
if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType),
ImGuiComboFlags_HeightLargest)) {
for (int n = 0; n < IM_ARRAYSIZE(supported_data_types); n++) {
ImGuiDataType data_type = supported_data_types[n];
if (ImGui::Selectable(DataTypeGetDesc(data_type), PreviewDataType == data_type))
PreviewDataType = data_type;
}
ImGui::EndCombo();
}
ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f +
style.ItemInnerSpacing.x);
ImGui::Combo("##combo_endianness", &PreviewEndianness, "LE\0BE\0\0");
char buf[128] = "";
float x = s.GlyphWidth * 6.0f;
bool has_value = DataPreviewAddr != (size_t)-1;
if (has_value)
DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Dec,
buf, (size_t)IM_ARRAYSIZE(buf));
ImGui::Text("Dec");
ImGui::SameLine(x);
ImGui::TextUnformatted(has_value ? buf : "N/A");
if (has_value)
DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Hex,
buf, (size_t)IM_ARRAYSIZE(buf));
ImGui::Text("Hex");
ImGui::SameLine(x);
ImGui::TextUnformatted(has_value ? buf : "N/A");
if (has_value)
DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Bin,
buf, (size_t)IM_ARRAYSIZE(buf));
buf[IM_ARRAYSIZE(buf) - 1] = 0;
ImGui::Text("Bin");
ImGui::SameLine(x);
ImGui::TextUnformatted(has_value ? buf : "N/A");
}
// Utilities for Data Preview (since we don't access imgui_internal.h)
// FIXME: This technically depends on ImGuiDataType order.
const char* DataTypeGetDesc(ImGuiDataType data_type) const {
const char* descs[] = {"Int8", "Uint8", "Int16", "Uint16", "Int32",
"Uint32", "Int64", "Uint64", "Float", "Double"};
IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(descs));
return descs[data_type];
}
size_t DataTypeGetSize(ImGuiDataType data_type) const {
const size_t sizes[] = {1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double)};
IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(sizes));
return sizes[data_type];
}
const char* DataFormatGetDesc(DataFormat data_format) const {
const char* descs[] = {"Bin", "Dec", "Hex"};
IM_ASSERT(data_format >= 0 && data_format < DataFormat_COUNT);
return descs[data_format];
}
bool IsBigEndian() const {
uint16_t x = 1;
char c[2];
memcpy(c, &x, 2);
return c[0] != 0;
}
static void* EndiannessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian) {
if (is_little_endian) {
uint8_t* dst = (uint8_t*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1);
return _dst;
} else {
return memcpy(_dst, _src, s);
}
}
static void* EndiannessCopyLittleEndian(void* _dst, void* _src, size_t s,
int is_little_endian) {
if (is_little_endian) {
return memcpy(_dst, _src, s);
} else {
uint8_t* dst = (uint8_t*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1);
return _dst;
}
}
void* EndiannessCopy(void* dst, void* src, size_t size) const {
static void* (*fp)(void*, void*, size_t, int) = nullptr;
if (fp == nullptr)
fp = IsBigEndian() ? EndiannessCopyBigEndian : EndiannessCopyLittleEndian;
return fp(dst, src, size, PreviewEndianness);
}
const char* FormatBinary(const uint8_t* buf, int width) const {
IM_ASSERT(width <= 64);
size_t out_n = 0;
static char out_buf[64 + 8 + 1];
int n = width / 8;
for (int j = n - 1; j >= 0; --j) {
for (int i = 0; i < 8; ++i)
out_buf[out_n++] = (buf[j] & (1 << (7 - i))) ? '1' : '0';
out_buf[out_n++] = ' ';
}
IM_ASSERT(out_n < IM_ARRAYSIZE(out_buf));
out_buf[out_n] = 0;
return out_buf;
}
// [Internal]
void DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size,
ImGuiDataType data_type, DataFormat data_format, char* out_buf,
size_t out_buf_size) const {
uint8_t buf[8];
size_t elem_size = DataTypeGetSize(data_type);
size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size;
if (ReadFn)
for (int i = 0, n = (int)size; i < n; ++i)
buf[i] = ReadFn(mem_data, addr + i, UserData);
else
memcpy(buf, mem_data + addr, size);
if (data_format == DataFormat_Bin) {
uint8_t binbuf[8];
EndiannessCopy(binbuf, buf, size);
ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8));
return;
}
out_buf[0] = 0;
switch (data_type) {
case ImGuiDataType_S8: {
int8_t data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) {
ImSnprintf(out_buf, out_buf_size, "%hhd", data);
return;
}
if (data_format == DataFormat_Hex) {
ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0xFF);
return;
}
break;
}
case ImGuiDataType_U8: {
uint8_t data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) {
ImSnprintf(out_buf, out_buf_size, "%hhu", data);
return;
}
if (data_format == DataFormat_Hex) {
ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0XFF);
return;
}
break;
}
case ImGuiDataType_S16: {
int16_t data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) {
ImSnprintf(out_buf, out_buf_size, "%hd", data);
return;
}
if (data_format == DataFormat_Hex) {
ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF);
return;
}
break;
}
case ImGuiDataType_U16: {
uint16_t data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) {
ImSnprintf(out_buf, out_buf_size, "%hu", data);
return;
}
if (data_format == DataFormat_Hex) {
ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF);
return;
}
break;
}
case ImGuiDataType_S32: {
int32_t data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) {
ImSnprintf(out_buf, out_buf_size, "%d", data);
return;
}
if (data_format == DataFormat_Hex) {
ImSnprintf(out_buf, out_buf_size, "0x%08x", data);
return;
}
break;
}
case ImGuiDataType_U32: {
uint32_t data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) {
ImSnprintf(out_buf, out_buf_size, "%u", data);
return;
}
if (data_format == DataFormat_Hex) {
ImSnprintf(out_buf, out_buf_size, "0x%08x", data);
return;
}
break;
}
case ImGuiDataType_S64: {
int64_t data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) {
ImSnprintf(out_buf, out_buf_size, "%lld", (long long)data);
return;
}
if (data_format == DataFormat_Hex) {
ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data);
return;
}
break;
}
case ImGuiDataType_U64: {
uint64_t data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) {
ImSnprintf(out_buf, out_buf_size, "%llu", (long long)data);
return;
}
if (data_format == DataFormat_Hex) {
ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data);
return;
}
break;
}
case ImGuiDataType_Float: {
float data = 0.0f;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) {
ImSnprintf(out_buf, out_buf_size, "%f", data);
return;
}
if (data_format == DataFormat_Hex) {
ImSnprintf(out_buf, out_buf_size, "%a", data);
return;
}
break;
}
case ImGuiDataType_Double: {
double data = 0.0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) {
ImSnprintf(out_buf, out_buf_size, "%f", data);
return;
}
if (data_format == DataFormat_Hex) {
ImSnprintf(out_buf, out_buf_size, "%a", data);
return;
}
break;
}
default:
case ImGuiDataType_COUNT:
break;
} // Switch
IM_ASSERT(0); // Shouldn't reach
}
};
#undef _PRISizeT
#undef ImSnprintf
#ifdef _MSC_VER
#pragma warning(pop)
#endif

View File

@ -249,12 +249,6 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
file.Seek(currentPos); file.Seek(currentPos);
} }
// Extract trophy files
if (!trp.Extract(extract_path)) {
// Do nothing some pkg come with no trp file.
// return false;
}
// Read the seed // Read the seed
std::array<u8, 16> seed; std::array<u8, 16> seed;
if (!file.Seek(pkgheader.pfs_image_offset + 0x370)) { if (!file.Seek(pkgheader.pfs_image_offset + 0x370)) {

View File

@ -12,6 +12,7 @@ void TRP::GetNPcommID(const std::filesystem::path& trophyPath, int index) {
std::filesystem::path trpPath = trophyPath / "sce_sys/npbind.dat"; std::filesystem::path trpPath = trophyPath / "sce_sys/npbind.dat";
Common::FS::IOFile npbindFile(trpPath, Common::FS::FileAccessMode::Read); Common::FS::IOFile npbindFile(trpPath, Common::FS::FileAccessMode::Read);
if (!npbindFile.IsOpen()) { if (!npbindFile.IsOpen()) {
LOG_CRITICAL(Common_Filesystem, "Failed to open npbind.dat file");
return; return;
} }
if (!npbindFile.Seek(0x84 + (index * 0x180))) { if (!npbindFile.Seek(0x84 + (index * 0x180))) {
@ -32,10 +33,10 @@ static void removePadding(std::vector<u8>& vec) {
} }
} }
bool TRP::Extract(const std::filesystem::path& trophyPath) { bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string titleId) {
std::filesystem::path title = trophyPath.filename();
std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/"; std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/";
if (!std::filesystem::exists(gameSysDir)) { if (!std::filesystem::exists(gameSysDir)) {
LOG_CRITICAL(Common_Filesystem, "Game sce_sys directory doesn't exist");
return false; return false;
} }
for (int index = 0; const auto& it : std::filesystem::directory_iterator(gameSysDir)) { for (int index = 0; const auto& it : std::filesystem::directory_iterator(gameSysDir)) {
@ -44,18 +45,21 @@ bool TRP::Extract(const std::filesystem::path& trophyPath) {
Common::FS::IOFile file(it.path(), Common::FS::FileAccessMode::Read); Common::FS::IOFile file(it.path(), Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) { if (!file.IsOpen()) {
LOG_CRITICAL(Common_Filesystem, "Unable to open trophy file for read");
return false; return false;
} }
TrpHeader header; TrpHeader header;
file.Read(header); file.Read(header);
if (header.magic != 0xDCA24D00) if (header.magic != 0xDCA24D00) {
LOG_CRITICAL(Common_Filesystem, "Wrong trophy magic number");
return false; return false;
}
s64 seekPos = sizeof(TrpHeader); s64 seekPos = sizeof(TrpHeader);
std::filesystem::path trpFilesPath( std::filesystem::path trpFilesPath(
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / title / "TrophyFiles" / Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / titleId /
it.path().stem()); "TrophyFiles" / it.path().stem());
std::filesystem::create_directories(trpFilesPath / "Icons"); std::filesystem::create_directories(trpFilesPath / "Icons");
std::filesystem::create_directory(trpFilesPath / "Xml"); std::filesystem::create_directory(trpFilesPath / "Xml");
@ -69,7 +73,7 @@ bool TRP::Extract(const std::filesystem::path& trophyPath) {
file.Read(entry); file.Read(entry);
std::string_view name(entry.entry_name); std::string_view name(entry.entry_name);
if (entry.flag == 0 && name.find("TROP") != std::string::npos) { // PNG if (entry.flag == 0 && name.find("TROP") != std::string::npos) { // PNG
if (file.Seek(entry.entry_pos)) { if (!file.Seek(entry.entry_pos)) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset"); LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
return false; return false;
} }
@ -79,7 +83,7 @@ bool TRP::Extract(const std::filesystem::path& trophyPath) {
} }
if (entry.flag == 3 && np_comm_id[0] == 'N' && if (entry.flag == 3 && np_comm_id[0] == 'N' &&
np_comm_id[1] == 'P') { // ESFM, encrypted. np_comm_id[1] == 'P') { // ESFM, encrypted.
if (file.Seek(entry.entry_pos)) { if (!file.Seek(entry.entry_pos)) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset"); LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
return false; return false;
} }
@ -88,7 +92,7 @@ bool TRP::Extract(const std::filesystem::path& trophyPath) {
// clean xml file. // clean xml file.
std::vector<u8> ESFM(entry.entry_len - iv_len); std::vector<u8> ESFM(entry.entry_len - iv_len);
std::vector<u8> XML(entry.entry_len - iv_len); std::vector<u8> XML(entry.entry_len - iv_len);
if (file.Seek(entry.entry_pos + iv_len)) { if (!file.Seek(entry.entry_pos + iv_len)) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry + iv offset"); LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry + iv offset");
return false; return false;
} }
@ -99,7 +103,14 @@ bool TRP::Extract(const std::filesystem::path& trophyPath) {
size_t pos = xml_name.find("ESFM"); size_t pos = xml_name.find("ESFM");
if (pos != std::string::npos) if (pos != std::string::npos)
xml_name.replace(pos, xml_name.length(), "XML"); xml_name.replace(pos, xml_name.length(), "XML");
Common::FS::IOFile::WriteBytes(trpFilesPath / "Xml" / xml_name, XML); std::filesystem::path path = trpFilesPath / "Xml" / xml_name;
size_t written = Common::FS::IOFile::WriteBytes(path, XML);
if (written != XML.size()) {
LOG_CRITICAL(
Common_Filesystem,
"Trophy XML {} write failed, wanted to write {} bytes, wrote {}",
fmt::UTF(path.u8string()), XML.size(), written);
}
} }
} }
} }

View File

@ -33,7 +33,7 @@ class TRP {
public: public:
TRP(); TRP();
~TRP(); ~TRP();
bool Extract(const std::filesystem::path& trophyPath); bool Extract(const std::filesystem::path& trophyPath, const std::string titleId);
void GetNPcommID(const std::filesystem::path& trophyPath, int index); void GetNPcommID(const std::filesystem::path& trophyPath, int index);
private: private:

View File

@ -5,6 +5,7 @@
#include "app_content.h" #include "app_content.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/config.h"
#include "common/io_file.h" #include "common/io_file.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/path_util.h" #include "common/path_util.h"
@ -59,8 +60,7 @@ int PS4_SYSV_ABI sceAppContentAddcontMount(u32 service_label,
OrbisAppContentMountPoint* mount_point) { OrbisAppContentMountPoint* mount_point) {
LOG_INFO(Lib_AppContent, "called"); LOG_INFO(Lib_AppContent, "called");
const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) / title_id / const auto& mount_dir = Config::getAddonInstallDir() / title_id / entitlement_label->data;
entitlement_label->data;
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance(); auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
for (int i = 0; i < addcont_count; i++) { for (int i = 0; i < addcont_count; i++) {
@ -246,7 +246,7 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar
LOG_ERROR(Lib_AppContent, "(DUMMY) called"); LOG_ERROR(Lib_AppContent, "(DUMMY) called");
auto* param_sfo = Common::Singleton<PSF>::Instance(); auto* param_sfo = Common::Singleton<PSF>::Instance();
const auto addons_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir); const auto addons_dir = Config::getAddonInstallDir();
if (const auto value = param_sfo->GetString("TITLE_ID"); value.has_value()) { if (const auto value = param_sfo->GetString("TITLE_ID"); value.has_value()) {
title_id = *value; title_id = *value;
} else { } else {

View File

@ -176,11 +176,15 @@ int PS4_SYSV_ABI sceAudioOutGetLastOutputTime() {
} }
int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state) { int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state) {
if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
int type = 0; int type = 0;
int channels_num = 0; int channels_num = 0;
if (!audio->AudioOutGetStatus(handle, &type, &channels_num)) { if (const auto err = audio->AudioOutGetStatus(handle, &type, &channels_num); err != ORBIS_OK) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; return err;
} }
state->rerouteCounter = 0; state->rerouteCounter = 0;
@ -310,12 +314,7 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
LOG_ERROR(Lib_AudioOut, "Invalid format attribute"); LOG_ERROR(Lib_AudioOut, "Invalid format attribute");
return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT; return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT;
} }
int result = audio->AudioOutOpen(port_type, length, sample_rate, format); return audio->AudioOutOpen(port_type, length, sample_rate, format);
if (result == -1) {
LOG_ERROR(Lib_AudioOut, "Audio ports are full");
return ORBIS_AUDIO_OUT_ERROR_PORT_FULL;
}
return result;
} }
int PS4_SYSV_ABI sceAudioOutOpenEx() { int PS4_SYSV_ABI sceAudioOutOpenEx() {
@ -324,12 +323,19 @@ int PS4_SYSV_ABI sceAudioOutOpenEx() {
} }
s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr) { s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr) {
if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
if (ptr == nullptr) {
// Nothing to output
return ORBIS_OK;
}
return audio->AudioOutOutput(handle, ptr); return audio->AudioOutOutput(handle, ptr);
} }
int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) { int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) {
for (u32 i = 0; i < num; i++) { for (u32 i = 0; i < num; i++) {
if (auto err = audio->AudioOutOutput(param[i].handle, param[i].ptr); err != 0) if (const auto err = sceAudioOutOutput(param[i].handle, param[i].ptr); err != 0)
return err; return err;
} }
return ORBIS_OK; return ORBIS_OK;
@ -426,10 +432,10 @@ int PS4_SYSV_ABI sceAudioOutSetUsbVolume() {
} }
s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) { s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) {
if (!audio->AudioOutSetVolume(handle, flag, vol)) { if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
} }
return ORBIS_OK; return audio->AudioOutSetVolume(handle, flag, vol);
} }
int PS4_SYSV_ABI sceAudioOutSetVolumeDown() { int PS4_SYSV_ABI sceAudioOutSetVolumeDown() {

View File

@ -11,6 +11,10 @@ namespace Libraries::AudioOut {
constexpr int SCE_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value constexpr int SCE_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value
// main up to 8 ports, BGM 1 port, voice up to 4 ports,
// personal up to 4 ports, padspk up to 5 ports, aux 1 port
constexpr int SCE_AUDIO_OUT_NUM_PORTS = 22;
enum OrbisAudioOutPort { enum OrbisAudioOutPort {
ORBIS_AUDIO_OUT_PORT_TYPE_MAIN = 0, ORBIS_AUDIO_OUT_PORT_TYPE_MAIN = 0,
ORBIS_AUDIO_OUT_PORT_TYPE_BGM = 1, ORBIS_AUDIO_OUT_PORT_TYPE_BGM = 1,

View File

@ -91,7 +91,7 @@ void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, SceAvPlayer
const auto callback = self->m_event_replacement.event_callback; const auto callback = self->m_event_replacement.event_callback;
const auto ptr = self->m_event_replacement.object_ptr; const auto ptr = self->m_event_replacement.object_ptr;
if (callback != nullptr) { if (callback != nullptr) {
auto* linker = Common::Singleton<Core::Linker>::Instance(); const auto* linker = Common::Singleton<Core::Linker>::Instance();
linker->ExecuteGuest(callback, ptr, event_id, 0, event_data); linker->ExecuteGuest(callback, ptr, event_id, 0, event_data);
} }
} }
@ -367,7 +367,7 @@ void AvPlayerState::EmitEvent(SceAvPlayerEvents event_id, void* event_data) {
const auto callback = m_init_data.event_replacement.event_callback; const auto callback = m_init_data.event_replacement.event_callback;
if (callback) { if (callback) {
const auto ptr = m_init_data.event_replacement.object_ptr; const auto ptr = m_init_data.event_replacement.object_ptr;
auto* linker = Common::Singleton<Core::Linker>::Instance(); const auto* linker = Common::Singleton<Core::Linker>::Instance();
linker->ExecuteGuest(callback, ptr, event_id, 0, event_data); linker->ExecuteGuest(callback, ptr, event_id, 0, event_data);
} }
} }

View File

@ -11,6 +11,7 @@
#include "common/path_util.h" #include "common/path_util.h"
#include "common/slot_vector.h" #include "common/slot_vector.h"
#include "core/address_space.h" #include "core/address_space.h"
#include "core/debug_state.h"
#include "core/libraries/error_codes.h" #include "core/libraries/error_codes.h"
#include "core/libraries/kernel/libkernel.h" #include "core/libraries/kernel/libkernel.h"
#include "core/libraries/libs.h" #include "core/libraries/libs.h"
@ -320,20 +321,6 @@ static void WaitGpuIdle() {
cv_lock.wait(lock, [] { return submission_lock == 0; }); cv_lock.wait(lock, [] { return submission_lock == 0; });
} }
static void DumpCommandList(std::span<const u32> cmd_list, const std::string& postfix) {
using namespace Common::FS;
const auto dump_dir = GetUserPath(PathType::PM4Dir);
if (!std::filesystem::exists(dump_dir)) {
std::filesystem::create_directories(dump_dir);
}
if (cmd_list.empty()) {
return;
}
const auto filename = fmt::format("{:08}_{}", frames_submitted, postfix);
const auto file = IOFile{dump_dir / filename, FileAccessMode::Write};
file.WriteSpan(cmd_list);
}
// Write a special ending NOP packet with N DWs data block // Write a special ending NOP packet with N DWs data block
template <u32 data_block_size> template <u32 data_block_size>
static inline u32* WriteTrailingNop(u32* cmdbuf) { static inline u32* WriteTrailingNop(u32* cmdbuf) {
@ -507,16 +494,18 @@ void PS4_SYSV_ABI sceGnmDingDong(u32 gnm_vqid, u32 next_offs_dw) {
WaitGpuIdle(); WaitGpuIdle();
/* Suspend logic goes here */ if (DebugState.ShouldPauseInSubmit()) {
DebugState.PauseGuestThreads();
}
auto vqid = gnm_vqid - 1; auto vqid = gnm_vqid - 1;
auto& asc_queue = asc_queues[{vqid}]; auto& asc_queue = asc_queues[{vqid}];
const auto* acb_ptr = reinterpret_cast<const u32*>(asc_queue.map_addr + *asc_queue.read_addr); const auto* acb_ptr = reinterpret_cast<const u32*>(asc_queue.map_addr + *asc_queue.read_addr);
const auto acb_size = next_offs_dw ? (next_offs_dw << 2u) - *asc_queue.read_addr const auto acb_size = next_offs_dw ? (next_offs_dw << 2u) - *asc_queue.read_addr
: (asc_queue.ring_size_dw << 2u) - *asc_queue.read_addr; : (asc_queue.ring_size_dw << 2u) - *asc_queue.read_addr;
const std::span<const u32> acb_span{acb_ptr, acb_size >> 2u}; const std::span acb_span{acb_ptr, acb_size >> 2u};
if (Config::dumpPM4()) { if (DebugState.DumpingCurrentFrame()) {
static auto last_frame_num = -1LL; static auto last_frame_num = -1LL;
static u32 seq_num{}; static u32 seq_num{};
if (last_frame_num == frames_submitted) { if (last_frame_num == frames_submitted) {
@ -536,8 +525,14 @@ void PS4_SYSV_ABI sceGnmDingDong(u32 gnm_vqid, u32 next_offs_dw) {
acb = {indirect_buffer->Address<const u32>(), indirect_buffer->ib_size}; acb = {indirect_buffer->Address<const u32>(), indirect_buffer->ib_size};
} }
// File name format is: <queue>_<queue num>_<submit_num> using namespace DebugStateType;
DumpCommandList(acb, fmt::format("acb_{}_{}", gnm_vqid, seq_num));
DebugState.PushQueueDump({
.type = QueueType::acb,
.submit_num = seq_num,
.num2 = gnm_vqid,
.data = {acb.begin(), acb.end()},
});
} }
liverpool->SubmitAsc(vqid, acb_span); liverpool->SubmitAsc(vqid, acb_span);
@ -2108,7 +2103,9 @@ s32 PS4_SYSV_ABI sceGnmSubmitCommandBuffers(u32 count, const u32* dcb_gpu_addrs[
WaitGpuIdle(); WaitGpuIdle();
/* Suspend logic goes here */ if (DebugState.ShouldPauseInSubmit()) {
DebugState.PauseGuestThreads();
}
if (send_init_packet) { if (send_init_packet) {
if (sdk_version <= 0x1ffffffu) { if (sdk_version <= 0x1ffffffu) {
@ -2128,10 +2125,10 @@ s32 PS4_SYSV_ABI sceGnmSubmitCommandBuffers(u32 count, const u32* dcb_gpu_addrs[
const auto dcb_size_dw = dcb_sizes_in_bytes[cbpair] >> 2; const auto dcb_size_dw = dcb_sizes_in_bytes[cbpair] >> 2;
const auto ccb_size_dw = ccb_size_in_bytes >> 2; const auto ccb_size_dw = ccb_size_in_bytes >> 2;
const auto& dcb_span = std::span<const u32>{dcb_gpu_addrs[cbpair], dcb_size_dw}; const auto& dcb_span = std::span{dcb_gpu_addrs[cbpair], dcb_size_dw};
const auto& ccb_span = std::span<const u32>{ccb, ccb_size_dw}; const auto& ccb_span = std::span{ccb, ccb_size_dw};
if (Config::dumpPM4()) { if (DebugState.DumpingCurrentFrame()) {
static auto last_frame_num = -1LL; static auto last_frame_num = -1LL;
static u32 seq_num{}; static u32 seq_num{};
if (last_frame_num == frames_submitted && cbpair == 0) { if (last_frame_num == frames_submitted && cbpair == 0) {
@ -2141,9 +2138,20 @@ s32 PS4_SYSV_ABI sceGnmSubmitCommandBuffers(u32 count, const u32* dcb_gpu_addrs[
seq_num = 0u; seq_num = 0u;
} }
// File name format is: <queue>_<submit num>_<buffer_in_submit> using DebugStateType::QueueType;
DumpCommandList(dcb_span, fmt::format("dcb_{}_{}", seq_num, cbpair));
DumpCommandList(ccb_span, fmt::format("ccb_{}_{}", seq_num, cbpair)); DebugState.PushQueueDump({
.type = QueueType::dcb,
.submit_num = seq_num,
.num2 = cbpair,
.data = {dcb_span.begin(), dcb_span.end()},
});
DebugState.PushQueueDump({
.type = QueueType::ccb,
.submit_num = seq_num,
.num2 = cbpair,
.data = {ccb_span.begin(), ccb_span.end()},
});
} }
liverpool->SubmitGfx(dcb_span, ccb_span); liverpool->SubmitGfx(dcb_span, ccb_span);
@ -2166,6 +2174,7 @@ int PS4_SYSV_ABI sceGnmSubmitDone() {
liverpool->SubmitDone(); liverpool->SubmitDone();
send_init_packet = true; send_init_packet = true;
++frames_submitted; ++frames_submitted;
DebugState.IncGnmFrameNum();
return ORBIS_OK; return ORBIS_OK;
} }

View File

@ -626,15 +626,19 @@ void fileSystemSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("AqBioC2vF3I", "libScePosix", 1, "libkernel", 1, 1, posix_read); LIB_FUNCTION("AqBioC2vF3I", "libScePosix", 1, "libkernel", 1, 1, posix_read);
LIB_FUNCTION("1-LFLmRFxxM", "libkernel", 1, "libkernel", 1, 1, sceKernelMkdir); LIB_FUNCTION("1-LFLmRFxxM", "libkernel", 1, "libkernel", 1, 1, sceKernelMkdir);
LIB_FUNCTION("JGMio+21L4c", "libScePosix", 1, "libkernel", 1, 1, posix_mkdir); LIB_FUNCTION("JGMio+21L4c", "libScePosix", 1, "libkernel", 1, 1, posix_mkdir);
LIB_FUNCTION("JGMio+21L4c", "libkernel", 1, "libkernel", 1, 1, posix_mkdir);
LIB_FUNCTION("naInUjYt3so", "libkernel", 1, "libkernel", 1, 1, sceKernelRmdir); LIB_FUNCTION("naInUjYt3so", "libkernel", 1, "libkernel", 1, 1, sceKernelRmdir);
LIB_FUNCTION("c7ZnT7V1B98", "libScePosix", 1, "libkernel", 1, 1, posix_rmdir); LIB_FUNCTION("c7ZnT7V1B98", "libScePosix", 1, "libkernel", 1, 1, posix_rmdir);
LIB_FUNCTION("c7ZnT7V1B98", "libkernel", 1, "libkernel", 1, 1, posix_rmdir);
LIB_FUNCTION("eV9wAD2riIA", "libkernel", 1, "libkernel", 1, 1, sceKernelStat); LIB_FUNCTION("eV9wAD2riIA", "libkernel", 1, "libkernel", 1, 1, sceKernelStat);
LIB_FUNCTION("kBwCPsYX-m4", "libkernel", 1, "libkernel", 1, 1, sceKernelFStat); LIB_FUNCTION("kBwCPsYX-m4", "libkernel", 1, "libkernel", 1, 1, sceKernelFStat);
LIB_FUNCTION("mqQMh1zPPT8", "libScePosix", 1, "libkernel", 1, 1, posix_fstat); LIB_FUNCTION("mqQMh1zPPT8", "libScePosix", 1, "libkernel", 1, 1, posix_fstat);
LIB_FUNCTION("mqQMh1zPPT8", "libkernel", 1, "libkernel", 1, 1, posix_fstat);
LIB_FUNCTION("VW3TVZiM4-E", "libkernel", 1, "libkernel", 1, 1, sceKernelFtruncate); LIB_FUNCTION("VW3TVZiM4-E", "libkernel", 1, "libkernel", 1, 1, sceKernelFtruncate);
LIB_FUNCTION("52NcYU9+lEo", "libkernel", 1, "libkernel", 1, 1, sceKernelRename); LIB_FUNCTION("52NcYU9+lEo", "libkernel", 1, "libkernel", 1, 1, sceKernelRename);
LIB_FUNCTION("E6ao34wPw+U", "libScePosix", 1, "libkernel", 1, 1, posix_stat); LIB_FUNCTION("E6ao34wPw+U", "libScePosix", 1, "libkernel", 1, 1, posix_stat);
LIB_FUNCTION("E6ao34wPw+U", "libkernel", 1, "libkernel", 1, 1, posix_stat);
LIB_FUNCTION("+r3rMFwItV4", "libkernel", 1, "libkernel", 1, 1, sceKernelPread); LIB_FUNCTION("+r3rMFwItV4", "libkernel", 1, "libkernel", 1, 1, sceKernelPread);
LIB_FUNCTION("uWyW3v98sU4", "libkernel", 1, "libkernel", 1, 1, sceKernelCheckReachability); LIB_FUNCTION("uWyW3v98sU4", "libkernel", 1, "libkernel", 1, 1, sceKernelCheckReachability);
LIB_FUNCTION("fTx66l5iWIA", "libkernel", 1, "libkernel", 1, 1, sceKernelFsync); LIB_FUNCTION("fTx66l5iWIA", "libkernel", 1, "libkernel", 1, 1, sceKernelFsync);

View File

@ -11,6 +11,7 @@
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/singleton.h" #include "common/singleton.h"
#include "common/thread.h" #include "common/thread.h"
#include "core/debug_state.h"
#include "core/libraries/error_codes.h" #include "core/libraries/error_codes.h"
#include "core/libraries/kernel/libkernel.h" #include "core/libraries/kernel/libkernel.h"
#include "core/libraries/kernel/thread_management.h" #include "core/libraries/kernel/thread_management.h"
@ -988,16 +989,18 @@ static void cleanup_thread(void* arg) {
} }
Core::SetTcbBase(nullptr); Core::SetTcbBase(nullptr);
thread->is_almost_done = true; thread->is_almost_done = true;
DebugState.RemoveCurrentThreadFromGuestList();
} }
static void* run_thread(void* arg) { static void* run_thread(void* arg) {
auto* thread = static_cast<ScePthread>(arg); auto* thread = static_cast<ScePthread>(arg);
Common::SetCurrentThreadName(thread->name.c_str()); Common::SetCurrentThreadName(thread->name.c_str());
auto* linker = Common::Singleton<Core::Linker>::Instance(); const auto* linker = Common::Singleton<Core::Linker>::Instance();
void* ret = nullptr; void* ret = nullptr;
g_pthread_self = thread; g_pthread_self = thread;
pthread_cleanup_push(cleanup_thread, thread); pthread_cleanup_push(cleanup_thread, thread);
thread->is_started = true; thread->is_started = true;
DebugState.AddCurrentThreadToGuestList();
ret = linker->ExecuteGuest(thread->entry, thread->arg); ret = linker->ExecuteGuest(thread->entry, thread->arg);
pthread_cleanup_pop(1); pthread_cleanup_pop(1);
return ret; return ret;

View File

@ -247,6 +247,17 @@ int PS4_SYSV_ABI sceKernelConvertLocaltimeToUtc(time_t param_1, int64_t param_2,
return SCE_OK; return SCE_OK;
} }
namespace Dev {
u64& GetInitialPtc() {
return initial_ptc;
}
Common::NativeClock* GetClock() {
return clock.get();
}
} // namespace Dev
void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym) { void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
clock = std::make_unique<Common::NativeClock>(); clock = std::make_unique<Common::NativeClock>();
initial_ptc = clock->GetUptime(); initial_ptc = clock->GetUptime();

View File

@ -7,6 +7,10 @@
#include "common/types.h" #include "common/types.h"
namespace Common {
class NativeClock;
}
namespace Core::Loader { namespace Core::Loader {
class SymbolsResolver; class SymbolsResolver;
} }
@ -47,6 +51,12 @@ constexpr int ORBIS_CLOCK_EXT_DEBUG_NETWORK = 17;
constexpr int ORBIS_CLOCK_EXT_AD_NETWORK = 18; constexpr int ORBIS_CLOCK_EXT_AD_NETWORK = 18;
constexpr int ORBIS_CLOCK_EXT_RAW_NETWORK = 19; constexpr int ORBIS_CLOCK_EXT_RAW_NETWORK = 19;
namespace Dev {
u64& GetInitialPtc();
Common::NativeClock* GetClock();
} // namespace Dev
u64 PS4_SYSV_ABI sceKernelGetTscFrequency(); u64 PS4_SYSV_ABI sceKernelGetTscFrequency();
u64 PS4_SYSV_ABI sceKernelGetProcessTime(); u64 PS4_SYSV_ABI sceKernelGetProcessTime();
u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter(); u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter();

View File

@ -59,7 +59,7 @@ s32 Libraries::NetCtl::NetCtlInternal::registerNpToolkitCallback(
void Libraries::NetCtl::NetCtlInternal::checkCallback() { void Libraries::NetCtl::NetCtlInternal::checkCallback() {
std::unique_lock lock{m_mutex}; std::unique_lock lock{m_mutex};
auto* linker = Common::Singleton<Core::Linker>::Instance(); const auto* linker = Common::Singleton<Core::Linker>::Instance();
for (auto& callback : callbacks) { for (auto& callback : callbacks) {
if (callback.func != nullptr) { if (callback.func != nullptr) {
linker->ExecuteGuest(callback.func, ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED, linker->ExecuteGuest(callback.func, ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED,
@ -70,7 +70,7 @@ void Libraries::NetCtl::NetCtlInternal::checkCallback() {
void Libraries::NetCtl::NetCtlInternal::checkNpToolkitCallback() { void Libraries::NetCtl::NetCtlInternal::checkNpToolkitCallback() {
std::unique_lock lock{m_mutex}; std::unique_lock lock{m_mutex};
auto* linker = Common::Singleton<Core::Linker>::Instance(); const auto* linker = Common::Singleton<Core::Linker>::Instance();
for (auto& callback : nptoolCallbacks) { for (auto& callback : nptoolCallbacks) {
if (callback.func != nullptr) { if (callback.func != nullptr) {
linker->ExecuteGuest(callback.func, ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED, linker->ExecuteGuest(callback.func, ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED,

View File

@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <common/singleton.h>
#include <core/linker.h>
#include "common/config.h" #include "common/config.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/libraries/error_codes.h" #include "core/libraries/error_codes.h"
@ -874,19 +876,6 @@ int PS4_SYSV_ABI sceNpCheckCallback() {
return ORBIS_OK; return ORBIS_OK;
} }
struct NpStateCallbackForNpToolkit {
OrbisNpStateCallbackForNpToolkit func;
void* userdata;
};
NpStateCallbackForNpToolkit NpStateCbForNp;
int PS4_SYSV_ABI sceNpCheckCallbackForLib() {
// LOG_ERROR(Lib_NpManager, "(STUBBED) called");
NpStateCbForNp.func(0, ORBIS_NP_STATE_SIGNED_OUT, NpStateCbForNp.userdata);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpCheckNpAvailability() { int PS4_SYSV_ABI sceNpCheckNpAvailability() {
LOG_ERROR(Lib_NpManager, "(STUBBED) called"); LOG_ERROR(Lib_NpManager, "(STUBBED) called");
return ORBIS_OK; return ORBIS_OK;
@ -983,9 +972,10 @@ int PS4_SYSV_ABI sceNpGetGamePresenceStatusA() {
} }
int PS4_SYSV_ABI sceNpGetNpId(OrbisUserServiceUserId userId, OrbisNpId* npId) { int PS4_SYSV_ABI sceNpGetNpId(OrbisUserServiceUserId userId, OrbisNpId* npId) {
LOG_ERROR(Lib_NpManager, "(DUMMY) called"); LOG_INFO(Lib_NpManager, "userId {}", userId);
std::string name = Config::getUserName(); std::string name = Config::getUserName();
// Fill the unused stuffs to 0
memset(npId, 0, sizeof(*npId));
strcpy(npId->handle.data, name.c_str()); strcpy(npId->handle.data, name.c_str());
return ORBIS_OK; return ORBIS_OK;
} }
@ -1010,8 +1000,9 @@ int PS4_SYSV_ABI sceNpGetParentalControlInfoA() {
return ORBIS_OK; return ORBIS_OK;
} }
int PS4_SYSV_ABI sceNpGetState() { int PS4_SYSV_ABI sceNpGetState(s32 userId, OrbisNpState* state) {
LOG_ERROR(Lib_NpManager, "(STUBBED) called"); *state = ORBIS_NP_STATE_SIGNED_OUT;
LOG_DEBUG(Lib_NpManager, "Signed out");
return ORBIS_OK; return ORBIS_OK;
} }
@ -2515,12 +2506,28 @@ int PS4_SYSV_ABI Func_FF966E4351E564D6() {
return ORBIS_OK; return ORBIS_OK;
} }
struct NpStateCallbackForNpToolkit {
OrbisNpStateCallbackForNpToolkit func;
void* userdata;
};
NpStateCallbackForNpToolkit NpStateCbForNp;
int PS4_SYSV_ABI sceNpCheckCallbackForLib() {
// LOG_ERROR(Lib_NpManager, "(STUBBED) called");
const auto* linker = Common::Singleton<Core::Linker>::Instance();
linker->ExecuteGuest(NpStateCbForNp.func, 1, ORBIS_NP_STATE_SIGNED_OUT,
NpStateCbForNp.userdata);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit(OrbisNpStateCallbackForNpToolkit callback, int PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit(OrbisNpStateCallbackForNpToolkit callback,
void* userdata) { void* userdata) {
static int id = 0;
LOG_ERROR(Lib_NpManager, "(STUBBED) called"); LOG_ERROR(Lib_NpManager, "(STUBBED) called");
NpStateCbForNp.func = callback; NpStateCbForNp.func = callback;
NpStateCbForNp.userdata = userdata; NpStateCbForNp.userdata = userdata;
return 1; return id;
} }
int PS4_SYSV_ABI sceNpUnregisterStateCallbackForToolkit() { int PS4_SYSV_ABI sceNpUnregisterStateCallbackForToolkit() {

View File

@ -11,6 +11,8 @@ class SymbolsResolver;
namespace Libraries::NpManager { namespace Libraries::NpManager {
constexpr int ORBIS_NP_ERROR_SIGNED_OUT = 0x80550006;
enum OrbisNpState { enum OrbisNpState {
ORBIS_NP_STATE_UNKNOWN = 0, ORBIS_NP_STATE_UNKNOWN = 0,
ORBIS_NP_STATE_SIGNED_OUT, ORBIS_NP_STATE_SIGNED_OUT,
@ -234,7 +236,7 @@ int PS4_SYSV_ABI sceNpGetNpReachabilityState();
int PS4_SYSV_ABI sceNpGetOnlineId(); int PS4_SYSV_ABI sceNpGetOnlineId();
int PS4_SYSV_ABI sceNpGetParentalControlInfo(); int PS4_SYSV_ABI sceNpGetParentalControlInfo();
int PS4_SYSV_ABI sceNpGetParentalControlInfoA(); int PS4_SYSV_ABI sceNpGetParentalControlInfoA();
int PS4_SYSV_ABI sceNpGetState(); int PS4_SYSV_ABI sceNpGetState(s32 userId, OrbisNpState* state);
int PS4_SYSV_ABI sceNpGetUserIdByAccountId(); int PS4_SYSV_ABI sceNpGetUserIdByAccountId();
int PS4_SYSV_ABI sceNpGetUserIdByOnlineId(); int PS4_SYSV_ABI sceNpGetUserIdByOnlineId();
int PS4_SYSV_ABI sceNpHasSignedUp(); int PS4_SYSV_ABI sceNpHasSignedUp();

View File

@ -14,8 +14,6 @@
namespace Libraries::NpTrophy { namespace Libraries::NpTrophy {
static TrophyUI g_trophy_ui;
std::string game_serial; std::string game_serial;
static constexpr auto MaxTrophyHandles = 4u; static constexpr auto MaxTrophyHandles = 4u;
@ -223,6 +221,14 @@ int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTro
return ORBIS_OK; return ORBIS_OK;
} }
struct GameTrophyInfo {
uint32_t num_groups;
uint32_t num_trophies;
uint32_t num_trophies_by_rarity[5];
uint32_t unlocked_trophies;
uint32_t unlocked_trophies_by_rarity[5];
};
int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyGameDetails* details, OrbisNpTrophyGameDetails* details,
OrbisNpTrophyGameData* data) { OrbisNpTrophyGameData* data) {
@ -240,79 +246,69 @@ int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTro
if (details->size != 0x4A0 || data->size != 0x20) if (details->size != 0x4A0 || data->size != 0x20)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
const auto trophyDir = const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
pugi::xml_document doc; pugi::xml_document doc;
pugi::xml_parse_result result = pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
if (result) { if (!result) {
LOG_ERROR(Lib_NpTrophy, "Failed to parse trophy xml : {}", result.description());
return ORBIS_OK;
}
uint32_t numGroups = 0; GameTrophyInfo game_info{};
uint32_t numTrophies = 0;
uint32_t numTrophiesByRarity[5];
numTrophiesByRarity[1] = 0;
numTrophiesByRarity[2] = 0;
numTrophiesByRarity[3] = 0;
numTrophiesByRarity[4] = 0;
uint32_t unlockedTrophies = 0;
uint32_t unlockedTrophiesByRarity[5];
unlockedTrophiesByRarity[1] = 0;
unlockedTrophiesByRarity[2] = 0;
unlockedTrophiesByRarity[3] = 0;
unlockedTrophiesByRarity[4] = 0;
auto trophyconf = doc.child("trophyconf"); auto trophyconf = doc.child("trophyconf");
for (pugi::xml_node_iterator it = trophyconf.children().begin(); for (const pugi::xml_node& node : trophyconf.children()) {
it != trophyconf.children().end(); ++it) { std::string_view node_name = node.name();
if (std::string(it->name()) == "title-name") { if (node_name == "title-name") {
strncpy(details->title, it->text().as_string(), strncpy(details->title, node.text().as_string(), ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE);
ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE);
}
if (std::string(it->name()) == "title-detail") {
strncpy(details->description, it->text().as_string(),
ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE);
}
if (std::string(it->name()) == "group")
numGroups++;
if (std::string(it->name()) == "trophy") {
std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
std::string currentTrophyGrade = it->attribute("ttype").value();
numTrophies++;
if (!currentTrophyGrade.empty()) {
int trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0));
numTrophiesByRarity[trophyGrade]++;
if (currentTrophyUnlockState == "unlocked") {
unlockedTrophies++;
unlockedTrophiesByRarity[trophyGrade]++;
}
}
}
} }
details->numGroups = numGroups; if (node_name == "title-detail") {
details->numTrophies = numTrophies; strncpy(details->description, node.text().as_string(),
details->numPlatinum = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM]; ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE);
details->numGold = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD]; }
details->numSilver = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER];
details->numBronze = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
data->unlockedTrophies = unlockedTrophies;
data->unlockedPlatinum = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
data->unlockedGold = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD];
data->unlockedSilver = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER];
data->unlockedBronze = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
// maybe this should be 1 instead of 100? if (node_name == "group")
data->progressPercentage = 100; game_info.num_groups++;
} else if (node_name == "trophy") {
LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description()); bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
std::string_view current_trophy_grade = node.attribute("ttype").value();
if (current_trophy_grade.empty()) {
continue;
}
game_info.num_trophies++;
int trophy_grade = GetTrophyGradeFromChar(current_trophy_grade.at(0));
game_info.num_trophies_by_rarity[trophy_grade]++;
if (current_trophy_unlockstate) {
game_info.unlocked_trophies++;
game_info.unlocked_trophies_by_rarity[trophy_grade]++;
}
}
}
details->num_groups = game_info.num_groups;
details->num_trophies = game_info.num_trophies;
details->num_platinum = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
details->num_gold = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD];
details->num_silver = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
details->num_bronze = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
data->unlocked_trophies = game_info.unlocked_trophies;
data->unlocked_platinum = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
data->unlocked_gold = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD];
data->unlocked_silver = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
data->unlocked_bronze = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
// maybe this should be 1 instead of 100?
data->progress_percentage = 100;
return ORBIS_OK; return ORBIS_OK;
} }
@ -323,6 +319,13 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(OrbisNpTrophyContext context, OrbisNpTr
return ORBIS_OK; return ORBIS_OK;
} }
struct GroupTrophyInfo {
uint32_t num_trophies;
uint32_t num_trophies_by_rarity[5];
uint32_t unlocked_trophies;
uint32_t unlocked_trophies_by_rarity[5];
};
int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyGroupId groupId, OrbisNpTrophyGroupId groupId,
OrbisNpTrophyGroupDetails* details, OrbisNpTrophyGroupDetails* details,
@ -341,89 +344,78 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTr
if (details->size != 0x4A0 || data->size != 0x28) if (details->size != 0x4A0 || data->size != 0x28)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
const auto trophyDir = const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
pugi::xml_document doc; pugi::xml_document doc;
pugi::xml_parse_result result = pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
if (result) { if (!result) {
LOG_ERROR(Lib_NpTrophy, "Failed to open trophy xml : {}", result.description());
return ORBIS_OK;
}
uint32_t numGroups = 0; GroupTrophyInfo group_info{};
uint32_t numTrophies = 0;
uint32_t numTrophiesByRarity[5];
numTrophiesByRarity[1] = 0;
numTrophiesByRarity[2] = 0;
numTrophiesByRarity[3] = 0;
numTrophiesByRarity[4] = 0;
uint32_t unlockedTrophies = 0;
uint32_t unlockedTrophiesByRarity[5];
unlockedTrophiesByRarity[1] = 0;
unlockedTrophiesByRarity[2] = 0;
unlockedTrophiesByRarity[3] = 0;
unlockedTrophiesByRarity[4] = 0;
auto trophyconf = doc.child("trophyconf"); auto trophyconf = doc.child("trophyconf");
for (pugi::xml_node_iterator it = trophyconf.children().begin(); for (const pugi::xml_node& node : trophyconf.children()) {
it != trophyconf.children().end(); ++it) { std::string_view node_name = node.name();
if (std::string(it->name()) == "group") { if (node_name == "group") {
numGroups++; int current_group_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_GROUP_ID);
std::string currentGroupId = it->attribute("id").value(); if (current_group_id != ORBIS_NP_TROPHY_INVALID_GROUP_ID) {
if (!currentGroupId.empty()) { if (current_group_id == groupId) {
if (std::stoi(currentGroupId) == groupId) { std::string_view current_group_name = node.child("name").text().as_string();
std::string currentGroupName = it->child("name").text().as_string(); std::string_view current_group_description =
std::string currentGroupDescription = node.child("detail").text().as_string();
it->child("detail").text().as_string();
strncpy(details->title, currentGroupName.c_str(), strncpy(details->title, current_group_name.data(),
ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE); ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE);
strncpy(details->description, currentGroupDescription.c_str(), strncpy(details->description, current_group_description.data(),
ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE); ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE);
}
}
}
data->groupId = groupId;
if (std::string(it->name()) == "trophy") {
std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
std::string currentTrophyGrade = it->attribute("ttype").value();
std::string currentTrophyGroupID = it->attribute("gid").value();
if (!currentTrophyGroupID.empty()) {
if (std::stoi(currentTrophyGroupID) == groupId) {
numTrophies++;
if (!currentTrophyGrade.empty()) {
int trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0));
numTrophiesByRarity[trophyGrade]++;
if (currentTrophyUnlockState == "unlocked") {
unlockedTrophies++;
unlockedTrophiesByRarity[trophyGrade]++;
}
}
}
} }
} }
} }
details->numTrophies = numTrophies; details->group_id = groupId;
details->numPlatinum = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM]; data->group_id = groupId;
details->numGold = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD];
details->numSilver = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER];
details->numBronze = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
data->unlockedTrophies = unlockedTrophies;
data->unlockedPlatinum = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
data->unlockedGold = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD];
data->unlockedSilver = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER];
data->unlockedBronze = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
// maybe this should be 1 instead of 100? if (node_name == "trophy") {
data->progressPercentage = 100; bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
std::string_view current_trophy_grade = node.attribute("ttype").value();
int current_trophy_group_id = node.attribute("gid").as_int(-1);
} else if (current_trophy_grade.empty()) {
LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description()); continue;
}
if (current_trophy_group_id == groupId) {
group_info.num_trophies++;
int trophyGrade = GetTrophyGradeFromChar(current_trophy_grade.at(0));
group_info.num_trophies_by_rarity[trophyGrade]++;
if (current_trophy_unlockstate) {
group_info.unlocked_trophies++;
group_info.unlocked_trophies_by_rarity[trophyGrade]++;
}
}
}
}
details->num_trophies = group_info.num_trophies;
details->num_platinum = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
details->num_gold = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD];
details->num_silver = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
details->num_bronze = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
data->unlocked_trophies = group_info.unlocked_trophies;
data->unlocked_platinum =
group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
data->unlocked_gold = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD];
data->unlocked_silver = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
data->unlocked_bronze = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
// maybe this should be 1 instead of 100?
data->progress_percentage = 100;
return ORBIS_OK; return ORBIS_OK;
} }
@ -454,87 +446,51 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT
if (details->size != 0x498 || data->size != 0x18) if (details->size != 0x498 || data->size != 0x18)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
const auto trophyDir = const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
pugi::xml_document doc; pugi::xml_document doc;
pugi::xml_parse_result result = pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
if (result) { if (!result) {
auto trophyconf = doc.child("trophyconf"); LOG_ERROR(Lib_NpTrophy, "Failed to open trophy xml : {}", result.description());
for (pugi::xml_node_iterator it = trophyconf.children().begin(); return ORBIS_OK;
it != trophyconf.children().end(); ++it) { }
if (std::string(it->name()) == "trophy") { auto trophyconf = doc.child("trophyconf");
std::string currentTrophyId = it->attribute("id").value();
if (std::stoi(currentTrophyId) == trophyId) {
std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
std::string currentTrophyTimestamp = it->attribute("timestamp").value();
std::string currentTrophyGrade = it->attribute("ttype").value();
std::string currentTrophyGroupID = it->attribute("gid").value();
std::string currentTrophyHidden = it->attribute("hidden").value();
std::string currentTrophyName = it->child("name").text().as_string();
std::string currentTrophyDescription = it->child("detail").text().as_string();
if (currentTrophyUnlockState == "unlocked") { for (const pugi::xml_node& node : trophyconf.children()) {
details->trophyId = trophyId; std::string_view node_name = node.name();
if (currentTrophyGrade.empty()) {
details->trophyGrade = ORBIS_NP_TROPHY_GRADE_UNKNOWN;
} else {
details->trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0));
}
if (currentTrophyGroupID.empty()) {
details->groupId = ORBIS_NP_TROPHY_BASE_GAME_GROUP_ID;
} else {
details->groupId = std::stoi(currentTrophyGroupID);
}
if (currentTrophyHidden == "yes") {
details->hidden = true;
} else {
details->hidden = false;
}
strncpy(details->name, currentTrophyName.c_str(), if (node_name == "trophy") {
ORBIS_NP_TROPHY_NAME_MAX_SIZE); int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
strncpy(details->description, currentTrophyDescription.c_str(), if (current_trophy_id == trophyId) {
ORBIS_NP_TROPHY_DESCR_MAX_SIZE); bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
std::string_view current_trophy_grade = node.attribute("ttype").value();
std::string_view current_trophy_name = node.child("name").text().as_string();
std::string_view current_trophy_description =
node.child("detail").text().as_string();
data->trophyId = trophyId; uint64_t current_trophy_timestamp = node.attribute("timestamp").as_ullong();
data->unlocked = true; int current_trophy_groupid = node.attribute("gid").as_int(-1);
data->timestamp.tick = std::stoull(currentTrophyTimestamp); bool current_trophy_hidden = node.attribute("hidden").as_bool();
} else {
details->trophyId = trophyId;
if (currentTrophyGrade.empty()) {
details->trophyGrade = ORBIS_NP_TROPHY_GRADE_UNKNOWN;
} else {
details->trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0));
}
if (currentTrophyGroupID.empty()) {
details->groupId = ORBIS_NP_TROPHY_BASE_GAME_GROUP_ID;
} else {
details->groupId = std::stoi(currentTrophyGroupID);
}
if (currentTrophyHidden == "yes") {
details->hidden = true;
} else {
details->hidden = false;
}
strncpy(details->name, currentTrophyName.c_str(), details->trophy_id = trophyId;
ORBIS_NP_TROPHY_NAME_MAX_SIZE); details->trophy_grade = GetTrophyGradeFromChar(current_trophy_grade.at(0));
strncpy(details->description, currentTrophyDescription.c_str(), details->group_id = current_trophy_groupid;
ORBIS_NP_TROPHY_DESCR_MAX_SIZE); details->hidden = current_trophy_hidden;
data->trophyId = trophyId; strncpy(details->name, current_trophy_name.data(), ORBIS_NP_TROPHY_NAME_MAX_SIZE);
data->unlocked = false; strncpy(details->description, current_trophy_description.data(),
data->timestamp.tick = 0; ORBIS_NP_TROPHY_DESCR_MAX_SIZE);
}
} data->trophy_id = trophyId;
data->unlocked = current_trophy_unlockstate;
data->timestamp.tick = current_trophy_timestamp;
} }
} }
} else }
LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
return ORBIS_OK; return ORBIS_OK;
} }
@ -555,35 +511,36 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
ORBIS_NP_TROPHY_FLAG_ZERO(flags); ORBIS_NP_TROPHY_FLAG_ZERO(flags);
const auto trophyDir = const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
auto trophy_file = trophyDir / "trophy00" / "Xml" / "TROP.XML"; auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
pugi::xml_document doc; pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str()); pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
int numTrophies = 0; if (!result) {
LOG_ERROR(Lib_NpTrophy, "Failed to open trophy xml : {}", result.description());
return ORBIS_OK;
}
if (result) { int num_trophies = 0;
auto trophyconf = doc.child("trophyconf"); auto trophyconf = doc.child("trophyconf");
for (pugi::xml_node_iterator it = trophyconf.children().begin();
it != trophyconf.children().end(); ++it) {
std::string currentTrophyId = it->attribute("id").value(); for (const pugi::xml_node& node : trophyconf.children()) {
std::string currentTrophyUnlockState = it->attribute("unlockstate").value(); std::string_view node_name = node.name();
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
if (std::string(it->name()) == "trophy") { if (node_name == "trophy") {
numTrophies++; num_trophies++;
}
if (currentTrophyUnlockState == "unlocked") {
ORBIS_NP_TROPHY_FLAG_SET(std::stoi(currentTrophyId), flags);
}
} }
} else
LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
*count = numTrophies; if (current_trophy_unlockstate) {
ORBIS_NP_TROPHY_FLAG_SET(current_trophy_id, flags);
}
}
*count = num_trophies;
return ORBIS_OK; return ORBIS_OK;
} }
@ -912,148 +869,119 @@ int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTr
if (platinumId == nullptr) if (platinumId == nullptr)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
const auto trophyDir = const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
pugi::xml_document doc; pugi::xml_document doc;
pugi::xml_parse_result result = pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
if (!result) {
LOG_ERROR(Lib_NpTrophy, "Failed to parse trophy xml : {}", result.description());
return ORBIS_OK;
}
*platinumId = ORBIS_NP_TROPHY_INVALID_TROPHY_ID; *platinumId = ORBIS_NP_TROPHY_INVALID_TROPHY_ID;
int numTrophies = 0; int num_trophies = 0;
int numTrophiesUnlocked = 0; int num_trophies_unlocked = 0;
pugi::xml_node platinum_node;
pugi::xml_node_iterator platinumIt; auto trophyconf = doc.child("trophyconf");
int platinumTrophyGroup = -1;
if (result) { for (pugi::xml_node& node : trophyconf.children()) {
auto trophyconf = doc.child("trophyconf"); int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
for (pugi::xml_node_iterator it = trophyconf.children().begin(); bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
it != trophyconf.children().end(); ++it) { const char* current_trophy_name = node.child("name").text().as_string();
std::string_view current_trophy_description = node.child("detail").text().as_string();
std::string_view current_trophy_type = node.attribute("ttype").value();
std::string currentTrophyId = it->attribute("id").value(); if (current_trophy_type == "P") {
std::string currentTrophyName = it->child("name").text().as_string(); platinum_node = node;
std::string currentTrophyDescription = it->child("detail").text().as_string(); if (trophyId == current_trophy_id) {
std::string currentTrophyType = it->attribute("ttype").value(); return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK;
std::string currentTrophyUnlockState = it->attribute("unlockstate").value(); }
}
if (currentTrophyType == "P") { if (std::string_view(node.name()) == "trophy") {
platinumIt = it; if (node.attribute("pid").as_int(-1) != ORBIS_NP_TROPHY_INVALID_TROPHY_ID) {
num_trophies++;
if (std::string(platinumIt->attribute("gid").value()).empty()) { if (current_trophy_unlockstate) {
platinumTrophyGroup = -1; num_trophies_unlocked++;
} else {
platinumTrophyGroup =
std::stoi(std::string(platinumIt->attribute("gid").value()));
}
if (trophyId == std::stoi(currentTrophyId)) {
return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK;
} }
} }
if (std::string(it->name()) == "trophy") { if (current_trophy_id == trophyId) {
if (platinumTrophyGroup == -1) { if (current_trophy_unlockstate) {
if (std::string(it->attribute("gid").value()).empty()) { LOG_INFO(Lib_NpTrophy, "Trophy already unlocked");
numTrophies++; return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
if (currentTrophyUnlockState == "unlocked") {
numTrophiesUnlocked++;
}
}
} else { } else {
if (!std::string(it->attribute("gid").value()).empty()) { if (node.attribute("unlockstate").empty()) {
if (std::stoi(std::string(it->attribute("gid").value())) == node.append_attribute("unlockstate") = "true";
platinumTrophyGroup) {
numTrophies++;
if (currentTrophyUnlockState == "unlocked") {
numTrophiesUnlocked++;
}
}
}
}
if (std::stoi(currentTrophyId) == trophyId) {
LOG_INFO(Lib_NpTrophy, "Found trophy to unlock {} : {}",
it->child("name").text().as_string(),
it->child("detail").text().as_string());
if (currentTrophyUnlockState == "unlocked") {
LOG_INFO(Lib_NpTrophy, "Trophy already unlocked");
return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
} else { } else {
if (std::string(it->attribute("unlockstate").value()).empty()) { node.attribute("unlockstate").set_value("true");
it->append_attribute("unlockstate") = "unlocked";
} else {
it->attribute("unlockstate").set_value("unlocked");
}
Rtc::OrbisRtcTick trophyTimestamp;
Rtc::sceRtcGetCurrentTick(&trophyTimestamp);
if (std::string(it->attribute("timestamp").value()).empty()) {
it->append_attribute("timestamp") =
std::to_string(trophyTimestamp.tick).c_str();
} else {
it->attribute("timestamp")
.set_value(std::to_string(trophyTimestamp.tick).c_str());
}
g_trophy_ui.AddTrophyToQueue(trophyId, currentTrophyName);
} }
Rtc::OrbisRtcTick trophyTimestamp;
Rtc::sceRtcGetCurrentTick(&trophyTimestamp);
if (node.attribute("timestamp").empty()) {
node.append_attribute("timestamp") =
std::to_string(trophyTimestamp.tick).c_str();
} else {
node.attribute("timestamp")
.set_value(std::to_string(trophyTimestamp.tick).c_str());
}
std::string trophy_icon_file = "TROP";
trophy_icon_file.append(node.attribute("id").value());
trophy_icon_file.append(".PNG");
std::filesystem::path current_icon_path =
trophy_dir / "trophy00" / "Icons" / trophy_icon_file;
AddTrophyToQueue(current_icon_path, current_trophy_name);
} }
} }
} }
}
if (std::string(platinumIt->attribute("unlockstate").value()).empty()) { if (!platinum_node.attribute("unlockstate").as_bool()) {
if ((numTrophies - 2) == numTrophiesUnlocked) { if ((num_trophies - 1) == num_trophies_unlocked) {
if (platinum_node.attribute("unlockstate").empty()) {
platinumIt->append_attribute("unlockstate") = "unlocked"; platinum_node.append_attribute("unlockstate") = "true";
} else {
Rtc::OrbisRtcTick trophyTimestamp; platinum_node.attribute("unlockstate").set_value("true");
Rtc::sceRtcGetCurrentTick(&trophyTimestamp);
if (std::string(platinumIt->attribute("timestamp").value()).empty()) {
platinumIt->append_attribute("timestamp") =
std::to_string(trophyTimestamp.tick).c_str();
} else {
platinumIt->attribute("timestamp")
.set_value(std::to_string(trophyTimestamp.tick).c_str());
}
std::string platinumTrophyId = platinumIt->attribute("id").value();
std::string platinumTrophyName = platinumIt->child("name").text().as_string();
*platinumId = std::stoi(platinumTrophyId);
g_trophy_ui.AddTrophyToQueue(*platinumId, platinumTrophyName);
} }
} else if (std::string(platinumIt->attribute("unlockstate").value()) == "locked") {
if ((numTrophies - 2) == numTrophiesUnlocked) {
platinumIt->attribute("unlockstate").set_value("unlocked"); Rtc::OrbisRtcTick trophyTimestamp;
Rtc::sceRtcGetCurrentTick(&trophyTimestamp);
Rtc::OrbisRtcTick trophyTimestamp; if (platinum_node.attribute("timestamp").empty()) {
Rtc::sceRtcGetCurrentTick(&trophyTimestamp); platinum_node.append_attribute("timestamp") =
std::to_string(trophyTimestamp.tick).c_str();
if (std::string(platinumIt->attribute("timestamp").value()).empty()) { } else {
platinumIt->append_attribute("timestamp") = platinum_node.attribute("timestamp")
std::to_string(trophyTimestamp.tick).c_str(); .set_value(std::to_string(trophyTimestamp.tick).c_str());
} else {
platinumIt->attribute("timestamp")
.set_value(std::to_string(trophyTimestamp.tick).c_str());
}
std::string platinumTrophyId = platinumIt->attribute("id").value();
std::string platinumTrophyName = platinumIt->child("name").text().as_string();
*platinumId = std::stoi(platinumTrophyId);
g_trophy_ui.AddTrophyToQueue(*platinumId, platinumTrophyName);
} }
int platinum_trophy_id =
platinum_node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
const char* platinum_trophy_name = platinum_node.child("name").text().as_string();
std::string platinum_icon_file = "TROP";
platinum_icon_file.append(platinum_node.attribute("id").value());
platinum_icon_file.append(".PNG");
std::filesystem::path platinum_icon_path =
trophy_dir / "trophy00" / "Icons" / platinum_icon_file;
*platinumId = platinum_trophy_id;
AddTrophyToQueue(platinum_icon_path, platinum_trophy_name);
} }
}
doc.save_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str()); doc.save_file((trophy_dir / "trophy00" / "Xml" / "TROP.XML").native().c_str());
} else
LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
return ORBIS_OK; return ORBIS_OK;
} }

View File

@ -47,7 +47,7 @@ bool ORBIS_NP_TROPHY_FLAG_ISSET(int32_t trophyId, OrbisNpTrophyFlagArray* p);
struct OrbisNpTrophyData { struct OrbisNpTrophyData {
size_t size; size_t size;
OrbisNpTrophyId trophyId; OrbisNpTrophyId trophy_id;
bool unlocked; bool unlocked;
uint8_t reserved[3]; uint8_t reserved[3];
Rtc::OrbisRtcTick timestamp; Rtc::OrbisRtcTick timestamp;
@ -66,9 +66,9 @@ constexpr int ORBIS_NP_TROPHY_INVALID_GROUP_ID = -2;
struct OrbisNpTrophyDetails { struct OrbisNpTrophyDetails {
size_t size; size_t size;
OrbisNpTrophyId trophyId; OrbisNpTrophyId trophy_id;
OrbisNpTrophyGrade trophyGrade; OrbisNpTrophyGrade trophy_grade;
OrbisNpTrophyGroupId groupId; OrbisNpTrophyGroupId group_id;
bool hidden; bool hidden;
uint8_t reserved[3]; uint8_t reserved[3];
char name[ORBIS_NP_TROPHY_NAME_MAX_SIZE]; char name[ORBIS_NP_TROPHY_NAME_MAX_SIZE];
@ -77,46 +77,46 @@ struct OrbisNpTrophyDetails {
struct OrbisNpTrophyGameData { struct OrbisNpTrophyGameData {
size_t size; size_t size;
uint32_t unlockedTrophies; uint32_t unlocked_trophies;
uint32_t unlockedPlatinum; uint32_t unlocked_platinum;
uint32_t unlockedGold; uint32_t unlocked_gold;
uint32_t unlockedSilver; uint32_t unlocked_silver;
uint32_t unlockedBronze; uint32_t unlocked_bronze;
uint32_t progressPercentage; uint32_t progress_percentage;
}; };
struct OrbisNpTrophyGameDetails { struct OrbisNpTrophyGameDetails {
size_t size; size_t size;
uint32_t numGroups; uint32_t num_groups;
uint32_t numTrophies; uint32_t num_trophies;
uint32_t numPlatinum; uint32_t num_platinum;
uint32_t numGold; uint32_t num_gold;
uint32_t numSilver; uint32_t num_silver;
uint32_t numBronze; uint32_t num_bronze;
char title[ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE]; char title[ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE];
char description[ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE]; char description[ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE];
}; };
struct OrbisNpTrophyGroupData { struct OrbisNpTrophyGroupData {
size_t size; size_t size;
OrbisNpTrophyGroupId groupId; OrbisNpTrophyGroupId group_id;
uint32_t unlockedTrophies; uint32_t unlocked_trophies;
uint32_t unlockedPlatinum; uint32_t unlocked_platinum;
uint32_t unlockedGold; uint32_t unlocked_gold;
uint32_t unlockedSilver; uint32_t unlocked_silver;
uint32_t unlockedBronze; uint32_t unlocked_bronze;
uint32_t progressPercentage; uint32_t progress_percentage;
uint8_t reserved[4]; uint8_t reserved[4];
}; };
struct OrbisNpTrophyGroupDetails { struct OrbisNpTrophyGroupDetails {
size_t size; size_t size;
OrbisNpTrophyGroupId groupId; OrbisNpTrophyGroupId group_id;
uint32_t numTrophies; uint32_t num_trophies;
uint32_t numPlatinum; uint32_t num_platinum;
uint32_t numGold; uint32_t num_gold;
uint32_t numSilver; uint32_t num_silver;
uint32_t numBronze; uint32_t num_bronze;
char title[ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE]; char title[ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE];
char description[ORBIS_NP_TROPHY_GROUP_DESCR_MAX_SIZE]; char description[ORBIS_NP_TROPHY_GROUP_DESCR_MAX_SIZE];
}; };

View File

@ -2,15 +2,28 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono> #include <chrono>
#include <mutex>
#include <imgui.h> #include <imgui.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/singleton.h"
#include "imgui/imgui_std.h" #include "imgui/imgui_std.h"
#include "trophy_ui.h" #include "trophy_ui.h"
using namespace ImGui; using namespace ImGui;
using namespace Libraries::NpTrophy; namespace Libraries::NpTrophy {
TrophyUI::TrophyUI() { std::optional<TrophyUI> current_trophy_ui;
std::queue<TrophyInfo> trophy_queue;
std::mutex queueMtx;
TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::string& trophyName)
: trophy_name(trophyName) {
if (std::filesystem::exists(trophyIconPath)) {
trophy_icon = RefCountedTexture::DecodePngFile(trophyIconPath);
} else {
LOG_ERROR(Lib_NpTrophy, "Couldnt load trophy icon at {}",
fmt::UTF(trophyIconPath.u8string()));
}
AddLayer(this); AddLayer(this);
} }
@ -18,57 +31,63 @@ TrophyUI::~TrophyUI() {
Finish(); Finish();
} }
void Libraries::NpTrophy::TrophyUI::AddTrophyToQueue(int trophyId, std::string trophyName) {
TrophyInfo newInfo;
newInfo.trophyId = trophyId;
newInfo.trophyName = trophyName;
trophyQueue.push_back(newInfo);
}
void TrophyUI::Finish() { void TrophyUI::Finish() {
RemoveLayer(this); RemoveLayer(this);
} }
bool displayingTrophy;
std::chrono::steady_clock::time_point trophyStartedTime;
void TrophyUI::Draw() { void TrophyUI::Draw() {
const auto& io = GetIO(); const auto& io = GetIO();
const ImVec2 window_size{ const ImVec2 window_size{
std::min(io.DisplaySize.x, 200.f), std::min(io.DisplaySize.x, 250.f),
std::min(io.DisplaySize.y, 75.f), std::min(io.DisplaySize.y, 70.f),
}; };
if (trophyQueue.size() != 0) { SetNextWindowSize(window_size);
if (!displayingTrophy) { SetNextWindowCollapsed(false);
displayingTrophy = true; SetNextWindowPos(ImVec2(io.DisplaySize.x - 250, 50));
trophyStartedTime = std::chrono::steady_clock::now(); KeepNavHighlight();
if (Begin("Trophy Window", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoInputs)) {
if (trophy_icon) {
Image(trophy_icon.GetTexture().im_id, ImVec2(50, 50));
ImGui::SameLine();
} else {
// placeholder
const auto pos = GetCursorScreenPos();
ImGui::GetWindowDrawList()->AddRectFilled(pos, pos + ImVec2{50.0f},
GetColorU32(ImVec4{0.7f}));
ImGui::Indent(60);
} }
TextWrapped("Trophy earned!\n%s", trophy_name.c_str());
}
End();
std::chrono::steady_clock::time_point timeNow = std::chrono::steady_clock::now(); trophy_timer -= io.DeltaTime;
std::chrono::seconds duration = if (trophy_timer <= 0) {
std::chrono::duration_cast<std::chrono::seconds>(timeNow - trophyStartedTime); std::lock_guard<std::mutex> lock(queueMtx);
if (!trophy_queue.empty()) {
if (duration.count() >= 5) { TrophyInfo next_trophy = trophy_queue.front();
trophyQueue.erase(trophyQueue.begin()); trophy_queue.pop();
displayingTrophy = false; current_trophy_ui.emplace(next_trophy.trophy_icon_path, next_trophy.trophy_name);
} } else {
current_trophy_ui.reset();
if (trophyQueue.size() != 0) {
SetNextWindowSize(window_size);
SetNextWindowCollapsed(false);
SetNextWindowPos(ImVec2(io.DisplaySize.x - 200, 50));
KeepNavHighlight();
TrophyInfo currentTrophyInfo = trophyQueue[0];
if (Begin("Trophy Window", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoInputs)) {
Text("Trophy earned!");
TextWrapped("%s", currentTrophyInfo.trophyName.c_str());
}
End();
} }
} }
} }
void AddTrophyToQueue(const std::filesystem::path& trophyIconPath, const std::string& trophyName) {
std::lock_guard<std::mutex> lock(queueMtx);
if (current_trophy_ui.has_value()) {
TrophyInfo new_trophy;
new_trophy.trophy_icon_path = trophyIconPath;
new_trophy.trophy_name = trophyName;
trophy_queue.push(new_trophy);
} else {
current_trophy_ui.emplace(trophyIconPath, trophyName);
}
}
} // namespace Libraries::NpTrophy

View File

@ -5,32 +5,36 @@
#include <string> #include <string>
#include <variant> #include <variant>
#include <vector> #include <queue>
#include "common/fixed_value.h" #include "common/fixed_value.h"
#include "common/types.h" #include "common/types.h"
#include "core/libraries/np_trophy/np_trophy.h" #include "core/libraries/np_trophy/np_trophy.h"
#include "imgui/imgui_layer.h" #include "imgui/imgui_layer.h"
#include "imgui/imgui_texture.h"
namespace Libraries::NpTrophy { namespace Libraries::NpTrophy {
struct TrophyInfo {
int trophyId = -1;
std::string trophyName;
};
class TrophyUI final : public ImGui::Layer { class TrophyUI final : public ImGui::Layer {
std::vector<TrophyInfo> trophyQueue;
public: public:
TrophyUI(); TrophyUI(const std::filesystem::path& trophyIconPath, const std::string& trophyName);
~TrophyUI() override; ~TrophyUI() override;
void AddTrophyToQueue(int trophyId, std::string trophyName);
void Finish(); void Finish();
void Draw() override; void Draw() override;
private:
std::string trophy_name;
float trophy_timer = 5.0f;
ImGui::RefCountedTexture trophy_icon;
}; };
struct TrophyInfo {
std::filesystem::path trophy_icon_path;
std::string trophy_name;
};
void AddTrophyToQueue(const std::filesystem::path& trophyIconPath, const std::string& trophyName);
}; // namespace Libraries::NpTrophy }; // namespace Libraries::NpTrophy

View File

@ -368,12 +368,13 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
pData->angularVelocity.x = 0.0f; pData->angularVelocity.x = 0.0f;
pData->angularVelocity.y = 0.0f; pData->angularVelocity.y = 0.0f;
pData->angularVelocity.z = 0.0f; pData->angularVelocity.z = 0.0f;
pData->touchData.touchNum = 0; pData->touchData.touchNum =
pData->touchData.touch[0].x = 0; (state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0);
pData->touchData.touch[0].y = 0; pData->touchData.touch[0].x = state.touchpad[0].x;
pData->touchData.touch[0].y = state.touchpad[0].y;
pData->touchData.touch[0].id = 1; pData->touchData.touch[0].id = 1;
pData->touchData.touch[1].x = 0; pData->touchData.touch[1].x = state.touchpad[1].x;
pData->touchData.touch[1].y = 0; pData->touchData.touch[1].y = state.touchpad[1].y;
pData->touchData.touch[1].id = 2; pData->touchData.touch[1].id = 2;
pData->timestamp = state.time; pData->timestamp = state.time;
pData->connected = true; // isConnected; //TODO fix me proper pData->connected = true; // isConnected; //TODO fix me proper

View File

@ -157,7 +157,11 @@ void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_cor
if (copy_icon) { if (copy_icon) {
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png"); const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
if (fs::exists(src_icon)) { if (fs::exists(src_icon)) {
fs::copy_file(src_icon, GetIconPath()); auto output_icon = GetIconPath();
if (fs::exists(output_icon)) {
fs::remove(output_icon);
}
fs::copy_file(src_icon, output_icon);
} }
} }
exists = true; exists = true;

View File

@ -207,7 +207,7 @@ void SetIcon(void* buf, size_t buf_size) {
} else { } else {
g_icon_memory.resize(buf_size); g_icon_memory.resize(buf_size);
std::memcpy(g_icon_memory.data(), buf, buf_size); std::memcpy(g_icon_memory.data(), buf, buf_size);
IOFile file(g_icon_path, Common::FS::FileAccessMode::Append); IOFile file(g_icon_path, Common::FS::FileAccessMode::Write);
file.Seek(0); file.Seek(0);
file.WriteRaw<u8>(g_icon_memory.data(), buf_size); file.WriteRaw<u8>(g_icon_memory.data(), buf_size);
file.Close(); file.Close();

View File

@ -262,6 +262,14 @@ struct OrbisSaveDataRestoreBackupData {
s32 : 32; s32 : 32;
}; };
struct OrbisSaveDataTransferringMount {
OrbisUserServiceUserId userId;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
const OrbisSaveDataFingerprint* fingerprint;
std::array<u8, 32> _reserved;
};
struct OrbisSaveDataDirNameSearchCond { struct OrbisSaveDataDirNameSearchCond {
OrbisUserServiceUserId userId; OrbisUserServiceUserId userId;
int : 32; int : 32;
@ -357,7 +365,8 @@ static Error setNotInitializedError() {
} }
static Error saveDataMount(const OrbisSaveDataMount2* mount_info, static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
OrbisSaveDataMountResult* mount_result) { OrbisSaveDataMountResult* mount_result,
std::string_view title_id = g_game_serial) {
if (mount_info->userId < 0) { if (mount_info->userId < 0) {
return Error::INVALID_LOGIN_USER; return Error::INVALID_LOGIN_USER;
@ -369,8 +378,8 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
// check backup status // check backup status
{ {
const auto save_path = SaveInstance::MakeDirSavePath(mount_info->userId, g_game_serial, const auto save_path =
mount_info->dirName->data); SaveInstance::MakeDirSavePath(mount_info->userId, title_id, mount_info->dirName->data);
if (Backup::IsBackupExecutingFor(save_path) && g_fw_ver) { if (Backup::IsBackupExecutingFor(save_path) && g_fw_ver) {
return Error::BACKUP_BUSY; return Error::BACKUP_BUSY;
} }
@ -409,7 +418,7 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
return Error::MOUNT_FULL; return Error::MOUNT_FULL;
} }
SaveInstance save_instance{slot_num, mount_info->userId, g_game_serial, dir_name, SaveInstance save_instance{slot_num, mount_info->userId, std::string{title_id}, dir_name,
(int)mount_info->blocks}; (int)mount_info->blocks};
if (save_instance.Mounted()) { if (save_instance.Mounted()) {
@ -1573,6 +1582,7 @@ Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetu
SaveMemory::SetIcon(nullptr, 0); SaveMemory::SetIcon(nullptr, 0);
} }
} }
SaveMemory::TriggerSaveWithoutEvent();
if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) { if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) {
result->existedMemorySize = existed_size; result->existedMemorySize = existed_size;
} }
@ -1646,9 +1656,24 @@ Error PS4_SYSV_ABI sceSaveDataTerminate() {
return Error::OK; return Error::OK;
} }
int PS4_SYSV_ABI sceSaveDataTransferringMount() { Error PS4_SYSV_ABI sceSaveDataTransferringMount(const OrbisSaveDataTransferringMount* mount,
LOG_ERROR(Lib_SaveData, "(STUBBED) called"); OrbisSaveDataMountResult* mountResult) {
return ORBIS_OK; LOG_DEBUG(Lib_SaveData, "called");
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return setNotInitializedError();
}
if (mount == nullptr || mount->titleId == nullptr || mount->dirName == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER;
}
LOG_DEBUG(Lib_SaveData, "called titleId: {}, dirName: {}", mount->titleId->data.to_view(),
mount->dirName->data.to_view());
OrbisSaveDataMount2 mount_info{};
mount_info.userId = mount->userId;
mount_info.dirName = mount->dirName;
mount_info.mountMode = OrbisSaveDataMountMode::RDONLY;
return saveDataMount(&mount_info, mountResult, mount->titleId->data.to_string());
} }
Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) { Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) {

View File

@ -70,6 +70,7 @@ struct OrbisSaveDataMountInfo;
struct OrbisSaveDataMountPoint; struct OrbisSaveDataMountPoint;
struct OrbisSaveDataMountResult; struct OrbisSaveDataMountResult;
struct OrbisSaveDataRestoreBackupData; struct OrbisSaveDataRestoreBackupData;
struct OrbisSaveDataTransferringMount;
int PS4_SYSV_ABI sceSaveDataAbort(); int PS4_SYSV_ABI sceSaveDataAbort();
Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup); Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup);
@ -174,7 +175,8 @@ int PS4_SYSV_ABI sceSaveDataSupportedFakeBrokenStatus();
int PS4_SYSV_ABI sceSaveDataSyncCloudList(); int PS4_SYSV_ABI sceSaveDataSyncCloudList();
Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam); Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam);
Error PS4_SYSV_ABI sceSaveDataTerminate(); Error PS4_SYSV_ABI sceSaveDataTerminate();
int PS4_SYSV_ABI sceSaveDataTransferringMount(); Error PS4_SYSV_ABI sceSaveDataTransferringMount(const OrbisSaveDataTransferringMount* mount,
OrbisSaveDataMountResult* mountResult);
Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint); Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint);
int PS4_SYSV_ABI sceSaveDataUmountSys(); int PS4_SYSV_ABI sceSaveDataUmountSys();
Error PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint); Error PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint);

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <thread> #include <thread>
#include <utility>
#include <imgui.h> #include <imgui.h>
#include "common/assert.h" #include "common/assert.h"
@ -281,10 +282,15 @@ void MsgDialogUi::Draw() {
first_render = false; first_render = false;
} }
DialogResult Libraries::MsgDialog::ShowMsgDialog(MsgDialogState state, bool block) { DialogResult Libraries::MsgDialog::ShowMsgDialog(MsgDialogState p_state, bool block) {
DialogResult result{}; static DialogResult result{};
Status status = Status::RUNNING; static Status status;
MsgDialogUi dialog(&state, &status, &result); static MsgDialogUi dialog;
static MsgDialogState state;
dialog = MsgDialogUi{};
status = Status::RUNNING;
state = std::move(p_state);
dialog = MsgDialogUi(&state, &status, &result);
if (block) { if (block) {
while (status == Status::RUNNING) { while (status == Status::RUNNING) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));

View File

@ -8,6 +8,7 @@
#include "common/config.h" #include "common/config.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/thread.h" #include "common/thread.h"
#include "core/debug_state.h"
#include "core/libraries/error_codes.h" #include "core/libraries/error_codes.h"
#include "core/libraries/kernel/time_management.h" #include "core/libraries/kernel/time_management.h"
#include "core/libraries/videoout/driver.h" #include "core/libraries/videoout/driver.h"
@ -284,7 +285,7 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
if (vblank_status.count % (main_port.flip_rate + 1) == 0) { if (vblank_status.count % (main_port.flip_rate + 1) == 0) {
const auto request = receive_request(); const auto request = receive_request();
if (!request) { if (!request) {
if (!main_port.is_open) { if (!main_port.is_open || DebugState.IsGuestThreadsPaused()) {
DrawBlankFrame(); DrawBlankFrame();
} }
} else { } else {

View File

@ -18,6 +18,7 @@
#include "core/memory.h" #include "core/memory.h"
#include "core/tls.h" #include "core/tls.h"
#include "core/virtual_memory.h" #include "core/virtual_memory.h"
#include "debug_state.h"
namespace Core { namespace Core {
@ -27,8 +28,8 @@ static PS4_SYSV_ABI void ProgramExitFunc() {
fmt::print("exit function called\n"); fmt::print("exit function called\n");
} }
static void RunMainEntry(VAddr addr, EntryParams* params, ExitFunc exit_func) {
#ifdef ARCH_X86_64 #ifdef ARCH_X86_64
static PS4_SYSV_ABI void RunMainEntry(VAddr addr, EntryParams* params, ExitFunc exit_func) {
// reinterpret_cast<entry_func_t>(addr)(params, exit_func); // can't be used, stack has to have // reinterpret_cast<entry_func_t>(addr)(params, exit_func); // can't be used, stack has to have
// a specific layout // a specific layout
asm volatile("andq $-16, %%rsp\n" // Align to 16 bytes asm volatile("andq $-16, %%rsp\n" // Align to 16 bytes
@ -48,10 +49,8 @@ static void RunMainEntry(VAddr addr, EntryParams* params, ExitFunc exit_func) {
: :
: "r"(addr), "r"(params), "r"(exit_func) : "r"(addr), "r"(params), "r"(exit_func)
: "rax", "rsi", "rdi"); : "rax", "rsi", "rdi");
#else
UNIMPLEMENTED_MSG("Missing RunMainEntry() implementation for target CPU architecture.");
#endif
} }
#endif
Linker::Linker() : memory{Memory::Instance()} {} Linker::Linker() : memory{Memory::Instance()} {}
@ -90,6 +89,7 @@ void Linker::Execute() {
// Init primary thread. // Init primary thread.
Common::SetCurrentThreadName("GAME_MainThread"); Common::SetCurrentThreadName("GAME_MainThread");
DebugState.AddCurrentThreadToGuestList();
Libraries::Kernel::pthreadInitSelfMainThread(); Libraries::Kernel::pthreadInitSelfMainThread();
EnsureThreadInitialized(true); EnsureThreadInitialized(true);
@ -107,7 +107,12 @@ void Linker::Execute() {
for (auto& m : m_modules) { for (auto& m : m_modules) {
if (!m->IsSharedLib()) { if (!m->IsSharedLib()) {
RunMainEntry(m->GetEntryAddress(), &p, ProgramExitFunc); #ifdef ARCH_X86_64
ExecuteGuest(RunMainEntry, m->GetEntryAddress(), &p, ProgramExitFunc);
#else
UNIMPLEMENTED_MSG(
"Missing guest entrypoint implementation for target CPU architecture.");
#endif
} }
} }
@ -322,7 +327,8 @@ void* Linker::TlsGetAddr(u64 module_index, u64 offset) {
Module* module = m_modules[module_index - 1].get(); Module* module = m_modules[module_index - 1].get();
const u32 init_image_size = module->tls.init_image_size; const u32 init_image_size = module->tls.init_image_size;
// TODO: Determine if Windows will crash from this // TODO: Determine if Windows will crash from this
u8* dest = reinterpret_cast<u8*>(heap_api->heap_malloc(module->tls.image_size)); u8* dest =
reinterpret_cast<u8*>(ExecuteGuest(heap_api->heap_malloc, module->tls.image_size));
const u8* src = reinterpret_cast<const u8*>(module->tls.image_virtual_addr); const u8* src = reinterpret_cast<const u8*>(module->tls.image_virtual_addr);
std::memcpy(dest, src, init_image_size); std::memcpy(dest, src, init_image_size);
std::memset(dest + init_image_size, 0, module->tls.image_size - init_image_size); std::memset(dest + init_image_size, 0, module->tls.image_size - init_image_size);
@ -334,7 +340,7 @@ void* Linker::TlsGetAddr(u64 module_index, u64 offset) {
thread_local std::once_flag init_tls_flag; thread_local std::once_flag init_tls_flag;
void Linker::EnsureThreadInitialized(bool is_primary) { void Linker::EnsureThreadInitialized(bool is_primary) const {
std::call_once(init_tls_flag, [this, is_primary] { std::call_once(init_tls_flag, [this, is_primary] {
#ifdef ARCH_X86_64 #ifdef ARCH_X86_64
InitializeThreadPatchStack(); InitializeThreadPatchStack();
@ -343,7 +349,7 @@ void Linker::EnsureThreadInitialized(bool is_primary) {
}); });
} }
void Linker::InitTlsForThread(bool is_primary) { void Linker::InitTlsForThread(bool is_primary) const {
static constexpr size_t TcbSize = 0x40; static constexpr size_t TcbSize = 0x40;
static constexpr size_t TlsAllocAlign = 0x20; static constexpr size_t TlsAllocAlign = 0x20;
const size_t total_tls_size = Common::AlignUp(static_tls_size, TlsAllocAlign) + TcbSize; const size_t total_tls_size = Common::AlignUp(static_tls_size, TlsAllocAlign) + TcbSize;
@ -365,7 +371,7 @@ void Linker::InitTlsForThread(bool is_primary) {
} else { } else {
if (heap_api) { if (heap_api) {
#ifndef WIN32 #ifndef WIN32
addr_out = heap_api->heap_malloc(total_tls_size); addr_out = ExecuteGuestWithoutTls(heap_api->heap_malloc, total_tls_size);
} else { } else {
addr_out = std::malloc(total_tls_size); addr_out = std::malloc(total_tls_size);
#else #else

View File

@ -109,16 +109,23 @@ public:
void DebugDump(); void DebugDump();
template <class ReturnType, class... FuncArgs, class... CallArgs> template <class ReturnType, class... FuncArgs, class... CallArgs>
ReturnType ExecuteGuest(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...), CallArgs&&... args) { ReturnType ExecuteGuest(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...),
CallArgs&&... args) const {
// Make sure TLS is initialized for the thread before entering guest. // Make sure TLS is initialized for the thread before entering guest.
EnsureThreadInitialized(); EnsureThreadInitialized();
return func(std::forward<CallArgs>(args)...); return ExecuteGuestWithoutTls(func, args...);
} }
private: private:
const Module* FindExportedModule(const ModuleInfo& m, const LibraryInfo& l); const Module* FindExportedModule(const ModuleInfo& m, const LibraryInfo& l);
void EnsureThreadInitialized(bool is_primary = false); void EnsureThreadInitialized(bool is_primary = false) const;
void InitTlsForThread(bool is_primary); void InitTlsForThread(bool is_primary) const;
template <class ReturnType, class... FuncArgs, class... CallArgs>
ReturnType ExecuteGuestWithoutTls(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...),
CallArgs&&... args) const {
return func(std::forward<CallArgs>(args)...);
}
MemoryManager* memory; MemoryManager* memory;
std::mutex mutex; std::mutex mutex;

View File

@ -70,7 +70,7 @@ Module::~Module() = default;
s32 Module::Start(size_t args, const void* argp, void* param) { s32 Module::Start(size_t args, const void* argp, void* param) {
LOG_INFO(Core_Linker, "Module started : {}", name); LOG_INFO(Core_Linker, "Module started : {}", name);
auto* linker = Common::Singleton<Core::Linker>::Instance(); const auto* linker = Common::Singleton<Core::Linker>::Instance();
const VAddr addr = dynamic_info.init_virtual_addr + GetBaseAddress(); const VAddr addr = dynamic_info.init_virtual_addr + GetBaseAddress();
return linker->ExecuteGuest(reinterpret_cast<EntryFunc>(addr), args, argp, param); return linker->ExecuteGuest(reinterpret_cast<EntryFunc>(addr), args, argp, param);
} }

View File

@ -83,6 +83,12 @@ static void SignalHandler(int sig, siginfo_t* info, void* raw_context) {
fmt::ptr(code_address), DisassembleInstruction(code_address)); fmt::ptr(code_address), DisassembleInstruction(code_address));
} }
break; break;
case SIGUSR1: { // Sleep thread until signal is received
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGUSR1);
sigwait(&sigset, &sig);
} break;
default: default:
break; break;
} }
@ -105,6 +111,8 @@ SignalDispatch::SignalDispatch() {
"Failed to register access violation signal handler."); "Failed to register access violation signal handler.");
ASSERT_MSG(sigaction(SIGILL, &action, nullptr) == 0, ASSERT_MSG(sigaction(SIGILL, &action, nullptr) == 0,
"Failed to register illegal instruction signal handler."); "Failed to register illegal instruction signal handler.");
ASSERT_MSG(sigaction(SIGUSR1, &action, nullptr) == 0,
"Failed to register sleep signal handler.");
#endif #endif
} }

View File

@ -59,10 +59,10 @@ Emulator::Emulator() {
LOG_INFO(Loader, "Branch {}", Common::g_scm_branch); LOG_INFO(Loader, "Branch {}", Common::g_scm_branch);
LOG_INFO(Loader, "Description {}", Common::g_scm_desc); LOG_INFO(Loader, "Description {}", Common::g_scm_desc);
LOG_INFO(Config, "General Logtype: {}", Config::getLogType());
LOG_INFO(Config, "General isNeo: {}", Config::isNeoMode()); LOG_INFO(Config, "General isNeo: {}", Config::isNeoMode());
LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu()); LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu());
LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders()); LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders());
LOG_INFO(Config, "GPU shouldDumpPM4: {}", Config::dumpPM4());
LOG_INFO(Config, "GPU vblankDivider: {}", Config::vblankDiv()); LOG_INFO(Config, "GPU vblankDivider: {}", Config::vblankDiv());
LOG_INFO(Config, "Vulkan gpuId: {}", Config::getGpuId()); LOG_INFO(Config, "Vulkan gpuId: {}", Config::getGpuId());
LOG_INFO(Config, "Vulkan vkValidation: {}", Config::vkValidationEnabled()); LOG_INFO(Config, "Vulkan vkValidation: {}", Config::vkValidationEnabled());
@ -116,7 +116,7 @@ void Emulator::Run(const std::filesystem::path& file) {
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles"; Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles";
if (!std::filesystem::exists(trophyDir)) { if (!std::filesystem::exists(trophyDir)) {
TRP trp; TRP trp;
if (!trp.Extract(file.parent_path())) { if (!trp.Extract(file.parent_path(), id)) {
LOG_ERROR(Loader, "Couldn't extract trophies"); LOG_ERROR(Loader, "Couldn't extract trophies");
} }
} }

View File

@ -10,6 +10,9 @@
#define IM_COL32_GRAY(x) IM_COL32(x, x, x, 0xFF) #define IM_COL32_GRAY(x) IM_COL32(x, x, x, 0xFF)
#define IMGUI_FONT_TEXT 0
#define IMGUI_FONT_MONO 1
namespace ImGui { namespace ImGui {
namespace Easing { namespace Easing {
@ -27,6 +30,20 @@ inline void CentralizeWindow() {
SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f}); SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f});
} }
inline void KeepWindowInside(ImVec2 display_size = GetIO().DisplaySize) {
const auto cur_pos = GetWindowPos();
if (cur_pos.x < 0.0f || cur_pos.y < 0.0f) {
SetWindowPos(ImMax(cur_pos, ImVec2(0.0f, 0.0f)));
return;
}
const auto cur_size = GetWindowSize();
const auto bottom_right = cur_pos + cur_size;
if (bottom_right.x > display_size.x || bottom_right.y > display_size.y) {
const auto max_pos = display_size - cur_size;
SetWindowPos(ImMin(cur_pos, max_pos));
}
}
inline void KeepNavHighlight() { inline void KeepNavHighlight() {
GetCurrentContext()->NavDisableHighlight = false; GetCurrentContext()->NavDisableHighlight = false;
} }

View File

@ -1,123 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <imgui.h>
#include "common/config.h"
#include "common/types.h"
#include "imgui_internal.h"
#include "video_info.h"
using namespace ImGui;
struct FrameInfo {
u32 num;
float delta;
};
static bool show = false;
static bool show_advanced = false;
static u32 current_frame = 0;
constexpr float TARGET_FPS = 60.0f;
constexpr u32 FRAME_BUFFER_SIZE = 1024;
constexpr float BAR_WIDTH_MULT = 1.4f;
constexpr float BAR_HEIGHT_MULT = 1.25f;
constexpr float FRAME_GRAPH_PADDING_Y = 3.0f;
static std::array<FrameInfo, FRAME_BUFFER_SIZE> frame_list;
static float frame_graph_height = 50.0f;
static void DrawSimple() {
const auto io = GetIO();
Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
}
static void DrawAdvanced() {
const auto& ctx = *GetCurrentContext();
const auto& io = ctx.IO;
const auto& window = *ctx.CurrentWindow;
auto& draw_list = *window.DrawList;
Text("Frame time: %.3f ms (%.1f FPS)", io.DeltaTime * 1000.0f, io.Framerate);
SeparatorText("Frame graph");
const float full_width = GetContentRegionAvail().x;
{ // Frame graph - inspired by
// https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times
auto pos = GetCursorScreenPos();
const ImVec2 size{full_width, frame_graph_height + FRAME_GRAPH_PADDING_Y * 2.0f};
ItemSize(size);
if (!ItemAdd({pos, pos + size}, GetID("FrameGraph"))) {
return;
}
float target_dt = 1.0f / (TARGET_FPS * (float)Config::vblankDiv());
float cur_pos_x = pos.x + full_width;
pos.y += FRAME_GRAPH_PADDING_Y;
const float final_pos_y = pos.y + frame_graph_height;
draw_list.AddRectFilled({pos.x, pos.y - FRAME_GRAPH_PADDING_Y},
{pos.x + full_width, final_pos_y + FRAME_GRAPH_PADDING_Y},
IM_COL32(0x33, 0x33, 0x33, 0xFF));
draw_list.PushClipRect({pos.x, pos.y}, {pos.x + full_width, final_pos_y}, true);
for (u32 i = 0; i < FRAME_BUFFER_SIZE; ++i) {
const auto& frame_info = frame_list[(current_frame - i) % FRAME_BUFFER_SIZE];
const float dt_factor = target_dt / frame_info.delta;
const float width = std::ceil(BAR_WIDTH_MULT / dt_factor);
const float height =
std::min(std::log2(BAR_HEIGHT_MULT / dt_factor) / 3.0f, 1.0f) * frame_graph_height;
ImU32 color;
if (dt_factor >= 0.95f) { // BLUE
color = IM_COL32(0x33, 0x33, 0xFF, 0xFF);
} else if (dt_factor >= 0.5f) { // GREEN <> YELLOW
float t = 1.0f - (dt_factor - 0.5f) * 2.0f;
int r = (int)(0xFF * t);
color = IM_COL32(r, 0xFF, 0, 0xFF);
} else { // YELLOW <> RED
float t = dt_factor * 2.0f;
int g = (int)(0xFF * t);
color = IM_COL32(0xFF, g, 0, 0xFF);
}
draw_list.AddRectFilled({cur_pos_x - width, final_pos_y - height},
{cur_pos_x, final_pos_y}, color);
cur_pos_x -= width;
if (cur_pos_x < width) {
break;
}
}
draw_list.PopClipRect();
}
}
void Layers::VideoInfo::Draw() {
const auto io = GetIO();
const FrameInfo frame_info{
.num = ++current_frame,
.delta = io.DeltaTime,
};
frame_list[current_frame % FRAME_BUFFER_SIZE] = frame_info;
if (IsKeyPressed(ImGuiKey_F10, false)) {
const bool changed_ctrl = io.KeyCtrl != show_advanced;
show_advanced = io.KeyCtrl;
show = changed_ctrl || !show;
}
if (show) {
if (show_advanced) {
if (Begin("Video debug info", &show, 0)) {
DrawAdvanced();
}
} else {
if (Begin("Video Info", nullptr,
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_AlwaysAutoResize)) {
DrawSimple();
}
}
End();
}
}

View File

@ -1,22 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "imgui/imgui_layer.h"
namespace Vulkan {
class RendererVulkan;
}
namespace ImGui::Layers {
class VideoInfo : public Layer {
::Vulkan::RendererVulkan* renderer{};
public:
explicit VideoInfo(::Vulkan::RendererVulkan* renderer) : renderer(renderer) {}
void Draw() override;
};
} // namespace ImGui::Layers

View File

@ -7,6 +7,7 @@ add_executable(Dear_ImGui_FontEmbed ${CMAKE_SOURCE_DIR}/externals/dear_imgui/mis
set(FONT_LIST set(FONT_LIST
NotoSansJP-Regular.ttf NotoSansJP-Regular.ttf
ProggyVector-Regular.ttf
) )
set(OutputList "") set(OutputList "")

Binary file not shown.

View File

@ -6,6 +6,7 @@
#include "common/config.h" #include "common/config.h"
#include "common/path_util.h" #include "common/path_util.h"
#include "core/devtools/layer.h"
#include "imgui/imgui_layer.h" #include "imgui/imgui_layer.h"
#include "imgui_core.h" #include "imgui_core.h"
#include "imgui_impl_sdl3.h" #include "imgui_impl_sdl3.h"
@ -16,6 +17,7 @@
#include "video_core/renderer_vulkan/renderer_vulkan.h" #include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "imgui_fonts/notosansjp_regular.ttf.g.cpp" #include "imgui_fonts/notosansjp_regular.ttf.g.cpp"
#include "imgui_fonts/proggyvector_regular.ttf.g.cpp"
static void CheckVkResult(const vk::Result err) { static void CheckVkResult(const vk::Result err) {
LOG_ERROR(ImGui, "Vulkan error {}", vk::to_string(err)); LOG_ERROR(ImGui, "Vulkan error {}", vk::to_string(err));
@ -26,14 +28,11 @@ static std::vector<ImGui::Layer*> layers;
// Update layers before rendering to allow layer changes to be applied during rendering. // Update layers before rendering to allow layer changes to be applied during rendering.
// Using deque to keep the order of changes in case a Layer is removed then added again between // Using deque to keep the order of changes in case a Layer is removed then added again between
// frames. // frames.
std::deque<std::pair<bool, ImGui::Layer*>>& GetChangeLayers() { static std::deque<std::pair<bool, ImGui::Layer*>> change_layers{};
static std::deque<std::pair<bool, ImGui::Layer*>>* change_layers =
new std::deque<std::pair<bool, ImGui::Layer*>>;
return *change_layers;
}
static std::mutex change_layers_mutex{}; static std::mutex change_layers_mutex{};
static ImGuiID dock_id;
namespace ImGui { namespace ImGui {
namespace Core { namespace Core {
@ -51,6 +50,7 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.DisplaySize = ImVec2((float)window.getWidth(), (float)window.getHeight()); io.DisplaySize = ImVec2((float)window.getWidth(), (float)window.getHeight());
PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f); // Makes the window edges rounded
auto path = config_path.u8string(); auto path = config_path.u8string();
char* config_file_buf = new char[path.size() + 1](); char* config_file_buf = new char[path.size() + 1]();
@ -73,12 +73,16 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w
ImFontConfig font_cfg{}; ImFontConfig font_cfg{};
font_cfg.OversampleH = 2; font_cfg.OversampleH = 2;
font_cfg.OversampleV = 1; font_cfg.OversampleV = 1;
io.Fonts->AddFontFromMemoryCompressedTTF(imgui_font_notosansjp_regular_compressed_data, io.FontDefault = io.Fonts->AddFontFromMemoryCompressedTTF(
imgui_font_notosansjp_regular_compressed_size, 16.0f, imgui_font_notosansjp_regular_compressed_data,
&font_cfg, ranges.Data); imgui_font_notosansjp_regular_compressed_size, 16.0f, &font_cfg, ranges.Data);
io.Fonts->AddFontFromMemoryCompressedTTF(imgui_font_proggyvector_regular_compressed_data,
imgui_font_proggyvector_regular_compressed_size,
16.0f);
StyleColorsDark(); StyleColorsDark();
::Core::Devtools::Layer::SetupSettings();
Sdl::Init(window.GetSdlWindow()); Sdl::Init(window.GetSdlWindow());
const Vulkan::InitInfo vk_info{ const Vulkan::InitInfo vk_info{
@ -99,6 +103,14 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w
Vulkan::Init(vk_info); Vulkan::Init(vk_info);
TextureManager::StartWorker(); TextureManager::StartWorker();
char label[32];
ImFormatString(label, IM_ARRAYSIZE(label), "WindowOverViewport_%08X", GetMainViewport()->ID);
dock_id = ImHashStr(label);
if (const auto dpi = SDL_GetWindowDisplayScale(window.GetSdlWindow()); dpi > 0.0f) {
GetIO().FontGlobalScale = dpi;
}
} }
void OnResize() { void OnResize() {
@ -132,16 +144,24 @@ bool ProcessEvent(SDL_Event* event) {
// Don't block release/up events // Don't block release/up events
case SDL_EVENT_MOUSE_MOTION: case SDL_EVENT_MOUSE_MOTION:
case SDL_EVENT_MOUSE_WHEEL: case SDL_EVENT_MOUSE_WHEEL:
case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_DOWN: {
return GetIO().WantCaptureMouse; const auto& io = GetIO();
return io.WantCaptureMouse && io.Ctx->NavWindow != nullptr &&
io.Ctx->NavWindow->ID != dock_id;
}
case SDL_EVENT_TEXT_INPUT: case SDL_EVENT_TEXT_INPUT:
case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_DOWN: {
return GetIO().WantCaptureKeyboard; const auto& io = GetIO();
return io.WantCaptureKeyboard && io.Ctx->NavWindow != nullptr &&
io.Ctx->NavWindow->ID != dock_id;
}
case SDL_EVENT_GAMEPAD_BUTTON_DOWN: case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_AXIS_MOTION: case SDL_EVENT_GAMEPAD_AXIS_MOTION:
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: {
return GetIO().NavActive; const auto& io = GetIO();
return io.NavActive && io.Ctx->NavWindow != nullptr && io.Ctx->NavWindow->ID != dock_id;
}
default: default:
return false; return false;
} }
@ -150,21 +170,23 @@ bool ProcessEvent(SDL_Event* event) {
void NewFrame() { void NewFrame() {
{ {
std::scoped_lock lock{change_layers_mutex}; std::scoped_lock lock{change_layers_mutex};
while (!GetChangeLayers().empty()) { while (!change_layers.empty()) {
const auto [to_be_added, layer] = GetChangeLayers().front(); const auto [to_be_added, layer] = change_layers.front();
if (to_be_added) { if (to_be_added) {
layers.push_back(layer); layers.push_back(layer);
} else { } else {
const auto [begin, end] = std::ranges::remove(layers, layer); const auto [begin, end] = std::ranges::remove(layers, layer);
layers.erase(begin, end); layers.erase(begin, end);
} }
GetChangeLayers().pop_front(); change_layers.pop_front();
} }
} }
Sdl::NewFrame(); Sdl::NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
DockSpaceOverViewport(0, GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);
for (auto* layer : layers) { for (auto* layer : layers) {
layer->Draw(); layer->Draw();
} }
@ -191,7 +213,7 @@ void Render(const vk::CommandBuffer& cmdbuf, ::Vulkan::Frame* frame) {
.storeOp = vk::AttachmentStoreOp::eStore, .storeOp = vk::AttachmentStoreOp::eStore,
}, },
}; };
vk::RenderingInfo render_info = {}; vk::RenderingInfo render_info{};
render_info.renderArea = vk::Rect2D{ render_info.renderArea = vk::Rect2D{
.offset = {0, 0}, .offset = {0, 0},
.extent = {frame->width, frame->height}, .extent = {frame->width, frame->height},
@ -211,12 +233,12 @@ void Render(const vk::CommandBuffer& cmdbuf, ::Vulkan::Frame* frame) {
void Layer::AddLayer(Layer* layer) { void Layer::AddLayer(Layer* layer) {
std::scoped_lock lock{change_layers_mutex}; std::scoped_lock lock{change_layers_mutex};
GetChangeLayers().emplace_back(true, layer); change_layers.emplace_back(true, layer);
} }
void Layer::RemoveLayer(Layer* layer) { void Layer::RemoveLayer(Layer* layer) {
std::scoped_lock lock{change_layers_mutex}; std::scoped_lock lock{change_layers_mutex};
GetChangeLayers().emplace_back(false, layer); change_layers.emplace_back(false, layer);
} }
} // namespace ImGui } // namespace ImGui

View File

@ -277,7 +277,6 @@ vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_lay
vk::DescriptorSet descriptor_set; vk::DescriptorSet descriptor_set;
{ {
vk::DescriptorSetAllocateInfo alloc_info{ vk::DescriptorSetAllocateInfo alloc_info{
.sType = vk::StructureType::eDescriptorSetAllocateInfo,
.descriptorPool = bd->descriptor_pool, .descriptorPool = bd->descriptor_pool,
.descriptorSetCount = 1, .descriptorSetCount = 1,
.pSetLayouts = &bd->descriptor_set_layout, .pSetLayouts = &bd->descriptor_set_layout,
@ -296,7 +295,6 @@ vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_lay
}; };
vk::WriteDescriptorSet write_desc[1]{ vk::WriteDescriptorSet write_desc[1]{
{ {
.sType = vk::StructureType::eWriteDescriptorSet,
.dstSet = descriptor_set, .dstSet = descriptor_set,
.descriptorCount = 1, .descriptorCount = 1,
.descriptorType = vk::DescriptorType::eCombinedImageSampler, .descriptorType = vk::DescriptorType::eCombinedImageSampler,
@ -411,7 +409,6 @@ UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width,
{ {
vk::ImageMemoryBarrier copy_barrier[1]{ vk::ImageMemoryBarrier copy_barrier[1]{
{ {
.sType = vk::StructureType::eImageMemoryBarrier,
.dstAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask = vk::AccessFlagBits::eTransferWrite,
.oldLayout = vk::ImageLayout::eUndefined, .oldLayout = vk::ImageLayout::eUndefined,
.newLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferDstOptimal,
@ -444,7 +441,6 @@ UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width,
vk::ImageLayout::eTransferDstOptimal, {region}); vk::ImageLayout::eTransferDstOptimal, {region});
vk::ImageMemoryBarrier use_barrier[1]{{ vk::ImageMemoryBarrier use_barrier[1]{{
.sType = vk::StructureType::eImageMemoryBarrier,
.srcAccessMask = vk::AccessFlagBits::eTransferWrite, .srcAccessMask = vk::AccessFlagBits::eTransferWrite,
.dstAccessMask = vk::AccessFlagBits::eShaderRead, .dstAccessMask = vk::AccessFlagBits::eShaderRead,
.oldLayout = vk::ImageLayout::eTransferDstOptimal, .oldLayout = vk::ImageLayout::eTransferDstOptimal,
@ -488,7 +484,6 @@ static void CreateOrResizeBuffer(RenderBuffer& rb, size_t new_size, vk::BufferUs
const vk::DeviceSize buffer_size_aligned = const vk::DeviceSize buffer_size_aligned =
AlignBufferSize(IM_MAX(v.min_allocation_size, new_size), bd->buffer_memory_alignment); AlignBufferSize(IM_MAX(v.min_allocation_size, new_size), bd->buffer_memory_alignment);
vk::BufferCreateInfo buffer_info{ vk::BufferCreateInfo buffer_info{
.sType = vk::StructureType::eBufferCreateInfo,
.size = buffer_size_aligned, .size = buffer_size_aligned,
.usage = usage, .usage = usage,
.sharingMode = vk::SharingMode::eExclusive, .sharingMode = vk::SharingMode::eExclusive,
@ -498,7 +493,6 @@ static void CreateOrResizeBuffer(RenderBuffer& rb, size_t new_size, vk::BufferUs
const vk::MemoryRequirements req = v.device.getBufferMemoryRequirements(rb.buffer); const vk::MemoryRequirements req = v.device.getBufferMemoryRequirements(rb.buffer);
bd->buffer_memory_alignment = IM_MAX(bd->buffer_memory_alignment, req.alignment); bd->buffer_memory_alignment = IM_MAX(bd->buffer_memory_alignment, req.alignment);
vk::MemoryAllocateInfo alloc_info{ vk::MemoryAllocateInfo alloc_info{
.sType = vk::StructureType::eMemoryAllocateInfo,
.allocationSize = req.size, .allocationSize = req.size,
.memoryTypeIndex = .memoryTypeIndex =
FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits), FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits),
@ -608,12 +602,10 @@ void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer,
} }
vk::MappedMemoryRange range[2]{ vk::MappedMemoryRange range[2]{
{ {
.sType = vk::StructureType::eMappedMemoryRange,
.memory = frb.vertex.buffer_memory, .memory = frb.vertex.buffer_memory,
.size = VK_WHOLE_SIZE, .size = VK_WHOLE_SIZE,
}, },
{ {
.sType = vk::StructureType::eMappedMemoryRange,
.memory = frb.index.buffer_memory, .memory = frb.index.buffer_memory,
.size = VK_WHOLE_SIZE, .size = VK_WHOLE_SIZE,
}, },
@ -725,7 +717,6 @@ static bool CreateFontsTexture() {
// Create command buffer // Create command buffer
if (bd->font_command_buffer == VK_NULL_HANDLE) { if (bd->font_command_buffer == VK_NULL_HANDLE) {
vk::CommandBufferAllocateInfo info{ vk::CommandBufferAllocateInfo info{
.sType = vk::StructureType::eCommandBufferAllocateInfo,
.commandPool = bd->command_pool, .commandPool = bd->command_pool,
.commandBufferCount = 1, .commandBufferCount = 1,
}; };
@ -737,7 +728,6 @@ static bool CreateFontsTexture() {
{ {
CheckVkErr(bd->font_command_buffer.reset()); CheckVkErr(bd->font_command_buffer.reset());
vk::CommandBufferBeginInfo begin_info{}; vk::CommandBufferBeginInfo begin_info{};
begin_info.sType = vk::StructureType::eCommandBufferBeginInfo;
begin_info.flags |= vk::CommandBufferUsageFlagBits::eOneTimeSubmit; begin_info.flags |= vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
CheckVkErr(bd->font_command_buffer.begin(&begin_info)); CheckVkErr(bd->font_command_buffer.begin(&begin_info));
} }
@ -750,7 +740,6 @@ static bool CreateFontsTexture() {
// Create the Image: // Create the Image:
{ {
vk::ImageCreateInfo info{ vk::ImageCreateInfo info{
.sType = vk::StructureType::eImageCreateInfo,
.imageType = vk::ImageType::e2D, .imageType = vk::ImageType::e2D,
.format = vk::Format::eR8G8B8A8Unorm, .format = vk::Format::eR8G8B8A8Unorm,
.extent{ .extent{
@ -769,7 +758,6 @@ static bool CreateFontsTexture() {
bd->font_image = CheckVkResult(v.device.createImage(info, v.allocator)); bd->font_image = CheckVkResult(v.device.createImage(info, v.allocator));
vk::MemoryRequirements req = v.device.getImageMemoryRequirements(bd->font_image); vk::MemoryRequirements req = v.device.getImageMemoryRequirements(bd->font_image);
vk::MemoryAllocateInfo alloc_info{ vk::MemoryAllocateInfo alloc_info{
.sType = vk::StructureType::eMemoryAllocateInfo,
.allocationSize = IM_MAX(v.min_allocation_size, req.size), .allocationSize = IM_MAX(v.min_allocation_size, req.size),
.memoryTypeIndex = .memoryTypeIndex =
FindMemoryType(vk::MemoryPropertyFlagBits::eDeviceLocal, req.memoryTypeBits), FindMemoryType(vk::MemoryPropertyFlagBits::eDeviceLocal, req.memoryTypeBits),
@ -781,7 +769,6 @@ static bool CreateFontsTexture() {
// Create the Image View: // Create the Image View:
{ {
vk::ImageViewCreateInfo info{ vk::ImageViewCreateInfo info{
.sType = vk::StructureType::eImageViewCreateInfo,
.image = bd->font_image, .image = bd->font_image,
.viewType = vk::ImageViewType::e2D, .viewType = vk::ImageViewType::e2D,
.format = vk::Format::eR8G8B8A8Unorm, .format = vk::Format::eR8G8B8A8Unorm,
@ -802,7 +789,6 @@ static bool CreateFontsTexture() {
vk::Buffer upload_buffer{}; vk::Buffer upload_buffer{};
{ {
vk::BufferCreateInfo buffer_info{ vk::BufferCreateInfo buffer_info{
.sType = vk::StructureType::eBufferCreateInfo,
.size = upload_size, .size = upload_size,
.usage = vk::BufferUsageFlagBits::eTransferSrc, .usage = vk::BufferUsageFlagBits::eTransferSrc,
.sharingMode = vk::SharingMode::eExclusive, .sharingMode = vk::SharingMode::eExclusive,
@ -811,7 +797,6 @@ static bool CreateFontsTexture() {
vk::MemoryRequirements req = v.device.getBufferMemoryRequirements(upload_buffer); vk::MemoryRequirements req = v.device.getBufferMemoryRequirements(upload_buffer);
bd->buffer_memory_alignment = IM_MAX(bd->buffer_memory_alignment, req.alignment); bd->buffer_memory_alignment = IM_MAX(bd->buffer_memory_alignment, req.alignment);
vk::MemoryAllocateInfo alloc_info{ vk::MemoryAllocateInfo alloc_info{
.sType = vk::StructureType::eMemoryAllocateInfo,
.allocationSize = IM_MAX(v.min_allocation_size, req.size), .allocationSize = IM_MAX(v.min_allocation_size, req.size),
.memoryTypeIndex = .memoryTypeIndex =
FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits), FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits),
@ -826,7 +811,6 @@ static bool CreateFontsTexture() {
memcpy(map, pixels, upload_size); memcpy(map, pixels, upload_size);
vk::MappedMemoryRange range[1]{ vk::MappedMemoryRange range[1]{
{ {
.sType = vk::StructureType::eMappedMemoryRange,
.memory = upload_buffer_memory, .memory = upload_buffer_memory,
.size = upload_size, .size = upload_size,
}, },
@ -839,7 +823,6 @@ static bool CreateFontsTexture() {
{ {
vk::ImageMemoryBarrier copy_barrier[1]{ vk::ImageMemoryBarrier copy_barrier[1]{
{ {
.sType = vk::StructureType::eImageMemoryBarrier,
.dstAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask = vk::AccessFlagBits::eTransferWrite,
.oldLayout = vk::ImageLayout::eUndefined, .oldLayout = vk::ImageLayout::eUndefined,
.newLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferDstOptimal,
@ -872,7 +855,6 @@ static bool CreateFontsTexture() {
vk::ImageLayout::eTransferDstOptimal, {region}); vk::ImageLayout::eTransferDstOptimal, {region});
vk::ImageMemoryBarrier use_barrier[1]{{ vk::ImageMemoryBarrier use_barrier[1]{{
.sType = vk::StructureType::eImageMemoryBarrier,
.srcAccessMask = vk::AccessFlagBits::eTransferWrite, .srcAccessMask = vk::AccessFlagBits::eTransferWrite,
.dstAccessMask = vk::AccessFlagBits::eShaderRead, .dstAccessMask = vk::AccessFlagBits::eShaderRead,
.oldLayout = vk::ImageLayout::eTransferDstOptimal, .oldLayout = vk::ImageLayout::eTransferDstOptimal,
@ -892,11 +874,10 @@ static bool CreateFontsTexture() {
} }
// Store our identifier // Store our identifier
io.Fonts->SetTexID((ImTextureID)bd->font_descriptor_set); io.Fonts->SetTexID(bd->font_descriptor_set);
// End command buffer // End command buffer
vk::SubmitInfo end_info = {}; vk::SubmitInfo end_info = {};
end_info.sType = vk::StructureType::eSubmitInfo;
end_info.commandBufferCount = 1; end_info.commandBufferCount = 1;
end_info.pCommandBuffers = &bd->font_command_buffer; end_info.pCommandBuffers = &bd->font_command_buffer;
CheckVkErr(bd->font_command_buffer.end()); CheckVkErr(bd->font_command_buffer.end());
@ -965,7 +946,6 @@ static void CreateShaderModules(vk::Device device, const vk::AllocationCallbacks
VkData* bd = GetBackendData(); VkData* bd = GetBackendData();
if (bd->shader_module_vert == VK_NULL_HANDLE) { if (bd->shader_module_vert == VK_NULL_HANDLE) {
vk::ShaderModuleCreateInfo vert_info{ vk::ShaderModuleCreateInfo vert_info{
.sType = vk::StructureType::eShaderModuleCreateInfo,
.codeSize = sizeof(glsl_shader_vert_spv), .codeSize = sizeof(glsl_shader_vert_spv),
.pCode = (uint32_t*)glsl_shader_vert_spv, .pCode = (uint32_t*)glsl_shader_vert_spv,
}; };
@ -973,7 +953,6 @@ static void CreateShaderModules(vk::Device device, const vk::AllocationCallbacks
} }
if (bd->shader_module_frag == VK_NULL_HANDLE) { if (bd->shader_module_frag == VK_NULL_HANDLE) {
vk::ShaderModuleCreateInfo frag_info{ vk::ShaderModuleCreateInfo frag_info{
.sType = vk::StructureType::eShaderModuleCreateInfo,
.codeSize = sizeof(glsl_shader_frag_spv), .codeSize = sizeof(glsl_shader_frag_spv),
.pCode = (uint32_t*)glsl_shader_frag_spv, .pCode = (uint32_t*)glsl_shader_frag_spv,
}; };
@ -991,13 +970,11 @@ static void CreatePipeline(vk::Device device, const vk::AllocationCallbacks* all
vk::PipelineShaderStageCreateInfo stage[2]{ vk::PipelineShaderStageCreateInfo stage[2]{
{ {
.sType = vk::StructureType::ePipelineShaderStageCreateInfo,
.stage = vk::ShaderStageFlagBits::eVertex, .stage = vk::ShaderStageFlagBits::eVertex,
.module = bd->shader_module_vert, .module = bd->shader_module_vert,
.pName = "main", .pName = "main",
}, },
{ {
.sType = vk::StructureType::ePipelineShaderStageCreateInfo,
.stage = vk::ShaderStageFlagBits::eFragment, .stage = vk::ShaderStageFlagBits::eFragment,
.module = bd->shader_module_frag, .module = bd->shader_module_frag,
.pName = "main", .pName = "main",
@ -1033,7 +1010,6 @@ static void CreatePipeline(vk::Device device, const vk::AllocationCallbacks* all
}; };
vk::PipelineVertexInputStateCreateInfo vertex_info{ vk::PipelineVertexInputStateCreateInfo vertex_info{
.sType = vk::StructureType::ePipelineVertexInputStateCreateInfo,
.vertexBindingDescriptionCount = 1, .vertexBindingDescriptionCount = 1,
.pVertexBindingDescriptions = binding_desc, .pVertexBindingDescriptions = binding_desc,
.vertexAttributeDescriptionCount = 3, .vertexAttributeDescriptionCount = 3,
@ -1041,18 +1017,15 @@ static void CreatePipeline(vk::Device device, const vk::AllocationCallbacks* all
}; };
vk::PipelineInputAssemblyStateCreateInfo ia_info{ vk::PipelineInputAssemblyStateCreateInfo ia_info{
.sType = vk::StructureType::ePipelineInputAssemblyStateCreateInfo,
.topology = vk::PrimitiveTopology::eTriangleList, .topology = vk::PrimitiveTopology::eTriangleList,
}; };
vk::PipelineViewportStateCreateInfo viewport_info{ vk::PipelineViewportStateCreateInfo viewport_info{
.sType = vk::StructureType::ePipelineViewportStateCreateInfo,
.viewportCount = 1, .viewportCount = 1,
.scissorCount = 1, .scissorCount = 1,
}; };
vk::PipelineRasterizationStateCreateInfo raster_info{ vk::PipelineRasterizationStateCreateInfo raster_info{
.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo,
.polygonMode = vk::PolygonMode::eFill, .polygonMode = vk::PolygonMode::eFill,
.cullMode = vk::CullModeFlagBits::eNone, .cullMode = vk::CullModeFlagBits::eNone,
.frontFace = vk::FrontFace::eCounterClockwise, .frontFace = vk::FrontFace::eCounterClockwise,
@ -1060,7 +1033,6 @@ static void CreatePipeline(vk::Device device, const vk::AllocationCallbacks* all
}; };
vk::PipelineMultisampleStateCreateInfo ms_info{ vk::PipelineMultisampleStateCreateInfo ms_info{
.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo,
.rasterizationSamples = vk::SampleCountFlagBits::e1, .rasterizationSamples = vk::SampleCountFlagBits::e1,
}; };
@ -1078,12 +1050,9 @@ static void CreatePipeline(vk::Device device, const vk::AllocationCallbacks* all
}, },
}; };
vk::PipelineDepthStencilStateCreateInfo depth_info{ vk::PipelineDepthStencilStateCreateInfo depth_info{};
.sType = vk::StructureType::ePipelineDepthStencilStateCreateInfo,
};
vk::PipelineColorBlendStateCreateInfo blend_info{ vk::PipelineColorBlendStateCreateInfo blend_info{
.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo,
.attachmentCount = 1, .attachmentCount = 1,
.pAttachments = color_attachment, .pAttachments = color_attachment,
}; };
@ -1093,13 +1062,11 @@ static void CreatePipeline(vk::Device device, const vk::AllocationCallbacks* all
vk::DynamicState::eScissor, vk::DynamicState::eScissor,
}; };
vk::PipelineDynamicStateCreateInfo dynamic_state{ vk::PipelineDynamicStateCreateInfo dynamic_state{
.sType = vk::StructureType::ePipelineDynamicStateCreateInfo,
.dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states), .dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states),
.pDynamicStates = dynamic_states, .pDynamicStates = dynamic_states,
}; };
vk::GraphicsPipelineCreateInfo info{ vk::GraphicsPipelineCreateInfo info{
.sType = vk::StructureType::eGraphicsPipelineCreateInfo,
.pNext = &v.pipeline_rendering_create_info, .pNext = &v.pipeline_rendering_create_info,
.flags = bd->pipeline_create_flags, .flags = bd->pipeline_create_flags,
.stageCount = 2, .stageCount = 2,
@ -1143,7 +1110,6 @@ bool CreateDeviceObjects() {
}; };
vk::DescriptorPoolCreateInfo pool_info{ vk::DescriptorPoolCreateInfo pool_info{
.sType = vk::StructureType::eDescriptorPoolCreateInfo,
.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
.maxSets = 1000, .maxSets = 1000,
.poolSizeCount = std::size(pool_sizes), .poolSizeCount = std::size(pool_sizes),
@ -1162,7 +1128,6 @@ bool CreateDeviceObjects() {
}, },
}; };
vk::DescriptorSetLayoutCreateInfo info{ vk::DescriptorSetLayoutCreateInfo info{
.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo,
.bindingCount = 1, .bindingCount = 1,
.pBindings = binding, .pBindings = binding,
}; };
@ -1182,7 +1147,6 @@ bool CreateDeviceObjects() {
}; };
vk::DescriptorSetLayout set_layout[1] = {bd->descriptor_set_layout}; vk::DescriptorSetLayout set_layout[1] = {bd->descriptor_set_layout};
vk::PipelineLayoutCreateInfo layout_info{ vk::PipelineLayoutCreateInfo layout_info{
.sType = vk::StructureType::ePipelineLayoutCreateInfo,
.setLayoutCount = 1, .setLayoutCount = 1,
.pSetLayouts = set_layout, .pSetLayouts = set_layout,
.pushConstantRangeCount = 1, .pushConstantRangeCount = 1,
@ -1196,7 +1160,6 @@ bool CreateDeviceObjects() {
if (bd->command_pool == VK_NULL_HANDLE) { if (bd->command_pool == VK_NULL_HANDLE) {
vk::CommandPoolCreateInfo info{ vk::CommandPoolCreateInfo info{
.sType = vk::StructureType::eCommandPoolCreateInfo,
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
.queueFamilyIndex = v.queue_family, .queueFamilyIndex = v.queue_family,
}; };
@ -1209,7 +1172,6 @@ bool CreateDeviceObjects() {
// ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow // ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow
// point/nearest sampling. // point/nearest sampling.
vk::SamplerCreateInfo info{ vk::SamplerCreateInfo info{
.sType = vk::StructureType::eSamplerCreateInfo,
.magFilter = vk::Filter::eLinear, .magFilter = vk::Filter::eLinear,
.minFilter = vk::Filter::eLinear, .minFilter = vk::Filter::eLinear,
.mipmapMode = vk::SamplerMipmapMode::eLinear, .mipmapMode = vk::SamplerMipmapMode::eLinear,

View File

@ -456,10 +456,9 @@ void CheatsPatches::downloadCheats(const QString& source, const QString& gameSer
if (source == "GoldHEN") { if (source == "GoldHEN") {
url = "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/main/json.txt"; url = "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/main/json.txt";
} else if (source == "wolf2022") { } else if (source == "wolf2022") {
url = "https://wolf2022.ir/trainer/" + gameSerial + "_" + gameVersion + ".json"; url = "https://wolf2022.ir/trainer/list.json";
} else if (source == "shadPS4") { } else if (source == "shadPS4") {
url = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/main/" url = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/main/CHEATS_JSON.txt";
"CHEATS_JSON.txt";
} else { } else {
QMessageBox::warning(this, tr("Invalid Source"), QMessageBox::warning(this, tr("Invalid Source"),
QString(tr("The selected source is invalid.") + "\n%1").arg(source)); QString(tr("The selected source is invalid.") + "\n%1").arg(source));
@ -474,44 +473,32 @@ void CheatsPatches::downloadCheats(const QString& source, const QString& gameSer
QByteArray jsonData = reply->readAll(); QByteArray jsonData = reply->readAll();
bool foundFiles = false; bool foundFiles = false;
if (source == "GoldHEN" || source == "shadPS4") { if (source == "wolf2022") {
QString textContent(jsonData); QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
QRegularExpression regex( QJsonArray gamesArray = jsonDoc.object().value("games").toArray();
QString("%1_%2[^=]*\\.json").arg(gameSerial).arg(gameVersion));
QRegularExpressionMatchIterator matches = regex.globalMatch(textContent);
QString baseUrl;
if (source == "GoldHEN") { foreach (const QJsonValue& value, gamesArray) {
baseUrl = "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/" QJsonObject gameObject = value.toObject();
"main/json/"; QString title = gameObject.value("title").toString();
} else { QString version = gameObject.value("version").toString();
baseUrl = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/"
"main/CHEATS/";
}
while (matches.hasNext()) { if (title == gameSerial &&
QRegularExpressionMatch match = matches.next(); (version == gameVersion || version == gameVersion.mid(1))) {
QString fileName = match.captured(0); QString fileUrl =
"https://wolf2022.ir/trainer/" + gameObject.value("url").toString();
if (!fileName.isEmpty()) { QString localFileName = gameObject.value("url").toString();
QString newFileName = fileName; localFileName =
int dotIndex = newFileName.lastIndexOf('.'); localFileName.left(localFileName.lastIndexOf('.')) + "_wolf2022.json";
if (dotIndex != -1) {
if (source == "GoldHEN") { QString localFilePath = dir.filePath(localFileName);
newFileName.insert(dotIndex, "_GoldHEN");
} else {
newFileName.insert(dotIndex, "_shadPS4");
}
}
QString fileUrl = baseUrl + fileName;
QString localFilePath = dir.filePath(newFileName);
if (QFile::exists(localFilePath) && showMessageBox) { if (QFile::exists(localFilePath) && showMessageBox) {
QMessageBox::StandardButton reply; QMessageBox::StandardButton reply;
reply = QMessageBox::question( reply = QMessageBox::question(
this, tr("File Exists"), this, tr("File Exists"),
tr("File already exists. Do you want to replace it?"), tr("File already exists. Do you want to replace it?") + "\n" +
localFileName,
QMessageBox::Yes | QMessageBox::No); QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::No) { if (reply == QMessageBox::No) {
continue; continue;
@ -549,38 +536,81 @@ void CheatsPatches::downloadCheats(const QString& source, const QString& gameSer
if (!foundFiles && showMessageBox) { if (!foundFiles && showMessageBox) {
QMessageBox::warning(this, tr("Cheats Not Found"), tr("CheatsNotFound_MSG")); QMessageBox::warning(this, tr("Cheats Not Found"), tr("CheatsNotFound_MSG"));
} }
} else if (source == "wolf2022") { } else if (source == "GoldHEN" || source == "shadPS4") {
QString fileName = QFileInfo(QUrl(url).path()).fileName(); QString textContent(jsonData);
QString baseFileName = fileName; QRegularExpression regex(
int dotIndex = baseFileName.lastIndexOf('.'); QString("%1_%2[^=]*\\.json").arg(gameSerial).arg(gameVersion));
if (dotIndex != -1) { QRegularExpressionMatchIterator matches = regex.globalMatch(textContent);
baseFileName.insert(dotIndex, "_wolf2022"); QString baseUrl;
if (source == "GoldHEN") {
baseUrl = "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/"
"main/json/";
} else {
baseUrl = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/"
"main/CHEATS/";
} }
QString filePath;
Common::FS::PathToQString(filePath, while (matches.hasNext()) {
Common::FS::GetUserPath(Common::FS::PathType::CheatsDir)); QRegularExpressionMatch match = matches.next();
filePath += "/" + baseFileName; QString fileName = match.captured(0);
if (QFile::exists(filePath) && showMessageBox) {
QMessageBox::StandardButton reply2; if (!fileName.isEmpty()) {
reply2 = QString newFileName = fileName;
QMessageBox::question(this, tr("File Exists"), int dotIndex = newFileName.lastIndexOf('.');
tr("File already exists. Do you want to replace it?"), if (dotIndex != -1) {
QMessageBox::Yes | QMessageBox::No);
if (reply2 == QMessageBox::No) { if (source == "GoldHEN") {
reply->deleteLater(); newFileName.insert(dotIndex, "_GoldHEN");
return; } else {
newFileName.insert(dotIndex, "_shadPS4");
}
}
QString fileUrl = baseUrl + fileName;
QString localFilePath = dir.filePath(newFileName);
if (QFile::exists(localFilePath) && showMessageBox) {
QMessageBox::StandardButton reply;
reply = QMessageBox::question(
this, tr("File Exists"),
tr("File already exists. Do you want to replace it?") + "\n" +
newFileName,
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::No) {
continue;
}
}
QNetworkRequest fileRequest(fileUrl);
QNetworkReply* fileReply = manager->get(fileRequest);
connect(fileReply, &QNetworkReply::finished, [=, this]() {
if (fileReply->error() == QNetworkReply::NoError) {
QByteArray fileData = fileReply->readAll();
QFile localFile(localFilePath);
if (localFile.open(QIODevice::WriteOnly)) {
localFile.write(fileData);
localFile.close();
} else {
QMessageBox::warning(
this, tr("Error"),
QString(tr("Failed to save file:") + "\n%1")
.arg(localFilePath));
}
} else {
QMessageBox::warning(this, tr("Error"),
QString(tr("Failed to download file:") +
"%1\n\n" + tr("Error:") + "%2")
.arg(fileUrl)
.arg(fileReply->errorString()));
}
fileReply->deleteLater();
});
foundFiles = true;
} }
} }
QFile cheatFile(filePath); if (!foundFiles && showMessageBox) {
if (cheatFile.open(QIODevice::WriteOnly)) { QMessageBox::warning(this, tr("Cheats Not Found"), tr("CheatsNotFound_MSG"));
cheatFile.write(jsonData);
cheatFile.close();
foundFiles = true;
populateFileListCheats();
} else {
QMessageBox::warning(
this, tr("Error"),
QString(tr("Failed to save file:") + "\n%1").arg(filePath));
} }
} }
if (foundFiles && showMessageBox) { if (foundFiles && showMessageBox) {
@ -910,11 +940,16 @@ void CheatsPatches::addCheatsToLayout(const QJsonArray& modsArray, const QJsonAr
void CheatsPatches::populateFileListCheats() { void CheatsPatches::populateFileListCheats() {
QString cheatsDir; QString cheatsDir;
Common::FS::PathToQString(cheatsDir, Common::FS::GetUserPath(Common::FS::PathType::CheatsDir)); Common::FS::PathToQString(cheatsDir, Common::FS::GetUserPath(Common::FS::PathType::CheatsDir));
QString pattern = m_gameSerial + "_" + m_gameVersion + "*.json";
QString fullGameVersion = m_gameVersion;
QString modifiedGameVersion = m_gameVersion.mid(1);
QString patternWithFirstChar = m_gameSerial + "_" + fullGameVersion + "*.json";
QString patternWithoutFirstChar = m_gameSerial + "_" + modifiedGameVersion + "*.json";
QDir dir(cheatsDir); QDir dir(cheatsDir);
QStringList filters; QStringList filters;
filters << pattern; filters << patternWithFirstChar << patternWithoutFirstChar;
dir.setNameFilters(filters); dir.setNameFilters(filters);
QFileInfoList fileList = dir.entryInfoList(QDir::Files); QFileInfoList fileList = dir.entryInfoList(QDir::Files);

View File

@ -343,8 +343,8 @@ void CheckUpdate::DownloadUpdate(const QString& url) {
return; return;
} }
QString userPath = QString userPath;
QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::UserDir).string()); Common::FS::PathToQString(userPath, Common::FS::GetUserPath(Common::FS::PathType::UserDir));
QString tempDownloadPath = userPath + "/temp_download_update"; QString tempDownloadPath = userPath + "/temp_download_update";
QDir dir(tempDownloadPath); QDir dir(tempDownloadPath);
if (!dir.exists()) { if (!dir.exists()) {
@ -371,12 +371,13 @@ void CheckUpdate::DownloadUpdate(const QString& url) {
} }
void CheckUpdate::Install() { void CheckUpdate::Install() {
QString userPath = QString userPath;
QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::UserDir).string()); Common::FS::PathToQString(userPath, Common::FS::GetUserPath(Common::FS::PathType::UserDir));
QString startingUpdate = tr("Starting Update..."); QString startingUpdate = tr("Starting Update...");
QString tempDirPath = userPath + "/temp_download_update"; QString tempDirPath = userPath + "/temp_download_update";
QString rootPath = QString::fromStdString(std::filesystem::current_path().string()); QString rootPath;
Common::FS::PathToQString(rootPath, std::filesystem::current_path());
QString scriptContent; QString scriptContent;
QString scriptFileName; QString scriptFileName;

View File

@ -53,7 +53,7 @@ void GameGridFrame::onCurrentCellChanged(int currentRow, int currentColumn, int
} }
void GameGridFrame::PlayBackgroundMusic(QString path) { void GameGridFrame::PlayBackgroundMusic(QString path) {
if (path.isEmpty()) { if (path.isEmpty() || !Config::getPlayBGM()) {
BackgroundMusicPlayer::getInstance().stopMusic(); BackgroundMusicPlayer::getInstance().stopMusic();
return; return;
} }
@ -102,7 +102,9 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from
name_label->setGraphicsEffect(shadowEffect); name_label->setGraphicsEffect(shadowEffect);
widget->setLayout(layout); widget->setLayout(layout);
QString tooltipText = QString::fromStdString(m_games_[gameCounter].name); QString tooltipText = QString::fromStdString(m_games_[gameCounter].name + " (" +
m_games_[gameCounter].version + ", " +
m_games_[gameCounter].region + ")");
widget->setToolTip(tooltipText); widget->setToolTip(tooltipText);
QString tooltipStyle = QString("QToolTip {" QString tooltipStyle = QString("QToolTip {"
"background-color: #ffffff;" "background-color: #ffffff;"

View File

@ -18,6 +18,7 @@ GameInstallDialog::GameInstallDialog() : m_gamesDirectory(nullptr) {
auto layout = new QVBoxLayout(this); auto layout = new QVBoxLayout(this);
layout->addWidget(SetupGamesDirectory()); layout->addWidget(SetupGamesDirectory());
layout->addWidget(SetupAddonsDirectory());
layout->addStretch(); layout->addStretch();
layout->addWidget(SetupDialogActions()); layout->addWidget(SetupDialogActions());
@ -27,7 +28,7 @@ GameInstallDialog::GameInstallDialog() : m_gamesDirectory(nullptr) {
GameInstallDialog::~GameInstallDialog() {} GameInstallDialog::~GameInstallDialog() {}
void GameInstallDialog::Browse() { void GameInstallDialog::BrowseGamesDirectory() {
auto path = QFileDialog::getExistingDirectory(this, tr("Directory to install games")); auto path = QFileDialog::getExistingDirectory(this, tr("Directory to install games"));
if (!path.isEmpty()) { if (!path.isEmpty()) {
@ -35,6 +36,14 @@ void GameInstallDialog::Browse() {
} }
} }
void GameInstallDialog::BrowseAddonsDirectory() {
auto path = QFileDialog::getExistingDirectory(this, tr("Directory to install DLC"));
if (!path.isEmpty()) {
m_addonsDirectory->setText(QDir::toNativeSeparators(path));
}
}
QWidget* GameInstallDialog::SetupGamesDirectory() { QWidget* GameInstallDialog::SetupGamesDirectory() {
auto group = new QGroupBox(tr("Directory to install games")); auto group = new QGroupBox(tr("Directory to install games"));
auto layout = new QHBoxLayout(group); auto layout = new QHBoxLayout(group);
@ -51,7 +60,30 @@ QWidget* GameInstallDialog::SetupGamesDirectory() {
// Browse button. // Browse button.
auto browse = new QPushButton(tr("Browse")); auto browse = new QPushButton(tr("Browse"));
connect(browse, &QPushButton::clicked, this, &GameInstallDialog::Browse); connect(browse, &QPushButton::clicked, this, &GameInstallDialog::BrowseGamesDirectory);
layout->addWidget(browse);
return group;
}
QWidget* GameInstallDialog::SetupAddonsDirectory() {
auto group = new QGroupBox(tr("Directory to install DLC"));
auto layout = new QHBoxLayout(group);
// Input.
m_addonsDirectory = new QLineEdit();
QString install_dir;
Common::FS::PathToQString(install_dir, Config::getAddonInstallDir());
m_addonsDirectory->setText(install_dir);
m_addonsDirectory->setMinimumWidth(400);
layout->addWidget(m_addonsDirectory);
// Browse button.
auto browse = new QPushButton(tr("Browse"));
connect(browse, &QPushButton::clicked, this, &GameInstallDialog::BrowseAddonsDirectory);
layout->addWidget(browse); layout->addWidget(browse);
@ -70,6 +102,7 @@ QWidget* GameInstallDialog::SetupDialogActions() {
void GameInstallDialog::Save() { void GameInstallDialog::Save() {
// Check games directory. // Check games directory.
auto gamesDirectory = m_gamesDirectory->text(); auto gamesDirectory = m_gamesDirectory->text();
auto addonsDirectory = m_addonsDirectory->text();
if (gamesDirectory.isEmpty() || !QDir(gamesDirectory).exists() || if (gamesDirectory.isEmpty() || !QDir(gamesDirectory).exists() ||
!QDir::isAbsolutePath(gamesDirectory)) { !QDir::isAbsolutePath(gamesDirectory)) {
@ -78,7 +111,22 @@ void GameInstallDialog::Save() {
return; return;
} }
if (addonsDirectory.isEmpty() || !QDir::isAbsolutePath(addonsDirectory)) {
QMessageBox::critical(this, tr("Error"),
"The value for location to install DLC is not valid.");
return;
}
QDir addonsDir(addonsDirectory);
if (!addonsDir.exists()) {
if (!addonsDir.mkpath(".")) {
QMessageBox::critical(this, tr("Error"),
"The DLC install location could not be created.");
return;
}
}
Config::setGameInstallDir(Common::FS::PathFromQString(gamesDirectory)); Config::setGameInstallDir(Common::FS::PathFromQString(gamesDirectory));
Config::setAddonInstallDir(Common::FS::PathFromQString(addonsDirectory));
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::save(config_dir / "config.toml"); Config::save(config_dir / "config.toml");
accept(); accept();

View File

@ -16,13 +16,16 @@ public:
~GameInstallDialog(); ~GameInstallDialog();
private slots: private slots:
void Browse(); void BrowseGamesDirectory();
void BrowseAddonsDirectory();
private: private:
QWidget* SetupGamesDirectory(); QWidget* SetupGamesDirectory();
QWidget* SetupAddonsDirectory();
QWidget* SetupDialogActions(); QWidget* SetupDialogActions();
void Save(); void Save();
private: private:
QLineEdit* m_gamesDirectory; QLineEdit* m_gamesDirectory;
QLineEdit* m_addonsDirectory;
}; };

View File

@ -80,7 +80,7 @@ void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int
} }
void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) { void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) {
if (!item) { if (!item || !Config::getPlayBGM()) {
BackgroundMusicPlayer::getInstance().stopMusic(); BackgroundMusicPlayer::getInstance().stopMusic();
return; return;
} }

View File

@ -44,25 +44,54 @@ public:
int icon_size; int icon_size;
static float parseAsFloat(const std::string& str, const int& offset) {
return std::stof(str.substr(0, str.size() - offset));
}
static float parseSizeMB(const std::string& size) {
float num = parseAsFloat(size, 3);
return (size[size.size() - 2] == 'G') ? num * 1024 : num;
}
static bool CompareStringsAscending(GameInfo a, GameInfo b, int columnIndex) { static bool CompareStringsAscending(GameInfo a, GameInfo b, int columnIndex) {
if (columnIndex == 1) { switch (columnIndex) {
case 1:
return a.name < b.name; return a.name < b.name;
} else if (columnIndex == 2) { case 2:
return a.serial < b.serial; return a.serial.substr(4) < b.serial.substr(4);
} else if (columnIndex == 3) { case 3:
return a.fw < b.fw; return a.region < b.region;
case 4:
return parseAsFloat(a.fw, 0) < parseAsFloat(b.fw, 0);
case 5:
return parseSizeMB(b.size) < parseSizeMB(a.size);
case 6:
return a.version < b.version;
case 7:
return a.path < b.path;
default:
return false;
} }
return false;
} }
static bool CompareStringsDescending(GameInfo a, GameInfo b, int columnIndex) { static bool CompareStringsDescending(GameInfo a, GameInfo b, int columnIndex) {
if (columnIndex == 1) { switch (columnIndex) {
case 1:
return a.name > b.name; return a.name > b.name;
} else if (columnIndex == 2) { case 2:
return a.serial > b.serial; return a.serial.substr(4) > b.serial.substr(4);
} else if (columnIndex == 3) { case 3:
return a.fw > b.fw; return a.region > b.region;
case 4:
return parseAsFloat(a.fw, 0) > parseAsFloat(b.fw, 0);
case 5:
return parseSizeMB(b.size) > parseSizeMB(a.size);
case 6:
return a.version > b.version;
case 7:
return a.path > b.path;
default:
return false;
} }
return false;
} }
}; };

View File

@ -695,8 +695,8 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
} }
std::string entitlement_label = Common::SplitString(content_id, '-')[2]; std::string entitlement_label = Common::SplitString(content_id, '-')[2];
auto addon_extract_path = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) / auto addon_extract_path =
pkg.GetTitleID() / entitlement_label; Config::getAddonInstallDir() / pkg.GetTitleID() / entitlement_label;
QString addonDirPath; QString addonDirPath;
Common::FS::PathToQString(addonDirPath, addon_extract_path); Common::FS::PathToQString(addonDirPath, addon_extract_path);
QDir addon_dir(addonDirPath); QDir addon_dir(addonDirPath);
@ -776,7 +776,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
} }
} }
} else { } else {
msgBox.setText(QString(tr("Game already installed") + "\n" + addonDirPath + "\n" + msgBox.setText(QString(tr("Game already installed") + "\n" + gameDirPath + "\n" +
tr("Would you like to overwrite?"))); tr("Would you like to overwrite?")));
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No);

View File

@ -185,9 +185,6 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
connect(ui->nullGpuCheckBox, &QCheckBox::stateChanged, this, connect(ui->nullGpuCheckBox, &QCheckBox::stateChanged, this,
[](int val) { Config::setNullGpu(val); }); [](int val) { Config::setNullGpu(val); });
connect(ui->dumpPM4CheckBox, &QCheckBox::stateChanged, this,
[](int val) { Config::setDumpPM4(val); });
} }
// DEBUG TAB // DEBUG TAB
@ -226,7 +223,6 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
ui->heightDivider->installEventFilter(this); ui->heightDivider->installEventFilter(this);
ui->dumpShadersCheckBox->installEventFilter(this); ui->dumpShadersCheckBox->installEventFilter(this);
ui->nullGpuCheckBox->installEventFilter(this); ui->nullGpuCheckBox->installEventFilter(this);
ui->dumpPM4CheckBox->installEventFilter(this);
// Debug // Debug
ui->debugDump->installEventFilter(this); ui->debugDump->installEventFilter(this);
@ -249,7 +245,6 @@ void SettingsDialog::LoadValuesFromConfig() {
ui->vblankSpinBox->setValue(Config::vblankDiv()); ui->vblankSpinBox->setValue(Config::vblankDiv());
ui->dumpShadersCheckBox->setChecked(Config::dumpShaders()); ui->dumpShadersCheckBox->setChecked(Config::dumpShaders());
ui->nullGpuCheckBox->setChecked(Config::nullGpu()); ui->nullGpuCheckBox->setChecked(Config::nullGpu());
ui->dumpPM4CheckBox->setChecked(Config::dumpPM4());
ui->playBGMCheckBox->setChecked(Config::getPlayBGM()); ui->playBGMCheckBox->setChecked(Config::getPlayBGM());
ui->BGMVolumeSlider->setValue((Config::getBGMvolume())); ui->BGMVolumeSlider->setValue((Config::getBGMvolume()));
ui->discordRPCCheckbox->setChecked(Config::getEnableDiscordRPC()); ui->discordRPCCheckbox->setChecked(Config::getEnableDiscordRPC());
@ -369,7 +364,7 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) {
ui->descriptionText->setText(text.replace("\\n", "\n")); ui->descriptionText->setText(text.replace("\\n", "\n"));
} }
bool SettingsDialog::eventFilter(QObject* obj, QEvent* event) { bool SettingsDialog::override(QObject* obj, QEvent* event) {
if (event->type() == QEvent::Enter || event->type() == QEvent::Leave) { if (event->type() == QEvent::Enter || event->type() == QEvent::Leave) {
if (qobject_cast<QWidget*>(obj)) { if (qobject_cast<QWidget*>(obj)) {
bool hovered = (event->type() == QEvent::Enter); bool hovered = (event->type() == QEvent::Enter);

View File

@ -21,7 +21,7 @@ public:
explicit SettingsDialog(std::span<const QString> physical_devices, QWidget* parent = nullptr); explicit SettingsDialog(std::span<const QString> physical_devices, QWidget* parent = nullptr);
~SettingsDialog(); ~SettingsDialog();
bool eventFilter(QObject* obj, QEvent* event); bool override(QObject* obj, QEvent* event);
void updateNoteTextEdit(const QString& groupName); void updateNoteTextEdit(const QString& groupName);
int exec() override; int exec() override;

View File

@ -671,13 +671,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="dumpPM4CheckBox">
<property name="text">
<string>Enable PM4 Dumping</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="ar_AR"> <TS version="2.1" language="ar_AR">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="da_DK"> <TS version="2.1" language="da_DK">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="de"> <TS version="2.1" language="de">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="el"> <TS version="2.1" language="el">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="en_US"> <TS version="2.1" language="en_US">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="es_ES"> <TS version="2.1" language="es_ES">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="fi"> <TS version="2.1" language="fi">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="fr"> <TS version="2.1" language="fr">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="hu_HU"> <TS version="2.1" language="hu_HU">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="id"> <TS version="2.1" language="id">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="it"> <TS version="2.1" language="it">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="ko_KR"> <TS version="2.1" language="ko_KR">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="it_LT"> <TS version="2.1" language="it_LT">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="nb"> <TS version="2.1" language="nb">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="nl"> <TS version="2.1" language="nl">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="pl_PL"> <TS version="2.1" language="pl_PL">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="pt_BR"> <TS version="2.1" language="pt_BR">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="ro_RO"> <TS version="2.1" language="ro_RO">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="ru_RU"> <TS version="2.1" language="ru_RU">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project <!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project

Some files were not shown because too many files have changed in this diff Show More