diff --git a/.github/workflows/deploy_configurator.yml b/.github/workflows/deploy_configurator.yml
index b80dbf39..f9f0cba9 100644
--- a/.github/workflows/deploy_configurator.yml
+++ b/.github/workflows/deploy_configurator.yml
@@ -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/src' # optional
+ directory: './Source/loaderConfigurator/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' # optional
+ output: './Source/loaderConfigurator/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'
+ path: './Source/loaderConfigurator/dist_min'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
diff --git a/Source/loaderConfigurator/.gitignore b/Source/loaderConfigurator/.gitignore
index 320c107b..504afef8 100644
--- a/Source/loaderConfigurator/.gitignore
+++ b/Source/loaderConfigurator/.gitignore
@@ -1,3 +1,2 @@
node_modules/
-dist/
package-lock.json
diff --git a/Source/loaderConfigurator/build.sh b/Source/loaderConfigurator/build.sh
new file mode 100755
index 00000000..70d687f3
--- /dev/null
+++ b/Source/loaderConfigurator/build.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+[ -d "./dist" ] || mkdir ./dist
+cp -Rf ./src/*.html ./dist/
+tsc ./src/main.ts --outDir ./dist/ -lib es2015,dom -t es2015
+npx tailwindcss -i ./src/style.css -o ./dist/output.css
diff --git a/Source/loaderConfigurator/dist/index.html b/Source/loaderConfigurator/dist/index.html
new file mode 100644
index 00000000..7daab2d8
--- /dev/null
+++ b/Source/loaderConfigurator/dist/index.html
@@ -0,0 +1,22 @@
+
+
+
+
-
-
-
\ No newline at end of file
+
+
diff --git a/Source/loaderConfigurator/src/main.ts b/Source/loaderConfigurator/src/main.ts
new file mode 100644
index 00000000..b2cb8271
--- /dev/null
+++ b/Source/loaderConfigurator/src/main.ts
@@ -0,0 +1,323 @@
+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, 1_000_000], step = 1, extra_validator = null) {
+ 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,
+ "
DRAM Timing\
+
0: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density.\
+
1: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.\
+
2: ENTIRE_TABLE_ERISTA: Not implemented.\
+
3: ENTIRE_TABLE_MARIKO: Not implemented.",
+ 0,
+ [0, 3],
+ ),
+ new CustEntry(
+ "marikoCpuMaxClock",
+ 4,
+ "
Mariko CPU Max Clock in kHz\
+
System default: 1785000\
+
≥ 2193000 will enable overvolting (> 1120 mV)",
+ 2397_000,
+ [1785_000, 3000_000],
+ 100,
+ (x) => { return (x % 100) == 0; }
+ ),
+ new CustEntry(
+ "marikoCpuBoostClock",
+ 4,
+ "
Mariko CPU Boost Clock in kHz\
+
System default: 1785000\
+
Must not be higher than marikoCpuMaxClock",
+ 1785_000,
+ [1785_000, 3000_000],
+ 100,
+ (x) => { return (x % 100) == 0; }
+ ),
+ new CustEntry(
+ "marikoCpuMaxVolt",
+ 4,
+ "
Mariko CPU Max Voltage in mV\
+
System default: 1120\
+
Acceptable range: 1100 ≤ x ≤ 1300",
+ 1220,
+ [1100, 1300],
+ ),
+ new CustEntry(
+ "marikoGpuMaxClock",
+ 4,
+ "
Mariko GPU Max Clock in kHz\
+
System default: 921600\
+
Tegra X1+ official maximum: 1267200",
+ 1305_600,
+ [768_000, 1536_000],
+ 100,
+ (x) => { return (x % 100) == 0; }
+ ),
+ new CustEntry(
+ "marikoEmcMaxClock",
+ 4,
+ "
Mariko RAM Max Clock in kHz\
+
Values should be > 1600000, and divided evenly by 3200.\
+
WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM",
+ 1996_800,
+ [1612_800, 2400_000],
+ 3200,
+ (x) => { return (x % 3200) == 0; }
+ ),
+ new CustEntry(
+ "eristaCpuOCEnable",
+ 4,
+ "
Erista CPU Enable Overclock\
+
Not usable unless CPU cvb table is filled in",
+ 0,
+ [0, 1]
+ ),
+ new CustEntry(
+ "eristaCpuMaxVolt",
+ 4,
+ "
Erista CPU Max Voltage in mV\
+
Acceptable range: 1100 ≤ x ≤ 1400\
+
Not enabled by default",
+ 0,
+ [0, 1400],
+ 100,
+ (x) => { return x >= 1100; }
+ ),
+ new CustEntry(
+ "eristaEmcMaxClock",
+ 4,
+ "
Erista RAM Max Clock in kHz\
+
Values should be > 1600000, and divided evenly by 3200.\
+
WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM",
+ 1862_400,
+ [1600_000, 2400_000],
+ 3200,
+ (x) => { return (x % 3200) == 0; }
+ ),
+ new CustEntry(
+ "eristaEmcVolt",
+ 4,
+ "
Erista RAM Voltage in uV\
+
Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.\
+
Not enabled by default",
+ 0,
+ [0, 1250_000],
+ 12500,
+ (x) => { return (x % 12500) == 0 && x >= 1100_000; }
+ ),
+ ];
+ 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) 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) {
+ let dict = Object.assign({}, ...cust.map((x) => ({[x.name]: x})));
+ for (let i of cust) {
+ let id = i.name;
+ (document.getElementById(id) as HTMLInputElement).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) 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 = "";
+ }
+ }
+ }
+})
\ No newline at end of file
diff --git a/Source/loaderConfigurator/src/style.css b/Source/loaderConfigurator/src/style.css
index 24f6deb3..03978d39 100644
--- a/Source/loaderConfigurator/src/style.css
+++ b/Source/loaderConfigurator/src/style.css
@@ -3,17 +3,37 @@
@tailwind utilities;
label.form-entry {
- @apply inline-block mb-2 text-gray-700;
+ @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-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none;
+ @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-gray-600 px-3 py-1.5;
+ @apply block mt-1 text-xs text-slate-600 dark:text-slate-400 px-3 py-1.5;
}
-button.save-btn {
- @apply inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out;
-}
\ No newline at end of file
+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;
+}
diff --git a/Source/loaderConfigurator/tailwind.config.js b/Source/loaderConfigurator/tailwind.config.js
index a3c23427..96b69fd8 100644
--- a/Source/loaderConfigurator/tailwind.config.js
+++ b/Source/loaderConfigurator/tailwind.config.js
@@ -1,6 +1,6 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
- content: ['./src/*.html', './node_modules/tw-elements/dist/js/**/*.js'],
+ content: ['./dist/*.html', './dist/*.js', './node_modules/tw-elements/dist/js/**/*.js'],
theme: {
extend: {},
},