422 lines
14 KiB
C#
422 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
|
|
namespace KeepersCompound.LGS;
|
|
|
|
public enum ResourceType
|
|
{
|
|
Mission,
|
|
Object,
|
|
ObjectTexture,
|
|
Texture,
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
private bool _initialised = false;
|
|
private readonly string _extractionPath;
|
|
private string _fmsDir;
|
|
private CampaignResources _omResources;
|
|
private Dictionary<string, CampaignResources> _fmResources;
|
|
|
|
public ResourcePathManager(string extractionPath)
|
|
{
|
|
_extractionPath = extractionPath;
|
|
}
|
|
|
|
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);
|
|
|
|
_omResources = new CampaignResources
|
|
{
|
|
missionPathMap = omsMap,
|
|
texturePathMap = texturePathMap,
|
|
objectPathMap = objectPathMap,
|
|
objectTexturePathMap = objectTexturePathMap,
|
|
};
|
|
}
|
|
|
|
{
|
|
_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);
|
|
|
|
var resource = new CampaignResources
|
|
{
|
|
missionPathMap = missionPathMap,
|
|
texturePathMap = texturePathMap,
|
|
objectPathMap = objectPathMap,
|
|
objectTexturePathMap = objectTexturePathMap,
|
|
};
|
|
_fmResources.Add(campaign, resource);
|
|
_fmsDir = fmsDir;
|
|
}
|
|
}
|
|
|
|
_initialised = true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public List<string> GetCampaignNames()
|
|
{
|
|
if (!_initialised) return null;
|
|
|
|
var names = new List<string>(_fmResources.Keys);
|
|
names.Sort();
|
|
return names;
|
|
}
|
|
|
|
public List<string> GetResourceNames(ResourceType type, string campaignName)
|
|
{
|
|
if (!_initialised)
|
|
{
|
|
throw new InvalidOperationException("Resource Path Manager hasn't been initialised.");
|
|
}
|
|
|
|
if (campaignName == null || campaignName == "")
|
|
{
|
|
return _omResources.GetResourceNames(type);
|
|
}
|
|
else if (_fmResources.TryGetValue(campaignName, out var campaign))
|
|
{
|
|
return campaign.GetResourceNames(type);
|
|
}
|
|
|
|
throw new ArgumentException("No campaign found with given name", nameof(campaignName));
|
|
}
|
|
|
|
public (string, string) GetResourcePath(
|
|
ResourceType type,
|
|
string campaignName,
|
|
string resourceName)
|
|
{
|
|
if (!_initialised)
|
|
{
|
|
throw new InvalidOperationException("Resource Path Manager hasn't been initialised.");
|
|
}
|
|
|
|
resourceName = resourceName.ToLower();
|
|
var omResourcePath = _omResources.GetResourcePath(type, resourceName);
|
|
|
|
if (campaignName == null || campaignName == "" && omResourcePath != null)
|
|
{
|
|
return ("", omResourcePath);
|
|
}
|
|
else if (_fmResources.TryGetValue(campaignName, out var campaign))
|
|
{
|
|
var fmResourcePath = campaign.GetResourcePath(type, resourceName);
|
|
if (fmResourcePath != null)
|
|
{
|
|
return (campaignName, fmResourcePath);
|
|
}
|
|
else if (omResourcePath != null)
|
|
{
|
|
return ("", omResourcePath);
|
|
}
|
|
}
|
|
|
|
// throw new ArgumentException($"No resource found with given type and name: {type}, {resourceName}", nameof(resourceName));
|
|
return (null, null);
|
|
}
|
|
|
|
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))
|
|
{
|
|
var ext = Path.GetExtension(path);
|
|
if (validExtensions.Contains(ext.ToLower()))
|
|
{
|
|
var key = Path.GetFileNameWithoutExtension(path).ToLower();
|
|
pathMap.TryAdd(key, path);
|
|
}
|
|
}
|
|
}
|
|
|
|
return pathMap;
|
|
}
|
|
|
|
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))
|
|
{
|
|
var ext = Path.GetExtension(path);
|
|
if (validExtensions.Contains(ext.ToLower()))
|
|
{
|
|
var key = Path.GetRelativePath(root, path)[..^ext.Length].ToLower();
|
|
pathMap.TryAdd(key, path);
|
|
}
|
|
}
|
|
}
|
|
|
|
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))
|
|
{
|
|
var key = Path.GetRelativePath(dir, path).ToLower();
|
|
pathMap.TryAdd(key, path);
|
|
}
|
|
}
|
|
|
|
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"))
|
|
{
|
|
// TODO: Do we only need to replace on systems with a different path separator?
|
|
var path = line.Split(" ")[1].Replace("\\", "/");
|
|
omsPath = Path.GetFullPath(root + path);
|
|
break;
|
|
}
|
|
}
|
|
if (omsPath == "")
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var searchOptions = new EnumerationOptions
|
|
{
|
|
MatchCasing = MatchCasing.CaseInsensitive,
|
|
};
|
|
foreach (var path in Directory.GetFiles(omsPath, "*.mis", searchOptions))
|
|
{
|
|
var baseName = Path.GetFileName(path).ToLower();
|
|
map.Add(baseName, path);
|
|
}
|
|
|
|
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
|
|
var path = line.Split(" ")[1].Replace("\\", "/");
|
|
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))
|
|
{
|
|
if (extensions.Contains(Path.GetExtension(path).ToLower()))
|
|
{
|
|
var baseName = Path.GetFileName(path).ToLower();
|
|
campaignMap.Add(baseName, path);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
} |