Compare commits
1 Commits
dmnt2
...
pipelinede
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a20d3f6799 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -37,8 +37,6 @@ CMakeLists.txt.user*
|
||||
# *nix related
|
||||
# Common convention for backup or temporary files
|
||||
*~
|
||||
*.core
|
||||
dtrace-out/
|
||||
|
||||
# Visual Studio CMake settings
|
||||
CMakeSettings.json
|
||||
|
||||
@@ -13,6 +13,8 @@ Copyright: yuzu Emulator Project
|
||||
License: GPL-2.0-or-later
|
||||
|
||||
Files: dist/qt_themes/default/icons/256x256/eden.png
|
||||
dist/qt_themes/default/icons/256x256/eden_named.png
|
||||
dist/Assets.car
|
||||
dist/yuzu.bmp
|
||||
dist/eden.icns
|
||||
dist/eden.ico
|
||||
|
||||
@@ -26,9 +26,8 @@ endif()
|
||||
|
||||
# Needed for FFmpeg w/ VAAPI and DRM
|
||||
if (PLATFORM_OPENBSD)
|
||||
# OpenBSD 7.8 broke libcxx when upgrading, so we must define the PSTL backend manually
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R6/include -D_LIBCPP_PSTL_BACKEND_SERIAL=1")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R6/include -D_LIBCPP_PSTL_BACKEND_SERIAL=1")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R6/include")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R6/include")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R6/lib")
|
||||
elseif (PLATFORM_NETBSD)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include")
|
||||
|
||||
10
README.md
10
README.md
@@ -25,9 +25,9 @@ It is written in C++ with portability in mind, and we actively maintain builds f
|
||||
<img src="https://img.shields.io/discord/1367654015269339267?color=5865F2&label=Eden&logo=discord&logoColor=white"
|
||||
alt="Discord">
|
||||
</a>
|
||||
<a href="https://stt.gg/qKgFEAbH">
|
||||
<img src="https://img.shields.io/revolt/invite/qKgFEAbH?color=d61f3a&label=Stoat"
|
||||
alt="Stoat">
|
||||
<a href="https://rvlt.gg/qKgFEAbH">
|
||||
<img src="https://img.shields.io/revolt/invite/qKgFEAbH?color=d61f3a&label=Revolt"
|
||||
alt="Revolt">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -52,10 +52,10 @@ Check out our [website](https://eden-emu.dev) for the latest news on exciting fe
|
||||
|
||||
## Development
|
||||
|
||||
Most of the development happens on our Git server. It is also where [our central repository](https://git.eden-emu.dev/eden-emu/eden) is hosted. For development discussions, please join us on [Discord](https://discord.gg/HstXbPch7X) or [Stoat](https://stt.gg/qKgFEAbH).
|
||||
Most of the development happens on our Git server. It is also where [our central repository](https://git.eden-emu.dev/eden-emu/eden) is hosted. For development discussions, please join us on [Discord](https://discord.gg/HstXbPch7X) or [Revolt](https://rvlt.gg/qKgFEAbH).
|
||||
You can also follow us on [X (Twitter)](https://nitter.poast.org/edenemuofficial) for updates and announcements.
|
||||
|
||||
If you would like to contribute, we are open to new developers and pull requests. Please ensure that your work is of a high standard and properly documented. You can also contact any of the developers on Discord or Stoat to learn more about the current state of the emulator.
|
||||
If you would like to contribute, we are open to new developers and pull requests. Please ensure that your work is of a high standard and properly documented. You can also contact any of the developers on Discord or Revolt to learn more about the current state of the emulator.
|
||||
|
||||
See the [sign-up instructions](docs/SIGNUP.md) for information on registration.
|
||||
|
||||
|
||||
81
dist/eden_named.svg
vendored
Normal file
81
dist/eden_named.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 37 KiB |
@@ -137,7 +137,7 @@ If your initial configure failed:
|
||||
- Evaluate the error and find any related settings
|
||||
- See the [CPM docs](CPM.md) to see if you may need to forcefully bundle any packages
|
||||
|
||||
Otherwise, feel free to ask for help in Stoat or Discord.
|
||||
Otherwise, feel free to ask for help in Revolt or Discord.
|
||||
|
||||
## Caveats
|
||||
|
||||
|
||||
@@ -77,8 +77,6 @@ For this reason this patch is NOT applied to default on all platforms (for obvio
|
||||
|
||||
Still will not run flawlessly until `mesa-24` is available. Modify CMakeCache.txt with the `.so` of libGL and libGLESv2 by doing the incredibly difficult task of copy pasting them (`cp /boot/system/lib/libGL.so .`)
|
||||
|
||||
If you have `quazip1_qt6_devel`, uninstall it. It may call `Core5Compat` on CMake which is wrongly packaged.
|
||||
|
||||
## OpenBSD
|
||||
|
||||
After configuration, you may need to modify `externals/ffmpeg/CMakeFiles/ffmpeg-build/build.make` to use `-j$(nproc)` instead of just `-j`.
|
||||
@@ -101,17 +99,11 @@ ip6addrctl=YES
|
||||
ip6addrctl_policy=ipv4_prefer
|
||||
```
|
||||
|
||||
System provides a default `g++-10` which doesn't support the current C++ codebase; install `clang-19` with `pkgin install clang-19`. Or install `gcc14` (or `gcc15` with current pkgsrc). Provided that, the following CMake commands may work:
|
||||
|
||||
- `cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -Bbuild`
|
||||
- `cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/pkg/gcc14/bin/gcc -DCMAKE_CXX_COMPILER=/usr/pkg/gcc14/bin/g++ -Bbuild`
|
||||
- `cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/pkg/gcc15/bin/gcc -DCMAKE_CXX_COMPILER=/usr/pkg/gcc15/bin/g++ -Bbuild`
|
||||
System provides a default `g++-10` which doesn't support the current C++ codebase; install `clang-19` with `pkgin install clang-19`. Then build with `cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -B build`.
|
||||
|
||||
Make may error out when generating C++ headers of SPIRV shaders, hence it's recommended to use `gmake` over the default system one.
|
||||
|
||||
[parallel/spirv-tools](https://iso.us.netbsd.org/pub/pkgsrc/current/pkgsrc/parallel/spirv-tools/index.html) isn't available in binary form and must be build from source.
|
||||
|
||||
Such that glslang is not available on NetBSD, to circumvent this simply build glslang by yourself:
|
||||
glslang is not available on NetBSD, to circumvent this simply build glslang by yourself:
|
||||
```sh
|
||||
pkgin python313
|
||||
git clone --depth=1 https://github.com/KhronosGroup/glslang.git
|
||||
@@ -122,8 +114,6 @@ cmake --build build -- -j`nproc`
|
||||
cmake --install build
|
||||
```
|
||||
|
||||
However, pkgsrc is highly recommended, see [getting pkgsrc](https://iso.us.netbsd.org/pub/pkgsrc/current/pkgsrc/doc/pkgsrc.html#getting). You must get `current` not the `2025Q2` version.
|
||||
|
||||
# DragonFlyBSD
|
||||
|
||||
If `libstdc++.so.6` is not found (`GLIBCXX_3.4.30`) then attempt:
|
||||
|
||||
@@ -183,8 +183,6 @@ sudo dnf config-manager --enable crb
|
||||
sudo dnf install qt6-qtbase-private-devel
|
||||
```
|
||||
|
||||
For systems like OpenEuler or derivates, don't forget to also install: `SDL2-devel pkg-config fmt-dev nlohmann-json-dev`.
|
||||
|
||||
* [RPM Fusion](https://rpmfusion.org/Configuration) is required for `ffmpeg-devel`
|
||||
* Fedora 32 or later is required.
|
||||
* Fedora 36+ users with GCC 12 need Clang and should configure CMake with: `cmake -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -B build`
|
||||
@@ -243,7 +241,7 @@ brew install molten-vk vulkan-loader
|
||||
<details>
|
||||
<summary>FreeBSD</summary>
|
||||
|
||||
As root run: `pkg install devel/cmake devel/sdl20 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib devel/unordered-dense mbedtls3 vulkan-headers quazip-qt6`
|
||||
As root run: `pkg install devel/cmake devel/sdl20 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib devel/jwt-cpp devel/unordered-dense mbedtls3 vulkan-headers quazip-qt6`
|
||||
|
||||
If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
||||
|
||||
@@ -253,7 +251,7 @@ If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
||||
<details>
|
||||
<summary>NetBSD</summary>
|
||||
|
||||
For NetBSD +10.1: `pkgin install git cmake boost fmtlib SDL2 catch2 libjwt spirv-headers ffmpeg7 libva nlohmann-json jq libopus qt6 mbedtls3 cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx`.
|
||||
For NetBSD +10.1: `pkgin install git cmake boost fmtlib SDL2 catch2 libjwt spirv-headers ffmpeg7 libva nlohmann-json jq libopus qt6 mbedtls3 cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1`.
|
||||
|
||||
[Caveats](./Caveats.md#netbsd).
|
||||
|
||||
@@ -322,7 +320,7 @@ pacman -Syuu --needed --noconfirm $packages
|
||||
<summary>HaikuOS</summary>
|
||||
|
||||
```sh
|
||||
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.89_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl2_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel mbedtls3_devel glslang qt6_devel
|
||||
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.89_devel vulkan_devel qt6_base_devel libsdl2_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt6_devel qt6_5compat_devel libusb1_devel libz_devel mbedtls3_devel glslang
|
||||
```
|
||||
|
||||
[Caveats](./Caveats.md#haikuos).
|
||||
|
||||
@@ -1,874 +0,0 @@
|
||||
# The NVIDIA SM86 (Maxwell) GPU - Instruction set
|
||||
|
||||
<!-- TOC -->
|
||||
[AL2P](#AL2P)
|
||||
[ALD](#ALD)
|
||||
[AST](#AST)
|
||||
[ATOM](#ATOM)
|
||||
[ATOMS](#ATOMS)
|
||||
[B2R](#B2R)
|
||||
[BAR](#BAR)
|
||||
[BFE](#BFE)
|
||||
[BFI](#BFI)
|
||||
[BPT](#BPT)
|
||||
[BRA](#BRA)
|
||||
[BRK](#BRK)
|
||||
[BRX](#BRX)
|
||||
[CAL](#CAL)
|
||||
[CCTL](#CCTL)
|
||||
[CCTLL](#CCTLL)
|
||||
[CONT](#CONT)
|
||||
[CS2R](#CS2R)
|
||||
[CSET](#CSET)
|
||||
[CSETP](#CSETP)
|
||||
[DADD](#DADD)
|
||||
[DEPBAR](#DEPBAR)
|
||||
[DFMA](#DFMA)
|
||||
[DMNMX](#DMNMX)
|
||||
[DMUL](#DMUL)
|
||||
[DSET](#DSET)
|
||||
[DSETP](#DSETP)
|
||||
[EXIT](#EXIT)
|
||||
[F2F](#F2F)
|
||||
[F2I](#F2I)
|
||||
[FADD](#FADD)
|
||||
[FCHK](#FCHK)
|
||||
[FCMP](#FCMP)
|
||||
[FFMA](#FFMA)
|
||||
[FLO](#FLO)
|
||||
[FMNMX](#FMNMX)
|
||||
[FMUL](#FMUL)
|
||||
[FSET](#FSET)
|
||||
[FSETP](#FSETP)
|
||||
[FSWZADD](#FSWZADD)
|
||||
[GETCRSPTR](#GETCRSPTR)
|
||||
[GETLMEMBASE](#GETLMEMBASE)
|
||||
[HADD2](#HADD2)
|
||||
[HFMA2](#HFMA2)
|
||||
[HMUL2](#HMUL2)
|
||||
[HSET2](#HSET2)
|
||||
[HSETP2](#HSETP2)
|
||||
[I2F](#I2F)
|
||||
[I2I](#I2I)
|
||||
[IADD](#IADD)
|
||||
[IADD3](#IADD3)
|
||||
[ICMP](#ICMP)
|
||||
[IDE](#IDE)
|
||||
[IDP](#IDP)
|
||||
[IMAD](#IMAD)
|
||||
[IMADSP](#IMADSP)
|
||||
[IMNMX](#IMNMX)
|
||||
[IMUL](#IMUL)
|
||||
[IPA](#IPA)
|
||||
[ISBERD](#ISBERD)
|
||||
[ISCADD](#ISCADD)
|
||||
[ISET](#ISET)
|
||||
[ISETP](#ISETP)
|
||||
[JCAL](#JCAL)
|
||||
[JMP](#JMP)
|
||||
[JMX](#JMX)
|
||||
[KIL](#KIL)
|
||||
[LD](#LD)
|
||||
[LDC](#LDC)
|
||||
[LDG](#LDG)
|
||||
[LDL](#LDL)
|
||||
[LDS](#LDS)
|
||||
[LEA](#LEA)
|
||||
[LEPC](#LEPC)
|
||||
[LONGJMP](#LONGJMP)
|
||||
[LOP](#LOP)
|
||||
[LOP3](#LOP3)
|
||||
[MEMBAR](#MEMBAR)
|
||||
[MOV](#MOV)
|
||||
[MUFU](#MUFU)
|
||||
[NOP](#NOP)
|
||||
[OUT](#OUT)
|
||||
[P2R](#P2R)
|
||||
[PBK](#PBK)
|
||||
[PCNT](#PCNT)
|
||||
[PEXIT](#PEXIT)
|
||||
[PIXLD](#PIXLD)
|
||||
[PLONGJMP](#PLONGJMP)
|
||||
[POPC](#POPC)
|
||||
[PRET](#PRET)
|
||||
[PRMT](#PRMT)
|
||||
[PSET](#PSET)
|
||||
[PSETP](#PSETP)
|
||||
[R2B](#R2B)
|
||||
[R2P](#R2P)
|
||||
[RAM](#RAM)
|
||||
[RED](#RED)
|
||||
[RET](#RET)
|
||||
[RRO](#RRO)
|
||||
[RTT](#RTT)
|
||||
[S2R](#S2R)
|
||||
[SAM](#SAM)
|
||||
[SEL](#SEL)
|
||||
[SETCRSPTR](#SETCRSPTR)
|
||||
[SETLMEMBASE](#SETLMEMBASE)
|
||||
[SHF](#SHF)
|
||||
[SHFL](#SHFL)
|
||||
[SHL](#SHL)
|
||||
[SHR](#SHR)
|
||||
[SSY](#SSY)
|
||||
[ST](#ST)
|
||||
[STG](#STG)
|
||||
[STL](#STL)
|
||||
[STP](#STP)
|
||||
[STS](#STS)
|
||||
[SUATOM](#SUATOM)
|
||||
[SULD](#SULD)
|
||||
[SURED](#SURED)
|
||||
[SUST](#SUST)
|
||||
[SYNC](#SYNC)
|
||||
[TEX](#TEX)
|
||||
[TLD](#TLD)
|
||||
[TLD4](#TLD4)
|
||||
[TMML](#TMML)
|
||||
[TXA](#TXA)
|
||||
[TXD](#TXD)
|
||||
[TXQ](#TXQ)
|
||||
[VABSDIFF](#VABSDIFF)
|
||||
[VABSDIFF4](#VABSDIFF4)
|
||||
[VADD](#VADD)
|
||||
[VMAD](#VMAD)
|
||||
[VMNMX](#VMNMX)
|
||||
[VOTE](#VOTE)
|
||||
[VSET](#VSET)
|
||||
[VSETP](#VSETP)
|
||||
[VSHL](#VSHL)
|
||||
[VSHR](#VSHR)
|
||||
[XMAD](#XMAD)
|
||||
<!-- /TOC -->
|
||||
|
||||
NOTE: Regenerate TOC with `cat docs/gpu/README.md | grep '#' | cut -d '#' -f 2 | tr -d ' ' | awk '{print "["$1"](#"$1")"}'`.
|
||||
|
||||
The numbers (in binary) represent the opcodes; `-` signifies "don't care".
|
||||
|
||||
# AL2P
|
||||
`1110 1111 1010 0---`
|
||||
|
||||
# ALD
|
||||
`1110 1111 1101 1---`
|
||||
|
||||
# AST
|
||||
`1110 1111 1111 0---`
|
||||
|
||||
# ATOM
|
||||
- **ATOM_cas**: `1110 1110 1111 ----`
|
||||
- **ATOM**: `1110 1101 ---- ----`
|
||||
|
||||
Atomic operation.
|
||||
|
||||
- INC, DEC for U32/S32/U64 does nothing.
|
||||
- ADD, INC, DEC for S64 does nothing.
|
||||
- Only ADD does something for F32.
|
||||
- Only ADD, MIN and MAX does something for F16x2.
|
||||
|
||||
# ATOMS
|
||||
- **ATOMS_cas**: `1110 1110 ---- ----`
|
||||
- **ATOMS**: `1110 1100 ---- ----`
|
||||
|
||||
# B2R
|
||||
`1111 0000 1011 1---`
|
||||
|
||||
# BAR
|
||||
`1111 0000 1010 1---`
|
||||
|
||||
# BFE
|
||||
- **BFE_reg**: `0101 1100 0000 0---`
|
||||
- **BFE_cbuf**: `0100 1100 0000 0---`
|
||||
- **BFE_imm**: `0011 100- 0000 0---`
|
||||
|
||||
Bit Field Extract.
|
||||
|
||||
# BFI
|
||||
- **BFI_reg**: `0101 1011 1111 0---`
|
||||
- **BFI_rc**: `0101 0011 1111 0---`
|
||||
- **BFI_cr**: `0100 1011 1111 0---`
|
||||
- **BFI_imm**: `0011 011- 1111 0---`
|
||||
|
||||
Bit Field Insert.
|
||||
|
||||
# BPT
|
||||
`1110 0011 1010 ----`
|
||||
|
||||
Breakpoint trap.
|
||||
|
||||
# BRA
|
||||
`1110 0010 0100 ----`
|
||||
|
||||
Relative branch.
|
||||
|
||||
# BRK
|
||||
`1110 0011 0100 ----`
|
||||
|
||||
Break.
|
||||
|
||||
# BRX
|
||||
`1110 0010 0101 ----`
|
||||
|
||||
# CAL
|
||||
`1110 0010 0110 ----`
|
||||
|
||||
# CCTL
|
||||
`1110 1111 011- ----`
|
||||
|
||||
Cache Control.
|
||||
|
||||
# CCTLL
|
||||
`1110 1111 100- ----`
|
||||
|
||||
Texture Cache Control.
|
||||
|
||||
# CONT
|
||||
`1110 0011 0101 ----`
|
||||
|
||||
Continue.
|
||||
|
||||
# CS2R
|
||||
`0101 0000 1100 1---`
|
||||
|
||||
Move Special Register to Register.
|
||||
|
||||
# CSET
|
||||
`0101 0000 1001 1---`
|
||||
|
||||
Test Condition Code And Set.
|
||||
|
||||
# CSETP
|
||||
`0101 0000 1010 0---`
|
||||
|
||||
Test Condition Code and Set Predicate.
|
||||
|
||||
# DADD
|
||||
- **DADD_reg**: `0101 1100 0111 0---`
|
||||
- **DADD_cbuf**: `0100 1100 0111 0---`
|
||||
- **DADD_imm**: `0011 100- 0111 0---`
|
||||
|
||||
# DEPBAR
|
||||
`1111 0000 1111 0---`
|
||||
|
||||
# DFMA
|
||||
- **DFMA_reg**: `0101 1011 0111 ----`
|
||||
- **DFMA_rc**: `0101 0011 0111 ----`
|
||||
- **DFMA_cr**: `0100 1011 0111 ----`
|
||||
- **DFMA_imm**: `0011 011- 0111 ----`
|
||||
|
||||
FP64 Fused Mutiply Add.
|
||||
|
||||
# DMNMX
|
||||
- **DMNMX_reg**: `0101 1100 0101 0---`
|
||||
- **DMNMX_cbuf**: `0100 1100 0101 0---`
|
||||
- **DMNMX_imm**: `0011 100- 0101 0---`
|
||||
|
||||
FP64 Minimum/Maximum.
|
||||
|
||||
# DMUL
|
||||
- **DMUL_reg**: `0101 1100 1000 0---`
|
||||
- **DMUL_cbuf**: `0100 1100 1000 0---`
|
||||
- **DMUL_imm**: `0011 100- 1000 0---`
|
||||
|
||||
FP64 Multiply.
|
||||
|
||||
# DSET
|
||||
- **DSET_reg**: `0101 1001 0--- ----`
|
||||
- **DSET_cbuf**: `0100 1001 0--- ----`
|
||||
- **DSET_imm**: `0011 001- 0--- ----`
|
||||
|
||||
FP64 Compare And Set.
|
||||
|
||||
# DSETP
|
||||
- **DSETP_reg**: `0101 1011 1000 ----`
|
||||
- **DSETP_cbuf**: `0100 1011 1000 ----`
|
||||
- **DSETP_imm**: `0011 011- 1000 ----`
|
||||
|
||||
FP64 Compare And Set Predicate.
|
||||
|
||||
# EXIT
|
||||
`1110 0011 0000 ----`
|
||||
|
||||
# F2F
|
||||
- **F2F_reg**: `0101 1100 1010 1---`
|
||||
- **F2F_cbuf**: `0100 1100 1010 1---`
|
||||
- **F2F_imm**: `0011 100- 1010 1---`
|
||||
|
||||
# F2I
|
||||
- **F2I_reg**: `0101 1100 1011 0---`
|
||||
- **F2I_cbuf**: `0100 1100 1011 0---`
|
||||
- **F2I_imm**: `0011 100- 1011 0---`
|
||||
|
||||
# FADD
|
||||
- **FADD_reg**: `0101 1100 0101 1---`
|
||||
- **FADD_cbuf**: `0100 1100 0101 1---`
|
||||
- **FADD_imm**: `0011 100- 0101 1---`
|
||||
- **FADD32I**: `0000 10-- ---- ----`
|
||||
|
||||
FP32 Add.
|
||||
|
||||
# FCHK
|
||||
- **FCHK_reg**: `0101 1100 1000 1---`
|
||||
- **FCHK_cbuf**: `0100 1100 1000 1---`
|
||||
- **FCHK_imm**: `0011 100- 1000 1---`
|
||||
|
||||
Single Precision FP Divide Range Check.
|
||||
|
||||
# FCMP
|
||||
- **FCMP_reg**: `0101 1011 1010 ----`
|
||||
- **FCMP_rc**: `0101 0011 1010 ----`
|
||||
- **FCMP_cr**: `0100 1011 1010 ----`
|
||||
- **FCMP_imm**: `0011 011- 1010 ----`
|
||||
|
||||
FP32 Compare to Zero and Select Source.
|
||||
|
||||
# FFMA
|
||||
- **FFMA_reg**: `0101 1001 1--- ----`
|
||||
- **FFMA_rc**: `0101 0001 1--- ----`
|
||||
- **FFMA_cr**: `0100 1001 1--- ----`
|
||||
- **FFMA_imm**: `0011 001- 1--- ----`
|
||||
- **FFMA32I**: `0000 11-- ---- ----`
|
||||
|
||||
FP32 Fused Multiply and Add.
|
||||
|
||||
# FLO
|
||||
- **FLO_reg**: `0101 1100 0011 0---`
|
||||
- **FLO_cbuf**: `0100 1100 0011 0---`
|
||||
- **FLO_imm**: `0011 100- 0011 0---`
|
||||
|
||||
# FMNMX
|
||||
- **FMNMX_reg**: `0101 1100 0110 0---`
|
||||
- **FMNMX_cbuf**: `0100 1100 0110 0---`
|
||||
- **FMNMX_imm**: `0011 100- 0110 0---`
|
||||
|
||||
FP32 Minimum/Maximum.
|
||||
|
||||
# FMUL
|
||||
- **FMUL_reg**: `0101 1100 0110 1---`
|
||||
- **FMUL_cbuf**: `0100 1100 0110 1---`
|
||||
- **FMUL_imm**: `0011 100- 0110 1---`
|
||||
- **FMUL32I**: `0001 1110 ---- ----`
|
||||
|
||||
FP32 Multiply.
|
||||
|
||||
# FSET
|
||||
- **FSET_reg**: `0101 1000 ---- ----`
|
||||
- **FSET_cbuf**: `0100 1000 ---- ----`
|
||||
- **FSET_imm**: `0011 000- ---- ----`
|
||||
|
||||
FP32 Compare And Set.
|
||||
|
||||
# FSETP
|
||||
- **FSETP_reg**: `0101 1011 1011 ----`
|
||||
- **FSETP_cbuf**: `0100 1011 1011 ----`
|
||||
- **FSETP_imm**: `0011 011- 1011 ----`
|
||||
|
||||
FP32 Compare And Set Predicate.
|
||||
|
||||
# FSWZADD
|
||||
`0101 0000 1111 1---`
|
||||
|
||||
FP32 Add used for FSWZ emulation.
|
||||
|
||||
# GETCRSPTR
|
||||
`1110 0010 1100 ----`
|
||||
|
||||
# GETLMEMBASE
|
||||
`1110 0010 1101 ----`
|
||||
|
||||
# HADD2
|
||||
- **HADD2_reg**: `0101 1101 0001 0---`
|
||||
- **HADD2_cbuf**: `0111 101- 1--- ----`
|
||||
- **HADD2_imm**: `0111 101- 0--- ----`
|
||||
- **HADD2_32I**: `0010 110- ---- ----`
|
||||
|
||||
FP16 Add.
|
||||
|
||||
# HFMA2
|
||||
- **HFMA2_reg**: `0101 1101 0000 0---`
|
||||
- **HFMA2_rc**: `0110 0--- 1--- ----`
|
||||
- **HFMA2_cr**: `0111 0--- 1--- ----`
|
||||
- **HFMA2_imm**: `0111 0--- 0--- ----`
|
||||
- **HFMA2_32I**: `0010 100- ---- ----`
|
||||
|
||||
FP16 Fused Mutiply Add.
|
||||
|
||||
# HMUL2
|
||||
- **HMUL2_reg**: `0101 1101 0000 1---`
|
||||
- **HMUL2_cbuf**: `0111 100- 1--- ----`
|
||||
- **HMUL2_imm**: `0111 100- 0--- ----`
|
||||
- **HMUL2_32I**: `0010 101- ---- ----`
|
||||
|
||||
FP16 Multiply.
|
||||
|
||||
# HSET2
|
||||
- **HSET2_reg**: `0101 1101 0001 1---`
|
||||
- **HSET2_cbuf**: `0111 110- 1--- ----`
|
||||
- **HSET2_imm**: `0111 110- 0--- ----`
|
||||
|
||||
FP16 Compare And Set.
|
||||
|
||||
# HSETP2
|
||||
- **HSETP2_reg**: `0101 1101 0010 0---`
|
||||
- **HSETP2_cbuf**: `0111 111- 1--- ----`
|
||||
- **HSETP2_imm**: `0111 111- 0--- ----`
|
||||
|
||||
FP16 Compare And Set Predicate.
|
||||
|
||||
# I2F
|
||||
- **I2F_reg**: `0101 1100 1011 1---`
|
||||
- **I2F_cbuf**: `0100 1100 1011 1---`
|
||||
- **I2F_imm**: `0011 100- 1011 1---`
|
||||
|
||||
# I2I
|
||||
- **I2I_reg**: `0101 1100 1110 0---`
|
||||
- **I2I_cbuf**: `0100 1100 1110 0---`
|
||||
- **I2I_imm**: `0011 100- 1110 0---`
|
||||
|
||||
# IADD
|
||||
- **IADD_reg**: `0101 1100 0001 0---`
|
||||
- **IADD_cbuf**: `0100 1100 0001 0---`
|
||||
- **IADD_imm**: `0011 100- 0001 0---`
|
||||
|
||||
Integer Addition.
|
||||
|
||||
# IADD3
|
||||
- **IADD3_reg**: `0101 1100 1100 ----`
|
||||
- **IADD3_cbuf**: `0100 1100 1100 ----`
|
||||
- **IADD3_imm**: `0011 100- 1100 ----`
|
||||
- **IADD32I**: `0001 110- ---- ----`
|
||||
|
||||
3-input Integer Addition.
|
||||
|
||||
# ICMP
|
||||
- **ICMP_reg**: `0101 1011 0100 ----`
|
||||
- **ICMP_rc**: `0101 0011 0100 ----`
|
||||
- **ICMP_cr**: `0100 1011 0100 ----`
|
||||
- **ICMP_imm**: `0011 011- 0100 ----`
|
||||
|
||||
Integer Compare to Zero and Select Source.
|
||||
|
||||
# IDE
|
||||
`1110 0011 1001 ----`
|
||||
|
||||
# IDP
|
||||
- **IDP_reg**: `0101 0011 1111 1---`
|
||||
- **IDP_imm**: `0101 0011 1101 1---`
|
||||
|
||||
# IMAD
|
||||
- **IMAD_reg**: `0101 1010 0--- ----`
|
||||
- **IMAD_rc**: `0101 0010 0--- ----`
|
||||
- **IMAD_cr**: `0100 1010 0--- ----`
|
||||
- **IMAD_imm**: `0011 010- 0--- ----`
|
||||
- **IMAD32I**: `1000 00-- ---- ----`
|
||||
|
||||
Integer Multiply And Add.
|
||||
|
||||
# IMADSP
|
||||
- **IMADSP_reg**: `0101 1010 1--- ----`
|
||||
- **IMADSP_rc**: `0101 0010 1--- ----`
|
||||
- **IMADSP_cr**: `0100 1010 1--- ----`
|
||||
- **IMADSP_imm**: `0011 010- 1--- ----`
|
||||
|
||||
Extracted Integer Multiply And Add..
|
||||
|
||||
# IMNMX
|
||||
- **IMNMX_reg**: `0101 1100 0010 0---`
|
||||
- **IMNMX_cbuf**: `0100 1100 0010 0---`
|
||||
- **IMNMX_imm**: `0011 100- 0010 0---`
|
||||
|
||||
Integer Minimum/Maximum.
|
||||
|
||||
# IMUL
|
||||
- **IMUL_reg**: `0101 1100 0011 1---`
|
||||
- **IMUL_cbuf**: `0100 1100 0011 1---`
|
||||
- **IMUL_imm**: `0011 100- 0011 1---`
|
||||
- **IMUL32I**: `0001 1111 ---- ----`
|
||||
|
||||
Integer Multiply.
|
||||
|
||||
# IPA
|
||||
`1110 0000 ---- ----`
|
||||
|
||||
# ISBERD
|
||||
`1110 1111 1101 0---`
|
||||
|
||||
In-Stage-Buffer Entry Read.
|
||||
|
||||
# ISCADD
|
||||
- **ISCADD_reg**: `0101 1100 0001 1---`
|
||||
- **ISCADD_cbuf**: `0100 1100 0001 1---`
|
||||
- **ISCADD_imm**: `0011 100- 0001 1---`
|
||||
- **ISCADD32I**: `0001 01-- ---- ----`
|
||||
|
||||
Scaled Integer Addition.
|
||||
|
||||
# ISET
|
||||
- **ISET_reg**: `0101 1011 0101 ----`
|
||||
- **ISET_cbuf**: `0100 1011 0101 ----`
|
||||
- **ISET_imm**: `0011 011- 0101 ----`
|
||||
|
||||
Integer Compare And Set.
|
||||
|
||||
# ISETP
|
||||
- **ISETP_reg**: `0101 1011 0110 ----`
|
||||
- **ISETP_cbuf**: `0100 1011 0110 ----`
|
||||
- **ISETP_imm**: `0011 011- 0110 ----`
|
||||
|
||||
Integer Compare And Set Predicate.
|
||||
|
||||
# JCAL
|
||||
`1110 0010 0010 ----`
|
||||
|
||||
Absolute Call.
|
||||
|
||||
# JMP
|
||||
`1110 0010 0001 ----`
|
||||
|
||||
Absolute Jump.
|
||||
|
||||
# JMX
|
||||
`1110 0010 0000 ----`
|
||||
|
||||
Absolute Jump Indirect.
|
||||
|
||||
# KIL
|
||||
`1110 0011 0011 ----`
|
||||
|
||||
# LD
|
||||
`100- ---- ---- ----`
|
||||
|
||||
Load from generic Memory.
|
||||
|
||||
# LDC
|
||||
`1110 1111 1001 0---`
|
||||
|
||||
Load Constant.
|
||||
|
||||
# LDG
|
||||
`1110 1110 1101 0---`
|
||||
|
||||
Load from Global Memory.
|
||||
|
||||
# LDL
|
||||
`1110 1111 0100 0---`
|
||||
|
||||
Load within Local Memory Window.
|
||||
|
||||
# LDS
|
||||
`1110 1111 0100 1---`
|
||||
|
||||
Load within Shared Memory Window.
|
||||
|
||||
# LEA
|
||||
- **LEA_hi_reg**: `0101 1011 1101 1---`
|
||||
- **LEA_hi_cbuf**: `0001 10-- ---- ----`
|
||||
- **LEA_lo_reg**: `0101 1011 1101 0---`
|
||||
- **LEA_lo_cbuf**: `0100 1011 1101 ----`
|
||||
- **LEA_lo_imm**: `0011 011- 1101 0---`
|
||||
|
||||
# LEPC
|
||||
`0101 0000 1101 0---`
|
||||
|
||||
# LONGJMP
|
||||
`1110 0011 0001 ----`
|
||||
|
||||
# LOP
|
||||
- **LOP_reg**: `0101 1100 0100 0---`
|
||||
- **LOP_cbuf**: `0100 1100 0100 0---`
|
||||
- **LOP_imm**: `0011 100- 0100 0---`
|
||||
|
||||
# LOP3
|
||||
- **LOP3_reg**: `0101 1011 1110 0---`
|
||||
- **LOP3_cbuf**: `0000 001- ---- ----`
|
||||
- **LOP3_imm**: `0011 11-- ---- ----`
|
||||
- **LOP32I**: `0000 01-- ---- ----`
|
||||
|
||||
# MEMBAR
|
||||
`1110 1111 1001 1---`
|
||||
|
||||
Memory Barrier.
|
||||
|
||||
# MOV
|
||||
- **MOV_reg**: `0101 1100 1001 1---`
|
||||
- **MOV_cbuf**: `0100 1100 1001 1---`
|
||||
- **MOV_imm**: `0011 100- 1001 1---`
|
||||
- **MOV32I**: `0000 0001 0000 ----`
|
||||
|
||||
# MUFU
|
||||
`0101 0000 1000 0---`
|
||||
|
||||
Multi Function Operation.
|
||||
|
||||
# NOP
|
||||
`0101 0000 1011 0---`
|
||||
|
||||
No operation.
|
||||
|
||||
# OUT
|
||||
- **OUT_reg**: `1111 1011 1110 0---`
|
||||
- **OUT_cbuf**: `1110 1011 1110 0---`
|
||||
- **OUT_imm**: `1111 011- 1110 0---`
|
||||
|
||||
# P2R
|
||||
- **P2R_reg**: `0101 1100 1110 1---`
|
||||
- **P2R_cbuf**: `0100 1100 1110 1---`
|
||||
- **P2R_imm**: `0011 1000 1110 1---`
|
||||
|
||||
Move Predicate Register To Register.
|
||||
|
||||
# PBK
|
||||
`1110 0010 1010 ----`
|
||||
|
||||
Pre-break.
|
||||
|
||||
# PCNT
|
||||
`1110 0010 1011 ----`
|
||||
|
||||
Pre-continue.
|
||||
|
||||
# PEXIT
|
||||
`1110 0010 0011 ----`
|
||||
|
||||
Pre-exit.
|
||||
|
||||
# PIXLD
|
||||
`1110 1111 1110 1---`
|
||||
|
||||
# PLONGJMP
|
||||
`1110 0010 1000 ----`
|
||||
|
||||
Pre-long jump.
|
||||
|
||||
# POPC
|
||||
- **POPC_reg**: `0101 1100 0000 1---`
|
||||
- **POPC_cbuf**: `0100 1100 0000 1---`
|
||||
- **POPC_imm**: `0011 100- 0000 1---`
|
||||
|
||||
Population/Bit count.
|
||||
|
||||
# PRET
|
||||
`1110 0010 0111 ----`
|
||||
|
||||
Pre-return from subroutine. Pushes the return address to the CRS stack.
|
||||
|
||||
# PRMT
|
||||
- **PRMT_reg**: `0101 1011 1100 ----`
|
||||
- **PRMT_rc**: `0101 0011 1100 ----`
|
||||
- **PRMT_cr**: `0100 1011 1100 ----`
|
||||
- **PRMT_imm**: `0011 011- 1100 ----`
|
||||
|
||||
# PSET
|
||||
`0101 0000 1000 1---`
|
||||
|
||||
Combine Predicates and Set.
|
||||
|
||||
# PSETP
|
||||
`0101 0000 1001 0---`
|
||||
|
||||
Combine Predicates and Set Predicate.
|
||||
|
||||
# R2B
|
||||
`1111 0000 1100 0---`
|
||||
|
||||
Move Register to Barrier.
|
||||
|
||||
# R2P
|
||||
- **R2P_reg**: `0101 1100 1111 0---`
|
||||
- **R2P_cbuf**: `0100 1100 1111 0---`
|
||||
- **R2P_imm**: `0011 100- 1111 0---`
|
||||
|
||||
Move Register To Predicate/CC Register.
|
||||
|
||||
# RAM
|
||||
`1110 0011 1000 ----`
|
||||
|
||||
# RED
|
||||
`1110 1011 1111 1---`
|
||||
|
||||
Reduction Operation on Generic Memory.
|
||||
|
||||
# RET
|
||||
`1110 0011 0010 ----`
|
||||
|
||||
Return.
|
||||
|
||||
# RRO
|
||||
- **RRO_reg**: `0101 1100 1001 0---`
|
||||
- **RRO_cbuf**: `0100 1100 1001 0---`
|
||||
- **RRO_imm**: `0011 100- 1001 0---`
|
||||
|
||||
# RTT
|
||||
`1110 0011 0110 ----`
|
||||
|
||||
# S2R
|
||||
`1111 0000 1100 1---`
|
||||
|
||||
# SAM
|
||||
`1110 0011 0111 ----`
|
||||
|
||||
# SEL
|
||||
- **SEL_reg**: `0101 1100 1010 0---`
|
||||
- **SEL_cbuf**: `0100 1100 1010 0---`
|
||||
- **SEL_imm**: `0011 100- 1010 0---`
|
||||
|
||||
# SETCRSPTR
|
||||
`1110 0010 1110 ----`
|
||||
|
||||
# SETLMEMBASE
|
||||
`1110 0010 1111 ----`
|
||||
|
||||
# SHF
|
||||
- **SHF_l_reg**: `0101 1011 1111 1---`
|
||||
- **SHF_l_imm**: `0011 011- 1111 1---`
|
||||
- **SHF_r_reg**: `0101 1100 1111 1---`
|
||||
- **SHF_r_imm**: `0011 100- 1111 1---`
|
||||
|
||||
# SHFL
|
||||
`1110 1111 0001 0---`
|
||||
|
||||
# SHL
|
||||
- **SHL_reg**: `0101 1100 0100 1---`
|
||||
- **SHL_cbuf**: `0100 1100 0100 1---`
|
||||
- **SHL_imm**: `0011 100- 0100 1---`
|
||||
|
||||
# SHR
|
||||
- **SHR_reg**: `0101 1100 0010 1---`
|
||||
- **SHR_cbuf**: `0100 1100 0010 1---`
|
||||
- **SHR_imm**: `0011 100- 0010 1---`
|
||||
|
||||
# SSY
|
||||
`1110 0010 1001 ----`
|
||||
|
||||
Set Synchronization Point.
|
||||
|
||||
# ST
|
||||
`101- ---- ---- ----`
|
||||
|
||||
Store to generic Memory.
|
||||
|
||||
# STG
|
||||
`1110 1110 1101 1---`
|
||||
|
||||
Store to global Memory.
|
||||
|
||||
# STL
|
||||
`1110 1111 0101 0---`
|
||||
|
||||
Store within Local or Shared Window.
|
||||
|
||||
# STP
|
||||
`1110 1110 1010 0---`
|
||||
|
||||
Store to generic Memory and Predicate.
|
||||
|
||||
# STS
|
||||
`1110 1111 0101 1---`
|
||||
|
||||
Store within Local or Shared Window.
|
||||
|
||||
# SUATOM
|
||||
- **SUATOM**: `1110 1010 0--- ----`
|
||||
- **SUATOM_cas**: `1110 1010 1--- ----`
|
||||
|
||||
Atomic Op on Surface Memory.
|
||||
|
||||
# SULD
|
||||
`1110 1011 000- ----`
|
||||
|
||||
Surface Load.
|
||||
|
||||
# SURED
|
||||
`1110 1011 010- ----`
|
||||
|
||||
Reduction Op on Surface Memory.
|
||||
|
||||
# SUST
|
||||
`1110 1011 001- ----`
|
||||
|
||||
Surface Store.
|
||||
|
||||
# SYNC
|
||||
`1111 0000 1111 1---`
|
||||
|
||||
# TEX
|
||||
- **TEX**: `1100 0--- ---- ----`
|
||||
- **TEX_b**: `1101 1110 10-- ----`
|
||||
- **TEXS**: `1101 -00- ---- ----`
|
||||
|
||||
Texture Fetch with scalar/non-vec4 source/destinations.
|
||||
|
||||
# TLD
|
||||
- **TLD**: `1101 1100 ---- ----`
|
||||
- **TLD_b**: `1101 1101 ---- ----`
|
||||
- **TLDS**: `1101 -01- ---- ----`
|
||||
|
||||
Texture Load with scalar/non-vec4 source/destinations.
|
||||
|
||||
# TLD4
|
||||
- **TLD4**: `1100 10-- ---- ----`
|
||||
- **TLD4_b**: `1101 1110 11-- ----`
|
||||
- **TLD4S**: `1101 1111 -0-- ----`
|
||||
|
||||
Texture Load 4 with scalar/non-vec4 source/destinations.
|
||||
|
||||
# TMML
|
||||
- **TMML**: `1101 1111 0101 1---`
|
||||
- **TMML_b**: `1101 1111 0110 0---`
|
||||
|
||||
Texture MipMap Level.
|
||||
|
||||
# TXA
|
||||
`1101 1111 0100 0---`
|
||||
|
||||
# TXD
|
||||
- **TXD**: `1101 1110 00-- ----`
|
||||
- **TXD_b**: `1101 1110 01-- ----`
|
||||
|
||||
Texture Fetch With Derivatives.
|
||||
|
||||
# TXQ
|
||||
- **TXQ**: `1101 1111 0100 1---`
|
||||
- **TXQ_b**: `1101 1111 0101 0---`
|
||||
|
||||
Texture Query.
|
||||
|
||||
# VABSDIFF
|
||||
`0101 0100 ---- ----`
|
||||
|
||||
# VABSDIFF4
|
||||
`0101 0000 0--- ----`
|
||||
|
||||
# VADD
|
||||
`0010 00-- ---- ----`
|
||||
|
||||
# VMAD
|
||||
`0101 1111 ---- ----`
|
||||
|
||||
# VMNMX
|
||||
`0011 101- ---- ----`
|
||||
|
||||
# VOTE
|
||||
- **VOTE**: `0101 0000 1101 1---`
|
||||
- **VOTE_vtg**: `0101 0000 1110 0---`
|
||||
|
||||
Vote Across SIMD Thread Group
|
||||
|
||||
# VSET
|
||||
`0100 000- ---- ----`
|
||||
|
||||
# VSETP
|
||||
`0101 0000 1111 0---`
|
||||
|
||||
# VSHL
|
||||
`0101 0111 ---- ----`
|
||||
|
||||
# VSHR
|
||||
`0101 0110 ---- ----`
|
||||
|
||||
# XMAD
|
||||
- **XMAD_reg**: `0101 1011 00-- ----`
|
||||
- **XMAD_rc**: `0101 0001 0--- ----`
|
||||
- **XMAD_cr**: `0100 111- ---- ----`
|
||||
- **XMAD_imm**: `0011 011- 00-- ----`
|
||||
|
||||
Integer Short Multiply Add.
|
||||
@@ -10,6 +10,4 @@ This contains documentation created by developers. This contains build instructi
|
||||
- **[Debug Guidelines](./Debug.md)**
|
||||
- **[CPM - CMake Package Manager](CPMUtil.md)**
|
||||
- **[Platform-Specific Caveats](Caveats.md)**
|
||||
- **[The NVIDIA SM86 (Maxwell) GPU](./NvidiaGpu.md)**
|
||||
- **[User Handbook](./user)**
|
||||
- **[Release Policy](./ReleasePolicy.md)**
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Release Policy
|
||||
|
||||
While releases are usually made at the discretion of the team, we feel that establishing a clearer guideline on how those come to be will help expectations when it comes to features and fixes per version.
|
||||
|
||||
## Release candidates
|
||||
|
||||
Every full release is *preceded* by at least, 3 release candidates. The reasoning is that each week of the month, there will be a release candidate, with the "4th one" being the final full release.
|
||||
|
||||
The main expectation is that the release candidates bring both fixes and, sometimes, new features. But not guarantee a regression-free experience.
|
||||
|
||||
The criteria for choosing a date for a release candidate is at discretion, or "perceived necesity" at any given time.
|
||||
|
||||
## Full release
|
||||
|
||||
A full release means there are *no major* leftover regressions, importantly this means that a grand portion of regressions found between release candidates are swept out before declaring a full release. This doesn't mean a full release is regression-free; but we do a best-effort approach to reduce them for end-users.
|
||||
|
||||
The main expectation is that users can safely upgrade from a stable build to another, with no major regressions.
|
||||
|
||||
## Snapshot/rolling release
|
||||
|
||||
While we don't publish rolling releases, we are aware users may compile from source and/or provide binaries to master builds of the project.
|
||||
|
||||
This is mostly fine since we keep master very stable from major hiccups. However sometimes bugs do slip between tests or reviews - so users are advised to keep that in mind.
|
||||
|
||||
We advise that users also read git logs (`git log --oneline`) before recompiling to get a clearer picture of the changes given into the emulator.
|
||||
28
docs/user/ControllerProfileByGame.md
Normal file
28
docs/user/ControllerProfileByGame.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Setting Controller Profiles By Game
|
||||
|
||||
Use this guide when you want to set up specific controller profiles for specific games. This can be useful for certain games like *Captain Toad Treasure Tracker* where a blue dot appears in the middle of the screen when you have docked mode enabled, but not handheld mode.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Setting-Controller-Profiles-By-Game-2b057c2edaf681658a57f0c199cb6083) for a version of this guide with images & visual elements.**
|
||||
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- Eden Emulator set up and fully configured
|
||||
- Controller Profile Created
|
||||
- See [*Configuring Controller Profiles*](./ControllerProfiles.md) for instructions on how to do this if needed.
|
||||
|
||||
---
|
||||
|
||||
## Steps
|
||||
|
||||
1. *Right-Click* the game you want to apply the profile to in the main window and select **Properties.**
|
||||
2. Navigate to the **Input Profiles** tab in the window that appears. Drop down on *Player 1 profile* (or whatever player profile you want to apply it to) and select the profile you want.
|
||||
|
||||
<aside>
|
||||
|
||||
***NOTE***: You may have to resize the window to see all tabs, or press the arrows by the tabs to see **Input Profiles**.
|
||||
|
||||
</aside>
|
||||
1. Click **OK** to apply the profile mapping.
|
||||
2. Launch the game and confirm that the profile is applied, regardless of what the global configuration is.
|
||||
@@ -12,38 +12,9 @@ Use this guide for when you want to configure specific controller settings to be
|
||||
|
||||
---
|
||||
|
||||
### Steps
|
||||
## Steps
|
||||
1. Launch Eden and wait for it to load.
|
||||
2. Navigate to *Emulation > Configure…*
|
||||
3. Select **Controls** from the left-hand menu and configure your controller for the way you want it to be in game.
|
||||
4. Select **New** and enter a name for the profile in the box that appears. Press **OK** to save the profile settings.
|
||||
5. Select **OK** to close the settings menu.
|
||||
|
||||
## Setting Controller Profiles By Game
|
||||
|
||||
Use this guide when you want to set up specific controller profiles for specific games. This can be useful for certain games like *Captain Toad Treasure Tracker* where a blue dot appears in the middle of the screen when you have docked mode enabled, but not handheld mode.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Setting-Controller-Profiles-By-Game-2b057c2edaf681658a57f0c199cb6083) for a version of this guide with images & visual elements.**
|
||||
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- Eden Emulator set up and fully configured
|
||||
- Controller Profile Created
|
||||
- See [*Configuring Controller Profiles*](./ControllerProfiles.md) for instructions on how to do this if needed.
|
||||
|
||||
---
|
||||
|
||||
### Steps
|
||||
|
||||
1. *Right-Click* the game you want to apply the profile to in the main window and select **Properties.**
|
||||
2. Navigate to the **Input Profiles** tab in the window that appears. Drop down on *Player 1 profile* (or whatever player profile you want to apply it to) and select the profile you want.
|
||||
|
||||
<aside>
|
||||
|
||||
***NOTE***: You may have to resize the window to see all tabs, or press the arrows by the tabs to see **Input Profiles**.
|
||||
|
||||
</aside>
|
||||
1. Click **OK** to apply the profile mapping.
|
||||
2. Launch the game and confirm that the profile is applied, regardless of what the global configuration is.
|
||||
5. Select **OK** to close the settings menu.
|
||||
@@ -1,22 +1,10 @@
|
||||
# User Handbook - Troubleshooting
|
||||
|
||||
## Vulkan initialization error
|
||||
|
||||
- Ensure you have the latest drivers
|
||||
- Uninstall old drivers, for Windows you can use [Display Driver Uninstaller](https://www.guru3d.com/download/display-driver-uninstaller-download/)
|
||||
- Change backend manually in the settings file (set it from `0` to `1` or viceversa).
|
||||
- Disconnect your second monitor, if any
|
||||
|
||||
## This mod only works on an Emulator
|
||||
|
||||
- Enable RNG seed
|
||||
- Set RNG seed to 0
|
||||
|
||||
## Eden Fails to Launch and Does Not Leave Any Logs
|
||||
# Eden Fails to Launch and Does Not Leave Any Logs
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Windows-Eden-Fails-to-Launch-and-Does-Not-Leave-Any-Logs-2b057c2edaf68156b640cf1ac549870a) for a version of this guide with images & visual elements.**
|
||||
|
||||
### Error Details
|
||||
---
|
||||
|
||||
## Error Details
|
||||
|
||||
*Behavior*: Program appears not to launch or exits immediately without leaving any log entries.
|
||||
*Platform(s) Affected*:
|
||||
@@ -44,11 +32,13 @@ Faulting package-relative application ID:
|
||||
|
||||
---
|
||||
|
||||
### Causes
|
||||
## Causes
|
||||
|
||||
<aside>
|
||||
|
||||
#### Issue 1: Missing C++ Redistributable
|
||||
### Issue 1: Missing C++ Redistributable
|
||||
|
||||
---
|
||||
|
||||
*Eden requires the latest C++ redistributable from Microsoft in order to run. Like many other programs, it relies on aspects and libraries included in this runtime, without it - the program cannot run.*
|
||||
|
||||
@@ -60,7 +50,9 @@ Faulting package-relative application ID:
|
||||
|
||||
<aside>
|
||||
|
||||
#### Issue 2: Corrupted System Files
|
||||
### Issue 2: Corrupted System Files
|
||||
|
||||
---
|
||||
|
||||
*A corruption of necessary system files can cause odd behaviors when Eden tries to access them. It is a very rare case and you would likely see other programs misbehaving if this is what your issue is, but you can try if you have no other options.*
|
||||
|
||||
@@ -96,4 +88,4 @@ Faulting package-relative application ID:
|
||||
|
||||
8. Reboot your computer.
|
||||
9. Launch Eden and verify it is now working.
|
||||
</aside>
|
||||
</aside>
|
||||
@@ -90,22 +90,3 @@ The OpenGL backend would invoke behaviour that would result in swarst/LLVMpipe w
|
||||
### HaikuOS compatibility
|
||||
|
||||
HaikuOS bundles a Mesa library that doesn't support full core OpenGL 4.6 (required by the emulator). This leads to HaikuOS being one of the few computer platforms where Vulkan is the only available option for users. If OpenGL is desired, Mesa has to be built manually from source. For debugging purpouses `lavapipe` is recommended over the GPU driver; there is in-kernel support for NVIDIA cards through.
|
||||
|
||||
### Fixes for Windows 10 and above having "Device loss"
|
||||
|
||||
Run the following batch script *inside* the Eden folder:
|
||||
```cmd
|
||||
@echo off
|
||||
pushd "%~dp0"
|
||||
if exist "%temp%\FixFullScreen.reg" (
|
||||
del %temp%\FixFullScreen.reg
|
||||
)
|
||||
set str_path="%cd:\=\\%\\eden.exe"
|
||||
echo Windows Registry Editor Version 5.00 >> %temp%\FixFullScreen.reg
|
||||
echo. >> %temp%\FixFullScreen.reg
|
||||
echo [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers] >> %temp%\FixFullScreen.reg
|
||||
echo %str_path%="~ DISABLEDXMAXIMIZEDWINDOWEDMODE HIGHDPIAWARE" >> %temp%\FixFullScreen.reg
|
||||
regedit /s %temp%\FixFullScreen.reg
|
||||
del %temp%\FixFullScreen.reg
|
||||
exit /b
|
||||
```
|
||||
|
||||
@@ -3,7 +3,7 @@ Use this guide when you want to use the Steam Deck's native gyro functionality f
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Getting-Gyro-Motion-Controls-Working-on-Steam-Deck-2b057c2edaf681a1aaade35db6e0fd1b) for a version of this guide with images & visual elements.**
|
||||
|
||||
## Steamdeck
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
@@ -13,7 +13,7 @@ Use this guide when you want to use the Steam Deck's native gyro functionality f
|
||||
|
||||
---
|
||||
|
||||
### Steps
|
||||
## Steps
|
||||
|
||||
1. Go into Steam Deck's Desktop Mode, and use the shortcut to launch EmuDeck.
|
||||
2. Install [SteamDeckGyroDSU](https://github.com/kmicki/SteamDeckGyroDSU/releases) by going to *3rd Party Tools > Gyroscope* and clicking **Install.**
|
||||
@@ -4,13 +4,15 @@ Use this guide when you want to manually import save files for use in the Eden e
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Importing-Saves-Into-Eden-2b057c2edaf681fe968df8d63821ccae) for a version of this guide with images & visual elements.**
|
||||
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
- Eden emulator already set up and configured.
|
||||
- The save file(s) you want to import
|
||||
|
||||
## Desktop
|
||||
---
|
||||
|
||||
### Steps
|
||||
## Steps
|
||||
1. Open Eden and wait for it to load.
|
||||
2. Start the game and create a save file to establish the directories.
|
||||
3. *Right-Click* the game for which you want to load a save in.
|
||||
@@ -23,8 +25,4 @@ Use this guide when you want to manually import save files for use in the Eden e
|
||||
|
||||
</aside>
|
||||
7. Close the file explorer as it is no longer needed.
|
||||
8. Launch the game in Eden and verify that the save data appears through whatever method the game implements.
|
||||
|
||||
## Android
|
||||
|
||||
TBD
|
||||
8. Launch the game in Eden and verify that the save data appears through whatever method the game implements.
|
||||
@@ -1,4 +1,4 @@
|
||||
# User Handbook - Installing Atmosphere Mods
|
||||
# Installing Atmosphere Mods
|
||||
|
||||
Use this guide for when you want to install an Atmosphere-based mod for use in Eden.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# User Handbook - Working with Updates/DLC in Eden
|
||||
# Working with Updates/DLC in Eden
|
||||
|
||||
Use this guide when you want to install Updates or DLC for your games in Eden.
|
||||
|
||||
@@ -1,296 +0,0 @@
|
||||
# Multiplayer
|
||||
Use this guide to answer questions regarding and to start using the multiplayer functionality of Eden.
|
||||
|
||||
## Multiplayer FAQ
|
||||
This FAQ will serve as a general quick question and answer simple questions.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Multiplayer-FAQ-2c357c2edaf680fca2e9ce59969a220f) for a version of this guide with images & visual elements.**
|
||||
|
||||
### Can Eden Play Games with a Switch Console?
|
||||
No - The only emulator that has this kind of functionality is *Ryujinx* and it's forks. This solution requires loading a custom module on a modded switch console to work.
|
||||
|
||||
### Can I Play Online Games?
|
||||
No - This would require hijacking requests to Nintendo's official servers to a custom server infrastructure built to emulate that functionality. This is how services like [*Pretendo*](https://pretendo.network/) operate. As such, you would not be able to play “Online”, you can however play multiplayer games.
|
||||
|
||||
### What's the Difference Between Online and Multiplayer?
|
||||
I have chosen the wording carefully here for a reason.
|
||||
|
||||
- Online: Games that connect to Nintendo's Official servers and allow games/functionality using that communication method are unsupported on any emulator currently (for obvious reasons).
|
||||
- Multiplayer: The ability to play with multiple players on separate systems. This is supported for games that support LDN Local Wireless.
|
||||
|
||||
The rule of thumb here is simple: If a game supports the ability to communicate without a server (Local Wireless, LAN, etc.) you will be able to play with other users. If it requires a server to function - it will not. You will need to look up if your title support Local Wireless/LAN play as an option.
|
||||
|
||||
### How Does Multiplayer Work on Eden Exactly?
|
||||
Eden's multiplayer works by emulating the Switch's local wireless (LDN) system, then tunneling that traffic over the internet through “rooms” that act like lobbies. Each player runs their own instance of the emulator, and as long as everyone joins the same room and the game supports local wireless multiplayer, the emulated consoles see each other as if they were on the same local network. This design avoids typical one-save netplay issues because every user keeps an independent save and console state while only the in-game wireless packets are forwarded through the room server. In practice, you pick or host a room, configure your network interface/port forwarding if needed, then launch any LDN-capable game; from the game's perspective it is just doing standard local wireless, while the emulator handles discovery and communication over the internet or LAN.
|
||||
|
||||
### What Do I Need to Do?
|
||||
That depends entirely on what your goal is and your level of technical ability, you have a 2 options on how to proceed.
|
||||
|
||||
1. Join a Public Lobby.
|
||||
1. If you just want to play *Mario Kart 8 Deluxe* with other people and don't care how it works or about latency - this is the option for you. See the *Joining a Multiplayer Room* section for instructions on how to do this.
|
||||
2. Host Your Own Room.
|
||||
1. This option will require you to be comfortable with accessing your router's configuration, altering firewall rules, and troubleshooting when things (inevitably) don't work out perfectly on the first try. Use this option if you want to control the room entirely, are concerned about latency issues, or just want to run something for your friends. See the *Hosting a Multiplayer Room* section for next steps*.*
|
||||
|
||||
### Can Other Platforms Play Together?
|
||||
Yes - the platform you choose to run the emulator on does not matter. Steam Deck users can play with Windows users, Android users can play with MacOS users, etc. Furthermore different emulators can play together as well (Eden/Citron/Ryubing, etc.) - but be you may want to all go to the same one if you are having issues.
|
||||
|
||||
### What Pitfalls Should I Look Out For?
|
||||
While it would be nice if everything always worked perfectly - that is not reality. Here are some things you should watch out for when attempting to play multiplayer.
|
||||
|
||||
1. Emulator Version Mismatches
|
||||
1. Occasionally updates to the emulator of choice alter how the LDN functionality is handled. In these situations, unexpected behavior can occur when trying to establish LDN connections. This is a good first step to check if you are having issues playing a game together, but can join the same lobby without issue.
|
||||
2. Game Version Mismatches
|
||||
1. It is best practice to have the game version be identical to each other in order to ensure that there is no difference in how the programs are handling the LDN logic. Games are black boxes that the dev team cannot see into to ensure the logic handling operates the same way. For this reason, it is highly advised that the game versions match across all the players. This would be a good 2nd step to check if you are having issues playing a game together, but can join the same lobby without issue.
|
||||
3. Latency
|
||||
1. Because this implementation is emulating a LAN/Local Wireless connection - it is extremely sensitive to network latency and drops. Eden has done a good job of trying to account for this and not immediately drop users out - but it is not infallible. If latency is a concern or becomes an issue - consider hosting a room.
|
||||
|
||||
---
|
||||
|
||||
## Joining a Multiplayer Room
|
||||
Use this when you need to connect to a multiplayer room for LDN functionality inside of Eden. This does not cover how to host a room, only joining existing ones.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Access-Your-Multiplayer-Room-Externally-2c357c2edaf681c0ab2ce2ee624d809d) for a version of this guide with images & visual elements.**
|
||||
|
||||
### Pre-Requisites
|
||||
- Eden set up and functioning
|
||||
- Multiplayer Options Configured in Eden Settings
|
||||
- Network Access
|
||||
|
||||
## Steps
|
||||
There are 2 primary methods that you can use to connect to an existing room, depending on how the room is hosted.
|
||||
|
||||
- Joining a Public Lobby
|
||||
- This option allows you to view publicly hosted lobbies and join them easily. Use this option if you just want to join a room and play quickly.
|
||||
- Directly Connecting to a Room
|
||||
- Use this option if the hosted room is not on the public lobby list (private, internal network only, etc.)
|
||||
|
||||
<aside>
|
||||
|
||||
***NOTE:*** Just because a lobby appears on the public lobby list, does not mean that the hoster has properly configured the necessary port forwarding/firewall rules to allow a connection. If you cannot connect to a lobby, move onto another entry as the issue is probably not on your end. Start looking at your environment if you are unable to connect to multiple/any lobbies.
|
||||
|
||||
</aside>
|
||||
|
||||
### Joining a Public Lobby
|
||||
1. Open Eden and navigate to *Multiplayer → Browse Public Game Lobby*.
|
||||
2. The **Public Room Browser** will now open and display a list of publicly accessible rooms. Find one you want to connect to and double click it.
|
||||
|
||||
<aside>
|
||||
|
||||
***NOTE***: Just because a room is set for a specific game, does not mean that you ***have*** to play that game in that lobby. It is generally good practice to do so, but there is no enforcement of that.
|
||||
|
||||
</aside>
|
||||
3. You will now see a window showing everyone on the lobby, or an error message.
|
||||
|
||||
### Direct Connecting to a Room
|
||||
If the hoster has not made the lobby public, or you don't want to find it in the public game browser - use this option to connect.
|
||||
|
||||
1. Open Eden and navigate to *Multiplayer → Direct Connect*.
|
||||
2. Enter the *Server Address, Port*, *Nickname* (what your user will be called in the room), and a *Password* (if the hoster set one, otherwise leave it blank) and hit **Connect.**
|
||||
3. You will now see a window showing everyone on the lobby, or an error message.
|
||||
|
||||
---
|
||||
|
||||
# Hosting a Multiplayer Room
|
||||
Use this guide for when you want to host a multiplayer lobby to play with others in Eden. In order to have someone access the room from outside your local network, see the *Access Your Multiplayer Room Externally* section for next steps.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Hosting-a-Multiplayer-Room-2c357c2edaf6819481dbe8a99926cea2) for a version of this guide with images & visual elements.**
|
||||
|
||||
### Pre-Requisites
|
||||
- Eden set up and Functioning
|
||||
- Network Access
|
||||
- Ability to allow programs through the firewall on your device.
|
||||
|
||||
## Steps
|
||||
1. Open Eden and navigate to *Emulation → Multiplayer → Create Room.*
|
||||
2. Fill out the following information in the popup dialog box.
|
||||
|
||||
|
||||
| **Option** | **Acceptable Values** | **Default** | **Description** |
|
||||
| --- | --- | --- | --- |
|
||||
| Room Name | *Any string between 4 - 20 characters.* | *None* | Controls the name of the room and how it would appear in the Public Game Lobby/Room screen. |
|
||||
| Username | *Any string between 4 - 20 characters.* | *None* | Controls the name your user will appear as to the other users in the room. |
|
||||
| Preferred Game | *Any Game from your Game List.* | The first game on your game list | What game will the lobby be playing? You are not forced to play the game you choose and can switch games without needing to recreate the room. |
|
||||
| Password | *None or any string* | *None* | What password do you want to secure the room with, if any. |
|
||||
| Max Players | 2 - 16 | 8 | How many players do you want to allow in the room at a time? |
|
||||
| Port | 1024 - 65535 | 24872 | What port do you want to run the lobby on? Could technically be any port number, but it's best to choose an uncommon port to avoid potential conflicts. See [*Well-Known Ports*](https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports) for more information on ports commonly used. |
|
||||
| Room Description | *None or any string* | *None* | An optional message that elaborates on what the room is for, or for a makeshift message of the day presented to users in the lobby. |
|
||||
| Load Previous Ban List | [Checked, Unchecked] | Checked | Tells Eden to load the list containing users you have banned before. |
|
||||
| Room Type | [Public, Unlisted] | Public | Specifies whether you want the server to appear in the public game lobby browser |
|
||||
3. Click **Host Room** to start the room server. You may get a notice to allow the program through the firewall from your operating system. Allow it and then users can attempt to connect to your room.
|
||||
|
||||
---
|
||||
|
||||
# Access Your Multiplayer Room Externally
|
||||
Quite often the person with whom you want to play is located off of your internal network (LAN). If you want to host a room and play with them you will need to get your devices to communicate with each other. This guide will go over your options on how to do this so that you can play together.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Access-Your-Multiplayer-Room-Externally-2c357c2edaf681c0ab2ce2ee624d809d) for a version of this guide with images & visual elements.**
|
||||
|
||||
### Pre-Requisites
|
||||
- Eden set up and Functioning
|
||||
- Network Access
|
||||
|
||||
## Options
|
||||
|
||||
### Port Forwarding
|
||||
|
||||
- **Difficulty Level**: High
|
||||
|
||||
<aside>
|
||||
|
||||
Use this option if you want the greatest performance/lowest latency, don't want to install any software, and have the ability to modify your networking equipment's configuration (most notably - your router). Avoid this option if you cannot modify your router's configuration or are uncomfortable with looking up things on your own.
|
||||
|
||||
</aside>
|
||||
|
||||
|
||||
Port forwarding is a networking technique that directs incoming traffic arriving at a specific port on a router or firewall to a designated device and port inside a private local network. When an external client contacts the public IP address of the router on that port, the router rewrites the packet's destination information (IP address and sometimes port number) and forwards it to the internal host that is listening on the corresponding service. This allows services such as web servers, game servers, or remote desktop sessions hosted behind NAT (Network Address Translation) to be reachable from the wider Internet despite the devices themselves having non-routable private addresses.
|
||||
|
||||
The process works by creating a static mapping—often called a “port-forward rule”—in the router's configuration. The rule specifies three pieces of data: the external (public) port, the internal (private) IP address of the target machine, and the internal port on which that machine expects the traffic. When a packet arrives, the router checks its NAT table, matches the external port to a rule, and then translates the packet's destination to the internal address before sending it onward. Responses from the internal host are similarly rewritten so they appear to come from the router's public IP, completing the bidirectional communication loop. This mechanism enables seamless access to services inside a protected LAN without exposing the entire network.
|
||||
|
||||
For our purposes we would pick the port we want to expose (*e.g. 24872*) and we would access our router's configuration and create a port-forward rule to send the traffic from an external connection to your local machine over our specified port (*24872)*. The exact way to do so, varies greatly by router manufacturer - and sometimes require contacting your ISP to do so depending on your agreement. You can look up your router on [*portforward.com*](https://portforward.com/router.htm) which may have instructions on how to do so for your specific equipment. If it is not there, you will have to use Google/ChatGPT to determine the steps for your equipment.
|
||||
|
||||
|
||||
### Use a Tunnelling Service
|
||||
- **Difficulty Level**: Easy
|
||||
|
||||
<aside>
|
||||
|
||||
Use this option if you don't want to have to worry about other users machine/configuration settings, but also cannot do port forwarding. This will still require that you as the hoster install a program and sign up for an account - but will prevent you from having to deal with port forwards or networking equipment. Avoid this option if there is not a close relay and you are getting issues with latency.
|
||||
|
||||
</aside>
|
||||
|
||||
|
||||
Using a Tunnelling service may be the solution to avoid port forward, but also avoid worrying about your users setup. A tunnelling service works by having a lightweight client run on the machine that hosts the game server. That client immediately opens an **outbound** encrypted connection (typically over TLS/QUIC) to a relay node operated by the tunnel provider's cloud infrastructure. Because outbound traffic is almost always allowed through NAT routers and ISP firewalls, the tunnel can be established even when the host sits behind carrier-grade NAT or a strict firewall. The tunnel provider then assigns a public address (e.g., `mygame.playit.gg:12345`). When a remote player connects to that address, the traffic reaches the the tunnel provider relay, which forwards it through the already-established tunnel back to the client on the private network, and finally onto the local game server's port. In effect, the server appears to the Internet as if it were listening on the public address, while the host never needs to configure port-forwarding rules or expose its own IP directly.
|
||||
|
||||
For our purposes we would spawn the listener for the port that way chose when hosting our room. The user would connect to our assigned public address/port combination, and it would be routed to our machine. The tunnel must remain active for as long as you want the connection to remain open. Closing the terminal will kill the tunnel and disconnect the users.
|
||||
|
||||
**Recommended Services:**
|
||||
- [*Playit.GG*](https://playit.gg/)
|
||||
|
||||
|
||||
### Use a VPN Service
|
||||
|
||||
- **Difficulty**: Easy
|
||||
|
||||
<aside>
|
||||
|
||||
Use this option if you don't want to use a tunnelling service, or don't want to manage the overhead of the playit gg solution, don't want to send data through the relay, or any other reason. Avoid this method if you do not want to have to manage VPN installation/configuration for your users.
|
||||
|
||||
</aside>
|
||||
|
||||
The VPN solution is a good compromise between the tunnelling solution and port forwarding. You do not have to port forward or touch your networking equipment at all - but also don't need to send all your data connections through a 3rd party relay. The big downside is that you will have to ensure all of your users have your VPN solution installed *and* that they have a valid configuration. When looking for a solution, it is advised to find one that uses the WireGuard protocol for speed, and does not require communication with a server beyond the initial handshake.
|
||||
|
||||
**Recommended Services:**
|
||||
- [*Tailscale*](https://tailscale.com/)
|
||||
- [*ZeroTier*](https://www.zerotier.com/)
|
||||
- *Self-hosted VPN Solution*
|
||||
- This is so far out of the scope of this document it has a different postal code.
|
||||
|
||||
*Check with the provider you select on the sign up and installation process specific to that provider.*
|
||||
|
||||
---
|
||||
|
||||
# Finding the Server Information for a Multiplayer Room
|
||||
Use this guide when you need to determine the connection information for the Public Multiplayer Lobby you are connected to.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Finding-the-Server-Information-for-a-Multiplayer-Room-2c557c2edaf6809e94e8ed3429b9eb26) for a version of this guide with images & visual elements.**
|
||||
|
||||
### Pre-Requisites
|
||||
- Eden set up and configured
|
||||
- Internet Access
|
||||
|
||||
## Steps
|
||||
|
||||
### Method 1: Grabbing the Address from the Log File
|
||||
1. Open Eden and Connect to the room you want to identify.
|
||||
1. See the *Joining a Multiplayer Room* section for instructions on how to do so if you need them.
|
||||
2. Go to *File → Open Eden Folder*, then open the **config** folder.
|
||||
3. Open the the **qt-config.ini** file in a text editor.
|
||||
4. Search for the following keys:
|
||||
1. `Multiplayer\ip=`
|
||||
2. `Multiplayer\port=`
|
||||
5. Copy the Server Address and Port.
|
||||
|
||||
### Method 2: Using a Web Browser
|
||||
1. Obtain the name of the room you want the information for.
|
||||
2. Open a Web Browser.
|
||||
3. Navigate to [`https://api.ynet-fun.xyz/lobby`](https://api.ynet-fun.xyz/lobby)
|
||||
4. Press *Ctrl + F* and search for the name of your room.
|
||||
5. Look for and copy the Server Address and Port.
|
||||
|
||||
### Method 3: Using a Terminal (PowerShell or CURL)
|
||||
1. Obtain the name of the room you want the information for.
|
||||
2. Open the terminal supported by your operating system.
|
||||
3. Run one of the following commands, replacing *<Name>* with the name of the server from step 1.
|
||||
|
||||
### PowerShell Command [Windows Users]
|
||||
|
||||
```powershell
|
||||
# Calls the API to get the address and port information
|
||||
(Invoke-RestMethod -Method Get -Uri "https://api.ynet-fun.xyz/lobby").rooms | Where-Object {$_.Name -eq '<NAME>'} | Select address,port | ConvertTo-Json
|
||||
|
||||
# Example Output
|
||||
#{
|
||||
# "address": "118.208.233.90",
|
||||
# "port": 5001
|
||||
#}
|
||||
```
|
||||
|
||||
### CURL Command [MacOS/Linux Users] **Requires jq*
|
||||
|
||||
```bash
|
||||
# Calls the API to get the address and port information
|
||||
curl -s "https://api.ynet-fun.xyz/lobby" | jq '.rooms[] | select(.name == "<NAME>") | {address, port}'
|
||||
|
||||
# Example Output
|
||||
#{
|
||||
# "address": "118.208.233.90",
|
||||
# "port": 5001
|
||||
#}
|
||||
```
|
||||
|
||||
4. Copy the Server Address and Port.
|
||||
|
||||
---
|
||||
|
||||
# Multiplayer for Local Co-Op Games
|
||||
Use this guide when you want to play with a friend on a different system for games that only support local co-op.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Multiplayer-for-Local-Co-Op-Games-2c657c2edaf680c59975ec6b52022a2d) for a version of this guide with images & visual elements.**
|
||||
|
||||
Occasionally you will want to play a game with a friend on a game that does not support LDN multiplayer, and only offer local co-op (multiple controllers connected to a single console), such as with *New Super Mario Bros. U Deluxe.* Emulation solutions have developed 2 primary methods for handling these cases.
|
||||
|
||||
1. Netplay: Netplay lets two or more players run the same game on their own computers while sharing each other's controller inputs over the internet, so everyone sees the same game world in sync. One player hosts the session, and the others join as guests, sending their button presses back and forth to keep the gameplay coordinated.
|
||||
1. This is a huge over-simplification of how it works, but gives you an idea
|
||||
2. Low-Latency remote desktop solutions: Using a service like *Parsec*, the host shares his screen to a remote party with an input device connected. This device sends inputs to the host machine.
|
||||
|
||||
In either situation at its core, we are emulating an input device on the host machine, so the game believes 2 controllers are connected. No current Switch emulator has a Netplay offering, so we use Parsec to accomplish this for us.
|
||||
|
||||
### Pre-Requisites
|
||||
- Eden Set Up and Fully Configured
|
||||
- A [*Parsec*](https://parsec.app/) Account
|
||||
- Parsec is free to use for personal, non-commercial use. For instructions on how to set up an account and install the client you should refer to the Parsec documentation on it's site.
|
||||
- Parsec client installed on your machine and remote (friend's) machine
|
||||
|
||||
## Steps
|
||||
|
||||
<aside>
|
||||
|
||||
This guide will assume you are the one hosting the game and go over things *Parsec* specific at a high level, as their system is subject to change. Follow *Parsec's* documentation where needed.
|
||||
|
||||
</aside>
|
||||
|
||||
1. Launch Parsec on the host machine.
|
||||
2. Connect to the other player in Parsec. You will know it is successful when the other player can see the host's screen.
|
||||
1. If you are the one hosting the game, you will have your friend initiate the remote connection you will accept.
|
||||
2. If you are joining a game, you will have to send a connection request the host will have to accept.
|
||||
3. Verify that the remote player can see the screen and that there is no issues with the connection.
|
||||
4. Launch Eden.
|
||||
5. Navigate to *Emulation → Configure*.
|
||||
6. Select the **Controls** tab.
|
||||
7. Set up your controller, if necessary.
|
||||
8. Select the **Player 2** tab and select the **Connect Controller** checkbox. This enables inputs from another device to be seen as a second controller.
|
||||
9. Dropdown the **Input Device** and select the controller.
|
||||
1. What exactly it shows up as depends on the Parsec settings.
|
||||
10. Set up the remote player's controller.
|
||||
11. Hit **OK** to apply the changes.
|
||||
12. Launch the game you want to play and enter the co-op mode. How this works depends on the game, so you will have to look in the menus or online to find out.
|
||||
42
docs/user/QuickStart_SteamDeck.md
Normal file
42
docs/user/QuickStart_SteamDeck.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Eden Quick Start
|
||||
|
||||
Use this guide to get starting using the Eden emulator on Steam Deck.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Eden-Quick-Start-2b757c2edaf680d49ffdcda291a32840) for a version of this guide with images & visual elements.**
|
||||
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- Firmware dumped from your console
|
||||
- Keys extracted from your console
|
||||
- Games dumped from your console
|
||||
- Internet Connection
|
||||
|
||||
---
|
||||
|
||||
## Steps
|
||||
|
||||
1. Access Steam Desktop Mode.
|
||||
2. Download either the *Stable* or *Nightly* Eden AppImage onto your Steam Deck and save it somewhere accessible.
|
||||
|
||||
<aside>
|
||||
|
||||
***TIP***: If you have questions about the requirements, architectures, or general information surrounding what release you need - see the [*Basics Guide*](./Basics.md) and [*Architectures Guide*](./Architectures.md).
|
||||
|
||||
</aside>
|
||||
|
||||
3. Double-Click the Eden executable to launch the program.
|
||||
<aside>
|
||||
|
||||
***NOTE***: The first time you run the AppImage you will get a notification asking you to confirm you want to launch the program. Hit **Continue**.
|
||||
|
||||
</aside>
|
||||
|
||||
4. If you have had a different Switch emulator installed, it will detect and ask if you want to import those settings. Make your selection to close the screen.
|
||||
5. Eden will now launch and notify you about missing Encryption keys. Close the dialog box by hitting **OK**.
|
||||
6. Navigate to **Tools → Install Decryption Keys**, navigate to the folder containing your ***prod.keys*** file and select the file and hit **Open**.
|
||||
7. Navigate to **Tools → Install Firmware →** *Select **From Folder*** or ***From ZIP*** - depending on how your firmware is stored, navigate to where it is stored and select it.
|
||||
8. Double-Click the main window to add the folder containing your games.
|
||||
9. Go to *Emulation > Configure > Input* and set up your controller. Click **OK** to close the dialog window.
|
||||
10. Double-Click a game to run it.
|
||||
@@ -4,18 +4,21 @@ Use this guide to get starting using the Eden emulator.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Eden-Quick-Start-2b057c2edaf6817b9859d8bcdb474017) for a version of this guide with images & visual elements.**
|
||||
|
||||
## Windows
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
|
||||
- The [*latest C++ Redistributable*](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#latest-supported-redistributable-version) from Microsoft.
|
||||
- Eden will not even launch without it see [*Eden Fails to Launch*](./Troubleshoot.md) for more information.
|
||||
- Eden will not even launch without it see [*Eden Fails to Launch*](./EdenFailsToLaunch.md) for more information.
|
||||
- Firmware dumped from your console
|
||||
- Keys extracted from your console
|
||||
- Games dumped from your console
|
||||
- Internet Connection
|
||||
|
||||
### Steps
|
||||
---
|
||||
|
||||
## Steps
|
||||
|
||||
1. Download either the *Stable* or *Nightly* Eden application.
|
||||
<aside>
|
||||
@@ -41,39 +44,4 @@ Use this guide to get starting using the Eden emulator.
|
||||
6. Navigate to **Tools → Install Firmware**, *Select **From Folder*** or ***From ZIP*** - depending on how your firmware is stored, navigate to where it is stored and select it.
|
||||
7. Double-Click the main window to add the folder containing your games.
|
||||
8. Go to *Emulation > Configure > Input* and set up your controller of choice. Click **OK** to close the dialog window.
|
||||
9. Double-Click a game to run it.
|
||||
|
||||
## Steamdeck
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- Firmware dumped from your console
|
||||
- Keys extracted from your console
|
||||
- Games dumped from your console
|
||||
- Internet Connection
|
||||
|
||||
### Steps
|
||||
|
||||
1. Access Steam Desktop Mode.
|
||||
2. Download either the *Stable* or *Nightly* Eden AppImage onto your Steam Deck and save it somewhere accessible.
|
||||
|
||||
<aside>
|
||||
|
||||
***TIP***: If you have questions about the requirements, architectures, or general information surrounding what release you need - see the [*Basics Guide*](./Basics.md) and [*Architectures Guide*](./Architectures.md).
|
||||
|
||||
</aside>
|
||||
|
||||
3. Double-Click the Eden executable to launch the program.
|
||||
<aside>
|
||||
|
||||
***NOTE***: The first time you run the AppImage you will get a notification asking you to confirm you want to launch the program. Hit **Continue**.
|
||||
|
||||
</aside>
|
||||
|
||||
4. If you have had a different Switch emulator installed, it will detect and ask if you want to import those settings. Make your selection to close the screen.
|
||||
5. Eden will now launch and notify you about missing Encryption keys. Close the dialog box by hitting **OK**.
|
||||
6. Navigate to **Tools → Install Decryption Keys**, navigate to the folder containing your ***prod.keys*** file and select the file and hit **Open**.
|
||||
7. Navigate to **Tools → Install Firmware →** *Select **From Folder*** or ***From ZIP*** - depending on how your firmware is stored, navigate to where it is stored and select it.
|
||||
8. Double-Click the main window to add the folder containing your games.
|
||||
9. Go to *Emulation > Configure > Input* and set up your controller. Click **OK** to close the dialog window.
|
||||
10. Double-Click a game to run it.
|
||||
9. Double-Click a game to run it.
|
||||
@@ -4,35 +4,14 @@ The "FAQ".
|
||||
|
||||
This handbook is primarily aimed at the end-user - baking useful knowledge for enhancing their emulation experience.
|
||||
|
||||
## Basics
|
||||
|
||||
- **[The Basics](Basics.md)**
|
||||
- **[Quickstart](./QuickStart.md)**
|
||||
- **[Run On macOS](./RunOnMacOS.md)**
|
||||
- **[Audio](Audio.md)**
|
||||
- **[Graphics](Graphics.md)**
|
||||
- **[Data, Savefiles and Storage](Storage.md)**
|
||||
- **[Orphaned Profiles](Orphaned.md)**
|
||||
- **[Troubleshooting](./Troubleshoot.md)**
|
||||
- **[Using Amiibo](./UsingAmiibo.md)**
|
||||
- **[Using Cheats](./UsingCheats.md)**
|
||||
- **[Importing Saves](./ImportingSaves.md)**
|
||||
- **[Add Eden to Steam ROM Manager](./AddEdenToSRM.md)**
|
||||
- **[Add Games to Steam ROM Manager](./AddGamesToSRM.md)**
|
||||
- **[Installing Atmosphere Mods](./InstallingAtmosphereMods.md)**
|
||||
- **[Installing Updates & DLCs](./InstallingUpdatesDLC.md)**
|
||||
- **[Controller Profiles](./ControllerProfiles.md)**
|
||||
- **[Alter Date & Time](./AlterDateTime.md)**
|
||||
|
||||
## Advanced
|
||||
|
||||
- **[How To Access Logs](./HowToAccessLogs.md)**
|
||||
- **[Gyro Controls](./GyroControls.md)**
|
||||
- **[Platforms and Architectures](Architectures.md)**
|
||||
- **[Server hosting](ServerHosting.md)**
|
||||
- **[Graphics](Graphics.md)**
|
||||
- **[Platforms and Architectures](Architectures.md)**
|
||||
- **[Testing](Testing.md)**
|
||||
- **[Data, savefiles and storage](Storage.md)**
|
||||
- **[Orphaned Profiles](Orphaned.md)**
|
||||
- **[Command Line](CommandLine.md)**
|
||||
- **[Native Application Development](Native.md)**
|
||||
- **[Adding Boolean Settings Toggles](AddingBooleanToggles.md)**
|
||||
- **[Adding Debug Knobs](./AddingDebugKnobs.md)**
|
||||
- **[Syncthing Guide](./SyncthingGuide.md)**
|
||||
- **[Testing](Testing.md)**
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
# User Handbook - Backing Up/Syncing Eden Game Saves
|
||||
|
||||
Use this guide for when you want to configure automated backup/syncing of your Eden save files using [*Syncthing*](https://syncthing.net/).
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Backing-Up-Syncing-Eden-Game-Saves-2b357c2edaf68000b40cfab2c2c3dc0a) for a version of this guide with images & visual elements.**
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- Eden already installed, configured, and functioning.
|
||||
- Devices to run Syncthing on.
|
||||
- Ability to allow a program to communicate through the firewall of your device.
|
||||
|
||||
## Introduction
|
||||
|
||||
<aside>
|
||||
|
||||
***WARNING***: You should manually back up your save files before proceeding with this guide. If you incorrectly perform the steps, you risk losing them!
|
||||
|
||||
</aside>
|
||||
|
||||
- While this is a de-centralized model without the concepts of a Server/Client, Parent/Child, etc. - For the purposes of these guides, we will borrow from this models terminology to avoid sync conflicts and potential data loss. After the initial setup, all the devices in the sync network are equals and can push & pull files from any other device.
|
||||
- In order for this to work, you should get all of the save files in Eden in the save folder on the Parent.
|
||||
- If you need help doing that, see the ***Importing Saves into Eden*** guide for the platform you elect to act as the Parent, and delete the save files on the "Child" devices.
|
||||
|
||||
### Terminology
|
||||
|
||||
- **Sync Network**: All the devices configured in *Syncthing* to push/pull files.
|
||||
- **Parent**: This will be the device that you elect to push files to the other devices. There can only be one here initially in order to avoid sync conflicts.
|
||||
- **Child**: All the other devices added to the Sync Network. These devices will pull files from the Parent.
|
||||
|
||||
## Overview
|
||||
|
||||
Rather than giving a breakdown of all the platforms and configurations, those will be in the platform’s specific guides - this will serve as a general overview of Syncthing.
|
||||
|
||||
### What is Syncthing Anyway?
|
||||
|
||||
Syncthing is a continuous file synchronization program (in the layman’s - make sure 2 or more systems with the same files are always up to date). This is perfect for game saves where we would want to play on 1 device, save our game, and then continue playing it on another device. This technology is what Epic/Steam/etc. use to allow you to do this on games run through their respective services. Syncthing is an open source implementation of this technology that you control, rather than relying on a 3rd party. This has a few key benefits, most notably - better security, privacy, and speed (when on your LAN).
|
||||
|
||||
### What are some common issues?
|
||||
|
||||
Syncthing is fairly robust and doesn’t have many issues luckily, but there are some things you should watch out for (almost all of them a user issue).
|
||||
|
||||
- Sync conflicts
|
||||
- If for whatever reason you update the same file on 2 different machines, the system does not know which updated file is considered the one to sync across. This results in a ***sync conflict*** where it may not sync the files as you would expect. Worst case scenario, this can result in your save progress being lost if you are not careful. When one of these occurs, it will create a copy of the file and store it with a specific name, like this example, *Paper Mario.sync-conflict-20251102-072925-TZBBN6S.srm.* To resolve this, you must remove the other files and remove the *.sync-conflict-<TIMESTAMP>-<Syncthing Device ID>* from the file name of the file you want to keep.
|
||||
- Accidental Deletions
|
||||
- If you delete a file from one of the devices, it will also remove the file on the other devices when they perform a sync so be careful when doing this.
|
||||
|
||||
## Windows
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- Eden already installed, configured, and functioning.
|
||||
- Ability to allow a program to communicate through the firewall in Windows.
|
||||
- Ability to extract archive (.zip/.7z/.rar) files.
|
||||
|
||||
### Steps
|
||||
|
||||
<aside>
|
||||
|
||||
***WARNING***: You should manually back up your save files before proceeding with this guide. If you incorrectly perform the steps, you risk losing them!
|
||||
|
||||
</aside>
|
||||
|
||||
#### Downloading and Installing *Syncthing*
|
||||
|
||||
1. Download [*Syncthing Tray*](https://martchus.github.io/syncthingtray/#downloads-section).
|
||||
1. While it is available as a command line interface, for most people I would recommend *Syncthing Tray* on Windows. For most people here, you would download the **64-bit (Intel/AMD)** version.
|
||||
2. Open the downloaded archive and extract the **syncthingtray.exe** to wherever you want to store the executable.
|
||||
3. Double-Click the application to run it, select the **Start guided setup** on the splash screen that appears and press **Next**.
|
||||
|
||||
<aside>
|
||||
|
||||
***NOTE***: You may get a Windows Defender Smart Screen pop up, this is a known thing, just accept and run anyway.
|
||||
|
||||
</aside>
|
||||
4. It will then look for an existing Syncthing instance to pull settings from, but will likely fail to do so if you are here. Regardless, select the **Yes, continue configuration** option.
|
||||
5. Select ***Start Syncthing application that is built into Syncthing Tray***, this means it will use a built in Syncthing executable rather than relying on an externally provided one. Press **Next** to continue.
|
||||
6. Check the box to start Syncthing Tray on login - as the name implies, this means the program will run automatically whenever you log onto the computer. Press Next to continue.
|
||||
7. You will now be presented with a confirmation window with your selections, confirm they are what you want and hit **Apply** to continue.
|
||||
8. You will now be prompted with a confirmation window and a message to allow it through the firewall. Allow the access through the firewall to close that pop up. The confirmation screen has a QR code and the devices identifier - you will need one of these to add other devices to the sync system.
|
||||
9. *Syncthing/Syncthing Tray* are now installed.
|
||||
|
||||
#### Configuring this Machine as a Parent
|
||||
|
||||
Use this when you want to set this machine as the initial source of truth (push files out to all the other devices). Afterwards they will all be equal partners, not a parent/child relationship, this just helps with initial setup.
|
||||
|
||||
1. Right-Click the *Syncthing* Tray icon in your taskbar and select **Open Syncthing.**
|
||||
2. You will now have a browser window open up to a web GUI to configure *Syncthing*. You will get a pop up about allowing anonymous usage and setting a password, make your selections to close them.
|
||||
3. We’ll start by adding the folder with our save files that we want to sync by Pressing **+ Add Folder**.
|
||||
4. A pop-up window will appear, fill in the Folder label field with whatever you want to call it, like Switch Saves.
|
||||
5. Enter the Full folder path to where your save files are stored on this machine.
|
||||
|
||||
<aside>
|
||||
|
||||
***TIP***: The easiest way to do this would be to open Eden, right-click a game that has a save, hit ***Open Save Data Location,*** and then go up 1 directory. It should contain folders with the TitleID of your games.
|
||||
|
||||
It should look similar to this: ..*\nand\user\save\0000000000000000\EC573727F509799675F6E5112C581D7E*
|
||||
|
||||
</aside>
|
||||
|
||||
6. Ignore the other tabs for now and hit **Save**.
|
||||
7. The folder is now ready to be shared with other devices.
|
||||
|
||||
#### Configuring this Machine as a Child
|
||||
|
||||
Use this when you want to set this machine up as a child (pull files from the other devices). Afterwards they will all be equal partners, not a parent/child relationship, this just helps with initial setup.
|
||||
|
||||
1. Install Syncthing Tray on the client device following the section above. Copy the child’s ID and store it so it is accessible to the Parent.
|
||||
2. ***ON THE PARENT***: Right-Click the *Syncthing* Tray icon in your taskbar and select **Open Syncthing** if it is not open already**.**
|
||||
3. You will now have a browser window open up to a web GUI to configure *Syncthing*. You will get a pop up about allowing anonymous usage and setting a password, make your selections to close them.
|
||||
4. Navigate down to **+ Add Remote Device**, we are going to add our Child device, so I hope you have its ID handy. If not, go back and get it.
|
||||
5. Add the ID and Name the device, the device may appear as a **nearby device**, in which case you can just click it to pre-populate the Device ID.
|
||||
6. Click the **Sharing** Tab, and check the box next to the folder you set up on the Parent (Switch Saves in my case). Hit **Save.**
|
||||
7. We are done with the parent, now **SWITCH OVER TO THE CHILD.**
|
||||
8. ***ON THE CHILD***: Right-Click the *Syncthing* Tray icon in your taskbar and select **Open Syncthing** if it is not open already**.**
|
||||
9. You should now see a connection request from the parent. Hit **+ Add Device** to add the device.
|
||||
10. Hit **Save** to finish adding the device.
|
||||
11. That pop-up will close and you will get notification that the device wants to share a folder now. Hit **Add.**
|
||||
12. Enter the path to the save folder in Eden and hit **Save.**
|
||||
|
||||
<aside>
|
||||
|
||||
***TIP***: The easiest way to do this would be to open Eden, right-click a game that has a save, hit ***Open Save Data Location,*** and then go up 1 directory. It should contain folders with the TitleID of your games.
|
||||
|
||||
It should look similar to this: ..*\nand\user\save\0000000000000000\EC573727F509799675F6E5112C581D7E*
|
||||
|
||||
</aside>
|
||||
|
||||
13. *Syncthing* will now pull all the files from the Parent and store them in your local save directory. At this point the files are in sync and alterations to one will affect the other and both can be considered “*Parents*” for other devices you want to add. Repeat these steps for as many devices you want.
|
||||
|
||||
## Linux
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- Eden already installed, configured, and functioning.
|
||||
|
||||
### Step 1: Downloading and Installing Syncthing
|
||||
|
||||
<aside>
|
||||
|
||||
***WARNING***: You should manually back up your save files before proceeding with this guide. If you incorrectly perform the steps, you risk losing them!
|
||||
|
||||
</aside>
|
||||
|
||||
<aside>
|
||||
|
||||
***NOTE***: I am using Linux Mint for my guides, but the steps should translate pretty easily to your distro. I ***hope*** that if you are running Linux you know the basic operations. Steam Deck users should follow the guide specific to that platform.
|
||||
|
||||
</aside>
|
||||
|
||||
1. Download [*Syncthing Tray*](https://flathub.org/en/apps/io.github.martchus.syncthingtray) from the Flatpak store.
|
||||
2. Launch *Syncthing Tray* to run it, select the **Start guided setup** on the splash screen that appears and press **Next**.
|
||||
3. It will then look for an existing *Syncthing* instance to pull settings from, but will likely fail to do so if you are here. Regardless, select the **Yes, continue configuration** option.
|
||||
4. Select ***Start installed Syncthing application via Syncthing Tray***, this means it will use a built in Syncthing executable rather than relying on an externally provided one. Press **Next** to continue.
|
||||
5. You will now be presented with a confirmation window with your selections, confirm they are what you want and hit **Apply** to continue.
|
||||
6. You will now be prompted with a confirmation window that has a QR code and the devices identifier - you will need one of these to add other devices to the sync system.
|
||||
7. *Syncthing/Syncthing Tray* are now installed. Press Finish to close the pop up.
|
||||
<aside>
|
||||
|
||||
***NOTE***: By default due to flatpak sandboxing limitations, Syncthing Tray will not run automatically on login. You can get around this by following the [*instructions here*](https://github.com/flathub/io.github.martchus.syncthingtray).
|
||||
|
||||
</aside>
|
||||
|
||||
### Step 2: Configuring this Machine as a Parent
|
||||
|
||||
Use this when you want to set this machine as the initial source of truth (push files out to all the other devices). Afterwards they will all be equal partners, not a parent/child relationship, this just helps with initial setup.
|
||||
|
||||
1. Right-Click the *Syncthing* Tray icon in your taskbar and select **Open Syncthing.**
|
||||
1. If you don’t have a taskbar in your distro, you can also reach it directly by opening a web browser to: *http://127.0.0.1:8384/.*
|
||||
2. You will now have a browser window open up to a web GUI to configure *Syncthing*. You will get a pop up about allowing anonymous usage and setting a password, make your selections to close them.
|
||||
3. We’ll start by adding the folder with our save files that we want to sync by Pressing **+ Add Folder**.
|
||||
4. A pop-up window will appear, fill in the Folder label field with whatever you want to call it, like Switch Saves.
|
||||
5. Enter the Full folder path to where your save files are stored on this machine.
|
||||
|
||||
<aside>
|
||||
|
||||
***TIP***: The easiest way to do this would be to open Eden, right-click a game that has a save, hit ***Open Save Data Location,*** and then go up 1 directory. It should contain folders with the TitleID of your games.
|
||||
|
||||
It should look similar to this: ..*\nand\user\save\0000000000000000\EC573727F509799675F6E5112C581D7E*
|
||||
|
||||
</aside>
|
||||
|
||||
6. Ignore the other tabs for now and hit **Save**.
|
||||
7. The folder is now ready to be shared with other devices.
|
||||
|
||||
### Step 3: Configuring this Machine as a Child
|
||||
|
||||
Use this when you want to set this machine up as a child (pull files from the other devices). Afterwards they will all be equal partners, not a parent/child relationship, this just helps with initial setup.
|
||||
|
||||
1. Install Syncthing Tray on the client device following the section above. Copy the child’s ID and store it so it is accessible to the Parent.
|
||||
2. ***ON THE PARENT***: Right-Click the *Syncthing* Tray icon in your taskbar and select **Open Syncthing** if it is not open already**.**
|
||||
3. You will now have a browser window open up to a web GUI to configure *Syncthing*. You will get a pop up about allowing anonymous usage and setting a password, make your selections to close them.
|
||||
4. Navigate down to **+ Add Remote Device**, we are going to add our Child device, so I hope you have its ID handy. If not, go back and get it.
|
||||
5. Add the ID and Name the device, the device may appear as a **nearby device**, in which case you can just click it to pre-populate the Device ID.
|
||||
6. Click the **Sharing** Tab, and check the box next to the folder you set up on the Parent (Switch Saves in my case). Hit **Save.**
|
||||
7. We are done with the parent, now **SWITCH OVER TO THE CHILD.**
|
||||
8. ***ON THE CHILD***: Right-Click the *Syncthing* Tray icon in your taskbar and select **Open Syncthing** if it is not open already.
|
||||
9. You should now see a connection request pop-up from the parent. Hit **+ Add Device** to add the device.
|
||||
10. Hit **Save** to finish adding the device.
|
||||
11. That pop-up will close and you will get notification that the device wants to share a folder now. Hit **Add.**
|
||||
12. Enter the path to the save folder in Eden and hit **Save.**
|
||||
|
||||
<aside>
|
||||
|
||||
***TIP***: The easiest way to do this would be to open Eden, right-click a game that has a save, hit ***Open Save Data Location,*** and then go up 1 directory. It should contain folders with the TitleID of your games.
|
||||
|
||||
It should look similar to this: ..*\nand\user\save\0000000000000000\EC573727F509799675F6E5112C581D7E*
|
||||
|
||||
</aside>
|
||||
|
||||
13. *Syncthing* will now pull all the files from the Parent and store them in your local save directory. At this point the files are in sync and alterations to one will affect the other and both can be considered “*Parents*” for other devices you want to add. Repeat these steps for as many devices you want.
|
||||
66
docs/user/SyncthingGuide_General.md
Normal file
66
docs/user/SyncthingGuide_General.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Backing Up/Syncing Eden Game Saves
|
||||
|
||||
Use this guide for when you want to configure automated backup/syncing of your Eden save files using [*Syncthing*](https://syncthing.net/).
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Backing-Up-Syncing-Eden-Game-Saves-2b357c2edaf68000b40cfab2c2c3dc0a) for a version of this guide with images & visual elements.**
|
||||
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- Eden already installed, configured, and functioning.
|
||||
- Devices to run Syncthing on.
|
||||
- Ability to allow a program to communicate through the firewall of your device.
|
||||
|
||||
---
|
||||
|
||||
## Platform Specific Setup Guides
|
||||
|
||||
- [*Windows*](./SyncthingGuide_Windows.md)
|
||||
- *MacOS (Coming Soon)*
|
||||
- *Steam Deck (Coming Soon)*
|
||||
- *Android (Coming Soon)*
|
||||
- [*Linux*](./SyncthingGuide_Linux.md)
|
||||
|
||||
---
|
||||
|
||||
## A Few Notes Before You Proceed
|
||||
|
||||
<aside>
|
||||
|
||||
***WARNING***: You should manually back up your save files before proceeding with this guide. If you incorrectly perform the steps, you risk losing them!
|
||||
|
||||
</aside>
|
||||
|
||||
- While this is a de-centralized model without the concepts of a Server/Client, Parent/Child, etc. - For the purposes of these guides, we will borrow from this models terminology to avoid sync conflicts and potential data loss. After the initial setup, all the devices in the sync network are equals and can push & pull files from any other device.
|
||||
- In order for this to work, you should get all of the save files in Eden in the save folder on the Parent.
|
||||
- If you need help doing that, see the ***Importing Saves into Eden*** guide for the platform you elect to act as the Parent, and delete the save files on the "Child" devices.
|
||||
|
||||
### Terminology
|
||||
|
||||
- **Sync Network**: All the devices configured in *Syncthing* to push/pull files.
|
||||
- **Parent**: This will be the device that you elect to push files to the other devices. There can only be one here initially in order to avoid sync conflicts.
|
||||
- **Child**: All the other devices added to the Sync Network. These devices will pull files from the Parent.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Rather than giving a breakdown of all the platforms and configurations, those will be in the platform’s specific guides - this will serve as a general overview of Syncthing.
|
||||
|
||||
---
|
||||
|
||||
### What is Syncthing Anyway?
|
||||
|
||||
Syncthing is a continuous file synchronization program (in the layman’s - make sure 2 or more systems with the same files are always up to date). This is perfect for game saves where we would want to play on 1 device, save our game, and then continue playing it on another device. This technology is what Epic/Steam/etc. use to allow you to do this on games run through their respective services. Syncthing is an open source implementation of this technology that you control, rather than relying on a 3rd party. This has a few key benefits, most notably - better security, privacy, and speed (when on your LAN).
|
||||
|
||||
---
|
||||
|
||||
### What are some common issues?
|
||||
|
||||
Syncthing is fairly robust and doesn’t have many issues luckily, but there are some things you should watch out for (almost all of them a user issue).
|
||||
|
||||
- Sync conflicts
|
||||
- If for whatever reason you update the same file on 2 different machines, the system does not know which updated file is considered the one to sync across. This results in a ***sync conflict*** where it may not sync the files as you would expect. Worst case scenario, this can result in your save progress being lost if you are not careful. When one of these occurs, it will create a copy of the file and store it with a specific name, like this example, *Paper Mario.sync-conflict-20251102-072925-TZBBN6S.srm.* To resolve this, you must remove the other files and remove the *.sync-conflict-<TIMESTAMP>-<Syncthing Device ID>* from the file name of the file you want to keep.
|
||||
- Accidental Deletions
|
||||
- If you delete a file from one of the devices, it will also remove the file on the other devices when they perform a sync so be careful when doing this.
|
||||
96
docs/user/SyncthingGuide_Linux.md
Normal file
96
docs/user/SyncthingGuide_Linux.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Backing Up/Syncing Eden Game Saves
|
||||
|
||||
Use this guide for when you want to configure automated backup/syncing of your Eden save files using [*Syncthing*](https://syncthing.net/) on Linux.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Backing-Up-Syncing-Eden-Game-Saves-2b057c2edaf680fc8a28eba5a05fd7a3) for a version of this guide with images & visual elements.**
|
||||
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- Read the [*Syncthing General Guide*](./SyncthingGuide_General.md).
|
||||
- Eden already installed, configured, and functioning.
|
||||
|
||||
---
|
||||
|
||||
## Steps
|
||||
|
||||
<aside>
|
||||
|
||||
***WARNING***: You should manually back up your save files before proceeding with this guide. If you incorrectly perform the steps, you risk losing them!
|
||||
|
||||
</aside>
|
||||
|
||||
<aside>
|
||||
|
||||
***NOTE***: I am using Linux Mint for my guides, but the steps should translate pretty easily to your distro. I ***hope*** that if you are running Linux you know the basic operations. Steam Deck users should follow the guide specific to that platform.
|
||||
|
||||
</aside>
|
||||
|
||||
### Downloading and Installing Syncthing
|
||||
|
||||
1. Download [*Syncthing Tray*](https://flathub.org/en/apps/io.github.martchus.syncthingtray) from the Flatpak store.
|
||||
2. Launch *Syncthing Tray* to run it, select the **Start guided setup** on the splash screen that appears and press **Next**.
|
||||
3. It will then look for an existing *Syncthing* instance to pull settings from, but will likely fail to do so if you are here. Regardless, select the **Yes, continue configuration** option.
|
||||
4. Select ***Start installed Syncthing application via Syncthing Tray***, this means it will use a built in Syncthing executable rather than relying on an externally provided one. Press **Next** to continue.
|
||||
5. You will now be presented with a confirmation window with your selections, confirm they are what you want and hit **Apply** to continue.
|
||||
6. You will now be prompted with a confirmation window that has a QR code and the devices identifier - you will need one of these to add other devices to the sync system.
|
||||
7. *Syncthing/Syncthing Tray* are now installed. Press Finish to close the pop up.
|
||||
<aside>
|
||||
|
||||
***NOTE***: By default due to flatpak sandboxing limitations, Syncthing Tray will not run automatically on login. You can get around this by following the [*instructions here*](https://github.com/flathub/io.github.martchus.syncthingtray).
|
||||
|
||||
</aside>
|
||||
|
||||
---
|
||||
|
||||
### Configuring this Machine as a Parent
|
||||
|
||||
Use this when you want to set this machine as the initial source of truth (push files out to all the other devices). Afterwards they will all be equal partners, not a parent/child relationship, this just helps with initial setup.
|
||||
|
||||
1. Right-Click the *Syncthing* Tray icon in your taskbar and select **Open Syncthing.**
|
||||
1. If you don’t have a taskbar in your distro, you can also reach it directly by opening a web browser to: *http://127.0.0.1:8384/.*
|
||||
2. You will now have a browser window open up to a web GUI to configure *Syncthing*. You will get a pop up about allowing anonymous usage and setting a password, make your selections to close them.
|
||||
3. We’ll start by adding the folder with our save files that we want to sync by Pressing **+ Add Folder**.
|
||||
4. A pop-up window will appear, fill in the Folder label field with whatever you want to call it, like Switch Saves.
|
||||
5. Enter the Full folder path to where your save files are stored on this machine.
|
||||
|
||||
<aside>
|
||||
|
||||
***TIP***: The easiest way to do this would be to open Eden, right-click a game that has a save, hit ***Open Save Data Location,*** and then go up 1 directory. It should contain folders with the TitleID of your games.
|
||||
|
||||
It should look similar to this: ..*\nand\user\save\0000000000000000\EC573727F509799675F6E5112C581D7E*
|
||||
|
||||
</aside>
|
||||
|
||||
6. Ignore the other tabs for now and hit **Save**.
|
||||
7. The folder is now ready to be shared with other devices.
|
||||
|
||||
---
|
||||
|
||||
### Configuring this Machine as a Child
|
||||
|
||||
Use this when you want to set this machine up as a child (pull files from the other devices). Afterwards they will all be equal partners, not a parent/child relationship, this just helps with initial setup.
|
||||
|
||||
1. Install Syncthing Tray on the client device following the section above. Copy the child’s ID and store it so it is accessible to the Parent.
|
||||
2. ***ON THE PARENT***: Right-Click the *Syncthing* Tray icon in your taskbar and select **Open Syncthing** if it is not open already**.**
|
||||
3. You will now have a browser window open up to a web GUI to configure *Syncthing*. You will get a pop up about allowing anonymous usage and setting a password, make your selections to close them.
|
||||
4. Navigate down to **+ Add Remote Device**, we are going to add our Child device, so I hope you have its ID handy. If not, go back and get it.
|
||||
5. Add the ID and Name the device, the device may appear as a **nearby device**, in which case you can just click it to pre-populate the Device ID.
|
||||
6. Click the **Sharing** Tab, and check the box next to the folder you set up on the Parent (Switch Saves in my case). Hit **Save.**
|
||||
7. We are done with the parent, now **SWITCH OVER TO THE CHILD.**
|
||||
8. ***ON THE CHILD***: Right-Click the *Syncthing* Tray icon in your taskbar and select **Open Syncthing** if it is not open already.
|
||||
9. You should now see a connection request pop-up from the parent. Hit **+ Add Device** to add the device.
|
||||
10. Hit **Save** to finish adding the device.
|
||||
11. That pop-up will close and you will get notification that the device wants to share a folder now. Hit **Add.**
|
||||
12. Enter the path to the save folder in Eden and hit **Save.**
|
||||
|
||||
<aside>
|
||||
|
||||
***TIP***: The easiest way to do this would be to open Eden, right-click a game that has a save, hit ***Open Save Data Location,*** and then go up 1 directory. It should contain folders with the TitleID of your games.
|
||||
|
||||
It should look similar to this: ..*\nand\user\save\0000000000000000\EC573727F509799675F6E5112C581D7E*
|
||||
|
||||
</aside>
|
||||
|
||||
13. *Syncthing* will now pull all the files from the Parent and store them in your local save directory. At this point the files are in sync and alterations to one will affect the other and both can be considered “*Parents*” for other devices you want to add. Repeat these steps for as many devices you want.
|
||||
95
docs/user/SyncthingGuide_Windows.md
Normal file
95
docs/user/SyncthingGuide_Windows.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# Backing Up/Syncing Eden Game Saves
|
||||
|
||||
Use this guide for when you want to configure automated backup/syncing of your Eden save files using [*Syncthing](https://syncthing.net/)* on Windows.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Backing-Up-Syncing-Eden-Game-Saves-2b057c2edaf680f5aa9cd1c4f97121ce) for a version of this guide with images & visual elements.**
|
||||
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- Read the [*Syncthing General Guide*](./SyncthingGuide_General.md).
|
||||
- Eden already installed, configured, and functioning.
|
||||
- Ability to allow a program to communicate through the firewall in Windows.
|
||||
- Ability to extract archive (.zip/.7z/.rar) files.
|
||||
|
||||
---
|
||||
|
||||
## Steps
|
||||
|
||||
<aside>
|
||||
|
||||
***WARNING***: You should manually back up your save files before proceeding with this guide. If you incorrectly perform the steps, you risk losing them!
|
||||
|
||||
</aside>
|
||||
|
||||
### Downloading and Installing *Syncthing*
|
||||
|
||||
1. Download [*Syncthing Tray*](https://martchus.github.io/syncthingtray/#downloads-section).
|
||||
1. While it is available as a command line interface, for most people I would recommend *Syncthing Tray* on Windows. For most people here, you would download the **64-bit (Intel/AMD)** version.
|
||||
2. Open the downloaded archive and extract the **syncthingtray.exe** to wherever you want to store the executable.
|
||||
3. Double-Click the application to run it, select the **Start guided setup** on the splash screen that appears and press **Next**.
|
||||
|
||||
<aside>
|
||||
|
||||
***NOTE***: You may get a Windows Defender Smart Screen pop up, this is a known thing, just accept and run anyway.
|
||||
|
||||
</aside>
|
||||
4. It will then look for an existing Syncthing instance to pull settings from, but will likely fail to do so if you are here. Regardless, select the **Yes, continue configuration** option.
|
||||
5. Select ***Start Syncthing application that is built into Syncthing Tray***, this means it will use a built in Syncthing executable rather than relying on an externally provided one. Press **Next** to continue.
|
||||
6. Check the box to start Syncthing Tray on login - as the name implies, this means the program will run automatically whenever you log onto the computer. Press Next to continue.
|
||||
7. You will now be presented with a confirmation window with your selections, confirm they are what you want and hit **Apply** to continue.
|
||||
8. You will now be prompted with a confirmation window and a message to allow it through the firewall. Allow the access through the firewall to close that pop up. The confirmation screen has a QR code and the devices identifier - you will need one of these to add other devices to the sync system.
|
||||
9. *Syncthing/Syncthing Tray* are now installed.
|
||||
|
||||
---
|
||||
|
||||
### Configuring this Machine as a Parent
|
||||
|
||||
Use this when you want to set this machine as the initial source of truth (push files out to all the other devices). Afterwards they will all be equal partners, not a parent/child relationship, this just helps with initial setup.
|
||||
|
||||
1. Right-Click the *Syncthing* Tray icon in your taskbar and select **Open Syncthing.**
|
||||
2. You will now have a browser window open up to a web GUI to configure *Syncthing*. You will get a pop up about allowing anonymous usage and setting a password, make your selections to close them.
|
||||
3. We’ll start by adding the folder with our save files that we want to sync by Pressing **+ Add Folder**.
|
||||
4. A pop-up window will appear, fill in the Folder label field with whatever you want to call it, like Switch Saves.
|
||||
5. Enter the Full folder path to where your save files are stored on this machine.
|
||||
|
||||
<aside>
|
||||
|
||||
***TIP***: The easiest way to do this would be to open Eden, right-click a game that has a save, hit ***Open Save Data Location,*** and then go up 1 directory. It should contain folders with the TitleID of your games.
|
||||
|
||||
It should look similar to this: ..*\nand\user\save\0000000000000000\EC573727F509799675F6E5112C581D7E*
|
||||
|
||||
</aside>
|
||||
|
||||
6. Ignore the other tabs for now and hit **Save**.
|
||||
7. The folder is now ready to be shared with other devices.
|
||||
|
||||
---
|
||||
|
||||
### Configuring this Machine as a Child
|
||||
|
||||
Use this when you want to set this machine up as a child (pull files from the other devices). Afterwards they will all be equal partners, not a parent/child relationship, this just helps with initial setup.
|
||||
|
||||
1. Install Syncthing Tray on the client device following the section above. Copy the child’s ID and store it so it is accessible to the Parent.
|
||||
2. ***ON THE PARENT***: Right-Click the *Syncthing* Tray icon in your taskbar and select **Open Syncthing** if it is not open already**.**
|
||||
3. You will now have a browser window open up to a web GUI to configure *Syncthing*. You will get a pop up about allowing anonymous usage and setting a password, make your selections to close them.
|
||||
4. Navigate down to **+ Add Remote Device**, we are going to add our Child device, so I hope you have its ID handy. If not, go back and get it.
|
||||
5. Add the ID and Name the device, the device may appear as a **nearby device**, in which case you can just click it to pre-populate the Device ID.
|
||||
6. Click the **Sharing** Tab, and check the box next to the folder you set up on the Parent (Switch Saves in my case). Hit **Save.**
|
||||
7. We are done with the parent, now **SWITCH OVER TO THE CHILD.**
|
||||
8. ***ON THE CHILD***: Right-Click the *Syncthing* Tray icon in your taskbar and select **Open Syncthing** if it is not open already**.**
|
||||
9. You should now see a connection request from the parent. Hit **+ Add Device** to add the device.
|
||||
10. Hit **Save** to finish adding the device.
|
||||
11. That pop-up will close and you will get notification that the device wants to share a folder now. Hit **Add.**
|
||||
12. Enter the path to the save folder in Eden and hit **Save.**
|
||||
|
||||
<aside>
|
||||
|
||||
***TIP***: The easiest way to do this would be to open Eden, right-click a game that has a save, hit ***Open Save Data Location,*** and then go up 1 directory. It should contain folders with the TitleID of your games.
|
||||
|
||||
It should look similar to this: ..*\nand\user\save\0000000000000000\EC573727F509799675F6E5112C581D7E*
|
||||
|
||||
</aside>
|
||||
|
||||
13. *Syncthing* will now pull all the files from the Parent and store them in your local save directory. At this point the files are in sync and alterations to one will affect the other and both can be considered “*Parents*” for other devices you want to add. Repeat these steps for as many devices you want.
|
||||
@@ -2,11 +2,6 @@
|
||||
|
||||
While this is mainly aimed for testers - normal users can benefit from these guidelines to make their life easier when trying to outline and/or report an issue.
|
||||
|
||||
## Getting logs
|
||||
|
||||
In order to get more information, you can find logs in the following location:
|
||||
|
||||
|
||||
## How to Test a PR Against the Based Master When Issues Arise
|
||||
|
||||
When you're testing a pull request (PR) and encounter unexpected behavior, it's important to determine whether the issue was introduced by the PR or if it already exists in the base code. To do this, compare the behavior against the based master branch.
|
||||
|
||||
@@ -6,4 +6,3 @@ While most of the links mentioned in this guide are relatively "safe"; we urge u
|
||||
|
||||
- [Nightly Eden builds](https://github.com/pflyly/eden-nightly)
|
||||
- [NixOS Eden Flake](https://github.com/Grantimatter/eden-flake)
|
||||
- [ES-DE Frontend Support](https://github.com/GlazedBelmont/es-de-android-custom-systems)
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
# User Handbook - Using Amiibo
|
||||
# Using Amiibo with Eden
|
||||
|
||||
Use this guide when you want to load Amiibo into your games for use with the Eden emulator.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Using-Amiibo-with-Eden-2b057c2edaf681b1b28ec6be600c6d3e) for a version of this guide with images & visual elements.**
|
||||
|
||||
## Android
|
||||
|
||||
TBD
|
||||
|
||||
## Desktop
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
@@ -21,7 +17,9 @@ TBD
|
||||
|
||||
</aside>
|
||||
|
||||
### Steps
|
||||
## Steps
|
||||
|
||||
---
|
||||
|
||||
1. Launch Eden and launch the game you want to load Amiibo for.
|
||||
<aside>
|
||||
@@ -42,4 +40,4 @@ TBD
|
||||
</aside>
|
||||
|
||||
5. Upon loading a valid file, you will get a confirmation screen and your bonus content will be unlocked/functionality activated.
|
||||
6. Repeat with any other Amiibo you want to use.
|
||||
6. Repeat with any other Amiibo you want to use.
|
||||
@@ -1,10 +1,10 @@
|
||||
# User Handbook - Using Cheats
|
||||
# Using Cheats with Eden
|
||||
|
||||
Use this guide when you want to add cheats into a game to alter gameplay for use with the Eden emulator.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Using-Cheats-with-Eden-2b057c2edaf6818fab66c276e2304bb4) for a version of this guide with images & visual elements.**
|
||||
|
||||
## Android
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
@@ -21,7 +21,11 @@ Another option would be to launch the game in Eden and close it - then go into t
|
||||
|
||||
</aside>
|
||||
|
||||
### Step 1: Configuring a Cheat
|
||||
---
|
||||
|
||||
## Steps
|
||||
|
||||
### Configuring a Cheat
|
||||
|
||||
1. Create a directory somewhere accessible on your phone with the name of the cheat. The name you choose only affects how it is displayed in Eden.
|
||||
2. Create a directory inside of this folder called **cheats.**
|
||||
@@ -48,7 +52,9 @@ Another option would be to launch the game in Eden and close it - then go into t
|
||||
2. You should now see the cheat appear in the **Add-ons** screen.
|
||||
3. Launch the game and confirm that the cheat is applied.
|
||||
|
||||
### Step 2: Multiple Cheats
|
||||
---
|
||||
|
||||
### Multiple Cheats
|
||||
|
||||
In order to install multiple cheats, you must repeat the steps above with the new cheat, creating a new directory with the name of the cheat and cheats directory. You **cannot** install multiple cheats with a single file.
|
||||
|
||||
@@ -73,7 +79,9 @@ Community Member [Ninjistix](https://github.com/Ninjistix) created a utility (Wi
|
||||
040E0000 0048A818 52800028
|
||||
```
|
||||
|
||||
### Step 3: Enabling/Disabling Cheats
|
||||
---
|
||||
|
||||
### Enabling/Disabling Cheats
|
||||
|
||||
Cheats are enabled by default, but can be disabled so they don’t affect gameplay fairly easily using the game properties.
|
||||
|
||||
@@ -81,76 +89,4 @@ Cheats are enabled by default, but can be disabled so they don’t affect gamepl
|
||||
2. Scroll down on the properties until you see **Add-ons**, select this option.
|
||||
3. *Select/Deselect* the name of the cheat you wish to enable/disable.
|
||||
4. Click **OK** to close the window.
|
||||
5. Launch the game to confirm the cheat is/is not active.
|
||||
|
||||
## Desktop
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- Eden Emulator fully set up and configured
|
||||
- The cheat(s) you want to apply
|
||||
- The **Build ID** of the game.
|
||||
|
||||
<aside>
|
||||
|
||||
***TIP***: The easiest way I have found to find the Build ID is by Right-Clicking the game **IN RYUJINX** and hitting **Manage Cheats**. Your Build ID will be displayed at the top.
|
||||
|
||||
Another option would be to launch the game in Eden and close it - then go into the log and look for a line like this - the first 16 characters if your Build ID. **Make sure that it is the MAIN line**.
|
||||
`[ 27.098382] Loader <Info> core/file_sys/patch_manager.cpp:HasNSOPatch:304: Querying NSO patch existence for build_id=AEE6DCCC06D9C05B42061E2019123A61, name=main`
|
||||
|
||||
</aside>
|
||||
|
||||
### Step 1: Configuring a Cheat
|
||||
|
||||
1. Copy the Hex Code of the cheat into a text file, optionally with the cheat name at the beginning like the example. Here this code will set the timer to 999 in *New Super Mario Bros. U Deluxe.*
|
||||
|
||||
```bash
|
||||
[Time = 999]
|
||||
58000000 00C88A70
|
||||
78001000 00000090
|
||||
64000000 00000000 003E6F00
|
||||
```
|
||||
|
||||
1. Save the file as a **txt** file with the Build ID of the game. For my example, my Build ID is **AEE6DCCC06D9C05B** so my file would be `AEE6DCCC06D9C05B.txt`.
|
||||
2. Launch Eden and wait for the program to load.
|
||||
3. *Right-Click* the game in Eden and select **Open Mod Data Location**. A file explorer window should appear.
|
||||
4. Create a folder inside of the file explorer window with the name of the cheat. This name does not matter and only affects how it appears in the game properties inside of Eden.
|
||||
5. Navigate inside of this folder and create another folder called **cheats.**
|
||||
6. Move the txt file you created earlier into this **cheats** folder. (e.g. `<mod_location>/Time 999/cheats/AEE6DCCC06D9C05B.txt` )
|
||||
7. Go back to Eden and *right-click* the game. Select *Configure Game* and you should now see the cheat you created appear in the **Add-Ons** section with the name of the folder from step 6.
|
||||
8. Launch the game to verify that the cheat is enabled.
|
||||
|
||||
### Step 2: Multiple Cheats
|
||||
|
||||
In order to install multiple cheats, you must repeat the steps above with the new cheat, creating a new directory with the name of the cheat and cheats directory. You **cannot** install multiple cheats with a single file.
|
||||
|
||||
Community Member [Ninjistix](https://github.com/Ninjistix) created a utility (Windows or anything that can run Python) that can take a file with multiple cheats and create the files/structure for you with a provided Build ID. To download and run it, see the [GitHub Project](https://github.com/Ninjistix/nxCheat_Splitter) page.
|
||||
|
||||
**Example cheat TXT file with multiple cheats. It must be in this format to work:**
|
||||
```
|
||||
[Super Mario Bros. Wonder - Various] <- Optional
|
||||
|
||||
[♯ 1. Always Star Power]
|
||||
040E0000 00880580 52800035
|
||||
|
||||
[♯ 2. Star Power + Bubble Mode (Invincible)]
|
||||
040E0000 00880580 52800075
|
||||
|
||||
[♯ 3. Can Fast Travel to Any Course and World]
|
||||
040E0000 00935E10 52800036
|
||||
040E0000 0048A528 52800028
|
||||
040E0000 005D9F58 52800028
|
||||
|
||||
[♯ 4. Got All Top of Flag Poles]
|
||||
040E0000 0048A818 52800028
|
||||
```
|
||||
|
||||
### Step 3: Enabling/Disabling Cheats
|
||||
|
||||
Cheats are enabled by default, but can be disabled so they don’t affect gameplay fairly easily using the game properties.
|
||||
|
||||
1. *Right-Click* the game and select *Configure Game*.
|
||||
2. In the **Add-Ons** section, locate the cheat you wish to enable.
|
||||
3. *Select/Deselect* the name of the cheat you wish to enable/disable.
|
||||
4. Click **OK** to close the window.
|
||||
5. Launch the game to confirm the cheat is/is not active.
|
||||
5. Launch the game to confirm the cheat is/is not active.
|
||||
83
docs/user/UsingCheats_Windows.md
Normal file
83
docs/user/UsingCheats_Windows.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Using Cheats with Eden
|
||||
|
||||
Use this guide when you want to add cheats into a game to alter gameplay for use with the Eden emulator.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Using-Cheats-with-Eden-2b057c2edaf6818fab66c276e2304bb4) for a version of this guide with images & visual elements.**
|
||||
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- Eden Emulator fully set up and configured
|
||||
- The cheat(s) you want to apply
|
||||
- The **Build ID** of the game.
|
||||
|
||||
<aside>
|
||||
|
||||
***TIP***: The easiest way I have found to find the Build ID is by Right-Clicking the game **IN RYUJINX** and hitting **Manage Cheats**. Your Build ID will be displayed at the top.
|
||||
|
||||
Another option would be to launch the game in Eden and close it - then go into the log and look for a line like this - the first 16 characters if your Build ID. **Make sure that it is the MAIN line**.
|
||||
`[ 27.098382] Loader <Info> core/file_sys/patch_manager.cpp:HasNSOPatch:304: Querying NSO patch existence for build_id=AEE6DCCC06D9C05B42061E2019123A61, name=main`
|
||||
|
||||
</aside>
|
||||
|
||||
## Steps
|
||||
|
||||
---
|
||||
|
||||
### Configuring a Cheat
|
||||
|
||||
1. Copy the Hex Code of the cheat into a text file, optionally with the cheat name at the beginning like the example. Here this code will set the timer to 999 in *New Super Mario Bros. U Deluxe.*
|
||||
|
||||
```bash
|
||||
[Time = 999]
|
||||
58000000 00C88A70
|
||||
78001000 00000090
|
||||
64000000 00000000 003E6F00
|
||||
```
|
||||
|
||||
1. Save the file as a **txt** file with the Build ID of the game. For my example, my Build ID is **AEE6DCCC06D9C05B** so my file would be `AEE6DCCC06D9C05B.txt`.
|
||||
2. Launch Eden and wait for the program to load.
|
||||
3. *Right-Click* the game in Eden and select **Open Mod Data Location**. A file explorer window should appear.
|
||||
4. Create a folder inside of the file explorer window with the name of the cheat. This name does not matter and only affects how it appears in the game properties inside of Eden.
|
||||
5. Navigate inside of this folder and create another folder called **cheats.**
|
||||
6. Move the txt file you created earlier into this **cheats** folder. (e.g. `<mod_location>/Time 999/cheats/AEE6DCCC06D9C05B.txt` )
|
||||
7. Go back to Eden and *right-click* the game. Select *Configure Game* and you should now see the cheat you created appear in the **Add-Ons** section with the name of the folder from step 6.
|
||||
8. Launch the game to verify that the cheat is enabled.
|
||||
|
||||
### Multiple Cheats
|
||||
|
||||
In order to install multiple cheats, you must repeat the steps above with the new cheat, creating a new directory with the name of the cheat and cheats directory. You **cannot** install multiple cheats with a single file.
|
||||
|
||||
Community Member [Ninjistix](https://github.com/Ninjistix) created a utility (Windows or anything that can run Python) that can take a file with multiple cheats and create the files/structure for you with a provided Build ID. To download and run it, see the [GitHub Project](https://github.com/Ninjistix/nxCheat_Splitter) page.
|
||||
|
||||
**Example cheat TXT file with multiple cheats. It must be in this format to work:**
|
||||
```
|
||||
[Super Mario Bros. Wonder - Various] <- Optional
|
||||
|
||||
[♯ 1. Always Star Power]
|
||||
040E0000 00880580 52800035
|
||||
|
||||
[♯ 2. Star Power + Bubble Mode (Invincible)]
|
||||
040E0000 00880580 52800075
|
||||
|
||||
[♯ 3. Can Fast Travel to Any Course and World]
|
||||
040E0000 00935E10 52800036
|
||||
040E0000 0048A528 52800028
|
||||
040E0000 005D9F58 52800028
|
||||
|
||||
[♯ 4. Got All Top of Flag Poles]
|
||||
040E0000 0048A818 52800028
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Enabling/Disabling Cheats
|
||||
|
||||
Cheats are enabled by default, but can be disabled so they don’t affect gameplay fairly easily using the game properties.
|
||||
|
||||
1. *Right-Click* the game and select *Configure Game*.
|
||||
2. In the **Add-Ons** section, locate the cheat you wish to enable.
|
||||
3. *Select/Deselect* the name of the cheat you wish to enable/disable.
|
||||
4. Click **OK** to close the window.
|
||||
5. Launch the game to confirm the cheat is/is not active.
|
||||
6
externals/ffmpeg/cpmfile.json
vendored
6
externals/ffmpeg/cpmfile.json
vendored
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"ffmpeg": {
|
||||
"repo": "FFmpeg/FFmpeg",
|
||||
"sha": "5e56937b74",
|
||||
"hash": "9ab0457dcd6ce6359b5053c1662f57910d332f68ca0cca9d4134d858464840917027374de3d97e0863c3a7daaea2fe4f4cd17d1c6d8e7f740f4ad91e71c2932b",
|
||||
"sha": "ddf443f1e9",
|
||||
"hash": "ded1c313843f23805102565bd3ca92602fb9c2951e059ca5e1a486ab3ef7d589acccf3cde05c5ff0cfc5199c3a261dccb4d2a93254e585824850696fb41a292e",
|
||||
"bundled": true
|
||||
},
|
||||
"ffmpeg-ci": {
|
||||
@@ -10,7 +10,7 @@
|
||||
"package": "FFmpeg",
|
||||
"name": "ffmpeg",
|
||||
"repo": "crueter-ci/FFmpeg",
|
||||
"version": "8.0.1-5e56937b74",
|
||||
"version": "8.0-ddf443f1e9",
|
||||
"min_version": "4.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
let
|
||||
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-25.11";
|
||||
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-24.05";
|
||||
pkgs = import nixpkgs { config = {}; overlays = []; };
|
||||
in
|
||||
pkgs.mkShellNoCC {
|
||||
|
||||
@@ -18,7 +18,6 @@ import android.content.IntentFilter
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Icon
|
||||
import android.hardware.input.InputManager
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
@@ -64,12 +63,11 @@ import kotlin.math.roundToInt
|
||||
import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||
import androidx.core.os.BundleCompat
|
||||
|
||||
class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager.InputDeviceListener {
|
||||
class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
private lateinit var binding: ActivityEmulationBinding
|
||||
|
||||
var isActivityRecreated = false
|
||||
private lateinit var nfcReader: NfcReader
|
||||
private lateinit var inputManager: InputManager
|
||||
|
||||
private var touchDownTime: Long = 0
|
||||
private val maxTapDuration = 500L
|
||||
@@ -142,9 +140,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
nfcReader = NfcReader(this)
|
||||
nfcReader.initialize()
|
||||
|
||||
inputManager = getSystemService(INPUT_SERVICE) as InputManager
|
||||
inputManager.registerInputDeviceListener(this, null)
|
||||
|
||||
foregroundService = Intent(this, ForegroundService::class.java)
|
||||
startForegroundService(foregroundService)
|
||||
|
||||
@@ -211,9 +206,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
inputManager.unregisterInputDeviceListener(this)
|
||||
stopForegroundService(this)
|
||||
NativeLibrary.playTimeManagerStop()
|
||||
|
||||
}
|
||||
|
||||
override fun onUserLeaveHint() {
|
||||
@@ -249,10 +244,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
val isPhysicalKeyboard = event.source and InputDevice.SOURCE_KEYBOARD == InputDevice.SOURCE_KEYBOARD &&
|
||||
event.device?.isVirtual == false
|
||||
|
||||
val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK ||
|
||||
event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
|
||||
|
||||
if (!isControllerInput &&
|
||||
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
|
||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
|
||||
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE &&
|
||||
!isPhysicalKeyboard
|
||||
) {
|
||||
@@ -263,18 +256,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
if (isControllerInput && event.action == KeyEvent.ACTION_DOWN) {
|
||||
notifyControllerInput()
|
||||
}
|
||||
|
||||
return InputHandler.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
|
||||
val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK ||
|
||||
event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
|
||||
|
||||
if (!isControllerInput &&
|
||||
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
|
||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
|
||||
event.source and InputDevice.SOURCE_KEYBOARD != InputDevice.SOURCE_KEYBOARD &&
|
||||
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE
|
||||
) {
|
||||
@@ -290,54 +277,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
return true
|
||||
}
|
||||
|
||||
if (isControllerInput) {
|
||||
notifyControllerInput()
|
||||
}
|
||||
|
||||
return InputHandler.dispatchGenericMotionEvent(event)
|
||||
}
|
||||
|
||||
private fun notifyControllerInput() {
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
|
||||
val emulationFragment =
|
||||
navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment
|
||||
emulationFragment?.onControllerInputDetected()
|
||||
}
|
||||
|
||||
private fun isGameController(deviceId: Int): Boolean {
|
||||
val device = InputDevice.getDevice(deviceId) ?: return false
|
||||
val sources = device.sources
|
||||
return sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD ||
|
||||
sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
|
||||
}
|
||||
|
||||
override fun onInputDeviceAdded(deviceId: Int) {
|
||||
if (isGameController(deviceId)) {
|
||||
InputHandler.updateControllerData()
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
|
||||
val emulationFragment =
|
||||
navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment
|
||||
emulationFragment?.onControllerConnected()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInputDeviceRemoved(deviceId: Int) {
|
||||
InputHandler.updateControllerData()
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
|
||||
val emulationFragment =
|
||||
navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment
|
||||
emulationFragment?.onControllerDisconnected()
|
||||
}
|
||||
|
||||
override fun onInputDeviceChanged(deviceId: Int) {
|
||||
if (isGameController(deviceId)) {
|
||||
InputHandler.updateControllerData()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSensorChanged(event: SensorEvent) {
|
||||
val rotation = this.display?.rotation
|
||||
if (rotation == Surface.ROTATION_90) {
|
||||
@@ -577,10 +519,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
touchDownTime = System.currentTimeMillis()
|
||||
// show overlay immediately on touch and cancel timer when only auto-hide is enabled
|
||||
if (!emulationViewModel.drawerOpen.value &&
|
||||
BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean() &&
|
||||
!BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) {
|
||||
// show overlay immediately on touch and cancel timer
|
||||
if (!emulationViewModel.drawerOpen.value) {
|
||||
fragment.handler.removeCallbacksAndMessages(null)
|
||||
fragment.showOverlay()
|
||||
}
|
||||
|
||||
@@ -6,15 +6,10 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding
|
||||
import org.yuzu.yuzu_emu.model.Patch
|
||||
import org.yuzu.yuzu_emu.model.PatchType
|
||||
import org.yuzu.yuzu_emu.model.AddonViewModel
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
@@ -32,95 +27,18 @@ class AddonAdapter(val addonViewModel: AddonViewModel) :
|
||||
binding.addonSwitch.performClick()
|
||||
}
|
||||
binding.title.text = model.name
|
||||
|
||||
binding.addonSwitch.setOnCheckedChangeListener(null)
|
||||
binding.version.text = model.version
|
||||
binding.addonSwitch.isChecked = model.enabled
|
||||
|
||||
binding.addonSwitch.setOnCheckedChangeListener { _, checked ->
|
||||
model.enabled = checked
|
||||
}
|
||||
|
||||
val isCheat = model.isCheat()
|
||||
val indentPx = if (isCheat) {
|
||||
binding.root.context.resources.getDimensionPixelSize(R.dimen.spacing_large)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
(binding.addonCard.layoutParams as? ViewGroup.MarginLayoutParams)?.let {
|
||||
it.marginStart = indentPx
|
||||
binding.addonCard.layoutParams = it
|
||||
}
|
||||
|
||||
if (isCheat) {
|
||||
binding.version.visibility = View.GONE
|
||||
binding.deleteCard.visibility = View.GONE
|
||||
binding.buttonDelete.visibility = View.GONE
|
||||
|
||||
binding.addonSwitch.scaleX = 0.7f
|
||||
binding.addonSwitch.scaleY = 0.7f
|
||||
|
||||
val compactPaddingVertical = binding.root.context.resources.getDimensionPixelSize(R.dimen.spacing_small)
|
||||
binding.root.setPadding(
|
||||
binding.root.paddingLeft,
|
||||
compactPaddingVertical / 2,
|
||||
binding.root.paddingRight,
|
||||
compactPaddingVertical / 2
|
||||
)
|
||||
|
||||
val innerLayout = binding.addonCard.getChildAt(0) as? ViewGroup
|
||||
innerLayout?.let {
|
||||
val leftPadding = binding.root.context.resources.getDimensionPixelSize(R.dimen.spacing_large)
|
||||
val smallPadding = binding.root.context.resources.getDimensionPixelSize(R.dimen.spacing_med)
|
||||
it.setPadding(leftPadding, smallPadding, smallPadding, smallPadding)
|
||||
}
|
||||
|
||||
binding.title.textSize = 14f
|
||||
binding.title.gravity = Gravity.CENTER_VERTICAL
|
||||
|
||||
(binding.title.layoutParams as? ConstraintLayout.LayoutParams)?.let { params ->
|
||||
params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID
|
||||
params.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID
|
||||
params.topMargin = 0
|
||||
binding.title.layoutParams = params
|
||||
}
|
||||
} else {
|
||||
binding.version.visibility = View.VISIBLE
|
||||
binding.version.text = model.version
|
||||
binding.deleteCard.visibility = View.VISIBLE
|
||||
binding.buttonDelete.visibility = View.VISIBLE
|
||||
|
||||
binding.addonSwitch.scaleX = 1.0f
|
||||
binding.addonSwitch.scaleY = 1.0f
|
||||
|
||||
val normalPadding = binding.root.context.resources.getDimensionPixelSize(R.dimen.spacing_med)
|
||||
binding.root.setPadding(
|
||||
binding.root.paddingLeft,
|
||||
normalPadding,
|
||||
binding.root.paddingRight,
|
||||
normalPadding
|
||||
)
|
||||
|
||||
val innerLayout = binding.addonCard.getChildAt(0) as? ViewGroup
|
||||
innerLayout?.let {
|
||||
val normalInnerPadding = binding.root.context.resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
|
||||
it.setPadding(normalInnerPadding, normalInnerPadding, normalInnerPadding, normalInnerPadding)
|
||||
}
|
||||
|
||||
binding.title.textSize = 16f
|
||||
binding.title.gravity = Gravity.START
|
||||
|
||||
(binding.title.layoutParams as? ConstraintLayout.LayoutParams)?.let { params ->
|
||||
params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID
|
||||
params.bottomToBottom = ConstraintLayout.LayoutParams.UNSET
|
||||
params.topMargin = 0
|
||||
binding.title.layoutParams = params
|
||||
}
|
||||
|
||||
val deleteAction = {
|
||||
addonViewModel.setAddonToDelete(model)
|
||||
}
|
||||
binding.deleteCard.setOnClickListener { deleteAction() }
|
||||
binding.buttonDelete.setOnClickListener { deleteAction() }
|
||||
val deleteAction = {
|
||||
addonViewModel.setAddonToDelete(model)
|
||||
}
|
||||
binding.deleteCard.setOnClickListener { deleteAction() }
|
||||
binding.buttonDelete.setOnClickListener { deleteAction() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
|
||||
SOC_OVERLAY_BACKGROUND("soc_overlay_background"),
|
||||
ENABLE_INPUT_OVERLAY_AUTO_HIDE("enable_input_overlay_auto_hide"),
|
||||
HIDE_OVERLAY_ON_CONTROLLER_INPUT("hide_overlay_on_controller_input"),
|
||||
|
||||
PERF_OVERLAY_BACKGROUND("perf_overlay_background"),
|
||||
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
|
||||
|
||||
@@ -387,13 +387,6 @@ abstract class SettingsItem(
|
||||
valueHint = R.string.seconds
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT,
|
||||
titleId = R.string.hide_overlay_on_controller_input,
|
||||
descriptionId = R.string.hide_overlay_on_controller_input_description
|
||||
)
|
||||
)
|
||||
|
||||
put(
|
||||
SwitchSetting(
|
||||
|
||||
@@ -274,7 +274,6 @@ class SettingsFragmentPresenter(
|
||||
sl.apply {
|
||||
add(BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.key)
|
||||
add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key)
|
||||
add(BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ class AboutFragment : Fragment() {
|
||||
}
|
||||
|
||||
binding.buttonDiscord.setOnClickListener { openLink(getString(R.string.discord_link)) }
|
||||
binding.buttonStoat.setOnClickListener { openLink(getString(R.string.stoat_link)) }
|
||||
binding.buttonRevolt.setOnClickListener { openLink(getString(R.string.revolt_link)) }
|
||||
binding.buttonX.setOnClickListener { openLink(getString(R.string.x_link)) }
|
||||
binding.buttonWebsite.setOnClickListener { openLink(getString(R.string.website_link)) }
|
||||
binding.buttonGithub.setOnClickListener { openLink(getString(R.string.github_link)) }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
@@ -73,7 +73,7 @@ class AddonsFragment : Fragment() {
|
||||
}
|
||||
|
||||
addonViewModel.addonList.collect(viewLifecycleOwner) {
|
||||
(binding.listAddons.adapter as AddonAdapter).submitList(it.toList())
|
||||
(binding.listAddons.adapter as AddonAdapter).submitList(it)
|
||||
}
|
||||
addonViewModel.showModInstallPicker.collect(
|
||||
viewLifecycleOwner,
|
||||
@@ -127,9 +127,7 @@ class AddonsFragment : Fragment() {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (!requireActivity().isChangingConfigurations) {
|
||||
addonViewModel.onCloseAddons()
|
||||
}
|
||||
addonViewModel.onCloseAddons()
|
||||
}
|
||||
|
||||
val installAddon =
|
||||
|
||||
@@ -93,6 +93,7 @@ import org.yuzu.yuzu_emu.utils.collect
|
||||
import org.yuzu.yuzu_emu.utils.CustomSettingsHandler
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import kotlin.coroutines.coroutineContext
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
@@ -105,7 +106,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
private var isOverlayVisible = true
|
||||
private var controllerInputReceived = false
|
||||
|
||||
private var _binding: FragmentEmulationBinding? = null
|
||||
|
||||
@@ -656,12 +656,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(newState)
|
||||
updateQuickOverlayMenuEntry(newState)
|
||||
binding.surfaceInputOverlay.refreshControls()
|
||||
// Sync view visibility with the setting
|
||||
if (newState) {
|
||||
showOverlay()
|
||||
} else {
|
||||
hideOverlay()
|
||||
}
|
||||
NativeConfig.saveGlobalConfig()
|
||||
true
|
||||
}
|
||||
@@ -1907,7 +1901,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
companion object {
|
||||
fun fromValue(value: Int): AmiiboState =
|
||||
entries.firstOrNull { it.value == value } ?: Disabled
|
||||
values().firstOrNull { it.value == value } ?: Disabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1920,7 +1914,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
companion object {
|
||||
fun fromValue(value: Int): AmiiboLoadResult =
|
||||
entries.firstOrNull { it.value == value } ?: Unknown
|
||||
values().firstOrNull { it.value == value } ?: Unknown
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1977,8 +1971,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
fun showOverlay() {
|
||||
if (!isOverlayVisible) {
|
||||
isOverlayVisible = true
|
||||
// Reset controller input flag so controller can hide overlay again
|
||||
controllerInputReceived = false
|
||||
ViewUtils.showView(binding.surfaceInputOverlay, 500)
|
||||
}
|
||||
}
|
||||
@@ -1986,26 +1978,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
private fun hideOverlay() {
|
||||
if (isOverlayVisible) {
|
||||
isOverlayVisible = false
|
||||
ViewUtils.hideView(binding.surfaceInputOverlay)
|
||||
ViewUtils.hideView(binding.surfaceInputOverlay, 500)
|
||||
}
|
||||
}
|
||||
|
||||
fun onControllerInputDetected() {
|
||||
if (!BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) return
|
||||
if (!BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) return
|
||||
if (controllerInputReceived) return
|
||||
controllerInputReceived = true
|
||||
hideOverlay()
|
||||
}
|
||||
|
||||
fun onControllerConnected() {
|
||||
controllerInputReceived = false
|
||||
}
|
||||
|
||||
fun onControllerDisconnected() {
|
||||
if (!BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) return
|
||||
if (!BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) return
|
||||
controllerInputReceived = false
|
||||
showOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -31,14 +28,10 @@ class AddonViewModel : ViewModel() {
|
||||
val addonToDelete = _addonToDelete.asStateFlow()
|
||||
|
||||
var game: Game? = null
|
||||
private set
|
||||
|
||||
private val isRefreshing = AtomicBoolean(false)
|
||||
|
||||
fun onOpenAddons(game: Game) {
|
||||
if (this.game?.programId == game.programId && _patchList.value.isNotEmpty()) {
|
||||
return
|
||||
}
|
||||
this.game = game
|
||||
refreshAddons()
|
||||
}
|
||||
@@ -54,7 +47,8 @@ class AddonViewModel : ViewModel() {
|
||||
NativeLibrary.getPatchesForFile(game!!.path, game!!.programId)
|
||||
?: emptyArray()
|
||||
).toMutableList()
|
||||
_patchList.value = sortPatchesWithCheatsGrouped(patchList)
|
||||
patchList.sortBy { it.name }
|
||||
_patchList.value = patchList
|
||||
isRefreshing.set(false)
|
||||
}
|
||||
}
|
||||
@@ -69,9 +63,7 @@ class AddonViewModel : ViewModel() {
|
||||
PatchType.Update -> NativeLibrary.removeUpdate(patch.programId)
|
||||
PatchType.DLC -> NativeLibrary.removeDLC(patch.programId)
|
||||
PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name)
|
||||
PatchType.Cheat -> {}
|
||||
}
|
||||
_patchList.value.clear()
|
||||
refreshAddons()
|
||||
}
|
||||
|
||||
@@ -86,7 +78,7 @@ class AddonViewModel : ViewModel() {
|
||||
if (it.enabled) {
|
||||
null
|
||||
} else {
|
||||
it.getStorageKey()
|
||||
it.name
|
||||
}
|
||||
}.toTypedArray()
|
||||
)
|
||||
@@ -102,28 +94,4 @@ class AddonViewModel : ViewModel() {
|
||||
fun showModNoticeDialog(show: Boolean) {
|
||||
_showModNoticeDialog.value = show
|
||||
}
|
||||
|
||||
private fun sortPatchesWithCheatsGrouped(patches: MutableList<Patch>): MutableList<Patch> {
|
||||
val individualCheats = patches.filter { it.isCheat() }
|
||||
val nonCheats = patches.filter { !it.isCheat() }.sortedBy { it.name }
|
||||
|
||||
val cheatsByParent = individualCheats.groupBy { it.parentName }
|
||||
|
||||
val result = mutableListOf<Patch>()
|
||||
for (patch in nonCheats) {
|
||||
result.add(patch)
|
||||
cheatsByParent[patch.name]?.sortedBy { it.name }?.let { childCheats ->
|
||||
result.addAll(childCheats)
|
||||
}
|
||||
}
|
||||
|
||||
val knownParents = nonCheats.map { it.name }.toSet()
|
||||
for ((parentName, orphanCheats) in cheatsByParent) {
|
||||
if (parentName !in knownParents) {
|
||||
result.addAll(orphanCheats.sortedBy { it.name })
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -15,24 +12,5 @@ data class Patch(
|
||||
val version: String,
|
||||
val type: Int,
|
||||
val programId: String,
|
||||
val titleId: String,
|
||||
val parentName: String = "" // For cheats: name of the mod folder containing them
|
||||
) {
|
||||
/**
|
||||
* Returns the storage key used for saving enabled/disabled state.
|
||||
* For cheats with a parent, returns "ParentName::CheatName".
|
||||
*/
|
||||
fun getStorageKey(): String {
|
||||
return if (parentName.isNotEmpty()) {
|
||||
"$parentName::$name"
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this patch is an individual cheat entry (not a cheat mod).
|
||||
* Individual cheats have type=Cheat and a parent mod name.
|
||||
*/
|
||||
fun isCheat(): Boolean = type == PatchType.Cheat.int && parentName.isNotEmpty()
|
||||
}
|
||||
val titleId: String
|
||||
)
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -9,8 +6,7 @@ package org.yuzu.yuzu_emu.model
|
||||
enum class PatchType(val int: Int) {
|
||||
Update(0),
|
||||
DLC(1),
|
||||
Mod(2),
|
||||
Cheat(3);
|
||||
Mod(2);
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): PatchType = entries.firstOrNull { it.int == int } ?: Update
|
||||
|
||||
@@ -92,11 +92,6 @@ namespace AndroidSettings {
|
||||
Settings::Setting<u32> input_overlay_auto_hide{linkage, 5, "input_overlay_auto_hide",
|
||||
Settings::Category::Overlay,
|
||||
Settings::Specialization::Default, true, true, &enable_input_overlay_auto_hide};
|
||||
Settings::Setting<bool> hide_overlay_on_controller_input{linkage, false,
|
||||
"hide_overlay_on_controller_input",
|
||||
Settings::Category::Overlay,
|
||||
Settings::Specialization::Default, true,
|
||||
true};
|
||||
Settings::Setting<bool> perf_overlay_background{linkage, false, "perf_overlay_background",
|
||||
Settings::Category::Overlay,
|
||||
Settings::Specialization::Default, true,
|
||||
|
||||
@@ -1298,10 +1298,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env
|
||||
FileSys::VirtualFile update_raw;
|
||||
loader->ReadUpdateRaw(update_raw);
|
||||
|
||||
// Get build ID for individual cheat enumeration
|
||||
const auto build_id = pm.GetBuildID(update_raw);
|
||||
|
||||
auto patches = pm.GetPatches(update_raw, build_id);
|
||||
auto patches = pm.GetPatches(update_raw);
|
||||
jobjectArray jpatchArray =
|
||||
env->NewObjectArray(patches.size(), Common::Android::GetPatchClass(), nullptr);
|
||||
int i = 0;
|
||||
@@ -1311,8 +1308,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env
|
||||
Common::Android::ToJString(env, patch.name),
|
||||
Common::Android::ToJString(env, patch.version), static_cast<jint>(patch.type),
|
||||
Common::Android::ToJString(env, std::to_string(patch.program_id)),
|
||||
Common::Android::ToJString(env, std::to_string(patch.title_id)),
|
||||
Common::Android::ToJString(env, patch.parent_name));
|
||||
Common::Android::ToJString(env, std::to_string(patch.title_id)));
|
||||
env->SetObjectArrayElement(jpatchArray, i, jpatch);
|
||||
++i;
|
||||
}
|
||||
|
||||
@@ -220,12 +220,12 @@
|
||||
app:iconPadding="0dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_stoat"
|
||||
android:id="@+id/button_revolt"
|
||||
style="@style/EdenButton.Secondary"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
app:icon="@drawable/ic_stoat"
|
||||
app:icon="@drawable/ic_revolt"
|
||||
app:iconGravity="textStart"
|
||||
app:iconSize="24dp"
|
||||
app:iconPadding="0dp" />
|
||||
@@ -270,4 +270,4 @@
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -215,12 +215,12 @@
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/EdenButton.Secondary"
|
||||
android:id="@+id/button_stoat"
|
||||
android:id="@+id/button_revolt"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:icon="@drawable/ic_stoat"
|
||||
app:icon="@drawable/ic_revolt"
|
||||
app:iconSize="24dp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp" />
|
||||
@@ -235,7 +235,7 @@
|
||||
app:icon="@drawable/ic_x"
|
||||
app:iconSize="24dp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp" />
|
||||
app:iconPadding="0dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/EdenButton.Secondary"
|
||||
@@ -267,4 +267,4 @@
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -26,8 +26,6 @@
|
||||
<string name="overlay_auto_hide">Overlay Auto Hide</string>
|
||||
<string name="overlay_auto_hide_description">Automatically hide the touch controls overlay after the specified time of inactivity.</string>
|
||||
<string name="enable_input_overlay_auto_hide">Enable Overlay Auto Hide</string>
|
||||
<string name="hide_overlay_on_controller_input">Hide Overlay on Controller Input</string>
|
||||
<string name="hide_overlay_on_controller_input_description">Automatically hide the touch controls overlay when a physical controller is used. Overlay reappears when controller is disconnected.</string>
|
||||
|
||||
<string name="input_overlay_options">Input Overlay</string>
|
||||
<string name="input_overlay_options_description">Configure on-screen controls</string>
|
||||
@@ -447,7 +445,7 @@
|
||||
<string name="user_data_export_cancelled">Export cancelled</string>
|
||||
<string name="user_data_import_failed_description">Make sure the user data folders are at the root of the zip folder and contain a config file at config/config.ini and try again.</string>
|
||||
<string name="discord_link" translatable="false">https://discord.gg/HstXbPch7X</string>
|
||||
<string name="stoat_link" translatable="false">https://stt.gg/qKgFEAbH</string>
|
||||
<string name="revolt_link" translatable="false">https://rvlt.gg/qKgFEAbH</string>
|
||||
<string name="x_link" translatable="false">https://nitter.poast.org/edenemuofficial</string>
|
||||
<string name="website_link" translatable="false">https://eden-emu.dev</string>
|
||||
<string name="github_link" translatable="false">https://git.eden-emu.dev/eden-emu</string>
|
||||
|
||||
@@ -21,15 +21,6 @@
|
||||
|
||||
namespace AudioCore::Renderer {
|
||||
|
||||
namespace {
|
||||
constexpr f32 BiquadParameterFixedScaleQ14 = 16384.0f; // 1 << 14
|
||||
|
||||
[[nodiscard]] inline s16 ToQ14Clamped(f32 v) {
|
||||
const f32 scaled = std::clamp(v * BiquadParameterFixedScaleQ14, -32768.0f, 32767.0f);
|
||||
return static_cast<s16>(scaled);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
template <typename T, CommandId Id>
|
||||
T& CommandBuffer::GenerateStart(const s32 node_id) {
|
||||
if (size + sizeof(T) >= command_list.size_bytes()) {
|
||||
@@ -268,42 +259,18 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBas
|
||||
const bool use_float_processing) {
|
||||
auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)};
|
||||
|
||||
const auto& parameter{
|
||||
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
|
||||
const auto state{reinterpret_cast<VoiceState::BiquadFilterState*>(
|
||||
effect_info.GetStateBuffer() + channel * sizeof(VoiceState::BiquadFilterState))};
|
||||
|
||||
if (behavior->IsEffectInfoVersion2Supported()) {
|
||||
const auto& p{
|
||||
*reinterpret_cast<BiquadFilterInfo::ParameterVersion2*>(effect_info.GetParameter())};
|
||||
cmd.input = buffer_offset + parameter.inputs[channel];
|
||||
cmd.output = buffer_offset + parameter.outputs[channel];
|
||||
|
||||
if (!IsChannelCountValid(p.channel_count) || channel < 0 || channel >= p.channel_count) {
|
||||
return;
|
||||
}
|
||||
cmd.biquad.b = parameter.b;
|
||||
cmd.biquad.a = parameter.a;
|
||||
|
||||
cmd.input = buffer_offset + p.inputs[channel];
|
||||
cmd.output = buffer_offset + p.outputs[channel];
|
||||
|
||||
// Convert float coefficients to Q2.14 fixed-point as expected by the legacy DSP path.
|
||||
cmd.biquad.b[0] = ToQ14Clamped(p.b[0]);
|
||||
cmd.biquad.b[1] = ToQ14Clamped(p.b[1]);
|
||||
cmd.biquad.b[2] = ToQ14Clamped(p.b[2]);
|
||||
cmd.biquad.a[0] = ToQ14Clamped(p.a[0]);
|
||||
cmd.biquad.a[1] = ToQ14Clamped(p.a[1]);
|
||||
} else {
|
||||
const auto& p{
|
||||
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
|
||||
|
||||
if (!IsChannelCountValid(p.channel_count) || channel < 0 || channel >= p.channel_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
cmd.input = buffer_offset + p.inputs[channel];
|
||||
cmd.output = buffer_offset + p.outputs[channel];
|
||||
|
||||
cmd.biquad.b = p.b;
|
||||
cmd.biquad.a = p.a;
|
||||
}
|
||||
|
||||
// Effects always use the fixed-point coefficient path on the DSP.
|
||||
// Effects use legacy fixed-point format
|
||||
cmd.use_float_coefficients = false;
|
||||
|
||||
cmd.state = memory_pool->Translate(CpuAddr(state),
|
||||
@@ -628,15 +595,6 @@ void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBa
|
||||
const s16 buffer_offset, const s8 channel) {
|
||||
auto& cmd{GenerateStart<CopyMixBufferCommand, CommandId::CopyMixBuffer>(node_id)};
|
||||
|
||||
if (behavior->IsEffectInfoVersion2Supported()) {
|
||||
const auto& parameter_v2{
|
||||
*reinterpret_cast<BiquadFilterInfo::ParameterVersion2*>(effect_info.GetParameter())};
|
||||
cmd.input_index = buffer_offset + parameter_v2.inputs[channel];
|
||||
cmd.output_index = buffer_offset + parameter_v2.outputs[channel];
|
||||
GenerateEnd<CopyMixBufferCommand>(cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& parameter{
|
||||
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
|
||||
cmd.input_index = buffer_offset + parameter.inputs[channel];
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -364,37 +361,12 @@ void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBas
|
||||
void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset,
|
||||
EffectInfoBase& effect_info,
|
||||
const s32 node_id) {
|
||||
EffectInfoBase::ParameterState param_state{};
|
||||
s8 channel_count = 0;
|
||||
|
||||
if (render_context.behavior->IsEffectInfoVersion2Supported()) {
|
||||
const auto* parameter =
|
||||
reinterpret_cast<const BiquadFilterInfo::ParameterVersion2*>(effect_info.GetParameter());
|
||||
if (!parameter) {
|
||||
LOG_ERROR(Service_Audio, "Biquad filter parameter is null");
|
||||
return;
|
||||
}
|
||||
param_state = parameter->state;
|
||||
channel_count = parameter->channel_count;
|
||||
} else {
|
||||
const auto* parameter =
|
||||
reinterpret_cast<const BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter());
|
||||
if (!parameter) {
|
||||
LOG_ERROR(Service_Audio, "Biquad filter parameter is null");
|
||||
return;
|
||||
}
|
||||
param_state = parameter->state;
|
||||
channel_count = parameter->channel_count;
|
||||
}
|
||||
|
||||
if (channel_count <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& parameter{
|
||||
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
|
||||
if (effect_info.IsEnabled()) {
|
||||
bool needs_init{false};
|
||||
|
||||
switch (param_state) {
|
||||
switch (parameter.state) {
|
||||
case EffectInfoBase::ParameterState::Initialized:
|
||||
needs_init = true;
|
||||
break;
|
||||
@@ -403,26 +375,22 @@ void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset
|
||||
if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) {
|
||||
needs_init = false;
|
||||
} else {
|
||||
needs_init = param_state == EffectInfoBase::ParameterState::Updating;
|
||||
needs_init = parameter.state == EffectInfoBase::ParameterState::Updating;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Service_Audio,
|
||||
"Invalid biquad parameter state {}, treating as uninitialized",
|
||||
static_cast<u32>(param_state));
|
||||
needs_init = true;
|
||||
LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}",
|
||||
static_cast<u32>(parameter.state));
|
||||
break;
|
||||
}
|
||||
|
||||
const bool use_float_processing =
|
||||
render_context.behavior->UseBiquadFilterFloatProcessing();
|
||||
|
||||
for (s8 channel = 0; channel < channel_count; channel++) {
|
||||
command_buffer.GenerateBiquadFilterCommand(node_id, effect_info, buffer_offset, channel,
|
||||
needs_init, use_float_processing);
|
||||
for (s8 channel = 0; channel < parameter.channel_count; channel++) {
|
||||
command_buffer.GenerateBiquadFilterCommand(
|
||||
node_id, effect_info, buffer_offset, channel, needs_init,
|
||||
render_context.behavior->UseBiquadFilterFloatProcessing());
|
||||
}
|
||||
} else {
|
||||
for (s8 channel = 0; channel < channel_count; channel++) {
|
||||
for (s8 channel = 0; channel < parameter.channel_count; channel++) {
|
||||
command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset,
|
||||
channel);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -37,22 +34,14 @@ void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
}
|
||||
|
||||
void BiquadFilterInfo::UpdateForCommandGeneration() {
|
||||
usage_state = enabled ? UsageState::Enabled : UsageState::Disabled;
|
||||
|
||||
auto* params_v1 = reinterpret_cast<ParameterVersion1*>(parameter.data());
|
||||
auto* params_v2 = reinterpret_cast<ParameterVersion2*>(parameter.data());
|
||||
|
||||
const auto raw_state_v1 = static_cast<u8>(params_v1->state);
|
||||
const auto raw_state_v2 = static_cast<u8>(params_v2->state);
|
||||
|
||||
if (raw_state_v1 <= static_cast<u8>(ParameterState::Updated)) {
|
||||
params_v1->state = ParameterState::Updated;
|
||||
} else if (raw_state_v2 <= static_cast<u8>(ParameterState::Updated)) {
|
||||
params_v2->state = ParameterState::Updated;
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
params_v1->state = ParameterState::Updated;
|
||||
params_v2->state = ParameterState::Updated;
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
params->state = ParameterState::Updated;
|
||||
}
|
||||
|
||||
void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -30,12 +27,10 @@ public:
|
||||
struct ParameterVersion2 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ u32 padding;
|
||||
/* 0x10 */ std::array<f32, 3> b;
|
||||
/* 0x1C */ std::array<f32, 2> a;
|
||||
/* 0x24 */ s8 channel_count;
|
||||
/* 0x25 */ ParameterState state;
|
||||
/* 0x26 */ u16 reserved;
|
||||
/* 0x0C */ std::array<s16, 3> b;
|
||||
/* 0x12 */ std::array<s16, 2> a;
|
||||
/* 0x16 */ s8 channel_count;
|
||||
/* 0x17 */ ParameterState state;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
|
||||
"BiquadFilterInfo::ParameterVersion2 has the wrong size!");
|
||||
|
||||
@@ -515,7 +515,7 @@ namespace Common::Android {
|
||||
s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class));
|
||||
s_patch_constructor = env->GetMethodID(
|
||||
patch_class, "<init>",
|
||||
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
|
||||
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V");
|
||||
s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z");
|
||||
s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;");
|
||||
s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;");
|
||||
|
||||
@@ -17,8 +17,6 @@ add_library(core STATIC
|
||||
constants.h
|
||||
core.cpp
|
||||
core.h
|
||||
game_settings.cpp
|
||||
game_settings.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
cpu_manager.cpp
|
||||
@@ -611,18 +609,6 @@ add_library(core STATIC
|
||||
hle/service/caps/caps_u.h
|
||||
hle/service/cmif_serialization.h
|
||||
hle/service/cmif_types.h
|
||||
hle/service/dmnt/cheat_interface.cpp
|
||||
hle/service/dmnt/cheat_interface.h
|
||||
hle/service/dmnt/cheat_parser.cpp
|
||||
hle/service/dmnt/cheat_parser.h
|
||||
hle/service/dmnt/cheat_process_manager.cpp
|
||||
hle/service/dmnt/cheat_process_manager.h
|
||||
hle/service/dmnt/cheat_virtual_machine.cpp
|
||||
hle/service/dmnt/cheat_virtual_machine.h
|
||||
hle/service/dmnt/dmnt.cpp
|
||||
hle/service/dmnt/dmnt.h
|
||||
hle/service/dmnt/dmnt_results.h
|
||||
hle/service/dmnt/dmnt_types.h
|
||||
hle/service/erpt/erpt.cpp
|
||||
hle/service/erpt/erpt.h
|
||||
hle/service/es/es.cpp
|
||||
@@ -1157,6 +1143,11 @@ add_library(core STATIC
|
||||
loader/xci.h
|
||||
memory.cpp
|
||||
memory.h
|
||||
memory/cheat_engine.cpp
|
||||
memory/cheat_engine.h
|
||||
memory/dmnt_cheat_types.h
|
||||
memory/dmnt_cheat_vm.cpp
|
||||
memory/dmnt_cheat_vm.h
|
||||
perf_stats.cpp
|
||||
perf_stats.h
|
||||
reporter.cpp
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "game_settings.h"
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -52,12 +51,12 @@
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/reporter.h"
|
||||
#include "core/tools/freezer.h"
|
||||
#include "core/tools/renderdoc.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "hle/service/dmnt/cheat_process_manager.h"
|
||||
#include "network/network.h"
|
||||
#include "video_core/host1x/host1x.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
@@ -277,18 +276,9 @@ struct System::Impl {
|
||||
audio_core = std::make_unique<AudioCore::AudioCore>(system);
|
||||
|
||||
service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
|
||||
|
||||
// Create cheat_manager BEFORE services, as DMNT::LoopProcess needs it
|
||||
cheat_manager = std::make_unique<Service::DMNT::CheatProcessManager>(system);
|
||||
|
||||
services =
|
||||
std::make_unique<Service::Services>(service_manager, system, stop_event.get_token());
|
||||
|
||||
// Apply any pending cheats that were registered before cheat_manager was initialized
|
||||
if (pending_cheats.has_pending) {
|
||||
ApplyPendingCheats(system);
|
||||
}
|
||||
|
||||
is_powered_on = true;
|
||||
exit_locked = false;
|
||||
exit_requested = false;
|
||||
@@ -302,6 +292,48 @@ struct System::Impl {
|
||||
return SystemResultStatus::Success;
|
||||
}
|
||||
|
||||
|
||||
void LoadOverrides(u64 programId) const {
|
||||
std::string vendor = gpu_core->Renderer().GetDeviceVendor();
|
||||
LOG_INFO(Core, "GPU Vendor: {}", vendor);
|
||||
|
||||
// Reset all per-game flags
|
||||
Settings::values.use_squashed_iterated_blend = false;
|
||||
|
||||
// Insert PC overrides here
|
||||
|
||||
#ifdef ANDROID
|
||||
// Example on how to set a setting based on the program ID and vendor
|
||||
if (programId == 0x010028600EBDA000 && vendor == "Mali") { // Mario 3d World
|
||||
// Settings::values.example = true;
|
||||
}
|
||||
|
||||
// Example array of program IDs
|
||||
const std::array<u64, 10> example_array = {
|
||||
//0xprogramId
|
||||
0x0004000000033400, // Game 1
|
||||
0x0004000000033500 // Game 2
|
||||
// And so on
|
||||
};
|
||||
|
||||
for (auto id : example_array) {
|
||||
if (programId == id) {
|
||||
// Settings::values.example = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Ninja Gaiden Ragebound
|
||||
constexpr u64 ngr = 0x0100781020710000ULL;
|
||||
|
||||
if (programId == ngr) {
|
||||
LOG_INFO(Core, "Enabling game specifc override: use_squashed_iterated_blend");
|
||||
Settings::values.use_squashed_iterated_blend = true;
|
||||
}
|
||||
}
|
||||
|
||||
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window,
|
||||
const std::string& filepath,
|
||||
Service::AM::FrontendAppletParameters& params) {
|
||||
@@ -352,6 +384,11 @@ struct System::Impl {
|
||||
return init_result;
|
||||
}
|
||||
|
||||
// Initialize cheat engine
|
||||
if (cheat_engine) {
|
||||
cheat_engine->Initialize();
|
||||
}
|
||||
|
||||
// Register with applet manager
|
||||
// All threads are started, begin main process execution, now that we're in the clear
|
||||
applet_manager.CreateAndInsertByFrontendAppletParameters(std::move(process), params);
|
||||
@@ -382,8 +419,7 @@ struct System::Impl {
|
||||
LOG_ERROR(Core, "Failed to find program id for ROM");
|
||||
}
|
||||
|
||||
|
||||
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
|
||||
LoadOverrides(program_id);
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info;
|
||||
game_info.name = name;
|
||||
@@ -419,6 +455,7 @@ struct System::Impl {
|
||||
services.reset();
|
||||
service_manager.reset();
|
||||
fs_controller.Reset();
|
||||
cheat_engine.reset();
|
||||
core_timing.ClearPendingEvents();
|
||||
app_loader.reset();
|
||||
audio_core.reset();
|
||||
@@ -494,6 +531,7 @@ struct System::Impl {
|
||||
bool nvdec_active{};
|
||||
|
||||
Reporter reporter;
|
||||
std::unique_ptr<Memory::CheatEngine> cheat_engine;
|
||||
std::unique_ptr<Tools::Freezer> memory_freezer;
|
||||
std::array<u8, 0x20> build_id{};
|
||||
|
||||
@@ -522,18 +560,6 @@ struct System::Impl {
|
||||
/// Debugger
|
||||
std::unique_ptr<Core::Debugger> debugger;
|
||||
|
||||
/// Cheat Manager (DMNT)
|
||||
std::unique_ptr<Service::DMNT::CheatProcessManager> cheat_manager;
|
||||
|
||||
/// Pending cheats to register after cheat_manager is initialized
|
||||
struct PendingCheats {
|
||||
std::vector<Service::DMNT::CheatEntry> list;
|
||||
std::array<u8, 32> build_id{};
|
||||
u64 main_region_begin{};
|
||||
u64 main_region_size{};
|
||||
bool has_pending{false};
|
||||
} pending_cheats;
|
||||
|
||||
SystemResultStatus status = SystemResultStatus::Success;
|
||||
std::string status_details = "";
|
||||
|
||||
@@ -569,61 +595,6 @@ struct System::Impl {
|
||||
general_channel_event = std::make_unique<Service::Event>(*general_channel_context);
|
||||
general_channel_initialized = true;
|
||||
}
|
||||
|
||||
void ApplyPendingCheats(System& system) {
|
||||
if (!pending_cheats.has_pending || !cheat_manager) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Core, "Applying {} pending cheats", pending_cheats.list.size());
|
||||
|
||||
const auto result = cheat_manager->AttachToApplicationProcess(
|
||||
pending_cheats.build_id, pending_cheats.main_region_begin,
|
||||
pending_cheats.main_region_size);
|
||||
|
||||
if (result.IsError()) {
|
||||
LOG_WARNING(Core, "Failed to attach cheat process: result={}", result.raw);
|
||||
pending_cheats = {};
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Core, "Cheat process attached successfully");
|
||||
|
||||
for (const auto& entry : pending_cheats.list) {
|
||||
if (entry.cheat_id == 0 && entry.definition.num_opcodes != 0) {
|
||||
LOG_DEBUG(Core, "Setting master cheat '{}' with {} opcodes",
|
||||
entry.definition.readable_name.data(), entry.definition.num_opcodes);
|
||||
const auto set_result = cheat_manager->SetMasterCheat(entry.definition);
|
||||
if (set_result.IsError()) {
|
||||
LOG_WARNING(Core, "Failed to set master cheat: result={}", set_result.raw);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add normal cheats (cheat_id != 0)
|
||||
for (const auto& entry : pending_cheats.list) {
|
||||
if (entry.cheat_id == 0 || entry.definition.num_opcodes == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 assigned_id = 0;
|
||||
LOG_DEBUG(Core, "Adding cheat '{}' (enabled={}, {} opcodes)",
|
||||
entry.definition.readable_name.data(), entry.enabled,
|
||||
entry.definition.num_opcodes);
|
||||
const auto add_result = cheat_manager->AddCheat(assigned_id, entry.enabled,
|
||||
entry.definition);
|
||||
if (add_result.IsError()) {
|
||||
LOG_WARNING(Core,
|
||||
"Failed to add cheat (original_id={} enabled={} name='{}'): result={}",
|
||||
entry.cheat_id, entry.enabled,
|
||||
entry.definition.readable_name.data(), add_result.raw);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear pending cheats
|
||||
pending_cheats = {};
|
||||
}
|
||||
};
|
||||
|
||||
System::System() : impl{std::make_unique<Impl>(*this)} {}
|
||||
@@ -869,61 +840,11 @@ FileSys::VirtualFilesystem System::GetFilesystem() const {
|
||||
return impl->virtual_filesystem;
|
||||
}
|
||||
|
||||
void System::RegisterCheatList(const std::vector<Service::DMNT::CheatEntry>& list,
|
||||
void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
||||
const std::array<u8, 32>& build_id, u64 main_region_begin,
|
||||
u64 main_region_size) {
|
||||
// If cheat_manager is not yet initialized, cache the cheats for later
|
||||
if (!impl->cheat_manager) {
|
||||
impl->pending_cheats.list = list;
|
||||
impl->pending_cheats.build_id = build_id;
|
||||
impl->pending_cheats.main_region_begin = main_region_begin;
|
||||
impl->pending_cheats.main_region_size = main_region_size;
|
||||
impl->pending_cheats.has_pending = true;
|
||||
LOG_INFO(Core, "Cached {} cheats for later registration", list.size());
|
||||
return;
|
||||
}
|
||||
|
||||
// Attach cheat process to the current application process
|
||||
const auto result = impl->cheat_manager->AttachToApplicationProcess(build_id, main_region_begin,
|
||||
main_region_size);
|
||||
if (result.IsError()) {
|
||||
LOG_WARNING(Core, "Failed to attach cheat process: result={}", result.raw);
|
||||
return;
|
||||
}
|
||||
|
||||
// Empty list: nothing more to do
|
||||
if (list.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set master cheat if present (cheat_id == 0)
|
||||
for (const auto& entry : list) {
|
||||
if (entry.cheat_id == 0 && entry.definition.num_opcodes != 0) {
|
||||
const auto set_result = impl->cheat_manager->SetMasterCheat(entry.definition);
|
||||
if (set_result.IsError()) {
|
||||
LOG_WARNING(Core, "Failed to set master cheat: result={}", set_result.raw);
|
||||
}
|
||||
// Only one master cheat allowed
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add normal cheats (cheat_id != 0)
|
||||
for (const auto& entry : list) {
|
||||
if (entry.cheat_id == 0 || entry.definition.num_opcodes == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 assigned_id = 0;
|
||||
const auto add_result = impl->cheat_manager->AddCheat(assigned_id, entry.enabled,
|
||||
entry.definition);
|
||||
if (add_result.IsError()) {
|
||||
LOG_WARNING(Core,
|
||||
"Failed to add cheat (original_id={} enabled={} name='{}'): result={}",
|
||||
entry.cheat_id, entry.enabled,
|
||||
entry.definition.readable_name.data(), add_result.raw);
|
||||
}
|
||||
}
|
||||
impl->cheat_engine = std::make_unique<Memory::CheatEngine>(*this, list, build_id);
|
||||
impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size);
|
||||
}
|
||||
|
||||
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) {
|
||||
@@ -1067,15 +988,6 @@ Tools::RenderdocAPI& System::GetRenderdocAPI() {
|
||||
return *impl->renderdoc_api;
|
||||
}
|
||||
|
||||
Service::DMNT::CheatProcessManager& System::GetCheatManager()
|
||||
{
|
||||
return *impl->cheat_manager;
|
||||
}
|
||||
|
||||
const Service::DMNT::CheatProcessManager& System::GetCheatManager() const {
|
||||
return *impl->cheat_manager;
|
||||
}
|
||||
|
||||
void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
|
||||
return impl->kernel.RunServer(std::move(server_manager));
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
#include "core/hle/service/os/event.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
|
||||
@@ -46,14 +45,10 @@ enum class ResultStatus : u16;
|
||||
} // namespace Loader
|
||||
|
||||
namespace Core::Memory {
|
||||
struct CheatEntry;
|
||||
class Memory;
|
||||
} // namespace Core::Memory
|
||||
|
||||
namespace Service::DMNT {
|
||||
class CheatProcessManager;
|
||||
struct CheatEntry;
|
||||
}
|
||||
|
||||
namespace Service {
|
||||
|
||||
namespace Account {
|
||||
@@ -344,7 +339,7 @@ public:
|
||||
|
||||
[[nodiscard]] FileSys::VirtualFilesystem GetFilesystem() const;
|
||||
|
||||
void RegisterCheatList(const std::vector<Service::DMNT::CheatEntry>& list,
|
||||
void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
||||
const std::array<u8, 0x20>& build_id, u64 main_region_begin,
|
||||
u64 main_region_size);
|
||||
|
||||
@@ -388,9 +383,6 @@ public:
|
||||
|
||||
[[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI();
|
||||
|
||||
[[nodiscard]] Service::DMNT::CheatProcessManager& GetCheatManager();
|
||||
[[nodiscard]] const Service::DMNT::CheatProcessManager& GetCheatManager() const;
|
||||
|
||||
void SetExitLocked(bool locked);
|
||||
bool GetExitLocked() const;
|
||||
|
||||
|
||||
@@ -27,13 +27,12 @@
|
||||
#include "core/file_sys/vfs/vfs_cached.h"
|
||||
#include "core/file_sys/vfs/vfs_layered.h"
|
||||
#include "core/file_sys/vfs/vfs_vector.h"
|
||||
#include "core/hle/service/dmnt/cheat_parser.h"
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/ns/language.h"
|
||||
#include "core/hle/service/set/settings_server.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/loader/nso.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
|
||||
namespace FileSys {
|
||||
namespace {
|
||||
@@ -65,15 +64,16 @@ std::string FormatTitleVersion(u32 version,
|
||||
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
|
||||
}
|
||||
|
||||
// Returns a directory with name matching case-insensitively.
|
||||
// Returns nullptr if directory doesn't contain a subdirectory with the given name.
|
||||
VirtualDir FindSubdirectoryCaseless(const VirtualDir& dir, std::string_view name) {
|
||||
// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
|
||||
// doesn't have a directory with name.
|
||||
VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) {
|
||||
#ifdef _WIN32
|
||||
return dir->GetSubdirectory(name);
|
||||
#else
|
||||
const auto target = Common::ToLower(std::string(name));
|
||||
for (const auto& subdir : dir->GetSubdirectories()) {
|
||||
if (Common::ToLower(subdir->GetName()) == target) {
|
||||
const auto subdirs = dir->GetSubdirectories();
|
||||
for (const auto& subdir : subdirs) {
|
||||
std::string dir_name = Common::ToLower(subdir->GetName());
|
||||
if (dir_name == name) {
|
||||
return subdir;
|
||||
}
|
||||
}
|
||||
@@ -82,35 +82,36 @@ VirtualDir FindSubdirectoryCaseless(const VirtualDir& dir, std::string_view name
|
||||
#endif
|
||||
}
|
||||
|
||||
std::optional<std::vector<Service::DMNT::CheatEntry>> ReadCheatFileFromFolder(
|
||||
std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
|
||||
u64 title_id, const PatchManager::BuildID& build_id_, const VirtualDir& base_path, bool upper) {
|
||||
|
||||
const auto build_id_raw = Common::HexToString(build_id_, upper);
|
||||
const auto build_id = build_id_raw.substr(0, std::min(build_id_raw.size(), sizeof(u64) * 2));
|
||||
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
|
||||
const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
|
||||
|
||||
if (file == nullptr) {
|
||||
LOG_DEBUG(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<u8> data(file->GetSize());
|
||||
if (file->Read(data.data(), data.size()) != data.size()) {
|
||||
LOG_WARNING(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const Service::DMNT::CheatParser parser;
|
||||
const Core::Memory::TextCheatParser parser;
|
||||
return parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
||||
}
|
||||
|
||||
void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
|
||||
if (!to.empty()) {
|
||||
if (to.empty()) {
|
||||
to += with;
|
||||
} else {
|
||||
to += ", ";
|
||||
to += with;
|
||||
}
|
||||
to += with;
|
||||
}
|
||||
|
||||
bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
@@ -315,7 +316,7 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name)
|
||||
return !CollectPatches(patch_dirs, build_id).empty();
|
||||
}
|
||||
|
||||
std::vector<Service::DMNT::CheatEntry> PatchManager::CreateCheatList(const BuildID& build_id_) const {
|
||||
std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildID& build_id_) const {
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
@@ -324,71 +325,36 @@ std::vector<Service::DMNT::CheatEntry> PatchManager::CreateCheatList(const Build
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(), [](auto const& l, auto const& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
std::vector<Service::DMNT::CheatEntry> out;
|
||||
|
||||
// Load cheats from: <mod dir>/<folder>/cheats/<build_id>.txt
|
||||
// <mod dir> / <folder> / cheats / <build id>.txt
|
||||
std::vector<Core::Memory::CheatEntry> out;
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
const auto mod_name = subdir->GetName();
|
||||
|
||||
// Skip entirely disabled mods
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), mod_name) != disabled.cend()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats");
|
||||
if (cheats_dir == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try uppercase build_id first, then lowercase
|
||||
std::optional<std::vector<Service::DMNT::CheatEntry>> cheat_entries;
|
||||
if (auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true)) {
|
||||
cheat_entries = std::move(res);
|
||||
} else if (auto res_lower = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false)) {
|
||||
cheat_entries = std::move(res_lower);
|
||||
}
|
||||
|
||||
if (cheat_entries) {
|
||||
for (auto& entry : *cheat_entries) {
|
||||
// Check if this individual cheat is disabled
|
||||
const std::string cheat_name = entry.definition.readable_name.data();
|
||||
const std::string cheat_key = mod_name + "::" + cheat_name;
|
||||
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), cheat_key) != disabled.cend()) {
|
||||
// Individual cheat is disabled - mark it as disabled but still include it
|
||||
entry.enabled = false;
|
||||
}
|
||||
|
||||
out.push_back(entry);
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), subdir->GetName()) == disabled.cend()) {
|
||||
if (auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats"); cheats_dir != nullptr) {
|
||||
if (auto const res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true))
|
||||
std::copy(res->begin(), res->end(), std::back_inserter(out));
|
||||
if (auto const res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false))
|
||||
std::copy(res->begin(), res->end(), std::back_inserter(out));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// User-friendly cheat loading from: <mod dir>/cheat_*.txt
|
||||
for (const auto& file : load_dir->GetFiles()) {
|
||||
const auto& name = file->GetName();
|
||||
if (!name.starts_with("cheat_")) {
|
||||
continue;
|
||||
// Uncareless user-friendly loading of patches (must start with 'cheat_')
|
||||
// <mod dir> / <cheat file>.txt
|
||||
auto const patch_files = load_dir->GetFiles();
|
||||
for (auto const& f : patch_files) {
|
||||
auto const name = f->GetName();
|
||||
if (name.starts_with("cheat_") && std::find(disabled.cbegin(), disabled.cend(), name) == disabled.cend()) {
|
||||
std::vector<u8> data(f->GetSize());
|
||||
if (f->Read(data.data(), data.size()) == data.size()) {
|
||||
const Core::Memory::TextCheatParser parser;
|
||||
auto const res = parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
||||
std::copy(res.begin(), res.end(), std::back_inserter(out));
|
||||
} else {
|
||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}", title_id);
|
||||
}
|
||||
}
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), name) != disabled.cend()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<u8> data(file->GetSize());
|
||||
if (file->Read(data.data(), data.size()) != static_cast<size_t>(data.size())) {
|
||||
LOG_WARNING(Common_Filesystem, "Failed to read cheat file '{}' for title_id={:016X}",
|
||||
name, title_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const Service::DMNT::CheatParser parser;
|
||||
auto entries = parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
||||
out.insert(out.end(), entries.begin(), entries.end());
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -515,53 +481,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
||||
return romfs;
|
||||
}
|
||||
|
||||
PatchManager::BuildID PatchManager::GetBuildID(VirtualFile update_raw) const {
|
||||
BuildID build_id{};
|
||||
|
||||
// Get the base NCA
|
||||
const auto base_nca = content_provider.GetEntry(title_id, ContentRecordType::Program);
|
||||
if (base_nca == nullptr) {
|
||||
return build_id;
|
||||
}
|
||||
|
||||
// Try to get ExeFS from update first, then base
|
||||
VirtualDir exefs;
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program);
|
||||
|
||||
if (update != nullptr && update->GetExeFS() != nullptr) {
|
||||
exefs = update->GetExeFS();
|
||||
} else if (update_raw != nullptr) {
|
||||
const auto new_nca = std::make_shared<NCA>(update_raw, base_nca.get());
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetExeFS() != nullptr) {
|
||||
exefs = new_nca->GetExeFS();
|
||||
}
|
||||
}
|
||||
|
||||
if (exefs == nullptr) {
|
||||
exefs = base_nca->GetExeFS();
|
||||
}
|
||||
|
||||
if (exefs == nullptr) {
|
||||
return build_id;
|
||||
}
|
||||
|
||||
// Try to read the main NSO header
|
||||
const auto main_file = exefs->GetFile("main");
|
||||
if (main_file == nullptr || main_file->GetSize() < sizeof(Loader::NSOHeader)) {
|
||||
return build_id;
|
||||
}
|
||||
|
||||
Loader::NSOHeader header{};
|
||||
if (main_file->Read(reinterpret_cast<u8*>(&header), sizeof(header)) == sizeof(header)) {
|
||||
build_id = header.build_id;
|
||||
}
|
||||
|
||||
return build_id;
|
||||
}
|
||||
|
||||
std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildID& build_id) const {
|
||||
std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
if (title_id == 0) {
|
||||
return {};
|
||||
}
|
||||
@@ -582,8 +502,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildI
|
||||
.version = "",
|
||||
.type = PatchType::Update,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id,
|
||||
.parent_name = ""};
|
||||
.title_id = title_id};
|
||||
|
||||
if (nacp != nullptr) {
|
||||
update_patch.version = nacp->GetVersionString();
|
||||
@@ -603,15 +522,11 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildI
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have a valid build_id for cheat enumeration
|
||||
const bool has_build_id = std::any_of(build_id.begin(), build_id.end(), [](u8 b) { return b != 0; });
|
||||
|
||||
// General Mods (LayeredFS and IPS)
|
||||
const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (mod_dir != nullptr) {
|
||||
for (const auto& mod : mod_dir->GetSubdirectories()) {
|
||||
std::string types;
|
||||
bool has_cheats = false;
|
||||
|
||||
const auto exefs_dir = FindSubdirectoryCaseless(mod, "exefs");
|
||||
if (IsDirValidAndNonEmpty(exefs_dir)) {
|
||||
@@ -640,12 +555,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildI
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfs")) ||
|
||||
IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfslite")))
|
||||
AppendCommaIfNotEmpty(types, "LayeredFS");
|
||||
|
||||
const auto cheats_dir = FindSubdirectoryCaseless(mod, "cheats");
|
||||
if (IsDirValidAndNonEmpty(cheats_dir)) {
|
||||
has_cheats = true;
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "cheats")))
|
||||
AppendCommaIfNotEmpty(types, "Cheats");
|
||||
}
|
||||
|
||||
if (types.empty())
|
||||
continue;
|
||||
@@ -657,46 +568,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildI
|
||||
.version = types,
|
||||
.type = PatchType::Mod,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id,
|
||||
.parent_name = ""});
|
||||
|
||||
// Add individual cheats as sub-entries if we have a build_id
|
||||
if (has_cheats && has_build_id && !mod_disabled) {
|
||||
// Try to read cheat file (uppercase first, then lowercase)
|
||||
std::optional<std::vector<Service::DMNT::CheatEntry>> cheat_entries;
|
||||
if (auto res = ReadCheatFileFromFolder(title_id, build_id, cheats_dir, true)) {
|
||||
cheat_entries = std::move(res);
|
||||
} else if (auto res_lower = ReadCheatFileFromFolder(title_id, build_id, cheats_dir, false)) {
|
||||
cheat_entries = std::move(res_lower);
|
||||
}
|
||||
|
||||
if (cheat_entries) {
|
||||
for (const auto& cheat : *cheat_entries) {
|
||||
// Skip master cheat (id 0) with no readable name
|
||||
if (cheat.cheat_id == 0 && cheat.definition.readable_name[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string cheat_name = cheat.definition.readable_name.data();
|
||||
if (cheat_name.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create unique key for this cheat: "ModName::CheatName"
|
||||
const std::string cheat_key = mod->GetName() + "::" + cheat_name;
|
||||
const auto cheat_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), cheat_key) != disabled.end();
|
||||
|
||||
out.push_back({.enabled = !cheat_disabled,
|
||||
.name = cheat_name,
|
||||
.version = types,
|
||||
.type = PatchType::Cheat,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id,
|
||||
.parent_name = mod->GetName()});
|
||||
}
|
||||
}
|
||||
}
|
||||
.title_id = title_id});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,8 +592,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildI
|
||||
.version = types,
|
||||
.type = PatchType::Mod,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id,
|
||||
.parent_name = ""});
|
||||
.title_id = title_id});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -753,8 +624,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildI
|
||||
.version = std::move(list),
|
||||
.type = PatchType::DLC,
|
||||
.program_id = title_id,
|
||||
.title_id = dlc_match.back().title_id,
|
||||
.parent_name = ""});
|
||||
.title_id = dlc_match.back().title_id});
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -13,6 +10,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "core/memory/dmnt_cheat_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
@@ -22,17 +20,13 @@ namespace Service::FileSystem {
|
||||
class FileSystemController;
|
||||
}
|
||||
|
||||
namespace Service::DMNT {
|
||||
struct CheatEntry;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class ContentProvider;
|
||||
class NCA;
|
||||
class NACP;
|
||||
|
||||
enum class PatchType { Update, DLC, Mod, Cheat };
|
||||
enum class PatchType { Update, DLC, Mod };
|
||||
|
||||
struct Patch {
|
||||
bool enabled;
|
||||
@@ -41,7 +35,6 @@ struct Patch {
|
||||
PatchType type;
|
||||
u64 program_id;
|
||||
u64 title_id;
|
||||
std::string parent_name;
|
||||
};
|
||||
|
||||
// A centralized class to manage patches to games.
|
||||
@@ -72,7 +65,7 @@ public:
|
||||
[[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const;
|
||||
|
||||
// Creates a CheatList object with all
|
||||
[[nodiscard]] std::vector<Service::DMNT::CheatEntry> CreateCheatList(
|
||||
[[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
|
||||
const BuildID& build_id) const;
|
||||
|
||||
// Currently tracked RomFS patches:
|
||||
@@ -83,11 +76,8 @@ public:
|
||||
VirtualFile packed_update_raw = nullptr,
|
||||
bool apply_layeredfs = true) const;
|
||||
|
||||
// Returns a vector of patches including individual cheats
|
||||
[[nodiscard]] std::vector<Patch> GetPatches(VirtualFile update_raw = nullptr,
|
||||
const BuildID& build_id = {}) const;
|
||||
|
||||
[[nodiscard]] BuildID GetBuildID(VirtualFile update_raw = nullptr) const;
|
||||
// Returns a vector of patches
|
||||
[[nodiscard]] std::vector<Patch> GetPatches(VirtualFile update_raw = nullptr) const;
|
||||
|
||||
// If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails,
|
||||
// it will fallback to the Meta-type NCA of the base game. If that fails, the result will be
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/game_settings.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
namespace Core::GameSettings {
|
||||
|
||||
static GPUVendor GetGPU(const std::string& gpu_vendor_string) {
|
||||
struct Entry { const char* name; GPUVendor vendor; };
|
||||
static constexpr Entry GpuVendor[] = {
|
||||
// NVIDIA
|
||||
{"NVIDIA", GPUVendor::Nvidia},
|
||||
{"Nouveau", GPUVendor::Nvidia},
|
||||
{"NVK", GPUVendor::Nvidia},
|
||||
{"Tegra", GPUVendor::Nvidia},
|
||||
// AMD
|
||||
{"AMD", GPUVendor::AMD},
|
||||
{"RadeonSI", GPUVendor::AMD},
|
||||
{"RADV", GPUVendor::AMD},
|
||||
{"AMDVLK", GPUVendor::AMD},
|
||||
{"R600", GPUVendor::AMD},
|
||||
// Intel
|
||||
{"Intel", GPUVendor::Intel},
|
||||
{"ANV", GPUVendor::Intel},
|
||||
{"i965", GPUVendor::Intel},
|
||||
{"i915", GPUVendor::Intel},
|
||||
{"OpenSWR", GPUVendor::Intel},
|
||||
// Apple
|
||||
{"Apple", GPUVendor::Apple},
|
||||
{"MoltenVK", GPUVendor::Apple},
|
||||
// Qualcomm / Adreno
|
||||
{"Qualcomm", GPUVendor::Qualcomm},
|
||||
{"Turnip", GPUVendor::Qualcomm},
|
||||
// ARM / Mali
|
||||
{"Mali", GPUVendor::ARM},
|
||||
{"PanVK", GPUVendor::ARM},
|
||||
// Imagination / PowerVR
|
||||
{"PowerVR", GPUVendor::Imagination},
|
||||
{"PVR", GPUVendor::Imagination},
|
||||
// Microsoft / WARP / D3D12 GL
|
||||
{"D3D12", GPUVendor::Microsoft},
|
||||
{"Microsoft", GPUVendor::Microsoft},
|
||||
{"WARP", GPUVendor::Microsoft},
|
||||
};
|
||||
|
||||
for (const auto& entry : GpuVendor) {
|
||||
if (gpu_vendor_string == entry.name) {
|
||||
return entry.vendor;
|
||||
}
|
||||
}
|
||||
|
||||
// legacy (shouldn't be needed anymore, but just in case)
|
||||
std::string gpu = gpu_vendor_string;
|
||||
std::transform(gpu.begin(), gpu.end(), gpu.begin(), [](unsigned char c){ return (char)std::tolower(c); });
|
||||
if (gpu.find("geforce") != std::string::npos) {
|
||||
return GPUVendor::Nvidia;
|
||||
}
|
||||
if (gpu.find("radeon") != std::string::npos || gpu.find("ati") != std::string::npos) {
|
||||
return GPUVendor::AMD;
|
||||
}
|
||||
|
||||
return GPUVendor::Unknown;
|
||||
}
|
||||
|
||||
static OS DetectOS() {
|
||||
#if defined(_WIN32)
|
||||
return OS::Windows;
|
||||
#elif defined(__FIREOS__)
|
||||
return OS::FireOS;
|
||||
#elif defined(__ANDROID__)
|
||||
return OS::Android;
|
||||
#elif defined(__OHOS__)
|
||||
return OS::HarmonyOS;
|
||||
#elif defined(__HAIKU__)
|
||||
return OS::HaikuOS;
|
||||
#elif defined(__DragonFly__)
|
||||
return OS::DragonFlyBSD;
|
||||
#elif defined(__NetBSD__)
|
||||
return OS::NetBSD;
|
||||
#elif defined(__OpenBSD__)
|
||||
return OS::OpenBSD;
|
||||
#elif defined(_AIX)
|
||||
return OS::AIX;
|
||||
#elif defined(__managarm__)
|
||||
return OS::Managarm;
|
||||
#elif defined(__redox__)
|
||||
return OS::RedoxOS;
|
||||
#elif defined(__APPLE__) && defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
|
||||
return OS::IOS;
|
||||
#elif defined(__APPLE__)
|
||||
return OS::MacOS;
|
||||
#elif defined(__FreeBSD__)
|
||||
return OS::FreeBSD;
|
||||
#elif defined(__sun) && defined(__SVR4)
|
||||
return OS::Solaris;
|
||||
#elif defined(__linux__)
|
||||
return OS::Linux;
|
||||
#else
|
||||
return OS::Unknown;
|
||||
#endif
|
||||
}
|
||||
|
||||
EnvironmentInfo DetectEnvironment(const VideoCore::RendererBase& renderer) {
|
||||
EnvironmentInfo env{};
|
||||
env.os = DetectOS();
|
||||
env.vendor_string = renderer.GetDeviceVendor();
|
||||
env.vendor = GetGPU(env.vendor_string);
|
||||
return env;
|
||||
}
|
||||
|
||||
void LoadOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer) {
|
||||
const auto env = DetectEnvironment(renderer);
|
||||
|
||||
switch (static_cast<TitleID>(program_id)) {
|
||||
case TitleID::NinjaGaidenRagebound:
|
||||
Settings::values.use_squashed_iterated_blend = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_INFO(Core, "Applied game settings for title ID {:016X} on OS {}, GPU vendor {} ({})",
|
||||
program_id,
|
||||
static_cast<int>(env.os),
|
||||
static_cast<int>(env.vendor),
|
||||
env.vendor_string);
|
||||
}
|
||||
|
||||
} // namespace Core::GameSettings
|
||||
@@ -1,60 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace VideoCore { class RendererBase; }
|
||||
|
||||
namespace Core::GameSettings {
|
||||
|
||||
enum class OS {
|
||||
Windows,
|
||||
Linux,
|
||||
MacOS,
|
||||
IOS,
|
||||
Android,
|
||||
FireOS,
|
||||
HarmonyOS,
|
||||
FreeBSD,
|
||||
DragonFlyBSD,
|
||||
NetBSD,
|
||||
OpenBSD,
|
||||
HaikuOS,
|
||||
AIX,
|
||||
Managarm,
|
||||
RedoxOS,
|
||||
Solaris,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
enum class GPUVendor {
|
||||
Nvidia,
|
||||
AMD,
|
||||
Intel,
|
||||
Apple,
|
||||
Qualcomm,
|
||||
ARM,
|
||||
Imagination,
|
||||
Microsoft,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
enum class TitleID : std::uint64_t {
|
||||
NinjaGaidenRagebound = 0x0100781020710000ULL
|
||||
};
|
||||
|
||||
struct EnvironmentInfo {
|
||||
OS os{OS::Unknown};
|
||||
GPUVendor vendor{GPUVendor::Unknown};
|
||||
std::string vendor_string; // raw string from driver
|
||||
};
|
||||
|
||||
EnvironmentInfo DetectEnvironment(const VideoCore::RendererBase& renderer);
|
||||
|
||||
void LoadOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer);
|
||||
|
||||
} // namespace Core::GameSettings
|
||||
@@ -36,6 +36,10 @@ void DisplayLayerManager::Initialize(Core::System& system, Kernel::KProcess* pro
|
||||
m_buffer_sharing_enabled = false;
|
||||
m_blending_enabled = mode == LibraryAppletMode::PartialForeground ||
|
||||
mode == LibraryAppletMode::PartialForegroundIndirectDisplay;
|
||||
|
||||
if (m_applet_id != AppletId::Application) {
|
||||
(void)this->IsSystemBufferSharingEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayLayerManager::Finalize() {
|
||||
@@ -76,10 +80,11 @@ Result DisplayLayerManager::CreateManagedDisplayLayer(u64* out_layer_id) {
|
||||
if (m_applet_id != AppletId::Application) {
|
||||
(void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id);
|
||||
if (m_applet_id == AppletId::OverlayDisplay) {
|
||||
(void)m_manager_display_service->SetLayerZIndex(-1, *out_layer_id);
|
||||
(void)m_display_service->GetContainer()->SetLayerIsOverlay(*out_layer_id, true);
|
||||
static constexpr s32 kOverlayBackgroundZ = -1;
|
||||
(void)m_manager_display_service->SetLayerZIndex(kOverlayBackgroundZ, *out_layer_id);
|
||||
} else {
|
||||
(void)m_manager_display_service->SetLayerZIndex(1, *out_layer_id);
|
||||
static constexpr s32 kOverlayZ = 3;
|
||||
(void)m_manager_display_service->SetLayerZIndex(kOverlayZ, *out_layer_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +131,6 @@ Result DisplayLayerManager::IsSystemBufferSharingEnabled() {
|
||||
s32 initial_z = 1;
|
||||
if (m_applet_id == AppletId::OverlayDisplay) {
|
||||
initial_z = -1;
|
||||
(void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true);
|
||||
}
|
||||
m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id);
|
||||
R_SUCCEED();
|
||||
|
||||
@@ -88,8 +88,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_
|
||||
{181, nullptr, "UpgradeLaunchRequiredVersion"},
|
||||
{190, nullptr, "SendServerMaintenanceOverlayNotification"},
|
||||
{200, nullptr, "GetLastApplicationExitReason"},
|
||||
{210, D<&IApplicationFunctions::GetLaunchRequiredVersionUpgrade>, "GetLaunchRequiredVersionUpgrade"}, // [20.0.0+]
|
||||
{211, nullptr, "GetLaunchRequiredVersionUpgradeStatus"}, // [20.0.0+]
|
||||
{210, D<&IApplicationFunctions::GetUnknownEvent210>, "Unknown210"},
|
||||
{220, nullptr, "Unknown220"}, // [20.0.0+]
|
||||
{300, nullptr, "Unknown300"}, // [20.0.0+]
|
||||
{310, nullptr, "Unknown310"}, // [20.0.0+]
|
||||
@@ -497,10 +496,10 @@ Result IApplicationFunctions::GetHealthWarningDisappearedSystemEvent(
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationFunctions::GetLaunchRequiredVersionUpgrade(
|
||||
Result IApplicationFunctions::GetUnknownEvent210(
|
||||
OutCopyHandle<Kernel::KReadableEvent> out_event) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
*out_event = m_applet->state_changed_event.GetHandle();
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
*out_event = m_applet->unknown_event.GetHandle();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ private:
|
||||
Result TryPopFromFriendInvitationStorageChannel(Out<SharedPointer<IStorage>> out_storage);
|
||||
Result GetNotificationStorageChannelEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result GetHealthWarningDisappearedSystemEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result GetLaunchRequiredVersionUpgrade(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result GetUnknownEvent210(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result PrepareForJit();
|
||||
|
||||
const std::shared_ptr<Applet> m_applet;
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Service::AM {
|
||||
{20, D<&IOverlayFunctions::SetHandlingHomeButtonShortPressedEnabled>, "SetHandlingHomeButtonShortPressedEnabled"},
|
||||
{21, nullptr, "SetHandlingTouchScreenInputEnabled"},
|
||||
{30, nullptr, "SetHealthWarningShowingState"},
|
||||
{31, D<&IOverlayFunctions::IsHealthWarningRequired>, "IsHealthWarningRequired"},
|
||||
{31, nullptr, "IsHealthWarningRequired"},
|
||||
{40, nullptr, "GetApplicationNintendoLogo"},
|
||||
{41, nullptr, "GetApplicationStartupMovie"},
|
||||
{50, nullptr, "SetGpuTimeSliceBoostForApplication"},
|
||||
@@ -69,33 +69,12 @@ namespace Service::AM {
|
||||
Result IOverlayFunctions::GetApplicationIdForLogo(Out<u64> out_application_id) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
std::shared_ptr<Applet> target_applet;
|
||||
|
||||
auto* window_system = system.GetAppletManager().GetWindowSystem();
|
||||
if (window_system) {
|
||||
target_applet = window_system->GetMainApplet();
|
||||
if (target_applet) {
|
||||
std::scoped_lock lk{target_applet->lock};
|
||||
LOG_DEBUG(Service_AM, "applet_id={}, program_id={:016X}, type={}",
|
||||
static_cast<u32>(target_applet->applet_id), target_applet->program_id,
|
||||
static_cast<u32>(target_applet->type));
|
||||
|
||||
u64 id = target_applet->screen_shot_identity.application_id;
|
||||
if (id == 0) {
|
||||
id = target_applet->program_id;
|
||||
}
|
||||
LOG_DEBUG(Service_AM, "application_id={:016X}", id);
|
||||
*out_application_id = id;
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer explicit application_id if available, else fall back to program_id
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
u64 id = m_applet->screen_shot_identity.application_id;
|
||||
if (id == 0) {
|
||||
id = m_applet->program_id;
|
||||
}
|
||||
LOG_DEBUG(Service_AM, "application_id={:016X} (fallback)", id);
|
||||
*out_application_id = id;
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -107,13 +86,6 @@ namespace Service::AM {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IOverlayFunctions::IsHealthWarningRequired(Out<bool> is_required) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
*is_required = false;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IOverlayFunctions::SetHandlingHomeButtonShortPressedEnabled(bool enabled) {
|
||||
LOG_DEBUG(Service_AM, "called, enabled={}", enabled);
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace Service::AM {
|
||||
Result EndToWatchShortHomeButtonMessage();
|
||||
Result GetApplicationIdForLogo(Out<u64> out_application_id);
|
||||
Result SetAutoSleepTimeAndDimmingTimeEnabled(bool enabled);
|
||||
Result IsHealthWarningRequired(Out<bool> is_required);
|
||||
Result SetHandlingHomeButtonShortPressedEnabled(bool enabled);
|
||||
Result Unknown70();
|
||||
|
||||
|
||||
@@ -186,6 +186,8 @@ void WindowSystem::OnSystemButtonPress(SystemButtonType type) {
|
||||
if (m_overlay_display) {
|
||||
std::scoped_lock lk_overlay{m_overlay_display->lock};
|
||||
m_overlay_display->overlay_in_foreground = !m_overlay_display->overlay_in_foreground;
|
||||
// Tie window visibility to foreground state so hidden when not active
|
||||
m_overlay_display->window_visible = m_overlay_display->overlay_in_foreground;
|
||||
LOG_INFO(Service_AM, "Overlay long-press toggle: overlay_in_foreground={} window_visible={}", m_overlay_display->overlay_in_foreground, m_overlay_display->window_visible);
|
||||
}
|
||||
SendButtonAppletMessageLocked(AppletMessage::DetectLongPressingHomeButton);
|
||||
@@ -391,7 +393,7 @@ void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, b
|
||||
s32 z_index = 0;
|
||||
const bool now_foreground = inherited_foreground;
|
||||
if (applet->applet_id == AppletId::OverlayDisplay) {
|
||||
z_index = applet->overlay_in_foreground ? 100000 : -1;
|
||||
z_index = applet->overlay_in_foreground ? 100000 : -100000;
|
||||
} else if (now_foreground && !is_obscured) {
|
||||
z_index = 2;
|
||||
} else if (now_foreground) {
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/dmnt/cheat_interface.h"
|
||||
#include "core/hle/service/dmnt/cheat_process_manager.h"
|
||||
#include "core/hle/service/dmnt/dmnt_results.h"
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
ICheatInterface::ICheatInterface(Core::System& system_, CheatProcessManager& manager)
|
||||
: ServiceFramework{system_, "dmnt:cht"}, cheat_process_manager{manager} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{65000, C<&ICheatInterface::HasCheatProcess>, "HasCheatProcess"},
|
||||
{65001, C<&ICheatInterface::GetCheatProcessEvent>, "GetCheatProcessEvent"},
|
||||
{65002, C<&ICheatInterface::GetCheatProcessMetadata>, "GetCheatProcessMetadata"},
|
||||
{65003, C<&ICheatInterface::ForceOpenCheatProcess>, "ForceOpenCheatProcess"},
|
||||
{65004, C<&ICheatInterface::PauseCheatProcess>, "PauseCheatProcess"},
|
||||
{65005, C<&ICheatInterface::ResumeCheatProcess>, "ResumeCheatProcess"},
|
||||
{65006, C<&ICheatInterface::ForceCloseCheatProcess>, "ForceCloseCheatProcess"},
|
||||
{65100, C<&ICheatInterface::GetCheatProcessMappingCount>, "GetCheatProcessMappingCount"},
|
||||
{65101, C<&ICheatInterface::GetCheatProcessMappings>, "GetCheatProcessMappings"},
|
||||
{65102, C<&ICheatInterface::ReadCheatProcessMemory>, "ReadCheatProcessMemory"},
|
||||
{65103, C<&ICheatInterface::WriteCheatProcessMemory>, "WriteCheatProcessMemory"},
|
||||
{65104, C<&ICheatInterface::QueryCheatProcessMemory>, "QueryCheatProcessMemory"},
|
||||
{65200, C<&ICheatInterface::GetCheatCount>, "GetCheatCount"},
|
||||
{65201, C<&ICheatInterface::GetCheats>, "GetCheats"},
|
||||
{65202, C<&ICheatInterface::GetCheatById>, "GetCheatById"},
|
||||
{65203, C<&ICheatInterface::ToggleCheat>, "ToggleCheat"},
|
||||
{65204, C<&ICheatInterface::AddCheat>, "AddCheat"},
|
||||
{65205, C<&ICheatInterface::RemoveCheat>, "RemoveCheat"},
|
||||
{65206, C<&ICheatInterface::ReadStaticRegister>, "ReadStaticRegister"},
|
||||
{65207, C<&ICheatInterface::WriteStaticRegister>, "WriteStaticRegister"},
|
||||
{65208, C<&ICheatInterface::ResetStaticRegisters>, "ResetStaticRegisters"},
|
||||
{65209, C<&ICheatInterface::SetMasterCheat>, "SetMasterCheat"},
|
||||
{65300, C<&ICheatInterface::GetFrozenAddressCount>, "GetFrozenAddressCount"},
|
||||
{65301, C<&ICheatInterface::GetFrozenAddresses>, "GetFrozenAddresses"},
|
||||
{65302, C<&ICheatInterface::GetFrozenAddress>, "GetFrozenAddress"},
|
||||
{65303, C<&ICheatInterface::EnableFrozenAddress>, "EnableFrozenAddress"},
|
||||
{65304, C<&ICheatInterface::DisableFrozenAddress>, "DisableFrozenAddress"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
ICheatInterface::~ICheatInterface() = default;
|
||||
|
||||
Result ICheatInterface::HasCheatProcess(Out<bool> out_has_cheat) {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
*out_has_cheat = cheat_process_manager.HasCheatProcess();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatProcessEvent(OutCopyHandle<Kernel::KReadableEvent> out_event) {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
*out_event = &cheat_process_manager.GetCheatProcessEvent();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata) {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.GetCheatProcessMetadata(*out_metadata));
|
||||
}
|
||||
|
||||
Result ICheatInterface::ForceOpenCheatProcess() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_UNLESS(R_SUCCEEDED(cheat_process_manager.ForceOpenCheatProcess()), ResultCheatNotAttached);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ICheatInterface::PauseCheatProcess() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.PauseCheatProcess());
|
||||
}
|
||||
|
||||
Result ICheatInterface::ResumeCheatProcess() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.ResumeCheatProcess());
|
||||
}
|
||||
|
||||
Result ICheatInterface::ForceCloseCheatProcess() {
|
||||
LOG_WARNING(CheatEngine, "(STUBBED) called");
|
||||
R_RETURN(cheat_process_manager.ForceCloseCheatProcess());
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatProcessMappingCount(Out<u64> out_count) {
|
||||
LOG_WARNING(CheatEngine, "(STUBBED) called");
|
||||
R_RETURN(cheat_process_manager.GetCheatProcessMappingCount(*out_count));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatProcessMappings(
|
||||
Out<u64> out_count, u64 offset,
|
||||
OutArray<Kernel::Svc::MemoryInfo, BufferAttr_HipcMapAlias> out_mappings) {
|
||||
LOG_INFO(CheatEngine, "called, offset={}", offset);
|
||||
R_UNLESS(!out_mappings.empty(), ResultCheatNullBuffer);
|
||||
R_RETURN(cheat_process_manager.GetCheatProcessMappings(*out_count, offset, out_mappings));
|
||||
}
|
||||
|
||||
Result ICheatInterface::ReadCheatProcessMemory(u64 address, u64 size,
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer) {
|
||||
LOG_DEBUG(CheatEngine, "called, address={}, size={}", address, size);
|
||||
R_UNLESS(!out_buffer.empty(), ResultCheatNullBuffer);
|
||||
R_RETURN(cheat_process_manager.ReadCheatProcessMemory(address, size, out_buffer));
|
||||
}
|
||||
|
||||
Result ICheatInterface::WriteCheatProcessMemory(u64 address, u64 size,
|
||||
InBuffer<BufferAttr_HipcMapAlias> buffer) {
|
||||
LOG_DEBUG(CheatEngine, "called, address={}, size={}", address, size);
|
||||
R_UNLESS(!buffer.empty(), ResultCheatNullBuffer);
|
||||
R_RETURN(cheat_process_manager.WriteCheatProcessMemory(address, size, buffer));
|
||||
}
|
||||
|
||||
Result ICheatInterface::QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> out_mapping,
|
||||
u64 address) {
|
||||
LOG_WARNING(CheatEngine, "(STUBBED) called, address={}", address);
|
||||
R_RETURN(cheat_process_manager.QueryCheatProcessMemory(out_mapping, address));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatCount(Out<u64> out_count) {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.GetCheatCount(*out_count));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheats(Out<u64> out_count, u64 offset,
|
||||
OutArray<CheatEntry, BufferAttr_HipcMapAlias> out_cheats) {
|
||||
LOG_INFO(CheatEngine, "called, offset={}", offset);
|
||||
R_UNLESS(!out_cheats.empty(), ResultCheatNullBuffer);
|
||||
R_RETURN(cheat_process_manager.GetCheats(*out_count, offset, out_cheats));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatById(OutLargeData<CheatEntry, BufferAttr_HipcMapAlias> out_cheat,
|
||||
u32 cheat_id) {
|
||||
LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id);
|
||||
R_RETURN(cheat_process_manager.GetCheatById(out_cheat, cheat_id));
|
||||
}
|
||||
|
||||
Result ICheatInterface::ToggleCheat(u32 cheat_id) {
|
||||
LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id);
|
||||
R_RETURN(cheat_process_manager.ToggleCheat(cheat_id));
|
||||
}
|
||||
|
||||
Result ICheatInterface::AddCheat(
|
||||
Out<u32> out_cheat_id, bool is_enabled,
|
||||
InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition) {
|
||||
LOG_INFO(CheatEngine, "called, is_enabled={}", is_enabled);
|
||||
R_RETURN(cheat_process_manager.AddCheat(*out_cheat_id, is_enabled, *cheat_definition));
|
||||
}
|
||||
|
||||
Result ICheatInterface::RemoveCheat(u32 cheat_id) {
|
||||
LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id);
|
||||
R_RETURN(cheat_process_manager.RemoveCheat(cheat_id));
|
||||
}
|
||||
|
||||
Result ICheatInterface::ReadStaticRegister(Out<u64> out_value, u8 register_index) {
|
||||
LOG_DEBUG(CheatEngine, "called, register_index={}", register_index);
|
||||
R_RETURN(cheat_process_manager.ReadStaticRegister(*out_value, register_index));
|
||||
}
|
||||
|
||||
Result ICheatInterface::WriteStaticRegister(u8 register_index, u64 value) {
|
||||
LOG_DEBUG(CheatEngine, "called, register_index={}, value={}", register_index, value);
|
||||
R_RETURN(cheat_process_manager.WriteStaticRegister(register_index, value));
|
||||
}
|
||||
|
||||
Result ICheatInterface::ResetStaticRegisters() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.ResetStaticRegisters());
|
||||
}
|
||||
|
||||
Result ICheatInterface::SetMasterCheat(
|
||||
InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition) {
|
||||
LOG_INFO(CheatEngine, "called, name={}, num_opcodes={}", cheat_definition->readable_name.data(),
|
||||
cheat_definition->num_opcodes);
|
||||
R_RETURN(cheat_process_manager.SetMasterCheat(*cheat_definition));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetFrozenAddressCount(Out<u64> out_count) {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.GetFrozenAddressCount(*out_count));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetFrozenAddresses(
|
||||
Out<u64> out_count, u64 offset,
|
||||
OutArray<FrozenAddressEntry, BufferAttr_HipcMapAlias> out_frozen_address) {
|
||||
LOG_INFO(CheatEngine, "called, offset={}", offset);
|
||||
R_UNLESS(!out_frozen_address.empty(), ResultCheatNullBuffer);
|
||||
R_RETURN(cheat_process_manager.GetFrozenAddresses(*out_count, offset, out_frozen_address));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetFrozenAddress(Out<FrozenAddressEntry> out_frozen_address_entry,
|
||||
u64 address) {
|
||||
LOG_INFO(CheatEngine, "called, address={}", address);
|
||||
R_RETURN(cheat_process_manager.GetFrozenAddress(*out_frozen_address_entry, address));
|
||||
}
|
||||
|
||||
Result ICheatInterface::EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width) {
|
||||
LOG_INFO(CheatEngine, "called, address={}, width={}", address, width);
|
||||
R_UNLESS(width > 0, ResultFrozenAddressInvalidWidth);
|
||||
R_UNLESS(width <= sizeof(u64), ResultFrozenAddressInvalidWidth);
|
||||
R_UNLESS((width & (width - 1)) == 0, ResultFrozenAddressInvalidWidth);
|
||||
R_RETURN(cheat_process_manager.EnableFrozenAddress(*out_value, address, width));
|
||||
}
|
||||
|
||||
Result ICheatInterface::DisableFrozenAddress(u64 address) {
|
||||
LOG_INFO(CheatEngine, "called, address={}", address);
|
||||
R_RETURN(cheat_process_manager.DisableFrozenAddress(address));
|
||||
}
|
||||
|
||||
void ICheatInterface::InitializeCheatManager() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
}
|
||||
|
||||
Result ICheatInterface::ReadCheatProcessMemoryUnsafe(u64 process_addr, std::span<u8> out_data,
|
||||
size_t size) {
|
||||
LOG_DEBUG(CheatEngine, "called, process_addr={}, size={}", process_addr, size);
|
||||
R_RETURN(cheat_process_manager.ReadCheatProcessMemoryUnsafe(process_addr, &out_data, size));
|
||||
}
|
||||
|
||||
Result ICheatInterface::WriteCheatProcessMemoryUnsafe(u64 process_addr, std::span<const u8> data,
|
||||
size_t size) {
|
||||
LOG_DEBUG(CheatEngine, "called, process_addr={}, size={}", process_addr, size);
|
||||
R_RETURN(cheat_process_manager.WriteCheatProcessMemoryUnsafe(process_addr, &data, size));
|
||||
}
|
||||
|
||||
Result ICheatInterface::PauseCheatProcessUnsafe() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.PauseCheatProcessUnsafe());
|
||||
}
|
||||
|
||||
Result ICheatInterface::ResumeCheatProcessUnsafe() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.ResumeCheatProcessUnsafe());
|
||||
}
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,88 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class KEvent;
|
||||
class KReadableEvent;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Kernel::Svc {
|
||||
struct MemoryInfo;
|
||||
}
|
||||
|
||||
namespace Service::DMNT {
|
||||
struct CheatDefinition;
|
||||
struct CheatEntry;
|
||||
struct CheatProcessMetadata;
|
||||
struct FrozenAddressEntry;
|
||||
class CheatProcessManager;
|
||||
|
||||
class ICheatInterface final : public ServiceFramework<ICheatInterface> {
|
||||
public:
|
||||
explicit ICheatInterface(Core::System& system_, CheatProcessManager& manager);
|
||||
~ICheatInterface() override;
|
||||
|
||||
private:
|
||||
Result HasCheatProcess(Out<bool> out_has_cheat);
|
||||
Result GetCheatProcessEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata);
|
||||
Result ForceOpenCheatProcess();
|
||||
Result PauseCheatProcess();
|
||||
Result ResumeCheatProcess();
|
||||
Result ForceCloseCheatProcess();
|
||||
|
||||
Result GetCheatProcessMappingCount(Out<u64> out_count);
|
||||
Result GetCheatProcessMappings(
|
||||
Out<u64> out_count, u64 offset,
|
||||
OutArray<Kernel::Svc::MemoryInfo, BufferAttr_HipcMapAlias> out_mappings);
|
||||
Result ReadCheatProcessMemory(u64 address, u64 size,
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer);
|
||||
Result WriteCheatProcessMemory(u64 address, u64 size, InBuffer<BufferAttr_HipcMapAlias> buffer);
|
||||
|
||||
Result QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> out_mapping, u64 address);
|
||||
Result GetCheatCount(Out<u64> out_count);
|
||||
Result GetCheats(Out<u64> out_count, u64 offset,
|
||||
OutArray<CheatEntry, BufferAttr_HipcMapAlias> out_cheats);
|
||||
Result GetCheatById(OutLargeData<CheatEntry, BufferAttr_HipcMapAlias> out_cheat, u32 cheat_id);
|
||||
Result ToggleCheat(u32 cheat_id);
|
||||
|
||||
Result AddCheat(Out<u32> out_cheat_id, bool enabled,
|
||||
InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition);
|
||||
Result RemoveCheat(u32 cheat_id);
|
||||
Result ReadStaticRegister(Out<u64> out_value, u8 register_index);
|
||||
Result WriteStaticRegister(u8 register_index, u64 value);
|
||||
Result ResetStaticRegisters();
|
||||
Result SetMasterCheat(InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition);
|
||||
Result GetFrozenAddressCount(Out<u64> out_count);
|
||||
Result GetFrozenAddresses(
|
||||
Out<u64> out_count, u64 offset,
|
||||
OutArray<FrozenAddressEntry, BufferAttr_HipcMapAlias> out_frozen_address);
|
||||
Result GetFrozenAddress(Out<FrozenAddressEntry> out_frozen_address_entry, u64 address);
|
||||
Result EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width);
|
||||
Result DisableFrozenAddress(u64 address);
|
||||
|
||||
private:
|
||||
void InitializeCheatManager();
|
||||
|
||||
Result ReadCheatProcessMemoryUnsafe(u64 process_addr, std::span<u8> out_data, size_t size);
|
||||
Result WriteCheatProcessMemoryUnsafe(u64 process_addr, std::span<const u8> data, size_t size);
|
||||
|
||||
Result PauseCheatProcessUnsafe();
|
||||
Result ResumeCheatProcessUnsafe();
|
||||
|
||||
CheatProcessManager& cheat_process_manager;
|
||||
};
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,121 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "core/hle/service/dmnt/cheat_parser.h"
|
||||
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
CheatParser::CheatParser() {}
|
||||
|
||||
CheatParser::~CheatParser() = default;
|
||||
|
||||
std::vector<CheatEntry> CheatParser::Parse(std::string_view data) const {
|
||||
std::vector<CheatEntry> out(1);
|
||||
std::optional<u64> current_entry;
|
||||
|
||||
for (std::size_t i = 0; i < data.size(); ++i) {
|
||||
if (std::isspace(data[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data[i] == '{') {
|
||||
current_entry = 0;
|
||||
|
||||
if (out[*current_entry].definition.num_opcodes > 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::size_t name_size{};
|
||||
const auto name = ExtractName(name_size, data, i + 1, '}');
|
||||
if (name.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
|
||||
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
|
||||
name.size()));
|
||||
out[*current_entry]
|
||||
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
|
||||
'\0';
|
||||
|
||||
i += name_size + 1;
|
||||
} else if (data[i] == '[') {
|
||||
current_entry = out.size();
|
||||
out.emplace_back();
|
||||
|
||||
std::size_t name_size{};
|
||||
const auto name = ExtractName(name_size, data, i + 1, ']');
|
||||
if (name.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
|
||||
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
|
||||
name.size()));
|
||||
out[*current_entry]
|
||||
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
|
||||
'\0';
|
||||
|
||||
i += name_size + 1;
|
||||
} else if (std::isxdigit(data[i])) {
|
||||
if (!current_entry || out[*current_entry].definition.num_opcodes >=
|
||||
out[*current_entry].definition.opcodes.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto hex = std::string(data.substr(i, 8));
|
||||
if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
|
||||
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
|
||||
value;
|
||||
|
||||
i += 7; // 7 because the for loop will increment by 1 more
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
out[0].enabled = out[0].definition.num_opcodes > 0;
|
||||
out[0].cheat_id = 0;
|
||||
|
||||
for (u32 i = 1; i < out.size(); ++i) {
|
||||
out[i].enabled = out[i].definition.num_opcodes > 0;
|
||||
out[i].cheat_id = i;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string_view CheatParser::ExtractName(std::size_t& out_name_size, std::string_view data,
|
||||
std::size_t start_index, char match) const {
|
||||
auto end_index = start_index;
|
||||
while (data[end_index] != match) {
|
||||
++end_index;
|
||||
if (end_index > data.size()) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
out_name_size = end_index - start_index;
|
||||
|
||||
// Clamp name if it's too big
|
||||
if (out_name_size > sizeof(CheatDefinition::readable_name)) {
|
||||
end_index = start_index + sizeof(CheatDefinition::readable_name);
|
||||
}
|
||||
|
||||
return data.substr(start_index, end_index - start_index);
|
||||
}
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace Service::DMNT {
|
||||
struct CheatEntry;
|
||||
|
||||
class CheatParser final {
|
||||
public:
|
||||
CheatParser();
|
||||
~CheatParser();
|
||||
|
||||
std::vector<CheatEntry> Parse(std::string_view data) const;
|
||||
|
||||
private:
|
||||
std::string_view ExtractName(std::size_t& out_name_size, std::string_view data,
|
||||
std::size_t start_index, char match) const;
|
||||
};
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,599 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/arm/debug.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/dmnt/cheat_process_manager.h"
|
||||
#include "core/hle/service/dmnt/cheat_virtual_machine.h"
|
||||
#include "core/hle/service/dmnt/dmnt_results.h"
|
||||
#include "core/hle/service/hid/hid_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "hid_core/resource_manager.h"
|
||||
#include "hid_core/resources/npad/npad.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
|
||||
|
||||
CheatProcessManager::CheatProcessManager(Core::System& system_)
|
||||
: system{system_}, service_context{system_, "dmnt:cht"}, core_timing{system_.CoreTiming()} {
|
||||
update_event = Core::Timing::CreateEvent("CheatEngine::FrameCallback",
|
||||
[this](s64 time, std::chrono::nanoseconds ns_late)
|
||||
-> std::optional<std::chrono::nanoseconds> {
|
||||
FrameCallback(ns_late);
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < MaxCheatCount; i++) {
|
||||
ResetCheatEntry(i);
|
||||
}
|
||||
|
||||
cheat_vm = std::make_unique<CheatVirtualMachine>(*this);
|
||||
|
||||
cheat_process_event = service_context.CreateEvent("CheatProcessManager::ProcessEvent");
|
||||
unsafe_break_event = service_context.CreateEvent("CheatProcessManager::ProcessEvent");
|
||||
}
|
||||
|
||||
CheatProcessManager::~CheatProcessManager() {
|
||||
service_context.CloseEvent(cheat_process_event);
|
||||
service_context.CloseEvent(unsafe_break_event);
|
||||
core_timing.UnscheduleEvent(update_event);
|
||||
}
|
||||
|
||||
void CheatProcessManager::SetVirtualMachine(std::unique_ptr<CheatVirtualMachine> vm) {
|
||||
if (vm) {
|
||||
cheat_vm = std::move(vm);
|
||||
SetNeedsReloadVm(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool CheatProcessManager::HasActiveCheatProcess() {
|
||||
// Note: This function *MUST* be called only with the cheat lock held.
|
||||
bool has_cheat_process =
|
||||
cheat_process_debug_handle != InvalidHandle &&
|
||||
system.ApplicationProcess()->GetProcessId() == cheat_process_metadata.process_id;
|
||||
|
||||
if (!has_cheat_process) {
|
||||
CloseActiveCheatProcess();
|
||||
}
|
||||
|
||||
return has_cheat_process;
|
||||
}
|
||||
|
||||
void CheatProcessManager::CloseActiveCheatProcess() {
|
||||
if (cheat_process_debug_handle != InvalidHandle) {
|
||||
broken_unsafe = false;
|
||||
unsafe_break_event->Signal();
|
||||
core_timing.UnscheduleEvent(update_event);
|
||||
|
||||
// Close resources.
|
||||
cheat_process_debug_handle = InvalidHandle;
|
||||
|
||||
// Save cheat toggles.
|
||||
if (always_save_cheat_toggles || should_save_cheat_toggles) {
|
||||
// TODO: save cheat toggles
|
||||
should_save_cheat_toggles = false;
|
||||
}
|
||||
|
||||
cheat_process_metadata = {};
|
||||
|
||||
ResetAllCheatEntries();
|
||||
|
||||
{
|
||||
auto it = frozen_addresses_map.begin();
|
||||
while (it != frozen_addresses_map.end()) {
|
||||
it = frozen_addresses_map.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
cheat_process_event->Signal();
|
||||
}
|
||||
}
|
||||
|
||||
Result CheatProcessManager::EnsureCheatProcess() {
|
||||
R_UNLESS(HasActiveCheatProcess(), ResultCheatNotAttached);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void CheatProcessManager::SetNeedsReloadVm(bool reload) {
|
||||
needs_reload_vm = reload;
|
||||
}
|
||||
|
||||
void CheatProcessManager::ResetCheatEntry(size_t i) {
|
||||
if (i < MaxCheatCount) {
|
||||
cheat_entries[i] = {};
|
||||
cheat_entries[i].cheat_id = static_cast<u32>(i);
|
||||
|
||||
SetNeedsReloadVm(true);
|
||||
}
|
||||
}
|
||||
|
||||
void CheatProcessManager::ResetAllCheatEntries() {
|
||||
for (size_t i = 0; i < MaxCheatCount; i++) {
|
||||
ResetCheatEntry(i);
|
||||
}
|
||||
|
||||
cheat_vm->ResetStaticRegisters();
|
||||
}
|
||||
|
||||
CheatEntry* CheatProcessManager::GetCheatEntryById(size_t i) {
|
||||
if (i < MaxCheatCount) {
|
||||
return cheat_entries.data() + i;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CheatEntry* CheatProcessManager::GetCheatEntryByReadableName(const char* readable_name) {
|
||||
for (size_t i = 1; i < MaxCheatCount; i++) {
|
||||
if (std::strncmp(cheat_entries[i].definition.readable_name.data(), readable_name,
|
||||
sizeof(cheat_entries[i].definition.readable_name)) == 0) {
|
||||
return cheat_entries.data() + i;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CheatEntry* CheatProcessManager::GetFreeCheatEntry() {
|
||||
// Check all non-master cheats for availability.
|
||||
for (size_t i = 1; i < MaxCheatCount; i++) {
|
||||
if (cheat_entries[i].definition.num_opcodes == 0) {
|
||||
return cheat_entries.data() + i;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool CheatProcessManager::HasCheatProcess() {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
return HasActiveCheatProcess();
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent& CheatProcessManager::GetCheatProcessEvent() const {
|
||||
return cheat_process_event->GetReadableEvent();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::AttachToApplicationProcess(const std::array<u8, 0x20>& build_id,
|
||||
VAddr main_region_begin,
|
||||
u64 main_region_size) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
{
|
||||
if (this->HasActiveCheatProcess()) {
|
||||
this->CloseActiveCheatProcess();
|
||||
}
|
||||
}
|
||||
|
||||
cheat_process_metadata.process_id = system.ApplicationProcess()->GetProcessId();
|
||||
|
||||
{
|
||||
const auto& page_table = system.ApplicationProcess()->GetPageTable();
|
||||
cheat_process_metadata.program_id = system.GetApplicationProcessProgramID();
|
||||
cheat_process_metadata.heap_extents = {
|
||||
.base = GetInteger(page_table.GetHeapRegionStart()),
|
||||
.size = page_table.GetHeapRegionSize(),
|
||||
};
|
||||
cheat_process_metadata.aslr_extents = {
|
||||
.base = GetInteger(page_table.GetAliasCodeRegionStart()),
|
||||
.size = page_table.GetAliasCodeRegionSize(),
|
||||
};
|
||||
cheat_process_metadata.alias_extents = {
|
||||
.base = GetInteger(page_table.GetAliasRegionStart()),
|
||||
.size = page_table.GetAliasRegionSize(),
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
cheat_process_metadata.main_nso_extents = {
|
||||
.base = main_region_begin,
|
||||
.size = main_region_size,
|
||||
};
|
||||
cheat_process_metadata.main_nso_build_id = build_id;
|
||||
}
|
||||
|
||||
cheat_process_debug_handle = cheat_process_metadata.process_id;
|
||||
|
||||
broken_unsafe = false;
|
||||
unsafe_break_event->Signal();
|
||||
|
||||
core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, update_event);
|
||||
LOG_INFO(CheatEngine, "Cheat engine started");
|
||||
|
||||
// Signal to our fans.
|
||||
cheat_process_event->Signal();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheatProcessMetadata(CheatProcessMetadata& out_metadata) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
out_metadata = cheat_process_metadata;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ForceOpenCheatProcess() {
|
||||
// R_RETURN(AttachToApplicationProcess(false));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::PauseCheatProcess() {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_RETURN(PauseCheatProcessUnsafe());
|
||||
}
|
||||
|
||||
Result CheatProcessManager::PauseCheatProcessUnsafe() {
|
||||
broken_unsafe = true;
|
||||
unsafe_break_event->Clear();
|
||||
if (system.ApplicationProcess()->IsSuspended()) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
R_RETURN(system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Paused));
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ResumeCheatProcess() {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_RETURN(ResumeCheatProcessUnsafe());
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ResumeCheatProcessUnsafe() {
|
||||
broken_unsafe = true;
|
||||
unsafe_break_event->Clear();
|
||||
if (!system.ApplicationProcess()->IsSuspended()) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Runnable);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ForceCloseCheatProcess() {
|
||||
CloseActiveCheatProcess();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheatProcessMappingCount(u64& out_count) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(this->EnsureCheatProcess());
|
||||
|
||||
// TODO: Call svc::QueryDebugProcessMemory
|
||||
|
||||
out_count = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheatProcessMappings(
|
||||
u64& out_count, u64 offset, std::span<Kernel::Svc::MemoryInfo> out_mappings) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(this->EnsureCheatProcess());
|
||||
|
||||
// TODO: Call svc::QueryDebugProcessMemory
|
||||
|
||||
out_count = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ReadCheatProcessMemory(u64 process_address, u64 size,
|
||||
std::span<u8> out_data) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_RETURN(ReadCheatProcessMemoryUnsafe(process_address, &out_data, size));
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ReadCheatProcessMemoryUnsafe(u64 process_address, void* out_data,
|
||||
size_t size) {
|
||||
if (!system.ApplicationMemory().IsValidVirtualAddress(process_address)) {
|
||||
std::memset(out_data, 0, size);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
system.ApplicationMemory().ReadBlock(process_address, out_data, size);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::WriteCheatProcessMemory(u64 process_address, u64 size,
|
||||
std::span<const u8> data) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_RETURN(WriteCheatProcessMemoryUnsafe(process_address, &data, size));
|
||||
}
|
||||
|
||||
Result CheatProcessManager::WriteCheatProcessMemoryUnsafe(u64 process_address, const void* data,
|
||||
size_t size) {
|
||||
if (!system.ApplicationMemory().IsValidVirtualAddress(process_address)) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
if (system.ApplicationMemory().WriteBlock(process_address, data, size)) {
|
||||
Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), process_address, size);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> mapping,
|
||||
u64 address) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(this->EnsureCheatProcess());
|
||||
|
||||
// TODO: Call svc::QueryDebugProcessMemory
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheatCount(u64& out_count) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
out_count = std::count_if(cheat_entries.begin(), cheat_entries.end(),
|
||||
[](const auto& entry) { return entry.definition.num_opcodes != 0; });
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheats(u64& out_count, u64 offset,
|
||||
std::span<CheatEntry> out_cheats) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
size_t count = 0, total_count = 0;
|
||||
for (size_t i = 0; i < MaxCheatCount && count < out_cheats.size(); i++) {
|
||||
if (cheat_entries[i].definition.num_opcodes) {
|
||||
total_count++;
|
||||
if (total_count > offset) {
|
||||
out_cheats[count++] = cheat_entries[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out_count = count;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheatById(CheatEntry* out_cheat, u32 cheat_id) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
const CheatEntry* entry = GetCheatEntryById(cheat_id);
|
||||
R_UNLESS(entry != nullptr, ResultCheatUnknownId);
|
||||
R_UNLESS(entry->definition.num_opcodes != 0, ResultCheatUnknownId);
|
||||
|
||||
*out_cheat = *entry;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ToggleCheat(u32 cheat_id) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
CheatEntry* entry = GetCheatEntryById(cheat_id);
|
||||
R_UNLESS(entry != nullptr, ResultCheatUnknownId);
|
||||
R_UNLESS(entry->definition.num_opcodes != 0, ResultCheatUnknownId);
|
||||
|
||||
R_UNLESS(cheat_id != 0, ResultCheatCannotDisable);
|
||||
|
||||
entry->enabled = !entry->enabled;
|
||||
|
||||
SetNeedsReloadVm(true);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::AddCheat(u32& out_cheat_id, bool enabled,
|
||||
const CheatDefinition& cheat_definition) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
R_UNLESS(cheat_definition.num_opcodes != 0, ResultCheatInvalid);
|
||||
R_UNLESS(cheat_definition.num_opcodes <= cheat_definition.opcodes.size(), ResultCheatInvalid);
|
||||
|
||||
CheatEntry* new_entry = GetFreeCheatEntry();
|
||||
R_UNLESS(new_entry != nullptr, ResultCheatOutOfResource);
|
||||
|
||||
new_entry->enabled = enabled;
|
||||
new_entry->definition = cheat_definition;
|
||||
|
||||
SetNeedsReloadVm(true);
|
||||
|
||||
out_cheat_id = new_entry->cheat_id;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::RemoveCheat(u32 cheat_id) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_UNLESS(cheat_id < MaxCheatCount, ResultCheatUnknownId);
|
||||
|
||||
ResetCheatEntry(cheat_id);
|
||||
SetNeedsReloadVm(true);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ReadStaticRegister(u64& out_value, u64 register_index) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_UNLESS(register_index < CheatVirtualMachine::NumStaticRegisters, ResultCheatInvalid);
|
||||
|
||||
out_value = cheat_vm->GetStaticRegister(register_index);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::WriteStaticRegister(u64 register_index, u64 value) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_UNLESS(register_index < CheatVirtualMachine::NumStaticRegisters, ResultCheatInvalid);
|
||||
|
||||
cheat_vm->SetStaticRegister(register_index, value);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ResetStaticRegisters() {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
cheat_vm->ResetStaticRegisters();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::SetMasterCheat(const CheatDefinition& cheat_definition) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
R_UNLESS(cheat_definition.num_opcodes != 0, ResultCheatInvalid);
|
||||
R_UNLESS(cheat_definition.num_opcodes <= cheat_definition.opcodes.size(), ResultCheatInvalid);
|
||||
|
||||
cheat_entries[0] = {
|
||||
.enabled = true,
|
||||
.definition = cheat_definition,
|
||||
};
|
||||
|
||||
SetNeedsReloadVm(true);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetFrozenAddressCount(u64& out_count) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
out_count = std::distance(frozen_addresses_map.begin(), frozen_addresses_map.end());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetFrozenAddresses(u64& out_count, u64 offset,
|
||||
std::span<FrozenAddressEntry> out_frozen_address) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
u64 total_count = 0, written_count = 0;
|
||||
for (const auto& [address, value] : frozen_addresses_map) {
|
||||
if (written_count >= out_frozen_address.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (offset <= total_count) {
|
||||
out_frozen_address[written_count].address = address;
|
||||
out_frozen_address[written_count].value = value;
|
||||
written_count++;
|
||||
}
|
||||
total_count++;
|
||||
}
|
||||
|
||||
out_count = written_count;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetFrozenAddress(FrozenAddressEntry& out_frozen_address_entry,
|
||||
u64 address) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
const auto it = frozen_addresses_map.find(address);
|
||||
R_UNLESS(it != frozen_addresses_map.end(), ResultFrozenAddressNotFound);
|
||||
|
||||
out_frozen_address_entry = {
|
||||
.address = it->first,
|
||||
.value = it->second,
|
||||
};
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::EnableFrozenAddress(u64& out_value, u64 address, u64 width) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
const auto it = frozen_addresses_map.find(address);
|
||||
R_UNLESS(it == frozen_addresses_map.end(), ResultFrozenAddressAlreadyExists);
|
||||
|
||||
FrozenAddressValue value{};
|
||||
value.width = static_cast<u8>(width);
|
||||
R_TRY(ReadCheatProcessMemoryUnsafe(address, &value.value, width));
|
||||
|
||||
frozen_addresses_map.insert({address, value});
|
||||
out_value = value.value;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::DisableFrozenAddress(u64 address) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
const auto it = frozen_addresses_map.find(address);
|
||||
R_UNLESS(it != frozen_addresses_map.end(), ResultFrozenAddressNotFound);
|
||||
|
||||
frozen_addresses_map.erase(it);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
u64 CheatProcessManager::HidKeysDown() const {
|
||||
const auto hid = system.ServiceManager().GetService<Service::HID::IHidServer>("hid");
|
||||
if (hid == nullptr) {
|
||||
LOG_WARNING(CheatEngine, "Attempted to read input state, but hid is not initialized!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto applet_resource = hid->GetResourceManager();
|
||||
if (applet_resource == nullptr || applet_resource->GetNpad() == nullptr) {
|
||||
LOG_WARNING(CheatEngine,
|
||||
"Attempted to read input state, but applet resource is not initialized!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto press_state = applet_resource->GetNpad()->GetAndResetPressState();
|
||||
return static_cast<u64>(press_state & Core::HID::NpadButton::All);
|
||||
}
|
||||
|
||||
void CheatProcessManager::DebugLog(u8 id, u64 value) const {
|
||||
LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value);
|
||||
}
|
||||
|
||||
void CheatProcessManager::CommandLog(std::string_view data) const {
|
||||
LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}",
|
||||
data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
|
||||
}
|
||||
|
||||
void CheatProcessManager::FrameCallback(std::chrono::nanoseconds ns_late) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
if (cheat_vm == nullptr) {
|
||||
LOG_DEBUG(CheatEngine, "FrameCallback: VM is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (needs_reload_vm) {
|
||||
LOG_INFO(CheatEngine, "Reloading cheat VM with {} entries", cheat_entries.size());
|
||||
|
||||
size_t enabled_count = 0;
|
||||
for (const auto& entry : cheat_entries) {
|
||||
if (entry.enabled && entry.definition.num_opcodes > 0) {
|
||||
enabled_count++;
|
||||
LOG_INFO(CheatEngine, " Cheat '{}': {} opcodes, enabled={}",
|
||||
entry.definition.readable_name.data(),
|
||||
entry.definition.num_opcodes, entry.enabled);
|
||||
}
|
||||
}
|
||||
LOG_INFO(CheatEngine, "Total enabled cheats: {}", enabled_count);
|
||||
|
||||
cheat_vm->LoadProgram(cheat_entries);
|
||||
LOG_INFO(CheatEngine, "Cheat VM loaded, program size: {}", cheat_vm->GetProgramSize());
|
||||
needs_reload_vm = false;
|
||||
}
|
||||
|
||||
if (cheat_vm->GetProgramSize() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
cheat_vm->Execute(cheat_process_metadata);
|
||||
}
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,126 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <span>
|
||||
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
#include "common/intrusive_red_black_tree.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class KEvent;
|
||||
class KReadableEvent;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Kernel::Svc {
|
||||
struct MemoryInfo;
|
||||
}
|
||||
|
||||
namespace Service::DMNT {
|
||||
class CheatVirtualMachine;
|
||||
|
||||
class CheatProcessManager final {
|
||||
public:
|
||||
static constexpr size_t MaxCheatCount = 0x80;
|
||||
static constexpr size_t MaxFrozenAddressCount = 0x80;
|
||||
|
||||
CheatProcessManager(Core::System& system_);
|
||||
~CheatProcessManager();
|
||||
|
||||
void SetVirtualMachine(std::unique_ptr<CheatVirtualMachine> vm);
|
||||
|
||||
bool HasCheatProcess();
|
||||
Kernel::KReadableEvent& GetCheatProcessEvent() const;
|
||||
Result GetCheatProcessMetadata(CheatProcessMetadata& out_metadata);
|
||||
Result AttachToApplicationProcess(const std::array<u8, 0x20>& build_id, VAddr main_region_begin,
|
||||
u64 main_region_size);
|
||||
Result ForceOpenCheatProcess();
|
||||
Result PauseCheatProcess();
|
||||
Result PauseCheatProcessUnsafe();
|
||||
Result ResumeCheatProcess();
|
||||
Result ResumeCheatProcessUnsafe();
|
||||
Result ForceCloseCheatProcess();
|
||||
|
||||
Result GetCheatProcessMappingCount(u64& out_count);
|
||||
Result GetCheatProcessMappings(u64& out_count, u64 offset,
|
||||
std::span<Kernel::Svc::MemoryInfo> out_mappings);
|
||||
Result ReadCheatProcessMemory(u64 process_address, u64 size, std::span<u8> out_data);
|
||||
Result ReadCheatProcessMemoryUnsafe(u64 process_address, void* out_data, size_t size);
|
||||
Result WriteCheatProcessMemory(u64 process_address, u64 size, std::span<const u8> data);
|
||||
Result WriteCheatProcessMemoryUnsafe(u64 process_address, const void* data, size_t size);
|
||||
|
||||
Result QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> mapping, u64 address);
|
||||
Result GetCheatCount(u64& out_count);
|
||||
Result GetCheats(u64& out_count, u64 offset, std::span<CheatEntry> out_cheats);
|
||||
Result GetCheatById(CheatEntry* out_cheat, u32 cheat_id);
|
||||
Result ToggleCheat(u32 cheat_id);
|
||||
|
||||
Result AddCheat(u32& out_cheat_id, bool enabled, const CheatDefinition& cheat_definition);
|
||||
Result RemoveCheat(u32 cheat_id);
|
||||
Result ReadStaticRegister(u64& out_value, u64 register_index);
|
||||
Result WriteStaticRegister(u64 register_index, u64 value);
|
||||
Result ResetStaticRegisters();
|
||||
Result SetMasterCheat(const CheatDefinition& cheat_definition);
|
||||
Result GetFrozenAddressCount(u64& out_count);
|
||||
Result GetFrozenAddresses(u64& out_count, u64 offset,
|
||||
std::span<FrozenAddressEntry> out_frozen_address);
|
||||
Result GetFrozenAddress(FrozenAddressEntry& out_frozen_address_entry, u64 address);
|
||||
Result EnableFrozenAddress(u64& out_value, u64 address, u64 width);
|
||||
Result DisableFrozenAddress(u64 address);
|
||||
|
||||
u64 HidKeysDown() const;
|
||||
void DebugLog(u8 id, u64 value) const;
|
||||
void CommandLog(std::string_view data) const;
|
||||
|
||||
private:
|
||||
bool HasActiveCheatProcess();
|
||||
void CloseActiveCheatProcess();
|
||||
Result EnsureCheatProcess();
|
||||
void SetNeedsReloadVm(bool reload);
|
||||
void ResetCheatEntry(size_t i);
|
||||
void ResetAllCheatEntries();
|
||||
CheatEntry* GetCheatEntryById(size_t i);
|
||||
CheatEntry* GetCheatEntryByReadableName(const char* readable_name);
|
||||
CheatEntry* GetFreeCheatEntry();
|
||||
|
||||
void FrameCallback(std::chrono::nanoseconds ns_late);
|
||||
|
||||
static constexpr u64 InvalidHandle = 0;
|
||||
|
||||
mutable std::mutex cheat_lock;
|
||||
Kernel::KEvent* unsafe_break_event;
|
||||
|
||||
Kernel::KEvent* cheat_process_event;
|
||||
u64 cheat_process_debug_handle = InvalidHandle;
|
||||
CheatProcessMetadata cheat_process_metadata = {};
|
||||
|
||||
bool broken_unsafe = false;
|
||||
bool needs_reload_vm = false;
|
||||
std::unique_ptr<CheatVirtualMachine> cheat_vm;
|
||||
|
||||
bool enable_cheats_by_default = true;
|
||||
bool always_save_cheat_toggles = false;
|
||||
bool should_save_cheat_toggles = false;
|
||||
std::array<CheatEntry, MaxCheatCount> cheat_entries = {};
|
||||
// TODO: Replace with IntrusiveRedBlackTree
|
||||
std::map<u64, FrozenAddressValue> frozen_addresses_map = {};
|
||||
|
||||
Core::System& system;
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
std::shared_ptr<Core::Timing::EventType> update_event;
|
||||
Core::Timing::CoreTiming& core_timing;
|
||||
};
|
||||
} // namespace Service::DMNT
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,323 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <variant>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
class CheatProcessManager;
|
||||
|
||||
enum class CheatVmOpcodeType : u32 {
|
||||
StoreStatic = 0,
|
||||
BeginConditionalBlock = 1,
|
||||
EndConditionalBlock = 2,
|
||||
ControlLoop = 3,
|
||||
LoadRegisterStatic = 4,
|
||||
LoadRegisterMemory = 5,
|
||||
StoreStaticToAddress = 6,
|
||||
PerformArithmeticStatic = 7,
|
||||
BeginKeypressConditionalBlock = 8,
|
||||
|
||||
// These are not implemented by Gateway's VM.
|
||||
PerformArithmeticRegister = 9,
|
||||
StoreRegisterToAddress = 10,
|
||||
Reserved11 = 11,
|
||||
|
||||
// This is a meta entry, and not a real opcode.
|
||||
// This is to facilitate multi-nybble instruction decoding.
|
||||
ExtendedWidth = 12,
|
||||
|
||||
// Extended width opcodes.
|
||||
BeginRegisterConditionalBlock = 0xC0,
|
||||
SaveRestoreRegister = 0xC1,
|
||||
SaveRestoreRegisterMask = 0xC2,
|
||||
ReadWriteStaticRegister = 0xC3,
|
||||
|
||||
// This is a meta entry, and not a real opcode.
|
||||
// This is to facilitate multi-nybble instruction decoding.
|
||||
DoubleExtendedWidth = 0xF0,
|
||||
|
||||
// Double-extended width opcodes.
|
||||
PauseProcess = 0xFF0,
|
||||
ResumeProcess = 0xFF1,
|
||||
DebugLog = 0xFFF,
|
||||
};
|
||||
|
||||
enum class MemoryAccessType : u32 {
|
||||
MainNso = 0,
|
||||
Heap = 1,
|
||||
Alias = 2,
|
||||
Aslr = 3,
|
||||
};
|
||||
|
||||
enum class ConditionalComparisonType : u32 {
|
||||
GT = 1,
|
||||
GE = 2,
|
||||
LT = 3,
|
||||
LE = 4,
|
||||
EQ = 5,
|
||||
NE = 6,
|
||||
};
|
||||
|
||||
enum class RegisterArithmeticType : u32 {
|
||||
Addition = 0,
|
||||
Subtraction = 1,
|
||||
Multiplication = 2,
|
||||
LeftShift = 3,
|
||||
RightShift = 4,
|
||||
|
||||
// These are not supported by Gateway's VM.
|
||||
LogicalAnd = 5,
|
||||
LogicalOr = 6,
|
||||
LogicalNot = 7,
|
||||
LogicalXor = 8,
|
||||
|
||||
None = 9,
|
||||
};
|
||||
|
||||
enum class StoreRegisterOffsetType : u32 {
|
||||
None = 0,
|
||||
Reg = 1,
|
||||
Imm = 2,
|
||||
MemReg = 3,
|
||||
MemImm = 4,
|
||||
MemImmReg = 5,
|
||||
};
|
||||
|
||||
enum class CompareRegisterValueType : u32 {
|
||||
MemoryRelAddr = 0,
|
||||
MemoryOfsReg = 1,
|
||||
RegisterRelAddr = 2,
|
||||
RegisterOfsReg = 3,
|
||||
StaticValue = 4,
|
||||
OtherRegister = 5,
|
||||
};
|
||||
|
||||
enum class SaveRestoreRegisterOpType : u32 {
|
||||
Restore = 0,
|
||||
Save = 1,
|
||||
ClearSaved = 2,
|
||||
ClearRegs = 3,
|
||||
};
|
||||
|
||||
enum class DebugLogValueType : u32 {
|
||||
MemoryRelAddr = 0,
|
||||
MemoryOfsReg = 1,
|
||||
RegisterRelAddr = 2,
|
||||
RegisterOfsReg = 3,
|
||||
RegisterValue = 4,
|
||||
};
|
||||
|
||||
union VmInt {
|
||||
u8 bit8;
|
||||
u16 bit16;
|
||||
u32 bit32;
|
||||
u64 bit64;
|
||||
};
|
||||
|
||||
struct StoreStaticOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 offset_register{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct BeginConditionalOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
ConditionalComparisonType cond_type{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct EndConditionalOpcode {
|
||||
bool is_else;
|
||||
};
|
||||
|
||||
struct ControlLoopOpcode {
|
||||
bool start_loop{};
|
||||
u32 reg_index{};
|
||||
u32 num_iters{};
|
||||
};
|
||||
|
||||
struct LoadRegisterStaticOpcode {
|
||||
u32 reg_index{};
|
||||
u64 value{};
|
||||
};
|
||||
|
||||
struct LoadRegisterMemoryOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 reg_index{};
|
||||
bool load_from_reg{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct StoreStaticToAddressOpcode {
|
||||
u32 bit_width{};
|
||||
u32 reg_index{};
|
||||
bool increment_reg{};
|
||||
bool add_offset_reg{};
|
||||
u32 offset_reg_index{};
|
||||
u64 value{};
|
||||
};
|
||||
|
||||
struct PerformArithmeticStaticOpcode {
|
||||
u32 bit_width{};
|
||||
u32 reg_index{};
|
||||
RegisterArithmeticType math_type{};
|
||||
u32 value{};
|
||||
};
|
||||
|
||||
struct BeginKeypressConditionalOpcode {
|
||||
u32 key_mask{};
|
||||
};
|
||||
|
||||
struct PerformArithmeticRegisterOpcode {
|
||||
u32 bit_width{};
|
||||
RegisterArithmeticType math_type{};
|
||||
u32 dst_reg_index{};
|
||||
u32 src_reg_1_index{};
|
||||
u32 src_reg_2_index{};
|
||||
bool has_immediate{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct StoreRegisterToAddressOpcode {
|
||||
u32 bit_width{};
|
||||
u32 str_reg_index{};
|
||||
u32 addr_reg_index{};
|
||||
bool increment_reg{};
|
||||
StoreRegisterOffsetType ofs_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct BeginRegisterConditionalOpcode {
|
||||
u32 bit_width{};
|
||||
ConditionalComparisonType cond_type{};
|
||||
u32 val_reg_index{};
|
||||
CompareRegisterValueType comp_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 addr_reg_index{};
|
||||
u32 other_reg_index{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterOpcode {
|
||||
u32 dst_index{};
|
||||
u32 src_index{};
|
||||
SaveRestoreRegisterOpType op_type{};
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterMaskOpcode {
|
||||
SaveRestoreRegisterOpType op_type{};
|
||||
std::array<bool, 0x10> should_operate{};
|
||||
};
|
||||
|
||||
struct ReadWriteStaticRegisterOpcode {
|
||||
u32 static_idx{};
|
||||
u32 idx{};
|
||||
};
|
||||
|
||||
struct PauseProcessOpcode {};
|
||||
|
||||
struct ResumeProcessOpcode {};
|
||||
|
||||
struct DebugLogOpcode {
|
||||
u32 bit_width{};
|
||||
u32 log_id{};
|
||||
DebugLogValueType val_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 addr_reg_index{};
|
||||
u32 val_reg_index{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct UnrecognizedInstruction {
|
||||
CheatVmOpcodeType opcode{};
|
||||
};
|
||||
|
||||
struct CheatVmOpcode {
|
||||
bool begin_conditional_block{};
|
||||
std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
|
||||
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
|
||||
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
|
||||
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
|
||||
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
|
||||
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
|
||||
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction>
|
||||
opcode{};
|
||||
};
|
||||
|
||||
class CheatVirtualMachine {
|
||||
public:
|
||||
static constexpr std::size_t MaximumProgramOpcodeCount = 0x400;
|
||||
static constexpr std::size_t NumRegisters = 0x10;
|
||||
static constexpr std::size_t NumReadableStaticRegisters = 0x80;
|
||||
static constexpr std::size_t NumWritableStaticRegisters = 0x80;
|
||||
static constexpr std::size_t NumStaticRegisters =
|
||||
NumReadableStaticRegisters + NumWritableStaticRegisters;
|
||||
|
||||
explicit CheatVirtualMachine(CheatProcessManager& cheat_manager);
|
||||
~CheatVirtualMachine();
|
||||
|
||||
std::size_t GetProgramSize() const {
|
||||
return this->num_opcodes;
|
||||
}
|
||||
|
||||
bool LoadProgram(std::span<const CheatEntry> cheats);
|
||||
void Execute(const CheatProcessMetadata& metadata);
|
||||
|
||||
u64 GetStaticRegister(std::size_t register_index) const {
|
||||
return static_registers[register_index];
|
||||
}
|
||||
|
||||
void SetStaticRegister(std::size_t register_index, u64 value) {
|
||||
static_registers[register_index] = value;
|
||||
}
|
||||
|
||||
void ResetStaticRegisters() {
|
||||
static_registers = {};
|
||||
}
|
||||
|
||||
private:
|
||||
bool DecodeNextOpcode(CheatVmOpcode& out);
|
||||
void SkipConditionalBlock(bool is_if);
|
||||
void ResetState();
|
||||
|
||||
// For implementing the DebugLog opcode.
|
||||
void DebugLog(u32 log_id, u64 value) const;
|
||||
|
||||
void LogOpcode(const CheatVmOpcode& opcode) const;
|
||||
|
||||
static u64 GetVmInt(VmInt value, u32 bit_width);
|
||||
static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata,
|
||||
MemoryAccessType mem_type, u64 rel_address);
|
||||
|
||||
CheatProcessManager& manager;
|
||||
|
||||
std::size_t num_opcodes = 0;
|
||||
std::size_t instruction_ptr = 0;
|
||||
std::size_t condition_depth = 0;
|
||||
bool decode_success = false;
|
||||
std::array<u32, MaximumProgramOpcodeCount> program{};
|
||||
std::array<u64, NumRegisters> registers{};
|
||||
std::array<u64, NumRegisters> saved_values{};
|
||||
std::array<u64, NumStaticRegisters> static_registers{};
|
||||
std::array<std::size_t, NumRegisters> loop_tops{};
|
||||
};
|
||||
}// namespace Service::DMNT
|
||||
@@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/dmnt/cheat_interface.h"
|
||||
#include "core/hle/service/dmnt/cheat_process_manager.h"
|
||||
#include "core/hle/service/dmnt/cheat_virtual_machine.h"
|
||||
#include "core/hle/service/dmnt/dmnt.h"
|
||||
#include "core/hle/service/server_manager.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
auto& cheat_manager = system.GetCheatManager();
|
||||
auto cheat_vm = std::make_unique<CheatVirtualMachine>(cheat_manager);
|
||||
cheat_manager.SetVirtualMachine(std::move(cheat_vm));
|
||||
|
||||
server_manager->RegisterNamedService("dmnt:cht",
|
||||
std::make_shared<ICheatInterface>(system, cheat_manager));
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,15 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
};
|
||||
|
||||
namespace Service::DMNT {
|
||||
void LoopProcess(Core::System& system);
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
constexpr Result ResultDebuggingDisabled(ErrorModule::DMNT, 2);
|
||||
|
||||
constexpr Result ResultCheatNotAttached(ErrorModule::DMNT, 6500);
|
||||
constexpr Result ResultCheatNullBuffer(ErrorModule::DMNT, 6501);
|
||||
constexpr Result ResultCheatInvalidBuffer(ErrorModule::DMNT, 6502);
|
||||
constexpr Result ResultCheatUnknownId(ErrorModule::DMNT, 6503);
|
||||
constexpr Result ResultCheatOutOfResource(ErrorModule::DMNT, 6504);
|
||||
constexpr Result ResultCheatInvalid(ErrorModule::DMNT, 6505);
|
||||
constexpr Result ResultCheatCannotDisable(ErrorModule::DMNT, 6506);
|
||||
constexpr Result ResultFrozenAddressInvalidWidth(ErrorModule::DMNT, 6600);
|
||||
constexpr Result ResultFrozenAddressAlreadyExists(ErrorModule::DMNT, 6601);
|
||||
constexpr Result ResultFrozenAddressNotFound(ErrorModule::DMNT, 6602);
|
||||
constexpr Result ResultFrozenAddressOutOfResource(ErrorModule::DMNT, 6603);
|
||||
constexpr Result ResultVirtualMachineInvalidConditionDepth(ErrorModule::DMNT, 6700);
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,55 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
struct MemoryRegionExtents {
|
||||
u64 base{};
|
||||
u64 size{};
|
||||
};
|
||||
static_assert(sizeof(MemoryRegionExtents) == 0x10, "MemoryRegionExtents is an invalid size");
|
||||
|
||||
struct CheatProcessMetadata {
|
||||
u64 process_id{};
|
||||
u64 program_id{};
|
||||
MemoryRegionExtents main_nso_extents{};
|
||||
MemoryRegionExtents heap_extents{};
|
||||
MemoryRegionExtents alias_extents{};
|
||||
MemoryRegionExtents aslr_extents{};
|
||||
std::array<u8, 0x20> main_nso_build_id{};
|
||||
};
|
||||
static_assert(sizeof(CheatProcessMetadata) == 0x70, "CheatProcessMetadata is an invalid size");
|
||||
|
||||
struct CheatDefinition {
|
||||
std::array<char, 0x40> readable_name;
|
||||
u32 num_opcodes;
|
||||
std::array<u32, 0x100> opcodes;
|
||||
};
|
||||
static_assert(sizeof(CheatDefinition) == 0x444, "CheatDefinition is an invalid size");
|
||||
|
||||
struct CheatEntry {
|
||||
bool enabled;
|
||||
u32 cheat_id;
|
||||
CheatDefinition definition;
|
||||
};
|
||||
static_assert(sizeof(CheatEntry) == 0x44C, "CheatEntry is an invalid size");
|
||||
static_assert(std::is_trivial_v<CheatEntry>, "CheatEntry type must be trivially copyable.");
|
||||
|
||||
struct FrozenAddressValue {
|
||||
u64 value;
|
||||
u8 width;
|
||||
};
|
||||
static_assert(sizeof(FrozenAddressValue) == 0x10, "FrozenAddressValue is an invalid size");
|
||||
|
||||
struct FrozenAddressEntry {
|
||||
u64 address;
|
||||
FrozenAddressValue value;
|
||||
};
|
||||
static_assert(sizeof(FrozenAddressEntry) == 0x18, "FrozenAddressEntry is an invalid size");
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -70,7 +67,7 @@ public:
|
||||
{20701, &IFriendService::GetPlayHistoryStatistics, "GetPlayHistoryStatistics"},
|
||||
{20800, &IFriendService::LoadUserSetting, "LoadUserSetting"},
|
||||
{20801, nullptr, "SyncUserSetting"},
|
||||
{20900, &IFriendService::RequestListSummaryOverlayNotification, "RequestListSummaryOverlayNotification"},
|
||||
{20900, nullptr, "RequestListSummaryOverlayNotification"},
|
||||
{21000, nullptr, "GetExternalApplicationCatalog"},
|
||||
{22000, nullptr, "GetReceivedFriendInvitationList"},
|
||||
{22001, nullptr, "GetReceivedFriendInvitationDetailedInfo"},
|
||||
@@ -320,13 +317,6 @@ private:
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void RequestListSummaryOverlayNotification(HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_Friend, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void GetReceivedFriendInvitationCountCache(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Friend, "(STUBBED) called, check in out");
|
||||
|
||||
|
||||
@@ -10,12 +10,9 @@
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/ns/application_manager_interface.h"
|
||||
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/hle/service/ns/content_management_interface.h"
|
||||
#include "core/hle/service/ns/read_only_application_control_data_interface.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "frontend_common/firmware_manager.h"
|
||||
|
||||
namespace Service::NS {
|
||||
|
||||
@@ -56,7 +53,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
|
||||
{37, nullptr, "ListRequiredVersion"},
|
||||
{38, D<&IApplicationManagerInterface::CheckApplicationLaunchVersion>, "CheckApplicationLaunchVersion"},
|
||||
{39, nullptr, "CheckApplicationLaunchRights"},
|
||||
{40, D<&IApplicationManagerInterface::GetApplicationLogoData>, "GetApplicationLogoData"},
|
||||
{40, nullptr, "GetApplicationLogoData"},
|
||||
{41, nullptr, "CalculateApplicationDownloadRequiredSize"},
|
||||
{42, nullptr, "CleanupSdCard"},
|
||||
{43, D<&IApplicationManagerInterface::CheckSdCardMountStatus>, "CheckSdCardMountStatus"},
|
||||
@@ -333,53 +330,6 @@ Result IApplicationManagerInterface::UnregisterNetworkServiceAccountWithUserSave
|
||||
LOG_DEBUG(Service_NS, "called, user_id={}", user_id.FormattedString());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::GetApplicationLogoData(
|
||||
Out<s64> out_size, OutBuffer<BufferAttr_HipcMapAlias> out_buffer, u64 application_id,
|
||||
InBuffer<BufferAttr_HipcMapAlias> logo_path_buffer) {
|
||||
const std::string path_view{reinterpret_cast<const char*>(logo_path_buffer.data()),
|
||||
logo_path_buffer.size()};
|
||||
|
||||
// Find null terminator and trim the path
|
||||
auto null_pos = path_view.find('\0');
|
||||
std::string path = (null_pos != std::string::npos) ? path_view.substr(0, null_pos) : path_view;
|
||||
|
||||
LOG_DEBUG(Service_NS, "called, application_id={:016X}, logo_path={}", application_id, path);
|
||||
|
||||
auto& content_provider = system.GetContentProviderUnion();
|
||||
|
||||
auto program = content_provider.GetEntry(application_id, FileSys::ContentRecordType::Program);
|
||||
if (!program) {
|
||||
LOG_WARNING(Service_NS, "Application program not found for id={:016X}", application_id);
|
||||
R_RETURN(ResultUnknown);
|
||||
}
|
||||
|
||||
const auto logo_dir = program->GetLogoPartition();
|
||||
if (!logo_dir) {
|
||||
LOG_WARNING(Service_NS, "Logo partition not found for id={:016X}", application_id);
|
||||
R_RETURN(ResultUnknown);
|
||||
}
|
||||
|
||||
const auto file = logo_dir->GetFile(path);
|
||||
if (!file) {
|
||||
LOG_WARNING(Service_NS, "Logo path not found: {} for id={:016X}", path,
|
||||
application_id);
|
||||
R_RETURN(ResultUnknown);
|
||||
}
|
||||
|
||||
const auto data = file->ReadAllBytes();
|
||||
if (data.size() > out_buffer.size()) {
|
||||
LOG_WARNING(Service_NS, "Logo buffer too small: have={}, need={}", out_buffer.size(),
|
||||
data.size());
|
||||
R_RETURN(ResultUnknown);
|
||||
}
|
||||
|
||||
std::memcpy(out_buffer.data(), data.data(), data.size());
|
||||
*out_size = static_cast<s64>(data.size());
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::GetApplicationControlData(
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_actual_size,
|
||||
ApplicationControlSource application_control_source, u64 application_id) {
|
||||
@@ -479,13 +429,13 @@ Result IApplicationManagerInterface::IsAnyApplicationEntityInstalled(
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::GetApplicationViewDeprecated(
|
||||
OutArray<ApplicationViewV19, BufferAttr_HipcMapAlias> out_application_views,
|
||||
OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
|
||||
InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
|
||||
const auto size = (std::min)(out_application_views.size(), application_ids.size());
|
||||
LOG_WARNING(Service_NS, "(STUBBED) called, size={}", application_ids.size());
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
ApplicationViewV19 view{};
|
||||
ApplicationView view{};
|
||||
view.application_id = application_ids[i];
|
||||
view.version = 0x70000;
|
||||
view.flags = 0x401f17;
|
||||
@@ -497,53 +447,34 @@ Result IApplicationManagerInterface::GetApplicationViewDeprecated(
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::GetApplicationViewWithPromotionInfo(
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
|
||||
Out<u32> out_count,
|
||||
InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
|
||||
const auto requested = application_ids.size();
|
||||
LOG_WARNING(Service_NS, "called, size={}", requested);
|
||||
|
||||
const auto fw_pair = FirmwareManager::GetFirmwareVersion(system);
|
||||
const bool is_fw20 = fw_pair.first.major >= 20;
|
||||
|
||||
const size_t per_entry_size = is_fw20 ? (sizeof(ApplicationViewV20) + sizeof(PromotionInfo))
|
||||
: (sizeof(ApplicationViewV19) + sizeof(PromotionInfo));
|
||||
const size_t capacity_entries = out_buffer.size() / per_entry_size;
|
||||
const size_t to_write_entries = (std::min)(requested, capacity_entries);
|
||||
|
||||
u8* dst = out_buffer.data();
|
||||
for (size_t i = 0; i < to_write_entries; ++i) {
|
||||
ApplicationViewWithPromotionData data{};
|
||||
data.view.application_id = application_ids[i];
|
||||
data.view.version = 0x70000;
|
||||
data.view.unk = 0;
|
||||
data.view.flags = 0x401f17;
|
||||
data.view.download_state = {};
|
||||
data.view.download_progress = {};
|
||||
data.promotion = {};
|
||||
|
||||
const size_t written = WriteApplicationViewWithPromotion(dst, out_buffer.size() - (dst - out_buffer.data()), data, is_fw20);
|
||||
if (written == 0) {
|
||||
break;
|
||||
}
|
||||
dst += written;
|
||||
}
|
||||
|
||||
*out_count = static_cast<u32>(dst - out_buffer.data()) / static_cast<u32>(per_entry_size);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::GetApplicationView(
|
||||
OutArray<ApplicationViewV20, BufferAttr_HipcMapAlias> out_application_views,
|
||||
OutArray<ApplicationViewWithPromotionInfo, BufferAttr_HipcMapAlias> out_application_views,
|
||||
InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
|
||||
const auto size = (std::min)(out_application_views.size(), application_ids.size());
|
||||
LOG_WARNING(Service_NS, "(STUBBED) called, size={}", application_ids.size());
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
ApplicationViewV20 view{};
|
||||
ApplicationViewWithPromotionInfo view{};
|
||||
view.view.application_id = application_ids[i];
|
||||
view.view.version = 0x70000;
|
||||
view.view.flags = 0x401f17;
|
||||
view.promotion = {};
|
||||
|
||||
out_application_views[i] = view;
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::GetApplicationView(
|
||||
OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
|
||||
InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
|
||||
const auto size = (std::min)(out_application_views.size(), application_ids.size());
|
||||
LOG_WARNING(Service_NS, "(STUBBED) called, size={}", application_ids.size());
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
ApplicationView view{};
|
||||
view.application_id = application_ids[i];
|
||||
view.version = 0x70000;
|
||||
view.unk = 0;
|
||||
view.flags = 0x401f17;
|
||||
|
||||
out_application_views[i] = view;
|
||||
|
||||
@@ -36,14 +36,13 @@ public:
|
||||
Result IsGameCardApplicationRunning(Out<bool> out_is_running);
|
||||
Result IsAnyApplicationEntityInstalled(Out<bool> out_is_any_application_entity_installed);
|
||||
Result GetApplicationViewDeprecated(
|
||||
OutArray<ApplicationViewV19, BufferAttr_HipcMapAlias> out_application_views,
|
||||
OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
|
||||
InArray<u64, BufferAttr_HipcMapAlias> application_ids);
|
||||
Result GetApplicationViewWithPromotionInfo(
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
|
||||
Out<u32> out_count,
|
||||
OutArray<ApplicationViewWithPromotionInfo, BufferAttr_HipcMapAlias> out_application_views,
|
||||
InArray<u64, BufferAttr_HipcMapAlias> application_ids);
|
||||
Result GetApplicationView(
|
||||
OutArray<ApplicationViewV20, BufferAttr_HipcMapAlias> out_application_views,
|
||||
OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
|
||||
InArray<u64, BufferAttr_HipcMapAlias> application_ids);
|
||||
Result GetApplicationRightsOnClient(
|
||||
OutArray<ApplicationRightsOnClient, BufferAttr_HipcMapAlias> out_rights, Out<u32> out_count,
|
||||
@@ -60,10 +59,6 @@ public:
|
||||
u64 application_id);
|
||||
Result CheckApplicationLaunchVersion(u64 application_id);
|
||||
Result GetApplicationTerminateResult(Out<Result> out_result, u64 application_id);
|
||||
Result GetApplicationLogoData(Out<s64> out_size,
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
|
||||
u64 application_id,
|
||||
InBuffer<BufferAttr_HipcMapAlias> logo_path_buffer);
|
||||
Result Unknown4022(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result Unknown4023(Out<u64> out_result);
|
||||
Result Unknown4053();
|
||||
|
||||
@@ -55,6 +55,16 @@ struct ApplicationDownloadState {
|
||||
static_assert(sizeof(ApplicationDownloadState) == 0x20,
|
||||
"ApplicationDownloadState has incorrect size.");
|
||||
|
||||
/// ApplicationView
|
||||
struct ApplicationView {
|
||||
u64 application_id; ///< ApplicationId.
|
||||
u32 version; ///< Application Version(?)
|
||||
u32 flags; ///< Flags.
|
||||
ApplicationDownloadState download_state; ///< \ref ApplicationDownloadState
|
||||
ApplicationDownloadState download_progress; ///< \ref ApplicationDownloadState
|
||||
};
|
||||
static_assert(sizeof(ApplicationView) == 0x50, "ApplicationView has incorrect size.");
|
||||
|
||||
struct ApplicationRightsOnClient {
|
||||
u64 application_id;
|
||||
Common::UUID uid;
|
||||
@@ -78,74 +88,14 @@ struct PromotionInfo {
|
||||
};
|
||||
static_assert(sizeof(PromotionInfo) == 0x20, "PromotionInfo has incorrect size.");
|
||||
|
||||
struct ApplicationViewV19 {
|
||||
u64 application_id;
|
||||
u32 version;
|
||||
u32 flags;
|
||||
ApplicationDownloadState download_state;
|
||||
ApplicationDownloadState download_progress;
|
||||
// TODO(Maufeat): NsApplicationViewWithPromotionInfo is on SDK20+ 0x78 bytes
|
||||
/// NsApplicationViewWithPromotionInfo
|
||||
struct ApplicationViewWithPromotionInfo {
|
||||
ApplicationView view; ///< \ref NsApplicationView
|
||||
PromotionInfo promotion; ///< \ref NsPromotionInfo
|
||||
};
|
||||
static_assert(sizeof(ApplicationViewV19) == 0x50, "ApplicationViewV19 has incorrect size.");
|
||||
|
||||
struct ApplicationViewV20 {
|
||||
u64 application_id;
|
||||
u32 version;
|
||||
u32 flags;
|
||||
u32 unk;
|
||||
ApplicationDownloadState download_state;
|
||||
ApplicationDownloadState download_progress;
|
||||
};
|
||||
static_assert(sizeof(ApplicationViewV20) == 0x58, "ApplicationViewV20 has incorrect size.");
|
||||
|
||||
struct ApplicationViewData {
|
||||
u64 application_id{};
|
||||
u32 version{};
|
||||
u32 flags{};
|
||||
u32 unk{};
|
||||
ApplicationDownloadState download_state{};
|
||||
ApplicationDownloadState download_progress{};
|
||||
};
|
||||
|
||||
inline size_t WriteApplicationView(void* dst, size_t dst_size, const ApplicationViewData& data,
|
||||
bool is_fw20) {
|
||||
if (is_fw20) {
|
||||
if (dst_size < sizeof(ApplicationViewV20)) return 0;
|
||||
auto* out = reinterpret_cast<ApplicationViewV20*>(dst);
|
||||
out->application_id = data.application_id;
|
||||
out->version = data.version;
|
||||
out->flags = data.flags;
|
||||
out->unk = data.unk;
|
||||
out->download_state = data.download_state;
|
||||
out->download_progress = data.download_progress;
|
||||
return sizeof(ApplicationViewV20);
|
||||
} else {
|
||||
if (dst_size < sizeof(ApplicationViewV19)) return 0;
|
||||
auto* out = reinterpret_cast<ApplicationViewV19*>(dst);
|
||||
out->application_id = data.application_id;
|
||||
out->version = data.version;
|
||||
out->flags = data.flags;
|
||||
out->download_state = data.download_state;
|
||||
out->download_progress = data.download_progress;
|
||||
return sizeof(ApplicationViewV19);
|
||||
}
|
||||
}
|
||||
|
||||
struct ApplicationViewWithPromotionData {
|
||||
ApplicationViewData view;
|
||||
PromotionInfo promotion;
|
||||
};
|
||||
|
||||
inline size_t WriteApplicationViewWithPromotion(void* dst, size_t dst_size,
|
||||
const ApplicationViewWithPromotionData& data,
|
||||
bool sdk20_plus) {
|
||||
const size_t view_written = WriteApplicationView(dst, dst_size, data.view, sdk20_plus);
|
||||
if (view_written == 0) return 0;
|
||||
const size_t remaining = dst_size - view_written;
|
||||
if (remaining < sizeof(PromotionInfo)) return 0;
|
||||
auto* promo_dst = reinterpret_cast<u8*>(dst) + view_written;
|
||||
std::memcpy(promo_dst, &data.promotion, sizeof(PromotionInfo));
|
||||
return view_written + sizeof(PromotionInfo);
|
||||
}
|
||||
static_assert(sizeof(ApplicationViewWithPromotionInfo) == 0x70,
|
||||
"ApplicationViewWithPromotionInfo has incorrect size.");
|
||||
|
||||
struct ApplicationOccupiedSizeEntity {
|
||||
FileSys::StorageId storage_id;
|
||||
@@ -179,9 +129,4 @@ struct ApplicationDisplayData {
|
||||
};
|
||||
static_assert(sizeof(ApplicationDisplayData) == 0x300, "ApplicationDisplayData has incorrect size.");
|
||||
|
||||
struct LogoPath {
|
||||
std::array<char, 0x300> path;
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<LogoPath>, "LogoPath must be trivially copyable.");
|
||||
|
||||
} // namespace Service::NS
|
||||
|
||||
@@ -4,12 +4,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <stb_image.h>
|
||||
#include <stb_image_resize.h>
|
||||
#include <stb_image_write.h>
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
@@ -23,49 +17,6 @@
|
||||
|
||||
namespace Service::NS {
|
||||
|
||||
namespace {
|
||||
|
||||
void JPGToMemory(void* context, void* data, int size) {
|
||||
auto* buffer = static_cast<std::vector<u8>*>(context);
|
||||
const auto* char_data = static_cast<const u8*>(data);
|
||||
buffer->insert(buffer->end(), char_data, char_data + size);
|
||||
}
|
||||
|
||||
void SanitizeJPEGImageSize(std::vector<u8>& image) {
|
||||
constexpr std::size_t max_jpeg_image_size = 0x20000;
|
||||
constexpr int profile_dimensions = 174; // for grid view thingy
|
||||
int original_width, original_height, color_channels;
|
||||
|
||||
auto* plain_image =
|
||||
stbi_load_from_memory(image.data(), static_cast<int>(image.size()), &original_width,
|
||||
&original_height, &color_channels, STBI_rgb);
|
||||
|
||||
if (plain_image == nullptr) {
|
||||
LOG_ERROR(Service_NS, "Failed to load JPEG for sanitization.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (original_width != profile_dimensions || original_height != profile_dimensions) {
|
||||
std::vector<u8> out_image(profile_dimensions * profile_dimensions * STBI_rgb);
|
||||
stbir_resize_uint8_srgb(plain_image, original_width, original_height, 0, out_image.data(),
|
||||
profile_dimensions, profile_dimensions, 0, STBI_rgb, 0,
|
||||
STBIR_FILTER_BOX);
|
||||
image.clear();
|
||||
if (!stbi_write_jpg_to_func(JPGToMemory, &image, profile_dimensions, profile_dimensions,
|
||||
STBI_rgb, out_image.data(), 90)) {
|
||||
LOG_ERROR(Service_NS, "Failed to resize the user provided image.");
|
||||
}
|
||||
}
|
||||
|
||||
stbi_image_free(plain_image);
|
||||
|
||||
if (image.size() > max_jpeg_image_size) {
|
||||
image.resize(max_jpeg_image_size);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterface(
|
||||
Core::System& system_)
|
||||
: ServiceFramework{system_, "IReadOnlyApplicationControlDataInterface"} {
|
||||
@@ -76,8 +27,8 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
|
||||
{2, D<&IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"},
|
||||
{3, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
|
||||
{4, nullptr, "SelectApplicationDesiredLanguage"},
|
||||
{5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData2>, "GetApplicationControlDataWithoutIcon"},
|
||||
{19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlDataWithoutIcon3"},
|
||||
{5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlDataWithoutIcon>, "GetApplicationControlDataWithoutIcon"},
|
||||
{19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlDataWithoutIcon>, "GetApplicationControlDataWithoutIcon"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -174,77 +125,11 @@ Result IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLan
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData2(
|
||||
Result IReadOnlyApplicationControlDataInterface::GetApplicationControlDataWithoutIcon(
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u64> out_total_size,
|
||||
ApplicationControlSource application_control_source, u8 flag1, u8 flag2, u64 application_id) {
|
||||
LOG_INFO(Service_NS, "called with control_source={}, flags=({:02X},{:02X}), application_id={:016X}",
|
||||
application_control_source, flag1, flag2, application_id);
|
||||
|
||||
const FileSys::PatchManager pm{application_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
const auto size = out_buffer.size();
|
||||
|
||||
const auto nacp_size = sizeof(FileSys::RawNACP);
|
||||
|
||||
if (size < nacp_size) {
|
||||
LOG_ERROR(Service_NS, "output buffer is too small! (actual={:016X}, expected_min={:08X})",
|
||||
size, nacp_size);
|
||||
R_THROW(ResultUnknown);
|
||||
}
|
||||
|
||||
if (control.first != nullptr) {
|
||||
const auto bytes = control.first->GetRawBytes();
|
||||
const auto copy_len = (std::min)(static_cast<size_t>(bytes.size()), static_cast<size_t>(nacp_size));
|
||||
std::memcpy(out_buffer.data(), bytes.data(), copy_len);
|
||||
if (copy_len < nacp_size) {
|
||||
std::memset(out_buffer.data() + copy_len, 0, nacp_size - copy_len);
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Service_NS, "missing NACP data for application_id={:016X}", application_id);
|
||||
std::memset(out_buffer.data(), 0, nacp_size);
|
||||
}
|
||||
|
||||
const auto icon_area_size = size - nacp_size;
|
||||
std::vector<u8> final_icon_data;
|
||||
|
||||
if (control.second != nullptr) {
|
||||
size_t full_size = control.second->GetSize();
|
||||
if (full_size > 0) {
|
||||
final_icon_data.resize(full_size);
|
||||
control.second->Read(final_icon_data.data(), full_size);
|
||||
|
||||
if (flag1 == 1) {
|
||||
SanitizeJPEGImageSize(final_icon_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t available_icon_bytes = final_icon_data.size();
|
||||
|
||||
if (icon_area_size > 0) {
|
||||
const size_t to_copy = (std::min)(available_icon_bytes, icon_area_size);
|
||||
|
||||
if (to_copy > 0) {
|
||||
std::memcpy(out_buffer.data() + nacp_size, final_icon_data.data(), to_copy);
|
||||
}
|
||||
|
||||
if (to_copy < icon_area_size) {
|
||||
std::memset(out_buffer.data() + nacp_size + to_copy, 0, icon_area_size - to_copy);
|
||||
}
|
||||
}
|
||||
|
||||
const u32 total_available = static_cast<u32>(nacp_size + available_icon_bytes);
|
||||
|
||||
*out_total_size = (static_cast<u64>(total_available) << 32) | static_cast<u64>(flag1);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData3(
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_flags_a, Out<u32> out_flags_b,
|
||||
Out<u32> out_actual_size, ApplicationControlSource application_control_source, u8 flag1, u8 flag2, u64 application_id) {
|
||||
LOG_INFO(Service_NS, "called with control_source={}, flags=({:02X},{:02X}), application_id={:016X}",
|
||||
application_control_source, flag1, flag2, application_id);
|
||||
ApplicationControlSource application_control_source, u64 application_id) {
|
||||
LOG_INFO(Service_NS, "called with control_source={}, application_id={:016X}",
|
||||
application_control_source, application_id);
|
||||
|
||||
const FileSys::PatchManager pm{application_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
@@ -273,43 +158,22 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData3(
|
||||
}
|
||||
|
||||
const auto icon_area_size = size - nacp_size;
|
||||
std::vector<u8> final_icon_data;
|
||||
|
||||
if (control.second != nullptr) {
|
||||
size_t full_size = control.second->GetSize();
|
||||
if (full_size > 0) {
|
||||
final_icon_data.resize(full_size);
|
||||
control.second->Read(final_icon_data.data(), full_size);
|
||||
|
||||
if (flag1 == 1) {
|
||||
SanitizeJPEGImageSize(final_icon_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t available_icon_bytes = final_icon_data.size();
|
||||
|
||||
if (icon_area_size > 0) {
|
||||
const size_t to_copy = (std::min)(available_icon_bytes, icon_area_size);
|
||||
if (to_copy > 0) {
|
||||
std::memcpy(out_buffer.data() + nacp_size, final_icon_data.data(), to_copy);
|
||||
if (control.second != nullptr) {
|
||||
const auto icon_size = control.second->GetSize();
|
||||
const auto to_copy = static_cast<size_t>((std::min)(icon_size, icon_area_size));
|
||||
control.second->Read(out_buffer.data() + nacp_size, to_copy);
|
||||
if (to_copy < icon_area_size) {
|
||||
std::memset(out_buffer.data() + nacp_size + to_copy, 0, icon_area_size - to_copy);
|
||||
}
|
||||
} else {
|
||||
std::memset(out_buffer.data() + nacp_size, 0, icon_area_size);
|
||||
LOG_WARNING(Service_NS, "missing icon data for application_id={:016X}, zero-filling icon area",
|
||||
application_id);
|
||||
}
|
||||
if (to_copy < icon_area_size) {
|
||||
std::memset(out_buffer.data() + nacp_size + to_copy, 0, icon_area_size - to_copy);
|
||||
}
|
||||
} else {
|
||||
std::memset(out_buffer.data() + nacp_size, 0, icon_area_size);
|
||||
}
|
||||
|
||||
const u32 actual_total_size = static_cast<u32>(nacp_size + available_icon_bytes);
|
||||
|
||||
// Out 1: always 0x10001 (likely presents flags: Bit0=Icon, Bit16=NACP)
|
||||
// Out 2: reflects flag1 application (0 if flag1=0, 0x10001 if flag1=1)
|
||||
// Out 3: The actual size of data
|
||||
*out_flags_a = 0x10001;
|
||||
*out_flags_b = (flag1 == 1) ? 0x10001 : 0;
|
||||
*out_actual_size = actual_total_size;
|
||||
|
||||
*out_total_size = static_cast<u64>(size);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,21 +27,10 @@ public:
|
||||
u32 supported_languages);
|
||||
Result ConvertApplicationLanguageToLanguageCode(Out<u64> out_language_code,
|
||||
ApplicationLanguage application_language);
|
||||
Result GetApplicationControlData2(
|
||||
Result GetApplicationControlDataWithoutIcon(
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
|
||||
Out<u64> out_total_size,
|
||||
ApplicationControlSource application_control_source,
|
||||
u8 flag1,
|
||||
u8 flag2,
|
||||
u64 application_id);
|
||||
Result GetApplicationControlData3(
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
|
||||
Out<u32> out_flags_a,
|
||||
Out<u32> out_flags_b,
|
||||
Out<u32> out_actual_size,
|
||||
ApplicationControlSource application_control_source,
|
||||
u8 flag1,
|
||||
u8 flag2,
|
||||
u64 application_id);
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ struct Layer {
|
||||
explicit Layer(std::shared_ptr<android::BufferItemConsumer> buffer_item_consumer_,
|
||||
s32 consumer_id_)
|
||||
: buffer_item_consumer(std::move(buffer_item_consumer_)), consumer_id(consumer_id_),
|
||||
blending(LayerBlending::None), visible(true), z_index(0), is_overlay(false) {}
|
||||
blending(LayerBlending::None), visible(true), z_index(0) {}
|
||||
~Layer() {
|
||||
buffer_item_consumer->Abandon();
|
||||
}
|
||||
@@ -25,7 +25,6 @@ struct Layer {
|
||||
LayerBlending blending;
|
||||
bool visible;
|
||||
s32 z_index;
|
||||
bool is_overlay;
|
||||
};
|
||||
|
||||
struct LayerStack {
|
||||
|
||||
@@ -78,25 +78,8 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
for (auto& layer : display.stack.layers) {
|
||||
auto consumer_id = layer->consumer_id;
|
||||
|
||||
bool should_try_acquire = true;
|
||||
if (!layer->is_overlay) {
|
||||
auto fb_it = m_framebuffers.find(consumer_id);
|
||||
if (fb_it != m_framebuffers.end() && fb_it->second.is_acquired) {
|
||||
const u64 frames_since_last_acquire = m_frame_number - fb_it->second.last_acquire_frame;
|
||||
const s32 expected_interval = NormalizeSwapInterval(nullptr, fb_it->second.item.swap_interval);
|
||||
|
||||
if (frames_since_last_acquire < static_cast<u64>(expected_interval)) {
|
||||
should_try_acquire = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to fetch the framebuffer (either new or stale).
|
||||
const auto result = should_try_acquire
|
||||
? this->CacheFramebufferLocked(*layer, consumer_id)
|
||||
: (m_framebuffers.find(consumer_id) != m_framebuffers.end() && m_framebuffers[consumer_id].is_acquired
|
||||
? CacheStatus::CachedBufferReused
|
||||
: CacheStatus::NoBufferAvailable);
|
||||
const auto result = this->CacheFramebufferLocked(*layer, consumer_id);
|
||||
|
||||
// If we failed, skip this layer.
|
||||
if (result == CacheStatus::NoBufferAvailable) {
|
||||
@@ -128,12 +111,6 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
});
|
||||
}
|
||||
|
||||
// Overlay layers run at their own framerate independently of the game.
|
||||
// Skip them when calculating the swap interval for the main game.
|
||||
if (layer->is_overlay) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We need to compose again either before this frame is supposed to
|
||||
// be released, or exactly on the vsync period it should be released.
|
||||
const s32 item_swap_interval = NormalizeSwapInterval(out_speed_scale, item.swap_interval);
|
||||
@@ -161,44 +138,33 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
// Batch framebuffer releases, instead of one-into-one.
|
||||
std::vector<std::pair<Layer*, Framebuffer*>> to_release;
|
||||
for (auto& [layer_id, framebuffer] : m_framebuffers) {
|
||||
if (!framebuffer.is_acquired)
|
||||
if (framebuffer.release_frame_number > m_frame_number || !framebuffer.is_acquired)
|
||||
continue;
|
||||
|
||||
auto layer = display.stack.FindLayer(layer_id);
|
||||
if (!layer)
|
||||
continue;
|
||||
|
||||
// Overlay layers always release after every compose
|
||||
// Non-overlay layers release based on their swap interval
|
||||
if (layer->is_overlay || framebuffer.release_frame_number <= m_frame_number) {
|
||||
if (auto layer = display.stack.FindLayer(layer_id); layer)
|
||||
to_release.emplace_back(layer.get(), &framebuffer);
|
||||
}
|
||||
}
|
||||
for (auto& [layer, framebuffer] : to_release) {
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer->item, android::Fence::NoFence());
|
||||
framebuffer->is_acquired = false;
|
||||
}
|
||||
|
||||
// Advance by 1 frame (60 FPS compositing)
|
||||
m_frame_number += 1;
|
||||
// Advance by at least one frame.
|
||||
const u32 frame_advance = swap_interval.value_or(1);
|
||||
m_frame_number += frame_advance;
|
||||
|
||||
// Release any necessary framebuffers (non-overlay layers only, as overlays are already released above).
|
||||
// Release any necessary framebuffers.
|
||||
for (auto& [layer_id, framebuffer] : m_framebuffers) {
|
||||
if (framebuffer.release_frame_number > m_frame_number) {
|
||||
// Not yet ready to release this framebuffer.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!framebuffer.is_acquired) {
|
||||
// Already released.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (framebuffer.release_frame_number > m_frame_number) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const auto layer = display.stack.FindLayer(layer_id); layer != nullptr) {
|
||||
// Skip overlay layers as they were already released above
|
||||
if (layer->is_overlay) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: support release fence
|
||||
// This is needed to prevent screen tearing
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
|
||||
@@ -206,7 +172,7 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
return frame_advance;
|
||||
}
|
||||
|
||||
void HardwareComposer::RemoveLayerLocked(Display& display, ConsumerId consumer_id) {
|
||||
@@ -234,9 +200,8 @@ bool HardwareComposer::TryAcquireFramebufferLocked(Layer& layer, Framebuffer& fr
|
||||
}
|
||||
|
||||
// We succeeded, so set the new release frame info.
|
||||
const s32 swap_interval = layer.is_overlay ? 1 : NormalizeSwapInterval(nullptr, framebuffer.item.swap_interval);
|
||||
framebuffer.release_frame_number = m_frame_number + swap_interval;
|
||||
framebuffer.last_acquire_frame = m_frame_number;
|
||||
framebuffer.release_frame_number =
|
||||
NormalizeSwapInterval(nullptr, framebuffer.item.swap_interval);
|
||||
framebuffer.is_acquired = true;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -37,7 +34,6 @@ private:
|
||||
struct Framebuffer {
|
||||
android::BufferItem item{};
|
||||
ReleaseFrameNumber release_frame_number{};
|
||||
u64 last_acquire_frame{0};
|
||||
bool is_acquired{false};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -104,13 +101,6 @@ void SurfaceFlinger::SetLayerBlending(s32 consumer_binder_id, LayerBlending blen
|
||||
}
|
||||
}
|
||||
|
||||
void SurfaceFlinger::SetLayerIsOverlay(s32 consumer_binder_id, bool is_overlay) {
|
||||
if (const auto layer = this->FindLayer(consumer_binder_id); layer != nullptr) {
|
||||
layer->is_overlay = is_overlay;
|
||||
LOG_DEBUG(Service_VI, "Layer {} marked as overlay: {}", consumer_binder_id, is_overlay);
|
||||
}
|
||||
}
|
||||
|
||||
Display* SurfaceFlinger::FindDisplay(u64 display_id) {
|
||||
for (auto& display : m_displays) {
|
||||
if (display.id == display_id) {
|
||||
|
||||
@@ -47,7 +47,6 @@ public:
|
||||
|
||||
void SetLayerVisibility(s32 consumer_binder_id, bool visible);
|
||||
void SetLayerBlending(s32 consumer_binder_id, LayerBlending blending);
|
||||
void SetLayerIsOverlay(s32 consumer_binder_id, bool is_overlay);
|
||||
|
||||
std::shared_ptr<Layer> FindLayer(s32 consumer_binder_id);
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include "core/hle/service/btdrv/btdrv.h"
|
||||
#include "core/hle/service/btm/btm.h"
|
||||
#include "core/hle/service/caps/caps.h"
|
||||
#include "core/hle/service/dmnt/dmnt.h"
|
||||
#include "core/hle/service/erpt/erpt.h"
|
||||
#include "core/hle/service/es/es.h"
|
||||
#include "core/hle/service/eupld/eupld.h"
|
||||
@@ -108,7 +107,6 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
|
||||
{"btdrv", &BtDrv::LoopProcess},
|
||||
{"btm", &BTM::LoopProcess},
|
||||
{"capsrv", &Capture::LoopProcess},
|
||||
{"dmnt", &DMNT::LoopProcess},
|
||||
{"erpt", &ERPT::LoopProcess},
|
||||
{"es", &ES::LoopProcess},
|
||||
{"eupld", &EUPLD::LoopProcess},
|
||||
|
||||
@@ -36,29 +36,6 @@ struct SettingsHeader {
|
||||
u32 version;
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
void SyncGlobalLanguageFromCode(LanguageCode language_code) {
|
||||
const auto it = std::find_if(available_language_codes.begin(), available_language_codes.end(),
|
||||
[language_code](LanguageCode code) { return code == language_code; });
|
||||
if (it == available_language_codes.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t index = static_cast<std::size_t>(std::distance(available_language_codes.begin(), it));
|
||||
if (index >= static_cast<std::size_t>(Settings::values.language_index.GetValue())) {
|
||||
Settings::values.language_index.SetValue(static_cast<Settings::Language>(index));
|
||||
}
|
||||
}
|
||||
|
||||
void SyncGlobalRegionFromCode(SystemRegionCode region_code) {
|
||||
const auto region_index = static_cast<std::size_t>(region_code);
|
||||
if (region_index > static_cast<std::size_t>(Settings::Region::Taiwan)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Settings::values.region_index.SetValue(static_cast<Settings::Region>(region_index));
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system,
|
||||
@@ -480,7 +457,6 @@ Result ISystemSettingsServer::SetLanguageCode(LanguageCode language_code) {
|
||||
LOG_INFO(Service_SET, "called, language_code={}", language_code);
|
||||
|
||||
m_system_settings.language_code = language_code;
|
||||
SyncGlobalLanguageFromCode(language_code);
|
||||
SetSaveNeeded();
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -913,7 +889,6 @@ Result ISystemSettingsServer::SetRegionCode(SystemRegionCode region_code) {
|
||||
LOG_INFO(Service_SET, "called, region_code={}", region_code);
|
||||
|
||||
m_system_settings.region_code = region_code;
|
||||
SyncGlobalRegionFromCode(region_code);
|
||||
SetSaveNeeded();
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -1249,7 +1224,6 @@ Result ISystemSettingsServer::SetKeyboardLayout(KeyboardLayout keyboard_layout)
|
||||
LOG_INFO(Service_SET, "called, keyboard_layout={}", keyboard_layout);
|
||||
|
||||
m_system_settings.keyboard_layout = keyboard_layout;
|
||||
SetSaveNeeded();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
||||
@@ -312,7 +312,7 @@ void BSD::Listen(HLERequestContext& ctx) {
|
||||
void BSD::Fcntl(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
const u32 cmd = rp.Pop<u32>();
|
||||
const s32 cmd = rp.Pop<s32>();
|
||||
const s32 arg = rp.Pop<s32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} cmd={} arg={}", fd, cmd, arg);
|
||||
@@ -578,7 +578,7 @@ std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::span<con
|
||||
}
|
||||
|
||||
std::vector<Network::PollFD> host_pollfds(fds.size());
|
||||
std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [](PollFD pollfd) {
|
||||
std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [this](PollFD pollfd) {
|
||||
Network::PollFD result;
|
||||
result.socket = file_descriptors[pollfd.fd]->socket.get();
|
||||
result.events = Translate(pollfd.events);
|
||||
@@ -627,45 +627,23 @@ std::pair<s32, Errno> BSD::AcceptImpl(s32 fd, std::vector<u8>& write_buffer) {
|
||||
|
||||
Errno BSD::BindImpl(s32 fd, std::span<const u8> addr) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
LOG_ERROR(Service, "Bind failed: Invalid fd={}", fd);
|
||||
return Errno::BADF;
|
||||
}
|
||||
ASSERT(addr.size() == sizeof(SockAddrIn));
|
||||
auto addr_in = GetValue<SockAddrIn>(addr);
|
||||
|
||||
LOG_INFO(Service, "Bind fd={} to {}:{}", fd, Network::IPv4AddressToString(addr_in.ip),
|
||||
addr_in.portno);
|
||||
|
||||
const auto result = Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in)));
|
||||
if (result != Errno::SUCCESS) {
|
||||
LOG_ERROR(Service, "Bind fd={} failed with errno={}", fd, static_cast<int>(result));
|
||||
}
|
||||
return result;
|
||||
return Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in)));
|
||||
}
|
||||
|
||||
Errno BSD::ConnectImpl(s32 fd, std::span<const u8> addr) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
LOG_ERROR(Service, "Connect failed: Invalid fd={}", fd);
|
||||
return Errno::BADF;
|
||||
}
|
||||
|
||||
UNIMPLEMENTED_IF(addr.size() != sizeof(SockAddrIn));
|
||||
auto addr_in = GetValue<SockAddrIn>(addr);
|
||||
|
||||
LOG_INFO(Service, "Connect fd={} to {}:{}", fd, Network::IPv4AddressToString(addr_in.ip),
|
||||
addr_in.portno);
|
||||
|
||||
const auto result = Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in)));
|
||||
if (result != Errno::SUCCESS) {
|
||||
if (result == Errno::INPROGRESS || result == Errno::AGAIN) {
|
||||
LOG_DEBUG(Service, "Connect fd={} in progress (non-blocking), errno={}", fd, static_cast<int>(result));
|
||||
} else {
|
||||
LOG_ERROR(Service, "Connect fd={} failed with errno={}", fd, static_cast<int>(result));
|
||||
}
|
||||
} else {
|
||||
LOG_INFO(Service, "Connect fd={} succeeded", fd);
|
||||
}
|
||||
return result;
|
||||
return Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in)));
|
||||
}
|
||||
|
||||
Errno BSD::GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer) {
|
||||
@@ -741,8 +719,8 @@ Errno BSD::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector<u8>& o
|
||||
}
|
||||
|
||||
if (level != static_cast<u32>(SocketLevel::SOCKET)) {
|
||||
LOG_WARNING(Service, "(STUBBED) Unknown getsockopt level={}, returning INVAL", level);
|
||||
return Errno::INVAL;
|
||||
UNIMPLEMENTED_MSG("Unknown getsockopt level");
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
|
||||
Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
|
||||
@@ -772,52 +750,32 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<const u8
|
||||
}
|
||||
|
||||
if (level != static_cast<u32>(SocketLevel::SOCKET)) {
|
||||
LOG_WARNING(Service, "(STUBBED) Unknown setsockopt level={}, returning INVAL", level);
|
||||
return Errno::INVAL;
|
||||
UNIMPLEMENTED_MSG("Unknown setsockopt level");
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
|
||||
Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
|
||||
|
||||
if (optname == OptName::LINGER) {
|
||||
if (optval.size() != sizeof(Linger)) {
|
||||
LOG_WARNING(Service, "LINGER optval size mismatch: expected {}, got {}", sizeof(Linger),
|
||||
optval.size());
|
||||
return Errno::INVAL;
|
||||
}
|
||||
ASSERT(optval.size() == sizeof(Linger));
|
||||
auto linger = GetValue<Linger>(optval);
|
||||
if (linger.onoff != 0 && linger.onoff != 1) {
|
||||
LOG_WARNING(Service, "Invalid LINGER onoff value: {}", linger.onoff);
|
||||
return Errno::INVAL;
|
||||
}
|
||||
ASSERT(linger.onoff == 0 || linger.onoff == 1);
|
||||
|
||||
return Translate(socket->SetLinger(linger.onoff != 0, linger.linger));
|
||||
}
|
||||
|
||||
if (optval.size() != sizeof(u32)) {
|
||||
LOG_WARNING(Service, "optval size mismatch: expected {}, got {} for optname={}", sizeof(u32),
|
||||
optval.size(), static_cast<u32>(optname));
|
||||
return Errno::INVAL;
|
||||
}
|
||||
ASSERT(optval.size() == sizeof(u32));
|
||||
auto value = GetValue<u32>(optval);
|
||||
|
||||
switch (optname) {
|
||||
case OptName::REUSEADDR:
|
||||
if (value != 0 && value != 1) {
|
||||
LOG_WARNING(Service, "Invalid REUSEADDR value: {}", value);
|
||||
return Errno::INVAL;
|
||||
}
|
||||
ASSERT(value == 0 || value == 1);
|
||||
return Translate(socket->SetReuseAddr(value != 0));
|
||||
case OptName::KEEPALIVE:
|
||||
if (value != 0 && value != 1) {
|
||||
LOG_WARNING(Service, "Invalid KEEPALIVE value: {}", value);
|
||||
return Errno::INVAL;
|
||||
}
|
||||
ASSERT(value == 0 || value == 1);
|
||||
return Translate(socket->SetKeepAlive(value != 0));
|
||||
case OptName::BROADCAST:
|
||||
if (value != 0 && value != 1) {
|
||||
LOG_WARNING(Service, "Invalid BROADCAST value: {}", value);
|
||||
return Errno::INVAL;
|
||||
}
|
||||
ASSERT(value == 0 || value == 1);
|
||||
return Translate(socket->SetBroadcast(value != 0));
|
||||
case OptName::SNDBUF:
|
||||
return Translate(socket->SetSndBuf(value));
|
||||
@@ -831,9 +789,8 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<const u8
|
||||
LOG_WARNING(Service, "(STUBBED) setting NOSIGPIPE to {}", value);
|
||||
return Errno::SUCCESS;
|
||||
default:
|
||||
LOG_WARNING(Service, "(STUBBED) Unimplemented optname={} (0x{:x}), returning INVAL",
|
||||
static_cast<u32>(optname), static_cast<u32>(optname));
|
||||
return Errno::INVAL;
|
||||
UNIMPLEMENTED_MSG("Unimplemented optname={}", optname);
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -971,11 +928,7 @@ Expected<s32, Errno> BSD::DuplicateSocketImpl(s32 fd) {
|
||||
return Unexpected(Errno::MFILE);
|
||||
}
|
||||
|
||||
file_descriptors[new_fd] = FileDescriptor{
|
||||
.socket = file_descriptors[fd]->socket,
|
||||
.flags = file_descriptors[fd]->flags,
|
||||
.is_connection_based = file_descriptors[fd]->is_connection_based,
|
||||
};
|
||||
file_descriptors[new_fd] = file_descriptors[fd];
|
||||
return new_fd;
|
||||
}
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ private:
|
||||
|
||||
void BuildErrnoResponse(HLERequestContext& ctx, Errno bsd_errno) const noexcept;
|
||||
|
||||
static inline std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors{};
|
||||
std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
|
||||
|
||||
/// Callback to parse and handle a received wifi packet.
|
||||
void OnProxyPacketReceived(const Network::ProxyPacket& packet);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user