uvmod/js/modframework.js
2023-08-01 23:40:37 +02:00

259 lines
8.6 KiB
JavaScript

class FirmwareMod {
constructor(name, description, size) {
this.name = name;
this.description = description;
this.size = size; // Additional flash usage in bytes
this.enabled = false; // Checkbox status, initially disabled
this.modSpecificDiv = document.createElement("div"); // Div for mod-specific inputs
// If needed, create input fields here and append them to the modSpecificDiv
}
apply(firmwareData) {
// This method should be overridden in each mod implementation
// It should apply the mod on the firmwareData and return the modified firmwareData
return firmwareData;
}
}
function addModToUI(mod, modDiv) {
// Create a card div
const card = document.createElement("div");
card.classList.add("card", "mb-3", "border-left-primary", "border-left-secondary");
// Create a card body div
const cardBody = document.createElement("div");
cardBody.classList.add("card-body");
// Create a row div
const row = document.createElement("div");
row.classList.add("row");
// Create checkbox column
const checkboxCol = document.createElement("div");
checkboxCol.classList.add("col-auto");
// Create checkbox for enabling the mod
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.style.height = "1.45rem";
checkbox.style.width = "1.45rem";
checkbox.checked = mod.enabled;
checkbox.addEventListener("change", function () {
mod.enabled = checkbox.checked;
if (checkbox.checked) {
card.classList.remove("border-left-secondary");
} else {
card.classList.add("border-left-secondary");
}
});
checkboxCol.appendChild(checkbox);
// Create name column
const nameCol = document.createElement("div");
nameCol.classList.add("col-auto", "mr-auto", "pl-1");
const nameText = document.createElement("h5");
nameText.textContent = mod.name;
nameCol.appendChild(nameText);
// Create size column
const sizeCol = document.createElement("div");
sizeCol.classList.add("col-auto");
const sizeText = document.createElement("p");
sizeText.textContent = "Flash usage: " + mod.size + " Bytes";
sizeCol.appendChild(sizeText);
// Add columns to the row
row.appendChild(checkboxCol);
row.appendChild(nameCol);
row.appendChild(sizeCol);
// Create description column
const descCol = document.createElement("div");
//descCol.classList.add("col");
const descriptionText = document.createElement("p");
descriptionText.textContent = mod.description;
descCol.appendChild(descriptionText);
// Add the mod-specific div for custom inputs
cardBody.appendChild(row);
cardBody.appendChild(descCol);
cardBody.appendChild(mod.modSpecificDiv);
// Add card body to the card div
card.appendChild(cardBody);
// Add the card to the modDiv
modDiv.appendChild(card);
}
var modClasses = []; // Will be populated in mods.js
var modInstances = [];
function modLoader() {
modClasses.forEach(ModClass => {
const modInstance = new ModClass();
modInstances.push(modInstance); // Add the instance to the array
const modDiv = document.createElement("div");
addModToUI(modInstance, modDiv);
document.getElementById("modsContainer").appendChild(modDiv);
});
log("Patcher ready.");
return modInstances; // Return the array of mod instances
}
function applyMods(firmware) {
for (const modInstance of modInstances) {
if (modInstance.enabled) {
firmware = modInstance.apply(firmware);
}
}
log("Finished applying mods...");
return firmware;
}
// Helper functions:
/**
* Converts a hexadecimal string to a Uint8Array.
* The input hex string should have the format "HH" where HH is a two-digit hexadecimal value.
*
* 0x or \x is not allowed
*
* To output a python bytearray in the correct format, use this in python: ''.join('%02x'%i for i in bytearray)
* @example hexString("0102AAFF") // Outputs Uint8Array of 1, 2, 170, 255
* @param {string} hexString - The hexadecimal string to convert.
* @returns {Uint8Array} The Uint8Array representing the converted data.
*/
function hexString(hexString) {
const byteArray = new Uint8Array(hexString.length / 2);
for (let i = 0; i < byteArray.length; i++) {
const byteValue = parseInt(hexString.substr(i * 2, 2), 16);
byteArray[i] = byteValue;
}
return byteArray;
}
/**
* Replaces or appends a section in the firmware data with new data at the specified offset.
* To append data to the firmware, use firmwareData.length as the offset.
* @param {Uint8Array} firmwareData - The original firmware Uint8array.
* @param {Uint8Array} newData - The new data to replace the section with.
* @param {number} offset - The offset where the section should be replaced.
* @returns {Uint8Array} - The updated firmware data with the section replaced.
*/
function replaceSection(firmwareData, newData, offset) {
const updatedFirmwareData = new Uint8Array(Math.max(firmwareData.length, offset + newData.length));
updatedFirmwareData.set(firmwareData.subarray(0, offset));
updatedFirmwareData.set(newData, offset);
if (offset + newData.length < firmwareData.length) {
updatedFirmwareData.set(firmwareData.subarray(offset + newData.length), offset + newData.length);
}
return updatedFirmwareData;
}
/**
* Compares two Uint8Arrays to check if they are equal.
* @param {Uint8Array} array1 - The first Uint8Array to compare.
* @param {Uint8Array} array2 - The second Uint8Array to compare.
* @returns {boolean} - True if the Uint8Arrays are equal, false otherwise.
*/
function compareUint8Arrays(array1, array2) {
if (array1.length !== array2.length) {
return false;
}
for (let i = 0; i < array1.length; i++) {
if (array1[i] !== array2[i]) {
return false;
}
}
return true;
}
/**
* Compares a section of a Uint8Array with another Uint8Array.
* @param {Uint8Array} array - The Uint8Array to compare a section from.
* @param {Uint8Array} section - The Uint8Array representing the section to compare.
* @param {number} offset - The offset within the main array to start the comparison.
* @returns {boolean} - True if the section matches the part of the main array starting from the offset, false otherwise.
*/
function compareSection(array, section, offset) {
if (offset < 0 || offset + section.length > array.length) {
throw new Error("Offset is out of bounds.");
}
const slicedArray = array.slice(offset, offset + section.length);
return compareUint8Arrays(slicedArray, section);
}
/**
* Adds an input field to a parent div with a label and default text.
*
* @param {HTMLElement} parentDiv - The parent div to which the input field will be added. Usually this.modSpecificDiv
* @param {string} labelText - The label text (title) for the input field.
* @param {string} defaultText - The default text to pre-fill the input field with.
* @returns {HTMLInputElement} - The created input element, assign it to a constant for later use.
*/
function addInputField(parentDiv, labelText, defaultValue) {
const formGroup = document.createElement("div");
formGroup.classList.add("form-group");
const label = document.createElement("label");
label.textContent = labelText;
formGroup.appendChild(label);
const input = document.createElement("input");
input.classList.add("form-control");
input.type = "text";
input.value = defaultValue; // Set the default value
formGroup.appendChild(input);
parentDiv.appendChild(formGroup);
return input; // Return the input element
}
/**
* Adds a radio input field to a parent div with an id, name, value and label.
*
* @param {HTMLElement} parentDiv - The parent div to which the input field will be added. Usually this.modSpecificDiv
* @param {string} labelText - The label text (title) for the input field.
* @param {string} id - The id is needed to link radio button and label, choose any unique id.
* @param {string} name - The name of the radio button needs to be the same for all radio buttons in a mutually exclusive group.
* @returns {HTMLInputElement} - The created input element, assign it to a constant for later use.
*/
function addRadioButton(parentDiv, labelText, id, name) {
const formCheckDiv = document.createElement("div");
formCheckDiv.classList.add("form-check");
const inputElement = document.createElement("input");
inputElement.classList.add("form-check-input");
inputElement.type = "radio";
inputElement.name = name;
inputElement.id = id;
const labelElement = document.createElement("label");
labelElement.classList.add("form-check-label");
labelElement.htmlFor = id;
labelElement.textContent = labelText;
formCheckDiv.appendChild(inputElement);
formCheckDiv.appendChild(labelElement);
parentDiv.appendChild(formCheckDiv);
return inputElement;
}