thief-mission-viewer/project/code/LGS/ResourcePathManager.cs

433 lines
14 KiB
C#
Raw Normal View History

using System;
2024-08-24 16:09:27 +00:00
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
namespace KeepersCompound.LGS;
public enum ResourceType
{
Mission,
Object,
ObjectTexture,
Texture,
}
2024-08-24 16:09:27 +00:00
public class ResourcePathManager
{
private record CampaignResources
{
public Dictionary<string, string> missionPathMap;
public Dictionary<string, string> texturePathMap;
public Dictionary<string, string> objectPathMap;
public Dictionary<string, string> objectTexturePathMap;
public List<string> GetResourceNames(ResourceType type)
{
List<string> keys = type switch
{
ResourceType.Mission => [.. missionPathMap.Keys],
ResourceType.Object => [.. objectPathMap.Keys],
ResourceType.ObjectTexture => [.. objectTexturePathMap.Keys],
ResourceType.Texture => [.. texturePathMap.Keys],
_ => throw new ArgumentOutOfRangeException(nameof(type)),
};
keys.Sort();
return keys;
}
public string GetResourcePath(ResourceType type, string name)
{
var map = type switch
{
ResourceType.Mission => missionPathMap,
ResourceType.Object => objectPathMap,
ResourceType.ObjectTexture => objectTexturePathMap,
ResourceType.Texture => texturePathMap,
_ => throw new ArgumentOutOfRangeException(nameof(type)),
};
return map.TryGetValue(name, out var resourcePath) ? resourcePath : null;
}
2024-08-24 16:09:27 +00:00
}
private bool _initialised = false;
private readonly string _extractionPath;
2024-08-25 15:49:43 +00:00
private string _fmsDir;
2024-08-24 16:09:27 +00:00
private CampaignResources _omResources;
private Dictionary<string, CampaignResources> _fmResources;
public ResourcePathManager(string extractionPath)
{
_extractionPath = extractionPath;
}
2024-09-07 07:01:15 +00:00
public static string ConvertSeparator(string path)
{
return path.Replace('\\', '/');
}
2024-08-24 16:09:27 +00:00
public bool Init(string installPath)
{
// TODO: This can be done less awkwardly with the resource paths
if (DirContainsThiefExe(installPath) &&
TryGetInstallCfgPath(installPath, out var installCfgPath) &&
TryBuildOmsPathMap(installPath, installCfgPath, out var omsMap) &&
TryBuildFmsPathMap(installPath, out var fmsMap, out var fmsDir) &&
TryGetPath(installPath, "fam.crf", out var famPath) &&
TryGetPath(installPath, "obj.crf", out var objPath))
{
// Register OM resources
{
var famExtractPath = Path.Join(_extractionPath, "fam");
if (Directory.Exists(famExtractPath))
{
Directory.Delete(famExtractPath, true);
}
ZipFile.OpenRead(famPath).ExtractToDirectory(famExtractPath);
var texturePathMap = GetTexturePaths(_extractionPath);
var objExtractPath = Path.Join(_extractionPath, "obj");
if (Directory.Exists(objExtractPath))
{
Directory.Delete(objExtractPath, true);
}
ZipFile.OpenRead(objPath).ExtractToDirectory(objExtractPath);
var objectPathMap = GetObjectPaths(_extractionPath);
var objectTexturePathMap = GetObjectTexturePaths(_extractionPath);
2024-08-24 16:09:27 +00:00
_omResources = new CampaignResources
{
missionPathMap = omsMap,
texturePathMap = texturePathMap,
objectPathMap = objectPathMap,
objectTexturePathMap = objectTexturePathMap,
2024-08-24 16:09:27 +00:00
};
}
{
_fmResources = new Dictionary<string, CampaignResources>();
foreach (var (campaign, missionPathMap) in fmsMap)
{
var root = Path.Join(fmsDir, campaign);
var texturePathMap = GetTexturePaths(root);
var objectPathMap = GetObjectPaths(root);
var objectTexturePathMap = GetObjectTexturePaths(root);
2024-08-24 16:09:27 +00:00
var resource = new CampaignResources
{
missionPathMap = missionPathMap,
texturePathMap = texturePathMap,
objectPathMap = objectPathMap,
objectTexturePathMap = objectTexturePathMap,
2024-08-24 16:09:27 +00:00
};
_fmResources.Add(campaign, resource);
2024-08-25 15:49:43 +00:00
_fmsDir = fmsDir;
2024-08-24 16:09:27 +00:00
}
}
_initialised = true;
return true;
}
return false;
}
public List<string> GetCampaignNames()
2024-08-25 08:15:42 +00:00
{
if (!_initialised) return null;
var names = new List<string>(_fmResources.Keys);
names.Sort();
return names;
2024-08-25 08:15:42 +00:00
}
public List<string> GetResourceNames(ResourceType type, string campaignName)
2024-08-24 16:09:27 +00:00
{
if (!_initialised)
2024-08-24 16:09:27 +00:00
{
throw new InvalidOperationException("Resource Path Manager hasn't been initialised.");
2024-08-24 16:09:27 +00:00
}
2024-08-25 09:03:52 +00:00
if (campaignName == null || campaignName == "")
{
return _omResources.GetResourceNames(type);
}
else if (_fmResources.TryGetValue(campaignName, out var campaign))
2024-08-25 08:15:42 +00:00
{
return campaign.GetResourceNames(type);
2024-09-05 16:31:59 +00:00
}
throw new ArgumentException("No campaign found with given name", nameof(campaignName));
2024-08-25 08:15:42 +00:00
}
2024-09-07 07:01:15 +00:00
// This expects resourceName to already have it's separator converted
public (string, string) GetResourcePath(
ResourceType type,
string campaignName,
string resourceName)
2024-08-24 16:09:27 +00:00
{
if (!_initialised)
2024-08-24 16:09:27 +00:00
{
throw new InvalidOperationException("Resource Path Manager hasn't been initialised.");
2024-08-24 16:09:27 +00:00
}
resourceName = resourceName.ToLower();
var omResourcePath = _omResources.GetResourcePath(type, resourceName);
2024-08-24 16:09:27 +00:00
if (campaignName == null || campaignName == "" && omResourcePath != null)
2024-08-24 16:09:27 +00:00
{
return ("", omResourcePath);
2024-08-25 15:49:43 +00:00
}
else if (_fmResources.TryGetValue(campaignName, out var campaign))
{
var fmResourcePath = campaign.GetResourcePath(type, resourceName);
if (fmResourcePath != null)
2024-08-25 15:49:43 +00:00
{
return (campaignName, fmResourcePath);
2024-08-25 15:49:43 +00:00
}
else if (omResourcePath != null)
2024-08-25 15:49:43 +00:00
{
return ("", omResourcePath);
2024-08-25 15:49:43 +00:00
}
}
// throw new ArgumentException($"No resource found with given type and name: {type}, {resourceName}", nameof(resourceName));
return (null, null);
2024-08-25 15:49:43 +00:00
}
private static Dictionary<string, string> GetObjectTexturePaths(string root)
{
string[] validExtensions = { ".dds", ".png", ".tga", ".pcx", ".gif", ".bmp", ".cel", };
var dirOptions = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
var texOptions = new EnumerationOptions
{
MatchCasing = MatchCasing.CaseInsensitive,
RecurseSubdirectories = true,
};
var pathMap = new Dictionary<string, string>();
foreach (var dir in Directory.EnumerateDirectories(root, "obj", dirOptions))
{
foreach (var path in Directory.EnumerateFiles(dir, "*", texOptions))
{
2024-09-07 07:01:15 +00:00
var convertedPath = ConvertSeparator(path);
var ext = Path.GetExtension(convertedPath);
if (validExtensions.Contains(ext.ToLower()))
{
2024-09-07 07:01:15 +00:00
var key = Path.GetFileNameWithoutExtension(convertedPath).ToLower();
pathMap.TryAdd(key, convertedPath);
}
}
}
return pathMap;
}
2024-08-24 16:09:27 +00:00
private static Dictionary<string, string> GetTexturePaths(string root)
{
string[] validExtensions = { ".dds", ".png", ".tga", ".pcx", ".gif", ".bmp", ".cel", };
var famOptions = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
var textureOptions = new EnumerationOptions
{
MatchCasing = MatchCasing.CaseInsensitive,
RecurseSubdirectories = true,
};
var pathMap = new Dictionary<string, string>();
foreach (var dir in Directory.EnumerateDirectories(root, "fam", famOptions))
{
foreach (var path in Directory.EnumerateFiles(dir, "*", textureOptions))
{
2024-09-07 07:01:15 +00:00
var convertedPath = ConvertSeparator(path);
var ext = Path.GetExtension(convertedPath);
2024-08-24 16:09:27 +00:00
if (validExtensions.Contains(ext.ToLower()))
{
2024-09-07 07:01:15 +00:00
var key = Path.GetRelativePath(root, convertedPath)[..^ext.Length].ToLower();
pathMap.TryAdd(key, convertedPath);
2024-08-24 16:09:27 +00:00
}
}
}
return pathMap;
}
private static Dictionary<string, string> GetObjectPaths(string root)
{
var dirOptions = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
var binOptions = new EnumerationOptions
{
MatchCasing = MatchCasing.CaseInsensitive,
RecurseSubdirectories = true,
};
var pathMap = new Dictionary<string, string>();
foreach (var dir in Directory.EnumerateDirectories(root, "obj", dirOptions))
{
foreach (var path in Directory.EnumerateFiles(dir, "*.bin", binOptions))
{
2024-09-07 07:01:15 +00:00
var convertedPath = ConvertSeparator(path);
var key = Path.GetRelativePath(dir, convertedPath).ToLower();
pathMap.TryAdd(key, convertedPath);
2024-08-24 16:09:27 +00:00
}
}
return pathMap;
}
private static bool TryBuildOmsPathMap(string root, string cfgPath, out Dictionary<string, string> map)
{
map = new Dictionary<string, string>();
var omsPath = "";
foreach (var line in File.ReadLines(cfgPath))
{
if (line.StartsWith("load_path"))
{
2024-09-07 07:01:15 +00:00
// TODO: This can have multiple paths I think
var path = ConvertSeparator(line.Split(" ")[1]);
2024-08-24 16:09:27 +00:00
omsPath = Path.GetFullPath(root + path);
break;
}
}
if (omsPath == "")
{
return false;
}
var searchOptions = new EnumerationOptions
{
MatchCasing = MatchCasing.CaseInsensitive,
};
2024-08-25 08:49:27 +00:00
foreach (var path in Directory.GetFiles(omsPath, "*.mis", searchOptions))
2024-08-24 16:09:27 +00:00
{
2024-09-07 07:01:15 +00:00
var convertedPath = ConvertSeparator(path);
var baseName = Path.GetFileName(convertedPath).ToLower();
map.Add(baseName, convertedPath);
2024-08-24 16:09:27 +00:00
}
return true;
}
private static bool TryBuildFmsPathMap(
string root,
out Dictionary<string, Dictionary<string, string>> fmsMap,
out string fmsPath)
{
fmsMap = new Dictionary<string, Dictionary<string, string>>();
fmsPath = root + "/FMs";
var searchOptions = new EnumerationOptions
{
MatchCasing = MatchCasing.CaseInsensitive,
RecurseSubdirectories = true
};
// Cam Mod tells us where any FMs are installed (alongside other things)
// If it doesn't exist then something is wrong with the install directory
if (!TryGetPath(root, "cam_mod.ini", out var camModPath))
{
return false;
}
// We're looking for an uncommented line defining an fm_path, but its
// find if we don't find one because there's a default path.
foreach (var line in File.ReadLines(camModPath))
{
if (line.StartsWith("fm_path"))
{
// TODO: I think this can technically contain multiple paths
2024-09-07 07:01:15 +00:00
var path = ConvertSeparator(line.Split(" ")[1]);
2024-08-24 16:09:27 +00:00
fmsPath = Path.GetFullPath(root + path);
break;
}
}
// Now we can iterate through all the FM directories and map their mission paths
// if they have any.
string[] extensions = { ".mis", ".cow" };
searchOptions.RecurseSubdirectories = false;
foreach (var dir in Directory.GetDirectories(fmsPath))
{
var campaignMap = new Dictionary<string, string>();
foreach (var path in Directory.GetFiles(dir))
{
2024-09-07 07:01:15 +00:00
var convertedPath = ConvertSeparator(path);
if (extensions.Contains(Path.GetExtension(convertedPath).ToLower()))
2024-08-24 16:09:27 +00:00
{
2024-09-07 07:01:15 +00:00
var baseName = Path.GetFileName(convertedPath).ToLower();
campaignMap.Add(baseName, ConvertSeparator(path));
2024-08-24 16:09:27 +00:00
}
}
if (campaignMap.Count != 0)
{
fmsMap.Add(Path.GetFileName(dir), campaignMap);
}
}
return true;
}
private static bool DirContainsThiefExe(string dir)
{
var searchOptions = new EnumerationOptions
{
MatchCasing = MatchCasing.CaseInsensitive,
};
foreach (var path in Directory.GetFiles(dir, "*.exe", searchOptions))
{
var baseName = Path.GetFileName(path).ToLower();
if (baseName.Contains("thief"))
{
return true;
}
}
return false;
}
private static bool TryGetInstallCfgPath(string dir, out string installCfgPath)
{
var searchOptions = new EnumerationOptions
{
MatchCasing = MatchCasing.CaseInsensitive,
RecurseSubdirectories = true
};
foreach (var path in Directory.GetFiles(dir, "*.cfg", searchOptions))
{
var baseName = Path.GetFileName(path).ToLower();
if (baseName == "install.cfg" || baseName == "darkinst.cfg")
{
installCfgPath = path;
return true;
}
}
installCfgPath = "";
return false;
}
private static bool TryGetPath(string dir, string searchPattern, out string path)
{
var searchOptions = new EnumerationOptions
{
MatchCasing = MatchCasing.CaseInsensitive,
RecurseSubdirectories = true
};
var paths = Directory.GetFiles(dir, searchPattern, searchOptions);
if (paths.Length > 0)
{
path = paths[0];
return true;
}
path = "";
return false;
}
}