pages: cleanup

This commit is contained in:
KazushiM
2023-02-12 23:55:54 +08:00
parent b0ace1d8ae
commit 63bbde2f58
10 changed files with 571 additions and 516 deletions

View File

@@ -80,29 +80,6 @@ Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW.
5. Hekate-ipl bootloader Only 5. Hekate-ipl bootloader Only
- Add `kip1=atmosphere/kips/loader.kip` to boot entry section in `bootloader/hekate_ipl.ini`. - Add `kip1=atmosphere/kips/loader.kip` to boot entry section in `bootloader/hekate_ipl.ini`.
<details>
<summary>Deprecated: patching sysmodules manually</summary>
- This method is only served as reference as it could damage your MMC file system if not handled properly.
- Patched sysmodules would be persistent until pcv or ptm was updated in new HOS (normally in `x.0.0`).
- Tools:
- Lockpick_RCM
- TegraExplorer
- [hactool](https://github.com/SciresM/hactool)
- [nx2elf](https://github.com/shuffle2/nx2elf)
- elf2nso from [switch-tools](https://github.com/switchbrew/switch-tools/)
- [hacpack](https://github.com/The-4n/hacPack)
1. Dump `prod.keys` with Lockpick_RCM
2. Dump HOS firmware with TegraExplorer
3. Configure and run `test_patch.sh` to generate patched pcv & ptm sysmodules in nca
4. Replace nca in `SYSTEM:/Contents/registered/` with TegraExplorer
5. `ValidateAcidSignature()` should be stubbed to allow unsigned sysmodules to load (a.k.a. `loader_patch`)
</details>
## Build ## Build

View File

@@ -77,15 +77,46 @@ uint32_t GetModuleMaximumFreq(SysClkModule module);
typedef enum { typedef enum {
SysClkOcGovernorConfig_AllDisabled = 0, SysClkOcGovernorConfig_AllDisabled = 0,
SysClkOcGovernorConfig_CPU_Shift = 0, SysClkOcGovernorConfig_CPU_Shift = 0,
SysClkOcGovernorConfig_CPUOnly = 1, SysClkOcGovernorConfig_CPUOnly = 1 << SysClkOcGovernorConfig_CPU_Shift,
SysClkOcGovernorConfig_CPU = 1 << SysClkOcGovernorConfig_CPU_Shift, SysClkOcGovernorConfig_CPU = 1 << SysClkOcGovernorConfig_CPU_Shift,
SysClkOcGovernorConfig_GPU_Shift = 1 << SysClkOcGovernorConfig_CPU_Shift, SysClkOcGovernorConfig_GPU_Shift = 1,
SysClkOcGovernorConfig_GPUOnly = 1 << SysClkOcGovernorConfig_GPU_Shift, SysClkOcGovernorConfig_GPUOnly = 1 << SysClkOcGovernorConfig_GPU_Shift,
SysClkOcGovernorConfig_GPU = 1 << SysClkOcGovernorConfig_GPU_Shift, SysClkOcGovernorConfig_GPU = 1 << SysClkOcGovernorConfig_GPU_Shift,
SysClkOcGovernorConfig_AllEnabled = 3,
SysClkOcGovernorConfig_Default = 3, SysClkOcGovernorConfig_Default = 3,
SysClkOcGovernorConfig_Mask = 3, SysClkOcGovernorConfig_Mask = 3,
} SysClkOcGovernorConfig; } SysClkOcGovernorConfig;
inline bool GetGovernorEnabled(SysClkOcGovernorConfig config, SysClkModule module) {
switch (module) {
case SysClkModule_CPU:
return (config >> SysClkOcGovernorConfig_CPU_Shift) & 1;
case SysClkModule_GPU:
return (config >> SysClkOcGovernorConfig_GPU_Shift) & 1;
case SysClkModule_MEM:
return false;
default:
return config != SysClkOcGovernorConfig_AllDisabled;
}
}
inline SysClkOcGovernorConfig ToggleGovernor(SysClkOcGovernorConfig prev, SysClkModule module, bool state) {
uint8_t shift;
switch (module) {
case SysClkModule_CPU:
shift = SysClkOcGovernorConfig_CPU_Shift;
break;
case SysClkModule_GPU:
shift = SysClkOcGovernorConfig_GPU_Shift;
break;
case SysClkModule_MEM:
return prev;
default:
return state ? SysClkOcGovernorConfig_AllEnabled : SysClkOcGovernorConfig_AllDisabled;
}
return (SysClkOcGovernorConfig)((prev & ~(1 << shift)) | state << shift);
}
typedef struct typedef struct
{ {
union { union {

View File

@@ -74,10 +74,9 @@ void AppProfileGui::listUI()
if (globalGovernorEnabled) { if (globalGovernorEnabled) {
tsl::elm::ToggleListItem* cpuGovernorToggle = new tsl::elm::ToggleListItem("CPU Freq Governor", tsl::elm::ToggleListItem* cpuGovernorToggle = new tsl::elm::ToggleListItem("CPU Freq Governor",
(this->profileList->governorConfig >> SysClkOcGovernorConfig_CPU_Shift) & 1); GetGovernorEnabled(this->profileList->governorConfig, SysClkModule_CPU));
cpuGovernorToggle->setStateChangedListener([this](bool state) { cpuGovernorToggle->setStateChangedListener([this](bool state) {
this->profileList->governorConfig = this->profileList->governorConfig = ToggleGovernor(this->profileList->governorConfig, SysClkModule_CPU, state);
SysClkOcGovernorConfig((this->profileList->governorConfig & SysClkOcGovernorConfig_GPUOnly) | state << SysClkOcGovernorConfig_CPU_Shift);
Result rc = sysclkIpcSetProfiles(this->applicationId, this->profileList); Result rc = sysclkIpcSetProfiles(this->applicationId, this->profileList);
if (R_FAILED(rc)) if (R_FAILED(rc))
@@ -86,10 +85,9 @@ void AppProfileGui::listUI()
this->listElement->addItem(cpuGovernorToggle); this->listElement->addItem(cpuGovernorToggle);
tsl::elm::ToggleListItem* gpuGovernorToggle = new tsl::elm::ToggleListItem("GPU Freq Governor", tsl::elm::ToggleListItem* gpuGovernorToggle = new tsl::elm::ToggleListItem("GPU Freq Governor",
(this->profileList->governorConfig >> SysClkOcGovernorConfig_GPU_Shift) & 1); GetGovernorEnabled(this->profileList->governorConfig, SysClkModule_GPU));
gpuGovernorToggle->setStateChangedListener([this](bool state) { gpuGovernorToggle->setStateChangedListener([this](bool state) {
this->profileList->governorConfig = this->profileList->governorConfig = ToggleGovernor(this->profileList->governorConfig, SysClkModule_GPU, state);
SysClkOcGovernorConfig((this->profileList->governorConfig & SysClkOcGovernorConfig_CPUOnly) | state << SysClkOcGovernorConfig_GPU_Shift);
Result rc = sysclkIpcSetProfiles(this->applicationId, this->profileList); Result rc = sysclkIpcSetProfiles(this->applicationId, this->profileList);
if (R_FAILED(rc)) if (R_FAILED(rc))

View File

@@ -257,19 +257,6 @@ void GpuGovernor::Apply() {
} }
bool Governor::IsHandledByGovernor(SysClkModule module) {
switch (module) {
case SysClkModule_CPU:
return ((this->GetConfig() >> SysClkOcGovernorConfig_CPU_Shift) & 1);
case SysClkModule_GPU:
return ((this->GetConfig() >> SysClkOcGovernorConfig_GPU_Shift) & 1);
case SysClkModule_MEM:
return false;
default:
return this->GetConfig() != SysClkOcGovernorConfig_AllDisabled;
}
}
void Governor::SetConfig(SysClkOcGovernorConfig config) { void Governor::SetConfig(SysClkOcGovernorConfig config) {
if (m_config == config) if (m_config == config)
return; return;
@@ -342,16 +329,16 @@ void Governor::GovernorManager::ContextManager(void* args) {
} }
uint32_t perf_conf = self->GetPerfConf(); uint32_t perf_conf = self->GetPerfConf();
if ((gpuThrottled = apmExtIsBoostMode(perf_conf)) && (self->GetConfig() & SysClkOcGovernorConfig_GPU)) if ((gpuThrottled = apmExtIsBoostMode(perf_conf)) && self->IsHandledByGovernor(SysClkModule_GPU))
self->m_gpu_gov->ApplyBoost(); self->m_gpu_gov->ApplyBoost();
if ((cpuBoosted = apmExtIsCPUBoosted(perf_conf)) && (self->GetConfig() & SysClkOcGovernorConfig_CPU)) if ((cpuBoosted = apmExtIsCPUBoosted(perf_conf)) && self->IsHandledByGovernor(SysClkModule_CPU))
self->m_cpu_gov->ApplyBoost(); self->m_cpu_gov->ApplyBoost();
} }
if (!gpuThrottled && (self->GetConfig() & SysClkOcGovernorConfig_GPU)) if (!gpuThrottled && self->IsHandledByGovernor(SysClkModule_GPU))
self->m_gpu_gov->Apply(); self->m_gpu_gov->Apply();
if (!cpuBoosted && (self->GetConfig() & SysClkOcGovernorConfig_CPU)) if (!cpuBoosted && self->IsHandledByGovernor(SysClkModule_CPU))
self->m_cpu_gov->Apply(); self->m_cpu_gov->Apply();
svcSleepThread(TICK_TIME_NS); svcSleepThread(TICK_TIME_NS);

View File

@@ -329,7 +329,7 @@ public:
}; };
SysClkOcGovernorConfig GetConfig() { return m_config; }; SysClkOcGovernorConfig GetConfig() { return m_config; };
bool IsHandledByGovernor(SysClkModule module = SysClkModule_EnumMax); inline bool IsHandledByGovernor(SysClkModule module) { return GetGovernorEnabled(this->GetConfig(), module); };
void SetConfig(SysClkOcGovernorConfig config); void SetConfig(SysClkOcGovernorConfig config);
void SetPerfConf(uint32_t id); void SetPerfConf(uint32_t id);

View File

@@ -6,7 +6,7 @@
# README_HTML=`pandoc -f gfm -t html5 ../README.md` # README_HTML=`pandoc -f gfm -t html5 ../README.md`
cp -Rf ./src/*.html ./tmp/ cp -Rf ./src/*.html ./tmp/
tsc ./src/main.ts --outDir ./tmp/ -lib es2015,dom -t es2015 tsc ./src/main.ts --outDir ./tmp/ -lib es2019,dom -t es2015
for FILE in ./tmp/*; do for FILE in ./tmp/*; do
minify "${FILE}" > "./dist/${FILE/.\/tmp\/}" minify "${FILE}" > "./dist/${FILE/.\/tmp\/}"

File diff suppressed because one or more lines are too long

2
pages/dist/main.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -10,25 +10,59 @@
content="Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. Licensed under GPL v2."> content="Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. Licensed under GPL v2.">
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css"> <link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
<style> <style>
nav[role="tab-control"] > button { #nav-list {
display: none;
}
#nav-popup {
display: unset;
}
@media (min-width: 768px) {
#nav-list {
display: unset;
}
#nav-popup {
display: none;
}
}
details[open] > summary:not([role]):not(:focus) {
color: unset;
}
div.hero > nav.container-fluid {
position: fixed;
top: 0;
z-index: 99;
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
}
div.hero > header {
padding-top: 6rem;
}
button[role="tab"] {
border-radius: 0; border-radius: 0;
border-left-width: calc(var(--border-width) / 2); border-left-width: calc(var(--border-width) / 2);
border-right-width: calc(var(--border-width) / 2); border-right-width: calc(var(--border-width) / 2);
} }
nav[role="tab-control"] > button:first-child { button[role="tab"]:first-child {
border-top-left-radius: var(--border-radius); border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius); border-bottom-left-radius: var(--border-radius);
border-left-width: var(--border-width); border-left-width: var(--border-width);
} }
nav[role="tab-control"] > button:last-child { button[role="tab"]:last-child {
border-top-right-radius: var(--border-radius); border-top-right-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius); border-bottom-right-radius: var(--border-radius);
border-right-width: var(--border-width); border-right-width: var(--border-width);
} }
nav[role="tab-control"] > button:not(.outline) { button[role="tab"]:not(.outline) {
pointer-events: none; pointer-events: none;
cursor: default; cursor: default;
} }
@@ -37,8 +71,10 @@
margin: 1.5rem 0 1.5rem 0; margin: 1.5rem 0 1.5rem 0;
} }
div.cust-element > blockquote { div.cust-element > blockquote, div.cust-element > blockquote > ul {
margin-top: 0; margin: 0;
font-size: calc(var(--font-size) * 0.8);
color: var(--muted-color);
} }
div#download_btn_grid > a { div#download_btn_grid > a {
@@ -49,57 +85,64 @@
<body> <body>
<div class="hero"> <div class="hero">
<nav class="container-fluid" <nav class="container-fluid">
style="position: fixed; top: 0; z-index: 99; -webkit-backdrop-filter: blur(20px); backdrop-filter: blur(20px);">
<ul> <ul>
<li><a href="#head" class="contrast">Switch OC Suite</a></li> <li><a href="#head" class="contrast"><b>Switch OC Suite</b></a></li>
</ul> </ul>
<ul> <ul id="nav-popup">
<li><a href="#readme" class="secondary">Read Me</a></li> <li>
<li><a href="#download" class="secondary">Download</a></li> <details role="list" dir="rtl">
<li><a href="#config" class="secondary">Config</a></li> <summary aria-haspopup="group" role="link" class="contrast">Jump to</summary>
<li><a href="https://github.com/KazushiMe/Switch-OC-Suite" class="contrast"> <ul role="group">
<svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" <li><a href="#readme">README</a></li>
viewBox="0 0 496 512" height="16px"> <li><a href="#download">Download</a></li>
<path fill="currentColor" <li><a href="#config">Configurator</a></li>
d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"> <li><a href="https://github.com/KazushiMe/Switch-OC-Suite" target="_blank">GitHub repository</a></li>
</path> </ul>
</svg> </details>
</a></li> </li>
</ul>
<ul id="nav-list">
<li><a href="#readme">README</a></li>
<li><a href="#download">Download</a></li>
<li><a href="#config">Config</a></li>
<li><a href="https://github.com/KazushiMe/Switch-OC-Suite" target="_blank">GitHub</a></li>
</ul> </ul>
</nav> </nav>
<header class="container" style="padding-top: 6rem;"> <header class="container">
<hgroup> <hgroup>
<h1>Switch OC Suite</h1> <h1>Switch OC Suite</h1>
<h2>Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. Licensed under GPL v2.</h2> <h2>Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. Licensed under GPL v2.</h2>
</hgroup> </hgroup>
</header> </header>
</div> </div>
<section aria-label="Read Me" id="readme"> <section aria-label="README" id="readme">
<div class="container"> <div class="container">
<article> <article>
<header> <header>
<hgroup> <hgroup>
<h2>Read Me</h2> <h2>README</h2>
<h3>🚨DISCLAIMER: THIS IS PROVIDED AS IS. USE AT YOUR OWN RISK!🚨</h3> <h3>🚨DISCLAIMER: THIS IS PROVIDED AS IS. USE AT YOUR OWN RISK!🚨</h3>
</hgroup> </hgroup>
<li> <ul>
Overclocking in general will shorten the lifespan of some <li>
hardware components. <strong>YOU ARE RESPONSIBLE for any problem or Overclocking in general will shorten the lifespan of some
potential damage</strong> if unsafe frequencies are ENABLED in hardware components. <strong>YOU ARE RESPONSIBLE for any problem or
sys-clk-OC. Issues like asking for bypassing limit will BE IGNORED OR potential damage</strong> if unsafe frequencies are ENABLED in
CLOSED WITHOUT REPLY. sys-clk-OC. Issues like asking for bypassing limit will BE IGNORED OR
</li> CLOSED WITHOUT REPLY.
<li> </li>
Due to HorizonOS design, instabilities from unsafe RAM clocks may <li>
cause filesystem corruption. <strong>Always make backup before enabling Due to HorizonOS design, instabilities from unsafe RAM clocks may
DRAM OC.</strong> cause filesystem corruption. <strong>Always make backup before enabling
</li> DRAM OC.</strong>
</li>
</ul>
</header> </header>
<body> <body>
<h3>Features</h3> <h3>Features</h3>
<details> <details open>
<summary>For Erista variant (HAC-001)</summary> <summary>For Erista variant (HAC-001)</summary>
<ul> <ul>
<li>CPU Overclock (Safe: 1785 MHz) <li>CPU Overclock (Safe: 1785 MHz)
@@ -119,7 +162,7 @@
<li>DRAM Overclock (Safe: 1862.4 MHz)</li> <li>DRAM Overclock (Safe: 1862.4 MHz)</li>
</ul> </ul>
</details> </details>
<details> <details open>
<summary>For Mariko variant (HAC-001-01, HDH-001, HEG-001)</summary> <summary>For Mariko variant (HAC-001-01, HDH-001, HEG-001)</summary>
<ul> <ul>
<li>CPU / GPU Overclock (Safe: 1963 / 998 MHz) <li>CPU / GPU Overclock (Safe: 1963 / 998 MHz)
@@ -139,7 +182,7 @@
<li>DRAM Overclock (Safe: 1996.8 MHz)</li> <li>DRAM Overclock (Safe: 1996.8 MHz)</li>
</ul> </ul>
</details> </details>
<details> <details open>
<summary>Modded sys-clk and ReverseNX-RT</summary> <summary>Modded sys-clk and ReverseNX-RT</summary>
<ul> <ul>
<li>Auto CPU Boost <li>Auto CPU Boost
@@ -179,67 +222,64 @@
</li> </li>
</ul> </ul>
</details> </details>
<details> <details open>
<summary>System Settings (Optional)</summary> <summary>System Settings (Optional)</summary>
See <a See <a
href="https://github.com/KazushiMe/Switch-OC-Suite/blob/master/system_settings.md">system_settings.md</a> href="https://github.com/KazushiMe/Switch-OC-Suite/blob/master/system_settings.md">system_settings.md</a>
</details> </details>
<h3 id="installation">Installation</h3> <h3 id="installation" open>Installation</h3>
<ol type="1"> <ol type="1">
<li>Download latest <a href="#download">release</a>.</li> <li>Download latest <a href="#download">release</a>.</li>
<li>Copy all files in <code>SdOut</code> to the root of SD card.</li> <li>Copy all files in <code>SdOut</code> to the root of SD card.</li>
<li>Grab <code>x.x.x_loader.kip</code> for your Atmosphere version, rename it to <code>loader.kip</code> and <li>Grab <code>x.x.x_loader.kip</code> for your Atmosphere version, rename it to <code>loader.kip</code> and
place it in <code>/atmosphere/kips/</code>.</li> place it in <code>/atmosphere/kips/</code>.</li>
<li>Customization via <a href="#config">online loader configurator</a> <li>Customization via <a href="#config">online loader configurator</a>
<details> <table role="grid">
<summary>Default config</summary> <thead>
<table role="grid"> <tr>
<thead> <th>Defaults</th>
<tr> <th>Mariko</th>
<th>Defaults</th> <th>Erista</th>
<th>Mariko</th> </tr>
<th>Erista</th> </thead>
</tr> <tbody>
</thead> <tr>
<tbody> <td>CPU OC</td>
<tr> <td>2397 MHz Max</td>
<td>CPU OC</td> <td>2091 MHz Max</td>
<td>2397 MHz Max</td> </tr>
<td>2091 MHz Max</td> <tr>
</tr> <td>CPU Boost</td>
<tr> <td>1785 MHz</td>
<td>CPU Boost</td> <td>N/A</td>
<td>1785 MHz</td> </tr>
<td>N/A</td> <tr>
</tr> <td>CPU Volt</td>
<tr> <td>1235 mV Max</td>
<td>CPU Volt</td> <td>1235 mV Max</td>
<td>1235 mV Max</td> </tr>
<td>1235 mV Max</td> <tr>
</tr> <td>GPU OC</td>
<tr> <td>1305 MHz Max</td>
<td>GPU OC</td> <td>N/A</td>
<td>1305 MHz Max</td> </tr>
<td>N/A</td> <tr>
</tr> <td>RAM OC</td>
<tr> <td>1996 MHz Max</td>
<td>RAM OC</td> <td>1862 MHz Max</td>
<td>1996 MHz Max</td> </tr>
<td>1862 MHz Max</td> <tr>
</tr> <td>RAM Volt</td>
<tr> <td>Disabled</td>
<td>RAM Volt</td> <td>Disabled</td>
<td>Disabled</td> </tr>
<td>Disabled</td> <tr>
</tr> <td>RAM Timing</td>
<tr> <td>Auto-Adjusted</td>
<td>RAM Timing</td> <td>Disabled</td>
<td>Auto-Adjusted</td> </tr>
<td>Disabled</td> </tbody>
</tr> </table>
</tbody>
</table>
</details>
</li> </li>
<li>Hekate-ipl bootloader Only <li>Hekate-ipl bootloader Only
<ul> <ul>
@@ -248,35 +288,6 @@
</ul> </ul>
</li> </li>
</ol> </ol>
<details>
<summary><s>Deprecated: patching sysmodules manually</s></summary>
<ul>
<li>This method is only served as reference as it could damage your MMC file system if not handled
properly.</li>
<li>Patched sysmodules would be persistent until pcv or ptm was updated in new HOS (normally in
<code>x.0.0</code>).
</li>
<li>Tools:
<ul>
<li>Lockpick_RCM</li>
<li>TegraExplorer</li>
<li><a href="https://github.com/SciresM/hactool">hactool</a></li>
<li><a href="https://github.com/shuffle2/nx2elf">nx2elf</a></li>
<li>elf2nso from <a href="https://github.com/switchbrew/switch-tools/">switch-tools</a></li>
<li><a href="https://github.com/The-4n/hacPack">hacpack</a></li>
</ul>
</li>
</ul>
<ol type="1">
<li>Dump <code>prod.keys</code> with Lockpick_RCM</li>
<li>Dump HOS firmware with TegraExplorer</li>
<li>Configure and run <code>test_patch.sh</code> to generate patched pcv &amp; ptm sysmodules in nca</li>
<li>Replace nca in <code>SYSTEM:/Contents/registered/</code> with TegraExplorer</li>
<li><code>ValidateAcidSignature()</code> should be stubbed to allow unsigned sysmodules to load (a.k.a.
<code>loader_patch</code>)
</li>
</ol>
</details>
<details> <details>
<summary>How to build this project</summary> <summary>How to build this project</summary>
<ol type="1"> <ol type="1">
@@ -291,29 +302,33 @@
</ol> </ol>
</details> </details>
<h3 id="faq">Frequently Asked Questions</h3> <h3 id="faq">Frequently Asked Questions</h3>
<details> <details open>
<summary>How to enable unsafe frequencies in sys-clk-OC?</summary> <summary>How to enable unsafe frequencies in sys-clk-OC?</summary>
<li>Above all else, you should know <a href="#readme">what "unsafe" means and issues might arise</a>.</li> <ul>
<li>See the end of <a <li>Above all else, you should know <a href="#readme">what "unsafe" means and issues might arise</a>.</li>
href="https://github.com/KazushiMe/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md" <li>See the end of <a href="https://github.com/KazushiMe/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md"
target="_blank">README in sys-clk-OC</a>. Add this line <code>allow_unsafe_freq=1</code> into target="_blank">README in sys-clk-OC</a>. Place this line <code>allow_unsafe_freq=1</code> under
<code>/config/sys-clk/config.ini</code> <code>[value]</code> section in
</li> <code>/config/sys-clk/config.ini</code>
</li>
</ul>
</details> </details>
<details> <details open>
<summary>I would like to bypass limit enforced in sys-clk to improve handheld performance without charger <summary>I would like to bypass limit enforced in sys-clk to improve handheld performance without charger
connected.</summary> connected.</summary>
<li>Never will it be implemented here, or work out of the box.</li> <ul>
<li>You have to modify the code yourself for your own use. If you are to share modified binaries you have <li>Never will it be implemented here, or work out of the box.</li>
made based on this project publicly, make sure to comply with GPL v2 licenses.</li> <li>You have to modify the code yourself for your own use. If you are to share modified binaries you have
made based on this project publicly, make sure to comply with GPL v2 licenses.</li>
</ul>
</details> </details>
</body> </body>
<footer> <footer>
<details role="list"> <details role="list">
<summary aria-haspopup="listbox" role="button" class="secondary"> <summary aria-haspopup="group" role="button" class="secondary">
Acknowledgement Acknowledgement
</summary> </summary>
<ul role="listbox"> <ul role="group">
<li><a href="https://github.com/CTCaer/hekate" target="_blank">CTCaer for Hekate-ipl bootloader, RE and <li><a href="https://github.com/CTCaer/hekate" target="_blank">CTCaer for Hekate-ipl bootloader, RE and
hardware research</a></li> hardware research</a></li>
<li><a href="https://devkitpro.org/" target="_blank">devkitPro for All-In-One homebrew toolchains</a> <li><a href="https://devkitpro.org/" target="_blank">devkitPro for All-In-One homebrew toolchains</a>
@@ -371,31 +386,38 @@
<h3>Configure frequencies and voltages to suit your hardware and preferences.</h3> <h3>Configure frequencies and voltages to suit your hardware and preferences.</h3>
</hgroup> </hgroup>
</header> </header>
<body> <body>
<nav role="tab-control">
<button class="" id="tab_all" data-filter="0" disabled>Show All</button>
<button class="outline" id="tab_erista" data-filter="1" disabled>For Erista</button>
<button class="outline" id="tab_mariko" data-filter="2" disabled>For Mariko</button>
</nav>
<form id="form"> <form id="form">
<nav role="tablist">
<button type="button" role="tab" class="" id="tab_all" data-platform="0" disabled>All</button>
<button type="button" role="tab" class="outline" id="tab_erista" data-platform="1" disabled>Erista</button>
<button type="button" role="tab" class="outline" id="tab_mariko" data-platform="2" disabled>Mariko</button>
</nav>
<label for="file"> <label for="file">
<input id="file" type="file"> <input id="file" type="file">
<small id="cust_rev">Upload loader.kip here</small> <small id="cust_rev">Upload loader.kip here</small>
</label> </label>
<div id="config-list-basic"></div>
<div id="config-list-advanced"></div>
</form> </form>
</body> </body>
<footer> <footer>
<div class="grid"> <div class="grid">
<button id="load_default" role="button" disabled>Load Default</button> <button type="button" id="load_default" role="button" disabled>Load Default</button>
<button id="load_saved" role="button" disabled>Load Last Saved</button> <button type="button" id="load_saved" role="button" disabled>Load Last Saved</button>
<button id="save" role="button" disabled>Save</button> <button type="submit" id="save" role="button" disabled>Save</button>
</div> </div>
</footer> </footer>
</article> </article>
</div> </div>
</section> </section>
<footer class="container"> <footer class="container">
<small>Build with <a href="https://picocss.com" target="_blank">Pico</a></small> <small>
Build with <a href="https://picocss.com" target="_blank">Pico</a>.
All trademarks, logos and brand names are the property of their respective owners, used for identification purposes
only.
</small>
</footer> </footer>
</body> </body>
<script type="text/javascript" src="./main.js"></script> <script type="text/javascript" src="./main.js"></script>

View File

@@ -1,6 +1,5 @@
/* Config: Cust */ /* Config: Cust */
const CUST_REV = 3; const CUST_REV = 3;
var buffer: ArrayBuffer;
enum CustPlatform { enum CustPlatform {
Undefined = 0, Undefined = 0,
@@ -10,50 +9,39 @@ enum CustPlatform {
}; };
class CustEntry { class CustEntry {
id: string;
name: string;
platform: CustPlatform;
size: number;
desc: string;
defval: number;
min: number; min: number;
max: number; max: number;
step: number; // also as quotient
zeroable: boolean;
value?: number; value?: number;
offset?: number; offset?: number;
constructor(id: string, name: string, platform: CustPlatform, size: number, desc: string, defval: number, minmax: [number, number] = [0, 1_000_000], step: number = 1, zeroable: boolean = true) { constructor(
this.id = id; public id: string,
this.name = name; public name: string,
this.platform = platform; public platform: CustPlatform,
this.size = size; public size: number,
this.desc = desc; public desc: string[],
this.defval = defval; public defval: number,
minmax: [number, number] = [0, 1_000_000],
public step: number = 1,
public zeroable: boolean = true) {
this.min = minmax[0]; this.min = minmax[0];
this.max = minmax[1]; this.max = minmax[1];
this.step = step;
this.zeroable = zeroable;
}; };
validate(): boolean { validate(): boolean {
let tip = new ErrorToolTip(this.id); let tip = new ErrorToolTip(this.id).clear();
tip.clear();
if (Number.isNaN(this.value) || this.value === undefined) { if (Number.isNaN(this.value) || this.value === undefined) {
tip.setMsg(`Invalid value: Not a number`); tip.setMsg(`Invalid value: Not a number`).show();
tip.show();
return false; return false;
} }
if (this.zeroable && this.value == 0) if (this.zeroable && this.value == 0)
return true; return true;
if (this.value < this.min || this.value > this.max) { if (this.value < this.min || this.value > this.max) {
tip.setMsg(`Expected range: [${this.min}, ${this.max}], got ${this.value}.`); tip.setMsg(`Expected range: [${this.min}, ${this.max}], got ${this.value}.`).show();
tip.show();
return false; return false;
} }
if (this.value % this.step != 0) { if (this.value % this.step != 0) {
tip.setMsg(`${this.value} % ${this.step} ≠ 0`); tip.setMsg(`${this.value} % ${this.step} ≠ 0`).show();
tip.show();
return false; return false;
} }
return true; return true;
@@ -64,7 +52,7 @@ class CustEntry {
} }
updateValueFromElement() { updateValueFromElement() {
this.value = Number(this.getInputElement()!.value); this.value = Number(this.getInputElement()?.value);
} }
isAvailableFor(platform: CustPlatform): boolean { isAvailableFor(platform: CustPlatform): boolean {
@@ -92,20 +80,23 @@ class CustEntry {
// Description in blockquote style // Description in blockquote style
let desc = document.createElement("blockquote"); let desc = document.createElement("blockquote");
desc.innerHTML = this.desc; desc.innerHTML = "<ul>" + this.desc.map(i => `<li>${i}</li>`).join('') + "</ul>";
desc.setAttribute("for", this.id); desc.setAttribute("for", this.id);
grid.appendChild(desc); grid.appendChild(desc);
document.getElementById("form")!.appendChild(grid); document.getElementById("config-list-basic")!.appendChild(grid);
let tooltip = new ErrorToolTip(this.id); new ErrorToolTip(this.id).addChangeListener();
tooltip.addChangeListener();
} }
input.value = String(this.value); input.value = String(this.value);
} }
setElementValue() {
this.getInputElement()!.value = String(this.value!);
}
setElementDefaultValue() { setElementDefaultValue() {
(document.getElementById(this.id) as HTMLInputElement).value = String(this.defval); this.getInputElement()!.value = String(this.defval);
} }
removeElement() { removeElement() {
@@ -136,9 +127,9 @@ var CustTable: Array<CustEntry> = [
"DRAM Timing", "DRAM Timing",
CustPlatform.Mariko, CustPlatform.Mariko,
2, 2,
"<li><b>0</b>: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density. (Default)</li>\ ["<b>0</b>: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density. (Default)",
<li><b>1</b>: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.</li>\ "<b>1</b>: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.",
<li><b>2</b>: NO_ADJ_ALL: No timing adjustment for both Erista and Mariko. Might achieve better performance on Mariko but lower maximum frequency is expected.", "<b>2</b>: NO_ADJ_ALL: No timing adjustment for both Erista and Mariko. Might achieve better performance on Mariko but lower maximum frequency is expected."],
0, 0,
[0, 2], [0, 2],
1 1
@@ -148,8 +139,8 @@ var CustTable: Array<CustEntry> = [
"Mariko CPU Max Clock in kHz", "Mariko CPU Max Clock in kHz",
CustPlatform.Mariko, CustPlatform.Mariko,
4, 4,
"<li>System default: 1785000</li>\ ["System default: 1785000",
<li>2397000 might be unreachable for some SoCs.</li>", "2397000 might be unreachable for some SoCs."],
2397_000, 2397_000,
[1785_000, 3000_000], [1785_000, 3000_000],
1, 1,
@@ -159,9 +150,9 @@ var CustTable: Array<CustEntry> = [
"Mariko CPU Boost Clock in kHz", "Mariko CPU Boost Clock in kHz",
CustPlatform.Mariko, CustPlatform.Mariko,
4, 4,
"<li>System default: 1785000</li>\ ["System default: 1785000",
<li>Boost clock will be applied when applications request higher CPU frequency for quicker loading.</li>\ "Boost clock will be applied when applications request higher CPU frequency for quicker loading.",
<li>This will be set regardless of whether sys-clk is enabled.</li>", "This will be set regardless of whether sys-clk is enabled."],
1785_000, 1785_000,
[1020_000, 3000_000], [1020_000, 3000_000],
1, 1,
@@ -172,8 +163,8 @@ var CustTable: Array<CustEntry> = [
"Mariko CPU Max Voltage in mV", "Mariko CPU Max Voltage in mV",
CustPlatform.Mariko, CustPlatform.Mariko,
4, 4,
"<li>System default: 1120</li>\ ["System default: 1120",
<li>Acceptable range: 1100 ≤ x ≤ 1300</li>", "Acceptable range: 1100 ≤ x ≤ 1300"],
1235, 1235,
[1100, 1300], [1100, 1300],
5 5
@@ -183,9 +174,9 @@ var CustTable: Array<CustEntry> = [
"Mariko GPU Max Clock in kHz", "Mariko GPU Max Clock in kHz",
CustPlatform.Mariko, CustPlatform.Mariko,
4, 4,
"<li>System default: 921600</li>\ ["System default: 921600",
<li>Tegra X1+ official maximum: 1267200</li>\ "Tegra X1+ official maximum: 1267200",
<li>1305600 might be unreachable for some SoCs.</li>", "1305600 might be unreachable for some SoCs."],
1305_600, 1305_600,
[768_000, 1536_000], [768_000, 1536_000],
100, 100,
@@ -195,8 +186,8 @@ var CustTable: Array<CustEntry> = [
"Mariko RAM Max Clock in kHz", "Mariko RAM Max Clock in kHz",
CustPlatform.Mariko, CustPlatform.Mariko,
4, 4,
"<li>Values should be ≥ 1600000, and divided evenly by 3200.</li>\ ["Values should be ≥ 1600000, and divided evenly by 3200.",
<li><b>WARNING:</b> RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM</li>", "<b>WARNING:</b> RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM."],
1996_800, 1996_800,
[1600_000, 2400_000], [1600_000, 2400_000],
3200, 3200,
@@ -206,11 +197,11 @@ var CustTable: Array<CustEntry> = [
"EMC Vddq (Mariko Only) Voltage in uV", "EMC Vddq (Mariko Only) Voltage in uV",
CustPlatform.Mariko, CustPlatform.Mariko,
4, 4,
"<li>Acceptable range: 550000 ≤ x ≤ 650000</li>\ ["Acceptable range: 550000 ≤ x ≤ 650000",
<li>Value should be divided evenly by 5000</li>\ "Value should be divided evenly by 5000",
<li>Default: 600000</li>\ "Default: 600000",
<li>Not enabled by default.</li>\ "Not enabled by default.",
<li>This will not work without sys-clk-OC.</li>", "This will not work without sys-clk-OC."],
0, 0,
[550_000, 650_000], [550_000, 650_000],
5000, 5000,
@@ -220,7 +211,7 @@ var CustTable: Array<CustEntry> = [
"Erista CPU Max Voltage in mV", "Erista CPU Max Voltage in mV",
CustPlatform.Erista, CustPlatform.Erista,
4, 4,
"<li>Acceptable range: 1100 ≤ x ≤ 1300</li>", ["Acceptable range: 1100 ≤ x ≤ 1300"],
1235, 1235,
[1100, 1300], [1100, 1300],
1, 1,
@@ -230,8 +221,8 @@ var CustTable: Array<CustEntry> = [
"Erista RAM Max Clock in kHz", "Erista RAM Max Clock in kHz",
CustPlatform.Erista, CustPlatform.Erista,
4, 4,
"<li>Values should be ≥ 1600000, and divided evenly by 3200.</li>\ ["Values should be ≥ 1600000, and divided evenly by 3200.",
<li><b>WARNING:</b> RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM</li>", "<b>WARNING:</b> RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM"],
1862_400, 1862_400,
[1600_000, 2400_000], [1600_000, 2400_000],
3200, 3200,
@@ -241,314 +232,363 @@ var CustTable: Array<CustEntry> = [
"EMC Vddq (Erista Only) & RAM Vdd2 Voltage in uV", "EMC Vddq (Erista Only) & RAM Vdd2 Voltage in uV",
CustPlatform.All, CustPlatform.All,
4, 4,
"<li>Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.</li>\ ["Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.",
<li>Erista Default (HOS): 1125000 (bootloader: 1100000)</li>\ "Erista Default (HOS): 1125000 (bootloader: 1100000)",
<li>Mariko Default: 1100000 (It will not work without sys-clk-OC)</li>\ "Mariko Default: 1100000 (It will not work without sys-clk-OC)",
<li>Not enabled by default</li>", "Not enabled by default"],
0, 0,
[1100_000, 1250_000], [1100_000, 1250_000],
12500, 12500,
), ),
]; ];
function FindMagicOffset(buffer) {
let view = new DataView(buffer);
for (let i = 0; i < view.byteLength; i += 4) {
if (view.getUint32(i, true) == 0x54535543) { // "CUST"
return i;
}
}
throw new Error("Invalid loader.kip file");
}
class ErrorToolTip { class ErrorToolTip {
id: string;
message: string | null;
element: HTMLElement | null; element: HTMLElement | null;
constructor(id: string, msg?: string) { constructor(public id: string, public msg?: string) {
this.id = id; this.id = id;
this.element = document.getElementById(id); this.element = document.getElementById(id);
if (msg) { this.setMsg(msg); } if (msg) { this.setMsg(msg); }
}; };
setMsg(msg: string) { setMsg(msg: string): ErrorToolTip {
this.message = msg; this.msg = msg;
return this;
} }
show() { show(): ErrorToolTip {
if (this.element) { this.element?.setAttribute("aria-invalid", "true");
this.element.setAttribute("aria-invalid", "true"); if (this.msg) {
if (this.message) { this.element?.setAttribute("title", this.msg);
this.element.setAttribute("title", this.message); this.element?.parentElement?.setAttribute("data-tooltip", this.msg);
this.element.parentElement!.setAttribute("data-tooltip", this.message); this.element?.parentElement?.setAttribute("data-placement", "top");
this.element.parentElement!.setAttribute("data-placement", "top");
}
} }
return this;
}; };
clear() { clear(): ErrorToolTip {
if (this.element) { this.element?.removeAttribute("aria-invalid");
this.element.removeAttribute("aria-invalid"); this.element?.removeAttribute("title");
this.element.removeAttribute("title"); this.element?.parentElement?.removeAttribute("data-tooltip");
this.element.parentElement!.removeAttribute("data-tooltip"); this.element?.parentElement?.removeAttribute("data-placement");
this.element.parentElement!.removeAttribute("data-placement"); return this;
}
} }
addChangeListener() { addChangeListener() {
if (this.element) { this.element?.addEventListener('change', (_evt) => {
this.element.addEventListener('change', (_evt) => { let obj = CustTable.filter((obj) => { return obj.id === this.id; })[0];
let obj = CustTable.filter((obj) => { return obj.id === this.id; })[0]; obj.value = Number((this.element as HTMLInputElement).value);
obj.value = Number((this.element as HTMLInputElement).value); obj.validate();
obj.validate(); });
});
}
} }
}; };
function SaveCust(buffer) { class CustStorage {
let view = new DataView(buffer); storage: { [key: string]: (number | undefined) } = {};
let storage = {}; readonly key = "last_saved";
CustTable.forEach(i => { updateFromTable() {
i.updateValueFromElement(); CustTable.forEach(i => {
if (!i.validate()) { i.updateValueFromElement();
document.getElementById(i.id)!.focus(); if (!i.validate()) {
throw new Error(`Invalid ${i.name}`); i.getInputElement()?.focus();
} throw new Error(`Invalid ${i.name}`);
if (!i.offset) {
document.getElementById(i.id)!.focus();
throw new Error(`Failed to get offset for ${i.name}`);;
}
switch (i.size) {
case 2:
view.setUint16(i.offset, i.value!, true);
break;
case 4:
view.setUint32(i.offset, i.value!, true);
break;
default:
document.getElementById(i.id)!.focus();
throw new Error(`Unknown size at ${i.name}`);
}
storage[i.id] = i.value;
});
storage["custRev"] = CUST_REV;
localStorage.setItem("last_saved", JSON.stringify(storage));
let a = document.createElement("a");
a.href = window.URL.createObjectURL(new Blob([buffer], { type: "application/octet-stream" }));
a.download = "loader.kip";
a.click();
}
function LastSaved() {
let storage = localStorage.getItem("last_saved");
if (!storage) {
return false;
}
let sObj = JSON.parse(storage);
if (!sObj["custRev"] || sObj["custRev"] != CUST_REV) {
localStorage.removeItem("last_saved");
return false;
}
return true;
}
function CustNavTabsInit() {
const custNavTabs = Array.from(document.querySelectorAll(`nav[role="tab-control"] > button`)) as HTMLElement[];
custNavTabs.forEach(i => {
i.removeAttribute("disabled");
let platform = Number(i.getAttribute("data-filter")!) as CustPlatform;
i.addEventListener('click', (_evt) => {
const unfocusedClasses = ["outline"];
i.classList.remove(...unfocusedClasses);
custNavTabs.filter(j => j != i).forEach(k => k.classList.add(...unfocusedClasses));
CustTable.forEach(e => {
if (e.isAvailableFor(platform)) {
e.showElement();
} else {
e.hideElement();
}
});
});
});
}
function UpdateHTMLForm() {
CustTable.forEach(i => i.createElement());
let default_btn = document.getElementById("load_default")!;
default_btn.removeAttribute("disabled");
default_btn.addEventListener('click', () => {
CustTable.forEach(i => i.setElementDefaultValue());
});
let last_btn = document.getElementById("load_saved")!;
if (LastSaved()) {
last_btn.style.removeProperty("display");
last_btn.removeAttribute("disabled");
last_btn.addEventListener('click', () => {
// Load last saved from localStorage
let dict = JSON.parse(localStorage.getItem("last_saved")!);
delete dict["custRev"];
dict.forEach((key: string) => (document.getElementById(key) as HTMLInputElement).value = key);
});
} else {
last_btn.style.setProperty("display", "none");
}
let save_btn = document.getElementById("save")!;
save_btn.removeAttribute("disabled");
save_btn.addEventListener('click', () => {
try {
SaveCust(buffer);
} catch (e) {
console.log(e);
alert(e);
}
});
}
function ParseCust(magicOffset: number, buffer: ArrayBuffer) {
let view = new DataView(buffer);
let offset = magicOffset + 4;
let rev = view.getUint16(offset, true);
offset += 2;
if (rev != CUST_REV) {
throw new Error(`Unsupported custRev, expected: ${CUST_REV}, got ${rev}`);
}
document.getElementById("cust_rev")!.innerHTML = `Cust V${CUST_REV} is loaded.`;
CustTable.forEach(i => {
i.offset = offset;
switch (i.size) {
case 2:
i.value = view.getUint16(offset, true);
break;
case 4:
i.value = view.getUint32(offset, true);
break;
default:
document.getElementById(i.id)!.focus();
throw new Error("Unknown size at " + i);
}
offset += i.size;
i.validate();
});
}
const fileInput = document.getElementById("file") as HTMLInputElement;
fileInput.addEventListener('change', (event) => {
let reader = new FileReader();
reader.readAsArrayBuffer((event.target as HTMLInputElement).files![0]);
reader.onloadend = (progEvent) => {
if (progEvent.target!.readyState === FileReader.DONE) {
buffer = (progEvent.target!.result!) as ArrayBuffer;
try {
let offset = FindMagicOffset(buffer);
CustTable.forEach(i => i.removeElement());
ParseCust(offset, buffer);
CustNavTabsInit();
UpdateHTMLForm();
} catch (e) {
console.log(e);
alert(e);
fileInput.value = "";
} }
}
}
});
/* GitHub Release fetch */
type ReleaseInfo = {
OCSuiteVer: string;
LoaderKipUrl: string;
LoaderKipTime: string;
SdOutZipUrl: string;
SdOutZipTime: string;
AMSVer: string;
AMSUrl: string;
};
async function fetchRelease(): Promise<ReleaseInfo | void> {
try {
const responseFromSuite = await fetch('https://api.github.com/repos/KazushiMe/Switch-OC-Suite/releases/latest', {
method: 'GET',
headers: {
Accept: 'application/json',
},
}); });
if (!responseFromSuite.ok) {
throw new Error(`Failed to fetch latest release info from GitHub: ${responseFromSuite.status}`); this.storage = {};
let kv = Object.fromEntries(CustTable.map((i) => [i.id, i.value]));
Object.keys(kv)
.forEach(k => this.storage[k] = kv[k]);
}
setTable() {
let keys = Object.keys(this.storage);
keys.forEach(k => CustTable.filter(i => i.id == k)[0].value = this.storage[k]);
// Set default for missing values
CustTable.filter(i => !keys.includes(i.id))
.forEach(i => i.value = i.defval);
CustTable.forEach(i => {
if (!i.validate()) {
i.getInputElement()?.focus();
throw new Error(`Invalid ${i.name}`);
}
i.setElementValue();
});
}
save() {
localStorage.setItem(this.key, JSON.stringify(this.storage));
}
load(): { [key: string]: (number | undefined) } | null {
let s = localStorage.getItem(this.key);
if (!s) {
return null;
} }
let dict = JSON.parse(s);
const resultFromSuite = await responseFromSuite.json(); let keys = CustTable.map(i => i.id);
const latestVerFromSuite = resultFromSuite.tag_name; let ignoredKeys: string[] = Object.keys(dict).filter(k => !keys.includes(k));
const amsVer = latestVerFromSuite.split(".").slice(0, 3).join("."); if (ignoredKeys.length) {
console.log(`Ignored: ${ignoredKeys}`);
const loaderKip = resultFromSuite.assets.filter((obj) => { }
return obj.name.endsWith("loader.kip"); Object.keys(dict)
})[0]; .filter(k => keys.includes(k))
const sdOut = resultFromSuite.assets.filter((obj) => { .forEach(k => this.storage[k] = dict[k]);
return obj.name.endsWith(".zip"); return this.storage;
})[0];
const amsReleaseUrl = `https://github.com/Atmosphere-NX/Atmosphere/releases/tags/${amsVer}`;
let info: ReleaseInfo = {
OCSuiteVer: latestVerFromSuite,
LoaderKipUrl: loaderKip.browser_download_url,
LoaderKipTime: loaderKip.updated_at,
SdOutZipUrl: sdOut.browser_download_url,
SdOutZipTime: sdOut.updated_at,
AMSVer: amsVer,
AMSUrl: amsReleaseUrl
};
return info;
} catch (e) {
console.log(e);
alert(e);
} }
} }
const isElementVisible = (element: HTMLElement): boolean => { class Cust {
let rect = element.getBoundingClientRect(); buffer: ArrayBuffer;
return ( view: DataView;
rect.top > 0 && beginOffset: number;
rect.left > 0 && storage: CustStorage = new CustStorage();
rect.bottom - rect.height < (window.innerHeight || document.documentElement.clientHeight) && readonly magic = 0x54535543; // "CUST"
rect.right - rect.width < (window.innerWidth || document.documentElement.clientWidth) readonly magicLen = 4;
);
};
async function updateDownloadUrls() { mapper : {[size: number]: { get: any, set: any }} = {
// Wait until download buttons are visible 2: {
while (!isElementVisible(document.getElementById("download_btn_grid")!)) { get: (offset: number) => this.view.getUint16(offset, true),
await new Promise(r => setTimeout(r, 1000)); set: (offset: number, value: number) => this.view.setUint16(offset, value, true),
},
4: {
get: (offset: number) => this.view.getUint32(offset, true),
set: (offset: number, value: number) => this.view.setUint32(offset, value, true) },
}; };
const updateHref = (id: string, name: string, url: string) => { findMagicOffset() {
this.view = new DataView(this.buffer);
for (let offset = 0; offset < this.view.byteLength; offset += this.magicLen) {
if (this.mapper[this.magicLen].get(offset) == this.magic) {
this.beginOffset = offset;
return;
}
}
throw new Error("Invalid loader.kip file");
}
save() {
this.storage.updateFromTable();
CustTable.forEach(i => {
if (!i.offset) {
i.getInputElement()?.focus();
throw new Error(`Failed to get offset for ${i.name}`);
}
let mapper = this.mapper[i.size];
if (!mapper) {
i.getInputElement()?.focus();
throw new Error(`Unknown size at ${i.name}`);
}
mapper.set(i.offset, i.value!);
});
this.storage.save();
let a = document.createElement("a");
a.href = window.URL.createObjectURL(new Blob([this.buffer], { type: "application/octet-stream" }));
a.download = "loader.kip";
a.click();
this.toggleLoadLastSavedBtn(true);
}
removeHTMLForm() {
CustTable.forEach(i => i.removeElement());
}
toggleLoadLastSavedBtn(enable: boolean) {
let last_btn = document.getElementById("load_saved")!;
if (enable) {
last_btn.addEventListener('click', () => {
if (this.storage.load()) {
this.storage.setTable();
}
});
last_btn.style.removeProperty("display");
last_btn.removeAttribute("disabled");
} else {
last_btn.style.setProperty("display", "none");
}
}
createHTMLForm() {
CustTable.forEach(i => i.createElement());
let default_btn = document.getElementById("load_default")!;
default_btn.removeAttribute("disabled");
default_btn.addEventListener('click', () => {
CustTable.forEach(i => i.setElementDefaultValue());
});
this.toggleLoadLastSavedBtn(this.storage.load() !== null);
let save_btn = document.getElementById("save")!;
save_btn.removeAttribute("disabled");
save_btn.addEventListener('click', () => {
try {
this.save();
} catch (e) {
console.error(e);
alert(e);
}
});
}
initCustTabs() {
const custTabs = Array.from(document.querySelectorAll(`nav[role="tablist"] > button`)) as HTMLElement[];
custTabs.forEach(tab => {
tab.removeAttribute("disabled");
let platform = Number(tab.getAttribute("data-platform")!) as CustPlatform;
tab.addEventListener('click', (_evt) => {
// Set other tabs to unfocused state
const unfocusedClasses = ["outline"];
tab.classList.remove(...unfocusedClasses);
let otherTabs = custTabs.filter(j => j != tab);
otherTabs.forEach(k => k.classList.add(...unfocusedClasses));
CustTable.forEach(e => {
e.isAvailableFor(platform) ? e.showElement() : e.hideElement();
});
});
});
}
parse() {
let offset = this.beginOffset + this.magicLen;
let revLen = 2;
let rev = this.mapper[revLen].get(offset);
if (rev != CUST_REV) {
throw new Error(`Unsupported custRev, expected: ${CUST_REV}, got ${rev}`);
}
offset += revLen;
document.getElementById("cust_rev")!.innerHTML = `Cust v${CUST_REV} is loaded.`;
CustTable.forEach(i => {
i.offset = offset;
let mapper = this.mapper[i.size];
if (!mapper) {
i.getInputElement()?.focus();
throw new Error(`Unknown size at ${i}`);
}
i.value = mapper.get(offset);
offset += i.size;
i.validate();
});
}
load(buffer: ArrayBuffer) {
try {
this.buffer = buffer;
this.findMagicOffset();
this.removeHTMLForm();
this.parse();
this.initCustTabs();
this.createHTMLForm();
} catch (e) {
console.error(e);
alert(e);
}
}
}
/* GitHub Release fetch */
class ReleaseAsset {
downloadUrl: string;
updatedAt: string;
constructor (obj: { browser_download_url: string; updated_at: string; }) {
this.downloadUrl = obj.browser_download_url;
this.updatedAt = obj.updated_at;
};
};
class ReleaseInfo {
ocVer: string;
amsVer: string;
loaderKipAsset: ReleaseAsset;
sdOutZipAsset: ReleaseAsset;
amsUrl: string;
readonly ocLatestApi = "https://api.github.com/repos/KazushiMe/Switch-OC-Suite/releases/latest";
async load() {
try {
this.parseOcResponse(await this.responseFromApi(this.ocLatestApi).catch());
} catch (e) {
console.error(e);
alert(e);
}
};
async responseFromApi(apiUrl: string) : Promise<any> {
const response = await fetch(apiUrl, { method: 'GET', headers: { Accept: 'application/json' } } );
if (response.ok) {
return await response.json();
}
throw new Error(`Failed to connect to "${apiUrl}": ${response.status}`);
};
parseOcResponse(response) {
this.ocVer = response.tag_name;
this.amsVer = this.ocVer.split(".").slice(0, 3).join(".");
this.loaderKipAsset = new ReleaseAsset(response.assets.filter( a => a.name.endsWith("loader.kip") )[0]);
this.sdOutZipAsset = new ReleaseAsset(response.assets.filter( a => a.name.endsWith(".zip") )[0]);
this.amsUrl = `https://github.com/Atmosphere-NX/Atmosphere/releases/tags/${this.amsVer}`;
};
};
class DownloadSection {
readonly element: HTMLElement = document.getElementById("download_btn_grid")!;
async load() {
while(!this.isVisible()) {
await new Promise(r => setTimeout(r, 1000));
}
const info = new ReleaseInfo()
await info.load();
this.update("loader_kip_btn", `loader.kip <b>${info.ocVer}</b><br>${info.loaderKipAsset.updatedAt}`, info.loaderKipAsset.downloadUrl);
this.update("sdout_zip_btn", `SdOut.zip <b>${info.ocVer}</b><br>${info.sdOutZipAsset.updatedAt}`, info.sdOutZipAsset.downloadUrl);
this.update("ams_btn", `Atmosphere-NX <b>${info.amsVer}</b>`, info.amsUrl);
}
isVisible(): boolean {
let rect = this.element.getBoundingClientRect();
return (
rect.top > 0 &&
rect.left > 0 &&
rect.bottom - rect.height < (window.innerHeight || document.documentElement.clientHeight) &&
rect.right - rect.width < (window.innerWidth || document.documentElement.clientWidth)
);
}
update(id: string, name: string, url: string) {
let element = document.getElementById(id)!; let element = document.getElementById(id)!;
element.innerHTML = name; element.innerHTML = name;
element.removeAttribute("aria-busy"); element.removeAttribute("aria-busy");
element.setAttribute("href", url); element.setAttribute("href", url);
};
let info = await fetchRelease();
if (info) {
const loaderKipName = `loader.kip <b>${info.OCSuiteVer}</b><br>${info.LoaderKipTime}`;
updateHref("loader_kip_btn", loaderKipName, info.LoaderKipUrl);
const sdOutName = `SdOut.zip <b>${info.OCSuiteVer}</b><br>${info.SdOutZipTime}`;
updateHref("sdout_zip_btn", sdOutName, info.SdOutZipUrl);
const amsName = `Atmosphere-NX <b>${info.AMSVer}</b>`;
updateHref("ams_btn", amsName, info.AMSUrl);
} }
} }
addEventListener('DOMContentLoaded', async (_evt) => { const fileInput = document.getElementById("file") as HTMLInputElement;
await updateDownloadUrls(); fileInput.addEventListener('change', (event) => {
var cust: Cust = new Cust();
// User canceled or non files selected
if (!event.target || !(event.target as HTMLInputElement).files) {
return;
}
let reader = new FileReader();
reader.readAsArrayBuffer((event.target as HTMLInputElement).files![0]);
reader.onloadend = (progEvent) => {
if (progEvent.target!.readyState == FileReader.DONE) {
cust.load(progEvent.target!.result! as ArrayBuffer);
}
};
});
addEventListener('DOMContentLoaded', async (_evt) => {
await new DownloadSection().load();
}); });