Compare commits

..

4 Commits

Author SHA1 Message Date
wildcard d88f9e00c9 Update src/core/loader/nso.cpp 2026-05-11 02:38:01 +02:00
wildcard 4bedd49376 Fix Paper Mario TTYD crashes on Android
This fixes the crash in Paper Mario TTYD on Android. The issue was a 32bit svc was being executed at module_start + 0x112F50 so we replace that svc we nop to fix the crash issue.
2026-05-11 02:34:57 +02:00
CamilleLaVey 5575d77520 [android] Another set of QoL changes for Android - 2 (#3886)
Changes:

- Defaults: Set Async GPU and Async Vulkan Presentation to OFF. Stability wasn't worth the trade-off.
- Threading: Lowered default pipeline workers from 7 to 4 to reduce heat and CPU contention.
- Settings: Added a slider for manual pipeline worker count so users can test what works best for their SoC.
- QCOM: Removed SPIRV bans; improves load times and thermals in heavy titles like Jump Force.
- UI: Cleaned up settings descriptions to be less ambiguous.

------------------------
Some games fixed:

-> Trinity Fusion: No longer crashes with Turnip, no longer shows the black dot in the middle of the screen on both QCOM and Turnip drivers.
-> Naruto X Boruto - Ultimate Ninja Storm Connections: Game no longer requires a fixed version of turnip to work (previously requiring Turnip driver from MESA 24.3/ @MrPurple666 EoL v2 driver)

Co-authored-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3886
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
2026-05-10 06:38:02 +02:00
lizzie afe92c5bed [dist] new 1st anniversary icon (#3942)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3942
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-05-10 05:36:58 +02:00
52 changed files with 1456 additions and 2293 deletions
+133 -74
View File
@@ -6,8 +6,8 @@
viewBox="0 0 512 512"
version="1.1"
id="svg7"
sodipodi:docname="base.svg.2026_01_12_14_43_47.0.svg"
inkscape:version="1.4.2 (ebf0e94, 2025-05-08)"
sodipodi:docname="1stanni.svg"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
inkscape:export-filename="base.svg.2026_01_12_14_43_47.0.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
@@ -19,36 +19,34 @@
<defs
id="defs7">
<linearGradient
id="linearGradient1"
id="linearGradient34"
inkscape:collect="always">
<stop
style="stop-color:#ff2e88;stop-opacity:0.5;"
style="stop-color:#ffd700;stop-opacity:1;"
offset="0"
id="stop3" />
id="stop34" />
<stop
style="stop-color:#bf42f6;stop-opacity:0.5;"
offset="0.44631511"
id="stop4" />
<stop
style="stop-color:#5da5ed;stop-opacity:0.5;"
offset="0.90088946"
id="stop2" />
style="stop-color:#ffd700;stop-opacity:0.48031053;"
offset="1"
id="stop35" />
</linearGradient>
<rect
x="20.999999"
y="287.30493"
width="487.07235"
height="134.69506"
id="rect22" />
<linearGradient
id="linearGradient138"
id="linearGradient21"
inkscape:collect="always">
<stop
style="stop-color:#ff2e88;stop-opacity:1;"
style="stop-color:#3a0057;stop-opacity:1;"
offset="0"
id="stop152" />
id="stop21" />
<stop
style="stop-color:#bf42f6;stop-opacity:1;"
offset="0.44971901"
id="stop137" />
<stop
style="stop-color:#5da5ed;stop-opacity:1;"
offset="0.89793283"
id="stop138" />
style="stop-color:#830091;stop-opacity:1;"
offset="1"
id="stop22" />
</linearGradient>
<linearGradient
id="swatch37"
@@ -116,33 +114,6 @@
width="521.34025"
height="248.94868"
id="rect24" />
<linearGradient
id="linearGradient11"
inkscape:collect="always">
<stop
style="stop-color:#ff2e88;stop-opacity:1;"
offset="0"
id="stop11" />
<stop
style="stop-color:#bf42f6;stop-opacity:1;"
offset="0.44971901"
id="stop154" />
<stop
style="stop-color:#5da5ed;stop-opacity:1;"
offset="0.89793283"
id="stop12" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient138"
id="linearGradient6"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.118028,0,0,1.116699,-46.314723,-42.388667)"
x1="270.39996"
y1="40.000019"
x2="270.39996"
y2="494.39996"
spreadMethod="pad" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath18">
@@ -165,16 +136,6 @@
inkscape:label="Circle"
r="191.89999" />
</clipPath>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient11"
id="linearGradient27"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-6.9401139e-5,-2.8678628)"
x1="256.00012"
y1="102.94693"
x2="256.00012"
y2="409.05307" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath128">
@@ -187,14 +148,106 @@
</clipPath>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1"
id="linearGradient2"
xlink:href="#linearGradient21"
id="linearGradient22"
x1="256"
y1="64"
y1="0"
x2="256"
y2="448"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.3229974,0,0,1.3214002,-82.687336,-82.290326)" />
y2="512"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient34"
id="linearGradient35"
x1="256"
y1="-0.048701428"
x2="256"
y2="512.04932"
gradientUnits="userSpaceOnUse" />
<filter
inkscape:label="Glowing Bubble"
inkscape:menu="Ridges"
inkscape:menu-tooltip="Bubble effect with refraction and glow"
x="-0.19420711"
y="-0.11239541"
width="1.3884142"
height="1.2247908"
style="color-interpolation-filters:sRGB;"
id="filter61">
<feGaussianBlur
stdDeviation="1"
result="result1"
id="feGaussianBlur56" />
<feGaussianBlur
stdDeviation="10"
result="result6"
in="result1"
id="feGaussianBlur57" />
<feComposite
operator="atop"
in="result6"
in2="result1"
result="result8"
id="feComposite57" />
<feComposite
operator="xor"
result="fbSourceGraphic"
in="result6"
in2="result8"
id="feComposite58" />
<feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 2 0 "
id="feColorMatrix58" />
<feGaussianBlur
result="result0"
in="fbSourceGraphicAlpha"
stdDeviation="1"
id="feGaussianBlur58" />
<feSpecularLighting
specularExponent="35"
specularConstant="1.5"
surfaceScale="-2"
lighting-color="rgb(255,255,255)"
result="result1"
in="result0"
id="feSpecularLighting58">
<feDistantLight
azimuth="230"
elevation="60"
id="feDistantLight58" />
</feSpecularLighting>
<feComposite
operator="in"
result="result2"
in="result1"
in2="fbSourceGraphicAlpha"
id="feComposite59" />
<feComposite
k3="1.2"
k2="1.1"
operator="arithmetic"
result="result4"
in="fbSourceGraphic"
in2="result2"
id="feComposite60" />
<feGaussianBlur
result="result80"
in="result4"
stdDeviation="0.5"
id="feGaussianBlur60" />
<feComposite
operator="atop"
in="result9"
in2="result80"
result="result91"
id="feComposite61" />
<feBlend
mode="multiply"
in2="result91"
id="feBlend61" />
</filter>
</defs>
<sodipodi:namedview
id="namedview7"
@@ -205,23 +258,29 @@
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="1.4142136"
inkscape:cx="261.62951"
inkscape:cy="230.87036"
inkscape:window-width="1920"
inkscape:window-height="1008"
inkscape:window-x="1080"
inkscape:window-y="351"
inkscape:zoom="1"
inkscape:cx="213.49999"
inkscape:cy="248.99999"
inkscape:window-width="1600"
inkscape:window-height="849"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg7" />
<circle
style="fill:url(#linearGradient22);fill-opacity:1;stroke:none;stroke-width:8"
id="path21"
cx="256"
cy="256"
r="256" />
<path
id="path8-7"
style="display:inline;mix-blend-mode:multiply;fill:url(#linearGradient6);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2);stroke-width:3.9666;stroke-dasharray:none;stroke-opacity:0.566238;paint-order:stroke fill markers"
style="display:inline;mix-blend-mode:normal;fill:url(#linearGradient35);fill-opacity:1;fill-rule:nonzero;stroke:#320081;stroke-width:4.067;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
inkscape:label="Circle"
d="M 256,2.2792898 A 254.0155,253.71401 0 0 0 150.68475,25.115202 c 19.54414,1.070775 38.74692,5.250294 51.56848,11.647658 14.14361,7.056691 28.63804,19.185961 39.4212,29.347551 h 40.60981 c 1.03847,-0.68139 2.10297,-1.36938 3.1938,-2.05957 5.45602,-15.78533 14.79164,-43.183497 19.49612,-57.0097682 A 254.0155,253.71401 0 0 0 256,2.2792898 Z m 61.57106,7.567234 -18.26098,46.1544672 c 7.79702,-4.13918 16.35655,-7.87447 25.20671,-10.87081 23.1229,-7.828433 43.96931,-10.170904 54.94058,-10.868226 A 254.0155,253.71401 0 0 0 317.57106,9.8465238 Z m 65.39277,26.4001532 c -9.68256,4.806644 -33.05532,16.642034 -55.68217,29.863734 H 424.4677 A 254.0155,253.71401 0 0 0 382.96383,36.246677 Z M 113.90698,45.690231 A 254.0155,253.71401 0 0 0 87.532302,66.110411 H 194.2739 c -1.47402,-0.80231 -2.35141,-1.25949 -2.35141,-1.25949 l 10.4496,-11.83348 -38.40568,7.01234 c 0,1e-5 -12.21537,-4.60266 -40.17313,-12.27223 -3.45336,-0.94731 -6.75329,-1.61824 -9.8863,-2.06732 z m -36.803618,30.18635 a 254.0155,253.71401 0 0 0 -34.88372,43.090929 h 59.976738 c 18.11461,-12.04145 40.14252,-22.882149 62.31266,-24.534159 52.93006,-3.9444 70.16538,1.86342 70.16538,1.86342 0,0 -4.612,-4.8206 -14.51938,-13.36656 -2.72366,-2.34942 -6.0844,-4.77373 -9.52455,-7.05363 z m 174.472868,0 c 4.57322,4.7186 7.29716,7.83565 7.29716,7.83565 0,0 3.53501,-3.18484 9.62532,-7.83565 z m 60.27649,0 c -21.56573,15.45339 -25.4703,27.979669 -25.4703,27.979669 0,0 54.83326,-19.215729 100.70543,-0.31228 11.63986,4.79661 21.58481,10.13159 29.94832,15.42354 h 52.74419 A 254.0155,253.71401 0 0 0 434.89664,75.876581 Z M 36.250648,128.73367 A 254.0155,253.71401 0 0 0 16.372095,171.82459 H 147.45478 c 1.45695,-2.5815 3.06539,-5.08648 4.83979,-7.48982 14.23694,-19.28301 27.92088,-30.0088 36.86047,-35.6011 h -30.25323 c -5.87346,0.93472 -12.04945,1.99094 -18.28166,3.16937 -30.12936,5.69727 -81.157618,22.78945 -81.157618,22.78945 0,0 11.47125,-12.39249 29.11369,-25.95882 z m 265.630492,0 c 33.48676,11.2434 52.42799,26.78443 62.7752,43.09092 h 130.97157 a 254.0155,253.71401 0 0 0 -19.87856,-43.09092 h -44.81136 c 14.85233,11.5863 21.59948,20.9854 21.59948,20.9854 0,0 -33.5226,-12.37087 -66.0646,-20.9854 z m -45.96641,16.27007 c -1.00419,0.0106 -10.12705,0.72026 -44.98966,20.64729 -3.12132,1.78406 -6.25434,3.86182 -9.37468,6.17356 h 41.81911 c 7.17181,-17.34774 12.64083,-26.82085 12.64083,-26.82085 0,0 -0.0287,-7.1e-4 -0.0957,0 z m 14.18088,0.0465 c 0,0 -3.31228,9.32762 -7.30492,26.77438 h 51.78554 C 287.6577,146.14158 270.09561,145.0502 270.09561,145.0502 Z M 13.152456,181.59075 A 254.0155,253.71401 0 0 0 3.927651,224.68167 H 134.1447 c 0.56161,-12.72411 2.67825,-28.50188 8.61499,-43.09092 z m 176.661504,0 c -14.27121,13.10564 -27.60733,29.58761 -37.56073,43.09092 h 73.3721 c 4.47018,-16.79061 9.35068,-31.26371 13.86562,-43.09092 z m 70.85787,0 c -2.41384,11.76417 -4.9032,26.20707 -6.94831,43.09092 H 360.4832 c -8.32133,-10.88917 -20.66988,-26.17008 -36.35141,-43.09092 z m 109.17313,0 c 6.63611,15.24089 6.92441,30.5373 5.57882,43.09092 h 132.64857 a 254.0155,253.71401 0 0 0 -9.22481,-43.09092 z M 2.90181,234.44783 A 254.0155,253.71401 0 0 0 1.984498,255.9933 254.0155,253.71401 0 0 0 2.90181,277.53876 h 211.89923 c 2.25762,-15.52555 5.14325,-29.93448 8.3385,-43.09093 h -77.8863 c -6.46396,9.27617 -10.33076,15.56549 -10.33076,15.56549 0,0 -0.82623,-6.14945 -0.9354,-15.56549 z m 249.72093,0 c -1.3692,13.09684 -2.4456,27.49209 -3.02068,43.09093 h 259.49613 a 254.0155,253.71401 0 0 0 0.91731,-21.54546 254.0155,253.71401 0 0 0 -0.91731,-21.54547 H 374.02584 c -0.445,2.5469 -0.90878,4.89768 -1.32817,7.01751 0,0 -1.69726,-2.53821 -4.94056,-7.01751 z M 3.927651,287.30493 a 254.0155,253.71401 0 0 0 9.224805,43.09091 H 214.04393 c -1.29238,-15.40742 -1.57503,-30.04388 -0.41861,-43.09091 z m 245.385009,0 c -0.30355,13.54349 -0.22032,27.92598 0.36951,43.09091 h 249.16537 a 254.0155,253.71401 0 0 0 9.22481,-43.09091 z M 16.369511,340.16201 a 254.0155,253.71401 0 0 0 19.878554,43.09091 H 221.4677 c -2.69781,-14.4523 -4.96108,-29.01285 -6.4832,-43.09091 z m 233.842379,0 c 1.15864,15.47765 3.81286,29.83979 7.51679,43.09091 h 218.02325 a 254.0155,253.71401 0 0 0 19.87856,-43.09091 z M 42.217052,393.01909 a 254.0155,253.71401 0 0 0 34.88372,43.09093 H 233.09561 c -3.40902,-13.67281 -6.76794,-28.2531 -9.73902,-43.09093 z m 218.490958,0 c 5.34985,16.15926 12.22007,30.51982 19.68733,43.09093 h 154.50389 a 254.0155,253.71401 0 0 0 34.88371,-43.09093 z M 87.529722,445.87618 a 254.0155,253.71401 0 0 0 166.229968,63.8208 c -3.67805,-12.0825 -10.85464,-35.49828 -18.18088,-63.8208 z m 199.010328,0 c 17.5887,26.43772 36.99259,43.60598 47.33592,51.61309 a 254.0155,253.71401 0 0 0 90.59431,-51.61309 z" />
<path
id="path27"
style="display:inline;mix-blend-mode:multiply;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient27);stroke-width:3;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers"
style="display:inline;mix-blend-mode:multiply;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers"
d="m 318.98012,441.7375 c -9.87518,-6.73978 -64.39137,-49.0272 -67.68975,-127.81978 -3.69298,-88.21893 15.36468,-141.91029 15.36468,-141.91029 0,0 16.00378,0.99513 39.80316,26.53195 23.79939,25.53753 37.74965,46.43102 37.74965,46.43102 3.91262,-19.79992 12.84563,-66.32402 -60.72865,-87.55523 0,0 12.82326,-5.38883 39.3925,-3.81382 26.56907,1.57572 81.6822,21.93799 81.6822,21.93799 0,0 -14.79766,-20.63773 -49.47063,-34.94295 -34.67291,-14.30533 -76.1182,0.23644 -76.1182,0.23644 0,0 3.86959,-12.43127 27.22669,-26.38478 23.35718,-13.9537 49.27409,-26.501533 49.27409,-26.501533 0,0 -21.97854,-0.26548 -47.67725,8.44535 -6.68948,2.267506 -13.15863,5.094213 -19.05208,8.226563 l 16.05803,-40.634103 -4.4617,-1.89059 -5.1305,-0.95965 c 0,0 -11.24072,33.12428 -16.92051,49.576513 -12.13137,7.68489 -20.11005,14.87735 -20.11005,14.87735 0,0 -21.90573,-25.09227 -42.79668,-35.527803 -26.03412,-13.00525 -86.88249,-13.90359 -94.0044,10.401173 0,0 13.56804,-7.884703 34.70032,-2.080917 21.13214,5.803997 30.3644,9.287307 30.3644,9.287307 l 29.02989,-5.30681 -7.89811,8.95527 c 0,0 13.8496,7.21324 21.33822,13.68063 7.48859,6.46722 10.9757,10.11472 10.9757,10.11472 0,0 -13.02739,-4.39388 -53.03507,-1.40893 -40.00771,2.98473 -79.40016,45.60209 -79.40016,45.60209 0,0 38.57037,-12.93531 61.34393,-17.24677 22.77354,-4.31126 44.52166,-6.46757 44.52166,-6.46757 0,0 -17.23298,5.97003 -35.69792,31.00932 -18.46522,25.03987 -13.13146,64.83866 -13.13146,64.83866 0,0 29.33874,-47.7577 57.44675,-63.84249 28.10798,-16.08527 34.0799,-15.6238 34.0799,-15.6238 0,0 -22.56785,39.13486 -31.39017,101.98268 -8.03005,57.2039 26.77689,163.75449 31.1572,178.89699"
sodipodi:nodetypes="cscsccscscscsccccccscscccscscscscscsc"
inkscape:label="MainOutline"

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

