Introduce redesigned project homepage; Fix #64 #65

- loaderConfigurator now renamed to pages, with configurator section supporting both v2 and v3 Cust
This commit is contained in:
KazushiM
2023-01-25 18:58:43 +08:00
parent 120367cf7c
commit f33a59370a
23 changed files with 1810 additions and 4952 deletions

View File

@@ -38,16 +38,16 @@ jobs:
uses: devatherock/minify-js@v1.0.3
with:
# File to minify or a folder containing files to minify. By default, all files in current folder and its subfolders will be minified
directory: './Source/loaderConfigurator/dist' # optional
directory: './pages/dist' # optional
# Path where the minified files will be saved. By default, the minified files will be saved in the original file path
output: './Source/loaderConfigurator/dist_min' # optional
output: './pages/dist_min' # optional
# Indicates if the output files should have the suffix '.min' added after the name. Default is true
add_suffix: false # optional
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
# Upload entire repository
path: './Source/loaderConfigurator/dist_min'
path: './pages/dist_min'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1

View File

@@ -4,6 +4,7 @@
Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW.
[Project Homepage](https://kazushime.github.io/Switch-OC-Suite)
**DISCLAIMER: USE AT YOUR OWN RISK!**
@@ -15,20 +16,13 @@ Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW.
- Erista variant (HAC-001)
- CPU Overclock (Safe: 1785 MHz)
<details><summary>Unsafe</summary>
- Due to the limit of board power draw or power IC
- Unlockable frequencies up to 2091 MHz
- See [README for sys-clk-OC](https://github.com/KazushiMe/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md)
</details>
- Unsafe
- Due to the limit of board power draw or power IC
- Unlockable frequencies up to 2091 MHz
- See [README for sys-clk-OC](https://github.com/KazushiMe/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md)
- DRAM Overclock (Safe: 1862.4 MHz)
<details><summary>Unsafe</summary>
- Up to 2131 MHz with DRAM bus overvolting depending on your DRAM chip
</details>
- Unsafe: Up to 2131 MHz with DRAM bus overvolting depending on your DRAM chip
- Modded sys-clk and ReverseNX-RT
- CPU & GPU frequency governor (Experimental)
@@ -38,20 +32,12 @@ Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW.
- Mariko variant (HAC-001-01, HDH-001, HEG-001)
- CPU / GPU Overclock (Safe: 1963 / 998 MHz)
<details><summary>Unsafe</summary>
- Due to the limit of board power draw or power IC
- Unlockable frequencies up to 2397 / 1305 MHz or 2295 / 1267 MHz
- See [README for sys-clk-OC](https://github.com/KazushiMe/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md)
</details>
- Unsafe
- Due to the limit of board power draw or power IC
- Unlockable frequencies up to 2397 / 1305 MHz or 2295 / 1267 MHz
- See [README for sys-clk-OC](https://github.com/KazushiMe/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md)
- DRAM Overclock (Safe: 1996.8 MHz)
<details><summary>Unsafe</summary>
- [DRAM bus overvolting](https://gist.github.com/KazushiMe/6bb0fcbefe0e03b1274079522516d56d).
</details>
- Modded sys-clk and ReverseNX-RT
- Auto CPU Boost
@@ -99,7 +85,7 @@ Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW.
| CPU Volt | 1235 mV Max | 1257 mV Max |
| GPU OC | 1305 MHz Max | N/A |
| RAM OC | 1996 MHz Max | 1862 MHz Max |
| RAM Volt | N/A | Disabled |
| RAM Volt | Disabled | Disabled |
| RAM Timing | Auto-Adjusted | Disabled |
</details>
@@ -151,5 +137,5 @@ When compilation is done, uncompress the kip to make it work with configurator:
- RetroNX team for [sys-clk](https://github.com/retronx-team/sys-clk)
- SciresM and Reswitched Team for the state-of-the-art [Atmosphere](https://github.com/Atmosphere-NX/Atmosphere) CFW of Switch
- Switchbrew [wiki](http://switchbrew.org/wiki/) for Switch in-depth info
- Switchroot for their [modified L4T kernel and device tree]((https://gitlab.com/switchroot/kernel))
- Switchroot for their [modified L4T kernel and device tree](https://gitlab.com/switchroot/kernel)
- ZatchyCatGames for RE and original OC loader patches for Atmosphere

View File

@@ -71,7 +71,6 @@ volatile CustomizeTable C = {
.marikoEmcVolt = 0,
/* Erista CPU:
* Not tested but enabled by default.
* - Max Voltage in mV
*/
.eristaCpuMaxVolt = 1235,

View File

@@ -31,8 +31,8 @@ Result MemFreqPllmLimit(u32* ptr) {
void SafetyCheck() {
if (C.custRev != CUST_REV ||
C.marikoCpuMaxVolt >= 1300 ||
C.eristaCpuMaxVolt >= 1300 ||
C.marikoCpuMaxVolt > 1300 ||
C.eristaCpuMaxVolt > 1300 ||
(C.eristaEmcVolt && (C.eristaEmcVolt < 600'000 || C.eristaEmcVolt > 1250'000)) ||
(C.marikoEmcVolt && (C.marikoEmcVolt < 600'000 || C.marikoEmcVolt > 650'000)))
{

View File

@@ -31,7 +31,7 @@ file_replace_str(ldr_process_creation,
namespace ams::ldr {""",
"""#include "ldr_ro_manager.hpp"
#include "oc/oc_suite.hpp"
#include "oc/oc_loader.hpp"
namespace ams::ldr {"""),
(""" NsoHeader g_nso_headers[Nso_Count];

View File

@@ -1,2 +0,0 @@
node_modules/
package-lock.json

View File

@@ -1,22 +0,0 @@
<!DOCTYPE html>
<html class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="./output.css" rel="stylesheet">
</head>
<body class="bg-slate-200 dark:bg-slate-900">
<div class="flex justify-center">
<div id="form" class="form-group p-6 rounded-lg shadow-lg max-w-md my-10 bg-slate-100 dark:bg-slate-800">
<label for="file" class="form-entry">Upload loader.kip</label>
<a href="https://github.com/KazushiMe/Switch-OC-Suite/releases/latest" target="_blank" class="link-primary">Get latest here</a>
<input id="file" type="file" class="form-control form-entry">
<div class="btn">
<button id="load" class="hide btn btn-secondary">Load Default</button>
<button id="save" class="hide btn btn-primary">Save</button>
</div>
</div>
</div>
</body>
<script type="text/javascript" src="./main.js"></script>
</html>

View File

@@ -1,234 +0,0 @@
const CUST_REV = 2;
var buffer;
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");
}
function CustEntry(name, size, desc, defval, minmax = [0, 1000000], step = 1, extra_validator) {
this.name = name;
this.size = size;
this.desc = desc;
this.defval = defval;
this.min = minmax[0];
this.max = minmax[1];
this.step = step;
this.validator = extra_validator;
this.value = null;
this.offset = null;
}
function InitCustTable() {
let cust = [
new CustEntry("mtcConf", 2, "<b>DRAM Timing</b>\
<li><b>0</b>: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density.</li>\
<li><b>1</b>: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.</li>", 0, [0, 3]),
new CustEntry("marikoCpuMaxClock", 4, "<b>Mariko CPU Max Clock in kHz</b>\
<li>System default: 1785000</li>\
<li>≥ 2397000 will enable overvolting (> 1120 mV)</li>", 2397000, [1785000, 3000000], 100, (x) => (x % 100) == 0),
new CustEntry("marikoCpuBoostClock", 4, "<b>Mariko CPU Boost Clock in kHz</b>\
<li>System default: 1785000</li>\
<li>Must not be higher than marikoCpuMaxClock</li>", 1785000, [1785000, 3000000], 100, (x) => (x % 100) == 0),
new CustEntry("marikoCpuMaxVolt", 4, "<b>Mariko CPU Max Voltage in mV</b>\
<li>System default: 1120</li>\
<li>Acceptable range: 1100 ≤ x ≤ 1300</li>", 1235, [1100, 1300]),
new CustEntry("marikoGpuMaxClock", 4, "<b>Mariko GPU Max Clock in kHz</b>\
<li>System default: 921600</li>\
<li>Tegra X1+ official maximum: 1267200</li>", 1305600, [768000, 1536000], 100, (x) => (x % 100) == 0),
new CustEntry("marikoEmcMaxClock", 4, "<b>Mariko RAM Max Clock in kHz</b>\
<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>", 1996800, [1612800, 2400000], 3200, (x) => (x % 3200) == 0),
new CustEntry("eristaCpuOCEnable", 4, "<b>Erista CPU Enable Overclock</b>\
<li>Not tested</li>", 1, [0, 1]),
new CustEntry("eristaCpuMaxVolt", 4, "<b>Erista CPU Max Voltage in mV</b>\
<li>Acceptable range: 1100 ≤ x ≤ 1400</li>", 1257, [0, 1400], 100, (x) => x >= 1100),
new CustEntry("eristaEmcMaxClock", 4, "<b>Erista RAM Max Clock in kHz</b>\
<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>", 1862400, [1600000, 2400000], 3200, (x) => (x % 3200) == 0),
new CustEntry("eristaEmcVolt", 4, "<b>Erista RAM Voltage in uV</b>\
<li>Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.</li>\
<li>Not enabled by default</li>", 0, [0, 1250000], 12500, (x) => (x % 12500) == 0 && x >= 1100000),
];
return cust;
}
function ValidateCust(cust) {
for (let i of cust) {
if (i.value == 0)
continue;
if (i.value < i.min || i.value > i.max) {
document.getElementById(i.name).focus();
throw new Error(`Expected range: ${i.min}${i.name}${i.max}, got ${i.value}`);
}
if (i.validator && !i.validator(i.value)) {
document.getElementById(i.name).focus();
throw new Error(`Invalid value: ${i.value}(${i.name})\nValidator: ${i.validator}`);
}
}
}
function SaveCust(cust, buffer) {
let dict = Object.assign({}, ...cust.map((x) => ({ [x.name]: x })));
let view = new DataView(buffer);
let storage = {};
for (let i of cust) {
let id = i.name;
i.value = document.getElementById(id).value;
storage[i.name] = i.value;
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.name).focus();
throw new Error("Unknown size at " + i);
}
}
ValidateCust(cust);
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 LoadLastSaved() {
if (LastSaved()) {
let storage = localStorage.getItem("last_saved");
let sObj = JSON.parse(storage);
for (let key in sObj) {
if (key == "custRev") {
continue;
}
document.getElementById(key).value = sObj[key];
}
}
}
function LoadDefault(cust) {
let dict = Object.assign({}, ...cust.map((x) => ({ [x.name]: x })));
for (let i of cust) {
let id = i.name;
document.getElementById(id).value = i.defval;
}
}
function UpdateHTMLForm(cust) {
let dict = Object.assign({}, ...cust.map((x) => ({ [x.name]: x })));
let form = document.getElementById("form");
for (let i of cust) {
let id = i.name;
let input = document.getElementById(id);
if (!input) {
let div = document.createElement("div");
div.classList.add("form-floating", "my-3");
let label = document.createElement("label");
label.setAttribute("for", id);
label.innerHTML = id;
label.classList.add("form-entry");
input = document.createElement("input");
input.classList.add("form-control", "form-entry");
input.min = dict[i.name].min;
input.max = dict[i.name].max;
input.id = id;
input.type = "number";
input.step = dict[i.name].step;
div.appendChild(input);
div.appendChild(label);
let desc = dict[i.name].desc;
if (desc) {
let tip = document.createElement("small");
tip.innerHTML = desc;
tip.classList.add("form-entry");
tip.setAttribute("for", id);
div.appendChild(tip);
}
form === null || form === void 0 ? void 0 : form.appendChild(div);
}
input.value = dict[i.name].value;
}
let btn = document.getElementById("load");
btn.classList.remove("hide");
if (LastSaved()) {
btn.innerHTML = "Load Last Saved";
btn.addEventListener('click', () => {
LoadLastSaved();
});
}
else {
btn.addEventListener('click', () => {
LoadDefault(cust);
});
}
btn = document.getElementById("save");
btn.classList.remove("hide");
btn.addEventListener('click', () => {
try {
SaveCust(cust, buffer);
}
catch (e) {
console.log(e);
alert(e);
}
});
}
function ParseCust(magicOffset, buffer) {
let view = new DataView(buffer);
let cust = InitCustTable();
let offset = magicOffset + 4;
let rev = view.getUint16(offset, true);
if (rev != CUST_REV) {
throw new Error("Unsupported custRev, expected: " + CUST_REV + ", got " + rev);
}
offset += 2;
for (let i of cust) {
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.name).focus();
throw new Error("Unknown size at " + i);
}
offset += i.size;
}
ValidateCust(cust);
UpdateHTMLForm(cust);
}
const fileInput = document.getElementById("file");
fileInput.addEventListener('change', (event) => {
let reader = new FileReader();
reader.readAsArrayBuffer(event.target.files[0]);
reader.onloadend = (progEvent) => {
if (progEvent.target.readyState === FileReader.DONE) {
try {
buffer = progEvent.target.result;
let offset = FindMagicOffset(buffer);
ParseCust(offset, buffer);
}
catch (e) {
console.log(e);
alert(e);
fileInput.value = "";
}
}
};
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,173 +0,0 @@
#!python3
cust_conf = {
# DRAM Timing:
# 0: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density (Default).
# 1: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.
# 2: ENTIRE_TABLE_ERISTA:
# 3: ENTIRE_TABLE_MARIKO: Replace the entire max mtc table with customized one (provided by user).
"mtcConf": 0,
# Mariko CPU:
# - Max Clock in kHz:
# Default: 1785000
# >= 2193000 will enable overvolting (> 1120 mV)
# - Boost Clock in kHz:
# Default: 1785000
# Boost clock will be applied when applications request higher CPU frequency for quicker loading.
# - Max Voltage in mV:
# Default voltage: 1120
# Haven't tested anything higher than 1220.
"marikoCpuMaxClock": 2397000,
"marikoCpuBoostClock": 1785000,
"marikoCpuMaxVolt": 1220,
# Mariko GPU:
# - Max Clock in kHz:
# Default: 921600
# NVIDIA Maximum: 1267200
"marikoGpuMaxClock": 1305600,
# Mariko EMC:
# - RAM Clock in kHz:
# Values should be > 1600000, and divided evenly by 9600.
# [WARNING]
# RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM:
# - Graphical glitches
# - System instabilities
# - NAND corruption
# Timings from auto-adjustment have been tested safe for up to 1996.8 MHz for all DRAM chips.
"marikoEmcMaxClock": 1996800,
# Erista CPU:
# Not tested and not enabled by default.
# - Enable Overclock
# Require modificaitions towards NewCpuTables!
# - Max Voltage in mV
"eristaCpuOCEnable": 0,
"eristaCpuMaxVolt": 0,
# Erista EMC:
# - RAM Clock in kHz
# [WARNING]
# RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM:
# - Graphical glitches
# - System instabilities
# - NAND corruption
# - RAM Voltage in uV
# Range: 600'000 to 1250'000 uV
# Value should be divided evenly by 12'500
# Default(HOS): 1125'000
# Not enabled by default.
"eristaEmcMaxClock": 1862400,
"eristaEmcVolt": 0
}
cust_range = {
"mtcConf": (0, 3),
"marikoCpuMaxClock": (1785000, 3000000),
"marikoCpuBoostClock": (1785000, 3000000),
"marikoCpuMaxVolt": (1100, 1300),
"marikoGpuMaxClock": (768000, 1536000),
"marikoEmcMaxClock": (1612800, 2400000),
"eristaCpuMaxVolt": (1100, 1400),
"eristaEmcMaxClock": (1600000, 2400000),
"eristaEmcVolt": (1100000, 1250000)
}
import struct
import argparse
cust_rev = 2
cust_head = ["cust", "custRev"]
cust_body = ["mtcConf",
"marikoCpuMaxClock", "marikoCpuBoostClock", "marikoCpuMaxVolt", "marikoGpuMaxClock", "marikoEmcMaxClock",
"eristaCpuOCEnable", "eristaCpuMaxVolt", "eristaEmcMaxClock", "eristaEmcVolt"]
cust_key = [*cust_head, *cust_body]
cust_val_num = len(cust_conf) - 1
cust_fmt = '<4s2H' + str(cust_val_num) + 'I'
cust_head_fmt = '<4s1H'
cust_body_fmt = '<1H' + str(cust_val_num) + 'I'
parser = argparse.ArgumentParser(description='Loader Configurator v'+str(cust_rev))
parser.add_argument("file", help="Path of loader.kip")
parser.add_argument("--save", "-s", action="store_true", help="Save configuration to loader.kip")
parser.add_argument("--ignore", action="store_true", help="Ignore range safety check")
args = parser.parse_args()
def KIPCustParse(file_loc, conf_print=True) -> (int, dict):
with open(file_loc, "rb") as file:
header_str = b'KIP1Loader'
header = file.read(len(header_str))
file.seek(0)
cust_magic = b'CUST'
cust_pos = file.read().find(cust_magic)
if header != header_str or cust_pos == -1:
raise Exception("\n Invalid kip file!")
file.seek(cust_pos)
cust_size = struct.calcsize(cust_fmt)
cust_buf = file.read(cust_size)
cust_val = struct.unpack(cust_fmt, cust_buf)
cust_dict = dict(zip(cust_key, cust_val))
if cust_dict['custRev'] != cust_rev:
raise Exception(f"\n custRev does NOT match, expected: {cust_rev}, got: {cust_dict['custRev']}!")
[cust_dict.pop(key) for key in cust_head]
if conf_print:
print("Configuration from file")
[print(f"- {i:20s} : {cust_dict[i]:8d}") for i in cust_dict]
return (cust_pos, cust_dict)
def CustRangeCheck(cust):
range_error_str = ""
for i in cust_range:
val = int(cust[i])
if val and (val < cust_range[i][0] or val > cust_range[i][1]) :
range_error_str += f"\n- {i:20s} = {val:8d}, Expected range: {[*cust_range[i]]}"
if range_error_str:
raise ValueError(range_error_str)
def KIPCustSave(file_loc, cust_pos, cust_dict, range_check=True, cust_to_save={}):
missing = set(cust_body) - set(cust_to_save.keys())
if missing:
missing_str = "\n Invalid cust! Missing: "
for i in missing:
missing_str += f"\n- {i}"
raise Exception(missing_str)
if range_check:
CustRangeCheck(cust_to_save)
diff_count = 0
for i in cust_body:
diff_str = ""
if cust_dict[i] != cust_conf[i]:
diff_str = f"-> {cust_conf[i]:8d}"
diff_count += 1
print(f"- {i:20s} : {cust_dict[i]:8d} {diff_str}")
if not diff_count:
print("Cust is identical, abort saving!")
return
with open(file_loc, "rb+") as file:
cust_bin = struct.pack(cust_body_fmt, *[cust_to_save[i] for i in cust_body])
file.seek(cust_pos + struct.calcsize(cust_head_fmt))
file.write(cust_bin)
print("Done!")
def main(file_loc, ignore=False, save=False):
(cust_pos, cust_dict) = KIPCustParse(file_loc, conf_print=(not save))
if save:
print("Saving new configuration...")
KIPCustSave(file_loc, cust_pos, cust_dict, range_check=(not ignore), cust_to_save=cust_conf)
if __name__ == "__main__":
main(args.file, args.ignore, args.save)

View File

@@ -1,8 +0,0 @@
{
"devDependencies": {
"tailwindcss": "^3.2.2"
},
"dependencies": {
"tw-elements": "^1.0.0-alpha12"
}
}

View File

@@ -1,22 +0,0 @@
<!DOCTYPE html>
<html class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="./output.css" rel="stylesheet">
</head>
<body class="bg-slate-200 dark:bg-slate-900">
<div class="flex justify-center">
<div id="form" class="form-group p-6 rounded-lg shadow-lg max-w-md my-10 bg-slate-100 dark:bg-slate-800">
<label for="file" class="form-entry">Upload loader.kip</label>
<a href="https://github.com/KazushiMe/Switch-OC-Suite/releases/latest" target="_blank" class="link-primary">Get latest here</a>
<input id="file" type="file" class="form-control form-entry">
<div class="btn">
<button id="load" class="hide btn btn-secondary">Load Default</button>
<button id="save" class="hide btn btn-primary">Save</button>
</div>
</div>
</div>
</body>
<script type="text/javascript" src="./main.js"></script>
</html>

View File

@@ -1,320 +0,0 @@
const CUST_REV = 2;
var buffer: string | ArrayBuffer;
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");
}
function CustEntry(name: string, size: number, desc: string, defval: number, minmax = [0, 1_000_000], step = 1, extra_validator?) {
this.name = name;
this.size = size;
this.desc = desc;
this.defval = defval;
this.min = minmax[0];
this.max = minmax[1];
this.step = step;
this.validator = extra_validator;
this.value = null;
this.offset = null;
}
function InitCustTable() {
let cust = [
new CustEntry(
"mtcConf",
2,
"<b>DRAM Timing</b>\
<li><b>0</b>: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density.</li>\
<li><b>1</b>: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.</li>",
0,
[0, 3],
),
new CustEntry(
"marikoCpuMaxClock",
4,
"<b>Mariko CPU Max Clock in kHz</b>\
<li>System default: 1785000</li>\
<li>≥ 2397000 will enable overvolting (> 1120 mV)</li>",
2397_000,
[1785_000, 3000_000],
100,
(x: number) => (x % 100) == 0
),
new CustEntry(
"marikoCpuBoostClock",
4,
"<b>Mariko CPU Boost Clock in kHz</b>\
<li>System default: 1785000</li>\
<li>Must not be higher than marikoCpuMaxClock</li>",
1785_000,
[1785_000, 3000_000],
100,
(x: number) => (x % 100) == 0
),
new CustEntry(
"marikoCpuMaxVolt",
4,
"<b>Mariko CPU Max Voltage in mV</b>\
<li>System default: 1120</li>\
<li>Acceptable range: 1100 ≤ x ≤ 1300</li>",
1235,
[1100, 1300],
),
new CustEntry(
"marikoGpuMaxClock",
4,
"<b>Mariko GPU Max Clock in kHz</b>\
<li>System default: 921600</li>\
<li>Tegra X1+ official maximum: 1267200</li>",
1305_600,
[768_000, 1536_000],
100,
(x: number) => (x % 100) == 0
),
new CustEntry(
"marikoEmcMaxClock",
4,
"<b>Mariko RAM Max Clock in kHz</b>\
<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>",
1996_800,
[1612_800, 2400_000],
3200,
(x: number) => (x % 3200) == 0
),
new CustEntry(
"eristaCpuOCEnable",
4,
"<b>Erista CPU Enable Overclock</b>\
<li>Not tested</li>",
1,
[0, 1]
),
new CustEntry(
"eristaCpuMaxVolt",
4,
"<b>Erista CPU Max Voltage in mV</b>\
<li>Acceptable range: 1100 ≤ x ≤ 1400</li>",
1257,
[0, 1400],
100,
(x: number) => x >= 1100
),
new CustEntry(
"eristaEmcMaxClock",
4,
"<b>Erista RAM Max Clock in kHz</b>\
<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>",
1862_400,
[1600_000, 2400_000],
3200,
(x: number) => (x % 3200) == 0
),
new CustEntry(
"eristaEmcVolt",
4,
"<b>Erista RAM Voltage in uV</b>\
<li>Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.</li>\
<li>Not enabled by default</li>",
0,
[0, 1250_000],
12500,
(x: number) => (x % 12500) == 0 && x >= 1100000
),
];
return cust;
}
function ValidateCust(cust) {
for (let i of cust) {
if (i.value == 0)
continue;
if (i.value < i.min || i.value > i.max) {
document.getElementById(i.name)!.focus();
throw new Error(`Expected range: ${i.min}${i.name}${i.max}, got ${i.value}`);
}
if (i.validator && !i.validator(i.value)) {
document.getElementById(i.name)!.focus();
throw new Error(`Invalid value: ${i.value}(${i.name})\nValidator: ${i.validator}`);
}
}
}
function SaveCust(cust, buffer) {
let dict = Object.assign({}, ...cust.map((x: { name: any; }) => ({[x.name]: x})));
let view = new DataView(buffer);
let storage = {};
for (let i of cust) {
let id = i.name;
i.value = (document.getElementById(id) as HTMLInputElement).value;
storage[i.name] = i.value;
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.name)!.focus();
throw new Error("Unknown size at " + i);
}
}
ValidateCust(cust);
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 LoadLastSaved() {
if (LastSaved()) {
let storage = localStorage.getItem("last_saved");
let sObj = JSON.parse(storage!);
for (let key in sObj) {
if (key == "custRev") {
continue;
}
(document.getElementById(key) as HTMLInputElement).value = sObj[key];
}
}
}
function LoadDefault(cust: any[]) {
let dict = Object.assign({}, ...cust.map((x: { name: any; }) => ({[x.name]: x})));
for (let i of cust) {
let id = i.name;
(document.getElementById(id) as HTMLInputElement).value = i.defval;
}
}
function UpdateHTMLForm(cust: any[]) {
let dict = Object.assign({}, ...cust.map((x: { name: any; }) => ({[x.name]: x})));
let form = document.getElementById("form");
for (let i of cust) {
let id = i.name;
let input = document.getElementById(id) as HTMLInputElement;
if (!input) {
let div = document.createElement("div");
div.classList.add("form-floating", "my-3");
let label = document.createElement("label");
label.setAttribute("for", id);
label.innerHTML = id;
label.classList.add("form-entry");
input = document.createElement("input");
input.classList.add("form-control", "form-entry");
input.min = dict[i.name].min;
input.max = dict[i.name].max;
input.id = id;
input.type = "number";
input.step = dict[i.name].step;
div.appendChild(input);
div.appendChild(label);
let desc = dict[i.name].desc;
if (desc) {
let tip = document.createElement("small");
tip.innerHTML = desc;
tip.classList.add("form-entry");
tip.setAttribute("for", id);
div.appendChild(tip);
}
form?.appendChild(div);
}
input.value = dict[i.name].value;
}
let btn = document.getElementById("load")!;
btn.classList.remove("hide");
if (LastSaved()) {
btn.innerHTML = "Load Last Saved";
btn.addEventListener('click', () => {
LoadLastSaved();
});
} else {
btn.addEventListener('click', () => {
LoadDefault(cust);
});
}
btn = document.getElementById("save")!;
btn.classList.remove("hide");
btn.addEventListener('click', () => {
try {
SaveCust(cust, buffer);
} catch (e) {
console.log(e);
alert(e);
}
});
}
function ParseCust(magicOffset, buffer) {
let view = new DataView(buffer);
let cust = InitCustTable();
let offset = magicOffset + 4;
let rev = view.getUint16(offset, true);
if (rev != CUST_REV) {
throw new Error("Unsupported custRev, expected: " + CUST_REV + ", got " + rev);
}
offset += 2;
for (let i of cust) {
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.name)!.focus();
throw new Error("Unknown size at " + i);
}
offset += i.size;
}
ValidateCust(cust);
UpdateHTMLForm(cust);
}
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!;
try {
let offset = FindMagicOffset(buffer);
ParseCust(offset, buffer);
} catch (e) {
console.log(e);
alert(e);
fileInput.value = "";
}
}
}
})

View File

@@ -1,39 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
label.form-entry {
@apply inline-block mb-2 text-slate-700 dark:text-slate-200;
}
input.form-entry {
@apply block w-full px-3 py-1.5 text-base font-normal text-slate-600 dark:text-slate-300 bg-slate-100 dark:bg-slate-900 bg-clip-padding border border-solid border-slate-300 dark:border-gray-600 rounded transition ease-in-out m-0 focus:text-slate-700 dark:focus:text-slate-200 focus:bg-slate-100 dark:focus:bg-slate-800 focus:border-blue-600 dark:focus:border-blue-300 focus:outline-none;
}
small.form-entry {
@apply block mt-1 text-xs text-slate-600 dark:text-slate-400 px-3 py-1.5;
}
a.link-primary {
@apply text-blue-500 hover:text-blue-600 active:text-blue-700 transition duration-300 ease-in-out mb-4;
}
div.btn {
@apply grid grid-cols-2 gap-x-6 mt-3;
}
button.btn {
@apply inline-block px-6 py-2.5 text-slate-100 font-medium text-xs leading-tight uppercase rounded shadow-md hover:shadow-lg focus:shadow-lg focus:outline-none focus:ring-0 active:shadow-lg transition duration-150 ease-in-out;
}
button.btn-primary {
@apply bg-blue-600 hover:bg-blue-700 focus:bg-blue-700 active:bg-blue-900;
}
button.btn-secondary {
@apply bg-slate-600 hover:bg-slate-700 focus:bg-slate-700 active:bg-slate-900;
}
.hide {
display: none !important;
}

View File

@@ -1,10 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./dist/*.html', './dist/*.js', './node_modules/tw-elements/dist/js/**/*.js'],
theme: {
extend: {},
},
plugins: [
require('tw-elements/dist/plugin')
],
}

View File

@@ -10,6 +10,10 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "types.h"
#include "../config.h"
#include "../clocks.h"
@@ -40,3 +44,7 @@ static inline Result sysclkIpcRemoveOverride(SysClkModule module)
{
return sysclkIpcSetOverride(module, 0);
}
#ifdef __cplusplus
}
#endif

View File

@@ -22,13 +22,5 @@
#include "ipc.h"
#if defined(__SWITCH__) && defined(__cplusplus)
extern "C" {
#endif
#include <sysclk.h>
#include <sysclk/client/ipc.h>
#if defined(__SWITCH__) && defined(__cplusplus)
}
#endif

View File

@@ -10,14 +10,5 @@
#pragma once
#if defined(__cplusplus)
extern "C"
{
#endif
#include <sysclk.h>
#include <sysclk/client/ipc.h>
#if defined(__cplusplus)
}
#endif

View File

@@ -2,5 +2,5 @@
[ -d "./dist" ] || mkdir ./dist
cp -Rf ./src/*.html ./dist/
# README_HTML=`pandoc -f gfm -t html5 ../README.md`
tsc ./src/main.ts --outDir ./dist/ -lib es2015,dom -t es2015
npx tailwindcss -i ./src/style.css -o ./dist/output.css

382
pages/dist/index.html vendored Normal file
View File

@@ -0,0 +1,382 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Switch OC Suite</title>
<meta name="description"
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">
</head>
<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);">
<ul>
<li><a href="#head" class="contrast">Switch OC Suite</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>
</nav>
<header class="container" style="padding-top: 6rem;">
<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">
<div class="container">
<article>
<header>
<hgroup>
<h2>Read Me</h2>
<h3>DISCLAIMER: 🚨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>
</header>
<body>
<h3>Features</h3>
<details>
<summary>For Erista variant (HAC-001)</summary>
<ul>
<li>CPU Overclock (Safe: 1785 MHz)
<ul>
<li>Unsafe
<ul>
<li>Due to the limit of board power draw or power IC</li>
<li>Unlockable frequencies up to 2091 MHz</li>
<li>See <a
href="https://github.com/KazushiMe/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md"
target="_blank">README
for sys-clk-OC</a></li>
</ul>
</li>
</ul>
</li>
<li>DRAM Overclock (Safe: 1862.4 MHz)
<ul>
<li>Unsafe: Up to 2131 MHz with DRAM bus overvolting depending on your
DRAM chip</li>
</ul>
</li>
<li>Modded sys-clk and ReverseNX-RT
<ul>
<li>CPU &amp; GPU frequency governor (Experimental)</li>
<li>Set charging current (100 mA - 2000 mA) and charging limit (20% -
100%)</li>
<li>Global Profile</li>
<li>Sync ReverseNX Mode</li>
</ul>
</li>
</ul>
</details>
<details>
<summary>For Mariko variant (HAC-001-01, HDH-001, HEG-001)</summary>
<ul>
<li>CPU / GPU Overclock (Safe: 1963 / 998 MHz)
<ul>
<li>Unsafe
<ul>
<li>Due to the limit of board power draw or power IC</li>
<li>Unlockable frequencies up to 2397 / 1305 MHz or 2295 / 1267 MHz</li>
<li>See <a
href="https://github.com/KazushiMe/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md"
target="_blank">README
for sys-clk-OC</a></li>
</ul>
</li>
</ul>
</li>
<li>DRAM Overclock (Safe: 1996.8 MHz)</li>
<li>Modded sys-clk and ReverseNX-RT
<ul>
<li>Auto CPU Boost</li>
<li>CPU &amp; GPU frequency governor (Experimental)</li>
<li>Set charging current (100 mA - 2000 mA) and charging limit (20% -
100%)</li>
<li>Global Profile</li>
<li>Sync ReverseNX Mode</li>
</ul>
</li>
</ul>
</details>
<details>
<summary>Auto CPU Boost</summary>
<ul>
<li>For faster game loading</li>
<li>Enable CPU Boost (1785 MHz) when CPU Core#3 (System Core) is
stressed (mainly I/O operations).</li>
<li>Effective only when charger is connected.</li>
</ul>
</details>
<details>
<summary>CPU &amp; GPU frequency governor (Experimental)</summary>
<ul>
<li>Adjust frequency based on load. Might decrease power draw but can
introduce stutters. Can be turned off for specific titles.</li>
</ul>
</details>
<details>
<summary>Setting charge limit (20% - 100%)</summary>
<ul>
<li>Long-term use of charge limit may render the battery gauge
inaccurate. Performing full cycles could help recalibration, or try <a
href="https://github.com/CTCaer/battery_desync_fix_nx">battery_desync_fix_nx</a>.</li>
</ul>
</details>
<details>
<summary>Global profile</summary>
<ul>
<li>Designated a dummy title id <code>0xA111111111111111</code>.</li>
<li>Priority: "Temp overrides" &gt; "Application profile" &gt; "Global
profile" &gt; "System default".</li>
</ul>
</details>
<details>
<summary>Sync ReverseNX Mode</summary>
<ul>
<li>No need to change clocks manually after toggling modes in ReverseNX
(-RT and -Tool)</li>
</ul>
</details>
<details>
<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>
<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>1257 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>
</li>
<li>Hekate-ipl bootloader Only
<ul>
<li>Add <code>kip1=atmosphere/kips/loader.kip</code> to boot entry
section in <code>bootloader/hekate_ipl.ini</code>.</li>
</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">
<li>Grab necessary patches from the repo, then compile sys-clk, ReverseNX-RT and Atmosphere loader with
devkitpro.</li>
<li>Before compiling Atmosphere loader, run <code>patch.py</code> in
<code>Atmosphere/stratosphere/loader/source/</code> to insert oc module into loader sysmodule.
</li>
<li>When compilation is done, uncompress the kip to make it work with configurator:
<code>hactool -t kip1 Atmosphere/stratosphere/loader/out/nintendo_nx_arm64_armv8a/release/loader.kip --uncompress=./loader.kip</code>
</li>
</ol>
</details>
<h3 id="faq">Frequently Asked Questions</h3>
<details>
<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>
</details>
<details>
<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>
</details>
</body>
<footer>
<details role="list">
<summary aria-haspopup="listbox" role="button" class="secondary">
Acknowledgement
</summary>
<ul role="listbox">
<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>
</li>
<li><a href="https://github.com/masagrator/ReverseNX-RT" target="_blank">masagrator for ReverseNX-RT and
info
on BatteryChargeInfoFields in psm module</a></li>
<li><a href="https://developer.nvidia.com/embedded/dlc/tegra-x1-technical-reference-manual"
target="_blank">Nvidia for Tegra X1 Technical Reference Manual</a></li>
<li><a href="https://github.com/retronx-team/sys-clk" target="_blank">RetroNX team for sys-clk</a></li>
<li><a href="https://github.com/Atmosphere-NX/Atmosphere" target="_blank">SciresM and Reswitched Team for
the
state-of-the-art Atmosphere CFW of Switch</a></li>
<li><a href="http://switchbrew.org/wiki/" target="_blank">Switchbrew wiki for Switch in-depth info</a>
</li>
<li><a href="https://gitlab.com/switchroot/kernel" target="_blank">Switchroot for their modified L4T
kernel
and device tree</a></li>
<li>ZatchyCatGames for RE and original OC loader patches for Atmosphere</li>
</ul>
</details>
</footer>
</article>
</div>
</section>
<section aria-label="Download" id="download">
<div class="container">
<article>
<header>
<hgroup>
<h2>Download</h2>
<h3>Get latest version of Switch OC Suite and its corresponding Atmosphere package here.</h3>
</hgroup>
</header>
<body>
<div class="grid">
<a role="button" aria-busy="true" id="loader_kip_btn">Generating link, please wait...</a>
<a role="button" aria-busy="true" id="sdout_zip_btn">Generating link, please wait...</a>
<a target="_blank" role="button" aria-busy="true" id="ams_btn">Generating link, please wait...</a>
</div>
</body>
<footer>
See <a href="#installation">Installation section</a> for how to use Switch OC Suite.
</footer>
</article>
</div>
</section>
<section aria-label="Config" id="config">
<div class="container">
<article>
<header>
<hgroup>
<h2>Configurator</h2>
<h3>Configure frequencies and voltages to suit your hardware and yourself.</h3>
</hgroup>
</header>
<form id="form">
<label for="file">File loader.kip
<input id="file" type="file">
<small id="cust_rev"></small>
</label>
</form>
<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>
</div>
</footer>
</article>
</div>
</section>
<footer class="container">
<small>Build with <a href="https://picocss.com" target="_blank">Pico</a></small>
</footer>
</body>
<script type="text/javascript" src="./main.js"></script>
</html>

414
pages/dist/main.js vendored Normal file
View File

@@ -0,0 +1,414 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
/* Config: Cust */
var CUST_REV;
var buffer;
class CustEntry {
constructor(name, size, desc, defval, minmax = [0, 1000000], step = 1, extra_validator) {
this.name = name;
this.size = size;
this.desc = desc;
this.defval = defval;
this.min = minmax[0];
this.max = minmax[1];
this.step = step;
this.validator = extra_validator;
this.value = null;
this.offset = null;
}
;
}
var CustTable = null;
const CustTableV2 = [
new CustEntry("mtcConf", 2, "<b>DRAM Timing</b>\
<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>", 0, [0, 3]),
new CustEntry("marikoCpuMaxClock", 4, "<b>Mariko CPU Max Clock in kHz</b>\
<li>System default: 1785000</li>\
<li>2397000 might be unreachable for some SoCs.</li>", 2397000, [1785000, 3000000], 100, (x) => (x % 100) == 0),
new CustEntry("marikoCpuBoostClock", 4, "<b>Mariko CPU Boost Clock in kHz</b>\
<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>", 1785000, [1785000, 3000000], 100, (x) => (x % 100) == 0),
new CustEntry("marikoCpuMaxVolt", 4, "<b>Mariko CPU Max Voltage in mV</b>\
<li>System default: 1120</li>\
<li>Acceptable range: 1100 ≤ x ≤ 1300</li>", 1235, [1100, 1300]),
new CustEntry("marikoGpuMaxClock", 4, "<b>Mariko GPU Max Clock in kHz</b>\
<li>System default: 921600</li>\
<li>Tegra X1+ official maximum: 1267200</li>\
<li>1305600 might be unreachable for some SoCs.</li>", 1305600, [768000, 1536000], 100, (x) => (x % 100) == 0),
new CustEntry("marikoEmcMaxClock", 4, "<b>Mariko RAM Max Clock in kHz</b>\
<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>", 1996800, [1612800, 2400000], 3200, (x) => (x % 3200) == 0),
new CustEntry("eristaOCEnable", 4, "<b>Erista CPU Enable Overclock</b>\
<li>Not tested</li>", 1, [0, 1]),
new CustEntry("eristaCpuMaxVolt", 4, "<b>Erista CPU Max Voltage in mV</b>\
<li>Acceptable range: 1100 ≤ x ≤ 1300</li>", 1235, [0, 1300], 1, (x) => x >= 1100),
new CustEntry("eristaEmcMaxClock", 4, "<b>Erista RAM Max Clock in kHz</b>\
<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>", 1862400, [1600000, 2400000], 3200, (x) => (x % 3200) == 0),
new CustEntry("eristaEmcVolt", 4, "<b>Erista RAM Voltage in uV</b>\
<li>Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.</li>\
<li>Not enabled by default</li>", 0, [0, 1250000], 12500, (x) => (x % 12500) == 0 && x >= 1100000),
];
const CustTableV3 = [
new CustEntry("mtcConf", 2, "<b>DRAM Timing</b>\
<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.", 0, [0, 3]),
new CustEntry("marikoCpuMaxClock", 4, "<b>Mariko CPU Max Clock in kHz</b>\
<li>System default: 1785000</li>\
<li>2397000 might be unreachable for some SoCs.</li>", 2397000, [1785000, 3000000], 100, (x) => (x % 100) == 0),
new CustEntry("marikoCpuBoostClock", 4, "<b>Mariko CPU Boost Clock in kHz</b>\
<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>", 1785000, [1785000, 3000000], 100, (x) => (x % 100) == 0),
new CustEntry("marikoCpuMaxVolt", 4, "<b>Mariko CPU Max Voltage in mV</b>\
<li>System default: 1120</li>\
<li>Acceptable range: 1100 ≤ x ≤ 1300</li>", 1235, [1100, 1300]),
new CustEntry("marikoGpuMaxClock", 4, "<b>Mariko GPU Max Clock in kHz</b>\
<li>System default: 921600</li>\
<li>Tegra X1+ official maximum: 1267200</li>\
<li>1305600 might be unreachable for some SoCs.</li>", 1305600, [768000, 1536000], 100, (x) => (x % 100) == 0),
new CustEntry("marikoEmcMaxClock", 4, "<b>Mariko RAM Max Clock in kHz</b>\
<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>", 1996800, [1612800, 2400000], 3200, (x) => (x % 3200) == 0),
new CustEntry("marikoEmcVolt", 4, "<b>Mariko RAM Voltage in uV</b>\
<li>Acceptable range: 600000 ≤ x ≤ 650000</li>\
<li>Value should be divided evenly by 5'000</li>\
<li>Not enabled by default.</li>\
<li>This will not work without sys-clk-OC.</li>", 1, [0, 1]),
new CustEntry("eristaCpuMaxVolt", 4, "<b>Erista CPU Max Voltage in mV</b>\
<li>Acceptable range: 1100 ≤ x ≤ 1300</li>", 1235, [0, 1300], 1, (x) => x >= 1100),
new CustEntry("eristaEmcMaxClock", 4, "<b>Erista RAM Max Clock in kHz</b>\
<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>", 1862400, [1600000, 2400000], 3200, (x) => (x % 3200) == 0),
new CustEntry("eristaEmcVolt", 4, "<b>Erista RAM Voltage in uV</b>\
<li>Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.</li>\
<li>Not enabled by default</li>", 0, [0, 1250000], 12500, (x) => (x % 12500) == 0 && x >= 1100000),
];
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 {
constructor(id) {
this.id = id;
this.element = document.getElementById(id);
}
;
setMsg(msg) {
this.message = msg;
}
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");
}
}
}
;
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");
}
}
addChangeListener() {
if (this.element) {
this.element.addEventListener('change', (_evt) => {
let obj = CustTable.filter((obj) => { return obj.name === this.id; })[0];
obj.value = Number(this.element.value);
ValidateCustEntry(obj);
});
}
}
}
;
function ValidateCustEntry(entry) {
let elementId = entry.name;
let tip = new ErrorToolTip(elementId);
tip.clear();
if (entry.value == 0)
return null;
if (entry.value < entry.min || entry.value > entry.max) {
tip.setMsg(`Expected range: [${entry.min}, ${entry.max}], got ${entry.value}.`);
tip.show();
return tip;
}
if (entry.validator && !entry.validator(entry.value)) {
tip.setMsg(`Invalid value: ${entry.value}. Did not pass this validator: ${entry.validator}.`);
tip.show();
return tip;
}
return null;
}
function ValidateCust() {
let tooltips = [];
for (let i of CustTable) {
let tip = ValidateCustEntry(i);
if (tip) {
tooltips.push(tip);
}
}
if (tooltips.length > 0) {
throw new Error("Invalid cust");
}
}
function SaveCust(buffer) {
let view = new DataView(buffer);
let storage = {};
storage["custRev"] = CUST_REV;
for (let i of CustTable) {
let id = i.name;
i.value = Number(document.getElementById(id).value);
storage[i.name] = i.value;
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.name).focus();
throw new Error("Unknown size at " + i);
}
}
ValidateCust();
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 LoadLastSaved() {
if (LastSaved()) {
let storage = localStorage.getItem("last_saved");
let sObj = JSON.parse(storage);
for (let key in sObj) {
if (key == "custRev") {
continue;
}
document.getElementById(key).value = sObj[key];
}
}
}
function LoadDefault() {
for (let i of CustTable) {
let id = i.name;
document.getElementById(id).value = String(i.defval);
}
}
function ClearHTMLForm() {
var _a, _b;
if (!CustTable) {
return;
}
for (let i of CustTable) {
let id = i.name;
let input = document.getElementById(id);
(_b = (_a = input.parentElement) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.remove();
}
}
function UpdateHTMLForm() {
let dict = Object.assign({}, ...CustTable.map((x) => ({ [x.name]: x })));
let form = document.getElementById("form");
for (let i of CustTable) {
let id = i.name;
let input = document.getElementById(id);
if (!input) {
let grid = document.createElement("div");
grid.classList.add("grid");
// Label and input box
input = document.createElement("input");
input.min = dict[i.name].min;
input.max = dict[i.name].max;
input.id = id;
input.type = "number";
input.step = dict[i.name].step;
let label = document.createElement("label");
label.setAttribute("for", id);
label.innerHTML = id;
label.appendChild(input);
grid.appendChild(label);
// Description in blockquote style
let desc = dict[i.name].desc;
let block = document.createElement("blockquote");
block.style["margin-top"] = "0";
block.innerHTML = desc;
block.setAttribute("for", id);
grid.appendChild(block);
grid.style["margin-top"] = "3rem";
form.appendChild(grid);
let tooltip = new ErrorToolTip(id);
tooltip.addChangeListener();
}
input.value = dict[i.name].value;
}
let default_btn = document.getElementById("load_default");
default_btn.removeAttribute("disabled");
default_btn.addEventListener('click', () => {
LoadDefault();
});
if (LastSaved()) {
let last_btn = document.getElementById("load_saved");
last_btn.removeAttribute("disabled");
last_btn.addEventListener('click', () => {
LoadLastSaved();
});
}
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, buffer) {
let view = new DataView(buffer);
let offset = magicOffset + 4;
let rev = view.getUint16(offset, true);
if (rev != 2 && rev != 3) {
throw new Error("Unsupported custRev, expected: 2 or 3, got " + rev);
}
CUST_REV = rev;
document.getElementById("cust_rev").innerHTML = `Cust V${CUST_REV} is loaded.`;
if (rev == 2) {
CustTable = CustTableV2;
}
else {
CustTable = CustTableV3;
}
offset += 2;
for (let i of CustTable) {
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.name).focus();
throw new Error("Unknown size at " + i);
}
offset += i.size;
}
}
const fileInput = document.getElementById("file");
fileInput.addEventListener('change', (event) => {
let reader = new FileReader();
reader.readAsArrayBuffer(event.target.files[0]);
reader.onloadend = (progEvent) => {
if (progEvent.target.readyState === FileReader.DONE) {
buffer = progEvent.target.result;
try {
let offset = FindMagicOffset(buffer);
ClearHTMLForm();
ParseCust(offset, buffer);
ValidateCust();
UpdateHTMLForm();
}
catch (e) {
console.log(e);
alert(e);
fileInput.value = "";
}
}
};
});
function fetchRelease() {
return __awaiter(this, void 0, void 0, function* () {
try {
const responseFromSuite = yield 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}`);
}
const resultFromSuite = yield responseFromSuite.json();
const latestVerFromSuite = resultFromSuite.tag_name;
const correspondingVerFromAMS = 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/${correspondingVerFromAMS}`;
let info = {
OCSuiteVer: latestVerFromSuite,
LoaderKipUrl: loaderKip.browser_download_url,
SdOutZipUrl: sdOut.browser_download_url,
AMSVer: correspondingVerFromAMS,
AMSUrl: amsReleaseUrl
};
return info;
}
catch (e) {
console.log(e);
alert(e);
}
});
}
function updateDownloadUrls() {
return __awaiter(this, void 0, void 0, function* () {
const updateHref = (id, name, url) => {
let element = document.getElementById(id);
element.innerHTML = name;
element.removeAttribute("aria-busy");
element.setAttribute("href", url);
};
let info = yield fetchRelease();
if (info) {
const loaderKipName = `loader.kip ${info.OCSuiteVer}`;
updateHref("loader_kip_btn", loaderKipName, info.LoaderKipUrl);
const sdOutName = `SdOut.zip ${info.OCSuiteVer}`;
updateHref("sdout_zip_btn", sdOutName, info.SdOutZipUrl);
const amsName = `Atmosphere-NX ${info.AMSVer}`;
updateHref("ams_btn", amsName, info.AMSUrl);
}
});
}
addEventListener('DOMContentLoaded', (_evt) => __awaiter(this, void 0, void 0, function* () {
yield updateDownloadUrls();
}));

