chore: many changes

This commit is contained in:
souldbminersmwc
2025-10-06 19:18:21 -04:00
parent 4d910c1b39
commit 3bdb6dc23e
31 changed files with 3156 additions and 472 deletions

140
docs/configurator.html Normal file
View File

@@ -0,0 +1,140 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Horizon OC Configurator</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
<style>
body {
background: linear-gradient(160deg, #1a1a1a 0%, #2a2a2a 50%, #3a3a3a 100%);
color: white;
font-family: 'Segoe UI', Roboto, system-ui, sans-serif;
overflow-x: hidden;
}
.glass {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.15);
backdrop-filter: blur(20px);
border-radius: 20px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}
.glass:hover {
background: rgba(255, 255, 255, 0.12);
transform: translateY(-3px);
}
.button-glass {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.25);
color: white;
padding: 0.8rem 1.6rem;
border-radius: 9999px;
font-weight: 600;
transition: all 0.3s;
display: inline-block;
text-decoration: none;
margin: 0.25rem 0;
}
.button-glass:hover {
background: rgba(255, 255, 255, 0.25);
transform: scale(1.05);
}
input[type="number"], input[type="file"] {
padding: 0.5rem 0.8rem;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.25);
background: rgba(255, 255, 255, 0.1);
color: white;
font-weight: 500;
outline: none;
transition: all 0.2s;
}
input[type="number"]:focus, input[type="file"]:focus {
border-color: #b8f3ff;
background: rgba(255, 255, 255, 0.2);
}
label {
margin-right: 0.5rem;
}
section {
margin-top: 2rem;
}
footer {
background: rgba(255, 255, 255, 0.05);
border-top: 1px solid rgba(255, 255, 255, 0.15);
padding: 1.5rem 0;
text-align: center;
color: #ccc;
font-size: 0.9rem;
margin-top: 4rem;
backdrop-filter: blur(10px);
}
</style>
</head>
<body>
<!-- Navigation / Go Back -->
<nav class="flex justify-start p-6">
<a href="index.html" class="button-glass">← Go Back to Main Page</a>
</nav>
<!-- Configurator -->
<section id="config" class="max-w-4xl mx-auto px-6 py-16 glass mt-6" data-aos="fade-up">
<h2 class="text-3xl font-bold mb-4 text-center">Configurator</h2>
<p class="text-gray-400 text-center mb-8">Configure frequencies and voltages to suit your hardware and preferences.</p>
<form class="space-y-6 text-center">
<!-- Unit selection buttons -->
<div class="flex justify-center gap-2">
<button type="button" class="button-glass">All</button>
<button type="button" class="button-glass">Erista</button>
<button type="button" class="button-glass">Mariko</button>
</div>
<!-- File upload -->
<div class="flex flex-col items-center">
<input type="file" id="file">
<p class="text-sm text-gray-400 mt-2">Upload loader.kip here</p>
</div>
<!-- Example numeric inputs -->
<div class="flex flex-wrap justify-center gap-4 mt-4">
<div>
<label for="commonEmcMemVolt">EMC Voltage:</label>
<input type="number" id="commonEmcMemVolt" min="0" max="1250000" step="12500">
</div>
<div>
<label for="commonCpuVolt">CPU Voltage:</label>
<input type="number" id="commonCpuVolt" min="0" max="1250000" step="12500">
</div>
<div>
<label for="commonGpuVolt">GPU Voltage:</label>
<input type="number" id="commonGpuVolt" min="0" max="1250000" step="12500">
</div>
</div>
</form>
</section>
<!-- Footer -->
<footer>
Built with ❤️. All trademarks and logos are the property of their respective owners.
</footer>
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script>AOS.init({ once: true, duration: 700 });</script>
<script src="configurator.js"></script>
</body>
</html>

683
docs/configurator.js Normal file
View File

