file formats and qt (#88)
* added psf file format * clang format fix * crypto functions for pkg decryption * pkg decryption * initial add of qt gui , not yet usable * renamed ini for qt gui settings into shadps4qt.ini * file detection and loader support * option to build QT qui * clang format fix * fixed reuse * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/loader.h Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/loader.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * uppercase fix * clang format fix * small fixes * let's try windows qt build ci * some more fixes for ci * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update .github/workflows/windows-qt.yml Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/loader.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/psf.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * loader namespace * Update src/core/loader.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * constexpr magic * linux qt ci by qurious * fix for linux qt * Make script executable * ci fix? --------- Co-authored-by: raziel1000 <ckraziel@gmail.com> Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> Co-authored-by: GPUCode <geoster3d@gmail.com>
This commit is contained in:
parent
99d013f612
commit
02cbebbf78
|
@ -0,0 +1,24 @@
|
||||||
|
# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [[ -z $GITHUB_WORKSPACE ]]; then
|
||||||
|
GITHUB_WORKSPACE="${PWD%/*}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export PATH="$Qt6_DIR/bin:$PATH"
|
||||||
|
|
||||||
|
# Prepare Tools for building the AppImage
|
||||||
|
wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||||||
|
wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
|
||||||
|
wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh
|
||||||
|
|
||||||
|
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
|
||||||
|
./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
|
|
@ -4,6 +4,6 @@ Exec=shadps4
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Icon=shadps4
|
Icon=shadps4
|
||||||
Comment=gui for shadps4
|
Comment=shadps4 emulator
|
||||||
Categories=Game;
|
Categories=Game;
|
||||||
StartupWMClass=shadps4;
|
StartupWMClass=shadps4;
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
name: Linux-Qt
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||||
|
BUILD_TYPE: Release
|
||||||
|
CLANG_VER: 17
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Fetch submodules
|
||||||
|
run: git submodule update --init --recursive
|
||||||
|
|
||||||
|
- name: Install misc packages
|
||||||
|
run: |
|
||||||
|
sudo apt-get update && sudo apt install libx11-dev libgl1-mesa-glx mesa-common-dev libfuse2 libwayland-dev libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-icccm4 libxcb-image0-dev libxcb-cursor-dev libxxhash-dev libvulkan-dev
|
||||||
|
|
||||||
|
- name: Install newer Clang
|
||||||
|
run: |
|
||||||
|
wget https://apt.llvm.org/llvm.sh
|
||||||
|
chmod +x ./llvm.sh
|
||||||
|
sudo ./llvm.sh ${{env.CLANG_VER}}
|
||||||
|
|
||||||
|
- name: Install Qt
|
||||||
|
uses: jurplel/install-qt-action@v3
|
||||||
|
with:
|
||||||
|
version: 6.6.1
|
||||||
|
host: linux
|
||||||
|
target: desktop
|
||||||
|
#arch: clang++-17
|
||||||
|
dir: ${{ runner.temp }}
|
||||||
|
#modules: qtcharts qt3d
|
||||||
|
setup-python: false
|
||||||
|
|
||||||
|
- name: Configure CMake
|
||||||
|
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-${{env.CLANG_VER}} -DCMAKE_CXX_COMPILER=clang++-${{env.CLANG_VER}} -DENABLE_QT_GUI=ON
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel
|
||||||
|
|
||||||
|
- name: Run AppImage packaging script
|
||||||
|
run: ./.github/linux-appimage-qt.sh
|
||||||
|
|
||||||
|
- name: Upload executable
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: shadps4-linux-qt
|
||||||
|
path: Shadps4-x86_64.AppImage
|
|
@ -0,0 +1,54 @@
|
||||||
|
# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
name: Windows-Qt
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||||
|
BUILD_TYPE: Release
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Setup Qt
|
||||||
|
uses: jurplel/install-qt-action@v3
|
||||||
|
with:
|
||||||
|
arch: win64_msvc2019_64
|
||||||
|
version: 6.6.1
|
||||||
|
|
||||||
|
- name: Configure CMake
|
||||||
|
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||||
|
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||||
|
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL -DENABLE_QT_GUI=ON
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
# Build your program with the given configuration
|
||||||
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
run: |
|
||||||
|
mkdir upload
|
||||||
|
move build/Release/shadps4.exe upload
|
||||||
|
move build/Release/zlib-ng2.dll upload
|
||||||
|
windeployqt --dir upload upload/shadps4.exe
|
||||||
|
|
||||||
|
- name: Upload executable
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: shadps4-win64-qt
|
||||||
|
path: upload
|
|
@ -9,5 +9,7 @@ Files: CMakeSettings.json
|
||||||
.github/shadps4.desktop
|
.github/shadps4.desktop
|
||||||
.github/shadps4.png
|
.github/shadps4.png
|
||||||
.gitmodules
|
.gitmodules
|
||||||
|
src/images/shadps4.ico
|
||||||
|
src/shadps4.rc
|
||||||
Copyright: shadPS4 Emulator Project
|
Copyright: shadPS4 Emulator Project
|
||||||
License: GPL-2.0-or-later
|
License: GPL-2.0-or-later
|
||||||
|
|
203
CMakeLists.txt
203
CMakeLists.txt
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.16.3)
|
cmake_minimum_required(VERSION 3.16.3)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||||
|
|
||||||
if (NOT CMAKE_BUILD_TYPE)
|
if (NOT CMAKE_BUILD_TYPE)
|
||||||
|
@ -12,6 +12,13 @@ endif()
|
||||||
|
|
||||||
project(shadps4)
|
project(shadps4)
|
||||||
|
|
||||||
|
option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF)
|
||||||
|
|
||||||
|
if(ENABLE_QT_GUI)
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent)
|
||||||
|
qt_standard_project_setup()
|
||||||
|
endif()
|
||||||
|
|
||||||
# This function should be passed a list of all files in a target. It will automatically generate
|
# This function should be passed a list of all files in a target. It will automatically generate
|
||||||
# file groups following the directory hierarchy, so that the layout of the files in IDEs matches the
|
# file groups following the directory hierarchy, so that the layout of the files in IDEs matches the
|
||||||
# one in the filesystem.
|
# one in the filesystem.
|
||||||
|
@ -127,44 +134,113 @@ set(HOST_SOURCES src/Emulator/Host/controller.cpp
|
||||||
src/Emulator/Host/controller.h
|
src/Emulator/Host/controller.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# the above is shared in sdl and qt version (TODO share them all)
|
||||||
|
|
||||||
|
if(ENABLE_QT_GUI)
|
||||||
|
set(QT_GUI
|
||||||
|
src/qt_gui/main_window_ui.h
|
||||||
|
src/qt_gui/main_window.cpp
|
||||||
|
src/qt_gui/main_window.h
|
||||||
|
src/qt_gui/gui_settings.cpp
|
||||||
|
src/qt_gui/gui_settings.h
|
||||||
|
src/qt_gui/settings.cpp
|
||||||
|
src/qt_gui/settings.h
|
||||||
|
src/qt_gui/gui_save.h
|
||||||
|
src/qt_gui/custom_dock_widget.h
|
||||||
|
src/qt_gui/custom_table_widget_item.cpp
|
||||||
|
src/qt_gui/custom_table_widget_item.h
|
||||||
|
src/qt_gui/game_list_item.h
|
||||||
|
src/qt_gui/game_list_table.cpp
|
||||||
|
src/qt_gui/game_list_table.h
|
||||||
|
src/qt_gui/game_list_utils.h
|
||||||
|
src/qt_gui/game_info.h
|
||||||
|
src/qt_gui/game_list_grid.cpp
|
||||||
|
src/qt_gui/game_list_grid.h
|
||||||
|
src/qt_gui/game_list_grid_delegate.cpp
|
||||||
|
src/qt_gui/game_list_grid_delegate.h
|
||||||
|
src/qt_gui/game_list_frame.cpp
|
||||||
|
src/qt_gui/game_list_frame.h
|
||||||
|
src/qt_gui/qt_utils.h
|
||||||
|
src/qt_gui/game_install_dialog.cpp
|
||||||
|
src/qt_gui/game_install_dialog.h
|
||||||
|
src/qt_gui/main_window_themes.cpp
|
||||||
|
src/qt_gui/main_window_themes.h
|
||||||
|
src/qt_gui/main.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(COMMON src/common/logging/backend.cpp
|
||||||
|
src/common/logging/backend.h
|
||||||
|
src/common/logging/filter.cpp
|
||||||
|
src/common/logging/filter.h
|
||||||
|
src/common/logging/formatter.h
|
||||||
|
src/common/logging/log_entry.h
|
||||||
|
src/common/logging/log.h
|
||||||
|
src/common/logging/text_formatter.cpp
|
||||||
|
src/common/logging/text_formatter.h
|
||||||
|
src/common/logging/types.h
|
||||||
|
src/common/assert.cpp
|
||||||
|
src/common/assert.h
|
||||||
|
src/common/bounded_threadsafe_queue.h
|
||||||
|
src/common/concepts.h
|
||||||
|
src/common/debug.h
|
||||||
|
src/common/disassembler.cpp
|
||||||
|
src/common/disassembler.h
|
||||||
|
src/common/discord.cpp
|
||||||
|
src/common/discord.h
|
||||||
|
src/common/endian.h
|
||||||
|
src/common/io_file.cpp
|
||||||
|
src/common/io_file.h
|
||||||
|
src/common/error.cpp
|
||||||
|
src/common/error.h
|
||||||
|
src/common/native_clock.cpp
|
||||||
|
src/common/native_clock.h
|
||||||
|
src/common/path_util.cpp
|
||||||
|
src/common/path_util.h
|
||||||
|
src/common/rdtsc.cpp
|
||||||
|
src/common/rdtsc.h
|
||||||
|
src/common/singleton.h
|
||||||
|
src/common/string_util.cpp
|
||||||
|
src/common/string_util.h
|
||||||
|
src/common/thread.cpp
|
||||||
|
src/common/thread.h
|
||||||
|
src/common/types.h
|
||||||
|
src/common/uint128.h
|
||||||
|
src/common/version.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CORE src/core/loader.cpp
|
||||||
|
src/core/loader.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CRYPTO src/core/crypto/crypto.cpp
|
||||||
|
src/core/crypto/crypto.h
|
||||||
|
src/core/crypto/keys.h
|
||||||
|
)
|
||||||
|
set(FILE_FORMAT src/core/file_format/pfs.h
|
||||||
|
src/core/file_format/pkg.cpp
|
||||||
|
src/core/file_format/pkg.h
|
||||||
|
src/core/file_format/pkg_type.cpp
|
||||||
|
src/core/file_format/pkg_type.h
|
||||||
|
src/core/file_format/psf.cpp
|
||||||
|
src/core/file_format/psf.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(UTILITIES src/Util/config.cpp
|
||||||
|
src/Util/config.h
|
||||||
|
)
|
||||||
|
|
||||||
|
if(ENABLE_QT_GUI)
|
||||||
|
qt_add_executable(shadps4
|
||||||
|
${QT_GUI}
|
||||||
|
${COMMON}
|
||||||
|
${CORE}
|
||||||
|
${CRYPTO}
|
||||||
|
${FILE_FORMAT}
|
||||||
|
${UTILITIES}
|
||||||
|
)
|
||||||
|
else()
|
||||||
add_executable(shadps4
|
add_executable(shadps4
|
||||||
src/common/assert.cpp
|
|
||||||
src/common/assert.h
|
|
||||||
src/common/bounded_threadsafe_queue.h
|
|
||||||
src/common/concepts.h
|
|
||||||
src/common/debug.h
|
|
||||||
src/common/disassembler.cpp
|
|
||||||
src/common/disassembler.h
|
|
||||||
src/common/discord.cpp
|
|
||||||
src/common/discord.h
|
|
||||||
src/common/error.cpp
|
|
||||||
src/common/error.h
|
|
||||||
src/common/io_file.cpp
|
|
||||||
src/common/io_file.h
|
|
||||||
src/common/path_util.cpp
|
|
||||||
src/common/path_util.h
|
|
||||||
src/common/logging/backend.cpp
|
|
||||||
src/common/logging/backend.h
|
|
||||||
src/common/logging/filter.cpp
|
|
||||||
src/common/logging/filter.h
|
|
||||||
src/common/logging/formatter.h
|
|
||||||
src/common/logging/log_entry.h
|
|
||||||
src/common/logging/log.h
|
|
||||||
src/common/logging/text_formatter.cpp
|
|
||||||
src/common/logging/text_formatter.h
|
|
||||||
src/common/logging/types.h
|
|
||||||
src/common/native_clock.cpp
|
|
||||||
src/common/native_clock.h
|
|
||||||
src/common/rdtsc.cpp
|
|
||||||
src/common/rdtsc.h
|
|
||||||
src/common/singleton.h
|
|
||||||
src/common/string_util.cpp
|
|
||||||
src/common/string_util.h
|
|
||||||
src/common/thread.cpp
|
|
||||||
src/common/thread.h
|
|
||||||
src/common/types.h
|
|
||||||
src/common/uint128.h
|
|
||||||
src/common/version.h
|
|
||||||
${LIBC_SOURCES}
|
${LIBC_SOURCES}
|
||||||
${USERSERVICE_SOURCES}
|
${USERSERVICE_SOURCES}
|
||||||
${PAD_SOURCES}
|
${PAD_SOURCES}
|
||||||
|
@ -175,8 +251,6 @@ add_executable(shadps4
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/core/loader/elf.cpp
|
src/core/loader/elf.cpp
|
||||||
src/core/loader/elf.h
|
src/core/loader/elf.h
|
||||||
src/Util/config.cpp
|
|
||||||
src/Util/config.h
|
|
||||||
src/core/virtual_memory.cpp
|
src/core/virtual_memory.cpp
|
||||||
src/core/virtual_memory.h
|
src/core/virtual_memory.h
|
||||||
src/core/linker.cpp
|
src/core/linker.cpp
|
||||||
|
@ -227,22 +301,69 @@ add_executable(shadps4
|
||||||
src/core/hle/libraries/libkernel/time_management.h
|
src/core/hle/libraries/libkernel/time_management.h
|
||||||
src/core/tls.cpp
|
src/core/tls.cpp
|
||||||
src/core/tls.h
|
src/core/tls.h
|
||||||
|
${COMMON}
|
||||||
|
${CORE}
|
||||||
|
${CRYPTO}
|
||||||
|
${FILE_FORMAT}
|
||||||
|
${UTILITIES}
|
||||||
)
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
create_target_directory_groups(shadps4)
|
create_target_directory_groups(shadps4)
|
||||||
|
|
||||||
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11)
|
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11)
|
||||||
target_link_libraries(shadps4 PRIVATE discord-rpc SDL3-shared vulkan-1 xxhash Zydis)
|
target_link_libraries(shadps4 PRIVATE discord-rpc vulkan-1 xxhash Zydis)
|
||||||
|
|
||||||
|
if(NOT ENABLE_QT_GUI)
|
||||||
|
target_link_libraries(shadps4 PRIVATE SDL3-shared)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND MSVC)
|
||||||
|
target_link_libraries(shadps4 PRIVATE cryptoppwin zlib)
|
||||||
|
else()
|
||||||
|
target_link_libraries(shadps4 PRIVATE cryptopp::cryptopp zlib)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_QT_GUI)
|
||||||
|
target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_link_libraries(shadps4 PRIVATE mincore winpthread clang_rt.builtins-x86_64.lib)
|
target_link_libraries(shadps4 PRIVATE mincore winpthread clang_rt.builtins-x86_64.lib)
|
||||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
|
add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
|
||||||
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
|
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_sources(shadps4 PRIVATE src/shadps4.rc)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_include_directories(shadps4 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
if(ENABLE_QT_GUI)
|
||||||
|
set_target_properties(shadps4 PROPERTIES
|
||||||
|
WIN32_EXECUTABLE ON
|
||||||
|
MACOSX_BUNDLE ON
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
add_custom_command(TARGET shadps4 POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"${PROJECT_SOURCE_DIR}/externals/zlib-ng-win/bin/zlib-ngd2.dll" $<TARGET_FILE_DIR:shadps4>)
|
||||||
|
else()
|
||||||
|
add_custom_command(TARGET shadps4 POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"${PROJECT_SOURCE_DIR}/externals/zlib-ng-win/bin/zlib-ng2.dll" $<TARGET_FILE_DIR:shadps4>)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT ENABLE_QT_GUI)
|
||||||
add_custom_command(TARGET shadps4 POST_BUILD
|
add_custom_command(TARGET shadps4 POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
$<TARGET_FILE:SDL3-shared>
|
$<TARGET_FILE:SDL3-shared>
|
||||||
$<TARGET_FILE_DIR:shadps4>)
|
$<TARGET_FILE_DIR:shadps4>)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
add_custom_command(TARGET shadps4 POST_BUILD
|
add_custom_command(TARGET shadps4 POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
||||||
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Style guidelines
|
||||||
|
|
||||||
|
## General Rules
|
||||||
|
|
||||||
|
* Line width is typically 100 characters. Please do not use 80-characters.
|
||||||
|
* Don't ever introduce new external dependencies into Core
|
||||||
|
* Don't use any platform specific code in Core
|
||||||
|
* Use namespaces often
|
||||||
|
* Avoid the use of C-style casts and instead prefer C++-style static_cast and reinterpret_cast. Try to avoid using dynamic_cast. Never use const_cast except for when dealing with external const-incorrect APIs.
|
||||||
|
|
||||||
|
## Naming Rules
|
||||||
|
|
||||||
|
* Functions: `PascalCase`
|
||||||
|
* Variables: `lower_case_underscored. Prefix with g_ if global.`
|
||||||
|
* Classes: `PascalCase`
|
||||||
|
* Files and Directories: `lower_case_underscored`
|
||||||
|
* Namespaces: `PascalCase`, `_` may also be used for clarity (e.g. `ARM_InitCore`)
|
||||||
|
|
||||||
|
# Indentation/Whitespace Style
|
||||||
|
|
||||||
|
Follow the indentation/whitespace style shown below. Do not use tabs, use 4-spaces instead.
|
||||||
|
|
||||||
|
# Comments
|
||||||
|
|
||||||
|
* For regular comments, use C++ style (//) comments, even for multi-line ones.
|
||||||
|
* For doc-comments (Doxygen comments), use /// if it's a single line, else use the /** */ style featured in the example. Start the text on the second line, not the first containing /**.
|
||||||
|
* For items that are both defined and declared in two separate files, put the doc-comment only next to the associated declaration. (In a header file, usually.) Otherwise, put it next to the implementation. Never duplicate doc-comments in both places.
|
||||||
|
|
||||||
|
```
|
||||||
|
// Includes should be sorted lexicographically
|
||||||
|
// STD includes first
|
||||||
|
#include <array>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
// then, library includes
|
||||||
|
#include <nihstro/shared_binary.h>
|
||||||
|
|
||||||
|
// finally, shadps4 includes
|
||||||
|
#include "common/math_util.h"
|
||||||
|
#include "common/vector_math.h"
|
||||||
|
|
||||||
|
// each major module is separated
|
||||||
|
#include "video_core/pica.h"
|
||||||
|
#include "video_core/video_core.h"
|
||||||
|
|
||||||
|
namespace Example {
|
||||||
|
|
||||||
|
// Namespace contents are not indented
|
||||||
|
|
||||||
|
// Declare globals at the top (better yet, don't use globals at all!)
|
||||||
|
int g_foo{}; // {} can be used to initialize types as 0, false, or nullptr
|
||||||
|
char* g_some_pointer{}; // Pointer * and reference & stick to the type name, and make sure to initialize as nullptr!
|
||||||
|
|
||||||
|
/// A colorful enum.
|
||||||
|
enum class SomeEnum {
|
||||||
|
Red, ///< The color of fire.
|
||||||
|
Green, ///< The color of grass.
|
||||||
|
Blue, ///< Not actually the color of water.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Very important struct that does a lot of stuff.
|
||||||
|
* Note that the asterisks are indented by one space to align to the first line.
|
||||||
|
*/
|
||||||
|
struct Position {
|
||||||
|
// Always intitialize member variables!
|
||||||
|
int x{};
|
||||||
|
int y{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use "typename" rather than "class" here
|
||||||
|
template <typename T>
|
||||||
|
void FooBar() {
|
||||||
|
const std::string some_string{"prefer uniform initialization"};
|
||||||
|
|
||||||
|
const std::array<int, 4> some_array{
|
||||||
|
5,
|
||||||
|
25,
|
||||||
|
7,
|
||||||
|
42,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (note == the_space_after_the_if) {
|
||||||
|
CallAFunction();
|
||||||
|
} else {
|
||||||
|
// Use a space after the // when commenting
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place a single space after the for loop semicolons, prefer pre-increment
|
||||||
|
for (int i = 0; i != 25; ++i) {
|
||||||
|
// This is how we write loops
|
||||||
|
}
|
||||||
|
|
||||||
|
DoStuff(this, function, call, takes, up, multiple,
|
||||||
|
lines, like, this);
|
||||||
|
|
||||||
|
if (this || condition_takes_up_multiple &&
|
||||||
|
lines && like && this || everything ||
|
||||||
|
alright || then) {
|
||||||
|
|
||||||
|
// Leave a blank space before the if block body if the condition was continued across
|
||||||
|
// several lines.
|
||||||
|
}
|
||||||
|
|
||||||
|
// No indentation for case labels
|
||||||
|
switch (var) {
|
||||||
|
case 1: {
|
||||||
|
const int case_var{var + 3};
|
||||||
|
DoSomething(case_var);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
DoSomething(var);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
// Yes, even break for the last case
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Example
|
||||||
|
```
|
|
@ -11,4 +11,27 @@ set(BUILD_EXAMPLES OFF CACHE BOOL "")
|
||||||
add_subdirectory(discord-rpc EXCLUDE_FROM_ALL)
|
add_subdirectory(discord-rpc EXCLUDE_FROM_ALL)
|
||||||
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
|
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
|
||||||
|
|
||||||
|
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND MSVC)
|
||||||
|
# If it is clang and MSVC we will add a static lib
|
||||||
|
# CryptoPP
|
||||||
|
add_subdirectory(cryptoppwin EXCLUDE_FROM_ALL)
|
||||||
|
target_include_directories(cryptoppwin INTERFACE cryptoppwin/include)
|
||||||
|
|
||||||
|
# Zlib-Ng
|
||||||
|
add_subdirectory(zlib-ng-win EXCLUDE_FROM_ALL)
|
||||||
|
target_include_directories(zlib INTERFACE zlib-ng-win/include)
|
||||||
|
else()
|
||||||
|
# CryptoPP
|
||||||
|
set(CRYPTOPP_BUILD_TESTING OFF)
|
||||||
|
set(CRYPTOPP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/)
|
||||||
|
add_subdirectory(cryptopp-cmake EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
|
# Zlib-Ng
|
||||||
|
set(ZLIB_ENABLE_TESTS OFF)
|
||||||
|
set(WITH_GTEST OFF)
|
||||||
|
set(WITH_NEW_STRATEGIES ON)
|
||||||
|
set(WITH_NATIVE_INSTRUCTIONS ON)
|
||||||
|
add_subdirectory(zlib-ng)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,242 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (c) 2014-2016 Alexandro Sanchez Bach. All rights reserved.
|
||||||
|
* Released under GPL v2 license. Read LICENSE for more details.
|
||||||
|
* Some modifications for using with shadps4 by georgemoralis
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <bit>
|
||||||
|
#include <concepts>
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native endianness
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
using NativeEndian = T;
|
||||||
|
|
||||||
|
template <std::integral T>
|
||||||
|
class SwappedEndian {
|
||||||
|
public:
|
||||||
|
const T& Raw() const {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
T Swap() const {
|
||||||
|
return std::byteswap(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FromRaw(const T& value) {
|
||||||
|
data = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FromSwap(const T& value) {
|
||||||
|
data = std::byteswap(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
operator const T() const {
|
||||||
|
return Swap();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1>
|
||||||
|
explicit operator const SwappedEndian<T1>() const {
|
||||||
|
SwappedEndian<T1> res;
|
||||||
|
if (sizeof(T1) < sizeof(T)) {
|
||||||
|
res.FromRaw(Raw() >> ((sizeof(T) - sizeof(T1)) * 8));
|
||||||
|
} else if (sizeof(T1) > sizeof(T)) {
|
||||||
|
res.FromSwap(Swap());
|
||||||
|
} else {
|
||||||
|
res.FromRaw(Raw());
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
SwappedEndian<T>& operator=(const T& right) {
|
||||||
|
FromSwap(right);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
SwappedEndian<T>& operator=(const SwappedEndian<T>& right) = default;
|
||||||
|
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator+=(T1 right) {
|
||||||
|
return *this = T(*this) + right;
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator-=(T1 right) {
|
||||||
|
return *this = T(*this) - right;
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator*=(T1 right) {
|
||||||
|
return *this = T(*this) * right;
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator/=(T1 right) {
|
||||||
|
return *this = T(*this) / right;
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator%=(T1 right) {
|
||||||
|
return *this = T(*this) % right;
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator&=(T1 right) {
|
||||||
|
return *this = T(*this) & right;
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator|=(T1 right) {
|
||||||
|
return *this = T(*this) | right;
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator^=(T1 right) {
|
||||||
|
return *this = T(*this) ^ right;
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator<<=(T1 right) {
|
||||||
|
return *this = T(*this) << right;
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator>>=(T1 right) {
|
||||||
|
return *this = T(*this) >> right;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator+=(const SwappedEndian<T1>& right) {
|
||||||
|
return *this = Swap() + right.Swap();
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator-=(const SwappedEndian<T1>& right) {
|
||||||
|
return *this = Swap() - right.Swap();
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator*=(const SwappedEndian<T1>& right) {
|
||||||
|
return *this = Swap() * right.Swap();
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator/=(const SwappedEndian<T1>& right) {
|
||||||
|
return *this = Swap() / right.Swap();
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator%=(const SwappedEndian<T1>& right) {
|
||||||
|
return *this = Swap() % right.Swap();
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator&=(const SwappedEndian<T1>& right) {
|
||||||
|
return *this = Raw() & right.Raw();
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator|=(const SwappedEndian<T1>& right) {
|
||||||
|
return *this = Raw() | right.Raw();
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T>& operator^=(const SwappedEndian<T1>& right) {
|
||||||
|
return *this = Raw() ^ right.Raw();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T> operator&(const SwappedEndian<T1>& right) const {
|
||||||
|
return SwappedEndian<T>{Raw() & right.Raw()};
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T> operator|(const SwappedEndian<T1>& right) const {
|
||||||
|
return SwappedEndian<T>{Raw() | right.Raw()};
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
SwappedEndian<T> operator^(const SwappedEndian<T1>& right) const {
|
||||||
|
return SwappedEndian<T>{Raw() ^ right.Raw()};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1>
|
||||||
|
bool operator==(T1 right) const {
|
||||||
|
return (T1)Swap() == right;
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
bool operator!=(T1 right) const {
|
||||||
|
return !(*this == right);
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
bool operator>(T1 right) const {
|
||||||
|
return (T1)Swap() > right;
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
bool operator<(T1 right) const {
|
||||||
|
return (T1)Swap() < right;
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
bool operator>=(T1 right) const {
|
||||||
|
return (T1)Swap() >= right;
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
bool operator<=(T1 right) const {
|
||||||
|
return (T1)Swap() <= right;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1>
|
||||||
|
bool operator==(const SwappedEndian<T1>& right) const {
|
||||||
|
return Raw() == right.Raw();
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
bool operator!=(const SwappedEndian<T1>& right) const {
|
||||||
|
return !(*this == right);
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
bool operator>(const SwappedEndian<T1>& right) const {
|
||||||
|
return (T1)Swap() > right.Swap();
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
bool operator<(const SwappedEndian<T1>& right) const {
|
||||||
|
return (T1)Swap() < right.Swap();
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
bool operator>=(const SwappedEndian<T1>& right) const {
|
||||||
|
return (T1)Swap() >= right.Swap();
|
||||||
|
}
|
||||||
|
template <typename T1>
|
||||||
|
bool operator<=(const SwappedEndian<T1>& right) const {
|
||||||
|
return (T1)Swap() <= right.Swap();
|
||||||
|
}
|
||||||
|
|
||||||
|
SwappedEndian<T> operator++(int) {
|
||||||
|
SwappedEndian<T> res = *this;
|
||||||
|
*this += 1;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
SwappedEndian<T> operator--(int) {
|
||||||
|
SwappedEndian<T> res = *this;
|
||||||
|
*this -= 1;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
SwappedEndian<T>& operator++() {
|
||||||
|
*this += 1;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
SwappedEndian<T>& operator--() {
|
||||||
|
*this -= 1;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T data;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using LittleEndian = std::conditional_t<std::endian::native == std::endian::little, NativeEndian<T>,
|
||||||
|
SwappedEndian<T>>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using BigEndian =
|
||||||
|
std::conditional_t<std::endian::native == std::endian::big, NativeEndian<T>, SwappedEndian<T>>;
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
|
|
||||||
|
using u16_be = Common::BigEndian<u16>;
|
||||||
|
using u32_be = Common::BigEndian<u32>;
|
||||||
|
using u64_be = Common::BigEndian<u64>;
|
||||||
|
|
||||||
|
using u16_le = Common::LittleEndian<u16>;
|
||||||
|
using u32_le = Common::LittleEndian<u32>;
|
||||||
|
using u64_le = Common::LittleEndian<u64>;
|
|
@ -178,6 +178,11 @@ public:
|
||||||
return std::fread(&object, sizeof(T), 1, file) == 1;
|
return std::fread(&object, sizeof(T), 1, file) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
size_t WriteRaw(void* data, size_t size) const {
|
||||||
|
return std::fwrite(data, sizeof(T), size, file);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
bool WriteObject(const T& object) const {
|
bool WriteObject(const T& object) const {
|
||||||
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
|
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include "crypto.h"
|
||||||
|
|
||||||
|
RSA::PrivateKey Crypto::key_pkg_derived_key3_keyset_init() {
|
||||||
|
InvertibleRSAFunction params;
|
||||||
|
params.SetPrime1(Integer(pkg_derived_key3_keyset.Prime1, 0x80));
|
||||||
|
params.SetPrime2(Integer(pkg_derived_key3_keyset.Prime2, 0x80));
|
||||||
|
|
||||||
|
params.SetPublicExponent(Integer(pkg_derived_key3_keyset.PublicExponent, 4));
|
||||||
|
params.SetPrivateExponent(Integer(pkg_derived_key3_keyset.PrivateExponent, 0x100));
|
||||||
|
|
||||||
|
params.SetModPrime1PrivateExponent(Integer(pkg_derived_key3_keyset.Exponent1, 0x80));
|
||||||
|
params.SetModPrime2PrivateExponent(Integer(pkg_derived_key3_keyset.Exponent2, 0x80));
|
||||||
|
|
||||||
|
params.SetModulus(Integer(pkg_derived_key3_keyset.Modulus, 0x100));
|
||||||
|
params.SetMultiplicativeInverseOfPrime2ModPrime1(
|
||||||
|
Integer(pkg_derived_key3_keyset.Coefficient, 0x80));
|
||||||
|
|
||||||
|
RSA::PrivateKey privateKey(params);
|
||||||
|
|
||||||
|
return privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
RSA::PrivateKey Crypto::FakeKeyset_keyset_init() {
|
||||||
|
InvertibleRSAFunction params;
|
||||||
|
params.SetPrime1(Integer(FakeKeyset_keyset.Prime1, 0x80));
|
||||||
|
params.SetPrime2(Integer(FakeKeyset_keyset.Prime2, 0x80));
|
||||||
|
|
||||||
|
params.SetPublicExponent(Integer(FakeKeyset_keyset.PublicExponent, 4));
|
||||||
|
params.SetPrivateExponent(Integer(FakeKeyset_keyset.PrivateExponent, 0x100));
|
||||||
|
|
||||||
|
params.SetModPrime1PrivateExponent(Integer(FakeKeyset_keyset.Exponent1, 0x80));
|
||||||
|
params.SetModPrime2PrivateExponent(Integer(FakeKeyset_keyset.Exponent2, 0x80));
|
||||||
|
|
||||||
|
params.SetModulus(Integer(FakeKeyset_keyset.Modulus, 0x100));
|
||||||
|
params.SetMultiplicativeInverseOfPrime2ModPrime1(Integer(FakeKeyset_keyset.Coefficient, 0x80));
|
||||||
|
|
||||||
|
RSA::PrivateKey privateKey(params);
|
||||||
|
|
||||||
|
return privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
RSA::PrivateKey Crypto::DebugRifKeyset_init() {
|
||||||
|
AutoSeededRandomPool rng;
|
||||||
|
InvertibleRSAFunction params;
|
||||||
|
params.SetPrime1(Integer(DebugRifKeyset_keyset.Prime1, sizeof(DebugRifKeyset_keyset.Prime1)));
|
||||||
|
params.SetPrime2(Integer(DebugRifKeyset_keyset.Prime2, sizeof(DebugRifKeyset_keyset.Prime2)));
|
||||||
|
|
||||||
|
params.SetPublicExponent(Integer(DebugRifKeyset_keyset.PrivateExponent,
|
||||||
|
sizeof(DebugRifKeyset_keyset.PrivateExponent)));
|
||||||
|
params.SetPrivateExponent(Integer(DebugRifKeyset_keyset.PrivateExponent,
|
||||||
|
sizeof(DebugRifKeyset_keyset.PrivateExponent)));
|
||||||
|
|
||||||
|
params.SetModPrime1PrivateExponent(
|
||||||
|
Integer(DebugRifKeyset_keyset.Exponent1, sizeof(DebugRifKeyset_keyset.Exponent1)));
|
||||||
|
params.SetModPrime2PrivateExponent(
|
||||||
|
Integer(DebugRifKeyset_keyset.Exponent2, sizeof(DebugRifKeyset_keyset.Exponent2)));
|
||||||
|
|
||||||
|
params.SetModulus(
|
||||||
|
Integer(DebugRifKeyset_keyset.Modulus, sizeof(DebugRifKeyset_keyset.Modulus)));
|
||||||
|
params.SetMultiplicativeInverseOfPrime2ModPrime1(
|
||||||
|
Integer(DebugRifKeyset_keyset.Coefficient, sizeof(DebugRifKeyset_keyset.Coefficient)));
|
||||||
|
|
||||||
|
RSA::PrivateKey privateKey(params);
|
||||||
|
|
||||||
|
return privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Crypto::RSA2048Decrypt(std::span<CryptoPP::byte, 32> dec_key,
|
||||||
|
std::span<const CryptoPP::byte, 256> ciphertext,
|
||||||
|
bool is_dk3) { // RSAES_PKCS1v15_
|
||||||
|
// Create an RSA decryptor
|
||||||
|
RSA::PrivateKey privateKey;
|
||||||
|
if (is_dk3) {
|
||||||
|
privateKey = key_pkg_derived_key3_keyset_init();
|
||||||
|
} else {
|
||||||
|
privateKey = FakeKeyset_keyset_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
RSAES_PKCS1v15_Decryptor rsaDecryptor(privateKey);
|
||||||
|
|
||||||
|
// Allocate memory for the decrypted data
|
||||||
|
std::array<CryptoPP::byte, 256> decrypted;
|
||||||
|
|
||||||
|
// Perform the decryption
|
||||||
|
AutoSeededRandomPool rng;
|
||||||
|
DecodingResult result =
|
||||||
|
rsaDecryptor.Decrypt(rng, ciphertext.data(), decrypted.size(), decrypted.data());
|
||||||
|
std::copy(decrypted.begin(), decrypted.begin() + dec_key.size(), dec_key.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Crypto::ivKeyHASH256(std::span<const CryptoPP::byte, 64> cipher_input,
|
||||||
|
std::span<CryptoPP::byte, 32> ivkey_result) {
|
||||||
|
CryptoPP::SHA256 sha256;
|
||||||
|
std::array<CryptoPP::byte, CryptoPP::SHA256::DIGESTSIZE> hashResult;
|
||||||
|
auto array_sink = new CryptoPP::ArraySink(hashResult.data(), CryptoPP::SHA256::DIGESTSIZE);
|
||||||
|
auto filter = new CryptoPP::HashFilter(sha256, array_sink);
|
||||||
|
CryptoPP::ArraySource r(cipher_input.data(), cipher_input.size(), true, filter);
|
||||||
|
std::copy(hashResult.begin(), hashResult.begin() + ivkey_result.size(), ivkey_result.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Crypto::aesCbcCfb128Decrypt(std::span<const CryptoPP::byte, 32> ivkey,
|
||||||
|
std::span<const CryptoPP::byte, 256> ciphertext,
|
||||||
|
std::span<CryptoPP::byte, 256> decrypted) {
|
||||||
|
std::array<CryptoPP::byte, CryptoPP::AES::DEFAULT_KEYLENGTH> key;
|
||||||
|
std::array<CryptoPP::byte, CryptoPP::AES::DEFAULT_KEYLENGTH> iv;
|
||||||
|
|
||||||
|
std::copy(ivkey.begin() + 16, ivkey.begin() + 16 + key.size(), key.begin());
|
||||||
|
std::copy(ivkey.begin(), ivkey.begin() + iv.size(), iv.begin());
|
||||||
|
|
||||||
|
CryptoPP::AES::Decryption aesDecryption(key.data(), CryptoPP::AES::DEFAULT_KEYLENGTH);
|
||||||
|
CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, iv.data());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < decrypted.size(); i += CryptoPP::AES::BLOCKSIZE) {
|
||||||
|
cbcDecryption.ProcessData(decrypted.data() + i, ciphertext.data() + i,
|
||||||
|
CryptoPP::AES::BLOCKSIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Crypto::PfsGenCryptoKey(std::span<const CryptoPP::byte, 32> ekpfs,
|
||||||
|
std::span<const CryptoPP::byte, 16> seed,
|
||||||
|
std::span<CryptoPP::byte, 16> dataKey,
|
||||||
|
std::span<CryptoPP::byte, 16> tweakKey) {
|
||||||
|
CryptoPP::HMAC<CryptoPP::SHA256> hmac(ekpfs.data(), ekpfs.size());
|
||||||
|
|
||||||
|
CryptoPP::SecByteBlock d(20); // Use Crypto++ SecByteBlock for better memory management
|
||||||
|
|
||||||
|
// Copy the bytes of 'index' to the 'd' array
|
||||||
|
uint32_t index = 1;
|
||||||
|
std::memcpy(d, &index, sizeof(uint32_t));
|
||||||
|
|
||||||
|
// Copy the bytes of 'seed' to the 'd' array starting from index 4
|
||||||
|
std::memcpy(d + sizeof(uint32_t), seed.data(), seed.size());
|
||||||
|
|
||||||
|
// Allocate memory for 'u64' using new
|
||||||
|
std::vector<CryptoPP::byte> data_tweak_key(hmac.DigestSize());
|
||||||
|
|
||||||
|
// Calculate the HMAC
|
||||||
|
hmac.CalculateDigest(data_tweak_key.data(), d, d.size());
|
||||||
|
std::copy(data_tweak_key.begin(), data_tweak_key.begin() + dataKey.size(), tweakKey.begin());
|
||||||
|
std::copy(data_tweak_key.begin() + tweakKey.size(),
|
||||||
|
data_tweak_key.begin() + tweakKey.size() + dataKey.size(), dataKey.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Crypto::decryptPFS(std::span<const CryptoPP::byte, 16> dataKey,
|
||||||
|
std::span<const CryptoPP::byte, 16> tweakKey, std::span<const u8> src_image,
|
||||||
|
std::span<CryptoPP::byte> dst_image, u64 sector) {
|
||||||
|
// Start at 0x10000 to keep the header when decrypting the whole pfs_image.
|
||||||
|
for (int i = 0; i < src_image.size(); i += 0x1000) {
|
||||||
|
const u64 current_sector = sector + (i / 0x1000);
|
||||||
|
CryptoPP::ECB_Mode<AES>::Encryption encrypt(tweakKey.data(), tweakKey.size());
|
||||||
|
CryptoPP::ECB_Mode<AES>::Decryption decrypt(dataKey.data(), dataKey.size());
|
||||||
|
|
||||||
|
std::array<CryptoPP::byte, 16> tweak{};
|
||||||
|
std::array<CryptoPP::byte, 16> encryptedTweak;
|
||||||
|
std::array<CryptoPP::byte, 16> xorBuffer;
|
||||||
|
std::memcpy(tweak.data(), ¤t_sector, sizeof(u64));
|
||||||
|
|
||||||
|
// Encrypt the tweak for each sector.
|
||||||
|
encrypt.ProcessData(encryptedTweak.data(), tweak.data(), 16);
|
||||||
|
|
||||||
|
for (int plaintextOffset = 0; plaintextOffset < 0x1000; plaintextOffset += 16) {
|
||||||
|
xtsXorBlock(xorBuffer.data(), src_image.data() + i + plaintextOffset,
|
||||||
|
encryptedTweak.data()); // x, c, t
|
||||||
|
decrypt.ProcessData(xorBuffer.data(), xorBuffer.data(), 16); // x, x
|
||||||
|
xtsXorBlock(dst_image.data() + i + plaintextOffset, xorBuffer.data(),
|
||||||
|
encryptedTweak.data()); //(p) c, x , t
|
||||||
|
xtsMult(encryptedTweak);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include <aes.h>
|
||||||
|
#include <filters.h>
|
||||||
|
#include <modes.h>
|
||||||
|
#include <oaep.h>
|
||||||
|
#include <osrng.h>
|
||||||
|
#include <rsa.h>
|
||||||
|
#include <sha.h>
|
||||||
|
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "keys.h"
|
||||||
|
|
||||||
|
using namespace CryptoPP;
|
||||||
|
|
||||||
|
class Crypto {
|
||||||
|
public:
|
||||||
|
PkgDerivedKey3Keyset pkg_derived_key3_keyset;
|
||||||
|
FakeKeyset FakeKeyset_keyset;
|
||||||
|
DebugRifKeyset DebugRifKeyset_keyset;
|
||||||
|
|
||||||
|
RSA::PrivateKey key_pkg_derived_key3_keyset_init();
|
||||||
|
RSA::PrivateKey FakeKeyset_keyset_init();
|
||||||
|
RSA::PrivateKey DebugRifKeyset_init();
|
||||||
|
|
||||||
|
void RSA2048Decrypt(std::span<CryptoPP::byte, 32> dk3,
|
||||||
|
std::span<const CryptoPP::byte, 256> ciphertext,
|
||||||
|
bool is_dk3); // RSAES_PKCS1v15_
|
||||||
|
void ivKeyHASH256(std::span<const CryptoPP::byte, 64> cipher_input,
|
||||||
|
std::span<CryptoPP::byte, 32> ivkey_result);
|
||||||
|
void aesCbcCfb128Decrypt(std::span<const CryptoPP::byte, 32> ivkey,
|
||||||
|
std::span<const CryptoPP::byte, 256> ciphertext,
|
||||||
|
std::span<CryptoPP::byte, 256> decrypted);
|
||||||
|
void PfsGenCryptoKey(std::span<const CryptoPP::byte, 32> ekpfs,
|
||||||
|
std::span<const CryptoPP::byte, 16> seed,
|
||||||
|
std::span<CryptoPP::byte, 16> dataKey,
|
||||||
|
std::span<CryptoPP::byte, 16> tweakKey);
|
||||||
|
void decryptPFS(std::span<const CryptoPP::byte, 16> dataKey,
|
||||||
|
std::span<const CryptoPP::byte, 16> tweakKey, std::span<const u8> src_image,
|
||||||
|
std::span<CryptoPP::byte> dst_image, u64 sector);
|
||||||
|
|
||||||
|
void xtsXorBlock(CryptoPP::byte* x, const CryptoPP::byte* a, const CryptoPP::byte* b) {
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
x[i] = a[i] ^ b[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void xtsMult(std::span<CryptoPP::byte, 16> encryptedTweak) {
|
||||||
|
int feedback = 0;
|
||||||
|
for (int k = 0; k < encryptedTweak.size(); k++) {
|
||||||
|
const auto tmp = (encryptedTweak[k] >> 7) & 1;
|
||||||
|
encryptedTweak[k] = ((encryptedTweak[k] << 1) + feedback) & 0xFF;
|
||||||
|
feedback = tmp;
|
||||||
|
}
|
||||||
|
if (feedback != 0) {
|
||||||
|
encryptedTweak[0] ^= 0x87;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,389 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
class FakeKeyset {
|
||||||
|
public:
|
||||||
|
// Constructor
|
||||||
|
const CryptoPP::byte* Exponent1;
|
||||||
|
// exponent2 = d mod (q - 1)
|
||||||
|
const CryptoPP::byte* Exponent2;
|
||||||
|
// e
|
||||||
|
const CryptoPP::byte* PublicExponent;
|
||||||
|
// (InverseQ)(q) = 1 mod p
|
||||||
|
const CryptoPP::byte* Coefficient;
|
||||||
|
// n = p * q
|
||||||
|
const CryptoPP::byte* Modulus;
|
||||||
|
// p
|
||||||
|
const CryptoPP::byte* Prime1;
|
||||||
|
// q
|
||||||
|
const CryptoPP::byte* Prime2;
|
||||||
|
const CryptoPP::byte* PrivateExponent;
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
FakeKeyset() {
|
||||||
|
// Initialize PrivateExponent
|
||||||
|
PrivateExponent = new CryptoPP::byte[0x100]{
|
||||||
|
0x7F, 0x76, 0xCD, 0x0E, 0xE2, 0xD4, 0xDE, 0x05, 0x1C, 0xC6, 0xD9, 0xA8, 0x0E, 0x8D,
|
||||||
|
0xFA, 0x7B, 0xCA, 0x1E, 0xAA, 0x27, 0x1A, 0x40, 0xF8, 0xF1, 0x22, 0x87, 0x35, 0xDD,
|
||||||
|
0xDB, 0xFD, 0xEE, 0xF8, 0xC2, 0xBC, 0xBD, 0x01, 0xFB, 0x8B, 0xE2, 0x3E, 0x63, 0xB2,
|
||||||
|
0xB1, 0x22, 0x5C, 0x56, 0x49, 0x6E, 0x11, 0xBE, 0x07, 0x44, 0x0B, 0x9A, 0x26, 0x66,
|
||||||
|
0xD1, 0x49, 0x2C, 0x8F, 0xD3, 0x1B, 0xCF, 0xA4, 0xA1, 0xB8, 0xD1, 0xFB, 0xA4, 0x9E,
|
||||||
|
0xD2, 0x21, 0x28, 0x83, 0x09, 0x8A, 0xF6, 0xA0, 0x0B, 0xA3, 0xD6, 0x0F, 0x9B, 0x63,
|
||||||
|
0x68, 0xCC, 0xBC, 0x0C, 0x4E, 0x14, 0x5B, 0x27, 0xA4, 0xA9, 0xF4, 0x2B, 0xB9, 0xB8,
|
||||||
|
0x7B, 0xC0, 0xE6, 0x51, 0xAD, 0x1D, 0x77, 0xD4, 0x6B, 0xB9, 0xCE, 0x20, 0xD1, 0x26,
|
||||||
|
0x66, 0x7E, 0x5E, 0x9E, 0xA2, 0xE9, 0x6B, 0x90, 0xF3, 0x73, 0xB8, 0x52, 0x8F, 0x44,
|
||||||
|
0x11, 0x03, 0x0C, 0x13, 0x97, 0x39, 0x3D, 0x13, 0x22, 0x58, 0xD5, 0x43, 0x82, 0x49,
|
||||||
|
0xDA, 0x6E, 0x7C, 0xA1, 0xC5, 0x8C, 0xA5, 0xB0, 0x09, 0xE0, 0xCE, 0x3D, 0xDF, 0xF4,
|
||||||
|
0x9D, 0x3C, 0x97, 0x15, 0xE2, 0x6A, 0xC7, 0x2B, 0x3C, 0x50, 0x93, 0x23, 0xDB, 0xBA,
|
||||||
|
0x4A, 0x22, 0x66, 0x44, 0xAC, 0x78, 0xBB, 0x0E, 0x1A, 0x27, 0x43, 0xB5, 0x71, 0x67,
|
||||||
|
0xAF, 0xF4, 0xAB, 0x48, 0x46, 0x93, 0x73, 0xD0, 0x42, 0xAB, 0x93, 0x63, 0xE5, 0x6C,
|
||||||
|
0x9A, 0xDE, 0x50, 0x24, 0xC0, 0x23, 0x7D, 0x99, 0x79, 0x3F, 0x22, 0x07, 0xE0, 0xC1,
|
||||||
|
0x48, 0x56, 0x1B, 0xDF, 0x83, 0x09, 0x12, 0xB4, 0x2D, 0x45, 0x6B, 0xC9, 0xC0, 0x68,
|
||||||
|
0x85, 0x99, 0x90, 0x79, 0x96, 0x1A, 0xD7, 0xF5, 0x4D, 0x1F, 0x37, 0x83, 0x40, 0x4A,
|
||||||
|
0xEC, 0x39, 0x37, 0xA6, 0x80, 0x92, 0x7D, 0xC5, 0x80, 0xC7, 0xD6, 0x6F, 0xFE, 0x8A,
|
||||||
|
0x79, 0x89, 0xC6, 0xB1};
|
||||||
|
|
||||||
|
// Initialize Exponent1
|
||||||
|
Exponent1 = new CryptoPP::byte[0x80]{
|
||||||
|
0x6D, 0x48, 0xE0, 0x54, 0x40, 0x25, 0xC8, 0x41, 0x29, 0x52, 0x42, 0x27, 0xEB,
|
||||||
|
0xD2, 0xC7, 0xAB, 0x6B, 0x9C, 0x27, 0x0A, 0xB4, 0x1F, 0x94, 0x4E, 0xFA, 0x42,
|
||||||
|
0x1D, 0xB7, 0xBC, 0xB9, 0xAE, 0xBC, 0x04, 0x6F, 0x75, 0x8F, 0x10, 0x5F, 0x89,
|
||||||
|
0xAC, 0xAB, 0x9C, 0xD2, 0xFA, 0xE6, 0xA4, 0x13, 0x83, 0x68, 0xD4, 0x56, 0x38,
|
||||||
|
0xFE, 0xE5, 0x2B, 0x78, 0x44, 0x9C, 0x34, 0xE6, 0x5A, 0xA0, 0xBE, 0x05, 0x70,
|
||||||
|
0xAD, 0x15, 0xC3, 0x2D, 0x31, 0xAC, 0x97, 0x5D, 0x88, 0xFC, 0xC1, 0x62, 0x3D,
|
||||||
|
0xE2, 0xED, 0x11, 0xDB, 0xB6, 0x9E, 0xFC, 0x5A, 0x5A, 0x03, 0xF6, 0xCF, 0x08,
|
||||||
|
0xD4, 0x5D, 0x90, 0xC9, 0x2A, 0xB9, 0x9B, 0xCF, 0xC8, 0x1A, 0x65, 0xF3, 0x5B,
|
||||||
|
0xE8, 0x7F, 0xCF, 0xA5, 0xA6, 0x4C, 0x5C, 0x2A, 0x12, 0x0F, 0x92, 0xA5, 0xE3,
|
||||||
|
0xF0, 0x17, 0x1E, 0x9A, 0x97, 0x45, 0x86, 0xFD, 0xDB, 0x54, 0x25
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Exponent2 = new CryptoPP::byte[0x80]{
|
||||||
|
0x2A, 0x51, 0xCE, 0x02, 0x44, 0x28, 0x50, 0xE8, 0x30, 0x20, 0x7C, 0x9C, 0x55,
|
||||||
|
0xBF, 0x60, 0x39, 0xBC, 0xD1, 0xF0, 0xE7, 0x68, 0xF8, 0x08, 0x5B, 0x61, 0x1F,
|
||||||
|
0xA7, 0xBF, 0xD0, 0xE8, 0x8B, 0xB5, 0xB1, 0xD5, 0xD9, 0x16, 0xAC, 0x75, 0x0C,
|
||||||
|
0x6D, 0xF2, 0xE0, 0xB5, 0x97, 0x75, 0xD2, 0x68, 0x16, 0x1F, 0x00, 0x7D, 0x8B,
|
||||||
|
0x17, 0xE8, 0x78, 0x48, 0x41, 0x71, 0x2B, 0x18, 0x96, 0x80, 0x11, 0xDB, 0x68,
|
||||||
|
0x39, 0x9C, 0xD6, 0xE0, 0x72, 0x42, 0x86, 0xF0, 0x1B, 0x16, 0x0D, 0x3E, 0x12,
|
||||||
|
0x94, 0x3D, 0x25, 0xA8, 0xA9, 0x30, 0x9E, 0x54, 0x5A, 0xD6, 0x36, 0x6C, 0xD6,
|
||||||
|
0x8C, 0x20, 0x62, 0x8F, 0xA1, 0x6B, 0x1F, 0x7C, 0x6D, 0xB2, 0xB1, 0xC1, 0x2E,
|
||||||
|
0xAD, 0x36, 0x02, 0x9C, 0x3A, 0xCA, 0x2F, 0x09, 0xD2, 0x45, 0x9E, 0xEB, 0xF2,
|
||||||
|
0xBC, 0x6C, 0xAA, 0x3B, 0x3E, 0x90, 0xBC, 0x38, 0x67, 0x35, 0x4D};
|
||||||
|
|
||||||
|
PublicExponent = new CryptoPP::byte[4]{0, 1, 0, 1};
|
||||||
|
|
||||||
|
Coefficient = new CryptoPP::byte[0x80]{
|
||||||
|
0x0B, 0x67, 0x1C, 0x0D, 0x6C, 0x57, 0xD3, 0xE7, 0x05, 0x65, 0x94, 0x31, 0x56,
|
||||||
|
0x55, 0xFD, 0x28, 0x08, 0xFA, 0x05, 0x8A, 0xCC, 0x55, 0x39, 0x61, 0x97, 0x63,
|
||||||
|
0xA0, 0x16, 0x27, 0x3D, 0xED, 0xC1, 0x16, 0x40, 0x2A, 0x12, 0xEA, 0x6F, 0xD9,
|
||||||
|
0xD8, 0x58, 0x56, 0xA8, 0x56, 0x8B, 0x0D, 0x38, 0x5E, 0x1E, 0x80, 0x3B, 0x5F,
|
||||||
|
0x40, 0x80, 0x6F, 0x62, 0x4F, 0x28, 0xA2, 0x69, 0xF3, 0xD3, 0xF7, 0xFD, 0xB2,
|
||||||
|
0xC3, 0x52, 0x43, 0x20, 0x92, 0x9D, 0x97, 0x8D, 0xA0, 0x15, 0x07, 0x15, 0x6E,
|
||||||
|
0xA4, 0x0D, 0x56, 0xD3, 0x37, 0x1A, 0xC4, 0x9E, 0xDF, 0x02, 0x49, 0xB8, 0x0A,
|
||||||
|
0x84, 0x62, 0xF5, 0xFA, 0xB9, 0x3F, 0xA4, 0x09, 0x76, 0xCC, 0xAA, 0xB9, 0x9B,
|
||||||
|
0xA6, 0x4F, 0xC1, 0x6A, 0x64, 0xCE, 0xD8, 0x77, 0xAB, 0x4B, 0xF9, 0xA0, 0xAE,
|
||||||
|
0xDA, 0xF1, 0x67, 0x87, 0x7C, 0x98, 0x5C, 0x7E, 0xB8, 0x73, 0xF5};
|
||||||
|
|
||||||
|
Modulus = new CryptoPP::byte[0x100]{
|
||||||
|
0xC6, 0xCF, 0x71, 0xE7, 0xE5, 0x9A, 0xF0, 0xD1, 0x2A, 0x2C, 0x45, 0x8B, 0xF9, 0x2A,
|
||||||
|
0x0E, 0xC1, 0x43, 0x05, 0x8B, 0xC3, 0x71, 0x17, 0x80, 0x1D, 0xCD, 0x49, 0x7D, 0xDE,
|
||||||
|
0x35, 0x9D, 0x25, 0x9B, 0xA0, 0xD7, 0xA0, 0xF2, 0x7D, 0x6C, 0x08, 0x7E, 0xAA, 0x55,
|
||||||
|
0x02, 0x68, 0x2B, 0x23, 0xC6, 0x44, 0xB8, 0x44, 0x18, 0xEB, 0x56, 0xCF, 0x16, 0xA2,
|
||||||
|
0x48, 0x03, 0xC9, 0xE7, 0x4F, 0x87, 0xEB, 0x3D, 0x30, 0xC3, 0x15, 0x88, 0xBF, 0x20,
|
||||||
|
0xE7, 0x9D, 0xFF, 0x77, 0x0C, 0xDE, 0x1D, 0x24, 0x1E, 0x63, 0xA9, 0x4F, 0x8A, 0xBF,
|
||||||
|
0x5B, 0xBE, 0x60, 0x19, 0x68, 0x33, 0x3B, 0xFC, 0xED, 0x9F, 0x47, 0x4E, 0x5F, 0xF8,
|
||||||
|
0xEA, 0xCB, 0x3D, 0x00, 0xBD, 0x67, 0x01, 0xF9, 0x2C, 0x6D, 0xC6, 0xAC, 0x13, 0x64,
|
||||||
|
0xE7, 0x67, 0x14, 0xF3, 0xDC, 0x52, 0x69, 0x6A, 0xB9, 0x83, 0x2C, 0x42, 0x30, 0x13,
|
||||||
|
0x1B, 0xB2, 0xD8, 0xA5, 0x02, 0x0D, 0x79, 0xED, 0x96, 0xB1, 0x0D, 0xF8, 0xCC, 0x0C,
|
||||||
|
0xDF, 0x81, 0x95, 0x4F, 0x03, 0x58, 0x09, 0x57, 0x0E, 0x80, 0x69, 0x2E, 0xFE, 0xFF,
|
||||||
|
0x52, 0x77, 0xEA, 0x75, 0x28, 0xA8, 0xFB, 0xC9, 0xBE, 0xBF, 0x9F, 0xBB, 0xB7, 0x79,
|
||||||
|
0x8E, 0x18, 0x05, 0xE1, 0x80, 0xBD, 0x50, 0x34, 0x94, 0x81, 0xD3, 0x53, 0xC2, 0x69,
|
||||||
|
0xA2, 0xD2, 0x4C, 0xCF, 0x6C, 0xF4, 0x57, 0x2C, 0x10, 0x4A, 0x3F, 0xFB, 0x22, 0xFD,
|
||||||
|
0x8B, 0x97, 0xE2, 0xC9, 0x5B, 0xA6, 0x2B, 0xCD, 0xD6, 0x1B, 0x6B, 0xDB, 0x68, 0x7F,
|
||||||
|
0x4B, 0xC2, 0xA0, 0x50, 0x34, 0xC0, 0x05, 0xE5, 0x8D, 0xEF, 0x24, 0x67, 0xFF, 0x93,
|
||||||
|
0x40, 0xCF, 0x2D, 0x62, 0xA2, 0xA0, 0x50, 0xB1, 0xF1, 0x3A, 0xA8, 0x3D, 0xFD, 0x80,
|
||||||
|
0xD1, 0xF9, 0xB8, 0x05, 0x22, 0xAF, 0xC8, 0x35, 0x45, 0x90, 0x58, 0x8E, 0xE3, 0x3A,
|
||||||
|
0x7C, 0xBD, 0x3E, 0x27};
|
||||||
|
|
||||||
|
Prime1 = new CryptoPP::byte[0x80]{
|
||||||
|
0xFE, 0xF6, 0xBF, 0x1D, 0x69, 0xAB, 0x16, 0x25, 0x08, 0x47, 0x55, 0x6B, 0x86,
|
||||||
|
0xE4, 0x35, 0x88, 0x72, 0x2A, 0xB1, 0x3D, 0xF8, 0xB6, 0x44, 0xCA, 0xB3, 0xAB,
|
||||||
|
0x19, 0xD1, 0x04, 0x24, 0x28, 0x0A, 0x74, 0x55, 0xB8, 0x15, 0x45, 0x09, 0xCC,
|
||||||
|
0x13, 0x1C, 0xF2, 0xBA, 0x37, 0xA9, 0x03, 0x90, 0x8F, 0x02, 0x10, 0xFF, 0x25,
|
||||||
|
0x79, 0x86, 0xCC, 0x18, 0x50, 0x9A, 0x10, 0x5F, 0x5B, 0x4C, 0x1C, 0x4E, 0xB0,
|
||||||
|
0xA7, 0xE3, 0x59, 0xB1, 0x2D, 0xA0, 0xC6, 0xB0, 0x20, 0x2C, 0x21, 0x33, 0x12,
|
||||||
|
0xB3, 0xAF, 0x72, 0x34, 0x83, 0xCD, 0x52, 0x2F, 0xAF, 0x0F, 0x20, 0x5A, 0x1B,
|
||||||
|
0xC0, 0xE2, 0xA3, 0x76, 0x34, 0x0F, 0xD7, 0xFC, 0xC1, 0x41, 0xC9, 0xF9, 0x79,
|
||||||
|
0x40, 0x17, 0x42, 0x21, 0x3E, 0x9D, 0xFD, 0xC7, 0xC1, 0x50, 0xDE, 0x44, 0x5A,
|
||||||
|
0xC9, 0x31, 0x89, 0x6A, 0x78, 0x05, 0xBE, 0x65, 0xB4, 0xE8, 0x2D};
|
||||||
|
|
||||||
|
Prime2 = new CryptoPP::byte[0x80]{
|
||||||
|
0xC7, 0x9E, 0x47, 0x58, 0x00, 0x7D, 0x62, 0x82, 0xB0, 0xD2, 0x22, 0x81, 0xD4,
|
||||||
|
0xA8, 0x97, 0x1B, 0x79, 0x0C, 0x3A, 0xB0, 0xD7, 0xC9, 0x30, 0xE3, 0xC3, 0x53,
|
||||||
|
0x8E, 0x57, 0xEF, 0xF0, 0x9B, 0x9F, 0xB3, 0x90, 0x52, 0xC6, 0x94, 0x22, 0x36,
|
||||||
|
0xAA, 0xE6, 0x4A, 0x5F, 0x72, 0x1D, 0x70, 0xE8, 0x76, 0x58, 0xC8, 0xB2, 0x91,
|
||||||
|
0xCE, 0x9C, 0xC3, 0xE9, 0x09, 0x7F, 0x2E, 0x47, 0x97, 0xCC, 0x90, 0x39, 0x15,
|
||||||
|
0x35, 0x31, 0xDE, 0x1F, 0x0C, 0x8C, 0x0D, 0xC1, 0xC2, 0x92, 0xBE, 0x97, 0xBF,
|
||||||
|
0x2F, 0x91, 0xA1, 0x8C, 0x7D, 0x50, 0xA8, 0x21, 0x2F, 0xD7, 0xA2, 0x9A, 0x7E,
|
||||||
|
0xB5, 0xA7, 0x2A, 0x90, 0x02, 0xD9, 0xF3, 0x3D, 0xD1, 0xEB, 0xB8, 0xE0, 0x5A,
|
||||||
|
0x79, 0x9E, 0x7D, 0x8D, 0xCA, 0x18, 0x6D, 0xBD, 0x9E, 0xA1, 0x80, 0x28, 0x6B,
|
||||||
|
0x2A, 0xFE, 0x51, 0x24, 0x9B, 0x6F, 0x4D, 0x84, 0x77, 0x80, 0x23};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class DebugRifKeyset {
|
||||||
|
public:
|
||||||
|
// Constructor
|
||||||
|
// std::uint8_t* PrivateExponent;
|
||||||
|
const CryptoPP::byte* Exponent1;
|
||||||
|
// exponent2 = d mod (q - 1)
|
||||||
|
const CryptoPP::byte* Exponent2;
|
||||||
|
// e
|
||||||
|
const CryptoPP::byte* PublicExponent;
|
||||||
|
// (InverseQ)(q) = 1 mod p
|
||||||
|
const CryptoPP::byte* Coefficient;
|
||||||
|
// n = p * q
|
||||||
|
const CryptoPP::byte* Modulus;
|
||||||
|
// p
|
||||||
|
const CryptoPP::byte* Prime1;
|
||||||
|
// q
|
||||||
|
const CryptoPP::byte* Prime2;
|
||||||
|
const CryptoPP::byte* PrivateExponent;
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
DebugRifKeyset() {
|
||||||
|
// Initialize PrivateExponent
|
||||||
|
PrivateExponent = new CryptoPP::byte[0x100]{
|
||||||
|
0x01, 0x61, 0xAD, 0xD8, 0x9C, 0x06, 0x89, 0xD0, 0x60, 0xC8, 0x41, 0xF0, 0xB3, 0x83,
|
||||||
|
0x01, 0x5D, 0xE3, 0xA2, 0x6B, 0xA2, 0xBA, 0x9A, 0x0A, 0x58, 0xCD, 0x1A, 0xA0, 0x97,
|
||||||
|
0x64, 0xEC, 0xD0, 0x31, 0x1F, 0xCA, 0x36, 0x0E, 0x69, 0xDD, 0x40, 0xF7, 0x4E, 0xC0,
|
||||||
|
0xC6, 0xA3, 0x73, 0xF0, 0x69, 0x84, 0xB2, 0xF4, 0x4B, 0x29, 0x14, 0x2A, 0x6D, 0xB8,
|
||||||
|
0x23, 0xD8, 0x1B, 0x61, 0xD4, 0x9E, 0x87, 0xB3, 0xBB, 0xA9, 0xC4, 0x85, 0x4A, 0xF8,
|
||||||
|
0x03, 0x4A, 0xBF, 0xFE, 0xF9, 0xFE, 0x8B, 0xDD, 0x54, 0x83, 0xBA, 0xE0, 0x2F, 0x3F,
|
||||||
|
0xB1, 0xEF, 0xA5, 0x05, 0x5D, 0x28, 0x8B, 0xAB, 0xB5, 0xD0, 0x23, 0x2F, 0x8A, 0xCF,
|
||||||
|
0x48, 0x7C, 0xAA, 0xBB, 0xC8, 0x5B, 0x36, 0x27, 0xC5, 0x16, 0xA4, 0xB6, 0x61, 0xAC,
|
||||||
|
0x0C, 0x28, 0x47, 0x79, 0x3F, 0x38, 0xAE, 0x5E, 0x25, 0xC6, 0xAF, 0x35, 0xAE, 0xBC,
|
||||||
|
0xB0, 0xF3, 0xBC, 0xBD, 0xFD, 0xA4, 0x87, 0x0D, 0x14, 0x3D, 0x90, 0xE4, 0xDE, 0x5D,
|
||||||
|
0x1D, 0x46, 0x81, 0xF1, 0x28, 0x6D, 0x2F, 0x2C, 0x5E, 0x97, 0x2D, 0x89, 0x2A, 0x51,
|
||||||
|
0x72, 0x3C, 0x20, 0x02, 0x59, 0xB1, 0x98, 0x93, 0x05, 0x1E, 0x3F, 0xA1, 0x8A, 0x69,
|
||||||
|
0x30, 0x0E, 0x70, 0x84, 0x8B, 0xAE, 0x97, 0xA1, 0x08, 0x95, 0x63, 0x4C, 0xC7, 0xE8,
|
||||||
|
0x5D, 0x59, 0xCA, 0x78, 0x2A, 0x23, 0x87, 0xAC, 0x6F, 0x04, 0x33, 0xB1, 0x61, 0xB9,
|
||||||
|
0xF0, 0x95, 0xDA, 0x33, 0xCC, 0xE0, 0x4C, 0x82, 0x68, 0x82, 0x14, 0x51, 0xBE, 0x49,
|
||||||
|
0x1C, 0x58, 0xA2, 0x8B, 0x05, 0x4E, 0x98, 0x37, 0xEB, 0x94, 0x0B, 0x01, 0x22, 0xDC,
|
||||||
|
0xB3, 0x19, 0xCA, 0x77, 0xA6, 0x6E, 0x97, 0xFF, 0x8A, 0x53, 0x5A, 0xC5, 0x24, 0xE4,
|
||||||
|
0xAF, 0x6E, 0xA8, 0x2B, 0x53, 0xA4, 0xBE, 0x96, 0xA5, 0x7B, 0xCE, 0x22, 0x56, 0xA3,
|
||||||
|
0xF1, 0xCF, 0x14, 0xA5};
|
||||||
|
|
||||||
|
// Initialize Exponent1
|
||||||
|
Exponent1 = new CryptoPP::byte[0x80]{
|
||||||
|
0xCD, 0x9A, 0x61, 0xB0, 0xB8, 0xD5, 0xB4, 0xE4, 0xE4, 0xF6, 0xAB, 0xF7, 0x27,
|
||||||
|
0xB7, 0x56, 0x59, 0x6B, 0xB9, 0x11, 0xE7, 0xF4, 0x83, 0xAF, 0xB9, 0x73, 0x99,
|
||||||
|
0x7F, 0x49, 0xA2, 0x9C, 0xF0, 0xB5, 0x6D, 0x37, 0x82, 0x14, 0x15, 0xF1, 0x04,
|
||||||
|
0x8A, 0xD4, 0x8E, 0xEB, 0x2E, 0x1F, 0xE2, 0x81, 0xA9, 0x62, 0x6E, 0xB1, 0x68,
|
||||||
|
0x75, 0x62, 0xF3, 0x0F, 0xFE, 0xD4, 0x91, 0x87, 0x98, 0x78, 0xBF, 0x26, 0xB5,
|
||||||
|
0x07, 0x58, 0xD0, 0xEE, 0x3F, 0x21, 0xE8, 0xC8, 0x0F, 0x5F, 0xFA, 0x1C, 0x64,
|
||||||
|
0x74, 0x49, 0x52, 0xEB, 0xE7, 0xEE, 0xDE, 0xBA, 0x23, 0x26, 0x4A, 0xF6, 0x9C,
|
||||||
|
0x1A, 0x09, 0x3F, 0xB9, 0x0B, 0x36, 0x26, 0x1A, 0xBE, 0xA9, 0x76, 0xE6, 0xF2,
|
||||||
|
0x69, 0xDE, 0xFF, 0xAF, 0xCC, 0x0C, 0x9A, 0x66, 0x03, 0x86, 0x0A, 0x1F, 0x49,
|
||||||
|
0xA4, 0x10, 0xB6, 0xBC, 0xC3, 0x7C, 0x88, 0xE8, 0xCE, 0x4B, 0xD9
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Exponent2 = new CryptoPP::byte[0x80]{
|
||||||
|
0xB3, 0x73, 0xA3, 0x59, 0xE6, 0x97, 0xC0, 0xAB, 0x3B, 0x68, 0xFC, 0x39, 0xAC,
|
||||||
|
0xDB, 0x44, 0xB1, 0xB4, 0x9E, 0x35, 0x4D, 0xBE, 0xC5, 0x36, 0x69, 0x6C, 0x3D,
|
||||||
|
0xC5, 0xFC, 0xFE, 0x4B, 0x2F, 0xDC, 0x86, 0x80, 0x46, 0x96, 0x40, 0x1A, 0x0D,
|
||||||
|
0x6E, 0xFA, 0x8C, 0xE0, 0x47, 0x91, 0xAC, 0xAD, 0x95, 0x2B, 0x8E, 0x1F, 0xF2,
|
||||||
|
0x0A, 0x45, 0xF8, 0x29, 0x95, 0x70, 0xC6, 0x88, 0x5F, 0x71, 0x03, 0x99, 0x79,
|
||||||
|
0xBC, 0x84, 0x71, 0xBD, 0xE8, 0x84, 0x8C, 0x0E, 0xD4, 0x7B, 0x30, 0x74, 0x57,
|
||||||
|
0x1A, 0x95, 0xE7, 0x90, 0x19, 0x8D, 0xAD, 0x8B, 0x4C, 0x4E, 0xC3, 0xE7, 0x6B,
|
||||||
|
0x23, 0x86, 0x01, 0xEE, 0x9B, 0xE0, 0x2F, 0x15, 0xA2, 0x2C, 0x4C, 0x39, 0xD3,
|
||||||
|
0xDF, 0x9C, 0x39, 0x01, 0xF1, 0x8C, 0x44, 0x4A, 0x15, 0x44, 0xDC, 0x51, 0xF7,
|
||||||
|
0x22, 0xD7, 0x7F, 0x41, 0x7F, 0x68, 0xFA, 0xEE, 0x56, 0xE8, 0x05};
|
||||||
|
|
||||||
|
PublicExponent = new CryptoPP::byte[4]{0x00, 0x01, 0x00, 0x01};
|
||||||
|
|
||||||
|
Coefficient = new CryptoPP::byte[0x80]{
|
||||||
|
0xC0, 0x32, 0x43, 0xD3, 0x8C, 0x3D, 0xB4, 0xD2, 0x48, 0x8C, 0x42, 0x41, 0x24,
|
||||||
|
0x94, 0x6C, 0x80, 0xC9, 0xC1, 0x79, 0x36, 0x7F, 0xAC, 0xC3, 0xFF, 0x6A, 0x25,
|
||||||
|
0xEB, 0x2C, 0xFB, 0xD4, 0x2B, 0xA0, 0xEB, 0xFE, 0x25, 0xE9, 0xC6, 0x77, 0xCE,
|
||||||
|
0xFE, 0x2D, 0x23, 0xFE, 0xD0, 0xF4, 0x0F, 0xD9, 0x7E, 0xD5, 0xA5, 0x7D, 0x1F,
|
||||||
|
0xC0, 0xE8, 0xE8, 0xEC, 0x80, 0x5B, 0xC7, 0xFD, 0xE2, 0xBD, 0x94, 0xA6, 0x2B,
|
||||||
|
0xDD, 0x6A, 0x60, 0x45, 0x54, 0xAB, 0xCA, 0x42, 0x9C, 0x6A, 0x6C, 0xBF, 0x3C,
|
||||||
|
0x84, 0xF9, 0xA5, 0x0E, 0x63, 0x0C, 0x51, 0x58, 0x62, 0x6D, 0x5A, 0xB7, 0x3C,
|
||||||
|
0x3F, 0x49, 0x1A, 0xD0, 0x93, 0xB8, 0x4F, 0x1A, 0x6C, 0x5F, 0xC5, 0xE5, 0xA9,
|
||||||
|
0x75, 0xD4, 0x86, 0x9E, 0xDF, 0x87, 0x0F, 0x27, 0xB0, 0x26, 0x78, 0x4E, 0xFB,
|
||||||
|
0xC1, 0x8A, 0x4A, 0x24, 0x3F, 0x7F, 0x8F, 0x9A, 0x12, 0x51, 0xCB};
|
||||||
|
|
||||||
|
Modulus = new CryptoPP::byte[0x100]{
|
||||||
|
0xC2, 0xD2, 0x44, 0xBC, 0xDD, 0x84, 0x3F, 0xD9, 0xC5, 0x22, 0xAF, 0xF7, 0xFC, 0x88,
|
||||||
|
0x8A, 0x33, 0x80, 0xED, 0x8E, 0xE2, 0xCC, 0x81, 0xF7, 0xEC, 0xF8, 0x1C, 0x79, 0xBF,
|
||||||
|
0x02, 0xBB, 0x12, 0x8E, 0x61, 0x68, 0x29, 0x1B, 0x15, 0xB6, 0x5E, 0xC6, 0xF8, 0xBF,
|
||||||
|
0x5A, 0xE0, 0x3B, 0x6A, 0x6C, 0xD9, 0xD6, 0xF5, 0x75, 0xAB, 0xA0, 0x6F, 0x34, 0x81,
|
||||||
|
0x34, 0x9A, 0x5B, 0xAD, 0xED, 0x31, 0xE3, 0xC6, 0xEA, 0x1A, 0xD1, 0x13, 0x22, 0xBB,
|
||||||
|
0xB3, 0xDA, 0xB3, 0xB2, 0x53, 0xBD, 0x45, 0x79, 0x87, 0xAD, 0x0A, 0x01, 0x72, 0x18,
|
||||||
|
0x10, 0x29, 0x49, 0xF4, 0x41, 0x7F, 0xD6, 0x47, 0x0C, 0x72, 0x92, 0x9E, 0xE9, 0xBB,
|
||||||
|
0x95, 0xA9, 0x5D, 0x79, 0xEB, 0xE4, 0x30, 0x76, 0x90, 0x45, 0x4B, 0x9D, 0x9C, 0xCF,
|
||||||
|
0x92, 0x03, 0x60, 0x8C, 0x4B, 0x6C, 0xB3, 0x7A, 0x3A, 0x05, 0x39, 0xA0, 0x66, 0xA9,
|
||||||
|
0x35, 0xCF, 0xB9, 0xFA, 0xAD, 0x9C, 0xAB, 0xEB, 0xE4, 0x6A, 0x8C, 0xE9, 0x3B, 0xCC,
|
||||||
|
0x72, 0x12, 0x62, 0x63, 0xBD, 0x80, 0xC4, 0xEE, 0x37, 0x2B, 0x32, 0x03, 0xA3, 0x09,
|
||||||
|
0xF7, 0xA0, 0x61, 0x57, 0xAD, 0x0D, 0xCF, 0x15, 0x98, 0x9E, 0x4E, 0x49, 0xF8, 0xB5,
|
||||||
|
0xA3, 0x5C, 0x27, 0xEE, 0x45, 0x04, 0xEA, 0xE4, 0x4B, 0xBC, 0x8F, 0x87, 0xED, 0x19,
|
||||||
|
0x1E, 0x46, 0x75, 0x63, 0xC4, 0x5B, 0xD5, 0xBC, 0x09, 0x2F, 0x02, 0x73, 0x19, 0x3C,
|
||||||
|
0x58, 0x55, 0x49, 0x66, 0x4C, 0x11, 0xEC, 0x0F, 0x09, 0xFA, 0xA5, 0x56, 0x0A, 0x5A,
|
||||||
|
0x63, 0x56, 0xAD, 0xA0, 0x0D, 0x86, 0x08, 0xC1, 0xE6, 0xB6, 0x13, 0x22, 0x49, 0x2F,
|
||||||
|
0x7C, 0xDB, 0x4C, 0x56, 0x97, 0x0E, 0xC2, 0xD9, 0x2E, 0x87, 0xBC, 0x0E, 0x67, 0xC0,
|
||||||
|
0x1B, 0x58, 0xBC, 0x64, 0x2B, 0xC2, 0x6E, 0xE2, 0x93, 0x2E, 0xB5, 0x6B, 0x70, 0xA4,
|
||||||
|
0x42, 0x9F, 0x64, 0xC1};
|
||||||
|
|
||||||
|
Prime1 = new CryptoPP::byte[0x80]{
|
||||||
|
0xE5, 0x62, 0xE1, 0x7F, 0x9F, 0x86, 0x08, 0xE2, 0x61, 0xD3, 0xD0, 0x42, 0xE2,
|
||||||
|
0xC4, 0xB6, 0xA8, 0x51, 0x09, 0x19, 0x14, 0xA4, 0x3A, 0x11, 0x4C, 0x33, 0xA5,
|
||||||
|
0x9C, 0x01, 0x5E, 0x34, 0xB6, 0x3F, 0x02, 0x1A, 0xCA, 0x47, 0xF1, 0x4F, 0x3B,
|
||||||
|
0x35, 0x2A, 0x07, 0x20, 0xEC, 0xD8, 0xC1, 0x15, 0xD9, 0xCA, 0x03, 0x4F, 0xB8,
|
||||||
|
0xE8, 0x09, 0x73, 0x3F, 0x85, 0xB7, 0x41, 0xD5, 0x51, 0x3E, 0x7B, 0xE3, 0x53,
|
||||||
|
0x2B, 0x48, 0x8B, 0x8E, 0xCB, 0xBA, 0xF7, 0xE0, 0x60, 0xF5, 0x35, 0x0E, 0x6F,
|
||||||
|
0xB0, 0xD9, 0x2A, 0x99, 0xD0, 0xFF, 0x60, 0x14, 0xED, 0x40, 0xEA, 0xF8, 0xD7,
|
||||||
|
0x0B, 0xC3, 0x8D, 0x8C, 0xE8, 0x81, 0xB3, 0x75, 0x93, 0x15, 0xB3, 0x7D, 0xF6,
|
||||||
|
0x39, 0x60, 0x1A, 0x00, 0xE7, 0xC3, 0x27, 0xAD, 0xA4, 0x33, 0xD5, 0x3E, 0xA4,
|
||||||
|
0x35, 0x48, 0x6F, 0x22, 0xEF, 0x5D, 0xDD, 0x7D, 0x7B, 0x61, 0x05};
|
||||||
|
|
||||||
|
Prime2 = new CryptoPP::byte[0x80]{
|
||||||
|
0xD9, 0x6C, 0xC2, 0x0C, 0xF7, 0xAE, 0xD1, 0xF3, 0x3B, 0x3B, 0x49, 0x1E, 0x9F,
|
||||||
|
0x12, 0x9C, 0xA1, 0x78, 0x1F, 0x35, 0x1D, 0x98, 0x26, 0x13, 0x71, 0xF9, 0x09,
|
||||||
|
0xFD, 0xF0, 0xAD, 0x38, 0x55, 0xB7, 0xEE, 0x61, 0x04, 0x72, 0x51, 0x87, 0x2E,
|
||||||
|
0x05, 0x84, 0xB1, 0x1D, 0x0C, 0x0D, 0xDB, 0xD4, 0x25, 0x3E, 0x26, 0xED, 0xEA,
|
||||||
|
0xB8, 0xF7, 0x49, 0xFE, 0xA2, 0x94, 0xE6, 0xF2, 0x08, 0x92, 0xA7, 0x85, 0xF5,
|
||||||
|
0x30, 0xB9, 0x84, 0x22, 0xBF, 0xCA, 0xF0, 0x5F, 0xCB, 0x31, 0x20, 0x34, 0x49,
|
||||||
|
0x16, 0x76, 0x34, 0xCC, 0x7A, 0xCB, 0x96, 0xFE, 0x78, 0x7A, 0x41, 0xFE, 0x9A,
|
||||||
|
0xA2, 0x23, 0xF7, 0x68, 0x80, 0xD6, 0xCE, 0x4A, 0x78, 0xA5, 0xB7, 0x05, 0x77,
|
||||||
|
0x81, 0x1F, 0xDE, 0x5E, 0xA8, 0x6E, 0x3E, 0x87, 0xEC, 0x44, 0xD2, 0x69, 0xC6,
|
||||||
|
0x54, 0x91, 0x6B, 0x5E, 0x13, 0x8A, 0x03, 0x87, 0x05, 0x31, 0x8D};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class PkgDerivedKey3Keyset {
|
||||||
|
public:
|
||||||
|
// PkgDerivedKey3Keyset();
|
||||||
|
//~PkgDerivedKey3Keyset();
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
// std::uint8_t* PrivateExponent;
|
||||||
|
const CryptoPP::byte* Exponent1;
|
||||||
|
// exponent2 = d mod (q - 1)
|
||||||
|
const CryptoPP::byte* Exponent2;
|
||||||
|
// e
|
||||||
|
const CryptoPP::byte* PublicExponent;
|
||||||
|
// (InverseQ)(q) = 1 mod p
|
||||||
|
const CryptoPP::byte* Coefficient;
|
||||||
|
// n = p * q
|
||||||
|
const CryptoPP::byte* Modulus;
|
||||||
|
// p
|
||||||
|
const CryptoPP::byte* Prime1;
|
||||||
|
// q
|
||||||
|
const CryptoPP::byte* Prime2;
|
||||||
|
const CryptoPP::byte* PrivateExponent;
|
||||||
|
|
||||||
|
PkgDerivedKey3Keyset() {
|
||||||
|
|
||||||
|
Prime1 = new CryptoPP::byte[0x80]{
|
||||||
|
0xF9, 0x67, 0xAD, 0x99, 0x12, 0x31, 0x0C, 0x56, 0xA2, 0x2E, 0x16, 0x1C, 0x46,
|
||||||
|
0xB3, 0x4D, 0x5B, 0x43, 0xBE, 0x42, 0xA2, 0xF6, 0x86, 0x96, 0x80, 0x42, 0xC3,
|
||||||
|
0xC7, 0x3F, 0xC3, 0x42, 0xF5, 0x87, 0x49, 0x33, 0x9F, 0x07, 0x5D, 0x6E, 0x2C,
|
||||||
|
0x04, 0xFD, 0xE3, 0xE1, 0xB2, 0xAE, 0x0A, 0x0C, 0xF0, 0xC7, 0xA6, 0x1C, 0xA1,
|
||||||
|
0x63, 0x50, 0xC8, 0x09, 0x9C, 0x51, 0x24, 0x52, 0x6C, 0x5E, 0x5E, 0xBD, 0x1E,
|
||||||
|
0x27, 0x06, 0xBB, 0xBC, 0x9E, 0x94, 0xE1, 0x35, 0xD4, 0x6D, 0xB3, 0xCB, 0x3C,
|
||||||
|
0x68, 0xDD, 0x68, 0xB3, 0xFE, 0x6C, 0xCB, 0x8D, 0x82, 0x20, 0x76, 0x23, 0x63,
|
||||||
|
0xB7, 0xE9, 0x68, 0x10, 0x01, 0x4E, 0xDC, 0xBA, 0x27, 0x5D, 0x01, 0xC1, 0x2D,
|
||||||
|
0x80, 0x5E, 0x2B, 0xAF, 0x82, 0x6B, 0xD8, 0x84, 0xB6, 0x10, 0x52, 0x86, 0xA7,
|
||||||
|
0x89, 0x8E, 0xAE, 0x9A, 0xE2, 0x89, 0xC6, 0xF7, 0xD5, 0x87, 0xFB};
|
||||||
|
|
||||||
|
Prime2 = new CryptoPP::byte[0x80]{
|
||||||
|
0xD7, 0xA1, 0x0F, 0x9A, 0x8B, 0xF2, 0xC9, 0x11, 0x95, 0x32, 0x9A, 0x8C, 0xF0,
|
||||||
|
0xD9, 0x40, 0x47, 0xF5, 0x68, 0xA0, 0x0D, 0xBD, 0xC1, 0xFC, 0x43, 0x2F, 0x65,
|
||||||
|
0xF9, 0xC3, 0x61, 0x0F, 0x25, 0x77, 0x54, 0xAD, 0xD7, 0x58, 0xAC, 0x84, 0x40,
|
||||||
|
0x60, 0x8D, 0x3F, 0xF3, 0x65, 0x89, 0x75, 0xB5, 0xC6, 0x2C, 0x51, 0x1A, 0x2F,
|
||||||
|
0x1F, 0x22, 0xE4, 0x43, 0x11, 0x54, 0xBE, 0xC9, 0xB4, 0xC7, 0xB5, 0x1B, 0x05,
|
||||||
|
0x0B, 0xBC, 0x56, 0x9A, 0xCD, 0x4A, 0xD9, 0x73, 0x68, 0x5E, 0x5C, 0xFB, 0x92,
|
||||||
|
0xB7, 0x8B, 0x0D, 0xFF, 0xF5, 0x07, 0xCA, 0xB4, 0xC8, 0x9B, 0x96, 0x3C, 0x07,
|
||||||
|
0x9E, 0x3E, 0x6B, 0x2A, 0x11, 0xF2, 0x8A, 0xB1, 0x8A, 0xD7, 0x2E, 0x1B, 0xA5,
|
||||||
|
0x53, 0x24, 0x06, 0xED, 0x50, 0xB8, 0x90, 0x67, 0xB1, 0xE2, 0x41, 0xC6, 0x92,
|
||||||
|
0x01, 0xEE, 0x10, 0xF0, 0x61, 0xBB, 0xFB, 0xB2, 0x7D, 0x4A, 0x73};
|
||||||
|
PrivateExponent = new CryptoPP::byte[0x100]{
|
||||||
|
0x32, 0xD9, 0x03, 0x90, 0x8F, 0xBD, 0xB0, 0x8F, 0x57, 0x2B, 0x28, 0x5E, 0x0B, 0x8D,
|
||||||
|
0xB3, 0xEA, 0x5C, 0xD1, 0x7E, 0xA8, 0x90, 0x88, 0x8C, 0xDD, 0x6A, 0x80, 0xBB, 0xB1,
|
||||||
|
0xDF, 0xC1, 0xF7, 0x0D, 0xAA, 0x32, 0xF0, 0xB7, 0x7C, 0xCB, 0x88, 0x80, 0x0E, 0x8B,
|
||||||
|
0x64, 0xB0, 0xBE, 0x4C, 0xD6, 0x0E, 0x9B, 0x8C, 0x1E, 0x2A, 0x64, 0xE1, 0xF3, 0x5C,
|
||||||
|
0xD7, 0x76, 0x01, 0x41, 0x5E, 0x93, 0x5C, 0x94, 0xFE, 0xDD, 0x46, 0x62, 0xC3, 0x1B,
|
||||||
|
0x5A, 0xE2, 0xA0, 0xBC, 0x2D, 0xEB, 0xC3, 0x98, 0x0A, 0xA7, 0xB7, 0x85, 0x69, 0x70,
|
||||||
|
0x68, 0x2B, 0x64, 0x4A, 0xB3, 0x1F, 0xCC, 0x7D, 0xDC, 0x7C, 0x26, 0xF4, 0x77, 0xF6,
|
||||||
|
0x5C, 0xF2, 0xAE, 0x5A, 0x44, 0x2D, 0xD3, 0xAB, 0x16, 0x62, 0x04, 0x19, 0xBA, 0xFB,
|
||||||
|
0x90, 0xFF, 0xE2, 0x30, 0x50, 0x89, 0x6E, 0xCB, 0x56, 0xB2, 0xEB, 0xC0, 0x91, 0x16,
|
||||||
|
0x92, 0x5E, 0x30, 0x8E, 0xAE, 0xC7, 0x94, 0x5D, 0xFD, 0x35, 0xE1, 0x20, 0xF8, 0xAD,
|
||||||
|
0x3E, 0xBC, 0x08, 0xBF, 0xC0, 0x36, 0x74, 0x9F, 0xD5, 0xBB, 0x52, 0x08, 0xFD, 0x06,
|
||||||
|
0x66, 0xF3, 0x7A, 0xB3, 0x04, 0xF4, 0x75, 0x29, 0x5D, 0xE9, 0x5F, 0xAA, 0x10, 0x30,
|
||||||
|
0xB2, 0x0F, 0x5A, 0x1A, 0xC1, 0x2A, 0xB3, 0xFE, 0xCB, 0x21, 0xAD, 0x80, 0xEC, 0x8F,
|
||||||
|
0x20, 0x09, 0x1C, 0xDB, 0xC5, 0x58, 0x94, 0xC2, 0x9C, 0xC6, 0xCE, 0x82, 0x65, 0x3E,
|
||||||
|
0x57, 0x90, 0xBC, 0xA9, 0x8B, 0x06, 0xB4, 0xF0, 0x72, 0xF6, 0x77, 0xDF, 0x98, 0x64,
|
||||||
|
0xF1, 0xEC, 0xFE, 0x37, 0x2D, 0xBC, 0xAE, 0x8C, 0x08, 0x81, 0x1F, 0xC3, 0xC9, 0x89,
|
||||||
|
0x1A, 0xC7, 0x42, 0x82, 0x4B, 0x2E, 0xDC, 0x8E, 0x8D, 0x73, 0xCE, 0xB1, 0xCC, 0x01,
|
||||||
|
0xD9, 0x08, 0x70, 0x87, 0x3C, 0x44, 0x08, 0xEC, 0x49, 0x8F, 0x81, 0x5A, 0xE2, 0x40,
|
||||||
|
0xFF, 0x77, 0xFC, 0x0D};
|
||||||
|
Exponent1 = new CryptoPP::byte[0x80]{
|
||||||
|
0x52, 0xCC, 0x2D, 0xA0, 0x9C, 0x9E, 0x75, 0xE7, 0x28, 0xEE, 0x3D, 0xDE, 0xE3,
|
||||||
|
0x45, 0xD1, 0x4F, 0x94, 0x1C, 0xCC, 0xC8, 0x87, 0x29, 0x45, 0x3B, 0x8D, 0x6E,
|
||||||
|
0xAB, 0x6E, 0x2A, 0xA7, 0xC7, 0x15, 0x43, 0xA3, 0x04, 0x8F, 0x90, 0x5F, 0xEB,
|
||||||
|
0xF3, 0x38, 0x4A, 0x77, 0xFA, 0x36, 0xB7, 0x15, 0x76, 0xB6, 0x01, 0x1A, 0x8E,
|
||||||
|
0x25, 0x87, 0x82, 0xF1, 0x55, 0xD8, 0xC6, 0x43, 0x2A, 0xC0, 0xE5, 0x98, 0xC9,
|
||||||
|
0x32, 0xD1, 0x94, 0x6F, 0xD9, 0x01, 0xBA, 0x06, 0x81, 0xE0, 0x6D, 0x88, 0xF2,
|
||||||
|
0x24, 0x2A, 0x25, 0x01, 0x64, 0x5C, 0xBF, 0xF2, 0xD9, 0x99, 0x67, 0x3E, 0xF6,
|
||||||
|
0x72, 0xEE, 0xE4, 0xE2, 0x33, 0x5C, 0xF8, 0x00, 0x40, 0xE3, 0x2A, 0x9A, 0xF4,
|
||||||
|
0x3D, 0x22, 0x86, 0x44, 0x3C, 0xFB, 0x0A, 0xA5, 0x7C, 0x3F, 0xCC, 0xF5, 0xF1,
|
||||||
|
0x16, 0xC4, 0xAC, 0x88, 0xB4, 0xDE, 0x62, 0x94, 0x92, 0x6A, 0x13};
|
||||||
|
Exponent2 = new CryptoPP::byte[0x80]{
|
||||||
|
0x7C, 0x9D, 0xAD, 0x39, 0xE0, 0xD5, 0x60, 0x14, 0x94, 0x48, 0x19, 0x7F, 0x88,
|
||||||
|
0x95, 0xD5, 0x8B, 0x80, 0xAD, 0x85, 0x8A, 0x4B, 0x77, 0x37, 0x85, 0xD0, 0x77,
|
||||||
|
0xBB, 0xBF, 0x89, 0x71, 0x4A, 0x72, 0xCB, 0x72, 0x68, 0x38, 0xEC, 0x02, 0xC6,
|
||||||
|
0x7D, 0xC6, 0x44, 0x06, 0x33, 0x51, 0x1C, 0xC0, 0xFF, 0x95, 0x8F, 0x0D, 0x75,
|
||||||
|
0xDC, 0x25, 0xBB, 0x0B, 0x73, 0x91, 0xA9, 0x6D, 0x42, 0xD8, 0x03, 0xB7, 0x68,
|
||||||
|
0xD4, 0x1E, 0x75, 0x62, 0xA3, 0x70, 0x35, 0x79, 0x78, 0x00, 0xC8, 0xF5, 0xEF,
|
||||||
|
0x15, 0xB9, 0xFC, 0x4E, 0x47, 0x5A, 0xC8, 0x70, 0x70, 0x5B, 0x52, 0x98, 0xC0,
|
||||||
|
0xC2, 0x58, 0x4A, 0x70, 0x96, 0xCC, 0xB8, 0x10, 0xE1, 0x2F, 0x78, 0x8B, 0x2B,
|
||||||
|
0xA1, 0x7F, 0xF9, 0xAC, 0xDE, 0xF0, 0xBB, 0x2B, 0xE2, 0x66, 0xE3, 0x22, 0x92,
|
||||||
|
0x31, 0x21, 0x57, 0x92, 0xC4, 0xB8, 0xF2, 0x3E, 0x76, 0x20, 0x37};
|
||||||
|
Coefficient = new CryptoPP::byte[0x80]{
|
||||||
|
0x45, 0x97, 0x55, 0xD4, 0x22, 0x08, 0x5E, 0xF3, 0x5C, 0xB4, 0x05, 0x7A, 0xFD,
|
||||||
|
0xAA, 0x42, 0x42, 0xAD, 0x9A, 0x8C, 0xA0, 0x6C, 0xBB, 0x1D, 0x68, 0x54, 0x54,
|
||||||
|
0x6E, 0x3E, 0x32, 0xE3, 0x53, 0x73, 0x76, 0xF1, 0x3E, 0x01, 0xEA, 0xD3, 0xCF,
|
||||||
|
0xEB, 0xEB, 0x23, 0x3E, 0xC0, 0xBE, 0xCE, 0xEC, 0x2C, 0x89, 0x5F, 0xA8, 0x27,
|
||||||
|
0x3A, 0x4C, 0xB7, 0xE6, 0x74, 0xBC, 0x45, 0x4C, 0x26, 0xC8, 0x25, 0xFF, 0x34,
|
||||||
|
0x63, 0x25, 0x37, 0xE1, 0x48, 0x10, 0xC1, 0x93, 0xA6, 0xAF, 0xEB, 0xBA, 0xE3,
|
||||||
|
0xA2, 0xF1, 0x3D, 0xEF, 0x63, 0xD8, 0xF4, 0xFD, 0xD3, 0xEE, 0xE2, 0x5D, 0xE9,
|
||||||
|
0x33, 0xCC, 0xAD, 0xBA, 0x75, 0x5C, 0x85, 0xAF, 0xCE, 0xA9, 0x3D, 0xD1, 0xA2,
|
||||||
|
0x17, 0xF3, 0xF6, 0x98, 0xB3, 0x50, 0x8E, 0x5E, 0xF6, 0xEB, 0x02, 0x8E, 0xA1,
|
||||||
|
0x62, 0xA7, 0xD6, 0x2C, 0xEC, 0x91, 0xFF, 0x15, 0x40, 0xD2, 0xE3};
|
||||||
|
Modulus = new CryptoPP::byte[0x100]{
|
||||||
|
0xd2, 0x12, 0xfc, 0x33, 0x5f, 0x6d, 0xdb, 0x83, 0x16, 0x09, 0x62, 0x8b, 0x03, 0x56,
|
||||||
|
0x27, 0x37, 0x82, 0xd4, 0x77, 0x85, 0x35, 0x29, 0x39, 0x2d, 0x52, 0x6b, 0x8c, 0x4c,
|
||||||
|
0x8c, 0xfb, 0x06, 0xc1, 0x84, 0x5b, 0xe7, 0xd4, 0xf7, 0xbc, 0xd2, 0x4e, 0x62, 0x45,
|
||||||
|
0xcd, 0x2a, 0xbb, 0xd7, 0x77, 0x76, 0x45, 0x36, 0x55, 0x27, 0x3f, 0xb3, 0xf5, 0xf9,
|
||||||
|
0x8e, 0xda, 0x4b, 0xef, 0xaa, 0x59, 0xae, 0xb3, 0x9b, 0xea, 0x54, 0x98, 0xd2, 0x06,
|
||||||
|
0x32, 0x6a, 0x58, 0x31, 0x2a, 0xe0, 0xd4, 0x4f, 0x90, 0xb5, 0x0a, 0x7d, 0xec, 0xf4,
|
||||||
|
0x3a, 0x9c, 0x52, 0x67, 0x2d, 0x99, 0x31, 0x8e, 0x0c, 0x43, 0xe6, 0x82, 0xfe, 0x07,
|
||||||
|
0x46, 0xe1, 0x2e, 0x50, 0xd4, 0x1f, 0x2d, 0x2f, 0x7e, 0xd9, 0x08, 0xba, 0x06, 0xb3,
|
||||||
|
0xbf, 0x2e, 0x20, 0x3f, 0x4e, 0x3f, 0xfe, 0x44, 0xff, 0xaa, 0x50, 0x43, 0x57, 0x91,
|
||||||
|
0x69, 0x94, 0x49, 0x15, 0x82, 0x82, 0xe4, 0x0f, 0x4c, 0x8d, 0x9d, 0x2c, 0xc9, 0x5b,
|
||||||
|
0x1d, 0x64, 0xbf, 0x88, 0x8b, 0xd4, 0xc5, 0x94, 0xe7, 0x65, 0x47, 0x84, 0x1e, 0xe5,
|
||||||
|
0x79, 0x10, 0xfb, 0x98, 0x93, 0x47, 0xb9, 0x7d, 0x85, 0x12, 0xa6, 0x40, 0x98, 0x2c,
|
||||||
|
0xf7, 0x92, 0xbc, 0x95, 0x19, 0x32, 0xed, 0xe8, 0x90, 0x56, 0x0d, 0x65, 0xc1, 0xaa,
|
||||||
|
0x78, 0xc6, 0x2e, 0x54, 0xfd, 0x5f, 0x54, 0xa1, 0xf6, 0x7e, 0xe5, 0xe0, 0x5f, 0x61,
|
||||||
|
0xc1, 0x20, 0xb4, 0xb9, 0xb4, 0x33, 0x08, 0x70, 0xe4, 0xdf, 0x89, 0x56, 0xed, 0x01,
|
||||||
|
0x29, 0x46, 0x77, 0x5f, 0x8c, 0xb8, 0xa9, 0xf5, 0x1e, 0x2e, 0xb3, 0xb9, 0xbf, 0xe0,
|
||||||
|
0x09, 0xb7, 0x8d, 0x28, 0xd4, 0xa6, 0xc3, 0xb8, 0x1e, 0x1f, 0x07, 0xeb, 0xb4, 0x12,
|
||||||
|
0x0b, 0x95, 0xb8, 0x85, 0x30, 0xfd, 0xdc, 0x39, 0x13, 0xd0, 0x7c, 0xdc, 0x8f, 0xed,
|
||||||
|
0xf9, 0xc9, 0xa3, 0xc1};
|
||||||
|
PublicExponent = new CryptoPP::byte[4]{0, 1, 0, 1};
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,123 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
|
#define PFS_FILE 2
|
||||||
|
#define PFS_DIR 3
|
||||||
|
#define PFS_CURRENT_DIR 4
|
||||||
|
#define PFS_PARENT_DIR 5
|
||||||
|
|
||||||
|
enum PfsMode : unsigned short {
|
||||||
|
None = 0,
|
||||||
|
Signed = 0x1,
|
||||||
|
Is64Bit = 0x2,
|
||||||
|
Encrypted = 0x4,
|
||||||
|
UnknownFlagAlwaysSet = 0x8
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PSFHeader_ {
|
||||||
|
s64 version;
|
||||||
|
s64 magic;
|
||||||
|
s64 id;
|
||||||
|
u8 fmode;
|
||||||
|
u8 clean;
|
||||||
|
u8 read_only;
|
||||||
|
u8 rsv;
|
||||||
|
PfsMode mode;
|
||||||
|
s16 unk1;
|
||||||
|
s32 block_size;
|
||||||
|
s32 n_backup;
|
||||||
|
s64 n_block;
|
||||||
|
s64 dinode_count;
|
||||||
|
s64 nd_block;
|
||||||
|
s64 dinode_block_count;
|
||||||
|
s64 superroot_ino;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PFSCHdr {
|
||||||
|
s32 magic;
|
||||||
|
s32 unk4;
|
||||||
|
s32 unk8;
|
||||||
|
s32 block_sz;
|
||||||
|
s64 block_sz2;
|
||||||
|
s64 block_offsets;
|
||||||
|
u64 data_start;
|
||||||
|
s64 data_length;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum InodeMode : u16 {
|
||||||
|
o_read = 1,
|
||||||
|
o_write = 2,
|
||||||
|
o_execute = 4,
|
||||||
|
g_read = 8,
|
||||||
|
g_write = 16,
|
||||||
|
g_execute = 32,
|
||||||
|
u_read = 64,
|
||||||
|
u_write = 128,
|
||||||
|
u_execute = 256,
|
||||||
|
dir = 16384,
|
||||||
|
file = 32768,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum InodeFlags : u32 {
|
||||||
|
compressed = 0x1,
|
||||||
|
unk1 = 0x2,
|
||||||
|
unk2 = 0x4,
|
||||||
|
unk3 = 0x8,
|
||||||
|
readonly = 0x10,
|
||||||
|
unk4 = 0x20,
|
||||||
|
unk5 = 0x40,
|
||||||
|
unk6 = 0x80,
|
||||||
|
unk7 = 0x100,
|
||||||
|
unk8 = 0x200,
|
||||||
|
unk9 = 0x400,
|
||||||
|
unk10 = 0x800,
|
||||||
|
unk11 = 0x1000,
|
||||||
|
unk12 = 0x2000,
|
||||||
|
unk13 = 0x4000,
|
||||||
|
unk14 = 0x8000,
|
||||||
|
unk15 = 0x10000,
|
||||||
|
internal = 0x20000
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Inode {
|
||||||
|
u16 Mode;
|
||||||
|
u16 Nlink;
|
||||||
|
u32 Flags;
|
||||||
|
s64 Size;
|
||||||
|
s64 SizeCompressed;
|
||||||
|
s64 Time1_sec;
|
||||||
|
s64 Time2_sec;
|
||||||
|
s64 Time3_sec;
|
||||||
|
s64 Time4_sec;
|
||||||
|
u32 Time1_nsec;
|
||||||
|
u32 Time2_nsec;
|
||||||
|
u32 Time3_nsec;
|
||||||
|
u32 Time4_nsec;
|
||||||
|
u32 Uid;
|
||||||
|
u32 Gid;
|
||||||
|
u64 Unk1;
|
||||||
|
u64 Unk2;
|
||||||
|
u32 Blocks;
|
||||||
|
u32 loc;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct pfs_fs_table {
|
||||||
|
std::string name;
|
||||||
|
u32 inode;
|
||||||
|
u32 type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Dirent {
|
||||||
|
s32 ino;
|
||||||
|
s32 type;
|
||||||
|
s32 namelen;
|
||||||
|
s32 entsize;
|
||||||
|
char name[512];
|
||||||
|
};
|
|
@ -0,0 +1,375 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <zlib-ng.h>
|
||||||
|
#include "common/io_file.h"
|
||||||
|
#include "pkg.h"
|
||||||
|
#include "pkg_type.h"
|
||||||
|
|
||||||
|
static void DecompressPFSC(std::span<const char> compressed_data,
|
||||||
|
std::span<char> decompressed_data) {
|
||||||
|
zng_stream decompressStream;
|
||||||
|
decompressStream.zalloc = Z_NULL;
|
||||||
|
decompressStream.zfree = Z_NULL;
|
||||||
|
decompressStream.opaque = Z_NULL;
|
||||||
|
|
||||||
|
if (zng_inflateInit(&decompressStream) != Z_OK) {
|
||||||
|
// std::cerr << "Error initializing zlib for deflation." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
decompressStream.avail_in = compressed_data.size();
|
||||||
|
decompressStream.next_in = reinterpret_cast<const Bytef*>(compressed_data.data());
|
||||||
|
decompressStream.avail_out = decompressed_data.size();
|
||||||
|
decompressStream.next_out = reinterpret_cast<Bytef*>(decompressed_data.data());
|
||||||
|
|
||||||
|
if (zng_inflate(&decompressStream, Z_FINISH)) {
|
||||||
|
}
|
||||||
|
if (zng_inflateEnd(&decompressStream) != Z_OK) {
|
||||||
|
// std::cerr << "Error ending zlib inflate" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetPFSCOffset(std::span<const u8> pfs_image) {
|
||||||
|
static constexpr u32 PfscMagic = 0x43534650;
|
||||||
|
u32 value;
|
||||||
|
for (u32 i = 0x20000; i < pfs_image.size(); i += 0x10000) {
|
||||||
|
std::memcpy(&value, &pfs_image[i], sizeof(u32));
|
||||||
|
if (value == PfscMagic)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path findDirectory(const std::filesystem::path& rootFolder,
|
||||||
|
const std::filesystem::path& targetDirectory) {
|
||||||
|
for (const auto& entry : std::filesystem::recursive_directory_iterator(rootFolder)) {
|
||||||
|
if (std::filesystem::is_directory(entry) &&
|
||||||
|
entry.path().filename() == targetDirectory.filename()) {
|
||||||
|
return entry.path();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::filesystem::path(); // Return an empty path if not found
|
||||||
|
}
|
||||||
|
|
||||||
|
PKG::PKG() = default;
|
||||||
|
|
||||||
|
PKG::~PKG() = default;
|
||||||
|
|
||||||
|
bool PKG::Open(const std::string& filepath) {
|
||||||
|
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pkgSize = file.GetSize();
|
||||||
|
|
||||||
|
PKGHeader pkgheader;
|
||||||
|
file.Read(pkgheader);
|
||||||
|
|
||||||
|
// Find title id it is part of pkg_content_id starting at offset 0x40
|
||||||
|
file.Seek(0x47); // skip first 7 characters of content_id
|
||||||
|
file.Read(pkgTitleID);
|
||||||
|
|
||||||
|
file.Close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extract,
|
||||||
|
std::string& failreason) {
|
||||||
|
extract_path = extract;
|
||||||
|
pkgpath = filepath;
|
||||||
|
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pkgSize = file.GetSize();
|
||||||
|
file.ReadRaw<u8>(&pkgheader, sizeof(PKGHeader));
|
||||||
|
|
||||||
|
if (pkgheader.pkg_size > pkgSize) {
|
||||||
|
failreason = "PKG file size is different";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((pkgheader.pkg_content_size + pkgheader.pkg_content_offset) > pkgheader.pkg_size) {
|
||||||
|
failreason = "Content size is bigger than pkg size";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
file.Seek(0);
|
||||||
|
pkg.resize(pkgheader.pkg_promote_size);
|
||||||
|
file.Read(pkg);
|
||||||
|
|
||||||
|
u32 offset = pkgheader.pkg_table_entry_offset;
|
||||||
|
u32 n_files = pkgheader.pkg_table_entry_count;
|
||||||
|
|
||||||
|
std::array<u8, 32> seed_digest;
|
||||||
|
std::array<std::array<u8, 32>, 7> digest1;
|
||||||
|
std::array<std::array<u8, 256>, 7> key1;
|
||||||
|
std::array<u8, 256> imgkeydata;
|
||||||
|
|
||||||
|
for (int i = 0; i < n_files; i++) {
|
||||||
|
PKGEntry entry;
|
||||||
|
std::memcpy(&entry, &pkg[offset + i * 0x20], sizeof(entry));
|
||||||
|
|
||||||
|
// Try to figure out the name
|
||||||
|
const auto name = GetEntryNameByType(entry.id);
|
||||||
|
if (name.empty()) {
|
||||||
|
// Just print with id
|
||||||
|
Common::FS::IOFile out(extract_path / "sce_sys" / std::to_string(entry.id),
|
||||||
|
Common::FS::FileAccessMode::Write);
|
||||||
|
out.WriteRaw<u8>(pkg.data() + entry.offset, entry.size);
|
||||||
|
out.Close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto filepath = extract_path / "sce_sys" / name;
|
||||||
|
std::filesystem::create_directories(filepath.parent_path());
|
||||||
|
|
||||||
|
if (entry.id == 0x1) { // DIGESTS, seek;
|
||||||
|
// file.Seek(entry.offset, fsSeekSet);
|
||||||
|
} else if (entry.id == 0x10) { // ENTRY_KEYS, seek;
|
||||||
|
file.Seek(entry.offset);
|
||||||
|
file.Read(seed_digest);
|
||||||
|
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
file.Read(digest1[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
file.Read(key1);
|
||||||
|
}
|
||||||
|
|
||||||
|
PKG::crypto.RSA2048Decrypt(dk3_, key1[3], true); // decrypt DK3
|
||||||
|
} else if (entry.id == 0x20) { // IMAGE_KEY, seek; IV_KEY
|
||||||
|
file.Seek(entry.offset);
|
||||||
|
file.Read(imgkeydata);
|
||||||
|
|
||||||
|
// The Concatenated iv + dk3 imagekey for HASH256
|
||||||
|
std::array<CryptoPP::byte, 64> concatenated_ivkey_dk3;
|
||||||
|
std::memcpy(concatenated_ivkey_dk3.data(), &entry, sizeof(entry));
|
||||||
|
std::memcpy(concatenated_ivkey_dk3.data() + sizeof(entry), dk3_.data(), sizeof(dk3_));
|
||||||
|
|
||||||
|
PKG::crypto.ivKeyHASH256(concatenated_ivkey_dk3, ivKey); // ivkey_
|
||||||
|
// imgkey_ to use for last step to get ekpfs
|
||||||
|
PKG::crypto.aesCbcCfb128Decrypt(ivKey, imgkeydata, imgKey);
|
||||||
|
// ekpfs key to get data and tweak keys.
|
||||||
|
PKG::crypto.RSA2048Decrypt(ekpfsKey, imgKey, false);
|
||||||
|
} else if (entry.id == 0x80) {
|
||||||
|
// GENERAL_DIGESTS, seek;
|
||||||
|
// file.Seek(entry.offset, fsSeekSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::FS::IOFile out(extract_path / "sce_sys" / name, Common::FS::FileAccessMode::Write);
|
||||||
|
out.WriteRaw<u8>(pkg.data() + entry.offset, entry.size);
|
||||||
|
out.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the seed
|
||||||
|
std::array<CryptoPP::byte, 16> seed;
|
||||||
|
file.Seek(pkgheader.pfs_image_offset + 0x370);
|
||||||
|
file.Read(seed);
|
||||||
|
|
||||||
|
// Get data and tweak keys.
|
||||||
|
PKG::crypto.PfsGenCryptoKey(ekpfsKey, seed, dataKey, tweakKey);
|
||||||
|
const u32 length = pkgheader.pfs_cache_size * 0x2; // Seems to be ok.
|
||||||
|
|
||||||
|
// Read encrypted pfs_image
|
||||||
|
std::vector<u8> pfs_encrypted(length);
|
||||||
|
file.Seek(pkgheader.pfs_image_offset);
|
||||||
|
file.Read(pfs_encrypted);
|
||||||
|
|
||||||
|
// Decrypt the pfs_image.
|
||||||
|
std::vector<u8> pfs_decrypted(length);
|
||||||
|
PKG::crypto.decryptPFS(dataKey, tweakKey, pfs_encrypted, pfs_decrypted, 0);
|
||||||
|
|
||||||
|
// Retrieve PFSC from decrypted pfs_image.
|
||||||
|
pfsc_offset = GetPFSCOffset(pfs_decrypted);
|
||||||
|
std::vector<u8> pfsc(length);
|
||||||
|
std::memcpy(pfsc.data(), pfs_decrypted.data() + pfsc_offset, length - pfsc_offset);
|
||||||
|
|
||||||
|
PFSCHdr pfsChdr;
|
||||||
|
std::memcpy(&pfsChdr, pfsc.data(), sizeof(pfsChdr));
|
||||||
|
|
||||||
|
const int num_blocks = (int)(pfsChdr.data_length / pfsChdr.block_sz2);
|
||||||
|
sectorMap.resize(num_blocks + 1); // 8 bytes, need extra 1 to get the last offset.
|
||||||
|
|
||||||
|
for (int i = 0; i < num_blocks + 1; i++) {
|
||||||
|
std::memcpy(§orMap[i], pfsc.data() + pfsChdr.block_offsets + i * 8, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 ent_size = 0;
|
||||||
|
u32 ndinode = 0;
|
||||||
|
|
||||||
|
std::vector<char> compressedData;
|
||||||
|
std::vector<char> decompressedData(0x10000);
|
||||||
|
bool dinode_reached = false;
|
||||||
|
// Get iNdoes.
|
||||||
|
for (int i = 0; i < num_blocks; i++) {
|
||||||
|
const u64 sectorOffset = sectorMap[i];
|
||||||
|
const u64 sectorSize = sectorMap[i + 1] - sectorOffset;
|
||||||
|
|
||||||
|
compressedData.resize(sectorSize);
|
||||||
|
std::memcpy(compressedData.data(), pfsc.data() + sectorOffset, sectorSize);
|
||||||
|
|
||||||
|
if (sectorSize == 0x10000) // Uncompressed data
|
||||||
|
std::memcpy(decompressedData.data(), compressedData.data(), 0x10000);
|
||||||
|
else if (sectorSize < 0x10000) // Compressed data
|
||||||
|
DecompressPFSC(compressedData, decompressedData);
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
std::memcpy(&ndinode, decompressedData.data() + 0x30, 4); // number of folders and files
|
||||||
|
}
|
||||||
|
|
||||||
|
int occupied_blocks =
|
||||||
|
(ndinode * 0xA8) / 0x10000; // how many blocks(0x10000) are taken by iNodes.
|
||||||
|
if (((ndinode * 0xA8) % 0x10000) != 0)
|
||||||
|
occupied_blocks += 1;
|
||||||
|
|
||||||
|
if (i >= 1 && i <= occupied_blocks) { // Get all iNodes, gives type, file size and location.
|
||||||
|
for (int p = 0; p < 0x10000; p += 0xA8) {
|
||||||
|
Inode node;
|
||||||
|
std::memcpy(&node, &decompressedData[p], sizeof(node));
|
||||||
|
if (node.Mode == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
iNodeBuf.push_back(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char dot = decompressedData[0x10];
|
||||||
|
const std::string_view dotdot(&decompressedData[0x28], 2);
|
||||||
|
if (dot == '.' && dotdot == "..") {
|
||||||
|
dinode_reached = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get folder and file names.
|
||||||
|
bool end_reached = false;
|
||||||
|
if (dinode_reached) {
|
||||||
|
for (int j = 0; j < 0x10000; j += ent_size) { // Skip the first parent and child.
|
||||||
|
Dirent dirent;
|
||||||
|
std::memcpy(&dirent, &decompressedData[j], sizeof(dirent));
|
||||||
|
|
||||||
|
// Stop here and continue the main loop
|
||||||
|
if (dirent.ino == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dot != '.' && dotdot != "..") {
|
||||||
|
end_reached = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ent_size = dirent.entsize;
|
||||||
|
auto& table = fsTable.emplace_back();
|
||||||
|
table.name = std::string(dirent.name, dirent.namelen);
|
||||||
|
table.inode = dirent.ino;
|
||||||
|
table.type = dirent.type;
|
||||||
|
|
||||||
|
if (table.type == PFS_DIR) {
|
||||||
|
folderMap[table.inode] = table.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seems to be the last entry, at least for the games I tested. To check as we go.
|
||||||
|
const std::string_view rightsprx(&decompressedData[0x40], 10);
|
||||||
|
if (rightsprx == "right.sprx" || end_reached) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Folders.
|
||||||
|
folderMap[2] = GetTitleID(); // Set up game path instead of calling it uroot
|
||||||
|
game_dir = extract_path.parent_path();
|
||||||
|
title_dir = game_dir / GetTitleID();
|
||||||
|
|
||||||
|
for (int i = 0; i < fsTable.size(); i++) {
|
||||||
|
const u32 inode_number = fsTable[i].inode;
|
||||||
|
const u32 inode_type = fsTable[i].type;
|
||||||
|
const auto inode_name = fsTable[i].name;
|
||||||
|
|
||||||
|
if (inode_type == PFS_CURRENT_DIR) {
|
||||||
|
current_dir = folderMap[inode_number];
|
||||||
|
} else if (inode_type == PFS_PARENT_DIR) {
|
||||||
|
parent_dir = folderMap[inode_number];
|
||||||
|
// Skip uroot folder. we create our own game uid folder
|
||||||
|
if (i > 1) {
|
||||||
|
const auto par_dir = inode_number == 2 ? findDirectory(game_dir, parent_dir)
|
||||||
|
: findDirectory(title_dir, parent_dir);
|
||||||
|
const auto cur_dir = findDirectory(par_dir, current_dir);
|
||||||
|
|
||||||
|
if (cur_dir == "") {
|
||||||
|
extract_path = par_dir / current_dir;
|
||||||
|
std::filesystem::create_directories(extract_path);
|
||||||
|
} else {
|
||||||
|
extract_path = cur_dir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extractMap[inode_number] = extract_path.string();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PKG::ExtractFiles(const int& index) {
|
||||||
|
int inode_number = fsTable[index].inode;
|
||||||
|
int inode_type = fsTable[index].type;
|
||||||
|
std::string inode_name = fsTable[index].name;
|
||||||
|
|
||||||
|
if (inode_type == PFS_FILE) {
|
||||||
|
int sector_loc = iNodeBuf[inode_number].loc;
|
||||||
|
int nblocks = iNodeBuf[inode_number].Blocks;
|
||||||
|
int bsize = iNodeBuf[inode_number].Size;
|
||||||
|
std::string file_extracted = extractMap[inode_number] + "/" + inode_name;
|
||||||
|
|
||||||
|
Common::FS::IOFile inflated;
|
||||||
|
inflated.Open(file_extracted, Common::FS::FileAccessMode::Write);
|
||||||
|
|
||||||
|
Common::FS::IOFile pkgFile; // Open the file for each iteration to avoid conflict.
|
||||||
|
pkgFile.Open(pkgpath, Common::FS::FileAccessMode::Read);
|
||||||
|
|
||||||
|
int size_decompressed = 0;
|
||||||
|
std::vector<char> compressedData;
|
||||||
|
std::vector<char> decompressedData(0x10000);
|
||||||
|
|
||||||
|
u64 pfsc_buf_size = 0x11000; // extra 0x1000
|
||||||
|
std::vector<u8> pfsc(pfsc_buf_size);
|
||||||
|
std::vector<u8> pfs_decrypted(pfsc_buf_size);
|
||||||
|
|
||||||
|
for (int j = 0; j < nblocks; j++) {
|
||||||
|
u64 sectorOffset =
|
||||||
|
sectorMap[sector_loc + j]; // offset into PFSC_image and not pfs_image.
|
||||||
|
u64 sectorSize = sectorMap[sector_loc + j + 1] -
|
||||||
|
sectorOffset; // indicates if data is compressed or not.
|
||||||
|
u64 fileOffset = (pkgheader.pfs_image_offset + pfsc_offset + sectorOffset);
|
||||||
|
u64 currentSector1 =
|
||||||
|
(pfsc_offset + sectorOffset) / 0x1000; // block size is 0x1000 for xts decryption.
|
||||||
|
|
||||||
|
int sectorOffsetMask = (sectorOffset + pfsc_offset) & 0xFFFFF000;
|
||||||
|
int previousData = (sectorOffset + pfsc_offset) - sectorOffsetMask;
|
||||||
|
|
||||||
|
pkgFile.Seek(fileOffset - previousData);
|
||||||
|
pkgFile.Read(pfsc);
|
||||||
|
|
||||||
|
PKG::crypto.decryptPFS(dataKey, tweakKey, pfsc, pfs_decrypted, currentSector1);
|
||||||
|
|
||||||
|
compressedData.resize(sectorSize);
|
||||||
|
std::memcpy(compressedData.data(), pfs_decrypted.data() + previousData, sectorSize);
|
||||||
|
|
||||||
|
if (sectorSize == 0x10000) // Uncompressed data
|
||||||
|
std::memcpy(decompressedData.data(), compressedData.data(), 0x10000);
|
||||||
|
else if (sectorSize < 0x10000) // Compressed data
|
||||||
|
DecompressPFSC(compressedData, decompressedData);
|
||||||
|
|
||||||
|
size_decompressed += 0x10000;
|
||||||
|
|
||||||
|
if (j < nblocks - 1) {
|
||||||
|
inflated.WriteRaw<u8>(decompressedData.data(), decompressedData.size());
|
||||||
|
} else {
|
||||||
|
// This is to remove the zeros at the end of the file.
|
||||||
|
const u32 write_size = decompressedData.size() - (size_decompressed - bsize);
|
||||||
|
inflated.WriteRaw<u8>(decompressedData.data(), write_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pkgFile.Close();
|
||||||
|
inflated.Close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
#include "common/endian.h"
|
||||||
|
#include "core/crypto/crypto.h"
|
||||||
|
#include "pfs.h"
|
||||||
|
|
||||||
|
struct PKGHeader {
|
||||||
|
u32_be magic; // Magic
|
||||||
|
u32_be pkg_type;
|
||||||
|
u32_be pkg_0x8; // unknown field
|
||||||
|
u32_be pkg_file_count;
|
||||||
|
u32_be pkg_table_entry_count;
|
||||||
|
u16_be pkg_sc_entry_count;
|
||||||
|
u16_be pkg_table_entry_count_2; // same as pkg_entry_count
|
||||||
|
u32_be pkg_table_entry_offset; // file table offset
|
||||||
|
u32_be pkg_sc_entry_data_size;
|
||||||
|
u64_be pkg_body_offset; // offset of PKG entries
|
||||||
|
u64_be pkg_body_size; // length of all PKG entries
|
||||||
|
u64_be pkg_content_offset;
|
||||||
|
u64_be pkg_content_size;
|
||||||
|
u8 pkg_content_id[0x24]; // packages' content ID as a 36-byte string
|
||||||
|
u8 pkg_padding[0xC]; // padding
|
||||||
|
u32_be pkg_drm_type; // DRM type
|
||||||
|
u32_be pkg_content_type; // Content type
|
||||||
|
u32_be pkg_content_flags; // Content flags
|
||||||
|
u32_be pkg_promote_size;
|
||||||
|
u32_be pkg_version_date;
|
||||||
|
u32_be pkg_version_hash;
|
||||||
|
u32_be pkg_0x088;
|
||||||
|
u32_be pkg_0x08C;
|
||||||
|
u32_be pkg_0x090;
|
||||||
|
u32_be pkg_0x094;
|
||||||
|
u32_be pkg_iro_tag;
|
||||||
|
u32_be pkg_drm_type_version;
|
||||||
|
|
||||||
|
u8 pkg_zeroes_1[0x60];
|
||||||
|
|
||||||
|
/* Digest table */
|
||||||
|
u8 digest_entries1[0x20]; // sha256 digest for main entry 1
|
||||||
|
u8 digest_entries2[0x20]; // sha256 digest for main entry 2
|
||||||
|
u8 digest_table_digest[0x20]; // sha256 digest for digest table
|
||||||
|
u8 digest_body_digest[0x20]; // sha256 digest for main table
|
||||||
|
|
||||||
|
u8 pkg_zeroes_2[0x280];
|
||||||
|
|
||||||
|
u32_be pkg_0x400;
|
||||||
|
|
||||||
|
u32_be pfs_image_count; // count of PFS images
|
||||||
|
u64_be pfs_image_flags; // PFS flags
|
||||||
|
u64_be pfs_image_offset; // offset to start of external PFS image
|
||||||
|
u64_be pfs_image_size; // size of external PFS image
|
||||||
|
u64_be mount_image_offset;
|
||||||
|
u64_be mount_image_size;
|
||||||
|
u64_be pkg_size;
|
||||||
|
u32_be pfs_signed_size;
|
||||||
|
u32_be pfs_cache_size;
|
||||||
|
u8 pfs_image_digest[0x20];
|
||||||
|
u8 pfs_signed_digest[0x20];
|
||||||
|
u64_be pfs_split_size_nth_0;
|
||||||
|
u64_be pfs_split_size_nth_1;
|
||||||
|
|
||||||
|
u8 pkg_zeroes_3[0xB50];
|
||||||
|
|
||||||
|
u8 pkg_digest[0x20];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PKGEntry {
|
||||||
|
u32_be id; // File ID, useful for files without a filename entry
|
||||||
|
u32_be filename_offset; // Offset into the filenames table (ID 0x200) where this file's name is
|
||||||
|
// located
|
||||||
|
u32_be flags1; // Flags including encrypted flag, etc
|
||||||
|
u32_be flags2; // Flags including encryption key index, etc
|
||||||
|
u32_be offset; // Offset into PKG to find the file
|
||||||
|
u32_be size; // Size of the file
|
||||||
|
u64_be padding; // blank padding
|
||||||
|
};
|
||||||
|
static_assert(sizeof(PKGEntry) == 32);
|
||||||
|
|
||||||
|
class PKG {
|
||||||
|
public:
|
||||||
|
PKG();
|
||||||
|
~PKG();
|
||||||
|
|
||||||
|
bool Open(const std::string& filepath);
|
||||||
|
void ExtractFiles(const int& index);
|
||||||
|
bool Extract(const std::string& filepath, const std::filesystem::path& extract,
|
||||||
|
std::string& failreason);
|
||||||
|
|
||||||
|
u32 GetNumberOfFiles() {
|
||||||
|
return fsTable.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 GetPkgSize() {
|
||||||
|
return pkgSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view GetTitleID() {
|
||||||
|
return std::string_view(pkgTitleID, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Crypto crypto;
|
||||||
|
std::vector<u8> pkg;
|
||||||
|
u64 pkgSize = 0;
|
||||||
|
char pkgTitleID[9];
|
||||||
|
PKGHeader pkgheader;
|
||||||
|
|
||||||
|
std::unordered_map<int, std::string> folderMap;
|
||||||
|
std::unordered_map<int, std::string> extractMap;
|
||||||
|
std::vector<pfs_fs_table> fsTable;
|
||||||
|
std::vector<Inode> iNodeBuf;
|
||||||
|
std::vector<u64> sectorMap;
|
||||||
|
u64 pfsc_offset;
|
||||||
|
|
||||||
|
std::array<CryptoPP::byte, 32> dk3_;
|
||||||
|
std::array<CryptoPP::byte, 32> ivKey;
|
||||||
|
std::array<CryptoPP::byte, 256> imgKey;
|
||||||
|
std::array<CryptoPP::byte, 32> ekpfsKey;
|
||||||
|
std::array<CryptoPP::byte, 16> dataKey;
|
||||||
|
std::array<CryptoPP::byte, 16> tweakKey;
|
||||||
|
|
||||||
|
std::filesystem::path pkgpath;
|
||||||
|
std::filesystem::path current_dir;
|
||||||
|
std::filesystem::path parent_dir;
|
||||||
|
std::filesystem::path extract_path;
|
||||||
|
std::filesystem::path game_dir;
|
||||||
|
std::filesystem::path title_dir;
|
||||||
|
};
|
|
@ -0,0 +1,638 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include "pkg_type.h"
|
||||||
|
|
||||||
|
struct PkgEntryValue {
|
||||||
|
u32 type;
|
||||||
|
std::string_view name;
|
||||||
|
|
||||||
|
operator u32() const noexcept {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr static std::array<PkgEntryValue, 611> PkgEntries = {{
|
||||||
|
{0x0001, "digests"},
|
||||||
|
{0x0010, "entry_keys"},
|
||||||
|
{0x0020, "image_key"},
|
||||||
|
{0x0080, "general_digests"},
|
||||||
|
{0x0100, "metas"},
|
||||||
|
{0x0200, "entry_names"},
|
||||||
|
{0x0400, "license.dat"},
|
||||||
|
{0x0401, "license.info"},
|
||||||
|
{0x0402, "nptitle.dat"},
|
||||||
|
{0x0403, "npbind.dat"},
|
||||||
|
{0x0404, "selfinfo.dat"},
|
||||||
|
{0x0406, "imageinfo.dat"},
|
||||||
|
{0x0407, "target-deltainfo.dat"},
|
||||||
|
{0x0408, "origin-deltainfo.dat"},
|
||||||
|
{0x0409, "psreserved.dat"},
|
||||||
|
{0x1000, "param.sfo"},
|
||||||
|
{0x1001, "playgo-chunk.dat"},
|
||||||
|
{0x1002, "playgo-chunk.sha"},
|
||||||
|
{0x1003, "playgo-manifest.xml"},
|
||||||
|
{0x1004, "pronunciation.xml"},
|
||||||
|
{0x1005, "pronunciation.sig"},
|
||||||
|
{0x1006, "pic1.png"},
|
||||||
|
{0x1007, "pubtoolinfo.dat"},
|
||||||
|
{0x1008, "app/playgo-chunk.dat"},
|
||||||
|
{0x1009, "app/playgo-chunk.sha"},
|
||||||
|
{0x100A, "app/playgo-manifest.xml"},
|
||||||
|
{0x100B, "shareparam.json"},
|
||||||
|
{0x100C, "shareoverlayimage.png"},
|
||||||
|
{0x100D, "save_data.png"},
|
||||||
|
{0x100E, "shareprivacyguardimage.png"},
|
||||||
|
{0x1200, "icon0.png"},
|
||||||
|
{0x1201, "icon0_00.png"},
|
||||||
|
{0x1202, "icon0_01.png"},
|
||||||
|
{0x1203, "icon0_02.png"},
|
||||||
|
{0x1204, "icon0_03.png"},
|
||||||
|
{0x1205, "icon0_04.png"},
|
||||||
|
{0x1206, "icon0_05.png"},
|
||||||
|
{0x1207, "icon0_06.png"},
|
||||||
|
{0x1208, "icon0_07.png"},
|
||||||
|
{0x1209, "icon0_08.png"},
|
||||||
|
{0x120A, "icon0_09.png"},
|
||||||
|
{0x120B, "icon0_10.png"},
|
||||||
|
{0x120C, "icon0_11.png"},
|
||||||
|
{0x120D, "icon0_12.png"},
|
||||||
|
{0x120E, "icon0_13.png"},
|
||||||
|
{0x120F, "icon0_14.png"},
|
||||||
|
{0x1210, "icon0_15.png"},
|
||||||
|
{0x1211, "icon0_16.png"},
|
||||||
|
{0x1212, "icon0_17.png"},
|
||||||
|
{0x1213, "icon0_18.png"},
|
||||||
|
{0x1214, "icon0_19.png"},
|
||||||
|
{0x1215, "icon0_20.png"},
|
||||||
|
{0x1216, "icon0_21.png"},
|
||||||
|
{0x1217, "icon0_22.png"},
|
||||||
|
{0x1218, "icon0_23.png"},
|
||||||
|
{0x1219, "icon0_24.png"},
|
||||||
|
{0x121A, "icon0_25.png"},
|
||||||
|
{0x121B, "icon0_26.png"},
|
||||||
|
{0x121C, "icon0_27.png"},
|
||||||
|
{0x121D, "icon0_28.png"},
|
||||||
|
{0x121E, "icon0_29.png"},
|
||||||
|
{0x121F, "icon0_30.png"},
|
||||||
|
{0x1220, "pic0.png"},
|
||||||
|
{0x1240, "snd0.at9"},
|
||||||
|
{0x1241, "pic1_00.png"},
|
||||||
|
{0x1242, "pic1_01.png"},
|
||||||
|
{0x1243, "pic1_02.png"},
|
||||||
|
{0x1244, "pic1_03.png"},
|
||||||
|
{0x1245, "pic1_04.png"},
|
||||||
|
{0x1246, "pic1_05.png"},
|
||||||
|
{0x1247, "pic1_06.png"},
|
||||||
|
{0x1248, "pic1_07.png"},
|
||||||
|
{0x1249, "pic1_08.png"},
|
||||||
|
{0x124A, "pic1_09.png"},
|
||||||
|
{0x124B, "pic1_10.png"},
|
||||||
|
{0x124C, "pic1_11.png"},
|
||||||
|
{0x124D, "pic1_12.png"},
|
||||||
|
{0x124E, "pic1_13.png"},
|
||||||
|
{0x124F, "pic1_14.png"},
|
||||||
|
{0x1250, "pic1_15.png"},
|
||||||
|
{0x1251, "pic1_16.png"},
|
||||||
|
{0x1252, "pic1_17.png"},
|
||||||
|
{0x1253, "pic1_18.png"},
|
||||||
|
{0x1254, "pic1_19.png"},
|
||||||
|
{0x1255, "pic1_20.png"},
|
||||||
|
{0x1256, "pic1_21.png"},
|
||||||
|
{0x1257, "pic1_22.png"},
|
||||||
|
{0x1258, "pic1_23.png"},
|
||||||
|
{0x1259, "pic1_24.png"},
|
||||||
|
{0x125A, "pic1_25.png"},
|
||||||
|
{0x125B, "pic1_26.png"},
|
||||||
|
{0x125C, "pic1_27.png"},
|
||||||
|
{0x125D, "pic1_28.png"},
|
||||||
|
{0x125E, "pic1_29.png"},
|
||||||
|
{0x125F, "pic1_30.png"},
|
||||||
|
{0x1260, "changeinfo/changeinfo.xml"},
|
||||||
|
{0x1261, "changeinfo/changeinfo_00.xml"},
|
||||||
|
{0x1262, "changeinfo/changeinfo_01.xml"},
|
||||||
|
{0x1263, "changeinfo/changeinfo_02.xml"},
|
||||||
|
{0x1264, "changeinfo/changeinfo_03.xml"},
|
||||||
|
{0x1265, "changeinfo/changeinfo_04.xml"},
|
||||||
|
{0x1266, "changeinfo/changeinfo_05.xml"},
|
||||||
|
{0x1267, "changeinfo/changeinfo_06.xml"},
|
||||||
|
{0x1268, "changeinfo/changeinfo_07.xml"},
|
||||||
|
{0x1269, "changeinfo/changeinfo_08.xml"},
|
||||||
|
{0x126A, "changeinfo/changeinfo_09.xml"},
|
||||||
|
{0x126B, "changeinfo/changeinfo_10.xml"},
|
||||||
|
{0x126C, "changeinfo/changeinfo_11.xml"},
|
||||||
|
{0x126D, "changeinfo/changeinfo_12.xml"},
|
||||||
|
{0x126E, "changeinfo/changeinfo_13.xml"},
|
||||||
|
{0x126F, "changeinfo/changeinfo_14.xml"},
|
||||||
|
{0x1270, "changeinfo/changeinfo_15.xml"},
|
||||||
|
{0x1271, "changeinfo/changeinfo_16.xml"},
|
||||||
|
{0x1272, "changeinfo/changeinfo_17.xml"},
|
||||||
|
{0x1273, "changeinfo/changeinfo_18.xml"},
|
||||||
|
{0x1274, "changeinfo/changeinfo_19.xml"},
|
||||||
|
{0x1275, "changeinfo/changeinfo_20.xml"},
|
||||||
|
{0x1276, "changeinfo/changeinfo_21.xml"},
|
||||||
|
{0x1277, "changeinfo/changeinfo_22.xml"},
|
||||||
|
{0x1278, "changeinfo/changeinfo_23.xml"},
|
||||||
|
{0x1279, "changeinfo/changeinfo_24.xml"},
|
||||||
|
{0x127A, "changeinfo/changeinfo_25.xml"},
|
||||||
|
{0x127B, "changeinfo/changeinfo_26.xml"},
|
||||||
|
{0x127C, "changeinfo/changeinfo_27.xml"},
|
||||||
|
{0x127D, "changeinfo/changeinfo_28.xml"},
|
||||||
|
{0x127E, "changeinfo/changeinfo_29.xml"},
|
||||||
|
{0x127F, "changeinfo/changeinfo_30.xml"},
|
||||||
|
{0x1280, "icon0.dds"},
|
||||||
|
{0x1281, "icon0_00.dds"},
|
||||||
|
{0x1282, "icon0_01.dds"},
|
||||||
|
{0x1283, "icon0_02.dds"},
|
||||||
|
{0x1284, "icon0_03.dds"},
|
||||||
|
{0x1285, "icon0_04.dds"},
|
||||||
|
{0x1286, "icon0_05.dds"},
|
||||||
|
{0x1287, "icon0_06.dds"},
|
||||||
|
{0x1288, "icon0_07.dds"},
|
||||||
|
{0x1289, "icon0_08.dds"},
|
||||||
|
{0x128A, "icon0_09.dds"},
|
||||||
|
{0x128B, "icon0_10.dds"},
|
||||||
|
{0x128C, "icon0_11.dds"},
|
||||||
|
{0x128D, "icon0_12.dds"},
|
||||||
|
{0x128E, "icon0_13.dds"},
|
||||||
|
{0x128F, "icon0_14.dds"},
|
||||||
|
{0x1290, "icon0_15.dds"},
|
||||||
|
{0x1291, "icon0_16.dds"},
|
||||||
|
{0x1292, "icon0_17.dds"},
|
||||||
|
{0x1293, "icon0_18.dds"},
|
||||||
|
{0x1294, "icon0_19.dds"},
|
||||||
|
{0x1295, "icon0_20.dds"},
|
||||||
|
{0x1296, "icon0_21.dds"},
|
||||||
|
{0x1297, "icon0_22.dds"},
|
||||||
|
{0x1298, "icon0_23.dds"},
|
||||||
|
{0x1299, "icon0_24.dds"},
|
||||||
|
{0x129A, "icon0_25.dds"},
|
||||||
|
{0x129B, "icon0_26.dds"},
|
||||||
|
{0x129C, "icon0_27.dds"},
|
||||||
|
{0x129D, "icon0_28.dds"},
|
||||||
|
{0x129E, "icon0_29.dds"},
|
||||||
|
{0x129F, "icon0_30.dds"},
|
||||||
|
{0x12A0, "pic0.dds"},
|
||||||
|
{0x12C0, "pic1.dds"},
|
||||||
|
{0x12C1, "pic1_00.dds"},
|
||||||
|
{0x12C2, "pic1_01.dds"},
|
||||||
|
{0x12C3, "pic1_02.dds"},
|
||||||
|
{0x12C4, "pic1_03.dds"},
|
||||||
|
{0x12C5, "pic1_04.dds"},
|
||||||
|
{0x12C6, "pic1_05.dds"},
|
||||||
|
{0x12C7, "pic1_06.dds"},
|
||||||
|
{0x12C8, "pic1_07.dds"},
|
||||||
|
{0x12C9, "pic1_08.dds"},
|
||||||
|
{0x12CA, "pic1_09.dds"},
|
||||||
|
{0x12CB, "pic1_10.dds"},
|
||||||
|
{0x12CC, "pic1_11.dds"},
|
||||||
|
{0x12CD, "pic1_12.dds"},
|
||||||
|
{0x12CE, "pic1_13.dds"},
|
||||||
|
{0x12CF, "pic1_14.dds"},
|
||||||
|
{0x12D0, "pic1_15.dds"},
|
||||||
|
{0x12D1, "pic1_16.dds"},
|
||||||
|
{0x12D2, "pic1_17.dds"},
|
||||||
|
{0x12D3, "pic1_18.dds"},
|
||||||
|
{0x12D4, "pic1_19.dds"},
|
||||||
|
{0x12D5, "pic1_20.dds"},
|
||||||
|
{0x12D6, "pic1_21.dds"},
|
||||||
|
{0x12D7, "pic1_22.dds"},
|
||||||
|
{0x12D8, "pic1_23.dds"},
|
||||||
|
{0x12D9, "pic1_24.dds"},
|
||||||
|
{0x12DA, "pic1_25.dds"},
|
||||||
|
{0x12DB, "pic1_26.dds"},
|
||||||
|
{0x12DC, "pic1_27.dds"},
|
||||||
|
{0x12DD, "pic1_28.dds"},
|
||||||
|
{0x12DE, "pic1_29.dds"},
|
||||||
|
{0x12DF, "pic1_30.dds"},
|
||||||
|
{0x1400, "trophy/trophy00.trp"},
|
||||||
|
{0x1401, "trophy/trophy01.trp"},
|
||||||
|
{0x1402, "trophy/trophy02.trp"},
|
||||||
|
{0x1403, "trophy/trophy03.trp"},
|
||||||
|
{0x1404, "trophy/trophy04.trp"},
|
||||||
|
{0x1405, "trophy/trophy05.trp"},
|
||||||
|
{0x1406, "trophy/trophy06.trp"},
|
||||||
|
{0x1407, "trophy/trophy07.trp"},
|
||||||
|
{0x1408, "trophy/trophy08.trp"},
|
||||||
|
{0x1409, "trophy/trophy09.trp"},
|
||||||
|
{0x140A, "trophy/trophy10.trp"},
|
||||||
|
{0x140B, "trophy/trophy11.trp"},
|
||||||
|
{0x140C, "trophy/trophy12.trp"},
|
||||||
|
{0x140D, "trophy/trophy13.trp"},
|
||||||
|
{0x140E, "trophy/trophy14.trp"},
|
||||||
|
{0x140F, "trophy/trophy15.trp"},
|
||||||
|
{0x1410, "trophy/trophy16.trp"},
|
||||||
|
{0x1411, "trophy/trophy17.trp"},
|
||||||
|
{0x1412, "trophy/trophy18.trp"},
|
||||||
|
{0x1413, "trophy/trophy19.trp"},
|
||||||
|
{0x1414, "trophy/trophy20.trp"},
|
||||||
|
{0x1415, "trophy/trophy21.trp"},
|
||||||
|
{0x1416, "trophy/trophy22.trp"},
|
||||||
|
{0x1417, "trophy/trophy23.trp"},
|
||||||
|
{0x1418, "trophy/trophy24.trp"},
|
||||||
|
{0x1419, "trophy/trophy25.trp"},
|
||||||
|
{0x141A, "trophy/trophy26.trp"},
|
||||||
|
{0x141B, "trophy/trophy27.trp"},
|
||||||
|
{0x141C, "trophy/trophy28.trp"},
|
||||||
|
{0x141D, "trophy/trophy29.trp"},
|
||||||
|
{0x141E, "trophy/trophy30.trp"},
|
||||||
|
{0x141F, "trophy/trophy31.trp"},
|
||||||
|
{0x1420, "trophy/trophy32.trp"},
|
||||||
|
{0x1421, "trophy/trophy33.trp"},
|
||||||
|
{0x1422, "trophy/trophy34.trp"},
|
||||||
|
{0x1423, "trophy/trophy35.trp"},
|
||||||
|
{0x1424, "trophy/trophy36.trp"},
|
||||||
|
{0x1425, "trophy/trophy37.trp"},
|
||||||
|
{0x1426, "trophy/trophy38.trp"},
|
||||||
|
{0x1427, "trophy/trophy39.trp"},
|
||||||
|
{0x1428, "trophy/trophy40.trp"},
|
||||||
|
{0x1429, "trophy/trophy41.trp"},
|
||||||
|
{0x142A, "trophy/trophy42.trp"},
|
||||||
|
{0x142B, "trophy/trophy43.trp"},
|
||||||
|
{0x142C, "trophy/trophy44.trp"},
|
||||||
|
{0x142D, "trophy/trophy45.trp"},
|
||||||
|
{0x142E, "trophy/trophy46.trp"},
|
||||||
|
{0x142F, "trophy/trophy47.trp"},
|
||||||
|
{0x1430, "trophy/trophy48.trp"},
|
||||||
|
{0x1431, "trophy/trophy49.trp"},
|
||||||
|
{0x1432, "trophy/trophy50.trp"},
|
||||||
|
{0x1433, "trophy/trophy51.trp"},
|
||||||
|
{0x1434, "trophy/trophy52.trp"},
|
||||||
|
{0x1435, "trophy/trophy53.trp"},
|
||||||
|
{0x1436, "trophy/trophy54.trp"},
|
||||||
|
{0x1437, "trophy/trophy55.trp"},
|
||||||
|
{0x1438, "trophy/trophy56.trp"},
|
||||||
|
{0x1439, "trophy/trophy57.trp"},
|
||||||
|
{0x143A, "trophy/trophy58.trp"},
|
||||||
|
{0x143B, "trophy/trophy59.trp"},
|
||||||
|
{0x143C, "trophy/trophy60.trp"},
|
||||||
|
{0x143D, "trophy/trophy61.trp"},
|
||||||
|
{0x143E, "trophy/trophy62.trp"},
|
||||||
|
{0x143F, "trophy/trophy63.trp"},
|
||||||
|
{0x1440, "trophy/trophy64.trp"},
|
||||||
|
{0x1441, "trophy/trophy65.trp"},
|
||||||
|
{0x1442, "trophy/trophy66.trp"},
|
||||||
|
{0x1443, "trophy/trophy67.trp"},
|
||||||
|
{0x1444, "trophy/trophy68.trp"},
|
||||||
|
{0x1445, "trophy/trophy69.trp"},
|
||||||
|
{0x1446, "trophy/trophy70.trp"},
|
||||||
|
{0x1447, "trophy/trophy71.trp"},
|
||||||
|
{0x1448, "trophy/trophy72.trp"},
|
||||||
|
{0x1449, "trophy/trophy73.trp"},
|
||||||
|
{0x144A, "trophy/trophy74.trp"},
|
||||||
|
{0x144B, "trophy/trophy75.trp"},
|
||||||
|
{0x144C, "trophy/trophy76.trp"},
|
||||||
|
{0x144D, "trophy/trophy77.trp"},
|
||||||
|
{0x144E, "trophy/trophy78.trp"},
|
||||||
|
{0x144F, "trophy/trophy79.trp"},
|
||||||
|
{0x1450, "trophy/trophy80.trp"},
|
||||||
|
{0x1451, "trophy/trophy81.trp"},
|
||||||
|
{0x1452, "trophy/trophy82.trp"},
|
||||||
|
{0x1453, "trophy/trophy83.trp"},
|
||||||
|
{0x1454, "trophy/trophy84.trp"},
|
||||||
|
{0x1455, "trophy/trophy85.trp"},
|
||||||
|
{0x1456, "trophy/trophy86.trp"},
|
||||||
|
{0x1457, "trophy/trophy87.trp"},
|
||||||
|
{0x1458, "trophy/trophy88.trp"},
|
||||||
|
{0x1459, "trophy/trophy89.trp"},
|
||||||
|
{0x145A, "trophy/trophy90.trp"},
|
||||||
|
{0x145B, "trophy/trophy91.trp"},
|
||||||
|
{0x145C, "trophy/trophy92.trp"},
|
||||||
|
{0x145D, "trophy/trophy93.trp"},
|
||||||
|
{0x145E, "trophy/trophy94.trp"},
|
||||||
|
{0x145F, "trophy/trophy95.trp"},
|
||||||
|
{0x1460, "trophy/trophy96.trp"},
|
||||||
|
{0x1461, "trophy/trophy97.trp"},
|
||||||
|
{0x1462, "trophy/trophy98.trp"},
|
||||||
|
{0x1463, "trophy/trophy99.trp"},
|
||||||
|
{0x1600, "keymap_rp/001.png"},
|
||||||
|
{0x1601, "keymap_rp/002.png"},
|
||||||
|
{0x1602, "keymap_rp/003.png"},
|
||||||
|
{0x1603, "keymap_rp/004.png"},
|
||||||
|
{0x1604, "keymap_rp/005.png"},
|
||||||
|
{0x1605, "keymap_rp/006.png"},
|
||||||
|
{0x1606, "keymap_rp/007.png"},
|
||||||
|
{0x1607, "keymap_rp/008.png"},
|
||||||
|
{0x1608, "keymap_rp/009.png"},
|
||||||
|
{0x1609, "keymap_rp/010.png"},
|
||||||
|
{0x1610, "keymap_rp/00/001.png"},
|
||||||
|
{0x1611, "keymap_rp/00/002.png"},
|
||||||
|
{0x1612, "keymap_rp/00/003.png"},
|
||||||
|
{0x1613, "keymap_rp/00/004.png"},
|
||||||
|
{0x1614, "keymap_rp/00/005.png"},
|
||||||
|
{0x1615, "keymap_rp/00/006.png"},
|
||||||
|
{0x1616, "keymap_rp/00/007.png"},
|
||||||
|
{0x1617, "keymap_rp/00/008.png"},
|
||||||
|
{0x1618, "keymap_rp/00/009.png"},
|
||||||
|
{0x1619, "keymap_rp/00/010.png"},
|
||||||
|
{0x1620, "keymap_rp/01/001.png"},
|
||||||
|
{0x1621, "keymap_rp/01/002.png"},
|
||||||
|
{0x1622, "keymap_rp/01/003.png"},
|
||||||
|
{0x1623, "keymap_rp/01/004.png"},
|
||||||
|
{0x1624, "keymap_rp/01/005.png"},
|
||||||
|
{0x1625, "keymap_rp/01/006.png"},
|
||||||
|
{0x1626, "keymap_rp/01/007.png"},
|
||||||
|
{0x1627, "keymap_rp/01/008.png"},
|
||||||
|
{0x1628, "keymap_rp/01/009.png"},
|
||||||
|
{0x1629, "keymap_rp/01/010.png"},
|
||||||
|
{0x1630, "keymap_rp/02/001.png"},
|
||||||
|
{0x1631, "keymap_rp/02/002.png"},
|
||||||
|
{0x1632, "keymap_rp/02/003.png"},
|
||||||
|
{0x1633, "keymap_rp/02/004.png"},
|
||||||
|
{0x1634, "keymap_rp/02/005.png"},
|
||||||
|
{0x1635, "keymap_rp/02/006.png"},
|
||||||
|
{0x1636, "keymap_rp/02/007.png"},
|
||||||
|
{0x1637, "keymap_rp/02/008.png"},
|
||||||
|
{0x1638, "keymap_rp/02/009.png"},
|
||||||
|
{0x1639, "keymap_rp/02/010.png"},
|
||||||
|
{0x1640, "keymap_rp/03/001.png"},
|
||||||
|
{0x1641, "keymap_rp/03/002.png"},
|
||||||
|
{0x1642, "keymap_rp/03/003.png"},
|
||||||
|
{0x1643, "keymap_rp/03/004.png"},
|
||||||
|
{0x1644, "keymap_rp/03/005.png"},
|
||||||
|
{0x1645, "keymap_rp/03/006.png"},
|
||||||
|
{0x1646, "keymap_rp/03/007.png"},
|
||||||
|
{0x1647, "keymap_rp/03/008.png"},
|
||||||
|
{0x1648, "keymap_rp/03/0010.png"},
|
||||||
|
{0x1650, "keymap_rp/04/001.png"},
|
||||||
|
{0x1651, "keymap_rp/04/002.png"},
|
||||||
|
{0x1652, "keymap_rp/04/003.png"},
|
||||||
|
{0x1653, "keymap_rp/04/004.png"},
|
||||||
|
{0x1654, "keymap_rp/04/005.png"},
|
||||||
|
{0x1655, "keymap_rp/04/006.png"},
|
||||||
|
{0x1656, "keymap_rp/04/007.png"},
|
||||||
|
{0x1657, "keymap_rp/04/008.png"},
|
||||||
|
{0x1658, "keymap_rp/04/009.png"},
|
||||||
|
{0x1659, "keymap_rp/04/010.png"},
|
||||||
|
{0x1660, "keymap_rp/05/001.png"},
|
||||||
|
{0x1661, "keymap_rp/05/002.png"},
|
||||||
|
{0x1662, "keymap_rp/05/003.png"},
|
||||||
|
{0x1663, "keymap_rp/05/004.png"},
|
||||||
|
{0x1664, "keymap_rp/05/005.png"},
|
||||||
|
{0x1665, "keymap_rp/05/006.png"},
|
||||||
|
{0x1666, "keymap_rp/05/007.png"},
|
||||||
|
{0x1667, "keymap_rp/05/008.png"},
|
||||||
|
{0x1668, "keymap_rp/05/009.png"},
|
||||||
|
{0x1669, "keymap_rp/05/010.png"},
|
||||||
|
{0x1670, "keymap_rp/06/001.png"},
|
||||||
|
{0x1671, "keymap_rp/06/002.png"},
|
||||||
|
{0x1672, "keymap_rp/06/003.png"},
|
||||||
|
{0x1673, "keymap_rp/06/004.png"},
|
||||||
|
{0x1674, "keymap_rp/06/005.png"},
|
||||||
|
{0x1675, "keymap_rp/06/006.png"},
|
||||||
|
{0x1676, "keymap_rp/06/007.png"},
|
||||||
|
{0x1677, "keymap_rp/06/008.png"},
|
||||||
|
{0x1678, "keymap_rp/06/009.png"},
|
||||||
|
{0x1679, "keymap_rp/06/010.png"},
|
||||||
|
{0x1680, "keymap_rp/07/001.png"},
|
||||||
|
{0x1681, "keymap_rp/07/002.png"},
|
||||||
|
{0x1682, "keymap_rp/07/003.png"},
|
||||||
|
{0x1683, "keymap_rp/07/004.png"},
|
||||||
|
{0x1684, "keymap_rp/07/005.png"},
|
||||||
|
{0x1685, "keymap_rp/07/006.png"},
|
||||||
|
{0x1686, "keymap_rp/07/007.png"},
|
||||||
|
{0x1687, "keymap_rp/07/008.png"},
|
||||||
|
{0x1688, "keymap_rp/07/009.png"},
|
||||||
|
{0x1689, "keymap_rp/07/010.png"},
|
||||||
|
{0x1690, "keymap_rp/08/001.png"},
|
||||||
|
{0x1691, "keymap_rp/08/002.png"},
|
||||||
|
{0x1692, "keymap_rp/08/003.png"},
|
||||||
|
{0x1693, "keymap_rp/08/004.png"},
|
||||||
|
{0x1694, "keymap_rp/08/005.png"},
|
||||||
|
{0x1695, "keymap_rp/08/006.png"},
|
||||||
|
{0x1696, "keymap_rp/08/007.png"},
|
||||||
|
{0x1697, "keymap_rp/08/008.png"},
|
||||||
|
{0x1698, "keymap_rp/08/009.png"},
|
||||||
|
{0x1699, "keymap_rp/08/010.png"},
|
||||||
|
{0x16A0, "keymap_rp/09/001.png"},
|
||||||
|
{0x16A1, "keymap_rp/09/002.png"},
|
||||||
|
{0x16A2, "keymap_rp/09/003.png"},
|
||||||
|
{0x16A3, "keymap_rp/09/004.png"},
|
||||||
|
{0x16A4, "keymap_rp/09/005.png"},
|
||||||
|
{0x16A5, "keymap_rp/09/006.png"},
|
||||||
|
{0x16A6, "keymap_rp/09/007.png"},
|
||||||
|
{0x16A7, "keymap_rp/09/008.png"},
|
||||||
|
{0x16A8, "keymap_rp/09/009.png"},
|
||||||
|
{0x16A9, "keymap_rp/09/010.png"},
|
||||||
|
{0x16B0, "keymap_rp/10/001.png"},
|
||||||
|
{0x16B1, "keymap_rp/10/002.png"},
|
||||||
|
{0x16B2, "keymap_rp/10/003.png"},
|
||||||
|
{0x16B3, "keymap_rp/10/004.png"},
|
||||||
|
{0x16B4, "keymap_rp/10/005.png"},
|
||||||
|
{0x16B5, "keymap_rp/10/006.png"},
|
||||||
|
{0x16B6, "keymap_rp/10/007.png"},
|
||||||
|
{0x16B7, "keymap_rp/10/008.png"},
|
||||||
|
{0x16B8, "keymap_rp/10/009.png"},
|
||||||
|
{0x16B9, "keymap_rp/10/010.png"},
|
||||||
|
{0x16C0, "keymap_rp/11/001.png"},
|
||||||
|
{0x16C1, "keymap_rp/11/002.png"},
|
||||||
|
{0x16C2, "keymap_rp/11/003.png"},
|
||||||
|
{0x16C3, "keymap_rp/11/004.png"},
|
||||||
|
{0x16C4, "keymap_rp/11/005.png"},
|
||||||
|
{0x16C5, "keymap_rp/11/006.png"},
|
||||||
|
{0x16C6, "keymap_rp/11/007.png"},
|
||||||
|
{0x16C7, "keymap_rp/11/008.png"},
|
||||||
|
{0x16C8, "keymap_rp/11/009.png"},
|
||||||
|
{0x16C9, "keymap_rp/11/010.png"},
|
||||||
|
{0x16D0, "keymap_rp/12/001.png"},
|
||||||
|
{0x16D1, "keymap_rp/12/002.png"},
|
||||||
|
{0x16D2, "keymap_rp/12/003.png"},
|
||||||
|
{0x16D3, "keymap_rp/12/004.png"},
|
||||||
|
{0x16D4, "keymap_rp/12/005.png"},
|
||||||
|
{0x16D5, "keymap_rp/12/006.png"},
|
||||||
|
{0x16D6, "keymap_rp/12/007.png"},
|
||||||
|
{0x16D7, "keymap_rp/12/008.png"},
|
||||||
|
{0x16D8, "keymap_rp/12/009.png"},
|
||||||
|
{0x16D9, "keymap_rp/12/010.png"},
|
||||||
|
{0x16E0, "keymap_rp/13/001.png"},
|
||||||
|
{0x16E1, "keymap_rp/13/002.png"},
|
||||||
|
{0x16E2, "keymap_rp/13/003.png"},
|
||||||
|
{0x16E3, "keymap_rp/13/004.png"},
|
||||||
|
{0x16E4, "keymap_rp/13/005.png"},
|
||||||
|
{0x16E5, "keymap_rp/13/006.png"},
|
||||||
|
{0x16E6, "keymap_rp/13/007.png"},
|
||||||
|
{0x16E7, "keymap_rp/13/008.png"},
|
||||||
|
{0x16E8, "keymap_rp/13/009.png"},
|
||||||
|
{0x16E9, "keymap_rp/13/010.png"},
|
||||||
|
{0x16F0, "keymap_rp/14/001.png"},
|
||||||
|
{0x16F1, "keymap_rp/14/002.png"},
|
||||||
|
{0x16F2, "keymap_rp/14/003.png"},
|
||||||
|
{0x16F3, "keymap_rp/14/004.png"},
|
||||||
|
{0x16F4, "keymap_rp/14/005.png"},
|
||||||
|
{0x16F5, "keymap_rp/14/006.png"},
|
||||||
|
{0x16F6, "keymap_rp/14/007.png"},
|
||||||
|
{0x16F7, "keymap_rp/14/008.png"},
|
||||||
|
{0x16F8, "keymap_rp/14/009.png"},
|
||||||
|
{0x16F9, "keymap_rp/14/010.png"},
|
||||||
|
{0x1700, "keymap_rp/15/001.png"},
|
||||||
|
{0x1701, "keymap_rp/15/002.png"},
|
||||||
|
{0x1702, "keymap_rp/15/003.png"},
|
||||||
|
{0x1703, "keymap_rp/15/004.png"},
|
||||||
|
{0x1704, "keymap_rp/15/005.png"},
|
||||||
|
{0x1705, "keymap_rp/15/006.png"},
|
||||||
|
{0x1706, "keymap_rp/15/007.png"},
|
||||||
|
{0x1707, "keymap_rp/15/008.png"},
|
||||||
|
{0x1708, "keymap_rp/15/009.png"},
|
||||||
|
{0x1709, "keymap_rp/15/010.png"},
|
||||||
|
{0x1710, "keymap_rp/16/001.png"},
|
||||||
|
{0x1711, "keymap_rp/16/002.png"},
|
||||||
|
{0x1712, "keymap_rp/16/003.png"},
|
||||||
|
{0x1713, "keymap_rp/16/004.png"},
|
||||||
|
{0x1714, "keymap_rp/16/005.png"},
|
||||||
|
{0x1715, "keymap_rp/16/006.png"},
|
||||||
|
{0x1716, "keymap_rp/16/007.png"},
|
||||||
|
{0x1717, "keymap_rp/16/008.png"},
|
||||||
|
{0x1718, "keymap_rp/16/009.png"},
|
||||||
|
{0x1719, "keymap_rp/16/010.png"},
|
||||||
|
{0x1720, "keymap_rp/17/001.png"},
|
||||||
|
{0x1721, "keymap_rp/17/002.png"},
|
||||||
|
{0x1722, "keymap_rp/17/003.png"},
|
||||||
|
{0x1723, "keymap_rp/17/004.png"},
|
||||||
|
{0x1724, "keymap_rp/17/005.png"},
|
||||||
|
{0x1725, "keymap_rp/17/006.png"},
|
||||||
|
{0x1726, "keymap_rp/17/007.png"},
|
||||||
|
{0x1727, "keymap_rp/17/008.png"},
|
||||||
|
{0x1728, "keymap_rp/17/009.png"},
|
||||||
|
{0x1729, "keymap_rp/17/010.png"},
|
||||||
|
{0x1730, "keymap_rp/18/001.png"},
|
||||||
|
{0x1731, "keymap_rp/18/002.png"},
|
||||||
|
{0x1732, "keymap_rp/18/003.png"},
|
||||||
|
{0x1733, "keymap_rp/18/004.png"},
|
||||||
|
{0x1734, "keymap_rp/18/005.png"},
|
||||||
|
{0x1735, "keymap_rp/18/006.png"},
|
||||||
|
{0x1736, "keymap_rp/18/007.png"},
|
||||||
|
{0x1737, "keymap_rp/18/008.png"},
|
||||||
|
{0x1738, "keymap_rp/18/009.png"},
|
||||||
|
{0x1739, "keymap_rp/18/010.png"},
|
||||||
|
{0x1740, "keymap_rp/19/001.png"},
|
||||||
|
{0x1741, "keymap_rp/19/002.png"},
|
||||||
|
{0x1742, "keymap_rp/19/003.png"},
|
||||||
|
{0x1743, "keymap_rp/19/004.png"},
|
||||||
|
{0x1744, "keymap_rp/19/005.png"},
|
||||||
|
{0x1745, "keymap_rp/19/006.png"},
|
||||||
|
{0x1746, "keymap_rp/19/007.png"},
|
||||||
|
{0x1747, "keymap_rp/19/008.png"},
|
||||||
|
{0x1748, "keymap_rp/19/009.png"},
|
||||||
|
{0x1749, "keymap_rp/19/010.png"},
|
||||||
|
{0x1750, "keymap_rp/20/001.png"},
|
||||||
|
{0x1751, "keymap_rp/20/002.png"},
|
||||||
|
{0x1752, "keymap_rp/20/003.png"},
|
||||||
|
{0x1753, "keymap_rp/20/004.png"},
|
||||||
|
{0x1754, "keymap_rp/20/005.png"},
|
||||||
|
{0x1755, "keymap_rp/20/006.png"},
|
||||||
|
{0x1756, "keymap_rp/20/007.png"},
|
||||||
|
{0x1757, "keymap_rp/20/008.png"},
|
||||||
|
{0x1758, "keymap_rp/20/009.png"},
|
||||||
|
{0x1759, "keymap_rp/20/010.png"},
|
||||||
|
{0x1760, "keymap_rp/21/001.png"},
|
||||||
|
{0x1761, "keymap_rp/21/002.png"},
|
||||||
|
{0x1762, "keymap_rp/21/003.png"},
|
||||||
|
{0x1763, "keymap_rp/21/004.png"},
|
||||||
|
{0x1764, "keymap_rp/21/005.png"},
|
||||||
|
{0x1765, "keymap_rp/21/006.png"},
|
||||||
|
{0x1766, "keymap_rp/21/007.png"},
|
||||||
|
{0x1767, "keymap_rp/21/008.png"},
|
||||||
|
{0x1768, "keymap_rp/21/009.png"},
|
||||||
|
{0x1769, "keymap_rp/21/010.png"},
|
||||||
|
{0x1770, "keymap_rp/22/001.png"},
|
||||||
|
{0x1771, "keymap_rp/22/002.png"},
|
||||||
|
{0x1772, "keymap_rp/22/003.png"},
|
||||||
|
{0x1773, "keymap_rp/22/004.png"},
|
||||||
|
{0x1774, "keymap_rp/22/005.png"},
|
||||||
|
{0x1775, "keymap_rp/22/006.png"},
|
||||||
|
{0x1776, "keymap_rp/22/007.png"},
|
||||||
|
{0x1777, "keymap_rp/22/008.png"},
|
||||||
|
{0x1778, "keymap_rp/22/009.png"},
|
||||||
|
{0x1779, "keymap_rp/22/010.png"},
|
||||||
|
{0x1780, "keymap_rp/23/001.png"},
|
||||||
|
{0x1781, "keymap_rp/23/002.png"},
|
||||||
|
{0x1782, "keymap_rp/23/003.png"},
|
||||||
|
{0x1783, "keymap_rp/23/004.png"},
|
||||||
|
{0x1784, "keymap_rp/23/005.png"},
|
||||||
|
{0x1785, "keymap_rp/23/006.png"},
|
||||||
|
{0x1786, "keymap_rp/23/007.png"},
|
||||||
|
{0x1787, "keymap_rp/23/008.png"},
|
||||||
|
{0x1788, "keymap_rp/23/009.png"},
|
||||||
|
{0x1789, "keymap_rp/23/010.png"},
|
||||||
|
{0x1790, "keymap_rp/24/001.png"},
|
||||||
|
{0x1791, "keymap_rp/24/002.png"},
|
||||||
|
{0x1792, "keymap_rp/24/003.png"},
|
||||||
|
{0x1793, "keymap_rp/24/004.png"},
|
||||||
|
{0x1794, "keymap_rp/24/005.png"},
|
||||||
|
{0x1795, "keymap_rp/24/006.png"},
|
||||||
|
{0x1796, "keymap_rp/24/007.png"},
|
||||||
|
{0x1797, "keymap_rp/24/008.png"},
|
||||||
|
{0x1798, "keymap_rp/24/009.png"},
|
||||||
|
{0x1799, "keymap_rp/24/010.png"},
|
||||||
|
{0x17A0, "keymap_rp/25/001.png"},
|
||||||
|
{0x17A1, "keymap_rp/25/002.png"},
|
||||||
|
{0x17A2, "keymap_rp/25/003.png"},
|
||||||
|
{0x17A3, "keymap_rp/25/004.png"},
|
||||||
|
{0x17A4, "keymap_rp/25/005.png"},
|
||||||
|
{0x17A5, "keymap_rp/25/006.png"},
|
||||||
|
{0x17A6, "keymap_rp/25/007.png"},
|
||||||
|
{0x17A7, "keymap_rp/25/008.png"},
|
||||||
|
{0x17A8, "keymap_rp/25/009.png"},
|
||||||
|
{0x17A9, "keymap_rp/25/010.png"},
|
||||||
|
{0x17B0, "keymap_rp/26/001.png"},
|
||||||
|
{0x17B1, "keymap_rp/26/002.png"},
|
||||||
|
{0x17B2, "keymap_rp/26/003.png"},
|
||||||
|
{0x17B3, "keymap_rp/26/004.png"},
|
||||||
|
{0x17B4, "keymap_rp/26/005.png"},
|
||||||
|
{0x17B5, "keymap_rp/26/006.png"},
|
||||||
|
{0x17B6, "keymap_rp/26/007.png"},
|
||||||
|
{0x17B7, "keymap_rp/26/008.png"},
|
||||||
|
{0x17B8, "keymap_rp/26/009.png"},
|
||||||
|
{0x17B9, "keymap_rp/26/010.png"},
|
||||||
|
{0x17C0, "keymap_rp/27/001.png"},
|
||||||
|
{0x17C1, "keymap_rp/27/002.png"},
|
||||||
|
{0x17C2, "keymap_rp/27/003.png"},
|
||||||
|
{0x17C3, "keymap_rp/27/004.png"},
|
||||||
|
{0x17C4, "keymap_rp/27/005.png"},
|
||||||
|
{0x17C5, "keymap_rp/27/006.png"},
|
||||||
|
{0x17C6, "keymap_rp/27/007.png"},
|
||||||
|
{0x17C7, "keymap_rp/27/008.png"},
|
||||||
|
{0x17C8, "keymap_rp/27/009.png"},
|
||||||
|
{0x17C9, "keymap_rp/27/010.png"},
|
||||||
|
{0x17D0, "keymap_rp/28/001.png"},
|
||||||
|
{0x17D1, "keymap_rp/28/002.png"},
|
||||||
|
{0x17D2, "keymap_rp/28/003.png"},
|
||||||
|
{0x17D3, "keymap_rp/28/004.png"},
|
||||||
|
{0x17D4, "keymap_rp/28/005.png"},
|
||||||
|
{0x17D5, "keymap_rp/28/006.png"},
|
||||||
|
{0x17D6, "keymap_rp/28/007.png"},
|
||||||
|
{0x17D7, "keymap_rp/28/008.png"},
|
||||||
|
{0x17D8, "keymap_rp/28/009.png"},
|
||||||
|
{0x17D9, "keymap_rp/28/010.png"},
|
||||||
|
{0x17E0, "keymap_rp/29/001.png"},
|
||||||
|
{0x17E1, "keymap_rp/29/002.png"},
|
||||||
|
{0x17E2, "keymap_rp/29/003.png"},
|
||||||
|
{0x17E3, "keymap_rp/29/004.png"},
|
||||||
|
{0x17E4, "keymap_rp/29/005.png"},
|
||||||
|
{0x17E5, "keymap_rp/29/006.png"},
|
||||||
|
{0x17E6, "keymap_rp/29/007.png"},
|
||||||
|
{0x17E7, "keymap_rp/29/008.png"},
|
||||||
|
{0x17E8, "keymap_rp/29/009.png"},
|
||||||
|
{0x17E9, "keymap_rp/29/010.png"},
|
||||||
|
{0x17F0, "keymap_rp/30/001.png"},
|
||||||
|
{0x17F1, "keymap_rp/30/002.png"},
|
||||||
|
{0x17F2, "keymap_rp/30/003.png"},
|
||||||
|
{0x17F3, "keymap_rp/30/004.png"},
|
||||||
|
{0x17F4, "keymap_rp/30/005.png"},
|
||||||
|
{0x17F5, "keymap_rp/30/006.png"},
|
||||||
|
{0x17F6, "keymap_rp/30/007.png"},
|
||||||
|
{0x17F7, "keymap_rp/30/008.png"},
|
||||||
|
{0x17F8, "keymap_rp/30/009.png"},
|
||||||
|
{0x17F9, "keymap_rp/30/010.png"},
|
||||||
|
}};
|
||||||
|
|
||||||
|
std::string_view GetEntryNameByType(u32 type) {
|
||||||
|
const auto key = PkgEntryValue{type};
|
||||||
|
const auto it = std::ranges::lower_bound(PkgEntries, key);
|
||||||
|
if (it != PkgEntries.end() && it->type == type) {
|
||||||
|
return it->name;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
|
/// Retrieves the PKG entry name from its type identifier.
|
||||||
|
std::string_view GetEntryNameByType(u32 type);
|
|
@ -0,0 +1,59 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "common/io_file.h"
|
||||||
|
#include "psf.h"
|
||||||
|
|
||||||
|
PSF::PSF() = default;
|
||||||
|
|
||||||
|
PSF::~PSF() = default;
|
||||||
|
|
||||||
|
bool PSF::open(const std::string& filepath) {
|
||||||
|
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u64 psfSize = file.GetSize();
|
||||||
|
psf.resize(psfSize);
|
||||||
|
file.Seek(0);
|
||||||
|
file.Read(psf);
|
||||||
|
|
||||||
|
// Parse file contents
|
||||||
|
PSFHeader header;
|
||||||
|
std::memcpy(&header, psf.data(), sizeof(header));
|
||||||
|
for (u32 i = 0; i < header.index_table_entries; i++) {
|
||||||
|
PSFEntry entry;
|
||||||
|
std::memcpy(&entry, &psf[sizeof(PSFHeader) + i * sizeof(PSFEntry)], sizeof(entry));
|
||||||
|
|
||||||
|
const std::string key = (char*)&psf[header.key_table_offset + entry.key_offset];
|
||||||
|
if (entry.param_fmt == PSFEntry::Fmt::TextRaw ||
|
||||||
|
entry.param_fmt == PSFEntry::Fmt::TextNormal) {
|
||||||
|
map_strings[key] = (char*)&psf[header.data_table_offset + entry.data_offset];
|
||||||
|
}
|
||||||
|
if (entry.param_fmt == PSFEntry::Fmt::Integer) {
|
||||||
|
u32 value;
|
||||||
|
std::memcpy(&value, &psf[header.data_table_offset + entry.data_offset], sizeof(value));
|
||||||
|
map_integers[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PSF::GetString(const std::string& key) {
|
||||||
|
if (map_strings.find(key) != map_strings.end()) {
|
||||||
|
return map_strings.at(key);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 PSF::GetInteger(const std::string& key) {
|
||||||
|
if (map_integers.find(key) != map_integers.end()) {
|
||||||
|
return map_integers.at(key); // TODO std::invalid_argument exception if it fails?
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
#include "common/endian.h"
|
||||||
|
|
||||||
|
struct PSFHeader {
|
||||||
|
u32_be magic;
|
||||||
|
u32_le version;
|
||||||
|
u32_le key_table_offset;
|
||||||
|
u32_le data_table_offset;
|
||||||
|
u32_le index_table_entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PSFEntry {
|
||||||
|
enum Fmt : u16 {
|
||||||
|
TextRaw = 0x0400, // String in UTF-8 format and not NULL terminated
|
||||||
|
TextNormal = 0x0402, // String in UTF-8 format and NULL terminated
|
||||||
|
Integer = 0x0404, // Unsigned 32-bit integer
|
||||||
|
};
|
||||||
|
|
||||||
|
u16_le key_offset;
|
||||||
|
u16_be param_fmt;
|
||||||
|
u32_le param_len;
|
||||||
|
u32_le param_max_len;
|
||||||
|
u32_le data_offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PSF {
|
||||||
|
public:
|
||||||
|
PSF();
|
||||||
|
~PSF();
|
||||||
|
|
||||||
|
bool open(const std::string& filepath);
|
||||||
|
|
||||||
|
std::string GetString(const std::string& key);
|
||||||
|
u32 GetInteger(const std::string& key);
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::string> map_strings;
|
||||||
|
std::unordered_map<std::string, u32> map_integers;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<u8> psf;
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/io_file.h"
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "loader.h"
|
||||||
|
|
||||||
|
namespace Loader {
|
||||||
|
|
||||||
|
FileTypes DetectFileType(const std::string& filepath) {
|
||||||
|
// No file loaded
|
||||||
|
if (filepath.empty()) {
|
||||||
|
return FileTypes::Unknown;
|
||||||
|
}
|
||||||
|
Common::FS::IOFile file;
|
||||||
|
file.Open(filepath, Common::FS::FileAccessMode::Read);
|
||||||
|
file.Seek(0);
|
||||||
|
u32 magic;
|
||||||
|
file.Read(magic);
|
||||||
|
file.Close();
|
||||||
|
switch (magic) {
|
||||||
|
case PkgMagic:
|
||||||
|
return FileTypes::Pkg;
|
||||||
|
}
|
||||||
|
return FileTypes::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Loader
|
|
@ -0,0 +1,18 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Loader {
|
||||||
|
|
||||||
|
constexpr static u32 PkgMagic = 0x544e437f;
|
||||||
|
|
||||||
|
enum class FileTypes {
|
||||||
|
Unknown,
|
||||||
|
Pkg,
|
||||||
|
};
|
||||||
|
|
||||||
|
FileTypes DetectFileType(const std::string& filepath);
|
||||||
|
} // namespace Loader
|
Binary file not shown.
After Width: | Height: | Size: 154 KiB |
|
@ -0,0 +1,62 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDockWidget>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QStyleOption>
|
||||||
|
|
||||||
|
class CustomDockWidget : public QDockWidget {
|
||||||
|
private:
|
||||||
|
std::shared_ptr<QWidget> m_title_bar_widget;
|
||||||
|
bool m_is_title_bar_visible = true;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CustomDockWidget(const QString& title, QWidget* parent = Q_NULLPTR,
|
||||||
|
Qt::WindowFlags flags = Qt::WindowFlags())
|
||||||
|
: QDockWidget(title, parent, flags) {
|
||||||
|
m_title_bar_widget.reset(titleBarWidget());
|
||||||
|
|
||||||
|
connect(this, &QDockWidget::topLevelChanged, [this](bool /* topLevel*/) {
|
||||||
|
SetTitleBarVisible(m_is_title_bar_visible);
|
||||||
|
style()->unpolish(this);
|
||||||
|
style()->polish(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetTitleBarVisible(bool visible) {
|
||||||
|
if (visible || isFloating()) {
|
||||||
|
if (m_title_bar_widget.get() != titleBarWidget()) {
|
||||||
|
setTitleBarWidget(m_title_bar_widget.get());
|
||||||
|
QMargins margins = widget()->contentsMargins();
|
||||||
|
margins.setTop(0);
|
||||||
|
widget()->setContentsMargins(margins);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setTitleBarWidget(new QWidget());
|
||||||
|
QMargins margins = widget()->contentsMargins();
|
||||||
|
margins.setTop(1);
|
||||||
|
widget()->setContentsMargins(margins);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_is_title_bar_visible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void paintEvent(QPaintEvent* event) override {
|
||||||
|
// We need to repaint the dock widgets as plain widgets in floating mode.
|
||||||
|
// Source:
|
||||||
|
// https://stackoverflow.com/questions/10272091/cannot-add-a-background-image-to-a-qdockwidget
|
||||||
|
if (isFloating()) {
|
||||||
|
QStyleOption opt;
|
||||||
|
opt.initFrom(this);
|
||||||
|
QPainter p(this);
|
||||||
|
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use inherited method for docked mode because otherwise the dock would lose the title etc.
|
||||||
|
QDockWidget::paintEvent(event);
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,67 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include "custom_table_widget_item.h"
|
||||||
|
|
||||||
|
CustomTableWidgetItem::CustomTableWidgetItem(const std::string& text, int sort_role,
|
||||||
|
const QVariant& sort_value)
|
||||||
|
: GameListItem(
|
||||||
|
QString::fromStdString(text).simplified()) // simplified() forces single line text
|
||||||
|
{
|
||||||
|
if (sort_role != Qt::DisplayRole) {
|
||||||
|
setData(sort_role, sort_value, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomTableWidgetItem::CustomTableWidgetItem(const QString& text, int sort_role,
|
||||||
|
const QVariant& sort_value)
|
||||||
|
: GameListItem(text.simplified()) // simplified() forces single line text
|
||||||
|
{
|
||||||
|
if (sort_role != Qt::DisplayRole) {
|
||||||
|
setData(sort_role, sort_value, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CustomTableWidgetItem::operator<(const QTableWidgetItem& other) const {
|
||||||
|
if (m_sort_role == Qt::DisplayRole) {
|
||||||
|
return QTableWidgetItem::operator<(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QVariant data_l = data(m_sort_role);
|
||||||
|
const QVariant data_r = other.data(m_sort_role);
|
||||||
|
const QVariant::Type type_l = data_l.type();
|
||||||
|
const QVariant::Type type_r = data_r.type();
|
||||||
|
|
||||||
|
switch (type_l) {
|
||||||
|
case QVariant::Type::Bool:
|
||||||
|
case QVariant::Type::Int:
|
||||||
|
return data_l.toInt() < data_r.toInt();
|
||||||
|
case QVariant::Type::UInt:
|
||||||
|
return data_l.toUInt() < data_r.toUInt();
|
||||||
|
case QVariant::Type::LongLong:
|
||||||
|
return data_l.toLongLong() < data_r.toLongLong();
|
||||||
|
case QVariant::Type::ULongLong:
|
||||||
|
return data_l.toULongLong() < data_r.toULongLong();
|
||||||
|
case QVariant::Type::Double:
|
||||||
|
return data_l.toDouble() < data_r.toDouble();
|
||||||
|
case QVariant::Type::Date:
|
||||||
|
return data_l.toDate() < data_r.toDate();
|
||||||
|
case QVariant::Type::Time:
|
||||||
|
return data_l.toTime() < data_r.toTime();
|
||||||
|
case QVariant::Type::DateTime:
|
||||||
|
return data_l.toDateTime() < data_r.toDateTime();
|
||||||
|
case QVariant::Type::Char:
|
||||||
|
case QVariant::Type::String:
|
||||||
|
return data_l.toString() < data_r.toString();
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("unsupported type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomTableWidgetItem::setData(int role, const QVariant& value, bool assign_sort_role) {
|
||||||
|
if (assign_sort_role) {
|
||||||
|
m_sort_role = role;
|
||||||
|
}
|
||||||
|
QTableWidgetItem::setData(role, value);
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QTableWidgetItem>
|
||||||
|
|
||||||
|
#include "game_list_item.h"
|
||||||
|
|
||||||
|
class CustomTableWidgetItem : public GameListItem {
|
||||||
|
private:
|
||||||
|
int m_sort_role = Qt::DisplayRole;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using QTableWidgetItem::setData;
|
||||||
|
|
||||||
|
CustomTableWidgetItem() = default;
|
||||||
|
CustomTableWidgetItem(const std::string& text, int sort_role = Qt::DisplayRole,
|
||||||
|
const QVariant& sort_value = 0);
|
||||||
|
CustomTableWidgetItem(const QString& text, int sort_role = Qt::DisplayRole,
|
||||||
|
const QVariant& sort_value = 0);
|
||||||
|
|
||||||
|
bool operator<(const QTableWidgetItem& other) const override;
|
||||||
|
|
||||||
|
void setData(int role, const QVariant& value, bool assign_sort_role);
|
||||||
|
};
|
|
@ -0,0 +1,20 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct GameInfo {
|
||||||
|
std::string path; // root path of game directory (normaly directory that contains eboot.bin)
|
||||||
|
std::string icon_path; // path of icon0.png
|
||||||
|
std::string pic_path; // path of pic1.png
|
||||||
|
|
||||||
|
// variables extracted from param.sfo
|
||||||
|
std::string name = "Unknown";
|
||||||
|
std::string serial = "Unknown";
|
||||||
|
std::string app_ver = "Unknown";
|
||||||
|
std::string version = "Unknown";
|
||||||
|
std::string category = "Unknown";
|
||||||
|
std::string fw = "Unknown";
|
||||||
|
};
|
|
@ -0,0 +1,83 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "game_install_dialog.h"
|
||||||
|
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include "gui_settings.h"
|
||||||
|
|
||||||
|
GameInstallDialog::GameInstallDialog(std::shared_ptr<GuiSettings> gui_settings)
|
||||||
|
: m_gamesDirectory(nullptr), m_gui_settings(std::move(gui_settings)) {
|
||||||
|
auto layout = new QVBoxLayout(this);
|
||||||
|
|
||||||
|
layout->addWidget(SetupGamesDirectory());
|
||||||
|
layout->addStretch();
|
||||||
|
layout->addWidget(SetupDialogActions());
|
||||||
|
|
||||||
|
setWindowTitle("Shadps4 - Choose directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
GameInstallDialog::~GameInstallDialog() {}
|
||||||
|
|
||||||
|
void GameInstallDialog::Browse() {
|
||||||
|
auto path = QFileDialog::getExistingDirectory(this, "Directory to install games");
|
||||||
|
|
||||||
|
if (!path.isEmpty()) {
|
||||||
|
m_gamesDirectory->setText(QDir::toNativeSeparators(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget* GameInstallDialog::SetupGamesDirectory() {
|
||||||
|
auto group = new QGroupBox("Directory to install games");
|
||||||
|
auto layout = new QHBoxLayout(group);
|
||||||
|
|
||||||
|
// Input.
|
||||||
|
m_gamesDirectory = new QLineEdit();
|
||||||
|
m_gamesDirectory->setText(m_gui_settings->GetValue(gui::settings_install_dir).toString());
|
||||||
|
m_gamesDirectory->setMinimumWidth(400);
|
||||||
|
|
||||||
|
layout->addWidget(m_gamesDirectory);
|
||||||
|
|
||||||
|
// Browse button.
|
||||||
|
auto browse = new QPushButton("...");
|
||||||
|
|
||||||
|
connect(browse, &QPushButton::clicked, this, &GameInstallDialog::Browse);
|
||||||
|
|
||||||
|
layout->addWidget(browse);
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget* GameInstallDialog::SetupDialogActions() {
|
||||||
|
auto actions = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||||
|
|
||||||
|
connect(actions, &QDialogButtonBox::accepted, this, &GameInstallDialog::Save);
|
||||||
|
connect(actions, &QDialogButtonBox::rejected, this, &GameInstallDialog::reject);
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameInstallDialog::Save() {
|
||||||
|
// Check games directory.
|
||||||
|
auto gamesDirectory = m_gamesDirectory->text();
|
||||||
|
|
||||||
|
if (gamesDirectory.isEmpty() || !QDir(gamesDirectory).exists() ||
|
||||||
|
!QDir::isAbsolutePath(gamesDirectory)) {
|
||||||
|
QMessageBox::critical(this, "Error",
|
||||||
|
"The value for location to install games is not valid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_gui_settings->SetValue(gui::settings_install_dir, QDir::toNativeSeparators(gamesDirectory));
|
||||||
|
|
||||||
|
accept();
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include "gui_settings.h"
|
||||||
|
|
||||||
|
class QLineEdit;
|
||||||
|
|
||||||
|
class GameInstallDialog final : public QDialog {
|
||||||
|
public:
|
||||||
|
GameInstallDialog(std::shared_ptr<GuiSettings> gui_settings);
|
||||||
|
~GameInstallDialog();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void Browse();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QWidget* SetupGamesDirectory();
|
||||||
|
QWidget* SetupDialogActions();
|
||||||
|
void Save();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QLineEdit* m_gamesDirectory;
|
||||||
|
std::shared_ptr<GuiSettings> m_gui_settings;
|
||||||
|
};
|
|
@ -0,0 +1,886 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QScrollBar>
|
||||||
|
|
||||||
|
#include "core/file_format/psf.h"
|
||||||
|
#include "custom_table_widget_item.h"
|
||||||
|
#include "game_list_frame.h"
|
||||||
|
#include "gui_settings.h"
|
||||||
|
#include "qt_utils.h"
|
||||||
|
|
||||||
|
GameListFrame::GameListFrame(std::shared_ptr<GuiSettings> gui_settings, QWidget* parent)
|
||||||
|
: CustomDockWidget(tr("Game List"), parent), m_gui_settings(std::move(gui_settings)) {
|
||||||
|
m_icon_size = gui::game_list_icon_size_min; // ensure a valid size
|
||||||
|
m_is_list_layout = m_gui_settings->GetValue(gui::game_list_listMode).toBool();
|
||||||
|
m_margin_factor = m_gui_settings->GetValue(gui::game_list_marginFactor).toReal();
|
||||||
|
m_text_factor = m_gui_settings->GetValue(gui::game_list_textFactor).toReal();
|
||||||
|
m_icon_color = m_gui_settings->GetValue(gui::game_list_iconColor).value<QColor>();
|
||||||
|
m_col_sort_order = m_gui_settings->GetValue(gui::game_list_sortAsc).toBool()
|
||||||
|
? Qt::AscendingOrder
|
||||||
|
: Qt::DescendingOrder;
|
||||||
|
m_sort_column = m_gui_settings->GetValue(gui::game_list_sortCol).toInt();
|
||||||
|
|
||||||
|
m_old_layout_is_list = m_is_list_layout;
|
||||||
|
|
||||||
|
// Save factors for first setup
|
||||||
|
m_gui_settings->SetValue(gui::game_list_iconColor, m_icon_color);
|
||||||
|
m_gui_settings->SetValue(gui::game_list_marginFactor, m_margin_factor);
|
||||||
|
m_gui_settings->SetValue(gui::game_list_textFactor, m_text_factor);
|
||||||
|
|
||||||
|
m_game_dock = new QMainWindow(this);
|
||||||
|
m_game_dock->setWindowFlags(Qt::Widget);
|
||||||
|
setWidget(m_game_dock);
|
||||||
|
|
||||||
|
m_game_grid = new GameListGrid(QSize(), m_icon_color, m_margin_factor, m_text_factor, false);
|
||||||
|
|
||||||
|
m_game_list = new GameListTable();
|
||||||
|
m_game_list->setShowGrid(false);
|
||||||
|
m_game_list->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||||
|
m_game_list->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
|
m_game_list->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
|
m_game_list->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||||
|
m_game_list->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||||
|
m_game_list->verticalScrollBar()->installEventFilter(this);
|
||||||
|
m_game_list->verticalScrollBar()->setSingleStep(20);
|
||||||
|
m_game_list->horizontalScrollBar()->setSingleStep(20);
|
||||||
|
m_game_list->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
|
||||||
|
m_game_list->verticalHeader()->setVisible(false);
|
||||||
|
m_game_list->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
m_game_list->horizontalHeader()->setHighlightSections(false);
|
||||||
|
m_game_list->horizontalHeader()->setSortIndicatorShown(true);
|
||||||
|
m_game_list->horizontalHeader()->setStretchLastSection(true);
|
||||||
|
m_game_list->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
m_game_list->installEventFilter(this);
|
||||||
|
m_game_list->setColumnCount(gui::column_count);
|
||||||
|
m_game_list->setColumnWidth(1, 250);
|
||||||
|
m_game_list->setColumnWidth(2, 110);
|
||||||
|
m_game_list->setColumnWidth(3, 80);
|
||||||
|
m_game_list->setColumnWidth(4, 90);
|
||||||
|
m_game_list->setColumnWidth(5, 80);
|
||||||
|
m_game_list->setColumnWidth(6, 80);
|
||||||
|
QPalette palette;
|
||||||
|
palette.setColor(QPalette::Base, QColor(230, 230, 230, 80));
|
||||||
|
QColor transparentColor = QColor(135, 206, 235, 40);
|
||||||
|
palette.setColor(QPalette::Highlight, transparentColor);
|
||||||
|
m_game_list->setPalette(palette);
|
||||||
|
m_central_widget = new QStackedWidget(this);
|
||||||
|
m_central_widget->addWidget(m_game_list);
|
||||||
|
m_central_widget->addWidget(m_game_grid);
|
||||||
|
m_central_widget->setCurrentWidget(m_is_list_layout ? m_game_list : m_game_grid);
|
||||||
|
|
||||||
|
m_game_dock->setCentralWidget(m_central_widget);
|
||||||
|
|
||||||
|
// Actions regarding showing/hiding columns
|
||||||
|
auto add_column = [this](gui::game_list_columns col, const QString& header_text,
|
||||||
|
const QString& action_text) {
|
||||||
|
QTableWidgetItem* item_ = new QTableWidgetItem(header_text);
|
||||||
|
item_->setTextAlignment(Qt::AlignCenter); // Center-align text
|
||||||
|
m_game_list->setHorizontalHeaderItem(col, item_);
|
||||||
|
m_columnActs.append(new QAction(action_text, this));
|
||||||
|
};
|
||||||
|
|
||||||
|
add_column(gui::column_icon, tr("Icon"), tr("Show Icons"));
|
||||||
|
add_column(gui::column_name, tr("Name"), tr("Show Names"));
|
||||||
|
add_column(gui::column_serial, tr("Serial"), tr("Show Serials"));
|
||||||
|
add_column(gui::column_firmware, tr("Firmware"), tr("Show Firmwares"));
|
||||||
|
add_column(gui::column_size, tr("Size"), tr("Show Size"));
|
||||||
|
add_column(gui::column_version, tr("Version"), tr("Show Versions"));
|
||||||
|
add_column(gui::column_category, tr("Category"), tr("Show Categories"));
|
||||||
|
add_column(gui::column_path, tr("Path"), tr("Show Paths"));
|
||||||
|
|
||||||
|
for (int col = 0; col < m_columnActs.count(); ++col) {
|
||||||
|
m_columnActs[col]->setCheckable(true);
|
||||||
|
|
||||||
|
connect(m_columnActs[col], &QAction::triggered, this, [this, col](bool checked) {
|
||||||
|
if (!checked) // be sure to have at least one column left so you can call the context
|
||||||
|
// menu at all time
|
||||||
|
{
|
||||||
|
int c = 0;
|
||||||
|
for (int i = 0; i < m_columnActs.count(); ++i) {
|
||||||
|
if (m_gui_settings->GetGamelistColVisibility(i) && ++c > 1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c < 2) {
|
||||||
|
m_columnActs[col]->setChecked(
|
||||||
|
true); // re-enable the checkbox if we don't change the actual state
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_game_list->setColumnHidden(
|
||||||
|
col, !checked); // Negate because it's a set col hidden and we have menu say show.
|
||||||
|
m_gui_settings->SetGamelistColVisibility(col, checked);
|
||||||
|
|
||||||
|
if (checked) // handle hidden columns that have zero width after showing them (stuck
|
||||||
|
// between others)
|
||||||
|
{
|
||||||
|
FixNarrowColumns();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// events
|
||||||
|
connect(m_game_list->horizontalHeader(), &QHeaderView::customContextMenuRequested, this,
|
||||||
|
[this](const QPoint& pos) {
|
||||||
|
QMenu* configure = new QMenu(this);
|
||||||
|
configure->addActions(m_columnActs);
|
||||||
|
configure->exec(m_game_list->horizontalHeader()->viewport()->mapToGlobal(pos));
|
||||||
|
});
|
||||||
|
connect(m_game_list->horizontalHeader(), &QHeaderView::sectionClicked, this,
|
||||||
|
&GameListFrame::OnHeaderColumnClicked);
|
||||||
|
connect(&m_repaint_watcher, &QFutureWatcher<GameListItem*>::resultReadyAt, this,
|
||||||
|
[this](int index) {
|
||||||
|
if (!m_is_list_layout)
|
||||||
|
return;
|
||||||
|
if (GameListItem* item = m_repaint_watcher.resultAt(index)) {
|
||||||
|
item->call_icon_func();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(&m_repaint_watcher, &QFutureWatcher<GameListItem*>::finished, this,
|
||||||
|
&GameListFrame::OnRepaintFinished);
|
||||||
|
|
||||||
|
connect(&m_refresh_watcher, &QFutureWatcher<void>::finished, this,
|
||||||
|
&GameListFrame::OnRefreshFinished);
|
||||||
|
connect(&m_refresh_watcher, &QFutureWatcher<void>::canceled, this, [this]() {
|
||||||
|
gui::utils::stop_future_watcher(m_repaint_watcher, true);
|
||||||
|
|
||||||
|
m_path_list.clear();
|
||||||
|
m_game_data.clear();
|
||||||
|
m_games.clear();
|
||||||
|
});
|
||||||
|
connect(m_game_list, &QTableWidget::customContextMenuRequested, this,
|
||||||
|
&GameListFrame::RequestGameMenu);
|
||||||
|
connect(m_game_grid, &QTableWidget::customContextMenuRequested, this,
|
||||||
|
&GameListFrame::RequestGameMenu);
|
||||||
|
|
||||||
|
connect(m_game_list, &QTableWidget::itemClicked, this, &GameListFrame::SetListBackgroundImage);
|
||||||
|
connect(this, &GameListFrame::ResizedWindow, this, &GameListFrame::SetListBackgroundImage);
|
||||||
|
connect(m_game_list->verticalScrollBar(), &QScrollBar::valueChanged, this,
|
||||||
|
&GameListFrame::RefreshListBackgroundImage);
|
||||||
|
connect(m_game_list->horizontalScrollBar(), &QScrollBar::valueChanged, this,
|
||||||
|
&GameListFrame::RefreshListBackgroundImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
GameListFrame::~GameListFrame() {
|
||||||
|
gui::utils::stop_future_watcher(m_repaint_watcher, true);
|
||||||
|
gui::utils::stop_future_watcher(m_refresh_watcher, true);
|
||||||
|
SaveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListFrame::OnRefreshFinished() {
|
||||||
|
gui::utils::stop_future_watcher(m_repaint_watcher, true);
|
||||||
|
for (auto&& g : m_games) {
|
||||||
|
m_game_data.push_back(g);
|
||||||
|
}
|
||||||
|
m_games.clear();
|
||||||
|
// Sort by name at the very least.
|
||||||
|
std::sort(m_game_data.begin(), m_game_data.end(),
|
||||||
|
[&](const game_info& game1, const game_info& game2) {
|
||||||
|
const QString title1 = m_titles.value(QString::fromStdString(game1->info.serial),
|
||||||
|
QString::fromStdString(game1->info.name));
|
||||||
|
const QString title2 = m_titles.value(QString::fromStdString(game2->info.serial),
|
||||||
|
QString::fromStdString(game2->info.name));
|
||||||
|
return title1.toLower() < title2.toLower();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_path_list.clear();
|
||||||
|
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListFrame::RequestGameMenu(const QPoint& pos) {
|
||||||
|
|
||||||
|
QPoint global_pos;
|
||||||
|
game_info gameinfo;
|
||||||
|
|
||||||
|
if (m_is_list_layout) {
|
||||||
|
QTableWidgetItem* item = m_game_list->item(
|
||||||
|
m_game_list->indexAt(pos).row(), static_cast<int>(gui::game_list_columns::column_icon));
|
||||||
|
global_pos = m_game_list->viewport()->mapToGlobal(pos);
|
||||||
|
gameinfo = GetGameInfoFromItem(item);
|
||||||
|
} else {
|
||||||
|
const QModelIndex mi = m_game_grid->indexAt(pos);
|
||||||
|
QTableWidgetItem* item = m_game_grid->item(mi.row(), mi.column());
|
||||||
|
global_pos = m_game_grid->viewport()->mapToGlobal(pos);
|
||||||
|
gameinfo = GetGameInfoFromItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gameinfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup menu.
|
||||||
|
QMenu menu(this);
|
||||||
|
QAction openFolder("Open Game Folder", this);
|
||||||
|
QAction openSfoViewer("SFO Viewer", this);
|
||||||
|
|
||||||
|
menu.addAction(&openFolder);
|
||||||
|
menu.addAction(&openSfoViewer);
|
||||||
|
// Show menu.
|
||||||
|
auto selected = menu.exec(global_pos);
|
||||||
|
if (!selected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected == &openFolder) {
|
||||||
|
QString folderPath = QString::fromStdString(gameinfo->info.path);
|
||||||
|
QDesktopServices::openUrl(QUrl::fromLocalFile(folderPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected == &openSfoViewer) {
|
||||||
|
PSF psf;
|
||||||
|
if (psf.open(gameinfo->info.path + "/sce_sys/param.sfo")) {
|
||||||
|
int rows = psf.map_strings.size() + psf.map_integers.size();
|
||||||
|
QTableWidget* tableWidget = new QTableWidget(rows, 2);
|
||||||
|
tableWidget->verticalHeader()->setVisible(false); // Hide vertical header
|
||||||
|
int row = 0;
|
||||||
|
|
||||||
|
for (const auto& pair : psf.map_strings) {
|
||||||
|
QTableWidgetItem* keyItem =
|
||||||
|
new QTableWidgetItem(QString::fromStdString(pair.first));
|
||||||
|
QTableWidgetItem* valueItem =
|
||||||
|
new QTableWidgetItem(QString::fromStdString(pair.second));
|
||||||
|
|
||||||
|
tableWidget->setItem(row, 0, keyItem);
|
||||||
|
tableWidget->setItem(row, 1, valueItem);
|
||||||
|
keyItem->setFlags(keyItem->flags() & ~Qt::ItemIsEditable);
|
||||||
|
valueItem->setFlags(valueItem->flags() & ~Qt::ItemIsEditable);
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
for (const auto& pair : psf.map_integers) {
|
||||||
|
QTableWidgetItem* keyItem =
|
||||||
|
new QTableWidgetItem(QString::fromStdString(pair.first));
|
||||||
|
QTableWidgetItem* valueItem = new QTableWidgetItem(QString::number(pair.second));
|
||||||
|
|
||||||
|
tableWidget->setItem(row, 0, keyItem);
|
||||||
|
tableWidget->setItem(row, 1, valueItem);
|
||||||
|
keyItem->setFlags(keyItem->flags() & ~Qt::ItemIsEditable);
|
||||||
|
valueItem->setFlags(valueItem->flags() & ~Qt::ItemIsEditable);
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
tableWidget->resizeColumnsToContents();
|
||||||
|
tableWidget->resizeRowsToContents();
|
||||||
|
|
||||||
|
int width = tableWidget->horizontalHeader()->sectionSize(0) +
|
||||||
|
tableWidget->horizontalHeader()->sectionSize(1) + 2;
|
||||||
|
int height = (rows + 1) * (tableWidget->rowHeight(0));
|
||||||
|
tableWidget->setFixedSize(width, height);
|
||||||
|
tableWidget->sortItems(0, Qt::AscendingOrder);
|
||||||
|
tableWidget->horizontalHeader()->setVisible(false);
|
||||||
|
|
||||||
|
tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
|
||||||
|
tableWidget->setWindowTitle("SFO Viewer");
|
||||||
|
tableWidget->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListFrame::RefreshListBackgroundImage() {
|
||||||
|
QPixmap blurredPixmap = QPixmap::fromImage(backgroundImage);
|
||||||
|
QPalette palette;
|
||||||
|
palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio)));
|
||||||
|
QColor transparentColor = QColor(135, 206, 235, 40);
|
||||||
|
palette.setColor(QPalette::Highlight, transparentColor);
|
||||||
|
m_game_list->setPalette(palette);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) {
|
||||||
|
if (!item) {
|
||||||
|
// handle case where no item was clicked
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QTableWidgetItem* iconItem =
|
||||||
|
m_game_list->item(item->row(), static_cast<int>(gui::game_list_columns::column_icon));
|
||||||
|
|
||||||
|
if (!iconItem) {
|
||||||
|
// handle case where icon item does not exist
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
game_info gameinfo = GetGameInfoFromItem(iconItem);
|
||||||
|
QString pic1Path = QString::fromStdString(gameinfo->info.pic_path);
|
||||||
|
QString blurredPic1Path =
|
||||||
|
qApp->applicationDirPath() +
|
||||||
|
QString::fromStdString("/game_data/" + gameinfo->info.serial + "/pic1.png");
|
||||||
|
|
||||||
|
backgroundImage = QImage(blurredPic1Path);
|
||||||
|
if (backgroundImage.isNull()) {
|
||||||
|
QImage image(pic1Path);
|
||||||
|
backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 18);
|
||||||
|
|
||||||
|
std::filesystem::path img_path =
|
||||||
|
std::filesystem::path("game_data/") / gameinfo->info.serial;
|
||||||
|
std::filesystem::create_directories(img_path);
|
||||||
|
if (!backgroundImage.save(blurredPic1Path, "PNG")) {
|
||||||
|
// qDebug() << "Error: Unable to save image.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QPixmap blurredPixmap = QPixmap::fromImage(backgroundImage);
|
||||||
|
QPalette palette;
|
||||||
|
palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio)));
|
||||||
|
QColor transparentColor = QColor(135, 206, 235, 40);
|
||||||
|
palette.setColor(QPalette::Highlight, transparentColor);
|
||||||
|
m_game_list->setPalette(palette);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListFrame::OnRepaintFinished() {
|
||||||
|
if (m_is_list_layout) {
|
||||||
|
// Fixate vertical header and row height
|
||||||
|
m_game_list->verticalHeader()->setMinimumSectionSize(m_icon_size.height());
|
||||||
|
m_game_list->verticalHeader()->setMaximumSectionSize(m_icon_size.height());
|
||||||
|
|
||||||
|
// Resize the icon column
|
||||||
|
m_game_list->resizeColumnToContents(gui::column_icon);
|
||||||
|
|
||||||
|
// Shorten the last section to remove horizontal scrollbar if possible
|
||||||
|
m_game_list->resizeColumnToContents(gui::column_count - 1);
|
||||||
|
} else {
|
||||||
|
// The game grid needs to be recreated from scratch
|
||||||
|
int games_per_row = 0;
|
||||||
|
|
||||||
|
if (m_icon_size.width() > 0 && m_icon_size.height() > 0) {
|
||||||
|
games_per_row = width() / (m_icon_size.width() +
|
||||||
|
m_icon_size.width() * m_game_grid->getMarginFactor() * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int scroll_position = m_game_grid->verticalScrollBar()->value();
|
||||||
|
// TODO add connections
|
||||||
|
PopulateGameGrid(games_per_row, m_icon_size, m_icon_color);
|
||||||
|
m_central_widget->addWidget(m_game_grid);
|
||||||
|
m_central_widget->setCurrentWidget(m_game_grid);
|
||||||
|
m_game_grid->verticalScrollBar()->setValue(scroll_position);
|
||||||
|
|
||||||
|
connect(m_game_grid, &QTableWidget::customContextMenuRequested, this,
|
||||||
|
&GameListFrame::RequestGameMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GameListFrame::IsEntryVisible(const game_info& game) {
|
||||||
|
const QString serial = QString::fromStdString(game->info.serial);
|
||||||
|
return SearchMatchesApp(QString::fromStdString(game->info.name), serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
game_info GameListFrame::GetGameInfoFromItem(const QTableWidgetItem* item) {
|
||||||
|
if (!item) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QVariant var = item->data(gui::game_role);
|
||||||
|
if (!var.canConvert<game_info>()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return var.value<game_info>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListFrame::PopulateGameGrid(int maxCols, const QSize& image_size,
|
||||||
|
const QColor& image_color) {
|
||||||
|
int r = 0;
|
||||||
|
int c = 0;
|
||||||
|
|
||||||
|
const std::string selected_item = CurrentSelectionPath();
|
||||||
|
|
||||||
|
// Release old data
|
||||||
|
m_game_list->clear_list();
|
||||||
|
m_game_grid->deleteLater();
|
||||||
|
|
||||||
|
const bool show_text = m_icon_size_index > gui::game_list_max_slider_pos * 2 / 5;
|
||||||
|
|
||||||
|
if (m_icon_size_index < gui::game_list_max_slider_pos * 2 / 3) {
|
||||||
|
m_game_grid = new GameListGrid(image_size, image_color, m_margin_factor, m_text_factor * 2,
|
||||||
|
show_text);
|
||||||
|
} else {
|
||||||
|
m_game_grid =
|
||||||
|
new GameListGrid(image_size, image_color, m_margin_factor, m_text_factor, show_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get list of matching apps
|
||||||
|
QList<game_info> matching_apps;
|
||||||
|
|
||||||
|
for (const auto& app : m_game_data) {
|
||||||
|
if (IsEntryVisible(app)) {
|
||||||
|
matching_apps.push_back(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int entries = matching_apps.count();
|
||||||
|
|
||||||
|
// Edge cases!
|
||||||
|
if (entries == 0) { // For whatever reason, 0%x is division by zero. Absolute nonsense by
|
||||||
|
// definition of modulus. But, I'll acquiesce.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
maxCols = std::clamp(maxCols, 1, entries);
|
||||||
|
|
||||||
|
const int needs_extra_row = (entries % maxCols) != 0;
|
||||||
|
const int max_rows = needs_extra_row + entries / maxCols;
|
||||||
|
m_game_grid->setRowCount(max_rows);
|
||||||
|
m_game_grid->setColumnCount(maxCols);
|
||||||
|
|
||||||
|
for (const auto& app : matching_apps) {
|
||||||
|
const QString serial = QString::fromStdString(app->info.serial);
|
||||||
|
const QString title = m_titles.value(serial, QString::fromStdString(app->info.name));
|
||||||
|
|
||||||
|
GameListItem* item = m_game_grid->addItem(app, title, r, c);
|
||||||
|
app->item = item;
|
||||||
|
item->setData(gui::game_role, QVariant::fromValue(app));
|
||||||
|
|
||||||
|
item->setToolTip(tr("%0 [%1]").arg(title).arg(serial));
|
||||||
|
|
||||||
|
if (selected_item == app->info.path + app->info.icon_path) {
|
||||||
|
m_game_grid->setCurrentItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++c >= maxCols) {
|
||||||
|
c = 0;
|
||||||
|
r++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c != 0) { // if left over games exist -- if empty entries exist
|
||||||
|
for (int col = c; col < maxCols; ++col) {
|
||||||
|
GameListItem* empty_item = new GameListItem();
|
||||||
|
empty_item->setFlags(Qt::NoItemFlags);
|
||||||
|
m_game_grid->setItem(r, col, empty_item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_game_grid->resizeColumnsToContents();
|
||||||
|
m_game_grid->resizeRowsToContents();
|
||||||
|
m_game_grid->installEventFilter(this);
|
||||||
|
m_game_grid->verticalScrollBar()->installEventFilter(this);
|
||||||
|
}
|
||||||
|
void GameListFrame::Refresh(const bool from_drive, const bool scroll_after) {
|
||||||
|
gui::utils::stop_future_watcher(m_repaint_watcher, true);
|
||||||
|
gui::utils::stop_future_watcher(m_refresh_watcher, from_drive);
|
||||||
|
|
||||||
|
if (from_drive) {
|
||||||
|
m_path_list.clear();
|
||||||
|
m_game_data.clear();
|
||||||
|
m_games.clear();
|
||||||
|
|
||||||
|
// TODO better ATM manually add path from 1 dir to m_paths_list
|
||||||
|
QDir parent_folder(m_gui_settings->GetValue(gui::settings_install_dir).toString() + '/');
|
||||||
|
QFileInfoList fList =
|
||||||
|
parent_folder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::DirsFirst);
|
||||||
|
foreach (QFileInfo item, fList) {
|
||||||
|
m_path_list.emplace_back(item.absoluteFilePath().toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_refresh_watcher.setFuture(QtConcurrent::map(m_path_list, [this](const std::string& dir) {
|
||||||
|
GameInfo game{};
|
||||||
|
game.path = dir;
|
||||||
|
PSF psf;
|
||||||
|
if (psf.open(game.path + "/sce_sys/param.sfo")) {
|
||||||
|
QString iconpath(QString::fromStdString(game.path) + "/sce_sys/icon0.png");
|
||||||
|
QString picpath(QString::fromStdString(game.path) + "/sce_sys/pic1.png");
|
||||||
|
game.icon_path = iconpath.toStdString();
|
||||||
|
game.pic_path = picpath.toStdString();
|
||||||
|
game.name = psf.GetString("TITLE");
|
||||||
|
game.serial = psf.GetString("TITLE_ID");
|
||||||
|
game.fw = (QString("%1").arg(psf.GetInteger("SYSTEM_VER"), 8, 16, QLatin1Char('0')))
|
||||||
|
.mid(1, 3)
|
||||||
|
.insert(1, '.')
|
||||||
|
.toStdString();
|
||||||
|
game.version = psf.GetString("APP_VER");
|
||||||
|
game.category = psf.GetString("CATEGORY");
|
||||||
|
|
||||||
|
m_titles.insert(QString::fromStdString(game.serial),
|
||||||
|
QString::fromStdString(game.name));
|
||||||
|
|
||||||
|
GuiGameInfo info{};
|
||||||
|
info.info = game;
|
||||||
|
|
||||||
|
m_games.push_back(std::make_shared<GuiGameInfo>(std::move(info)));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Fill Game List / Game Grid
|
||||||
|
|
||||||
|
if (m_is_list_layout) {
|
||||||
|
const int scroll_position = m_game_list->verticalScrollBar()->value();
|
||||||
|
PopulateGameList();
|
||||||
|
SortGameList();
|
||||||
|
RepaintIcons();
|
||||||
|
|
||||||
|
if (scroll_after) {
|
||||||
|
m_game_list->scrollTo(m_game_list->currentIndex(), QAbstractItemView::PositionAtCenter);
|
||||||
|
} else {
|
||||||
|
m_game_list->verticalScrollBar()->setValue(scroll_position);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RepaintIcons();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
Cleans and readds entries to table widget in UI.
|
||||||
|
*/
|
||||||
|
void GameListFrame::PopulateGameList() {
|
||||||
|
int selected_row = -1;
|
||||||
|
|
||||||
|
const std::string selected_item = CurrentSelectionPath();
|
||||||
|
|
||||||
|
// Release old data
|
||||||
|
m_game_grid->clear_list();
|
||||||
|
m_game_list->clear_list();
|
||||||
|
|
||||||
|
m_game_list->setRowCount(m_game_data.size());
|
||||||
|
|
||||||
|
int row = 0;
|
||||||
|
int index = -1;
|
||||||
|
for (const auto& game : m_game_data) {
|
||||||
|
index++;
|
||||||
|
|
||||||
|
if (!IsEntryVisible(game)) {
|
||||||
|
game->item = nullptr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon
|
||||||
|
CustomTableWidgetItem* icon_item = new CustomTableWidgetItem;
|
||||||
|
game->item = icon_item;
|
||||||
|
icon_item->set_icon_func([this, icon_item, game](int) {
|
||||||
|
icon_item->setData(Qt::DecorationRole, game->pxmap);
|
||||||
|
game->pxmap = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
icon_item->setData(Qt::UserRole, index, true);
|
||||||
|
icon_item->setData(gui::custom_roles::game_role, QVariant::fromValue(game));
|
||||||
|
|
||||||
|
m_game_list->setItem(row, gui::column_icon, icon_item);
|
||||||
|
SetTableItem(m_game_list, row, gui::column_name, QString::fromStdString(game->info.name));
|
||||||
|
SetTableItem(m_game_list, row, gui::column_serial,
|
||||||
|
QString::fromStdString(game->info.serial));
|
||||||
|
SetTableItem(m_game_list, row, gui::column_firmware, QString::fromStdString(game->info.fw));
|
||||||
|
SetTableItem(
|
||||||
|
m_game_list, row, gui::column_size,
|
||||||
|
m_game_list_utils.GetFolderSize(QDir(QString::fromStdString(game->info.path))));
|
||||||
|
SetTableItem(m_game_list, row, gui::column_version,
|
||||||
|
QString::fromStdString(game->info.version));
|
||||||
|
SetTableItem(m_game_list, row, gui::column_category,
|
||||||
|
QString::fromStdString(game->info.category));
|
||||||
|
SetTableItem(m_game_list, row, gui::column_path, QString::fromStdString(game->info.path));
|
||||||
|
|
||||||
|
if (selected_item == game->info.path + game->info.icon_path) {
|
||||||
|
selected_row = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
m_game_list->setRowCount(row);
|
||||||
|
m_game_list->selectRow(selected_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GameListFrame::CurrentSelectionPath() {
|
||||||
|
std::string selection;
|
||||||
|
|
||||||
|
QTableWidgetItem* item = nullptr;
|
||||||
|
|
||||||
|
if (m_old_layout_is_list) {
|
||||||
|
if (!m_game_list->selectedItems().isEmpty()) {
|
||||||
|
item = m_game_list->item(m_game_list->currentRow(), 0);
|
||||||
|
}
|
||||||
|
} else if (m_game_grid) {
|
||||||
|
if (!m_game_grid->selectedItems().isEmpty()) {
|
||||||
|
item = m_game_grid->currentItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
if (const QVariant var = item->data(gui::game_role); var.canConvert<game_info>()) {
|
||||||
|
if (const game_info game = var.value<game_info>()) {
|
||||||
|
selection = game->info.path + game->info.icon_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_old_layout_is_list = m_is_list_layout;
|
||||||
|
|
||||||
|
return selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListFrame::RepaintIcons(const bool& from_settings) {
|
||||||
|
gui::utils::stop_future_watcher(m_repaint_watcher, true);
|
||||||
|
|
||||||
|
if (from_settings) {
|
||||||
|
// TODO m_icon_color = gui::utils::get_label_color("gamelist_icon_background_color");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_is_list_layout) {
|
||||||
|
QPixmap placeholder(m_icon_size);
|
||||||
|
placeholder.fill(Qt::transparent);
|
||||||
|
|
||||||
|
for (auto& game : m_game_data) {
|
||||||
|
game->pxmap = placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixate vertical header and row height
|
||||||
|
m_game_list->verticalHeader()->setMinimumSectionSize(m_icon_size.height());
|
||||||
|
m_game_list->verticalHeader()->setMaximumSectionSize(m_icon_size.height());
|
||||||
|
|
||||||
|
// Resize the icon column
|
||||||
|
m_game_list->resizeColumnToContents(gui::column_icon);
|
||||||
|
|
||||||
|
// Shorten the last section to remove horizontal scrollbar if possible
|
||||||
|
m_game_list->resizeColumnToContents(gui::column_count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::function func = [this](const game_info& game) -> GameListItem* {
|
||||||
|
if (game->icon.isNull() &&
|
||||||
|
(game->info.icon_path.empty() ||
|
||||||
|
!game->icon.load(QString::fromStdString(game->info.icon_path)))) {
|
||||||
|
// TODO added warning message if no found
|
||||||
|
}
|
||||||
|
game->pxmap = PaintedPixmap(game->icon);
|
||||||
|
return game->item;
|
||||||
|
};
|
||||||
|
m_repaint_watcher.setFuture(QtConcurrent::mapped(m_game_data, func));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListFrame::FixNarrowColumns() const {
|
||||||
|
qApp->processEvents();
|
||||||
|
|
||||||
|
// handle columns (other than the icon column) that have zero width after showing them (stuck
|
||||||
|
// between others)
|
||||||
|
for (int col = 1; col < m_columnActs.count(); ++col) {
|
||||||
|
if (m_game_list->isColumnHidden(col)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_game_list->columnWidth(col) <=
|
||||||
|
m_game_list->horizontalHeader()->minimumSectionSize()) {
|
||||||
|
m_game_list->setColumnWidth(col, m_game_list->horizontalHeader()->minimumSectionSize());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListFrame::ResizeColumnsToContents(int spacing) const {
|
||||||
|
if (!m_game_list) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_game_list->verticalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents);
|
||||||
|
m_game_list->horizontalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents);
|
||||||
|
|
||||||
|
// Make non-icon columns slighty bigger for better visuals
|
||||||
|
for (int i = 1; i < m_game_list->columnCount(); i++) {
|
||||||
|
if (m_game_list->isColumnHidden(i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int size = m_game_list->horizontalHeader()->sectionSize(i) + spacing;
|
||||||
|
m_game_list->horizontalHeader()->resizeSection(i, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListFrame::OnHeaderColumnClicked(int col) {
|
||||||
|
if (col == 0)
|
||||||
|
return; // Don't "sort" icons.
|
||||||
|
|
||||||
|
if (col == m_sort_column) {
|
||||||
|
m_col_sort_order =
|
||||||
|
(m_col_sort_order == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder;
|
||||||
|
} else {
|
||||||
|
m_col_sort_order = Qt::AscendingOrder;
|
||||||
|
}
|
||||||
|
m_sort_column = col;
|
||||||
|
|
||||||
|
m_gui_settings->SetValue(gui::game_list_sortAsc, m_col_sort_order == Qt::AscendingOrder);
|
||||||
|
m_gui_settings->SetValue(gui::game_list_sortCol, col);
|
||||||
|
|
||||||
|
SortGameList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListFrame::SortGameList() const {
|
||||||
|
// Back-up old header sizes to handle unwanted column resize in case of zero search results
|
||||||
|
QList<int> column_widths;
|
||||||
|
const int old_row_count = m_game_list->rowCount();
|
||||||
|
const int old_game_count = m_game_data.count();
|
||||||
|
|
||||||
|
for (int i = 0; i < m_game_list->columnCount(); i++) {
|
||||||
|
column_widths.append(m_game_list->columnWidth(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sorting resizes hidden columns, so unhide them as a workaround
|
||||||
|
QList<int> columns_to_hide;
|
||||||
|
|
||||||
|
for (int i = 0; i < m_game_list->columnCount(); i++) {
|
||||||
|
if (m_game_list->isColumnHidden(i)) {
|
||||||
|
m_game_list->setColumnHidden(i, false);
|
||||||
|
columns_to_hide << i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the list by column and sort order
|
||||||
|
m_game_list->sortByColumn(m_sort_column, m_col_sort_order);
|
||||||
|
|
||||||
|
// Hide columns again
|
||||||
|
for (auto i : columns_to_hide) {
|
||||||
|
m_game_list->setColumnHidden(i, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't resize the columns if no game is shown to preserve the header settings
|
||||||
|
if (!m_game_list->rowCount()) {
|
||||||
|
for (int i = 0; i < m_game_list->columnCount(); i++) {
|
||||||
|
m_game_list->setColumnWidth(i, column_widths[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_game_list->horizontalHeader()->setSectionResizeMode(gui::column_icon, QHeaderView::Fixed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixate vertical header and row height
|
||||||
|
m_game_list->verticalHeader()->setMinimumSectionSize(m_icon_size.height());
|
||||||
|
m_game_list->verticalHeader()->setMaximumSectionSize(m_icon_size.height());
|
||||||
|
m_game_list->resizeRowsToContents();
|
||||||
|
|
||||||
|
// Resize columns if the game list was empty before
|
||||||
|
if (!old_row_count && !old_game_count) {
|
||||||
|
ResizeColumnsToContents();
|
||||||
|
} else {
|
||||||
|
m_game_list->resizeColumnToContents(gui::column_icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixate icon column
|
||||||
|
m_game_list->horizontalHeader()->setSectionResizeMode(gui::column_icon, QHeaderView::Fixed);
|
||||||
|
|
||||||
|
// Shorten the last section to remove horizontal scrollbar if possible
|
||||||
|
m_game_list->resizeColumnToContents(gui::column_count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap GameListFrame::PaintedPixmap(const QPixmap& icon) const {
|
||||||
|
const qreal device_pixel_ratio = devicePixelRatioF();
|
||||||
|
QSize canvas_size(512, 512);
|
||||||
|
QSize icon_size(icon.size());
|
||||||
|
QPoint target_pos;
|
||||||
|
|
||||||
|
if (!icon.isNull()) {
|
||||||
|
// Let's upscale the original icon to at least fit into the outer rect of the size of PS4's
|
||||||
|
// ICON0.PNG
|
||||||
|
if (icon_size.width() < 512 || icon_size.height() < 512) {
|
||||||
|
icon_size.scale(512, 512, Qt::KeepAspectRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_size = icon_size;
|
||||||
|
|
||||||
|
// Calculate the centered size and position of the icon on our canvas. not needed I believe.
|
||||||
|
if (icon_size.width() != 512 || icon_size.height() != 512) {
|
||||||
|
constexpr double target_ratio = 1.0; // aspect ratio 20:11
|
||||||
|
|
||||||
|
if ((icon_size.width() / static_cast<double>(icon_size.height())) > target_ratio) {
|
||||||
|
canvas_size.setHeight(std::ceil(icon_size.width() / target_ratio));
|
||||||
|
} else {
|
||||||
|
canvas_size.setWidth(std::ceil(icon_size.height() * target_ratio));
|
||||||
|
}
|
||||||
|
|
||||||
|
target_pos.setX(std::max<int>(0, (canvas_size.width() - icon_size.width()) / 2.0));
|
||||||
|
target_pos.setY(std::max<int>(0, (canvas_size.height() - icon_size.height()) / 2.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a canvas large enough to fit our entire scaled icon
|
||||||
|
QPixmap canvas(canvas_size * device_pixel_ratio);
|
||||||
|
canvas.setDevicePixelRatio(device_pixel_ratio);
|
||||||
|
canvas.fill(m_icon_color);
|
||||||
|
|
||||||
|
// Create a painter for our canvas
|
||||||
|
QPainter painter(&canvas);
|
||||||
|
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||||
|
|
||||||
|
// Draw the icon onto our canvas
|
||||||
|
if (!icon.isNull()) {
|
||||||
|
painter.drawPixmap(target_pos.x(), target_pos.y(), icon_size.width(), icon_size.height(),
|
||||||
|
icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish the painting
|
||||||
|
painter.end();
|
||||||
|
|
||||||
|
// Scale and return our final image
|
||||||
|
return canvas.scaled(m_icon_size * device_pixel_ratio, Qt::KeepAspectRatio,
|
||||||
|
Qt::TransformationMode::SmoothTransformation);
|
||||||
|
}
|
||||||
|
void GameListFrame::SetListMode(const bool& is_list) {
|
||||||
|
m_old_layout_is_list = m_is_list_layout;
|
||||||
|
m_is_list_layout = is_list;
|
||||||
|
|
||||||
|
m_gui_settings->SetValue(gui::game_list_listMode, is_list);
|
||||||
|
|
||||||
|
Refresh(true);
|
||||||
|
|
||||||
|
m_central_widget->setCurrentWidget(m_is_list_layout ? m_game_list : m_game_grid);
|
||||||
|
}
|
||||||
|
void GameListFrame::SetSearchText(const QString& text) {
|
||||||
|
m_search_text = text;
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
void GameListFrame::closeEvent(QCloseEvent* event) {
|
||||||
|
QDockWidget::closeEvent(event);
|
||||||
|
Q_EMIT GameListFrameClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListFrame::resizeEvent(QResizeEvent* event) {
|
||||||
|
if (!m_is_list_layout) {
|
||||||
|
Refresh(false, m_game_grid->selectedItems().count());
|
||||||
|
}
|
||||||
|
Q_EMIT ResizedWindow(m_game_list->currentItem());
|
||||||
|
QDockWidget::resizeEvent(event);
|
||||||
|
}
|
||||||
|
void GameListFrame::ResizeIcons(const int& slider_pos) {
|
||||||
|
m_icon_size_index = slider_pos;
|
||||||
|
m_icon_size = GuiSettings::SizeFromSlider(slider_pos);
|
||||||
|
|
||||||
|
RepaintIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListFrame::LoadSettings() {
|
||||||
|
m_col_sort_order = m_gui_settings->GetValue(gui::game_list_sortAsc).toBool()
|
||||||
|
? Qt::AscendingOrder
|
||||||
|
: Qt::DescendingOrder;
|
||||||
|
m_sort_column = m_gui_settings->GetValue(gui::game_list_sortCol).toInt();
|
||||||
|
|
||||||
|
Refresh(true);
|
||||||
|
|
||||||
|
const QByteArray state = m_gui_settings->GetValue(gui::game_list_state).toByteArray();
|
||||||
|
if (!m_game_list->horizontalHeader()->restoreState(state) && m_game_list->rowCount()) {
|
||||||
|
// If no settings exist, resize to contents.
|
||||||
|
ResizeColumnsToContents();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int col = 0; col < m_columnActs.count(); ++col) {
|
||||||
|
const bool vis = m_gui_settings->GetGamelistColVisibility(col);
|
||||||
|
m_columnActs[col]->setChecked(vis);
|
||||||
|
m_game_list->setColumnHidden(col, !vis);
|
||||||
|
}
|
||||||
|
SortGameList();
|
||||||
|
FixNarrowColumns();
|
||||||
|
|
||||||
|
m_game_list->horizontalHeader()->restoreState(m_game_list->horizontalHeader()->saveState());
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListFrame::SaveSettings() {
|
||||||
|
for (int col = 0; col < m_columnActs.count(); ++col) {
|
||||||
|
m_gui_settings->SetGamelistColVisibility(col, m_columnActs[col]->isChecked());
|
||||||
|
}
|
||||||
|
m_gui_settings->SetValue(gui::game_list_sortCol, m_sort_column);
|
||||||
|
m_gui_settings->SetValue(gui::game_list_sortAsc, m_col_sort_order == Qt::AscendingOrder);
|
||||||
|
m_gui_settings->SetValue(gui::game_list_state, m_game_list->horizontalHeader()->saveState());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if the game should be hidden because it doesn't match search term in toolbar.
|
||||||
|
*/
|
||||||
|
bool GameListFrame::SearchMatchesApp(const QString& name, const QString& serial) const {
|
||||||
|
if (!m_search_text.isEmpty()) {
|
||||||
|
const QString search_text = m_search_text.toLower();
|
||||||
|
return m_titles.value(serial, name).toLower().contains(search_text) ||
|
||||||
|
serial.toLower().contains(search_text);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
#include <QGraphicsBlurEffect>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QStackedWidget>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
#include "custom_dock_widget.h"
|
||||||
|
#include "game_list_grid.h"
|
||||||
|
#include "game_list_item.h"
|
||||||
|
#include "game_list_table.h"
|
||||||
|
#include "game_list_utils.h"
|
||||||
|
#include "gui_settings.h"
|
||||||
|
|
||||||
|
class GameListFrame : public CustomDockWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit GameListFrame(std::shared_ptr<GuiSettings> gui_settings, QWidget* parent = nullptr);
|
||||||
|
~GameListFrame();
|
||||||
|
/** Fix columns with width smaller than the minimal section size */
|
||||||
|
void FixNarrowColumns() const;
|
||||||
|
|
||||||
|
/** Loads from settings. Public so that main frame can easily reset these settings if needed. */
|
||||||
|
void LoadSettings();
|
||||||
|
|
||||||
|
/** Saves settings. Public so that main frame can save this when a caching of column widths is
|
||||||
|
* needed for settings backup */
|
||||||
|
void SaveSettings();
|
||||||
|
|
||||||
|
/** Resizes the columns to their contents and adds a small spacing */
|
||||||
|
void ResizeColumnsToContents(int spacing = 20) const;
|
||||||
|
|
||||||
|
/** Refresh the gamelist with/without loading game data from files. Public so that main frame
|
||||||
|
* can refresh after vfs or install */
|
||||||
|
void Refresh(const bool from_drive = false, const bool scroll_after = true);
|
||||||
|
|
||||||
|
/** Repaint Gamelist Icons with new background color */
|
||||||
|
void RepaintIcons(const bool& from_settings = false);
|
||||||
|
|
||||||
|
/** Resize Gamelist Icons to size given by slider position */
|
||||||
|
void ResizeIcons(const int& slider_pos);
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void SetSearchText(const QString& text);
|
||||||
|
void SetListMode(const bool& is_list);
|
||||||
|
private Q_SLOTS:
|
||||||
|
void OnHeaderColumnClicked(int col);
|
||||||
|
void OnRepaintFinished();
|
||||||
|
void OnRefreshFinished();
|
||||||
|
void RequestGameMenu(const QPoint& pos);
|
||||||
|
void SetListBackgroundImage(QTableWidgetItem* item);
|
||||||
|
void RefreshListBackgroundImage();
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void GameListFrameClosed();
|
||||||
|
void RequestIconSizeChange(const int& val);
|
||||||
|
void ResizedWindow(QTableWidgetItem* item);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void closeEvent(QCloseEvent* event) override;
|
||||||
|
void resizeEvent(QResizeEvent* event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPixmap PaintedPixmap(const QPixmap& icon) const;
|
||||||
|
void SortGameList() const;
|
||||||
|
std::string CurrentSelectionPath();
|
||||||
|
void PopulateGameList();
|
||||||
|
void PopulateGameGrid(int maxCols, const QSize& image_size, const QColor& image_color);
|
||||||
|
bool SearchMatchesApp(const QString& name, const QString& serial) const;
|
||||||
|
bool IsEntryVisible(const game_info& game);
|
||||||
|
static game_info GetGameInfoFromItem(const QTableWidgetItem* item);
|
||||||
|
|
||||||
|
// Which widget we are displaying depends on if we are in grid or list mode.
|
||||||
|
QMainWindow* m_game_dock = nullptr;
|
||||||
|
QStackedWidget* m_central_widget = nullptr;
|
||||||
|
|
||||||
|
// Game Grid
|
||||||
|
GameListGrid* m_game_grid = nullptr;
|
||||||
|
|
||||||
|
// Game List
|
||||||
|
GameListTable* m_game_list = nullptr;
|
||||||
|
QList<QAction*> m_columnActs;
|
||||||
|
Qt::SortOrder m_col_sort_order;
|
||||||
|
int m_sort_column;
|
||||||
|
QMap<QString, QString> m_titles;
|
||||||
|
|
||||||
|
// Game List Utils
|
||||||
|
GameListUtils m_game_list_utils;
|
||||||
|
|
||||||
|
// List Mode
|
||||||
|
bool m_is_list_layout = true;
|
||||||
|
bool m_old_layout_is_list = true;
|
||||||
|
|
||||||
|
// data
|
||||||
|
std::shared_ptr<GuiSettings> m_gui_settings;
|
||||||
|
QList<game_info> m_game_data;
|
||||||
|
std::vector<std::string> m_path_list;
|
||||||
|
std::vector<game_info> m_games;
|
||||||
|
QFutureWatcher<GameListItem*> m_repaint_watcher;
|
||||||
|
QFutureWatcher<void> m_refresh_watcher;
|
||||||
|
|
||||||
|
// Search
|
||||||
|
QString m_search_text;
|
||||||
|
|
||||||
|
// Icon Size
|
||||||
|
int m_icon_size_index = 0;
|
||||||
|
|
||||||
|
// Icons
|
||||||
|
QSize m_icon_size;
|
||||||
|
QColor m_icon_color;
|
||||||
|
qreal m_margin_factor;
|
||||||
|
qreal m_text_factor;
|
||||||
|
|
||||||
|
// Background Image
|
||||||
|
QImage backgroundImage;
|
||||||
|
|
||||||
|
void SetTableItem(GameListTable* game_list, int row, int column, QString itemStr) {
|
||||||
|
QWidget* widget = new QWidget();
|
||||||
|
QVBoxLayout* layout = new QVBoxLayout();
|
||||||
|
QLabel* label = new QLabel(itemStr);
|
||||||
|
QTableWidgetItem* item = new QTableWidgetItem();
|
||||||
|
|
||||||
|
label->setStyleSheet("color: white; font-size: 15px; font-weight: bold;");
|
||||||
|
|
||||||
|
// Create shadow effect
|
||||||
|
QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect();
|
||||||
|
shadowEffect->setBlurRadius(5); // Set the blur radius of the shadow
|
||||||
|
shadowEffect->setColor(QColor(0, 0, 0, 160)); // Set the color and opacity of the shadow
|
||||||
|
shadowEffect->setOffset(2, 2); // Set the offset of the shadow
|
||||||
|
|
||||||
|
label->setGraphicsEffect(shadowEffect); // Apply shadow effect to the QLabel
|
||||||
|
|
||||||
|
layout->addWidget(label);
|
||||||
|
if (column != 7 && column != 1)
|
||||||
|
layout->setAlignment(Qt::AlignCenter);
|
||||||
|
widget->setLayout(layout);
|
||||||
|
game_list->setItem(row, column, item);
|
||||||
|
game_list->setCellWidget(row, column, widget);
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,164 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QScrollBar>
|
||||||
|
#include "game_list_grid.h"
|
||||||
|
#include "game_list_grid_delegate.h"
|
||||||
|
#include "game_list_item.h"
|
||||||
|
|
||||||
|
GameListGrid::GameListGrid(const QSize& icon_size, QColor icon_color, const qreal& margin_factor,
|
||||||
|
const qreal& text_factor, const bool& showText)
|
||||||
|
: m_icon_size(icon_size), m_icon_color(std::move(icon_color)), m_margin_factor(margin_factor),
|
||||||
|
m_text_factor(text_factor), m_text_enabled(showText) {
|
||||||
|
setObjectName("game_grid");
|
||||||
|
|
||||||
|
QSize item_size;
|
||||||
|
if (m_text_enabled) {
|
||||||
|
item_size =
|
||||||
|
m_icon_size + QSize(m_icon_size.width() * m_margin_factor * 2,
|
||||||
|
m_icon_size.height() * m_margin_factor * (m_text_factor + 1));
|
||||||
|
} else {
|
||||||
|
item_size = m_icon_size + m_icon_size * m_margin_factor * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid_item_delegate = new GameListGridDelegate(item_size, m_margin_factor, m_text_factor, this);
|
||||||
|
setItemDelegate(grid_item_delegate);
|
||||||
|
setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||||
|
setSelectionBehavior(QAbstractItemView::SelectItems);
|
||||||
|
setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
|
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||||
|
setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||||
|
verticalScrollBar()->setSingleStep(20);
|
||||||
|
horizontalScrollBar()->setSingleStep(20);
|
||||||
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
verticalHeader()->setVisible(false);
|
||||||
|
horizontalHeader()->setVisible(false);
|
||||||
|
setShowGrid(false);
|
||||||
|
QPalette palette;
|
||||||
|
palette.setColor(QPalette::Base, QColor(230, 230, 230, 80));
|
||||||
|
setPalette(palette);
|
||||||
|
|
||||||
|
connect(this, &GameListTable::itemClicked, this, &GameListGrid::SetGridBackgroundImage);
|
||||||
|
connect(this, &GameListGrid::ResizedWindowGrid, this, &GameListGrid::SetGridBackgroundImage);
|
||||||
|
connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this,
|
||||||
|
&GameListGrid::RefreshBackgroundImage);
|
||||||
|
connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this,
|
||||||
|
&GameListGrid::RefreshBackgroundImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListGrid::enableText(const bool& enabled) {
|
||||||
|
m_text_enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListGrid::setIconSize(const QSize& size) const {
|
||||||
|
if (m_text_enabled) {
|
||||||
|
grid_item_delegate->setItemSize(
|
||||||
|
size + QSize(size.width() * m_margin_factor * 2,
|
||||||
|
size.height() * m_margin_factor * (m_text_factor + 1)));
|
||||||
|
} else {
|
||||||
|
grid_item_delegate->setItemSize(size + size * m_margin_factor * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GameListItem* GameListGrid::addItem(const game_info& app, const QString& name, const int& row,
|
||||||
|
const int& col) {
|
||||||
|
GameListItem* item = new GameListItem;
|
||||||
|
item->set_icon_func([this, app, item](int) {
|
||||||
|
const qreal device_pixel_ratio = devicePixelRatioF();
|
||||||
|
|
||||||
|
// define size of expanded image, which is raw image size + margins
|
||||||
|
QSizeF exp_size_f;
|
||||||
|
if (m_text_enabled) {
|
||||||
|
exp_size_f =
|
||||||
|
m_icon_size + QSizeF(m_icon_size.width() * m_margin_factor * 2,
|
||||||
|
m_icon_size.height() * m_margin_factor * (m_text_factor + 1));
|
||||||
|
} else {
|
||||||
|
exp_size_f = m_icon_size + m_icon_size * m_margin_factor * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// define offset for raw image placement
|
||||||
|
QPoint offset(m_icon_size.width() * m_margin_factor,
|
||||||
|
m_icon_size.height() * m_margin_factor);
|
||||||
|
const QSize exp_size = (exp_size_f * device_pixel_ratio).toSize();
|
||||||
|
|
||||||
|
// create empty canvas for expanded image
|
||||||
|
QImage exp_img(exp_size, QImage::Format_ARGB32);
|
||||||
|
exp_img.setDevicePixelRatio(device_pixel_ratio);
|
||||||
|
exp_img.fill(Qt::transparent);
|
||||||
|
|
||||||
|
// create background for image
|
||||||
|
QImage bg_img(app->pxmap.size(), QImage::Format_ARGB32);
|
||||||
|
bg_img.setDevicePixelRatio(device_pixel_ratio);
|
||||||
|
bg_img.fill(m_icon_color);
|
||||||
|
|
||||||
|
// place raw image inside expanded image
|
||||||
|
QPainter painter(&exp_img);
|
||||||
|
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||||
|
painter.drawImage(offset, bg_img);
|
||||||
|
painter.drawPixmap(offset, app->pxmap);
|
||||||
|
app->pxmap = {};
|
||||||
|
painter.end();
|
||||||
|
|
||||||
|
// create item with expanded image, title and position
|
||||||
|
item->setData(Qt::ItemDataRole::DecorationRole, QPixmap::fromImage(exp_img));
|
||||||
|
});
|
||||||
|
if (m_text_enabled) {
|
||||||
|
item->setData(Qt::ItemDataRole::DisplayRole, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
setItem(row, col, item);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal GameListGrid::getMarginFactor() const {
|
||||||
|
return m_margin_factor;
|
||||||
|
}
|
||||||
|
void GameListGrid::RefreshBackgroundImage() {
|
||||||
|
QPixmap blurredPixmap = QPixmap::fromImage(backgroundImage);
|
||||||
|
QPalette palette;
|
||||||
|
palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio)));
|
||||||
|
QColor transparentColor = QColor(135, 206, 235, 40);
|
||||||
|
palette.setColor(QPalette::Highlight, transparentColor);
|
||||||
|
this->setPalette(palette);
|
||||||
|
}
|
||||||
|
void GameListGrid::SetGridBackgroundImage(QTableWidgetItem* item) {
|
||||||
|
if (!item) {
|
||||||
|
// handle case where icon item does not exist
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QTableWidgetItem* iconItem = this->item(item->row(), item->column());
|
||||||
|
|
||||||
|
if (!iconItem) {
|
||||||
|
// handle case where icon item does not exist
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
game_info gameinfo = GetGameInfoFromItem(iconItem);
|
||||||
|
QString pic1Path = QString::fromStdString(gameinfo->info.pic_path);
|
||||||
|
QString blurredPic1Path =
|
||||||
|
qApp->applicationDirPath() +
|
||||||
|
QString::fromStdString("/game_data/" + gameinfo->info.serial + "/pic1.png");
|
||||||
|
|
||||||
|
backgroundImage = QImage(blurredPic1Path);
|
||||||
|
if (backgroundImage.isNull()) {
|
||||||
|
QImage image(pic1Path);
|
||||||
|
backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 18);
|
||||||
|
|
||||||
|
std::filesystem::path img_path =
|
||||||
|
std::filesystem::path("game_data/") / gameinfo->info.serial;
|
||||||
|
std::filesystem::create_directories(img_path);
|
||||||
|
if (!backgroundImage.save(blurredPic1Path, "PNG")) {
|
||||||
|
// qDebug() << "Error: Unable to save image.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QPixmap blurredPixmap = QPixmap::fromImage(backgroundImage);
|
||||||
|
QPalette palette;
|
||||||
|
palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio)));
|
||||||
|
QColor transparentColor = QColor(135, 206, 235, 40);
|
||||||
|
palette.setColor(QPalette::Highlight, transparentColor);
|
||||||
|
this->setPalette(palette);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListGrid::resizeEvent(QResizeEvent* event) {
|
||||||
|
Q_EMIT ResizedWindowGrid(this->currentItem());
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include "custom_dock_widget.h"
|
||||||
|
#include "game_list_table.h"
|
||||||
|
#include "game_list_utils.h"
|
||||||
|
#include "gui_settings.h"
|
||||||
|
|
||||||
|
class GameListGridDelegate;
|
||||||
|
|
||||||
|
class GameListGrid : public GameListTable {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
QSize m_icon_size;
|
||||||
|
QColor m_icon_color;
|
||||||
|
qreal m_margin_factor;
|
||||||
|
qreal m_text_factor;
|
||||||
|
bool m_text_enabled = true;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void ResizedWindowGrid(QTableWidgetItem* item);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void resizeEvent(QResizeEvent* event) override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GameListGrid(const QSize& icon_size, QColor icon_color, const qreal& margin_factor,
|
||||||
|
const qreal& text_factor, const bool& showText);
|
||||||
|
|
||||||
|
void enableText(const bool& enabled);
|
||||||
|
void setIconSize(const QSize& size) const;
|
||||||
|
GameListItem* addItem(const game_info& app, const QString& name, const int& row,
|
||||||
|
const int& col);
|
||||||
|
|
||||||
|
[[nodiscard]] qreal getMarginFactor() const;
|
||||||
|
|
||||||
|
game_info GetGameInfoFromItem(const QTableWidgetItem* item) {
|
||||||
|
if (!item) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QVariant var = item->data(gui::game_role);
|
||||||
|
if (!var.canConvert<game_info>()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return var.value<game_info>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SetGridBackgroundImage(QTableWidgetItem* item);
|
||||||
|
void RefreshBackgroundImage();
|
||||||
|
|
||||||
|
GameListGridDelegate* grid_item_delegate;
|
||||||
|
GameListUtils m_game_list_utils;
|
||||||
|
|
||||||
|
// Background Image
|
||||||
|
QImage backgroundImage;
|
||||||
|
};
|
|
@ -0,0 +1,67 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "game_list_grid_delegate.h"
|
||||||
|
|
||||||
|
GameListGridDelegate::GameListGridDelegate(const QSize& size, const qreal& margin_factor,
|
||||||
|
const qreal& text_factor, QObject* parent)
|
||||||
|
: QStyledItemDelegate(parent), m_size(size), m_margin_factor(margin_factor),
|
||||||
|
m_text_factor(text_factor) {}
|
||||||
|
|
||||||
|
void GameListGridDelegate::initStyleOption(QStyleOptionViewItem* option,
|
||||||
|
const QModelIndex& index) const {
|
||||||
|
Q_UNUSED(index)
|
||||||
|
|
||||||
|
// Remove the focus frame around selected items
|
||||||
|
option->state &= ~QStyle::State_HasFocus;
|
||||||
|
|
||||||
|
// Call initStyleOption without a model index, since we want to paint the relevant data
|
||||||
|
// ourselves
|
||||||
|
QStyledItemDelegate::initStyleOption(option, QModelIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListGridDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
|
||||||
|
const QModelIndex& index) const {
|
||||||
|
const QRect r = option.rect;
|
||||||
|
|
||||||
|
painter->setRenderHints(QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
|
||||||
|
painter->eraseRect(r);
|
||||||
|
|
||||||
|
// Get title and image
|
||||||
|
const QPixmap image = qvariant_cast<QPixmap>(index.data(Qt::DecorationRole));
|
||||||
|
const QString title = index.data(Qt::DisplayRole).toString();
|
||||||
|
|
||||||
|
// Paint from our stylesheet
|
||||||
|
QStyledItemDelegate::paint(painter, option, index);
|
||||||
|
|
||||||
|
// image
|
||||||
|
if (image.isNull() == false) {
|
||||||
|
painter->drawPixmap(option.rect, image);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int h = r.height() / (1 + m_margin_factor + m_margin_factor * m_text_factor);
|
||||||
|
const int height = r.height() - h - h * m_margin_factor;
|
||||||
|
const int top = r.bottom() - height;
|
||||||
|
|
||||||
|
// title
|
||||||
|
if (option.state & QStyle::State_Selected) {
|
||||||
|
painter->setPen(QPen(option.palette.color(QPalette::HighlightedText), 1, Qt::SolidLine));
|
||||||
|
} else {
|
||||||
|
painter->setPen(QPen(option.palette.color(QPalette::WindowText), 1, Qt::SolidLine));
|
||||||
|
}
|
||||||
|
|
||||||
|
painter->setFont(option.font);
|
||||||
|
painter->drawText(QRect(r.left(), top, r.width(), height), +Qt::TextWordWrap | +Qt::AlignCenter,
|
||||||
|
title);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize GameListGridDelegate::sizeHint(const QStyleOptionViewItem& option,
|
||||||
|
const QModelIndex& index) const {
|
||||||
|
Q_UNUSED(option)
|
||||||
|
Q_UNUSED(index)
|
||||||
|
return m_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListGridDelegate::setItemSize(const QSize& size) {
|
||||||
|
m_size = size;
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
|
|
||||||
|
class GameListGridDelegate : public QStyledItemDelegate {
|
||||||
|
public:
|
||||||
|
GameListGridDelegate(const QSize& imageSize, const qreal& margin_factor,
|
||||||
|
const qreal& margin_ratio, QObject* parent = nullptr);
|
||||||
|
|
||||||
|
void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override;
|
||||||
|
void paint(QPainter* painter, const QStyleOptionViewItem& option,
|
||||||
|
const QModelIndex& index) const override;
|
||||||
|
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
|
||||||
|
void setItemSize(const QSize& size);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSize m_size;
|
||||||
|
qreal m_margin_factor;
|
||||||
|
qreal m_text_factor;
|
||||||
|
};
|
|
@ -0,0 +1,35 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTableWidgetItem>
|
||||||
|
|
||||||
|
using icon_callback_t = std::function<void(int)>;
|
||||||
|
|
||||||
|
class GameListItem : public QTableWidgetItem {
|
||||||
|
public:
|
||||||
|
GameListItem() : QTableWidgetItem() {}
|
||||||
|
GameListItem(const QString& text, int type = Type) : QTableWidgetItem(text, type) {}
|
||||||
|
GameListItem(const QIcon& icon, const QString& text, int type = Type)
|
||||||
|
: QTableWidgetItem(icon, text, type) {}
|
||||||
|
|
||||||
|
~GameListItem() {}
|
||||||
|
|
||||||
|
void call_icon_func() const {
|
||||||
|
if (m_icon_callback) {
|
||||||
|
m_icon_callback(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_icon_func(const icon_callback_t& func) {
|
||||||
|
m_icon_callback = func;
|
||||||
|
call_icon_func();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
icon_callback_t m_icon_callback = nullptr;
|
||||||
|
};
|
|
@ -0,0 +1,18 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "game_list_table.h"
|
||||||
|
|
||||||
|
void GameListTable::clear_list() {
|
||||||
|
clearSelection();
|
||||||
|
clearContents();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListTable::mousePressEvent(QMouseEvent* event) {
|
||||||
|
if (QTableWidgetItem* item = itemAt(event->pos());
|
||||||
|
!item || !item->data(Qt::UserRole).isValid()) {
|
||||||
|
clearSelection();
|
||||||
|
setCurrentItem(nullptr); // Needed for currentItemChanged
|
||||||
|
}
|
||||||
|
QTableWidget::mousePressEvent(event);
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QTableWidget>
|
||||||
|
|
||||||
|
#include "game_info.h"
|
||||||
|
#include "game_list_item.h"
|
||||||
|
|
||||||
|
struct GuiGameInfo {
|
||||||
|
GameInfo info{};
|
||||||
|
QPixmap icon;
|
||||||
|
QPixmap pxmap;
|
||||||
|
GameListItem* item = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::shared_ptr<GuiGameInfo> game_info;
|
||||||
|
Q_DECLARE_METATYPE(game_info)
|
||||||
|
|
||||||
|
class GameListTable : public QTableWidget {
|
||||||
|
public:
|
||||||
|
void clear_list();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void mousePressEvent(QMouseEvent* event) override;
|
||||||
|
};
|
|
@ -0,0 +1,109 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QDirIterator>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class GameListUtils {
|
||||||
|
public:
|
||||||
|
static QString FormatSize(qint64 size) {
|
||||||
|
static const QStringList suffixes = {"B", "KB", "MB", "GB", "TB"};
|
||||||
|
int suffixIndex = 0;
|
||||||
|
|
||||||
|
while (size >= 1024 && suffixIndex < suffixes.size() - 1) {
|
||||||
|
size /= 1024;
|
||||||
|
++suffixIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString("%1 %2").arg(size).arg(suffixes[suffixIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString GetFolderSize(const QDir& dir) {
|
||||||
|
|
||||||
|
QDirIterator it(dir.absolutePath(), QDirIterator::Subdirectories);
|
||||||
|
qint64 total = 0;
|
||||||
|
|
||||||
|
while (it.hasNext()) {
|
||||||
|
// check if entry is file
|
||||||
|
if (it.fileInfo().isFile()) {
|
||||||
|
total += it.fileInfo().size();
|
||||||
|
}
|
||||||
|
it.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there is a file left "at the end" get it's size
|
||||||
|
if (it.fileInfo().isFile()) {
|
||||||
|
total += it.fileInfo().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
return FormatSize(total);
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage BlurImage(const QImage& image, const QRect& rect, int radius) {
|
||||||
|
int tab[] = {14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2};
|
||||||
|
int alpha = (radius < 1) ? 16 : (radius > 17) ? 1 : tab[radius - 1];
|
||||||
|
|
||||||
|
QImage result = image.convertToFormat(QImage::Format_ARGB32);
|
||||||
|
int r1 = rect.top();
|
||||||
|
int r2 = rect.bottom();
|
||||||
|
int c1 = rect.left();
|
||||||
|
int c2 = rect.right();
|
||||||
|
|
||||||
|
int bpl = result.bytesPerLine();
|
||||||
|
int rgba[4];
|
||||||
|
unsigned char* p;
|
||||||
|
|
||||||
|
int i1 = 0;
|
||||||
|
int i2 = 3;
|
||||||
|
|
||||||
|
for (int col = c1; col <= c2; col++) {
|
||||||
|
p = result.scanLine(r1) + col * 4;
|
||||||
|
for (int i = i1; i <= i2; i++)
|
||||||
|
rgba[i] = p[i] << 4;
|
||||||
|
|
||||||
|
p += bpl;
|
||||||
|
for (int j = r1; j < r2; j++, p += bpl)
|
||||||
|
for (int i = i1; i <= i2; i++)
|
||||||
|
p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int row = r1; row <= r2; row++) {
|
||||||
|
p = result.scanLine(row) + c1 * 4;
|
||||||
|
for (int i = i1; i <= i2; i++)
|
||||||
|
rgba[i] = p[i] << 4;
|
||||||
|
|
||||||
|
p += 4;
|
||||||
|
for (int j = c1; j < c2; j++, p += 4)
|
||||||
|
for (int i = i1; i <= i2; i++)
|
||||||
|
p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int col = c1; col <= c2; col++) {
|
||||||
|
p = result.scanLine(r2) + col * 4;
|
||||||
|
for (int i = i1; i <= i2; i++)
|
||||||
|
rgba[i] = p[i] << 4;
|
||||||
|
|
||||||
|
p -= bpl;
|
||||||
|
for (int j = r1; j < r2; j++, p -= bpl)
|
||||||
|
for (int i = i1; i <= i2; i++)
|
||||||
|
p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int row = r1; row <= r2; row++) {
|
||||||
|
p = result.scanLine(row) + c2 * 4;
|
||||||
|
for (int i = i1; i <= i2; i++)
|
||||||
|
rgba[i] = p[i] << 4;
|
||||||
|
|
||||||
|
p -= 4;
|
||||||
|
for (int j = c1; j < c2; j++, p -= 4)
|
||||||
|
for (int i = i1; i <= i2; i++)
|
||||||
|
p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,29 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
struct GuiSave {
|
||||||
|
QString key;
|
||||||
|
QString name;
|
||||||
|
QVariant def;
|
||||||
|
|
||||||
|
GuiSave() {
|
||||||
|
key = "";
|
||||||
|
name = "";
|
||||||
|
def = QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
GuiSave(const QString& k, const QString& n, const QVariant& d) {
|
||||||
|
key = k;
|
||||||
|
name = n;
|
||||||
|
def = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const GuiSave& rhs) const noexcept {
|
||||||
|
return key == rhs.key && name == rhs.name && def == rhs.def;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,29 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "gui_settings.h"
|
||||||
|
|
||||||
|
GuiSettings::GuiSettings(QObject* parent) {
|
||||||
|
m_settings.reset(new QSettings("shadps4qt.ini", QSettings::Format::IniFormat,
|
||||||
|
parent)); // TODO make the path configurable
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuiSettings::SetGamelistColVisibility(int col, bool val) const {
|
||||||
|
SetValue(GetGuiSaveForColumn(col), val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GuiSettings::GetGamelistColVisibility(int col) const {
|
||||||
|
return GetValue(GetGuiSaveForColumn(col)).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
GuiSave GuiSettings::GetGuiSaveForColumn(int col) {
|
||||||
|
return GuiSave{gui::game_list,
|
||||||
|
"visibility_" +
|
||||||
|
gui::get_game_list_column_name(static_cast<gui::game_list_columns>(col)),
|
||||||
|
true};
|
||||||
|
}
|
||||||
|
QSize GuiSettings::SizeFromSlider(int pos) {
|
||||||
|
return gui::game_list_icon_size_min +
|
||||||
|
(gui::game_list_icon_size_max - gui::game_list_icon_size_min) *
|
||||||
|
(1.f * pos / gui::game_list_max_slider_pos);
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QColor>
|
||||||
|
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
namespace gui {
|
||||||
|
enum custom_roles {
|
||||||
|
game_role = Qt::UserRole + 1337,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum game_list_columns {
|
||||||
|
column_icon,
|
||||||
|
column_name,
|
||||||
|
column_serial,
|
||||||
|
column_firmware,
|
||||||
|
column_size,
|
||||||
|
column_version,
|
||||||
|
column_category,
|
||||||
|
column_path,
|
||||||
|
column_count
|
||||||
|
};
|
||||||
|
|
||||||
|
inline QString get_game_list_column_name(game_list_columns col) {
|
||||||
|
switch (col) {
|
||||||
|
case column_icon:
|
||||||
|
return "column_icon";
|
||||||
|
case column_name:
|
||||||
|
return "column_name";
|
||||||
|
case column_serial:
|
||||||
|
return "column_serial";
|
||||||
|
case column_firmware:
|
||||||
|
return "column_firmware";
|
||||||
|
case column_size:
|
||||||
|
return "column_size";
|
||||||
|
case column_version:
|
||||||
|
return "column_version";
|
||||||
|
case column_category:
|
||||||
|
return "column_category";
|
||||||
|
case column_path:
|
||||||
|
return "column_path";
|
||||||
|
case column_count:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error("get_game_list_column_name: Invalid column");
|
||||||
|
}
|
||||||
|
|
||||||
|
const QSize game_list_icon_size_min = QSize(28, 28);
|
||||||
|
const QSize game_list_icon_size_small = QSize(56, 56);
|
||||||
|
const QSize game_list_icon_size_medium = QSize(128, 128);
|
||||||
|
const QSize game_list_icon_size_max =
|
||||||
|
QSize(256, 256); // let's do 256, 512 is too big (that's what she said)
|
||||||
|
|
||||||
|
const int game_list_max_slider_pos = 100;
|
||||||
|
|
||||||
|
inline int get_Index(const QSize& current) {
|
||||||
|
const int size_delta = game_list_icon_size_max.width() - game_list_icon_size_min.width();
|
||||||
|
const int current_delta = current.width() - game_list_icon_size_min.width();
|
||||||
|
return game_list_max_slider_pos * current_delta / size_delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString main_window = "main_window";
|
||||||
|
const QString game_list = "GameList";
|
||||||
|
const QString settings = "Settings";
|
||||||
|
const QString themes = "Themes";
|
||||||
|
|
||||||
|
const QColor game_list_icon_color = QColor(240, 240, 240, 255);
|
||||||
|
|
||||||
|
const GuiSave main_window_gamelist_visible = GuiSave(main_window, "gamelistVisible", true);
|
||||||
|
const GuiSave main_window_geometry = GuiSave(main_window, "geometry", QByteArray());
|
||||||
|
const GuiSave main_window_windowState = GuiSave(main_window, "windowState", QByteArray());
|
||||||
|
const GuiSave main_window_mwState = GuiSave(main_window, "mwState", QByteArray());
|
||||||
|
|
||||||
|
const GuiSave game_list_sortAsc = GuiSave(game_list, "sortAsc", true);
|
||||||
|
const GuiSave game_list_sortCol = GuiSave(game_list, "sortCol", 1);
|
||||||
|
const GuiSave game_list_state = GuiSave(game_list, "state", QByteArray());
|
||||||
|
const GuiSave game_list_iconSize =
|
||||||
|
GuiSave(game_list, "iconSize", get_Index(game_list_icon_size_small));
|
||||||
|
const GuiSave game_list_iconSizeGrid =
|
||||||
|
GuiSave(game_list, "iconSizeGrid", get_Index(game_list_icon_size_small));
|
||||||
|
const GuiSave game_list_iconColor = GuiSave(game_list, "iconColor", game_list_icon_color);
|
||||||
|
const GuiSave game_list_listMode = GuiSave(game_list, "listMode", true);
|
||||||
|
const GuiSave game_list_textFactor = GuiSave(game_list, "textFactor", qreal{2.0});
|
||||||
|
const GuiSave game_list_marginFactor = GuiSave(game_list, "marginFactor", qreal{0.09});
|
||||||
|
const GuiSave settings_install_dir = GuiSave(settings, "installDirectory", "");
|
||||||
|
const GuiSave mw_themes = GuiSave(themes, "Themes", 0);
|
||||||
|
|
||||||
|
} // namespace gui
|
||||||
|
|
||||||
|
class GuiSettings : public Settings {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GuiSettings(QObject* parent = nullptr);
|
||||||
|
|
||||||
|
bool GetGamelistColVisibility(int col) const;
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void SetGamelistColVisibility(int col, bool val) const;
|
||||||
|
static GuiSave GetGuiSaveForColumn(int col);
|
||||||
|
static QSize SizeFromSlider(int pos);
|
||||||
|
};
|
|
@ -0,0 +1,21 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <QtWidgets/QApplication>
|
||||||
|
|
||||||
|
#include "qt_gui/game_install_dialog.h"
|
||||||
|
#include "qt_gui/gui_settings.h"
|
||||||
|
#include "qt_gui/main_window.h"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
QApplication a(argc, argv);
|
||||||
|
auto m_gui_settings = std::make_shared<GuiSettings>();
|
||||||
|
if (m_gui_settings->GetValue(gui::settings_install_dir) == "") {
|
||||||
|
GameInstallDialog dlg(m_gui_settings);
|
||||||
|
dlg.exec();
|
||||||
|
}
|
||||||
|
MainWindow* m_main_window = new MainWindow(m_gui_settings, nullptr);
|
||||||
|
m_main_window->Init();
|
||||||
|
|
||||||
|
return a.exec();
|
||||||
|
}
|
|
@ -0,0 +1,364 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QProgressDialog>
|
||||||
|
|
||||||
|
#include "common/io_file.h"
|
||||||
|
#include "core/file_format/pkg.h"
|
||||||
|
#include "core/loader.h"
|
||||||
|
#include "game_install_dialog.h"
|
||||||
|
#include "game_list_frame.h"
|
||||||
|
#include "gui_settings.h"
|
||||||
|
#include "main_window.h"
|
||||||
|
|
||||||
|
MainWindow::MainWindow(std::shared_ptr<GuiSettings> gui_settings, QWidget* parent)
|
||||||
|
: QMainWindow(parent), ui(new Ui::MainWindow), m_gui_settings(std::move(gui_settings)) {
|
||||||
|
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWindow::~MainWindow() {
|
||||||
|
SaveWindowState();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MainWindow::Init() {
|
||||||
|
// add toolbar widgets
|
||||||
|
QApplication::setStyle("Fusion");
|
||||||
|
ui->toolBar->setObjectName("mw_toolbar");
|
||||||
|
ui->sizeSlider->setRange(0, gui::game_list_max_slider_pos);
|
||||||
|
ui->toolBar->addWidget(ui->sizeSliderContainer);
|
||||||
|
ui->toolBar->addWidget(ui->mw_searchbar);
|
||||||
|
|
||||||
|
CreateActions();
|
||||||
|
CreateDockWindows();
|
||||||
|
CreateConnects();
|
||||||
|
SetLastUsedTheme();
|
||||||
|
|
||||||
|
setMinimumSize(350, minimumSizeHint().height());
|
||||||
|
setWindowTitle(QString::fromStdString("ShadPS4 v0.0.2"));
|
||||||
|
|
||||||
|
ConfigureGuiFromSettings();
|
||||||
|
|
||||||
|
show();
|
||||||
|
|
||||||
|
// Fix possible hidden game list columns. The game list has to be visible already. Use this
|
||||||
|
// after show()
|
||||||
|
m_game_list_frame->FixNarrowColumns();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::CreateActions() {
|
||||||
|
// create action group for icon size
|
||||||
|
m_icon_size_act_group = new QActionGroup(this);
|
||||||
|
m_icon_size_act_group->addAction(ui->setIconSizeTinyAct);
|
||||||
|
m_icon_size_act_group->addAction(ui->setIconSizeSmallAct);
|
||||||
|
m_icon_size_act_group->addAction(ui->setIconSizeMediumAct);
|
||||||
|
m_icon_size_act_group->addAction(ui->setIconSizeLargeAct);
|
||||||
|
|
||||||
|
// create action group for list mode
|
||||||
|
m_list_mode_act_group = new QActionGroup(this);
|
||||||
|
m_list_mode_act_group->addAction(ui->setlistModeListAct);
|
||||||
|
m_list_mode_act_group->addAction(ui->setlistModeGridAct);
|
||||||
|
|
||||||
|
// create action group for themes
|
||||||
|
m_theme_act_group = new QActionGroup(this);
|
||||||
|
m_theme_act_group->addAction(ui->setThemeLight);
|
||||||
|
m_theme_act_group->addAction(ui->setThemeDark);
|
||||||
|
m_theme_act_group->addAction(ui->setThemeGreen);
|
||||||
|
m_theme_act_group->addAction(ui->setThemeBlue);
|
||||||
|
m_theme_act_group->addAction(ui->setThemeViolet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::CreateDockWindows() {
|
||||||
|
m_main_window = new QMainWindow();
|
||||||
|
m_main_window->setContextMenuPolicy(Qt::PreventContextMenu);
|
||||||
|
|
||||||
|
m_game_list_frame = new GameListFrame(m_gui_settings, m_main_window);
|
||||||
|
m_game_list_frame->setObjectName("gamelist");
|
||||||
|
|
||||||
|
m_main_window->addDockWidget(Qt::LeftDockWidgetArea, m_game_list_frame);
|
||||||
|
|
||||||
|
m_main_window->setDockNestingEnabled(true);
|
||||||
|
|
||||||
|
setCentralWidget(m_main_window);
|
||||||
|
|
||||||
|
connect(m_game_list_frame, &GameListFrame::GameListFrameClosed, this, [this]() {
|
||||||
|
if (ui->showGameListAct->isChecked()) {
|
||||||
|
ui->showGameListAct->setChecked(false);
|
||||||
|
m_gui_settings->SetValue(gui::main_window_gamelist_visible, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
void MainWindow::CreateConnects() {
|
||||||
|
connect(ui->exitAct, &QAction::triggered, this, &QWidget::close);
|
||||||
|
|
||||||
|
connect(ui->showGameListAct, &QAction::triggered, this, [this](bool checked) {
|
||||||
|
checked ? m_game_list_frame->show() : m_game_list_frame->hide();
|
||||||
|
m_gui_settings->SetValue(gui::main_window_gamelist_visible, checked);
|
||||||
|
});
|
||||||
|
connect(ui->refreshGameListAct, &QAction::triggered, this,
|
||||||
|
[this] { m_game_list_frame->Refresh(true); });
|
||||||
|
|
||||||
|
connect(m_icon_size_act_group, &QActionGroup::triggered, this, [this](QAction* act) {
|
||||||
|
static const int index_small = gui::get_Index(gui::game_list_icon_size_small);
|
||||||
|
static const int index_medium = gui::get_Index(gui::game_list_icon_size_medium);
|
||||||
|
|
||||||
|
int index;
|
||||||
|
|
||||||
|
if (act == ui->setIconSizeTinyAct)
|
||||||
|
index = 0;
|
||||||
|
else if (act == ui->setIconSizeSmallAct)
|
||||||
|
index = index_small;
|
||||||
|
else if (act == ui->setIconSizeMediumAct)
|
||||||
|
index = index_medium;
|
||||||
|
else
|
||||||
|
index = gui::game_list_max_slider_pos;
|
||||||
|
|
||||||
|
m_save_slider_pos = true;
|
||||||
|
ResizeIcons(index);
|
||||||
|
});
|
||||||
|
connect(m_game_list_frame, &GameListFrame::RequestIconSizeChange, this, [this](const int& val) {
|
||||||
|
const int idx = ui->sizeSlider->value() + val;
|
||||||
|
m_save_slider_pos = true;
|
||||||
|
ResizeIcons(idx);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_list_mode_act_group, &QActionGroup::triggered, this, [this](QAction* act) {
|
||||||
|
const bool is_list_act = act == ui->setlistModeListAct;
|
||||||
|
if (is_list_act == m_is_list_mode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const int slider_pos = ui->sizeSlider->sliderPosition();
|
||||||
|
ui->sizeSlider->setSliderPosition(m_other_slider_pos);
|
||||||
|
SetIconSizeActions(m_other_slider_pos);
|
||||||
|
m_other_slider_pos = slider_pos;
|
||||||
|
|
||||||
|
m_is_list_mode = is_list_act;
|
||||||
|
m_game_list_frame->SetListMode(m_is_list_mode);
|
||||||
|
});
|
||||||
|
connect(ui->sizeSlider, &QSlider::valueChanged, this, &MainWindow::ResizeIcons);
|
||||||
|
connect(ui->sizeSlider, &QSlider::sliderReleased, this, [this] {
|
||||||
|
const int index = ui->sizeSlider->value();
|
||||||
|
m_gui_settings->SetValue(
|
||||||
|
m_is_list_mode ? gui::game_list_iconSize : gui::game_list_iconSizeGrid, index);
|
||||||
|
SetIconSizeActions(index);
|
||||||
|
});
|
||||||
|
connect(ui->sizeSlider, &QSlider::actionTriggered, this, [this](int action) {
|
||||||
|
if (action != QAbstractSlider::SliderNoAction &&
|
||||||
|
action !=
|
||||||
|
QAbstractSlider::SliderMove) { // we only want to save on mouseclicks or slider
|
||||||
|
// release (the other connect handles this)
|
||||||
|
m_save_slider_pos = true; // actionTriggered happens before the value was changed
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(ui->mw_searchbar, &QLineEdit::textChanged, m_game_list_frame,
|
||||||
|
&GameListFrame::SetSearchText);
|
||||||
|
connect(ui->bootInstallPkgAct, &QAction::triggered, this, [this] { InstallPkg(); });
|
||||||
|
connect(ui->gameInstallPathAct, &QAction::triggered, this, [this] { InstallDirectory(); });
|
||||||
|
|
||||||
|
// Themes
|
||||||
|
connect(ui->setThemeLight, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
|
m_window_themes.SetWindowTheme(Theme::Light, ui->mw_searchbar);
|
||||||
|
m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Light));
|
||||||
|
});
|
||||||
|
connect(ui->setThemeDark, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
|
m_window_themes.SetWindowTheme(Theme::Dark, ui->mw_searchbar);
|
||||||
|
m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Dark));
|
||||||
|
});
|
||||||
|
connect(ui->setThemeGreen, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
|
m_window_themes.SetWindowTheme(Theme::Green, ui->mw_searchbar);
|
||||||
|
m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Green));
|
||||||
|
});
|
||||||
|
connect(ui->setThemeBlue, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
|
m_window_themes.SetWindowTheme(Theme::Blue, ui->mw_searchbar);
|
||||||
|
m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Blue));
|
||||||
|
});
|
||||||
|
connect(ui->setThemeViolet, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
|
m_window_themes.SetWindowTheme(Theme::Violet, ui->mw_searchbar);
|
||||||
|
m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Violet));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::SetIconSizeActions(int idx) const {
|
||||||
|
static const int threshold_tiny =
|
||||||
|
gui::get_Index((gui::game_list_icon_size_small + gui::game_list_icon_size_min) / 2);
|
||||||
|
static const int threshold_small =
|
||||||
|
gui::get_Index((gui::game_list_icon_size_medium + gui::game_list_icon_size_small) / 2);
|
||||||
|
static const int threshold_medium =
|
||||||
|
gui::get_Index((gui::game_list_icon_size_max + gui::game_list_icon_size_medium) / 2);
|
||||||
|
|
||||||
|
if (idx < threshold_tiny)
|
||||||
|
ui->setIconSizeTinyAct->setChecked(true);
|
||||||
|
else if (idx < threshold_small)
|
||||||
|
ui->setIconSizeSmallAct->setChecked(true);
|
||||||
|
else if (idx < threshold_medium)
|
||||||
|
ui->setIconSizeMediumAct->setChecked(true);
|
||||||
|
else
|
||||||
|
ui->setIconSizeLargeAct->setChecked(true);
|
||||||
|
}
|
||||||
|
void MainWindow::ResizeIcons(int index) {
|
||||||
|
if (ui->sizeSlider->value() != index) {
|
||||||
|
ui->sizeSlider->setSliderPosition(index);
|
||||||
|
return; // ResizeIcons will be triggered again by setSliderPosition, so return here
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_save_slider_pos) {
|
||||||
|
m_save_slider_pos = false;
|
||||||
|
m_gui_settings->SetValue(
|
||||||
|
m_is_list_mode ? gui::game_list_iconSize : gui::game_list_iconSizeGrid, index);
|
||||||
|
|
||||||
|
// this will also fire when we used the actions, but i didn't want to add another boolean
|
||||||
|
// member
|
||||||
|
SetIconSizeActions(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_game_list_frame->ResizeIcons(index);
|
||||||
|
}
|
||||||
|
void MainWindow::ConfigureGuiFromSettings() {
|
||||||
|
// Restore GUI state if needed. We need to if they exist.
|
||||||
|
if (!restoreGeometry(m_gui_settings->GetValue(gui::main_window_geometry).toByteArray())) {
|
||||||
|
resize(QGuiApplication::primaryScreen()->availableSize() * 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreState(m_gui_settings->GetValue(gui::main_window_windowState).toByteArray());
|
||||||
|
m_main_window->restoreState(m_gui_settings->GetValue(gui::main_window_mwState).toByteArray());
|
||||||
|
|
||||||
|
ui->showGameListAct->setChecked(
|
||||||
|
m_gui_settings->GetValue(gui::main_window_gamelist_visible).toBool());
|
||||||
|
|
||||||
|
m_game_list_frame->setVisible(ui->showGameListAct->isChecked());
|
||||||
|
|
||||||
|
// handle icon size options
|
||||||
|
m_is_list_mode = m_gui_settings->GetValue(gui::game_list_listMode).toBool();
|
||||||
|
if (m_is_list_mode)
|
||||||
|
ui->setlistModeListAct->setChecked(true);
|
||||||
|
else
|
||||||
|
ui->setlistModeGridAct->setChecked(true);
|
||||||
|
|
||||||
|
const int icon_size_index =
|
||||||
|
m_gui_settings
|
||||||
|
->GetValue(m_is_list_mode ? gui::game_list_iconSize : gui::game_list_iconSizeGrid)
|
||||||
|
.toInt();
|
||||||
|
m_other_slider_pos =
|
||||||
|
m_gui_settings
|
||||||
|
->GetValue(!m_is_list_mode ? gui::game_list_iconSize : gui::game_list_iconSizeGrid)
|
||||||
|
.toInt();
|
||||||
|
ui->sizeSlider->setSliderPosition(icon_size_index);
|
||||||
|
SetIconSizeActions(icon_size_index);
|
||||||
|
|
||||||
|
// Gamelist
|
||||||
|
m_game_list_frame->LoadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::SaveWindowState() const {
|
||||||
|
// Save gui settings
|
||||||
|
m_gui_settings->SetValue(gui::main_window_geometry, saveGeometry());
|
||||||
|
m_gui_settings->SetValue(gui::main_window_windowState, saveState());
|
||||||
|
m_gui_settings->SetValue(gui::main_window_mwState, m_main_window->saveState());
|
||||||
|
|
||||||
|
// Save column settings
|
||||||
|
m_game_list_frame->SaveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::InstallPkg() {
|
||||||
|
QStringList fileNames = QFileDialog::getOpenFileNames(
|
||||||
|
this, tr("Install PKG Files"), QDir::currentPath(), tr("PKG File (*.PKG)"));
|
||||||
|
int nPkg = fileNames.size();
|
||||||
|
int pkgNum = 0;
|
||||||
|
for (const QString& file : fileNames) {
|
||||||
|
pkgNum++;
|
||||||
|
MainWindow::InstallDragDropPkg(file.toStdString(), pkgNum, nPkg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::InstallDragDropPkg(std::string file, int pkgNum, int nPkg) {
|
||||||
|
|
||||||
|
if (Loader::DetectFileType(file) == Loader::FileTypes::Pkg) {
|
||||||
|
PKG pkg;
|
||||||
|
pkg.Open(file);
|
||||||
|
std::string failreason;
|
||||||
|
const auto extract_path =
|
||||||
|
std::filesystem::path(
|
||||||
|
m_gui_settings->GetValue(gui::settings_install_dir).toString().toStdString()) /
|
||||||
|
pkg.GetTitleID();
|
||||||
|
if (!pkg.Extract(file, extract_path, failreason)) {
|
||||||
|
QMessageBox::critical(this, "PKG ERROR", QString::fromStdString(failreason),
|
||||||
|
QMessageBox::Ok, 0);
|
||||||
|
} else {
|
||||||
|
int nfiles = pkg.GetNumberOfFiles();
|
||||||
|
|
||||||
|
QList<int> indices;
|
||||||
|
for (int i = 0; i < nfiles; i++) {
|
||||||
|
indices.append(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
QProgressDialog dialog;
|
||||||
|
dialog.setWindowTitle("PKG Extraction");
|
||||||
|
QString extractmsg = QString("Extracting PKG %1/%2").arg(pkgNum).arg(nPkg);
|
||||||
|
dialog.setLabelText(extractmsg);
|
||||||
|
|
||||||
|
// Create a QFutureWatcher and connect signals and slots.
|
||||||
|
QFutureWatcher<void> futureWatcher;
|
||||||
|
QObject::connect(&futureWatcher, SIGNAL(finished()), &dialog, SLOT(reset()));
|
||||||
|
QObject::connect(&dialog, SIGNAL(canceled()), &futureWatcher, SLOT(cancel()));
|
||||||
|
QObject::connect(&futureWatcher, SIGNAL(progressRangeChanged(int, int)), &dialog,
|
||||||
|
SLOT(setRange(int, int)));
|
||||||
|
QObject::connect(&futureWatcher, SIGNAL(progressValueChanged(int)), &dialog,
|
||||||
|
SLOT(setValue(int)));
|
||||||
|
|
||||||
|
futureWatcher.setFuture(QtConcurrent::map(
|
||||||
|
indices, std::bind(&PKG::ExtractFiles, pkg, std::placeholders::_1)));
|
||||||
|
|
||||||
|
// Display the dialog and start the event loop.
|
||||||
|
dialog.exec();
|
||||||
|
futureWatcher.waitForFinished();
|
||||||
|
|
||||||
|
auto path = m_gui_settings->GetValue(gui::settings_install_dir).toString();
|
||||||
|
if (pkgNum == nPkg) {
|
||||||
|
QMessageBox::information(this, "Extraction Finished",
|
||||||
|
"Game successfully installed at " + path, QMessageBox::Ok,
|
||||||
|
0);
|
||||||
|
m_game_list_frame->Refresh(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
QMessageBox::critical(this, "PKG ERROR", "File doesn't appear to be a valid PKG file",
|
||||||
|
QMessageBox::Ok, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::InstallDirectory() {
|
||||||
|
GameInstallDialog dlg(m_gui_settings);
|
||||||
|
dlg.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::SetLastUsedTheme() {
|
||||||
|
|
||||||
|
Theme lastTheme = static_cast<Theme>(m_gui_settings->GetValue(gui::mw_themes).toInt());
|
||||||
|
m_window_themes.SetWindowTheme(lastTheme, ui->mw_searchbar);
|
||||||
|
|
||||||
|
switch (lastTheme) {
|
||||||
|
case Theme::Light:
|
||||||
|
ui->setThemeLight->setChecked(true);
|
||||||
|
break;
|
||||||
|
case Theme::Dark:
|
||||||
|
ui->setThemeDark->setChecked(true);
|
||||||
|
break;
|
||||||
|
case Theme::Green:
|
||||||
|
ui->setThemeGreen->setChecked(true);
|
||||||
|
break;
|
||||||
|
case Theme::Blue:
|
||||||
|
ui->setThemeBlue->setChecked(true);
|
||||||
|
break;
|
||||||
|
case Theme::Violet:
|
||||||
|
ui->setThemeViolet->setChecked(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QActionGroup>
|
||||||
|
#include <QDragEnterEvent>
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QMimeData>
|
||||||
|
#include "main_window_themes.h"
|
||||||
|
#include "main_window_ui.h"
|
||||||
|
|
||||||
|
class GuiSettings;
|
||||||
|
class GameListFrame;
|
||||||
|
|
||||||
|
class MainWindow : public QMainWindow {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
std::unique_ptr<Ui_MainWindow> ui;
|
||||||
|
|
||||||
|
bool m_is_list_mode = true;
|
||||||
|
bool m_save_slider_pos = false;
|
||||||
|
int m_other_slider_pos = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit MainWindow(std::shared_ptr<GuiSettings> gui_settings, QWidget* parent = nullptr);
|
||||||
|
~MainWindow();
|
||||||
|
bool Init();
|
||||||
|
void InstallPkg();
|
||||||
|
void InstallDragDropPkg(std::string file, int pkgNum, int nPkg);
|
||||||
|
void InstallDirectory();
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void ConfigureGuiFromSettings();
|
||||||
|
void SetIconSizeActions(int idx) const;
|
||||||
|
void ResizeIcons(int index);
|
||||||
|
void SaveWindowState() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void CreateActions();
|
||||||
|
void CreateDockWindows();
|
||||||
|
void CreateConnects();
|
||||||
|
void SetLastUsedTheme();
|
||||||
|
|
||||||
|
QActionGroup* m_icon_size_act_group = nullptr;
|
||||||
|
QActionGroup* m_list_mode_act_group = nullptr;
|
||||||
|
QActionGroup* m_theme_act_group = nullptr;
|
||||||
|
|
||||||
|
// Dockable widget frames
|
||||||
|
QMainWindow* m_main_window = nullptr;
|
||||||
|
GameListFrame* m_game_list_frame = nullptr;
|
||||||
|
WindowThemes m_window_themes;
|
||||||
|
|
||||||
|
std::shared_ptr<GuiSettings> m_gui_settings;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void dragEnterEvent(QDragEnterEvent* event1) override {
|
||||||
|
if (event1->mimeData()->hasUrls()) {
|
||||||
|
event1->acceptProposedAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dropEvent(QDropEvent* event1) override {
|
||||||
|
const QMimeData* mimeData = event1->mimeData();
|
||||||
|
if (mimeData->hasUrls()) {
|
||||||
|
QList<QUrl> urlList = mimeData->urls();
|
||||||
|
int pkgNum = 0;
|
||||||
|
int nPkg = urlList.size();
|
||||||
|
for (const QUrl& url : urlList) {
|
||||||
|
pkgNum++;
|
||||||
|
InstallDragDropPkg(url.toLocalFile().toStdString(), pkgNum, nPkg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,120 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "main_window_themes.h"
|
||||||
|
|
||||||
|
void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) {
|
||||||
|
QPalette themePalette;
|
||||||
|
|
||||||
|
switch (theme) {
|
||||||
|
case Theme::Light:
|
||||||
|
mw_searchbar->setStyleSheet("background-color: #ffffff; /* Light gray background */"
|
||||||
|
"color: #000000; /* Black text */"
|
||||||
|
"padding: 5px;");
|
||||||
|
themePalette.setColor(QPalette::Window, QColor(240, 240, 240)); // Light gray
|
||||||
|
themePalette.setColor(QPalette::WindowText, Qt::black); // Black
|
||||||
|
themePalette.setColor(QPalette::Base, QColor(230, 230, 230, 80)); // Grayish
|
||||||
|
themePalette.setColor(QPalette::ToolTipBase, Qt::black); // Black
|
||||||
|
themePalette.setColor(QPalette::ToolTipText, Qt::black); // Black
|
||||||
|
themePalette.setColor(QPalette::Text, Qt::black); // Black
|
||||||
|
themePalette.setColor(QPalette::Button, QColor(240, 240, 240)); // Light gray
|
||||||
|
themePalette.setColor(QPalette::ButtonText, Qt::black); // Black
|
||||||
|
themePalette.setColor(QPalette::BrightText, Qt::red); // Red
|
||||||
|
themePalette.setColor(QPalette::Link, QColor(42, 130, 218)); // Blue
|
||||||
|
themePalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); // Blue
|
||||||
|
themePalette.setColor(QPalette::HighlightedText, Qt::white); // White
|
||||||
|
qApp->setPalette(themePalette);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Theme::Dark:
|
||||||
|
mw_searchbar->setStyleSheet("background-color: #1e1e1e; /* Dark background */"
|
||||||
|
"color: #ffffff; /* White text */"
|
||||||
|
"border: 1px solid #ffffff; /* White border */"
|
||||||
|
"padding: 5px;");
|
||||||
|
themePalette.setColor(QPalette::Window, QColor(53, 53, 53));
|
||||||
|
themePalette.setColor(QPalette::WindowText, Qt::white);
|
||||||
|
themePalette.setColor(QPalette::Base, QColor(25, 25, 25));
|
||||||
|
themePalette.setColor(QPalette::AlternateBase, QColor(25, 25, 25));
|
||||||
|
themePalette.setColor(QPalette::AlternateBase, QColor(53, 53, 53));
|
||||||
|
themePalette.setColor(QPalette::ToolTipBase, Qt::white);
|
||||||
|
themePalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||||
|
themePalette.setColor(QPalette::Text, Qt::white);
|
||||||
|
themePalette.setColor(QPalette::Button, QColor(53, 53, 53));
|
||||||
|
themePalette.setColor(QPalette::ButtonText, Qt::white);
|
||||||
|
themePalette.setColor(QPalette::BrightText, Qt::red);
|
||||||
|
themePalette.setColor(QPalette::Link, QColor(42, 130, 218));
|
||||||
|
themePalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
|
||||||
|
themePalette.setColor(QPalette::HighlightedText, Qt::black);
|
||||||
|
qApp->setPalette(themePalette);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Theme::Green:
|
||||||
|
mw_searchbar->setStyleSheet("background-color: #354535; /* Dark green background */"
|
||||||
|
"color: #ffffff; /* White text */"
|
||||||
|
"border: 1px solid #ffffff; /* White border */"
|
||||||
|
"padding: 5px;");
|
||||||
|
themePalette.setColor(QPalette::Window, QColor(53, 69, 53)); // Dark green background
|
||||||
|
themePalette.setColor(QPalette::WindowText, Qt::white); // White text
|
||||||
|
themePalette.setColor(QPalette::Base, QColor(25, 40, 25)); // Darker green base
|
||||||
|
themePalette.setColor(QPalette::AlternateBase,
|
||||||
|
QColor(53, 69, 53)); // Dark green alternate base
|
||||||
|
themePalette.setColor(QPalette::ToolTipBase, Qt::white); // White tooltip background
|
||||||
|
themePalette.setColor(QPalette::ToolTipText, Qt::white); // White tooltip text
|
||||||
|
themePalette.setColor(QPalette::Text, Qt::white); // White text
|
||||||
|
themePalette.setColor(QPalette::Button, QColor(53, 69, 53)); // Dark green button
|
||||||
|
themePalette.setColor(QPalette::ButtonText, Qt::white); // White button text
|
||||||
|
themePalette.setColor(QPalette::BrightText, Qt::red); // Bright red text for alerts
|
||||||
|
themePalette.setColor(QPalette::Link, QColor(42, 130, 218)); // Light blue links
|
||||||
|
themePalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); // Light blue highlight
|
||||||
|
themePalette.setColor(QPalette::HighlightedText, Qt::black); // Black highlighted text
|
||||||
|
|
||||||
|
qApp->setPalette(themePalette);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Theme::Blue:
|
||||||
|
mw_searchbar->setStyleSheet("background-color: #283c5a; /* Dark blue background */"
|
||||||
|
"color: #ffffff; /* White text */"
|
||||||
|
"border: 1px solid #ffffff; /* White border */"
|
||||||
|
"padding: 5px;");
|
||||||
|
themePalette.setColor(QPalette::Window, QColor(40, 60, 90)); // Dark blue background
|
||||||
|
themePalette.setColor(QPalette::WindowText, Qt::white); // White text
|
||||||
|
themePalette.setColor(QPalette::Base, QColor(20, 40, 60)); // Darker blue base
|
||||||
|
themePalette.setColor(QPalette::AlternateBase,
|
||||||
|
QColor(40, 60, 90)); // Dark blue alternate base
|
||||||
|
themePalette.setColor(QPalette::ToolTipBase, Qt::white); // White tooltip background
|
||||||
|
themePalette.setColor(QPalette::ToolTipText, Qt::white); // White tooltip text
|
||||||
|
themePalette.setColor(QPalette::Text, Qt::white); // White text
|
||||||
|
themePalette.setColor(QPalette::Button, QColor(40, 60, 90)); // Dark blue button
|
||||||
|
themePalette.setColor(QPalette::ButtonText, Qt::white); // White button text
|
||||||
|
themePalette.setColor(QPalette::BrightText, Qt::red); // Bright red text for alerts
|
||||||
|
themePalette.setColor(QPalette::Link, QColor(42, 130, 218)); // Light blue links
|
||||||
|
themePalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); // Light blue highlight
|
||||||
|
themePalette.setColor(QPalette::HighlightedText, Qt::black); // Black highlighted text
|
||||||
|
|
||||||
|
qApp->setPalette(themePalette);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Theme::Violet:
|
||||||
|
mw_searchbar->setStyleSheet("background-color: #643278; /* Violet background */"
|
||||||
|
"color: #ffffff; /* White text */"
|
||||||
|
"border: 1px solid #ffffff; /* White border */"
|
||||||
|
"padding: 5px;");
|
||||||
|
themePalette.setColor(QPalette::Window, QColor(100, 50, 120)); // Violet background
|
||||||
|
themePalette.setColor(QPalette::WindowText, Qt::white); // White text
|
||||||
|
themePalette.setColor(QPalette::Base, QColor(80, 30, 90)); // Darker violet base
|
||||||
|
themePalette.setColor(QPalette::AlternateBase,
|
||||||
|
QColor(100, 50, 120)); // Violet alternate base
|
||||||
|
themePalette.setColor(QPalette::ToolTipBase, Qt::white); // White tooltip background
|
||||||
|
themePalette.setColor(QPalette::ToolTipText, Qt::white); // White tooltip text
|
||||||
|
themePalette.setColor(QPalette::Text, Qt::white); // White text
|
||||||
|
themePalette.setColor(QPalette::Button, QColor(100, 50, 120)); // Violet button
|
||||||
|
themePalette.setColor(QPalette::ButtonText, Qt::white); // White button text
|
||||||
|
themePalette.setColor(QPalette::BrightText, Qt::red); // Bright red text for alerts
|
||||||
|
themePalette.setColor(QPalette::Link, QColor(42, 130, 218)); // Light blue links
|
||||||
|
themePalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); // Light blue highlight
|
||||||
|
themePalette.setColor(QPalette::HighlightedText, Qt::black); // Black highlighted text
|
||||||
|
|
||||||
|
qApp->setPalette(themePalette);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
enum class Theme : int {
|
||||||
|
Light,
|
||||||
|
Dark,
|
||||||
|
Green,
|
||||||
|
Blue,
|
||||||
|
Violet,
|
||||||
|
};
|
||||||
|
|
||||||
|
class WindowThemes : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public Q_SLOTS:
|
||||||
|
void SetWindowTheme(Theme theme, QLineEdit* mw_searchbar);
|
||||||
|
};
|
|
@ -0,0 +1,279 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
/********************************************************************************
|
||||||
|
** Form generated from reading UI file 'main_window.ui'
|
||||||
|
**
|
||||||
|
** Created by: Qt User Interface Compiler version 6.6.1
|
||||||
|
**
|
||||||
|
** WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
#ifndef MAIN_WINDOW_UI_H
|
||||||
|
#define MAIN_WINDOW_UI_H
|
||||||
|
|
||||||
|
#include <QtCore/QVariant>
|
||||||
|
#include <QtGui/QAction>
|
||||||
|
#include <QtWidgets/QApplication>
|
||||||
|
#include <QtWidgets/QHBoxLayout>
|
||||||
|
#include <QtWidgets/QLineEdit>
|
||||||
|
#include <QtWidgets/QMainWindow>
|
||||||
|
#include <QtWidgets/QMenu>
|
||||||
|
#include <QtWidgets/QMenuBar>
|
||||||
|
#include <QtWidgets/QPushButton>
|
||||||
|
#include <QtWidgets/QSlider>
|
||||||
|
#include <QtWidgets/QToolBar>
|
||||||
|
#include <QtWidgets/QWidget>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
class Ui_MainWindow {
|
||||||
|
public:
|
||||||
|
QAction* bootInstallPkgAct;
|
||||||
|
QAction* exitAct;
|
||||||
|
QAction* showGameListAct;
|
||||||
|
QAction* refreshGameListAct;
|
||||||
|
QAction* setIconSizeTinyAct;
|
||||||
|
QAction* setIconSizeSmallAct;
|
||||||
|
QAction* setIconSizeMediumAct;
|
||||||
|
QAction* setIconSizeLargeAct;
|
||||||
|
QAction* setlistModeListAct;
|
||||||
|
QAction* setlistModeGridAct;
|
||||||
|
QAction* gameInstallPathAct;
|
||||||
|
QAction* setThemeLight;
|
||||||
|
QAction* setThemeDark;
|
||||||
|
QAction* setThemeGreen;
|
||||||
|
QAction* setThemeBlue;
|
||||||
|
QAction* setThemeViolet;
|
||||||
|
QWidget* centralWidget;
|
||||||
|
QLineEdit* mw_searchbar;
|
||||||
|
|
||||||
|
QWidget* sizeSliderContainer;
|
||||||
|
QHBoxLayout* sizeSliderContainer_layout;
|
||||||
|
QSlider* sizeSlider;
|
||||||
|
QMenuBar* menuBar;
|
||||||
|
QMenu* menuFile;
|
||||||
|
QMenu* menuView;
|
||||||
|
QMenu* menuGame_List_Icons;
|
||||||
|
QMenu* menuGame_List_Mode;
|
||||||
|
QMenu* menuSettings;
|
||||||
|
QMenu* menuThemes;
|
||||||
|
QToolBar* toolBar;
|
||||||
|
|
||||||
|
void setupUi(QMainWindow* MainWindow) {
|
||||||
|
if (MainWindow->objectName().isEmpty())
|
||||||
|
MainWindow->setObjectName("MainWindow");
|
||||||
|
MainWindow->resize(1058, 580);
|
||||||
|
QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
sizePolicy.setHorizontalStretch(0);
|
||||||
|
sizePolicy.setVerticalStretch(0);
|
||||||
|
sizePolicy.setHeightForWidth(MainWindow->sizePolicy().hasHeightForWidth());
|
||||||
|
MainWindow->setSizePolicy(sizePolicy);
|
||||||
|
MainWindow->setMinimumSize(QSize(4, 0));
|
||||||
|
MainWindow->setAutoFillBackground(false);
|
||||||
|
MainWindow->setAnimated(true);
|
||||||
|
MainWindow->setDockNestingEnabled(true);
|
||||||
|
MainWindow->setDockOptions(QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks |
|
||||||
|
QMainWindow::AnimatedDocks | QMainWindow::GroupedDragging);
|
||||||
|
bootInstallPkgAct = new QAction(MainWindow);
|
||||||
|
bootInstallPkgAct->setObjectName("bootInstallPkgAct");
|
||||||
|
exitAct = new QAction(MainWindow);
|
||||||
|
exitAct->setObjectName("exitAct");
|
||||||
|
showGameListAct = new QAction(MainWindow);
|
||||||
|
showGameListAct->setObjectName("showGameListAct");
|
||||||
|
showGameListAct->setCheckable(true);
|
||||||
|
refreshGameListAct = new QAction(MainWindow);
|
||||||
|
refreshGameListAct->setObjectName("refreshGameListAct");
|
||||||
|
setIconSizeTinyAct = new QAction(MainWindow);
|
||||||
|
setIconSizeTinyAct->setObjectName("setIconSizeTinyAct");
|
||||||
|
setIconSizeTinyAct->setCheckable(true);
|
||||||
|
setIconSizeSmallAct = new QAction(MainWindow);
|
||||||
|
setIconSizeSmallAct->setObjectName("setIconSizeSmallAct");
|
||||||
|
setIconSizeSmallAct->setCheckable(true);
|
||||||
|
setIconSizeSmallAct->setChecked(true);
|
||||||
|
setIconSizeMediumAct = new QAction(MainWindow);
|
||||||
|
setIconSizeMediumAct->setObjectName("setIconSizeMediumAct");
|
||||||
|
setIconSizeMediumAct->setCheckable(true);
|
||||||
|
setIconSizeLargeAct = new QAction(MainWindow);
|
||||||
|
setIconSizeLargeAct->setObjectName("setIconSizeLargeAct");
|
||||||
|
setIconSizeLargeAct->setCheckable(true);
|
||||||
|
setlistModeListAct = new QAction(MainWindow);
|
||||||
|
setlistModeListAct->setObjectName("setlistModeListAct");
|
||||||
|
setlistModeListAct->setCheckable(true);
|
||||||
|
setlistModeListAct->setChecked(true);
|
||||||
|
setlistModeGridAct = new QAction(MainWindow);
|
||||||
|
setlistModeGridAct->setObjectName("setlistModeGridAct");
|
||||||
|
setlistModeGridAct->setCheckable(true);
|
||||||
|
gameInstallPathAct = new QAction(MainWindow);
|
||||||
|
gameInstallPathAct->setObjectName("gameInstallPathAct");
|
||||||
|
setThemeLight = new QAction(MainWindow);
|
||||||
|
setThemeLight->setObjectName("setThemeLight");
|
||||||
|
setThemeLight->setCheckable(true);
|
||||||
|
setThemeLight->setChecked(true);
|
||||||
|
setThemeDark = new QAction(MainWindow);
|
||||||
|
setThemeDark->setObjectName("setThemeDark");
|
||||||
|
setThemeDark->setCheckable(true);
|
||||||
|
setThemeGreen = new QAction(MainWindow);
|
||||||
|
setThemeGreen->setObjectName("setThemeGreen");
|
||||||
|
setThemeGreen->setCheckable(true);
|
||||||
|
setThemeBlue = new QAction(MainWindow);
|
||||||
|
setThemeBlue->setObjectName("setThemeBlue");
|
||||||
|
setThemeBlue->setCheckable(true);
|
||||||
|
setThemeViolet = new QAction(MainWindow);
|
||||||
|
setThemeViolet->setObjectName("setThemeViolet");
|
||||||
|
setThemeViolet->setCheckable(true);
|
||||||
|
centralWidget = new QWidget(MainWindow);
|
||||||
|
centralWidget->setObjectName("centralWidget");
|
||||||
|
sizePolicy.setHeightForWidth(centralWidget->sizePolicy().hasHeightForWidth());
|
||||||
|
centralWidget->setSizePolicy(sizePolicy);
|
||||||
|
mw_searchbar = new QLineEdit(centralWidget);
|
||||||
|
mw_searchbar->setObjectName("mw_searchbar");
|
||||||
|
mw_searchbar->setGeometry(QRect(480, 10, 150, 31));
|
||||||
|
sizePolicy.setHeightForWidth(mw_searchbar->sizePolicy().hasHeightForWidth());
|
||||||
|
mw_searchbar->setSizePolicy(sizePolicy);
|
||||||
|
mw_searchbar->setMaximumWidth(250);
|
||||||
|
QFont font;
|
||||||
|
font.setPointSize(10);
|
||||||
|
font.setBold(false);
|
||||||
|
mw_searchbar->setFont(font);
|
||||||
|
mw_searchbar->setFocusPolicy(Qt::ClickFocus);
|
||||||
|
mw_searchbar->setFrame(false);
|
||||||
|
mw_searchbar->setClearButtonEnabled(false);
|
||||||
|
|
||||||
|
sizeSliderContainer = new QWidget(centralWidget);
|
||||||
|
sizeSliderContainer->setObjectName("sizeSliderContainer");
|
||||||
|
sizeSliderContainer->setGeometry(QRect(280, 10, 181, 31));
|
||||||
|
QSizePolicy sizePolicy1(QSizePolicy::Fixed, QSizePolicy::Expanding);
|
||||||
|
sizePolicy1.setHorizontalStretch(0);
|
||||||
|
sizePolicy1.setVerticalStretch(0);
|
||||||
|
sizePolicy1.setHeightForWidth(sizeSliderContainer->sizePolicy().hasHeightForWidth());
|
||||||
|
sizeSliderContainer->setSizePolicy(sizePolicy1);
|
||||||
|
sizeSliderContainer_layout = new QHBoxLayout(sizeSliderContainer);
|
||||||
|
sizeSliderContainer_layout->setSpacing(0);
|
||||||
|
sizeSliderContainer_layout->setContentsMargins(11, 11, 11, 11);
|
||||||
|
sizeSliderContainer_layout->setObjectName("sizeSliderContainer_layout");
|
||||||
|
sizeSliderContainer_layout->setContentsMargins(14, 0, 14, 0);
|
||||||
|
sizeSlider = new QSlider(sizeSliderContainer);
|
||||||
|
sizeSlider->setObjectName("sizeSlider");
|
||||||
|
QSizePolicy sizePolicy2(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||||
|
sizePolicy2.setHorizontalStretch(0);
|
||||||
|
sizePolicy2.setVerticalStretch(0);
|
||||||
|
sizePolicy2.setHeightForWidth(sizeSlider->sizePolicy().hasHeightForWidth());
|
||||||
|
sizeSlider->setSizePolicy(sizePolicy2);
|
||||||
|
sizeSlider->setFocusPolicy(Qt::ClickFocus);
|
||||||
|
sizeSlider->setAutoFillBackground(false);
|
||||||
|
sizeSlider->setOrientation(Qt::Horizontal);
|
||||||
|
sizeSlider->setTickPosition(QSlider::NoTicks);
|
||||||
|
|
||||||
|
sizeSliderContainer_layout->addWidget(sizeSlider);
|
||||||
|
|
||||||
|
MainWindow->setCentralWidget(centralWidget);
|
||||||
|
menuBar = new QMenuBar(MainWindow);
|
||||||
|
menuBar->setObjectName("menuBar");
|
||||||
|
menuBar->setGeometry(QRect(0, 0, 1058, 22));
|
||||||
|
menuBar->setContextMenuPolicy(Qt::PreventContextMenu);
|
||||||
|
menuFile = new QMenu(menuBar);
|
||||||
|
menuFile->setObjectName("menuFile");
|
||||||
|
menuView = new QMenu(menuBar);
|
||||||
|
menuView->setObjectName("menuView");
|
||||||
|
menuGame_List_Icons = new QMenu(menuView);
|
||||||
|
menuGame_List_Icons->setObjectName("menuGame_List_Icons");
|
||||||
|
menuGame_List_Mode = new QMenu(menuView);
|
||||||
|
menuGame_List_Mode->setObjectName("menuGame_List_Mode");
|
||||||
|
menuSettings = new QMenu(menuBar);
|
||||||
|
menuSettings->setObjectName("menuSettings");
|
||||||
|
menuThemes = new QMenu(menuView);
|
||||||
|
menuThemes->setObjectName("menuThemes");
|
||||||
|
MainWindow->setMenuBar(menuBar);
|
||||||
|
toolBar = new QToolBar(MainWindow);
|
||||||
|
toolBar->setObjectName("toolBar");
|
||||||
|
MainWindow->addToolBar(Qt::TopToolBarArea, toolBar);
|
||||||
|
|
||||||
|
menuBar->addAction(menuFile->menuAction());
|
||||||
|
menuBar->addAction(menuView->menuAction());
|
||||||
|
menuBar->addAction(menuSettings->menuAction());
|
||||||
|
menuFile->addAction(bootInstallPkgAct);
|
||||||
|
menuFile->addSeparator();
|
||||||
|
menuFile->addAction(exitAct);
|
||||||
|
menuView->addAction(showGameListAct);
|
||||||
|
menuView->addSeparator();
|
||||||
|
menuView->addAction(refreshGameListAct);
|
||||||
|
menuView->addAction(menuGame_List_Mode->menuAction());
|
||||||
|
menuView->addAction(menuGame_List_Icons->menuAction());
|
||||||
|
menuView->addAction(menuThemes->menuAction());
|
||||||
|
menuThemes->addAction(setThemeLight);
|
||||||
|
menuThemes->addAction(setThemeDark);
|
||||||
|
menuThemes->addAction(setThemeGreen);
|
||||||
|
menuThemes->addAction(setThemeBlue);
|
||||||
|
menuThemes->addAction(setThemeViolet);
|
||||||
|
menuGame_List_Icons->addAction(setIconSizeTinyAct);
|
||||||
|
menuGame_List_Icons->addAction(setIconSizeSmallAct);
|
||||||
|
menuGame_List_Icons->addAction(setIconSizeMediumAct);
|
||||||
|
menuGame_List_Icons->addAction(setIconSizeLargeAct);
|
||||||
|
menuGame_List_Mode->addAction(setlistModeListAct);
|
||||||
|
menuGame_List_Mode->addAction(setlistModeGridAct);
|
||||||
|
menuSettings->addAction(gameInstallPathAct);
|
||||||
|
|
||||||
|
retranslateUi(MainWindow);
|
||||||
|
|
||||||
|
QMetaObject::connectSlotsByName(MainWindow);
|
||||||
|
} // setupUi
|
||||||
|
|
||||||
|
void retranslateUi(QMainWindow* MainWindow) {
|
||||||
|
MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "Shadps4", nullptr));
|
||||||
|
bootInstallPkgAct->setText(
|
||||||
|
QCoreApplication::translate("MainWindow", "Install Packages (PKG)", nullptr));
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
bootInstallPkgAct->setToolTip(QCoreApplication::translate(
|
||||||
|
"MainWindow", "Install application from a .pkg file", nullptr));
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
exitAct->setText(QCoreApplication::translate("MainWindow", "Exit", nullptr));
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
exitAct->setToolTip(QCoreApplication::translate("MainWindow", "Exit Shadps4", nullptr));
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
#if QT_CONFIG(statustip)
|
||||||
|
exitAct->setStatusTip(
|
||||||
|
QCoreApplication::translate("MainWindow", "Exit the application.", nullptr));
|
||||||
|
#endif // QT_CONFIG(statustip)
|
||||||
|
showGameListAct->setText(
|
||||||
|
QCoreApplication::translate("MainWindow", "Show Game List", nullptr));
|
||||||
|
refreshGameListAct->setText(
|
||||||
|
QCoreApplication::translate("MainWindow", "Game List Refresh", nullptr));
|
||||||
|
setIconSizeTinyAct->setText(QCoreApplication::translate("MainWindow", "Tiny", nullptr));
|
||||||
|
setIconSizeSmallAct->setText(QCoreApplication::translate("MainWindow", "Small", nullptr));
|
||||||
|
setIconSizeMediumAct->setText(QCoreApplication::translate("MainWindow", "Medium", nullptr));
|
||||||
|
setIconSizeLargeAct->setText(QCoreApplication::translate("MainWindow", "Large", nullptr));
|
||||||
|
setlistModeListAct->setText(
|
||||||
|
QCoreApplication::translate("MainWindow", "List View", nullptr));
|
||||||
|
setlistModeGridAct->setText(
|
||||||
|
QCoreApplication::translate("MainWindow", "Grid View", nullptr));
|
||||||
|
gameInstallPathAct->setText(
|
||||||
|
QCoreApplication::translate("MainWindow", "Game Install Directory", nullptr));
|
||||||
|
mw_searchbar->setPlaceholderText(
|
||||||
|
QCoreApplication::translate("MainWindow", "Search...", nullptr));
|
||||||
|
// darkModeSwitch->setText(
|
||||||
|
// QCoreApplication::translate("MainWindow", "Game", nullptr));
|
||||||
|
menuFile->setTitle(QCoreApplication::translate("MainWindow", "File", nullptr));
|
||||||
|
menuView->setTitle(QCoreApplication::translate("MainWindow", "View", nullptr));
|
||||||
|
menuGame_List_Icons->setTitle(
|
||||||
|
QCoreApplication::translate("MainWindow", "Game List Icons", nullptr));
|
||||||
|
menuGame_List_Mode->setTitle(
|
||||||
|
QCoreApplication::translate("MainWindow", "Game List Mode", nullptr));
|
||||||
|
menuSettings->setTitle(QCoreApplication::translate("MainWindow", "Settings", nullptr));
|
||||||
|
menuThemes->setTitle(QCoreApplication::translate("MainWindow", "Themes", nullptr));
|
||||||
|
setThemeLight->setText(QCoreApplication::translate("MainWindow", "Light", nullptr));
|
||||||
|
setThemeDark->setText(QCoreApplication::translate("MainWindow", "Dark", nullptr));
|
||||||
|
setThemeGreen->setText(QCoreApplication::translate("MainWindow", "Green", nullptr));
|
||||||
|
setThemeBlue->setText(QCoreApplication::translate("MainWindow", "Blue", nullptr));
|
||||||
|
setThemeViolet->setText(QCoreApplication::translate("MainWindow", "Violet", nullptr));
|
||||||
|
toolBar->setWindowTitle(QCoreApplication::translate("MainWindow", "toolBar", nullptr));
|
||||||
|
} // retranslateUi
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class MainWindow : public Ui_MainWindow {};
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
#endif // MAIN_WINDOW_UI_H
|
|
@ -0,0 +1,20 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
|
||||||
|
namespace gui {
|
||||||
|
namespace utils {
|
||||||
|
template <typename T>
|
||||||
|
void stop_future_watcher(QFutureWatcher<T>& watcher, bool cancel) {
|
||||||
|
if (watcher.isStarted() || watcher.isRunning()) {
|
||||||
|
if (cancel) {
|
||||||
|
watcher.cancel();
|
||||||
|
}
|
||||||
|
watcher.waitForFinished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace utils
|
||||||
|
} // namespace gui
|
|
@ -0,0 +1,77 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
Settings::Settings(QObject* parent) : QObject(parent), m_settings_dir(ComputeSettingsDir()) {}
|
||||||
|
|
||||||
|
Settings::~Settings() {
|
||||||
|
if (m_settings) {
|
||||||
|
m_settings->sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Settings::GetSettingsDir() const {
|
||||||
|
return m_settings_dir.absolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Settings::ComputeSettingsDir() {
|
||||||
|
return ""; // TODO currently we configure same dir , make it configurable
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::RemoveValue(const QString& key, const QString& name) const {
|
||||||
|
if (m_settings) {
|
||||||
|
m_settings->beginGroup(key);
|
||||||
|
m_settings->remove(name);
|
||||||
|
m_settings->endGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::RemoveValue(const GuiSave& entry) const {
|
||||||
|
RemoveValue(entry.key, entry.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant Settings::GetValue(const QString& key, const QString& name, const QVariant& def) const {
|
||||||
|
return m_settings ? m_settings->value(key + "/" + name, def) : def;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant Settings::GetValue(const GuiSave& entry) const {
|
||||||
|
return GetValue(entry.key, entry.name, entry.def);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant Settings::List2Var(const q_pair_list& list) {
|
||||||
|
QByteArray ba;
|
||||||
|
QDataStream stream(&ba, QIODevice::WriteOnly);
|
||||||
|
stream << list;
|
||||||
|
return QVariant(ba);
|
||||||
|
}
|
||||||
|
|
||||||
|
q_pair_list Settings::Var2List(const QVariant& var) {
|
||||||
|
q_pair_list list;
|
||||||
|
QByteArray ba = var.toByteArray();
|
||||||
|
QDataStream stream(&ba, QIODevice::ReadOnly);
|
||||||
|
stream >> list;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::SetValue(const GuiSave& entry, const QVariant& value) const {
|
||||||
|
if (m_settings) {
|
||||||
|
m_settings->beginGroup(entry.key);
|
||||||
|
m_settings->setValue(entry.name, value);
|
||||||
|
m_settings->endGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::SetValue(const QString& key, const QVariant& value) const {
|
||||||
|
if (m_settings) {
|
||||||
|
m_settings->setValue(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::SetValue(const QString& key, const QString& name, const QVariant& value) const {
|
||||||
|
if (m_settings) {
|
||||||
|
m_settings->beginGroup(key);
|
||||||
|
m_settings->setValue(name, value);
|
||||||
|
m_settings->endGroup();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QSize>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
#include "gui_save.h"
|
||||||
|
|
||||||
|
typedef QPair<QString, QString> q_string_pair;
|
||||||
|
typedef QPair<QString, QSize> q_size_pair;
|
||||||
|
typedef QList<q_string_pair> q_pair_list;
|
||||||
|
typedef QList<q_size_pair> q_size_list;
|
||||||
|
|
||||||
|
// Parent Class for GUI settings
|
||||||
|
class Settings : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Settings(QObject* parent = nullptr);
|
||||||
|
~Settings();
|
||||||
|
|
||||||
|
QString GetSettingsDir() const;
|
||||||
|
|
||||||
|
QVariant GetValue(const QString& key, const QString& name, const QVariant& def) const;
|
||||||
|
QVariant GetValue(const GuiSave& entry) const;
|
||||||
|
static QVariant List2Var(const q_pair_list& list);
|
||||||
|
static q_pair_list Var2List(const QVariant& var);
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
/** Remove entry */
|
||||||
|
void RemoveValue(const QString& key, const QString& name) const;
|
||||||
|
void RemoveValue(const GuiSave& entry) const;
|
||||||
|
|
||||||
|
/** Write value to entry */
|
||||||
|
void SetValue(const GuiSave& entry, const QVariant& value) const;
|
||||||
|
void SetValue(const QString& key, const QVariant& value) const;
|
||||||
|
void SetValue(const QString& key, const QString& name, const QVariant& value) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static QString ComputeSettingsDir();
|
||||||
|
|
||||||
|
std::unique_ptr<QSettings> m_settings;
|
||||||
|
QDir m_settings_dir;
|
||||||
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
IDI_ICON1 ICON "images/shadps4.ico"
|
|
@ -12,8 +12,10 @@ add_subdirectory(fmt EXCLUDE_FROM_ALL)
|
||||||
# MagicEnum
|
# MagicEnum
|
||||||
add_subdirectory(magic_enum EXCLUDE_FROM_ALL)
|
add_subdirectory(magic_enum EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
|
if(NOT ENABLE_QT_GUI)
|
||||||
# SDL3
|
# SDL3
|
||||||
add_subdirectory(SDL EXCLUDE_FROM_ALL)
|
add_subdirectory(SDL EXCLUDE_FROM_ALL)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Toml11
|
# Toml11
|
||||||
add_subdirectory(toml11 EXCLUDE_FROM_ALL)
|
add_subdirectory(toml11 EXCLUDE_FROM_ALL)
|
||||||
|
|
Loading…
Reference in New Issue