Drop the wx GUI application and the standalone's GitHub workflows

This repository builds the libretro core; the standalone wx application was
built from it only by upstream's workflows. Gone: src/gui/wxgui, the CemuBin
target with its macOS bundle and Windows resources, the wx/GTK and SDL vcpkg
features, dist/linux and dist/windows, the GUI's translations in
bin/resources (the Wii U shared fonts there stay - the core loads them),
src/tools, CMakeSettings.json, and the Build Cemu, Build check, Deploy
release, Calculate Next Version and Generate translation template workflows.
ENABLE_LIBRETRO is now always on; it stays a variable because the rest of the
build, shared with upstream, still asks for it.

At the next upstream sync, a modify/delete conflict on any of these is
resolved by keeping the file deleted.

Built on all seven targets plus iOS and tvOS.
This commit is contained in:
WizzardSK committed 2026-09-26 10:45:33 +02:00
1 parent 627802831c
commit b05c7a51a7
157 files changed
+1 -33335

No files matched your search

-302
View File
@@ -1,302 +0,0 @@
name: Build Cemu
on:
workflow_call:
inputs:
next_version_major:
required: false
type: string
next_version_minor:
required: false
type: string
env:
VCPKG_ROOT: "${{github.workspace}}/dependencies/vcpkg"
# No nuget binary cache: this fork does not publish packages, and the one it
# had was 537 vcpkg artifacts nothing here reads - the libretro core build in
# libretro.yml does not use it.
VCPKG_BINARY_SOURCES: 'clear'
VCPKG_FORCE_DOWNLOADED_BINARIES: true
jobs:
build-ubuntu:
continue-on-error: true
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
arch: x64
- os: ubuntu-22.04-arm
arch: arm
name: build-ubuntu-${{ matrix.arch }}
runs-on: ${{ matrix.os }}
steps:
- name: "Checkout repo"
uses: actions/checkout@v6
with:
submodules: "recursive"
fetch-depth: 0
- name: Setup release mode parameters
run: |
echo "BUILD_MODE=release" >> $GITHUB_ENV
echo "BUILD_FLAGS=" >> $GITHUB_ENV
echo "Build mode is release"
- name: Setup build flags for version
if: ${{ inputs.next_version_major != '' }}
run: |
echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" >> $GITHUB_ENV
- name: "Install system dependencies"
run: |
sudo apt update -qq
sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build libbluetooth-dev
- name: "Bootstrap vcpkg"
run: |
bash ./dependencies/vcpkg/bootstrap-vcpkg.sh
- name: 'Setup NuGet Credentials for vcpkg'
shell: 'bash'
run: |
mono `./dependencies/vcpkg/vcpkg fetch nuget | tail -n 1` \
sources add \
-source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \
-storepasswordincleartext \
-name "GitHub" \
-username "${{ github.repository_owner }}" \
-password "${{ secrets.GITHUB_TOKEN }}"
mono `./dependencies/vcpkg/vcpkg fetch nuget | tail -n 1` \
setapikey "${{ secrets.GITHUB_TOKEN }}" \
-source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json"
- name: "cmake"
run: |
cmake -S . -B build ${{ env.BUILD_FLAGS }} -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja
- name: "Build Cemu"
run: |
cmake --build build
- name: Prepare artifact
run: mv bin/Cemu_release bin/Cemu
- name: Upload artifact
uses: actions/upload-artifact@v6
with:
name: cemu-bin-linux-${{ matrix.arch }}
path: ./bin/Cemu
build-appimage:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
arch: x64
- os: ubuntu-22.04-arm
arch: arm
name: build-appimage-${{ matrix.arch }}
runs-on: ${{ matrix.os }}
needs: build-ubuntu
steps:
- name: Checkout Upstream Repo
uses: actions/checkout@v6
- uses: actions/download-artifact@v8
with:
name: cemu-bin-linux-${{ matrix.arch }}
path: bin
- name: "Install system dependencies"
run: |
sudo apt update -qq
sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build appstream libbluetooth-dev
- name: "Build AppImage"
run: |
export LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH"
export DEPLOY_GTK_VERSION=3
dist/linux/appimage.sh ${{ runner.arch }}
- name: Upload artifact
uses: actions/upload-artifact@v6
with:
name: cemu-appimage-${{ matrix.arch }}
path: artifacts
build-windows:
runs-on: windows-2022
steps:
- name: "Checkout repo"
uses: actions/checkout@v6
with:
submodules: "recursive"
- name: Setup release mode parameters
run: |
echo "BUILD_MODE=release" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
echo "BUILD_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
echo "Build mode is release"
- name: Setup build flags for version
if: ${{ inputs.next_version_major != '' }}
run: |
echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
- name: "Setup cmake"
uses: jwlawson/actions-setup-cmake@v2
with:
cmake-version: '3.29.0'
- name: "Bootstrap vcpkg"
run: |
./dependencies/vcpkg/bootstrap-vcpkg.bat
- name: 'Setup NuGet Credentials for vcpkg'
shell: 'bash'
run: |
`./dependencies/vcpkg/vcpkg.exe fetch nuget | tail -n 1` \
sources add \
-source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \
-storepasswordincleartext \
-name "GitHub" \
-username "${{ github.repository_owner }}" \
-password "${{ secrets.GITHUB_TOKEN }}"
`./dependencies/vcpkg/vcpkg.exe fetch nuget | tail -n 1` \
setapikey "${{ secrets.GITHUB_TOKEN }}" \
-source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json"
- name: "cmake"
run: |
mkdir -p build
cd build
echo "[INFO] BUILD_FLAGS: ${{ env.BUILD_FLAGS }}"
echo "[INFO] BUILD_MODE: ${{ env.BUILD_MODE }}"
cmake .. ${{ env.BUILD_FLAGS }} -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} -DVCPKG_INSTALL_OPTIONS="--clean-after-build"
- name: "Build Cemu"
run: |
cd build
cmake --build . --config ${{ env.BUILD_MODE }}
- name: Prepare artifact
run: Rename-Item bin/Cemu_release.exe Cemu.exe
- name: Build NSIS Installer
shell: cmd
run: |
cd src\resource
makensis /DPRODUCT_VERSION=${{ inputs.next_version_major }}.${{ inputs.next_version_minor }} installer.nsi
- name: Upload artifact
uses: actions/upload-artifact@v6
with:
name: cemu-bin-windows-x64
path: ./bin/Cemu.exe
- name: Upload NSIS Installer
uses: actions/upload-artifact@v6
with:
name: cemu-installer-windows-x64
path: ./src/resource/cemu-${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}-windows-x64-installer.exe
build-macos:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
arch: [x86_64, arm64]
steps:
- name: "Checkout repo"
uses: actions/checkout@v6
with:
submodules: "recursive"
- name: Setup release mode parameters
run: |
echo "BUILD_MODE=release" >> $GITHUB_ENV
echo "BUILD_FLAGS=" >> $GITHUB_ENV
echo "Build mode is release"
- name: Setup build flags for version
if: ${{ inputs.next_version_major != '' }}
run: |
echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" >> $GITHUB_ENV
- name: "Install system dependencies"
run: |
# Removing azure/bicep and aws to silence warnings in Github Actions
brew uninstall bicep
brew untap aws/tap azure/bicep
brew update
brew install nasm automake libtool
- name: "Install molten-vk"
run: |
curl -L -O https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-macos-privateapi.tar
tar xf MoltenVK-macos-privateapi.tar
sudo mkdir -p /usr/local/lib
sudo cp MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib /usr/local/lib
- name: "Setup cmake"
uses: jwlawson/actions-setup-cmake@v2
with:
cmake-version: '3.29.0'
- name: "Bootstrap vcpkg"
run: |
bash ./dependencies/vcpkg/bootstrap-vcpkg.sh
- name: 'Setup NuGet Credentials for vcpkg'
shell: 'bash'
run: |
mono `./dependencies/vcpkg/vcpkg fetch nuget | tail -n 1` \
sources add \
-source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \
-storepasswordincleartext \
-name "GitHub" \
-username "${{ github.repository_owner }}" \
-password "${{ secrets.GITHUB_TOKEN }}"
mono `./dependencies/vcpkg/vcpkg fetch nuget | tail -n 1` \
setapikey "${{ secrets.GITHUB_TOKEN }}" \
-source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json"
- name: "cmake"
run: |
mkdir build
cd build
cmake .. ${{ env.BUILD_FLAGS }} \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} \
-DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} \
-DMACOS_BUNDLE=ON \
-G Ninja
- name: "Build Cemu"
run: |
cmake --build build
- name: Prepare artifact
run: |
mkdir bin/Cemu_app
mv bin/Cemu_release.app bin/Cemu_app/Cemu.app
mv bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu_release bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu
sed -i '' 's/Cemu_release/Cemu/g' bin/Cemu_app/Cemu.app/Contents/Info.plist
codesign --entitlements ${{github.workspace}}/src/resource/cemu.macos.entitlements --force --deep --preserve-metadata=entitlements,requirements,flags,runtime --sign - --timestamp --options runtime bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu
ln -s /Applications bin/Cemu_app/Applications
hdiutil create ./bin/tmp.dmg -ov -volname "Cemu" -fs HFS+ -srcfolder "./bin/Cemu_app"
hdiutil convert ./bin/tmp.dmg -format UDZO -o bin/Cemu.dmg
rm bin/tmp.dmg
- name: Upload artifact
uses: actions/upload-artifact@v6
with:
name: cemu-bin-macos-${{ matrix.arch }}
path: ./bin/Cemu.dmg
-18
View File
@@ -1,18 +0,0 @@
name: Build check
on:
pull_request:
paths-ignore:
- "*.md"
types:
- opened
- synchronize
- reopened
push:
paths-ignore:
- "*.md"
branches:
- main
jobs:
build:
uses: ./.github/workflows/build.yml
-164
View File
@@ -1,164 +0,0 @@
name: Deploy release
on:
workflow_dispatch:
inputs:
changelog0:
description: 'Enter the changelog lines for this release. Each line is a feature / bullet point. Do not use dash.'
required: true
type: string
changelog1:
description: 'Feature 2'
required: false
type: string
changelog2:
description: 'Feature 3'
required: false
type: string
changelog3:
description: 'Feature 4'
required: false
type: string
changelog4:
description: 'Feature 5'
required: false
type: string
changelog5:
description: 'Feature 6'
required: false
type: string
changelog6:
description: 'Feature 7'
required: false
type: string
changelog7:
description: 'Feature 8'
required: false
type: string
changelog8:
description: 'Feature 9'
required: false
type: string
changelog9:
description: 'Feature 10'
required: false
type: string
jobs:
calculate-version:
name: Calculate Version
uses: ./.github/workflows/determine_release_version.yml
call-release-build:
uses: ./.github/workflows/build.yml
needs: calculate-version
with:
next_version_major: ${{ needs.calculate-version.outputs.next_version_major }}
next_version_minor: ${{ needs.calculate-version.outputs.next_version_minor }}
deploy:
name: Deploy release
runs-on: ubuntu-22.04
needs: [call-release-build, calculate-version]
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Generate changelog
id: generate_changelog
run: |
CHANGELOG=""
if [ -n "${{ github.event.inputs.changelog0 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog0 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog1 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog1 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog2 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog2 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog3 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog3 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog4 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog4 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog5 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog5 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog6 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog6 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog7 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog7 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog8 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog8 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog9 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog9 }}\n"; fi
echo -e "$CHANGELOG"
echo "RELEASE_BODY=$CHANGELOG" >> $GITHUB_ENV
- uses: actions/download-artifact@v8
with:
name: cemu-bin-linux-x64
path: cemu-bin-linux-x64
- uses: actions/download-artifact@v8
with:
name: cemu-appimage-x64
path: cemu-appimage-x64
- uses: actions/download-artifact@v8
with:
name: cemu-bin-windows-x64
path: cemu-bin-windows-x64
- uses: actions/download-artifact@v8
with:
name: cemu-installer-windows-x64
path: cemu-installer-windows-x64
- uses: actions/download-artifact@v8
with:
pattern: cemu-bin-macos*
path: cemu-macos
- name: Initialize
run: |
mkdir upload
sudo apt install zip
- name: Set version dependent vars
run: |
echo "Version: ${{ needs.calculate-version.outputs.next_version }}"
echo "CEMU_FOLDER_NAME=Cemu_${{ needs.calculate-version.outputs.next_version }}"
echo "CEMU_VERSION=${{ needs.calculate-version.outputs.next_version }}"
echo "CEMU_FOLDER_NAME=Cemu_${{ needs.calculate-version.outputs.next_version }}" >> $GITHUB_ENV
echo "CEMU_VERSION=${{ needs.calculate-version.outputs.next_version }}" >> $GITHUB_ENV
- name: Create release from windows-bin
run: |
ls ./
ls ./bin/
cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }}
mv cemu-bin-windows-x64/Cemu.exe ./${{ env.CEMU_FOLDER_NAME }}/Cemu.exe
zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-windows-x64.zip ${{ env.CEMU_FOLDER_NAME }}
rm -r ./${{ env.CEMU_FOLDER_NAME }}
- name: Create release from windows-installer
run: cp cemu-installer-windows-x64/cemu-${{ env.CEMU_VERSION }}-windows-x64-installer.exe upload/cemu-${{ env.CEMU_VERSION }}-windows-x64-installer.exe
- name: Create appimage
run: |
VERSION=${{ env.CEMU_VERSION }}
echo "Cemu Version is $VERSION"
ls cemu-appimage-x64
mv cemu-appimage-x64/Cemu-*-x86_64.AppImage upload/Cemu-$VERSION-x86_64.AppImage
- name: Create release from linux-bin
run: |
ls ./
ls ./bin/
cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }}
mv cemu-bin-linux-x64/Cemu ./${{ env.CEMU_FOLDER_NAME }}/Cemu
zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-ubuntu-22.04-x64.zip ${{ env.CEMU_FOLDER_NAME }}
rm -r ./${{ env.CEMU_FOLDER_NAME }}
- name: Create release from macos-bin
run: |
cd cemu-macos
for bin_dir in cemu-bin-macos-*; do
arch="${bin_dir##cemu-bin-macos-}"
cp $bin_dir/Cemu.dmg ../upload/cemu-${{ env.CEMU_VERSION }}-macos-12-$arch.dmg
done
- name: Create release
run: |
wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.15.0/ghr_v0.15.0_linux_amd64.tar.gz
tar xvzf ghr.tar.gz; rm ghr.tar.gz
echo "[INFO] Release tag: v${{ env.CEMU_VERSION }}"
CHANGELOG_UNESCAPED=$(printf "%s\n" "${{ env.RELEASE_BODY }}" | sed 's/\\n/\n/g')
RELEASE_BODY=$(printf "%s\n%s" \
"**Changelog:**" \
"$CHANGELOG_UNESCAPED")
ghr_v0.15.0_linux_amd64/ghr -draft -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }}" -b "$RELEASE_BODY" "v${{ env.CEMU_VERSION }}" ./upload
@@ -1,74 +0,0 @@
name: Calculate Next Version from release history
on:
workflow_dispatch:
workflow_call:
outputs:
next_version:
description: "The next semantic version"
value: ${{ jobs.calculate-version.outputs.next_version }}
next_version_major:
description: "The next semantic version (major)"
value: ${{ jobs.calculate-version.outputs.next_version_major }}
next_version_minor:
description: "The next semantic version (minor)"
value: ${{ jobs.calculate-version.outputs.next_version_minor }}
jobs:
calculate-version:
runs-on: ubuntu-latest
outputs:
next_version: ${{ steps.calculate_next_version.outputs.next_version }}
next_version_major: ${{ steps.calculate_next_version.outputs.next_version_major }}
next_version_minor: ${{ steps.calculate_next_version.outputs.next_version_minor }}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Get all releases
id: get_all_releases
run: |
# Fetch all releases and check for API errors
RESPONSE=$(curl -s -o response.json -w "%{http_code}" "https://api.github.com/repos/${{ github.repository }}/releases?per_page=100")
if [ "$RESPONSE" -ne 200 ]; then
echo "Failed to fetch releases. HTTP status: $RESPONSE"
cat response.json
exit 1
fi
# Extract and sort tags
ALL_TAGS=$(jq -r '.[].tag_name' response.json | grep -E '^v[0-9]+\.[0-9]+(-[0-9]+)?$' | sed 's/-.*//' | sort -V | tail -n 1)
# Exit if no tags were found
if [ -z "$ALL_TAGS" ]; then
echo "No valid tags found."
exit 1
fi
echo "::set-output name=tag::$ALL_TAGS"
# echo "tag=$ALL_TAGS" >> $GITHUB_STATE
- name: Calculate next semver minor
id: calculate_next_version
run: |
LATEST_VERSION=${{ steps.get_all_releases.outputs.tag }}
# strip 'v' prefix and split into major.minor
LATEST_VERSION=${LATEST_VERSION//v/}
IFS='.' read -r -a VERSION_PARTS <<< "$LATEST_VERSION"
MAJOR=${VERSION_PARTS[0]}
MINOR=${VERSION_PARTS[1]}
# increment the minor version
MINOR=$((MINOR + 1))
NEXT_VERSION="${MAJOR}.${MINOR}"
echo "Major: $MAJOR"
echo "Minor: $MINOR"
echo "Next version: $NEXT_VERSION"
echo "::set-output name=next_version::$NEXT_VERSION"
echo "::set-output name=next_version_major::$MAJOR"
echo "::set-output name=next_version_minor::$MINOR"
-43
View File
@@ -1,43 +0,0 @@
name: Generate translation template
on:
pull_request:
paths-ignore:
- "*.md"
types:
- opened
- synchronize
- reopened
push:
paths-ignore:
- "*.md"
branches:
- main
jobs:
generate-pot:
runs-on: ubuntu-latest
steps:
- name: "Checkout repo"
uses: actions/checkout@v6
- name: "Install gettext"
run: |
sudo apt update -qq
sudo apt install -y gettext
- name: "Generate POT file using xgettext"
run: >
find src -name *.cpp -o -name *.hpp -o -name *.h |
xargs xgettext --from-code=utf-8 -w 100
--keyword="_" --keyword="wxTRANSLATE" --keyword="wxPLURAL:1,2"
--keyword="_tr" --keyword="TR_NOOP"
--check=space-ellipsis --omit-header
-o cemu.pot
- name: Upload artifact
uses: actions/upload-artifact@v6
with:
name: POT file
path: ./cemu.pot
if-no-files-found: error
+1 -1
View File
@@ -101,7 +101,7 @@ jobs:
- name: "Install molten-vk" - name: "Install molten-vk"
# The Vulkan renderer links against the loader; on macOS that is # The Vulkan renderer links against the loader; on macOS that is
# MoltenVK. Same release the standalone macOS build in build.yml uses. # MoltenVK, the release upstream's macOS build uses.
run: | run: |
curl -L -O https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-macos-privateapi.tar curl -L -O https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-macos-privateapi.tar
tar xf MoltenVK-macos-privateapi.tar tar xf MoltenVK-macos-privateapi.tar
-28
View File
@@ -1,28 +0,0 @@
{
"configurations": [
{
"name": "RelWithDebInfo",
"configurationType": "RelWithDebInfo",
"generator": "Ninja",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}"
},
{
"name": "Release",
"configurationType": "Release",
"generator": "Ninja",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}"
},
{
"name": "Debug",
"configurationType": "Debug",
"generator": "Ninja",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}"
}
]
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-59
View File
@@ -1,59 +0,0 @@
#!/bin/bash
if [ "$1" == 'X64' ]; then CPU_ARCH="x86_64"; else CPU_ARCH="aarch64"; fi
if [[ -z "${GITHUB_WORKSPACE}" ]]; then
export GITHUB_WORKSPACE="."
fi
curl -sSfLO "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-"${CPU_ARCH}".AppImage"
chmod a+x linuxdeploy*.AppImage
curl -sSfL https://github.com"$(curl https://github.com/probonopd/go-appimage/releases/expanded_assets/continuous | grep "mkappimage-.*-"${CPU_ARCH}".AppImage" | head -n 1 | cut -d '"' -f 2)" -o mkappimage.AppImage
chmod a+x mkappimage.AppImage
curl -sSfLO "https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh"
chmod a+x linuxdeploy-plugin-gtk.sh
curl -sSfLO "https://github.com/darealshinji/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt.sh"
chmod a+x linuxdeploy-plugin-checkrt.sh
if [[ ! -e /usr/lib/"${CPU_ARCH}"-linux-gnu ]]; then
sed -i 's#lib\/"${CPU_ARCH}"-linux-gnu#lib64#g' linuxdeploy-plugin-gtk.sh
fi
mkdir -p AppDir/usr/bin
mkdir -p AppDir/usr/share/Cemu
mkdir -p AppDir/usr/share/applications
mkdir -p AppDir/usr/share/icons/hicolor/128x128/apps
mkdir -p AppDir/usr/share/metainfo
mkdir -p AppDir/usr/lib
cp dist/linux/info.cemu.Cemu.{desktop,png} AppDir/
cp dist/linux/info.cemu.Cemu.metainfo.xml AppDir/usr/share/metainfo/info.cemu.Cemu.appdata.xml
cp -r bin/* AppDir/usr/share/Cemu
mv AppDir/usr/share/Cemu/Cemu AppDir/usr/bin/
chmod +x AppDir/usr/bin/Cemu
cp /usr/lib/"${CPU_ARCH}"-linux-gnu/{libsepol.so.1,libffi.so.7,libpcre.so.3,libGLU.so.1,libthai.so.0} AppDir/usr/lib
export UPD_INFO="gh-releases-zsync|cemu-project|Cemu|ci|Cemu.AppImage.zsync"
export NO_STRIP=1
./linuxdeploy-"${CPU_ARCH}".AppImage --appimage-extract-and-run \
--appdir="${GITHUB_WORKSPACE}"/AppDir/ \
-d "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.desktop \
-i "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.png \
-e "${GITHUB_WORKSPACE}"/AppDir/usr/bin/Cemu \
--plugin gtk \
--plugin checkrt
if ! GITVERSION="$(git rev-parse --short HEAD 2>/dev/null)"; then
GITVERSION=experimental
fi
echo "Cemu Version Cemu-${GITVERSION}"
rm AppDir/usr/lib/libwayland-client.so.0
echo -e "export LC_ALL=C\nexport FONTCONFIG_PATH=/etc/fonts" >> AppDir/apprun-hooks/linuxdeploy-plugin-gtk.sh
VERSION="${GITVERSION}" ./mkappimage.AppImage --appimage-extract-and-run "${GITHUB_WORKSPACE}"/AppDir
mkdir -p "${GITHUB_WORKSPACE}"/artifacts/
mv Cemu-"${GITVERSION}"-"${CPU_ARCH}".AppImage "${GITHUB_WORKSPACE}"/artifacts/
-27
View File
@@ -1,27 +0,0 @@
[Desktop Entry]
Name=Cemu
Type=Application
Terminal=false
Icon=info.cemu.Cemu
Exec=Cemu
GenericName=Wii U Emulator
GenericName[fi]=Wii U -emulaattori
GenericName[el]=Πρόγραμμα προσομοίωσης Wii U
GenericName[es]=Emulador de Wii U
GenericName[pt_BR]=Emulador de Wii U
GenericName[fr]=Émulateur de Wii U
GenericName[it]=Emulatore Wii U
Comment=Software to emulate Wii U games and applications on PC
Comment[fi]=Ohjelmisto Wii U -pelien ja sovellusten emulointiin PC:lla
Comment[de]=Software zum emulieren von Wii U Spielen und Anwendungen auf dem PC
Comment[ru]=Программа для эмуляции игр и приложений Wii U на PC
Comment[fr]=Application pour émuler des jeux et des applications Wii U sur PC
Comment[nl]=Applicatie om Wii U spellen en applicaties te emuleren op PC
Comment[el]=Πρόγραμμα προσομοίωσης παιχνιδιών και εφαρμογών Wii U στον υπολογιστή
Comment[es]=Software para emular juegos y aplicaciones de Wii U en PC
Comment[pt_BR]=Software para emular jogos e aplicativos de Wii U no PC
Comment[it]=Software per emulare giochi e applicazioni per Wii U su PC
Categories=Game;Emulator;
Keywords=Nintendo;
MimeType=application/x-wii-u-rom;
StartupWMClass=Cemu
-84
View File
@@ -1,84 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<component type="desktop">
<!--Created with jdAppdataEdit 4.2-->
<id>info.cemu.Cemu</id>
<name>Cemu</name>
<summary>Nintendo Wii U Emulator</summary>
<summary xml:lang="de">Nintendo Wii U Emulator</summary>
<summary xml:lang="fr">Émulateur Nintendo Wii U</summary>
<summary xml:lang="nl">Nintendo Wii U Emulator</summary>
<summary xml:lang="el">Εξομοιωτής Nintendo Wii U</summary>
<summary xml:lang="es">Emulador de Nintendo Wii U</summary>
<summary xml:lang="pt_BR">Emulador Nintendo Wii U</summary>
<summary xml:lang="it">Emulatore Nintendo Wii U</summary>
<summary xml:lang="fi">Nintendo Wii U Emulaattori</summary>
<developer_name>Cemu Project</developer_name>
<launchable type="desktop-id">info.cemu.Cemu.desktop</launchable>
<metadata_license>CC0-1.0</metadata_license>
<project_license>MPL-2.0</project_license>
<description>
<p>Cemu is a Nintendo Wii U emulator that is able to run most Wii U games and homebrew in a playable state. Created by Exzap, and written in C/C++.</p>
<p xml:lang="de">Cemu ist ein Nintendo Wii U-Emulator, der die meisten Wii U Spiele und Homebrew in einem spielbaren Zustand ausführen kann. Erstellt von Exzap, und geschrieben in C/C++.</p>
<p xml:lang="fr">Cemu est un émulateur de Wii U capable de lancer la plupart des jeux Wii U et des homebrews en interface de jeu. Crée par Exzap, et développé en C/C++.</p>
<p xml:lang="nl">Cemu is een Nintendo Wii U emulator die de meeste Wii U en Homebrew games speelbaar kan runnen. Het project is begonnen door Exzap, en het is geschreven in C/C++.</p>
<p xml:lang="el">Το Cemu είναι ένας προσομοιωτής της κονσόλας Nintendo Wii U που προσομοιώνει επίσημα παιχνίδια, καθώς και μη επίσημα προγράμματα ("homebrew") του Wii U. Δημιουργήθηκε από τον Exzap σε C/C++.</p>
<p xml:lang="es">Cemu es un emulador de Nintendo Wii U que es capaz de ejecutar la mayoría de los juegos de Wii U y homebrew en un estado jugable. Creado por Exzap, y escrito en C y C++.</p>
<p xml:lang="pt_BR">Cemu é um emulador de Nintendo Wii U que é capaz de executar a maioría dos jogos de Wii U e homebrew em um estado jogavel. Criado por Exzap, e escrito em C e C++.</p>
<p xml:lang="it">Cemu è un emulatore del Nintendo Wii U in grado di eseguire in stato giocabile la maggior parte dei giochi e homebrew per Wii U. Creato da Exzap, e scritto in C/C++.</p>
<p xml:lang="fi">Cemu on Wii U -emulaattori, joka pystyy toistamaan useimpia Wii U -pelejä ja homebrew'ta pelikelvollisesti. Luonut Exzap, kirjoitettu C:llä/C++:lla.</p>
<p>This emulator aims at providing both high-accuracy and performance, and is actively being developed with new features and fixes to increase compatibility, convenience and usability.</p>
<p xml:lang="de">Dieser Emulator zielt darauf ab, sowohl hohe Genauigkeit als auch Leistung zu bieten, und wird aktiv mit neuen Funktionen und Korrekturen weiterentwickelt, um Kompatibilität, Komfort und Benutzerfreundlichkeit zu verbessern.</p>
<p xml:lang="fr">Cet émulateur vise à la fois à offrir fidélité et performance, il est activement développé avec des nouvelles fonctionnalités et des correctifs pour augmenter la compatibilité, la commodité et la facilité d'utilisation.</p>
<p xml:lang="nl">De emulator richt zich op integriteit en snelheid, en wordt continu verder ontwikkeld met nieuwe toevoegingen en fixes om de compatibiliteit, het gemak en de gebruiksvriendelijkheid te verbeteren.</p>
<p xml:lang="el">Ο προσομοιωτής αυτός στοχεύει τόσο στην ακρίβεια, όσο και στην ταχύτητα, και βελτιώνεται συνεχώς με νέες δυνατότητες και διορθώσεις που τον καθιστούν πιο βολικό, εύκολο στην χρήση και συμβατό με περισσότερα παιχνίδια.</p>
<p xml:lang="es">Este emulador tiene como objetivo proporcionar tanto alta precisión como rendimiento y se desarrolla activamente con nuevas características y correcciones para mejorar la compatibilidad, la comodidad y la usabilidad.</p>
<p xml:lang="pt_BR">Esse emulador visa proporcionar tanto alta precisão como rendimento e está sendo ativamente desenvolvido com novas características e correções para melhorar a compatibilidade, a comodidade e a usabilidade.</p>
<p xml:lang="it">Questo emulatore ha l'obiettivo di fornire sia alta precisione che alte prestazioni, ed è in continuo sviluppo con nuove funzionalità e correzioni per aumentare la compatibilità, la comodità e l'usabilità.</p>
<p xml:lang="fi">Tämä emulaattori pyrkii tarjoamaan korkeaa tarkkuutta sekä suorituskykyä, ja sitä kehitetään aktiivisesti uusilla ominaisuuksilla ja korjauksilla, jotka parantavat yhteensopivuutta, mukavuutta ja käytettävyyttä..</p>
<p>It was written from scratch and development on the project began roughly early 2015.</p>
<p xml:lang="de">Er wird seit Anfang 2015 entwickelt.</p>
<p xml:lang="fr">Il a été écrit à partir de zéro et son développement a débuté vers le début de l'année 2015.</p>
<p xml:lang="nl">Ontwikkeling van Cemu begon ongeveer in het voorjaar van 2015.</p>
<p xml:lang="el">Αρχισε απο το τίποτα και βρίσκεται υπό ανάπτυξη από το 2015.</p>
<p xml:lang="es">Fue escrito desde cero y el desarrollo del proyecto comenzó aproximadamente a principios de 2015.</p>
<p xml:lang="pt_BR">Foi escrito do zero e o desenvolvimento do projeto começou aproximadamente a princípio de 2015.</p>
<p xml:lang="it">È stato scritto da zero e lo sviluppo del progetto è cominciato intorno all'inizio del 2015.</p>
<p xml:lang="fi">Se kirjoitettiin tyhjästä ja sen kehitys alkoi suunnilleen vuoden 2015 alussa.</p>
</description>
<screenshots>
<screenshot type="default">
<image type="source">https://upload.wikimedia.org/wikipedia/commons/5/58/Cemu_Screenshot.png</image>
</screenshot>
</screenshots>
<releases>
<release version="2.0" date="2022-08-22" type="stable">
<url>https://github.com/cemu-project/Cemu/releases/tag/v2.0</url>
</release>
</releases>
<url type="homepage">https://cemu.info</url>
<url type="bugtracker">https://github.com/cemu-project/Cemu/issues</url>
<url type="faq">https://cemu.info/faq.html</url>
<url type="help">https://wiki.cemu.info</url>
<!-- <url type="vcs-browser">https://github.com/cemu-project/Cemu</url> -->
<categories>
<category>Game</category>
<category>Emulator</category>
</categories>
<recommends>
<memory>8192</memory>
</recommends>
<supports>
<control>pointing</control>
<control>keyboard</control>
<control>gamepad</control>
</supports>
<content_rating type="oars-1.1"/>
<provides>
<binary>cemu</binary>
<mediatype>application/x-wii-u-rom</mediatype>
</provides>
<keywords>
<keyword>nintendo</keyword>
</keywords>
</component>
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

-16
View File
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="amd64" publicKeyToken="6595b64144ccf1df" language="*"></assemblyIdentity>
</dependentAssembly>
</dependency>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"><security><requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel></requestedPrivileges></security>
</trustInfo>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
</windowsSettings>
</application>
</assembly>
-305
View File
@@ -1,305 +0,0 @@
#include "wxgui.h"
#include "AudioDebuggerWindow.h"
#include "Cafe/OS/libs/snd_core/ax.h"
#include "Cafe/OS/libs/snd_core/ax_internal.h"
enum
{
// options
REFRESH_ID,
CLOSE_ID,
VOICELIST_ID,
REFRESH_TIMER_ID,
};
wxBEGIN_EVENT_TABLE(AudioDebuggerWindow, wxFrame)
EVT_BUTTON(CLOSE_ID, AudioDebuggerWindow::OnCloseButton)
EVT_BUTTON(REFRESH_ID, AudioDebuggerWindow::OnRefreshButton)
EVT_TIMER(REFRESH_TIMER_ID, AudioDebuggerWindow::OnRefreshTimer)
EVT_CLOSE(AudioDebuggerWindow::OnClose)
wxEND_EVENT_TABLE()
AudioDebuggerWindow::AudioDebuggerWindow(wxFrame& parent)
: wxFrame(&parent, wxID_ANY, _("AX voice viewer"), wxDefaultPosition, wxSize(1126, 580), wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER)
{
wxPanel* mainPane = new wxPanel(this);
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
voiceListbox = new wxListCtrl(mainPane, VOICELIST_ID, wxPoint(0, 0), wxSize(1126, 570), wxLC_REPORT);
voiceListbox->SetFont(wxFont(8, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, "Courier New"));
// add columns
wxListItem col0;
col0.SetId(0);
col0.SetText("idx");
col0.SetWidth(40);
voiceListbox->InsertColumn(0, col0);
wxListItem col1;
col1.SetId(1);
col1.SetText("state");
col1.SetWidth(48);
voiceListbox->InsertColumn(1, col1);
//wxListItem col2;
// format
col1.SetId(2);
col1.SetText("fmt");
col1.SetWidth(52);
voiceListbox->InsertColumn(2, col1);
// sample base addr
col1.SetId(3);
col1.SetText("base");
col1.SetWidth(70);
voiceListbox->InsertColumn(3, col1);
// current offset
col1.SetId(4);
col1.SetText("current");
col1.SetWidth(70);
voiceListbox->InsertColumn(4, col1);
// loop offset
col1.SetId(5);
col1.SetText("loop");
col1.SetWidth(70);
voiceListbox->InsertColumn(5, col1);
// end offset
col1.SetId(6);
col1.SetText("end");
col1.SetWidth(70);
voiceListbox->InsertColumn(6, col1);
// volume
col1.SetId(7);
col1.SetText("vol");
col1.SetWidth(46);
voiceListbox->InsertColumn(7, col1);
// volume delta
col1.SetId(8);
col1.SetText("volD");
col1.SetWidth(46);
voiceListbox->InsertColumn(8, col1);
// src
col1.SetId(9);
col1.SetText("src");
col1.SetWidth(70);
voiceListbox->InsertColumn(9, col1);
// low-pass filter coef a0
col1.SetId(10);
col1.SetText("lpa0");
col1.SetWidth(46);
voiceListbox->InsertColumn(10, col1);
// low-pass filter coef b0
col1.SetId(11);
col1.SetText("lpb0");
col1.SetWidth(46);
voiceListbox->InsertColumn(11, col1);
// biquad filter coef b0
col1.SetId(12);
col1.SetText("bqb0");
col1.SetWidth(46);
voiceListbox->InsertColumn(12, col1);
// biquad filter coef b0
col1.SetId(13);
col1.SetText("bqb1");
col1.SetWidth(46);
voiceListbox->InsertColumn(13, col1);
// biquad filter coef b0
col1.SetId(14);
col1.SetText("bqb2");
col1.SetWidth(46);
voiceListbox->InsertColumn(14, col1);
// biquad filter coef a0
col1.SetId(15);
col1.SetText("bqa1");
col1.SetWidth(46);
voiceListbox->InsertColumn(15, col1);
// biquad filter coef a1
col1.SetId(16);
col1.SetText("bqa2");
col1.SetWidth(46);
voiceListbox->InsertColumn(16, col1);
// device mix
col1.SetId(17);
col1.SetText("deviceMix");
col1.SetWidth(186);
voiceListbox->InsertColumn(17, col1);
sizer->Add(voiceListbox, 1, wxEXPAND | wxBOTTOM, 0);
voiceListbox->Bind(wxEVT_RIGHT_DOWN, &AudioDebuggerWindow::OnVoiceListRightClick, this);
mainPane->SetSizer(sizer);
// add empty entries
for (sint32 i = 0; i < snd_core::AX_MAX_VOICES; i++)
{
wxListItem item;
item.SetId(i);
item.SetText(wxString::Format("%d", snd_core::AX_MAX_VOICES-i-1));
voiceListbox->InsertItem(item);
}
RefreshVoiceList();
// start refresh timer
static const int INTERVAL = 100; // milliseconds
refreshTimer = new wxTimer(this, REFRESH_TIMER_ID);
refreshTimer->Start(INTERVAL);
}
void AudioDebuggerWindow::OnRefreshTimer(wxTimerEvent& event)
{
RefreshVoiceList();
}
void AudioDebuggerWindow::OnCloseButton(wxCommandEvent& event)
{
Close();
}
void AudioDebuggerWindow::OnRefreshButton(wxCommandEvent& event)
{
RefreshVoiceList();
}
void AudioDebuggerWindow::OnClose(wxCloseEvent& event)
{
Close();
}
#define _r(__idx) _swapEndianU32(threadItrBE->context.gpr[__idx])
void AudioDebuggerWindow::RefreshVoiceList_sndgeneric()
{
if (snd_core::__AXVPBInternalVoiceArray == nullptr || snd_core::__AXVPBArrayPtr == nullptr)
return;
snd_core::AXVPB tempVoiceArray[snd_core::AX_MAX_VOICES];
memcpy(tempVoiceArray, snd_core::__AXVPBArrayPtr, sizeof(snd_core::AXVPB)*snd_core::AX_MAX_VOICES);
voiceListbox->Freeze();
for (sint32 i = 0; i < snd_core::AX_MAX_VOICES; i++)
{
sint32 voiceIndex = snd_core::AX_MAX_VOICES - 1 - i;
snd_core::AXVPBInternal_t* internal = snd_core::__AXVPBInternalVoiceArray + voiceIndex;
// index
voiceListbox->SetItem(i, 0, wxString::Format("%d", (sint32)tempVoiceArray[voiceIndex].index));
// state
uint16 playbackState = _swapEndianU16(internal->playbackState);
voiceListbox->SetItem(i, 1, playbackState ? "on" : "off");
// if voice index is invalid then stop updating here to prevent crashes
if (voiceIndex < 0 || playbackState == 0)
{
voiceListbox->SetItem(i, 0, "");
voiceListbox->SetItem(i, 1, "");
voiceListbox->SetItem(i, 2, "");
voiceListbox->SetItem(i, 3, "");
voiceListbox->SetItem(i, 4, "");
voiceListbox->SetItem(i, 5, "");
voiceListbox->SetItem(i, 6, "");
voiceListbox->SetItem(i, 7, "");
voiceListbox->SetItem(i, 8, "");
voiceListbox->SetItem(i, 9, "");
voiceListbox->SetItem(i, 10, "");
voiceListbox->SetItem(i, 11, "");
voiceListbox->SetItem(i, 12, "");
voiceListbox->SetItem(i, 13, "");
voiceListbox->SetItem(i, 14, "");
voiceListbox->SetItem(i, 15, "");
voiceListbox->SetItem(i, 16, "");
voiceListbox->SetItem(i, 17, "");
continue;
}
// format
wxString formatLabel;
if (tempVoiceArray[voiceIndex].offsets.format == _swapEndianU16(snd_core::AX_FORMAT_ADPCM))
formatLabel = "adpcm";
else if (tempVoiceArray[voiceIndex].offsets.format == _swapEndianU16(snd_core::AX_FORMAT_PCM16))
formatLabel = "pcm16";
else if (tempVoiceArray[voiceIndex].offsets.format == _swapEndianU16(snd_core::AX_FORMAT_PCM8))
formatLabel = "pcm8";
else
formatLabel = "ukn";
voiceListbox->SetItem(i, 2, formatLabel);
// update offsets
snd_core::AXPBOFFSET_t tempOffsets;
snd_core::AXGetVoiceOffsets(tempVoiceArray + voiceIndex, &tempOffsets);
// sample base
voiceListbox->SetItem(i, 3, wxString::Format("%08x", _swapEndianU32(tempOffsets.samples)));
// current offset
voiceListbox->SetItem(i, 4, wxString::Format("%08x", _swapEndianU32(tempOffsets.currentOffset)));
// loop offset
voiceListbox->SetItem(i, 5, tempOffsets.loopFlag ? wxString::Format("%08x", _swapEndianU32(tempOffsets.loopOffset)) : "");
// end offset
voiceListbox->SetItem(i, 6, wxString::Format("%08x", _swapEndianU32(tempOffsets.endOffset)));
// volume
voiceListbox->SetItem(i, 7, wxString::Format("%04x", (uint16)internal->veVolume));
// volume delta
voiceListbox->SetItem(i, 8, wxString::Format("%04x", (uint16)internal->veDelta));
// src
voiceListbox->SetItem(i, 9, wxString::Format("%04x%04x", _swapEndianU16(internal->src.ratioHigh), _swapEndianU16(internal->src.ratioLow)));
// lpf
if (internal->lpf.on)
{
voiceListbox->SetItem(i, 10, wxString::Format("%04x", _swapEndianU16(internal->lpf.a0)));
voiceListbox->SetItem(i, 11, wxString::Format("%04x", _swapEndianU16(internal->lpf.b0)));
}
else
{
voiceListbox->SetItem(i, 10, "");
voiceListbox->SetItem(i, 11, "");
}
// biquad
if (internal->biquad.on)
{
voiceListbox->SetItem(i, 12, wxString::Format("%04x", _swapEndianU16(internal->biquad.b0)));
voiceListbox->SetItem(i, 13, wxString::Format("%04x", _swapEndianU16(internal->biquad.b1)));
voiceListbox->SetItem(i, 14, wxString::Format("%04x", _swapEndianU16(internal->biquad.b2)));
voiceListbox->SetItem(i, 15, wxString::Format("%04x", _swapEndianU16(internal->biquad.a1)));
voiceListbox->SetItem(i, 16, wxString::Format("%04x", _swapEndianU16(internal->biquad.a2)));
}
else
{
voiceListbox->SetItem(i, 12, "");
voiceListbox->SetItem(i, 13, "");
voiceListbox->SetItem(i, 14, "");
voiceListbox->SetItem(i, 15, "");
voiceListbox->SetItem(i, 16, "");
}
wxString label;
// device mix
for (uint32 f = 0; f < snd_core::AX_TV_CHANNEL_COUNT*snd_core::AX_MAX_NUM_BUS; f++)
{
sint32 busIndex = f% snd_core::AX_MAX_NUM_BUS;
sint32 channelIndex = f / snd_core::AX_MAX_NUM_BUS;
//debug_printf("DeviceMix TV Voice %08x b%02d/c%02d vol %04x delta %04x\n", hCPU->gpr[3], busIndex, channelIndex, _swapEndianU16(mixArrayBE[f].vol), _swapEndianU16(mixArrayBE[f].volDelta));
uint32 mixVol = internal->deviceMixTV[channelIndex * 4 + busIndex].vol;
mixVol = (mixVol + 0x0FFF) >> (12);
label += wxString::Format("%x", mixVol);
//ax.voiceInternal[voiceIndex].deviceMixTVChannel[channelIndex].bus[busIndex].vol = _swapEndianU16(mixArrayBE[f].vol);
}
voiceListbox->SetItem(i, 17, label);
}
voiceListbox->Thaw();
}
void AudioDebuggerWindow::RefreshVoiceList()
{
RefreshVoiceList_sndgeneric();
}
void AudioDebuggerWindow::OnVoiceListPopupClick(wxCommandEvent &evt)
{
}
void AudioDebuggerWindow::OnVoiceListRightClick(wxMouseEvent& event)
{
}
void AudioDebuggerWindow::Close()
{
// this->MakeModal(false);
refreshTimer->Stop();
this->Destroy();
}
-30
View File
@@ -1,30 +0,0 @@
#pragma once
#include <wx/wx.h>
class wxListCtrl;
class AudioDebuggerWindow : public wxFrame
{
public:
AudioDebuggerWindow(wxFrame& parent);
void OnCloseButton(wxCommandEvent& event);
void OnRefreshButton(wxCommandEvent& event);
void OnClose(wxCloseEvent& event);
void RefreshVoiceList_sndgeneric();
void RefreshVoiceList();
void OnRefreshTimer(wxTimerEvent& event);
void OnVoiceListPopupClick(wxCommandEvent &evt);
void OnVoiceListRightClick(wxMouseEvent& event);
void Close();
private:
wxListCtrl* voiceListbox;
wxTimer* refreshTimer;
wxDECLARE_EVENT_TABLE();
};
-198
View File
@@ -1,198 +0,0 @@
add_library(CemuWxGui STATIC
canvas/IRenderCanvas.h
CemuApp.cpp
CemuApp.h
CemuUpdateWindow.cpp
CemuUpdateWindow.h
ChecksumTool.cpp
ChecksumTool.h
components/TextList.cpp
components/TextList.h
components/wxDownloadManagerList.cpp
components/wxDownloadManagerList.h
components/wxGameList.cpp
components/wxGameList.h
components/wxInputDraw.cpp
components/wxInputDraw.h
components/wxLogCtrl.cpp
components/wxLogCtrl.h
components/wxTitleManagerList.cpp
components/wxTitleManagerList.h
debugger/BreakpointWindow.cpp
debugger/BreakpointWindow.h
debugger/DebuggerWindow2.cpp
debugger/DebuggerWindow2.h
debugger/DisasmCtrl.cpp
debugger/DisasmCtrl.h
debugger/DumpCtrl.cpp
debugger/DumpCtrl.h
debugger/DumpWindow.cpp
debugger/DumpWindow.h
debugger/ModuleWindow.cpp
debugger/ModuleWindow.h
debugger/RegisterWindow.cpp
debugger/RegisterWindow.h
debugger/SymbolCtrl.cpp
debugger/SymbolCtrl.h
debugger/SymbolWindow.cpp
debugger/SymbolWindow.h
dialogs/CreateAccount/wxCreateAccountDialog.cpp
dialogs/CreateAccount/wxCreateAccountDialog.h
dialogs/SaveImport/SaveImportWindow.cpp
dialogs/SaveImport/SaveImportWindow.h
dialogs/SaveImport/SaveTransfer.cpp
dialogs/SaveImport/SaveTransfer.h
AudioDebuggerWindow.cpp
AudioDebuggerWindow.h
DownloadGraphicPacksWindow.cpp
DownloadGraphicPacksWindow.h
DownloadCustomGraphicPackWindow.cpp
DownloadCustomGraphicPackWindow.h
GameProfileWindow.cpp
GameProfileWindow.h
GameUpdateWindow.cpp
GameUpdateWindow.h
GeneralSettings2.cpp
GeneralSettings2.h
GettingStartedDialog.cpp
GettingStartedDialog.h
GraphicPacksWindow2.cpp
GraphicPacksWindow2.h
wxWindowSystem.cpp
helpers/wxControlObject.h
helpers/wxCustomData.h
helpers/wxCustomEvents.cpp
helpers/wxCustomEvents.h
helpers/wxHelpers.cpp
helpers/wxHelpers.h
helpers/wxLogEvent.h
helpers/wxWayland.cpp
helpers/wxWayland.h
input/HotkeySettings.cpp
input/HotkeySettings.h
input/InputAPIAddWindow.cpp
input/InputAPIAddWindow.h
input/InputSettings2.cpp
input/InputSettings2.h
input/panels/ClassicControllerInputPanel.cpp
input/panels/ClassicControllerInputPanel.h
input/panels/InputPanel.cpp
input/panels/InputPanel.h
input/panels/ProControllerInputPanel.cpp
input/panels/ProControllerInputPanel.h
input/panels/VPADInputPanel.cpp
input/panels/VPADInputPanel.h
input/panels/WiimoteInputPanel.cpp
input/panels/WiimoteInputPanel.h
input/settings/DefaultControllerSettings.cpp
input/settings/DefaultControllerSettings.h
input/settings/WiimoteControllerSettings.cpp
input/settings/WiimoteControllerSettings.h
LoggingWindow.cpp
LoggingWindow.h
MainWindow.cpp
MainWindow.h
MemorySearcherTool.cpp
MemorySearcherTool.h
PadViewFrame.cpp
PadViewFrame.h
TitleManager.cpp
TitleManager.h
wxCemuConfig.cpp
wxCemuConfig.h
EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp
EmulatedUSBDevices/EmulatedUSBDeviceFrame.h
windows/PPCThreadsViewer
windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp
windows/PPCThreadsViewer/DebugPPCThreadsWindow.h
windows/TextureRelationViewer
windows/TextureRelationViewer/TextureRelationWindow.cpp
windows/TextureRelationViewer/TextureRelationWindow.h
wxcomponents/checktree.cpp
wxcomponents/checktree.h
wxgui.h
wxHelper.h
)
if (ENABLE_OPENGL)
target_sources(CemuWxGui PRIVATE
canvas/OpenGLCanvas.cpp
canvas/OpenGLCanvas.h
)
endif()
if (ENABLE_VULKAN)
target_sources(CemuWxGui PRIVATE
canvas/VulkanCanvas.cpp
canvas/VulkanCanvas.h
)
endif()
if (ENABLE_METAL)
target_sources(CemuWxGui PRIVATE
canvas/MetalCanvas.cpp
canvas/MetalCanvas.h
)
endif()
if (ENABLE_BLUEZ)
target_sources(CemuWxGui PRIVATE
input/PairingDialog.cpp
input/PairingDialog.h
)
endif()
set_property(TARGET CemuWxGui PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
cemu_use_precompiled_header(CemuWxGui)
target_include_directories(CemuWxGui PUBLIC "../")
# PUBLIC because rapidjson/document.h is included in ChecksumTool.h
target_include_directories(CemuWxGui PUBLIC ${RAPIDJSON_INCLUDE_DIRS})
target_link_libraries(CemuWxGui PRIVATE
CemuCommon
CemuResource
libzip::zip
ZArchive::zarchive
CemuComponents
pugixml::pugixml
CemuCafe
PUBLIC
CURL::libcurl
)
if(ENABLE_WXWIDGETS AND UNIX AND NOT APPLE)
# PUBLIC because gdk/gdkkeysyms.h is included in guiWrapper.h
target_link_libraries(CemuWxGui PUBLIC GTK3::gtk)
if (ENABLE_WAYLAND)
target_link_libraries(CemuWxGui PRIVATE Wayland::Client CemuWaylandProtocols)
endif()
endif()
if(ENABLE_CUBEB)
target_link_libraries(CemuWxGui PRIVATE cubeb::cubeb)
endif()
if(ENABLE_SDL)
target_link_libraries(CemuWxGui PRIVATE SDL3::SDL3)
endif()
if(UNIX AND NOT APPLE)
if(ENABLE_FERAL_GAMEMODE)
target_link_libraries(CemuWxGui PRIVATE gamemode)
endif()
endif()
if (ENABLE_WXWIDGETS)
# PUBLIC because wx/app.h is included in CemuApp.h
target_link_libraries(CemuWxGui PUBLIC wxWidgets::wxWidgets)
endif()
if(WIN32)
target_link_libraries(CemuWxGui PRIVATE bthprops)
endif()
if(ALLOW_PORTABLE)
target_compile_definitions(CemuWxGui PRIVATE CEMU_ALLOW_PORTABLE)
endif ()
-678
View File
@@ -1,678 +0,0 @@
#include "wxgui/CemuApp.h"
#include "wxCemuConfig.h"
#include "wxgui/MainWindow.h"
#include "wxgui/wxgui.h"
#include "config/CemuConfig.h"
#ifdef ENABLE_VULKAN
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
#endif
#include "Cafe/HW/Latte/Core/LatteOverlay.h"
#include "config/ActiveSettings.h"
#include "config/LaunchSettings.h"
#include "wxgui/GettingStartedDialog.h"
#include "input/InputManager.h"
#include "input/api/SDL/SDLControllerProvider.h"
#include "wxgui/helpers/wxHelpers.h"
#include "Cemu/ncrypto/ncrypto.h"
#include "wxgui/input/HotkeySettings.h"
#include "wxgui/debugger/DebuggerWindow2.h"
#include <wx/language.h>
#if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND
#include "wxgui/helpers/wxWayland.h"
#endif
#if __WXGTK__
#include <glib.h>
#endif
#include <wx/image.h>
#include <wx/filename.h>
#include <wx/stdpaths.h>
#include <wx/clipbrd.h>
#include <wx/timer.h>
#include "wxHelper.h"
#include "Cafe/TitleList/TitleList.h"
#include "Cafe/TitleList/SaveList.h"
#include "Cafe/CafeSystem.h"
wxIMPLEMENT_APP_NO_MAIN(CemuApp);
// defined in wxWindowSystem.cpp
extern WindowSystem::WindowInfo g_window_info;
extern std::shared_mutex g_mutex;
// forward declarations from main.cpp
void UnitTests();
void CemuCommonInit();
void HandlePostUpdate();
// Translation strings to extract for gettext:
void unused_translation_dummy()
{
void(_("Browse"));
void(_("Select a file"));
void(_("Select a directory"));
void(_("Japanese"));
void(_("English"));
void(_("French"));
void(_("German"));
void(_("Italian"));
void(_("Spanish"));
void(_("Chinese"));
void(_("Korean"));
void(_("Dutch"));
void(_("Portugese"));
void(_("Russian"));
void(_("Taiwanese"));
void(_("unknown"));
}
#if BOOST_OS_WINDOWS
#include <shlobj.h>
fs::path GetAppDataRoamingPath()
{
PWSTR path = nullptr;
HRESULT result = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &path);
if (FAILED(result))
{
CoTaskMemFree(path);
return {};
}
std::string appDataPath = boost::nowide::narrow(path);
CoTaskMemFree(path);
return _utf8ToPath(appDataPath);
}
#endif
#if BOOST_OS_WINDOWS
void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Windows
{
std::error_code ec;
bool isPortable = false;
fs::path user_data_path, config_path, cache_path, data_path;
auto standardPaths = wxStandardPaths::Get();
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
fs::path portablePath = exePath.parent_path() / "portable";
data_path = exePath.parent_path(); // the data path is always the same as the exe path
#ifdef CEMU_ALLOW_PORTABLE
if (fs::is_directory(portablePath, ec))
{
isPortable = true;
user_data_path = config_path = cache_path = portablePath;
}
else
#endif
{
fs::path roamingPath = GetAppDataRoamingPath() / "Cemu";
user_data_path = config_path = cache_path = roamingPath;
}
// on Windows Cemu used to be portable by default prior to 2.0-89
// to remain backwards compatible with old installations we check for settings.xml in the Cemu directory
// if it exists, we use the exe path as the portable directory
if(!isPortable) // lower priority than portable directory
{
if (fs::exists(exePath.parent_path() / "settings.xml", ec))
{
isPortable = true;
user_data_path = config_path = cache_path = exePath.parent_path();
}
}
ActiveSettings::SetPaths(isPortable, exePath, user_data_path, config_path, cache_path, data_path, failedWriteAccess);
}
#endif
#if BOOST_OS_LINUX || BOOST_OS_BSD
void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Linux
{
std::error_code ec;
bool isPortable = false;
fs::path user_data_path, config_path, cache_path, data_path;
auto standardPaths = wxStandardPaths::Get();
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
fs::path portablePath = exePath.parent_path() / "portable";
// GetExecutablePath returns the AppImage's temporary mount location
wxString appImagePath;
if (wxGetEnv("APPIMAGE", &appImagePath))
{
exePath = wxHelper::MakeFSPath(appImagePath);
portablePath = exePath.parent_path() / "portable";
}
#ifdef CEMU_ALLOW_PORTABLE
if (fs::is_directory(portablePath, ec))
{
isPortable = true;
user_data_path = config_path = cache_path = portablePath;
// in portable mode assume the data directories (resources, gameProfiles/default/) are next to the executable
data_path = exePath.parent_path();
}
else
#endif
{
SetAppName("Cemu");
wxString appName = GetAppName();
standardPaths.SetFileLayout(wxStandardPaths::FileLayout::FileLayout_XDG);
auto getEnvDir = [&](const wxString& varName, const wxString& defaultValue)
{
wxString dir;
if (!wxGetEnv(varName, &dir) || dir.empty())
return defaultValue;
return dir;
};
wxString homeDir = wxFileName::GetHomeDir();
user_data_path = (getEnvDir(wxS("XDG_DATA_HOME"), homeDir + wxS("/.local/share")) + "/" + appName).ToStdString();
config_path = (getEnvDir(wxS("XDG_CONFIG_HOME"), homeDir + wxS("/.config")) + "/" + appName).ToStdString();
data_path = standardPaths.GetDataDir().ToStdString();
cache_path = standardPaths.GetUserDir(wxStandardPaths::Dir::Dir_Cache).ToStdString();
cache_path /= appName.ToStdString();
}
ActiveSettings::SetPaths(isPortable, exePath, user_data_path, config_path, cache_path, data_path, failedWriteAccess);
}
#endif
#if BOOST_OS_MACOS
void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for MacOS
{
std::error_code ec;
bool isPortable = false;
fs::path user_data_path, config_path, cache_path, data_path;
auto standardPaths = wxStandardPaths::Get();
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
// If run from an app bundle, use its parent directory
fs::path appPath = exePath.parent_path().parent_path().parent_path();
fs::path portablePath = appPath.extension() == ".app" ? appPath.parent_path() / "portable" : exePath.parent_path() / "portable";
#ifdef CEMU_ALLOW_PORTABLE
if (fs::is_directory(portablePath, ec))
{
isPortable = true;
user_data_path = config_path = cache_path = portablePath;
data_path = exePath.parent_path();
}
else
#endif
{
SetAppName("Cemu");
wxString appName = GetAppName();
user_data_path = config_path = standardPaths.GetUserDataDir().ToStdString();
data_path = standardPaths.GetDataDir().ToStdString();
cache_path = standardPaths.GetUserDir(wxStandardPaths::Dir::Dir_Cache).ToStdString();
cache_path /= appName.ToStdString();
}
ActiveSettings::SetPaths(isPortable, exePath, user_data_path, config_path, cache_path, data_path, failedWriteAccess);
}
#endif
// create default MLC files or quit if it fails
void CemuApp::InitializeNewMLCOrFail(fs::path mlc)
{
if( CemuApp::CreateDefaultMLCFiles(mlc) )
return; // all good
cemu_assert_debug(!ActiveSettings::IsCustomMlcPath()); // should not be possible?
if(ActiveSettings::IsCommandLineMlcPath() || ActiveSettings::IsCustomMlcPath())
{
// tell user that the custom path is not writable
wxMessageBox(formatWxString(_("Cemu failed to write to the custom mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
exit(0);
}
wxMessageBox(formatWxString(_("Cemu failed to write to the mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
exit(0);
}
void CemuApp::InitializeExistingMLCOrFail(fs::path mlc)
{
if(CreateDefaultMLCFiles(mlc))
return; // all good
// failed to write mlc files
if(ActiveSettings::IsCommandLineMlcPath() || ActiveSettings::IsCustomMlcPath())
{
// tell user that the custom path is not writable
// if it's a command line path then just quit. Otherwise ask if user wants to reset the path
if(ActiveSettings::IsCommandLineMlcPath())
{
wxMessageBox(formatWxString(_("Cemu failed to write to the custom mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
exit(0);
}
// ask user if they want to reset the path
const wxString message = formatWxString(_("Cemu failed to write to the custom mlc directory.\n\nThe path is:\n{}\n\nCemu cannot start without a valid mlc path. Do you want to reset the path? You can later change it again in the General Settings."),
_pathToUtf8(mlc));
wxMessageDialog dialog(nullptr, message, _("Error"), wxCENTRE | wxYES_NO | wxICON_WARNING);
dialog.SetYesNoLabels(_("Reset path"), _("Exit"));
const auto dialogResult = dialog.ShowModal();
if (dialogResult == wxID_NO)
exit(0);
else // reset path
{
GetConfig().mlc_path = "";
GetConfigHandle().Save();
}
}
else
{
// default path is not writeable. Just let the user know and quit. Unsure if it would be a good idea to ask the user to choose an alternative path instead
wxMessageBox(formatWxString(_("Cemu failed to write to the default mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
exit(0);
}
}
std::string TranslationCallback(std::string_view msgId)
{
return wxGetTranslation(wxString::FromUTF8(msgId)).utf8_string();
}
bool CemuApp::OnInit()
{
#if __WXGTK__
GTKSuppressDiagnostics(G_LOG_LEVEL_MASK & ~G_LOG_FLAG_FATAL);
#endif
std::set<fs::path> failedWriteAccess;
DeterminePaths(failedWriteAccess);
// make sure default cemu directories exist
CreateDefaultCemuFiles();
GetConfigHandle().SetFilename(ActiveSettings::GetConfigPath("settings.xml").generic_wstring());
auto& config = GetWxGUIConfig();
std::error_code ec;
bool isFirstStart = !fs::exists(ActiveSettings::GetConfigPath("settings.xml"), ec);
NetworkConfig::LoadOnce();
if (!isFirstStart)
{
GetConfigHandle().Load();
sint32 language = config.language.GetValue();
LocalizeUI(static_cast<wxLanguage>(language == wxLANGUAGE_DEFAULT ? wxLocale::GetSystemLanguage() : language));
}
else
{
LocalizeUI(static_cast<wxLanguage>(wxLocale::GetSystemLanguage()));
}
SetTranslationCallback(TranslationCallback);
#if __WXMSW__
auto& wxGuiConfig = GetWxGUIConfig();
if (wxGuiConfig.msw_theme.GetValue() == static_cast<int>(MSWThemeOption::kAuto))
{
MSWEnableDarkMode(DarkMode_Auto);
}
else if (wxGuiConfig.msw_theme.GetValue() == static_cast<int>(MSWThemeOption::kDark))
{
MSWEnableDarkMode(DarkMode_Always);
}
// extend tooltip duration to the maximum possible value
wxToolTip::SetDelay(-1);
wxToolTip::SetAutoPop(MAKELPARAM(std::numeric_limits<short>::max(),0));
#endif
for (auto&& path : failedWriteAccess)
{
wxMessageBox(formatWxString(_("Cemu can't write to {}!"), wxHelper::FromPath(path)),
_("Warning"), wxOK | wxCENTRE | wxICON_EXCLAMATION, nullptr);
}
if (isFirstStart)
{
// show the getting started dialog
GettingStartedDialog dia(nullptr);
dia.ShowModal();
// make sure config is created. Gfx pack UI and input UI may create it earlier already, but we still want to update it
GetConfigHandle().Save();
// create mlc, on failure the user can quit here. So do this after the Getting Started dialog
InitializeNewMLCOrFail(ActiveSettings::GetMlcPath());
}
else
{
// check if mlc is valid and recreate default files
InitializeExistingMLCOrFail(ActiveSettings::GetMlcPath());
}
ActiveSettings::Init(); // this is a bit of a misnomer, right now this call only loads certs for online play. In the future we should move the logic to a more appropriate place
HandlePostUpdate();
LatteOverlay_init();
// run a couple of tests if in non-release mode
#ifdef CEMU_DEBUG_ASSERT
UnitTests();
#endif
#if BOOST_OS_MACOS
SDLControllerProvider::InitSDL();
#endif
CemuCommonInit();
#if BOOST_OS_MACOS
m_sdlEventPumpTimer = new wxTimer(this);
Bind(wxEVT_TIMER, &CemuApp::OnSDLEventPumpTimer, this);
m_sdlEventPumpTimer->Start(5, wxTIMER_CONTINUOUS);
#endif
wxInitAllImageHandlers();
// fill colour db
wxTheColourDatabase->AddColour("ERROR", wxColour(0xCC, 0, 0));
wxTheColourDatabase->AddColour("SUCCESS", wxColour(0, 0xbb, 0));
#if BOOST_OS_WINDOWS
const auto parent_path = GetParentProcess();
if(parent_path.has_filename())
{
const auto filename = parent_path.filename().generic_string();
if (boost::icontains(filename, "WiiU_USB_Helper"))
__fastfail(0);
}
#endif
#ifdef ENABLE_VULKAN
InitializeGlobalVulkan();
#endif
Bind(wxEVT_ACTIVATE_APP, &CemuApp::ActivateApp, this);
m_mainFrame = new MainWindow();
std::unique_lock lock(g_mutex);
g_window_info.app_active = true;
HotkeySettings::Init(m_mainFrame);
SetTopWindow(m_mainFrame);
m_mainFrame->Show();
#if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND
if (wxWlIsWaylandWindow(m_mainFrame))
wxWlSetAppId(m_mainFrame, "info.cemu.Cemu");
#endif
return true;
}
int CemuApp::OnExit()
{
#if BOOST_OS_MACOS
if (m_sdlEventPumpTimer)
{
m_sdlEventPumpTimer->Stop();
Unbind(wxEVT_TIMER, &CemuApp::OnSDLEventPumpTimer, this);
delete m_sdlEventPumpTimer;
m_sdlEventPumpTimer = nullptr;
}
#endif
wxApp::OnExit();
wxTheClipboard->Flush();
InputManager::instance().Shutdown();
int retValue = 0;
if (auto r = CafeSystem::GetForegroundTitleReturnStatus(); (LaunchSettings::GetLoadFile() || LaunchSettings::GetLoadTitleID()) && r)
retValue = *r;
#if BOOST_OS_MACOS
SDLControllerProvider::ShutdownSDL();
#endif
// handle restart if requested
if (m_restartExecutable.has_value() && !m_restartExecutable->empty() && fs::exists(*m_restartExecutable))
{
fs::path restartPath = *m_restartExecutable;
#if BOOST_OS_WINDOWS
PROCESS_INFORMATION pi{};
STARTUPINFOW si{};
si.cb = sizeof(si);
std::wstring cmdline;
cmdline = L"\"" + boost::nowide::widen(_pathToUtf8(restartPath)) + L"\"";
CreateProcessW(nullptr, (wchar_t*)cmdline.c_str(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi);
#elif BOOST_OS_LINUX
std::string appPath = _pathToUtf8(restartPath);
execlp(appPath.c_str(), appPath.c_str(), (char *)NULL);
#elif BOOST_OS_MACOS
std::string appPath = _pathToUtf8(restartPath);
execlp(appPath.c_str(), appPath.c_str(), (char *)NULL);
#endif
}
#if BOOST_OS_WINDOWS
ExitProcess(retValue);
#else
_Exit(retValue);
#endif
}
#if BOOST_OS_MACOS
void CemuApp::OnSDLEventPumpTimer(wxTimerEvent& event)
{
// this callback is only used on macOS where SDL event functions need to be called on the main thread
// on other platforms SDLControllerProvider creates a separate thread for SDL event polling
SDLControllerProvider::PumpSDLEvents();
}
#endif
#if BOOST_OS_WINDOWS
void DumpThreadStackTrace();
#endif
void CemuApp::OnAssertFailure(const wxChar* file, int line, const wxChar* func, const wxChar* cond, const wxChar* msg)
{
cemuLog_createLogFile(false);
cemuLog_log(LogType::Force, "Encountered wxWidgets assert!");
cemuLog_log(LogType::Force, "File: {0} Line: {1}", wxString(file).utf8_string(), line);
cemuLog_log(LogType::Force, "Func: {0} Cond: {1}", wxString(func).utf8_string(), wxString(cond).utf8_string());
cemuLog_log(LogType::Force, "Message: {}", wxString(msg).utf8_string());
#if BOOST_OS_WINDOWS
DumpThreadStackTrace();
#endif
cemu_assert_debug(false);
}
int CemuApp::FilterEvent(wxEvent& event)
{
if(event.GetEventType() == wxEVT_KEY_DOWN)
{
const auto& key_event = (wxKeyEvent&)event;
g_window_info.set_keystate(fix_raw_keycode(key_event.GetRawKeyCode(), key_event.GetRawKeyFlags()), true);
}
else if(event.GetEventType() == wxEVT_KEY_UP)
{
const auto& key_event = (wxKeyEvent&)event;
g_window_info.set_keystate(fix_raw_keycode(key_event.GetRawKeyCode(), key_event.GetRawKeyFlags()), false);
}
else if(event.GetEventType() == wxEVT_ACTIVATE_APP)
{
const auto& activate_event = (wxActivateEvent&)event;
if(!activate_event.GetActive())
g_window_info.set_keystatesup();
}
// track if debugger window or its child windows are focused
if (s_debuggerWindow && (event.GetEventType() == wxEVT_SET_FOCUS || event.GetEventType() == wxEVT_ACTIVATE))
{
wxWindow* target_window = wxDynamicCast(event.GetEventObject(), wxWindow);
if (target_window && event.GetEventType() == wxEVT_ACTIVATE && !((wxActivateEvent&)event).GetActive())
target_window = nullptr;
if (target_window)
{
g_window_info.debugger_focused = false;
wxWindow* window_it = target_window;
while (window_it)
{
if (window_it == s_debuggerWindow) g_window_info.debugger_focused = true;
window_it = window_it->GetParent();
}
}
}
else if (!s_debuggerWindow)
{
g_window_info.debugger_focused = false;
}
return wxApp::FilterEvent(event);
}
std::vector<const wxLanguageInfo *> CemuApp::GetLanguages() const {
std::vector availableLanguages(m_availableTranslations);
availableLanguages.insert(availableLanguages.begin(), wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH));
return availableLanguages;
}
void CemuApp::LocalizeUI(wxLanguage languageToUse)
{
std::unique_ptr<wxTranslations> translationsMgr(new wxTranslations());
m_availableTranslations = GetAvailableTranslationLanguages(translationsMgr.get());
bool isTranslationAvailable = std::any_of(m_availableTranslations.begin(), m_availableTranslations.end(),
[languageToUse](const wxLanguageInfo* info) { return info->Language == languageToUse; });
if (languageToUse == wxLANGUAGE_DEFAULT || isTranslationAvailable)
{
translationsMgr->SetLanguage(static_cast<wxLanguage>(languageToUse));
translationsMgr->AddCatalog("cemu");
if (translationsMgr->IsLoaded("cemu") && wxLocale::IsAvailable(languageToUse))
{
m_locale.Init(languageToUse);
}
// This must be run after wxLocale::Init, as the latter sets up its own wxTranslations instance which we want to override
wxTranslations::Set(translationsMgr.release());
}
}
std::vector<const wxLanguageInfo*> CemuApp::GetAvailableTranslationLanguages(wxTranslations* translationsMgr)
{
wxFileTranslationsLoader::AddCatalogLookupPathPrefix(wxHelper::FromPath(ActiveSettings::GetDataPath("resources")));
std::vector<const wxLanguageInfo*> languages;
for (const auto& langName : translationsMgr->GetAvailableTranslations("cemu"))
{
const auto* langInfo = wxLocale::FindLanguageInfo(langName);
if (langInfo)
languages.emplace_back(langInfo);
}
return languages;
}
bool CemuApp::CheckMLCPath(const fs::path& mlc)
{
std::error_code ec;
if (!fs::exists(mlc, ec))
return false;
if (!fs::exists(mlc / "usr", ec) || !fs::exists(mlc / "sys", ec))
return false;
return true;
}
bool CemuApp::CreateDefaultMLCFiles(const fs::path& mlc)
{
auto CreateDirectoriesIfNotExist = [](const fs::path& path)
{
std::error_code ec;
if (!fs::exists(path, ec))
return fs::create_directories(path, ec);
return true;
};
// list of directories to create
const fs::path directories[] = {
mlc,
mlc / "sys",
mlc / "usr",
mlc / "usr/title/00050000", // base
mlc / "usr/title/0005000c", // dlc
mlc / "usr/title/0005000e", // update
mlc / "usr/save/00050010/1004a000/user/common/db", // Mii Maker save folders {0x500101004A000, 0x500101004A100, 0x500101004A200}
mlc / "usr/save/00050010/1004a100/user/common/db",
mlc / "usr/save/00050010/1004a200/user/common/db",
mlc / "sys/title/0005001b/1005c000/content" // lang files
};
for(auto& path : directories)
{
if(!CreateDirectoriesIfNotExist(path))
return false;
}
// create sys/usr folder in mlc01
try
{
const auto langDir = fs::path(mlc).append("sys/title/0005001b/1005c000/content");
auto langFile = fs::path(langDir).append("language.txt");
if (!fs::exists(langFile))
{
std::ofstream file(langFile);
if (file.is_open())
{
const char* langStrings[] = { "ja","en","fr","de","it","es","zh","ko","nl","pt","ru","zh" };
for (const char* lang : langStrings)
file << fmt::format(R"("{}",)", lang) << std::endl;
file.flush();
file.close();
}
}
auto countryFile = fs::path(langDir).append("country.txt");
if (!fs::exists(countryFile))
{
std::ofstream file(countryFile);
for (sint32 i = 0; i < NCrypto::GetCountryCount(); i++)
{
const char* countryCode = NCrypto::GetCountryAsString(i);
if (boost::iequals(countryCode, "NN"))
file << "NULL," << std::endl;
else
file << fmt::format(R"("{}",)", countryCode) << std::endl;
}
file.flush();
file.close();
}
// create a dummy file in the mlc folder to check if it's writable
const auto dummyFile = fs::path(mlc).append("writetestdummy");
std::ofstream file(dummyFile);
if (!file.is_open())
return false;
file.close();
fs::remove(dummyFile);
}
catch (const std::exception& ex)
{
return false;
}
return true;
}
void CemuApp::CreateDefaultCemuFiles()
{
// cemu directories
try
{
const auto controllerProfileFolder = ActiveSettings::GetConfigPath("controllerProfiles");
if (!fs::exists(controllerProfileFolder))
fs::create_directories(controllerProfileFolder);
const auto memorySearcherFolder = ActiveSettings::GetUserDataPath("memorySearcher");
if (!fs::exists(memorySearcherFolder))
fs::create_directories(memorySearcherFolder);
}
catch (const std::exception& ex)
{
wxString errorMsg = formatWxString(_("Couldn't create a required cemu directory or file!\n\nError: {0}"), ex.what());
#if BOOST_OS_WINDOWS
const DWORD lastError = GetLastError();
if (lastError != ERROR_SUCCESS)
errorMsg << fmt::format("\n\n{}", GetSystemErrorMessage(lastError));
#endif
wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
exit(0);
}
}
void CemuApp::ActivateApp(wxActivateEvent& event)
{
g_window_info.app_active = event.GetActive();
event.Skip();
}
void CemuApp::RequestRestart(fs::path executablePath)
{
m_restartExecutable = executablePath;
CallAfter([this, executablePath = std::move(executablePath)]() mutable
{
if (m_mainFrame && !m_mainFrame->IsBeingDeleted())
m_mainFrame->Close(true);
});
}
-47
View File
@@ -1,47 +0,0 @@
#pragma once
#include <wx/app.h>
class MainWindow;
class wxTimer;
class wxTimerEvent;
class CemuApp : public wxApp
{
public:
bool OnInit() override;
int OnExit() override;
void OnAssertFailure(const wxChar* file, int line, const wxChar* func, const wxChar* cond, const wxChar* msg) override;
int FilterEvent(wxEvent& event) override;
std::vector<const wxLanguageInfo*> GetLanguages() const;
static bool CheckMLCPath(const fs::path& mlc);
static bool CreateDefaultMLCFiles(const fs::path& mlc);
static void CreateDefaultCemuFiles();
static void InitializeNewMLCOrFail(fs::path mlc);
static void InitializeExistingMLCOrFail(fs::path mlc);
void RequestRestart(fs::path executablePath);
private:
void LocalizeUI(wxLanguage languageToUse);
void DeterminePaths(std::set<fs::path>& failedWriteAccess);
void ActivateApp(wxActivateEvent& event);
static std::vector<const wxLanguageInfo*> GetAvailableTranslationLanguages(wxTranslations* translationsMgr);
MainWindow* m_mainFrame = nullptr;
#if BOOST_OS_MACOS
void OnSDLEventPumpTimer(wxTimerEvent& event);
wxTimer* m_sdlEventPumpTimer = nullptr;
#endif
wxLocale m_locale;
std::vector<const wxLanguageInfo*> m_availableTranslations;
std::optional<fs::path> m_restartExecutable;
};
wxDECLARE_APP(CemuApp);
-861
View File
@@ -1,861 +0,0 @@
#include "wxgui/CemuUpdateWindow.h"
#include "Common/version.h"
#include "util/helpers/helpers.h"
#include "util/helpers/SystemException.h"
#include "util/helpers/ZipArchive.h"
#include "config/ActiveSettings.h"
#include "Common/FileStream.h"
#include "wxCemuConfig.h"
#include "CemuApp.h"
#include <wx/sizer.h>
#include <wx/gauge.h>
#include <wx/button.h>
#include <wx/msgdlg.h>
#include <wx/stdpaths.h>
#ifndef BOOST_OS_WINDOWS
#include <unistd.h>
#include <sys/stat.h>
#endif
#include <curl/curl.h>
#include <boost/tokenizer.hpp>
#include <openssl/rand.h>
wxDECLARE_EVENT(wxEVT_RESULT, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_RESULT, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_PROGRESS, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_PROGRESS, wxCommandEvent);
CemuUpdateWindow::CemuUpdateWindow(wxWindow* parent)
: wxDialog(parent, wxID_ANY, _("Cemu update"), wxDefaultPosition, wxDefaultSize,
wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX)
{
auto* sizer = new wxBoxSizer(wxVERTICAL);
m_gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(500, 20), wxGA_HORIZONTAL);
m_gauge->SetValue(0);
sizer->Add(m_gauge, 0, wxALL | wxEXPAND, 5);
auto* rows = new wxFlexGridSizer(0, 2, 0, 0);
rows->AddGrowableCol(1);
m_text = new wxStaticText(this, wxID_ANY, _("Checking for latest version..."));
rows->Add(m_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
{
auto* right_side = new wxBoxSizer(wxHORIZONTAL);
m_updateButton = new wxButton(this, wxID_ANY, _("Update"));
m_updateButton->Bind(wxEVT_BUTTON, &CemuUpdateWindow::OnUpdateButton, this);
right_side->Add(m_updateButton, 0, wxALL, 5);
m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel"));
m_cancelButton->Bind(wxEVT_BUTTON, &CemuUpdateWindow::OnCancelButton, this);
right_side->Add(m_cancelButton, 0, wxALL, 5);
rows->Add(right_side, 1, wxALIGN_RIGHT, 5);
}
m_changelog = new wxHyperlinkCtrl(this, wxID_ANY, _("Changelog"), wxEmptyString);
rows->Add(m_changelog, 0, wxLEFT | wxBOTTOM | wxRIGHT | wxEXPAND, 5);
sizer->Add(rows, 0, wxALL | wxEXPAND, 5);
SetSizerAndFit(sizer);
Centre(wxBOTH);
Bind(wxEVT_CLOSE_WINDOW, &CemuUpdateWindow::OnClose, this);
Bind(wxEVT_RESULT, &CemuUpdateWindow::OnResult, this);
Bind(wxEVT_PROGRESS, &CemuUpdateWindow::OnGaugeUpdate, this);
m_thread = std::thread(&CemuUpdateWindow::WorkerThread, this);
m_updateButton->Hide();
m_changelog->Hide();
}
CemuUpdateWindow::~CemuUpdateWindow()
{
m_order = WorkerOrder::Exit;
if (m_thread.joinable())
m_thread.join();
}
size_t CemuUpdateWindow::WriteStringCallback(char* ptr, size_t size, size_t nmemb, void* userdata)
{
((std::string*)userdata)->append(ptr, size * nmemb);
return size * nmemb;
};
static std::string CurlUrlEscape(CURL* curl, const std::string& input)
{
char* escapedStr = curl_easy_escape(curl, input.c_str(), input.size());
std::string r(escapedStr);
curl_free(escapedStr);
return r;
}
static std::string CurlUrlUnescape(CURL* curl, std::string_view input)
{
int decodedLen = 0;
char* decoded = curl_easy_unescape(curl, input.data(), input.size(), &decodedLen);
std::string r(decoded, decodedLen);
curl_free(decoded);
return r;
}
static std::string GenerateSecureRandomString()
{
std::array<unsigned char, 20> bytes{};
if (RAND_bytes(bytes.data(), bytes.size()) != 1)
return "";
std::string result;
result.reserve(21);
for (unsigned char byte : bytes)
{
result.push_back('a' + (byte % 26));
}
return result;
}
static bool HexToBytes(const std::string& hex, std::vector<uint8>& dataOut)
{
if (hex.size() % 2)
return false;
dataOut.clear();
dataOut.reserve(hex.size() / 2);
for (size_t i = 0; i < hex.size(); i += 2)
{
unsigned int x;
auto [p, ec] = std::from_chars(hex.data() + i, hex.data() + i + 2, x, 16);
if (ec != std::errc{} || p != hex.data() + i + 2)
return false;
dataOut.push_back(static_cast<uint8>(x));
}
return true;
}
static bool VerifyUpdateResponseSignature(std::string_view message, std::string signatureAsHex)
{
std::vector<uint8> signatureData;
if (!HexToBytes(signatureAsHex, signatureData))
return false;
EVP_PKEY* key = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, nullptr, (const unsigned char*)"\xa0\x73\xda\x45\x12\x11\xd6\xe0\x2d\x7b\x1a\xb3\x69\xfd\x6c\xc5\x99\xd3\xd3\x1e\xe7\x9d\x57\x89\x2a\xe6\xe8\x40\x54\x7c\xc5\xbd", 32);
if (!key)
return false;
EVP_MD_CTX* context = EVP_MD_CTX_new();
if (!context)
{
EVP_PKEY_free(key);
return false;
}
bool valid = EVP_DigestVerifyInit(context, nullptr, nullptr, nullptr, key) == 1 &&
EVP_DigestVerify(context,signatureData.data(),signatureData.size(), reinterpret_cast<const unsigned char*>(message.data()), message.size()) == 1;
EVP_MD_CTX_free(context);
EVP_PKEY_free(key);
return valid;
}
// returns empty string for platforms which cant auto-update or dont have support for it yet
static std::string GetPlatformUpdateIdentifier()
{
std::string identifier;
#if BOOST_OS_LINUX
const char* appImagePath = std::getenv("APPIMAGE");
if (appImagePath && *appImagePath)
identifier.append("linux_appimage");
else
return "";
#elif BOOST_OS_WINDOWS
identifier.append("windows");
#elif BOOST_OS_MACOS
identifier.append("macos_bundle");
#elif BOOST_OS_BSD
return ""; // BSD users must update from source
#else
return "";
#endif
#if defined(__aarch64__)
identifier.append("_aarch64");
#elif defined(ARCH_X86_64)
identifier.append("_x86_64");
#else
return "";
#endif
return identifier;
}
// returns true if update is available and sets output parameters
bool CemuUpdateWindow::QueryUpdateInfo(std::string& downloadUrlOut, std::string& changelogUrlOut, std::array<uint8, 32>& fileSha256Out)
{
std::string rdString = GenerateSecureRandomString();
if (rdString.empty())
return false;
std::string buffer;
std::string urlStr("https://cemu.info/api2/version.php?v=");
auto* curl = curl_easy_init();
urlStr.append(CurlUrlEscape(curl, BUILD_VERSION_STRING));
std::string platformIdentifier = GetPlatformUpdateIdentifier();
if (platformIdentifier.empty())
return false;
urlStr.append("&platform=");
urlStr.append(platformIdentifier);
const auto& config = GetWxGUIConfig();
urlStr.append("&s=");
urlStr.append(rdString);
if(config.receive_untested_updates)
urlStr.append("&allowNewUpdates=1");
curl_easy_setopt(curl, CURLOPT_URL, urlStr.c_str());
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteStringCallback);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
bool result = false;
CURLcode cr = curl_easy_perform(curl);
if (cr == CURLE_OK)
{
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code != 0 && http_code != 200)
{
cemuLog_log(LogType::Force, "Update check failed (http code: {})", http_code);
cemu_assert_debug(false);
return false;
}
std::vector<std::string> tokens;
const boost::char_separator<char> sep{ "|" };
for (const auto& token : boost::tokenizer(buffer, sep))
tokens.emplace_back(token);
if (tokens.size() >= 5 && tokens[0] == "UPDATE")
{
// first token: "UPDATE"
// second token: Download URL
// third token: Changelog URL
// fourth token: SHA256
// fifth token: Signature
// we allow more tokens in case we ever want to add extra information for future releases
downloadUrlOut = CurlUrlUnescape(curl, tokens[1]);
changelogUrlOut = CurlUrlUnescape(curl, tokens[2]);
if (!downloadUrlOut.empty() && !changelogUrlOut.empty())
result = true;
// check signature
std::string signedMessage = platformIdentifier.append("|").append(tokens[0]).append("|").append(tokens[1]).append("|").append(tokens[2]).append("|").append(tokens[3]).append("|").append(rdString);
bool isValidSignature = VerifyUpdateResponseSignature(signedMessage, tokens[4]);
if (!isValidSignature)
{
cemuLog_log(LogType::Force, "Failed server signature check");
return false;
}
std::vector<uint8> fileShaVec;
if (!HexToBytes(tokens[3], fileShaVec))
return false;
if (fileShaVec.size() != 32)
return false;
std::memcpy(fileSha256Out.data(), fileShaVec.data(), 32);
}
}
else
{
cemuLog_log(LogType::Force, "Update check failed with CURL error {}", (int)cr);
cemu_assert_debug(false);
}
curl_easy_cleanup(curl);
return result;
}
std::future<bool> CemuUpdateWindow::IsUpdateAvailableAsync()
{
return std::async(std::launch::async, CheckVersion);
}
bool CemuUpdateWindow::CheckVersion()
{
std::string downloadUrl, changelogUrl;
std::array<uint8, 32> fileSha256;
return QueryUpdateInfo(downloadUrl, changelogUrl, fileSha256);
}
int CemuUpdateWindow::ProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
auto* thisptr = (CemuUpdateWindow*)clientp;
auto* event = new wxCommandEvent(wxEVT_PROGRESS);
event->SetInt((int)dlnow);
wxQueueEvent(thisptr, event);
return 0;
}
void CemuUpdateWindow::SubmitWorkerResult(Result newWorkerState)
{
auto* event = new wxCommandEvent(wxEVT_RESULT);
event->SetInt((int)newWorkerState);
wxQueueEvent(this, event);
}
static std::atomic<bool> s_cemuUpdateCancelRequest{false};
bool CemuUpdateWindow::DownloadCemuUpdateFile(const std::string& url, const fs::path& filename)
{
if (!fs::exists(filename.parent_path()))
fs::create_directories(filename.parent_path());
auto* curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
curl_easy_setopt(curl, CURLOPT_USERAGENT, BUILD_VERSION_WITH_NAME_STRING);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
auto r = curl_easy_perform(curl);
if (r != CURLE_OK)
{
cemuLog_log(LogType::Force, "Cemu update download failed with error {}", r);
curl_easy_cleanup(curl);
return false;
}
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code != 0 && http_code != 200)
{
cemuLog_log(LogType::Force, "Unable to download cemu update file from {} (http error: {})", url, http_code);
curl_easy_cleanup(curl);
return false;
}
curl_off_t update_size;
if (curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &update_size) == CURLE_OK)
m_gaugeMaxValue = (int)update_size;
std::vector<uint8> fileBuffer;
s_cemuUpdateCancelRequest = false;
auto _curlWriteData = +[](void* ptr, size_t size, size_t nmemb, void* ctx) -> size_t
{
if (s_cemuUpdateCancelRequest)
return 0;
std::vector<uint8>* fileBufferVec = (std::vector<uint8>*)ctx;
const size_t writeSize = size * nmemb;
fileBufferVec->insert(fileBufferVec->end(), (uint8*)ptr, (uint8*)ptr + writeSize);
return writeSize;
};
curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _curlWriteData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &fileBuffer);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallback);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, this);
auto curl_result = std::async(std::launch::async, [](CURL* curl, long* http_code)
{
const auto r = curl_easy_perform(curl);
return r;
}, curl, &http_code);
while (true)
{
auto r = curl_result.wait_for(std::chrono::milliseconds(100));
if (r == std::future_status::ready)
break;
if (m_order == WorkerOrder::Exit && !s_cemuUpdateCancelRequest)
s_cemuUpdateCancelRequest = true;
}
curl_easy_cleanup(curl);
CURLcode curlResult = curl_result.get();
if (curlResult == CURLE_ABORTED_BY_CALLBACK)
{
return false;
}
else if (curlResult != CURLE_OK)
{
return false;
}
// store file to temp location
std::error_code ec;
fs::remove(filename, ec);
if (!FileStream::WriteFileAtomic(filename, fileBuffer))
{
cemuLog_log(LogType::Force, "Failed to store update file");
return false;
}
// read back file and verify the hash
auto diskFileData = FileStream::LoadIntoMemory(filename);
if (!diskFileData)
{
cemuLog_log(LogType::Force, "Failed to read back update file");
return false;
}
// verify the file hash
std::array<uint8_t, 32> hash;
EVP_Q_digest(nullptr, "SHA256", nullptr, diskFileData->data(), diskFileData->size(), hash.data(), nullptr);
if (hash != m_fileSHA256)
{
cemuLog_log(LogType::Force, "File hash of downloaded update does not match");
fs::remove(filename, ec);
return false;
}
SubmitWorkerResult(Result::UpdateDownloaded);
return true;
}
bool CemuUpdateWindow::ExtractZipUpdate(const fs::path& zipname, const fs::path& targetpath)
{
ZipArchive archive(zipname);
if (!archive.IsValid())
{
cemuLog_log(LogType::Force, "Cannot open zip file: {}", _pathToUtf8(zipname));
return false;
}
const size_t count = archive.GetNumEntries();
m_gaugeMaxValue = count;
// the update files are in a directory called "Cemu_<version>". Find the name of the directory
std::string cemuPathPrefix;
for (size_t i=0; i<count; i++)
{
ZipArchive::ZipFileEntry entry;
if (!archive.GetFileEntryByIndex(i, entry))
continue;
std::string_view firstDir = std::string_view(entry.fullPath).substr(0, entry.fullPath.find_first_of('/'));
if (!firstDir.empty() && firstDir.length() != entry.fullPath.length() && firstDir.starts_with("Cemu_"))
{
if (!cemuPathPrefix.empty() && cemuPathPrefix != firstDir)
{
cemuLog_log(LogType::Force, "Cemu update zip contains multiple Cemu directories in root? Found {} and {}", firstDir, cemuPathPrefix);
return false;
}
cemuPathPrefix = firstDir;
}
}
if (cemuPathPrefix.empty())
{
cemuLog_log(LogType::Force, "Cemu update zip does not match expected structure");
return false;
}
if (m_order == WorkerOrder::Exit)
return false;
std::string cemuPathPrefxWithSlash = cemuPathPrefix + "/";
// extract each file's data first to force CRC checks and we create the folders already
// this acts as a basic permission check too
for (size_t i = 0; i < count; i++)
{
ZipArchive::ZipFileEntry entry;
if (archive.GetFileEntryByIndex(i, entry))
{
if (!entry.fullPath.starts_with(cemuPathPrefxWithSlash))
continue; // ignore files outside the directory, there shouldn't be any but future releases might add additional files so we'll allow it just in case
const fs::path entryPath = _utf8ToPath(entry.fullPath.substr(cemuPathPrefxWithSlash.length()));
// read full file into memory
std::vector<uint8> fileData;
if (!archive.ExtractIntoMemoryByIndex(i, fileData))
{
cemuLog_log(LogType::Force, "Failed to extract file {}", entry.fullPath);
return false;
}
// make sure the folder exists
const fs::path fname = targetpath / entryPath;
try
{
fs::create_directories(fname.parent_path());
}
catch (const std::exception& ex)
{
SystemException sys(ex);
cemuLog_log(LogType::Force, "can't create folder for update file \"{}\": {}", entry.fullPath, sys.what());
return false;
}
}
}
// check if there is enough disk space (to keep this simple we assume 100MB, generally releases should be below that)
std::error_code ec;
const fs::space_info targetSpace = fs::space(ActiveSettings::GetExecutablePath().parent_path(), ec);
if (targetSpace.free <= 100 * 1024 * 1024)
{
cemuLog_log(LogType::Force, "Not enough disk space available for update");
return false;
}
// actually extract and replace files
for (size_t i = 0; i < count; i++)
{
ZipArchive::ZipFileEntry entry;
if (archive.GetFileEntryByIndex(i, entry))
{
if (!entry.fullPath.starts_with(cemuPathPrefxWithSlash))
continue;
const fs::path entryPath = _utf8ToPath(entry.fullPath.substr(cemuPathPrefxWithSlash.length()));
// read into memory
std::vector<uint8> fileData;
if (!archive.ExtractIntoMemoryByIndex(i, fileData))
{
cemu_assert(false); // extraction tested earlier, it should not fail here
}
const fs::path fname = targetpath / entryPath;
if ( !FileStream::WriteFileAtomic(fname, fileData, true) )
{
cemuLog_log(LogType::Force, "Failed to update {}", _pathToUtf8(fname));
// continue regardless
}
if ((i / 10) * 10 == i)
{
auto* event = new wxCommandEvent(wxEVT_PROGRESS);
event->SetInt(static_cast<int>(i));
wxQueueEvent(this, event);
}
}
}
auto* event = new wxCommandEvent(wxEVT_PROGRESS);
event->SetInt(m_gaugeMaxValue);
wxQueueEvent(this, event);
return true;
}
#if BOOST_OS_WINDOWS
bool CemuUpdateWindow::WorkerThread_Windows()
{
const auto tmppath = fs::temp_directory_path() / L"cemu_update";
std::error_code ec;
if (exists(tmppath))
remove_all(tmppath, ec);
const auto updateSrcFile = tmppath / L"update.zip";
if (!DownloadCemuUpdateFile(m_downloadUrl, updateSrcFile))
return false;
if (m_order == WorkerOrder::Exit)
return false;
// extract and replace files
if (!ExtractZipUpdate(updateSrcFile, ActiveSettings::GetExecutablePath().parent_path()))
{
cemuLog_log(LogType::Force, "Extracting Cemu zip failed");
return false;
}
SubmitWorkerResult(Result::ExtractSuccess);
// set relaunch path
fs::path newExePath = ActiveSettings::GetExecutablePath();
newExePath = newExePath.parent_path().append("Cemu.exe");
m_restartFile = newExePath;
return true;
}
#endif
#if BOOST_OS_LINUX
bool CemuUpdateWindow::WorkerThread_AppImage()
{
const auto tmppath = fs::temp_directory_path() / L"cemu_update";
std::error_code ec;
if (exists(tmppath, ec))
remove_all(tmppath, ec);
const auto updateSrcFile = tmppath / L"Cemu.AppImage";
if (!DownloadCemuUpdateFile(m_downloadUrl, updateSrcFile))
return false;
if (m_order == WorkerOrder::Exit)
return false;
const char* appimage_path = std::getenv("APPIMAGE");
auto backupExecutable = fs::path(appimage_path);
backupExecutable.replace_extension( _utf8ToPath(_pathToUtf8(backupExecutable.extension()).append(".backup")));
const char* filePath = updateSrcFile.c_str();
mode_t permissions = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
fs::rename(appimage_path, backupExecutable, ec);
if (ec)
{
cemuLog_log(LogType::Force, "Failed to rename current .AppImage for update replacement");
return false;
}
m_restartFile = appimage_path;
chmod(filePath, permissions);
wxString wxAppPath = wxString::FromUTF8(appimage_path);
wxCopyFile (wxString::FromUTF8(_pathToUtf8(updateSrcFile)), wxAppPath);
return true;
}
#endif
#if BOOST_OS_MACOS
bool CemuUpdateWindow::WorkerThread_MacBundle()
{
const fs::path tempPath = fs::temp_directory_path() / "cemu_update";
std::error_code ec;
fs::remove_all(tempPath, ec);
fs::create_directories(tempPath, ec);
if (ec)
return false;
const fs::path dmgPath = tempPath / "cemu.dmg";
if (!DownloadCemuUpdateFile(m_downloadUrl, dmgPath))
return false;
if (m_order == WorkerOrder::Exit)
return false;
// .../Cemu.app/Contents/MacOS/Cemu
const fs::path executable = ActiveSettings::GetExecutablePath();
const fs::path appBundle = executable.parent_path().parent_path().parent_path();
if (appBundle.extension() != ".app")
{
cemuLog_log(LogType::Force, "Cemu is not running from a macOS app bundle");
return false;
}
const fs::path scriptPath = tempPath / "update.sh";
const fs::path mountPath = tempPath / "mount";
auto ShellQuote = [](std::string_view value)
{
std::string result = "'";
for (const char c : value)
{
if (c == '\'')
result += "'\\''";
else
result += c;
}
result += "'";
return result;
};
std::string dmg = ShellQuote(_pathToUtf8(dmgPath));
std::string app = ShellQuote(_pathToUtf8(appBundle));
std::string mount = ShellQuote(_pathToUtf8(mountPath));
std::string script;
script += "#!/bin/sh\n";
script += "DMG_PATH=" + ShellQuote(_pathToUtf8(dmgPath)) + "\n";
script += "APP_BUNDLE=" + ShellQuote(_pathToUtf8(appBundle)) + "\n";
script += "MOUNT_PATH=" + ShellQuote(_pathToUtf8(mountPath)) + "\n";
script += R"SH(
set -eu
STAGED_BUNDLE="${APP_BUNDLE}.update"
BACKUP_BUNDLE="${APP_BUNDLE}.backup"
MOUNTED=0
finish()
{
STATUS=$?
trap - EXIT HUP INT TERM
if [ "$MOUNTED" -eq 1 ]; then
/usr/bin/hdiutil detach "$MOUNT_PATH" >/dev/null 2>&1 || true
fi
if [ -d "$APP_BUNDLE" ]; then
/usr/bin/open -n "$APP_BUNDLE" || true
elif [ -x "$BACKUP_BUNDLE/Contents/MacOS/Cemu" ]; then
"$BACKUP_BUNDLE/Contents/MacOS/Cemu" &
fi
exit "$STATUS"
}
trap finish EXIT HUP INT TERM
/bin/rm -rf "$MOUNT_PATH" "$STAGED_BUNDLE"
/bin/mkdir -p "$MOUNT_PATH"
/usr/bin/hdiutil attach \
-nobrowse \
-readonly \
-mountpoint "$MOUNT_PATH" \
"$DMG_PATH"
MOUNTED=1
/usr/bin/ditto \
"$MOUNT_PATH/Cemu.app" \
"$STAGED_BUNDLE"
/usr/bin/codesign \
--verify \
--strict \
"$STAGED_BUNDLE/Contents/MacOS/Cemu"
/usr/bin/hdiutil detach "$MOUNT_PATH"
MOUNTED=0
/bin/rm -rf "$BACKUP_BUNDLE"
/bin/mv "$APP_BUNDLE" "$BACKUP_BUNDLE"
if ! /bin/mv "$STAGED_BUNDLE" "$APP_BUNDLE"; then
/bin/mv "$BACKUP_BUNDLE" "$APP_BUNDLE" || true
exit 1
fi
)SH";
std::vector<uint8> scriptData(script.begin(), script.end());
if (!FileStream::WriteFileAtomic(scriptPath, scriptData))
{
cemuLog_log(LogType::Force, "Failed to create macOS updater script");
return false;
}
if (chmod(scriptPath.c_str(), S_IRUSR | S_IWUSR | S_IXUSR) != 0)
{
cemuLog_log(LogType::Force, "Failed to make updater script executable");
return false;
}
// run the script on exit
m_restartFile = scriptPath;
return true;
}
#endif
void CemuUpdateWindow::WorkerThread()
{
// start writing log.txt if not created yet
cemuLog_createLogFile(false);
while (true)
{
std::unique_lock lock(m_mutex);
WorkerOrder nextWorkOrder = WorkerOrder::Idle;
while (true)
{
nextWorkOrder = m_order.exchange(WorkerOrder::Idle);
if (nextWorkOrder != WorkerOrder::Idle)
break;
m_condition.wait_for(lock, std::chrono::milliseconds(125));
}
if (nextWorkOrder == WorkerOrder::Exit)
break;
if (nextWorkOrder == WorkerOrder::CheckVersion)
{
auto* event = new wxCommandEvent(wxEVT_RESULT);
if (QueryUpdateInfo(m_downloadUrl, m_changelogUrl, m_fileSHA256))
event->SetInt((int)Result::UpdateAvailable);
else
event->SetInt((int)Result::NoUpdateAvailable);
wxQueueEvent(this, event);
}
else if (nextWorkOrder == WorkerOrder::UpdateVersion)
{
// download update
const std::string url = m_downloadUrl;
bool r = false;
#if BOOST_OS_WINDOWS
r = WorkerThread_Windows();
#elif BOOST_OS_LINUX
r = WorkerThread_AppImage();
#elif BOOST_OS_BSD
// dummy placeholder on BSD for now
#elif BOOST_OS_MACOS
r = WorkerThread_MacBundle();
#endif
// update done
if (r)
{
// report success
auto* event = new wxCommandEvent(wxEVT_PROGRESS);
event->SetInt(m_gaugeMaxValue);
wxQueueEvent(this, event);
auto* result_event = new wxCommandEvent(wxEVT_RESULT);
result_event->SetInt((int)Result::Success);
wxQueueEvent(this, result_event);
}
else if (m_order != WorkerOrder::Exit)
{
SubmitWorkerResult(Result::UpdateDownloadError);
}
}
}
}
void CemuUpdateWindow::OnClose(wxCloseEvent& event)
{
event.Skip();
if (m_restartRequired)
{
wxGetApp().RequestRestart(m_restartFile);
}
}
void CemuUpdateWindow::OnResult(wxCommandEvent& event)
{
switch ((Result)event.GetInt())
{
case Result::NoUpdateAvailable:
m_cancelButton->SetLabel(_("Exit"));
m_text->SetLabel(_("No update available!"));
m_gauge->SetValue(100);
break;
case Result::UpdateAvailable:
{
if (!m_changelogUrl.empty())
{
m_changelog->SetURL(m_changelogUrl);
m_changelog->Show();
}
else
m_changelog->Hide();
m_updateButton->Show();
m_text->SetLabel(_("Update available!"));
m_cancelButton->SetLabel(_("Exit"));
break;
}
case Result::UpdateDownloaded:
m_text->SetLabel(_("Extracting update..."));
m_gauge->SetValue(0);
break;
case Result::UpdateDownloadError:
m_updateButton->Enable();
m_cancelButton->Enable();
m_text->SetLabel(_("Couldn't download the update!"));
break;
case Result::ExtractSuccess:
m_text->SetLabel(_("Applying update..."));
m_gauge->SetValue(0);
m_cancelButton->Disable();
break;
case Result::ExtractError:
m_updateButton->Enable();
m_cancelButton->Enable();
m_text->SetLabel(_("Extracting failed!"));
break;
case Result::Success:
m_cancelButton->Enable();
m_updateButton->Hide();
m_text->SetLabel(_("Success"));
m_cancelButton->SetLabel(_("Restart"));
m_restartRequired = true;
break;
default:;
}
}
void CemuUpdateWindow::OnGaugeUpdate(wxCommandEvent& event)
{
const int total_size = m_gaugeMaxValue > 0 ? m_gaugeMaxValue : 10000000;
m_gauge->SetValue((event.GetInt() * 100) / total_size);
}
void CemuUpdateWindow::OnUpdateButton(const wxCommandEvent& event)
{
std::unique_lock lock(m_mutex);
m_order = WorkerOrder::UpdateVersion;
m_condition.notify_all();
m_updateButton->Disable();
m_text->SetLabel(_("Downloading update..."));
}
void CemuUpdateWindow::OnCancelButton(const wxCommandEvent& event)
{
Close();
}
-84
View File
@@ -1,84 +0,0 @@
#pragma once
#include <wx/dialog.h>
#include <wx/stattext.h>
#include <wx/gauge.h>
#include <wx/timer.h>
#include <wx/hyperlink.h>
#include <wx/checkbox.h>
#include <curl/system.h>
class CemuUpdateWindow : public wxDialog
{
public:
CemuUpdateWindow(wxWindow* parent);
~CemuUpdateWindow();
static std::future<bool> IsUpdateAvailableAsync();
private:
wxStaticText* m_text;
wxGauge* m_gauge;
wxButton* m_cancelButton, *m_updateButton;
wxHyperlinkCtrl* m_changelog;
void OnUpdateButton(const wxCommandEvent& event);
void OnCancelButton(const wxCommandEvent& event);
void OnClose(wxCloseEvent& event);
void OnResult(wxCommandEvent& event);
void OnGaugeUpdate(wxCommandEvent& event);
static size_t WriteStringCallback(char* ptr, size_t size, size_t nmemb, void* userdata);
static bool QueryUpdateInfo(std::string& downloadUrlOut, std::string& changelogUrlOut, std::array<uint8, 32>& fileSha256Out);
static bool CheckVersion();
static int ProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
bool DownloadCemuUpdateFile(const std::string& url, const fs::path& filename);
bool ExtractZipUpdate(const fs::path& zipname, const fs::path& targetpath);
enum class WorkerOrder
{
Idle,
InProgress, // work command received
Exit,
CheckVersion,
UpdateVersion,
};
enum class Result
{
NoUpdateAvailable,
UpdateAvailable,
UpdateDownloaded,
UpdateDownloadError,
ExtractSuccess,
ExtractError,
Success,
Error
};
std::mutex m_mutex;
std::condition_variable m_condition;
std::atomic<WorkerOrder> m_order = WorkerOrder::CheckVersion;
void WorkerThread();
void SubmitWorkerResult(Result newWorkerState);
#if BOOST_OS_WINDOWS
bool WorkerThread_Windows();
#endif
#if BOOST_OS_LINUX
bool WorkerThread_AppImage();
#endif
#if BOOST_OS_MACOS
bool WorkerThread_MacBundle();
#endif
std::string m_downloadUrl, m_changelogUrl;
std::array<uint8, 32> m_fileSHA256;
int m_gaugeMaxValue = 0;
std::thread m_thread;
fs::path m_restartFile;
bool m_restartRequired = false;
};
-792
View File
@@ -1,792 +0,0 @@
#include "wxgui/ChecksumTool.h"
#include "Cafe/TitleList/GameInfo.h"
#include "wxgui/helpers/wxCustomEvents.h"
#include "util/helpers/helpers.h"
#include "wxgui/helpers/wxHelpers.h"
#include "wxgui/wxHelper.h"
#include "Cafe/Filesystem/WUD/wud.h"
#include <zip.h>
#include <curl/curl.h>
#include <openssl/evp.h> /* EVP_Digest */
#include <openssl/sha.h> /* SHA256_DIGEST_LENGTH */
#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>
#include <rapidjson/ostreamwrapper.h>
#include <rapidjson/prettywriter.h>
#include <rapidjson/schema.h>
#include <wx/frame.h>
#include <wx/translation.h>
#include <wx/xrc/xmlres.h>
#include <wx/gauge.h>
#include <wx/gdicmn.h>
#include <wx/string.h>
#include <wx/sizer.h>
#include <wx/statbox.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include <wx/filedlg.h>
#include <wx/dirdlg.h>
#include <wx/msgdlg.h>
#include "config/ActiveSettings.h"
#include "Cafe/TitleList/TitleList.h"
const char kSchema[] = R"(
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"title_id": {
"type": "string"
},
"region": {
"type": "integer"
},
"version": {
"type": "integer"
},
"wud_hash": {
"type": "string"
},
"files": {
"type": "array",
"items": {
"type": "object",
"properties": {
"file": {
"type": "string"
},
"hash": {
"type": "string"
}
},
"required": [
"file",
"hash"
]
}
}
},
"required": [
"title_id",
"region",
"version",
"files"
]
})";
ChecksumTool::ChecksumTool(wxWindow* parent, wxTitleManagerList::TitleEntry& entry)
: wxDialog(parent, wxID_ANY,
formatWxString(_("Title checksum of {:08x}-{:08x}"), (uint32) (entry.title_id >> 32), (uint32) (entry.title_id & 0xFFFFFFFF)),
wxDefaultPosition, wxDefaultSize, wxCAPTION | wxFRAME_TOOL_WINDOW | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX), m_entry(entry)
{
m_info = CafeTitleList::GetTitleInfoByUID(m_entry.location_uid);
if (!m_info.IsValid())
throw std::runtime_error("Invalid title");
// only request online update once
static bool s_once = false;
if (!s_once)
{
s_once = true;
m_online_ready = std::async(std::launch::async, &ChecksumTool::LoadOnlineData, this);
}
else
m_enable_verify_button = 1;
auto* sizer = new wxBoxSizer(wxVERTICAL);
{
auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Verifying integrity of game files...")), wxVERTICAL);
auto* box = box_sizer->GetStaticBox();
m_progress = new wxGauge(box, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH);
m_progress->SetMinSize({ 400, -1 });
m_progress->SetValue(0);
box_sizer->Add(m_progress, 0, wxALL | wxEXPAND, 5);
m_status = new wxStaticText(box, wxID_ANY, wxEmptyString);
m_status->Wrap(-1);
box_sizer->Add(m_status, 0, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, 5);
sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
}
{
auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Control")), wxHORIZONTAL);
auto* box = box_sizer->GetStaticBox();
m_verify_online = new wxButton(box, wxID_ANY, _("Verify online"));
m_verify_online->SetToolTip(_("Verifies the checksum online"));
m_verify_online->Disable();
m_verify_online->Bind(wxEVT_BUTTON, &ChecksumTool::OnVerifyOnline, this);
m_verify_online->Bind(wxEVT_ENABLE, [this](wxCommandEvent&)
{
++m_enable_verify_button;
if (m_enable_verify_button >= 2)
{
// only enable if we have a file for it
const auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id);
const auto default_file = fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion());
const auto checksum_path = ActiveSettings::GetUserDataPath("resources/checksums/{}", default_file);
if (exists(checksum_path))
m_verify_online->Enable();
}
});
box_sizer->Add(m_verify_online, 0, wxALL | wxEXPAND, 5);
m_verify_local = new wxButton(box, wxID_ANY, _("Verify with local file"));
m_verify_local->SetToolTip(_("Verifies the checksum with a local JSON file you can select"));
m_verify_local->Disable();
m_verify_local->Bind(wxEVT_BUTTON, &ChecksumTool::OnVerifyLocal, this);
box_sizer->Add(m_verify_local, 0, wxALL | wxEXPAND, 5);
m_export_button = new wxButton(box, wxID_ANY, _("Export"));
m_export_button->SetToolTip(_("Export the title checksum data to a local JSON file"));
m_export_button->Disable();
m_export_button->Bind(wxEVT_BUTTON, &ChecksumTool::OnExportChecksums, this);
box_sizer->Add(m_export_button, 0, wxALL | wxEXPAND, 5);
sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
}
this->Bind(wxEVT_SET_GAUGE_VALUE, &ChecksumTool::OnSetGaugevalue, this);
m_worker = std::thread(&ChecksumTool::DoWork, this);
this->SetSizerAndFit(sizer);
this->Centre(wxBOTH);
}
ChecksumTool::~ChecksumTool()
{
m_running = false;
if (m_worker.joinable())
m_worker.join();
}
std::size_t WriteCallback(const char* in, std::size_t size, std::size_t num, std::string* out)
{
const std::size_t totalBytes(size * num);
out->append(in, totalBytes);
return totalBytes;
}
void ChecksumTool::LoadOnlineData() const
{
try
{
bool updated_required = true;
std::string latest_commit;
const auto checksum_path = ActiveSettings::GetUserDataPath("resources/checksums");
if (exists(checksum_path))
{
std::string current_commit;
// check for current version
std::ifstream file(checksum_path / "commit.txt");
if (file.is_open())
{
std::getline(file, current_commit);
file.close();
}
// check latest version
/*
https://api.github.com/repos/cemu-project/title-checksums/branches/master
https://api.github.com/repos/cemu-project/title-checksums/commits?per_page=1
*/
std::string data;
auto* curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, "https://api.github.com/repos/cemu-project/title-checksums/commits/master");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
curl_easy_setopt(curl, CURLOPT_USERAGENT, BUILD_VERSION_WITH_NAME_STRING);
curl_easy_perform(curl);
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl);
if (http_code == 200 && !data.empty())
{
rapidjson::Document doc;
doc.Parse(data.c_str(), data.size());
if (!doc.HasParseError() && doc.HasMember("sha"))
{
latest_commit = doc["sha"].GetString();
if (boost::iequals(current_commit, latest_commit))
{
updated_required = false;
}
}
}
}
else
{
// create directory since not available yet
fs::create_directories(checksum_path);
}
if (updated_required)
{
std::string data;
auto* curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, "https://github.com/cemu-project/title-checksums/archive/master.zip");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_USERAGENT, BUILD_VERSION_WITH_NAME_STRING);
curl_easy_perform(curl);
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl);
if (http_code == 200 && !data.empty())
{
// init zip source
zip_error_t error;
zip_error_init(&error);
zip_source_t* src;
if ((src = zip_source_buffer_create(data.data(), data.size(), 0, &error)) == nullptr)
{
zip_error_fini(&error);
return;
}
auto* za = zip_open_from_source(src, ZIP_RDONLY, &error);
if (!za)
{
zip_source_free(src);
zip_error_fini(&error);
wxQueueEvent(m_verify_online, new wxCommandEvent(wxEVT_ENABLE));
return;
}
const auto numEntries = zip_get_num_entries(za, 0);
for (sint64 i = 0; i < numEntries; i++)
{
zip_stat_t sb = { 0 };
if (zip_stat_index(za, i, 0, &sb) != 0)
continue;
if (std::strstr(sb.name, "../") != nullptr ||
std::strstr(sb.name, "..\\") != nullptr)
continue; // bad path
if (boost::equals(sb.name, "title-checksums-master/"))
continue;
// title-checksums-master/
const auto path = checksum_path / &sb.name[sizeof("title-checksums-master")];
size_t sbNameLen = strlen(sb.name);
if (sbNameLen == 0)
continue;
if (sb.name[sbNameLen - 1] == '/')
{
std::error_code ec;
fs::create_directories(path, ec);
continue;
}
if (sb.size == 0)
continue;
if (sb.size > (1024 * 1024 * 128))
continue; // skip unusually huge files
zip_file_t* zipFile = zip_fopen_index(za, i, 0);
if (zipFile == nullptr)
continue;
std::vector<char> buffer(sb.size);
if (zip_fread(zipFile, buffer.data(), sb.size) == sb.size)
{
std::ofstream file(path);
if (file.is_open())
{
file.write(buffer.data(), sb.size);
file.flush();
file.close();
}
}
zip_fclose(zipFile);
}
zip_discard(za);
zip_error_fini(&error);
std::ofstream file(checksum_path / "commit.txt");
if (file.is_open())
{
file << latest_commit;
file.close();
}
}
}
}
catch(const std::exception& ex)
{
cemuLog_log(LogType::Force, "error on updating json checksum data: {}", ex.what());
}
wxQueueEvent(m_verify_online, new wxCommandEvent(wxEVT_ENABLE));
}
void ChecksumTool::OnSetGaugevalue(wxSetGaugeValue& event)
{
event.GetGauge()->SetValue(event.GetValue());
event.GetTextCtrl()->SetLabelText(event.GetText());
// no error
if(event.GetInt() == 0 && event.GetValue() == 100)
{
m_export_button->Enable();
m_verify_local->Enable();
wxPostEvent(m_verify_online, wxCommandEvent(wxEVT_ENABLE));
}
}
void ChecksumTool::OnExportChecksums(wxCommandEvent& event)
{
// TODO: merge if json already exists
wxDirDialog dialog(this, _("Export checksum entry"), "", wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
if (dialog.ShowModal() != wxID_OK || dialog.GetPath().IsEmpty())
return;
rapidjson::Document doc;
doc.SetObject();
auto& a = doc.GetAllocator();
/*
title_id
region
version
wud_hash
files:
[
{file, hash}
]
*/
auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id);
doc.AddMember("title_id", rapidjson::StringRef(title_id_str.c_str(), title_id_str.size()), a);
doc.AddMember("region", (int)m_info.GetMetaRegion(), a);
doc.AddMember("version", m_info.GetAppTitleVersion(), a);
if (!m_json_entry.wud_hash.empty())
doc.AddMember("wud_hash", rapidjson::StringRef(m_json_entry.wud_hash.c_str(), m_json_entry.wud_hash.size()), a);
rapidjson::Value entry_array(rapidjson::kArrayType);
rapidjson::Value file_array(rapidjson::kArrayType);
for(const auto& file : m_json_entry.file_hashes)
{
rapidjson::Value file_entry;
file_entry.SetObject();
file_entry.AddMember("file", rapidjson::StringRef(file.first.c_str(), file.first.size()), a);
file_entry.AddMember("hash", rapidjson::StringRef(file.second.c_str(), file.second.size()), a);
file_array.PushBack(file_entry, a);
}
doc.AddMember("files", file_array, a);
std::filesystem::path target_file{ dialog.GetPath().c_str().AsInternal() };
target_file /= fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion());
std::ofstream file(target_file);
if(file.is_open())
{
rapidjson::OStreamWrapper osw(file);
rapidjson::PrettyWriter<rapidjson::OStreamWrapper> writer(osw);
//rapidjson::GenericSchemaValidator<rapidjson::SchemaDocument, rapidjson::Writer<rapidjson::StringBuffer> > validator(schema, writer);
doc.Accept(writer);
wxMessageBox(_("Export successful"), wxMessageBoxCaptionStr, wxOK | wxCENTRE, this);
}
else
{
wxMessageBox(formatWxString(_("Can't write to file: {}"), target_file.string()), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
}
}
void ChecksumTool::VerifyJsonEntry(const rapidjson::Document& doc)
{
rapidjson::Document sdoc;
sdoc.Parse(kSchema, std::size(kSchema));
wxASSERT(!sdoc.HasParseError());
rapidjson::SchemaDocument schema(sdoc);
rapidjson::SchemaValidator validator(schema);
if (!doc.Accept(validator))
{
//// validation error:
//rapidjson::StringBuffer sb;
//validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);
//printf("Invalid schema: %s\n", sb.GetString());
//printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword());
//sb.Clear();
//validator.GetInvalidDocumentPointer().StringifyUriFragment(sb);
//printf("Invalid document: %s\n", sb.GetString());
///*
//Invalid schema: #
//Invalid keyword: required
//Invalid document: #
// */
wxMessageBox(_("JSON file doesn't satisfy needed schema"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
return;
}
try
{
JsonEntry test_entry{};
test_entry.title_id = ConvertString<uint64>(doc["title_id"].GetString(), 16);
test_entry.region = (CafeConsoleRegion)doc["region"].GetInt();
test_entry.version = doc["version"].GetInt();
if (doc.HasMember("wud_hash"))
test_entry.wud_hash = doc["wud_hash"].GetString();
for (const auto& v : doc["files"].GetArray())
{
std::filesystem::path genericFilePath(v["file"].GetString(), std::filesystem::path::generic_format); // convert path to generic form (forward slashes)
test_entry.file_hashes[genericFilePath.generic_string()] = v["hash"].GetString();
}
if (m_json_entry.title_id != test_entry.title_id)
{
wxMessageBox(formatWxString(_("The file you are comparing with is for a different title.")), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
return;
}
if (m_json_entry.version != test_entry.version)
{
wxMessageBox(formatWxString(_("Wrong version: {}"), test_entry.version), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
return;
}
if (m_json_entry.region != test_entry.region)
{
wxMessageBox(formatWxString(_("Wrong region: {}"), test_entry.region), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
return;
}
if (!m_json_entry.wud_hash.empty())
{
if (test_entry.wud_hash.empty())
{
wxMessageBox(_("The verification data doesn't include a WUD hash!"), _("Error"), wxOK | wxCENTRE | wxICON_WARNING, this);
return;
}
if(!boost::iequals(test_entry.wud_hash, m_json_entry.wud_hash))
{
wxMessageBox(formatWxString(_("Your game image is invalid!\n\nYour hash:\n{}\n\nExpected hash:\n{}"), m_json_entry.wud_hash, test_entry.wud_hash), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
return;
}
}
else
{
std::map<std::string_view, std::pair<std::string, std::string>> invalid_hashes;
std::vector<std::string_view> missing_files;
const auto writeMismatchInfoToLog = [this, &missing_files, &invalid_hashes]()
{
wxFileDialog dialog(this, _("Select a file to export the errors"), wxEmptyString, wxEmptyString, "Error list (*.txt)|*.txt", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (dialog.ShowModal() != wxID_OK || dialog.GetPath().IsEmpty())
return;
const std::string path = dialog.GetPath().utf8_string();
std::ofstream file(path);
if (file.is_open())
{
if (!missing_files.empty())
{
file << "The following files are missing:\n";
for (const auto& f : missing_files)
file << "\t" << f << "\n";
file << "\n";
}
if (!invalid_hashes.empty())
{
file << "The following files have an invalid hash (name | current hash | expected hash):\n";
for (const auto& f : invalid_hashes)
file << "\t" << f.first << " | " << f.second.first << " | " << f.second.second << "\n";
}
file.flush();
file.close();
wxLaunchDefaultBrowser(formatWxString("file:{}", path));
}
else
wxMessageBox(_("Can't open file to write!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
};
for (const auto& f : test_entry.file_hashes)
{
const auto it = m_json_entry.file_hashes.find(f.first);
if (it == m_json_entry.file_hashes.cend())
{
missing_files.emplace_back(f.first);
}
else if (!boost::iequals(f.second, it->second))
{
invalid_hashes[f.first] = std::make_pair(it->second, f.second);
}
}
// files are missing but rest is okay
if ((!missing_files.empty() || !invalid_hashes.empty()) && (missing_files.size() + invalid_hashes.size()) < 30)
{
// the list of missing + invalid hashes is short enough that we can print it to the message box
std::stringstream str;
if (missing_files.size() > 0)
{
str << _("The following files are missing:").ToUTF8().data() << "\n";
for (const auto& v : missing_files)
str << v << "\n";
if(invalid_hashes.size() > 0)
str << "\n";
}
if (invalid_hashes.size() > 0)
{
str << _("The following files are damaged:").ToUTF8().data() << "\n";
for (const auto& v : invalid_hashes)
str << v.first << "\n";
}
wxMessageBox(str.str(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
return;
}
else if (missing_files.empty() && !invalid_hashes.empty())
{
const int result = wxMessageBox(formatWxString(
_("{} files have an invalid hash!\nDo you want to export a list of them to a file?"),
invalid_hashes.size()), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR, this);
if (result == wxYES)
{
writeMismatchInfoToLog();
}
return;
}
else if (!missing_files.empty() && !invalid_hashes.empty())
{
const int result = wxMessageBox(formatWxString(
_("Multiple issues with your game files have been found!\nDo you want to export them to a file?"),
invalid_hashes.size()), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR, this);
if (result == wxYES)
{
writeMismatchInfoToLog();
}
return;
}
}
wxMessageBox(_("Your game files are valid"), _("Success"), wxOK | wxCENTRE, this);
}
catch (const std::exception& ex)
{
wxMessageBox(formatWxString(_("JSON parse error: {}"), ex.what()), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
}
}
void ChecksumTool::OnVerifyOnline(wxCommandEvent& event)
{
const auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id);
const auto default_file = fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion());
const auto checksum_path = ActiveSettings::GetUserDataPath("resources/checksums/{}", default_file);
if(!exists(checksum_path))
return;
std::ifstream file(checksum_path);
if (!file.is_open())
{
wxMessageBox(_("Can't open file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
return;
}
rapidjson::IStreamWrapper str(file);
rapidjson::Document d;
d.ParseStream(str);
if (d.HasParseError())
{
wxMessageBox(_("Can't parse JSON file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
return;
}
VerifyJsonEntry(d);
}
void ChecksumTool::OnVerifyLocal(wxCommandEvent& event)
{
const auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id);
const auto default_file = fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion());
wxFileDialog file_dialog(this, _("Open checksum entry"), "", default_file.c_str(),"JSON files (*.json)|*.json", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (file_dialog.ShowModal() != wxID_OK || file_dialog.GetPath().IsEmpty())
return;
std::filesystem::path filename{ file_dialog.GetPath().c_str().AsInternal() };
std::ifstream file(filename);
if(!file.is_open())
{
wxMessageBox(_("Can't open file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
return;
}
rapidjson::IStreamWrapper str(file);
rapidjson::Document d;
d.ParseStream(str);
if (d.HasParseError())
{
wxMessageBox(_("Can't parse JSON file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
return;
}
VerifyJsonEntry(d);
}
static void _fscGetAllFiles(std::set<std::string>& allFilesOut, const std::string& fscBasePath, const std::string& relativePath)
{
sint32 fscStatus;
FSCVirtualFile* fsc = fsc_openDirIterator((fscBasePath + relativePath).c_str(), &fscStatus);
cemu_assert(fsc);
FSCDirEntry dirEntry;
while (fsc_nextDir(fsc, &dirEntry))
{
if (dirEntry.isDirectory)
{
_fscGetAllFiles(allFilesOut, fscBasePath, std::string(relativePath).append(dirEntry.GetPath()).append("/"));
}
else
{
allFilesOut.emplace(std::string(relativePath).append(dirEntry.GetPath()));
}
}
delete fsc;
}
void ChecksumTool::DoWork()
{
m_json_entry.title_id = m_info.GetAppTitleId();
m_json_entry.region = m_info.GetMetaRegion();
m_json_entry.version = m_info.GetAppTitleVersion();
static_assert(SHA256_DIGEST_LENGTH == 32);
std::array<uint8, SHA256_DIGEST_LENGTH> checksum{};
switch (m_info.GetFormat())
{
case TitleInfo::TitleDataFormat::WUD:
{
const auto path = m_entry.path.string();
wxQueueEvent(this, new wxSetGaugeValue(1, m_progress, m_status, formatWxString(_("Reading game image: {}"), path)));
wud_t* wud = wud_open(m_info.GetPath());
if (!wud)
throw std::runtime_error("can't open game image");
const auto wud_size = wud_getWUDSize(wud);
std::vector<uint8> buffer(1024 * 1024 * 8);
EVP_MD_CTX *sha256 = EVP_MD_CTX_new();
EVP_DigestInit(sha256, EVP_sha256());
uint32 read = 0;
size_t offset = 0;
auto size = wud_size;
do
{
if (!m_running.load(std::memory_order_relaxed))
{
wud_close(wud);
return;
}
read = wud_readData(wud, buffer.data(), std::min(buffer.size(), (size_t)wud_size - offset), offset);
offset += read;
size -= read;
EVP_DigestUpdate(sha256, buffer.data(), read);
wxQueueEvent(this, new wxSetGaugeValue((int)((offset * 90) / wud_size), m_progress, m_status, formatWxString(_("Reading game image: {0}/{1} kB"), offset / 1024, wud_size / 1024)));
} while (read != 0 && size > 0);
wud_close(wud);
wxQueueEvent(this, new wxSetGaugeValue(90, m_progress, m_status, formatWxString(_("Generating checksum of game image: {}"), path)));
if (!m_running.load(std::memory_order_relaxed))
return;
EVP_DigestFinal_ex(sha256, checksum.data(), NULL);
EVP_MD_CTX_free(sha256);
std::stringstream str;
for (const auto& b : checksum)
{
str << fmt::format("{:02X}", b);
}
m_json_entry.wud_hash = str.str();
wxQueueEvent(this, new wxSetGaugeValue(100, m_progress, m_status, formatWxString(_("Generated checksum of game image: {}"), path)));
break;
}
default:
// we hash the individual files for all formats except WUD/WUX
std::string temporaryMountPath = TitleInfo::GetUniqueTempMountingPath();
m_info.Mount(temporaryMountPath.c_str(), "", FSC_PRIORITY_BASE);
wxQueueEvent(this, new wxSetGaugeValue(1, m_progress, m_status, _("Grabbing game files")));
// get list of all files
std::set<std::string> files;
_fscGetAllFiles(files, temporaryMountPath, "");
const size_t file_count = files.size();
size_t counter = 0;
for (const auto& filename : files)
{
auto fileData = fsc_extractFile((temporaryMountPath + "/" + filename).c_str());
if (!fileData)
{
cemuLog_log(LogType::Force, "Failed to open {}", filename);
continue;
}
SHA256(fileData->data(), fileData->size(), checksum.data());
std::stringstream str;
for (const auto& b : checksum)
{
str << fmt::format("{:02X}", b);
}
// store relative path and hash
m_json_entry.file_hashes[filename] = str.str();
++counter;
wxQueueEvent(this, new wxSetGaugeValue((int)((counter * 100) / file_count), m_progress, m_status, formatWxString(_("Hashing game file: {}/{}"), counter, file_count)));
if (!m_running.load(std::memory_order_relaxed))
{
m_info.Unmount(temporaryMountPath.c_str());
return;
}
}
m_info.Unmount(temporaryMountPath.c_str());
wxQueueEvent(this, new wxSetGaugeValue(100, m_progress, m_status, formatWxString(_("Generated checksum of {} game files"), file_count)));
break;
}
}
-51
View File
@@ -1,51 +0,0 @@
#pragma once
#include <wx/dialog.h>
#include "wxgui/components/wxTitleManagerList.h"
#include "Cafe/TitleList/TitleInfo.h"
#include <rapidjson/document.h>
class wxSetGaugeValue;
class ChecksumTool : public wxDialog
{
public:
ChecksumTool(wxWindow* parent, wxTitleManagerList::TitleEntry& entry);
~ChecksumTool();
private:
std::future<void> m_online_ready;
void LoadOnlineData() const;
void VerifyJsonEntry(const rapidjson::Document& doc);
void OnSetGaugevalue(wxSetGaugeValue& event);
void OnExportChecksums(wxCommandEvent& event);
void OnVerifyOnline(wxCommandEvent& event);
void OnVerifyLocal(wxCommandEvent& event);
void DoWork();
std::atomic_bool m_running = true;
std::thread m_worker;
class wxGauge* m_progress;
class wxStaticText* m_status;
class wxButton *m_verify_online, *m_verify_local, *m_export_button;
int m_enable_verify_button = 0;
TitleInfo m_info;
wxTitleManagerList::TitleEntry m_entry;
wxColour m_default_color;
struct JsonEntry
{
uint64 title_id;
uint32 version;
CafeConsoleRegion region;
std::string wud_hash;
std::map<std::string, std::string> file_hashes;
};
JsonEntry m_json_entry;
inline static std::mutex s_mutex{};
inline static std::vector<JsonEntry> s_entries{};
};
@@ -1,418 +0,0 @@
#include "wxgui/DownloadCustomGraphicPackWindow.h"
#include "Cafe/CafeSystem.h"
#include "config/ActiveSettings.h"
#include <cstring>
#include <filesystem>
#include <string>
#include <wx/event.h>
#include <wx/gauge.h>
#include <wx/button.h>
#include <wx/gdicmn.h>
#include <wx/msgdlg.h>
#include <wx/sizer.h>
#include <wx/timer.h>
#include <curl/curl.h>
#include <zip.h>
static size_t curlDownloadFile_writeData(void *ptr, size_t size, size_t nmemb, DownloadCustomGraphicPackWindow::curlDownloadFileState_t* downloadState)
{
const size_t writeSize = size * nmemb;
const size_t currentSize = downloadState->fileData.size();
const size_t newSize = currentSize + writeSize;
auto* bytePtr = static_cast<const uint8*>(ptr);
downloadState->fileData.insert(downloadState->fileData.end(), bytePtr, bytePtr + writeSize);
return writeSize;
}
static int progress_callback(DownloadCustomGraphicPackWindow::curlDownloadFileState_t* downloadState, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
if (downloadState->isCanceled)
return 1;
if (dltotal > 1.0)
downloadState->progress = dlnow / dltotal;
else
downloadState->progress = 0.0;
return 0;
}
static bool curlDownloadFile(const char *url, DownloadCustomGraphicPackWindow::curlDownloadFileState_t* downloadState)
{
CURL* curl = curl_easy_init();
if (curl == nullptr)
return false;
downloadState->progress = 0.0;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlDownloadFile_writeData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, downloadState);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, downloadState);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 30L);
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 15L);
curl_easy_setopt(curl, CURLOPT_USERAGENT, BUILD_VERSION_WITH_NAME_STRING);
downloadState->fileData.resize(0);
const CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
return res == CURLE_OK;
}
DownloadCustomGraphicPackWindow::DownloadCustomGraphicPackWindow(wxWindow* parent)
: wxDialog(parent, wxID_ANY, _("Download Graphic Pack from URL"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX),
m_stage(StageDone), m_currentStage(StageDone)
{
auto* sizer = new wxBoxSizer(wxVERTICAL);
m_urlField = new wxTextCtrl(this, wxID_ANY, wxEmptyString);
m_urlField->SetHint(_("Enter download URL..."));
sizer->Add(m_urlField, 0, wxALL | wxEXPAND, 5);
m_processBar = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(500, 20), wxGA_HORIZONTAL);
m_processBar->SetValue(0);
m_processBar->SetRange(100);
sizer->Add(m_processBar, 0, wxALL | wxEXPAND, 5);
auto* buttonSizer = new wxBoxSizer(wxHORIZONTAL);
m_statusText = new wxStaticText(this, wxID_ANY, _("Ready..."));
buttonSizer->Add(m_statusText, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
buttonSizer->AddStretchSpacer(1);
auto* m_closeButton = new wxButton(this, wxID_ANY, _("Close"));
m_closeButton->Bind(wxEVT_BUTTON, &DownloadCustomGraphicPackWindow::OnCancelButton, this);
buttonSizer->Add(m_closeButton, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_downloadButton = new wxButton(this, wxID_ANY, _("Download"));
m_downloadButton->Bind(wxEVT_BUTTON, &DownloadCustomGraphicPackWindow::OnDownloadButton, this);
buttonSizer->Add(m_downloadButton, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
sizer->Add(buttonSizer, 0, wxEXPAND | wxALL, 5);
this->SetSizer(sizer);
this->Centre(wxBOTH);
wxWindowBase::Layout();
wxWindowBase::Fit();
m_timer = new wxTimer(this);
this->Bind(wxEVT_TIMER, &DownloadCustomGraphicPackWindow::OnUpdate, this);
this->Bind(wxEVT_CLOSE_WINDOW, &DownloadCustomGraphicPackWindow::OnClose, this);
m_timer->Start(100);
m_downloadState = std::make_unique<curlDownloadFileState_t>();
}
DownloadCustomGraphicPackWindow::~DownloadCustomGraphicPackWindow()
{
if (m_downloadState)
m_downloadState->isCanceled = true;
m_timer->Stop();
if (m_thread.joinable())
m_thread.join();
}
int DownloadCustomGraphicPackWindow::ShowModal()
{
wxDialog::ShowModal();
return wxID_OK;
}
void DownloadCustomGraphicPackWindow::OnClose(wxCloseEvent& event)
{
if (m_downloadState)
{
m_downloadState->isCanceled = true;
}
m_timer->Stop();
if (m_thread.joinable())
m_thread.join();
event.Skip();
}
void DownloadCustomGraphicPackWindow::OnUpdate(const wxTimerEvent& event)
{
if (m_currentStage >= StageDone)
{
m_downloadButton->Enable();
m_urlField->Enable();
}
else
{
m_downloadButton->Disable();
m_urlField->Disable();
}
if (m_currentStage == StageDownloading)
{
const sint32 processedSize = (sint32)(m_downloadState->progress * 100.0f);
if (m_processBar->GetValue() != processedSize)
m_processBar->SetValue(processedSize);
}
else if (m_currentStage == StageExtracting)
{
const sint32 processedSize = (sint32)(m_extractionProgress * 100.0f);
if (m_processBar->GetValue() != processedSize)
m_processBar->SetValue(processedSize);
}
if (m_currentStage != m_stage)
{
wxString status = "...";
const wxColour* colour = wxWHITE;
switch (m_stage)
{
case StageDownloading:
status = "Downloading...";
colour = wxWHITE;
break;
case StageVerifying:
status = "Verifying...";
colour = wxWHITE;
break;
case StageExtracting:
status = "Extracting...";
colour = wxWHITE;
break;
case StageDone:
status = "Done!";
colour = wxGREEN;
m_processBar->SetValue(100.0);
break;
case StageErrConnectFailed:
if (m_urlField->GetValue().empty())
{
status = "Please enter a valid URL.";
colour = wxWHITE;
}
else
{
status = "ERROR: Connection failed.";
colour = wxRED;
}
m_processBar->SetValue(0.0);
break;
case StageErrInvalidPack:
status = "ERROR: Invalid pack.";
colour = wxRED;
m_processBar->SetValue(0.0);
break;
case StageErrSourceFailed:
status = "ERROR: Failed to create ZIP source.";
colour = wxRED;
m_processBar->SetValue(0.0);
break;
case StageErrOpenFailed:
status = "ERROR: Failed to open downloaded ZIP.";
colour = wxRED;
m_processBar->SetValue(0.0);
break;
case StageErrConflict:
status = "ERROR: File conflict. Pack already installed?";
colour = wxRED;
m_processBar->SetValue(0.0);
break;
}
m_currentStage = m_stage;
m_statusText->SetLabel(status);
m_statusText->SetForegroundColour(*colour);
}
}
void DownloadCustomGraphicPackWindow::OnCancelButton(const wxCommandEvent& event)
{
Close();
}
void DownloadCustomGraphicPackWindow::OnDownloadButton(const wxCommandEvent& event)
{
m_downloadButton->Disable();
m_urlField->Disable();
if (m_thread.joinable())
m_thread.join();
m_thread = std::thread(&DownloadCustomGraphicPackWindow::UpdateThread, this);
}
void DownloadCustomGraphicPackWindow::UpdateThread()
{
m_stage = StageDownloading;
if (curlDownloadFile(m_urlField->GetValue(), m_downloadState.get()) == false)
{
m_stage = StageErrConnectFailed;
return;
}
m_extractionProgress = 0.0;
m_stage = StageExtracting;
zip_source_t *src;
zip_t *za;
zip_error_t error;
// init zip source
zip_error_init(&error);
if ((src = zip_source_buffer_create(m_downloadState->fileData.data(), m_downloadState->fileData.size(), 0, &error)) == NULL)
{
zip_error_fini(&error);
m_stage = StageErrSourceFailed;
return;
}
// open zip from source
if ((za = zip_open_from_source(src, 0, &error)) == NULL)
{
zip_source_free(src);
zip_error_fini(&error);
m_stage = StageErrOpenFailed;
return;
}
auto path = ActiveSettings::GetUserDataPath("graphicPacks/customGraphicPacks");
fs::create_directories(path);
// check if zip root directly contains a rules.txt
zip_stat_t zs;
zip_stat_init(&zs);
bool hasRootRulesTxt = (zip_stat(za, "rules.txt", 0, &zs) == 0);
fs::path extractionRoot = path;
if (hasRootRulesTxt)
{
// make a folder and extract into that
wxString urlStr = m_urlField->GetValue();
std::string folderName = "NewCustomPack"; // fallback
int lastSlash = urlStr.Find('/', true);
wxString fileNameBase = (lastSlash != wxNOT_FOUND) ? urlStr.Mid(lastSlash + 1) : urlStr;
int lastDot = fileNameBase.Find('.', true);
if (lastDot != wxNOT_FOUND)
{
fileNameBase = fileNameBase.Left(lastDot);
}
fileNameBase.Trim(true).Trim(false);
if (!fileNameBase.IsEmpty())
{
folderName = fileNameBase.ToStdString();
}
extractionRoot = path / folderName;
fs::create_directories(extractionRoot);
}
else
{
// not a gfxpack
m_stage = StageErrInvalidPack;
zip_close(za);
return;
}
// extract
bool err = false;
zip_int64_t numEntries = zip_get_num_entries(za, 0);
for (zip_int64_t i = 0; i < numEntries; i++)
{
m_extractionProgress = (double)i / (double)numEntries;
if (m_downloadState->isCanceled)
{
err = true;
}
if (err)
{
break;
}
zip_stat_t sb = { 0 };
zip_stat_init(&sb);
if (zip_stat_index(za, i, 0, &sb) != 0)
{
assert_dbg();
continue;
}
std::string fileName = sb.name;
if (std::strstr(sb.name, "../") != nullptr ||
std::strstr(sb.name, "..\\") != nullptr ||
(!fileName.empty() && (fileName[0] == '/' || fileName[0] == '\\')))
{
// bad path, CommunityGraphicPacks silently continues
// but this is a low-trust environment, so we halt
m_stage = StageErrInvalidPack;
err = true;
break;
}
fs::path targetDest = extractionRoot / fileName;
if (!fileName.empty() && fileName.back() == '/')
{
fs::create_directories(targetDest);
continue;
}
if (fs::exists(targetDest) && fs::is_regular_file(targetDest))
{
m_stage = StageErrConflict;
err = true;
break;
}
fs::create_directories(targetDest.parent_path());
zip_file_t* zf = zip_fopen_index(za, i, 0);
if (!zf)
{
continue;
}
if (sb.size > 1000000000) // 1GB suspicious limit per file
{
m_stage = StageErrInvalidPack;
err = true;
break;
}
std::vector<uint8> fileBuffer(sb.size);
if (zip_fread(zf, fileBuffer.data(), sb.size) == sb.size)
{
std::unique_ptr<FileStream> fs(FileStream::createFile2(targetDest));
if (fs)
{
fs->writeData(fileBuffer.data(), sb.size);
}
}
else
{
err = true;
m_stage = StageErrInvalidPack;
}
zip_fclose(zf);
}
zip_discard(za);
zip_error_fini(&error);
if (!err)
{
m_stage = StageDone;
}
}
@@ -1,66 +0,0 @@
#pragma once
#include <atomic>
#include <memory>
#include <thread>
#include <vector>
#include <wx/dialog.h>
#include <wx/event.h>
#include <wx/gauge.h>
#include <wx/stattext.h>
#include <wx/timer.h>
#include <wx/textctrl.h>
struct curlDownloadFileState_t;
class DownloadCustomGraphicPackWindow : public wxDialog
{
public:
DownloadCustomGraphicPackWindow(wxWindow* parent);
~DownloadCustomGraphicPackWindow() override;
int ShowModal() override;
void OnClose(wxCloseEvent& event);
void OnUpdate(const wxTimerEvent& event);
void OnCancelButton(const wxCommandEvent& event);
void OnDownloadButton(const wxCommandEvent& event);
public:
struct curlDownloadFileState_t
{
std::vector<uint8> fileData;
std::atomic<double> progress = 0.0;
std::atomic<bool> isCanceled = false;
};
private:
void UpdateThread();
enum DownloadStage_t
{
StageDownloading,
StageExtracting,
StageVerifying,
StageDone,
// Error stages
StageErrConnectFailed,
StageErrSourceFailed,
StageErrInvalidPack,
StageErrOpenFailed,
StageErrConflict,
};
std::unique_ptr<curlDownloadFileState_t> m_downloadState;
std::atomic<DownloadStage_t> m_stage;
DownloadStage_t m_currentStage;
std::atomic<double> m_extractionProgress;
wxTimer* m_timer;
wxTextCtrl* m_urlField;
wxGauge* m_processBar;
wxStaticText* m_statusText;
wxButton* m_downloadButton;
std::thread m_thread;
};
@@ -1,404 +0,0 @@
#include "wxgui/wxgui.h"
#include "wxgui/DownloadGraphicPacksWindow.h"
#include <filesystem>
#include <curl/curl.h>
#include <zip.h>
#include <rapidjson/document.h>
#include <boost/algorithm/string.hpp>
#include "config/ActiveSettings.h"
#include "Common/FileStream.h"
#include "Cafe/CafeSystem.h"
struct DownloadGraphicPacksWindow::curlDownloadFileState_t
{
std::vector<uint8> fileData;
double progress;
};
size_t DownloadGraphicPacksWindow::curlDownloadFile_writeData(void *ptr, size_t size, size_t nmemb, curlDownloadFileState_t* downloadState)
{
const size_t writeSize = size * nmemb;
const size_t currentSize = downloadState->fileData.size();
const size_t newSize = currentSize + writeSize;
downloadState->fileData.resize(newSize);
memcpy(downloadState->fileData.data() + currentSize, ptr, writeSize);
return writeSize;
}
int DownloadGraphicPacksWindow::progress_callback(curlDownloadFileState_t* downloadState, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
if (dltotal > 1.0)
downloadState->progress = dlnow / dltotal;
else
downloadState->progress = 0.0;
return 0;
}
bool DownloadGraphicPacksWindow::curlDownloadFile(const char *url, curlDownloadFileState_t* downloadState)
{
CURL* curl = curl_easy_init();
if (curl == nullptr)
return false;
downloadState->progress = 0.0;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlDownloadFile_writeData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, downloadState);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, downloadState);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
curl_easy_setopt(curl, CURLOPT_USERAGENT, BUILD_VERSION_WITH_NAME_STRING);
downloadState->fileData.resize(0);
const CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
return res == CURLE_OK;
}
// returns true if the version matches
bool checkGraphicPackDownloadedVersion(const char* nameVersion, bool& hasVersionFile)
{
hasVersionFile = false;
const auto path = ActiveSettings::GetUserDataPath("graphicPacks/downloadedGraphicPacks/version.txt");
std::unique_ptr<FileStream> file(FileStream::openFile2(path));
std::string versionInFile;
if (file && file->readLine(versionInFile))
{
return boost::iequals(versionInFile, nameVersion);
}
return false;
}
void createGraphicPackDownloadedVersionFile(const char* nameVersion)
{
const auto path = ActiveSettings::GetUserDataPath("graphicPacks/downloadedGraphicPacks/version.txt");
std::unique_ptr<FileStream> file(FileStream::createFile2(path));
if (file)
file->writeString(nameVersion);
else
{
cemuLog_log(LogType::Force, "Failed to write graphic pack version.txt");
}
}
void deleteDownloadedGraphicPacks()
{
const auto path = ActiveSettings::GetUserDataPath("graphicPacks/downloadedGraphicPacks");
std::error_code er;
if (!fs::exists(path, er))
return;
try
{
for (auto& p : fs::directory_iterator(path))
{
fs::remove_all(p.path(), er);
}
}
catch (std::filesystem::filesystem_error& e)
{
cemuLog_log(LogType::Force, "Error in deleteDownloadedGraphicPacks():");
cemuLog_log(LogType::Force, e.what());
}
}
void DownloadGraphicPacksWindow::UpdateThread()
{
// get github url
std::string githubAPIUrl;
curlDownloadFileState_t tempDownloadState;
std::string queryUrl("https://cemu.info/api2/query_graphicpack_url.php?");
char temp[64];
sprintf(temp, "version=%d.%d.%d", EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH);
queryUrl.append(temp);
queryUrl.append("&");
sprintf(temp, "t=%u", (uint32)std::chrono::seconds(std::time(NULL)).count()); // add a dynamic part to the url to bypass overly aggressive caching (like some proxies do)
queryUrl.append(temp);
if (curlDownloadFile(queryUrl.c_str(), &tempDownloadState))
{
// convert downloaded data to url string
githubAPIUrl.assign(tempDownloadState.fileData.cbegin(), tempDownloadState.fileData.cend());
}
if (githubAPIUrl.empty())
{
// cemu api request failed, use hardcoded github url
cemuLog_log(LogType::Force, "Graphic pack update request failed or returned invalid URL. Using default repository URL instead");
githubAPIUrl = "https://api.github.com/repos/cemu-project/cemu_graphic_packs/releases/latest";
}
// github API request
if (curlDownloadFile(githubAPIUrl.c_str(), &tempDownloadState) == false)
{
wxMessageBox( _("Error"), _(L"Failed to connect to server"), wxOK | wxCENTRE | wxICON_ERROR, this);
m_threadState = ThreadError;
return;
}
// parse json result
rapidjson::Document d;
d.Parse((const char*)tempDownloadState.fileData.data(), tempDownloadState.fileData.size());
if (d.HasParseError())
{
m_threadState = ThreadError;
return;
}
auto& jsonName = d["name"];
if (jsonName.IsString() == false)
{
m_threadState = ThreadError;
return;
}
const char* assetName = jsonName.GetString(); // name includes version
if( d.IsObject() == false)
{
m_threadState = ThreadError;
return;
}
auto& jsonAssets = d["assets"];
if (jsonAssets.IsArray() == false || jsonAssets.GetArray().Size() == 0)
{
m_threadState = ThreadError;
return;
}
auto& jsonAsset0 = jsonAssets.GetArray()[0];
if (jsonAsset0.IsObject() == false)
{
m_threadState = ThreadError;
return;
}
auto& jsonDownloadUrl = jsonAsset0["browser_download_url"];
if (jsonDownloadUrl.IsString() == false)
{
m_threadState = ThreadError;
return;
}
const char* browserDownloadUrl = jsonDownloadUrl.GetString();
// check version
bool hasVersionFile = false;
if (checkGraphicPackDownloadedVersion(assetName, hasVersionFile))
{
// already up to date
wxMessageBox(_("No updates available."), _("Graphic packs"), wxOK | wxCENTRE, this);
m_threadState = ThreadFinished;
return;
}
if (hasVersionFile)
{
// if a version file already exists (and graphic packs are installed) ask the user if he really wants to update
if (wxMessageBox(_("Updated graphic packs are available. Do you want to download and install them?"), _("Graphic packs"), wxYES_NO, this) != wxYES)
{
// cancel update
m_threadState = ThreadFinished;
return;
}
}
// download zip
m_stage = StageDownloading;
if (curlDownloadFile(browserDownloadUrl, m_downloadState.get()) == false)
{
wxMessageBox(_("Error"), _(L"Failed to connect to server"), wxOK | wxCENTRE | wxICON_ERROR, this);
m_threadState = ThreadError;
return;
}
m_extractionProgress = 0.0;
m_stage = StageExtracting;
zip_source_t *src;
zip_t *za;
zip_error_t error;
// init zip source
zip_error_init(&error);
if ((src = zip_source_buffer_create(m_downloadState->fileData.data(), m_downloadState->fileData.size(), 0, &error)) == NULL)
{
zip_error_fini(&error);
m_threadState = ThreadError;
return;
}
// open zip from source
if ((za = zip_open_from_source(src, 0, &error)) == NULL)
{
zip_source_free(src);
zip_error_fini(&error);
m_threadState = ThreadError;
return;
}
auto path = ActiveSettings::GetUserDataPath("graphicPacks/downloadedGraphicPacks");
std::error_code er;
//fs::remove_all(path, er); -> Don't delete the whole folder and recreate it immediately afterwards because sometimes it just fails
deleteDownloadedGraphicPacks();
fs::create_directories(path, er); // make sure downloadedGraphicPacks folder exists
sint32 numEntries = zip_get_num_entries(za, 0);
for (sint32 i = 0; i < numEntries; i++)
{
m_extractionProgress = (double)i / (double)numEntries;
zip_stat_t sb = { 0 };
if (zip_stat_index(za, i, 0, &sb) != 0)
{
assert_dbg();
}
if(std::strstr(sb.name, "../") != nullptr ||
std::strstr(sb.name, "..\\") != nullptr)
continue; // bad path
path = ActiveSettings::GetUserDataPath("graphicPacks/downloadedGraphicPacks/{}", sb.name);
size_t sbNameLen = strlen(sb.name);
if(sbNameLen == 0)
continue;
if (sb.name[sbNameLen - 1] == '/')
{
fs::create_directories(path, er);
continue;
}
if(sb.size == 0)
continue;
if (sb.size > (1024 * 1024 * 128))
continue; // skip unusually huge files
zip_file_t* zipFile = zip_fopen_index(za, i, 0);
if (zipFile == nullptr)
continue;
uint8* fileBuffer = new uint8[sb.size];
if (zip_fread(zipFile, fileBuffer, sb.size) == sb.size)
{
FileStream* fs = FileStream::createFile2(path);
if (fs)
{
fs->writeData(fileBuffer, sb.size);
delete fs;
}
}
delete [] fileBuffer;
zip_fclose(zipFile);
}
zip_discard(za);
zip_error_fini(&error);
createGraphicPackDownloadedVersionFile(assetName);
m_threadState = ThreadFinished;
}
DownloadGraphicPacksWindow::DownloadGraphicPacksWindow(wxWindow* parent)
: wxDialog(parent, wxID_ANY, _("Checking version..."), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX),
m_threadState(ThreadRunning), m_stage(StageCheckVersion), m_currentStage(StageCheckVersion)
{
auto* sizer = new wxBoxSizer(wxVERTICAL);
m_processBar = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(500, 20), wxGA_HORIZONTAL);
m_processBar->SetValue(0);
m_processBar->SetRange(100);
sizer->Add(m_processBar, 0, wxALL | wxEXPAND, 5);
auto* m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel"));
m_cancelButton->Bind(wxEVT_BUTTON, &DownloadGraphicPacksWindow::OnCancelButton, this);
sizer->Add(m_cancelButton, 0, wxALIGN_RIGHT | wxALL, 5);
this->SetSizer(sizer);
this->Centre(wxBOTH);
wxWindowBase::Layout();
wxWindowBase::Fit();
m_timer = new wxTimer(this);
this->Bind(wxEVT_TIMER, &DownloadGraphicPacksWindow::OnUpdate, this);
this->Bind(wxEVT_CLOSE_WINDOW, &DownloadGraphicPacksWindow::OnClose, this);
m_timer->Start(250);
m_downloadState = std::make_unique<curlDownloadFileState_t>();
}
DownloadGraphicPacksWindow::~DownloadGraphicPacksWindow()
{
m_timer->Stop();
if (m_thread.joinable())
m_thread.join();
}
const std::string& DownloadGraphicPacksWindow::GetException() const
{
return m_threadException;
}
int DownloadGraphicPacksWindow::ShowModal()
{
if(CafeSystem::IsTitleRunning())
{
wxMessageBox(_("Graphic packs cannot be updated while a game is running."), _("Graphic packs"), 5, this);
return wxID_CANCEL;
}
m_thread = std::thread(&DownloadGraphicPacksWindow::UpdateThread, this);
wxDialog::ShowModal();
return m_threadState == ThreadCanceled ? wxID_CANCEL : wxID_OK;
}
void DownloadGraphicPacksWindow::OnClose(wxCloseEvent& event)
{
if (m_threadState == ThreadRunning)
{
//wxMessageDialog dialog(this, _("Do you really want to cancel the update process?\n\nCanceling the process will delete the applied update."), _("Info"), wxCENTRE | wxYES_NO);
//if (dialog.ShowModal() != wxID_YES)
// return;
m_threadState = ThreadCanceled;
}
m_timer->Stop();
if (m_thread.joinable())
m_thread.join();
event.Skip();
}
void DownloadGraphicPacksWindow::OnUpdate(const wxTimerEvent& event)
{
if (m_threadState != ThreadRunning)
{
Close();
return;
}
if (m_currentStage != m_stage)
{
if (m_stage == StageDownloading)
{
this->SetTitle(_("Downloading graphic packs..."));
}
else if (m_stage == StageExtracting)
{
this->SetTitle(_("Extracting..."));
}
m_currentStage = m_stage;
}
if (m_currentStage == StageDownloading)
{
const sint32 processedSize = (sint32)(m_downloadState->progress * 100.0f);
if (m_processBar->GetValue() != processedSize)
m_processBar->SetValue(processedSize);
}
else if (m_currentStage == StageExtracting)
{
const sint32 processedSize = (sint32)(m_extractionProgress * 100.0f);
if (m_processBar->GetValue() != processedSize)
m_processBar->SetValue(processedSize);
}
}
void DownloadGraphicPacksWindow::OnCancelButton(const wxCommandEvent& event)
{
Close();
}
@@ -1,62 +0,0 @@
#pragma once
#include <atomic>
#include <curl/system.h>
#include <thread>
#include <string>
#include <memory>
#include <wx/dialog.h>
#include <wx/timer.h>
#include <wx/gauge.h>
class DownloadGraphicPacksWindow : public wxDialog
{
public:
DownloadGraphicPacksWindow(wxWindow* parent);
~DownloadGraphicPacksWindow();
const std::string& GetException() const;
int ShowModal() override;
void OnClose(wxCloseEvent& event);
void OnUpdate(const wxTimerEvent& event);
void OnCancelButton(const wxCommandEvent& event);
private:
void UpdateThread();
enum ThreadState_t
{
ThreadRunning,
ThreadCanceled,
ThreadError,
ThreadFinished,
};
enum DownloadStage_t
{
StageCheckVersion,
StageDownloading,
StageExtracting
};
std::atomic<ThreadState_t> m_threadState;
std::atomic<DownloadStage_t> m_stage;
std::atomic<double> m_extractionProgress;
std::string m_threadException;
std::thread m_thread;
DownloadStage_t m_currentStage;
wxGauge* m_processBar;
wxTimer* m_timer;
struct curlDownloadFileState_t;
std::unique_ptr<curlDownloadFileState_t> m_downloadState;
static size_t curlDownloadFile_writeData(void* ptr, size_t size, size_t nmemb, curlDownloadFileState_t* downloadState);
static int progress_callback(curlDownloadFileState_t* downloadState, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
static bool curlDownloadFile(const char* url, curlDownloadFileState_t* downloadState);
};
@@ -1,842 +0,0 @@
#include "EmulatedUSBDeviceFrame.h"
#include <algorithm>
#include "config/CemuConfig.h"
#include "wxgui/helpers/wxHelpers.h"
#include "wxgui/wxHelper.h"
#include "util/helpers/helpers.h"
#include "Cafe/OS/libs/nsyshid/nsyshid.h"
#include "Cafe/OS/libs/nsyshid/Dimensions.h"
#include "Common/FileStream.h"
#include <wx/arrstr.h>
#include <wx/button.h>
#include <wx/combobox.h>
#include <wx/checkbox.h>
#include <wx/combobox.h>
#include <wx/filedlg.h>
#include <wx/log.h>
#include <wx/msgdlg.h>
#include <wx/notebook.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/statbox.h>
#include <wx/stattext.h>
#include <wx/stream.h>
#include <wx/textctrl.h>
#include <wx/textentry.h>
#include <wx/valnum.h>
#include <wx/wfstream.h>
#include "resource/embedded/resources.h"
EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent)
: wxFrame(parent, wxID_ANY, _("Emulated USB Devices"), wxDefaultPosition,
wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL)
{
SetIcon(wxICON(X_BOX));
auto& config = GetConfig();
auto* sizer = new wxBoxSizer(wxVERTICAL);
auto* notebook = new wxNotebook(this, wxID_ANY);
notebook->AddPage(AddSkylanderPage(notebook), _("Skylanders Portal"));
notebook->AddPage(AddInfinityPage(notebook), _("Infinity Base"));
notebook->AddPage(AddDimensionsPage(notebook), _("Dimensions Toypad"));
sizer->Add(notebook, 1, wxEXPAND | wxALL, 2);
SetSizerAndFit(sizer);
Layout();
Centre(wxBOTH);
}
EmulatedUSBDeviceFrame::~EmulatedUSBDeviceFrame() {}
wxPanel* EmulatedUSBDeviceFrame::AddSkylanderPage(wxNotebook* notebook)
{
auto* panel = new wxPanel(notebook);
auto* panelSizer = new wxBoxSizer(wxVERTICAL);
auto* box = new wxStaticBox(panel, wxID_ANY, _("Skylanders Manager"));
auto* boxSizer = new wxStaticBoxSizer(box, wxVERTICAL);
auto* row = new wxBoxSizer(wxHORIZONTAL);
m_emulatePortal =
new wxCheckBox(box, wxID_ANY, _("Emulate Skylander Portal"));
m_emulatePortal->SetValue(
GetConfig().emulated_usb_devices.emulate_skylander_portal);
m_emulatePortal->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) {
GetConfig().emulated_usb_devices.emulate_skylander_portal =
m_emulatePortal->IsChecked();
GetConfigHandle().Save();
});
row->Add(m_emulatePortal, 1, wxEXPAND | wxALL, 2);
boxSizer->Add(row, 1, wxEXPAND | wxALL, 2);
for (int i = 0; i < nsyshid::MAX_SKYLANDERS; i++)
{
boxSizer->Add(AddSkylanderRow(i, box), 1, wxEXPAND | wxALL, 2);
}
panelSizer->Add(boxSizer, 1, wxEXPAND | wxALL, 2);
panel->SetSizerAndFit(panelSizer);
return panel;
}
wxPanel* EmulatedUSBDeviceFrame::AddInfinityPage(wxNotebook* notebook)
{
auto* panel = new wxPanel(notebook);
auto* panelSizer = new wxBoxSizer(wxVERTICAL);
auto* box = new wxStaticBox(panel, wxID_ANY, _("Infinity Manager"));
auto* boxSizer = new wxStaticBoxSizer(box, wxVERTICAL);
auto* row = new wxBoxSizer(wxHORIZONTAL);
m_emulateBase =
new wxCheckBox(box, wxID_ANY, _("Emulate Infinity Base"));
m_emulateBase->SetValue(
GetConfig().emulated_usb_devices.emulate_infinity_base);
m_emulateBase->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) {
GetConfig().emulated_usb_devices.emulate_infinity_base =
m_emulateBase->IsChecked();
GetConfigHandle().Save();
});
row->Add(m_emulateBase, 1, wxEXPAND | wxALL, 2);
boxSizer->Add(row, 1, wxEXPAND | wxALL, 2);
boxSizer->Add(AddInfinityRow("Play Set/Power Disc", 0, box), 1, wxEXPAND | wxALL, 2);
boxSizer->Add(AddInfinityRow("Power Disc Two", 1, box), 1, wxEXPAND | wxALL, 2);
boxSizer->Add(AddInfinityRow("Power Disc Three", 2, box), 1, wxEXPAND | wxALL, 2);
boxSizer->Add(AddInfinityRow("Player One", 3, box), 1, wxEXPAND | wxALL, 2);
boxSizer->Add(AddInfinityRow("Player One Ability One", 4, box), 1, wxEXPAND | wxALL, 2);
boxSizer->Add(AddInfinityRow("Player One Ability Two", 5, box), 1, wxEXPAND | wxALL, 2);
boxSizer->Add(AddInfinityRow("Player Two", 6, box), 1, wxEXPAND | wxALL, 2);
boxSizer->Add(AddInfinityRow("Player Two Ability One", 7, box), 1, wxEXPAND | wxALL, 2);
boxSizer->Add(AddInfinityRow("Player Two Ability Two", 8, box), 1, wxEXPAND | wxALL, 2);
panelSizer->Add(boxSizer, 1, wxEXPAND | wxALL, 2);
panel->SetSizerAndFit(panelSizer);
return panel;
}
wxPanel* EmulatedUSBDeviceFrame::AddDimensionsPage(wxNotebook* notebook)
{
auto* panel = new wxPanel(notebook);
auto* panel_sizer = new wxBoxSizer(wxVERTICAL);
auto* box = new wxStaticBox(panel, wxID_ANY, _("Dimensions Manager"));
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
auto* row = new wxBoxSizer(wxHORIZONTAL);
m_emulateToypad =
new wxCheckBox(box, wxID_ANY, _("Emulate Dimensions Toypad"));
m_emulateToypad->SetValue(
GetConfig().emulated_usb_devices.emulate_dimensions_toypad);
m_emulateToypad->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) {
GetConfig().emulated_usb_devices.emulate_dimensions_toypad =
m_emulateToypad->IsChecked();
GetConfigHandle().Save();
});
row->Add(m_emulateToypad, 1, wxEXPAND | wxALL, 2);
box_sizer->Add(row, 1, wxEXPAND | wxALL, 2);
auto* top_row = new wxBoxSizer(wxHORIZONTAL);
auto* bottom_row = new wxBoxSizer(wxHORIZONTAL);
top_row->Add(AddDimensionPanel(2, 0, box), 1, wxEXPAND | wxALL, 2);
top_row->Add(0, 0, 1, wxEXPAND | wxLEFT | wxRIGHT, 2);
top_row->Add(AddDimensionPanel(1, 1, box), 1, wxEXPAND | wxALL, 2);
top_row->Add(0, 0, 1, wxEXPAND | wxLEFT | wxRIGHT, 2);
top_row->Add(AddDimensionPanel(3, 2, box), 1, wxEXPAND | wxALL, 2);
bottom_row->Add(AddDimensionPanel(2, 3, box), 1, wxEXPAND | wxALL, 2);
bottom_row->Add(AddDimensionPanel(2, 4, box), 1, wxEXPAND | wxALL, 2);
bottom_row->Add(0, 0, 1, wxEXPAND | wxLEFT | wxRIGHT, 0);
bottom_row->Add(AddDimensionPanel(3, 5, box), 1, wxEXPAND | wxALL, 2);
bottom_row->Add(AddDimensionPanel(3, 6, box), 1, wxEXPAND | wxALL, 2);
box_sizer->Add(top_row, 1, wxEXPAND | wxALL, 2);
box_sizer->Add(bottom_row, 1, wxEXPAND | wxALL, 2);
panel_sizer->Add(box_sizer, 1, wxEXPAND | wxALL, 2);
panel->SetSizerAndFit(panel_sizer);
return panel;
}
wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 rowNumber, wxStaticBox* box)
{
auto* row = new wxBoxSizer(wxHORIZONTAL);
row->Add(new wxStaticText(box, wxID_ANY,
fmt::format("{} {}", _("Skylander").ToStdString(),
rowNumber + 1)),
1, wxEXPAND | wxALL, 2);
m_skylanderSlots[rowNumber] =
new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize,
wxTE_READONLY);
m_skylanderSlots[rowNumber]->SetMinSize(wxSize(150, -1));
m_skylanderSlots[rowNumber]->Disable();
row->Add(m_skylanderSlots[rowNumber], 1, wxEXPAND | wxALL, 2);
auto* loadButton = new wxButton(box, wxID_ANY, _("Load"));
loadButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) {
LoadSkylander(rowNumber);
});
auto* createButton = new wxButton(box, wxID_ANY, _("Create"));
createButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) {
CreateSkylander(rowNumber);
});
auto* clearButton = new wxButton(box, wxID_ANY, _("Clear"));
clearButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) {
ClearSkylander(rowNumber);
});
row->Add(loadButton, 1, wxEXPAND | wxALL, 2);
row->Add(createButton, 1, wxEXPAND | wxALL, 2);
row->Add(clearButton, 1, wxEXPAND | wxALL, 2);
return row;
}
wxBoxSizer* EmulatedUSBDeviceFrame::AddInfinityRow(wxString name, uint8 rowNumber, wxStaticBox* box)
{
auto* row = new wxBoxSizer(wxHORIZONTAL);
row->Add(new wxStaticText(box, wxID_ANY, name), 1, wxEXPAND | wxALL, 2);
m_infinitySlots[rowNumber] =
new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize,
wxTE_READONLY);
m_infinitySlots[rowNumber]->SetMinSize(wxSize(150, -1));
m_infinitySlots[rowNumber]->Disable();
row->Add(m_infinitySlots[rowNumber], 1, wxALL | wxEXPAND, 5);
auto* loadButton = new wxButton(box, wxID_ANY, _("Load"));
loadButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) {
LoadFigure(rowNumber);
});
auto* createButton = new wxButton(box, wxID_ANY, _("Create"));
createButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) {
CreateFigure(rowNumber);
});
auto* clearButton = new wxButton(box, wxID_ANY, _("Clear"));
clearButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) {
ClearFigure(rowNumber);
});
row->Add(loadButton, 1, wxEXPAND | wxALL, 2);
row->Add(createButton, 1, wxEXPAND | wxALL, 2);
row->Add(clearButton, 1, wxEXPAND | wxALL, 2);
return row;
}
wxBoxSizer* EmulatedUSBDeviceFrame::AddDimensionPanel(uint8 pad, uint8 index, wxStaticBox* box)
{
auto* panel = new wxBoxSizer(wxVERTICAL);
auto* combo_row = new wxBoxSizer(wxHORIZONTAL);
m_dimensionSlots[index] = new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize,
wxTE_READONLY);
combo_row->Add(m_dimensionSlots[index], 1, wxEXPAND | wxALL, 2);
auto* move_button = new wxButton(box, wxID_ANY, _("Move"));
move_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) {
MoveMinifig(pad, index);
});
combo_row->Add(move_button, 1, wxEXPAND | wxALL, 2);
auto* button_row = new wxBoxSizer(wxHORIZONTAL);
auto* load_button = new wxButton(box, wxID_ANY, _("Load"));
load_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) {
LoadMinifig(pad, index);
});
auto* clear_button = new wxButton(box, wxID_ANY, _("Clear"));
clear_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) {
ClearMinifig(pad, index);
});
auto* create_button = new wxButton(box, wxID_ANY, _("Create"));
create_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) {
CreateMinifig(pad, index);
});
button_row->Add(clear_button, 1, wxEXPAND | wxALL, 2);
button_row->Add(create_button, 1, wxEXPAND | wxALL, 2);
button_row->Add(load_button, 1, wxEXPAND | wxALL, 2);
panel->Add(combo_row, 1, wxEXPAND | wxALL, 2);
panel->Add(button_row, 1, wxEXPAND | wxALL, 2);
return panel;
}
void EmulatedUSBDeviceFrame::LoadSkylander(uint8 slot)
{
wxFileDialog openFileDialog(this, _("Open Skylander dump"), "", "",
"Skylander files (*.sky;*.bin;*.dump;*.dmp)|*.sky;*.bin;*.dump;*.dmp",
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty())
return;
LoadSkylanderPath(slot, openFileDialog.GetPath());
}
void EmulatedUSBDeviceFrame::LoadSkylanderPath(uint8 slot, wxString path)
{
std::unique_ptr<FileStream> skyFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true));
if (!skyFile)
{
wxMessageDialog open_error(this, "Error Opening File: " + path);
open_error.ShowModal();
return;
}
std::array<uint8, nsyshid::SKY_FIGURE_SIZE> fileData;
if (skyFile->readData(fileData.data(), fileData.size()) != fileData.size())
{
wxMessageDialog open_error(this, "Failed to read file! File was too small");
open_error.ShowModal();
return;
}
ClearSkylander(slot);
uint16 skyId = uint16(fileData[0x11]) << 8 | uint16(fileData[0x10]);
uint16 skyVar = uint16(fileData[0x1D]) << 8 | uint16(fileData[0x1C]);
uint8 portalSlot = nsyshid::g_skyportal.LoadSkylander(fileData.data(),
std::move(skyFile));
m_skySlots[slot] = std::tuple(portalSlot, skyId, skyVar);
UpdateSkylanderEdits();
}
void EmulatedUSBDeviceFrame::CreateSkylander(uint8 slot)
{
CreateSkylanderDialog create_dlg(this, slot);
create_dlg.ShowModal();
if (create_dlg.GetReturnCode() == 1)
{
LoadSkylanderPath(slot, create_dlg.GetFilePath());
}
}
void EmulatedUSBDeviceFrame::ClearSkylander(uint8 slot)
{
if (auto slotInfos = m_skySlots[slot])
{
auto [curSlot, id, var] = slotInfos.value();
nsyshid::g_skyportal.RemoveSkylander(curSlot);
m_skySlots[slot] = {};
UpdateSkylanderEdits();
}
}
CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot)
: wxDialog(parent, wxID_ANY, _("Skylander Figure Creator"), wxDefaultPosition, wxSize(500, 150))
{
auto* sizer = new wxBoxSizer(wxVERTICAL);
auto* comboRow = new wxBoxSizer(wxHORIZONTAL);
auto* comboBox = new wxComboBox(this, wxID_ANY);
comboBox->Append("---Select---", reinterpret_cast<void*>(0xFFFFFFFF));
wxArrayString filterlist;
for (const auto& it : nsyshid::g_skyportal.GetListSkylanders())
{
const uint32 variant = uint32(uint32(it.first.first) << 16) | uint32(it.first.second);
comboBox->Append(it.second, reinterpret_cast<void*>(variant));
filterlist.Add(it.second);
}
comboBox->SetSelection(0);
bool enabled = comboBox->AutoComplete(filterlist);
comboRow->Add(comboBox, 1, wxEXPAND | wxALL, 2);
auto* idVarRow = new wxBoxSizer(wxHORIZONTAL);
wxIntegerValidator<uint32> validator;
auto* labelId = new wxStaticText(this, wxID_ANY, "ID:");
auto* labelVar = new wxStaticText(this, wxID_ANY, "Variant:");
auto* editId = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator);
auto* editVar = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator);
idVarRow->Add(labelId, 1, wxALL, 5);
idVarRow->Add(editId, 1, wxALL, 5);
idVarRow->Add(labelVar, 1, wxALL, 5);
idVarRow->Add(editVar, 1, wxALL, 5);
auto* buttonRow = new wxBoxSizer(wxHORIZONTAL);
auto* createButton = new wxButton(this, wxID_ANY, _("Create"));
createButton->Bind(wxEVT_BUTTON, [editId, editVar, this](wxCommandEvent&) {
long longSkyId;
if (!editId->GetValue().ToLong(&longSkyId) || longSkyId > 0xFFFF)
{
wxMessageDialog idError(this, "Error Converting ID!", "ID Entered is Invalid");
idError.ShowModal();
return;
}
long longSkyVar;
if (!editVar->GetValue().ToLong(&longSkyVar) || longSkyVar > 0xFFFF)
{
wxMessageDialog idError(this, "Error Converting Variant!", "Variant Entered is Invalid");
idError.ShowModal();
return;
}
uint16 skyId = longSkyId & 0xFFFF;
uint16 skyVar = longSkyVar & 0xFFFF;
wxString predefName = nsyshid::g_skyportal.FindSkylander(skyId, skyVar) + ".sky";
wxFileDialog
saveFileDialog(this, _("Create Skylander file"), "", predefName,
"SKY files (*.sky)|*.sky", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (saveFileDialog.ShowModal() == wxID_CANCEL)
return;
m_filePath = saveFileDialog.GetPath();
if (!nsyshid::g_skyportal.CreateSkylander(_utf8ToPath(m_filePath.utf8_string()), skyId, skyVar))
{
wxMessageDialog errorMessage(this, "Failed to create file");
errorMessage.ShowModal();
this->EndModal(0);
return;
}
this->EndModal(1);
});
auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel"));
cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
this->EndModal(0);
});
comboBox->Bind(wxEVT_COMBOBOX, [comboBox, editId, editVar, this](wxCommandEvent&) {
const uint64 sky_info = reinterpret_cast<uint64>(comboBox->GetClientData(comboBox->GetSelection()));
if (sky_info != 0xFFFFFFFF)
{
const uint16 skyId = sky_info >> 16;
const uint16 skyVar = sky_info & 0xFFFF;
editId->SetValue(wxString::Format(wxT("%i"), skyId));
editVar->SetValue(wxString::Format(wxT("%i"), skyVar));
}
});
buttonRow->Add(createButton, 1, wxALL, 5);
buttonRow->Add(cancelButton, 1, wxALL, 5);
sizer->Add(comboRow, 1, wxEXPAND | wxALL, 2);
sizer->Add(idVarRow, 1, wxEXPAND | wxALL, 2);
sizer->Add(buttonRow, 1, wxEXPAND | wxALL, 2);
this->SetSizer(sizer);
this->Centre(wxBOTH);
}
wxString CreateSkylanderDialog::GetFilePath() const
{
return m_filePath;
}
void EmulatedUSBDeviceFrame::UpdateSkylanderEdits()
{
for (auto i = 0; i < nsyshid::MAX_SKYLANDERS; i++)
{
std::string displayString;
if (auto sd = m_skySlots[i])
{
auto [portalSlot, skyId, skyVar] = sd.value();
displayString = nsyshid::g_skyportal.FindSkylander(skyId, skyVar);
}
else
{
displayString = "None";
}
m_skylanderSlots[i]->ChangeValue(displayString);
}
}
void EmulatedUSBDeviceFrame::LoadFigure(uint8 slot)
{
wxFileDialog openFileDialog(this, _("Open Infinity Figure dump"), "", "",
"BIN files (*.bin)|*.bin",
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty())
{
wxMessageDialog errorMessage(this, "File Okay Error");
errorMessage.ShowModal();
return;
}
LoadFigurePath(slot, openFileDialog.GetPath());
}
void EmulatedUSBDeviceFrame::LoadFigurePath(uint8 slot, wxString path)
{
std::unique_ptr<FileStream> infFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true));
if (!infFile)
{
wxMessageDialog errorMessage(this, "File Open Error");
errorMessage.ShowModal();
return;
}
std::array<uint8, nsyshid::INF_FIGURE_SIZE> fileData;
if (infFile->readData(fileData.data(), fileData.size()) != fileData.size())
{
wxMessageDialog open_error(this, "Failed to read file! File was too small");
open_error.ShowModal();
return;
}
ClearFigure(slot);
uint32 number = nsyshid::g_infinitybase.LoadFigure(fileData, std::move(infFile), slot);
m_infinitySlots[slot]->ChangeValue(nsyshid::g_infinitybase.FindFigure(number).second);
}
void EmulatedUSBDeviceFrame::CreateFigure(uint8 slot)
{
cemuLog_log(LogType::Force, "Create Figure: {}", slot);
CreateInfinityFigureDialog create_dlg(this, slot);
create_dlg.ShowModal();
if (create_dlg.GetReturnCode() == 1)
{
LoadFigurePath(slot, create_dlg.GetFilePath());
}
}
void EmulatedUSBDeviceFrame::ClearFigure(uint8 slot)
{
m_infinitySlots[slot]->ChangeValue("None");
nsyshid::g_infinitybase.RemoveFigure(slot);
}
CreateInfinityFigureDialog::CreateInfinityFigureDialog(wxWindow* parent, uint8 slot)
: wxDialog(parent, wxID_ANY, _("Infinity Figure Creator"), wxDefaultPosition, wxSize(500, 150))
{
auto* sizer = new wxBoxSizer(wxVERTICAL);
auto* comboRow = new wxBoxSizer(wxHORIZONTAL);
auto* comboBox = new wxComboBox(this, wxID_ANY);
comboBox->Append("---Select---", reinterpret_cast<void*>(0xFFFFFF));
wxArrayString filterlist;
for (const auto& it : nsyshid::g_infinitybase.GetFigureList())
{
const uint32 figure = it.first;
if ((slot == 0 &&
((figure > 0x1E8480 && figure < 0x2DC6BF) || (figure > 0x3D0900 && figure < 0x4C4B3F))) ||
((slot == 1 || slot == 2) && (figure > 0x3D0900 && figure < 0x4C4B3F)) ||
((slot == 3 || slot == 6) && figure < 0x1E847F) ||
((slot == 4 || slot == 5 || slot == 7 || slot == 8) &&
(figure > 0x2DC6C0 && figure < 0x3D08FF)))
{
comboBox->Append(it.second.second, reinterpret_cast<void*>(figure));
filterlist.Add(it.second.second);
}
}
comboBox->SetSelection(0);
bool enabled = comboBox->AutoComplete(filterlist);
comboRow->Add(comboBox, 1, wxEXPAND | wxALL, 2);
auto* figNumRow = new wxBoxSizer(wxHORIZONTAL);
wxIntegerValidator<uint32> validator;
auto* labelFigNum = new wxStaticText(this, wxID_ANY, "Figure Number:");
auto* editFigNum = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator);
figNumRow->Add(labelFigNum, 1, wxALL, 5);
figNumRow->Add(editFigNum, 1, wxALL, 5);
auto* buttonRow = new wxBoxSizer(wxHORIZONTAL);
auto* createButton = new wxButton(this, wxID_ANY, _("Create"));
createButton->Bind(wxEVT_BUTTON, [editFigNum, this](wxCommandEvent&) {
long longFigNum;
if (!editFigNum->GetValue().ToLong(&longFigNum))
{
wxMessageDialog idError(this, "Error Converting Figure Number!", "Number Entered is Invalid");
idError.ShowModal();
this->EndModal(0);
return;
}
uint32 figNum = longFigNum & 0xFFFFFFFF;
auto figure = nsyshid::g_infinitybase.FindFigure(figNum);
wxString predefName = figure.second + ".bin";
wxFileDialog
saveFileDialog(this, _("Create Infinity Figure file"), "", predefName,
"BIN files (*.bin)|*.bin", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (saveFileDialog.ShowModal() == wxID_CANCEL)
{
this->EndModal(0);
return;
}
m_filePath = saveFileDialog.GetPath();
nsyshid::g_infinitybase.CreateFigure(_utf8ToPath(m_filePath.utf8_string()), figNum, figure.first);
this->EndModal(1);
});
auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel"));
cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
this->EndModal(0);
});
comboBox->Bind(wxEVT_COMBOBOX, [comboBox, editFigNum, this](wxCommandEvent&) {
const uint64 fig_info = reinterpret_cast<uint64>(comboBox->GetClientData(comboBox->GetSelection()));
if (fig_info != 0xFFFFFF)
{
const uint32 figNum = fig_info & 0xFFFFFFFF;
editFigNum->SetValue(wxString::Format(wxT("%i"), figNum));
}
});
buttonRow->Add(createButton, 1, wxALL, 5);
buttonRow->Add(cancelButton, 1, wxALL, 5);
sizer->Add(comboRow, 1, wxEXPAND | wxALL, 2);
sizer->Add(figNumRow, 1, wxEXPAND | wxALL, 2);
sizer->Add(buttonRow, 1, wxEXPAND | wxALL, 2);
this->SetSizer(sizer);
this->Centre(wxBOTH);
}
wxString CreateInfinityFigureDialog::GetFilePath() const
{
return m_filePath;
}
void EmulatedUSBDeviceFrame::LoadMinifig(uint8 pad, uint8 index)
{
wxFileDialog openFileDialog(this, _("Load Dimensions Figure"), "", "",
"Dimensions files (*.bin)|*.bin",
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty())
return;
LoadMinifigPath(openFileDialog.GetPath(), pad, index);
}
void EmulatedUSBDeviceFrame::LoadMinifigPath(wxString path_name, uint8 pad, uint8 index)
{
std::unique_ptr<FileStream> dim_file(FileStream::openFile2(_utf8ToPath(path_name.utf8_string()), true));
if (!dim_file)
{
wxMessageDialog errorMessage(this, "Failed to open minifig file");
errorMessage.ShowModal();
return;
}
std::array<uint8, 0x2D * 0x04> file_data;
if (dim_file->readData(file_data.data(), file_data.size()) != file_data.size())
{
wxMessageDialog errorMessage(this, "Failed to read minifig file data");
errorMessage.ShowModal();
return;
}
ClearMinifig(pad, index);
uint32 id = nsyshid::g_dimensionstoypad.LoadFigure(file_data, std::move(dim_file), pad, index);
m_dimensionSlots[index]->ChangeValue(nsyshid::g_dimensionstoypad.FindFigure(id));
m_dimSlots[index] = id;
}
void EmulatedUSBDeviceFrame::ClearMinifig(uint8 pad, uint8 index)
{
nsyshid::g_dimensionstoypad.RemoveFigure(pad, index, true);
m_dimensionSlots[index]->ChangeValue("None");
m_dimSlots[index] = std::nullopt;
}
void EmulatedUSBDeviceFrame::CreateMinifig(uint8 pad, uint8 index)
{
CreateDimensionFigureDialog create_dlg(this);
create_dlg.ShowModal();
if (create_dlg.GetReturnCode() == 1)
{
LoadMinifigPath(create_dlg.GetFilePath(), pad, index);
}
}
void EmulatedUSBDeviceFrame::MoveMinifig(uint8 pad, uint8 index)
{
if (!m_dimSlots[index])
return;
MoveDimensionFigureDialog move_dlg(this, index);
nsyshid::g_dimensionstoypad.TempRemove(index);
move_dlg.ShowModal();
if (move_dlg.GetReturnCode() == 1)
{
nsyshid::g_dimensionstoypad.MoveFigure(move_dlg.GetNewPad(), move_dlg.GetNewIndex(), pad, index);
if (index != move_dlg.GetNewIndex())
{
m_dimSlots[move_dlg.GetNewIndex()] = m_dimSlots[index];
m_dimensionSlots[move_dlg.GetNewIndex()]->ChangeValue(m_dimensionSlots[index]->GetValue());
m_dimSlots[index] = std::nullopt;
m_dimensionSlots[index]->ChangeValue("None");
}
}
else
{
nsyshid::g_dimensionstoypad.CancelRemove(index);
}
}
CreateDimensionFigureDialog::CreateDimensionFigureDialog(wxWindow* parent)
: wxDialog(parent, wxID_ANY, _("Dimensions Figure Creator"), wxDefaultPosition, wxSize(500, 200))
{
auto* sizer = new wxBoxSizer(wxVERTICAL);
auto* comboRow = new wxBoxSizer(wxHORIZONTAL);
auto* comboBox = new wxComboBox(this, wxID_ANY);
comboBox->Append("---Select---", reinterpret_cast<void*>(0xFFFFFFFF));
wxArrayString filterlist;
for (const auto& it : nsyshid::g_dimensionstoypad.GetListMinifigs())
{
const uint32 figure = it.first;
comboBox->Append(it.second, reinterpret_cast<void*>(figure));
filterlist.Add(it.second);
}
comboBox->SetSelection(0);
bool enabled = comboBox->AutoComplete(filterlist);
comboRow->Add(comboBox, 1, wxEXPAND | wxALL, 2);
auto* figNumRow = new wxBoxSizer(wxHORIZONTAL);
wxIntegerValidator<uint32> validator;
auto* labelFigNum = new wxStaticText(this, wxID_ANY, "Figure Number:");
auto* editFigNum = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator);
figNumRow->Add(labelFigNum, 1, wxALL, 5);
figNumRow->Add(editFigNum, 1, wxALL, 5);
auto* buttonRow = new wxBoxSizer(wxHORIZONTAL);
auto* createButton = new wxButton(this, wxID_ANY, _("Create"));
createButton->Bind(wxEVT_BUTTON, [editFigNum, this](wxCommandEvent&) {
long longFigNum;
if (!editFigNum->GetValue().ToLong(&longFigNum) || longFigNum > 0xFFFF)
{
wxMessageDialog idError(this, "Error Converting Figure Number!", "Number Entered is Invalid");
idError.ShowModal();
this->EndModal(0);
return;
}
uint16 figNum = longFigNum & 0xFFFF;
auto figure = nsyshid::g_dimensionstoypad.FindFigure(figNum);
wxString predefName = figure + ".bin";
wxFileDialog
saveFileDialog(this, _("Create Dimensions Figure file"), "", predefName,
"BIN files (*.bin)|*.bin", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (saveFileDialog.ShowModal() == wxID_CANCEL)
{
this->EndModal(0);
return;
}
m_filePath = saveFileDialog.GetPath();
nsyshid::g_dimensionstoypad.CreateFigure(_utf8ToPath(m_filePath.utf8_string()), figNum);
this->EndModal(1);
});
auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel"));
cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
this->EndModal(0);
});
comboBox->Bind(wxEVT_COMBOBOX, [comboBox, editFigNum, this](wxCommandEvent&) {
const uint64 fig_info = reinterpret_cast<uint64>(comboBox->GetClientData(comboBox->GetSelection()));
if (fig_info != 0xFFFF)
{
const uint16 figNum = fig_info & 0xFFFF;
editFigNum->SetValue(wxString::Format(wxT("%i"), figNum));
}
});
buttonRow->Add(createButton, 1, wxALL, 5);
buttonRow->Add(cancelButton, 1, wxALL, 5);
sizer->Add(comboRow, 1, wxEXPAND | wxALL, 2);
sizer->Add(figNumRow, 1, wxEXPAND | wxALL, 2);
sizer->Add(buttonRow, 1, wxEXPAND | wxALL, 2);
this->SetSizer(sizer);
this->Centre(wxBOTH);
}
wxString CreateDimensionFigureDialog::GetFilePath() const
{
return m_filePath;
}
MoveDimensionFigureDialog::MoveDimensionFigureDialog(EmulatedUSBDeviceFrame* parent, uint8 currentIndex)
: wxDialog(parent, wxID_ANY, _("Dimensions Figure Mover"), wxDefaultPosition, wxSize(700, 300))
{
auto* sizer = new wxGridSizer(2, 5, 10, 10);
std::array<std::optional<uint32>, 7> ids = parent->GetCurrentMinifigs();
sizer->Add(AddMinifigSlot(2, 0, currentIndex, ids[0]), 1, wxALL, 5);
sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5);
sizer->Add(AddMinifigSlot(1, 1, currentIndex, ids[1]), 1, wxALL, 5);
sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5);
sizer->Add(AddMinifigSlot(3, 2, currentIndex, ids[2]), 1, wxALL, 5);
sizer->Add(AddMinifigSlot(2, 3, currentIndex, ids[3]), 1, wxALL, 5);
sizer->Add(AddMinifigSlot(2, 4, currentIndex, ids[4]), 1, wxALL, 5);
sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5);
sizer->Add(AddMinifigSlot(3, 5, currentIndex, ids[5]), 1, wxALL, 5);
sizer->Add(AddMinifigSlot(3, 6, currentIndex, ids[6]), 1, wxALL, 5);
this->SetSizer(sizer);
this->Centre(wxBOTH);
}
wxBoxSizer* MoveDimensionFigureDialog::AddMinifigSlot(uint8 pad, uint8 index, uint8 currentIndex, std::optional<uint32> currentId)
{
auto* panel = new wxBoxSizer(wxVERTICAL);
auto* label = new wxStaticText(this, wxID_ANY, "None");
if (currentId)
label->SetLabel(nsyshid::g_dimensionstoypad.FindFigure(currentId.value()));
auto* moveButton = new wxButton(this, wxID_ANY, _("Move Here"));
if (index == currentIndex)
moveButton->SetLabelText("Pick up and Place");
moveButton->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) {
m_newPad = pad;
m_newIndex = index;
this->EndModal(1);
});
panel->Add(label, 1, wxALL, 5);
panel->Add(moveButton, 1, wxALL, 5);
return panel;
}
uint8 MoveDimensionFigureDialog::GetNewPad() const
{
return m_newPad;
}
uint8 MoveDimensionFigureDialog::GetNewIndex() const
{
return m_newIndex;
}
std::array<std::optional<uint32>, 7> EmulatedUSBDeviceFrame::GetCurrentMinifigs()
{
return m_dimSlots;
}
@@ -1,102 +0,0 @@
#pragma once
#include <array>
#include <wx/dialog.h>
#include <wx/frame.h>
#include "Cafe/OS/libs/nsyshid/Infinity.h"
#include "Cafe/OS/libs/nsyshid/Skylander.h"
class wxBoxSizer;
class wxCheckBox;
class wxFlexGridSizer;
class wxNotebook;
class wxPanel;
class wxStaticBox;
class wxString;
class wxTextCtrl;
class EmulatedUSBDeviceFrame : public wxFrame
{
public:
EmulatedUSBDeviceFrame(wxWindow* parent);
~EmulatedUSBDeviceFrame();
std::array<std::optional<uint32>, 7> GetCurrentMinifigs();
private:
wxCheckBox* m_emulatePortal;
wxCheckBox* m_emulateBase;
wxCheckBox* m_emulateToypad;
std::array<wxTextCtrl*, nsyshid::MAX_SKYLANDERS> m_skylanderSlots;
std::array<wxTextCtrl*, nsyshid::MAX_FIGURES> m_infinitySlots;
std::array<wxTextCtrl*, 7> m_dimensionSlots;
std::array<std::optional<std::tuple<uint8, uint16, uint16>>, nsyshid::MAX_SKYLANDERS> m_skySlots;
std::array<std::optional<uint32>, 7> m_dimSlots;
wxPanel* AddSkylanderPage(wxNotebook* notebook);
wxPanel* AddInfinityPage(wxNotebook* notebook);
wxPanel* AddDimensionsPage(wxNotebook* notebook);
wxBoxSizer* AddSkylanderRow(uint8 row_number, wxStaticBox* box);
wxBoxSizer* AddInfinityRow(wxString name, uint8 row_number, wxStaticBox* box);
wxBoxSizer* AddDimensionPanel(uint8 pad, uint8 index, wxStaticBox* box);
void LoadSkylander(uint8 slot);
void LoadSkylanderPath(uint8 slot, wxString path);
void CreateSkylander(uint8 slot);
void ClearSkylander(uint8 slot);
void UpdateSkylanderEdits();
void LoadFigure(uint8 slot);
void LoadFigurePath(uint8 slot, wxString path);
void CreateFigure(uint8 slot);
void ClearFigure(uint8 slot);
void LoadMinifig(uint8 pad, uint8 index);
void LoadMinifigPath(wxString path_name, uint8 pad, uint8 index);
void CreateMinifig(uint8 pad, uint8 index);
void ClearMinifig(uint8 pad, uint8 index);
void MoveMinifig(uint8 pad, uint8 index);
};
class CreateSkylanderDialog : public wxDialog
{
public:
explicit CreateSkylanderDialog(wxWindow* parent, uint8 slot);
wxString GetFilePath() const;
protected:
wxString m_filePath;
};
class CreateInfinityFigureDialog : public wxDialog
{
public:
explicit CreateInfinityFigureDialog(wxWindow* parent, uint8 slot);
wxString GetFilePath() const;
protected:
wxString m_filePath;
};
class CreateDimensionFigureDialog : public wxDialog
{
public:
explicit CreateDimensionFigureDialog(wxWindow* parent);
wxString GetFilePath() const;
protected:
wxString m_filePath;
};
class MoveDimensionFigureDialog : public wxDialog
{
public:
explicit MoveDimensionFigureDialog(EmulatedUSBDeviceFrame* parent, uint8 currentIndex);
uint8 GetNewPad() const;
uint8 GetNewIndex() const;
protected:
uint8 m_newIndex = 0;
uint8 m_newPad = 0;
private:
wxBoxSizer* AddMinifigSlot(uint8 pad, uint8 index, uint8 oldIndex, std::optional<uint32> currentId);
};
-405
View File
@@ -1,405 +0,0 @@
#include "wxgui/GameProfileWindow.h"
#include <wx/statbox.h>
#include <wx/sizer.h>
#include <wx/panel.h>
#include <wx/notebook.h>
#include <wx/stattext.h>
#include <wx/wupdlock.h>
#include <wx/slider.h>
#include "wxgui/helpers/wxHelpers.h"
#include "input/InputManager.h"
#if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD
#include "resource/embedded/resources.h"
#endif
GameProfileWindow::GameProfileWindow(wxWindow* parent, uint64_t title_id)
: wxFrame(parent, wxID_ANY, _("Edit game profile"), wxDefaultPosition, wxSize{ 390, 350 }, wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER | wxTAB_TRAVERSAL | wxSYSTEM_MENU), m_title_id(title_id)
{
SetIcon(wxICON(X_GAME_PROFILE));
m_game_profile.Reset();
m_game_profile.Load(title_id);
this->SetSizeHints(wxDefaultSize, wxDefaultSize);
auto* main_sizer = new wxBoxSizer(wxVERTICAL);
auto* m_notebook = new wxNotebook(this, wxID_ANY);
// general
{
auto* panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
auto* sizer = new wxBoxSizer(wxVERTICAL);
{
auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(panel, wxID_ANY, _("General")), wxVERTICAL);
auto* box = box_sizer->GetStaticBox();
m_load_libs = new wxCheckBox(box, wxID_ANY, _("Load shared libraries"));
m_load_libs->SetToolTip(_("EXPERT OPTION\nThis option will load libraries from the cafeLibs directory"));
box_sizer->Add(m_load_libs, 0, wxALL, 5);
m_start_with_padview = new wxCheckBox(box, wxID_ANY, _("Launch with gamepad view"));
m_start_with_padview->SetToolTip(_("Games will be launched with gamepad view toggled as default. The view can be toggled with CTRL + TAB"));
box_sizer->Add(m_start_with_padview, 0, wxALL, 5);
sizer->Add(box_sizer, 0, wxEXPAND, 5);
}
// cpu
{
auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(panel, wxID_ANY, _("CPU")), wxVERTICAL);
auto* box = box_sizer->GetStaticBox();
auto* first_row = new wxFlexGridSizer(0, 2, 0, 0);
first_row->SetFlexibleDirection(wxBOTH);
first_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
first_row->Add(new wxStaticText(box, wxID_ANY, _("Mode")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
wxString cpu_modes[] = { _("Single-core interpreter"), _("Single-core recompiler"), _("Multi-core recompiler"), _("Auto (recommended)") };
const sint32 m_cpu_modeNChoices = std::size(cpu_modes);
m_cpu_mode = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_cpu_modeNChoices, cpu_modes, 0);
m_cpu_mode->SetToolTip(_("Set the CPU emulation mode"));
first_row->Add(m_cpu_mode, 0, wxALL, 5);
first_row->Add(new wxStaticText(box, wxID_ANY, _("Thread quantum")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
auto* quantum_sizer = new wxFlexGridSizer(0, 2, 0, 0);
quantum_sizer->SetFlexibleDirection(wxBOTH);
quantum_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
wxString quantum_values[] = { "20000", "45000", "60000", "80000" ,"100000" };
m_thread_quantum = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(quantum_values), quantum_values);
m_thread_quantum->SetMinSize(wxSize(85, -1));
m_thread_quantum->SetToolTip(_("EXPERT OPTION\nSet the maximum thread slice runtime (in virtual cycles)"));
quantum_sizer->Add(m_thread_quantum, 0, wxALL, 5);
quantum_sizer->Add(new wxStaticText(box, wxID_ANY, _("cycles")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
first_row->Add(quantum_sizer, 0, wxEXPAND, 5);
box_sizer->Add(first_row, 0, wxEXPAND, 5);
sizer->Add(box_sizer, 0, wxEXPAND, 5);
}
panel->SetSizer(sizer);
panel->Layout();
sizer->Fit(panel);
m_notebook->AddPage(panel, _("General"), true);
}
// graphic
{
auto* panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
auto* sizer = new wxBoxSizer(wxVERTICAL);
//m_extended_texture_readback = new wxCheckBox(panel, wxID_ANY, _("Extended texture readback"));
//m_extended_texture_readback->SetToolTip(_("Improves emulation accuracy of CPU to GPU memory access at the cost of performance. Required for some games."));
//sizer->Add(m_extended_texture_readback, 0, wxALL, 5);
auto* first_row = new wxFlexGridSizer(0, 2, 0, 0);
first_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
/*first_row->Add(new wxStaticText(panel, wxID_ANY, _("Precompiled shaders")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
wxString precompiled_modes[] = { _("auto"), _("enable"), _("disable") };
const sint32 precompiled_count = std::size(precompiled_modes);
m_precompiled = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, precompiled_count, precompiled_modes, 0);
m_precompiled->SetToolTip(_("Precompiled shaders can speed up the load time on the shader loading screen.\nAuto will enable it for AMD/Intel but disable it for NVIDIA GPUs as a workaround for a driver bug.\n\nRecommended: Auto"));
first_row->Add(m_precompiled, 0, wxALL, 5);*/
first_row->Add(new wxStaticText(panel, wxID_ANY, _("Graphics API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
wxString gapi_values[] = { "", "OpenGL", "Vulkan",
#ifdef ENABLE_METAL
"Metal"
#endif
};
m_graphic_api = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(gapi_values), gapi_values);
first_row->Add(m_graphic_api, 0, wxALL, 5);
first_row->Add(new wxStaticText(panel, wxID_ANY, _("Shader multiplication accuracy")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
wxString mul_values[] = { _("false"), _("true")};
m_shader_mul_accuracy = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(mul_values), mul_values);
m_shader_mul_accuracy->SetToolTip(_("EXPERT OPTION\nControls the accuracy of floating point multiplication in shaders.\n\nRecommended: true"));
first_row->Add(m_shader_mul_accuracy, 0, wxALL, 5);
#ifdef ENABLE_METAL
first_row->Add(new wxStaticText(panel, wxID_ANY, _("Shader fast math")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
wxString math_values[] = { _("false"), _("true") };
m_shader_fast_math = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(math_values), math_values);
m_shader_fast_math->SetToolTip(_("EXPERT OPTION\nEnables fast math for all shaders. May (rarely) cause graphical bugs.\n\nMetal only\n\nRecommended: true"));
first_row->Add(m_shader_fast_math, 0, wxALL, 5);
first_row->Add(new wxStaticText(panel, wxID_ANY, _("Metal buffer cache mode")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
wxString cache_values[] = { _("auto"), _("device private"), _("device shared"), _("host") };
m_metal_buffer_cache_mode = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(cache_values), cache_values);
m_metal_buffer_cache_mode->SetToolTip(_("EXPERT OPTION\nDecides how the buffer cache memory will be managed.\n\nMetal only\n\nRecommended: auto"));
first_row->Add(m_metal_buffer_cache_mode, 0, wxALL, 5);
first_row->Add(new wxStaticText(panel, wxID_ANY, _("Position invariance")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
wxString pos_values[] = { _("auto"), _("false"), _("true") };
m_position_invariance = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(pos_values), pos_values);
m_position_invariance->SetToolTip(_("EXPERT OPTION\nDisables most optimizations for vertex positions. May fix polygon cutouts or flickering in some games.\n\nMetal only\n\nRecommended: auto"));
first_row->Add(m_position_invariance, 0, wxALL, 5);
#endif
/*first_row->Add(new wxStaticText(panel, wxID_ANY, _("GPU buffer cache accuracy")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
wxString accuarcy_values[] = { _("high"), _("medium"), _("low") };
m_cache_accuracy = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(accuarcy_values), accuarcy_values);
m_cache_accuracy->SetToolTip(_("Lower value results in higher performance, but may cause graphical issues"));
first_row->Add(m_cache_accuracy, 0, wxALL, 5);*/
sizer->Add(first_row, 0, wxEXPAND, 5);
panel->SetSizer(sizer);
panel->Layout();
sizer->Fit(panel);
m_notebook->AddPage(panel, _("Graphic"), false);
}
//// audio
//{
// auto panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
// wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
// m_disable_audio = new wxCheckBox(panel, wxID_ANY, _("Disable Audio"), wxDefaultPosition, wxDefaultSize, wxCHK_3STATE | wxCHK_ALLOW_3RD_STATE_FOR_USER);
// sizer->Add(m_disable_audio, 0, wxALL, 5);
// panel->SetSizer(sizer);
// panel->Layout();
// sizer->Fit(panel);
// m_notebook->AddPage(panel, _("Audio"), false);
//}
// controller
{
auto panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
wxBoxSizer* sizer;
sizer = new wxBoxSizer(wxVERTICAL);
wxFlexGridSizer* profile_sizer;
profile_sizer = new wxFlexGridSizer(0, 2, 0, 0);
profile_sizer->SetFlexibleDirection(wxBOTH);
profile_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
for (int i = 0; i < 8; ++i)
{
profile_sizer->Add(new wxStaticText(panel, wxID_ANY, formatWxString(_("Controller {}"), i + 1)), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_controller_profile[i] = new wxComboBox(panel, wxID_ANY,"", wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_DROPDOWN| wxCB_READONLY);
m_controller_profile[i]->SetMinSize(wxSize(250, -1));
m_controller_profile[i]->Bind(wxEVT_COMBOBOX_DROPDOWN, &GameProfileWindow::OnControllerProfileDropdown, this);
m_controller_profile[i]->SetToolTip(_("Forces a given controller profile"));
profile_sizer->Add(m_controller_profile[i], 0, wxALL, 5);
}
sizer->Add(profile_sizer, 0, wxEXPAND, 5);
panel->SetSizer(sizer);
panel->Layout();
sizer->Fit(panel);
m_notebook->AddPage(panel, _("Controller"), false);
}
main_sizer->Add(m_notebook, 1, wxEXPAND | wxALL, 5);
this->SetSizer(main_sizer);
this->Layout();
this->Centre(wxBOTH);
ApplyProfile();
}
GameProfileWindow::~GameProfileWindow()
{
SaveProfile();
}
void GameProfileWindow::OnStreamoutSizeChange(wxCommandEvent& event)
{
wxSlider* slider = wxDynamicCast(event.GetEventObject(), wxSlider);
wxASSERT(slider);
wxStaticText* text = wxDynamicCast(slider->GetClientData(), wxStaticText);
wxASSERT(text);
text->SetLabelText(fmt::format("{} MB", slider->GetValue()));
event.Skip();
}
void GameProfileWindow::OnControllerProfileDropdown(wxCommandEvent& event)
{
wxComboBox* cb = wxDynamicCast(event.GetEventObject(), wxComboBox);
wxASSERT(cb);
wxWindowUpdateLocker lock(cb);
const auto selected_value = cb->GetStringSelection();
cb->Clear();
cb->Append(wxEmptyString);
auto profiles = InputManager::get_profiles();
for (const auto& profile : profiles)
{
cb->Append(wxString::FromUTF8(profile));
}
cb->SetStringSelection(selected_value);
}
void GameProfileWindow::SetProfileInt(gameProfileIntegerOption_t& option, wxCheckBox* checkbox, sint32 value) const
{
const auto state = checkbox->GetValue();
if (state)
{
option.isPresent = true;
option.value = value;
}
else
option.isPresent = false;
}
void GameProfileWindow::ApplyProfile()
{
if(m_game_profile.m_gameName)
this->SetTitle(_("Edit game profile") + " - " + m_game_profile.m_gameName.value());
// general
m_load_libs->SetValue(m_game_profile.m_loadSharedLibraries.value());
m_start_with_padview->SetValue(m_game_profile.m_startWithPadView);
// cpu
// wxString cpu_modes[] = { _("Singlecore-Interpreter"), _("Singlecore-Recompiler"), _("Triplecore-Recompiler"), _("Auto (recommended)") };
switch(m_game_profile.m_cpuMode.value())
{
case CPUMode::SinglecoreInterpreter: m_cpu_mode->SetSelection(0); break;
case CPUMode::SinglecoreRecompiler: m_cpu_mode->SetSelection(1); break;
case CPUMode::DualcoreRecompiler: m_cpu_mode->SetSelection(2); break;
case CPUMode::MulticoreRecompiler: m_cpu_mode->SetSelection(2); break;
default: m_cpu_mode->SetSelection(3);
}
m_thread_quantum->SetStringSelection(fmt::format("{}", m_game_profile.m_threadQuantum));
// gpu
if (!m_game_profile.m_graphics_api.has_value())
m_graphic_api->SetSelection(0); // selecting ""
else
m_graphic_api->SetSelection(1 + m_game_profile.m_graphics_api.value()); // "", OpenGL, Vulkan, Metal
m_shader_mul_accuracy->SetSelection((int)m_game_profile.m_accurateShaderMul);
#ifdef ENABLE_METAL
m_shader_fast_math->SetSelection((int)m_game_profile.m_shaderFastMath);
m_metal_buffer_cache_mode->SetSelection((int)m_game_profile.m_metalBufferCacheMode);
m_position_invariance->SetSelection((int)m_game_profile.m_positionInvariance);
#endif
//// audio
//m_disable_audio->Set3StateValue(GetCheckboxState(m_game_profile.disableAudio));
// controller
auto profiles = InputManager::get_profiles();
for (const auto& cb : m_controller_profile)
{
cb->Clear();
for (const auto& profile : profiles)
{
cb->Append(wxString::FromUTF8(profile));
}
}
for (int i = 0; i < InputManager::kMaxController; ++i)
{
const bool has_value = m_game_profile.m_controllerProfile[i].has_value();
if (has_value)
{
const auto& v = m_game_profile.m_controllerProfile[i].value();
m_controller_profile[i]->SetStringSelection(wxString::FromUTF8(v));
}
else
m_controller_profile[i]->SetSelection(wxNOT_FOUND);
}
}
void GameProfileWindow::SaveProfile()
{
// update game profile struct
m_game_profile.Reset();
// general
m_game_profile.m_loadSharedLibraries = m_load_libs->GetValue();
m_game_profile.m_startWithPadView = m_start_with_padview->GetValue();
// cpu
switch(m_cpu_mode->GetSelection())
{
case 0: m_game_profile.m_cpuMode = CPUMode::SinglecoreInterpreter; break;
case 1: m_game_profile.m_cpuMode = CPUMode::SinglecoreRecompiler; break;
case 2: m_game_profile.m_cpuMode = CPUMode::MulticoreRecompiler; break;
default:
m_game_profile.m_cpuMode = CPUMode::Auto;
}
const wxString thread_quantum = m_thread_quantum->GetStringSelection();
if (!thread_quantum.empty())
{
m_game_profile.m_threadQuantum = ConvertString<uint32>(thread_quantum.ToStdString());
m_game_profile.m_threadQuantum = std::min<uint32>(m_game_profile.m_threadQuantum, 536870912);
m_game_profile.m_threadQuantum = std::max<uint32>(m_game_profile.m_threadQuantum, 5000);
}
// gpu
m_game_profile.m_accurateShaderMul = (AccurateShaderMulOption)m_shader_mul_accuracy->GetSelection();
if (m_game_profile.m_accurateShaderMul != AccurateShaderMulOption::False && m_game_profile.m_accurateShaderMul != AccurateShaderMulOption::True)
m_game_profile.m_accurateShaderMul = AccurateShaderMulOption::True; // force a legal value
#ifdef ENABLE_METAL
m_game_profile.m_shaderFastMath = (bool)m_shader_fast_math->GetSelection();
m_game_profile.m_metalBufferCacheMode = (MetalBufferCacheMode)m_metal_buffer_cache_mode->GetSelection();
m_game_profile.m_positionInvariance = (PositionInvariance)m_position_invariance->GetSelection();
#endif
if (m_graphic_api->GetSelection() == 0)
m_game_profile.m_graphics_api = {};
else
m_game_profile.m_graphics_api = (GraphicAPI)(m_graphic_api->GetSelection() - 1); // "", OpenGL, Vulkan, Metal
// controller
for (int i = 0; i < 8; ++i)
{
if(m_controller_profile[i]->GetSelection() == wxNOT_FOUND)
{
m_game_profile.m_controllerProfile[i].reset();
continue;
}
const wxString profile_name = m_controller_profile[i]->GetStringSelection();
if (profile_name.empty())
m_game_profile.m_controllerProfile[i].reset();
else
m_game_profile.m_controllerProfile[i] = profile_name.ToUTF8();
}
// update game profile file
m_game_profile.Save(m_title_id);
}
void GameProfileWindow::SetSliderValue(wxSlider* slider, sint32 new_value) const
{
wxASSERT(slider);
slider->SetValue(new_value);
wxCommandEvent slider_event(wxEVT_SLIDER, slider->GetId());
slider_event.SetEventObject(slider);
slider_event.SetClientData((void*)IsFrozen());
wxPostEvent(slider->GetEventHandler(), slider_event);
}
-55
View File
@@ -1,55 +0,0 @@
#pragma once
#include <wx/frame.h>
#include <wx/checkbox.h>
#include <wx/choice.h>
#include <wx/combobox.h>
#include <wx/slider.h>
#include "Cafe/GameProfile/GameProfile.h"
class GameProfileWindow : public wxFrame
{
public:
GameProfileWindow(wxWindow* parent, uint64_t title_id);
~GameProfileWindow();
private:
uint64_t m_title_id;
GameProfile m_game_profile;
void OnStreamoutSizeChange(wxCommandEvent& event);
void OnControllerProfileDropdown(wxCommandEvent& event);
void SetSliderValue(wxSlider* slider, sint32 new_value) const;
void SetProfileInt(gameProfileIntegerOption_t& option, wxCheckBox* checkbox, sint32 value) const;
void ApplyProfile();
void SaveProfile();
// general
wxCheckBox* m_load_libs, *m_start_with_padview;
// cpu
wxChoice *m_cpu_mode;
wxChoice* m_thread_quantum;
// gpu
//wxCheckBox* m_extended_texture_readback;
//wxChoice* m_precompiled;
wxChoice* m_graphic_api;
wxChoice* m_shader_mul_accuracy;
#ifdef ENABLE_METAL
wxChoice* m_shader_fast_math;
wxChoice* m_metal_buffer_cache_mode;
wxChoice* m_position_invariance;
#endif
//wxChoice* m_cache_accuracy;
// audio
//wxCheckBox* m_disable_audio;
// controller
wxComboBox* m_controller_profile[8];
};
-339
View File
@@ -1,339 +0,0 @@
#include "wxgui/wxgui.h"
#include "wxgui/GameUpdateWindow.h"
#include "util/helpers/helpers.h"
#include "util/helpers/SystemException.h"
#include "Cafe/TitleList/GameInfo.h"
#include "config/ActiveSettings.h"
#include "wxgui/helpers/wxHelpers.h"
wxString _GetTitleIdTypeStr(TitleId titleId)
{
TitleIdParser tip(titleId);
switch (tip.GetType())
{
case TitleIdParser::TITLE_TYPE::AOC:
return _("DLC");
case TitleIdParser::TITLE_TYPE::BASE_TITLE:
return _("Base game");
case TitleIdParser::TITLE_TYPE::BASE_TITLE_DEMO:
return _("Demo");
case TitleIdParser::TITLE_TYPE::SYSTEM_TITLE:
case TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE:
return _("System title");
case TitleIdParser::TITLE_TYPE::SYSTEM_DATA:
return _("System data title");
case TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE:
return _("Update");
default:
break;
}
return "Unknown";
}
bool GameUpdateWindow::ParseUpdate(const fs::path& metaPath)
{
m_title_info = TitleInfo(metaPath);
if (!m_title_info.IsValid())
return false;
fs::path target_location = ActiveSettings::GetMlcPath(m_title_info.GetInstallPath());
std::error_code ec;
if (fs::exists(target_location, ec))
{
try
{
const TitleInfo tmp(target_location);
if (!tmp.IsValid())
{
// does not exist / is not valid. We allow to overwrite it
}
else
{
TitleIdParser tip(m_title_info.GetAppTitleId());
TitleIdParser tipOther(tmp.GetAppTitleId());
if (tip.GetType() != tipOther.GetType())
{
auto typeStrToInstall = _GetTitleIdTypeStr(m_title_info.GetAppTitleId());
auto typeStrCurrentlyInstalled = _GetTitleIdTypeStr(tmp.GetAppTitleId());
auto wxMsg = _("It seems that there is already a title installed at the target location but it has a different type.\nCurrently installed: \'{}\' Installing: \'{}\'\n\nThis can happen for titles which were installed with very old Cemu versions.\nDo you still want to continue with the installation? It will replace the currently installed title.");
wxMessageDialog dialog(this, formatWxString(wxMsg, typeStrCurrentlyInstalled, typeStrToInstall), _("Warning"), wxCENTRE | wxYES_NO | wxICON_EXCLAMATION);
if (dialog.ShowModal() != wxID_YES)
return false;
}
else if (tmp.GetAppTitleVersion() == m_title_info.GetAppTitleVersion())
{
wxMessageDialog dialog(this, _("It seems that the selected title is already installed, do you want to reinstall it?"), _("Warning"), wxCENTRE | wxYES_NO);
if (dialog.ShowModal() != wxID_YES)
return false;
}
else if (tmp.GetAppTitleVersion() > m_title_info.GetAppTitleVersion())
{
wxMessageDialog dialog(this, _("It seems that a newer version is already installed, do you still want to install the older version?"), _("Warning"), wxCENTRE | wxYES_NO);
if (dialog.ShowModal() != wxID_YES)
return false;
}
}
// temp rename until done
m_backup_folder = target_location;
m_backup_folder.replace_extension(".backup");
std::error_code ec;
while (fs::exists(m_backup_folder, ec) || ec)
{
fs::remove_all(m_backup_folder, ec);
if (ec)
{
const auto error_msg = formatWxString(_("Error when trying to move former title installation:\n{}"), GetSystemErrorMessage(ec));
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this);
return false;
}
// wait so filesystem doesnt
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
fs::rename(target_location, m_backup_folder);
}
catch (const std::exception& ex)
{
cemuLog_log(LogType::Force, "GameUpdateWindow::ParseUpdate exist-error: {} at {}", ex.what(), _pathToUtf8(target_location));
}
}
m_target_path = target_location;
fs::path source(metaPath);
m_source_paths =
{
fs::path(source).append("content"),
fs::path(source).append("code"),
fs::path(source).append("meta")
};
m_required_size = 0;
for (auto& path : m_source_paths)
{
for (const fs::directory_entry& f : fs::recursive_directory_iterator(path))
{
if (is_regular_file(f.path()))
m_required_size += file_size(f.path());
}
}
const fs::space_info targetSpace = fs::space(ActiveSettings::GetMlcPath());
if (targetSpace.free <= m_required_size)
{
auto string = formatWxString(_("Not enough space available.\nRequired: {0} MB\nAvailable: {1} MB"), (m_required_size / 1024 / 1024), (targetSpace.free / 1024 / 1024));
throw std::runtime_error(string.utf8_string());
}
return true;
}
GameUpdateWindow::GameUpdateWindow(wxWindow& parent, const fs::path& filePath)
: wxDialog(&parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX),
m_thread_state(ThreadRunning)
{
try
{
#if BOOST_OS_WINDOWS
SetLastError(0);
#endif
if(!ParseUpdate(filePath))
throw AbortException();
}
catch (const std::runtime_error& ex)
{
throw SystemException(ex);
}
auto sizer = new wxBoxSizer(wxVERTICAL);
TitleIdParser tip(GetTitleId());
if (tip.GetType() == TitleIdParser::TITLE_TYPE::AOC)
SetTitle(_("Installing DLC..."));
else if (tip.GetType() == TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE)
SetTitle(_("Installing update..."));
else if (tip.IsSystemTitle())
SetTitle(_("Installing system title..."));
else
SetTitle(_("Installing title..."));
m_processBar = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(500, 20), wxGA_HORIZONTAL);
m_processBar->SetValue(0);
m_processBar->SetRange((sint32)(m_required_size / 1000));
sizer->Add(m_processBar, 0, wxALL | wxEXPAND, 5);
wxButton* m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel"));
m_cancelButton->Bind(wxEVT_BUTTON, &GameUpdateWindow::OnCancelButton, this);
sizer->Add(m_cancelButton, 0, wxALIGN_RIGHT | wxALL, 5);
this->SetSizer(sizer);
this->Centre(wxBOTH);
wxWindowBase::Layout();
wxWindowBase::Fit();
m_timer = new wxTimer(this);
this->Bind(wxEVT_TIMER, &GameUpdateWindow::OnUpdate, this);
this->Bind(wxEVT_CLOSE_WINDOW, &GameUpdateWindow::OnClose, this);
m_timer->Start(250);
m_thread_state = ThreadRunning;
m_thread = std::thread(&GameUpdateWindow::ThreadWork, this);
}
void GameUpdateWindow::ThreadWork()
{
fs::directory_entry currentDirEntry;
try
{
// create base directories
for (auto& path : m_source_paths)
{
if (!path.has_stem())
continue;
fs::path targetDir = fs::path(m_target_path) / path.stem();
create_directories(targetDir);
}
for (auto& path : m_source_paths)
{
if (m_thread_state == ThreadCanceled)
break;
if (!path.has_parent_path())
continue;
const auto len = path.parent_path().string().size() + 1;
for (const fs::directory_entry& f : fs::recursive_directory_iterator {path})
{
if (m_thread_state == ThreadCanceled)
break;
currentDirEntry = f;
fs::path relative(f.path().string().substr(len));
fs::path target = fs::path(m_target_path) / relative;
if (is_directory(f))
{
create_directories(target);
continue;
}
copy(f, target, fs::copy_options::overwrite_existing);
if (is_regular_file(f.path()))
{
m_processed_size += file_size(f.path());
}
}
}
}
catch (const std::exception& ex)
{
std::stringstream error_msg;
error_msg << GetSystemErrorMessage(ex);
if(currentDirEntry != fs::directory_entry{})
error_msg << fmt::format("\n{}\n{}",_("Current file:").utf8_string(), _pathToUtf8(currentDirEntry.path()));
m_thread_exception = error_msg.str();
m_thread_state = ThreadCanceled;
}
if (m_thread_state == ThreadCanceled)
{
if(fs::exists(m_target_path))
fs::remove_all(m_target_path);
}
else
m_thread_state = ThreadFinished;
}
GameUpdateWindow::~GameUpdateWindow()
{
m_timer->Stop();
if (m_thread.joinable())
m_thread.join();
}
int GameUpdateWindow::ShowModal()
{
wxDialog::ShowModal();
return m_thread_state == ThreadCanceled ? wxID_CANCEL : wxID_OK;
}
void GameUpdateWindow::OnClose(wxCloseEvent& event)
{
if (m_thread_state == ThreadRunning)
{
wxMessageDialog dialog(this, _("Do you really want to cancel the installation process?\n\nCanceling the process will delete the applied files."), _("Info"), wxCENTRE | wxYES_NO);
if (dialog.ShowModal() != wxID_YES)
return;
m_thread_state = ThreadCanceled;
}
m_timer->Stop();
if (m_thread.joinable())
m_thread.join();
if(!m_backup_folder.empty())
{
if(m_thread_state == ThreadCanceled)
{
// restore backup
try
{
if(fs::exists(m_target_path))
fs::remove_all(m_target_path);
fs::rename(m_backup_folder, m_target_path);
}
catch (const std::exception& ex)
{
cemuLog_logDebug(LogType::Force, "can't restore update backup: {}",ex.what());
}
}
else
{
// delete backup
try
{
if(fs::exists(m_backup_folder))
fs::remove_all(m_backup_folder);
}
catch (const std::exception& ex)
{
cemuLog_logDebug(LogType::Force, "can't delete update backup: {}",ex.what());
}
}
m_backup_folder.clear();
}
event.Skip();
}
void GameUpdateWindow::OnUpdate(const wxTimerEvent& event)
{
if (m_thread_state != ThreadRunning)
{
Close();
return;
}
const auto processedSize = (sint32)(m_processed_size / 1000);
if (m_processBar->GetValue() != processedSize)
m_processBar->SetValue(processedSize);
}
void GameUpdateWindow::OnCancelButton(const wxCommandEvent& event)
{
Close();
}
-67
View File
@@ -1,67 +0,0 @@
#pragma once
#include "Cafe/TitleList/GameInfo.h"
#include <wx/dialog.h>
#include <wx/timer.h>
#include <wx/gauge.h>
#include <atomic>
#include <string>
#include <array>
#include <memory>
// thrown if users doesn't wish to reinstall update/dlc
class AbortException : public std::exception {};
class GameUpdateWindow : public wxDialog
{
public:
GameUpdateWindow(wxWindow& parent, const fs::path& metaPath);
~GameUpdateWindow();
uint64 GetTitleId() const { return m_title_info.GetAppTitleId(); }
bool HasException() const { return !m_thread_exception.empty(); }
//bool IsDLC() const { return m_game_info->IsDLC(); }
//bool IsUpdate() const { return m_game_info->IsUpdate(); }
const std::string& GetExceptionMessage() const { return m_thread_exception; }
const std::string GetGameName() const { return m_title_info.GetMetaTitleName(); }
uint32 GetTargetVersion() const { return m_title_info.GetAppTitleVersion(); }
fs::path GetTargetPath() const { return fs::path(m_target_path); }
int ShowModal() override;
void OnClose(wxCloseEvent& event);
void OnUpdate(const wxTimerEvent& event);
void OnCancelButton(const wxCommandEvent& event);
//uint64 GetUpdateTitleId() const { return m_title_info->GetUpdateTitleId(); }
//uint64 GetDLCTitleId() const { return m_game_info->GetDLCTitleId(); }
private:
//std::unique_ptr<GameInfoDEPRECATED> m_game_info;
TitleInfo m_title_info;
enum ThreadState_t
{
ThreadRunning,
ThreadCanceled,
ThreadFinished,
};
uint64_t m_required_size;
fs::path m_target_path;
std::array<fs::path, 3> m_source_paths;
bool ParseUpdate(const fs::path& metaPath);
std::atomic<uint64> m_processed_size = 0;
std::atomic<ThreadState_t> m_thread_state;
std::string m_thread_exception;
std::thread m_thread;
void ThreadWork();
fs::path m_backup_folder; // for prev update data
wxGauge* m_processBar;
wxTimer* m_timer;
};
File diff suppressed because it is too large. Load diff
-143
View File
@@ -1,143 +0,0 @@
#pragma once
#include "config/CemuConfig.h"
#include <wx/collpane.h>
#include <wx/propgrid/propgrid.h>
#include <Cafe/Account/Account.h>
class wxCheckBox;
class wxChoice;
class wxColourPickerCtrl;
class wxListBox;
class wxNotebook;
class wxRadioBox;
class wxSlider;
class wxSpinCtrl;
class wxSpinCtrlDouble;
class wxStaticText;
wxDECLARE_EVENT(wxEVT_ACCOUNTLIST_REFRESH, wxCommandEvent);
class GeneralSettings2 : public wxDialog
{
public:
GeneralSettings2(wxWindow* parent, bool game_launched);
~GeneralSettings2();
[[nodiscard]] bool ShouldReloadGamelist() const { return m_reload_gamelist; }
[[nodiscard]] bool MLCModified() const { return m_mlc_modified; }
void OnClose(wxCloseEvent& event);
private:
void ValidateConfig();
void StoreConfig();
void DisableSettings(bool game_launched);
bool m_reload_gamelist = false;
bool m_mlc_modified = false;
bool m_game_launched;
bool m_has_account_change = false; // keep track of dirty state of accounts
std::vector<GraphicAPI> m_api_map; // map from dropdown index to GraphicsAPISetting, used in HandleGraphicsApiSelection
wxPanel* AddGeneralPage(wxNotebook* notebook);
wxPanel* AddGraphicsPage(wxNotebook* notebook);
wxPanel* AddAudioPage(wxNotebook* notebook);
wxPanel* AddOverlayPage(wxNotebook* notebook);
wxPanel* AddAccountPage(wxNotebook* notebook);
wxPanel* AddDebugPage(wxNotebook* notebook);
// General
wxChoice * m_language;
wxCheckBox* m_save_window_position_size;
wxCheckBox* m_save_padwindow_position_size;
wxCheckBox* m_discord_presence, *m_fullscreen_menubar;
wxCheckBox* m_auto_update, *m_receive_untested_releases, *m_save_screenshot;
wxCheckBox* m_disable_screensaver;
wxCheckBox* m_play_boot_sound;
#if BOOST_OS_WINDOWS
wxChoice* m_msw_theme;
#endif
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
wxCheckBox* m_feral_gamemode;
#endif
wxListBox* m_game_paths;
wxTextCtrl* m_mlc_path;
// Graphics
wxChoice* m_graphic_api, * m_graphic_device;
wxChoice* m_vsync;
wxCheckBox* m_overrideGamma;
wxSpinCtrlDouble* m_overrideGammaValue;
wxSpinCtrlDouble* m_userDisplayGamma;
wxCheckBox* m_userDisplayisSRGB;
wxCheckBox *m_async_compile, *m_gx2drawdone_sync;
#ifdef ENABLE_METAL
wxCheckBox *m_force_mesh_shaders;
#endif
wxRadioBox* m_upscale_filter, *m_downscale_filter, *m_fullscreen_scaling;
wxChoice* m_overlay_position, *m_notification_position, *m_overlay_scale, *m_notification_scale;
wxCheckBox* m_controller_profile_name, *m_controller_low_battery, *m_shader_compiling, *m_friends_data;
wxCheckBox *m_overlay_fps, *m_overlay_drawcalls, *m_overlay_cpu, *m_overlay_cpu_per_core,*m_overlay_ram, *m_overlay_vram, *m_overlay_debug;
wxColourPickerCtrl *m_overlay_font_color, *m_notification_font_color;
// Audio
wxChoice* m_audio_api;
wxSlider *m_audio_latency;
wxSlider *m_tv_volume, *m_pad_volume, *m_input_volume, *m_portal_volume;
wxChoice *m_tv_channels, *m_pad_channels, *m_input_channels;
wxChoice *m_tv_device, *m_pad_device, *m_input_device, *m_portal_device;
// Account
wxButton* m_create_account, * m_delete_account;
wxChoice* m_active_account;
wxRadioBox* m_active_service;
wxCollapsiblePane* m_account_information;
wxPropertyGrid* m_account_grid;
wxBitmapButton* m_validate_online;
wxStaticText* m_online_status;
// Debug
wxChoice* m_crash_dump;
wxSpinCtrl* m_gdb_port;
#ifdef ENABLE_METAL
wxTextCtrl* m_gpu_capture_dir;
wxCheckBox* m_framebuffer_fetch;
#endif
void OnAccountCreate(wxCommandEvent& event);
void OnAccountDelete(wxCommandEvent& event);
void OnAccountSettingsChanged(wxPropertyGridEvent& event);
void OnAudioLatencyChanged(wxCommandEvent& event);
void OnVolumeChanged(wxCommandEvent& event);
void OnInputVolumeChanged(wxCommandEvent& event);
void OnSliderChangedPercent(wxCommandEvent& event);
void OnLatencySliderChanged(wxCommandEvent& event);
void OnAudioAPISelected(wxCommandEvent& event);
void OnAudioDeviceSelected(wxCommandEvent& event);
void OnAudioChannelsSelected(wxCommandEvent& event);
void OnGraphicAPISelected(wxCommandEvent& event);
void OnUserDisplaySRGBSelected(wxCommandEvent& event);
void OnAddPathClicked(wxCommandEvent& event);
void OnRemovePathClicked(wxCommandEvent& event);
void OnActiveAccountChanged(wxCommandEvent& event);
void OnMLCPathSelect(wxCommandEvent& event);
void OnMLCPathClear(wxCommandEvent& event);
void OnShowOnlineValidator(wxCommandEvent& event);
void OnAccountServiceChanged(wxCommandEvent& event);
static wxString GetOnlineAccountErrorMessage(OnlineAccountError error);
uint32 GetSelectedAccountPersistentId();
// updates cemu audio devices
void UpdateAudioDevice();
// refreshes audio device list for dropdown
void UpdateAudioDeviceList();
void ResetAccountInformation();
void UpdateAccountInformation();
void UpdateOnlineAccounts();
void HandleGraphicsApiSelection();
void ApplyConfig();
};
-283
View File
@@ -1,283 +0,0 @@
#include "wxgui/GettingStartedDialog.h"
#include <wx/sizer.h>
#include <wx/filepicker.h>
#include <wx/statbox.h>
#include <wx/msgdlg.h>
#include <wx/radiobox.h>
#include <wx/button.h>
#include "config/ActiveSettings.h"
#include "wxCemuConfig.h"
#include "wxgui/CemuApp.h"
#include "wxgui/DownloadGraphicPacksWindow.h"
#include "wxgui/GraphicPacksWindow2.h"
#include "wxgui/input/InputSettings2.h"
#include "config/CemuConfig.h"
#include "Cafe/TitleList/TitleList.h"
#if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD
#include "resource/embedded/resources.h"
#endif
#include "wxHelper.h"
wxDEFINE_EVENT(EVT_REFRESH_FIRST_PAGE, wxCommandEvent); // used to refresh the first page after the language change
wxPanel* GettingStartedDialog::CreatePage1()
{
auto* mainPanel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
auto* page1_sizer = new wxBoxSizer(wxVERTICAL);
{
auto* sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(new wxStaticBitmap(mainPanel, wxID_ANY, wxICON(M_WND_ICON128)), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_page1.staticText11 = new wxStaticText(mainPanel, wxID_ANY, _("It looks like you're starting Cemu for the first time.\nThis quick setup assistant will help you get the best experience"));
m_page1.staticText11->Wrap(-1);
sizer->Add(m_page1.staticText11, 0, wxALL, 5);
page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5);
}
if(ActiveSettings::IsPortableMode())
{
m_page1.portableModeInfoText = new wxStaticText(mainPanel, wxID_ANY, _("Cemu is running in portable mode"));
m_page1.portableModeInfoText->Show(true);
page1_sizer->Add(m_page1.portableModeInfoText, 0, wxALL, 5);
}
// language selection
#if 0
{
m_page1.languageBoxSizer = new wxStaticBoxSizer(wxVERTICAL, mainPanel, _("Language"));
m_page1.languageText = new wxStaticText(m_page1.languageBoxSizer->GetStaticBox(), wxID_ANY, _("Select the language you want to use in Cemu"));
m_page1.languageBoxSizer->Add(m_page1.languageText, 0, wxALL, 5);
wxString language_choices[] = { _("Default") };
wxChoice* m_language = new wxChoice(m_page1.languageBoxSizer->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(language_choices), language_choices);
m_language->SetSelection(0);
for (const auto& language : wxGetApp().GetLanguages())
{
m_language->Append(language->DescriptionNative);
}
m_language->SetSelection(0);
m_page1.languageBoxSizer->Add(m_language, 0, wxALL | wxEXPAND, 5);
page1_sizer->Add(m_page1.languageBoxSizer, 0, wxALL | wxEXPAND, 5);
m_language->Bind(wxEVT_CHOICE, [this, m_language](const auto&)
{
const auto language = m_language->GetStringSelection();
auto selection = m_language->GetSelection();
if (selection == 0)
GetConfig().language = wxLANGUAGE_DEFAULT;
else
{
auto& app = wxGetApp();
const auto language = m_language->GetStringSelection();
for (const auto& lang : app.GetLanguages())
{
if (lang->DescriptionNative == language)
{
app.LocalizeUI(static_cast<wxLanguage>(lang->Language));
wxCommandEvent event(EVT_REFRESH_FIRST_PAGE);
wxPostEvent(this, event);
break;
}
}
}
});
}
#endif
{
m_page1.gamePathBoxSizer = new wxStaticBoxSizer(wxVERTICAL, mainPanel, _("Game paths"));
m_page1.gamePathText = new wxStaticText(m_page1.gamePathBoxSizer->GetStaticBox(), wxID_ANY, _("The game path is scanned by Cemu to automatically locate your games, game updates and DLCs. We recommend creating a dedicated directory in which\nyou place all your Wii U game files. Additional paths can be set later in Cemu's general settings. All common Wii U game formats are supported by Cemu."));
m_page1.gamePathBoxSizer->Add(m_page1.gamePathText, 0, wxALL, 5);
auto* game_path_sizer = new wxBoxSizer(wxHORIZONTAL);
m_page1.gamePathText2 = new wxStaticText(m_page1.gamePathBoxSizer->GetStaticBox(), wxID_ANY, _("Game path"));
game_path_sizer->Add(m_page1.gamePathText2, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_page1.gamePathPicker = new wxDirPickerCtrl(m_page1.gamePathBoxSizer->GetStaticBox(), wxID_ANY, wxEmptyString, _("Select a folder"));
game_path_sizer->Add(m_page1.gamePathPicker, 1, wxALL, 5);
m_page1.gamePathBoxSizer->Add(game_path_sizer, 0, wxEXPAND, 5);
page1_sizer->Add(m_page1.gamePathBoxSizer, 0, wxALL | wxEXPAND, 5);
}
{
auto* sizer = new wxStaticBoxSizer(wxVERTICAL, mainPanel, _("Graphic packs && mods"));
sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("Graphic packs improve games by offering the ability to change resolution, increase FPS, tweak visuals or add gameplay modifications.\nGet started by opening the graphic packs configuration window.\n")), 0, wxALL, 5);
auto* download_gp = new wxButton(sizer->GetStaticBox(), wxID_ANY, _("Download and configure graphic packs"));
download_gp->Bind(wxEVT_BUTTON, &GettingStartedDialog::OnConfigureGPs, this);
sizer->Add(download_gp, 0, wxALIGN_CENTER | wxALL, 5);
page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5);
}
{
auto* sizer = new wxFlexGridSizer(0, 1, 0, 0);
sizer->AddGrowableCol(0);
sizer->AddGrowableRow(0);
sizer->SetFlexibleDirection(wxBOTH);
sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_ALL);
auto* next = new wxButton(mainPanel, wxID_ANY, _("Next"));
next->Bind(wxEVT_BUTTON, [this](const auto&){m_notebook->SetSelection(1); });
sizer->Add(next, 0, wxALIGN_BOTTOM | wxALIGN_RIGHT | wxALL, 5);
page1_sizer->Add(sizer, 1, wxEXPAND, 5);
}
mainPanel->SetSizer(page1_sizer);
return mainPanel;
}
wxPanel* GettingStartedDialog::CreatePage2()
{
auto* result = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
auto* page2_sizer = new wxBoxSizer(wxVERTICAL);
{
auto* sizer = new wxStaticBoxSizer(new wxStaticBox(result, wxID_ANY, _("Input settings")), wxVERTICAL);
sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("You can configure one controller for each player.\nWe advise you to always use GamePad as emulated input for the first player, since many games expect the GamePad to be present.\nIt is also required for touch functionality.\nThe default global hotkeys are:\nCTRL - show pad screen\nCTRL + TAB - toggle pad screen\nALT + ENTER - toggle fullscreen\nESC - leave fullscreen\n\nIf you're having trouble configuring your controller, make sure to have it in idle state and press calibrate.\nAlso don't set the axis deadzone too low.")), 0, wxALL, 5);
auto* input_button = new wxButton(sizer->GetStaticBox(), wxID_ANY, _("Configure input"));
input_button->Bind(wxEVT_BUTTON, &GettingStartedDialog::OnInputSettings, this);
sizer->Add(input_button, 0, wxALIGN_CENTER | wxALL, 5);
page2_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5);
}
{
auto* sizer = new wxStaticBoxSizer(new wxStaticBox(result, wxID_ANY, _("Additional options")), wxVERTICAL);
auto* option_sizer = new wxFlexGridSizer(0, 2, 0, 0);
option_sizer->SetFlexibleDirection(wxBOTH);
option_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
m_page2.fullscreenCheckbox = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Start games with fullscreen"));
option_sizer->Add(m_page2.fullscreenCheckbox, 0, wxALL, 5);
m_page2.separateCheckbox = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Open separate pad screen"));
option_sizer->Add(m_page2.separateCheckbox, 0, wxALL, 5);
m_page2.updateCheckbox = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Automatically check for updates"));
option_sizer->Add(m_page2.updateCheckbox, 0, wxALL, 5);
#if BOOST_OS_LINUX
if (!std::getenv("APPIMAGE")) {
m_page2.updateCheckbox->Disable();
}
#endif
sizer->Add(option_sizer, 1, wxEXPAND, 5);
page2_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5);
}
{
auto* sizer = new wxFlexGridSizer(0, 3, 0, 0);
sizer->AddGrowableCol(1);
sizer->AddGrowableRow(0);
sizer->SetFlexibleDirection(wxBOTH);
sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_ALL);
auto* previous = new wxButton(result, wxID_ANY, _("Previous"));
previous->Bind(wxEVT_BUTTON, [this](const auto&) {m_notebook->SetSelection(0); });
sizer->Add(previous, 0, wxALIGN_BOTTOM | wxALIGN_RIGHT | wxALL, 5);
auto* close = new wxButton(result, wxID_ANY, _("Close"));
close->Bind(wxEVT_BUTTON, [this](const auto&){ Close(); });
sizer->Add(close, 1, wxALIGN_BOTTOM | wxALIGN_RIGHT | wxALL, 5);
page2_sizer->Add(sizer, 1, wxEXPAND | wxLEFT, 5);
}
result->SetSizer(page2_sizer);
return result;
}
void GettingStartedDialog::ApplySettings()
{
auto& config = GetWxGUIConfig();
m_page2.fullscreenCheckbox->SetValue(config.fullscreen.GetValue());
m_page2.updateCheckbox->SetValue(config.check_update.GetValue());
m_page2.separateCheckbox->SetValue(config.pad_open.GetValue());
}
void GettingStartedDialog::UpdateWindowSize()
{
for (auto it = m_notebook->GetChildren().GetFirst(); it; it = it->GetNext())
{
it->GetData()->Layout();
}
m_notebook->Layout();
Layout();
Fit();
}
void GettingStartedDialog::OnClose(wxCloseEvent& event)
{
event.Skip();
auto& wxGUIConfig = GetWxGUIConfig();
wxGUIConfig.fullscreen = m_page2.fullscreenCheckbox->GetValue();
wxGUIConfig.check_update = m_page2.updateCheckbox->GetValue();
wxGUIConfig.pad_open = m_page2.separateCheckbox->GetValue();
auto& config = GetConfig();
const fs::path gamePath = wxHelper::MakeFSPath(m_page1.gamePathPicker->GetPath());
std::error_code ec;
if (!gamePath.empty() && fs::exists(gamePath, ec))
{
const auto it = std::find(config.game_paths.cbegin(), config.game_paths.cend(), gamePath);
if (it == config.game_paths.cend())
{
config.game_paths.emplace_back(_pathToUtf8(gamePath));
}
}
}
GettingStartedDialog::GettingStartedDialog(wxWindow* parent)
: wxDialog(parent, wxID_ANY, _("Getting started"), wxDefaultPosition, { 740,530 }, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
auto* sizer = new wxBoxSizer(wxVERTICAL);
m_notebook = new wxSimplebook(this, wxID_ANY);
auto* m_page1 = CreatePage1();
m_notebook->AddPage(m_page1, wxEmptyString);
auto* m_page2 = CreatePage2();
m_notebook->AddPage(m_page2, wxEmptyString);
sizer->Add(m_notebook, 1, wxEXPAND | wxALL, 5);
this->SetSizer(sizer);
this->Centre(wxBOTH);
this->Bind(wxEVT_CLOSE_WINDOW, &GettingStartedDialog::OnClose, this);
ApplySettings();
UpdateWindowSize();
}
void GettingStartedDialog::OnConfigureGPs(wxCommandEvent& event)
{
DownloadGraphicPacksWindow dialog(this);
dialog.ShowModal();
GraphicPacksWindow2::RefreshGraphicPacks();
GraphicPacksWindow2 window(this, 0);
window.ShowModal();
}
void GettingStartedDialog::OnInputSettings(wxCommandEvent& event)
{
InputSettings2 dialog(this);
dialog.ShowModal();
}
-48
View File
@@ -1,48 +0,0 @@
#pragma once
#include <wx/dialog.h>
#include <wx/checkbox.h>
#include <wx/filepicker.h>
#include <wx/statbmp.h>
#include <wx/simplebook.h>
#include <wx/stattext.h>
#include <wx/panel.h>
class GettingStartedDialog : public wxDialog
{
public:
GettingStartedDialog(wxWindow* parent = nullptr);
private:
wxPanel* CreatePage1();
wxPanel* CreatePage2();
void ApplySettings();
void UpdateWindowSize();
void OnClose(wxCloseEvent& event);
void OnConfigureGPs(wxCommandEvent& event);
void OnInputSettings(wxCommandEvent& event);
wxSimplebook* m_notebook;
struct
{
// header
wxStaticText* staticText11{};
wxStaticText* portableModeInfoText{};
// game path box
wxStaticBoxSizer* gamePathBoxSizer{};
wxStaticText* gamePathText{};
wxStaticText* gamePathText2{};
wxDirPickerCtrl* gamePathPicker{};
}m_page1;
struct
{
wxCheckBox* fullscreenCheckbox;
wxCheckBox* separateCheckbox;
wxCheckBox* updateCheckbox;
}m_page2;
};
-741
View File
@@ -1,741 +0,0 @@
#include "helpers/wxHelpers.h"
#include "wxgui/wxgui.h"
#include "wxgui/GraphicPacksWindow2.h"
#include "wxgui/DownloadGraphicPacksWindow.h"
#include "wxgui/DownloadCustomGraphicPackWindow.h"
#include "Cafe/GraphicPack/GraphicPack2.h"
#include "config/CemuConfig.h"
#include "config/ActiveSettings.h"
#include "Cafe/HW/Latte/Core/LatteAsyncCommands.h"
#include "Cafe/CafeSystem.h"
#include "Cafe/TitleList/TitleList.h"
#include "wxHelper.h"
#if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD
#include "resource/embedded/resources.h"
#endif
// main.cpp
class wxGraphicPackData : public wxTreeItemData
{
public:
wxGraphicPackData(GraphicPackPtr pack)
: m_pack(std::move(pack)) { }
const GraphicPackPtr& GetGraphicPack() const { return m_pack; }
private:
GraphicPackPtr m_pack;
};
void GraphicPacksWindow2::FillGraphicPackList() const
{
wxWindowUpdateLocker lock(m_graphic_pack_tree);
m_graphic_pack_tree->DeleteAllItems();
auto graphic_packs = GraphicPack2::GetGraphicPacks();
const auto root = m_graphic_pack_tree->AddRoot("Root");
const bool has_filter = !m_filter.empty();
for(auto& p : graphic_packs)
{
if (!p->IsUniversal())
{
// filter graphic packs by given title id
if (m_filter_installed_games && !m_installed_games.empty())
{
bool found = false;
for (uint64 titleId : p->GetTitleIds())
{
if (std::find(m_installed_games.cbegin(), m_installed_games.cend(), titleId) != m_installed_games.cend())
{
found = true;
break;
}
}
if (!found)
continue;
}
// filter graphic packs by given title id
if(has_filter)
{
bool found = false;
if (boost::icontains(p->GetVirtualPath(), m_filter))
found = true;
else
{
for (uint64 titleId : p->GetTitleIds())
{
if (boost::icontains(fmt::format("{:x}", titleId), m_filter))
{
found = true;
break;
}
}
}
if (!found)
continue;
}
}
const auto& path = p->GetVirtualPath();
auto tokens = TokenizeView(path, '/');
auto node = root;
for(size_t i=0; i<tokens.size(); i++)
{
auto& token = tokens[i];
const auto parent_node = node;
if (i < (tokens.size() - 1))
{
node = FindTreeItem(parent_node, wxString::FromUTF8(token));
if (!node.IsOk())
{
node = m_graphic_pack_tree->AppendItem(parent_node, wxString::FromUTF8(token));
}
}
else
{
// last token
// if a node with same name already exists, add a number suffix
for (sint32 s = 0; s < 999; s++)
{
wxString nodeName = wxString::FromUTF8(token);
if (s > 0)
nodeName.append(formatWxString(" #{}", s + 1));
node = FindTreeItem(parent_node, nodeName);
if (!node.IsOk())
{
node = m_graphic_pack_tree->AppendItem(parent_node, nodeName);
break;
}
}
}
}
if(node.IsOk() && node != root)
{
m_graphic_pack_tree->SetItemData(node, new wxGraphicPackData(p));
bool canEnable = true;
if (p->GetVersion() == 3)
{
auto tmp_text = m_graphic_pack_tree->GetItemText(node);
m_graphic_pack_tree->SetItemText(node, tmp_text + " (may not be compatible with Vulkan)");
}
else if (p->GetVersion() != 3 && p->GetVersion() != 4 && p->GetVersion() != 5 && p->GetVersion() != 6 && p->GetVersion() != GraphicPack2::GFXPACK_VERSION_7 && p->GetVersion() != GraphicPack2::GFXPACK_VERSION_8)
{
auto tmp_text = m_graphic_pack_tree->GetItemText(node);
m_graphic_pack_tree->SetItemText(node, tmp_text + " (Unsupported version)");
m_graphic_pack_tree->SetItemTextColour(node, m_incompatible_colour);
canEnable = false;
}
else if (p->IsActivated())
m_graphic_pack_tree->SetItemTextColour(node, m_activated_colour);
m_graphic_pack_tree->MakeCheckable(node, p->IsEnabled());
if (!canEnable)
m_graphic_pack_tree->DisableCheckBox(node);
}
}
m_graphic_pack_tree->Sort(root, true);
if (!m_filter.empty())
{
size_t counter = 0;
ExpandChildren({ root }, counter);
}
}
void GraphicPacksWindow2::GetChildren(const wxTreeItemId& id, std::vector<wxTreeItemId>& children) const
{
wxTreeItemIdValue cookie;
wxTreeItemId child = m_graphic_pack_tree->GetFirstChild(id, cookie);
while (child.IsOk())
{
children.emplace_back(child);
child = m_graphic_pack_tree->GetNextChild(child, cookie);
}
}
void GraphicPacksWindow2::ExpandChildren(const std::vector<wxTreeItemId>& ids, size_t& counter) const
{
std::vector<wxTreeItemId> children;
for (const auto& id : ids)
GetChildren(id, children);
counter += children.size();
if (counter >= 30 || children.empty())
return;
for (const auto& id : ids)
{
if(id != m_graphic_pack_tree->GetRootItem() && m_graphic_pack_tree->HasChildren(id))
m_graphic_pack_tree->Expand(id);
}
ExpandChildren(children, counter);
}
void GraphicPacksWindow2::RefreshGraphicPacks()
{
GraphicPack2::ClearGraphicPacks();
GraphicPack2::LoadAll();
}
GraphicPacksWindow2::GraphicPacksWindow2(wxWindow* parent, uint64_t title_id_filter)
: wxDialog(parent, wxID_ANY, _("Graphic packs"), wxDefaultPosition, wxSize(1000,670), wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER),
m_installed_games(CafeTitleList::GetAllTitleIds())
{
if (title_id_filter != 0)
m_filter = fmt::format("{:x}", title_id_filter);
m_filter_installed_games = !m_installed_games.empty();
SetIcon(wxICON(X_BOX));
SetMinSize(wxSize(500, 400));
auto main_sizer = new wxBoxSizer(wxVERTICAL);
m_splitter_window = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_3D);
m_splitter_window->Bind(wxEVT_SIZE, &GraphicPacksWindow2::OnSizeChanged, this);
m_splitter_window->Bind(wxEVT_SPLITTER_SASH_POS_CHANGED, &GraphicPacksWindow2::SashPositionChanged, this);
// left side
auto left_panel = new wxPanel(m_splitter_window);
{
auto sizer = new wxBoxSizer(wxVERTICAL);
wxFlexGridSizer* filter_row = new wxFlexGridSizer(0, 3, 0, 0);
filter_row->AddGrowableCol(1);
filter_row->SetFlexibleDirection(wxBOTH);
filter_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
const auto text = new wxStaticText(left_panel, wxID_ANY, _("Filter"));
text->Wrap(-1);
filter_row->Add(text, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_filter_text = new wxTextCtrl(left_panel, wxID_ANY, wxString::FromUTF8(m_filter));
filter_row->Add(m_filter_text, 0, wxALL | wxEXPAND, 5);
m_filter_text->Bind(wxEVT_COMMAND_TEXT_UPDATED, &GraphicPacksWindow2::OnFilterUpdate, this);
m_installed_games_only = new wxCheckBox(left_panel, wxID_ANY, _("Installed games"));
m_installed_games_only->SetValue(m_filter_installed_games);
filter_row->Add(m_installed_games_only, 0, wxALL | wxEXPAND, 5);
m_installed_games_only->Bind(wxEVT_CHECKBOX, &GraphicPacksWindow2::OnInstalledGamesChanged, this);
if (m_installed_games.empty())
m_installed_games_only->Disable();
sizer->Add(filter_row, 0, wxEXPAND, 5);
m_graphic_pack_tree = new wxCheckTree(left_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_DEFAULT_STYLE | wxTR_HIDE_ROOT);
m_graphic_pack_tree->Bind(wxEVT_TREE_SEL_CHANGED, &GraphicPacksWindow2::OnTreeSelectionChanged, this);
m_graphic_pack_tree->Bind(wxEVT_CHECKTREE_CHOICE, &GraphicPacksWindow2::OnTreeChoiceChanged, this);
//m_graphic_pack_tree->SetMinSize(wxSize(600, 400));
sizer->Add(m_graphic_pack_tree, 1, wxEXPAND | wxALL, 5);
left_panel->SetSizerAndFit(sizer);
}
// right side
m_right_panel = new wxPanel(m_splitter_window, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxFULL_REPAINT_ON_RESIZE);
{
auto* sizer = new wxBoxSizer(wxVERTICAL);
{
m_gp_options = new wxScrolled<wxPanel>(m_right_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxVSCROLL);
m_gp_options->SetScrollRate(-1, 10);
auto* inner_sizer = new wxBoxSizer(wxVERTICAL);
{
auto* box = new wxStaticBox(m_gp_options, wxID_ANY, _("Graphic pack"));
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
m_graphic_pack_name = new wxStaticText(box, wxID_ANY, wxEmptyString);
box_sizer->Add(m_graphic_pack_name, 1, wxEXPAND | wxALL, 5);
inner_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
}
{
auto* box = new wxStaticBox(m_gp_options, wxID_ANY, _("Description"));
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
m_graphic_pack_description = new wxStaticText(box, wxID_ANY, wxEmptyString);
box_sizer->Add(m_graphic_pack_description, 1, wxEXPAND | wxALL, 5);
inner_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
}
m_preset_sizer = new wxBoxSizer(wxVERTICAL);
inner_sizer->Add(m_preset_sizer, 0, wxEXPAND, 0);
{
auto* box = new wxStaticBox(m_gp_options, wxID_ANY, _("Control"));
auto* box_sizer = new wxStaticBoxSizer(box, wxHORIZONTAL);
m_reload_shaders = new wxButton(box, wxID_ANY, _("Reload edited shaders"));
m_reload_shaders->Bind(wxEVT_BUTTON, &GraphicPacksWindow2::OnReloadShaders, this);
m_reload_shaders->Disable();
box_sizer->Add(m_reload_shaders, 0, wxEXPAND | wxALL, 5);
inner_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
}
inner_sizer->Add(new wxStaticText(m_gp_options, wxID_ANY, wxEmptyString), 1, wxALL | wxEXPAND, 5);
m_gp_options->SetSizerAndFit(inner_sizer);
m_gp_options->Hide();
sizer->Add(m_gp_options, 1, wxEXPAND | wxRESERVE_SPACE_EVEN_IF_HIDDEN);
}
sizer->Add(new wxStaticLine(m_right_panel, wxID_ANY), 0, wxLEFT|wxRIGHT | wxEXPAND, 3);
auto* row = new wxBoxSizer(wxHORIZONTAL);
m_update_graphicPacks = new wxButton(m_right_panel, wxID_ANY, _("Download latest community graphic packs"));
m_update_graphicPacks->Bind(wxEVT_BUTTON, &GraphicPacksWindow2::OnCheckForUpdates, this);
row->Add(m_update_graphicPacks, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
m_download_from_url = new wxHyperlinkCtrl(m_right_panel, wxID_ANY, _("Or download pack from URL..."), _(""));
m_download_from_url->Bind(wxEVT_HYPERLINK, &GraphicPacksWindow2::OnClickCustomDownload, this);
row->Add(m_download_from_url, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
sizer->Add(row, 0, wxALL | wxEXPAND, 5);
m_right_panel->SetSizerAndFit(sizer);
}
m_splitter_window->SetMinimumPaneSize(50);
m_splitter_window->SplitVertically(left_panel, m_right_panel, (sint32)(m_ratio * m_splitter_window->GetParent()->GetSize().GetWidth()));
main_sizer->Add(m_splitter_window, 1, wxEXPAND, 5);
m_info_bar = new wxInfoBar(this);
m_info_bar->SetShowHideEffects(wxSHOW_EFFECT_BLEND, wxSHOW_EFFECT_BLEND);
m_info_bar->SetEffectDuration(500);
main_sizer->Add(m_info_bar, 0, wxALL | wxEXPAND, 5);
SetSizer(main_sizer);
UpdateTitleRunning(CafeSystem::IsTitleRunning());
FillGraphicPackList();
}
void GraphicPacksWindow2::SaveStateToConfig()
{
auto& data = GetConfigHandle().data();
data.graphic_pack_entries.clear();
for (const auto& gp : GraphicPack2::GetGraphicPacks())
{
auto filename = _utf8ToPath(gp->GetNormalizedPathString());
if (gp->IsEnabled())
{
data.graphic_pack_entries.try_emplace(filename);
auto& it = data.graphic_pack_entries[filename];
// otherwise store all selected presets
for (const auto& preset : gp->GetActivePresets())
it.try_emplace(preset->category, preset->name);
}
else if(gp->IsDefaultEnabled())
{
// save that its disabled
data.graphic_pack_entries.try_emplace(filename);
auto& it = data.graphic_pack_entries[filename];
it.try_emplace("_disabled", "false");
}
}
GetConfigHandle().Save();
}
GraphicPacksWindow2::~GraphicPacksWindow2()
{
m_graphic_pack_tree->Unbind(wxEVT_CHECKTREE_CHOICE, &GraphicPacksWindow2::OnTreeSelectionChanged, this);
m_graphic_pack_tree->Unbind(wxEVT_CHECKTREE_CHOICE, &GraphicPacksWindow2::OnTreeChoiceChanged, this);
// m_active_preset->Unbind(wxEVT_CHOICE, &GraphicPacksWindow2::OnActivePresetChanged, this);
m_reload_shaders->Unbind(wxEVT_BUTTON, &GraphicPacksWindow2::OnReloadShaders, this);
SaveStateToConfig();
}
wxTreeItemId GraphicPacksWindow2::FindTreeItem(const wxTreeItemId& root, const wxString& text) const
{
wxTreeItemIdValue cookie;
for(auto item = m_graphic_pack_tree->GetFirstChild(root, cookie); item.IsOk(); item = m_graphic_pack_tree->GetNextSibling(item))
{
if (m_graphic_pack_tree->GetItemText(item) == text)
return item;
}
return {};
}
void GraphicPacksWindow2::LoadPresetSelections(const GraphicPackPtr& gp)
{
std::vector<std::string> order;
auto presets = gp->GetCategorizedPresets(order);
for(const auto& category : order)
{
const auto& entry = presets[category];
// test if any preset is visible and update its status
if (std::none_of(entry.cbegin(), entry.cend(), [gp](const auto& p)
{
return p->visible;
}))
{
continue;
}
wxString categoryWxStr = wxString::FromUTF8(category);
wxString label(category.empty() ? _("Active preset") : categoryWxStr);
auto* box = new wxStaticBox(m_preset_sizer->GetContainingWindow(), wxID_ANY, label);
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
auto* preset = new wxChoice(box, wxID_ANY);
preset->SetClientObject(new wxStringClientData(categoryWxStr));
preset->Bind(wxEVT_CHOICE, &GraphicPacksWindow2::OnActivePresetChanged, this);
std::optional<std::string> active_preset;
for (auto& pentry : entry)
{
if (!pentry->visible)
continue;
preset->AppendString(wxString::FromUTF8(pentry->name));
if (pentry->active)
active_preset = pentry->name;
}
if (active_preset)
preset->SetStringSelection(wxString::FromUTF8(active_preset.value()));
else if (preset->GetCount() > 0)
preset->SetSelection(0);
box_sizer->Add(preset, 1, wxEXPAND | wxALL, 5);
m_preset_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
}
}
void GraphicPacksWindow2::OnTreeSelectionChanged(wxTreeEvent& event)
{
wxWindowUpdateLocker lock(this);
bool item_deselected = m_graphic_pack_tree->GetSelection() == m_graphic_pack_tree->GetRootItem();
if (item_deselected)
return;
const auto selection = m_graphic_pack_tree->GetSelection();
if (selection.IsOk())
{
const auto data = dynamic_cast<wxGraphicPackData*>(m_graphic_pack_tree->GetItemData(selection));
if (data)
{
if(!m_gp_options->IsShown())
m_gp_options->Show();
const auto& gp = data->GetGraphicPack();
if (gp != m_shown_graphic_pack)
{
m_preset_sizer->Clear(true);
m_gp_name = gp->GetName();
m_graphic_pack_name->SetLabel(wxString::FromUTF8(m_gp_name));
if (gp->GetDescription().empty())
m_gp_description = _("This graphic pack has no description").utf8_string();
else
m_gp_description = gp->GetDescription();
m_graphic_pack_description->SetLabel(wxString::FromUTF8(m_gp_description));
LoadPresetSelections(gp);
m_reload_shaders->Enable(gp->HasShaders());
m_shown_graphic_pack = gp;
m_graphic_pack_name->Wrap(m_graphic_pack_name->GetParent()->GetClientSize().GetWidth() - 20);
m_graphic_pack_name->GetGrandParent()->Layout();
m_graphic_pack_description->Wrap(m_graphic_pack_description->GetParent()->GetClientSize().GetWidth() - 20);
m_graphic_pack_description->GetGrandParent()->Layout();
m_right_panel->FitInside();
m_right_panel->Layout();
}
return;
}
}
m_preset_sizer->Clear(true);
m_graphic_pack_name->SetLabel(wxEmptyString);
m_graphic_pack_description->SetLabel(wxEmptyString);
m_reload_shaders->Disable();
m_shown_graphic_pack.reset();
m_gp_options->Hide();
m_right_panel->FitInside();
m_right_panel->Layout();
}
void GraphicPacksWindow2::OnTreeChoiceChanged(wxTreeEvent& event)
{
auto item = event.GetItem();
if (!item.IsOk())
return;
const bool state = event.GetExtraLong() != 0;
const auto data = dynamic_cast<wxGraphicPackData*>(m_graphic_pack_tree->GetItemData(item));
if (!data)
return;
auto& graphic_pack = data->GetGraphicPack();
graphic_pack->SetEnabled(state);
bool requiresRestart = graphic_pack->RequiresRestart(true, false);
bool isRunning = CafeSystem::IsTitleRunning() && graphic_pack->ContainsTitleId(CafeSystem::GetForegroundTitleId());
if (isRunning)
{
if (state)
{
GraphicPack2::ActivateGraphicPack(graphic_pack);
if (!requiresRestart)
{
ReloadPack(graphic_pack);
m_graphic_pack_tree->SetItemTextColour(item, m_activated_colour);
}
}
else
{
if (!requiresRestart)
{
DeleteShadersFromRuntimeCache(graphic_pack);
m_graphic_pack_tree->SetItemTextColour(item, m_default_colour);
}
GraphicPack2::DeactivateGraphicPack(graphic_pack);
}
}
if (!m_info_bar->IsShown() && (isRunning && requiresRestart))
m_info_bar->ShowMessage(_("Restart of Cemu required for changes to take effect"));
// also change selection to activated gp
m_graphic_pack_tree->SelectItem(item);
}
// In some environments with GTK (e.g. a flatpak app with org.freedesktop.Platform 22.08 runtime),
// destroying the event source inside the handler crashes the app.
// As a workaround to that, the wxWindow that needs to be destroyed is hidden and then
// destroyed at a later time, outside the handler.
void GraphicPacksWindow2::ClearPresets()
{
size_t item_count = m_preset_sizer->GetItemCount();
std::vector<wxSizer*> sizers;
sizers.reserve(item_count);
for (size_t i = 0; i < item_count; i++)
sizers.push_back(m_preset_sizer->GetItem(i)->GetSizer());
for (auto&& sizer : sizers)
{
auto static_box_sizer = dynamic_cast<wxStaticBoxSizer*>(sizer);
if (static_box_sizer)
{
wxStaticBox* parent_window = static_box_sizer->GetStaticBox();
if (parent_window)
{
m_preset_sizer->Detach(sizer);
parent_window->Hide();
CallAfter([=]()
{
parent_window->DestroyChildren();
delete static_box_sizer;
});
}
}
}
}
void GraphicPacksWindow2::OnActivePresetChanged(wxCommandEvent& event)
{
if (!m_shown_graphic_pack)
return;
const auto obj = wxDynamicCast(event.GetEventObject(), wxChoice);
wxASSERT(obj);
const auto string_data = dynamic_cast<wxStringClientData*>(obj->GetClientObject());
wxASSERT(string_data);
const auto preset = obj->GetStringSelection().utf8_string();
if(m_shown_graphic_pack->SetActivePreset(string_data->GetData().utf8_string(), preset))
{
wxWindowUpdateLocker lock(this);
ClearPresets();
LoadPresetSelections(m_shown_graphic_pack);
//m_preset_sizer->GetContainingWindow()->Layout();
//m_right_panel->FitInside();
m_right_panel->FitInside();
m_right_panel->Layout();
}
if (!m_shown_graphic_pack->RequiresRestart(false, true))
ReloadPack(m_shown_graphic_pack);
else if (!m_info_bar->IsShown())
m_info_bar->ShowMessage(_("Restart of Cemu required for changes to take effect"));
}
void GraphicPacksWindow2::OnReloadShaders(wxCommandEvent& event)
{
if (m_shown_graphic_pack)
ReloadPack(m_shown_graphic_pack);
}
void GraphicPacksWindow2::OnClickCustomDownload(wxCommandEvent& event)
{
DownloadCustomGraphicPackWindow frame(this);
if (frame.ShowModal() == wxID_OK)
{
RefreshGraphicPacks();
FillGraphicPackList();
}
}
void GraphicPacksWindow2::OnCheckForUpdates(wxCommandEvent& event)
{
DownloadGraphicPacksWindow frame(this);
SaveStateToConfig();
const int updateResult = frame.ShowModal();
if (updateResult == wxID_OK)
{
if (!CafeSystem::IsTitleRunning())
{
// remember virtual paths of all the enabled packs
std::map<std::string, std::string> previouslyEnabledPacks;
for(auto& it : GraphicPack2::GetGraphicPacks())
{
if(it->IsEnabled())
previouslyEnabledPacks.emplace(it->GetNormalizedPathString(), it->GetVirtualPath());
}
// reload graphic packs
RefreshGraphicPacks();
FillGraphicPackList();
// remove packs which are still present
for(auto& it : GraphicPack2::GetGraphicPacks())
previouslyEnabledPacks.erase(it->GetNormalizedPathString());
if(!previouslyEnabledPacks.empty())
{
std::string lost_packs;
for(auto& it : previouslyEnabledPacks)
{
lost_packs.append(it.second);
lost_packs.push_back('\n');
}
wxString message = _("This update removed or renamed the following graphic packs:");
message << "\n \n" << wxString::FromUTF8(lost_packs) << " \n" << _("You may need to set them up again.");
wxMessageBox(message, _("Warning"), wxOK | wxCENTRE | wxICON_INFORMATION, this);
}
}
}
}
void GraphicPacksWindow2::OnSizeChanged(wxSizeEvent& event)
{
const auto obj = (wxSplitterWindow*)event.GetEventObject();
wxASSERT(obj);
const auto width = std::max(obj->GetMinimumPaneSize(), obj->GetParent()->GetClientSize().GetWidth());
obj->SetSashPosition((sint32)(m_ratio * width));
if (!m_gp_name.empty())
m_graphic_pack_name->SetLabel(wxString::FromUTF8(m_gp_name));
if (!m_gp_description.empty())
m_graphic_pack_description->SetLabel(wxString::FromUTF8(m_gp_description));
m_graphic_pack_name->Wrap(m_graphic_pack_name->GetParent()->GetClientSize().GetWidth() - 10);
m_graphic_pack_description->Wrap(m_graphic_pack_description->GetParent()->GetClientSize().GetWidth() - 10);
event.Skip();
}
void GraphicPacksWindow2::SashPositionChanged(wxEvent& event)
{
const auto obj = (wxSplitterWindow*)event.GetEventObject();
wxASSERT(obj);
const auto width = std::max(obj->GetMinimumPaneSize(), obj->GetParent()->GetClientSize().GetWidth());
m_ratio = (float)obj->GetSashPosition() / width;
event.Skip();
}
void GraphicPacksWindow2::OnFilterUpdate(wxEvent& event)
{
m_filter = m_filter_text->GetValue().utf8_string();
FillGraphicPackList();
event.Skip();
}
void GraphicPacksWindow2::OnInstalledGamesChanged(wxCommandEvent& event)
{
m_filter_installed_games = m_installed_games_only->GetValue();
FillGraphicPackList();
event.Skip();
}
void GraphicPacksWindow2::UpdateTitleRunning(bool running)
{
m_update_graphicPacks->Enable(!running);
m_download_from_url->Enable(!running);
if(running)
{
m_download_from_url->SetToolTip(_("Graphic packs cannot be updated while a game is running."));
m_update_graphicPacks->SetToolTip(_("Graphic packs cannot be updated while a game is running."));
}
else
{
m_update_graphicPacks->SetToolTip(nullptr);
m_download_from_url->SetToolTip(nullptr);
}
}
void GraphicPacksWindow2::ReloadPack(const GraphicPackPtr& graphic_pack) const
{
if (graphic_pack->HasShaders() || graphic_pack->HasPatches() || graphic_pack->HasCustomVSyncFrequency())
{
if (graphic_pack->Reload())
{
DeleteShadersFromRuntimeCache(graphic_pack);
}
}
}
void GraphicPacksWindow2::DeleteShadersFromRuntimeCache(const GraphicPackPtr& graphic_pack) const
{
for (const auto& shader : graphic_pack->GetCustomShaders())
{
LatteConst::ShaderType shaderType;
if (shader.type == GraphicPack2::GP_SHADER_TYPE::VERTEX)
shaderType = LatteConst::ShaderType::Vertex;
else if (shader.type == GraphicPack2::GP_SHADER_TYPE::GEOMETRY)
shaderType = LatteConst::ShaderType::Geometry;
else if (shader.type == GraphicPack2::GP_SHADER_TYPE::PIXEL)
shaderType = LatteConst::ShaderType::Pixel;
LatteAsyncCommands_queueDeleteShader(shader.shader_base_hash, shader.shader_aux_hash, shaderType);
}
}
-80
View File
@@ -1,80 +0,0 @@
#pragma once
#include <wx/dialog.h>
#include <wx/scrolwin.h>
#include <wx/infobar.h>
#include <wx/hyperlink.h>
#include "wxcomponents/checktree.h"
#include "Cafe/GraphicPack/GraphicPack2.h"
class wxSplitterWindow;
class wxPanel;
class wxButton;
class wxChoice;
class GraphicPacksWindow2 : public wxDialog
{
public:
GraphicPacksWindow2(wxWindow* parent, uint64_t title_id_filter);
~GraphicPacksWindow2();
static void RefreshGraphicPacks();
void UpdateTitleRunning(bool running);
private:
std::string m_filter;
bool m_filter_installed_games;
std::vector<uint64_t> m_installed_games;
void ClearPresets();
void FillGraphicPackList() const;
void GetChildren(const wxTreeItemId& id, std::vector<wxTreeItemId>& children) const;
void ExpandChildren(const std::vector<wxTreeItemId>& ids, size_t& counter) const;
wxSplitterWindow * m_splitter_window;
wxPanel* m_right_panel;
wxScrolled<wxPanel>* m_gp_options;
wxCheckTree * m_graphic_pack_tree;
wxTextCtrl* m_filter_text;
wxCheckBox* m_installed_games_only;
wxStaticText* m_graphic_pack_name, *m_graphic_pack_description;
wxBoxSizer* m_preset_sizer;
std::vector<wxChoice*> m_active_preset;
wxButton* m_reload_shaders;
wxHyperlinkCtrl* m_download_from_url;
wxButton* m_update_graphicPacks;
wxInfoBar* m_info_bar;
GraphicPackPtr m_shown_graphic_pack;
std::string m_gp_name, m_gp_description;
float m_ratio = 0.55f;
wxColour m_default_colour = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
wxColour m_activated_colour = wxSystemSettings::SelectLightDark(wxColour(0x00, 0xCC, 0x00), wxColour(0x42, 0xB3, 0x42));
wxColour m_incompatible_colour = wxSystemSettings::SelectLightDark(wxColour(0xCC, 0x00, 0x00), wxColour(0xDE, 0x49, 0x49));
wxTreeItemId FindTreeItem(const wxTreeItemId& root, const wxString& text) const;
void LoadPresetSelections(const GraphicPackPtr& gp);
void OnTreeSelectionChanged(wxTreeEvent& event);
void OnTreeChoiceChanged(wxTreeEvent& event);
void OnActivePresetChanged(wxCommandEvent& event);
void OnReloadShaders(wxCommandEvent& event);
void OnClickCustomDownload(wxCommandEvent& event);
void OnCheckForUpdates(wxCommandEvent& event);
void OnSizeChanged(wxSizeEvent& event);
void SashPositionChanged(wxEvent& event);
void OnFilterUpdate(wxEvent& event);
void OnInstalledGamesChanged(wxCommandEvent& event);
void SaveStateToConfig();
void ReloadPack(const GraphicPackPtr& graphic_pack) const;
void DeleteShadersFromRuntimeCache(const GraphicPackPtr& graphic_pack) const;
};
-85
View File
@@ -1,85 +0,0 @@
#include "wxgui/LoggingWindow.h"
#include "Cemu/Logging/CemuLogging.h"
#include "wxgui/helpers/wxLogEvent.h"
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/wupdlock.h>
wxDEFINE_EVENT(EVT_LOG, wxLogEvent);
LoggingWindow::LoggingWindow(wxFrame* parent)
: wxFrame(parent, wxID_ANY, _("Logging window"), wxDefaultPosition, wxSize(800, 600), wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL)
{
auto* sizer = new wxBoxSizer( wxVERTICAL );
{
auto filter_row = new wxBoxSizer( wxHORIZONTAL );
filter_row->Add(new wxStaticText( this, wxID_ANY, _("Filter")), 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
wxString choices[] = {"Unsupported APIs calls", "Coreinit Logging", "Coreinit File-Access", "Coreinit Thread-Synchronization", "Coreinit Memory", "Coreinit MP", "Coreinit Thread", "nn::nfp", "GX2", "Audio", "Input", "Socket", "Save", "H264", "Graphic pack patches", "Texture cache", "Texture readback", "OpenGL debug output", "Vulkan validation layer", "Metal debug output"};
m_filter = new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, std::size(choices), choices);
m_filter->Bind(wxEVT_COMBOBOX, &LoggingWindow::OnFilterChange, this);
m_filter->Bind(wxEVT_TEXT, &LoggingWindow::OnFilterChange, this);
filter_row->Add(m_filter, 1, wxALL, 5 );
m_filter_message = new wxCheckBox(this, wxID_ANY, _("Filter messages"));
m_filter_message->Bind(wxEVT_CHECKBOX, &LoggingWindow::OnFilterMessageChange, this);
filter_row->Add(m_filter_message, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
sizer->Add( filter_row, 0, wxEXPAND, 5 );
}
m_log_list = new wxLogCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxScrolledWindowStyle, true);
sizer->Add( m_log_list, 1, wxALL | wxEXPAND, 5 );
this->SetSizer( sizer );
this->Layout();
this->Bind(EVT_LOG, &LoggingWindow::OnLogMessage, this);
cemuLog_setCallbacks(this);
}
LoggingWindow::~LoggingWindow()
{
this->Unbind(EVT_LOG, &LoggingWindow::OnLogMessage, this);
cemuLog_clearCallbacks();
}
void LoggingWindow::Log(std::string_view filter, std::string_view message)
{
wxLogEvent event(std::string {filter}, std::string{ message });
OnLogMessage(event);
//const auto log_event = new wxLogEvent(filter, message);
//wxQueueEvent(s_instance, log_event);
}
void LoggingWindow::Log(std::string_view filter, std::wstring_view message)
{
wxLogEvent event(std::string {filter}, std::wstring{ message });
OnLogMessage(event);
//const auto log_event = new wxLogEvent(filter, message);
//wxQueueEvent(s_instance, log_event);
}
void LoggingWindow::OnLogMessage(wxLogEvent& event)
{
m_log_list->PushEntry(event.GetFilter(), event.GetMessage());
}
void LoggingWindow::OnFilterChange(wxCommandEvent& event)
{
m_log_list->SetActiveFilter(m_filter->GetValue().utf8_string());
event.Skip();
}
void LoggingWindow::OnFilterMessageChange(wxCommandEvent& event)
{
m_log_list->SetFilterMessage(m_filter_message->GetValue());
event.Skip();
}
-29
View File
@@ -1,29 +0,0 @@
#pragma once
#include "Cemu/Logging/CemuLogging.h"
#include <wx/frame.h>
#include <wx/listbox.h>
#include <wx/combobox.h>
#include "wxgui/components/wxLogCtrl.h"
class wxLogEvent;
class LoggingWindow : public wxFrame, public LoggingCallbacks
{
public:
LoggingWindow(wxFrame* parent);
~LoggingWindow();
void Log(std::string_view filter, std::string_view message) override;
void Log(std::string_view filter, std::wstring_view message) override;
private:
void OnLogMessage(wxLogEvent& event);
void OnFilterChange(wxCommandEvent& event);
void OnFilterMessageChange(wxCommandEvent& event);
wxComboBox* m_filter;
wxLogCtrl* m_log_list;
wxCheckBox* m_filter_message;
};
File diff suppressed because it is too large. Load diff
-248
View File
@@ -1,248 +0,0 @@
#pragma once
#include <wx/wx.h>
#include <wx/dataview.h>
#include <wx/infobar.h>
#include "wxgui/PadViewFrame.h"
#include "wxgui/MemorySearcherTool.h"
#include "config/XMLConfig.h"
#include "wxgui/LoggingWindow.h"
#include "wxgui/components/wxGameList.h"
#include <future>
#include "Cafe/HW/Espresso/Debugger/GDBStub.h"
#include "Cafe/CafeSystem.h"
class DebuggerWindow2;
struct GameEntry;
class DiscordPresence;
class TitleManager;
class GraphicPacksWindow2;
class EmulatedUSBDeviceFrame;
class wxLaunchGameEvent;
wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent);
wxDECLARE_EVENT(wxEVT_SET_WINDOW_TITLE, wxCommandEvent);
class wxLaunchGameEvent : public wxCommandEvent
{
public:
enum class INITIATED_BY
{
MENU, // via file menu
DRAG_AND_DROP,
GAME_LIST,
TITLE_MANAGER,
COMMAND_LINE, // -g parameter
};
wxLaunchGameEvent(fs::path path, INITIATED_BY initiatedBy)
: wxCommandEvent(wxEVT_LAUNCH_GAME), m_launchPath(path), m_initiatedBy(initiatedBy) {}
[[nodiscard]] fs::path GetPath() const { return m_launchPath; }
[[nodiscard]] INITIATED_BY GetInitiatedBy() const { return m_initiatedBy; }
wxEvent* Clone() const { return new wxLaunchGameEvent(*this); }
private:
fs::path m_launchPath;
INITIATED_BY m_initiatedBy;
};
class MainWindow : public wxFrame, public CafeSystem::SystemImplementation
{
friend class CemuApp;
public:
MainWindow();
~MainWindow();
void CreateGameListAndStatusBar();
void DestroyGameListAndStatusBar();
void UpdateSettingsAfterGameLaunch();
void RestoreSettingsAfterGameExited();
bool FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATED_BY initiatedBy);
[[nodiscard]] bool IsGameLaunched() const { return m_game_launched; }
void SetFullScreen(bool state);
void EndEmulation();
void SetMenuVisible(bool state);
void UpdateNFCMenu();
bool IsMenuHidden() const;
void TogglePadView();
void OpenPPCDebugger();
#if BOOST_OS_WINDOWS
WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) override;
#endif
void OpenSettings();
PadViewFrame* GetPadView() const { return m_padView; }
void OnSizeEvent(wxSizeEvent& event);
void OnDPIChangedEvent(wxDPIChangedEvent& event);
void OnMove(wxMoveEvent& event);
void OnDebuggerClose(wxCloseEvent& event);
void OnPadClose(wxCloseEvent& event);
void OnMemorySearcherClose(wxCloseEvent& event);
void OnMouseWheel(wxMouseEvent& event);
void OnClose(wxCloseEvent& event);
void OnFileMenu(wxCommandEvent& event);
void OnOpenFolder(wxCommandEvent& event);
void OnClearSpotPassCache(wxCommandEvent& event);
void OnLaunchFromFile(wxLaunchGameEvent& event);
void OnInstallUpdate(wxCommandEvent& event);
void OnFileExit(wxCommandEvent& event);
void OnNFCMenu(wxCommandEvent& event);
void OnOptionsInput(wxCommandEvent& event);
void OnAccountSelect(wxCommandEvent& event);
void OnConsoleLanguage(wxCommandEvent& event);
void OnHelpAbout(wxCommandEvent& event);
void OnHelpUpdate(wxCommandEvent& event);
void OnDebugSetting(wxCommandEvent& event);
void OnDebugLoggingToggleFlagGeneric(wxCommandEvent& event);
void OnPPCInfoToggle(wxCommandEvent& event);
void OnDebugDumpGeneric(wxCommandEvent& event);
void OnLoggingWindow(wxCommandEvent& event);
void OnGDBStubToggle(wxCommandEvent& event);
void OnDebugViewPPCThreads(wxCommandEvent& event);
void OnDebugViewPPCDebugger(wxCommandEvent& event);
void OnDebugViewAudioDebugger(wxCommandEvent& event);
void OnDebugViewTextureRelations(wxCommandEvent& event);
void OnMouseMove(wxMouseEvent& event);
void OnMouseLeft(wxMouseEvent& event);
void OnMouseRight(wxMouseEvent& event);
void OnGameListBeginUpdate(wxCommandEvent& event);
void OnGameListEndUpdate(wxCommandEvent& event);
void OnAccountListRefresh(wxCommandEvent& event);
void OnRequestGameListRefresh(wxCommandEvent& event);
void OnSetWindowTitle(wxCommandEvent& event);
void OnKeyUp(wxKeyEvent& event);
void OnKeyDown(wxKeyEvent& event);
void OnChar(wxKeyEvent& event);
void OnToolsInput(wxCommandEvent& event);
void OnGesturePan(wxPanGestureEvent& event);
void OnGameLoaded();
void AsyncSetTitle(std::string_view windowTitle);
void CreateCanvas();
void DestroyCanvas();
static void ShowCursor(bool state);
uintptr_t GetRenderCanvasHWND();
static void RequestGameListRefresh();
static void RequestLaunchGame(fs::path filePath, wxLaunchGameEvent::INITIATED_BY initiatedBy);
private:
bool FullscreenEnabled() const;
void RecreateMenu();
void UpdateChildWindowTitleRunningState();
static wxString GetInitialWindowTitle();
bool InstallUpdate(const fs::path& metaFilePath);
void OnTimer(wxTimerEvent& event);
// CafeSystem implementation
void CafeRecreateCanvas() override;
void CafePPCProcessExit() override;
void OnRequestRestart(wxCommandEvent& event);
void OnRequestRecreateCanvas(wxCommandEvent& event);
void OnRequestGameExit(wxCommandEvent& event);
wxRect GetDesktopRect();
MemorySearcherTool* m_toolWindow = nullptr;
TitleManager* m_title_manager = nullptr;
EmulatedUSBDeviceFrame* m_usb_devices = nullptr;
PadViewFrame* m_padView = nullptr;
GraphicPacksWindow2* m_graphic_pack_window = nullptr;
wxTimer* m_timer;
wxPoint m_mouse_position{};
std::chrono::steady_clock::time_point m_last_mouse_move_time;
wxSize m_restored_size;
wxPoint m_restored_position;
bool m_menu_visible = false;
bool m_game_launched = false;
#ifdef ENABLE_DISCORD_RPC
std::unique_ptr<DiscordPresence> m_discord;
#endif
std::string m_launched_game_name;
wxMenuItem* m_gdbstub_toggle{};
DebuggerWindow2* m_debugger_window = nullptr;
LoggingWindow* m_logging_window = nullptr;
std::future<bool> m_update_available;
wxMenuItem* m_check_update_menu{};
void LoadSettings();
void SaveSettings();
void OnGraphicWindowClose(wxCloseEvent& event);
void OnGraphicWindowOpen(wxTitleIdEvent& event);
// panels
wxPanel* m_main_panel{}, * m_game_panel{};
// rendering
wxWindow* m_render_canvas{};
// gamelist
wxGameList* m_game_list{};
wxInfoBar* m_info_bar{};
// menu
wxMenuBar* m_menuBar{};
// file
wxMenu* m_fileMenu{};
wxMenuItem* m_fileMenuSeparator0{};
wxMenuItem* m_fileMenuSeparator1{};
wxMenuItem* m_loadMenuItem{};
wxMenuItem* m_installUpdateMenuItem{};
wxMenuItem* m_exitMenuItem{};
// options
wxMenu* m_optionsAccountMenu{};
wxMenuItem* m_fullscreenMenuItem{};
wxMenuItem* m_padViewMenuItem{};
// tools
wxMenuItem* m_memorySearcherMenuItem{};
// cpu
wxMenu* m_cpuTimerSubmenu{};
// nfc
wxMenu* m_nfcMenu{};
wxMenuItem* m_nfcMenuSeparator0{};
// debug
wxMenu* m_debugMenu{};
wxMenu* m_loggingSubmenu{};
wxMenuItem* m_asyncCompile{};
wxDECLARE_EVENT_TABLE();
};
extern MainWindow* g_mainFrame;
-742
View File
@@ -1,742 +0,0 @@
#include "wxgui/wxgui.h"
#include "wxgui/MemorySearcherTool.h"
#include <vector>
#include <sstream>
#include <thread>
#include <wx/listctrl.h>
#include "config/ActiveSettings.h"
#include "wxgui/helpers/wxHelpers.h"
#include "Common/FileStream.h"
#include "util/IniParser/IniParser.h"
#include "util/helpers/StringHelpers.h"
#include "Cafe/CafeSystem.h"
enum
{
COMBOBOX_DATATYPE = wxID_HIGHEST + 1,
TEXT_VALUE,
BUTTON_START,
BUTTON_FILTER,
LIST_RESULTS,
LIST_ENTRYTABLE,
TIMER_REFRESH,
LIST_ENTRY_ADD,
LIST_ENTRY_REMOVE,
};
wxDEFINE_EVENT(wxEVT_SEARCH_FINISHED, wxCommandEvent);
wxBEGIN_EVENT_TABLE(MemorySearcherTool, wxFrame)
EVT_CLOSE(MemorySearcherTool::OnClose)
EVT_BUTTON(BUTTON_START, MemorySearcherTool::OnSearch)
EVT_BUTTON(BUTTON_FILTER, MemorySearcherTool::OnFilter)
EVT_TIMER(TIMER_REFRESH, MemorySearcherTool::OnTimerTick)
wxEND_EVENT_TABLE()
constexpr auto kMaxResultCount = 5000;
const wxString kDatatypeFloat = "float";
const wxString kDatatypeDouble = "double";
const wxString kDatatypeString = "string";
const wxString kDatatypeInt8 = "int8";
const wxString kDatatypeInt16 = "int16";
const wxString kDatatypeInt32 = "int32";
const wxString kDatatypeInt64 = "int64";
const wxString kDataTypeNames[] = {kDatatypeFloat,kDatatypeDouble,/*DATATYPE_STRING,*/kDatatypeInt8,kDatatypeInt16,kDatatypeInt32,kDatatypeInt64};
MemorySearcherTool::MemorySearcherTool(wxFrame* parent)
: wxFrame(parent, wxID_ANY, _("Memory Searcher"), wxDefaultPosition, wxSize(600, 540), wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL)
{
this->SetSizeHints(wxDefaultSize, wxDefaultSize);
this->wxTopLevelWindowBase::SetMinSize(wxSize(600, 540));
auto* sizer = new wxBoxSizer(wxVERTICAL);
auto* row1 = new wxFlexGridSizer(0, 4, 0, 0);
row1->AddGrowableCol(1);
m_cbDataType = new wxComboBox(this, COMBOBOX_DATATYPE, kDatatypeFloat, wxDefaultPosition, wxDefaultSize, std::size(kDataTypeNames), kDataTypeNames, wxCB_READONLY);
m_textValue = new wxTextCtrl(this, TEXT_VALUE);
m_buttonStart = new wxButton(this, BUTTON_START, _("Search"));
m_buttonFilter = new wxButton(this, BUTTON_FILTER, _("Filter"));
m_buttonFilter->Disable();
row1->Add(m_cbDataType, 0, wxALL, 5);
row1->Add(m_textValue, 0, wxALL | wxEXPAND, 5);
row1->Add(m_buttonStart, 0, wxALL, 5);
row1->Add(m_buttonFilter, 0, wxALL, 5);
sizer->Add(row1, 0, wxEXPAND, 5);
auto* row2 = new wxFlexGridSizer(0, 1, 0, 0);
row2->AddGrowableCol(0);
m_gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL);
m_gauge->SetValue(0);
m_gauge->Enable(false);
m_textEntryTable = new wxStaticText(this, wxID_ANY, _("Results"));
m_listResults = new wxListView(this, LIST_RESULTS, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SORT_ASCENDING);
m_listResults->Bind(wxEVT_LEFT_DCLICK, &MemorySearcherTool::OnResultListClick, this);
{
wxListItem col0;
col0.SetId(0);
col0.SetText(_("Address"));
col0.SetWidth(100);
m_listResults->InsertColumn(0, col0);
wxListItem col1;
col1.SetId(1);
col1.SetText(_("Value"));
col1.SetWidth(250);
m_listResults->InsertColumn(1, col1);
}
auto textEntryTable = new wxStaticText(this, wxID_ANY, _("Stored Entries"));
m_listEntryTable = new wxDataViewListCtrl(this, LIST_ENTRYTABLE, wxDefaultPosition, wxSize(420, 200), wxDV_HORIZ_RULES);
m_listEntryTable->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &MemorySearcherTool::OnEntryListRightClick, this);
m_listEntryTable->Bind(wxEVT_COMMAND_DATAVIEW_ITEM_EDITING_DONE, &MemorySearcherTool::OnItemEdited, this);
{
m_listEntryTable->AppendTextColumn(_("Description"), wxDATAVIEW_CELL_EDITABLE, 150, wxALIGN_LEFT, wxDATAVIEW_COL_SORTABLE);
m_listEntryTable->AppendTextColumn(_("Address"), wxDATAVIEW_CELL_INERT, 100, wxALIGN_LEFT, wxDATAVIEW_COL_SORTABLE);
m_listEntryTable->AppendTextColumn(_("Type"));
m_listEntryTable->AppendTextColumn(_("Value"), wxDATAVIEW_CELL_EDITABLE);
m_listEntryTable->AppendToggleColumn(_("Freeze"), wxDATAVIEW_CELL_ACTIVATABLE, 50, wxALIGN_LEFT, 0);
}
row2->AddGrowableRow(3);
row2->AddGrowableRow(5);
row2->Add(m_gauge, 0, wxALL | wxEXPAND, 5);
row2->Add(0, 10, 1, wxEXPAND, 5);
row2->Add(m_textEntryTable, 0, wxALL, 5);
row2->Add(m_listResults, 1, wxALL | wxEXPAND, 5);
row2->Add(textEntryTable, 0, wxALL, 5);
row2->Add(m_listEntryTable, 1, wxALL | wxEXPAND, 5);
sizer->Add(row2, 1, wxEXPAND, 5);
// load stored entries
Load();
this->Bind(wxEVT_SEARCH_FINISHED, &MemorySearcherTool::OnSearchFinished, this);
this->Bind(wxEVT_SET_GAUGE_VALUE, &MemorySearcherTool::OnUpdateGauge, this);
m_refresh_timer = new wxTimer(this, TIMER_REFRESH);
m_refresh_timer->Start(250);
this->SetSizer(sizer);
this->wxWindowBase::Layout();
this->Centre(wxBOTH);
}
MemorySearcherTool::~MemorySearcherTool()
{
m_refresh_timer->Stop();
m_running = false;
if (m_worker.joinable())
m_worker.join();
}
void MemorySearcherTool::OnTimerTick(wxTimerEvent& event)
{
RefreshResultList();
RefreshStashList();
}
void MemorySearcherTool::OnClose(wxCloseEvent& event)
{
Save();
event.Skip();
}
void MemorySearcherTool::OnSearchFinished(wxCommandEvent&)
{
FillResultList();
m_search_running = false;
m_buttonStart->Enable();
m_buttonFilter->Enable();
}
void MemorySearcherTool::OnUpdateGauge(wxSetGaugeValue& event)
{
auto* gauge = event.GetGauge();
const auto value = event.GetValue();
if (event.GetRange() != 0)
{
gauge->SetRange(event.GetRange());
gauge->SetValue(value);
return;
}
debug_printf("update gauge: %d + %d = %d (/%d)\n", gauge->GetValue(), value, gauge->GetValue() + value, gauge->GetRange());
gauge->SetValue(gauge->GetValue() + value);
}
void MemorySearcherTool::OnSearch(wxCommandEvent&)
{
if (m_clear_state)
{
Reset();
return;
}
if (m_search_running)
return;
if (m_textValue->IsEmpty())
return;
SetSearchDataType();
if (!VerifySearchValue())
{
wxMessageBox(_("Your entered value is not valid for the selected datatype."), _("Error"), wxICON_ERROR);
return;
}
m_buttonStart->Disable();
m_buttonFilter->Disable();
m_cbDataType->Disable();
m_buttonStart->SetLabelText(_("Clear"));
m_clear_state = true;
if (m_worker.joinable())
m_worker.join();
m_worker = std::thread([this]()
{
m_search_jobs.clear();
uint32 total_size = 0;
for (const auto& itr : memory_getMMURanges())
{
if (!m_running)
return;
if (!itr->isMapped())
continue;
void* ptr = itr->getPtr();
const uint32 size = itr->getSize();
total_size += (size / kGaugeStep);
m_search_jobs.emplace_back(std::async(std::launch::async, [this, ptr, size]() { return SearchValues(m_searchDataType, ptr, size); }));
}
wxQueueEvent(this, new wxSetGaugeValue(0, total_size, m_gauge));
ListType_t tmp;
for (auto& it : m_search_jobs)
{
const auto result = it.get();
tmp.insert(tmp.end(), result.cbegin(), result.cend());
}
std::unique_lock lock(m_mutex);
m_searchBuffer.swap(tmp);
lock.unlock();
wxQueueEvent(this, new wxCommandEvent(wxEVT_SEARCH_FINISHED));
});
}
void MemorySearcherTool::OnFilter(wxCommandEvent& event)
{
m_buttonStart->Disable();
m_buttonFilter->Disable();
if (m_worker.joinable())
m_worker.join();
m_worker = std::thread([this]()
{
const auto count = (uint32)m_searchBuffer.size();
wxQueueEvent(this, new wxSetGaugeValue(0, count, m_gauge));
auto tmp = FilterValues(m_searchDataType);
std::unique_lock lock(m_mutex);
m_searchBuffer.swap(tmp);
lock.unlock();
wxQueueEvent(this, new wxCommandEvent(wxEVT_SEARCH_FINISHED));
});
m_gauge->SetValue(0);
}
void MemorySearcherTool::Load()
{
const auto memorySearcherPath = ActiveSettings::GetUserDataPath("memorySearcher/{:016x}.ini", CafeSystem::GetForegroundTitleId());
auto memSearcherIniContents = FileStream::LoadIntoMemory(memorySearcherPath);
if (!memSearcherIniContents)
return;
IniParser iniParser(*memSearcherIniContents, _pathToUtf8(memorySearcherPath));
while (iniParser.NextSection())
{
auto option_description = iniParser.FindOption("description");
auto option_address = iniParser.FindOption("address");
auto option_type = iniParser.FindOption("type");
auto option_value = iniParser.FindOption("value");
if (!option_description || !option_address || !option_type || !option_value)
continue;
try
{
const auto addr = StringHelpers::ToInt64(*option_address);
if (!IsAddressValid(addr))
continue;
}
catch (const std::invalid_argument&)
{
continue;
}
bool found = false;
for (const auto& entry : kDataTypeNames)
{
if (boost::iequals(entry.ToStdString(), *option_type))
{
found = true;
break;
}
}
if (!found && !boost::iequals(kDatatypeString.ToStdString(), *option_type))
continue;
wxVector<wxVariant> data;
data.push_back(std::string(*option_description).c_str());
data.push_back(std::string(*option_address).c_str());
data.push_back(std::string(*option_type).c_str());
data.push_back(std::string(*option_value).c_str());
data.push_back(!option_value->empty());
m_listEntryTable->AppendItem(data);
}
}
void MemorySearcherTool::Save()
{
const auto memorySearcherPath = ActiveSettings::GetUserDataPath("memorySearcher/{:016x}.ini", CafeSystem::GetForegroundTitleId());
FileStream* fs = FileStream::createFile2(memorySearcherPath);
if (fs)
{
for (int i = 0; i < m_listEntryTable->GetItemCount(); ++i)
{
fs->writeLine("[Entry]");
std::string tmp = "description=" + m_listEntryTable->GetTextValue(i, 0).ToStdString();
fs->writeLine(tmp.c_str());
tmp = "address=" + m_listEntryTable->GetTextValue(i, 1).ToStdString();
fs->writeLine(tmp.c_str());
tmp = "type=" + m_listEntryTable->GetTextValue(i, 2).ToStdString();
fs->writeLine(tmp.c_str());
// only save value when FREEZE is active
if (m_listEntryTable->GetToggleValue(i, 4))
{
tmp = "value=" + m_listEntryTable->GetTextValue(i, 3).ToStdString();
}
else
{
tmp = "value=";
}
fs->writeLine(tmp.c_str());
fs->writeLine("");
}
delete fs;
}
}
bool MemorySearcherTool::IsAddressValid(uint32 addr)
{
for (const auto& itr : memory_getMMURanges())
{
if (!itr->isMapped())
continue;
void* ptr = itr->getPtr();
const uint32 size = itr->getSize();
MEMPTR start((uint8*)ptr);
MEMPTR end((uint8*)ptr + size);
if (start.GetMPTR() <= addr && addr < end.GetMPTR())
return true;
}
return false;
}
void MemorySearcherTool::OnEntryListRightClick(wxDataViewEvent& event)
{
//void *data = reinterpret_cast<void *>(event.GetItem().GetData());
wxMenu mnu;
//mnu.SetClientData(data);
mnu.Append(LIST_ENTRY_ADD, _("&Add new entry"))->Enable(false);
mnu.Append(LIST_ENTRY_REMOVE, _("&Remove entry"));
mnu.Bind(wxEVT_COMMAND_MENU_SELECTED, &MemorySearcherTool::OnPopupClick, this);
PopupMenu(&mnu);
}
void MemorySearcherTool::OnResultListClick(wxMouseEvent& event)
{
for (long selectedIndex = m_listResults->GetFirstSelected(); selectedIndex != wxNOT_FOUND; selectedIndex = m_listResults->GetNextSelected(selectedIndex))
{
long address = m_listResults->GetItemData(selectedIndex);
auto currValue = m_listResults->GetItemText(selectedIndex, 1);
wxString addressString = wxString::Format("0x%08lx", address);
// description, address, type, value, freeze
wxVector<wxVariant> data;
data.push_back("");
data.push_back(addressString);
data.push_back(m_cbDataType->GetValue());
data.push_back(currValue);
data.push_back(false);
m_listEntryTable->AppendItem(data);
}
}
void MemorySearcherTool::Reset()
{
m_searchBuffer.clear();
m_buttonStart->SetLabelText(_("Search"));
m_textEntryTable->SetLabelText(_("Results"));
m_buttonFilter->Disable();
m_cbDataType->Enable();
m_textValue->SetValue("");
m_listResults->DeleteAllItems();
m_clear_state = false;
}
bool MemorySearcherTool::VerifySearchValue() const
{
const auto inputString = m_textValue->GetValue().ToStdString();
switch (m_searchDataType)
{
case SearchDataType_String:
return true;
case SearchDataType_Float:
{
float value;
return ConvertStringToType(inputString, value);
}
case SearchDataType_Double:
{
double value;
return ConvertStringToType(inputString, value);
}
case SearchDataType_Int8:
{
sint8 value;
return ConvertStringToType(inputString, value);
}
case SearchDataType_Int16:
{
sint16 value;
return ConvertStringToType(inputString, value);
}
case SearchDataType_Int32:
{
sint32 value;
return ConvertStringToType(inputString, value);
}
case SearchDataType_Int64:
{
sint64 value;
return ConvertStringToType(inputString, value);
}
default:
return false;
}
}
void MemorySearcherTool::FillResultList()
{
auto text = formatWxString(_("Results ({0})"), m_searchBuffer.size());
m_textEntryTable->SetLabelText(text);
m_listResults->DeleteAllItems();
if (m_searchBuffer.empty() || m_searchBuffer.size() > kMaxResultCount)
return;
for (const auto& address : m_searchBuffer)
{
const auto index = m_listResults->InsertItem(0, fmt::format("{:#08x}", address.GetMPTR()));
m_listResults->SetItemData(index, address.GetMPTR());
m_listResults->SetItem(index, 1, m_textValue->GetValue());
}
}
void MemorySearcherTool::RefreshResultList()
{
std::unique_lock lock(m_mutex);
if (m_searchBuffer.empty() || m_searchBuffer.size() > kMaxResultCount)
return;
for (int i = 0; i < m_listResults->GetItemCount(); ++i)
{
const auto addr = m_listResults->GetItemData(i);
switch (m_searchDataType)
{
case SearchDataType_String:
{
// TODO Peter
break;
}
case SearchDataType_Float:
{
const auto value = memory_read<float>(addr);
m_listResults->SetItem(i, 1, fmt::format("{}", value));
break;
}
case SearchDataType_Double:
{
const auto value = memory_read<double>(addr);
m_listResults->SetItem(i, 1, fmt::format("{}", value));
break;
}
case SearchDataType_Int8:
{
const auto value = memory_read<sint8>(addr);
m_listResults->SetItem(i, 1, fmt::format("{}", value));
break;
}
case SearchDataType_Int16:
{
const auto value = memory_read<sint16>(addr);
m_listResults->SetItem(i, 1, fmt::format("{}", value));
break;
}
case SearchDataType_Int32:
{
const auto value = memory_read<sint32>(addr);
m_listResults->SetItem(i, 1, fmt::format("{}", value));
break;
}
case SearchDataType_Int64:
{
const auto value = memory_read<sint64>(addr);
m_listResults->SetItem(i, 1, fmt::format("{}", value));
break;
}
}
}
}
void MemorySearcherTool::RefreshStashList()
{
for (int i = 0; i < m_listEntryTable->GetItemCount(); ++i)
{
auto freeze = m_listEntryTable->GetToggleValue(i, 4);
auto addressText = m_listEntryTable->GetTextValue(i, 1).ToStdString();
auto type = m_listEntryTable->GetTextValue(i, 2);
auto address = std::stol(addressText, nullptr, 16);
// if freeze is activated, set the value instead of refreshing it
if (freeze)
{
wxVariant var;
m_listEntryTable->GetValue(var, i, 3);
if (type == kDatatypeFloat)
{
auto value = (float)var.GetDouble();
memory_writeFloat(address, value);
}
else if (type == kDatatypeDouble)
{
auto value = var.GetDouble();
memory_writeDouble(address, value);
}
else if (type == kDatatypeInt8)
{
auto value = var.GetInteger();
memory_writeU8(address, value);
}
else if (type == kDatatypeInt16)
{
auto value = var.GetInteger();
memory_writeU16(address, value);
}
else if (type == kDatatypeInt32)
{
auto value = var.GetInteger();
memory_writeU32(address, value);
}
else if (type == kDatatypeInt64)
{
auto valueText = var.GetString().ToStdString();
auto value = std::stoull(valueText);
memory_writeU64(address, value);
}
else if (type == kDatatypeString)
{
auto valueText = var.GetString().ToStdString();
for (int i = 0; i < valueText.size(); ++i)
{
memory_writeU8(address + i, valueText[i]);
}
memory_writeU8(address + valueText.size(), 0x00);
}
}
else
{
if (type == kDatatypeFloat)
{
auto value = memory_readFloat(address);
m_listEntryTable->SetValue(fmt::format("{}", value), i, 3);
}
else if (type == kDatatypeDouble)
{
auto value = memory_readDouble(address);
m_listEntryTable->SetValue(fmt::format("{}", value), i, 3);
}
else if (type == kDatatypeInt8)
{
auto value = (sint8)memory_readU8(address);
m_listEntryTable->SetValue(fmt::format("{}", (int)value), i, 3);
}
else if (type == kDatatypeInt16)
{
auto value = (sint16)memory_readU16(address);
m_listEntryTable->SetValue(fmt::format("{}", value), i, 3);
}
else if (type == kDatatypeInt32)
{
auto value = (sint32)memory_readU32(address);
m_listEntryTable->SetValue(fmt::format("{}", value), i, 3);
}
else if (type == kDatatypeInt64)
{
auto value = (sint64)memory_readU64(address);
m_listEntryTable->SetValue(fmt::format("{}", value), i, 3);
}
else if (type == kDatatypeString)
{
// TODO Peter
}
}
}
}
void MemorySearcherTool::SetSearchDataType()
{
const auto type = m_cbDataType->GetStringSelection();
if (type == kDatatypeFloat)
m_searchDataType = SearchDataType_Float;
else if (type == kDatatypeDouble)
m_searchDataType = SearchDataType_Double;
else if (type == kDatatypeInt8)
m_searchDataType = SearchDataType_Int8;
else if (type == kDatatypeInt16)
m_searchDataType = SearchDataType_Int16;
else if (type == kDatatypeInt32)
m_searchDataType = SearchDataType_Int32;
else if (type == kDatatypeInt64)
m_searchDataType = SearchDataType_Int64;
else if (type == kDatatypeString)
m_searchDataType = SearchDataType_String;
else
m_searchDataType = SearchDataType_None;
}
template<>
bool MemorySearcherTool::ConvertStringToType<signed char>(const std::string& inValue, sint8& outValue) const
{
sint16 tmp;
std::istringstream iss(inValue);
iss >> std::noskipws >> tmp;
if (iss && iss.eof())
{
if (SCHAR_MIN <= tmp && tmp <= SCHAR_MAX)
{
outValue = tmp;
return true;
}
}
return false;
}
void MemorySearcherTool::OnPopupClick(wxCommandEvent& event)
{
if (event.GetId() == LIST_ENTRY_REMOVE)
{
const int row = m_listEntryTable->GetSelectedRow();
if (row == wxNOT_FOUND)
return;
m_listEntryTable->DeleteItem(row);
}
}
void MemorySearcherTool::OnItemEdited(wxDataViewEvent& event)
{
auto column = event.GetColumn();
// Edit description
if (column == 0) { }
// Edit value
else if (column == 3)
{
auto row = m_listEntryTable->GetSelectedRow();
if (row == wxNOT_FOUND)
return;
auto addressText = m_listEntryTable->GetTextValue(row, 1).ToStdString();
uint32 address = std::stoul(addressText, nullptr, 16);
auto type = m_listEntryTable->GetTextValue(row, 2);
if (type == kDatatypeFloat)
{
auto value = (float)event.GetValue().GetDouble();
memory_writeFloat(address, value);
}
else if (type == kDatatypeDouble)
{
auto value = event.GetValue().GetDouble();
memory_writeDouble(address, value);
}
else if (type == kDatatypeInt8)
{
auto value = event.GetValue().GetInteger();
memory_writeU8(address, value);
}
else if (type == kDatatypeInt16)
{
auto value = event.GetValue().GetInteger();
memory_writeU16(address, value);
}
else if (type == kDatatypeInt32)
{
auto value = event.GetValue().GetInteger();
memory_writeU32(address, value);
}
else if (type == kDatatypeInt64)
{
auto valueText = event.GetValue().GetString().ToStdString();
auto value = std::stoull(valueText);
memory_writeU64(address, value);
}
else if (type == kDatatypeString)
{
auto valueText = event.GetValue().GetString().ToStdString();
for (int i = 0; i < valueText.size(); ++i)
{
memory_writeU8(address + i, valueText[i]);
}
memory_writeU8(address + valueText.size(), 0x00);
}
}
}
-264
View File
@@ -1,264 +0,0 @@
#pragma once
#include <mutex>
#include <thread>
#include <atomic>
#include <future>
#include <wx/listctrl.h>
#include <wx/frame.h>
#include "Cafe/HW/MMU/MMU.h"
#include "util/helpers/helpers.h"
#include "wxgui/helpers/wxCustomEvents.h"
class wxComboBox;
class wxDataViewEvent;
class wxDataViewListCtrl;
class wxStaticText;
class wxTimer;
class wxTimerEvent;
enum SearchDataType
{
SearchDataType_None,
SearchDataType_String,
SearchDataType_Float,
SearchDataType_Double,
SearchDataType_Int8,
SearchDataType_Int16,
SearchDataType_Int32,
SearchDataType_Int64,
};
struct TableEntry_t
{
uint32 address;
const char description[32];
SearchDataType type;
bool freeze;
uint16 dataLen;
uint8* data;
};
class MemorySearcherTool : public wxFrame
{
public:
MemorySearcherTool(wxFrame* parent);
virtual ~MemorySearcherTool();
void OnTimerTick(wxTimerEvent& event);
void OnClose(wxCloseEvent&);
void OnSearch(wxCommandEvent&);
void OnSearchFinished(wxCommandEvent& event);
void OnUpdateGauge(wxSetGaugeValue& event);
void OnFilter(wxCommandEvent& event);
void OnResultListClick(wxMouseEvent& event);
void OnEntryListRightClick(wxDataViewEvent& event);
//void OnEntryListRightClick2(wxContextMenuEvent& event);
void OnPopupClick(wxCommandEvent& event);
void OnItemEdited(wxDataViewEvent& event);
private:
void Reset();
bool VerifySearchValue() const;
void FillResultList();
void RefreshResultList();
void RefreshStashList();
void SetSearchDataType();
void Load();
void Save();
static bool IsAddressValid(uint32 addr);
template<typename T>
bool ConvertStringToType(const std::string& inValue, T& outValue) const
{
std::istringstream iss(inValue);
iss >> std::noskipws >> outValue;
return iss && iss.eof();
}
using ListType_t = std::vector<MEMPTR<void>>;
ListType_t SearchValues(SearchDataType type, void* ptr, uint32 size)
{
/*if (type == SearchDataType_String)
return SearchValues((const char* )ptr, size);
else */if (type == SearchDataType_Float)
return SearchValues((float*)ptr, size);
else if (type == SearchDataType_Double)
return SearchValues((double*)ptr, size);
else if (type == SearchDataType_Int8)
return SearchValues((sint8*)ptr, size);
else if (type == SearchDataType_Int16)
return SearchValues((sint16*)ptr, size);
else if (type == SearchDataType_Int32)
return SearchValues((sint32*)ptr, size);
else if (type == SearchDataType_Int64)
return SearchValues((sint64*)ptr, size);
return {};
}
ListType_t FilterValues(SearchDataType type)
{
/*if (type == SearchDataType_String)
return FilterValues<const char*>();
else */if (type == SearchDataType_Float)
return FilterValues<float>();
else if (type == SearchDataType_Double)
return FilterValues<double>();
else if (type == SearchDataType_Int8)
return FilterValues<sint8>();
else if (type == SearchDataType_Int16)
return FilterValues<sint16>();
else if (type == SearchDataType_Int32)
return FilterValues<sint32>();
else if (type == SearchDataType_Int64)
return FilterValues<sint64>();
return {};
}
constexpr static int kGaugeStep = 0x10000;
template <typename T>
ListType_t SearchValues(T* ptr, uint32 size)
{
const auto search_value = ConvertString<T>(m_textValue->GetValue().ToStdString());
const auto* end = (T*)((uint8*)ptr + size - sizeof(T));
uint32 counter = 0;
std::vector<MEMPTR<void>> result;
for (; ptr < end; ++ptr)
{
if (!m_running)
return result;
const auto tmp = (betype<T>*)ptr;
if (equals(search_value, tmp->value()))
result.emplace_back(ptr);
counter += sizeof(T);
if(counter >= kGaugeStep)
{
wxQueueEvent(this, new wxSetGaugeValue(1, m_gauge));
counter -= kGaugeStep;
}
}
return result;
}
template <typename T>
ListType_t FilterValues()
{
const auto search_value = ConvertString<T>(m_textValue->GetValue().ToStdString());
ListType_t newSearchBuffer;
newSearchBuffer.reserve(m_searchBuffer.size());
for (const auto& it : m_searchBuffer)
{
if (!m_running)
return newSearchBuffer;
const auto tmp = (betype<T>*)it.GetPtr();
if (equals(search_value , tmp->value()))
newSearchBuffer.emplace_back(it);
wxQueueEvent(this, new wxSetGaugeValue(1, m_gauge));
}
return newSearchBuffer;
}
wxDECLARE_EVENT_TABLE();
SearchDataType m_searchDataType = SearchDataType_None;
wxComboBox* m_cbDataType;
wxTextCtrl* m_textValue;
wxButton *m_buttonStart, *m_buttonFilter;
wxListView* m_listResults;
wxDataViewListCtrl* m_listEntryTable;
wxStaticText* m_textEntryTable;
wxGauge* m_gauge;
wxTimer* m_refresh_timer;
ListType_t m_searchBuffer;
std::vector<TableEntry_t> m_tableEntries;
std::mutex m_mutex;
std::thread m_worker;
std::atomic_bool m_running = true;
std::atomic_bool m_search_running = false;
std::vector<std::future<ListType_t>> m_search_jobs;
bool m_clear_state = false;
};
template<>
bool MemorySearcherTool::ConvertStringToType(const std::string& inValue, sint8& outValue) const;
//
//template <typename T>
//void MemorySearcherTool::FilterValues()
//{
// auto s1 = m_textValue->GetValue();
// auto s2 = s1.c_str();
// auto stringValue = s2.AsChar();
//
// T filterValue;
// ConvertStringToType(stringValue, filterValue);
//
// std::vector<uint32> newSearchBuffer;
// newSearchBuffer.reserve(m_searchBuffer.size());
//
// uint32 gaugeValue = 0;
// uint32 count = m_searchBuffer.size();
// uint32 counter = 0;
//
// for (auto it = m_searchBuffer.begin(); it != m_searchBuffer.end(); ++it)
// {
// if (m_running)
// return;
//
// T value = memory_read<T>(*it);
// if constexpr (std::is_same<T, float>::value)
// {
// //float value1 = filterValue * 0.95f;
// float diff = filterValue - value;
// diff = fabs(diff);
// if(diff < value*0.05f)
// {
// newSearchBuffer.push_back(*it);
// }
//
// }
// else
// {
// if (value == filterValue)
// {
// newSearchBuffer.push_back(*it);
// }
// }
//
// ++counter;
// uint32 newValue = counter * 100 / count;
// if (newValue > gaugeValue + GAUGE_STEP_SIZE)
// {
// gaugeValue = newValue;
// m_gaugeValue = newValue;
// }
// }
//
// m_mutex.lock();
// m_searchBuffer = newSearchBuffer;
// m_gaugeValue = 100;
// m_mutex.unlock();
// m_isSearchFinished = true;
//}
//
-242
View File
@@ -1,242 +0,0 @@
#include "interface/WindowSystem.h"
#include "wxgui/wxgui.h"
#include "wxgui/PadViewFrame.h"
#include <wx/display.h>
#include "config/ActiveSettings.h"
#include "Cafe/OS/libs/swkbd/swkbd.h"
#ifdef ENABLE_OPENGL
#include "wxgui/canvas/OpenGLCanvas.h"
#endif
#ifdef ENABLE_VULKAN
#include "wxgui/canvas/VulkanCanvas.h"
#endif
#ifdef ENABLE_METAL
#include "wxgui/canvas/MetalCanvas.h"
#endif
#include "config/CemuConfig.h"
#include "wxgui/MainWindow.h"
#include "wxgui/helpers/wxHelpers.h"
#include "input/InputManager.h"
#if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD
#include "resource/embedded/resources.h"
#endif
#include "wxHelper.h"
extern WindowSystem::WindowInfo g_window_info;
#define PAD_MIN_WIDTH 320
#define PAD_MIN_HEIGHT 180
PadViewFrame::PadViewFrame(wxFrame* parent)
: wxFrame(nullptr, wxID_ANY, _("GamePad View"), wxDefaultPosition, wxDefaultSize, wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxCLOSE_BOX | wxWANTS_CHARS)
{
g_window_info.window_pad = initHandleContextFromWxWidgetsWindow(this);
SetIcon(wxICON(M_WND_ICON128));
wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES);
SetMinClientSize({ PAD_MIN_WIDTH, PAD_MIN_HEIGHT });
SetPosition({ g_window_info.restored_pad_x, g_window_info.restored_pad_y });
if (g_window_info.restored_pad_width >= PAD_MIN_WIDTH && g_window_info.restored_pad_height >= PAD_MIN_HEIGHT)
SetClientSize({ g_window_info.restored_pad_width, g_window_info.restored_pad_height });
else
SetClientSize(wxSize(854, 480));
if (g_window_info.pad_maximized)
Maximize();
Bind(wxEVT_SIZE, &PadViewFrame::OnSizeEvent, this);
Bind(wxEVT_DPI_CHANGED, &PadViewFrame::OnDPIChangedEvent, this);
Bind(wxEVT_MOVE, &PadViewFrame::OnMoveEvent, this);
Bind(wxEVT_MOTION, &PadViewFrame::OnMouseMove, this);
Bind(wxEVT_SET_WINDOW_TITLE, &PadViewFrame::OnSetWindowTitle, this);
g_window_info.pad_open = true;
}
PadViewFrame::~PadViewFrame()
{
g_window_info.pad_open = false;
}
bool PadViewFrame::Initialize()
{
const wxSize client_size = GetClientSize();
g_window_info.pad_width = client_size.GetWidth();
g_window_info.pad_height = client_size.GetHeight();
g_window_info.phys_pad_width = ToPhys(client_size.GetWidth());
g_window_info.phys_pad_height = ToPhys(client_size.GetHeight());
return true;
}
void PadViewFrame::InitializeRenderCanvas()
{
auto sizer = new wxBoxSizer(wxVERTICAL);
{
#ifdef ENABLE_VULKAN
if (ActiveSettings::GetGraphicsAPI() == kVulkan)
m_render_canvas = new VulkanCanvas(this, wxSize(854, 480), false);
#endif
#ifdef ENABLE_OPENGL
if (ActiveSettings::GetGraphicsAPI() == kOpenGL)
m_render_canvas = GLCanvas_Create(this, wxSize(854, 480), false);
#endif
#ifdef ENABLE_METAL
if (ActiveSettings::GetGraphicsAPI() == kMetal)
m_render_canvas = new MetalCanvas(this, wxSize(854, 480), false);
#endif
sizer->Add(m_render_canvas, 1, wxEXPAND, 0, nullptr);
}
cemu_assert(m_render_canvas != nullptr);
SetSizer(sizer);
Layout();
m_render_canvas->Bind(wxEVT_KEY_UP, &PadViewFrame::OnKeyUp, this);
m_render_canvas->Bind(wxEVT_CHAR, &PadViewFrame::OnChar, this);
m_render_canvas->Bind(wxEVT_MOTION, &PadViewFrame::OnMouseMove, this);
m_render_canvas->Bind(wxEVT_LEFT_DOWN, &PadViewFrame::OnMouseLeft, this);
m_render_canvas->Bind(wxEVT_LEFT_UP, &PadViewFrame::OnMouseLeft, this);
m_render_canvas->Bind(wxEVT_RIGHT_DOWN, &PadViewFrame::OnMouseRight, this);
m_render_canvas->Bind(wxEVT_RIGHT_UP, &PadViewFrame::OnMouseRight, this);
m_render_canvas->Bind(wxEVT_GESTURE_PAN, &PadViewFrame::OnGesturePan, this);
m_render_canvas->SetFocus();
SendSizeEvent();
}
void PadViewFrame::DestroyCanvas()
{
if(!m_render_canvas)
return;
m_render_canvas->Destroy();
m_render_canvas = nullptr;
}
void PadViewFrame::OnSizeEvent(wxSizeEvent& event)
{
if (!IsMaximized() && !IsFullScreen())
{
g_window_info.restored_pad_width = GetSize().x;
g_window_info.restored_pad_height = GetSize().y;
}
g_window_info.pad_maximized = IsMaximized() && !IsFullScreen();
const wxSize client_size = GetClientSize();
g_window_info.pad_width = client_size.GetWidth();
g_window_info.pad_height = client_size.GetHeight();
g_window_info.phys_pad_width = ToPhys(client_size.GetWidth());
g_window_info.phys_pad_height = ToPhys(client_size.GetHeight());
g_window_info.pad_dpi_scale = GetDPIScaleFactor();
event.Skip();
}
void PadViewFrame::OnDPIChangedEvent(wxDPIChangedEvent& event)
{
event.Skip();
const wxSize client_size = GetClientSize();
g_window_info.pad_width = client_size.GetWidth();
g_window_info.pad_height = client_size.GetHeight();
g_window_info.phys_pad_width = ToPhys(client_size.GetWidth());
g_window_info.phys_pad_height = ToPhys(client_size.GetHeight());
g_window_info.pad_dpi_scale = GetDPIScaleFactor();
}
void PadViewFrame::OnMoveEvent(wxMoveEvent& event)
{
if (!IsMaximized() && !IsFullScreen())
{
g_window_info.restored_pad_x = GetPosition().x;
g_window_info.restored_pad_y = GetPosition().y;
}
}
void PadViewFrame::OnKeyUp(wxKeyEvent& event)
{
event.Skip();
if (swkbd_hasKeyboardInputHook())
return;
const auto code = event.GetKeyCode();
if (code == WXK_ESCAPE)
ShowFullScreen(false);
else if (code == WXK_RETURN && event.AltDown() || code == WXK_F11)
ShowFullScreen(!IsFullScreen());
}
void PadViewFrame::OnGesturePan(wxPanGestureEvent& event)
{
auto& instance = InputManager::instance();
std::scoped_lock lock(instance.m_pad_touch.m_mutex);
auto physPos = ToPhys(event.GetPosition());
instance.m_pad_touch.position = { physPos.x, physPos.y };
instance.m_pad_touch.left_down = event.IsGestureStart() || !event.IsGestureEnd();
if (event.IsGestureStart() || !event.IsGestureEnd())
instance.m_pad_touch.left_down_toggle = true;
}
void PadViewFrame::OnChar(wxKeyEvent& event)
{
if (swkbd_hasKeyboardInputHook())
swkbd_keyInput(event.GetUnicodeKey());
event.Skip();
}
void PadViewFrame::OnMouseMove(wxMouseEvent& event)
{
auto& instance = InputManager::instance();
std::scoped_lock lock(instance.m_pad_touch.m_mutex);
auto physPos = ToPhys(event.GetPosition());
instance.m_pad_mouse.position = { physPos.x, physPos.y };
event.Skip();
}
void PadViewFrame::OnMouseLeft(wxMouseEvent& event)
{
auto& instance = InputManager::instance();
std::scoped_lock lock(instance.m_pad_mouse.m_mutex);
instance.m_pad_mouse.left_down = event.ButtonDown(wxMOUSE_BTN_LEFT);
auto physPos = ToPhys(event.GetPosition());
instance.m_pad_mouse.position = { physPos.x, physPos.y };
if (event.ButtonDown(wxMOUSE_BTN_LEFT))
instance.m_pad_mouse.left_down_toggle = true;
}
void PadViewFrame::OnMouseRight(wxMouseEvent& event)
{
auto& instance = InputManager::instance();
std::scoped_lock lock(instance.m_pad_mouse.m_mutex);
instance.m_pad_mouse.right_down = event.ButtonDown(wxMOUSE_BTN_LEFT);
auto physPos = ToPhys(event.GetPosition());
instance.m_pad_mouse.position = { physPos.x, physPos.y };
if (event.ButtonDown(wxMOUSE_BTN_RIGHT))
instance.m_pad_mouse.right_down_toggle = true;
}
void PadViewFrame::OnSetWindowTitle(wxCommandEvent& event)
{
this->SetTitle(event.GetString());
}
void PadViewFrame::AsyncSetTitle(std::string_view windowTitle)
{
wxCommandEvent set_title_event(wxEVT_SET_WINDOW_TITLE);
set_title_event.SetString(wxString::FromUTF8(windowTitle));
QueueEvent(set_title_event.Clone());
}
-38
View File
@@ -1,38 +0,0 @@
#pragma once
#include <wx/frame.h>
#define WM_CREATE_PAD (WM_USER+1)
#define WM_DESTROY_PAD (WM_USER+2)
wxDECLARE_EVENT(EVT_PAD_CLOSE, wxCommandEvent);
wxDECLARE_EVENT(EVT_SET_WINDOW_TITLE, wxCommandEvent);
class PadViewFrame : public wxFrame
{
public:
PadViewFrame(wxFrame* parent);
~PadViewFrame();
bool Initialize();
void InitializeRenderCanvas();
void DestroyCanvas();
void OnKeyUp(wxKeyEvent& event);
void OnChar(wxKeyEvent& event);
void AsyncSetTitle(std::string_view windowTitle);
private:
void OnMouseMove(wxMouseEvent& event);
void OnMouseLeft(wxMouseEvent& event);
void OnMouseRight(wxMouseEvent& event);
void OnSizeEvent(wxSizeEvent& event);
void OnDPIChangedEvent(wxDPIChangedEvent& event);
void OnMoveEvent(wxMoveEvent& event);
void OnGesturePan(wxPanGestureEvent& event);
void OnSetWindowTitle(wxCommandEvent& event);
wxWindow* m_render_canvas = nullptr;
};
-907
View File
@@ -1,907 +0,0 @@
#include "wxgui/TitleManager.h"
#include "wxgui/helpers/wxCustomEvents.h"
#include "wxgui/helpers/wxCustomData.h"
#include "Cafe/TitleList/GameInfo.h"
#include "util/helpers/helpers.h"
#include "wxgui/helpers/wxHelpers.h"
#include "wxgui/wxHelper.h"
#include "wxgui/components/wxTitleManagerList.h"
#include "wxgui/components/wxDownloadManagerList.h"
#include "wxgui/GameUpdateWindow.h"
#include "wxgui/dialogs/SaveImport/SaveTransfer.h"
#include <wx/listbase.h>
#include <wx/sizer.h>
#include <wx/listctrl.h>
#include <wx/statline.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/statbmp.h>
#include <wx/panel.h>
#include <wx/bmpbuttn.h>
#include <wx/choice.h>
#include <wx/msgdlg.h>
#include <wx/filedlg.h>
#include <wx/combobox.h>
#include <wx/checkbox.h>
#include <pugixml.hpp>
#include <zip.h>
#include <wx/dirdlg.h>
#include <wx/notebook.h>
#include <wx/settings.h>
#include "Cafe/IOSU/legacy/iosu_crypto.h"
#include "config/ActiveSettings.h"
#include "wxgui/dialogs/SaveImport/SaveImportWindow.h"
#include "Cafe/Account/Account.h"
#include "Cemu/Tools/DownloadManager/DownloadManager.h"
#include "wxgui/CemuApp.h"
#include "Cafe/TitleList/TitleList.h"
#include "Cafe/TitleList/SaveList.h"
#include "resource/embedded/resources.h"
wxDEFINE_EVENT(wxEVT_TITLE_FOUND, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_TITLE_SEARCH_COMPLETE, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_DL_TITLE_UPDATE, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_DL_DISCONNECT_COMPLETE, wxCommandEvent);
wxPanel* TitleManager::CreateTitleManagerPage()
{
auto* panel = new wxPanel(m_notebook);
auto* sizer = new wxBoxSizer(wxVERTICAL);
{
auto* row = new wxFlexGridSizer(0, 4, 0, 0);
row->AddGrowableCol(1);
row->Add(new wxStaticText(panel, wxID_ANY, _("Filter")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_filter = new wxTextCtrl(panel, wxID_ANY);
m_filter->Bind(wxEVT_TEXT, &TitleManager::OnFilterChanged, this);
row->Add(m_filter, 1, wxALL | wxEXPAND, 5);
const wxImage refresh = wxHelper::LoadThemedBitmapFromPNG(PNG_REFRESH_png, sizeof(PNG_REFRESH_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)).ConvertToImage();
m_refresh_button = new wxBitmapButton(panel, wxID_ANY, refresh.Scale(16, 16));
m_refresh_button->Disable();
m_refresh_button->Bind(wxEVT_BUTTON, &TitleManager::OnRefreshButton, this);
m_refresh_button->SetToolTip(_("Refresh"));
row->Add(m_refresh_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
const wxBitmap help_bitmap = wxHelper::LoadThemedBitmapFromPNG(PNG_HELP_png, sizeof(PNG_HELP_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
auto* help_button = new wxStaticBitmap(panel, wxID_ANY, help_bitmap);
help_button->SetToolTip(formatWxString(_("The following prefixes are supported:\n{0}\n{1}\n{2}\n{3}\n{4}"),
"titleid:", "name:", "type:", "version:", "region:"));
row->Add(help_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
sizer->Add(row, 0, wxEXPAND, 5);
}
sizer->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL), 0, wxEXPAND | wxALL, 5);
m_title_list = new wxTitleManagerList(panel);
m_title_list->SetSizeHints(800, 600);
m_title_list->Bind(wxEVT_LIST_ITEM_SELECTED, &TitleManager::OnTitleSelected, this);
sizer->Add(m_title_list, 1, wxALL | wxEXPAND, 5);
sizer->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL), 0, wxEXPAND | wxALL, 5);
{
auto* row = new wxFlexGridSizer(0, 3, 0, 0);
row->AddGrowableCol(2);
auto* install_button = new wxButton(panel, wxID_ANY, _("Install title"));
install_button->Bind(wxEVT_BUTTON, &TitleManager::OnInstallTitle, this);
row->Add(install_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
row->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL), 0, wxEXPAND | wxALL, 5);
{
m_save_panel = new wxPanel(panel);
auto* save_sizer = new wxFlexGridSizer(0, 7, 0, 0);
save_sizer->Add(new wxStaticText(m_save_panel, wxID_ANY, _("Account")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_save_account_list = new wxChoice(m_save_panel, wxID_ANY);
m_save_account_list->SetMinSize({ 170, -1 });
m_save_account_list->Bind(wxEVT_CHOICE, &TitleManager::OnSaveAccountSelected, this);
save_sizer->Add(m_save_account_list, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
auto* save_open = new wxButton(m_save_panel, wxID_ANY, _("Open directory"));
save_open->Bind(wxEVT_BUTTON, &TitleManager::OnSaveOpenDirectory, this);
save_open->SetToolTip(_("Open the directory of the save entry"));
save_open->Disable();
save_sizer->Add(save_open, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
auto* save_transfer = new wxButton(m_save_panel, wxID_ANY, _("Transfer"));
save_transfer->Bind(wxEVT_BUTTON, &TitleManager::OnSaveTransfer, this);
save_transfer->SetToolTip(_("Transfers the save entry to another persistent account id"));
save_transfer->Disable();
save_sizer->Add(save_transfer, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
auto* save_delete = new wxButton(m_save_panel, wxID_ANY, _("Delete"));
save_delete->Bind(wxEVT_BUTTON, &TitleManager::OnSaveDelete, this);
save_delete->SetToolTip(_("Permanently delete the save entry"));
save_delete->Disable();
save_sizer->Add(save_delete, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_save_import = new wxButton(m_save_panel, wxID_ANY, _("Import"));
m_save_import->Bind(wxEVT_BUTTON, &TitleManager::OnSaveImport, this);
m_save_import->SetToolTip(_("Imports a zipped save entry"));
save_sizer->Add(m_save_import, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxTOP | wxBOTTOM, 5);
auto* export_bttn = new wxButton(m_save_panel, wxID_ANY, _("Export"));
export_bttn->Bind(wxEVT_BUTTON, &TitleManager::OnSaveExport, this);
export_bttn->SetToolTip(_("Exports the selected save entry as zip file"));
export_bttn->Disable();
save_sizer->Add(export_bttn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxTOP | wxBOTTOM, 5);
m_save_panel->SetSizerAndFit(save_sizer);
row->Add(m_save_panel, 1, wxRESERVE_SPACE_EVEN_IF_HIDDEN | wxALIGN_CENTER_VERTICAL, 0);
m_save_panel->Hide(); // hide by default
}
sizer->Add(row, 0, wxEXPAND, 5);
}
panel->SetSizerAndFit(sizer);
return panel;
}
wxPanel* TitleManager::CreateDownloadManagerPage()
{
auto* panel = new wxPanel(m_notebook);
auto* sizer = new wxBoxSizer(wxVERTICAL);
{
auto* row = new wxBoxSizer(wxHORIZONTAL);
#if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN
m_account = new wxChoice(panel, wxID_ANY);
m_account->SetMinSize({ 250,-1 });
auto accounts = Account::GetAccounts();
if (!accounts.empty())
{
const auto id = GetConfig().account.m_persistent_id.GetValue();
for (const auto& a : accounts)
{
m_account->Append(a.ToString(), (void*)static_cast<uintptr_t>(a.GetPersistentId()));
if(a.GetPersistentId() == id)
{
m_account->SetSelection(m_account->GetCount() - 1);
}
}
}
row->Add(m_account, 0, wxALL, 5);
#endif
m_connect = new wxButton(panel, wxID_ANY, _("Connect"));
m_connect->Bind(wxEVT_BUTTON, &TitleManager::OnConnect, this);
row->Add(m_connect, 0, wxALL, 5);
sizer->Add(row, 0, wxEXPAND, 5);
}
#if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN
m_status_text = new wxStaticText(panel, wxID_ANY, _("Select an account and press Connect"));
#else
if(!NCrypto::HasDataForConsoleCert())
{
m_status_text = new wxStaticText(panel, wxID_ANY, _("Valid online files are required to download eShop titles. For more information, go to the Account tab in the General Settings."));
m_connect->Enable(false);
}
else
m_status_text = new wxStaticText(panel, wxID_ANY, _("Click on Connect to load the list of downloadable titles"));
#endif
this->Bind(wxEVT_SET_TEXT, &TitleManager::OnSetStatusText, this);
sizer->Add(m_status_text, 0, wxALL, 5);
sizer->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL), 0, wxEXPAND | wxALL, 5);
{
auto* row = new wxFlexGridSizer(0, 3, 0, 0);
m_show_titles = new wxCheckBox(panel, wxID_ANY, _("Show available titles"));
m_show_titles->SetValue(true);
m_show_titles->Enable(false);
row->Add(m_show_titles, 0, wxALL, 5);
m_show_titles->Bind(wxEVT_CHECKBOX, &TitleManager::OnDlFilterCheckbox, this);
m_show_updates = new wxCheckBox(panel, wxID_ANY, _("Show available updates"));
m_show_updates->SetValue(true);
m_show_updates->Enable(false);
row->Add(m_show_updates, 0, wxALL, 5);
m_show_updates->Bind(wxEVT_CHECKBOX, &TitleManager::OnDlFilterCheckbox, this);
m_show_installed = new wxCheckBox(panel, wxID_ANY, _("Show installed"));
m_show_installed->SetValue(true);
m_show_installed->Enable(false);
row->Add(m_show_installed, 0, wxALL, 5);
m_show_installed->Bind(wxEVT_CHECKBOX, &TitleManager::OnDlFilterCheckbox, this);
sizer->Add(row, 0, wxEXPAND, 5);
}
m_download_list = new wxDownloadManagerList(panel);
m_download_list->SetSizeHints(800, 600);
m_download_list->Bind(wxEVT_LIST_ITEM_SELECTED, &TitleManager::OnTitleSelected, this);
sizer->Add(m_download_list, 1, wxALL | wxEXPAND, 5);
panel->SetSizerAndFit(sizer);
return panel;
}
TitleManager::TitleManager(wxWindow* parent, TitleManagerPage default_page)
: wxFrame(parent, wxID_ANY, _("Title Manager"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL)
{
SetIcon(wxICON(X_BOX));
auto* sizer = new wxBoxSizer(wxVERTICAL);
m_notebook = new wxNotebook(this, wxID_ANY);
m_notebook->AddPage(CreateTitleManagerPage(), _("Title Manager"), default_page == TitleManagerPage::TitleManager);
m_notebook->AddPage(CreateDownloadManagerPage(), _("Download Manager"), default_page == TitleManagerPage::DownloadManager);
sizer->Add(m_notebook, 1, wxEXPAND);
m_status_bar = CreateStatusBar(2, wxSTB_SIZEGRIP);
m_status_bar->SetStatusText(_("Searching for titles..."));
this->SetSizerAndFit(sizer);
this->Centre(wxBOTH);
this->Bind(wxEVT_SET_STATUS_BAR_TEXT, &TitleManager::OnSetStatusBarText, this);
this->Bind(wxEVT_TITLE_FOUND, &TitleManager::OnTitleFound, this);
this->Bind(wxEVT_TITLE_SEARCH_COMPLETE, &TitleManager::OnTitleSearchComplete, this);
this->Bind(wxEVT_DL_TITLE_UPDATE, &TitleManager::OnDownloadableTitleUpdate, this);
this->Bind(wxEVT_DL_DISCONNECT_COMPLETE, &TitleManager::OnDisconnect, this);
// TODO typing on title list should change filter text and filter!
// if download manager is already active then restore state
DownloadManager* dlMgr = DownloadManager::GetInstance(false);
if (dlMgr && dlMgr->IsConnected())
{
dlMgr->setUserData(this);
dlMgr->registerCallbacks(
TitleManager::Callback_ConnectStatusUpdate,
TitleManager::Callback_AddDownloadableTitle,
TitleManager::Callback_RemoveDownloadableTitle);
SetConnected(true);
}
m_callbackId = CafeTitleList::RegisterCallback([](CafeTitleListCallbackEvent* evt, void* ctx) { ((TitleManager*)ctx)->HandleTitleListCallback(evt); }, this);
}
TitleManager::~TitleManager()
{
CafeTitleList::UnregisterCallback(m_callbackId);
// unregister callbacks for download manager
DownloadManager* dlMgr = DownloadManager::GetInstance(false);
if (dlMgr)
{
dlMgr->setUserData(nullptr);
dlMgr->registerCallbacks(
nullptr,
nullptr,
nullptr);
// if download manager is still downloading / installing then show a warning
if (dlMgr->hasActiveDownloads())
{
static bool s_showedBGDownloadWarning = false;
if (!s_showedBGDownloadWarning)
{
wxMessageBox(_("Currently active downloads will continue in the background."), _("Information"), wxOK | wxCENTRE, this);
s_showedBGDownloadWarning = true;
}
}
}
m_running = false;
}
void TitleManager::SetFocusAndTab(TitleManagerPage page)
{
m_notebook->SetSelection((int)page);
this->SetFocus();
}
void TitleManager::SetDownloadStatusText(const wxString& text)
{
auto* evt = new wxCommandEvent(wxEVT_SET_TEXT);
evt->SetEventObject(m_status_text);
evt->SetString(text);
wxQueueEvent(this, evt);
}
void TitleManager::HandleTitleListCallback(CafeTitleListCallbackEvent* evt)
{
if (evt->eventType == CafeTitleListCallbackEvent::TYPE::SCAN_FINISHED)
{
auto* evt = new wxCommandEvent(wxEVT_TITLE_SEARCH_COMPLETE);
wxQueueEvent(this, evt);
}
}
void TitleManager::OnTitleFound(wxCommandEvent& event)
{
auto* obj = dynamic_cast<wxTitleManagerList::TitleEntryData_t*>(event.GetClientObject());
wxASSERT(obj);
m_title_list->AddTitle(obj);
}
void TitleManager::OnTitleSearchComplete(wxCommandEvent& event)
{
m_isScanning = false;
if (m_connectRequested)
{
InitiateConnect();
m_connectRequested = false;
}
// update status bar text
m_title_list->SortEntries(-1);
m_status_bar->SetStatusText(formatWxString(_("Found {0} games, {1} updates, {2} DLCs and {3} save entries"),
m_title_list->GetCountByType(wxTitleManagerList::EntryType::Base) + m_title_list->GetCountByType(wxTitleManagerList::EntryType::System),
m_title_list->GetCountByType(wxTitleManagerList::EntryType::Update),
m_title_list->GetCountByType(wxTitleManagerList::EntryType::Dlc),
m_title_list->GetCountByType(wxTitleManagerList::EntryType::Save)
));
// re-filter if any filter is set
const auto filter = m_filter->GetValue();
if (!filter.IsEmpty())
m_title_list->Filter(m_filter->GetValue());
m_title_list->AutosizeColumns();
m_refresh_button->Enable();
}
void TitleManager::OnSetStatusBarText(wxSetStatusBarTextEvent& event)
{
m_status_bar->SetStatusText(event.GetText(), event.GetNumber());
}
void TitleManager::OnFilterChanged(wxCommandEvent& event)
{
event.Skip();
m_title_list->Filter(m_filter->GetValue());
}
void TitleManager::OnRefreshButton(wxCommandEvent& event)
{
m_refresh_button->Disable();
m_isScanning = true;
// m_title_list->ClearItems(); -> Dont clear. Refresh() triggers incremental updates via notifications
m_status_bar->SetStatusText(_("Searching for titles..."));
CafeTitleList::Refresh();
}
void TitleManager::OnInstallTitle(wxCommandEvent& event)
{
wxFileDialog openFileDialog(this, _("Select title to install"), "", "", "meta.xml|meta.xml", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (openFileDialog.ShowModal() == wxID_CANCEL || openFileDialog.GetPath().IsEmpty())
return;
fs::path filePath(openFileDialog.GetPath().wc_string());
try
{
filePath = filePath.parent_path();
filePath = filePath.parent_path();
GameUpdateWindow frame(*this, filePath);
const int updateResult = frame.ShowModal();
if (updateResult == wxID_OK)
{
CafeTitleList::AddTitleFromPath(frame.GetTargetPath());
}
else
{
if (frame.GetExceptionMessage().empty())
wxMessageBox(_("Update installation has been canceled!"));
else
{
throw std::runtime_error(frame.GetExceptionMessage());
}
}
}
catch (const AbortException&)
{
// ignored
}
catch (const std::exception& ex)
{
wxMessageBox(ex.what(), _("Update error"));
}
}
static void PopulateSavePersistentIds(wxTitleManagerList::TitleEntry& entry)
{
if (!entry.persistent_ids.empty())
return;
cemu_assert(entry.type == wxTitleManagerList::EntryType::Save);
SaveInfo saveInfo = CafeSaveList::GetSaveByTitleId(entry.title_id);
if (!saveInfo.IsValid())
return;
fs::path savePath = saveInfo.GetPath();
savePath /= "user";
std::error_code ec;
for (auto it : fs::directory_iterator(savePath, ec))
{
if(!it.is_directory(ec))
continue;
if(fs::is_empty(it.path()))
continue;
std::string dirName = it.path().filename().string();
if(!std::regex_match(dirName, std::regex("[0-9a-fA-F]{8}")))
continue;
entry.persistent_ids.emplace_back(ConvertString<uint32>(dirName, 16));
}
}
void TitleManager::OnTitleSelected(wxListEvent& event)
{
event.Skip();
const auto entry = m_title_list->GetSelectedTitleEntry();
if(entry.has_value() && entry->type == wxTitleManagerList::EntryType::Save)
{
m_save_panel->Show();
m_save_account_list->Clear();
PopulateSavePersistentIds(*entry);
// an account must be selected before any of the control buttons can be used
for(auto&& v : m_save_panel->GetChildren())
{
if (dynamic_cast<wxButton*>(v) && v->GetId() != m_save_import->GetId()) // import is always enabled
v->Disable();
}
const auto& accounts = Account::GetAccounts();
for (const auto& id : entry->persistent_ids)
{
const auto it = std::find_if(accounts.cbegin(), accounts.cend(), [id](const auto& acc) { return acc.GetPersistentId() == id; });
if(it != accounts.cend())
{
m_save_account_list->Append(fmt::format("{:x} ({})", id,
boost::nowide::narrow(it->GetMiiName().data(), it->GetMiiName().size())),
(void*)(uintptr_t)id);
}
else
m_save_account_list->Append(fmt::format("{:x}", id), (void*)(uintptr_t)id);
}
}
else
{
m_save_panel->Hide();
}
}
void TitleManager::OnSaveOpenDirectory(wxCommandEvent& event)
{
const auto selection= m_save_account_list->GetSelection();
if (selection == wxNOT_FOUND)
return;
const auto entry = m_title_list->GetSelectedTitleEntry();
if (!entry.has_value())
return;
const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection);
const fs::path target = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(entry->title_id >> 32), (uint32)(entry->title_id & 0xFFFFFFFF), persistent_id);
if (!fs::exists(target) || !fs::is_directory(target))
return;
wxLaunchDefaultApplication(wxHelper::FromPath(target));
}
void TitleManager::OnSaveDelete(wxCommandEvent& event)
{
const auto selection_index = m_save_account_list->GetSelection();
if (selection_index == wxNOT_FOUND)
return;
const auto selection = m_save_account_list->GetStringSelection();
if (selection.IsEmpty())
return;
const auto msg = formatWxString(_("Are you really sure that you want to delete the save entry for {}"), selection);
const auto result = wxMessageBox(msg, _("Warning"), wxYES_NO | wxCENTRE | wxICON_EXCLAMATION, this);
if (result == wxNO)
return;
auto entry = m_title_list->GetSelectedTitleEntry();
if (!entry.has_value())
return;
const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection_index);
const fs::path target = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(entry->title_id >> 32), (uint32)(entry->title_id & 0xFFFFFFFF), persistent_id);
if (!fs::exists(target) || !fs::is_directory(target))
return;
// edit meta saveinfo.xml
bool meta_file_edited = false;
const fs::path saveinfo = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", (uint32)(entry->title_id >> 32), (uint32)(entry->title_id & 0xFFFFFFFF));
if (fs::exists(saveinfo) || fs::is_regular_file(saveinfo))
{
pugi::xml_document doc;
if (doc.load_file(saveinfo.c_str()))
{
auto info_node = doc.child("info");
if(info_node)
{
auto persistend_id_string = fmt::format(L"{:08x}", persistent_id);
const auto delete_entry = info_node.find_child([&persistend_id_string](const pugi::xml_node& node)
{
return boost::iequals(node.attribute("persistentId").as_string(), persistend_id_string);
});
if (delete_entry)
{
info_node.remove_child(delete_entry);
meta_file_edited = doc.save_file(saveinfo.c_str());
}
}
}
}
if (!meta_file_edited)
cemuLog_log(LogType::Force, "TitleManager::OnSaveDelete: couldn't delete save entry in saveinfo.xml: {}", _pathToUtf8(saveinfo));
// remove from title entry
auto& persistent_ids = entry->persistent_ids;
persistent_ids.erase(std::remove(persistent_ids.begin(), persistent_ids.end(), persistent_id), persistent_ids.end());
std::error_code ec;
fs::remove_all(target, ec);
if (ec)
{
const auto error_msg = formatWxString(_("Error when trying to delete the save directory:\n{}"), GetSystemErrorMessage(ec));
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this);
return;
}
m_save_account_list->Delete(selection_index);
}
void TitleManager::OnSaveTransfer(wxCommandEvent& event)
{
const auto selection_index = m_save_account_list->GetSelection();
if (selection_index == wxNOT_FOUND)
return;
const auto selection = m_save_account_list->GetStringSelection();
if (selection.IsEmpty())
return;
auto entry = m_title_list->GetSelectedTitleEntry();
if (!entry.has_value())
return;
const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection_index);
SaveTransfer transfer(this, entry->title_id, selection, persistent_id);
if (transfer.ShowModal() == wxCANCEL)
return;
// remove old id entry
auto& persistent_ids = entry->persistent_ids;
persistent_ids.erase(std::remove(persistent_ids.begin(), persistent_ids.end(), persistent_id), persistent_ids.end());
// add new id if not added yet
const auto new_id = transfer.GetTargetPersistentId();
if (new_id != 0 && std::find(persistent_ids.cbegin(), persistent_ids.cend(), new_id) == persistent_ids.cend())
{
persistent_ids.emplace_back(new_id);
const auto& account = Account::GetAccount(new_id);
if(account.GetPersistentId() == new_id)
m_save_account_list->Append(fmt::format("{:x} ({})", new_id,
boost::nowide::narrow(account.GetMiiName().data(), account.GetMiiName().size())),
(void*)(uintptr_t)new_id);
else
m_save_account_list->Append(fmt::format("{:x}", new_id), (void*)(uintptr_t)new_id);
}
m_save_account_list->Delete(selection_index);
}
void TitleManager::OnSaveAccountSelected(wxCommandEvent& event)
{
event.Skip();
for (auto&& v : m_save_panel->GetChildren())
{
if (!v->IsEnabled())
v->Enable();
}
}
void TitleManager::OnSaveExport(wxCommandEvent& event)
{
const auto selection_index = m_save_account_list->GetSelection();
if (selection_index == wxNOT_FOUND)
return;
const auto selection = m_save_account_list->GetStringSelection();
if (selection.IsEmpty())
return;
const auto entry = m_title_list->GetSelectedTitleEntry();
if (!entry.has_value())
return;
const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection_index);
wxFileDialog path_dialog(this, _("Select a target file to export the save entry"), wxHelper::FromPath(entry->path), wxEmptyString,
fmt::format("{}|*.zip", _("Exported save entry (*.zip)")), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().IsEmpty())
return;
const auto path = path_dialog.GetPath();
int ze;
auto* zip = zip_open(path.ToUTF8().data(), ZIP_CREATE | ZIP_TRUNCATE, &ze);
if (!zip)
{
zip_error_t ziperror;
zip_error_init_with_code(&ziperror, ze);
const auto error_msg = formatWxString(_("Error when creating the zip for the save entry:\n{}"), zip_error_strerror(&ziperror));
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
return;
}
// grab all files to zip
const auto savedir = fs::path(entry->path).append(fmt::format("user/{:08x}", persistent_id));
const auto savedir_str = savedir.generic_u8string();
for(const auto& it : fs::recursive_directory_iterator(savedir))
{
if (it.path() == "." || it.path() == "..")
continue;
const auto entryname = it.path().generic_u8string();
if(fs::is_directory(it.path()))
{
if(zip_dir_add(zip, (const char*)entryname.substr(savedir_str.size() + 1).c_str(), ZIP_FL_ENC_UTF_8) < 0 )
{
const auto error_msg = formatWxString(_("Error when trying to add a directory to the zip:\n{}"), zip_strerror(zip));
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
}
}
else
{
auto* source = zip_source_file(zip, (const char*)entryname.c_str(), 0, 0);
if(!source)
{
const auto error_msg = formatWxString(_("Error when trying to add a file to the zip:\n{}"), zip_strerror(zip));
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
}
if (zip_file_add(zip, (const char*)entryname.substr(savedir_str.size() + 1).c_str(), source, ZIP_FL_ENC_UTF_8) < 0)
{
const auto error_msg = formatWxString(_("Error when trying to add a file to the zip:\n{}"), zip_strerror(zip));
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
zip_source_free(source);
}
}
}
// add own metainfo like file and store title id for verification later
std::string metacontent = fmt::format("titleId = {:#016x}", entry->title_id);
auto* metabuff = zip_source_buffer(zip, metacontent.data(), metacontent.size(), 0);
if(zip_file_add(zip, "cemu_meta", metabuff, ZIP_FL_ENC_UTF_8) < 0)
{
const auto error_msg = formatWxString(_("Error when trying to add cemu_meta file to the zip:\n{}"), zip_strerror(zip));
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
zip_source_free(metabuff);
}
zip_close(zip);
}
void TitleManager::OnSaveImport(wxCommandEvent& event)
{
auto entry = m_title_list->GetSelectedTitleEntry();
if (!entry.has_value())
return;
SaveImportWindow save_import(this, entry->title_id);
if (save_import.ShowModal() == wxCANCEL)
return;
// add new id if not added yet
auto& persistent_ids = entry->persistent_ids;
const auto new_id = save_import.GetTargetPersistentId();
if (new_id != 0 && std::find(persistent_ids.cbegin(), persistent_ids.cend(), new_id) == persistent_ids.cend())
{
persistent_ids.emplace_back(new_id);
const auto& account = Account::GetAccount(new_id);
if (account.GetPersistentId() == new_id)
m_save_account_list->Append(fmt::format("{:x} ({})", new_id,
boost::nowide::narrow(account.GetMiiName().data(), account.GetMiiName().size())),
(void*)(uintptr_t)new_id);
else
m_save_account_list->Append(fmt::format("{:x}", new_id), (void*)(uintptr_t)new_id);
}
}
void TitleManager::InitiateConnect()
{
// init connection to download manager if queued
#if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN
uint32 persistentId = (uint32)(uintptr_t)m_account->GetClientData(m_account->GetSelection());
auto& account = Account::GetAccount(persistentId);
#endif
DownloadManager* dlMgr = DownloadManager::GetInstance();
dlMgr->reset();
m_download_list->SetCurrentDownloadMgr(dlMgr);
std::string deviceCertBase64 = NCrypto::CertECC::GetDeviceCertificate().encodeToBase64();
if (!NCrypto::SEEPROM_IsPresent())
{
SetDownloadStatusText(_("Dumped online files not found"));
return;
}
SetDownloadStatusText(_("Connecting..."));
// begin async connect
dlMgr->setUserData(this);
dlMgr->registerCallbacks(
TitleManager::Callback_ConnectStatusUpdate,
TitleManager::Callback_AddDownloadableTitle,
TitleManager::Callback_RemoveDownloadableTitle);
std::string accountName;
std::array<uint8, 32> accountPassword;
std::string accountCountry;
#if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN
accountName = account.GetAccountId();
accountPassword = account.GetAccountPasswordCache();
accountCountry.assign(NCrypto::GetCountryAsString(account.GetCountry()));
#endif
dlMgr->connect(accountName, accountPassword, NCrypto::SEEPROM_GetRegion(), accountCountry, NCrypto::GetDeviceId(), NCrypto::GetSerial(), deviceCertBase64);
}
void TitleManager::OnConnect(wxCommandEvent& event)
{
if (!m_isScanning)
{
InitiateConnect();
SetConnected(true);
}
else
{
SetDownloadStatusText(_("Getting installed title information..."));
SetConnected(true);
m_connectRequested = true;
}
}
void TitleManager::OnDlFilterCheckbox(wxCommandEvent& event)
{
m_download_list->Filter2(m_show_titles->GetValue(), m_show_updates->GetValue(), m_show_installed->GetValue());
m_download_list->SortEntries();
}
void TitleManager::OnSetStatusText(wxCommandEvent& event)
{
auto* text = wxDynamicCast(event.GetEventObject(), wxControl);
wxASSERT(text);
text->SetLabel(event.GetString());
}
void TitleManager::OnDownloadableTitleUpdate(wxCommandEvent& event)
{
auto* obj = dynamic_cast<wxDownloadManagerList::TitleEntryData_t*>(event.GetClientObject());
auto entry = obj->GetData();
m_download_list->AddOrUpdateTitle(obj);
}
void TitleManager::OnDisconnect(wxCommandEvent& event)
{
SetConnected(false);
}
void TitleManager::SetConnected(bool state)
{
#if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN
m_account->Enable(!state);
#endif
m_connect->Enable(!state);
m_show_titles->Enable(state);
m_show_updates->Enable(state);
m_show_installed->Enable(state);
m_download_list->Enable(state);
}
void TitleManager::Callback_ConnectStatusUpdate(std::string statusText, DLMGR_STATUS_CODE statusCode)
{
TitleManager* titleManager = static_cast<TitleManager*>(DownloadManager::GetInstance()->getUserData());
titleManager->SetDownloadStatusText(wxString::FromUTF8(statusText));
if (statusCode == DLMGR_STATUS_CODE::FAILED)
{
auto* evt = new wxCommandEvent(wxEVT_DL_DISCONNECT_COMPLETE);
wxQueueEvent(titleManager, evt); // this will set SetConnected() to false
return;
}
}
void TitleManager::Callback_AddDownloadableTitle(const DlMgrTitleReport& titleInfo)
{
TitleManager* titleManager = static_cast<TitleManager*>(DownloadManager::GetInstance()->getUserData());
wxDownloadManagerList::EntryType type = wxDownloadManagerList::EntryType::Base;
if (((titleInfo.titleId >> 32) & 0xF) == 0xE)
type = wxDownloadManagerList::EntryType::Update;
else if (((titleInfo.titleId >> 32) & 0xF) == 0xC)
type = wxDownloadManagerList::EntryType::DLC;
if (titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE || titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UNFINISHED || titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UPDATE)
{
// installable title
wxDownloadManagerList::TitleEntry titleEntry(type, false, titleInfo.titleId, titleInfo.version, titleInfo.isPaused);
titleEntry.name = wxString::FromUTF8(titleInfo.name);
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Available;
titleEntry.progress = 0;
if (titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UNFINISHED)
titleEntry.progress = 1;
if (titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UPDATE)
titleEntry.progress = 2;
auto* evt = new wxCommandEvent(wxEVT_DL_TITLE_UPDATE);
evt->SetClientObject(new wxCustomData(titleEntry));
wxQueueEvent(titleManager, evt);
}
else
{
// package
wxDownloadManagerList::TitleEntry titleEntry(type, true, titleInfo.titleId, titleInfo.version, titleInfo.isPaused);
titleEntry.name = wxString::FromUTF8(titleInfo.name);
titleEntry.progress = titleInfo.progress;
titleEntry.progressMax = titleInfo.progressMax;
switch (titleInfo.status)
{
case DlMgrTitleReport::STATUS::INITIALIZING:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Initializing;
break;
case DlMgrTitleReport::STATUS::QUEUED:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Queued;
break;
case DlMgrTitleReport::STATUS::CHECKING:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Checking;
break;
case DlMgrTitleReport::STATUS::DOWNLOADING:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Downloading;
titleEntry.progress = titleInfo.progress;
break;
case DlMgrTitleReport::STATUS::VERIFYING:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Verifying;
break;
case DlMgrTitleReport::STATUS::INSTALLING:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Installing;
break;
case DlMgrTitleReport::STATUS::INSTALLED:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Installed;
break;
case DlMgrTitleReport::STATUS::HAS_ERROR:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Error;
titleEntry.errorMsg = titleInfo.errorMsg;
break;
default:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::None;
break;
}
auto* evt = new wxCommandEvent(wxEVT_DL_TITLE_UPDATE);
evt->SetClientObject(new wxCustomData(titleEntry));
wxQueueEvent(titleManager, evt);
}
}
void TitleManager::Callback_RemoveDownloadableTitle(uint64 titleId, uint16 version)
{
TitleManager* titleManager = static_cast<TitleManager*>(DownloadManager::GetInstance()->getUserData());
assert_dbg();
}
-104
View File
@@ -1,104 +0,0 @@
#pragma once
#include <thread>
#include <atomic>
#include <wx/frame.h>
#include <wx/button.h>
#include "Cemu/Tools/DownloadManager/DownloadManager.h"
#define DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN 0
class wxCheckBox;
class wxStaticText;
class wxListEvent;
class wxSetStatusBarTextEvent;
class wxTitleManagerList;
class wxDownloadManagerList;
class wxTextCtrl;
class wxStatusBar;
class wxImageList;
class wxBitmapButton;
class wxPanel;
class wxChoice;
class wxNotebook;
enum class TitleManagerPage
{
TitleManager = 0,
DownloadManager = 1
};
enum class DLMGR_STATUS_CODE;
class TitleManager : public wxFrame
{
public:
TitleManager(wxWindow* parent, TitleManagerPage default_page = TitleManagerPage::TitleManager);
~TitleManager();
void SetFocusAndTab(TitleManagerPage page);
void SetDownloadStatusText(const wxString& text);
private:
wxPanel* CreateTitleManagerPage();
wxPanel* CreateDownloadManagerPage();
// title manager
void OnTitleFound(wxCommandEvent& event);
void OnTitleSearchComplete(wxCommandEvent& event);
void OnSetStatusBarText(wxSetStatusBarTextEvent& event);
void OnFilterChanged(wxCommandEvent& event);
void OnRefreshButton(wxCommandEvent& event);
void OnInstallTitle(wxCommandEvent& event);
void OnTitleSelected(wxListEvent& event);
void OnSaveOpenDirectory(wxCommandEvent& event);
void OnSaveDelete(wxCommandEvent& event);
void OnSaveTransfer(wxCommandEvent& event);
void OnSaveAccountSelected(wxCommandEvent& event);
void OnSaveExport(wxCommandEvent& event);
void OnSaveImport(wxCommandEvent& event);
void HandleTitleListCallback(struct CafeTitleListCallbackEvent* evt);
wxNotebook* m_notebook;
uint32 m_callbackId;
// title manager
wxTextCtrl* m_filter;
wxTitleManagerList* m_title_list;
wxStatusBar* m_status_bar;
wxBitmapButton* m_refresh_button;
wxPanel* m_save_panel;
wxChoice* m_save_account_list;
wxButton* m_save_import;
bool m_isScanning{ true }; // set when CafeTitleList is scanning
std::atomic_bool m_running = true;
// download manager
void InitiateConnect();
void OnConnect(wxCommandEvent& event);
void OnSetStatusText(wxCommandEvent& event);
void OnDownloadableTitleUpdate(wxCommandEvent& event);
void OnDisconnect(wxCommandEvent& event);
void OnDlFilterCheckbox(wxCommandEvent& event);
void SetConnected(bool state);
static void Callback_ConnectStatusUpdate(std::string statusText, DLMGR_STATUS_CODE statusCode);
static void Callback_AddDownloadableTitle(const struct DlMgrTitleReport& titleInfo);
static void Callback_RemoveDownloadableTitle(uint64 titleId, uint16 version);
wxChoice* m_account;
wxButton* m_connect;
wxStaticText* m_status_text;
wxCheckBox *m_show_titles, *m_show_updates, *m_show_installed;
wxDownloadManagerList* m_download_list;
bool m_connectRequested{false}; // connect was clicked before m_foundTitles was available
};
-14
View File
@@ -1,14 +0,0 @@
#pragma once
#include <wx/wxprec.h>
// base class for all render interfaces
class IRenderCanvas
{
public:
IRenderCanvas(bool is_main_window)
: m_is_main_window(is_main_window) {}
protected:
bool m_is_main_window;
};
-61
View File
@@ -1,61 +0,0 @@
#include "wxgui/canvas/MetalCanvas.h"
#include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h"
#include <wx/msgdlg.h>
#include <helpers/wxHelpers.h>
MetalCanvas::MetalCanvas(wxWindow* parent, const wxSize& size, bool is_main_window)
: IRenderCanvas(is_main_window), wxWindow(parent, wxID_ANY, wxDefaultPosition, size, wxNO_FULL_REPAINT_ON_RESIZE | wxWANTS_CHARS)
{
Bind(wxEVT_PAINT, &MetalCanvas::OnPaint, this);
Bind(wxEVT_SIZE, &MetalCanvas::OnResize, this);
auto& canvas = is_main_window ? WindowSystem::GetWindowInfo().canvas_main : WindowSystem::GetWindowInfo().canvas_pad;
canvas = initHandleContextFromWxWidgetsWindow(this);
try
{
if (is_main_window)
g_renderer = std::make_unique<MetalRenderer>();
auto metal_renderer = MetalRenderer::GetInstance();
metal_renderer->InitializeLayer({size.x, size.y}, is_main_window);
}
catch(const std::exception& ex)
{
cemuLog_log(LogType::Force, "Error when initializing Metal renderer: {}", ex.what());
auto msg = formatWxString(_("Error when initializing Metal renderer:\n{}"), ex.what());
wxMessageDialog dialog(this, msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
dialog.ShowModal();
exit(0);
}
wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES);
}
MetalCanvas::~MetalCanvas()
{
Unbind(wxEVT_PAINT, &MetalCanvas::OnPaint, this);
Unbind(wxEVT_SIZE, &MetalCanvas::OnResize, this);
MetalRenderer* mtlr = (MetalRenderer*)g_renderer.get();
if (mtlr)
mtlr->ShutdownLayer(m_is_main_window);
}
void MetalCanvas::OnPaint(wxPaintEvent& event)
{
}
void MetalCanvas::OnResize(wxSizeEvent& event)
{
const wxSize size = GetSize();
if (size.GetWidth() == 0 || size.GetHeight() == 0)
return;
const wxRect refreshRect(size);
RefreshRect(refreshRect, false);
auto metal_renderer = MetalRenderer::GetInstance();
metal_renderer->ResizeLayer({size.x, size.y}, m_is_main_window);
}
-19
View File
@@ -1,19 +0,0 @@
#pragma once
#include "wxgui/canvas/IRenderCanvas.h"
#include <wx/frame.h>
#include <set>
class MetalCanvas : public IRenderCanvas, public wxWindow
{
public:
MetalCanvas(wxWindow* parent, const wxSize& size, bool is_main_window);
~MetalCanvas();
private:
void OnPaint(wxPaintEvent& event);
void OnResize(wxSizeEvent& event);
};
-179
View File
@@ -1,179 +0,0 @@
#include "wxgui/canvas/OpenGLCanvas.h"
#include "wxgui/canvas/IRenderCanvas.h"
#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h"
#include "config/CemuConfig.h"
#include "Common/GLInclude/GLInclude.h"
#include <wx/glcanvas.h> // this includes GL/gl.h, avoid using this in a header because it would contaminate our own OpenGL definitions (GLInclude)
static const int g_gl_attribute_list[] =
{
WX_GL_RGBA,
WX_GL_DOUBLEBUFFER,
WX_GL_DEPTH_SIZE, 16,
WX_GL_MIN_RED, 8,
WX_GL_MIN_GREEN, 8,
WX_GL_MIN_BLUE, 8,
WX_GL_MIN_ALPHA, 8,
WX_GL_STENCIL_SIZE, 8,
//WX_GL_MAJOR_VERSION, 4,
//WX_GL_MINOR_VERSION, 1,
//wx_GL_COMPAT_PROFILE,
0, // end of list
};
class OpenGLCanvas;
class GLCanvasManager : public OpenGLCanvasCallbacks
{
public:
GLCanvasManager()
{
SetOpenGLCanvasCallbacks(this);
}
~GLCanvasManager()
{
ClearOpenGLCanvasCallbacks();
}
void SetTVView(OpenGLCanvas* canvas)
{
m_tvView = canvas;
}
void SetPadView(OpenGLCanvas* canvas)
{
m_padView = canvas;
}
void SetGLContext(wxGLContext* context)
{
m_glContext = context;
}
void DeleteGLContext()
{
if (m_tvView == nullptr && m_padView == nullptr && m_glContext)
{
delete m_glContext;
m_glContext = nullptr;
}
}
bool HasPadViewOpen() const
{
return m_padView != nullptr;
}
bool MakeCurrent(bool padView);
void SwapBuffers(bool swapTV, bool swapDRC);
private:
wxGLContext* m_glContext = nullptr;
OpenGLCanvas* m_tvView = nullptr;
OpenGLCanvas* m_padView = nullptr;
} s_glCanvasManager;
class OpenGLCanvas : public IRenderCanvas, public wxGLCanvas
{
public:
OpenGLCanvas(wxWindow* parent, const wxSize& size, bool is_main_window)
: IRenderCanvas(is_main_window), wxGLCanvas(parent, wxID_ANY, g_gl_attribute_list, wxDefaultPosition, size, wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS)
{
if (m_is_main_window)
{
s_glCanvasManager.SetTVView(this);
s_glCanvasManager.SetGLContext(new wxGLContext(this));
g_renderer = std::make_unique<OpenGLRenderer>();
}
else
{
s_glCanvasManager.SetPadView(this);
}
wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES);
}
~OpenGLCanvas() override
{
// todo - if this is the main window, make sure the renderer has been shut down
if (m_is_main_window)
s_glCanvasManager.SetTVView(nullptr);
else
s_glCanvasManager.SetPadView(nullptr);
s_glCanvasManager.DeleteGLContext();
}
void UpdateVSyncState()
{
int configValue = GetConfig().vsync.GetValue();
configValue = configValue > 0 ? 1 : 0;
if(m_activeVSyncState != configValue)
{
#if BOOST_OS_WINDOWS
if(wglSwapIntervalEXT)
wglSwapIntervalEXT(configValue); // 1 = enabled, 0 = disabled
#elif BOOST_OS_LINUX || BOOST_OS_BSD
if (eglSwapInterval)
{
if (eglSwapInterval(eglGetCurrentDisplay(), configValue) == EGL_FALSE)
{
cemuLog_log(LogType::Force, "Failed to set vsync using EGL");
}
}
#else
cemuLog_log(LogType::Force, "OpenGL vsync not implemented");
#endif
m_activeVSyncState = configValue;
}
}
private:
int m_activeVSyncState = -1;
//wxGLContext* m_context = nullptr;
};
wxWindow* GLCanvas_Create(wxWindow* parent, const wxSize& size, bool is_main_window)
{
return new OpenGLCanvas(parent, size, is_main_window);
}
void GLCanvasManager::SwapBuffers(bool swapTV, bool swapDRC)
{
if (swapTV && m_tvView)
{
MakeCurrent(false);
m_tvView->SwapBuffers();
m_tvView->UpdateVSyncState();
}
if (swapDRC && m_padView)
{
MakeCurrent(true);
m_padView->SwapBuffers();
m_padView->UpdateVSyncState();
}
MakeCurrent(false);
}
bool GLCanvasManager::MakeCurrent(bool padView)
{
OpenGLCanvas* canvas = padView ? m_padView : m_tvView;
if (!canvas)
return false;
m_glContext->SetCurrent(*canvas);
return true;
}
-4
View File
@@ -1,4 +0,0 @@
#pragma once
#include <wx/window.h>
wxWindow* GLCanvas_Create(wxWindow* parent, const wxSize& size, bool is_main_window);
-85
View File
@@ -1,85 +0,0 @@
#include "wxgui/canvas/VulkanCanvas.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
#if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND
#include "wxgui/helpers/wxWayland.h"
#endif
#include <wx/msgdlg.h>
#include <helpers/wxHelpers.h>
VulkanCanvas::VulkanCanvas(wxWindow* parent, const wxSize& size, bool is_main_window)
: IRenderCanvas(is_main_window), wxWindow(parent, wxID_ANY, wxDefaultPosition, size, wxNO_FULL_REPAINT_ON_RESIZE | wxWANTS_CHARS)
{
Bind(wxEVT_PAINT, &VulkanCanvas::OnPaint, this);
Bind(wxEVT_SIZE, &VulkanCanvas::OnResize, this);
#if __WXMSW__
MSWDisableComposited();
#endif
auto& canvas = is_main_window ? WindowSystem::GetWindowInfo().canvas_main : WindowSystem::GetWindowInfo().canvas_pad;
canvas = initHandleContextFromWxWidgetsWindow(this);
#if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND
if (canvas.backend == WindowSystem::WindowHandleInfo::Backend::Wayland)
{
m_subsurface = std::make_unique<wxWlSubsurface>(this);
canvas.surface = m_subsurface->getSurface();
}
#endif
cemu_assert(g_vulkan_available);
try
{
if (is_main_window)
g_renderer = std::make_unique<VulkanRenderer>();
auto vulkan_renderer = VulkanRenderer::GetInstance();
vulkan_renderer->InitializeSurface({size.x, size.y}, is_main_window);
}
catch(const std::exception& ex)
{
cemuLog_log(LogType::Force, "Error when initializing Vulkan renderer: {}", ex.what());
auto msg = formatWxString(_("Error when initializing Vulkan renderer:\n{}"), ex.what());
wxMessageDialog dialog(this, msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
dialog.ShowModal();
_exit(0);
}
wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES);
}
VulkanCanvas::~VulkanCanvas()
{
Unbind(wxEVT_PAINT, &VulkanCanvas::OnPaint, this);
Unbind(wxEVT_SIZE, &VulkanCanvas::OnResize, this);
if(!m_is_main_window)
{
VulkanRenderer* vkr = (VulkanRenderer*)g_renderer.get();
if(vkr)
vkr->StopUsingPadAndWait();
}
}
void VulkanCanvas::OnPaint(wxPaintEvent& event)
{
}
void VulkanCanvas::OnResize(wxSizeEvent& event)
{
const wxSize size = GetSize();
if (size.GetWidth() == 0 || size.GetHeight() == 0)
return;
#if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND
if(m_subsurface)
{
auto sRect = GetScreenRect();
m_subsurface->setSize(sRect.GetX(), sRect.GetY(), sRect.GetWidth(), sRect.GetHeight());
}
#endif
const wxRect refreshRect(size);
RefreshRect(refreshRect, false);
}
-23
View File
@@ -1,23 +0,0 @@
#pragma once
#include "wxgui/canvas/IRenderCanvas.h"
#include <wx/frame.h>
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
#include <set>
class VulkanCanvas : public IRenderCanvas, public wxWindow
{
#if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND
std::unique_ptr<class wxWlSubsurface> m_subsurface;
#endif
public:
VulkanCanvas(wxWindow* parent, const wxSize& size, bool is_main_window);
~VulkanCanvas();
private:
void OnPaint(wxPaintEvent& event);
void OnResize(wxSizeEvent& event);
};
-320
View File
@@ -1,320 +0,0 @@
#include "wxgui/wxgui.h"
#include "TextList.h"
#include "debugger/DisasmCtrl.h"
#include <wx/setup.h>
#include <wx/tooltip.h>
TextList::~TextList()
{
m_tooltip_timer->Stop();
this->Unbind(wxEVT_MOTION, &TextList::OnMouseMoveEvent, this);
this->Unbind(wxEVT_KEY_DOWN, &TextList::OnKeyDownEvent, this);
this->Unbind(wxEVT_KEY_UP, &TextList::OnKeyUpEvent, this);
this->Unbind(wxEVT_PAINT, &TextList::OnPaintEvent, this);
this->Unbind(wxEVT_LEFT_DOWN, &TextList::OnMouseDownEvent, this);
this->Unbind(wxEVT_LEFT_UP, &TextList::OnMouseUpEvent, this);
this->Unbind(wxEVT_LEFT_DCLICK, &TextList::OnMouseDClickEvent, this);
this->Unbind(wxEVT_CONTEXT_MENU, &TextList::OnContextMenu, this);
this->Unbind(wxEVT_ERASE_BACKGROUND, &TextList::OnEraseBackground, this);
}
TextList::TextList(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: wxControl(parent, id, pos, size, style), wxScrollHelper(this)
{
m_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
this->wxWindowBase::SetBackgroundStyle(wxBG_STYLE_PAINT);
wxInfoDC dc(this);
this->DoPrepareReadOnlyDC(dc);
dc.SetFont(m_font);
m_line_height = dc.GetCharHeight();
m_char_width = dc.GetCharWidth();
m_yScrollPixelsPerLine = m_line_height;
this->ShowScrollbars(wxSHOW_SB_DEFAULT, wxSHOW_SB_DEFAULT);
m_tooltip = new wxToolTip(wxEmptyString);
this->Bind(wxEVT_MOTION, &TextList::OnMouseMoveEvent, this);
this->Bind(wxEVT_KEY_DOWN, &TextList::OnKeyDownEvent, this);
this->Bind(wxEVT_KEY_UP, &TextList::OnKeyUpEvent, this);
this->Bind(wxEVT_PAINT, &TextList::OnPaintEvent, this);
this->Bind(wxEVT_LEFT_DOWN, &TextList::OnMouseDownEvent, this);
this->Bind(wxEVT_LEFT_UP, &TextList::OnMouseUpEvent, this);
this->Bind(wxEVT_LEFT_DCLICK, &TextList::OnMouseDClickEvent, this);
this->Bind(wxEVT_CONTEXT_MENU, &TextList::OnContextMenu, this);
this->Bind(wxEVT_ERASE_BACKGROUND, &TextList::OnEraseBackground, this);
m_tooltip_window = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER);
m_tooltip_window->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK));
m_tooltip_window->Hide();
m_tooltip_timer = new wxTimer(this);
this->Bind(wxEVT_TIMER, &TextList::OnTooltipTimer, this);
}
void TextList::RefreshControl(const wxRect* update_region)
{
if(update_region)
Refresh(true, update_region);
else
{
wxRect region = GetClientRect();
update_region = &region;
Refresh(true, update_region);
}
}
void TextList::RefreshLine(uint32 line)
{
wxRect update_region = GetClientRect();
update_region.y = (sint32)line * m_line_height;
update_region.height = m_line_height;
CalcScrolledPosition(0, update_region.y, nullptr, &update_region.y);
// debug_printf("update: <%x, %x>\n", update_region.y, update_region.height);
Refresh(true, &update_region);
}
wxSize TextList::DoGetVirtualSize() const
{
return {wxDefaultCoord, (int)m_element_count * m_line_height};
}
void TextList::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{
wxControl::DoSetSize(x, y, width, height, sizeFlags);
m_elements_visible = (height + m_line_height - 1) / m_line_height;
Refresh();
}
void TextList::SetScrollPos(int orient, int pos, bool refresh)
{
wxControl::SetScrollPos(orient, pos, refresh);
}
void TextList::DrawLineBackground(wxDC& dc, uint32 line, const wxColour& colour, uint32 lines) const
{
wxRect rect;
rect.x = GetPosition().x;
rect.y = line * m_line_height;
rect.width = GetSize().x;
rect.height = m_line_height * lines;
dc.SetBrush(colour);
dc.DrawRectangle(rect);
}
void TextList::DrawLineBackground(wxDC& dc, const wxPoint& position, const wxColour& colour, uint32 lines) const
{
wxRect rect;
rect.x = position.x;
rect.y = position.y;
rect.width = this->GetSize().x;
rect.height = m_line_height * lines;
dc.SetBrush(colour);
dc.DrawRectangle(rect);
}
bool TextList::SetElementCount(size_t element_count)
{
if (m_element_count == element_count)
return false;
if (element_count > 0x7FFFFFFF)
element_count = 0x7FFFFFFF;
m_element_count = element_count;
this->AdjustScrollbars();
return true;
}
uint32 TextList::GetElementCount() const
{
return m_element_count;
}
bool TextList::IsKeyDown(sint32 keycode)
{
return m_key_states[keycode];
}
void TextList::WriteText(wxDC& dc, const wxString& text, wxPoint& position) const
{
dc.ResetBoundingBox();
dc.DrawText(text, position);
position.x += dc.MaxX() - dc.MinX();
}
void TextList::WriteText(wxDC& dc, const wxString& text, wxPoint& position, const wxColour& color) const
{
dc.SetTextForeground(color);
WriteText(dc, text, position);
}
void TextList::NextLine(wxPoint& position, const wxPoint* start_position) const
{
position.y += m_line_height;
if (start_position)
position.x = start_position->x;
}
void TextList::OnMouseDown() {}
void TextList::OnMouseUp() {}
bool TextList::OnShowTooltip(const wxPoint& position, uint32 line)
{
return false;
}
void TextList::OnMouseMoveEvent(wxMouseEvent& event)
{
m_tooltip_timer->Stop();
m_tooltip_timer->StartOnce(250);
wxPoint position = event.GetPosition();
CalcUnscrolledPosition(position.x, position.y, &position.x, &position.y);
m_mouse_position = position;
if(m_mouse_down)
m_selection.SetBottomRight(position);
const sint32 line = position.y / m_line_height;
OnMouseMove(position, line);
}
void TextList::OnKeyDownEvent(wxKeyEvent& event)
{
const auto key_code = event.GetKeyCode();
const auto it = m_key_states.find(key_code);
if(it == m_key_states.end() || !it->second)
{
m_key_states[key_code] = true;
OnKeyPressed(key_code, event.GetPosition());
}
event.Skip();
}
void TextList::OnKeyUpEvent(wxKeyEvent& event)
{
m_key_states[event.GetKeyCode()] = false;
}
void TextList::OnMouseDownEvent(wxMouseEvent& event)
{
m_mouse_down = true;
wxPoint position = event.GetPosition();
CalcUnscrolledPosition(position.x, position.y, &position.x, &position.y);
m_selection.SetPosition(position);
m_selection.SetBottomRight(position);
OnMouseDown();
event.Skip();
}
void TextList::OnMouseUpEvent(wxMouseEvent& event)
{
m_mouse_down = false;
OnMouseUp();
event.Skip();
}
void TextList::OnMouseDClickEvent(wxMouseEvent& event)
{
wxPoint position = event.GetPosition();
CalcUnscrolledPosition(position.x, position.y, &position.x, &position.y);
m_selection.SetPosition(position);
m_selection.SetBottomRight(position);
const uint32 line = position.y / m_line_height;
OnMouseDClick(position, line);
}
void TextList::OnContextMenu(wxContextMenuEvent& event)
{
wxPoint position = event.GetPosition();
if (position == wxDefaultPosition)
return;
wxPoint clientPosition = ScreenToClient(position);
CalcUnscrolledPosition(clientPosition.x, clientPosition.y, &clientPosition.x, &clientPosition.y);
m_selection.SetPosition(clientPosition);
m_selection.SetBottomRight(clientPosition);
const uint32 line = clientPosition.y / m_line_height;
OnContextMenu(clientPosition, line);
}
void TextList::OnTooltipTimer(wxTimerEvent& event)
{
m_tooltip_window->Hide();
const auto cursor_position = wxGetMousePosition();
const auto position = GetScreenPosition();
if (cursor_position.x < position.x || cursor_position.y < position.y)
return;
const auto size = GetSize();
if (position.x + size.x < cursor_position.x || position.y + size.y < cursor_position.y)
return;
const sint32 line = position.y / m_line_height;
if(OnShowTooltip(m_mouse_position, line))
{
m_tooltip_window->SetPosition(wxPoint(m_mouse_position.x + 15, m_mouse_position.y + 15));
m_tooltip_window->SendSizeEvent();
m_tooltip_window->Show();
}
}
void TextList::OnPaintEvent(wxPaintEvent& event)
{
wxAutoBufferedPaintDC dc(m_targetWindow);
// get window position
auto position = GetPosition();
// get current real position
wxRect rect_update = GetUpdateRegion().GetBox();
const auto count = (uint32)std::ceil((float)rect_update.GetHeight() / m_line_height);
position.y = (rect_update.y / m_line_height) * m_line_height;
// paint background
dc.SetFont(m_font);
const wxColour window_colour = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
dc.SetBrush(GetBackgroundColour());
dc.SetPen(*wxTRANSPARENT_PEN);
dc.DrawRectangle(rect_update);
//// paint selection
//if (!m_selected_text.eof())
//{
// dc.SetBrush(*wxBLUE_BRUSH);
// dc.SetPen(*wxBLUE_PEN);
// dc.DrawRectangle(m_selection);
//}
sint32 start;
CalcUnscrolledPosition(rect_update.x, rect_update.y, nullptr, &start);
start /= m_line_height;
m_scrolled_to_end = (start + count) >= m_element_count;
OnDraw(dc, start, count, position);
// removed Update() here since all text is white
}
-73
View File
@@ -1,73 +0,0 @@
#pragma once
#include <wx/wx.h>
#include <unordered_map>
#include <sstream>
class TextList : public wxControl, public wxScrollHelper
{
public:
virtual ~TextList();
TextList(wxWindow* parent, wxWindowID id,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize, long style = 0);
void RefreshControl(const wxRect* update_region = nullptr);
void RefreshLine(uint32 line);
wxSize DoGetVirtualSize() const override;
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override;
void SetScrollPos(int orient, int pos, bool refresh) override;
void DrawLineBackground(wxDC& dc, uint32 line, const wxColour& colour, uint32 lines = 1) const;
void DrawLineBackground(wxDC& dc, const wxPoint& position, const wxColour& colour, uint32 lines = 1) const;
bool SetElementCount(size_t element_count);
uint32 GetElementCount() const;
bool IsKeyDown(sint32 keycode);
protected:
void WriteText(wxDC& dc, const wxString& text, wxPoint& position) const;
void WriteText(wxDC& dc, const wxString& text, wxPoint& position, const wxColour& color) const;
void NextLine(wxPoint& position, const wxPoint* start_position = nullptr) const;
virtual void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) {};
virtual void OnMouseMove(const wxPoint& position, uint32 line) {}
virtual void OnKeyPressed(sint32 key_code, const wxPoint& position) {}
virtual void OnMouseDown();
virtual void OnMouseUp();
virtual void OnMouseDClick(const wxPoint& position, uint32 line) {}
virtual void OnContextMenu(const wxPoint& position, uint32 line) {}
virtual bool OnShowTooltip(const wxPoint& position, uint32 line);
void OnEraseBackground(wxEraseEvent&) {}
sint32 m_line_height;
sint32 m_char_width;
size_t m_elements_visible = 0;
size_t m_element_count = 0;
bool m_scrolled_to_end = true;
std::wstringstream m_selected_text;
bool m_mouse_down = false;
wxRect m_selection;
wxPanel* m_tooltip_window;
private:
void OnPaintEvent(wxPaintEvent& event);
void OnMouseMoveEvent(wxMouseEvent& event);
void OnKeyDownEvent(wxKeyEvent& event);
void OnKeyUpEvent(wxKeyEvent& event);
void OnMouseDownEvent(wxMouseEvent& event);
void OnMouseUpEvent(wxMouseEvent& event);
void OnMouseDClickEvent(wxMouseEvent& event);
void OnContextMenu(wxContextMenuEvent& event);
void OnTooltipTimer(wxTimerEvent& event);
std::unordered_map<sint32, bool> m_key_states;
wxFont m_font;
wxTimer* m_tooltip_timer;
wxPoint m_mouse_position;
};
@@ -1,813 +0,0 @@
#include "wxgui/components/wxDownloadManagerList.h"
#include "wxHelper.h"
#include "wxgui/helpers/wxHelpers.h"
#include "util/helpers/SystemException.h"
#include "Cafe/TitleList/GameInfo.h"
#include "wxgui/components/wxGameList.h"
#include "wxgui/helpers/wxCustomEvents.h"
#include <wx/wupdlock.h>
#include <wx/menu.h>
#include <wx/msgdlg.h>
#include <wx/stattext.h>
#include <wx/sizer.h>
#include <wx/timer.h>
#include <wx/panel.h>
#include <functional>
#include "config/ActiveSettings.h"
#include "wxgui/ChecksumTool.h"
#include "Cemu/Tools/DownloadManager/DownloadManager.h"
#include "Cafe/TitleList/TitleId.h"
#include "wxgui/MainWindow.h"
wxDEFINE_EVENT(wxEVT_REMOVE_ENTRY, wxCommandEvent);
wxDownloadManagerList::wxDownloadManagerList(wxWindow* parent, wxWindowID id)
: wxListView(parent, id, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_VIRTUAL)
{
AddColumns();
// tooltip TODO: extract class mb wxPanelTooltip
m_tooltip_window = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER);
auto* tooltip_sizer = new wxBoxSizer(wxVERTICAL);
m_tooltip_text = new wxStaticText(m_tooltip_window, wxID_ANY, wxEmptyString);
tooltip_sizer->Add(m_tooltip_text , 0, wxALL, 5);
m_tooltip_window->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK));
m_tooltip_window->SetSizerAndFit(tooltip_sizer);
m_tooltip_window->Hide();
m_tooltip_timer = new wxTimer(this);
Bind(wxEVT_LIST_COL_CLICK, &wxDownloadManagerList::OnColumnClick, this);
Bind(wxEVT_CONTEXT_MENU, &wxDownloadManagerList::OnContextMenu, this);
Bind(wxEVT_LIST_ITEM_SELECTED, &wxDownloadManagerList::OnItemSelected, this);
Bind(wxEVT_TIMER, &wxDownloadManagerList::OnTimer, this);
Bind(wxEVT_REMOVE_ITEM, &wxDownloadManagerList::OnRemoveItem, this);
Bind(wxEVT_REMOVE_ENTRY, &wxDownloadManagerList::OnRemoveEntry, this);
Bind(wxEVT_CLOSE_WINDOW, &wxDownloadManagerList::OnClose, this);
ShowSortIndicator(ColumnName);
}
boost::optional<const wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetSelectedTitleEntry() const
{
const auto selection = GetFirstSelected();
if (selection != wxNOT_FOUND)
{
const auto tmp = GetTitleEntry(selection);
if (tmp.has_value())
return tmp.value();
}
return {};
}
boost::optional<wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetSelectedTitleEntry()
{
const auto selection = GetFirstSelected();
if (selection != wxNOT_FOUND)
{
const auto tmp = GetTitleEntry(selection);
if (tmp.has_value())
return tmp.value();
}
return {};
}
boost::optional<wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetTitleEntry(uint64 titleId, uint16 titleVersion)
{
for(const auto& v : m_data)
{
if (v->entry.titleId == titleId && v->entry.version == titleVersion)
return v->entry;
}
return {};
}
void wxDownloadManagerList::AddColumns()
{
wxListItem col0;
col0.SetId(ColumnTitleId);
col0.SetText(_("Title ID"));
col0.SetWidth(120);
InsertColumn(ColumnTitleId, col0);
wxListItem col1;
col1.SetId(ColumnName);
col1.SetText(_("Name"));
col1.SetWidth(260);
InsertColumn(ColumnName, col1);
wxListItem col2;
col2.SetId(ColumnVersion);
col2.SetText(_("Version"));
col2.SetWidth(55);
InsertColumn(ColumnVersion, col2);
wxListItem col3;
col3.SetId(ColumnType);
col3.SetText(_("Type"));
col3.SetWidth(60);
InsertColumn(ColumnType, col3);
wxListItem col4;
col4.SetId(ColumnProgress);
col4.SetText(_("Progress"));
col4.SetWidth(wxLIST_AUTOSIZE_USEHEADER);
InsertColumn(ColumnProgress, col4);
wxListItem col5;
col5.SetId(ColumnStatus);
col5.SetText(_("Status"));
col5.SetWidth(240);
InsertColumn(ColumnStatus, col5);
}
wxString wxDownloadManagerList::OnGetItemText(long item, long column) const
{
if (item >= GetItemCount())
return wxEmptyString;
const auto entry = GetTitleEntry(item);
if (entry.has_value())
return GetTitleEntryText(entry.value(), (ItemColumn)column);
return wxEmptyString;
}
wxItemAttr* wxDownloadManagerList::OnGetItemAttr(long item) const
{
const auto entry = GetTitleEntry(item);
const wxColour bgColour = GetBackgroundColour();
const bool isDarkTheme = wxSystemSettings::GetAppearance().IsDark();
if (entry.has_value())
{
auto& entryData = entry.value();
if (entryData.status == TitleDownloadStatus::Downloading ||
entryData.status == TitleDownloadStatus::Verifying ||
entryData.status == TitleDownloadStatus::Installing)
{
const wxColour kActiveColor = isDarkTheme ? wxColour(80, 40, 40) : wxColour(0xFFE0E0);
static wxListItemAttr s_active_attr;
s_active_attr.SetBackgroundColour(kActiveColor);
s_active_attr.SetTextColour(GetTextColour());
s_active_attr.SetFont(GetFont());
return &s_active_attr;
}
else if (entryData.status == TitleDownloadStatus::Installed && entryData.isPackage)
{
const wxColour kActiveColor = isDarkTheme ? wxColour(40, 80, 40) : wxColour(0xE0FFE0);
static wxListItemAttr s_installed_attr;
s_installed_attr.SetBackgroundColour(kActiveColor);
s_installed_attr.SetTextColour(GetTextColour());
s_installed_attr.SetFont(GetFont());
return &s_installed_attr;
}
else if (entryData.status == TitleDownloadStatus::Error)
{
const wxColour kActiveColor = isDarkTheme ? wxColour(40, 40, 80) : wxColour(0xCCCCF2);
static wxListItemAttr s_error_attr;
s_error_attr.SetBackgroundColour(kActiveColor);
s_error_attr.SetTextColour(GetTextColour());
s_error_attr.SetFont(GetFont());
return &s_error_attr;
}
}
wxColour bgColourSecondary = wxHelper::CalculateAccentColour(bgColour);
static wxListItemAttr s_coloured_attr;
s_coloured_attr.SetBackgroundColour(bgColourSecondary);
s_coloured_attr.SetTextColour(GetTextColour());
s_coloured_attr.SetFont(GetFont());
return item % 2 == 0 ? nullptr : &s_coloured_attr;
}
boost::optional<wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetTitleEntry(long item)
{
long counter = 0;
for (const auto& data : m_sorted_data)
{
if (!data.get().visible)
continue;
if (item != counter++)
continue;
return data.get().entry;
}
return {};
}
boost::optional<const wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetTitleEntry(long item) const
{
long counter = 0;
for (const auto& data : m_sorted_data)
{
if (!data.get().visible)
continue;
if (item != counter++)
continue;
return data.get().entry;
}
return {};
}
void wxDownloadManagerList::OnClose(wxCloseEvent& event)
{
event.Skip();
// wait until all tasks are complete
if (m_context_worker.valid())
m_context_worker.get();
g_mainFrame->RequestGameListRefresh(); // todo: add games instead of fully refreshing game list if a game is downloaded
}
void wxDownloadManagerList::OnColumnClick(wxListEvent& event)
{
const int column = event.GetColumn();
SortEntries(column);
event.Skip();
}
void wxDownloadManagerList::RemoveItem(long item)
{
const int item_count = GetItemCount();
const ItemData* ref = nullptr;
long counter = 0;
for(auto it = m_sorted_data.begin(); it != m_sorted_data.end(); ++it)
{
if (!it->get().visible)
continue;
if (item != counter++)
continue;
ref = &(it->get());
m_sorted_data.erase(it);
break;
}
// shouldn't happen
if (ref == nullptr)
return;
for(auto it = m_data.begin(); it != m_data.end(); ++it)
{
if (ref != (*it).get())
continue;
m_data.erase(it);
break;
}
SetItemCount(std::max(0, item_count - 1));
RefreshPage();
}
void wxDownloadManagerList::RemoveItem(const TitleEntry& entry)
{
const int item_count = GetItemCount();
const TitleEntry* ref = &entry;
for (auto it = m_sorted_data.begin(); it != m_sorted_data.end(); ++it)
{
if (ref != &it->get().entry)
continue;
m_sorted_data.erase(it);
break;
}
for (auto it = m_data.begin(); it != m_data.end(); ++it)
{
if (ref != &(*it).get()->entry)
continue;
m_data.erase(it);
break;
}
SetItemCount(std::max(0, item_count - 1));
RefreshPage();
}
void wxDownloadManagerList::OnItemSelected(wxListEvent& event)
{
event.Skip();
m_tooltip_timer->Stop();
const auto selection = event.GetIndex();
if (selection == wxNOT_FOUND)
{
m_tooltip_window->Hide();
return;
}
}
enum ContextMenuEntries
{
kContextMenuRetry = wxID_HIGHEST + 1,
kContextMenuDownload,
kContextMenuPause,
kContextMenuResume,
};
void wxDownloadManagerList::OnContextMenu(wxContextMenuEvent& event)
{
// still doing work
if (m_context_worker.valid() && !future_is_ready(m_context_worker))
return;
wxMenu menu;
menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &wxDownloadManagerList::OnContextMenuSelected, this);
const auto selection = GetFirstSelected();
if (selection == wxNOT_FOUND)
return;
const auto entry = GetTitleEntry(selection);
if (!entry.has_value())
return;
if (entry->isPaused)
menu.Append(kContextMenuResume, _("&Resume"));
else if (entry->status == TitleDownloadStatus::Error)
menu.Append(kContextMenuRetry, _("&Retry"));
else if(entry->status == TitleDownloadStatus::Available)
menu.Append(kContextMenuDownload, _("&Download"));
//else if(entry->status == TitleDownloadStatus::Downloading || entry->status == TitleDownloadStatus::Initializing)
// menu.Append(kContextMenuPause, _("&Pause")); buggy!
PopupMenu(&menu);
// TODO: fix tooltip position
}
void wxDownloadManagerList::SetCurrentDownloadMgr(DownloadManager* dlMgr)
{
std::unique_lock<std::mutex> _l(m_dlMgrMutex);
m_dlMgr = dlMgr;
}
bool wxDownloadManagerList::StartDownloadEntry(const TitleEntry& entry)
{
std::unique_lock<std::mutex> _l(m_dlMgrMutex);
if (m_dlMgr)
m_dlMgr->initiateDownload(entry.titleId, entry.version);
return true;
}
bool wxDownloadManagerList::RetryDownloadEntry(const TitleEntry& entry)
{
StartDownloadEntry(entry);
return true;
}
bool wxDownloadManagerList::PauseDownloadEntry(const TitleEntry& entry)
{
std::unique_lock<std::mutex> _l(m_dlMgrMutex);
if (m_dlMgr)
m_dlMgr->pauseDownload(entry.titleId, entry.version);
return true;
}
void wxDownloadManagerList::OnContextMenuSelected(wxCommandEvent& event)
{
// still doing work
if (m_context_worker.valid() && !future_is_ready(m_context_worker))
return;
const auto selection = GetFirstSelected();
if (selection == wxNOT_FOUND)
return;
const auto entry = GetTitleEntry(selection);
if (!entry.has_value())
return;
switch (event.GetId())
{
case kContextMenuDownload:
StartDownloadEntry(entry.value());
break;
case kContextMenuRetry:
RetryDownloadEntry(entry.value());
break;
case kContextMenuResume:
StartDownloadEntry(entry.value());
break;
case kContextMenuPause:
PauseDownloadEntry(entry.value());
break;
}
}
void wxDownloadManagerList::OnTimer(wxTimerEvent& event)
{
if(event.GetTimer().GetId() != m_tooltip_timer->GetId())
{
event.Skip();
return;
}
m_tooltip_window->Show();
}
void wxDownloadManagerList::OnRemoveItem(wxCommandEvent& event)
{
RemoveItem(event.GetInt());
}
void wxDownloadManagerList::OnRemoveEntry(wxCommandEvent& event)
{
wxASSERT(event.GetClientData() != nullptr);
RemoveItem(*(TitleEntry*)event.GetClientData());
}
wxString wxDownloadManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColumn column)
{
switch (column)
{
case ColumnTitleId:
return formatWxString("{:08x}-{:08x}", (uint32) (entry.titleId >> 32), (uint32) (entry.titleId & 0xFFFFFFFF));
case ColumnName:
return entry.name;
case ColumnType:
return GetTranslatedTitleEntryType(entry.type);
case ColumnVersion:
{
// dont show version for base game unless it is not v0
if (entry.type == EntryType::Base && entry.version == 0)
return "";
if (entry.type == EntryType::DLC && entry.version == 0)
return "";
return formatWxString("v{}", entry.version);
}
case ColumnProgress:
{
if (entry.status == TitleDownloadStatus::Downloading)
{
if (entry.progress >= 1000)
return "100%";
return formatWxString("{:.1f}%", (float) entry.progress / 10.0f); // one decimal
}
else if (entry.status == TitleDownloadStatus::Installing || entry.status == TitleDownloadStatus::Checking || entry.status == TitleDownloadStatus::Verifying)
{
return formatWxString("{0}/{1}", entry.progress, entry.progressMax); // number of processed files/content files
}
return "";
}
case ColumnStatus:
{
if (entry.isPaused)
return _("Paused");
else if (entry.status == TitleDownloadStatus::Available)
{
if (entry.progress == 1)
return _("Not installed (Partially downloaded)");
if (entry.progress == 2)
return _("Update available");
return _("Not installed");
}
else if (entry.status == TitleDownloadStatus::Initializing)
return _("Initializing");
else if (entry.status == TitleDownloadStatus::Checking)
return _("Checking");
else if (entry.status == TitleDownloadStatus::Queued)
return _("Queued");
else if (entry.status == TitleDownloadStatus::Downloading)
return _("Downloading");
else if (entry.status == TitleDownloadStatus::Verifying)
return _("Verifying");
else if (entry.status == TitleDownloadStatus::Installing)
return _("Installing");
else if (entry.status == TitleDownloadStatus::Installed)
return _("Installed");
else if (entry.status == TitleDownloadStatus::Error)
{
wxString errorStatusMsg = _("Error:");
errorStatusMsg.append(" ");
errorStatusMsg.append(entry.errorMsg);
return errorStatusMsg;
}
else
return "Unknown status";
}
}
return wxEmptyString;
}
wxString wxDownloadManagerList::GetTranslatedTitleEntryType(EntryType type)
{
switch (type)
{
case EntryType::Base:
return _("base");
case EntryType::Update:
return _("update");
case EntryType::DLC:
return _("DLC");
default:
return std::to_string(static_cast<std::underlying_type_t<EntryType>>(type));
}
}
void wxDownloadManagerList::AddOrUpdateTitle(TitleEntryData_t* obj)
{
const auto& data = obj->GetData();
// if already in list only update
auto entry = GetTitleEntry(data.titleId, data.version);
if (entry.has_value())
{
// update item
entry.value() = data;
RefreshPage(); // more efficient way to do this?
return;
}
m_data.emplace_back(std::make_unique<ItemData>(true, data));
m_sorted_data.emplace_back(*m_data[m_data.size() - 1]);
SetItemCount(m_data.size());
// reapply sort
Filter2(m_filterShowTitles, m_filterShowUpdates, m_filterShowInstalled);
SortEntries();
}
void wxDownloadManagerList::UpdateTitleStatusDepr(TitleEntryData_t* obj, const wxString& text)
{
const auto& data = obj->GetData();
const auto entry = GetTitleEntry(data.titleId, data.version);
// check if already added to list
if (!entry.has_value())
return;
// update gamelist text
for(size_t i = 0; i < m_sorted_data.size(); ++i)
{
if (m_sorted_data[i].get().entry == data)
{
SetItem(i, ColumnStatus, text);
return;
}
}
cemuLog_logDebug(LogType::Force, "cant update title status of {:x}", data.titleId);
}
int wxDownloadManagerList::AddImage(const wxImage& image) const
{
return -1; // m_image_list->Add(image.Scale(kListIconWidth, kListIconWidth, wxIMAGE_QUALITY_BICUBIC));
}
// return <
bool wxDownloadManagerList::SortFunc(std::span<int> sortColumnOrder, const Type_t& v1, const Type_t& v2)
{
cemu_assert_debug(sortColumnOrder.size() == 5);
// visible have always priority
if (!v1.get().visible && v2.get().visible)
return false;
else if (v1.get().visible && !v2.get().visible)
return true;
const auto& entry1 = v1.get().entry;
const auto& entry2 = v2.get().entry;
for (size_t i = 0; i < sortColumnOrder.size(); i++)
{
int sortByColumn = sortColumnOrder[i];
if (sortByColumn == ColumnTitleId)
{
// ensure strong ordering -> use type since only one entry should be now (should be changed if every save for every user is displayed spearately?)
if (entry1.titleId != entry2.titleId)
return entry1.titleId < entry2.titleId;
}
else if (sortByColumn == ColumnName)
{
const int tmp = entry1.name.CmpNoCase(entry2.name);
if (tmp != 0)
return tmp < 0;
}
else if (sortByColumn == ColumnType)
{
if (std::underlying_type_t<EntryType>(entry1.type) != std::underlying_type_t<EntryType>(entry2.type))
return std::underlying_type_t<EntryType>(entry1.type) < std::underlying_type_t<EntryType>(entry2.type);
}
else if (sortByColumn == ColumnVersion)
{
if (entry1.version != entry2.version)
return entry1.version < entry2.version;
}
else if (sortByColumn == ColumnProgress)
{
if (entry1.progress != entry2.progress)
return entry1.progress < entry2.progress;
}
else
{
cemu_assert_debug(false);
return (uintptr_t)&entry1 < (uintptr_t)&entry2;
}
}
return false;
}
#include <boost/container/small_vector.hpp>
void wxDownloadManagerList::SortEntries(int column)
{
boost::container::small_vector<int, 12> s_SortColumnOrder{ ColumnName, ColumnType, ColumnVersion, ColumnTitleId, ColumnProgress };
bool ascending;
if (column == -1)
{
column = GetSortIndicator();
if (column == -1)
column = ColumnName;
ascending = IsAscendingSortIndicator();
}
else
ascending = GetUpdatedAscendingSortIndicator(column);
// prioritize column by moving it to first position in the column sort order list
s_SortColumnOrder.erase(std::remove(s_SortColumnOrder.begin(), s_SortColumnOrder.end(), column), s_SortColumnOrder.end());
s_SortColumnOrder.insert(s_SortColumnOrder.begin(), column);
std::sort(m_sorted_data.begin(), m_sorted_data.end(),
[this, &s_SortColumnOrder, ascending](const Type_t& v1, const Type_t& v2) -> bool {
return ascending ? SortFunc(s_SortColumnOrder, v1, v2) : SortFunc(s_SortColumnOrder, v2, v1);
});
ShowSortIndicator(column, ascending);
RefreshPage();
}
void wxDownloadManagerList::RefreshPage()
{
long item_count = GetItemCount();
if (item_count > 0)
RefreshItems(GetTopItem(), std::min(item_count - 1, GetTopItem() + GetCountPerPage() + 1));
}
int wxDownloadManagerList::Filter(const wxString& filter, const wxString& prefix, ItemColumn column)
{
if (prefix.empty())
return -1;
if (!filter.StartsWith(prefix))
return -1;
int counter = 0;
const auto tmp_filter = filter.substr(prefix.size() - 1).Trim(false);
for (auto&& data : m_data)
{
if (GetTitleEntryText(data->entry, column).Upper().Contains(tmp_filter))
{
data->visible = true;
++counter;
}
else
data->visible = false;
}
return counter;
}
void wxDownloadManagerList::Filter(const wxString& filter)
{
if(filter.empty())
{
std::for_each(m_data.begin(), m_data.end(), [](ItemDataPtr& data) { data->visible = true; });
SetItemCount(m_data.size());
RefreshPage();
return;
}
const auto filter_upper = filter.Upper().Trim(false).Trim(true);
int counter = 0;
if (const auto result = Filter(filter_upper, "TITLEID:", ColumnTitleId) != -1)
counter = result;
else if (const auto result = Filter(filter_upper, "NAME:", ColumnName) != -1)
counter = result;
else if (const auto result = Filter(filter_upper, "TYPE:", ColumnType) != -1)
counter = result;
//else if (const auto result = Filter(filter_upper, "REGION:", ColumnRegion) != -1)
// counter = result;
else if (const auto result = Filter(filter_upper, "VERSION:", ColumnVersion) != -1)
counter = result;
else if(filter_upper == "ERROR")
{
for (auto&& data : m_data)
{
bool visible = false;
//if (data->entry.error != TitleError::None)
// visible = true;
data->visible = visible;
if (visible)
++counter;
}
}
else
{
for (auto&& data : m_data)
{
bool visible = false;
if (data->entry.name.Upper().Contains(filter_upper))
visible = true;
else if (GetTitleEntryText(data->entry, ColumnTitleId).Upper().Contains(filter_upper))
visible = true;
else if (GetTitleEntryText(data->entry, ColumnType).Upper().Contains(filter_upper))
visible = true;
data->visible = visible;
if (visible)
++counter;
}
}
SetItemCount(counter);
RefreshPage();
}
void wxDownloadManagerList::Filter2(bool showTitles, bool showUpdates, bool showInstalled)
{
m_filterShowTitles = showTitles;
m_filterShowUpdates = showUpdates;
m_filterShowInstalled = showInstalled;
if (showTitles && showUpdates && showInstalled)
{
std::for_each(m_data.begin(), m_data.end(), [](ItemDataPtr& data) { data->visible = true; });
SetItemCount(m_data.size());
RefreshPage();
return;
}
size_t counter = 0;
for (auto&& data : m_data)
{
bool visible = false;
TitleIdParser tParser(data->entry.titleId);
bool isInstalled = data->entry.status == TitleDownloadStatus::Installed;
if (tParser.IsBaseTitleUpdate())
{
if (showUpdates && (showInstalled || !isInstalled))
visible = true;
}
else
{
if (showTitles && (showInstalled || !isInstalled))
visible = true;
}
data->visible = visible;
if (visible)
++counter;
}
SetItemCount(counter);
RefreshPage();
}
size_t wxDownloadManagerList::GetCountByType(EntryType type) const
{
size_t result = 0;
for(const auto& data : m_data)
{
if (data->entry.type == type)
++result;
}
return result;
}
void wxDownloadManagerList::ClearItems()
{
m_sorted_data.clear();
m_data.clear();
SetItemCount(0);
RefreshPage();
}
void wxDownloadManagerList::AutosizeColumns()
{
wxAutosizeColumns(this, ColumnTitleId, ColumnMAX - 1);
}
@@ -1,152 +0,0 @@
#pragma once
#include "wxgui/helpers/wxCustomData.h"
#include "config/CemuConfig.h"
#include <wx/listctrl.h>
#include <boost/optional.hpp> // std::optional doesn't support optional reference inner types yet
#include <utility>
#include <vector>
class wxDownloadManagerList : public wxListView
{
friend class TitleManager;
public:
wxDownloadManagerList(wxWindow* parent, wxWindowID id = wxID_ANY);
enum ItemColumn
{
ColumnTitleId = 0,
ColumnName,
ColumnVersion,
ColumnType,
ColumnProgress,
ColumnStatus,
ColumnMAX,
};
enum class EntryType
{
Base,
Update,
DLC,
};
enum class TitleDownloadStatus
{
None,
Available, // available for download
Error, // error state
Queued, // queued for download
Initializing, // downloading/parsing TMD
Checking, // checking for previously downloaded files
Downloading,
Verifying, // verifying downloaded files
Installing,
Installed,
// error state?
};
void SortEntries(int column = -1);
void RefreshPage();
void Filter(const wxString& filter);
void Filter2(bool showTitles, bool showUpdates, bool showInstalled);
[[nodiscard]] size_t GetCountByType(EntryType type) const;
void ClearItems();
void AutosizeColumns();
struct TitleEntry
{
TitleEntry(const EntryType& type, bool isPackage, uint64 titleId, uint16 version, bool isPaused)
: type(type), isPackage(isPackage), titleId(titleId), version(version), isPaused(isPaused) {}
EntryType type;
bool isPaused{};
int icon = -1;
bool isPackage;
uint64 titleId;
wxString name;
uint32_t version{ 0 };
uint32 progress; // downloading: in 1/10th of a percent, installing: number of files
uint32 progressMax{ 0 };
CafeConsoleRegion region;
TitleDownloadStatus status = TitleDownloadStatus::None;
std::string errorMsg;
bool operator==(const TitleEntry& e) const
{
return type == e.type && titleId == e.titleId && version == e.version;
}
bool operator!=(const TitleEntry& e) const { return !(*this == e); }
};
boost::optional<const TitleEntry&> GetSelectedTitleEntry() const;
private:
void AddColumns();
int Filter(const wxString& filter, const wxString& prefix, ItemColumn column);
boost::optional<TitleEntry&> GetSelectedTitleEntry();
boost::optional<TitleEntry&> GetTitleEntry(uint64 titleId, uint16 titleVersion);
class wxPanel* m_tooltip_window;
class wxStaticText* m_tooltip_text;
class wxTimer* m_tooltip_timer;
void OnClose(wxCloseEvent& event);
void OnColumnClick(wxListEvent& event);
void OnContextMenu(wxContextMenuEvent& event);
void OnItemSelected(wxListEvent& event);
void OnContextMenuSelected(wxCommandEvent& event);
void OnTimer(class wxTimerEvent& event);
void OnRemoveItem(wxCommandEvent& event);
void OnRemoveEntry(wxCommandEvent& event);
using TitleEntryData_t = wxCustomData<TitleEntry>;
void AddOrUpdateTitle(TitleEntryData_t* obj);
void UpdateTitleStatusDepr(TitleEntryData_t* obj, const wxString& text);
int AddImage(const wxImage& image) const;
wxString OnGetItemText(long item, long column) const override;
wxItemAttr* OnGetItemAttr(long item) const override;
[[nodiscard]] boost::optional<const TitleEntry&> GetTitleEntry(long item) const;
[[nodiscard]] boost::optional<TitleEntry&> GetTitleEntry(long item);
//[[nodiscard]] boost::optional<const TitleEntry&> GetTitleEntry(const fs::path& path) const;
//[[nodiscard]] boost::optional<TitleEntry&> GetTitleEntry(const fs::path& path);
void SetCurrentDownloadMgr(class DownloadManager* dlMgr);
//bool FixEntry(TitleEntry& entry);
//bool VerifyEntryFiles(TitleEntry& entry);
bool StartDownloadEntry(const TitleEntry& entry);
bool RetryDownloadEntry(const TitleEntry& entry);
bool PauseDownloadEntry(const TitleEntry& entry);
void RemoveItem(long item);
void RemoveItem(const TitleEntry& entry);
struct ItemData
{
ItemData(bool visible, const TitleEntry& entry)
: visible(visible), entry(entry) {}
bool visible;
TitleEntry entry;
};
using ItemDataPtr = std::unique_ptr<ItemData>;
std::vector<ItemDataPtr> m_data;
std::vector<std::reference_wrapper<ItemData>> m_sorted_data;
bool m_filterShowTitles = true;
bool m_filterShowUpdates = true;
bool m_filterShowInstalled = true;
DownloadManager* m_dlMgr{};
std::mutex m_dlMgrMutex;
using Type_t = std::reference_wrapper<const ItemData>;
bool SortFunc(std::span<int> sortColumnOrder, const Type_t& v1, const Type_t& v2);
static wxString GetTitleEntryText(const TitleEntry& entry, ItemColumn column);
static wxString GetTranslatedTitleEntryType(EntryType entryType);
std::future<bool> m_context_worker;
};
File diff suppressed because it is too large. Load diff
-166
View File
@@ -1,166 +0,0 @@
#pragma once
#include "config/CemuConfig.h"
#include "Cafe/TitleList/TitleId.h"
#include <wx/listctrl.h>
#include <wx/timer.h>
#include <wx/panel.h>
#include <wx/settings.h>
#include <Cafe/TitleList/GameInfo.h>
#include "wxHelper.h"
#include "util/helpers/Semaphore.h"
class wxTitleIdEvent : public wxCommandEvent
{
public:
wxTitleIdEvent(int type, uint64 title_id)
: wxCommandEvent(type), m_title_id(title_id) {}
[[nodiscard]] uint64 GetTitleId() const { return m_title_id; }
private:
uint64 m_title_id;
};
wxDECLARE_EVENT(wxEVT_OPEN_SETTINGS, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_OPEN_GRAPHIC_PACK, wxTitleIdEvent);
wxDECLARE_EVENT(wxEVT_GAMELIST_BEGIN_UPDATE, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_GAMELIST_END_UPDATE, wxCommandEvent);
class wxGameList : public wxListView
{
friend class MainWindow;
public:
enum class Style
{
kList,
kIcons,
kSmallIcons
};
wxGameList(wxWindow* parent, wxWindowID id = wxID_ANY);
~wxGameList();
void SetStyle(Style style, bool save = true);
void LoadConfig();
void SaveConfig(bool flush = false);
bool IsVisible(long item) const; // only available in wxwidgets 3.1.3
void ReloadGameEntries();
void DeleteCachedStrings();
void CreateShortcut(GameInfo2& gameInfo);
long FindListItemByTitleId(uint64 title_id) const;
void OnClose(wxCloseEvent& event);
private:
std::atomic_bool m_exit = false;
Style m_style;
long GetStyleFlags(Style style) const;
const wxColour kUpdateColor{ wxSystemSettings::SelectLightDark(wxColour(195, 57, 57), wxColour(84, 29, 29)) };
const wxColour kFavoriteColor{ wxSystemSettings::SelectLightDark(wxColour(253, 246, 211), wxColour(82, 84, 48)) };
const wxColour kPrimaryColor = GetBackgroundColour();
const wxColour kAlternateColor = wxHelper::CalculateAccentColour(kPrimaryColor);
void UpdateItemColors(sint32 startIndex = 0);
enum ItemColumns : int
{
ColumnHiddenName = 0,
ColumnIcon,
ColumnName,
ColumnVersion,
ColumnDLC,
ColumnGameTime,
ColumnGameStarted,
ColumnRegion,
ColumnTitleID,
//ColumnFavorite,
ColumnCounts,
};
void SortEntries(int column = -1);
struct SortData
{
wxGameList* thisptr;
ItemColumns column;
int dir;
};
int FindInsertPosition(TitleId titleId, bool& entryAlreadyExists);
std::weak_ordering SortComparator(uint64 titleId1, uint64 titleId2, SortData* sortData);
static int SortFunction(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData);
wxTimer* m_tooltip_timer;
wxToolTip* m_tool_tip;
wxPanel* m_tooltip_window;
wxPoint m_mouse_position;
std::mutex m_async_worker_mutex;
std::thread m_async_worker_thread;
CounterSemaphore m_async_task_count;
std::atomic_bool m_async_worker_active{false};
std::vector<TitleId> m_icon_load_queue;
uint64 m_callbackIdTitleList;
std::string GetNameByTitleId(uint64 titleId);
void HandleTitleListCallback(struct CafeTitleListCallbackEvent* evt);
void RemoveCache(const std::vector<fs::path>& cachePath, const std::string& titleName);
void AsyncWorkerThread();
void RequestLoadIconAsync(TitleId titleId);
bool QueryIconForTitle(TitleId titleId, int& icon, int& iconSmall);
inline static constexpr int kListIconWidth = 64;
inline static constexpr int kIconWidth = 128;
wxImageList m_image_list_data = wxImageList(kIconWidth, kIconWidth, false, 1);
wxImageList m_image_list_small_data = wxImageList(kListIconWidth, kListIconWidth, false, 1);
std::mutex m_icon_cache_mtx;
std::set<TitleId> m_icon_loaded;
std::map<TitleId, std::pair<int, int>> m_icon_cache; // pair contains icon and small icon
std::map<TitleId, std::string> m_name_cache;
// bulk update handling
std::vector<TitleId> m_bulkTitlesToAdd;
wxTimer m_bulkUpdateTimer;
// list mode
void CreateListColumns();
void OnColumnClick(wxListEvent& event);
void OnColumnRightClick(wxListEvent& event);
void ApplyGameListColumnWidths();
void OnColumnBeginResize(wxListEvent& event);
void OnColumnResize(wxListEvent& event);
void OnColumnDrag(wxListEvent& event);
// generic events
void OnKeyDown(wxListEvent& event);
void OnContextMenu(wxContextMenuEvent& event);
void OnContextMenuSelected(wxCommandEvent& event);
void OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event);
void OnItemActivated(wxListEvent& event);
void OnTimer(wxTimerEvent& event);
void OnMouseMove(wxMouseEvent& event);
void OnLeaveWindow(wxMouseEvent& event);
void OnGameListSize(wxSizeEvent& event);
void AdjustLastColumnWidth();
int GetColumnDefaultWidth(int column);
void OnTimerBulkAddEntriesToGameList(wxTimerEvent& event);
static inline std::once_flag s_launch_file_once;
};
-121
View File
@@ -1,121 +0,0 @@
#include "wxgui/components/wxInputDraw.h"
#include <wx/dcbuffer.h>
#include <wx/settings.h>
wxInputDraw::wxInputDraw(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size)
: wxWindow(parent, id, pos, size, 0, wxPanelNameStr)
{
SetBackgroundStyle(wxBG_STYLE_PAINT);
Bind(wxEVT_PAINT, &wxInputDraw::OnPaintEvent, this);
}
wxInputDraw::~wxInputDraw() { Unbind(wxEVT_PAINT, &wxInputDraw::OnPaintEvent, this); }
void wxInputDraw::OnPaintEvent(wxPaintEvent& event)
{
wxAutoBufferedPaintDC dc(this);
OnRender(dc);
}
void wxInputDraw::OnRender(wxDC& dc)
{
dc.Clear();
glm::vec2 position = m_position;
wxPen black = wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
wxPen red = *wxRED_PEN;
wxPen grey = wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT));
wxPen green = wxSystemSettings::SelectLightDark(0x336600, 0x99FF99);
wxBrush black_brush = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
wxBrush red_brush = *wxRED_BRUSH;
wxBrush grey_brush = wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT);
wxBrush green_brush = wxSystemSettings::SelectLightDark(0x336600, 0x99FF99);
if(!IsEnabled())
{
position = {};
black.SetColour(black.GetColour());
red.SetColour(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT));
grey.SetColour(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT).MakeDisabled());
black_brush = wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT);
red_brush = wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT).MakeDisabled();
grey_brush = wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT).MakeDisabled();
}
dc.SetBackgroundMode(wxSOLID);
dc.SetBackground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
dc.Clear();
const auto size = GetSize();
const auto min_size = (float)std::min(size.GetWidth(), size.GetHeight()) - 1.0f;
const Vector2f middle{min_size / 2.0f, min_size / 2.0f};
// border
const wxRect border{0, 0, (int)min_size, (int)min_size};
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT));
dc.DrawRectangle(border);
dc.SetPen(IsEnabled() ? green.GetColour() : grey.GetColour());
dc.DrawCircle((int)middle.x, (int)middle.y, (int)middle.x);
if (m_deadzone > 0)
{
dc.SetPen(grey);
dc.SetBrush(grey_brush);
const auto deadzone_size = m_deadzone * min_size / 2.0f;
dc.DrawCircle(
static_cast<int>(middle.x),
static_cast<int>(middle.x),
(int)deadzone_size);
if (length(position) >= m_deadzone)
{
dc.SetPen(red);
dc.SetBrush(red_brush);
if (std::abs(1.0f - length(position)) < 0.05f)
{
dc.SetPen(green);
dc.SetBrush(green_brush);
}
}
else
{
dc.SetPen(black);
dc.SetBrush(black_brush);
}
}
else
{
dc.SetPen(red);
dc.SetBrush(red_brush);
}
// draw axis
const wxPoint pos
{
static_cast<int>(middle.x + position.x * middle.x),
static_cast<int>(middle.y - position.y * middle.y)
};
dc.DrawCircle(pos.x, pos.y, 2);
dc.SetBrush(*wxTRANSPARENT_BRUSH);
}
void wxInputDraw::SetAxisValue(const glm::vec2& position)
{
m_position = position;
Refresh();
}
void wxInputDraw::SetDeadzone(float deadzone)
{
m_deadzone = deadzone;
Refresh();
}
-24
View File
@@ -1,24 +0,0 @@
#pragma once
#include "input/api/ControllerState.h"
#include "util/math/vector2.h"
#include <wx/window.h>
class wxInputDraw : public wxWindow
{
public:
wxInputDraw(wxWindow *parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize);
~wxInputDraw();
virtual void OnRender(wxDC& dc);
void SetAxisValue(const glm::vec2& position);
void SetDeadzone(float deadzone);
private:
void OnPaintEvent(wxPaintEvent& event);
glm::vec2 m_position{};
float m_deadzone{0};
};
-161
View File
@@ -1,161 +0,0 @@
#include "wxgui/components/wxLogCtrl.h"
#include <boost/algorithm/string.hpp>
wxDEFINE_EVENT(EVT_ON_LIST_UPDATED, wxEvent);
wxLogCtrl::wxLogCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, bool alternateRowColours)
: TextList(parent, id, pos, size, style)
{
m_timer = new wxTimer(this);
this->Bind(wxEVT_TIMER, &wxLogCtrl::OnTimer, this);
this->Bind(EVT_ON_LIST_UPDATED, &wxLogCtrl::OnActiveListUpdated, this);
m_timer->Start(250);
m_alternateRowColoursEnabled = alternateRowColours;
}
wxLogCtrl::~wxLogCtrl()
{
this->Unbind(wxEVT_TIMER, &wxLogCtrl::OnTimer, this);
this->Unbind(EVT_ON_LIST_UPDATED, &wxLogCtrl::OnActiveListUpdated, this);
if(m_timer)
m_timer->Stop();
if(m_update_worker.joinable())
m_update_worker.join();
}
void wxLogCtrl::SetActiveFilter(const std::string& active_filter)
{
if(m_active_filter == active_filter)
return;
m_active_filter = active_filter;
if(m_update_worker.joinable())
m_update_worker.join();
m_update_worker = std::thread(&wxLogCtrl::UpdateActiveEntries, this);
}
const std::string& wxLogCtrl::GetActiveFilter() const
{
return m_active_filter;
}
void wxLogCtrl::SetFilterMessage(bool state)
{
if(m_filter_messages == state)
return;
m_filter_messages = state;
if(m_update_worker.joinable())
m_update_worker.join();
m_update_worker = std::thread(&wxLogCtrl::UpdateActiveEntries, this);
}
bool wxLogCtrl::GetFilterMessage() const
{
return m_filter_messages;
}
void wxLogCtrl::PushEntry(const wxString& filter, const wxString& message)
{
std::unique_lock lock(m_mutex);
m_log_entries.emplace_back(filter, message);
ListIt_t it = m_log_entries.back();
lock.unlock();
if(m_active_filter.empty() || filter == m_active_filter || (m_filter_messages && boost::icontains(message.ToStdString(), m_active_filter)))
{
std::unique_lock active_lock(m_active_mutex);
m_active_entries.emplace_back(std::cref(it));
const auto entry_count = m_active_entries.size();
active_lock.unlock();
if(entry_count <= m_elements_visible)
{
RefreshLine(entry_count - 1);
}
}
}
void wxLogCtrl::OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position)
{
wxPoint position = start_position;
std::scoped_lock lock(m_active_mutex);
auto it = std::next(m_active_entries.cbegin(), start);
if(it == m_active_entries.cend())
return;
for (sint32 i = 0; i <= count && it != m_active_entries.cend(); ++i, ++it)
{
wxColour background_colour = GetBackgroundColour();
if((start + i) % 2 != 0 && m_alternateRowColoursEnabled)
background_colour = GetAlternateRowColour();
DrawLineBackground(dc, position, background_colour);
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
dc.DrawText(it->get().second, position);
NextLine(position, &start_position);
}
}
void wxLogCtrl::OnTimer(wxTimerEvent& event)
{
std::unique_lock lock(m_active_mutex);
const auto count = m_active_entries.size();
if(count == m_element_count)
return;
lock.unlock();
SetElementCount(count);
if(m_scrolled_to_end)
{
Scroll(0, count);
RefreshControl();
}
}
void wxLogCtrl::OnActiveListUpdated(wxEvent& event)
{
std::unique_lock lock(m_active_mutex);
const auto count = m_active_entries.size();
lock.unlock();
SetElementCount(count);
RefreshControl();
}
void wxLogCtrl::UpdateActiveEntries()
{
{
std::scoped_lock lock(m_mutex, m_active_mutex);
m_active_entries.clear();
if(m_active_filter.empty())
{
for (const auto& it : m_log_entries)
m_active_entries.emplace_back(it);
}
else
{
for (const auto& it : m_log_entries)
{
if(it.first == m_active_filter ||
(m_filter_messages && boost::icontains(it.second.ToStdString(), m_active_filter)) )
m_active_entries.emplace_back(it);
}
}
}
wxQueueEvent(this, new wxCommandEvent(EVT_ON_LIST_UPDATED));
}
-52
View File
@@ -1,52 +0,0 @@
#pragma once
#include "TextList.h"
class wxLogCtrl : public TextList
{
public:
wxLogCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, bool alternateRowColours = false);
~wxLogCtrl();
void SetActiveFilter(const std::string& active_filter);
[[nodiscard]] const std::string& GetActiveFilter() const;
void SetFilterMessage(bool state);
[[nodiscard]] bool GetFilterMessage() const;
void PushEntry(const wxString& filter, const wxString& message);
protected:
void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position);
private:
void OnTimer(wxTimerEvent& event);
void OnActiveListUpdated(wxEvent& event);
wxTimer* m_timer;
bool m_alternateRowColoursEnabled = false;
wxColour m_alternateRowColour;
wxColour GetAlternateRowColour()
{
if (m_alternateRowColour.IsOk())
return m_alternateRowColour;
// Depending on the background, alternate row colour should be a bit
// darker or brighter.
const wxColour bgColour = GetBackgroundColour();
int alpha = bgColour.GetRGB() > 0x808080 ? 97 : 110;
m_alternateRowColour = bgColour.ChangeLightness(alpha);
return m_alternateRowColour;
}
std::string m_active_filter;
std::thread m_update_worker;
bool m_filter_messages = false;
std::mutex m_mutex, m_active_mutex;
using ListEntry_t = std::pair<wxString, wxString>;
using ListIt_t = std::list<ListEntry_t>::const_reference;
std::list<ListEntry_t> m_log_entries;
std::list<std::reference_wrapper<const ListEntry_t>> m_active_entries;
void UpdateActiveEntries();
};
@@ -1,118 +0,0 @@
#pragma once
#include <wx/event.h>
#include <wx/progdlg.h>
#include <wx/thread.h>
#include "util/helpers/Semaphore.h"
wxDEFINE_EVENT(wxEVT_CREATE_PROGRESS_DIALOG, wxThreadEvent);
wxDEFINE_EVENT(wxEVT_DESTROY_PROGRESS_DIALOG, wxThreadEvent);
wxDEFINE_EVENT(wxEVT_UPDATE_PROGRESS_DIALOG, wxThreadEvent);
// wrapper for wxGenericProgressDialog which can be used from any thread
class wxProgressDialogManager : public wxEvtHandler
{
public:
wxProgressDialogManager(wxWindow* parent) : m_parent(parent), m_dialog(nullptr)
{
Bind(wxEVT_CREATE_PROGRESS_DIALOG, &wxProgressDialogManager::OnCreateProgressDialog, this);
Bind(wxEVT_DESTROY_PROGRESS_DIALOG, &wxProgressDialogManager::OnDestroyProgressDialog, this);
Bind(wxEVT_UPDATE_PROGRESS_DIALOG, &wxProgressDialogManager::OnUpdateProgressDialog, this);
}
~wxProgressDialogManager()
{
if (m_dialog)
Destroy();
}
void Create(const wxString& title, const wxString& message, int maximum, int style = wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_REMAINING_TIME)
{
m_instanceSemaphore.increment();
m_isCancelled = false;
m_isSkipped = false;
wxThreadEvent event(wxEVT_CREATE_PROGRESS_DIALOG);
event.SetString(title);
event.SetInt(maximum);
event.SetExtraLong(style);
wxQueueEvent(this, event.Clone());
}
void Destroy()
{
wxThreadEvent event(wxEVT_DESTROY_PROGRESS_DIALOG);
wxQueueEvent(this, event.Clone());
m_instanceSemaphore.waitUntilZero(); // wait until destruction is complete
}
// this also updates the cancel and skip state
void Update(int value, const wxString& newmsg = wxEmptyString)
{
wxThreadEvent event(wxEVT_UPDATE_PROGRESS_DIALOG);
event.SetInt(value);
event.SetString(newmsg);
wxQueueEvent(this, event.Clone());
}
bool IsCancelled() const
{
return m_isCancelled;
}
bool IsSkipped() const
{
return m_isSkipped;
}
bool IsCancelledOrSkipped() const
{
return m_isCancelled || m_isSkipped;
}
private:
void OnCreateProgressDialog(wxThreadEvent& event)
{
if (m_dialog)
{
m_dialog->Destroy();
m_instanceSemaphore.waitUntilZero();
}
m_maximum = event.GetInt();
m_dialog = new wxGenericProgressDialog(event.GetString(), "Please wait...", m_maximum, m_parent, event.GetExtraLong());
}
void OnDestroyProgressDialog(wxThreadEvent& event)
{
if (m_dialog)
{
m_dialog->Destroy();
m_dialog = nullptr;
m_instanceSemaphore.decrement();
}
}
void OnUpdateProgressDialog(wxThreadEvent& event)
{
if (m_dialog)
{
// make sure that progress is never >= maximum
// because wxGenericProgressDialog seems to become crashy on destruction otherwise
int progress = event.GetInt();
if(progress >= m_maximum)
progress = m_maximum - 1;
bool wasSkipped = false;
bool r = m_dialog->Update(progress, event.GetString(), &wasSkipped);
if(!r)
m_isCancelled = true;
if(wasSkipped)
m_isSkipped = true;
}
}
wxWindow* m_parent;
wxGenericProgressDialog* m_dialog;
bool m_isCancelled{false};
bool m_isSkipped{false};
int m_maximum{0};
CounterSemaphore m_instanceSemaphore; // used to synchronize destruction of the dialog
};
File diff suppressed because it is too large. Load diff
@@ -1,139 +0,0 @@
#pragma once
#include "wxgui/helpers/wxCustomData.h"
#include "config/CemuConfig.h"
#include <wx/listctrl.h>
#include <boost/optional.hpp> // std::optional doesn't support optional reference inner types yet
#include <utility>
#include <vector>
class wxTitleManagerList : public wxListView
{
friend class TitleManager;
public:
wxTitleManagerList(wxWindow* parent, wxWindowID id = wxID_ANY);
~wxTitleManagerList();
enum ItemColumn
{
ColumnTitleId = 0,
ColumnName,
ColumnType,
ColumnVersion,
ColumnRegion,
ColumnFormat,
ColumnLocation,
ColumnMAX,
};
enum class EntryType
{
Base,
Update,
Dlc,
Save,
System,
};
enum class EntryFormat
{
Folder,
WUD,
NUS,
WUA,
WUHB,
};
// sort by column, if -1 will sort by last column or default (=titleid)
void SortEntries(int column);
void RefreshPage();
void Filter(const wxString& filter);
[[nodiscard]] size_t GetCountByType(EntryType type) const;
void ClearItems();
void AutosizeColumns();
struct TitleEntry
{
TitleEntry(const EntryType& type, const EntryFormat& format, fs::path path)
: type(type), format(format), path(std::move(path)) {}
EntryType type;
EntryFormat format;
fs::path path;
int icon = -1;
uint64 location_uid;
uint64 title_id;
wxString name;
uint32_t version = 0;
CafeConsoleRegion region;
std::vector<uint32> persistent_ids; // only used for save
};
boost::optional<const TitleEntry&> GetSelectedTitleEntry() const;
private:
void AddColumns();
int Filter(const wxString& filter, const wxString& prefix, ItemColumn column);
boost::optional<TitleEntry&> GetSelectedTitleEntry();
boost::optional<TitleEntry&> GetTitleEntryByUID(uint64 uid);
class wxPanel* m_tooltip_window;
class wxStaticText* m_tooltip_text;
class wxTimer* m_tooltip_timer;
void OnClose(wxCloseEvent& event);
void OnColumnClick(wxListEvent& event);
void OnContextMenu(wxContextMenuEvent& event);
void OnItemSelected(wxListEvent& event);
void OnContextMenuSelected(wxCommandEvent& event);
void OnTimer(class wxTimerEvent& event);
void OnRemoveItem(wxCommandEvent& event);
void OnRemoveEntry(wxCommandEvent& event);
void OnTitleDiscovered(wxCommandEvent& event);
void OnTitleRemoved(wxCommandEvent& event);
void HandleTitleListCallback(struct CafeTitleListCallbackEvent* evt);
void HandleSaveListCallback(struct CafeSaveListCallbackEvent* evt);
using TitleEntryData_t = wxCustomData<TitleEntry>;
void AddTitle(TitleEntryData_t* obj);
int AddImage(const wxImage& image) const;
wxString OnGetItemText(long item, long column) const override;
wxItemAttr* OnGetItemAttr(long item) const override;
[[nodiscard]] boost::optional<const TitleEntry&> GetTitleEntry(long item) const;
[[nodiscard]] boost::optional<TitleEntry&> GetTitleEntry(long item);
[[nodiscard]] boost::optional<const TitleEntry&> GetTitleEntry(const fs::path& path) const;
[[nodiscard]] boost::optional<TitleEntry&> GetTitleEntry(const fs::path& path);
void OnConvertToCompressedFormat(uint64 titleId, uint64 rightClickedUID);
bool DeleteEntry(long index, const TitleEntry& entry);
void RemoveItem(long item);
void RemoveItem(const TitleEntry& entry);
struct ItemData
{
ItemData(bool visible, const TitleEntry& entry)
: visible(visible), entry(entry) {}
bool visible;
TitleEntry entry;
};
using ItemDataPtr = std::unique_ptr<ItemData>;
std::vector<ItemDataPtr> m_data;
std::vector<std::reference_wrapper<ItemData>> m_sorted_data;
using Type_t = std::reference_wrapper<const ItemData>;
bool SortFunc(int column, const Type_t& v1, const Type_t& v2);
static wxString GetTitleEntryText(const TitleEntry& entry, ItemColumn column);
static wxString GetTranslatedTitleEntryType(EntryType entryType);
std::future<bool> m_context_worker;
uint64 m_callbackIdTitleList;
uint64 m_callbackIdSaveList;
};
-321
View File
@@ -1,321 +0,0 @@
#include "wxgui/wxgui.h"
#include "wxgui/debugger/BreakpointWindow.h"
#include <sstream>
#include "wxgui/debugger/DebuggerWindow2.h"
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
#include "Cemu/ExpressionParser/ExpressionParser.h"
enum
{
MENU_ID_CREATE_CODE_BP_EXECUTION = 1,
MENU_ID_CREATE_CODE_BP_LOGGING,
MENU_ID_CREATE_MEM_BP_READ,
MENU_ID_CREATE_MEM_BP_WRITE,
MENU_ID_DELETE_BP,
};
enum ItemColumns
{
ColumnEnabled = 0,
ColumnAddress,
ColumnType,
ColumnComment,
};
BreakpointWindow::BreakpointWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size)
: wxFrame(&parent, wxID_ANY, _("Breakpoints"), wxDefaultPosition, wxSize(420, 250), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT)
{
this->SetSizeHints(wxDefaultSize, wxDefaultSize);
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
m_breakpoints = new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL);
m_breakpoints->EnableCheckBoxes(true);
wxListItem col0;
col0.SetId(ColumnEnabled);
col0.SetText(_("On"));
col0.SetWidth(32);
m_breakpoints->InsertColumn(ColumnEnabled, col0);
wxListItem col1;
col1.SetId(ColumnAddress);
col1.SetText(_("Address"));
col1.SetWidth(75);
m_breakpoints->InsertColumn(ColumnAddress, col1);
wxListItem col2;
col2.SetId(ColumnType);
col2.SetText(_("Type"));
col2.SetWidth(42);
m_breakpoints->InsertColumn(ColumnType, col2);
wxListItem col3;
col3.SetId(ColumnComment);
col3.SetText(_("Comment"));
col3.SetWidth(250);
m_breakpoints->InsertColumn(ColumnComment, col3);
main_sizer->Add(m_breakpoints, 1, wxEXPAND);
this->SetSizer(main_sizer);
this->wxWindowBase::Layout();
this->Centre(wxBOTH);
if (parent.GetConfig().data().pin_to_main)
OnMainMove(main_position, main_size);
m_breakpoints->Bind(wxEVT_LIST_ITEM_CHECKED, &BreakpointWindow::OnBreakpointToggled, this);
m_breakpoints->Bind(wxEVT_LIST_ITEM_UNCHECKED, &BreakpointWindow::OnBreakpointToggled, this);
m_breakpoints->Bind(wxEVT_LEFT_DCLICK, &BreakpointWindow::OnLeftDClick, this);
m_breakpoints->Bind(wxEVT_RIGHT_DOWN, &BreakpointWindow::OnRightDown, this);
OnUpdateView();
}
BreakpointWindow::~BreakpointWindow()
{
m_breakpoints->Unbind(wxEVT_LIST_ITEM_CHECKED, &BreakpointWindow::OnBreakpointToggled, this);
m_breakpoints->Unbind(wxEVT_LIST_ITEM_UNCHECKED, &BreakpointWindow::OnBreakpointToggled, this);
m_breakpoints->Unbind(wxEVT_LEFT_DCLICK, &BreakpointWindow::OnLeftDClick, this);
m_breakpoints->Unbind(wxEVT_RIGHT_DOWN, &BreakpointWindow::OnRightDown, this);
}
void BreakpointWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size)
{
wxSize size(420, 250);
this->SetSize(size);
wxPoint position = main_position;
position.x -= 420;
position.y += main_size.y - 250;
this->SetPosition(position);
}
void BreakpointWindow::OnUpdateView()
{
Freeze();
m_breakpoints->DeleteAllItems();
std::vector<DebuggerBreakpoint*>& breakpoints = debugger_lockBreakpoints();
uint32 i = 0;
for (const auto bpBase : breakpoints)
{
DebuggerBreakpoint* bp = bpBase;
while (bp)
{
wxListItem item = {};
item.SetId(i++);
const auto index = m_breakpoints->InsertItem(item);
m_breakpoints->SetItemPtrData(index, (uintptr_t)bp->id);
m_breakpoints->SetItem(index, ColumnAddress, wxString::Format("%08x", bp->address));
const char* typeName = "UKN";
if (bp->bpType == DEBUGGER_BP_T_NORMAL)
typeName = "X";
else if (bp->bpType == DEBUGGER_BP_T_LOGGING)
typeName = "LOG";
else if (bp->bpType == DEBUGGER_BP_T_ONE_SHOT)
typeName = "XS";
else if (bp->bpType == DEBUGGER_BP_T_MEMORY_READ)
typeName = "R";
else if (bp->bpType == DEBUGGER_BP_T_MEMORY_WRITE)
typeName = "W";
m_breakpoints->SetItem(index, ColumnType, typeName);
m_breakpoints->SetItem(index, ColumnComment, bp->comment);
m_breakpoints->CheckItem(index, bp->enabled);
bp = bp->next;
}
}
debugger_unlockBreakpoints();
Thaw();
}
void BreakpointWindow::OnGameLoaded()
{
OnUpdateView();
}
void BreakpointWindow::OnBreakpointToggled(wxListEvent& event)
{
const sint32 index = event.GetIndex();
uint64 bpId = (uint64)event.GetData();
debugger_lockBreakpoints();
DebuggerBreakpoint* bp = debugger_getBreakpointById(bpId);
if (bp)
{
const bool state = m_breakpoints->IsItemChecked(index);
debugger_toggleBreakpoint(bp->address, state, bp);
}
debugger_unlockBreakpoints();
}
void BreakpointWindow::OnLeftDClick(wxMouseEvent& event)
{
const auto position = event.GetPosition();
int flags = 0;
const long index = m_breakpoints->HitTest(position, flags);
if (index == wxNOT_FOUND)
return;
sint32 x = position.x;
const auto enabled_width = m_breakpoints->GetColumnWidth(ColumnEnabled);
if (x <= enabled_width)
return;
x -= enabled_width;
const auto address_width = m_breakpoints->GetColumnWidth(ColumnAddress);
if (x <= address_width)
{
const auto item = m_breakpoints->GetItemText(index, ColumnAddress);
const auto address = std::stoul(item.ToStdString(), nullptr, 16);
g_debuggerDispatcher.MoveToAddressInDisassembly(address);
return;
}
x -= address_width;
const auto type_width = m_breakpoints->GetColumnWidth(ColumnType);
if (x <= type_width)
return;
x -= type_width;
const auto comment_width = m_breakpoints->GetColumnWidth(ColumnComment);
if (x <= comment_width)
{
std::vector<DebuggerBreakpoint*>& breakpoints = debugger_lockBreakpoints();
if (index >= breakpoints.size())
{
debugger_unlockBreakpoints();
return;
}
DebuggerBreakpoint* bp = breakpoints[index];
auto bpId = bp->id;
const auto item = m_breakpoints->GetItemText(index, ColumnAddress);
const auto address = std::stoul(item.ToStdString(), nullptr, 16);
bool isLoggingBP = bp->bpType == DEBUGGER_BP_T_LOGGING;
const wxString dialogTitle = isLoggingBP ? _("Enter a new logging message") : _("Enter a new comment");
const wxString dialogMessage = isLoggingBP ? _("Set logging message when code at address %08x is ran.\nUse placeholders like {r3} or {f3} to log register values") : _("Set comment for breakpoint at address %08x");
const wxString existingComment = bp->comment;
debugger_unlockBreakpoints();
wxTextEntryDialog set_comment_dialog(this, dialogMessage, dialogTitle, existingComment);
if (set_comment_dialog.ShowModal() == wxID_OK)
{
if (isLoggingBP)
{
debugger_lockBreakpoints();
DebuggerBreakpoint* bp = debugger_getBreakpointById(bpId);
if (bp)
{
bp->comment = set_comment_dialog.GetValue().ToStdWstring();
m_breakpoints->SetItem(index, ColumnComment, set_comment_dialog.GetValue());
}
debugger_unlockBreakpoints();
}
}
}
}
void BreakpointWindow::OnRightDown(wxMouseEvent& event)
{
int flags = 0;
const long index = m_breakpoints->HitTest(event.GetPosition(), flags);
if (index == wxNOT_FOUND || index < 0 || index >= m_breakpoints->GetItemCount())
{
wxMenu menu;
menu.Append(MENU_ID_CREATE_CODE_BP_EXECUTION, _("Create execution breakpoint"));
menu.Append(MENU_ID_CREATE_CODE_BP_LOGGING, _("Create logging breakpoint"));
menu.Append(MENU_ID_CREATE_MEM_BP_READ, _("Create memory breakpoint (read)"));
menu.Append(MENU_ID_CREATE_MEM_BP_WRITE, _("Create memory breakpoint (write)"));
menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &BreakpointWindow::OnContextMenuClick, this);
PopupMenu(&menu);
}
else
{
m_breakpoints->Focus(index);
m_breakpoints->Select(index);
wxMenu menu;
menu.Append(MENU_ID_DELETE_BP, _("Delete breakpoint"));
menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &BreakpointWindow::OnContextMenuClickSelected, this);
PopupMenu(&menu);
}
}
void BreakpointWindow::OnContextMenuClickSelected(wxCommandEvent& evt)
{
auto& breakpoints = debugger_lockBreakpoints();
long selectedIndex = m_breakpoints->GetFirstSelected();
if (selectedIndex == wxNOT_FOUND || selectedIndex < 0 || selectedIndex >= (long)breakpoints.size())
{
debugger_unlockBreakpoints();
return;
}
BreakpointId bpId = (uint64)m_breakpoints->GetItemData(selectedIndex);
DebuggerBreakpoint* bp = debugger_getBreakpointById(bpId);
if (!bp)
{
debugger_unlockBreakpoints();
return;
}
if (evt.GetId() == MENU_ID_DELETE_BP)
{
debugger_deleteBreakpoint(bpId);
wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE);
wxPostEvent(this->m_parent, evt);
}
debugger_unlockBreakpoints();
}
void BreakpointWindow::OnContextMenuClick(wxCommandEvent& evt)
{
wxTextEntryDialog goto_dialog(this, _("Enter a memory address"), _("Set breakpoint"), wxEmptyString);
if (goto_dialog.ShowModal() == wxID_OK)
{
ExpressionParser parser;
auto value = goto_dialog.GetValue().ToStdString();
std::transform(value.begin(), value.end(), value.begin(), tolower);
uint32_t newBreakpointAddress = 0;
try
{
debugger_addParserSymbols(parser);
newBreakpointAddress = parser.IsConstantExpression("0x"+value) ? (uint32)parser.Evaluate("0x"+value) : (uint32)parser.Evaluate(value);
}
catch (const std::exception& ex)
{
wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
return;
}
switch (evt.GetId())
{
case MENU_ID_CREATE_CODE_BP_EXECUTION:
debugger_createCodeBreakpoint(newBreakpointAddress, DEBUGGER_BP_T_NORMAL);
break;
case MENU_ID_CREATE_CODE_BP_LOGGING:
debugger_createCodeBreakpoint(newBreakpointAddress, DEBUGGER_BP_T_LOGGING);
break;
case MENU_ID_CREATE_MEM_BP_READ:
debugger_createMemoryBreakpoint(newBreakpointAddress, true, false);
break;
case MENU_ID_CREATE_MEM_BP_WRITE:
debugger_createMemoryBreakpoint(newBreakpointAddress, false, true);
break;
}
this->OnUpdateView();
}
}
-28
View File
@@ -1,28 +0,0 @@
#pragma once
#include <wx/frame.h>
class DebuggerWindow2;
class wxListEvent;
class wxListView;
class BreakpointWindow : public wxFrame
{
public:
BreakpointWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size);
virtual ~BreakpointWindow();
void OnMainMove(const wxPoint& position, const wxSize& main_size);
void OnUpdateView();
void OnGameLoaded();
private:
void OnBreakpointToggled(wxListEvent& event);
void OnLeftDClick(wxMouseEvent& event);
void OnRightDown(wxMouseEvent& event);
void OnContextMenuClick(wxCommandEvent& evt);
void OnContextMenuClickSelected(wxCommandEvent& evt);
wxListView* m_breakpoints;
};
-936
View File
@@ -1,936 +0,0 @@
#include "wxgui/wxgui.h"
#include "wxgui/debugger/DebuggerWindow2.h"
#include "wxHelper.h"
#include "Common/FileStream.h"
#include "config/ActiveSettings.h"
#include "Cafe/HW/MMU/MMU.h"
#include "Cafe/OS/RPL/rpl_structs.h"
#include "Cafe/OS/RPL/rpl_debug_symbols.h"
#include "Cafe/OS/RPL/rpl_symbol_storage.h"
#include "wxgui/debugger/RegisterWindow.h"
#include "wxgui/debugger/DumpWindow.h"
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
#include "Cafe/OS/RPL/rpl.h"
#include "wxgui/debugger/DisasmCtrl.h"
#include "wxgui/debugger/SymbolWindow.h"
#include "wxgui/debugger/BreakpointWindow.h"
#include "wxgui/debugger/ModuleWindow.h"
#include "util/helpers/helpers.h"
#include "util/helpers/StringHelpers.h"
#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h"
#include "Cemu/Logging/CemuLogging.h"
#include "resource/embedded/resources.h"
enum
{
// file
MENU_ID_FILE_EXIT = wxID_HIGHEST + 8000,
// settings
MENU_ID_OPTIONS_PIN_TO_MAINWINDOW,
MENU_ID_OPTIONS_BREAK_ON_START,
MENU_ID_OPTIONS_LOG_MEMORY_BREAKPOINTS,
MENU_ID_OPTIONS_SWITCH_CPU_MODE,
// window
MENU_ID_WINDOW_REGISTERS,
MENU_ID_WINDOW_DUMP,
MENU_ID_WINDOW_STACK,
MENU_ID_WINDOW_BREAKPOINTS,
MENU_ID_WINDOW_MODULE,
MENU_ID_WINDOW_SYMBOL,
// tool
TOOL_ID_GOTO,
TOOL_ID_BP,
TOOL_ID_PAUSE,
TOOL_ID_STEP_OVER,
TOOL_ID_STEP_INTO,
};
wxDEFINE_EVENT(wxEVT_DEBUGGER_CLOSE, wxCloseEvent);
wxDEFINE_EVENT(wxEVT_UPDATE_VIEW, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_BREAKPOINT_CHANGE, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_BREAKPOINT_HIT, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_MOVE_TO_DISASM_ADDR, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_RUN, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_NOTIFY_MODULE_LOADED, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_NOTIFY_MODULE_UNLOADED, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_NOTIFY_GRAPHIC_PACKS_MODIFIED, wxCommandEvent);
wxBEGIN_EVENT_TABLE(DebuggerWindow2, wxFrame)
EVT_SHOW(DebuggerWindow2::OnShow)
EVT_CLOSE(DebuggerWindow2::OnClose)
EVT_COMMAND(wxID_ANY, wxEVT_UPDATE_VIEW, DebuggerWindow2::OnUpdateView)
EVT_COMMAND(wxID_ANY, wxEVT_BREAKPOINT_CHANGE, DebuggerWindow2::OnBreakpointChange)
EVT_COMMAND(wxID_ANY, wxEVT_MOVE_TO_DISASM_ADDR, DebuggerWindow2::OnMoveToDisasmAddr)
EVT_COMMAND(wxID_ANY, wxEVT_COMMAND_TOOL_CLICKED, DebuggerWindow2::OnToolClicked)
EVT_COMMAND(wxID_ANY, wxEVT_BREAKPOINT_HIT, DebuggerWindow2::OnBreakpointHit)
EVT_COMMAND(wxID_ANY, wxEVT_RUN, DebuggerWindow2::OnRunProgram)
EVT_COMMAND(wxID_ANY, wxEVT_NOTIFY_MODULE_LOADED, DebuggerWindow2::OnNotifyModuleLoaded)
EVT_COMMAND(wxID_ANY, wxEVT_NOTIFY_MODULE_UNLOADED, DebuggerWindow2::OnNotifyModuleUnloaded)
EVT_COMMAND(wxID_ANY, wxEVT_NOTIFY_GRAPHIC_PACKS_MODIFIED, DebuggerWindow2::OnNotifyGraphicPacksModified)
EVT_COMMAND(wxID_ANY, wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS, DebuggerWindow2::OnDisasmCtrlGotoAddress)
// file menu
EVT_MENU(MENU_ID_FILE_EXIT, DebuggerWindow2::OnExit)
// window
EVT_MENU_RANGE(MENU_ID_WINDOW_REGISTERS, MENU_ID_WINDOW_MODULE, DebuggerWindow2::OnWindowMenu)
wxEND_EVENT_TABLE()
DebuggerModuleInfo::DebuggerModuleInfo(RPLModule* module)
{
moduleName = module->moduleName;
patchCRC = module->patchCRC;
textArea.base = module->regionMappingBase_text.GetMPTR();
textArea.origBase = module->regionOrigAddr_text;
textArea.size = module->regionSize_text;
dataArea.base = module->regionMappingBase_data;
dataArea.origBase = module->regionOrigAddr_data;
dataArea.size = module->regionSize_data;
}
struct DebuggerModuleInfoNotify : public wxClientData
{
DebuggerModuleInfo moduleInfo;
DebuggerModuleInfoNotify(RPLModule* module) : moduleInfo(module) {};
};
DebuggerWindow2* s_debuggerWindow;
static bool TryParseMapAddress(std::string_view token, uint32& value)
{
trim(token, "\t\n\v\f\r ,:;()[]");
if (const auto colonIndex = token.find_last_of(':'); colonIndex != std::string_view::npos)
token = token.substr(colonIndex + 1);
trim(token, "\t\n\v\f\r ,:;()[]");
if (!token.empty() && (token.back() == 'h' || token.back() == 'H'))
token.remove_suffix(1);
if (token.size() > 2 && token[0] == '0' && (token[1] == 'x' || token[1] == 'X'))
token.remove_prefix(2);
if (token.empty())
return false;
uint64 parsedValue = 0;
const auto result = std::from_chars(token.data(), token.data() + token.size(), parsedValue, 16);
if (result.ec != std::errc() || result.ptr != (token.data() + token.size()) || parsedValue > std::numeric_limits<uint32>::max())
return false;
value = static_cast<uint32>(parsedValue);
return true;
}
static bool TryParseMapSymbolLine(std::string_view rawLine, uint32& address, std::string& symbolName)
{
auto line = rawLine;
trim(line);
if (line.empty())
return false;
const auto addressEnd = line.find_first_of(" \t");
if (addressEnd == std::string_view::npos)
return false;
if (!TryParseMapAddress(line.substr(0, addressEnd), address))
return false;
line = line.substr(addressEnd + 1);
trim(line);
if (line.empty())
return false;
const auto symbolEnd = line.find_first_of(" \t");
auto symbolToken = symbolEnd == std::string_view::npos ? line : line.substr(0, symbolEnd);
trim(symbolToken);
if (symbolToken.empty())
return false;
symbolName.assign(symbolToken);
return true;
}
static std::optional<MPTR> GetRelocatedMapAddress(const DebuggerModuleInfo& moduleInfo, uint32 mapAddress)
{
if (moduleInfo.textArea.ContainsOriginalAddress(mapAddress))
return moduleInfo.textArea.base + (mapAddress - moduleInfo.textArea.origBase);
if (moduleInfo.dataArea.ContainsOriginalAddress(mapAddress))
return moduleInfo.dataArea.base + (mapAddress - moduleInfo.dataArea.origBase);
return std::nullopt;
}
void DebuggerConfig::Load(XMLConfigParser& parser)
{
pin_to_main = parser.get("PinToMainWindow", true);
break_on_start = parser.get("break_on_start", true);
log_memory_breakpoints = parser.get("log_memory_breakpoints", false);
auto window_parser = parser.get("Windows");
show_register = window_parser.get("Registers", true);
show_dump = window_parser.get("MemoryDump", true);
show_stack = window_parser.get("Stack", true);
show_breakpoints = window_parser.get("Breakpoints", true);
show_modules = window_parser.get("Modules", true);
show_symbols = window_parser.get("Symbols", true);
}
void DebuggerConfig::Save(XMLConfigParser& parser)
{
parser.set("PinToMainWindow", pin_to_main);
parser.set("break_on_start", break_on_start);
parser.set("log_memory_breakpoints", log_memory_breakpoints);
auto window_parser = parser.set("Windows");
window_parser.set("Registers", show_register);
window_parser.set("MemoryDump", show_dump);
window_parser.set("Stack", show_stack);
window_parser.set("Breakpoints", show_breakpoints);
window_parser.set("Modules", show_modules);
window_parser.set("Symbols", show_symbols);
}
void DebuggerModuleStorage::Load(XMLConfigParser& parser)
{
auto breakpoints_parser = parser.get("Breakpoints");
for (auto element = breakpoints_parser.get("Entry"); element.valid(); element = breakpoints_parser.get("Entry", element))
{
const auto address_string = element.get("Address", "");
if (*address_string == '\0')
continue;
uint32 relative_address = std::strtoul(address_string, nullptr, 16);
if (relative_address == 0)
continue;
auto type = element.get("Type", 0);
auto enabled = element.get("Enabled", true);
const auto comment = element.get("Comment", "");
// calculate absolute address
uint32 module_base_address = (type == DEBUGGER_BP_T_NORMAL || type == DEBUGGER_BP_T_LOGGING) ? this->moduleInfo.textArea.base : this->moduleInfo.dataArea.base;
uint32 address = module_base_address + relative_address;
// don't change anything if there's already a breakpoint
if (debugger_getFirstBP(address) != nullptr)
continue;
// register breakpoints in debugger
if (type == DEBUGGER_BP_T_NORMAL)
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
else if (type == DEBUGGER_BP_T_LOGGING)
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_LOGGING);
else if (type == DEBUGGER_BP_T_MEMORY_READ)
debugger_createMemoryBreakpoint(address, true, false);
else if (type == DEBUGGER_BP_T_MEMORY_WRITE)
debugger_createMemoryBreakpoint(address, false, true);
else
continue;
DebuggerBreakpoint* debugBreakpoint = debugger_getFirstBP(address);
if (!enabled)
debugger_toggleBreakpoint(address, false, debugBreakpoint);
debugBreakpoint->comment = boost::nowide::widen(comment);
}
auto comments_parser = parser.get("Comments");
for (XMLConfigParser element = comments_parser.get("Entry"); element.valid(); element = comments_parser.get("Entry", element))
{
const auto address_string = element.get("Address", "");
if (*address_string == '\0')
continue;
uint32 address = std::strtoul(address_string, nullptr, 16);
if (address == 0)
continue;
const auto comment = element.get("Comment", "");
if (*comment == '\0')
continue;
rplDebugSymbol_createComment(address, boost::nowide::widen(comment).c_str());
}
}
void DebuggerModuleStorage::Save(XMLConfigParser& parser)
{
auto breakpoints_parser = parser.set("Breakpoints");
auto& breakpoints = debugger_lockBreakpoints();
for (const auto& bp : breakpoints)
{
// check breakpoint type
if (bp->dbType != DEBUGGER_BP_T_DEBUGGER)
continue;
// check whether the breakpoint is part of the current module being saved
uint32 relativeAddress;
if (moduleInfo.textArea.ContainsAddress(bp->address))
relativeAddress = bp->address - moduleInfo.textArea.base;
else if (moduleInfo.dataArea.ContainsAddress(bp->address))
relativeAddress = bp->address - moduleInfo.dataArea.base;
else
continue;
auto entry = breakpoints_parser.set("Entry");
entry.set("Address", fmt::format("{:#10x}", relativeAddress));
entry.set("Comment", boost::nowide::narrow(bp->comment).c_str());
entry.set("Type", bp->bpType);
entry.set("Enabled", bp->enabled);
if (this->delete_breakpoints_after_saving)
debugger_deleteBreakpoint(bp->id);
this->delete_breakpoints_after_saving = false;
}
debugger_unlockBreakpoints();
auto comments_parser = parser.set("Comments");
for (const auto& comment_entry : rplDebugSymbol_getSymbols())
{
// check comment type
const auto commentAddress = comment_entry.first;
const auto comment = static_cast<rplDebugSymbolComment*>(comment_entry.second);
if (!comment || comment->type != RplDebugSymbolComment)
continue;
// check whether the comment is part of the current module being saved
uint32 relativeAddress;
if (moduleInfo.textArea.ContainsAddress(commentAddress))
relativeAddress = commentAddress - moduleInfo.textArea.base;
else
continue;
auto entry = comments_parser.set("Entry");
entry.set("Address", fmt::format("{:#10x}", relativeAddress));
entry.set("Comment", boost::nowide::narrow(comment->comment).c_str());
}
}
void DebuggerWindow2::CreateToolBar()
{
m_toolbar = wxFrame::CreateToolBar(wxTB_HORIZONTAL, wxID_ANY);
m_toolbar->SetToolBitmapSize(wxSize(16, 16));
wxBitmap goto_bitmap = wxHelper::LoadThemedBitmapFromPNG(DEBUGGER_GOTO_png, sizeof(DEBUGGER_GOTO_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
m_toolbar->AddTool(TOOL_ID_GOTO, wxEmptyString, goto_bitmap, wxNullBitmap, wxITEM_NORMAL, _("GoTo (CTRL + G)"), "test", NULL);
m_toolbar->AddSeparator();
wxBitmap bp_bitmap = wxHelper::LoadThemedBitmapFromPNG(DEBUGGER_BP_RED_png, sizeof(DEBUGGER_BP_RED_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
m_toolbar->AddTool(TOOL_ID_BP, wxEmptyString, bp_bitmap, wxNullBitmap, wxITEM_NORMAL, _("Toggle Breakpoint (F9)"), wxEmptyString, NULL);
m_toolbar->AddSeparator();
m_pause = wxHelper::LoadThemedBitmapFromPNG(DEBUGGER_PAUSE_png, sizeof(DEBUGGER_PAUSE_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
m_run = wxHelper::LoadThemedBitmapFromPNG(DEBUGGER_PLAY_png, sizeof(DEBUGGER_PLAY_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
m_toolbar->AddTool(TOOL_ID_PAUSE, wxEmptyString, m_pause, wxNullBitmap, wxITEM_NORMAL, _("Break (F5)"), wxEmptyString, NULL);
wxBitmap step_into_bitmap = wxHelper::LoadThemedBitmapFromPNG(DEBUGGER_STEP_INTO_png, sizeof(DEBUGGER_STEP_INTO_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
wxBitmap step_over_bitmap = wxHelper::LoadThemedBitmapFromPNG(DEBUGGER_STEP_OVER_png, sizeof(DEBUGGER_STEP_OVER_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
m_toolbar->AddTool(TOOL_ID_STEP_INTO, wxEmptyString, step_into_bitmap, wxNullBitmap, wxITEM_NORMAL, _("Step Into (F11)"), wxEmptyString, NULL);
m_toolbar->AddTool(TOOL_ID_STEP_OVER, wxEmptyString, step_over_bitmap, wxNullBitmap, wxITEM_NORMAL, _("Step Over (F10)"), wxEmptyString, NULL);
m_toolbar->AddSeparator();
m_toolbar->Realize();
m_toolbar->EnableTool(TOOL_ID_STEP_INTO, false);
m_toolbar->EnableTool(TOOL_ID_STEP_OVER, false);
}
void DebuggerWindow2::LoadModuleStorage(const DebuggerModuleInfo& moduleInfo)
{
auto path = GetModuleStoragePath(moduleInfo.moduleName, moduleInfo.patchCRC);
bool already_loaded = std::any_of(m_modulesStorage.begin(), m_modulesStorage.end(), [path](const std::unique_ptr<XMLDebuggerModuleConfig>& debug) { return debug->GetFilename() == path; });
if (!path.empty() && !already_loaded)
{
auto& moduleStorage = m_modulesStorage.emplace_back(new XMLDebuggerModuleConfig(path, { moduleInfo, false }));
moduleStorage->Load();
LoadModuleMap(moduleStorage->data());
}
}
void DebuggerWindow2::SaveModuleStorage(const DebuggerModuleInfo& moduleInfo, bool deleteModuleStorage)
{
auto path = GetModuleStoragePath(moduleInfo.moduleName, moduleInfo.patchCRC);
for (auto it = m_modulesStorage.begin(); it != m_modulesStorage.end(); ++it)
{
auto& moduleStorage = *it;
if (moduleStorage->data().moduleInfo.moduleName == moduleInfo.moduleName && moduleStorage->data().moduleInfo.patchCRC == moduleInfo.patchCRC)
{
moduleStorage->data().delete_breakpoints_after_saving = deleteModuleStorage; // make sure breakpoints are deleted when the storage is removed
moduleStorage->Save(path);
if (deleteModuleStorage)
{
UnloadModuleMap(moduleStorage->data());
m_modulesStorage.erase(it);
}
return;
}
}
}
void DebuggerWindow2::LoadModuleMap(DebuggerModuleStorage& moduleStorage)
{
UnloadModuleMap(moduleStorage);
const auto mapPath = GetModuleMapPath(moduleStorage.moduleInfo.moduleName, moduleStorage.moduleInfo.patchCRC);
if (mapPath.empty())
return;
std::unique_ptr<FileStream> fileStream(FileStream::openFile(mapPath.c_str()));
if (!fileStream)
return;
const auto fileSize = fileStream->GetSize();
if (fileSize == 0 || fileSize > std::numeric_limits<uint32>::max())
return;
const auto readSize = static_cast<uint32>(fileSize);
std::vector<char> fileData(readSize);
if (fileStream->readData(fileData.data(), readSize) != readSize)
return;
std::string_view fileView(fileData.data(), fileData.size());
for (const auto& line : StringHelpers::StringLineIterator(fileView))
{
uint32 mapAddress = 0;
std::string symbolName;
if (TryParseMapSymbolLine(line, mapAddress, symbolName))
{
const auto relocatedAddress = GetRelocatedMapAddress(moduleStorage.moduleInfo, mapAddress);
if (relocatedAddress)
{
moduleStorage.loaded_map_symbols.emplace_back(rplSymbolStorage_store(moduleStorage.moduleInfo.moduleName.c_str(), symbolName.c_str(), *relocatedAddress, RPL_STORED_SYMBOL_MAP));
}
}
}
}
void DebuggerWindow2::UnloadModuleMap(DebuggerModuleStorage& moduleStorage)
{
for (auto* symbol : moduleStorage.loaded_map_symbols)
{
if (symbol)
rplSymbolStorage_remove(symbol);
}
moduleStorage.loaded_map_symbols.clear();
}
DebuggerWindow2::DebuggerWindow2(wxFrame& parent, const wxRect& display_size)
: wxFrame(&parent, wxID_ANY, _("PPC Debugger"), wxDefaultPosition, wxSize(1280, 300), wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT),
m_module_address(0)
{
g_debuggerDispatcher.SetDebuggerCallbacks(this);
const auto file = ActiveSettings::GetConfigPath("debugger/config.xml");
m_config.SetFilename(file.generic_wstring());
m_config.Load();
debugger_setOptionBreakOnEntry(m_config.data().break_on_start);
debugger_setOptionLogOnlyMemoryBreakpoints(m_config.data().log_memory_breakpoints);
m_main_position = parent.GetPosition();
m_main_size = parent.GetSize();
auto y = std::max<uint32>(300, (display_size.GetHeight() - 500 - 300) * 0.8);
this->SetSize(1280, y);
CreateMenuBar();
CreateToolBar();
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
// load configs and module storage for already loaded modules
const auto module_list = RPLLoader_GetModuleList();
for (sint32 i = 0; i < module_list.size(); i++)
{
const auto module = module_list[i];
DebuggerModuleInfo moduleInfo(module);
LoadModuleStorage(moduleInfo);
}
wxString label_text = _("> no modules loaded");
if (module_list.size() != 0)
{
RPLModule* currentModule = RPLLoader_FindModuleByCodeAddr(MEMORY_CODEAREA_ADDR);
if (currentModule)
label_text = wxString::Format("> %s", currentModule->moduleName.c_str());
else
label_text = _("> unknown module");
}
m_module_label = new wxStaticText(this, wxID_ANY, label_text);
m_module_label->SetForegroundColour(wxColour(0xFFbf52fe));
main_sizer->Add(m_module_label, 0, wxEXPAND | wxALL, 5);
m_disasm_ctrl = new DisasmCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxScrolledWindowStyle);
main_sizer->Add(m_disasm_ctrl, 1, wxEXPAND);
this->SetSizer(main_sizer);
this->wxWindowBase::Layout();
m_register_window = new RegisterWindow(*this, m_main_position, m_main_size);
m_dump_window = new DumpWindow(*this, m_main_position, m_main_size);
m_breakpoint_window = new BreakpointWindow(*this, m_main_position, m_main_size);
m_module_window = new ModuleWindow(*this, m_main_position, m_main_size);
m_symbol_window = new SymbolWindow(*this, m_main_position, m_main_size);
const bool value = m_config.data().pin_to_main;
m_config.data().pin_to_main = true;
OnParentMove(m_main_position, m_main_size);
m_config.data().pin_to_main = value;
s_debuggerWindow = this;
}
DebuggerWindow2::~DebuggerWindow2()
{
g_debuggerDispatcher.ClearDebuggerCallbacks();
s_debuggerWindow = nullptr;
CleanupForDestroy();
if (m_register_window && m_register_window->IsShown())
m_register_window->Close(true);
if (m_dump_window && m_dump_window->IsShown())
m_dump_window->Close(true);
if (m_breakpoint_window && m_breakpoint_window->IsShown())
m_breakpoint_window->Close(true);
if (m_module_window && m_module_window->IsShown())
m_module_window->Close(true);
if (m_symbol_window && m_symbol_window->IsShown())
m_symbol_window->Close(true);
// reenable recompiler if force interpreter option was enabled
if (m_forceInterpreter)
{
PPCRecompiler_Enable();
m_forceInterpreter = false;
cemuLog_log(LogType::Force, "Debugger: Switched CPU mode to recompiler");
}
}
// note: Can be called twice in some circumstances where early cleanup is needed. Will also always be called from destructor
void DebuggerWindow2::CleanupForDestroy()
{
debugger_setOptionBreakOnEntry(false);
// save module storage
while (!m_modulesStorage.empty())
{
SaveModuleStorage(m_modulesStorage[0]->data().moduleInfo, true); // deleting the module storage will also delete breakpoints belonging to the module's data
}
m_config.Save();
}
void DebuggerWindow2::OnClose(wxCloseEvent& event)
{
debugger_setOptionBreakOnEntry(false);
const wxCloseEvent parentEvent(wxEVT_DEBUGGER_CLOSE);
wxPostEvent(m_parent, parentEvent);
event.Skip();
}
void DebuggerWindow2::OnMoveToDisasmAddr(wxCommandEvent& event)
{
MPTR address = (uint32)(uint64)event.GetClientData();
if (!address)
return;
UpdateModuleLabel(address);
m_disasm_ctrl->CenterOffset(address);
}
void DebuggerWindow2::OnDisasmCtrlGotoAddress(wxCommandEvent& event)
{
uint32 address = static_cast<uint32>(event.GetExtraLong());
UpdateModuleLabel(address);
}
void DebuggerWindow2::OnParentMove(const wxPoint& main_position, const wxSize& main_size)
{
m_main_position = main_position;
m_main_size = main_size;
if (!m_config.data().pin_to_main)
return;
wxSize size(m_main_size.x, GetSize().GetHeight());
SetSize(size);
wxPoint position = m_main_position;
position.y -= size.y;
SetPosition(position);
m_register_window->OnMainMove(main_position, main_size);
m_dump_window->OnMainMove(main_position, main_size);
m_breakpoint_window->OnMainMove(main_position, main_size);
m_module_window->OnMainMove(main_position, main_size);
m_symbol_window->OnMainMove(main_position, main_size);
}
void DebuggerWindow2::OnNotifyModuleLoaded(wxCommandEvent& event)
{
DebuggerModuleInfoNotify* moduleNotif = static_cast<DebuggerModuleInfoNotify*>(event.GetClientObject());
LoadModuleStorage(moduleNotif->moduleInfo);
m_module_window->OnGameLoaded();
m_symbol_window->OnGameLoaded();
m_disasm_ctrl->Init();
}
void DebuggerWindow2::OnNotifyModuleUnloaded(wxCommandEvent& event)
{
DebuggerModuleInfoNotify* moduleNotif = static_cast<DebuggerModuleInfoNotify*>(event.GetClientObject());
SaveModuleStorage(moduleNotif->moduleInfo, true);
m_module_window->OnGameLoaded();
m_symbol_window->OnGameLoaded();
m_disasm_ctrl->Init();
}
void DebuggerWindow2::OnNotifyGraphicPacksModified(wxCommandEvent& event)
{
m_module_window->OnGameLoaded();
m_symbol_window->OnGameLoaded();
m_disasm_ctrl->Init();
}
void DebuggerWindow2::OnGameLoaded()
{
m_disasm_ctrl->Init();
m_dump_window->OnGameLoaded();
m_module_window->OnGameLoaded();
m_breakpoint_window->OnGameLoaded();
m_symbol_window->OnGameLoaded();
RPLModule* current_rpl_module = RPLLoader_FindModuleByCodeAddr(MEMORY_CODEAREA_ADDR);
if(current_rpl_module)
m_module_label->SetLabel(wxString::Format("> %s", current_rpl_module->moduleName.c_str()));
this->SendSizeEvent();
}
XMLDebuggerConfig& DebuggerWindow2::GetConfig()
{
return m_config;
}
bool DebuggerWindow2::Show(bool show)
{
const bool result = wxFrame::Show(show);
if (show)
{
m_register_window->Show(m_config.data().show_register);
m_dump_window->Show(m_config.data().show_dump);
m_breakpoint_window->Show(m_config.data().show_breakpoints);
m_module_window->Show(m_config.data().show_modules);
m_symbol_window->Show(m_config.data().show_symbols);
}
else
{
m_register_window->Show(false);
m_dump_window->Show(false);
m_breakpoint_window->Show(false);
m_module_window->Show(false);
m_symbol_window->Show(false);
}
return result;
}
std::wstring DebuggerWindow2::GetModuleStoragePath(std::string module_name, uint32_t crc_hash) const
{
if (module_name.empty() || crc_hash == 0) return {};
return ActiveSettings::GetConfigPath("debugger/{}_{:#10x}.xml", module_name, crc_hash).generic_wstring();
}
std::wstring DebuggerWindow2::GetModuleMapPath(std::string module_name, uint32_t crc_hash) const
{
if (module_name.empty() || crc_hash == 0) return {};
return ActiveSettings::GetConfigPath("debugger/{}_{:#10x}.map", module_name, crc_hash).generic_wstring();
}
void DebuggerWindow2::OnBreakpointHit(wxCommandEvent& event)
{
PPCInterpreter_t* hCPU = debugger_lockDebugSession();
if (!hCPU)
return;
MPTR ip = hCPU->instructionPointer;
debugger_unlockDebugSession(hCPU);
UpdateModuleLabel(ip);
m_toolbar->SetToolShortHelp(TOOL_ID_PAUSE, _("Run (F5)"));
m_toolbar->SetToolNormalBitmap(TOOL_ID_PAUSE, m_run);
m_toolbar->EnableTool(TOOL_ID_STEP_INTO, true);
m_toolbar->EnableTool(TOOL_ID_STEP_OVER, true);
m_disasm_ctrl->CenterOffset(ip);
}
void DebuggerWindow2::OnRunProgram(wxCommandEvent& event)
{
m_toolbar->SetToolShortHelp(TOOL_ID_PAUSE, _("Break (F5)"));
m_toolbar->SetToolNormalBitmap(TOOL_ID_PAUSE, m_pause);
m_toolbar->EnableTool(TOOL_ID_STEP_INTO, false);
m_toolbar->EnableTool(TOOL_ID_STEP_OVER, false);
}
void DebuggerWindow2::OnToolClicked(wxCommandEvent& event)
{
switch(event.GetId())
{
case TOOL_ID_GOTO:
m_disasm_ctrl->GoToAddressDialog();
break;
case TOOL_ID_PAUSE:
if (debugger_isTrapped())
debugger_stepCommand(DebuggerStepCommand::Run);
else
debugger_requestBreak();
break;
case TOOL_ID_STEP_INTO:
if (debugger_isTrapped())
debugger_stepCommand(DebuggerStepCommand::StepInto);
break;
case TOOL_ID_STEP_OVER:
if (debugger_isTrapped())
debugger_stepCommand(DebuggerStepCommand::StepOver);
break;
}
}
void DebuggerWindow2::OnBreakpointChange(wxCommandEvent& event)
{
m_breakpoint_window->OnUpdateView();
m_disasm_ctrl->RefreshControl();
UpdateModuleLabel();
}
void DebuggerWindow2::OnOptionsInput(wxCommandEvent& event)
{
switch (event.GetId())
{
case MENU_ID_OPTIONS_PIN_TO_MAINWINDOW:
{
const bool value = !m_config.data().pin_to_main;
m_config.data().pin_to_main = value;
if (value)
OnParentMove(m_main_position, m_main_size);
break;
}
case MENU_ID_OPTIONS_BREAK_ON_START:
{
const bool value = !m_config.data().break_on_start;
m_config.data().break_on_start = value;
debugger_setOptionBreakOnEntry(value);
break;
}
case MENU_ID_OPTIONS_LOG_MEMORY_BREAKPOINTS:
{
const bool value = !m_config.data().log_memory_breakpoints;
m_config.data().log_memory_breakpoints = value;
debugger_setOptionLogOnlyMemoryBreakpoints(value);
break;
}
case MENU_ID_OPTIONS_SWITCH_CPU_MODE:
{
if (m_forceInterpreter)
{
PPCRecompiler_Enable();
m_forceInterpreter = false;
cemuLog_log(LogType::Force, "Debugger: Switched CPU mode to recompiler");
}
else
{
PPCRecompiler_Disable();
m_forceInterpreter = true;
cemuLog_log(LogType::Force, "Debugger: Switched CPU mode to interpreter");
}
break;
}
default:
return;
}
m_config.Save();
}
void DebuggerWindow2::OnWindowMenu(wxCommandEvent& event)
{
switch (event.GetId())
{
case MENU_ID_WINDOW_REGISTERS:
{
const bool value = !m_config.data().show_register;
m_config.data().show_register = value;
m_register_window->Show(value);
break;
}
case MENU_ID_WINDOW_DUMP:
{
const bool value = !m_config.data().show_dump;
m_config.data().show_dump = value;
m_dump_window->Show(value);
break;
}
case MENU_ID_WINDOW_BREAKPOINTS:
{
const bool value = !m_config.data().show_breakpoints;
m_config.data().show_breakpoints = value;
m_breakpoint_window->Show(value);
break;
}
case MENU_ID_WINDOW_MODULE:
{
const bool value = !m_config.data().show_modules;
m_config.data().show_modules = value;
m_module_window->Show(value);
break;
}
case MENU_ID_WINDOW_SYMBOL:
{
const bool value = !m_config.data().show_symbols;
m_config.data().show_symbols = value;
m_symbol_window->Show(value);
break;
}
default:
return;
}
m_config.Save();
}
void DebuggerWindow2::OnUpdateView(wxCommandEvent& event)
{
UpdateModuleLabel();
m_disasm_ctrl->OnUpdateView();
m_register_window->OnUpdateView();
m_breakpoint_window->OnUpdateView();
}
void DebuggerWindow2::OnExit(wxCommandEvent& event)
{
this->Close();
}
void DebuggerWindow2::OnShow(wxShowEvent& event)
{
m_register_window->Show();
m_dump_window->Show();
m_breakpoint_window->Show();
m_module_window->Show();
m_symbol_window->Show();
event.Skip();
}
void DebuggerWindow2::CreateMenuBar()
{
auto menu_bar = new wxMenuBar;
// file
wxMenu* file_menu = new wxMenu;
file_menu->Append(MENU_ID_FILE_EXIT, _("&Exit"));
file_menu->Bind(wxEVT_MENU, &DebuggerWindow2::OnExit, this);
menu_bar->Append(file_menu, _("&File"));
// options
wxMenu* options_menu = new wxMenu;
options_menu->Append(MENU_ID_OPTIONS_PIN_TO_MAINWINDOW, _("&Pin to main window"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().pin_to_main);
options_menu->Append(MENU_ID_OPTIONS_BREAK_ON_START, _("Break on &entry point"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().break_on_start);
options_menu->Append(MENU_ID_OPTIONS_LOG_MEMORY_BREAKPOINTS, _("Log only memory breakpoints"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().log_memory_breakpoints);
options_menu->Append(MENU_ID_OPTIONS_SWITCH_CPU_MODE, _("Switch to &interpreter CPU mode"), wxEmptyString, wxITEM_CHECK);
menu_bar->Append(options_menu, _("&Options"));
// window
wxMenu* window_menu = new wxMenu;
window_menu->Append(MENU_ID_WINDOW_REGISTERS, _("&Registers"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_register);
window_menu->Append(MENU_ID_WINDOW_DUMP, _("&Memory Dump"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_dump);
window_menu->Append(MENU_ID_WINDOW_BREAKPOINTS, _("&Breakpoints"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_breakpoints);
window_menu->Append(MENU_ID_WINDOW_MODULE, _("Module&list"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_modules);
window_menu->Append(MENU_ID_WINDOW_SYMBOL, _("&Symbols"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_symbols);
menu_bar->Append(window_menu, _("&Window"));
SetMenuBar(menu_bar);
options_menu->Bind(wxEVT_MENU, &DebuggerWindow2::OnOptionsInput, this);
window_menu->Bind(wxEVT_MENU, &DebuggerWindow2::OnWindowMenu, this);
}
void DebuggerWindow2::UpdateModuleLabel(uint32 address)
{
if (address == 0)
address = m_disasm_ctrl->GetViewBaseAddress();
RPLModule* module = RPLLoader_FindModuleByCodeAddr(address);
if (module && m_module_address != module->regionMappingBase_text.GetMPTR())
{
m_module_label->SetLabel(wxString::Format("> %s", module->moduleName.c_str()));
m_module_address = module->regionMappingBase_text.GetMPTR();
}
else if (address >= mmuRange_CODECAVE.getBase() && address < mmuRange_CODECAVE.getEnd())
{
m_module_label->SetLabel(wxString::Format("> %s", "Cemu codecave"));
m_module_address = mmuRange_CODECAVE.getBase();
}
}
void DebuggerWindow2::UpdateViewThreadsafe()
{
auto* evt = new wxCommandEvent(wxEVT_UPDATE_VIEW);
wxQueueEvent(this, evt);
}
void DebuggerWindow2::NotifyDebugBreakpointHit()
{
auto* evt = new wxCommandEvent(wxEVT_BREAKPOINT_HIT);
wxQueueEvent(this, evt);
}
void DebuggerWindow2::NotifyRun()
{
auto* evt = new wxCommandEvent(wxEVT_RUN);
wxQueueEvent(this, evt);
}
void DebuggerWindow2::MoveToAddressInDisassembly(MPTR address)
{
auto* evt = new wxCommandEvent(wxEVT_MOVE_TO_DISASM_ADDR);
evt->SetClientData((void*)(uintptr_t)address);
wxQueueEvent(this, evt);
}
void DebuggerWindow2::NotifyGraphicPacksModified()
{
auto* evt = new wxCommandEvent(wxEVT_NOTIFY_GRAPHIC_PACKS_MODIFIED);
wxQueueEvent(this, evt);
}
void DebuggerWindow2::NotifyModuleLoaded(RPLModule* module)
{
DebuggerModuleInfoNotify* notifData = new DebuggerModuleInfoNotify(module);
auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_LOADED);
evt->SetClientObject(notifData);
wxQueueEvent(this, evt);
}
void DebuggerWindow2::NotifyModuleUnloaded(RPLModule* module)
{
DebuggerModuleInfoNotify* notifData = new DebuggerModuleInfoNotify(module);
auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_UNLOADED);
evt->SetClientObject(notifData);
wxQueueEvent(this, evt);
}
-173
View File
@@ -1,173 +0,0 @@
#pragma once
#include "wxgui/debugger/DisasmCtrl.h"
#include "config/XMLConfig.h"
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/HW/Espresso/Debugger/GDBStub.h"
#include <wx/bitmap.h>
#include <wx/frame.h>
#include <wx/mstream.h>
class BreakpointWindow;
class RegisterWindow;
class DumpWindow;
class ModuleWindow;
class SymbolWindow;
class wxStaticText;
struct RPLStoredSymbol;
wxDECLARE_EVENT(wxEVT_UPDATE_VIEW, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_BREAKPOINT_HIT, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_RUN, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_BREAKPOINT_CHANGE, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_MOVE_TO_DISASM_ADDR, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_NOTIFY_MODULE_LOADED, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_NOTIFY_MODULE_UNLOADED, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_NOTIFY_GRAPHIC_PACKS_MODIFIED, wxCommandEvent);
extern class DebuggerWindow2* s_debuggerWindow;
struct DebuggerConfig
{
DebuggerConfig()
: pin_to_main(true), break_on_start(true), log_memory_breakpoints(false), show_register(true), show_dump(true), show_stack(true), show_breakpoints(true), show_modules(true), show_symbols(true) {}
bool pin_to_main;
bool break_on_start;
bool log_memory_breakpoints;
bool show_register;
bool show_dump;
bool show_stack;
bool show_breakpoints;
bool show_modules;
bool show_symbols;
void Load(XMLConfigParser& parser);
void Save(XMLConfigParser& parser);
};
typedef XMLDataConfig<DebuggerConfig> XMLDebuggerConfig;
struct DebuggerModuleInfo
{
struct ModuleArea
{
MPTR base;
MPTR origBase;
uint32 size;
bool ContainsAddress(MPTR addr) const
{
return addr >= base && (addr < (base+size));
}
bool ContainsOriginalAddress(MPTR addr) const
{
return addr >= origBase && (addr < (origBase + size));
}
};
std::string moduleName;
uint32 patchCRC;
ModuleArea textArea;
ModuleArea dataArea;
DebuggerModuleInfo(RPLModule* module);
};
struct DebuggerModuleStorage
{
DebuggerModuleInfo moduleInfo;
bool delete_breakpoints_after_saving;
std::vector<RPLStoredSymbol*> loaded_map_symbols;
void Load(XMLConfigParser& parser);
void Save(XMLConfigParser& parser);
};
typedef XMLDataConfig<DebuggerModuleStorage> XMLDebuggerModuleConfig;
static wxBitmap LoadThemedBitmapFromPNG(const uint8* data, size_t size, const wxColour& tint)
{
wxMemoryInputStream strm(data, size);
wxImage img(strm, wxBITMAP_TYPE_PNG);
img.Replace(0x00, 0x00, 0x00, tint.Red(), tint.Green(), tint.Blue());
return wxBitmap(img);
}
class DebuggerWindow2 : public wxFrame, public DebuggerCallbacks
{
public:
void CreateToolBar();
void LoadModuleStorage(const struct DebuggerModuleInfo& moduleInfo);
void SaveModuleStorage(const struct DebuggerModuleInfo& moduleInfo, bool deleteModuleStorage);
DebuggerWindow2(wxFrame& parent, const wxRect& display_size);
~DebuggerWindow2();
void CleanupForDestroy();
void OnParentMove(const wxPoint& position, const wxSize& size);
void OnGameLoaded();
XMLDebuggerConfig& GetConfig();
bool Show(bool show = true) override;
std::wstring GetModuleStoragePath(std::string module_name, uint32_t crc_hash) const;
private:
void OnBreakpointHit(wxCommandEvent& event);
void OnRunProgram(wxCommandEvent& event);
void OnToolClicked(wxCommandEvent& event);
void OnBreakpointChange(wxCommandEvent& event);
void OnOptionsInput(wxCommandEvent& event);
void OnWindowMenu(wxCommandEvent& event);
void OnUpdateView(wxCommandEvent& event);
void OnExit(wxCommandEvent& event);
void OnShow(wxShowEvent& event);
void OnClose(wxCloseEvent& event);
void OnMoveToDisasmAddr(wxCommandEvent& event);
void OnNotifyModuleLoaded(wxCommandEvent& event);
void OnNotifyModuleUnloaded(wxCommandEvent& event);
void OnNotifyGraphicPacksModified(wxCommandEvent& event);
// events from DisasmCtrl
void OnDisasmCtrlGotoAddress(wxCommandEvent& event);
void CreateMenuBar();
void UpdateModuleLabel(uint32 address = 0);
void LoadModuleMap(DebuggerModuleStorage& moduleStorage);
void UnloadModuleMap(DebuggerModuleStorage& moduleStorage);
std::wstring GetModuleMapPath(std::string module_name, uint32_t crc_hash) const;
void UpdateViewThreadsafe() override;
void NotifyDebugBreakpointHit() override;
void NotifyRun() override;
void MoveToAddressInDisassembly(MPTR address) override;
void NotifyGraphicPacksModified() override;
void NotifyModuleLoaded(struct RPLModule* module) override;
void NotifyModuleUnloaded(struct RPLModule* module) override;
XMLDebuggerConfig m_config;
std::vector<std::unique_ptr<XMLDebuggerModuleConfig>> m_modulesStorage;
wxPoint m_main_position;
wxSize m_main_size;
RegisterWindow* m_register_window;
DumpWindow* m_dump_window;
BreakpointWindow* m_breakpoint_window;
ModuleWindow* m_module_window;
SymbolWindow* m_symbol_window;
DisasmCtrl* m_disasm_ctrl;
wxToolBar* m_toolbar;
wxBitmap m_run, m_pause;
uint32 m_module_address;
wxStaticText* m_module_label;
bool m_forceInterpreter{false};
wxDECLARE_EVENT_TABLE();
};
-895
View File
@@ -1,895 +0,0 @@
#include "wxgui/wxgui.h"
#include "wxgui/debugger/DisasmCtrl.h"
#include "wxHelper.h"
#include "Cafe/OS/RPL/rpl_structs.h"
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/OS/RPL/rpl_symbol_storage.h"
#include "Cafe/OS/RPL/rpl_debug_symbols.h"
#include "Cemu/PPCAssembler/ppcAssembler.h"
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
#include "wxgui/debugger/DebuggerWindow2.h"
#include "util/helpers/helpers.h"
#include "Cemu/ExpressionParser/ExpressionParser.h"
#include "Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h"
#include <wx/clipbrd.h>
wxDEFINE_EVENT(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS, wxCommandEvent);
#define MAX_SYMBOL_LEN (120)
constexpr uint8 arrowRightPNG[] =
{
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0B,
0x08, 0x03, 0x00, 0x00, 0x00, 0x41, 0x3C, 0xFD, 0x0B, 0x00, 0x00, 0x00,
0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00,
0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC,
0x61, 0x05, 0x00, 0x00, 0x00, 0x06, 0x50, 0x4C, 0x54, 0x45, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xA5, 0x67, 0xB9, 0xCF, 0x00, 0x00, 0x00, 0x02,
0x74, 0x52, 0x4E, 0x53, 0xFF, 0x00, 0xE5, 0xB7, 0x30, 0x4A, 0x00, 0x00,
0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC3, 0x00, 0x00,
0x0E, 0xC3, 0x01, 0xC7, 0x6F, 0xA8, 0x64, 0x00, 0x00, 0x00, 0x2C, 0x49,
0x44, 0x41, 0x54, 0x18, 0x57, 0x63, 0x60, 0x84, 0x03, 0x08, 0x13, 0x59,
0x00, 0xCC, 0x46, 0x11, 0x00, 0x71, 0x80, 0x24, 0x32, 0xC0, 0x10, 0x60,
0xC0, 0x10, 0xC0, 0x00, 0x58, 0xCC, 0x80, 0xD8, 0x00, 0x02, 0x60, 0x3E,
0x7E, 0x77, 0x00, 0x31, 0x23, 0x23, 0x00, 0x21, 0x95, 0x00, 0x5B, 0x20,
0x73, 0x8D, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
0x42, 0x60, 0x82
};
std::optional<wxBitmap> g_ipArrowBitmap;
static wxColour theme_textForeground;
static wxColour theme_textForegroundMuted;
static wxColour theme_background;
// background colors for lines in the disassembly view
static wxColour theme_lineBreakpointAndCurrentInstruction;
static wxColour theme_lineCurrentInstruction;
static wxColour theme_lineBreakpointSet;
static wxColour theme_lineLoggingBreakpointSet;
static wxColour theme_lineLastGotoAddress;
// text colors for addresses in the disassembly view
static wxColour theme_syntaxGPR;
static wxColour theme_syntaxFPR;
static wxColour theme_syntaxSPR;
static wxColour theme_syntaxCR;
static wxColour theme_syntaxIMM;
static wxColour theme_syntaxIMMOffset;
static wxColour theme_syntaxCallIMM;
static wxColour theme_syntaxPseudoOrUnknown;
static wxColour theme_syntaxSymbol;
static wxColour theme_opCode;
static wxColour theme_typeData;
static wxColour theme_patchedOpCode;
static wxColour theme_patchedData;
static void InitSyntaxColors()
{
theme_textForeground = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
theme_textForegroundMuted = wxSystemSettings::SelectLightDark(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT), wxColour(140, 142, 145));
theme_background = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
// line background highlights
theme_lineBreakpointSet = wxSystemSettings::SelectLightDark(wxColour(0xFF,0x80,0x80), wxColour(0x84,0x21,0x21)); // red for a non-current breakpoint
theme_lineBreakpointAndCurrentInstruction = wxSystemSettings::SelectLightDark(wxColour(0xD2,0x7E,0xD2), wxColour(0x91,0x44,0xA4)); // pink for a current breakpoint
theme_lineCurrentInstruction = wxSystemSettings::SelectLightDark(wxColour(0xFF,0xA0,0x80), wxColour(0x87,0x53,0x1A)); // light orange
theme_lineLoggingBreakpointSet = wxSystemSettings::SelectLightDark(wxColour(0xAB,0xED,0xEE), wxColour(0x25,0x5D,0x6D)); // light blue
theme_lineLastGotoAddress = wxSystemSettings::SelectLightDark(wxColour(0xE0,0xE0,0xE0), wxColour(0x40,0x40,0x40)); // very light gray to indicate the line that was last jumped to
// disassembly syntax colors
theme_syntaxGPR = wxSystemSettings::SelectLightDark(wxColour(0x66,0x00,0x00), wxColour(0xE0,0x6C,0x75));
theme_syntaxFPR = wxSystemSettings::SelectLightDark(wxColour(0x66,0x66,0x00), wxColour(0xD1,0x9A,0x66));
theme_syntaxSPR = wxSystemSettings::SelectLightDark(wxColour(0x00,0x66,0x66), wxColour(0x56,0xB6,0xC2));
theme_syntaxCR = wxSystemSettings::SelectLightDark(wxColour(0x00,0x66,0x66), wxColour(0x56,0xB6,0xC2));
theme_syntaxIMM = wxSystemSettings::SelectLightDark(wxColour(0x00,0x66,0x00), wxColour(0x9D,0xDE,0x6F));
theme_syntaxIMMOffset = wxSystemSettings::SelectLightDark(wxColour(0x00,0x66,0x00), wxColour(0x9D,0xDE,0x6F));
theme_syntaxCallIMM = wxSystemSettings::SelectLightDark(wxColour(0x00,0x00,0x88), wxColour(0x61,0xAF,0xEF));
theme_syntaxPseudoOrUnknown = wxSystemSettings::SelectLightDark(wxColour(0xA0,0xA0,0xA0), wxColour(0x5C,0x63,0x70));
theme_syntaxSymbol = wxSystemSettings::SelectLightDark(wxColour(0xA0,0x00,0x00), wxColour(0xE0,0x6C,0x75));
// opcode & data highlighting
theme_opCode = wxSystemSettings::SelectLightDark(wxColour(0xFF,0x00,0x40), wxColour(0xFF,0x70,0x7B));
theme_patchedOpCode = wxSystemSettings::SelectLightDark(wxColour(0xFF,0x20,0x20), wxColour(0xCC,0x52,0xF5));
theme_typeData = wxSystemSettings::SelectLightDark(wxColour(0xFF,0x20,0x20), wxColour(0xCE,0x91,0x78));
theme_patchedData = wxSystemSettings::SelectLightDark(wxColour(0xFF,0x20,0x20), wxColour(0xF4,0x43,0x36));
// theme the current instruction pointer arrow
g_ipArrowBitmap = wxHelper::LoadThemedBitmapFromPNG(arrowRightPNG, sizeof(arrowRightPNG), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
}
#define OFFSET_ADDRESS (60)
#define OFFSET_ADDRESS_RELATIVE (90)
#define OFFSET_DISASSEMBLY (300)
#define OFFSET_DISASSEMBLY_OPERAND (80)
DisasmCtrl::DisasmCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style)
: TextList(parent, id, pos, size, style), m_mouse_line(-1), m_mouse_line_drawn(-1), m_active_line(-1)
{
Init();
if (!g_ipArrowBitmap.has_value())
{
InitSyntaxColors();
}
auto tooltip_sizer = new wxBoxSizer(wxVERTICAL);
tooltip_sizer->Add(new wxStaticText(m_tooltip_window, wxID_ANY, wxEmptyString), 0, wxALL, 5);
m_tooltip_window->SetSizer(tooltip_sizer);
Bind(wxEVT_MENU, &DisasmCtrl::OnContextMenuEntryClicked, this, IDContextMenu_ToggleBreakpoint, IDContextMenu_Last);
}
void DisasmCtrl::Init()
{
SelectCodeRegion(mmuRange_TEXT_AREA.getBase());
}
void DisasmCtrl::SelectCodeRegion(uint32 newAddress)
{
if (newAddress >= mmuRange_TEXT_AREA.getBase() && newAddress < mmuRange_TEXT_AREA.getEnd())
{
currentCodeRegionStart = MEMORY_CODEAREA_ADDR;
currentCodeRegionEnd = RPLLoader_GetMaxCodeOffset();
currentCodeRegionEnd = std::max(currentCodeRegionEnd, currentCodeRegionStart + 0x1000);
}
MMURange* mmuRange = memory_getMMURangeByAddress(newAddress);
if (mmuRange)
{
currentCodeRegionStart = mmuRange->getBase();
currentCodeRegionEnd = mmuRange->getEnd();
}
else
{
currentCodeRegionStart = 0;
currentCodeRegionEnd = 0;
}
// update line tracking
sint32 element_count = currentCodeRegionEnd - currentCodeRegionStart;
// if (element_count <= 0x00010000)
// element_count = 0x00010000;
if (this->SetElementCount(element_count / 4))
{
Scroll(0, 0);
RefreshControl();
}
}
void DisasmCtrl::DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, RPLModule* rplModule)
{
wxPoint position = linePosition;
bool hasPatch = debugger_hasPatch(virtualAddress);
PPCDisassembledInstruction disasmInstr;
const DebuggerBreakpoint* bp = debugger_getFirstBP(virtualAddress);
while (bp)
{
if (bp->isExecuteBP() && bp->enabled)
break;
bp = bp->next;
}
uint32 opcode;
if (bp)
opcode = bp->originalOpcodeValue;
else
opcode = memory_readU32(virtualAddress);
ppcAssembler_disassemble(virtualAddress, opcode, &disasmInstr);
bool hasActiveBP = false;
PPCInterpreter_t* sessionCpu = debugger_lockDebugSession();
if (sessionCpu)
{
hasActiveBP = sessionCpu->instructionPointer == virtualAddress;
debugger_unlockDebugSession(sessionCpu);
}
// write virtual address
wxColour background_colour;
if (hasActiveBP && bp != nullptr)
background_colour = theme_lineBreakpointAndCurrentInstruction;
else if (hasActiveBP)
background_colour = theme_lineCurrentInstruction;
else if (bp != nullptr)
background_colour = bp->bpType == DEBUGGER_BP_T_NORMAL ? theme_lineBreakpointSet : theme_lineLoggingBreakpointSet;
else if(virtualAddress == m_lastGotoTarget)
background_colour = theme_lineLastGotoAddress;
else
background_colour = theme_background;
DrawLineBackground(dc, position, background_colour);
dc.SetTextForeground(theme_textForeground);
dc.DrawText(wxString::Format("%08x", virtualAddress), position);
position.x += OFFSET_ADDRESS;
dc.SetTextForeground(theme_textForegroundMuted);
if (rplModule)
dc.DrawText(wxString::Format("+0x%-8x", virtualAddress - rplModule->regionMappingBase_text.GetMPTR()), position);
else
dc.DrawText("???", position);
position.x += OFFSET_ADDRESS_RELATIVE;
// draw arrow to clearly indicate instruction pointer
if(hasActiveBP)
dc.DrawBitmap(*g_ipArrowBitmap, wxPoint(position.x - 24, position.y + 2), false);
// handle data symbols
auto debugSymbolDataType = DebugSymbolStorage::GetDataType(virtualAddress);
if (debugSymbolDataType == DEBUG_SYMBOL_TYPE::FLOAT)
{
dc.SetTextForeground(hasPatch ? theme_patchedData : theme_typeData);
dc.DrawText(fmt::format(".float"), position);
position.x += OFFSET_DISASSEMBLY_OPERAND;
dc.SetTextForeground(hasPatch ? theme_patchedData : theme_syntaxIMM);
dc.DrawText(fmt::format("{}", memory_readFloat(virtualAddress)), position);
return;
}
else if (debugSymbolDataType == DEBUG_SYMBOL_TYPE::U32)
{
dc.SetTextForeground(hasPatch ? theme_patchedData : theme_typeData);
dc.DrawText(fmt::format(".uint"), position);
position.x += OFFSET_DISASSEMBLY_OPERAND;
dc.SetTextForeground(hasPatch ? theme_patchedData : theme_syntaxIMM);
dc.DrawText(fmt::format("{}", memory_readU32(virtualAddress)), position);
return;
}
sint32 start_width = position.x;
dc.SetTextForeground(hasPatch ? theme_patchedOpCode : theme_opCode);
std::string opName = ppcAssembler_getInstructionName(disasmInstr.ppcAsmCode);
std::transform(opName.begin(), opName.end(), opName.begin(), tolower);
dc.DrawText(wxString::Format("%-12s", opName.c_str()), position);
position.x += OFFSET_DISASSEMBLY_OPERAND;
bool isRLWINM = disasmInstr.ppcAsmCode == PPCASM_OP_RLWINM || disasmInstr.ppcAsmCode == PPCASM_OP_RLWINM_ ||
disasmInstr.ppcAsmCode == PPCASM_OP_CLRLWI || disasmInstr.ppcAsmCode == PPCASM_OP_CLRLWI_ ||
disasmInstr.ppcAsmCode == PPCASM_OP_CLRRWI || disasmInstr.ppcAsmCode == PPCASM_OP_CLRRWI_ ||
disasmInstr.ppcAsmCode == PPCASM_OP_EXTLWI || disasmInstr.ppcAsmCode == PPCASM_OP_EXTLWI_ ||
disasmInstr.ppcAsmCode == PPCASM_OP_EXTRWI || disasmInstr.ppcAsmCode == PPCASM_OP_EXTRWI_ ||
disasmInstr.ppcAsmCode == PPCASM_OP_SLWI || disasmInstr.ppcAsmCode == PPCASM_OP_SLWI_ ||
disasmInstr.ppcAsmCode == PPCASM_OP_SRWI || disasmInstr.ppcAsmCode == PPCASM_OP_SRWI_ ||
disasmInstr.ppcAsmCode == PPCASM_OP_ROTRWI || disasmInstr.ppcAsmCode == PPCASM_OP_ROTRWI_ ||
disasmInstr.ppcAsmCode == PPCASM_OP_ROTLWI || disasmInstr.ppcAsmCode == PPCASM_OP_ROTLWI_;
bool forceDecDisplay = isRLWINM;
if (disasmInstr.ppcAsmCode == PPCASM_OP_UKN)
{
// show raw bytes
WriteText(dc, wxString::Format("%02x %02x %02x %02x", (opcode >> 24) & 0xFF, (opcode >> 16) & 0xFF, (opcode >> 8) & 0xFF, (opcode >> 0) & 0xFF), position, theme_syntaxPseudoOrUnknown);
}
bool is_first_operand = true;
for (sint32 o = 0; o < PPCASM_OPERAND_COUNT; o++)
{
if (((disasmInstr.operandMask >> o) & 1) == 0)
continue;
if (!is_first_operand)
WriteText(dc, ", ", position, theme_textForeground);
is_first_operand = false;
switch (disasmInstr.operand[o].type)
{
case PPCASM_OPERAND_TYPE_GPR:
WriteText(dc, wxString::Format("r%d", disasmInstr.operand[o].registerIndex), position, theme_syntaxGPR);
break;
case PPCASM_OPERAND_TYPE_FPR:
WriteText(dc, wxString::Format("f%d", disasmInstr.operand[o].registerIndex), position, theme_syntaxFPR);
break;
case PPCASM_OPERAND_TYPE_SPR:
WriteText(dc, wxString::Format("spr%d", disasmInstr.operand[o].registerIndex), position, theme_syntaxSPR);
break;
case PPCASM_OPERAND_TYPE_CR:
WriteText(dc, wxString::Format("cr%d", disasmInstr.operand[o].registerIndex), position, theme_syntaxCR);
break;
case PPCASM_OPERAND_TYPE_IMM:
{
wxString string;
if (disasmInstr.operand[o].isSignedImm)
{
sint32 sImm = disasmInstr.operand[o].immS32;
if (disasmInstr.operand[o].immWidth == 16 && (sImm & 0x8000))
sImm |= (sint32)0xFFFF0000;
if ((sImm > -10 && sImm < 10) || forceDecDisplay)
string = wxString::Format("%d", sImm);
else
{
if (sImm < 0)
string = wxString::Format("-0x%x", -sImm);
else
string = wxString::Format("0x%x", sImm);
}
}
else
{
uint32 uImm = disasmInstr.operand[o].immS32;
if ((uImm >= 0 && uImm < 10) || forceDecDisplay)
string = wxString::Format("%u", uImm);
else
string = wxString::Format("0x%x", uImm);
}
WriteText(dc, string, position, theme_syntaxIMM);
break;
}
case PPCASM_OPERAND_TYPE_PSQMODE:
{
if (disasmInstr.operand[o].immS32)
WriteText(dc, "single", position, theme_syntaxIMM);
else
WriteText(dc, "paired", position, theme_syntaxIMM);
break;
}
case PPCASM_OPERAND_TYPE_CIMM:
{
wxString string;
// use symbol for function calls if available
uint32 callDest = disasmInstr.operand[o].immU32;
RPLStoredSymbol* storedSymbol = nullptr;
if (disasmInstr.ppcAsmCode == PPCASM_OP_BL || disasmInstr.ppcAsmCode == PPCASM_OP_BLA)
storedSymbol = rplSymbolStorage_getByAddress(callDest);
if (storedSymbol)
{
// if symbol is within same module then don't display libname prefix
RPLModule* rplModuleCurrent = RPLLoader_FindModuleByCodeAddr(virtualAddress); // cache this
if (rplModuleCurrent && callDest >= rplModuleCurrent->regionMappingBase_text.GetMPTR() && callDest < (rplModuleCurrent->regionMappingBase_text.GetMPTR() + rplModuleCurrent->regionSize_text))
string = wxString((char*)storedSymbol->symbolName);
else
string = wxString::Format("%s.%s", (char*)storedSymbol->libName, (char*)storedSymbol->symbolName);
// truncate name after 36 characters
if (string.Length() >= 36)
{
string.Truncate(34);
string.Append("..");
}
}
else
{
string = wxString::Format("0x%08x", disasmInstr.operand[o].immU32);
}
WriteText(dc, string, position, theme_syntaxCallIMM);
if (disasmInstr.ppcAsmCode != PPCASM_OP_BL)
{
wxString x = wxString(" ");
if (callDest <= virtualAddress)
x.Append(wxUniChar(0x2191)); // arrow up
else
x.Append(wxUniChar(0x2193)); // arrow down
WriteText(dc, x, position, theme_textForeground);
}
break;
}
case PPCASM_OPERAND_TYPE_MEM:
{
// offset
wxString string;
if (disasmInstr.operand[o].isSignedImm && disasmInstr.operand[o].immS32 >= 0)
string = wxString::Format("+0x%x", disasmInstr.operand[o].immS32);
else
string = wxString::Format("-0x%x", -disasmInstr.operand[o].immS32);
WriteText(dc, string, position, theme_syntaxIMMOffset);
WriteText(dc, "(", position, theme_textForeground);
// register
WriteText(dc, wxString::Format("r%d", disasmInstr.operand[o].registerIndex), position, theme_syntaxGPR);
WriteText(dc, ")", position, theme_textForeground);
break;
}
default:
// TODO
WriteText(dc, "<TODO>", position, wxColour(0xFF444444));
}
}
position.x = start_width + OFFSET_DISASSEMBLY;
const auto comment = static_cast<rplDebugSymbolComment*>(rplDebugSymbol_getForAddress(virtualAddress));
if (comment && comment->type == RplDebugSymbolComment)
WriteText(dc, comment->comment, position, theme_textForeground);
else if (isRLWINM)
{
sint32 rS, rA, SH, MB, ME;
rS = (opcode >> 21) & 0x1f;
rA = (opcode >> 16) & 0x1f;
SH = (opcode >> 11) & 0x1f;
MB = (opcode >> 6) & 0x1f;
ME = (opcode >> 1) & 0x1f;
uint32 mask = ppcAssembler_generateMaskRLW(MB, ME);
wxString string;
if (SH == 0)
string = wxString::Format("r%d=r%d&0x%x", rA, rS, mask);
else if ((0xFFFFFFFF << (uint32)disasmInstr.operand[2].immS32) == mask)
string = wxString::Format("r%d=r%d<<%d", rA, rS, SH);
else if ((0xFFFFFFFF >> (32 - SH) == mask))
string = wxString::Format("r%d=r%d>>%d", rA, rS, 32 - SH);
else
string = wxString::Format("r%d=(r%d<<<%d)&0x%x", rA, rS, SH, mask);
WriteText(dc, string, position, theme_textForegroundMuted);
}
else if (disasmInstr.ppcAsmCode == PPCASM_OP_SUBF || disasmInstr.ppcAsmCode == PPCASM_OP_SUBF_)
{
sint32 rD, rA, rB;
rD = (opcode >> 21) & 0x1f;
rA = (opcode >> 16) & 0x1f;
rB = (opcode >> 11) & 0x1f;
wxString string;
string = wxString::Format("r%d=r%d-r%d", rD, rB, rA);
WriteText(dc, string, position, theme_textForegroundMuted);
}
}
void DisasmCtrl::DrawLabelName(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, RPLStoredSymbol* storedSymbol)
{
wxString symbol_string = wxString::Format("%s:", (char*)storedSymbol->symbolName);
if (symbol_string.Length() > MAX_SYMBOL_LEN)
{
symbol_string.Truncate(MAX_SYMBOL_LEN - 3);
symbol_string.Append("..:");
}
wxPoint tmpPos(linePosition);
WriteText(dc, symbol_string, tmpPos, theme_syntaxSymbol);
}
void DisasmCtrl::OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position)
{
wxPoint position(0, 0);
RPLModule* current_rpl_module = RPLLoader_FindModuleByCodeAddr(GetViewBaseAddress());
if (currentCodeRegionStart == currentCodeRegionEnd)
return;
sint32 viewFirstLine = GetViewStart().y;
sint32 lineOffset = start - viewFirstLine;
cemu_assert_debug(lineOffset >= 0);
sint32 instructionIndex = 0;
sint32 numLinesToUpdate = lineOffset + count;
numLinesToUpdate = std::min(numLinesToUpdate, (sint32)m_elements_visible);
if(m_lineToAddress.size() != m_elements_visible)
m_lineToAddress.resize(m_elements_visible);
sint32 lineIndex = 0;
while(lineIndex < numLinesToUpdate)
{
const uint32 virtualAddress = GetViewBaseAddress() + instructionIndex * 4;
instructionIndex++;
if (virtualAddress < currentCodeRegionStart ||
virtualAddress >= currentCodeRegionEnd)
{
NextLine(position, &start_position);
m_lineToAddress[lineIndex] = std::nullopt;
lineIndex++;
continue;
}
// check if valid memory address
if (!memory_isAddressRangeAccessible(virtualAddress, 4))
{
NextLine(position, &start_position);
m_lineToAddress[lineIndex] = std::nullopt;
lineIndex++;
continue;
}
// draw symbol as label
RPLStoredSymbol* storedSymbol = rplSymbolStorage_getByAddress(virtualAddress);
if (storedSymbol)
{
if(lineIndex >= lineOffset)
DrawLabelName(dc, position, virtualAddress, storedSymbol);
m_lineToAddress[lineIndex] = virtualAddress;
lineIndex++;
if (lineIndex >= numLinesToUpdate)
break;
NextLine(position, &start_position);
}
m_lineToAddress[lineIndex] = virtualAddress;
if (lineIndex >= lineOffset)
DrawDisassemblyLine(dc, position, virtualAddress, current_rpl_module);
NextLine(position, &start_position);
lineIndex++;
}
// draw vertical separator lines: offset | disassembly | comment
dc.SetPen(*wxLIGHT_GREY_PEN);
wxPoint line_from = start_position;
line_from.x = OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE - 5;
wxPoint line_to = line_from;
line_to.y += (count + 1) * m_line_height;
dc.DrawLine(line_from, line_to);
line_from.x += OFFSET_DISASSEMBLY;
line_to.x += OFFSET_DISASSEMBLY;
dc.DrawLine(line_from, line_to);
}
void DisasmCtrl::OnMouseMove(const wxPoint& start_position, uint32 line)
{
/*m_mouse_line = line;
if (m_mouse_line_drawn != -1)
RefreshLine(m_mouse_line_drawn);
if (m_mouse_line != -1)
RefreshLine(m_mouse_line);*/
wxPoint position = start_position;
// address
if (position.x <= OFFSET_ADDRESS)
{
return;
}
position.x -= OFFSET_ADDRESS;
// relative offset
if (position.x <= OFFSET_ADDRESS_RELATIVE)
{
return;
}
position.x -= OFFSET_ADDRESS_RELATIVE;
// disassembly code
if (position.x <= OFFSET_DISASSEMBLY)
{
if(m_mouse_down)
{
}
if (position.x <= OFFSET_DISASSEMBLY_OPERAND)
{
return;
}
position.x -= OFFSET_DISASSEMBLY_OPERAND;
}
}
void DisasmCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position)
{
auto optVirtualAddress = LinePixelPosToAddress(position.y);
switch (key_code)
{
case WXK_F9:
{
if (optVirtualAddress)
{
debugger_toggleExecuteBreakpoint(*optVirtualAddress);
wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE);
wxPostEvent(this->m_parent, evt);
}
return;
}
case 'G':
{
if(IsKeyDown(WXK_CONTROL))
{
GoToAddressDialog();
return;
}
}
}
// debugger currently in break state
PPCInterpreter_t* debugCpu = debugger_lockDebugSession();
if (debugCpu)
{
switch (key_code)
{
case WXK_F5:
{
debugger_stepCommand(DebuggerStepCommand::Run);
break;
}
case WXK_F10:
{
debugger_stepCommand(DebuggerStepCommand::StepOver);
break;
}
case WXK_F11:
{
debugger_stepCommand(DebuggerStepCommand::StepInto);
break;
}
}
debugger_unlockDebugSession(debugCpu);
}
else
{
if (key_code == WXK_F5)
{
debugger_requestBreak();
}
}
}
void DisasmCtrl::OnMouseDClick(const wxPoint& position, uint32 line)
{
wxPoint pos = position;
auto optVirtualAddress = LinePixelPosToAddress(position.y - GetViewStart().y * m_line_height);
if (!optVirtualAddress)
return;
MPTR virtualAddress = *optVirtualAddress;
// address
if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE)
{
// address + address relative
debugger_toggleExecuteBreakpoint(virtualAddress);
RefreshLine(line);
wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE);
wxPostEvent(this->m_parent, evt);
return;
}
else if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE + OFFSET_DISASSEMBLY)
{
// double-clicked on disassembly (operation and operand data)
wxString currentInstruction = wxEmptyString;
wxTextEntryDialog set_value_dialog(this, _("Enter a new instruction."), wxString::Format(_("Overwrite instruction at address %08x"), virtualAddress), currentInstruction);
if (set_value_dialog.ShowModal() == wxID_OK)
{
PPCAssemblerInOut ctx = { 0 };
ctx.virtualAddress = virtualAddress;
if (ppcAssembler_assembleSingleInstruction(set_value_dialog.GetValue().c_str(), &ctx))
{
debugger_createPatch(virtualAddress, { ctx.outputData.data(), ctx.outputData.size() });
RefreshLine(line);
}
}
return;
}
else
{
// comment
const auto comment = static_cast<rplDebugSymbolComment*>(rplDebugSymbol_getForAddress(virtualAddress));
wxString old_comment = wxEmptyString;
if (comment && comment->type == RplDebugSymbolComment)
old_comment = comment->comment;
wxTextEntryDialog set_value_dialog(this, _("Enter a new comment."), wxString::Format(_("Create comment at address %08x"), virtualAddress), old_comment);
if (set_value_dialog.ShowModal() == wxID_OK)
{
rplDebugSymbol_createComment(virtualAddress, set_value_dialog.GetValue().wc_str());
RefreshLine(line);
}
return;
}
}
void DisasmCtrl::CopyToClipboard(std::string text)
{
if (wxClipboard::Get()->Open())
{
wxClipboard::Get()->SetData(new wxTextDataObject(text));
wxClipboard::Get()->Close();
}
}
static uint32 GetUnrelocatedAddress(MPTR address)
{
RPLModule* rplModule = RPLLoader_FindModuleByCodeAddr(address);
if (!rplModule)
return 0;
if (address >= rplModule->regionMappingBase_text.GetMPTR() && address < (rplModule->regionMappingBase_text.GetMPTR() + rplModule->regionSize_text))
return 0x02000000 + (address - rplModule->regionMappingBase_text.GetMPTR());
return 0;
}
void DisasmCtrl::OnContextMenu(const wxPoint& position, uint32 line)
{
auto optVirtualAddress = LinePixelPosToAddress(position.y - GetViewStart().y * m_line_height);
if (!optVirtualAddress)
return;
MPTR virtualAddress = *optVirtualAddress;
m_contextMenuAddress = virtualAddress;
// show dialog
wxMenu menu;
menu.Append(IDContextMenu_ToggleBreakpoint, _("Toggle breakpoint"));
menu.Append(IDContextMenu_ToggleLoggingBreakpoint, _("Toggle logging point"));
if(debugger_hasPatch(virtualAddress))
menu.Append(IDContextMenu_RestoreOriginalInstructions, _("Restore original instructions"));
menu.AppendSeparator();
menu.Append(IDContextMenu_CopyAddress, _("Copy address"));
uint32 unrelocatedAddress = GetUnrelocatedAddress(virtualAddress);
if (unrelocatedAddress && unrelocatedAddress != virtualAddress)
menu.Append(IDContextMenu_CopyUnrelocatedAddress, _("Copy virtual address (for IDA/Ghidra)"));
PopupMenu(&menu);
}
void DisasmCtrl::OnContextMenuEntryClicked(wxCommandEvent& event)
{
switch(event.GetId())
{
case IDContextMenu_ToggleBreakpoint:
{
debugger_toggleExecuteBreakpoint(m_contextMenuAddress);
wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE);
wxPostEvent(this->m_parent, evt);
break;
}
case IDContextMenu_ToggleLoggingBreakpoint:
{
debugger_toggleLoggingBreakpoint(m_contextMenuAddress);
wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE);
wxPostEvent(this->m_parent, evt);
break;
}
case IDContextMenu_RestoreOriginalInstructions:
{
debugger_removePatch(m_contextMenuAddress);
wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); // This also refreshes the disassembly view
wxPostEvent(this->m_parent, evt);
break;
}
case IDContextMenu_CopyAddress:
{
CopyToClipboard(fmt::format("{:#10x}", m_contextMenuAddress));
break;
}
case IDContextMenu_CopyUnrelocatedAddress:
{
uint32 unrelocatedAddress = GetUnrelocatedAddress(m_contextMenuAddress);
CopyToClipboard(fmt::format("{:#10x}", unrelocatedAddress));
break;
}
default:
UNREACHABLE;
}
}
bool DisasmCtrl::OnShowTooltip(const wxPoint& position, uint32 line)
{
return false;
}
void DisasmCtrl::ScrollWindow(int dx, int dy, const wxRect* prect)
{
// scroll and repaint everything
RefreshControl(nullptr);
TextList::ScrollWindow(dx, dy, nullptr);
}
wxSize DisasmCtrl::DoGetBestSize() const
{
return TextList::DoGetBestSize();
}
void DisasmCtrl::OnUpdateView()
{
RefreshControl();
}
uint32 DisasmCtrl::GetViewBaseAddress() const
{
if (GetViewStart().y < 0)
return MPTR_NULL;
return currentCodeRegionStart + GetViewStart().y * 4;
}
std::optional<MPTR> DisasmCtrl::LinePixelPosToAddress(sint32 posY)
{
if (posY < 0)
return std::nullopt;
sint32 lineIndex = posY / m_line_height;
if (lineIndex >= m_lineToAddress.size())
return std::nullopt;
return m_lineToAddress[lineIndex];
}
uint32 DisasmCtrl::AddressToScrollPos(uint32 offset) const
{
return (offset - currentCodeRegionStart) / 4;
}
void DisasmCtrl::CenterOffset(uint32 offset)
{
if (offset < currentCodeRegionStart || offset >= currentCodeRegionEnd)
SelectCodeRegion(offset);
const sint32 line = AddressToScrollPos(offset);
if (line < 0 || line >= (sint32)m_element_count)
return;
if (m_active_line != -1)
RefreshLine(m_active_line);
DoScroll(0, std::max(0, line - (sint32)(m_elements_visible / 2)));
m_active_line = line;
RefreshLine(m_active_line);
}
void DisasmCtrl::GoToAddressDialog()
{
wxTextEntryDialog goto_dialog(this, _("Enter a target address."), _("GoTo address"), wxEmptyString);
if (goto_dialog.ShowModal() == wxID_OK)
{
ExpressionParser parser;
auto value = goto_dialog.GetValue().ToStdString();
std::transform(value.begin(), value.end(), value.begin(), tolower);
// trim any leading spaces
while(!value.empty() && value[0] == ' ')
value.erase(value.begin());
debugger_addParserSymbols(parser);
// try to parse expression as hex value first (it should interpret 1234 as 0x1234, not 1234)
if (parser.IsConstantExpression("0x"+value))
{
const auto result = (uint32)parser.Evaluate("0x"+value);
m_lastGotoTarget = result;
CenterOffset(result);
wxCommandEvent evt(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS);
evt.SetExtraLong(static_cast<long>(result));
wxPostEvent(GetParent(), evt);
}
else if (parser.IsConstantExpression(value))
{
const auto result = (uint32)parser.Evaluate(value);
m_lastGotoTarget = result;
CenterOffset(result);
wxCommandEvent evt(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS);
evt.SetExtraLong(static_cast<long>(result));
wxPostEvent(GetParent(), evt);
}
else
{
try
{
// if not a constant expression (i.e. relying on unknown variables), then evaluating will throw an exception with a detailed error message
const auto _ = (uint32)parser.Evaluate(value);
}
catch (const std::exception& ex)
{
wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
}
}
}
}
-60
View File
@@ -1,60 +0,0 @@
#pragma once
#include "wxgui/components/TextList.h"
wxDECLARE_EVENT(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS, wxCommandEvent); // Notify parent that goto address operation completed. Event contains the address that was jumped to.
class DisasmCtrl : public TextList
{
enum
{
IDContextMenu_ToggleBreakpoint = wxID_HIGHEST + 1,
IDContextMenu_ToggleLoggingBreakpoint,
IDContextMenu_RestoreOriginalInstructions,
IDContextMenu_CopyAddress,
IDContextMenu_CopyUnrelocatedAddress,
IDContextMenu_Last
};
public:
DisasmCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style);
void Init();
wxSize DoGetBestSize() const override;
void OnUpdateView();
uint32 GetViewBaseAddress() const;
std::optional<MPTR> LinePixelPosToAddress(sint32 posY);
uint32 AddressToScrollPos(uint32 offset) const;
void CenterOffset(uint32 offset);
void GoToAddressDialog();
protected:
void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) override;
void OnMouseMove(const wxPoint& position, uint32 line) override;
void OnKeyPressed(sint32 key_code, const wxPoint& position) override;
void OnMouseDClick(const wxPoint& position, uint32 line) override;
void OnContextMenu(const wxPoint& position, uint32 line) override;
void OnContextMenuEntryClicked(wxCommandEvent& event);
bool OnShowTooltip(const wxPoint& position, uint32 line) override;
void ScrollWindow(int dx, int dy, const wxRect* prect) override;
void DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, struct RPLModule* rplModule);
void DrawLabelName(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, struct RPLStoredSymbol* storedSymbol);
void SelectCodeRegion(uint32 newAddress);
private:
void CopyToClipboard(std::string text);
sint32 m_mouse_line, m_mouse_line_drawn;
sint32 m_active_line;
uint32 m_lastGotoTarget{};
uint32 m_contextMenuAddress{};
// code region info
uint32 currentCodeRegionStart;
uint32 currentCodeRegionEnd;
// line to address mapping
std::vector<std::optional<MPTR>> m_lineToAddress;
};
-441
View File
@@ -1,441 +0,0 @@
#include "wxgui/wxgui.h"
#include "wxgui/debugger/DumpCtrl.h"
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/OS/RPL/rpl_structs.h"
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
#include "util/helpers/helpers.h"
#include "Cemu/ExpressionParser/ExpressionParser.h"
#define OFFSET_ADDRESS (60)
#define OFFSET_ADDRESS_RELATIVE (90)
#define OFFSET_MEMORY (450)
enum {
ID_WRITE_U8 = wxID_HIGHEST + 1,
ID_WRITE_U16,
ID_WRITE_U32,
ID_WRITE_FLOAT,
ID_WRITE_STRING
};
DumpCtrl::DumpCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style)
: TextList(parent, id, pos, size, style)
{
MMURange* range = memory_getMMURangeByAddress(0x10000000);
if (range)
{
m_memoryRegion.baseAddress = range->getBase();
m_memoryRegion.size = range->getSize();
Init();
}
else
{
m_memoryRegion.baseAddress = 0x10000000;
m_memoryRegion.size = 0x1000;
Init();
}
Bind(wxEVT_MENU, &DumpCtrl::OnMenuSelected, this);
}
void DumpCtrl::Init()
{
uint32 element_count = m_memoryRegion.size;
this->SetElementCount(element_count / 0x10);
Scroll(0, 0);
RefreshControl();
}
void DumpCtrl::OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position)
{
wxPoint position = start_position;
uint32 endAddr = m_memoryRegion.baseAddress + m_memoryRegion.size;
RPLModule* currentCodeRPL = RPLLoader_FindModuleByCodeAddr(LineToOffset(start));
RPLModule* currentDataRPL = RPLLoader_FindModuleByDataAddr(LineToOffset(start));
for (sint32 i = 0; i <= count; i++)
{
const uint32 virtual_address = LineToOffset(start + i);
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
dc.DrawText(wxString::Format("%08x", virtual_address), position);
position.x += OFFSET_ADDRESS;
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT));
if (currentCodeRPL)
{
dc.DrawText(wxString::Format("+0x%-8x", virtual_address - currentCodeRPL->regionMappingBase_text.GetMPTR()), position);
}
else if (currentDataRPL)
{
dc.DrawText(wxString::Format("+0x%-8x", virtual_address - currentDataRPL->regionMappingBase_data), position);
}
else
{
dc.DrawText("???", position);
}
position.x += OFFSET_ADDRESS_RELATIVE;
sint32 start_width = position.x;
if (!memory_isAddressRangeAccessible(virtual_address, 0x10))
{
for (sint32 f=0; f<0x10; f++)
{
wxPoint p(position);
WriteText(dc, wxString::Format("?? "), p);
position.x += (m_char_width * 3);
}
}
else
{
std::array<uint8, 0x10> data;
memory_readBytes(virtual_address, data);
for (auto b : data)
{
wxPoint p(position);
WriteText(dc, wxString::Format("%02x ", b), p);
position.x += (m_char_width * 3);
}
position.x = start_width = OFFSET_MEMORY;
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
for (auto b : data)
{
if (isprint(b))
dc.DrawText(wxString::Format("%c ", b), position);
else
dc.DrawText(".", position);
position.x += m_char_width;
}
}
// display goto indicator
if (m_lastGotoOffset >= virtual_address && m_lastGotoOffset < (virtual_address + 16))
{
sint32 indicatorX = start_position.x + OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE + (m_lastGotoOffset - virtual_address) * m_char_width * 3;
wxPoint line1(start_position.x + indicatorX - 2, position.y);
wxPoint line2(line1.x, line1.y + m_line_height);
dc.SetPen(*wxRED_PEN);
dc.DrawLine(line1, line2);
dc.DrawLine(line1, wxPoint(line1.x + 3, line1.y));
dc.DrawLine(line2, wxPoint(line2.x + 3, line2.y));
}
NextLine(position, &start_position);
}
// draw vertical separator lines for 4 byte blocks
sint32 cursorOffsetHexBytes = start_position.x + OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE;
wxPoint line_from(
cursorOffsetHexBytes + m_char_width * (3 * 4 - 1) + m_char_width / 2,
start_position.y
);
wxPoint line_to(line_from.x, line_from.y + m_line_height * (count + 1));
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_INACTIVECAPTIONTEXT));
for (sint32 i = 0; i < 3; i++)
{
dc.DrawLine(line_from, line_to);
line_from.x += m_char_width * (3 * 4);
line_to.x += m_char_width * (3 * 4);
}
}
void DumpCtrl::OnMouseMove(const wxPoint& start_position, uint32 line)
{
wxPoint position = start_position;
// address
if (position.x <= OFFSET_ADDRESS)
{
return;
}
position.x -= OFFSET_ADDRESS;
// relative offset
if (position.x <= OFFSET_ADDRESS_RELATIVE)
{
return;
}
position.x -= OFFSET_ADDRESS_RELATIVE;
// byte code
if (position.x <= OFFSET_MEMORY)
{
}
// string view
position.x -= OFFSET_MEMORY;
}
uint32 DumpCtrl::PositionToAddress(const wxPoint& position, uint32 line)
{
wxPoint pos = position;
if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE)
return MPTR_NULL;
pos.x -= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE;
if (pos.x > OFFSET_MEMORY)
return MPTR_NULL;
const uint32 byteIndex = (pos.x / m_char_width) / 3;
return LineToOffset(line) + byteIndex;
}
void DumpCtrl::OnMouseDClick(const wxPoint& position, uint32 line)
{
uint32 address = PositionToAddress(position, line);
if (address == MPTR_NULL)
return;
if (!memory_isAddressRangeAccessible(address, 1))
return;
if (WriteNumericDialog<uint8>(address))
{
wxRect updateRect(0, line * m_line_height, GetSize().x, m_line_height);
RefreshControl(&updateRect);
}
}
void DumpCtrl::GoToAddressDialog()
{
wxTextEntryDialog goto_dialog(this, _("Enter a target address."), _("GoTo address"), wxEmptyString);
if (goto_dialog.ShowModal() == wxID_OK)
{
ExpressionParser parser;
auto value = goto_dialog.GetValue().ToStdString();
std::transform(value.begin(), value.end(), value.begin(), tolower);
debugger_addParserSymbols(parser);
// try to parse expression as hex value first (it should interpret 1234 as 0x1234, not 1234)
if (parser.IsConstantExpression("0x"+value))
{
const auto result = (uint32)parser.Evaluate("0x"+value);
m_lastGotoOffset = result;
CenterOffset(result);
}
else if (parser.IsConstantExpression(value))
{
const auto result = (uint32)parser.Evaluate(value);
m_lastGotoOffset = result;
CenterOffset(result);
}
else
{
try
{
const auto _ = (uint32)parser.Evaluate(value);
}
catch (const std::exception& ex)
{
wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
}
}
}
}
void DumpCtrl::CenterOffset(uint32 offset)
{
// check if the offset is valid
if (!memory_isAddressRangeAccessible(offset, 1))
return;
// set region and line
MMURange* range = memory_getMMURangeByAddress(offset);
if (m_memoryRegion.baseAddress != range->getBase() || m_memoryRegion.size != range->getSize())
{
m_memoryRegion.baseAddress = range->getBase();
m_memoryRegion.size = range->getSize();
Init();
}
const sint32 line = OffsetToLine(offset);
if (line < 0 || line >= (sint32)m_element_count)
return;
DoScroll(0, std::max(0, line - ((sint32)m_elements_visible / 2)));
RefreshControl();
//RefreshLine(line);
}
uint32 DumpCtrl::LineToOffset(uint32 line)
{
return m_memoryRegion.baseAddress + line * 0x10;
}
uint32 DumpCtrl::OffsetToLine(uint32 offset)
{
return (offset - m_memoryRegion.baseAddress) / 0x10;
}
void DumpCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position)
{
switch (key_code)
{
case 'G':
{
if (IsKeyDown(WXK_CONTROL))
{
GoToAddressDialog();
return;
}
}
}
}
wxSize DumpCtrl::DoGetBestSize() const
{
return TextList::DoGetBestSize();
}
void DumpCtrl::OnContextMenu(const wxPoint& position, uint32 line)
{
const uint32 address = PositionToAddress(position, line);
if (!memory_isAddressRangeAccessible(address, 1))
return;
m_writerContextAddress = address;
m_writerContextLine = line;
wxMenu menu;
menu.Append(ID_WRITE_U8, _("Write Byte"));
menu.Append(ID_WRITE_U16, _("Write Int16"));
menu.Append(ID_WRITE_U32, _("Write Int32"));
menu.Append(ID_WRITE_FLOAT, _("Write Float"));
menu.Append(ID_WRITE_STRING, _("Write String"));
PopupMenu(&menu);
}
void DumpCtrl::OnMenuSelected(wxCommandEvent& event)
{
bool update = false;
switch (event.GetId())
{
case ID_WRITE_U8:
update = WriteNumericDialog<uint8>(m_writerContextAddress);
break;
case ID_WRITE_U16:
update = WriteNumericDialog<uint16>(m_writerContextAddress);
break;
case ID_WRITE_U32:
update = WriteNumericDialog<uint32>(m_writerContextAddress);
break;
case ID_WRITE_FLOAT:
update = WriteNumericDialog<float>(m_writerContextAddress);
break;
case ID_WRITE_STRING:
update = WriteString(m_writerContextAddress);
break;
}
if (update)
{
wxRect updateRect(0, m_writerContextLine * m_line_height, GetSize().x, m_line_height);
RefreshControl(&updateRect);
}
}
template <typename T>
bool DumpCtrl::WriteNumericDialog(uint32 address)
{
static_assert(
std::is_same<T, uint8>::value ||
std::is_same<T, uint16>::value ||
std::is_same<T, uint32>::value ||
std::is_same<T, float>::value,
"Unsupported type"
);
T value;
const char* dataType;
wxString label;
if constexpr (std::is_same<T, uint8>::value)
{
value = memory_readU8(address);
dataType = "byte";
label = wxString::Format("0x%02x", value);
}
else if constexpr (std::is_same<T, uint16>::value)
{
value = memory_readU16(address);
dataType = "int16";
label = wxString::Format("0x%04x", value);
}
else if constexpr (std::is_same<T, uint32>::value)
{
value = memory_readU32(address);
dataType = "int32";
label = wxString::Format("0x%08x", value);
}
else if constexpr (std::is_same<T, float>::value)
{
value = memory_readFloat(address);
dataType = "float";
label = wxString::Format("%f", value);
}
wxTextEntryDialog dialog(
this,
_("Enter a new value."),
wxString::Format(_("Write %s at address 0x%08x"), dataType, address),
label
);
if (dialog.ShowModal() != wxID_OK)
return false;
const T newValue = ConvertString<T>(dialog.GetValue().ToStdString());
if constexpr (std::is_same<T, uint8>::value)
memory_writeU8(address, newValue);
else if constexpr (std::is_same<T, uint16>::value)
memory_writeU16(address, newValue);
else if constexpr (std::is_same<T, uint32>::value)
memory_writeU32(address, newValue);
else if constexpr (std::is_same<T, float>::value)
memory_writeFloat(address, newValue);
return true;
}
bool DumpCtrl::WriteString(uint32 address)
{
wxTextEntryDialog dialog(
this,
_("Enter string"),
wxString::Format(_("Write string at address 0x%08x"), address),
""
);
if (dialog.ShowModal() != wxID_OK)
return false;
std::string text = dialog.GetValue().ToStdString();
if (text.empty())
return false;
for (size_t i = 0; i < text.size(); i++)
memory_writeU8(address + i, static_cast<uint8>(text[i]));
// null-terminator
memory_writeU8(address + text.size(), 0);
return true;
}
-38
View File
@@ -1,38 +0,0 @@
#pragma once
#include "wxgui/components/TextList.h"
class DumpCtrl : public TextList
{
public:
DumpCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style);
void Init();
wxSize DoGetBestSize() const override;
protected:
void GoToAddressDialog();
void CenterOffset(uint32 offset);
uint32 LineToOffset(uint32 line);
uint32 OffsetToLine(uint32 offset);
uint32 PositionToAddress(const wxPoint& position, uint32 line);
void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) override;
void OnMouseMove(const wxPoint& position, uint32 line) override;
void OnMouseDClick(const wxPoint& position, uint32 line) override;
void OnKeyPressed(sint32 key_code, const wxPoint& position) override;
void OnContextMenu(const wxPoint& position, uint32 line) override;
void OnMenuSelected(wxCommandEvent& event);
template <typename T>
bool WriteNumericDialog(uint32 address);
bool WriteString(uint32 address);
private:
struct
{
uint32 baseAddress;
uint32 size;
}m_memoryRegion;
uint32 m_lastGotoOffset{0};
uint32 m_writerContextAddress {0};
uint32 m_writerContextLine {0};
};
-46
View File
@@ -1,46 +0,0 @@
#include "wxgui/wxgui.h"
#include "wxgui/debugger/DumpWindow.h"
#include "wxgui/debugger/DebuggerWindow2.h"
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
#include "wxgui/debugger/DumpCtrl.h"
enum
{
// REGISTER
REGISTER_ADDRESS_R0 = wxID_HIGHEST + 8200,
REGISTER_LABEL_R0 = REGISTER_ADDRESS_R0 + 32,
REGISTER_LABEL_FPR0_0 = REGISTER_LABEL_R0 + 32,
REGISTER_LABEL_FPR1_0 = REGISTER_LABEL_R0 + 32,
};
DumpWindow::DumpWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size)
: wxFrame(&parent, wxID_ANY, _("Memory Dump"), wxDefaultPosition, wxSize(600, 250), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT)
{
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
m_dump_ctrl = new DumpCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxScrolledWindowStyle);
main_sizer->Add(m_dump_ctrl, 1, wxEXPAND);
this->SetSizer(main_sizer);
this->wxWindowBase::Layout();
this->Centre(wxBOTH);
if (parent.GetConfig().data().pin_to_main)
OnMainMove(main_position, main_size);
}
void DumpWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size)
{
wxSize size(600, 250);
this->SetSize(size);
wxPoint position = main_position;
position.y += main_size.GetHeight();
this->SetPosition(position);
}
void DumpWindow::OnGameLoaded()
{
m_dump_ctrl->Init();
}
-18
View File
@@ -1,18 +0,0 @@
#pragma once
#include "wxgui/debugger/DumpCtrl.h"
class DebuggerWindow2;
class DumpWindow : public wxFrame
{
public:
DumpWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size);
void OnMainMove(const wxPoint& position, const wxSize& main_size);
void OnGameLoaded();
private:
wxScrolledWindow* m_scrolled_window;
DumpCtrl* m_dump_ctrl;
};
-132
View File
@@ -1,132 +0,0 @@
#include "wxgui/wxgui.h"
#include "wxgui/debugger/ModuleWindow.h"
#include <sstream>
#include "wxgui/debugger/DebuggerWindow2.h"
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/OS/RPL/rpl_structs.h"
#include "Cafe/GraphicPack/GraphicPack2.h"
enum ItemColumns
{
ColumnName = 0,
ColumnAddress,
ColumnSize,
};
ModuleWindow::ModuleWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size)
: wxFrame(&parent, wxID_ANY, _("Modules"), wxDefaultPosition, wxSize(420, 250), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT)
{
this->SetSizeHints(wxDefaultSize, wxDefaultSize);
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
m_modules = new wxListView(this, wxID_ANY);
wxListItem col0;
col0.SetId(ColumnName);
col0.SetText(_("Name"));
col0.SetWidth(125);
m_modules->InsertColumn(ColumnName, col0);
wxListItem col1;
col1.SetId(ColumnAddress);
col1.SetText(_("Address"));
col1.SetWidth(75);
col1.SetAlign(wxLIST_FORMAT_RIGHT);
m_modules->InsertColumn(ColumnAddress, col1);
wxListItem col2;
col2.SetId(ColumnSize);
col2.SetText(_("Size"));
col2.SetWidth(75);
col2.SetAlign(wxLIST_FORMAT_RIGHT);
m_modules->InsertColumn(ColumnSize, col2);
main_sizer->Add(m_modules, 1, wxEXPAND);
this->SetSizer(main_sizer);
this->wxWindowBase::Layout();
this->Centre(wxBOTH);
if (parent.GetConfig().data().pin_to_main)
OnMainMove(main_position, main_size);
m_modules->Bind(wxEVT_LEFT_DCLICK, &ModuleWindow::OnLeftDClick, this);
OnGameLoaded();
}
void ModuleWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size)
{
wxSize size(420, 250);
this->SetSize(size);
wxPoint position = main_position;
position.x -= 420;
position.y += main_size.y;
this->SetPosition(position);
}
void ModuleWindow::OnGameLoaded()
{
Freeze();
m_modules->DeleteAllItems();
const auto module_list = RPLLoader_GetModuleList();
for (size_t i = 0; i < module_list.size(); i++)
{
const auto module = module_list[i];
if (module)
{
wxListItem item;
item.SetId(i);
item.SetText(module->moduleName);
const auto index = m_modules->InsertItem(item);
m_modules->SetItem(index, ColumnAddress, wxString::Format("%08x", module->regionMappingBase_text.GetMPTR()));
m_modules->SetItem(index, ColumnSize, wxString::Format("%x", module->regionSize_text));
}
}
sint32 patch_count = 0;
for (auto& gfx_pack : GraphicPack2::GetGraphicPacks())
{
for (auto& patch_group : gfx_pack->GetPatchGroups())
{
if (patch_group->isApplied())
{
wxListItem item;
item.SetId(module_list.size() + patch_count);
item.SetText(std::string(patch_group->getName()));
const auto index = m_modules->InsertItem(item);
m_modules->SetItem(index, ColumnAddress, wxString::Format("%08x", patch_group->getCodeCaveBase()));
m_modules->SetItem(index, ColumnSize, wxString::Format("%x", patch_group->getCodeCaveSize()));
patch_count++;
}
}
}
Thaw();
}
void ModuleWindow::OnLeftDClick(wxMouseEvent& event)
{
long selected = m_modules->GetFirstSelected();
if (selected == wxNOT_FOUND)
return;
const auto text = m_modules->GetItemText(selected, ColumnAddress);
const auto address = std::stoul(text.ToStdString(), nullptr, 16);
if (address == 0)
return;
debugger_jumpToAddressInDisasm(address);
}
-20
View File
@@ -1,20 +0,0 @@
#pragma once
#include <wx/frame.h>
class DebuggerWindow2;
class wxListView;
class ModuleWindow : public wxFrame
{
public:
ModuleWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size);
void OnMainMove(const wxPoint& position, const wxSize& main_size);
void OnGameLoaded();
private:
void OnLeftDClick(wxMouseEvent& event);
wxListView* m_modules;
};
-485
View File
@@ -1,485 +0,0 @@
#include "wxgui/wxgui.h"
#include "wxgui/debugger/RegisterWindow.h"
#include <sstream>
#include "wxgui/debugger/DebuggerWindow2.h"
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/OS/RPL/rpl_structs.h"
#include "Cafe/HW/Espresso/EspressoISA.h"
#include "util/helpers/helpers.h"
enum
{
// REGISTER
kRegisterValueR0 = wxID_HIGHEST + 8400,
kRegisterLabelR0 = kRegisterValueR0 + 32,
kRegisterLabelLR = kRegisterLabelR0 + 32,
kRegisterValueLR = kRegisterLabelLR + 1,
kRegisterValueFPR0_0 = kRegisterValueLR + 32,
kRegisterValueFPR1_0 = kRegisterValueFPR0_0 + 32,
kRegisterValueCR0 = kRegisterValueFPR1_0 + 32,
kContextMenuZero,
kContextMenuInc,
kContextMenuDec,
kContextMenuCopy,
kContextMenuGotoDisasm,
kContextMenuGotoDump,
};
RegisterWindow::RegisterWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size)
: wxFrame(&parent, wxID_ANY, _("Registers"), wxDefaultPosition, wxSize(400, 975), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT),
m_prev_snapshot({}), m_show_double_values(true), m_context_ctrl(nullptr)
{
SetSizeHints(wxDefaultSize, wxDefaultSize);
SetMaxSize({ 400, 975 });
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
auto scrolled_win = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
auto gpr_sizer = new wxFlexGridSizer(0, 3, 0, 0);
// GPRs
for(sint32 i = 0; i < 32; ++i)
{
gpr_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("R%d", i)), 0, wxLEFT, 5);
auto value = new wxTextCtrl(scrolled_win, kRegisterValueR0 + i, wxString::Format("%08x", 0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RICH2);
value->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
value->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
value->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this);
//value->Bind(wxEVT_CONTEXT_MENU, &RegisterWindow::OnValueContextMenu, this);
gpr_sizer->Add(value, 0, wxLEFT|wxRIGHT, 5);
auto label = new wxTextCtrl(scrolled_win, kRegisterLabelR0 + i, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RICH2);
label->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
label->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
label->SetMinSize(wxSize(500, -1));
gpr_sizer->Add(label, 0, wxEXPAND);
}
{
// LR
gpr_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("LR")), 0, wxLEFT, 5);
auto value = new wxTextCtrl(scrolled_win, kRegisterValueLR, wxString::Format("%08x", 0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RICH2);
value->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
value->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
value->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this);
//value->Bind(wxEVT_CONTEXT_MENU, &RegisterWindow::OnValueContextMenu, this);
gpr_sizer->Add(value, 0, wxLEFT | wxRIGHT, 5);
auto label = new wxTextCtrl(scrolled_win, kRegisterLabelLR, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RICH2);
label->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
label->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
label->SetMinSize(wxSize(500, -1));
gpr_sizer->Add(label, 0, wxEXPAND);
}
sizer->Add(gpr_sizer, 1, wxEXPAND);
auto button = new wxButton(scrolled_win, wxID_ANY, _("FP view mode"));
button->Bind(wxEVT_BUTTON, &RegisterWindow::OnFPViewModePress, this);
sizer->Add(button, 0, wxALL, 5);
auto fp_sizer = new wxFlexGridSizer(0, 3, 0, 0);
for (sint32 i = 0; i < 32; ++i)
{
fp_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("FP%d", i)), 0, wxLEFT, 5);
auto value0 = new wxTextCtrl(scrolled_win, kRegisterValueFPR0_0 + i, wxString::Format("%lf", 0.0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RICH2);
value0->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
value0->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
value0->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this);
fp_sizer->Add(value0, 0, wxLEFT | wxRIGHT, 5);
auto value1 = new wxTextCtrl(scrolled_win, kRegisterValueFPR1_0 + i, wxString::Format("%lf", 0.0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RICH2);
value1->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
value1->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
value1->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this);
fp_sizer->Add(value1, 0, wxLEFT | wxRIGHT, 5);
}
sizer->Add(fp_sizer, 0, wxEXPAND);
auto cr_sizer = new wxFlexGridSizer(0, 2, 0, 0);
// CRs
for (sint32 i = 0; i < 8; ++i)
{
cr_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("CR%d", i)), 0, wxLEFT, 5);
auto value = new wxTextCtrl(scrolled_win, kRegisterValueCR0 + i, "-", wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RICH2);
value->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
value->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
//value->Bind(wxEVT_CONTEXT_MENU, &RegisterWindow::OnValueContextMenu, this);
cr_sizer->Add(value, 0, wxRIGHT, 5);
}
sizer->Add(cr_sizer, 0, wxEXPAND);
scrolled_win->SetSizerAndFit(sizer);
scrolled_win->SetScrollRate(-1, 10);
main_sizer->Add(scrolled_win, 1, wxEXPAND);
SetSizer(main_sizer);
Layout();
if (parent.GetConfig().data().pin_to_main)
OnMainMove(main_position, main_size);
//Bind(wxEVT_COMMAND_MENU_SELECTED, &RegisterWindow::OnValueContextMenuSelected, this, kContextMenuZero, kContextMenuGotoDump);
}
void RegisterWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size)
{
wxSize size(400, 255 + main_size.y + 250);
this->SetSize(size);
wxPoint position = main_position;
position.x += main_size.x;
position.y -= 255;
this->SetPosition(position);
}
void RegisterWindow::UpdateIntegerRegister(wxTextCtrl* label, wxTextCtrl* value, uint32 registerValue, bool hasChanged)
{
//const auto value = dynamic_cast<wxTextCtrl*>(GetWindowChild(kRegisterValueR0 + i));
//wxASSERT(value);
//const bool has_changed = register_value != m_prev_snapshot.gpr[i];
if (hasChanged)
value->SetForegroundColour(m_changed_color);
else if (value->GetForegroundColour() != wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT))
value->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
value->ChangeValue(wxString::Format("%08x", registerValue));
//const auto label = dynamic_cast<wxTextCtrl*>(GetWindowChild(kRegisterLabelR0 + i));
//wxASSERT(label);
label->SetForegroundColour(hasChanged ? m_changed_color : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
// check if address is a string
if (registerValue >= MEMORY_DATA_AREA_ADDR && registerValue < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE))
{
bool is_valid_string = true;
std::stringstream buffer;
uint32 string_offset = registerValue;
while (string_offset < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE))
{
const uint8 c = memory_readU8(string_offset++);
if (isprint(c))
buffer << c;
else if (c == '\0')
{
buffer << c;
break;
}
else
{
is_valid_string = false;
break;
}
}
if (is_valid_string && buffer.tellp() > 1)
{
label->ChangeValue(wxString::Format("\"%s\"", buffer.str().c_str()));
return;
}
// check for widestring
is_valid_string = true;
string_offset = registerValue;
std::wstringstream wbuffer;
while (string_offset < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE - 1))
{
const uint16 c = memory_readU16(string_offset);
string_offset += 2;
if (iswprint(c))
wbuffer << c;
else if (c == L'\0')
{
wbuffer << c;
break;
}
else
{
is_valid_string = false;
break;
}
}
if (is_valid_string && buffer.tellp() > 1)
{
label->ChangeValue(wxString::Format(L"ws\"%s\"", wbuffer.str().c_str()));
return;
}
}
// check if address is a code offset
RPLModule* code_module = RPLLoader_FindModuleByCodeAddr(registerValue);
if (code_module)
{
label->ChangeValue(wxString::Format("<%s> + %x", code_module->moduleName.c_str(), registerValue - code_module->regionMappingBase_text.GetMPTR()));
return;
}
label->ChangeValue(wxEmptyString);
}
void RegisterWindow::OnUpdateView()
{
PPCInterpreter_t* hCPU = debugger_lockDebugSession();
if (!hCPU)
return;
PPCSnapshot snapshot = debugger_getSnapshotFromSession(hCPU);
debugger_unlockDebugSession(hCPU);
for (int i = 0; i < 32; ++i)
{
const uint32 registerValue = snapshot.gpr[i];
const bool hasChanged = registerValue != m_prev_snapshot.gpr[i];
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueR0 + i));
wxASSERT(value);
const auto label = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterLabelR0 + i));
wxASSERT(label);
UpdateIntegerRegister(label, value, registerValue, hasChanged);
}
// update LR
{
const uint32 registerValue = snapshot.spr_lr;
const bool hasChanged = registerValue != m_prev_snapshot.spr_lr;
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueLR));
wxASSERT(value);
const auto label = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterLabelLR));
wxASSERT(label);
UpdateIntegerRegister(label, value, registerValue, hasChanged);
}
for (int i = 0; i < 32; ++i)
{
const uint64_t register_value = snapshot.fpr[i].fp0int;
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueFPR0_0 + i));
wxASSERT(value);
const bool has_changed = register_value != m_prev_snapshot.fpr[i].fp0int;
if (has_changed)
value->SetForegroundColour(m_changed_color);
else if (value->GetForegroundColour() != wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT))
value->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
else
continue;
if(m_show_double_values)
value->ChangeValue(wxString::Format("%lf", snapshot.fpr[i].fp0));
else
value->ChangeValue(wxString::Format("%016llx", register_value));
}
for (int i = 0; i < 32; ++i)
{
const uint64_t register_value = snapshot.fpr[i].fp1int;
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueFPR1_0 + i));
wxASSERT(value);
const bool has_changed = register_value != m_prev_snapshot.fpr[i].fp1int;
if (has_changed)
value->SetForegroundColour(m_changed_color);
else if (value->GetForegroundColour() != wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT))
value->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
else
continue;
if (m_show_double_values)
value->ChangeValue(wxString::Format("%lf", snapshot.fpr[i].fp1));
else
value->ChangeValue(wxString::Format("%016llx", register_value));
}
// update CRs
for (int i = 0; i < 8; ++i)
{
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueCR0 + i));
wxASSERT(value);
auto cr_bits_ptr = snapshot.cr + i * 4;
auto cr_bits_ptr_cmp = m_prev_snapshot.cr + i * 4;
const bool has_changed = !std::equal(cr_bits_ptr, cr_bits_ptr + 4, cr_bits_ptr_cmp);
if (has_changed)
value->SetForegroundColour(m_changed_color);
else if (value->GetForegroundColour() != wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT))
value->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
else
continue;
std::vector<std::string> joinArray = {};
if (cr_bits_ptr[Espresso::CR_BIT_INDEX_LT] != 0)
joinArray.emplace_back("LT");
if (cr_bits_ptr[Espresso::CR_BIT_INDEX_GT] != 0)
joinArray.emplace_back("GT");
if (cr_bits_ptr[Espresso::CR_BIT_INDEX_EQ] != 0)
joinArray.emplace_back("EQ");
if (cr_bits_ptr[Espresso::CR_BIT_INDEX_SO] != 0)
joinArray.emplace_back("SO");
if (joinArray.empty())
value->ChangeValue("-");
else
value->ChangeValue(fmt::format("{}", fmt::join(joinArray, ", ")));
}
m_prev_snapshot = snapshot;
}
void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event)
{
PPCInterpreter_t* debugSession = debugger_lockDebugSession();
if (!debugSession)
{
event.Skip();
return;
}
PPCSnapshot ppcSnapshot = debugger_getSnapshotFromSession(debugSession);
debugger_unlockDebugSession(debugSession);
debugSession = nullptr;
const auto id = event.GetId();
if(kRegisterValueR0 <= id && id < kRegisterValueR0 + 32)
{
const uint32 register_index = id - kRegisterValueR0;
const uint32 register_value = ppcSnapshot.gpr[register_index];
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set R%d value"), register_index), wxString::Format("0x%08x", register_value));
if (set_value_dialog.ShowModal() == wxID_OK)
{
const uint32 value = ConvertString<uint32>(set_value_dialog.GetValue().ToStdString());
if (debugSession = debugger_lockDebugSession(); debugSession)
{
debugSession->gpr[register_index] = value;
debugger_unlockDebugSession(debugSession);
}
OnUpdateView();
}
return;
}
if (kRegisterValueFPR0_0 <= id && id < kRegisterValueFPR0_0 + 32)
{
const uint32 register_index = id - kRegisterValueFPR0_0;
const double register_value = ppcSnapshot.fpr[register_index].fp0;
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set FP0_%d value"), register_index), wxString::Format("%lf", register_value));
if (set_value_dialog.ShowModal() == wxID_OK)
{
const double value = ConvertString<double>(set_value_dialog.GetValue().ToStdString());
if (debugSession = debugger_lockDebugSession(); debugSession)
{
debugSession->fpr[register_index].fp0 = value;
debugger_unlockDebugSession(debugSession);
}
OnUpdateView();
}
return;
}
if (kRegisterValueFPR1_0 <= id && id < kRegisterValueFPR1_0 + 32)
{
const uint32 register_index = id - kRegisterValueFPR1_0;
const double register_value = ppcSnapshot.fpr[register_index].fp1;
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set FP1_%d value"), register_index), wxString::Format("%lf", register_value));
if (set_value_dialog.ShowModal() == wxID_OK)
{
const double value = ConvertString<double>(set_value_dialog.GetValue().ToStdString());
if (debugSession = debugger_lockDebugSession(); debugSession)
{
debugSession->fpr[register_index].fp1 = value;
debugger_unlockDebugSession(debugSession);
}
OnUpdateView();
}
return;
}
event.Skip();
}
void RegisterWindow::OnFPViewModePress(wxCommandEvent& event)
{
m_show_double_values = !m_show_double_values;
PPCInterpreter_t* debugSession = debugger_lockDebugSession();
if (!debugSession)
return;
PPCSnapshot ppcSnapshot = debugger_getSnapshotFromSession(debugSession);
debugger_unlockDebugSession(debugSession);
for (int i = 0; i < 32; ++i)
{
const auto value0 = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueFPR0_0 + i));
const auto value1 = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueFPR1_0 + i));
wxASSERT(value0);
wxASSERT(value1);
if (m_show_double_values)
{
value0->ChangeValue(wxString::Format("%lf", ppcSnapshot.fpr[i].fp0));
value1->ChangeValue(wxString::Format("%lf", ppcSnapshot.fpr[i].fp1));
}
else
{
value0->ChangeValue(wxString::Format("%016llx", ppcSnapshot.fpr[i].fp0int));
value1->ChangeValue(wxString::Format("%016llx", ppcSnapshot.fpr[i].fp1int));
}
}
}
void RegisterWindow::OnValueContextMenu(wxContextMenuEvent& event)
{
wxMenu menu;
menu.Append(kContextMenuZero, _("&Zero"));
menu.Append(kContextMenuInc, _("&Increment"));
menu.Append(kContextMenuDec, _("&Decrement"));
menu.AppendSeparator();
menu.Append(kContextMenuCopy, _("&Copy"));
menu.AppendSeparator();
menu.Append(kContextMenuGotoDisasm, _("&Goto Disasm"));
menu.Append(kContextMenuGotoDump, _("G&oto Dump"));
m_context_ctrl = dynamic_cast<wxTextCtrl*>(event.GetEventObject());
PopupMenu(&menu);
}
void RegisterWindow::OnValueContextMenuSelected(wxCommandEvent& event)
{
wxASSERT(m_context_ctrl);
switch (event.GetId())
{
case kContextMenuZero:
break;
case kContextMenuInc:
break;
case kContextMenuDec:
break;
case kContextMenuCopy:
break;
case kContextMenuGotoDisasm:
break;
case kContextMenuGotoDump:
break;
}
}
-29
View File
@@ -1,29 +0,0 @@
#pragma once
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
#include <wx/frame.h>
class DebuggerWindow2;
class wxTextCtrl;
class RegisterWindow : public wxFrame
{
public:
RegisterWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size);
void OnMainMove(const wxPoint& position, const wxSize& main_size);
void OnUpdateView();
private:
void OnMouseDClickEvent(wxMouseEvent& event);
void OnFPViewModePress(wxCommandEvent& event);
void OnValueContextMenu(wxContextMenuEvent& event);
void OnValueContextMenuSelected(wxCommandEvent& event);
void UpdateIntegerRegister(wxTextCtrl* label, wxTextCtrl* value, uint32 registerValue, bool hasChanged);
PPCSnapshot m_prev_snapshot;
bool m_show_double_values;
wxColour m_changed_color = {0xFF0000FF};
wxTextCtrl* m_context_ctrl;
};
Loaded 100 of 157 files, more files were not shown because too many files have changed in this diff. Show more