After

Width:  |  Height:  |  Size: 256 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 KiB

After

Width:  |  Height:  |  Size: 556 KiB

+289
View File
@@ -0,0 +1,289 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="512"
height="512"
fill="none"
viewBox="0 0 512 512"
version="1.1"
id="svg7"
sodipodi:docname="1stanni.svg"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
inkscape:export-filename="base.svg.2026_01_12_14_43_47.0.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs7">
<linearGradient
id="linearGradient34"
inkscape:collect="always">
<stop
style="stop-color:#ffd700;stop-opacity:1;"
offset="0"
id="stop34" />
<stop
style="stop-color:#ffd700;stop-opacity:0.48031053;"
offset="1"
id="stop35" />
</linearGradient>
<rect
x="20.999999"
y="287.30493"
width="487.07235"
height="134.69506"
id="rect22" />
<linearGradient
id="linearGradient21"
inkscape:collect="always">
<stop
style="stop-color:#3a0057;stop-opacity:1;"
offset="0"
id="stop21" />
<stop
style="stop-color:#830091;stop-opacity:1;"
offset="1"
id="stop22" />
</linearGradient>
<linearGradient
id="swatch37"
inkscape:swatch="solid">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop37" />
</linearGradient>
<linearGradient
id="swatch28"
inkscape:swatch="solid">
<stop
style="stop-color:#252525;stop-opacity:1;"
offset="0"
id="stop28" />
</linearGradient>
<linearGradient
id="swatch27"
inkscape:swatch="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop27" />
</linearGradient>
<linearGradient
id="swatch15"
inkscape:swatch="solid">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop16" />
</linearGradient>
<linearGradient
id="linearGradient14"
inkscape:swatch="gradient">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop14" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop15" />
</linearGradient>
<linearGradient
id="swatch9"
inkscape:swatch="solid">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop10" />
</linearGradient>
<linearGradient
id="swatch8"
inkscape:swatch="solid">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop9" />
</linearGradient>
<rect
x="22.627417"
y="402.76802"
width="521.34025"
height="248.94868"
id="rect24" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath18">
<circle
style="opacity:1;mix-blend-mode:normal;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10.8382;stroke-opacity:0.566238;paint-order:stroke fill markers"
id="circle18"
cx="-246.8315"
cy="246.8338"
inkscape:label="Circle"
r="191.89999" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath22">
<circle
style="opacity:1;mix-blend-mode:normal;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10.8382;stroke-opacity:0.566238;paint-order:stroke fill markers"
id="circle22"
cx="256"
cy="256"
inkscape:label="Circle"
r="191.89999" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath128">
<circle
style="fill:none;fill-opacity:1;stroke:#03ffff;stroke-width:0;stroke-dasharray:none;stroke-opacity:1"
id="circle128"
cx="256"
cy="256"
r="192" />
</clipPath>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient21"
id="linearGradient22"
x1="256"
y1="0"
x2="256"
y2="512"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient34"
id="linearGradient35"
x1="256"
y1="-0.048701428"
x2="256"
y2="512.04932"
gradientUnits="userSpaceOnUse" />
<filter
inkscape:label="Glowing Bubble"
inkscape:menu="Ridges"
inkscape:menu-tooltip="Bubble effect with refraction and glow"
x="-0.19420711"
y="-0.11239541"
width="1.3884142"
height="1.2247908"
style="color-interpolation-filters:sRGB;"
id="filter61">
<feGaussianBlur
stdDeviation="1"
result="result1"
id="feGaussianBlur56" />
<feGaussianBlur
stdDeviation="10"
result="result6"
in="result1"
id="feGaussianBlur57" />
<feComposite
operator="atop"
in="result6"
in2="result1"
result="result8"
id="feComposite57" />
<feComposite
operator="xor"
result="fbSourceGraphic"
in="result6"
in2="result8"
id="feComposite58" />
<feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 2 0 "
id="feColorMatrix58" />
<feGaussianBlur
result="result0"
in="fbSourceGraphicAlpha"
stdDeviation="1"
id="feGaussianBlur58" />
<feSpecularLighting
specularExponent="35"
specularConstant="1.5"
surfaceScale="-2"
lighting-color="rgb(255,255,255)"
result="result1"
in="result0"
id="feSpecularLighting58">
<feDistantLight
azimuth="230"
elevation="60"
id="feDistantLight58" />
</feSpecularLighting>
<feComposite
operator="in"
result="result2"
in="result1"
in2="fbSourceGraphicAlpha"
id="feComposite59" />
<feComposite
k3="1.2"
k2="1.1"
operator="arithmetic"
result="result4"
in="fbSourceGraphic"
in2="result2"
id="feComposite60" />
<feGaussianBlur
result="result80"
in="result4"
stdDeviation="0.5"
id="feGaussianBlur60" />
<feComposite
operator="atop"
in="result9"
in2="result80"
result="result91"
id="feComposite61" />
<feBlend
mode="multiply"
in2="result91"
id="feBlend61" />
</filter>
</defs>
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="1"
inkscape:cx="213.49999"
inkscape:cy="248.99999"
inkscape:window-width="1600"
inkscape:window-height="849"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg7" />
<circle
style="fill:url(#linearGradient22);fill-opacity:1;stroke:none;stroke-width:8"
id="path21"
cx="256"
cy="256"
r="256" />
<path
id="path8-7"
style="display:inline;mix-blend-mode:normal;fill:url(#linearGradient35);fill-opacity:1;fill-rule:nonzero;stroke:#320081;stroke-width:4.067;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
inkscape:label="Circle"
d="M 256,2.2792898 A 254.0155,253.71401 0 0 0 150.68475,25.115202 c 19.54414,1.070775 38.74692,5.250294 51.56848,11.647658 14.14361,7.056691 28.63804,19.185961 39.4212,29.347551 h 40.60981 c 1.03847,-0.68139 2.10297,-1.36938 3.1938,-2.05957 5.45602,-15.78533 14.79164,-43.183497 19.49612,-57.0097682 A 254.0155,253.71401 0 0 0 256,2.2792898 Z m 61.57106,7.567234 -18.26098,46.1544672 c 7.79702,-4.13918 16.35655,-7.87447 25.20671,-10.87081 23.1229,-7.828433 43.96931,-10.170904 54.94058,-10.868226 A 254.0155,253.71401 0 0 0 317.57106,9.8465238 Z m 65.39277,26.4001532 c -9.68256,4.806644 -33.05532,16.642034 -55.68217,29.863734 H 424.4677 A 254.0155,253.71401 0 0 0 382.96383,36.246677 Z M 113.90698,45.690231 A 254.0155,253.71401 0 0 0 87.532302,66.110411 H 194.2739 c -1.47402,-0.80231 -2.35141,-1.25949 -2.35141,-1.25949 l 10.4496,-11.83348 -38.40568,7.01234 c 0,1e-5 -12.21537,-4.60266 -40.17313,-12.27223 -3.45336,-0.94731 -6.75329,-1.61824 -9.8863,-2.06732 z m -36.803618,30.18635 a 254.0155,253.71401 0 0 0 -34.88372,43.090929 h 59.976738 c 18.11461,-12.04145 40.14252,-22.882149 62.31266,-24.534159 52.93006,-3.9444 70.16538,1.86342 70.16538,1.86342 0,0 -4.612,-4.8206 -14.51938,-13.36656 -2.72366,-2.34942 -6.0844,-4.77373 -9.52455,-7.05363 z m 174.472868,0 c 4.57322,4.7186 7.29716,7.83565 7.29716,7.83565 0,0 3.53501,-3.18484 9.62532,-7.83565 z m 60.27649,0 c -21.56573,15.45339 -25.4703,27.979669 -25.4703,27.979669 0,0 54.83326,-19.215729 100.70543,-0.31228 11.63986,4.79661 21.58481,10.13159 29.94832,15.42354 h 52.74419 A 254.0155,253.71401 0 0 0 434.89664,75.876581 Z M 36.250648,128.73367 A 254.0155,253.71401 0 0 0 16.372095,171.82459 H 147.45478 c 1.45695,-2.5815 3.06539,-5.08648 4.83979,-7.48982 14.23694,-19.28301 27.92088,-30.0088 36.86047,-35.6011 h -30.25323 c -5.87346,0.93472 -12.04945,1.99094 -18.28166,3.16937 -30.12936,5.69727 -81.157618,22.78945 -81.157618,22.78945 0,0 11.47125,-12.39249 29.11369,-25.95882 z m 265.630492,0 c 33.48676,11.2434 52.42799,26.78443 62.7752,43.09092 h 130.97157 a 254.0155,253.71401 0 0 0 -19.87856,-43.09092 h -44.81136 c 14.85233,11.5863 21.59948,20.9854 21.59948,20.9854 0,0 -33.5226,-12.37087 -66.0646,-20.9854 z m -45.96641,16.27007 c -1.00419,0.0106 -10.12705,0.72026 -44.98966,20.64729 -3.12132,1.78406 -6.25434,3.86182 -9.37468,6.17356 h 41.81911 c 7.17181,-17.34774 12.64083,-26.82085 12.64083,-26.82085 0,0 -0.0287,-7.1e-4 -0.0957,0 z m 14.18088,0.0465 c 0,0 -3.31228,9.32762 -7.30492,26.77438 h 51.78554 C 287.6577,146.14158 270.09561,145.0502 270.09561,145.0502 Z M 13.152456,181.59075 A 254.0155,253.71401 0 0 0 3.927651,224.68167 H 134.1447 c 0.56161,-12.72411 2.67825,-28.50188 8.61499,-43.09092 z m 176.661504,0 c -14.27121,13.10564 -27.60733,29.58761 -37.56073,43.09092 h 73.3721 c 4.47018,-16.79061 9.35068,-31.26371 13.86562,-43.09092 z m 70.85787,0 c -2.41384,11.76417 -4.9032,26.20707 -6.94831,43.09092 H 360.4832 c -8.32133,-10.88917 -20.66988,-26.17008 -36.35141,-43.09092 z m 109.17313,0 c 6.63611,15.24089 6.92441,30.5373 5.57882,43.09092 h 132.64857 a 254.0155,253.71401 0 0 0 -9.22481,-43.09092 z M 2.90181,234.44783 A 254.0155,253.71401 0 0 0 1.984498,255.9933 254.0155,253.71401 0 0 0 2.90181,277.53876 h 211.89923 c 2.25762,-15.52555 5.14325,-29.93448 8.3385,-43.09093 h -77.8863 c -6.46396,9.27617 -10.33076,15.56549 -10.33076,15.56549 0,0 -0.82623,-6.14945 -0.9354,-15.56549 z m 249.72093,0 c -1.3692,13.09684 -2.4456,27.49209 -3.02068,43.09093 h 259.49613 a 254.0155,253.71401 0 0 0 0.91731,-21.54546 254.0155,253.71401 0 0 0 -0.91731,-21.54547 H 374.02584 c -0.445,2.5469 -0.90878,4.89768 -1.32817,7.01751 0,0 -1.69726,-2.53821 -4.94056,-7.01751 z M 3.927651,287.30493 a 254.0155,253.71401 0 0 0 9.224805,43.09091 H 214.04393 c -1.29238,-15.40742 -1.57503,-30.04388 -0.41861,-43.09091 z m 245.385009,0 c -0.30355,13.54349 -0.22032,27.92598 0.36951,43.09091 h 249.16537 a 254.0155,253.71401 0 0 0 9.22481,-43.09091 z M 16.369511,340.16201 a 254.0155,253.71401 0 0 0 19.878554,43.09091 H 221.4677 c -2.69781,-14.4523 -4.96108,-29.01285 -6.4832,-43.09091 z m 233.842379,0 c 1.15864,15.47765 3.81286,29.83979 7.51679,43.09091 h 218.02325 a 254.0155,253.71401 0 0 0 19.87856,-43.09091 z M 42.217052,393.01909 a 254.0155,253.71401 0 0 0 34.88372,43.09093 H 233.09561 c -3.40902,-13.67281 -6.76794,-28.2531 -9.73902,-43.09093 z m 218.490958,0 c 5.34985,16.15926 12.22007,30.51982 19.68733,43.09093 h 154.50389 a 254.0155,253.71401 0 0 0 34.88371,-43.09093 z M 87.529722,445.87618 a 254.0155,253.71401 0 0 0 166.229968,63.8208 c -3.67805,-12.0825 -10.85464,-35.49828 -18.18088,-63.8208 z m 199.010328,0 c 17.5887,26.43772 36.99259,43.60598 47.33592,51.61309 a 254.0155,253.71401 0 0 0 90.59431,-51.61309 z" />
<path
id="path27"
style="display:inline;mix-blend-mode:multiply;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers"
d="m 318.98012,441.7375 c -9.87518,-6.73978 -64.39137,-49.0272 -67.68975,-127.81978 -3.69298,-88.21893 15.36468,-141.91029 15.36468,-141.91029 0,0 16.00378,0.99513 39.80316,26.53195 23.79939,25.53753 37.74965,46.43102 37.74965,46.43102 3.91262,-19.79992 12.84563,-66.32402 -60.72865,-87.55523 0,0 12.82326,-5.38883 39.3925,-3.81382 26.56907,1.57572 81.6822,21.93799 81.6822,21.93799 0,0 -14.79766,-20.63773 -49.47063,-34.94295 -34.67291,-14.30533 -76.1182,0.23644 -76.1182,0.23644 0,0 3.86959,-12.43127 27.22669,-26.38478 23.35718,-13.9537 49.27409,-26.501533 49.27409,-26.501533 0,0 -21.97854,-0.26548 -47.67725,8.44535 -6.68948,2.267506 -13.15863,5.094213 -19.05208,8.226563 l 16.05803,-40.634103 -4.4617,-1.89059 -5.1305,-0.95965 c 0,0 -11.24072,33.12428 -16.92051,49.576513 -12.13137,7.68489 -20.11005,14.87735 -20.11005,14.87735 0,0 -21.90573,-25.09227 -42.79668,-35.527803 -26.03412,-13.00525 -86.88249,-13.90359 -94.0044,10.401173 0,0 13.56804,-7.884703 34.70032,-2.080917 21.13214,5.803997 30.3644,9.287307 30.3644,9.287307 l 29.02989,-5.30681 -7.89811,8.95527 c 0,0 13.8496,7.21324 21.33822,13.68063 7.48859,6.46722 10.9757,10.11472 10.9757,10.11472 0,0 -13.02739,-4.39388 -53.03507,-1.40893 -40.00771,2.98473 -79.40016,45.60209 -79.40016,45.60209 0,0 38.57037,-12.93531 61.34393,-17.24677 22.77354,-4.31126 44.52166,-6.46757 44.52166,-6.46757 0,0 -17.23298,5.97003 -35.69792,31.00932 -18.46522,25.03987 -13.13146,64.83866 -13.13146,64.83866 0,0 29.33874,-47.7577 57.44675,-63.84249 28.10798,-16.08527 34.0799,-15.6238 34.0799,-15.6238 0,0 -22.56785,39.13486 -31.39017,101.98268 -8.03005,57.2039 26.77689,163.75449 31.1572,178.89699"
sodipodi:nodetypes="cscsccscscscsccccccscscccscscscscscsc"
inkscape:label="MainOutline"
clip-path="url(#clipPath128)"
transform="matrix(1.3229974,0,0,1.3214002,-82.687282,-82.278451)" />
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

