@@ -44,6 +44,65 @@ module ModDB =
4444
4545 let private (| StringValueIgnoreCase | _ |) node = Yaml.toOptionalString( Some( node)) |> strToLower
4646
47+ let mutable allAvailableFiles = [||]
48+ let tryLocateFile ( filepath : string ): string option =
49+ if String.IsNullOrWhiteSpace( filepath) then
50+ None
51+ else if File.Exists( filepath) then
52+ Some( filepath)
53+ else
54+ log() .Warn " File is missing: %A " filepath
55+ let matches =
56+ allAvailableFiles
57+ |> Array.filter ( fun file -> String.Equals( Path.GetFileName( file), Path.GetFileName( filepath), StringComparison.OrdinalIgnoreCase))
58+ match matches with
59+ | [| singleMatch |] ->
60+ log() .Warn " File relocated: %s -> %s " filepath singleMatch
61+ Some( singleMatch)
62+ | [||] ->
63+ log() .Error " No alternate files found matching: %s " filepath
64+ None
65+ | many ->
66+ // Use this LLM generated stuff to try to find a file in the list who most closely matches the source
67+ let dirParts ( p : string ) =
68+ let d = Path.GetDirectoryName( p)
69+ let d = if String.IsNullOrEmpty( d) then " " else d
70+ d.Replace( Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)
71+ .Split([| Path.DirectorySeparatorChar |], StringSplitOptions.RemoveEmptyEntries)
72+
73+ let commonSuffixLen ( a : string []) ( b : string []) =
74+ let mutable i = a.Length - 1
75+ let mutable j = b.Length - 1
76+ let mutable n = 0
77+ while i >= 0 && j >= 0 && a.[ i]. Equals( b.[ j], StringComparison.OrdinalIgnoreCase) do
78+ n <- n + 1
79+ i <- i - 1
80+ j <- j - 1
81+ n
82+
83+ let pickMostSimilarByDir ( original : string ) ( candidates : string []) =
84+ let target = dirParts original
85+ let scored =
86+ candidates
87+ |> Array.map ( fun c ->
88+ let parts = dirParts c
89+ let score = commonSuffixLen target parts
90+ let depthDiff = abs ( parts.Length - target.Length)
91+ c, score, depthDiff)
92+
93+ let ordered =
94+ scored
95+ |> Array.sortBy ( fun ( c , s , dd ) -> (- s, dd, c.ToLowerInvariant()))
96+
97+ let first = ordered.[ 0 ] |> fun ( c , _ , _ ) -> c
98+ let rest = ordered |> Array.skip 1 |> Array.map ( fun ( c , _ , _ ) -> c)
99+ first, rest
100+
101+ let first , rest = pickMostSimilarByDir filepath many
102+
103+ log() .Warn " Multiple files found matching name; loading (%A ); other matches: %A ;\n to fix this message fix the path to point at an extant file" first rest
104+ Some( first)
105+
47106 /// Root type for the Mod Database; everything is stored in here.
48107 type ModDB ( refObjects , modObjects , meshRels : MeshRelation list ) =
49108 // explode deletion mods into interop representation now.
@@ -85,6 +144,8 @@ module ModDB =
85144 member x.MeshRelations = meshRels
86145 member x.DeletionMods = deletionMods
87146
147+ member x.TryLocate = tryLocateFile
148+
88149 /// Unpack a "transforms" list from yaml.
89150 let getMeshTransforms ( node : YamlMappingNode ) =
90151 let transforms = node |> Yaml.getOptionalValue " transforms" |> Yaml.toOptionalSequence
@@ -138,17 +199,18 @@ module ModDB =
138199 let basePath = Path.GetDirectoryName filename
139200 let modName = Path.GetFileNameWithoutExtension filename
140201
202+ let makeAbsolute ( path : string ) =
203+ match path with
204+ | " " -> " "
205+ | path when Path.IsPathRooted path -> path
206+ | _ -> Path.GetFullPath( Path.Combine( basePath, path))
141207 let unpackPath =
142208 let useEmptyStringForMissing ( x : string option ) =
143209 match x with
144210 | None -> " "
145211 | Some s when s.Trim() = " " -> " "
146212 | Some s -> s
147- let makeAbsolute ( path : string ) =
148- match path with
149- | " " -> " "
150- | path when Path.IsPathRooted path -> path
151- | _ -> Path.GetFullPath( Path.Combine( basePath, path))
213+
152214
153215 Yaml.toOptionalString >> useEmptyStringForMissing >> makeAbsolute
154216
@@ -213,10 +275,17 @@ module ModDB =
213275 let meshPath = node |> Yaml.getValue " meshPath" |> Yaml.toString
214276 if meshPath = " " then failwithf " meshPath is empty"
215277
216- let tex0Path = node |> Yaml.getOptionalValue " Tex0Path" |> unpackPath
217- let tex1Path = node |> Yaml.getOptionalValue " Tex1Path" |> unpackPath
218- let tex2Path = node |> Yaml.getOptionalValue " Tex2Path" |> unpackPath
219- let tex3Path = node |> Yaml.getOptionalValue " Tex3Path" |> unpackPath
278+ let maybeTryRelocate ( f : string ) =
279+ match tryLocateFile f with
280+ | Some( f) -> f
281+ | None -> f
282+
283+ let meshPath = meshPath |> makeAbsolute |> maybeTryRelocate
284+
285+ let tex0Path = node |> Yaml.getOptionalValue " Tex0Path" |> unpackPath |> maybeTryRelocate
286+ let tex1Path = node |> Yaml.getOptionalValue " Tex1Path" |> unpackPath |> maybeTryRelocate
287+ let tex2Path = node |> Yaml.getOptionalValue " Tex2Path" |> unpackPath |> maybeTryRelocate
288+ let tex3Path = node |> Yaml.getOptionalValue " Tex3Path" |> unpackPath |> maybeTryRelocate
220289 numOverrideTextures <-
221290 let oneIfNotEmpty ( s : string ) = if s.Trim() <> " " then 1 else 0
222291 oneIfNotEmpty tex0Path + oneIfNotEmpty tex1Path + oneIfNotEmpty tex2Path + oneIfNotEmpty tex3Path
@@ -342,6 +411,12 @@ module ModDB =
342411 let meshPath = node |> Yaml.getValue " meshpath" |> Yaml.toString
343412
344413 let meshFullPath = Path.Combine( basePath, meshPath)
414+
415+ let meshFullPath =
416+ match tryLocateFile meshFullPath with
417+ | Some( path) -> path
418+ | None -> meshFullPath
419+
345420 let meshReadFlags = CoreTypes.DefaultReadFlags
346421 let doMeshLoad () =
347422 let mesh = loadMesh ( meshFullPath, ModType.Reference, meshReadFlags)
@@ -499,8 +574,14 @@ module ModDB =
499574 | _ -> failwithf " Expected data with 'type: \" Index\" ' in %s " filename
500575
501576 // get a list of all the yaml files in all subdirectories beneath the index file.
577+ let start = new Util.StopwatchTracker( " file scan" );
502578 let searchRoot = Directory.GetParent( filename) .FullName
503- let allFiles = Directory.GetFiles( searchRoot, " *.yaml" , SearchOption.AllDirectories)
579+ let allFiles = Directory.GetFiles( searchRoot, " *.*" , SearchOption.AllDirectories)
580+ allAvailableFiles <- allFiles
581+ log() .Info " %A files in data dir" ( allFiles.Length)
582+ let allFiles = allFiles |> Array.filter ( fun f -> String.Equals( Path.GetExtension( f), " .yaml" , StringComparison.OrdinalIgnoreCase) )
583+ log() .Info " %A yaml files in data dir" ( allFiles.Length)
584+ start.StopAndPrint()
504585
505586 // walk the file list, loading the mods that are on the load list
506587 let nameMatches f1 f2 =
0 commit comments