├── .github
├── ISSUE_TEMPLATE
│ └── bug_report.yaml
├── dependabot.yaml
└── workflows
│ └── deploy.yaml
├── .gitignore
├── IFR-Formatter
└── IFR-Formatter.js
├── README.md
├── eslint.config.js
├── images
├── extraction
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── 6.png
│ └── 7.png
├── insertion
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ └── 4.png
├── showcase
│ ├── 1.png
│ └── 2.png
└── usage
│ ├── 1.png
│ ├── 2.jpg
│ ├── 3.png
│ └── 4.png
├── index.html
├── package-lock.json
├── package.json
├── postcss.config.cjs
├── public
└── bios.png
├── src
├── App.module.css
├── App.tsx
├── components
│ ├── FileUploads
│ │ └── FileUploads.tsx
│ ├── Footer
│ │ ├── Footer.module.css
│ │ └── Footer.tsx
│ ├── FormUi
│ │ ├── FormUi.module.css
│ │ ├── FormUi.tsx
│ │ └── SearchUi
│ │ │ └── SearchUi.tsx
│ ├── Header
│ │ ├── Header.module.css
│ │ └── Header.tsx
│ ├── Navigation
│ │ ├── Navigation.module.css
│ │ └── Navigation.tsx
│ └── scripts
│ │ ├── hexWorker.ts
│ │ ├── scripts.ts
│ │ └── types.ts
├── main.tsx
└── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
/.github/ISSUE_TEMPLATE/bug_report.yaml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: Only use this form for bug reports.
3 | body:
4 | - type: input
5 | id: uefi-download
6 | attributes:
7 | label: Provide the official URL to the UEFI you tried to modify.
8 | description: I will only investigate official downloads from motherboard vendors.
9 | validations:
10 | required: true
11 | - type: dropdown
12 | id: is-modified
13 | attributes:
14 | label: Did you modify a previously modified UEFI or the stock one?
15 | options:
16 | - Previously modified
17 | - Stock
18 | validations:
19 | required: true
20 | - type: input
21 | id: flashing-method
22 | attributes:
23 | label: What flashing method did you use?
24 | description: E.g. motherboard's flashback feature
25 | validations:
26 | required: true
27 | - type: dropdown
28 | id: tested
29 | attributes:
30 | label: Did you successfully flash a stock UEFI with said method?
31 | options:
32 | - "No"
33 | - "Yes"
34 | validations:
35 | required: true
36 | - type: input
37 | id: setup-sct-sha256
38 | attributes:
39 | label: SHA256 hash of unmodified Setup SCT
40 | validations:
41 | required: true
42 | - type: input
43 | id: setup-txt-sha256
44 | attributes:
45 | label: SHA256 hash of IFR Extractor output TXT
46 | validations:
47 | required: true
48 | - type: input
49 | id: amitse-sct-sha256
50 | attributes:
51 | label: SHA256 hash of unmodified AMITSE SCT
52 | validations:
53 | required: true
54 | - type: input
55 | id: setupdata-bin-sha256
56 | attributes:
57 | label: SHA256 hash of unmodified Setupdata BIN
58 | validations:
59 | required: true
60 | - type: textarea
61 | id: data-json
62 | attributes:
63 | label: Attach the data.json file with the modification you attempted to do.
64 | description: Limit it to the problematic setting for easier debugging.
65 | validations:
66 | required: true
67 | - type: input
68 | id: final-rom-sha256
69 | attributes:
70 | label: SHA256 hash of modified UEFI
71 | validations:
72 | required: true
73 | - type: textarea
74 | id: expected-outcome
75 | attributes:
76 | label: Describe the expected outcome as detailed as possible.
77 | description: Attach images if necessary.
78 | validations:
79 | required: true
80 | - type: textarea
81 | id: actual-outcome
82 | attributes:
83 | label: Describe the actual outcome as detailed as possible.
84 | description: Attach images if necessary.
85 | validations:
86 | required: true
87 |
--------------------------------------------------------------------------------
/.github/dependabot.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yaml:
--------------------------------------------------------------------------------
1 | # Simple workflow for deploying static content to GitHub Pages
2 | name: Deploy static content to Pages
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | # push:
7 | # branches: ["master"]
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | # Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages
13 | permissions:
14 | contents: read
15 | pages: write
16 | id-token: write
17 |
18 | # Allow one concurrent deployment
19 | concurrency:
20 | group: "pages"
21 | cancel-in-progress: true
22 |
23 | jobs:
24 | # Single deploy job since we're just deploying
25 | deploy:
26 | environment:
27 | name: github-pages
28 | url: ${{ steps.deployment.outputs.page_url }}
29 | runs-on: ubuntu-latest
30 | steps:
31 | - name: Checkout
32 | uses: actions/checkout@v4
33 | - name: Set up Node
34 | uses: actions/setup-node@v4
35 | with:
36 | node-version: 20
37 | cache: "npm"
38 | - name: Install dependencies
39 | run: npm ci
40 | - name: Build
41 | run: npm run build
42 | - name: Setup Pages
43 | uses: actions/configure-pages@v5
44 | - name: Upload artifact
45 | uses: actions/upload-pages-artifact@v3
46 | with:
47 | # Upload dist folder
48 | path: "./dist"
49 | - name: Deploy to GitHub Pages
50 | id: deployment
51 | uses: actions/deploy-pages@v4
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/IFR-Formatter/IFR-Formatter.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const path = require("path");
3 |
4 | function returnForm(currentForm) {
5 | return `${" ".repeat(120)}${currentForm}\n`;
6 | }
7 |
8 | (async function () {
9 | const currentVersion = "0.1.2";
10 | const wantedIFRExtractorVersions = ["1.5.1", "1.6.0"];
11 |
12 | let script;
13 | let latestVersionMatch;
14 |
15 | try {
16 | script = await (
17 | await fetch(
18 | "https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/master/IFR-Formatter/IFR-Formatter.js"
19 | )
20 | ).text();
21 | } catch {
22 | // skip
23 | }
24 |
25 | if (script) {
26 | latestVersionMatch = script.match(
27 | /const currentVersion = "(\d).(\d).(\d)";/
28 | );
29 | }
30 |
31 | if (latestVersionMatch) {
32 | const [currentMajor, currentMinor, currentPatch] = currentVersion
33 | .split(".")
34 | .map((number) => parseInt(number, 10));
35 | const [, latestMajor, latestMinor, latestPatch] = latestVersionMatch.map(
36 | (number) => parseInt(number, 10)
37 | );
38 |
39 | if (
40 | currentMajor < latestMajor ||
41 | (currentMajor === latestMajor && currentMinor < latestMinor) ||
42 | (currentMajor === latestMajor &&
43 | currentMinor === latestMinor &&
44 | currentPatch < latestPatch)
45 | ) {
46 | fs.writeFileSync(process.argv[1], script);
47 |
48 | return console.log("The script has been updated. Run it again.");
49 | }
50 | }
51 |
52 | const filePath = process.argv[2];
53 | let file = fs.readFileSync(filePath, "utf8");
54 | let formattedFile = "";
55 |
56 | if (
57 | !wantedIFRExtractorVersions.some((version) =>
58 | file.includes(`Program version: ${version}`)
59 | )
60 | ) {
61 | return console.log(
62 | `Wrong IFRExtractor-RS version. Compatible versions: ${wantedIFRExtractorVersions.join(
63 | ", "
64 | )}.`
65 | );
66 | }
67 |
68 | if (!file.includes("Extraction mode: UEFI")) {
69 | return console.log("Only UEFI is supported.");
70 | }
71 |
72 | if (!/\{ .* \}/.test(file)) {
73 | return console.log(`Use the "verbose" option of IFRExtractor.`);
74 | }
75 |
76 | file = file.replaceAll(/[\r\n|\n|\r](?!0x[0-9A-F]{3})/g, "
");
77 |
78 | const varStores = {};
79 | let currentForm;
80 |
81 | for (const line of file.split("\n")) {
82 | const varStore = line.match(
83 | /VarStore Guid: (.*), VarStoreId: (.*), Size: (.*), Name: "(.*)" \{/
84 | );
85 | const form = line.match(/Form FormId: (.*), Title: "(.*)" \{ (.*) \}/);
86 | const string = line.match(
87 | /String Prompt: "(.*)", Help: "(.*)", QuestionFlags: (.*), QuestionId: (.*), VarStoreId: (.*), VarStoreInfo: (.*), MinSize: (.*), MaxSize: (.*), Flags: (.*) \{ (.*) \}/
88 | );
89 | const numeric = line.match(
90 | /Numeric Prompt: "(.*)", Help: "(.*)", QuestionFlags: (.*), QuestionId: (.*), VarStoreId: (.*), VarOffset: (.*), Flags: (.*), Size: (.*), Min: (.*), Max: (.*), Step: (.*) \{ (.*) \}/
91 | );
92 | const checkBox = line.match(
93 | /CheckBox Prompt: "(.*)", Help: "(.*)", QuestionFlags: (.*), QuestionId: (.*), VarStoreId: (.*), VarOffset: (.*), Flags: (.*) \{ (.*) \}/
94 | );
95 | const oneOf = line.match(
96 | /OneOf Prompt: "(.*)", Help: "(.*)", QuestionFlags: (.*), QuestionId: (.*), VarStoreId: (.*), VarOffset: (.*), Flags: (.*), Size: (.*), Min: (.*), Max: (.*), Step: (.*) \{ (.*) \}/
97 | );
98 | const oneOfOption = line.match(/OneOfOption Option: "(.*)" Value: (.*) \{/);
99 |
100 | if (varStore) {
101 | varStores[varStore[2]] = varStore[4];
102 | }
103 |
104 | if (form) {
105 | currentForm = form[2];
106 | }
107 |
108 | if (string) {
109 | formattedFile += `${returnForm(currentForm)}${string[1]}\n`;
110 | }
111 |
112 | if (numeric) {
113 | formattedFile += `${returnForm(currentForm)}${numeric[1]} | VarStore: ${
114 | varStores[numeric[5]]
115 | } | VarOffset: ${numeric[6]} | Size: 0x${(parseInt(numeric[8], 10) / 8)
116 | .toString(16)
117 | .toUpperCase()}\n Min: ${numeric[9]} | Max: ${numeric[10]} | Step: ${
118 | numeric[11]
119 | }\n`;
120 | }
121 |
122 | if (checkBox) {
123 | formattedFile += `${returnForm(currentForm)}${checkBox[1]} | VarStore: ${
124 | varStores[checkBox[5]]
125 | } | VarOffset: ${checkBox[6]}\n`;
126 | }
127 |
128 | if (oneOf) {
129 | formattedFile += `${returnForm(currentForm)}${oneOf[1]} | VarStore: ${
130 | varStores[oneOf[5]]
131 | } | VarOffset: ${oneOf[6]} | Size: 0x${(parseInt(oneOf[8], 10) / 8)
132 | .toString(16)
133 | .toUpperCase()}\n`;
134 | }
135 |
136 | if (oneOfOption) {
137 | formattedFile += ` ${oneOfOption[1]}: 0x${parseInt(
138 | oneOfOption[2].split(",")[0],
139 | 10
140 | )
141 | .toString(16)
142 | .toUpperCase()}\n`;
143 | }
144 | }
145 |
146 | fs.writeFileSync(
147 | path.join(path.dirname(filePath), `formatted_${path.basename(filePath)}`),
148 | formattedFile
149 | );
150 | })();
151 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Aptio V UEFI Editor](https://boringboredom.github.io/UEFI-Editor/)
2 |
3 | 
4 | 
5 |
6 | # Usage guide
7 |
8 | ## Prerequisites
9 |
10 | - [UEFITool NE](https://github.com/LongSoft/UEFITool/releases) (press `Show all assets`)
11 | - [UEFITool 0.28.0](https://github.com/LongSoft/UEFITool/releases/tag/0.28.0) ([why?](https://github.com/LongSoft/UEFITool#known-issues))
12 | - [IFRExtractor-RS](https://github.com/LongSoft/IFRExtractor-RS/releases)
13 | - [UEFI Editor](https://boringboredom.github.io/UEFI-Editor/)
14 |
15 | ## Extracting the necessary files
16 |
17 | - Drag and drop the BIOS file into `UEFITool NE`.
18 | - Search (`CTRL + F`) for a known setting.
19 |
20 | 
21 |
22 | - Double-click the reference to `Setup/PE32 image section` at the bottom.
23 |
24 | 
25 |
26 | - Extract `PE32 image section` `as is`.
27 |
28 | 
29 |
30 | - Move `ifrextractor.exe` to the current folder, open the CLI inside and convert the `.sct` file you just extracted.
31 |
32 | ```
33 | ifrextractor.exe "Section_PE32_image_Setup_Setup.sct" verbose
34 | ```
35 |
36 | 
37 |
38 | - Scroll down inside the currently expanded section and find `AMITSE` and `setupdata` (sometimes both required files are under `AMITSE`). Extract `PE32 image section` `as is` and `setupdata` as `body`.
39 |
40 | 
41 | 
42 |
43 | - Upload the 4 files to the `UEFI Editor` page.
44 |
45 | 
46 |
47 | ## Using the UEFI Editor GUI
48 |
49 | - ### Navigation
50 | - Dotted underlined text has references to Forms and can be clicked.
51 | - ### Menu
52 |
53 | - You can change the target Form of top-level references here. This is useful for UEFIs that have a custom `Advanced` Form.
54 |
55 | 
56 | 
57 |
58 | - E.g. on MSI boards, you can replace `OC Profiles` with `Advanced` (child of `Setup`) to gain access to a lot of Forms that are otherwise inaccessible due to missing references while still retaining access to `OC Profiles`. Press `ESC` after selecting `OC Profiles` to access `Setup`.
59 |
60 | - ### Item visibility control
61 |
62 | - Make sure the parent forms are visible when targeting a setting. Use the top-right navigation to travel upwards.
63 | - If one method doesn't work, try the other one. Using both at the same time can cause issues. It varies from UEFI to UEFI. Try modifying `Access Level` first.
64 | - #### Suppress If
65 |
66 | - A `Suppress If` opcode hides nested items if the condition is true. The presence of a `Suppress If` opcode doesn't always mean the condition is true. However, if it is, you can remove the suppression by unchecking the offset.
67 |
68 | 
69 |
70 | - #### Access level
71 |
72 | - Another method of controlling item visibility is changing the access level. `05` usually works. A different value does not necessarily mean it's hidden. [Here is a forum post by Lost_N_BIOS with possible access level values](https://winraid.level1techs.com/t/request-maximus-xi-hero-unlock-amibcp/33743/4) (`CTRL + F` `05/Yes`).
73 |
74 | 
75 |
76 | ## Inserting modified files
77 |
78 | - Press the `UEFI Files` download button to download the modified files and the change log.
79 | - To find the correct sections in `UEFITool 0.28.0` you can search for `File GUID`s you copy from `UEFITool NE`.
80 | - Replace files the same way you extracted them: `Extract as is` -> `Replace as is` and `Extract body` -> `Replace body`
81 |
82 | Example for `Setup/PE32 image section`:
83 |
84 | `UEFITool NE`:
85 |
86 | 
87 |
88 | `UEFITool 0.28.0`:
89 |
90 | 
91 | 
92 |
93 | - Save the modifications.
94 |
95 | 
96 |
97 | ---
98 |
99 | The section below is unrelated to the above tool.
100 |
101 | ---
102 |
103 | # How to change hidden settings without flashing a modded BIOS
104 |
105 | ## Preparation
106 |
107 | Download [datasone's modded shell](https://github.com/datasone/grub-mod-setup_var/releases) and rename it to `BOOTX64.EFI`.
108 |
109 | Format a USB drive as `FAT32` and move `BOOTX64.EFI` to `USB:\EFI\BOOT\` (create the folders `EFI` and `BOOT` manually). The final path of the shell will be `USB:\EFI\BOOT\BOOTX64.EFI`.
110 |
111 | Download your **_current_** BIOS version from the motherboard vendor's site. The structure changes across different versions, so make sure you have the **_same_** BIOS.
112 |
113 | Follow [these instructions](#extracting-the-necessary-files) until and including the conversion with `ifrextractor.exe`. If there are two `Setup` sections, use the one that has matching offsets (change settings in BIOS and read values with datasone's shell to confirm).
114 |
115 | Optionally, download [IFR-Formatter.js](https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/master/IFR-Formatter/IFR-Formatter.js) (right-click and `Save link as...`) and [node.exe](https://nodejs.org/dist/latest/win-x64/node.exe). Place them in the same folder as the IFR Extractor output and execute `node IFR-Formatter.js yourIfrExtractorOutput.txt` in the CLI.
116 |
117 | Disable `Secure Boot` and `CSM` and boot from the USB drive in UEFI mode.
118 |
119 | ## Example
120 |
121 | ### IFR Extractor output
122 |
123 | ```
124 | OneOf Prompt: "Intel C-State", Help: "[...]", QuestionFlags: [...], QuestionId: [...], VarStoreId: 0x2, VarOffset: 0x14, Flags: [...], Size: 8, Min: [...], Max: [...], Step: [...] { [...] }
125 | OneOfOption Option: "Auto" Value: 2, Default, MfgDefault { [...] }
126 | OneOfOption Option: "Enabled" Value: 1 { [...] }
127 | OneOfOption Option: "Disabled" Value: 0 { [...] }
128 | End { 29 02 }
129 | ```
130 |
131 | `Size` is a decimal in bits. Convert it to a hexadecimal in bytes.
132 | `Value` is a decimal. Convert it to a hexadecimal.
133 |
134 | Search for the `VarStoreId` to find the `VarStoreName`.
135 |
136 | ```
137 | VarStore Guid: [...], VarStoreId: 0x2, Size: [...], Name: "CpuSetup" { [...] }
138 | ```
139 |
140 | ### IFR-Formatter.js output
141 |
142 | ```
143 | Intel C-State | VarStore: CpuSetup | VarOffset: 0x14 | Size: 0x1
144 | Auto: 0x2
145 | Enabled: 0x1
146 | Disabled: 0x0
147 | ```
148 |
149 | ### [Syntax](https://github.com/datasone/grub-mod-setup_var#setup_var_cv) (READ THIS)
150 |
151 | #### Writing
152 |
153 | ```
154 | setup_var_cv VarStoreName VarOffset Size Value
155 | ```
156 |
157 | ```
158 | setup_var_cv CpuSetup 0x14 0x1 0x0
159 | ```
160 |
161 | #### Reading
162 |
163 | ```
164 | setup_var_cv VarStoreName VarOffset Size
165 | ```
166 |
167 | ```
168 | setup_var_cv CpuSetup 0x14 0x1
169 | ```
170 |
171 | ### Miscellaneous
172 |
173 | To exit and reboot, type:
174 |
175 | ```
176 | reboot
177 | ```
178 |
179 | ---
180 |
181 | Workarounds for various issues (e.g. multiple `Setup` `VarStores`): [legacy commands](https://github.com/datasone/grub-mod-setup_var#legacy-commands)
182 |
183 | ---
184 |
185 | If something unexpected happens, force shutdown and reset CMOS.
186 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js";
2 | import globals from "globals";
3 | import reactHooks from "eslint-plugin-react-hooks";
4 | import reactRefresh from "eslint-plugin-react-refresh";
5 | import tseslint from "typescript-eslint";
6 | import reactX from "eslint-plugin-react-x";
7 | import reactDom from "eslint-plugin-react-dom";
8 |
9 | export default tseslint.config(
10 | { ignores: ["dist"] },
11 | {
12 | extends: [
13 | js.configs.recommended,
14 | ...tseslint.configs.strictTypeChecked,
15 | ...tseslint.configs.stylisticTypeChecked,
16 | ],
17 | files: ["**/*.{ts,tsx}"],
18 | languageOptions: {
19 | ecmaVersion: 2020,
20 | globals: globals.browser,
21 | parserOptions: {
22 | project: ["./tsconfig.node.json", "./tsconfig.app.json"],
23 | tsconfigRootDir: import.meta.dirname,
24 | },
25 | },
26 | settings: { react: { version: "detect" } },
27 | plugins: {
28 | "react-hooks": reactHooks,
29 | "react-refresh": reactRefresh,
30 | "react-x": reactX,
31 | "react-dom": reactDom,
32 | },
33 | rules: {
34 | ...reactHooks.configs.recommended.rules,
35 | "react-refresh/only-export-components": [
36 | "warn",
37 | { allowConstantExport: true },
38 | ],
39 | ...reactX.configs["recommended-typescript"].rules,
40 | ...reactDom.configs.recommended.rules,
41 | },
42 | }
43 | );
44 |
--------------------------------------------------------------------------------
/images/extraction/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/extraction/1.png
--------------------------------------------------------------------------------
/images/extraction/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/extraction/2.png
--------------------------------------------------------------------------------
/images/extraction/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/extraction/3.png
--------------------------------------------------------------------------------
/images/extraction/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/extraction/4.png
--------------------------------------------------------------------------------
/images/extraction/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/extraction/5.png
--------------------------------------------------------------------------------
/images/extraction/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/extraction/6.png
--------------------------------------------------------------------------------
/images/extraction/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/extraction/7.png
--------------------------------------------------------------------------------
/images/insertion/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/insertion/1.png
--------------------------------------------------------------------------------
/images/insertion/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/insertion/2.png
--------------------------------------------------------------------------------
/images/insertion/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/insertion/3.png
--------------------------------------------------------------------------------
/images/insertion/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/insertion/4.png
--------------------------------------------------------------------------------
/images/showcase/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/showcase/1.png
--------------------------------------------------------------------------------
/images/showcase/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/showcase/2.png
--------------------------------------------------------------------------------
/images/usage/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/usage/1.png
--------------------------------------------------------------------------------
/images/usage/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/usage/2.jpg
--------------------------------------------------------------------------------
/images/usage/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/usage/3.png
--------------------------------------------------------------------------------
/images/usage/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/images/usage/4.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | UEFI Editor
9 |
13 |
14 |
15 |
19 |
23 |
24 |
25 |
26 |
30 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "uefi-editor",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc -b && vite build",
9 | "lint": "eslint .",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@mantine/core": "7.17.2",
14 | "@mantine/hooks": "7.17.2",
15 | "@tabler/icons-react": "^3.0.0",
16 | "crypto-js": "^4.1.1",
17 | "file-saver": "^2.0.5",
18 | "immer": "^10.0.3",
19 | "react": "^19",
20 | "react-dom": "^19",
21 | "use-immer": "^0.11"
22 | },
23 | "devDependencies": {
24 | "@eslint/js": "^9.20.0",
25 | "@types/crypto-js": "^4.2.1",
26 | "@types/file-saver": "^2.0.5",
27 | "@types/react": "^19",
28 | "@types/react-dom": "^19",
29 | "@vitejs/plugin-react": "^4.2.1",
30 | "eslint": "^9",
31 | "eslint-plugin-react-dom": "^1.40.1",
32 | "eslint-plugin-react-hooks": "^5",
33 | "eslint-plugin-react-refresh": "^0.4.5",
34 | "eslint-plugin-react-x": "^1.40.1",
35 | "globals": "^16",
36 | "postcss": "^8.4.33",
37 | "postcss-preset-mantine": "^1.12.3",
38 | "postcss-simple-vars": "^7.0.1",
39 | "typescript": "~5.7.2",
40 | "typescript-eslint": "^8.24.1",
41 | "vite": "^6",
42 | "vite-plugin-checker": "^0.9"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | "postcss-preset-mantine": {},
4 | "postcss-simple-vars": {
5 | variables: {
6 | "mantine-breakpoint-xs": "36em",
7 | "mantine-breakpoint-sm": "48em",
8 | "mantine-breakpoint-md": "62em",
9 | "mantine-breakpoint-lg": "75em",
10 | "mantine-breakpoint-xl": "88em",
11 | },
12 | },
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/public/bios.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BoringBoredom/UEFI-Editor/f28cf867a0bff16a70a2f46f9b55f94365718e97/public/bios.png
--------------------------------------------------------------------------------
/src/App.module.css:
--------------------------------------------------------------------------------
1 | .padding {
2 | padding: 3vw;
3 | }
4 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import s from "./App.module.css";
3 | import { useImmer } from "use-immer";
4 | import { AppShell, Button, Group, Stack } from "@mantine/core";
5 | import type { Data } from "./components/scripts/types";
6 | import FileUploads, {
7 | type Files,
8 | type PopulatedFiles,
9 | } from "./components/FileUploads/FileUploads";
10 | import FormUi from "./components/FormUi/FormUi";
11 | import Navigation from "./components/Navigation/Navigation";
12 | import Header from "./components/Header/Header";
13 | import Footer from "./components/Footer/Footer";
14 | import { IconBrandGithub } from "@tabler/icons-react";
15 |
16 | export default function App() {
17 | const [files, setFiles] = useImmer({
18 | setupSctContainer: { isWrongFile: false },
19 | setupTxtContainer: { isWrongFile: false },
20 | amitseSctContainer: { isWrongFile: false },
21 | setupdataBinContainer: { isWrongFile: false },
22 | });
23 |
24 | const [data, setData] = useImmer({} as Data);
25 |
26 | const [currentFormIndex, setCurrentFormIndex] = React.useState(-1);
27 |
28 | return (
29 | <>
30 | {Object.values(data).length !== 0 ? (
31 | <>
32 |
33 |
38 |
39 |
40 |
45 |
46 |
47 |
53 |
54 |
55 |
61 |
62 | >
63 | ) : (
64 |
65 |
66 |
67 | }
74 | >
75 | Usage guide
76 |
77 | }
84 | >
85 | Report a bug
86 |
87 |
88 |
89 | )}
90 | >
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/src/components/FileUploads/FileUploads.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import type { Updater } from "use-immer";
3 | import { FileInput, Stack, LoadingOverlay } from "@mantine/core";
4 | import { IconUpload } from "@tabler/icons-react";
5 | import { parseData } from "../scripts/scripts";
6 | import type { Data } from "../scripts/types";
7 | const hexWorker = () =>
8 | new Worker(new URL("../scripts/hexWorker.ts", import.meta.url));
9 |
10 | export interface Files {
11 | setupSctContainer: FileContainer;
12 | setupTxtContainer: FileContainer;
13 | amitseSctContainer: FileContainer;
14 | setupdataBinContainer: FileContainer;
15 | }
16 |
17 | export interface PopulatedFiles {
18 | setupSctContainer: Required;
19 | setupTxtContainer: Required;
20 | amitseSctContainer: Required;
21 | setupdataBinContainer: Required;
22 | }
23 |
24 | export interface FileContainer {
25 | file?: File;
26 | textContent?: string;
27 | isWrongFile: boolean;
28 | }
29 |
30 | export interface FileUploadsProps {
31 | files: Files;
32 | setFiles: Updater;
33 | setData: Updater;
34 | }
35 |
36 | export default function FileUploads({
37 | files,
38 | setFiles,
39 | setData,
40 | }: FileUploadsProps) {
41 | React.useEffect(() => {
42 | if (
43 | files.setupSctContainer.file &&
44 | !files.setupSctContainer.isWrongFile &&
45 | files.setupTxtContainer.file &&
46 | !files.setupTxtContainer.isWrongFile &&
47 | files.amitseSctContainer.file &&
48 | !files.amitseSctContainer.isWrongFile &&
49 | files.setupdataBinContainer.file &&
50 | !files.setupdataBinContainer.isWrongFile
51 | ) {
52 | if (
53 | Object.values(files).every(
54 | (fileContainer: FileContainer) => !fileContainer.textContent
55 | )
56 | ) {
57 | void Promise.all([
58 | files.setupTxtContainer.file.text(),
59 | ...[
60 | files.setupSctContainer.file,
61 | files.amitseSctContainer.file,
62 | files.setupdataBinContainer.file,
63 | ].map((file) => {
64 | return new Promise((resolve) => {
65 | const worker = hexWorker();
66 | worker.onmessage = (e: MessageEvent) => {
67 | resolve(e.data);
68 | };
69 | worker.postMessage(file);
70 | });
71 | }),
72 | ]).then((values) => {
73 | setFiles((draft) => {
74 | draft.setupTxtContainer.textContent = values[0];
75 | draft.setupSctContainer.textContent = values[1];
76 | draft.amitseSctContainer.textContent = values[2];
77 | draft.setupdataBinContainer.textContent = values[3];
78 | });
79 | });
80 | } else {
81 | void parseData(files as PopulatedFiles).then((data) => {
82 | setData(data);
83 | });
84 | }
85 | }
86 | }, [files, setFiles, setData]);
87 |
88 | return (
89 | <>
90 |
105 |
106 | }
108 | size="lg"
109 | placeholder="Setup SCT"
110 | accept=".sct"
111 | value={files.setupSctContainer.file}
112 | error={files.setupSctContainer.isWrongFile}
113 | onChange={(file) => {
114 | if (file) {
115 | const name = file.name.toLowerCase();
116 |
117 | setFiles((draft) => {
118 | draft.setupSctContainer = {
119 | file,
120 | isWrongFile: !(
121 | name.includes("setup") && name.endsWith(".sct")
122 | ),
123 | };
124 | });
125 | }
126 | }}
127 | />
128 |
129 | }
131 | size="lg"
132 | placeholder="IFR Extractor output TXT"
133 | accept=".txt"
134 | value={files.setupTxtContainer.file}
135 | error={files.setupTxtContainer.isWrongFile}
136 | onChange={(file) => {
137 | if (file) {
138 | const name = file.name.toLowerCase();
139 |
140 | setFiles((draft) => {
141 | draft.setupTxtContainer = {
142 | file,
143 | isWrongFile: !(name.includes("ifr") && name.endsWith(".txt")),
144 | };
145 | });
146 | }
147 | }}
148 | />
149 |
150 | }
152 | size="lg"
153 | placeholder="AMITSE SCT"
154 | accept=".sct"
155 | value={files.amitseSctContainer.file}
156 | error={files.amitseSctContainer.isWrongFile}
157 | onChange={(file) => {
158 | if (file) {
159 | const name = file.name.toLowerCase();
160 |
161 | setFiles((draft) => {
162 | draft.amitseSctContainer = {
163 | file,
164 | isWrongFile: !(
165 | name.includes("amitse") && name.endsWith(".sct")
166 | ),
167 | };
168 | });
169 | }
170 | }}
171 | />
172 |
173 | }
175 | size="lg"
176 | placeholder="Setupdata BIN"
177 | accept=".bin"
178 | value={files.setupdataBinContainer.file}
179 | error={files.setupdataBinContainer.isWrongFile}
180 | onChange={(file) => {
181 | if (file) {
182 | const name = file.name.toLowerCase();
183 |
184 | setFiles((draft) => {
185 | draft.setupdataBinContainer = {
186 | file,
187 | isWrongFile: !(
188 | name.includes("setupdata") && name.endsWith(".bin")
189 | ),
190 | };
191 | });
192 | }
193 | }}
194 | />
195 |
196 | >
197 | );
198 | }
199 |
--------------------------------------------------------------------------------
/src/components/Footer/Footer.module.css:
--------------------------------------------------------------------------------
1 | .root {
2 | padding: 0 1rem;
3 | display: flex;
4 | align-items: center;
5 | height: 100%;
6 | }
7 | .textInput {
8 | width: 3rem;
9 | min-width: 3rem;
10 | }
11 | .maxWidth {
12 | width: 100%;
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/Footer/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import s from "./Footer.module.css";
3 | import { Button, Group, TextInput, FileButton } from "@mantine/core";
4 | import { IconDownload, IconUpload } from "@tabler/icons-react";
5 | import { saveAs } from "file-saver";
6 | import type { Updater } from "use-immer";
7 | import {
8 | validateByteInput,
9 | calculateJsonChecksum,
10 | downloadModifiedFiles,
11 | version,
12 | } from "../scripts/scripts";
13 | import type { Data, Suppression } from "../scripts/types";
14 | import type { PopulatedFiles } from "../FileUploads/FileUploads";
15 |
16 | interface FooterProps {
17 | files: PopulatedFiles;
18 | data: Data;
19 | setData: Updater;
20 | currentFormIndex: number;
21 | }
22 |
23 | export default function Footer({
24 | files,
25 | currentFormIndex,
26 | data,
27 | setData,
28 | }: FooterProps) {
29 | const resetRef = React.useRef<() => void>(null);
30 | const [input, setInput] = React.useState("05");
31 |
32 | return (
33 |
34 |
35 |
36 | {
40 | if (file) {
41 | void file.text().then((fileData) => {
42 | const jsonData = JSON.parse(fileData) as Data;
43 |
44 | if (
45 | jsonData.version === version &&
46 | jsonData.hashes.setupTxt === data.hashes.setupTxt &&
47 | jsonData.hashes.setupSct === data.hashes.setupSct &&
48 | jsonData.hashes.amitseSct === data.hashes.amitseSct &&
49 | jsonData.hashes.setupdataBin === data.hashes.setupdataBin &&
50 | calculateJsonChecksum(
51 | jsonData.menu,
52 | jsonData.forms,
53 | jsonData.suppressions
54 | ) === data.hashes.offsetChecksum
55 | ) {
56 | setData(jsonData);
57 | } else {
58 | alert("Wrong JSON version or file hashes.");
59 | }
60 |
61 | resetRef.current?.();
62 | });
63 | }
64 | }}
65 | >
66 | {(props) => (
67 | }
71 | variant="default"
72 | >
73 | data.json
74 |
75 | )}
76 |
77 |
78 | }
82 | onClick={() => {
83 | saveAs(
84 | new Blob([JSON.stringify(data, null, 2)], {
85 | type: "text/plain",
86 | }),
87 | "data.json"
88 | );
89 | }}
90 | >
91 | data.json
92 |
93 |
94 | }
98 | onClick={() => {
99 | void downloadModifiedFiles(data, files);
100 | }}
101 | >
102 | UEFI files
103 |
104 |
105 |
106 | {currentFormIndex >= 0 && (
107 |
108 |
130 |
131 |
146 |
147 | {
152 | const value = ev.target.value.toUpperCase();
153 |
154 | if (validateByteInput(value)) {
155 | setInput(value);
156 | }
157 | }}
158 | />
159 |
160 | )}
161 |
162 |
163 | );
164 | }
165 |
--------------------------------------------------------------------------------
/src/components/FormUi/FormUi.module.css:
--------------------------------------------------------------------------------
1 | .width {
2 | width: 5rem;
3 | min-width: 5rem;
4 | }
5 | .pointer {
6 | cursor: pointer;
7 | text-decoration: underline dotted;
8 | }
9 | .formIdWidth {
10 | width: 50%;
11 | }
12 | .formIdChildWidth {
13 | width: 7rem;
14 | min-width: 7rem;
15 | }
16 | .infoRow {
17 | display: flex;
18 | }
19 | .infoRow > div {
20 | width: 50%;
21 | }
22 | .memoRow:not(:last-of-type) {
23 | border-bottom: calc(0.0625rem * 1) solid #373a40;
24 | }
25 | .memoRow > td {
26 | padding: var(--table-vertical-spacing)
27 | var(--table-horizontal-spacing, var(--mantine-spacing-xs));
28 | }
29 | .memoRow > td:not(:last-child) {
30 | border-right: calc(0.0625rem * 1) solid #373a40;
31 | }
32 | .striped > tr:nth-child(odd) {
33 | background-color: #25262b;
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/FormUi/FormUi.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import s from "./FormUi.module.css";
3 | import type { Updater } from "use-immer";
4 | import {
5 | Table,
6 | TextInput,
7 | NativeSelect,
8 | Spoiler,
9 | Chip,
10 | Stack,
11 | Group,
12 | } from "@mantine/core";
13 | import { useDebouncedState } from "@mantine/hooks";
14 | import type { Data, FormChildren } from "../scripts/types";
15 | import { validateByteInput } from "../scripts/scripts";
16 | import SearchUi from "./SearchUi/SearchUi";
17 |
18 | interface SuppressionChipProps {
19 | suppressionOffset: string;
20 | data: Data;
21 | setData: Updater;
22 | }
23 |
24 | function SuppressionChip({
25 | suppressionOffset,
26 | data,
27 | setData,
28 | }: SuppressionChipProps) {
29 | const suppressionIndex = data.suppressions.findIndex(
30 | (suppression) => suppression.offset === suppressionOffset
31 | );
32 |
33 | const suppression = data.suppressions[suppressionIndex];
34 |
35 | return (
36 | {
42 | setData((draft) => {
43 | draft.suppressions[suppressionIndex].active = !suppression.active;
44 | });
45 | }}
46 | >
47 | {suppressionOffset}
48 |
49 | );
50 | }
51 |
52 | interface TableRowProps {
53 | child: FormChildren;
54 | index: number;
55 | handleRefClick: (formId: string) => void;
56 | data: Data;
57 | setData: Updater;
58 | currentFormIndex: number;
59 | }
60 |
61 | const TableRow = React.memo(
62 | function TableRow({
63 | child,
64 | index,
65 | handleRefClick,
66 | data,
67 | setData,
68 | currentFormIndex,
69 | }: TableRowProps) {
70 | const type = child.type;
71 | const info = [];
72 |
73 | if (type === "CheckBox" || type === "OneOf" || type === "Numeric") {
74 | if (type === "OneOf") {
75 | for (const option of child.options) {
76 | info.push([option.option, option.value]);
77 | }
78 |
79 | info.push(["newline"]);
80 | }
81 |
82 | if (type === "Numeric") {
83 | info.push(
84 | ["Min", child.min],
85 | ["Max", child.max],
86 | ["Step", child.step],
87 | ["newline"]
88 | );
89 | }
90 |
91 | if (child.defaults) {
92 | for (const def of child.defaults) {
93 | info.push([`DefaultId ${def.defaultId}`, def.value]);
94 | }
95 |
96 | if (type !== "CheckBox") {
97 | info.push(["newline"]);
98 | }
99 | }
100 |
101 | if (type === "CheckBox") {
102 | const def = /\bDefault: (Enabled|Disabled)/.exec(child.flags);
103 | if (def) {
104 | info.push(["Default", def[1] === "Enabled" ? "1" : "0"]);
105 | }
106 |
107 | const mfgDef = /MfgDefault: (Enabled|Disabled)/.exec(child.flags);
108 | if (mfgDef) {
109 | info.push(["MfgDefault", mfgDef[1] === "Enabled" ? "1" : "0"]);
110 | }
111 |
112 | if (def ?? mfgDef ?? child.defaults) {
113 | info.push(["newline"]);
114 | }
115 | }
116 |
117 | info.push(
118 | ["QuestionId", child.questionId],
119 | ["VarStoreId", child.varStoreId],
120 | ["VarStoreName", child.varStoreName],
121 | ["VarOffset", child.varOffset]
122 | );
123 |
124 | if (type !== "CheckBox") {
125 | info.push(["Size (bits)", child.size]);
126 | }
127 | }
128 |
129 | return (
130 |
131 | {
134 | if (type === "Ref") {
135 | handleRefClick(child.formId);
136 | }
137 | }}
138 | >
139 | {child.name}
140 | |
141 | {type} |
142 |
143 | {child.accessLevel !== null && (
144 | {
147 | const value = ev.target.value.toUpperCase();
148 |
149 | if (validateByteInput(value)) {
150 | setData((draft) => {
151 | draft.forms[currentFormIndex].children[index].accessLevel =
152 | value;
153 | });
154 | }
155 | }}
156 | />
157 | )}
158 | |
159 |
160 | {child.failsafe !== null && (
161 | {
164 | const value = ev.target.value.toUpperCase();
165 |
166 | if (validateByteInput(value)) {
167 | setData((draft) => {
168 | draft.forms[currentFormIndex].children[index].failsafe =
169 | value;
170 | });
171 | }
172 | }}
173 | />
174 | )}
175 | |
176 |
177 | {child.optimal !== null && (
178 | {
181 | const value = ev.target.value.toUpperCase();
182 |
183 | if (validateByteInput(value)) {
184 | setData((draft) => {
185 | draft.forms[currentFormIndex].children[index].optimal =
186 | value;
187 | });
188 | }
189 | }}
190 | />
191 | )}
192 | |
193 |
194 |
195 | {child.suppressIf?.map((suppressionOffset, index) => (
196 |
202 | ))}
203 |
204 | |
205 |
206 |
212 |
213 | {child.description && (
214 |
215 | {child.description
216 | .split(" ")
217 | .filter((line) => line !== "")
218 | .map((line, index) => (
219 |
220 | {line}
221 |
222 | ))}
223 |
224 | )}
225 | {info.length > 0 && (
226 |
227 | {info.map((item, index) => (
228 |
232 | {item[0] === "newline" ? (
233 |
234 | ) : (
235 | <>
236 | {item[0]}
237 | {item[1]}
238 | >
239 | )}
240 |
241 | ))}
242 |
243 | )}
244 |
245 |
246 | |
247 |
248 | );
249 | },
250 | (oldProps: TableRowProps, newProps: TableRowProps) => {
251 | const oldChild =
252 | oldProps.data.forms[oldProps.currentFormIndex].children[oldProps.index];
253 | const newChild =
254 | newProps.data.forms[newProps.currentFormIndex].children[newProps.index];
255 |
256 | return (
257 | oldChild.accessLevel === newChild.accessLevel &&
258 | oldChild.failsafe === newChild.failsafe &&
259 | oldChild.optimal === newChild.optimal &&
260 | JSON.stringify(
261 | oldChild.suppressIf?.map(
262 | (offset) =>
263 | oldProps.data.suppressions.find(
264 | (suppression) => suppression.offset === offset
265 | )?.active
266 | )
267 | ) ===
268 | JSON.stringify(
269 | newChild.suppressIf?.map(
270 | (offset) =>
271 | newProps.data.suppressions.find(
272 | (suppression) => suppression.offset === offset
273 | )?.active
274 | )
275 | )
276 | );
277 | }
278 | );
279 |
280 | interface FormUiProps {
281 | data: Data;
282 | setData: Updater;
283 | currentFormIndex: number;
284 | setCurrentFormIndex: React.Dispatch>;
285 | }
286 |
287 | export default function FormUi({
288 | data,
289 | setData,
290 | currentFormIndex,
291 | setCurrentFormIndex,
292 | }: FormUiProps) {
293 | const [search, setSearch] = useDebouncedState("", 200);
294 |
295 | function handleRefClick(formId: string) {
296 | const formIndex = data.forms.findIndex(
297 | (form) => parseInt(form.formId) === parseInt(formId)
298 | );
299 |
300 | if (formIndex >= 0) {
301 | setCurrentFormIndex(formIndex);
302 |
303 | document.getElementById(`nav-${formIndex.toString()}`)?.scrollIntoView();
304 | }
305 | }
306 |
307 | if (currentFormIndex === -2) {
308 | return (
309 |
315 | );
316 | }
317 |
318 | if (currentFormIndex === -1) {
319 | return (
320 |
321 |
322 |
323 | Name
324 | Form Id
325 |
326 |
327 |
328 | {data.menu.map((entry, index) => (
329 |
330 | {
333 | handleRefClick(entry.formId);
334 | }}
335 | >
336 | {entry.name}
337 |
338 |
339 | form.formId)}
343 | onChange={(ev) => {
344 | const value = ev.target.value;
345 |
346 | setData((draft) => {
347 | draft.menu[index].formId = value;
348 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
349 | draft.menu[index].name = data.forms.find(
350 | (form) => parseInt(form.formId) === parseInt(value)
351 | )?.name!;
352 | });
353 | }}
354 | />
355 |
356 |
357 | ))}
358 |
359 |
360 | );
361 | }
362 |
363 | return (
364 |
365 |
366 |
367 | Name
368 | Type
369 | Access Level
370 | Failsafe
371 | Optimal
372 | Suppress If
373 | Info
374 |
375 |
376 |
377 | {data.forms[currentFormIndex].children.map((child, index) => (
378 |
387 | ))}
388 |
389 |
390 | );
391 | }
392 |
--------------------------------------------------------------------------------
/src/components/FormUi/SearchUi/SearchUi.tsx:
--------------------------------------------------------------------------------
1 | import s from "../FormUi.module.css";
2 | import { useFocusTrap } from "@mantine/hooks";
3 | import type { Data } from "../../scripts/types";
4 | import { Stack, Table, TextInput } from "@mantine/core";
5 | import { IconSearch } from "@tabler/icons-react";
6 |
7 | interface SearchUiProps {
8 | data: Data;
9 | handleRefClick: (formId: string) => void;
10 | search: string;
11 | setSearch: (newValue: string) => void;
12 | }
13 |
14 | export default function SearchUi({
15 | data,
16 | handleRefClick,
17 | search,
18 | setSearch,
19 | }: SearchUiProps) {
20 | const focusTrapRef = useFocusTrap();
21 |
22 | const found: {
23 | type: string;
24 | formId: string;
25 | formName: string;
26 | name: string;
27 | }[] = [];
28 |
29 | if (search.length >= 2) {
30 | for (const form of data.forms) {
31 | if (form.name.toLowerCase().includes(search)) {
32 | found.push({
33 | type: "Form",
34 | formId: form.formId,
35 | formName: form.name,
36 | name: form.name,
37 | });
38 | }
39 |
40 | for (const setting of form.children) {
41 | if (setting.name.toLowerCase().includes(search)) {
42 | found.push({
43 | type: setting.type,
44 | formId: form.formId,
45 | formName: form.name,
46 | name: setting.name,
47 | });
48 | }
49 | }
50 | }
51 | }
52 |
53 | return (
54 |
55 | }
60 | onChange={(ev) => {
61 | setSearch(ev.target.value.toLowerCase());
62 | }}
63 | />
64 |
65 |
66 |
67 |
68 | Name
69 | Type
70 | Form Name
71 | Form Id
72 |
73 |
74 |
75 | {found.map((entry, index) => (
76 |
79 | {entry.name}
80 | {entry.type}
81 | {
84 | handleRefClick(entry.formId);
85 | }}
86 | >
87 | {entry.formName}
88 |
89 | {entry.formId}
90 |
91 | ))}
92 |
93 |
94 |
95 | );
96 | }
97 |
--------------------------------------------------------------------------------
/src/components/Header/Header.module.css:
--------------------------------------------------------------------------------
1 | .root {
2 | padding: 0 1rem;
3 | font-size: 2rem;
4 | display: flex;
5 | align-items: center;
6 | justify-content: right;
7 | height: 100%;
8 | }
9 | .pointer {
10 | cursor: pointer;
11 | text-decoration: underline dotted;
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/Header/Header.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Group } from "@mantine/core";
3 | import s from "./Header.module.css";
4 | import type { Data } from "../scripts/types";
5 |
6 | interface HeaderProps {
7 | data: Data;
8 | currentFormIndex: number;
9 | setCurrentFormIndex: React.Dispatch>;
10 | }
11 |
12 | export default function Header({
13 | data,
14 | currentFormIndex,
15 | setCurrentFormIndex,
16 | }: HeaderProps) {
17 | const currentForm = data.forms[currentFormIndex];
18 |
19 | return (
20 | <>
21 | {currentFormIndex >= 0 && (
22 |
23 |
24 | {currentForm.referencedIn.length > 0 && (
25 | <>
26 | {currentForm.referencedIn.map((formId) => (
27 | {
31 | const formIndex = data.forms.findIndex(
32 | (form) => parseInt(form.formId) === parseInt(formId)
33 | );
34 |
35 | if (formIndex >= 0) {
36 | setCurrentFormIndex(formIndex);
37 |
38 | document
39 | .getElementById(`nav-${formIndex.toString()}`)
40 | ?.scrollIntoView();
41 | }
42 | }}
43 | >
44 | {
45 | data.forms.find(
46 | (form) => parseInt(form.formId) === parseInt(formId)
47 | )?.name
48 | }
49 |
50 | ))}
51 | {">"}
52 | >
53 | )}
54 | {currentForm.name}
55 |
56 |
57 | )}
58 | >
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/Navigation/Navigation.module.css:
--------------------------------------------------------------------------------
1 | .navElement {
2 | padding: 1rem;
3 | cursor: pointer;
4 | }
5 | .navElement:hover {
6 | background-color: var(--mantine-color-dark-6);
7 | }
8 | .selected {
9 | background-color: rgba(34, 139, 230, 0.15);
10 | color: #74c0fc;
11 | }
12 | .menu {
13 | border-bottom: 1px solid var(--app-shell-border-color);
14 | }
15 | .search {
16 | border-top: 1px solid var(--app-shell-border-color);
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/Navigation/Navigation.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import s from "./Navigation.module.css";
3 | import { NavLink, AppShell, ScrollArea } from "@mantine/core";
4 | import type { Data } from "../scripts/types";
5 |
6 | interface NavigationProps {
7 | data: Data;
8 | currentFormIndex: number;
9 | setCurrentFormIndex: React.Dispatch>;
10 | }
11 |
12 | const Navigation = React.memo(
13 | function Navigation({
14 | data,
15 | currentFormIndex,
16 | setCurrentFormIndex,
17 | }: NavigationProps) {
18 | return (
19 | <>
20 | {
27 | setCurrentFormIndex(-1);
28 | }}
29 | >
30 | Menu
31 |
32 |
33 | {data.forms.map((form, index) => (
34 | {
40 | setCurrentFormIndex(index);
41 | }}
42 | />
43 | ))}
44 |
45 | {
52 | setCurrentFormIndex(-2);
53 | }}
54 | >
55 | Search
56 |
57 | >
58 | );
59 | },
60 | (oldProps: NavigationProps, newProps: NavigationProps) =>
61 | oldProps.currentFormIndex === newProps.currentFormIndex
62 | );
63 |
64 | export default Navigation;
65 |
--------------------------------------------------------------------------------
/src/components/scripts/hexWorker.ts:
--------------------------------------------------------------------------------
1 | onmessage = async (e: MessageEvent) => {
2 | postMessage(
3 | [...new Uint8Array(await e.data.arrayBuffer())]
4 | .map((x) => x.toString(16).toUpperCase().padStart(2, "0"))
5 | .join("")
6 | );
7 | };
8 |
--------------------------------------------------------------------------------
/src/components/scripts/scripts.ts:
--------------------------------------------------------------------------------
1 | import { saveAs } from "file-saver";
2 | import type { PopulatedFiles } from "../FileUploads/FileUploads";
3 | import sha256 from "crypto-js/sha256";
4 | import type {
5 | Data,
6 | Forms,
7 | Form,
8 | RefPrompt,
9 | NumericPrompt,
10 | CheckBoxPrompt,
11 | OneOfPrompt,
12 | VarStores,
13 | Scopes,
14 | Menu,
15 | Offsets,
16 | FormChildren,
17 | StringPrompt,
18 | Suppression,
19 | } from "./types";
20 |
21 | export const version = "0.0.8";
22 | const wantedIFRExtractorVersions = ["1.5.1", "1.6.0"];
23 |
24 | export function validateByteInput(value: string) {
25 | return (
26 | value.length <= 2 &&
27 | (value.length === 0 ||
28 | value.split("").every((char) => /[a-fA-F0-9]/.test(char)))
29 | );
30 | }
31 |
32 | function hasScope(hexString: string) {
33 | const header = hexString.split(" ")[1];
34 |
35 | return parseInt(header, 16).toString(2).padStart(8, "0").startsWith("1");
36 | }
37 |
38 | export function calculateJsonChecksum(
39 | menu: Menu,
40 | forms: Forms,
41 | suppressions: Suppression[]
42 | ) {
43 | let offsetChecksum = "";
44 |
45 | for (const menuItem of menu) {
46 | offsetChecksum += menuItem.offset;
47 | }
48 |
49 | for (const form of forms) {
50 | for (const child of form.children) {
51 | offsetChecksum += JSON.stringify(child.offsets);
52 | }
53 | }
54 |
55 | for (const suppression of suppressions) {
56 | offsetChecksum += suppression.offset + suppression.start + suppression.end;
57 | }
58 |
59 | return sha256(offsetChecksum).toString();
60 | }
61 |
62 | function replaceAt(
63 | string: string,
64 | index: number,
65 | length: number,
66 | replacement: string
67 | ) {
68 | return string.slice(0, index) + replacement + string.slice(index + length);
69 | }
70 |
71 | function offsetToIndex(offset: string) {
72 | return parseInt(offset, 16) * 2;
73 | }
74 |
75 | function decToHexString(decimal: number) {
76 | return `0x${decimal.toString(16).toUpperCase()}`;
77 | }
78 |
79 | function checkSuppressions(scopes: Scopes, formChild: FormChildren) {
80 | const suppressions = scopes
81 | .filter((scope) => scope.type === "SuppressIf")
82 | .map((scope) => scope.offset) as string[];
83 |
84 | if (suppressions.length !== 0) {
85 | formChild.suppressIf = [...suppressions];
86 | }
87 | }
88 |
89 | function getAdditionalData(
90 | bytes: string,
91 | hexSetupdataBin: string,
92 | isRef: boolean
93 | ): {
94 | pageId: string | null;
95 | accessLevel: string | null;
96 | failsafe: string | null;
97 | optimal: string | null;
98 | offsets: Offsets | null;
99 | } {
100 | const byteArray = bytes.split(" ");
101 | const regex = new RegExp(
102 | byteArray[6] +
103 | byteArray[7] +
104 | ".{20}(....).{4}(..).{6}" +
105 | byteArray[4] +
106 | byteArray[5] +
107 | ".{52}" +
108 | byteArray[2] +
109 | byteArray[3] +
110 | ".{4}(..)(..)",
111 | "g"
112 | );
113 |
114 | const matches = [...hexSetupdataBin.matchAll(regex)].filter(
115 | (element) => element.index % 2 === 0
116 | );
117 |
118 | if (matches.length === 1) {
119 | const match = matches[0];
120 | const index = match.index;
121 |
122 | const offsets: Offsets = {
123 | accessLevel: decToHexString((index + 32) / 2),
124 | failsafe: decToHexString((index + 104) / 2),
125 | optimal: decToHexString((index + 106) / 2),
126 | };
127 |
128 | if (isRef) {
129 | offsets.pageId = decToHexString((index + 24) / 2);
130 | }
131 |
132 | return {
133 | pageId: match[1],
134 | accessLevel: match[2],
135 | failsafe: match[3],
136 | optimal: match[4],
137 | offsets,
138 | };
139 | }
140 |
141 | return {
142 | pageId: null,
143 | accessLevel: null,
144 | failsafe: null,
145 | optimal: null,
146 | offsets: null,
147 | };
148 | }
149 |
150 | function getUint8Array(string: string) {
151 | const array = [];
152 | for (let i = 0, len = string.length; i < len; i += 2) {
153 | array[i / 2] = parseInt(string.slice(i, i + 2), 16);
154 | }
155 |
156 | return array;
157 | }
158 |
159 | export async function downloadModifiedFiles(data: Data, files: PopulatedFiles) {
160 | let wasSetupSctModified = false;
161 | let wasAmitseSctModified = false;
162 | let wasSetupdataBinModified = false;
163 |
164 | let changeLog = "";
165 |
166 | let modifiedSetupSct = files.setupSctContainer.textContent;
167 | let setupSctChangeLog = "";
168 |
169 | const suppressions = JSON.parse(
170 | JSON.stringify(data.suppressions)
171 | ) as Suppression[];
172 |
173 | for (const suppression of suppressions) {
174 | if (!suppression.active) {
175 | if (
176 | modifiedSetupSct.slice(
177 | offsetToIndex(suppression.end),
178 | offsetToIndex(suppression.end) + 4
179 | ) !== "2902"
180 | ) {
181 | alert("Something went wrong. Please file a bug report on Github.");
182 | return;
183 | }
184 |
185 | modifiedSetupSct = replaceAt(
186 | modifiedSetupSct,
187 | offsetToIndex(suppression.end),
188 | 4,
189 | ""
190 | );
191 |
192 | modifiedSetupSct = replaceAt(
193 | modifiedSetupSct,
194 | offsetToIndex(suppression.start),
195 | 0,
196 | "2902"
197 | );
198 |
199 | for (const suppressionToUpdate of suppressions) {
200 | if (suppressionToUpdate.offset !== suppression.offset) {
201 | if (
202 | parseInt(suppression.start, 16) <
203 | parseInt(suppressionToUpdate.start, 16) &&
204 | parseInt(suppressionToUpdate.start, 16) <
205 | parseInt(suppression.end, 16)
206 | ) {
207 | suppressionToUpdate.start = decToHexString(
208 | (offsetToIndex(suppressionToUpdate.start) + 8) / 2
209 | );
210 | }
211 |
212 | if (
213 | parseInt(suppression.start, 16) <
214 | parseInt(suppressionToUpdate.end, 16) &&
215 | parseInt(suppressionToUpdate.end, 16) <
216 | parseInt(suppression.end, 16)
217 | ) {
218 | suppressionToUpdate.end = decToHexString(
219 | (offsetToIndex(suppressionToUpdate.end) + 8) / 2
220 | );
221 | }
222 | }
223 | }
224 |
225 | setupSctChangeLog += `Unsuppressed ${suppression.offset}\n`;
226 |
227 | wasSetupSctModified = true;
228 | }
229 | }
230 |
231 | let modifiedAmitseSct = files.amitseSctContainer.textContent;
232 | let amitseSctChangeLog = "";
233 |
234 | for (const entry of data.menu) {
235 | const padded = entry.formId.split("x")[1].padStart(4, "0");
236 | const newValue = padded.slice(2) + padded.slice(0, 2);
237 | const index = offsetToIndex(entry.offset);
238 | const oldValue = modifiedAmitseSct.slice(index, index + 4);
239 |
240 | if (newValue !== oldValue) {
241 | modifiedAmitseSct = replaceAt(modifiedAmitseSct, index, 4, newValue);
242 |
243 | const oldFormId = decToHexString(
244 | parseInt(oldValue.slice(-2) + oldValue.slice(-4, -2), 16)
245 | );
246 |
247 | amitseSctChangeLog += `${
248 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
249 | data.forms.find(
250 | (form) => parseInt(form.formId) === parseInt(oldFormId)
251 | )!.name
252 | } | FormId ${oldFormId} -> ${
253 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
254 | data.forms.find(
255 | (form) => parseInt(form.formId) === parseInt(entry.formId)
256 | )!.name
257 | } | FormId ${entry.formId}\n`;
258 |
259 | wasAmitseSctModified = true;
260 | }
261 | }
262 |
263 | let modifiedSetupdataBin = files.setupdataBinContainer.textContent;
264 | let setupdataBinChangeLog = "";
265 |
266 | for (const form of data.forms) {
267 | for (const child of form.children) {
268 | if (
269 | child.offsets &&
270 | child.accessLevel &&
271 | child.failsafe &&
272 | child.optimal
273 | ) {
274 | const accessLevelIndex = offsetToIndex(child.offsets.accessLevel);
275 | const oldAccessLevel = modifiedSetupdataBin.slice(
276 | accessLevelIndex,
277 | accessLevelIndex + 2
278 | );
279 | const newAccessLevel = child.accessLevel.padStart(2, "0");
280 | if (oldAccessLevel !== newAccessLevel) {
281 | modifiedSetupdataBin = replaceAt(
282 | modifiedSetupdataBin,
283 | accessLevelIndex,
284 | 2,
285 | newAccessLevel
286 | );
287 | setupdataBinChangeLog += `${child.name} | QuestionId ${child.questionId}: Access Level ${oldAccessLevel} -> ${newAccessLevel}\n`;
288 |
289 | wasSetupdataBinModified = true;
290 | }
291 |
292 | const failsafeIndex = offsetToIndex(child.offsets.failsafe);
293 | const oldFailsafe = modifiedSetupdataBin.slice(
294 | failsafeIndex,
295 | failsafeIndex + 2
296 | );
297 | const newFailsafe = child.failsafe.padStart(2, "0");
298 | if (oldFailsafe !== newFailsafe) {
299 | modifiedSetupdataBin = replaceAt(
300 | modifiedSetupdataBin,
301 | failsafeIndex,
302 | 2,
303 | newFailsafe
304 | );
305 | setupdataBinChangeLog += `${child.name} | QuestionId ${child.questionId}: Failsafe ${oldFailsafe} -> ${newFailsafe}\n`;
306 |
307 | wasSetupdataBinModified = true;
308 | }
309 |
310 | const optimalIndex = offsetToIndex(child.offsets.optimal);
311 | const oldOptimal = modifiedSetupdataBin.slice(
312 | optimalIndex,
313 | optimalIndex + 2
314 | );
315 | const newOptimal = child.optimal.padStart(2, "0");
316 | if (oldOptimal !== newOptimal) {
317 | modifiedSetupdataBin = replaceAt(
318 | modifiedSetupdataBin,
319 | optimalIndex,
320 | 2,
321 | newOptimal
322 | );
323 | setupdataBinChangeLog += `${child.name} | QuestionId ${child.questionId}: Optimal ${oldOptimal} -> ${newOptimal}\n`;
324 |
325 | wasSetupdataBinModified = true;
326 | }
327 | }
328 | }
329 | }
330 |
331 | if (wasSetupSctModified) {
332 | changeLog += `========== ${files.setupSctContainer.file.name} ==========\n\n${setupSctChangeLog}\n\n\n`;
333 |
334 | saveAs(
335 | new Blob([new Uint8Array(getUint8Array(modifiedSetupSct))], {
336 | type: "application/octet-stream",
337 | }),
338 | files.setupSctContainer.file.name
339 | );
340 | }
341 |
342 | if (wasAmitseSctModified) {
343 | changeLog += `========== ${files.amitseSctContainer.file.name} ==========\n\n${amitseSctChangeLog}\n\n\n`;
344 |
345 | saveAs(
346 | new Blob([new Uint8Array(getUint8Array(modifiedAmitseSct))], {
347 | type: "application/octet-stream",
348 | }),
349 | files.amitseSctContainer.file.name
350 | );
351 | }
352 |
353 | if (wasSetupdataBinModified) {
354 | changeLog += `========== ${files.setupdataBinContainer.file.name} ==========\n\n${setupdataBinChangeLog}\n\n\n`;
355 |
356 | saveAs(
357 | new Blob([new Uint8Array(getUint8Array(modifiedSetupdataBin))], {
358 | type: "application/octet-stream",
359 | }),
360 | files.setupdataBinContainer.file.name
361 | );
362 | }
363 |
364 | if (wasSetupSctModified || wasAmitseSctModified || wasSetupdataBinModified) {
365 | saveAs(
366 | new Blob([changeLog], {
367 | type: "text/plain",
368 | }),
369 | "changelog.txt"
370 | );
371 | } else {
372 | alert("No modifications have been done.");
373 | }
374 |
375 | return Promise.resolve();
376 | }
377 |
378 | function determineSuppressionStart(setupTxtArray: string[], index: number) {
379 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
380 | if (!hasScope(/\{ (.*) \}/.exec(setupTxtArray[index + 1])![1])) {
381 | return setupTxtArray[index + 2].split(" ")[0].slice(0, -1);
382 | }
383 |
384 | let openScopes = 1;
385 | let currentIndex = index + 2;
386 | while (openScopes !== 0) {
387 | const line = setupTxtArray[currentIndex];
388 |
389 | const anyOpcode = /\{ (.*) \}/.exec(line);
390 | const end = /\{ 29 02 \}/.exec(line);
391 |
392 | if (anyOpcode && hasScope(anyOpcode[1])) {
393 | openScopes++;
394 | }
395 |
396 | if (end) {
397 | openScopes--;
398 | }
399 |
400 | currentIndex++;
401 | }
402 |
403 | return setupTxtArray[currentIndex].split(" ")[0].slice(0, -1);
404 | }
405 |
406 | export async function parseData(files: PopulatedFiles) {
407 | let setupTxt = files.setupTxtContainer.textContent;
408 | const amitseSct = files.amitseSctContainer.textContent;
409 | const setupdataBin = files.setupdataBinContainer.textContent;
410 |
411 | if (
412 | !wantedIFRExtractorVersions.some((version) =>
413 | setupTxt.includes(`Program version: ${version}`)
414 | )
415 | ) {
416 | alert(
417 | `Wrong IFRExtractor-RS version. Compatible versions: ${wantedIFRExtractorVersions.join(
418 | ", "
419 | )}.`
420 | );
421 | window.location.reload();
422 | return {} as Data;
423 | }
424 |
425 | if (!setupTxt.includes("Extraction mode: UEFI")) {
426 | alert("Only UEFI is supported.");
427 | window.location.reload();
428 | return {} as Data;
429 | }
430 |
431 | if (!/\{ .* \}/.test(setupTxt)) {
432 | alert(`Use the "verbose" option of IFRExtractor.`);
433 | window.location.reload();
434 | return {} as Data;
435 | }
436 |
437 | setupTxt = setupTxt.replace(/[\r\n|\n|\r](?!0x[0-9A-F]{3})/g, "
");
438 |
439 | let formSetId = "";
440 | const varStores: VarStores = [];
441 | const forms: Forms = [];
442 | const suppressions: Suppression[] = [];
443 | const scopes: Scopes = [];
444 | let currentForm: Form = {} as Form;
445 | let currentString: StringPrompt = {} as StringPrompt;
446 | let currentOneOf: OneOfPrompt = {} as OneOfPrompt;
447 | let currentNumeric: NumericPrompt = {} as NumericPrompt;
448 | let currentCheckBox: CheckBoxPrompt = {} as CheckBoxPrompt;
449 |
450 | const currentSuppressions: Suppression[] = [];
451 |
452 | const references: Record> = {};
453 |
454 | const setupTxtArray = setupTxt.split("\n");
455 |
456 | for (const [index, line] of setupTxtArray.entries()) {
457 | const formSet = /FormSet Guid: (.*)-(.*)-(.*)-(.*)-(.*), Title:/.exec(line);
458 | const varStore =
459 | /VarStore Guid: (.*), VarStoreId: (.*), Size: (.*), Name: "(.*)" \{/.exec(
460 | line
461 | );
462 | const form = /Form FormId: (.*), Title: "(.*)" \{ (.*) \}/.exec(line);
463 | const suppressIf = /\{ 0A 82 \}/.exec(line);
464 | const ref =
465 | /Ref Prompt: "(.*)", Help: "(.*)", QuestionFlags: (.*), QuestionId: (.*), VarStoreId: (.*), VarStoreInfo: (.*), FormId: (.*) \{ (.*) \}/.exec(
466 | line
467 | );
468 | const string =
469 | /String Prompt: "(.*)", Help: "(.*)", QuestionFlags: (.*), QuestionId: (.*), VarStoreId: (.*), VarStoreInfo: (.*), MinSize: (.*), MaxSize: (.*), Flags: (.*) \{ (.*) \}/.exec(
470 | line
471 | );
472 | const numeric =
473 | /Numeric Prompt: "(.*)", Help: "(.*)", QuestionFlags: (.*), QuestionId: (.*), VarStoreId: (.*), VarOffset: (.*), Flags: (.*), Size: (.*), Min: (.*), Max: (.*), Step: (.*) \{ (.*) \}/.exec(
474 | line
475 | );
476 | const checkBox =
477 | /CheckBox Prompt: "(.*)", Help: "(.*)", QuestionFlags: (.*), QuestionId: (.*), VarStoreId: (.*), VarOffset: (.*), Flags: (.*) \{ (.*) \}/.exec(
478 | line
479 | );
480 | const oneOf =
481 | /OneOf Prompt: "(.*)", Help: "(.*)", QuestionFlags: (.*), QuestionId: (.*), VarStoreId: (.*), VarOffset: (.*), Flags: (.*), Size: (.*), Min: (.*), Max: (.*), Step: (.*) \{ (.*) \}/.exec(
482 | line
483 | );
484 | const oneOfOption = /OneOfOption Option: "(.*)" Value: (.*) \{/.exec(line);
485 | const defaultId = /Default DefaultId: (.*) Value: (.*) \{/.exec(line);
486 | const end = /\{ 29 02 \}/.exec(line);
487 | const indentations = (line.match(/\t/g) ?? []).length;
488 | const offset = line.split(" ")[0].slice(0, -1);
489 | const currentScope = scopes[scopes.length - 1];
490 |
491 | if (formSet) {
492 | formSetId = formSet[4] + formSet[5];
493 | }
494 |
495 | if (varStore) {
496 | varStores.push({
497 | varStoreId: varStore[2],
498 | size: varStore[3],
499 | name: varStore[4],
500 | });
501 | }
502 |
503 | if (form) {
504 | currentForm = {
505 | name: form[2],
506 | type: "Form",
507 | formId: form[1],
508 | referencedIn: [],
509 | children: [],
510 | };
511 |
512 | if (hasScope(form[3])) {
513 | scopes.push({ type: "Form", indentations });
514 | }
515 | }
516 |
517 | if (suppressIf) {
518 | scopes.push({
519 | type: "SuppressIf",
520 | indentations,
521 | offset,
522 | });
523 |
524 | currentSuppressions.push({
525 | offset,
526 | active: true,
527 | start: determineSuppressionStart(setupTxtArray, index),
528 | } as Suppression);
529 | }
530 |
531 | if (ref) {
532 | const formId = ref[7];
533 |
534 | const currentRef: RefPrompt = {
535 | name: ref[1],
536 | description: ref[2],
537 | type: "Ref",
538 | questionId: ref[4],
539 | varStoreId: ref[5],
540 | varStoreName: varStores.find(
541 | (varStore) => varStore.varStoreId === ref[5]
542 | )?.name,
543 | formId,
544 | ...getAdditionalData(ref[8], setupdataBin, true),
545 | };
546 |
547 | checkSuppressions(scopes, currentRef);
548 |
549 | currentForm.children.push(currentRef);
550 |
551 | if (formId in references) {
552 | references[formId].add(currentForm.formId);
553 | } else {
554 | references[formId] = new Set([currentForm.formId]);
555 | }
556 | }
557 |
558 | if (string) {
559 | const { accessLevel, failsafe, optimal, offsets } = getAdditionalData(
560 | string[10],
561 | setupdataBin,
562 | false
563 | );
564 |
565 | currentString = {
566 | name: string[1],
567 | description: string[2],
568 | type: "String",
569 | questionId: string[4],
570 | varStoreId: string[5],
571 | varStoreName: varStores.find(
572 | (varStore) => varStore.varStoreId === string[5]
573 | )?.name,
574 | accessLevel,
575 | failsafe,
576 | optimal,
577 | offsets,
578 | };
579 |
580 | checkSuppressions(scopes, currentString);
581 |
582 | if (hasScope(string[10])) {
583 | scopes.push({ type: "String", indentations });
584 | }
585 | }
586 |
587 | if (numeric) {
588 | const { accessLevel, failsafe, optimal, offsets } = getAdditionalData(
589 | numeric[12],
590 | setupdataBin,
591 | false
592 | );
593 |
594 | currentNumeric = {
595 | name: numeric[1],
596 | description: numeric[2],
597 | type: "Numeric",
598 | questionId: numeric[4],
599 | varStoreId: numeric[5],
600 | varStoreName: varStores.find(
601 | (varStore) => varStore.varStoreId === numeric[5]
602 | )?.name,
603 | varOffset: numeric[6],
604 | size: numeric[8],
605 | min: numeric[9],
606 | max: numeric[10],
607 | step: numeric[11],
608 | accessLevel,
609 | failsafe,
610 | optimal,
611 | offsets,
612 | };
613 |
614 | checkSuppressions(scopes, currentNumeric);
615 |
616 | if (hasScope(numeric[12])) {
617 | scopes.push({ type: "Numeric", indentations });
618 | }
619 | }
620 |
621 | if (checkBox) {
622 | const { accessLevel, failsafe, optimal, offsets } = getAdditionalData(
623 | checkBox[8],
624 | setupdataBin,
625 | false
626 | );
627 |
628 | currentCheckBox = {
629 | name: checkBox[1],
630 | description: checkBox[2],
631 | type: "CheckBox",
632 | questionId: checkBox[4],
633 | varStoreId: checkBox[5],
634 | varStoreName: varStores.find(
635 | (varStore) => varStore.varStoreId === checkBox[5]
636 | )?.name,
637 | varOffset: checkBox[6],
638 | flags: checkBox[7],
639 | accessLevel,
640 | failsafe,
641 | optimal,
642 | offsets,
643 | };
644 |
645 | checkSuppressions(scopes, currentCheckBox);
646 |
647 | if (hasScope(checkBox[8])) {
648 | scopes.push({ type: "CheckBox", indentations });
649 | }
650 | }
651 |
652 | if (oneOf) {
653 | const { accessLevel, failsafe, optimal, offsets } = getAdditionalData(
654 | oneOf[12],
655 | setupdataBin,
656 | false
657 | );
658 |
659 | currentOneOf = {
660 | name: oneOf[1],
661 | description: oneOf[2],
662 | type: "OneOf",
663 | questionId: oneOf[4],
664 | varStoreId: oneOf[5],
665 | varStoreName: varStores.find(
666 | (varStore) => varStore.varStoreId === oneOf[5]
667 | )?.name,
668 | varOffset: oneOf[6],
669 | size: oneOf[8],
670 | options: [],
671 | accessLevel,
672 | failsafe,
673 | optimal,
674 | offsets,
675 | };
676 |
677 | checkSuppressions(scopes, currentOneOf);
678 |
679 | if (hasScope(oneOf[12])) {
680 | scopes.push({ type: "OneOf", indentations });
681 | }
682 | }
683 |
684 | if (
685 | oneOfOption &&
686 | (currentScope.type === "OneOf" || currentScope.type === "SuppressIf")
687 | ) {
688 | currentOneOf.options.push({
689 | option: oneOfOption[1],
690 | value: oneOfOption[2],
691 | });
692 | }
693 |
694 | if (scopes.length !== 0) {
695 | if (defaultId) {
696 | const oneDefault = {
697 | defaultId: defaultId[1],
698 | value: defaultId[2],
699 | };
700 |
701 | if (currentScope.type === "Numeric") {
702 | currentNumeric.defaults ??= [];
703 | currentNumeric.defaults.push(oneDefault);
704 | } else if (currentScope.type === "CheckBox") {
705 | currentCheckBox.defaults ??= [];
706 | currentCheckBox.defaults.push(oneDefault);
707 | } else if (currentScope.type === "OneOf") {
708 | currentOneOf.defaults ??= [];
709 | currentOneOf.defaults.push(oneDefault);
710 | }
711 | }
712 |
713 | if (end && currentScope.indentations === indentations) {
714 | const scopeType = currentScope.type;
715 |
716 | if (scopeType === "Form") {
717 | forms.push(currentForm);
718 | } else if (scopeType === "Numeric") {
719 | currentForm.children.push(currentNumeric);
720 | } else if (scopeType === "CheckBox") {
721 | currentForm.children.push(currentCheckBox);
722 | } else if (scopeType === "OneOf") {
723 | currentForm.children.push(currentOneOf);
724 | } else if (scopeType === "String") {
725 | currentForm.children.push(currentString);
726 | } else {
727 | const latestSuppression = currentSuppressions.pop();
728 |
729 | if (!latestSuppression) {
730 | alert("Something went wrong. Please file a bug report on Github.");
731 | window.location.reload();
732 | return {} as Data;
733 | }
734 |
735 | suppressions.push({ ...latestSuppression, end: offset });
736 | }
737 |
738 | scopes.pop();
739 | }
740 | }
741 | }
742 |
743 | if (scopes.length !== 0 || currentSuppressions.length !== 0) {
744 | alert("Something went wrong. Please file a bug report on Github.");
745 | window.location.reload();
746 | return {} as Data;
747 | }
748 |
749 | const matches = [
750 | ...amitseSct.matchAll(new RegExp(formSetId + "(.{4})", "g")),
751 | ];
752 | const menu: Menu = matches
753 | .map((match) => {
754 | const hexEntry = decToHexString(
755 | parseInt(match[1].slice(2) + match[1].slice(0, 2), 16)
756 | );
757 | return {
758 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
759 | name: forms.find((form) => parseInt(form.formId) === parseInt(hexEntry))
760 | ?.name!,
761 | formId: hexEntry,
762 | offset: decToHexString((match.index + formSetId.length) / 2),
763 | };
764 | })
765 | .filter((x) => x.name);
766 |
767 | for (const form of forms) {
768 | if (form.formId in references) {
769 | form.referencedIn = [...references[form.formId]];
770 | }
771 | }
772 |
773 | const dataJson: Data = {
774 | menu,
775 | forms,
776 | varStores,
777 | suppressions,
778 | version,
779 | hashes: {
780 | setupTxt: sha256(setupTxt).toString(),
781 | setupSct: sha256(files.setupSctContainer.textContent).toString(),
782 | amitseSct: sha256(amitseSct).toString(),
783 | setupdataBin: sha256(setupdataBin).toString(),
784 | offsetChecksum: calculateJsonChecksum(menu, forms, suppressions),
785 | },
786 | };
787 |
788 | return Promise.resolve(dataJson);
789 | }
790 |
--------------------------------------------------------------------------------
/src/components/scripts/types.ts:
--------------------------------------------------------------------------------
1 | export interface Data {
2 | menu: Menu;
3 | varStores: VarStores;
4 | forms: Forms;
5 | suppressions: Suppression[];
6 | version: string;
7 | hashes: {
8 | setupTxt: string;
9 | setupSct: string;
10 | amitseSct: string;
11 | setupdataBin: string;
12 | offsetChecksum: string;
13 | };
14 | }
15 |
16 | export interface Suppression {
17 | offset: string;
18 | active: boolean;
19 | start: string;
20 | end: string;
21 | }
22 |
23 | export type Menu = {
24 | name: string;
25 | formId: string;
26 | offset: string;
27 | }[];
28 |
29 | export type Forms = Form[];
30 |
31 | export interface Form {
32 | name: string;
33 | type: "Form";
34 | formId: string;
35 | referencedIn: string[];
36 | children: FormChildren[];
37 | }
38 |
39 | export interface Offsets {
40 | accessLevel: string;
41 | failsafe: string;
42 | optimal: string;
43 | pageId?: string;
44 | }
45 |
46 | export interface FormChild {
47 | name: string;
48 | description: string;
49 | questionId: string;
50 | varStoreId: string;
51 | varStoreName?: string;
52 | accessLevel: string | null;
53 | failsafe: string | null;
54 | optimal: string | null;
55 | offsets: Offsets | null;
56 | suppressIf?: string[];
57 | }
58 |
59 | export type FormChildren =
60 | | RefPrompt
61 | | NumericPrompt
62 | | CheckBoxPrompt
63 | | OneOfPrompt
64 | | StringPrompt;
65 |
66 | export interface RefPrompt extends FormChild {
67 | type: "Ref";
68 | formId: string;
69 | pageId: string | null;
70 | }
71 |
72 | export interface NumericPrompt extends FormChild {
73 | type: "Numeric";
74 | varOffset: string;
75 | size: string;
76 | min: string;
77 | max: string;
78 | step: string;
79 | defaults?: Default[];
80 | }
81 |
82 | export interface CheckBoxPrompt extends FormChild {
83 | type: "CheckBox";
84 | varOffset: string;
85 | flags: string;
86 | defaults?: Default[];
87 | }
88 |
89 | export interface OneOfPrompt extends FormChild {
90 | type: "OneOf";
91 | varOffset: string;
92 | size: string;
93 | options: { option: string; value: string }[];
94 | defaults?: Default[];
95 | }
96 |
97 | export interface StringPrompt extends FormChild {
98 | type: "String";
99 | }
100 |
101 | export type VarStores = {
102 | varStoreId: string;
103 | size: string;
104 | name: string;
105 | }[];
106 |
107 | export interface Default {
108 | defaultId: string;
109 | value: string;
110 | }
111 |
112 | export type Scopes = {
113 | type: "Form" | "Numeric" | "CheckBox" | "OneOf" | "String" | "SuppressIf";
114 | indentations: number;
115 | offset?: string;
116 | }[];
117 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.tsx";
4 |
5 | import "@mantine/core/styles.css";
6 | import { AppShell, MantineProvider, createTheme } from "@mantine/core";
7 |
8 | const theme = createTheme({
9 | colors: {
10 | dark: [
11 | "#C1C2C5",
12 | "#A6A7AB",
13 | "#909296",
14 | "#5c5f66",
15 | "#373A40",
16 | "#2C2E33",
17 | "#25262b",
18 | "#1A1B1E",
19 | "#141517",
20 | "#101113",
21 | ],
22 | },
23 | });
24 |
25 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
26 | ReactDOM.createRoot(document.getElementById("root")!).render(
27 |
28 |
29 |
42 |
43 |
44 |
45 |
46 | );
47 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4 | "target": "ES2020",
5 | "useDefineForClassFields": true,
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "module": "ESNext",
8 | "skipLibCheck": true,
9 |
10 | /* Bundler mode */
11 | "moduleResolution": "bundler",
12 | "allowImportingTsExtensions": true,
13 | "isolatedModules": true,
14 | "moduleDetection": "force",
15 | "noEmit": true,
16 | "jsx": "react-jsx",
17 |
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "noFallthroughCasesInSwitch": true,
23 | "noUncheckedSideEffectImports": true
24 | },
25 | "include": ["src"]
26 | }
27 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | { "path": "./tsconfig.app.json" },
5 | { "path": "./tsconfig.node.json" }
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4 | "target": "ES2022",
5 | "lib": ["ES2023"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "isolatedModules": true,
13 | "moduleDetection": "force",
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "noUncheckedSideEffectImports": true
22 | },
23 | "include": ["vite.config.ts"]
24 | }
25 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 | import checker from "vite-plugin-checker";
4 |
5 | // https://vite.dev/config/
6 | export default defineConfig({
7 | plugins: [
8 | react(),
9 | checker({
10 | typescript: true,
11 | eslint: {
12 | lintCommand: 'eslint "./src/**/*.{ts,tsx}"',
13 | useFlatConfig: true,
14 | },
15 | }),
16 | ],
17 | base: "/UEFI-Editor/",
18 | });
19 |
--------------------------------------------------------------------------------