mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-04 05:51:19 +12:00
Merge with latest.
This commit is contained in:
commit
345098c37a
484 changed files with 30773 additions and 15076 deletions
66
.clang-format
Normal file
66
.clang-format
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
AlignAfterOpenBracket: Align
|
||||||
|
AlignConsecutiveAssignments: false
|
||||||
|
AlignConsecutiveDeclarations: false
|
||||||
|
AlignEscapedNewlinesLeft: false
|
||||||
|
AlignOperands: true
|
||||||
|
AlignTrailingComments: true
|
||||||
|
AllowShortBlocksOnASingleLine: Empty
|
||||||
|
AllowShortCaseLabelsOnASingleLine: false
|
||||||
|
AllowShortEnumsOnASingleLine: true
|
||||||
|
AllowShortFunctionsOnASingleLine: Empty
|
||||||
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
|
AllowShortLambdasOnASingleLine: Inline
|
||||||
|
AlwaysBreakTemplateDeclarations: true
|
||||||
|
BinPackArguments: true
|
||||||
|
BinPackParameters: true
|
||||||
|
BraceWrapping:
|
||||||
|
AfterCaseLabel: true
|
||||||
|
AfterClass: true
|
||||||
|
AfterControlStatement: Always
|
||||||
|
AfterEnum: true
|
||||||
|
AfterExternBlock: true
|
||||||
|
AfterFunction: true
|
||||||
|
AfterNamespace: true
|
||||||
|
AfterStruct: true
|
||||||
|
AfterUnion: true
|
||||||
|
BeforeElse: true
|
||||||
|
BeforeWhile: true
|
||||||
|
SplitEmptyFunction: false
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BreakBeforeTernaryOperators: true
|
||||||
|
ColumnLimit: 0
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||||
|
ConstructorInitializerIndentWidth: 4
|
||||||
|
ContinuationIndentWidth: 4
|
||||||
|
Cpp11BracedListStyle: true
|
||||||
|
IndentWidth: 4
|
||||||
|
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||||
|
Language: Cpp
|
||||||
|
MaxEmptyLinesToKeep: 1
|
||||||
|
NamespaceIndentation: All
|
||||||
|
ObjCSpaceAfterProperty: false
|
||||||
|
PointerAlignment: Left
|
||||||
|
ReflowComments: true
|
||||||
|
SortIncludes: false
|
||||||
|
SortUsingDeclarations: true
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
SpaceBeforeCtorInitializerColon: true
|
||||||
|
SpaceAfterLogicalNot: false
|
||||||
|
SpaceAfterTemplateKeyword: false
|
||||||
|
SpaceBeforeAssignmentOperators: true
|
||||||
|
SpaceBeforeCaseColon: false
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
SpaceBeforeRangeBasedForLoopColon: true
|
||||||
|
SpaceBeforeSquareBrackets: false
|
||||||
|
SpaceInEmptyBlock: false
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesBeforeTrailingComments: 1
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInConditionalStatement: false
|
||||||
|
SpacesInContainerLiterals: true
|
||||||
|
SpacesInParentheses: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
Standard: Latest
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: Always
|
|
@ -1,34 +0,0 @@
|
||||||
---
|
|
||||||
name: Bug Report / Feature Request
|
|
||||||
about: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with Cemu or you are requesting a feature you believe would make Cemu better.
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!---
|
|
||||||
Please keep in mind Cemu is EXPERIMENTAL SOFTWARE.
|
|
||||||
|
|
||||||
Please read the FAQ:
|
|
||||||
https://cemu.info/faq.html
|
|
||||||
|
|
||||||
THIS IS NOT A SUPPORT FORUM, FOR SUPPORT GO TO:
|
|
||||||
https://discord.com/invite/5psYsup
|
|
||||||
|
|
||||||
If the FAQ does not answer your question, please go to:
|
|
||||||
https://discord.com/invite/5psYsup
|
|
||||||
|
|
||||||
When submitting an issue, please check the following:
|
|
||||||
|
|
||||||
- You have read the above.
|
|
||||||
- You have provided the version (commit hash) of Cemu you are using.
|
|
||||||
- You have provided sufficient detail for the issue to be reproduced.
|
|
||||||
- You have provided system specs (if relevant).
|
|
||||||
- Please also provide:
|
|
||||||
- For any issues, a log file
|
|
||||||
- For crashes, a backtrace.
|
|
||||||
- For graphical issues, comparison screenshots with real hardware.
|
|
||||||
- For emulation inaccuracies, a test-case (if able).
|
|
||||||
|
|
||||||
-->
|
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -2,4 +2,4 @@ blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Cemu Discord
|
- name: Cemu Discord
|
||||||
url: https://discord.com/invite/5psYsup
|
url: https://discord.com/invite/5psYsup
|
||||||
about: If you are experiencing an issue with Cemu, and you need tech support, or if you have a general question, try asking in the official Cemu Discord linked here. Piracy is not allowed.
|
about: If you need technical support with Cemu or have other questions the best place to ask is on the official Cemu Discord linked here
|
||||||
|
|
69
.github/ISSUE_TEMPLATE/emulation_bug_report.yaml
vendored
Normal file
69
.github/ISSUE_TEMPLATE/emulation_bug_report.yaml
vendored
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
# Docs - https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
|
||||||
|
name: Bug Report
|
||||||
|
description: Report an issue with Cemu emulator
|
||||||
|
title: "Enter a title for the bug report here"
|
||||||
|
labels: bug
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
id: md_readme
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## Important: Read First
|
||||||
|
|
||||||
|
If you discovered a bug you can report it here. Please make sure of the following first:
|
||||||
|
- That you are using the latest version of Cemu
|
||||||
|
- Only report something if you are sure it's a bug and not any technical issue on your end. For troubleshooting help see the [links page](https://github.com/cemu-project/Cemu#links)
|
||||||
|
- Problems specific to a single game should be reported on the [compatibility wiki](https://wiki.cemu.info/wiki/Main_Page) instead
|
||||||
|
- Verify that your problem isn't already mentioned on the [issue tracker](https://github.com/cemu-project/Cemu/issues)
|
||||||
|
|
||||||
|
Additionally, be aware that graphic packs can also causes issues. There is a separate issue tracker for graphic pack bugs over at the [graphic pack repository](https://github.com/cemu-project/cemu_graphic_packs)
|
||||||
|
- type: textarea
|
||||||
|
id: current_behavior
|
||||||
|
attributes:
|
||||||
|
label: Current Behavior
|
||||||
|
description: "What the bug is, in a brief description"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: expected_behavior
|
||||||
|
attributes:
|
||||||
|
label: Expected Behavior
|
||||||
|
description: "What did you expect to happen?"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: steps_to_reproduce
|
||||||
|
attributes:
|
||||||
|
label: Steps to Reproduce
|
||||||
|
description: "How to reproduce the issue"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: sys_info
|
||||||
|
attributes:
|
||||||
|
label: System Info (Optional)
|
||||||
|
description: "Your PC specifications. Usually only the operating system and graphics card is important. But feel free to add more info."
|
||||||
|
placeholder: |
|
||||||
|
Info
|
||||||
|
OS: Windows 10
|
||||||
|
GPU: NVIDIA GeForce RTX 4090
|
||||||
|
value: |
|
||||||
|
OS:
|
||||||
|
GPU:
|
||||||
|
- type: textarea
|
||||||
|
id: emulation_settings
|
||||||
|
attributes:
|
||||||
|
label: Emulation Settings (Optional)
|
||||||
|
description: |
|
||||||
|
Any non-default settings. You can leave this empty if you didn't change anything other than input settings.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: logs_files
|
||||||
|
attributes:
|
||||||
|
label: "Logs (Optional)"
|
||||||
|
description: |
|
||||||
|
"Attach `log.txt` from your Cemu folder (*File > Open Cemu folder*)".
|
||||||
|
validations:
|
||||||
|
required: false
|
28
.github/ISSUE_TEMPLATE/feature_report.yaml
vendored
Normal file
28
.github/ISSUE_TEMPLATE/feature_report.yaml
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Docs - https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
|
||||||
|
name: Feature suggestion
|
||||||
|
description: Suggest a new feature
|
||||||
|
title: "Enter a title for the suggestion here"
|
||||||
|
labels: feature request
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
id: md_readme
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## Important: Read First
|
||||||
|
|
||||||
|
While we appreciate suggestions, it is important to note that we are a very small team and there are already many more ideas than we could ever implement in the near future. Therefore, please only suggest something if you believe it is a great addition and the idea is reasonably unique.
|
||||||
|
|
||||||
|
*Avoid* to create suggestions for:
|
||||||
|
- Overly obvious features ("Game xyz does not work and should be fixed", "Wiimote support should be improved", "You should add an Android port", "Copy feature xyz from another emulator", "A button to pause/stop emulation")
|
||||||
|
- Niche features which are only interesting to a tiny percentage of users
|
||||||
|
- Large scale features ("Add a Metal backend for MacOS", "Add ARM support", "Add savestates")
|
||||||
|
|
||||||
|
Note that this doesn't mean we aren't interested in these ideas, but rather we likely have them planned anyway and it's mostly up to finding the time to implement them.
|
||||||
|
If you believe your idea is worthwhile even if it doesn't meet all the criteria above, you can still try suggesting it but we might close it.
|
||||||
|
- type: textarea
|
||||||
|
id: idea_suggestion
|
||||||
|
attributes:
|
||||||
|
label: Your suggestion
|
||||||
|
description: "Describe what your suggestion is in as much detail as possible"
|
||||||
|
validations:
|
||||||
|
required: true
|
9
.github/getversion.cpp
vendored
9
.github/getversion.cpp
vendored
|
@ -1,9 +0,0 @@
|
||||||
#include <stdio.h>
|
|
||||||
#include "./../src/Common/version.h"
|
|
||||||
|
|
||||||
// output current Cemu version for CI workflow. Do not modify
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
printf("%d.%d", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR);
|
|
||||||
return 0;
|
|
||||||
}
|
|
142
.github/workflows/build.yml
vendored
142
.github/workflows/build.yml
vendored
|
@ -3,10 +3,10 @@ name: Build Cemu
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
deploymode:
|
next_version_major:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
experimentalversion:
|
next_version_minor:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
|
@ -16,44 +16,35 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-ubuntu:
|
build-ubuntu:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout repo"
|
- name: "Checkout repo"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: "Fetch full history for vcpkg submodule"
|
- name: Setup release mode parameters
|
||||||
run: |
|
|
||||||
cd dependencies/vcpkg
|
|
||||||
git fetch --unshallow
|
|
||||||
git pull --all
|
|
||||||
|
|
||||||
- name: Setup release mode parameters (for deploy)
|
|
||||||
if: ${{ inputs.deploymode == 'release' }}
|
|
||||||
run: |
|
run: |
|
||||||
echo "BUILD_MODE=release" >> $GITHUB_ENV
|
echo "BUILD_MODE=release" >> $GITHUB_ENV
|
||||||
echo "BUILD_FLAGS=" >> $GITHUB_ENV
|
echo "BUILD_FLAGS=" >> $GITHUB_ENV
|
||||||
echo "Build mode is release"
|
echo "Build mode is release"
|
||||||
|
|
||||||
- name: Setup debug mode parameters (for continous build)
|
- name: Setup build flags for version
|
||||||
if: ${{ inputs.deploymode != 'release' }}
|
if: ${{ inputs.next_version_major != '' }}
|
||||||
run: |
|
run: |
|
||||||
echo "BUILD_MODE=debug" >> $GITHUB_ENV
|
echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
|
||||||
echo "BUILD_FLAGS=" >> $GITHUB_ENV
|
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" >> $GITHUB_ENV
|
||||||
echo "Build mode is debug"
|
|
||||||
|
|
||||||
- name: Setup version for experimental
|
|
||||||
if: ${{ inputs.experimentalversion != '' }}
|
|
||||||
run: |
|
|
||||||
echo "[INFO] Experimental version ${{ inputs.experimentalversion }}"
|
|
||||||
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: "Install system dependencies"
|
- name: "Install system dependencies"
|
||||||
run: |
|
run: |
|
||||||
sudo apt update -qq
|
sudo apt update -qq
|
||||||
sudo apt install -y clang-12 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build
|
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: "Setup cmake"
|
||||||
|
uses: jwlawson/actions-setup-cmake@v2
|
||||||
|
with:
|
||||||
|
cmake-version: '3.29.0'
|
||||||
|
|
||||||
- name: "Bootstrap vcpkg"
|
- name: "Bootstrap vcpkg"
|
||||||
run: |
|
run: |
|
||||||
|
@ -75,31 +66,29 @@ jobs:
|
||||||
|
|
||||||
- name: "cmake"
|
- name: "cmake"
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B build ${{ env.BUILD_FLAGS }} -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} -DPORTABLE=OFF -DCMAKE_C_COMPILER=/usr/bin/clang-12 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-12 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja
|
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"
|
- name: "Build Cemu"
|
||||||
run: |
|
run: |
|
||||||
cmake --build build
|
cmake --build build
|
||||||
|
|
||||||
- name: Prepare artifact
|
- name: Prepare artifact
|
||||||
if: ${{ inputs.deploymode == 'release' }}
|
|
||||||
run: mv bin/Cemu_release bin/Cemu
|
run: mv bin/Cemu_release bin/Cemu
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ inputs.deploymode == 'release' }}
|
|
||||||
with:
|
with:
|
||||||
name: cemu-bin-linux-x64
|
name: cemu-bin-linux-x64
|
||||||
path: ./bin/Cemu
|
path: ./bin/Cemu
|
||||||
|
|
||||||
build-appimage:
|
build-appimage:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
needs: build-ubuntu
|
needs: build-ubuntu
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Upstream Repo
|
- name: Checkout Upstream Repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: cemu-bin-linux-x64
|
name: cemu-bin-linux-x64
|
||||||
path: bin
|
path: bin
|
||||||
|
@ -107,7 +96,7 @@ jobs:
|
||||||
- name: "Install system dependencies"
|
- name: "Install system dependencies"
|
||||||
run: |
|
run: |
|
||||||
sudo apt update -qq
|
sudo apt update -qq
|
||||||
sudo apt install -y clang-12 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build appstream
|
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"
|
- name: "Build AppImage"
|
||||||
run: |
|
run: |
|
||||||
|
@ -116,7 +105,7 @@ jobs:
|
||||||
dist/linux/appimage.sh
|
dist/linux/appimage.sh
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: cemu-appimage-x64
|
name: cemu-appimage-x64
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
@ -125,34 +114,26 @@ jobs:
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout repo"
|
- name: "Checkout repo"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
|
|
||||||
- name: "Fetch full history for vcpkg submodule"
|
- name: Setup release mode parameters
|
||||||
run: |
|
|
||||||
cd dependencies/vcpkg
|
|
||||||
git fetch --unshallow
|
|
||||||
git pull --all
|
|
||||||
|
|
||||||
- name: Setup release mode parameters (for deploy)
|
|
||||||
if: ${{ inputs.deploymode == 'release' }}
|
|
||||||
run: |
|
run: |
|
||||||
echo "BUILD_MODE=release" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
|
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_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
|
||||||
echo "Build mode is release"
|
echo "Build mode is release"
|
||||||
|
|
||||||
- name: Setup debug mode parameters (for continous build)
|
- name: Setup build flags for version
|
||||||
if: ${{ inputs.deploymode != 'release' }}
|
if: ${{ inputs.next_version_major != '' }}
|
||||||
run: |
|
run: |
|
||||||
echo "BUILD_MODE=debug" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
|
echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
|
||||||
echo "BUILD_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
|
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
|
||||||
echo "Build mode is debug"
|
|
||||||
- name: Setup version for experimental
|
- name: "Setup cmake"
|
||||||
if: ${{ inputs.experimentalversion != '' }}
|
uses: jwlawson/actions-setup-cmake@v2
|
||||||
run: |
|
with:
|
||||||
echo "[INFO] Experimental version ${{ inputs.experimentalversion }}"
|
cmake-version: '3.29.0'
|
||||||
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
|
|
||||||
|
|
||||||
- name: "Bootstrap vcpkg"
|
- name: "Bootstrap vcpkg"
|
||||||
run: |
|
run: |
|
||||||
|
@ -183,56 +164,53 @@ jobs:
|
||||||
- name: "Build Cemu"
|
- name: "Build Cemu"
|
||||||
run: |
|
run: |
|
||||||
cd build
|
cd build
|
||||||
cmake --build . --config ${{ env.BUILD_MODE }} -j 2
|
cmake --build . --config ${{ env.BUILD_MODE }}
|
||||||
|
|
||||||
- name: Prepare artifact
|
- name: Prepare artifact
|
||||||
if: ${{ inputs.deploymode == 'release' }}
|
|
||||||
run: Rename-Item bin/Cemu_release.exe Cemu.exe
|
run: Rename-Item bin/Cemu_release.exe Cemu.exe
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ inputs.deploymode == 'release' }}
|
|
||||||
with:
|
with:
|
||||||
name: cemu-bin-windows-x64
|
name: cemu-bin-windows-x64
|
||||||
path: ./bin/Cemu.exe
|
path: ./bin/Cemu.exe
|
||||||
|
|
||||||
build-macos:
|
build-macos:
|
||||||
runs-on: macos-12
|
runs-on: macos-14
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout repo"
|
- name: "Checkout repo"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
|
|
||||||
- name: "Fetch full history for vcpkg submodule"
|
- name: Setup release mode parameters
|
||||||
run: |
|
|
||||||
cd dependencies/vcpkg
|
|
||||||
git fetch --unshallow
|
|
||||||
git pull --all
|
|
||||||
|
|
||||||
- name: Setup release mode parameters (for deploy)
|
|
||||||
if: ${{ inputs.deploymode == 'release' }}
|
|
||||||
run: |
|
run: |
|
||||||
echo "BUILD_MODE=release" >> $GITHUB_ENV
|
echo "BUILD_MODE=release" >> $GITHUB_ENV
|
||||||
echo "BUILD_FLAGS=" >> $GITHUB_ENV
|
echo "BUILD_FLAGS=" >> $GITHUB_ENV
|
||||||
echo "Build mode is release"
|
echo "Build mode is release"
|
||||||
- name: Setup debug mode parameters (for continous build)
|
|
||||||
if: ${{ inputs.deploymode != 'release' }}
|
|
||||||
run: |
|
|
||||||
echo "BUILD_MODE=debug" >> $GITHUB_ENV
|
|
||||||
echo "BUILD_FLAGS=" >> $GITHUB_ENV
|
|
||||||
echo "Build mode is debug"
|
|
||||||
|
|
||||||
- name: Setup version for experimental
|
- name: Setup build flags for version
|
||||||
if: ${{ inputs.experimentalversion != '' }}
|
if: ${{ inputs.next_version_major != '' }}
|
||||||
run: |
|
run: |
|
||||||
echo "[INFO] Experimental version ${{ inputs.experimentalversion }}"
|
echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
|
||||||
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" >> $GITHUB_ENV
|
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"
|
- name: "Install system dependencies"
|
||||||
run: |
|
run: |
|
||||||
brew update
|
brew update
|
||||||
brew install llvm@15 ninja nasm molten-vk
|
brew install ninja nasm automake libtool
|
||||||
|
|
||||||
|
- name: "Install molten-vk"
|
||||||
|
run: |
|
||||||
|
curl -L -O https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-macos.tar
|
||||||
|
tar xf MoltenVK-macos.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"
|
- name: "Bootstrap vcpkg"
|
||||||
run: |
|
run: |
|
||||||
|
@ -258,10 +236,8 @@ jobs:
|
||||||
cd build
|
cd build
|
||||||
cmake .. ${{ env.BUILD_FLAGS }} \
|
cmake .. ${{ env.BUILD_FLAGS }} \
|
||||||
-DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} \
|
-DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} \
|
||||||
-DPORTABLE=OFF \
|
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
||||||
-DMACOS_BUNDLE=ON \
|
-DMACOS_BUNDLE=ON \
|
||||||
-DCMAKE_C_COMPILER=/usr/local/opt/llvm@15/bin/clang \
|
|
||||||
-DCMAKE_CXX_COMPILER=/usr/local/opt/llvm@15/bin/clang++ \
|
|
||||||
-G Ninja
|
-G Ninja
|
||||||
|
|
||||||
- name: "Build Cemu"
|
- name: "Build Cemu"
|
||||||
|
@ -269,21 +245,19 @@ jobs:
|
||||||
cmake --build build
|
cmake --build build
|
||||||
|
|
||||||
- name: Prepare artifact
|
- name: Prepare artifact
|
||||||
if: ${{ inputs.deploymode == 'release' }}
|
|
||||||
run: |
|
run: |
|
||||||
mkdir bin/Cemu_app
|
mkdir bin/Cemu_app
|
||||||
mv bin/Cemu_release.app bin/Cemu_app/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
|
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
|
sed -i '' 's/Cemu_release/Cemu/g' bin/Cemu_app/Cemu.app/Contents/Info.plist
|
||||||
chmod a+x bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu
|
chmod a+x bin/Cemu_app/Cemu.app/Contents/MacOS/{Cemu,update.sh}
|
||||||
ln -s /Applications bin/Cemu_app/Applications
|
ln -s /Applications bin/Cemu_app/Applications
|
||||||
hdiutil create ./bin/tmp.dmg -ov -volname "Cemu" -fs HFS+ -srcfolder "./bin/Cemu_app"
|
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
|
hdiutil convert ./bin/tmp.dmg -format UDZO -o bin/Cemu.dmg
|
||||||
rm bin/tmp.dmg
|
rm bin/tmp.dmg
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ inputs.deploymode == 'release' }}
|
|
||||||
with:
|
with:
|
||||||
name: cemu-bin-macos-x64
|
name: cemu-bin-macos-x64
|
||||||
path: ./bin/Cemu.dmg
|
path: ./bin/Cemu.dmg
|
||||||
|
|
3
.github/workflows/build_check.yml
vendored
3
.github/workflows/build_check.yml
vendored
|
@ -16,6 +16,3 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
uses: ./.github/workflows/build.yml
|
uses: ./.github/workflows/build.yml
|
||||||
with:
|
|
||||||
deploymode: release
|
|
||||||
experimentalversion: 999999
|
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
name: Deploy experimental release
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
call-release-build:
|
|
||||||
uses: ./.github/workflows/build.yml
|
|
||||||
with:
|
|
||||||
deploymode: release
|
|
||||||
experimentalversion: ${{ github.run_number }}
|
|
||||||
deploy:
|
|
||||||
name: Deploy experimental release
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
needs: call-release-build
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: cemu-bin-linux-x64
|
|
||||||
path: cemu-bin-linux-x64
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: cemu-appimage-x64
|
|
||||||
path: cemu-appimage-x64
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: cemu-bin-windows-x64
|
|
||||||
path: cemu-bin-windows-x64
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: cemu-bin-macos-x64
|
|
||||||
path: cemu-bin-macos-x64
|
|
||||||
|
|
||||||
- name: Initialize
|
|
||||||
run: |
|
|
||||||
mkdir upload
|
|
||||||
sudo apt install zip
|
|
||||||
|
|
||||||
- name: Get version
|
|
||||||
run: |
|
|
||||||
echo "Experimental version: ${{ github.run_number }}"
|
|
||||||
ls
|
|
||||||
gcc -o getversion .github/getversion.cpp
|
|
||||||
./getversion
|
|
||||||
echo "Cemu CI version: $(./getversion)"
|
|
||||||
echo "CEMU_FOLDER_NAME=Cemu_$(./getversion)-${{ github.run_number }}" >> $GITHUB_ENV
|
|
||||||
echo "CEMU_VERSION=$(./getversion)-${{ github.run_number }}" >> $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 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-20.04-x64.zip ${{ env.CEMU_FOLDER_NAME }}
|
|
||||||
rm -r ./${{ env.CEMU_FOLDER_NAME }}
|
|
||||||
|
|
||||||
- name: Create release from macos-bin
|
|
||||||
run: cp cemu-bin-macos-x64/Cemu.dmg upload/cemu-${{ env.CEMU_VERSION }}-macos-12-x64.dmg
|
|
||||||
|
|
||||||
- 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 }}"
|
|
||||||
ghr_v0.15.0_linux_amd64/ghr -prerelease -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }} (Experimental)" -b "Cemu experimental release" "v${{ env.CEMU_VERSION }}" ./upload
|
|
151
.github/workflows/deploy_release.yml
vendored
Normal file
151
.github/workflows/deploy_release.yml
vendored
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
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@v4
|
||||||
|
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@v4
|
||||||
|
with:
|
||||||
|
name: cemu-bin-linux-x64
|
||||||
|
path: cemu-bin-linux-x64
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: cemu-appimage-x64
|
||||||
|
path: cemu-appimage-x64
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: cemu-bin-windows-x64
|
||||||
|
path: cemu-bin-windows-x64
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: cemu-bin-macos-x64
|
||||||
|
path: cemu-bin-macos-x64
|
||||||
|
|
||||||
|
- 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 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: cp cemu-bin-macos-x64/Cemu.dmg upload/cemu-${{ env.CEMU_VERSION }}-macos-12-x64.dmg
|
||||||
|
|
||||||
|
- 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
|
85
.github/workflows/deploy_stable_release.yml
vendored
85
.github/workflows/deploy_stable_release.yml
vendored
|
@ -1,85 +0,0 @@
|
||||||
name: Create new release
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
PlaceholderInput:
|
|
||||||
description: PlaceholderInput
|
|
||||||
required: false
|
|
||||||
jobs:
|
|
||||||
call-release-build:
|
|
||||||
uses: ./.github/workflows/build.yml
|
|
||||||
with:
|
|
||||||
deploymode: release
|
|
||||||
deploy:
|
|
||||||
name: Deploy release
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
needs: call-release-build
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: cemu-bin-linux-x64
|
|
||||||
path: cemu-bin-linux-x64
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: cemu-appimage-x64
|
|
||||||
path: cemu-appimage-x64
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: cemu-bin-windows-x64
|
|
||||||
path: cemu-bin-windows-x64
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: cemu-bin-macos-x64
|
|
||||||
path: cemu-bin-macos-x64
|
|
||||||
|
|
||||||
- name: Initialize
|
|
||||||
run: |
|
|
||||||
mkdir upload
|
|
||||||
sudo apt update -qq
|
|
||||||
sudo apt install -y zip
|
|
||||||
|
|
||||||
- name: Get Cemu release version
|
|
||||||
run: |
|
|
||||||
gcc -o getversion .github/getversion.cpp
|
|
||||||
echo "Cemu CI version: $(./getversion)"
|
|
||||||
echo "CEMU_FOLDER_NAME=Cemu_$(./getversion)" >> $GITHUB_ENV
|
|
||||||
echo "CEMU_VERSION=$(./getversion)" >> $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 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 ubuntu-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-20.04-x64.zip ${{ env.CEMU_FOLDER_NAME }}
|
|
||||||
rm -r ./${{ env.CEMU_FOLDER_NAME }}
|
|
||||||
|
|
||||||
- name: Create release from macos-bin
|
|
||||||
run: cp cemu-bin-macos-x64/Cemu.dmg upload/cemu-${{ env.CEMU_VERSION }}-macos-12-x64.dmg
|
|
||||||
|
|
||||||
- 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
|
|
||||||
ghr_v0.15.0_linux_amd64/ghr -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }}" -b "Changelog:" v${{ env.CEMU_VERSION }} ./upload
|
|
74
.github/workflows/determine_release_version.yml
vendored
Normal file
74
.github/workflows/determine_release_version.yml
vendored
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
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@v2
|
||||||
|
|
||||||
|
- 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"
|
6
.github/workflows/generate_pot.yml
vendored
6
.github/workflows/generate_pot.yml
vendored
|
@ -29,13 +29,13 @@ jobs:
|
||||||
- name: "Generate POT file using xgettext"
|
- name: "Generate POT file using xgettext"
|
||||||
run: >
|
run: >
|
||||||
find src -name *.cpp -o -name *.hpp -o -name *.h |
|
find src -name *.cpp -o -name *.hpp -o -name *.h |
|
||||||
xargs xgettext --from-code=utf-8
|
xargs xgettext --from-code=utf-8 -w 100
|
||||||
-k_ -kwxTRANSLATE -w 100
|
--keyword="_" --keyword="wxTRANSLATE" --keyword="wxPLURAL:1,2"
|
||||||
--check=space-ellipsis --omit-header
|
--check=space-ellipsis --omit-header
|
||||||
-o cemu.pot
|
-o cemu.pot
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: POT file
|
name: POT file
|
||||||
path: ./cemu.pot
|
path: ./cemu.pot
|
||||||
|
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -17,7 +17,7 @@
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
build/
|
build/
|
||||||
cmake-build-*-*/
|
cmake-build-*/
|
||||||
out/
|
out/
|
||||||
.cache/
|
.cache/
|
||||||
bin/Cemu_*
|
bin/Cemu_*
|
||||||
|
@ -39,6 +39,8 @@ bin/sdcard/*
|
||||||
bin/screenshots/*
|
bin/screenshots/*
|
||||||
bin/dump/*
|
bin/dump/*
|
||||||
bin/cafeLibs/*
|
bin/cafeLibs/*
|
||||||
|
bin/portable/*
|
||||||
|
bin/keys.txt
|
||||||
|
|
||||||
!bin/shaderCache/info.txt
|
!bin/shaderCache/info.txt
|
||||||
bin/shaderCache/*
|
bin/shaderCache/*
|
||||||
|
@ -49,3 +51,6 @@ bin/controllerProfiles/*
|
||||||
bin/gameProfiles/*
|
bin/gameProfiles/*
|
||||||
|
|
||||||
bin/graphicPacks/*
|
bin/graphicPacks/*
|
||||||
|
|
||||||
|
# Ignore Finder view option files created by OS X
|
||||||
|
.DS_Store
|
4
.gitmodules
vendored
4
.gitmodules
vendored
|
@ -9,10 +9,12 @@
|
||||||
[submodule "dependencies/vcpkg"]
|
[submodule "dependencies/vcpkg"]
|
||||||
path = dependencies/vcpkg
|
path = dependencies/vcpkg
|
||||||
url = https://github.com/microsoft/vcpkg
|
url = https://github.com/microsoft/vcpkg
|
||||||
shallow = true
|
shallow = false
|
||||||
[submodule "dependencies/Vulkan-Headers"]
|
[submodule "dependencies/Vulkan-Headers"]
|
||||||
path = dependencies/Vulkan-Headers
|
path = dependencies/Vulkan-Headers
|
||||||
url = https://github.com/KhronosGroup/Vulkan-Headers
|
url = https://github.com/KhronosGroup/Vulkan-Headers
|
||||||
|
shallow = true
|
||||||
[submodule "dependencies/imgui"]
|
[submodule "dependencies/imgui"]
|
||||||
path = dependencies/imgui
|
path = dependencies/imgui
|
||||||
url = https://github.com/ocornut/imgui
|
url = https://github.com/ocornut/imgui
|
||||||
|
shallow = true
|
||||||
|
|
248
BUILD.md
248
BUILD.md
|
@ -1,97 +1,184 @@
|
||||||
# Build instructions
|
# Build Instructions
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Windows](#windows)
|
||||||
|
- [Linux](#linux)
|
||||||
|
- [Dependencies](#dependencies)
|
||||||
|
- [For Arch and derivatives:](#for-arch-and-derivatives)
|
||||||
|
- [For Debian, Ubuntu and derivatives](#for-debian-ubuntu-and-derivatives)
|
||||||
|
- [For Fedora and derivatives:](#for-fedora-and-derivatives)
|
||||||
|
- [Build Cemu](#build-cemu)
|
||||||
|
- [CMake and Clang](#cmake-and-clang)
|
||||||
|
- [GCC](#gcc)
|
||||||
|
- [Debug Build](#debug-build)
|
||||||
|
- [Troubleshooting Steps](#troubleshooting-steps)
|
||||||
|
- [Compiling Errors](#compiling-errors)
|
||||||
|
- [Building Errors](#building-errors)
|
||||||
|
- [macOS](#macos)
|
||||||
|
- [Installing brew](#installing-brew)
|
||||||
|
- [Installing Tool Dependencies](#installing-tool-dependencies)
|
||||||
|
- [Installing Library Dependencies](#installing-library-dependencies)
|
||||||
|
- [Build Cemu using CMake](#build-cemu-using-cmake)
|
||||||
|
- [Updating Cemu and source code](#updating-cemu-and-source-code)
|
||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
- A recent version of Visual Studio 2022 (recommended but not required) with the following additional components:
|
- git
|
||||||
|
- A recent version of Visual Studio 2022 with the following additional components:
|
||||||
- C++ CMake tools for Windows
|
- C++ CMake tools for Windows
|
||||||
- Windows 10/11 SDK
|
- Windows 10/11 SDK
|
||||||
- git
|
|
||||||
|
|
||||||
Instructions:
|
Instructions for Visual Studio 2022:
|
||||||
|
|
||||||
1. Run `git clone --recursive https://github.com/cemu-project/Cemu`
|
1. Run `git clone --recursive https://github.com/cemu-project/Cemu`
|
||||||
2. Launch `Cemu/generate_vs_solution.bat`.
|
2. Open the newly created Cemu directory in Visual Studio using the "Open a local folder" option
|
||||||
- If you installed VS to a custom location or use VS 2019, you may need to manually change the path inside the .bat file.
|
3. In the menu select Project -> Configure CMake. Wait until it is done, this may take a long time
|
||||||
3. Wait until it's done, then open `Cemu/build/Cemu.sln` in Visual Studio.
|
4. You can now build, run and debug Cemu
|
||||||
4. Then build the solution and once finished you can run and debug it, or build it and check the /bin folder for the final Cemu_release.exe.
|
|
||||||
|
|
||||||
You can also skip steps 3-5 and open the root folder of the cloned repo directly in Visual Studio (as a folder) and use the built-in CMake support but be warned that cmake support in VS can be a bit finicky.
|
Any other IDE should also work as long as it has CMake and MSVC support. CLion and Visual Studio Code have been confirmed to work.
|
||||||
|
|
||||||
## Linux
|
## Linux
|
||||||
|
|
||||||
To compile Cemu, a recent enough compiler and STL with C++20 support is required! clang-12 or higher is what we recommend.
|
To compile Cemu, a recent enough compiler and STL with C++20 support is required! Clang-15 or higher is what we recommend.
|
||||||
|
|
||||||
### Installing dependencies
|
### Dependencies
|
||||||
|
|
||||||
#### For Ubuntu and derivatives:
|
|
||||||
`sudo apt install -y cmake curl freeglut3-dev git libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build`
|
|
||||||
|
|
||||||
*Additionally, for Ubuntu 22.04 only:*
|
|
||||||
- `sudo apt install -y clang-12`
|
|
||||||
- At step 3 while building, use
|
|
||||||
`cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-12 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-12 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja`
|
|
||||||
|
|
||||||
#### For Arch and derivatives:
|
#### For Arch and derivatives:
|
||||||
`sudo pacman -S --needed base-devel clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip`
|
`sudo pacman -S --needed base-devel bluez-libs clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip`
|
||||||
|
|
||||||
|
#### For Debian, Ubuntu and derivatives:
|
||||||
|
`sudo apt install -y cmake curl clang-15 freeglut3-dev git libbluetooth-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build`
|
||||||
|
|
||||||
|
You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package.
|
||||||
|
|
||||||
|
At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clang), use the following command instead:
|
||||||
|
`cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja`
|
||||||
|
|
||||||
#### For Fedora and derivatives:
|
#### For Fedora and derivatives:
|
||||||
`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel nasm ninja-build perl-core systemd-devel zlib-devel`
|
`sudo dnf install bluez-libs-devel clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel wayland-protocols-devel zlib-devel zlib-static`
|
||||||
|
|
||||||
### Build Cemu using cmake and clang
|
### Build Cemu
|
||||||
1. `git clone --recursive https://github.com/cemu-project/Cemu`
|
|
||||||
2. `cd Cemu`
|
|
||||||
3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja`
|
|
||||||
4. `cmake --build build`
|
|
||||||
5. You should now have a Cemu executable file in the /bin folder, which you can run using `./bin/Cemu_release`.
|
|
||||||
|
|
||||||
#### Using GCC
|
#### CMake and Clang
|
||||||
While we use and test Cemu using clang, using GCC might work better with your distro (they should be fairly similar performance/issues wise and should only be considered if compilation is the issue).
|
|
||||||
You can use GCC by doing the following:
|
|
||||||
- make sure you have g++ installed in your system
|
|
||||||
- installation for Ubuntu and derivatives: `sudo apt install g++`
|
|
||||||
- installation for Fedora and derivatives: `sudo dnf install gcc-c++`
|
|
||||||
- replace the step 3 with the following:
|
|
||||||
`cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -G Ninja`
|
|
||||||
|
|
||||||
#### Troubleshooting steps
|
```
|
||||||
- If step 3 gives you an error about not being able to find ninja, try appending `-DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` to the command and running it again.
|
git clone --recursive https://github.com/cemu-project/Cemu
|
||||||
- If step 3 fails while compiling the boost-build dependency, it means you don't have a working/good standard library installation. Check the integrity of your system headers and making sure that C++ related packages are installed and intact.
|
cd Cemu
|
||||||
- If step 3 gives a random error, read the `[package-name-and-platform]-out.log` and `[package-name-and-platform]-err.log` for the actual reason to see if you might be lacking the headers from a dependency.
|
cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja
|
||||||
- If step 3 is still failing or if you're not able to find the cause, please make an issue on our Github about it!
|
cmake --build build
|
||||||
- If step 3 fails during rebuild after `git pull` with an error that mentions RPATH, add this to the end of step 3: `-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON`
|
```
|
||||||
- If step 4 gives you an error that contains something like `main.cpp.o: in function 'std::__cxx11::basic_string...`, you likely are experiencing a clang-14 issue. This can only be fixed by either lowering the clang version or using GCC, see below.
|
|
||||||
- If step 4 gives you a different error, you could report it to this repo or try using GCC. Just make sure your standard library and compilers are updated since Cemu uses a lot of modern features!
|
#### GCC
|
||||||
- If step 4 gives you undefined libdecor_xx, you are likely experiencing an issue with sdl2 package that comes with vcpkg. Delete sdl2 from vcpkg.json in source file and recompile.
|
|
||||||
- If step 4 gives you `fatal error: 'span' file not found`, then you're either missing `libstdc++` or are using a version that's too old. Install at least v10 with your package manager, eg `sudo apt install libstdc++-10-dev`. See #644.
|
If you are building using GCC, make sure you have g++ installed:
|
||||||
|
- Installation for Arch and derivatives: `sudo pacman -S gcc`
|
||||||
|
- Installation for Debian, Ubuntu and derivatives: `sudo apt install g++`
|
||||||
|
- Installation for Fedora and derivatives: `sudo dnf install gcc-c++`
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone --recursive https://github.com/cemu-project/Cemu
|
||||||
|
cd Cemu
|
||||||
|
cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -G Ninja
|
||||||
|
cmake --build build
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Debug Build
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone --recursive https://github.com/cemu-project/Cemu
|
||||||
|
cd Cemu
|
||||||
|
cmake -S . -B build -DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja
|
||||||
|
cmake --build build
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are using GCC, replace `cmake -S . -B build -DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja` with `cmake -S . -B build -DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -G Ninja`
|
||||||
|
|
||||||
|
#### Troubleshooting Steps
|
||||||
|
|
||||||
|
##### Compiling Errors
|
||||||
|
|
||||||
|
This section refers to running `cmake -S...` (truncated).
|
||||||
|
|
||||||
|
* `vcpkg install failed`
|
||||||
|
* Run the following in the root directory and try running the command again (don't forget to change directories afterwards):
|
||||||
|
* `cd dependencies/vcpkg && git fetch --unshallow`
|
||||||
|
* `Please ensure you're using the latest port files with git pull and vcpkg update.`
|
||||||
|
* Either:
|
||||||
|
* Update vcpkg by running by the following command:
|
||||||
|
* `git submodule update --remote dependencies/vcpkg`
|
||||||
|
* If you are sure vcpkg is up to date, check the following logs:
|
||||||
|
* `Cemu/dependencies/vcpkg/buildtrees/wxwidgets/config-x64-linux-out.log`
|
||||||
|
* `Cemu/dependencies/vcpkg/buildtrees/libsystemd/config-x64-linux-dbg-meson-log.txt.log`
|
||||||
|
* `Cemu/dependencies/vcpkg/buildtrees/libsystemd/config-x64-linux-dbg-out.log`
|
||||||
|
* Not able to find Ninja.
|
||||||
|
* Add the following and try running the command again:
|
||||||
|
* `-DCMAKE_MAKE_PROGRAM=/usr/bin/ninja`
|
||||||
|
* Compiling failed during the boost-build dependency.
|
||||||
|
* It means you don't have a working/good standard library installation. Check the integrity of your system headers and making sure that C++ related packages are installed and intact.
|
||||||
|
* Compiling failed during rebuild after `git pull` with an error that mentions RPATH
|
||||||
|
* Add the following and try running the command again:
|
||||||
|
* `-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON`
|
||||||
|
* Environment variable `VCPKG_FORCE_SYSTEM_BINARIES` must be set.
|
||||||
|
* Execute the folowing and then try running the command again:
|
||||||
|
* `export VCPKG_FORCE_SYSTEM_BINARIES=1`
|
||||||
|
* If you are getting a random error, read the [package-name-and-platform]-out.log and [package-name-and-platform]-err.log for the actual reason to see if you might be lacking the headers from a dependency.
|
||||||
|
|
||||||
|
|
||||||
|
If you are getting a different error than any of the errors listed above, you may either open an issue in this repo or try using [GCC](#gcc). Make sure your standard library and compilers are updated since Cemu uses a lot of modern features!
|
||||||
|
|
||||||
|
|
||||||
|
##### Building Errors
|
||||||
|
|
||||||
|
This section refers to running `cmake --build build`.
|
||||||
|
|
||||||
|
* `main.cpp.o: in function 'std::__cxx11::basic_string...`
|
||||||
|
* You likely are experiencing a clang-14 issue. This can only be fixed by either lowering the clang version or using GCC, see [GCC](#gcc).
|
||||||
|
* `fatal error: 'span' file not found`
|
||||||
|
* You're either missing `libstdc++` or are using a version that's too old. Install at least v10 with your package manager, eg `sudo apt install libstdc++-10-dev`. See [#644](https://github.com/cemu-project/Cemu/issues/644).
|
||||||
|
* `undefined libdecor_xx`
|
||||||
|
* You are likely experiencing an issue with sdl2 package that comes with vcpkg. Delete sdl2 from vcpkg.json in source file and recompile.
|
||||||
|
|
||||||
|
If you are getting a different error than any of the errors listed above, you may either open an issue in this repo or try using [GCC](#gcc). Make sure your standard library and compilers are updated since Cemu uses a lot of modern features!
|
||||||
|
|
||||||
## macOS
|
## macOS
|
||||||
|
|
||||||
To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and
|
To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and below
|
||||||
below, built in LLVM, and Xcode LLVM don't support the C++20 feature set required. The OpenGL graphics
|
don't support the C++20 feature set required, so either install LLVM from Homebrew or make sure that
|
||||||
API isn't support on macOS, Vulkan must be used. Additionally Vulkan must be used through the
|
you have a recent enough version of Xcode. Xcode 15 is known to work. The OpenGL graphics API isn't
|
||||||
Molten-VK compatibility layer
|
supported on macOS, so Vulkan must be used through the Molten-VK compatibility layer.
|
||||||
|
|
||||||
### On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used
|
|
||||||
|
|
||||||
You can skip this section if you have an Intel Mac. Every time you compile, you need to perform steps 2.
|
|
||||||
|
|
||||||
1. `softwareupdate --install-rosetta` # Install Rosetta 2 if you don't have it. This only has to be done once
|
|
||||||
2. `arch -x86_64 zsh` # run an x64 shell
|
|
||||||
|
|
||||||
### Installing brew
|
### Installing brew
|
||||||
|
|
||||||
1. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
|
1. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
|
||||||
2. `eval "$(/usr/local/Homebrew/bin/brew shellenv)"` # set x86_64 brew env
|
2. Set up the Homebrew shell environment:
|
||||||
|
1. **On an Intel Mac:** `eval "$(/usr/local/Homebrew/bin/brew shellenv)"`
|
||||||
|
2. **On an Apple Silicon Mac:** eval `"$(/opt/homebrew/bin/brew shellenv)"`
|
||||||
|
|
||||||
### Installing dependencies
|
### Installing Tool Dependencies
|
||||||
|
|
||||||
`brew install boost git cmake llvm ninja nasm molten-vk`
|
The native versions of these can be used regardless of what type of Mac you have.
|
||||||
|
|
||||||
|
`brew install git cmake ninja nasm automake libtool`
|
||||||
|
|
||||||
|
### Installing Library Dependencies
|
||||||
|
|
||||||
|
**On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used to install these dependencies:**
|
||||||
|
1. `softwareupdate --install-rosetta` # Install Rosetta 2 if you don't have it. This only has to be done once
|
||||||
|
2. `arch -x86_64 zsh` # run an x64 shell
|
||||||
|
3. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
|
||||||
|
4. `eval "$(/usr/local/Homebrew/bin/brew shellenv)"`
|
||||||
|
|
||||||
|
Then install the dependencies:
|
||||||
|
|
||||||
|
`brew install boost molten-vk`
|
||||||
|
|
||||||
|
### Build Cemu using CMake
|
||||||
|
|
||||||
### Build Cemu using cmake and clang
|
|
||||||
1. `git clone --recursive https://github.com/cemu-project/Cemu`
|
1. `git clone --recursive https://github.com/cemu-project/Cemu`
|
||||||
2. `cd Cemu`
|
2. `cd Cemu`
|
||||||
3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++ -G Ninja`
|
3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_OSX_ARCHITECTURES=x86_64 -G Ninja`
|
||||||
4. `cmake --build build`
|
4. `cmake --build build`
|
||||||
5. You should now have a Cemu executable file in the /bin folder, which you can run using `./bin/Cemu_release`.
|
5. You should now have a Cemu executable file in the /bin folder, which you can run using `./bin/Cemu_release`.
|
||||||
|
|
||||||
|
@ -104,3 +191,42 @@ You can skip this section if you have an Intel Mac. Every time you compile, you
|
||||||
2. Then, you can rebuild Cemu using the steps listed above, according to whether you use Linux or Windows.
|
2. Then, you can rebuild Cemu using the steps listed above, according to whether you use Linux or Windows.
|
||||||
|
|
||||||
If CMake complains about Cemu already being compiled or another similar error, try deleting the `CMakeCache.txt` file inside the `build` folder and retry building.
|
If CMake complains about Cemu already being compiled or another similar error, try deleting the `CMakeCache.txt` file inside the `build` folder and retry building.
|
||||||
|
|
||||||
|
## CMake configure flags
|
||||||
|
Some flags can be passed during CMake configure to customise which features are enabled on build.
|
||||||
|
|
||||||
|
Example usage: `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DENABLE_SDL=ON -DENABLE_VULKAN=OFF`
|
||||||
|
|
||||||
|
### All platforms
|
||||||
|
| Flag | | Description | Default | Note |
|
||||||
|
|--------------------|:--|-----------------------------------------------------------------------------|---------|--------------------|
|
||||||
|
| ALLOW_PORTABLE | | Allow Cemu to use the `portable` directory to store configs and data | ON | |
|
||||||
|
| CEMU_CXX_FLAGS | | Flags passed straight to the compiler, e.g. `-march=native`, `-Wall`, `/W3` | "" | |
|
||||||
|
| ENABLE_CUBEB | | Enable cubeb audio backend | ON | |
|
||||||
|
| ENABLE_DISCORD_RPC | | Enable Discord Rich presence support | ON | |
|
||||||
|
| ENABLE_OPENGL | | Enable OpenGL graphics backend | ON | Currently required |
|
||||||
|
| ENABLE_HIDAPI | | Enable HIDAPI (used for Wiimote controller API) | ON | |
|
||||||
|
| ENABLE_SDL | | Enable SDLController controller API | ON | Currently required |
|
||||||
|
| ENABLE_VCPKG | | Use VCPKG package manager to obtain dependencies | ON | |
|
||||||
|
| ENABLE_VULKAN | | Enable the Vulkan graphics backend | ON | |
|
||||||
|
| ENABLE_WXWIDGETS | | Enable wxWidgets UI | ON | Currently required |
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
| Flag | Description | Default | Note |
|
||||||
|
|--------------------|-----------------------------------|---------|--------------------|
|
||||||
|
| ENABLE_DIRECTAUDIO | Enable DirectAudio audio backend | ON | Currently required |
|
||||||
|
| ENABLE_DIRECTINPUT | Enable DirectInput controller API | ON | Currently required |
|
||||||
|
| ENABLE_XAUDIO | Enable XAudio audio backend | ON | |
|
||||||
|
| ENABLE_XINPUT | Enable XInput controller API | ON | |
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
| Flag | Description | Default |
|
||||||
|
|-----------------------|----------------------------------------------------|---------|
|
||||||
|
| ENABLE_BLUEZ | Build with Bluez (used for Wiimote controller API) | ON |
|
||||||
|
| ENABLE_FERAL_GAMEMODE | Enable Feral Interactive GameMode support | ON |
|
||||||
|
| ENABLE_WAYLAND | Enable Wayland support | ON |
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
| Flag | Description | Default |
|
||||||
|
|--------------|------------------------------------------------|---------|
|
||||||
|
| MACOS_BUNDLE | MacOS executable will be an application bundle | OFF |
|
||||||
|
|
|
@ -1,24 +1,45 @@
|
||||||
cmake_minimum_required(VERSION 3.21.1)
|
cmake_minimum_required(VERSION 3.21.1)
|
||||||
|
|
||||||
option(ENABLE_VCPKG "Enable the vcpkg package manager" ON)
|
option(ENABLE_VCPKG "Enable the vcpkg package manager" ON)
|
||||||
option(PORTABLE "All data created and maintained by Cemu will be in the directory where the executable file is located" ON)
|
|
||||||
option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF)
|
option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF)
|
||||||
set(EXPERIMENTAL_VERSION "" CACHE STRING "") # used by CI script to set experimental version
|
option(ALLOW_PORTABLE "Allow Cemu to be run in portable mode" ON)
|
||||||
|
|
||||||
if (EXPERIMENTAL_VERSION)
|
# used by CI script to set version:
|
||||||
add_definitions(-DEMULATOR_VERSION_MINOR=${EXPERIMENTAL_VERSION})
|
set(EMULATOR_VERSION_MAJOR "0" CACHE STRING "")
|
||||||
execute_process(
|
set(EMULATOR_VERSION_MINOR "0" CACHE STRING "")
|
||||||
|
set(EMULATOR_VERSION_PATCH "0" CACHE STRING "")
|
||||||
|
|
||||||
|
execute_process(
|
||||||
COMMAND git log --format=%h -1
|
COMMAND git log --format=%h -1
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
||||||
OUTPUT_VARIABLE GIT_HASH
|
OUTPUT_VARIABLE GIT_HASH
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
)
|
)
|
||||||
add_definitions(-DEMULATOR_HASH=${GIT_HASH})
|
add_definitions(-DEMULATOR_HASH=${GIT_HASH})
|
||||||
endif()
|
|
||||||
|
|
||||||
if (ENABLE_VCPKG)
|
if (ENABLE_VCPKG)
|
||||||
|
# check if vcpkg is shallow and unshallow it if necessary
|
||||||
|
execute_process(
|
||||||
|
COMMAND git rev-parse --is-shallow-repository
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/dependencies/vcpkg
|
||||||
|
OUTPUT_VARIABLE is_vcpkg_shallow
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
)
|
||||||
|
|
||||||
|
if(is_vcpkg_shallow STREQUAL "true")
|
||||||
|
message(STATUS "vcpkg is shallow. Unshallowing it now...")
|
||||||
|
execute_process(
|
||||||
|
COMMAND git fetch --unshallow
|
||||||
|
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/dependencies/vcpkg"
|
||||||
|
RESULT_VARIABLE result
|
||||||
|
OUTPUT_VARIABLE output
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_linux")
|
set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_linux")
|
||||||
|
elseif(APPLE)
|
||||||
|
set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_mac")
|
||||||
else()
|
else()
|
||||||
set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports")
|
set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports")
|
||||||
endif()
|
endif()
|
||||||
|
@ -43,9 +64,9 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
add_compile_definitions($<$<CONFIG:Debug>:CEMU_DEBUG_ASSERT>) # if build type is debug, set CEMU_DEBUG_ASSERT
|
add_compile_definitions($<$<CONFIG:Debug>:CEMU_DEBUG_ASSERT>) # if build type is debug, set CEMU_DEBUG_ASSERT
|
||||||
|
|
||||||
if(PORTABLE)
|
add_definitions(-DEMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR})
|
||||||
add_compile_definitions(PORTABLE)
|
add_definitions(-DEMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR})
|
||||||
endif()
|
add_definitions(-DEMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH})
|
||||||
|
|
||||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||||
|
|
||||||
|
@ -72,11 +93,13 @@ endif()
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
enable_language(OBJC OBJCXX)
|
enable_language(OBJC OBJCXX)
|
||||||
|
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (UNIX AND NOT APPLE)
|
if (UNIX AND NOT APPLE)
|
||||||
option(ENABLE_WAYLAND "Build with Wayland support" ON)
|
option(ENABLE_WAYLAND "Build with Wayland support" ON)
|
||||||
option(ENABLE_FERAL_GAMEMODE "Enables Feral Interactive GameMode Support" ON)
|
option(ENABLE_FERAL_GAMEMODE "Enables Feral Interactive GameMode Support" ON)
|
||||||
|
option(ENABLE_BLUEZ "Build with Bluez support" ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
option(ENABLE_OPENGL "Enables the OpenGL backend" ON)
|
option(ENABLE_OPENGL "Enables the OpenGL backend" ON)
|
||||||
|
@ -89,10 +112,9 @@ if (WIN32)
|
||||||
option(ENABLE_XINPUT "Enables the usage of XInput" ON)
|
option(ENABLE_XINPUT "Enables the usage of XInput" ON)
|
||||||
option(ENABLE_DIRECTINPUT "Enables the usage of DirectInput" ON)
|
option(ENABLE_DIRECTINPUT "Enables the usage of DirectInput" ON)
|
||||||
add_compile_definitions(HAS_DIRECTINPUT)
|
add_compile_definitions(HAS_DIRECTINPUT)
|
||||||
set(ENABLE_WIIMOTE ON)
|
|
||||||
elseif (UNIX)
|
|
||||||
option(ENABLE_HIDAPI "Build with HIDAPI" ON)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
option(ENABLE_HIDAPI "Build with HIDAPI" ON)
|
||||||
option(ENABLE_SDL "Enables the SDLController backend" ON)
|
option(ENABLE_SDL "Enables the SDLController backend" ON)
|
||||||
|
|
||||||
# audio backends
|
# audio backends
|
||||||
|
@ -117,7 +139,7 @@ find_package(ZLIB REQUIRED)
|
||||||
find_package(zstd MODULE REQUIRED) # MODULE so that zstd::zstd is available
|
find_package(zstd MODULE REQUIRED) # MODULE so that zstd::zstd is available
|
||||||
find_package(OpenSSL COMPONENTS Crypto SSL REQUIRED)
|
find_package(OpenSSL COMPONENTS Crypto SSL REQUIRED)
|
||||||
find_package(glm REQUIRED)
|
find_package(glm REQUIRED)
|
||||||
find_package(fmt 9.1.0...<10 REQUIRED)
|
find_package(fmt 9 REQUIRED)
|
||||||
find_package(PNG REQUIRED)
|
find_package(PNG REQUIRED)
|
||||||
|
|
||||||
# glslang versions older than 11.11.0 define targets without a namespace
|
# glslang versions older than 11.11.0 define targets without a namespace
|
||||||
|
@ -142,6 +164,12 @@ if (UNIX AND NOT APPLE)
|
||||||
endif()
|
endif()
|
||||||
find_package(GTK3 REQUIRED)
|
find_package(GTK3 REQUIRED)
|
||||||
|
|
||||||
|
if(ENABLE_BLUEZ)
|
||||||
|
find_package(bluez REQUIRED)
|
||||||
|
set(ENABLE_WIIMOTE ON)
|
||||||
|
add_compile_definitions(HAS_BLUEZ)
|
||||||
|
endif()
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ENABLE_VULKAN)
|
if (ENABLE_VULKAN)
|
||||||
|
@ -185,7 +213,7 @@ if (ENABLE_CUBEB)
|
||||||
option(BUILD_TOOLS "" OFF)
|
option(BUILD_TOOLS "" OFF)
|
||||||
option(BUNDLE_SPEEX "" OFF)
|
option(BUNDLE_SPEEX "" OFF)
|
||||||
set(USE_WINMM OFF CACHE BOOL "")
|
set(USE_WINMM OFF CACHE BOOL "")
|
||||||
add_subdirectory("dependencies/cubeb" EXCLUDE_FROM_ALL)
|
add_subdirectory("dependencies/cubeb" EXCLUDE_FROM_ALL SYSTEM)
|
||||||
set_property(TARGET cubeb PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
set_property(TARGET cubeb PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||||
add_library(cubeb::cubeb ALIAS cubeb)
|
add_library(cubeb::cubeb ALIAS cubeb)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
"generator": "Ninja",
|
"generator": "Ninja",
|
||||||
"inheritEnvironments": [ "msvc_x64_x64" ],
|
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
"installRoot": "${projectDir}\\out\\install\\${name}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Debug",
|
"name": "Debug",
|
||||||
|
|
99
CODING_STYLE.md
Normal file
99
CODING_STYLE.md
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
|
||||||
|
# Coding style guidelines for Cemu
|
||||||
|
|
||||||
|
This document describes the latest version of our coding-style guidelines. Since we did not use this style from the beginning, older code may not adhere to these guidelines. Nevertheless, use these rules even if the surrounding code does not match.
|
||||||
|
|
||||||
|
Cemu comes with a `.clang-format` file which is supported by most IDEs for formatting. Avoid auto-reformatting whole files, PRs with a lot of formatting changes are difficult to review.
|
||||||
|
|
||||||
|
## Names for variables, functions and classes
|
||||||
|
|
||||||
|
- Always prefix class member variables with `m_`
|
||||||
|
- Always prefix static class variables with `s_`
|
||||||
|
- For variable names: Camel case, starting with a lower case letter after the prefix. Examples: `m_option`, `s_audioVolume`
|
||||||
|
- For functions/class names: Use camel case starting with a capital letter. Examples: `MyClass`, `SetActive`
|
||||||
|
- Avoid underscores in variable names after the prefix. Use `m_myVariable` instead of `m_my_variable`
|
||||||
|
|
||||||
|
## About types
|
||||||
|
|
||||||
|
Cemu provides its own set of basic fixed-width types. They are:
|
||||||
|
`uint8`, `sint8`, `uint16`, `sint16`, `uint32`, `sint32`, `uint64`, `sint64`. Always use these types over something like `uint32_t`. Using `size_t` is also acceptable where suitable. Avoid C types like `int` or `long`. The only exception is when interacting with external libraries which expect these types as parameters.
|
||||||
|
|
||||||
|
## When and where to put brackets
|
||||||
|
|
||||||
|
Always put curly-brackets (`{ }`) on their own line. Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
void FooBar()
|
||||||
|
{
|
||||||
|
if (m_hasFoo)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
As an exception, you can put short lambdas onto the same line:
|
||||||
|
```
|
||||||
|
SomeFunc([]() { .... });
|
||||||
|
```
|
||||||
|
You can skip brackets for single-statement `if`. Example:
|
||||||
|
```
|
||||||
|
if (cond)
|
||||||
|
action();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Printing
|
||||||
|
|
||||||
|
Avoid sprintf and similar C-style formatting API. Use `fmt::format()`.
|
||||||
|
In UI related code you can use `formatWxString`, but be aware that number formatting with this function will be locale dependent!
|
||||||
|
|
||||||
|
## Strings and encoding
|
||||||
|
|
||||||
|
We use UTF-8 encoded `std::string` where possible. Some conversions need special handling and we have helper functions for those:
|
||||||
|
```cpp
|
||||||
|
// std::filesystem::path <-> std::string (in precompiled.h)
|
||||||
|
std::string _pathToUtf8(const fs::path& path);
|
||||||
|
fs::path _utf8ToPath(std::string_view input);
|
||||||
|
|
||||||
|
// wxString <-> std::string
|
||||||
|
wxString wxString::FromUTF8(const std::string& s)
|
||||||
|
wxString to_wxString(std::string_view str); // in gui/helpers.h
|
||||||
|
std::string wxString::utf8_string();
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
If you want to write to log.txt use `cemuLog_log()`. The log type parameter should be mostly self-explanatory. Use `LogType::Force` if you always want to log something. For example:
|
||||||
|
`cemuLog_log(LogType::Force, "The value is {}", 123);`
|
||||||
|
|
||||||
|
## HLE and endianness
|
||||||
|
|
||||||
|
A pretty large part of Cemu's code base are re-implementations of various Cafe OS modules (e.g. `coreinit.rpl`, `gx2.rpl`...). These generally run in the context of the emulated process, thus special care has to be taken to use types with the correct size and endianness when interacting with memory.
|
||||||
|
|
||||||
|
Keep in mind that the emulated Espresso CPU is 32bit big-endian, while the host architectures targeted by Cemu are 64bit little-endian!
|
||||||
|
|
||||||
|
To keep code simple and remove the need for manual endian-swapping, Cemu has templates and aliases of the basic types with explicit endian-ness.
|
||||||
|
For big-endian types add the suffix `be`. Example: `uint32be`
|
||||||
|
|
||||||
|
When you need to store a pointer in the guest's memory. Use `MEMPTR<T>`. It will automatically store any pointer as 32bit big-endian. The pointer you store must point to memory that is within the guest address space.
|
||||||
|
|
||||||
|
## HLE interfaces
|
||||||
|
|
||||||
|
The implementation for each HLE module is inside a namespace with a matching name. E.g. `coreinit.rpl` functions go into `coreinit` namespace.
|
||||||
|
|
||||||
|
To expose a new function as callable from within the emulated machine, use `cafeExportRegister` or `cafeExportRegisterFunc`. Here is a short example:
|
||||||
|
```cpp
|
||||||
|
namespace coreinit
|
||||||
|
{
|
||||||
|
uint32 OSGetCoreCount()
|
||||||
|
{
|
||||||
|
return Espresso::CORE_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Init()
|
||||||
|
{
|
||||||
|
cafeExportRegister("coreinit", OSGetCoreCount, LogType::CoreinitThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
You may also see some code which uses `osLib_addFunction` directly. This is a deprecated way of registering functions.
|
13
README.md
13
README.md
|
@ -5,7 +5,7 @@
|
||||||
[](https://matrix.to/#/#cemu:cemu.info)
|
[](https://matrix.to/#/#cemu:cemu.info)
|
||||||
|
|
||||||
This is the code repository of Cemu, a Wii U emulator that is able to run most Wii U games and homebrew in a playable state.
|
This is the code repository of Cemu, a Wii U emulator that is able to run most Wii U games and homebrew in a playable state.
|
||||||
It's written in C/C++ and is being actively developed with new features and fixes to increase compatibility, convenience and usability.
|
It's written in C/C++ and is being actively developed with new features and fixes.
|
||||||
|
|
||||||
Cemu is currently only available for 64-bit Windows, Linux & macOS devices.
|
Cemu is currently only available for 64-bit Windows, Linux & macOS devices.
|
||||||
|
|
||||||
|
@ -24,11 +24,9 @@ Cemu is currently only available for 64-bit Windows, Linux & macOS devices.
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
You can download the latest Cemu releases from the [GitHub Releases](https://github.com/cemu-project/Cemu/releases/) or from [Cemu's website](https://cemu.info).
|
You can download the latest Cemu releases for Windows, Linux and Mac from the [GitHub Releases](https://github.com/cemu-project/Cemu/releases/). For Linux you can also find Cemu on [flathub](https://flathub.org/apps/info.cemu.Cemu).
|
||||||
|
|
||||||
Cemu is currently only available in a portable format so no installation is required besides extracting it in a safe place.
|
On Windows Cemu is currently only available in a portable format so no installation is required besides extracting it in a safe place.
|
||||||
|
|
||||||
The native Linux build is currently a work-in-progress. See [Current State Of Linux builds](https://github.com/cemu-project/Cemu/issues/107) for more information about the things to be aware of.
|
|
||||||
|
|
||||||
The native macOS build is currently purely experimental and should not be considered stable or ready for issue-free gameplay. There are also known issues with degraded performance due to the use of MoltenVK and Rosetta for ARM Macs. We appreciate your patience while we improve Cemu for macOS.
|
The native macOS build is currently purely experimental and should not be considered stable or ready for issue-free gameplay. There are also known issues with degraded performance due to the use of MoltenVK and Rosetta for ARM Macs. We appreciate your patience while we improve Cemu for macOS.
|
||||||
|
|
||||||
|
@ -36,7 +34,7 @@ Pre-2.0 releases can be found on Cemu's [changelog page](https://cemu.info/chang
|
||||||
|
|
||||||
## Build Instructions
|
## Build Instructions
|
||||||
|
|
||||||
To compile Cemu yourself on Windows, Linux or macOS, view the [BUILD.md file](/BUILD.md).
|
To compile Cemu yourself on Windows, Linux or macOS, view [BUILD.md](/BUILD.md).
|
||||||
|
|
||||||
## Issues
|
## Issues
|
||||||
|
|
||||||
|
@ -46,10 +44,11 @@ The old bug tracker can be found at [bugs.cemu.info](https://bugs.cemu.info) and
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Pull requests are very welcome. For easier coordination you can visit the developer discussion channel on [Discord](https://discord.gg/5psYsup) or alternatively the [Matrix Server](https://matrix.to/#/#cemu:cemu.info).
|
Pull requests are very welcome. For easier coordination you can visit the developer discussion channel on [Discord](https://discord.gg/5psYsup) or alternatively the [Matrix Server](https://matrix.to/#/#cemu:cemu.info).
|
||||||
|
Before submitting a pull request, please read and follow our code style guidelines listed in [CODING_STYLE.md](/CODING_STYLE.md).
|
||||||
|
|
||||||
If coding isn't your thing, testing games and making detailed bug reports or updating the (usually outdated) compatibility wiki is also appreciated!
|
If coding isn't your thing, testing games and making detailed bug reports or updating the (usually outdated) compatibility wiki is also appreciated!
|
||||||
|
|
||||||
Questions about Cemu's software architecture can also be answered on Discord (through the Matrix bridge).
|
Questions about Cemu's software architecture can also be answered on Discord (or through the Matrix bridge).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Cemu is licensed under [Mozilla Public License 2.0](/LICENSE.txt). Exempt from this are all files in the dependencies directory for which the licenses of the original code apply as well as some individual files in the src folder, as specified in those file headers respectively.
|
Cemu is licensed under [Mozilla Public License 2.0](/LICENSE.txt). Exempt from this are all files in the dependencies directory for which the licenses of the original code apply as well as some individual files in the src folder, as specified in those file headers respectively.
|
||||||
|
|
BIN
bin/resources/ar/cemu.mo
Normal file
BIN
bin/resources/ar/cemu.mo
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
bin/resources/he/cemu.mo
Normal file
BIN
bin/resources/he/cemu.mo
Normal file
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.
|
@ -1 +0,0 @@
|
||||||
If you plan to transfer the shader cache to a different PC or Cemu installation you only need to copy the 'transferable' directory.
|
|
20
cmake/Findbluez.cmake
Normal file
20
cmake/Findbluez.cmake
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 Andrea Pappacoda <andrea@pappacoda.it>
|
||||||
|
# SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
find_package(bluez CONFIG)
|
||||||
|
if (NOT bluez_FOUND)
|
||||||
|
find_package(PkgConfig)
|
||||||
|
if (PKG_CONFIG_FOUND)
|
||||||
|
pkg_search_module(bluez IMPORTED_TARGET GLOBAL bluez-1.0 bluez)
|
||||||
|
if (bluez_FOUND)
|
||||||
|
add_library(bluez::bluez ALIAS PkgConfig::bluez)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
find_package_handle_standard_args(bluez
|
||||||
|
REQUIRED_VARS
|
||||||
|
bluez_LINK_LIBRARIES
|
||||||
|
bluez_FOUND
|
||||||
|
VERSION_VAR bluez_VERSION
|
||||||
|
)
|
20
cmake/Findlibusb.cmake
Normal file
20
cmake/Findlibusb.cmake
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 Andrea Pappacoda <andrea@pappacoda.it>
|
||||||
|
# SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
find_package(libusb CONFIG)
|
||||||
|
if (NOT libusb_FOUND)
|
||||||
|
find_package(PkgConfig)
|
||||||
|
if (PKG_CONFIG_FOUND)
|
||||||
|
pkg_search_module(libusb IMPORTED_TARGET GLOBAL libusb-1.0 libusb)
|
||||||
|
if (libusb_FOUND)
|
||||||
|
add_library(libusb::libusb ALIAS PkgConfig::libusb)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
find_package_handle_standard_args(libusb
|
||||||
|
REQUIRED_VARS
|
||||||
|
libusb_LINK_LIBRARIES
|
||||||
|
libusb_FOUND
|
||||||
|
VERSION_VAR libusb_VERSION
|
||||||
|
)
|
18
dependencies/ih264d/CMakeLists.txt
vendored
18
dependencies/ih264d/CMakeLists.txt
vendored
|
@ -117,7 +117,13 @@ add_library (ih264d
|
||||||
"decoder/ivd.h"
|
"decoder/ivd.h"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "amd64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64")
|
if (CMAKE_OSX_ARCHITECTURES)
|
||||||
|
set(IH264D_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES})
|
||||||
|
else()
|
||||||
|
set(IH264D_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (IH264D_ARCHITECTURE STREQUAL "x86_64" OR IH264D_ARCHITECTURE STREQUAL "amd64" OR IH264D_ARCHITECTURE STREQUAL "AMD64")
|
||||||
set(LIBAVCDEC_X86_INCLUDES "common/x86" "decoder/x86")
|
set(LIBAVCDEC_X86_INCLUDES "common/x86" "decoder/x86")
|
||||||
include_directories("common/" "decoder/" ${LIBAVCDEC_X86_INCLUDES})
|
include_directories("common/" "decoder/" ${LIBAVCDEC_X86_INCLUDES})
|
||||||
target_sources(ih264d PRIVATE
|
target_sources(ih264d PRIVATE
|
||||||
|
@ -140,7 +146,7 @@ target_sources(ih264d PRIVATE
|
||||||
"decoder/x86/ih264d_function_selector_sse42.c"
|
"decoder/x86/ih264d_function_selector_sse42.c"
|
||||||
"decoder/x86/ih264d_function_selector_ssse3.c"
|
"decoder/x86/ih264d_function_selector_ssse3.c"
|
||||||
)
|
)
|
||||||
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
|
elseif(IH264D_ARCHITECTURE STREQUAL "aarch64" OR IH264D_ARCHITECTURE STREQUAL "arm64")
|
||||||
enable_language( C CXX ASM )
|
enable_language( C CXX ASM )
|
||||||
set(LIBAVCDEC_ARM_INCLUDES "common/armv8" "decoder/arm")
|
set(LIBAVCDEC_ARM_INCLUDES "common/armv8" "decoder/arm")
|
||||||
include_directories("common/" "decoder/" ${LIBAVCDEC_ARM_INCLUDES})
|
include_directories("common/" "decoder/" ${LIBAVCDEC_ARM_INCLUDES})
|
||||||
|
@ -178,9 +184,15 @@ target_sources(ih264d PRIVATE
|
||||||
)
|
)
|
||||||
target_compile_options(ih264d PRIVATE -DARMV8)
|
target_compile_options(ih264d PRIVATE -DARMV8)
|
||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "ih264d unknown architecture: ${CMAKE_SYSTEM_PROCESSOR}")
|
message(FATAL_ERROR "ih264d unknown architecture: ${IH264D_ARCHITECTURE}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
set_property(TARGET ih264d PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
set_property(TARGET ih264d PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||||
|
|
||||||
|
# tune settings for slightly better performance
|
||||||
|
target_compile_options(ih264d PRIVATE $<$<CONFIG:Release,RelWithDebInfo>:/Oi>) # enable intrinsic functions
|
||||||
|
target_compile_options(ih264d PRIVATE $<$<CONFIG:Release,RelWithDebInfo>:/Ot>) # favor speed
|
||||||
|
target_compile_options(ih264d PRIVATE "/GS-") # disable runtime checks
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
48
dependencies/ih264d/common/ithread.c
vendored
48
dependencies/ih264d/common/ithread.c
vendored
|
@ -85,28 +85,59 @@ UWORD32 ithread_get_mutex_lock_size(void)
|
||||||
return sizeof(CRITICAL_SECTION);
|
return sizeof(CRITICAL_SECTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct _ithread_launch_param
|
||||||
|
{
|
||||||
|
void (*startFunc)(void* argument);
|
||||||
|
void* argument;
|
||||||
|
};
|
||||||
|
|
||||||
|
DWORD WINAPI _ithread_WinThreadStartRoutine(LPVOID lpThreadParameter)
|
||||||
|
{
|
||||||
|
struct _ithread_launch_param* param = (struct _ithread_launch_param*)lpThreadParameter;
|
||||||
|
typedef void *(*ThreadStartRoutineType)(void *);
|
||||||
|
ThreadStartRoutineType pfnThreadRoutine = (ThreadStartRoutineType)param->startFunc;
|
||||||
|
void* arg = param->argument;
|
||||||
|
free(param);
|
||||||
|
pfnThreadRoutine(arg);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
WORD32 ithread_create(void* thread_handle, void* attribute, void* strt, void* argument)
|
WORD32 ithread_create(void* thread_handle, void* attribute, void* strt, void* argument)
|
||||||
{
|
{
|
||||||
//UNUSED(attribute);
|
UNUSED(attribute);
|
||||||
//return pthread_create((pthread_t*)thread_handle, NULL, (void* (*)(void*)) strt, argument);
|
struct _ithread_launch_param* param = malloc(sizeof(struct _ithread_launch_param));
|
||||||
__debugbreak();
|
param->startFunc = (void (*)(void*))strt;
|
||||||
|
param->argument = argument;
|
||||||
|
HANDLE *handle = (HANDLE*)thread_handle;
|
||||||
|
*handle = CreateThread(NULL, 0, _ithread_WinThreadStartRoutine, param, 0, NULL);
|
||||||
|
if(*handle == NULL)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
WORD32 ithread_join(void* thread_handle, void** val_ptr)
|
WORD32 ithread_join(void* thread_handle, void** val_ptr)
|
||||||
{
|
{
|
||||||
//UNUSED(val_ptr);
|
//UNUSED(val_ptr);
|
||||||
//pthread_t* pthread_handle = (pthread_t*)thread_handle;
|
HANDLE *handle = (HANDLE*)thread_handle;
|
||||||
//return pthread_join(*pthread_handle, NULL);
|
DWORD result = WaitForSingleObject(*handle, INFINITE);
|
||||||
|
if(result == WAIT_OBJECT_0)
|
||||||
__debugbreak();
|
{
|
||||||
|
CloseHandle(*handle);
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WORD32 ithread_get_mutex_struct_size(void)
|
WORD32 ithread_get_mutex_struct_size(void)
|
||||||
{
|
{
|
||||||
return sizeof(CRITICAL_SECTION);
|
return sizeof(CRITICAL_SECTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
WORD32 ithread_mutex_init(void* mutex)
|
WORD32 ithread_mutex_init(void* mutex)
|
||||||
{
|
{
|
||||||
InitializeCriticalSection((LPCRITICAL_SECTION)mutex);
|
InitializeCriticalSection((LPCRITICAL_SECTION)mutex);
|
||||||
|
@ -153,7 +184,6 @@ UWORD32 ithread_get_sem_struct_size(void)
|
||||||
//return(sizeof(sem_t));
|
//return(sizeof(sem_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
WORD32 ithread_sem_init(void* sem, WORD32 pshared, UWORD32 value)
|
WORD32 ithread_sem_init(void* sem, WORD32 pshared, UWORD32 value)
|
||||||
{
|
{
|
||||||
__debugbreak();
|
__debugbreak();
|
||||||
|
@ -168,7 +198,6 @@ WORD32 ithread_sem_post(void* sem)
|
||||||
//return sem_post((sem_t*)sem);
|
//return sem_post((sem_t*)sem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
WORD32 ithread_sem_wait(void* sem)
|
WORD32 ithread_sem_wait(void* sem)
|
||||||
{
|
{
|
||||||
__debugbreak();
|
__debugbreak();
|
||||||
|
@ -176,7 +205,6 @@ WORD32 ithread_sem_wait(void* sem)
|
||||||
//return sem_wait((sem_t*)sem);
|
//return sem_wait((sem_t*)sem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
WORD32 ithread_sem_destroy(void* sem)
|
WORD32 ithread_sem_destroy(void* sem)
|
||||||
{
|
{
|
||||||
__debugbreak();
|
__debugbreak();
|
||||||
|
|
|
@ -79,10 +79,8 @@
|
||||||
static inline int __builtin_clz(unsigned x)
|
static inline int __builtin_clz(unsigned x)
|
||||||
{
|
{
|
||||||
unsigned long n;
|
unsigned long n;
|
||||||
if (x == 0)
|
|
||||||
return 32;
|
|
||||||
_BitScanReverse(&n, x);
|
_BitScanReverse(&n, x);
|
||||||
return 31 - n;
|
return n ^ 31;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int __builtin_ctz(unsigned x) {
|
static inline int __builtin_ctz(unsigned x) {
|
||||||
|
|
2
dependencies/vcpkg
vendored
2
dependencies/vcpkg
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit b81bc3a83fdbdffe80325eeabb2ec735a1f3c29d
|
Subproject commit 533a5fda5c0646d1771345fb572e759283444d5f
|
13
dependencies/vcpkg_overlay_ports/sdl2/deps.patch
vendored
13
dependencies/vcpkg_overlay_ports/sdl2/deps.patch
vendored
|
@ -1,13 +0,0 @@
|
||||||
diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake
|
|
||||||
index 65a98efbe..2f99f28f1 100644
|
|
||||||
--- a/cmake/sdlchecks.cmake
|
|
||||||
+++ b/cmake/sdlchecks.cmake
|
|
||||||
@@ -352,7 +352,7 @@ endmacro()
|
|
||||||
# - HAVE_SDL_LOADSO opt
|
|
||||||
macro(CheckLibSampleRate)
|
|
||||||
if(SDL_LIBSAMPLERATE)
|
|
||||||
- find_package(SampleRate QUIET)
|
|
||||||
+ find_package(SampleRate CONFIG REQUIRED)
|
|
||||||
if(SampleRate_FOUND AND TARGET SampleRate::samplerate)
|
|
||||||
set(HAVE_LIBSAMPLERATE TRUE)
|
|
||||||
set(HAVE_LIBSAMPLERATE_H TRUE)
|
|
130
dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake
vendored
130
dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake
vendored
|
@ -1,130 +0,0 @@
|
||||||
vcpkg_from_github(
|
|
||||||
OUT_SOURCE_PATH SOURCE_PATH
|
|
||||||
REPO libsdl-org/SDL
|
|
||||||
REF "release-${VERSION}"
|
|
||||||
SHA512 90858ae8c5fdddd5e13724e05ad0970e11bbab1df8a0201c3f4ce354dc6018e5d4ab7279402a263c716aacdaa52745f78531dc225d48d790ee9307e2f6198695
|
|
||||||
HEAD_REF main
|
|
||||||
PATCHES
|
|
||||||
deps.patch
|
|
||||||
)
|
|
||||||
|
|
||||||
string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC)
|
|
||||||
string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED)
|
|
||||||
string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT)
|
|
||||||
|
|
||||||
vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS
|
|
||||||
FEATURES
|
|
||||||
vulkan SDL_VULKAN
|
|
||||||
x11 SDL_X11
|
|
||||||
wayland SDL_WAYLAND
|
|
||||||
samplerate SDL_LIBSAMPLERATE
|
|
||||||
ibus SDL_IBUS
|
|
||||||
)
|
|
||||||
|
|
||||||
if ("x11" IN_LIST FEATURES)
|
|
||||||
message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n")
|
|
||||||
endif()
|
|
||||||
if ("wayland" IN_LIST FEATURES)
|
|
||||||
message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n")
|
|
||||||
endif()
|
|
||||||
if ("ibus" IN_LIST FEATURES)
|
|
||||||
message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(VCPKG_TARGET_IS_UWP)
|
|
||||||
set(configure_opts WINDOWS_USE_MSBUILD)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
vcpkg_cmake_configure(
|
|
||||||
SOURCE_PATH "${SOURCE_PATH}"
|
|
||||||
${configure_opts}
|
|
||||||
OPTIONS ${FEATURE_OPTIONS}
|
|
||||||
-DSDL_STATIC=${SDL_STATIC}
|
|
||||||
-DSDL_SHARED=${SDL_SHARED}
|
|
||||||
-DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT}
|
|
||||||
-DSDL_LIBC=ON
|
|
||||||
-DSDL_TEST=OFF
|
|
||||||
-DSDL_INSTALL_CMAKEDIR="cmake"
|
|
||||||
-DCMAKE_DISABLE_FIND_PACKAGE_Git=ON
|
|
||||||
-DSDL_LIBSAMPLERATE_SHARED=OFF
|
|
||||||
MAYBE_UNUSED_VARIABLES
|
|
||||||
SDL_FORCE_STATIC_VCRT
|
|
||||||
)
|
|
||||||
|
|
||||||
vcpkg_cmake_install()
|
|
||||||
vcpkg_cmake_config_fixup(CONFIG_PATH cmake)
|
|
||||||
|
|
||||||
file(REMOVE_RECURSE
|
|
||||||
"${CURRENT_PACKAGES_DIR}/debug/include"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/debug/share"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/bin/sdl2-config"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/SDL2.framework"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/debug/SDL2.framework"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/share/licenses"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/share/aclocal"
|
|
||||||
)
|
|
||||||
|
|
||||||
file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*")
|
|
||||||
if(NOT BINS)
|
|
||||||
file(REMOVE_RECURSE
|
|
||||||
"${CURRENT_PACKAGES_DIR}/bin"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/debug/bin"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW)
|
|
||||||
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
|
|
||||||
file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link")
|
|
||||||
file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib")
|
|
||||||
endif()
|
|
||||||
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
|
|
||||||
file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link")
|
|
||||||
file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake")
|
|
||||||
foreach(SHARE_FILE ${SHARE_FILES})
|
|
||||||
vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main")
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
vcpkg_copy_pdbs()
|
|
||||||
|
|
||||||
set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)")
|
|
||||||
set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)")
|
|
||||||
file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX})
|
|
||||||
file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX})
|
|
||||||
string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}")
|
|
||||||
string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}")
|
|
||||||
|
|
||||||
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW)
|
|
||||||
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ")
|
|
||||||
endif()
|
|
||||||
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(VCPKG_TARGET_IS_UWP)
|
|
||||||
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:")
|
|
||||||
endif()
|
|
||||||
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "d")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
vcpkg_fixup_pkgconfig()
|
|
||||||
|
|
||||||
file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
|
|
||||||
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt")
|
|
8
dependencies/vcpkg_overlay_ports/sdl2/usage
vendored
8
dependencies/vcpkg_overlay_ports/sdl2/usage
vendored
|
@ -1,8 +0,0 @@
|
||||||
sdl2 provides CMake targets:
|
|
||||||
|
|
||||||
find_package(SDL2 CONFIG REQUIRED)
|
|
||||||
target_link_libraries(main
|
|
||||||
PRIVATE
|
|
||||||
$<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>
|
|
||||||
$<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>
|
|
||||||
)
|
|
58
dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json
vendored
58
dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json
vendored
|
@ -1,58 +0,0 @@
|
||||||
{
|
|
||||||
"name": "sdl2",
|
|
||||||
"version": "2.26.5",
|
|
||||||
"description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.",
|
|
||||||
"homepage": "https://www.libsdl.org/download-2.0.php",
|
|
||||||
"license": "Zlib",
|
|
||||||
"dependencies": [
|
|
||||||
{
|
|
||||||
"name": "vcpkg-cmake",
|
|
||||||
"host": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "vcpkg-cmake-config",
|
|
||||||
"host": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"default-features": [
|
|
||||||
"base"
|
|
||||||
],
|
|
||||||
"features": {
|
|
||||||
"base": {
|
|
||||||
"description": "Base functionality for SDL",
|
|
||||||
"dependencies": [
|
|
||||||
{
|
|
||||||
"name": "sdl2",
|
|
||||||
"default-features": false,
|
|
||||||
"features": [
|
|
||||||
"ibus",
|
|
||||||
"wayland",
|
|
||||||
"x11"
|
|
||||||
],
|
|
||||||
"platform": "linux"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"ibus": {
|
|
||||||
"description": "Build with ibus IME support",
|
|
||||||
"supports": "linux"
|
|
||||||
},
|
|
||||||
"samplerate": {
|
|
||||||
"description": "Use libsamplerate for audio rate conversion",
|
|
||||||
"dependencies": [
|
|
||||||
"libsamplerate"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"vulkan": {
|
|
||||||
"description": "Vulkan functionality for SDL"
|
|
||||||
},
|
|
||||||
"wayland": {
|
|
||||||
"description": "Build with Wayland support",
|
|
||||||
"supports": "linux"
|
|
||||||
},
|
|
||||||
"x11": {
|
|
||||||
"description": "Build with X11 support",
|
|
||||||
"supports": "!windows"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake
|
|
||||||
index 65a98efbe..2f99f28f1 100644
|
|
||||||
--- a/cmake/sdlchecks.cmake
|
|
||||||
+++ b/cmake/sdlchecks.cmake
|
|
||||||
@@ -352,7 +352,7 @@ endmacro()
|
|
||||||
# - HAVE_SDL_LOADSO opt
|
|
||||||
macro(CheckLibSampleRate)
|
|
||||||
if(SDL_LIBSAMPLERATE)
|
|
||||||
- find_package(SampleRate QUIET)
|
|
||||||
+ find_package(SampleRate CONFIG REQUIRED)
|
|
||||||
if(SampleRate_FOUND AND TARGET SampleRate::samplerate)
|
|
||||||
set(HAVE_LIBSAMPLERATE TRUE)
|
|
||||||
set(HAVE_LIBSAMPLERATE_H TRUE)
|
|
|
@ -1,130 +0,0 @@
|
||||||
vcpkg_from_github(
|
|
||||||
OUT_SOURCE_PATH SOURCE_PATH
|
|
||||||
REPO libsdl-org/SDL
|
|
||||||
REF "release-${VERSION}"
|
|
||||||
SHA512 90858ae8c5fdddd5e13724e05ad0970e11bbab1df8a0201c3f4ce354dc6018e5d4ab7279402a263c716aacdaa52745f78531dc225d48d790ee9307e2f6198695
|
|
||||||
HEAD_REF main
|
|
||||||
PATCHES
|
|
||||||
deps.patch
|
|
||||||
)
|
|
||||||
|
|
||||||
string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC)
|
|
||||||
string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED)
|
|
||||||
string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT)
|
|
||||||
|
|
||||||
vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS
|
|
||||||
FEATURES
|
|
||||||
vulkan SDL_VULKAN
|
|
||||||
x11 SDL_X11
|
|
||||||
wayland SDL_WAYLAND
|
|
||||||
samplerate SDL_LIBSAMPLERATE
|
|
||||||
ibus SDL_IBUS
|
|
||||||
)
|
|
||||||
|
|
||||||
if ("x11" IN_LIST FEATURES)
|
|
||||||
message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n")
|
|
||||||
endif()
|
|
||||||
if ("wayland" IN_LIST FEATURES)
|
|
||||||
message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n")
|
|
||||||
endif()
|
|
||||||
if ("ibus" IN_LIST FEATURES)
|
|
||||||
message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(VCPKG_TARGET_IS_UWP)
|
|
||||||
set(configure_opts WINDOWS_USE_MSBUILD)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
vcpkg_cmake_configure(
|
|
||||||
SOURCE_PATH "${SOURCE_PATH}"
|
|
||||||
${configure_opts}
|
|
||||||
OPTIONS ${FEATURE_OPTIONS}
|
|
||||||
-DSDL_STATIC=${SDL_STATIC}
|
|
||||||
-DSDL_SHARED=${SDL_SHARED}
|
|
||||||
-DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT}
|
|
||||||
-DSDL_LIBC=ON
|
|
||||||
-DSDL_TEST=OFF
|
|
||||||
-DSDL_INSTALL_CMAKEDIR="cmake"
|
|
||||||
-DCMAKE_DISABLE_FIND_PACKAGE_Git=ON
|
|
||||||
-DSDL_LIBSAMPLERATE_SHARED=OFF
|
|
||||||
MAYBE_UNUSED_VARIABLES
|
|
||||||
SDL_FORCE_STATIC_VCRT
|
|
||||||
)
|
|
||||||
|
|
||||||
vcpkg_cmake_install()
|
|
||||||
vcpkg_cmake_config_fixup(CONFIG_PATH cmake)
|
|
||||||
|
|
||||||
file(REMOVE_RECURSE
|
|
||||||
"${CURRENT_PACKAGES_DIR}/debug/include"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/debug/share"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/bin/sdl2-config"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/SDL2.framework"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/debug/SDL2.framework"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/share/licenses"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/share/aclocal"
|
|
||||||
)
|
|
||||||
|
|
||||||
file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*")
|
|
||||||
if(NOT BINS)
|
|
||||||
file(REMOVE_RECURSE
|
|
||||||
"${CURRENT_PACKAGES_DIR}/bin"
|
|
||||||
"${CURRENT_PACKAGES_DIR}/debug/bin"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW)
|
|
||||||
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
|
|
||||||
file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link")
|
|
||||||
file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib")
|
|
||||||
endif()
|
|
||||||
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
|
|
||||||
file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link")
|
|
||||||
file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake")
|
|
||||||
foreach(SHARE_FILE ${SHARE_FILES})
|
|
||||||
vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main")
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
vcpkg_copy_pdbs()
|
|
||||||
|
|
||||||
set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)")
|
|
||||||
set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)")
|
|
||||||
file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX})
|
|
||||||
file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX})
|
|
||||||
string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}")
|
|
||||||
string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}")
|
|
||||||
|
|
||||||
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW)
|
|
||||||
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ")
|
|
||||||
endif()
|
|
||||||
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(VCPKG_TARGET_IS_UWP)
|
|
||||||
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:")
|
|
||||||
endif()
|
|
||||||
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "d")
|
|
||||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
vcpkg_fixup_pkgconfig()
|
|
||||||
|
|
||||||
file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
|
|
||||||
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt")
|
|
|
@ -1,8 +0,0 @@
|
||||||
sdl2 provides CMake targets:
|
|
||||||
|
|
||||||
find_package(SDL2 CONFIG REQUIRED)
|
|
||||||
target_link_libraries(main
|
|
||||||
PRIVATE
|
|
||||||
$<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>
|
|
||||||
$<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>
|
|
||||||
)
|
|
|
@ -1,58 +0,0 @@
|
||||||
{
|
|
||||||
"name": "sdl2",
|
|
||||||
"version": "2.26.5",
|
|
||||||
"description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.",
|
|
||||||
"homepage": "https://www.libsdl.org/download-2.0.php",
|
|
||||||
"license": "Zlib",
|
|
||||||
"dependencies": [
|
|
||||||
{
|
|
||||||
"name": "vcpkg-cmake",
|
|
||||||
"host": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "vcpkg-cmake-config",
|
|
||||||
"host": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"default-features": [
|
|
||||||
"base"
|
|
||||||
],
|
|
||||||
"features": {
|
|
||||||
"base": {
|
|
||||||
"description": "Base functionality for SDL",
|
|
||||||
"dependencies": [
|
|
||||||
{
|
|
||||||
"name": "sdl2",
|
|
||||||
"default-features": false,
|
|
||||||
"features": [
|
|
||||||
"ibus",
|
|
||||||
"wayland",
|
|
||||||
"x11"
|
|
||||||
],
|
|
||||||
"platform": "linux"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"ibus": {
|
|
||||||
"description": "Build with ibus IME support",
|
|
||||||
"supports": "linux"
|
|
||||||
},
|
|
||||||
"samplerate": {
|
|
||||||
"description": "Use libsamplerate for audio rate conversion",
|
|
||||||
"dependencies": [
|
|
||||||
"libsamplerate"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"vulkan": {
|
|
||||||
"description": "Vulkan functionality for SDL"
|
|
||||||
},
|
|
||||||
"wayland": {
|
|
||||||
"description": "Build with Wayland support",
|
|
||||||
"supports": "linux"
|
|
||||||
},
|
|
||||||
"x11": {
|
|
||||||
"description": "Build with X11 support",
|
|
||||||
"supports": "!windows"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
71
dependencies/vcpkg_overlay_ports_mac/libusb/portfile.cmake
vendored
Normal file
71
dependencies/vcpkg_overlay_ports_mac/libusb/portfile.cmake
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
set(VCPKG_LIBRARY_LINKAGE dynamic)
|
||||||
|
|
||||||
|
if(VCPKG_TARGET_IS_LINUX)
|
||||||
|
message("${PORT} currently requires the following tools and libraries from the system package manager:\n autoreconf\n libudev\n\nThese can be installed on Ubuntu systems via apt-get install autoconf libudev-dev")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(VERSION 1.0.26)
|
||||||
|
vcpkg_from_github(
|
||||||
|
OUT_SOURCE_PATH SOURCE_PATH
|
||||||
|
REPO libusb/libusb
|
||||||
|
REF fcf0c710ef5911ae37fbbf1b39d48a89f6f14e8a # v1.0.26.11791 2023-03-12
|
||||||
|
SHA512 0aa6439f7988487adf2a3bff473fec80b5c722a47f117a60696d2aa25c87cc3f20fb6aaca7c66e49be25db6a35eb0bb5f71ed7b211d1b8ee064c5d7f1b985c73
|
||||||
|
HEAD_REF master
|
||||||
|
)
|
||||||
|
|
||||||
|
if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW)
|
||||||
|
|
||||||
|
if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic")
|
||||||
|
set(LIBUSB_PROJECT_TYPE dll)
|
||||||
|
else()
|
||||||
|
set(LIBUSB_PROJECT_TYPE static)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# The README.md file in the archive is a symlink to README
|
||||||
|
# which causes issues with the windows MSBUILD process
|
||||||
|
file(REMOVE "${SOURCE_PATH}/README.md")
|
||||||
|
|
||||||
|
vcpkg_msbuild_install(
|
||||||
|
SOURCE_PATH "${SOURCE_PATH}"
|
||||||
|
PROJECT_SUBPATH msvc/libusb_${LIBUSB_PROJECT_TYPE}.vcxproj
|
||||||
|
)
|
||||||
|
|
||||||
|
file(INSTALL "${SOURCE_PATH}/libusb/libusb.h" DESTINATION "${CURRENT_PACKAGES_DIR}/include/libusb-1.0")
|
||||||
|
set(prefix "")
|
||||||
|
set(exec_prefix [[${prefix}]])
|
||||||
|
set(libdir [[${prefix}/lib]])
|
||||||
|
set(includedir [[${prefix}/include]])
|
||||||
|
configure_file("${SOURCE_PATH}/libusb-1.0.pc.in" "${CURRENT_PACKAGES_DIR}/lib/pkgconfig/libusb-1.0.pc" @ONLY)
|
||||||
|
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/libusb-1.0.pc" " -lusb-1.0" " -llibusb-1.0")
|
||||||
|
if(NOT VCPKG_BUILD_TYPE)
|
||||||
|
set(includedir [[${prefix}/../include]])
|
||||||
|
configure_file("${SOURCE_PATH}/libusb-1.0.pc.in" "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libusb-1.0.pc" @ONLY)
|
||||||
|
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libusb-1.0.pc" " -lusb-1.0" " -llibusb-1.0")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
vcpkg_list(SET MAKE_OPTIONS)
|
||||||
|
vcpkg_list(SET LIBUSB_LINK_LIBRARIES)
|
||||||
|
if(VCPKG_TARGET_IS_EMSCRIPTEN)
|
||||||
|
vcpkg_list(APPEND MAKE_OPTIONS BUILD_TRIPLET --host=wasm32)
|
||||||
|
endif()
|
||||||
|
if("udev" IN_LIST FEATURES)
|
||||||
|
vcpkg_list(APPEND MAKE_OPTIONS "--enable-udev")
|
||||||
|
vcpkg_list(APPEND LIBUSB_LINK_LIBRARIES udev)
|
||||||
|
else()
|
||||||
|
vcpkg_list(APPEND MAKE_OPTIONS "--disable-udev")
|
||||||
|
endif()
|
||||||
|
vcpkg_configure_make(
|
||||||
|
SOURCE_PATH "${SOURCE_PATH}"
|
||||||
|
AUTOCONFIG
|
||||||
|
OPTIONS
|
||||||
|
${MAKE_OPTIONS}
|
||||||
|
"--enable-examples-build=no"
|
||||||
|
"--enable-tests-build=no"
|
||||||
|
)
|
||||||
|
vcpkg_install_make()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
vcpkg_fixup_pkgconfig()
|
||||||
|
|
||||||
|
file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
|
||||||
|
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/COPYING")
|
5
dependencies/vcpkg_overlay_ports_mac/libusb/usage
vendored
Normal file
5
dependencies/vcpkg_overlay_ports_mac/libusb/usage
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
libusb can be imported via CMake FindPkgConfig module:
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0)
|
||||||
|
|
||||||
|
target_link_libraries(main PRIVATE PkgConfig::libusb)
|
8
dependencies/vcpkg_overlay_ports_mac/libusb/vcpkg.json
vendored
Normal file
8
dependencies/vcpkg_overlay_ports_mac/libusb/vcpkg.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "libusb",
|
||||||
|
"version": "1.0.26.11791",
|
||||||
|
"port-version": 7,
|
||||||
|
"description": "a cross-platform library to access USB devices",
|
||||||
|
"homepage": "https://github.com/libusb/libusb",
|
||||||
|
"license": "LGPL-2.1-or-later"
|
||||||
|
}
|
6
dist/linux/appimage.sh
vendored
6
dist/linux/appimage.sh
vendored
|
@ -10,6 +10,8 @@ curl -sSfL https://github.com"$(curl https://github.com/probonopd/go-appimage/re
|
||||||
chmod a+x mkappimage.AppImage
|
chmod a+x mkappimage.AppImage
|
||||||
curl -sSfLO "https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh"
|
curl -sSfLO "https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh"
|
||||||
chmod a+x 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/x86_64-linux-gnu ]]; then
|
if [[ ! -e /usr/lib/x86_64-linux-gnu ]]; then
|
||||||
sed -i 's#lib\/x86_64-linux-gnu#lib64#g' linuxdeploy-plugin-gtk.sh
|
sed -i 's#lib\/x86_64-linux-gnu#lib64#g' linuxdeploy-plugin-gtk.sh
|
||||||
|
@ -33,12 +35,14 @@ chmod +x AppDir/usr/bin/Cemu
|
||||||
cp /usr/lib/x86_64-linux-gnu/{libsepol.so.1,libffi.so.7,libpcre.so.3,libGLU.so.1,libthai.so.0} AppDir/usr/lib
|
cp /usr/lib/x86_64-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 UPD_INFO="gh-releases-zsync|cemu-project|Cemu|ci|Cemu.AppImage.zsync"
|
||||||
|
export NO_STRIP=1
|
||||||
./linuxdeploy-x86_64.AppImage --appimage-extract-and-run \
|
./linuxdeploy-x86_64.AppImage --appimage-extract-and-run \
|
||||||
--appdir="${GITHUB_WORKSPACE}"/AppDir/ \
|
--appdir="${GITHUB_WORKSPACE}"/AppDir/ \
|
||||||
-d "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.desktop \
|
-d "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.desktop \
|
||||||
-i "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.png \
|
-i "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.png \
|
||||||
-e "${GITHUB_WORKSPACE}"/AppDir/usr/bin/Cemu \
|
-e "${GITHUB_WORKSPACE}"/AppDir/usr/bin/Cemu \
|
||||||
--plugin gtk
|
--plugin gtk \
|
||||||
|
--plugin checkrt
|
||||||
|
|
||||||
if ! GITVERSION="$(git rev-parse --short HEAD 2>/dev/null)"; then
|
if ! GITVERSION="$(git rev-parse --short HEAD 2>/dev/null)"; then
|
||||||
GITVERSION=experimental
|
GITVERSION=experimental
|
||||||
|
|
17
dist/network_services.xml
vendored
Normal file
17
dist/network_services.xml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<content>
|
||||||
|
<networkname>CustomExample</networkname>
|
||||||
|
<disablesslverification>0</disablesslverification>
|
||||||
|
<urls>
|
||||||
|
<act>https://account.nintendo.net</act>
|
||||||
|
<ecs>https://ecs.wup.shop.nintendo.net/ecs/services/ECommerceSOAP</ecs>
|
||||||
|
<nus>https://nus.wup.shop.nintendo.net/nus/services/NetUpdateSOAP</nus>
|
||||||
|
<ias>https://ias.wup.shop.nintendo.net/ias/services/IdentityAuthenticationSOAP</ias>
|
||||||
|
<ccsu>https://ccs.wup.shop.nintendo.net/ccs/download</ccsu>
|
||||||
|
<ccs>http://ccs.cdn.wup.shop.nintendo.net/ccs/download</ccs>
|
||||||
|
<idbe>https://idbe-wup.cdn.nintendo.net/icondata</idbe>
|
||||||
|
<boss>https://npts.app.nintendo.net/p01/tasksheet</boss>
|
||||||
|
<tagaya>https://tagaya.wup.shop.nintendo.net/tagaya/versionlist</tagaya>
|
||||||
|
<olv>https://discovery.olv.nintendo.net/v1/endpoint</olv>
|
||||||
|
</urls>
|
||||||
|
</content>
|
16
dist/windows/Cemu.manifest
vendored
Normal file
16
dist/windows/Cemu.manifest
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?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>
|
|
@ -1,2 +0,0 @@
|
||||||
"C:\PROGRAM FILES\MICROSOFT VISUAL STUDIO\2022\COMMUNITY\COMMON7\IDE\COMMONEXTENSIONS\MICROSOFT\CMAKE\CMake\bin\cmake.exe" -B build/
|
|
||||||
pause
|
|
|
@ -56,10 +56,17 @@ add_executable(CemuBin
|
||||||
mainLLE.cpp
|
mainLLE.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(MSVC AND MSVC_VERSION EQUAL 1940)
|
||||||
|
# workaround for an msvc issue on VS 17.10 where generated ILK files are too large
|
||||||
|
# see https://developercommunity.visualstudio.com/t/After-updating-to-VS-1710-the-size-of-/10665511
|
||||||
|
set_target_properties(CemuBin PROPERTIES LINK_FLAGS "/INCREMENTAL:NO")
|
||||||
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_sources(CemuBin PRIVATE
|
target_sources(CemuBin PRIVATE
|
||||||
resource/cemu.rc
|
resource/cemu.rc
|
||||||
)
|
../dist/windows/cemu.manifest
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set_property(TARGET CemuBin PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
set_property(TARGET CemuBin PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||||
|
@ -75,11 +82,13 @@ if (MACOS_BUNDLE)
|
||||||
set(MACOSX_BUNDLE_ICON_FILE "cemu.icns")
|
set(MACOSX_BUNDLE_ICON_FILE "cemu.icns")
|
||||||
set(MACOSX_BUNDLE_GUI_IDENTIFIER "info.cemu.Cemu")
|
set(MACOSX_BUNDLE_GUI_IDENTIFIER "info.cemu.Cemu")
|
||||||
set(MACOSX_BUNDLE_BUNDLE_NAME "Cemu")
|
set(MACOSX_BUNDLE_BUNDLE_NAME "Cemu")
|
||||||
set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${CMAKE_PROJECT_VERSION})
|
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}")
|
||||||
set(MACOSX_BUNDLE_BUNDLE_VERSION ${CMAKE_PROJECT_VERSION})
|
set(MACOSX_BUNDLE_BUNDLE_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}")
|
||||||
set(MACOSX_BUNDLE_COPYRIGHT "Copyright © 2022 Cemu Project")
|
set(MACOSX_BUNDLE_COPYRIGHT "Copyright © 2024 Cemu Project")
|
||||||
|
|
||||||
set(MACOSX_BUNDLE_CATEGORY "public.app-category.games")
|
set(MACOSX_BUNDLE_CATEGORY "public.app-category.games")
|
||||||
|
set(MACOSX_MINIMUM_SYSTEM_VERSION "12.0")
|
||||||
|
set(MACOSX_BUNDLE_TYPE_EXTENSION "wua")
|
||||||
|
|
||||||
set_target_properties(CemuBin PROPERTIES
|
set_target_properties(CemuBin PROPERTIES
|
||||||
MACOSX_BUNDLE true
|
MACOSX_BUNDLE true
|
||||||
|
@ -92,11 +101,18 @@ if (MACOS_BUNDLE)
|
||||||
COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory "${CMAKE_SOURCE_DIR}/bin/${folder}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/SharedSupport/${folder}")
|
COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory "${CMAKE_SOURCE_DIR}/bin/${folder}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/SharedSupport/${folder}")
|
||||||
endforeach(folder)
|
endforeach(folder)
|
||||||
|
|
||||||
add_custom_command (TARGET CemuBin POST_BUILD
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
COMMAND ${CMAKE_COMMAND} ARGS -E copy "/usr/local/lib/libMoltenVK.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib")
|
set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/debug/lib/libusb-1.0.0.dylib")
|
||||||
|
else()
|
||||||
|
set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib")
|
||||||
|
endif()
|
||||||
|
|
||||||
add_custom_command (TARGET CemuBin POST_BUILD
|
add_custom_command (TARGET CemuBin POST_BUILD
|
||||||
COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}")
|
COMMAND ${CMAKE_COMMAND} ARGS -E copy "/usr/local/lib/libMoltenVK.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib"
|
||||||
|
COMMAND ${CMAKE_COMMAND} ARGS -E copy "${LIBUSB_PATH}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib"
|
||||||
|
COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_SOURCE_DIR}/src/resource/update.sh" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/update.sh"
|
||||||
|
COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}"
|
||||||
|
COMMAND bash -c "install_name_tool -change ${LIBUSB_PATH} @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set_target_properties(CemuBin PROPERTIES
|
set_target_properties(CemuBin PROPERTIES
|
||||||
|
|
|
@ -16,22 +16,6 @@ enum class OnlineAccountError
|
||||||
kPasswordCacheEmpty,
|
kPasswordCacheEmpty,
|
||||||
kNoPrincipalId,
|
kNoPrincipalId,
|
||||||
};
|
};
|
||||||
template <>
|
|
||||||
struct fmt::formatter<OnlineAccountError> : formatter<string_view> {
|
|
||||||
template <typename FormatContext>
|
|
||||||
auto format(const OnlineAccountError v, FormatContext& ctx) {
|
|
||||||
switch (v)
|
|
||||||
{
|
|
||||||
case OnlineAccountError::kNoAccountId: return formatter<string_view>::format("AccountId missing (The account is not connected to a NNID)", ctx);
|
|
||||||
case OnlineAccountError::kNoPasswordCached: return formatter<string_view>::format("IsPasswordCacheEnabled is set to false (The remember password option on your Wii U must be enabled for this account before dumping it)", ctx);
|
|
||||||
case OnlineAccountError::kPasswordCacheEmpty: return formatter<string_view>::format("AccountPasswordCache is empty (The remember password option on your Wii U must be enabled for this account before dumping it)", ctx);
|
|
||||||
case OnlineAccountError::kNoPrincipalId: return formatter<string_view>::format("PrincipalId missing", ctx);
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
return formatter<string_view>::format("no error", ctx);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct OnlineValidator
|
struct OnlineValidator
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,6 +10,7 @@ add_library(CemuCafe
|
||||||
Filesystem/fscDeviceRedirect.cpp
|
Filesystem/fscDeviceRedirect.cpp
|
||||||
Filesystem/fscDeviceWua.cpp
|
Filesystem/fscDeviceWua.cpp
|
||||||
Filesystem/fscDeviceWud.cpp
|
Filesystem/fscDeviceWud.cpp
|
||||||
|
Filesystem/fscDeviceWuhb.cpp
|
||||||
Filesystem/fsc.h
|
Filesystem/fsc.h
|
||||||
Filesystem/FST/FST.cpp
|
Filesystem/FST/FST.cpp
|
||||||
Filesystem/FST/FST.h
|
Filesystem/FST/FST.h
|
||||||
|
@ -18,6 +19,9 @@ add_library(CemuCafe
|
||||||
Filesystem/FST/KeyCache.h
|
Filesystem/FST/KeyCache.h
|
||||||
Filesystem/WUD/wud.cpp
|
Filesystem/WUD/wud.cpp
|
||||||
Filesystem/WUD/wud.h
|
Filesystem/WUD/wud.h
|
||||||
|
Filesystem/WUHB/RomFSStructs.h
|
||||||
|
Filesystem/WUHB/WUHBReader.cpp
|
||||||
|
Filesystem/WUHB/WUHBReader.h
|
||||||
GamePatch.cpp
|
GamePatch.cpp
|
||||||
GamePatch.h
|
GamePatch.h
|
||||||
GameProfile/GameProfile.cpp
|
GameProfile/GameProfile.cpp
|
||||||
|
@ -40,6 +44,7 @@ add_library(CemuCafe
|
||||||
HW/Espresso/Debugger/DebugSymbolStorage.h
|
HW/Espresso/Debugger/DebugSymbolStorage.h
|
||||||
HW/Espresso/Debugger/GDBStub.h
|
HW/Espresso/Debugger/GDBStub.h
|
||||||
HW/Espresso/Debugger/GDBStub.cpp
|
HW/Espresso/Debugger/GDBStub.cpp
|
||||||
|
HW/Espresso/Debugger/GDBBreakpoints.cpp
|
||||||
HW/Espresso/Debugger/GDBBreakpoints.h
|
HW/Espresso/Debugger/GDBBreakpoints.h
|
||||||
HW/Espresso/EspressoISA.h
|
HW/Espresso/EspressoISA.h
|
||||||
HW/Espresso/Interpreter/PPCInterpreterALU.hpp
|
HW/Espresso/Interpreter/PPCInterpreterALU.hpp
|
||||||
|
@ -213,6 +218,8 @@ add_library(CemuCafe
|
||||||
HW/SI/SI.cpp
|
HW/SI/SI.cpp
|
||||||
HW/SI/si.h
|
HW/SI/si.h
|
||||||
HW/VI/VI.cpp
|
HW/VI/VI.cpp
|
||||||
|
IOSU/ccr_nfc/iosu_ccr_nfc.cpp
|
||||||
|
IOSU/ccr_nfc/iosu_ccr_nfc.h
|
||||||
IOSU/fsa/fsa_types.h
|
IOSU/fsa/fsa_types.h
|
||||||
IOSU/fsa/iosu_fsa.cpp
|
IOSU/fsa/iosu_fsa.cpp
|
||||||
IOSU/fsa/iosu_fsa.h
|
IOSU/fsa/iosu_fsa.h
|
||||||
|
@ -367,12 +374,24 @@ add_library(CemuCafe
|
||||||
OS/libs/gx2/GX2_Texture.h
|
OS/libs/gx2/GX2_Texture.h
|
||||||
OS/libs/gx2/GX2_TilingAperture.cpp
|
OS/libs/gx2/GX2_TilingAperture.cpp
|
||||||
OS/libs/h264_avc/H264Dec.cpp
|
OS/libs/h264_avc/H264Dec.cpp
|
||||||
|
OS/libs/h264_avc/H264DecBackendAVC.cpp
|
||||||
OS/libs/h264_avc/h264dec.h
|
OS/libs/h264_avc/h264dec.h
|
||||||
|
OS/libs/h264_avc/H264DecInternal.h
|
||||||
OS/libs/h264_avc/parser
|
OS/libs/h264_avc/parser
|
||||||
OS/libs/h264_avc/parser/H264Parser.cpp
|
OS/libs/h264_avc/parser/H264Parser.cpp
|
||||||
OS/libs/h264_avc/parser/H264Parser.h
|
OS/libs/h264_avc/parser/H264Parser.h
|
||||||
OS/libs/mic/mic.cpp
|
OS/libs/mic/mic.cpp
|
||||||
OS/libs/mic/mic.h
|
OS/libs/mic/mic.h
|
||||||
|
OS/libs/nfc/ndef.cpp
|
||||||
|
OS/libs/nfc/ndef.h
|
||||||
|
OS/libs/nfc/nfc.cpp
|
||||||
|
OS/libs/nfc/nfc.h
|
||||||
|
OS/libs/nfc/stream.cpp
|
||||||
|
OS/libs/nfc/stream.h
|
||||||
|
OS/libs/nfc/TagV0.cpp
|
||||||
|
OS/libs/nfc/TagV0.h
|
||||||
|
OS/libs/nfc/TLV.cpp
|
||||||
|
OS/libs/nfc/TLV.h
|
||||||
OS/libs/nlibcurl/nlibcurl.cpp
|
OS/libs/nlibcurl/nlibcurl.cpp
|
||||||
OS/libs/nlibcurl/nlibcurlDebug.hpp
|
OS/libs/nlibcurl/nlibcurlDebug.hpp
|
||||||
OS/libs/nlibcurl/nlibcurl.h
|
OS/libs/nlibcurl/nlibcurl.h
|
||||||
|
@ -403,6 +422,8 @@ add_library(CemuCafe
|
||||||
OS/libs/nn_ndm/nn_ndm.h
|
OS/libs/nn_ndm/nn_ndm.h
|
||||||
OS/libs/nn_spm/nn_spm.cpp
|
OS/libs/nn_spm/nn_spm.cpp
|
||||||
OS/libs/nn_spm/nn_spm.h
|
OS/libs/nn_spm/nn_spm.h
|
||||||
|
OS/libs/nn_sl/nn_sl.cpp
|
||||||
|
OS/libs/nn_sl/nn_sl.h
|
||||||
OS/libs/nn_nfp/AmiiboCrypto.h
|
OS/libs/nn_nfp/AmiiboCrypto.h
|
||||||
OS/libs/nn_nfp/nn_nfp.cpp
|
OS/libs/nn_nfp/nn_nfp.cpp
|
||||||
OS/libs/nn_nfp/nn_nfp.h
|
OS/libs/nn_nfp/nn_nfp.h
|
||||||
|
@ -434,10 +455,26 @@ add_library(CemuCafe
|
||||||
OS/libs/nn_uds/nn_uds.h
|
OS/libs/nn_uds/nn_uds.h
|
||||||
OS/libs/nsyshid/nsyshid.cpp
|
OS/libs/nsyshid/nsyshid.cpp
|
||||||
OS/libs/nsyshid/nsyshid.h
|
OS/libs/nsyshid/nsyshid.h
|
||||||
|
OS/libs/nsyshid/Backend.h
|
||||||
|
OS/libs/nsyshid/AttachDefaultBackends.cpp
|
||||||
|
OS/libs/nsyshid/Whitelist.cpp
|
||||||
|
OS/libs/nsyshid/Whitelist.h
|
||||||
|
OS/libs/nsyshid/BackendEmulated.cpp
|
||||||
|
OS/libs/nsyshid/BackendEmulated.h
|
||||||
|
OS/libs/nsyshid/BackendLibusb.cpp
|
||||||
|
OS/libs/nsyshid/BackendLibusb.h
|
||||||
|
OS/libs/nsyshid/Dimensions.cpp
|
||||||
|
OS/libs/nsyshid/Dimensions.h
|
||||||
|
OS/libs/nsyshid/Infinity.cpp
|
||||||
|
OS/libs/nsyshid/Infinity.h
|
||||||
|
OS/libs/nsyshid/Skylander.cpp
|
||||||
|
OS/libs/nsyshid/Skylander.h
|
||||||
OS/libs/nsyskbd/nsyskbd.cpp
|
OS/libs/nsyskbd/nsyskbd.cpp
|
||||||
OS/libs/nsyskbd/nsyskbd.h
|
OS/libs/nsyskbd/nsyskbd.h
|
||||||
OS/libs/nsysnet/nsysnet.cpp
|
OS/libs/nsysnet/nsysnet.cpp
|
||||||
OS/libs/nsysnet/nsysnet.h
|
OS/libs/nsysnet/nsysnet.h
|
||||||
|
OS/libs/ntag/ntag.cpp
|
||||||
|
OS/libs/ntag/ntag.h
|
||||||
OS/libs/padscore/padscore.cpp
|
OS/libs/padscore/padscore.cpp
|
||||||
OS/libs/padscore/padscore.h
|
OS/libs/padscore/padscore.h
|
||||||
OS/libs/proc_ui/proc_ui.cpp
|
OS/libs/proc_ui/proc_ui.cpp
|
||||||
|
@ -493,6 +530,12 @@ set_property(TARGET CemuCafe PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CON
|
||||||
|
|
||||||
target_include_directories(CemuCafe PUBLIC "../")
|
target_include_directories(CemuCafe PUBLIC "../")
|
||||||
|
|
||||||
|
if (glslang_VERSION VERSION_LESS "15.0.0")
|
||||||
|
set(glslang_target "glslang::SPIRV")
|
||||||
|
else()
|
||||||
|
set(glslang_target "glslang")
|
||||||
|
endif()
|
||||||
|
|
||||||
target_link_libraries(CemuCafe PRIVATE
|
target_link_libraries(CemuCafe PRIVATE
|
||||||
CemuAsm
|
CemuAsm
|
||||||
CemuAudio
|
CemuAudio
|
||||||
|
@ -508,7 +551,7 @@ target_link_libraries(CemuCafe PRIVATE
|
||||||
Boost::nowide
|
Boost::nowide
|
||||||
CURL::libcurl
|
CURL::libcurl
|
||||||
fmt::fmt
|
fmt::fmt
|
||||||
glslang::SPIRV
|
${glslang_target}
|
||||||
ih264d
|
ih264d
|
||||||
OpenSSL::Crypto
|
OpenSSL::Crypto
|
||||||
OpenSSL::SSL
|
OpenSSL::SSL
|
||||||
|
@ -524,6 +567,18 @@ if (ENABLE_WAYLAND)
|
||||||
target_link_libraries(CemuCafe PUBLIC Wayland::Client)
|
target_link_libraries(CemuCafe PUBLIC Wayland::Client)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (ENABLE_VCPKG)
|
||||||
|
if(WIN32)
|
||||||
|
set(PKG_CONFIG_EXECUTABLE "${VCPKG_INSTALLED_DIR}/x64-windows/tools/pkgconf/pkgconf.exe")
|
||||||
|
endif()
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0)
|
||||||
|
target_link_libraries(CemuCafe PRIVATE PkgConfig::libusb)
|
||||||
|
else ()
|
||||||
|
find_package(libusb MODULE REQUIRED)
|
||||||
|
target_link_libraries(CemuCafe PRIVATE libusb::libusb)
|
||||||
|
endif ()
|
||||||
|
|
||||||
if (ENABLE_WXWIDGETS)
|
if (ENABLE_WXWIDGETS)
|
||||||
target_link_libraries(CemuCafe PRIVATE wx::base wx::core)
|
target_link_libraries(CemuCafe PRIVATE wx::base wx::core)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "audio/IAudioAPI.h"
|
#include "audio/IAudioAPI.h"
|
||||||
#include "audio/IAudioInputAPI.h"
|
#include "audio/IAudioInputAPI.h"
|
||||||
#include "config/ActiveSettings.h"
|
#include "config/ActiveSettings.h"
|
||||||
|
#include "config/LaunchSettings.h"
|
||||||
#include "Cafe/TitleList/GameInfo.h"
|
#include "Cafe/TitleList/GameInfo.h"
|
||||||
#include "Cafe/GraphicPack/GraphicPack2.h"
|
#include "Cafe/GraphicPack/GraphicPack2.h"
|
||||||
#include "util/helpers/SystemException.h"
|
#include "util/helpers/SystemException.h"
|
||||||
|
@ -35,6 +36,7 @@
|
||||||
#include "Cafe/IOSU/legacy/iosu_boss.h"
|
#include "Cafe/IOSU/legacy/iosu_boss.h"
|
||||||
#include "Cafe/IOSU/legacy/iosu_nim.h"
|
#include "Cafe/IOSU/legacy/iosu_nim.h"
|
||||||
#include "Cafe/IOSU/PDM/iosu_pdm.h"
|
#include "Cafe/IOSU/PDM/iosu_pdm.h"
|
||||||
|
#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h"
|
||||||
|
|
||||||
// IOSU initializer functions
|
// IOSU initializer functions
|
||||||
#include "Cafe/IOSU/kernel/iosu_kernel.h"
|
#include "Cafe/IOSU/kernel/iosu_kernel.h"
|
||||||
|
@ -51,6 +53,8 @@
|
||||||
#include "Cafe/OS/libs/gx2/GX2.h"
|
#include "Cafe/OS/libs/gx2/GX2.h"
|
||||||
#include "Cafe/OS/libs/gx2/GX2_Misc.h"
|
#include "Cafe/OS/libs/gx2/GX2_Misc.h"
|
||||||
#include "Cafe/OS/libs/mic/mic.h"
|
#include "Cafe/OS/libs/mic/mic.h"
|
||||||
|
#include "Cafe/OS/libs/nfc/nfc.h"
|
||||||
|
#include "Cafe/OS/libs/ntag/ntag.h"
|
||||||
#include "Cafe/OS/libs/nn_aoc/nn_aoc.h"
|
#include "Cafe/OS/libs/nn_aoc/nn_aoc.h"
|
||||||
#include "Cafe/OS/libs/nn_pdm/nn_pdm.h"
|
#include "Cafe/OS/libs/nn_pdm/nn_pdm.h"
|
||||||
#include "Cafe/OS/libs/nn_cmpt/nn_cmpt.h"
|
#include "Cafe/OS/libs/nn_cmpt/nn_cmpt.h"
|
||||||
|
@ -167,7 +171,7 @@ void LoadMainExecutable()
|
||||||
{
|
{
|
||||||
// RPX
|
// RPX
|
||||||
RPLLoader_AddDependency(_pathToExecutable.c_str());
|
RPLLoader_AddDependency(_pathToExecutable.c_str());
|
||||||
applicationRPX = rpl_loadFromMem(rpxData, rpxSize, (char*)_pathToExecutable.c_str());
|
applicationRPX = RPLLoader_LoadFromMemory(rpxData, rpxSize, (char*)_pathToExecutable.c_str());
|
||||||
if (!applicationRPX)
|
if (!applicationRPX)
|
||||||
{
|
{
|
||||||
wxMessageBox(_("Failed to run this title because the executable is damaged"));
|
wxMessageBox(_("Failed to run this title because the executable is damaged"));
|
||||||
|
@ -253,14 +257,7 @@ void InfoLog_PrintActiveSettings()
|
||||||
if(!GetConfig().vk_accurate_barriers.GetValue())
|
if(!GetConfig().vk_accurate_barriers.GetValue())
|
||||||
cemuLog_log(LogType::Force, "Accurate barriers are disabled!");
|
cemuLog_log(LogType::Force, "Accurate barriers are disabled!");
|
||||||
}
|
}
|
||||||
cemuLog_log(LogType::Force, "Console language: {}", config.console_language);
|
cemuLog_log(LogType::Force, "Console language: {}", stdx::to_underlying(config.console_language.GetValue()));
|
||||||
}
|
|
||||||
|
|
||||||
void PPCCore_setupSPR(PPCInterpreter_t* hCPU, uint32 coreIndex)
|
|
||||||
{
|
|
||||||
hCPU->sprExtended.PVR = 0x70010001;
|
|
||||||
hCPU->spr.UPIR = coreIndex;
|
|
||||||
hCPU->sprExtended.msr |= MSR_FP; // enable floating point
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SharedDataEntry
|
struct SharedDataEntry
|
||||||
|
@ -402,7 +399,7 @@ void cemu_initForGame()
|
||||||
// replace any known function signatures with our HLE implementations and patch bugs in the games
|
// replace any known function signatures with our HLE implementations and patch bugs in the games
|
||||||
GamePatch_scan();
|
GamePatch_scan();
|
||||||
}
|
}
|
||||||
LatteGPUState.alwaysDisplayDRC = ActiveSettings::DisplayDRCEnabled();
|
LatteGPUState.isDRCPrimary = ActiveSettings::DisplayDRCEnabled();
|
||||||
InfoLog_PrintActiveSettings();
|
InfoLog_PrintActiveSettings();
|
||||||
Latte_Start();
|
Latte_Start();
|
||||||
// check for debugger entrypoint bp
|
// check for debugger entrypoint bp
|
||||||
|
@ -489,7 +486,7 @@ namespace CafeSystem
|
||||||
#if BOOST_OS_WINDOWS
|
#if BOOST_OS_WINDOWS
|
||||||
std::string GetWindowsNamedVersion(uint32& buildNumber)
|
std::string GetWindowsNamedVersion(uint32& buildNumber)
|
||||||
{
|
{
|
||||||
static char productName[256];
|
char productName[256];
|
||||||
HKEY hKey;
|
HKEY hKey;
|
||||||
DWORD dwType = REG_SZ;
|
DWORD dwType = REG_SZ;
|
||||||
DWORD dwSize = sizeof(productName);
|
DWORD dwSize = sizeof(productName);
|
||||||
|
@ -535,6 +532,16 @@ namespace CafeSystem
|
||||||
cemuLog_log(LogType::Force, "Platform: {}", platform);
|
cemuLog_log(LogType::Force, "Platform: {}", platform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::vector<IOSUModule*> s_iosuModules =
|
||||||
|
{
|
||||||
|
// entries in this list are ordered by initialization order. Shutdown in reverse order
|
||||||
|
iosu::kernel::GetModule(),
|
||||||
|
iosu::acp::GetModule(),
|
||||||
|
iosu::fpd::GetModule(),
|
||||||
|
iosu::pdm::GetModule(),
|
||||||
|
iosu::ccr_nfc::GetModule(),
|
||||||
|
};
|
||||||
|
|
||||||
// initialize all subsystems which are persistent and don't depend on a game running
|
// initialize all subsystems which are persistent and don't depend on a game running
|
||||||
void Initialize()
|
void Initialize()
|
||||||
{
|
{
|
||||||
|
@ -559,20 +566,20 @@ namespace CafeSystem
|
||||||
// allocate memory for all SysAllocators
|
// allocate memory for all SysAllocators
|
||||||
// must happen before COS module init, but also before iosu::kernel::Initialize()
|
// must happen before COS module init, but also before iosu::kernel::Initialize()
|
||||||
SysAllocatorContainer::GetInstance().Initialize();
|
SysAllocatorContainer::GetInstance().Initialize();
|
||||||
// init IOSU
|
// init IOSU modules
|
||||||
|
for(auto& module : s_iosuModules)
|
||||||
|
module->SystemLaunch();
|
||||||
|
// init IOSU (deprecated manual init)
|
||||||
iosuCrypto_init();
|
iosuCrypto_init();
|
||||||
iosu::kernel::Initialize();
|
|
||||||
iosu::fsa::Initialize();
|
iosu::fsa::Initialize();
|
||||||
iosuIoctl_init();
|
iosuIoctl_init();
|
||||||
iosuAct_init_depr();
|
iosuAct_init_depr();
|
||||||
iosu::act::Initialize();
|
iosu::act::Initialize();
|
||||||
iosu::fpd::Initialize();
|
|
||||||
iosu::iosuMcp_init();
|
iosu::iosuMcp_init();
|
||||||
iosu::mcp::Init();
|
iosu::mcp::Init();
|
||||||
iosu::iosuAcp_init();
|
iosu::iosuAcp_init();
|
||||||
iosu::boss_init();
|
iosu::boss_init();
|
||||||
iosu::nim::Initialize();
|
iosu::nim::Initialize();
|
||||||
iosu::pdm::Initialize();
|
|
||||||
iosu::odm::Initialize();
|
iosu::odm::Initialize();
|
||||||
// init Cafe OS
|
// init Cafe OS
|
||||||
avm::Initialize();
|
avm::Initialize();
|
||||||
|
@ -587,6 +594,8 @@ namespace CafeSystem
|
||||||
H264::Initialize();
|
H264::Initialize();
|
||||||
snd_core::Initialize();
|
snd_core::Initialize();
|
||||||
mic::Initialize();
|
mic::Initialize();
|
||||||
|
nfc::Initialize();
|
||||||
|
ntag::Initialize();
|
||||||
// init hardware register interfaces
|
// init hardware register interfaces
|
||||||
HW_SI::Initialize();
|
HW_SI::Initialize();
|
||||||
}
|
}
|
||||||
|
@ -602,11 +611,14 @@ namespace CafeSystem
|
||||||
// if a title is running, shut it down
|
// if a title is running, shut it down
|
||||||
if (sSystemRunning)
|
if (sSystemRunning)
|
||||||
ShutdownTitle();
|
ShutdownTitle();
|
||||||
// shutdown persistent subsystems
|
// shutdown persistent subsystems (deprecated manual shutdown)
|
||||||
iosu::odm::Shutdown();
|
iosu::odm::Shutdown();
|
||||||
iosu::act::Stop();
|
iosu::act::Stop();
|
||||||
iosu::mcp::Shutdown();
|
iosu::mcp::Shutdown();
|
||||||
iosu::fsa::Shutdown();
|
iosu::fsa::Shutdown();
|
||||||
|
// shutdown IOSU modules
|
||||||
|
for(auto it = s_iosuModules.rbegin(); it != s_iosuModules.rend(); ++it)
|
||||||
|
(*it)->SystemExit();
|
||||||
s_initialized = false;
|
s_initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,40 +640,40 @@ namespace CafeSystem
|
||||||
fsc_unmount("/cemuBossStorage/", FSC_PRIORITY_BASE);
|
fsc_unmount("/cemuBossStorage/", FSC_PRIORITY_BASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
STATUS_CODE LoadAndMountForegroundTitle(TitleId titleId)
|
PREPARE_STATUS_CODE LoadAndMountForegroundTitle(TitleId titleId)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Mounting title {:016x}", (uint64)titleId);
|
cemuLog_log(LogType::Force, "Mounting title {:016x}", (uint64)titleId);
|
||||||
sGameInfo_ForegroundTitle = CafeTitleList::GetGameInfo(titleId);
|
sGameInfo_ForegroundTitle = CafeTitleList::GetGameInfo(titleId);
|
||||||
if (!sGameInfo_ForegroundTitle.IsValid())
|
if (!sGameInfo_ForegroundTitle.IsValid())
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Mounting failed: Game meta information is either missing, inaccessible or not valid (missing or invalid .xml files in code and meta folder)");
|
cemuLog_log(LogType::Force, "Mounting failed: Game meta information is either missing, inaccessible or not valid (missing or invalid .xml files in code and meta folder)");
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
}
|
}
|
||||||
// check base
|
// check base
|
||||||
TitleInfo& titleBase = sGameInfo_ForegroundTitle.GetBase();
|
TitleInfo& titleBase = sGameInfo_ForegroundTitle.GetBase();
|
||||||
if (!titleBase.IsValid())
|
if (!titleBase.IsValid())
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
if(!titleBase.ParseXmlInfo())
|
if(!titleBase.ParseXmlInfo())
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
cemuLog_log(LogType::Force, "Base: {}", titleBase.GetPrintPath());
|
cemuLog_log(LogType::Force, "Base: {}", titleBase.GetPrintPath());
|
||||||
// mount base
|
// mount base
|
||||||
if (!titleBase.Mount("/vol/content", "content", FSC_PRIORITY_BASE) || !titleBase.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_BASE))
|
if (!titleBase.Mount("/vol/content", "content", FSC_PRIORITY_BASE) || !titleBase.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_BASE))
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Mounting failed");
|
cemuLog_log(LogType::Force, "Mounting failed");
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
}
|
}
|
||||||
// check update
|
// check update
|
||||||
TitleInfo& titleUpdate = sGameInfo_ForegroundTitle.GetUpdate();
|
TitleInfo& titleUpdate = sGameInfo_ForegroundTitle.GetUpdate();
|
||||||
if (titleUpdate.IsValid())
|
if (titleUpdate.IsValid())
|
||||||
{
|
{
|
||||||
if (!titleUpdate.ParseXmlInfo())
|
if (!titleUpdate.ParseXmlInfo())
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
cemuLog_log(LogType::Force, "Update: {}", titleUpdate.GetPrintPath());
|
cemuLog_log(LogType::Force, "Update: {}", titleUpdate.GetPrintPath());
|
||||||
// mount update
|
// mount update
|
||||||
if (!titleUpdate.Mount("/vol/content", "content", FSC_PRIORITY_PATCH) || !titleUpdate.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_PATCH))
|
if (!titleUpdate.Mount("/vol/content", "content", FSC_PRIORITY_PATCH) || !titleUpdate.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_PATCH))
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Mounting failed");
|
cemuLog_log(LogType::Force, "Mounting failed");
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -673,20 +685,20 @@ namespace CafeSystem
|
||||||
// todo - support for multi-title AOC
|
// todo - support for multi-title AOC
|
||||||
TitleInfo& titleAOC = aocList[0];
|
TitleInfo& titleAOC = aocList[0];
|
||||||
if (!titleAOC.ParseXmlInfo())
|
if (!titleAOC.ParseXmlInfo())
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
cemu_assert_debug(titleAOC.IsValid());
|
cemu_assert_debug(titleAOC.IsValid());
|
||||||
cemuLog_log(LogType::Force, "DLC: {}", titleAOC.GetPrintPath());
|
cemuLog_log(LogType::Force, "DLC: {}", titleAOC.GetPrintPath());
|
||||||
// mount AOC
|
// mount AOC
|
||||||
if (!titleAOC.Mount(fmt::format("/vol/aoc{:016x}", titleAOC.GetAppTitleId()), "content", FSC_PRIORITY_PATCH))
|
if (!titleAOC.Mount(fmt::format("/vol/aoc{:016x}", titleAOC.GetAppTitleId()), "content", FSC_PRIORITY_PATCH))
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Mounting failed");
|
cemuLog_log(LogType::Force, "Mounting failed");
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
cemuLog_log(LogType::Force, "DLC: Not present");
|
cemuLog_log(LogType::Force, "DLC: Not present");
|
||||||
sForegroundTitleId = titleId;
|
sForegroundTitleId = titleId;
|
||||||
return STATUS_CODE::SUCCESS;
|
return PREPARE_STATUS_CODE::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnmountForegroundTitle()
|
void UnmountForegroundTitle()
|
||||||
|
@ -714,7 +726,7 @@ namespace CafeSystem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
STATUS_CODE SetupExecutable()
|
PREPARE_STATUS_CODE SetupExecutable()
|
||||||
{
|
{
|
||||||
// set rpx path from cos.xml if available
|
// set rpx path from cos.xml if available
|
||||||
_pathToBaseExecutable = _pathToExecutable;
|
_pathToBaseExecutable = _pathToExecutable;
|
||||||
|
@ -746,8 +758,7 @@ namespace CafeSystem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LoadMainExecutable();
|
LoadMainExecutable();
|
||||||
gameProfile_load();
|
return PREPARE_STATUS_CODE::SUCCESS;
|
||||||
return STATUS_CODE::SUCCESS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetupMemorySpace()
|
void SetupMemorySpace()
|
||||||
|
@ -761,7 +772,7 @@ namespace CafeSystem
|
||||||
memory_unmapForCurrentTitle();
|
memory_unmapForCurrentTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
STATUS_CODE PrepareForegroundTitle(TitleId titleId)
|
PREPARE_STATUS_CODE PrepareForegroundTitle(TitleId titleId)
|
||||||
{
|
{
|
||||||
CafeTitleList::WaitForMandatoryScan();
|
CafeTitleList::WaitForMandatoryScan();
|
||||||
sLaunchModeIsStandalone = false;
|
sLaunchModeIsStandalone = false;
|
||||||
|
@ -772,20 +783,21 @@ namespace CafeSystem
|
||||||
// mount mlc storage
|
// mount mlc storage
|
||||||
MountBaseDirectories();
|
MountBaseDirectories();
|
||||||
// mount title folders
|
// mount title folders
|
||||||
STATUS_CODE r = LoadAndMountForegroundTitle(titleId);
|
PREPARE_STATUS_CODE r = LoadAndMountForegroundTitle(titleId);
|
||||||
if (r != STATUS_CODE::SUCCESS)
|
if (r != PREPARE_STATUS_CODE::SUCCESS)
|
||||||
return r;
|
return r;
|
||||||
|
gameProfile_load();
|
||||||
// setup memory space and PPC recompiler
|
// setup memory space and PPC recompiler
|
||||||
SetupMemorySpace();
|
SetupMemorySpace();
|
||||||
PPCRecompiler_init();
|
PPCRecompiler_init();
|
||||||
r = SetupExecutable(); // load RPX
|
r = SetupExecutable(); // load RPX
|
||||||
if (r != STATUS_CODE::SUCCESS)
|
if (r != PREPARE_STATUS_CODE::SUCCESS)
|
||||||
return r;
|
return r;
|
||||||
InitVirtualMlcStorage();
|
InitVirtualMlcStorage();
|
||||||
return STATUS_CODE::SUCCESS;
|
return PREPARE_STATUS_CODE::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path)
|
PREPARE_STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path)
|
||||||
{
|
{
|
||||||
sLaunchModeIsStandalone = true;
|
sLaunchModeIsStandalone = true;
|
||||||
cemuLog_log(LogType::Force, "Launching executable in standalone mode due to incorrect layout or missing meta files");
|
cemuLog_log(LogType::Force, "Launching executable in standalone mode due to incorrect layout or missing meta files");
|
||||||
|
@ -803,7 +815,7 @@ namespace CafeSystem
|
||||||
if (!r)
|
if (!r)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Failed to mount {}", _pathToUtf8(contentPath));
|
cemuLog_log(LogType::Force, "Failed to mount {}", _pathToUtf8(contentPath));
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -815,7 +827,7 @@ namespace CafeSystem
|
||||||
// since a lot of systems (including save folder location) rely on a TitleId, we derive a placeholder id from the executable hash
|
// since a lot of systems (including save folder location) rely on a TitleId, we derive a placeholder id from the executable hash
|
||||||
auto execData = fsc_extractFile(_pathToExecutable.c_str());
|
auto execData = fsc_extractFile(_pathToExecutable.c_str());
|
||||||
if (!execData)
|
if (!execData)
|
||||||
return STATUS_CODE::INVALID_RPX;
|
return PREPARE_STATUS_CODE::INVALID_RPX;
|
||||||
uint32 h = generateHashFromRawRPXData(execData->data(), execData->size());
|
uint32 h = generateHashFromRawRPXData(execData->data(), execData->size());
|
||||||
sForegroundTitleId = 0xFFFFFFFF00000000ULL | (uint64)h;
|
sForegroundTitleId = 0xFFFFFFFF00000000ULL | (uint64)h;
|
||||||
cemuLog_log(LogType::Force, "Generated placeholder TitleId: {:016x}", sForegroundTitleId);
|
cemuLog_log(LogType::Force, "Generated placeholder TitleId: {:016x}", sForegroundTitleId);
|
||||||
|
@ -825,19 +837,19 @@ namespace CafeSystem
|
||||||
// load executable
|
// load executable
|
||||||
SetupExecutable();
|
SetupExecutable();
|
||||||
InitVirtualMlcStorage();
|
InitVirtualMlcStorage();
|
||||||
return STATUS_CODE::SUCCESS;
|
return PREPARE_STATUS_CODE::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _LaunchTitleThread()
|
void _LaunchTitleThread()
|
||||||
{
|
{
|
||||||
// init
|
for(auto& module : s_iosuModules)
|
||||||
|
module->TitleStart();
|
||||||
cemu_initForGame();
|
cemu_initForGame();
|
||||||
// enter scheduler
|
// enter scheduler
|
||||||
if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler)
|
if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler && !LaunchSettings::ForceInterpreter())
|
||||||
coreinit::OSSchedulerBegin(3);
|
coreinit::OSSchedulerBegin(3);
|
||||||
else
|
else
|
||||||
coreinit::OSSchedulerBegin(1);
|
coreinit::OSSchedulerBegin(1);
|
||||||
iosu::pdm::StartTrackingTime(GetForegroundTitleId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LaunchForegroundTitle()
|
void LaunchForegroundTitle()
|
||||||
|
@ -912,6 +924,27 @@ namespace CafeSystem
|
||||||
return sGameInfo_ForegroundTitle.GetBase().GetArgStr();
|
return sGameInfo_ForegroundTitle.GetBase().GetArgStr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group)
|
||||||
|
{
|
||||||
|
if (sLaunchModeIsStandalone)
|
||||||
|
return CosCapabilityBits::All;
|
||||||
|
auto& update = sGameInfo_ForegroundTitle.GetUpdate();
|
||||||
|
if (update.IsValid())
|
||||||
|
{
|
||||||
|
ParsedCosXml* cosXml = update.GetCosInfo();
|
||||||
|
if (cosXml)
|
||||||
|
return cosXml->GetCapabilityBits(group);
|
||||||
|
}
|
||||||
|
auto& base = sGameInfo_ForegroundTitle.GetBase();
|
||||||
|
if(base.IsValid())
|
||||||
|
{
|
||||||
|
ParsedCosXml* cosXml = base.GetCosInfo();
|
||||||
|
if (cosXml)
|
||||||
|
return cosXml->GetCapabilityBits(group);
|
||||||
|
}
|
||||||
|
return CosCapabilityBits::All;
|
||||||
|
}
|
||||||
|
|
||||||
// when switching titles custom parameters can be passed, returns true if override args are used
|
// when switching titles custom parameters can be passed, returns true if override args are used
|
||||||
bool GetOverrideArgStr(std::vector<std::string>& args)
|
bool GetOverrideArgStr(std::vector<std::string>& args)
|
||||||
{
|
{
|
||||||
|
@ -965,8 +998,8 @@ namespace CafeSystem
|
||||||
nn::save::ResetToDefaultState();
|
nn::save::ResetToDefaultState();
|
||||||
coreinit::__OSDeleteAllActivePPCThreads();
|
coreinit::__OSDeleteAllActivePPCThreads();
|
||||||
RPLLoader_ResetState();
|
RPLLoader_ResetState();
|
||||||
// stop time tracking
|
for(auto it = s_iosuModules.rbegin(); it != s_iosuModules.rend(); ++it)
|
||||||
iosu::pdm::Stop();
|
(*it)->TitleStop();
|
||||||
// reset Cemu subsystems
|
// reset Cemu subsystems
|
||||||
PPCRecompiler_Shutdown();
|
PPCRecompiler_Shutdown();
|
||||||
GraphicPack2::Reset();
|
GraphicPack2::Reset();
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
#include "Cafe/TitleList/TitleId.h"
|
#include "Cafe/TitleList/TitleId.h"
|
||||||
#include "config/CemuConfig.h"
|
#include "config/CemuConfig.h"
|
||||||
|
|
||||||
|
enum class CosCapabilityBits : uint64;
|
||||||
|
enum class CosCapabilityGroup : uint32;
|
||||||
|
|
||||||
namespace CafeSystem
|
namespace CafeSystem
|
||||||
{
|
{
|
||||||
class SystemImplementation
|
class SystemImplementation
|
||||||
|
@ -12,20 +15,19 @@ namespace CafeSystem
|
||||||
virtual void CafeRecreateCanvas() = 0;
|
virtual void CafeRecreateCanvas() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class STATUS_CODE
|
enum class PREPARE_STATUS_CODE
|
||||||
{
|
{
|
||||||
SUCCESS,
|
SUCCESS,
|
||||||
INVALID_RPX,
|
INVALID_RPX,
|
||||||
UNABLE_TO_MOUNT, // failed to mount through TitleInfo (most likely caused by an invalid or outdated path)
|
UNABLE_TO_MOUNT, // failed to mount through TitleInfo (most likely caused by an invalid or outdated path)
|
||||||
//BAD_META_DATA, - the title list only stores titles with valid meta, so this error code is impossible
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void Initialize();
|
void Initialize();
|
||||||
void SetImplementation(SystemImplementation* impl);
|
void SetImplementation(SystemImplementation* impl);
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
STATUS_CODE PrepareForegroundTitle(TitleId titleId);
|
PREPARE_STATUS_CODE PrepareForegroundTitle(TitleId titleId);
|
||||||
STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path);
|
PREPARE_STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path);
|
||||||
void LaunchForegroundTitle();
|
void LaunchForegroundTitle();
|
||||||
bool IsTitleRunning();
|
bool IsTitleRunning();
|
||||||
|
|
||||||
|
@ -41,6 +43,7 @@ namespace CafeSystem
|
||||||
std::string GetForegroundTitleName();
|
std::string GetForegroundTitleName();
|
||||||
std::string GetForegroundTitleArgStr();
|
std::string GetForegroundTitleArgStr();
|
||||||
uint32 GetForegroundTitleOlvAccesskey();
|
uint32 GetForegroundTitleOlvAccesskey();
|
||||||
|
CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group);
|
||||||
|
|
||||||
void ShutdownTitle();
|
void ShutdownTitle();
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
#include "Cemu/ncrypto/ncrypto.h"
|
#include "Cemu/ncrypto/ncrypto.h"
|
||||||
#include "Cafe/Filesystem/WUD/wud.h"
|
#include "Cafe/Filesystem/WUD/wud.h"
|
||||||
#include "util/crypto/aes128.h"
|
#include "util/crypto/aes128.h"
|
||||||
#include "openssl/evp.h" /* EVP_Digest */
|
#include "openssl/sha.h" /* SHA1 / SHA256 */
|
||||||
#include "openssl/sha.h" /* SHA1 / SHA256_DIGEST_LENGTH */
|
|
||||||
#include "fstUtil.h"
|
#include "fstUtil.h"
|
||||||
|
|
||||||
#include "FST.h"
|
#include "FST.h"
|
||||||
|
@ -12,6 +11,8 @@
|
||||||
|
|
||||||
#include "boost/range/adaptor/reversed.hpp"
|
#include "boost/range/adaptor/reversed.hpp"
|
||||||
|
|
||||||
|
#define SET_FST_ERROR(__code) if (errorCodeOut) *errorCodeOut = ErrorCode::__code
|
||||||
|
|
||||||
class FSTDataSource
|
class FSTDataSource
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -139,7 +140,7 @@ struct DiscPartitionTableHeader
|
||||||
static constexpr uint32 MAGIC_VALUE = 0xCCA6E67B;
|
static constexpr uint32 MAGIC_VALUE = 0xCCA6E67B;
|
||||||
|
|
||||||
/* +0x00 */ uint32be magic;
|
/* +0x00 */ uint32be magic;
|
||||||
/* +0x04 */ uint32be sectorSize; // must be 0x8000?
|
/* +0x04 */ uint32be blockSize; // must be 0x8000?
|
||||||
/* +0x08 */ uint8 partitionTableHash[20]; // hash of the data range at +0x800 to end of sector (0x8000)
|
/* +0x08 */ uint8 partitionTableHash[20]; // hash of the data range at +0x800 to end of sector (0x8000)
|
||||||
/* +0x1C */ uint32be numPartitions;
|
/* +0x1C */ uint32be numPartitions;
|
||||||
};
|
};
|
||||||
|
@ -162,10 +163,10 @@ struct DiscPartitionHeader
|
||||||
static constexpr uint32 MAGIC_VALUE = 0xCC93A4F5;
|
static constexpr uint32 MAGIC_VALUE = 0xCC93A4F5;
|
||||||
|
|
||||||
/* +0x00 */ uint32be magic;
|
/* +0x00 */ uint32be magic;
|
||||||
/* +0x04 */ uint32be sectorSize; // must match DISC_SECTOR_SIZE
|
/* +0x04 */ uint32be sectorSize; // must match DISC_SECTOR_SIZE for hashed blocks
|
||||||
|
|
||||||
/* +0x08 */ uint32be ukn008;
|
/* +0x08 */ uint32be ukn008;
|
||||||
/* +0x0C */ uint32be ukn00C;
|
/* +0x0C */ uint32be ukn00C; // h3 array size?
|
||||||
/* +0x10 */ uint32be h3HashNum;
|
/* +0x10 */ uint32be h3HashNum;
|
||||||
/* +0x14 */ uint32be fstSize; // in bytes
|
/* +0x14 */ uint32be fstSize; // in bytes
|
||||||
/* +0x18 */ uint32be fstSector; // relative to partition start
|
/* +0x18 */ uint32be fstSector; // relative to partition start
|
||||||
|
@ -176,13 +177,15 @@ struct DiscPartitionHeader
|
||||||
/* +0x24 */ uint8 fstHashType;
|
/* +0x24 */ uint8 fstHashType;
|
||||||
/* +0x25 */ uint8 fstEncryptionType; // purpose of this isn't really understood. Maybe it controls which key is being used? (1 -> disc key, 2 -> partition key)
|
/* +0x25 */ uint8 fstEncryptionType; // purpose of this isn't really understood. Maybe it controls which key is being used? (1 -> disc key, 2 -> partition key)
|
||||||
|
|
||||||
/* +0x26 */ uint8 versionA;
|
/* +0x26 */ uint8be versionA;
|
||||||
/* +0x27 */ uint8 ukn027; // also a version field?
|
/* +0x27 */ uint8be ukn027; // also a version field?
|
||||||
|
|
||||||
// there is an array at +0x40 ? Related to H3 list. Also related to value at +0x0C and h3HashNum
|
// there is an array at +0x40 ? Related to H3 list. Also related to value at +0x0C and h3HashNum
|
||||||
|
/* +0x28 */ uint8be _uknOrPadding028[0x18];
|
||||||
|
/* +0x40 */ uint8be h3HashArray[32]; // dynamic size. Only present if fstHashType != 0
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(DiscPartitionHeader) == 0x28);
|
static_assert(sizeof(DiscPartitionHeader) == 0x40+0x20);
|
||||||
|
|
||||||
bool FSTVolume::FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey)
|
bool FSTVolume::FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey)
|
||||||
{
|
{
|
||||||
|
@ -215,23 +218,22 @@ bool FSTVolume::FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey)
|
||||||
|
|
||||||
// open WUD image using key cache
|
// open WUD image using key cache
|
||||||
// if no matching key is found then keyFound will return false
|
// if no matching key is found then keyFound will return false
|
||||||
FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, bool* keyFound)
|
FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, ErrorCode* errorCodeOut)
|
||||||
{
|
{
|
||||||
|
SET_FST_ERROR(UNKNOWN_ERROR);
|
||||||
KeyCache_Prepare();
|
KeyCache_Prepare();
|
||||||
NCrypto::AesKey discTitleKey;
|
NCrypto::AesKey discTitleKey;
|
||||||
if (!FindDiscKey(path, discTitleKey))
|
if (!FindDiscKey(path, discTitleKey))
|
||||||
{
|
{
|
||||||
if(keyFound)
|
SET_FST_ERROR(DISC_KEY_MISSING);
|
||||||
*keyFound = false;
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if(keyFound)
|
return OpenFromDiscImage(path, discTitleKey, errorCodeOut);
|
||||||
*keyFound = true;
|
|
||||||
return OpenFromDiscImage(path, discTitleKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// open WUD image
|
// open WUD image
|
||||||
FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey)
|
FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey, ErrorCode* errorCodeOut)
|
||||||
|
|
||||||
{
|
{
|
||||||
// WUD images support multiple partitions, each with their own key and FST
|
// WUD images support multiple partitions, each with their own key and FST
|
||||||
// the process for loading game data FSTVolume from a WUD image is as follows:
|
// the process for loading game data FSTVolume from a WUD image is as follows:
|
||||||
|
@ -240,6 +242,7 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d
|
||||||
// 3) find main GM partition
|
// 3) find main GM partition
|
||||||
// 4) use SI information to get titleKey for GM partition
|
// 4) use SI information to get titleKey for GM partition
|
||||||
// 5) Load FST for GM
|
// 5) Load FST for GM
|
||||||
|
SET_FST_ERROR(UNKNOWN_ERROR);
|
||||||
std::unique_ptr<FSTDataSourceWUD> dataSource(FSTDataSourceWUD::Open(path));
|
std::unique_ptr<FSTDataSourceWUD> dataSource(FSTDataSourceWUD::Open(path));
|
||||||
if (!dataSource)
|
if (!dataSource)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -267,7 +270,7 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d
|
||||||
cemuLog_log(LogType::Force, "Disc image rejected because decryption failed");
|
cemuLog_log(LogType::Force, "Disc image rejected because decryption failed");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (partitionHeader->sectorSize != DISC_SECTOR_SIZE)
|
if (partitionHeader->blockSize != DISC_SECTOR_SIZE)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Disc image rejected because partition sector size is invalid");
|
cemuLog_log(LogType::Force, "Disc image rejected because partition sector size is invalid");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -334,6 +337,9 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d
|
||||||
cemu_assert_debug(partitionHeaderSI.fstEncryptionType == 1);
|
cemu_assert_debug(partitionHeaderSI.fstEncryptionType == 1);
|
||||||
// todo - check other fields?
|
// todo - check other fields?
|
||||||
|
|
||||||
|
if(partitionHeaderSI.fstHashType == 0 && partitionHeaderSI.h3HashNum != 0)
|
||||||
|
cemuLog_log(LogType::Force, "FST: Partition uses unhashed blocks but stores a non-zero amount of H3 hashes");
|
||||||
|
|
||||||
// GM partition
|
// GM partition
|
||||||
DiscPartitionHeader partitionHeaderGM{};
|
DiscPartitionHeader partitionHeaderGM{};
|
||||||
if (!readPartitionHeader(partitionHeaderGM, gmPartitionIndex))
|
if (!readPartitionHeader(partitionHeaderGM, gmPartitionIndex))
|
||||||
|
@ -347,9 +353,10 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d
|
||||||
// if decryption is necessary
|
// if decryption is necessary
|
||||||
// load SI FST
|
// load SI FST
|
||||||
dataSource->SetBaseOffset((uint64)partitionArray[siPartitionIndex].partitionAddress * DISC_SECTOR_SIZE);
|
dataSource->SetBaseOffset((uint64)partitionArray[siPartitionIndex].partitionAddress * DISC_SECTOR_SIZE);
|
||||||
auto siFST = OpenFST(dataSource.get(), (uint64)partitionHeaderSI.fstSector * DISC_SECTOR_SIZE, partitionHeaderSI.fstSize, &discTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderSI.fstHashType));
|
auto siFST = OpenFST(dataSource.get(), (uint64)partitionHeaderSI.fstSector * DISC_SECTOR_SIZE, partitionHeaderSI.fstSize, &discTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderSI.fstHashType), nullptr);
|
||||||
if (!siFST)
|
if (!siFST)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
cemu_assert_debug(!(siFST->HashIsDisabled() && partitionHeaderSI.h3HashNum != 0)); // if hash is disabled, no H3 data may be present
|
||||||
// load ticket file for partition that we want to decrypt
|
// load ticket file for partition that we want to decrypt
|
||||||
NCrypto::ETicketParser ticketParser;
|
NCrypto::ETicketParser ticketParser;
|
||||||
std::vector<uint8> ticketData = siFST->ExtractFile(fmt::format("{:02x}/title.tik", gmPartitionIndex));
|
std::vector<uint8> ticketData = siFST->ExtractFile(fmt::format("{:02x}/title.tik", gmPartitionIndex));
|
||||||
|
@ -358,18 +365,38 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d
|
||||||
cemuLog_log(LogType::Force, "Disc image ticket file is invalid");
|
cemuLog_log(LogType::Force, "Disc image ticket file is invalid");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
#if 0
|
||||||
|
// each SI partition seems to contain a title.tmd that we could parse and which should have information about the associated GM partition
|
||||||
|
// but the console seems to ignore this file for disc images, at least when mounting, so we shouldn't rely on it either
|
||||||
|
std::vector<uint8> tmdData = siFST->ExtractFile(fmt::format("{:02x}/title.tmd", gmPartitionIndex));
|
||||||
|
if (tmdData.empty())
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Disc image TMD file is missing");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// parse TMD
|
||||||
|
NCrypto::TMDParser tmdParser;
|
||||||
|
if (!tmdParser.parse(tmdData.data(), tmdData.size()))
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Disc image TMD file is invalid");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
delete siFST;
|
delete siFST;
|
||||||
|
|
||||||
NCrypto::AesKey gmTitleKey;
|
NCrypto::AesKey gmTitleKey;
|
||||||
ticketParser.GetTitleKey(gmTitleKey);
|
ticketParser.GetTitleKey(gmTitleKey);
|
||||||
|
|
||||||
// load GM partition
|
// load GM partition
|
||||||
dataSource->SetBaseOffset((uint64)partitionArray[gmPartitionIndex].partitionAddress * DISC_SECTOR_SIZE);
|
dataSource->SetBaseOffset((uint64)partitionArray[gmPartitionIndex].partitionAddress * DISC_SECTOR_SIZE);
|
||||||
return OpenFST(std::move(dataSource), (uint64)partitionHeaderGM.fstSector * DISC_SECTOR_SIZE, partitionHeaderGM.fstSize, &gmTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderGM.fstHashType));
|
FSTVolume* r = OpenFST(std::move(dataSource), (uint64)partitionHeaderGM.fstSector * DISC_SECTOR_SIZE, partitionHeaderGM.fstSize, &gmTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderGM.fstHashType), nullptr);
|
||||||
|
if (r)
|
||||||
|
SET_FST_ERROR(OK);
|
||||||
|
cemu_assert_debug(!(r->HashIsDisabled() && partitionHeaderGM.h3HashNum != 0)); // if hash is disabled, no H3 data may be present
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath)
|
FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath, ErrorCode* errorCodeOut)
|
||||||
{
|
{
|
||||||
|
SET_FST_ERROR(UNKNOWN_ERROR);
|
||||||
// load TMD
|
// load TMD
|
||||||
FileStream* tmdFile = FileStream::openFile2(folderPath / "title.tmd");
|
FileStream* tmdFile = FileStream::openFile2(folderPath / "title.tmd");
|
||||||
if (!tmdFile)
|
if (!tmdFile)
|
||||||
|
@ -379,17 +406,26 @@ FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath)
|
||||||
delete tmdFile;
|
delete tmdFile;
|
||||||
NCrypto::TMDParser tmdParser;
|
NCrypto::TMDParser tmdParser;
|
||||||
if (!tmdParser.parse(tmdData.data(), tmdData.size()))
|
if (!tmdParser.parse(tmdData.data(), tmdData.size()))
|
||||||
|
{
|
||||||
|
SET_FST_ERROR(BAD_TITLE_TMD);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
// load ticket
|
// load ticket
|
||||||
FileStream* ticketFile = FileStream::openFile2(folderPath / "title.tik");
|
FileStream* ticketFile = FileStream::openFile2(folderPath / "title.tik");
|
||||||
if (!ticketFile)
|
if (!ticketFile)
|
||||||
|
{
|
||||||
|
SET_FST_ERROR(TITLE_TIK_MISSING);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
std::vector<uint8> ticketData;
|
std::vector<uint8> ticketData;
|
||||||
ticketFile->extract(ticketData);
|
ticketFile->extract(ticketData);
|
||||||
delete ticketFile;
|
delete ticketFile;
|
||||||
NCrypto::ETicketParser ticketParser;
|
NCrypto::ETicketParser ticketParser;
|
||||||
if (!ticketParser.parse(ticketData.data(), ticketData.size()))
|
if (!ticketParser.parse(ticketData.data(), ticketData.size()))
|
||||||
|
{
|
||||||
|
SET_FST_ERROR(BAD_TITLE_TIK);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
NCrypto::AesKey titleKey;
|
NCrypto::AesKey titleKey;
|
||||||
ticketParser.GetTitleKey(titleKey);
|
ticketParser.GetTitleKey(titleKey);
|
||||||
// open data source
|
// open data source
|
||||||
|
@ -411,13 +447,15 @@ FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath)
|
||||||
}
|
}
|
||||||
// load FST
|
// load FST
|
||||||
// fstSize = size of first cluster?
|
// fstSize = size of first cluster?
|
||||||
FSTVolume* fstVolume = FSTVolume::OpenFST(std::move(dataSource), 0, fstSize, &titleKey, fstHashMode);
|
FSTVolume* fstVolume = FSTVolume::OpenFST(std::move(dataSource), 0, fstSize, &titleKey, fstHashMode, &tmdParser);
|
||||||
|
if (fstVolume)
|
||||||
|
SET_FST_ERROR(OK);
|
||||||
return fstVolume;
|
return fstVolume;
|
||||||
}
|
}
|
||||||
|
|
||||||
FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode)
|
FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD)
|
||||||
{
|
{
|
||||||
cemu_assert_debug(fstHashMode != ClusterHashMode::RAW || fstHashMode != ClusterHashMode::RAW2);
|
cemu_assert_debug(fstHashMode != ClusterHashMode::RAW || fstHashMode != ClusterHashMode::RAW_STREAM);
|
||||||
if (fstSize < sizeof(FSTHeader))
|
if (fstSize < sizeof(FSTHeader))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
constexpr uint64 FST_CLUSTER_OFFSET = 0;
|
constexpr uint64 FST_CLUSTER_OFFSET = 0;
|
||||||
|
@ -448,6 +486,34 @@ FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint3
|
||||||
clusterTable[i].offset = clusterDataTable[i].offset;
|
clusterTable[i].offset = clusterDataTable[i].offset;
|
||||||
clusterTable[i].size = clusterDataTable[i].size;
|
clusterTable[i].size = clusterDataTable[i].size;
|
||||||
clusterTable[i].hashMode = static_cast<FSTVolume::ClusterHashMode>((uint8)clusterDataTable[i].hashMode);
|
clusterTable[i].hashMode = static_cast<FSTVolume::ClusterHashMode>((uint8)clusterDataTable[i].hashMode);
|
||||||
|
clusterTable[i].hasContentHash = false; // from the TMD file (H4?)
|
||||||
|
}
|
||||||
|
// if the TMD is available (when opening .app files) we can use the extra info from it to validate unhashed clusters
|
||||||
|
// each content entry in the TMD corresponds to one cluster used by the FST
|
||||||
|
if(optionalTMD)
|
||||||
|
{
|
||||||
|
if(numCluster != optionalTMD->GetContentList().size())
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "FST: Number of clusters does not match TMD content list");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto& contentList = optionalTMD->GetContentList();
|
||||||
|
for(size_t i=0; i<contentList.size(); i++)
|
||||||
|
{
|
||||||
|
auto& cluster = clusterTable[i];
|
||||||
|
auto& content = contentList[i];
|
||||||
|
cluster.hasContentHash = true;
|
||||||
|
cluster.contentHashIsSHA1 = HAS_FLAG(contentList[i].contentFlags, NCrypto::TMDParser::TMDContentFlags::FLAG_SHA1);
|
||||||
|
cluster.contentSize = content.size;
|
||||||
|
static_assert(sizeof(content.hash32) == sizeof(cluster.contentHash32));
|
||||||
|
memcpy(cluster.contentHash32, content.hash32, sizeof(cluster.contentHash32));
|
||||||
|
// if unhashed mode, then initialize the hash context
|
||||||
|
if(cluster.hashMode == ClusterHashMode::RAW || cluster.hashMode == ClusterHashMode::RAW_STREAM)
|
||||||
|
{
|
||||||
|
cluster.singleHashCtx.reset(EVP_MD_CTX_new());
|
||||||
|
EVP_DigestInit_ex(cluster.singleHashCtx.get(), cluster.contentHashIsSHA1 ? EVP_sha1() : EVP_sha256(), nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// preprocess FST table
|
// preprocess FST table
|
||||||
FSTHeader_FileEntry* fileTable = (FSTHeader_FileEntry*)(clusterDataTable + numCluster);
|
FSTHeader_FileEntry* fileTable = (FSTHeader_FileEntry*)(clusterDataTable + numCluster);
|
||||||
|
@ -474,16 +540,17 @@ FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint3
|
||||||
fstVolume->m_offsetFactor = fstHeader->offsetFactor;
|
fstVolume->m_offsetFactor = fstHeader->offsetFactor;
|
||||||
fstVolume->m_sectorSize = DISC_SECTOR_SIZE;
|
fstVolume->m_sectorSize = DISC_SECTOR_SIZE;
|
||||||
fstVolume->m_partitionTitlekey = *partitionTitleKey;
|
fstVolume->m_partitionTitlekey = *partitionTitleKey;
|
||||||
std::swap(fstVolume->m_cluster, clusterTable);
|
fstVolume->m_hashIsDisabled = fstHeader->hashIsDisabled != 0;
|
||||||
std::swap(fstVolume->m_entries, fstEntries);
|
fstVolume->m_cluster = std::move(clusterTable);
|
||||||
std::swap(fstVolume->m_nameStringTable, nameStringTable);
|
fstVolume->m_entries = std::move(fstEntries);
|
||||||
|
fstVolume->m_nameStringTable = std::move(nameStringTable);
|
||||||
return fstVolume;
|
return fstVolume;
|
||||||
}
|
}
|
||||||
|
|
||||||
FSTVolume* FSTVolume::OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode)
|
FSTVolume* FSTVolume::OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD)
|
||||||
{
|
{
|
||||||
FSTDataSource* ds = dataSource.release();
|
FSTDataSource* ds = dataSource.release();
|
||||||
FSTVolume* fstVolume = OpenFST(ds, fstOffset, fstSize, partitionTitleKey, fstHashMode);
|
FSTVolume* fstVolume = OpenFST(ds, fstOffset, fstSize, partitionTitleKey, fstHashMode, optionalTMD);
|
||||||
if (!fstVolume)
|
if (!fstVolume)
|
||||||
{
|
{
|
||||||
delete ds;
|
delete ds;
|
||||||
|
@ -669,25 +736,25 @@ bool FSTVolume::OpenFile(std::string_view path, FSTFileHandle& fileHandleOut, bo
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FSTVolume::IsDirectory(FSTFileHandle& fileHandle) const
|
bool FSTVolume::IsDirectory(const FSTFileHandle& fileHandle) const
|
||||||
{
|
{
|
||||||
cemu_assert_debug(fileHandle.m_fstIndex < m_entries.size());
|
cemu_assert_debug(fileHandle.m_fstIndex < m_entries.size());
|
||||||
return m_entries[fileHandle.m_fstIndex].GetType() == FSTEntry::TYPE::DIRECTORY;
|
return m_entries[fileHandle.m_fstIndex].GetType() == FSTEntry::TYPE::DIRECTORY;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool FSTVolume::IsFile(FSTFileHandle& fileHandle) const
|
bool FSTVolume::IsFile(const FSTFileHandle& fileHandle) const
|
||||||
{
|
{
|
||||||
cemu_assert_debug(fileHandle.m_fstIndex < m_entries.size());
|
cemu_assert_debug(fileHandle.m_fstIndex < m_entries.size());
|
||||||
return m_entries[fileHandle.m_fstIndex].GetType() == FSTEntry::TYPE::FILE;
|
return m_entries[fileHandle.m_fstIndex].GetType() == FSTEntry::TYPE::FILE;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool FSTVolume::HasLinkFlag(FSTFileHandle& fileHandle) const
|
bool FSTVolume::HasLinkFlag(const FSTFileHandle& fileHandle) const
|
||||||
{
|
{
|
||||||
cemu_assert_debug(fileHandle.m_fstIndex < m_entries.size());
|
cemu_assert_debug(fileHandle.m_fstIndex < m_entries.size());
|
||||||
return HAS_FLAG(m_entries[fileHandle.m_fstIndex].GetFlags(), FSTEntry::FLAGS::FLAG_LINK);
|
return HAS_FLAG(m_entries[fileHandle.m_fstIndex].GetFlags(), FSTEntry::FLAGS::FLAG_LINK);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string_view FSTVolume::GetName(FSTFileHandle& fileHandle) const
|
std::string_view FSTVolume::GetName(const FSTFileHandle& fileHandle) const
|
||||||
{
|
{
|
||||||
if (fileHandle.m_fstIndex > m_entries.size())
|
if (fileHandle.m_fstIndex > m_entries.size())
|
||||||
return "";
|
return "";
|
||||||
|
@ -695,7 +762,7 @@ std::string_view FSTVolume::GetName(FSTFileHandle& fileHandle) const
|
||||||
return entryName;
|
return entryName;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string FSTVolume::GetPath(FSTFileHandle& fileHandle) const
|
std::string FSTVolume::GetPath(const FSTFileHandle& fileHandle) const
|
||||||
{
|
{
|
||||||
std::string path;
|
std::string path;
|
||||||
auto& entry = m_entries[fileHandle.m_fstIndex];
|
auto& entry = m_entries[fileHandle.m_fstIndex];
|
||||||
|
@ -726,7 +793,7 @@ std::string FSTVolume::GetPath(FSTFileHandle& fileHandle) const
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 FSTVolume::GetFileSize(FSTFileHandle& fileHandle) const
|
uint32 FSTVolume::GetFileSize(const FSTFileHandle& fileHandle) const
|
||||||
{
|
{
|
||||||
if (m_entries[fileHandle.m_fstIndex].GetType() != FSTEntry::TYPE::FILE)
|
if (m_entries[fileHandle.m_fstIndex].GetType() != FSTEntry::TYPE::FILE)
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -740,7 +807,7 @@ uint32 FSTVolume::ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size
|
||||||
return 0;
|
return 0;
|
||||||
cemu_assert_debug(!HAS_FLAG(entry.GetFlags(), FSTEntry::FLAGS::FLAG_LINK));
|
cemu_assert_debug(!HAS_FLAG(entry.GetFlags(), FSTEntry::FLAGS::FLAG_LINK));
|
||||||
FSTCluster& cluster = m_cluster[entry.fileInfo.clusterIndex];
|
FSTCluster& cluster = m_cluster[entry.fileInfo.clusterIndex];
|
||||||
if (cluster.hashMode == ClusterHashMode::RAW || cluster.hashMode == ClusterHashMode::RAW2)
|
if (cluster.hashMode == ClusterHashMode::RAW || cluster.hashMode == ClusterHashMode::RAW_STREAM)
|
||||||
return ReadFile_HashModeRaw(entry.fileInfo.clusterIndex, entry, offset, size, dataOut);
|
return ReadFile_HashModeRaw(entry.fileInfo.clusterIndex, entry, offset, size, dataOut);
|
||||||
else if (cluster.hashMode == ClusterHashMode::HASH_INTERLEAVED)
|
else if (cluster.hashMode == ClusterHashMode::HASH_INTERLEAVED)
|
||||||
return ReadFile_HashModeHashed(entry.fileInfo.clusterIndex, entry, offset, size, dataOut);
|
return ReadFile_HashModeHashed(entry.fileInfo.clusterIndex, entry, offset, size, dataOut);
|
||||||
|
@ -748,87 +815,15 @@ uint32 FSTVolume::ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 FSTVolume::ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut)
|
|
||||||
{
|
|
||||||
const uint32 readSizeInput = readSize;
|
|
||||||
uint8* dataOutU8 = (uint8*)dataOut;
|
|
||||||
if (readOffset >= entry.fileInfo.fileSize)
|
|
||||||
return 0;
|
|
||||||
else if ((readOffset + readSize) >= entry.fileInfo.fileSize)
|
|
||||||
readSize = (entry.fileInfo.fileSize - readOffset);
|
|
||||||
|
|
||||||
const FSTCluster& cluster = m_cluster[clusterIndex];
|
|
||||||
uint64 clusterOffset = (uint64)cluster.offset * m_sectorSize;
|
|
||||||
uint64 absFileOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset;
|
|
||||||
|
|
||||||
// make sure the raw range we read is aligned to AES block size (16)
|
|
||||||
uint64 readAddrStart = absFileOffset & ~0xF;
|
|
||||||
uint64 readAddrEnd = (absFileOffset + readSize + 0xF) & ~0xF;
|
|
||||||
|
|
||||||
bool usesInitialIV = readOffset < 16;
|
|
||||||
if (!usesInitialIV)
|
|
||||||
readAddrStart -= 16; // read previous AES block since we require it for the IV
|
|
||||||
uint32 prePadding = (uint32)(absFileOffset - readAddrStart); // number of extra bytes we read before readOffset (for AES alignment and IV calculation)
|
|
||||||
uint32 postPadding = (uint32)(readAddrEnd - (absFileOffset + readSize));
|
|
||||||
|
|
||||||
uint8 readBuffer[64 * 1024];
|
|
||||||
// read first chunk
|
|
||||||
// if file read offset (readOffset) is within the first AES-block then use initial IV calculated from cluster index
|
|
||||||
// otherwise read previous AES-block is the IV (AES-CBC)
|
|
||||||
uint64 readAddrCurrent = readAddrStart;
|
|
||||||
uint32 rawBytesToRead = (uint32)std::min((readAddrEnd - readAddrStart), (uint64)sizeof(readBuffer));
|
|
||||||
if (m_dataSource->readData(clusterIndex, clusterOffset, readAddrCurrent, readBuffer, rawBytesToRead) != rawBytesToRead)
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::Force, "FST read error in raw content");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
readAddrCurrent += rawBytesToRead;
|
|
||||||
|
|
||||||
uint8 iv[16]{};
|
|
||||||
if (usesInitialIV)
|
|
||||||
{
|
|
||||||
// for the first AES block, the IV is initialized from cluster index
|
|
||||||
iv[0] = (uint8)(clusterIndex >> 8);
|
|
||||||
iv[1] = (uint8)(clusterIndex >> 0);
|
|
||||||
AES128_CBC_decrypt_updateIV(readBuffer, readBuffer, rawBytesToRead, m_partitionTitlekey.b, iv);
|
|
||||||
std::memcpy(dataOutU8, readBuffer + prePadding, rawBytesToRead - prePadding - postPadding);
|
|
||||||
dataOutU8 += (rawBytesToRead - prePadding - postPadding);
|
|
||||||
readSize -= (rawBytesToRead - prePadding - postPadding);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// IV is initialized from previous AES block (AES-CBC)
|
|
||||||
std::memcpy(iv, readBuffer, 16);
|
|
||||||
AES128_CBC_decrypt_updateIV(readBuffer + 16, readBuffer + 16, rawBytesToRead - 16, m_partitionTitlekey.b, iv);
|
|
||||||
std::memcpy(dataOutU8, readBuffer + prePadding, rawBytesToRead - prePadding - postPadding);
|
|
||||||
dataOutU8 += (rawBytesToRead - prePadding - postPadding);
|
|
||||||
readSize -= (rawBytesToRead - prePadding - postPadding);
|
|
||||||
}
|
|
||||||
|
|
||||||
// read remaining chunks
|
|
||||||
while (readSize > 0)
|
|
||||||
{
|
|
||||||
uint32 bytesToRead = (uint32)std::min((uint32)sizeof(readBuffer), readSize);
|
|
||||||
uint32 alignedBytesToRead = (bytesToRead + 15) & ~0xF;
|
|
||||||
if (m_dataSource->readData(clusterIndex, clusterOffset, readAddrCurrent, readBuffer, alignedBytesToRead) != alignedBytesToRead)
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::Force, "FST read error in raw content");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
AES128_CBC_decrypt_updateIV(readBuffer, readBuffer, alignedBytesToRead, m_partitionTitlekey.b, iv);
|
|
||||||
std::memcpy(dataOutU8, readBuffer, bytesToRead);
|
|
||||||
dataOutU8 += bytesToRead;
|
|
||||||
readSize -= bytesToRead;
|
|
||||||
readAddrCurrent += alignedBytesToRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
return readSizeInput - readSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr size_t BLOCK_SIZE = 0x10000;
|
constexpr size_t BLOCK_SIZE = 0x10000;
|
||||||
constexpr size_t BLOCK_HASH_SIZE = 0x0400;
|
constexpr size_t BLOCK_HASH_SIZE = 0x0400;
|
||||||
constexpr size_t BLOCK_FILE_SIZE = 0xFC00;
|
constexpr size_t BLOCK_FILE_SIZE = 0xFC00;
|
||||||
|
|
||||||
|
struct FSTRawBlock
|
||||||
|
{
|
||||||
|
std::vector<uint8> rawData; // unhashed block size depends on sector size field in partition header
|
||||||
|
};
|
||||||
|
|
||||||
struct FSTHashedBlock
|
struct FSTHashedBlock
|
||||||
{
|
{
|
||||||
uint8 rawData[BLOCK_SIZE];
|
uint8 rawData[BLOCK_SIZE];
|
||||||
|
@ -870,12 +865,160 @@ struct FSTHashedBlock
|
||||||
|
|
||||||
static_assert(sizeof(FSTHashedBlock) == BLOCK_SIZE);
|
static_assert(sizeof(FSTHashedBlock) == BLOCK_SIZE);
|
||||||
|
|
||||||
|
struct FSTCachedRawBlock
|
||||||
|
{
|
||||||
|
FSTRawBlock blockData;
|
||||||
|
uint8 ivForNextBlock[16];
|
||||||
|
uint64 lastAccess;
|
||||||
|
};
|
||||||
|
|
||||||
struct FSTCachedHashedBlock
|
struct FSTCachedHashedBlock
|
||||||
{
|
{
|
||||||
FSTHashedBlock blockData;
|
FSTHashedBlock blockData;
|
||||||
uint64 lastAccess;
|
uint64 lastAccess;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Checks cache fill state and if necessary drops least recently accessed block from the cache. Optionally allows to recycle the released cache entry to cut down cost of memory allocation and clearing
|
||||||
|
void FSTVolume::TrimCacheIfRequired(FSTCachedRawBlock** droppedRawBlock, FSTCachedHashedBlock** droppedHashedBlock)
|
||||||
|
{
|
||||||
|
// calculate size used by cache
|
||||||
|
size_t cacheSize = 0;
|
||||||
|
for (auto& itr : m_cacheDecryptedRawBlocks)
|
||||||
|
cacheSize += itr.second->blockData.rawData.size();
|
||||||
|
for (auto& itr : m_cacheDecryptedHashedBlocks)
|
||||||
|
cacheSize += sizeof(FSTCachedHashedBlock) + sizeof(FSTHashedBlock);
|
||||||
|
// only trim if cache is full (larger than 2MB)
|
||||||
|
if (cacheSize < 2*1024*1024) // 2MB
|
||||||
|
return;
|
||||||
|
// scan both cache lists to find least recently accessed block to drop
|
||||||
|
auto dropRawItr = std::min_element(m_cacheDecryptedRawBlocks.begin(), m_cacheDecryptedRawBlocks.end(), [](const auto& a, const auto& b) -> bool
|
||||||
|
{ return a.second->lastAccess < b.second->lastAccess; });
|
||||||
|
auto dropHashedItr = std::min_element(m_cacheDecryptedHashedBlocks.begin(), m_cacheDecryptedHashedBlocks.end(), [](const auto& a, const auto& b) -> bool
|
||||||
|
{ return a.second->lastAccess < b.second->lastAccess; });
|
||||||
|
uint64 lastAccess = std::numeric_limits<uint64>::max();
|
||||||
|
if(dropRawItr != m_cacheDecryptedRawBlocks.end())
|
||||||
|
lastAccess = dropRawItr->second->lastAccess;
|
||||||
|
if(dropHashedItr != m_cacheDecryptedHashedBlocks.end())
|
||||||
|
lastAccess = std::min<uint64>(lastAccess, dropHashedItr->second->lastAccess);
|
||||||
|
if(dropRawItr != m_cacheDecryptedRawBlocks.end() && dropRawItr->second->lastAccess == lastAccess)
|
||||||
|
{
|
||||||
|
if (droppedRawBlock)
|
||||||
|
*droppedRawBlock = dropRawItr->second;
|
||||||
|
else
|
||||||
|
delete dropRawItr->second;
|
||||||
|
m_cacheDecryptedRawBlocks.erase(dropRawItr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(dropHashedItr != m_cacheDecryptedHashedBlocks.end() && dropHashedItr->second->lastAccess == lastAccess)
|
||||||
|
{
|
||||||
|
if (droppedHashedBlock)
|
||||||
|
*droppedHashedBlock = dropHashedItr->second;
|
||||||
|
else
|
||||||
|
delete dropHashedItr->second;
|
||||||
|
m_cacheDecryptedHashedBlocks.erase(dropHashedItr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FSTVolume::DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, uint8 ivOut[16])
|
||||||
|
{
|
||||||
|
memset(ivOut, 0, sizeof(ivOut));
|
||||||
|
if(blockIndex == 0)
|
||||||
|
{
|
||||||
|
ivOut[0] = (uint8)(clusterIndex >> 8);
|
||||||
|
ivOut[1] = (uint8)(clusterIndex >> 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// the last 16 encrypted bytes of the previous block are the IV (AES CBC)
|
||||||
|
// if the previous block is cached we can grab the IV from there. Otherwise we have to read the 16 bytes from the data source
|
||||||
|
uint32 prevBlockIndex = blockIndex - 1;
|
||||||
|
uint64 cacheBlockId = ((uint64)clusterIndex << (64 - 16)) | (uint64)prevBlockIndex;
|
||||||
|
auto itr = m_cacheDecryptedRawBlocks.find(cacheBlockId);
|
||||||
|
if (itr != m_cacheDecryptedRawBlocks.end())
|
||||||
|
{
|
||||||
|
memcpy(ivOut, itr->second->ivForNextBlock, 16);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cemu_assert(m_sectorSize >= 16);
|
||||||
|
uint64 clusterOffset = (uint64)m_cluster[clusterIndex].offset * m_sectorSize;
|
||||||
|
uint8 prevIV[16];
|
||||||
|
if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * m_sectorSize - 16, prevIV, 16) != 16)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Failed to read IV for raw FST block");
|
||||||
|
m_detectedCorruption = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(ivOut, prevIV, 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FSTCachedRawBlock* FSTVolume::GetDecryptedRawBlock(uint32 clusterIndex, uint32 blockIndex)
|
||||||
|
{
|
||||||
|
FSTCluster& cluster = m_cluster[clusterIndex];
|
||||||
|
uint64 clusterOffset = (uint64)cluster.offset * m_sectorSize;
|
||||||
|
// generate id for cache
|
||||||
|
uint64 cacheBlockId = ((uint64)clusterIndex << (64 - 16)) | (uint64)blockIndex;
|
||||||
|
// lookup block in cache
|
||||||
|
FSTCachedRawBlock* block = nullptr;
|
||||||
|
auto itr = m_cacheDecryptedRawBlocks.find(cacheBlockId);
|
||||||
|
if (itr != m_cacheDecryptedRawBlocks.end())
|
||||||
|
{
|
||||||
|
block = itr->second;
|
||||||
|
block->lastAccess = ++m_cacheAccessCounter;
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
// if cache already full, drop least recently accessed block and recycle FSTCachedRawBlock object if possible
|
||||||
|
TrimCacheIfRequired(&block, nullptr);
|
||||||
|
if (!block)
|
||||||
|
block = new FSTCachedRawBlock();
|
||||||
|
block->blockData.rawData.resize(m_sectorSize);
|
||||||
|
// block not cached, read new
|
||||||
|
block->lastAccess = ++m_cacheAccessCounter;
|
||||||
|
if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * m_sectorSize, block->blockData.rawData.data(), m_sectorSize) != m_sectorSize)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Failed to read raw FST block");
|
||||||
|
delete block;
|
||||||
|
m_detectedCorruption = true;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// decrypt hash data
|
||||||
|
uint8 iv[16]{};
|
||||||
|
DetermineUnhashedBlockIV(clusterIndex, blockIndex, iv);
|
||||||
|
memcpy(block->ivForNextBlock, block->blockData.rawData.data() + m_sectorSize - 16, 16);
|
||||||
|
AES128_CBC_decrypt(block->blockData.rawData.data(), block->blockData.rawData.data(), m_sectorSize, m_partitionTitlekey.b, iv);
|
||||||
|
// if this is the next block, then hash it
|
||||||
|
if(cluster.hasContentHash)
|
||||||
|
{
|
||||||
|
if(cluster.singleHashNumBlocksHashed == blockIndex)
|
||||||
|
{
|
||||||
|
cemu_assert_debug(!(cluster.contentSize % m_sectorSize)); // size should be multiple of sector size? Regardless, the hashing code below can handle non-aligned sizes
|
||||||
|
bool isLastBlock = blockIndex == (std::max<uint32>(cluster.contentSize / m_sectorSize, 1) - 1);
|
||||||
|
uint32 hashSize = m_sectorSize;
|
||||||
|
if(isLastBlock)
|
||||||
|
hashSize = cluster.contentSize - (uint64)blockIndex*m_sectorSize;
|
||||||
|
EVP_DigestUpdate(cluster.singleHashCtx.get(), block->blockData.rawData.data(), hashSize);
|
||||||
|
cluster.singleHashNumBlocksHashed++;
|
||||||
|
if(isLastBlock)
|
||||||
|
{
|
||||||
|
uint8 hash[32];
|
||||||
|
EVP_DigestFinal_ex(cluster.singleHashCtx.get(), hash, nullptr);
|
||||||
|
if(memcmp(hash, cluster.contentHash32, cluster.contentHashIsSHA1 ? 20 : 32) != 0)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "FST: Raw section hash mismatch");
|
||||||
|
delete block;
|
||||||
|
m_detectedCorruption = true;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// register in cache
|
||||||
|
m_cacheDecryptedRawBlocks.emplace(cacheBlockId, block);
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex)
|
FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex)
|
||||||
{
|
{
|
||||||
const FSTCluster& cluster = m_cluster[clusterIndex];
|
const FSTCluster& cluster = m_cluster[clusterIndex];
|
||||||
|
@ -891,22 +1034,17 @@ FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, ui
|
||||||
block->lastAccess = ++m_cacheAccessCounter;
|
block->lastAccess = ++m_cacheAccessCounter;
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
// if cache already full, drop least recently accessed block (but recycle the FSTHashedBlock* object)
|
// if cache already full, drop least recently accessed block and recycle FSTCachedHashedBlock object if possible
|
||||||
if (m_cacheDecryptedHashedBlocks.size() >= 16)
|
TrimCacheIfRequired(nullptr, &block);
|
||||||
{
|
if (!block)
|
||||||
auto dropItr = std::min_element(m_cacheDecryptedHashedBlocks.begin(), m_cacheDecryptedHashedBlocks.end(), [](const auto& a, const auto& b) -> bool
|
|
||||||
{ return a.second->lastAccess < b.second->lastAccess; });
|
|
||||||
block = dropItr->second;
|
|
||||||
m_cacheDecryptedHashedBlocks.erase(dropItr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
block = new FSTCachedHashedBlock();
|
block = new FSTCachedHashedBlock();
|
||||||
// block not cached, read new
|
// block not cached, read new
|
||||||
block->lastAccess = ++m_cacheAccessCounter;
|
block->lastAccess = ++m_cacheAccessCounter;
|
||||||
if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * BLOCK_SIZE, block->blockData.rawData, BLOCK_SIZE) != BLOCK_SIZE)
|
if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * BLOCK_SIZE, block->blockData.rawData, BLOCK_SIZE) != BLOCK_SIZE)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Failed to read FST block");
|
cemuLog_log(LogType::Force, "Failed to read hashed FST block");
|
||||||
delete block;
|
delete block;
|
||||||
|
m_detectedCorruption = true;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
// decrypt hash data
|
// decrypt hash data
|
||||||
|
@ -914,11 +1052,46 @@ FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, ui
|
||||||
AES128_CBC_decrypt(block->blockData.getHashData(), block->blockData.getHashData(), BLOCK_HASH_SIZE, m_partitionTitlekey.b, iv);
|
AES128_CBC_decrypt(block->blockData.getHashData(), block->blockData.getHashData(), BLOCK_HASH_SIZE, m_partitionTitlekey.b, iv);
|
||||||
// decrypt file data
|
// decrypt file data
|
||||||
AES128_CBC_decrypt(block->blockData.getFileData(), block->blockData.getFileData(), BLOCK_FILE_SIZE, m_partitionTitlekey.b, block->blockData.getH0Hash(blockIndex%16));
|
AES128_CBC_decrypt(block->blockData.getFileData(), block->blockData.getFileData(), BLOCK_FILE_SIZE, m_partitionTitlekey.b, block->blockData.getH0Hash(blockIndex%16));
|
||||||
|
// compare with H0 to verify data integrity
|
||||||
|
NCrypto::CHash160 h0;
|
||||||
|
SHA1(block->blockData.getFileData(), BLOCK_FILE_SIZE, h0.b);
|
||||||
|
uint32 h0Index = (blockIndex % 4096);
|
||||||
|
if (memcmp(h0.b, block->blockData.getH0Hash(h0Index & 0xF), sizeof(h0.b)) != 0)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "FST: Hash H0 mismatch in hashed block (section {} index {})", clusterIndex, blockIndex);
|
||||||
|
delete block;
|
||||||
|
m_detectedCorruption = true;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
// register in cache
|
// register in cache
|
||||||
m_cacheDecryptedHashedBlocks.emplace(cacheBlockId, block);
|
m_cacheDecryptedHashedBlocks.emplace(cacheBlockId, block);
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32 FSTVolume::ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut)
|
||||||
|
{
|
||||||
|
uint8* dataOutU8 = (uint8*)dataOut;
|
||||||
|
if (readOffset >= entry.fileInfo.fileSize)
|
||||||
|
return 0;
|
||||||
|
else if ((readOffset + readSize) >= entry.fileInfo.fileSize)
|
||||||
|
readSize = (entry.fileInfo.fileSize - readOffset);
|
||||||
|
uint64 absFileOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset;
|
||||||
|
uint32 remainingReadSize = readSize;
|
||||||
|
while (remainingReadSize > 0)
|
||||||
|
{
|
||||||
|
const FSTCachedRawBlock* rawBlock = this->GetDecryptedRawBlock(clusterIndex, absFileOffset/m_sectorSize);
|
||||||
|
if (!rawBlock)
|
||||||
|
break;
|
||||||
|
uint32 blockOffset = (uint32)(absFileOffset % m_sectorSize);
|
||||||
|
uint32 bytesToRead = std::min<uint32>(remainingReadSize, m_sectorSize - blockOffset);
|
||||||
|
std::memcpy(dataOutU8, rawBlock->blockData.rawData.data() + blockOffset, bytesToRead);
|
||||||
|
dataOutU8 += bytesToRead;
|
||||||
|
remainingReadSize -= bytesToRead;
|
||||||
|
absFileOffset += bytesToRead;
|
||||||
|
}
|
||||||
|
return readSize - remainingReadSize;
|
||||||
|
}
|
||||||
|
|
||||||
uint32 FSTVolume::ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut)
|
uint32 FSTVolume::ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
@ -949,7 +1122,6 @@ uint32 FSTVolume::ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const FSTCluster& cluster = m_cluster[clusterIndex];
|
const FSTCluster& cluster = m_cluster[clusterIndex];
|
||||||
uint64 clusterBaseOffset = (uint64)cluster.offset * m_sectorSize;
|
|
||||||
uint64 fileReadOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset;
|
uint64 fileReadOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset;
|
||||||
uint32 blockIndex = (uint32)(fileReadOffset / BLOCK_FILE_SIZE);
|
uint32 blockIndex = (uint32)(fileReadOffset / BLOCK_FILE_SIZE);
|
||||||
uint32 bytesRemaining = readSize;
|
uint32 bytesRemaining = readSize;
|
||||||
|
@ -977,6 +1149,7 @@ bool FSTVolume::OpenDirectoryIterator(std::string_view path, FSTDirectoryIterato
|
||||||
if (!IsDirectory(fileHandle))
|
if (!IsDirectory(fileHandle))
|
||||||
return false;
|
return false;
|
||||||
auto const& fstEntry = m_entries[fileHandle.m_fstIndex];
|
auto const& fstEntry = m_entries[fileHandle.m_fstIndex];
|
||||||
|
directoryIteratorOut.dirHandle = fileHandle;
|
||||||
directoryIteratorOut.startIndex = fileHandle.m_fstIndex + 1;
|
directoryIteratorOut.startIndex = fileHandle.m_fstIndex + 1;
|
||||||
directoryIteratorOut.endIndex = fstEntry.dirInfo.endIndex;
|
directoryIteratorOut.endIndex = fstEntry.dirInfo.endIndex;
|
||||||
directoryIteratorOut.currentIndex = directoryIteratorOut.startIndex;
|
directoryIteratorOut.currentIndex = directoryIteratorOut.startIndex;
|
||||||
|
@ -1001,6 +1174,8 @@ bool FSTVolume::Next(FSTDirectoryIterator& directoryIterator, FSTFileHandle& fil
|
||||||
|
|
||||||
FSTVolume::~FSTVolume()
|
FSTVolume::~FSTVolume()
|
||||||
{
|
{
|
||||||
|
for (auto& itr : m_cacheDecryptedRawBlocks)
|
||||||
|
delete itr.second;
|
||||||
for (auto& itr : m_cacheDecryptedHashedBlocks)
|
for (auto& itr : m_cacheDecryptedHashedBlocks)
|
||||||
delete itr.second;
|
delete itr.second;
|
||||||
if (m_sourceIsOwned)
|
if (m_sourceIsOwned)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Cemu/ncrypto/ncrypto.h"
|
#include "Cemu/ncrypto/ncrypto.h"
|
||||||
|
#include "openssl/evp.h"
|
||||||
|
|
||||||
struct FSTFileHandle
|
struct FSTFileHandle
|
||||||
{
|
{
|
||||||
|
@ -11,7 +12,13 @@ private:
|
||||||
struct FSTDirectoryIterator
|
struct FSTDirectoryIterator
|
||||||
{
|
{
|
||||||
friend class FSTVolume;
|
friend class FSTVolume;
|
||||||
|
|
||||||
|
const FSTFileHandle& GetDirHandle() const
|
||||||
|
{
|
||||||
|
return dirHandle;
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
|
FSTFileHandle dirHandle;
|
||||||
uint32 startIndex;
|
uint32 startIndex;
|
||||||
uint32 endIndex;
|
uint32 endIndex;
|
||||||
uint32 currentIndex;
|
uint32 currentIndex;
|
||||||
|
@ -20,28 +27,39 @@ private:
|
||||||
class FSTVolume
|
class FSTVolume
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
enum class ErrorCode
|
||||||
|
{
|
||||||
|
OK = 0,
|
||||||
|
UNKNOWN_ERROR = 1,
|
||||||
|
DISC_KEY_MISSING = 2,
|
||||||
|
TITLE_TIK_MISSING = 3,
|
||||||
|
BAD_TITLE_TMD = 4,
|
||||||
|
BAD_TITLE_TIK = 5,
|
||||||
|
};
|
||||||
|
|
||||||
static bool FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey);
|
static bool FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey);
|
||||||
|
|
||||||
static FSTVolume* OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey);
|
static FSTVolume* OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey, ErrorCode* errorCodeOut = nullptr);
|
||||||
static FSTVolume* OpenFromDiscImage(const fs::path& path, bool* keyFound = nullptr);
|
static FSTVolume* OpenFromDiscImage(const fs::path& path, ErrorCode* errorCodeOut = nullptr);
|
||||||
static FSTVolume* OpenFromContentFolder(fs::path folderPath);
|
static FSTVolume* OpenFromContentFolder(fs::path folderPath, ErrorCode* errorCodeOut = nullptr);
|
||||||
|
|
||||||
~FSTVolume();
|
~FSTVolume();
|
||||||
|
|
||||||
uint32 GetFileCount() const;
|
uint32 GetFileCount() const;
|
||||||
|
bool HasCorruption() const { return m_detectedCorruption; }
|
||||||
|
|
||||||
bool OpenFile(std::string_view path, FSTFileHandle& fileHandleOut, bool openOnlyFiles = false);
|
bool OpenFile(std::string_view path, FSTFileHandle& fileHandleOut, bool openOnlyFiles = false);
|
||||||
|
|
||||||
// file and directory functions
|
// file and directory functions
|
||||||
bool IsDirectory(FSTFileHandle& fileHandle) const;
|
bool IsDirectory(const FSTFileHandle& fileHandle) const;
|
||||||
bool IsFile(FSTFileHandle& fileHandle) const;
|
bool IsFile(const FSTFileHandle& fileHandle) const;
|
||||||
bool HasLinkFlag(FSTFileHandle& fileHandle) const;
|
bool HasLinkFlag(const FSTFileHandle& fileHandle) const;
|
||||||
|
|
||||||
std::string_view GetName(FSTFileHandle& fileHandle) const;
|
std::string_view GetName(const FSTFileHandle& fileHandle) const;
|
||||||
std::string GetPath(FSTFileHandle& fileHandle) const;
|
std::string GetPath(const FSTFileHandle& fileHandle) const;
|
||||||
|
|
||||||
// file functions
|
// file functions
|
||||||
uint32 GetFileSize(FSTFileHandle& fileHandle) const;
|
uint32 GetFileSize(const FSTFileHandle& fileHandle) const;
|
||||||
uint32 ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size, void* dataOut);
|
uint32 ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size, void* dataOut);
|
||||||
|
|
||||||
// directory iterator
|
// directory iterator
|
||||||
|
@ -70,15 +88,25 @@ private:
|
||||||
enum class ClusterHashMode : uint8
|
enum class ClusterHashMode : uint8
|
||||||
{
|
{
|
||||||
RAW = 0, // raw data + encryption, no hashing?
|
RAW = 0, // raw data + encryption, no hashing?
|
||||||
RAW2 = 1, // raw data + encryption, with hash stored in tmd?
|
RAW_STREAM = 1, // raw data + encryption, with hash stored in tmd?
|
||||||
HASH_INTERLEAVED = 2, // hashes + raw interleaved in 0x10000 blocks (0x400 bytes of hashes at the beginning, followed by 0xFC00 bytes of data)
|
HASH_INTERLEAVED = 2, // hashes + raw interleaved in 0x10000 blocks (0x400 bytes of hashes at the beginning, followed by 0xFC00 bytes of data)
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FSTCluster
|
struct FSTCluster
|
||||||
{
|
{
|
||||||
|
FSTCluster() : singleHashCtx(nullptr, &EVP_MD_CTX_free) {}
|
||||||
|
|
||||||
uint32 offset;
|
uint32 offset;
|
||||||
uint32 size;
|
uint32 size;
|
||||||
ClusterHashMode hashMode;
|
ClusterHashMode hashMode;
|
||||||
|
// extra data if TMD is available
|
||||||
|
bool hasContentHash;
|
||||||
|
uint8 contentHash32[32];
|
||||||
|
bool contentHashIsSHA1; // if true then it's SHA1 (with extra bytes zeroed out), otherwise it's SHA256
|
||||||
|
uint64 contentSize; // size of the content (in blocks)
|
||||||
|
// hash context for single hash mode (content hash must be available)
|
||||||
|
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> singleHashCtx; // unique_ptr to make this move-only
|
||||||
|
uint32 singleHashNumBlocksHashed{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FSTEntry
|
struct FSTEntry
|
||||||
|
@ -148,17 +176,30 @@ private:
|
||||||
bool m_sourceIsOwned{};
|
bool m_sourceIsOwned{};
|
||||||
uint32 m_sectorSize{}; // for cluster offsets
|
uint32 m_sectorSize{}; // for cluster offsets
|
||||||
uint32 m_offsetFactor{}; // for file offsets
|
uint32 m_offsetFactor{}; // for file offsets
|
||||||
|
bool m_hashIsDisabled{}; // disables hash verification (for all clusters of this volume?)
|
||||||
std::vector<FSTCluster> m_cluster;
|
std::vector<FSTCluster> m_cluster;
|
||||||
std::vector<FSTEntry> m_entries;
|
std::vector<FSTEntry> m_entries;
|
||||||
std::vector<char> m_nameStringTable;
|
std::vector<char> m_nameStringTable;
|
||||||
NCrypto::AesKey m_partitionTitlekey;
|
NCrypto::AesKey m_partitionTitlekey;
|
||||||
|
bool m_detectedCorruption{false};
|
||||||
|
|
||||||
/* Cache for decrypted hashed blocks */
|
bool HashIsDisabled() const
|
||||||
|
{
|
||||||
|
return m_hashIsDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cache for decrypted raw and hashed blocks */
|
||||||
|
std::unordered_map<uint64, struct FSTCachedRawBlock*> m_cacheDecryptedRawBlocks;
|
||||||
std::unordered_map<uint64, struct FSTCachedHashedBlock*> m_cacheDecryptedHashedBlocks;
|
std::unordered_map<uint64, struct FSTCachedHashedBlock*> m_cacheDecryptedHashedBlocks;
|
||||||
uint64 m_cacheAccessCounter{};
|
uint64 m_cacheAccessCounter{};
|
||||||
|
|
||||||
|
void DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, uint8 ivOut[16]);
|
||||||
|
|
||||||
|
struct FSTCachedRawBlock* GetDecryptedRawBlock(uint32 clusterIndex, uint32 blockIndex);
|
||||||
struct FSTCachedHashedBlock* GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex);
|
struct FSTCachedHashedBlock* GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex);
|
||||||
|
|
||||||
|
void TrimCacheIfRequired(struct FSTCachedRawBlock** droppedRawBlock, struct FSTCachedHashedBlock** droppedHashedBlock);
|
||||||
|
|
||||||
/* File reading */
|
/* File reading */
|
||||||
uint32 ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut);
|
uint32 ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut);
|
||||||
uint32 ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut);
|
uint32 ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut);
|
||||||
|
@ -169,7 +210,10 @@ private:
|
||||||
/* +0x00 */ uint32be magic;
|
/* +0x00 */ uint32be magic;
|
||||||
/* +0x04 */ uint32be offsetFactor;
|
/* +0x04 */ uint32be offsetFactor;
|
||||||
/* +0x08 */ uint32be numCluster;
|
/* +0x08 */ uint32be numCluster;
|
||||||
/* +0x0C */ uint32be ukn0C;
|
/* +0x0C */ uint8be hashIsDisabled;
|
||||||
|
/* +0x0D */ uint8be ukn0D;
|
||||||
|
/* +0x0E */ uint8be ukn0E;
|
||||||
|
/* +0x0F */ uint8be ukn0F;
|
||||||
/* +0x10 */ uint32be ukn10;
|
/* +0x10 */ uint32be ukn10;
|
||||||
/* +0x14 */ uint32be ukn14;
|
/* +0x14 */ uint32be ukn14;
|
||||||
/* +0x18 */ uint32be ukn18;
|
/* +0x18 */ uint32be ukn18;
|
||||||
|
@ -246,8 +290,8 @@ private:
|
||||||
|
|
||||||
static_assert(sizeof(FSTHeader_FileEntry) == 0x10);
|
static_assert(sizeof(FSTHeader_FileEntry) == 0x10);
|
||||||
|
|
||||||
static FSTVolume* OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode);
|
static FSTVolume* OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD);
|
||||||
static FSTVolume* OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode);
|
static FSTVolume* OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD);
|
||||||
static bool ProcessFST(FSTHeader_FileEntry* fileTable, uint32 numFileEntries, uint32 numCluster, std::vector<char>& nameStringTable, std::vector<FSTEntry>& fstEntries);
|
static bool ProcessFST(FSTHeader_FileEntry* fileTable, uint32 numFileEntries, uint32 numCluster, std::vector<char>& nameStringTable, std::vector<FSTEntry>& fstEntries);
|
||||||
|
|
||||||
bool MatchFSTEntryName(FSTEntry& entry, std::string_view comparedName)
|
bool MatchFSTEntryName(FSTEntry& entry, std::string_view comparedName)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include <wx/msgdlg.h>
|
#include <wx/msgdlg.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <gui/helpers/wxHelpers.h>
|
||||||
|
|
||||||
#include "config/ActiveSettings.h"
|
#include "config/ActiveSettings.h"
|
||||||
#include "util/crypto/aes128.h"
|
#include "util/crypto/aes128.h"
|
||||||
|
@ -74,7 +75,7 @@ void KeyCache_Prepare()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
wxMessageBox("Unable to create file keys.txt\nThis can happen if Cemu does not have write permission to it's own directory, the disk is full or if anti-virus software is blocking Cemu.", "Error", wxOK | wxCENTRE | wxICON_ERROR);
|
wxMessageBox(_("Unable to create file keys.txt\nThis can happen if Cemu does not have write permission to its own directory, the disk is full or if anti-virus software is blocking Cemu."), _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
|
||||||
}
|
}
|
||||||
mtxKeyCache.unlock();
|
mtxKeyCache.unlock();
|
||||||
return;
|
return;
|
||||||
|
@ -107,10 +108,8 @@ void KeyCache_Prepare()
|
||||||
continue;
|
continue;
|
||||||
if( strishex(line) == false )
|
if( strishex(line) == false )
|
||||||
{
|
{
|
||||||
// show error message
|
auto errorMsg = formatWxString(_("Error in keys.txt at line {}"), lineNumber);
|
||||||
char errorMsg[512];
|
wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
|
||||||
sprintf(errorMsg, "Error in keys.txt in line %d\n", lineNumber);
|
|
||||||
wxMessageBox(errorMsg, "Error", wxOK | wxCENTRE | wxICON_ERROR);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(line.size() == 32 )
|
if(line.size() == 32 )
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include <boost/container/small_vector.hpp>
|
#include <boost/container/small_vector.hpp>
|
||||||
|
|
||||||
|
#include "../fsc.h"
|
||||||
|
|
||||||
// path parser and utility class for Wii U paths
|
// path parser and utility class for Wii U paths
|
||||||
// optimized to be allocation-free for common path lengths
|
// optimized to be allocation-free for common path lengths
|
||||||
class FSCPath
|
class FSCPath
|
||||||
|
@ -119,9 +121,7 @@ public:
|
||||||
template<typename F>
|
template<typename F>
|
||||||
class FSAFileTree
|
class FSAFileTree
|
||||||
{
|
{
|
||||||
public:
|
private:
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
enum NODETYPE : uint8
|
enum NODETYPE : uint8
|
||||||
{
|
{
|
||||||
|
@ -133,6 +133,7 @@ private:
|
||||||
{
|
{
|
||||||
std::string name;
|
std::string name;
|
||||||
std::vector<node_t*> subnodes;
|
std::vector<node_t*> subnodes;
|
||||||
|
size_t fileSize;
|
||||||
F* custom;
|
F* custom;
|
||||||
NODETYPE type;
|
NODETYPE type;
|
||||||
};
|
};
|
||||||
|
@ -179,13 +180,54 @@ private:
|
||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DirectoryIterator : public FSCVirtualFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DirectoryIterator(node_t* node)
|
||||||
|
: m_node(node), m_subnodeIndex(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
sint32 fscGetType() override
|
||||||
|
{
|
||||||
|
return FSC_TYPE_DIRECTORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fscDirNext(FSCDirEntry* dirEntry) override
|
||||||
|
{
|
||||||
|
if (m_subnodeIndex >= m_node->subnodes.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const node_t* subnode = m_node->subnodes[m_subnodeIndex];
|
||||||
|
|
||||||
|
strncpy(dirEntry->path, subnode->name.c_str(), sizeof(dirEntry->path) - 1);
|
||||||
|
dirEntry->path[sizeof(dirEntry->path) - 1] = '\0';
|
||||||
|
dirEntry->isDirectory = subnode->type == FSAFileTree::NODETYPE_DIRECTORY;
|
||||||
|
dirEntry->isFile = subnode->type == FSAFileTree::NODETYPE_FILE;
|
||||||
|
dirEntry->fileSize = subnode->type == FSAFileTree::NODETYPE_FILE ? subnode->fileSize : 0;
|
||||||
|
|
||||||
|
++m_subnodeIndex;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fscRewindDir() override
|
||||||
|
{
|
||||||
|
m_subnodeIndex = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
node_t* m_node;
|
||||||
|
size_t m_subnodeIndex;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FSAFileTree()
|
FSAFileTree()
|
||||||
{
|
{
|
||||||
rootNode.type = NODETYPE_DIRECTORY;
|
rootNode.type = NODETYPE_DIRECTORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool addFile(std::string_view path, F* custom)
|
bool addFile(std::string_view path, size_t fileSize, F* custom)
|
||||||
{
|
{
|
||||||
FSCPath p(path);
|
FSCPath p(path);
|
||||||
if (p.GetNodeCount() == 0)
|
if (p.GetNodeCount() == 0)
|
||||||
|
@ -196,6 +238,7 @@ public:
|
||||||
return false; // node already exists
|
return false; // node already exists
|
||||||
// add file node
|
// add file node
|
||||||
node_t* fileNode = newNode(directoryNode, NODETYPE_FILE, p.GetNodeName(p.GetNodeCount() - 1));
|
node_t* fileNode = newNode(directoryNode, NODETYPE_FILE, p.GetNodeName(p.GetNodeCount() - 1));
|
||||||
|
fileNode->fileSize = fileSize;
|
||||||
fileNode->custom = custom;
|
fileNode->custom = custom;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -214,6 +257,20 @@ public:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool getDirectory(std::string_view path, FSCVirtualFile*& dirIterator)
|
||||||
|
{
|
||||||
|
FSCPath p(path);
|
||||||
|
if (p.GetNodeCount() == 0)
|
||||||
|
return false;
|
||||||
|
node_t* node = getByNodePath(p, p.GetNodeCount(), false);
|
||||||
|
if (node == nullptr)
|
||||||
|
return false;
|
||||||
|
if (node->type != NODETYPE_DIRECTORY)
|
||||||
|
return false;
|
||||||
|
dirIterator = new DirectoryIterator(node);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool removeFile(std::string_view path)
|
bool removeFile(std::string_view path)
|
||||||
{
|
{
|
||||||
FSCPath p(path);
|
FSCPath p(path);
|
||||||
|
|
40
src/Cafe/Filesystem/WUHB/RomFSStructs.h
Normal file
40
src/Cafe/Filesystem/WUHB/RomFSStructs.h
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct romfs_header_t
|
||||||
|
{
|
||||||
|
uint32 header_magic;
|
||||||
|
uint32be header_size;
|
||||||
|
uint64be dir_hash_table_ofs;
|
||||||
|
uint64be dir_hash_table_size;
|
||||||
|
uint64be dir_table_ofs;
|
||||||
|
uint64be dir_table_size;
|
||||||
|
uint64be file_hash_table_ofs;
|
||||||
|
uint64be file_hash_table_size;
|
||||||
|
uint64be file_table_ofs;
|
||||||
|
uint64be file_table_size;
|
||||||
|
uint64be file_partition_ofs;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct romfs_direntry_t
|
||||||
|
{
|
||||||
|
uint32be parent;
|
||||||
|
uint32be listNext; // offset to next directory entry in linked list of parent directory (aka "sibling")
|
||||||
|
uint32be dirListHead; // offset to first entry in linked list of directory entries (aka "child")
|
||||||
|
uint32be fileListHead; // offset to first entry in linked list of file entries (aka "file")
|
||||||
|
uint32be hash;
|
||||||
|
uint32be name_size;
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct romfs_fentry_t
|
||||||
|
{
|
||||||
|
uint32be parent;
|
||||||
|
uint32be listNext; // offset to next file entry in linked list of parent directory (aka "sibling")
|
||||||
|
uint64be offset;
|
||||||
|
uint64be size;
|
||||||
|
uint32be hash;
|
||||||
|
uint32be name_size;
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define ROMFS_ENTRY_EMPTY 0xFFFFFFFF
|
224
src/Cafe/Filesystem/WUHB/WUHBReader.cpp
Normal file
224
src/Cafe/Filesystem/WUHB/WUHBReader.cpp
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
#include "WUHBReader.h"
|
||||||
|
WUHBReader* WUHBReader::FromPath(const fs::path& path)
|
||||||
|
{
|
||||||
|
FileStream* fileIn{FileStream::openFile2(path)};
|
||||||
|
if (!fileIn)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
WUHBReader* ret = new WUHBReader(fileIn);
|
||||||
|
if (!ret->CheckMagicValue())
|
||||||
|
{
|
||||||
|
delete ret;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ret->ReadHeader())
|
||||||
|
{
|
||||||
|
delete ret;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const romfs_direntry_t fallbackDirEntry{
|
||||||
|
.parent = ROMFS_ENTRY_EMPTY,
|
||||||
|
.listNext = ROMFS_ENTRY_EMPTY,
|
||||||
|
.dirListHead = ROMFS_ENTRY_EMPTY,
|
||||||
|
.fileListHead = ROMFS_ENTRY_EMPTY,
|
||||||
|
.hash = ROMFS_ENTRY_EMPTY,
|
||||||
|
.name_size = 0,
|
||||||
|
.name = ""
|
||||||
|
};
|
||||||
|
static const romfs_fentry_t fallbackFileEntry{
|
||||||
|
.parent = ROMFS_ENTRY_EMPTY,
|
||||||
|
.listNext = ROMFS_ENTRY_EMPTY,
|
||||||
|
.offset = 0,
|
||||||
|
.size = 0,
|
||||||
|
.hash = ROMFS_ENTRY_EMPTY,
|
||||||
|
.name_size = 0,
|
||||||
|
.name = ""
|
||||||
|
};
|
||||||
|
template<bool File>
|
||||||
|
const WUHBReader::EntryType<File>& WUHBReader::GetFallback()
|
||||||
|
{
|
||||||
|
if constexpr (File)
|
||||||
|
return fallbackFileEntry;
|
||||||
|
else
|
||||||
|
return fallbackDirEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool File>
|
||||||
|
WUHBReader::EntryType<File> WUHBReader::GetEntry(uint32 offset) const
|
||||||
|
{
|
||||||
|
auto fallback = GetFallback<File>();
|
||||||
|
if(offset == ROMFS_ENTRY_EMPTY)
|
||||||
|
return fallback;
|
||||||
|
|
||||||
|
const char* typeName = File ? "fentry" : "direntry";
|
||||||
|
EntryType<File> ret;
|
||||||
|
if (offset >= (File ? m_header.file_table_size : m_header.dir_table_size))
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "WUHB {} offset exceeds table size declared in header", typeName);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the entry
|
||||||
|
m_fileIn->SetPosition((File ? m_header.file_table_ofs : m_header.dir_table_ofs) + offset);
|
||||||
|
auto read = m_fileIn->readData(&ret, offsetof(EntryType<File>, name));
|
||||||
|
if (read != offsetof(EntryType<File>, name))
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "failed to read WUHB {} at offset: {}", typeName, offset);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the name
|
||||||
|
ret.name.resize(ret.name_size);
|
||||||
|
read = m_fileIn->readData(ret.name.data(), ret.name_size);
|
||||||
|
if (read != ret.name_size)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "failed to read WUHB {} name", typeName);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
romfs_direntry_t WUHBReader::GetDirEntry(uint32 offset) const
|
||||||
|
{
|
||||||
|
return GetEntry<false>(offset);
|
||||||
|
}
|
||||||
|
romfs_fentry_t WUHBReader::GetFileEntry(uint32 offset) const
|
||||||
|
{
|
||||||
|
return GetEntry<true>(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 WUHBReader::GetFileSize(uint32 entryOffset) const
|
||||||
|
{
|
||||||
|
return GetFileEntry(entryOffset).size;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 WUHBReader::ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const
|
||||||
|
{
|
||||||
|
const auto fileEntry = GetFileEntry(entryOffset);
|
||||||
|
if (fileOffset >= fileEntry.size)
|
||||||
|
return 0;
|
||||||
|
const uint64 readAmount = std::min(length, fileEntry.size - fileOffset);
|
||||||
|
const uint64 wuhbOffset = m_header.file_partition_ofs + fileEntry.offset + fileOffset;
|
||||||
|
m_fileIn->SetPosition(wuhbOffset);
|
||||||
|
return m_fileIn->readData(buffer, readAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 WUHBReader::GetHashTableEntryOffset(uint32 hash, bool isFile) const
|
||||||
|
{
|
||||||
|
const uint64 hash_table_size = (isFile ? m_header.file_hash_table_size : m_header.dir_hash_table_size);
|
||||||
|
const uint64 hash_table_ofs = (isFile ? m_header.file_hash_table_ofs : m_header.dir_hash_table_ofs);
|
||||||
|
|
||||||
|
const uint64 hash_table_entry_count = hash_table_size / sizeof(uint32);
|
||||||
|
const uint64 hash_table_entry_offset = hash_table_ofs + (hash % hash_table_entry_count) * sizeof(uint32);
|
||||||
|
|
||||||
|
m_fileIn->SetPosition(hash_table_entry_offset);
|
||||||
|
uint32 tableOffset;
|
||||||
|
if (!m_fileIn->readU32(tableOffset))
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "failed to read WUHB hash table entry at file offset: {}", hash_table_entry_offset);
|
||||||
|
return ROMFS_ENTRY_EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint32be::from_bevalue(tableOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool T>
|
||||||
|
bool WUHBReader::SearchHashList(uint32& entryOffset, const fs::path& targetName) const
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (entryOffset == ROMFS_ENTRY_EMPTY)
|
||||||
|
return false;
|
||||||
|
auto entry = GetEntry<T>(entryOffset);
|
||||||
|
|
||||||
|
if (entry.name == targetName)
|
||||||
|
return true;
|
||||||
|
entryOffset = entry.hash;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 WUHBReader::Lookup(const std::filesystem::path& path, bool isFile) const
|
||||||
|
{
|
||||||
|
uint32 currentEntryOffset = 0;
|
||||||
|
auto look = [&](const fs::path& part, bool lookInFileHT) {
|
||||||
|
const auto partString = part.string();
|
||||||
|
currentEntryOffset = GetHashTableEntryOffset(CalcPathHash(currentEntryOffset, partString.c_str(), 0, partString.size()), lookInFileHT);
|
||||||
|
if (lookInFileHT)
|
||||||
|
return SearchHashList<true>(currentEntryOffset, part);
|
||||||
|
else
|
||||||
|
return SearchHashList<false>(currentEntryOffset, part);
|
||||||
|
};
|
||||||
|
// look for the root entry
|
||||||
|
if (!look("", false))
|
||||||
|
return ROMFS_ENTRY_EMPTY;
|
||||||
|
|
||||||
|
auto it = path.begin();
|
||||||
|
while (it != path.end())
|
||||||
|
{
|
||||||
|
fs::path part = *it;
|
||||||
|
++it;
|
||||||
|
// no need to recurse after trailing forward slash (e.g. directory/)
|
||||||
|
if (part.empty() && !isFile)
|
||||||
|
break;
|
||||||
|
// skip leading forward slash
|
||||||
|
if (part == "/")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// if the lookup target is a file and this is the last iteration, look in the file hash table instead.
|
||||||
|
if (!look(part, it == path.end() && isFile))
|
||||||
|
return ROMFS_ENTRY_EMPTY;
|
||||||
|
}
|
||||||
|
return currentEntryOffset;
|
||||||
|
}
|
||||||
|
bool WUHBReader::CheckMagicValue() const
|
||||||
|
{
|
||||||
|
uint8 magic[4];
|
||||||
|
m_fileIn->SetPosition(0);
|
||||||
|
int read = m_fileIn->readData(magic, 4);
|
||||||
|
if (read != 4)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Failed to read WUHB magic numbers");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
static_assert(sizeof(magic) == s_headerMagicValue.size());
|
||||||
|
return std::memcmp(&magic, s_headerMagicValue.data(), sizeof(magic)) == 0;
|
||||||
|
}
|
||||||
|
bool WUHBReader::ReadHeader()
|
||||||
|
{
|
||||||
|
m_fileIn->SetPosition(0);
|
||||||
|
auto read = m_fileIn->readData(&m_header, sizeof(m_header));
|
||||||
|
auto readSuccess = read == sizeof(m_header);
|
||||||
|
if (!readSuccess)
|
||||||
|
cemuLog_log(LogType::Force, "Failed to read WUHB header");
|
||||||
|
return readSuccess;
|
||||||
|
}
|
||||||
|
unsigned char WUHBReader::NormalizeChar(unsigned char c)
|
||||||
|
{
|
||||||
|
if (c >= 'a' && c <= 'z')
|
||||||
|
{
|
||||||
|
return c + 'A' - 'a';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint32 WUHBReader::CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len)
|
||||||
|
{
|
||||||
|
cemu_assert(path != nullptr || path_len == 0);
|
||||||
|
uint32 hash = parent ^ 123456789;
|
||||||
|
for (uint32 i = 0; i < path_len; i++)
|
||||||
|
{
|
||||||
|
hash = (hash >> 5) | (hash << 27);
|
||||||
|
hash ^= NormalizeChar(path[start + i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
45
src/Cafe/Filesystem/WUHB/WUHBReader.h
Normal file
45
src/Cafe/Filesystem/WUHB/WUHBReader.h
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
#pragma once
|
||||||
|
#include <Common/FileStream.h>
|
||||||
|
#include "RomFSStructs.h"
|
||||||
|
class WUHBReader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static WUHBReader* FromPath(const fs::path& path);
|
||||||
|
|
||||||
|
romfs_direntry_t GetDirEntry(uint32 offset) const;
|
||||||
|
romfs_fentry_t GetFileEntry(uint32 offset) const;
|
||||||
|
|
||||||
|
uint64 GetFileSize(uint32 entryOffset) const;
|
||||||
|
|
||||||
|
uint64 ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const;
|
||||||
|
|
||||||
|
uint32 Lookup(const std::filesystem::path& path, bool isFile) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
WUHBReader(FileStream* file)
|
||||||
|
: m_fileIn(file)
|
||||||
|
{
|
||||||
|
cemu_assert_debug(file != nullptr);
|
||||||
|
};
|
||||||
|
WUHBReader() = delete;
|
||||||
|
|
||||||
|
romfs_header_t m_header;
|
||||||
|
std::unique_ptr<FileStream> m_fileIn;
|
||||||
|
constexpr static std::string_view s_headerMagicValue = "WUHB";
|
||||||
|
bool ReadHeader();
|
||||||
|
bool CheckMagicValue() const;
|
||||||
|
|
||||||
|
static inline unsigned char NormalizeChar(unsigned char c);
|
||||||
|
static uint32 CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len);
|
||||||
|
|
||||||
|
template<bool File>
|
||||||
|
using EntryType = std::conditional_t<File, romfs_fentry_t, romfs_direntry_t>;
|
||||||
|
template<bool File>
|
||||||
|
static const EntryType<File>& GetFallback();
|
||||||
|
template<bool File>
|
||||||
|
EntryType<File> GetEntry(uint32 offset) const;
|
||||||
|
|
||||||
|
template<bool T>
|
||||||
|
bool SearchHashList(uint32& entryOffset, const fs::path& targetName) const;
|
||||||
|
uint32 GetHashTableEntryOffset(uint32 hash, bool isFile) const;
|
||||||
|
};
|
|
@ -212,9 +212,12 @@ bool FSCDeviceWUD_Mount(std::string_view mountPath, std::string_view destination
|
||||||
// wua device
|
// wua device
|
||||||
bool FSCDeviceWUA_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class ZArchiveReader* archive, sint32 priority);
|
bool FSCDeviceWUA_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class ZArchiveReader* archive, sint32 priority);
|
||||||
|
|
||||||
|
// wuhb device
|
||||||
|
bool FSCDeviceWUHB_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class WUHBReader* wuhbReader, sint32 priority);
|
||||||
|
|
||||||
// hostFS device
|
// hostFS device
|
||||||
bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority);
|
bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority);
|
||||||
|
|
||||||
// redirect device
|
// redirect device
|
||||||
void fscDeviceRedirect_map();
|
void fscDeviceRedirect_map();
|
||||||
void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& targetFilePath, sint32 priority);
|
void fscDeviceRedirect_add(std::string_view virtualSourcePath, size_t fileSize, const fs::path& targetFilePath, sint32 priority);
|
||||||
|
|
|
@ -11,7 +11,7 @@ struct RedirectEntry
|
||||||
|
|
||||||
FSAFileTree<RedirectEntry> redirectTree;
|
FSAFileTree<RedirectEntry> redirectTree;
|
||||||
|
|
||||||
void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& targetFilePath, sint32 priority)
|
void fscDeviceRedirect_add(std::string_view virtualSourcePath, size_t fileSize, const fs::path& targetFilePath, sint32 priority)
|
||||||
{
|
{
|
||||||
// check if source already has a redirection
|
// check if source already has a redirection
|
||||||
RedirectEntry* existingEntry;
|
RedirectEntry* existingEntry;
|
||||||
|
@ -24,7 +24,7 @@ void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& t
|
||||||
delete existingEntry;
|
delete existingEntry;
|
||||||
}
|
}
|
||||||
RedirectEntry* entry = new RedirectEntry(targetFilePath, priority);
|
RedirectEntry* entry = new RedirectEntry(targetFilePath, priority);
|
||||||
redirectTree.addFile(virtualSourcePath, entry);
|
redirectTree.addFile(virtualSourcePath, fileSize, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
class fscDeviceTypeRedirect : public fscDeviceC
|
class fscDeviceTypeRedirect : public fscDeviceC
|
||||||
|
@ -32,8 +32,15 @@ class fscDeviceTypeRedirect : public fscDeviceC
|
||||||
FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override
|
FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override
|
||||||
{
|
{
|
||||||
RedirectEntry* redirectionEntry;
|
RedirectEntry* redirectionEntry;
|
||||||
if (redirectTree.getFile(path, redirectionEntry))
|
|
||||||
|
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE) && redirectTree.getFile(path, redirectionEntry))
|
||||||
return FSCVirtualFile_Host::OpenFile(redirectionEntry->dstPath, accessFlags, *fscStatus);
|
return FSCVirtualFile_Host::OpenFile(redirectionEntry->dstPath, accessFlags, *fscStatus);
|
||||||
|
|
||||||
|
FSCVirtualFile* dirIterator;
|
||||||
|
|
||||||
|
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR) && redirectTree.getDirectory(path, dirIterator))
|
||||||
|
return dirIterator;
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,7 +133,7 @@ class fscDeviceWUDC : public fscDeviceC
|
||||||
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE))
|
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE))
|
||||||
{
|
{
|
||||||
FSTFileHandle fstFileHandle;
|
FSTFileHandle fstFileHandle;
|
||||||
if (mountedVolume->OpenFile(path, fstFileHandle, true))
|
if (mountedVolume->OpenFile(path, fstFileHandle, true) && !mountedVolume->HasLinkFlag(fstFileHandle))
|
||||||
{
|
{
|
||||||
*fscStatus = FSC_STATUS_OK;
|
*fscStatus = FSC_STATUS_OK;
|
||||||
return new FSCDeviceWudFileCtx(mountedVolume, fstFileHandle);
|
return new FSCDeviceWudFileCtx(mountedVolume, fstFileHandle);
|
||||||
|
@ -142,7 +142,7 @@ class fscDeviceWUDC : public fscDeviceC
|
||||||
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR))
|
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR))
|
||||||
{
|
{
|
||||||
FSTDirectoryIterator dirIterator;
|
FSTDirectoryIterator dirIterator;
|
||||||
if (mountedVolume->OpenDirectoryIterator(path, dirIterator))
|
if (mountedVolume->OpenDirectoryIterator(path, dirIterator) && !mountedVolume->HasLinkFlag(dirIterator.GetDirHandle()))
|
||||||
{
|
{
|
||||||
*fscStatus = FSC_STATUS_OK;
|
*fscStatus = FSC_STATUS_OK;
|
||||||
return new FSCDeviceWudFileCtx(mountedVolume, dirIterator);
|
return new FSCDeviceWudFileCtx(mountedVolume, dirIterator);
|
||||||
|
|
151
src/Cafe/Filesystem/fscDeviceWuhb.cpp
Normal file
151
src/Cafe/Filesystem/fscDeviceWuhb.cpp
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
#include "Filesystem/WUHB/WUHBReader.h"
|
||||||
|
#include "Cafe/Filesystem/fsc.h"
|
||||||
|
#include "Cafe/Filesystem/FST/FST.h"
|
||||||
|
|
||||||
|
class FSCDeviceWuhbFileCtx : public FSCVirtualFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FSCDeviceWuhbFileCtx(WUHBReader* reader, uint32 entryOffset, uint32 fscType)
|
||||||
|
: m_wuhbReader(reader), m_entryOffset(entryOffset), m_fscType(fscType)
|
||||||
|
{
|
||||||
|
cemu_assert(entryOffset != ROMFS_ENTRY_EMPTY);
|
||||||
|
if (fscType == FSC_TYPE_DIRECTORY)
|
||||||
|
{
|
||||||
|
romfs_direntry_t entry = reader->GetDirEntry(entryOffset);
|
||||||
|
m_dirIterOffset = entry.dirListHead;
|
||||||
|
m_fileIterOffset = entry.fileListHead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sint32 fscGetType() override
|
||||||
|
{
|
||||||
|
return m_fscType;
|
||||||
|
}
|
||||||
|
uint64 fscQueryValueU64(uint32 id) override
|
||||||
|
{
|
||||||
|
if (m_fscType == FSC_TYPE_FILE)
|
||||||
|
{
|
||||||
|
if (id == FSC_QUERY_SIZE)
|
||||||
|
return m_wuhbReader->GetFileSize(m_entryOffset);
|
||||||
|
else if (id == FSC_QUERY_WRITEABLE)
|
||||||
|
return 0; // WUHB images are read-only
|
||||||
|
else
|
||||||
|
cemu_assert_error();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cemu_assert_unimplemented();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
uint32 fscWriteData(void* buffer, uint32 size) override
|
||||||
|
{
|
||||||
|
cemu_assert_error();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
uint32 fscReadData(void* buffer, uint32 size) override
|
||||||
|
{
|
||||||
|
if (m_fscType != FSC_TYPE_FILE)
|
||||||
|
return 0;
|
||||||
|
auto read = m_wuhbReader->ReadFromFile(m_entryOffset, m_seek, size, buffer);
|
||||||
|
m_seek += read;
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
void fscSetSeek(uint64 seek) override
|
||||||
|
{
|
||||||
|
m_seek = seek;
|
||||||
|
}
|
||||||
|
uint64 fscGetSeek() override
|
||||||
|
{
|
||||||
|
if (m_fscType != FSC_TYPE_FILE)
|
||||||
|
return 0;
|
||||||
|
return m_seek;
|
||||||
|
}
|
||||||
|
void fscSetFileLength(uint64 endOffset) override
|
||||||
|
{
|
||||||
|
cemu_assert_error();
|
||||||
|
}
|
||||||
|
bool fscDirNext(FSCDirEntry* dirEntry) override
|
||||||
|
{
|
||||||
|
if (m_dirIterOffset != ROMFS_ENTRY_EMPTY)
|
||||||
|
{
|
||||||
|
romfs_direntry_t entry = m_wuhbReader->GetDirEntry(m_dirIterOffset);
|
||||||
|
m_dirIterOffset = entry.listNext;
|
||||||
|
if(entry.name_size > 0)
|
||||||
|
{
|
||||||
|
dirEntry->isDirectory = true;
|
||||||
|
dirEntry->isFile = false;
|
||||||
|
dirEntry->fileSize = 0;
|
||||||
|
std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_fileIterOffset != ROMFS_ENTRY_EMPTY)
|
||||||
|
{
|
||||||
|
romfs_fentry_t entry = m_wuhbReader->GetFileEntry(m_fileIterOffset);
|
||||||
|
m_fileIterOffset = entry.listNext;
|
||||||
|
if(entry.name_size > 0)
|
||||||
|
{
|
||||||
|
dirEntry->isDirectory = false;
|
||||||
|
dirEntry->isFile = true;
|
||||||
|
dirEntry->fileSize = entry.size;
|
||||||
|
std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
WUHBReader* m_wuhbReader{};
|
||||||
|
uint32 m_fscType;
|
||||||
|
uint32 m_entryOffset = ROMFS_ENTRY_EMPTY;
|
||||||
|
uint32 m_dirIterOffset = ROMFS_ENTRY_EMPTY;
|
||||||
|
uint32 m_fileIterOffset = ROMFS_ENTRY_EMPTY;
|
||||||
|
uint64 m_seek = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class fscDeviceWUHB : public fscDeviceC
|
||||||
|
{
|
||||||
|
FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override
|
||||||
|
{
|
||||||
|
WUHBReader* reader = (WUHBReader*)ctx;
|
||||||
|
cemu_assert_debug(!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION)); // writing to WUHB is not supported
|
||||||
|
|
||||||
|
bool isFile;
|
||||||
|
uint32 table_offset = ROMFS_ENTRY_EMPTY;
|
||||||
|
|
||||||
|
if (table_offset == ROMFS_ENTRY_EMPTY && HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR))
|
||||||
|
{
|
||||||
|
table_offset = reader->Lookup(path, false);
|
||||||
|
isFile = false;
|
||||||
|
}
|
||||||
|
if (table_offset == ROMFS_ENTRY_EMPTY && HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE))
|
||||||
|
{
|
||||||
|
table_offset = reader->Lookup(path, true);
|
||||||
|
isFile = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (table_offset == ROMFS_ENTRY_EMPTY)
|
||||||
|
{
|
||||||
|
*fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
*fscStatus = FSC_STATUS_OK;
|
||||||
|
return new FSCDeviceWuhbFileCtx(reader, table_offset, isFile ? FSC_TYPE_FILE : FSC_TYPE_DIRECTORY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// singleton
|
||||||
|
public:
|
||||||
|
static fscDeviceWUHB& instance()
|
||||||
|
{
|
||||||
|
static fscDeviceWUHB _instance;
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool FSCDeviceWUHB_Mount(std::string_view mountPath, std::string_view destinationBaseDir, WUHBReader* wuhbReader, sint32 priority)
|
||||||
|
{
|
||||||
|
return fsc_mount(mountPath, destinationBaseDir, &fscDeviceWUHB::instance(), wuhbReader, priority) == FSC_STATUS_OK;
|
||||||
|
}
|
|
@ -52,7 +52,7 @@ typedef struct
|
||||||
void hleExport_xcx_enterCriticalSection(PPCInterpreter_t* hCPU)
|
void hleExport_xcx_enterCriticalSection(PPCInterpreter_t* hCPU)
|
||||||
{
|
{
|
||||||
ppcDefineParamStructPtr(xcxCS, xcxCS_t, 0);
|
ppcDefineParamStructPtr(xcxCS, xcxCS_t, 0);
|
||||||
uint32 threadId = coreinitThread_getCurrentThreadMPTRDepr(hCPU);
|
uint32 threadId = MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR();
|
||||||
cemu_assert_debug(xcxCS->ukn08 != 0);
|
cemu_assert_debug(xcxCS->ukn08 != 0);
|
||||||
cemu_assert_debug(threadId);
|
cemu_assert_debug(threadId);
|
||||||
if (xcxCS->ownerThreadId == (uint32be)threadId)
|
if (xcxCS->ownerThreadId == (uint32be)threadId)
|
||||||
|
|
|
@ -140,7 +140,7 @@ bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, T&
|
||||||
for(const T& v : T())
|
for(const T& v : T())
|
||||||
{
|
{
|
||||||
// test integer option
|
// test integer option
|
||||||
if (boost::iequals(fmt::format("{}", static_cast<typename std::underlying_type<T>::type>(v)), *option_value))
|
if (boost::iequals(fmt::format("{}", fmt::underlying(v)), *option_value))
|
||||||
{
|
{
|
||||||
option = v;
|
option = v;
|
||||||
return true;
|
return true;
|
||||||
|
@ -209,7 +209,7 @@ bool GameProfile::Load(uint64_t title_id)
|
||||||
m_gameName = std::string(game_name.begin(), game_name.end());
|
m_gameName = std::string(game_name.begin(), game_name.end());
|
||||||
trim(m_gameName.value());
|
trim(m_gameName.value());
|
||||||
}
|
}
|
||||||
IniParser iniParser(*profileContents, gameProfilePath.string());
|
IniParser iniParser(*profileContents, _pathToUtf8(gameProfilePath));
|
||||||
// parse ini
|
// parse ini
|
||||||
while (iniParser.NextSection())
|
while (iniParser.NextSection())
|
||||||
{
|
{
|
||||||
|
|
|
@ -28,7 +28,7 @@ void GraphicPack2::LoadGraphicPack(fs::path graphicPackPath)
|
||||||
return;
|
return;
|
||||||
std::vector<uint8> rulesData;
|
std::vector<uint8> rulesData;
|
||||||
fs_rules->extract(rulesData);
|
fs_rules->extract(rulesData);
|
||||||
IniParser iniParser(rulesData, rulesPath.string());
|
IniParser iniParser(rulesData, _pathToUtf8(rulesPath));
|
||||||
|
|
||||||
if (!iniParser.NextSection())
|
if (!iniParser.NextSection())
|
||||||
{
|
{
|
||||||
|
@ -51,10 +51,9 @@ void GraphicPack2::LoadGraphicPack(fs::path graphicPackPath)
|
||||||
cemuLog_log(LogType::Force, "{}: Unable to parse version", _pathToUtf8(rulesPath));
|
cemuLog_log(LogType::Force, "{}: Unable to parse version", _pathToUtf8(rulesPath));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (versionNum > GP_LEGACY_VERSION)
|
if (versionNum > GP_LEGACY_VERSION)
|
||||||
{
|
{
|
||||||
GraphicPack2::LoadGraphicPack(rulesPath.generic_wstring(), iniParser);
|
GraphicPack2::LoadGraphicPack(rulesPath, iniParser);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,22 +78,22 @@ void GraphicPack2::LoadAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicPack2::LoadGraphicPack(const std::wstring& filename, IniParser& rules)
|
bool GraphicPack2::LoadGraphicPack(const fs::path& rulesPath, IniParser& rules)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
auto gp = std::make_shared<GraphicPack2>(filename, rules);
|
auto gp = std::make_shared<GraphicPack2>(rulesPath, rules);
|
||||||
|
|
||||||
// check if enabled and preset set
|
// check if enabled and preset set
|
||||||
const auto& config_entries = g_config.data().graphic_pack_entries;
|
const auto& config_entries = g_config.data().graphic_pack_entries;
|
||||||
|
|
||||||
// legacy absolute path checking for not breaking compatibility
|
// legacy absolute path checking for not breaking compatibility
|
||||||
auto file = gp->GetFilename2();
|
auto file = gp->GetRulesPath();
|
||||||
auto it = config_entries.find(file.lexically_normal());
|
auto it = config_entries.find(file.lexically_normal());
|
||||||
if (it == config_entries.cend())
|
if (it == config_entries.cend())
|
||||||
{
|
{
|
||||||
// check for relative path
|
// check for relative path
|
||||||
it = config_entries.find(MakeRelativePath(ActiveSettings::GetUserDataPath(), gp->GetFilename2()).lexically_normal());
|
it = config_entries.find(_utf8ToPath(gp->GetNormalizedPathString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it != config_entries.cend())
|
if (it != config_entries.cend())
|
||||||
|
@ -145,7 +144,7 @@ bool GraphicPack2::DeactivateGraphicPack(const std::shared_ptr<GraphicPack2>& gr
|
||||||
const auto it = std::find_if(s_active_graphic_packs.begin(), s_active_graphic_packs.end(),
|
const auto it = std::find_if(s_active_graphic_packs.begin(), s_active_graphic_packs.end(),
|
||||||
[graphic_pack](const GraphicPackPtr& gp)
|
[graphic_pack](const GraphicPackPtr& gp)
|
||||||
{
|
{
|
||||||
return gp->GetFilename() == graphic_pack->GetFilename();
|
return gp->GetNormalizedPathString() == graphic_pack->GetNormalizedPathString();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -173,12 +172,12 @@ void GraphicPack2::ActivateForCurrentTitle()
|
||||||
{
|
{
|
||||||
if (gp->GetPresets().empty())
|
if (gp->GetPresets().empty())
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Activate graphic pack: {}", gp->GetPath());
|
cemuLog_log(LogType::Force, "Activate graphic pack: {}", gp->GetVirtualPath());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::string logLine;
|
std::string logLine;
|
||||||
logLine.assign(fmt::format("Activate graphic pack: {} [Presets: ", gp->GetPath()));
|
logLine.assign(fmt::format("Activate graphic pack: {} [Presets: ", gp->GetVirtualPath()));
|
||||||
bool isFirst = true;
|
bool isFirst = true;
|
||||||
for (auto& itr : gp->GetPresets())
|
for (auto& itr : gp->GetPresets())
|
||||||
{
|
{
|
||||||
|
@ -216,12 +215,6 @@ void GraphicPack2::WaitUntilReady()
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphicPack2::GraphicPack2(std::wstring filename)
|
|
||||||
: m_filename(std::move(filename))
|
|
||||||
{
|
|
||||||
// unused for now
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unordered_map<std::string, GraphicPack2::PresetVar> GraphicPack2::ParsePresetVars(IniParser& rules) const
|
std::unordered_map<std::string, GraphicPack2::PresetVar> GraphicPack2::ParsePresetVars(IniParser& rules) const
|
||||||
{
|
{
|
||||||
ExpressionParser parser;
|
ExpressionParser parser;
|
||||||
|
@ -255,8 +248,8 @@ std::unordered_map<std::string, GraphicPack2::PresetVar> GraphicPack2::ParsePres
|
||||||
return vars;
|
return vars;
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules)
|
GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
|
||||||
: m_filename(std::move(filename))
|
: m_rulesPath(std::move(rulesPath))
|
||||||
{
|
{
|
||||||
// we're already in [Definition]
|
// we're already in [Definition]
|
||||||
auto option_version = rules.FindOption("version");
|
auto option_version = rules.FindOption("version");
|
||||||
|
@ -265,7 +258,7 @@ GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules)
|
||||||
m_version = StringHelpers::ToInt(*option_version, -1);
|
m_version = StringHelpers::ToInt(*option_version, -1);
|
||||||
if (m_version < 0)
|
if (m_version < 0)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, L"{}: Invalid version", m_filename);
|
cemuLog_log(LogType::Force, "{}: Invalid version", _pathToUtf8(m_rulesPath));
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,6 +280,10 @@ GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules)
|
||||||
m_enabled = m_default_enabled;
|
m_enabled = m_default_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto option_allowRendertargetSizeOptimization = rules.FindOption("colorbufferOptimizationAware");
|
||||||
|
if (option_allowRendertargetSizeOptimization)
|
||||||
|
m_allowRendertargetSizeOptimization = boost::iequals(*option_allowRendertargetSizeOptimization, "true") || boost::iequals(*option_allowRendertargetSizeOptimization, "1");
|
||||||
|
|
||||||
auto option_vendorFilter = rules.FindOption("vendorFilter");
|
auto option_vendorFilter = rules.FindOption("vendorFilter");
|
||||||
if (option_vendorFilter)
|
if (option_vendorFilter)
|
||||||
{
|
{
|
||||||
|
@ -311,7 +308,7 @@ GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules)
|
||||||
cemuLog_log(LogType::Force, "[Definition] section from '{}' graphic pack must contain option: path", gp_name_log.has_value() ? *gp_name_log : "Unknown");
|
cemuLog_log(LogType::Force, "[Definition] section from '{}' graphic pack must contain option: path", gp_name_log.has_value() ? *gp_name_log : "Unknown");
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
m_path = *option_path;
|
m_virtualPath = *option_path;
|
||||||
|
|
||||||
auto option_gp_name = rules.FindOption("name");
|
auto option_gp_name = rules.FindOption("name");
|
||||||
if (option_gp_name)
|
if (option_gp_name)
|
||||||
|
@ -348,7 +345,7 @@ GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules)
|
||||||
const auto preset_name = rules.FindOption("name");
|
const auto preset_name = rules.FindOption("name");
|
||||||
if (!preset_name)
|
if (!preset_name)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Preset in line {} skipped because it has no name option defined", m_name, rules.GetCurrentSectionLineNumber());
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Preset in line {} skipped because it has no name option defined", GetNormalizedPathString(), rules.GetCurrentSectionLineNumber());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,7 +369,7 @@ GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules)
|
||||||
}
|
}
|
||||||
catch (const std::exception & ex)
|
catch (const std::exception & ex)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Can't parse preset \"{}\": {}", m_name, *preset_name, ex.what());
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Can't parse preset \"{}\": {}", GetNormalizedPathString(), *preset_name, ex.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (boost::iequals(currentSectionName, "RAM"))
|
else if (boost::iequals(currentSectionName, "RAM"))
|
||||||
|
@ -386,7 +383,7 @@ GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules)
|
||||||
{
|
{
|
||||||
if (m_version <= 5)
|
if (m_version <= 5)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack \"{}\": [RAM] options are only available for graphic pack version 6 or higher", m_name, optionNameBuf);
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": [RAM] options are only available for graphic pack version 6 or higher", GetNormalizedPathString(), optionNameBuf);
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -396,12 +393,12 @@ GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules)
|
||||||
{
|
{
|
||||||
if (addrEnd <= addrStart)
|
if (addrEnd <= addrStart)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack \"{}\": start address (0x{:08x}) must be greater than end address (0x{:08x}) for {}", m_name, addrStart, addrEnd, optionNameBuf);
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": start address (0x{:08x}) must be greater than end address (0x{:08x}) for {}", GetNormalizedPathString(), addrStart, addrEnd, optionNameBuf);
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
else if ((addrStart & 0xFFF) != 0 || (addrEnd & 0xFFF) != 0)
|
else if ((addrStart & 0xFFF) != 0 || (addrEnd & 0xFFF) != 0)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack \"{}\": addresses for %s are not aligned to 0x1000", m_name, optionNameBuf);
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": addresses for %s are not aligned to 0x1000", GetNormalizedPathString(), optionNameBuf);
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -411,7 +408,7 @@ GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack \"{}\": has invalid syntax for option {}", m_name, optionNameBuf);
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": has invalid syntax for option {}", GetNormalizedPathString(), optionNameBuf);
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -425,22 +422,30 @@ GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules)
|
||||||
std::unordered_map<std::string, std::vector<PresetPtr>> tmp_map;
|
std::unordered_map<std::string, std::vector<PresetPtr>> tmp_map;
|
||||||
|
|
||||||
// all vars must be defined in the default preset vars before
|
// all vars must be defined in the default preset vars before
|
||||||
for (const auto& entry : m_presets)
|
std::vector<std::pair<std::string, std::string>> mismatchingPresetVars;
|
||||||
|
for (const auto& presetEntry : m_presets)
|
||||||
{
|
{
|
||||||
tmp_map[entry->category].emplace_back(entry);
|
tmp_map[presetEntry->category].emplace_back(presetEntry);
|
||||||
|
|
||||||
for (auto& kv : entry->variables)
|
for (auto& presetVar : presetEntry->variables)
|
||||||
{
|
{
|
||||||
const auto it = m_preset_vars.find(kv.first);
|
const auto it = m_preset_vars.find(presetVar.first);
|
||||||
if (it == m_preset_vars.cend())
|
if (it == m_preset_vars.cend())
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains preset variables which are not defined in the default section", m_name);
|
mismatchingPresetVars.emplace_back(presetEntry->name, presetVar.first);
|
||||||
throw std::exception();
|
continue;
|
||||||
|
}
|
||||||
|
// overwrite var type with default var type
|
||||||
|
presetVar.second.first = it->second.first;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// overwrite var type with default var type
|
if(!mismatchingPresetVars.empty())
|
||||||
kv.second.first = it->second.first;
|
{
|
||||||
}
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\" contains preset variables which are not defined in the [Default] section:", GetNormalizedPathString());
|
||||||
|
for (const auto& [presetName, varName] : mismatchingPresetVars)
|
||||||
|
cemuLog_log(LogType::Force, "Preset: {} Variable: {}", presetName, varName);
|
||||||
|
throw std::exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
// have first entry be default active for every category if no default= is set
|
// have first entry be default active for every category if no default= is set
|
||||||
|
@ -472,7 +477,7 @@ GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules)
|
||||||
auto& p2 = kv.second[i + 1];
|
auto& p2 = kv.second[i + 1];
|
||||||
if (p1->variables.size() != p2->variables.size())
|
if (p1->variables.size() != p2->variables.size())
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", m_name);
|
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", GetNormalizedPathString());
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -480,14 +485,14 @@ GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules)
|
||||||
std::set<std::string> keys2(get_keys(p2->variables).begin(), get_keys(p2->variables).end());
|
std::set<std::string> keys2(get_keys(p2->variables).begin(), get_keys(p2->variables).end());
|
||||||
if (keys1 != keys2)
|
if (keys1 != keys2)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", m_name);
|
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", GetNormalizedPathString());
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(p1->is_default)
|
if(p1->is_default)
|
||||||
{
|
{
|
||||||
if(has_default)
|
if(has_default)
|
||||||
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" has more than one preset with the default key set for the same category \"{}\"", m_name, p1->name);
|
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" has more than one preset with the default key set for the same category \"{}\"", GetNormalizedPathString(), p1->name);
|
||||||
p1->active = true;
|
p1->active = true;
|
||||||
has_default = true;
|
has_default = true;
|
||||||
}
|
}
|
||||||
|
@ -514,6 +519,11 @@ bool GraphicPack2::Reload()
|
||||||
return Activate();
|
return Activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string GraphicPack2::GetNormalizedPathString() const
|
||||||
|
{
|
||||||
|
return _pathToUtf8(MakeRelativePath(ActiveSettings::GetUserDataPath(), GetRulesPath()).lexically_normal());
|
||||||
|
}
|
||||||
|
|
||||||
bool GraphicPack2::ContainsTitleId(uint64_t title_id) const
|
bool GraphicPack2::ContainsTitleId(uint64_t title_id) const
|
||||||
{
|
{
|
||||||
const auto it = std::find_if(m_title_ids.begin(), m_title_ids.end(), [title_id](uint64 id) { return id == title_id; });
|
const auto it = std::find_if(m_title_ids.begin(), m_title_ids.end(), [title_id](uint64 id) { return id == title_id; });
|
||||||
|
@ -656,7 +666,7 @@ bool GraphicPack2::SetActivePreset(std::string_view category, std::string_view n
|
||||||
|
|
||||||
void GraphicPack2::LoadShaders()
|
void GraphicPack2::LoadShaders()
|
||||||
{
|
{
|
||||||
fs::path path(m_filename);
|
fs::path path = GetRulesPath();
|
||||||
for (auto& it : fs::directory_iterator(path.remove_filename()))
|
for (auto& it : fs::directory_iterator(path.remove_filename()))
|
||||||
{
|
{
|
||||||
if (!is_regular_file(it))
|
if (!is_regular_file(it))
|
||||||
|
@ -682,7 +692,7 @@ void GraphicPack2::LoadShaders()
|
||||||
{
|
{
|
||||||
std::ifstream file(p);
|
std::ifstream file(p);
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
throw std::runtime_error(fmt::format("can't open graphic pack file: {}", p.filename().string()).c_str());
|
throw std::runtime_error(fmt::format("can't open graphic pack file: {}", _pathToUtf8(p.filename())));
|
||||||
|
|
||||||
file.seekg(0, std::ios::end);
|
file.seekg(0, std::ios::end);
|
||||||
m_output_shader_source.reserve(file.tellg());
|
m_output_shader_source.reserve(file.tellg());
|
||||||
|
@ -695,7 +705,7 @@ void GraphicPack2::LoadShaders()
|
||||||
{
|
{
|
||||||
std::ifstream file(p);
|
std::ifstream file(p);
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
throw std::runtime_error(fmt::format("can't open graphic pack file: {}", p.filename().string()).c_str());
|
throw std::runtime_error(fmt::format("can't open graphic pack file: {}", _pathToUtf8(p.filename())));
|
||||||
|
|
||||||
file.seekg(0, std::ios::end);
|
file.seekg(0, std::ios::end);
|
||||||
m_upscaling_shader_source.reserve(file.tellg());
|
m_upscaling_shader_source.reserve(file.tellg());
|
||||||
|
@ -708,7 +718,7 @@ void GraphicPack2::LoadShaders()
|
||||||
{
|
{
|
||||||
std::ifstream file(p);
|
std::ifstream file(p);
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
throw std::runtime_error(fmt::format("can't open graphic pack file: {}", p.filename().string()).c_str());
|
throw std::runtime_error(fmt::format("can't open graphic pack file: {}", _pathToUtf8(p.filename())));
|
||||||
|
|
||||||
file.seekg(0, std::ios::end);
|
file.seekg(0, std::ios::end);
|
||||||
m_downscaling_shader_source.reserve(file.tellg());
|
m_downscaling_shader_source.reserve(file.tellg());
|
||||||
|
@ -811,7 +821,7 @@ void GraphicPack2::AddConstantsForCurrentPreset(ExpressionParser& ep)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, std::wstring& internalPath, bool isAOC)
|
void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, bool isAOC)
|
||||||
{
|
{
|
||||||
uint64 currentTitleId = CafeSystem::GetForegroundTitleId();
|
uint64 currentTitleId = CafeSystem::GetForegroundTitleId();
|
||||||
uint64 aocTitleId = (currentTitleId & 0xFFFFFFFFull) | 0x0005000c00000000ull;
|
uint64 aocTitleId = (currentTitleId & 0xFFFFFFFFull) | 0x0005000c00000000ull;
|
||||||
|
@ -828,7 +838,7 @@ void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, std::wstri
|
||||||
{
|
{
|
||||||
virtualMountPath = fs::path("vol/content/") / virtualMountPath;
|
virtualMountPath = fs::path("vol/content/") / virtualMountPath;
|
||||||
}
|
}
|
||||||
fscDeviceRedirect_add(virtualMountPath.generic_string(), it.path().generic_string(), m_fs_priority);
|
fscDeviceRedirect_add(virtualMountPath.generic_string(), it.file_size(), it.path().generic_string(), m_fs_priority);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -839,7 +849,7 @@ void GraphicPack2::LoadReplacedFiles()
|
||||||
return;
|
return;
|
||||||
m_patchedFilesLoaded = true;
|
m_patchedFilesLoaded = true;
|
||||||
|
|
||||||
fs::path gfxPackPath(m_filename.c_str());
|
fs::path gfxPackPath = GetRulesPath();
|
||||||
gfxPackPath = gfxPackPath.remove_filename();
|
gfxPackPath = gfxPackPath.remove_filename();
|
||||||
|
|
||||||
// /content/
|
// /content/
|
||||||
|
@ -849,10 +859,9 @@ void GraphicPack2::LoadReplacedFiles()
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
if (fs::exists(contentPath, ec))
|
if (fs::exists(contentPath, ec))
|
||||||
{
|
{
|
||||||
std::wstring internalPath(L"/vol/content/");
|
|
||||||
// setup redirections
|
// setup redirections
|
||||||
fscDeviceRedirect_map();
|
fscDeviceRedirect_map();
|
||||||
_iterateReplacedFiles(contentPath, internalPath, false);
|
_iterateReplacedFiles(contentPath, false);
|
||||||
}
|
}
|
||||||
// /aoc/
|
// /aoc/
|
||||||
fs::path aocPath(gfxPackPath);
|
fs::path aocPath(gfxPackPath);
|
||||||
|
@ -863,13 +872,9 @@ void GraphicPack2::LoadReplacedFiles()
|
||||||
uint64 aocTitleId = CafeSystem::GetForegroundTitleId();
|
uint64 aocTitleId = CafeSystem::GetForegroundTitleId();
|
||||||
aocTitleId = aocTitleId & 0xFFFFFFFFULL;
|
aocTitleId = aocTitleId & 0xFFFFFFFFULL;
|
||||||
aocTitleId |= 0x0005000c00000000ULL;
|
aocTitleId |= 0x0005000c00000000ULL;
|
||||||
wchar_t internalAocPath[128];
|
|
||||||
swprintf(internalAocPath, sizeof(internalAocPath)/sizeof(wchar_t), L"/aoc/%016llx/", aocTitleId);
|
|
||||||
|
|
||||||
std::wstring internalPath(internalAocPath);
|
|
||||||
// setup redirections
|
// setup redirections
|
||||||
fscDeviceRedirect_map();
|
fscDeviceRedirect_map();
|
||||||
_iterateReplacedFiles(aocPath, internalPath, true);
|
_iterateReplacedFiles(aocPath, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -885,21 +890,18 @@ bool GraphicPack2::Activate()
|
||||||
if (m_gfx_vendor.has_value())
|
if (m_gfx_vendor.has_value())
|
||||||
{
|
{
|
||||||
auto vendor = g_renderer->GetVendor();
|
auto vendor = g_renderer->GetVendor();
|
||||||
if (vendor == GfxVendor::IntelLegacy || vendor == GfxVendor::IntelNoLegacy)
|
|
||||||
vendor = GfxVendor::Intel;
|
|
||||||
|
|
||||||
if (m_gfx_vendor.value() != vendor)
|
if (m_gfx_vendor.value() != vendor)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileStream* fs_rules = FileStream::openFile2({ m_filename });
|
FileStream* fs_rules = FileStream::openFile2(m_rulesPath);
|
||||||
if (!fs_rules)
|
if (!fs_rules)
|
||||||
return false;
|
return false;
|
||||||
std::vector<uint8> rulesData;
|
std::vector<uint8> rulesData;
|
||||||
fs_rules->extract(rulesData);
|
fs_rules->extract(rulesData);
|
||||||
delete fs_rules;
|
delete fs_rules;
|
||||||
|
|
||||||
IniParser rules({ (char*)rulesData.data(), rulesData.size()}, boost::nowide::narrow(m_filename));
|
IniParser rules({ (char*)rulesData.data(), rulesData.size()}, GetNormalizedPathString());
|
||||||
|
|
||||||
// load rules
|
// load rules
|
||||||
try
|
try
|
||||||
|
@ -953,7 +955,7 @@ bool GraphicPack2::Activate()
|
||||||
else if (anisotropyValue == 16)
|
else if (anisotropyValue == 16)
|
||||||
rule.overwrite_settings.anistropic_value = 4;
|
rule.overwrite_settings.anistropic_value = 4;
|
||||||
else
|
else
|
||||||
cemuLog_log(LogType::Force, fmt::format(L"Invalid value {} for overwriteAnisotropy in graphic pack {}. Only the values 1, 2, 4, 8 or 16 are allowed.", anisotropyValue, m_filename));
|
cemuLog_log(LogType::Force, "Invalid value {} for overwriteAnisotropy in graphic pack {}. Only the values 1, 2, 4, 8 or 16 are allowed.", anisotropyValue, GetNormalizedPathString());
|
||||||
}
|
}
|
||||||
m_texture_rules.emplace_back(rule);
|
m_texture_rules.emplace_back(rule);
|
||||||
}
|
}
|
||||||
|
@ -966,7 +968,7 @@ bool GraphicPack2::Activate()
|
||||||
auto option_upscale = rules.FindOption("upscaleMagFilter");
|
auto option_upscale = rules.FindOption("upscaleMagFilter");
|
||||||
if(option_upscale && boost::iequals(*option_upscale, "NearestNeighbor"))
|
if(option_upscale && boost::iequals(*option_upscale, "NearestNeighbor"))
|
||||||
m_output_settings.upscale_filter = LatteTextureView::MagFilter::kNearestNeighbor;
|
m_output_settings.upscale_filter = LatteTextureView::MagFilter::kNearestNeighbor;
|
||||||
auto option_downscale = rules.FindOption("NearestNeighbor");
|
auto option_downscale = rules.FindOption("downscaleMinFilter");
|
||||||
if (option_downscale && boost::iequals(*option_downscale, "NearestNeighbor"))
|
if (option_downscale && boost::iequals(*option_downscale, "NearestNeighbor"))
|
||||||
m_output_settings.downscale_filter = LatteTextureView::MagFilter::kNearestNeighbor;
|
m_output_settings.downscale_filter = LatteTextureView::MagFilter::kNearestNeighbor;
|
||||||
}
|
}
|
||||||
|
@ -998,11 +1000,11 @@ bool GraphicPack2::Activate()
|
||||||
if (LatteTiming_getCustomVsyncFrequency(globalCustomVsyncFreq))
|
if (LatteTiming_getCustomVsyncFrequency(globalCustomVsyncFreq))
|
||||||
{
|
{
|
||||||
if (customVsyncFreq != globalCustomVsyncFreq)
|
if (customVsyncFreq != globalCustomVsyncFreq)
|
||||||
cemuLog_log(LogType::Force, "rules.txt error: Mismatching vsync frequency {} in graphic pack \'{}\'", customVsyncFreq, GetPath());
|
cemuLog_log(LogType::Force, "rules.txt error: Mismatching vsync frequency {} in graphic pack \'{}\'", customVsyncFreq, GetVirtualPath());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Set vsync frequency to {} (graphic pack {})", customVsyncFreq, GetPath());
|
cemuLog_log(LogType::Force, "Set vsync frequency to {} (graphic pack {})", customVsyncFreq, GetVirtualPath());
|
||||||
LatteTiming_setCustomVsyncFrequency(customVsyncFreq);
|
LatteTiming_setCustomVsyncFrequency(customVsyncFreq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,23 +97,23 @@ public:
|
||||||
};
|
};
|
||||||
using PresetPtr = std::shared_ptr<Preset>;
|
using PresetPtr = std::shared_ptr<Preset>;
|
||||||
|
|
||||||
GraphicPack2(std::wstring filename);
|
GraphicPack2(fs::path rulesPath, IniParser& rules);
|
||||||
GraphicPack2(std::wstring filename, IniParser& rules);
|
|
||||||
|
|
||||||
bool IsEnabled() const { return m_enabled; }
|
bool IsEnabled() const { return m_enabled; }
|
||||||
bool IsActivated() const { return m_activated; }
|
bool IsActivated() const { return m_activated; }
|
||||||
sint32 GetVersion() const { return m_version; }
|
sint32 GetVersion() const { return m_version; }
|
||||||
const std::wstring& GetFilename() const { return m_filename; }
|
const fs::path GetRulesPath() const { return m_rulesPath; }
|
||||||
const fs::path GetFilename2() const { return fs::path(m_filename); }
|
std::string GetNormalizedPathString() const;
|
||||||
bool RequiresRestart(bool changeEnableState, bool changePreset);
|
bool RequiresRestart(bool changeEnableState, bool changePreset);
|
||||||
bool Reload();
|
bool Reload();
|
||||||
|
|
||||||
bool HasName() const { return !m_name.empty(); }
|
bool HasName() const { return !m_name.empty(); }
|
||||||
|
|
||||||
const std::string& GetName() const { return m_name.empty() ? m_path : m_name; }
|
const std::string& GetName() const { return m_name.empty() ? m_virtualPath : m_name; }
|
||||||
const std::string& GetPath() const { return m_path; }
|
const std::string& GetVirtualPath() const { return m_virtualPath; } // returns the path in the gfx tree hierarchy
|
||||||
const std::string& GetDescription() const { return m_description; }
|
const std::string& GetDescription() const { return m_description; }
|
||||||
bool IsDefaultEnabled() const { return m_default_enabled; }
|
bool IsDefaultEnabled() const { return m_default_enabled; }
|
||||||
|
bool AllowRendertargetSizeOptimization() const { return m_allowRendertargetSizeOptimization; }
|
||||||
|
|
||||||
void SetEnabled(bool state) { m_enabled = state; }
|
void SetEnabled(bool state) { m_enabled = state; }
|
||||||
|
|
||||||
|
@ -165,7 +165,7 @@ public:
|
||||||
static const std::vector<std::shared_ptr<GraphicPack2>>& GetGraphicPacks() { return s_graphic_packs; }
|
static const std::vector<std::shared_ptr<GraphicPack2>>& GetGraphicPacks() { return s_graphic_packs; }
|
||||||
static const std::vector<std::shared_ptr<GraphicPack2>>& GetActiveGraphicPacks() { return s_active_graphic_packs; }
|
static const std::vector<std::shared_ptr<GraphicPack2>>& GetActiveGraphicPacks() { return s_active_graphic_packs; }
|
||||||
static void LoadGraphicPack(fs::path graphicPackPath);
|
static void LoadGraphicPack(fs::path graphicPackPath);
|
||||||
static bool LoadGraphicPack(const std::wstring& filename, class IniParser& rules);
|
static bool LoadGraphicPack(const fs::path& rulesPath, class IniParser& rules);
|
||||||
static bool ActivateGraphicPack(const std::shared_ptr<GraphicPack2>& graphic_pack);
|
static bool ActivateGraphicPack(const std::shared_ptr<GraphicPack2>& graphic_pack);
|
||||||
static bool DeactivateGraphicPack(const std::shared_ptr<GraphicPack2>& graphic_pack);
|
static bool DeactivateGraphicPack(const std::shared_ptr<GraphicPack2>& graphic_pack);
|
||||||
static void ClearGraphicPacks();
|
static void ClearGraphicPacks();
|
||||||
|
@ -209,15 +209,17 @@ private:
|
||||||
parser.TryAddConstant(var.first, (TType)var.second.second);
|
parser.TryAddConstant(var.first, (TType)var.second.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::wstring m_filename;
|
fs::path m_rulesPath;
|
||||||
|
|
||||||
sint32 m_version;
|
sint32 m_version;
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
std::string m_path;
|
std::string m_virtualPath;
|
||||||
std::string m_description;
|
std::string m_description;
|
||||||
|
|
||||||
bool m_default_enabled = false;
|
bool m_default_enabled = false;
|
||||||
|
|
||||||
|
bool m_allowRendertargetSizeOptimization = false; // gfx pack supports framebuffers with non-padded sizes, which is an optional optimization introduced with Cemu 2.0-74
|
||||||
|
|
||||||
// filter
|
// filter
|
||||||
std::optional<RendererAPI> m_renderer_api;
|
std::optional<RendererAPI> m_renderer_api;
|
||||||
std::optional<GfxVendor> m_gfx_vendor;
|
std::optional<GfxVendor> m_gfx_vendor;
|
||||||
|
@ -258,7 +260,7 @@ private:
|
||||||
CustomShader LoadShader(const fs::path& path, uint64 shader_base_hash, uint64 shader_aux_hash, GP_SHADER_TYPE shader_type) const;
|
CustomShader LoadShader(const fs::path& path, uint64 shader_base_hash, uint64 shader_aux_hash, GP_SHADER_TYPE shader_type) const;
|
||||||
void ApplyShaderPresets(std::string& shader_source) const;
|
void ApplyShaderPresets(std::string& shader_source) const;
|
||||||
void LoadReplacedFiles();
|
void LoadReplacedFiles();
|
||||||
void _iterateReplacedFiles(const fs::path& currentPath, std::wstring& internalPath, bool isAOC);
|
void _iterateReplacedFiles(const fs::path& currentPath, bool isAOC);
|
||||||
|
|
||||||
// ram mappings
|
// ram mappings
|
||||||
std::vector<std::pair<MPTR, MPTR>> m_ramMappings;
|
std::vector<std::pair<MPTR, MPTR>> m_ramMappings;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "boost/algorithm/string.hpp"
|
#include "boost/algorithm/string.hpp"
|
||||||
|
|
||||||
#include "gui/wxgui.h" // for wxMessageBox
|
#include "gui/wxgui.h" // for wxMessageBox
|
||||||
|
#include "gui/helpers/wxHelpers.h"
|
||||||
|
|
||||||
// error handler
|
// error handler
|
||||||
void PatchErrorHandler::printError(class PatchGroup* patchGroup, sint32 lineNumber, std::string_view errorMsg)
|
void PatchErrorHandler::printError(class PatchGroup* patchGroup, sint32 lineNumber, std::string_view errorMsg)
|
||||||
|
@ -39,13 +40,13 @@ void PatchErrorHandler::printError(class PatchGroup* patchGroup, sint32 lineNumb
|
||||||
|
|
||||||
void PatchErrorHandler::showStageErrorMessageBox()
|
void PatchErrorHandler::showStageErrorMessageBox()
|
||||||
{
|
{
|
||||||
std::string errorMsg;
|
wxString errorMsg;
|
||||||
if (m_gp)
|
if (m_gp)
|
||||||
{
|
{
|
||||||
if (m_stage == STAGE::PARSER)
|
if (m_stage == STAGE::PARSER)
|
||||||
errorMsg.assign(fmt::format("Failed to load patches for graphic pack \'{}\'", m_gp->GetName()));
|
errorMsg.assign(formatWxString(_("Failed to load patches for graphic pack \'{}\'"), m_gp->GetName()));
|
||||||
else
|
else
|
||||||
errorMsg.assign(fmt::format("Failed to apply patches for graphic pack \'{}\'", m_gp->GetName()));
|
errorMsg.assign(formatWxString(_("Failed to apply patches for graphic pack \'{}\'"), m_gp->GetName()));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -53,7 +54,9 @@ void PatchErrorHandler::showStageErrorMessageBox()
|
||||||
}
|
}
|
||||||
if (cemuLog_isLoggingEnabled(LogType::Patches))
|
if (cemuLog_isLoggingEnabled(LogType::Patches))
|
||||||
{
|
{
|
||||||
errorMsg.append("\n \nDetails:\n");
|
errorMsg.append("\n \n")
|
||||||
|
.append(_("Details:"))
|
||||||
|
.append("\n");
|
||||||
for (auto& itr : errorMessages)
|
for (auto& itr : errorMessages)
|
||||||
{
|
{
|
||||||
errorMsg.append(itr);
|
errorMsg.append(itr);
|
||||||
|
@ -61,26 +64,15 @@ void PatchErrorHandler::showStageErrorMessageBox()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wxMessageBox(errorMsg, "Graphic pack error");
|
wxMessageBox(errorMsg, _("Graphic pack error"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// loads Cemu-style patches (patch_<anything>.asm)
|
// loads Cemu-style patches (patch_<anything>.asm)
|
||||||
// returns true if at least one file was found even if it could not be successfully parsed
|
// returns true if at least one file was found even if it could not be successfully parsed
|
||||||
bool GraphicPack2::LoadCemuPatches()
|
bool GraphicPack2::LoadCemuPatches()
|
||||||
{
|
{
|
||||||
// todo - once we have updated to C++20 we can replace these with the new std::string functions
|
|
||||||
auto startsWith = [](const std::wstring& str, const std::wstring& prefix)
|
|
||||||
{
|
|
||||||
return str.size() >= prefix.size() && 0 == str.compare(0, prefix.size(), prefix);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto endsWith = [](const std::wstring& str, const std::wstring& suffix)
|
|
||||||
{
|
|
||||||
return str.size() >= suffix.size() && 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix);
|
|
||||||
};
|
|
||||||
|
|
||||||
bool foundPatches = false;
|
bool foundPatches = false;
|
||||||
fs::path path(m_filename);
|
fs::path path(m_rulesPath);
|
||||||
path.remove_filename();
|
path.remove_filename();
|
||||||
for (auto& p : fs::directory_iterator(path))
|
for (auto& p : fs::directory_iterator(path))
|
||||||
{
|
{
|
||||||
|
@ -88,10 +80,10 @@ bool GraphicPack2::LoadCemuPatches()
|
||||||
if (fs::is_regular_file(p.status()) && path.has_filename())
|
if (fs::is_regular_file(p.status()) && path.has_filename())
|
||||||
{
|
{
|
||||||
// check if filename matches
|
// check if filename matches
|
||||||
std::wstring filename = path.filename().generic_wstring();
|
std::string filename = _pathToUtf8(path.filename());
|
||||||
if (boost::istarts_with(filename, L"patch_") && boost::iends_with(filename, L".asm"))
|
if (boost::istarts_with(filename, "patch_") && boost::iends_with(filename, ".asm"))
|
||||||
{
|
{
|
||||||
FileStream* patchFile = FileStream::openFile(path.generic_wstring().c_str());
|
FileStream* patchFile = FileStream::openFile2(path);
|
||||||
if (patchFile)
|
if (patchFile)
|
||||||
{
|
{
|
||||||
// read file
|
// read file
|
||||||
|
@ -123,27 +115,20 @@ void GraphicPack2::LoadPatchFiles()
|
||||||
// order of loading patches:
|
// order of loading patches:
|
||||||
// 1) Load Cemu-style patches (patch_<name>.asm), stop here if at least one patch file exists
|
// 1) Load Cemu-style patches (patch_<name>.asm), stop here if at least one patch file exists
|
||||||
// 2) Load Cemuhook patches.txt
|
// 2) Load Cemuhook patches.txt
|
||||||
|
|
||||||
// update: As of 1.20.2b Cemu always takes over patching since Cemuhook patching broke due to other internal changes (memory allocation changed and some reordering on when graphic packs get loaded)
|
|
||||||
if (LoadCemuPatches())
|
if (LoadCemuPatches())
|
||||||
return; // exit if at least one Cemu style patch file was found
|
return; // exit if at least one Cemu style patch file was found
|
||||||
// fall back to Cemuhook patches.txt to guarantee backward compatibility
|
// fall back to Cemuhook patches.txt to guarantee backward compatibility
|
||||||
fs::path path(m_filename);
|
fs::path path(m_rulesPath);
|
||||||
path.remove_filename();
|
path.remove_filename();
|
||||||
path.append("patches.txt");
|
path.append("patches.txt");
|
||||||
|
FileStream* patchFile = FileStream::openFile2(path);
|
||||||
FileStream* patchFile = FileStream::openFile(path.generic_wstring().c_str());
|
|
||||||
|
|
||||||
if (patchFile == nullptr)
|
if (patchFile == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// read file
|
// read file
|
||||||
std::vector<uint8> fileData;
|
std::vector<uint8> fileData;
|
||||||
patchFile->extract(fileData);
|
patchFile->extract(fileData);
|
||||||
delete patchFile;
|
delete patchFile;
|
||||||
|
|
||||||
cemu_assert_debug(list_patchGroups.empty());
|
cemu_assert_debug(list_patchGroups.empty());
|
||||||
|
|
||||||
// parse
|
// parse
|
||||||
MemStreamReader patchesStream(fileData.data(), (sint32)fileData.size());
|
MemStreamReader patchesStream(fileData.data(), (sint32)fileData.size());
|
||||||
ParseCemuhookPatchesTxtInternal(patchesStream);
|
ParseCemuhookPatchesTxtInternal(patchesStream);
|
||||||
|
|
|
@ -25,7 +25,7 @@ sint32 GraphicPack2::GetLengthWithoutComment(const char* str, size_t length)
|
||||||
|
|
||||||
void GraphicPack2::LogPatchesSyntaxError(sint32 lineNumber, std::string_view errorMsg)
|
void GraphicPack2::LogPatchesSyntaxError(sint32 lineNumber, std::string_view errorMsg)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, fmt::format(L"Syntax error while parsing patch for graphic pack '{}':", this->GetFilename()));
|
cemuLog_log(LogType::Force, "Syntax error while parsing patch for graphic pack '{}':", _pathToUtf8(this->GetRulesPath()));
|
||||||
if(lineNumber >= 0)
|
if(lineNumber >= 0)
|
||||||
cemuLog_log(LogType::Force, fmt::format("Line {0}: {1}", lineNumber, errorMsg));
|
cemuLog_log(LogType::Force, fmt::format("Line {0}: {1}", lineNumber, errorMsg));
|
||||||
else
|
else
|
||||||
|
|
|
@ -45,12 +45,17 @@ public:
|
||||||
|
|
||||||
static void ClearRange(MPTR address, uint32 length)
|
static void ClearRange(MPTR address, uint32 length)
|
||||||
{
|
{
|
||||||
|
if (length == 0)
|
||||||
|
return;
|
||||||
s_lock.lock();
|
s_lock.lock();
|
||||||
while (length > 0)
|
for (;;)
|
||||||
{
|
{
|
||||||
auto itr = s_typeStorage.find(address);
|
auto itr = s_typeStorage.find(address);
|
||||||
if (itr != s_typeStorage.end())
|
if (itr != s_typeStorage.end())
|
||||||
s_typeStorage.erase(itr);
|
s_typeStorage.erase(itr);
|
||||||
|
|
||||||
|
if (length <= 4)
|
||||||
|
break;
|
||||||
address += 4;
|
address += 4;
|
||||||
length -= 4;
|
length -= 4;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "gui/debugger/DebuggerWindow2.h"
|
#include "gui/debugger/DebuggerWindow2.h"
|
||||||
|
|
||||||
#include "Cafe/OS/libs/coreinit/coreinit.h"
|
#include "Cafe/OS/libs/coreinit/coreinit.h"
|
||||||
|
#include "util/helpers/helpers.h"
|
||||||
|
|
||||||
#if BOOST_OS_WINDOWS
|
#if BOOST_OS_WINDOWS
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
@ -136,11 +137,6 @@ void debugger_createCodeBreakpoint(uint32 address, uint8 bpType)
|
||||||
debugger_updateExecutionBreakpoint(address);
|
debugger_updateExecutionBreakpoint(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
void debugger_createExecuteBreakpoint(uint32 address)
|
|
||||||
{
|
|
||||||
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace coreinit
|
namespace coreinit
|
||||||
{
|
{
|
||||||
std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads();
|
std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads();
|
||||||
|
@ -210,7 +206,8 @@ void debugger_handleSingleStepException(uint64 dr6)
|
||||||
}
|
}
|
||||||
if (catchBP)
|
if (catchBP)
|
||||||
{
|
{
|
||||||
debugger_createCodeBreakpoint(ppcInterpreterCurrentInstance->instructionPointer + 4, DEBUGGER_BP_T_ONE_SHOT);
|
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
|
||||||
|
debugger_createCodeBreakpoint(hCPU->instructionPointer + 4, DEBUGGER_BP_T_ONE_SHOT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,8 +290,23 @@ void debugger_toggleExecuteBreakpoint(uint32 address)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// create new breakpoint
|
// create new execution breakpoint
|
||||||
debugger_createExecuteBreakpoint(address);
|
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void debugger_toggleLoggingBreakpoint(uint32 address)
|
||||||
|
{
|
||||||
|
auto existingBP = debugger_getFirstBP(address, DEBUGGER_BP_T_LOGGING);
|
||||||
|
if (existingBP)
|
||||||
|
{
|
||||||
|
// delete existing breakpoint
|
||||||
|
debugger_deleteBreakpoint(existingBP);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// create new logging breakpoint
|
||||||
|
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_LOGGING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -446,6 +458,34 @@ bool debugger_hasPatch(uint32 address)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void debugger_removePatch(uint32 address)
|
||||||
|
{
|
||||||
|
for (sint32 i = 0; i < debuggerState.patches.size(); i++)
|
||||||
|
{
|
||||||
|
auto& patch = debuggerState.patches[i];
|
||||||
|
if (address < patch->address || address >= (patch->address + patch->length))
|
||||||
|
continue;
|
||||||
|
MPTR startAddress = patch->address;
|
||||||
|
MPTR endAddress = patch->address + patch->length;
|
||||||
|
// remove any breakpoints overlapping with the patch
|
||||||
|
for (auto& bp : debuggerState.breakpoints)
|
||||||
|
{
|
||||||
|
if (bp->address + 4 > startAddress && bp->address < endAddress)
|
||||||
|
{
|
||||||
|
bp->enabled = false;
|
||||||
|
debugger_updateExecutionBreakpoint(bp->address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// restore original data
|
||||||
|
memcpy(MEMPTR<void>(startAddress).GetPtr(), patch->origData.data(), patch->length);
|
||||||
|
PPCRecompiler_invalidateRange(startAddress, endAddress);
|
||||||
|
// remove patch
|
||||||
|
delete patch;
|
||||||
|
debuggerState.patches.erase(debuggerState.patches.begin() + i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void debugger_stepInto(PPCInterpreter_t* hCPU, bool updateDebuggerWindow = true)
|
void debugger_stepInto(PPCInterpreter_t* hCPU, bool updateDebuggerWindow = true)
|
||||||
{
|
{
|
||||||
bool isRecEnabled = ppcRecompilerEnabled;
|
bool isRecEnabled = ppcRecompilerEnabled;
|
||||||
|
@ -500,8 +540,6 @@ void debugger_createPPCStateSnapshot(PPCInterpreter_t* hCPU)
|
||||||
debuggerState.debugSession.ppcSnapshot.cr[i] = hCPU->cr[i];
|
debuggerState.debugSession.ppcSnapshot.cr[i] = hCPU->cr[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugLogStackTrace(OSThread_t* thread, MPTR sp);
|
|
||||||
|
|
||||||
void debugger_enterTW(PPCInterpreter_t* hCPU)
|
void debugger_enterTW(PPCInterpreter_t* hCPU)
|
||||||
{
|
{
|
||||||
// handle logging points
|
// handle logging points
|
||||||
|
@ -511,11 +549,52 @@ void debugger_enterTW(PPCInterpreter_t* hCPU)
|
||||||
{
|
{
|
||||||
if (bp->bpType == DEBUGGER_BP_T_LOGGING && bp->enabled)
|
if (bp->bpType == DEBUGGER_BP_T_LOGGING && bp->enabled)
|
||||||
{
|
{
|
||||||
std::wstring logName = !bp->comment.empty() ? L"Breakpoint '"+bp->comment+L"'" : fmt::format(L"Breakpoint at 0x{:08X} (no comment)", bp->address);
|
std::string comment = !bp->comment.empty() ? boost::nowide::narrow(bp->comment) : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address);
|
||||||
std::wstring logContext = fmt::format(L"Thread: {:08x} LR: 0x{:08x}", coreinitThread_getCurrentThreadMPTRDepr(hCPU), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? L" Stack Trace:" : L"");
|
|
||||||
cemuLog_log(LogType::Force, L"[Debugger] {} was executed! {}", logName, logContext);
|
auto replacePlaceholders = [&](const std::string& prefix, const auto& formatFunc)
|
||||||
|
{
|
||||||
|
size_t pos = 0;
|
||||||
|
while ((pos = comment.find(prefix, pos)) != std::string::npos)
|
||||||
|
{
|
||||||
|
size_t endPos = comment.find('}', pos);
|
||||||
|
if (endPos == std::string::npos)
|
||||||
|
break;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (int regNum = ConvertString<int>(comment.substr(pos + prefix.length(), endPos - pos - prefix.length())); regNum >= 0 && regNum < 32)
|
||||||
|
{
|
||||||
|
std::string replacement = formatFunc(regNum);
|
||||||
|
comment.replace(pos, endPos - pos + 1, replacement);
|
||||||
|
pos += replacement.length();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pos = endPos + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
pos = endPos + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Replace integer register placeholders {rX}
|
||||||
|
replacePlaceholders("{r", [&](int regNum) {
|
||||||
|
return fmt::format("0x{:08X}", hCPU->gpr[regNum]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Replace floating point register placeholders {fX}
|
||||||
|
replacePlaceholders("{f", [&](int regNum) {
|
||||||
|
return fmt::format("{}", hCPU->fpr[regNum].fpr);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::string logName = "Breakpoint '" + comment + "'";
|
||||||
|
std::string logContext = fmt::format("Thread: {:08x} LR: 0x{:08x}", MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR(), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? " Stack Trace:" : "");
|
||||||
|
cemuLog_log(LogType::Force, "[Debugger] {} was executed! {}", logName, logContext);
|
||||||
if (cemuLog_advancedPPCLoggingEnabled())
|
if (cemuLog_advancedPPCLoggingEnabled())
|
||||||
DebugLogStackTrace(coreinitThread_getCurrentThreadDepr(hCPU), hCPU->gpr[1]);
|
DebugLogStackTrace(coreinit::OSGetCurrentThread(), hCPU->gpr[1]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
bp = bp->next;
|
bp = bp->next;
|
||||||
|
@ -534,7 +613,7 @@ void debugger_enterTW(PPCInterpreter_t* hCPU)
|
||||||
|
|
||||||
// handle breakpoints
|
// handle breakpoints
|
||||||
debuggerState.debugSession.isTrapped = true;
|
debuggerState.debugSession.isTrapped = true;
|
||||||
debuggerState.debugSession.debuggedThreadMPTR = coreinitThread_getCurrentThreadMPTRDepr(hCPU);
|
debuggerState.debugSession.debuggedThreadMPTR = MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR();
|
||||||
debuggerState.debugSession.instructionPointer = hCPU->instructionPointer;
|
debuggerState.debugSession.instructionPointer = hCPU->instructionPointer;
|
||||||
debuggerState.debugSession.hCPU = hCPU;
|
debuggerState.debugSession.hCPU = hCPU;
|
||||||
debugger_createPPCStateSnapshot(hCPU);
|
debugger_createPPCStateSnapshot(hCPU);
|
||||||
|
@ -548,7 +627,7 @@ void debugger_enterTW(PPCInterpreter_t* hCPU)
|
||||||
debuggerState.debugSession.stepInto = false;
|
debuggerState.debugSession.stepInto = false;
|
||||||
debuggerState.debugSession.stepOver = false;
|
debuggerState.debugSession.stepOver = false;
|
||||||
debuggerState.debugSession.run = false;
|
debuggerState.debugSession.run = false;
|
||||||
while (true)
|
while (debuggerState.debugSession.isTrapped)
|
||||||
{
|
{
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
// check for step commands
|
// check for step commands
|
||||||
|
|
|
@ -100,8 +100,8 @@ extern debuggerState_t debuggerState;
|
||||||
// new API
|
// new API
|
||||||
DebuggerBreakpoint* debugger_getFirstBP(uint32 address);
|
DebuggerBreakpoint* debugger_getFirstBP(uint32 address);
|
||||||
void debugger_createCodeBreakpoint(uint32 address, uint8 bpType);
|
void debugger_createCodeBreakpoint(uint32 address, uint8 bpType);
|
||||||
void debugger_createExecuteBreakpoint(uint32 address);
|
|
||||||
void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint
|
void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint
|
||||||
|
void debugger_toggleLoggingBreakpoint(uint32 address); // create/remove logging breakpoint
|
||||||
void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp);
|
void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp);
|
||||||
|
|
||||||
void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite);
|
void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite);
|
||||||
|
@ -114,6 +114,7 @@ void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore = fals
|
||||||
|
|
||||||
void debugger_createPatch(uint32 address, std::span<uint8> patchData);
|
void debugger_createPatch(uint32 address, std::span<uint8> patchData);
|
||||||
bool debugger_hasPatch(uint32 address);
|
bool debugger_hasPatch(uint32 address);
|
||||||
|
void debugger_removePatch(uint32 address);
|
||||||
|
|
||||||
void debugger_forceBreak(); // force breakpoint at the next possible instruction
|
void debugger_forceBreak(); // force breakpoint at the next possible instruction
|
||||||
bool debugger_isTrapped();
|
bool debugger_isTrapped();
|
||||||
|
|
304
src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.cpp
Normal file
304
src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.cpp
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
#include "GDBBreakpoints.h"
|
||||||
|
#include "Debugger.h"
|
||||||
|
#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h"
|
||||||
|
|
||||||
|
#if defined(ARCH_X86_64) && BOOST_OS_LINUX
|
||||||
|
#include <sys/ptrace.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <sys/user.h>
|
||||||
|
|
||||||
|
DRType _GetDR(pid_t tid, int drIndex)
|
||||||
|
{
|
||||||
|
size_t drOffset = offsetof(struct user, u_debugreg) + drIndex * sizeof(user::u_debugreg[0]);
|
||||||
|
|
||||||
|
long v;
|
||||||
|
v = ptrace(PTRACE_PEEKUSER, tid, drOffset, nullptr);
|
||||||
|
if (v == -1)
|
||||||
|
perror("ptrace(PTRACE_PEEKUSER)");
|
||||||
|
|
||||||
|
return (DRType)v;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _SetDR(pid_t tid, int drIndex, DRType newValue)
|
||||||
|
{
|
||||||
|
size_t drOffset = offsetof(struct user, u_debugreg) + drIndex * sizeof(user::u_debugreg[0]);
|
||||||
|
|
||||||
|
long rc = ptrace(PTRACE_POKEUSER, tid, drOffset, newValue);
|
||||||
|
if (rc == -1)
|
||||||
|
perror("ptrace(PTRACE_POKEUSER)");
|
||||||
|
}
|
||||||
|
|
||||||
|
DRType _ReadDR6()
|
||||||
|
{
|
||||||
|
pid_t tid = gettid();
|
||||||
|
|
||||||
|
// linux doesn't let us attach to the current thread / threads in the current thread group
|
||||||
|
// we have to create a child process which then modifies the debug registers and quits
|
||||||
|
pid_t child = fork();
|
||||||
|
if (child == -1)
|
||||||
|
{
|
||||||
|
perror("fork");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child == 0)
|
||||||
|
{
|
||||||
|
if (ptrace(PTRACE_ATTACH, tid, nullptr, nullptr))
|
||||||
|
{
|
||||||
|
perror("attach");
|
||||||
|
_exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
waitpid(tid, NULL, 0);
|
||||||
|
|
||||||
|
uint64_t dr6 = _GetDR(tid, 6);
|
||||||
|
|
||||||
|
if (ptrace(PTRACE_DETACH, tid, nullptr, nullptr))
|
||||||
|
perror("detach");
|
||||||
|
|
||||||
|
// since the status code only uses the lower 8 bits, we have to discard the rest of DR6
|
||||||
|
// this should be fine though, since the lower 4 bits of DR6 contain all the bp conditions
|
||||||
|
_exit(dr6 & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for child process
|
||||||
|
int wstatus;
|
||||||
|
waitpid(child, &wstatus, 0);
|
||||||
|
|
||||||
|
return (DRType)WEXITSTATUS(wstatus);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
GDBServer::ExecutionBreakpoint::ExecutionBreakpoint(MPTR address, BreakpointType type, bool visible, std::string reason)
|
||||||
|
: m_address(address), m_removedAfterInterrupt(false), m_reason(std::move(reason))
|
||||||
|
{
|
||||||
|
if (type == BreakpointType::BP_SINGLE)
|
||||||
|
{
|
||||||
|
this->m_pauseThreads = true;
|
||||||
|
this->m_restoreAfterInterrupt = false;
|
||||||
|
this->m_deleteAfterAnyInterrupt = false;
|
||||||
|
this->m_pauseOnNextInterrupt = false;
|
||||||
|
this->m_visible = visible;
|
||||||
|
}
|
||||||
|
else if (type == BreakpointType::BP_PERSISTENT)
|
||||||
|
{
|
||||||
|
this->m_pauseThreads = true;
|
||||||
|
this->m_restoreAfterInterrupt = true;
|
||||||
|
this->m_deleteAfterAnyInterrupt = false;
|
||||||
|
this->m_pauseOnNextInterrupt = false;
|
||||||
|
this->m_visible = visible;
|
||||||
|
}
|
||||||
|
else if (type == BreakpointType::BP_RESTORE_POINT)
|
||||||
|
{
|
||||||
|
this->m_pauseThreads = false;
|
||||||
|
this->m_restoreAfterInterrupt = false;
|
||||||
|
this->m_deleteAfterAnyInterrupt = false;
|
||||||
|
this->m_pauseOnNextInterrupt = false;
|
||||||
|
this->m_visible = false;
|
||||||
|
}
|
||||||
|
else if (type == BreakpointType::BP_STEP_POINT)
|
||||||
|
{
|
||||||
|
this->m_pauseThreads = false;
|
||||||
|
this->m_restoreAfterInterrupt = false;
|
||||||
|
this->m_deleteAfterAnyInterrupt = true;
|
||||||
|
this->m_pauseOnNextInterrupt = true;
|
||||||
|
this->m_visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->m_origOpCode = memory_readU32(address);
|
||||||
|
memory_writeU32(address, DEBUGGER_BP_T_GDBSTUB_TW);
|
||||||
|
PPCRecompiler_invalidateRange(address, address + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
GDBServer::ExecutionBreakpoint::~ExecutionBreakpoint()
|
||||||
|
{
|
||||||
|
memory_writeU32(this->m_address, this->m_origOpCode);
|
||||||
|
PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 GDBServer::ExecutionBreakpoint::GetVisibleOpCode() const
|
||||||
|
{
|
||||||
|
if (this->m_visible)
|
||||||
|
return memory_readU32(this->m_address);
|
||||||
|
else
|
||||||
|
return this->m_origOpCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDBServer::ExecutionBreakpoint::RemoveTemporarily()
|
||||||
|
{
|
||||||
|
memory_writeU32(this->m_address, this->m_origOpCode);
|
||||||
|
PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4);
|
||||||
|
this->m_restoreAfterInterrupt = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDBServer::ExecutionBreakpoint::Restore()
|
||||||
|
{
|
||||||
|
memory_writeU32(this->m_address, DEBUGGER_BP_T_GDBSTUB_TW);
|
||||||
|
PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4);
|
||||||
|
this->m_restoreAfterInterrupt = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace coreinit
|
||||||
|
{
|
||||||
|
#if BOOST_OS_LINUX
|
||||||
|
std::vector<pid_t>& OSGetSchedulerThreadIds();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
GDBServer::AccessBreakpoint::AccessBreakpoint(MPTR address, AccessPointType type)
|
||||||
|
: m_address(address), m_type(type)
|
||||||
|
{
|
||||||
|
#if defined(ARCH_X86_64) && BOOST_OS_WINDOWS
|
||||||
|
for (auto& hThreadNH : coreinit::OSGetSchedulerThreads())
|
||||||
|
{
|
||||||
|
HANDLE hThread = (HANDLE)hThreadNH;
|
||||||
|
CONTEXT ctx{};
|
||||||
|
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
||||||
|
SuspendThread(hThread);
|
||||||
|
GetThreadContext(hThread, &ctx);
|
||||||
|
|
||||||
|
// use BP 2/3 for gdb stub since cemu's internal debugger uses BP 0/1 already
|
||||||
|
ctx.Dr2 = (DWORD64)memory_getPointerFromVirtualOffset(address);
|
||||||
|
ctx.Dr3 = (DWORD64)memory_getPointerFromVirtualOffset(address);
|
||||||
|
// breakpoint 2
|
||||||
|
SetBits(ctx.Dr7, 4, 1, 1); // breakpoint #3 enabled: true
|
||||||
|
SetBits(ctx.Dr7, 24, 2, 1); // breakpoint #3 condition: 1 (write)
|
||||||
|
SetBits(ctx.Dr7, 26, 2, 3); // breakpoint #3 length: 3 (4 bytes)
|
||||||
|
// breakpoint 3
|
||||||
|
SetBits(ctx.Dr7, 6, 1, 1); // breakpoint #4 enabled: true
|
||||||
|
SetBits(ctx.Dr7, 28, 2, 3); // breakpoint #4 condition: 3 (read & write)
|
||||||
|
SetBits(ctx.Dr7, 30, 2, 3); // breakpoint #4 length: 3 (4 bytes)
|
||||||
|
|
||||||
|
SetThreadContext(hThread, &ctx);
|
||||||
|
ResumeThread(hThread);
|
||||||
|
}
|
||||||
|
#elif defined(ARCH_X86_64) && BOOST_OS_LINUX
|
||||||
|
// linux doesn't let us attach to threads which are in the same thread group as our current thread
|
||||||
|
// we have to create a child process which then modifies the debug registers and quits
|
||||||
|
pid_t child = fork();
|
||||||
|
if (child == -1)
|
||||||
|
{
|
||||||
|
perror("fork");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child == 0)
|
||||||
|
{
|
||||||
|
for (pid_t tid : coreinit::OSGetSchedulerThreadIds())
|
||||||
|
{
|
||||||
|
long rc = ptrace(PTRACE_ATTACH, tid, nullptr, nullptr);
|
||||||
|
if (rc == -1)
|
||||||
|
perror("ptrace(PTRACE_ATTACH)");
|
||||||
|
|
||||||
|
waitpid(tid, nullptr, 0);
|
||||||
|
|
||||||
|
DRType dr7 = _GetDR(tid, 7);
|
||||||
|
// use BP 2/3 for gdb stub since cemu's internal debugger uses BP 0/1 already
|
||||||
|
DRType dr2 = (uint64)memory_getPointerFromVirtualOffset(address);
|
||||||
|
DRType dr3 = (uint64)memory_getPointerFromVirtualOffset(address);
|
||||||
|
// breakpoint 2
|
||||||
|
SetBits(dr7, 4, 1, 1); // breakpoint #3 enabled: true
|
||||||
|
SetBits(dr7, 24, 2, 1); // breakpoint #3 condition: 1 (write)
|
||||||
|
SetBits(dr7, 26, 2, 3); // breakpoint #3 length: 3 (4 bytes)
|
||||||
|
// breakpoint 3
|
||||||
|
SetBits(dr7, 6, 1, 1); // breakpoint #4 enabled: true
|
||||||
|
SetBits(dr7, 28, 2, 3); // breakpoint #4 condition: 3 (read & write)
|
||||||
|
SetBits(dr7, 30, 2, 3); // breakpoint #4 length: 3 (4 bytes)
|
||||||
|
|
||||||
|
_SetDR(tid, 2, dr2);
|
||||||
|
_SetDR(tid, 3, dr3);
|
||||||
|
_SetDR(tid, 7, dr7);
|
||||||
|
|
||||||
|
rc = ptrace(PTRACE_DETACH, tid, nullptr, nullptr);
|
||||||
|
if (rc == -1)
|
||||||
|
perror("ptrace(PTRACE_DETACH)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// exit child process
|
||||||
|
_exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for child process
|
||||||
|
waitpid(child, nullptr, 0);
|
||||||
|
#else
|
||||||
|
cemuLog_log(LogType::Force, "Debugger read/write breakpoints are not supported on non-x86 CPUs yet.");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
GDBServer::AccessBreakpoint::~AccessBreakpoint()
|
||||||
|
{
|
||||||
|
#if defined(ARCH_X86_64) && BOOST_OS_WINDOWS
|
||||||
|
for (auto& hThreadNH : coreinit::OSGetSchedulerThreads())
|
||||||
|
{
|
||||||
|
HANDLE hThread = (HANDLE)hThreadNH;
|
||||||
|
CONTEXT ctx{};
|
||||||
|
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
||||||
|
SuspendThread(hThread);
|
||||||
|
GetThreadContext(hThread, &ctx);
|
||||||
|
|
||||||
|
// reset BP 2/3 to zero
|
||||||
|
ctx.Dr2 = (DWORD64)0;
|
||||||
|
ctx.Dr3 = (DWORD64)0;
|
||||||
|
// breakpoint 2
|
||||||
|
SetBits(ctx.Dr7, 4, 1, 0);
|
||||||
|
SetBits(ctx.Dr7, 24, 2, 0);
|
||||||
|
SetBits(ctx.Dr7, 26, 2, 0);
|
||||||
|
// breakpoint 3
|
||||||
|
SetBits(ctx.Dr7, 6, 1, 0);
|
||||||
|
SetBits(ctx.Dr7, 28, 2, 0);
|
||||||
|
SetBits(ctx.Dr7, 30, 2, 0);
|
||||||
|
SetThreadContext(hThread, &ctx);
|
||||||
|
ResumeThread(hThread);
|
||||||
|
}
|
||||||
|
#elif defined(ARCH_X86_64) && BOOST_OS_LINUX
|
||||||
|
// linux doesn't let us attach to threads which are in the same thread group as our current thread
|
||||||
|
// we have to create a child process which then modifies the debug registers and quits
|
||||||
|
pid_t child = fork();
|
||||||
|
if (child == -1)
|
||||||
|
{
|
||||||
|
perror("fork");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child == 0)
|
||||||
|
{
|
||||||
|
for (pid_t tid : coreinit::OSGetSchedulerThreadIds())
|
||||||
|
{
|
||||||
|
long rc = ptrace(PTRACE_ATTACH, tid, nullptr, nullptr);
|
||||||
|
if (rc == -1)
|
||||||
|
perror("ptrace(PTRACE_ATTACH)");
|
||||||
|
|
||||||
|
waitpid(tid, nullptr, 0);
|
||||||
|
|
||||||
|
DRType dr7 = _GetDR(tid, 7);
|
||||||
|
// reset BP 2/3 to zero
|
||||||
|
DRType dr2 = 0;
|
||||||
|
DRType dr3 = 0;
|
||||||
|
// breakpoint 2
|
||||||
|
SetBits(dr7, 4, 1, 0);
|
||||||
|
SetBits(dr7, 24, 2, 0);
|
||||||
|
SetBits(dr7, 26, 2, 0);
|
||||||
|
// breakpoint 3
|
||||||
|
SetBits(dr7, 6, 1, 0);
|
||||||
|
SetBits(dr7, 28, 2, 0);
|
||||||
|
SetBits(dr7, 30, 2, 0);
|
||||||
|
|
||||||
|
_SetDR(tid, 2, dr2);
|
||||||
|
_SetDR(tid, 3, dr3);
|
||||||
|
_SetDR(tid, 7, dr7);
|
||||||
|
|
||||||
|
rc = ptrace(PTRACE_DETACH, tid, nullptr, nullptr);
|
||||||
|
if (rc == -1)
|
||||||
|
perror("ptrace(PTRACE_DETACH)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// exit child process
|
||||||
|
_exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for child process
|
||||||
|
waitpid(child, nullptr, 0);
|
||||||
|
#endif
|
||||||
|
}
|
|
@ -1,33 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
#include "GDBStub.h"
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#if defined(ARCH_X86_64) && BOOST_OS_LINUX && FALSE
|
#if defined(ARCH_X86_64) && BOOST_OS_LINUX
|
||||||
#include <sys/ptrace.h>
|
#include <sys/types.h>
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <sys/user.h>
|
|
||||||
|
|
||||||
// helpers for accessing debug register
|
// helpers for accessing debug register
|
||||||
typedef unsigned long DRType;
|
typedef unsigned long DRType;
|
||||||
|
|
||||||
DRType _GetDR(pid_t tid, int drIndex)
|
DRType _GetDR(pid_t tid, int drIndex);
|
||||||
{
|
void _SetDR(pid_t tid, int drIndex, DRType newValue);
|
||||||
unsigned long v;
|
DRType _ReadDR6();
|
||||||
v = ptrace (PTRACE_PEEKUSER, tid, offsetof (struct user, u_debugreg[drIndex]), 0);
|
|
||||||
return (DRType)v;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _SetDR(pid_t tid, int drIndex, DRType newValue)
|
|
||||||
{
|
|
||||||
unsigned long v = newValue;
|
|
||||||
ptrace (PTRACE_POKEUSER, tid, offsetof (struct user, u_debugreg[drIndex]), v);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace coreinit
|
|
||||||
{
|
|
||||||
std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads();
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class BreakpointType
|
enum class BreakpointType
|
||||||
{
|
{
|
||||||
BP_SINGLE,
|
BP_SINGLE,
|
||||||
|
@ -38,59 +23,10 @@ enum class BreakpointType
|
||||||
|
|
||||||
class GDBServer::ExecutionBreakpoint {
|
class GDBServer::ExecutionBreakpoint {
|
||||||
public:
|
public:
|
||||||
ExecutionBreakpoint(MPTR address, BreakpointType type, bool visible, std::string reason)
|
ExecutionBreakpoint(MPTR address, BreakpointType type, bool visible, std::string reason);
|
||||||
: m_address(address), m_removedAfterInterrupt(false), m_reason(std::move(reason))
|
~ExecutionBreakpoint();
|
||||||
{
|
|
||||||
if (type == BreakpointType::BP_SINGLE)
|
|
||||||
{
|
|
||||||
this->m_pauseThreads = true;
|
|
||||||
this->m_restoreAfterInterrupt = false;
|
|
||||||
this->m_deleteAfterAnyInterrupt = false;
|
|
||||||
this->m_pauseOnNextInterrupt = false;
|
|
||||||
this->m_visible = visible;
|
|
||||||
}
|
|
||||||
else if (type == BreakpointType::BP_PERSISTENT)
|
|
||||||
{
|
|
||||||
this->m_pauseThreads = true;
|
|
||||||
this->m_restoreAfterInterrupt = true;
|
|
||||||
this->m_deleteAfterAnyInterrupt = false;
|
|
||||||
this->m_pauseOnNextInterrupt = false;
|
|
||||||
this->m_visible = visible;
|
|
||||||
}
|
|
||||||
else if (type == BreakpointType::BP_RESTORE_POINT)
|
|
||||||
{
|
|
||||||
this->m_pauseThreads = false;
|
|
||||||
this->m_restoreAfterInterrupt = false;
|
|
||||||
this->m_deleteAfterAnyInterrupt = false;
|
|
||||||
this->m_pauseOnNextInterrupt = false;
|
|
||||||
this->m_visible = false;
|
|
||||||
}
|
|
||||||
else if (type == BreakpointType::BP_STEP_POINT)
|
|
||||||
{
|
|
||||||
this->m_pauseThreads = false;
|
|
||||||
this->m_restoreAfterInterrupt = false;
|
|
||||||
this->m_deleteAfterAnyInterrupt = true;
|
|
||||||
this->m_pauseOnNextInterrupt = true;
|
|
||||||
this->m_visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->m_origOpCode = memory_readU32(address);
|
[[nodiscard]] uint32 GetVisibleOpCode() const;
|
||||||
memory_writeU32(address, DEBUGGER_BP_T_GDBSTUB_TW);
|
|
||||||
PPCRecompiler_invalidateRange(address, address + 4);
|
|
||||||
};
|
|
||||||
~ExecutionBreakpoint()
|
|
||||||
{
|
|
||||||
memory_writeU32(this->m_address, this->m_origOpCode);
|
|
||||||
PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4);
|
|
||||||
};
|
|
||||||
|
|
||||||
[[nodiscard]] uint32 GetVisibleOpCode() const
|
|
||||||
{
|
|
||||||
if (this->m_visible)
|
|
||||||
return memory_readU32(this->m_address);
|
|
||||||
else
|
|
||||||
return this->m_origOpCode;
|
|
||||||
};
|
|
||||||
[[nodiscard]] bool ShouldBreakThreads() const
|
[[nodiscard]] bool ShouldBreakThreads() const
|
||||||
{
|
{
|
||||||
return this->m_pauseThreads;
|
return this->m_pauseThreads;
|
||||||
|
@ -118,18 +54,8 @@ public:
|
||||||
return m_reason;
|
return m_reason;
|
||||||
};
|
};
|
||||||
|
|
||||||
void RemoveTemporarily()
|
void RemoveTemporarily();
|
||||||
{
|
void Restore();
|
||||||
memory_writeU32(this->m_address, this->m_origOpCode);
|
|
||||||
PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4);
|
|
||||||
this->m_restoreAfterInterrupt = true;
|
|
||||||
};
|
|
||||||
void Restore()
|
|
||||||
{
|
|
||||||
memory_writeU32(this->m_address, DEBUGGER_BP_T_GDBSTUB_TW);
|
|
||||||
PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4);
|
|
||||||
this->m_restoreAfterInterrupt = false;
|
|
||||||
};
|
|
||||||
void PauseOnNextInterrupt()
|
void PauseOnNextInterrupt()
|
||||||
{
|
{
|
||||||
this->m_pauseOnNextInterrupt = true;
|
this->m_pauseOnNextInterrupt = true;
|
||||||
|
@ -162,115 +88,8 @@ enum class AccessPointType
|
||||||
|
|
||||||
class GDBServer::AccessBreakpoint {
|
class GDBServer::AccessBreakpoint {
|
||||||
public:
|
public:
|
||||||
AccessBreakpoint(MPTR address, AccessPointType type)
|
AccessBreakpoint(MPTR address, AccessPointType type);
|
||||||
: m_address(address), m_type(type)
|
~AccessBreakpoint();
|
||||||
{
|
|
||||||
#if defined(ARCH_X86_64) && BOOST_OS_WINDOWS
|
|
||||||
for (auto& hThreadNH : coreinit::OSGetSchedulerThreads())
|
|
||||||
{
|
|
||||||
HANDLE hThread = (HANDLE)hThreadNH;
|
|
||||||
CONTEXT ctx{};
|
|
||||||
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
|
||||||
SuspendThread(hThread);
|
|
||||||
GetThreadContext(hThread, &ctx);
|
|
||||||
|
|
||||||
// use BP 2/3 for gdb stub since cemu's internal debugger uses BP 0/1 already
|
|
||||||
ctx.Dr2 = (DWORD64)memory_getPointerFromVirtualOffset(address);
|
|
||||||
ctx.Dr3 = (DWORD64)memory_getPointerFromVirtualOffset(address);
|
|
||||||
// breakpoint 2
|
|
||||||
SetBits(ctx.Dr7, 4, 1, 1); // breakpoint #3 enabled: true
|
|
||||||
SetBits(ctx.Dr7, 24, 2, 1); // breakpoint #3 condition: 1 (write)
|
|
||||||
SetBits(ctx.Dr7, 26, 2, 3); // breakpoint #3 length: 3 (4 bytes)
|
|
||||||
// breakpoint 3
|
|
||||||
SetBits(ctx.Dr7, 6, 1, 1); // breakpoint #4 enabled: true
|
|
||||||
SetBits(ctx.Dr7, 28, 2, 3); // breakpoint #4 condition: 3 (read & write)
|
|
||||||
SetBits(ctx.Dr7, 30, 2, 3); // breakpoint #4 length: 3 (4 bytes)
|
|
||||||
|
|
||||||
SetThreadContext(hThread, &ctx);
|
|
||||||
ResumeThread(hThread);
|
|
||||||
}
|
|
||||||
// todo: port the following code to all unix platforms, they seem to differ quite a bit
|
|
||||||
#elif defined(ARCH_X86_64) && BOOST_OS_LINUX && FALSE
|
|
||||||
for (auto& hThreadNH : coreinit::OSGetSchedulerThreads())
|
|
||||||
{
|
|
||||||
pid_t pid = (pid_t)(uintptr_t)hThreadNH;
|
|
||||||
ptrace(PTRACE_ATTACH, pid, nullptr, nullptr);
|
|
||||||
waitpid(pid, nullptr, 0);
|
|
||||||
|
|
||||||
DRType dr7 = _GetDR(pid, 7);
|
|
||||||
// use BP 2/3 for gdb stub since cemu's internal debugger uses BP 0/1 already
|
|
||||||
DRType dr2 = (uint64)memory_getPointerFromVirtualOffset(address);
|
|
||||||
DRType dr3 = (uint64)memory_getPointerFromVirtualOffset(address);
|
|
||||||
// breakpoint 2
|
|
||||||
SetBits(dr7, 4, 1, 1); // breakpoint #3 enabled: true
|
|
||||||
SetBits(dr7, 24, 2, 1); // breakpoint #3 condition: 1 (write)
|
|
||||||
SetBits(dr7, 26, 2, 3); // breakpoint #3 length: 3 (4 bytes)
|
|
||||||
// breakpoint 3
|
|
||||||
SetBits(dr7, 6, 1, 1); // breakpoint #4 enabled: true
|
|
||||||
SetBits(dr7, 28, 2, 3); // breakpoint #4 condition: 3 (read & write)
|
|
||||||
SetBits(dr7, 30, 2, 3); // breakpoint #4 length: 3 (4 bytes)
|
|
||||||
|
|
||||||
_SetDR(pid, 2, dr2);
|
|
||||||
_SetDR(pid, 3, dr3);
|
|
||||||
_SetDR(pid, 7, dr7);
|
|
||||||
ptrace(PTRACE_DETACH, pid, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
cemuLog_log(LogType::Force, "Debugger read/write breakpoints are not supported on non-x86 CPUs yet.");
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
~AccessBreakpoint()
|
|
||||||
{
|
|
||||||
#if defined(ARCH_X86_64) && BOOST_OS_WINDOWS
|
|
||||||
for (auto& hThreadNH : coreinit::OSGetSchedulerThreads())
|
|
||||||
{
|
|
||||||
HANDLE hThread = (HANDLE)hThreadNH;
|
|
||||||
CONTEXT ctx{};
|
|
||||||
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
|
||||||
SuspendThread(hThread);
|
|
||||||
GetThreadContext(hThread, &ctx);
|
|
||||||
|
|
||||||
// reset BP 2/3 to zero
|
|
||||||
ctx.Dr2 = (DWORD64)0;
|
|
||||||
ctx.Dr3 = (DWORD64)0;
|
|
||||||
// breakpoint 2
|
|
||||||
SetBits(ctx.Dr7, 4, 1, 0);
|
|
||||||
SetBits(ctx.Dr7, 24, 2, 0);
|
|
||||||
SetBits(ctx.Dr7, 26, 2, 0);
|
|
||||||
// breakpoint 3
|
|
||||||
SetBits(ctx.Dr7, 6, 1, 0);
|
|
||||||
SetBits(ctx.Dr7, 28, 2, 0);
|
|
||||||
SetBits(ctx.Dr7, 30, 2, 0);
|
|
||||||
SetThreadContext(hThread, &ctx);
|
|
||||||
ResumeThread(hThread);
|
|
||||||
}
|
|
||||||
#elif defined(ARCH_X86_64) && BOOST_OS_LINUX && FALSE
|
|
||||||
for (auto& hThreadNH : coreinit::OSGetSchedulerThreads())
|
|
||||||
{
|
|
||||||
pid_t pid = (pid_t)(uintptr_t)hThreadNH;
|
|
||||||
ptrace(PTRACE_ATTACH, pid, nullptr, nullptr);
|
|
||||||
waitpid(pid, nullptr, 0);
|
|
||||||
|
|
||||||
DRType dr7 = _GetDR(pid, 7);
|
|
||||||
// reset BP 2/3 to zero
|
|
||||||
DRType dr2 = 0;
|
|
||||||
DRType dr3 = 0;
|
|
||||||
// breakpoint 2
|
|
||||||
SetBits(dr7, 4, 1, 0);
|
|
||||||
SetBits(dr7, 24, 2, 0);
|
|
||||||
SetBits(dr7, 26, 2, 0);
|
|
||||||
// breakpoint 3
|
|
||||||
SetBits(dr7, 6, 1, 0);
|
|
||||||
SetBits(dr7, 28, 2, 0);
|
|
||||||
SetBits(dr7, 30, 2, 0);
|
|
||||||
|
|
||||||
_SetDR(pid, 2, dr2);
|
|
||||||
_SetDR(pid, 3, dr3);
|
|
||||||
_SetDR(pid, 7, dr7);
|
|
||||||
ptrace(PTRACE_DETACH, pid, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
MPTR GetAddress() const
|
MPTR GetAddress() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -263,6 +263,14 @@ bool GDBServer::Initialize()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int nodelayEnabled = TRUE;
|
||||||
|
if (setsockopt(m_server_socket, IPPROTO_TCP, TCP_NODELAY, (char*)&nodelayEnabled, sizeof(nodelayEnabled)) == SOCKET_ERROR)
|
||||||
|
{
|
||||||
|
closesocket(m_server_socket);
|
||||||
|
m_server_socket = INVALID_SOCKET;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
memset(&m_server_addr, 0, sizeof(m_server_addr));
|
memset(&m_server_addr, 0, sizeof(m_server_addr));
|
||||||
m_server_addr.sin_family = AF_INET;
|
m_server_addr.sin_family = AF_INET;
|
||||||
m_server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
m_server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
@ -289,7 +297,7 @@ bool GDBServer::Initialize()
|
||||||
|
|
||||||
void GDBServer::ThreadFunc()
|
void GDBServer::ThreadFunc()
|
||||||
{
|
{
|
||||||
SetThreadName("GDBServer::ThreadFunc");
|
SetThreadName("GDBServer");
|
||||||
|
|
||||||
while (!m_stopRequested)
|
while (!m_stopRequested)
|
||||||
{
|
{
|
||||||
|
@ -356,7 +364,7 @@ void GDBServer::ThreadFunc()
|
||||||
}
|
}
|
||||||
char checkSumStr[2];
|
char checkSumStr[2];
|
||||||
receiveMessage(checkSumStr, 2);
|
receiveMessage(checkSumStr, 2);
|
||||||
uint32_t checkSum = std::stoi(checkSumStr, nullptr, 16);
|
uint32_t checkSum = std::stoi(std::string(checkSumStr, sizeof(checkSumStr)), nullptr, 16);
|
||||||
assert((checkedSum & 0xFF) == checkSum);
|
assert((checkedSum & 0xFF) == checkSum);
|
||||||
|
|
||||||
HandleCommand(message);
|
HandleCommand(message);
|
||||||
|
@ -900,7 +908,7 @@ void GDBServer::HandleTrapInstruction(PPCInterpreter_t* hCPU)
|
||||||
return cemu_assert_suspicious();
|
return cemu_assert_suspicious();
|
||||||
|
|
||||||
// Secondly, delete one-shot breakpoints but also temporarily delete patched instruction to run original instruction
|
// Secondly, delete one-shot breakpoints but also temporarily delete patched instruction to run original instruction
|
||||||
OSThread_t* currThread = coreinitThread_getCurrentThreadDepr(hCPU);
|
OSThread_t* currThread = coreinit::OSGetCurrentThread();
|
||||||
std::string pauseReason = fmt::format("T05thread:{:08X};core:{:02X};{}", GET_THREAD_ID(currThread), PPCInterpreter_getCoreIndex(hCPU), patchedBP->second.GetReason());
|
std::string pauseReason = fmt::format("T05thread:{:08X};core:{:02X};{}", GET_THREAD_ID(currThread), PPCInterpreter_getCoreIndex(hCPU), patchedBP->second.GetReason());
|
||||||
bool pauseThreads = patchedBP->second.ShouldBreakThreads() || patchedBP->second.ShouldBreakThreadsOnNextInterrupt();
|
bool pauseThreads = patchedBP->second.ShouldBreakThreads() || patchedBP->second.ShouldBreakThreadsOnNextInterrupt();
|
||||||
if (patchedBP->second.IsPersistent())
|
if (patchedBP->second.IsPersistent())
|
||||||
|
@ -939,7 +947,7 @@ void GDBServer::HandleTrapInstruction(PPCInterpreter_t* hCPU)
|
||||||
ThreadPool::FireAndForget(&waitForBrokenThreads, std::move(m_resumed_context), pauseReason);
|
ThreadPool::FireAndForget(&waitForBrokenThreads, std::move(m_resumed_context), pauseReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
breakThreads(GET_THREAD_ID(coreinitThread_getCurrentThreadDepr(hCPU)));
|
breakThreads(GET_THREAD_ID(coreinit::OSGetCurrentThread()));
|
||||||
cemuLog_logDebug(LogType::Force, "[GDBStub] Resumed from a breakpoint!");
|
cemuLog_logDebug(LogType::Force, "[GDBStub] Resumed from a breakpoint!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -959,8 +967,9 @@ void GDBServer::HandleAccessException(uint64 dr6)
|
||||||
|
|
||||||
if (!response.empty())
|
if (!response.empty())
|
||||||
{
|
{
|
||||||
|
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
|
||||||
cemuLog_logDebug(LogType::Force, "Received matching breakpoint exception: {}", response);
|
cemuLog_logDebug(LogType::Force, "Received matching breakpoint exception: {}", response);
|
||||||
auto nextInstructions = findNextInstruction(ppcInterpreterCurrentInstance->instructionPointer, ppcInterpreterCurrentInstance->spr.LR, ppcInterpreterCurrentInstance->spr.CTR);
|
auto nextInstructions = findNextInstruction(hCPU->instructionPointer, hCPU->spr.LR, hCPU->spr.CTR);
|
||||||
for (MPTR nextInstr : nextInstructions)
|
for (MPTR nextInstr : nextInstructions)
|
||||||
{
|
{
|
||||||
auto bpIt = m_patchedInstructions.find(nextInstr);
|
auto bpIt = m_patchedInstructions.find(nextInstr);
|
||||||
|
|
|
@ -212,11 +212,12 @@ static void PPCInterpreter_SUBF(PPCInterpreter_t* hCPU, uint32 opcode)
|
||||||
|
|
||||||
static void PPCInterpreter_SUBFO(PPCInterpreter_t* hCPU, uint32 opcode)
|
static void PPCInterpreter_SUBFO(PPCInterpreter_t* hCPU, uint32 opcode)
|
||||||
{
|
{
|
||||||
// untested (Don't Starve Giant Edition uses this)
|
// Seen in Don't Starve Giant Edition and Teslagrad
|
||||||
// also used by DS Virtual Console (Super Mario 64 DS)
|
// also used by DS Virtual Console (Super Mario 64 DS)
|
||||||
PPC_OPC_TEMPL3_XO();
|
PPC_OPC_TEMPL3_XO();
|
||||||
hCPU->gpr[rD] = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1;
|
uint32 result = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1;
|
||||||
PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~hCPU->gpr[rA], hCPU->gpr[rB], hCPU->gpr[rD]));
|
PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~hCPU->gpr[rA], hCPU->gpr[rB], result));
|
||||||
|
hCPU->gpr[rD] = result;
|
||||||
if (opHasRC())
|
if (opHasRC())
|
||||||
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
|
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
|
||||||
PPCInterpreter_nextInstruction(hCPU);
|
PPCInterpreter_nextInstruction(hCPU);
|
||||||
|
|
|
@ -19,7 +19,7 @@ void PPCInterpreter_handleUnsupportedHLECall(PPCInterpreter_t* hCPU)
|
||||||
|
|
||||||
std::vector<void(*)(PPCInterpreter_t* hCPU)>* sPPCHLETable{};
|
std::vector<void(*)(PPCInterpreter_t* hCPU)>* sPPCHLETable{};
|
||||||
|
|
||||||
HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall)
|
HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall, std::string hleName)
|
||||||
{
|
{
|
||||||
if (!sPPCHLETable)
|
if (!sPPCHLETable)
|
||||||
sPPCHLETable = new std::vector<void(*)(PPCInterpreter_t* hCPU)>();
|
sPPCHLETable = new std::vector<void(*)(PPCInterpreter_t* hCPU)>();
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
thread_local PPCInterpreter_t* ppcInterpreterCurrentInstance;
|
thread_local PPCInterpreter_t* ppcInterpreterCurrentInstance;
|
||||||
|
|
||||||
// main thread instruction counter and timing
|
// main thread instruction counter and timing
|
||||||
volatile uint64 ppcMainThreadCycleCounter = 0;
|
|
||||||
uint64 ppcMainThreadDECCycleValue = 0; // value that was set to dec register
|
uint64 ppcMainThreadDECCycleValue = 0; // value that was set to dec register
|
||||||
uint64 ppcMainThreadDECCycleStart = 0; // at which cycle the dec register was set, if == 0 -> dec is 0
|
uint64 ppcMainThreadDECCycleStart = 0; // at which cycle the dec register was set, if == 0 -> dec is 0
|
||||||
uint64 ppcCyclesSince2000 = 0;
|
uint64 ppcCyclesSince2000 = 0;
|
||||||
|
@ -29,11 +28,16 @@ PPCInterpreter_t* PPCInterpreter_createInstance(unsigned int Entrypoint)
|
||||||
return pData;
|
return pData;
|
||||||
}
|
}
|
||||||
|
|
||||||
PPCInterpreter_t* PPCInterpreter_getCurrentInstance()
|
TLS_WORKAROUND_NOINLINE PPCInterpreter_t* PPCInterpreter_getCurrentInstance()
|
||||||
{
|
{
|
||||||
return ppcInterpreterCurrentInstance;
|
return ppcInterpreterCurrentInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TLS_WORKAROUND_NOINLINE void PPCInterpreter_setCurrentInstance(PPCInterpreter_t* hCPU)
|
||||||
|
{
|
||||||
|
ppcInterpreterCurrentInstance = hCPU;
|
||||||
|
}
|
||||||
|
|
||||||
uint64 PPCInterpreter_getMainCoreCycleCounter()
|
uint64 PPCInterpreter_getMainCoreCycleCounter()
|
||||||
{
|
{
|
||||||
return PPCTimer_getFromRDTSC();
|
return PPCTimer_getFromRDTSC();
|
||||||
|
@ -78,24 +82,25 @@ uint32 PPCInterpreter_getCoreIndex(PPCInterpreter_t* hCPU)
|
||||||
|
|
||||||
uint32 PPCInterpreter_getCurrentCoreIndex()
|
uint32 PPCInterpreter_getCurrentCoreIndex()
|
||||||
{
|
{
|
||||||
return ppcInterpreterCurrentInstance->spr.UPIR;
|
return PPCInterpreter_getCurrentInstance()->spr.UPIR;
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8* PPCInterpreterGetStackPointer()
|
uint8* PPCInterpreterGetStackPointer()
|
||||||
{
|
{
|
||||||
return memory_getPointerFromVirtualOffset(ppcInterpreterCurrentInstance->gpr[1]);
|
return memory_getPointerFromVirtualOffset(PPCInterpreter_getCurrentInstance()->gpr[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset)
|
uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset)
|
||||||
{
|
{
|
||||||
uint8* result = memory_getPointerFromVirtualOffset(ppcInterpreterCurrentInstance->gpr[1] - offset);
|
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
|
||||||
ppcInterpreterCurrentInstance->gpr[1] -= offset;
|
uint8* result = memory_getPointerFromVirtualOffset(hCPU->gpr[1] - offset);
|
||||||
|
hCPU->gpr[1] -= offset;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PPCInterpreterModifyStackPointer(sint32 offset)
|
void PPCInterpreterModifyStackPointer(sint32 offset)
|
||||||
{
|
{
|
||||||
ppcInterpreterCurrentInstance->gpr[1] -= offset;
|
PPCInterpreter_getCurrentInstance()->gpr[1] -= offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 RPLLoader_MakePPCCallable(void(*ppcCallableExport)(PPCInterpreter_t* hCPU));
|
uint32 RPLLoader_MakePPCCallable(void(*ppcCallableExport)(PPCInterpreter_t* hCPU));
|
||||||
|
|
|
@ -5,8 +5,28 @@ struct PPCCoreCallbackData_t
|
||||||
{
|
{
|
||||||
sint32 gprCount = 0;
|
sint32 gprCount = 0;
|
||||||
sint32 floatCount = 0;
|
sint32 floatCount = 0;
|
||||||
|
sint32 stackCount = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline void _PPCCoreCallback_writeGPRArg(PPCCoreCallbackData_t& data, PPCInterpreter_t* hCPU, uint32 value)
|
||||||
|
{
|
||||||
|
if (data.gprCount < 8)
|
||||||
|
{
|
||||||
|
hCPU->gpr[3 + data.gprCount] = value;
|
||||||
|
data.gprCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint32 stackOffset = 8 + data.stackCount * 4;
|
||||||
|
|
||||||
|
// PPCCore_executeCallbackInternal does -16*4 to save the current stack area
|
||||||
|
stackOffset -= 16 * 4;
|
||||||
|
|
||||||
|
memory_writeU32(hCPU->gpr[1] + stackOffset, value);
|
||||||
|
data.stackCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// callback functions
|
// callback functions
|
||||||
inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data)
|
inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data)
|
||||||
{
|
{
|
||||||
|
@ -16,22 +36,21 @@ inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data)
|
||||||
template <typename T, typename... TArgs>
|
template <typename T, typename... TArgs>
|
||||||
uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, TArgs... args)
|
uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, TArgs... args)
|
||||||
{
|
{
|
||||||
cemu_assert_debug(data.gprCount <= 8);
|
// TODO float arguments on stack
|
||||||
cemu_assert_debug(data.floatCount <= 8);
|
cemu_assert_debug(data.floatCount < 8);
|
||||||
|
|
||||||
|
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
|
||||||
if constexpr (std::is_pointer_v<T>)
|
if constexpr (std::is_pointer_v<T>)
|
||||||
{
|
{
|
||||||
ppcInterpreterCurrentInstance->gpr[3 + data.gprCount] = MEMPTR(currentArg).GetMPTR();
|
_PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(currentArg).GetMPTR());
|
||||||
data.gprCount++;
|
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_base_of_v<MEMPTRBase, std::remove_reference_t<T>>)
|
else if constexpr (std::is_base_of_v<MEMPTRBase, std::remove_reference_t<T>>)
|
||||||
{
|
{
|
||||||
ppcInterpreterCurrentInstance->gpr[3 + data.gprCount] = currentArg.GetMPTR();
|
_PPCCoreCallback_writeGPRArg(data, hCPU, currentArg.GetMPTR());
|
||||||
data.gprCount++;
|
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_reference_v<T>)
|
else if constexpr (std::is_reference_v<T>)
|
||||||
{
|
{
|
||||||
ppcInterpreterCurrentInstance->gpr[3 + data.gprCount] = MEMPTR(¤tArg).GetMPTR();
|
_PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(¤tArg).GetMPTR());
|
||||||
data.gprCount++;
|
|
||||||
}
|
}
|
||||||
else if constexpr(std::is_enum_v<T>)
|
else if constexpr(std::is_enum_v<T>)
|
||||||
{
|
{
|
||||||
|
@ -40,20 +59,19 @@ uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg,
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_floating_point_v<T>)
|
else if constexpr (std::is_floating_point_v<T>)
|
||||||
{
|
{
|
||||||
ppcInterpreterCurrentInstance->fpr[1 + data.floatCount].fpr = (double)currentArg;
|
hCPU->fpr[1 + data.floatCount].fpr = (double)currentArg;
|
||||||
data.floatCount++;
|
data.floatCount++;
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_integral_v<T> && sizeof(T) == sizeof(uint64))
|
else if constexpr (std::is_integral_v<T> && sizeof(T) == sizeof(uint64))
|
||||||
{
|
{
|
||||||
ppcInterpreterCurrentInstance->gpr[3 + data.gprCount] = (uint32)(currentArg >> 32); // high
|
hCPU->gpr[3 + data.gprCount] = (uint32)(currentArg >> 32); // high
|
||||||
ppcInterpreterCurrentInstance->gpr[3 + data.gprCount + 1] = (uint32)currentArg; // low
|
hCPU->gpr[3 + data.gprCount + 1] = (uint32)currentArg; // low
|
||||||
|
|
||||||
data.gprCount += 2;
|
data.gprCount += 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ppcInterpreterCurrentInstance->gpr[3 + data.gprCount] = (uint32)currentArg;
|
_PPCCoreCallback_writeGPRArg(data, hCPU, (uint32)currentArg);
|
||||||
data.gprCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return PPCCoreCallback(function, data, args...);
|
return PPCCoreCallback(function, data, args...);
|
||||||
|
|
|
@ -11,21 +11,24 @@ uint32 ppcThreadQuantum = 45000; // execute 45000 instructions before thread res
|
||||||
|
|
||||||
void PPCInterpreter_relinquishTimeslice()
|
void PPCInterpreter_relinquishTimeslice()
|
||||||
{
|
{
|
||||||
if( ppcInterpreterCurrentInstance->remainingCycles >= 0 )
|
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
|
||||||
|
if( hCPU->remainingCycles >= 0 )
|
||||||
{
|
{
|
||||||
ppcInterpreterCurrentInstance->skippedCycles = ppcInterpreterCurrentInstance->remainingCycles + 1;
|
hCPU->skippedCycles = hCPU->remainingCycles + 1;
|
||||||
ppcInterpreterCurrentInstance->remainingCycles = -1;
|
hCPU->remainingCycles = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PPCCore_boostQuantum(sint32 numCycles)
|
void PPCCore_boostQuantum(sint32 numCycles)
|
||||||
{
|
{
|
||||||
ppcInterpreterCurrentInstance->remainingCycles += numCycles;
|
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
|
||||||
|
hCPU->remainingCycles += numCycles;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PPCCore_deboostQuantum(sint32 numCycles)
|
void PPCCore_deboostQuantum(sint32 numCycles)
|
||||||
{
|
{
|
||||||
ppcInterpreterCurrentInstance->remainingCycles -= numCycles;
|
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
|
||||||
|
hCPU->remainingCycles -= numCycles;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace coreinit
|
namespace coreinit
|
||||||
|
@ -36,7 +39,7 @@ namespace coreinit
|
||||||
void PPCCore_switchToScheduler()
|
void PPCCore_switchToScheduler()
|
||||||
{
|
{
|
||||||
cemu_assert_debug(__OSHasSchedulerLock() == false); // scheduler lock must not be hold past thread time slice
|
cemu_assert_debug(__OSHasSchedulerLock() == false); // scheduler lock must not be hold past thread time slice
|
||||||
cemu_assert_debug(ppcInterpreterCurrentInstance->coreInterruptMask != 0 || CafeSystem::GetForegroundTitleId() == 0x000500001019e600);
|
cemu_assert_debug(PPCInterpreter_getCurrentInstance()->coreInterruptMask != 0 || CafeSystem::GetForegroundTitleId() == 0x000500001019e600);
|
||||||
__OSLockScheduler();
|
__OSLockScheduler();
|
||||||
coreinit::__OSThreadSwitchToNext();
|
coreinit::__OSThreadSwitchToNext();
|
||||||
__OSUnlockScheduler();
|
__OSUnlockScheduler();
|
||||||
|
@ -45,7 +48,7 @@ void PPCCore_switchToScheduler()
|
||||||
void PPCCore_switchToSchedulerWithLock()
|
void PPCCore_switchToSchedulerWithLock()
|
||||||
{
|
{
|
||||||
cemu_assert_debug(__OSHasSchedulerLock() == true); // scheduler lock must be hold
|
cemu_assert_debug(__OSHasSchedulerLock() == true); // scheduler lock must be hold
|
||||||
cemu_assert_debug(ppcInterpreterCurrentInstance->coreInterruptMask != 0 || CafeSystem::GetForegroundTitleId() == 0x000500001019e600);
|
cemu_assert_debug(PPCInterpreter_getCurrentInstance()->coreInterruptMask != 0 || CafeSystem::GetForegroundTitleId() == 0x000500001019e600);
|
||||||
coreinit::__OSThreadSwitchToNext();
|
coreinit::__OSThreadSwitchToNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +61,7 @@ void _PPCCore_callbackExit(PPCInterpreter_t* hCPU)
|
||||||
PPCInterpreter_t* PPCCore_executeCallbackInternal(uint32 functionMPTR)
|
PPCInterpreter_t* PPCCore_executeCallbackInternal(uint32 functionMPTR)
|
||||||
{
|
{
|
||||||
cemu_assert_debug(functionMPTR != 0);
|
cemu_assert_debug(functionMPTR != 0);
|
||||||
PPCInterpreter_t* hCPU = ppcInterpreterCurrentInstance;
|
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
|
||||||
// remember LR and instruction pointer
|
// remember LR and instruction pointer
|
||||||
uint32 lr = hCPU->spr.LR;
|
uint32 lr = hCPU->spr.LR;
|
||||||
uint32 ip = hCPU->instructionPointer;
|
uint32 ip = hCPU->instructionPointer;
|
||||||
|
|
|
@ -220,7 +220,7 @@ void PPCCoreLLE_startSingleCoreScheduler(uint32 entrypoint)
|
||||||
for (uint32 coreIndex = 0; coreIndex < 3; coreIndex++)
|
for (uint32 coreIndex = 0; coreIndex < 3; coreIndex++)
|
||||||
{
|
{
|
||||||
PPCInterpreter_t* hCPU = cpuContext->cores+coreIndex;
|
PPCInterpreter_t* hCPU = cpuContext->cores+coreIndex;
|
||||||
ppcInterpreterCurrentInstance = hCPU;
|
PPCInterpreter_setCurrentInstance(hCPU);
|
||||||
if (coreIndex == 1)
|
if (coreIndex == 1)
|
||||||
{
|
{
|
||||||
// check SCR core 1 enable bit
|
// check SCR core 1 enable bit
|
||||||
|
|
|
@ -149,6 +149,7 @@ static uint64 PPCInterpreter_getCallParamU64(PPCInterpreter_t* hCPU, uint32 inde
|
||||||
|
|
||||||
PPCInterpreter_t* PPCInterpreter_createInstance(unsigned int Entrypoint);
|
PPCInterpreter_t* PPCInterpreter_createInstance(unsigned int Entrypoint);
|
||||||
PPCInterpreter_t* PPCInterpreter_getCurrentInstance();
|
PPCInterpreter_t* PPCInterpreter_getCurrentInstance();
|
||||||
|
void PPCInterpreter_setCurrentInstance(PPCInterpreter_t* hCPU);
|
||||||
|
|
||||||
uint64 PPCInterpreter_getMainCoreCycleCounter();
|
uint64 PPCInterpreter_getMainCoreCycleCounter();
|
||||||
|
|
||||||
|
@ -192,7 +193,6 @@ uint32 PPCInterpreter_getCurrentCoreIndex();
|
||||||
void PPCInterpreter_setDEC(PPCInterpreter_t* hCPU, uint32 newValue);
|
void PPCInterpreter_setDEC(PPCInterpreter_t* hCPU, uint32 newValue);
|
||||||
|
|
||||||
// timing for main processor
|
// timing for main processor
|
||||||
extern volatile uint64 ppcMainThreadCycleCounter;
|
|
||||||
extern uint64 ppcCyclesSince2000; // on init this is set to the cycles that passed since 1.1.2000
|
extern uint64 ppcCyclesSince2000; // on init this is set to the cycles that passed since 1.1.2000
|
||||||
extern uint64 ppcCyclesSince2000TimerClock; // on init this is set to the cycles that passed since 1.1.2000 / 20
|
extern uint64 ppcCyclesSince2000TimerClock; // on init this is set to the cycles that passed since 1.1.2000 / 20
|
||||||
extern uint64 ppcCyclesSince2000_UTC;
|
extern uint64 ppcCyclesSince2000_UTC;
|
||||||
|
@ -213,8 +213,7 @@ void PPCTimer_start();
|
||||||
// core info and control
|
// core info and control
|
||||||
extern uint32 ppcThreadQuantum;
|
extern uint32 ppcThreadQuantum;
|
||||||
|
|
||||||
extern thread_local PPCInterpreter_t *ppcInterpreterCurrentInstance;
|
uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset);
|
||||||
uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset);
|
|
||||||
uint8* PPCInterpreterGetStackPointer();
|
uint8* PPCInterpreterGetStackPointer();
|
||||||
void PPCInterpreterModifyStackPointer(sint32 offset);
|
void PPCInterpreterModifyStackPointer(sint32 offset);
|
||||||
|
|
||||||
|
@ -231,7 +230,7 @@ static inline float flushDenormalToZero(float f)
|
||||||
typedef void(*HLECALL)(PPCInterpreter_t* hCPU);
|
typedef void(*HLECALL)(PPCInterpreter_t* hCPU);
|
||||||
|
|
||||||
typedef sint32 HLEIDX;
|
typedef sint32 HLEIDX;
|
||||||
HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall);
|
HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall, std::string hleName);
|
||||||
HLECALL PPCInterpreter_getHLECall(HLEIDX funcIndex);
|
HLECALL PPCInterpreter_getHLECall(HLEIDX funcIndex);
|
||||||
|
|
||||||
// HLE scheduler
|
// HLE scheduler
|
||||||
|
|
|
@ -294,7 +294,7 @@ std::atomic_bool s_recompilerThreadStopSignal{false};
|
||||||
|
|
||||||
void PPCRecompiler_thread()
|
void PPCRecompiler_thread()
|
||||||
{
|
{
|
||||||
SetThreadName("PPCRecompiler_thread");
|
SetThreadName("PPCRecompiler");
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if(s_recompilerThreadStopSignal)
|
if(s_recompilerThreadStopSignal)
|
||||||
|
@ -325,6 +325,8 @@ void PPCRecompiler_thread()
|
||||||
PPCRecompilerState.recompilerSpinlock.unlock();
|
PPCRecompilerState.recompilerSpinlock.unlock();
|
||||||
|
|
||||||
PPCRecompiler_recompileAtAddress(enterAddress);
|
PPCRecompiler_recompileAtAddress(enterAddress);
|
||||||
|
if(s_recompilerThreadStopSignal)
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ void* ATTR_MS_ABI PPCRecompiler_virtualHLE(PPCInterpreter_t* hCPU, uint32 hleFun
|
||||||
hCPU->remainingCycles -= 500; // let subtract about 500 cycles for each HLE call
|
hCPU->remainingCycles -= 500; // let subtract about 500 cycles for each HLE call
|
||||||
hCPU->gpr[3] = 0;
|
hCPU->gpr[3] = 0;
|
||||||
PPCInterpreter_nextInstruction(hCPU);
|
PPCInterpreter_nextInstruction(hCPU);
|
||||||
return ppcInterpreterCurrentInstance;
|
return hCPU;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -109,18 +109,18 @@ void* ATTR_MS_ABI PPCRecompiler_virtualHLE(PPCInterpreter_t* hCPU, uint32 hleFun
|
||||||
hleCall(hCPU);
|
hleCall(hCPU);
|
||||||
}
|
}
|
||||||
hCPU->rspTemp = prevRSPTemp;
|
hCPU->rspTemp = prevRSPTemp;
|
||||||
return ppcInterpreterCurrentInstance;
|
return PPCInterpreter_getCurrentInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATTR_MS_ABI PPCRecompiler_getTBL(PPCInterpreter_t* hCPU, uint32 gprIndex)
|
void ATTR_MS_ABI PPCRecompiler_getTBL(PPCInterpreter_t* hCPU, uint32 gprIndex)
|
||||||
{
|
{
|
||||||
uint64 coreTime = coreinit::coreinit_getTimerTick();
|
uint64 coreTime = coreinit::OSGetSystemTime();
|
||||||
hCPU->gpr[gprIndex] = (uint32)(coreTime&0xFFFFFFFF);
|
hCPU->gpr[gprIndex] = (uint32)(coreTime&0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATTR_MS_ABI PPCRecompiler_getTBU(PPCInterpreter_t* hCPU, uint32 gprIndex)
|
void ATTR_MS_ABI PPCRecompiler_getTBU(PPCInterpreter_t* hCPU, uint32 gprIndex)
|
||||||
{
|
{
|
||||||
uint64 coreTime = coreinit::coreinit_getTimerTick();
|
uint64 coreTime = coreinit::OSGetSystemTime();
|
||||||
hCPU->gpr[gprIndex] = (uint32)((coreTime>>32)&0xFFFFFFFF);
|
hCPU->gpr[gprIndex] = (uint32)((coreTime>>32)&0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -228,13 +228,13 @@ void _fetchShaderDecompiler_parseInstruction_VTX_SEMANTIC(LatteFetchShader* pars
|
||||||
else if (srcSelX == LatteClauseInstruction_VTX::SRC_SEL::SEL_Y)
|
else if (srcSelX == LatteClauseInstruction_VTX::SRC_SEL::SEL_Y)
|
||||||
{
|
{
|
||||||
// use alu divisor 1
|
// use alu divisor 1
|
||||||
attribGroup->attrib[groupAttribIndex].aluDivisor = (sint32)contextRegister[mmVGT_INSTANCE_STEP_RATE_0 + 0];
|
attribGroup->attrib[groupAttribIndex].aluDivisor = (sint32)contextRegister[Latte::REGADDR::VGT_INSTANCE_STEP_RATE_0];
|
||||||
cemu_assert_debug(attribGroup->attrib[groupAttribIndex].aluDivisor > 0);
|
cemu_assert_debug(attribGroup->attrib[groupAttribIndex].aluDivisor > 0);
|
||||||
}
|
}
|
||||||
else if (srcSelX == LatteClauseInstruction_VTX::SRC_SEL::SEL_Z)
|
else if (srcSelX == LatteClauseInstruction_VTX::SRC_SEL::SEL_Z)
|
||||||
{
|
{
|
||||||
// use alu divisor 2
|
// use alu divisor 2
|
||||||
attribGroup->attrib[groupAttribIndex].aluDivisor = (sint32)contextRegister[mmVGT_INSTANCE_STEP_RATE_0 + 1];
|
attribGroup->attrib[groupAttribIndex].aluDivisor = (sint32)contextRegister[Latte::REGADDR::VGT_INSTANCE_STEP_RATE_1];
|
||||||
cemu_assert_debug(attribGroup->attrib[groupAttribIndex].aluDivisor > 0);
|
cemu_assert_debug(attribGroup->attrib[groupAttribIndex].aluDivisor > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ struct LatteGPUState_t
|
||||||
// context control
|
// context control
|
||||||
uint32 contextControl0;
|
uint32 contextControl0;
|
||||||
uint32 contextControl1;
|
uint32 contextControl1;
|
||||||
|
// optional features
|
||||||
|
bool allowFramebufferSizeOptimization{false}; // allow using scissor box as size hint to determine non-padded rendertarget size
|
||||||
// draw context
|
// draw context
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
|
@ -50,7 +52,7 @@ struct LatteGPUState_t
|
||||||
uint32 gx2InitCalled; // incremented every time GX2Init() is called
|
uint32 gx2InitCalled; // incremented every time GX2Init() is called
|
||||||
// OpenGL control
|
// OpenGL control
|
||||||
uint32 glVendor; // GLVENDOR_*
|
uint32 glVendor; // GLVENDOR_*
|
||||||
bool alwaysDisplayDRC = false;
|
bool isDRCPrimary = false;
|
||||||
// temporary (replace with proper solution later)
|
// temporary (replace with proper solution later)
|
||||||
bool tvBufferUsesSRGB;
|
bool tvBufferUsesSRGB;
|
||||||
bool drcBufferUsesSRGB;
|
bool drcBufferUsesSRGB;
|
||||||
|
@ -98,7 +100,7 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa
|
||||||
void LatteRenderTarget_GetCurrentVirtualViewportSize(sint32* viewportWidth, sint32* viewportHeight);
|
void LatteRenderTarget_GetCurrentVirtualViewportSize(sint32* viewportWidth, sint32* viewportHeight);
|
||||||
|
|
||||||
void LatteRenderTarget_itHLESwapScanBuffer();
|
void LatteRenderTarget_itHLESwapScanBuffer();
|
||||||
void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorBufferMPTR, MPTR colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, MPTR depthBufferMPTR, MPTR depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, float r, float g, float b, float a, float clearDepth, uint32 clearStencil);
|
void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorBufferMPTR, Latte::E_GX2SURFFMT colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, MPTR depthBufferMPTR, Latte::E_GX2SURFFMT depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, float r, float g, float b, float a, float clearDepth, uint32 clearStencil);
|
||||||
void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferSliceIndex, uint32 colorBufferFormat, uint32 colorBufferPitch, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferSwizzle, uint32 renderTarget);
|
void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferSliceIndex, uint32 colorBufferFormat, uint32 colorBufferPitch, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferSwizzle, uint32 renderTarget);
|
||||||
|
|
||||||
void LatteRenderTarget_unloadAll();
|
void LatteRenderTarget_unloadAll();
|
||||||
|
@ -115,7 +117,7 @@ void LatteTC_RegisterTexture(LatteTexture* tex);
|
||||||
void LatteTC_UnregisterTexture(LatteTexture* tex);
|
void LatteTC_UnregisterTexture(LatteTexture* tex);
|
||||||
|
|
||||||
uint32 LatteTexture_CalculateTextureDataHash(LatteTexture* hostTexture);
|
uint32 LatteTexture_CalculateTextureDataHash(LatteTexture* hostTexture);
|
||||||
void LatteTexture_ReloadData(LatteTexture* hostTexture, uint32 textureUnit);
|
void LatteTexture_ReloadData(LatteTexture* hostTexture);
|
||||||
|
|
||||||
bool LatteTC_HasTextureChanged(LatteTexture* hostTexture, bool force = false);
|
bool LatteTC_HasTextureChanged(LatteTexture* hostTexture, bool force = false);
|
||||||
void LatteTC_ResetTextureChangeTracker(LatteTexture* hostTexture, bool force = false);
|
void LatteTC_ResetTextureChangeTracker(LatteTexture* hostTexture, bool force = false);
|
||||||
|
@ -167,7 +169,7 @@ void LatteBufferCache_LoadRemappedUniforms(struct LatteDecompilerShader* shader,
|
||||||
|
|
||||||
void LatteRenderTarget_updateViewport();
|
void LatteRenderTarget_updateViewport();
|
||||||
|
|
||||||
#define LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE (1024) // maximum size for uniform blocks (in vec4s). On Nvidia hardware 4096 is the maximum (64K / 16 = 4096) all other vendors have much higher limits
|
#define LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE (4096) // maximum size for uniform blocks (in vec4s). On Nvidia hardware 4096 is the maximum (64K / 16 = 4096) all other vendors have much higher limits
|
||||||
|
|
||||||
//static uint32 glTempError;
|
//static uint32 glTempError;
|
||||||
//#define catchOpenGLError() glFinish(); if( (glTempError = glGetError()) != 0 ) { printf("OpenGL error 0x%x: %s : %d timestamp %08x\n", glTempError, __FILE__, __LINE__, GetTickCount()); __debugbreak(); }
|
//#define catchOpenGLError() glFinish(); if( (glTempError = glGetError()) != 0 ) { printf("OpenGL error 0x%x: %s : %d timestamp %08x\n", glTempError, __FILE__, __LINE__, GetTickCount()); __debugbreak(); }
|
||||||
|
|
|
@ -309,7 +309,7 @@ public:
|
||||||
{
|
{
|
||||||
if ((rangeBegin & 0xF))
|
if ((rangeBegin & 0xF))
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force, "writeStreamout(): RangeBegin not aligned to 16. Begin {:08x} End {:08x}", rangeBegin, rangeEnd);
|
cemuLog_logDebugOnce(LogType::Force, "writeStreamout(): RangeBegin not aligned to 16. Begin {:08x} End {:08x}", rangeBegin, rangeEnd);
|
||||||
rangeBegin = (rangeBegin + 0xF) & ~0xF;
|
rangeBegin = (rangeBegin + 0xF) & ~0xF;
|
||||||
rangeEnd = std::max(rangeBegin, rangeEnd);
|
rangeEnd = std::max(rangeBegin, rangeEnd);
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,22 +132,18 @@ void LatteBufferCache_syncGPUUniformBuffers(LatteDecompilerShader* shader, const
|
||||||
{
|
{
|
||||||
if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK)
|
if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK)
|
||||||
{
|
{
|
||||||
// use full uniform buffers
|
for(const auto& buf : shader->list_quickBufferList)
|
||||||
for (sint32 t = 0; t < shader->uniformBufferListCount; t++)
|
|
||||||
{
|
{
|
||||||
sint32 i = shader->uniformBufferList[t];
|
sint32 i = buf.index;
|
||||||
MPTR physicalAddr = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 0];
|
MPTR physicalAddr = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 0];
|
||||||
uint32 uniformSize = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 1] + 1;
|
uint32 uniformSize = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 1] + 1;
|
||||||
|
if (physicalAddr == MPTR_NULL) [[unlikely]]
|
||||||
if (physicalAddr == MPTR_NULL)
|
|
||||||
{
|
{
|
||||||
// no data
|
|
||||||
g_renderer->buffer_bindUniformBuffer(shaderType, i, 0, 0);
|
g_renderer->buffer_bindUniformBuffer(shaderType, i, 0, 0);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
uniformSize = std::min<uint32>(uniformSize, buf.size);
|
||||||
uint32 bindOffset = LatteBufferCache_retrieveDataInCache(physicalAddr, uniformSize);
|
uint32 bindOffset = LatteBufferCache_retrieveDataInCache(physicalAddr, uniformSize);
|
||||||
|
|
||||||
g_renderer->buffer_bindUniformBuffer(shaderType, i, bindOffset, uniformSize);
|
g_renderer->buffer_bindUniformBuffer(shaderType, i, bindOffset, uniformSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ private:
|
||||||
if(colorBuffer[i].texture == nullptr)
|
if(colorBuffer[i].texture == nullptr)
|
||||||
continue;
|
continue;
|
||||||
sint32 effectiveWidth, effectiveHeight;
|
sint32 effectiveWidth, effectiveHeight;
|
||||||
LatteTexture_getEffectiveSize(colorBuffer[i].texture->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, colorBuffer[i].texture->firstMip);
|
colorBuffer[i].texture->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, colorBuffer[i].texture->firstMip);
|
||||||
if (rtEffectiveSize.x == 0 && rtEffectiveSize.y == 0)
|
if (rtEffectiveSize.x == 0 && rtEffectiveSize.y == 0)
|
||||||
{
|
{
|
||||||
rtEffectiveSize.x = effectiveWidth;
|
rtEffectiveSize.x = effectiveWidth;
|
||||||
|
@ -64,7 +64,7 @@ private:
|
||||||
if (depthBuffer.texture)
|
if (depthBuffer.texture)
|
||||||
{
|
{
|
||||||
sint32 effectiveWidth, effectiveHeight;
|
sint32 effectiveWidth, effectiveHeight;
|
||||||
LatteTexture_getEffectiveSize(depthBuffer.texture->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, depthBuffer.texture->firstMip);
|
depthBuffer.texture->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, depthBuffer.texture->firstMip);
|
||||||
if (rtEffectiveSize.x == 0 && rtEffectiveSize.y == 0)
|
if (rtEffectiveSize.x == 0 && rtEffectiveSize.y == 0)
|
||||||
{
|
{
|
||||||
rtEffectiveSize.x = effectiveWidth;
|
rtEffectiveSize.x = effectiveWidth;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,21 +1,27 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Cafe/HW/Latte/ISA/LatteReg.h"
|
#include "Cafe/HW/Latte/ISA/LatteReg.h"
|
||||||
|
|
||||||
// this file contains legacy C-style defines, modernize and merge into LatteReg.h
|
// todo - this file contains legacy C-style defines, modernize and merge into LatteReg.h
|
||||||
|
|
||||||
// GPU7/Latte hardware info
|
// GPU7/Latte hardware info
|
||||||
|
|
||||||
#define LATTE_NUM_GPR (128)
|
#define LATTE_NUM_GPR 128
|
||||||
#define LATTE_NUM_STREAMOUT_BUFFER (4)
|
#define LATTE_NUM_STREAMOUT_BUFFER 4
|
||||||
#define LATTE_NUM_COLOR_TARGET (8)
|
#define LATTE_NUM_COLOR_TARGET 8
|
||||||
|
|
||||||
#define LATTE_NUM_MAX_TEX_UNITS (18) // number of available texture units per shader stage (this might be higher than 18? BotW is the only game which uses more than 16?)
|
#define LATTE_NUM_MAX_TEX_UNITS 18 // number of available texture units per shader stage (this might be higher than 18? BotW is the only game which uses more than 16?)
|
||||||
#define LATTE_NUM_MAX_UNIFORM_BUFFERS (16) // number of supported uniform buffer binding locations
|
#define LATTE_NUM_MAX_UNIFORM_BUFFERS 16 // number of supported uniform buffer binding locations
|
||||||
|
|
||||||
#define LATTE_VS_ATTRIBUTE_LIMIT (32) // todo: verify
|
#define LATTE_VS_ATTRIBUTE_LIMIT 32 // todo: verify
|
||||||
#define LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS (256) // should this be 128 since there are only 128 GPRs?
|
#define LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS 256 // should this be 128 since there are only 128 GPRs?
|
||||||
|
|
||||||
#define LATTE_MAX_VERTEX_BUFFERS (16)
|
#define LATTE_MAX_VERTEX_BUFFERS 16
|
||||||
|
|
||||||
|
// Cemu-specific constants
|
||||||
|
|
||||||
|
#define LATTE_CEMU_PS_TEX_UNIT_BASE 0
|
||||||
|
#define LATTE_CEMU_VS_TEX_UNIT_BASE 32
|
||||||
|
#define LATTE_CEMU_GS_TEX_UNIT_BASE 64
|
||||||
|
|
||||||
// vertex formats
|
// vertex formats
|
||||||
|
|
||||||
|
@ -76,8 +82,6 @@
|
||||||
#define GLVENDOR_UNKNOWN (0)
|
#define GLVENDOR_UNKNOWN (0)
|
||||||
#define GLVENDOR_AMD (1) // AMD/ATI
|
#define GLVENDOR_AMD (1) // AMD/ATI
|
||||||
#define GLVENDOR_NVIDIA (2)
|
#define GLVENDOR_NVIDIA (2)
|
||||||
#define GLVENDOR_INTEL_LEGACY (3)
|
|
||||||
#define GLVENDOR_INTEL_NOLEGACY (4)
|
|
||||||
#define GLVENDOR_INTEL (5)
|
#define GLVENDOR_INTEL (5)
|
||||||
#define GLVENDOR_APPLE (6)
|
#define GLVENDOR_APPLE (6)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "Cafe/HW/Latte/Core/LatteConst.h"
|
#include "Cafe/HW/Latte/Core/LatteConst.h"
|
||||||
#include "Cafe/HW/Latte/Renderer/Renderer.h"
|
#include "Cafe/HW/Latte/Renderer/Renderer.h"
|
||||||
#include "Cafe/HW/Latte/ISA/RegDefines.h"
|
#include "Cafe/HW/Latte/ISA/RegDefines.h"
|
||||||
|
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
|
||||||
#include "Common/cpu_features.h"
|
#include "Common/cpu_features.h"
|
||||||
|
|
||||||
#if defined(ARCH_X86_64) && defined(__GNUC__)
|
#if defined(ARCH_X86_64) && defined(__GNUC__)
|
||||||
|
@ -9,32 +10,53 @@
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
|
struct CacheEntry
|
||||||
|
{
|
||||||
|
// input data
|
||||||
const void* lastPtr;
|
const void* lastPtr;
|
||||||
uint32 lastCount;
|
uint32 lastCount;
|
||||||
LattePrimitiveMode lastPrimitiveMode;
|
LattePrimitiveMode lastPrimitiveMode;
|
||||||
LatteIndexType lastIndexType;
|
LatteIndexType lastIndexType;
|
||||||
|
uint64 lastUsed;
|
||||||
// output
|
// output
|
||||||
uint32 indexMin;
|
uint32 indexMin;
|
||||||
uint32 indexMax;
|
uint32 indexMax;
|
||||||
Renderer::INDEX_TYPE renderIndexType;
|
Renderer::INDEX_TYPE renderIndexType;
|
||||||
uint32 outputCount;
|
uint32 outputCount;
|
||||||
uint32 indexBufferOffset;
|
Renderer::IndexAllocation indexAllocation;
|
||||||
uint32 indexBufferIndex;
|
};
|
||||||
|
std::array<CacheEntry, 8> entry;
|
||||||
|
uint64 currentUsageCounter{0};
|
||||||
}LatteIndexCache{};
|
}LatteIndexCache{};
|
||||||
|
|
||||||
void LatteIndices_invalidate(const void* memPtr, uint32 size)
|
void LatteIndices_invalidate(const void* memPtr, uint32 size)
|
||||||
{
|
{
|
||||||
if (LatteIndexCache.lastPtr >= memPtr && (LatteIndexCache.lastPtr < ((uint8*)memPtr + size)) )
|
for(auto& entry : LatteIndexCache.entry)
|
||||||
{
|
{
|
||||||
LatteIndexCache.lastPtr = nullptr;
|
if (entry.lastPtr >= memPtr && (entry.lastPtr < ((uint8*)memPtr + size)) )
|
||||||
LatteIndexCache.lastCount = 0;
|
{
|
||||||
|
if(entry.lastPtr != nullptr)
|
||||||
|
g_renderer->indexData_releaseIndexMemory(entry.indexAllocation);
|
||||||
|
entry.lastPtr = nullptr;
|
||||||
|
entry.lastCount = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LatteIndices_invalidateAll()
|
void LatteIndices_invalidateAll()
|
||||||
{
|
{
|
||||||
LatteIndexCache.lastPtr = nullptr;
|
for(auto& entry : LatteIndexCache.entry)
|
||||||
LatteIndexCache.lastCount = 0;
|
{
|
||||||
|
if (entry.lastPtr != nullptr)
|
||||||
|
g_renderer->indexData_releaseIndexMemory(entry.indexAllocation);
|
||||||
|
entry.lastPtr = nullptr;
|
||||||
|
entry.lastCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 LatteIndices_GetNextUsageIndex()
|
||||||
|
{
|
||||||
|
return LatteIndexCache.currentUsageCounter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 LatteIndices_calculateIndexOutputSize(LattePrimitiveMode primitiveMode, LatteIndexType indexType, uint32 count)
|
uint32 LatteIndices_calculateIndexOutputSize(LattePrimitiveMode primitiveMode, LatteIndexType indexType, uint32 count)
|
||||||
|
@ -532,7 +554,7 @@ void LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, LatteIn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, uint32& indexBufferOffset, uint32& indexBufferIndex)
|
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation)
|
||||||
{
|
{
|
||||||
// what this should do:
|
// what this should do:
|
||||||
// [x] use fast SIMD-based index decoding
|
// [x] use fast SIMD-based index decoding
|
||||||
|
@ -542,17 +564,18 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||||
// [ ] better cache implementation, allow to cache across frames
|
// [ ] better cache implementation, allow to cache across frames
|
||||||
|
|
||||||
// reuse from cache if data didn't change
|
// reuse from cache if data didn't change
|
||||||
if (LatteIndexCache.lastPtr == indexData &&
|
auto cacheEntry = std::find_if(LatteIndexCache.entry.begin(), LatteIndexCache.entry.end(), [indexData, count, primitiveMode, indexType](const auto& entry)
|
||||||
LatteIndexCache.lastCount == count &&
|
|
||||||
LatteIndexCache.lastPrimitiveMode == primitiveMode &&
|
|
||||||
LatteIndexCache.lastIndexType == indexType)
|
|
||||||
{
|
{
|
||||||
indexMin = LatteIndexCache.indexMin;
|
return entry.lastPtr == indexData && entry.lastCount == count && entry.lastPrimitiveMode == primitiveMode && entry.lastIndexType == indexType;
|
||||||
indexMax = LatteIndexCache.indexMax;
|
});
|
||||||
renderIndexType = LatteIndexCache.renderIndexType;
|
if (cacheEntry != LatteIndexCache.entry.end())
|
||||||
outputCount = LatteIndexCache.outputCount;
|
{
|
||||||
indexBufferOffset = LatteIndexCache.indexBufferOffset;
|
indexMin = cacheEntry->indexMin;
|
||||||
indexBufferIndex = LatteIndexCache.indexBufferIndex;
|
indexMax = cacheEntry->indexMax;
|
||||||
|
renderIndexType = cacheEntry->renderIndexType;
|
||||||
|
outputCount = cacheEntry->outputCount;
|
||||||
|
indexAllocation = cacheEntry->indexAllocation;
|
||||||
|
cacheEntry->lastUsed = LatteIndices_GetNextUsageIndex();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,10 +599,12 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||||
indexMin = 0;
|
indexMin = 0;
|
||||||
indexMax = std::max(count, 1u)-1;
|
indexMax = std::max(count, 1u)-1;
|
||||||
renderIndexType = Renderer::INDEX_TYPE::NONE;
|
renderIndexType = Renderer::INDEX_TYPE::NONE;
|
||||||
|
indexAllocation = {};
|
||||||
return; // no indices
|
return; // no indices
|
||||||
}
|
}
|
||||||
// query index buffer from renderer
|
// query index buffer from renderer
|
||||||
void* indexOutputPtr = g_renderer->indexData_reserveIndexMemory(indexOutputSize, indexBufferOffset, indexBufferIndex);
|
indexAllocation = g_renderer->indexData_reserveIndexMemory(indexOutputSize);
|
||||||
|
void* indexOutputPtr = indexAllocation.mem;
|
||||||
|
|
||||||
// decode indices
|
// decode indices
|
||||||
indexMin = std::numeric_limits<uint32>::max();
|
indexMin = std::numeric_limits<uint32>::max();
|
||||||
|
@ -704,16 +729,25 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||||
// recalculate index range but filter out primitive restart index
|
// recalculate index range but filter out primitive restart index
|
||||||
LatteIndices_alternativeCalculateIndexMinMax(indexData, indexType, count, indexMin, indexMax);
|
LatteIndices_alternativeCalculateIndexMinMax(indexData, indexType, count, indexMin, indexMax);
|
||||||
}
|
}
|
||||||
g_renderer->indexData_uploadIndexMemory(indexBufferOffset, indexOutputSize);
|
g_renderer->indexData_uploadIndexMemory(indexAllocation);
|
||||||
|
performanceMonitor.cycle[performanceMonitor.cycleIndex].indexDataUploaded += indexOutputSize;
|
||||||
|
// get least recently used cache entry
|
||||||
|
auto lruEntry = std::min_element(LatteIndexCache.entry.begin(), LatteIndexCache.entry.end(), [](const auto& a, const auto& b)
|
||||||
|
{
|
||||||
|
return a.lastUsed < b.lastUsed;
|
||||||
|
});
|
||||||
|
// invalidate previous allocation
|
||||||
|
if(lruEntry->lastPtr != nullptr)
|
||||||
|
g_renderer->indexData_releaseIndexMemory(lruEntry->indexAllocation);
|
||||||
// update cache
|
// update cache
|
||||||
LatteIndexCache.lastPtr = indexData;
|
lruEntry->lastPtr = indexData;
|
||||||
LatteIndexCache.lastCount = count;
|
lruEntry->lastCount = count;
|
||||||
LatteIndexCache.lastPrimitiveMode = primitiveMode;
|
lruEntry->lastPrimitiveMode = primitiveMode;
|
||||||
LatteIndexCache.lastIndexType = indexType;
|
lruEntry->lastIndexType = indexType;
|
||||||
LatteIndexCache.indexMin = indexMin;
|
lruEntry->indexMin = indexMin;
|
||||||
LatteIndexCache.indexMax = indexMax;
|
lruEntry->indexMax = indexMax;
|
||||||
LatteIndexCache.renderIndexType = renderIndexType;
|
lruEntry->renderIndexType = renderIndexType;
|
||||||
LatteIndexCache.outputCount = outputCount;
|
lruEntry->outputCount = outputCount;
|
||||||
LatteIndexCache.indexBufferOffset = indexBufferOffset;
|
lruEntry->indexAllocation = indexAllocation;
|
||||||
LatteIndexCache.indexBufferIndex = indexBufferIndex;
|
lruEntry->lastUsed = LatteIndices_GetNextUsageIndex();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,4 +4,4 @@
|
||||||
|
|
||||||
void LatteIndices_invalidate(const void* memPtr, uint32 size);
|
void LatteIndices_invalidate(const void* memPtr, uint32 size);
|
||||||
void LatteIndices_invalidateAll();
|
void LatteIndices_invalidateAll();
|
||||||
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, uint32& indexBufferOffset, uint32& indexBufferIndex);
|
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation);
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue