Compare commits
No commits in common. "cb2f016455d07e508fd23a8235ec55013b28ecdf" and "b9172df8c3792ad30678561f59291629b55f5034" have entirely different histories.
cb2f016455
...
b9172df8c3
|
@ -1,22 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
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,13 +98,9 @@ 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(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,362 +0,0 @@
|
||||||
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,12 +1,10 @@
|
||||||
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;
|
||||||
|
|
||||||
|
@ -39,19 +37,17 @@ 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) =>
|
||||||
{
|
{
|
||||||
var inited = _installPaths.Init(rootPath);
|
_installPaths = new InstallPaths(rootPath);
|
||||||
GD.Print($"Inited paths: {inited}");
|
|
||||||
FileName = missionPath;
|
FileName = missionPath;
|
||||||
Build = true;
|
Build = true;
|
||||||
};
|
};
|
||||||
|
@ -94,53 +90,11 @@ public partial class Mission : Node3D
|
||||||
{
|
{
|
||||||
ClearMap();
|
ClearMap();
|
||||||
|
|
||||||
// TODO: This shouldn't be set for things that aren't actually FMs
|
_textureLoader = new TextureLoader(_installPaths.famPath, FileName.GetBaseDir());
|
||||||
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);
|
||||||
if (
|
UseChunk<BrList>("BRLIST", PlaceObjects);
|
||||||
_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)
|
||||||
|
@ -155,11 +109,7 @@ public partial class Mission : Node3D
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PlaceObjects(
|
private void PlaceObjects(BrList brList)
|
||||||
BrList brList,
|
|
||||||
PropertyModelName modelNames,
|
|
||||||
LinkChunk metapropLink,
|
|
||||||
LinkDataMetaProp metaPropLinkData)
|
|
||||||
{
|
{
|
||||||
foreach (var brush in brList.Brushes)
|
foreach (var brush in brList.Brushes)
|
||||||
{
|
{
|
||||||
|
@ -168,68 +118,12 @@ public partial class Mission : Node3D
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Build an actual hierarchy and such :)
|
|
||||||
// TODO: We need to load the gamesys :)
|
|
||||||
// Determine if we have a model name :))
|
|
||||||
var id = (int)brush.brushInfo;
|
|
||||||
var modelName = "";
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// See if there's a modelname property
|
|
||||||
foreach (var prop in modelNames.properties)
|
|
||||||
{
|
|
||||||
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 pos = brush.position.ToGodotVec3();
|
||||||
var model = new Model();
|
var cube = new CsgBox3D
|
||||||
model.Position = pos;
|
|
||||||
if (objPath != null)
|
|
||||||
{
|
{
|
||||||
model.BuildModel("", objPath);
|
Position = pos
|
||||||
}
|
};
|
||||||
AddChild(model);
|
AddChild(cube);
|
||||||
|
|
||||||
// var pos = brush.position.ToGodotVec3();
|
|
||||||
// var cube = new CsgBox3D
|
|
||||||
// {
|
|
||||||
// Position = pos
|
|
||||||
// };
|
|
||||||
// AddChild(cube);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,7 +354,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];
|
||||||
|
@ -473,7 +367,7 @@ public partial class Mission : Node3D
|
||||||
}
|
}
|
||||||
path += item.Name;
|
path += item.Name;
|
||||||
|
|
||||||
if (!_textureLoader.Load(_installPaths, i, path))
|
if (!_textureLoader.Load(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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BuildModel(string rootPath, string modelPath)
|
private 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. Not only is it a hack, it doesn't support custom models
|
// TODO: Remove this disgusting hack
|
||||||
var baseDir = ProjectSettings.GlobalizePath($"user://objects/tmp");
|
var baseDir = ProjectSettings.GlobalizePath($"user://objects/tmp");
|
||||||
var options = new EnumerationOptions
|
var options = new EnumerationOptions
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,21 +1,45 @@
|
||||||
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 _fmName;
|
private readonly string _userTexturesPath;
|
||||||
|
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 fmName)
|
public TextureLoader(string rootFamPath, string fmPath)
|
||||||
{
|
{
|
||||||
_fmName = fmName;
|
_rootFamPath = rootFamPath;
|
||||||
|
_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()
|
||||||
|
@ -26,28 +50,46 @@ public partial class TextureLoader
|
||||||
_textureCache.Add(texture);
|
_textureCache.Add(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Load(ResourcePathManager installManager, int id, string path)
|
private static void RegisterTexturePaths(string rootDir, Dictionary<string, string> map)
|
||||||
{
|
{
|
||||||
var loaded = false;
|
// TODO: Load DDS BMP GIF CEL
|
||||||
string texPath;
|
string[] validExtensions = { "png", "tga", "pcx", "gif" };
|
||||||
if (_fmName != null)
|
|
||||||
|
var famOptions = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
|
||||||
|
var textureOptions = new EnumerationOptions
|
||||||
{
|
{
|
||||||
texPath = installManager.GetTexturePath(_fmName, path);
|
MatchCasing = MatchCasing.CaseInsensitive,
|
||||||
texPath ??= installManager.GetTexturePath(path);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
texPath = installManager.GetTexturePath(path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (texPath != null)
|
GD.Print($"Registered {map.Count} texture paths at : {rootDir}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Load(int id, string path)
|
||||||
{
|
{
|
||||||
var texture = LoadTexture(texPath);
|
var loaded = false;
|
||||||
if (texture != null)
|
if (_fmTexturePaths.TryGetValue(path.ToLower(), out var fmTexPath))
|
||||||
{
|
{
|
||||||
_textureCache.Add(texture);
|
_textureCache.Add(LoadTexture(fmTexPath));
|
||||||
loaded = true;
|
loaded = true;
|
||||||
}
|
}
|
||||||
|
else if (_rootTexturePaths.TryGetValue(path.ToLower(), out var rootTexPath))
|
||||||
|
{
|
||||||
|
_textureCache.Add(LoadTexture(rootTexPath));
|
||||||
|
loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var index = loaded ? _textureCache.Count - 1 : 0;
|
var index = loaded ? _textureCache.Count - 1 : 0;
|
||||||
|
@ -59,9 +101,6 @@ 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();
|
||||||
string[] validExtensions = { "png", "tga", "pcx", "gif" };
|
|
||||||
if (validExtensions.Contains(ext))
|
|
||||||
{
|
|
||||||
var texture = ext switch
|
var texture = ext switch
|
||||||
{
|
{
|
||||||
"pcx" => LoadPcx(path),
|
"pcx" => LoadPcx(path),
|
||||||
|
@ -70,8 +109,6 @@ public partial class TextureLoader
|
||||||
};
|
};
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImageTexture Get(int id)
|
public ImageTexture Get(int id)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue