Merge branch 'shadps4-emu:main' into sceSystemServiceReceiveEvent

This commit is contained in:
Stephen Miller 2024-10-04 07:45:37 -05:00 committed by GitHub
commit 675a3fb55d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
156 changed files with 21391 additions and 1230 deletions

View file

@ -19,12 +19,13 @@ chmod a+x linuxdeploy-x86_64.AppImage
chmod a+x linuxdeploy-plugin-qt-x86_64.AppImage
chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh
# Build AppImage
./linuxdeploy-x86_64.AppImage --appdir AppDir
./linuxdeploy-plugin-checkrt-x86_64.sh --appdir AppDir
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 --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

View file

@ -9,6 +9,10 @@ on:
pull_request:
branches: [ "*" ]
concurrency:
group: ci-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'push' }}
env:
BUILD_TYPE: Release
@ -287,7 +291,7 @@ jobs:
submodules: recursive
- 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
uses: actions/cache@v4
@ -343,7 +347,7 @@ jobs:
submodules: recursive
- 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
uses: actions/cache@v4

View file

@ -8,7 +8,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED True)
if(APPLE)
enable_language(OBJC)
set(CMAKE_OSX_DEPLOYMENT_TARGET 11)
set(CMAKE_OSX_DEPLOYMENT_TARGET 14)
endif()
if (NOT CMAKE_BUILD_TYPE)
@ -338,6 +338,19 @@ set(MISC_LIBS src/core/libraries/screenshot/screenshot.cpp
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
src/common/logging/backend.h
src/common/logging/filter.cpp
@ -449,6 +462,9 @@ set(CORE src/core/aerolib/stubs.cpp
${USBD_LIB}
${MISC_LIBS}
${DIALOGS_LIB}
${DEV_TOOLS}
src/core/debug_state.cpp
src/core/debug_state.h
src/core/linker.cpp
src/core/linker.h
src/core/memory.cpp
@ -623,8 +639,6 @@ set(IMGUI src/imgui/imgui_config.h
src/imgui/imgui_layer.h
src/imgui/imgui_std.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.h
src/imgui/renderer/imgui_impl_sdl3.cpp
@ -723,7 +737,7 @@ endif()
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_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h")

View file

@ -34,7 +34,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
# General information
shadPS4 is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++.
**shadPS4** is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++.
If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/Quickstart.md).
@ -44,7 +44,7 @@ To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Di
To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/).
For those who'd like to donate to the project, we now have a [Kofi page!](https://ko-fi.com/shadps4)
For those who'd like to donate to the project, we now have a [**Kofi page**](https://ko-fi.com/shadps4)!
# Status
@ -72,40 +72,11 @@ 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).
> [!IMPORTANT]
> macOS users need at least macOS 15 on Apple Silicon-based Mac devices and at least macOS 11 on Intel-based Mac devices.
## Building status
<details>
<summary><b>Windows</b></summary>
| Windows | Build status |
|--------|--------|
|Windows SDL Build|[![Windows-sdl](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows.yml)
|Windows Qt Build|[![Windows-qt](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows-qt.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows-qt.yml)
</details>
<details>
<summary><b>Linux</b></summary>
| Linux | Build status |
|--------|--------|
|Linux SDL Build|[![Linux-sdl](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux.yml)
|Linux Qt Build|[![Linux-qt](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux-qt.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux-qt.yml)
</details>
<details>
<summary><b>macOS</b></summary>
| macOS | Build status |
|--------|--------|
|macOS SDL Build|[![macOS-sdl](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos.yml)
|macOS Qt Build|[![macOS-qt](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos-qt.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos-qt.yml)
</details>
> 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
For more information on how to test, debug and report issues with the emulator or games, read the [Debugging documentation](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md).
For more information on how to test, debug and report issues with the emulator or games, read the [**Debugging documentation**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md).
# Keyboard mapping

View file

@ -3,16 +3,16 @@ version = 1
[[annotations]]
path = [
"REUSE.toml",
"CMakeSettings.json",
".github/FUNDING.yml",
".github/shadps4.desktop",
".github/shadps4.png",
".gitmodules",
"documents/changelog.txt",
"documents/Quickstart/2.png",
"documents/Screenshots/*",
"scripts/ps4_names.txt",
"src/images/about_icon.png",
"CMakeSettings.json",
".github/FUNDING.yml",
".github/shadps4.desktop",
".github/shadps4.png",
".gitmodules",
"documents/changelog.txt",
"documents/Quickstart/2.png",
"documents/Screenshots/*",
"scripts/ps4_names.txt",
"src/images/about_icon.png",
"src/images/controller_icon.png",
"src/images/dump_icon.png",
"src/images/exit_icon.png",
@ -26,18 +26,18 @@ path = [
"src/images/folder_icon.png",
"src/images/grid_icon.png",
"src/images/iconsize_icon.png",
"src/images/list_icon.png",
"src/images/list_mode_icon.png",
"src/images/pause_icon.png",
"src/images/play_icon.png",
"src/images/refresh_icon.png",
"src/images/settings_icon.png",
"src/images/stop_icon.png",
"src/images/shadPS4.icns",
"src/images/shadps4.ico",
"src/images/themes_icon.png",
"src/images/update_icon.png",
"src/shadps4.qrc",
"src/images/list_icon.png",
"src/images/list_mode_icon.png",
"src/images/pause_icon.png",
"src/images/play_icon.png",
"src/images/refresh_icon.png",
"src/images/settings_icon.png",
"src/images/stop_icon.png",
"src/images/shadPS4.icns",
"src/images/shadps4.ico",
"src/images/themes_icon.png",
"src/images/update_icon.png",
"src/shadps4.qrc",
"src/shadps4.rc",
]
precedence = "aggregate"
@ -73,3 +73,15 @@ path = "src/imgui/renderer/fonts/NotoSansJP-Regular.ttf"
precedence = "aggregate"
SPDX-FileCopyrightText = "2012 Google Inc. All Rights Reserved."
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

@ -3,28 +3,28 @@ SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
## Build shadPS4 for Linux
## Build shadPS4 for Linux
### Install the necessary tools to build shadPS4:
#### Debian & Ubuntu
```
sudo apt-get install build-essential libasound2-dev libpulse-dev libopenal-dev zlib1g-dev libedit-dev libvulkan-dev libudev-dev git libevdev-dev libsdl2-2.0 libsdl2-dev libjack-dev libsndio-dev qt6-base-dev qt6-tools-dev
sudo apt install build-essential clang git cmake libasound2-dev libpulse-dev libopenal-dev libssl-dev zlib1g-dev libedit-dev libudev-dev libevdev-dev libsdl2-dev libjack-dev libsndio-dev qt6-base-dev qt6-tools-dev qt6-multimedia-dev libvulkan-dev vulkan-validationlayers
```
#### Fedora
```
sudo dnf install alsa-lib-devel cmake libatomic libevdev-devel libudev-devel openal-devel qt6-qtbase-devel qt6-qtbase-private-devel vulkan-devel pipewire-jack-audio-connection-kit-devel qt6-qtmultimedia-devel qt6-qtsvg-devel
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
```
sudo pacman -S openal cmake vulkan-validation-layers qt6-base qt6-declarative qt6-multimedia sdl2 sndio jack2 base-devel
sudo pacman -S base-devel clang git cmake sndio jack2 openal qt6-base qt6-declarative qt6-multimedia sdl2 vulkan-validation-layers
```
#### OpenSUSE
```
sudo zypper install git cmake libasound2 libpulse-devel openal-soft-devel zlib-devel libedit-devel vulkan-devel libudev-devel libqt6-qtbase-devel libqt6-qtmultimedia-devel libqt6-qtsvg-devel libQt6Gui-private-headers-devel libevdev-devel libsndio7_1 libjack-devel
sudo zypper install clang git cmake libasound2 libpulse-devel libsndio7 libjack-devel openal-soft-devel libopenssl-devel zlib-devel libedit-devel systemd-devel libevdev-devel qt6-base-devel qt6-multimedia-devel qt6-svg-devel qt6-linguist-devel qt6-gui-private-devel vulkan-devel vulkan-validationlayers
```
### Cloning and compiling:
@ -34,9 +34,11 @@ git clone --recursive https://github.com/shadps4-emu/shadPS4.git
cd shadPS4
```
Generate the build directory in the shadPS4 directory. To enable the QT GUI, pass the ```-DENABLE_QT_GUI=ON``` flag:
Generate the build directory in the shadPS4 directory. To disable the QT GUI, remove the ```-DENABLE_QT_GUI=ON``` flag:
**Note**: Clang is the compiler used for official builds and CI. If you build with GCC, you might encounter issues—please report any you find. If you choose to use GCC, we recommend building with Clang at least once before submitting a pull request.
```
cmake -S . -B build/ -DENABLE_QT_GUI=ON
cmake -S . -B build/ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
```
Enter the directory:

View file

@ -182,4 +182,7 @@ add_subdirectory(tracy)
# pugixml
if (NOT TARGET pugixml::pugixml)
add_subdirectory(pugixml)
endif()
endif()
# 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 {
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) {
using Libraries::AudioOut::OrbisAudioOutParamFormat;
std::unique_lock lock{m_mutex};
@ -80,7 +82,7 @@ int SDLAudio::AudioOutOpen(int type, u32 samples_num, u32 freq,
SDL_zero(fmt);
fmt.format = sampleFormat;
fmt.channels = port.channels_num;
fmt.freq = 48000;
fmt.freq = freq; // Set frequency from the argument
port.stream =
SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, NULL, NULL);
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) {
@ -97,27 +100,28 @@ s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) {
if (!port.isOpen) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
if (ptr == nullptr) {
return 0;
}
// TODO mixing channels
int result = SDL_PutAudioStreamData(port.stream, ptr,
port.samples_num * port.sample_size * port.channels_num);
// TODO find a correct value 8192 is estimated
while (SDL_GetAudioStreamAvailable(port.stream) > 65536) {
const size_t data_size = port.samples_num * port.sample_size * port.channels_num;
SDL_bool result = SDL_PutAudioStreamData(port.stream, ptr, data_size);
lock.unlock(); // Unlock only after necessary operations
while (SDL_GetAudioStreamAvailable(port.stream) > AUDIO_STREAM_BUFFER_THRESHOLD) {
SDL_Delay(0);
}
return result;
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;
std::shared_lock lock{m_mutex};
auto& port = portsOut[handle - 1];
if (!port.isOpen) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
for (int i = 0; i < port.channels_num; i++, bitflag >>= 1u) {
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};
auto& port = portsOut[handle - 1];
*type = port.type;
*channels_num = port.channels_num;
return true;
return ORBIS_OK;
}
} // namespace Audio

View file

@ -14,11 +14,11 @@ public:
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);
s32 AudioOutOutput(s32 handle, const void* ptr);
bool AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume);
bool AudioOutGetStatus(s32 handle, int* type, int* channels_num);
s32 AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume);
s32 AudioOutGetStatus(s32 handle, int* type, int* channels_num);
private:
struct PortOut {
@ -33,8 +33,7 @@ private:
bool isOpen = false;
};
std::shared_mutex m_mutex;
std::array<PortOut, 22> portsOut; // 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
std::array<PortOut, Libraries::AudioOut::SCE_AUDIO_OUT_NUM_PORTS> portsOut;
};
} // namespace Audio

View file

@ -28,4 +28,16 @@ template <typename T>
return (value & 0x3FFF) == 0;
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr bool Is64KBAligned(T value) {
return (value & 0xFFFF) == 0;
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr bool Is2MBAligned(T value) {
return (value & 0x1FFFFF) == 0;
}
} // namespace Common

View file

@ -8,6 +8,7 @@
#include <fmt/xchar.h> // for wstring support
#include <toml.hpp>
#include "common/logging/formatter.h"
#include "common/path_util.h"
#include "config.h"
namespace toml {
@ -32,6 +33,7 @@ namespace Config {
static bool isNeo = false;
static bool isFullscreen = false;
static bool playBGM = false;
static int BGMvolume = 50;
static u32 screenWidth = 1280;
static u32 screenHeight = 720;
static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select
@ -47,7 +49,6 @@ static bool isAutoUpdate = false;
static bool isNullGpu = false;
static bool shouldCopyGPUBuffers = false;
static bool shouldDumpShaders = false;
static bool shouldDumpPM4 = false;
static u32 vblankDivider = 1;
static bool vkValidation = false;
static bool vkValidationSync = false;
@ -58,6 +59,7 @@ static bool vkCrashDiagnostic = false;
// Gui
std::filesystem::path settings_install_dir = {};
std::filesystem::path settings_addon_install_dir = {};
u32 main_window_geometry_x = 400;
u32 main_window_geometry_y = 400;
u32 main_window_geometry_w = 1280;
@ -89,6 +91,10 @@ bool getPlayBGM() {
return playBGM;
}
int getBGMvolume() {
return BGMvolume;
}
u32 getScreenWidth() {
return screenWidth;
}
@ -149,10 +155,6 @@ bool dumpShaders() {
return shouldDumpShaders;
}
bool dumpPM4() {
return shouldDumpPM4;
}
bool isRdocEnabled() {
return rdocEnable;
}
@ -221,10 +223,6 @@ void setDumpShaders(bool enable) {
shouldDumpShaders = enable;
}
void setDumpPM4(bool enable) {
shouldDumpPM4 = enable;
}
void setVkValidation(bool enable) {
vkValidation = enable;
}
@ -249,6 +247,10 @@ void setPlayBGM(bool enable) {
playBGM = enable;
}
void setBGMvolume(int volume) {
BGMvolume = volume;
}
void setLanguage(u32 language) {
m_language = language;
}
@ -290,6 +292,9 @@ void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) {
void setGameInstallDir(const std::filesystem::path& dir) {
settings_install_dir = dir;
}
void setAddonInstallDir(const std::filesystem::path& dir) {
settings_addon_install_dir = dir;
}
void setMainWindowTheme(u32 theme) {
mw_themes = theme;
}
@ -346,6 +351,13 @@ u32 getMainWindowGeometryH() {
std::filesystem::path getGameInstallDir() {
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() {
return mw_themes;
}
@ -412,6 +424,7 @@ void load(const std::filesystem::path& path) {
isNeo = toml::find_or<bool>(general, "isPS4Pro", false);
isFullscreen = toml::find_or<bool>(general, "Fullscreen", false);
playBGM = toml::find_or<bool>(general, "playBGM", false);
BGMvolume = toml::find_or<int>(general, "BGMvolume", 50);
logFilter = toml::find_or<std::string>(general, "logFilter", "");
logType = toml::find_or<std::string>(general, "logType", "sync");
userName = toml::find_or<std::string>(general, "userName", "shadPS4");
@ -439,7 +452,6 @@ void load(const std::filesystem::path& path) {
isNullGpu = toml::find_or<bool>(gpu, "nullGpu", false);
shouldCopyGPUBuffers = toml::find_or<bool>(gpu, "copyGPUBuffers", 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);
}
@ -472,6 +484,7 @@ void load(const std::filesystem::path& path) {
m_window_size_W = toml::find_or<int>(gui, "mw_width", 0);
m_window_size_H = toml::find_or<int>(gui, "mw_height", 0);
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_y = toml::find_or<int>(gui, "geometry_y", 0);
main_window_geometry_w = toml::find_or<int>(gui, "geometry_w", 0);
@ -513,6 +526,7 @@ void save(const std::filesystem::path& path) {
data["General"]["isPS4Pro"] = isNeo;
data["General"]["Fullscreen"] = isFullscreen;
data["General"]["playBGM"] = playBGM;
data["General"]["BGMvolume"] = BGMvolume;
data["General"]["logFilter"] = logFilter;
data["General"]["logType"] = logType;
data["General"]["userName"] = userName;
@ -526,7 +540,6 @@ void save(const std::filesystem::path& path) {
data["GPU"]["nullGpu"] = isNullGpu;
data["GPU"]["copyGPUBuffers"] = shouldCopyGPUBuffers;
data["GPU"]["dumpShaders"] = shouldDumpShaders;
data["GPU"]["dumpPM4"] = shouldDumpPM4;
data["GPU"]["vblankDivider"] = vblankDivider;
data["Vulkan"]["gpuId"] = gpuId;
data["Vulkan"]["validation"] = vkValidation;
@ -545,6 +558,8 @@ void save(const std::filesystem::path& path) {
data["GUI"]["mw_width"] = m_window_size_W;
data["GUI"]["mw_height"] = m_window_size_H;
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_y"] = main_window_geometry_y;
data["GUI"]["geometry_w"] = main_window_geometry_w;
@ -565,6 +580,7 @@ void setDefaultValues() {
isNeo = false;
isFullscreen = false;
playBGM = false;
BGMvolume = 50;
screenWidth = 1280;
screenHeight = 720;
logFilter = "";
@ -582,7 +598,6 @@ void setDefaultValues() {
isAutoUpdate = false;
isNullGpu = false;
shouldDumpShaders = false;
shouldDumpPM4 = false;
vblankDivider = 1;
vkValidation = false;
vkValidationSync = false;

View file

@ -14,6 +14,8 @@ void save(const std::filesystem::path& path);
bool isNeoMode();
bool isFullscreenMode();
bool getPlayBGM();
int getBGMvolume();
std::string getLogFilter();
std::string getLogType();
std::string getUserName();
@ -32,7 +34,6 @@ bool autoUpdate();
bool nullGpu();
bool copyGPUCmdBuffers();
bool dumpShaders();
bool dumpPM4();
bool isRdocEnabled();
u32 vblankDiv();
@ -42,13 +43,13 @@ void setAutoUpdate(bool enable);
void setNullGpu(bool enable);
void setCopyGPUCmdBuffers(bool enable);
void setDumpShaders(bool enable);
void setDumpPM4(bool enable);
void setVblankDiv(u32 value);
void setGpuId(s32 selectedGpuId);
void setScreenWidth(u32 width);
void setScreenHeight(u32 height);
void setFullscreenMode(bool enable);
void setPlayBGM(bool enable);
void setBGMvolume(int volume);
void setLanguage(u32 language);
void setNeoMode(bool enable);
void setUserName(const std::string& type);
@ -73,6 +74,7 @@ bool vkCrashDiagnosticEnabled();
// Gui
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h);
void setGameInstallDir(const std::filesystem::path& dir);
void setAddonInstallDir(const std::filesystem::path& dir);
void setMainWindowTheme(u32 theme);
void setIconSize(u32 size);
void setIconSizeGrid(u32 size);
@ -91,6 +93,7 @@ u32 getMainWindowGeometryY();
u32 getMainWindowGeometryW();
u32 getMainWindowGeometryH();
std::filesystem::path getGameInstallDir();
std::filesystem::path getAddonInstallDir();
u32 getMainWindowTheme();
u32 getIconSize();
u32 getIconSizeGrid();

View file

@ -192,8 +192,9 @@ int IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, FileS
#endif
if (!IsOpen()) {
LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}",
PathToUTF8String(file_path));
const auto ec = std::error_code{result, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}, error_message={}",
PathToUTF8String(file_path), ec.message());
}
return result;
@ -372,6 +373,18 @@ bool IOFile::Seek(s64 offset, SeekOrigin origin) const {
return false;
}
u64 size = GetSize();
if (origin == SeekOrigin::CurrentPosition && Tell() + offset > size) {
LOG_ERROR(Common_Filesystem, "Seeking past the end of the file");
return false;
} else if (origin == SeekOrigin::SetOrigin && (u64)offset > size) {
LOG_ERROR(Common_Filesystem, "Seeking past the end of the file");
return false;
} else if (origin == SeekOrigin::End && offset > 0) {
LOG_ERROR(Common_Filesystem, "Seeking past the end of the file");
return false;
}
errno = 0;
const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0;

View file

@ -205,9 +205,9 @@ public:
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);
out.Write(data);
return out.Write(data);
}
private:

View file

@ -110,7 +110,6 @@ static auto UserPaths = [] {
create_path(PathType::LogDir, user_dir / LOG_DIR);
create_path(PathType::ScreenshotsDir, user_dir / SCREENSHOTS_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::GameDataDir, user_dir / GAMEDATA_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::CheatsDir, user_dir / CHEATS_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);
return paths;

View file

@ -17,7 +17,6 @@ enum class PathType {
LogDir, // Where log files are stored.
ScreenshotsDir, // Where screenshots are stored.
ShaderDir, // Where shaders are stored.
PM4Dir, // Where command lists are stored.
SaveDataDir, // Where guest save data is stored.
TempDataDir, // Where game temp data is stored.
GameDataDir, // Where game data is stored.
@ -26,7 +25,6 @@ enum class PathType {
CapturesDir, // Where rdoc captures are stored.
CheatsDir, // Where cheats 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.
};
@ -36,7 +34,6 @@ constexpr auto PORTABLE_DIR = "user";
constexpr auto LOG_DIR = "log";
constexpr auto SCREENSHOTS_DIR = "screenshots";
constexpr auto SHADER_DIR = "shader";
constexpr auto PM4_DIR = "pm4";
constexpr auto SAVEDATA_DIR = "savedata";
constexpr auto GAMEDATA_DIR = "data";
constexpr auto TEMPDATA_DIR = "temp";
@ -45,7 +42,6 @@ constexpr auto DOWNLOAD_DIR = "download";
constexpr auto CAPTURES_DIR = "captures";
constexpr auto CHEATS_DIR = "cheats";
constexpr auto PATCHES_DIR = "patches";
constexpr auto ADDONS_DIR = "addcont";
constexpr auto METADATA_DIR = "game_data";
// Filenames

View file

@ -1057,7 +1057,7 @@ static bool TryExecuteIllegalInstruction(void* ctx, void* code_address) {
// Undefined behavior if length + index is bigger than 64 according to the spec,
// we'll warn and continue execution.
LOG_WARNING(Core,
"extrq at {:x} with length {} and index {} is bigger than 64, "
"extrq at {} with length {} and index {} is bigger than 64, "
"undefined behavior",
fmt::ptr(code_address), length, index);
}
@ -1117,7 +1117,7 @@ static bool TryExecuteIllegalInstruction(void* ctx, void* code_address) {
// Undefined behavior if length + index is bigger than 64 according to the spec,
// we'll warn and continue execution.
LOG_WARNING(Core,
"insertq at {:x} with length {} and index {} is bigger than 64, "
"insertq at {} with length {} and index {} is bigger than 64, "
"undefined behavior",
fmt::ptr(code_address), length, index);
}

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

@ -44,7 +44,7 @@ PKG::PKG() = default;
PKG::~PKG() = default;
bool PKG::Open(const std::filesystem::path& filepath) {
bool PKG::Open(const std::filesystem::path& filepath, std::string& failreason) {
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
return false;
@ -70,7 +70,11 @@ bool PKG::Open(const std::filesystem::path& filepath) {
u32 offset = pkgheader.pkg_table_entry_offset;
u32 n_files = pkgheader.pkg_table_entry_count;
file.Seek(offset);
if (!file.Seek(offset)) {
failreason = "Failed to seek to PKG table entry offset";
return false;
}
for (int i = 0; i < n_files; i++) {
PKGEntry entry{};
file.Read(entry.id);
@ -85,7 +89,10 @@ bool PKG::Open(const std::filesystem::path& filepath) {
const auto name = GetEntryNameByType(entry.id);
if (name == "param.sfo") {
sfo.clear();
file.Seek(entry.offset);
if (!file.Seek(entry.offset)) {
failreason = "Failed to seek to param.sfo offset";
return false;
}
sfo.resize(entry.size);
file.ReadRaw<u8>(sfo.data(), entry.size);
}
@ -127,7 +134,11 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
std::array<std::array<u8, 256>, 7> key1;
std::array<u8, 256> imgkeydata;
file.Seek(offset);
if (!file.Seek(offset)) {
failreason = "Failed to seek to PKG table entry offset";
return false;
}
for (int i = 0; i < n_files; i++) {
PKGEntry entry{};
file.Read(entry.id);
@ -149,7 +160,10 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
// Just print with id
Common::FS::IOFile out(extract_path / "sce_sys" / std::to_string(entry.id),
Common::FS::FileAccessMode::Write);
file.Seek(entry.offset);
if (!file.Seek(entry.offset)) {
failreason = "Failed to seek to PKG entry offset";
return false;
}
std::vector<u8> data;
data.resize(entry.size);
@ -195,7 +209,10 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
}
Common::FS::IOFile out(extract_path / "sce_sys" / name, Common::FS::FileAccessMode::Write);
file.Seek(entry.offset);
if (!file.Seek(entry.offset)) {
failreason = "Failed to seek to PKG entry offset";
return false;
}
std::vector<u8> data;
data.resize(entry.size);
@ -207,7 +224,10 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
if (entry.id == 0x400 || entry.id == 0x401 || entry.id == 0x402 ||
entry.id == 0x403) { // somehow 0x401 is not decrypting
decNp.resize(entry.size);
file.Seek(entry.offset);
if (!file.Seek(entry.offset)) {
failreason = "Failed to seek to PKG entry offset";
return false;
}
std::vector<u8> data;
data.resize(entry.size);
@ -229,15 +249,12 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
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
std::array<u8, 16> seed;
file.Seek(pkgheader.pfs_image_offset + 0x370);
if (!file.Seek(pkgheader.pfs_image_offset + 0x370)) {
failreason = "Failed to seek to PFS image offset";
return false;
}
file.Read(seed);
// Get data and tweak keys.

View file

@ -103,7 +103,7 @@ public:
PKG();
~PKG();
bool Open(const std::filesystem::path& filepath);
bool Open(const std::filesystem::path& filepath, std::string& failreason);
void ExtractFiles(const int index);
bool Extract(const std::filesystem::path& filepath, const std::filesystem::path& extract,
std::string& failreason);

View file

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "common/path_util.h"
#include "trp.h"
@ -11,9 +12,13 @@ void TRP::GetNPcommID(const std::filesystem::path& trophyPath, int index) {
std::filesystem::path trpPath = trophyPath / "sce_sys/npbind.dat";
Common::FS::IOFile npbindFile(trpPath, Common::FS::FileAccessMode::Read);
if (!npbindFile.IsOpen()) {
LOG_CRITICAL(Common_Filesystem, "Failed to open npbind.dat file");
return;
}
if (!npbindFile.Seek(0x84 + (index * 0x180))) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to NPbind offset");
return;
}
npbindFile.Seek(0x84 + (index * 0x180));
npbindFile.ReadRaw<u8>(np_comm_id.data(), 12);
std::fill(np_comm_id.begin() + 12, np_comm_id.end(), 0); // fill with 0, we need 16 bytes.
}
@ -28,10 +33,10 @@ static void removePadding(std::vector<u8>& vec) {
}
}
bool TRP::Extract(const std::filesystem::path& trophyPath) {
std::filesystem::path title = trophyPath.filename();
bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string titleId) {
std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/";
if (!std::filesystem::exists(gameSysDir)) {
LOG_CRITICAL(Common_Filesystem, "Game sce_sys directory doesn't exist");
return false;
}
for (int index = 0; const auto& it : std::filesystem::directory_iterator(gameSysDir)) {
@ -40,42 +45,57 @@ bool TRP::Extract(const std::filesystem::path& trophyPath) {
Common::FS::IOFile file(it.path(), Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
LOG_CRITICAL(Common_Filesystem, "Unable to open trophy file for read");
return false;
}
TrpHeader header;
file.Read(header);
if (header.magic != 0xDCA24D00)
if (header.magic != 0xDCA24D00) {
LOG_CRITICAL(Common_Filesystem, "Wrong trophy magic number");
return false;
}
s64 seekPos = sizeof(TrpHeader);
std::filesystem::path trpFilesPath(
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / title / "TrophyFiles" /
it.path().stem());
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / titleId /
"TrophyFiles" / it.path().stem());
std::filesystem::create_directories(trpFilesPath / "Icons");
std::filesystem::create_directory(trpFilesPath / "Xml");
for (int i = 0; i < header.entry_num; i++) {
file.Seek(seekPos);
if (!file.Seek(seekPos)) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
return false;
}
seekPos += (s64)header.entry_size;
TrpEntry entry;
file.Read(entry);
std::string_view name(entry.entry_name);
if (entry.flag == 0 && name.find("TROP") != std::string::npos) { // PNG
file.Seek(entry.entry_pos);
if (!file.Seek(entry.entry_pos)) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
return false;
}
std::vector<u8> icon(entry.entry_len);
file.Read(icon);
Common::FS::IOFile::WriteBytes(trpFilesPath / "Icons" / name, icon);
}
if (entry.flag == 3 && np_comm_id[0] == 'N' &&
np_comm_id[1] == 'P') { // ESFM, encrypted.
file.Seek(entry.entry_pos);
if (!file.Seek(entry.entry_pos)) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
return false;
}
file.Read(esfmIv); // get iv key.
// Skip the first 16 bytes which are the iv key on every entry as we want a
// clean xml file.
std::vector<u8> ESFM(entry.entry_len - iv_len);
std::vector<u8> XML(entry.entry_len - iv_len);
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");
return false;
}
file.Read(ESFM);
crypto.decryptEFSM(np_comm_id, esfmIv, ESFM, XML); // decrypt
removePadding(XML);
@ -83,7 +103,14 @@ bool TRP::Extract(const std::filesystem::path& trophyPath) {
size_t pos = xml_name.find("ESFM");
if (pos != std::string::npos)
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:
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);
private:

View file

@ -5,6 +5,7 @@
#include "app_content.h"
#include "common/assert.h"
#include "common/config.h"
#include "common/io_file.h"
#include "common/logging/log.h"
#include "common/path_util.h"
@ -59,8 +60,7 @@ int PS4_SYSV_ABI sceAppContentAddcontMount(u32 service_label,
OrbisAppContentMountPoint* mount_point) {
LOG_INFO(Lib_AppContent, "called");
const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) / title_id /
entitlement_label->data;
const auto& mount_dir = Config::getAddonInstallDir() / title_id / entitlement_label->data;
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
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");
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()) {
title_id = *value;
} else {

View file

@ -176,11 +176,15 @@ int PS4_SYSV_ABI sceAudioOutGetLastOutputTime() {
}
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 channels_num = 0;
if (!audio->AudioOutGetStatus(handle, &type, &channels_num)) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
if (const auto err = audio->AudioOutGetStatus(handle, &type, &channels_num); err != ORBIS_OK) {
return err;
}
state->rerouteCounter = 0;
@ -310,12 +314,7 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
LOG_ERROR(Lib_AudioOut, "Invalid format attribute");
return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT;
}
int result = 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;
return audio->AudioOutOpen(port_type, length, sample_rate, format);
}
int PS4_SYSV_ABI sceAudioOutOpenEx() {
@ -324,12 +323,19 @@ int PS4_SYSV_ABI sceAudioOutOpenEx() {
}
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);
}
int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) {
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 ORBIS_OK;
@ -426,10 +432,10 @@ int PS4_SYSV_ABI sceAudioOutSetUsbVolume() {
}
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_OK;
return audio->AudioOutSetVolume(handle, flag, vol);
}
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
// 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 {
ORBIS_AUDIO_OUT_PORT_TYPE_MAIN = 0,
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 ptr = self->m_event_replacement.object_ptr;
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);
}
}
@ -367,7 +367,7 @@ void AvPlayerState::EmitEvent(SceAvPlayerEvents event_id, void* event_data) {
const auto callback = m_init_data.event_replacement.event_callback;
if (callback) {
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);
}
}

View file

@ -11,6 +11,7 @@
#include "common/path_util.h"
#include "common/slot_vector.h"
#include "core/address_space.h"
#include "core/debug_state.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/libkernel.h"
#include "core/libraries/libs.h"
@ -320,20 +321,6 @@ static void WaitGpuIdle() {
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
template <u32 data_block_size>
static inline u32* WriteTrailingNop(u32* cmdbuf) {
@ -507,16 +494,18 @@ void PS4_SYSV_ABI sceGnmDingDong(u32 gnm_vqid, u32 next_offs_dw) {
WaitGpuIdle();
/* Suspend logic goes here */
if (DebugState.ShouldPauseInSubmit()) {
DebugState.PauseGuestThreads();
}
auto vqid = gnm_vqid - 1;
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_size = next_offs_dw ? (next_offs_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 u32 seq_num{};
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};
}
// File name format is: <queue>_<queue num>_<submit_num>
DumpCommandList(acb, fmt::format("acb_{}_{}", gnm_vqid, seq_num));
using namespace DebugStateType;
DebugState.PushQueueDump({
.type = QueueType::acb,
.submit_num = seq_num,
.num2 = gnm_vqid,
.data = {acb.begin(), acb.end()},
});
}
liverpool->SubmitAsc(vqid, acb_span);
@ -2108,7 +2103,9 @@ s32 PS4_SYSV_ABI sceGnmSubmitCommandBuffers(u32 count, const u32* dcb_gpu_addrs[
WaitGpuIdle();
/* Suspend logic goes here */
if (DebugState.ShouldPauseInSubmit()) {
DebugState.PauseGuestThreads();
}
if (send_init_packet) {
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 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& ccb_span = std::span<const u32>{ccb, ccb_size_dw};
const auto& dcb_span = std::span{dcb_gpu_addrs[cbpair], dcb_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 u32 seq_num{};
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;
}
// File name format is: <queue>_<submit num>_<buffer_in_submit>
DumpCommandList(dcb_span, fmt::format("dcb_{}_{}", seq_num, cbpair));
DumpCommandList(ccb_span, fmt::format("ccb_{}_{}", seq_num, cbpair));
using DebugStateType::QueueType;
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);
@ -2166,6 +2174,7 @@ int PS4_SYSV_ABI sceGnmSubmitDone() {
liverpool->SubmitDone();
send_init_packet = true;
++frames_submitted;
DebugState.IncGnmFrameNum();
return ORBIS_OK;
}

View file

@ -229,7 +229,10 @@ s64 PS4_SYSV_ABI sceKernelLseek(int d, s64 offset, int whence) {
}
std::scoped_lock lk{file->m_mutex};
file->f.Seek(offset, origin);
if (!file->f.Seek(offset, origin)) {
LOG_CRITICAL(Kernel_Fs, "sceKernelLseek: failed to seek");
return SCE_KERNEL_ERROR_EINVAL;
}
return file->f.Tell();
}
@ -290,7 +293,8 @@ int PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) {
}
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
if (dir_name.empty() || !std::filesystem::create_directory(dir_name)) {
std::error_code ec;
if (dir_name.empty() || !std::filesystem::create_directory(dir_name, ec)) {
return SCE_KERNEL_ERROR_EIO;
}
@ -310,6 +314,58 @@ int PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
return result;
}
int PS4_SYSV_ABI sceKernelRmdir(const char* path) {
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
bool ro = false;
const std::filesystem::path dir_name = mnt->GetHostPath(path, &ro);
if (dir_name.empty()) {
LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, permission denied",
fmt::UTF(dir_name.u8string()));
return SCE_KERNEL_ERROR_EACCES;
}
if (ro) {
LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, directory is read only",
fmt::UTF(dir_name.u8string()));
return SCE_KERNEL_ERROR_EROFS;
}
if (!std::filesystem::is_directory(dir_name)) {
LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, path is not a directory",
fmt::UTF(dir_name.u8string()));
return ORBIS_KERNEL_ERROR_ENOTDIR;
}
if (!std::filesystem::exists(dir_name)) {
LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, no such file or directory",
fmt::UTF(dir_name.u8string()));
return ORBIS_KERNEL_ERROR_ENOENT;
}
std::error_code ec;
int result = std::filesystem::remove_all(dir_name, ec);
if (!ec) {
LOG_DEBUG(Kernel_Fs, "Removed directory: {}", fmt::UTF(dir_name.u8string()));
return ORBIS_OK;
}
LOG_ERROR(Kernel_Fs, "Failed to remove directory: {}, error_code={}",
fmt::UTF(dir_name.u8string()), ec.message());
return ErrnoToSceKernelError(ec.value());
}
int PS4_SYSV_ABI posix_rmdir(const char* path) {
int result = sceKernelRmdir(path);
if (result < 0) {
LOG_ERROR(Kernel_Pthread, "posix_rmdir: error = {}", result);
ErrSceToPosix(result);
return -1;
}
return result;
}
int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) {
LOG_INFO(Kernel_Fs, "(PARTIAL) path = {}", path);
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
@ -379,7 +435,10 @@ s64 PS4_SYSV_ABI sceKernelPread(int d, void* buf, size_t nbytes, s64 offset) {
SCOPE_EXIT {
file->f.Seek(pos);
};
file->f.Seek(offset);
if (!file->f.Seek(offset)) {
LOG_CRITICAL(Kernel_Fs, "sceKernelPread: failed to seek");
return ORBIS_KERNEL_ERROR_EINVAL;
}
return file->f.ReadRaw<u8>(buf, nbytes);
}
@ -513,7 +572,10 @@ s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) {
SCOPE_EXIT {
file->f.Seek(pos);
};
file->f.Seek(offset);
if (!file->f.Seek(offset)) {
LOG_CRITICAL(Kernel_Fs, "sceKernelPwrite: failed to seek");
return ORBIS_KERNEL_ERROR_EINVAL;
}
return file->f.WriteRaw<u8>(buf, nbytes);
}
@ -564,13 +626,19 @@ void fileSystemSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("AqBioC2vF3I", "libScePosix", 1, "libkernel", 1, 1, posix_read);
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", "libkernel", 1, "libkernel", 1, 1, posix_mkdir);
LIB_FUNCTION("naInUjYt3so", "libkernel", 1, "libkernel", 1, 1, sceKernelRmdir);
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("kBwCPsYX-m4", "libkernel", 1, "libkernel", 1, 1, sceKernelFStat);
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("52NcYU9+lEo", "libkernel", 1, "libkernel", 1, 1, sceKernelRename);
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("uWyW3v98sU4", "libkernel", 1, "libkernel", 1, 1, sceKernelCheckReachability);
LIB_FUNCTION("fTx66l5iWIA", "libkernel", 1, "libkernel", 1, 1, sceKernelFsync);

View file

@ -56,7 +56,7 @@ void KernelSignalRequest() {
}
static void KernelServiceThread(std::stop_token stoken) {
Common::SetCurrentThreadName("Kernel_ServiceThread");
Common::SetCurrentThreadName("shadPS4:Kernel_ServiceThread");
while (!stoken.stop_requested()) {
HLE_TRACE;
@ -186,6 +186,16 @@ void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, int prot, int flags, int fd,
return ptr;
}
s32 PS4_SYSV_ABI sceKernelConfiguredFlexibleMemorySize(u64* sizeOut) {
if (sizeOut == nullptr) {
return ORBIS_KERNEL_ERROR_EINVAL;
}
auto* memory = Core::Memory::Instance();
*sizeOut = memory->GetTotalFlexibleSize();
return ORBIS_OK;
}
static uint64_t g_mspace_atomic_id_mask = 0;
static uint64_t g_mstate_table[64] = {0};
@ -403,10 +413,12 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
// obj
LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", 1, 1, &g_stack_chk_guard);
// misc
LIB_FUNCTION("JGfTMBOdUJo", "libkernel", 1, "libkernel", 1, 1, sceKernelGetFsSandboxRandomWord);
LIB_FUNCTION("XVL8So3QJUk", "libkernel", 1, "libkernel", 1, 1, posix_connect);
LIB_FUNCTION("6xVpy0Fdq+I", "libkernel", 1, "libkernel", 1, 1, _sigprocmask);
// memory
LIB_FUNCTION("OMDRKKAZ8I4", "libkernel", 1, "libkernel", 1, 1, sceKernelDebugRaiseException);
LIB_FUNCTION("rTXw65xmLIA", "libkernel", 1, "libkernel", 1, 1, sceKernelAllocateDirectMemory);
@ -443,6 +455,14 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("2SKEx6bSq-4", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap);
LIB_FUNCTION("kBJzF8x4SyE", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap2);
LIB_FUNCTION("DGMG3JshrZU", "libkernel", 1, "libkernel", 1, 1, sceKernelSetVirtualRangeName);
LIB_FUNCTION("n1-v6FgU7MQ", "libkernel", 1, "libkernel", 1, 1,
sceKernelConfiguredFlexibleMemorySize);
// Memory pool
LIB_FUNCTION("qCSfqDILlns", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolExpand);
LIB_FUNCTION("pU-QydtGcGY", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolReserve);
LIB_FUNCTION("Vzl66WmfLvk", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolCommit);
LIB_FUNCTION("LXo1tpFqJGs", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolDecommit);
// equeue
LIB_FUNCTION("D0OdFMjp46I", "libkernel", 1, "libkernel", 1, 1, sceKernelCreateEqueue);

View file

@ -347,4 +347,102 @@ s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, cons
memory->NameVirtualRange(std::bit_cast<VAddr>(addr), len, name);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_t len,
size_t alignment, u64* physAddrOut) {
if (searchStart < 0 || searchEnd <= searchStart) {
LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
const bool is_in_range = searchEnd - searchStart >= len;
if (len <= 0 || !Common::Is64KBAligned(len) || !is_in_range) {
LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (alignment != 0 && !Common::Is64KBAligned(alignment)) {
LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (physAddrOut == nullptr) {
LOG_ERROR(Kernel_Vmm, "Result physical address pointer is null!");
return SCE_KERNEL_ERROR_EINVAL;
}
auto* memory = Core::Memory::Instance();
PAddr phys_addr = memory->PoolExpand(searchStart, searchEnd, len, alignment);
*physAddrOut = static_cast<s64>(phys_addr);
LOG_INFO(Kernel_Vmm,
"searchStart = {:#x}, searchEnd = {:#x}, len = {:#x}, alignment = {:#x}, physAddrOut "
"= {:#x}",
searchStart, searchEnd, len, alignment, phys_addr);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t alignment, int flags,
void** addrOut) {
LOG_INFO(Kernel_Vmm, "addrIn = {}, len = {:#x}, alignment = {:#x}, flags = {:#x}",
fmt::ptr(addrIn), len, alignment, flags);
if (addrIn == nullptr) {
LOG_ERROR(Kernel_Vmm, "Address is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (len == 0 || !Common::Is2MBAligned(len)) {
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 2MB aligned!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (alignment != 0) {
if ((!std::has_single_bit(alignment) && !Common::Is2MBAligned(alignment))) {
LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
}
auto* memory = Core::Memory::Instance();
const VAddr in_addr = reinterpret_cast<VAddr>(addrIn);
const auto map_flags = static_cast<Core::MemoryMapFlags>(flags);
memory->PoolReserve(addrOut, in_addr, len, map_flags, alignment);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags) {
if (addr == nullptr) {
LOG_ERROR(Kernel_Vmm, "Address is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (len == 0 || !Common::Is64KBAligned(len)) {
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 64KB aligned!");
return SCE_KERNEL_ERROR_EINVAL;
}
LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, type = {:#x}, prot = {:#x}, flags = {:#x}",
fmt::ptr(addr), len, type, prot, flags);
const VAddr in_addr = reinterpret_cast<VAddr>(addr);
const auto mem_prot = static_cast<Core::MemoryProt>(prot);
auto* memory = Core::Memory::Instance();
return memory->PoolCommit(in_addr, len, mem_prot);
}
s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags) {
if (addr == nullptr) {
LOG_ERROR(Kernel_Vmm, "Address is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (len == 0 || !Common::Is64KBAligned(len)) {
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 64KB aligned!");
return SCE_KERNEL_ERROR_EINVAL;
}
LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, flags = {:#x}", fmt::ptr(addr), len, flags);
const VAddr pool_addr = reinterpret_cast<VAddr>(addr);
auto* memory = Core::Memory::Instance();
memory->PoolDecommit(pool_addr, len);
return ORBIS_OK;
}
} // namespace Libraries::Kernel

View file

@ -114,4 +114,11 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn
s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, const char* name);
s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_t len,
size_t alignment, u64* physAddrOut);
s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t alignment, int flags,
void** addrOut);
s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags);
s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags);
} // namespace Libraries::Kernel

View file

@ -11,6 +11,7 @@
#include "common/logging/log.h"
#include "common/singleton.h"
#include "common/thread.h"
#include "core/debug_state.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/libkernel.h"
#include "core/libraries/kernel/thread_management.h"
@ -414,6 +415,7 @@ ScePthreadMutex* createMutex(ScePthreadMutex* addr) {
if (addr == nullptr || *addr != nullptr) {
return addr;
}
const VAddr vaddr = reinterpret_cast<VAddr>(addr);
std::string name = fmt::format("mutex{:#x}", vaddr);
scePthreadMutexInit(addr, nullptr, name.c_str());
@ -515,9 +517,12 @@ int PS4_SYSV_ABI scePthreadMutexattrSettype(ScePthreadMutexattr* attr, int type)
ptype = PTHREAD_MUTEX_RECURSIVE;
break;
case ORBIS_PTHREAD_MUTEX_NORMAL:
case ORBIS_PTHREAD_MUTEX_ADAPTIVE:
ptype = PTHREAD_MUTEX_NORMAL;
break;
case ORBIS_PTHREAD_MUTEX_ADAPTIVE:
LOG_ERROR(Kernel_Pthread, "Unimplemented adaptive mutex");
ptype = PTHREAD_MUTEX_ERRORCHECK;
break;
default:
return SCE_KERNEL_ERROR_EINVAL;
}
@ -984,16 +989,18 @@ static void cleanup_thread(void* arg) {
}
Core::SetTcbBase(nullptr);
thread->is_almost_done = true;
DebugState.RemoveCurrentThreadFromGuestList();
}
static void* run_thread(void* arg) {
auto* thread = static_cast<ScePthread>(arg);
Common::SetCurrentThreadName(thread->name.c_str());
auto* linker = Common::Singleton<Core::Linker>::Instance();
const auto* linker = Common::Singleton<Core::Linker>::Instance();
void* ret = nullptr;
g_pthread_self = thread;
pthread_cleanup_push(cleanup_thread, thread);
thread->is_started = true;
DebugState.AddCurrentThreadToGuestList();
ret = linker->ExecuteGuest(thread->entry, thread->arg);
pthread_cleanup_pop(1);
return ret;
@ -1620,6 +1627,10 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("upoVrzMHFeE", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexTrylock);
LIB_FUNCTION("IafI2PxcPnQ", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexTimedlock);
// scePthreadMutexInitForInternalLibc, scePthreadMutexattrInitForInternalLibc
LIB_FUNCTION("qH1gXoq71RY", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexInit);
LIB_FUNCTION("n2MMpvU8igI", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexattrInit);
// cond calls
LIB_FUNCTION("2Tb92quprl0", "libkernel", 1, "libkernel", 1, 1, scePthreadCondInit);
LIB_FUNCTION("m5-2bsNfv7s", "libkernel", 1, "libkernel", 1, 1, scePthreadCondattrInit);

View file

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

View file

@ -7,6 +7,10 @@
#include "common/types.h"
namespace Common {
class NativeClock;
}
namespace Core::Loader {
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_RAW_NETWORK = 19;
namespace Dev {
u64& GetInitialPtc();
Common::NativeClock* GetClock();
} // namespace Dev
u64 PS4_SYSV_ABI sceKernelGetTscFrequency();
u64 PS4_SYSV_ABI sceKernelGetProcessTime();
u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter();

View file

@ -59,7 +59,7 @@ s32 Libraries::NetCtl::NetCtlInternal::registerNpToolkitCallback(
void Libraries::NetCtl::NetCtlInternal::checkCallback() {
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) {
if (callback.func != nullptr) {
linker->ExecuteGuest(callback.func, ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED,
@ -70,7 +70,7 @@ void Libraries::NetCtl::NetCtlInternal::checkCallback() {
void Libraries::NetCtl::NetCtlInternal::checkNpToolkitCallback() {
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) {
if (callback.func != nullptr) {
linker->ExecuteGuest(callback.func, ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED,

View file

@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <common/singleton.h>
#include <core/linker.h>
#include "common/config.h"
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
@ -874,19 +876,6 @@ int PS4_SYSV_ABI sceNpCheckCallback() {
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() {
LOG_ERROR(Lib_NpManager, "(STUBBED) called");
return ORBIS_OK;
@ -983,9 +972,10 @@ int PS4_SYSV_ABI sceNpGetGamePresenceStatusA() {
}
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();
// Fill the unused stuffs to 0
memset(npId, 0, sizeof(*npId));
strcpy(npId->handle.data, name.c_str());
return ORBIS_OK;
}
@ -1010,8 +1000,9 @@ int PS4_SYSV_ABI sceNpGetParentalControlInfoA() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpGetState() {
LOG_ERROR(Lib_NpManager, "(STUBBED) called");
int PS4_SYSV_ABI sceNpGetState(s32 userId, OrbisNpState* state) {
*state = ORBIS_NP_STATE_SIGNED_OUT;
LOG_DEBUG(Lib_NpManager, "Signed out");
return ORBIS_OK;
}
@ -2515,12 +2506,28 @@ int PS4_SYSV_ABI Func_FF966E4351E564D6() {
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,
void* userdata) {
static int id = 0;
LOG_ERROR(Lib_NpManager, "(STUBBED) called");
NpStateCbForNp.func = callback;
NpStateCbForNp.userdata = userdata;
return 1;
return id;
}
int PS4_SYSV_ABI sceNpUnregisterStateCallbackForToolkit() {

View file

@ -11,6 +11,8 @@ class SymbolsResolver;
namespace Libraries::NpManager {
constexpr int ORBIS_NP_ERROR_SIGNED_OUT = 0x80550006;
enum OrbisNpState {
ORBIS_NP_STATE_UNKNOWN = 0,
ORBIS_NP_STATE_SIGNED_OUT,
@ -234,7 +236,7 @@ int PS4_SYSV_ABI sceNpGetNpReachabilityState();
int PS4_SYSV_ABI sceNpGetOnlineId();
int PS4_SYSV_ABI sceNpGetParentalControlInfo();
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 sceNpGetUserIdByOnlineId();
int PS4_SYSV_ABI sceNpHasSignedUp();

View file

@ -14,8 +14,6 @@
namespace Libraries::NpTrophy {
static TrophyUI g_trophy_ui;
std::string game_serial;
static constexpr auto MaxTrophyHandles = 4u;
@ -223,6 +221,14 @@ int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTro
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,
OrbisNpTrophyGameDetails* details,
OrbisNpTrophyGameData* data) {
@ -240,79 +246,69 @@ int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTro
if (details->size != 0x4A0 || data->size != 0x20)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
const auto trophyDir =
const auto trophy_dir =
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_parse_result result =
doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
if (result) {
if (!result) {
LOG_ERROR(Lib_NpTrophy, "Failed to parse trophy xml : {}", result.description());
return ORBIS_OK;
}
uint32_t numGroups = 0;
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;
GameTrophyInfo game_info{};
auto trophyconf = doc.child("trophyconf");
for (pugi::xml_node_iterator it = trophyconf.children().begin();
it != trophyconf.children().end(); ++it) {
auto trophyconf = doc.child("trophyconf");
for (const pugi::xml_node& node : trophyconf.children()) {
std::string_view node_name = node.name();
if (std::string(it->name()) == "title-name") {
strncpy(details->title, it->text().as_string(),
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]++;
}
}
}
if (node_name == "title-name") {
strncpy(details->title, node.text().as_string(), ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE);
}
details->numGroups = numGroups;
details->numTrophies = numTrophies;
details->numPlatinum = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
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];
if (node_name == "title-detail") {
strncpy(details->description, node.text().as_string(),
ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE);
}
// maybe this should be 1 instead of 100?
data->progressPercentage = 100;
if (node_name == "group")
game_info.num_groups++;
} else
LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
if (node_name == "trophy") {
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;
}
@ -323,6 +319,13 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(OrbisNpTrophyContext context, OrbisNpTr
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,
OrbisNpTrophyGroupId groupId,
OrbisNpTrophyGroupDetails* details,
@ -341,89 +344,78 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTr
if (details->size != 0x4A0 || data->size != 0x28)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
const auto trophyDir =
const auto trophy_dir =
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_parse_result result =
doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
if (result) {
if (!result) {
LOG_ERROR(Lib_NpTrophy, "Failed to open trophy xml : {}", result.description());
return ORBIS_OK;
}
uint32_t numGroups = 0;
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;
GroupTrophyInfo group_info{};
auto trophyconf = doc.child("trophyconf");
for (pugi::xml_node_iterator it = trophyconf.children().begin();
it != trophyconf.children().end(); ++it) {
auto trophyconf = doc.child("trophyconf");
for (const pugi::xml_node& node : trophyconf.children()) {
std::string_view node_name = node.name();
if (std::string(it->name()) == "group") {
numGroups++;
std::string currentGroupId = it->attribute("id").value();
if (!currentGroupId.empty()) {
if (std::stoi(currentGroupId) == groupId) {
std::string currentGroupName = it->child("name").text().as_string();
std::string currentGroupDescription =
it->child("detail").text().as_string();
if (node_name == "group") {
int current_group_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_GROUP_ID);
if (current_group_id != ORBIS_NP_TROPHY_INVALID_GROUP_ID) {
if (current_group_id == groupId) {
std::string_view current_group_name = node.child("name").text().as_string();
std::string_view current_group_description =
node.child("detail").text().as_string();
strncpy(details->title, currentGroupName.c_str(),
ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE);
strncpy(details->description, currentGroupDescription.c_str(),
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]++;
}
}
}
strncpy(details->title, current_group_name.data(),
ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE);
strncpy(details->description, current_group_description.data(),
ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE);
}
}
}
details->numTrophies = numTrophies;
details->numPlatinum = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
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];
details->group_id = groupId;
data->group_id = groupId;
// maybe this should be 1 instead of 100?
data->progressPercentage = 100;
if (node_name == "trophy") {
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
LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
if (current_trophy_grade.empty()) {
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;
}
@ -454,87 +446,51 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT
if (details->size != 0x498 || data->size != 0x18)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
const auto trophyDir =
const auto trophy_dir =
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_parse_result result =
doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
if (result) {
auto trophyconf = doc.child("trophyconf");
for (pugi::xml_node_iterator it = trophyconf.children().begin();
it != trophyconf.children().end(); ++it) {
if (!result) {
LOG_ERROR(Lib_NpTrophy, "Failed to open trophy xml : {}", result.description());
return ORBIS_OK;
}
if (std::string(it->name()) == "trophy") {
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();
auto trophyconf = doc.child("trophyconf");
if (currentTrophyUnlockState == "unlocked") {
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;
}
for (const pugi::xml_node& node : trophyconf.children()) {
std::string_view node_name = node.name();
strncpy(details->name, currentTrophyName.c_str(),
ORBIS_NP_TROPHY_NAME_MAX_SIZE);
strncpy(details->description, currentTrophyDescription.c_str(),
ORBIS_NP_TROPHY_DESCR_MAX_SIZE);
if (node_name == "trophy") {
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
if (current_trophy_id == trophyId) {
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;
data->unlocked = true;
data->timestamp.tick = std::stoull(currentTrophyTimestamp);
} 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;
}
uint64_t current_trophy_timestamp = node.attribute("timestamp").as_ullong();
int current_trophy_groupid = node.attribute("gid").as_int(-1);
bool current_trophy_hidden = node.attribute("hidden").as_bool();
strncpy(details->name, currentTrophyName.c_str(),
ORBIS_NP_TROPHY_NAME_MAX_SIZE);
strncpy(details->description, currentTrophyDescription.c_str(),
ORBIS_NP_TROPHY_DESCR_MAX_SIZE);
details->trophy_id = trophyId;
details->trophy_grade = GetTrophyGradeFromChar(current_trophy_grade.at(0));
details->group_id = current_trophy_groupid;
details->hidden = current_trophy_hidden;
data->trophyId = trophyId;
data->unlocked = false;
data->timestamp.tick = 0;
}
}
strncpy(details->name, current_trophy_name.data(), ORBIS_NP_TROPHY_NAME_MAX_SIZE);
strncpy(details->description, current_trophy_description.data(),
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;
}
@ -555,35 +511,36 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
ORBIS_NP_TROPHY_FLAG_ZERO(flags);
const auto trophyDir =
const auto trophy_dir =
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_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) {
auto trophyconf = doc.child("trophyconf");
for (pugi::xml_node_iterator it = trophyconf.children().begin();
it != trophyconf.children().end(); ++it) {
int num_trophies = 0;
auto trophyconf = doc.child("trophyconf");
std::string currentTrophyId = it->attribute("id").value();
std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
for (const pugi::xml_node& node : trophyconf.children()) {
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") {
numTrophies++;
}
if (currentTrophyUnlockState == "unlocked") {
ORBIS_NP_TROPHY_FLAG_SET(std::stoi(currentTrophyId), flags);
}
if (node_name == "trophy") {
num_trophies++;
}
} 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;
}
@ -912,148 +869,119 @@ int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTr
if (platinumId == nullptr)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
const auto trophyDir =
const auto trophy_dir =
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_parse_result result =
doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
pugi::xml_parse_result result = doc.load_file(trophy_file.native().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;
int numTrophies = 0;
int numTrophiesUnlocked = 0;
int num_trophies = 0;
int num_trophies_unlocked = 0;
pugi::xml_node platinum_node;
pugi::xml_node_iterator platinumIt;
int platinumTrophyGroup = -1;
auto trophyconf = doc.child("trophyconf");
if (result) {
auto trophyconf = doc.child("trophyconf");
for (pugi::xml_node_iterator it = trophyconf.children().begin();
it != trophyconf.children().end(); ++it) {
for (pugi::xml_node& node : trophyconf.children()) {
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
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();
std::string currentTrophyName = it->child("name").text().as_string();
std::string currentTrophyDescription = it->child("detail").text().as_string();
std::string currentTrophyType = it->attribute("ttype").value();
std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
if (current_trophy_type == "P") {
platinum_node = node;
if (trophyId == current_trophy_id) {
return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK;
}
}
if (currentTrophyType == "P") {
platinumIt = it;
if (std::string(platinumIt->attribute("gid").value()).empty()) {
platinumTrophyGroup = -1;
} 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_view(node.name()) == "trophy") {
if (node.attribute("pid").as_int(-1) != ORBIS_NP_TROPHY_INVALID_TROPHY_ID) {
num_trophies++;
if (current_trophy_unlockstate) {
num_trophies_unlocked++;
}
}
if (std::string(it->name()) == "trophy") {
if (platinumTrophyGroup == -1) {
if (std::string(it->attribute("gid").value()).empty()) {
numTrophies++;
if (currentTrophyUnlockState == "unlocked") {
numTrophiesUnlocked++;
}
}
if (current_trophy_id == trophyId) {
if (current_trophy_unlockstate) {
LOG_INFO(Lib_NpTrophy, "Trophy already unlocked");
return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
} else {
if (!std::string(it->attribute("gid").value()).empty()) {
if (std::stoi(std::string(it->attribute("gid").value())) ==
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;
if (node.attribute("unlockstate").empty()) {
node.append_attribute("unlockstate") = "true";
} else {
if (std::string(it->attribute("unlockstate").value()).empty()) {
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);
node.attribute("unlockstate").set_value("true");
}
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 ((numTrophies - 2) == numTrophiesUnlocked) {
platinumIt->append_attribute("unlockstate") = "unlocked";
Rtc::OrbisRtcTick trophyTimestamp;
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);
if (!platinum_node.attribute("unlockstate").as_bool()) {
if ((num_trophies - 1) == num_trophies_unlocked) {
if (platinum_node.attribute("unlockstate").empty()) {
platinum_node.append_attribute("unlockstate") = "true";
} else {
platinum_node.attribute("unlockstate").set_value("true");
}
} 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;
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);
if (platinum_node.attribute("timestamp").empty()) {
platinum_node.append_attribute("timestamp") =
std::to_string(trophyTimestamp.tick).c_str();
} else {
platinum_node.attribute("timestamp")
.set_value(std::to_string(trophyTimestamp.tick).c_str());
}
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());
} else
LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
doc.save_file((trophy_dir / "trophy00" / "Xml" / "TROP.XML").native().c_str());
return ORBIS_OK;
}

View file

@ -47,7 +47,7 @@ bool ORBIS_NP_TROPHY_FLAG_ISSET(int32_t trophyId, OrbisNpTrophyFlagArray* p);
struct OrbisNpTrophyData {
size_t size;
OrbisNpTrophyId trophyId;
OrbisNpTrophyId trophy_id;
bool unlocked;
uint8_t reserved[3];
Rtc::OrbisRtcTick timestamp;
@ -66,9 +66,9 @@ constexpr int ORBIS_NP_TROPHY_INVALID_GROUP_ID = -2;
struct OrbisNpTrophyDetails {
size_t size;
OrbisNpTrophyId trophyId;
OrbisNpTrophyGrade trophyGrade;
OrbisNpTrophyGroupId groupId;
OrbisNpTrophyId trophy_id;
OrbisNpTrophyGrade trophy_grade;
OrbisNpTrophyGroupId group_id;
bool hidden;
uint8_t reserved[3];
char name[ORBIS_NP_TROPHY_NAME_MAX_SIZE];
@ -77,46 +77,46 @@ struct OrbisNpTrophyDetails {
struct OrbisNpTrophyGameData {
size_t size;
uint32_t unlockedTrophies;
uint32_t unlockedPlatinum;
uint32_t unlockedGold;
uint32_t unlockedSilver;
uint32_t unlockedBronze;
uint32_t progressPercentage;
uint32_t unlocked_trophies;
uint32_t unlocked_platinum;
uint32_t unlocked_gold;
uint32_t unlocked_silver;
uint32_t unlocked_bronze;
uint32_t progress_percentage;
};
struct OrbisNpTrophyGameDetails {
size_t size;
uint32_t numGroups;
uint32_t numTrophies;
uint32_t numPlatinum;
uint32_t numGold;
uint32_t numSilver;
uint32_t numBronze;
uint32_t num_groups;
uint32_t num_trophies;
uint32_t num_platinum;
uint32_t num_gold;
uint32_t num_silver;
uint32_t num_bronze;
char title[ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE];
char description[ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE];
};
struct OrbisNpTrophyGroupData {
size_t size;
OrbisNpTrophyGroupId groupId;
uint32_t unlockedTrophies;
uint32_t unlockedPlatinum;
uint32_t unlockedGold;
uint32_t unlockedSilver;
uint32_t unlockedBronze;
uint32_t progressPercentage;
OrbisNpTrophyGroupId group_id;
uint32_t unlocked_trophies;
uint32_t unlocked_platinum;
uint32_t unlocked_gold;
uint32_t unlocked_silver;
uint32_t unlocked_bronze;
uint32_t progress_percentage;
uint8_t reserved[4];
};
struct OrbisNpTrophyGroupDetails {
size_t size;
OrbisNpTrophyGroupId groupId;
uint32_t numTrophies;
uint32_t numPlatinum;
uint32_t numGold;
uint32_t numSilver;
uint32_t numBronze;
OrbisNpTrophyGroupId group_id;
uint32_t num_trophies;
uint32_t num_platinum;
uint32_t num_gold;
uint32_t num_silver;
uint32_t num_bronze;
char title[ORBIS_NP_TROPHY_GROUP_TITLE_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
#include <chrono>
#include <mutex>
#include <imgui.h>
#include "common/assert.h"
#include "common/singleton.h"
#include "imgui/imgui_std.h"
#include "trophy_ui.h"
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);
}
@ -18,57 +31,63 @@ TrophyUI::~TrophyUI() {
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() {
RemoveLayer(this);
}
bool displayingTrophy;
std::chrono::steady_clock::time_point trophyStartedTime;
void TrophyUI::Draw() {
const auto& io = GetIO();
const ImVec2 window_size{
std::min(io.DisplaySize.x, 200.f),
std::min(io.DisplaySize.y, 75.f),
std::min(io.DisplaySize.x, 250.f),
std::min(io.DisplaySize.y, 70.f),
};
if (trophyQueue.size() != 0) {
if (!displayingTrophy) {
displayingTrophy = true;
trophyStartedTime = std::chrono::steady_clock::now();
SetNextWindowSize(window_size);
SetNextWindowCollapsed(false);
SetNextWindowPos(ImVec2(io.DisplaySize.x - 250, 50));
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();
std::chrono::seconds duration =
std::chrono::duration_cast<std::chrono::seconds>(timeNow - trophyStartedTime);
if (duration.count() >= 5) {
trophyQueue.erase(trophyQueue.begin());
displayingTrophy = false;
}
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();
trophy_timer -= io.DeltaTime;
if (trophy_timer <= 0) {
std::lock_guard<std::mutex> lock(queueMtx);
if (!trophy_queue.empty()) {
TrophyInfo next_trophy = trophy_queue.front();
trophy_queue.pop();
current_trophy_ui.emplace(next_trophy.trophy_icon_path, next_trophy.trophy_name);
} else {
current_trophy_ui.reset();
}
}
}
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 <variant>
#include <vector>
#include <queue>
#include "common/fixed_value.h"
#include "common/types.h"
#include "core/libraries/np_trophy/np_trophy.h"
#include "imgui/imgui_layer.h"
#include "imgui/imgui_texture.h"
namespace Libraries::NpTrophy {
struct TrophyInfo {
int trophyId = -1;
std::string trophyName;
};
class TrophyUI final : public ImGui::Layer {
std::vector<TrophyInfo> trophyQueue;
public:
TrophyUI();
TrophyUI(const std::filesystem::path& trophyIconPath, const std::string& trophyName);
~TrophyUI() override;
void AddTrophyToQueue(int trophyId, std::string trophyName);
void Finish();
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

View file

@ -79,7 +79,7 @@ static void backup(const std::filesystem::path& dir_name) {
}
static void BackupThreadBody() {
Common::SetCurrentThreadName("SaveData_BackupThread");
Common::SetCurrentThreadName("shadPS4:SaveData_BackupThread");
while (g_backup_status != WorkerStatus::Stopping) {
g_backup_status = WorkerStatus::Waiting;

View file

@ -157,7 +157,11 @@ void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_cor
if (copy_icon) {
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
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;

View file

@ -66,7 +66,7 @@ static void SaveFileSafe(void* buf, size_t count, const std::filesystem::path& p
}
[[noreturn]] void SaveThreadLoop() {
Common::SetCurrentThreadName("SaveData_SaveDataMemoryThread");
Common::SetCurrentThreadName("shadPS4:SaveData_SaveDataMemoryThread");
std::mutex mtx;
while (true) {
{
@ -207,7 +207,7 @@ void SetIcon(void* buf, size_t buf_size) {
} else {
g_icon_memory.resize(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.WriteRaw<u8>(g_icon_memory.data(), buf_size);
file.Close();

View file

@ -262,6 +262,14 @@ struct OrbisSaveDataRestoreBackupData {
s32 : 32;
};
struct OrbisSaveDataTransferringMount {
OrbisUserServiceUserId userId;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
const OrbisSaveDataFingerprint* fingerprint;
std::array<u8, 32> _reserved;
};
struct OrbisSaveDataDirNameSearchCond {
OrbisUserServiceUserId userId;
int : 32;
@ -357,7 +365,8 @@ static Error setNotInitializedError() {
}
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) {
return Error::INVALID_LOGIN_USER;
@ -369,8 +378,8 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
// check backup status
{
const auto save_path = SaveInstance::MakeDirSavePath(mount_info->userId, g_game_serial,
mount_info->dirName->data);
const auto save_path =
SaveInstance::MakeDirSavePath(mount_info->userId, title_id, mount_info->dirName->data);
if (Backup::IsBackupExecutingFor(save_path) && g_fw_ver) {
return Error::BACKUP_BUSY;
}
@ -409,7 +418,7 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
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};
if (save_instance.Mounted()) {
@ -1573,6 +1582,7 @@ Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetu
SaveMemory::SetIcon(nullptr, 0);
}
}
SaveMemory::TriggerSaveWithoutEvent();
if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) {
result->existedMemorySize = existed_size;
}
@ -1646,9 +1656,24 @@ Error PS4_SYSV_ABI sceSaveDataTerminate() {
return Error::OK;
}
int PS4_SYSV_ABI sceSaveDataTransferringMount() {
LOG_ERROR(Lib_SaveData, "(STUBBED) called");
return ORBIS_OK;
Error PS4_SYSV_ABI sceSaveDataTransferringMount(const OrbisSaveDataTransferringMount* mount,
OrbisSaveDataMountResult* mountResult) {
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) {

View file

@ -70,6 +70,7 @@ struct OrbisSaveDataMountInfo;
struct OrbisSaveDataMountPoint;
struct OrbisSaveDataMountResult;
struct OrbisSaveDataRestoreBackupData;
struct OrbisSaveDataTransferringMount;
int PS4_SYSV_ABI sceSaveDataAbort();
Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup);
@ -174,7 +175,8 @@ int PS4_SYSV_ABI sceSaveDataSupportedFakeBrokenStatus();
int PS4_SYSV_ABI sceSaveDataSyncCloudList();
Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam);
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);
int PS4_SYSV_ABI sceSaveDataUmountSys();
Error PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint);

View file

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

View file

@ -8,6 +8,7 @@
#include "common/config.h"
#include "common/debug.h"
#include "common/thread.h"
#include "core/debug_state.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/time_management.h"
#include "core/libraries/videoout/driver.h"
@ -260,7 +261,7 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
static constexpr std::chrono::nanoseconds VblankPeriod{16666667};
const auto vblank_period = VblankPeriod / Config::vblankDiv();
Common::SetCurrentThreadName("PresentThread");
Common::SetCurrentThreadName("shadPS4:PresentThread");
Common::SetCurrentThreadRealtime(vblank_period);
Common::AccurateTimer timer{vblank_period};
@ -284,7 +285,7 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
if (vblank_status.count % (main_port.flip_rate + 1) == 0) {
const auto request = receive_request();
if (!request) {
if (!main_port.is_open) {
if (!main_port.is_open || DebugState.IsGuestThreadsPaused()) {
DrawBlankFrame();
}
} else {

View file

@ -18,6 +18,7 @@
#include "core/memory.h"
#include "core/tls.h"
#include "core/virtual_memory.h"
#include "debug_state.h"
namespace Core {
@ -27,8 +28,8 @@ static PS4_SYSV_ABI void ProgramExitFunc() {
fmt::print("exit function called\n");
}
static void RunMainEntry(VAddr addr, EntryParams* params, ExitFunc exit_func) {
#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
// a specific layout
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)
: "rax", "rsi", "rdi");
#else
UNIMPLEMENTED_MSG("Missing RunMainEntry() implementation for target CPU architecture.");
#endif
}
#endif
Linker::Linker() : memory{Memory::Instance()} {}
@ -90,6 +89,7 @@ void Linker::Execute() {
// Init primary thread.
Common::SetCurrentThreadName("GAME_MainThread");
DebugState.AddCurrentThreadToGuestList();
Libraries::Kernel::pthreadInitSelfMainThread();
EnsureThreadInitialized(true);
@ -107,7 +107,12 @@ void Linker::Execute() {
for (auto& m : m_modules) {
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();
const u32 init_image_size = module->tls.init_image_size;
// 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);
std::memcpy(dest, src, 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;
void Linker::EnsureThreadInitialized(bool is_primary) {
void Linker::EnsureThreadInitialized(bool is_primary) const {
std::call_once(init_tls_flag, [this, is_primary] {
#ifdef ARCH_X86_64
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 TlsAllocAlign = 0x20;
const size_t total_tls_size = Common::AlignUp(static_tls_size, TlsAllocAlign) + TcbSize;
@ -365,7 +371,7 @@ void Linker::InitTlsForThread(bool is_primary) {
} else {
if (heap_api) {
#ifndef WIN32
addr_out = heap_api->heap_malloc(total_tls_size);
addr_out = ExecuteGuestWithoutTls(heap_api->heap_malloc, total_tls_size);
} else {
addr_out = std::malloc(total_tls_size);
#else

View file

@ -109,16 +109,23 @@ public:
void DebugDump();
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.
EnsureThreadInitialized();
return func(std::forward<CallArgs>(args)...);
return ExecuteGuestWithoutTls(func, args...);
}
private:
const Module* FindExportedModule(const ModuleInfo& m, const LibraryInfo& l);
void EnsureThreadInitialized(bool is_primary = false);
void InitTlsForThread(bool is_primary);
void EnsureThreadInitialized(bool is_primary = false) const;
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;
std::mutex mutex;

View file

@ -204,7 +204,10 @@ void Elf::Open(const std::filesystem::path& file_name) {
}
out.resize(num);
m_f.Seek(offset, SeekOrigin::SetOrigin);
if (!m_f.Seek(offset, SeekOrigin::SetOrigin)) {
LOG_CRITICAL(Loader, "Failed to seek to header tables");
return;
}
m_f.Read(out);
};
@ -465,7 +468,10 @@ std::string Elf::ElfPHeaderStr(u16 no) {
void Elf::LoadSegment(u64 virtual_addr, u64 file_offset, u64 size) {
if (!is_self) {
// It's elf file
m_f.Seek(file_offset, SeekOrigin::SetOrigin);
if (!m_f.Seek(file_offset, SeekOrigin::SetOrigin)) {
LOG_CRITICAL(Loader, "Failed to seek to ELF header");
return;
}
m_f.ReadRaw<u8>(reinterpret_cast<u8*>(virtual_addr), size);
return;
}
@ -479,7 +485,10 @@ void Elf::LoadSegment(u64 virtual_addr, u64 file_offset, u64 size) {
if (file_offset >= phdr.p_offset && file_offset < phdr.p_offset + phdr.p_filesz) {
auto offset = file_offset - phdr.p_offset;
m_f.Seek(offset + seg.file_offset, SeekOrigin::SetOrigin);
if (!m_f.Seek(offset + seg.file_offset, SeekOrigin::SetOrigin)) {
LOG_CRITICAL(Loader, "Failed to seek to segment");
return;
}
m_f.ReadRaw<u8>(reinterpret_cast<u8*>(virtual_addr), size);
return;
}

View file

@ -51,6 +51,35 @@ void MemoryManager::SetupMemoryRegions(u64 flexible_size) {
total_flexible_size, total_direct_size);
}
PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment) {
std::scoped_lock lk{mutex};
auto dmem_area = FindDmemArea(search_start);
const auto is_suitable = [&] {
const auto aligned_base = alignment > 0 ? Common::AlignUp(dmem_area->second.base, alignment)
: dmem_area->second.base;
const auto alignment_size = aligned_base - dmem_area->second.base;
const auto remaining_size =
dmem_area->second.size >= alignment_size ? dmem_area->second.size - alignment_size : 0;
return dmem_area->second.is_free && remaining_size >= size;
};
while (!is_suitable() && dmem_area->second.GetEnd() <= search_end) {
dmem_area++;
}
ASSERT_MSG(is_suitable(), "Unable to find free direct memory area: size = {:#x}", size);
// Align free position
PAddr free_addr = dmem_area->second.base;
free_addr = alignment > 0 ? Common::AlignUp(free_addr, alignment) : free_addr;
// Add the allocated region to the list and commit its pages.
auto& area = CarveDmemArea(free_addr, size)->second;
area.is_free = false;
area.is_pooled = true;
return free_addr;
}
PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment,
int memory_type) {
std::scoped_lock lk{mutex};
@ -112,6 +141,43 @@ void MemoryManager::Free(PAddr phys_addr, size_t size) {
MergeAdjacent(dmem_map, dmem_area);
}
int MemoryManager::PoolReserve(void** out_addr, VAddr virtual_addr, size_t size,
MemoryMapFlags flags, u64 alignment) {
std::scoped_lock lk{mutex};
virtual_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
alignment = alignment > 0 ? alignment : 2_MB;
VAddr mapped_addr = alignment > 0 ? Common::AlignUp(virtual_addr, alignment) : virtual_addr;
// Fixed mapping means the virtual address must exactly match the provided one.
if (True(flags & MemoryMapFlags::Fixed)) {
const auto& vma = FindVMA(mapped_addr)->second;
// If the VMA is mapped, unmap the region first.
if (vma.IsMapped()) {
UnmapMemoryImpl(mapped_addr, size);
}
const size_t remaining_size = vma.base + vma.size - mapped_addr;
ASSERT_MSG(vma.type == VMAType::Free && remaining_size >= size);
}
// Find the first free area starting with provided virtual address.
if (False(flags & MemoryMapFlags::Fixed)) {
mapped_addr = SearchFree(mapped_addr, size, alignment);
}
// Add virtual memory area
const auto new_vma_handle = CarveVMA(mapped_addr, size);
auto& new_vma = new_vma_handle->second;
new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
new_vma.prot = MemoryProt::NoAccess;
new_vma.name = "";
new_vma.type = VMAType::PoolReserved;
MergeAdjacent(vma_map, new_vma_handle);
*out_addr = std::bit_cast<void*>(mapped_addr);
return ORBIS_OK;
}
int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
u64 alignment) {
std::scoped_lock lk{mutex};
@ -149,6 +215,36 @@ int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, Mem
return ORBIS_OK;
}
int MemoryManager::PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot) {
std::scoped_lock lk{mutex};
const u64 alignment = 64_KB;
// When virtual addr is zero, force it to virtual_base. The guest cannot pass Fixed
// flag so we will take the branch that searches for free (or reserved) mappings.
virtual_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
VAddr mapped_addr = Common::AlignUp(virtual_addr, alignment);
// This should return SCE_KERNEL_ERROR_ENOMEM but shouldn't normally happen.
const auto& vma = FindVMA(mapped_addr)->second;
const size_t remaining_size = vma.base + vma.size - mapped_addr;
ASSERT_MSG(!vma.IsMapped() && remaining_size >= size);
// Perform the mapping.
void* out_addr = impl.Map(mapped_addr, size, alignment, -1, false);
TRACK_ALLOC(out_addr, size, "VMEM");
auto& new_vma = CarveVMA(mapped_addr, size)->second;
new_vma.disallow_merge = false;
new_vma.prot = prot;
new_vma.name = "";
new_vma.type = Core::VMAType::Pooled;
new_vma.is_exec = false;
new_vma.phys_base = 0;
return ORBIS_OK;
}
int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
MemoryMapFlags flags, VMAType type, std::string_view name,
bool is_exec, PAddr phys_addr, u64 alignment) {
@ -232,6 +328,39 @@ int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, Mem
return ORBIS_OK;
}
void MemoryManager::PoolDecommit(VAddr virtual_addr, size_t size) {
std::scoped_lock lk{mutex};
const auto it = FindVMA(virtual_addr);
const auto& vma_base = it->second;
ASSERT_MSG(vma_base.Contains(virtual_addr, size),
"Existing mapping does not contain requested unmap range");
const auto vma_base_addr = vma_base.base;
const auto vma_base_size = vma_base.size;
const auto phys_base = vma_base.phys_base;
const bool is_exec = vma_base.is_exec;
const auto start_in_vma = virtual_addr - vma_base_addr;
const auto type = vma_base.type;
rasterizer->UnmapMemory(virtual_addr, size);
// Mark region as free and attempt to coalesce it with neighbours.
const auto new_it = CarveVMA(virtual_addr, size);
auto& vma = new_it->second;
vma.type = VMAType::PoolReserved;
vma.prot = MemoryProt::NoAccess;
vma.phys_base = 0;
vma.disallow_merge = false;
vma.name = "";
MergeAdjacent(vma_map, new_it);
// Unmap the memory region.
impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + size, phys_base, is_exec,
false, false);
TRACK_FREE(virtual_addr, "VMEM");
}
void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) {
std::scoped_lock lk{mutex};
UnmapMemoryImpl(virtual_addr, size);

View file

@ -50,15 +50,17 @@ enum class VMAType : u32 {
Direct = 2,
Flexible = 3,
Pooled = 4,
Stack = 5,
Code = 6,
File = 7,
PoolReserved = 5,
Stack = 6,
Code = 7,
File = 8,
};
struct DirectMemoryArea {
PAddr base = 0;
size_t size = 0;
int memory_type = 0;
bool is_pooled = false;
bool is_free = true;
PAddr GetEnd() const {
@ -96,7 +98,7 @@ struct VirtualMemoryArea {
}
bool IsMapped() const noexcept {
return type != VMAType::Free && type != VMAType::Reserved;
return type != VMAType::Free && type != VMAType::Reserved && type != VMAType::PoolReserved;
}
bool CanMergeWith(const VirtualMemoryArea& next) const {
@ -135,6 +137,10 @@ public:
return total_direct_size;
}
u64 GetTotalFlexibleSize() const {
return total_flexible_size;
}
u64 GetAvailableFlexibleSize() const {
return total_flexible_size - flexible_usage;
}
@ -145,14 +151,21 @@ public:
void SetupMemoryRegions(u64 flexible_size);
PAddr PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment);
PAddr Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment,
int memory_type);
void Free(PAddr phys_addr, size_t size);
int PoolReserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
u64 alignment = 0);
int Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
u64 alignment = 0);
int PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot);
int MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
MemoryMapFlags flags, VMAType type, std::string_view name = "",
bool is_exec = false, PAddr phys_addr = -1, u64 alignment = 0);
@ -160,6 +173,8 @@ public:
int MapFile(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
MemoryMapFlags flags, uintptr_t fd, size_t offset);
void PoolDecommit(VAddr virtual_addr, size_t size);
void UnmapMemory(VAddr virtual_addr, size_t size);
int QueryProtection(VAddr addr, void** start, void** end, u32* prot);

View file

@ -70,7 +70,7 @@ Module::~Module() = default;
s32 Module::Start(size_t args, const void* argp, void* param) {
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();
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));
}
break;
case SIGUSR1: { // Sleep thread until signal is received
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGUSR1);
sigwait(&sigset, &sig);
} break;
default:
break;
}
@ -105,6 +111,8 @@ SignalDispatch::SignalDispatch() {
"Failed to register access violation signal handler.");
ASSERT_MSG(sigaction(SIGILL, &action, nullptr) == 0,
"Failed to register illegal instruction signal handler.");
ASSERT_MSG(sigaction(SIGUSR1, &action, nullptr) == 0,
"Failed to register sleep signal handler.");
#endif
}

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 B

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 942 B

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 B

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 B

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -10,6 +10,9 @@
#define IM_COL32_GRAY(x) IM_COL32(x, x, x, 0xFF)
#define IMGUI_FONT_TEXT 0
#define IMGUI_FONT_MONO 1
namespace ImGui {
namespace Easing {
@ -27,6 +30,20 @@ inline void CentralizeWindow() {
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() {
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
NotoSansJP-Regular.ttf
ProggyVector-Regular.ttf
)
set(OutputList "")

Binary file not shown.

View file

@ -6,6 +6,7 @@
#include "common/config.h"
#include "common/path_util.h"
#include "core/devtools/layer.h"
#include "imgui/imgui_layer.h"
#include "imgui_core.h"
#include "imgui_impl_sdl3.h"
@ -16,6 +17,7 @@
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "imgui_fonts/notosansjp_regular.ttf.g.cpp"
#include "imgui_fonts/proggyvector_regular.ttf.g.cpp"
static void CheckVkResult(const vk::Result err) {
LOG_ERROR(ImGui, "Vulkan error {}", vk::to_string(err));
@ -33,6 +35,7 @@ std::deque<std::pair<bool, ImGui::Layer*>>& GetChangeLayers() {
}
static std::mutex change_layers_mutex{};
static ImGuiID dock_id;
namespace ImGui {
@ -51,6 +54,7 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.DisplaySize = ImVec2((float)window.getWidth(), (float)window.getHeight());
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f); // Makes the window edges rounded
auto path = config_path.u8string();
char* config_file_buf = new char[path.size() + 1]();
@ -73,12 +77,16 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w
ImFontConfig font_cfg{};
font_cfg.OversampleH = 2;
font_cfg.OversampleV = 1;
io.Fonts->AddFontFromMemoryCompressedTTF(imgui_font_notosansjp_regular_compressed_data,
imgui_font_notosansjp_regular_compressed_size, 16.0f,
&font_cfg, ranges.Data);
io.FontDefault = io.Fonts->AddFontFromMemoryCompressedTTF(
imgui_font_notosansjp_regular_compressed_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();
::Core::Devtools::Layer::SetupSettings();
Sdl::Init(window.GetSdlWindow());
const Vulkan::InitInfo vk_info{
@ -99,6 +107,14 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w
Vulkan::Init(vk_info);
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() {
@ -140,8 +156,10 @@ bool ProcessEvent(SDL_Event* event) {
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
return GetIO().NavActive;
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: {
const auto& io = GetIO();
return io.NavActive && io.Ctx->NavWindow != nullptr && io.Ctx->NavWindow->ID != dock_id;
}
default:
return false;
}
@ -165,6 +183,8 @@ void NewFrame() {
Sdl::NewFrame();
ImGui::NewFrame();
DockSpaceOverViewport(0, GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);
for (auto* layer : layers) {
layer->Draw();
}

View file

@ -127,7 +127,7 @@ void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
if (m_sdl_gamepad != nullptr) {
return SDL_RumbleGamepad(m_sdl_gamepad, (smallMotor / 255.0f) * 0xFFFF,
(largeMotor / 255.0f) * 0xFFFF, -1) == 0;
(largeMotor / 255.0f) * 0xFFFF, -1);
}
return true;
}

View file

@ -10,6 +10,12 @@ BackgroundMusicPlayer::BackgroundMusicPlayer(QObject* parent) : QObject(parent)
m_mediaPlayer->setLoops(QMediaPlayer::Infinite);
}
void BackgroundMusicPlayer::setVolume(int volume) {
float linearVolume = QAudio::convertVolume(volume / 100.0f, QAudio::LogarithmicVolumeScale,
QAudio::LinearVolumeScale);
m_audioOutput->setVolume(linearVolume);
}
void BackgroundMusicPlayer::playMusic(const QString& snd0path) {
if (snd0path.isEmpty()) {
stopMusic();

View file

@ -16,6 +16,7 @@ public:
return instance;
}
void setVolume(int volume);
void playMusic(const QString& snd0path);
void stopMusic();
@ -25,4 +26,4 @@ private:
QMediaPlayer* m_mediaPlayer;
QAudioOutput* m_audioOutput;
QUrl m_currentMusic;
};
};

View file

@ -456,10 +456,9 @@ void CheatsPatches::downloadCheats(const QString& source, const QString& gameSer
if (source == "GoldHEN") {
url = "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/main/json.txt";
} else if (source == "wolf2022") {
url = "https://wolf2022.ir/trainer/" + gameSerial + "_" + gameVersion + ".json";
url = "https://wolf2022.ir/trainer/list.json";
} else if (source == "shadPS4") {
url = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/main/"
"CHEATS_JSON.txt";
url = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/main/CHEATS_JSON.txt";
} else {
QMessageBox::warning(this, tr("Invalid 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();
bool foundFiles = false;
if (source == "GoldHEN" || source == "shadPS4") {
QString textContent(jsonData);
QRegularExpression regex(
QString("%1_%2[^=]*\\.json").arg(gameSerial).arg(gameVersion));
QRegularExpressionMatchIterator matches = regex.globalMatch(textContent);
QString baseUrl;
if (source == "wolf2022") {
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
QJsonArray gamesArray = jsonDoc.object().value("games").toArray();
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/";
}
foreach (const QJsonValue& value, gamesArray) {
QJsonObject gameObject = value.toObject();
QString title = gameObject.value("title").toString();
QString version = gameObject.value("version").toString();
while (matches.hasNext()) {
QRegularExpressionMatch match = matches.next();
QString fileName = match.captured(0);
if (title == gameSerial &&
(version == gameVersion || version == gameVersion.mid(1))) {
QString fileUrl =
"https://wolf2022.ir/trainer/" + gameObject.value("url").toString();
if (!fileName.isEmpty()) {
QString newFileName = fileName;
int dotIndex = newFileName.lastIndexOf('.');
if (dotIndex != -1) {
QString localFileName = gameObject.value("url").toString();
localFileName =
localFileName.left(localFileName.lastIndexOf('.')) + "_wolf2022.json";
if (source == "GoldHEN") {
newFileName.insert(dotIndex, "_GoldHEN");
} else {
newFileName.insert(dotIndex, "_shadPS4");
}
}
QString fileUrl = baseUrl + fileName;
QString localFilePath = dir.filePath(newFileName);
QString localFilePath = dir.filePath(localFileName);
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?"),
tr("File already exists. Do you want to replace it?") + "\n" +
localFileName,
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::No) {
continue;
@ -549,38 +536,81 @@ void CheatsPatches::downloadCheats(const QString& source, const QString& gameSer
if (!foundFiles && showMessageBox) {
QMessageBox::warning(this, tr("Cheats Not Found"), tr("CheatsNotFound_MSG"));
}
} else if (source == "wolf2022") {
QString fileName = QFileInfo(QUrl(url).path()).fileName();
QString baseFileName = fileName;
int dotIndex = baseFileName.lastIndexOf('.');
if (dotIndex != -1) {
baseFileName.insert(dotIndex, "_wolf2022");
} else if (source == "GoldHEN" || source == "shadPS4") {
QString textContent(jsonData);
QRegularExpression regex(
QString("%1_%2[^=]*\\.json").arg(gameSerial).arg(gameVersion));
QRegularExpressionMatchIterator matches = regex.globalMatch(textContent);
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,
Common::FS::GetUserPath(Common::FS::PathType::CheatsDir));
filePath += "/" + baseFileName;
if (QFile::exists(filePath) && showMessageBox) {
QMessageBox::StandardButton reply2;
reply2 =
QMessageBox::question(this, tr("File Exists"),
tr("File already exists. Do you want to replace it?"),
QMessageBox::Yes | QMessageBox::No);
if (reply2 == QMessageBox::No) {
reply->deleteLater();
return;
while (matches.hasNext()) {
QRegularExpressionMatch match = matches.next();
QString fileName = match.captured(0);
if (!fileName.isEmpty()) {
QString newFileName = fileName;
int dotIndex = newFileName.lastIndexOf('.');
if (dotIndex != -1) {
if (source == "GoldHEN") {
newFileName.insert(dotIndex, "_GoldHEN");
} 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 (cheatFile.open(QIODevice::WriteOnly)) {
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) {
QMessageBox::warning(this, tr("Cheats Not Found"), tr("CheatsNotFound_MSG"));
}
}
if (foundFiles && showMessageBox) {
@ -910,11 +940,16 @@ void CheatsPatches::addCheatsToLayout(const QJsonArray& modsArray, const QJsonAr
void CheatsPatches::populateFileListCheats() {
QString 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);
QStringList filters;
filters << pattern;
filters << patternWithFirstChar << patternWithoutFirstChar;
dir.setNameFilters(filters);
QFileInfoList fileList = dir.entryInfoList(QDir::Files);
@ -1248,4 +1283,4 @@ void CheatsPatches::onPatchCheckBoxHovered(QCheckBox* checkBox, bool hovered) {
} else {
instructionsTextEdit->setText(defaultTextEdit);
}
}
}

View file

@ -23,6 +23,7 @@
#include <common/path_util.h>
#include <common/scm_rev.h>
#include <common/version.h>
#include <qprogressbar.h>
#include "check_update.h"
using namespace Common::FS;
@ -313,20 +314,37 @@ void CheckUpdate::requestChangelog(const QString& currentRev, const QString& lat
}
void CheckUpdate::DownloadUpdate(const QString& url) {
QProgressBar* progressBar = new QProgressBar(this);
progressBar->setRange(0, 100);
progressBar->setTextVisible(true);
progressBar->setValue(0);
layout()->addWidget(progressBar);
QNetworkRequest request(url);
QNetworkReply* reply = networkManager->get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply, url]() {
connect(reply, &QNetworkReply::downloadProgress, this,
[progressBar](qint64 bytesReceived, qint64 bytesTotal) {
if (bytesTotal > 0) {
int percentage = static_cast<int>((bytesReceived * 100) / bytesTotal);
progressBar->setValue(percentage);
}
});
connect(reply, &QNetworkReply::finished, this, [this, reply, progressBar, url]() {
progressBar->setValue(100);
if (reply->error() != QNetworkReply::NoError) {
QMessageBox::warning(this, tr("Error"),
tr("Network error occurred while trying to access the URL") +
":\n" + url + "\n" + reply->errorString());
reply->deleteLater();
progressBar->deleteLater();
return;
}
QString userPath =
QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::UserDir).string());
QString userPath;
Common::FS::PathToQString(userPath, Common::FS::GetUserPath(Common::FS::PathType::UserDir));
QString tempDownloadPath = userPath + "/temp_download_update";
QDir dir(tempDownloadPath);
if (!dir.exists()) {
@ -348,16 +366,18 @@ void CheckUpdate::DownloadUpdate(const QString& url) {
}
reply->deleteLater();
progressBar->deleteLater();
});
}
void CheckUpdate::Install() {
QString userPath =
QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::UserDir).string());
QString userPath;
Common::FS::PathToQString(userPath, Common::FS::GetUserPath(Common::FS::PathType::UserDir));
QString startingUpdate = tr("Starting 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 scriptFileName;

View file

@ -22,7 +22,7 @@ GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidg
this->setContextMenuPolicy(Qt::CustomContextMenu);
PopulateGameGrid(m_game_info->m_games, false);
connect(this, &QTableWidget::cellClicked, this, &GameGridFrame::SetGridBackgroundImage);
connect(this, &QTableWidget::currentCellChanged, this, &GameGridFrame::onCurrentCellChanged);
connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this,
&GameGridFrame::RefreshGridBackgroundImage);
@ -31,22 +31,33 @@ GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidg
connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) {
m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, this, false);
});
connect(this, &QTableWidget::cellClicked, this, [&]() {
cellClicked = true;
crtRow = this->currentRow();
crtColumn = this->currentColumn();
columnCnt = this->columnCount();
});
}
void GameGridFrame::PlayBackgroundMusic(QTableWidgetItem* item) {
if (!item) {
void GameGridFrame::onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
int previousColumn) {
cellClicked = true;
crtRow = currentRow;
crtColumn = currentColumn;
columnCnt = this->columnCount();
auto itemID = (crtRow * columnCnt) + currentColumn;
if (itemID > m_game_info->m_games.count() - 1) {
validCellSelected = false;
BackgroundMusicPlayer::getInstance().stopMusic();
return;
}
QString snd0path;
Common::FS::PathToQString(snd0path, m_game_info->m_games[item->row()].snd0_path);
BackgroundMusicPlayer::getInstance().playMusic(snd0path);
validCellSelected = true;
SetGridBackgroundImage(crtRow, crtColumn);
auto snd0Path = QString::fromStdString(m_game_info->m_games[itemID].snd0_path.string());
PlayBackgroundMusic(snd0Path);
}
void GameGridFrame::PlayBackgroundMusic(QString path) {
if (path.isEmpty() || !Config::getPlayBGM()) {
BackgroundMusicPlayer::getInstance().stopMusic();
return;
}
BackgroundMusicPlayer::getInstance().playMusic(path);
}
void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool fromSearch) {
@ -157,3 +168,7 @@ void GameGridFrame::RefreshGridBackgroundImage() {
this->setPalette(palette);
}
}
bool GameGridFrame::IsValidCellSelected() {
return validCellSelected;
}

View file

@ -20,7 +20,9 @@ Q_SIGNALS:
public Q_SLOTS:
void SetGridBackgroundImage(int row, int column);
void RefreshGridBackgroundImage();
void PlayBackgroundMusic(QTableWidgetItem* item);
void PlayBackgroundMusic(QString path);
void onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
int previousColumn);
private:
QImage backgroundImage;
@ -28,10 +30,12 @@ private:
GuiContextMenus m_gui_context_menus;
std::shared_ptr<GameInfoClass> m_game_info;
std::shared_ptr<QVector<GameInfo>> m_games_shared;
bool validCellSelected = false;
public:
explicit GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent = nullptr);
void PopulateGameGrid(QVector<GameInfo> m_games, bool fromSearch);
bool IsValidCellSelected();
bool cellClicked = false;
int icon_size;

View file

@ -18,6 +18,7 @@ GameInstallDialog::GameInstallDialog() : m_gamesDirectory(nullptr) {
auto layout = new QVBoxLayout(this);
layout->addWidget(SetupGamesDirectory());
layout->addWidget(SetupAddonsDirectory());
layout->addStretch();
layout->addWidget(SetupDialogActions());
@ -27,7 +28,7 @@ GameInstallDialog::GameInstallDialog() : m_gamesDirectory(nullptr) {
GameInstallDialog::~GameInstallDialog() {}
void GameInstallDialog::Browse() {
void GameInstallDialog::BrowseGamesDirectory() {
auto path = QFileDialog::getExistingDirectory(this, tr("Directory to install games"));
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() {
auto group = new QGroupBox(tr("Directory to install games"));
auto layout = new QHBoxLayout(group);
@ -51,7 +60,30 @@ QWidget* GameInstallDialog::SetupGamesDirectory() {
// Browse button.
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);
@ -70,6 +102,7 @@ QWidget* GameInstallDialog::SetupDialogActions() {
void GameInstallDialog::Save() {
// Check games directory.
auto gamesDirectory = m_gamesDirectory->text();
auto addonsDirectory = m_addonsDirectory->text();
if (gamesDirectory.isEmpty() || !QDir(gamesDirectory).exists() ||
!QDir::isAbsolutePath(gamesDirectory)) {
@ -78,7 +111,22 @@ void GameInstallDialog::Save() {
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::setAddonInstallDir(Common::FS::PathFromQString(addonsDirectory));
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::save(config_dir / "config.toml");
accept();

View file

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

View file

@ -41,7 +41,7 @@ GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidg
this->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
PopulateGameList();
connect(this, &QTableWidget::itemClicked, this, &GameListFrame::SetListBackgroundImage);
connect(this, &QTableWidget::currentCellChanged, this, &GameListFrame::onCurrentCellChanged);
connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this,
&GameListFrame::RefreshListBackgroundImage);
connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this,
@ -69,8 +69,18 @@ GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidg
});
}
void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) {
void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
int previousColumn) {
QTableWidgetItem* item = this->item(currentRow, currentColumn);
if (!item) {
return;
}
SetListBackgroundImage(item);
PlayBackgroundMusic(item);
}
void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) {
if (!item || !Config::getPlayBGM()) {
BackgroundMusicPlayer::getInstance().stopMusic();
return;
}

View file

@ -23,6 +23,8 @@ public Q_SLOTS:
void SortNameAscending(int columnIndex);
void SortNameDescending(int columnIndex);
void PlayBackgroundMusic(QTableWidgetItem* item);
void onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
int previousColumn);
private:
void SetTableItem(int row, int column, QString itemStr);
@ -43,24 +45,44 @@ public:
int icon_size;
static bool CompareStringsAscending(GameInfo a, GameInfo b, int columnIndex) {
if (columnIndex == 1) {
switch (columnIndex) {
case 1:
return a.name < b.name;
} else if (columnIndex == 2) {
case 2:
return a.serial < b.serial;
} else if (columnIndex == 3) {
case 3:
return a.region < b.region;
case 4:
return a.fw < b.fw;
case 5:
return a.size < b.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) {
if (columnIndex == 1) {
switch (columnIndex) {
case 1:
return a.name > b.name;
} else if (columnIndex == 2) {
case 2:
return a.serial > b.serial;
} else if (columnIndex == 3) {
case 3:
return a.region > b.region;
case 4:
return a.fw > b.fw;
case 5:
return a.size > b.size;
case 6:
return a.version > b.version;
case 7:
return a.path > b.path;
default:
return false;
}
return false;
}
};

View file

@ -312,10 +312,7 @@ public:
if (selected == &installPackage) {
QStringList pkg_app_ = m_pkg_app_list[itemIndex].split(";;");
std::filesystem::path path(pkg_app_[9].toStdString());
#ifdef _WIN32
path = std::filesystem::path(pkg_app_[9].toStdWString());
#endif
std::filesystem::path path = Common::FS::PathFromQString(pkg_app_[9]);
InstallDragDropPkg(path, 1, 1);
}
}

View file

@ -2,14 +2,15 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QDockWidget>
#include <QKeyEvent>
#include <QProgressDialog>
#include <common/scm_rev.h>
#include "about_dialog.h"
#include "cheats_patches.h"
#include "check_update.h"
#include "common/io_file.h"
#include "common/path_util.h"
#include "common/scm_rev.h"
#include "common/string_util.h"
#include "common/version.h"
#include "core/file_format/pkg.h"
@ -21,6 +22,7 @@
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
ui->setupUi(this);
installEventFilter(this);
setAttribute(Qt::WA_DeleteOnClose);
}
@ -306,6 +308,7 @@ void MainWindow::CreateConnects() {
});
// List
connect(ui->setlistModeListAct, &QAction::triggered, m_dock_widget.data(), [this]() {
BackgroundMusicPlayer::getInstance().stopMusic();
m_dock_widget->setWidget(m_game_list_frame.data());
m_game_grid_frame->hide();
m_elf_viewer->hide();
@ -322,6 +325,7 @@ void MainWindow::CreateConnects() {
});
// Grid
connect(ui->setlistModeGridAct, &QAction::triggered, m_dock_widget.data(), [this]() {
BackgroundMusicPlayer::getInstance().stopMusic();
m_dock_widget->setWidget(m_game_grid_frame.data());
m_game_grid_frame->show();
m_game_list_frame->hide();
@ -338,6 +342,7 @@ void MainWindow::CreateConnects() {
});
// Elf
connect(ui->setlistElfAct, &QAction::triggered, m_dock_widget.data(), [this]() {
BackgroundMusicPlayer::getInstance().stopMusic();
m_dock_widget->setWidget(m_elf_viewer.data());
m_game_grid_frame->hide();
m_game_list_frame->hide();
@ -512,25 +517,6 @@ void MainWindow::CreateConnects() {
isIconBlack = false;
}
});
connect(m_game_grid_frame.get(), &QTableWidget::cellClicked, this,
&MainWindow::PlayBackgroundMusic);
connect(m_game_list_frame.get(), &QTableWidget::cellClicked, this,
&MainWindow::PlayBackgroundMusic);
}
void MainWindow::PlayBackgroundMusic() {
if (isGameRunning || !Config::getPlayBGM()) {
BackgroundMusicPlayer::getInstance().stopMusic();
return;
}
int itemID = isTableList ? m_game_list_frame->currentItem()->row()
: m_game_grid_frame->crtRow * m_game_grid_frame->columnCnt +
m_game_grid_frame->crtColumn;
QString snd0path;
Common::FS::PathToQString(snd0path, m_game_info->m_games[itemID].snd0_path);
BackgroundMusicPlayer::getInstance().playMusic(snd0path);
}
void MainWindow::StartGame() {
@ -619,6 +605,7 @@ void MainWindow::ConfigureGuiFromSettings() {
} else {
ui->setlistModeGridAct->setChecked(true);
}
BackgroundMusicPlayer::getInstance().setVolume(Config::getBGMvolume());
}
void MainWindow::SaveWindowState() const {
@ -638,10 +625,7 @@ void MainWindow::InstallPkg() {
int pkgNum = 0;
for (const QString& file : fileNames) {
++pkgNum;
std::filesystem::path path(file.toStdString());
#ifdef _WIN64
path = std::filesystem::path(file.toStdWString());
#endif
std::filesystem::path path = Common::FS::PathFromQString(file);
MainWindow::InstallDragDropPkg(path, pkgNum, nPkg);
}
}
@ -659,10 +643,7 @@ void MainWindow::BootGame() {
QMessageBox::critical(nullptr, tr("Game Boot"),
QString(tr("Only one file can be selected!")));
} else {
std::filesystem::path path(fileNames[0].toStdString());
#ifdef _WIN64
path = std::filesystem::path(fileNames[0].toStdWString());
#endif
std::filesystem::path path = Common::FS::PathFromQString(fileNames[0]);
Core::Emulator emulator;
if (!std::filesystem::exists(path)) {
QMessageBox::critical(nullptr, tr("Run Game"),
@ -676,9 +657,12 @@ void MainWindow::BootGame() {
void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int nPkg) {
if (Loader::DetectFileType(file) == Loader::FileTypes::Pkg) {
pkg = PKG();
pkg.Open(file);
std::string failreason;
pkg = PKG();
if (!pkg.Open(file, failreason)) {
QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason));
return;
}
auto extract_path = Config::getGameInstallDir() / pkg.GetTitleID();
QString pkgType = QString::fromStdString(pkg.GetPkgFlags());
QString gameDirPath;
@ -703,8 +687,8 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
}
std::string entitlement_label = Common::SplitString(content_id, '-')[2];
auto addon_extract_path = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) /
pkg.GetTitleID() / entitlement_label;
auto addon_extract_path =
Config::getAddonInstallDir() / pkg.GetTitleID() / entitlement_label;
QString addonDirPath;
Common::FS::PathToQString(addonDirPath, addon_extract_path);
QDir addon_dir(addonDirPath);
@ -784,7 +768,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
}
}
} 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?")));
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No);
@ -1042,3 +1026,17 @@ void MainWindow::OnLanguageChanged(const std::string& locale) {
LoadTranslation();
}
bool MainWindow::eventFilter(QObject* obj, QEvent* event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
auto tblMode = Config::getTableMode();
if (tblMode != 2 && (tblMode != 1 || m_game_grid_frame->IsValidCellSelected())) {
StartGame();
return true;
}
}
}
return QMainWindow::eventFilter(obj, event);
}

View file

@ -94,6 +94,8 @@ private:
QTranslator* translator;
protected:
bool eventFilter(QObject* obj, QEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event1) override {
if (event1->mimeData()->hasUrls()) {
event1->acceptProposedAction();
@ -108,10 +110,7 @@ protected:
int nPkg = urlList.size();
for (const QUrl& url : urlList) {
pkgNum++;
std::filesystem::path path(url.toLocalFile().toStdString());
#ifdef _WIN64
path = std::filesystem::path(url.toLocalFile().toStdWString());
#endif
std::filesystem::path path = Common::FS::PathFromQString(url.toLocalFile());
InstallDragDropPkg(path, pkgNum, nPkg);
}
}

View file

@ -104,11 +104,12 @@ void PKGViewer::ProcessPKGInfo() {
m_pkg_patch_list.clear();
m_full_pkg_list.clear();
for (int i = 0; i < m_pkg_list.size(); i++) {
std::filesystem::path path(m_pkg_list[i].toStdString());
#ifdef _WIN32
path = std::filesystem::path(m_pkg_list[i].toStdWString());
#endif
package.Open(path);
std::filesystem::path path = Common::FS::PathFromQString(m_pkg_list[i]);
std::string failreason;
if (!package.Open(path, failreason)) {
QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason));
return;
}
psf.Open(package.sfo);
QString title_name =
QString::fromStdString(std::string{psf.GetString("TITLE").value_or("Unknown")});

View file

@ -70,7 +70,7 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
InitializeEmulatorLanguages();
LoadValuesFromConfig();
defaultTextEdit = tr("Point your mouse at an options to display a description in here");
defaultTextEdit = tr("Point your mouse at an option to display its description.");
ui->descriptionText->setText(defaultTextEdit);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close);
@ -140,8 +140,17 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
checkUpdate->exec();
});
connect(ui->playBGMCheckBox, &QCheckBox::stateChanged, this,
[](int val) { Config::setPlayBGM(val); });
connect(ui->playBGMCheckBox, &QCheckBox::stateChanged, this, [](int val) {
Config::setPlayBGM(val);
if (val == Qt::Unchecked) {
BackgroundMusicPlayer::getInstance().stopMusic();
}
});
connect(ui->BGMVolumeSlider, &QSlider::valueChanged, this, [](float val) {
Config::setBGMvolume(val);
BackgroundMusicPlayer::getInstance().setVolume(val);
});
}
// GPU TAB
@ -165,9 +174,6 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
connect(ui->nullGpuCheckBox, &QCheckBox::stateChanged, this,
[](int val) { Config::setNullGpu(val); });
connect(ui->dumpPM4CheckBox, &QCheckBox::stateChanged, this,
[](int val) { Config::setDumpPM4(val); });
}
// DEBUG TAB
@ -206,7 +212,6 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
ui->heightDivider->installEventFilter(this);
ui->dumpShadersCheckBox->installEventFilter(this);
ui->nullGpuCheckBox->installEventFilter(this);
ui->dumpPM4CheckBox->installEventFilter(this);
// Debug
ui->debugDump->installEventFilter(this);
@ -229,8 +234,8 @@ void SettingsDialog::LoadValuesFromConfig() {
ui->vblankSpinBox->setValue(Config::vblankDiv());
ui->dumpShadersCheckBox->setChecked(Config::dumpShaders());
ui->nullGpuCheckBox->setChecked(Config::nullGpu());
ui->dumpPM4CheckBox->setChecked(Config::dumpPM4());
ui->playBGMCheckBox->setChecked(Config::getPlayBGM());
ui->BGMVolumeSlider->setValue((Config::getBGMvolume()));
ui->fullscreenCheckBox->setChecked(Config::isFullscreenMode());
ui->showSplashCheckBox->setChecked(Config::showSplash());
ui->ps4proCheckBox->setChecked(Config::isNeoMode());
@ -371,4 +376,4 @@ bool SettingsDialog::eventFilter(QObject* obj, QEvent* event) {
}
}
return QDialog::eventFilter(obj, event);
}
}

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