From a360904ac0adf4e7b97c3371362e16a212a3eda7 Mon Sep 17 00:00:00 2001 From: KazushiM <85604869+KazushiMe@users.noreply.github.com> Date: Wed, 9 Nov 2022 12:01:24 +0800 Subject: [PATCH] Save cust config to localStorage --- .github/workflows/deploy_configurator.yml | 6 +- Source/loaderConfigurator/.gitignore | 1 - Source/loaderConfigurator/build.sh | 6 + Source/loaderConfigurator/dist/index.html | 22 ++ Source/loaderConfigurator/dist/main.js | 237 +++++++++++++ .../{src => dist}/output.css | 269 +++++++++------ .../loaderConfigurator/ldr_config.py | 0 Source/loaderConfigurator/src/index.html | 294 +--------------- Source/loaderConfigurator/src/main.ts | 323 ++++++++++++++++++ Source/loaderConfigurator/src/style.css | 32 +- Source/loaderConfigurator/tailwind.config.js | 2 +- 11 files changed, 788 insertions(+), 404 deletions(-) create mode 100755 Source/loaderConfigurator/build.sh create mode 100644 Source/loaderConfigurator/dist/index.html create mode 100644 Source/loaderConfigurator/dist/main.js rename Source/loaderConfigurator/{src => dist}/output.css (95%) rename ldr_config.py => Source/loaderConfigurator/ldr_config.py (100%) create mode 100644 Source/loaderConfigurator/src/main.ts 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 @@ + + + + + + + + +
+
+ + Get latest here + +
+ + +
+
+
+ + + diff --git a/Source/loaderConfigurator/dist/main.js b/Source/loaderConfigurator/dist/main.js new file mode 100644 index 00000000..5272b623 --- /dev/null +++ b/Source/loaderConfigurator/dist/main.js @@ -0,0 +1,237 @@ +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 = 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)
  • ", 2397000, [1785000, 3000000], 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
  • ", 1785000, [1785000, 3000000], 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
  • ", 1305600, [768000, 1536000], 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
  • ", 1996800, [1612800, 2400000], 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
  • ", 1862400, [1600000, 2400000], 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, 1250000], 12500, (x) => { return (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.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) { + buffer = progEvent.target.result; + try { + let offset = FindMagicOffset(buffer); + ParseCust(offset, buffer); + } + catch (e) { + console.log(e); + alert(e); + fileInput.value = ""; + } + } + }; +}); diff --git a/Source/loaderConfigurator/src/output.css b/Source/loaderConfigurator/dist/output.css similarity index 95% rename from Source/loaderConfigurator/src/output.css rename to Source/loaderConfigurator/dist/output.css index 8ba1ff9d..40ed4ded 100644 --- a/Source/loaderConfigurator/src/output.css +++ b/Source/loaderConfigurator/dist/output.css @@ -660,24 +660,12 @@ video { opacity: 0.65; } -.btn-check[disabled] + .\!btn { - pointer-events: none !important; - filter: none !important; - opacity: 0.65 !important; -} - .btn-check:disabled + .btn { pointer-events: none; filter: none; opacity: 0.65; } -.btn-check:disabled + .\!btn { - pointer-events: none !important; - filter: none !important; - opacity: 0.65 !important; -} - .form-floating { position: relative; } @@ -766,19 +754,10 @@ video { z-index: 2; } -.input-group .\!btn { - position: relative !important; - z-index: 2 !important; -} - .input-group .btn:focus { z-index: 3; } -.input-group .\!btn:focus { - z-index: 3 !important; -} - .input-group:not(.has-validation) > .dropdown-toggle:nth-last-child(n+3) { border-top-right-radius: 0; border-bottom-right-radius: 0; @@ -957,85 +936,43 @@ textarea.form-control.is-invalid { box-shadow: none; } -.btn-check:focus + .\!btn { - outline: 0 !important; - box-shadow: none !important; -} - .btn:focus { outline: 0; box-shadow: none; } -.\!btn:focus { - outline: 0 !important; - box-shadow: none !important; -} - .btn-check:checked + .btn { box-shadow: none; } -.btn-check:checked + .\!btn { - box-shadow: none !important; -} - .btn-check:active + .btn { box-shadow: none; } -.btn-check:active + .\!btn { - box-shadow: none !important; -} - .btn:active { box-shadow: none; } -.\!btn:active { - box-shadow: none !important; -} - .btn.active { box-shadow: none; } -.\!btn.active { - box-shadow: none !important; -} - .btn-check:checked + .btn:focus { box-shadow: none; } -.btn-check:checked + .\!btn:focus { - box-shadow: none !important; -} - .btn-check:active + .btn:focus { box-shadow: none; } -.btn-check:active + .\!btn:focus { - box-shadow: none !important; -} - .btn:active:focus { box-shadow: none; } -.\!btn:active:focus { - box-shadow: none !important; -} - .btn.active:focus { box-shadow: none; } -.\!btn.active:focus { - box-shadow: none !important; -} - .fade { transition: opacity 0.15s linear; } @@ -1780,6 +1717,18 @@ textarea.form-control.is-invalid { transform: none; } +.link-primary { + color: #0d6efd; +} + +.link-primary:hover { + color: #0a58ca; +} + +.link-primary:focus { + color: #0a58ca; +} + .sticky-top { position: sticky; top: 0; @@ -1898,10 +1847,6 @@ textarea.form-control.is-invalid { background-image: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%); } -.\!btn .ripple-wave { - background-image: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%) !important; -} - .ripple-surface-primary .ripple-wave { background-image: radial-gradient(circle, rgba(18, 102, 241, 0.2) 0, rgba(18, 102, 241, 0.3) 40%, rgba(18, 102, 241, 0.4) 50%, rgba(18, 102, 241, 0.5) 60%, rgba(18, 102, 241, 0) 70%); } @@ -3785,23 +3730,20 @@ textarea.form-control.is-invalid { position: sticky; } +.my-10 { + margin-top: 2.5rem; + margin-bottom: 2.5rem; +} + .my-3 { margin-top: 0.75rem; margin-bottom: 0.75rem; } -.mb-2 { - margin-bottom: 0.5rem; -} - .block { display: block; } -.inline-block { - display: inline-block; -} - .inline { display: inline; } @@ -3826,8 +3768,8 @@ textarea.form-control.is-invalid { width: 100%; } -.max-w-sm { - max-width: 24rem; +.max-w-md { + max-width: 28rem; } .transform { @@ -3866,30 +3808,24 @@ textarea.form-control.is-invalid { justify-content: space-evenly; } -.space-x-2 > :not([hidden]) ~ :not([hidden]) { - --tw-space-x-reverse: 0; - margin-right: calc(0.5rem * var(--tw-space-x-reverse)); - margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); -} - .rounded-lg { border-radius: 0.5rem; } -.bg-white { +.bg-slate-200 { --tw-bg-opacity: 1; - background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + background-color: rgb(226 232 240 / var(--tw-bg-opacity)); +} + +.bg-slate-100 { + --tw-bg-opacity: 1; + background-color: rgb(241 245 249 / var(--tw-bg-opacity)); } .p-6 { padding: 1.5rem; } -.text-gray-700 { - --tw-text-opacity: 1; - color: rgb(55 65 81 / var(--tw-text-opacity)); -} - .shadow-lg { --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); @@ -3913,7 +3849,14 @@ label.form-entry { margin-bottom: 0.5rem; display: inline-block; --tw-text-opacity: 1; - color: rgb(55 65 81 / var(--tw-text-opacity)); + color: rgb(51 65 85 / var(--tw-text-opacity)); +} + +@media (prefers-color-scheme: dark) { + label.form-entry { + --tw-text-opacity: 1; + color: rgb(226 232 240 / var(--tw-text-opacity)); + } } input.form-entry { @@ -3924,9 +3867,9 @@ input.form-entry { border-width: 1px; border-style: solid; --tw-border-opacity: 1; - border-color: rgb(209 213 219 / var(--tw-border-opacity)); + border-color: rgb(203 213 225 / var(--tw-border-opacity)); --tw-bg-opacity: 1; - background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + background-color: rgb(241 245 249 / var(--tw-bg-opacity)); background-clip: padding-box; padding-left: 0.75rem; padding-right: 0.75rem; @@ -3936,7 +3879,7 @@ input.form-entry { line-height: 1.5rem; font-weight: 400; --tw-text-opacity: 1; - color: rgb(55 65 81 / var(--tw-text-opacity)); + color: rgb(71 85 105 / var(--tw-text-opacity)); transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; @@ -3948,13 +3891,33 @@ input.form-entry:focus { --tw-border-opacity: 1; border-color: rgb(37 99 235 / var(--tw-border-opacity)); --tw-bg-opacity: 1; - background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + background-color: rgb(241 245 249 / var(--tw-bg-opacity)); --tw-text-opacity: 1; - color: rgb(55 65 81 / var(--tw-text-opacity)); + color: rgb(51 65 85 / var(--tw-text-opacity)); outline: 2px solid transparent; outline-offset: 2px; } +@media (prefers-color-scheme: dark) { + input.form-entry { + --tw-border-opacity: 1; + border-color: rgb(75 85 99 / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: rgb(15 23 42 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(203 213 225 / var(--tw-text-opacity)); + } + + input.form-entry:focus { + --tw-border-opacity: 1; + border-color: rgb(147 197 253 / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: rgb(30 41 59 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(226 232 240 / var(--tw-text-opacity)); + } +} + small.form-entry { margin-top: 0.25rem; display: block; @@ -3965,14 +3928,48 @@ small.form-entry { font-size: 0.75rem; line-height: 1rem; --tw-text-opacity: 1; - color: rgb(75 85 99 / var(--tw-text-opacity)); + color: rgb(71 85 105 / var(--tw-text-opacity)); } -button.save-btn { +@media (prefers-color-scheme: dark) { + small.form-entry { + --tw-text-opacity: 1; + color: rgb(148 163 184 / var(--tw-text-opacity)); + } +} + +a.link-primary { + margin-bottom: 1rem; + --tw-text-opacity: 1; + color: rgb(59 130 246 / var(--tw-text-opacity)); + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-duration: 300ms; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +a.link-primary:hover { + --tw-text-opacity: 1; + color: rgb(37 99 235 / var(--tw-text-opacity)); +} + +a.link-primary:active { + --tw-text-opacity: 1; + color: rgb(29 78 216 / var(--tw-text-opacity)); +} + +div.btn { + margin-top: 0.75rem; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + -moz-column-gap: 1.5rem; + column-gap: 1.5rem; +} + +button.btn { display: inline-block; border-radius: 0.25rem; - --tw-bg-opacity: 1; - background-color: rgb(37 99 235 / var(--tw-bg-opacity)); padding-left: 1.5rem; padding-right: 1.5rem; padding-top: 0.625rem; @@ -3983,7 +3980,7 @@ button.save-btn { text-transform: uppercase; line-height: 1.25; --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); + color: rgb(241 245 249 / var(--tw-text-opacity)); --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); @@ -3994,17 +3991,13 @@ button.save-btn { transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } -button.save-btn:hover { - --tw-bg-opacity: 1; - background-color: rgb(29 78 216 / var(--tw-bg-opacity)); +button.btn:hover { --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } -button.save-btn:focus { - --tw-bg-opacity: 1; - background-color: rgb(29 78 216 / var(--tw-bg-opacity)); +button.btn:focus { --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); @@ -4015,10 +4008,64 @@ button.save-btn:focus { box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); } -button.save-btn:active { - --tw-bg-opacity: 1; - background-color: rgb(30 64 175 / var(--tw-bg-opacity)); +button.btn:active { --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} \ No newline at end of file +} + +button.btn-primary { + --tw-bg-opacity: 1; + background-color: rgb(37 99 235 / var(--tw-bg-opacity)); +} + +button.btn-primary:hover { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity)); +} + +button.btn-primary:focus { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity)); +} + +button.btn-primary:active { + --tw-bg-opacity: 1; + background-color: rgb(30 58 138 / var(--tw-bg-opacity)); +} + +button.btn-secondary { + --tw-bg-opacity: 1; + background-color: rgb(71 85 105 / var(--tw-bg-opacity)); +} + +button.btn-secondary:hover { + --tw-bg-opacity: 1; + background-color: rgb(51 65 85 / var(--tw-bg-opacity)); +} + +button.btn-secondary:focus { + --tw-bg-opacity: 1; + background-color: rgb(51 65 85 / var(--tw-bg-opacity)); +} + +button.btn-secondary:active { + --tw-bg-opacity: 1; + background-color: rgb(15 23 42 / var(--tw-bg-opacity)); +} + +.hide { + display: none !important; +} + +@media (prefers-color-scheme: dark) { + .dark\:bg-slate-900 { + --tw-bg-opacity: 1; + background-color: rgb(15 23 42 / var(--tw-bg-opacity)); + } + + .dark\:bg-slate-800 { + --tw-bg-opacity: 1; + background-color: rgb(30 41 59 / var(--tw-bg-opacity)); + } +} diff --git a/ldr_config.py b/Source/loaderConfigurator/ldr_config.py similarity index 100% rename from ldr_config.py rename to Source/loaderConfigurator/ldr_config.py diff --git a/Source/loaderConfigurator/src/index.html b/Source/loaderConfigurator/src/index.html index 11c5d9cb..7daab2d8 100644 --- a/Source/loaderConfigurator/src/index.html +++ b/Source/loaderConfigurator/src/index.html @@ -1,292 +1,22 @@ - + + - +
    -
    - +
    + + Get latest here +
    + + +
    - - \ 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: {}, },