Compare commits
8 Commits
b9172df8c3
...
cb2f016455
Author | SHA1 | Date |
---|---|---|
Jarrod Doyle | cb2f016455 | |
Jarrod Doyle | de4f3d6381 | |
Jarrod Doyle | 9fd76815a2 | |
Jarrod Doyle | 62ff5306fe | |
Jarrod Doyle | c48eb018f5 | |
Jarrod Doyle | db293791e0 | |
Jarrod Doyle | acc3add512 | |
Jarrod Doyle | 0eb918d863 |
|
@ -0,0 +1,22 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
public class GamFile : IChunk
|
||||||
|
{
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
public string fileName;
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(256)).Replace("\0", string.Empty);
|
||||||
|
fileName = tmpName[..Math.Min(255, tmpName.Length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
public record LinkId
|
||||||
|
{
|
||||||
|
private readonly uint _data;
|
||||||
|
|
||||||
|
public LinkId(uint data)
|
||||||
|
{
|
||||||
|
_data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GetId()
|
||||||
|
{
|
||||||
|
return _data & 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsConcrete()
|
||||||
|
{
|
||||||
|
return (_data & 0xF0000) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GetRelation()
|
||||||
|
{
|
||||||
|
return (_data >> 20) & 0xFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GetRaw()
|
||||||
|
{
|
||||||
|
return _data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LinkChunk : IChunk
|
||||||
|
{
|
||||||
|
public record Link
|
||||||
|
{
|
||||||
|
public LinkId linkId;
|
||||||
|
public int source;
|
||||||
|
public int destination;
|
||||||
|
public ushort relation;
|
||||||
|
|
||||||
|
public Link(BinaryReader reader)
|
||||||
|
{
|
||||||
|
linkId = new LinkId(reader.ReadUInt32());
|
||||||
|
source = reader.ReadInt32();
|
||||||
|
destination = reader.ReadInt32();
|
||||||
|
relation = reader.ReadUInt16();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
public List<Link> links;
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
links = new List<Link>();
|
||||||
|
while (reader.BaseStream.Position < entry.Offset + entry.Size + 24)
|
||||||
|
{
|
||||||
|
links.Add(new Link(reader));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LinkDataMetaProp : IChunk
|
||||||
|
{
|
||||||
|
public record LinkData
|
||||||
|
{
|
||||||
|
public LinkId linkId;
|
||||||
|
public int priority;
|
||||||
|
|
||||||
|
public LinkData(BinaryReader reader)
|
||||||
|
{
|
||||||
|
linkId = new LinkId(reader.ReadUInt32());
|
||||||
|
priority = reader.ReadInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
public int DataSize;
|
||||||
|
public List<LinkData> linkData;
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
DataSize = reader.ReadInt32();
|
||||||
|
linkData = new List<LinkData>();
|
||||||
|
while (reader.BaseStream.Position < entry.Offset + entry.Size + 24)
|
||||||
|
{
|
||||||
|
linkData.Add(new LinkData(reader));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
public class PropertyModelName : IChunk
|
||||||
|
{
|
||||||
|
public record Property
|
||||||
|
{
|
||||||
|
public int objectId;
|
||||||
|
public int length;
|
||||||
|
public string modelName;
|
||||||
|
|
||||||
|
public Property(BinaryReader reader)
|
||||||
|
{
|
||||||
|
objectId = reader.ReadInt32();
|
||||||
|
length = (int)reader.ReadUInt32();
|
||||||
|
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(length)).Replace("\0", string.Empty);
|
||||||
|
modelName = tmpName[..Math.Min(length - 1, tmpName.Length)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
public List<Property> properties;
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
properties = new List<Property>();
|
||||||
|
while (reader.BaseStream.Position < entry.Offset + entry.Size + 24)
|
||||||
|
{
|
||||||
|
properties.Add(new Property(reader));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -98,9 +98,13 @@ public class DbFile
|
||||||
{
|
{
|
||||||
// "AI_ROOM_DB" => new AiRoomDb(),
|
// "AI_ROOM_DB" => new AiRoomDb(),
|
||||||
// "AICONVERSE" => new AiConverseChunk(),
|
// "AICONVERSE" => new AiConverseChunk(),
|
||||||
|
"GAM_FILE" => new GamFile(),
|
||||||
"TXLIST" => new TxList(),
|
"TXLIST" => new TxList(),
|
||||||
"WREXT" => new WorldRep(),
|
"WREXT" => new WorldRep(),
|
||||||
"BRLIST" => new BrList(),
|
"BRLIST" => new BrList(),
|
||||||
|
"P$ModelName" => new PropertyModelName(),
|
||||||
|
"LD$MetaProp" => new LinkDataMetaProp(),
|
||||||
|
_ when entryName.StartsWith("L$") => new LinkChunk(),
|
||||||
_ => new GenericChunk(),
|
_ => new GenericChunk(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,362 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS;
|
||||||
|
|
||||||
|
// TODO: Support FMs resource paths!
|
||||||
|
public class ResourcePathManager
|
||||||
|
{
|
||||||
|
private record CampaignResources
|
||||||
|
{
|
||||||
|
public Dictionary<string, string> missionPathMap;
|
||||||
|
public Dictionary<string, string> texturePathMap;
|
||||||
|
public Dictionary<string, string> objectPathMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _initialised = false;
|
||||||
|
private readonly string _extractionPath;
|
||||||
|
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);
|
||||||
|
|
||||||
|
_omResources = new CampaignResources
|
||||||
|
{
|
||||||
|
missionPathMap = omsMap,
|
||||||
|
texturePathMap = texturePathMap,
|
||||||
|
objectPathMap = objectPathMap,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
_fmResources = new Dictionary<string, CampaignResources>();
|
||||||
|
foreach (var (campaign, missionPathMap) in fmsMap)
|
||||||
|
{
|
||||||
|
var texturePathMap = GetTexturePaths(Path.Join(fmsDir, campaign));
|
||||||
|
var objectPathMap = GetObjectPaths(Path.Join(fmsDir, campaign));
|
||||||
|
var resource = new CampaignResources
|
||||||
|
{
|
||||||
|
missionPathMap = missionPathMap,
|
||||||
|
texturePathMap = texturePathMap,
|
||||||
|
objectPathMap = objectPathMap,
|
||||||
|
};
|
||||||
|
_fmResources.Add(campaign, resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_initialised = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetMissionPath(string missionName)
|
||||||
|
{
|
||||||
|
if (!_initialised) return null;
|
||||||
|
|
||||||
|
if (_omResources.missionPathMap.TryGetValue(missionName, out var path))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetMissionPath(string campaignName, string missionName)
|
||||||
|
{
|
||||||
|
if (!_initialised) return null;
|
||||||
|
|
||||||
|
if (_fmResources.TryGetValue(campaignName, out var campaign) &&
|
||||||
|
campaign.missionPathMap.TryGetValue(missionName, out var path))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetTexturePath(string textureName)
|
||||||
|
{
|
||||||
|
if (!_initialised) return null;
|
||||||
|
|
||||||
|
textureName = textureName.ToLower();
|
||||||
|
if (_omResources.texturePathMap.TryGetValue(textureName, out var path))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetTexturePath(string campaignName, string textureName)
|
||||||
|
{
|
||||||
|
if (!_initialised) return null;
|
||||||
|
|
||||||
|
textureName = textureName.ToLower();
|
||||||
|
if (_fmResources.TryGetValue(campaignName, out var campaign) &&
|
||||||
|
campaign.texturePathMap.TryGetValue(textureName, out var path))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetObjectPath(string objectName)
|
||||||
|
{
|
||||||
|
if (!_initialised) return null;
|
||||||
|
|
||||||
|
objectName = objectName.ToLower();
|
||||||
|
if (_omResources.objectPathMap.TryGetValue(objectName, out var path))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetObjectPath(string campaignName, string objectName)
|
||||||
|
{
|
||||||
|
if (!_initialised) return null;
|
||||||
|
|
||||||
|
objectName = objectName.ToLower();
|
||||||
|
if (_fmResources.TryGetValue(campaignName, out var campaign) &&
|
||||||
|
campaign.objectPathMap.TryGetValue(objectName, out var path))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle object textures?
|
||||||
|
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(root, "*.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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
using KeepersCompound.LGS;
|
||||||
using KeepersCompound.LGS.Database;
|
using KeepersCompound.LGS.Database;
|
||||||
using KeepersCompound.LGS.Database.Chunks;
|
using KeepersCompound.LGS.Database.Chunks;
|
||||||
using KeepersCompound.TMV.UI;
|
using KeepersCompound.TMV.UI;
|
||||||
using RectpackSharp;
|
using RectpackSharp;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace KeepersCompound.TMV;
|
namespace KeepersCompound.TMV;
|
||||||
|
|
||||||
|
@ -37,17 +39,19 @@ public partial class Mission : Node3D
|
||||||
[Export]
|
[Export]
|
||||||
public bool Dump = false;
|
public bool Dump = false;
|
||||||
|
|
||||||
|
ResourcePathManager _installPaths;
|
||||||
InstallPaths _installPaths;
|
|
||||||
DbFile _file;
|
DbFile _file;
|
||||||
TextureLoader _textureLoader;
|
TextureLoader _textureLoader;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
|
var extractPath = ProjectSettings.GlobalizePath($"user://extracted/tmp");
|
||||||
|
_installPaths = new ResourcePathManager(extractPath);
|
||||||
var missionSelector = GetNode<Control>("%MissionSelector") as MissionSelector;
|
var missionSelector = GetNode<Control>("%MissionSelector") as MissionSelector;
|
||||||
missionSelector.LoadMission += (string rootPath, string missionPath) =>
|
missionSelector.LoadMission += (string rootPath, string missionPath) =>
|
||||||
{
|
{
|
||||||
_installPaths = new InstallPaths(rootPath);
|
var inited = _installPaths.Init(rootPath);
|
||||||
|
GD.Print($"Inited paths: {inited}");
|
||||||
FileName = missionPath;
|
FileName = missionPath;
|
||||||
Build = true;
|
Build = true;
|
||||||
};
|
};
|
||||||
|
@ -90,11 +94,53 @@ public partial class Mission : Node3D
|
||||||
{
|
{
|
||||||
ClearMap();
|
ClearMap();
|
||||||
|
|
||||||
_textureLoader = new TextureLoader(_installPaths.famPath, FileName.GetBaseDir());
|
// TODO: This shouldn't be set for things that aren't actually FMs
|
||||||
|
var fmName = FileName.GetBaseDir().GetFile();
|
||||||
|
_textureLoader = new TextureLoader(fmName);
|
||||||
_file = new(FileName);
|
_file = new(FileName);
|
||||||
UseChunk<TxList>("TXLIST", LoadTextures);
|
UseChunk<TxList>("TXLIST", LoadTextures);
|
||||||
UseChunk<WorldRep>("WREXT", BuildWrMeshes);
|
UseChunk<WorldRep>("WREXT", BuildWrMeshes);
|
||||||
UseChunk<BrList>("BRLIST", PlaceObjects);
|
if (
|
||||||
|
_file.Chunks.TryGetValue("BRLIST", out var brListRaw) &&
|
||||||
|
_file.Chunks.TryGetValue("P$ModelName", out var modelNamesRaw) &&
|
||||||
|
_file.Chunks.TryGetValue("L$MetaProp", out var metaPropLinksRaw) &&
|
||||||
|
_file.Chunks.TryGetValue("LD$MetaProp", out var metaPropLinkDataRaw)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var brList = (BrList)brListRaw;
|
||||||
|
var modelNames = (PropertyModelName)modelNamesRaw;
|
||||||
|
var metaPropLinks = (LinkChunk)metaPropLinksRaw;
|
||||||
|
var metaPropLinkData = (LinkDataMetaProp)metaPropLinkDataRaw;
|
||||||
|
|
||||||
|
// TODO: Do this somewhere else lol
|
||||||
|
if (_file.Chunks.TryGetValue("GAM_FILE", out var gamFileChunk))
|
||||||
|
{
|
||||||
|
GD.Print("GAM_FILE detected");
|
||||||
|
|
||||||
|
var options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
|
||||||
|
var name = ((GamFile)gamFileChunk).fileName;
|
||||||
|
GD.Print($"Searching for GAM: {FileName.GetBaseDir()}/{name}");
|
||||||
|
var paths = Directory.GetFiles(FileName.GetBaseDir(), name, options);
|
||||||
|
GD.Print($"Found paths: {paths.Length}");
|
||||||
|
if (!paths.IsEmpty())
|
||||||
|
{
|
||||||
|
GD.Print($"Attempting to load GAM at: {paths[0]}");
|
||||||
|
var gamFile = new DbFile(paths[0]);
|
||||||
|
if (gamFile.Chunks.TryGetValue("P$ModelName", out var gamChunk1) &&
|
||||||
|
gamFile.Chunks.TryGetValue("L$MetaProp", out var gamChunk2) &&
|
||||||
|
gamFile.Chunks.TryGetValue("LD$MetaProp", out var gamChunk3))
|
||||||
|
{
|
||||||
|
GD.Print($"Pre-Merged chunks: {modelNames.properties.Count} {metaPropLinks.links.Count} {metaPropLinkData.linkData.Count}");
|
||||||
|
modelNames.properties.AddRange(((PropertyModelName)gamChunk1).properties);
|
||||||
|
metaPropLinks.links.AddRange(((LinkChunk)gamChunk2).links);
|
||||||
|
metaPropLinkData.linkData.AddRange(((LinkDataMetaProp)gamChunk3).linkData);
|
||||||
|
GD.Print($"Post-Merged chunks: {modelNames.properties.Count} {metaPropLinks.links.Count} {metaPropLinkData.linkData.Count}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaceObjects(brList, modelNames, metaPropLinks, metaPropLinkData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UseChunk<T>(string name, Action<T> action)
|
private void UseChunk<T>(string name, Action<T> action)
|
||||||
|
@ -109,7 +155,11 @@ public partial class Mission : Node3D
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PlaceObjects(BrList brList)
|
private void PlaceObjects(
|
||||||
|
BrList brList,
|
||||||
|
PropertyModelName modelNames,
|
||||||
|
LinkChunk metapropLink,
|
||||||
|
LinkDataMetaProp metaPropLinkData)
|
||||||
{
|
{
|
||||||
foreach (var brush in brList.Brushes)
|
foreach (var brush in brList.Brushes)
|
||||||
{
|
{
|
||||||
|
@ -118,12 +168,68 @@ public partial class Mission : Node3D
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pos = brush.position.ToGodotVec3();
|
// TODO: Build an actual hierarchy and such :)
|
||||||
var cube = new CsgBox3D
|
// TODO: We need to load the gamesys :)
|
||||||
|
// Determine if we have a model name :))
|
||||||
|
var id = (int)brush.brushInfo;
|
||||||
|
var modelName = "";
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
Position = pos
|
// See if there's a modelname property
|
||||||
};
|
foreach (var prop in modelNames.properties)
|
||||||
AddChild(cube);
|
{
|
||||||
|
if (prop.objectId == id)
|
||||||
|
{
|
||||||
|
modelName = prop.modelName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (modelName != "") break;
|
||||||
|
|
||||||
|
// No modelname so check for a parent
|
||||||
|
var length = metapropLink.links.Count;
|
||||||
|
var prevId = id;
|
||||||
|
for (var i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
var link = metapropLink.links[i];
|
||||||
|
var linkData = metaPropLinkData.linkData[i];
|
||||||
|
if (link.source == id && linkData.priority == 0)
|
||||||
|
{
|
||||||
|
id = link.destination;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No parent found
|
||||||
|
if (id == prevId)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (modelName == "")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's try and place an object :)
|
||||||
|
var fmName = FileName.GetBaseDir().GetFile();
|
||||||
|
var objPath = _installPaths.GetObjectPath(fmName, modelName + ".bin");
|
||||||
|
objPath ??= _installPaths.GetObjectPath(modelName + ".bin");
|
||||||
|
|
||||||
|
var pos = brush.position.ToGodotVec3();
|
||||||
|
var model = new Model();
|
||||||
|
model.Position = pos;
|
||||||
|
if (objPath != null)
|
||||||
|
{
|
||||||
|
model.BuildModel("", objPath);
|
||||||
|
}
|
||||||
|
AddChild(model);
|
||||||
|
|
||||||
|
// var pos = brush.position.ToGodotVec3();
|
||||||
|
// var cube = new CsgBox3D
|
||||||
|
// {
|
||||||
|
// Position = pos
|
||||||
|
// };
|
||||||
|
// AddChild(cube);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,7 +460,7 @@ public partial class Mission : Node3D
|
||||||
for (var i = 0; i < count; i++)
|
for (var i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
var item = textureList.Items[i];
|
var item = textureList.Items[i];
|
||||||
var path = "/";
|
var path = "";
|
||||||
for (var j = 0; j < item.Tokens.Length; j++)
|
for (var j = 0; j < item.Tokens.Length; j++)
|
||||||
{
|
{
|
||||||
var token = item.Tokens[j];
|
var token = item.Tokens[j];
|
||||||
|
@ -367,7 +473,7 @@ public partial class Mission : Node3D
|
||||||
}
|
}
|
||||||
path += item.Name;
|
path += item.Name;
|
||||||
|
|
||||||
if (!_textureLoader.Load(i, path))
|
if (!_textureLoader.Load(_installPaths, i, path))
|
||||||
{
|
{
|
||||||
GD.Print($"Failed to load texture: {path}");
|
GD.Print($"Failed to load texture: {path}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ public partial class Model : Node3D
|
||||||
modelSelector.LoadModel += BuildModel;
|
modelSelector.LoadModel += BuildModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildModel(string rootPath, string modelPath)
|
public void BuildModel(string rootPath, string modelPath)
|
||||||
{
|
{
|
||||||
foreach (var node in GetChildren())
|
foreach (var node in GetChildren())
|
||||||
{
|
{
|
||||||
|
@ -28,7 +28,7 @@ public partial class Model : Node3D
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove this disgusting hack
|
// TODO: Remove this disgusting hack. Not only is it a hack, it doesn't support custom models
|
||||||
var baseDir = ProjectSettings.GlobalizePath($"user://objects/tmp");
|
var baseDir = ProjectSettings.GlobalizePath($"user://objects/tmp");
|
||||||
var options = new EnumerationOptions
|
var options = new EnumerationOptions
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,45 +1,21 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
using KeepersCompound.LGS;
|
||||||
|
|
||||||
namespace KeepersCompound.TMV;
|
namespace KeepersCompound.TMV;
|
||||||
|
|
||||||
public partial class TextureLoader
|
public partial class TextureLoader
|
||||||
{
|
{
|
||||||
private readonly string _userTexturesPath;
|
private readonly string _fmName;
|
||||||
private readonly string _rootFamPath; // TODO: Load from installation resources
|
|
||||||
private readonly string _fmPath;
|
|
||||||
private readonly Dictionary<string, string> _rootTexturePaths = new();
|
|
||||||
private readonly Dictionary<string, string> _fmTexturePaths = new();
|
|
||||||
|
|
||||||
private readonly List<ImageTexture> _textureCache = new();
|
private readonly List<ImageTexture> _textureCache = new();
|
||||||
private readonly Dictionary<int, int> _idMap = new();
|
private readonly Dictionary<int, int> _idMap = new();
|
||||||
private readonly Dictionary<string, int> _pathMap = new();
|
private readonly Dictionary<string, int> _pathMap = new();
|
||||||
|
|
||||||
public TextureLoader(string rootFamPath, string fmPath)
|
public TextureLoader(string fmName)
|
||||||
{
|
{
|
||||||
_rootFamPath = rootFamPath;
|
_fmName = fmName;
|
||||||
_fmPath = fmPath;
|
|
||||||
_userTexturesPath = ProjectSettings.GlobalizePath($"user://textures/tmp");
|
|
||||||
|
|
||||||
ExtractRootFamFiles();
|
|
||||||
LoadDefaultTexture();
|
LoadDefaultTexture();
|
||||||
RegisterTexturePaths(_fmPath, _fmTexturePaths);
|
|
||||||
RegisterTexturePaths(_userTexturesPath, _rootTexturePaths);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExtractRootFamFiles()
|
|
||||||
{
|
|
||||||
var dir = new DirectoryInfo(_userTexturesPath);
|
|
||||||
if (dir.Exists)
|
|
||||||
{
|
|
||||||
dir.Delete(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
var zip = ZipFile.OpenRead(_rootFamPath);
|
|
||||||
zip.ExtractToDirectory(_userTexturesPath.PathJoin("fam"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadDefaultTexture()
|
private void LoadDefaultTexture()
|
||||||
|
@ -50,46 +26,28 @@ public partial class TextureLoader
|
||||||
_textureCache.Add(texture);
|
_textureCache.Add(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RegisterTexturePaths(string rootDir, Dictionary<string, string> map)
|
public bool Load(ResourcePathManager installManager, int id, string path)
|
||||||
{
|
|
||||||
// TODO: Load DDS BMP GIF CEL
|
|
||||||
string[] validExtensions = { "png", "tga", "pcx", "gif" };
|
|
||||||
|
|
||||||
var famOptions = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
|
|
||||||
var textureOptions = new EnumerationOptions
|
|
||||||
{
|
|
||||||
MatchCasing = MatchCasing.CaseInsensitive,
|
|
||||||
RecurseSubdirectories = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var dirPath in Directory.EnumerateDirectories(rootDir, "fam", famOptions))
|
|
||||||
{
|
|
||||||
foreach (var path in Directory.EnumerateFiles(dirPath, "*", textureOptions))
|
|
||||||
{
|
|
||||||
if (validExtensions.Contains(path.GetExtension().ToLower()))
|
|
||||||
{
|
|
||||||
// TODO: This only adds the first one found rather than the highest priority
|
|
||||||
var key = path.TrimPrefix(rootDir).GetBaseName().ToLower();
|
|
||||||
map.TryAdd(key, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GD.Print($"Registered {map.Count} texture paths at : {rootDir}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Load(int id, string path)
|
|
||||||
{
|
{
|
||||||
var loaded = false;
|
var loaded = false;
|
||||||
if (_fmTexturePaths.TryGetValue(path.ToLower(), out var fmTexPath))
|
string texPath;
|
||||||
|
if (_fmName != null)
|
||||||
{
|
{
|
||||||
_textureCache.Add(LoadTexture(fmTexPath));
|
texPath = installManager.GetTexturePath(_fmName, path);
|
||||||
loaded = true;
|
texPath ??= installManager.GetTexturePath(path);
|
||||||
}
|
}
|
||||||
else if (_rootTexturePaths.TryGetValue(path.ToLower(), out var rootTexPath))
|
else
|
||||||
{
|
{
|
||||||
_textureCache.Add(LoadTexture(rootTexPath));
|
texPath = installManager.GetTexturePath(path);
|
||||||
loaded = true;
|
}
|
||||||
|
|
||||||
|
if (texPath != null)
|
||||||
|
{
|
||||||
|
var texture = LoadTexture(texPath);
|
||||||
|
if (texture != null)
|
||||||
|
{
|
||||||
|
_textureCache.Add(texture);
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var index = loaded ? _textureCache.Count - 1 : 0;
|
var index = loaded ? _textureCache.Count - 1 : 0;
|
||||||
|
@ -101,13 +59,18 @@ public partial class TextureLoader
|
||||||
public static ImageTexture LoadTexture(string path)
|
public static ImageTexture LoadTexture(string path)
|
||||||
{
|
{
|
||||||
var ext = path.GetExtension().ToLower();
|
var ext = path.GetExtension().ToLower();
|
||||||
var texture = ext switch
|
string[] validExtensions = { "png", "tga", "pcx", "gif" };
|
||||||
|
if (validExtensions.Contains(ext))
|
||||||
{
|
{
|
||||||
"pcx" => LoadPcx(path),
|
var texture = ext switch
|
||||||
"gif" => LoadGif(path),
|
{
|
||||||
_ => ImageTexture.CreateFromImage(Image.LoadFromFile(path)),
|
"pcx" => LoadPcx(path),
|
||||||
};
|
"gif" => LoadGif(path),
|
||||||
return texture;
|
_ => ImageTexture.CreateFromImage(Image.LoadFromFile(path)),
|
||||||
|
};
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageTexture Get(int id)
|
public ImageTexture Get(int id)
|
||||||
|
|
Loading…
Reference in New Issue