commit b67075d71a8ed159b93567f83a7d876de76650ae Author: Muggle345 Date: Wed Oct 22 09:41:11 2025 +0800 First working version commit diff --git a/.ci/clang-format.sh b/.ci/clang-format.sh new file mode 100644 index 0000000..e239b72 --- /dev/null +++ b/.ci/clang-format.sh @@ -0,0 +1,37 @@ +!/bin/bash -ex + +if grep -nrI '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .ci* dist/*.desktop \ + dist/*.svg dist/*.xml; then + echo Trailing whitespace found, aborting + exit 1 +fi + +# Default clang-format points to default 3.5 version one +CLANG_FORMAT=clang-format-19 +$CLANG_FORMAT --version + +if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then + # Get list of every file modified in this pull request + files_to_lint="$(git diff --name-only --diff-filter=ACMRTUXB $COMMIT_RANGE | grep '^src/[^.]*[.]\(cpp\|h\)$' || true)" +else + # Check everything for branch pushes + files_to_lint="$(find src/ -name '*.cpp' -or -name '*.h')" +fi + +# Turn off tracing for this because it's too verbose +set +x + +for f in $files_to_lint; do + d=$(diff -u "$f" <($CLANG_FORMAT "$f") || true) + if ! [ -z "$d" ]; then + echo "!!! $f not compliant to coding style, here is the fix:" + echo "$d" + fail=1 + fi +done + +set -x + +if [ "$fail" = 1 ]; then + exit 1 +fi diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..d69044b --- /dev/null +++ b/.clang-format @@ -0,0 +1,259 @@ +# SPDX-FileCopyrightText: 2016 Emmanuel Gil Peyrot +# SPDX-License-Identifier: GPL-2.0-or-later + +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '^\<[^Q][^/.>]*\>' + Priority: -2 + - Regex: '^\<' + Priority: -1 + - Regex: '^\"' + Priority: 0 +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 150 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never +--- +Language: Java +# BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 100 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +IncludeCategories: + - Regex: '^\<[^Q][^/.>]*\>' + Priority: -2 + - Regex: '^\<' + Priority: -1 + - Regex: '^\"' + Priority: 0 +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 150 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Never +--- +Language: ObjC +# BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 100 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +IncludeCategories: + - Regex: '^\<[^Q][^/.>]*\>' + Priority: -2 + - Regex: '^\<' + Priority: -1 + - Regex: '^\"' + Priority: 0 +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 150 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Never +... diff --git a/.github/linux-appimage-qt.sh b/.github/linux-appimage-qt.sh new file mode 100644 index 0000000..994b519 --- /dev/null +++ b/.github/linux-appimage-qt.sh @@ -0,0 +1,25 @@ +if [[ -z $GITHUB_WORKSPACE ]]; then + GITHUB_WORKSPACE="${PWD%/*}" +fi + +export Qt6_DIR="/usr/lib/qt6" +export PATH="$Qt6_DIR/bin:$PATH" +export EXTRA_QT_PLUGINS="waylandcompositor" +export EXTRA_PLATFORM_PLUGINS="libqwayland-egl.so;libqwayland-generic.so" + +# 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"/PKGInstall.desktop -e "$GITHUB_WORKSPACE"/build/PKGInstall -i "$GITHUB_WORKSPACE"/PKGIcon.png --plugin qt +./linuxdeploy-x86_64.AppImage --appdir AppDir --output appimage +mv PKGInstall-x86_64.AppImage PKGInstall.AppImage diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..94aaba1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,266 @@ +name: Build and Prerelease + +on: + push: + branches: [ "master"] + pull_request: + branches: [ "master" ] + +env: + BUILD_TYPE: Release + +jobs: + get-info: + runs-on: ubuntu-24.04 + outputs: + date: ${{ steps.vars.outputs.date }} + shorthash: ${{ steps.vars.outputs.shorthash }} + fullhash: ${{ steps.vars.outputs.fullhash }} + steps: + - uses: actions/checkout@v4 + - name: Get date and git hash + id: vars + run: | + echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + echo "shorthash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + echo "fullhash=$(git rev-parse HEAD)" >> $GITHUB_ENV + echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + echo "shorthash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + echo "fullhash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + clang-format: + runs-on: ubuntu-24.04 + continue-on-error: true + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install + run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main' + sudo apt update + sudo apt install clang-format-19 + - name: Build + env: + COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} + run: bash ./.ci/clang-format.sh + + build-windows: + runs-on: windows-2025 + needs: get-info + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Qt + uses: jurplel/install-qt-action@v4 + with: + version: 6.9.2 + host: windows + target: desktop + arch: win64_msvc2022_64 + archives: qtbase qttools + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + - name: Deploy and Package + run: | + mkdir upload + mkdir upload/qtplugins + move build/Release/PKGInstall.exe upload + cp dist/qt.conf upload/qt.conf + windeployqt --plugindir upload/qtplugins --no-compiler-runtime --no-system-d3d-compiler --no-system-dxc-compiler --dir upload upload/PKGInstall.exe + Compress-Archive -Path upload/* -DestinationPath PKGPKGInstall-win64-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}.zip + - name: Upload Windows Qt artifact + uses: actions/upload-artifact@v4 + with: + name: PKGInstall-win64-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} + path: upload/ + + build-linux: + runs-on: ubuntu-24.04 + needs: get-info + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Add LLVM repository + run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main' + + - name: Install dependencies + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + version: 6.9.3 + arch: linux_gcc_64 + modules: "qtnetworkauth" + tools: 'tools_opensslv3_src' + setup-python: false + cache: true + + - name: Configure CMake + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold" -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=mold" + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc) + + - name: Package and Upload Linux(ubuntu64) artifact + run: | + ls -la ${{ github.workspace }}/build/PKGInstall + + - uses: actions/upload-artifact@v4 + with: + name: PKGInstall-ubuntu64-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} + path: ${{ github.workspace }}/build/PKGInstall + + build-linux-AppImage: + runs-on: ubuntu-24.04 + needs: get-info + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Add LLVM repository + run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main' + + - name: Install dependencies + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + version: 6.9.3 + arch: linux_gcc_64 + modules: "qtnetworkauth" + tools: 'tools_opensslv3_src' + setup-python: false + cache: true + - name: Configure CMake + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold" -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=mold" + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Build AppImage + run: | + wget --no-verbose "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" + wget --no-verbose "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" + chmod +x linuxdeploy*.AppImage + ./linuxdeploy-x86_64.AppImage --appdir AppDir -d ${{github.workspace}}/dist/PKGInstall.desktop -e ${{github.workspace}}/build/PKGInstall -i ${{github.workspace}}/dist/PKGIcon.png --plugin qt + ./linuxdeploy-x86_64.AppImage --appdir AppDir --output appimage + mv PKGInstall-x86_64.AppImage PKGInstall.AppImage + + - name: Package and Upload Linux AppImage artifact + uses: actions/upload-artifact@v4 + with: + name: PKGInstall-linux-AppImage-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} + path: PKGInstall.AppImage + + pre-release: + if: github.ref == 'refs/heads/master' && github.repository == 'Muggle345/PKGInstall' && github.event_name == 'push' + needs: [get-info, build-windows, build-linux, build-linux-AppImage] + runs-on: ubuntu-24.04 + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: ./artifacts + + - name: Compress individual directories (without parent directory) + run: | + cd ./artifacts + for dir in */; do + if [ -d "$dir" ]; then + dir_name=${dir%/} + echo "Creating zip for $dir_name" + (cd "$dir_name" && zip -r "../${dir_name}.zip" .) + fi + done + + - name: Get latest release information + id: get_latest_release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + api_url="https://api.github.com/repos/${{ github.repository }}" + latest_release_info=$(curl -H "Authorization: token $GITHUB_TOKEN" "$api_url/releases/latest") + echo "last_release_tag=$(echo "$latest_release_info" | jq -r '.tag_name')" >> $GITHUB_ENV + + - name: Create Pre-Release on GitHub + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + id: create_release + uses: ncipollo/release-action@v1 + with: + name: "PKGInstall-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}" + tag: "PKGInstall-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}" + draft: false + prerelease: true + body: "Prerelease build" + artifacts: ./artifacts/*.zip + + - name: Get current pre-release information + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + api_url="https://api.github.com/repos/${{ github.repository }}/releases" + + # Get all releases (sorted by date) + releases=$(curl -H "Authorization: token $GITHUB_TOKEN" "$api_url") + + # Capture the most recent pre-release (assuming the first one is the latest) + current_release=$(echo "$releases" | jq -c '.[] | select(.prerelease == true) | .published_at' | sort -r | head -n 1) + + # Remove extra quotes from captured date + current_release=$(echo $current_release | tr -d '"') + + # Export the current published_at to be available for the next step + echo "CURRENT_PUBLISHED_AT=$current_release" >> $GITHUB_ENV + + - name: Delete old pre-releases and tags + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + api_url="https://api.github.com/repos/${{ github.repository }}/releases" + + # Get current pre-releases + releases=$(curl -H "Authorization: token $GITHUB_TOKEN" "$api_url") + + # Remove extra quotes from captured date + CURRENT_PUBLISHED_AT=$(echo $CURRENT_PUBLISHED_AT | tr -d '"') + + # Convert CURRENT_PUBLISHED_AT para timestamp Unix + current_published_ts=$(date -d "$CURRENT_PUBLISHED_AT" +%s) + + # Identify pre-releases + echo "$releases" | jq -c '.[] | select(.prerelease == true)' | while read -r release; do + release_date=$(echo "$release" | jq -r '.published_at') + release_id=$(echo "$release" | jq -r '.id') + release_tag=$(echo "$release" | jq -r '.tag_name') + + # Remove extra quotes from captured date + release_date=$(echo $release_date | tr -d '"') + + # Convert release_date para timestamp Unix + release_date_ts=$(date -d "$release_date" +%s) + + # Compare timestamps and delete old pre-releases + if [[ "$release_date_ts" -lt "$current_published_ts" ]]; then + echo "Deleting old pre-release: $release_id from $release_date with tag: $release_tag" + # Delete the pre-release + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" "$api_url/$release_id" + # Delete the tag + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$release_tag" + else + echo "Skipping pre-release: $release_id (newer or same date)" + fi + done diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0474c57 --- /dev/null +++ b/.gitignore @@ -0,0 +1,76 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +/.qmake.cache +/.qmake.stash +/build* +/out* +/.vs* + +# qtcreator generated files +*.pro.user* +CMakeLists.txt.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c5758be --- /dev/null +++ b/.gitmodules @@ -0,0 +1,20 @@ +[submodule "externals/cryptopp"] + path = externals/cryptopp + url = https://github.com/Muggle345/cryptopp + shallow = true +[submodule "externals/cryptopp-cmake"] + path = externals/cryptopp-cmake + url = https://github.com/Muggle345/cryptopp-cmake + shallow = true +[submodule "externals/fmt"] + path = externals/fmt + url = https://github.com/shadps4-emu/ext-fmt + shallow = true +[submodule "externals/zlib-ng"] + path = externals/zlib-ng + url = https://github.com/shadps4-emu/ext-zlib-ng + shallow = true +[submodule "externals/toml11"] + path = externals/toml11 + url = https://github.com/ToruNiina/toml11 + shallow = true diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3da0c23 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,106 @@ +cmake_minimum_required(VERSION 3.24) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +project(PKGInstall LANGUAGES CXX) + +set(BASE_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}") + +# Next, match common architecture strings down to a known common value. +if (BASE_ARCHITECTURE MATCHES "(x86)|(X86)|(amd64)|(AMD64)") + set(ARCHITECTURE "x86_64") +elseif (BASE_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)") + set(ARCHITECTURE "arm64") +else() + message(FATAL_ERROR "Unsupported CPU architecture: ${BASE_ARCHITECTURE}") +endif() + +# Setup a custom clang-format target (if clang-format can be found) that will run +# against all the src files. This should be used before making a pull request. + +if (CLANG_FORMAT) + set(SRCS ${PROJECT_SOURCE_DIR) + set(CCOMMENT "Running clang format against all the .h and .cpp files in src/") + if (WIN32) + add_custom_target(clang-format + COMMAND powershell.exe -Command "Get-ChildItem '${SRCS}/*' -Include *.cpp,*.h,*.mm -Recurse | Foreach {&'${CLANG_FORMAT}' -i $_.fullname}" + COMMENT ${CCOMMENT}) + else() + add_custom_target(clang-format + COMMAND find ${SRCS} -iname *.h -o -iname *.cpp -o -iname *.mm | xargs ${CLANG_FORMAT} -i + COMMENT ${CCOMMENT}) + endif() + unset(SRCS) + unset(CCOMMENT) +endif() + +if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR NOT MSVC) + find_package(cryptopp 8.9.0 MODULE) +endif() + +find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent) +find_package(ZLIB 1.3 MODULE) +find_package(fmt 10.2.0 CONFIG) +find_package(toml11 4.2.0 CONFIG) + +add_subdirectory(externals) + +set(PROJECT_SOURCES + src/alignment.h + src/concepts.h + src/crypto.cpp + src/crypto.h + src/endian.h + src/enum.h + src/io_file.cpp + src/io_file.h + src/keys.h + src/loader.cpp + src/loader.h + src/nt_api.cpp + src/nt_api.h + src/pfs.h + src/pkg.cpp + src/pkg.h + src/pkg_type.cpp + src/pkg_type.h + src/psf.cpp + src/psf.h + src/types.h + main.cpp + mainWindow.cpp + mainWindow.h + mainWindow.ui +) + +qt_add_executable(PKGInstall + ${PROJECT_SOURCES} +) + +if (WIN32) + target_sources(PKGInstall PRIVATE dist/PKGInstall.rc) +endif() + +target_link_libraries(PKGInstall PRIVATE Qt6::Widgets Qt6::Concurrent) + +target_link_libraries(PKGInstall PRIVATE cryptopp::cryptopp ZLIB::ZLIB fmt::fmt toml11::toml11) + +set_target_properties(PKGInstall PROPERTIES + WIN32_EXECUTABLE TRUE +) + +target_include_directories(PKGInstall PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/dist) +if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + install(FILES "PKGInstall.desktop" DESTINATION "share/applications") + install(FILES "PKGIcon.png" DESTINATION "share/icons/hicolor/512x512/apps") +endif() + diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..56c4ce3 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,22 @@ +{ + "version": 3, + "configurePresets": [ + { + "hidden": true, + "name": "Qt", + "cacheVariables": { + "CMAKE_PREFIX_PATH": "$env{QTDIR}" + }, + "vendor": { + "qt-project.org/Qt": { + "checksum": "wVa86FgEkvdCTVp1/nxvrkaemJc=" + } + } + } + ], + "vendor": { + "qt-project.org/Presets": { + "checksum": "67SmY24ZeVbebyKD0fGfIzb/bGI=" + } + } +} \ No newline at end of file diff --git a/CMakeUserPresets.json b/CMakeUserPresets.json new file mode 100644 index 0000000..27f6e37 --- /dev/null +++ b/CMakeUserPresets.json @@ -0,0 +1,58 @@ +{ + "version": 3, + "configurePresets": [ + { + "name": "Qt-Debug", + "inherits": "Qt-Default", + "binaryDir": "${sourceDir}/out/build/debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_CXX_FLAGS": "-DQT_QML_DEBUG" + }, + "environment": { + "QML_DEBUG_ARGS": "-qmljsdebugger=file:{f7004220-2768-4d15-9d97-f594192a7a4b},block" + } + }, + { + "name": "Qt-Release", + "inherits": "Qt-Default", + "binaryDir": "${sourceDir}/out/build/release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "hidden": true, + "name": "Qt-Default", + "inherits": "6.9.2_msvc2022_64", + "vendor": { + "qt-project.org/Default": { + "checksum": "jhm0eIMTyiLmqGPHQUjKB7uUdQ4=" + } + } + }, + { + "hidden": true, + "name": "6.9.2_msvc2022_64", + "inherits": "Qt", + "environment": { + "QTDIR": "C:/Qt/6.9.2/msvc2022_64" + }, + "architecture": { + "strategy": "external", + "value": "x64" + }, + "generator": "Ninja", + "vendor": { + "qt-project.org/Version": { + "checksum": "1tC8/DYUin+OhCBg2HAFsCfIVkY=" + } + } + } + ], + "vendor": { + "qt-project.org/Presets": { + "checksum": "vcy4AgYtMP5oFPGo4uQQdc7ONco=" + } + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..57b0eac --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +linux dependencies for non-appimage (not sure if this others needed) + +#### Ubuntu / Debian + +sudo apt install build-essential qt6-base-dev qt6-tools-dev + +#### Fedora + +sudo dnf install qt6-qtbase-devel qt6-qtbase-private-devel qt6-qttools-devel + +#### Arch Linux + +sudo pacman -S base-devel qt6-base qt6-declarative qt6-multimedia qt6-tools diff --git a/dist/PKGIcon.png b/dist/PKGIcon.png new file mode 100644 index 0000000..45a1ddf Binary files /dev/null and b/dist/PKGIcon.png differ diff --git a/dist/PKGInstall.desktop b/dist/PKGInstall.desktop new file mode 100644 index 0000000..78d4251 --- /dev/null +++ b/dist/PKGInstall.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=PKGInstall +Exec=PKGInstall +Terminal=false +Type=Application +Icon=PKGIcon +Comment=PKG PKGInstall +Categories=Game; +StartupWMClass=PKGPKGInstall; diff --git a/dist/PKGInstall.ico b/dist/PKGInstall.ico new file mode 100644 index 0000000..99882a0 Binary files /dev/null and b/dist/PKGInstall.ico differ diff --git a/dist/PKGInstall.rc b/dist/PKGInstall.rc new file mode 100644 index 0000000..dc88cfa --- /dev/null +++ b/dist/PKGInstall.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON "PKGInstall.ico" \ No newline at end of file diff --git a/dist/qt.conf b/dist/qt.conf new file mode 100644 index 0000000..d1402c4 --- /dev/null +++ b/dist/qt.conf @@ -0,0 +1,2 @@ +[Paths] +plugins = "./qtplugins" \ No newline at end of file diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt new file mode 100644 index 0000000..3f29924 --- /dev/null +++ b/externals/CMakeLists.txt @@ -0,0 +1,58 @@ +set(BUILD_SHARED_LIBS OFF) +set(BUILD_TESTING OFF) +set_directory_properties(PROPERTIES + EXCLUDE_FROM_ALL ON + SYSTEM ON +) + +# CryptoPP +if (NOT TARGET cryptopp::cryptopp) + set(CRYPTOPP_INSTALL OFF) + set(CRYPTOPP_BUILD_TESTING OFF) + set(CRYPTOPP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp) + # cryptopp instruction set checks do not account for added compile options, + # so disable extensions in the library config to match our chosen target CPU. + set(CRYPTOPP_DISABLE_AESNI ON) + set(CRYPTOPP_DISABLE_AVX2 ON) + add_subdirectory(cryptopp-cmake) + file(COPY cryptopp DESTINATION cryptopp FILES_MATCHING PATTERN "*.h") + # remove externals/cryptopp from include directories because it contains a conflicting zlib.h file + set_target_properties(cryptopp PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/cryptopp") +endif() + +# zlib +if (NOT TARGET ZLIB::ZLIB) + set(ZLIB_ENABLE_TESTS OFF) + set(WITH_GTEST OFF) + set(WITH_NEW_STRATEGIES ON) + set(WITH_NATIVE_INSTRUCTIONS ON) + set(ZLIB_COMPAT ON CACHE BOOL "" FORCE) + include(FetchContent) + FetchContent_Declare( + ZLIB + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/zlib-ng" + OVERRIDE_FIND_PACKAGE + ) + FetchContent_MakeAvailable(ZLIB) + add_library(ZLIB::ZLIB ALIAS zlib) + # libpng expects this variable to exist after its find_package(ZLIB) + set(ZLIB_INCLUDE_DIRS "${FETCHCONTENT_BASE_DIR}/zlib-build") +endif() + +# fmtlib +if (NOT TARGET fmt::fmt) + add_subdirectory(fmt) +endif() + +# Toml11 +if (NOT TARGET toml11::toml11) + add_subdirectory(toml11) + + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + get_target_property(_toml11_compile_options toml11 INTERFACE_COMPILE_OPTIONS) + list(REMOVE_ITEM _toml11_compile_options "/Zc:preprocessor") + set_target_properties(toml11 PROPERTIES INTERFACE_COMPILE_OPTIONS ${_toml11_compile_options}) + endif() + endif() +endif() diff --git a/externals/cryptopp b/externals/cryptopp new file mode 160000 index 0000000..0d0ecd0 --- /dev/null +++ b/externals/cryptopp @@ -0,0 +1 @@ +Subproject commit 0d0ecd00da8a8bcd988a602215ef863cfec7273f diff --git a/externals/cryptopp-cmake b/externals/cryptopp-cmake new file mode 160000 index 0000000..866aceb --- /dev/null +++ b/externals/cryptopp-cmake @@ -0,0 +1 @@ +Subproject commit 866aceb8b13b6427a3c4541288ff412ad54f11ea diff --git a/externals/fmt b/externals/fmt new file mode 160000 index 0000000..656d14d --- /dev/null +++ b/externals/fmt @@ -0,0 +1 @@ +Subproject commit 656d14db8bea59bb9a6d9321530df1f14a9e2ab4 diff --git a/externals/toml11 b/externals/toml11 new file mode 160000 index 0000000..2a18a89 --- /dev/null +++ b/externals/toml11 @@ -0,0 +1 @@ +Subproject commit 2a18a89008d3daac6d8f9db03ddd582173032c7a diff --git a/externals/zlib-ng b/externals/zlib-ng new file mode 160000 index 0000000..fd0d263 --- /dev/null +++ b/externals/zlib-ng @@ -0,0 +1 @@ +Subproject commit fd0d263cedab1a136f40d65199987e3eaeecfcbd diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..2f9c6e8 --- /dev/null +++ b/main.cpp @@ -0,0 +1,10 @@ +#include "mainWindow.h" + +#include + +int main(int argc, char* argv[]) { + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/mainWindow.cpp b/mainWindow.cpp new file mode 100644 index 0000000..cce14e4 --- /dev/null +++ b/mainWindow.cpp @@ -0,0 +1,593 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "./ui_mainWindow.h" +#include "mainWindow.h" +#include "src/loader.h" + +#ifndef MAX_PATH +#ifdef _WIN32 +#include +#include +// This is the maximum number of UTF-16 code units permissible in Windows file paths +#define MAX_PATH 260 +#else +// This is the maximum number of UTF-8 code units permissible in all other OSes' file paths +#define MAX_PATH 1024 +#endif +#endif + +namespace toml { +template +std::filesystem::path find_fs_path_or(const basic_value& v, const K& ky, + std::filesystem::path opt) { + try { + auto str = find(v, ky); + if (str.empty()) { + return opt; + } + std::u8string u8str{(char8_t*)&str.front(), (char8_t*)&str.back() + 1}; + return std::filesystem::path{u8str}; + } catch (...) { + return opt; + } +} + +} // namespace toml + +namespace fmt { +template +struct UTF { + T data; + + explicit UTF(const std::u8string_view view) { + data = view.empty() ? T{} : T{(const char*)&view.front(), (const char*)&view.back() + 1}; + } + + explicit UTF(const std::u8string& str) : UTF(std::u8string_view{str}) {} +}; + +} // namespace fmt + +MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) { + ui->setupUi(this); + + this->setWindowTitle("PKGInstall"); + this->setFixedSize(this->width(), this->height()); + + GetSettingsFileLocation(); + LoadSettings(); + + connect(ui->browsePkgButton, &QPushButton::clicked, this, &MainWindow::pkgButtonClicked); + connect(ui->browseFolderButton, &QPushButton::clicked, this, &MainWindow::folderButtonClicked); + connect(ui->dlcFolderButton, &QPushButton::clicked, this, &MainWindow::dlcButtonClicked); + connect(ui->closeButton, &QPushButton::clicked, this, &MainWindow::close); + + connect(ui->extractButton, &QPushButton::clicked, this, + [this]() { InstallDragDropPkg(pkgPath); }); + + connect(ui->settingsButton, &QPushButton::clicked, this, [this]() { SaveSettings(); }); + + connect(ui->loadConfigButton, &QPushButton::clicked, this, + [this]() { LoadFoldersFromShadps4File(); }); + + connect(ui->setOutputButton, &QPushButton::clicked, this, [this]() { + if (!ui->folderComboBox->currentText().isEmpty()) { + ui->outputLineEdit->setText(ui->folderComboBox->currentText()); + outputPath = PathFromQString(ui->folderComboBox->currentText()); + } else { + QMessageBox::information( + this, "Error", + "Folder list is empty, load a shadPS4 config.toml file to get list."); + } + }); + + connect(ui->separateUpdateCheckBox, &QCheckBox::checkStateChanged, this, + [this](Qt::CheckState state) { useSeparateUpdate = state; }); +} + +void MainWindow::GetSettingsFileLocation() { +#if defined(__linux__) + const char* xdg_data_home = getenv("XDG_DATA_HOME"); + if (xdg_data_home != nullptr && strlen(xdg_data_home) > 0) { + settingsFile = std::filesystem::path(xdg_data_home) / "PKGInstall" / "settings.toml"; + } else { + settingsFile = std::filesystem::path(getenv("HOME")) / ".local" / "share" / "PKGInstall" / + "settings.toml"; + } +#elif _WIN32 + TCHAR appdata[MAX_PATH] = {0}; + SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, appdata); + settingsFile = std::filesystem::path(appdata) / "PKGInstall" / "settings.toml"; +#endif +} + +void MainWindow::LoadSettings() { + if (!std::filesystem::exists(settingsFile.parent_path())) { + std::filesystem::create_directories(settingsFile.parent_path()); + } + + if (!std::filesystem::exists(settingsFile)) + return; + + toml::value data; + try { + std::ifstream ifs; + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); + ifs.open(settingsFile, std::ios_base::binary); + data = toml::parse(ifs, std::string{fmt::UTF(settingsFile.filename().u8string()).data}); + } catch (std::exception& ex) { + QMessageBox::critical(NULL, "Filesystem error", ex.what()); + return; + } + + useSeparateUpdate = toml::find_or(data, "Settings", "UseSeparateUpdateFolder", true); + ui->separateUpdateCheckBox->setChecked(useSeparateUpdate); + + if (data.contains("Paths")) { + const toml::value& launcher = data.at("Paths"); + outputPath = toml::find_fs_path_or(launcher, "outputPath", {}); + dlcPath = toml::find_fs_path_or(launcher, "dlcPath", {}); + } + + QString outputPathString; + PathToQString(outputPathString, outputPath); + ui->outputLineEdit->setText(outputPathString); + + QString dlcPathString; + PathToQString(dlcPathString, dlcPath); + ui->dlcLineEdit->setText(dlcPathString); + + std::vector install_dirs; + if (data.contains("ShadPS4InstallFolders")) { + const toml::value& installFolders = data.at("ShadPS4InstallFolders"); + toml::array arr = toml::find_or(installFolders, "Folders", {}); + for (const auto& folder : arr) { + if (folder.is_string()) { + install_dirs.push_back(folder.as_string()); + } + } + } + + for (size_t i = 0; i < install_dirs.size(); i++) { + QString path_string; + PathToQString(path_string, install_dirs[i]); + ui->folderComboBox->addItem(path_string); + } +} + +void MainWindow::SaveSettings() { + toml::value data; + try { + std::ifstream ifs; + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); + ifs.open(settingsFile, std::ios_base::binary); + data = toml::parse(ifs, std::string{fmt::UTF(settingsFile.filename().u8string()).data}); + } catch (std::exception& ex) { + // if it doesn't exist, the save function will create the file + if (std::filesystem::exists(settingsFile)) { + QMessageBox::critical(NULL, "Filesystem error", ex.what()); + return; + } + } + + std::vector game_dirs; + for (int i = 0; i < ui->folderComboBox->count(); ++i) { + std::string itemText = ui->folderComboBox->itemText(i).toStdString(); + game_dirs.push_back(itemText); + } + + data["ShadPS4InstallFolders"]["Folders"] = game_dirs; + data["Paths"]["outputPath"] = std::string{fmt::UTF(outputPath.u8string()).data}; + data["Paths"]["dlcPath"] = std::string{fmt::UTF(dlcPath.u8string()).data}; + data["Settings"]["UseSeparateUpdateFolder"] = useSeparateUpdate; + + std::ofstream file(settingsFile, std::ios::binary); + file << data; + file.close(); +} + +void MainWindow::LoadFoldersFromShadps4File() { + + QString shadConfig = + QFileDialog::getOpenFileName(this, "Load shadPS4 config.toml file", QDir::currentPath(), + "shadPS4 config file (config.toml)"); + + if (shadConfig.isEmpty()) + return; + + std::filesystem::path shadConfigFile = PathFromQString(shadConfig); + + toml::value data; + try { + std::ifstream ifs; + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); + ifs.open(shadConfigFile, std::ios_base::binary); + data = toml::parse(ifs, std::string{fmt::UTF(shadConfigFile.filename().u8string()).data}); + } catch (std::exception& ex) { + QMessageBox::critical(NULL, "Filesystem error", ex.what()); + return; + } + + std::vector install_dirs; + if (data.contains("GUI")) { + const toml::value& installFolders = data.at("GUI"); + toml::array arr = toml::find_or(installFolders, "installDirs", {}); + + if (!arr.empty()) { + ui->folderComboBox->clear(); + } else { + QMessageBox::information(this, "PKGInstall", + "No game install folders found in this file."); + } + + for (const auto& folder : arr) { + if (folder.is_string()) { + install_dirs.push_back(folder.as_string()); + } + } + + dlcPath = toml::find_fs_path_or(installFolders, "addonInstallDir", dlcPath); + QString path; + PathToQString(path, dlcPath); + ui->dlcLineEdit->setText(path); + } + + for (size_t i = 0; i < install_dirs.size(); i++) { + QString path_string; + PathToQString(path_string, install_dirs[i]); + ui->folderComboBox->addItem(path_string); + } +} + +void MainWindow::folderButtonClicked() { + QString folder = QFileDialog::getExistingDirectory(nullptr, + "Set Output folder", + QDir::homePath()); + ui->outputLineEdit->setText(folder); + outputPath = PathFromQString(folder); +} + +void MainWindow::dlcButtonClicked() { + QString folder = QFileDialog::getExistingDirectory(nullptr, "Set DLC folder", QDir::homePath()); + + ui->dlcLineEdit->setText(folder); + dlcPath = PathFromQString(folder); +} + +void MainWindow::pkgButtonClicked() { + QString file = QFileDialog::getOpenFileName(nullptr, + "Set Output folder", + QDir::homePath(), + "PKGs (*.pkg)"); + + pkgPath = PathFromQString(file); + ui->pkgLineEdit->setText(file); +} + +void MainWindow::InstallDragDropPkg(std::filesystem::path file) { + if (!std::filesystem::exists(pkgPath) || !std::filesystem::exists(outputPath)) { + QMessageBox::information(this, "Error", "Existing PKG file and output folder must be set"); + return; + } + + if (Loader::DetectFileType(file) == Loader::FileTypes::Pkg) { + std::string failreason; + pkg = PKG(); + if (!pkg.Open(file, failreason)) { + QMessageBox::critical(nullptr, tr("PKG ERROR"), QString::fromStdString(failreason)); + return; + } + if (!psf.Open(pkg.sfo)) { + QMessageBox::critical(nullptr, + tr("PKG ERROR"), + "Could not read SFO. Check log for details"); + return; + } + auto category = psf.GetString("CATEGORY"); + + std::filesystem::path game_install_dir = outputPath; + QString pkgType = QString::fromStdString(pkg.GetPkgFlags()); + bool use_game_update = pkgType.contains("PATCH") && useSeparateUpdate; + + // Default paths + auto game_folder_path = game_install_dir / pkg.GetTitleID(); + auto game_update_path = use_game_update ? game_folder_path.parent_path() + / (std::string{pkg.GetTitleID()} + "-patch") + : game_folder_path; + const int max_depth = 5; + + if (pkgType.contains("PATCH")) { + // For patches, try to find the game recursively + auto found_game = FindGameByID(game_install_dir, + std::string{pkg.GetTitleID()}, + max_depth); + if (found_game.has_value()) { + game_folder_path = found_game.value().parent_path(); + game_update_path = use_game_update + ? game_folder_path.parent_path() + / (std::string{pkg.GetTitleID()} + "-patch") + : game_folder_path; + } + } else { + // For base games, we check if the game is already installed + auto found_game = FindGameByID(game_install_dir, + std::string{pkg.GetTitleID()}, + max_depth); + if (found_game.has_value()) { + game_folder_path = found_game.value().parent_path(); + } + // If the game is not found, we install it in the game install directory + else { + game_folder_path = game_install_dir / pkg.GetTitleID(); + } + game_update_path = use_game_update ? game_folder_path.parent_path() + / (std::string{pkg.GetTitleID()} + "-patch") + : game_folder_path; + } + + QString gameDirPath; + PathToQString(gameDirPath, game_folder_path); + QDir game_dir(gameDirPath); + if (game_dir.exists()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("PKG Installation")); + + std::string content_id; + if (auto value = psf.GetString("CONTENT_ID"); value.has_value()) { + content_id = std::string{*value}; + } else { + QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no CONTENT_ID"); + return; + } + std::string entitlement_label = SplitString(content_id, '-')[2]; + + auto addon_extract_path = dlcPath; + QString addonDirPath; + PathToQString(addonDirPath, addon_extract_path); + QDir addon_dir(addonDirPath); + + if (pkgType.contains("PATCH")) { + QString pkg_app_version; + if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) { + pkg_app_version = QString::fromStdString(std::string{*app_ver}); + } else { + QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no APP_VER"); + return; + } + std::filesystem::path sce_folder_path + = std::filesystem::exists(game_update_path / "sce_sys" / "param.sfo") + ? game_update_path / "sce_sys" / "param.sfo" + : game_folder_path / "sce_sys" / "param.sfo"; + psf.Open(sce_folder_path); + QString game_app_version; + if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) { + game_app_version = QString::fromStdString(std::string{*app_ver}); + } else { + QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no APP_VER"); + return; + } + double appD = game_app_version.toDouble(); + double pkgD = pkg_app_version.toDouble(); + if (pkgD == appD) { + msgBox.setText(QString(tr("Patch detected!") + "\n" + + tr("PKG and Game versions match: ") + pkg_app_version + + "\n" + tr("Would you like to overwrite?"))); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + } else if (pkgD < appD) { + msgBox.setText(QString( + tr("Patch detected!") + "\n" + + tr("PKG Version %1 is older than existing version: ").arg(pkg_app_version) + + game_app_version + "\n" + tr("Would you like to overwrite?"))); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + } else { + msgBox.setText(QString( + tr("Patch detected!") + "\n" + tr("Game exists: ") + game_app_version + "\n" + + tr("Would you like to apply Patch: ") + pkg_app_version + " ?")); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + } + int result = msgBox.exec(); + if (result == QMessageBox::Yes) { + // Do nothing. + } else { + return; + } + } else if (category == "ac") { + if (!addon_dir.exists()) { + QMessageBox addonMsgBox; + addonMsgBox.setWindowTitle(tr("DLC Install")); + addonMsgBox.setText(QString(tr("Would you like to install DLC: %1?")) + .arg(QString::fromStdString(entitlement_label))); + + addonMsgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + addonMsgBox.setDefaultButton(QMessageBox::No); + int result = addonMsgBox.exec(); + if (result == QMessageBox::Yes) { + game_update_path = addon_extract_path; + } else { + return; + } + } else { + msgBox.setText(QString(tr("DLC already installed:") + "\n" + addonDirPath + + "\n\n" + tr("Would you like to overwrite?"))); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + int result = msgBox.exec(); + if (result == QMessageBox::Yes) { + game_update_path = addon_extract_path; + } else { + return; + } + } + } else { + msgBox.setText(QString(tr("Game already installed") + "\n" + gameDirPath + "\n" + + tr("Would you like to overwrite?"))); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + int result = msgBox.exec(); + if (result == QMessageBox::Yes) { + // Do nothing. + } else { + return; + } + } + } else { + // Do nothing; + if (pkgType.contains("PATCH") || category == "ac") { + QMessageBox::information( + this, + tr("PKG Installation"), + tr("PKG is a patch or DLC, please install base game first!")); + return; + } + // what else? + } + + if (!pkg.Extract(file, game_update_path, failreason)) { + QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason)); + } else { + int nfiles = pkg.GetNumberOfFiles(); + + if (nfiles > 0) { + QVector indices; + for (int i = 0; i < nfiles; i++) { + indices.append(i); + } + + QProgressDialog dialog; + dialog.setWindowTitle(tr("PKG Installation")); + dialog.setWindowModality(Qt::WindowModal); + QString extractmsg = QString(tr("Installing PKG")); + dialog.setLabelText(extractmsg); + dialog.setAutoClose(true); + dialog.setRange(0, nfiles); + + bool isSystemDarkMode; +#if defined(__linux__) + const QPalette defaultPalette; + const auto text = defaultPalette.color(QPalette::WindowText); + const auto window = defaultPalette.color(QPalette::Window); + if (text.lightness() > window.lightness()) { + isSystemDarkMode = true; + } else { + isSystemDarkMode = false; + } +#else + if (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark) { + isSystemDarkMode = true; + } else { + isSystemDarkMode = false; + } +#endif + + if (isSystemDarkMode) { + dialog.setStyleSheet( + "QProgressBar::chunk { background-color: #0000A3; border-radius: 5px; }" + "QProgressBar { border: 2px solid grey; border-radius: 5px; text-align: " + "center; }"); + + } else { + dialog.setStyleSheet( + "QProgressBar::chunk { background-color: #aaaaaa; border-radius: 5px; }" + "QProgressBar { border: 2px solid grey; border-radius: 5px; text-align: " + "center; }"); + } + + QFutureWatcher futureWatcher; + connect(&futureWatcher, &QFutureWatcher::finished, this, [=, this]() { + QString path; + + // We want to show the parent path instead of the full path + PathToQString(path, game_folder_path.parent_path()); + QIcon windowIcon( + PathToUTF8String(game_folder_path / "sce_sys/icon0.png").c_str()); + + QMessageBox extractMsgBox(this); + extractMsgBox.setWindowTitle(tr("Installation Finished")); + if (!windowIcon.isNull()) { + extractMsgBox.setWindowIcon(windowIcon); + } + extractMsgBox.setText( + QString(tr("Game successfully installed at %1")).arg(path)); + extractMsgBox.addButton(QMessageBox::Ok); + extractMsgBox.setDefaultButton(QMessageBox::Ok); + connect(&extractMsgBox, + &QMessageBox::buttonClicked, + this, + [&](QAbstractButton *button) { + if (extractMsgBox.button(QMessageBox::Ok) == button) { + extractMsgBox.close(); + // emit ExtractionFinished(); + } + }); + extractMsgBox.exec(); + + //if (delete_file_on_install) { + // std::filesystem::remove(file); + //} + }); + + connect(&dialog, &QProgressDialog::canceled, [&]() { futureWatcher.cancel(); }); + + connect(&futureWatcher, + &QFutureWatcher::progressValueChanged, + &dialog, + &QProgressDialog::setValue); + + futureWatcher.setFuture( + QtConcurrent::map(indices, [&](int index) { pkg.ExtractFiles(index); })); + + dialog.exec(); + } + } + } else { + QMessageBox::critical(this, + tr("PKG ERROR"), + tr("File doesn't appear to be a valid PKG file")); + } +} + +std::optional MainWindow::FindGameByID(const std::filesystem::path &dir, + const std::string &game_id, + int max_depth) +{ + if (max_depth < 0) { + return std::nullopt; + } + + // Check if this is the game we're looking for + if (dir.filename() == game_id && std::filesystem::exists(dir / "sce_sys" / "param.sfo")) { + auto eboot_path = dir / "eboot.bin"; + if (std::filesystem::exists(eboot_path)) { + return eboot_path; + } + } + + // Recursively search subdirectories + std::error_code ec; + for (const auto &entry : std::filesystem::directory_iterator(dir, ec)) { + if (!entry.is_directory()) { + continue; + } + if (auto found = FindGameByID(entry.path(), game_id, max_depth - 1)) { + return found; + } + } + + return std::nullopt; +} + +MainWindow::~MainWindow() +{ + delete ui; +} diff --git a/mainWindow.h b/mainWindow.h new file mode 100644 index 0000000..f368c72 --- /dev/null +++ b/mainWindow.h @@ -0,0 +1,77 @@ +#include +#include + +#include "src/pkg.h" +#include "src/psf.h" + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow { + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + void pkgButtonClicked(); + void folderButtonClicked(); + void dlcButtonClicked(); + +private: + std::optional FindGameByID(const std::filesystem::path &dir, + const std::string &game_id, + int max_depth); + void InstallDragDropPkg(std::filesystem::path file); + void LoadSettings(); + void SaveSettings(); + void LoadFoldersFromShadps4File(); + void GetSettingsFileLocation(); + + Ui::MainWindow* ui; + + bool useSeparateUpdate = true; + std::filesystem::path outputPath = ""; + std::filesystem::path dlcPath = ""; + std::filesystem::path pkgPath = ""; + std::filesystem::path tomlPath = ""; + std::filesystem::path settingsFile; + PKG pkg; + PSF psf; + + // static funcs + std::filesystem::path PathFromQString(const QString& path) { +#ifdef _WIN32 + return std::filesystem::path(path.toStdWString()); +#else + return std::filesystem::path(path.toStdString()); +#endif + } + + void PathToQString(QString& result, const std::filesystem::path& path) { +#ifdef _WIN32 + result = QString::fromStdWString(path.wstring()); +#else + result = QString::fromStdString(path.string()); +#endif + } + + std::vector SplitString(const std::string& str, char delimiter) { + std::istringstream iss(str); + std::vector output(1); + + while (std::getline(iss, *output.rbegin(), delimiter)) { + output.emplace_back(); + } + + output.pop_back(); + return output; + } + + std::string PathToUTF8String(const std::filesystem::path& path) { + const auto u8_string = path.u8string(); + return std::string{u8_string.begin(), u8_string.end()}; + } +}; diff --git a/mainWindow.ui b/mainWindow.ui new file mode 100644 index 0000000..3cad98e --- /dev/null +++ b/mainWindow.ui @@ -0,0 +1,229 @@ + + + MainWindow + + + + 0 + 0 + 752 + 279 + + + + MainWindow + + + + + + 10 + 230 + 80 + 24 + + + + Close + + + + + + 20 + 20 + 711 + 92 + + + + + + + + + PKG File: + + + + + + + + + + + 0 + 0 + + + + Browse + + + + + + + + + + + Output Folder: + + + + + + + + + + + 0 + 0 + + + + Browse + + + + + + + + + + + DLC Folder (optional) : + + + + + + + + + + + 0 + 0 + + + + Browse + + + + + + + + + + + 280 + 230 + 221 + 24 + + + + + 12 + true + + + + Extract + + + + + + 100 + 230 + 101 + 24 + + + + Save Settings + + + + + + 20 + 190 + 171 + 21 + + + + Use separate update folder + + + + + + 20 + 120 + 711 + 61 + + + + + + + + + + 0 + 0 + + + + shadPS4 Install Folders: + + + + + + + + + + + + + + Set the above folder as Output Folder + + + + + + + + 0 + 0 + + + + Load shadPS4 install folders from config file + + + + + + + + + + + + + diff --git a/src/alignment.h b/src/alignment.h new file mode 100644 index 0000000..3fb961c --- /dev/null +++ b/src/alignment.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2014 Jannik Vogel +// SPDX-License-Identifier: CC0-1.0 + +#pragma once + +#include +#include + +namespace Common { + +template +[[nodiscard]] constexpr T AlignUp(T value, std::size_t size) { + static_assert(std::is_unsigned_v, "T must be an unsigned value."); + auto mod{static_cast(value % size)}; + value -= mod; + return static_cast(mod == T{0} ? value : value + size); +} + +template +[[nodiscard]] constexpr T AlignDown(T value, std::size_t size) { + static_assert(std::is_unsigned_v, "T must be an unsigned value."); + return static_cast(value - value % size); +} + +template + requires std::is_integral_v +[[nodiscard]] constexpr bool IsAligned(T value, std::size_t alignment) { + return (value & (alignment - 1)) == 0; +} + +template + requires std::is_integral_v +[[nodiscard]] constexpr bool Is16KBAligned(T value) { + return (value & 0x3FFF) == 0; +} + +template + requires std::is_integral_v +[[nodiscard]] constexpr bool Is64KBAligned(T value) { + return (value & 0xFFFF) == 0; +} + +template + requires std::is_integral_v +[[nodiscard]] constexpr bool Is2MBAligned(T value) { + return (value & 0x1FFFFF) == 0; +} + +} // namespace Common diff --git a/src/concepts.h b/src/concepts.h new file mode 100644 index 0000000..a61add7 --- /dev/null +++ b/src/concepts.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +namespace Common { + +// Check if type satisfies the ContiguousContainer named requirement. +template +concept IsContiguousContainer = std::contiguous_iterator; + +template +concept DerivedFrom = std::derived_from; + +// TODO: Replace with std::convertible_to when libc++ implements it. +template +concept ConvertibleTo = std::is_convertible_v; + +// No equivalents in the stdlib + +template +concept IsArithmetic = std::is_arithmetic_v; + +template +concept IsIntegral = std::is_integral_v; + +} // namespace Common diff --git a/src/crypto.cpp b/src/crypto.cpp new file mode 100644 index 0000000..4020edf --- /dev/null +++ b/src/crypto.cpp @@ -0,0 +1,215 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "crypto.h" + +CryptoPP::RSA::PrivateKey Crypto::key_pkg_derived_key3_keyset_init() { + CryptoPP::InvertibleRSAFunction params; + params.SetPrime1(CryptoPP::Integer(PkgDerivedKey3Keyset::Prime1, 0x80)); + params.SetPrime2(CryptoPP::Integer(PkgDerivedKey3Keyset::Prime2, 0x80)); + + params.SetPublicExponent(CryptoPP::Integer(PkgDerivedKey3Keyset::PublicExponent, 4)); + params.SetPrivateExponent(CryptoPP::Integer(PkgDerivedKey3Keyset::PrivateExponent, 0x100)); + + params.SetModPrime1PrivateExponent(CryptoPP::Integer(PkgDerivedKey3Keyset::Exponent1, 0x80)); + params.SetModPrime2PrivateExponent(CryptoPP::Integer(PkgDerivedKey3Keyset::Exponent2, 0x80)); + + params.SetModulus(CryptoPP::Integer(PkgDerivedKey3Keyset::Modulus, 0x100)); + params.SetMultiplicativeInverseOfPrime2ModPrime1( + CryptoPP::Integer(PkgDerivedKey3Keyset::Coefficient, 0x80)); + + CryptoPP::RSA::PrivateKey privateKey(params); + + return privateKey; +} + +CryptoPP::RSA::PrivateKey Crypto::FakeKeyset_keyset_init() { + CryptoPP::InvertibleRSAFunction params; + params.SetPrime1(CryptoPP::Integer(FakeKeyset::Prime1, 0x80)); + params.SetPrime2(CryptoPP::Integer(FakeKeyset::Prime2, 0x80)); + + params.SetPublicExponent(CryptoPP::Integer(FakeKeyset::PublicExponent, 4)); + params.SetPrivateExponent(CryptoPP::Integer(FakeKeyset::PrivateExponent, 0x100)); + + params.SetModPrime1PrivateExponent(CryptoPP::Integer(FakeKeyset::Exponent1, 0x80)); + params.SetModPrime2PrivateExponent(CryptoPP::Integer(FakeKeyset::Exponent2, 0x80)); + + params.SetModulus(CryptoPP::Integer(FakeKeyset::Modulus, 0x100)); + params.SetMultiplicativeInverseOfPrime2ModPrime1( + CryptoPP::Integer(FakeKeyset::Coefficient, 0x80)); + + CryptoPP::RSA::PrivateKey privateKey(params); + + return privateKey; +} + +CryptoPP::RSA::PrivateKey Crypto::DebugRifKeyset_init() { + CryptoPP::InvertibleRSAFunction params; + params.SetPrime1(CryptoPP::Integer(DebugRifKeyset::Prime1, sizeof(DebugRifKeyset::Prime1))); + params.SetPrime2(CryptoPP::Integer(DebugRifKeyset::Prime2, sizeof(DebugRifKeyset::Prime2))); + + params.SetPublicExponent( + CryptoPP::Integer(DebugRifKeyset::PublicExponent, sizeof(DebugRifKeyset::PublicExponent))); + params.SetPrivateExponent(CryptoPP::Integer(DebugRifKeyset::PrivateExponent, + sizeof(DebugRifKeyset::PrivateExponent))); + + params.SetModPrime1PrivateExponent( + CryptoPP::Integer(DebugRifKeyset::Exponent1, sizeof(DebugRifKeyset::Exponent1))); + params.SetModPrime2PrivateExponent( + CryptoPP::Integer(DebugRifKeyset::Exponent2, sizeof(DebugRifKeyset::Exponent2))); + + params.SetModulus(CryptoPP::Integer(DebugRifKeyset::Modulus, sizeof(DebugRifKeyset::Modulus))); + params.SetMultiplicativeInverseOfPrime2ModPrime1( + CryptoPP::Integer(DebugRifKeyset::Coefficient, sizeof(DebugRifKeyset::Coefficient))); + + CryptoPP::RSA::PrivateKey privateKey(params); + + return privateKey; +} + +void Crypto::RSA2048Decrypt(std::span dec_key, + std::span ciphertext, + bool is_dk3) { // RSAES_PKCS1v15_ + // Create an RSA decryptor + CryptoPP::RSA::PrivateKey privateKey; + if (is_dk3) { + privateKey = key_pkg_derived_key3_keyset_init(); + } else { + privateKey = FakeKeyset_keyset_init(); + } + + CryptoPP::RSAES_PKCS1v15_Decryptor rsaDecryptor(privateKey); + + // Allocate memory for the decrypted data + std::array decrypted; + + // Perform the decryption + CryptoPP::AutoSeededRandomPool rng; + CryptoPP::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 cipher_input, + std::span ivkey_result) { + CryptoPP::SHA256 sha256; + std::array 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 ivkey, + std::span ciphertext, + std::span decrypted) { + std::array key; + std::array 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::aesCbcCfb128DecryptEntry(std::span ivkey, + std::span ciphertext, + std::span decrypted) { + std::array key; + std::array 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::decryptEFSM(std::span trophyKey, + std::span NPcommID, + std::span efsmIv, std::span ciphertext, + std::span decrypted) { + + // step 1: Encrypt NPcommID + CryptoPP::CBC_Mode::Encryption encrypt; + + std::vector trophyIv(16, 0); + std::vector trpKey(16); + + encrypt.SetKeyWithIV(trophyKey.data(), trophyKey.size(), trophyIv.data()); + encrypt.ProcessData(trpKey.data(), NPcommID.data(), 16); + + // step 2: decrypt efsm. + CryptoPP::CBC_Mode::Decryption decrypt; + decrypt.SetKeyWithIV(trpKey.data(), trpKey.size(), efsmIv.data()); + + for (size_t i = 0; i < decrypted.size(); i += CryptoPP::AES::BLOCKSIZE) { + decrypt.ProcessData(decrypted.data() + i, ciphertext.data() + i, CryptoPP::AES::BLOCKSIZE); + } +} + +void Crypto::PfsGenCryptoKey(std::span ekpfs, + std::span seed, + std::span dataKey, + std::span tweakKey) { + CryptoPP::HMAC 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 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 dataKey, + std::span tweakKey, std::span src_image, + std::span 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::Encryption encrypt(tweakKey.data(), tweakKey.size()); + CryptoPP::ECB_Mode::Decryption decrypt(dataKey.data(), dataKey.size()); + + std::array tweak{}; + std::array encryptedTweak; + std::array 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); + } + } +} diff --git a/src/crypto.h b/src/crypto.h new file mode 100644 index 0000000..b088f8b --- /dev/null +++ b/src/crypto.h @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "keys.h" +#include "types.h" + +class Crypto { +public: + CryptoPP::RSA::PrivateKey key_pkg_derived_key3_keyset_init(); + CryptoPP::RSA::PrivateKey FakeKeyset_keyset_init(); + CryptoPP::RSA::PrivateKey DebugRifKeyset_init(); + + void RSA2048Decrypt(std::span dk3, + std::span ciphertext, + bool is_dk3); // RSAES_PKCS1v15_ + void ivKeyHASH256(std::span cipher_input, + std::span ivkey_result); + void aesCbcCfb128Decrypt(std::span ivkey, + std::span ciphertext, + std::span decrypted); + void aesCbcCfb128DecryptEntry(std::span ivkey, + std::span ciphertext, + std::span decrypted); + void decryptEFSM(std::span trophyKey, + std::span NPcommID, std::span efsmIv, + std::span ciphertext, std::span decrypted); + void PfsGenCryptoKey(std::span ekpfs, + std::span seed, + std::span dataKey, + std::span tweakKey); + void decryptPFS(std::span dataKey, + std::span tweakKey, std::span src_image, + std::span 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 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; + } + } +}; diff --git a/src/endian.h b/src/endian.h new file mode 100644 index 0000000..aaf2e38 --- /dev/null +++ b/src/endian.h @@ -0,0 +1,243 @@ +// 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 +#include + +#include "types.h" + +namespace Common { + +/** + * Native endianness + */ +template +using NativeEndian = T; + +template +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 + explicit operator const SwappedEndian() const { + SwappedEndian 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& operator=(const T& right) { + FromSwap(right); + return *this; + } + SwappedEndian& operator=(const SwappedEndian& right) = default; + + template + SwappedEndian& operator+=(T1 right) { + return *this = T(*this) + right; + } + template + SwappedEndian& operator-=(T1 right) { + return *this = T(*this) - right; + } + template + SwappedEndian& operator*=(T1 right) { + return *this = T(*this) * right; + } + template + SwappedEndian& operator/=(T1 right) { + return *this = T(*this) / right; + } + template + SwappedEndian& operator%=(T1 right) { + return *this = T(*this) % right; + } + template + SwappedEndian& operator&=(T1 right) { + return *this = T(*this) & right; + } + template + SwappedEndian& operator|=(T1 right) { + return *this = T(*this) | right; + } + template + SwappedEndian& operator^=(T1 right) { + return *this = T(*this) ^ right; + } + template + SwappedEndian& operator<<=(T1 right) { + return *this = T(*this) << right; + } + template + SwappedEndian& operator>>=(T1 right) { + return *this = T(*this) >> right; + } + + template + SwappedEndian& operator+=(const SwappedEndian& right) { + return *this = Swap() + right.Swap(); + } + template + SwappedEndian& operator-=(const SwappedEndian& right) { + return *this = Swap() - right.Swap(); + } + template + SwappedEndian& operator*=(const SwappedEndian& right) { + return *this = Swap() * right.Swap(); + } + template + SwappedEndian& operator/=(const SwappedEndian& right) { + return *this = Swap() / right.Swap(); + } + template + SwappedEndian& operator%=(const SwappedEndian& right) { + return *this = Swap() % right.Swap(); + } + template + SwappedEndian& operator&=(const SwappedEndian& right) { + return *this = Raw() & right.Raw(); + } + template + SwappedEndian& operator|=(const SwappedEndian& right) { + return *this = Raw() | right.Raw(); + } + template + SwappedEndian& operator^=(const SwappedEndian& right) { + return *this = Raw() ^ right.Raw(); + } + + template + SwappedEndian operator&(const SwappedEndian& right) const { + return SwappedEndian{Raw() & right.Raw()}; + } + template + SwappedEndian operator|(const SwappedEndian& right) const { + return SwappedEndian{Raw() | right.Raw()}; + } + template + SwappedEndian operator^(const SwappedEndian& right) const { + return SwappedEndian{Raw() ^ right.Raw()}; + } + + template + bool operator==(T1 right) const { + return (T1)Swap() == right; + } + template + bool operator!=(T1 right) const { + return !(*this == right); + } + template + bool operator>(T1 right) const { + return (T1)Swap() > right; + } + template + bool operator<(T1 right) const { + return (T1)Swap() < right; + } + template + bool operator>=(T1 right) const { + return (T1)Swap() >= right; + } + template + bool operator<=(T1 right) const { + return (T1)Swap() <= right; + } + + template + bool operator==(const SwappedEndian& right) const { + return Raw() == right.Raw(); + } + template + bool operator!=(const SwappedEndian& right) const { + return !(*this == right); + } + template + bool operator>(const SwappedEndian& right) const { + return (T1)Swap() > right.Swap(); + } + template + bool operator<(const SwappedEndian& right) const { + return (T1)Swap() < right.Swap(); + } + template + bool operator>=(const SwappedEndian& right) const { + return (T1)Swap() >= right.Swap(); + } + template + bool operator<=(const SwappedEndian& right) const { + return (T1)Swap() <= right.Swap(); + } + + SwappedEndian operator++(int) { + SwappedEndian res = *this; + *this += 1; + return res; + } + SwappedEndian operator--(int) { + SwappedEndian res = *this; + *this -= 1; + return res; + } + SwappedEndian& operator++() { + *this += 1; + return *this; + } + SwappedEndian& operator--() { + *this -= 1; + return *this; + } + +private: + T data; +}; + +template +using LittleEndian = std::conditional_t, + SwappedEndian>; + +template +using BigEndian = + std::conditional_t, SwappedEndian>; + +} // namespace Common + +using u16_be = Common::BigEndian; +using u32_be = Common::BigEndian; +using u64_be = Common::BigEndian; + +using u16_le = Common::LittleEndian; +using u32_le = Common::LittleEndian; +using u64_le = Common::LittleEndian; diff --git a/src/enum.h b/src/enum.h new file mode 100644 index 0000000..7bead68 --- /dev/null +++ b/src/enum.h @@ -0,0 +1,162 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "types.h" + +#define DECLARE_ENUM_FLAG_OPERATORS(type) \ + [[nodiscard]] constexpr type operator|(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) | static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator&(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) & static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator^(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) ^ static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) << static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) >> static_cast(b)); \ + } \ + constexpr type& operator|=(type& a, type b) noexcept { \ + a = a | b; \ + return a; \ + } \ + constexpr type& operator&=(type& a, type b) noexcept { \ + a = a & b; \ + return a; \ + } \ + constexpr type& operator^=(type& a, type b) noexcept { \ + a = a ^ b; \ + return a; \ + } \ + constexpr type& operator<<=(type& a, type b) noexcept { \ + a = a << b; \ + return a; \ + } \ + constexpr type& operator>>=(type& a, type b) noexcept { \ + a = a >> b; \ + return a; \ + } \ + [[nodiscard]] constexpr type operator~(type key) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(~static_cast(key)); \ + } \ + [[nodiscard]] constexpr bool True(type key) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(key) != 0; \ + } \ + [[nodiscard]] constexpr bool False(type key) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(key) == 0; \ + } + +namespace Common { + +template +class Flags { +public: + using IntType = std::underlying_type_t; + + Flags() {} + + Flags(IntType t) : m_bits(t) {} + + template + Flags(T f, Tx... fx) { + this->set(f, fx...); + } + + template + void set(Tx... fx) { + m_bits |= bits(fx...); + } + + void set(Flags flags) { + m_bits |= flags.m_bits; + } + + template + void clr(Tx... fx) { + m_bits &= ~bits(fx...); + } + + void clr(Flags flags) { + m_bits &= ~flags.m_bits; + } + + template + bool any(Tx... fx) const { + return (m_bits & bits(fx...)) != 0; + } + + template + bool all(Tx... fx) const { + const IntType mask = bits(fx...); + return (m_bits & mask) == mask; + } + + bool test(T f) const { + return this->any(f); + } + + bool isClear() const { + return m_bits == 0; + } + + void clrAll() { + m_bits = 0; + } + + u32 raw() const { + return m_bits; + } + + Flags operator&(const Flags& other) const { + return Flags(m_bits & other.m_bits); + } + + Flags operator|(const Flags& other) const { + return Flags(m_bits | other.m_bits); + } + + Flags operator^(const Flags& other) const { + return Flags(m_bits ^ other.m_bits); + } + + bool operator==(const Flags& other) const { + return m_bits == other.m_bits; + } + + bool operator!=(const Flags& other) const { + return m_bits != other.m_bits; + } + +private: + IntType m_bits = 0; + + static IntType bit(T f) { + return IntType(1) << static_cast(f); + } + + template + static IntType bits(T f, Tx... fx) { + return bit(f) | bits(fx...); + } + + static IntType bits() { + return 0; + } +}; + +} // namespace Common diff --git a/src/io_file.cpp b/src/io_file.cpp new file mode 100644 index 0000000..74c6df5 --- /dev/null +++ b/src/io_file.cpp @@ -0,0 +1,422 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "alignment.h" +// #include "common/assert.h" +// #include "common/error.h" +#include "io_file.h" +// #include "common/logging/log.h" +// #include "common/path_util.h" + +#ifdef _WIN32 +#include "nt_api.h" + +#include +#include +#include +#include +#else +#include +#endif + +#ifdef _MSC_VER +#define fileno _fileno +#define fseeko _fseeki64 +#define ftello _ftelli64 +#endif + +namespace Common::FS { + +namespace fs = std::filesystem; + +namespace { + +#ifdef _WIN32 + +[[nodiscard]] constexpr const wchar_t* AccessModeToWStr(FileAccessMode mode, FileType type) { + switch (type) { + case FileType::BinaryFile: + switch (mode) { + case FileAccessMode::Read: + return L"rb"; + case FileAccessMode::Write: + return L"wb"; + case FileAccessMode::Append: + return L"ab"; + case FileAccessMode::ReadWrite: + return L"r+b"; + case FileAccessMode::ReadAppend: + return L"a+b"; + } + break; + case FileType::TextFile: + switch (mode) { + case FileAccessMode::Read: + return L"r"; + case FileAccessMode::Write: + return L"w"; + case FileAccessMode::Append: + return L"a"; + case FileAccessMode::ReadWrite: + return L"r+"; + case FileAccessMode::ReadAppend: + return L"a+"; + } + break; + } + + return L""; +} + +[[nodiscard]] constexpr int ToWindowsFileShareFlag(FileShareFlag flag) { + switch (flag) { + case FileShareFlag::ShareNone: + default: + return _SH_DENYRW; + case FileShareFlag::ShareReadOnly: + return _SH_DENYWR; + case FileShareFlag::ShareWriteOnly: + return _SH_DENYRD; + case FileShareFlag::ShareReadWrite: + return _SH_DENYNO; + } +} + +#else + +[[nodiscard]] constexpr const char* AccessModeToStr(FileAccessMode mode, FileType type) { + switch (type) { + case FileType::BinaryFile: + switch (mode) { + case FileAccessMode::Read: + return "rb"; + case FileAccessMode::Write: + return "wb"; + case FileAccessMode::Append: + return "ab"; + case FileAccessMode::ReadWrite: + return "r+b"; + case FileAccessMode::ReadAppend: + return "a+b"; + } + break; + case FileType::TextFile: + switch (mode) { + case FileAccessMode::Read: + return "r"; + case FileAccessMode::Write: + return "w"; + case FileAccessMode::Append: + return "a"; + case FileAccessMode::ReadWrite: + return "r+"; + case FileAccessMode::ReadAppend: + return "a+"; + } + break; + } + + return ""; +} + +#endif + +[[nodiscard]] constexpr int ToSeekOrigin(SeekOrigin origin) { + switch (origin) { + case SeekOrigin::SetOrigin: + return SEEK_SET; + case SeekOrigin::CurrentPosition: + return SEEK_CUR; + case SeekOrigin::End: + return SEEK_END; + default: + // UNREACHABLE_MSG("Impossible SeekOrigin {}", static_cast(origin)); + return SEEK_END; + break; + } +} + +} // Anonymous namespace + +IOFile::IOFile() = default; + +IOFile::IOFile(const std::string& path, FileAccessMode mode, FileType type, FileShareFlag flag) { + Open(path, mode, type, flag); +} + +IOFile::IOFile(std::string_view path, FileAccessMode mode, FileType type, FileShareFlag flag) { + Open(path, mode, type, flag); +} + +IOFile::IOFile(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) { + Open(path, mode, type, flag); +} + +IOFile::~IOFile() { + Close(); +} + +IOFile::IOFile(IOFile&& other) noexcept { + std::swap(file_path, other.file_path); + std::swap(file_access_mode, other.file_access_mode); + std::swap(file_type, other.file_type); + std::swap(file, other.file); +} + +IOFile& IOFile::operator=(IOFile&& other) noexcept { + std::swap(file_path, other.file_path); + std::swap(file_access_mode, other.file_access_mode); + std::swap(file_type, other.file_type); + std::swap(file, other.file); + return *this; +} + +int IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) { + Close(); + + file_path = path; + file_access_mode = mode; + file_type = type; + + errno = 0; + int result = 0; + +#ifdef _WIN32 + if (flag != FileShareFlag::ShareNone) { + file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag)); + result = errno; + } else { + result = _wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type)); + } +#else + file = std::fopen(path.c_str(), AccessModeToStr(mode, type)); + result = errno; +#endif + + if (!IsOpen()) { + const auto ec = std::error_code{result, std::generic_category()}; + // LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}, error_message={}", + // PathToUTF8String(file_path), ec.message()); + } + + return result; +} + +void IOFile::Close() { + if (!IsOpen()) { + return; + } + + errno = 0; + + const auto close_result = std::fclose(file) == 0; + + if (!close_result) { + const auto ec = std::error_code{errno, std::generic_category()}; + // LOG_ERROR(Common_Filesystem, "Failed to close the file at path={}, ec_message={}", + // PathToUTF8String(file_path), ec.message()); + } + + file = nullptr; + +#ifdef _WIN64 + if (file_mapping && file_access_mode == FileAccessMode::ReadWrite) { + CloseHandle(std::bit_cast(file_mapping)); + } +#endif +} + +void IOFile::Unlink() { + if (!IsOpen()) { + return; + } + + // Mark the file for deletion + // TODO: Also remove the file path? +#ifdef _WIN64 + FILE_DISPOSITION_INFORMATION disposition; + IO_STATUS_BLOCK iosb; + + const int fd = fileno(file); + HANDLE hfile = reinterpret_cast(_get_osfhandle(fd)); + + disposition.DeleteFile = TRUE; + NtSetInformationFile(hfile, &iosb, &disposition, sizeof(disposition), + FileDispositionInformation); +#else + if (unlink(file_path.c_str()) != 0) { + const auto ec = std::error_code{errno, std::generic_category()}; + // LOG_ERROR(Common_Filesystem, "Failed to unlink the file at path={}, ec_message={}", + // PathToUTF8String(file_path), ec.message()); + } +#endif +} + +uintptr_t IOFile::GetFileMapping() { + if (file_mapping) { + return file_mapping; + } +#ifdef _WIN64 + const int fd = fileno(file); + + HANDLE hfile = reinterpret_cast(_get_osfhandle(fd)); + HANDLE mapping = nullptr; + + if (file_access_mode == FileAccessMode::ReadWrite) { + // mapping = CreateFileMapping2(hfile, NULL, FILE_MAP_WRITE, PAGE_READWRITE, SEC_COMMIT, 0, + // NULL, NULL, 0); + } else { + mapping = hfile; + } + + file_mapping = std::bit_cast(mapping); + // ASSERT_MSG(file_mapping, "{}", Common::GetLastErrorMsg()); + return file_mapping; +#else + file_mapping = fileno(file); + return file_mapping; +#endif +} + +std::string IOFile::ReadString(size_t length) const { + std::vector string_buffer(length); + + const auto chars_read = ReadSpan(string_buffer); + const auto string_size = chars_read != length ? chars_read : length; + + return std::string{string_buffer.data(), string_size}; +} + +bool IOFile::Flush() const { + if (!IsOpen()) { + return false; + } + + errno = 0; + +#ifdef _WIN32 + const auto flush_result = std::fflush(file) == 0; +#else + const auto flush_result = std::fflush(file) == 0; +#endif + + if (!flush_result) { + const auto ec = std::error_code{errno, std::generic_category()}; + // LOG_ERROR(Common_Filesystem, "Failed to flush the file at path={}, ec_message={}", + // PathToUTF8String(file_path), ec.message()); + } + + return flush_result; +} + +bool IOFile::Commit() const { + if (!IsOpen()) { + return false; + } + + errno = 0; + +#ifdef _WIN32 + const auto commit_result = std::fflush(file) == 0 && _commit(fileno(file)) == 0; +#else + const auto commit_result = std::fflush(file) == 0 && fsync(fileno(file)) == 0; +#endif + + if (!commit_result) { + const auto ec = std::error_code{errno, std::generic_category()}; + // LOG_ERROR(Common_Filesystem, "Failed to commit the file at path={}, ec_message={}", + // PathToUTF8String(file_path), ec.message()); + } + + return commit_result; +} + +bool IOFile::SetSize(u64 size) const { + if (!IsOpen()) { + return false; + } + + errno = 0; + +#ifdef _WIN32 + const auto set_size_result = _chsize_s(fileno(file), static_cast(size)) == 0; +#else + const auto set_size_result = ftruncate(fileno(file), static_cast(size)) == 0; +#endif + + if (!set_size_result) { + const auto ec = std::error_code{errno, std::generic_category()}; + // LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={}, size={}, + // ec_message={}, PathToUTF8String(file_path), size, ec.message()); + } + + return set_size_result; +} + +u64 IOFile::GetSize() const { + if (!IsOpen()) { + return 0; + } + + // Flush any unwritten buffered data into the file prior to retrieving the file size. + std::fflush(file); + + std::error_code ec; + + const auto file_size = fs::file_size(file_path, ec); + + if (ec) { + // LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, + // ec_message={}", PathToUTF8String(file_path), ec.message()); + return 0; + } + + return file_size; +} + +bool IOFile::Seek(s64 offset, SeekOrigin origin) const { + if (!IsOpen()) { + return false; + } + + errno = 0; + + const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0; + + if (!seek_result) { + const auto ec = std::error_code{errno, std::generic_category()}; + // LOG_ERROR(Common_Filesystem, + // "Failed to seek the file at path={}, offset={}, origin={}, ec_message={}", + // PathToUTF8String(file_path), offset, static_cast(origin), ec.message()); + } + + return seek_result; +} + +s64 IOFile::Tell() const { + if (!IsOpen()) { + return 0; + } + + errno = 0; + + return ftello(file); +} + +u64 GetDirectorySize(const std::filesystem::path& path) { + if (!fs::exists(path)) { + return 0; + } + + u64 total = 0; + for (const auto& entry : fs::recursive_directory_iterator(path)) { + if (fs::is_regular_file(entry.path())) { + total += fs::file_size(entry.path()); + } + } + return total; +} + +} // namespace Common::FS diff --git a/src/io_file.h b/src/io_file.h new file mode 100644 index 0000000..0763a35 --- /dev/null +++ b/src/io_file.h @@ -0,0 +1,228 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "concepts.h" +#include "enum.h" +#include "types.h" + +namespace Common::FS { + +enum class FileAccessMode { + /** + * If the file at path exists, it opens the file for reading. + * If the file at path does not exist, it fails to open the file. + */ + Read = 1 << 0, + /** + * If the file at path exists, the existing contents of the file are erased. + * The empty file is then opened for writing. + * If the file at path does not exist, it creates and opens a new empty file for writing. + */ + Write = 1 << 1, + /** + * If the file at path exists, it opens the file for reading and writing. + * If the file at path does not exist, it fails to open the file. + */ + ReadWrite = Read | Write, + /** + * If the file at path exists, it opens the file for appending. + * If the file at path does not exist, it creates and opens a new empty file for appending. + */ + Append = 1 << 2, + /** + * If the file at path exists, it opens the file for both reading and appending. + * If the file at path does not exist, it creates and opens a new empty file for both + * reading and appending. + */ + ReadAppend = Read | Append, +}; +DECLARE_ENUM_FLAG_OPERATORS(FileAccessMode); + +enum class FileType { + BinaryFile, + TextFile, +}; + +enum class FileShareFlag { + ShareNone, // Provides exclusive access to the file. + ShareReadOnly, // Provides read only shared access to the file. + ShareWriteOnly, // Provides write only shared access to the file. + ShareReadWrite, // Provides read and write shared access to the file. +}; + +enum class SeekOrigin : u32 { + SetOrigin, // Seeks from the start of the file. + CurrentPosition, // Seeks from the current file pointer position. + End, // Seeks from the end of the file. +}; + +class IOFile final { +public: + IOFile(); + + explicit IOFile(const std::string& path, FileAccessMode mode, + FileType type = FileType::BinaryFile, + FileShareFlag flag = FileShareFlag::ShareReadOnly); + + explicit IOFile(std::string_view path, FileAccessMode mode, + FileType type = FileType::BinaryFile, + FileShareFlag flag = FileShareFlag::ShareReadOnly); + explicit IOFile(const std::filesystem::path& path, FileAccessMode mode, + FileType type = FileType::BinaryFile, + FileShareFlag flag = FileShareFlag::ShareReadOnly); + + ~IOFile(); + + IOFile(const IOFile&) = delete; + IOFile& operator=(const IOFile&) = delete; + + IOFile(IOFile&& other) noexcept; + IOFile& operator=(IOFile&& other) noexcept; + + std::filesystem::path GetPath() const { + return file_path; + } + + FileAccessMode GetAccessMode() const { + return file_access_mode; + } + + FileType GetType() const { + return file_type; + } + + bool IsOpen() const { + return file != nullptr; + } + + uintptr_t GetFileMapping(); + + int Open(const std::filesystem::path& path, FileAccessMode mode, + FileType type = FileType::BinaryFile, + FileShareFlag flag = FileShareFlag::ShareReadOnly); + void Close(); + + void Unlink(); + + bool Flush() const; + bool Commit() const; + + bool SetSize(u64 size) const; + u64 GetSize() const; + + bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const; + s64 Tell() const; + + template + size_t Read(T& data) const { + if constexpr (IsContiguousContainer) { + using ContiguousType = typename T::value_type; + static_assert(std::is_trivially_copyable_v, + "Data type must be trivially copyable."); + return ReadSpan(data); + } else { + return ReadObject(data) ? 1 : 0; + } + } + + template + size_t Write(const T& data) const { + if constexpr (IsContiguousContainer) { + using ContiguousType = typename T::value_type; + static_assert(std::is_trivially_copyable_v, + "Data type must be trivially copyable."); + return WriteSpan(data); + } else { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + return WriteObject(data) ? 1 : 0; + } + } + + template + size_t ReadSpan(std::span data) const { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + + if (!IsOpen()) { + return 0; + } + + return ReadRaw(data.data(), data.size()); + } + + template + size_t ReadRaw(void* data, size_t size) const { + return std::fread(data, sizeof(T), size, file); + } + + template + size_t WriteSpan(std::span data) const { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + + if (!IsOpen()) { + return 0; + } + + return std::fwrite(data.data(), sizeof(T), data.size(), file); + } + + template + bool ReadObject(T& object) const { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + static_assert(!std::is_pointer_v, "T must not be a pointer to an object."); + + if (!IsOpen()) { + return false; + } + + return std::fread(&object, sizeof(T), 1, file) == 1; + } + + template + size_t WriteRaw(const void* data, size_t size) const { + auto bytes = std::fwrite(data, sizeof(T), size, file); + std::fflush(file); + return bytes; + } + + template + bool WriteObject(const T& object) const { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + static_assert(!std::is_pointer_v, "T must not be a pointer to an object."); + + if (!IsOpen()) { + return false; + } + + return std::fwrite(&object, sizeof(T), 1, file) == 1; + } + + std::string ReadString(size_t length) const; + + size_t WriteString(std::span string) const { + return WriteSpan(string); + } + + static size_t WriteBytes(const std::filesystem::path path, const auto& data) { + IOFile out(path, FileAccessMode::Write); + return out.Write(data); + } + std::FILE* file = nullptr; + +private: + std::filesystem::path file_path; + FileAccessMode file_access_mode{}; + FileType file_type{}; + + uintptr_t file_mapping = 0; +}; + +u64 GetDirectorySize(const std::filesystem::path& path); + +} // namespace Common::FS diff --git a/src/keys.h b/src/keys.h new file mode 100644 index 0000000..4410824 --- /dev/null +++ b/src/keys.h @@ -0,0 +1,305 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include + +class FakeKeyset { +public: + // Constructor + static constexpr CryptoPP::byte Exponent1[] = { + 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 = d mod (q - 1) + static constexpr CryptoPP::byte Exponent2[] = { + 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}; + // e + static constexpr CryptoPP::byte PublicExponent[] = {0, 1, 0, 1}; + // (InverseQ)(q) = 1 mod p + static constexpr CryptoPP::byte Coefficient[] = { + 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}; + // n = p * q + static constexpr CryptoPP::byte Modulus[] = { + 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}; + // p + static constexpr CryptoPP::byte Prime1[] = { + 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}; + // q + static constexpr CryptoPP::byte Prime2[] = { + 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}; + static constexpr CryptoPP::byte PrivateExponent[] = { + 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}; +}; + +class DebugRifKeyset { +public: + // std::uint8_t* PrivateExponent; + static constexpr CryptoPP::byte Exponent1[] = { + 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 = d mod (q - 1) + static constexpr CryptoPP::byte Exponent2[] = { + 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}; + // e + static constexpr CryptoPP::byte PublicExponent[] = {0x00, 0x01, 0x00, 0x01}; + // (InverseQ)(q) = 1 mod p + static constexpr CryptoPP::byte Coefficient[] = { + 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}; + // n = p * q + static constexpr CryptoPP::byte Modulus[] = { + 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}; + // p + static constexpr CryptoPP::byte Prime1[] = { + 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}; + // q + static constexpr CryptoPP::byte Prime2[] = { + 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}; + static constexpr CryptoPP::byte PrivateExponent[] = { + 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}; +}; + +class PkgDerivedKey3Keyset { +public: + // std::uint8_t* PrivateExponent; + static constexpr CryptoPP::byte Exponent1[] = { + 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 = d mod (q - 1) + static constexpr CryptoPP::byte Exponent2[] = { + 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}; + // e + static constexpr CryptoPP::byte PublicExponent[] = {0, 1, 0, 1}; + // (InverseQ)(q) = 1 mod p + static constexpr CryptoPP::byte Coefficient[] = { + 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}; + // n = p * q + static constexpr CryptoPP::byte Modulus[] = { + 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}; + // p + static constexpr CryptoPP::byte Prime1[] = { + 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}; + // q + static constexpr CryptoPP::byte Prime2[] = { + 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}; + static constexpr CryptoPP::byte PrivateExponent[] = { + 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}; +}; \ No newline at end of file diff --git a/src/loader.cpp b/src/loader.cpp new file mode 100644 index 0000000..2a7efc5 --- /dev/null +++ b/src/loader.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "io_file.h" +#include "loader.h" + +namespace Loader { + +FileTypes DetectFileType(const std::filesystem::path& 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 diff --git a/src/loader.h b/src/loader.h new file mode 100644 index 0000000..785d0a5 --- /dev/null +++ b/src/loader.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "types.h" + +namespace Loader { + +constexpr static u32 PkgMagic = 0x544e437f; + +enum class FileTypes { + Unknown, + Pkg, +}; + +FileTypes DetectFileType(const std::filesystem::path& filepath); +} // namespace Loader diff --git a/src/nt_api.cpp b/src/nt_api.cpp new file mode 100644 index 0000000..6c01df8 --- /dev/null +++ b/src/nt_api.cpp @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifdef _WIN32 + +#include "nt_api.h" + +NtClose_t NtClose = nullptr; +NtSetInformationFile_t NtSetInformationFile = nullptr; +NtCreateThread_t NtCreateThread = nullptr; +NtTerminateThread_t NtTerminateThread = nullptr; +NtQueueApcThreadEx_t NtQueueApcThreadEx = nullptr; + +namespace Common::NtApi { + +void Initialize() { + HMODULE nt_handle = GetModuleHandleA("ntdll.dll"); + + // http://stackoverflow.com/a/31411628/4725495 + NtClose = (NtClose_t)GetProcAddress(nt_handle, "NtClose"); + NtSetInformationFile = + (NtSetInformationFile_t)GetProcAddress(nt_handle, "NtSetInformationFile"); + NtCreateThread = (NtCreateThread_t)GetProcAddress(nt_handle, "NtCreateThread"); + NtTerminateThread = (NtTerminateThread_t)GetProcAddress(nt_handle, "NtTerminateThread"); + NtQueueApcThreadEx = (NtQueueApcThreadEx_t)GetProcAddress(nt_handle, "NtQueueApcThreadEx"); +} + +} // namespace Common::NtApi + +#endif diff --git a/src/nt_api.h b/src/nt_api.h new file mode 100644 index 0000000..7d89261 --- /dev/null +++ b/src/nt_api.h @@ -0,0 +1,556 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#ifdef _WIN32 + +#include + +#include "types.h" + +typedef enum _FILE_INFORMATION_CLASS { + FileDirectoryInformation = 1, + FileFullDirectoryInformation = 2, + FileBothDirectoryInformation = 3, + FileBasicInformation = 4, + FileStandardInformation = 5, + FileInternalInformation = 6, + FileEaInformation = 7, + FileAccessInformation = 8, + FileNameInformation = 9, + FileRenameInformation = 10, + FileLinkInformation = 11, + FileNamesInformation = 12, + FileDispositionInformation = 13, + FilePositionInformation = 14, + FileFullEaInformation = 15, + FileModeInformation = 16, + FileAlignmentInformation = 17, + FileAllInformation = 18, + FileAllocationInformation = 19, + FileEndOfFileInformation = 20, + FileAlternateNameInformation = 21, + FileStreamInformation = 22, + FilePipeInformation = 23, + FilePipeLocalInformation = 24, + FilePipeRemoteInformation = 25, + FileMailslotQueryInformation = 26, + FileMailslotSetInformation = 27, + FileCompressionInformation = 28, + FileObjectIdInformation = 29, + FileCompletionInformation = 30, + FileMoveClusterInformation = 31, + FileQuotaInformation = 32, + FileReparsePointInformation = 33, + FileNetworkOpenInformation = 34, + FileAttributeTagInformation = 35, + FileTrackingInformation = 36, + FileIdBothDirectoryInformation = 37, + FileIdFullDirectoryInformation = 38, + FileValidDataLengthInformation = 39, + FileShortNameInformation = 40, + FileIoCompletionNotificationInformation = 41, + FileIoStatusBlockRangeInformation = 42, + FileIoPriorityHintInformation = 43, + FileSfioReserveInformation = 44, + FileSfioVolumeInformation = 45, + FileHardLinkInformation = 46, + FileProcessIdsUsingFileInformation = 47, + FileNormalizedNameInformation = 48, + FileNetworkPhysicalNameInformation = 49, + FileIdGlobalTxDirectoryInformation = 50, + FileIsRemoteDeviceInformation = 51, + FileUnusedInformation = 52, + FileNumaNodeInformation = 53, + FileStandardLinkInformation = 54, + FileRemoteProtocolInformation = 55, + FileRenameInformationBypassAccessCheck = 56, + FileLinkInformationBypassAccessCheck = 57, + FileVolumeNameInformation = 58, + FileIdInformation = 59, + FileIdExtdDirectoryInformation = 60, + FileReplaceCompletionInformation = 61, + FileHardLinkFullIdInformation = 62, + FileIdExtdBothDirectoryInformation = 63, + FileDispositionInformationEx = 64, + FileRenameInformationEx = 65, + FileRenameInformationExBypassAccessCheck = 66, + FileDesiredStorageClassInformation = 67, + FileStatInformation = 68, + FileMemoryPartitionInformation = 69, + FileStatLxInformation = 70, + FileCaseSensitiveInformation = 71, + FileLinkInformationEx = 72, + FileLinkInformationExBypassAccessCheck = 73, + FileStorageReserveIdInformation = 74, + FileCaseSensitiveInformationForceAccessCheck = 75, + FileKnownFolderInformation = 76, + FileStatBasicInformation = 77, + FileId64ExtdDirectoryInformation = 78, + FileId64ExtdBothDirectoryInformation = 79, + FileIdAllExtdDirectoryInformation = 80, + FileIdAllExtdBothDirectoryInformation = 81, + FileStreamReservationInformation, + FileMupProviderInfo, + FileMaximumInformation +} FILE_INFORMATION_CLASS, + *PFILE_INFORMATION_CLASS; + +typedef struct _IO_STATUS_BLOCK { + union { + u32 Status; + PVOID Pointer; + }; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +typedef struct _FILE_DISPOSITION_INFORMATION { + BOOLEAN DeleteFile; +} FILE_DISPOSITION_INFORMATION, *PFILE_DISPOSITION_INFORMATION; + +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWCH Buffer; +} UNICODE_STRING, *PUNICODE_STRING; + +typedef const UNICODE_STRING* PCUNICODE_STRING; + +typedef struct _OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + PCUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; // PSECURITY_DESCRIPTOR; + PVOID SecurityQualityOfService; // PSECURITY_QUALITY_OF_SERVICE +} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; + +typedef const OBJECT_ATTRIBUTES* PCOBJECT_ATTRIBUTES; + +typedef struct _CLIENT_ID { + HANDLE UniqueProcess; + HANDLE UniqueThread; +} CLIENT_ID, *PCLIENT_ID; + +typedef struct _INITIAL_TEB { + struct { + PVOID OldStackBase; + PVOID OldStackLimit; + } OldInitialTeb; + PVOID StackBase; + PVOID StackLimit; + PVOID StackAllocationBase; +} INITIAL_TEB, *PINITIAL_TEB; + +typedef struct _PEB_LDR_DATA { + ULONG Length; + BOOLEAN Initialized; + PVOID SsHandle; + LIST_ENTRY InLoadOrderModuleList; + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; + PVOID EntryInProgress; + BOOLEAN ShutdownInProgress; + HANDLE ShutdownThreadId; +} PEB_LDR_DATA, *PPEB_LDR_DATA; + +typedef struct _CURDIR { + UNICODE_STRING DosPath; + PVOID Handle; +} CURDIR, *PCURDIR; + +typedef struct RTL_DRIVE_LETTER_CURDIR { + USHORT Flags; + USHORT Length; + ULONG TimeStamp; + UNICODE_STRING DosPath; +} RTL_DRIVE_LETTER_CURDIR, *PRTL_DRIVE_LETTER_CURDIR; + +typedef struct _RTL_USER_PROCESS_PARAMETERS { + ULONG AllocationSize; + ULONG Size; + ULONG Flags; + ULONG DebugFlags; + HANDLE ConsoleHandle; + ULONG ConsoleFlags; + HANDLE hStdInput; + HANDLE hStdOutput; + HANDLE hStdError; + CURDIR CurrentDirectory; + UNICODE_STRING DllPath; + UNICODE_STRING ImagePathName; + UNICODE_STRING CommandLine; + PWSTR Environment; + ULONG dwX; + ULONG dwY; + ULONG dwXSize; + ULONG dwYSize; + ULONG dwXCountChars; + ULONG dwYCountChars; + ULONG dwFillAttribute; + ULONG dwFlags; + ULONG wShowWindow; + UNICODE_STRING WindowTitle; + UNICODE_STRING Desktop; + UNICODE_STRING ShellInfo; + UNICODE_STRING RuntimeInfo; + RTL_DRIVE_LETTER_CURDIR DLCurrentDirectory[0x20]; + ULONG_PTR EnvironmentSize; + ULONG_PTR EnvironmentVersion; + PVOID PackageDependencyData; + ULONG ProcessGroupId; + ULONG LoaderThreads; +} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; + +typedef struct tagRTL_BITMAP { + ULONG SizeOfBitMap; + PULONG Buffer; +} RTL_BITMAP, *PRTL_BITMAP; + +typedef struct { + UINT next; + UINT id; + ULONGLONG addr; + ULONGLONG size; + UINT args[4]; +} CROSS_PROCESS_WORK_ENTRY; + +typedef union { + struct { + UINT first; + UINT counter; + }; + volatile LONGLONG hdr; +} CROSS_PROCESS_WORK_HDR; + +typedef struct { + CROSS_PROCESS_WORK_HDR free_list; + CROSS_PROCESS_WORK_HDR work_list; + ULONGLONG unknown[4]; + CROSS_PROCESS_WORK_ENTRY entries[1]; +} CROSS_PROCESS_WORK_LIST; + +typedef struct _CHPEV2_PROCESS_INFO { + ULONG Wow64ExecuteFlags; /* 000 */ + USHORT NativeMachineType; /* 004 */ + USHORT EmulatedMachineType; /* 006 */ + HANDLE SectionHandle; /* 008 */ + CROSS_PROCESS_WORK_LIST* CrossProcessWorkList; /* 010 */ + void* unknown; /* 018 */ +} CHPEV2_PROCESS_INFO, *PCHPEV2_PROCESS_INFO; + +typedef u64(__stdcall* KERNEL_CALLBACK_PROC)(void*, ULONG); + +typedef struct _PEB { /* win32/win64 */ + BOOLEAN InheritedAddressSpace; /* 000/000 */ + BOOLEAN ReadImageFileExecOptions; /* 001/001 */ + BOOLEAN BeingDebugged; /* 002/002 */ + UCHAR ImageUsedLargePages : 1; /* 003/003 */ + UCHAR IsProtectedProcess : 1; + UCHAR IsImageDynamicallyRelocated : 1; + UCHAR SkipPatchingUser32Forwarders : 1; + UCHAR IsPackagedProcess : 1; + UCHAR IsAppContainer : 1; + UCHAR IsProtectedProcessLight : 1; + UCHAR IsLongPathAwareProcess : 1; + HANDLE Mutant; /* 004/008 */ + HMODULE ImageBaseAddress; /* 008/010 */ + PPEB_LDR_DATA LdrData; /* 00c/018 */ + RTL_USER_PROCESS_PARAMETERS* ProcessParameters; /* 010/020 */ + PVOID SubSystemData; /* 014/028 */ + HANDLE ProcessHeap; /* 018/030 */ + PRTL_CRITICAL_SECTION FastPebLock; /* 01c/038 */ + PVOID AtlThunkSListPtr; /* 020/040 */ + PVOID IFEOKey; /* 024/048 */ + ULONG ProcessInJob : 1; /* 028/050 */ + ULONG ProcessInitializing : 1; + ULONG ProcessUsingVEH : 1; + ULONG ProcessUsingVCH : 1; + ULONG ProcessUsingFTH : 1; + ULONG ProcessPreviouslyThrottled : 1; + ULONG ProcessCurrentlyThrottled : 1; + ULONG ProcessImagesHotPatched : 1; + ULONG ReservedBits0 : 24; + KERNEL_CALLBACK_PROC* KernelCallbackTable; /* 02c/058 */ + ULONG Reserved; /* 030/060 */ + ULONG AtlThunkSListPtr32; /* 034/064 */ + PVOID ApiSetMap; /* 038/068 */ + ULONG TlsExpansionCounter; /* 03c/070 */ + PRTL_BITMAP TlsBitmap; /* 040/078 */ + ULONG TlsBitmapBits[2]; /* 044/080 */ + PVOID ReadOnlySharedMemoryBase; /* 04c/088 */ + PVOID SharedData; /* 050/090 */ + PVOID* ReadOnlyStaticServerData; /* 054/098 */ + PVOID AnsiCodePageData; /* 058/0a0 */ + PVOID OemCodePageData; /* 05c/0a8 */ + PVOID UnicodeCaseTableData; /* 060/0b0 */ + ULONG NumberOfProcessors; /* 064/0b8 */ + ULONG NtGlobalFlag; /* 068/0bc */ + LARGE_INTEGER CriticalSectionTimeout; /* 070/0c0 */ + SIZE_T HeapSegmentReserve; /* 078/0c8 */ + SIZE_T HeapSegmentCommit; /* 07c/0d0 */ + SIZE_T HeapDeCommitTotalFreeThreshold; /* 080/0d8 */ + SIZE_T HeapDeCommitFreeBlockThreshold; /* 084/0e0 */ + ULONG NumberOfHeaps; /* 088/0e8 */ + ULONG MaximumNumberOfHeaps; /* 08c/0ec */ + PVOID* ProcessHeaps; /* 090/0f0 */ + PVOID GdiSharedHandleTable; /* 094/0f8 */ + PVOID ProcessStarterHelper; /* 098/100 */ + PVOID GdiDCAttributeList; /* 09c/108 */ + PVOID LoaderLock; /* 0a0/110 */ + ULONG OSMajorVersion; /* 0a4/118 */ + ULONG OSMinorVersion; /* 0a8/11c */ + ULONG OSBuildNumber; /* 0ac/120 */ + ULONG OSPlatformId; /* 0b0/124 */ + ULONG ImageSubSystem; /* 0b4/128 */ + ULONG ImageSubSystemMajorVersion; /* 0b8/12c */ + ULONG ImageSubSystemMinorVersion; /* 0bc/130 */ + KAFFINITY ActiveProcessAffinityMask; /* 0c0/138 */ +#ifdef _WIN64 + ULONG GdiHandleBuffer[60]; /* /140 */ +#else + ULONG GdiHandleBuffer[34]; /* 0c4/ */ +#endif + PVOID PostProcessInitRoutine; /* 14c/230 */ + PRTL_BITMAP TlsExpansionBitmap; /* 150/238 */ + ULONG TlsExpansionBitmapBits[32]; /* 154/240 */ + ULONG SessionId; /* 1d4/2c0 */ + ULARGE_INTEGER AppCompatFlags; /* 1d8/2c8 */ + ULARGE_INTEGER AppCompatFlagsUser; /* 1e0/2d0 */ + PVOID ShimData; /* 1e8/2d8 */ + PVOID AppCompatInfo; /* 1ec/2e0 */ + UNICODE_STRING CSDVersion; /* 1f0/2e8 */ + PVOID ActivationContextData; /* 1f8/2f8 */ + PVOID ProcessAssemblyStorageMap; /* 1fc/300 */ + PVOID SystemDefaultActivationData; /* 200/308 */ + PVOID SystemAssemblyStorageMap; /* 204/310 */ + SIZE_T MinimumStackCommit; /* 208/318 */ + PVOID* FlsCallback; /* 20c/320 */ + LIST_ENTRY FlsListHead; /* 210/328 */ + union { + PRTL_BITMAP FlsBitmap; /* 218/338 */ +#ifdef _WIN64 + CHPEV2_PROCESS_INFO* ChpeV2ProcessInfo; /* /338 */ +#endif + }; + ULONG FlsBitmapBits[4]; /* 21c/340 */ + ULONG FlsHighIndex; /* 22c/350 */ + PVOID WerRegistrationData; /* 230/358 */ + PVOID WerShipAssertPtr; /* 234/360 */ + PVOID EcCodeBitMap; /* 238/368 */ + PVOID pImageHeaderHash; /* 23c/370 */ + ULONG HeapTracingEnabled : 1; /* 240/378 */ + ULONG CritSecTracingEnabled : 1; + ULONG LibLoaderTracingEnabled : 1; + ULONG SpareTracingBits : 29; + ULONGLONG CsrServerReadOnlySharedMemoryBase; /* 248/380 */ + ULONG TppWorkerpListLock; /* 250/388 */ + LIST_ENTRY TppWorkerpList; /* 254/390 */ + PVOID WaitOnAddressHashTable[0x80]; /* 25c/3a0 */ + PVOID TelemetryCoverageHeader; /* 45c/7a0 */ + ULONG CloudFileFlags; /* 460/7a8 */ + ULONG CloudFileDiagFlags; /* 464/7ac */ + CHAR PlaceholderCompatibilityMode; /* 468/7b0 */ + CHAR PlaceholderCompatibilityModeReserved[7]; /* 469/7b1 */ + PVOID LeapSecondData; /* 470/7b8 */ + ULONG LeapSecondFlags; /* 474/7c0 */ + ULONG NtGlobalFlag2; /* 478/7c4 */ +} PEB, *PPEB; + +typedef struct _RTL_ACTIVATION_CONTEXT_STACK_FRAME { + struct _RTL_ACTIVATION_CONTEXT_STACK_FRAME* Previous; + struct _ACTIVATION_CONTEXT* ActivationContext; + ULONG Flags; +} RTL_ACTIVATION_CONTEXT_STACK_FRAME, *PRTL_ACTIVATION_CONTEXT_STACK_FRAME; + +typedef struct _ACTIVATION_CONTEXT_STACK { + RTL_ACTIVATION_CONTEXT_STACK_FRAME* ActiveFrame; + LIST_ENTRY FrameListCache; + ULONG Flags; + ULONG NextCookieSequenceNumber; + ULONG_PTR StackId; +} ACTIVATION_CONTEXT_STACK, *PACTIVATION_CONTEXT_STACK; + +typedef struct _GDI_TEB_BATCH { + ULONG Offset; + HANDLE HDC; + ULONG Buffer[0x136]; +} GDI_TEB_BATCH; + +typedef struct _TEB_ACTIVE_FRAME_CONTEXT { + ULONG Flags; + const char* FrameName; +} TEB_ACTIVE_FRAME_CONTEXT, *PTEB_ACTIVE_FRAME_CONTEXT; + +typedef struct _TEB_ACTIVE_FRAME { + ULONG Flags; + struct _TEB_ACTIVE_FRAME* Previous; + TEB_ACTIVE_FRAME_CONTEXT* Context; +} TEB_ACTIVE_FRAME, *PTEB_ACTIVE_FRAME; + +typedef struct _TEB { /* win32/win64 */ + NT_TIB Tib; /* 000/0000 */ + PVOID EnvironmentPointer; /* 01c/0038 */ + CLIENT_ID ClientId; /* 020/0040 */ + PVOID ActiveRpcHandle; /* 028/0050 */ + PVOID ThreadLocalStoragePointer; /* 02c/0058 */ + PPEB Peb; /* 030/0060 */ + ULONG LastErrorValue; /* 034/0068 */ + ULONG CountOfOwnedCriticalSections; /* 038/006c */ + PVOID CsrClientThread; /* 03c/0070 */ + PVOID Win32ThreadInfo; /* 040/0078 */ + ULONG User32Reserved[26]; /* 044/0080 */ + ULONG UserReserved[5]; /* 0ac/00e8 */ + PVOID WOW32Reserved; /* 0c0/0100 */ + ULONG CurrentLocale; /* 0c4/0108 */ + ULONG FpSoftwareStatusRegister; /* 0c8/010c */ + PVOID ReservedForDebuggerInstrumentation[16]; /* 0cc/0110 */ +#ifdef _WIN64 + PVOID SystemReserved1[30]; /* /0190 */ +#else + PVOID SystemReserved1[26]; /* 10c/ */ +#endif + char PlaceholderCompatibilityMode; /* 174/0280 */ + BOOLEAN PlaceholderHydrationAlwaysExplicit; /* 175/0281 */ + char PlaceholderReserved[10]; /* 176/0282 */ + DWORD ProxiedProcessId; /* 180/028c */ + ACTIVATION_CONTEXT_STACK ActivationContextStack; /* 184/0290 */ + UCHAR WorkingOnBehalfOfTicket[8]; /* 19c/02b8 */ + LONG ExceptionCode; /* 1a4/02c0 */ + ACTIVATION_CONTEXT_STACK* ActivationContextStackPointer; /* 1a8/02c8 */ + ULONG_PTR InstrumentationCallbackSp; /* 1ac/02d0 */ + ULONG_PTR InstrumentationCallbackPreviousPc; /* 1b0/02d8 */ + ULONG_PTR InstrumentationCallbackPreviousSp; /* 1b4/02e0 */ +#ifdef _WIN64 + ULONG TxFsContext; /* /02e8 */ + BOOLEAN InstrumentationCallbackDisabled; /* /02ec */ + BOOLEAN UnalignedLoadStoreExceptions; /* /02ed */ +#else + BOOLEAN InstrumentationCallbackDisabled; /* 1b8/ */ + BYTE SpareBytes1[23]; /* 1b9/ */ + ULONG TxFsContext; /* 1d0/ */ +#endif + GDI_TEB_BATCH GdiTebBatch; /* 1d4/02f0 */ + CLIENT_ID RealClientId; /* 6b4/07d8 */ + HANDLE GdiCachedProcessHandle; /* 6bc/07e8 */ + ULONG GdiClientPID; /* 6c0/07f0 */ + ULONG GdiClientTID; /* 6c4/07f4 */ + PVOID GdiThreadLocaleInfo; /* 6c8/07f8 */ + ULONG_PTR Win32ClientInfo[62]; /* 6cc/0800 */ + PVOID glDispatchTable[233]; /* 7c4/09f0 */ + PVOID glReserved1[29]; /* b68/1138 */ + PVOID glReserved2; /* bdc/1220 */ + PVOID glSectionInfo; /* be0/1228 */ + PVOID glSection; /* be4/1230 */ + PVOID glTable; /* be8/1238 */ + PVOID glCurrentRC; /* bec/1240 */ + PVOID glContext; /* bf0/1248 */ + ULONG LastStatusValue; /* bf4/1250 */ + UNICODE_STRING StaticUnicodeString; /* bf8/1258 */ + WCHAR StaticUnicodeBuffer[261]; /* c00/1268 */ + PVOID DeallocationStack; /* e0c/1478 */ + PVOID TlsSlots[64]; /* e10/1480 */ + LIST_ENTRY TlsLinks; /* f10/1680 */ + PVOID Vdm; /* f18/1690 */ + PVOID ReservedForNtRpc; /* f1c/1698 */ + PVOID DbgSsReserved[2]; /* f20/16a0 */ + ULONG HardErrorMode; /* f28/16b0 */ +#ifdef _WIN64 + PVOID Instrumentation[11]; /* /16b8 */ +#else + PVOID Instrumentation[9]; /* f2c/ */ +#endif + GUID ActivityId; /* f50/1710 */ + PVOID SubProcessTag; /* f60/1720 */ + PVOID PerflibData; /* f64/1728 */ + PVOID EtwTraceData; /* f68/1730 */ + PVOID WinSockData; /* f6c/1738 */ + ULONG GdiBatchCount; /* f70/1740 */ + ULONG IdealProcessorValue; /* f74/1744 */ + ULONG GuaranteedStackBytes; /* f78/1748 */ + PVOID ReservedForPerf; /* f7c/1750 */ + PVOID ReservedForOle; /* f80/1758 */ + ULONG WaitingOnLoaderLock; /* f84/1760 */ + PVOID SavedPriorityState; /* f88/1768 */ + ULONG_PTR ReservedForCodeCoverage; /* f8c/1770 */ + PVOID ThreadPoolData; /* f90/1778 */ + PVOID* TlsExpansionSlots; /* f94/1780 */ +#ifdef _WIN64 + union { + PVOID DeallocationBStore; /* /1788 */ + PVOID* ChpeV2CpuAreaInfo; /* /1788 */ + } DUMMYUNIONNAME; + PVOID BStoreLimit; /* /1790 */ +#endif + ULONG MuiGeneration; /* f98/1798 */ + ULONG IsImpersonating; /* f9c/179c */ + PVOID NlsCache; /* fa0/17a0 */ + PVOID ShimData; /* fa4/17a8 */ + ULONG HeapVirtualAffinity; /* fa8/17b0 */ + PVOID CurrentTransactionHandle; /* fac/17b8 */ + TEB_ACTIVE_FRAME* ActiveFrame; /* fb0/17c0 */ + PVOID* FlsSlots; /* fb4/17c8 */ + PVOID PreferredLanguages; /* fb8/17d0 */ + PVOID UserPrefLanguages; /* fbc/17d8 */ + PVOID MergedPrefLanguages; /* fc0/17e0 */ + ULONG MuiImpersonation; /* fc4/17e8 */ + USHORT CrossTebFlags; /* fc8/17ec */ + USHORT SameTebFlags; /* fca/17ee */ + PVOID TxnScopeEnterCallback; /* fcc/17f0 */ + PVOID TxnScopeExitCallback; /* fd0/17f8 */ + PVOID TxnScopeContext; /* fd4/1800 */ + ULONG LockCount; /* fd8/1808 */ + LONG WowTebOffset; /* fdc/180c */ + PVOID ResourceRetValue; /* fe0/1810 */ + PVOID ReservedForWdf; /* fe4/1818 */ + ULONGLONG ReservedForCrt; /* fe8/1820 */ + GUID EffectiveContainerId; /* ff0/1828 */ +} TEB, *PTEB; +static_assert(offsetof(TEB, DeallocationStack) == + 0x1478); /* The only member we care about at the moment */ + +/* +typedef enum _QUEUE_USER_APC_FLAGS { + QueueUserApcFlagsNone, + QueueUserApcFlagsSpecialUserApc, + QueueUserApcFlagsMaxValue +} QUEUE_USER_APC_FLAGS; */ + +typedef union _USER_APC_OPTION { + ULONG_PTR UserApcFlags; + HANDLE MemoryReserveHandle; +} USER_APC_OPTION, *PUSER_APC_OPTION; + +using PPS_APC_ROUTINE = void (*)(PVOID ApcArgument1, PVOID ApcArgument2, PVOID ApcArgument3, + PCONTEXT Context); + +typedef u64(__stdcall* NtClose_t)(HANDLE Handle); + +typedef u64(__stdcall* NtSetInformationFile_t)(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, + PVOID FileInformation, ULONG Length, + FILE_INFORMATION_CLASS FileInformationClass); + +typedef u64(__stdcall* NtCreateThread_t)(PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, + PCOBJECT_ATTRIBUTES ObjectAttributes, HANDLE ProcessHandle, + PCLIENT_ID ClientId, PCONTEXT ThreadContext, + PINITIAL_TEB InitialTeb, BOOLEAN CreateSuspended); + +typedef u64(__stdcall* NtTerminateThread_t)(HANDLE ThreadHandle, u64 ExitStatus); + +typedef u64(__stdcall* NtQueueApcThreadEx_t)(HANDLE ThreadHandle, + USER_APC_OPTION UserApcReserveHandle, + PPS_APC_ROUTINE ApcRoutine, PVOID ApcArgument1, + PVOID ApcArgument2, PVOID ApcArgument3); + +extern NtClose_t NtClose; +extern NtSetInformationFile_t NtSetInformationFile; +extern NtCreateThread_t NtCreateThread; +extern NtTerminateThread_t NtTerminateThread; +extern NtQueueApcThreadEx_t NtQueueApcThreadEx; + +namespace Common::NtApi { +void Initialize(); +} + +#endif diff --git a/src/pfs.h b/src/pfs.h new file mode 100644 index 0000000..e469f96 --- /dev/null +++ b/src/pfs.h @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "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]; +}; diff --git a/src/pkg.cpp b/src/pkg.cpp new file mode 100644 index 0000000..d7232cb --- /dev/null +++ b/src/pkg.cpp @@ -0,0 +1,488 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "io_file.h" +#include "pkg.h" +#include "pkg_type.h" + +namespace fmt { +template +struct UTF { + T data; + + explicit UTF(const std::u8string_view view) { + data = view.empty() ? T{} : T{(const char*)&view.front(), (const char*)&view.back() + 1}; + } + + explicit UTF(const std::u8string& str) : UTF(std::u8string_view{str}) {} +}; + +} // namespace fmt + +static void DecompressPFSC(std::span compressed_data, std::span decompressed_data) { + z_stream decompressStream; + decompressStream.zalloc = Z_NULL; + decompressStream.zfree = Z_NULL; + decompressStream.opaque = Z_NULL; + + if (inflateInit(&decompressStream) != Z_OK) { + // std::cerr << "Error initializing zlib for deflation." << std::endl; + } + + decompressStream.avail_in = compressed_data.size(); + decompressStream.next_in = reinterpret_cast(compressed_data.data()); + decompressStream.avail_out = decompressed_data.size(); + decompressStream.next_out = reinterpret_cast(decompressed_data.data()); + + if (inflate(&decompressStream, Z_FINISH)) { + } + if (inflateEnd(&decompressStream) != Z_OK) { + // std::cerr << "Error ending zlib inflate" << std::endl; + } +} + +u32 GetPFSCOffset(std::span 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; +} + +PKG::PKG() = default; + +PKG::~PKG() = default; + +bool PKG::Open(const std::filesystem::path& filepath, std::string& failreason) { + Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); + if (!file.IsOpen()) { + return false; + } + pkgSize = file.GetSize(); + + file.Read(pkgheader); + if (pkgheader.magic != 0x7F434E54) + return false; + + for (const auto& flag : flagNames) { + if (isFlagSet(pkgheader.pkg_content_flags, flag.first)) { + if (!pkgFlags.empty()) + pkgFlags += (", "); + pkgFlags += (flag.second); + } + } + + // 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); + + u32 offset = pkgheader.pkg_table_entry_offset; + u32 n_files = pkgheader.pkg_table_entry_count; + + if (!file.Seek(offset)) { + failreason = "Failed to seek to PKG table entry offset"; + return false; + } + + for (int i = 0; i < n_files; i++) { + PKGEntry entry{}; + file.Read(entry.id); + file.Read(entry.filename_offset); + file.Read(entry.flags1); + file.Read(entry.flags2); + file.Read(entry.offset); + file.Read(entry.size); + file.Seek(8, Common::FS::SeekOrigin::CurrentPosition); + + // Try to figure out the name + const auto name = GetEntryNameByType(entry.id); + if (name == "param.sfo") { + sfo.clear(); + if (!file.Seek(entry.offset)) { + failreason = "Failed to seek to param.sfo offset"; + return false; + } + sfo.resize(entry.size); + file.ReadRaw(sfo.data(), entry.size); + } + } + file.Close(); + + return true; +} + +bool PKG::Extract(const std::filesystem::path& 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(&pkgheader, sizeof(PKGHeader)); + + if (pkgheader.magic != 0x7F434E54) + return false; + + 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; + } + + u32 offset = pkgheader.pkg_table_entry_offset; + u32 n_files = pkgheader.pkg_table_entry_count; + + std::array concatenated_ivkey_dk3; + std::array seed_digest; + std::array, 7> digest1; + std::array, 7> key1; + std::array imgkeydata; + + if (!file.Seek(offset)) { + failreason = "Failed to seek to PKG table entry offset"; + return false; + } + + for (int i = 0; i < n_files; i++) { + PKGEntry entry{}; + file.Read(entry.id); + file.Read(entry.filename_offset); + file.Read(entry.flags1); + file.Read(entry.flags2); + file.Read(entry.offset); + file.Read(entry.size); + file.Seek(8, Common::FS::SeekOrigin::CurrentPosition); + + auto currentPos = file.Tell(); + + // Try to figure out the name + const auto name = GetEntryNameByType(entry.id); + const auto filepath = extract_path / "sce_sys" / name; + std::filesystem::create_directories(filepath.parent_path()); + + if (name.empty()) { + // Just print with id + Common::FS::IOFile out(extract_path / "sce_sys" / std::to_string(entry.id), + Common::FS::FileAccessMode::Write); + if (!file.Seek(entry.offset)) { + failreason = "Failed to seek to PKG entry offset"; + return false; + } + + std::vector data; + data.resize(entry.size); + file.ReadRaw(data.data(), entry.size); + out.WriteRaw(data.data(), entry.size); + out.Close(); + + file.Seek(currentPos); + continue; + } + + 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[i]); + } + + 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::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); + if (!file.Seek(entry.offset)) { + failreason = "Failed to seek to PKG entry offset"; + return false; + } + + std::vector data; + data.resize(entry.size); + file.ReadRaw(data.data(), entry.size); + out.WriteRaw(data.data(), entry.size); + out.Close(); + + // Decrypt Np stuff and overwrite. + if (entry.id == 0x400 || entry.id == 0x401 || entry.id == 0x402 || + entry.id == 0x403) { // somehow 0x401 is not decrypting + decNp.resize(entry.size); + if (!file.Seek(entry.offset)) { + failreason = "Failed to seek to PKG entry offset"; + return false; + } + + std::vector data; + data.resize(entry.size); + file.ReadRaw(data.data(), entry.size); + + std::span cipherNp(data.data(), entry.size); + std::array 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); + PKG::crypto.aesCbcCfb128DecryptEntry(ivKey, cipherNp, decNp); + + Common::FS::IOFile out(extract_path / "sce_sys" / name, + Common::FS::FileAccessMode::Write); + out.Write(decNp); + out.Close(); + } + + file.Seek(currentPos); + } + + // Read the seed + std::array seed; + if (!file.Seek(pkgheader.pfs_image_offset + 0x370)) { + failreason = "Failed to seek to PFS image offset"; + return false; + } + file.Read(seed); + + // Get data and tweak keys. + PKG::crypto.PfsGenCryptoKey(ekpfsKey, seed, dataKey, tweakKey); + const u32 length = pkgheader.pfs_cache_size * 0x2; // Seems to be ok. + + int num_blocks = 0; + std::vector pfsc(length); + if (length != 0) { + // Read encrypted pfs_image + std::vector pfs_encrypted(length); + file.Seek(pkgheader.pfs_image_offset); + file.Read(pfs_encrypted); + file.Close(); + // Decrypt the pfs_image. + std::vector 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::memcpy(pfsc.data(), pfs_decrypted.data() + pfsc_offset, length - pfsc_offset); + + PFSCHdr pfsChdr; + std::memcpy(&pfsChdr, pfsc.data(), sizeof(pfsChdr)); + + 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; + int ndinode_counter = 0; + bool dinode_reached = false; + bool uroot_reached = false; + std::vector compressedData; + std::vector decompressedData(0x10000); + + // Get iNdoes and Dirents. + 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); + } + } + + // let's deal with the root/uroot entries here. + // Sometimes it's more than 2 entries (Tomb Raider Remastered) + const std::string_view flat_path_table(&decompressedData[0x10], 15); + if (flat_path_table == "flat_path_table") { + uroot_reached = true; + } + + if (uroot_reached) { + for (int i = 0; i < 0x10000; i += ent_size) { + Dirent dirent; + std::memcpy(&dirent, &decompressedData[i], sizeof(dirent)); + ent_size = dirent.entsize; + if (dirent.ino != 0) { + ndinode_counter++; + } else { + // Set the the folder according to the current inode. + // Can be 2 or more (rarely) + auto parent_path = extract_path.parent_path(); + auto title_id = GetTitleID(); + + if (parent_path.filename() != title_id && + !fmt::UTF(extract_path.u8string()).data.ends_with("-patch")) { + extractPaths[ndinode_counter] = parent_path / title_id; + } else { + // DLCs path has different structure + extractPaths[ndinode_counter] = extract_path; + } + uroot_reached = false; + break; + } + } + } + + 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; + } + + 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_CURRENT_DIR) { + current_dir = extractPaths[table.inode]; + } + extractPaths[table.inode] = current_dir / std::filesystem::path(table.name); + + if (table.type == PFS_FILE || table.type == PFS_DIR) { + if (table.type == PFS_DIR) { // Create dirs. + std::filesystem::create_directory(extractPaths[table.inode]); + } + ndinode_counter++; + if ((ndinode_counter + 1) == ndinode) // 1 for the image itself (root). + end_reached = true; + } + } + if (end_reached) { + break; + } + } + } + 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; + + Common::FS::IOFile inflated; + inflated.Open(extractPaths[inode_number], 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 compressedData; + std::vector decompressedData(0x10000); + + u64 pfsc_buf_size = 0x11000; // extra 0x1000 + std::vector pfsc(pfsc_buf_size); + std::vector 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(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(decompressedData.data(), write_size); + } + } + pkgFile.Close(); + inflated.Close(); + } +} diff --git a/src/pkg.h b/src/pkg.h new file mode 100644 index 0000000..3e62ac0 --- /dev/null +++ b/src/pkg.h @@ -0,0 +1,179 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "crypto.h" +#include "endian.h" +#include "pfs.h" +#include "types.h" + +// #include "trp.h" + +using namespace Common; + +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]; +}; + +enum class PKGContentFlag { + FIRST_PATCH = 0x100000, + PATCHGO = 0x200000, + REMASTER = 0x400000, + PS_CLOUD = 0x800000, + GD_AC = 0x2000000, + NON_GAME = 0x4000000, + UNKNOWN_0x8000000 = 0x8000000, + SUBSEQUENT_PATCH = 0x40000000, + DELTA_PATCH = 0x41000000, + CUMULATIVE_PATCH = 0x60000000 +}; + +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::filesystem::path& filepath, std::string& failreason); + void ExtractFiles(const int index); + bool Extract(const std::filesystem::path& filepath, const std::filesystem::path& extract, + std::string& failreason); + + std::vector sfo; + + u32 GetNumberOfFiles() { + return fsTable.size(); + } + + u64 GetPkgSize() { + return pkgSize; + } + + std::string GetPkgFlags() { + return pkgFlags; + } + + std::string_view GetTitleID() { + return std::string_view(pkgTitleID, 9); + } + + PKGHeader GetPkgHeader() { + return pkgheader; + } + + static bool isFlagSet(u32_be variable, PKGContentFlag flag) { + return (variable) & static_cast(flag); + } + + static constexpr std::array, 10> flagNames = { + {{PKGContentFlag::FIRST_PATCH, "FIRST_PATCH"}, + {PKGContentFlag::PATCHGO, "PATCHGO"}, + {PKGContentFlag::REMASTER, "REMASTER"}, + {PKGContentFlag::PS_CLOUD, "PS_CLOUD"}, + {PKGContentFlag::GD_AC, "GD_AC"}, + {PKGContentFlag::NON_GAME, "NON_GAME"}, + {PKGContentFlag::UNKNOWN_0x8000000, "UNKNOWN_0x8000000"}, + {PKGContentFlag::SUBSEQUENT_PATCH, "SUBSEQUENT_PATCH"}, + {PKGContentFlag::DELTA_PATCH, "DELTA_PATCH"}, + {PKGContentFlag::CUMULATIVE_PATCH, "CUMULATIVE_PATCH"}}}; + +private: + Crypto crypto; + // TRP trp; + u64 pkgSize = 0; + char pkgTitleID[9]; + PKGHeader pkgheader; + std::string pkgFlags; + + std::unordered_map extractPaths; + std::vector fsTable; + std::vector iNodeBuf; + std::vector sectorMap; + u64 pfsc_offset; + + std::array dk3_; + std::array ivKey; + std::array imgKey; + std::array ekpfsKey; + std::array dataKey; + std::array tweakKey; + std::vector decNp; + + std::filesystem::path pkgpath; + std::filesystem::path current_dir; + std::filesystem::path extract_path; +}; diff --git a/src/pkg_type.cpp b/src/pkg_type.cpp new file mode 100644 index 0000000..464f0b9 --- /dev/null +++ b/src/pkg_type.cpp @@ -0,0 +1,638 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include "pkg_type.h" + +struct PkgEntryValue { + u32 type; + std::string_view name; + + operator u32() const noexcept { + return type; + } +}; + +constexpr static std::array 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 ""; +} diff --git a/src/pkg_type.h b/src/pkg_type.h new file mode 100644 index 0000000..5f37b15 --- /dev/null +++ b/src/pkg_type.h @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "types.h" + +/// Retrieves the PKG entry name from its type identifier. +std::string_view GetEntryNameByType(u32 type); diff --git a/src/psf.cpp b/src/psf.cpp new file mode 100644 index 0000000..81be0e5 --- /dev/null +++ b/src/psf.cpp @@ -0,0 +1,294 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +// #include "common/assert.h" +#include "io_file.h" +// #include "common/logging/log.h" +#include "psf.h" + +static const std::unordered_map psf_known_max_sizes = { + {"ACCOUNT_ID", 8}, {"CATEGORY", 4}, {"DETAIL", 1024}, {"FORMAT", 4}, + {"MAINTITLE", 128}, {"PARAMS", 1024}, {"SAVEDATA_BLOCKS", 8}, {"SAVEDATA_DIRECTORY", 32}, + {"SUBTITLE", 128}, {"TITLE_ID", 12}, +}; +static inline u32 get_max_size(std::string_view key, u32 default_value) { + if (const auto& v = psf_known_max_sizes.find(key); v != psf_known_max_sizes.end()) { + return v->second; + } + return default_value; +} + +bool PSF::Open(const std::filesystem::path& filepath) { + using namespace std::chrono; + if (std::filesystem::exists(filepath)) { + const auto t = std::filesystem::last_write_time(filepath); + const auto rel = + duration_cast(t - std::filesystem::file_time_type::clock::now()).count(); + const auto tp = system_clock::to_time_t(system_clock::now() + seconds{rel}); + last_write = system_clock::from_time_t(tp); + } + + Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); + if (!file.IsOpen()) { + return false; + } + + const u64 psfSize = file.GetSize(); + std::vector psf(psfSize); + file.Seek(0); + file.Read(psf); + file.Close(); + return Open(psf); +} + +bool PSF::Open(const std::vector& psf_buffer) { + const u8* psf_data = psf_buffer.data(); + + entry_list.clear(); + map_binaries.clear(); + map_strings.clear(); + map_integers.clear(); + + // Parse file contents + PSFHeader header{}; + std::memcpy(&header, psf_data, sizeof(header)); + + if (header.magic != PSF_MAGIC) { + // LOG_ERROR(Core, "Invalid PSF magic number"); + return false; + } + if (header.version != PSF_VERSION_1_1 && header.version != PSF_VERSION_1_0) { + // LOG_ERROR(Core, "Unsupported PSF version: 0x{:08x}", header.version); + return false; + } + + for (u32 i = 0; i < header.index_table_entries; i++) { + PSFRawEntry raw_entry{}; + std::memcpy(&raw_entry, psf_data + sizeof(PSFHeader) + i * sizeof(PSFRawEntry), + sizeof(raw_entry)); + + Entry& entry = entry_list.emplace_back(); + entry.key = std::string{(char*)(psf_data + header.key_table_offset + raw_entry.key_offset)}; + entry.param_fmt = static_cast(raw_entry.param_fmt.Raw()); + entry.max_len = raw_entry.param_max_len; + + const u8* data = psf_data + header.data_table_offset + raw_entry.data_offset; + + switch (entry.param_fmt) { + case PSFEntryFmt::Binary: { + std::vector value(raw_entry.param_len); + std::memcpy(value.data(), data, raw_entry.param_len); + map_binaries.emplace(i, std::move(value)); + } break; + case PSFEntryFmt::Text: { + std::string c_str{reinterpret_cast(data)}; + map_strings.emplace(i, std::move(c_str)); + } break; + case PSFEntryFmt::Integer: { + // ASSERT_MSG(raw_entry.param_len == sizeof(s32), "PSF integer entry size mismatch"); + s32 integer = *(s32*)data; + map_integers.emplace(i, integer); + } break; + default: + // UNREACHABLE_MSG("Unknown PSF entry format"); + break; + } + } + return true; +} + +bool PSF::Encode(const std::filesystem::path& filepath) const { + Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Write); + if (!file.IsOpen()) { + return false; + } + + last_write = std::chrono::system_clock::now(); + + const auto psf_buffer = Encode(); + const size_t written = file.Write(psf_buffer); + if (written != psf_buffer.size()) { + // LOG_ERROR(Core, "Failed to write PSF file. Written {} Expected {}", written, + // psf_buffer.size()); + } + return written == psf_buffer.size(); +} + +std::vector PSF::Encode() const { + std::vector psf_buffer; + Encode(psf_buffer); + return psf_buffer; +} + +void PSF::Encode(std::vector& psf_buffer) const { + psf_buffer.resize(sizeof(PSFHeader) + sizeof(PSFRawEntry) * entry_list.size()); + + { + auto& header = *(PSFHeader*)psf_buffer.data(); + header.magic = PSF_MAGIC; + header.version = PSF_VERSION_1_1; + header.index_table_entries = entry_list.size(); + } + + const size_t key_table_offset = psf_buffer.size(); + ((PSFHeader*)psf_buffer.data())->key_table_offset = key_table_offset; + for (size_t i = 0; i < entry_list.size(); i++) { + auto& raw_entry = ((PSFRawEntry*)(psf_buffer.data() + sizeof(PSFHeader)))[i]; + const Entry& entry = entry_list[i]; + raw_entry.key_offset = psf_buffer.size() - key_table_offset; + raw_entry.param_fmt.FromRaw(static_cast(entry.param_fmt)); + raw_entry.param_max_len = entry.max_len; + std::ranges::copy(entry.key, std::back_inserter(psf_buffer)); + psf_buffer.push_back(0); // NULL terminator + } + + const size_t data_table_offset = psf_buffer.size(); + ((PSFHeader*)psf_buffer.data())->data_table_offset = data_table_offset; + for (size_t i = 0; i < entry_list.size(); i++) { + if (psf_buffer.size() % 4 != 0) { + std::ranges::fill_n(std::back_inserter(psf_buffer), 4 - psf_buffer.size() % 4, 0); + } + auto& raw_entry = ((PSFRawEntry*)(psf_buffer.data() + sizeof(PSFHeader)))[i]; + const Entry& entry = entry_list[i]; + raw_entry.data_offset = psf_buffer.size() - data_table_offset; + + s32 additional_padding = s32(raw_entry.param_max_len); + + switch (entry.param_fmt) { + case PSFEntryFmt::Binary: { + const auto& value = map_binaries.at(i); + raw_entry.param_len = value.size(); + additional_padding -= s32(raw_entry.param_len); + std::ranges::copy(value, std::back_inserter(psf_buffer)); + } break; + case PSFEntryFmt::Text: { + const auto& value = map_strings.at(i); + raw_entry.param_len = value.size() + 1; + additional_padding -= s32(raw_entry.param_len); + std::ranges::copy(value, std::back_inserter(psf_buffer)); + psf_buffer.push_back(0); // NULL terminator + } break; + case PSFEntryFmt::Integer: { + const auto& value = map_integers.at(i); + raw_entry.param_len = sizeof(s32); + additional_padding -= s32(raw_entry.param_len); + const auto value_bytes = reinterpret_cast(&value); + std::ranges::copy(value_bytes, value_bytes + sizeof(s32), + std::back_inserter(psf_buffer)); + } break; + default: + // UNREACHABLE_MSG("Unknown PSF entry format"); + break; + } + // ASSERT_MSG(additional_padding >= 0, "PSF entry max size mismatch"); + std::ranges::fill_n(std::back_inserter(psf_buffer), additional_padding, 0); + } +} + +std::optional> PSF::GetBinary(std::string_view key) const { + const auto& [it, index] = FindEntry(key); + if (it == entry_list.end()) { + return {}; + } + // ASSERT(it->param_fmt == PSFEntryFmt::Binary); + return std::span{map_binaries.at(index)}; +} + +std::optional PSF::GetString(std::string_view key) const { + const auto& [it, index] = FindEntry(key); + if (it == entry_list.end()) { + return {}; + } + // ASSERT(it->param_fmt == PSFEntryFmt::Text); + return std::string_view{map_strings.at(index)}; +} + +std::optional PSF::GetInteger(std::string_view key) const { + const auto& [it, index] = FindEntry(key); + if (it == entry_list.end()) { + return {}; + } + // ASSERT(it->param_fmt == PSFEntryFmt::Integer); + return map_integers.at(index); +} + +void PSF::AddBinary(std::string key, std::vector value, bool update) { + auto [it, index] = FindEntry(key); + bool exist = it != entry_list.end(); + if (exist && !update) { + // LOG_ERROR(Core, "PSF: Tried to add binary key that already exists: {}", key); + return; + } + if (exist) { + // ASSERT_MSG(it->param_fmt == PSFEntryFmt::Binary, "PSF: Change format is not supported"); + it->max_len = get_max_size(key, value.size()); + map_binaries.at(index) = std::move(value); + return; + } + Entry& entry = entry_list.emplace_back(); + entry.max_len = get_max_size(key, value.size()); + entry.key = std::move(key); + entry.param_fmt = PSFEntryFmt::Binary; + map_binaries.emplace(entry_list.size() - 1, std::move(value)); +} + +void PSF::AddBinary(std::string key, uint64_t value, bool update) { + std::vector data(8); + std::memcpy(data.data(), &value, 8); + return AddBinary(std::move(key), std::move(data), update); +} + +void PSF::AddString(std::string key, std::string value, bool update) { + auto [it, index] = FindEntry(key); + bool exist = it != entry_list.end(); + if (exist && !update) { + // LOG_ERROR(Core, "PSF: Tried to add string key that already exists: {}", key); + return; + } + if (exist) { + // ASSERT_MSG(it->param_fmt == PSFEntryFmt::Text, "PSF: Change format is not supported"); + it->max_len = get_max_size(key, value.size() + 1); + map_strings.at(index) = std::move(value); + return; + } + Entry& entry = entry_list.emplace_back(); + entry.max_len = get_max_size(key, value.size() + 1); + entry.key = std::move(key); + entry.param_fmt = PSFEntryFmt::Text; + map_strings.emplace(entry_list.size() - 1, std::move(value)); +} + +void PSF::AddInteger(std::string key, s32 value, bool update) { + auto [it, index] = FindEntry(key); + bool exist = it != entry_list.end(); + if (exist && !update) { + // LOG_ERROR(Core, "PSF: Tried to add integer key that already exists: {}", key); + return; + } + if (exist) { + // ASSERT_MSG(it->param_fmt == PSFEntryFmt::Integer, "PSF: Change format is not supported"); + it->max_len = sizeof(s32); + map_integers.at(index) = value; + return; + } + Entry& entry = entry_list.emplace_back(); + entry.key = std::move(key); + entry.param_fmt = PSFEntryFmt::Integer; + entry.max_len = sizeof(s32); + map_integers.emplace(entry_list.size() - 1, value); +} + +std::pair::iterator, size_t> PSF::FindEntry(std::string_view key) { + auto entry = + std::ranges::find_if(entry_list, [&](const auto& entry) { return entry.key == key; }); + return {entry, std::distance(entry_list.begin(), entry)}; +} + +std::pair::const_iterator, size_t> PSF::FindEntry( + std::string_view key) const { + auto entry = + std::ranges::find_if(entry_list, [&](const auto& entry) { return entry.key == key; }); + return {entry, std::distance(entry_list.begin(), entry)}; +} diff --git a/src/psf.h b/src/psf.h new file mode 100644 index 0000000..c38f61a --- /dev/null +++ b/src/psf.h @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "endian.h" + +constexpr u32 PSF_MAGIC = 0x00505346; +constexpr u32 PSF_VERSION_1_1 = 0x00000101; +constexpr u32 PSF_VERSION_1_0 = 0x00000100; + +struct PSFHeader { + u32_be magic; + u32_le version; + u32_le key_table_offset; + u32_le data_table_offset; + u32_le index_table_entries; +}; +static_assert(sizeof(PSFHeader) == 0x14); + +struct PSFRawEntry { + u16_le key_offset; + u16_be param_fmt; + u32_le param_len; + u32_le param_max_len; + u32_le data_offset; +}; +static_assert(sizeof(PSFRawEntry) == 0x10); + +enum class PSFEntryFmt : u16 { + Binary = 0x0004, // Binary data + Text = 0x0204, // String in UTF-8 format and NULL terminated + Integer = 0x0404, // Signed 32-bit integer +}; + +class PSF { + struct Entry { + std::string key; + PSFEntryFmt param_fmt; + u32 max_len; + }; + +public: + PSF() = default; + ~PSF() = default; + + PSF(const PSF& other) = default; + PSF(PSF&& other) noexcept = default; + PSF& operator=(const PSF& other) = default; + PSF& operator=(PSF&& other) noexcept = default; + + bool Open(const std::filesystem::path& filepath); + bool Open(const std::vector& psf_buffer); + + [[nodiscard]] std::vector Encode() const; + void Encode(std::vector& buf) const; + bool Encode(const std::filesystem::path& filepath) const; + + std::optional> GetBinary(std::string_view key) const; + std::optional GetString(std::string_view key) const; + std::optional GetInteger(std::string_view key) const; + + void AddBinary(std::string key, std::vector value, bool update = false); + void AddBinary(std::string key, uint64_t value, bool update = false); // rsv4 format + void AddString(std::string key, std::string value, bool update = false); + void AddInteger(std::string key, s32 value, bool update = false); + + [[nodiscard]] std::chrono::system_clock::time_point GetLastWrite() const { + return last_write; + } + + [[nodiscard]] const std::vector& GetEntries() const { + return entry_list; + } + +private: + mutable std::chrono::system_clock::time_point last_write; + + std::vector entry_list; + + std::unordered_map> map_binaries; + std::unordered_map map_strings; + std::unordered_map map_integers; + + [[nodiscard]] std::pair::iterator, size_t> FindEntry(std::string_view key); + [[nodiscard]] std::pair::const_iterator, size_t> FindEntry( + std::string_view key) const; +}; diff --git a/src/types.h b/src/types.h new file mode 100644 index 0000000..c2b7bd3 --- /dev/null +++ b/src/types.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +using s8 = std::int8_t; +using s16 = std::int16_t; +using s32 = std::int32_t; +using s64 = std::int64_t; + +using u8 = std::uint8_t; +using u16 = std::uint16_t; +using u32 = std::uint32_t; +using u64 = std::uint64_t; + +using f32 = float; +using f64 = double; + +using u128 = std::array; +static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide"); + +using VAddr = uintptr_t; +using PAddr = uintptr_t; + +#define PS4_SYSV_ABI __attribute__((sysv_abi)) + +// UDLs for memory size values +constexpr unsigned long long operator""_KB(unsigned long long x) { + return 1024ULL * x; +} +constexpr unsigned long long operator""_MB(unsigned long long x) { + return 1024_KB * x; +} +constexpr unsigned long long operator""_GB(unsigned long long x) { + return 1024_MB * x; +}