Skip to content

Commit 28e3f36

Browse files
committed
Try to find missing files elsewhere in the mod file database
- Needing to manage paths in the yaml files every time I reorg stuff is a pain in the ass, and inconsistent with how mods are handled since they can be loaded from any path - So now its consistent, files (dds, mmobj) referenced by yaml files can basically be anywhere in the mod path; if there is more than one match for the basename, it will try to use the source path to find the closest one (which could end up being wrong, so it prints a warning message in this case). For this reason, if I need to specify a relative path, its best to specify at least one level of parent directory (like foo/somemobj.mmobj) to give that resolution more accuracy.
1 parent 97d2d9b commit 28e3f36

File tree

1 file changed

+91
-10
lines changed

1 file changed

+91
-10
lines changed

MMManaged/ModDB.fs

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)