@@ -0,0 +1,683 @@
/* configurator.js
Clean, readable reconstruction of the original minified configurator
- Keeps original behavior (tables, parsing, localStorage, GitHub release fetching)
- Uses modern async/await and DOMContentLoaded guarding
*/
const CUST_REV_ADV = 11;
const CustPlatform = Object.freeze({
Undefined: 0,
Erista: 1,
Mariko: 2,
All: 3
});
class CustEntry {
constructor(id, name, platform, size, desc = [], defval = 0, range = [0, 1e6], step = 1, zeroable = true) {
this.id = id;
this.name = name;
this.platform = platform;
this.size = size;
this.desc = desc;
this.defval = defval;
this.step = step;
this.zeroable = zeroable;
this.min = range[0];
this.max = range[1];
this.value = this.defval;
}
validate() {
const err = new ErrorToolTip(this.id).clear();
if (Number.isNaN(this.value) || this.value === undefined) {
err.setMsg("Invalid value: Not a number").show();
return false;
}
if (!this.zeroable && this.value === 0) {
err.setMsg("Zero is not allowed for this entry").show();
return false;
}
if (this.value < this.min || this.value > this.max) {
err.setMsg(`Expected range: [${this.min}, ${this.max}], got ${this.value}.`).show();
return false;
}
if ((this.value % this.step) !== 0) {
err.setMsg(`${this.value} % ${this.step} ≠ 0`).show();
return false;
}
return true;
}
getInputElement() {
return document.getElementById(this.id);
}
updateValueFromElement() {
const el = this.getInputElement();
this.value = Number(el?.value);
}
isAvailableFor(platform) {
return platform === CustPlatform.Undefined || this.platform === platform || this.platform === CustPlatform.All;
}
createElement(containerId = "config-list-basic") {
let input = this.getInputElement();
if (!input) {
const wrapper = document.createElement("div");
wrapper.classList.add("grid", "cust-element", "mb-3");
input = document.createElement("input");
input.min = String(this.zeroable ? 0 : this.min);
input.max = String(this.max);
input.id = this.id;
input.type = "number";
input.step = String(this.step);
input.classList.add("text-black", "w-36");
const label = document.createElement("label");
label.setAttribute("for", this.id);
label.innerHTML = `<strong>${this.name}</strong>`;
label.classList.add("block", "mb-1");
const desc = document.createElement("blockquote");
desc.innerHTML = "<ul>" + (this.desc || []).map(d => `<li>${d}</li>`).join("") + "</ul>";
desc.setAttribute("for", this.id);
desc.classList.add("text-sm", "text-gray-300");
wrapper.appendChild(label);
wrapper.appendChild(input);
wrapper.appendChild(desc);
const container = document.getElementById(containerId);
if (container) container.appendChild(wrapper);
new ErrorToolTip(this.id).addChangeListener();
}
input.value = String(this.value);
}
setElementValue() {
const el = this.getInputElement();
if (el) el.value = String(this.value);
}
setElementDefaultValue() {
const el = this.getInputElement();
if (el) el.value = String(this.defval);
}
removeElement() {
const el = this.getInputElement();
if (el && el.parentElement && el.parentElement.parentElement) el.parentElement.parentElement.remove();
}
showElement() {
const el = this.getInputElement();
if (el && el.parentElement && el.parentElement.parentElement) el.parentElement.parentElement.style.removeProperty("display");
}
hideElement() {
const el = this.getInputElement();
if (el && el.parentElement && el.parentElement.parentElement) el.parentElement.parentElement.style.setProperty("display", "none");
}
}
class AdvEntry extends CustEntry {
createElement() {
super.createElement("config-list-advanced");
}
}
class GpuEntry extends CustEntry {
constructor(id, name, platform = CustPlatform.Mariko, size = 4, desc = ["range: 610 ≤ x ≤ 1000"], defval = 610, range = [610, 1000], step = 5, zeroable = false) {
super(id, name, platform, size, desc, defval, range, step, zeroable);
}
createElement() {
super.createElement("config-list-gpu");
}
}
/* === Tables (reconstructed from original) ===
Note: Numeric defaults preserved where feasible.
*/
const CustTable = [
new CustEntry("mtcConf", "DRAM Timing", CustPlatform.All, 4,
[
"<b>0</b>: AUTO_ADJ_ALL: Auto adjust mtc table with LPDDR4 3733 Mbps specs, 8Gb density. Change timing with Advanced Config (Default)",
"<b>1</b>: CUSTOM_ADJ_ALL: Adjust only non-zero preset timings in Advanced Config",
"<b>2</b>: NO_ADJ_ALL: Use 1600 mtc table without adjusting (Timing becomes tighter if you raise dram clock)."
], 0, [0, 2], 1),
new CustEntry("commonCpuBoostClock", "Boost Clock in kHz", CustPlatform.All, 4,
["System default: 1785000", "Boost clock will be applied when applications request Boost Mode via performance configuration."],
1785000, [1020000, 3000000], 1, false),
new CustEntry("commonEmcMemVolt", "EMC Vdd2 Voltage in uV", CustPlatform.All, 4,
["Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.", "Erista Default: 1125000", "Mariko Default: 1100000", "Official lpddr4(x) range: 1060mV~1175mV (1100mV nominal)", "OCS need high voltage unlike l4t because of not scaling mtc table well. However it is recommended to stay within official limits", "Not enabled by default"],
0, [1100000, 1250000], 12500),
new CustEntry("eristaCpuMaxVolt", "Erista CPU Max Voltage in mV", CustPlatform.Erista, 4,
["Acceptable range: 1120 ≤ x ≤ 1300", "L4T Default: 1235"], 1235, [1120, 1300], 1),
new CustEntry("eristaEmcMaxClock", "Erista RAM Max Clock in kHz", CustPlatform.Erista, 4,
["Values should be ≥ 1600000, and divided evenly by 3200.", "Recommended Clocks: 1862400, 2131200 (JEDEC)", "<b>WARNING:</b> RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM"], 1862400, [1600000, 2131200], 3200),
new CustEntry("marikoCpuMaxVolt", "Mariko CPU Max Voltage in mV", CustPlatform.Mariko, 4,
["System default: 1120", "Acceptable range: 1120 ≤ x ≤ 1300", "Changing this value affects cpu voltage calculation"], 1235, [1120, 1300], 5),
new CustEntry("marikoEmcMaxClock", "Mariko RAM Max Clock in kHz", CustPlatform.Mariko, 4,
["Values should be ≥ 1600000, and divided evenly by 3200.", "Recommended Clocks: 1862400, 2131200, 2400000 (JEDEC)", "Some clocks above 2400Mhz might not boot, because OCS doesn't scale table very well", "<b>WARNING:</b> RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM."],
1996800, [1600000, 2502400], 3200),
new CustEntry("marikoEmcVddqVolt", "EMC Vddq (Mariko Only) Voltage in uV", CustPlatform.Mariko, 4,
["Acceptable range: 550000 ≤ x ≤ 650000", "Value should be divided evenly by 5000", "Default: 600000", "Official lpddr4(x) range: 570mV~650mV (600mV nominal)", "Not enabled by default."],
0, [550000, 650000], 5000),
new CustEntry("marikoCpuUV", "Enable Mariko CPU Undervolt", CustPlatform.Mariko, 4,
["Reduce CPU power draw", "<b>0</b> : Default Table", "<b>1</b> : Undervolt Level 1 (SLT - CPU speedo < 1650)", "<b>2</b> : Undervolt Level 1 (SLT - CPU speedo >= 1650)"], 0, [0, 2], 1),
new CustEntry("marikoGpuUV", "Enable Mariko GPU Undervolt", CustPlatform.Mariko, 4,
["Reduce GPU power draw", "Your GPU might not withstand undervolt, and can hang your console, or crash games", "Undervolting too much will drop GPU performance even if it seems stable", "GPU voltages are dynamic and will change with temperature and gpu speedo", "<b>0</b> : Default Table", "<b>1</b> : Undervolt Level 1 (SLT: Aggressive)", "<b>2</b> : Undervolt Level 2 (HiOPT: Drastic)", "<b>3</b> : Custom static GPU Voltage Table (Use Gpu Configuration below)"],
0, [0, 3], 1),
new CustEntry("commonGpuVoltOffset", "GPU Volt Offset", CustPlatform.All, 4,
["Negative Voltage offset value for gpu dynamic voltage calculation", "For example, value of 10 will decrease 10mV gpu volt from all frequencies", "Default gpu vmin: Erista - 812.5mV / Mariko - 610mV", "Acceptable range: 0 ~ 100"],
0, [0, 100], 1)
];
const AdvTable = [
new AdvEntry("marikoEmcDvbShift", "Step up Mariko EMC DVB Table", CustPlatform.Mariko, 4,
["Each number adds 25mV to SoC voltage", "Helps with stability at higher memory clock", "Acceptable range : 0~9"], 0, [0, 9], 1),
new AdvEntry("ramTimingPresetOne", "Primary RAM Timing Preset", CustPlatform.Mariko, 4,
["<b>WARNING</b>: Unstable timings can corrupt your nand", "Select Timing Preset for both AUTO_ADJ and CUSTOM_ADJ", "Values are : tRCD - tRP - tRAS (tRC = tRP + tRAS)", "<b>0</b> : Do Not Adjust (2400Mhz: 12 - 12 - 28) (CUST_ADJ only)", "<b>1</b> : 18 - 18 - 42 (Default timing)", "<b>2</b> : 17 - 17 - 39", "<b>3</b> : 16 - 16 - 36", "<b>4</b> : 15 - 15 - 34", "<b>5</b> : 14 - 14 - 32", "<b>6</b> : 13 - 13 - 30"],
1, [0, 6], 1),
new AdvEntry("ramTimingPresetTwo", "Secondary RAM Timing Preset", CustPlatform.Mariko, 4,
["WARNING: Unstable timings can corrupt your nand", "Secondary Timing Preset for both AUTO_ADJ and CUSTOM_ADJ", "Values are : tRRD - tFAW", "<b>0</b> : Do Not Adjust (2400Mhz: 6.6 - 26.6) (CUST_ADJ only)", "<b>1</b> : 10 - 40 (Default timing) (3733 specs)", "<b>2</b> : 7.5 - 30 (4266 specs)", "<b>3</b> : 6 - 24", "<b>4</b> : 4 - 16", "<b>5</b> : 3 - 12"],
1, [0, 5], 1),
new AdvEntry("ramTimingPresetThree", "Secondary RAM Timing Preset", CustPlatform.Mariko, 4,
["WARNING: Unstable timings can corrupt your nand", "Secondary Timing Preset for both AUTO_ADJ and CUSTOM_ADJ", "Values are : tWR - tRTP", "<b>0</b> : Do Not Adjust (2400Mhz: ?? - 5) (CUST_ADJ only)", "<b>1</b> : 18 - 7.5 (Default timing)", "<b>2</b> : 15 - 7.5", "<b>3</b> : 15 - 6", "<b>4</b> : 12 - 6", "<b>5</b> : 12 - 4", "<b>6</b> : 8 - 4"],
1, [0, 6], 1),
new AdvEntry("ramTimingPresetFour", "Secondary RAM Timing Preset", CustPlatform.Mariko, 4,
["WARNING: Unstable timings can corrupt your nand", "Secondary Timing Preset for both AUTO_ADJ and CUSTOM_ADJ", "Values are : tRFC", "<b>0</b> : Do Not Adjust (2400Mhz: 93.3) (CUST_ADJ only)", "<b>1</b> : 140 (Default timing)", "<b>2</b> : 120", "<b>3</b> : 100", "<b>4</b> : 80", "<b>5</b> : 70", "<b>6</b> : 60"],
1, [0, 6], 1),
new AdvEntry("ramTimingPresetFive", "Secondary RAM Timing Preset", CustPlatform.Mariko, 4,
["WARNING: Unstable timings can corrupt your nand", "Secondary Timing Preset for both AUTO_ADJ and CUSTOM_ADJ", "Values are : tWTR", "<b>0</b> : Do Not Adjust (2400Mhz: ??) (CUST_ADJ only)", "<b>1</b> : 10 (Default timing)", "<b>2</b> : 8", "<b>3</b> : 6", "<b>4</b> : 4", "<b>5</b> : 2", "<b>6</b> : 1"],
1, [0, 6], 1),
new AdvEntry("ramTimingPresetSix", "Tertiary RAM Timing Preset", CustPlatform.Mariko, 4,
["WARNING: Unstable timings can corrupt your nand", "Tertiary Timing Preset for both AUTO_ADJ and CUSTOM_ADJ", "Values are : tREFpb", "<b>0</b> : Do Not Adjust (2400Mhz: 325) (CUST_ADJ only)", "<b>1</b> : 488 (Default timing)", "<b>2</b> : 976", "<b>3</b> : 1952", "<b>4</b> : 3256", "<b>5</b> : MAX"],
1, [0, 5], 1),
new AdvEntry("ramTimingPresetSeven", "Latency Decrement", CustPlatform.Mariko, 4,
["WARNING: Unstable timings can corrupt your nand", "Latency decrement for both AUTO_ADJ and CUSTOM_ADJ", "This preset decreases Write/Read related delays. Values are Write - Read", "<b>0</b> : 0 - 0, Do Not Adjust for CUST_ADJ", "<b>1</b> : '-2' - '-4'", "<b>2</b> : '-4' - '-8'", "<b>3</b> : '-6' - '-12'", "<b>4</b> : '-8' - '-16'", "<b>5</b> : '-10' - '-20'", "<b>6</b> : '-12' - '-24'"],
0, [0, 6], 1)
];
const GpuTable = [
new GpuEntry("0", "76.8"),
new GpuEntry("1", "153.6"),
new GpuEntry("2", "230.4"),
new GpuEntry("3", "307.2"),
new GpuEntry("4", "384.0"),
new GpuEntry("5", "460.8"),
new GpuEntry("6", "537.6"),
new GpuEntry("7", "614.4"),
new GpuEntry("8", "691.2"),
new GpuEntry("9", "768.0"),
new GpuEntry("10", "844.8"),
new GpuEntry("11", "921.6"),
new GpuEntry("12", "998.4"),
new GpuEntry("13", "1075.2"),
new GpuEntry("14", "1152.0"),
new GpuEntry("15", "1228.8"),
new GpuEntry("16", "1267.2")
];
/* ===== ErrorToolTip ===== */
class ErrorToolTip {
constructor(id, msg) {
this.id = id;
this.msg = msg;
this.element = document.getElementById(id);
if (msg) this.setMsg(msg);
}
setMsg(msg) {
this.msg = String(msg);
return this;
}
show() {
if (this.element) this.element.setAttribute("aria-invalid", "true");
if (this.msg && this.element) {
this.element.setAttribute("title", this.msg);
if (this.element.parentElement) {
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");
if (this.element) this.element.removeAttribute("title");
if (this.element && this.element.parentElement) {
this.element.parentElement.removeAttribute("data-tooltip");
this.element.parentElement.removeAttribute("data-placement");
}
return this;
}
addChangeListener() {
if (!this.element) return;
this.element.addEventListener("change", () => {
const tableEntry = CustTable.find(e => e.id === this.id);
if (tableEntry) {
tableEntry.value = Number(this.element.value);
tableEntry.validate();
}
});
}
}
/* ===== CustStorage ===== */
class CustStorage {
constructor() {
this.storage = {};
this.key = "last_saved";
}
updateFromTable() {
// helper to update and validate each entry
const updateOne = (entry) => {
entry.updateValueFromElement();
if (!entry.validate()) {
const el = entry.getInputElement();
if (el) el.focus();
throw new Error(`Invalid ${entry.name}`);
}
};
CustTable.forEach(updateOne);
AdvTable.forEach(updateOne);
GpuTable.forEach(updateOne);
this.storage = {};
let t = Object.fromEntries(CustTable.map(e => [e.id, e.value]));
Object.keys(t).forEach(k => (this.storage[k] = t[k]));
t = Object.fromEntries(AdvTable.map(e => [e.id, e.value]));
Object.keys(t).forEach(k => (this.storage[k] = t[k]));
// GPU table could be included too if desired
}
setTable() {
const keys = Object.keys(this.storage);
keys.forEach(k => {
const c = CustTable.find(e => e.id === k);
if (c) c.value = this.storage[k];
});
keys.forEach(k => {
const a = AdvTable.find(e => e.id === k);
if (a) a.value = this.storage[k];
});
// Set defaults for entries not found
CustTable.filter(e => !keys.includes(e.id)).forEach(e => e.value = e.defval);
AdvTable.filter(e => !keys.includes(e.id)).forEach(e => e.value = e.defval);
// Validate and set element values
CustTable.forEach(e => {
if (!e.validate()) {
const el = e.getInputElement();
if (el) el.focus();
throw new Error(`Invalid ${e.name}`);
}
e.setElementValue();
});
AdvTable.forEach(e => {
if (!e.validate()) {
const el = e.getInputElement();
if (el) el.focus();
throw new Error(`Invalid ${e.name}`);
}
e.setElementValue();
});
GpuTable.forEach(e => {
if (!e.validate()) {
const el = e.getInputElement();
if (el) el.focus();
throw new Error(`Invalid ${e.name}`);
}
e.setElementValue();
});
}
save() {
localStorage.setItem(this.key, JSON.stringify(this.storage));
}
load() {
const raw = localStorage.getItem(this.key);
if (!raw) return null;
const parsed = JSON.parse(raw);
// Filter unknown keys and warn
const custIds = CustTable.map(e => e.id);
const ignoredCust = Object.keys(parsed).filter(k => !custIds.includes(k));
if (ignoredCust.length) console.log("Ignored (cust):", ignoredCust);
Object.keys(parsed).filter(k => custIds.includes(k)).forEach(k => (this.storage[k] = parsed[k]));
const advIds = AdvTable.map(e => e.id);
const ignoredAdv = Object.keys(parsed).filter(k => !advIds.includes(k));
if (ignoredAdv.length) console.log("Ignored (adv):", ignoredAdv);
Object.keys(parsed).filter(k => advIds.includes(k)).forEach(k => (this.storage[k] = parsed[k]));
return this.storage;
}
}
/* ===== Cust (binary parser/patcher) =====
This class follows the original structure:
- find magic in buffer using magicLen, mapper sizes
- parse data using size-mapped getters/setters
- produce Blob for download (patched buffer)
*/
class Cust {
constructor() {
this.storage = new CustStorage();
this.magic = 1414747459; // original magic
this.magicLen = 4;
this.mapper = {
2: { get: (offset) => this.view.getUint16(offset, true), set: (offset, v) => this.view.setUint16(offset, v, true) },
4: { get: (offset) => this.view.getUint32(offset, true), set: (offset, v) => this.view.setUint32(offset, v, true) }
};
}
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 (magic not found)");
}
save() {
// update from table (validate & populate storage)
this.storage.updateFromTable();
const writeOne = (entry) => {
if (!entry.offset && entry.offset !== 0) {
// If offset is missing in the UI, it's an error in mapping
const el = entry.getInputElement();
if (el) el.focus();
throw new Error(`Failed to get offset for ${entry.name}`);
}
const mapper = this.mapper[entry.size];
if (!mapper) {
const el = entry.getInputElement();
if (el) el.focus();
throw new Error(`Unknown size at ${entry.name}`);
}
mapper.set(entry.offset, entry.value);
};
// The original iterated over tables and used .offset values stored in entries
CustTable.forEach(writeOne);
AdvTable.forEach(writeOne);
GpuTable.forEach(writeOne);
// persist storage to localStorage
this.storage.save();
// create download link for patched buffer
const blob = new Blob([this.buffer], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "loader.kip";
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
// UI: enable load last saved if needed
this.toggleLoadLastSavedBtn(true);
}
removeHTMLForm() {
CustTable.forEach(e => e.removeElement());
}
toggleLoadLastSavedBtn(visible) {
const btn = document.getElementById("load_saved");
if (!btn) return;
if (visible) {
btn.addEventListener("click", () => {
if (this.storage.load()) this.storage.setTable();
});
btn.style.removeProperty("display");
btn.removeAttribute("disabled");
} else {
btn.style.setProperty("display", "none");
}
}
createHTMLForm() {
// create elements for each table entry
CustTable.forEach(e => e.createElement("config-list-basic"));
// Add separators/titles for advanced and gpu lists
const advContainer = document.getElementById("config-list-advanced");
if (advContainer) {
const p = document.createElement("p");
p.innerHTML = "Advanced configuration";
advContainer.appendChild(p);
}
const gpuContainer = document.getElementById("config-list-gpu");
if (gpuContainer) {
const p = document.createElement("p");
p.innerHTML = "Gpu Volt configuration";
gpuContainer.appendChild(p);
}
AdvTable.forEach(e => e.createElement("config-list-advanced"));
GpuTable.forEach(e => e.createElement("config-list-gpu"));
// Buttons: load default, load saved, save
const loadDefaultBtn = document.getElementById("load_default");
if (loadDefaultBtn) {
loadDefaultBtn.removeAttribute("disabled");
loadDefaultBtn.addEventListener("click", () => {
CustTable.forEach(e => e.setElementDefaultValue());
});
}
// show load saved if we have saved storage
this.toggleLoadLastSavedBtn(this.storage.load() !== null);
const saveBtn = document.getElementById("save");
if (saveBtn) {
saveBtn.removeAttribute("disabled");
saveBtn.addEventListener("click", () => {
try {
this.save();
} catch (err) {
console.error(err);
alert(String(err));
}
});
}
}
initCustTabs() {
// map nav buttons (role="tablist") to platform toggles
const tabButtons = Array.from(document.querySelectorAll('nav[role="tablist"] > button, .cust-platform-tabs > button'));
if (!tabButtons.length) return;
tabButtons.forEach(btn => {
btn.removeAttribute("disabled");
const plat = Number(btn.getAttribute("data-platform"));
btn.addEventListener("click", (ev) => {
// style toggling
const outlineClass = ["outline"];
btn.classList.remove(...outlineClass);
tabButtons.filter(x => x !== btn).forEach(x => x.classList.add(...outlineClass));
// show/hide entries
CustTable.forEach(e => {
if (e.isAvailableFor(plat)) e.showElement(); else e.hideElement();
});
});
});
}
parse() {
// beginOffset should already exist
let offset = this.beginOffset + this.magicLen;
// read revision (4-byte)
this.rev = this.mapper[4].get(offset);
if (this.rev !== CUST_REV_ADV) throw new Error(`Unsupported custRev, expected: ${CUST_REV_ADV}, got ${this.rev}`);
offset += 4;
const revEl = document.getElementById("cust_rev");
if (revEl) revEl.innerHTML = `Cust v${this.rev} is loaded.`;
const readOne = (entry) => {
entry.offset = offset;
const mapper = this.mapper[entry.size];
if (!mapper) {
const el = entry.getInputElement();
if (el) el.focus();
throw new Error(`Unknown size at ${entry.name}`);
}
entry.value = mapper.get(offset);
offset += entry.size;
entry.validate();
};
CustTable.forEach(readOne);
AdvTable.forEach(readOne);
GpuTable.forEach(readOne);
}
load(buffer) {
try {
this.buffer = buffer;
this.findMagicOffset();
this.removeHTMLForm();
this.parse();
this.initCustTabs();
this.createHTMLForm();
} catch (err) {
console.error(err);
alert(String(err));
}
}
}
/* ===== Release fetching classes ===== */
class ReleaseAsset {
constructor(obj) {
this.downloadUrl = obj?.browser_download_url;
this.updatedAt = obj?.updated_at;
}
}
class ReleaseInfo {
constructor() {
this.ocLatestApi = "https://api.github.com/repos/Horizon-OC/Horizon-OC/releases/latest";
}
async load() {
try {
const json = await this.responseFromApi(this.ocLatestApi);
this.parseOcResponse(json);
} catch (err) {
console.error(err);
alert(String(err));
}
}
async responseFromApi(url) {
const res = await fetch(url, { method: "GET", headers: { Accept: "application/json" } });
if (!res.ok) throw new Error(`Failed to connect to "${url}": ${res.status}`);
return await res.json();
}
parseOcResponse(json) {
this.ocVer = json.tag_name;
const assets = json.assets || [];
this.loaderKipAsset = new ReleaseAsset(assets.find(a => a.name.endsWith("hoc.kip")) || {});
this.sdOutZipAsset = new ReleaseAsset(assets.find(a => a.name.endsWith("dist.zip")) || {});
}
}
class DownloadSection {
constructor() {
this.element = document.getElementById("download_btn_grid") || document.getElementById("download");
}
async load() {
// Wait until the element is visible in the viewport
for (; !this.isVisible();) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
const release = new ReleaseInfo();
await release.load();
this.update("loader_kip_btn", `hoc.kip <b>${release.ocVer}</b><br>${release.loaderKipAsset.updatedAt}`, release.loaderKipAsset.downloadUrl);
this.update("sdout_zip_btn", `dist.zip <b>${release.ocVer}</b><br>${release.sdOutZipAsset.updatedAt}`, release.sdOutZipAsset.downloadUrl);
}
isVisible() {
if (!this.element) return true;
const r = this.element.getBoundingClientRect();
return r.top > 0 && r.left > 0 && r.bottom - r.height < (window.innerHeight || document.documentElement.clientHeight) && r.right - r.width < (window.innerWidth || document.documentElement.clientWidth);
}
update(id, html, href) {
const el = document.getElementById(id);
if (!el) return;
el.innerHTML = html;
el.removeAttribute("aria-busy");
if (href) el.setAttribute("href", href);
}
}
/* ===== Initialization wiring ===== */
document.addEventListener("DOMContentLoaded", async () => {
// Ensure containers exist (fail gracefully if not)
const ensureEl = (id, createTag = "div") => {
if (!document.getElementById(id)) {
const container = document.createElement(createTag);
container.id = id;
container.classList.add("mt-4");
// append to config section or body as fallback
const config = document.getElementById("config") || document.body;
config.appendChild(container);
}
};
["config-list-basic", "config-list-advanced", "config-list-gpu"].forEach(id => ensureEl(id));
// Wire file input
const fileInput = document.getElementById("file");
if (fileInput) {
fileInput.addEventListener("change", (ev) => {
const cust = new Cust();
const target = ev.target;
if (!target || !target.files) return;
const fr = new FileReader();
fr.readAsArrayBuffer(target.files[0]);
fr.onloadend = (e) => {
if (e.target && e.target.readyState === FileReader.DONE) {
cust.load(e.target.result);
}
};
});
}
// Wire DownloadSection (non-blocking)
(async () => {
try {
const ds = new DownloadSection();
await ds.load();
} catch (err) {
console.warn("DownloadSection failed:", err);
}
})();
// If HTML had DOMContentLoaded wiring in original minified code, emulate it:
// some initialization that original file did on DOMContentLoaded -> start the DownloadSection (done above)
});

View File

@@ -1,167 +1,153 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Horizon OC - Switch Overclocking Tool</title>
<title>Horizon OC</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
<style>
/* Background Image Cycling */
/* Background gradient */
body {
margin: 0;
font-family: sans-serif;
font-family: 'Segoe UI', Roboto, system-ui, sans-serif;
color: white;
background: linear-gradient(160deg, #1a1a1a 0%, #2a2a2a 50%, #3a3a3a 100%);
min-height: 100vh;
overflow-x: hidden;
color: white;
}
.bg-slide {
position: fixed;
top: 0; left: 0;
width: 100%;
height: 100%;
background-size: cover;
background-position: center;
z-index: -2;
opacity: 0;
transition: opacity 2s ease-in-out;
}
.bg-slide.active {
opacity: 1;
}
/* Gradient overlay that changes with image */
.gradient-overlay {
position: fixed;
top: 0; left: 0;
width: 100%;
height: 100%;
z-index: -1;
pointer-events: none;
transition: background 2s ease-in-out;
}
/* Pop-in effect */
.popin {
transform: scale(0.8);
opacity: 0;
transition: transform 0.6s ease, opacity 0.6s ease;
}
.popin.aos-animate {
transform: scale(1);
opacity: 1;
}
/* Glassmorphism cards */
/* Liquid glass panels */
.glass {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.25);
transition: transform 0.3s ease;
}
/* Apple-style liquid glass buttons */
.liquid-button {
padding: 0.75rem 2rem;
border-radius: 1.5rem;
font-weight: 600;
background: rgba(255,255,255,0.2);
backdrop-filter: blur(12px);
border: 1px solid rgba(255,255,255,0.3);
box-shadow: inset 0 1px 0 rgba(255,255,255,0.25), 0 4px 15px rgba(0,0,0,0.25);
color: white;
cursor: pointer;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.15);
backdrop-filter: blur(20px);
border-radius: 20px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}
.liquid-button:hover {
background: rgba(255,255,255,0.3);
box-shadow: inset 0 2px 0 rgba(255,255,255,0.35), 0 6px 20px rgba(0,0,0,0.3);
.glass:hover {
background: rgba(255, 255, 255, 0.12);
transform: translateY(-4px);
}
/* Hero gradient text */
.gradient-text {
background: linear-gradient(to right, #a3baff, #b8f3ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
/* Buttons: Liquid glass style */
.button-glass {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.25);
color: #fff;
padding: 0.8rem 2rem;
border-radius: 9999px;
font-weight: 600;
transition: all 0.25s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
display: inline-flex;
align-items: center;
gap: 0.6rem;
text-decoration: none;
}
.button-glass:hover {
background: rgba(255, 255, 255, 0.25);
transform: scale(1.05);
}
footer {
background: rgba(255, 255, 255, 0.05);
border-top: 1px solid rgba(255, 255, 255, 0.15);
padding: 1.5rem 0;
text-align: center;
color: #ccc;
font-size: 0.9rem;
margin-top: 4rem;
backdrop-filter: blur(10px);
}
html {
scroll-behavior: smooth;
}
/* GitHub Icon */
.github-icon {
width: 20px;
height: 20px;
fill: white;
transition: fill 0.3s;
}
.button-glass:hover .github-icon {
fill: #b8f3ff;
}
</style>
</head>
<body class="text-white">
<!-- Background Images -->
<div class="bg-slide" style="background-image: url('img1.png');"></div>
<div class="bg-slide" style="background-image: url('img2.png');"></div>
<div class="bg-slide" style="background-image: url('img3.png');"></div>
<body>
<!-- Gradient overlay -->
<div class="gradient-overlay" id="gradient-overlay" style="background: linear-gradient(135deg, rgba(50,0,100,0.6), rgba(0,0,50,0.6));"></div>
<!-- Header -->
<header class="text-center py-20">
<h1 class="text-6xl font-extrabold mb-4 gradient-text">Horizon OC</h1>
<p class="text-gray-300 text-lg">Upgrade the performance of your Nintendo Switch Console</p>
<!-- Hero Section -->
<header class="text-center py-28 relative overflow-hidden popin" data-aos="popin">
<h1 class="text-6xl font-extrabold mb-4 drop-shadow-lg">Horizon OC</h1>
<p class="text-lg text-gray-200 mt-4">
Upgrade the performance of your Nintendo Switch Console
</p>
<div class="mt-8 flex justify-center gap-6">
<a href="#download" class="liquid-button">Download Now</a>
<a href="benchmarks.html" class="liquid-button">View Benchmarks</a>
<div class="mt-6 flex justify-center gap-4">
<a href="#download" class="button-glass">Download Now</a>
<a href="configurator.html" class="button-glass">Open Configurator</a>
<a href="https://github.com/Horizon-OC/Horizon-OC" target="_blank" class="button-glass">
<!-- GitHub SVG Icon -->
<svg class="github-icon" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8a8 8 0 005.47 7.59c.4.07.55-.17.55-.38v-1.32C3.73 14.9 3.27 13.4 3.27 13.4c-.36-.9-.88-1.14-.88-1.14-.72-.49.05-.48.05-.48.8.06 1.22.83 1.22.83.71 1.21 1.87.86 2.33.66.07-.52.28-.86.5-1.06-2.22-.25-4.55-1.11-4.55-4.95 0-1.09.39-1.98 1.03-2.68-.1-.25-.45-1.27.1-2.65 0 0 .84-.27 2.75 1.02A9.56 9.56 0 018 3.43a9.6 9.6 0 012.5.34c1.9-1.29 2.74-1.02 2.74-1.02.55 1.38.2 2.4.1 2.65.64.7 1.03 1.59 1.03 2.68 0 3.85-2.34 4.7-4.57 4.95.29.25.54.73.54 1.48v2.2c0 .21.15.45.55.38A8 8 0 0016 8c0-4.42-3.58-8-8-8z"/>
</svg>
GitHub
</a>
</div>
</header>
<!-- About -->
<section class="max-w-4xl mx-auto text-center py-20 px-6 popin" data-aos="popin" data-aos-delay="100">
<h2 class="text-4xl font-bold mb-6">What is Horizon OC?</h2>
<p class="text-gray-300 text-lg leading-relaxed">
HOC is a tool to overclock your modded Nintendo Switch Console.
This can help improve framrates, load times and latency in games.
<!-- About Section -->
<section class="max-w-3xl mx-auto text-center py-16 px-6 glass" data-aos="fade-up">
<h2 class="text-3xl font-bold mb-4">What is Horizon OC?</h2>
<p class="text-gray-300">
Horizon OC is a tool to overclock your modded Nintendo Switch console.
This can help improve frame rates, load times, and latency in games.
</p>
</section>
<!-- Features -->
<section class="py-20">
<div class="max-w-6xl mx-auto px-6 grid md:grid-cols-3 gap-10">
<div class="glass p-8 rounded-2xl shadow-xl hover:scale-105 transition popin" data-aos="popin" data-aos-delay="200">
<h3 class="text-2xl font-semibold mb-3">⚡ Performance</h3>
<p class="text-gray-300">Overclocking your console can lead to significantly better performance in demanding titles</p>
<div class="max-w-6xl mx-auto grid md:grid-cols-3 gap-8 px-6">
<div class="glass p-6 text-center" data-aos="zoom-in" data-aos-delay="100">
<h3 class="text-xl font-semibold mb-2">⚡ Performance</h3>
<p class="text-gray-300">Overclocking your console can lead to significantly better performance in demanding titles.</p>
</div>
<div class="glass p-8 rounded-2xl shadow-xl hover:scale-105 transition popin" data-aos="popin" data-aos-delay="300">
<h3 class="text-2xl font-semibold mb-3">☣ Safely</h3>
<p class="text-gray-300">Horizon OC aims to be as safe as possible, while allowing advanced users to customize it further</p>
<div class="glass p-6 text-center" data-aos="zoom-in" data-aos-delay="200">
<h3 class="text-xl font-semibold mb-2">☣ Safety</h3>
<p class="text-gray-300">Horizon OC aims to be as safe as possible, while allowing advanced users to customize it further.</p>
</div>
<div class="glass p-8 rounded-2xl shadow-xl hover:scale-105 transition popin" data-aos="popin" data-aos-delay="400">
<h3 class="text-2xl font-semibold mb-3">🔧 Customizable</h3>
<p class="text-gray-300">Horizon OC comes with a advanced configurator which allows you to customize it to your own needs</p>
<div class="glass p-6 text-center" data-aos="zoom-in" data-aos-delay="300">
<h3 class="text-xl font-semibold mb-2">🔧 Customizable</h3>
<p class="text-gray-300">Horizon OC comes with an advanced configurator allowing you to tune it for your own needs.</p>
</div>
</div>
</section>
<!-- Download -->
<section id="download" class="text-center py-24 popin" data-aos="popin" data-aos-delay="500">
<!-- Download Section -->
<section id="download" class="text-center py-20 glass max-w-3xl mx-auto px-6" data-aos="fade-up">
<h2 class="text-3xl font-bold mb-6">Get Horizon OC</h2>
<a href="#" class="liquid-button">Download Latest Release</a>
<p class="text-gray-400 mt-4">Ensure to use the latest version of Atmosphere</p>
<a href="#" class="button-glass">Download Latest Release</a>
<p class="text-gray-400 mt-4">Ensure you are using the latest version of Atmosphere.</p>
</section>
<!-- Footer -->
<footer class="bg-black bg-opacity-60 text-center py-6 text-gray-500 text-sm popin" data-aos="popin">
This website is open source!
<footer>
This website is open source and licensed under the GPLv2.
</footer>
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script>
AOS.init({ once: true, duration: 800 });
// Background image cycling with gradient
const slides = document.querySelectorAll('.bg-slide');
const gradientOverlay = document.getElementById('gradient-overlay');
const gradients = [
'linear-gradient(135deg, rgba(80,0,150,0.6), rgba(0,0,60,0.6))',
'linear-gradient(135deg, rgba(150,50,0,0.6), rgba(50,0,0,0.6))',
'linear-gradient(135deg, rgba(0,100,150,0.6), rgba(0,30,50,0.6))'
];
let current = 0;
function cycleBg() {
slides.forEach((s,i)=>s.classList.remove('active'));
slides[current].classList.add('active');
gradientOverlay.style.background = gradients[current];
current = (current+1)%slides.length;
}
cycleBg();
setInterval(cycleBg, 6000); // every 6s
</script>
<script>AOS.init({ once: true, duration: 800 });</script>
</body>
</html>