const g_ModProperties = { // example: "community-mod" "name": { "required": true, "type": "string", "validate": validateName }, // example: "0.29.0" "version": { "required": true, "type": "string", "validate": validateVersion }, // example: ["0ad<=0.0.28", "rote"] "dependencies": { "required": true, "type": "object", "validate": validateDependencies }, // example: "Community Mod" "label": { "required": true, "type": "string", "validate": validateLabel }, // example: "The Community Mod is a community-led effort to improve the gameplay." "description": { "required": true, "type": "string" }, // example: "https://wildfiregames.com/" "url": { "required": false, "type": "string" } }; /** * Tests if the string only contains alphanumeric characters and _ - */ const g_RegExpName = /[a-zA-Z0-9\-_]+/; /** * Tests if the version string consists only of numbers and at most two periods. */ const g_RegExpVersion= /[0-9]+(\.[0-9]+){0,2}/; /** * Version checks in mod dependencies can use these operators. */ export const regExpComparisonOperator = /(<=|>=|<|>|=)/; /** * Tests if a dependency compares a mod version against another, for instance "0ad<=0.0.16". */ const g_RegExpComparison = globalRegExp(new RegExp(g_RegExpName.source + regExpComparisonOperator.source + g_RegExpVersion.source)); /** * The label may not be empty. */ const g_RegExpLabel = /.*\S.*/; function globalRegExp(regexp) { return new RegExp("^" + regexp.source + "$"); } /** * Returns whether the mod defines all required properties and whether all properties are valid. * Shows a notification if not. */ export function validateMod(folder, modData, notify) { let valid = true; for (const propertyName in g_ModProperties) { const property = g_ModProperties[propertyName]; if (modData[propertyName] === undefined) { if (!property.required) continue; if (notify) warn("Mod '" + folder + "' does not define '" + propertyName + "'!"); valid = false; } if (typeof modData[propertyName] !== property.type) { if (notify) warn(propertyName + " in mod '" + folder + "' is not of the type '" + property.type + "'!"); valid = false; continue; } if (property.validate && !property.validate(folder, modData, notify)) valid = false; } return valid; } function validateName(folder, modData, notify) { const valid = modData.name.match(globalRegExp(g_RegExpName)); if (!valid && notify) warn("mod name of " + folder + " may only contain alphanumeric characters, but found '" + modData.name + "'!"); return valid; } function validateVersion(folder, modData, notify) { const valid = modData.version.match(globalRegExp(g_RegExpVersion)); if (!valid && notify) warn("mod version of " + folder + " may only contain numbers and at most 2 periods, but found '" + modData.version + "'!"); return valid; } function validateDependencies(folder, modData, notify) { let valid = true; for (const dependency of modData.dependencies) { valid = valid && ( dependency.match(globalRegExp(g_RegExpName)) || dependency.match(globalRegExp(g_RegExpComparison))); if (!valid && notify) warn("mod folder " + folder + " requires an invalid dependency '" + dependency + "'!"); } return valid; } function validateLabel(folder, modData, notify) { const valid = modData.label.match(g_RegExpLabel); if (!valid && notify) warn("mod label of " + folder + " may not be empty!"); return valid; }