diff --git a/TACT.Net/SystemFiles/Install/Architectures.cs b/TACT.Net/SystemFiles/Install/Architectures.cs new file mode 100644 index 0000000..8f4b6b8 --- /dev/null +++ b/TACT.Net/SystemFiles/Install/Architectures.cs @@ -0,0 +1,12 @@ +using System; + +namespace TACT.Net.SystemFiles.Install +{ + [Flags] + public enum Architectures + { + x86_32, + x86_64, + Arm64 + } +} diff --git a/TACT.Net/SystemFiles/Install/InstallFile.cs b/TACT.Net/SystemFiles/Install/InstallFile.cs index a8ca57a..967302f 100644 --- a/TACT.Net/SystemFiles/Install/InstallFile.cs +++ b/TACT.Net/SystemFiles/Install/InstallFile.cs @@ -4,7 +4,9 @@ using TACT.Net.BlockTable; using TACT.Net.Common; using TACT.Net.Cryptography; +using TACT.Net.Encoding; using TACT.Net.Network; +using TACT.Net.SystemFiles.Install; using TACT.Net.Tags; namespace TACT.Net.Install @@ -15,12 +17,14 @@ namespace TACT.Net.Install /// public class InstallFile : TagFileBase, ISystemFile { + private const StringComparison StrCmp = StringComparison.OrdinalIgnoreCase; + public string FilePath { get; private set; } public InstallHeader InstallHeader { get; private set; } - public IEnumerable Files => _FileEntries.Values; + public IEnumerable Files => _FileEntries; public MD5Hash Checksum { get; private set; } - private readonly Dictionary _FileEntries; + private readonly List _FileEntries; private readonly EMap[] _EncodingMap; #region Constructors @@ -31,7 +35,7 @@ public class InstallFile : TagFileBase, ISystemFile public InstallFile() { InstallHeader = new InstallHeader(); - _FileEntries = new Dictionary(StringComparer.OrdinalIgnoreCase); + _FileEntries = new List(); _EncodingMap = new[] { @@ -106,11 +110,12 @@ private void Read(Stream stream) ReadTags(br, InstallHeader.TagCount, InstallHeader.EntryCount); // Files + _FileEntries.Capacity = (int)InstallHeader.EntryCount; for (int i = 0; i < InstallHeader.EntryCount; i++) { var fileEntry = new InstallFileEntry(); fileEntry.Read(br, InstallHeader); - _FileEntries.TryAdd(fileEntry.FilePath, fileEntry); + _FileEntries.Add(fileEntry); } Checksum = stream.MD5Hash(); @@ -137,7 +142,7 @@ public CASRecord Write(string directory, TACTRepo tactRepo = null) // File Entry block bt.AddBlock(_EncodingMap[1]); - foreach (var fileEntry in _FileEntries.Values) + foreach (var fileEntry in _FileEntries) fileEntry.Write(bw); // finalise @@ -196,22 +201,33 @@ public void AddOrUpdate(CASRecord record, TACTRepo tactRepo = null) tactRepo?.EncodingFile?.AddOrUpdate(record, tactRepo); } + /// + /// Adds a InstallFileEntry to the InstallFile, this will overwrite existing entries + /// + /// WARNING: This will replace all duplicated InstallFileEntries and override their tags + /// + /// + /// + /// public void AddOrUpdate(InstallFileEntry fileEntry, params string[] tags) { - int index; - if (!_FileEntries.ContainsKey(fileEntry.FilePath)) + bool found = false; + for(var i = 0; i < _FileEntries.Count; i++) { - index = _FileEntries.Count; - _FileEntries.Add(fileEntry.FilePath, fileEntry); - } - else - { - index = _FileEntries.IndexOfKey(x => x.IndexOf(fileEntry.FilePath, StringComparison.OrdinalIgnoreCase) >= 0); - _FileEntries[fileEntry.FilePath] = fileEntry; + if(_FileEntries[i].Equals(fileEntry)) + { + _FileEntries[i] = fileEntry; + SetTags(i, true, tags); + found = true; + } } - // update the tag masks - SetTags(index, true, tags); + if(!found) + { + var index = _FileEntries.Count; + _FileEntries.Add(fileEntry); + SetTags(index, true, tags); + } } public void AddOrUpdate(TagEntry tagEntry) @@ -221,59 +237,176 @@ public void AddOrUpdate(TagEntry tagEntry) /// - /// Removes a InstallFileEntry from the InstallFile + /// Removes an InstallFileEntry from the InstallFile /// /// public bool Remove(InstallFileEntry fileEntry) { - return Remove(fileEntry.FilePath); + var index = _FileEntries.IndexOf(fileEntry); + if (index > -1) + { + _FileEntries.RemoveAt(index); + RemoveFile(index); + } + + return false; } /// - /// Removes a InstallFileEntry from the InstallFile + /// Removes all InstallFileEntries with a specific filename /// /// public bool Remove(string filePath) { - int index = _FileEntries.IndexOfKey(x => x.IndexOf(filePath, StringComparison.OrdinalIgnoreCase) >= 0); - if (index > -1) + bool found = false; + + for (var i = 0; i < _FileEntries.Count; i++) { - _FileEntries.Remove(filePath); - return RemoveFile(index); + if (_FileEntries[i].FilePath.Equals(filePath, StrCmp)) + { + _FileEntries.RemoveAt(i); + RemoveFile(i); + found = true; + i--; + } } - return true; + return found; } /// - /// Returns a InstallFileEntry by name + /// Returns all InstallFileEntries with the supplied filepath /// /// /// /// - public bool TryGet(string filename, out InstallFileEntry fileEntry) + public IEnumerable Get(string filePath) + { + for (var i = 0; i < _FileEntries.Count; i++) + { + if (_FileEntries[i].FilePath.Equals(filePath, StrCmp)) + { + yield return _FileEntries[i]; + } + } + } + /// + /// Returns all InstallFileEntries with the supplied CKey + /// + /// + /// + /// + public IEnumerable Get(MD5Hash ckey) + { + for (var i = 0; i < _FileEntries.Count; i++) + { + if (_FileEntries[i].CKey == ckey) + { + yield return _FileEntries[i]; + } + } + } + + /// + /// Returns a specific InstallFileEntry based on it's filepath + /// filtered by platform and architecture + /// + /// + /// + /// + public InstallFileEntry Get(string filePath, Platforms platform, Architectures arch) + { + if (!_TagEntries.TryGetValue(platform.ToString(), out var osTag)) + return null; + if (!_TagEntries.TryGetValue(arch.ToString(), out var archTag)) + return null; + + for (var i = 0; i < _FileEntries.Count; i++) + { + if (_FileEntries[i].FilePath.Equals(filePath, StrCmp) && + osTag.FileMask[i] && + archTag.FileMask[i]) + { + return _FileEntries[i]; + } + } + + return null; + } + /// + /// Returns a specific InstallFileEntry based on it's CKey + /// filtered by platform and architecture + /// + /// + /// + /// + public InstallFileEntry Get(MD5Hash ckey, Platforms platform, Architectures arch) { - return _FileEntries.TryGetValue(filename, out fileEntry); + if (!_TagEntries.TryGetValue(platform.ToString(), out var osTag)) + return null; + if (!_TagEntries.TryGetValue(arch.ToString(), out var archTag)) + return null; + + for (var i = 0; i < _FileEntries.Count; i++) + { + if (_FileEntries[i].CKey == ckey && osTag.FileMask[i] && archTag.FileMask[i]) + { + return _FileEntries[i]; + } + } + + return null; } /// /// Determines whether the specific filename exists /// - /// + /// + /// + public bool ContainsFilename(string filePath) + { + return _FileEntries.FindIndex(x => x.FilePath.Equals(filePath, StrCmp)) >= 0; + } + /// + /// Determines whether the specific CKey exists + /// + /// /// - public bool ContainsFilename(string filename) => _FileEntries.ContainsKey(filename); + public bool ContainsCKey(MD5Hash ckey) + { + return _FileEntries.FindIndex(x => x.CKey == ckey) >= 0; + } + + /// + /// Opens a stream to the data of the supplied InstallFileEntry. Returns null if not found + /// + /// + /// + public Stream OpenFile(InstallFileEntry fileEntry, TACTRepo tactRepo) + { + if (fileEntry == null || tactRepo == null) + return null; + + if (tactRepo.EncodingFile != null && tactRepo.IndexContainer != null) + { + if (tactRepo.EncodingFile.TryGetCKeyEntry(fileEntry.CKey, out EncodingContentEntry encodingCKey)) + return tactRepo.IndexContainer.OpenFile(encodingCKey.EKeys[0]); + } + + return null; + } + /// /// Returns the Tags associated to a file /// /// /// - public IEnumerable GetTags(string filename) + public IEnumerable GetTags(InstallFileEntry fileEntry) { - int index = _FileEntries.IndexOfKey(x => x.IndexOf(filename, StringComparison.OrdinalIgnoreCase) >= 0); - return GetTags(index); + return GetTags(_FileEntries.IndexOf(fileEntry)); } /// @@ -282,10 +415,9 @@ public IEnumerable GetTags(string filename) /// /// /// - public void SetTags(string filename, bool value, params string[] tags) + public void SetTags(InstallFileEntry fileEntry, bool value, params string[] tags) { - int index = _FileEntries.IndexOfKey(x => x.IndexOf(filename, StringComparison.OrdinalIgnoreCase) >= 0); - SetTags(index, value, tags); + SetTags(_FileEntries.IndexOf(fileEntry), value, tags); } /// diff --git a/TACT.Net/SystemFiles/Install/InstallFileEntry.cs b/TACT.Net/SystemFiles/Install/InstallFileEntry.cs index d2db9cb..0414b6c 100644 --- a/TACT.Net/SystemFiles/Install/InstallFileEntry.cs +++ b/TACT.Net/SystemFiles/Install/InstallFileEntry.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using TACT.Net.Common; using TACT.Net.Cryptography; @@ -37,6 +38,19 @@ public void Write(BinaryWriter bw) #endregion + public override bool Equals(object obj) + { + return obj is InstallFileEntry entry && + entry.FilePath.ToUpper() == FilePath.ToUpper() && + entry.DecompressedSize == DecompressedSize && + entry.CKey == CKey; + } + + public override int GetHashCode() + { + return HashCode.Combine(FilePath.ToUpper(), DecompressedSize, CKey); + } + public override string ToString() => FilePath; } } diff --git a/TACT.Net/SystemFiles/Install/Platforms.cs b/TACT.Net/SystemFiles/Install/Platforms.cs new file mode 100644 index 0000000..3ec4581 --- /dev/null +++ b/TACT.Net/SystemFiles/Install/Platforms.cs @@ -0,0 +1,11 @@ +using System; + +namespace TACT.Net.SystemFiles.Install +{ + public enum Platforms + { + OSX, + Web, + Windows + } +}