382
pages/src/index.html Normal file
View File

@@ -0,0 +1,382 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Switch OC Suite</title>
<meta name="description"
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">
</head>
<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);">
<ul>
<li><a href="#head" class="contrast">Switch OC Suite</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>
</nav>
<header class="container" style="padding-top: 6rem;">
<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">
<div class="container">
<article>
<header>
<hgroup>
<h2>Read Me</h2>
<h3>DISCLAIMER: 🚨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>
</header>
<body>
<h3>Features</h3>
<details>
<summary>For Erista variant (HAC-001)</summary>
<ul>
<li>CPU Overclock (Safe: 1785 MHz)
<ul>
<li>Unsafe
<ul>
<li>Due to the limit of board power draw or power IC</li>
<li>Unlockable frequencies up to 2091 MHz</li>
<li>See <a
href="https://github.com/KazushiMe/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md"
target="_blank">README
for sys-clk-OC</a></li>
</ul>
</li>
</ul>
</li>
<li>DRAM Overclock (Safe: 1862.4 MHz)
<ul>
<li>Unsafe: Up to 2131 MHz with DRAM bus overvolting depending on your
DRAM chip</li>
</ul>
</li>
<li>Modded sys-clk and ReverseNX-RT
<ul>
<li>CPU &amp; GPU frequency governor (Experimental)</li>
<li>Set charging current (100 mA - 2000 mA) and charging limit (20% -
100%)</li>
<li>Global Profile</li>
<li>Sync ReverseNX Mode</li>
</ul>
</li>
</ul>
</details>
<details>
<summary>For Mariko variant (HAC-001-01, HDH-001, HEG-001)</summary>
<ul>
<li>CPU / GPU Overclock (Safe: 1963 / 998 MHz)
<ul>
<li>Unsafe
<ul>
<li>Due to the limit of board power draw or power IC</li>
<li>Unlockable frequencies up to 2397 / 1305 MHz or 2295 / 1267 MHz</li>
<li>See <a
href="https://github.com/KazushiMe/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md"
target="_blank">README
for sys-clk-OC</a></li>
</ul>
</li>
</ul>
</li>
<li>DRAM Overclock (Safe: 1996.8 MHz)</li>
<li>Modded sys-clk and ReverseNX-RT
<ul>
<li>Auto CPU Boost</li>
<li>CPU &amp; GPU frequency governor (Experimental)</li>
<li>Set charging current (100 mA - 2000 mA) and charging limit (20% -
100%)</li>
<li>Global Profile</li>
<li>Sync ReverseNX Mode</li>
</ul>
</li>
</ul>
</details>
<details>
<summary>Auto CPU Boost</summary>
<ul>
<li>For faster game loading</li>
<li>Enable CPU Boost (1785 MHz) when CPU Core#3 (System Core) is
stressed (mainly I/O operations).</li>
<li>Effective only when charger is connected.</li>
</ul>
</details>
<details>
<summary>CPU &amp; GPU frequency governor (Experimental)</summary>
<ul>
<li>Adjust frequency based on load. Might decrease power draw but can
introduce stutters. Can be turned off for specific titles.</li>
</ul>
</details>
<details>
<summary>Setting charge limit (20% - 100%)</summary>
<ul>
<li>Long-term use of charge limit may render the battery gauge
inaccurate. Performing full cycles could help recalibration, or try <a
href="https://github.com/CTCaer/battery_desync_fix_nx">battery_desync_fix_nx</a>.</li>
</ul>
</details>
<details>
<summary>Global profile</summary>
<ul>
<li>Designated a dummy title id <code>0xA111111111111111</code>.</li>
<li>Priority: "Temp overrides" &gt; "Application profile" &gt; "Global
profile" &gt; "System default".</li>
</ul>
</details>
<details>
<summary>Sync ReverseNX Mode</summary>
<ul>
<li>No need to change clocks manually after toggling modes in ReverseNX
(-RT and -Tool)</li>
</ul>
</details>
<details>
<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>
<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>1257 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>
</li>
<li>Hekate-ipl bootloader Only
<ul>
<li>Add <code>kip1=atmosphere/kips/loader.kip</code> to boot entry
section in <code>bootloader/hekate_ipl.ini</code>.</li>
</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">
<li>Grab necessary patches from the repo, then compile sys-clk, ReverseNX-RT and Atmosphere loader with
devkitpro.</li>
<li>Before compiling Atmosphere loader, run <code>patch.py</code> in
<code>Atmosphere/stratosphere/loader/source/</code> to insert oc module into loader sysmodule.
</li>
<li>When compilation is done, uncompress the kip to make it work with configurator:
<code>hactool -t kip1 Atmosphere/stratosphere/loader/out/nintendo_nx_arm64_armv8a/release/loader.kip --uncompress=./loader.kip</code>
</li>
</ol>
</details>
<h3 id="faq">Frequently Asked Questions</h3>
<details>
<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>
</details>
<details>
<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>
</details>
</body>
<footer>
<details role="list">
<summary aria-haspopup="listbox" role="button" class="secondary">
Acknowledgement
</summary>
<ul role="listbox">
<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>
</li>
<li><a href="https://github.com/masagrator/ReverseNX-RT" target="_blank">masagrator for ReverseNX-RT and
info
on BatteryChargeInfoFields in psm module</a></li>
<li><a href="https://developer.nvidia.com/embedded/dlc/tegra-x1-technical-reference-manual"
target="_blank">Nvidia for Tegra X1 Technical Reference Manual</a></li>
<li><a href="https://github.com/retronx-team/sys-clk" target="_blank">RetroNX team for sys-clk</a></li>
<li><a href="https://github.com/Atmosphere-NX/Atmosphere" target="_blank">SciresM and Reswitched Team for
the
state-of-the-art Atmosphere CFW of Switch</a></li>
<li><a href="http://switchbrew.org/wiki/" target="_blank">Switchbrew wiki for Switch in-depth info</a>
</li>
<li><a href="https://gitlab.com/switchroot/kernel" target="_blank">Switchroot for their modified L4T
kernel
and device tree</a></li>
<li>ZatchyCatGames for RE and original OC loader patches for Atmosphere</li>
</ul>
</details>
</footer>
</article>
</div>
</section>
<section aria-label="Download" id="download">
<div class="container">
<article>
<header>
<hgroup>
<h2>Download</h2>
<h3>Get latest version of Switch OC Suite and its corresponding Atmosphere package here.</h3>
</hgroup>
</header>
<body>
<div class="grid">
<a role="button" aria-busy="true" id="loader_kip_btn">Generating link, please wait...</a>
<a role="button" aria-busy="true" id="sdout_zip_btn">Generating link, please wait...</a>
<a target="_blank" role="button" aria-busy="true" id="ams_btn">Generating link, please wait...</a>
</div>
</body>
<footer>
See <a href="#installation">Installation section</a> for how to use Switch OC Suite.
</footer>
</article>
</div>
</section>
<section aria-label="Config" id="config">
<div class="container">
<article>
<header>
<hgroup>
<h2>Configurator</h2>
<h3>Configure frequencies and voltages to suit your hardware and yourself.</h3>
</hgroup>
</header>
<form id="form">
<label for="file">File loader.kip
<input id="file" type="file">
<small id="cust_rev"></small>
</label>
</form>
<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>
</div>
</footer>
</article>
</div>
</section>
<footer class="container">
<small>Build with <a href="https://picocss.com" target="_blank">Pico</a></small>
</footer>
</body>
<script type="text/javascript" src="./main.js"></script>
</html>

