158 Commits
1.1.0 ... 2.0.1

Author SHA1 Message Date
Lightos1
530588a818 bump version 2026-04-21 21:29:18 +02:00
Lightos1
a6402bd5d5 update startPtr AFTER PrepareMtcMemoryRegion 2026-04-21 21:28:18 +02:00
Lightos1
cd3d29ce88 I'm a fucking idiot... remove debug code 2026-04-21 09:58:11 +02:00
souldbminersmwc
192b70dae4 Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-04-20 19:19:44 -04:00
souldbminersmwc
aaa9f90794 hocclk: console specific changes 2026-04-20 19:19:40 -04:00
Lightos1
d5e38b0eb3 Update SECURITY.md 2026-04-20 21:44:44 +02:00
Lightos1
3704d46136 set default step mode to 66MHz 2026-04-20 21:30:02 +02:00
Lightos1
b374117c37 properly fix time stuff - thanks masa! 2026-04-20 20:14:29 +02:00
Lightos1
c9c5d08919 add updated spanish translation by tdrr 2026-04-20 19:57:24 +02:00
Lightos1
c963dd8369 fix auto latency according to spec 2026-04-20 15:18:14 +02:00
Lightos1
7fc9682de0 use max freq in jedec mode regardless of it being jedec or not 2026-04-20 15:06:14 +02:00
Lightos1
0c555f34c3 remove (failing) cust rev check 2026-04-20 14:54:37 +02:00
Lightos1
fb37d5f0e7 reduce context size 2026-04-20 14:37:36 +02:00
Lightos1
a7619c39d2 set read/write latency to 1600 cause it's only used on erista 2026-04-20 07:31:52 +02:00
souldbminersmwc
55b97156ce hocclk: improve kip logging and add failsafe 2026-04-19 19:16:51 -04:00
souldbminersmwc
37ad65e768 final hoc 2.0.0 changes 2026-04-19 19:15:10 -04:00
souldbminersmwc
3b4877b287 update submodules 2026-04-19 15:38:07 -04:00
Lightos1
01d79aab30 fix off by one 2026-04-19 19:57:58 +02:00
Lightos1
3bb7b4cbd5 change colors 2026-04-19 19:17:36 +02:00
Lightos1
9c16f8b2b8 fix typo 2026-04-19 16:12:57 +02:00
Lightos1
78112547b6 prevent switch from combusting into 4 billion volts :D 2026-04-19 15:47:46 +02:00
Lightos1
3c7a42b033 formating 2026-04-19 15:13:35 +02:00
Lightos1
203587523a change result 2026-04-19 15:13:19 +02:00
Lightos1
07d0824c55 Merge pull request #66 from Horizon-OC/mrf-but-no-z-with-z
mrf
2026-04-19 15:07:02 +02:00
Lightos1
f1eab00ce1 revert custom and pcv to default settings 2026-04-19 14:51:15 +02:00
Lightos1
6a851d4095 nso start check, timing tbreak and some horrendous ui code 2026-04-18 23:00:21 +02:00
Lightos1
822e9f2817 add t2 trp cap 2026-04-18 19:02:07 +02:00
Lightos1
55b3a4230c Remove useless comments, yes I cannot be bothered to write ui code myself :D; fuck ui 2026-04-18 16:31:49 +02:00
Lightos1
be61b9df0e add sloppy configurator: todo fix crap 2026-04-18 16:30:24 +02:00
Lightos1
6c8d429c64 add missing configs 2026-04-18 12:21:42 +02:00
Lightos1
aa95f526b8 add mrf latency stuff to configs 2026-04-18 12:19:55 +02:00
Lightos1
638ddb499c add jedec mode 2026-04-18 11:52:06 +02:00
Lightos1
c95b6fde88 assign all mtc tables 2026-04-17 23:40:17 +02:00
Lightos1
2dd726723e add latency switching 2026-04-17 20:26:32 +02:00
Lightos1
119f49a3a4 fix freq list generation and dvb 2026-04-17 16:36:12 +02:00
Lightos1
51f935c252 initial mrf implementation with some bugs to fix 2026-04-16 20:51:26 +02:00
Lightos1
e1003520eb Revert "initial mrf implementation with some bugs to fix"
This reverts commit 819913ffd9.
2026-04-16 20:49:58 +02:00
Lightos1
3b5185df8d Reapply "Partial workflow fixes? (#63)"
This reverts commit 26c1dd1f99.
2026-04-16 20:49:42 +02:00
Lightos1
26c1dd1f99 Revert "Partial workflow fixes? (#63)"
This reverts commit 923bd0c0ba.
2026-04-16 20:49:11 +02:00
Lightos1
819913ffd9 initial mrf implementation with some bugs to fix 2026-04-16 20:48:22 +02:00
Lightos1
923bd0c0ba Partial workflow fixes? (#63)
* Update build.yml

* Update build.yml

* Update build.yml
2026-04-13 16:53:25 +02:00
Lightos1
185e5bcf31 Update build.yml 2026-04-13 15:52:20 +02:00
souldbminersmwc
d1ef905ac5 hocmon: real temps fixes 2026-04-12 20:56:39 -04:00
souldbminersmwc
fad9b5be50 hocmon: fix vdd2/vddq flip and full/mini mode 2026-04-12 20:06:23 -04:00
souldbminersmwc
c6424403b3 hocmon: partially add ram bw 2026-04-12 17:43:45 -04:00
souldbminersmwc
2b23498285 Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-04-12 16:53:01 -04:00
souldbminersmwc
c7db7a7632 hocclk: fix compilation 2026-04-12 16:51:52 -04:00
Lightos1
985ecdc980 add fatal handler payload 2026-04-12 22:04:19 +02:00
Lightos1
e1d75f0084 add oc_log files 2026-04-12 22:00:45 +02:00
souldbminersmwc
f276ca0187 hocclk: rename mem display unit to ram display unit 2026-04-11 21:05:28 -04:00
souldbminersmwc
ae39b1b1bd hocclk: fix MT/s display on 1600mhz 2026-04-11 21:03:31 -04:00
souldbminersmwc
9321aed1d1 bump version 2026-04-11 20:58:22 -04:00
souldbminersmwc
756c44ba82 hocclk: fix compile errors 2026-04-11 20:57:28 -04:00
souldbminersmwc
037f011c9b hocclk: peak emc bw and mem display unit option
also correct bw reading to keep it accurate
2026-04-11 20:56:35 -04:00
souldbminersmwc
d2a46e1202 Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-04-11 19:08:19 -04:00
souldbminersmwc
976f133c97 add MT/S to mem profiles 2026-04-11 19:08:12 -04:00
Lightos1
accdf6dc34 fix Z - thanks masa! 2026-04-12 01:05:11 +02:00
souldbminersmwc
b417099a4a hocclk: add RAM bandwidth monitor 2026-04-11 19:00:22 -04:00
souldbminersmwc
b7440a38a5 hocclk: reduce memory usage for real this time 2026-04-11 18:25:30 -04:00
souldbminersmwc
6369382de1 Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-04-11 17:59:20 -04:00
souldbminersmwc
65db0b4989 hocclk: 800mV minimum display voltage 2026-04-11 17:59:18 -04:00
Lightos1
19b29b97e1 separate pcv.hpp into erista/mariko files 2026-04-10 17:45:32 +02:00
Lightos1
ec230e35d0 make this pretty 2026-04-09 11:33:44 +02:00
Lightos1
5942cfd68d ldr: refactor asm 2026-04-09 11:32:00 +02:00
souldbminersmwc
66d0109a9a hocclk: autosave kip and reduce uv3 to 675 2026-04-08 19:15:26 -04:00
souldbminersmwc
6334603f9e hocclk: undo alignment
this doesnt help at all, it makes it less aligned
2026-04-08 17:51:20 -04:00
souldbminersmwc
5d12c1721a hocclk: reorganize about section 2026-04-08 17:49:00 -04:00
Lightos1
7e42394894 fix GpuClkOsLimit name 2026-04-08 22:17:28 +02:00
Lightos1
07dd65eebf move GpuClkOsLimit to common 2026-04-08 22:15:52 +02:00
Lightos1
ca8bb25660 add GpuOsLimit verification to mariko; erista will be pushed once verified 2026-04-08 21:30:36 +02:00
souldbminersmwc
90e53b52b2 hocclk: refactoring and ram pll measurement 2026-04-07 19:48:17 -04:00
Lightos1
c6cd863526 Bump ams version 2026-04-07 19:45:16 +02:00
Lightos1
24eb3b1208 recover alignment improvement commit changes 2026-04-07 19:32:37 +02:00
Lightos1
9a6282af9c bump version 2026-04-07 19:20:52 +02:00
Lightos1
5de030c4e9 rename globals 2026-04-06 23:23:04 +02:00
souldbminersmwc
dc63dfdca2 hocclk: add support for FW <10.0.0 2026-04-06 16:52:18 -04:00
souldbminersmwc
803a838b35 Update Utils.hpp 2026-04-06 16:10:02 -04:00
souldbminersmwc
5b284db5ae hocmon: lower default font size 2026-04-06 16:09:59 -04:00
souldbminersmwc
861ee85289 Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-04-06 15:57:12 -04:00
souldbminersmwc
521e3c22dd hocclk: reduce ram usage and add display undervolt 2026-04-06 15:57:10 -04:00
Lightos1
78798a1cce actually fix cpu max 2026-04-06 12:58:54 +02:00
Lightos1
44d417fb89 fix cpu max clock, remove below 1963 caps on mariko 2026-04-06 12:56:06 +02:00
Lightos1
8faaaa58d9 mini: fix garbage temp reading 2026-04-04 12:15:09 +02:00
Lightos1
df0969ba6f update readme 2026-04-04 11:59:10 +02:00
Lightos1
646f8b8d69 update readme 2026-04-04 11:58:44 +02:00
Lightos1
456684152a more accurate Italian translation by miki 2026-04-04 11:56:10 +02:00
souldbminersmwc
a606a878ee hocclk: fix lightos z 2026-04-03 20:02:36 -04:00
souldbminersmwc
75531e6c4f add ldr patches for older atmosphere versions (in case anyone still uses them) 2026-04-03 19:42:29 -04:00
souldbminersmwc
fa7313c495 remove real temp decimals 2026-04-03 19:29:57 -04:00
souldbminersmwc
5816d49f68 Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-04-03 19:25:22 -04:00
souldbminersmwc
589c41d8e5 hocmon: fix vdd2/vddq order 2026-04-03 19:25:14 -04:00
Lightos1
b35971e265 bump ams version 2026-04-04 01:24:51 +02:00
Lightos1
1d3836fcbb ams 1.11 is now the latest version 2026-04-04 01:22:41 +02:00
Lightos1
e515df84a5 prepare ldr_process_creation for ams 1.11.0 2026-04-04 01:17:09 +02:00
souldbminersmwc
901cb15191 hocclk: fix nvcheck sched 2026-04-03 15:12:33 -04:00
souldbminersmwc
aaa00ca3d0 hocclk: add negatives before gpu offset 2026-04-03 12:39:49 -04:00
souldbminersmwc
1f449af24f hocclk: rename gpu uv options 2026-04-03 12:37:07 -04:00
souldbminersmwc
cb810367b8 hocclk: remove 800mv vmin option 2026-04-03 12:35:22 -04:00
souldbminersmwc
27cd40a7e8 Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-04-03 12:34:17 -04:00
souldbminersmwc
75480ffdb5 hocclk: fix vrr out of focus 2026-04-03 12:34:15 -04:00
Lightos1
d3ee7c2976 remove auto vmin from configurator 2026-04-03 15:34:21 +02:00
Lightos1
3e5cec375d ldr: remove unused structs 2026-04-03 14:55:22 +02:00
Lightos1
280392158e ldr: deprecate auto vmin, it was incorrectly implemente, proper implementation is inferior to pcv hijack 2026-04-03 14:52:40 +02:00
Lightos1
a8d47ae8c8 Fix garbage temp reading 2026-04-03 13:55:31 +02:00
Lightos1
eb6364f994 add decimal config 2026-04-03 13:39:46 +02:00
Lightos1
59ba05332f micro mode: fix padding 2026-04-03 13:15:01 +02:00
souldbminersmwc
fc203c723c Update MIGRATION.md 2026-04-02 22:10:24 -04:00
souldbminersmwc
5f427ec24d Update MIGRATION.md 2026-04-02 22:10:00 -04:00
souldbminersmwc
cf891190cd hocmon: fix VDD2/VDDQ reversal 2026-04-02 22:09:11 -04:00
souldbminersmwc
3411959537 hocclk: add migration guide 2026-04-02 17:13:44 -04:00
souldbminersmwc
cce2069a32 hocclk: build system changes
- update C++ version to GNU23, change how build scripts behave and more
2026-04-02 16:59:11 -04:00
souldbminersmwc
485aa83de5 hocclk: remove sysclk licensing from hocclk code 2026-04-02 16:51:43 -04:00
souldbminersmwc
52e8f5c584 sysclk: rename to hocclk
idc about compatability when the programs are structured very differently, work very differently, and send/get data in very different ways
2026-04-02 16:48:10 -04:00
souldbminersmwc
234fb1655c hocmon: fix hocmon 2026-04-02 16:32:50 -04:00
souldbminersmwc
78a52df596 hocclk: fix gpu load 2026-04-01 20:11:06 -04:00
souldbminersmwc
8bffe2dfc1 sysclk: add SOCTHERM to UI 2026-04-01 16:39:11 -04:00
souldbminersmwc
064ecc6d25 i hate submodules i hate submodules i hate submodules 2026-04-01 16:13:16 -04:00
souldbminersmwc
2d5ba3a841 more submodule stuff 2026-04-01 16:09:49 -04:00
souldbminersmwc
3b2de89d2f fix submodules again 2026-04-01 16:08:42 -04:00
souldbminersmwc
b959ee864b re-add libultrahand submodule 2026-04-01 16:01:26 -04:00
souldbminersmwc
5f2d7a68a9 sysclk: remove sysclk old and libultrahand old 2026-04-01 16:00:54 -04:00
souldbminersmwc
554b66e25f Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-04-01 15:58:44 -04:00
souldbminersmwc
e20bafd6ab sysclk: remove old hocclk, bump version 2026-04-01 15:58:40 -04:00
Lightos1
dc2252a7c6 add pllx to sysclkFormatThermalSensor 2026-04-01 21:58:04 +02:00
Lightos1
80fa802e88 fix typo 2026-04-01 17:18:31 +02:00
Lightos1
944c1ef1f2 Always get pllx 2026-04-01 17:17:25 +02:00
Lightos1
da0806df5d Chinese translation by q1332348216-glitch 2026-04-01 16:00:33 +02:00
Lightos1
eda88210a9 remove commented out code 2026-04-01 15:40:01 +02:00
Lightos1
bb1ae1d816 Fix sleep 2026-04-01 15:38:45 +02:00
Lightos1
0a2a4cbd6b Revert "Update board.cpp"
This reverts commit 46d7b4e5fe.
2026-04-01 15:36:29 +02:00
Lightos1
2607034b7a Revert "Rename files"
This reverts commit 5da2ec11db.
2026-04-01 13:07:03 +02:00
Lightos1
5da2ec11db Rename files 2026-04-01 13:04:48 +02:00
souldbminersmwc
46d7b4e5fe Update board.cpp 2026-03-31 19:35:37 -04:00
souldbminersmwc
4358df0308 wip sleep fix 2026-03-31 19:34:54 -04:00
Lightos1
4103777f48 add gym goat to startup log 2026-03-31 23:40:35 +02:00
Lightos1
bf17e53c8f add soctherm 2026-03-31 23:26:18 +02:00
souldbminersmwc
8f6a5eee28 finish rewrite (for old version) 2026-03-31 16:50:16 -04:00
souldbminersmwc
6d0de115eb Revert "bump version"
This reverts commit 91c12b9128.
2026-03-29 14:45:13 -04:00
souldbminersmwc
1f2999df2f Revert "sysclk: add PWM dimming"
This reverts commit ec661ac1c0.
2026-03-29 14:45:09 -04:00
souldbminersmwc
1f2b3848e4 Revert "sysclk: refinements to pwm dimming"
This reverts commit ce99462081.
2026-03-29 14:45:07 -04:00
souldbminersmwc
955d009f54 Revert "sysclk: remove stray e"
This reverts commit 6607c287db.
2026-03-29 14:45:05 -04:00
souldbminersmwc
6607c287db sysclk: remove stray e 2026-03-28 18:53:39 -04:00
souldbminersmwc
ce99462081 sysclk: refinements to pwm dimming 2026-03-28 18:49:36 -04:00
souldbminersmwc
91c12b9128 bump version 2026-03-28 16:22:58 -04:00
souldbminersmwc
99e5cfc97e Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-03-28 16:20:44 -04:00
souldbminersmwc
ec661ac1c0 sysclk: add PWM dimming 2026-03-28 16:20:42 -04:00
Souldbminer
1de73c4b74 Merge pull request #55 from Angelblaster/patch-7
Update ko.json
2026-03-27 16:09:36 -04:00
Angelblaster
8a3b6f9775 Update ko.json
Before →After



   "Governor Settings": "주지사 설정", 

→

   "Governor Settings": "거버너 설정",

--------------------------------------------------------------------------------------------------

    "refresh rates may cause stress": "새로 고침 빈도는 스트레스를 유발할 수 있습니다",
    "or damage to your display! ": "또는 디스플레이가 손상되었습니다!",
    "Proceed at your own risk!": "자신의 책임하에 진행하십시오!",

→

   "refresh rates may cause stress": "디스플레이 주사율 빈도 변경은",
   "or damage to your display! ": " 기기에 손상이 발생될 수 있습니다!",
   "Proceed at your own risk!": "책임하에 주의해서 사용하십시오!",

--------------------------------------------------------------------------------------------------


    "RAM Timing Reductions": "RAM 타이밍 감소",

→

    "RAM Timing Reductions": "RAM 타이밍 편집기",

--------------------------------------------------------------------------------------------------

    "CPU UV": "CPU UV",

→

    "CPU UV": "CPU 언더볼트",

--------------------------------------------------------------------------------------------------

    "Extreme UV Table": "극자외선 테이블",

→

    "Extreme UV Table": "익스트림 테이블",

--------------------------------------------------------------------------------------------------

    "GPU Voltage Table": "GPU 전압 표",

→

    "GPU Voltage Table": "GPU 전압 테이블",

--------------------------------------------------------------------------------------------------

    "1075MHz without UV, 1152MHz on SLT": "UV 없이 1075MHz, SLT에서 1152MHz",
    "or 1228MHz on HiOPT can cause ": "또는 HiOPT에서 1228MHz를 사용하면",
    "permanent damage to your Switch!": "스위치가 영구적으로 손상되었습니다!",

→

    "1075MHz without UV, 1152MHz on SLT": "UV 없이 1075MHz, SLT에서 1152MHz",
    "or 1228MHz on HiOPT can cause ": "또는 HiOPT에서 1228MHz를 사용하면",
    "permanent damage to your Switch!": "스위치가 영구적으로 손상될 수 있습니다!",
2026-03-26 10:06:28 +09:00
Lightos1
7f0f743f47 remove old files 2026-03-25 18:09:22 +01:00
Lightos1
5a0ae8da5b Recover changes 2026-03-25 18:06:22 +01:00
Souldbminer
77fddda8a9 Merge pull request #54 from Angelblaster/patch-6
Update ko.json
2026-03-24 18:19:04 -04:00
souldbminersmwc
2828687a18 Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-03-24 18:18:24 -04:00
souldbminersmwc
0c1f9e661c Delete dist.zip 2026-03-24 18:18:18 -04:00
Angelblaster
52aa25b212 Update ko.json
38)
"Boost Mode": "부스트 모드",  →     "Overwrite Boost Mode": "부스트 모드 덮어쓰기",

Other partial Korean corrections
2026-03-25 01:23:17 +09:00
Lightos1
3e98704a93 Merge pull request #52 from Angelblaster/patch-4
Update ko.json
2026-03-24 12:00:21 +01:00
Angelblaster
4a7277d8bd Update ko.json
I have corrected some of the Korean.
2026-03-24 19:57:55 +09:00
Lightos1
143de18f3a Merge pull request #51 from Angelblaster/patch-3
Update ko.json
2026-03-24 11:08:39 +01:00
Angelblaster
8626854d10 Update ko.json
I have corrected some of the Korean.
2026-03-24 19:06:23 +09:00
372 changed files with 16675 additions and 24886 deletions

View File

@@ -54,6 +54,9 @@ jobs:
echo $SHORT_SHA > dist/.commit
echo $GITHUB_SHA >> dist/.commit
- name: Clone Libnx
run: git clone https://github.com/switchbrew/libnx.git
- name: Clone Atmosphere
run: git clone --depth=1 --single-branch https://github.com/Atmosphere-NX/Atmosphere.git atmosphere -b $(cat ams_ver.txt)
@@ -81,14 +84,23 @@ jobs:
ccache --set-config=max_size=10G
ccache --set-config=compiler_check=content
ccache --zero-stats
- name: Build Libnx
shell: bash
run: |
export CC="ccache aarch64-none-elf-gcc"
export CXX="ccache aarch64-none-elf-g++"
pushd libnx
make -j$(($(nproc) * 4)) install CXX="ccache aarch64-none-elf-g++" CC="ccache aarch64-none-elf-gcc"
popd
- name: Build hoc-clk sysmodule and overlay
shell: bash
run: |
export CC="ccache aarch64-none-elf-gcc"
export CXX="ccache aarch64-none-elf-g++"
ROOT_DIR="$GITHUB_WORKSPACE/Source/sys-clk"
ROOT_DIR="$GITHUB_WORKSPACE/Source/hoc-clk"
DIST_DIR="$ROOT_DIR/dist"
mkdir -p "$DIST_DIR"
@@ -101,13 +113,14 @@ jobs:
echo "TITLE_ID: $TITLE_ID"
pushd "$ROOT_DIR/sysmodule"
git config --global --add safe.directory "$GITHUB_WORKSPACE"
make -j$(($(nproc) * 2)) CXX="ccache aarch64-none-elf-g++" CC="ccache aarch64-none-elf-gcc"
popd
mkdir -p "$DIST_DIR/atmosphere/contents/$TITLE_ID/flags"
cp -vf \
"$ROOT_DIR/sysmodule/out/horizon-oc.nsp" \
"$ROOT_DIR/sysmodule/out/hoc-clk.nsp" \
"$DIST_DIR/atmosphere/contents/$TITLE_ID/exefs.nsp"
: >"$DIST_DIR/atmosphere/contents/$TITLE_ID/flags/boot2.flag"

18
.gitmodules vendored
View File

@@ -1,12 +1,12 @@
[submodule "Source/Horizon-OC-Monitor/lib/Atmosphere-libs"]
path = Source/Horizon-OC-Monitor/lib/Atmosphere-libs
url = https://github.com/Atmosphere-NX/Atmosphere-libs
[submodule "Source/Horizon-OC-Monitor/lib/Atmosphere-libs"]
path = Source/Horizon-OC-Monitor/lib/Atmosphere-libs
url = https://github.com/Atmosphere-NX/Atmosphere-libs
branch = master
[submodule "Source/Horizon-OC-Monitor/lib/libultrahand"]
path = Source/Horizon-OC-Monitor/lib/libultrahand
url = https://github.com/ppkantorski/libultrahand
branch = main
[submodule "Source/sys-clk/overlay/lib/libultrahand"]
path = Source/sys-clk/overlay/lib/libultrahand
[submodule "Source/Horizon-OC-Monitor/lib/libultrahand"]
path = Source/Horizon-OC-Monitor/lib/libultrahand
url = https://github.com/ppkantorski/libultrahand
branch = main
[submodule "Source/hoc-clk/overlay/lib/libultrahand"]
path = Source/hoc-clk/overlay/lib/libultrahand
url = https://github.com/ppkantorski/libultrahand
branch = main

View File

@@ -32,7 +32,7 @@ It enables advanced CPU, GPU, and RAM tuning with user-friendly configuration to
---
## Features
## Default clocks
* **CPU:** Up to 1963MHz (Mariko) / 1785MHz (Erista)
* **GPU:** Up to 1075MHz (Mariko) / 921MHz (Erista)
@@ -93,7 +93,8 @@ Refer to COMPILATION.md
* 665
### CPU clocks
* 2601 → mariko absolute max, very dangerous
* 2703 → mariko absolute max, dangerous
* 2601 → unsafe
* 2499
* 2397 → mariko safe max with UV (low speedo)
* 2295
@@ -113,9 +114,6 @@ Refer to COMPILATION.md
* 816
* 714
* 612 → sleep mode
**Notes:**
1. On Erista, CPU in handheld is capped to 1581MHz
### GPU clocks
* 1536 → absolute max clock on mariko. very dangerous
@@ -142,27 +140,33 @@ Refer to COMPILATION.md
* 76 → boost mode
**Notes:**
1. GPU overclock is capped at 460MHz on erista in handheld
2. On Mariko, cap with No uv is 614MHz, with SLT it is 691MHz and with HiOPT it's 768MHz
3. Clocks higher than 768MHz on erista need the official charger is plugged in.
4. On Mariko, cap with No uv is 844MHz, with SLT it is 921MHz and with HiOPT it's 998MHz
1. On Erista, CPU in handheld is capped to 1581MHz
2. GPU overclock is capped at 460MHz on erista in handheld
3. On Mariko, cap with No uv is 614MHz, with SLT it is 691MHz and with HiOPT it's 768MHz
4. Clocks higher than 768MHz on erista need the official charger is plugged in.
5. On Mariko, cap with No uv is 844MHz, with SLT it is 921MHz and with HiOPT it's 998MHz
---
## Credits
* **Lightos's Cat** - Cat
* **Souldbminer** hoc-clk and loader development
* **Lightos** loader patches development
* **Souldbminer** - hoc-clk and loader development
* **Lightos** - Loader patches development, hoc-clk development, guides
* **SciresM** - Atmosphere CFW
* **CTCaer** - L4T, Hekate, perfect ram timings
* **KazushiMe** Switch OC Suite
* **hanai3bi (meha)** Switch OC Suite, EOS, sys-clk-eos
* **NaGaa95** L4T-OC-kernel
* **B3711 (halop)** EOS
* **sys-clk team (m4xw, p-sam, natinusala)** sys-clk
* **b0rd2death** Ultrahand sys-clk & Status Monitor fork
* **CTCaer** - L4T, Hekate, proper RAM timings
* **KazushiMe** - Switch OC Suite
* **Hanai3bi (Meha)** - Switch OC Suite, EOS, sys-clk-eos
* **NaGaa95** - L4T-OC kernel, Status Monitor fork
* **B3711 (halop)** - EOS
* **sys-clk team (m4xw, p-sam, natinusala)** - sys-clk
* **Dominatorul** - Soctherm driver, guides, general help
* **b0rd2death** - Ultrahand sys-clk & Status Monitor fork
* **MasaGratoR and ZachyCatGames** - General help
* **MasaGratoR** - Status Monitor & Display Refresh Rate Driver
* **Dom, Samybigio, Arcdelta, Miki, Happy, Flopsider, Winnerboi77, Blaise, Alvise, TDRR, agjeococh, frost, letum00 and Xenshen** - Testing
* **Samybigio2011** - Italian translations
* **MasaGratoR** - Status Monitor & Display Refresh Rate driver
* **Dominatorul, Samybigio, Arcdelta, Miki, Happy, Flopsider, Winnerboi77, Blaise, Alvise, TDRR, agjeococh, frost, letum00, and Xenshen** - Testing
* **Samybigio2011, Miki** - Italian translations
* **angelblaster** - Korean translations
* **q1332348216-glitch** - Chinese translations
* **Nvidia** - [Tegra X1 Technical Reference Manual](https://developer.nvidia.com/embedded/dlc/tegra-x1-technical-reference-manual), soctherm driver, L4T

View File

@@ -4,8 +4,9 @@
| Version | Supported |
| ------- | ------------------ |
| 1.x | :white_check_mark: |
| 0.x | Not supported |
| 2.x.x | :white_check_mark: |
| 1.x.x | Not supported |
| 0.x.x | Not supported |
## Reporting a Vulnerability

View File

@@ -1,8 +1,6 @@
/*
* Copyright (c) Atmosphère-NX
*
* Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
@@ -95,6 +93,8 @@ namespace ams::ldr {
struct ProcessInfo {
os::NativeHandle process_handle;
uintptr_t code_address;
size_t total_size;
uintptr_t args_address;
size_t args_size;
uintptr_t nso_address[Nso_Count];
@@ -106,7 +106,16 @@ namespace ams::ldr {
bool has_main;
bool has_sdk;
bool has_subsdk;
bool has_nso[Nso_Count];
s8 nso_indices[Nso_Count];
};
struct AutoLoadModuleContext {
NsoHeader *headers;
int nso_count;
int rtld_idx;
int main_nso_idx;
int sdk_nso_idx;
AutoLoadModuleInfo ali;
};
/* Global NSO header cache. */
@@ -116,6 +125,10 @@ namespace ams::ldr {
bool g_is_pcv;
bool g_is_ptm;
/* Global Zstd decompression context. */
constexpr size_t ZstdDctxWorkspaceSize = 0x176E8;
alignas(8) u8 g_zstd_dctx_workspace[ZstdDctxWorkspaceSize];
Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {
/* No version verification is done before 8.1.0. */
R_SUCCEED_IF(hos::GetVersion() < hos::Version_8_1_0);
@@ -176,10 +189,15 @@ namespace ams::ldr {
return static_cast<Acid::PoolPartition>((meta->acid->flags & Acid::AcidFlag_PoolPartitionMask) >> Acid::AcidFlag_PoolPartitionShift);
}
Result LoadAutoLoadHeaders(NsoHeader *nso_headers, AutoLoadModuleInfo *ali, u32 acid_flags) {
Result LoadAutoLoadHeaders(AutoLoadModuleContext &ctx, u32 acid_flags) {
/* Clear NSOs. */
std::memset(nso_headers, 0, sizeof(*nso_headers) * Nso_Count);
*ali = {};
std::memset(g_nso_headers, 0, sizeof(g_nso_headers));
ctx.headers = g_nso_headers;
ctx.nso_count = 0;
ctx.rtld_idx = -1;
ctx.main_nso_idx = -1;
ctx.sdk_nso_idx = -1;
ctx.ali = {};
for (size_t i = 0; i < Nso_Count; i++) {
/* Only load browser DLLs if acid flags say to do so. */
@@ -206,16 +224,18 @@ namespace ams::ldr {
/* Read NSO header. */
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, 0, nso_headers + i, sizeof(*nso_headers)));
R_UNLESS(read_size == sizeof(*nso_headers), ldr::ResultInvalidNso());
R_TRY(fs::ReadFile(std::addressof(read_size), file, 0, g_nso_headers + ctx.nso_count, sizeof(NsoHeader)));
R_UNLESS(read_size == sizeof(NsoHeader), ldr::ResultInvalidNso());
/* Note nso is present. */
switch (i) {
case Nso_Rtld:
ali->has_rtld = true;
ctx.rtld_idx = ctx.nso_count;
ctx.ali.has_rtld = true;
break;
case Nso_Main:
ali->has_main = true;
ctx.main_nso_idx = ctx.nso_count;
ctx.ali.has_main = true;
break;
case Nso_SubSdk0:
case Nso_SubSdk1:
@@ -227,64 +247,65 @@ namespace ams::ldr {
case Nso_SubSdk7:
case Nso_SubSdk8:
case Nso_SubSdk9:
ali->has_subsdk = true;
ctx.ali.has_subsdk = true;
break;
case Nso_Sdk:
ali->has_sdk = true;
ctx.sdk_nso_idx = ctx.nso_count;
ctx.ali.has_sdk = true;
break;
}
ali->has_nso[i] = true;
ctx.ali.nso_indices[ctx.nso_count] = static_cast<s8>(i);
ctx.nso_count++;
}
}
R_SUCCEED();
}
Result CheckAutoLoad(const NsoHeader *nso_headers, const AutoLoadModuleInfo *ali, u32 acid_flags) {
Result CheckAutoLoad(const AutoLoadModuleContext &ctx, u32 acid_flags) {
/* We must always have a main. */
R_UNLESS(ali->has_main, ldr::ResultInvalidNso());
R_UNLESS(ctx.ali.has_main, ldr::ResultInvalidNso());
/* All NSOs must not be --X. */
/* This is "probably" not checked on Ounce? */
for (size_t i = 0; i < Nso_Count; ++i) {
R_UNLESS((nso_headers[i].flags & NsoHeader::Flag_PreventCodeReads) == 0, ldr::ResultInvalidNso());
}
/* Validate flags and extents for all present NSOs. */
for (int i = 0; i < ctx.nso_count; ++i) {
const auto &hdr = ctx.headers[i];
/* If we don't have an RTLD, we must only have a main. */
const bool has_browser_dll = (acid_flags & Acid::AcidFlag_LoadBrowserCoreDll) != 0;
if (!ali->has_rtld) {
/* If don't have rtld, we must also not have sdk. */
R_UNLESS(!ali->has_sdk, ldr::ResultInvalidNso());
/* All NSOs must not be --X. */
/* This is "probably" not checked on Ounce? */
R_UNLESS((hdr.flags & NsoHeader::Flag_PreventCodeReads) == 0, ldr::ResultInvalidNso());
/* We must also not have both subsdk and browser dll. */
R_UNLESS(!(ali->has_subsdk && has_browser_dll), ldr::ResultInvalidNso());
} else {
/* If we have rtld, we must not have browser core dll. */
R_UNLESS(!has_browser_dll, ldr::ResultInvalidNso());
}
/* Check NSO extents. */
for (size_t i = 0; i < Nso_Count; i++) {
/* Only validate the nsos we have. */
if (!ali->has_nso[i]) {
continue;
/* Zstd compression only allowed on main, and only when both rtld+sdk are present. */
if (i != ctx.main_nso_idx || ctx.rtld_idx < 0 || ctx.sdk_nso_idx < 0) {
R_UNLESS((hdr.flags & NsoHeader::Flag_UseZbicCompression) == 0, ldr::ResultInvalidNso());
}
/* NSOs must have page-aligned segments. */
R_UNLESS(util::IsAligned(nso_headers[i].text_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
R_UNLESS(util::IsAligned(nso_headers[i].ro_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
R_UNLESS(util::IsAligned(nso_headers[i].rw_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
R_UNLESS(util::IsAligned(hdr.text_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
R_UNLESS(util::IsAligned(hdr.ro_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
R_UNLESS(util::IsAligned(hdr.rw_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
/* NSOs must have zero text offset. */
R_UNLESS(nso_headers[i].text_dst_offset == 0, ldr::ResultInvalidNso());
R_UNLESS(hdr.text_dst_offset == 0, ldr::ResultInvalidNso());
/* NSO .text must precede .rodata. */
const size_t text_end = static_cast<size_t>(nso_headers[i].text_dst_offset) + static_cast<size_t>(nso_headers[i].text_size);
R_UNLESS(text_end <= static_cast<size_t>(nso_headers[i].ro_dst_offset), ldr::ResultInvalidNso());
const size_t text_end = static_cast<size_t>(hdr.text_dst_offset) + static_cast<size_t>(hdr.text_size);
R_UNLESS(text_end <= static_cast<size_t>(hdr.ro_dst_offset), ldr::ResultInvalidNso());
/* NSO .rodata must precede .rwdata. */
const size_t ro_end = static_cast<size_t>(nso_headers[i].ro_dst_offset) + static_cast<size_t>(nso_headers[i].ro_size);
R_UNLESS(ro_end <= static_cast<size_t>(nso_headers[i].rw_dst_offset), ldr::ResultInvalidNso());
const size_t ro_end = static_cast<size_t>(hdr.ro_dst_offset) + static_cast<size_t>(hdr.ro_size);
R_UNLESS(ro_end <= static_cast<size_t>(hdr.rw_dst_offset), ldr::ResultInvalidNso());
}
const bool has_browser_dll = (acid_flags & Acid::AcidFlag_LoadBrowserCoreDll) != 0;
if (ctx.ali.has_rtld || ctx.ali.has_sdk) {
/* If we have sdk we must have rtld. */
R_UNLESS(ctx.ali.has_rtld, ldr::ResultInvalidNso());
/* If we have rtld, we must not have browser core dll. */
R_UNLESS(!has_browser_dll, ldr::ResultInvalidNso());
} else {
/* We must not have both subsdk and browser dll. */
R_UNLESS(!(ctx.ali.has_subsdk && has_browser_dll), ldr::ResultInvalidNso());
}
R_SUCCEED();
@@ -300,8 +321,8 @@ namespace ams::ldr {
{ 0x010049900F546001 }, /* Super Mario 3D All-Stars: Super Mario 64 */
{ 0x010057D00ECE4000 }, /* Nintendo Switch Online (Nintendo 64) [for Japan] */
{ 0x01006F8002326000 }, /* Animal Crossing: New Horizons */
{ 0x01006FB00F50E000 }, /* [???] */
{ 0x010070300F50C000 }, /* [???] */
{ 0x01006FB00F50E000 }, /* 宝可梦 走吧!伊布 [Pokemon: Let's Go, Eevee! for China] */
{ 0x010070300F50C000 }, /* 宝可梦 走吧!皮卡丘 [Pokemon: Let's Go, Pikachu! for China] */
{ 0x010075100E8EC000 }, /* 马力欧卡丁车8 豪华版 [Mario Kart 8 Deluxe for China] */
{ 0x01008DB008C2C000 }, /* Pokemon Shield */
{ 0x01009AD008C4C000 }, /* Pokemon: Let's Go, Pikachu! [Kiosk] */
@@ -525,7 +546,7 @@ namespace ams::ldr {
return rand % (max + 1);
}
Result DecideAddressSpaceLayout(ProcessInfo *out, svc::CreateProcessParameter *out_param, const NsoHeader *nso_headers, const AutoLoadModuleInfo *ali, const ArgumentStore::Entry *argument) {
Result DecideAddressSpaceLayout(ProcessInfo *out, svc::CreateProcessParameter *out_param, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument) {
/* Clear output. */
out->args_address = 0;
out->args_size = 0;
@@ -536,35 +557,33 @@ namespace ams::ldr {
bool argument_allocated = false;
/* Calculate base offsets. */
for (size_t i = 0; i < Nso_Count; i++) {
if (ali->has_nso[i]) {
out->nso_address[i] = total_size;
const size_t text_end = static_cast<size_t>(nso_headers[i].text_dst_offset) + static_cast<size_t>(nso_headers[i].text_size);
const size_t ro_end = static_cast<size_t>(nso_headers[i].ro_dst_offset) + static_cast<size_t>(nso_headers[i].ro_size);
const size_t rw_end = static_cast<size_t>(nso_headers[i].rw_dst_offset) + static_cast<size_t>(nso_headers[i].rw_size);
out->nso_size[i] = text_end;
out->nso_size[i] = std::max(out->nso_size[i], ro_end);
out->nso_size[i] = std::max(out->nso_size[i], rw_end);
out->nso_size[i] += static_cast<size_t>(nso_headers[i].bss_size);
for (int i = 0; i < ctx.nso_count; i++) {
out->nso_address[i] = total_size;
const size_t text_end = static_cast<size_t>(ctx.headers[i].text_dst_offset) + static_cast<size_t>(ctx.headers[i].text_size);
const size_t ro_end = static_cast<size_t>(ctx.headers[i].ro_dst_offset) + static_cast<size_t>(ctx.headers[i].ro_size);
const size_t rw_end = static_cast<size_t>(ctx.headers[i].rw_dst_offset) + static_cast<size_t>(ctx.headers[i].rw_size);
out->nso_size[i] = text_end;
out->nso_size[i] = std::max(out->nso_size[i], ro_end);
out->nso_size[i] = std::max(out->nso_size[i], rw_end);
out->nso_size[i] += static_cast<size_t>(ctx.headers[i].bss_size);
const size_t aligned_up_size = util::AlignUp(out->nso_size[i], os::MemoryPageSize) & (AutoLoadModuleSizeMax - 1);
R_UNLESS(out->nso_size[i] <= aligned_up_size, ldr::ResultInvalidNso());
R_UNLESS(aligned_up_size > 0, ldr::ResultInvalidNso());
const size_t aligned_up_size = util::AlignUp(out->nso_size[i], os::MemoryPageSize) & (AutoLoadModuleSizeMax - 1);
R_UNLESS(out->nso_size[i] <= aligned_up_size, ldr::ResultInvalidNso());
R_UNLESS(aligned_up_size > 0, ldr::ResultInvalidNso());
out->nso_size[i] = aligned_up_size;
out->nso_size[i] = aligned_up_size;
R_UNLESS(util::CanAddWithoutOverflow(total_size, out->nso_size[i]), ldr::ResultInvalidNso());
total_size += out->nso_size[i];
R_UNLESS(util::CanAddWithoutOverflow(total_size, out->nso_size[i]), ldr::ResultInvalidNso());
total_size += out->nso_size[i];
if (!argument_allocated && argument != nullptr) {
out->args_address = total_size;
out->args_size = util::AlignUp(2 * sizeof(u32) + argument->argument_size * 2 + ArgumentStore::ArgumentBufferSize, os::MemoryPageSize);
if (!argument_allocated && argument != nullptr) {
out->args_address = total_size;
out->args_size = util::AlignUp(2 * sizeof(u32) + argument->argument_size * 2 + ArgumentStore::ArgumentBufferSize, os::MemoryPageSize);
R_UNLESS(util::CanAddWithoutOverflow(total_size, out->args_size), ldr::ResultInvalidNso());
total_size += out->args_size;
R_UNLESS(util::CanAddWithoutOverflow(total_size, out->args_size), ldr::ResultInvalidNso());
total_size += out->args_size;
argument_allocated = true;
}
argument_allocated = true;
}
}
@@ -609,11 +628,9 @@ namespace ams::ldr {
/* Set out. */
aslr_start += aslr_slide;
for (size_t i = 0; i < Nso_Count; i++) {
if (ali->has_nso[i]) {
R_UNLESS(util::CanAddWithoutOverflow(out->nso_address[i], aslr_start), ldr::ResultInvalidNso());
out->nso_address[i] += aslr_start;
}
for (int i = 0; i < ctx.nso_count; i++) {
R_UNLESS(util::CanAddWithoutOverflow(out->nso_address[i], aslr_start), ldr::ResultInvalidNso());
out->nso_address[i] += aslr_start;
}
if (out->args_address) {
R_UNLESS(util::CanAddWithoutOverflow(out->args_address, aslr_start), ldr::ResultInvalidNso());
@@ -622,69 +639,88 @@ namespace ams::ldr {
out_param->code_address = aslr_start;
out_param->code_num_pages = total_size >> 12;
out->total_size = total_size;
R_SUCCEED();
}
Result LoadAutoLoadModuleSegment(fs::FileHandle file, const NsoHeader::SegmentInfo *segment, size_t file_size, const u8 *file_hash, bool is_compressed, bool check_hash, uintptr_t map_base, uintptr_t map_end) {
Result LoadAutoLoadModuleSegment(fs::FileHandle file, size_t file_offset, size_t compressed_size, size_t segment_size, bool is_compressed, bool is_zstd, uintptr_t map_base, uintptr_t map_end) {
/* Select read size based on compression. */
if (!is_compressed) {
file_size = segment->size;
}
size_t file_size = is_compressed ? compressed_size : segment_size;
/* Validate size. */
R_UNLESS(file_size <= segment->size, ldr::ResultInvalidNso());
R_UNLESS(segment->size <= std::numeric_limits<s32>::max(), ldr::ResultInvalidNso());
R_UNLESS(file_size <= segment_size, ldr::ResultInvalidNso());
R_UNLESS(file_size <= std::numeric_limits<s32>::max(), ldr::ResultInvalidNso());
R_UNLESS(segment_size <= std::numeric_limits<s32>::max(), ldr::ResultInvalidNso());
/* Load data from file. */
uintptr_t load_address = is_compressed ? map_end - file_size : map_base;
uintptr_t load_address = is_compressed ? map_end - compressed_size : map_base;
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, segment->file_offset, reinterpret_cast<void *>(load_address), file_size));
R_TRY(fs::ReadFile(std::addressof(read_size), file, file_offset, reinterpret_cast<void *>(load_address), file_size));
R_UNLESS(read_size == file_size, ldr::ResultInvalidNso());
/* Uncompress if necessary. */
if (is_compressed) {
bool decompressed = (util::DecompressLZ4(reinterpret_cast<void *>(map_base), segment->size, reinterpret_cast<const void *>(load_address), file_size) == static_cast<int>(segment->size));
R_SUCCEED_IF(!is_compressed);
auto compressed_data_buf = reinterpret_cast<const void *>(load_address);
if (is_zstd) {
bool decompressed = util::DecompressZstdForLoader(reinterpret_cast<void *>(g_zstd_dctx_workspace), ZstdDctxWorkspaceSize, reinterpret_cast<void *>(map_base), static_cast<size_t>(map_end - map_base), segment_size, compressed_data_buf, file_size);
R_UNLESS(decompressed, ldr::ResultInvalidNso());
} else {
bool decompressed = (util::DecompressLZ4(reinterpret_cast<void *>(map_base), segment_size, compressed_data_buf, file_size) == static_cast<int>(segment_size));
R_UNLESS(decompressed, ldr::ResultInvalidNso());
}
/* Check hash if necessary. */
if (check_hash) {
u8 hash[crypto::Sha256Generator::HashSize];
crypto::GenerateSha256(hash, sizeof(hash), reinterpret_cast<void *>(map_base), segment->size);
R_UNLESS(std::memcmp(hash, file_hash, sizeof(hash)) == 0, ldr::ResultInvalidNso());
}
R_SUCCEED();
}
Result LoadAutoLoadModule(os::NativeHandle process_handle, fs::FileHandle file, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size) {
Result CheckSegmentHash(const NsoHeader *nso_header, uintptr_t map_address, NsoHeader::Segment segment) {
if ((nso_header->flags & (NsoHeader::Flag_CheckHashText << segment)) == 0) {
R_SUCCEED();
}
u8 hash[crypto::Sha256Generator::HashSize];
crypto::GenerateSha256(hash, sizeof(hash),
reinterpret_cast<void *>(map_address + nso_header->segments[segment].dst_offset),
nso_header->segments[segment].size);
R_UNLESS(std::memcmp(hash, nso_header->segment_hashes[segment], sizeof(hash)) == 0, ldr::ResultInvalidNso());
R_SUCCEED();
}
Result LoadAutoLoadModule(os::NativeHandle process_handle, fs::FileHandle file, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size, size_t map_size) {
const bool is_zstd = (nso_header->flags & NsoHeader::Flag_UseZbicCompression) != 0;
/* Map and read data from file. */
{
/* Map the process memory. */
void *mapped_memory = nullptr;
R_TRY(os::MapProcessMemory(std::addressof(mapped_memory), process_handle, nso_address, nso_size, GenerateSecureRandom));
ON_SCOPE_EXIT { os::UnmapProcessMemory(mapped_memory, process_handle, nso_address, nso_size); };
R_TRY(os::MapProcessMemory(std::addressof(mapped_memory), process_handle, nso_address, map_size, GenerateSecureRandom));
ON_SCOPE_EXIT { os::UnmapProcessMemory(mapped_memory, process_handle, nso_address, map_size); };
const uintptr_t map_address = reinterpret_cast<uintptr_t>(mapped_memory);
const uintptr_t map_end = map_address + map_size;
/* Load NSO segments. */
R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Text]), nso_header->text_compressed_size, nso_header->text_hash, (nso_header->flags & NsoHeader::Flag_CompressedText) != 0,
(nso_header->flags & NsoHeader::Flag_CheckHashText) != 0, map_address + nso_header->text_dst_offset, map_address + nso_size));
R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Ro]), nso_header->ro_compressed_size, nso_header->ro_hash, (nso_header->flags & NsoHeader::Flag_CompressedRo) != 0,
(nso_header->flags & NsoHeader::Flag_CheckHashRo) != 0, map_address + nso_header->ro_dst_offset, map_address + nso_size));
R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Rw]), nso_header->rw_compressed_size, nso_header->rw_hash, (nso_header->flags & NsoHeader::Flag_CompressedRw) != 0,
(nso_header->flags & NsoHeader::Flag_CheckHashRw) != 0, map_address + nso_header->rw_dst_offset, map_address + nso_size));
R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Text].file_offset, nso_header->text_compressed_size, nso_header->text_size,
(nso_header->flags & NsoHeader::Flag_CompressedText) != 0, is_zstd, map_address + nso_header->text_dst_offset, map_end));
R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Ro].file_offset, nso_header->ro_compressed_size, nso_header->ro_size,
(nso_header->flags & NsoHeader::Flag_CompressedRo) != 0, is_zstd, map_address + nso_header->ro_dst_offset, map_end));
R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Rw].file_offset, nso_header->rw_compressed_size, nso_header->rw_size,
(nso_header->flags & NsoHeader::Flag_CompressedRw) != 0, is_zstd, map_address + nso_header->rw_dst_offset, map_end));
/* Clear unused space to zero. */
const size_t text_end = static_cast<size_t>(nso_header->text_dst_offset) + static_cast<size_t>(nso_header->text_size);
const size_t ro_end = static_cast<size_t>(nso_header->ro_dst_offset) + static_cast<size_t>(nso_header->ro_size);
const size_t rw_end = static_cast<size_t>(nso_header->rw_dst_offset) + static_cast<size_t>(nso_header->rw_size);
std::memset(reinterpret_cast<void *>(map_address + 0), 0, nso_header->text_dst_offset);
std::memset(reinterpret_cast<void *>(map_address + text_end), 0, nso_header->ro_dst_offset - text_end);
std::memset(reinterpret_cast<void *>(map_address + ro_end), 0, nso_header->rw_dst_offset - ro_end);
std::memset(reinterpret_cast<void *>(map_address + rw_end), 0, nso_header->bss_size);
std::memset(reinterpret_cast<void *>(map_address + rw_end), 0, nso_size - rw_end);
/* Check segment hashes. */
R_TRY(CheckSegmentHash(nso_header, map_address, NsoHeader::Segment_Text));
R_TRY(CheckSegmentHash(nso_header, map_address, NsoHeader::Segment_Ro));
R_TRY(CheckSegmentHash(nso_header, map_address, NsoHeader::Segment_Rw));
/* Apply embedded patches. */
ApplyEmbeddedPatchesToModule(nso_header->module_id, map_address, nso_size);
@@ -711,25 +747,31 @@ namespace ams::ldr {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->text_dst_offset, text_size, prevent_code_reads ? os::MemoryPermission_ExecuteOnly : os::MemoryPermission_ReadExecute));
}
if (ro_size) {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->ro_dst_offset, ro_size, os::MemoryPermission_ReadOnly));
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->ro_dst_offset, ro_size, os::MemoryPermission_ReadOnly));
}
if (rw_size) {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->rw_dst_offset, rw_size, os::MemoryPermission_ReadWrite));
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->rw_dst_offset, rw_size, os::MemoryPermission_ReadWrite));
}
R_SUCCEED();
}
Result LoadAutoLoadModules(const ProcessInfo *process_info, const NsoHeader *nso_headers, const AutoLoadModuleInfo *ali, const ArgumentStore::Entry *argument) {
Result LoadAutoLoadModules(const ProcessInfo *process_info, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument) {
/* Load each NSO. */
for (size_t i = 0; i < Nso_Count; i++) {
if (ali->has_nso[i]) {
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(file); };
const uintptr_t total_end = process_info->code_address + process_info->total_size;
R_TRY(LoadAutoLoadModule(process_info->process_handle, file, nso_headers + i, process_info->nso_address[i], process_info->nso_size[i]));
}
for (int i = 0; i < ctx.nso_count; i++) {
const NsoIndex nso_idx = static_cast<NsoIndex>(ctx.ali.nso_indices[i]);
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), GetNsoPath(nso_idx), fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(file); };
const bool is_zstd = (ctx.headers[i].flags & NsoHeader::Flag_UseZbicCompression) != 0;
const size_t map_size = is_zstd ? (total_end - process_info->nso_address[i]) : process_info->nso_size[i];
R_TRY(LoadAutoLoadModule(process_info->process_handle, file, ctx.headers + i,
process_info->nso_address[i], process_info->nso_size[i], map_size));
}
/* Load arguments, if present. */
@@ -755,13 +797,13 @@ namespace ams::ldr {
R_SUCCEED();
}
Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const NsoHeader *nso_headers, const AutoLoadModuleInfo *ali, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) {
Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) {
/* Get CreateProcessParameter. */
svc::CreateProcessParameter param;
R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit));
/* Decide on an NSO layout. */
R_TRY(DecideAddressSpaceLayout(out, std::addressof(param), nso_headers, ali, argument));
R_TRY(DecideAddressSpaceLayout(out, std::addressof(param), ctx, argument));
/* Actually create process. */
svc::Handle process_handle;
@@ -769,10 +811,11 @@ namespace ams::ldr {
/* Set the output handle, and ensure that if we fail after this point we clean it up. */
out->process_handle = process_handle;
out->code_address = param.code_address;
ON_RESULT_FAILURE { svc::CloseHandle(process_handle); };
/* Load all auto load modules. */
R_RETURN(LoadAutoLoadModules(out, nso_headers, ali, argument));
R_RETURN(LoadAutoLoadModules(out, ctx, argument));
}
}
@@ -813,13 +856,13 @@ namespace ams::ldr {
}
/* Load, validate NSO headers. */
AutoLoadModuleInfo auto_load_info = {};
R_TRY(LoadAutoLoadHeaders(g_nso_headers, std::addressof(auto_load_info), meta.acid->flags));
R_TRY(CheckAutoLoad(g_nso_headers, std::addressof(auto_load_info), meta.acid->flags));
AutoLoadModuleContext ctx;
R_TRY(LoadAutoLoadHeaders(ctx, meta.acid->flags));
R_TRY(CheckAutoLoad(ctx, meta.acid->flags));
/* Actually create the process and load NSOs into process memory. */
ProcessInfo info;
R_TRY(CreateProcessAndLoadAutoLoadModules(std::addressof(info), std::addressof(meta), g_nso_headers, std::addressof(auto_load_info), argument, flags, resource_limit));
R_TRY(CreateProcessAndLoadAutoLoadModules(std::addressof(info), std::addressof(meta), ctx, argument, flags, resource_limit));
/* Register NSOs with the RoManager. */
{
@@ -831,10 +874,8 @@ namespace ams::ldr {
RoManager::GetInstance().RegisterProcess(pin_id, process_id, meta.aci->program_id, as_type == Npdm::AddressSpaceType_64Bit || as_type == Npdm::AddressSpaceType_64BitDeprecated);
/* Register all NSOs. */
for (size_t i = 0; i < Nso_Count; i++) {
if (auto_load_info.has_nso[i]) {
RoManager::GetInstance().AddNso(pin_id, g_nso_headers[i].module_id, info.nso_address[i], info.nso_size[i]);
}
for (int i = 0; i < ctx.nso_count; i++) {
RoManager::GetInstance().AddNso(pin_id, ctx.headers[i].module_id, info.nso_address[i], info.nso_size[i]);
}
}

View File

@@ -31,16 +31,21 @@
namespace ams::ldr::hoc {
volatile CustomizeTable C = {
/* Disables RAM powerdown */
.hpMode = DISABLED,
.commonEmcMemVolt = 1175000, /* LPDDR4X JEDEC Specification */
.commonEmcMemVolt = 1175000, /* LPDDR4(X) JEDEC Specification */
.eristaEmcMaxClock = 1600000, /* Maximum HB-MGCH ram rating */
.eristaEmcMaxClock1 = 1600000,
.eristaEmcMaxClock2 = 1600000,
.marikoEmcMaxClock = 1866000, /* 1866MHz @ 1866tWRL is guaranteed to work on all Mariko units */
.marikoEmcVddqVolt = 600000, /* Micron: 600mV, other manafacturers: 640mV */
/* Available: 66MHz step rate, 100MHz step rate and jedec. */
/* Jedec freqs are 1333MHz, 1600MHz, 1866MHz, 2133MHz, 2400MHz, 2666MHz, 2933MHz, 3200MHz. */
.stepMode = StepMode_66MHz,
.marikoEmcMaxClock = 2133000, /* 1866MHz @ 1866tWRL is guaranteed to work on all Mariko units */
.marikoEmcVddqVolt = 600000,
.emcDvbShift = 0,
// Primary
@@ -54,6 +59,29 @@ volatile CustomizeTable C = {
.t7_tWTR = 0,
.t8_tREFI = 0,
/* At 1333WL, for some reason (incorrect ram timing config in mtc table?), tRP causes crashes at high reductions - 2 seems to be the most common limit. */
/* This is a lazy workaround until I find the issue... */
.t2_tRP_cap = 2,
/* Frequency where non low timings gets used. */
.timingEmcTbreak = DISABLED,
.low_t6_tRTW = DISABLED,
.low_t7_tWTR = DISABLED,
.readLatency = {
DISABLED,
DISABLED,
DISABLED,
DISABLED,
},
.writeLatency = {
DISABLED,
DISABLED,
DISABLED,
DISABLED,
},
/* You can mix and match different latencies if needed */
/*
* Read:
@@ -68,13 +96,13 @@ volatile CustomizeTable C = {
* 1331WL = 12
*/
.mem_burst_read_latency = RL_1866,
.mem_burst_write_latency = WL_1866,
.mem_burst_read_latency = RL_1600,
.mem_burst_write_latency = WL_1600,
.eristaCpuUV = 0,
.eristaCpuVmin = 800,
.eristaCpuMaxVolt = 1200,
/* Unlocks up to 2295 Mhz CPU, usage is not recommended. */
/* Unlocks up to 2397 Mhz CPU, usage is not recommended. */
.eristaCpuUnlock = DISABLED,
.marikoCpuUVLow = 0, // No undervolt
@@ -86,27 +114,25 @@ volatile CustomizeTable C = {
/* 1120mV is NVIDIA rating */
.marikoCpuMaxVolt = 1120,
/* Supported values: 1963000, 2091000, 2193000, 2295000, 2397000, 2499000, 2601000, 2703000. */
/* 1963000 is official rating of T214/Mariko, fully safe. */
/* Supported values: 1963500, 2091000, 2193000, 2295000, 2397000, 2499000, 2601000, 2703000. */
/* 1963500 is official rating of T214/Mariko, fully safe. */
/* 2091000-2295000 is a slight OC which should work on all units, but no guarantees. */
/* 2397000 is the max safe OC for most average units with tuned undervolt. */
/* 2499000 should be used with caution. */
/* 2601000 exceeds pmic limit on most consoles. */
/* 2703000 is potentially dangerous and not advised. */
.marikoCpuMaxClock = 1963000,
.marikoCpuMaxClock = 1963500,
.eristaCpuBoostClock = 1785000, // Default boost clock
.marikoCpuBoostClock = 1963000, // Default boost clock
.marikoCpuBoostClock = 1963500, // Default boost clock
.eristaGpuUV = 0,
.eristaGpuVmin = 810,
.marikoGpuUV = 0,
/* For automatic vmin detection, set this to AUTO. */
/* vmin past 795mV won't work due to HOS limitation */
/* Vmin is automatically set to 800mV when SoC temperature is below 20C */
.marikoGpuVmin = AUTO,
/* Vmin past 795mV won't work due boot voltage being 800mV. */
.marikoGpuVmin = 610,
.marikoGpuVmax = 800,
@@ -222,7 +248,7 @@ volatile CustomizeTable C = {
{ 1683000, { 1168000, }, { 5100873, -279186, 4747, } },
{ 1785000, { 1225000, }, { 5100873, -279186, 4747, } },
{ 1887000, { 1225000, }, { 5100873, -279186, 4747, } },
{ 1963500, { 1227500, }, { 5100873, -279186, 4747, } },
{ 1989000, { 1227500, }, { 5100873, -279186, 4747, } },
{ 2091000, { 1227500, }, { 5100873, -279186, 4747, } },
{ 2193000, { 1227500, }, { 5100873, -279186, 4747, } },
{ 2295000, { 1256250, }, { 5100873, -279186, 4747, } },

View File

@@ -20,7 +20,7 @@
#pragma once
#define CUST_REV 1
#define CUST_REV 2
#include "oc_common.hpp"
#include "pcv/pcv_common.hpp"
@@ -36,6 +36,12 @@ enum TableConfig: u32 {
EXTREME_TABLE = 4,
};
enum StepMode: u32 {
StepMode_66MHz = 0,
StepMode_100MHz = 1,
StepMode_Jedec = 2,
};
/*
* Read:
* 2133RL = 40
@@ -82,6 +88,8 @@ typedef struct CustomizeTable {
u32 eristaEmcMaxClock;
u32 eristaEmcMaxClock1;
u32 eristaEmcMaxClock2;
StepMode stepMode;
u32 marikoEmcMaxClock;
u32 marikoEmcVddqVolt;
u32 emcDvbShift;
@@ -95,6 +103,15 @@ typedef struct CustomizeTable {
u32 t7_tWTR;
u32 t8_tREFI;
u32 t2_tRP_cap;
u32 timingEmcTbreak;
u32 low_t6_tRTW;
u32 low_t7_tWTR;
u32 readLatency[4];
u32 writeLatency[4];
u32 mem_burst_read_latency;
u32 mem_burst_write_latency;

View File

@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../oc_common.hpp"
#include <stratosphere.hpp>
#include "../mtc_timing_value.hpp"
namespace ams::ldr::hoc::pcv::erista {

View File

@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../oc_common.hpp"
#include <stratosphere.hpp>
#include "../mtc_timing_value.hpp"
#include "timing_tables.hpp"
@@ -30,6 +30,70 @@ namespace ams::ldr::hoc::pcv::mariko {
rext = 0x1A;
}
void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) {
latency += index * latencyStep;
}
static u32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) {
u32 maxIndex = 0;
for (u32 i = 0; i < latencySize; ++i) {
if (latencyArray[i]) {
maxIndex = i;
}
}
return maxIndex;
}
void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) {
if (freq > 1600'000 && freq <= 1866'000) { /* 1866tRWL */
latency += latencyStep * 2;
} else { /* 2133tRWL */
latency += latencyStep * 3;
}
}
void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) {
for (u32 i = 0; i <= indexMax; ++i) {
if (latencyArray[i] != 0 && freq <= latencyArray[i]) {
SwitchLatency(latency, i, latencyStep);
return;
}
}
SwitchLatency(latency, indexMax, latencyStep);
}
void HandleLatency(u32 freq) {
static u32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency));
static u32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency));
constexpr u32 ReadLatencyStep = 4;
constexpr u32 WriteLatencyStep = 2;
bool autoLatencyRead = false, autoLatencyWrite = false;
if (rlIndexMax == 0) {
AutoLatency(RL, freq, ReadLatencyStep);
autoLatencyRead = true;
}
if (wlIndexMax == 0) {
AutoLatency(WL, freq, WriteLatencyStep);
autoLatencyWrite = true;
}
if (autoLatencyRead && autoLatencyWrite) {
return;
}
if (!autoLatencyRead) {
HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep);
}
if (!autoLatencyWrite) {
HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep);
}
}
void CalculateMrw2() {
static const u8 rlMapDBI[8] = {
6, 12, 16, 22, 28, 32, 36, 40
@@ -59,8 +123,80 @@ namespace ams::ldr::hoc::pcv::mariko {
mrw2 = static_cast<u8>(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6)));
}
void CalculateTimings() {
void CalculateTimings(double tCK_avg, u32 freq) {
RL = RL_1331;
WL = WL_1331;
HandleLatency(freq);
GetRext();
/* At 1333WL, for some reason (incorrect ram timing config in mtc table?), tRP causes crashes at high reductions - 2 seems to be the most common limit. */
/* This is a lazy workaround until I find the issue... */
u32 tRPpbIndex = C.t2_tRP;
if (WL == WL_1331) {
tRPpbIndex = MIN(C.t2_tRP_cap, C.t2_tRP);
}
tRCD = tRCD_values[C.t1_tRCD];
tRPpb = tRP_values[tRPpbIndex];
tRAS = tRAS_values[C.t3_tRAS];
tRRD = tRRD_values[C.t4_tRRD];
tRFCpb = tRFC_values[C.t5_tRFC];
u32 tRTW = C.t6_tRTW;
u32 tWTR = 10 - tWTR_values[C.t7_tWTR];
if (freq < C.timingEmcTbreak) {
tRTW = C.low_t6_tRTW;
tWTR = 10 - tWTR_values[C.low_t7_tWTR];
}
s32 finetRTW = C.fineTune_t6_tRTW;
s32 finetWTR = C.fineTune_t7_tWTR;
tRC = tRAS + tRPpb;
tRFCab = tRFCpb * 2;
tXSR = static_cast<double>(tRFCab + 7.5);
tFAW = static_cast<u32>(tRRD * 4.0);
tRPab = tRPpb + 3;
tR2P = CEIL((RL * 0.426) - 2.0);
tR2W = FLOOR(FLOOR((5.0 / tCK_avg) + ((FLOOR(48.0 / WL) - 0.478) * 3.0)) / 1.501) + RL - (tRTW * 3) + finetRTW;
tRTM = FLOOR((10.0 + RL) + (3.502 / tCK_avg)) + FLOOR(7.489 / tCK_avg);
tRATM = CEIL((tRTM - 10.0) + (RL * 0.426));
rdv = RL + FLOOR((5.105 / tCK_avg) + 17.017);
qpop = rdv - 14;
quse_width = CEIL(((4.897 / tCK_avg) - FLOOR(2.538 / tCK_avg)) + 3.782);
quse = FLOOR(RL + ((5.082 / tCK_avg) + FLOOR(2.560 / tCK_avg))) - CEIL(4.820 / tCK_avg);
einput_duration = FLOOR(9.936 / tCK_avg) + 5.0 + quse_width;
einput = quse - CEIL(9.928 / tCK_avg);
u32 qrst_duration = FLOOR(8.399 - tCK_avg);
u32 qrstLow = MAX(static_cast<s32>(einput - qrst_duration - 2), static_cast<s32>(0));
qrst = PACK_U32(qrst_duration, qrstLow);
ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
tW2P = (CEIL(WL * 1.7303) * 2) - 5;
tWTPDEN = CEIL(((1.803 / tCK_avg) + MAX(RL + (2.694 / tCK_avg), static_cast<double>(tW2P))) + (BL / 2));
tW2R = FLOOR(MAX((5.020 / tCK_avg) + 1.130, WL - MAX(-CEIL(0.258 * (WL - RL)), 1.964)) * 1.964) + WL - CEIL(tWTR / tCK_avg) + finetWTR;
tWTM = CEIL(WL + ((7.570 / tCK_avg) + 8.753));
tWATM = (tWTM + (FLOOR(WL / 0.816) * 2.0)) - 4.0;
wdv = WL;
wsv = WL - 2;
wev = 0xA + (WL - 14);
u32 obdlyHigh = 3 / FLOOR(MIN(static_cast<double>(2), tCK_avg * (WL - 7)));
u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
u32 tMMRI = tRCD + (tCK_avg * 3);
pdex2mrr = tMMRI + 10;
CalculateMrw2();
}

View File

@@ -18,7 +18,7 @@
namespace ams::ldr::hoc::pcv::mariko {
void CalculateTimings();
void CalculateTimings(double tCK_avg, u32 freq);
}

View File

@@ -111,60 +111,54 @@ namespace ams::ldr::hoc {
const std::array<u32, 10> tWTR_values = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
const std::array<u32, 6> tREFpb_values = { 3900, 5850, 7800, 11700, 15600, 99999 };
const double tCK_avg = 1000'000.0 / C.marikoEmcMaxClock;
inline u32 tRCD;
inline u32 tRPpb;
inline u32 tRAS;
inline double tRRD;
inline u32 tRFCpb;
const u32 tRCD = tRCD_values[C.t1_tRCD];
const u32 tRPpb = tRP_values[C.t2_tRP];
const u32 tRAS = tRAS_values[C.t3_tRAS];
const double tRRD = tRRD_values[C.t4_tRRD];
const u32 tRFCpb = tRFC_values[C.t5_tRFC];
const u32 tWTR = 10 - tWTR_values[C.t7_tWTR];
const s32 finetRTW = C.fineTune_t6_tRTW;
const s32 finetWTR = C.fineTune_t7_tWTR;
inline u32 tRC;
inline u32 tRFCab;
inline double tXSR;
inline u32 tFAW;
inline double tRPab;
const u32 tRC = tRAS + tRPpb;
const u32 tRFCab = tRFCpb * 2;
const double tXSR = static_cast<double>(tRFCab + 7.5);
const u32 tFAW = static_cast<u32>(tRRD * 4.0);
const double tRPab = tRPpb + 3;
inline u32 RL;
inline u32 WL;
const u32 tR2P = CEIL((RL * 0.426) - 2.0);
const u32 tR2W = FLOOR(FLOOR((5.0 / tCK_avg) + ((FLOOR(48.0 / WL) - 0.478) * 3.0)) / 1.501) + RL - (C.t6_tRTW * 3) + finetRTW;
const u32 tRTM = FLOOR((10.0 + RL) + (3.502 / tCK_avg)) + FLOOR(7.489 / tCK_avg);
const u32 tRATM = CEIL((tRTM - 10.0) + (RL * 0.426));
inline u32 tR2P;
inline u32 tR2W;
inline u32 tRTM;
inline u32 tRATM;
inline u32 rext;
const u32 rdv = RL + FLOOR((5.105 / tCK_avg) + 17.017);
const u32 qpop = rdv - 14;
const u32 quse_width = CEIL(((4.897 / tCK_avg) - FLOOR(2.538 / tCK_avg)) + 3.782);
const u32 quse = FLOOR(RL + ((5.082 / tCK_avg) + FLOOR(2.560 / tCK_avg))) - CEIL(4.820 / tCK_avg);
const u32 einput_duration = FLOOR(9.936 / tCK_avg) + 5.0 + quse_width;
const u32 einput = quse - CEIL(9.928 / tCK_avg);
const u32 qrst_duration = FLOOR(8.399 - tCK_avg);
const u32 qrstLow = MAX(static_cast<s32>(einput - qrst_duration - 2), static_cast<s32>(0));
const u32 qrst = PACK_U32(qrst_duration, qrstLow);
const u32 ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
const u32 qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
const u32 tW2P = (CEIL(WL * 1.7303) * 2) - 5;
const u32 tWTPDEN = CEIL(((1.803 / tCK_avg) + MAX(RL + (2.694 / tCK_avg), static_cast<double>(tW2P))) + (BL / 2));
const u32 tW2R = FLOOR(MAX((5.020 / tCK_avg) + 1.130, WL - MAX(-CEIL(0.258 * (WL - RL)), 1.964)) * 1.964) + WL - CEIL(tWTR / tCK_avg) + finetWTR;
const u32 tWTM = CEIL(WL + ((7.570 / tCK_avg) + 8.753));
const u32 tWATM = (tWTM + (FLOOR(WL / 0.816) * 2.0)) - 4.0;
inline u32 rdv;
inline u32 qpop;
inline u32 quse_width;
inline u32 quse;
inline u32 einput_duration;
inline u32 einput;
inline u32 qrst;
inline u32 ibdly;
inline u32 qsafe;
const u32 wdv = WL;
const u32 wsv = WL - 2;
const u32 wev = 0xA + (WL - 14);
inline u32 tW2P;
inline u32 tWTPDEN;
inline u32 tW2R;
inline u32 tWTM;
inline u32 tWATM;
const u32 obdlyHigh = 3 / FLOOR(MIN(static_cast<double>(2), tCK_avg * (WL - 7)));
const u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
const u32 obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
inline u32 wdv;
inline u32 wsv;
inline u32 wev;
const u32 pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
inline u32 obdly;
const u32 tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
inline u32 pdex2rw;
const double tMMRI = tRCD + (tCK_avg * 3);
const double pdex2mrr = tMMRI + 10; /* Do this properly? */
inline u32 tCLKSTOP;
inline u32 pdex2mrr;
inline u8 mrw2;
}

View File

@@ -28,6 +28,7 @@
#endif
#include "customize.hpp"
#include "oc_log.hpp"
#define PATCH_OFFSET(offset, value) \
static_assert(sizeof(__typeof__(offset)) <= sizeof(u64)); \
@@ -50,6 +51,7 @@ namespace ams::ldr {
R_DEFINE_ERROR_RESULT(UninitializedPatcher, 1013);
R_DEFINE_ERROR_RESULT(UnsuccessfulPatcher, 1014);
R_DEFINE_ERROR_RESULT(SafetyCheckFailure, 1015);
R_DEFINE_ERROR_RESULT(InvalidMtcTablePattern, 1016);
}
namespace ams::ldr::hoc {

View File

@@ -0,0 +1,128 @@
/*
* Copyright (c) 2019 m4xw <m4x@m4xw.net>
* Copyright (c) 2019 Atmosphere-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* See https://github.com/lulle2007200/emuMMC/blob/internal-emummc/source/ */
#include "oc_common.hpp"
#if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
#include "fatal_handler_bin.h"
#endif
namespace ams::ldr::hoc {
#define ATMOSPHERE_REBOOT_TO_FATAL_MAGIC 0x32454641
#define ATMOSPHERE_IRAM_PAYLOAD_BASE 0x40010000
#define ATMOSPHERE_FATAL_ERROR_ADDR 0x4003E000
_Alignas(4096) u8 working_buf[4096];
void SmcRebootToIramPayload() {
SecmonArgs args;
args.X[0] = 0xC3000401;
args.X[1] = 65001;
args.X[2] = 0;
args.X[3] = 2;
svcCallSecureMonitor(&args);
}
Result SmcCopyToIram(uintptr_t dest, const void *src, u32 size) {
SecmonArgs args;
args.X[0] = 0xF0000201;
args.X[1] = (u64)src;
args.X[2] = (u64)dest;
args.X[3] = size;
args.X[4] = 1;
svcCallSecureMonitor(&args);
Result rc = 0;
if (args.X[0] != 0) {
rc = (26u | ((u32)args.X[0] << 9u));
}
return rc;
}
Result SmcCopyFromIram(void *dest, uintptr_t src, u32 size) {
SecmonArgs args;
args.X[0] = 0xF0000201;
args.X[1] = (u64)dest;
args.X[2] = (u64)src;
args.X[3] = size;
args.X[4] = 0;
svcCallSecureMonitor(&args);
Result rc = 0;
if (args.X[0] != 0) {
rc = (26u | ((u32)args.X[0] << 9u));
}
return rc;
}
struct log_ctx_t {
u32 magic;
u32 sz;
u32 start;
u32 end;
char buf[];
};
#define IRAM_LOG_CTX_ADDR 0x4003C000
#define IRAM_LOG_MAX_SZ 4096
#if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
void Log(const char *data, ...) {
static const u32 max_log_sz = sizeof(working_buf) - sizeof(log_ctx_t);
static bool initDone = false;
log_ctx_t *log_ctx = (log_ctx_t*)working_buf;
SmcCopyFromIram(working_buf, IRAM_LOG_CTX_ADDR, sizeof(working_buf));
if (!initDone) {
initDone = true;
log_ctx->buf[0] = '\0';
log_ctx->magic = 0xaabbccdd;
log_ctx->start = 0;
log_ctx->end = 0;
}
va_list args;
va_start(args, data);
s32 res = vsnprintf(log_ctx->buf + log_ctx->end, max_log_sz - log_ctx->end, data, args);
va_end(args);
if (res < 0 || res >= (static_cast<s32>(max_log_sz - log_ctx->end))) {
SmcCopyToIram(IRAM_LOG_CTX_ADDR, working_buf, sizeof(working_buf));
return;
}
log_ctx->end += res;
SmcCopyToIram(IRAM_LOG_CTX_ADDR, working_buf, sizeof(working_buf));
}
#endif
#if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
void ViewLog() {
constexpr size_t PageSize = 4096;
for (size_t ofs = 0; ofs < fatal_handler_bin_size; ofs += PageSize) {
memcpy(&working_buf, fatal_handler_bin + ofs, std::min(fatal_handler_bin_size - ofs, PageSize));
SmcCopyToIram(ATMOSPHERE_IRAM_PAYLOAD_BASE + ofs, &working_buf, std::min(fatal_handler_bin_size - ofs, PageSize));
}
SmcRebootToIramPayload();
while(true){}
}
#endif
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) 2019 m4xw <m4x@m4xw.net>
* Copyright (c) 2019 Atmosphere-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* See https://github.com/lulle2007200/emuMMC/blob/internal-emummc/source/ */
#pragma once
namespace ams::ldr::hoc {
void Log(const char *data, ...);
void ViewLog();
}

View File

@@ -144,18 +144,18 @@ namespace ams::ldr::hoc::pcv {
using namespace ams::ldr::hoc::pcv;
sValidator validators[] = {
{ C.eristaCpuBoostClock, 1020'000, 2295'000, true, panic::Cpu },
{ C.eristaCpuBoostClock, 1020'000, 2397'000, true, panic::Cpu },
{ C.marikoCpuBoostClock, 1020'000, 2703'000, true, panic::Cpu },
{ C.eristaCpuMaxVolt, 1000, 1260, false, panic::Cpu },
{ C.marikoCpuMaxVolt, 1000, 1200, false, panic::Cpu },
{ eristaCpuDvfsMaxFreq, 1785'000, 2295'000, false, panic::Cpu },
{ eristaCpuDvfsMaxFreq, 1785'000, 2397'000, false, panic::Cpu },
{ marikoCpuDvfsMaxFreq, 1785'000, 2703'000, false, panic::Cpu },
{ C.commonEmcMemVolt, 912'500, 1350'000, false, panic::Emc }, // Official burst vmax for the RAMs is 1500mV
{ GET_MAX_OF_ARR(erista::maxEmcClocks), 1600'000, 2600'000, false, panic::Emc },
{ C.marikoEmcMaxClock, 1600'000, 3500'000, false, panic::Emc },
{ C.marikoEmcVddqVolt, 250'000, 700'000, false, panic::Emc },
{ eristaGpuDvfsMaxFreq, 768'000, 1152'000, false, panic::Gpu },
{ marikoGpuDvfsMaxFreq, 768'000, 1570'000, false, panic::Gpu },
{ marikoGpuDvfsMaxFreq, 768'000, 1536'000, false, panic::Gpu },
{ C.marikoGpuVmax, 800, 960, false, panic::Gpu },
};
@@ -173,11 +173,14 @@ namespace ams::ldr::hoc::pcv {
void Patch(uintptr_t mapped_nso, size_t nso_size) {
#ifdef ATMOSPHERE_IS_STRATOSPHERE
SafetyCheck();
bool isMariko = (spl::GetSocType() == spl::SocType_Mariko);
if (isMariko)
if (isMariko) {
mariko::Patch(mapped_nso, nso_size);
else
} else {
erista::Patch(mapped_nso, nso_size);
}
#endif
}

View File

@@ -22,276 +22,12 @@
#include "../oc_common.hpp"
#include "pcv_common.hpp"
#include "pcv_erista.hpp"
#include "pcv_mariko.hpp"
#include "pcv_asm.hpp"
namespace ams::ldr::hoc::pcv {
namespace mariko {
constexpr cvb_entry_t CpuCvbTableDefault[] = {
{ 204000, { 721589, -12695, 27 }, { } },
{ 306000, { 747134, -14195, 27 }, { } },
{ 408000, { 776324, -15705, 27 }, { } },
{ 510000, { 809160, -17205, 27 }, { } },
{ 612000, { 845641, -18715, 27 }, { } },
{ 714000, { 885768, -20215, 27 }, { } },
{ 816000, { 929540, -21725, 27 }, { } },
{ 918000, { 976958, -23225, 27 }, { } },
{ 1020000, { 1028021, -24725, 27 }, { 1120000 } },
{ 1122000, { 1082730, -26235, 27 }, { 1120000 } },
{ 1224000, { 1141084, -27735, 27 }, { 1120000 } },
{ 1326000, { 1203084, -29245, 27 }, { 1120000 } },
{ 1428000, { 1268729, -30745, 27 }, { 1120000 } },
{ 1581000, { 1374032, -33005, 27 }, { 1120000 } },
{ 1683000, { 1448791, -34505, 27 }, { 1120000 } },
{ 1785000, { 1527196, -36015, 27 }, { 1120000 } },
{ 1887000, { 1609246, -37515, 27 }, { 1120000 } },
{ 1963500, { 1675751, -38635, 27 }, { 1120000 } },
{ },
};
constexpr u32 CpuClkOfficial = 1963'500;
constexpr u32 CpuVoltOfficial = 1120;
constexpr u32 CpuVminOfficial = 620;
static const u32 cpuVoltagePatchValues[] = { 850, 38, 1120, 1000, 100, 1000, 0 };
static const s32 cpuVoltagePatchOffsets[] = { -2, -1, 5, 6, 7, 8, 9 };
static_assert(sizeof(cpuVoltagePatchValues) == sizeof(cpuVoltagePatchOffsets), "Invalid cpuVoltagePatch size");
static const u32 cpuVoltThermalData[] = { 620, 1120, 20000, 620, 1120, 70000, 950, 1132, 0, 950, 1227, 0 };
static const u32 allowedCpuMaxFrequencies[] = { 1'963'000, 2'091'000, 2'193'000, 2'295'000, 2'397'000, 2'499'000, 2'601'000, 2'703'000, };
constexpr cvb_entry_t GpuCvbTableDefault[] = {
// GPUB01_NA_CVB_TABLE
{ 76800, {}, { 610000, } },
{ 153600, {}, { 610000, } },
{ 230400, {}, { 610000, } },
{ 307200, {}, { 610000, } },
{ 384000, {}, { 610000, } },
{ 460800, {}, { 610000, } },
{ 537600, {}, { 801688, -10900, -163, 298, -10599, 162, } },
{ 614400, {}, { 824214, -5743, -452, 238, -6325, 81, } },
{ 691200, {}, { 848830, -3903, -552, 119, -4030, -2, } },
{ 768000, {}, { 891575, -4409, -584, 0, -2849, 39, } },
{ 844800, {}, { 940071, -5367, -602, -60, -63, -93, } },
{ 921600, {}, { 986765, -6637, -614, -179, 1905, -13, } },
{ 998400, {}, { 1098475, -13529, -497, -179, 3626, 9, } },
{ 1075200, {}, { 1163644, -12688, -648, 0, 1077, 40, } },
{ 1152000, {}, { 1204812, -9908, -830, 0, 1469, 110, } },
{ 1228800, {}, { 1277303, -11675, -859, 0, 3722, 313, } },
{ 1267200, {}, { 1335531, -12567, -867, 0, 3681, 559, } },
{ },
};
constexpr u32 GpuClkPllMax = 1300'000'000;
constexpr u32 GpuClkPllLimit = 2'600'000;
constexpr u32 GpuVminOfficial = 610;
static const u32 gpuDVFSPattern[] = { 1050, 1000, 100, 1000, 10, };
static const u32 gpuVoltThermalPattern[] = { 800, 1120, 0, 610, 1120, 20000, 610, 1120, 30000, 610, 1120, 50000, 610, 1120, 70000, 610, 1120, 90000, };
static_assert(sizeof(gpuVoltThermalPattern) == 72, "Invalid gpuVoltThermalPattern");
struct SpeedoVminTable {
u32 speedo;
u32 voltage;
};
struct RamVminOffsetTable {
u32 maxClock;
u32 offset;
};
static const SpeedoVminTable vminTable[] {
{1400, 610}, // LOW SPEEDO -> use stock vmin
{1560, 590},
{1583, 570},
{1620, 565},
{1670, 560},
{1694, 555},
{1731, 550},
{1750, 540},
{0xFFFFFFFF, 530},
};
static const RamVminOffsetTable ramOffset[] {
{2400000, 5},
{2533000, 10},
{2666000, 15},
{2800000, 20},
{2933000, 25},
{3200000, 30},
{0xFFFFFFFF, 35},
};
/* GPU Max Clock asm Pattern:
*
* MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1
*
* MOVK W11, #0xE, LSL#16 <shift>16 0xE 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1
*/
inline constexpr u32 asm_pattern[] = {0x52820000, 0x72A001C0};
inline auto asm_compare_no_rd = [](u32 ins1, u32 ins2) {
return ((ins1 ^ ins2) >> 5) == 0;
};
inline auto asm_get_rd = [](u32 ins) {
return ins & ((1 << 5) - 1);
};
inline auto asm_set_rd = [](u32 ins, u8 rd) {
return (ins & 0xFFFFFFE0) | (rd & 0x1F);
};
inline auto asm_set_imm16 = [](u32 ins, u16 imm) {
return (ins & 0xFFE0001F) | ((imm & 0xFFFF) << 5);
};
inline bool GpuMaxClockPatternFn(u32 *ptr32) {
return asm_compare_no_rd(*ptr32, asm_pattern[0]);
}
constexpr emc_dvb_dvfs_table_t EmcDvbTableDefault[] = {
{ 204000, { 637, 637, 637, } },
{ 408000, { 637, 637, 637, } },
{ 800000, { 637, 637, 637, } },
{ 1065600, { 637, 637, 637, } },
{ 1331200, { 650, 637, 637, } },
{ 1600000, { 675, 650, 637, } },
};
constexpr u32 EmcClkOSAlt = 1331'200;
constexpr u32 EmcClkPllmLimit = 2133'000'000;
constexpr u32 EmcVddqDefault = 600'000;
constexpr u32 MemVdd2Default = 1100'000;
constexpr u32 MTC_TABLE_REV = 3;
void Patch(uintptr_t mapped_nso, size_t nso_size);
}
namespace erista {
static u32 maxEmcClocks[] = { C.eristaEmcMaxClock2, C.eristaEmcMaxClock1, C.eristaEmcMaxClock, };
#define GET_MAX_OF_ARR(ARR) (*std::max_element(ARR, ARR + std::size(ARR)))
constexpr cvb_entry_t CpuCvbTableDefault[] = {
// CPU_PLL_CVB_TABLE_ODN
{ 204000, {721094}, { } },
{ 306000, {754040}, { } },
{ 408000, {786986}, { } },
{ 510000, {819932}, { } },
{ 612000, {852878}, { } },
{ 714000, {885824}, { } },
{ 816000, {918770}, { } },
{ 918000, {951716}, { } },
{ 1020000, {984662}, { -2875621, 358099, -8585} },
{ 1122000, {1017608}, { -52225, 104159, -2816} },
{ 1224000, {1050554}, { 1076868, 8356, -727} },
{ 1326000, {1083500}, { 2208191, -84659, 1240} },
{ 1428000, {1116446}, { 2519460, -105063, 1611} },
{ 1581000, {1130000}, { 2889664, -122173, 1834} },
{ 1683000, {1168000}, { 5100873, -279186, 4747} },
{ 1785000, {1227500}, { 5100873, -279186, 4747} },
{ },
};
constexpr u32 CpuVoltOfficial = 1227;
constexpr u32 CpuVminOfficial = 825;
constexpr u32 CpuVoltL4T = 1257'000;
static const u32 cpuVoltDvfsPattern[] = { 1227, 1000, 100, 1000, 0 };
static_assert(sizeof(cpuVoltDvfsPattern) == 0x14, "invalid cpuVoltDvfsPattern size");
static const u32 cpuVoltageThermalPattern[] = { 950, 1132, 0, 950, 1227, 0, 825, 1227, 15000, 825, 1170, 60000, 825, 1132, 80000 };
static_assert(sizeof(cpuVoltageThermalPattern) == 0x3c, "invalid cpuVoltageThermalPattern size");
constexpr u32 GpuClkPllLimit = 2'600'000;
constexpr u32 GpuClkPllMax = 921'600'000;
constexpr u32 GpuVminOfficial = 810;
constexpr u16 CpuMinVolts[] = { 950, 850, 825, 810 };
inline bool CpuMaxVoltPatternFn(u32* ptr32) {
u32 val = *ptr32;
return (val == 1132 || val == 1170 || val == 1227);
}
static const u32 gpuVoltDvfsPattern[] = { 810, 1150, 1000, 100, 1000, 10, };
static_assert(sizeof(gpuVoltDvfsPattern) == (sizeof(u32) * 6), "Invalid gpuVoltDvfsPattern");
static const u32 gpuVoltThermalPattern[] = { 950, 1132, 0, 810, 1132, 15000, 810, 1132, 30000, 810, 1132, 50000, 810, 1132, 70000, 810, 1132, 105000 };
static_assert(sizeof(gpuVoltThermalPattern) == 0x48, "invalid gpuVoltageThermalPattern size");
/* GPU Max Clock asm Pattern:
*
* MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1
*
* MOVK W11, #0xE, LSL#16 <shift>16 0xE 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1
*/
inline constexpr u32 asm_pattern[] = {
0x52820000, 0x72A001C0
};
inline auto asm_compare_no_rd = [](u32 ins1, u32 ins2) {
return ((ins1 ^ ins2) >> 5) == 0;
};
inline auto asm_get_rd = [](u32 ins) {
return ins & ((1 << 5) - 1);
};
inline auto asm_set_rd = [](u32 ins, u8 rd) {
return (ins & 0xFFFFFFE0) | (rd & 0x1F);
};
inline auto asm_set_imm16 = [](u32 ins, u16 imm) {
return (ins & 0xFFE0001F) | ((imm & 0xFFFF) << 5);
};
inline bool GpuMaxClockPatternFn(u32 *ptr32) {
return asm_compare_no_rd(*ptr32, asm_pattern[0]);
};
constexpr cvb_entry_t GpuCvbTableDefault[] = {
// NA_FREQ_CVB_TABLE
{ 76800, {}, { 814294, 8144, -940, 808, -21583, 226, } },
{ 153600, {}, { 856185, 8144, -940, 808, -21583, 226, } },
{ 230400, {}, { 898077, 8144, -940, 808, -21583, 226, } },
{ 307200, {}, { 939968, 8144, -940, 808, -21583, 226, } },
{ 384000, {}, { 981860, 8144, -940, 808, -21583, 226, } },
{ 460800, {}, { 1023751, 8144, -940, 808, -21583, 226, } },
{ 537600, {}, { 1065642, 8144, -940, 808, -21583, 226, } },
{ 614400, {}, { 1107534, 8144, -940, 808, -21583, 226, } },
{ 691200, {}, { 1149425, 8144, -940, 808, -21583, 226, } },
{ 768000, {}, { 1191317, 8144, -940, 808, -21583, 226, } },
{ 844800, {}, { 1233208, 8144, -940, 808, -21583, 226, } },
{ 921600, {}, { 1275100, 8144, -940, 808, -21583, 226, } },
{ },
};
constexpr u32 MemVoltHOS = 1125'000;
constexpr u32 EmcClkMinFreq = 40800; /* 40.8 MHz table only exists on erista. */
constexpr u32 EmcClkPllmLimit = 1866'000'000;
constexpr u32 MTC_TABLE_REV = 7;
void Patch(uintptr_t mapped_nso, size_t nso_size);
}
inline auto MatchesPattern = [](u32 *base, const auto &offsets, const auto &values) {
for (size_t i = 0; i < std::size(values); ++i) {
if (*(base + offsets[i]) != values[i]) {

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::ldr::hoc::pcv {
constexpr u32 NopIns = 0x1f2003d5;
inline auto asm_compare_no_rd = [](u32 ins1, u32 ins2) {
return ((ins1 ^ ins2) >> 5) == 0;
};
inline auto asm_get_rd = [](u32 ins) {
return ins & ((1 << 5) - 1);
};
inline auto asm_set_rd = [](u32 ins, u8 rd) {
return (ins & 0xFFFFFFE0) | (rd & 0x1F);
};
inline auto asm_set_imm16 = [](u32 ins, u16 imm) {
return (ins & 0xFFE0001F) | ((imm & 0xFFFF) << 5);
};
inline auto AsmGetImm16 = [](u32 ins) {
return static_cast<u16>((ins >> 5) & 0xFFFF);
};
inline auto AsmCompareBrNoRd = [](u32 ins1, u32 ins2) {
constexpr u32 RegMask = ~(((1 << 5) - 1) << 5);
return ((ins1 & RegMask) ^ (ins2 & RegMask)) == 0;
};
inline auto AsmCompareAddNoImm12 = [](u32 ins1, u32 ins2) {
constexpr u32 Imm12Mask = ~(((1 << 12) - 1) << 10);
return ((ins1 & Imm12Mask) ^ (ins2 & Imm12Mask)) == 0;
};
inline auto AsmCompareAdrpNoImm = [](u32 ins1, u32 ins2) {
constexpr u32 ImmMask = ~((((1 << 2) - 1) << 29) | (((1 << 19) - 1) << 5));
return ((ins1 & ImmMask) ^ (ins2 & ImmMask)) == 0;
};
}

View File

@@ -46,11 +46,6 @@ namespace ams::ldr::hoc::pcv {
unsigned long dvco_calibration_max;
} cvb_cpu_dfll_data;
typedef struct emc_dvb_dvfs_table_t {
u64 freq;
s32 volt[4] = {0};
} emc_dvb_dvfs_table_t;
typedef struct __attribute__((packed)) div_nmp {
u8 divn_shift;
u8 divn_width;
@@ -119,7 +114,7 @@ namespace ams::ldr::hoc::pcv {
static_assert(sizeof(regulator) == 0x120);
constexpr u32 CpuClkOSLimit = 1785'000;
constexpr u32 GpuClkOsLimit = 921'600;
constexpr u32 EmcClkOSLimit = 1600'000;
#define R_SKIP() R_SUCCEED()

View File

@@ -134,7 +134,7 @@ namespace ams::ldr::hoc::pcv::erista {
Result GpuFreqMaxAsm(u32 *ptr32) {
// Check if both two instructions match the pattern
u32 ins1 = *ptr32, ins2 = *(ptr32 + 1);
if (!(asm_compare_no_rd(ins1, asm_pattern[0]) && asm_compare_no_rd(ins2, asm_pattern[1])))
if (!(asm_compare_no_rd(ins1, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[1])))
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
// Both instructions should operate on the same register
@@ -142,6 +142,12 @@ namespace ams::ldr::hoc::pcv::erista {
if (rd != asm_get_rd(ins2))
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
/* Verify the limit. */
/* TODO: Make this a little bit cleaner at some point. */
if (AsmGetImm16(ins1) != (GpuClkOsLimit & 0xFFFF) || AsmGetImm16(ins2) != (GpuClkOsLimit >> 16)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
u32 max_clock;
switch (C.eristaGpuUV) {
case 0:
@@ -158,8 +164,8 @@ namespace ams::ldr::hoc::pcv::erista {
break;
}
u32 asm_patch[2] = {
asm_set_rd(asm_set_imm16(asm_pattern[0], max_clock), rd),
asm_set_rd(asm_set_imm16(asm_pattern[1], max_clock >> 16), rd)};
asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)};
PATCH_OFFSET(ptr32, asm_patch[0]);
PATCH_OFFSET(ptr32 + 1, asm_patch[1]);

View File

@@ -0,0 +1,151 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../oc_common.hpp"
#include "pcv_common.hpp"
#include "pcv_asm.hpp"
namespace ams::ldr::hoc::pcv::erista {
static u32 maxEmcClocks[] = { C.eristaEmcMaxClock2, C.eristaEmcMaxClock1, C.eristaEmcMaxClock, };
#define GET_MAX_OF_ARR(ARR) (*std::max_element(ARR, ARR + std::size(ARR)))
constexpr cvb_entry_t CpuCvbTableDefault[] = {
// CPU_PLL_CVB_TABLE_ODN
{ 204000, {721094}, { } },
{ 306000, {754040}, { } },
{ 408000, {786986}, { } },
{ 510000, {819932}, { } },
{ 612000, {852878}, { } },
{ 714000, {885824}, { } },
{ 816000, {918770}, { } },
{ 918000, {951716}, { } },
{ 1020000, {984662}, { -2875621, 358099, -8585} },
{ 1122000, {1017608}, { -52225, 104159, -2816} },
{ 1224000, {1050554}, { 1076868, 8356, -727} },
{ 1326000, {1083500}, { 2208191, -84659, 1240} },
{ 1428000, {1116446}, { 2519460, -105063, 1611} },
{ 1581000, {1130000}, { 2889664, -122173, 1834} },
{ 1683000, {1168000}, { 5100873, -279186, 4747} },
{ 1785000, {1227500}, { 5100873, -279186, 4747} },
{ },
};
constexpr u32 CpuVoltOfficial = 1227;
constexpr u32 CpuVminOfficial = 825;
constexpr u32 CpuVoltL4T = 1257'000;
static const u32 cpuVoltDvfsPattern[] = { 1227, 1000, 100, 1000, 0 };
static_assert(sizeof(cpuVoltDvfsPattern) == 0x14, "invalid cpuVoltDvfsPattern size");
static const u32 cpuVoltageThermalPattern[] = { 950, 1132, 0, 950, 1227, 0, 825, 1227, 15000, 825, 1170, 60000, 825, 1132, 80000 };
static_assert(sizeof(cpuVoltageThermalPattern) == 0x3c, "invalid cpuVoltageThermalPattern size");
constexpr u32 GpuClkPllLimit = 2'600'000;
constexpr u32 GpuClkPllMax = 921'600'000;
constexpr u32 GpuVminOfficial = 810;
constexpr u16 CpuMinVolts[] = { 950, 850, 825, 810 };
static const u32 gpuVoltDvfsPattern[] = { 810, 1150, 1000, 100, 1000, 10, };
static_assert(sizeof(gpuVoltDvfsPattern) == (sizeof(u32) * 6), "Invalid gpuVoltDvfsPattern");
static const u32 gpuVoltThermalPattern[] = { 950, 1132, 0, 810, 1132, 15000, 810, 1132, 30000, 810, 1132, 50000, 810, 1132, 70000, 810, 1132, 105000 };
static_assert(sizeof(gpuVoltThermalPattern) == 0x48, "invalid gpuVoltageThermalPattern size");
/* GPU Max Clock asm Pattern:
*
* MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1
*
* MOVK W11, #0xE, LSL#16 <shift>16 0xE 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1
*/
inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 };
inline bool GpuMaxClockPatternFn(u32 *ptr32) {
return asm_compare_no_rd(*ptr32, GpuAsmPattern[0]);
};
constexpr cvb_entry_t GpuCvbTableDefault[] = {
// NA_FREQ_CVB_TABLE
{ 76800, {}, { 814294, 8144, -940, 808, -21583, 226, } },
{ 153600, {}, { 856185, 8144, -940, 808, -21583, 226, } },
{ 230400, {}, { 898077, 8144, -940, 808, -21583, 226, } },
{ 307200, {}, { 939968, 8144, -940, 808, -21583, 226, } },
{ 384000, {}, { 981860, 8144, -940, 808, -21583, 226, } },
{ 460800, {}, { 1023751, 8144, -940, 808, -21583, 226, } },
{ 537600, {}, { 1065642, 8144, -940, 808, -21583, 226, } },
{ 614400, {}, { 1107534, 8144, -940, 808, -21583, 226, } },
{ 691200, {}, { 1149425, 8144, -940, 808, -21583, 226, } },
{ 768000, {}, { 1191317, 8144, -940, 808, -21583, 226, } },
{ 844800, {}, { 1233208, 8144, -940, 808, -21583, 226, } },
{ 921600, {}, { 1275100, 8144, -940, 808, -21583, 226, } },
{ },
};
constexpr u32 MemVoltHOS = 1125'000;
constexpr u32 EmcClkMinFreq = 40800; /* 40.8 MHz table only exists on erista. */
constexpr u32 EmcClkPllmLimit = 1866'000'000;
constexpr u32 MTC_TABLE_REV = 7;
constexpr u32 MtcTableCountDefault = 10;
constexpr size_t MtcFullTableSize = sizeof(EristaMtcTable) * MtcTableCountDefault;
constexpr u32 MtcFullTableCount = 3;
/* These dramids were copied from Hekate -- see /bdk/mem/sdram.h */
enum DramId {
ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH = 0,
ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE = 1,
ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC = 2, /* This doesn't have a table in pcv? Wtf? */
ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH = 4,
ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX = 7, /* No table, but expected */
};
enum MtcTableIndex {
T210SdevEmcDvfsTableS4gb01 = 0, /* HB-MGCH */
T210SdevEmcDvfsTableS6gb01 = 1, /* HM-MGCH */
T210SdevEmcDvfsTableH4gb01 = 2, /* HR-NLE */
MtcTableIndex_Invalid = 3,
};
struct MtcDramIndex {
DramId dramId;
MtcTableIndex index;
};
constexpr MtcDramIndex mtcIndexTable[] = {
{ ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH, T210SdevEmcDvfsTableS4gb01, },
{ ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH, T210SdevEmcDvfsTableS6gb01, },
{ ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE, T210SdevEmcDvfsTableH4gb01, },
};
void Patch(uintptr_t mapped_nso, size_t nso_size);
}

View File

@@ -18,52 +18,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <vector>
#include "pcv.hpp"
#include "../mtc_timing_value.hpp"
#include "../mariko/calculate_timings.hpp"
namespace ams::ldr::hoc::pcv::mariko {
u32 GetGpuVminVoltage() {
for (auto e : vminTable) {
if (C.gpuSpeedo <= e.speedo) {
return e.voltage;
}
}
return 530;
}
u32 GetRamVminAdjustment(u32 vmin) {
if (C.marikoEmcMaxClock < 2133000) {
return vmin;
}
const u32 ramScale = (((C.marikoEmcMaxClock / 1000) - 2133) / 33) * 5 + vmin;
for (auto r : ramOffset) {
if (C.marikoEmcMaxClock < r.maxClock) {
return ramScale + r.offset;
}
}
return ramScale;
}
/* Note: EOS (probably?) has a bug in this function that always results in high vmin, this is fixed here. */
u32 GetAutoVoltage() {
u32 voltage = GetGpuVminVoltage();
voltage = GetRamVminAdjustment(voltage);
u32 voltageOffset = 590 - C.commonGpuVoltOffset;
if (voltageOffset < voltage) {
voltage = voltageOffset;
}
return voltage;
}
Result GpuVoltDVFS(u32 *ptr) {
/* Check for valid pattern. */
for (size_t i = 0; i < std::size(gpuDVFSPattern); ++i) {
@@ -77,15 +38,10 @@ namespace ams::ldr::hoc::pcv::mariko {
PATCH_OFFSET(ptr + 1, C.marikoGpuVmax);
}
/* C.marikoGpuVmin is non zero, user sets manual voltage. */
if (C.marikoGpuVmin) {
PATCH_OFFSET(ptr, C.marikoGpuVmin);
R_SUCCEED();
}
/* C.marikoGpuVmin is zero, auto voltage is applied. */
u32 autoVmin = GetAutoVoltage();
PATCH_OFFSET(ptr, autoVmin);
R_SUCCEED();
}
@@ -94,24 +50,15 @@ namespace ams::ldr::hoc::pcv::mariko {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
u32 vmin = C.marikoGpuVmin;
/* Automatic voltage. */
if (!C.marikoGpuVmin) {
vmin = GetAutoVoltage();
PATCH_OFFSET(ptr, vmin);
PATCH_OFFSET(ptr + 3, vmin);
PATCH_OFFSET(ptr + 6, vmin);
PATCH_OFFSET(ptr + 9, vmin);
} else {
/* Manual voltage. */
PATCH_OFFSET(ptr, vmin);
PATCH_OFFSET(ptr + 3, vmin);
PATCH_OFFSET(ptr + 6, vmin);
PATCH_OFFSET(ptr + 9, vmin);
R_SKIP();
}
PATCH_OFFSET(ptr + 12, vmin);
PATCH_OFFSET(ptr, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 3, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 6, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 9, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 12, C.marikoGpuVmin);
R_SUCCEED();
}
@@ -322,13 +269,21 @@ namespace ams::ldr::hoc::pcv::mariko {
Result GpuFreqMaxAsm(u32 *ptr32) {
// Check if both two instructions match the pattern
u32 ins1 = *ptr32, ins2 = *(ptr32 + 1);
if (!(asm_compare_no_rd(ins1, asm_pattern[0]) && asm_compare_no_rd(ins2, asm_pattern[1])))
if (!(asm_compare_no_rd(ins1, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[1]))) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
// Both instructions should operate on the same register
u8 rd = asm_get_rd(ins1);
if (rd != asm_get_rd(ins2))
if (rd != asm_get_rd(ins2)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
/* Verify the limit. */
/* TODO: Make this a little bit cleaner at some point. */
if (AsmGetImm16(ins1) != (GpuClkOsLimit & 0xFFFF) || AsmGetImm16(ins2) != (GpuClkOsLimit >> 16)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
u32 max_clock;
switch (C.marikoGpuUV) {
@@ -345,9 +300,10 @@ namespace ams::ldr::hoc::pcv::mariko {
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq;
break;
}
u32 asm_patch[2] = {
asm_set_rd(asm_set_imm16(asm_pattern[0], max_clock), rd),
asm_set_rd(asm_set_imm16(asm_pattern[1], max_clock >> 16), rd)
asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)
};
PATCH_OFFSET(ptr32, asm_patch[0]);
@@ -394,6 +350,8 @@ namespace ams::ldr::hoc::pcv::mariko {
TABLE->shadow_regs_ca_train.PARAM = VALUE; \
TABLE->shadow_regs_rdwr_train.PARAM = VALUE;
const double tCK_avg = 1000'000.0 / table->rate_khz;
#define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg))
/* Ram power down */
@@ -417,7 +375,7 @@ namespace ams::ldr::hoc::pcv::mariko {
const u32 dyn_self_ref_control = (static_cast<u32>(7605.0 / tCK_avg) + 260) | (table->burst_regs.emc_dyn_self_ref_control & 0xffff0000);
CalculateTimings();
CalculateTimings(tCK_avg, table->rate_khz);
WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(tRCD));
WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(tRCD));
@@ -444,7 +402,7 @@ namespace ams::ldr::hoc::pcv::mariko {
WRITE_PARAM_ALL_REG(table, emc_twtm, tWTM);
WRITE_PARAM_ALL_REG(table, emc_twatm, tWATM);
WRITE_PARAM_ALL_REG(table, emc_rext, rext);
WRITE_PARAM_ALL_REG(table, emc_wext, (C.marikoEmcMaxClock >= 2533000) ? 0x19 : 0x16);
WRITE_PARAM_ALL_REG(table, emc_wext, (table->rate_khz >= 2533000) ? 0x19 : 0x16);
WRITE_PARAM_ALL_REG(table, emc_refresh, refresh_raw);
WRITE_PARAM_ALL_REG(table, emc_pre_refresh_req_cnt, refresh_raw / 4);
WRITE_PARAM_ALL_REG(table, emc_trefbw, trefbw);
@@ -488,7 +446,7 @@ namespace ams::ldr::hoc::pcv::mariko {
constexpr double MC_ARB_DIV = 4.0;
constexpr u32 MC_ARB_SFA = 2;
table->burst_mc_regs.mc_emem_arb_cfg = C.marikoEmcMaxClock / (33.3 * 1000) / MC_ARB_DIV;
table->burst_mc_regs.mc_emem_arb_cfg = table->rate_khz / (33.3 * 1000) / MC_ARB_DIV;
table->burst_mc_regs.mc_emem_arb_timing_rcd = CEIL(GET_CYCLE_CEIL(tRCD) / MC_ARB_DIV) - 2;
table->burst_mc_regs.mc_emem_arb_timing_rp = CEIL(GET_CYCLE_CEIL(tRPpb) / MC_ARB_DIV) - 1;
table->burst_mc_regs.mc_emem_arb_timing_rc = CEIL(GET_CYCLE_CEIL(tRC) / MC_ARB_DIV) - 1;
@@ -530,7 +488,7 @@ namespace ams::ldr::hoc::pcv::mariko {
table->la_scale_regs.mc_mll_mpcorer_ptsa_rate = 0x115;
if (C.marikoEmcMaxClock >= 2133000) {
if (table->rate_khz >= 2133000) {
table->la_scale_regs.mc_ftop_ptsa_rate = 0x1F;
} else {
table->la_scale_regs.mc_ftop_ptsa_rate = 0x1B;
@@ -539,14 +497,14 @@ namespace ams::ldr::hoc::pcv::mariko {
table->la_scale_regs.mc_ptsa_grant_decrement = 0x17ff;
constexpr u32 MaskHigh = 0xFF00FFFF;
constexpr u32 Mask2 = 0xFFFFFF00;
constexpr u32 Mask3 = 0xFF00FF00;
constexpr u32 Mask2 = 0xFFFFFF00;
constexpr u32 Mask3 = 0xFF00FF00;
const u32 allowance1 = static_cast<u32>(0x32000 / (C.marikoEmcMaxClock / 0x3E8)) & 0xFF;
const u32 allowance2 = static_cast<u32>(0x9C40 / (C.marikoEmcMaxClock / 0x3E8)) & 0xFF;
const u32 allowance3 = static_cast<u32>(0xB540 / (C.marikoEmcMaxClock / 0x3E8)) & 0xFF;
const u32 allowance4 = static_cast<u32>(0x9600 / (C.marikoEmcMaxClock / 0x3E8)) & 0xFF;
const u32 allowance5 = static_cast<u32>(0x8980 / (C.marikoEmcMaxClock / 0x3E8)) & 0xFF;
const u32 allowance1 = static_cast<u32>(0x32000 / (table->rate_khz / 1000)) & 0xFF;
const u32 allowance2 = static_cast<u32>(0x9C40 / (table->rate_khz / 1000)) & 0xFF;
const u32 allowance3 = static_cast<u32>(0xB540 / (table->rate_khz / 1000)) & 0xFF;
const u32 allowance4 = static_cast<u32>(0x9600 / (table->rate_khz / 1000)) & 0xFF;
const u32 allowance5 = static_cast<u32>(0x8980 / (table->rate_khz / 1000)) & 0xFF;
table->la_scale_regs.mc_latency_allowance_xusb_0 = (table->la_scale_regs.mc_latency_allowance_xusb_0 & MaskHigh) | (allowance1 << 16);
table->la_scale_regs.mc_latency_allowance_xusb_1 = (table->la_scale_regs.mc_latency_allowance_xusb_1 & MaskHigh) | (allowance1 << 16);
@@ -570,8 +528,8 @@ namespace ams::ldr::hoc::pcv::mariko {
table->dram_timings.t_rp = tRFCpb;
table->dram_timings.t_rfc = tRFCab;
table->dram_timings.rl = RL;
table->dram_timings.rl = RL;
table->emc_mrw2 = (table->emc_mrw2 & ~0xFFu) | static_cast<u32>(mrw2);
table->emc_cfg_2 = 0x11083D;
}
@@ -580,24 +538,24 @@ namespace ams::ldr::hoc::pcv::mariko {
constexpr u32 PllOscInKHz = 38400;
constexpr u32 PllOscHalfKHz = 19200;
double target_freq_d = static_cast<double>(C.marikoEmcMaxClock);
double target_freq_d = static_cast<double>(table->rate_khz);
s32 divm_candidate_half = static_cast<u8>(C.marikoEmcMaxClock / PllOscHalfKHz);
s32 divm_candidate_half = static_cast<u8>(table->rate_khz / PllOscHalfKHz);
bool remainder_check = (C.marikoEmcMaxClock - PllOscInKHz * (C.marikoEmcMaxClock / PllOscInKHz)) > (C.marikoEmcMaxClock - PllOscHalfKHz * divm_candidate_half) && static_cast<int>(((target_freq_d / PllOscHalfKHz - divm_candidate_half - 0.5) * 8192.0)) != 0;
bool remainder_check = (table->rate_khz - PllOscInKHz * (table->rate_khz / PllOscInKHz)) > (table->rate_khz - PllOscHalfKHz * divm_candidate_half) && static_cast<int>(((target_freq_d / PllOscHalfKHz - divm_candidate_half - 0.5) * 8192.0)) != 0;
u32 divm_final = remainder_check + 1;
table->pllmb_divm = divm_final;
double div_step_d = static_cast<double>(PllOscInKHz) / divm_final;
s32 divn_integer = static_cast<u8>(C.marikoEmcMaxClock / div_step_d);
s32 divn_integer = static_cast<u8>(table->rate_khz / div_step_d);
table->pllmb_divn = divn_integer;
u32 divn_fraction = static_cast<s32>((target_freq_d / div_step_d - divn_integer - 0.5) * 8192.0);
u32 actual_freq_khz = static_cast<u32>((divn_integer + 0.5 + divn_fraction * 0.000122070312) * div_step_d);
if (C.marikoEmcMaxClock - 2366001 < 133999) {
if (table->rate_khz - 2366001 < 133999) {
s32 divn_fraction_ssc = static_cast<s32>((actual_freq_khz * 0.997 / div_step_d - divn_integer - 0.5) * 8192.0);
double delta_scaled = (0.3 / div_step_d + 0.3 / div_step_d) * (divn_fraction - divn_fraction_ssc);
@@ -625,7 +583,7 @@ namespace ams::ldr::hoc::pcv::mariko {
table->pllm_ss_cfg &= 0xBFFFFFFF;
table->pllmb_ss_cfg &= 0xBFFFFFFF;
u64 pair = (static_cast<u64>(divn_fraction) << 32) | static_cast<u64>(C.marikoEmcMaxClock);
u64 pair = (static_cast<u64>(divn_fraction) << 32) | static_cast<u64>(table->rate_khz);
u32 pll_misc = (table->pllm_ss_ctrl2 & 0xFFFF0000) | static_cast<u32>((pair - actual_freq_khz) >> 32);
table->pllm_ss_ctrl2 = pll_misc;
@@ -633,80 +591,225 @@ namespace ams::ldr::hoc::pcv::mariko {
}
}
Result MemFreqMtcTable(u32 *ptr) {
u32 khz_list[] = {1600000, 1331200, 204000};
u32 khz_list_size = sizeof(khz_list) / sizeof(u32);
namespace {
std::vector<u32> newEmcList;
u32 *nsoStart;
}
// Generate list for mtc table pointers
MarikoMtcTable *table_list[khz_list_size];
for (u32 i = 0; i < khz_list_size; i++) {
u8 *table = reinterpret_cast<u8 *>(ptr) - offsetof(MarikoMtcTable, rate_khz) - i * sizeof(MarikoMtcTable);
table_list[i] = reinterpret_cast<MarikoMtcTable *>(table);
R_UNLESS(table_list[i]->rate_khz == khz_list[i], ldr::ResultInvalidMtcTable());
R_UNLESS(table_list[i]->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable());
void MtcGenerateJedecTable() {
const u32 jedecFreqs[] = { 1866000, 1996000, 2133000, 2400000, 2666000, 2933000, 3200000 };
constexpr u32 JedecFreqCount = std::size(jedecFreqs);
for (u32 i = 0; i < JedecFreqCount; ++i) {
if (jedecFreqs[i] <= C.marikoEmcMaxClock) {
newEmcList.push_back(jedecFreqs[i]);
} else {
break;
}
}
if (C.marikoEmcMaxClock <= EmcClkOSLimit)
if (newEmcList.back() != C.marikoEmcMaxClock) {
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
}
newEmcList.resize(std::min(newEmcList.size(), DvfsTableEntryLimit));
}
void MtcGenerateFreqTables() {
if (C.marikoEmcMaxClock <= EmcClkOSLimit) {
return;
}
newEmcList.clear();
newEmcList.reserve(DvfsTableEntryCount);
newEmcList.insert(newEmcList.end(), std::begin(EmcListDefault), std::end(EmcListDefault));
u32 stepRate = 0;
switch (C.stepMode) {
case StepMode_66MHz:
stepRate = 66667;
break;
case StepMode_100MHz:
stepRate = 100000;
break;
case StepMode_Jedec:
MtcGenerateJedecTable();
return;
default:
stepRate = 66667;
break;
}
constexpr u32 RoundHz = 1000;
for (u32 stepIndex = 1;; ++stepIndex) {
u32 newFreq = EmcClkOSLimit + stepIndex * stepRate;
newFreq = (newFreq / RoundHz) * RoundHz;
if (newFreq > C.marikoEmcMaxClock) {
if (newEmcList.back() != C.marikoEmcMaxClock) {
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
}
break;
}
newEmcList.push_back(newFreq);
}
newEmcList.resize(std::min(newEmcList.size(), DvfsTableEntryLimit));
}
Result VerifyMtcTable(MarikoMtcTable *tableStart, u32 expectedFreq) {
R_UNLESS(tableStart->rate_khz == expectedFreq, ldr::ResultInvalidMtcTable());
R_UNLESS(tableStart->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable());
R_SUCCEED();
}
Result MtcValidateAllTables(MarikoMtcTable *tableStart, const u32 *validationList, u32 tableCount) {
for (u32 i = 0; i < tableCount; ++i) {
R_UNLESS(R_SUCCEEDED(VerifyMtcTable(&tableStart[i], validationList[i])), ldr::ResultInvalidMtcTable());
}
R_SUCCEED();
}
DramId GetDramId() {
u64 id64;
splGetConfig(SplConfigItem_DramId, &id64);
return static_cast<DramId>(id64);
}
MtcTableIndex GetMtcDramIndex(DramId dramId) {
for (u32 i = 0; i < std::size(mtcIndexTable); ++i) {
if (mtcIndexTable[i].dramId == dramId) {
return mtcIndexTable[i].index;
}
}
return MtcTableIndex_Invalid;
}
NORETURN void AbortInvalidDramId() {
panic::SmcError(panic::Emc);
CRASH("Invalid dram id\n");
}
u32 GetMtcOffset(MtcTableIndex index) {
if (index < T210b0SdevEmcDvfsTableS4gb03) {
return index * mariko::MtcFullTableSize;
}
/* There are 2 erista mtc tables between T210b0SdevEmcDvfsTableS4gb01 and T210b0SdevEmcDvfsTableS4gb03, so we have to do this adjustment. */
return mariko::MtcFullTableSize * index + (2 * erista::MtcFullTableSize);
}
void PrepareMtcMemoryRegion(u8 *firstTable, MarikoMtcTable *usedTable) {
memmove(firstTable, usedTable, mariko::MtcFullTableSize);
/* Clear all other tables. */
/* 1 erista table is excluded because it's always before firstTable. */
/* We also exclude the used table obviously. */
constexpr size_t RemainingRegionSize = (mariko::MtcFullTableSize) * (mariko::MtcFullTableCount - 1) + (erista::MtcFullTableSize * (erista::MtcFullTableCount - 1));
memset(firstTable + mariko::MtcFullTableSize, 0, RemainingRegionSize);
}
void MtcExtendTables(MarikoMtcTable *table) {
for (u32 i = mariko::MtcTableCountDefault; i < newEmcList.size(); ++i) {
std::memcpy(&table[i], &table[i - 1], sizeof(MarikoMtcTable));
table[i].rate_khz = newEmcList[i];
}
}
bool patchedMtc = false;
Result MemFreqMtcTable(u32 *ptr) {
if (C.marikoEmcMaxClock <= EmcClkOSLimit || patchedMtc) {
R_SKIP();
}
MarikoMtcTable *table_alt = table_list[1], *table_max = table_list[0];
MarikoMtcTable *tmp = new MarikoMtcTable;
static const DramId dramId = [] {
DramId id = GetDramId();
return id;
}();
// Copy unmodified 1600000 table to tmp
std::memcpy(reinterpret_cast<void *>(tmp), reinterpret_cast<void *>(table_max), sizeof(MarikoMtcTable));
static const MtcTableIndex mtcIndex = [] {
MtcTableIndex idx = GetMtcDramIndex(dramId);
/* If for some reason this happens, there is no chance of recovering this. */
if (idx == MtcTableIndex_Invalid) {
AbortInvalidDramId();
}
return idx;
}();
/* Adjust timings properly according to the new frequency. */
MemMtcTableAutoAdjust(table_max);
/* Offset to dram id specific mtc table. */
static const u32 mtcOffset = GetMtcOffset(mtcIndex);
MemMtcPllmbDivisor(table_max);
// Overwrite 13312000 table with unmodified 1600000 table copied back
std::memcpy(reinterpret_cast<void *>(table_alt), reinterpret_cast<void *>(tmp), sizeof(MarikoMtcTable));
/* Offset from 1600MHz pointer to 204Mhz table start. */
constexpr u32 StartAdjustment = offsetof(MarikoMtcTable, rate_khz) + sizeof(MarikoMtcTable) * 2;
u8 *startPtr = reinterpret_cast<u8 *>(ptr) - StartAdjustment;
MarikoMtcTable *table = reinterpret_cast<MarikoMtcTable *>(startPtr + mtcOffset);
R_UNLESS(R_SUCCEEDED(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault)), ldr::ResultInvalidMtcTable());
delete tmp;
PrepareMtcMemoryRegion(startPtr, table);
table = reinterpret_cast<MarikoMtcTable *>(startPtr);
PATCH_OFFSET(ptr, C.marikoEmcMaxClock);
if (R_FAILED(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault))) {
panic::SmcError(panic::Emc);
}
MtcExtendTables(table);
if (R_FAILED(MtcValidateAllTables(table, newEmcList.data(), newEmcList.size()))) {
panic::SmcError(panic::Emc);
}
for (u32 i = mariko::MtcTableCountDefault; i < newEmcList.size(); ++i) {
MemMtcTableAutoAdjust(&table[i]);
MemMtcPllmbDivisor(&table[i]);
}
patchedMtc = true;
R_SUCCEED();
}
Result MemFreqDvbTable(u32 *ptr) {
emc_dvb_dvfs_table_t *default_end = reinterpret_cast<emc_dvb_dvfs_table_t *>(ptr);
emc_dvb_dvfs_table_t *new_start = default_end + 1;
DvbEntry *default_end = reinterpret_cast<DvbEntry *>(ptr);
DvbEntry *new_start = default_end + 1;
// Validate existing table
void *mem_dvb_table_head = reinterpret_cast<u8 *>(new_start) - sizeof(EmcDvbTableDefault);
bool validated = std::memcmp(mem_dvb_table_head, EmcDvbTableDefault, sizeof(EmcDvbTableDefault)) == 0;
bool validated = std::memcmp(mem_dvb_table_head, EmcDvbTableDefault, sizeof(EmcDvbTableDefault)) == 0;
R_UNLESS(validated, ldr::ResultInvalidDvbTable());
if (C.marikoEmcMaxClock <= EmcClkOSLimit)
if (C.marikoEmcMaxClock <= EmcClkOSLimit) {
R_SKIP();
int32_t voltAdd = 25 * C.emcDvbShift;
#define DVB_VOLT(zero, one, two) std::min(zero + voltAdd, 1050), std::min(one + voltAdd, 1025), std::min(two + voltAdd, 1000),
/* TODO: More fine tuned values? */
if (C.marikoEmcMaxClock < 1862400) {
std::memcpy(new_start, default_end, sizeof(emc_dvb_dvfs_table_t));
} else if (C.marikoEmcMaxClock < 2131200) {
emc_dvb_dvfs_table_t oc_table = {1862400, {700, 675, 650, }};
std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t));
} else if (C.marikoEmcMaxClock < 2400000) {
emc_dvb_dvfs_table_t oc_table = {2131200, { 725, 700, 675} };
std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t));
} else if (C.marikoEmcMaxClock < 2665600) {
emc_dvb_dvfs_table_t oc_table = {2400000, {DVB_VOLT(750, 725, 700)}};
std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t));
} else if (C.marikoEmcMaxClock < 2931200) {
emc_dvb_dvfs_table_t oc_table = {2665600, {DVB_VOLT(775, 750, 725)}};
std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t));
} else if (C.marikoEmcMaxClock < 3200000) {
emc_dvb_dvfs_table_t oc_table = {2931200, {DVB_VOLT(800, 775, 750)}};
std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t));
} else {
emc_dvb_dvfs_table_t oc_table = {3200000, {DVB_VOLT(800, 800, 775)}};
std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t));
}
new_start->freq = C.marikoEmcMaxClock;
s32 voltAdd = 25 * C.emcDvbShift;
#define DVB_VOLT(zero, one, two) std::min(zero + voltAdd, 1050), std::min(one + voltAdd, 1025), std::min(two + voltAdd, 1000),
DvbEntry emcDvbTableNew[] = {
{ 204000, { 637, 637, 637, } },
{ 1331200, { 650, 637, 637, } },
{ 1600000, { 675, 650, 637, } },
{ 1866000, { DVB_VOLT(700, 675, 650) } },
{ 2133000, { DVB_VOLT(725, 700, 675) } },
{ 2400000, { DVB_VOLT(750, 725, 700) } },
{ 2666000, { DVB_VOLT(775, 750, 725) } },
{ 2933000, { DVB_VOLT(800, 775, 750) } },
{ 3200000, { DVB_VOLT(800, 800, 775) } },
{ 0xFFFFFFFF, { } },
};
u32 j = MtcTableCountDefault;
for (u32 i = MtcTableCountDefault; i < newEmcList.size(); ++i) {
if (newEmcList[i] >= emcDvbTableNew[j].freq && newEmcList[i] < emcDvbTableNew[j + 1].freq) {
emcDvbTableNew[j].freq = newEmcList[i];
++j;
} else {
break;
}
}
std::memset(mem_dvb_table_head, 0, sizeof(EmcDvbTableDefault));
std::memcpy(mem_dvb_table_head, &emcDvbTableNew, sizeof(emcDvbTableNew));
/* Max dvfs entry is 32, but HOS doesn't seem to boot if exact freq doesn't exist in dvb table,
reason why it's like this
*/
@@ -715,8 +818,9 @@ namespace ams::ldr::hoc::pcv::mariko {
}
Result MemFreqMax(u32 *ptr) {
if (C.marikoEmcMaxClock <= EmcClkOSLimit)
if (C.marikoEmcMaxClock <= EmcClkOSLimit) {
R_SKIP();
}
PATCH_OFFSET(ptr, C.marikoEmcMaxClock);
R_SUCCEED();
@@ -730,12 +834,13 @@ namespace ams::ldr::hoc::pcv::mariko {
I2cSession _session;
Result res = i2cOpenSession(&_session, dev);
if (R_FAILED(res))
if (R_FAILED(res)) {
return res;
}
cmd.reg = reg;
cmd.val = val;
res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All);
res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All);
i2csessionClose(&_session);
return res;
}
@@ -747,21 +852,24 @@ namespace ams::ldr::hoc::pcv::mariko {
constexpr u32 uv_min = 250'000;
auto validator = [entry]() {
R_UNLESS(entry->id == 2, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type == 3, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->id == 2, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type == 3, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_2_3.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_2_3.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_2_3.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry());
R_SUCCEED();
};
R_TRY(validator());
u32 emc_uv = C.marikoEmcVddqVolt;
if (!emc_uv)
R_SKIP();
if (emc_uv % uv_step)
if (!emc_uv) {
R_SKIP();
}
if (emc_uv % uv_step) {
emc_uv = (emc_uv + uv_step - 1) / uv_step * uv_step; // rounding
}
PATCH_OFFSET(ptr, emc_uv);
@@ -776,28 +884,61 @@ namespace ams::ldr::hoc::pcv::mariko {
return resultI2C;
}
Result MemMtcTableAsm(u32 *ptr) {
constexpr u32 AddpOffset = 1;
constexpr u32 BrOffset = 12;
constexpr u32 MovOffset = 10;
/* Ensure we don't dereference memory before nso start. */
R_UNLESS(ptr - BrOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
u32 adrp = *(ptr - AddpOffset);
R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), ldr::ResultInvalidMtcTablePattern());
/* We don't check for matching register because both registers must be x0 in order to pass the previous checks. */
/* The correct instructions will always be x0 since the mtcTable pointer is returned. */
/* Pray this does not break. */
u32 br = *(ptr - BrOffset);
R_UNLESS(AsmCompareBrNoRd(br, MtcBrAsm), ldr::ResultInvalidMtcTablePattern());
/* Pray this does not break either. */
u32 mov = *(ptr - MovOffset);
R_UNLESS(asm_compare_no_rd(mov, MtcMovAsm), ldr::ResultInvalidMtcTablePattern());
u8 movRd = asm_get_rd(mov);
u32 movCountPatch = asm_set_rd(asm_set_imm16(MtcMovAsm, newEmcList.size()), movRd);
PATCH_OFFSET(ptr - BrOffset, NopIns);
PATCH_OFFSET(ptr - MovOffset, movCountPatch);
R_SUCCEED();
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
nsoStart = reinterpret_cast<u32 *>(mapped_nso);
MtcGenerateFreqTables();
u32 CpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
u32 GpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
PatcherEntry<u32> patches[] = {
{"CPU Freq Vdd", &CpuFreqVdd, 1, nullptr, CpuClkOSLimit},
{"CPU Freq Table", CpuFreqCvbTable<true>, 1, nullptr, CpuCvbDefaultMaxFreq},
{"CPU Volt DVFS", &CpuVoltDVFS, 1, nullptr, CpuVminOfficial},
{"CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial},
{"CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, 0x0000FFCF},
{"GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial},
{"GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial},
{"GPU Freq Table", GpuFreqCvbTable<true>, 1, nullptr, GpuCvbDefaultMaxFreq},
{"GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn},
{"GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax},
{"GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit},
{"MEM Freq Mtc", &MemFreqMtcTable, 0, nullptr, EmcClkOSLimit},
{"MEM Freq Dvb", &MemFreqDvbTable, 1, nullptr, EmcClkOSLimit},
{"MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit},
{"MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit},
{"MEM Vddq", &EmcVddqVolt, 2, nullptr, EmcVddqDefault},
{"MEM Vdd2", &MemVoltHandler, 2, nullptr, MemVdd2Default},
{ "CPU Freq Vdd", &CpuFreqVdd, 1, nullptr, CpuClkOSLimit },
{ "CPU Freq Table", CpuFreqCvbTable<true>, 1, nullptr, CpuCvbDefaultMaxFreq },
{ "CPU Volt DVFS", &CpuVoltDVFS, 1, nullptr, CpuVminOfficial },
{ "CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial },
{ "CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low },
{ "GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial },
{ "GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial },
{ "GPU Freq Table", GpuFreqCvbTable<true>, 1, nullptr, GpuCvbDefaultMaxFreq },
{ "GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn },
{ "GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax },
{ "GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
{ "MEM Freq Mtc", &MemFreqMtcTable, 0, nullptr, EmcClkOSLimit },
{ "MEM Freq Dvb", &MemFreqDvbTable, 1, nullptr, EmcClkOSLimit },
{ "MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit },
{ "MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit },
{ "MEM Vddq", &EmcVddqVolt, 2, nullptr, EmcVddqDefault },
{ "MEM Vdd2", &MemVoltHandler, 2, nullptr, MemVdd2Default },
{ "Mem Table Asm", &MemMtcTableAsm, 0, &MemMtcGetGetTablePatternFn },
};
for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(MarikoMtcTable); ptr += sizeof(u32)) {

View File

@@ -0,0 +1,285 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../oc_common.hpp"
#include "pcv_common.hpp"
#include "pcv_asm.hpp"
namespace ams::ldr::hoc::pcv::mariko {
constexpr cvb_entry_t CpuCvbTableDefault[] = {
{ 204000, { 721589, -12695, 27 }, { } },
{ 306000, { 747134, -14195, 27 }, { } },
{ 408000, { 776324, -15705, 27 }, { } },
{ 510000, { 809160, -17205, 27 }, { } },
{ 612000, { 845641, -18715, 27 }, { } },
{ 714000, { 885768, -20215, 27 }, { } },
{ 816000, { 929540, -21725, 27 }, { } },
{ 918000, { 976958, -23225, 27 }, { } },
{ 1020000, { 1028021, -24725, 27 }, { 1120000 } },
{ 1122000, { 1082730, -26235, 27 }, { 1120000 } },
{ 1224000, { 1141084, -27735, 27 }, { 1120000 } },
{ 1326000, { 1203084, -29245, 27 }, { 1120000 } },
{ 1428000, { 1268729, -30745, 27 }, { 1120000 } },
{ 1581000, { 1374032, -33005, 27 }, { 1120000 } },
{ 1683000, { 1448791, -34505, 27 }, { 1120000 } },
{ 1785000, { 1527196, -36015, 27 }, { 1120000 } },
{ 1887000, { 1609246, -37515, 27 }, { 1120000 } },
{ 1963500, { 1675751, -38635, 27 }, { 1120000 } },
{ },
};
constexpr u32 CpuClkOfficial = 1963'500;
constexpr u32 CpuVoltOfficial = 1120;
constexpr u32 CpuVminOfficial = 620;
constexpr u32 CpuTune0Low = 0xFFCF;
static const u32 cpuVoltagePatchValues[] = { 850, 38, 1120, 1000, 100, 1000, 0 };
static const s32 cpuVoltagePatchOffsets[] = { -2, -1, 5, 6, 7, 8, 9 };
static_assert(sizeof(cpuVoltagePatchValues) == sizeof(cpuVoltagePatchOffsets), "Invalid cpuVoltagePatch size");
static const u32 cpuVoltThermalData[] = { 620, 1120, 20000, 620, 1120, 70000, 950, 1132, 0, 950, 1227, 0 };
static const u32 allowedCpuMaxFrequencies[] = { 1'963'500, 2'091'000, 2'193'000, 2'295'000, 2'397'000, 2'499'000, 2'601'000, 2'703'000, };
constexpr cvb_entry_t GpuCvbTableDefault[] = {
// GPUB01_NA_CVB_TABLE
{ 76800, {}, { 610000, } },
{ 153600, {}, { 610000, } },
{ 230400, {}, { 610000, } },
{ 307200, {}, { 610000, } },
{ 384000, {}, { 610000, } },
{ 460800, {}, { 610000, } },
{ 537600, {}, { 801688, -10900, -163, 298, -10599, 162, } },
{ 614400, {}, { 824214, -5743, -452, 238, -6325, 81, } },
{ 691200, {}, { 848830, -3903, -552, 119, -4030, -2, } },
{ 768000, {}, { 891575, -4409, -584, 0, -2849, 39, } },
{ 844800, {}, { 940071, -5367, -602, -60, -63, -93, } },
{ 921600, {}, { 986765, -6637, -614, -179, 1905, -13, } },
{ 998400, {}, { 1098475, -13529, -497, -179, 3626, 9, } },
{ 1075200, {}, { 1163644, -12688, -648, 0, 1077, 40, } },
{ 1152000, {}, { 1204812, -9908, -830, 0, 1469, 110, } },
{ 1228800, {}, { 1277303, -11675, -859, 0, 3722, 313, } },
{ 1267200, {}, { 1335531, -12567, -867, 0, 3681, 559, } },
{ },
};
constexpr u32 GpuClkPllMax = 1300'000'000;
constexpr u32 GpuClkPllLimit = 2'600'000;
constexpr u32 GpuVminOfficial = 610;
static const u32 gpuDVFSPattern[] = { 1050, 1000, 100, 1000, 10, };
static const u32 gpuVoltThermalPattern[] = { 800, 1120, 0, 610, 1120, 20000, 610, 1120, 30000, 610, 1120, 50000, 610, 1120, 70000, 610, 1120, 90000, };
static_assert(sizeof(gpuVoltThermalPattern) == 72, "Invalid gpuVoltThermalPattern");
/* GPU Max Clock asm Pattern:
*
* MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1
*
* MOVK W11, #0xE, LSL#16 <shift>16 0xE 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1
*/
inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 };
inline bool GpuMaxClockPatternFn(u32 *ptr32) {
return asm_compare_no_rd(*ptr32, GpuAsmPattern[0]);
}
struct DvbEntry {
u64 freq;
s32 volt[4] = {};
};
constexpr DvbEntry EmcDvbTableDefault[] = {
{ 204000, { 637, 637, 637, } },
{ 408000, { 637, 637, 637, } },
{ 800000, { 637, 637, 637, } },
{ 1065600, { 637, 637, 637, } },
{ 1331200, { 650, 637, 637, } },
{ 1600000, { 675, 650, 637, } },
};
constexpr u32 EmcListDefault[] = { 204000, 1331200, 1600000, };
constexpr u32 EmcListSizeDefault = std::size(EmcListDefault);
constexpr u32 EmcListEndDefault = EmcListSizeDefault - 1;
constexpr u32 EmcRateStep = 33'000;
constexpr u32 EmcRateStepScale = 33'200;
constexpr u32 EmcClkOSAlt = 1331'200;
constexpr u32 EmcClkPllmLimit = 2133'000'000;
constexpr u32 EmcVddqDefault = 600'000;
constexpr u32 MemVdd2Default = 1100'000;
constexpr u32 MTC_TABLE_REV = 3;
constexpr u32 MtcTableCountDefault = 3;
constexpr size_t MtcFullTableSize = sizeof(MarikoMtcTable) * MtcTableCountDefault;
constexpr u32 MtcFullTableCount = 17;
/* These dramids were copied from Hekate -- see /bdk/mem/sdram.h */
enum DramId : u64 {
HOAG_4GB_HYNIX_H9HCNNNBKMMLXR_NEE = 3,
AULA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE = 5,
IOWA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE = 6,
IOWA_4GB_SAMSUNG_K4U6E3S4AM_MGCJ = 8,
IOWA_8GB_SAMSUNG_K4UBE3D4AM_MGCJ = 9,
IOWA_4GB_HYNIX_H9HCNNNBKMMLHR_NME = 10,
IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTE = 11,
HOAG_4GB_SAMSUNG_K4U6E3S4AM_MGCJ = 12,
HOAG_8GB_SAMSUNG_K4UBE3D4AM_MGCJ = 13,
HOAG_4GB_HYNIX_H9HCNNNBKMMLHR_NME = 14,
HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTE = 15,
IOWA_4GB_SAMSUNG_K4U6E3S4AA_MGCL = 17,
IOWA_8GB_SAMSUNG_K4UBE3D4AA_MGCL = 18,
HOAG_4GB_SAMSUNG_K4U6E3S4AA_MGCL = 19,
IOWA_4GB_SAMSUNG_K4U6E3S4AB_MGCL = 20,
HOAG_4GB_SAMSUNG_K4U6E3S4AB_MGCL = 21,
AULA_4GB_SAMSUNG_K4U6E3S4AB_MGCL = 22,
HOAG_8GB_SAMSUNG_K4UBE3D4AA_MGCL = 23,
AULA_4GB_SAMSUNG_K4U6E3S4AA_MGCL = 24,
IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTF = 25,
HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTF = 26,
AULA_4GB_MICRON_MT53E512M32D2NP_046_WTF = 27,
AULA_8GB_SAMSUNG_K4UBE3D4AA_MGCL = 28,
IOWA_4GB_HYNIX_H54G46CYRBX267 = 29,
HOAG_4GB_HYNIX_H54G46CYRBX267 = 30,
AULA_4GB_HYNIX_H54G46CYRBX267 = 31,
IOWA_4GB_MICRON_MT53E512M32D1NP_046_WTB = 32,
HOAG_4GB_MICRON_MT53E512M32D1NP_046_WTB = 33,
AULA_4GB_MICRON_MT53E512M32D1NP_046_WTB = 34,
};
enum MtcTableIndex {
T210b0SdevEmcDvfsTableS4gb01 = 0, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableS4gb03 = 1, /* Samsung AM-MGCJ 4Gb */
T210b0SdevEmcDvfsTableS8gb03 = 2, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableH4gb03 = 3, /* Hynix NME 4Gb */
T210b0SdevEmcDvfsTableM4gb03 = 4, /* Micron WT:F 4Gb */
T210b0SdevEmcDvfsTableS4gbY01 = 5, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableS1y4gbY01 = 6, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableS1y8gbY01 = 7, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableS1y4gbX03 = 8, /* Samsung AA-MGCL 4Gb */
T210b0SdevEmcDvfsTableS1y8gbX03 = 9, /* Samsung AA-MGCL 8Gb */
T210b0SdevEmcDvfsTableS1y4gb01 = 10, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableM1y4gb01 = 11, /* Micron WT:E 4Gb */
T210b0SdevEmcDvfsTableH1y4gb01 = 12, /* Hynix NEE 4Gb */
T210b0SdevEmcDvfsTableS1y8gb04 = 13, /* Samsung AM-MGCJ 8Gb */
T210b0SdevEmcDvfsTableS1z4gb01 = 14, /* Samsung AB-MGCL 4Gb */
T210b0SdevEmcDvfsTableH1a4gb01 = 15, /* Hynix x267 4Gb */
T210b0SdevEmcDvfsTableM1a4gb01 = 16, /* Micron WT:B 8Gb */
MtcTableIndex_Invalid = 17,
};
struct MtcDramIndex {
DramId dramId;
MtcTableIndex index;
};
constexpr MtcDramIndex mtcIndexTable[] = {
{ HOAG_4GB_HYNIX_H9HCNNNBKMMLXR_NEE, T210b0SdevEmcDvfsTableH1y4gb01, },
{ AULA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE, T210b0SdevEmcDvfsTableH1y4gb01, },
{ IOWA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE, T210b0SdevEmcDvfsTableH1y4gb01, },
{ IOWA_4GB_SAMSUNG_K4U6E3S4AM_MGCJ, T210b0SdevEmcDvfsTableS4gb03, },
{ IOWA_8GB_SAMSUNG_K4UBE3D4AM_MGCJ, T210b0SdevEmcDvfsTableS1y8gb04, },
{ IOWA_4GB_HYNIX_H9HCNNNBKMMLHR_NME, T210b0SdevEmcDvfsTableH4gb03, },
{ IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTE, T210b0SdevEmcDvfsTableM1y4gb01, },
{ HOAG_4GB_SAMSUNG_K4U6E3S4AM_MGCJ, T210b0SdevEmcDvfsTableS4gb03, },
{ HOAG_8GB_SAMSUNG_K4UBE3D4AM_MGCJ, T210b0SdevEmcDvfsTableS1y8gb04, },
{ HOAG_4GB_HYNIX_H9HCNNNBKMMLHR_NME, T210b0SdevEmcDvfsTableH4gb03, },
{ HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTE, T210b0SdevEmcDvfsTableM1y4gb01, },
{ IOWA_4GB_SAMSUNG_K4U6E3S4AA_MGCL, T210b0SdevEmcDvfsTableS1y4gbX03, },
{ IOWA_8GB_SAMSUNG_K4UBE3D4AA_MGCL, T210b0SdevEmcDvfsTableS1y8gbX03, },
{ HOAG_4GB_SAMSUNG_K4U6E3S4AA_MGCL, T210b0SdevEmcDvfsTableS1y4gbX03, },
{ IOWA_4GB_SAMSUNG_K4U6E3S4AB_MGCL, T210b0SdevEmcDvfsTableS1z4gb01, },
{ HOAG_4GB_SAMSUNG_K4U6E3S4AB_MGCL, T210b0SdevEmcDvfsTableS1y8gb04, },
{ AULA_4GB_SAMSUNG_K4U6E3S4AB_MGCL, T210b0SdevEmcDvfsTableS1y8gb04, },
{ HOAG_8GB_SAMSUNG_K4UBE3D4AA_MGCL, T210b0SdevEmcDvfsTableS1y8gbX03, },
{ AULA_4GB_SAMSUNG_K4U6E3S4AA_MGCL, T210b0SdevEmcDvfsTableS1y4gbX03, },
{ IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTF, T210b0SdevEmcDvfsTableM4gb03, },
{ HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTF, T210b0SdevEmcDvfsTableM4gb03, },
{ AULA_4GB_MICRON_MT53E512M32D2NP_046_WTF, T210b0SdevEmcDvfsTableM4gb03, },
{ AULA_8GB_SAMSUNG_K4UBE3D4AA_MGCL, T210b0SdevEmcDvfsTableS1y8gbX03, },
{ IOWA_4GB_HYNIX_H54G46CYRBX267, T210b0SdevEmcDvfsTableH1a4gb01, },
{ HOAG_4GB_HYNIX_H54G46CYRBX267, T210b0SdevEmcDvfsTableH1a4gb01, },
{ AULA_4GB_HYNIX_H54G46CYRBX267, T210b0SdevEmcDvfsTableH1a4gb01, },
{ IOWA_4GB_MICRON_MT53E512M32D1NP_046_WTB, T210b0SdevEmcDvfsTableM1a4gb01, },
{ HOAG_4GB_MICRON_MT53E512M32D1NP_046_WTB, T210b0SdevEmcDvfsTableM1a4gb01, },
{ AULA_4GB_MICRON_MT53E512M32D1NP_046_WTB, T210b0SdevEmcDvfsTableM1a4gb01, },
};
/*
710006abfc 40 01 1f d6 br x10
*/
/*
710006ac28 a0 03 00 90 adrp x0,0x71000de000
710006ac2c 00 80 16 91 add x0=>SdevEmcDvfsTableS4gb01,x0,#0x5a0
*/
/* Br */
/*
| Z | OP | Fixed | A | M | RN | RM
31 30 29 28 27 26 25 | 24 23 | 22 | 21 20 19 18 17 16 15 14 13 12 |11 | 10 | 9 8 7 6 5 | 4 3 2 1 0
1 1 0 1 0 1 1 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 Rn 0 0 0 0 0
Z op A M Rm
*/
/* Adrp */
/*
OP | ImmLow | ImmHigh | RD
31 | 30 29 28 27 26 25 24 | 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 | 4 3 2 1 0
*/
/* ADD (immediate) */
/*
SF | OP | S | Fixed value | Sh | Imm12 | RN | RD
31 | 30 | 29 | 28 27 26 25 24 23 | 22 | 21 20 19 18 17 16 15 14 13 12 11 10 | 9 8 7 6 5 | 4 3 2 1 0
*/
constexpr u32 MtcBrAsm = 0xD61F0140;
constexpr u32 MtcMovAsm = 0x52800068;
constexpr u32 MtcAdrpAsm = 0x900003A0;
constexpr u32 MtcAddAsm = 0x91168000;
ALWAYS_INLINE bool MemMtcGetGetTablePatternFn(u32 *ptr) {
/* This builds an address that gets returned, so the register must be x0 by convention. */
return AsmCompareAddNoImm12(*ptr, MtcAddAsm);
}
void Patch(uintptr_t mapped_nso, size_t nso_size);
}

View File

@@ -0,0 +1,2 @@
-CSn
/home/sould/Documents/GitHub/Horizon-OC/Source/Horizon-OC-Monitor/Horizon-OC-Monitor.elf

View File

@@ -38,11 +38,11 @@ include $(DEVKITPRO)/libnx/switch_rules
# NACP building is skipped as well.
#---------------------------------------------------------------------------------
APP_TITLE := Horizon OC Monitor
APP_VERSION := 1.3.2+r4-hoc-r2
APP_VERSION := 1.3.2+r4-hoc-r4
TARGET := $(notdir $(CURDIR))
BUILD := build
SOURCES := source
INCLUDES := include lib/Atmosphere-libs/libstratosphere/source/dmnt lib/Atmosphere-libs/libstratosphere/source ../sys-clk/common/include/
INCLUDES := include lib/Atmosphere-libs/libstratosphere/source/dmnt lib/Atmosphere-libs/libstratosphere/source ../hoc-clk/common/include/
NO_ICON := 1
#ROMFS := romfs
@@ -65,6 +65,7 @@ IS_STATUS_MONITOR_DIRECTIVE := 1
CFLAGS += -DIS_STATUS_MONITOR_DIRECTIVE=$(IS_STATUS_MONITOR_DIRECTIVE)
# Enable appearance overriding
export MSYS2_ARG_CONV_EXCL := -DUI_OVERRIDE_PATH
UI_OVERRIDE_PATH := /config/status-monitor/
CFLAGS += -DUI_OVERRIDE_PATH="\"$(UI_OVERRIDE_PATH)\""

View File

@@ -0,0 +1 @@
Thanks to NaGa for Status Monitor Pro!

File diff suppressed because it is too large Load Diff

View File

@@ -129,10 +129,8 @@ public:
//}
//tsl::elm::g_disableMenuCacheOnReturn.store(true, std::memory_order_release);
tsl::elm::HeaderOverlayFrame* rootFrame = new tsl::elm::HeaderOverlayFrame("Horizon OC Monitor", "Modes");
if (!lastSelectedItem.empty()) {
if (!lastSelectedItem.empty())
list->jumpToItem(lastSelectedItem);
}
lastSelectedItem = "Other";
rootFrame->setContent(list);
@@ -158,7 +156,7 @@ public:
}
if (keysDown & KEY_B) {
lastSelectedItem = "Other";
tsl::swapTo<MainMenu>();
triggerRumbleDoubleClick.store(true, std::memory_order_release);
triggerExitSound.store(true, std::memory_order_release);
@@ -369,11 +367,8 @@ public:
});
list->addItem(Other);
if (!lastSelectedItem.empty()) {
if (!lastSelectedItem.empty())
list->jumpToItem(lastSelectedItem);
lastSelectedItem = "";
}
//list->disableCaching();
tsl::elm::HeaderOverlayFrame* rootFrame = new tsl::elm::HeaderOverlayFrame("Horizon OC Monitor", APP_VERSION);
@@ -446,6 +441,14 @@ public:
if (SaltySD) {
LoadSharedMemoryAndRefreshRate();
}
if (hocclkIpcRunning() && R_SUCCEEDED(hocclkIpcInitialize())) {
uint32_t hocClkApiVer = 0;
hocclkIpcGetAPIVersion(&hocClkApiVer);
if (hocClkApiVer != HOCCLK_IPC_API_VERSION) {
hocclkIpcExit();
}
else hocclkCheck = 0;
}
if (R_SUCCEEDED(splInitialize())) {
u64 sku = 0;
splGetConfig(SplConfigItem_HardwareType, &sku);
@@ -458,14 +461,16 @@ public:
}
}
splExit();
sysclkIpcInitialize();
});
Hinted = envIsSyscallHinted(0x6F);
}
virtual void exitServices() override {
CloseThreads();
sysclkIpcExit();
if (R_SUCCEEDED(hocclkCheck)) {
hocclkIpcExit();
}
shmemClose(&_sharedmemory);
//Exit services
clkrstExit();
@@ -520,6 +525,14 @@ public:
if (SaltySD) {
LoadSharedMemory();
}
if (hocclkIpcRunning() && R_SUCCEEDED(hocclkIpcInitialize())) {
uint32_t hocClkApiVer = 0;
hocclkIpcGetAPIVersion(&hocClkApiVer);
if (hocClkApiVer != HOCCLK_IPC_API_VERSION) {
hocclkIpcExit();
}
else hocclkCheck = 0;
}
if (R_SUCCEEDED(splInitialize())) {
u64 sku = 0;
splGetConfig(SplConfigItem_HardwareType, &sku);
@@ -532,7 +545,6 @@ public:
}
}
splExit();
sysclkIpcInitialize();
});
Hinted = envIsSyscallHinted(0x6F);
}
@@ -540,7 +552,9 @@ public:
virtual void exitServices() override {
CloseThreads();
shmemClose(&_sharedmemory);
sysclkIpcExit();
if (R_SUCCEEDED(hocclkCheck)) {
hocclkIpcExit();
}
//Exit services
clkrstExit();
pcvExit();
@@ -598,6 +612,14 @@ public:
if (SaltySD) {
LoadSharedMemory();
}
if (hocclkIpcRunning() && R_SUCCEEDED(hocclkIpcInitialize())) {
uint32_t hocClkApiVer = 0;
hocclkIpcGetAPIVersion(&hocClkApiVer);
if (hocClkApiVer != HOCCLK_IPC_API_VERSION) {
hocclkIpcExit();
}
else hocclkCheck = 0;
}
if (R_SUCCEEDED(splInitialize())) {
u64 sku = 0;
splGetConfig(SplConfigItem_HardwareType, &sku);
@@ -610,7 +632,6 @@ public:
}
}
splExit();
sysclkIpcInitialize();
});
Hinted = envIsSyscallHinted(0x6F);
@@ -619,7 +640,9 @@ public:
virtual void exitServices() override {
CloseThreads();
shmemClose(&_sharedmemory);
sysclkIpcExit();
if (R_SUCCEEDED(hocclkCheck)) {
hocclkIpcExit();
}
// Exit services
clkrstExit();
pcvExit();
@@ -679,6 +702,14 @@ public:
if (SaltySD) {
LoadSharedMemoryAndRefreshRate();
}
if (hocclkIpcRunning() && R_SUCCEEDED(hocclkIpcInitialize())) {
uint32_t hocClkApiVer = 0;
hocclkIpcGetAPIVersion(&hocClkApiVer);
if (hocClkApiVer != HOCCLK_IPC_API_VERSION) {
hocclkIpcExit();
}
else hocclkCheck = 0;
}
if (R_SUCCEEDED(splInitialize())) {
u64 sku = 0;
splGetConfig(SplConfigItem_HardwareType, &sku);
@@ -691,7 +722,6 @@ public:
}
}
splExit();
sysclkIpcInitialize();
});
Hinted = envIsSyscallHinted(0x6F);
}
@@ -699,7 +729,9 @@ public:
virtual void exitServices() override {
CloseThreads();
shmemClose(&_sharedmemory);
sysclkIpcExit();
if (R_SUCCEEDED(hocclkCheck)) {
hocclkIpcExit();
}
clkrstExit();
pcvExit();
tsExit();
@@ -753,6 +785,14 @@ public:
if (SaltySD) {
LoadSharedMemoryAndRefreshRate();
}
if (hocclkIpcRunning() && R_SUCCEEDED(hocclkIpcInitialize())) {
uint32_t hocClkApiVer = 0;
hocclkIpcGetAPIVersion(&hocClkApiVer);
if (hocClkApiVer != HOCCLK_IPC_API_VERSION) {
hocclkIpcExit();
}
else hocclkCheck = 0;
}
if (R_SUCCEEDED(splInitialize())) {
u64 sku = 0;
splGetConfig(SplConfigItem_HardwareType, &sku);
@@ -765,7 +805,6 @@ public:
}
}
splExit();
sysclkIpcInitialize();
});
Hinted = envIsSyscallHinted(0x6F);
}
@@ -773,7 +812,9 @@ public:
virtual void exitServices() override {
CloseThreads();
shmemClose(&_sharedmemory);
sysclkIpcExit();
if (R_SUCCEEDED(hocclkCheck)) {
hocclkIpcExit();
}
clkrstExit();
pcvExit();
tsExit();
@@ -827,6 +868,14 @@ public:
if (SaltySD) {
LoadSharedMemoryAndRefreshRate();
}
if (hocclkIpcRunning() && R_SUCCEEDED(hocclkIpcInitialize())) {
uint32_t hocClkApiVer = 0;
hocclkIpcGetAPIVersion(&hocClkApiVer);
if (hocClkApiVer != HOCCLK_IPC_API_VERSION) {
hocclkIpcExit();
}
else hocclkCheck = 0;
}
if (R_SUCCEEDED(splInitialize())) {
u64 sku = 0;
splGetConfig(SplConfigItem_HardwareType, &sku);
@@ -839,7 +888,6 @@ public:
}
}
splExit();
sysclkIpcInitialize();
});
Hinted = envIsSyscallHinted(0x6F);
}
@@ -847,7 +895,9 @@ public:
virtual void exitServices() override {
CloseThreads();
shmemClose(&_sharedmemory);
sysclkIpcExit();
if (R_SUCCEEDED(hocclkCheck)) {
hocclkIpcExit();
}
clkrstExit();
pcvExit();
tsExit();
@@ -931,7 +981,7 @@ inline void setupMode(const std::string& modeType = "") {
// This function gets called on startup to create a new Overlay object
int main(int argc, char **argv) {
// load heap settings outside of loop (only Horizon OC Monitor directive)
// load heap settings outside of loop (only Status Monitor directive)
ult::currentHeapSize = ult::getCurrentHeapSize();
ult::expandedMemory = ult::currentHeapSize >= ult::OverlayHeapSize::Size_8MB;
ult::limitedMemory = ult::currentHeapSize == ult::OverlayHeapSize::Size_4MB;

View File

@@ -30,7 +30,7 @@ public:
disableJumpTo = true;
mutexInit(&mutex_BatteryChecker);
StartBatteryThread();
//tsl::elm::g_disableMenuCacheOnReturn.store(true, std::memory_order_release);
// tsl::elm::g_disableMenuCacheOnReturn.store(true, std::memory_order_release);
}
~BatteryOverlay() {
CloseBatteryThread();
@@ -141,8 +141,8 @@ public:
}
});
//tsl::elm::g_disableMenuCacheOnReturn.store(true, std::memory_order_release);
tsl::elm::HeaderOverlayFrame* rootFrame = new tsl::elm::HeaderOverlayFrame("Horizon OC Monitor", APP_VERSION, true);
// tsl::elm::g_disableMenuCacheOnReturn.store(true, std::memory_order_release);
tsl::elm::HeaderOverlayFrame* rootFrame = new tsl::elm::HeaderOverlayFrame("Status Monitor Pro", APP_VERSION, true);
rootFrame->setContent(Status);
return rootFrame;

View File

@@ -315,6 +315,12 @@ public:
});
list->addItem(showInfo);
auto* realTemps = new tsl::elm::ToggleListItem("Real Temperatures", getCurrentFPSGraphRealTemps());
realTemps->setStateChangedListener([this](bool state) {
ult::setIniFileValue(configIniPath, "fps-graph", "real_temps", state ? "true" : "false");
});
list->addItem(realTemps);
auto* dynamicColors = new tsl::elm::ToggleListItem("Use Dynamic Colors", getCurrentUseDynamicColors());
dynamicColors->setStateChangedListener([this](bool state) {
ult::setIniFileValue(configIniPath, "fps-graph", "use_dynamic_colors", state ? "true" : "false");
@@ -335,6 +341,12 @@ public:
});
list->addItem(realFreqs);
auto* realTemps = new tsl::elm::ToggleListItem("Real Temperatures", getCurrentFullRealTemps());
realTemps->setStateChangedListener([this](bool state) {
ult::setIniFileValue(configIniPath, "full", "real_temps", state ? "true" : "false");
});
list->addItem(realTemps);
auto* showDeltas = new tsl::elm::ToggleListItem("Deltas", getCurrentShowDeltas());
showDeltas->setStateChangedListener([this](bool state) {
ult::setIniFileValue(configIniPath, "full", "show_deltas", state ? "true" : "false");
@@ -365,6 +377,21 @@ public:
});
list->addItem(showRDSD);
{
const std::string curRamInfoMode = getCurrentRamInfoMode("full");
auto* ramInfoModeItem = new tsl::elm::ListItem("RAM Info Mode");
ramInfoModeItem->setValue(curRamInfoMode);
ramInfoModeItem->setClickListener([this, ramInfoModeItem](u64 keys) -> bool {
if (!(keys & KEY_A)) return false;
const std::string cur = ult::parseValueFromIniSection(configIniPath, "full", "ram_info_mode");
const std::string next = cur == "Bandwidth" ? "Load" : "Bandwidth";
ult::setIniFileValue(configIniPath, "full", "ram_info_mode", next);
ramInfoModeItem->setValue(next);
return true;
});
list->addItem(ramInfoModeItem);
}
auto* dynamicColors = new tsl::elm::ToggleListItem("Use Dynamic Colors", getCurrentUseDynamicColors());
dynamicColors->setStateChangedListener([this](bool state) {
ult::setIniFileValue(configIniPath, "fps-graph", "use_dynamic_colors", state ? "true" : "false");
@@ -393,6 +420,12 @@ public:
});
list->addItem(realVolts);
auto* realTemps = new tsl::elm::ToggleListItem("Real Temperatures", getCurrentRealTemps());
realTemps->setStateChangedListener([this, section](bool state) {
ult::setIniFileValue(configIniPath, section, "real_temps", state ? "true" : "false");
});
list->addItem(realTemps);
auto* showFullCPU = new tsl::elm::ToggleListItem("Full CPU", getCurrentShowFullCPU());
showFullCPU->setStateChangedListener([this, section](bool state) {
ult::setIniFileValue(configIniPath, section, "show_full_cpu", state ? "true" : "false");
@@ -424,11 +457,26 @@ public:
list->addItem(socVoltage);
if (isMiniMode) {
auto* PartLoadCPUGPU = new tsl::elm::ToggleListItem("RAM Load CPU/GPU", getCurrentShowRAMLoadCPUGPU());
PartLoadCPUGPU->setStateChangedListener([this, section](bool state) {
auto* partLoadCPUGPU = new tsl::elm::ToggleListItem("RAM Load CPU/GPU", getCurrentShowpartLoadCPUGPU());
partLoadCPUGPU->setStateChangedListener([this, section](bool state) {
ult::setIniFileValue(configIniPath, section, "show_RAM_load_CPU_GPU", state ? "true" : "false");
});
list->addItem(PartLoadCPUGPU);
list->addItem(partLoadCPUGPU);
}
if (isMiniMode || isMicroMode) {
const std::string curRamInfoMode = getCurrentRamInfoMode(section);
auto* ramInfoModeItem = new tsl::elm::ListItem("RAM Info Mode");
ramInfoModeItem->setValue(curRamInfoMode);
ramInfoModeItem->setClickListener([this, section, ramInfoModeItem](u64 keys) -> bool {
if (!(keys & KEY_A)) return false;
const std::string cur = ult::parseValueFromIniSection(configIniPath, section, "ram_info_mode");
const std::string next = cur == "Bandwidth" ? "Load" : "Bandwidth";
ult::setIniFileValue(configIniPath, section, "ram_info_mode", next);
ramInfoModeItem->setValue(next);
return true;
});
list->addItem(ramInfoModeItem);
}
if (isMiniMode || isMicroMode) {
@@ -518,6 +566,13 @@ private:
return value == "TRUE";
}
bool getCurrentFPSGraphRealTemps() {
std::string value = ult::parseValueFromIniSection(configIniPath, "fps-graph", "real_temps");
if (value.empty()) return false;
convertToUpper(value);
return value == "TRUE";
}
bool getCurrentRealFreqs() {
const std::string section = isMiniMode ? "mini" : "micro";
std::string value = ult::parseValueFromIniSection(configIniPath, section, "real_freqs");
@@ -534,6 +589,14 @@ private:
return value == "TRUE";
}
bool getCurrentRealTemps() {
const std::string section = isMiniMode ? "mini" : "micro";
std::string value = ult::parseValueFromIniSection(configIniPath, section, "real_temps");
if (value.empty()) return true;
convertToUpper(value);
return value == "TRUE";
}
bool getCurrentShowFullCPU() {
const std::string section = isMiniMode ? "mini" : "micro";
std::string value = ult::parseValueFromIniSection(configIniPath, section, "show_full_cpu");
@@ -575,7 +638,7 @@ private:
return value != "FALSE";
}
bool getCurrentShowRAMLoadCPUGPU() {
bool getCurrentShowpartLoadCPUGPU() {
const std::string section = isMiniMode ? "mini" : "micro";
std::string value = ult::parseValueFromIniSection(configIniPath, section, "show_RAM_load_CPU_GPU");
if (value.empty()) return false; // Default: false for mini, true for micro
@@ -583,6 +646,11 @@ private:
return value != "FALSE";
}
std::string getCurrentRamInfoMode(const std::string& section) {
const std::string value = ult::parseValueFromIniSection(configIniPath, section, "ram_info_mode");
return (value == "Bandwidth") ? "Bandwidth" : "Load";
}
bool getCurrentInvertBatteryDisplay() {
const std::string section = isMiniMode ? "mini" : "micro";
std::string value = ult::parseValueFromIniSection(configIniPath, section, "invert_battery_display");
@@ -636,6 +704,13 @@ private:
return value != "FALSE";
}
bool getCurrentFullRealTemps() {
std::string value = ult::parseValueFromIniSection(configIniPath, "full", "real_temps");
if (value.empty()) return false;
convertToUpper(value);
return value == "TRUE";
}
bool getCurrentShowDeltas() {
std::string value = ult::parseValueFromIniSection(configIniPath, "full", "show_deltas");
if (value.empty()) return true;
@@ -769,27 +844,12 @@ public:
class FramePaddingConfig : public tsl::Gui {
private:
std::string modeName;
bool isMiniMode;
bool isGameResolutionsMode;
bool isFPSCounterMode;
bool isFPSGraphMode;
int currentPadding;
public:
FramePaddingConfig(const std::string& mode) : modeName(mode) {
isMiniMode = (mode == "Mini");
isGameResolutionsMode = (mode == "Game Resolutions");
isFPSCounterMode = (mode == "FPS Counter");
isFPSGraphMode = (mode == "FPS Graph");
std::string section;
if (isMiniMode) section = "mini";
else if (isGameResolutionsMode) section = "game_resolutions";
else if (isFPSCounterMode) section = "fps-counter";
else if (isFPSGraphMode) section = "fps-graph";
const std::string value = ult::parseValueFromIniSection(configIniPath, section, "frame_padding");
currentPadding = value.empty() ? 10 : std::clamp(atoi(value.c_str()), 0, 14);
const std::string value = ult::parseValueFromIniSection(configIniPath, "mini", "frame_padding");
currentPadding = value.empty() ? 10 : std::clamp(atoi(value.c_str()), 0, 14); // max value 14
}
~FramePaddingConfig() {
@@ -800,12 +860,6 @@ public:
auto* list = new tsl::elm::List();
list->addItem(new tsl::elm::CategoryHeader("Frame Padding"));
std::string section;
if (isMiniMode) section = "mini";
else if (isGameResolutionsMode) section = "game_resolutions";
else if (isFPSCounterMode) section = "fps-counter";
else if (isFPSGraphMode) section = "fps-graph";
static const std::vector<int> paddingValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
for (int padding : paddingValues) {
auto* paddingItem = new tsl::elm::ListItem(std::to_string(padding) + " px");
@@ -813,9 +867,9 @@ public:
paddingItem->setValue(ult::CHECKMARK_SYMBOL);
lastSelectedListItem = paddingItem;
}
paddingItem->setClickListener([this, paddingItem, padding, section](uint64_t keys) {
paddingItem->setClickListener([this, paddingItem, padding](uint64_t keys) {
if (keys & KEY_A) {
ult::setIniFileValue(configIniPath, section, "frame_padding", std::to_string(padding));
ult::setIniFileValue(configIniPath, "mini", "frame_padding", std::to_string(padding));
paddingItem->setValue(ult::CHECKMARK_SYMBOL);
if (lastSelectedListItem && paddingItem != lastSelectedListItem)
lastSelectedListItem->setValue("");
@@ -2105,15 +2159,11 @@ private:
}
int getCurrentFramePadding() {
std::string section;
if (isMiniMode) section = "mini";
else if (isGameResolutionsMode) section = "game_resolutions";
else if (isFPSCounterMode) section = "fps-counter";
else if (isFPSGraphMode) section = "fps-graph";
else return 10;
std::string value = ult::parseValueFromIniSection(configIniPath, section, "frame_padding");
return value.empty() ? 10 : atoi(value.c_str());
if (isMiniMode) {
std::string value = ult::parseValueFromIniSection(configIniPath, "mini", "frame_padding");
return value.empty() ? 10 : atoi(value.c_str());
}
return 10;
}
std::string getCurrentTextAlign() {

View File

@@ -13,6 +13,9 @@ private:
char SOC_TEMP_c[12] = " -";
char PCB_TEMP_c[12] = " -";
char SKIN_TEMP_c[12] = " -";
char CPU_TEMP_c[12] = " -";
char GPU_TEMP_c[12] = " -";
char RAM_TEMP_c[12] = " -";
bool skipOnce = true;
bool runOnce = true;
@@ -402,19 +405,37 @@ public:
renderer->drawString("RAM", false, info_x, startY + lineHeight * 2+2*SPACING, fontSize, settings.catColor);
renderer->drawString(RAM_Load_c, false, value_x, startY + lineHeight * 2+2*SPACING, fontSize, settings.textColor);
// Line 3: SOC (with gradient color)
renderer->drawString("SOC", false, info_x, startY + lineHeight * 3+3*SPACING, fontSize, settings.catColor);
renderer->drawString(SOC_TEMP_c, false, value_x, startY + lineHeight * 3+3*SPACING, fontSize, socColor);
// Line 4: PCB (with gradient color)
renderer->drawString("PCB", false, info_x, startY + lineHeight * 4+4*SPACING, fontSize, settings.catColor);
renderer->drawString(PCB_TEMP_c, false, value_x, startY + lineHeight * 4+4*SPACING, fontSize, pcbColor);
// Line 5: SKIN (with gradient color)
renderer->drawString("Skin", false, info_x, startY + lineHeight * 5+5*SPACING, fontSize, settings.catColor);
renderer->drawString(SKIN_TEMP_c, false, value_x, startY + lineHeight * 5+5*SPACING, fontSize, skinColor);
}
});
// Line 3: CPU or SOC (with gradient color)
if (settings.realTemps && realCPU_Temp != 0) {
const tsl::Color cpuTempColor = settings.useDynamicColors ? tsl::GradientColor(realCPU_Temp / 1000.0f) : settings.textColor;
renderer->drawString("CPU", false, info_x, startY + lineHeight * 3+3*SPACING, fontSize, settings.catColor);
renderer->drawString(CPU_TEMP_c, false, value_x, startY + lineHeight * 3+3*SPACING, fontSize, cpuTempColor);
} else {
renderer->drawString("SOC", false, info_x, startY + lineHeight * 3+3*SPACING, fontSize, settings.catColor);
renderer->drawString(SOC_TEMP_c, false, value_x, startY + lineHeight * 3+3*SPACING, fontSize, socColor);
}
// Line 4: GPU or PCB (with gradient color)
if (settings.realTemps && realGPU_Temp != 0) {
const tsl::Color gpuTempColor = settings.useDynamicColors ? tsl::GradientColor(realGPU_Temp / 1000.0f) : settings.textColor;
renderer->drawString("GPU", false, info_x, startY + lineHeight * 4+4*SPACING, fontSize, settings.catColor);
renderer->drawString(GPU_TEMP_c, false, value_x, startY + lineHeight * 4+4*SPACING, fontSize, gpuTempColor);
} else {
renderer->drawString("PCB", false, info_x, startY + lineHeight * 4+4*SPACING, fontSize, settings.catColor);
renderer->drawString(PCB_TEMP_c, false, value_x, startY + lineHeight * 4+4*SPACING, fontSize, pcbColor);
}
// Line 5: RAM or SKIN (with gradient color)
if (settings.realTemps && realRAM_Temp != 0) {
const tsl::Color ramTempColor = settings.useDynamicColors ? tsl::GradientColor(realRAM_Temp / 1000.0f) : settings.textColor;
renderer->drawString("RAM", false, info_x, startY + lineHeight * 5+5*SPACING, fontSize, settings.catColor);
renderer->drawString(RAM_TEMP_c, false, value_x, startY + lineHeight * 5+5*SPACING, fontSize, ramTempColor);
} else {
renderer->drawString("Skin", false, info_x, startY + lineHeight * 5+5*SPACING, fontSize, settings.catColor);
renderer->drawString(SKIN_TEMP_c, false, value_x, startY + lineHeight * 5+5*SPACING, fontSize, skinColor);
}
}
});
tsl::elm::HeaderOverlayFrame* rootFrame = new tsl::elm::HeaderOverlayFrame("", "");
rootFrame->setContent(Status);
@@ -470,6 +491,16 @@ public:
snprintf(PCB_TEMP_c, sizeof PCB_TEMP_c, "%2.1f\u00B0C", PCB_temperatureF);
snprintf(SKIN_TEMP_c, sizeof SKIN_TEMP_c, "%2d.%d\u00B0C",
skin_temperaturemiliC / 1000, (skin_temperaturemiliC / 100) % 10);
if (realCPU_Temp != 0) {
snprintf(CPU_TEMP_c, sizeof(CPU_TEMP_c), "%.1f\u00B0C", realCPU_Temp / 1000.0f);
}
if (realGPU_Temp != 0) {
snprintf(GPU_TEMP_c, sizeof(GPU_TEMP_c), "%.1f\u00B0C", realGPU_Temp / 1000.0f);
}
if (realRAM_Temp != 0) {
snprintf(RAM_TEMP_c, sizeof(RAM_TEMP_c), "%.1f\u00B0C", realRAM_Temp / 1000.0f);
}
// Atomically snapshot each idle tick once
const uint64_t idle0 = idletick0.load(std::memory_order_acquire);
@@ -496,8 +527,8 @@ public:
snprintf(CPU_Load_c, sizeof(CPU_Load_c), "%.1f%%", cpu_usageM);
snprintf(GPU_Load_c, sizeof(GPU_Load_c), "%d.%d%%", GPU_Load_u / 10, GPU_Load_u % 10);
snprintf(RAM_Load_c, sizeof(RAM_Load_c), "%hu.%hhu%%",
partLoad[SysClkPartLoad_EMC] / 10,
partLoad[SysClkPartLoad_EMC] % 10);
partLoad[HocClkPartLoad_EMC] / 10,
partLoad[HocClkPartLoad_EMC] % 10);
mutexUnlock(&mutex_Misc);

View File

@@ -20,9 +20,17 @@ private:
char SOC_temperature_c[32] = "";
char PCB_temperature_c[32] = "";
char skin_temperature_c[32] = "";
char CPU_temp_c[32] = "";
char GPU_temp_c[32] = "";
char RAM_temp_c[32] = "";
char BatteryDraw_c[64] = "";
char FPS_var_compressed_c[64] = "";
char RAM_load_c[64] = "";
char RAM_load2_c[64] = "";
char RAM_bw_peak_c[16] = "";
char RAM_bw_total_c[16] = "";
char RAM_bw_gpu_c[16] = "";
char RAM_bw_cpu_c[16] = "";
char Resolutions_c[64] = "";
char readSpeed_c[32] = "";
@@ -233,16 +241,41 @@ public:
else if (realRAM_Hz && settings.showDeltas && (settings.showRealFreqs || settings.showTargetFreqs)) {
renderer->drawString(DeltaRAM_c, false, COMMON_MARGIN + deltaOffset, height_offset, 15, (settings.textColor));
}
static std::vector<std::string> PartLoadColoredChars = {"CPU", "GPU"};
//static auto loadLabelWidth = renderer->getTextDimensions("Load: ", false, 15).first;
renderer->drawString("Load", false, COMMON_MARGIN, height_offset+15, 15, (settings.catColor2));
renderer->drawStringWithColoredSections(RAM_load_c, false, PartLoadColoredChars, COMMON_MARGIN + valueOffset, height_offset+15, 15, (settings.textColor), settings.catColor2);
if (R_SUCCEEDED(hocclkCheck)) {
if (settings.ramInfoMode == "Bandwidth") {
// Fixed column layout: labels at fixed x, values at fixed x after max label width
// Col1: Peak/GPU labels, Col2: Total/CPU labels
// Values start at a fixed offset so columns don't shift with different digit counts
static const uint32_t bwLbl1W = renderer->getTextDimensions("Peak ", false, 15).first;
static const uint32_t bwLbl2W = renderer->getTextDimensions("Total ", false, 15).first;
static const uint32_t bwValW = renderer->getTextDimensions("99.9 GB/s", false, 15).first;
static const uint32_t bwGap = renderer->getTextDimensions(" ", false, 15).first;
const uint32_t xV1 = COMMON_MARGIN + bwLbl1W;
const uint32_t xL2 = xV1 + bwValW + bwGap;
const uint32_t xV2 = xL2 + bwLbl2W;
// Row 1: Peak ... Total
renderer->drawString("Peak", false, COMMON_MARGIN, height_offset+15, 15, settings.catColor2);
renderer->drawString(RAM_bw_peak_c, false, xV1, height_offset+15, 15, settings.textColor);
renderer->drawString("Total", false, xL2, height_offset+15, 15, settings.catColor2);
renderer->drawString(RAM_bw_total_c, false, xV2, height_offset+15, 15, settings.textColor);
// Row 2: GPU ... CPU
renderer->drawString("GPU", false, COMMON_MARGIN, height_offset+30, 15, settings.catColor2);
renderer->drawString(RAM_bw_gpu_c, false, xV1, height_offset+30, 15, settings.textColor);
renderer->drawString("CPU", false, xL2, height_offset+30, 15, settings.catColor2);
renderer->drawString(RAM_bw_cpu_c, false, xV2, height_offset+30, 15, settings.textColor);
} else {
static std::vector<std::string> partLoadColoredChars = {"CPU", "GPU"};
renderer->drawString("Load", false, COMMON_MARGIN, height_offset+15, 15, (settings.catColor2));
renderer->drawStringWithColoredSections(RAM_load_c, false, partLoadColoredChars, COMMON_MARGIN + valueOffset, height_offset+15, 15, (settings.textColor), settings.catColor2);
}
}
}
if (R_SUCCEEDED(Hinted)) {
const uint32_t ramUsageOffset = (R_SUCCEEDED(hocclkCheck) && settings.ramInfoMode == "Bandwidth") ? height_offset + 50 : height_offset + 40;
//static auto textWidth = renderer->getTextDimensions("Total \nApplication \nApplet \nSystem \nSystem Unsafe ", false, 15).first;
renderer->drawString("Total\nApplication\nApplet\nSystem\nSystem Unsafe", false, COMMON_MARGIN, height_offset + 40, 15, (settings.catColor2));
renderer->drawString(RAM_var_compressed_c, false, COMMON_MARGIN + valueOffset, height_offset + 40, 15, (settings.textColor));
renderer->drawString(RAM_percentage_var_compressed_c, false, ramPercentageOffset, height_offset + 40, 15, (settings.textColor));
renderer->drawString("Total\nApplication\nApplet\nSystem\nSystem Unsafe", false, COMMON_MARGIN, ramUsageOffset, 15, (settings.catColor2));
renderer->drawString(RAM_var_compressed_c, false, COMMON_MARGIN + valueOffset, ramUsageOffset, 15, (settings.textColor));
renderer->drawString(RAM_percentage_var_compressed_c, false, ramPercentageOffset, ramUsageOffset, 15, (settings.textColor));
}
}
@@ -289,6 +322,41 @@ public:
renderer->drawString(PCB_temperature_c, false, current_x, 620+2, 15, pcbColor);
}
}
// Real temps - CPU, GPU, RAM
if (settings.realTemps && (realCPU_Temp != 0 || realGPU_Temp != 0 || realRAM_Temp != 0)) {
static auto cpuTempLabelWidth = renderer->getTextDimensions("CPU ", false, 15).first;
static auto gpuTempLabelWidth = renderer->getTextDimensions("GPU ", false, 15).first;
static auto ramTempLabelWidth = renderer->getTextDimensions("RAM ", false, 15).first;
uint32_t current_x = COMMON_MARGIN + 58;;
// CPU temp
if (realCPU_Temp != 0) {
const tsl::Color cpuTempColor = settings.useDynamicColors ? tsl::GradientColor(realCPU_Temp / 1000.0f) : settings.textColor;
renderer->drawString("CPU ", false, current_x, 635+2, 15, (settings.catColor2));
current_x += cpuTempLabelWidth;
renderer->drawString(CPU_temp_c, false, current_x, 635+2, 15, cpuTempColor);
current_x += renderer->getTextDimensions(CPU_temp_c, false, 15).first + 15;
}
// GPU temp
if (realGPU_Temp != 0) {
const tsl::Color gpuTempColor = settings.useDynamicColors ? tsl::GradientColor(realGPU_Temp / 1000.0f) : settings.textColor;
renderer->drawString("GPU ", false, current_x, 635+2, 15, (settings.catColor2));
current_x += gpuTempLabelWidth;
renderer->drawString(GPU_temp_c, false, current_x, 635+2, 15, gpuTempColor);
current_x += renderer->getTextDimensions(GPU_temp_c, false, 15).first + 15;
}
// RAM temp
if (realRAM_Temp != 0) {
const tsl::Color ramTempColor = settings.useDynamicColors ? tsl::GradientColor(realRAM_Temp / 1000.0f) : settings.textColor;
renderer->drawString("RAM ", false, current_x, 635+2, 15, (settings.catColor2));
current_x += ramTempLabelWidth;
renderer->drawString(RAM_temp_c, false, current_x, 635+2, 15, ramTempColor);
}
}
///FPS
if (GameRunning) {
@@ -443,13 +511,28 @@ public:
RAMPct_systemunsafe
);
if (R_SUCCEEDED(sysclkCheck)) {
const int RAM_GPU_Load = partLoad[SysClkPartLoad_EMC] - partLoad[SysClkPartLoad_EMCCpu];
snprintf(RAM_load_c, sizeof RAM_load_c,
"%u.%u%% CPU %u.%u%% GPU %u.%u%%",
partLoad[SysClkPartLoad_EMC] / 10, partLoad[SysClkPartLoad_EMC] % 10,
partLoad[SysClkPartLoad_EMCCpu] / 10, partLoad[SysClkPartLoad_EMCCpu] % 10,
RAM_GPU_Load / 10, RAM_GPU_Load % 10);
if (R_SUCCEEDED(hocclkCheck)) {
if (settings.ramInfoMode == "Bandwidth") {
const unsigned bwAll = partLoad[HocClkPartLoad_RamBWAll] / 1000;
const unsigned bwAllD = (partLoad[HocClkPartLoad_RamBWAll] % 1000) / 100;
const unsigned bwPeak = partLoad[HocClkPartLoad_RamBWPeak] / 1000;
const unsigned bwPeakD= (partLoad[HocClkPartLoad_RamBWPeak]% 1000) / 100;
const unsigned bwCpu = partLoad[HocClkPartLoad_RamBWCpu] / 1000;
const unsigned bwCpuD = (partLoad[HocClkPartLoad_RamBWCpu] % 1000) / 100;
const unsigned bwGpu = partLoad[HocClkPartLoad_RamBWGpu] / 1000;
const unsigned bwGpuD = (partLoad[HocClkPartLoad_RamBWGpu] % 1000) / 100;
snprintf(RAM_bw_peak_c, sizeof RAM_bw_peak_c, "%u.%u GB/s", bwPeak, bwPeakD);
snprintf(RAM_bw_total_c, sizeof RAM_bw_total_c, "%u.%u GB/s", bwAll, bwAllD);
snprintf(RAM_bw_gpu_c, sizeof RAM_bw_gpu_c, "%u.%u GB/s", bwGpu, bwGpuD);
snprintf(RAM_bw_cpu_c, sizeof RAM_bw_cpu_c, "%u.%u GB/s", bwCpu, bwCpuD);
} else {
const int RAM_GPU_Load = partLoad[HocClkPartLoad_EMC] - partLoad[HocClkPartLoad_EMCCpu];
snprintf(RAM_load_c, sizeof RAM_load_c,
"%u.%u%% CPU %u.%u%% GPU %u.%u%%",
partLoad[HocClkPartLoad_EMC] / 10, partLoad[HocClkPartLoad_EMC] % 10,
partLoad[HocClkPartLoad_EMCCpu] / 10, partLoad[HocClkPartLoad_EMCCpu] % 10,
RAM_GPU_Load / 10, RAM_GPU_Load % 10);
}
}
///Thermal
snprintf(SOC_temperature_c, sizeof SOC_temperature_c, "%.1f\u00B0C", SOC_temperatureF);
@@ -458,6 +541,17 @@ public:
snprintf(Rotation_SpeedLevel_c, sizeof Rotation_SpeedLevel_c, "%.1f%%", Rotation_Duty);
if (settings.realTemps) {
if (realCPU_Temp != 0) {
snprintf(CPU_temp_c, sizeof(CPU_temp_c), "%.1f°C", realCPU_Temp / 1000.0f);
}
if (realGPU_Temp != 0) {
snprintf(GPU_temp_c, sizeof(GPU_temp_c), "%.1f°C", realGPU_Temp / 1000.0f);
}
if (realRAM_Temp != 0) {
snprintf(RAM_temp_c, sizeof(RAM_temp_c), "%.1f°C", realRAM_Temp / 1000.0f);
}
}
///FPS
if (settings.showFPS == true) {
snprintf(PFPS_value_c, sizeof PFPS_value_c, "%1u", FPS);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -50,7 +50,7 @@ public:
smExit();
StartMiscThread();
//tsl::elm::g_disableMenuCacheOnReturn.store(true, std::memory_order_release);
// tsl::elm::g_disableMenuCacheOnReturn.store(true, std::memory_order_release);
}
~MiscOverlay() {
@@ -121,8 +121,8 @@ public:
});
//tsl::elm::g_disableMenuCacheOnReturn.store(true, std::memory_order_release);
tsl::elm::HeaderOverlayFrame* rootFrame = new tsl::elm::HeaderOverlayFrame("Horizon OC Monitor", APP_VERSION, true);
// tsl::elm::g_disableMenuCacheOnReturn.store(true, std::memory_order_release);
tsl::elm::HeaderOverlayFrame* rootFrame = new tsl::elm::HeaderOverlayFrame("Status Monitor Pro", APP_VERSION, true);
rootFrame->setContent(Status);
return rootFrame;

View File

@@ -29,128 +29,128 @@
#include <switch.h>
#include <string.h>
#include <stdatomic.h>
#include <sysclk/client/ipc.h>
#include <hocclk/client/ipc.h>
static Service g_sysclkSrv;
static Service g_hocclkSrv;
static atomic_size_t g_refCnt;
bool sysclkIpcRunning()
bool hocclkIpcRunning()
{
Handle handle;
bool running = R_FAILED(smRegisterService(&handle, smEncodeName(SYSCLK_IPC_SERVICE_NAME), false, 1));
bool running = R_FAILED(smRegisterService(&handle, smEncodeName(HOCCLK_IPC_SERVICE_NAME), false, 1));
if (!running)
{
smUnregisterService(smEncodeName(SYSCLK_IPC_SERVICE_NAME));
smUnregisterService(smEncodeName(HOCCLK_IPC_SERVICE_NAME));
}
return running;
}
Result sysclkIpcInitialize(void)
Result hocclkIpcInitialize(void)
{
Result rc = 0;
g_refCnt++;
if (serviceIsActive(&g_sysclkSrv))
if (serviceIsActive(&g_hocclkSrv))
return 0;
rc = smGetService(&g_sysclkSrv, SYSCLK_IPC_SERVICE_NAME);
rc = smGetService(&g_hocclkSrv, HOCCLK_IPC_SERVICE_NAME);
if (R_FAILED(rc)) sysclkIpcExit();
if (R_FAILED(rc)) hocclkIpcExit();
return rc;
}
void sysclkIpcExit(void)
void hocclkIpcExit(void)
{
if (--g_refCnt == 0)
{
serviceClose(&g_sysclkSrv);
serviceClose(&g_hocclkSrv);
}
}
Result sysclkIpcGetAPIVersion(u32* out_ver)
Result hocclkIpcGetAPIVersion(u32* out_ver)
{
return serviceDispatchOut(&g_sysclkSrv, SysClkIpcCmd_GetApiVersion, *out_ver);
return serviceDispatchOut(&g_hocclkSrv, HocClkIpcCmd_GetApiVersion, *out_ver);
}
Result sysclkIpcGetVersionString(char* out, size_t len)
Result hocclkIpcGetVersionString(char* out, size_t len)
{
return serviceDispatch(&g_sysclkSrv, SysClkIpcCmd_GetVersionString,
return serviceDispatch(&g_hocclkSrv, HocClkIpcCmd_GetVersionString,
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out },
.buffers = {{out, len}},
);
}
Result sysclkIpcGetCurrentContext(SysClkContext* out_context)
Result hocclkIpcGetCurrentContext(HocClkContext* out_context)
{
return serviceDispatch(&g_sysclkSrv, SysClkIpcCmd_GetCurrentContext,
return serviceDispatch(&g_hocclkSrv, HocClkIpcCmd_GetCurrentContext,
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out },
.buffers = {{out_context, sizeof(SysClkContext)}},
.buffers = {{out_context, sizeof(HocClkContext)}},
);
}
Result sysclkIpcGetProfileCount(u64 tid, u8* out_count)
Result hocclkIpcGetProfileCount(u64 tid, u8* out_count)
{
return serviceDispatchInOut(&g_sysclkSrv, SysClkIpcCmd_GetProfileCount, tid, *out_count);
return serviceDispatchInOut(&g_hocclkSrv, HocClkIpcCmd_GetProfileCount, tid, *out_count);
}
Result sysclkIpcSetEnabled(bool enabled)
Result hocclkIpcSetEnabled(bool enabled)
{
u8 enabledRaw = (u8)enabled;
return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetEnabled, enabledRaw);
return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_SetEnabled, enabledRaw);
}
Result sysclkIpcSetOverride(SysClkModule module, u32 hz)
Result hocclkIpcSetOverride(HocClkModule module, u32 hz)
{
SysClkIpc_SetOverride_Args args = {
HocClkIpc_SetOverride_Args args = {
.module = module,
.hz = hz
};
return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetOverride, args);
return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_SetOverride, args);
}
Result sysclkIpcGetProfiles(u64 tid, SysClkTitleProfileList* out_profiles)
Result hocclkIpcGetProfiles(u64 tid, HocClkTitleProfileList* out_profiles)
{
return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_GetProfiles, tid,
return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_GetProfiles, tid,
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out },
.buffers = {{out_profiles, sizeof(SysClkTitleProfileList)}},
.buffers = {{out_profiles, sizeof(HocClkTitleProfileList)}},
);
}
Result sysclkIpcSetProfiles(u64 tid, SysClkTitleProfileList* profiles)
Result hocclkIpcSetProfiles(u64 tid, HocClkTitleProfileList* profiles)
{
SysClkIpc_SetProfiles_Args args;
HocClkIpc_SetProfiles_Args args;
args.tid = tid;
memcpy(&args.profiles, profiles, sizeof(SysClkTitleProfileList));
return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetProfiles, args);
memcpy(&args.profiles, profiles, sizeof(HocClkTitleProfileList));
return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_SetProfiles, args);
}
Result sysclkIpcGetConfigValues(SysClkConfigValueList* out_configValues)
Result hocclkIpcGetConfigValues(HocClkConfigValueList* out_configValues)
{
return serviceDispatch(&g_sysclkSrv, SysClkIpcCmd_GetConfigValues,
return serviceDispatch(&g_hocclkSrv, HocClkIpcCmd_GetConfigValues,
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out },
.buffers = {{out_configValues, sizeof(SysClkConfigValueList)}},
.buffers = {{out_configValues, sizeof(HocClkConfigValueList)}},
);
}
Result sysclkIpcSetConfigValues(SysClkConfigValueList* configValues)
Result hocclkIpcSetConfigValues(HocClkConfigValueList* configValues)
{
return serviceDispatch(&g_sysclkSrv, SysClkIpcCmd_SetConfigValues,
return serviceDispatch(&g_hocclkSrv, HocClkIpcCmd_SetConfigValues,
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_In },
.buffers = {{configValues, sizeof(SysClkConfigValueList)}},
.buffers = {{configValues, sizeof(HocClkConfigValueList)}},
);
}
Result sysclkIpcGetFreqList(SysClkModule module, u32* list, u32 maxCount, u32* outCount)
Result hocclkIpcGetFreqList(HocClkModule module, u32* list, u32 maxCount, u32* outCount)
{
SysClkIpc_GetFreqList_Args args = {
HocClkIpc_GetFreqList_Args args = {
.module = module,
.maxCount = maxCount
};
return serviceDispatchInOut(&g_sysclkSrv, SysClkIpcCmd_GetFreqList, args, *outCount,
return serviceDispatchInOut(&g_hocclkSrv, HocClkIpcCmd_GetFreqList, args, *outCount,
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out },
.buffers = {{list, maxCount * sizeof(u32)}},
);
@@ -159,11 +159,11 @@ Result sysclkIpcGetFreqList(SysClkModule module, u32* list, u32 maxCount, u32* o
Result hocClkIpcSetKipData()
{
u32 temp = 0;
return serviceDispatchIn(&g_sysclkSrv, HocClkIpcCmd_SetKipData, temp);
return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_SetKipData, temp);
}
Result hocClkIpcGetKipData()
{
u32 temp = 0;
return serviceDispatchIn(&g_sysclkSrv, HocClkIpcCmd_GetKipData, temp);
return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_GetKipData, temp);
}

View File

@@ -0,0 +1,897 @@
/*
* Copyright (c) Atmosphère-NX
*
* Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "ldr_capabilities.hpp"
#include "ldr_content_management.hpp"
#include "ldr_development_manager.hpp"
#include "ldr_launch_record.hpp"
#include "ldr_meta.hpp"
#include "ldr_patcher.hpp"
#include "ldr_process_creation.hpp"
#include "ldr_ro_manager.hpp"
#include "oc/oc_loader.hpp"
namespace ams::ldr {
namespace {
/* Convenience defines. */
constexpr size_t SystemResourceSizeMax = 0x1FE00000;
constexpr size_t AutoLoadModuleSizeMax = 0x800000000;
/* Types. */
enum NsoIndex {
Nso_Rtld = 0,
Nso_Main = 1,
Nso_Wkc0 = 2,
Nso_Wkc1 = 3,
Nso_Wkc2 = 4,
Nso_Wkc3 = 5,
Nso_Wkc4 = 6,
Nso_Wkc5 = 7,
Nso_Wkc6 = 8,
Nso_Wkc7 = 9,
Nso_Wkc8 = 10,
Nso_Wkc9 = 11,
Nso_SubSdk0 = 12,
Nso_SubSdk1 = 13,
Nso_SubSdk2 = 14,
Nso_SubSdk3 = 15,
Nso_SubSdk4 = 16,
Nso_SubSdk5 = 17,
Nso_SubSdk6 = 18,
Nso_SubSdk7 = 19,
Nso_SubSdk8 = 20,
Nso_SubSdk9 = 21,
Nso_Sdk = 22,
Nso_Count,
};
constexpr inline const char *NsoPaths[Nso_Count] = {
ENCODE_ATMOSPHERE_CODE_PATH("/rtld"),
ENCODE_ATMOSPHERE_CODE_PATH("/main"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc0"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc1"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc2"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc3"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc4"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc5"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc6"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc7"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc8"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc9"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk0"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk1"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk2"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk3"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk4"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk5"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk6"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk7"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk8"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk9"),
ENCODE_ATMOSPHERE_CODE_PATH("/sdk"),
};
constexpr const char *GetNsoPath(size_t idx) {
AMS_ABORT_UNLESS(idx < Nso_Count);
return NsoPaths[idx];
}
struct ProcessInfo {
os::NativeHandle process_handle;
uintptr_t args_address;
size_t args_size;
uintptr_t nso_address[Nso_Count];
size_t nso_size[Nso_Count];
};
struct AutoLoadModuleInfo {
bool has_rtld;
bool has_main;
bool has_sdk;
bool has_subsdk;
bool has_nso[Nso_Count];
};
/* Global NSO header cache. */
NsoHeader g_nso_headers[Nso_Count];
/* Pcv/Ptm check cache */
bool g_is_pcv;
bool g_is_ptm;
Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {
/* No version verification is done before 8.1.0. */
R_SUCCEED_IF(hos::GetVersion() < hos::Version_8_1_0);
/* No verification is done if development. */
R_SUCCEED_IF(IsDevelopmentForAntiDowngradeCheck());
/* TODO: Anti-downgrade checking does not make very much sense for us. Should we do anything? */
AMS_UNUSED(program_id, version);
R_SUCCEED();
}
/* Helpers. */
Result GetProgramInfoFromMeta(ProgramInfo *out, const Meta *meta) {
/* Copy basic info. */
out->main_thread_priority = meta->npdm->main_thread_priority;
out->default_cpu_id = meta->npdm->default_cpu_id;
out->main_thread_stack_size = meta->npdm->main_thread_stack_size;
out->program_id = meta->aci->program_id;
/* Copy access controls. */
size_t offset = 0;
#define COPY_ACCESS_CONTROL(source, which) \
({ \
const size_t size = meta->source->which##_size; \
R_UNLESS(offset + size <= sizeof(out->ac_buffer), ldr::ResultInternalError()); \
out->source##_##which##_size = size; \
std::memcpy(out->ac_buffer + offset, meta->source##_##which, size); \
offset += size; \
})
/* Copy all access controls to buffer. */
COPY_ACCESS_CONTROL(acid, sac);
COPY_ACCESS_CONTROL(aci, sac);
COPY_ACCESS_CONTROL(acid, fac);
COPY_ACCESS_CONTROL(aci, fah);
#undef COPY_ACCESS_CONTROL
/* Copy flags. */
out->flags = MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32));
R_SUCCEED();
}
bool IsApplet(const Meta *meta) {
return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Applet;
}
bool IsApplication(const Meta *meta) {
return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Application;
}
Npdm::AddressSpaceType GetAddressSpaceType(const Meta *meta) {
return static_cast<Npdm::AddressSpaceType>((meta->npdm->flags & Npdm::MetaFlag_AddressSpaceTypeMask) >> Npdm::MetaFlag_AddressSpaceTypeShift);
}
Acid::PoolPartition GetPoolPartition(const Meta *meta) {
return static_cast<Acid::PoolPartition>((meta->acid->flags & Acid::AcidFlag_PoolPartitionMask) >> Acid::AcidFlag_PoolPartitionShift);
}
Result LoadAutoLoadHeaders(NsoHeader *nso_headers, AutoLoadModuleInfo *ali, u32 acid_flags) {
/* Clear NSOs. */
std::memset(nso_headers, 0, sizeof(*nso_headers) * Nso_Count);
*ali = {};
for (size_t i = 0; i < Nso_Count; i++) {
/* Only load browser DLLs if acid flags say to do so. */
switch (i) {
case Nso_Wkc0:
case Nso_Wkc1:
case Nso_Wkc2:
case Nso_Wkc3:
case Nso_Wkc4:
case Nso_Wkc5:
case Nso_Wkc6:
case Nso_Wkc7:
case Nso_Wkc8:
case Nso_Wkc9:
if ((acid_flags & Acid::AcidFlag_LoadBrowserCoreDll) == 0) {
continue;
}
break;
}
fs::FileHandle file;
if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read))) {
ON_SCOPE_EXIT { fs::CloseFile(file); };
/* Read NSO header. */
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, 0, nso_headers + i, sizeof(*nso_headers)));
R_UNLESS(read_size == sizeof(*nso_headers), ldr::ResultInvalidNso());
/* Note nso is present. */
switch (i) {
case Nso_Rtld:
ali->has_rtld = true;
break;
case Nso_Main:
ali->has_main = true;
break;
case Nso_SubSdk0:
case Nso_SubSdk1:
case Nso_SubSdk2:
case Nso_SubSdk3:
case Nso_SubSdk4:
case Nso_SubSdk5:
case Nso_SubSdk6:
case Nso_SubSdk7:
case Nso_SubSdk8:
case Nso_SubSdk9:
ali->has_subsdk = true;
break;
case Nso_Sdk:
ali->has_sdk = true;
break;
}
ali->has_nso[i] = true;
}
}
R_SUCCEED();
}
Result CheckAutoLoad(const NsoHeader *nso_headers, const AutoLoadModuleInfo *ali, u32 acid_flags) {
/* We must always have a main. */
R_UNLESS(ali->has_main, ldr::ResultInvalidNso());
/* All NSOs must not be --X. */
/* This is "probably" not checked on Ounce? */
for (size_t i = 0; i < Nso_Count; ++i) {
R_UNLESS((nso_headers[i].flags & NsoHeader::Flag_PreventCodeReads) == 0, ldr::ResultInvalidNso());
}
/* If we don't have an RTLD, we must only have a main. */
const bool has_browser_dll = (acid_flags & Acid::AcidFlag_LoadBrowserCoreDll) != 0;
if (!ali->has_rtld) {
/* If don't have rtld, we must also not have sdk. */
R_UNLESS(!ali->has_sdk, ldr::ResultInvalidNso());
/* We must also not have both subsdk and browser dll. */
R_UNLESS(!(ali->has_subsdk && has_browser_dll), ldr::ResultInvalidNso());
} else {
/* If we have rtld, we must not have browser core dll. */
R_UNLESS(!has_browser_dll, ldr::ResultInvalidNso());
}
/* Check NSO extents. */
for (size_t i = 0; i < Nso_Count; i++) {
/* Only validate the nsos we have. */
if (!ali->has_nso[i]) {
continue;
}
/* NSOs must have page-aligned segments. */
R_UNLESS(util::IsAligned(nso_headers[i].text_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
R_UNLESS(util::IsAligned(nso_headers[i].ro_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
R_UNLESS(util::IsAligned(nso_headers[i].rw_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
/* NSOs must have zero text offset. */
R_UNLESS(nso_headers[i].text_dst_offset == 0, ldr::ResultInvalidNso());
/* NSO .text must precede .rodata. */
const size_t text_end = static_cast<size_t>(nso_headers[i].text_dst_offset) + static_cast<size_t>(nso_headers[i].text_size);
R_UNLESS(text_end <= static_cast<size_t>(nso_headers[i].ro_dst_offset), ldr::ResultInvalidNso());
/* NSO .rodata must precede .rwdata. */
const size_t ro_end = static_cast<size_t>(nso_headers[i].ro_dst_offset) + static_cast<size_t>(nso_headers[i].ro_size);
R_UNLESS(ro_end <= static_cast<size_t>(nso_headers[i].rw_dst_offset), ldr::ResultInvalidNso());
}
R_SUCCEED();
}
constexpr const ncm::ProgramId UnqualifiedApprovalProgramIds[] = {
{ 0x010003F003A34000 }, /* Pokemon: Let's Go, Pikachu! */
{ 0x0100152000022000 }, /* Mario Kart 8 Deluxe */
{ 0x0100165003504000 }, /* Nintendo Labo Toy-Con 04: VR Kit */
{ 0x0100187003A36000 }, /* Pokemon: Let's Go, Eevee! */
{ 0x01002E5008C56000 }, /* Pokemon Sword [Live Tournament] */
{ 0x01002FF008C24000 }, /* Ring Fit Adventure */
{ 0x010049900F546001 }, /* Super Mario 3D All-Stars: Super Mario 64 */
{ 0x010057D00ECE4000 }, /* Nintendo Switch Online (Nintendo 64) [for Japan] */
{ 0x01006F8002326000 }, /* Animal Crossing: New Horizons */
{ 0x01006FB00F50E000 }, /* [???] */
{ 0x010070300F50C000 }, /* [???] */
{ 0x010075100E8EC000 }, /* 马力欧卡丁车8 豪华版 [Mario Kart 8 Deluxe for China] */
{ 0x01008DB008C2C000 }, /* Pokemon Shield */
{ 0x01009AD008C4C000 }, /* Pokemon: Let's Go, Pikachu! [Kiosk] */
{ 0x0100A66003384000 }, /* Hulu */
{ 0x0100ABF008968000 }, /* Pokemon Sword */
{ 0x0100C9A00ECE6000 }, /* Nintendo Switch Online (Nintendo 64) [for America] */
{ 0x0100ED100BA3A000 }, /* Mario Kart Live: Home Circuit */
{ 0x0100F38011CFE000 }, /* Animal Crossing: New Horizons Island Transfer Tool */
{ 0x0100F6B011028000 }, /* 健身环大冒险 [Ring Fit Adventure for China] */
};
/* Check that the unqualified approval programs are sorted. */
static_assert([]() -> bool {
for (size_t i = 0; i < util::size(UnqualifiedApprovalProgramIds) - 1; ++i) {
if (UnqualifiedApprovalProgramIds[i].value >= UnqualifiedApprovalProgramIds[i + 1].value) {
return false;
}
}
return true;
}());
bool IsUnqualifiedApprovalProgramId(ncm::ProgramId program_id) {
/* Check if the program id is one with unqualified approval. */
return std::binary_search(std::begin(UnqualifiedApprovalProgramIds), std::end(UnqualifiedApprovalProgramIds), program_id);
}
bool IsUnqualifiedApproval(const Meta *meta) {
/* If the meta has unqualified approval flag, it's unqualified approval. */
if (meta->acid->flags & ldr::Acid::AcidFlag_UnqualifiedApproval) {
return true;
}
/* If the unqualified approval flag is not set, the program must be an application. */
if (!IsApplication(meta)) {
return false;
}
/* The program id must be a force unqualified approval program id. */
return IsUnqualifiedApprovalProgramId(meta->acid->program_id_min) && meta->acid->program_id_min == meta->acid->program_id_max;
}
Result ValidateMeta(const Meta *meta, const ncm::ProgramLocation &loc, const fs::CodeVerificationData &code_verification_data) {
/* Validate version. */
R_TRY(ValidateProgramVersion(loc.program_id, meta->npdm->version));
/* Validate program id. */
R_UNLESS(meta->aci->program_id >= meta->acid->program_id_min, ldr::ResultInvalidProgramId());
R_UNLESS(meta->aci->program_id <= meta->acid->program_id_max, ldr::ResultInvalidProgramId());
/* Validate the kernel capabilities. */
R_TRY(TestCapability(static_cast<const util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32), static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)));
/* Check if NCA is PCV or PTM */
g_is_pcv = meta->aci->program_id == ncm::SystemProgramId::Pcv;
g_is_ptm = meta->aci->program_id == ncm::SystemProgramId::Ptm;
/* If we have data to validate, validate it. */
if (meta->check_verification_data) {
const u8 *sig = code_verification_data.signature;
const size_t sig_size = sizeof(code_verification_data.signature);
const u8 *mod = static_cast<u8 *>(meta->modulus);
const size_t mod_size = crypto::Rsa2048PssSha256Verifier::ModulusSize;
const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent();
const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize;
const u8 *hsh = code_verification_data.target_hash;
const size_t hsh_size = sizeof(code_verification_data.target_hash);
const bool is_signature_valid = crypto::VerifyRsa2048PssSha256WithHash(sig, sig_size, mod, mod_size, exp, exp_size, hsh, hsh_size);
/* If the signature check fails, we need to check if this is allowable. */
if (!is_signature_valid) {
/* We have to enforce signature checks on prod and when we have a signature to check on dev. */
R_UNLESS(IsDevelopmentForAcidProductionCheck(), ldr::ResultInvalidNcaSignature());
R_UNLESS(!code_verification_data.has_data, ldr::ResultInvalidNcaSignature());
/* There was no signature to check on dev. Check if this is acceptable. */
R_UNLESS(IsUnqualifiedApproval(meta), ldr::ResultInvalidNcaSignature());
}
}
/* All good. */
R_SUCCEED();
}
Result GetCreateProcessFlags(u32 *out, const Meta *meta, const u32 ldr_flags) {
const u8 meta_flags = meta->npdm->flags;
u32 flags = 0;
/* Set Is64Bit. */
if (meta_flags & Npdm::MetaFlag_Is64Bit) {
flags |= svc::CreateProcessFlag_Is64Bit;
}
/* Set AddressSpaceType. */
switch (GetAddressSpaceType(meta)) {
case Npdm::AddressSpaceType_32Bit:
flags |= svc::CreateProcessFlag_AddressSpace32Bit;
break;
case Npdm::AddressSpaceType_64BitDeprecated:
flags |= svc::CreateProcessFlag_AddressSpace64BitDeprecated;
break;
case Npdm::AddressSpaceType_32BitWithoutAlias:
flags |= svc::CreateProcessFlag_AddressSpace32BitWithoutAlias;
break;
case Npdm::AddressSpaceType_64Bit:
flags |= svc::CreateProcessFlag_AddressSpace64Bit;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
}
/* Set Enable Debug. */
if (ldr_flags & CreateProcessFlag_EnableDebug) {
flags |= svc::CreateProcessFlag_EnableDebug;
}
/* Set Enable ASLR. */
if (!(ldr_flags & CreateProcessFlag_DisableAslr)) {
flags |= svc::CreateProcessFlag_EnableAslr;
}
/* Set Is Application. */
if (IsApplication(meta)) {
flags |= svc::CreateProcessFlag_IsApplication;
/* 7.0.0+: Set OptimizeMemoryAllocation if relevant. */
if (hos::GetVersion() >= hos::Version_7_0_0) {
if (meta_flags & Npdm::MetaFlag_OptimizeMemoryAllocation) {
flags |= svc::CreateProcessFlag_OptimizeMemoryAllocation;
}
}
}
/* 5.0.0+ Set Pool Partition. */
if (hos::GetVersion() >= hos::Version_5_0_0) {
/* TODO: Nintendo no longer accepts Applet when pool partition == application. Would this break hbl/anything else in the hb ecosystem? */
/* TODO: Nintendo uses a helper bool MakeSvcPoolPartitionFlag(u32 *out, Acid::PoolPartition partition); */
switch (GetPoolPartition(meta)) {
case Acid::PoolPartition_Application:
if (IsApplet(meta)) {
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
} else {
flags |= svc::CreateProcessFlag_PoolPartitionApplication;
}
break;
case Acid::PoolPartition_Applet:
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
break;
case Acid::PoolPartition_System:
flags |= svc::CreateProcessFlag_PoolPartitionSystem;
break;
case Acid::PoolPartition_SystemNonSecure:
flags |= svc::CreateProcessFlag_PoolPartitionSystemNonSecure;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
}
} else if (hos::GetVersion() >= hos::Version_4_0_0) {
/* On 4.0.0+, the corresponding bit was simply "UseSecureMemory". */
if (meta->acid->flags & Acid::AcidFlag_DeprecatedUseSecureMemory) {
flags |= svc::CreateProcessFlag_DeprecatedUseSecureMemory;
}
}
/* 11.0.0+/meso Set Disable DAS merge. */
if (meta_flags & Npdm::MetaFlag_DisableDeviceAddressSpaceMerge) {
flags |= svc::CreateProcessFlag_DisableDeviceAddressSpaceMerge;
}
/* 18.0.0+/meso Set Alias region extra size. */
if (meta_flags & Npdm::MetaFlag_EnableAliasRegionExtraSize) {
flags |= svc::CreateProcessFlag_EnableAliasRegionExtraSize;
}
*out = flags;
R_SUCCEED();
}
Result GetCreateProcessParameter(svc::CreateProcessParameter *out, const Meta *meta, u32 flags, os::NativeHandle resource_limit) {
/* Clear output. */
std::memset(out, 0, sizeof(*out));
/* Set name, version, program id, resource limit handle. */
std::memcpy(out->name, meta->npdm->program_name, sizeof(out->name) - 1);
out->version = meta->npdm->version;
out->program_id = meta->aci->program_id.value;
out->reslimit = resource_limit;
/* Set flags. */
R_TRY(GetCreateProcessFlags(std::addressof(out->flags), meta, flags));
/* 3.0.0+ System Resource Size. */
if (hos::GetVersion() >= hos::Version_3_0_0) {
/* Validate size is aligned. */
R_UNLESS(util::IsAligned(meta->npdm->system_resource_size, os::MemoryBlockUnitSize), ldr::ResultInvalidSize());
/* Validate system resource usage. */
if (meta->npdm->system_resource_size) {
/* Process must be 64-bit. */
R_UNLESS((out->flags & svc::CreateProcessFlag_AddressSpace64Bit), ldr::ResultInvalidMeta());
/* Process must be application or applet. */
R_UNLESS(IsApplication(meta) || IsApplet(meta), ldr::ResultInvalidMeta());
/* Size must be less than or equal to max. */
R_UNLESS(meta->npdm->system_resource_size <= SystemResourceSizeMax, ldr::ResultInvalidMeta());
}
out->system_resource_num_pages = meta->npdm->system_resource_size >> 12;
}
R_SUCCEED();
}
u64 GenerateSecureRandom(u64 max) {
/* Generate a cryptographically random number. */
u64 rand;
crypto::GenerateCryptographicallyRandomBytes(std::addressof(rand), sizeof(rand));
/* Coerce into range. */
return rand % (max + 1);
}
Result DecideAddressSpaceLayout(ProcessInfo *out, svc::CreateProcessParameter *out_param, const NsoHeader *nso_headers, const AutoLoadModuleInfo *ali, const ArgumentStore::Entry *argument) {
/* Clear output. */
out->args_address = 0;
out->args_size = 0;
std::memset(out->nso_address, 0, sizeof(out->nso_address));
std::memset(out->nso_size, 0, sizeof(out->nso_size));
size_t total_size = 0;
bool argument_allocated = false;
/* Calculate base offsets. */
for (size_t i = 0; i < Nso_Count; i++) {
if (ali->has_nso[i]) {
out->nso_address[i] = total_size;
const size_t text_end = static_cast<size_t>(nso_headers[i].text_dst_offset) + static_cast<size_t>(nso_headers[i].text_size);
const size_t ro_end = static_cast<size_t>(nso_headers[i].ro_dst_offset) + static_cast<size_t>(nso_headers[i].ro_size);
const size_t rw_end = static_cast<size_t>(nso_headers[i].rw_dst_offset) + static_cast<size_t>(nso_headers[i].rw_size);
out->nso_size[i] = text_end;
out->nso_size[i] = std::max(out->nso_size[i], ro_end);
out->nso_size[i] = std::max(out->nso_size[i], rw_end);
out->nso_size[i] += static_cast<size_t>(nso_headers[i].bss_size);
const size_t aligned_up_size = util::AlignUp(out->nso_size[i], os::MemoryPageSize) & (AutoLoadModuleSizeMax - 1);
R_UNLESS(out->nso_size[i] <= aligned_up_size, ldr::ResultInvalidNso());
R_UNLESS(aligned_up_size > 0, ldr::ResultInvalidNso());
out->nso_size[i] = aligned_up_size;
R_UNLESS(util::CanAddWithoutOverflow(total_size, out->nso_size[i]), ldr::ResultInvalidNso());
total_size += out->nso_size[i];
if (!argument_allocated && argument != nullptr) {
out->args_address = total_size;
out->args_size = util::AlignUp(2 * sizeof(u32) + argument->argument_size * 2 + ArgumentStore::ArgumentBufferSize, os::MemoryPageSize);
R_UNLESS(util::CanAddWithoutOverflow(total_size, out->args_size), ldr::ResultInvalidNso());
total_size += out->args_size;
argument_allocated = true;
}
}
}
/* Calculate ASLR. */
uintptr_t aslr_start = 0;
size_t aslr_size = 0;
if (hos::GetVersion() >= hos::Version_2_0_0) {
switch (out_param->flags & svc::CreateProcessFlag_AddressSpaceMask) {
case svc::CreateProcessFlag_AddressSpace32Bit:
case svc::CreateProcessFlag_AddressSpace32BitWithoutAlias:
aslr_start = svc::AddressSmallMap32Start;
aslr_size = svc::AddressSmallMap32Size;
break;
case svc::CreateProcessFlag_AddressSpace64BitDeprecated:
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
break;
case svc::CreateProcessFlag_AddressSpace64Bit:
aslr_start = svc::AddressMap39Start;
aslr_size = svc::AddressMap39Size;
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
} else {
/* On 1.0.0, only 2 address space types existed. */
if (out_param->flags & svc::CreateProcessFlag_AddressSpace64BitDeprecated) {
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
} else {
aslr_start = svc::AddressSmallMap32Start;
aslr_size = svc::AddressSmallMap32Size;
}
}
R_UNLESS(total_size <= aslr_size, svc::ResultOutOfMemory());
/* Set Create Process output. */
uintptr_t aslr_slide = 0;
size_t free_size = (aslr_size - total_size);
if (out_param->flags & svc::CreateProcessFlag_EnableAslr) {
aslr_slide = GenerateSecureRandom(free_size / os::MemoryBlockUnitSize) * os::MemoryBlockUnitSize;
}
/* Set out. */
aslr_start += aslr_slide;
for (size_t i = 0; i < Nso_Count; i++) {
if (ali->has_nso[i]) {
R_UNLESS(util::CanAddWithoutOverflow(out->nso_address[i], aslr_start), ldr::ResultInvalidNso());
out->nso_address[i] += aslr_start;
}
}
if (out->args_address) {
R_UNLESS(util::CanAddWithoutOverflow(out->args_address, aslr_start), ldr::ResultInvalidNso());
out->args_address += aslr_start;
}
out_param->code_address = aslr_start;
out_param->code_num_pages = total_size >> 12;
R_SUCCEED();
}
Result LoadAutoLoadModuleSegment(fs::FileHandle file, const NsoHeader::SegmentInfo *segment, size_t file_size, const u8 *file_hash, bool is_compressed, bool check_hash, uintptr_t map_base, uintptr_t map_end) {
/* Select read size based on compression. */
if (!is_compressed) {
file_size = segment->size;
}
/* Validate size. */
R_UNLESS(file_size <= segment->size, ldr::ResultInvalidNso());
R_UNLESS(segment->size <= std::numeric_limits<s32>::max(), ldr::ResultInvalidNso());
/* Load data from file. */
uintptr_t load_address = is_compressed ? map_end - file_size : map_base;
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, segment->file_offset, reinterpret_cast<void *>(load_address), file_size));
R_UNLESS(read_size == file_size, ldr::ResultInvalidNso());
/* Uncompress if necessary. */
if (is_compressed) {
bool decompressed = (util::DecompressLZ4(reinterpret_cast<void *>(map_base), segment->size, reinterpret_cast<const void *>(load_address), file_size) == static_cast<int>(segment->size));
R_UNLESS(decompressed, ldr::ResultInvalidNso());
}
/* Check hash if necessary. */
if (check_hash) {
u8 hash[crypto::Sha256Generator::HashSize];
crypto::GenerateSha256(hash, sizeof(hash), reinterpret_cast<void *>(map_base), segment->size);
R_UNLESS(std::memcmp(hash, file_hash, sizeof(hash)) == 0, ldr::ResultInvalidNso());
}
R_SUCCEED();
}
Result LoadAutoLoadModule(os::NativeHandle process_handle, fs::FileHandle file, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size) {
/* Map and read data from file. */
{
/* Map the process memory. */
void *mapped_memory = nullptr;
R_TRY(os::MapProcessMemory(std::addressof(mapped_memory), process_handle, nso_address, nso_size, GenerateSecureRandom));
ON_SCOPE_EXIT { os::UnmapProcessMemory(mapped_memory, process_handle, nso_address, nso_size); };
const uintptr_t map_address = reinterpret_cast<uintptr_t>(mapped_memory);
/* Load NSO segments. */
R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Text]), nso_header->text_compressed_size, nso_header->text_hash, (nso_header->flags & NsoHeader::Flag_CompressedText) != 0,
(nso_header->flags & NsoHeader::Flag_CheckHashText) != 0, map_address + nso_header->text_dst_offset, map_address + nso_size));
R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Ro]), nso_header->ro_compressed_size, nso_header->ro_hash, (nso_header->flags & NsoHeader::Flag_CompressedRo) != 0,
(nso_header->flags & NsoHeader::Flag_CheckHashRo) != 0, map_address + nso_header->ro_dst_offset, map_address + nso_size));
R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Rw]), nso_header->rw_compressed_size, nso_header->rw_hash, (nso_header->flags & NsoHeader::Flag_CompressedRw) != 0,
(nso_header->flags & NsoHeader::Flag_CheckHashRw) != 0, map_address + nso_header->rw_dst_offset, map_address + nso_size));
/* Clear unused space to zero. */
const size_t text_end = static_cast<size_t>(nso_header->text_dst_offset) + static_cast<size_t>(nso_header->text_size);
const size_t ro_end = static_cast<size_t>(nso_header->ro_dst_offset) + static_cast<size_t>(nso_header->ro_size);
const size_t rw_end = static_cast<size_t>(nso_header->rw_dst_offset) + static_cast<size_t>(nso_header->rw_size);
std::memset(reinterpret_cast<void *>(map_address + 0), 0, nso_header->text_dst_offset);
std::memset(reinterpret_cast<void *>(map_address + text_end), 0, nso_header->ro_dst_offset - text_end);
std::memset(reinterpret_cast<void *>(map_address + ro_end), 0, nso_header->rw_dst_offset - ro_end);
std::memset(reinterpret_cast<void *>(map_address + rw_end), 0, nso_header->bss_size);
/* Apply embedded patches. */
ApplyEmbeddedPatchesToModule(nso_header->module_id, map_address, nso_size);
/* Apply IPS patches. */
LocateAndApplyIpsPatchesToModule(nso_header->module_id, map_address, nso_size);
/* Apply PCV and PTM patches */
if (g_is_pcv) {
hoc::pcv::Patch(map_address, nso_size);
}
if (g_is_ptm) {
hoc::ptm::Patch(map_address, nso_size);
}
}
/* Set permissions. */
const size_t text_size = util::AlignUp(nso_header->text_size, os::MemoryPageSize);
const size_t ro_size = util::AlignUp(nso_header->ro_size, os::MemoryPageSize);
const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize);
if (text_size) {
const bool prevent_code_reads = (nso_header->flags & NsoHeader::Flag_PreventCodeReads);
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->text_dst_offset, text_size, prevent_code_reads ? os::MemoryPermission_ExecuteOnly : os::MemoryPermission_ReadExecute));
}
if (ro_size) {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->ro_dst_offset, ro_size, os::MemoryPermission_ReadOnly));
}
if (rw_size) {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->rw_dst_offset, rw_size, os::MemoryPermission_ReadWrite));
}
R_SUCCEED();
}
Result LoadAutoLoadModules(const ProcessInfo *process_info, const NsoHeader *nso_headers, const AutoLoadModuleInfo *ali, const ArgumentStore::Entry *argument) {
/* Load each NSO. */
for (size_t i = 0; i < Nso_Count; i++) {
if (ali->has_nso[i]) {
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(file); };
R_TRY(LoadAutoLoadModule(process_info->process_handle, file, nso_headers + i, process_info->nso_address[i], process_info->nso_size[i]));
}
}
/* Load arguments, if present. */
if (argument != nullptr) {
/* Write argument data into memory. */
{
void *map_address = nullptr;
R_TRY(os::MapProcessMemory(std::addressof(map_address), process_info->process_handle, process_info->args_address, process_info->args_size, GenerateSecureRandom));
ON_SCOPE_EXIT { os::UnmapProcessMemory(map_address, process_info->process_handle, process_info->args_address, process_info->args_size); };
ProgramArguments *args = static_cast<ProgramArguments *>(map_address);
std::memset(args, 0, sizeof(*args));
args->allocated_size = process_info->args_size;
args->arguments_size = argument->argument_size;
std::memcpy(args->arguments, argument->argument, argument->argument_size);
}
/* Set argument region permissions. */
/* NOTE: Nintendo uses svc::SetProcessMemoryPermission directly here. */
R_TRY(os::SetProcessMemoryPermission(process_info->process_handle, process_info->args_address, process_info->args_size, os::MemoryPermission_ReadWrite));
}
R_SUCCEED();
}
Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const NsoHeader *nso_headers, const AutoLoadModuleInfo *ali, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) {
/* Get CreateProcessParameter. */
svc::CreateProcessParameter param;
R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit));
/* Decide on an NSO layout. */
R_TRY(DecideAddressSpaceLayout(out, std::addressof(param), nso_headers, ali, argument));
/* Actually create process. */
svc::Handle process_handle;
R_TRY(svc::CreateProcess(std::addressof(process_handle), std::addressof(param), static_cast<const u32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(u32)));
/* Set the output handle, and ensure that if we fail after this point we clean it up. */
out->process_handle = process_handle;
ON_RESULT_FAILURE { svc::CloseHandle(process_handle); };
/* Load all auto load modules. */
R_RETURN(LoadAutoLoadModules(out, nso_headers, ali, argument));
}
}
/* Process Creation API. */
Result CreateProcess(os::NativeHandle *out, PinId pin_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status, const char *path, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit, const ldr::ProgramAttributes &attrs) {
/* Mount code. */
AMS_UNUSED(path);
ScopedCodeMountForCode mount(loc, override_status, attrs);
R_TRY(mount.GetResult());
/* Load meta, possibly from cache. */
Meta meta;
R_TRY(LoadMetaFromCache(std::addressof(meta), loc, override_status, attrs.platform));
/* Validate meta. */
R_TRY(ValidateMeta(std::addressof(meta), loc, mount.GetCodeVerificationData()));
/* If we should, load/validate the browser core dll. */
util::optional<ScopedCodeMountForBrowserCoreDll> bdll_mount;
if ((meta.acid->flags & Acid::AcidFlag_LoadBrowserCoreDll)) {
/* NOTE: I'm unsure whether we should be getting a fresh override status (allowing for different override between main and bdll?) */
/* or whether we should be using the main override status. Going to go with main, for sanity's sake. */
/* Also noting that Nintendo always passes ProgramAttributes=0 here, but this "should" be different on Ounce? */
/* Kind of unclear how to handle this without knowing what exactly is being ifdef'd. */
const ncm::ProgramLocation bdll_loc = ncm::ProgramLocation::Make(ncm::SystemProgramId::BrowserCoreDll, ncm::StorageId::BuiltInSystem);
const cfg::OverrideStatus bdll_override_status = override_status;
const ldr::ProgramAttributes bdll_attrs = attrs;
bdll_mount.emplace(bdll_loc, bdll_override_status, bdll_attrs);
R_TRY(bdll_mount->GetResult());
/* Load browser dll meta, possibly from cache. */
Meta bdll_meta;
R_TRY(LoadMetaFromCacheForBrowserCoreDll(std::addressof(bdll_meta), bdll_loc, bdll_override_status, bdll_attrs.platform));
/* Validate browser dll meta. */
R_TRY(ValidateMeta(std::addressof(bdll_meta), loc, mount.GetCodeVerificationData()));
}
/* Load, validate NSO headers. */
AutoLoadModuleInfo auto_load_info = {};
R_TRY(LoadAutoLoadHeaders(g_nso_headers, std::addressof(auto_load_info), meta.acid->flags));
R_TRY(CheckAutoLoad(g_nso_headers, std::addressof(auto_load_info), meta.acid->flags));
/* Actually create the process and load NSOs into process memory. */
ProcessInfo info;
R_TRY(CreateProcessAndLoadAutoLoadModules(std::addressof(info), std::addressof(meta), g_nso_headers, std::addressof(auto_load_info), argument, flags, resource_limit));
/* Register NSOs with the RoManager. */
{
/* Nintendo doesn't validate this get, but we do. */
os::ProcessId process_id = os::GetProcessId(info.process_handle);
/* Register new process. */
const auto as_type = GetAddressSpaceType(std::addressof(meta));
RoManager::GetInstance().RegisterProcess(pin_id, process_id, meta.aci->program_id, as_type == Npdm::AddressSpaceType_64Bit || as_type == Npdm::AddressSpaceType_64BitDeprecated);
/* Register all NSOs. */
for (size_t i = 0; i < Nso_Count; i++) {
if (auto_load_info.has_nso[i]) {
RoManager::GetInstance().AddNso(pin_id, g_nso_headers[i].module_id, info.nso_address[i], info.nso_size[i]);
}
}
}
/* If we're overriding for HBL, perform HTML document redirection. */
if (override_status.IsHbl()) {
/* Don't validate result, failure is okay. */
RedirectHtmlDocumentPathForHbl(loc);
}
/* Clear the external code for the program. */
fssystem::DestroyExternalCode(loc.program_id);
/* Note that we've created the program. */
SetLaunchedBootProgram(loc.program_id);
/* Move the process handle to output. */
*out = info.process_handle;
R_SUCCEED();
}
Result GetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, const char *path, const ldr::ProgramAttributes &attrs) {
Meta meta;
/* Load Meta. */
{
AMS_UNUSED(path);
ScopedCodeMountForCode mount(loc, attrs);
R_TRY(mount.GetResult());
R_TRY(LoadMeta(std::addressof(meta), loc, mount.GetOverrideStatus(), attrs.platform, false));
if (out_status != nullptr) {
*out_status = mount.GetOverrideStatus();
}
}
return GetProgramInfoFromMeta(out, std::addressof(meta));
}
Result PinProgram(PinId *out_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status) {
R_UNLESS(RoManager::GetInstance().Allocate(out_id, loc, override_status), ldr::ResultMaxProcess());
R_SUCCEED();
}
Result UnpinProgram(PinId id) {
R_UNLESS(RoManager::GetInstance().Free(id), ldr::ResultNotPinned());
R_SUCCEED();
}
Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out, size_t max_out_count, os::ProcessId process_id) {
R_UNLESS(RoManager::GetInstance().GetProcessModuleInfo(out_count, out, max_out_count, process_id), ldr::ResultNotPinned());
R_SUCCEED();
}
Result GetProgramLocationAndOverrideStatusFromPinId(ncm::ProgramLocation *out, cfg::OverrideStatus *out_status, PinId pin_id) {
R_UNLESS(RoManager::GetInstance().GetProgramLocationAndStatus(out, out_status, pin_id), ldr::ResultNotPinned());
R_SUCCEED();
}
}

View File

@@ -0,0 +1,938 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "ldr_capabilities.hpp"
#include "ldr_content_management.hpp"
#include "ldr_development_manager.hpp"
#include "ldr_launch_record.hpp"
#include "ldr_meta.hpp"
#include "ldr_patcher.hpp"
#include "ldr_process_creation.hpp"
#include "ldr_ro_manager.hpp"
#include "oc/oc_loader.hpp"
namespace ams::ldr {
namespace {
/* Convenience defines. */
constexpr size_t SystemResourceSizeMax = 0x1FE00000;
constexpr size_t AutoLoadModuleSizeMax = 0x800000000;
/* Types. */
enum NsoIndex {
Nso_Rtld = 0,
Nso_Main = 1,
Nso_Wkc0 = 2,
Nso_Wkc1 = 3,
Nso_Wkc2 = 4,
Nso_Wkc3 = 5,
Nso_Wkc4 = 6,
Nso_Wkc5 = 7,
Nso_Wkc6 = 8,
Nso_Wkc7 = 9,
Nso_Wkc8 = 10,
Nso_Wkc9 = 11,
Nso_SubSdk0 = 12,
Nso_SubSdk1 = 13,
Nso_SubSdk2 = 14,
Nso_SubSdk3 = 15,
Nso_SubSdk4 = 16,
Nso_SubSdk5 = 17,
Nso_SubSdk6 = 18,
Nso_SubSdk7 = 19,
Nso_SubSdk8 = 20,
Nso_SubSdk9 = 21,
Nso_Sdk = 22,
Nso_Count,
};
constexpr inline const char *NsoPaths[Nso_Count] = {
ENCODE_ATMOSPHERE_CODE_PATH("/rtld"),
ENCODE_ATMOSPHERE_CODE_PATH("/main"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc0"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc1"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc2"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc3"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc4"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc5"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc6"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc7"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc8"),
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc9"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk0"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk1"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk2"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk3"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk4"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk5"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk6"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk7"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk8"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk9"),
ENCODE_ATMOSPHERE_CODE_PATH("/sdk"),
};
constexpr const char *GetNsoPath(size_t idx) {
AMS_ABORT_UNLESS(idx < Nso_Count);
return NsoPaths[idx];
}
struct ProcessInfo {
os::NativeHandle process_handle;
uintptr_t code_address;
size_t total_size;
uintptr_t args_address;
size_t args_size;
uintptr_t nso_address[Nso_Count];
size_t nso_size[Nso_Count];
};
struct AutoLoadModuleInfo {
bool has_rtld;
bool has_main;
bool has_sdk;
bool has_subsdk;
s8 nso_indices[Nso_Count];
};
struct AutoLoadModuleContext {
NsoHeader *headers;
int nso_count;
int rtld_idx;
int main_nso_idx;
int sdk_nso_idx;
AutoLoadModuleInfo ali;
};
/* Global NSO header cache. */
NsoHeader g_nso_headers[Nso_Count];
/* Pcv/Ptm check cache */
bool g_is_pcv;
bool g_is_ptm;
/* Global Zstd decompression context. */
constexpr size_t ZstdDctxWorkspaceSize = 0x176E8;
alignas(8) u8 g_zstd_dctx_workspace[ZstdDctxWorkspaceSize];
Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {
/* No version verification is done before 8.1.0. */
R_SUCCEED_IF(hos::GetVersion() < hos::Version_8_1_0);
/* No verification is done if development. */
R_SUCCEED_IF(IsDevelopmentForAntiDowngradeCheck());
/* TODO: Anti-downgrade checking does not make very much sense for us. Should we do anything? */
AMS_UNUSED(program_id, version);
R_SUCCEED();
}
/* Helpers. */
Result GetProgramInfoFromMeta(ProgramInfo *out, const Meta *meta) {
/* Copy basic info. */
out->main_thread_priority = meta->npdm->main_thread_priority;
out->default_cpu_id = meta->npdm->default_cpu_id;
out->main_thread_stack_size = meta->npdm->main_thread_stack_size;
out->program_id = meta->aci->program_id;
/* Copy access controls. */
size_t offset = 0;
#define COPY_ACCESS_CONTROL(source, which) \
({ \
const size_t size = meta->source->which##_size; \
R_UNLESS(offset + size <= sizeof(out->ac_buffer), ldr::ResultInternalError()); \
out->source##_##which##_size = size; \
std::memcpy(out->ac_buffer + offset, meta->source##_##which, size); \
offset += size; \
})
/* Copy all access controls to buffer. */
COPY_ACCESS_CONTROL(acid, sac);
COPY_ACCESS_CONTROL(aci, sac);
COPY_ACCESS_CONTROL(acid, fac);
COPY_ACCESS_CONTROL(aci, fah);
#undef COPY_ACCESS_CONTROL
/* Copy flags. */
out->flags = MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32));
R_SUCCEED();
}
bool IsApplet(const Meta *meta) {
return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Applet;
}
bool IsApplication(const Meta *meta) {
return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Application;
}
Npdm::AddressSpaceType GetAddressSpaceType(const Meta *meta) {
return static_cast<Npdm::AddressSpaceType>((meta->npdm->flags & Npdm::MetaFlag_AddressSpaceTypeMask) >> Npdm::MetaFlag_AddressSpaceTypeShift);
}
Acid::PoolPartition GetPoolPartition(const Meta *meta) {
return static_cast<Acid::PoolPartition>((meta->acid->flags & Acid::AcidFlag_PoolPartitionMask) >> Acid::AcidFlag_PoolPartitionShift);
}
Result LoadAutoLoadHeaders(AutoLoadModuleContext &ctx, u32 acid_flags) {
/* Clear NSOs. */
std::memset(g_nso_headers, 0, sizeof(g_nso_headers));
ctx.headers = g_nso_headers;
ctx.nso_count = 0;
ctx.rtld_idx = -1;
ctx.main_nso_idx = -1;
ctx.sdk_nso_idx = -1;
ctx.ali = {};
for (size_t i = 0; i < Nso_Count; i++) {
/* Only load browser DLLs if acid flags say to do so. */
switch (i) {
case Nso_Wkc0:
case Nso_Wkc1:
case Nso_Wkc2:
case Nso_Wkc3:
case Nso_Wkc4:
case Nso_Wkc5:
case Nso_Wkc6:
case Nso_Wkc7:
case Nso_Wkc8:
case Nso_Wkc9:
if ((acid_flags & Acid::AcidFlag_LoadBrowserCoreDll) == 0) {
continue;
}
break;
}
fs::FileHandle file;
if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read))) {
ON_SCOPE_EXIT { fs::CloseFile(file); };
/* Read NSO header. */
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, 0, g_nso_headers + ctx.nso_count, sizeof(NsoHeader)));
R_UNLESS(read_size == sizeof(NsoHeader), ldr::ResultInvalidNso());
/* Note nso is present. */
switch (i) {
case Nso_Rtld:
ctx.rtld_idx = ctx.nso_count;
ctx.ali.has_rtld = true;
break;
case Nso_Main:
ctx.main_nso_idx = ctx.nso_count;
ctx.ali.has_main = true;
break;
case Nso_SubSdk0:
case Nso_SubSdk1:
case Nso_SubSdk2:
case Nso_SubSdk3:
case Nso_SubSdk4:
case Nso_SubSdk5:
case Nso_SubSdk6:
case Nso_SubSdk7:
case Nso_SubSdk8:
case Nso_SubSdk9:
ctx.ali.has_subsdk = true;
break;
case Nso_Sdk:
ctx.sdk_nso_idx = ctx.nso_count;
ctx.ali.has_sdk = true;
break;
}
ctx.ali.nso_indices[ctx.nso_count] = static_cast<s8>(i);
ctx.nso_count++;
}
}
R_SUCCEED();
}
Result CheckAutoLoad(const AutoLoadModuleContext &ctx, u32 acid_flags) {
/* We must always have a main. */
R_UNLESS(ctx.ali.has_main, ldr::ResultInvalidNso());
/* Validate flags and extents for all present NSOs. */
for (int i = 0; i < ctx.nso_count; ++i) {
const auto &hdr = ctx.headers[i];
/* All NSOs must not be --X. */
/* This is "probably" not checked on Ounce? */
R_UNLESS((hdr.flags & NsoHeader::Flag_PreventCodeReads) == 0, ldr::ResultInvalidNso());
/* Zstd compression only allowed on main, and only when both rtld+sdk are present. */
if (i != ctx.main_nso_idx || ctx.rtld_idx < 0 || ctx.sdk_nso_idx < 0) {
R_UNLESS((hdr.flags & NsoHeader::Flag_UseZbicCompression) == 0, ldr::ResultInvalidNso());
}
/* NSOs must have page-aligned segments. */
R_UNLESS(util::IsAligned(hdr.text_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
R_UNLESS(util::IsAligned(hdr.ro_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
R_UNLESS(util::IsAligned(hdr.rw_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
/* NSOs must have zero text offset. */
R_UNLESS(hdr.text_dst_offset == 0, ldr::ResultInvalidNso());
/* NSO .text must precede .rodata. */
const size_t text_end = static_cast<size_t>(hdr.text_dst_offset) + static_cast<size_t>(hdr.text_size);
R_UNLESS(text_end <= static_cast<size_t>(hdr.ro_dst_offset), ldr::ResultInvalidNso());
/* NSO .rodata must precede .rwdata. */
const size_t ro_end = static_cast<size_t>(hdr.ro_dst_offset) + static_cast<size_t>(hdr.ro_size);
R_UNLESS(ro_end <= static_cast<size_t>(hdr.rw_dst_offset), ldr::ResultInvalidNso());
}
const bool has_browser_dll = (acid_flags & Acid::AcidFlag_LoadBrowserCoreDll) != 0;
if (ctx.ali.has_rtld || ctx.ali.has_sdk) {
/* If we have sdk we must have rtld. */
R_UNLESS(ctx.ali.has_rtld, ldr::ResultInvalidNso());
/* If we have rtld, we must not have browser core dll. */
R_UNLESS(!has_browser_dll, ldr::ResultInvalidNso());
} else {
/* We must not have both subsdk and browser dll. */
R_UNLESS(!(ctx.ali.has_subsdk && has_browser_dll), ldr::ResultInvalidNso());
}
R_SUCCEED();
}
constexpr const ncm::ProgramId UnqualifiedApprovalProgramIds[] = {
{ 0x010003F003A34000 }, /* Pokemon: Let's Go, Pikachu! */
{ 0x0100152000022000 }, /* Mario Kart 8 Deluxe */
{ 0x0100165003504000 }, /* Nintendo Labo Toy-Con 04: VR Kit */
{ 0x0100187003A36000 }, /* Pokemon: Let's Go, Eevee! */
{ 0x01002E5008C56000 }, /* Pokemon Sword [Live Tournament] */
{ 0x01002FF008C24000 }, /* Ring Fit Adventure */
{ 0x010049900F546001 }, /* Super Mario 3D All-Stars: Super Mario 64 */
{ 0x010057D00ECE4000 }, /* Nintendo Switch Online (Nintendo 64) [for Japan] */
{ 0x01006F8002326000 }, /* Animal Crossing: New Horizons */
{ 0x01006FB00F50E000 }, /* 宝可梦 走吧!伊布 [Pokemon: Let's Go, Eevee! for China] */
{ 0x010070300F50C000 }, /* 宝可梦 走吧!皮卡丘 [Pokemon: Let's Go, Pikachu! for China] */
{ 0x010075100E8EC000 }, /* 马力欧卡丁车8 豪华版 [Mario Kart 8 Deluxe for China] */
{ 0x01008DB008C2C000 }, /* Pokemon Shield */
{ 0x01009AD008C4C000 }, /* Pokemon: Let's Go, Pikachu! [Kiosk] */
{ 0x0100A66003384000 }, /* Hulu */
{ 0x0100ABF008968000 }, /* Pokemon Sword */
{ 0x0100C9A00ECE6000 }, /* Nintendo Switch Online (Nintendo 64) [for America] */
{ 0x0100ED100BA3A000 }, /* Mario Kart Live: Home Circuit */
{ 0x0100F38011CFE000 }, /* Animal Crossing: New Horizons Island Transfer Tool */
{ 0x0100F6B011028000 }, /* 健身环大冒险 [Ring Fit Adventure for China] */
};
/* Check that the unqualified approval programs are sorted. */
static_assert([]() -> bool {
for (size_t i = 0; i < util::size(UnqualifiedApprovalProgramIds) - 1; ++i) {
if (UnqualifiedApprovalProgramIds[i].value >= UnqualifiedApprovalProgramIds[i + 1].value) {
return false;
}
}
return true;
}());
bool IsUnqualifiedApprovalProgramId(ncm::ProgramId program_id) {
/* Check if the program id is one with unqualified approval. */
return std::binary_search(std::begin(UnqualifiedApprovalProgramIds), std::end(UnqualifiedApprovalProgramIds), program_id);
}
bool IsUnqualifiedApproval(const Meta *meta) {
/* If the meta has unqualified approval flag, it's unqualified approval. */
if (meta->acid->flags & ldr::Acid::AcidFlag_UnqualifiedApproval) {
return true;
}
/* If the unqualified approval flag is not set, the program must be an application. */
if (!IsApplication(meta)) {
return false;
}
/* The program id must be a force unqualified approval program id. */
return IsUnqualifiedApprovalProgramId(meta->acid->program_id_min) && meta->acid->program_id_min == meta->acid->program_id_max;
}
Result ValidateMeta(const Meta *meta, const ncm::ProgramLocation &loc, const fs::CodeVerificationData &code_verification_data) {
/* Validate version. */
R_TRY(ValidateProgramVersion(loc.program_id, meta->npdm->version));
/* Validate program id. */
R_UNLESS(meta->aci->program_id >= meta->acid->program_id_min, ldr::ResultInvalidProgramId());
R_UNLESS(meta->aci->program_id <= meta->acid->program_id_max, ldr::ResultInvalidProgramId());
/* Validate the kernel capabilities. */
R_TRY(TestCapability(static_cast<const util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32), static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)));
/* Check if NCA is PCV or PTM */
g_is_pcv = meta->aci->program_id == ncm::SystemProgramId::Pcv;
g_is_ptm = meta->aci->program_id == ncm::SystemProgramId::Ptm;
/* If we have data to validate, validate it. */
if (meta->check_verification_data) {
const u8 *sig = code_verification_data.signature;
const size_t sig_size = sizeof(code_verification_data.signature);
const u8 *mod = static_cast<u8 *>(meta->modulus);
const size_t mod_size = crypto::Rsa2048PssSha256Verifier::ModulusSize;
const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent();
const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize;
const u8 *hsh = code_verification_data.target_hash;
const size_t hsh_size = sizeof(code_verification_data.target_hash);
const bool is_signature_valid = crypto::VerifyRsa2048PssSha256WithHash(sig, sig_size, mod, mod_size, exp, exp_size, hsh, hsh_size);
/* If the signature check fails, we need to check if this is allowable. */
if (!is_signature_valid) {
/* We have to enforce signature checks on prod and when we have a signature to check on dev. */
R_UNLESS(IsDevelopmentForAcidProductionCheck(), ldr::ResultInvalidNcaSignature());
R_UNLESS(!code_verification_data.has_data, ldr::ResultInvalidNcaSignature());
/* There was no signature to check on dev. Check if this is acceptable. */
R_UNLESS(IsUnqualifiedApproval(meta), ldr::ResultInvalidNcaSignature());
}
}
/* All good. */
R_SUCCEED();
}
Result GetCreateProcessFlags(u32 *out, const Meta *meta, const u32 ldr_flags) {
const u8 meta_flags = meta->npdm->flags;
u32 flags = 0;
/* Set Is64Bit. */
if (meta_flags & Npdm::MetaFlag_Is64Bit) {
flags |= svc::CreateProcessFlag_Is64Bit;
}
/* Set AddressSpaceType. */
switch (GetAddressSpaceType(meta)) {
case Npdm::AddressSpaceType_32Bit:
flags |= svc::CreateProcessFlag_AddressSpace32Bit;
break;
case Npdm::AddressSpaceType_64BitDeprecated:
flags |= svc::CreateProcessFlag_AddressSpace64BitDeprecated;
break;
case Npdm::AddressSpaceType_32BitWithoutAlias:
flags |= svc::CreateProcessFlag_AddressSpace32BitWithoutAlias;
break;
case Npdm::AddressSpaceType_64Bit:
flags |= svc::CreateProcessFlag_AddressSpace64Bit;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
}
/* Set Enable Debug. */
if (ldr_flags & CreateProcessFlag_EnableDebug) {
flags |= svc::CreateProcessFlag_EnableDebug;
}
/* Set Enable ASLR. */
if (!(ldr_flags & CreateProcessFlag_DisableAslr)) {
flags |= svc::CreateProcessFlag_EnableAslr;
}
/* Set Is Application. */
if (IsApplication(meta)) {
flags |= svc::CreateProcessFlag_IsApplication;
/* 7.0.0+: Set OptimizeMemoryAllocation if relevant. */
if (hos::GetVersion() >= hos::Version_7_0_0) {
if (meta_flags & Npdm::MetaFlag_OptimizeMemoryAllocation) {
flags |= svc::CreateProcessFlag_OptimizeMemoryAllocation;
}
}
}
/* 5.0.0+ Set Pool Partition. */
if (hos::GetVersion() >= hos::Version_5_0_0) {
/* TODO: Nintendo no longer accepts Applet when pool partition == application. Would this break hbl/anything else in the hb ecosystem? */
/* TODO: Nintendo uses a helper bool MakeSvcPoolPartitionFlag(u32 *out, Acid::PoolPartition partition); */
switch (GetPoolPartition(meta)) {
case Acid::PoolPartition_Application:
if (IsApplet(meta)) {
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
} else {
flags |= svc::CreateProcessFlag_PoolPartitionApplication;
}
break;
case Acid::PoolPartition_Applet:
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
break;
case Acid::PoolPartition_System:
flags |= svc::CreateProcessFlag_PoolPartitionSystem;
break;
case Acid::PoolPartition_SystemNonSecure:
flags |= svc::CreateProcessFlag_PoolPartitionSystemNonSecure;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
}
} else if (hos::GetVersion() >= hos::Version_4_0_0) {
/* On 4.0.0+, the corresponding bit was simply "UseSecureMemory". */
if (meta->acid->flags & Acid::AcidFlag_DeprecatedUseSecureMemory) {
flags |= svc::CreateProcessFlag_DeprecatedUseSecureMemory;
}
}
/* 11.0.0+/meso Set Disable DAS merge. */
if (meta_flags & Npdm::MetaFlag_DisableDeviceAddressSpaceMerge) {
flags |= svc::CreateProcessFlag_DisableDeviceAddressSpaceMerge;
}
/* 18.0.0+/meso Set Alias region extra size. */
if (meta_flags & Npdm::MetaFlag_EnableAliasRegionExtraSize) {
flags |= svc::CreateProcessFlag_EnableAliasRegionExtraSize;
}
*out = flags;
R_SUCCEED();
}
Result GetCreateProcessParameter(svc::CreateProcessParameter *out, const Meta *meta, u32 flags, os::NativeHandle resource_limit) {
/* Clear output. */
std::memset(out, 0, sizeof(*out));
/* Set name, version, program id, resource limit handle. */
std::memcpy(out->name, meta->npdm->program_name, sizeof(out->name) - 1);
out->version = meta->npdm->version;
out->program_id = meta->aci->program_id.value;
out->reslimit = resource_limit;
/* Set flags. */
R_TRY(GetCreateProcessFlags(std::addressof(out->flags), meta, flags));
/* 3.0.0+ System Resource Size. */
if (hos::GetVersion() >= hos::Version_3_0_0) {
/* Validate size is aligned. */
R_UNLESS(util::IsAligned(meta->npdm->system_resource_size, os::MemoryBlockUnitSize), ldr::ResultInvalidSize());
/* Validate system resource usage. */
if (meta->npdm->system_resource_size) {
/* Process must be 64-bit. */
R_UNLESS((out->flags & svc::CreateProcessFlag_AddressSpace64Bit), ldr::ResultInvalidMeta());
/* Process must be application or applet. */
R_UNLESS(IsApplication(meta) || IsApplet(meta), ldr::ResultInvalidMeta());
/* Size must be less than or equal to max. */
R_UNLESS(meta->npdm->system_resource_size <= SystemResourceSizeMax, ldr::ResultInvalidMeta());
}
out->system_resource_num_pages = meta->npdm->system_resource_size >> 12;
}
R_SUCCEED();
}
u64 GenerateSecureRandom(u64 max) {
/* Generate a cryptographically random number. */
u64 rand;
crypto::GenerateCryptographicallyRandomBytes(std::addressof(rand), sizeof(rand));
/* Coerce into range. */
return rand % (max + 1);
}
Result DecideAddressSpaceLayout(ProcessInfo *out, svc::CreateProcessParameter *out_param, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument) {
/* Clear output. */
out->args_address = 0;
out->args_size = 0;
std::memset(out->nso_address, 0, sizeof(out->nso_address));
std::memset(out->nso_size, 0, sizeof(out->nso_size));
size_t total_size = 0;
bool argument_allocated = false;
/* Calculate base offsets. */
for (int i = 0; i < ctx.nso_count; i++) {
out->nso_address[i] = total_size;
const size_t text_end = static_cast<size_t>(ctx.headers[i].text_dst_offset) + static_cast<size_t>(ctx.headers[i].text_size);
const size_t ro_end = static_cast<size_t>(ctx.headers[i].ro_dst_offset) + static_cast<size_t>(ctx.headers[i].ro_size);
const size_t rw_end = static_cast<size_t>(ctx.headers[i].rw_dst_offset) + static_cast<size_t>(ctx.headers[i].rw_size);
out->nso_size[i] = text_end;
out->nso_size[i] = std::max(out->nso_size[i], ro_end);
out->nso_size[i] = std::max(out->nso_size[i], rw_end);
out->nso_size[i] += static_cast<size_t>(ctx.headers[i].bss_size);
const size_t aligned_up_size = util::AlignUp(out->nso_size[i], os::MemoryPageSize) & (AutoLoadModuleSizeMax - 1);
R_UNLESS(out->nso_size[i] <= aligned_up_size, ldr::ResultInvalidNso());
R_UNLESS(aligned_up_size > 0, ldr::ResultInvalidNso());
out->nso_size[i] = aligned_up_size;
R_UNLESS(util::CanAddWithoutOverflow(total_size, out->nso_size[i]), ldr::ResultInvalidNso());
total_size += out->nso_size[i];
if (!argument_allocated && argument != nullptr) {
out->args_address = total_size;
out->args_size = util::AlignUp(2 * sizeof(u32) + argument->argument_size * 2 + ArgumentStore::ArgumentBufferSize, os::MemoryPageSize);
R_UNLESS(util::CanAddWithoutOverflow(total_size, out->args_size), ldr::ResultInvalidNso());
total_size += out->args_size;
argument_allocated = true;
}
}
/* Calculate ASLR. */
uintptr_t aslr_start = 0;
size_t aslr_size = 0;
if (hos::GetVersion() >= hos::Version_2_0_0) {
switch (out_param->flags & svc::CreateProcessFlag_AddressSpaceMask) {
case svc::CreateProcessFlag_AddressSpace32Bit:
case svc::CreateProcessFlag_AddressSpace32BitWithoutAlias:
aslr_start = svc::AddressSmallMap32Start;
aslr_size = svc::AddressSmallMap32Size;
break;
case svc::CreateProcessFlag_AddressSpace64BitDeprecated:
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
break;
case svc::CreateProcessFlag_AddressSpace64Bit:
aslr_start = svc::AddressMap39Start;
aslr_size = svc::AddressMap39Size;
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
} else {
/* On 1.0.0, only 2 address space types existed. */
if (out_param->flags & svc::CreateProcessFlag_AddressSpace64BitDeprecated) {
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
} else {
aslr_start = svc::AddressSmallMap32Start;
aslr_size = svc::AddressSmallMap32Size;
}
}
R_UNLESS(total_size <= aslr_size, svc::ResultOutOfMemory());
/* Set Create Process output. */
uintptr_t aslr_slide = 0;
size_t free_size = (aslr_size - total_size);
if (out_param->flags & svc::CreateProcessFlag_EnableAslr) {
aslr_slide = GenerateSecureRandom(free_size / os::MemoryBlockUnitSize) * os::MemoryBlockUnitSize;
}
/* Set out. */
aslr_start += aslr_slide;
for (int i = 0; i < ctx.nso_count; i++) {
R_UNLESS(util::CanAddWithoutOverflow(out->nso_address[i], aslr_start), ldr::ResultInvalidNso());
out->nso_address[i] += aslr_start;
}
if (out->args_address) {
R_UNLESS(util::CanAddWithoutOverflow(out->args_address, aslr_start), ldr::ResultInvalidNso());
out->args_address += aslr_start;
}
out_param->code_address = aslr_start;
out_param->code_num_pages = total_size >> 12;
out->total_size = total_size;
R_SUCCEED();
}
Result LoadAutoLoadModuleSegment(fs::FileHandle file, size_t file_offset, size_t compressed_size, size_t segment_size, bool is_compressed, bool is_zstd, uintptr_t map_base, uintptr_t map_end) {
/* Select read size based on compression. */
size_t file_size = is_compressed ? compressed_size : segment_size;
/* Validate size. */
R_UNLESS(file_size <= segment_size, ldr::ResultInvalidNso());
R_UNLESS(file_size <= std::numeric_limits<s32>::max(), ldr::ResultInvalidNso());
R_UNLESS(segment_size <= std::numeric_limits<s32>::max(), ldr::ResultInvalidNso());
/* Load data from file. */
uintptr_t load_address = is_compressed ? map_end - compressed_size : map_base;
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, file_offset, reinterpret_cast<void *>(load_address), file_size));
R_UNLESS(read_size == file_size, ldr::ResultInvalidNso());
/* Uncompress if necessary. */
R_SUCCEED_IF(!is_compressed);
auto compressed_data_buf = reinterpret_cast<const void *>(load_address);
if (is_zstd) {
bool decompressed = util::DecompressZstdForLoader(reinterpret_cast<void *>(g_zstd_dctx_workspace), ZstdDctxWorkspaceSize, reinterpret_cast<void *>(map_base), static_cast<size_t>(map_end - map_base), segment_size, compressed_data_buf, file_size);
R_UNLESS(decompressed, ldr::ResultInvalidNso());
} else {
bool decompressed = (util::DecompressLZ4(reinterpret_cast<void *>(map_base), segment_size, compressed_data_buf, file_size) == static_cast<int>(segment_size));
R_UNLESS(decompressed, ldr::ResultInvalidNso());
}
R_SUCCEED();
}
Result CheckSegmentHash(const NsoHeader *nso_header, uintptr_t map_address, NsoHeader::Segment segment) {
if ((nso_header->flags & (NsoHeader::Flag_CheckHashText << segment)) == 0) {
R_SUCCEED();
}
u8 hash[crypto::Sha256Generator::HashSize];
crypto::GenerateSha256(hash, sizeof(hash),
reinterpret_cast<void *>(map_address + nso_header->segments[segment].dst_offset),
nso_header->segments[segment].size);
R_UNLESS(std::memcmp(hash, nso_header->segment_hashes[segment], sizeof(hash)) == 0, ldr::ResultInvalidNso());
R_SUCCEED();
}
Result LoadAutoLoadModule(os::NativeHandle process_handle, fs::FileHandle file, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size, size_t map_size) {
const bool is_zstd = (nso_header->flags & NsoHeader::Flag_UseZbicCompression) != 0;
/* Map and read data from file. */
{
/* Map the process memory. */
void *mapped_memory = nullptr;
R_TRY(os::MapProcessMemory(std::addressof(mapped_memory), process_handle, nso_address, map_size, GenerateSecureRandom));
ON_SCOPE_EXIT { os::UnmapProcessMemory(mapped_memory, process_handle, nso_address, map_size); };
const uintptr_t map_address = reinterpret_cast<uintptr_t>(mapped_memory);
const uintptr_t map_end = map_address + map_size;
/* Load NSO segments. */
R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Text].file_offset, nso_header->text_compressed_size, nso_header->text_size,
(nso_header->flags & NsoHeader::Flag_CompressedText) != 0, is_zstd, map_address + nso_header->text_dst_offset, map_end));
R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Ro].file_offset, nso_header->ro_compressed_size, nso_header->ro_size,
(nso_header->flags & NsoHeader::Flag_CompressedRo) != 0, is_zstd, map_address + nso_header->ro_dst_offset, map_end));
R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Rw].file_offset, nso_header->rw_compressed_size, nso_header->rw_size,
(nso_header->flags & NsoHeader::Flag_CompressedRw) != 0, is_zstd, map_address + nso_header->rw_dst_offset, map_end));
/* Clear unused space to zero. */
const size_t text_end = static_cast<size_t>(nso_header->text_dst_offset) + static_cast<size_t>(nso_header->text_size);
const size_t ro_end = static_cast<size_t>(nso_header->ro_dst_offset) + static_cast<size_t>(nso_header->ro_size);
const size_t rw_end = static_cast<size_t>(nso_header->rw_dst_offset) + static_cast<size_t>(nso_header->rw_size);
std::memset(reinterpret_cast<void *>(map_address + text_end), 0, nso_header->ro_dst_offset - text_end);
std::memset(reinterpret_cast<void *>(map_address + ro_end), 0, nso_header->rw_dst_offset - ro_end);
std::memset(reinterpret_cast<void *>(map_address + rw_end), 0, nso_size - rw_end);
/* Check segment hashes. */
R_TRY(CheckSegmentHash(nso_header, map_address, NsoHeader::Segment_Text));
R_TRY(CheckSegmentHash(nso_header, map_address, NsoHeader::Segment_Ro));
R_TRY(CheckSegmentHash(nso_header, map_address, NsoHeader::Segment_Rw));
/* Apply embedded patches. */
ApplyEmbeddedPatchesToModule(nso_header->module_id, map_address, nso_size);
/* Apply IPS patches. */
LocateAndApplyIpsPatchesToModule(nso_header->module_id, map_address, nso_size);
/* Apply PCV and PTM patches */
if (g_is_pcv) {
hoc::pcv::Patch(map_address, nso_size);
}
if (g_is_ptm) {
hoc::ptm::Patch(map_address, nso_size);
}
}
/* Set permissions. */
const size_t text_size = util::AlignUp(nso_header->text_size, os::MemoryPageSize);
const size_t ro_size = util::AlignUp(nso_header->ro_size, os::MemoryPageSize);
const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize);
if (text_size) {
const bool prevent_code_reads = (nso_header->flags & NsoHeader::Flag_PreventCodeReads);
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->text_dst_offset, text_size, prevent_code_reads ? os::MemoryPermission_ExecuteOnly : os::MemoryPermission_ReadExecute));
}
if (ro_size) {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->ro_dst_offset, ro_size, os::MemoryPermission_ReadOnly));
}
if (rw_size) {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->rw_dst_offset, rw_size, os::MemoryPermission_ReadWrite));
}
R_SUCCEED();
}
Result LoadAutoLoadModules(const ProcessInfo *process_info, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument) {
/* Load each NSO. */
const uintptr_t total_end = process_info->code_address + process_info->total_size;
for (int i = 0; i < ctx.nso_count; i++) {
const NsoIndex nso_idx = static_cast<NsoIndex>(ctx.ali.nso_indices[i]);
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), GetNsoPath(nso_idx), fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(file); };
const bool is_zstd = (ctx.headers[i].flags & NsoHeader::Flag_UseZbicCompression) != 0;
const size_t map_size = is_zstd ? (total_end - process_info->nso_address[i]) : process_info->nso_size[i];
R_TRY(LoadAutoLoadModule(process_info->process_handle, file, ctx.headers + i,
process_info->nso_address[i], process_info->nso_size[i], map_size));
}
/* Load arguments, if present. */
if (argument != nullptr) {
/* Write argument data into memory. */
{
void *map_address = nullptr;
R_TRY(os::MapProcessMemory(std::addressof(map_address), process_info->process_handle, process_info->args_address, process_info->args_size, GenerateSecureRandom));
ON_SCOPE_EXIT { os::UnmapProcessMemory(map_address, process_info->process_handle, process_info->args_address, process_info->args_size); };
ProgramArguments *args = static_cast<ProgramArguments *>(map_address);
std::memset(args, 0, sizeof(*args));
args->allocated_size = process_info->args_size;
args->arguments_size = argument->argument_size;
std::memcpy(args->arguments, argument->argument, argument->argument_size);
}
/* Set argument region permissions. */
/* NOTE: Nintendo uses svc::SetProcessMemoryPermission directly here. */
R_TRY(os::SetProcessMemoryPermission(process_info->process_handle, process_info->args_address, process_info->args_size, os::MemoryPermission_ReadWrite));
}
R_SUCCEED();
}
Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) {
/* Get CreateProcessParameter. */
svc::CreateProcessParameter param;
R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit));
/* Decide on an NSO layout. */
R_TRY(DecideAddressSpaceLayout(out, std::addressof(param), ctx, argument));
/* Actually create process. */
svc::Handle process_handle;
R_TRY(svc::CreateProcess(std::addressof(process_handle), std::addressof(param), static_cast<const u32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(u32)));
/* Set the output handle, and ensure that if we fail after this point we clean it up. */
out->process_handle = process_handle;
out->code_address = param.code_address;
ON_RESULT_FAILURE { svc::CloseHandle(process_handle); };
/* Load all auto load modules. */
R_RETURN(LoadAutoLoadModules(out, ctx, argument));
}
}
/* Process Creation API. */
Result CreateProcess(os::NativeHandle *out, PinId pin_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status, const char *path, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit, const ldr::ProgramAttributes &attrs) {
/* Mount code. */
AMS_UNUSED(path);
ScopedCodeMountForCode mount(loc, override_status, attrs);
R_TRY(mount.GetResult());
/* Load meta, possibly from cache. */
Meta meta;
R_TRY(LoadMetaFromCache(std::addressof(meta), loc, override_status, attrs.platform));
/* Validate meta. */
R_TRY(ValidateMeta(std::addressof(meta), loc, mount.GetCodeVerificationData()));
/* If we should, load/validate the browser core dll. */
util::optional<ScopedCodeMountForBrowserCoreDll> bdll_mount;
if ((meta.acid->flags & Acid::AcidFlag_LoadBrowserCoreDll)) {
/* NOTE: I'm unsure whether we should be getting a fresh override status (allowing for different override between main and bdll?) */
/* or whether we should be using the main override status. Going to go with main, for sanity's sake. */
/* Also noting that Nintendo always passes ProgramAttributes=0 here, but this "should" be different on Ounce? */
/* Kind of unclear how to handle this without knowing what exactly is being ifdef'd. */
const ncm::ProgramLocation bdll_loc = ncm::ProgramLocation::Make(ncm::SystemProgramId::BrowserCoreDll, ncm::StorageId::BuiltInSystem);
const cfg::OverrideStatus bdll_override_status = override_status;
const ldr::ProgramAttributes bdll_attrs = attrs;
bdll_mount.emplace(bdll_loc, bdll_override_status, bdll_attrs);
R_TRY(bdll_mount->GetResult());
/* Load browser dll meta, possibly from cache. */
Meta bdll_meta;
R_TRY(LoadMetaFromCacheForBrowserCoreDll(std::addressof(bdll_meta), bdll_loc, bdll_override_status, bdll_attrs.platform));
/* Validate browser dll meta. */
R_TRY(ValidateMeta(std::addressof(bdll_meta), loc, mount.GetCodeVerificationData()));
}
/* Load, validate NSO headers. */
AutoLoadModuleContext ctx;
R_TRY(LoadAutoLoadHeaders(ctx, meta.acid->flags));
R_TRY(CheckAutoLoad(ctx, meta.acid->flags));
/* Actually create the process and load NSOs into process memory. */
ProcessInfo info;
R_TRY(CreateProcessAndLoadAutoLoadModules(std::addressof(info), std::addressof(meta), ctx, argument, flags, resource_limit));
/* Register NSOs with the RoManager. */
{
/* Nintendo doesn't validate this get, but we do. */
os::ProcessId process_id = os::GetProcessId(info.process_handle);
/* Register new process. */
const auto as_type = GetAddressSpaceType(std::addressof(meta));
RoManager::GetInstance().RegisterProcess(pin_id, process_id, meta.aci->program_id, as_type == Npdm::AddressSpaceType_64Bit || as_type == Npdm::AddressSpaceType_64BitDeprecated);
/* Register all NSOs. */
for (int i = 0; i < ctx.nso_count; i++) {
RoManager::GetInstance().AddNso(pin_id, ctx.headers[i].module_id, info.nso_address[i], info.nso_size[i]);
}
}
/* If we're overriding for HBL, perform HTML document redirection. */
if (override_status.IsHbl()) {
/* Don't validate result, failure is okay. */
RedirectHtmlDocumentPathForHbl(loc);
}
/* Clear the external code for the program. */
fssystem::DestroyExternalCode(loc.program_id);
/* Note that we've created the program. */
SetLaunchedBootProgram(loc.program_id);
/* Move the process handle to output. */
*out = info.process_handle;
R_SUCCEED();
}
Result GetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, const char *path, const ldr::ProgramAttributes &attrs) {
Meta meta;
/* Load Meta. */
{
AMS_UNUSED(path);
ScopedCodeMountForCode mount(loc, attrs);
R_TRY(mount.GetResult());
R_TRY(LoadMeta(std::addressof(meta), loc, mount.GetOverrideStatus(), attrs.platform, false));
if (out_status != nullptr) {
*out_status = mount.GetOverrideStatus();
}
}
return GetProgramInfoFromMeta(out, std::addressof(meta));
}
Result PinProgram(PinId *out_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status) {
R_UNLESS(RoManager::GetInstance().Allocate(out_id, loc, override_status), ldr::ResultMaxProcess());
R_SUCCEED();
}
Result UnpinProgram(PinId id) {
R_UNLESS(RoManager::GetInstance().Free(id), ldr::ResultNotPinned());
R_SUCCEED();
}
Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out, size_t max_out_count, os::ProcessId process_id) {
R_UNLESS(RoManager::GetInstance().GetProcessModuleInfo(out_count, out, max_out_count, process_id), ldr::ResultNotPinned());
R_SUCCEED();
}
Result GetProgramLocationAndOverrideStatusFromPinId(ncm::ProgramLocation *out, cfg::OverrideStatus *out_status, PinId pin_id) {
R_UNLESS(RoManager::GetInstance().GetProgramLocationAndStatus(out, out_status, pin_id), ldr::ResultNotPinned());
R_SUCCEED();
}
}

View File

@@ -0,0 +1,780 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "ldr_capabilities.hpp"
#include "ldr_content_management.hpp"
#include "ldr_development_manager.hpp"
#include "ldr_launch_record.hpp"
#include "ldr_meta.hpp"
#include "ldr_patcher.hpp"
#include "ldr_process_creation.hpp"
#include "ldr_ro_manager.hpp"
#include "ldr_ro_manager.hpp"
#include "oc/oc_loader.hpp"
namespace ams::ldr {
namespace {
/* Convenience defines. */
constexpr size_t SystemResourceSizeMax = 0x1FE00000;
/* Types. */
enum NsoIndex {
Nso_Rtld = 0,
Nso_Main = 1,
Nso_Compat0 = 2,
Nso_Compat1 = 3,
Nso_Compat2 = 4,
Nso_Compat3 = 5,
Nso_Compat4 = 6,
Nso_Compat5 = 7,
Nso_Compat6 = 8,
Nso_Compat7 = 9,
Nso_Compat8 = 10,
Nso_Compat9 = 11,
Nso_SubSdk0 = 12,
Nso_SubSdk1 = 13,
Nso_SubSdk2 = 14,
Nso_SubSdk3 = 15,
Nso_SubSdk4 = 16,
Nso_SubSdk5 = 17,
Nso_SubSdk6 = 18,
Nso_SubSdk7 = 19,
Nso_SubSdk8 = 20,
Nso_SubSdk9 = 21,
Nso_Sdk = 22,
Nso_Count,
};
constexpr inline const char *NsoPaths[Nso_Count] = {
ENCODE_ATMOSPHERE_CODE_PATH("/rtld"),
ENCODE_ATMOSPHERE_CODE_PATH("/main"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat0"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat1"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat2"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat3"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat4"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat5"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat6"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat7"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat8"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat9"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk0"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk1"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk2"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk3"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk4"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk5"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk6"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk7"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk8"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk9"),
ENCODE_ATMOSPHERE_CODE_PATH("/sdk"),
};
constexpr const char *GetNsoPath(size_t idx) {
AMS_ABORT_UNLESS(idx < Nso_Count);
return NsoPaths[idx];
}
struct ProcessInfo {
os::NativeHandle process_handle;
uintptr_t args_address;
size_t args_size;
uintptr_t nso_address[Nso_Count];
size_t nso_size[Nso_Count];
};
/* Global NSO header cache. */
bool g_has_nso[Nso_Count];
NsoHeader g_nso_headers[Nso_Count];
/* Pcv/Ptm check cache */
bool g_is_pcv;
bool g_is_ptm;
Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {
/* No version verification is done before 8.1.0. */
R_SUCCEED_IF(hos::GetVersion() < hos::Version_8_1_0);
/* No verification is done if development. */
R_SUCCEED_IF(IsDevelopmentForAntiDowngradeCheck());
/* TODO: Anti-downgrade checking does not make very much sense for us. Should we do anything? */
AMS_UNUSED(program_id, version);
R_SUCCEED();
}
/* Helpers. */
Result GetProgramInfoFromMeta(ProgramInfo *out, const Meta *meta) {
/* Copy basic info. */
out->main_thread_priority = meta->npdm->main_thread_priority;
out->default_cpu_id = meta->npdm->default_cpu_id;
out->main_thread_stack_size = meta->npdm->main_thread_stack_size;
out->program_id = meta->aci->program_id;
/* Copy access controls. */
size_t offset = 0;
#define COPY_ACCESS_CONTROL(source, which) \
({ \
const size_t size = meta->source->which##_size; \
R_UNLESS(offset + size <= sizeof(out->ac_buffer), ldr::ResultInternalError()); \
out->source##_##which##_size = size; \
std::memcpy(out->ac_buffer + offset, meta->source##_##which, size); \
offset += size; \
})
/* Copy all access controls to buffer. */
COPY_ACCESS_CONTROL(acid, sac);
COPY_ACCESS_CONTROL(aci, sac);
COPY_ACCESS_CONTROL(acid, fac);
COPY_ACCESS_CONTROL(aci, fah);
#undef COPY_ACCESS_CONTROL
/* Copy flags. */
out->flags = MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32));
R_SUCCEED();
}
bool IsApplet(const Meta *meta) {
return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Applet;
}
bool IsApplication(const Meta *meta) {
return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Application;
}
Npdm::AddressSpaceType GetAddressSpaceType(const Meta *meta) {
return static_cast<Npdm::AddressSpaceType>((meta->npdm->flags & Npdm::MetaFlag_AddressSpaceTypeMask) >> Npdm::MetaFlag_AddressSpaceTypeShift);
}
Acid::PoolPartition GetPoolPartition(const Meta *meta) {
return static_cast<Acid::PoolPartition>((meta->acid->flags & Acid::AcidFlag_PoolPartitionMask) >> Acid::AcidFlag_PoolPartitionShift);
}
Result LoadAutoLoadHeaders(NsoHeader *nso_headers, bool *has_nso) {
/* Clear NSOs. */
std::memset(nso_headers, 0, sizeof(*nso_headers) * Nso_Count);
std::memset(has_nso, 0, sizeof(*has_nso) * Nso_Count);
for (size_t i = 0; i < Nso_Count; i++) {
fs::FileHandle file;
if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read))) {
ON_SCOPE_EXIT { fs::CloseFile(file); };
/* Read NSO header. */
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, 0, nso_headers + i, sizeof(*nso_headers)));
R_UNLESS(read_size == sizeof(*nso_headers), ldr::ResultInvalidNso());
has_nso[i] = true;
}
}
R_SUCCEED();
}
Result CheckAutoLoad(const NsoHeader *nso_headers, const bool *has_nso) {
/* We must always have a main. */
R_UNLESS(has_nso[Nso_Main], ldr::ResultInvalidNso());
/* If we don't have an RTLD, we must only have a main. */
if (!has_nso[Nso_Rtld]) {
for (size_t i = Nso_Main + 1; i < Nso_Count; i++) {
R_UNLESS(!has_nso[i], ldr::ResultInvalidNso());
}
}
/* All NSOs must have zero text offset. */
for (size_t i = 0; i < Nso_Count; i++) {
R_UNLESS(nso_headers[i].text_dst_offset == 0, ldr::ResultInvalidNso());
}
R_SUCCEED();
}
constexpr const ncm::ProgramId UnqualifiedApprovalProgramIds[] = {
{ 0x010003F003A34000 }, /* Pokemon: Let's Go, Pikachu! */
{ 0x0100152000022000 }, /* Mario Kart 8 Deluxe */
{ 0x0100165003504000 }, /* Nintendo Labo Toy-Con 04: VR Kit */
{ 0x0100187003A36000 }, /* Pokemon: Let's Go, Eevee! */
{ 0x01002E5008C56000 }, /* Pokemon Sword [Live Tournament] */
{ 0x01002FF008C24000 }, /* Ring Fit Adventure */
{ 0x010049900F546001 }, /* Super Mario 3D All-Stars: Super Mario 64 */
{ 0x010057D00ECE4000 }, /* Nintendo Switch Online (Nintendo 64) [for Japan] */
{ 0x01006F8002326000 }, /* Animal Crossing: New Horizons */
{ 0x01006FB00F50E000 }, /* [???] */
{ 0x010070300F50C000 }, /* [???] */
{ 0x010075100E8EC000 }, /* 马力欧卡丁车8 豪华版 [Mario Kart 8 Deluxe for China] */
{ 0x01008DB008C2C000 }, /* Pokemon Shield */
{ 0x01009AD008C4C000 }, /* Pokemon: Let's Go, Pikachu! [Kiosk] */
{ 0x0100A66003384000 }, /* Hulu */
{ 0x0100ABF008968000 }, /* Pokemon Sword */
{ 0x0100C9A00ECE6000 }, /* Nintendo Switch Online (Nintendo 64) [for America] */
{ 0x0100ED100BA3A000 }, /* Mario Kart Live: Home Circuit */
{ 0x0100F38011CFE000 }, /* Animal Crossing: New Horizons Island Transfer Tool */
{ 0x0100F6B011028000 }, /* 健身环大冒险 [Ring Fit Adventure for China] */
};
/* Check that the unqualified approval programs are sorted. */
static_assert([]() -> bool {
for (size_t i = 0; i < util::size(UnqualifiedApprovalProgramIds) - 1; ++i) {
if (UnqualifiedApprovalProgramIds[i].value >= UnqualifiedApprovalProgramIds[i + 1].value) {
return false;
}
}
return true;
}());
bool IsUnqualifiedApprovalProgramId(ncm::ProgramId program_id) {
/* Check if the program id is one with unqualified approval. */
return std::binary_search(std::begin(UnqualifiedApprovalProgramIds), std::end(UnqualifiedApprovalProgramIds), program_id);
}
bool IsUnqualifiedApproval(const Meta *meta) {
/* If the meta has unqualified approval flag, it's unqualified approval. */
if (meta->acid->flags & ldr::Acid::AcidFlag_UnqualifiedApproval) {
return true;
}
/* If the unqualified approval flag is not set, the program must be an application. */
if (!IsApplication(meta)) {
return false;
}
/* The program id must be a force unqualified approval program id. */
return IsUnqualifiedApprovalProgramId(meta->acid->program_id_min) && meta->acid->program_id_min == meta->acid->program_id_max;
}
Result ValidateMeta(const Meta *meta, const ncm::ProgramLocation &loc, const fs::CodeVerificationData &code_verification_data) {
/* Validate version. */
R_TRY(ValidateProgramVersion(loc.program_id, meta->npdm->version));
/* Validate program id. */
R_UNLESS(meta->aci->program_id >= meta->acid->program_id_min, ldr::ResultInvalidProgramId());
R_UNLESS(meta->aci->program_id <= meta->acid->program_id_max, ldr::ResultInvalidProgramId());
/* Validate the kernel capabilities. */
R_TRY(TestCapability(static_cast<const util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32), static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)));
/* Check if NCA is PCV or PTM */
g_is_pcv = meta->aci->program_id == ncm::SystemProgramId::Pcv;
g_is_ptm = meta->aci->program_id == ncm::SystemProgramId::Ptm;
/* If we have data to validate, validate it. */
if (meta->check_verification_data) {
const u8 *sig = code_verification_data.signature;
const size_t sig_size = sizeof(code_verification_data.signature);
const u8 *mod = static_cast<u8 *>(meta->modulus);
const size_t mod_size = crypto::Rsa2048PssSha256Verifier::ModulusSize;
const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent();
const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize;
const u8 *hsh = code_verification_data.target_hash;
const size_t hsh_size = sizeof(code_verification_data.target_hash);
const bool is_signature_valid = crypto::VerifyRsa2048PssSha256WithHash(sig, sig_size, mod, mod_size, exp, exp_size, hsh, hsh_size);
/* If the signature check fails, we need to check if this is allowable. */
if (!is_signature_valid) {
/* We have to enforce signature checks on prod and when we have a signature to check on dev. */
R_UNLESS(IsDevelopmentForAcidProductionCheck(), ldr::ResultInvalidNcaSignature());
R_UNLESS(!code_verification_data.has_data, ldr::ResultInvalidNcaSignature());
/* There was no signature to check on dev. Check if this is acceptable. */
R_UNLESS(IsUnqualifiedApproval(meta), ldr::ResultInvalidNcaSignature());
}
}
/* All good. */
R_SUCCEED();
}
Result GetCreateProcessFlags(u32 *out, const Meta *meta, const u32 ldr_flags) {
const u8 meta_flags = meta->npdm->flags;
u32 flags = 0;
/* Set Is64Bit. */
if (meta_flags & Npdm::MetaFlag_Is64Bit) {
flags |= svc::CreateProcessFlag_Is64Bit;
}
/* Set AddressSpaceType. */
switch (GetAddressSpaceType(meta)) {
case Npdm::AddressSpaceType_32Bit:
flags |= svc::CreateProcessFlag_AddressSpace32Bit;
break;
case Npdm::AddressSpaceType_64BitDeprecated:
flags |= svc::CreateProcessFlag_AddressSpace64BitDeprecated;
break;
case Npdm::AddressSpaceType_32BitWithoutAlias:
flags |= svc::CreateProcessFlag_AddressSpace32BitWithoutAlias;
break;
case Npdm::AddressSpaceType_64Bit:
flags |= svc::CreateProcessFlag_AddressSpace64Bit;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
}
/* Set Enable Debug. */
if (ldr_flags & CreateProcessFlag_EnableDebug) {
flags |= svc::CreateProcessFlag_EnableDebug;
}
/* Set Enable ASLR. */
if (!(ldr_flags & CreateProcessFlag_DisableAslr)) {
flags |= svc::CreateProcessFlag_EnableAslr;
}
/* Set Is Application. */
if (IsApplication(meta)) {
flags |= svc::CreateProcessFlag_IsApplication;
/* 7.0.0+: Set OptimizeMemoryAllocation if relevant. */
if (hos::GetVersion() >= hos::Version_7_0_0) {
if (meta_flags & Npdm::MetaFlag_OptimizeMemoryAllocation) {
flags |= svc::CreateProcessFlag_OptimizeMemoryAllocation;
}
}
}
/* 5.0.0+ Set Pool Partition. */
if (hos::GetVersion() >= hos::Version_5_0_0) {
/* TODO: Nintendo no longer accepts Applet when pool partition == application. Would this break hbl/anything else in the hb ecosystem? */
/* TODO: Nintendo uses a helper bool MakeSvcPoolPartitionFlag(u32 *out, Acid::PoolPartition partition); */
switch (GetPoolPartition(meta)) {
case Acid::PoolPartition_Application:
if (IsApplet(meta)) {
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
} else {
flags |= svc::CreateProcessFlag_PoolPartitionApplication;
}
break;
case Acid::PoolPartition_Applet:
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
break;
case Acid::PoolPartition_System:
flags |= svc::CreateProcessFlag_PoolPartitionSystem;
break;
case Acid::PoolPartition_SystemNonSecure:
flags |= svc::CreateProcessFlag_PoolPartitionSystemNonSecure;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
}
} else if (hos::GetVersion() >= hos::Version_4_0_0) {
/* On 4.0.0+, the corresponding bit was simply "UseSecureMemory". */
if (meta->acid->flags & Acid::AcidFlag_DeprecatedUseSecureMemory) {
flags |= svc::CreateProcessFlag_DeprecatedUseSecureMemory;
}
}
/* 11.0.0+/meso Set Disable DAS merge. */
if (meta_flags & Npdm::MetaFlag_DisableDeviceAddressSpaceMerge) {
flags |= svc::CreateProcessFlag_DisableDeviceAddressSpaceMerge;
}
/* 18.0.0+/meso Set Alias region extra size. */
if (meta_flags & Npdm::MetaFlag_EnableAliasRegionExtraSize) {
flags |= svc::CreateProcessFlag_EnableAliasRegionExtraSize;
}
*out = flags;
R_SUCCEED();
}
Result GetCreateProcessParameter(svc::CreateProcessParameter *out, const Meta *meta, u32 flags, os::NativeHandle resource_limit) {
/* Clear output. */
std::memset(out, 0, sizeof(*out));
/* Set name, version, program id, resource limit handle. */
std::memcpy(out->name, meta->npdm->program_name, sizeof(out->name) - 1);
out->version = meta->npdm->version;
out->program_id = meta->aci->program_id.value;
out->reslimit = resource_limit;
/* Set flags. */
R_TRY(GetCreateProcessFlags(std::addressof(out->flags), meta, flags));
/* 3.0.0+ System Resource Size. */
if (hos::GetVersion() >= hos::Version_3_0_0) {
/* Validate size is aligned. */
R_UNLESS(util::IsAligned(meta->npdm->system_resource_size, os::MemoryBlockUnitSize), ldr::ResultInvalidSize());
/* Validate system resource usage. */
if (meta->npdm->system_resource_size) {
/* Process must be 64-bit. */
R_UNLESS((out->flags & svc::CreateProcessFlag_AddressSpace64Bit), ldr::ResultInvalidMeta());
/* Process must be application or applet. */
R_UNLESS(IsApplication(meta) || IsApplet(meta), ldr::ResultInvalidMeta());
/* Size must be less than or equal to max. */
R_UNLESS(meta->npdm->system_resource_size <= SystemResourceSizeMax, ldr::ResultInvalidMeta());
}
out->system_resource_num_pages = meta->npdm->system_resource_size >> 12;
}
R_SUCCEED();
}
u64 GenerateSecureRandom(u64 max) {
/* Generate a cryptographically random number. */
u64 rand;
crypto::GenerateCryptographicallyRandomBytes(std::addressof(rand), sizeof(rand));
/* Coerce into range. */
return rand % (max + 1);
}
Result DecideAddressSpaceLayout(ProcessInfo *out, svc::CreateProcessParameter *out_param, const NsoHeader *nso_headers, const bool *has_nso, const ArgumentStore::Entry *argument) {
/* Clear output. */
out->args_address = 0;
out->args_size = 0;
std::memset(out->nso_address, 0, sizeof(out->nso_address));
std::memset(out->nso_size, 0, sizeof(out->nso_size));
size_t total_size = 0;
bool argument_allocated = false;
/* Calculate base offsets. */
for (size_t i = 0; i < Nso_Count; i++) {
if (has_nso[i]) {
out->nso_address[i] = total_size;
const size_t text_end = nso_headers[i].text_dst_offset + nso_headers[i].text_size;
const size_t ro_end = nso_headers[i].ro_dst_offset + nso_headers[i].ro_size;
const size_t rw_end = nso_headers[i].rw_dst_offset + nso_headers[i].rw_size + nso_headers[i].bss_size;
out->nso_size[i] = text_end;
out->nso_size[i] = std::max(out->nso_size[i], ro_end);
out->nso_size[i] = std::max(out->nso_size[i], rw_end);
out->nso_size[i] = util::AlignUp(out->nso_size[i], os::MemoryPageSize);
total_size += out->nso_size[i];
if (!argument_allocated && argument != nullptr) {
out->args_address = total_size;
out->args_size = util::AlignUp(2 * sizeof(u32) + argument->argument_size * 2 + ArgumentStore::ArgumentBufferSize, os::MemoryPageSize);
total_size += out->args_size;
argument_allocated = true;
}
}
}
/* Calculate ASLR. */
uintptr_t aslr_start = 0;
size_t aslr_size = 0;
if (hos::GetVersion() >= hos::Version_2_0_0) {
switch (out_param->flags & svc::CreateProcessFlag_AddressSpaceMask) {
case svc::CreateProcessFlag_AddressSpace32Bit:
case svc::CreateProcessFlag_AddressSpace32BitWithoutAlias:
aslr_start = svc::AddressSmallMap32Start;
aslr_size = svc::AddressSmallMap32Size;
break;
case svc::CreateProcessFlag_AddressSpace64BitDeprecated:
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
break;
case svc::CreateProcessFlag_AddressSpace64Bit:
aslr_start = svc::AddressMap39Start;
aslr_size = svc::AddressMap39Size;
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
} else {
/* On 1.0.0, only 2 address space types existed. */
if (out_param->flags & svc::CreateProcessFlag_AddressSpace64BitDeprecated) {
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
} else {
aslr_start = svc::AddressSmallMap32Start;
aslr_size = svc::AddressSmallMap32Size;
}
}
R_UNLESS(total_size <= aslr_size, svc::ResultOutOfMemory());
/* Set Create Process output. */
uintptr_t aslr_slide = 0;
size_t free_size = (aslr_size - total_size);
if (out_param->flags & svc::CreateProcessFlag_EnableAslr) {
aslr_slide = GenerateSecureRandom(free_size / os::MemoryBlockUnitSize) * os::MemoryBlockUnitSize;
}
/* Set out. */
aslr_start += aslr_slide;
for (size_t i = 0; i < Nso_Count; i++) {
if (has_nso[i]) {
out->nso_address[i] += aslr_start;
}
}
if (out->args_address) {
out->args_address += aslr_start;
}
out_param->code_address = aslr_start;
out_param->code_num_pages = total_size >> 12;
R_SUCCEED();
}
Result LoadAutoLoadModuleSegment(fs::FileHandle file, const NsoHeader::SegmentInfo *segment, size_t file_size, const u8 *file_hash, bool is_compressed, bool check_hash, uintptr_t map_base, uintptr_t map_end) {
/* Select read size based on compression. */
if (!is_compressed) {
file_size = segment->size;
}
/* Validate size. */
R_UNLESS(file_size <= segment->size, ldr::ResultInvalidNso());
R_UNLESS(segment->size <= std::numeric_limits<s32>::max(), ldr::ResultInvalidNso());
/* Load data from file. */
uintptr_t load_address = is_compressed ? map_end - file_size : map_base;
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, segment->file_offset, reinterpret_cast<void *>(load_address), file_size));
R_UNLESS(read_size == file_size, ldr::ResultInvalidNso());
/* Uncompress if necessary. */
if (is_compressed) {
bool decompressed = (util::DecompressLZ4(reinterpret_cast<void *>(map_base), segment->size, reinterpret_cast<const void *>(load_address), file_size) == static_cast<int>(segment->size));
R_UNLESS(decompressed, ldr::ResultInvalidNso());
}
/* Check hash if necessary. */
if (check_hash) {
u8 hash[crypto::Sha256Generator::HashSize];
crypto::GenerateSha256(hash, sizeof(hash), reinterpret_cast<void *>(map_base), segment->size);
R_UNLESS(std::memcmp(hash, file_hash, sizeof(hash)) == 0, ldr::ResultInvalidNso());
}
R_SUCCEED();
}
Result LoadAutoLoadModule(os::NativeHandle process_handle, fs::FileHandle file, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size, bool prevent_code_reads) {
/* Map and read data from file. */
{
/* Map the process memory. */
void *mapped_memory = nullptr;
R_TRY(os::MapProcessMemory(std::addressof(mapped_memory), process_handle, nso_address, nso_size, GenerateSecureRandom));
ON_SCOPE_EXIT { os::UnmapProcessMemory(mapped_memory, process_handle, nso_address, nso_size); };
const uintptr_t map_address = reinterpret_cast<uintptr_t>(mapped_memory);
/* Load NSO segments. */
R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Text]), nso_header->text_compressed_size, nso_header->text_hash, (nso_header->flags & NsoHeader::Flag_CompressedText) != 0,
(nso_header->flags & NsoHeader::Flag_CheckHashText) != 0, map_address + nso_header->text_dst_offset, map_address + nso_size));
R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Ro]), nso_header->ro_compressed_size, nso_header->ro_hash, (nso_header->flags & NsoHeader::Flag_CompressedRo) != 0,
(nso_header->flags & NsoHeader::Flag_CheckHashRo) != 0, map_address + nso_header->ro_dst_offset, map_address + nso_size));
R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Rw]), nso_header->rw_compressed_size, nso_header->rw_hash, (nso_header->flags & NsoHeader::Flag_CompressedRw) != 0,
(nso_header->flags & NsoHeader::Flag_CheckHashRw) != 0, map_address + nso_header->rw_dst_offset, map_address + nso_size));
/* Clear unused space to zero. */
const size_t text_end = nso_header->text_dst_offset + nso_header->text_size;
const size_t ro_end = nso_header->ro_dst_offset + nso_header->ro_size;
const size_t rw_end = nso_header->rw_dst_offset + nso_header->rw_size;
std::memset(reinterpret_cast<void *>(map_address + 0), 0, nso_header->text_dst_offset);
std::memset(reinterpret_cast<void *>(map_address + text_end), 0, nso_header->ro_dst_offset - text_end);
std::memset(reinterpret_cast<void *>(map_address + ro_end), 0, nso_header->rw_dst_offset - ro_end);
std::memset(reinterpret_cast<void *>(map_address + rw_end), 0, nso_header->bss_size);
/* Apply embedded patches. */
ApplyEmbeddedPatchesToModule(nso_header->module_id, map_address, nso_size);
/* Apply IPS patches. */
LocateAndApplyIpsPatchesToModule(nso_header->module_id, map_address, nso_size);
/* Apply PCV and PTM patches */
if (g_is_pcv)
hoc::pcv::Patch(map_address, nso_size);
if (g_is_ptm)
hoc::ptm::Patch(map_address, nso_size);
}
/* Set permissions. */
const size_t text_size = util::AlignUp(nso_header->text_size, os::MemoryPageSize);
const size_t ro_size = util::AlignUp(nso_header->ro_size, os::MemoryPageSize);
const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize);
if (text_size) {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->text_dst_offset, text_size, prevent_code_reads ? os::MemoryPermission_ExecuteOnly : os::MemoryPermission_ReadExecute));
}
if (ro_size) {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->ro_dst_offset, ro_size, os::MemoryPermission_ReadOnly));
}
if (rw_size) {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->rw_dst_offset, rw_size, os::MemoryPermission_ReadWrite));
}
R_SUCCEED();
}
Result LoadAutoLoadModules(const ProcessInfo *process_info, const NsoHeader *nso_headers, const bool *has_nso, const ArgumentStore::Entry *argument, bool prevent_code_reads) {
/* Load each NSO. */
for (size_t i = 0; i < Nso_Count; i++) {
if (has_nso[i]) {
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(file); };
R_TRY(LoadAutoLoadModule(process_info->process_handle, file, nso_headers + i, process_info->nso_address[i], process_info->nso_size[i], prevent_code_reads));
}
}
/* Load arguments, if present. */
if (argument != nullptr) {
/* Write argument data into memory. */
{
void *map_address = nullptr;
R_TRY(os::MapProcessMemory(std::addressof(map_address), process_info->process_handle, process_info->args_address, process_info->args_size, GenerateSecureRandom));
ON_SCOPE_EXIT { os::UnmapProcessMemory(map_address, process_info->process_handle, process_info->args_address, process_info->args_size); };
ProgramArguments *args = static_cast<ProgramArguments *>(map_address);
std::memset(args, 0, sizeof(*args));
args->allocated_size = process_info->args_size;
args->arguments_size = argument->argument_size;
std::memcpy(args->arguments, argument->argument, argument->argument_size);
}
/* Set argument region permissions. */
/* NOTE: Nintendo uses svc::SetProcessMemoryPermission directly here. */
R_TRY(os::SetProcessMemoryPermission(process_info->process_handle, process_info->args_address, process_info->args_size, os::MemoryPermission_ReadWrite));
}
R_SUCCEED();
}
Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const NsoHeader *nso_headers, const bool *has_nso, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) {
/* Get CreateProcessParameter. */
svc::CreateProcessParameter param;
R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit));
/* Decide on an NSO layout. */
R_TRY(DecideAddressSpaceLayout(out, std::addressof(param), nso_headers, has_nso, argument));
/* Actually create process. */
svc::Handle process_handle;
R_TRY(svc::CreateProcess(std::addressof(process_handle), std::addressof(param), static_cast<const u32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(u32)));
/* Set the output handle, and ensure that if we fail after this point we clean it up. */
out->process_handle = process_handle;
ON_RESULT_FAILURE { svc::CloseHandle(process_handle); };
/* Load all auto load modules. */
R_RETURN(LoadAutoLoadModules(out, nso_headers, has_nso, argument, (meta->npdm->flags & ldr::Npdm::MetaFlag_PreventCodeReads) != 0));
}
}
/* Process Creation API. */
Result CreateProcess(os::NativeHandle *out, PinId pin_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status, const char *path, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit, const ldr::ProgramAttributes &attrs) {
/* Mount code. */
AMS_UNUSED(path);
ScopedCodeMount mount(loc, override_status, attrs);
R_TRY(mount.GetResult());
/* Load meta, possibly from cache. */
Meta meta;
R_TRY(LoadMetaFromCache(std::addressof(meta), loc, override_status, attrs.platform));
/* Validate meta. */
R_TRY(ValidateMeta(std::addressof(meta), loc, mount.GetCodeVerificationData()));
/* Load, validate NSO headers. */
R_TRY(LoadAutoLoadHeaders(g_nso_headers, g_has_nso));
R_TRY(CheckAutoLoad(g_nso_headers, g_has_nso));
/* Actually create the process and load NSOs into process memory. */
ProcessInfo info;
R_TRY(CreateProcessAndLoadAutoLoadModules(std::addressof(info), std::addressof(meta), g_nso_headers, g_has_nso, argument, flags, resource_limit));
/* Register NSOs with the RoManager. */
{
/* Nintendo doesn't validate this get, but we do. */
os::ProcessId process_id = os::GetProcessId(info.process_handle);
/* Register new process. */
const auto as_type = GetAddressSpaceType(std::addressof(meta));
RoManager::GetInstance().RegisterProcess(pin_id, process_id, meta.aci->program_id, as_type == Npdm::AddressSpaceType_64Bit || as_type == Npdm::AddressSpaceType_64BitDeprecated);
/* Register all NSOs. */
for (size_t i = 0; i < Nso_Count; i++) {
if (g_has_nso[i]) {
RoManager::GetInstance().AddNso(pin_id, g_nso_headers[i].module_id, info.nso_address[i], info.nso_size[i]);
}
}
}
/* If we're overriding for HBL, perform HTML document redirection. */
if (override_status.IsHbl()) {
/* Don't validate result, failure is okay. */
RedirectHtmlDocumentPathForHbl(loc);
}
/* Clear the external code for the program. */
fssystem::DestroyExternalCode(loc.program_id);
/* Note that we've created the program. */
SetLaunchedBootProgram(loc.program_id);
/* Move the process handle to output. */
*out = info.process_handle;
R_SUCCEED();
}
Result GetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, const char *path, const ldr::ProgramAttributes &attrs) {
Meta meta;
/* Load Meta. */
{
AMS_UNUSED(path);
ScopedCodeMount mount(loc, attrs);
R_TRY(mount.GetResult());
R_TRY(LoadMeta(std::addressof(meta), loc, mount.GetOverrideStatus(), attrs.platform, false));
if (out_status != nullptr) {
*out_status = mount.GetOverrideStatus();
}
}
return GetProgramInfoFromMeta(out, std::addressof(meta));
}
Result PinProgram(PinId *out_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status) {
R_UNLESS(RoManager::GetInstance().Allocate(out_id, loc, override_status), ldr::ResultMaxProcess());
R_SUCCEED();
}
Result UnpinProgram(PinId id) {
R_UNLESS(RoManager::GetInstance().Free(id), ldr::ResultNotPinned());
R_SUCCEED();
}
Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out, size_t max_out_count, os::ProcessId process_id) {
R_UNLESS(RoManager::GetInstance().GetProcessModuleInfo(out_count, out, max_out_count, process_id), ldr::ResultNotPinned());
R_SUCCEED();
}
Result GetProgramLocationAndOverrideStatusFromPinId(ncm::ProgramLocation *out, cfg::OverrideStatus *out_status, PinId pin_id) {
R_UNLESS(RoManager::GetInstance().GetProgramLocationAndStatus(out, out_status, pin_id), ldr::ResultNotPinned());
R_SUCCEED();
}
}

View File

@@ -0,0 +1,81 @@
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif
include $(DEVKITARM)/base_rules
################################################################################
IPL_LOAD_ADDR := 0x40010000
################################################################################
THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST)))
CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE)))
BUILDDIR := build
OUTPUTDIR := out
SOURCEDIR = fatal_handler
BDKDIR := bdk
GFXDIR := fatal_handler/gfx
TARGET := fatal_handler
BDKINC := -I$(BDKDIR)
GFXINC := -I$(GFXDIR)
GFX_INC := '"gfx.h"'
VPATH = $(dir ./$(SOURCEDIR)/) $(dir $(wildcard ./$(SOURCEDIR)/*/)) $(dir $(wildcard ./$(SOURCEDIR)/*/*/))
VPATH += $(dir $(wildcard ./$(BDKDIR)/)) $(dir $(wildcard ./$(BDKDIR)/*/)) $(dir $(wildcard ./$(BDKDIR)/*/*/))
OBJS = $(addprefix $(BUILDDIR)/$(TARGET)/, \
start.o exception_handlers.o irq.o main.o \
timer.o heap.o hw_init.o clock.o i2c.o gpio.o \
max7762x.o di.o util.o fuse.o pinmux.o \
secmon_exo.o gfx.o bpmp.o sdram.o minerva.o btn.o max77620-rtc.o se.o mc.o)
################################################################################
# BDK defines.
CUSTOMDEFINES := -DGFX_INC=$(GFX_INC)
CUSTOMDEFINES += -DBDK_MALLOC_NO_DEFRAG -DBDK_MC_ENABLE_AHB_REDIRECT -DBDK_EMUMMC_ENABLE
CUSTOMDEFINES += -DBDK_WATCHDOG_FIQ_ENABLE -DBDK_RESTART_BL_ON_WDT
INCDIRS := $(BDKINC) $(GFXINC)
WARNINGS := -Wall -Wsign-compare -Wno-array-bounds -Wno-stringop-overflow
ARCH := -march=armv4t -mtune=arm7tdmi -mthumb-interwork
CFLAGS = $(ARCH) -O2 -g -nostdlib -ffunction-sections -fdata-sections -fomit-frame-pointer -std=gnu11 $(WARNINGS) $(CUSTOMDEFINES)
LDFLAGS = $(ARCH) -nostartfiles -lgcc -Wl,--nmagic,--gc-sections -Xlinker --defsym=IPL_LOAD_ADDR=$(IPL_LOAD_ADDR)
################################################################################
BIN_TARGET := $(OUTPUTDIR)/$(TARGET).bin
BIN_OBJ := $(OUTPUTDIR)/$(TARGET).bin.o
BIN_HEADER := $(OUTPUTDIR)/$(TARGET)_bin.h
BIN_SYM := $(subst .,_,$(subst /,_,$(TARGET)))_bin
define bin2o
$(eval SYM := $(BIN_SYM))
$(eval BIN_SIZE := $(shell wc -c < $(BIN_TARGET)))
@$(OBJCOPY) --input-target binary --output-target elf32-littlearm --binary-architecture arm \
--redefine-sym _binary_$(shell echo $(BIN_TARGET) | tr '/.-' '___')_start=$(SYM) \
--redefine-sym _binary_$(shell echo $(BIN_TARGET) | tr '/.-' '___')_end=$(SYM)_end \
--redefine-sym _binary_$(shell echo $(BIN_TARGET) | tr '/.-' '___')_size=$(SYM)_size \
$(BIN_TARGET) $(BIN_OBJ)
@printf '#pragma once\n#include <stddef.h>\n#include <stdint.h>\nextern const uint8_t $(SYM)[];\nextern const uint8_t $(SYM)_end[];\n#if __cplusplus >= 201103L\nstatic constexpr size_t $(SYM)_size=$(BIN_SIZE);\n#else\nstatic const size_t $(SYM)_size=$(BIN_SIZE);\n#endif\n' > $(BIN_HEADER)
endef
################################################################################
.PHONY: all clean
all: $(BIN_OBJ) $(BIN_HEADER)
$(BIN_OBJ) $(BIN_HEADER): $(BIN_TARGET)
$(bin2o)
$(BIN_TARGET): $(BUILDDIR)/$(TARGET)/$(TARGET).elf
$(OBJCOPY) -S -O binary $< $(BIN_TARGET)
clean:
@rm -rf $(OBJS) $(BIN_OBJ) $(BIN_HEADER)
$(BUILDDIR)/$(TARGET)/$(TARGET).elf: $(OBJS)
@$(CC) $(LDFLAGS) -T $(SOURCEDIR)/link.ld $^ -o $@
$(BUILDDIR)/$(TARGET)/%.o: %.c
@$(CC) $(CFLAGS) $(INCDIRS) -c $< -o $@
$(BUILDDIR)/$(TARGET)/%.o: %.S
@$(CC) $(CFLAGS) -c $< -o $@
$(OBJS): $(BUILDDIR)/$(TARGET)
$(BUILDDIR)/$(TARGET):
@mkdir -p "$(BUILDDIR)"
@mkdir -p "$(BUILDDIR)/$(TARGET)"
@mkdir -p "$(OUTPUTDIR)"

View File

@@ -0,0 +1,558 @@
/*
* Copyright (c) 2018 naehrwert
* Copyright (c) 2018-2022 CTCaer
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdarg.h>
#include <string.h>
#include "gfx.h"
// Global gfx console and context.
gfx_ctxt_t gfx_ctxt;
gfx_con_t gfx_con;
static bool gfx_con_init_done = false;
static const u8 _gfx_font[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Char 032 ( )
0x00, 0x30, 0x30, 0x18, 0x18, 0x00, 0x0C, 0x00, // Char 033 (!)
0x00, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, // Char 034 (")
0x00, 0x66, 0x66, 0xFF, 0x66, 0xFF, 0x66, 0x66, // Char 035 (#)
0x00, 0x18, 0x7C, 0x06, 0x3C, 0x60, 0x3E, 0x18, // Char 036 ($)
0x00, 0x46, 0x66, 0x30, 0x18, 0x0C, 0x66, 0x62, // Char 037 (%)
0x00, 0x3C, 0x66, 0x3C, 0x1C, 0xE6, 0x66, 0xFC, // Char 038 (&)
0x00, 0x18, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00, // Char 039 (')
0x00, 0x30, 0x18, 0x0C, 0x0C, 0x18, 0x30, 0x00, // Char 040 (()
0x00, 0x0C, 0x18, 0x30, 0x30, 0x18, 0x0C, 0x00, // Char 041 ())
0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, // Char 042 (*)
0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, // Char 043 (+)
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x0C, 0x00, // Char 044 (,)
0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, // Char 045 (-)
0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, // Char 046 (.)
0x00, 0x40, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x00, // Char 047 (/)
0x00, 0x3C, 0x66, 0x76, 0x6E, 0x66, 0x3C, 0x00, // Char 048 (0)
0x00, 0x18, 0x1C, 0x18, 0x18, 0x18, 0x7E, 0x00, // Char 049 (1)
0x00, 0x3C, 0x62, 0x30, 0x0C, 0x06, 0x7E, 0x00, // Char 050 (2)
0x00, 0x3C, 0x62, 0x38, 0x60, 0x66, 0x3C, 0x00, // Char 051 (3)
0x00, 0x6C, 0x6C, 0x66, 0xFE, 0x60, 0x60, 0x00, // Char 052 (4)
0x00, 0x7E, 0x06, 0x7E, 0x60, 0x66, 0x3C, 0x00, // Char 053 (5)
0x00, 0x3C, 0x06, 0x3E, 0x66, 0x66, 0x3C, 0x00, // Char 054 (6)
0x00, 0x7E, 0x30, 0x30, 0x18, 0x18, 0x18, 0x00, // Char 055 (7)
0x00, 0x3C, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00, // Char 056 (8)
0x00, 0x3C, 0x66, 0x7C, 0x60, 0x66, 0x3C, 0x00, // Char 057 (9)
0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x00, // Char 058 (:)
0x00, 0x00, 0x18, 0x00, 0x18, 0x18, 0x0C, 0x00, // Char 059 (;)
0x00, 0x70, 0x1C, 0x06, 0x06, 0x1C, 0x70, 0x00, // Char 060 (<)
0x00, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x00, 0x00, // Char 061 (=)
0x00, 0x0E, 0x38, 0x60, 0x60, 0x38, 0x0E, 0x00, // Char 062 (>)
0x00, 0x3C, 0x66, 0x30, 0x18, 0x00, 0x18, 0x00, // Char 063 (?)
0x00, 0x3C, 0x66, 0x76, 0x76, 0x06, 0x46, 0x3C, // Char 064 (@)
0x00, 0x3C, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, // Char 065 (A)
0x00, 0x3E, 0x66, 0x3E, 0x66, 0x66, 0x3E, 0x00, // Char 066 (B)
0x00, 0x3C, 0x66, 0x06, 0x06, 0x66, 0x3C, 0x00, // Char 067 (C)
0x00, 0x1E, 0x36, 0x66, 0x66, 0x36, 0x1E, 0x00, // Char 068 (D)
0x00, 0x7E, 0x06, 0x1E, 0x06, 0x06, 0x7E, 0x00, // Char 069 (E)
0x00, 0x3E, 0x06, 0x1E, 0x06, 0x06, 0x06, 0x00, // Char 070 (F)
0x00, 0x3C, 0x66, 0x06, 0x76, 0x66, 0x3C, 0x00, // Char 071 (G)
0x00, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, // Char 072 (H)
0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, // Char 073 (I)
0x00, 0x78, 0x30, 0x30, 0x30, 0x36, 0x1C, 0x00, // Char 074 (J)
0x00, 0x66, 0x36, 0x1E, 0x1E, 0x36, 0x66, 0x00, // Char 075 (K)
0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x7E, 0x00, // Char 076 (L)
0x00, 0x46, 0x6E, 0x7E, 0x56, 0x46, 0x46, 0x00, // Char 077 (M)
0x00, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x66, 0x00, // Char 078 (N)
0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, // Char 079 (O)
0x00, 0x3E, 0x66, 0x3E, 0x06, 0x06, 0x06, 0x00, // Char 080 (P)
0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x70, 0x00, // Char 081 (Q)
0x00, 0x3E, 0x66, 0x3E, 0x1E, 0x36, 0x66, 0x00, // Char 082 (R)
0x00, 0x3C, 0x66, 0x0C, 0x30, 0x66, 0x3C, 0x00, // Char 083 (S)
0x00, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // Char 084 (T)
0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, // Char 085 (U)
0x00, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, // Char 086 (V)
0x00, 0x46, 0x46, 0x56, 0x7E, 0x6E, 0x46, 0x00, // Char 087 (W)
0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00, // Char 088 (X)
0x00, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x00, // Char 089 (Y)
0x00, 0x7E, 0x30, 0x18, 0x0C, 0x06, 0x7E, 0x00, // Char 090 (Z)
0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, // Char 091 ([)
0x00, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00, // Char 092 (\)
0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, // Char 093 (])
0x00, 0x18, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x00, // Char 094 (^)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, // Char 095 (_)
0x00, 0x0C, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, // Char 096 (`)
0x00, 0x00, 0x3C, 0x60, 0x7C, 0x66, 0x7C, 0x00, // Char 097 (a)
0x00, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3E, 0x00, // Char 098 (b)
0x00, 0x00, 0x3C, 0x06, 0x06, 0x06, 0x3C, 0x00, // Char 099 (c)
0x00, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00, // Char 100 (d)
0x00, 0x00, 0x3C, 0x66, 0x7E, 0x06, 0x3C, 0x00, // Char 101 (e)
0x00, 0x38, 0x0C, 0x3E, 0x0C, 0x0C, 0x0C, 0x00, // Char 102 (f)
0x00, 0x00, 0x7C, 0x66, 0x7C, 0x40, 0x3C, 0x00, // Char 103 (g)
0x00, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x66, 0x00, // Char 104 (h)
0x00, 0x18, 0x00, 0x1C, 0x18, 0x18, 0x3C, 0x00, // Char 105 (i)
0x00, 0x30, 0x00, 0x30, 0x30, 0x30, 0x1E, 0x00, // Char 106 (j)
0x00, 0x06, 0x06, 0x36, 0x1E, 0x36, 0x66, 0x00, // Char 107 (k)
0x00, 0x1C, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, // Char 108 (l)
0x00, 0x00, 0x66, 0xFE, 0xFE, 0xD6, 0xC6, 0x00, // Char 109 (m)
0x00, 0x00, 0x3E, 0x66, 0x66, 0x66, 0x66, 0x00, // Char 110 (n)
0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, // Char 111 (o)
0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x00, // Char 112 (p)
0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x00, // Char 113 (q)
0x00, 0x00, 0x3E, 0x66, 0x06, 0x06, 0x06, 0x00, // Char 114 (r)
0x00, 0x00, 0x7C, 0x06, 0x3C, 0x60, 0x3E, 0x00, // Char 115 (s)
0x00, 0x18, 0x7E, 0x18, 0x18, 0x18, 0x70, 0x00, // Char 116 (t)
0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x00, // Char 117 (u)
0x00, 0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, // Char 118 (v)
0x00, 0x00, 0xC6, 0xD6, 0xFE, 0x7C, 0x6C, 0x00, // Char 119 (w)
0x00, 0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x00, // Char 120 (x)
0x00, 0x00, 0x66, 0x66, 0x7C, 0x60, 0x3C, 0x00, // Char 121 (y)
0x00, 0x00, 0x7E, 0x30, 0x18, 0x0C, 0x7E, 0x00, // Char 122 (z)
0x00, 0x18, 0x08, 0x08, 0x04, 0x08, 0x08, 0x18, // Char 123 ({)
0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, // Char 124 (|)
0x00, 0x0C, 0x08, 0x08, 0x10, 0x08, 0x08, 0x0C, // Char 125 (})
0x00, 0x00, 0x00, 0x4C, 0x32, 0x00, 0x00, 0x00 // Char 126 (~)
};
void gfx_clear_grey(u8 color)
{
memset(gfx_ctxt.fb, color, gfx_ctxt.width * gfx_ctxt.height * 4);
}
void gfx_clear_partial_grey(u8 color, u32 pos_x, u32 height)
{
memset(gfx_ctxt.fb + pos_x * gfx_ctxt.stride, color, height * 4 * gfx_ctxt.stride);
}
void gfx_clear_color(u32 color)
{
for (u32 i = 0; i < gfx_ctxt.width * gfx_ctxt.height; i++)
gfx_ctxt.fb[i] = color;
}
void gfx_init_ctxt(u32 *fb, u32 width, u32 height, u32 stride)
{
gfx_ctxt.fb = fb;
gfx_ctxt.width = width;
gfx_ctxt.height = height;
gfx_ctxt.stride = stride;
}
void gfx_con_init()
{
gfx_con.gfx_ctxt = &gfx_ctxt;
gfx_con.fntsz = 16;
gfx_con.x = 0;
gfx_con.y = 0;
gfx_con.savedx = 0;
gfx_con.savedy = 0;
gfx_con.fgcol = TXT_CLR_DEFAULT;
gfx_con.fillbg = 1;
gfx_con.bgcol = TXT_CLR_BG;
gfx_con.mute = 0;
gfx_con_init_done = true;
}
void gfx_con_setcol(u32 fgcol, int fillbg, u32 bgcol)
{
gfx_con.fgcol = fgcol;
gfx_con.fillbg = fillbg;
gfx_con.bgcol = bgcol;
}
void gfx_con_getpos(u32 *x, u32 *y)
{
*x = gfx_con.x;
*y = gfx_con.y;
}
void gfx_con_setpos(u32 x, u32 y)
{
gfx_con.x = x;
gfx_con.y = y;
}
void gfx_putc(char c)
{
// Duplicate code for performance reasons.
switch (gfx_con.fntsz)
{
case 16:
if (c >= 32 && c <= 126)
{
u8 *cbuf = (u8 *)&_gfx_font[8 * (c - 32)];
u32 *fb = gfx_ctxt.fb + gfx_con.x + gfx_con.y * gfx_ctxt.stride;
for (u32 i = 0; i < 16; i += 2)
{
u8 v = *cbuf;
for (u32 k = 0; k < 2; k++)
{
for (u32 j = 0; j < 8; j++)
{
if (v & 1)
{
*fb = gfx_con.fgcol;
fb++;
*fb = gfx_con.fgcol;
}
else if (gfx_con.fillbg)
{
*fb = gfx_con.bgcol;
fb++;
*fb = gfx_con.bgcol;
}
else
fb++;
v >>= 1;
fb++;
}
fb += gfx_ctxt.stride - 16;
v = *cbuf;
}
cbuf++;
}
gfx_con.x += 16;
}
else if (c == '\n')
{
gfx_con.x = 0;
gfx_con.y += 16;
if (gfx_con.y > gfx_ctxt.height - 16)
gfx_con.y = 0;
}
break;
case 8:
default:
if (c >= 32 && c <= 126)
{
u8 *cbuf = (u8 *)&_gfx_font[8 * (c - 32)];
u32 *fb = gfx_ctxt.fb + gfx_con.x + gfx_con.y * gfx_ctxt.stride;
for (u32 i = 0; i < 8; i++)
{
u8 v = *cbuf++;
for (u32 j = 0; j < 8; j++)
{
if (v & 1)
*fb = gfx_con.fgcol;
else if (gfx_con.fillbg)
*fb = gfx_con.bgcol;
v >>= 1;
fb++;
}
fb += gfx_ctxt.stride - 8;
}
gfx_con.x += 8;
}
else if (c == '\n')
{
gfx_con.x = 0;
gfx_con.y += 8;
if (gfx_con.y > gfx_ctxt.height - 8)
gfx_con.y = 0;
}
break;
}
}
void gfx_puts(const char *s)
{
if (!s || !gfx_con_init_done || gfx_con.mute)
return;
for (; *s; s++)
gfx_putc(*s);
}
static void _gfx_putn(u32 v, int base, char fill, int fcnt)
{
static const char digits[] = "0123456789ABCDEF";
char *p;
char buf[65];
int c = fcnt;
bool negative = false;
if (base != 10 && base != 16)
return;
// Account for negative numbers.
if (base == 10 && v & 0x80000000)
{
negative = true;
v = (int)v * -1;
c--;
}
p = buf + 64;
*p = 0;
do
{
c--;
*--p = digits[v % base];
v /= base;
} while (v);
if (negative)
*--p = '-';
if (fill != 0)
{
while (c > 0 && p > buf)
{
*--p = fill;
c--;
}
}
gfx_puts(p);
}
void gfx_put_small_sep()
{
u8 prevFontSize = gfx_con.fntsz;
gfx_con.fntsz = 8;
gfx_putc('\n');
gfx_con.fntsz = prevFontSize;
}
void gfx_put_big_sep()
{
u8 prevFontSize = gfx_con.fntsz;
gfx_con.fntsz = 16;
gfx_putc('\n');
gfx_con.fntsz = prevFontSize;
}
void gfx_printf(const char *fmt, ...)
{
if (!gfx_con_init_done || gfx_con.mute)
return;
va_list ap;
int fill, fcnt;
va_start(ap, fmt);
while (*fmt)
{
if (*fmt == '%')
{
fmt++;
fill = 0;
fcnt = 0;
if ((*fmt >= '0' && *fmt <= '9') || *fmt == ' ')
{
fcnt = *fmt;
fmt++;
if (*fmt >= '0' && *fmt <= '9')
{
fill = fcnt;
fcnt = *fmt - '0';
fmt++;
}
else
{
fill = ' ';
fcnt -= '0';
}
}
switch(*fmt)
{
case 'c':
gfx_putc(va_arg(ap, u32));
break;
case 's':
gfx_puts(va_arg(ap, char *));
break;
case 'd':
_gfx_putn(va_arg(ap, u32), 10, fill, fcnt);
break;
case 'p':
case 'P':
case 'x':
case 'X':
_gfx_putn(va_arg(ap, u32), 16, fill, fcnt);
break;
case 'k':
gfx_con.fgcol = va_arg(ap, u32);
break;
case 'K':
gfx_con.bgcol = va_arg(ap, u32);
gfx_con.fillbg = 1;
break;
case '%':
gfx_putc('%');
break;
case '\0':
goto out;
default:
gfx_putc('%');
gfx_putc(*fmt);
break;
}
}
else
gfx_putc(*fmt);
fmt++;
}
out:
va_end(ap);
}
static void _gfx_cputs(u32 color, const char *s)
{
gfx_con.fgcol = color;
gfx_puts(s);
gfx_putc('\n');
gfx_con.fgcol = TXT_CLR_DEFAULT;
}
void gfx_wputs(const char *s) { _gfx_cputs(TXT_CLR_WARNING, s); }
void gfx_eputs(const char *s) { _gfx_cputs(TXT_CLR_ERROR, s); }
void gfx_hexdump(u32 base, const void *buf, u32 len)
{
if (!gfx_con_init_done || gfx_con.mute)
return;
u8 *buff = (u8 *)buf;
u8 prevFontSize = gfx_con.fntsz;
gfx_con.fntsz = 8;
for (u32 i = 0; i < len; i++)
{
if (i % 0x10 == 0)
{
if (i != 0)
{
gfx_puts("| ");
for (u32 j = 0; j < 0x10; j++)
{
u8 c = buff[i - 0x10 + j];
if (c >= 32 && c <= 126)
gfx_putc(c);
else
gfx_putc('.');
}
gfx_putc('\n');
}
gfx_printf("%08x: ", base + i);
}
gfx_printf("%02x ", buff[i]);
if (i == len - 1)
{
int ln = len % 0x10 != 0;
u32 k = 0x10 - 1;
if (ln)
{
k = (len & 0xF) - 1;
for (u32 j = 0; j < 0x10 - k; j++)
gfx_puts(" ");
}
gfx_puts("| ");
for (u32 j = 0; j < (ln ? k : k + 1); j++)
{
u8 c = buff[i - k + j];
if (c >= 32 && c <= 126)
gfx_putc(c);
else
gfx_putc('.');
}
gfx_putc('\n');
}
}
gfx_putc('\n');
gfx_con.fntsz = prevFontSize;
}
static int abs(int x)
{
if (x < 0)
return -x;
return x;
}
void gfx_set_pixel(u32 x, u32 y, u32 color)
{
gfx_ctxt.fb[x + y * gfx_ctxt.stride] = color;
}
void gfx_line(int x0, int y0, int x1, int y1, u32 color)
{
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = (dx > dy ? dx : -dy) / 2, e2;
while (1)
{
gfx_set_pixel(x0, y0, color);
if (x0 == x1 && y0 == y1)
break;
e2 = err;
if (e2 >-dx)
{
err -= dy;
x0 += sx;
}
if (e2 < dy)
{
err += dx;
y0 += sy;
}
}
}
void gfx_set_rect_grey(const u8 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y)
{
u32 pos = 0;
for (u32 y = pos_y; y < (pos_y + size_y); y++)
{
for (u32 x = pos_x; x < (pos_x + size_x); x++)
{
memset(&gfx_ctxt.fb[x + y*gfx_ctxt.stride], buf[pos], 4);
pos++;
}
}
}
void gfx_set_rect_rgb(const u8 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y)
{
u32 pos = 0;
for (u32 y = pos_y; y < (pos_y + size_y); y++)
{
for (u32 x = pos_x; x < (pos_x + size_x); x++)
{
gfx_ctxt.fb[x + y * gfx_ctxt.stride] = buf[pos + 2] | (buf[pos + 1] << 8) | (buf[pos] << 16);
pos+=3;
}
}
}
void gfx_set_rect_argb(const u32 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y)
{
u32 *ptr = (u32 *)buf;
for (u32 y = pos_y; y < (pos_y + size_y); y++)
for (u32 x = pos_x; x < (pos_x + size_x); x++)
gfx_ctxt.fb[x + y * gfx_ctxt.stride] = *ptr++;
}
void gfx_render_bmp_argb(const u32 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y)
{
for (u32 y = pos_y; y < (pos_y + size_y); y++)
{
for (u32 x = pos_x; x < (pos_x + size_x); x++)
gfx_ctxt.fb[x + y * gfx_ctxt.stride] = buf[(size_y + pos_y - 1 - y ) * size_x + x - pos_x];
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (c) 2018 naehrwert
* Copyright (c) 2018-2021 CTCaer
* Copyright (c) 2018 M4xw
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _GFX_H_
#define _GFX_H_
#include <bdk.h>
#define TXT_CLR_BG 0xFF1B1B1B // Dark Grey.
#define TXT_CLR_DEFAULT 0xFFCCCCCC // Light Grey.
#define TXT_CLR_WARNING 0xFFFFDD00 // Yellow.
#define TXT_CLR_ERROR 0xFFFF0000 // Red.
#define TXT_CLR_CYAN_L 0xFF00CCFF // Light Cyan.
#define TXT_CLR_TURQUOISE 0xFF00FFCC // Turquoise.
#define TXT_CLR_ORANGE 0xFFFFBA00 // Orange.
#define TXT_CLR_GREENISH 0xFF96FF00 // Toxic Green.
#define TXT_CLR_GREEN_D 0xFF008800 // Dark Green.
#define TXT_CLR_RED_D 0xFF880000 // Dark Red.
#define TXT_CLR_GREY_D 0xFF303030 // Darkest Grey.
#define TXT_CLR_GREY_DM 0xFF444444 // Darker Grey.
#define TXT_CLR_GREY_M 0xFF555555 // Dark Grey.
#define TXT_CLR_GREY 0xFF888888 // Grey.
#define EPRINTF(text) gfx_eputs(text)
#define EPRINTFARGS(text, args...) gfx_printf("%k"text"%k\n", TXT_CLR_ERROR, args, TXT_CLR_DEFAULT)
#define WPRINTF(text) gfx_wputs(text)
#define WPRINTFARGS(text, args...) gfx_printf("%k"text"%k\n", TXT_CLR_WARNING, args, TXT_CLR_DEFAULT)
typedef struct _gfx_ctxt_t
{
u32 *fb;
u32 width;
u32 height;
u32 stride;
} gfx_ctxt_t;
typedef struct _gfx_con_t
{
gfx_ctxt_t *gfx_ctxt;
u32 fntsz;
u32 x;
u32 y;
u32 savedx;
u32 savedy;
u32 fgcol;
int fillbg;
u32 bgcol;
bool mute;
} gfx_con_t;
// Global gfx console and context.
extern gfx_ctxt_t gfx_ctxt;
extern gfx_con_t gfx_con;
void gfx_init_ctxt(u32 *fb, u32 width, u32 height, u32 stride);
void gfx_clear_grey(u8 color);
void gfx_clear_partial_grey(u8 color, u32 pos_x, u32 height);
void gfx_clear_color(u32 color);
void gfx_con_init();
void gfx_con_setcol(u32 fgcol, int fillbg, u32 bgcol);
void gfx_con_getpos(u32 *x, u32 *y);
void gfx_con_setpos(u32 x, u32 y);
void gfx_putc(char c);
void gfx_puts(const char *s);
void gfx_wputs(const char *s);
void gfx_eputs(const char *s);
void gfx_printf(const char *fmt, ...) /* __attribute__((format(printf, 1, 2))) */;
void gfx_hexdump(u32 base, const void *buf, u32 len);
void gfx_set_pixel(u32 x, u32 y, u32 color);
void gfx_line(int x0, int y0, int x1, int y1, u32 color);
void gfx_put_small_sep();
void gfx_put_big_sep();
void gfx_set_rect_grey(const u8 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y);
void gfx_set_rect_rgb(const u8 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y);
void gfx_set_rect_argb(const u32 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y);
void gfx_render_bmp_argb(const u32 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y);
#endif

View File

@@ -0,0 +1,114 @@
/*
* Copyright (c) 2018-2024 CTCaer
* Copyright (c) 2019 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <bdk.h>
#include "secmon_exo.h"
// Atmosphère reboot-to-fatal-error.
typedef struct _atm_fatal_error_ctx
{
u32 magic;
u32 error_desc;
u64 title_id;
union
{
u64 gprs[32];
struct
{
u64 _gprs[29];
u64 fp;
u64 lr;
u64 sp;
};
};
u64 pc;
u64 module_base;
u32 pstate;
u32 afsr0;
u32 afsr1;
u32 esr;
u64 far;
u64 report_identifier; // Normally just system tick.
u64 stack_trace_size;
u64 stack_dump_size;
u64 stack_trace[0x20];
u8 stack_dump[0x100];
u8 tls[0x100];
} atm_fatal_error_ctx;
#define ATM_FATAL_ERR_CTX_ADDR 0x4003E000
#define ATM_FATAL_MAGIC 0x30454641 // AFE0
#define HOS_PID_BOOT2 0x8
static const char *get_error_desc(u32 error_desc)
{
switch (error_desc)
{
case 0x100:
return "IABRT"; // Instruction Abort.
case 0x101:
return "DABRT"; // Data Abort.
case 0x102:
return "IUA"; // Instruction Unaligned Access.
case 0x103:
return "DUA"; // Data Unaligned Access.
case 0x104:
return "UDF"; // Undefined Instruction.
case 0x106:
return "SYS"; // System Error.
case 0x301:
return "SVC"; // Bad arguments or unimplemented SVC.
case 0xF00:
return "KRNL"; // Kernel panic.
case 0xFFD:
return "SO"; // Stack Overflow.
case 0xFFE:
return "std::abort";
default:
return "UNK";
}
}
void secmon_exo_check_panic()
{
volatile atm_fatal_error_ctx *rpt = (atm_fatal_error_ctx *)ATM_FATAL_ERR_CTX_ADDR;
// Mask magic to maintain compatibility with any AFE version, thanks to additive struct members.
if ((rpt->magic & 0xF0FFFFFF) != ATM_FATAL_MAGIC)
return;
gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0);
WPRINTF("Panic occurred while running Atmosphere.\n\n");
WPRINTFARGS("Title ID: %08X%08X", (u32)((u64)rpt->title_id >> 32), (u32)rpt->title_id);
WPRINTFARGS("Error: %s (0x%x)\n", get_error_desc(rpt->error_desc), rpt->error_desc);
// Check if mixed atmosphere sysmodules.
if ((u32)rpt->title_id == HOS_PID_BOOT2)
WPRINTF("Mismatched Atmosphere files?\n");
// Change magic to invalid, to prevent double-display of error/bootlooping.
rpt->magic = 0;
display_backlight_brightness(100, 1000);
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) 2018-2021 CTCaer
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _SECMON_EXO_H_
#define _SECMON_EXO_H_
#include <bdk.h>
void secmon_exo_check_panic();
#endif

View File

@@ -0,0 +1,25 @@
ENTRY(_start)
SECTIONS {
PROVIDE(__ipl_start = IPL_LOAD_ADDR);
. = __ipl_start;
.text : {
*(.text._start);
KEEP(*(._boot_cfg));
KEEP(*(._ipl_version));
*(.text._irq_setup);
*(.text*);
}
.data : {
*(.data*);
*(.rodata*);
}
. = ALIGN(0x10);
__ipl_end = .;
.bss : {
__bss_start = .;
*(COMMON)
*(.bss*)
__bss_end = .;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2018 naehrwert
*
* Copyright (c) 2018-2024 CTCaer
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <bdk.h>
#include "hos/secmon_exo.h"
typedef struct _log_ctx
{
u32 magic;
u32 sz;
u32 start;
u32 end;
char buf[];
} log_ctx_t;
#define IRAM_LOG_CTX_ADDR 0x4003C000
static void check_log(){
volatile log_ctx_t *log_ctx = (log_ctx_t*)IRAM_LOG_CTX_ADDR;
if(log_ctx->magic == 0xaabbccdd){
gfx_printf("\nLogs:\n");
gfx_printf((char*)log_ctx->buf);
}
}
volatile nyx_storage_t *nyx_str = (nyx_storage_t *)NYX_STORAGE_ADDR;
extern void pivot_stack(u32 stack_top);
void ipl_main()
{
// Do initial HW configuration. This is compatible with consecutive reruns without a reset.
hw_init();
// Pivot the stack under IPL. (Only max 4KB is needed).
pivot_stack(IPL_LOAD_ADDR);
// Place heap at a place outside of L4T/HOS configuration and binaries.
heap_init((void *)IPL_HEAP_START);
// Prep RTC regs for read. Needed for T210B01 R2C.
max77620_rtc_prep_read();
// Initialize display.
display_init();
u32 *fb = display_init_window_a_pitch();
gfx_init_ctxt(fb, 720, 1280, 720);
gfx_con_init();
// Initialize backlight PWM.
display_backlight_pwm_init();
display_backlight_brightness(100, 0);
// Show AMS errors
secmon_exo_check_panic();
check_log();
gfx_printf("\n\nPress POWER to power off\nPress VOLUME to boot RCM\n");
msleep(250);
do{
u8 btn = btn_read();
if(btn & BTN_POWER){
power_set_state(POWER_OFF);
}
if(btn & (BTN_VOL_DOWN | BTN_VOL_UP)){
power_set_state(REBOOT_RCM);
}
}while(true);
// Halt BPMP if we managed to get out of execution.
while (true)
bpmp_halt();
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) 2018 naehrwert
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.section .text._start
.arm
.extern _reloc_ipl
.type _reloc_ipl, %function
.extern memset
.type memset, %function
.extern _irq_setup
.type _irq_setup, %function
.globl _start
.type _start, %function
_start:
ADR R0, _start
LDR R1, =__ipl_start
CMP R0, R1
BEQ _real_start
/* If we are not in the right location already, copy a relocator to upper IRAM. */
ADR R2, _reloc_ipl
LDR R3, =0x4003FF00
MOV R4, #(_real_start - _reloc_ipl)
_copy_loop:
LDMIA R2!, {R5}
STMIA R3!, {R5}
SUBS R4, #4
BNE _copy_loop
/* Use the relocator to copy ourselves into the right place. */
LDR R2, =__ipl_end
SUB R2, R2, R1
LDR R3, =_real_start
LDR R4, =0x4003FF00
BX R4
_reloc_ipl:
LDMIA R0!, {R4-R7}
STMIA R1!, {R4-R7}
SUBS R2, #0x10
BNE _reloc_ipl
/* Jump to the relocated entry. */
BX R3
_real_start:
/* Initially, we place our stack under relocator but will move it to under the payload. */
/* This depends on application scope. */
LDR SP, =0x4003FF00
LDR R0, =__bss_start
EOR R1, R1, R1
LDR R2, =__bss_end
SUB R2, R2, R0
BL memset
BL _irq_setup
B .
.globl pivot_stack
.type pivot_stack, %function
pivot_stack:
MOV SP, R0
BX LR

View File

@@ -0,0 +1,22 @@
** FOR DEVELOPERS UTILIZING SYSCLK API ONLY **
Ensure you include the latest hoc-clk ipc and header files in your project before proceeding
Before running migration replacements, change every reference to sys-clk's ramload api to this
ramLoad -> partLoad
SysClkRamLoad_All -> HocClkPartLoad_EMC
SysClkRamLoad_Cpu -> HocClkPartLoad_EMCCpu
API version reference must be changed. compare to HOCCLK_IPC_API_VERSION
If you use the service name, use HOCCLK_IPC_SERVICE_NAME
Remove checks for the u8 enabled in sysclk clockmanager struct. Check if hocclk is enabled by listening to IPC results
Run the following replace commands (case sensitive):
sysclk -> hocclk
SysClk -> HocClk
SYSCLK -> HOCCLK
sysClk -> hocClk
Your project is now migrated to run with HOC

View File

@@ -20,7 +20,7 @@ make -j$CORES
popd > /dev/null
mkdir -p "$DIST_DIR/atmosphere/contents/$TITLE_ID/flags"
cp -vf "$ROOT_DIR/sysmodule/out/horizon-oc.nsp" "$DIST_DIR/atmosphere/contents/$TITLE_ID/exefs.nsp"
cp -vf "$ROOT_DIR/sysmodule/out/hoc-clk.nsp" "$DIST_DIR/atmosphere/contents/$TITLE_ID/exefs.nsp"
>"$DIST_DIR/atmosphere/contents/$TITLE_ID/flags/boot2.flag"
cp -vf "$ROOT_DIR/sysmodule/toolbox.json" "$DIST_DIR/atmosphere/contents/$TITLE_ID/toolbox.json"
@@ -38,5 +38,4 @@ cp -vf "$ROOT_DIR/config.ini.template" "$DIST_DIR/config/horizon-oc/config.ini.t
cp -vf "$ROOT_DIR/../../README.md" "$DIST_DIR/README.md"
echo "*** lang ***"
cp -r "$ROOT_DIR/overlay/lang/" "$DIST_DIR/config/horizon-oc/lang/"

View File

@@ -12,9 +12,9 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
#pragma once
#include <switch.h>
#include <inttypes.h>
@@ -71,25 +71,6 @@ typedef struct {
#define IS_BATTERY_CHARGING_ENABLED(info) (((info)->unk_x14 >> 8) & 1)
Result batteryInfoInitialize(void);
void batteryInfoExit(void);
Result batteryInfoGetChargeInfo(BatteryChargeInfo *out);
Result batteryInfoGetChargePercentage(u32 *out);
Result batteryInfoIsEnoughPowerSupplied(bool *out);
Result batteryInfoEnableCharging(void);
Result batteryInfoDisableCharging(void);
Result batteryInfoEnableFastCharging(void);
Result batteryInfoDisableFastCharging(void);
const char* batteryInfoGetChargerTypeString(BatteryChargerType type);
const char* batteryInfoGetPowerRoleString(BatteryPowerRole role);
const char* batteryInfoGetPDStateString(BatteryPDControllerState state);
static inline int batteryInfoGetTemperatureMiliCelsius(BatteryChargeInfo *info) {
return info->BatteryTemperature;
}
@@ -106,147 +87,15 @@ static inline bool batteryInfoIsCharging(BatteryChargeInfo *info) {
return IS_BATTERY_CHARGING_ENABLED(info);
}
static const char* s_chargerTypeStrings[] = {
"None",
"Power Delivery",
"USB-C @ 1.5A",
"USB-C @ 3.0A",
"USB-DCP",
"USB-CDP",
"USB-SDP",
"Apple @ 0.5A",
"Apple @ 1.0A",
"Apple @ 2.0A",
};
static const char* s_powerRoleStrings[] = {
"Unknown",
"Sink",
"Source",
};
static const char* s_pdStateStrings[] = {
"Unknown",
"New PDO Received",
"No PD Source",
"RDO Accepted"
};
// Internal PSM service handle
static Service g_psmService = {0};
static bool g_batteryInfoInitialized = false;
// Internal PSM command implementations
static Result psmGetBatteryChargeInfoFields(BatteryChargeInfo *out) {
if (!g_batteryInfoInitialized)
return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
return serviceDispatchOut(&g_psmService, 17, *out);
}
static Result psmEnableBatteryCharging_internal(void) {
if (!g_batteryInfoInitialized)
return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
return serviceDispatch(&g_psmService, 2);
}
static Result psmDisableBatteryCharging_internal(void) {
if (!g_batteryInfoInitialized)
return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
return serviceDispatch(&g_psmService, 3);
}
static Result psmEnableFastBatteryCharging_internal(void) {
if (!g_batteryInfoInitialized)
return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
return serviceDispatch(&g_psmService, 10);
}
static Result psmDisableFastBatteryCharging_internal(void) {
if (!g_batteryInfoInitialized)
return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
return serviceDispatch(&g_psmService, 11);
}
Result batteryInfoInitialize(void) {
if (g_batteryInfoInitialized)
return 0;
Result rc = psmInitialize();
if (R_SUCCEEDED(rc)) {
memcpy(&g_psmService, psmGetServiceSession(), sizeof(Service));
g_batteryInfoInitialized = true;
}
return rc;
}
void batteryInfoExit(void) {
if (g_batteryInfoInitialized) {
psmExit();
memset(&g_psmService, 0, sizeof(Service));
g_batteryInfoInitialized = false;
}
}
Result batteryInfoGetChargeInfo(BatteryChargeInfo *out) {
if (!out)
return MAKERESULT(Module_Libnx, LibnxError_BadInput);
return psmGetBatteryChargeInfoFields(out);
}
Result batteryInfoGetChargePercentage(u32 *out) {
if (!g_batteryInfoInitialized)
return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
return psmGetBatteryChargePercentage(out);
}
Result batteryInfoIsEnoughPowerSupplied(bool *out) {
if (!g_batteryInfoInitialized)
return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
return psmIsEnoughPowerSupplied(out);
}
Result batteryInfoEnableCharging(void) {
return psmEnableBatteryCharging_internal();
}
Result batteryInfoDisableCharging(void) {
return psmDisableBatteryCharging_internal();
}
Result batteryInfoEnableFastCharging(void) {
return psmEnableFastBatteryCharging_internal();
}
Result batteryInfoDisableFastCharging(void) {
return psmDisableFastBatteryCharging_internal();
}
const char* batteryInfoGetChargerTypeString(BatteryChargerType type) {
if (type < 0 || type > ChargerType_Apple_2000mA)
return "Unknown";
return s_chargerTypeStrings[type];
}
const char* batteryInfoGetPowerRoleString(BatteryPowerRole role) {
if (role < PowerRole_Sink || role > PowerRole_Source)
return s_powerRoleStrings[0];
return s_powerRoleStrings[role];
}
const char* batteryInfoGetPDStateString(BatteryPDControllerState state) {
if (state < PDState_NewPDO || state > PDState_AcceptedRDO)
return s_pdStateStrings[0];
return s_pdStateStrings[state];
}
Result batteryInfoInitialize(void);
void batteryInfoExit(void);
Result batteryInfoGetChargeInfo(BatteryChargeInfo *out);
Result batteryInfoGetChargePercentage(u32 *out);
Result batteryInfoIsEnoughPowerSupplied(bool *out);
Result batteryInfoEnableCharging(void);
Result batteryInfoDisableCharging(void);
Result batteryInfoEnableFastCharging(void);
Result batteryInfoDisableFastCharging(void);
const char* batteryInfoGetChargerTypeString(BatteryChargerType type);
const char* batteryInfoGetPowerRoleString(BatteryPowerRole role);
const char* batteryInfoGetPDStateString(BatteryPDControllerState state);

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <stdio.h>
#include <stdint.h>
namespace crc32 {
uint32_t crc32(const uint8_t *data, size_t length);
uint32_t checksum_file(const char *filename);
}

View File

@@ -12,9 +12,9 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
/* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
@@ -42,13 +42,13 @@ extern "C" {
// typedef std::int16_t s16;
// typedef std::uint16_t u16;
#include "sysclk/ipc.h"
#include "sysclk/board.h"
#include "sysclk/clock_manager.h"
#include "sysclk/apm.h"
#include "sysclk/config.h"
#include "sysclk/errors.h"
#include "sysclk/psm_ext.h"
#include "hocclk/ipc.h"
#include "hocclk/board.h"
#include "hocclk/clock_manager.h"
#include "hocclk/apm.h"
#include "hocclk/config.h"
#include "hocclk/errors.h"
#include "hocclk/psm_ext.h"
#ifdef __cplusplus
}

View File

@@ -34,6 +34,6 @@ typedef struct {
uint32_t cpu_hz;
uint32_t gpu_hz;
uint32_t mem_hz;
} SysClkApmConfiguration;
} HocClkApmConfiguration;
extern SysClkApmConfiguration sysclk_g_apm_configurations[];
extern HocClkApmConfiguration hocclk_g_apm_configurations[];

View File

@@ -33,28 +33,29 @@
#include <switch/types.h>
typedef enum
{
SysClkSocType_Erista = 0,
SysClkSocType_Mariko,
SysClkSocType_EnumMax
} SysClkSocType;
HocClkSocType_Erista = 0, // T210, found in Icosa and Copper
HocClkSocType_Mariko, // T214/T210B01, found in Hoag, Iowa, Calcio and Aula
// HocClkSocType_Drake, // T239, found in Switch 2. Maybe someday...
HocClkSocType_EnumMax
} HocClkSocType;
typedef enum
{
HorizonOCConsoleType_Icosa = 0,
HorizonOCConsoleType_Copper,
HorizonOCConsoleType_Hoag,
HorizonOCConsoleType_Iowa,
HorizonOCConsoleType_Calcio,
HorizonOCConsoleType_Aula,
HorizonOCConsoleType_EnumMax,
} HorizonOCConsoleType;
HocClkConsoleType_Icosa = 0, // V1
HocClkConsoleType_Copper, // Unreleased Erista
HocClkConsoleType_Hoag, // Lite
HocClkConsoleType_Iowa, // V2
HocClkConsoleType_Calcio, // Unreleased Mariko
HocClkConsoleType_Aula, // OLED
HocClkConsoleType_EnumMax,
} HocClkConsoleType;
typedef enum {
HocClkVoltage_SOC = 0,
HocClkVoltage_EMCVDD2,
HocClkVoltage_CPU,
HocClkVoltage_GPU,
HocClkVoltage_EMCVDDQ_MarikoOnly,
HocClkVoltage_EMCVDDQ, // Returns VDD2 on Erista
HocClkVoltage_Display,
HocClkVoltage_Battery,
HocClkVoltage_EnumMax,
@@ -62,58 +63,66 @@ typedef enum {
typedef enum
{
SysClkProfile_Handheld = 0,
SysClkProfile_HandheldCharging,
SysClkProfile_HandheldChargingUSB,
SysClkProfile_HandheldChargingOfficial,
SysClkProfile_Docked,
SysClkProfile_EnumMax
} SysClkProfile;
HocClkProfile_Handheld = 0,
HocClkProfile_HandheldCharging, // Not a real profile, just a marker
HocClkProfile_HandheldChargingUSB,
HocClkProfile_HandheldChargingOfficial,
HocClkProfile_Docked, // Not shown on Lites
HocClkProfile_EnumMax
} HocClkProfile;
typedef enum
{
SysClkModule_CPU = 0,
SysClkModule_GPU,
SysClkModule_MEM,
HorizonOCModule_Governor,
HorizonOCModule_Display,
SysClkModule_EnumMax,
} SysClkModule;
HocClkModule_CPU = 0,
HocClkModule_GPU,
HocClkModule_MEM,
HocClkModule_Governor,
HocClkModule_Display,
HocClkModule_EnumMax,
} HocClkModule;
typedef enum
{
SysClkThermalSensor_SOC = 0,
SysClkThermalSensor_PCB,
SysClkThermalSensor_Skin,
HorizonOCThermalSensor_Battery,
HorizonOCThermalSensor_PMIC,
SysClkThermalSensor_EnumMax
} SysClkThermalSensor;
HocClkThermalSensor_SOC = 0,
HocClkThermalSensor_PCB,
HocClkThermalSensor_Skin,
HocClkThermalSensor_Battery,
HocClkThermalSensor_PMIC, // Always return 50.0C, as thats the only reasonable value the PMIC sensor can generate
HocClkThermalSensor_CPU,
HocClkThermalSensor_GPU,
HocClkThermalSensor_MEM, // Returns the PLLX sensor value on Mariko
HocClkThermalSensor_PLLX,
HocClkThermalSensor_EnumMax
} HocClkThermalSensor;
typedef enum
{
SysClkPowerSensor_Now = 0,
SysClkPowerSensor_Avg,
SysClkPowerSensor_EnumMax
} SysClkPowerSensor;
HocClkPowerSensor_Now = 0,
HocClkPowerSensor_Avg,
HocClkPowerSensor_EnumMax
} HocClkPowerSensor;
typedef enum
{
SysClkPartLoad_EMC = 0,
SysClkPartLoad_EMCCpu,
HocClkPartLoad_EMC = 0,
HocClkPartLoad_EMCCpu,
HocClkPartLoad_GPU,
HocClkPartLoad_CPUMax,
HocClkPartLoad_BAT,
HocClkPartLoad_BAT, // Battery raw charge percentage
HocClkPartLoad_FAN,
SysClkPartLoad_EnumMax
} SysClkPartLoad;
HocClkPartLoad_RamBWAll,
HocClkPartLoad_RamBWCpu,
HocClkPartLoad_RamBWGpu,
HocClkPartLoad_RamBWPeak,
HocClkPartLoad_EnumMax
} HocClkPartLoad;
typedef enum {
HorizonOCSpeedo_CPU = 0,
HorizonOCSpeedo_GPU,
HorizonOCSpeedo_SOC,
HorizonOCSpeedo_EnumMax,
} HorizonOCSpeedo;
HocClkSpeedo_CPU = 0,
HocClkSpeedo_GPU,
HocClkSpeedo_SOC,
HocClkSpeedo_EnumMax,
} HocClkSpeedo;
typedef enum {
GPUUVLevel_NoUV = 0,
@@ -149,13 +158,25 @@ typedef enum {
ComponentGovernor_EnumMax,
} ComponentGovernorState;
typedef enum {
RamDisplayMode_VDD2VDDQ = 0,
RamDisplayMode_VDD2Usage,
RamDisplayMode_VDDQUsage,
RamDisplayMode_VDD2 = 0,
RamDisplayMode_VDDQ,
RamDisplayMode_EnumMax,
} RamDisplayMode;
#define SYSCLK_ENUM_VALID(n, v) ((v) < n##_EnumMax)
typedef enum {
MemoryFrequencyMeasurementMode_Actmon = 0,
MemoryFrequencyMeasurementMode_PLL,
MemoryFrequencyMeasurementMode_EnumMax,
} MemoryFrequencyMeasurementMode;
typedef enum {
RamDisplayUnit_MHz = 0,
RamDisplayUnit_MTs,
RamDisplayUnit_MHzMTs,
RamDisplayUnit_EnumMax,
} RamDisplayUnit;
#define HOCCLK_ENUM_VALID(n, v) ((v) < n##_EnumMax)
// Packed u32
// Bits 0-7 - CPU
@@ -176,74 +197,80 @@ inline u8 GovernorStateVrr(u32 p) {
return (u8)((p >> 16) & 0xFF);
}
static inline const char* sysclkFormatModule(SysClkModule module, bool pretty)
static inline const char* hocclkFormatModule(HocClkModule module, bool pretty)
{
switch(module)
{
case SysClkModule_CPU:
case HocClkModule_CPU:
return pretty ? "CPU" : "cpu";
case SysClkModule_GPU:
case HocClkModule_GPU:
return pretty ? "GPU" : "gpu";
case SysClkModule_MEM:
case HocClkModule_MEM:
return pretty ? "Memory" : "mem";
case HorizonOCModule_Display:
case HocClkModule_Display:
return pretty ? "Display" : "display";
case HorizonOCModule_Governor:
case HocClkModule_Governor:
return pretty ? "Governor" : "governor";
default:
return "null";
}
}
static inline const char* sysclkFormatThermalSensor(SysClkThermalSensor thermSensor, bool pretty)
static inline const char* hocclkFormatThermalSensor(HocClkThermalSensor thermSensor, bool pretty)
{
switch(thermSensor)
{
case SysClkThermalSensor_SOC:
switch(thermSensor) {
case HocClkThermalSensor_SOC:
return pretty ? "SOC" : "soc";
case SysClkThermalSensor_PCB:
case HocClkThermalSensor_PCB:
return pretty ? "PCB" : "pcb";
case SysClkThermalSensor_Skin:
case HocClkThermalSensor_Skin:
return pretty ? "Skin" : "skin";
case HorizonOCThermalSensor_Battery:
case HocClkThermalSensor_Battery:
return pretty ? "BAT" : "battery";
case HorizonOCThermalSensor_PMIC:
case HocClkThermalSensor_PMIC:
return pretty ? "PMIC" : "pmic";
case HocClkThermalSensor_CPU:
return pretty ? "CPU" : "cpu";
case HocClkThermalSensor_GPU:
return pretty ? "GPU" : "gpu";
case HocClkThermalSensor_MEM:
return pretty ? "MEM" : "mem";
case HocClkThermalSensor_PLLX:
return pretty ? "PLLX" : "pllx";
default:
return NULL;
return "unknown";
}
}
static inline const char* sysclkFormatPowerSensor(SysClkPowerSensor powSensor, bool pretty)
static inline const char* hocclkFormatPowerSensor(HocClkPowerSensor powSensor, bool pretty)
{
switch(powSensor)
{
case SysClkPowerSensor_Now:
case HocClkPowerSensor_Now:
return pretty ? "Now" : "now";
case SysClkPowerSensor_Avg:
case HocClkPowerSensor_Avg:
return pretty ? "Avg" : "avg";
default:
return NULL;
return "unknown";
}
}
static inline const char* sysclkFormatProfile(SysClkProfile profile, bool pretty)
static inline const char* hocclkFormatProfile(HocClkProfile profile, bool pretty)
{
switch(profile)
{
case SysClkProfile_Docked:
case HocClkProfile_Docked:
return pretty ? "Docked" : "docked";
case SysClkProfile_Handheld:
case HocClkProfile_Handheld:
return pretty ? "Handheld" : "handheld";
case SysClkProfile_HandheldCharging:
case HocClkProfile_HandheldCharging:
return pretty ? "Charging" : "handheld_charging";
case SysClkProfile_HandheldChargingUSB:
case HocClkProfile_HandheldChargingUSB:
return pretty ? "USB Charger" : "handheld_charging_usb";
case SysClkProfile_HandheldChargingOfficial:
case HocClkProfile_HandheldChargingOfficial:
return pretty ? "PD Charger" : "handheld_charging_official";
default:
return NULL;
return "unknown";
}
}
@@ -258,13 +285,13 @@ static inline const char* hocClkFormatVoltage(HocClkVoltage voltage, bool pretty
return pretty ? "GPU" : "gpu";
case HocClkVoltage_EMCVDD2:
return pretty ? "VDD2" : "emcvdd2";
case HocClkVoltage_EMCVDDQ_MarikoOnly:
case HocClkVoltage_EMCVDDQ:
return pretty ? "VDDQ" : "vddq";
case HocClkVoltage_SOC:
return pretty ? "SOC" : "soc";
case HocClkVoltage_Display:
return pretty ? "Display" : "display";
default:
return NULL;
return "unknown";
}
}
}

View File

@@ -32,26 +32,26 @@
#include "../board.h"
#include "../ipc.h"
bool sysclkIpcRunning();
Result sysclkIpcInitialize(void);
void sysclkIpcExit(void);
bool hocclkIpcRunning();
Result hocclkIpcInitialize(void);
void hocclkIpcExit(void);
Result sysclkIpcGetAPIVersion(u32* out_ver);
Result sysclkIpcGetVersionString(char* out, size_t len);
Result sysclkIpcGetCurrentContext(SysClkContext* out_context);
Result sysclkIpcGetProfileCount(u64 tid, u8* out_count);
Result sysclkIpcSetEnabled(bool enabled);
Result sysclkIpcExitCmd();
Result sysclkIpcSetOverride(SysClkModule module, u32 hz);
Result sysclkIpcGetProfiles(u64 tid, SysClkTitleProfileList* out_profiles);
Result sysclkIpcSetProfiles(u64 tid, SysClkTitleProfileList* profiles);
Result sysclkIpcGetConfigValues(SysClkConfigValueList* out_configValues);
Result sysclkIpcSetConfigValues(SysClkConfigValueList* configValues);
Result sysclkIpcGetFreqList(SysClkModule module, u32* list, u32 maxCount, u32* outCount);
Result hocclkIpcGetAPIVersion(u32* out_ver);
Result hocclkIpcGetVersionString(char* out, size_t len);
Result hocclkIpcGetCurrentContext(HocClkContext* out_context);
Result hocclkIpcGetProfileCount(u64 tid, u8* out_count);
Result hocclkIpcSetEnabled(bool enabled);
Result hocclkIpcExitCmd();
Result hocclkIpcSetOverride(HocClkModule module, u32 hz);
Result hocclkIpcGetProfiles(u64 tid, HocClkTitleProfileList* out_profiles);
Result hocclkIpcSetProfiles(u64 tid, HocClkTitleProfileList* profiles);
Result hocclkIpcGetConfigValues(HocClkConfigValueList* out_configValues);
Result hocclkIpcSetConfigValues(HocClkConfigValueList* configValues);
Result hocclkIpcGetFreqList(HocClkModule module, u32* list, u32 maxCount, u32* outCount);
Result hocClkIpcSetKipData();
Result hocClkIpcGetKipData();
static inline Result sysclkIpcRemoveOverride(SysClkModule module)
static inline Result hocclkIpcRemoveOverride(HocClkModule module)
{
return sysclkIpcSetOverride(module, 0);
return hocclkIpcSetOverride(module, 0);
}

View File

@@ -12,9 +12,9 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
/* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
@@ -33,16 +33,16 @@
typedef struct
{
uint64_t applicationId;
SysClkProfile profile;
uint32_t freqs[SysClkModule_EnumMax];
uint32_t realFreqs[SysClkModule_EnumMax];
uint32_t overrideFreqs[SysClkModule_EnumMax];
uint32_t temps[SysClkThermalSensor_EnumMax];
int32_t power[SysClkPowerSensor_EnumMax];
uint32_t partLoad[SysClkPartLoad_EnumMax];
HocClkProfile profile;
uint32_t freqs[HocClkModule_EnumMax];
uint32_t realFreqs[HocClkModule_EnumMax];
uint32_t overrideFreqs[HocClkModule_EnumMax];
uint32_t temps[HocClkThermalSensor_EnumMax];
int32_t power[HocClkPowerSensor_EnumMax];
uint32_t partLoad[HocClkPartLoad_EnumMax];
uint32_t voltages[HocClkVoltage_EnumMax];
u16 speedos[HorizonOCSpeedo_EnumMax];
u16 iddq[HorizonOCSpeedo_EnumMax];
u16 speedos[HocClkSpeedo_EnumMax];
u16 iddq[HocClkSpeedo_EnumMax];
u16 waferX;
u16 waferY;
@@ -58,16 +58,21 @@ typedef struct
// FPS / Resolution
u8 fps;
u16 resolutionHeight;
} SysClkContext;
// Reserved for future use
u8 reserved[0x428];
} HocClkContext;
typedef struct
{
union {
uint32_t mhz[+SysClkProfile_EnumMax * +SysClkModule_EnumMax];
uint32_t mhzMap[+SysClkProfile_EnumMax][+SysClkModule_EnumMax];
uint32_t mhz[+HocClkProfile_EnumMax * +HocClkModule_EnumMax];
uint32_t mhzMap[+HocClkProfile_EnumMax][+HocClkModule_EnumMax];
};
} SysClkTitleProfileList;
} HocClkTitleProfileList;
#define SYSCLK_FREQ_LIST_MAX 32
#define HOCCLK_FREQ_LIST_MAX 32
#define GLOBAL_PROFILE_ID 0xA111111111111111
#define GLOBAL_PROFILE_ID 0xA111111111111111
static_assert(sizeof(HocClkContext) == 0x500);

View File

@@ -29,13 +29,13 @@
#include <stdint.h>
#include <stddef.h>
#include "board.h"
typedef enum {
SysClkConfigValue_PollingIntervalMs = 0,
SysClkConfigValue_TempLogIntervalMs,
SysClkConfigValue_FreqLogIntervalMs,
SysClkConfigValue_PowerLogIntervalMs,
SysClkConfigValue_CsvWriteIntervalMs,
HocClkConfigValue_PollingIntervalMs = 0,
HocClkConfigValue_TempLogIntervalMs,
HocClkConfigValue_FreqLogIntervalMs,
HocClkConfigValue_PowerLogIntervalMs,
HocClkConfigValue_CsvWriteIntervalMs,
HocClkConfigValue_UncappedClocks,
HocClkConfigValue_OverwriteBoostMode,
@@ -51,21 +51,26 @@ typedef enum {
HocClkConfigValue_LiteTDPLimit,
HorizonOCConfigValue_BatteryChargeCurrent,
HocClkConfigValue_BatteryChargeCurrent,
HorizonOCConfigValue_OverwriteRefreshRate,
HorizonOCConfigValue_MaxDisplayClockH,
HocClkConfigValue_OverwriteRefreshRate,
HocClkConfigValue_MaxDisplayClockH,
HorizonOCConfigValue_DVFSMode,
HorizonOCConfigValue_DVFSOffset,
HorizonOCConfigValue_LiveCpuUv,
HorizonOCConfigValue_EnableExperimentalSettings,
HocClkConfigValue_DVFSMode,
HocClkConfigValue_DVFSOffset,
HocClkConfigValue_LiveCpuUv,
HocClkConfigValue_EnableExperimentalSettings,
HorizonOCConfigValue_GPUScheduling,
HorizonOCConfigValue_GPUSchedulingMethod,
HocClkConfigValue_GPUScheduling,
HocClkConfigValue_GPUSchedulingMethod,
HorizonOCConfigValue_RAMVoltUsageDisplayMode,
HorizonOCConfigValue_CpuGovernorMinimumFreq,
HocClkConfigValue_RAMVoltDisplayMode,
HocClkConfigValue_CpuGovernorMinimumFreq,
HocClkConfigValue_DisplayVoltage,
HocClkConfigValue_MemoryFrequencyMeasurementMode,
HocClkConfigValue_RamDisplayUnit,
HocClkConfigValue_IsFirstLoad,
KipConfigValue_custRev,
// KipConfigValue_mtcConf,
@@ -75,6 +80,8 @@ typedef enum {
KipConfigValue_eristaEmcMaxClock,
KipConfigValue_eristaEmcMaxClock1,
KipConfigValue_eristaEmcMaxClock2,
KipConfigValue_stepMode,
KipConfigValue_marikoEmcMaxClock,
KipConfigValue_marikoEmcVddqVolt,
KipConfigValue_emcDvbShift,
@@ -87,6 +94,23 @@ typedef enum {
KipConfigValue_t6_tRTW,
KipConfigValue_t7_tWTR,
KipConfigValue_t8_tREFI,
KipConfigValue_timingEmcTbreak,
KipConfigValue_low_t6_tRTW,
KipConfigValue_low_t7_tWTR,
KipConfigValue_t2_tRP_cap,
KipConfigValue_read_latency_1333,
KipConfigValue_read_latency_1600,
KipConfigValue_read_latency_1866,
KipConfigValue_read_latency_2133,
KipConfigValue_write_latency_1333,
KipConfigValue_write_latency_1600,
KipConfigValue_write_latency_1866,
KipConfigValue_write_latency_2133,
KipConfigValue_mem_burst_read_latency,
KipConfigValue_mem_burst_write_latency,
@@ -172,27 +196,26 @@ typedef enum {
KipConfigValue_t7_tWTR_fine_tune,
KipCrc32,
HocClkConfigValue_IsFirstLoad,
SysClkConfigValue_EnumMax,
} SysClkConfigValue;
HocClkConfigValue_EnumMax,
} HocClkConfigValue;
typedef struct {
uint64_t values[SysClkConfigValue_EnumMax];
} SysClkConfigValueList;
uint64_t values[HocClkConfigValue_EnumMax];
} HocClkConfigValueList;
static inline const char* sysclkFormatConfigValue(SysClkConfigValue val, bool pretty)
static inline const char* hocclkFormatConfigValue(HocClkConfigValue val, bool pretty)
{
switch(val)
{
case SysClkConfigValue_PollingIntervalMs:
case HocClkConfigValue_PollingIntervalMs:
return pretty ? "Polling Interval (ms)" : "poll_interval_ms";
case SysClkConfigValue_TempLogIntervalMs:
case HocClkConfigValue_TempLogIntervalMs:
return pretty ? "Temperature logging interval (ms)" : "temp_log_interval_ms";
case SysClkConfigValue_FreqLogIntervalMs:
case HocClkConfigValue_FreqLogIntervalMs:
return pretty ? "Frequency logging interval (ms)" : "freq_log_interval_ms";
case SysClkConfigValue_PowerLogIntervalMs:
case HocClkConfigValue_PowerLogIntervalMs:
return pretty ? "Power logging interval (ms)" : "power_log_interval_ms";
case SysClkConfigValue_CsvWriteIntervalMs:
case HocClkConfigValue_CsvWriteIntervalMs:
return pretty ? "CSV write interval (ms)" : "csv_write_interval_ms";
case HocClkConfigValue_UncappedClocks:
@@ -221,37 +244,47 @@ static inline const char* sysclkFormatConfigValue(SysClkConfigValue val, bool pr
case HocClkConfigValue_LiteTDPLimit:
return pretty ? "Handheld TDP Limit" : "tdp_limit_l";
case HorizonOCConfigValue_BatteryChargeCurrent:
case HocClkConfigValue_BatteryChargeCurrent:
return pretty ? "Battery Charge Current" : "bat_charge_current";
case HorizonOCConfigValue_OverwriteRefreshRate:
case HocClkConfigValue_OverwriteRefreshRate:
return pretty ? "Display Refresh Rate Changing" : "drr_changing";
case HorizonOCConfigValue_MaxDisplayClockH:
case HocClkConfigValue_MaxDisplayClockH:
return pretty ? "Max Display Clock (Handheld)" : "drr_max_clock";
case HorizonOCConfigValue_DVFSMode:
case HocClkConfigValue_DVFSMode:
return pretty ? "DVFS Mode" : "dvfs_mode";
case HorizonOCConfigValue_DVFSOffset:
case HocClkConfigValue_DVFSOffset:
return pretty ? "DVFS Offset" : "dvfs_offset";
case HorizonOCConfigValue_GPUScheduling:
case HocClkConfigValue_GPUScheduling:
return pretty ? "GPU Scheduling" : "gpu_scheduling";
case HorizonOCConfigValue_GPUSchedulingMethod:
case HocClkConfigValue_GPUSchedulingMethod:
return pretty ? "GPU Scheduling Method" : "gpu_sched_method";
case HorizonOCConfigValue_LiveCpuUv:
case HocClkConfigValue_LiveCpuUv:
return pretty ? "Live CPU Undervolt" : "live_cpu_uv";
case HorizonOCConfigValue_EnableExperimentalSettings:
case HocClkConfigValue_EnableExperimentalSettings:
return pretty ? "Enable Experimental Settings" : "enable_experimental_settings";
case HorizonOCConfigValue_RAMVoltUsageDisplayMode:
case HocClkConfigValue_RAMVoltDisplayMode:
return pretty ? "RAM Voltage / Usage Display Mode" : "ram_volt_usage_display_mode";
case HorizonOCConfigValue_CpuGovernorMinimumFreq:
case HocClkConfigValue_CpuGovernorMinimumFreq:
return pretty ? "CPU Governor Minimum Frequency" : "cpu_gov_min_freq";
case HocClkConfigValue_DisplayVoltage:
return pretty ? "Display Voltage" : "display_voltage";
case HocClkConfigValue_MemoryFrequencyMeasurementMode:
return pretty ? "RAM Frequency Measurement Mode" : "mem_freq_measurement_mode";
case HocClkConfigValue_RamDisplayUnit:
return pretty ? "RAM Frequency Display Unit" : "RAM_display_unit";
// KIP config values
case KipConfigValue_custRev:
return pretty ? "Custom Revision" : "kip_cust_rev";
@@ -269,6 +302,8 @@ static inline const char* sysclkFormatConfigValue(SysClkConfigValue val, bool pr
return pretty ? "Erista EMC Max Clock 2" : "erista_emc_max_clock1";
case KipConfigValue_eristaEmcMaxClock2:
return pretty ? "Erista EMC Max Clock 3" : "erista_emc_max_clock2";
case KipConfigValue_stepMode:
return pretty ? "Step Mode:" : "step_mode";
case KipConfigValue_marikoEmcMaxClock:
return pretty ? "Mariko EMC Max Clock" : "mariko_emc_max_clock";
case KipConfigValue_marikoEmcVddqVolt:
@@ -293,6 +328,35 @@ static inline const char* sysclkFormatConfigValue(SysClkConfigValue val, bool pr
return pretty ? "t7 - tWTR" : "t7_twtr";
case KipConfigValue_t8_tREFI:
return pretty ? "t8 - tREFI" : "t8_trefi";
case KipConfigValue_timingEmcTbreak:
return pretty ? "Timing Emc Tbreak" : "timingEmcTbreak";
case KipConfigValue_low_t6_tRTW:
return pretty ? "Low T6 - tRTW" : "low_t6_tRTW";
case KipConfigValue_low_t7_tWTR:
return pretty ? "Low T7 - tWTR" : "low_t7_tWTR";
case KipConfigValue_t2_tRP_cap:
return pretty ? "t2 - trp 1333WL Cap" : "t2_tRP_cap";
case KipConfigValue_read_latency_1333:
return pretty ? "1333 Read Latency" : "read_latency_1333";
case KipConfigValue_read_latency_1600:
return pretty ? "1600 Read Latency" : "read_latency_1600";
case KipConfigValue_read_latency_1866:
return pretty ? "1866 Read Latency" : "read_latency_1866";
case KipConfigValue_read_latency_2133:
return pretty ? "2133 Read Latency" : "read_latency_2133";
case KipConfigValue_write_latency_1333:
return pretty ? "1333 Write Latency" : "write_latency_1333";
case KipConfigValue_write_latency_1600:
return pretty ? "1600 Write Latency" : "write_latency_1600";
case KipConfigValue_write_latency_1866:
return pretty ? "1866 Write Latency" : "write_latency_1866";
case KipConfigValue_write_latency_2133:
return pretty ? "2133 Write Latency" : "write_latency_2133";
case KipConfigValue_mem_burst_read_latency:
return pretty ? "Memory Burst Read Latency" : "mem_burst_read_latency";
case KipConfigValue_mem_burst_write_latency:
@@ -403,7 +467,7 @@ static inline const char* sysclkFormatConfigValue(SysClkConfigValue val, bool pr
case KipConfigValue_g_volt_e_998400: return pretty ? "Erista GPU Volt 998 MHz" : "g_volt_e_998400";
case KipConfigValue_g_volt_e_1036800: return pretty ? "Erista GPU Volt 1036 MHz" : "g_volt_e_1036800";
case KipConfigValue_g_volt_e_1075200: return pretty ? "Erista GPU Volt 1075 MHz" : "g_volt_e_1075200";
case KipConfigValue_t6_tRTW_fine_tune: return pretty ? "t6 - tRTW Fine Tune" : "t6_tRTW_fine_fune";
case KipConfigValue_t6_tRTW_fine_tune: return pretty ? "t6 - tRTW Fine Tune" : "t6_tRTW_fine_tune";
case KipConfigValue_t7_tWTR_fine_tune: return pretty ? "t7 - tWTR Fine Tune" : "t7_tWTR_fine_tune";
case KipCrc32:
return pretty ? "CRC32" : "crc32";
@@ -414,24 +478,27 @@ static inline const char* sysclkFormatConfigValue(SysClkConfigValue val, bool pr
}
}
static inline uint64_t sysclkDefaultConfigValue(SysClkConfigValue val)
static inline uint64_t hocclkDefaultConfigValue(HocClkConfigValue val)
{
switch(val)
{
case SysClkConfigValue_PollingIntervalMs:
case HocClkConfigValue_PollingIntervalMs:
return 300ULL;
case SysClkConfigValue_TempLogIntervalMs:
case SysClkConfigValue_FreqLogIntervalMs:
case SysClkConfigValue_PowerLogIntervalMs:
case SysClkConfigValue_CsvWriteIntervalMs:
case HocClkConfigValue_TempLogIntervalMs:
case HocClkConfigValue_FreqLogIntervalMs:
case HocClkConfigValue_PowerLogIntervalMs:
case HocClkConfigValue_CsvWriteIntervalMs:
case HocClkConfigValue_UncappedClocks:
case HocClkConfigValue_OverwriteBoostMode:
case HorizonOCConfigValue_BatteryChargeCurrent:
case HorizonOCConfigValue_OverwriteRefreshRate:
case HorizonOCConfigValue_GPUScheduling:
case HorizonOCConfigValue_LiveCpuUv:
case HorizonOCConfigValue_GPUSchedulingMethod:
case HocClkConfigValue_BatteryChargeCurrent:
case HocClkConfigValue_OverwriteRefreshRate:
case HocClkConfigValue_GPUScheduling:
case HocClkConfigValue_LiveCpuUv:
case HocClkConfigValue_GPUSchedulingMethod:
case HocClkConfigValue_MemoryFrequencyMeasurementMode:
return 0ULL;
case HocClkConfigValue_RamDisplayUnit:
return (uint64_t)RamDisplayUnit_MHz;
case HocClkConfigValue_EristaMaxCpuClock:
return 1785ULL;
@@ -441,7 +508,7 @@ static inline uint64_t sysclkDefaultConfigValue(SysClkConfigValue val)
case HocClkConfigValue_ThermalThrottle:
case HocClkConfigValue_HandheldTDP:
case HocClkConfigValue_IsFirstLoad:
case HorizonOCConfigValue_DVFSMode:
case HocClkConfigValue_DVFSMode:
return 1ULL;
case HocClkConfigValue_ThermalThrottleThreshold:
return 70ULL;
@@ -449,16 +516,18 @@ static inline uint64_t sysclkDefaultConfigValue(SysClkConfigValue val)
return 9600ULL; // 8600mW will trigger on erista stock, so raise it a bit
case HocClkConfigValue_LiteTDPLimit:
return 6400ULL; // 0.5C
case HorizonOCConfigValue_CpuGovernorMinimumFreq:
case HocClkConfigValue_CpuGovernorMinimumFreq:
return 612000000ULL; // 612MHz
case HorizonOCConfigValue_MaxDisplayClockH:
case HocClkConfigValue_MaxDisplayClockH:
return 60ULL;
case HocClkConfigValue_DisplayVoltage:
return 1200ULL; // Auto
default:
return 0ULL;
}
}
static inline uint64_t sysclkValidConfigValue(SysClkConfigValue val, uint64_t input)
static inline uint64_t hocclkValidConfigValue(HocClkConfigValue val, uint64_t input)
{
switch(val)
{
@@ -467,23 +536,23 @@ static inline uint64_t sysclkValidConfigValue(SysClkConfigValue val, uint64_t in
case HocClkConfigValue_ThermalThrottleThreshold:
case HocClkConfigValue_HandheldTDPLimit:
case HocClkConfigValue_LiteTDPLimit:
case SysClkConfigValue_PollingIntervalMs:
case HorizonOCConfigValue_MaxDisplayClockH:
case HocClkConfigValue_PollingIntervalMs:
case HocClkConfigValue_MaxDisplayClockH:
return input > 0;
case SysClkConfigValue_TempLogIntervalMs:
case SysClkConfigValue_FreqLogIntervalMs:
case SysClkConfigValue_PowerLogIntervalMs:
case SysClkConfigValue_CsvWriteIntervalMs:
case HocClkConfigValue_TempLogIntervalMs:
case HocClkConfigValue_FreqLogIntervalMs:
case HocClkConfigValue_PowerLogIntervalMs:
case HocClkConfigValue_CsvWriteIntervalMs:
case HocClkConfigValue_UncappedClocks:
case HocClkConfigValue_OverwriteBoostMode:
case HocClkConfigValue_ThermalThrottle:
case HocClkConfigValue_HandheldTDP:
case HorizonOCConfigValue_OverwriteRefreshRate:
case HocClkConfigValue_OverwriteRefreshRate:
case HocClkConfigValue_IsFirstLoad:
case HorizonOCConfigValue_EnableExperimentalSettings:
case HorizonOCConfigValue_LiveCpuUv:
case HorizonOCConfigValue_GPUSchedulingMethod:
case HocClkConfigValue_EnableExperimentalSettings:
case HocClkConfigValue_LiveCpuUv:
case HocClkConfigValue_GPUSchedulingMethod:
return (input & 0x1) == input;
case KipConfigValue_custRev:
@@ -493,6 +562,7 @@ static inline uint64_t sysclkValidConfigValue(SysClkConfigValue val, uint64_t in
case KipConfigValue_eristaEmcMaxClock:
case KipConfigValue_eristaEmcMaxClock1:
case KipConfigValue_eristaEmcMaxClock2:
case KipConfigValue_stepMode:
case KipConfigValue_marikoEmcMaxClock:
case KipConfigValue_marikoEmcVddqVolt:
case KipConfigValue_emcDvbShift:
@@ -504,6 +574,18 @@ static inline uint64_t sysclkValidConfigValue(SysClkConfigValue val, uint64_t in
case KipConfigValue_t6_tRTW:
case KipConfigValue_t7_tWTR:
case KipConfigValue_t8_tREFI:
case KipConfigValue_timingEmcTbreak:
case KipConfigValue_low_t6_tRTW:
case KipConfigValue_low_t7_tWTR:
case KipConfigValue_t2_tRP_cap:
case KipConfigValue_read_latency_1333:
case KipConfigValue_read_latency_1600:
case KipConfigValue_read_latency_1866:
case KipConfigValue_read_latency_2133:
case KipConfigValue_write_latency_1333:
case KipConfigValue_write_latency_1600:
case KipConfigValue_write_latency_1866:
case KipConfigValue_write_latency_2133:
case KipConfigValue_mem_burst_read_latency:
case KipConfigValue_mem_burst_write_latency:
case KipConfigValue_eristaCpuUV:
@@ -580,14 +662,19 @@ static inline uint64_t sysclkValidConfigValue(SysClkConfigValue val, uint64_t in
case KipConfigValue_t6_tRTW_fine_tune:
case KipConfigValue_t7_tWTR_fine_tune:
case KipCrc32:
case HorizonOCConfigValue_DVFSMode:
case HorizonOCConfigValue_DVFSOffset:
case HorizonOCConfigValue_GPUScheduling:
case HorizonOCConfigValue_RAMVoltUsageDisplayMode:
case HorizonOCConfigValue_CpuGovernorMinimumFreq:
case HocClkConfigValue_DVFSMode:
case HocClkConfigValue_DVFSOffset:
case HocClkConfigValue_GPUScheduling:
case HocClkConfigValue_RAMVoltDisplayMode:
case HocClkConfigValue_CpuGovernorMinimumFreq:
case HocClkConfigValue_MemoryFrequencyMeasurementMode:
case HocClkConfigValue_RamDisplayUnit:
return true;
case HorizonOCConfigValue_BatteryChargeCurrent:
case HocClkConfigValue_BatteryChargeCurrent:
return ((input >= 1024) && (input <= 3072)) || !input;
case HocClkConfigValue_DisplayVoltage:
return ((input >= 800) && (input <= 1325));
default:
return false;
}

View File

@@ -27,13 +27,13 @@
#pragma once
#define SYSCLK_ERROR_MODULE 388
#define SYSCLK_ERROR(desc) ((SYSCLK_ERROR_MODULE & 0x1FF) | (SysClkError_##desc & 0x1FFF)<<9)
#define HOCCLK_ERROR_MODULE 388
#define HOCCLK_ERROR(desc) ((HOCCLK_ERROR_MODULE & 0x1FF) | (HocClkError_##desc & 0x1FFF)<<9)
typedef enum
{
SysClkError_Generic = 0,
SysClkError_ConfigNotLoaded = 1,
SysClkError_ConfigSaveFailed = 2,
HocClkError_Generic = 0,
HocClkError_ConfigNotLoaded = 1,
HocClkError_ConfigSaveFailed = 2,
// HocClkError_SocThermFail = 3,
} SysClkError;
} HocClkError;

View File

@@ -31,23 +31,23 @@
#include "board.h"
#include "clock_manager.h"
#define SYSCLK_IPC_API_VERSION 1
#define SYSCLK_IPC_SERVICE_NAME "hoc:clk"
#define HOCCLK_IPC_API_VERSION 2
#define HOCCLK_IPC_SERVICE_NAME "hoc:clk"
enum SysClkIpcCmd
enum HocClkIpcCmd
{
SysClkIpcCmd_GetApiVersion = 0,
SysClkIpcCmd_GetVersionString = 1,
SysClkIpcCmd_GetCurrentContext = 2,
SysClkIpcCmd_Exit = 3,
SysClkIpcCmd_GetProfileCount = 4,
SysClkIpcCmd_GetProfiles = 5,
SysClkIpcCmd_SetProfiles = 6,
SysClkIpcCmd_SetEnabled = 7,
SysClkIpcCmd_SetOverride = 8,
SysClkIpcCmd_GetConfigValues = 9,
SysClkIpcCmd_SetConfigValues = 10,
SysClkIpcCmd_GetFreqList = 11,
HocClkIpcCmd_GetApiVersion = 0,
HocClkIpcCmd_GetVersionString = 1,
HocClkIpcCmd_GetCurrentContext = 2,
HocClkIpcCmd_Exit = 3,
HocClkIpcCmd_GetProfileCount = 4,
HocClkIpcCmd_GetProfiles = 5,
HocClkIpcCmd_SetProfiles = 6,
HocClkIpcCmd_SetEnabled = 7,
HocClkIpcCmd_SetOverride = 8,
HocClkIpcCmd_GetConfigValues = 9,
HocClkIpcCmd_SetConfigValues = 10,
HocClkIpcCmd_GetFreqList = 11,
HocClkIpcCmd_SetKipData = 12,
HocClkIpcCmd_GetKipData = 13,
};
@@ -56,17 +56,17 @@ enum SysClkIpcCmd
typedef struct
{
uint64_t tid;
SysClkTitleProfileList profiles;
} SysClkIpc_SetProfiles_Args;
HocClkTitleProfileList profiles;
} HocClkIpc_SetProfiles_Args;
typedef struct
{
SysClkModule module;
HocClkModule module;
uint32_t hz;
} SysClkIpc_SetOverride_Args;
} HocClkIpc_SetOverride_Args;
typedef struct
{
SysClkModule module;
HocClkModule module;
uint32_t maxCount;
} SysClkIpc_GetFreqList_Args;
} HocClkIpc_GetFreqList_Args;

View File

@@ -22,6 +22,7 @@ const u8 MAX17050_CURRENT_REG = 0x0A;
// Buck Converter
typedef enum I2c_BuckConverter_Reg {
I2c_Max77620_SD1VOLT_REG = 0x17, // Used for Erista DDR VDDQ+VDD2 / Mariko VDD2
I2c_Max77620_LDO0VOLT_REG = 0x23, // Used for Erista DDR VDDQ+VDD2 / Mariko VDD2
I2c_Max77621_VOLT_REG = 0x00,
I2c_Max77812_CPUVOLT_REG = 0x26,
I2c_Max77812_GPUVOLT_REG = 0x23,
@@ -40,7 +41,8 @@ typedef struct I2c_BuckConverter_Domain {
const I2c_BuckConverter_Domain I2c_Erista_CPU = { I2cDevice_Max77621Cpu, I2c_Max77621_VOLT_REG, 0x7F, 6250, 606250, 1400000, };
const I2c_BuckConverter_Domain I2c_Erista_GPU = { I2cDevice_Max77621Gpu, I2c_Max77621_VOLT_REG, 0x7F, 6250, 606250, 1400000, };
const I2c_BuckConverter_Domain I2c_Erista_DRAM = { I2cDevice_Max77620Pmic, I2c_Max77620_SD1VOLT_REG, 0x7F, 12500, 600000, 1250000, };
const I2c_BuckConverter_Domain I2c_Erista_DRAM = { I2cDevice_Max77620Pmic, I2c_Max77620_SD1VOLT_REG, 0x3F, 12500, 600000, 1250000, };
const I2c_BuckConverter_Domain I2c_Display = { I2cDevice_Max77620Pmic, I2c_Max77620_LDO0VOLT_REG, 0x7F, 25000, 800000, 1325000, };
const I2c_BuckConverter_Domain I2c_Mariko_CPU = { I2cDevice_Max77812_2, I2c_Max77812_CPUVOLT_REG, 0xFF, 5000, 250000, 1525000, 0x78 };
const I2c_BuckConverter_Domain I2c_Mariko_GPU = { I2cDevice_Max77812_2, I2c_Max77812_GPUVOLT_REG, 0xFF, 5000, 250000, 1525000, 0x78 };
const I2c_BuckConverter_Domain I2c_Mariko_DRAM_VDDQ = { I2cDevice_Max77812_2, I2c_Max77812_MEMVOLT_REG, 0xFF, 5000, 250000, 700000, 0x78 };

View File

@@ -17,16 +17,9 @@
#pragma once
#include <switch.h> // for Service, Result, hosversionBefore(), smGetService(), serviceClose(), etc.
#include "rgltr.h" // for RgltrSession, PowerDomainId, etc.
extern Service g_rgltrSrv;
Result rgltrInitialize(void);
void rgltrExit(void);
Result rgltrOpenSession(RgltrSession* session_out, PowerDomainId module_id);
Result rgltrGetVoltage(RgltrSession* session, u32* out_volt);
void rgltrCloseSession(RgltrSession* session);
#include <string>
#include <ctime>
#include <cstdio>
namespace notification {
void writeNotification(const std::string& message);
}

View File

@@ -525,3 +525,24 @@
#define CL_DVFS_I2C_STS_0 0x48
#define CL_DVFS_INTR_STS_0 0x5C
#define CL_DVFS_I2C_CLK_DIVISOR_REGISTER_0 0x16C
#define CLK_SOURCE_EMC 0x19c
#define PLLC_BASE 0x080
#define PLLM_BASE 0x090
#define PLLP_BASE 0x0a0
#define PLLA_BASE 0x0b0
#define PLLU_BASE 0x0c0
#define _PLLD_BASE 0x0d0
#define PLLX_BASE 0x0e0
#define PLLE_BASE 0x0e8
#define PLLC2_BASE 0x4e8
#define PLLC3_BASE 0x4fc
#define PLLD2_BASE 0x4b8
#define PLLRE_BASE 0x4c4
#define PLLC4_BASE 0x5a4
#define PLLMB_BASE 0x5e8
#define PLLA1_BASE 0x6a4
#define PLLDP_BASE 0x590
#define OSC_HZ 38400000ULL

View File

@@ -25,9 +25,9 @@
*/
#include <sysclk/apm.h>
#include <hocclk/apm.h>
SysClkApmConfiguration sysclk_g_apm_configurations[] = {
HocClkApmConfiguration hocclk_g_apm_configurations[] = {
{0x00010000, 1020000000, 384000000, 1600000000},
{0x00010001, 1020000000, 768000000, 1600000000},
{0x00010002, 1224000000, 691200000, 1600000000},

View File

@@ -12,99 +12,16 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
#pragma once
#include <switch.h>
#include <inttypes.h>
#include <string.h>
typedef enum {
BatteryFlag_NoHub = BIT(0), // Hub is disconnected
BatteryFlag_Rail = BIT(8), // At least one Joy-con is charging from rail
BatteryFlag_SPDSRC = BIT(12), // OTG
BatteryFlag_ACC = BIT(16) // Accessory
} BatteryChargeFlags;
#include <cstring>
#include "battery.h"
typedef enum {
PDState_NewPDO = 1, // Received new Power Data Object
PDState_NoPD = 2, // No Power Delivery source is detected
PDState_AcceptedRDO = 3 // Received and accepted Request Data Object
} BatteryPDControllerState;
// Charger type detection
typedef enum {
ChargerType_None = 0,
ChargerType_PD = 1,
ChargerType_TypeC_1500mA = 2,
ChargerType_TypeC_3000mA = 3,
ChargerType_DCP = 4, // Dedicated Charging Port
ChargerType_CDP = 5, // Charging Downstream Port
ChargerType_SDP = 6, // Standard Downstream Port
ChargerType_Apple_500mA = 7,
ChargerType_Apple_1000mA = 8,
ChargerType_Apple_2000mA = 9
} BatteryChargerType;
typedef enum {
PowerRole_Sink = 1, // Device is receiving power
PowerRole_Source = 2 // Device is providing power
} BatteryPowerRole;
typedef struct {
int32_t InputCurrentLimit; // Input (Sink) current limit in mA
int32_t VBUSCurrentLimit; // Output (Source/VBUS/OTG) current limit in mA
int32_t ChargeCurrentLimit; // Battery charging current limit in mA
int32_t ChargeVoltageLimit; // Battery charging voltage limit in mV
int32_t unk_x10; // Unknown field (possibly enum)
int32_t unk_x14; // Unknown field (possibly flags)
BatteryPDControllerState PDControllerState; // PD Controller State
int32_t BatteryTemperature; // Battery temperature in milli-Celsius
int32_t RawBatteryCharge; // Battery charge in percentmille
int32_t VoltageAvg; // Average voltage in mV
int32_t BatteryAge; // Battery health (capacity full/design) in pcm
BatteryPowerRole PowerRole; // Current power role
BatteryChargerType ChargerType; // Type of charger connected
int32_t ChargerVoltageLimit; // Charger voltage limit in mV
int32_t ChargerCurrentLimit; // Charger current limit in mA
BatteryChargeFlags Flags; // Various status flags
} BatteryChargeInfo;
#define IS_BATTERY_CHARGING_ENABLED(info) (((info)->unk_x14 >> 8) & 1)
Result batteryInfoInitialize(void);
void batteryInfoExit(void);
Result batteryInfoGetChargeInfo(BatteryChargeInfo *out);
Result batteryInfoGetChargePercentage(u32 *out);
Result batteryInfoIsEnoughPowerSupplied(bool *out);
Result batteryInfoEnableCharging(void);
Result batteryInfoDisableCharging(void);
Result batteryInfoEnableFastCharging(void);
Result batteryInfoDisableFastCharging(void);
const char* batteryInfoGetChargerTypeString(BatteryChargerType type);
const char* batteryInfoGetPowerRoleString(BatteryPowerRole role);
const char* batteryInfoGetPDStateString(BatteryPDControllerState state);
static inline int batteryInfoGetTemperatureMiliCelsius(BatteryChargeInfo *info) {
return info->BatteryTemperature;
}
static inline float batteryInfoGetChargePercent(BatteryChargeInfo *info) {
return (float)info->RawBatteryCharge / 1000.0f;
}
static inline float batteryInfoGetBatteryHealthPercent(BatteryChargeInfo *info) {
return (float)info->BatteryAge / 1000.0f;
}
static inline bool batteryInfoIsCharging(BatteryChargeInfo *info) {
return IS_BATTERY_CHARGING_ENABLED(info);
}
// Internal PSM service handle
static Service g_psmService = {0};
static bool g_batteryInfoInitialized = false;
static const char* s_chargerTypeStrings[] = {
"None",
@@ -132,56 +49,52 @@ static const char* s_pdStateStrings[] = {
"RDO Accepted"
};
// Internal PSM service handle
static Service g_psmService = {0};
static bool g_batteryInfoInitialized = false;
// Internal PSM command implementations
static Result psmGetBatteryChargeInfoFields(BatteryChargeInfo *out) {
if (!g_batteryInfoInitialized)
return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
return serviceDispatchOut(&g_psmService, 17, *out);
}
static Result psmEnableBatteryCharging_internal(void) {
if (!g_batteryInfoInitialized)
return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
return serviceDispatch(&g_psmService, 2);
}
static Result psmDisableBatteryCharging_internal(void) {
if (!g_batteryInfoInitialized)
return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
return serviceDispatch(&g_psmService, 3);
}
static Result psmEnableFastBatteryCharging_internal(void) {
if (!g_batteryInfoInitialized)
return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
return serviceDispatch(&g_psmService, 10);
}
static Result psmDisableFastBatteryCharging_internal(void) {
if (!g_batteryInfoInitialized)
return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
return serviceDispatch(&g_psmService, 11);
}
Result batteryInfoInitialize(void) {
if (g_batteryInfoInitialized)
return 0;
Result rc = psmInitialize();
if (R_SUCCEEDED(rc)) {
memcpy(&g_psmService, psmGetServiceSession(), sizeof(Service));
g_batteryInfoInitialized = true;
}
return rc;
}
@@ -196,21 +109,21 @@ void batteryInfoExit(void) {
Result batteryInfoGetChargeInfo(BatteryChargeInfo *out) {
if (!out)
return MAKERESULT(Module_Libnx, LibnxError_BadInput);
return psmGetBatteryChargeInfoFields(out);
}
Result batteryInfoGetChargePercentage(u32 *out) {
if (!g_batteryInfoInitialized)
return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
return psmGetBatteryChargePercentage(out);
}
Result batteryInfoIsEnoughPowerSupplied(bool *out) {
if (!g_batteryInfoInitialized)
return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
return psmIsEnoughPowerSupplied(out);
}
@@ -233,20 +146,20 @@ Result batteryInfoDisableFastCharging(void) {
const char* batteryInfoGetChargerTypeString(BatteryChargerType type) {
if (type < 0 || type > ChargerType_Apple_2000mA)
return "Unknown";
return s_chargerTypeStrings[type];
}
const char* batteryInfoGetPowerRoleString(BatteryPowerRole role) {
if (role < PowerRole_Sink || role > PowerRole_Source)
return s_powerRoleStrings[0];
return s_powerRoleStrings[role];
}
const char* batteryInfoGetPDStateString(BatteryPDControllerState state) {
if (state < PDState_NewPDO || state > PDState_AcceptedRDO)
return s_pdStateStrings[0];
return s_pdStateStrings[state];
}

View File

@@ -29,128 +29,128 @@
#include <switch.h>
#include <string.h>
#include <stdatomic.h>
#include <sysclk/client/ipc.h>
#include <hocclk/client/ipc.h>
static Service g_sysclkSrv;
static Service g_hocclkSrv;
static atomic_size_t g_refCnt;
bool sysclkIpcRunning()
bool hocclkIpcRunning()
{
Handle handle;
bool running = R_FAILED(smRegisterService(&handle, smEncodeName(SYSCLK_IPC_SERVICE_NAME), false, 1));
bool running = R_FAILED(smRegisterService(&handle, smEncodeName(HOCCLK_IPC_SERVICE_NAME), false, 1));
if (!running)
{
smUnregisterService(smEncodeName(SYSCLK_IPC_SERVICE_NAME));
smUnregisterService(smEncodeName(HOCCLK_IPC_SERVICE_NAME));
}
return running;
}
Result sysclkIpcInitialize(void)
Result hocclkIpcInitialize(void)
{
Result rc = 0;
g_refCnt++;
if (serviceIsActive(&g_sysclkSrv))
if (serviceIsActive(&g_hocclkSrv))
return 0;
rc = smGetService(&g_sysclkSrv, SYSCLK_IPC_SERVICE_NAME);
rc = smGetService(&g_hocclkSrv, HOCCLK_IPC_SERVICE_NAME);
if (R_FAILED(rc)) sysclkIpcExit();
if (R_FAILED(rc)) hocclkIpcExit();
return rc;
}
void sysclkIpcExit(void)
void hocclkIpcExit(void)
{
if (--g_refCnt == 0)
{
serviceClose(&g_sysclkSrv);
serviceClose(&g_hocclkSrv);
}
}
Result sysclkIpcGetAPIVersion(u32* out_ver)
Result hocclkIpcGetAPIVersion(u32* out_ver)
{
return serviceDispatchOut(&g_sysclkSrv, SysClkIpcCmd_GetApiVersion, *out_ver);
return serviceDispatchOut(&g_hocclkSrv, HocClkIpcCmd_GetApiVersion, *out_ver);
}
Result sysclkIpcGetVersionString(char* out, size_t len)
Result hocclkIpcGetVersionString(char* out, size_t len)
{
return serviceDispatch(&g_sysclkSrv, SysClkIpcCmd_GetVersionString,
return serviceDispatch(&g_hocclkSrv, HocClkIpcCmd_GetVersionString,
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out },
.buffers = {{out, len}},
);
}
Result sysclkIpcGetCurrentContext(SysClkContext* out_context)
Result hocclkIpcGetCurrentContext(HocClkContext* out_context)
{
return serviceDispatch(&g_sysclkSrv, SysClkIpcCmd_GetCurrentContext,
return serviceDispatch(&g_hocclkSrv, HocClkIpcCmd_GetCurrentContext,
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out },
.buffers = {{out_context, sizeof(SysClkContext)}},
.buffers = {{out_context, sizeof(HocClkContext)}},
);
}
Result sysclkIpcGetProfileCount(u64 tid, u8* out_count)
Result hocclkIpcGetProfileCount(u64 tid, u8* out_count)
{
return serviceDispatchInOut(&g_sysclkSrv, SysClkIpcCmd_GetProfileCount, tid, *out_count);
return serviceDispatchInOut(&g_hocclkSrv, HocClkIpcCmd_GetProfileCount, tid, *out_count);
}
Result sysclkIpcSetEnabled(bool enabled)
Result hocclkIpcSetEnabled(bool enabled)
{
u8 enabledRaw = (u8)enabled;
return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetEnabled, enabledRaw);
return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_SetEnabled, enabledRaw);
}
Result sysclkIpcSetOverride(SysClkModule module, u32 hz)
Result hocclkIpcSetOverride(HocClkModule module, u32 hz)
{
SysClkIpc_SetOverride_Args args = {
HocClkIpc_SetOverride_Args args = {
.module = module,
.hz = hz
};
return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetOverride, args);
return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_SetOverride, args);
}
Result sysclkIpcGetProfiles(u64 tid, SysClkTitleProfileList* out_profiles)
Result hocclkIpcGetProfiles(u64 tid, HocClkTitleProfileList* out_profiles)
{
return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_GetProfiles, tid,
return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_GetProfiles, tid,
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out },
.buffers = {{out_profiles, sizeof(SysClkTitleProfileList)}},
.buffers = {{out_profiles, sizeof(HocClkTitleProfileList)}},
);
}
Result sysclkIpcSetProfiles(u64 tid, SysClkTitleProfileList* profiles)
Result hocclkIpcSetProfiles(u64 tid, HocClkTitleProfileList* profiles)
{
SysClkIpc_SetProfiles_Args args;
HocClkIpc_SetProfiles_Args args;
args.tid = tid;
memcpy(&args.profiles, profiles, sizeof(SysClkTitleProfileList));
return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetProfiles, args);
memcpy(&args.profiles, profiles, sizeof(HocClkTitleProfileList));
return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_SetProfiles, args);
}
Result sysclkIpcGetConfigValues(SysClkConfigValueList* out_configValues)
Result hocclkIpcGetConfigValues(HocClkConfigValueList* out_configValues)
{
return serviceDispatch(&g_sysclkSrv, SysClkIpcCmd_GetConfigValues,
return serviceDispatch(&g_hocclkSrv, HocClkIpcCmd_GetConfigValues,
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out },
.buffers = {{out_configValues, sizeof(SysClkConfigValueList)}},
.buffers = {{out_configValues, sizeof(HocClkConfigValueList)}},
);
}
Result sysclkIpcSetConfigValues(SysClkConfigValueList* configValues)
Result hocclkIpcSetConfigValues(HocClkConfigValueList* configValues)
{
return serviceDispatch(&g_sysclkSrv, SysClkIpcCmd_SetConfigValues,
return serviceDispatch(&g_hocclkSrv, HocClkIpcCmd_SetConfigValues,
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_In },
.buffers = {{configValues, sizeof(SysClkConfigValueList)}},
.buffers = {{configValues, sizeof(HocClkConfigValueList)}},
);
}
Result sysclkIpcGetFreqList(SysClkModule module, u32* list, u32 maxCount, u32* outCount)
Result hocclkIpcGetFreqList(HocClkModule module, u32* list, u32 maxCount, u32* outCount)
{
SysClkIpc_GetFreqList_Args args = {
HocClkIpc_GetFreqList_Args args = {
.module = module,
.maxCount = maxCount
};
return serviceDispatchInOut(&g_sysclkSrv, SysClkIpcCmd_GetFreqList, args, *outCount,
return serviceDispatchInOut(&g_hocclkSrv, HocClkIpcCmd_GetFreqList, args, *outCount,
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out },
.buffers = {{list, maxCount * sizeof(u32)}},
);
@@ -159,11 +159,11 @@ Result sysclkIpcGetFreqList(SysClkModule module, u32* list, u32 maxCount, u32* o
Result hocClkIpcSetKipData()
{
u32 temp = 0;
return serviceDispatchIn(&g_sysclkSrv, HocClkIpcCmd_SetKipData, temp);
return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_SetKipData, temp);
}
Result hocClkIpcGetKipData()
{
u32 temp = 0;
return serviceDispatchIn(&g_sysclkSrv, HocClkIpcCmd_GetKipData, temp);
return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_GetKipData, temp);
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <crc32.h>
namespace crc32 {
uint32_t crc32(const uint8_t *data, size_t length) {
uint32_t crc = 0xFFFFFFFF;
for (size_t i = 0; i < length; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1));
}
}
return ~crc;
}
uint32_t checksum_file(const char *filename) {
FILE *file = fopen(filename, "rb");
if (!file) {
perror("[crc32] Error opening file");
return 0;
}
uint8_t buffer[1024];
uint32_t crc = 0xFFFFFFFF;
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
for (size_t i = 0; i < bytes_read; i++) {
crc ^= buffer[i];
for (int j = 0; j < 8; j++) {
crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1));
}
}
}
fclose(file);
return ~crc;
}
}

View File

@@ -15,13 +15,11 @@
*
*/
#pragma once
#include <string>
#include <ctime>
#include <cstdio>
#include "notification.h"
static void writeNotification(const std::string& message) {
namespace notification {
void writeNotification(const std::string& message) {
static const char* flagPath = "sdmc:/config/ultrahand/flags/NOTIFICATIONS.flag";
FILE* flagFile = fopen(flagPath, "r");
@@ -42,3 +40,4 @@ static void writeNotification(const std::string& message) {
fclose(file);
}
}
}

View File

@@ -15,7 +15,7 @@
*
*/
#include <sysclk/psm_ext.h>
#include <hocclk/psm_ext.h>
const char* PsmPowerRoleToStr(PsmPowerRole role) {
switch (role) {

View File

@@ -27,7 +27,7 @@ INCLUDES := ../common/include
EXEFS_SRC := exefs_src
IS_MINIMAL := 0
APP_TITLE := Horizon OC Zeus
APP_TITLE := Horizon OC Gaea
NO_ICON := 1
@@ -39,7 +39,7 @@ include ${TOPDIR}/lib/libultrahand/ultrahand.mk
# version control constants
#---------------------------------------------------------------------------------
#TARGET_VERSION := $(shell git describe --dirty --always --tags)
APP_VERSION := 1.1.0
APP_VERSION := 2.0.1
TARGET_VERSION := $(APP_VERSION)
#---------------------------------------------------------------------------------
@@ -64,7 +64,7 @@ CFLAGS += -DUI_OVERRIDE_PATH="\"$(UI_OVERRIDE_PATH)\""
#CFLAGS += -DNO_FSTREAM_DIRECTIVE=$(NO_FSTREAM_DIRECTIVE)
CXXFLAGS := $(CFLAGS) -fno-exceptions -std=gnu++20
CXXFLAGS := $(CFLAGS) -fno-exceptions -std=gnu++23
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)

View File

@@ -0,0 +1,141 @@
{
"Information": "Información",
"IDDQ:": "IDDQ:",
"Module: ": "Módulo:",
"sys-dock status:": "Estado de sys-dock:",
"SaltyNX status:": "Estado de SaltyNX:",
"RR Display status:": "Estado de pantalla RR:",
"Wafer Position:": "Posición del wafer:",
"Credits": "Créditos",
"Developers": "Desarrolladores",
"Contributors": "Colaboradores",
"Testers": "Testers",
"Special Thanks": "Agradecimientos especiales",
"Unknown": "Desconocido",
"Installed": "Instalado",
"Not Installed": "No instalado",
"X: %u Y: %u": "X: %u Y: %u",
"THE BEER-WARE LICENSE": "LICENCIA BEER-WARE",
"Default": "Predeterminado",
"Do Not Override": "No sobrescribir",
"Disabled": "Desactivado",
"Enabled": "Activado",
" \\ue0e3 Reset": "\\ue0e3 Restablecer",
"Display": "Pantalla",
"Application changed\\n\\n": "Aplicación cambiada\\n\\n",
"The running application changed\\n\\n": "La aplicación en ejecución ha cambiado\\n\\n",
"while editing was going on.": "mientras se estaba editando.",
"Board": "Placa",
"%u.%u%u mV": "%u.%u%u mV",
"Could not connect to hoc-clk sysmodule.\\n\\n": "No se pudo conectar al sysmodule hoc-clk.\\n\\n",
"Please make sure everything is\\n\\n": "Asegúrate de que todo esté\\n\\n",
"correctly installed and enabled.": "correctamente instalado y activado.",
"Fatal error": "Error fatal",
"Temporary Overrides ": "Ajustes temporales",
"Sleep Mode": "Modo reposo",
"Stock": "Valores de fábrica",
"Dev OC": "OC de desarrollo",
"Boost Mode": "Modo boost",
"Safe Max": "Máximo seguro",
"Unsafe Max": "Máximo no seguro",
"Absolute Max": "Máximo absoluto",
"Handheld Safe Max": "Máximo seguro en portátil",
"Enable": "Activar",
"Edit App Profile": "Editar perfil de aplicación",
"Edit Global Profile": "Editar perfil global",
"Temporary Overrides": "Ajustes temporales",
"Settings": "Configuración",
"About": "Acerca de",
"Compiling with minimal features": "Compilado con funciones mínimas",
"General Settings": "Configuración general",
"Governor Settings": "Configuración del governor",
"Safety Settings": "Configuración de seguridad",
"Save KIP Settings": "Guardar configuración KIP",
"RAM Settings": "Configuración de RAM",
"CPU Settings": "Configuración de CPU",
"GPU Settings": "Configuración de GPU",
"Display Settings": "Configuración de pantalla",
"Experimental": "Experimental",
"GPU Scheduling Override Method": "Método de sobrescritura del scheduling de GPU",
"can be dangerous and may cause": "puede ser peligroso y causar",
"damage to your battery or charger!": "daños a la batería o al cargador.",
"Charge Current Override": "Sobrescritura de corriente de carga",
"RAM Voltage Display Mode": "Modo de visualización de voltaje de RAM",
"Polling Interval": "Intervalo de sondeo",
"CPU Governor Minimum Frequency": "Frecuencia mínima del governor de CPU",
"refresh rates may cause stress": "las tasas de refresco pueden causar estrés",
"or damage to your display! ": "o dañar la pantalla.",
"Proceed at your own risk!": "¡Úsalo bajo tu propio riesgo!",
"Max Handheld Display": "Frecuencia máxima de pantalla en portátil",
"Display Clock": "Frecuencia de pantalla",
"Official Rating": "Valor oficial",
"TDP Threshold": "Umbral de TDP",
"Power": "Potencia",
"Thermal Throttle Limit": "Límite de thermal throttling",
"HP Mode": "Modo alto rendimiento",
"Default (Mariko)": "Predeterminado (Mariko)",
"Default (Erista)": "Predeterminado (Erista)",
"Rating": "Valor",
"Safe Max (Mariko)": "Máximo seguro (Mariko)",
"Safe Max (Erista)": "Máximo seguro (Erista)",
"RAM VDD2 Voltage": "Voltaje VDD2 de RAM",
"Voltage": "Voltaje",
"RAM VDDQ Voltage": "Voltaje VDDQ de RAM",
"RAM Frequency Editor": "Editor de frecuencia de RAM",
"JEDEC.": "JEDEC",
"High speedo needed!": "¡Se necesita alto speedo!",
"3333MHz (Needs extreme Speedo/PLL)": "3333MHz (requiere Speedo/PLL extremo)",
"3366MHz (Needs extreme Speedo/PLL)": "3366MHz (requiere Speedo/PLL extremo)",
"3400MHz (Needs extreme Speedo/PLL)": "3400MHz (requiere Speedo/PLL extremo)",
"3433MHz (Needs ridiculous Speedo/PLL)": "3433MHz (requiere Speedo/PLL muy alto)",
"3466MHz (Needs ridiculous Speedo/PLL)": "3466MHz (requiere Speedo/PLL muy alto)",
"3500MHz (Needs ridiculous Speedo/PLL)": "3500MHz (requiere Speedo/PLL muy alto)",
"Ram Max Clock": "Frecuencia máxima de RAM",
"RAM Latency Editor": "Editor de latencias de RAM",
"RAM Timing Reductions": "Reducción de timings de RAM",
"Memory Timings": "Timings de memoria",
"Advanced": "Avanzado",
"t6 tRTW Fine Tune": "Ajuste fino t6 tRTW",
"tRTW Fine Tune": "Ajuste fino tRTW",
"t7 tWTR Fine Tune": "Ajuste fino t7 tWTR",
"tWTR Fine Tune": "Ajuste fino tWTR",
"Memory Latencies": "Latencias de memoria",
"Read Latency": "Latencia de lectura",
"Write Latency": "Latencia de escritura",
"CPU Boost Clock": "Frecuencia boost de CPU",
"CPU UV": "Undervolt de CPU",
"CPU Unlock": "Desbloqueo de CPU",
"CPU VMIN": "VMIN de CPU",
"CPU Max Voltage": "Voltaje máximo de CPU",
"CPU Max Clock": "Frecuencia máxima de CPU",
"Extreme UV Table": "Tabla de undervolt extrema",
"CPU UV Table": "Tabla de undervolt de CPU",
"CPU Low UV": "Undervolt bajo de CPU",
"CPU High UV": "Undervolt alto de CPU",
"CPU Low VMIN": "VMIN bajo de CPU",
"CPU High VMIN": "VMIN alto de CPU",
"No Undervolt": "Sin undervolt",
"SLT Table": "Tabla SLT",
"HiOPT Table": "Tabla HiOPT",
"GPU Undervolt Table": "Tabla de undervolt de GPU",
"GPU Minimum Voltage": "Voltaje mínimo de GPU",
"Calculate GPU Vmin": "Calcular Vmin de GPU",
"GPU VMIN": "VMIN de GPU",
"GPU Maximum Voltage": "Voltaje máximo de GPU",
"GPU Voltage Offset": "Offset de voltaje de GPU",
"Do not override": "No sobrescribir",
"Enabled (Default)": "Activado (predeterminado)",
"96.6% limit": "Límite 96,6%",
"99.7% limit": "Límite 99,7%",
"GPU Scheduling Override": "Sobrescritura de scheduling de GPU",
"Official Service": "Servicio oficial",
"GPU DVFS Mode": "Modo DVFS de GPU",
"GPU DVFS Offset": "Offset DVFS de GPU",
"GPU Voltage Table": "Tabla de voltaje de GPU",
"GPU Custom Table (mV)": "Tabla personalizada de GPU (mV)",
"1075MHz without UV, 1152MHz on SLT": "1075MHz sin undervolt, 1152MHz en SLT",
"or 1228MHz on HiOPT can cause ": "o 1228MHz en HiOPT pueden causar ",
"permanent damage to your Switch!": "¡daño permanente a tu Switch!",
"921MHz without UV and 960MHz on": "921MHz sin undervolt y 960MHz en",
"SLT or HiOPT can cause ": "SLT o HiOPT pueden causar "
}

Some files were not shown because too many files have changed in this diff Show More