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);
}
}
}