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
- 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

View File

@@ -77,15 +77,46 @@ uint32_t GetModuleMaximumFreq(SysClkModule module);
typedef enum {
SysClkOcGovernorConfig_AllDisabled = 0,
SysClkOcGovernorConfig_CPU_Shift = 0,
SysClkOcGovernorConfig_CPUOnly = 1,
SysClkOcGovernorConfig_CPUOnly = 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_GPU = 1 << SysClkOcGovernorConfig_GPU_Shift,
SysClkOcGovernorConfig_AllEnabled = 3,
SysClkOcGovernorConfig_Default = 3,
SysClkOcGovernorConfig_Mask = 3,
} 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
{
union {

View File

@@ -74,10 +74,9 @@ void AppProfileGui::listUI()
if (globalGovernorEnabled) {
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) {
this->profileList->governorConfig =
SysClkOcGovernorConfig((this->profileList->governorConfig & SysClkOcGovernorConfig_GPUOnly) | state << SysClkOcGovernorConfig_CPU_Shift);
this->profileList->governorConfig = ToggleGovernor(this->profileList->governorConfig, SysClkModule_CPU, state);
Result rc = sysclkIpcSetProfiles(this->applicationId, this->profileList);
if (R_FAILED(rc))
@@ -86,10 +85,9 @@ void AppProfileGui::listUI()
this->listElement->addItem(cpuGovernorToggle);
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) {
this->profileList->governorConfig =
SysClkOcGovernorConfig((this->profileList->governorConfig & SysClkOcGovernorConfig_CPUOnly) | state << SysClkOcGovernorConfig_GPU_Shift);
this->profileList->governorConfig = ToggleGovernor(this->profileList->governorConfig, SysClkModule_GPU, state);
Result rc = sysclkIpcSetProfiles(this->applicationId, this->profileList);
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) {
if (m_config == config)
return;
@@ -342,16 +329,16 @@ void Governor::GovernorManager::ContextManager(void* args) {
}
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();
if ((cpuBoosted = apmExtIsCPUBoosted(perf_conf)) && (self->GetConfig() & SysClkOcGovernorConfig_CPU))
if ((cpuBoosted = apmExtIsCPUBoosted(perf_conf)) && self->IsHandledByGovernor(SysClkModule_CPU))
self->m_cpu_gov->ApplyBoost();
}
if (!gpuThrottled && (self->GetConfig() & SysClkOcGovernorConfig_GPU))
if (!gpuThrottled && self->IsHandledByGovernor(SysClkModule_GPU))
self->m_gpu_gov->Apply();
if (!cpuBoosted && (self->GetConfig() & SysClkOcGovernorConfig_CPU))
if (!cpuBoosted && self->IsHandledByGovernor(SysClkModule_CPU))
self->m_cpu_gov->Apply();
svcSleepThread(TICK_TIME_NS);

View File

@@ -329,7 +329,7 @@ public:
};
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 SetPerfConf(uint32_t id);

View File

@@ -6,7 +6,7 @@
# README_HTML=`pandoc -f gfm -t html5 ../README.md`
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
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.">
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
<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-left-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-bottom-left-radius: var(--border-radius);
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-bottom-right-radius: var(--border-radius);
border-right-width: var(--border-width);
}
nav[role="tab-control"] > button:not(.outline) {
button[role="tab"]:not(.outline) {
pointer-events: none;
cursor: default;
}
@@ -37,8 +71,10 @@
margin: 1.5rem 0 1.5rem 0;
}
div.cust-element > blockquote {
margin-top: 0;
div.cust-element > blockquote, div.cust-element > blockquote > ul {
margin: 0;
font-size: calc(var(--font-size) * 0.8);
color: var(--muted-color);
}
div#download_btn_grid > a {
@@ -49,57 +85,64 @@
<body>
<div class="hero">
<nav class="container-fluid"
style="position: fixed; top: 0; z-index: 99; -webkit-backdrop-filter: blur(20px); backdrop-filter: blur(20px);">
<nav class="container-fluid">
<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>
<li><a href="#readme" class="secondary">Read Me</a></li>
<li><a href="#download" class="secondary">Download</a></li>
<li><a href="#config" class="secondary">Config</a></li>
<li><a href="https://github.com/KazushiMe/Switch-OC-Suite" class="contrast">
<svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 496 512" height="16px">
<path fill="currentColor"
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">
</path>
</svg>
</a></li>
<ul id="nav-popup">
<li>
<details role="list" dir="rtl">
<summary aria-haspopup="group" role="link" class="contrast">Jump to</summary>
<ul role="group">
<li><a href="#readme">README</a></li>
<li><a href="#download">Download</a></li>
<li><a href="#config">Configurator</a></li>
<li><a href="https://github.com/KazushiMe/Switch-OC-Suite" target="_blank">GitHub repository</a></li>
</ul>
</details>
</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>
</nav>
<header class="container" style="padding-top: 6rem;">
<header class="container">
<hgroup>
<h1>Switch OC Suite</h1>
<h2>Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. Licensed under GPL v2.</h2>
</hgroup>
</header>
</div>
<section aria-label="Read Me" id="readme">
<section aria-label="README" id="readme">
<div class="container">
<article>
<header>
<hgroup>
<h2>Read Me</h2>
<h2>README</h2>
<h3>🚨DISCLAIMER: THIS IS PROVIDED AS IS. USE AT YOUR OWN RISK!🚨</h3>
</hgroup>
<li>
Overclocking in general will shorten the lifespan of some
hardware components. <strong>YOU ARE RESPONSIBLE for any problem or
potential damage</strong> if unsafe frequencies are ENABLED in
sys-clk-OC. Issues like asking for bypassing limit will BE IGNORED OR
CLOSED WITHOUT REPLY.
</li>
<li>
Due to HorizonOS design, instabilities from unsafe RAM clocks may
cause filesystem corruption. <strong>Always make backup before enabling
DRAM OC.</strong>
</li>
<ul>
<li>
Overclocking in general will shorten the lifespan of some
hardware components. <strong>YOU ARE RESPONSIBLE for any problem or
potential damage</strong> if unsafe frequencies are ENABLED in
sys-clk-OC. Issues like asking for bypassing limit will BE IGNORED OR
CLOSED WITHOUT REPLY.
</li>
<li>
Due to HorizonOS design, instabilities from unsafe RAM clocks may
cause filesystem corruption. <strong>Always make backup before enabling
DRAM OC.</strong>
</li>
</ul>
</header>
<body>
<h3>Features</h3>
<details>
<details open>
<summary>For Erista variant (HAC-001)</summary>
<ul>
<li>CPU Overclock (Safe: 1785 MHz)
@@ -119,7 +162,7 @@
<li>DRAM Overclock (Safe: 1862.4 MHz)</li>
</ul>
</details>
<details>
<details open>
<summary>For Mariko variant (HAC-001-01, HDH-001, HEG-001)</summary>
<ul>
<li>CPU / GPU Overclock (Safe: 1963 / 998 MHz)
@@ -139,7 +182,7 @@
<li>DRAM Overclock (Safe: 1996.8 MHz)</li>
</ul>
</details>
<details>
<details open>
<summary>Modded sys-clk and ReverseNX-RT</summary>
<ul>
<li>Auto CPU Boost
@@ -179,67 +222,64 @@
</li>
</ul>
</details>
<details>
<details open>
<summary>System Settings (Optional)</summary>
See <a
href="https://github.com/KazushiMe/Switch-OC-Suite/blob/master/system_settings.md">system_settings.md</a>
</details>
<h3 id="installation">Installation</h3>
<h3 id="installation" open>Installation</h3>
<ol type="1">
<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>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>
<li>Customization via <a href="#config">online loader configurator</a>
<details>
<summary>Default config</summary>
<table role="grid">
<thead>
<tr>
<th>Defaults</th>
<th>Mariko</th>
<th>Erista</th>
</tr>
</thead>
<tbody>
<tr>
<td>CPU OC</td>
<td>2397 MHz Max</td>
<td>2091 MHz Max</td>
</tr>
<tr>
<td>CPU Boost</td>
<td>1785 MHz</td>
<td>N/A</td>
</tr>
<tr>
<td>CPU Volt</td>
<td>1235 mV Max</td>
<td>1235 mV Max</td>
</tr>
<tr>
<td>GPU OC</td>
<td>1305 MHz Max</td>
<td>N/A</td>
</tr>
<tr>
<td>RAM OC</td>
<td>1996 MHz Max</td>
<td>1862 MHz Max</td>
</tr>
<tr>
<td>RAM Volt</td>
<td>Disabled</td>
<td>Disabled</td>
</tr>
<tr>
<td>RAM Timing</td>
<td>Auto-Adjusted</td>
<td>Disabled</td>
</tr>
</tbody>
</table>
</details>
<table role="grid">
<thead>
<tr>
<th>Defaults</th>
<th>Mariko</th>
<th>Erista</th>
</tr>
</thead>
<tbody>
<tr>
<td>CPU OC</td>
<td>2397 MHz Max</td>
<td>2091 MHz Max</td>
</tr>
<tr>
<td>CPU Boost</td>
<td>1785 MHz</td>
<td>N/A</td>
</tr>
<tr>
<td>CPU Volt</td>
<td>1235 mV Max</td>
<td>1235 mV Max</td>
</tr>
<tr>
<td>GPU OC</td>
<td>1305 MHz Max</td>
<td>N/A</td>
</tr>
<tr>
<td>RAM OC</td>
<td>1996 MHz Max</td>
<td>1862 MHz Max</td>
</tr>
<tr>
<td>RAM Volt</td>
<td>Disabled</td>
<td>Disabled</td>
</tr>
<tr>
<td>RAM Timing</td>
<td>Auto-Adjusted</td>
<td>Disabled</td>
</tr>
</tbody>
</table>
</li>
<li>Hekate-ipl bootloader Only
<ul>
@@ -248,35 +288,6 @@
</ul>
</li>
</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>
<summary>How to build this project</summary>
<ol type="1">
@@ -291,29 +302,33 @@
</ol>
</details>
<h3 id="faq">Frequently Asked Questions</h3>
<details>
<details open>
<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>
<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
<code>/config/sys-clk/config.ini</code>
</li>
<ul>
<li>Above all else, you should know <a href="#readme">what "unsafe" means and issues might arise</a>.</li>
<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>. Place this line <code>allow_unsafe_freq=1</code> under
<code>[value]</code> section in
<code>/config/sys-clk/config.ini</code>
</li>
</ul>
</details>
<details>
<details open>
<summary>I would like to bypass limit enforced in sys-clk to improve handheld performance without charger
connected.</summary>
<li>Never will it be implemented here, or work out of the box.</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>
<li>Never will it be implemented here, or work out of the box.</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>
</body>
<footer>
<details role="list">
<summary aria-haspopup="listbox" role="button" class="secondary">
<summary aria-haspopup="group" role="button" class="secondary">
Acknowledgement
</summary>
<ul role="listbox">
<ul role="group">
<li><a href="https://github.com/CTCaer/hekate" target="_blank">CTCaer for Hekate-ipl bootloader, RE and
hardware research</a></li>
<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>
</hgroup>
</header>
<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">
<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">
<input id="file" type="file">
<small id="cust_rev">Upload loader.kip here</small>
</label>
<div id="config-list-basic"></div>
<div id="config-list-advanced"></div>
</form>
</body>
<footer>
<div class="grid">
<button id="load_default" role="button" disabled>Load Default</button>
<button id="load_saved" role="button" disabled>Load Last Saved</button>
<button id="save" role="button" disabled>Save</button>
<button type="button" id="load_default" role="button" disabled>Load Default</button>
<button type="button" id="load_saved" role="button" disabled>Load Last Saved</button>
<button type="submit" id="save" role="button" disabled>Save</button>
</div>
</footer>
</article>
</div>
</section>
<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>
</body>
<script type="text/javascript" src="./main.js"></script>

View File

@@ -1,6 +1,5 @@
/* Config: Cust */
const CUST_REV = 3;
var buffer: ArrayBuffer;
enum CustPlatform {
Undefined = 0,
@@ -10,50 +9,39 @@ enum CustPlatform {
};
class CustEntry {
id: string;
name: string;
platform: CustPlatform;
size: number;
desc: string;
defval: number;
min: number;
max: number;
step: number; // also as quotient
zeroable: boolean;
value?: 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) {
this.id = id;
this.name = name;
this.platform = platform;
this.size = size;
this.desc = desc;
this.defval = defval;
constructor(
public id: string,
public name: string,
public platform: CustPlatform,
public size: number,
public desc: string[],
public defval: number,
minmax: [number, number] = [0, 1_000_000],
public step: number = 1,
public zeroable: boolean = true) {
this.min = minmax[0];
this.max = minmax[1];
this.step = step;
this.zeroable = zeroable;
};
validate(): boolean {
let tip = new ErrorToolTip(this.id);
tip.clear();
let tip = new ErrorToolTip(this.id).clear();
if (Number.isNaN(this.value) || this.value === undefined) {
tip.setMsg(`Invalid value: Not a number`);
tip.show();
tip.setMsg(`Invalid value: Not a number`).show();
return false;
}
if (this.zeroable && this.value == 0)
return true;
if (this.value < this.min || this.value > this.max) {
tip.setMsg(`Expected range: [${this.min}, ${this.max}], got ${this.value}.`);
tip.show();
tip.setMsg(`Expected range: [${this.min}, ${this.max}], got ${this.value}.`).show();
return false;
}
if (this.value % this.step != 0) {
tip.setMsg(`${this.value} % ${this.step} ≠ 0`);
tip.show();
tip.setMsg(`${this.value} % ${this.step} ≠ 0`).show();
return false;
}
return true;
@@ -64,7 +52,7 @@ class CustEntry {
}
updateValueFromElement() {
this.value = Number(this.getInputElement()!.value);
this.value = Number(this.getInputElement()?.value);
}
isAvailableFor(platform: CustPlatform): boolean {
@@ -92,20 +80,23 @@ class CustEntry {
// Description in blockquote style
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);
grid.appendChild(desc);
document.getElementById("form")!.appendChild(grid);
document.getElementById("config-list-basic")!.appendChild(grid);
let tooltip = new ErrorToolTip(this.id);
tooltip.addChangeListener();
new ErrorToolTip(this.id).addChangeListener();
}
input.value = String(this.value);
}
setElementValue() {
this.getInputElement()!.value = String(this.value!);
}
setElementDefaultValue() {
(document.getElementById(this.id) as HTMLInputElement).value = String(this.defval);
this.getInputElement()!.value = String(this.defval);
}
removeElement() {
@@ -136,9 +127,9 @@ var CustTable: Array<CustEntry> = [
"DRAM Timing",
CustPlatform.Mariko,
2,
"<li><b>0</b>: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density. (Default)</li>\
<li><b>1</b>: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.</li>\
<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>0</b>: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density. (Default)",
"<b>1</b>: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.",
"<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, 2],
1
@@ -148,8 +139,8 @@ var CustTable: Array<CustEntry> = [
"Mariko CPU Max Clock in kHz",
CustPlatform.Mariko,
4,
"<li>System default: 1785000</li>\
<li>2397000 might be unreachable for some SoCs.</li>",
["System default: 1785000",
"2397000 might be unreachable for some SoCs."],
2397_000,
[1785_000, 3000_000],
1,
@@ -159,9 +150,9 @@ var CustTable: Array<CustEntry> = [
"Mariko CPU Boost Clock in kHz",
CustPlatform.Mariko,
4,
"<li>System default: 1785000</li>\
<li>Boost clock will be applied when applications request higher CPU frequency for quicker loading.</li>\
<li>This will be set regardless of whether sys-clk is enabled.</li>",
["System default: 1785000",
"Boost clock will be applied when applications request higher CPU frequency for quicker loading.",
"This will be set regardless of whether sys-clk is enabled."],
1785_000,
[1020_000, 3000_000],
1,
@@ -172,8 +163,8 @@ var CustTable: Array<CustEntry> = [
"Mariko CPU Max Voltage in mV",
CustPlatform.Mariko,
4,
"<li>System default: 1120</li>\
<li>Acceptable range: 1100 ≤ x ≤ 1300</li>",
["System default: 1120",
"Acceptable range: 1100 ≤ x ≤ 1300"],
1235,
[1100, 1300],
5
@@ -183,9 +174,9 @@ var CustTable: Array<CustEntry> = [
"Mariko GPU Max Clock in kHz",
CustPlatform.Mariko,
4,
"<li>System default: 921600</li>\
<li>Tegra X1+ official maximum: 1267200</li>\
<li>1305600 might be unreachable for some SoCs.</li>",
["System default: 921600",
"Tegra X1+ official maximum: 1267200",
"1305600 might be unreachable for some SoCs."],
1305_600,
[768_000, 1536_000],
100,
@@ -195,8 +186,8 @@ var CustTable: Array<CustEntry> = [
"Mariko RAM Max Clock in kHz",
CustPlatform.Mariko,
4,
"<li>Values should be ≥ 1600000, and divided evenly by 3200.</li>\
<li><b>WARNING:</b> RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM</li>",
["Values should be ≥ 1600000, and divided evenly by 3200.",
"<b>WARNING:</b> RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM."],
1996_800,
[1600_000, 2400_000],
3200,
@@ -206,11 +197,11 @@ var CustTable: Array<CustEntry> = [
"EMC Vddq (Mariko Only) Voltage in uV",
CustPlatform.Mariko,
4,
"<li>Acceptable range: 550000 ≤ x ≤ 650000</li>\
<li>Value should be divided evenly by 5000</li>\
<li>Default: 600000</li>\
<li>Not enabled by default.</li>\
<li>This will not work without sys-clk-OC.</li>",
["Acceptable range: 550000 ≤ x ≤ 650000",
"Value should be divided evenly by 5000",
"Default: 600000",
"Not enabled by default.",
"This will not work without sys-clk-OC."],
0,
[550_000, 650_000],
5000,
@@ -220,7 +211,7 @@ var CustTable: Array<CustEntry> = [
"Erista CPU Max Voltage in mV",
CustPlatform.Erista,
4,
"<li>Acceptable range: 1100 ≤ x ≤ 1300</li>",
["Acceptable range: 1100 ≤ x ≤ 1300"],
1235,
[1100, 1300],
1,
@@ -230,8 +221,8 @@ var CustTable: Array<CustEntry> = [
"Erista RAM Max Clock in kHz",
CustPlatform.Erista,
4,
"<li>Values should be ≥ 1600000, and divided evenly by 3200.</li>\
<li><b>WARNING:</b> RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM</li>",
["Values should be ≥ 1600000, and divided evenly by 3200.",
"<b>WARNING:</b> RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM"],
1862_400,
[1600_000, 2400_000],
3200,
@@ -241,314 +232,363 @@ var CustTable: Array<CustEntry> = [
"EMC Vddq (Erista Only) & RAM Vdd2 Voltage in uV",
CustPlatform.All,
4,
"<li>Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.</li>\
<li>Erista Default (HOS): 1125000 (bootloader: 1100000)</li>\
<li>Mariko Default: 1100000 (It will not work without sys-clk-OC)</li>\
<li>Not enabled by default</li>",
["Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.",
"Erista Default (HOS): 1125000 (bootloader: 1100000)",
"Mariko Default: 1100000 (It will not work without sys-clk-OC)",
"Not enabled by default"],
0,
[1100_000, 1250_000],
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 {
id: string;
message: string | null;
element: HTMLElement | null;
constructor(id: string, msg?: string) {
constructor(public id: string, public msg?: string) {
this.id = id;
this.element = document.getElementById(id);
if (msg) { this.setMsg(msg); }
};
setMsg(msg: string) {
this.message = msg;
setMsg(msg: string): ErrorToolTip {
this.msg = msg;
return this;
}
show() {
if (this.element) {
this.element.setAttribute("aria-invalid", "true");
if (this.message) {
this.element.setAttribute("title", this.message);
this.element.parentElement!.setAttribute("data-tooltip", this.message);
this.element.parentElement!.setAttribute("data-placement", "top");
}
show(): ErrorToolTip {
this.element?.setAttribute("aria-invalid", "true");
if (this.msg) {
this.element?.setAttribute("title", this.msg);
this.element?.parentElement?.setAttribute("data-tooltip", this.msg);
this.element?.parentElement?.setAttribute("data-placement", "top");
}
return this;
};
clear() {
if (this.element) {
this.element.removeAttribute("aria-invalid");
this.element.removeAttribute("title");
this.element.parentElement!.removeAttribute("data-tooltip");
this.element.parentElement!.removeAttribute("data-placement");
}
clear(): ErrorToolTip {
this.element?.removeAttribute("aria-invalid");
this.element?.removeAttribute("title");
this.element?.parentElement?.removeAttribute("data-tooltip");
this.element?.parentElement?.removeAttribute("data-placement");
return this;
}
addChangeListener() {
if (this.element) {
this.element.addEventListener('change', (_evt) => {
let obj = CustTable.filter((obj) => { return obj.id === this.id; })[0];
obj.value = Number((this.element as HTMLInputElement).value);
obj.validate();
});
}
this.element?.addEventListener('change', (_evt) => {
let obj = CustTable.filter((obj) => { return obj.id === this.id; })[0];
obj.value = Number((this.element as HTMLInputElement).value);
obj.validate();
});
}
};
function SaveCust(buffer) {
let view = new DataView(buffer);
let storage = {};
class CustStorage {
storage: { [key: string]: (number | undefined) } = {};
readonly key = "last_saved";
CustTable.forEach(i => {
i.updateValueFromElement();
if (!i.validate()) {
document.getElementById(i.id)!.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 = "";
updateFromTable() {
CustTable.forEach(i => {
i.updateValueFromElement();
if (!i.validate()) {
i.getInputElement()?.focus();
throw new Error(`Invalid ${i.name}`);
}
}
}
});
/* 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;
}
const resultFromSuite = await responseFromSuite.json();
const latestVerFromSuite = resultFromSuite.tag_name;
const amsVer = latestVerFromSuite.split(".").slice(0, 3).join(".");
const loaderKip = resultFromSuite.assets.filter((obj) => {
return obj.name.endsWith("loader.kip");
})[0];
const sdOut = resultFromSuite.assets.filter((obj) => {
return obj.name.endsWith(".zip");
})[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);
let dict = JSON.parse(s);
let keys = CustTable.map(i => i.id);
let ignoredKeys: string[] = Object.keys(dict).filter(k => !keys.includes(k));
if (ignoredKeys.length) {
console.log(`Ignored: ${ignoredKeys}`);
}
Object.keys(dict)
.filter(k => keys.includes(k))
.forEach(k => this.storage[k] = dict[k]);
return this.storage;
}
}
const isElementVisible = (element: HTMLElement): boolean => {
let rect = 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)
);
};
class Cust {
buffer: ArrayBuffer;
view: DataView;
beginOffset: number;
storage: CustStorage = new CustStorage();
readonly magic = 0x54535543; // "CUST"
readonly magicLen = 4;
async function updateDownloadUrls() {
// Wait until download buttons are visible
while (!isElementVisible(document.getElementById("download_btn_grid")!)) {
await new Promise(r => setTimeout(r, 1000));
mapper : {[size: number]: { get: any, set: any }} = {
2: {
get: (offset: number) => this.view.getUint16(offset, true),
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)!;
element.innerHTML = name;
element.removeAttribute("aria-busy");
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);
}
}
const fileInput = document.getElementById("file") as HTMLInputElement;
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 updateDownloadUrls();
});
await new DownloadSection().load();
});