diff --git a/Scripts/Internal/FileValidation/00_shared.wscript b/Scripts/Internal/FileValidation/00_shared.wscript index 4723e55..4740ae4 100644 --- a/Scripts/Internal/FileValidation/00_shared.wscript +++ b/Scripts/Internal/FileValidation/00_shared.wscript @@ -74,10 +74,15 @@ export function stringifyPotentialCName(cnameOrString, _info = '', suppressSpace if (typeof cnameOrString === 'bigint') { return `${cnameOrString}`; } - const ret = !!cnameOrString.$value ? cnameOrString.$value : cnameOrString.value; + let ret = !!cnameOrString.$value ? cnameOrString.$value : cnameOrString.value; + + if (ret == '') { + return ret; + } + const info = _info ? `${_info}: '${ret}' ` : `'${ret}' `; - if (ret && ret.trim && ret.trim() !== ret) { + if (ret && ret.trim && ret.trim() !== ret && !!ret.trim()) { Logger.Error(`${info}has trailing or leading spaces! Make sure to remove them, or the component might not work!`); } else if (!suppressSpaceCheck && ret?.indexOf && ret.indexOf(" ") >= 0 && !PLACEHOLDER_NAME_REGEX.test(ret || '')) { Logger.Warning(`${info}includes spaces. Please use _ instead.`); @@ -174,7 +179,7 @@ export function checkDepotPath(_depotPath, _info, allowEmpty = false, suppressLo } if (!!currentMaterialName) { - Logger.Info(`${info}${resolvedPath}: substitution couldn't be resolved. It may not be defined yet.`); + Logger.Info(`${info}${resolvedPath}: substitution couldn't be resolved. It may not be defined yet, or the file is in a different mod.`); return; } Logger.Warning(`${info}${resolvedPath} not found in project or game files`); diff --git a/Scripts/Wolvenkit_FileValidation.wscript b/Scripts/Wolvenkit_FileValidation.wscript index f80462d..d0b6cba 100644 --- a/Scripts/Wolvenkit_FileValidation.wscript +++ b/Scripts/Wolvenkit_FileValidation.wscript @@ -86,12 +86,12 @@ function pushCurrentFilePath(path) { } function popCurrentFilePath() { - if (pathToParentFile === pathToCurrentFile) { + if (pathToParentFile === pathToCurrentFile || !pathToParentFile) { pathToParentFile = ''; return; } pathToCurrentFile = pathToParentFile; - + pathToParentFile = ''; } const LOGLEVEL_INFO = 0; @@ -1008,7 +1008,8 @@ function entFile_appFile_validateComponent(component, _index, validateRecursivel } if (validateRecursively) { - const fileContent = wkit.LoadGameFileFromProject(componentMeshPath, 'json'); + try { + const fileContent = wkit.LoadGameFileFromProject(componentMeshPath, 'json'); const mesh = TypeHelper.JsonParse(fileContent); meshSettings ||= { @@ -1020,6 +1021,9 @@ function entFile_appFile_validateComponent(component, _index, validateRecursivel pushCurrentFilePath(componentMeshPath); _validateMeshFile(mesh) popCurrentFilePath(); + } catch (err) { + Logger.Error(`Failed to load ${componentMeshPath}`); + } } }); } @@ -1526,13 +1530,13 @@ function checkMeshMaterialIndices(mesh) { addWarning(LOGLEVEL_WARN, "Your mesh is trying to use both externalMaterials and preloadExternalMaterials. To avoid unspecified behaviour, use only one of the lists. Material validation will abort."); } - if (mesh.localMaterialBuffer.materials !== null && mesh.localMaterialBuffer.materials.length > 0 + if (!!mesh.localMaterialBuffer?.materials && mesh.localMaterialBuffer.materials.length > 0 && mesh.preloadLocalMaterialInstances.length > 0) { addWarning(LOGLEVEL_WARN, "Your mesh is trying to use both localMaterialBuffer.materials and preloadLocalMaterialInstances. To avoid unspecified behaviour, use only one of the lists. Material validation will abort."); } let sumOfLocal = mesh.localMaterialInstances.length + mesh.preloadLocalMaterialInstances.length; - if (mesh.localMaterialBuffer.materials !== null) { + if (!!mesh.localMaterialBuffer?.materials) { sumOfLocal += mesh.localMaterialBuffer.materials.length; } let sumOfExternal = mesh.externalMaterials.length + mesh.preloadExternalMaterials.length; @@ -1708,7 +1712,7 @@ function _validateMeshFile(mesh) { addWarning(LOGLEVEL_ERROR, `You're using dynamic materials that are not defined. This will crash your game! [ ${undefinedDynamicMaterialNames.join(", ")} ]`); } - if (mesh.localMaterialBuffer.materials !== null) { + if (!!mesh.localMaterialBuffer?.materials) { for (let i = 0; i < mesh.localMaterialBuffer.materials.length; i++) { let material = mesh.localMaterialBuffer.materials[i]; @@ -2291,4 +2295,18 @@ export const validateQuestphaseFile = validate_questphase_file; export const validateSceneFile = validate_scene_file; -export const validateInkatlasFile = validate_inkatlas_file; \ No newline at end of file +export const validateInkatlasFile = validate_inkatlas_file; + +export function validateMlsetupFile(mlsetup, mlsetupSettings) { + // check if file is valid/needs to be called recursively + if (mlsetup?.Data?.RootChunk) return validateMlsetupFile(mlsetup.Data.RootChunk, _workspotSettings); + + if (checkIfFileIsBroken(mlsetup, 'mlsetup')) return; + + let layerIdx = 0; + mlsetup.layers.forEach((layer) => { + checkDepotPath(layer.material.DepotPath, `layer_${layerIdx}.material`); + checkDepotPath(layer.microblend.DepotPath, `layer_${layerIdx}.microblend`); + layerIdx++; + }); +} \ No newline at end of file diff --git a/Scripts/hook_global.wscript b/Scripts/hook_global.wscript index 3bd0c63..1e75bc3 100644 --- a/Scripts/hook_global.wscript +++ b/Scripts/hook_global.wscript @@ -13,7 +13,7 @@ import {hasUppercasePaths, isDataChangedForWriting} from "Wolvenkit_FileValidati * If this is set to "true" and file validation runs into any errors, then YOUR FILES WILL NO LONGER SAVE. * ONLY ENABLE THIS IF YOU KNOW WHAT YOU'RE DOING! */ -const isWolvenkitDeveloper = false; +const isWolvenkitDeveloper = true; const README_URL = 'https://wiki.redmodding.org/wolvenkit/wolvenkit-app/file-validation'; @@ -31,7 +31,7 @@ globalThis.onSave = function (ext, file) { export function RunFileValidation(ext, file) { const fileContent = TypeHelper.JsonParse(file); - + // grab file name from json and inform file validation about it const fileName = (fileContent.Header?.ArchiveFileName || '').split('archive\\').pop() || ''; FileValidation.setPathToCurrentFile(fileName); @@ -60,6 +60,9 @@ export function RunFileValidation(ext, file) { case "morphtarget": FileValidation.validateMorphtargetFile(data, Settings.Morphtarget); break; + case "mlsetup": + FileValidation.validateMlsetupFile(data, {}); + break; case "mi": FileValidation.validateMiFile(data, Settings.Mi); break; @@ -84,6 +87,8 @@ export function RunFileValidation(ext, file) { case "scene": FileValidation.validateSceneFile(data, Settings.GraphScene); break; + default: + Logger.Info("File validation not implemented for file type " + ext); } } catch (err) { if (isWolvenkitDeveloper) {