605
pages/src/main.ts Normal file
View File

@@ -0,0 +1,605 @@
/* Config: Cust */
var CUST_REV: number | null;
var buffer: string | ArrayBuffer;
class CustEntry {
name: string;
size: number;
desc: string;
defval: number;
min: number;
max: number;
step: number;
validator: Function | null;
value: number | null;
offset: number | null;
constructor(name: string, size: number, desc: string, defval: number, minmax = [0, 1_000_000], step = 1, extra_validator?) {
this.name = name;
this.size = size;
this.desc = desc;
this.defval = defval;
this.min = minmax[0];
this.max = minmax[1];
this.step = step;
this.validator = extra_validator;
this.value = null;
this.offset = null;
};
}
var CustTable: Array<CustEntry> | null = null;
const CustTableV2: Array<CustEntry> = [
new CustEntry(
"mtcConf",
2,
"<b>DRAM Timing</b>\
<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>",
0,
[0, 3],
),
new CustEntry(
"marikoCpuMaxClock",
4,
"<b>Mariko CPU Max Clock in kHz</b>\
<li>System default: 1785000</li>\
<li>2397000 might be unreachable for some SoCs.</li>",
2397_000,
[1785_000, 3000_000],
100,
(x: number) => (x % 100) == 0
),
new CustEntry(
"marikoCpuBoostClock",
4,
"<b>Mariko CPU Boost Clock in kHz</b>\
<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>",
1785_000,
[1785_000, 3000_000],
100,
(x: number) => (x % 100) == 0
),
new CustEntry(
"marikoCpuMaxVolt",
4,
"<b>Mariko CPU Max Voltage in mV</b>\
<li>System default: 1120</li>\
<li>Acceptable range: 1100 ≤ x ≤ 1300</li>",
1235,
[1100, 1300],
),
new CustEntry(
"marikoGpuMaxClock",
4,
"<b>Mariko GPU Max Clock in kHz</b>\
<li>System default: 921600</li>\
<li>Tegra X1+ official maximum: 1267200</li>\
<li>1305600 might be unreachable for some SoCs.</li>",
1305_600,
[768_000, 1536_000],
100,
(x: number) => (x % 100) == 0
),
new CustEntry(
"marikoEmcMaxClock",
4,
"<b>Mariko RAM Max Clock in kHz</b>\
<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>",
1996_800,
[1612_800, 2400_000],
3200,
(x: number) => (x % 3200) == 0
),
new CustEntry(
"eristaOCEnable",
4,
"<b>Erista CPU Enable Overclock</b>\
<li>Not tested</li>",
1,
[0, 1]
),
new CustEntry(
"eristaCpuMaxVolt",
4,
"<b>Erista CPU Max Voltage in mV</b>\
<li>Acceptable range: 1100 ≤ x ≤ 1300</li>",
1235,
[0, 1300],
1,
(x: number) => x >= 1100
),
new CustEntry(
"eristaEmcMaxClock",
4,
"<b>Erista RAM Max Clock in kHz</b>\
<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>",
1862_400,
[1600_000, 2400_000],
3200,
(x: number) => (x % 3200) == 0
),
new CustEntry(
"eristaEmcVolt",
4,
"<b>Erista RAM Voltage in uV</b>\
<li>Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.</li>\
<li>Not enabled by default</li>",
0,
[0, 1250_000],
12500,
(x: number) => (x % 12500) == 0 && x >= 1100000
),
];
const CustTableV3: Array<CustEntry> = [
new CustEntry(
"mtcConf",
2,
"<b>DRAM Timing</b>\
<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.",
0,
[0, 3],
),
new CustEntry(
"marikoCpuMaxClock",
4,
"<b>Mariko CPU Max Clock in kHz</b>\
<li>System default: 1785000</li>\
<li>2397000 might be unreachable for some SoCs.</li>",
2397_000,
[1785_000, 3000_000],
100,
(x: number) => (x % 100) == 0
),
new CustEntry(
"marikoCpuBoostClock",
4,
"<b>Mariko CPU Boost Clock in kHz</b>\
<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>",
1785_000,
[1785_000, 3000_000],
100,
(x: number) => (x % 100) == 0
),
new CustEntry(
"marikoCpuMaxVolt",
4,
"<b>Mariko CPU Max Voltage in mV</b>\
<li>System default: 1120</li>\
<li>Acceptable range: 1100 ≤ x ≤ 1300</li>",
1235,
[1100, 1300],
),
new CustEntry(
"marikoGpuMaxClock",
4,
"<b>Mariko GPU Max Clock in kHz</b>\
<li>System default: 921600</li>\
<li>Tegra X1+ official maximum: 1267200</li>\
<li>1305600 might be unreachable for some SoCs.</li>",
1305_600,
[768_000, 1536_000],
100,
(x: number) => (x % 100) == 0
),
new CustEntry(
"marikoEmcMaxClock",
4,
"<b>Mariko RAM Max Clock in kHz</b>\
<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>",
1996_800,
[1612_800, 2400_000],
3200,
(x: number) => (x % 3200) == 0
),
new CustEntry(
"marikoEmcVolt",
4,
"<b>Mariko RAM Voltage in uV</b>\
<li>Acceptable range: 600000 ≤ x ≤ 650000</li>\
<li>Value should be divided evenly by 5'000</li>\
<li>Not enabled by default.</li>\
<li>This will not work without sys-clk-OC.</li>",
1,
[0, 1]
),
new CustEntry(
"eristaCpuMaxVolt",
4,
"<b>Erista CPU Max Voltage in mV</b>\
<li>Acceptable range: 1100 ≤ x ≤ 1300</li>",
1235,
[0, 1300],
1,
(x: number) => x >= 1100
),
new CustEntry(
"eristaEmcMaxClock",
4,
"<b>Erista RAM Max Clock in kHz</b>\
<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>",
1862_400,
[1600_000, 2400_000],
3200,
(x: number) => (x % 3200) == 0
),
new CustEntry(
"eristaEmcVolt",
4,
"<b>Erista RAM Voltage in uV</b>\
<li>Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.</li>\
<li>Not enabled by default</li>",
0,
[0, 1250_000],
12500,
(x: number) => (x % 12500) == 0 && x >= 1100000
),
];
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) {
this.id = id;
this.element = document.getElementById(id);
};
setMsg(msg: string) {
this.message = msg;
}
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");
}
}
};
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");
}
}
addChangeListener() {
if (this.element) {
this.element.addEventListener('change', (_evt) => {
let obj = CustTable!.filter((obj) => { return obj.name === this.id; })[0];
obj.value = Number((this.element as HTMLInputElement).value);
ValidateCustEntry(obj);
});
}
}
};
function ValidateCustEntry(entry: CustEntry): ErrorToolTip | null {
let elementId = entry.name;
let tip = new ErrorToolTip(elementId);
tip.clear();
if (entry.value! == 0)
return null;
if (entry.value! < entry.min || entry.value! > entry.max) {
tip.setMsg(`Expected range: [${entry.min}, ${entry.max}], got ${entry.value}.`);
tip.show();
return tip;
}
if (entry.validator && !entry.validator(entry.value)) {
tip.setMsg(`Invalid value: ${entry.value}. Did not pass this validator: ${entry.validator}.`);
tip.show();
return tip;
}
return null;
}
function ValidateCust() {
let tooltips: Array<ErrorToolTip> = [];
for (let i of CustTable!) {
let tip = ValidateCustEntry(i);
if (tip) {
tooltips.push(tip);
}
}
if (tooltips.length > 0) {
throw new Error("Invalid cust");
}
}
function SaveCust(buffer) {
let view = new DataView(buffer);
let storage = {};
storage["custRev"] = CUST_REV;
for (let i of CustTable!) {
let id = i.name;
i.value = Number((document.getElementById(id) as HTMLInputElement).value);
storage[i.name] = i.value;
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.name)!.focus();
throw new Error("Unknown size at " + i);
}
}
ValidateCust();
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 LoadLastSaved() {
if (LastSaved()) {
let storage = localStorage.getItem("last_saved");
let sObj = JSON.parse(storage!);
for (let key in sObj) {
if (key == "custRev") {
continue;
}
(document.getElementById(key) as HTMLInputElement).value = sObj[key];
}
}
}
function LoadDefault() {
for (let i of CustTable!) {
let id = i.name;
(document.getElementById(id) as HTMLInputElement).value = String(i.defval);
}
}
function ClearHTMLForm() {
if (!CustTable) {
return;
}
for (let i of CustTable) {
let id = i.name;
let input = document.getElementById(id) as HTMLInputElement;
input.parentElement?.parentElement?.remove();
}
}
function UpdateHTMLForm() {
let dict = Object.assign({}, ...CustTable!.map((x: { name: any; }) => ({ [x.name]: x })));
let form = document.getElementById("form")!;
for (let i of CustTable!) {
let id = i.name;
let input = document.getElementById(id) as HTMLInputElement;
if (!input) {
let grid = document.createElement("div");
grid.classList.add("grid");
// Label and input box
input = document.createElement("input");
input.min = dict[i.name].min;
input.max = dict[i.name].max;
input.id = id;
input.type = "number";
input.step = dict[i.name].step;
let label = document.createElement("label");
label.setAttribute("for", id);
label.innerHTML = id;
label.appendChild(input);
grid.appendChild(label);
// Description in blockquote style
let desc = dict[i.name].desc!;
let block = document.createElement("blockquote");
block.style["margin-top"] = "0";
block.innerHTML = desc;
block.setAttribute("for", id);
grid.appendChild(block);
grid.style["margin-top"] = "3rem";
form.appendChild(grid);
let tooltip = new ErrorToolTip(id);
tooltip.addChangeListener();
}
input.value = dict[i.name].value;
}
let default_btn = document.getElementById("load_default")!;
default_btn.removeAttribute("disabled");
default_btn.addEventListener('click', () => {
LoadDefault();
});
if (LastSaved()) {
let last_btn = document.getElementById("load_saved")!;
last_btn.removeAttribute("disabled");
last_btn.addEventListener('click', () => {
LoadLastSaved();
});
}
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, buffer) {
let view = new DataView(buffer);
let offset = magicOffset + 4;
let rev = view.getUint16(offset, true);
if (rev != 2 && rev != 3) {
throw new Error("Unsupported custRev, expected: 2 or 3, got " + rev);
}
CUST_REV = rev;
document.getElementById("cust_rev")!.innerHTML = `Cust V${CUST_REV} is loaded.`;
if (rev == 2) {
CustTable = CustTableV2;
} else {
CustTable = CustTableV3;
}
offset += 2;
for (let i of CustTable) {
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.name)!.focus();
throw new Error("Unknown size at " + i);
}
offset += i.size;
}
}
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!;
try {
let offset = FindMagicOffset(buffer);
ClearHTMLForm();
ParseCust(offset, buffer);
ValidateCust();
UpdateHTMLForm();
} catch (e) {
console.log(e);
alert(e);
fileInput.value = "";
}
}
}
})
/* GitHub Release fetch */
type ReleaseInfo = {
OCSuiteVer: string;
LoaderKipUrl: string;
SdOutZipUrl: 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}`);
}
const resultFromSuite = await responseFromSuite.json();
const latestVerFromSuite = resultFromSuite.tag_name;
const correspondingVerFromAMS = 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/${correspondingVerFromAMS}`;
let info: ReleaseInfo = {
OCSuiteVer: latestVerFromSuite,
LoaderKipUrl: loaderKip.browser_download_url,
SdOutZipUrl: sdOut.browser_download_url,
AMSVer: correspondingVerFromAMS,
AMSUrl: amsReleaseUrl
};
return info;
} catch (e) {
console.log(e);
alert(e);
}
}
async function updateDownloadUrls() {
const updateHref = (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 ${info.OCSuiteVer}`;
updateHref("loader_kip_btn", loaderKipName, info.LoaderKipUrl);
const sdOutName = `SdOut.zip ${info.OCSuiteVer}`;
updateHref("sdout_zip_btn", sdOutName, info.SdOutZipUrl);
const amsName = `Atmosphere-NX ${info.AMSVer}`;
updateHref("ams_btn", amsName, info.AMSUrl);
}
}
addEventListener('DOMContentLoaded', async (_evt) => {
await updateDownloadUrls();
});