+1
View File
@@ -0,0 +1 @@
#ffd700
Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 27 KiB

@@ -8,8 +8,6 @@ package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding
import org.yuzu.yuzu_emu.model.Patch
import org.yuzu.yuzu_emu.model.PatchType
@@ -26,25 +24,13 @@ class AddonAdapter(val addonViewModel: AddonViewModel) :
inner class AddonViewHolder(val binding: ListItemAddonBinding) :
AbstractViewHolder<Patch>(binding) {
override fun bind(model: Patch) {
val isCheat = model.isCheat()
val isIncompatible = isCheat && model.cheatCompat == Patch.CHEAT_COMPAT_INCOMPATIBLE
val indentPx = if (isCheat) {
(32 * binding.root.context.resources.displayMetrics.density).toInt()
} else {
0
binding.addonCard.setOnClickListener {
binding.addonSwitch.performClick()
}
binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> {
marginStart = indentPx
}
binding.addonCard.setOnClickListener(
if (isIncompatible) null else { { binding.addonSwitch.performClick() } }
)
binding.title.text = model.name
binding.version.text = model.version
binding.addonSwitch.setOnCheckedChangeListener(null)
binding.addonSwitch.isChecked = model.enabled
binding.addonSwitch.isEnabled = !isIncompatible
binding.addonSwitch.alpha = if (isIncompatible) 0.38f else 1.0f
binding.addonSwitch.setOnCheckedChangeListener { _, checked ->
if (PatchType.from(model.type) == PatchType.Update && checked) {
addonViewModel.enableOnlyThisUpdate(model)
@@ -53,9 +39,12 @@ class AddonAdapter(val addonViewModel: AddonViewModel) :
model.enabled = checked
}
}
val canDelete = model.isRemovable && !isCheat
binding.deleteCard.isVisible = canDelete
binding.buttonDelete.isVisible = canDelete
val canDelete = model.isRemovable
binding.deleteCard.isEnabled = canDelete
binding.buttonDelete.isEnabled = canDelete
binding.deleteCard.alpha = if (canDelete) 1f else 0.38f
if (canDelete) {
val deleteAction = {
addonViewModel.setAddonToDelete(model)
@@ -67,7 +67,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
MY_PAGE_APPLET("my_page_applet_mode"),
INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide"),
OVERLAY_GRID_SIZE("overlay_grid_size"),
GPU_LOG_RING_BUFFER_SIZE("gpu_log_ring_buffer_size")
GPU_LOG_RING_BUFFER_SIZE("gpu_log_ring_buffer_size"),
ANDROID_PIPELINE_WORKERS("pipeline_worker_count")
;
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
@@ -582,6 +582,16 @@ abstract class SettingsItem(
units = "%"
)
)
put(
SliderSetting(
IntSetting.ANDROID_PIPELINE_WORKERS,
titleId = R.string.pipeline_worker_cores,
descriptionId = R.string.pipeline_worker_cores_description,
min = 4,
max = 8,
units = "cores"
)
)
put(
SingleChoiceSetting(
IntSetting.RENDERER_ANTI_ALIASING,
@@ -295,6 +295,7 @@ class SettingsFragmentPresenter(
add(BooleanSetting.EMULATE_BGR565.key)
add(BooleanSetting.RESCALE_HACK.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
add(IntSetting.ANDROID_PIPELINE_WORKERS.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
add(SettingsItem.GPU_UNSWIZZLE_COMBINED)
@@ -72,7 +72,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,
@@ -31,17 +31,12 @@ class AddonViewModel : ViewModel() {
val addonToDelete = _addonToDelete.asStateFlow()
var game: Game? = null
private set
private var loadedGameKey: String? = null
private val isRefreshing = AtomicBoolean(false)
private val pendingRefresh = AtomicBoolean(false)
fun onAddonsViewCreated(game: Game) {
if (this.game?.programId == game.programId && _patchList.value.isNotEmpty()) {
return
}
this.game = game
refreshAddons(commitEmpty = false)
}
@@ -71,7 +66,8 @@ class AddonViewModel : ViewModel() {
NativeLibrary.getPatchesForFile(currentGame.path, currentGame.programId)
} ?: return@launch
val patchList = sortPatchesWithCheatsGrouped(patches.toMutableList())
val patchList = patches.toMutableList()
patchList.sortBy { it.name }
// Ensure only one update is enabled
ensureSingleUpdateEnabled(patchList)
@@ -150,7 +146,6 @@ 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 -> {}
}
refreshAddons(force = true)
}
@@ -184,7 +179,7 @@ class AddonViewModel : ViewModel() {
it.name
}
} else {
it.getStorageKey()
it.name
}
}
}.toTypedArray()
@@ -206,28 +201,4 @@ class AddonViewModel : ViewModel() {
private fun gameKey(game: Game): String {
return "${game.programId}|${game.path}"
}
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
}
}
@@ -17,9 +17,7 @@ data class Patch(
val programId: String,
val titleId: String,
val numericVersion: Long = 0,
val source: Int = 0,
val parentName: String = "", // For cheats: name of the mod folder containing them
val cheatCompat: Int = CHEAT_COMPAT_COMPATIBLE
val source: Int = 0
) {
companion object {
const val SOURCE_UNKNOWN = 0
@@ -27,31 +25,8 @@ data class Patch(
const val SOURCE_SDMC = 2
const val SOURCE_EXTERNAL = 3
const val SOURCE_PACKED = 4
const val CHEAT_COMPAT_COMPATIBLE = 0
const val CHEAT_COMPAT_INCOMPATIBLE = 1
}
val isRemovable: Boolean
get() = source != SOURCE_EXTERNAL && source != SOURCE_PACKED
/**
* 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()
fun isIncompatibleCheat(): Boolean = isCheat() && cheatCompat == CHEAT_COMPAT_INCOMPATIBLE
}
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 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
@@ -147,6 +147,13 @@ namespace AndroidSettings {
&show_performance_overlay};
Settings::Setting<s32> pipeline_worker_count{linkage, 4, "pipeline_worker_count",
Settings::Category::Android,
Settings::Specialization::Default,
true,
true};
Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
Settings::Category::Overlay};
Settings::Setting<bool> overlay_snap_to_grid{linkage, false, "overlay_snap_to_grid",
+1 -3
View File
@@ -1406,9 +1406,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env
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)),
static_cast<jlong>(patch.numeric_version), static_cast<jint>(patch.source),
Common::Android::ToJString(env, patch.parent_name),
static_cast<jint>(patch.cheat_compat));
static_cast<jlong>(patch.numeric_version), static_cast<jint>(patch.source));
env->SetObjectArrayElement(jpatchArray, i, jpatch);
++i;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 61 KiB

@@ -1 +1 @@
<?xml version='1.0' encoding='utf-8'?><resources><color name='ic_launcher_background'>#1F143C</color></resources>
<?xml version='1.0' encoding='utf-8'?><resources><color name='ic_launcher_background'>#ffd700</color></resources>
@@ -71,6 +71,8 @@
<string name="show_power_info_description">Display current power draw and remaining capacity on battery</string>
<string name="show_shaders_building">Show Shaders Building</string>
<string name="show_shaders_building_description">Display current number of shaders being built</string>
<string name="pipeline_worker_cores">Pipeline Worker Threads</string>
<string name="pipeline_worker_cores_description">Manage the amount of cores used for building Vulkan pipelines, the higher value will improve pipeline compilation performance but temperatures will increase as well.</string>
<string name="overlay_position">Overlay Position</string>
<string name="overlay_position_description">Choose where the overlay is displayed on the screen</string>
<string name="overlay_position_top_left">Top Left</string>
@@ -491,15 +493,15 @@
<string name="renderer_force_max_clock">Force maximum clocks (Adreno only)</string>
<string name="renderer_force_max_clock_description">Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied).</string>
<string name="renderer_asynchronous_gpu_emulation">GPU async emulation</string>
<string name="renderer_asynchronous_gpu_emulation_description">Runs GPU emulation asynchronously to reduce CPU stalls and improve throughput. Disable this only if you run into timing-related issues.</string>
<string name="renderer_asynchronous_gpu_emulation_description">This hack can increase performance by running GPU emulation asynchronously at the cost of graphical issues and increased crash rates by timing-related operations.</string>
<string name="renderer_async_presentation">Asynchronous presentation</string>
<string name="renderer_async_presentation_description">Slightly improves performance by moving presentation to a separate CPU thread.</string>
<string name="renderer_async_presentation_description">This hack can increase performance by moving presentation to a separate CPU thread at the cost of graphical issues.</string>
<string name="renderer_reactive_flushing">Use reactive flushing</string>
<string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string>
<string name="enable_buffer_history">Enable buffer history</string>
<string name="enable_buffer_history_description">Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games.</string>
<string name="use_optimized_vertex_buffers">Optimized Vertex Buffers</string>
<string name="use_optimized_vertex_buffers_description">Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers/ QCOM drivers. Will crash on older Turnip drivers.</string>
<string name="use_optimized_vertex_buffers_description">Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers/ QCOM drivers. Will crash on older Turnip drivers (25.3 and below).</string>
<string name="hacks">Hacks</string>
@@ -531,10 +533,10 @@
<string name="extensions">Extensions</string>
<string name="dyna_state">Extended Dynamic State</string>
<string name="dyna_state_description">Controls the number of features that can be used in Extended Dynamic State. Higher numbers allow for more features and can increase performance, but may cause issues with some drivers and vendors.</string>
<string name="dyna_state_description">Controls the number of features that can be used in ExtendedDynamicState (EDS). The higher value will allow to reduce the amount of pipeline compilations based on the dynamic state supported by driver.</string>
<string name="disabled">Disabled</string>
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
<string name="vertex_input_dynamic_state_description">Enables vertex input dynamic state feature for better quality and performance.</string>
<string name="vertex_input_dynamic_state_description">Enabling this feature allows for more flexible vertex input handling, potentially reducing pipeline compilation time in vertex/buffer.</string>
<string name="sample_shading_fraction">Sample Shading</string>
<string name="sample_shading_fraction_description">Allows the fragment shader to execute per sample in a multi-sampled fragment instead once per fragment. Improves graphics quality at the cost of some performance.</string>
+1 -1
View File
@@ -516,7 +516,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;JILjava/lang/String;)V");
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JI)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;");
+3 -3
View File
@@ -389,7 +389,7 @@ struct Values {
true};
SwitchableSetting<bool> use_asynchronous_gpu_emulation{
linkage, true, "use_asynchronous_gpu_emulation", Category::Renderer};
linkage, false, "use_asynchronous_gpu_emulation", Category::Renderer};
// *nix platforms may have issues with the borderless windowed fullscreen mode.
// Default to exclusive fullscreen on these platforms for now.
SwitchableSetting<FullscreenMode, true> fullscreen_mode{linkage,
@@ -542,7 +542,7 @@ struct Values {
true};
SwitchableSetting<bool> async_presentation{linkage,
#ifdef ANDROID
true,
false,
#else
false,
#endif
@@ -609,7 +609,7 @@ struct Values {
#if defined (ANDROID)
false,
#else
true,
false,
#endif
"vertex_input_dynamic_state", Category::RendererExtensions};
+5 -12
View File
@@ -616,18 +616,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
@@ -1165,6 +1153,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
+11 -137
View File
@@ -40,7 +40,6 @@
#include "core/hle/service/am/frontend/applets.h"
#include "core/hle/service/am/process_creation.h"
#include "core/hle/service/apm/apm_controller.h"
#include "core/hle/service/dmnt/cheat_process_manager.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/glue/glue_manager.h"
#include "core/hle/service/glue/time/static.h"
@@ -55,6 +54,7 @@
#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"
@@ -278,17 +278,8 @@ struct System::Impl {
audio_core.emplace(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.emplace(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;
@@ -349,6 +340,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);
@@ -409,6 +405,7 @@ struct System::Impl {
services.reset();
service_manager.reset();
fs_controller.Reset();
cheat_engine.reset();
core_timing.ClearPendingEvents();
app_loader.reset();
audio_core.reset();
@@ -478,6 +475,7 @@ struct System::Impl {
std::optional<Tegra::Host1x::Host1x> host1x_core;
std::optional<Core::DeviceMemory> device_memory;
std::optional<AudioCore::AudioCore> audio_core;
std::optional<Memory::CheatEngine> cheat_engine;
std::optional<Tools::Freezer> memory_freezer;
std::optional<Tools::RenderdocAPI> renderdoc_api;
@@ -497,17 +495,6 @@ struct System::Impl {
std::unique_ptr<Tegra::GPU> gpu_core;
std::stop_source stop_event;
/// 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;
mutable std::mutex suspend_guard;
std::mutex general_channel_mutex;
std::atomic_bool is_paused{};
@@ -525,61 +512,6 @@ struct System::Impl {
general_channel_event.emplace(*general_channel_context);
}
}
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)} {}
@@ -817,61 +749,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.emplace(*this, list, build_id);
impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size);
}
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) {
@@ -1011,14 +893,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));
}
+2 -10
View File
@@ -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 {
@@ -340,7 +335,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);
@@ -381,9 +376,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;
+284 -324
View File
@@ -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,36 +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) {
@@ -130,7 +130,8 @@ std::string GetUpdateVersionStringFromSlot(const ContentProvider* provider, u64
}
auto control_nca = provider->GetEntry(update_tid, ContentRecordType::Control);
if (control_nca == nullptr || control_nca->GetStatus() != Loader::ResultStatus::Success) {
if (control_nca == nullptr ||
control_nca->GetStatus() != Loader::ResultStatus::Success) {
return {};
}
@@ -168,11 +169,16 @@ u64 PatchManager::GetTitleID() const {
return title_id;
}
PatchManager::UpdateResolution PatchManager::GetActiveUpdate(ContentRecordType type) const {
UpdateResolution update_res{};
VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
if (exefs == nullptr)
return exefs;
const auto& disabled = Settings::values.disabled_addons[title_id];
bool update_disabled = true;
std::optional<u32> enabled_version;
bool checked_external = false;
bool checked_manual = false;
@@ -189,10 +195,8 @@ PatchManager::UpdateResolution PatchManager::GetActiveUpdate(ContentRecordType t
checked_external = true;
for (const auto& update_entry : update_versions) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_res.update_disabled = false;
update_res.update_active_version = update_entry.version;
update_res.update_active_raw = external_provider->GetEntryForVersion(
update_tid, type, update_entry.version);
update_disabled = false;
enabled_version = update_entry.version;
break;
}
}
@@ -210,10 +214,8 @@ PatchManager::UpdateResolution PatchManager::GetActiveUpdate(ContentRecordType t
checked_manual = true;
for (const auto& update_entry : manual_update_versions) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_res.update_disabled = false;
update_res.update_active_version = update_entry.version;
update_res.update_active_raw = manual_provider->GetEntryForVersion(
update_tid, type, update_entry.version);
update_disabled = false;
enabled_version = update_entry.version;
break;
}
}
@@ -225,41 +227,37 @@ PatchManager::UpdateResolution PatchManager::GetActiveUpdate(ContentRecordType t
// check for original NAND style
// Check NAND if: no external updates exist, OR all external updates are disabled
if (!checked_external && !checked_manual) {
const bool nand_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend();
const bool sdmc_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update (SDMC)") != disabled.cend();
const bool generic_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
// Only enable NAND update if it exists AND is not disabled
// We need to check if an update actually exists in the content provider
const bool has_nand_update = content_provider.HasEntry(update_tid, ContentRecordType::Program);
if (!nand_disabled && !sdmc_disabled && !generic_disabled) {
update_res.update_active_raw = content_provider.GetEntryRaw(update_tid, type);
update_res.update_disabled = update_res.update_active_raw == nullptr;
if (has_nand_update) {
const bool nand_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend();
const bool sdmc_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (SDMC)") != disabled.cend();
const bool generic_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
if (!nand_disabled && !sdmc_disabled && !generic_disabled) {
update_disabled = false;
}
}
} else if (update_res.update_disabled && content_union) {
const bool nand_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend();
const bool sdmc_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update (SDMC)") != disabled.cend();
} else if (update_disabled && content_union) {
const bool nand_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend();
const bool sdmc_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (SDMC)") != disabled.cend();
if (!nand_disabled || !sdmc_disabled) {
const auto nand_sdmc_entries = content_union->ListEntriesFilterOrigin(
std::nullopt, TitleType::Update, type, update_tid);
std::nullopt, TitleType::Update, ContentRecordType::Program, update_tid);
for (const auto& [slot, entry] : nand_sdmc_entries) {
if (slot == ContentProviderUnionSlot::UserNAND ||
slot == ContentProviderUnionSlot::SysNAND) {
if (!nand_disabled) {
update_res.update_active_raw =
content_provider.GetEntryRaw(update_tid, type);
update_res.update_disabled = update_res.update_active_raw == nullptr;
update_disabled = false;
break;
}
} else if (slot == ContentProviderUnionSlot::SDMC) {
if (!sdmc_disabled) {
update_res.update_active_raw =
content_provider.GetEntryRaw(update_tid, type);
update_res.update_disabled = update_res.update_active_raw == nullptr;
update_disabled = false;
break;
}
}
@@ -267,36 +265,44 @@ PatchManager::UpdateResolution PatchManager::GetActiveUpdate(ContentRecordType t
}
}
return update_res;
}
VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
if (exefs == nullptr)
return exefs;
const auto update_tid = GetUpdateTitleID(title_id);
const auto update_res = GetActiveUpdate();
// Game Updates
std::unique_ptr<NCA> update = nullptr;
if (update_res.update_active_version.has_value() && update_res.update_active_raw != nullptr) {
update = std::make_unique<NCA>(update_res.update_active_raw);
// If we have a specific enabled version from external provider, use it
if (enabled_version.has_value() && content_union) {
const auto* external_provider = content_union->GetExternalProvider();
if (external_provider) {
auto file = external_provider->GetEntryForVersion(update_tid, ContentRecordType::Program, *enabled_version);
if (file != nullptr) {
update = std::make_unique<NCA>(file);
}
}
// Also try ManualContentProvider
if (update == nullptr) {
const auto* manual_provider = static_cast<const ManualContentProvider*>(
content_union->GetSlotProvider(ContentProviderUnionSlot::FrontendManual));
if (manual_provider) {
auto file = manual_provider->GetEntryForVersion(update_tid, ContentRecordType::Program, *enabled_version);
if (file != nullptr) {
update = std::make_unique<NCA>(file);
}
}
}
}
if (update == nullptr && !update_res.update_disabled) {
// Fallback to regular content provider if no external update was loaded
if (update == nullptr && !update_disabled) {
update = content_provider.GetEntry(update_tid, ContentRecordType::Program);
}
if (!update_res.update_disabled && update != nullptr && update->GetExeFS() != nullptr) {
if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) {
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
exefs = update->GetExeFS();
}
// LayeredExeFS
const auto& disabled = Settings::values.disabled_addons[title_id];
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
@@ -462,8 +468,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);
@@ -472,72 +477,35 @@ std::vector<Service::DMNT::CheatEntry> PatchManager::CreateCheatList(
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(); });
// Load cheats from: <mod dir>/<folder>/cheats/<build_id>.txt
std::vector<Service::DMNT::CheatEntry> out;
// <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
for (auto const& f : load_dir->GetFiles()) {
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;
}
@@ -629,22 +597,105 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
auto romfs = base_romfs;
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
const auto update_res = GetActiveUpdate(type);
const auto& disabled = Settings::values.disabled_addons[title_id];
if (!update_res.update_disabled && update_res.update_active_raw != nullptr &&
base_nca != nullptr) {
const auto new_nca = std::make_shared<NCA>(update_res.update_active_raw, base_nca);
bool update_disabled = true;
std::optional<u32> enabled_version;
VirtualFile update_raw = nullptr;
bool checked_external = false;
bool checked_manual = false;
const auto* content_union = static_cast<const ContentProviderUnion*>(&content_provider);
if (content_union) {
// First, check ExternalContentProvider
const auto* external_provider = content_union->GetExternalProvider();
if (external_provider) {
const auto update_versions = external_provider->ListUpdateVersions(update_tid);
if (!update_versions.empty()) {
checked_external = true;
for (const auto& update_entry : update_versions) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_disabled = false;
enabled_version = update_entry.version;
update_raw = external_provider->GetEntryForVersion(update_tid, type, update_entry.version);
break;
}
}
}
}
if (!checked_external) {
const auto* manual_provider = static_cast<const ManualContentProvider*>(
content_union->GetSlotProvider(ContentProviderUnionSlot::FrontendManual));
if (manual_provider) {
const auto manual_update_versions = manual_provider->ListUpdateVersions(update_tid);
if (!manual_update_versions.empty()) {
checked_manual = true;
for (const auto& update_entry : manual_update_versions) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_disabled = false;
enabled_version = update_entry.version;
update_raw = manual_provider->GetEntryForVersion(update_tid, type, update_entry.version);
break;
}
}
}
}
}
}
if (!checked_external && !checked_manual) {
const bool nand_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend();
const bool sdmc_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (SDMC)") != disabled.cend();
const bool generic_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
if (!nand_disabled && !sdmc_disabled && !generic_disabled) {
update_disabled = false;
}
if (!update_disabled) {
update_raw = content_provider.GetEntryRaw(update_tid, type);
}
} else if (update_disabled && content_union) {
const bool nand_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend();
const bool sdmc_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (SDMC)") != disabled.cend();
if (!nand_disabled || !sdmc_disabled) {
const auto nand_sdmc_entries = content_union->ListEntriesFilterOrigin(
std::nullopt, TitleType::Update, type, update_tid);
for (const auto& [slot, entry] : nand_sdmc_entries) {
if (slot == ContentProviderUnionSlot::UserNAND ||
slot == ContentProviderUnionSlot::SysNAND) {
if (!nand_disabled) {
update_disabled = false;
update_raw = content_provider.GetEntryRaw(update_tid, type);
break;
}
} else if (slot == ContentProviderUnionSlot::SDMC) {
if (!sdmc_disabled) {
update_disabled = false;
update_raw = content_provider.GetEntryRaw(update_tid, type);
break;
}
}
}
}
}
if (!update_disabled && update_raw != nullptr && base_nca != nullptr) {
const auto new_nca = std::make_shared<NCA>(update_raw, base_nca);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
LOG_INFO(
Loader, " RomFS: Update ({}) applied successfully",
update_res.update_active_version.has_value()
? FormatTitleVersion(*update_res.update_active_version)
: FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
enabled_version.has_value() ? FormatTitleVersion(*enabled_version) :
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
romfs = new_nca->GetRomFS();
}
} else if (!update_res.update_disabled && packed_update_raw != nullptr && base_nca != nullptr) {
} else if (!update_disabled && packed_update_raw != nullptr && base_nca != nullptr) {
const auto new_nca = std::make_shared<NCA>(packed_update_raw, base_nca);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
@@ -661,53 +712,6 @@ 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_res = GetActiveUpdate();
if (!update_res.update_disabled && update_res.update_active_raw != nullptr) {
const auto update = std::make_shared<NCA>(update_res.update_active_raw, base_nca.get());
if (update->GetStatus() == Loader::ResultStatus::Success && 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 {
if (title_id == 0) {
return {};
@@ -745,8 +749,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
.program_id = title_id,
.title_id = update_tid,
.source = PatchSource::External,
.numeric_version = update_entry.version,
.parent_name = ""};
.numeric_version = update_entry.version};
external_update_patches.push_back(update_patch);
}
@@ -766,6 +769,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
const auto update_disabled =
IsVersionedExternalUpdateDisabled(disabled, update_entry.version);
Patch update_patch = {.enabled = !update_disabled,
.name = "Update",
.version = version_str,
@@ -810,17 +814,17 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
std::string source_suffix;
switch (slot) {
case ContentProviderUnionSlot::UserNAND:
case ContentProviderUnionSlot::SysNAND:
source_type = PatchSource::NAND;
source_suffix = " (NAND)";
break;
case ContentProviderUnionSlot::SDMC:
source_type = PatchSource::SDMC;
source_suffix = " (SDMC)";
break;
default:
break;
case ContentProviderUnionSlot::UserNAND:
case ContentProviderUnionSlot::SysNAND:
source_type = PatchSource::NAND;
source_suffix = " (NAND)";
break;
case ContentProviderUnionSlot::SDMC:
source_type = PatchSource::SDMC;
source_suffix = " (SDMC)";
break;
default:
break;
}
std::string version_str;
@@ -891,137 +895,94 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
}
}
// Check if we have a valid build_id for cheat enumeration
const auto build_id = GetBuildID(update_raw);
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);
const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
if (mod_dir != nullptr) {
for (auto const& f : mod_dir->GetFiles())
if (auto const name = f->GetName(); name.starts_with("cheat_")) {
auto const mod_disabled = std::find(disabled.begin(), disabled.end(), name) != disabled.end();
out.push_back({
.enabled = !mod_disabled,
.name = name,
.version = "Cheats",
.type = PatchType::Mod,
.program_id = title_id,
.title_id = title_id,
.source = PatchSource::Unknown,
.location = f->GetFullPath(),
});
}
for (const auto& root : std::array{mod_dir, sdmc_mod_dir}) {
if (root == nullptr)
continue;
const bool is_sdmc = (root == sdmc_mod_dir);
const std::vector<VirtualDir> mods =
is_sdmc ? std::vector{root} : root->GetSubdirectories();
for (const auto& mod : mods) {
std::string mod_name = is_sdmc ? "SDMC" : mod->GetName();
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)) {
if (is_sdmc) {
AppendCommaIfNotEmpty(types, "LayeredExeFS");
} else {
bool ips = false, ipswitch = false, layeredfs = false;
for (const auto& file : exefs_dir->GetFiles()) {
if (file->GetExtension() == "ips")
ips = true;
else if (file->GetExtension() == "pchtxt")
ipswitch = true;
else if (std::find(EXEFS_FILE_NAMES.begin(), EXEFS_FILE_NAMES.end(),
file->GetName()) != EXEFS_FILE_NAMES.end())
layeredfs = true;
}
if (ips)
AppendCommaIfNotEmpty(types, "IPS");
if (ipswitch)
AppendCommaIfNotEmpty(types, "IPSwitch");
if (layeredfs)
AppendCommaIfNotEmpty(types, "LayeredExeFS");
}
}
bool ips = false;
bool ipswitch = false;
bool layeredfs = false;
for (const auto& file : exefs_dir->GetFiles()) {
if (file->GetExtension() == "ips") {
ips = true;
} else if (file->GetExtension() == "pchtxt") {
ipswitch = true;
} else if (std::find(EXEFS_FILE_NAMES.begin(), EXEFS_FILE_NAMES.end(),
file->GetName()) != EXEFS_FILE_NAMES.end()) {
layeredfs = true;
}
}
if (ips)
AppendCommaIfNotEmpty(types, "IPS");
if (ipswitch)
AppendCommaIfNotEmpty(types, "IPSwitch");
if (layeredfs)
AppendCommaIfNotEmpty(types, "LayeredExeFS");
}
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 (has_cheats && is_sdmc)
mod_name = "Atmosphere";
if (types.empty())
continue;
const auto mod_disabled =
std::find(disabled.begin(), disabled.end(), mod_name) != disabled.end();
const auto mod_disabled = std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end();
out.push_back({.enabled = !mod_disabled,
.name = mod_name,
.name = mod->GetName(),
.version = types,
.type = PatchType::Mod,
.program_id = title_id,
.title_id = title_id,
.source = PatchSource::Unknown,
.location = mod->GetFullPath(),
.parent_name = ""});
.location = mod->GetFullPath()});
}
}
// Add individual cheats as sub-entries
if (has_cheats) {
std::vector<Service::DMNT::CheatEntry> cheat_entries;
if (has_build_id) {
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.empty()) {
for (const auto& cheat : cheat_entries) {
if (cheat.cheat_id <= 1 || cheat.definition.readable_name[0] == '\0')
continue;
const std::string cheat_name = cheat.definition.readable_name.data();
const std::string cheat_key =
(is_sdmc ? "SDMC" : mod->GetName()) + "::" + cheat_name;
out.push_back({
.enabled = std::find(disabled.begin(), disabled.end(), cheat_key) ==
disabled.end(),
.name = cheat_name,
.version = types,
.type = PatchType::Cheat,
.program_id = title_id,
.title_id = title_id,
.parent_name = mod_name,
.cheat_compat = CheatCompatibility::Compatible,
});
}
} else {
std::string title;
for (const auto& cheat_file : cheats_dir->GetFiles()) {
if (!cheat_file->GetName().ends_with(".txt"))
continue;
std::vector<u8> data(cheat_file->GetSize());
if (cheat_file->Read(data.data(), data.size()) == data.size()) {
const auto entries =
Service::DMNT::CheatParser{}.Parse(std::string_view(
reinterpret_cast<const char*>(data.data()), data.size()));
if (entries.size() > 1)
title = entries[1].definition.readable_name.data();
}
break;
}
out.push_back({
.enabled = false,
.name = title.empty() ? "Incompatible cheat"
: fmt::format("Incompatible cheat: {}", title),
.version = types,
.type = PatchType::Cheat,
.program_id = title_id,
.title_id = title_id,
.parent_name = mod_name,
.cheat_compat = CheatCompatibility::Incompatible,
});
}
// SDMC mod directory (RomFS LayeredFS)
const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
if (sdmc_mod_dir != nullptr) {
std::string types;
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) {
AppendCommaIfNotEmpty(types, "LayeredExeFS");
}
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs")) ||
IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfslite"))) {
AppendCommaIfNotEmpty(types, "LayeredFS");
}
if (!types.empty()) {
const auto mod_disabled =
std::find(disabled.begin(), disabled.end(), "SDMC") != disabled.end();
out.push_back({.enabled = !mod_disabled,
.name = "SDMC",
.version = types,
.type = PatchType::Mod,
.program_id = title_id,
.title_id = title_id,
.source = PatchSource::Unknown});
}
}
@@ -1031,8 +992,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
bool has_nand_dlc = false;
bool has_sdmc_dlc = false;
bool has_other_dlc = false;
const auto dlc_entries_with_origin = content_union->ListEntriesFilterOrigin(
std::nullopt, TitleType::AOC, ContentRecordType::Data);
const auto dlc_entries_with_origin =
content_union->ListEntriesFilterOrigin(std::nullopt, TitleType::AOC, ContentRecordType::Data);
dlc_match.reserve(dlc_entries_with_origin.size());
for (const auto& [slot, entry] : dlc_entries_with_origin) {
@@ -1063,20 +1024,20 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
}
switch (slot) {
case ContentProviderUnionSlot::External:
case ContentProviderUnionSlot::FrontendManual:
has_external_dlc = true;
break;
case ContentProviderUnionSlot::UserNAND:
case ContentProviderUnionSlot::SysNAND:
has_nand_dlc = true;
break;
case ContentProviderUnionSlot::SDMC:
has_sdmc_dlc = true;
break;
default:
has_other_dlc = true;
break;
case ContentProviderUnionSlot::External:
case ContentProviderUnionSlot::FrontendManual:
has_external_dlc = true;
break;
case ContentProviderUnionSlot::UserNAND:
case ContentProviderUnionSlot::SysNAND:
has_nand_dlc = true;
break;
case ContentProviderUnionSlot::SDMC:
has_sdmc_dlc = true;
break;
default:
has_other_dlc = true;
break;
}
dlc_match.push_back(entry);
}
@@ -1108,8 +1069,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
.type = PatchType::DLC,
.program_id = title_id,
.title_id = dlc_match.back().title_id,
.source = dlc_source,
.parent_name = ""});
.source = dlc_source});
}
return out;
+4 -25
View File
@@ -10,10 +10,10 @@
#include <memory>
#include <optional>
#include <string>
#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;
@@ -23,17 +23,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 };
enum class PatchSource {
Unknown,
@@ -43,11 +39,6 @@ enum class PatchSource {
Packed,
};
enum class CheatCompatibility : u8 {
Incompatible,
Compatible,
};
struct Patch {
bool enabled;
std::string name;
@@ -58,8 +49,6 @@ struct Patch {
PatchSource source;
std::string location;
u32 numeric_version{0};
std::string parent_name;
CheatCompatibility cheat_compat{CheatCompatibility::Incompatible};
};
// A centralized class to manage patches to games.
@@ -90,7 +79,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:
@@ -101,11 +90,9 @@ public:
VirtualFile packed_update_raw = nullptr,
bool apply_layeredfs = true) const;
// Returns a vector of patches including individual cheats
// Returns a vector of patches
[[nodiscard]] std::vector<Patch> GetPatches(VirtualFile update_raw = nullptr) const;
[[nodiscard]] BuildID GetBuildID(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
// std::nullopt
@@ -122,14 +109,6 @@ private:
[[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) const;
struct UpdateResolution {
bool update_disabled{true};
VirtualFile update_active_raw;
std::optional<u32> update_active_version;
};
[[nodiscard]] UpdateResolution GetActiveUpdate(
ContentRecordType type = ContentRecordType::Program) const;
u64 title_id;
const Service::FileSystem::FileSystemController& fs_controller;
const ContentProvider& content_provider;
@@ -1,238 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 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 2026 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
-121
View File
@@ -1,121 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 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
-26
View File
@@ -1,26 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 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 2026 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 2026 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
-26
View File
@@ -1,26 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 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
-15
View File
@@ -1,15 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 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
-26
View File
@@ -1,26 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 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
-55
View File
@@ -1,55 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 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 -3
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -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},
+37
View File
@@ -5,6 +5,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <cinttypes>
#include <cstring>
#include <vector>
@@ -44,6 +45,40 @@ static_assert(sizeof(MODHeader) == 0x1c, "MODHeader has incorrect size.");
constexpr u32 PageAlignSize(u32 size) {
return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
}
constexpr u64 PaperMarioTTYDProgramId = 0x0100ECD018EBE000ULL;
constexpr u32 PaperMarioTTYDTrapOffset = 0x112F50;
bool IsPaperMarioTTYD(u64 program_id) {
return (program_id & ~0xFFFULL) == PaperMarioTTYDProgramId;
}
void ApplyPaperMarioTTYDWorkaround(const Kernel::KProcess& process, std::string_view module_name,
std::span<u8> image, size_t module_start) {
static constexpr std::array<u8, 8> kTrapThenRet{
0xFE, 0xDE, 0xFF, 0xE7, 0xC0, 0x03, 0x5F, 0xD6,
};
static constexpr std::array<u8, 4> kNop{
0x1F, 0x20, 0x03, 0xD5,
};
if (!IsPaperMarioTTYD(process.GetProgramId()) || module_name != "sdk") {
return;
}
const size_t trap_offset = module_start + PaperMarioTTYDTrapOffset;
if (trap_offset + kTrapThenRet.size() > image.size()) {
return;
}
if (!std::equal(kTrapThenRet.begin(), kTrapThenRet.end(), image.begin() + trap_offset)) {
return;
}
std::copy(kNop.begin(), kNop.end(), image.begin() + trap_offset);
LOG_WARNING(Loader, "Applied Paper Mario TTYD crash workaround for {:016X} at nnSdk+{:#x}",
process.GetProgramId(), PaperMarioTTYDTrapOffset);
}
} // Anonymous namespace
bool NSOHeader::IsSegmentCompressed(size_t segment_num) const {
@@ -149,6 +184,8 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), patchable_section.data());
}
ApplyPaperMarioTTYDWorkaround(process, name, codeset.memory, module_start);
#ifdef HAS_NCE
// If we are computing the process code layout and using nce backend, patch.
const auto& code = codeset.CodeSegment();
+288
View File
@@ -0,0 +1,288 @@
// 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
#include <locale>
#include "common/hex_util.h"
#include "common/swap.h"
#include "core/arm/debug.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_process_page_table.h"
#include "core/hle/kernel/svc_types.h"
#include "core/hle/service/hid/hid_server.h"
#include "core/hle/service/sm/sm.h"
#include "core/memory.h"
#include "core/memory/cheat_engine.h"
#include "hid_core/resource_manager.h"
#include "hid_core/resources/npad/npad.h"
namespace Core::Memory {
namespace {
constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
std::string_view ExtractName(std::size_t& out_name_size, std::string_view data,
std::size_t start_index, char match) {
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);
}
} // Anonymous namespace
StandardVmCallbacks::StandardVmCallbacks(System& system_, const CheatProcessMetadata& metadata_)
: metadata{metadata_}, system{system_} {}
StandardVmCallbacks::~StandardVmCallbacks() = default;
void StandardVmCallbacks::MemoryReadUnsafe(VAddr address, void* data, u64 size) {
// Return zero on invalid address
if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) {
std::memset(data, 0, size);
return;
}
system.ApplicationMemory().ReadBlock(address, data, size);
}
void StandardVmCallbacks::MemoryWriteUnsafe(VAddr address, const void* data, u64 size) {
// Skip invalid memory write address
if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) {
return;
}
if (system.ApplicationMemory().WriteBlock(address, data, size)) {
Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), address, size);
}
}
u64 StandardVmCallbacks::HidKeysDown() {
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 & HID::NpadButton::All);
}
void StandardVmCallbacks::PauseProcess() {
if (system.ApplicationProcess()->IsSuspended()) {
return;
}
system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Paused);
}
void StandardVmCallbacks::ResumeProcess() {
if (!system.ApplicationProcess()->IsSuspended()) {
return;
}
system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Runnable);
}
void StandardVmCallbacks::DebugLog(u8 id, u64 value) {
LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value);
}
void StandardVmCallbacks::CommandLog(std::string_view data) {
LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}",
data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
}
bool StandardVmCallbacks::IsAddressInRange(VAddr in) const {
if ((in < metadata.main_nso_extents.base ||
in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) &&
(in < metadata.heap_extents.base ||
in >= metadata.heap_extents.base + metadata.heap_extents.size) &&
(in < metadata.alias_extents.base ||
in >= metadata.alias_extents.base + metadata.alias_extents.size) &&
(in < metadata.aslr_extents.base ||
in >= metadata.aslr_extents.base + metadata.aslr_extents.size)) {
LOG_DEBUG(CheatEngine,
"Cheat attempting to access memory at invalid address={:016X}, if this "
"persists, "
"the cheat may be incorrect. However, this may be normal early in execution if "
"the game has not properly set up yet.",
in);
return false; ///< Invalid addresses will hard crash
}
return true;
}
CheatParser::~CheatParser() = default;
TextCheatParser::~TextCheatParser() = default;
std::vector<CheatEntry> TextCheatParser::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 (::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 (::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 += 8;
} 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;
}
CheatEngine::CheatEngine(System& system_, std::vector<CheatEntry> cheats_,
const std::array<u8, 0x20>& build_id_)
: vm{std::make_unique<StandardVmCallbacks>(system_, metadata)},
cheats(std::move(cheats_)), core_timing{system_.CoreTiming()}, system{system_} {
metadata.main_nso_build_id = build_id_;
}
CheatEngine::~CheatEngine() {
if (event)
core_timing.UnscheduleEvent(event);
else
LOG_ERROR(CheatEngine, "~CheatEngine before event was registered");
}
void CheatEngine::Initialize() {
event = Core::Timing::CreateEvent(
"CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
[this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
FrameCallback(ns_late);
return std::nullopt;
});
core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, event);
metadata.process_id = system.ApplicationProcess()->GetProcessId();
metadata.title_id = system.GetApplicationProcessProgramID();
const auto& page_table = system.ApplicationProcess()->GetPageTable();
metadata.heap_extents = {
.base = GetInteger(page_table.GetHeapRegionStart()),
.size = page_table.GetHeapRegionSize(),
};
metadata.aslr_extents = {
.base = GetInteger(page_table.GetAliasCodeRegionStart()),
.size = page_table.GetAliasCodeRegionSize(),
};
metadata.alias_extents = {
.base = GetInteger(page_table.GetAliasRegionStart()),
.size = page_table.GetAliasRegionSize(),
};
is_pending_reload.exchange(true);
}
void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) {
metadata.main_nso_extents = {
.base = main_region_begin,
.size = main_region_size,
};
}
void CheatEngine::Reload(std::vector<CheatEntry> reload_cheats) {
cheats = std::move(reload_cheats);
is_pending_reload.exchange(true);
}
void CheatEngine::FrameCallback(std::chrono::nanoseconds ns_late) {
if (is_pending_reload.exchange(false)) {
vm.LoadProgram(cheats);
}
if (vm.GetProgramSize() == 0) {
return;
}
vm.Execute(metadata);
}
} // namespace Core::Memory
+88
View File
@@ -0,0 +1,88 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <chrono>
#include <memory>
#include <vector>
#include "common/common_types.h"
#include "core/memory/dmnt_cheat_types.h"
#include "core/memory/dmnt_cheat_vm.h"
namespace Core {
class System;
}
namespace Core::Timing {
class CoreTiming;
struct EventType;
} // namespace Core::Timing
namespace Core::Memory {
class StandardVmCallbacks : public DmntCheatVm::Callbacks {
public:
StandardVmCallbacks(System& system_, const CheatProcessMetadata& metadata_);
~StandardVmCallbacks() override;
void MemoryReadUnsafe(VAddr address, void* data, u64 size) override;
void MemoryWriteUnsafe(VAddr address, const void* data, u64 size) override;
u64 HidKeysDown() override;
void PauseProcess() override;
void ResumeProcess() override;
void DebugLog(u8 id, u64 value) override;
void CommandLog(std::string_view data) override;
private:
bool IsAddressInRange(VAddr address) const;
const CheatProcessMetadata& metadata;
Core::System& system;
};
// Intermediary class that parses a text file or other disk format for storing cheats into a
// CheatList object, that can be used for execution.
class CheatParser {
public:
virtual ~CheatParser();
[[nodiscard]] virtual std::vector<CheatEntry> Parse(std::string_view data) const = 0;
};
// CheatParser implementation that parses text files
class TextCheatParser final : public CheatParser {
public:
~TextCheatParser() override;
[[nodiscard]] std::vector<CheatEntry> Parse(std::string_view data) const override;
};
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
class CheatEngine final {
public:
CheatEngine(System& system_, std::vector<CheatEntry> cheats_,
const std::array<u8, 0x20>& build_id_);
~CheatEngine();
void Initialize();
void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size);
void Reload(std::vector<CheatEntry> reload_cheats);
private:
void FrameCallback(std::chrono::nanoseconds ns_late);
DmntCheatVm vm;
CheatProcessMetadata metadata;
std::vector<CheatEntry> cheats;
std::atomic_bool is_pending_reload{false};
std::shared_ptr<Core::Timing::EventType> event;
Core::Timing::CoreTiming& core_timing;
Core::System& system;
};
} // namespace Core::Memory
+37
View File
@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
namespace Core::Memory {
struct MemoryRegionExtents {
u64 base{};
u64 size{};
};
struct CheatProcessMetadata {
u64 process_id{};
u64 title_id{};
MemoryRegionExtents main_nso_extents{};
MemoryRegionExtents heap_extents{};
MemoryRegionExtents alias_extents{};
MemoryRegionExtents aslr_extents{};
std::array<u8, 0x20> main_nso_build_id{};
};
struct CheatDefinition {
std::array<char, 0x40> readable_name{};
u32 num_opcodes{};
std::array<u32, 0x100> opcodes{};
};
struct CheatEntry {
bool enabled{};
u32 cheat_id{};
CheatDefinition definition{};
};
} // namespace Core::Memory
@@ -1,223 +1,226 @@
// SPDX-FileCopyrightText: Copyright 2026 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
#include <fmt/printf.h>
#include "common/assert.h"
#include "common/scope_exit.h"
#include "core/hle/service/dmnt/cheat_process_manager.h"
#include "core/hle/service/dmnt/cheat_virtual_machine.h"
#include "core/memory/dmnt_cheat_types.h"
#include "core/memory/dmnt_cheat_vm.h"
namespace Service::DMNT {
CheatVirtualMachine::CheatVirtualMachine(CheatProcessManager& cheat_manager)
: manager(cheat_manager) {}
namespace Core::Memory {
CheatVirtualMachine::~CheatVirtualMachine() = default;
DmntCheatVm::DmntCheatVm(std::unique_ptr<Callbacks> callbacks_)
: callbacks(std::move(callbacks_)) {}
void CheatVirtualMachine::DebugLog(u32 log_id, u64 value) const {
manager.DebugLog(static_cast<u8>(log_id), value);
DmntCheatVm::~DmntCheatVm() = default;
void DmntCheatVm::DebugLog(u32 log_id, u64 value) {
callbacks->DebugLog(static_cast<u8>(log_id), value);
}
void CheatVirtualMachine::LogOpcode(const CheatVmOpcode& opcode) const {
void DmntCheatVm::LogOpcode(const CheatVmOpcode& opcode) {
if (auto store_static = std::get_if<StoreStaticOpcode>(&opcode.opcode)) {
manager.CommandLog("Opcode: Store Static");
manager.CommandLog(fmt::format("Bit Width: {:X}", store_static->bit_width));
manager.CommandLog(
callbacks->CommandLog("Opcode: Store Static");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", store_static->bit_width));
callbacks->CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(store_static->mem_type)));
manager.CommandLog(fmt::format("Reg Idx: {:X}", store_static->offset_register));
manager.CommandLog(fmt::format("Rel Addr: {:X}", store_static->rel_address));
manager.CommandLog(fmt::format("Value: {:X}", store_static->value.bit64));
callbacks->CommandLog(fmt::format("Reg Idx: {:X}", store_static->offset_register));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", store_static->rel_address));
callbacks->CommandLog(fmt::format("Value: {:X}", store_static->value.bit64));
} else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&opcode.opcode)) {
manager.CommandLog("Opcode: Begin Conditional");
manager.CommandLog(fmt::format("Bit Width: {:X}", begin_cond->bit_width));
manager.CommandLog(fmt::format("Mem Type: {:X}", static_cast<u32>(begin_cond->mem_type)));
manager.CommandLog(fmt::format("Cond Type: {:X}", static_cast<u32>(begin_cond->cond_type)));
manager.CommandLog(fmt::format("Rel Addr: {:X}", begin_cond->rel_address));
manager.CommandLog(fmt::format("Value: {:X}", begin_cond->value.bit64));
callbacks->CommandLog("Opcode: Begin Conditional");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_cond->bit_width));
callbacks->CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(begin_cond->mem_type)));
callbacks->CommandLog(
fmt::format("Cond Type: {:X}", static_cast<u32>(begin_cond->cond_type)));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_cond->rel_address));
callbacks->CommandLog(fmt::format("Value: {:X}", begin_cond->value.bit64));
} else if (std::holds_alternative<EndConditionalOpcode>(opcode.opcode)) {
manager.CommandLog("Opcode: End Conditional");
callbacks->CommandLog("Opcode: End Conditional");
} else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&opcode.opcode)) {
if (ctrl_loop->start_loop) {
manager.CommandLog("Opcode: Start Loop");
manager.CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index));
manager.CommandLog(fmt::format("Num Iters: {:X}", ctrl_loop->num_iters));
callbacks->CommandLog("Opcode: Start Loop");
callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index));
callbacks->CommandLog(fmt::format("Num Iters: {:X}", ctrl_loop->num_iters));
} else {
manager.CommandLog("Opcode: End Loop");
manager.CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index));
callbacks->CommandLog("Opcode: End Loop");
callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index));
}
} else if (auto ldr_static = std::get_if<LoadRegisterStaticOpcode>(&opcode.opcode)) {
manager.CommandLog("Opcode: Load Register Static");
manager.CommandLog(fmt::format("Reg Idx: {:X}", ldr_static->reg_index));
manager.CommandLog(fmt::format("Value: {:X}", ldr_static->value));
callbacks->CommandLog("Opcode: Load Register Static");
callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_static->reg_index));
callbacks->CommandLog(fmt::format("Value: {:X}", ldr_static->value));
} else if (auto ldr_memory = std::get_if<LoadRegisterMemoryOpcode>(&opcode.opcode)) {
manager.CommandLog("Opcode: Load Register Memory");
manager.CommandLog(fmt::format("Bit Width: {:X}", ldr_memory->bit_width));
manager.CommandLog(fmt::format("Reg Idx: {:X}", ldr_memory->reg_index));
manager.CommandLog(fmt::format("Mem Type: {:X}", static_cast<u32>(ldr_memory->mem_type)));
manager.CommandLog(fmt::format("From Reg: {:d}", ldr_memory->load_from_reg));
manager.CommandLog(fmt::format("Rel Addr: {:X}", ldr_memory->rel_address));
callbacks->CommandLog("Opcode: Load Register Memory");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", ldr_memory->bit_width));
callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_memory->reg_index));
callbacks->CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(ldr_memory->mem_type)));
callbacks->CommandLog(fmt::format("From Reg: {:d}", ldr_memory->load_from_reg));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", ldr_memory->rel_address));
} else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&opcode.opcode)) {
manager.CommandLog("Opcode: Store Static to Address");
manager.CommandLog(fmt::format("Bit Width: {:X}", str_static->bit_width));
manager.CommandLog(fmt::format("Reg Idx: {:X}", str_static->reg_index));
callbacks->CommandLog("Opcode: Store Static to Address");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_static->bit_width));
callbacks->CommandLog(fmt::format("Reg Idx: {:X}", str_static->reg_index));
if (str_static->add_offset_reg) {
manager.CommandLog(fmt::format("O Reg Idx: {:X}", str_static->offset_reg_index));
callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_static->offset_reg_index));
}
manager.CommandLog(fmt::format("Incr Reg: {:d}", str_static->increment_reg));
manager.CommandLog(fmt::format("Value: {:X}", str_static->value));
callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_static->increment_reg));
callbacks->CommandLog(fmt::format("Value: {:X}", str_static->value));
} else if (auto perform_math_static =
std::get_if<PerformArithmeticStaticOpcode>(&opcode.opcode)) {
manager.CommandLog("Opcode: Perform Static Arithmetic");
manager.CommandLog(fmt::format("Bit Width: {:X}", perform_math_static->bit_width));
manager.CommandLog(fmt::format("Reg Idx: {:X}", perform_math_static->reg_index));
manager.CommandLog(
callbacks->CommandLog("Opcode: Perform Static Arithmetic");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_static->bit_width));
callbacks->CommandLog(fmt::format("Reg Idx: {:X}", perform_math_static->reg_index));
callbacks->CommandLog(
fmt::format("Math Type: {:X}", static_cast<u32>(perform_math_static->math_type)));
manager.CommandLog(fmt::format("Value: {:X}", perform_math_static->value));
callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_static->value));
} else if (auto begin_keypress_cond =
std::get_if<BeginKeypressConditionalOpcode>(&opcode.opcode)) {
manager.CommandLog("Opcode: Begin Keypress Conditional");
manager.CommandLog(fmt::format("Key Mask: {:X}", begin_keypress_cond->key_mask));
callbacks->CommandLog("Opcode: Begin Keypress Conditional");
callbacks->CommandLog(fmt::format("Key Mask: {:X}", begin_keypress_cond->key_mask));
} else if (auto perform_math_reg =
std::get_if<PerformArithmeticRegisterOpcode>(&opcode.opcode)) {
manager.CommandLog("Opcode: Perform Register Arithmetic");
manager.CommandLog(fmt::format("Bit Width: {:X}", perform_math_reg->bit_width));
manager.CommandLog(fmt::format("Dst Idx: {:X}", perform_math_reg->dst_reg_index));
manager.CommandLog(fmt::format("Src1 Idx: {:X}", perform_math_reg->src_reg_1_index));
callbacks->CommandLog("Opcode: Perform Register Arithmetic");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_reg->bit_width));
callbacks->CommandLog(fmt::format("Dst Idx: {:X}", perform_math_reg->dst_reg_index));
callbacks->CommandLog(fmt::format("Src1 Idx: {:X}", perform_math_reg->src_reg_1_index));
if (perform_math_reg->has_immediate) {
manager.CommandLog(fmt::format("Value: {:X}", perform_math_reg->value.bit64));
callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_reg->value.bit64));
} else {
manager.CommandLog(fmt::format("Src2 Idx: {:X}", perform_math_reg->src_reg_2_index));
callbacks->CommandLog(
fmt::format("Src2 Idx: {:X}", perform_math_reg->src_reg_2_index));
}
} else if (auto str_register = std::get_if<StoreRegisterToAddressOpcode>(&opcode.opcode)) {
manager.CommandLog("Opcode: Store Register to Address");
manager.CommandLog(fmt::format("Bit Width: {:X}", str_register->bit_width));
manager.CommandLog(fmt::format("S Reg Idx: {:X}", str_register->str_reg_index));
manager.CommandLog(fmt::format("A Reg Idx: {:X}", str_register->addr_reg_index));
manager.CommandLog(fmt::format("Incr Reg: {:d}", str_register->increment_reg));
callbacks->CommandLog("Opcode: Store Register to Address");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_register->bit_width));
callbacks->CommandLog(fmt::format("S Reg Idx: {:X}", str_register->str_reg_index));
callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", str_register->addr_reg_index));
callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_register->increment_reg));
switch (str_register->ofs_type) {
case StoreRegisterOffsetType::None:
break;
case StoreRegisterOffsetType::Reg:
manager.CommandLog(fmt::format("O Reg Idx: {:X}", str_register->ofs_reg_index));
callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_register->ofs_reg_index));
break;
case StoreRegisterOffsetType::Imm:
manager.CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
break;
case StoreRegisterOffsetType::MemReg:
manager.CommandLog(
callbacks->CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type)));
break;
case StoreRegisterOffsetType::MemImm:
case StoreRegisterOffsetType::MemImmReg:
manager.CommandLog(
callbacks->CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type)));
manager.CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
break;
}
} else if (auto begin_reg_cond = std::get_if<BeginRegisterConditionalOpcode>(&opcode.opcode)) {
manager.CommandLog("Opcode: Begin Register Conditional");
manager.CommandLog(fmt::format("Bit Width: {:X}", begin_reg_cond->bit_width));
manager.CommandLog(
callbacks->CommandLog("Opcode: Begin Register Conditional");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_reg_cond->bit_width));
callbacks->CommandLog(
fmt::format("Cond Type: {:X}", static_cast<u32>(begin_reg_cond->cond_type)));
manager.CommandLog(fmt::format("V Reg Idx: {:X}", begin_reg_cond->val_reg_index));
callbacks->CommandLog(fmt::format("V Reg Idx: {:X}", begin_reg_cond->val_reg_index));
switch (begin_reg_cond->comp_type) {
case CompareRegisterValueType::StaticValue:
manager.CommandLog("Comp Type: Static Value");
manager.CommandLog(fmt::format("Value: {:X}", begin_reg_cond->value.bit64));
callbacks->CommandLog("Comp Type: Static Value");
callbacks->CommandLog(fmt::format("Value: {:X}", begin_reg_cond->value.bit64));
break;
case CompareRegisterValueType::OtherRegister:
manager.CommandLog("Comp Type: Other Register");
manager.CommandLog(fmt::format("X Reg Idx: {:X}", begin_reg_cond->other_reg_index));
callbacks->CommandLog("Comp Type: Other Register");
callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", begin_reg_cond->other_reg_index));
break;
case CompareRegisterValueType::MemoryRelAddr:
manager.CommandLog("Comp Type: Memory Relative Address");
manager.CommandLog(
callbacks->CommandLog("Comp Type: Memory Relative Address");
callbacks->CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type)));
manager.CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address));
break;
case CompareRegisterValueType::MemoryOfsReg:
manager.CommandLog("Comp Type: Memory Offset Register");
manager.CommandLog(
callbacks->CommandLog("Comp Type: Memory Offset Register");
callbacks->CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type)));
manager.CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index));
callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index));
break;
case CompareRegisterValueType::RegisterRelAddr:
manager.CommandLog("Comp Type: Register Relative Address");
manager.CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index));
manager.CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address));
callbacks->CommandLog("Comp Type: Register Relative Address");
callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address));
break;
case CompareRegisterValueType::RegisterOfsReg:
manager.CommandLog("Comp Type: Register Offset Register");
manager.CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index));
manager.CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index));
callbacks->CommandLog("Comp Type: Register Offset Register");
callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index));
callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index));
break;
}
} else if (auto save_restore_reg = std::get_if<SaveRestoreRegisterOpcode>(&opcode.opcode)) {
manager.CommandLog("Opcode: Save or Restore Register");
manager.CommandLog(fmt::format("Dst Idx: {:X}", save_restore_reg->dst_index));
manager.CommandLog(fmt::format("Src Idx: {:X}", save_restore_reg->src_index));
manager.CommandLog(
callbacks->CommandLog("Opcode: Save or Restore Register");
callbacks->CommandLog(fmt::format("Dst Idx: {:X}", save_restore_reg->dst_index));
callbacks->CommandLog(fmt::format("Src Idx: {:X}", save_restore_reg->src_index));
callbacks->CommandLog(
fmt::format("Op Type: {:d}", static_cast<u32>(save_restore_reg->op_type)));
} else if (auto save_restore_regmask =
std::get_if<SaveRestoreRegisterMaskOpcode>(&opcode.opcode)) {
manager.CommandLog("Opcode: Save or Restore Register Mask");
manager.CommandLog(
callbacks->CommandLog("Opcode: Save or Restore Register Mask");
callbacks->CommandLog(
fmt::format("Op Type: {:d}", static_cast<u32>(save_restore_regmask->op_type)));
for (std::size_t i = 0; i < NumRegisters; i++) {
manager.CommandLog(
callbacks->CommandLog(
fmt::format("Act[{:02X}]: {:d}", i, save_restore_regmask->should_operate[i]));
}
} else if (auto rw_static_reg = std::get_if<ReadWriteStaticRegisterOpcode>(&opcode.opcode)) {
manager.CommandLog("Opcode: Read/Write Static Register");
callbacks->CommandLog("Opcode: Read/Write Static Register");
if (rw_static_reg->static_idx < NumReadableStaticRegisters) {
manager.CommandLog("Op Type: ReadStaticRegister");
callbacks->CommandLog("Op Type: ReadStaticRegister");
} else {
manager.CommandLog("Op Type: WriteStaticRegister");
callbacks->CommandLog("Op Type: WriteStaticRegister");
}
manager.CommandLog(fmt::format("Reg Idx {:X}", rw_static_reg->idx));
manager.CommandLog(fmt::format("Stc Idx {:X}", rw_static_reg->static_idx));
callbacks->CommandLog(fmt::format("Reg Idx {:X}", rw_static_reg->idx));
callbacks->CommandLog(fmt::format("Stc Idx {:X}", rw_static_reg->static_idx));
} else if (auto debug_log = std::get_if<DebugLogOpcode>(&opcode.opcode)) {
manager.CommandLog("Opcode: Debug Log");
manager.CommandLog(fmt::format("Bit Width: {:X}", debug_log->bit_width));
manager.CommandLog(fmt::format("Log ID: {:X}", debug_log->log_id));
manager.CommandLog(fmt::format("Val Type: {:X}", static_cast<u32>(debug_log->val_type)));
callbacks->CommandLog("Opcode: Debug Log");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", debug_log->bit_width));
callbacks->CommandLog(fmt::format("Log ID: {:X}", debug_log->log_id));
callbacks->CommandLog(
fmt::format("Val Type: {:X}", static_cast<u32>(debug_log->val_type)));
switch (debug_log->val_type) {
case DebugLogValueType::RegisterValue:
manager.CommandLog("Val Type: Register Value");
manager.CommandLog(fmt::format("X Reg Idx: {:X}", debug_log->val_reg_index));
callbacks->CommandLog("Val Type: Register Value");
callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", debug_log->val_reg_index));
break;
case DebugLogValueType::MemoryRelAddr:
manager.CommandLog("Val Type: Memory Relative Address");
manager.CommandLog(
callbacks->CommandLog("Val Type: Memory Relative Address");
callbacks->CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type)));
manager.CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address));
break;
case DebugLogValueType::MemoryOfsReg:
manager.CommandLog("Val Type: Memory Offset Register");
manager.CommandLog(
callbacks->CommandLog("Val Type: Memory Offset Register");
callbacks->CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type)));
manager.CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index));
callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index));
break;
case DebugLogValueType::RegisterRelAddr:
manager.CommandLog("Val Type: Register Relative Address");
manager.CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index));
manager.CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address));
callbacks->CommandLog("Val Type: Register Relative Address");
callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address));
break;
case DebugLogValueType::RegisterOfsReg:
manager.CommandLog("Val Type: Register Offset Register");
manager.CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index));
manager.CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index));
callbacks->CommandLog("Val Type: Register Offset Register");
callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index));
callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index));
break;
}
} else if (auto instr = std::get_if<UnrecognizedInstruction>(&opcode.opcode)) {
manager.CommandLog(fmt::format("Unknown opcode: {:X}", static_cast<u32>(instr->opcode)));
callbacks->CommandLog(fmt::format("Unknown opcode: {:X}", static_cast<u32>(instr->opcode)));
}
}
bool CheatVirtualMachine::DecodeNextOpcode(CheatVmOpcode& out) {
DmntCheatVm::Callbacks::~Callbacks() = default;
bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
// If we've ever seen a decode failure, return false.
bool valid = decode_success;
CheatVmOpcode opcode = {};
@@ -631,7 +634,7 @@ bool CheatVirtualMachine::DecodeNextOpcode(CheatVmOpcode& out) {
return valid;
}
void CheatVirtualMachine::SkipConditionalBlock(bool is_if) {
void DmntCheatVm::SkipConditionalBlock(bool is_if) {
if (condition_depth > 0) {
// We want to continue until we're out of the current block.
const std::size_t desired_depth = condition_depth - 1;
@@ -665,7 +668,7 @@ void CheatVirtualMachine::SkipConditionalBlock(bool is_if) {
}
}
u64 CheatVirtualMachine::GetVmInt(VmInt value, u32 bit_width) {
u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) {
switch (bit_width) {
case 1:
return value.bit8;
@@ -681,8 +684,8 @@ u64 CheatVirtualMachine::GetVmInt(VmInt value, u32 bit_width) {
}
}
u64 CheatVirtualMachine::GetCheatProcessAddress(const CheatProcessMetadata& metadata,
MemoryAccessType mem_type, u64 rel_address) {
u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata& metadata,
MemoryAccessType mem_type, u64 rel_address) {
switch (mem_type) {
case MemoryAccessType::MainNso:
default:
@@ -696,7 +699,7 @@ u64 CheatVirtualMachine::GetCheatProcessAddress(const CheatProcessMetadata& meta
}
}
void CheatVirtualMachine::ResetState() {
void DmntCheatVm::ResetState() {
registers.fill(0);
saved_values.fill(0);
loop_tops.fill(0);
@@ -705,7 +708,7 @@ void CheatVirtualMachine::ResetState() {
decode_success = true;
}
bool CheatVirtualMachine::LoadProgram(std::span<const CheatEntry> entries) {
bool DmntCheatVm::LoadProgram(const std::vector<CheatEntry>& entries) {
// Reset opcode count.
num_opcodes = 0;
@@ -726,31 +729,31 @@ bool CheatVirtualMachine::LoadProgram(std::span<const CheatEntry> entries) {
return true;
}
void CheatVirtualMachine::Execute(const CheatProcessMetadata& metadata) {
void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
CheatVmOpcode cur_opcode{};
// Get Keys down.
u64 kDown = manager.HidKeysDown();
u64 kDown = callbacks->HidKeysDown();
manager.CommandLog("Started VM execution.");
manager.CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base));
manager.CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base));
manager.CommandLog(fmt::format("Keys Down: {:08X}", static_cast<u32>(kDown & 0x0FFFFFFF)));
callbacks->CommandLog("Started VM execution.");
callbacks->CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base));
callbacks->CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base));
callbacks->CommandLog(fmt::format("Keys Down: {:08X}", static_cast<u32>(kDown & 0x0FFFFFFF)));
// Clear VM state.
ResetState();
// Loop until program finishes.
while (DecodeNextOpcode(cur_opcode)) {
manager.CommandLog(
callbacks->CommandLog(
fmt::format("Instruction Ptr: {:04X}", static_cast<u32>(instruction_ptr)));
for (std::size_t i = 0; i < NumRegisters; i++) {
manager.CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i]));
callbacks->CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i]));
}
for (std::size_t i = 0; i < NumRegisters; i++) {
manager.CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i]));
callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i]));
}
LogOpcode(cur_opcode);
@@ -770,8 +773,7 @@ void CheatVirtualMachine::Execute(const CheatProcessMetadata& metadata) {
case 2:
case 4:
case 8:
manager.WriteCheatProcessMemoryUnsafe(dst_address, &dst_value,
store_static->bit_width);
callbacks->MemoryWriteUnsafe(dst_address, &dst_value, store_static->bit_width);
break;
}
} else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&cur_opcode.opcode)) {
@@ -784,8 +786,7 @@ void CheatVirtualMachine::Execute(const CheatProcessMetadata& metadata) {
case 2:
case 4:
case 8:
manager.ReadCheatProcessMemoryUnsafe(src_address, &src_value,
begin_cond->bit_width);
callbacks->MemoryReadUnsafe(src_address, &src_value, begin_cond->bit_width);
break;
}
// Check against condition.
@@ -856,8 +857,8 @@ void CheatVirtualMachine::Execute(const CheatProcessMetadata& metadata) {
case 2:
case 4:
case 8:
manager.ReadCheatProcessMemoryUnsafe(src_address, &registers[ldr_memory->reg_index],
ldr_memory->bit_width);
callbacks->MemoryReadUnsafe(src_address, &registers[ldr_memory->reg_index],
ldr_memory->bit_width);
break;
}
} else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&cur_opcode.opcode)) {
@@ -873,8 +874,7 @@ void CheatVirtualMachine::Execute(const CheatProcessMetadata& metadata) {
case 2:
case 4:
case 8:
manager.WriteCheatProcessMemoryUnsafe(dst_address, &dst_value,
str_static->bit_width);
callbacks->MemoryWriteUnsafe(dst_address, &dst_value, str_static->bit_width);
break;
}
// Increment register if relevant.
@@ -1032,8 +1032,7 @@ void CheatVirtualMachine::Execute(const CheatProcessMetadata& metadata) {
case 2:
case 4:
case 8:
manager.WriteCheatProcessMemoryUnsafe(dst_address, &dst_value,
str_register->bit_width);
callbacks->MemoryWriteUnsafe(dst_address, &dst_value, str_register->bit_width);
break;
}
@@ -1112,8 +1111,8 @@ void CheatVirtualMachine::Execute(const CheatProcessMetadata& metadata) {
case 2:
case 4:
case 8:
manager.ReadCheatProcessMemoryUnsafe(cond_address, &cond_value,
begin_reg_cond->bit_width);
callbacks->MemoryReadUnsafe(cond_address, &cond_value,
begin_reg_cond->bit_width);
break;
}
}
@@ -1206,9 +1205,9 @@ void CheatVirtualMachine::Execute(const CheatProcessMetadata& metadata) {
static_registers[rw_static_reg->static_idx] = registers[rw_static_reg->idx];
}
} else if (std::holds_alternative<PauseProcessOpcode>(cur_opcode.opcode)) {
manager.PauseCheatProcessUnsafe();
callbacks->PauseProcess();
} else if (std::holds_alternative<ResumeProcessOpcode>(cur_opcode.opcode)) {
manager.ResumeCheatProcessUnsafe();
callbacks->ResumeProcess();
} else if (auto debug_log = std::get_if<DebugLogOpcode>(&cur_opcode.opcode)) {
// Read value from memory.
u64 log_value = 0;
@@ -1255,8 +1254,7 @@ void CheatVirtualMachine::Execute(const CheatProcessMetadata& metadata) {
case 2:
case 4:
case 8:
manager.ReadCheatProcessMemoryUnsafe(val_address, &log_value,
debug_log->bit_width);
callbacks->MemoryReadUnsafe(val_address, &log_value, debug_log->bit_width);
break;
}
}
@@ -1266,4 +1264,5 @@ void CheatVirtualMachine::Execute(const CheatProcessMetadata& metadata) {
}
}
}
} // namespace Service::DMNT
} // namespace Core::Memory
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -6,14 +6,15 @@
#pragma once
#include <span>
#include <variant>
#include <vector>
#include <memory>
#include <fmt/printf.h>
#include "common/common_types.h"
#include "core/hle/service/dmnt/dmnt_types.h"
#include "core/memory/dmnt_cheat_types.h"
namespace Service::DMNT {
class CheatProcessManager;
namespace Core::Memory {
enum class CheatVmOpcodeType : u32 {
StoreStatic = 0,
@@ -263,8 +264,25 @@ struct CheatVmOpcode {
opcode{};
};
class CheatVirtualMachine {
class DmntCheatVm {
public:
/// Helper Type for DmntCheatVm <=> yuzu Interface
class Callbacks {
public:
virtual ~Callbacks();
virtual void MemoryReadUnsafe(VAddr address, void* data, u64 size) = 0;
virtual void MemoryWriteUnsafe(VAddr address, const void* data, u64 size) = 0;
virtual u64 HidKeysDown() = 0;
virtual void PauseProcess() = 0;
virtual void ResumeProcess() = 0;
virtual void DebugLog(u8 id, u64 value) = 0;
virtual void CommandLog(std::string_view data) = 0;
};
static constexpr std::size_t MaximumProgramOpcodeCount = 0x400;
static constexpr std::size_t NumRegisters = 0x10;
static constexpr std::size_t NumReadableStaticRegisters = 0x80;
@@ -272,43 +290,18 @@ public:
static constexpr std::size_t NumStaticRegisters =
NumReadableStaticRegisters + NumWritableStaticRegisters;
explicit CheatVirtualMachine(CheatProcessManager& cheat_manager);
~CheatVirtualMachine();
explicit DmntCheatVm(std::unique_ptr<Callbacks> callbacks_);
~DmntCheatVm();
std::size_t GetProgramSize() const {
return this->num_opcodes;
}
bool LoadProgram(std::span<const CheatEntry> cheats);
bool LoadProgram(const std::vector<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::unique_ptr<Callbacks> callbacks;
std::size_t num_opcodes = 0;
std::size_t instruction_ptr = 0;
@@ -319,5 +312,19 @@ private:
std::array<u64, NumRegisters> saved_values{};
std::array<u64, NumStaticRegisters> static_registers{};
std::array<std::size_t, NumRegisters> loop_tops{};
bool DecodeNextOpcode(CheatVmOpcode& out);
void SkipConditionalBlock(bool is_if);
void ResetState();
// For implementing the DebugLog opcode.
void DebugLog(u32 log_id, u64 value);
void LogOpcode(const CheatVmOpcode& opcode);
static u64 GetVmInt(VmInt value, u32 bit_width);
static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata,
MemoryAccessType mem_type, u64 rel_address);
};
} // namespace Service::DMNT
}; // namespace Core::Memory
@@ -45,6 +45,10 @@
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "video_core/gpu_logging/gpu_logging.h"
#ifdef ANDROID
#include "../../android/app/src/main/jni/android_settings.h"
#endif
namespace Vulkan {
namespace {
@@ -325,13 +329,13 @@ size_t GetTotalPipelineWorkers() {
const size_t max_core_threads =
std::max<size_t>(static_cast<size_t>(std::thread::hardware_concurrency()), 2ULL) - 1ULL;
#ifdef ANDROID
// Leave at least one core free on Android. Previously we reserved two, but
// shipping builds benefit from one extra compilation worker.
constexpr size_t free_cores = 1ULL;
if (max_core_threads <= free_cores) {
const int configured = AndroidSettings::values.pipeline_worker_count.GetValue();
const int clamped = std::clamp(configured, 4, 8);
const size_t desired = static_cast<size_t>(clamped);
if (desired == 0) {
return 1ULL;
}
return max_core_threads - free_cores;
return std::min(max_core_threads, desired);
#else
return max_core_threads;
#endif
@@ -426,13 +430,12 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA,
.has_broken_spirv_clamp = driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS,
.has_broken_spirv_position_input = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
.has_broken_spirv_position_input = driver_id == false,
.has_broken_unsigned_image_offsets = false,
.has_broken_signed_operations = false,
.has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY,
.ignore_nan_fp_comparisons = false,
.has_broken_spirv_subgroup_mask_vector_extract_dynamic =
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
.has_broken_spirv_subgroup_mask_vector_extract_dynamic = false,
.has_broken_robust =
device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal,
.min_ssbo_alignment = device.GetStorageBufferAlignment(),
@@ -5,8 +5,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <functional>
#include <map>
#include <memory>
#include <utility>
@@ -108,39 +106,18 @@ void ConfigurePerGameAddons::OnItemChanged(QStandardItem* item) {
void ConfigurePerGameAddons::ApplyConfiguration() {
std::vector<std::string> disabled_addons;
// Helper function to recursively collect disabled items
std::function<void(QStandardItem*)> collect_disabled = [&](QStandardItem* item) {
if (item == nullptr) {
return;
}
// Check if this item is disabled
if (item->isCheckable() && item->checkState() == Qt::Unchecked) {
QVariant userData = item->data(Qt::UserRole);
for (const auto& item : list_items) {
const auto disabled = item.front()->checkState() == Qt::Unchecked;
if (disabled) {
QVariant userData = item.front()->data(Qt::UserRole);
if (userData.isValid() && userData.canConvert<quint32>() &&
item->text() == QStringLiteral("Update")) {
const quint32 numeric_version = userData.toUInt();
item.front()->text() == QStringLiteral("Update")) {
quint32 numeric_version = userData.toUInt();
disabled_addons.push_back(fmt::format("Update@{}", numeric_version));
} else {
// Use the stored key from UserRole, falling back to text
const auto key = userData.toString();
if (!key.isEmpty()) {
disabled_addons.push_back(key.toStdString());
} else {
disabled_addons.push_back(item->text().toStdString());
}
disabled_addons.push_back(item.front()->text().toStdString());
}
}
// Process children (for cheats under mods)
for (int row = 0; row < item->rowCount(); ++row) {
collect_disabled(item->child(row, 0));
}
};
// Process all root items
for (int row = 0; row < item_model->rowCount(); ++row) {
collect_disabled(item_model->item(row, 0));
}
auto current = Settings::values.disabled_addons[title_id];
@@ -337,40 +314,19 @@ void ConfigurePerGameAddons::LoadConfiguration() {
bool has_enabled_update = false;
// Map to store parent items for mods (for adding cheat children)
std::map<std::string, QStandardItem*> mod_items;
for (const auto& patch : patches) {
const auto name = QString::fromStdString(patch.name);
// For cheats, we need to use the full key (parent::name) for storage
std::string storage_key;
if (patch.type == FileSys::PatchType::Cheat && !patch.parent_name.empty()) {
storage_key = patch.parent_name + "::" + patch.name;
} else {
storage_key = patch.name;
}
auto* const first_item = new QStandardItem;
first_item->setText(name);
first_item->setCheckable(true);
const bool is_external_update = patch.type == FileSys::PatchType::Update &&
patch.source == FileSys::PatchSource::External &&
patch.numeric_version != 0;
const bool is_mod = patch.type == FileSys::PatchType::Mod;
const bool is_incompatible_cheat =
patch.type == FileSys::PatchType::Cheat &&
patch.cheat_compat != FileSys::CheatCompatibility::Compatible;
if (is_incompatible_cheat) {
first_item->setCheckable(false);
first_item->setEnabled(false);
} else {
first_item->setCheckable(true);
first_item->setData(QString::fromStdString(storage_key), Qt::UserRole);
}
if (is_external_update) {
first_item->setData(static_cast<quint32>(patch.numeric_version), NUMERIC_VERSION);
} else if (is_mod) {
@@ -378,18 +334,16 @@ void ConfigurePerGameAddons::LoadConfiguration() {
}
bool patch_disabled = false;
if (!is_incompatible_cheat) {
if (is_external_update) {
std::string disabled_key = fmt::format("Update@{}", patch.numeric_version);
patch_disabled =
std::find(disabled.begin(), disabled.end(), disabled_key) != disabled.end();
} else {
patch_disabled =
std::find(disabled.begin(), disabled.end(), storage_key) != disabled.end();
}
if (is_external_update) {
std::string disabled_key = fmt::format("Update@{}", patch.numeric_version);
patch_disabled =
std::find(disabled.begin(), disabled.end(), disabled_key) != disabled.end();
} else {
patch_disabled =
std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end();
}
bool should_enable = !patch_disabled && !is_incompatible_cheat;
bool should_enable = !patch_disabled;
if (patch.type == FileSys::PatchType::Update) {
if (should_enable) {
@@ -402,37 +356,12 @@ void ConfigurePerGameAddons::LoadConfiguration() {
update_items.push_back(first_item);
}
if (!is_incompatible_cheat) {
first_item->setCheckState(should_enable ? Qt::Checked : Qt::Unchecked);
}
first_item->setCheckState(should_enable ? Qt::Checked : Qt::Unchecked);
auto* const version_item = new QStandardItem{QString::fromStdString(patch.version)};
if (is_incompatible_cheat) {
version_item->setEnabled(false);
}
if (patch.type == FileSys::PatchType::Cheat && !patch.parent_name.empty()) {
// This is a cheat - add as child of its parent mod
auto parent_it = mod_items.find(patch.parent_name);
if (parent_it != mod_items.end()) {
parent_it->second->appendRow(QList<QStandardItem*>{first_item, version_item});
} else {
// Parent not found (shouldn't happen), add to root
list_items.push_back(QList<QStandardItem*>{first_item, version_item});
item_model->appendRow(list_items.back());
}
} else {
// This is a top-level item (Update, Mod, DLC)
list_items.push_back(QList<QStandardItem*>{first_item, version_item});
item_model->appendRow(list_items.back());
// Store mod items for later cheat attachment
if (patch.type == FileSys::PatchType::Mod) {
mod_items[patch.name] = first_item;
}
}
list_items.push_back(QList<QStandardItem*>{
first_item, new QStandardItem{QString::fromStdString(patch.version)}});
item_model->appendRow(list_items.back());
}
tree_view->expandAll();
tree_view->resizeColumnToContents(1);
}