diff --git a/CASCConsole/Program.cs b/CASCConsole/Program.cs index 4bc8a4c1..8301da43 100644 --- a/CASCConsole/Program.cs +++ b/CASCConsole/Program.cs @@ -3,13 +3,14 @@ using System; using System.ComponentModel; using System.IO; +using System.Linq; using System.Text.RegularExpressions; namespace CASCConsole { class Program { - static object progressLock = new object(); + static readonly object ProgressLock = new object(); static void Main(string[] args) { @@ -45,7 +46,7 @@ static void Main(string[] args) Console.WriteLine("Loaded."); - Console.WriteLine("Extract params:", pattern, dest, locale); + Console.WriteLine("Extract params:"); Console.WriteLine(" Pattern: {0}", pattern); Console.WriteLine(" Destination: {0}", dest); Console.WriteLine(" LocaleFlags: {0}", locale); @@ -53,7 +54,7 @@ static void Main(string[] args) Wildcard wildcard = new Wildcard(pattern, true, RegexOptions.IgnoreCase); - foreach (var file in root.GetFiles()) + foreach (var file in CASCFolder.GetFiles(root.Entries.Select(kv => kv.Value))) { if (wildcard.IsMatch(file.FullName)) { @@ -77,7 +78,7 @@ static void Main(string[] args) private static void BgLoader_ProgressChanged(object sender, ProgressChangedEventArgs e) { - lock (progressLock) + lock (ProgressLock) { if (e.UserState != null) Console.WriteLine(e.UserState); @@ -88,7 +89,7 @@ private static void BgLoader_ProgressChanged(object sender, ProgressChangedEvent private static void DrawProgressBar(long complete, long maxVal, int barSize, char progressCharacter) { - float perc = (float)complete / (float)maxVal; + float perc = (float)complete / maxVal; DrawProgressBar(perc, barSize, progressCharacter); } @@ -96,8 +97,8 @@ private static void DrawProgressBar(float percent, int barSize, char progressCha { Console.CursorVisible = false; int left = Console.CursorLeft; - int chars = (int)Math.Round(percent / (1.0f / (float)barSize)); - string p1 = String.Empty, p2 = String.Empty; + int chars = (int)Math.Round(percent / (1.0f / barSize)); + string p1 = string.Empty, p2 = string.Empty; for (int i = 0; i < chars; i++) p1 += progressCharacter; diff --git a/CASCExplorer/AboutBox.Designer.cs b/CASCExplorer/AboutBox.Designer.cs index 75ce1ddd..8e881562 100644 --- a/CASCExplorer/AboutBox.Designer.cs +++ b/CASCExplorer/AboutBox.Designer.cs @@ -1,6 +1,6 @@ namespace CASCExplorer { - partial class AboutBox + sealed partial class AboutBox { /// /// Required designer variable. diff --git a/CASCExplorer/AboutBox.cs b/CASCExplorer/AboutBox.cs index c47effd1..471ec75d 100644 --- a/CASCExplorer/AboutBox.cs +++ b/CASCExplorer/AboutBox.cs @@ -5,17 +5,16 @@ namespace CASCExplorer { - partial class AboutBox : Form + sealed partial class AboutBox : Form { public AboutBox() { InitializeComponent(); - this.Text = String.Format("About {0}", AssemblyTitle); + this.Text = string.Format("About {0}", AssemblyTitle); this.labelProductName.Text = AssemblyProduct; - this.labelVersion.Text = String.Format("Version {0}", AssemblyVersion); + this.labelVersion.Text = string.Format("Version {0}", AssemblyVersion); this.labelCopyright.Text = AssemblyCopyright; - var link = new LinkLabel.Link(); - link.LinkData = Properties.Resources.donateURL; + var link = new LinkLabel.Link {LinkData = Properties.Resources.donateURL}; this.labelDonate.Links.Add(link); this.textBoxDescription.Text = AssemblyDescription; } @@ -89,7 +88,7 @@ public string AssemblyCopyright private void labelDonate_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { - Process.Start(e.Link.LinkData as string); + Process.Start(e.Link?.LinkData as string); } } } diff --git a/CASCExplorer/CASCExplorer.csproj b/CASCExplorer/CASCExplorer.csproj index 0f1d8a95..f0aec6fb 100644 --- a/CASCExplorer/CASCExplorer.csproj +++ b/CASCExplorer/CASCExplorer.csproj @@ -68,8 +68,6 @@ BruteforceForm.cs - - diff --git a/CASCExplorer/CASCViewHelper.cs b/CASCExplorer/CASCViewHelper.cs index af4442ad..53bcca67 100644 --- a/CASCExplorer/CASCViewHelper.cs +++ b/CASCExplorer/CASCViewHelper.cs @@ -19,6 +19,8 @@ class CASCViewHelper private ExtractProgress extractProgress; private CASCHandler _casc; private CASCFolder _root; + private CASCFolder _currentFolder; + private List _displayedEntries; private CASCEntrySorter Sorter = new CASCEntrySorter(); private ScanForm scanForm; private NumberFormatInfo sizeNumberFmt = new NumberFormatInfo() @@ -31,21 +33,17 @@ class CASCViewHelper public event OnStorageChangedDelegate OnStorageChanged; public event OnCleanupDelegate OnCleanup; - public CASCHandler CASC - { - get { return _casc; } - } + public CASCHandler CASC => _casc; - public CASCFolder Root - { - get { return _root; } - } + public CASCFolder Root => _root; + + public CASCFolder CurrentFolder => _currentFolder; + + public List DisplayedEntries => _displayedEntries; public void ExtractFiles(NoFlickerListView filesList) { - CASCFolder folder = filesList.Tag as CASCFolder; - - if (folder == null) + if (_currentFolder == null) return; if (!filesList.HasSelection) @@ -54,7 +52,7 @@ public void ExtractFiles(NoFlickerListView filesList) if (extractProgress == null) extractProgress = new ExtractProgress(); - var files = folder.GetFiles(filesList.SelectedIndices.Cast()).ToList(); + var files = CASCFolder.GetFiles(_displayedEntries, filesList.SelectedIndices.Cast()).ToList(); extractProgress.SetExtractData(_casc, files); extractProgress.ShowDialog(); } @@ -76,7 +74,10 @@ await Task.Run(() => foreach (var file in installFiles) { - _casc.ExtractFile(_casc.Encoding.GetEntry(file.MD5).Key, "data\\" + build + "\\install_files", file.Name); + EncodingEntry enc; + + if (_casc.Encoding.GetEntry(file.MD5, out enc)) + _casc.SaveFileTo(enc.Key, Path.Combine("data", build, "install_files"), file.Name); progress.Report((int)(++numDone / (float)numFiles * 100.0f)); } @@ -118,7 +119,7 @@ await Task.Run(() => } } - if (_casc.FileExists("DBFilesClient\\SoundKit.db2") && _casc.FileExists("DBFilesClient\\SoundKitEntry.db2")) + if (false && _casc.FileExists("DBFilesClient\\SoundKit.db2") && _casc.FileExists("DBFilesClient\\SoundKitEntry.db2")) { using (Stream skStream = _casc.OpenFile("DBFilesClient\\SoundKit.db2")) using (Stream skeStream = _casc.OpenFile("DBFilesClient\\SoundKitEntry.db2")) @@ -168,16 +169,18 @@ await Task.Run(() => if (unknownFolder == null) return; - IEnumerable files = unknownFolder.GetFiles(null, true); + IEnumerable files = CASCFolder.GetFiles(unknownFolder.Entries.Select(kv => kv.Value), null, true); int numTotal = files.Count(); int numDone = 0; + WowRootHandler wowRoot = _casc.Root as WowRootHandler; + foreach (var unknownEntry in files) { CASCFile unknownFile = unknownEntry as CASCFile; string name; - if (idToName.TryGetValue(_casc.Root.GetEntries(unknownFile.Hash).First().FileDataId, out name)) + if (idToName.TryGetValue(wowRoot.GetFileDataIdByHash(unknownFile.Hash), out name)) unknownFile.FullName = name; else { @@ -220,13 +223,14 @@ public void UpdateListView(CASCFolder baseEntry, NoFlickerListView fileList, str Wildcard wildcard = new Wildcard(filter, false, RegexOptions.IgnoreCase); // Sort - baseEntry.Entries = baseEntry.EntriesMirror.Where(v => v.Value is CASCFolder || (v.Value is CASCFile && wildcard.IsMatch(v.Value.Name))). - OrderBy(v => v.Value, Sorter).ToDictionary(pair => pair.Key, pair => pair.Value); + _displayedEntries = baseEntry.Entries.Where(v => v.Value is CASCFolder || (v.Value is CASCFile && wildcard.IsMatch(v.Value.Name))). + OrderBy(v => v.Value, Sorter).Select(kv => kv.Value).ToList(); + + _currentFolder = baseEntry; // Update - fileList.Tag = baseEntry; fileList.VirtualListSize = 0; - fileList.VirtualListSize = baseEntry.Entries.Count; + fileList.VirtualListSize = _displayedEntries.Count; if (fileList.VirtualListSize > 0) { @@ -332,15 +336,13 @@ public void SetSort(int column) public void GetSize(NoFlickerListView fileList) { - CASCFolder folder = fileList.Tag as CASCFolder; - - if (folder == null) + if (_currentFolder == null) return; if (!fileList.HasSelection) return; - var files = folder.GetFiles(fileList.SelectedIndices.Cast()); + var files = CASCFolder.GetFiles(_displayedEntries, fileList.SelectedIndices.Cast()); long size = files.Sum(f => (long)f.GetSize(_casc)); @@ -349,15 +351,13 @@ public void GetSize(NoFlickerListView fileList) public void PreviewFile(NoFlickerListView fileList) { - CASCFolder folder = fileList.Tag as CASCFolder; - - if (folder == null) + if (_currentFolder == null) return; if (!fileList.HasSingleSelection) return; - var file = folder.Entries.ElementAt(fileList.SelectedIndex).Value as CASCFile; + var file = _displayedEntries[fileList.SelectedIndex] as CASCFile; var extension = Path.GetExtension(file.Name); @@ -427,15 +427,15 @@ private void PreviewBlp(CASCFile file) } } - public void CreateListViewItem(RetrieveVirtualItemEventArgs e, CASCFolder folder) + public void CreateListViewItem(RetrieveVirtualItemEventArgs e) { - if (folder == null) + if (_currentFolder == null) return; - if (e.ItemIndex < 0 || e.ItemIndex >= folder.Entries.Count) + if (e.ItemIndex < 0 || e.ItemIndex >= _displayedEntries.Count) return; - ICASCEntry entry = folder.Entries.ElementAt(e.ItemIndex).Value; + ICASCEntry entry = _displayedEntries[e.ItemIndex]; var localeFlags = LocaleFlags.None; var contentFlags = ContentFlags.None; @@ -447,19 +447,16 @@ public void CreateListViewItem(RetrieveVirtualItemEventArgs e, CASCFolder folder if (rootInfosLocale.Any()) { - var enc = _casc.Encoding.GetEntry(rootInfosLocale.First().MD5); + EncodingEntry enc; - if (enc != null) - size = enc.Size.ToString("N", sizeNumberFmt); - else - size = "0"; - - foreach (var rootInfo in rootInfosLocale) + if (_casc.Encoding.GetEntry(rootInfosLocale.First().MD5, out enc)) { - if (rootInfo.Block != null) + size = enc.Size.ToString("N", sizeNumberFmt) ?? "0"; + + foreach (var rootInfo in rootInfosLocale) { - localeFlags |= rootInfo.Block.LocaleFlags; - contentFlags |= rootInfo.Block.ContentFlags; + localeFlags |= rootInfo.LocaleFlags; + contentFlags |= rootInfo.ContentFlags; } } } @@ -469,21 +466,21 @@ public void CreateListViewItem(RetrieveVirtualItemEventArgs e, CASCFolder folder if (installInfos.Any()) { - var enc = _casc.Encoding.GetEntry(installInfos.First().MD5); - - if (enc != null) - size = enc.Size.ToString("N", sizeNumberFmt); - else - size = "0"; - - //foreach (var rootInfo in rootInfosLocale) - //{ - // if (rootInfo.Block != null) - // { - // localeFlags |= rootInfo.Block.LocaleFlags; - // contentFlags |= rootInfo.Block.ContentFlags; - // } - //} + EncodingEntry enc; + + if (_casc.Encoding.GetEntry(installInfos.First().MD5, out enc)) + { + size = enc.Size.ToString("N", sizeNumberFmt) ?? "0"; + + //foreach (var rootInfo in rootInfosLocale) + //{ + // if (rootInfo.Block != null) + // { + // localeFlags |= rootInfo.Block.LocaleFlags; + // contentFlags |= rootInfo.Block.ContentFlags; + // } + //} + } } } } @@ -501,17 +498,18 @@ public void CreateListViewItem(RetrieveVirtualItemEventArgs e, CASCFolder folder public void Cleanup() { - OnCleanup?.Invoke(); - Sorter.CASC = null; + _currentFolder = null; _root = null; - if (_casc != null) - { - _casc.Clear(); - _casc = null; - } + _displayedEntries?.Clear(); + _displayedEntries = null; + + _casc?.Clear(); + _casc = null; + + OnCleanup?.Invoke(); } public void Search(NoFlickerListView fileList, SearchForVirtualItemEventArgs e) @@ -520,8 +518,6 @@ public void Search(NoFlickerListView fileList, SearchForVirtualItemEventArgs e) bool searchUp = false; int SelectedIndex = fileList.SelectedIndex; - CASCFolder folder = fileList.Tag as CASCFolder; - var comparisonType = ignoreCase ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture; @@ -530,7 +526,7 @@ public void Search(NoFlickerListView fileList, SearchForVirtualItemEventArgs e) { for (var i = SelectedIndex - 1; i >= 0; --i) { - var op = folder.Entries.ElementAt(i).Value.Name; + var op = _displayedEntries[i].Name; if (op.IndexOf(e.Text, comparisonType) != -1) { e.Index = i; @@ -542,7 +538,7 @@ public void Search(NoFlickerListView fileList, SearchForVirtualItemEventArgs e) { for (int i = SelectedIndex + 1; i < fileList.Items.Count; ++i) { - var op = folder.Entries.ElementAt(i).Value.Name; + var op = _displayedEntries[i].Name; if (op.IndexOf(e.Text, comparisonType) != -1) { e.Index = i; @@ -556,7 +552,7 @@ public void ExportListFile() { using (StreamWriter sw = new StreamWriter("listfile_export.txt")) { - foreach (var file in Root.GetFiles(null, true).OrderBy(f => f.FullName, StringComparer.OrdinalIgnoreCase)) + foreach (var file in CASCFolder.GetFiles(_root.Entries.Select(kv => kv.Value), null, true).OrderBy(f => f.FullName, StringComparer.OrdinalIgnoreCase)) sw.WriteLine(file.FullName); } } @@ -566,18 +562,18 @@ public void ExtractCASCSystemFiles() if (_casc == null) return; - var files = new Dictionary() - { - { "root", _casc.Encoding.GetEntry(_casc.Config.RootMD5).Key }, - { "install", _casc.Encoding.GetEntry(_casc.Config.InstallMD5).Key }, - { "encoding", _casc.Config.EncodingKey }, - { "download", _casc.Encoding.GetEntry(_casc.Config.DownloadMD5).Key } - }; + EncodingEntry enc; - foreach (var file in files) - { - _casc.ExtractFile(file.Value, ".", file.Key); - } + _casc.SaveFileTo(_casc.Config.EncodingKey, ".", "encoding"); + + if (_casc.Encoding.GetEntry(_casc.Config.RootMD5, out enc)) + _casc.SaveFileTo(enc.Key, ".", "root"); + + if (_casc.Encoding.GetEntry(_casc.Config.InstallMD5, out enc)) + _casc.SaveFileTo(enc.Key, ".", "install"); + + if (_casc.Encoding.GetEntry(_casc.Config.DownloadMD5, out enc)) + _casc.SaveFileTo(enc.Key, ".", "download"); } } } diff --git a/CASCExplorer/InitForm.cs b/CASCExplorer/InitForm.cs index 74ea6349..5419439a 100644 --- a/CASCExplorer/InitForm.cs +++ b/CASCExplorer/InitForm.cs @@ -1,4 +1,5 @@ using CASCExplorer.Properties; +using System; using System.ComponentModel; using System.IO; using System.Windows.Forms; @@ -26,6 +27,7 @@ private void InitForm_FormClosing(object sender, FormClosingEventArgs e) private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { string arg = (string)e.Argument; + CASCConfig.LoadFlags |= LoadFlags.Install; CASCConfig config = _onlineMode ? CASCConfig.LoadOnlineStorageConfig(arg, "us") : CASCConfig.LoadLocalStorageConfig(arg); if (_onlineMode) @@ -55,6 +57,8 @@ private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) var fldr = casc.Root.SetFlags(Settings.Default.LocaleFlags, Settings.Default.ContentFlags); casc.Root.MergeInstall(casc.Install); + GC.Collect(); + e.Result = new object[] { casc, fldr }; } diff --git a/CASCExplorer/MainForm.cs b/CASCExplorer/MainForm.cs index 8223112e..dce1036b 100644 --- a/CASCExplorer/MainForm.cs +++ b/CASCExplorer/MainForm.cs @@ -149,7 +149,7 @@ private void listView1_ColumnClick(object sender, ColumnClickEventArgs e) { viewHelper.SetSort(e.Column); - CASCFolder folder = fileList.Tag as CASCFolder; + CASCFolder folder = viewHelper.CurrentFolder; if (folder == null) return; @@ -159,7 +159,7 @@ private void listView1_ColumnClick(object sender, ColumnClickEventArgs e) private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e) { - viewHelper.CreateListViewItem(e, fileList.Tag as CASCFolder); + viewHelper.CreateListViewItem(e); } private void listView1_MouseDoubleClick(object sender, MouseEventArgs e) @@ -190,7 +190,7 @@ private void listView1_KeyDown(object sender, KeyEventArgs e) private bool NavigateFolder() { // Current folder - CASCFolder folder = fileList.Tag as CASCFolder; + CASCFolder folder = viewHelper.CurrentFolder; if (folder == null) return false; @@ -199,7 +199,7 @@ private bool NavigateFolder() return false; // Selected folder - CASCFolder baseEntry = folder.Entries.ElementAt(fileList.SelectedIndex).Value as CASCFolder; + CASCFolder baseEntry = viewHelper.DisplayedEntries[fileList.SelectedIndex] as CASCFolder; if (baseEntry == null) return false; @@ -222,7 +222,7 @@ private void extractToolStripMenuItem_Click(object sender, EventArgs e) private void contextMenuStrip1_Opening(object sender, CancelEventArgs e) { extractToolStripMenuItem.Enabled = fileList.HasSelection; - copyNameToolStripMenuItem.Enabled = (fileList.HasSelection && (fileList.Tag as CASCFolder).GetFiles(fileList.SelectedIndices.Cast(), false).Count() > 0) || false; + copyNameToolStripMenuItem.Enabled = (fileList.HasSelection && CASCFolder.GetFiles(viewHelper.DisplayedEntries, fileList.SelectedIndices.Cast(), false).Count() > 0) || false; getSizeToolStripMenuItem.Enabled = fileList.HasSelection; } @@ -234,7 +234,7 @@ private void aboutToolStripMenuItem_Click(object sender, EventArgs e) private void copyNameToolStripMenuItem_Click(object sender, EventArgs e) { - CASCFolder folder = fileList.Tag as CASCFolder; + CASCFolder folder = viewHelper.CurrentFolder; if (folder == null) return; @@ -242,7 +242,7 @@ private void copyNameToolStripMenuItem_Click(object sender, EventArgs e) if (!fileList.HasSelection) return; - var files = folder.GetFiles(fileList.SelectedIndices.Cast(), false).Select(f => f.FullName); + var files = CASCFolder.GetFiles(viewHelper.DisplayedEntries, fileList.SelectedIndices.Cast(), false).Select(f => f.FullName); string temp = string.Join(Environment.NewLine, files); @@ -313,6 +313,12 @@ private void contentFlagsToolStripMenuItem_Click(object sender, EventArgs e) private void Cleanup() { fileList.VirtualListSize = 0; + + if (folderTree.Nodes.Count > 0) + { + folderTree.Nodes[0].Tag = null; + } + folderTree.Nodes.Clear(); CDNBuildsToolStripMenuItem.Enabled = false; @@ -336,7 +342,8 @@ private void findToolStripMenuItem_Click(object sender, EventArgs e) if (searchForm == null) searchForm = new SearchForm(fileList); - searchForm.Show(this); + if (!searchForm.Visible) + searchForm.Show(this); } private void fileList_SearchForVirtualItem(object sender, SearchForVirtualItemEventArgs e) @@ -436,7 +443,7 @@ private void exportListfileToolStripMenuItem_Click(object sender, EventArgs e) private void filterToolStripTextBox_TextChanged(object sender, EventArgs e) { - viewHelper.UpdateListView(fileList.Tag as CASCFolder, fileList, filterToolStripTextBox.Text); + viewHelper.UpdateListView(viewHelper.CurrentFolder, fileList, filterToolStripTextBox.Text); } private void openStorageToolStripButton_Click(object sender, EventArgs e) diff --git a/CascLib/BLTEHandler.cs b/CascLib/BLTEHandler.cs index 9e134a52..29f576eb 100644 --- a/CascLib/BLTEHandler.cs +++ b/CascLib/BLTEHandler.cs @@ -22,7 +22,7 @@ class DataBlock { public int CompSize; public int DecompSize; - public byte[] Hash; + public MD5Hash Hash; public byte[] Data; } @@ -37,7 +37,7 @@ class BLTEHandler : IDisposable private const byte ENCRYPTION_ARC4 = 0x41; private const int BLTE_MAGIC = 0x45544c42; - public BLTEHandler(Stream stream, byte[] md5) + public BLTEHandler(Stream stream, MD5Hash md5) { _reader = new BinaryReader(stream, Encoding.ASCII, true); Parse(md5); @@ -65,7 +65,7 @@ public MemoryStream OpenFile(bool leaveOpen = false) return _memStream; } - private void Parse(byte[] md5) + private void Parse(MD5Hash md5) { int size = (int)_reader.BaseStream.Length; @@ -87,8 +87,8 @@ private void Parse(byte[] md5) byte[] newHash = _md5.ComputeHash(_reader.ReadBytes(headerSize > 0 ? headerSize : size)); - if (!newHash.EqualsTo(md5)) - throw new Exception("data corrupted"); + if (!md5.EqualsTo(newHash)) + throw new BLTEDecoderException("data corrupted"); _reader.BaseStream.Position = oldPos; } @@ -126,13 +126,13 @@ private void Parse(byte[] md5) { block.CompSize = _reader.ReadInt32BE(); block.DecompSize = _reader.ReadInt32BE(); - block.Hash = _reader.ReadBytes(16); + block.Hash = _reader.Read(); } else { block.CompSize = size - 8; block.DecompSize = size - 8 - 1; - block.Hash = null; + block.Hash = default(MD5Hash); } blocks[i] = block; @@ -146,11 +146,11 @@ private void Parse(byte[] md5) block.Data = _reader.ReadBytes(block.CompSize); - if (block.Hash != null && CASCConfig.ValidateData) + if (!block.Hash.IsZeroed() && CASCConfig.ValidateData) { byte[] blockHash = _md5.ComputeHash(block.Data); - if (!blockHash.EqualsTo(block.Hash)) + if (!block.Hash.EqualsTo(blockHash)) throw new BLTEDecoderException("MD5 mismatch"); } diff --git a/CascLib/CASCConfig.cs b/CascLib/CASCConfig.cs index b69f686a..c1c8f697 100644 --- a/CascLib/CASCConfig.cs +++ b/CascLib/CASCConfig.cs @@ -9,6 +9,7 @@ namespace CASCExplorer public enum LoadFlags { All = -1, + None = 0, Download = 1, Install = 2, } @@ -124,7 +125,7 @@ public class CASCConfig public CASCGameType GameType { get; private set; } public static bool ValidateData { get; set; } = true; public static bool ThrowOnFileNotFound { get; set; } = true; - public static LoadFlags LoadFlags { get; set; } = LoadFlags.All; + public static LoadFlags LoadFlags { get; set; } = LoadFlags.None; public static CASCConfig LoadOnlineStorageConfig(string product, string region, bool useCurrentBuild = false) { @@ -258,29 +259,29 @@ public static CASCConfig LoadLocalStorageConfig(string basePath) public string Product { get; private set; } - public byte[] RootMD5 + public MD5Hash RootMD5 { - get { return _Builds[ActiveBuild]["root"][0].ToByteArray(); } + get { return _Builds[ActiveBuild]["root"][0].ToByteArray().ToMD5(); } } - public byte[] DownloadMD5 + public MD5Hash DownloadMD5 { - get { return _Builds[ActiveBuild]["download"][0].ToByteArray(); } + get { return _Builds[ActiveBuild]["download"][0].ToByteArray().ToMD5(); } } - public byte[] InstallMD5 + public MD5Hash InstallMD5 { - get { return _Builds[ActiveBuild]["install"][0].ToByteArray(); } + get { return _Builds[ActiveBuild]["install"][0].ToByteArray().ToMD5(); } } - public byte[] EncodingMD5 + public MD5Hash EncodingMD5 { - get { return _Builds[ActiveBuild]["encoding"][0].ToByteArray(); } + get { return _Builds[ActiveBuild]["encoding"][0].ToByteArray().ToMD5(); } } - public byte[] EncodingKey + public MD5Hash EncodingKey { - get { return _Builds[ActiveBuild]["encoding"][1].ToByteArray(); } + get { return _Builds[ActiveBuild]["encoding"][1].ToByteArray().ToMD5(); } } public string BuildUID diff --git a/CascLib/CASCEntry.cs b/CascLib/CASCEntry.cs index 2a3f0137..644ce443 100644 --- a/CascLib/CASCEntry.cs +++ b/CascLib/CASCEntry.cs @@ -17,12 +17,10 @@ public class CASCFolder : ICASCEntry private string _name; public Dictionary Entries { get; set; } - public Dictionary EntriesMirror { get; private set; } public CASCFolder(string name) { Entries = new Dictionary(StringComparer.OrdinalIgnoreCase); - EntriesMirror = new Dictionary(StringComparer.OrdinalIgnoreCase); _name = name; } @@ -43,23 +41,25 @@ public ICASCEntry GetEntry(string name) return entry; } - public IEnumerable GetFiles(IEnumerable selection = null, bool recursive = true) + public static IEnumerable GetFiles(IEnumerable entries, IEnumerable selection = null, bool recursive = true) { if (selection != null) { foreach (int index in selection) { - var entry = Entries.ElementAt(index); + var entry = entries.ElementAt(index); - if (entry.Value is CASCFile) + if (entry is CASCFile) { - yield return entry.Value as CASCFile; + yield return entry as CASCFile; } else { if (recursive) { - foreach (var file in (entry.Value as CASCFolder).GetFiles()) + var folder = entry as CASCFolder; + + foreach (var file in GetFiles(folder.Entries.Select(kv => kv.Value))) { yield return file; } @@ -69,17 +69,19 @@ public IEnumerable GetFiles(IEnumerable selection = null, bool re } else { - foreach (var entry in Entries) + foreach (var entry in entries) { - if (entry.Value is CASCFile) + if (entry is CASCFile) { - yield return entry.Value as CASCFile; + yield return entry as CASCFile; } else { if (recursive) { - foreach (var file in (entry.Value as CASCFolder).GetFiles()) + var folder = entry as CASCFolder; + + foreach (var file in GetFiles(folder.Entries.Select(kv => kv.Value))) { yield return file; } @@ -139,12 +141,8 @@ public ulong Hash public int GetSize(CASCHandler casc) { - var encoding = casc.GetEncodingEntry(hash); - - if (encoding != null) - return encoding.Size; - - return 0; + EncodingEntry enc; + return casc.GetEncodingEntry(hash, out enc) ? enc.Size : 0; } public int CompareTo(ICASCEntry other, int col, CASCHandler casc) @@ -166,8 +164,8 @@ public int CompareTo(ICASCEntry other, int col, CASCHandler casc) { var e1 = casc.Root.GetEntries(Hash); var e2 = casc.Root.GetEntries(other.Hash); - var flags1 = e1.Any() ? e1.First().Block.LocaleFlags : LocaleFlags.None; - var flags2 = e2.Any() ? e2.First().Block.LocaleFlags : LocaleFlags.None; + var flags1 = e1.Any() ? e1.First().LocaleFlags : LocaleFlags.None; + var flags2 = e2.Any() ? e2.First().LocaleFlags : LocaleFlags.None; result = flags1.CompareTo(flags2); } break; @@ -175,8 +173,8 @@ public int CompareTo(ICASCEntry other, int col, CASCHandler casc) { var e1 = casc.Root.GetEntries(Hash); var e2 = casc.Root.GetEntries(other.Hash); - var flags1 = e1.Any() ? e1.First().Block.ContentFlags : ContentFlags.None; - var flags2 = e2.Any() ? e2.First().Block.ContentFlags : ContentFlags.None; + var flags1 = e1.Any() ? e1.First().ContentFlags : ContentFlags.None; + var flags2 = e2.Any() ? e2.First().ContentFlags : ContentFlags.None; result = flags1.CompareTo(flags2); } break; diff --git a/CascLib/CASCHandler.cs b/CascLib/CASCHandler.cs index 7463df06..9f17306c 100644 --- a/CascLib/CASCHandler.cs +++ b/CascLib/CASCHandler.cs @@ -4,13 +4,6 @@ namespace CASCExplorer { - public class IndexEntry - { - public int Index; - public int Offset; - public int Size; - } - public sealed class CASCHandler : CASCHandlerBase { private EncodingHandler EncodingHandler; @@ -18,10 +11,10 @@ public sealed class CASCHandler : CASCHandlerBase private RootHandlerBase RootHandler; private InstallHandler InstallHandler; - public EncodingHandler Encoding { get { return EncodingHandler; } } - public DownloadHandler Download { get { return DownloadHandler; } } - public RootHandlerBase Root { get { return RootHandler; } } - public InstallHandler Install { get { return InstallHandler; } } + public EncodingHandler Encoding => EncodingHandler; + public DownloadHandler Download => DownloadHandler; + public RootHandlerBase Root => RootHandler; + public InstallHandler Install => InstallHandler; private CASCHandler(CASCConfig config, BackgroundWorkerEx worker) : base(config, worker) { @@ -65,7 +58,7 @@ private CASCHandler(CASCConfig config, BackgroundWorkerEx worker) : base(config, else if (config.GameType == CASCGameType.Hearthstone) RootHandler = new HSRootHandler(fs, worker); else if (config.GameType == CASCGameType.Overwatch) - RootHandler = new OWRootHandler(fs, worker, this); + RootHandler = new OwRootHandler(fs, worker, this); else throw new Exception("Unsupported game " + config.BuildUID); } @@ -81,16 +74,15 @@ private CASCHandler(CASCConfig config, BackgroundWorkerEx worker) : base(config, { using (var fs = OpenInstallFile(EncodingHandler, this)) InstallHandler = new InstallHandler(fs, worker); + + InstallHandler.Print(); } Logger.WriteLine("CASCHandler: loaded {0} install data", InstallHandler.Count); } } - public static CASCHandler OpenStorage(CASCConfig config, BackgroundWorkerEx worker = null) - { - return Open(worker, config); - } + public static CASCHandler OpenStorage(CASCConfig config, BackgroundWorkerEx worker = null) => Open(worker, config); public static CASCHandler OpenLocalStorage(string basePath, BackgroundWorkerEx worker = null) { @@ -114,46 +106,35 @@ private static CASCHandler Open(BackgroundWorkerEx worker, CASCConfig config) } } - public bool FileExists(int fileDataId) + public override bool FileExists(int fileDataId) { WowRootHandler rh = Root as WowRootHandler; - if (rh != null) - { - var hash = rh.GetHashByFileDataId(fileDataId); - - return FileExists(hash); - } + if (rh == null) + return false; - return false; + return FileExists(rh.GetHashByFileDataId(fileDataId)); } - public bool FileExists(string file) - { - var hash = Hasher.ComputeHash(file); - return FileExists(hash); - } + public override bool FileExists(string file) => FileExists(Hasher.ComputeHash(file)); - public bool FileExists(ulong hash) - { - var rootInfos = RootHandler.GetAllEntries(hash); - return rootInfos != null && rootInfos.Any(); - } + public override bool FileExists(ulong hash) => RootHandler.GetAllEntries(hash).Any(); - public EncodingEntry GetEncodingEntry(ulong hash) + public bool GetEncodingEntry(ulong hash, out EncodingEntry enc) { var rootInfos = RootHandler.GetEntries(hash); if (rootInfos.Any()) - return EncodingHandler.GetEntry(rootInfos.First().MD5); + return EncodingHandler.GetEntry(rootInfos.First().MD5, out enc); if ((CASCConfig.LoadFlags & LoadFlags.Install) != 0) { var installInfos = Install.GetEntries().Where(e => Hasher.ComputeHash(e.Name) == hash); if (installInfos.Any()) - return EncodingHandler.GetEntry(installInfos.First().MD5); + return EncodingHandler.GetEntry(installInfos.First().MD5, out enc); } - return null; + enc = default(EncodingEntry); + return false; } public override Stream OpenFile(int fileDataId) @@ -161,29 +142,20 @@ public override Stream OpenFile(int fileDataId) WowRootHandler rh = Root as WowRootHandler; if (rh != null) - { - var hash = rh.GetHashByFileDataId(fileDataId); - - return OpenFile(hash); - } + return OpenFile(rh.GetHashByFileDataId(fileDataId)); if (CASCConfig.ThrowOnFileNotFound) throw new FileNotFoundException("FileData: " + fileDataId.ToString()); return null; } - public override Stream OpenFile(string name) - { - var hash = Hasher.ComputeHash(name); - - return OpenFile(hash); - } + public override Stream OpenFile(string name) => OpenFile(Hasher.ComputeHash(name)); public override Stream OpenFile(ulong hash) { - EncodingEntry encInfo = GetEncodingEntry(hash); + EncodingEntry encInfo; - if (encInfo != null) + if (GetEncodingEntry(hash, out encInfo)) return OpenFile(encInfo.Key); if (CASCConfig.ThrowOnFileNotFound) @@ -191,20 +163,13 @@ public override Stream OpenFile(ulong hash) return null; } - public void SaveFileTo(string fullName, string extractPath) - { - var hash = Hasher.ComputeHash(fullName); - - SaveFileTo(hash, extractPath, fullName); - } - - public void SaveFileTo(ulong hash, string extractPath, string fullName) + public override void SaveFileTo(ulong hash, string extractPath, string fullName) { - EncodingEntry encInfo = GetEncodingEntry(hash); + EncodingEntry encInfo; - if (encInfo != null) + if (GetEncodingEntry(hash, out encInfo)) { - ExtractFile(encInfo.Key, extractPath, fullName); + SaveFileTo(encInfo.Key, extractPath, fullName); return; } @@ -212,9 +177,27 @@ public void SaveFileTo(ulong hash, string extractPath, string fullName) throw new FileNotFoundException(fullName); } + protected override Stream OpenFileOnline(MD5Hash key) + { + IndexEntry idxInfo = CDNIndex.GetIndexInfo(key); + return OpenFileLocalInternal(idxInfo, key); + } + + protected override Stream GetLocalDataStream(MD5Hash key) + { + IndexEntry idxInfo = LocalIndex.GetIndexInfo(key); + return GetLocalDataStreamInternal(idxInfo, key); + } + + protected override void ExtractFileOnline(MD5Hash key, string path, string name) + { + IndexEntry idxInfo = CDNIndex.GetIndexInfo(key); + ExtractFileOnlineInternal(idxInfo, key, path, name); + } + public void Clear() { - CDNIndex.Clear(); + CDNIndex?.Clear(); CDNIndex = null; foreach (var stream in DataStreams) @@ -222,29 +205,20 @@ public void Clear() DataStreams.Clear(); - EncodingHandler.Clear(); + EncodingHandler?.Clear(); EncodingHandler = null; - if (InstallHandler != null) - { - InstallHandler.Clear(); - InstallHandler = null; - } + InstallHandler?.Clear(); + InstallHandler = null; - if (LocalIndex != null) - { - LocalIndex.Clear(); - LocalIndex = null; - } + LocalIndex?.Clear(); + LocalIndex = null; - RootHandler.Clear(); + RootHandler?.Clear(); RootHandler = null; - if (DownloadHandler != null) - { - DownloadHandler.Clear(); - DownloadHandler = null; - } + DownloadHandler?.Clear(); + DownloadHandler = null; } } } diff --git a/CascLib/CASCHandlerBase.cs b/CascLib/CASCHandlerBase.cs index b50a1b93..8a077938 100644 --- a/CascLib/CASCHandlerBase.cs +++ b/CascLib/CASCHandlerBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text; namespace CASCExplorer { @@ -43,11 +44,18 @@ public CASCHandlerBase(CASCConfig config, BackgroundWorkerEx worker) } } + public abstract bool FileExists(int fileDataId); + public abstract bool FileExists(string file); + public abstract bool FileExists(ulong hash); + public abstract Stream OpenFile(int filedata); public abstract Stream OpenFile(string name); public abstract Stream OpenFile(ulong hash); - public Stream OpenFile(byte[] key) + public void SaveFileTo(string fullName, string extractPath) => SaveFileTo(Hasher.ComputeHash(fullName), extractPath, fullName); + public abstract void SaveFileTo(ulong hash, string extractPath, string fullName); + + public Stream OpenFile(MD5Hash key) { try { @@ -62,10 +70,10 @@ public Stream OpenFile(byte[] key) } } - private Stream OpenFileOnline(byte[] key) - { - IndexEntry idxInfo = CDNIndex.GetIndexInfo(key); + protected abstract Stream OpenFileOnline(MD5Hash key); + protected Stream OpenFileLocalInternal(IndexEntry idxInfo, MD5Hash key) + { if (idxInfo != null) { using (Stream s = CDNIndex.OpenDataFile(idxInfo)) @@ -84,7 +92,7 @@ private Stream OpenFileOnline(byte[] key) } } - private Stream OpenFileLocal(byte[] key) + private Stream OpenFileLocal(MD5Hash key) { Stream stream = GetLocalDataStream(key); @@ -94,22 +102,22 @@ private Stream OpenFileLocal(byte[] key) } } - private Stream GetLocalDataStream(byte[] key) - { - IndexEntry idxInfo = LocalIndex.GetIndexInfo(key); + protected abstract Stream GetLocalDataStream(MD5Hash key); + protected Stream GetLocalDataStreamInternal(IndexEntry idxInfo, MD5Hash key) + { if (idxInfo == null) throw new Exception("local index missing"); Stream dataStream = GetDataStream(idxInfo.Index); dataStream.Position = idxInfo.Offset; - using (BinaryReader reader = new BinaryReader(dataStream, System.Text.Encoding.ASCII, true)) + using (BinaryReader reader = new BinaryReader(dataStream, Encoding.ASCII, true)) { byte[] md5 = reader.ReadBytes(16); Array.Reverse(md5); - if (!md5.EqualsTo(key)) + if (!key.EqualsTo(md5)) throw new Exception("local data corrupted"); int size = reader.ReadInt32(); @@ -127,7 +135,7 @@ private Stream GetLocalDataStream(byte[] key) } } - public void ExtractFile(byte[] key, string path, string name) + public void SaveFileTo(MD5Hash key, string path, string name) { try { @@ -142,10 +150,10 @@ public void ExtractFile(byte[] key, string path, string name) } } - private void ExtractFileOnline(byte[] key, string path, string name) - { - IndexEntry idxInfo = CDNIndex.GetIndexInfo(key); + protected abstract void ExtractFileOnline(MD5Hash key, string path, string name); + protected void ExtractFileOnlineInternal(IndexEntry idxInfo, MD5Hash key, string path, string name) + { if (idxInfo != null) { using (Stream s = CDNIndex.OpenDataFile(idxInfo)) @@ -164,7 +172,7 @@ private void ExtractFileOnline(byte[] key, string path, string name) } } - private void ExtractFileLocal(byte[] key, string path, string name) + private void ExtractFileLocal(MD5Hash key, string path, string name) { Stream stream = GetLocalDataStream(key); @@ -176,9 +184,9 @@ private void ExtractFileLocal(byte[] key, string path, string name) protected static BinaryReader OpenInstallFile(EncodingHandler enc, CASCHandlerBase casc) { - var encInfo = enc.GetEntry(casc.Config.InstallMD5); + EncodingEntry encInfo; - if (encInfo == null) + if (!enc.GetEntry(casc.Config.InstallMD5, out encInfo)) throw new FileNotFoundException("encoding info for install file missing!"); //ExtractFile(encInfo.Key, ".", "install"); @@ -188,9 +196,9 @@ protected static BinaryReader OpenInstallFile(EncodingHandler enc, CASCHandlerBa protected BinaryReader OpenDownloadFile(EncodingHandler enc, CASCHandlerBase casc) { - var encInfo = enc.GetEntry(casc.Config.DownloadMD5); + EncodingEntry encInfo; - if (encInfo == null) + if (!enc.GetEntry(casc.Config.DownloadMD5, out encInfo)) throw new FileNotFoundException("encoding info for download file missing!"); //ExtractFile(encInfo.Key, ".", "download"); @@ -200,9 +208,9 @@ protected BinaryReader OpenDownloadFile(EncodingHandler enc, CASCHandlerBase cas protected BinaryReader OpenRootFile(EncodingHandler enc, CASCHandlerBase casc) { - var encInfo = enc.GetEntry(casc.Config.RootMD5); + EncodingEntry encInfo; - if (encInfo == null) + if (!enc.GetEntry(casc.Config.RootMD5, out encInfo)) throw new FileNotFoundException("encoding info for root file missing!"); //ExtractFile(encInfo.Key, ".", "root"); @@ -217,7 +225,7 @@ protected BinaryReader OpenEncodingFile(CASCHandlerBase casc) return new BinaryReader(casc.OpenFile(casc.Config.EncodingKey)); } - protected Stream GetDataStream(int index) + private Stream GetDataStream(int index) { Stream stream; diff --git a/CascLib/CASCHandlerLite.cs b/CascLib/CASCHandlerLite.cs index 5f9228ad..5b11bcb2 100644 --- a/CascLib/CASCHandlerLite.cs +++ b/CascLib/CASCHandlerLite.cs @@ -6,8 +6,11 @@ namespace CASCExplorer { public sealed class CASCHandlerLite : CASCHandlerBase { - private Dictionary HashToKey = new Dictionary(); - private Dictionary FileDataIdToHash = new Dictionary(); + private readonly Dictionary HashToKey = new Dictionary(); + private readonly Dictionary FileDataIdToHash = new Dictionary(); + private static readonly MD5HashComparer comparer = new MD5HashComparer(); + private readonly Dictionary CDNIndexData; + private readonly Dictionary LocalIndexData; private CASCHandlerLite(CASCConfig config, LocaleFlags locale, BackgroundWorkerEx worker) : base(config, worker) { @@ -28,7 +31,7 @@ private CASCHandlerLite(CASCConfig config, LocaleFlags locale, BackgroundWorkerE Logger.WriteLine("CASCHandlerLite: loading root data..."); - RootHandlerBase RootHandler; + WowRootHandler RootHandler; using (var _ = new PerfCounter("new RootHandler()")) { @@ -40,36 +43,90 @@ private CASCHandlerLite(CASCConfig config, LocaleFlags locale, BackgroundWorkerE RootHandler.SetFlags(locale, ContentFlags.None, false); + CDNIndexData = new Dictionary(comparer); + + if (LocalIndex != null) + LocalIndexData = new Dictionary(comparer); + RootEntry rootEntry; foreach (var entry in RootHandler.GetAllEntries()) { rootEntry = entry.Value; - if ((rootEntry.Block.LocaleFlags == locale || (rootEntry.Block.LocaleFlags & locale) != LocaleFlags.None) && (rootEntry.Block.ContentFlags & ContentFlags.LowViolence) == ContentFlags.None) + if ((rootEntry.LocaleFlags == locale || (rootEntry.LocaleFlags & locale) != LocaleFlags.None) && (rootEntry.ContentFlags & ContentFlags.LowViolence) == ContentFlags.None) { - var enc = EncodingHandler.GetEntry(rootEntry.MD5); + EncodingEntry enc; - if (enc != null) + if (EncodingHandler.GetEntry(rootEntry.MD5, out enc)) { if (!HashToKey.ContainsKey(entry.Key)) { HashToKey.Add(entry.Key, enc.Key); - FileDataIdToHash.Add(rootEntry.FileDataId, entry.Key); + FileDataIdToHash.Add(RootHandler.GetFileDataIdByHash(entry.Key), entry.Key); + + if (LocalIndex != null) + { + IndexEntry iLocal = LocalIndex.GetIndexInfo(enc.Key); + + if (iLocal != null && !LocalIndexData.ContainsKey(enc.Key)) + LocalIndexData.Add(enc.Key, iLocal); + } + + IndexEntry iCDN = CDNIndex.GetIndexInfo(enc.Key); + + if (iCDN != null && !CDNIndexData.ContainsKey(enc.Key)) + CDNIndexData.Add(enc.Key, iCDN); } } } } + CDNIndex.Clear(); + //CDNIndex = null; + LocalIndex?.Clear(); + LocalIndex = null; RootHandler.Clear(); - EncodingHandler.Clear(); RootHandler = null; + EncodingHandler.Clear(); EncodingHandler = null; GC.Collect(); Logger.WriteLine("CASCHandlerLite: loaded {0} files", HashToKey.Count); } + protected override Stream OpenFileOnline(MD5Hash key) + { + IndexEntry idxInfo = CDNIndex.GetIndexInfo(key); + + if (idxInfo == null) + CDNIndexData.TryGetValue(key, out idxInfo); + + return OpenFileLocalInternal(idxInfo, key); + } + + protected override Stream GetLocalDataStream(MD5Hash key) + { + IndexEntry idxInfo; + + if (LocalIndex != null) + idxInfo = LocalIndex.GetIndexInfo(key); + else + LocalIndexData.TryGetValue(key, out idxInfo); + + return GetLocalDataStreamInternal(idxInfo, key); + } + + protected override void ExtractFileOnline(MD5Hash key, string path, string name) + { + IndexEntry idxInfo = CDNIndex.GetIndexInfo(key); + + if (idxInfo == null) + CDNIndexData.TryGetValue(key, out idxInfo); + + ExtractFileOnlineInternal(idxInfo, key, path, name); + } + public static CASCHandlerLite OpenStorage(LocaleFlags locale, CASCConfig config, BackgroundWorkerEx worker = null) { return Open(locale, worker, config); @@ -97,21 +154,11 @@ private static CASCHandlerLite Open(LocaleFlags locale, BackgroundWorkerEx worke } } - public bool FileExists(int fileDataId) - { - return FileDataIdToHash.ContainsKey(fileDataId); - } + public override bool FileExists(int fileDataId) => FileDataIdToHash.ContainsKey(fileDataId); - public bool FileExists(string file) - { - var hash = Hasher.ComputeHash(file); - return FileExists(hash); - } + public override bool FileExists(string file) => FileExists(Hasher.ComputeHash(file)); - public bool FileExists(ulong hash) - { - return HashToKey.ContainsKey(hash); - } + public override bool FileExists(ulong hash) => HashToKey.ContainsKey(hash); public override Stream OpenFile(int filedata) { @@ -123,21 +170,30 @@ public override Stream OpenFile(int filedata) return null; } - public override Stream OpenFile(string name) - { - var hash = Hasher.ComputeHash(name); - - return OpenFile(hash); - } + public override Stream OpenFile(string name) => OpenFile(Hasher.ComputeHash(name)); public override Stream OpenFile(ulong hash) { - byte[] key; + MD5Hash key; if (HashToKey.TryGetValue(hash, out key)) return OpenFile(key); return null; } + + public override void SaveFileTo(ulong hash, string extractPath, string fullName) + { + MD5Hash key; + + if (HashToKey.TryGetValue(hash, out key)) + { + SaveFileTo(key, extractPath, fullName); + return; + } + + if (CASCConfig.ThrowOnFileNotFound) + throw new FileNotFoundException(fullName); + } } } diff --git a/CascLib/CDNCache.cs b/CascLib/CDNCache.cs index 5173dbb6..53b54ef2 100644 --- a/CascLib/CDNCache.cs +++ b/CascLib/CDNCache.cs @@ -7,8 +7,8 @@ namespace CASCExplorer { public class CacheMetaData { - public long Size { get; private set; } - public byte[] MD5 { get; private set; } + public long Size { get; } + public byte[] MD5 { get; } public CacheMetaData(long size, byte[] md5) { @@ -18,7 +18,7 @@ public CacheMetaData(long size, byte[] md5) public void Save(string file) { - File.WriteAllText(file + ".dat", string.Format("{0} {1}", Size, MD5.ToHexString())); + File.WriteAllText(file + ".dat", $"{Size} {MD5.ToHexString()}"); } public static CacheMetaData Load(string file) @@ -44,17 +44,17 @@ public static CacheMetaData AddToCache(HttpWebResponse resp, string file) public class CDNCache { public bool Enabled { get; set; } = true; - private bool CacheData { get; set; } = false; + public bool CacheData { get; set; } = false; public bool Validate { get; set; } = true; - private string cachePath; - private SyncDownloader downloader = new SyncDownloader(null); + private readonly string _cachePath; + private readonly SyncDownloader _downloader = new SyncDownloader(null); - private MD5 md5 = MD5.Create(); + private readonly MD5 _md5 = MD5.Create(); public CDNCache(string path) { - cachePath = path; + _cachePath = path; } public Stream OpenFile(string name, string url, bool isData) @@ -65,18 +65,18 @@ public Stream OpenFile(string name, string url, bool isData) if (isData && !CacheData) return null; - string file = Path.Combine(cachePath, name); + string file = Path.Combine(_cachePath, name); Logger.WriteLine("CDNCache: Opening file {0}", file); FileInfo fi = new FileInfo(file); if (!fi.Exists) - downloader.DownloadFile(url, file); + _downloader.DownloadFile(url, file); if (Validate) { - CacheMetaData meta = CacheMetaData.Load(file) ?? downloader.GetMetaData(url, file); + CacheMetaData meta = CacheMetaData.Load(file) ?? _downloader.GetMetaData(url, file); if (meta == null) throw new Exception(string.Format("unable to validate file {0}", file)); @@ -86,11 +86,11 @@ public Stream OpenFile(string name, string url, bool isData) using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { sizeOk = fs.Length == meta.Size; - md5Ok = md5.ComputeHash(fs).EqualsTo(meta.MD5); + md5Ok = _md5.ComputeHash(fs).EqualsTo(meta.MD5); } if (!sizeOk || !md5Ok) - downloader.DownloadFile(url, file); + _downloader.DownloadFile(url, file); } return new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); @@ -98,7 +98,7 @@ public Stream OpenFile(string name, string url, bool isData) public bool HasFile(string name) { - return File.Exists(Path.Combine(cachePath, name)); + return File.Exists(Path.Combine(_cachePath, name)); } } } diff --git a/CascLib/CDNIndexHandler.cs b/CascLib/CDNIndexHandler.cs index 24207d73..33857995 100644 --- a/CascLib/CDNIndexHandler.cs +++ b/CascLib/CDNIndexHandler.cs @@ -5,24 +5,28 @@ namespace CASCExplorer { + public class IndexEntry + { + public int Index; + public int Offset; + public int Size; + } + public class CDNIndexHandler { - private static readonly ByteArrayComparer comparer = new ByteArrayComparer(); - private readonly Dictionary CDNIndexData = new Dictionary(comparer); + private static readonly MD5HashComparer comparer = new MD5HashComparer(); + private Dictionary CDNIndexData = new Dictionary(comparer); - private CASCConfig CASCConfig; + private CASCConfig config; private BackgroundWorkerEx worker; private SyncDownloader downloader; - public static CDNCache Cache = new CDNCache("cache"); + public static readonly CDNCache Cache = new CDNCache("cache"); - public int Count - { - get { return CDNIndexData.Count; } - } + public int Count => CDNIndexData.Count; private CDNIndexHandler(CASCConfig cascConfig, BackgroundWorkerEx worker) { - CASCConfig = cascConfig; + config = cascConfig; this.worker = worker; downloader = new SyncDownloader(worker); } @@ -61,10 +65,10 @@ private void ParseIndex(Stream stream, int i) for (int j = 0; j < count; ++j) { - byte[] key = br.ReadBytes(16); + MD5Hash key = br.Read(); if (key.IsZeroed()) // wtf? - key = br.ReadBytes(16); + key = br.Read(); if (key.IsZeroed()) // wtf? throw new Exception("key.IsZeroed()"); @@ -83,8 +87,8 @@ private void DownloadIndexFile(string archive, int i) { try { - string file = CASCConfig.CDNPath + "/data/" + archive.Substring(0, 2) + "/" + archive.Substring(2, 2) + "/" + archive + ".index"; - string url = "http://" + CASCConfig.CDNHost + "/" + file; + string file = config.CDNPath + "/data/" + archive.Substring(0, 2) + "/" + archive.Substring(2, 2) + "/" + archive + ".index"; + string url = "http://" + config.CDNHost + "/" + file; Stream stream = Cache.OpenFile(file, url, false); @@ -108,9 +112,9 @@ private void OpenIndexFile(string archive, int i) { try { - string dataFolder = CASCGame.GetDataFolder(CASCConfig.GameType); + string dataFolder = CASCGame.GetDataFolder(config.GameType); - string path = Path.Combine(CASCConfig.BasePath, dataFolder, "indices", archive + ".index"); + string path = Path.Combine(config.BasePath, dataFolder, "indices", archive + ".index"); using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) ParseIndex(fs, i); @@ -123,10 +127,10 @@ private void OpenIndexFile(string archive, int i) public Stream OpenDataFile(IndexEntry entry) { - var archive = CASCConfig.Archives[entry.Index]; + var archive = config.Archives[entry.Index]; - string file = CASCConfig.CDNPath + "/data/" + archive.Substring(0, 2) + "/" + archive.Substring(2, 2) + "/" + archive; - string url = "http://" + CASCConfig.CDNHost + "/" + file; + string file = config.CDNPath + "/data/" + archive.Substring(0, 2) + "/" + archive.Substring(2, 2) + "/" + archive; + string url = "http://" + config.CDNHost + "/" + file; Stream stream = Cache.OpenFile(file, url, true); @@ -150,14 +154,14 @@ public Stream OpenDataFile(IndexEntry entry) } } - public Stream OpenDataFileDirect(byte[] key) + public Stream OpenDataFileDirect(MD5Hash key) { var keyStr = key.ToHexString().ToLower(); worker?.ReportProgress(0, string.Format("Downloading \"{0}\" file...", keyStr)); - string file = CASCConfig.CDNPath + "/data/" + keyStr.Substring(0, 2) + "/" + keyStr.Substring(2, 2) + "/" + keyStr; - string url = "http://" + CASCConfig.CDNHost + "/" + file; + string file = config.CDNPath + "/data/" + keyStr.Substring(0, 2) + "/" + keyStr.Substring(2, 2) + "/" + keyStr; + string url = "http://" + config.CDNHost + "/" + file; Stream stream = Cache.OpenFile(file, url, false); @@ -192,7 +196,7 @@ public static Stream OpenFileDirect(string url) } } - public IndexEntry GetIndexInfo(byte[] key) + public IndexEntry GetIndexInfo(MD5Hash key) { IndexEntry result; @@ -205,6 +209,11 @@ public IndexEntry GetIndexInfo(byte[] key) public void Clear() { CDNIndexData.Clear(); + CDNIndexData = null; + + config = null; + worker = null; + downloader = null; } } } diff --git a/CascLib/CascLib.csproj b/CascLib/CascLib.csproj index a759ecfc..32fcf671 100644 --- a/CascLib/CascLib.csproj +++ b/CascLib/CascLib.csproj @@ -47,13 +47,16 @@ + + + - + diff --git a/CascLib/D3RootHandler.cs b/CascLib/D3RootHandler.cs index 155ac9f4..56125b76 100644 --- a/CascLib/D3RootHandler.cs +++ b/CascLib/D3RootHandler.cs @@ -6,10 +6,10 @@ namespace CASCExplorer { - class D3RootEntry + struct D3RootEntry { + public MD5Hash MD5; public int Type; - public byte[] MD5; public int SNO; public int FileIndex; public string Name; @@ -19,7 +19,7 @@ public static D3RootEntry Read(int type, BinaryReader s) D3RootEntry e = new D3RootEntry(); e.Type = type; - e.MD5 = s.ReadBytes(16); + e.MD5 = s.Read(); if (type == 0 || type == 1) // has SNO id { @@ -60,13 +60,16 @@ public D3RootHandler(BinaryReader stream, BackgroundWorkerEx worker, CASCHandler for (int j = 0; j < count; j++) { - byte[] md5 = stream.ReadBytes(16); + MD5Hash md5 = stream.Read(); string name = stream.ReadCString(); var entries = new List(); D3RootData[name] = entries; - EncodingEntry enc = casc.Encoding.GetEntry(md5); + EncodingEntry enc; + + if (!casc.Encoding.GetEntry(md5, out enc)) + continue; using (BinaryReader s = new BinaryReader(casc.OpenFile(enc.Key))) { @@ -103,7 +106,8 @@ public D3RootHandler(BinaryReader stream, BackgroundWorkerEx worker, CASCHandler // Parse CoreTOC.dat var coreTocEntry = D3RootData["Base"].Find(e => e.Name == "CoreTOC.dat"); - EncodingEntry enc1 = casc.Encoding.GetEntry(coreTocEntry.MD5); + EncodingEntry enc1; + casc.Encoding.GetEntry(coreTocEntry.MD5, out enc1); using (var file = casc.OpenFile(enc1.Key)) tocParser = new CoreTOCParser(file); @@ -113,7 +117,8 @@ public D3RootHandler(BinaryReader stream, BackgroundWorkerEx worker, CASCHandler // Parse Packages.dat var pkgEntry = D3RootData["Base"].Find(e => e.Name == "Data_D3\\PC\\Misc\\Packages.dat"); - EncodingEntry enc2 = casc.Encoding.GetEntry(pkgEntry.MD5); + EncodingEntry enc2; + casc.Encoding.GetEntry(pkgEntry.MD5, out enc2); using (var file = casc.OpenFile(enc2.Key)) pkgParser = new PackagesParser(file); @@ -144,7 +149,7 @@ public override IEnumerable> GetAllEntries() public override IEnumerable GetAllEntries(ulong hash) { - HashSet result; + List result; RootData.TryGetValue(hash, out result); if (result == null) @@ -161,7 +166,7 @@ public override IEnumerable GetEntries(ulong hash) if (!rootInfos.Any()) yield break; - var rootInfosLocale = rootInfos.Where(re => (re.Block.LocaleFlags & Locale) != 0); + var rootInfosLocale = rootInfos.Where(re => (re.LocaleFlags & Locale) != 0); foreach (var entry in rootInfosLocale) yield return entry; @@ -204,12 +209,10 @@ private void AddFile(string pkg, D3RootEntry e) LocaleFlags locale; - entry.Block = new RootBlock(); - if (Enum.TryParse(pkg, out locale)) - entry.Block.LocaleFlags = locale; + entry.LocaleFlags = locale; else - entry.Block.LocaleFlags = LocaleFlags.All; + entry.LocaleFlags = LocaleFlags.All; ulong fileHash = Hasher.ComputeHash(name); CASCFile.FileNames[fileHash] = name; @@ -249,7 +252,7 @@ protected override CASCFolder CreateStorageTree() // Create new tree based on specified locale foreach (var rootEntry in RootData) { - var rootInfosLocale = rootEntry.Value.Where(re => (re.Block.LocaleFlags & Locale) != 0); + var rootInfosLocale = rootEntry.Value.Where(re => (re.LocaleFlags & Locale) != 0); if (!rootInfosLocale.Any()) continue; @@ -347,20 +350,20 @@ public class CoreTOCParser { private const int NUM_SNO_GROUPS = 70; - public struct TOCHeader + public unsafe struct TOCHeader { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = NUM_SNO_GROUPS)] - public int[] entryCounts; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = NUM_SNO_GROUPS)] - public int[] entryOffsets; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = NUM_SNO_GROUPS)] - public int[] entryUnkCounts; + //[MarshalAs(UnmanagedType.ByValArray, SizeConst = NUM_SNO_GROUPS)] + public fixed int entryCounts[NUM_SNO_GROUPS]; + //[MarshalAs(UnmanagedType.ByValArray, SizeConst = NUM_SNO_GROUPS)] + public fixed int entryOffsets[NUM_SNO_GROUPS]; + //[MarshalAs(UnmanagedType.ByValArray, SizeConst = NUM_SNO_GROUPS)] + public fixed int entryUnkCounts[NUM_SNO_GROUPS]; public int unk; } - Dictionary snoDic = new Dictionary(); + readonly Dictionary snoDic = new Dictionary(); - Dictionary extensions = new Dictionary() + readonly Dictionary extensions = new Dictionary() { { SNOGroup.Code, "" }, { SNOGroup.None, "" }, @@ -432,7 +435,7 @@ public struct TOCHeader { SNOGroup.DungeonFinder, "" }, }; - public CoreTOCParser(Stream stream) + public unsafe CoreTOCParser(Stream stream) { using (var br = new BinaryReader(stream)) { @@ -472,7 +475,7 @@ public SNOInfo GetSNO(int id) public class PackagesParser { - Dictionary nameToExtDic = new Dictionary(StringComparer.OrdinalIgnoreCase); + readonly Dictionary nameToExtDic = new Dictionary(StringComparer.OrdinalIgnoreCase); public PackagesParser(Stream stream) { diff --git a/CASCExplorer/DB2Reader.cs b/CascLib/DB2Reader.cs similarity index 91% rename from CASCExplorer/DB2Reader.cs rename to CascLib/DB2Reader.cs index a84e6e6c..b6d740bc 100644 --- a/CASCExplorer/DB2Reader.cs +++ b/CascLib/DB2Reader.cs @@ -6,12 +6,12 @@ namespace CASCExplorer { - class DB2Row + public class DB2Row { - private byte[] m_data; - private DB2Reader m_reader; + private readonly byte[] m_data; + private readonly DB2Reader m_reader; - public byte[] Data { get { return m_data; } } + public byte[] Data => m_data; public DB2Row(DB2Reader reader, byte[] data) { @@ -43,7 +43,7 @@ public T GetField(int field) } } - class DB2Reader : IEnumerable> + public class DB2Reader : IEnumerable> { private const int HeaderSize = 48; private const uint DB2FmtSig = 0x32424457; // WDB2 @@ -58,7 +58,7 @@ class DB2Reader : IEnumerable> private readonly DB2Row[] m_rows; public byte[] StringTable { get; private set; } - Dictionary m_index = new Dictionary(); + readonly Dictionary m_index = new Dictionary(); public DB2Reader(string dbcFile) : this(new FileStream(dbcFile, FileMode.Open)) { } @@ -129,10 +129,9 @@ public bool HasRow(int index) public DB2Row GetRow(int index) { - if (!m_index.ContainsKey(index)) - return null; - - return m_index[index]; + DB2Row row; + m_index.TryGetValue(index, out row); + return row; } public IEnumerator> GetEnumerator() diff --git a/CASCExplorer/DB3Reader.cs b/CascLib/DB3Reader.cs similarity index 91% rename from CASCExplorer/DB3Reader.cs rename to CascLib/DB3Reader.cs index dec7a5c9..d5c1c0d7 100644 --- a/CASCExplorer/DB3Reader.cs +++ b/CascLib/DB3Reader.cs @@ -6,7 +6,7 @@ namespace CASCExplorer { - class DB3Row + public class DB3Row { private byte[] m_data; private DB3Reader m_reader; @@ -63,11 +63,12 @@ public unsafe T GetField(int offset) } } - class DB3Reader : IEnumerable> + public class DB3Reader : IEnumerable> { private readonly int HeaderSize; private const uint DB3FmtSig = 0x33424457; // WDB3 private const uint DB4FmtSig = 0x34424457; // WDB4 + private const uint DB5FmtSig = 0x35424457; // WDB5 public int RecordsCount { get; private set; } public int FieldsCount { get; private set; } @@ -93,15 +94,17 @@ public DB3Reader(Stream stream) uint magic = reader.ReadUInt32(); - if (magic != DB3FmtSig && magic != DB4FmtSig) + if (magic != DB3FmtSig && magic != DB4FmtSig && magic != DB5FmtSig) { throw new InvalidDataException(string.Format("DB3 file is corrupted!")); } if (magic == DB3FmtSig) HeaderSize = 48; - else + else if (magic == DB4FmtSig) HeaderSize = 52; + else + HeaderSize = 56; RecordsCount = reader.ReadInt32(); FieldsCount = reader.ReadInt32(); @@ -110,7 +113,11 @@ public DB3Reader(Stream stream) uint tableHash = reader.ReadUInt32(); uint build = reader.ReadUInt32(); - uint unk1 = reader.ReadUInt32(); + + if (magic != DB5FmtSig) + { + uint unk1 = reader.ReadUInt32(); // timemodified + } int MinId = reader.ReadInt32(); int MaxId = reader.ReadInt32(); @@ -122,6 +129,11 @@ public DB3Reader(Stream stream) int metaFlags = reader.ReadInt32(); } + if (magic == DB5FmtSig) + { + reader.BaseStream.Position += FieldsCount * 4; + } + int stringTableStart = HeaderSize + RecordsCount * RecordSize; int stringTableEnd = stringTableStart + StringTableSize; @@ -204,10 +216,9 @@ public bool HasRow(int index) public DB3Row GetRow(int index) { - if (!m_index.ContainsKey(index)) - return null; - - return m_index[index]; + DB3Row row; + m_index.TryGetValue(index, out row); + return row; } public IEnumerator> GetEnumerator() diff --git a/CascLib/DBCReader.cs b/CascLib/DBCReader.cs index 38fd66bc..fb872b62 100644 --- a/CascLib/DBCReader.cs +++ b/CascLib/DBCReader.cs @@ -21,31 +21,24 @@ public DBCRow(DBCReader reader, byte[] data) public T GetField(int field) { - try - { - object retVal; + object retVal; - switch (Type.GetTypeCode(typeof(T))) - { - case TypeCode.String: - int start = BitConverter.ToInt32(m_data, field * 4), len = 0; - while (m_reader.StringTable[start + len] != 0) - len++; - retVal = Encoding.UTF8.GetString(m_reader.StringTable, start, len); - return (T)retVal; - case TypeCode.Int32: - retVal = BitConverter.ToInt32(m_data, field * 4); - return (T)retVal; - case TypeCode.Single: - retVal = BitConverter.ToSingle(m_data, field * 4); - return (T)retVal; - default: - return default(T); - } - } - catch + switch (Type.GetTypeCode(typeof(T))) { - return default(T); + case TypeCode.String: + int start = BitConverter.ToInt32(m_data, field * 4), len = 0; + while (m_reader.StringTable[start + len] != 0) + len++; + retVal = Encoding.UTF8.GetString(m_reader.StringTable, start, len); + return (T)retVal; + case TypeCode.Int32: + retVal = BitConverter.ToInt32(m_data, field * 4); + return (T)retVal; + case TypeCode.Single: + retVal = BitConverter.ToSingle(m_data, field * 4); + return (T)retVal; + default: + return default(T); } } } diff --git a/CascLib/DownloadHandler.cs b/CascLib/DownloadHandler.cs index cfb7e3e1..b1cd3e5b 100644 --- a/CascLib/DownloadHandler.cs +++ b/CascLib/DownloadHandler.cs @@ -8,9 +8,9 @@ namespace CASCExplorer public class DownloadEntry { public int Index; - public byte[] Unk; + //public byte[] Unk; - public Dictionary Tags; + public IEnumerable> Tags; } public class DownloadTag @@ -21,14 +21,11 @@ public class DownloadTag public class DownloadHandler { - private static readonly ByteArrayComparer comparer = new ByteArrayComparer(); - private readonly Dictionary DownloadData = new Dictionary(comparer); - Dictionary Tags = new Dictionary(); + private static readonly MD5HashComparer comparer = new MD5HashComparer(); + private Dictionary DownloadData = new Dictionary(comparer); + private Dictionary Tags = new Dictionary(); - public int Count - { - get { return DownloadData.Count; } - } + public int Count => DownloadData.Count; public DownloadHandler(BinaryReader stream, BackgroundWorkerEx worker) { @@ -48,11 +45,13 @@ public DownloadHandler(BinaryReader stream, BackgroundWorkerEx worker) for (int i = 0; i < numFiles; i++) { - byte[] key = stream.ReadBytes(0x10); + MD5Hash key = stream.Read(); - byte[] unk = stream.ReadBytes(0xA); + //byte[] unk = stream.ReadBytes(0xA); + stream.Skip(0xA); - var entry = new DownloadEntry() { Index = i, Unk = unk }; + //var entry = new DownloadEntry() { Index = i, Unk = unk }; + var entry = new DownloadEntry() { Index = i }; DownloadData.Add(key, entry); @@ -81,26 +80,29 @@ public void Dump() foreach (var entry in DownloadData) { if (entry.Value.Tags == null) - entry.Value.Tags = Tags.Where(kv => kv.Value.Bits[entry.Value.Index]).ToDictionary(kv => kv.Key, kv => kv.Value); + entry.Value.Tags = Tags.Where(kv => kv.Value.Bits[entry.Value.Index]); - Logger.WriteLine("{0} {1} {2}", entry.Key.ToHexString(), entry.Value.Unk.ToHexString(), string.Join(",", entry.Value.Tags.Select(tag => tag.Key))); + Logger.WriteLine("{0} {1}", entry.Key.ToHexString(), string.Join(",", entry.Value.Tags.Select(tag => tag.Key))); } } - public DownloadEntry GetEntry(byte[] key) + public DownloadEntry GetEntry(MD5Hash key) { DownloadEntry entry; DownloadData.TryGetValue(key, out entry); if (entry != null && entry.Tags == null) - entry.Tags = Tags.Where(kv => kv.Value.Bits[entry.Index]).ToDictionary(kv => kv.Key, kv => kv.Value); + entry.Tags = Tags.Where(kv => kv.Value.Bits[entry.Index]); return entry; } public void Clear() { + Tags.Clear(); + Tags = null; DownloadData.Clear(); + DownloadData = null; } } } diff --git a/CascLib/EncodingHandler.cs b/CascLib/EncodingHandler.cs index b23b8d1a..46984f75 100644 --- a/CascLib/EncodingHandler.cs +++ b/CascLib/EncodingHandler.cs @@ -3,16 +3,16 @@ namespace CASCExplorer { - public class EncodingEntry + public struct EncodingEntry { + public MD5Hash Key; public int Size; - public byte[] Key; } public class EncodingHandler { - private static readonly ByteArrayComparer comparer = new ByteArrayComparer(); - private readonly Dictionary EncodingData = new Dictionary(comparer); + private static readonly MD5HashComparer comparer = new MD5HashComparer(); + private Dictionary EncodingData = new Dictionary(comparer); private const int CHUNK_SIZE = 4096; @@ -55,7 +55,7 @@ public EncodingHandler(BinaryReader stream, BackgroundWorkerEx worker) while ((keysCount = stream.ReadUInt16()) != 0) { int fileSize = stream.ReadInt32BE(); - byte[] md5 = stream.ReadBytes(16); + MD5Hash md5 = stream.Read(); EncodingEntry entry = new EncodingEntry(); entry.Size = fileSize; @@ -63,7 +63,7 @@ public EncodingHandler(BinaryReader stream, BackgroundWorkerEx worker) // how do we handle multiple keys? for (int ki = 0; ki < keysCount; ++ki) { - byte[] key = stream.ReadBytes(16); + MD5Hash key = stream.Read(); // use first key for now if (ki == 0) @@ -111,7 +111,7 @@ public EncodingHandler(BinaryReader stream, BackgroundWorkerEx worker) // string block till the end of file } - public IEnumerable> Entries + public IEnumerable> Entries { get { @@ -120,16 +120,15 @@ public IEnumerable> Entries } } - public EncodingEntry GetEntry(byte[] md5) + public bool GetEntry(MD5Hash md5, out EncodingEntry enc) { - EncodingEntry result; - EncodingData.TryGetValue(md5, out result); - return result; + return EncodingData.TryGetValue(md5, out enc); } public void Clear() { EncodingData.Clear(); + EncodingData = null; } } } diff --git a/CascLib/Extensions.cs b/CascLib/Extensions.cs index 1b818dc5..b88d68e8 100644 --- a/CascLib/Extensions.cs +++ b/CascLib/Extensions.cs @@ -2,8 +2,6 @@ using System.Collections; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Runtime.InteropServices; using System.Text; namespace CASCExplorer @@ -12,7 +10,8 @@ public static class Extensions { public static int ReadInt32BE(this BinaryReader reader) { - return BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0); + byte[] val = reader.ReadBytes(4); + return val[3] | val[2] << 8 | val[1] << 16 | val[0] << 24; } public static void Skip(this BinaryReader reader, int bytes) @@ -22,37 +21,36 @@ public static void Skip(this BinaryReader reader, int bytes) public static uint ReadUInt32BE(this BinaryReader reader) { - return BitConverter.ToUInt32(reader.ReadBytes(4).Reverse().ToArray(), 0); + byte[] val = reader.ReadBytes(4); + return (uint)(val[3] | val[2] << 8 | val[1] << 16 | val[0] << 24); } - public static T Read(this BinaryReader reader) where T : struct + public unsafe static T Read(this BinaryReader reader) where T : struct { - byte[] result = reader.ReadBytes(Marshal.SizeOf(typeof(T))); - GCHandle handle = GCHandle.Alloc(result, GCHandleType.Pinned); - T returnObject = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); - handle.Free(); - return returnObject; + byte[] result = reader.ReadBytes(FastStruct.Size); + + fixed (byte* ptr = result) + return FastStruct.PtrToStructure(ptr); } - public static T[] ReadArray(this BinaryReader reader) where T : struct + public unsafe static T[] ReadArray(this BinaryReader reader) where T : struct { - long numBytes = reader.ReadInt64(); - - int itemCount = (int)numBytes / Marshal.SizeOf(typeof(T)); - - T[] data = new T[itemCount]; + int numBytes = (int)reader.ReadInt64(); - for (int i = 0; i < itemCount; ++i) - data[i] = reader.Read(); + byte[] result = reader.ReadBytes(numBytes); - reader.BaseStream.Position += (0 - (int)numBytes) & 0x07; - - return data; + fixed (byte* ptr = result) + { + T[] data = FastStruct.ReadArray((IntPtr)ptr, numBytes); + reader.BaseStream.Position += (0 - numBytes) & 0x07; + return data; + } } public static short ReadInt16BE(this BinaryReader reader) { - return BitConverter.ToInt16(reader.ReadBytes(2).Reverse().ToArray(), 0); + byte[] val = reader.ReadBytes(2); + return (short)(val[1] | val[0] << 8); } public static void CopyBytes(this Stream input, Stream output, int bytes) @@ -89,14 +87,6 @@ public static bool EqualsToIgnoreLength(this byte[] array, byte[] other) return true; } - public static bool IsZeroed(this byte[] array) - { - for (var i = 0; i < array.Length; ++i) - if (array[i] != 0) - return false; - return true; - } - public static byte[] Copy(this byte[] array, int len) { byte[] ret = new byte[len]; @@ -116,6 +106,54 @@ public static string ToBinaryString(this BitArray bits) return sb.ToString(); } + + public static unsafe bool EqualsTo(this MD5Hash key, byte[] array) + { + if (array.Length != 16) + return false; + + MD5Hash other; + + fixed (byte* ptr = array) + other = *(MD5Hash*)ptr; + + for (var i = 0; i < 16; ++i) + if (key.Value[i] != other.Value[i]) + return false; + + return true; + } + + public static unsafe string ToHexString(this MD5Hash key) + { + byte[] array = new byte[16]; + + fixed (byte* aptr = array) + { + *(MD5Hash*)aptr = key; + } + + return array.ToHexString(); + } + + public static unsafe bool IsZeroed(this MD5Hash key) + { + for (var i = 0; i < 16; ++i) + if (key.Value[i] != 0) + return false; + return true; + } + + public static unsafe MD5Hash ToMD5(this byte[] array) + { + if (array.Length != 16) + throw new ArgumentException("array size != 16"); + + fixed (byte* ptr = array) + { + return *(MD5Hash*)ptr; + } + } } public static class CStringExtensions diff --git a/CascLib/FastStruct.cs b/CascLib/FastStruct.cs new file mode 100644 index 00000000..1ee44e2a --- /dev/null +++ b/CascLib/FastStruct.cs @@ -0,0 +1,99 @@ +using System; +using System.Reflection.Emit; +using System.Runtime.InteropServices; +using System.Security; + +namespace CASCExplorer +{ + [SuppressUnmanagedCodeSecurity] + internal class UnsafeNativeMethods + { + [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)] + [SecurityCritical] + internal static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); + + [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)] + [SecurityCritical] + internal static extern unsafe void CopyMemoryPtr(void* dest, void* src, uint count); + } + + public static class FastStruct where T : struct + { + private delegate IntPtr GetPtrDelegate(ref T value); + private delegate T PtrToStructureDelegateIntPtr(IntPtr pointer); + private unsafe delegate T PtrToStructureDelegateBytePtr(byte* pointer); + + private readonly static GetPtrDelegate GetPtr = BuildGetPtrMethod(); + private readonly static PtrToStructureDelegateIntPtr PtrToStructureIntPtr = BuildLoadFromIntPtrMethod(); + private readonly static PtrToStructureDelegateBytePtr PtrToStructureBytePtr = BuildLoadFromBytePtrMethod(); + + public static readonly int Size = Marshal.SizeOf(typeof(T)); + + private static DynamicMethod methodGetPtr; + private static DynamicMethod methodLoadIntPtr; + private static DynamicMethod methodLoadBytePtr; + + private static GetPtrDelegate BuildGetPtrMethod() + { + methodGetPtr = new DynamicMethod("GetStructPtr<" + typeof(T).FullName + ">", + typeof(IntPtr), new[] { typeof(T).MakeByRefType() }, typeof(FastStruct).Module); + + ILGenerator generator = methodGetPtr.GetILGenerator(); + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Conv_U); + generator.Emit(OpCodes.Ret); + return (GetPtrDelegate)methodGetPtr.CreateDelegate(typeof(GetPtrDelegate)); + } + + private static PtrToStructureDelegateIntPtr BuildLoadFromIntPtrMethod() + { + methodLoadIntPtr = new DynamicMethod("PtrToStructureIntPtr<" + typeof(T).FullName + ">", + typeof(T), new[] { typeof(IntPtr) }, typeof(FastStruct).Module); + + ILGenerator generator = methodLoadIntPtr.GetILGenerator(); + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldobj, typeof(T)); + generator.Emit(OpCodes.Ret); + + return (PtrToStructureDelegateIntPtr)methodLoadIntPtr.CreateDelegate(typeof(PtrToStructureDelegateIntPtr)); + } + + private static PtrToStructureDelegateBytePtr BuildLoadFromBytePtrMethod() + { + methodLoadBytePtr = new DynamicMethod("PtrToStructureBytePtr<" + typeof(T).FullName + ">", + typeof(T), new[] { typeof(byte*) }, typeof(FastStruct).Module); + + ILGenerator generator = methodLoadBytePtr.GetILGenerator(); + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldobj, typeof(T)); + generator.Emit(OpCodes.Ret); + + return (PtrToStructureDelegateBytePtr)methodLoadBytePtr.CreateDelegate(typeof(PtrToStructureDelegateBytePtr)); + } + + public static T PtrToStructure(IntPtr ptr) + { + return PtrToStructureIntPtr(ptr); + } + + public static unsafe T PtrToStructure(byte* ptr) + { + return PtrToStructureBytePtr(ptr); + } + + public static T[] ReadArray(IntPtr source, int bytesCount) + { + uint elementSize = (uint)Size; + + T[] buffer = new T[bytesCount / elementSize]; + + if (bytesCount > 0) + { + IntPtr p = GetPtr(ref buffer[0]); + UnsafeNativeMethods.CopyMemory(p, source, (uint)bytesCount); + } + + return buffer; + } + } +} diff --git a/CascLib/InstallHandler.cs b/CascLib/InstallHandler.cs index 5c63281a..aed74883 100644 --- a/CascLib/InstallHandler.cs +++ b/CascLib/InstallHandler.cs @@ -8,7 +8,7 @@ namespace CASCExplorer public class InstallEntry { public string Name; - public byte[] MD5; + public MD5Hash MD5; public int Size; public List Tags; @@ -23,7 +23,7 @@ public class InstallTag public class InstallHandler { - private readonly List InstallData = new List(); + private List InstallData = new List(); private static readonly Jenkins96 Hasher = new Jenkins96(); public int Count @@ -66,7 +66,7 @@ public InstallHandler(BinaryReader stream, BackgroundWorkerEx worker) { InstallEntry entry = new InstallEntry(); entry.Name = stream.ReadCString(); - entry.MD5 = stream.ReadBytes(16); + entry.MD5 = stream.Read(); entry.Size = stream.ReadInt32BE(); InstallData.Add(entry); @@ -117,6 +117,7 @@ public void Print() public void Clear() { InstallData.Clear(); + InstallData = null; } } } diff --git a/CascLib/KeyService.cs b/CascLib/KeyService.cs index 70ab0036..9144f9a9 100644 --- a/CascLib/KeyService.cs +++ b/CascLib/KeyService.cs @@ -17,14 +17,19 @@ class KeyService [0xDEE3A0521EFF6F03] = "AD740CE3FFFF9231468126985708E1B9".ToByteArray(), [0x8C9106108AA84F07] = "53D859DDA2635A38DC32E72B11B32F29".ToByteArray(), [0x49166D358A34D815] = "667868CD94EA0135B9B16C93B1124ABA".ToByteArray(), + [0x1463A87356778D14] = "69BD2A78D05C503E93994959B30E5AEC".ToByteArray(), + [0x5E152DE44DFBEE01] = "E45A1793B37EE31A8EB85CEE0EEE1B68".ToByteArray(), + [0x9B1F39EE592CA415] = "54A99F081CAD0D08F7E336F4368E894C".ToByteArray(), // streamed WoW keys - [0xB76729641141CB34] = "9849D1AA7B1FD09819C5C66283A326EC".ToByteArray(), - [0x23C5B5DF837A226C] = "1406E2D873B6FC99217A180881DA8D62".ToByteArray(), - [0xD1E9B5EDF9283668] = "8E4A2579894E38B4AB9058BA5C7328EE".ToByteArray(), - - // 3ECB6A12785050FA - BDC51862ABED79B2DE48C8E7E66C6200 - // ? - AA0B5C77F088CCC2D39049BD267F066D - // ? - D514BD1909A9E5DC8703F4B8BB1DFD9A + [0xFA505078126ACB3E] = "BDC51862ABED79B2DE48C8E7E66C6200".ToByteArray(), // TactKeyId 15 + [0xFF813F7D062AC0BC] = "AA0B5C77F088CCC2D39049BD267F066D".ToByteArray(), // TactKeyId 25 + [0xD1E9B5EDF9283668] = "8E4A2579894E38B4AB9058BA5C7328EE".ToByteArray(), // TactKeyId 39 + [0xB76729641141CB34] = "9849D1AA7B1FD09819C5C66283A326EC".ToByteArray(), // TactKeyId 40 + [0xFFB9469FF16E6BF8] = "D514BD1909A9E5DC8703F4B8BB1DFD9A".ToByteArray(), // TactKeyId 41 + [0x23C5B5DF837A226C] = "1406E2D873B6FC99217A180881DA8D62".ToByteArray(), // TactKeyId 42 + [0xE2854509C471C554] = "433265F0CDEB2F4E65C0EE7008714D9E".ToByteArray(), // TactKeyId 52 + // BNA 1.5.0 Alpha + [0x2C547F26A2613E01] = "37C50C102D4C9E3A5AC069F072B1417D".ToByteArray(), }; private static Salsa20 salsa = new Salsa20(); diff --git a/CascLib/LocalIndexHandler.cs b/CascLib/LocalIndexHandler.cs index 50ed391d..72c3b5a5 100644 --- a/CascLib/LocalIndexHandler.cs +++ b/CascLib/LocalIndexHandler.cs @@ -7,8 +7,8 @@ namespace CASCExplorer { public class LocalIndexHandler { - private static readonly ByteArrayComparer comparer = new ByteArrayComparer(); - private readonly Dictionary LocalIndexData = new Dictionary(comparer); + private static readonly MD5HashComparer comparer = new MD5HashComparer(); + private Dictionary LocalIndexData = new Dictionary(comparer); public int Count { @@ -45,7 +45,7 @@ public static LocalIndexHandler Initialize(CASCConfig config, BackgroundWorkerEx return handler; } - private void ParseIndex(string idx) + private unsafe void ParseIndex(string idx) { using (var fs = new FileStream(idx, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var br = new BinaryReader(fs)) @@ -62,15 +62,32 @@ private void ParseIndex(string idx) int numBlocks = dataLen / 18; + //byte[] buf = new byte[8]; + for (int i = 0; i < numBlocks; i++) { IndexEntry info = new IndexEntry(); - byte[] key = br.ReadBytes(9); + byte[] keyBytes = br.ReadBytes(9); + Array.Resize(ref keyBytes, 16); + + MD5Hash key; + + fixed (byte *ptr = keyBytes) + key = *(MD5Hash*)ptr; + byte indexHigh = br.ReadByte(); int indexLow = br.ReadInt32BE(); info.Index = (indexHigh << 2 | (byte)((indexLow & 0xC0000000) >> 30)); info.Offset = (indexLow & 0x3FFFFFFF); + + //for (int j = 3; j < 8; j++) + // buf[7 - j] = br.ReadByte(); + + //long val = BitConverter.ToInt64(buf, 0); + //info.Index = (int)(val / 0x40000000); + //info.Offset = (int)(val % 0x40000000); + info.Size = br.ReadInt32(); // duplicate keys wtf... @@ -111,12 +128,13 @@ private static List GetIdxFiles(CASCConfig config) return latestIdx; } - public IndexEntry GetIndexInfo(byte[] key) + public unsafe IndexEntry GetIndexInfo(MD5Hash key) { - byte[] temp = key.Copy(9); + ulong* ptr = (ulong*)&key; + ptr[1] &= 0xFF; IndexEntry result; - if (!LocalIndexData.TryGetValue(temp, out result)) + if (!LocalIndexData.TryGetValue(key, out result)) Logger.WriteLine("LocalIndexHandler: missing index: {0}", key.ToHexString()); return result; @@ -125,6 +143,7 @@ public IndexEntry GetIndexInfo(byte[] key) public void Clear() { LocalIndexData.Clear(); + LocalIndexData = null; } } } diff --git a/CascLib/ByteArrayComparer.cs b/CascLib/MD5HashComparer.cs similarity index 53% rename from CascLib/ByteArrayComparer.cs rename to CascLib/MD5HashComparer.cs index e90fdab1..8da7f272 100644 --- a/CascLib/ByteArrayComparer.cs +++ b/CascLib/MD5HashComparer.cs @@ -2,35 +2,34 @@ namespace CASCExplorer { - class ByteArrayComparer : IEqualityComparer + class MD5HashComparer : IEqualityComparer { const uint FnvPrime32 = 16777619; const uint FnvOffset32 = 2166136261; - public bool Equals(byte[] x, byte[] y) + public unsafe bool Equals(MD5Hash x, MD5Hash y) { - if (x.Length != y.Length) - return false; - - for (int i = 0; i < x.Length; ++i) - if (x[i] != y[i]) + for (int i = 0; i < 16; ++i) + if (x.Value[i] != y.Value[i]) return false; return true; } - public int GetHashCode(byte[] obj) + public int GetHashCode(MD5Hash obj) { return To32BitFnv1aHash(obj); } - private int To32BitFnv1aHash(byte[] toHash) + private unsafe int To32BitFnv1aHash(MD5Hash toHash) { uint hash = FnvOffset32; - foreach (var chunk in toHash) + uint* ptr = (uint*)&toHash; + + for (int i = 0; i < 4; i++) { - hash ^= chunk; + hash ^= ptr[i]; hash *= FnvPrime32; } diff --git a/CascLib/MNDXRootHandler.cs b/CascLib/MNDXRootHandler.cs index a9ffbc1c..426237b9 100644 --- a/CascLib/MNDXRootHandler.cs +++ b/CascLib/MNDXRootHandler.cs @@ -41,11 +41,9 @@ public struct NAME_FRAG class CASC_ROOT_ENTRY_MNDX { + public MD5Hash MD5; // Encoding key for the file public int Flags; // High 8 bits: Flags, low 24 bits: package index - //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] - public byte[] MD5; // Encoding key for the file public int FileSize; // Uncompressed file size, in bytes - public CASC_ROOT_ENTRY_MNDX Next; } @@ -146,7 +144,7 @@ public MNDXRootHandler(BinaryReader stream, BackgroundWorkerEx worker) prevEntry = entry; entry.Flags = stream.ReadInt32(); - entry.MD5 = stream.ReadBytes(16); + entry.MD5 = stream.Read(); entry.FileSize = stream.ReadInt32(); mndxRootEntries.Add(i, entry); @@ -211,19 +209,15 @@ public MNDXRootHandler(BinaryReader stream, BackgroundWorkerEx worker) public override IEnumerable> GetAllEntries() { - foreach (var entry in mndxData) - yield return entry; + return mndxData; } public override IEnumerable GetAllEntries(ulong hash) { RootEntry rootEntry; - mndxData.TryGetValue(hash, out rootEntry); - if (rootEntry != null) + if (mndxData.TryGetValue(hash, out rootEntry)) yield return rootEntry; - else - yield break; } public override IEnumerable GetEntries(ulong hash) @@ -383,7 +377,8 @@ public override void LoadListFile(string path, BackgroundWorkerEx worker = null) RootEntry entry = new RootEntry(); int package = FindMNDXPackage(file); - entry.Block = new RootBlock() { LocaleFlags = PackagesLocale[package], ContentFlags = ContentFlags.None }; + entry.LocaleFlags = PackagesLocale[package]; + entry.ContentFlags = ContentFlags.None; entry.MD5 = FindMNDXInfo(file, package).MD5; mndxData[fileHash] = entry; @@ -407,7 +402,7 @@ protected override CASCFolder CreateStorageTree() foreach (var entry in mndxData) { - if ((entry.Value.Block.LocaleFlags & Locale) == 0) + if ((entry.Value.LocaleFlags & Locale) == 0) continue; CreateSubTree(root, entry.Key, CASCFile.FileNames[entry.Key]); @@ -848,7 +843,7 @@ private int sub_1959F50(int arg_0) { // Binary search // HOTS: 1959FAD - if ((eax + 1) < edi) + if (eax + 1 < edi) { // HOTS: 1959FB4 esi = (edi + eax) >> 1; diff --git a/CascLib/MultiDictionary.cs b/CascLib/MultiDictionary.cs index 126c595d..8afd308d 100644 --- a/CascLib/MultiDictionary.cs +++ b/CascLib/MultiDictionary.cs @@ -2,18 +2,18 @@ namespace CASCExplorer { - public class MultiDictionary : Dictionary> + public class MultiDictionary : Dictionary> { public void Add(K key, V value) { - HashSet hset; + List hset; if (TryGetValue(key, out hset)) { hset.Add(value); } else { - hset = new HashSet(); + hset = new List(); hset.Add(value); base[key] = hset; } diff --git a/CascLib/OWRootHandler.cs b/CascLib/OWRootHandler.cs index 38ab8b5b..eb7bfd25 100644 --- a/CascLib/OWRootHandler.cs +++ b/CascLib/OWRootHandler.cs @@ -6,107 +6,102 @@ namespace CASCExplorer { - public class OWRootHandler : RootHandlerBase + public class OwRootHandler : RootHandlerBase { - private readonly Dictionary RootData = new Dictionary(); + private readonly Dictionary _rootData = new Dictionary(); - public OWRootHandler(BinaryReader stream, BackgroundWorkerEx worker, CASCHandler casc) + public unsafe OwRootHandler(BinaryReader stream, BackgroundWorkerEx worker, CASCHandler casc) { worker?.ReportProgress(0, "Loading \"root\"..."); - string str = Encoding.ASCII.GetString(stream.ReadBytes((int)stream.BaseStream.Length)); + //string str = Encoding.ASCII.GetString(stream.ReadBytes((int)stream.BaseStream.Length)); - string[] array = str.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + //string[] array = str.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); // need to figure out what to do with those apm files - for (int i = 1; i < array.Length; i++) - { - string[] filedata = array[i].Split('|'); + //for (int i = 1; i < array.Length; i++) + //{ + // string[] filedata = array[i].Split('|'); - string name = filedata[4]; + // string name = filedata[4]; - if (Path.GetExtension(name) == ".apm") - { - // add apm file for dev purposes - ulong fileHash1 = Hasher.ComputeHash(name); - byte[] md5 = filedata[0].ToByteArray(); - RootData[fileHash1] = new RootEntry() { MD5 = md5, Block = RootBlock.Empty }; + // if (Path.GetExtension(name) == ".apm") + // { + // // add apm file for dev purposes + // ulong fileHash1 = Hasher.ComputeHash(name); + // MD5Hash md5 = filedata[0].ToByteArray().ToMD5(); + // _rootData[fileHash1] = new RootEntry() { MD5 = md5, LocaleFlags = LocaleFlags.All, ContentFlags = ContentFlags.None }; - CASCFile.FileNames[fileHash1] = name; + // CASCFile.FileNames[fileHash1] = name; - // add files listed in apm file - EncodingEntry enc = casc.Encoding.GetEntry(md5); + // // add files listed in apm file + // EncodingEntry enc; - using (Stream s = casc.OpenFile(enc.Key)) - using (BinaryReader br = new BinaryReader(s)) - { - // still need to figure out complete apm structure - // at start of file there's a lot of data that is same in all apm files - s.Position = 0xC; + // if (!casc.Encoding.GetEntry(md5, out enc)) + // continue; - uint count = br.ReadUInt32(); + // using (Stream s = casc.OpenFile(enc.Key)) + // using (BinaryReader br = new BinaryReader(s)) + // { + // // still need to figure out complete apm structure + // // at start of file there's a lot of data that is same in all apm files + // s.Position = 0xC; - s.Position = 0x930; + // uint count = br.ReadUInt32(); - // size of each entry seems to be 0x48 bytes (0x2C bytes unk data; int size; ulong unk; byte[16] md5) - for (int j = 0; j < count; j++) - { - s.Position += 0x2C; // skip unknown - int size = br.ReadInt32(); // size (matches size in encoding file) - s.Position += 8; // skip unknown - byte[] md5_2 = br.ReadBytes(16); + // s.Position = 0x930; - EncodingEntry enc2 = casc.Encoding.GetEntry(md5_2); + // // size of each entry seems to be 0x48 bytes (0x2C bytes unk data; int size; ulong unk; byte[16] md5) + // for (int j = 0; j < count; j++) + // { + // s.Position += 0x2C; // skip unknown + // int size = br.ReadInt32(); // size (matches size in encoding file) + // s.Position += 8; // skip unknown + // MD5Hash md5_2 = br.Read(); - if (enc2 == null) - { - throw new Exception("enc2 == null"); - } + // EncodingEntry enc2; - string fakeName = Path.GetFileNameWithoutExtension(name) + "/" + md5_2.ToHexString(); + // if (!casc.Encoding.GetEntry(md5_2, out enc2)) + // { + // throw new Exception("enc2 == null"); + // } - ulong fileHash = Hasher.ComputeHash(fakeName); - RootData[fileHash] = new RootEntry() { MD5 = md5_2, Block = RootBlock.Empty }; + // string fakeName = Path.GetFileNameWithoutExtension(name) + "/" + md5_2.ToHexString(); - CASCFile.FileNames[fileHash] = fakeName; - } - } - } - } + // ulong fileHash = Hasher.ComputeHash(fakeName); + // _rootData[fileHash] = new RootEntry() { MD5 = md5_2, LocaleFlags = LocaleFlags.All, ContentFlags = ContentFlags.None }; + + // CASCFile.FileNames[fileHash] = fakeName; + // } + // } + // } + //} int current = 0; - Func tag2locale = (s) => - { - LocaleFlags locale; + //Func tag2locale = (s) => + //{ + // LocaleFlags locale; + + // if (Enum.TryParse(s, out locale)) + // return locale; - if (Enum.TryParse(s, out locale)) - return locale; + // return LocaleFlags.All; + //}; - return LocaleFlags.All; - }; + MD5Hash key; foreach (var entry in casc.Encoding.Entries) { - DownloadEntry dl = casc.Download.GetEntry(entry.Value.Key); + key = entry.Key; - if (dl != null) - { - string fakeName = "unknown" + "/" + entry.Key[0].ToString("X2") + "/" + entry.Key.ToHexString(); + string fakeName = "unknown" + "/" + key.Value[0].ToString("X2") + "/" + entry.Key.ToHexString(); - var locales = dl.Tags.Where(tag => tag.Value.Type == 4).Select(tag => tag2locale(tag.Key)); + ulong fileHash = Hasher.ComputeHash(fakeName); + _rootData.Add(fileHash, new RootEntry() { MD5 = entry.Key, LocaleFlags = LocaleFlags.All, ContentFlags = ContentFlags.None }); - LocaleFlags locale = LocaleFlags.None; - - foreach (var loc in locales) - locale |= loc; - - ulong fileHash = Hasher.ComputeHash(fakeName); - RootData.Add(fileHash, new RootEntry() { MD5 = entry.Key, Block = new RootBlock() { LocaleFlags = locale } }); - - CASCFile.FileNames[fileHash] = fakeName; - } + CASCFile.FileNames[fileHash] = fakeName; worker?.ReportProgress((int)(++current / (float)casc.Encoding.Count * 100)); } @@ -114,18 +109,15 @@ public OWRootHandler(BinaryReader stream, BackgroundWorkerEx worker, CASCHandler public override IEnumerable> GetAllEntries() { - foreach (var entry in RootData) - yield return entry; + return _rootData; } public override IEnumerable GetAllEntries(ulong hash) { RootEntry entry; - if (RootData.TryGetValue(hash, out entry)) + if (_rootData.TryGetValue(hash, out entry)) yield return entry; - else - yield break; } // Returns only entries that match current locale and content flags @@ -133,7 +125,7 @@ public override IEnumerable GetEntries(ulong hash) { //RootEntry entry; - //if (RootData.TryGetValue(hash, out entry)) + //if (_rootData.TryGetValue(hash, out entry)) // yield return entry; //else // yield break; @@ -150,27 +142,25 @@ protected override CASCFolder CreateStorageTree() var root = new CASCFolder("root"); CountSelect = 0; - - // Cleanup fake names for unknown files CountUnknown = 0; - foreach (var rootEntry in RootData) + foreach (var rootEntry in _rootData) { - if ((rootEntry.Value.Block.LocaleFlags & Locale) == 0) + if ((rootEntry.Value.LocaleFlags & Locale) == 0) continue; CreateSubTree(root, rootEntry.Key, CASCFile.FileNames[rootEntry.Key]); CountSelect++; } - Logger.WriteLine("OWRootHandler: {0} file names missing for locale {1}", CountUnknown, Locale); + Logger.WriteLine("OwRootHandler: {0} file names missing for locale {1}", CountUnknown, Locale); return root; } public override void Clear() { - RootData.Clear(); + _rootData.Clear(); Root.Entries.Clear(); CASCFile.FileNames.Clear(); } diff --git a/CascLib/Properties/AssemblyInfo.cs b/CascLib/Properties/AssemblyInfo.cs index aa32f266..745b6216 100644 --- a/CascLib/Properties/AssemblyInfo.cs +++ b/CascLib/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/CascLib/RootHandlerBase.cs b/CascLib/RootHandlerBase.cs index acc9ec35..ba3d5842 100644 --- a/CascLib/RootHandlerBase.cs +++ b/CascLib/RootHandlerBase.cs @@ -57,7 +57,6 @@ protected void CreateSubTree(CASCFolder root, ulong filehash, string file) } folder.Entries[entryName] = entry; - folder.EntriesMirror[entryName] = entry; } folder = entry as CASCFolder; @@ -66,6 +65,9 @@ protected void CreateSubTree(CASCFolder root, ulong filehash, string file) public void MergeInstall(InstallHandler install) { + if (install == null) + return; + foreach (var entry in install.GetEntries()) { CreateSubTree(Root, Hasher.ComputeHash(entry.Name), entry.Name); diff --git a/CascLib/Wildcard.cs b/CascLib/Wildcard.cs index ea59a8f9..18ff3f31 100644 --- a/CascLib/Wildcard.cs +++ b/CascLib/Wildcard.cs @@ -22,7 +22,7 @@ public Wildcard(string pattern, bool matchStartEnd) /// /// The wildcard pattern to match. /// A combination of one or more - /// . + /// . public Wildcard(string pattern, bool matchStartEnd, RegexOptions options) : base(WildcardToRegex(pattern, matchStartEnd), options) { @@ -37,8 +37,7 @@ public static string WildcardToRegex(string pattern, bool matchStartEnd) { if (matchStartEnd) return "^" + Escape(pattern).Replace("\\*", ".*").Replace("\\?", ".") + "$"; - else - return Escape(pattern).Replace("\\*", ".*").Replace("\\?", "."); + return Escape(pattern).Replace("\\*", ".*").Replace("\\?", "."); } } } diff --git a/CascLib/WowRootHandler.cs b/CascLib/WowRootHandler.cs index 3e3d0864..9cc58eeb 100644 --- a/CascLib/WowRootHandler.cs +++ b/CascLib/WowRootHandler.cs @@ -40,34 +40,26 @@ public enum ContentFlags : uint NoCompression = 0x80000000 // sounds have this flag } - public class RootBlock + public unsafe struct MD5Hash { - public static readonly RootBlock Empty = new RootBlock() { ContentFlags = ContentFlags.None, LocaleFlags = LocaleFlags.All }; - public ContentFlags ContentFlags; - public LocaleFlags LocaleFlags; + public fixed byte Value[16]; } - public class RootEntry + public struct RootEntry { - public RootBlock Block; - public int FileDataId; - public byte[] MD5; - - public override string ToString() - { - return string.Format("RootBlock: {0:X8} {1:X8}, File: {2:X8} {3}", Block.ContentFlags, Block.LocaleFlags, FileDataId, MD5.ToHexString()); - } + public MD5Hash MD5; + public ContentFlags ContentFlags; + public LocaleFlags LocaleFlags; } public class WowRootHandler : RootHandlerBase { - private readonly MultiDictionary RootData = new MultiDictionary(); - private readonly Dictionary FileDataStore = new Dictionary(); - private readonly Dictionary FileDataStoreReverse = new Dictionary(); - private readonly HashSet UnknownFiles = new HashSet(); + private MultiDictionary RootData = new MultiDictionary(); + private Dictionary FileDataStore = new Dictionary(); + private Dictionary FileDataStoreReverse = new Dictionary(); - public override int Count { get { return RootData.Count; } } - public override int CountTotal { get { return RootData.Sum(re => re.Value.Count); } } + public override int Count => RootData.Count; + public override int CountTotal => RootData.Sum(re => re.Value.Count); public WowRootHandler(BinaryReader stream, BackgroundWorkerEx worker) { @@ -77,34 +69,34 @@ public WowRootHandler(BinaryReader stream, BackgroundWorkerEx worker) { int count = stream.ReadInt32(); - RootBlock block = new RootBlock(); - block.ContentFlags = (ContentFlags)stream.ReadUInt32(); - block.LocaleFlags = (LocaleFlags)stream.ReadUInt32(); + ContentFlags contentFlags = (ContentFlags)stream.ReadUInt32(); + LocaleFlags localeFlags = (LocaleFlags)stream.ReadUInt32(); - if (block.LocaleFlags == LocaleFlags.None) + if (localeFlags == LocaleFlags.None) throw new Exception("block.LocaleFlags == LocaleFlags.None"); - if (block.ContentFlags != ContentFlags.None && (block.ContentFlags & (ContentFlags.LowViolence | ContentFlags.NoCompression)) == 0) + if (contentFlags != ContentFlags.None && (contentFlags & (ContentFlags.LowViolence | ContentFlags.NoCompression)) == 0) throw new Exception("block.ContentFlags != ContentFlags.None"); RootEntry[] entries = new RootEntry[count]; + int[] filedataIds = new int[count]; int fileDataIndex = 0; for (var i = 0; i < count; ++i) { - entries[i] = new RootEntry(); - entries[i].Block = block; - entries[i].FileDataId = fileDataIndex + stream.ReadInt32(); + entries[i].LocaleFlags = localeFlags; + entries[i].ContentFlags = contentFlags; - fileDataIndex = entries[i].FileDataId + 1; + filedataIds[i] = fileDataIndex + stream.ReadInt32(); + fileDataIndex = filedataIds[i] + 1; } //Console.WriteLine("Block: {0} {1} (size {2})", block.ContentFlags, block.LocaleFlags, count); for (var i = 0; i < count; ++i) { - entries[i].MD5 = stream.ReadBytes(16); + entries[i].MD5 = stream.Read(); ulong hash = stream.ReadUInt64(); @@ -114,7 +106,7 @@ public WowRootHandler(BinaryReader stream, BackgroundWorkerEx worker) ulong hash2; - int fileDataId = entries[i].FileDataId; + int fileDataId = filedataIds[i]; if (FileDataStore.TryGetValue(fileDataId, out hash2)) { @@ -138,10 +130,7 @@ public WowRootHandler(BinaryReader stream, BackgroundWorkerEx worker) } } - public IEnumerable GetAllEntriesByFileDataId(int fileDataId) - { - return GetAllEntries(GetHashByFileDataId(fileDataId)); - } + public IEnumerable GetAllEntriesByFileDataId(int fileDataId) => GetAllEntries(GetHashByFileDataId(fileDataId)); public override IEnumerable> GetAllEntries() { @@ -152,7 +141,7 @@ public override IEnumerable> GetAllEntries() public override IEnumerable GetAllEntries(ulong hash) { - HashSet result; + List result; RootData.TryGetValue(hash, out result); if (result == null) @@ -162,10 +151,7 @@ public override IEnumerable GetAllEntries(ulong hash) yield return entry; } - public IEnumerable GetEntriesByFileDataId(int fileDataId) - { - return GetEntries(GetHashByFileDataId(fileDataId)); - } + public IEnumerable GetEntriesByFileDataId(int fileDataId) => GetEntries(GetHashByFileDataId(fileDataId)); // Returns only entries that match current locale and content flags public override IEnumerable GetEntries(ulong hash) @@ -175,11 +161,11 @@ public override IEnumerable GetEntries(ulong hash) if (!rootInfos.Any()) yield break; - var rootInfosLocale = rootInfos.Where(re => (re.Block.LocaleFlags & Locale) != 0); + var rootInfosLocale = rootInfos.Where(re => (re.LocaleFlags & Locale) != 0); if (rootInfosLocale.Count() > 1) { - var rootInfosLocaleAndContent = rootInfosLocale.Where(re => (re.Block.ContentFlags == Content)); + var rootInfosLocaleAndContent = rootInfosLocale.Where(re => (re.ContentFlags == Content)); if (rootInfosLocaleAndContent.Any()) rootInfosLocale = rootInfosLocaleAndContent; @@ -203,10 +189,7 @@ public int GetFileDataIdByHash(ulong hash) return fid; } - public int GetFileDataIdByName(string name) - { - return GetFileDataIdByHash(Hasher.ComputeHash(name)); - } + public int GetFileDataIdByName(string name) => GetFileDataIdByHash(Hasher.ComputeHash(name)); private bool LoadPreHashedListFile(string pathbin, string pathtext, BackgroundWorkerEx worker = null) { @@ -267,19 +250,19 @@ private bool LoadPreHashedListFile(string pathbin, string pathtext, BackgroundWo public void LoadFileDataComplete(CASCHandler casc) { - if (!casc.FileExists("DBFilesClient\\FileDataComplete.dbc")) + if (!casc.FileExists("DBFilesClient\\FileDataComplete.db2")) return; - Logger.WriteLine("WowRootHandler: loading file names from FileDataComplete.dbc..."); + Logger.WriteLine("WowRootHandler: loading file names from FileDataComplete.db2..."); - using (var s = casc.OpenFile("DBFilesClient\\FileDataComplete.dbc")) + using (var s = casc.OpenFile("DBFilesClient\\FileDataComplete.db2")) { - DBCReader fd = new DBCReader(s); + DB3Reader fd = new DB3Reader(s); foreach (var row in fd) { - string path = row.Value.GetField(1); - string name = row.Value.GetField(2); + string path = row.Value.GetField(4); + string name = row.Value.GetField(8); string fullname = path + name; @@ -302,16 +285,16 @@ public override void LoadListFile(string path, BackgroundWorkerEx worker = null) if (LoadPreHashedListFile("listfile.bin", path, worker)) return; - if (!File.Exists(path)) - { - Logger.WriteLine("WowRootHandler: list file missing!"); - return; - } - using (var _ = new PerfCounter("WowRootHandler::LoadListFile()")) { worker?.ReportProgress(0, "Loading \"listfile\"..."); + if (!File.Exists(path)) + { + Logger.WriteLine("WowRootHandler: list file missing!"); + return; + } + Logger.WriteLine("WowRootHandler: loading file names..."); Dictionary> dirData = new Dictionary>(StringComparer.OrdinalIgnoreCase); @@ -383,22 +366,18 @@ protected override CASCFolder CreateStorageTree() { var root = new CASCFolder("root"); + // Reset counts CountSelect = 0; - - // Cleanup fake names for unknown files CountUnknown = 0; - foreach (var unkFile in UnknownFiles) - CASCFile.FileNames.Remove(unkFile); - // Create new tree based on specified locale foreach (var rootEntry in RootData) { - var rootInfosLocale = rootEntry.Value.Where(re => (re.Block.LocaleFlags & Locale) != 0); + var rootInfosLocale = rootEntry.Value.Where(re => (re.LocaleFlags & Locale) != 0); if (rootInfosLocale.Count() > 1) { - var rootInfosLocaleAndContent = rootInfosLocale.Where(re => (re.Block.ContentFlags == Content)); + var rootInfosLocaleAndContent = rootInfosLocale.Where(re => (re.ContentFlags == Content)); if (rootInfosLocaleAndContent.Any()) rootInfosLocale = rootInfosLocaleAndContent; @@ -411,10 +390,9 @@ protected override CASCFolder CreateStorageTree() if (!CASCFile.FileNames.TryGetValue(rootEntry.Key, out file)) { - file = "unknown\\" + rootEntry.Key.ToString("X16") + "_" + rootEntry.Value.First().FileDataId; + file = "unknown\\" + rootEntry.Key.ToString("X16") + "_" + GetFileDataIdByHash(rootEntry.Key); CountUnknown++; - UnknownFiles.Add(rootEntry.Key); } CreateSubTree(root, rootEntry.Key, file); @@ -426,32 +404,31 @@ protected override CASCFolder CreateStorageTree() return root; } - public bool IsUnknownFile(ulong hash) - { - return UnknownFiles.Contains(hash); - } + public bool IsUnknownFile(ulong hash) => !CASCFile.FileNames.ContainsKey(hash); public override void Clear() { RootData.Clear(); + RootData = null; FileDataStore.Clear(); + FileDataStore = null; FileDataStoreReverse.Clear(); - UnknownFiles.Clear(); - if (Root != null) - Root.Entries.Clear(); + FileDataStoreReverse = null; + Root?.Entries.Clear(); + Root = null; CASCFile.FileNames.Clear(); } public override void Dump() { - foreach (var fd in RootData.OrderBy(r => r.Value.First().FileDataId)) + foreach (var fd in RootData.OrderBy(r => GetFileDataIdByHash(r.Key))) { string name; if (!CASCFile.FileNames.TryGetValue(fd.Key, out name)) name = fd.Key.ToString("X16"); - Logger.WriteLine("{0:D7} {1:X16} {2} {3}", fd.Value.First().FileDataId, fd.Key, string.Join(",", fd.Value.Select(r => r.Block.LocaleFlags.ToString())), name); + Logger.WriteLine("{0:D7} {1:X16} {2} {3}", GetFileDataIdByHash(fd.Key), fd.Key, string.Join(",", fd.Value.Select(r => r.LocaleFlags.ToString())), name); } } }