CascLibSharp isn't used and can be removed

This commit is contained in:
Matt Nadareski
2025-11-03 08:53:04 -05:00
parent 6511ecb5ad
commit 27f723bf2f
13 changed files with 2 additions and 1254 deletions

View File

@@ -1,239 +0,0 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using CascLibSharp.Native;
namespace CascLibSharp
{
internal sealed class CascApi
{
#region Imported method signature type definitions
[return: MarshalAs(UnmanagedType.I1)]
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate bool FnCascOpenStorage(
[MarshalAs(UnmanagedType.LPTStr)] string szDataPath,
uint dwFlags,
out CascStorageSafeHandle phStorage);
[return: MarshalAs(UnmanagedType.I1)]
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate bool FnCascGetStorageInfo(
CascStorageSafeHandle hStorage,
CascStorageInfoClass infoClass,
ref uint pvStorageInfo,
IntPtr cbStorageInfo,
ref uint pcbLengthNeeded);
[return: MarshalAs(UnmanagedType.I1)]
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate bool FnCascCloseStorage(IntPtr hStorage);
[return: MarshalAs(UnmanagedType.I1)]
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate bool FnCascOpenFileByIndexKey(
CascStorageSafeHandle hStorage,
ref QueryKey pIndexKey,
uint dwFlags,
out CascStorageFileSafeHandle phFile);
[return: MarshalAs(UnmanagedType.I1)]
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate bool FnCascOpenFileByEncodingKey(
CascStorageSafeHandle hStorage,
ref QueryKey pEncodingKey,
uint dwFlags,
out CascStorageFileSafeHandle phFile);
[return: MarshalAs(UnmanagedType.I1)]
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate bool FnCascOpenFile(
CascStorageSafeHandle hStorage,
[MarshalAs(UnmanagedType.LPStr)] string szFileName,
uint dwLocale,
uint dwFlags,
out CascStorageFileSafeHandle phFile);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate uint FnCascGetFileSize(
CascStorageFileSafeHandle hFile,
out uint pdwFileSizeHigh);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate uint FnCascSetFilePointer(
CascStorageFileSafeHandle hFile,
uint lFilePos,
ref uint plFilePosHigh,
SeekOrigin dwMoveMethod);
[return: MarshalAs(UnmanagedType.I1)]
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate bool FnCascReadFile(
CascStorageFileSafeHandle hFile,
IntPtr lpBuffer,
uint dwToRead,
out uint dwRead);
[return: MarshalAs(UnmanagedType.I1)]
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate bool FnCascCloseFile(IntPtr hFile);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate CascFileEnumerationSafeHandle FnCascFindFirstFile(
CascStorageSafeHandle hStorage,
[MarshalAs(UnmanagedType.LPStr)] string szMask,
ref CascFindData pFindData,
[MarshalAs(UnmanagedType.LPTStr)] string? szListFile);
[return: MarshalAs(UnmanagedType.I1)]
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate bool FnCascFindNextFile(
CascFileEnumerationSafeHandle hFind,
ref CascFindData pFindData);
[return: MarshalAs(UnmanagedType.I1)]
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate bool FnCascFindClose(IntPtr hFind);
#endregion
#region Field and method defs
public readonly FnCascOpenStorage? CascOpenStorage;
public readonly FnCascGetStorageInfo? CascGetStorageInfo;
public readonly FnCascCloseStorage? CascCloseStorage;
public readonly FnCascOpenFileByIndexKey? CascOpenFileByIndexKey;
public readonly FnCascOpenFileByEncodingKey? CascOpenFileByEncodingKey;
public readonly FnCascOpenFile? CascOpenFile;
public readonly FnCascGetFileSize? CascGetFileSizeBase;
public long CascGetFileSize(CascStorageFileSafeHandle hFile)
{
uint low = CascGetFileSizeBase!(hFile, out uint high);
long result = (high << 32) | low;
return result;
}
public readonly FnCascSetFilePointer? CascSetFilePointerBase;
public long CascSetFilePointer(CascStorageFileSafeHandle hFile, long filePos, SeekOrigin moveMethod)
{
uint low, high;
unchecked
{
low = (uint)(filePos & 0xffffffff);
high = (uint)(((ulong)filePos & 0xffffffff00000000) >> 32);
}
low = CascSetFilePointerBase!(hFile, low, ref high, moveMethod);
long result = (high << 32) | low;
return result;
}
public readonly FnCascReadFile? CascReadFile;
public readonly FnCascCloseFile? CascCloseFile;
public readonly FnCascFindFirstFile? CascFindFirstFile;
public readonly FnCascFindNextFile? CascFindNextFile;
public readonly FnCascFindClose? CascFindClose;
#endregion
internal CascApi(IntPtr hModule)
{
SetFn(ref CascOpenStorage, hModule, "CascOpenStorage");
SetFn(ref CascGetStorageInfo, hModule, "CascGetStorageInfo");
SetFn(ref CascCloseStorage, hModule, "CascCloseStorage");
SetFn(ref CascOpenFileByIndexKey, hModule, "CascOpenFileByIndexKey");
SetFn(ref CascOpenFileByEncodingKey, hModule, "CascOpenFileByEncodingKey");
SetFn(ref CascOpenFile, hModule, "CascOpenFile");
SetFn(ref CascGetFileSizeBase, hModule, "CascGetFileSize");
SetFn(ref CascSetFilePointerBase, hModule, "CascSetFilePointer");
SetFn(ref CascReadFile, hModule, "CascReadFile");
SetFn(ref CascCloseFile, hModule, "CascCloseFile");
SetFn(ref CascFindFirstFile, hModule, "CascFindFirstFile");
SetFn(ref CascFindNextFile, hModule, "CascFindNextFile");
SetFn(ref CascFindClose, hModule, "CascFindClose");
}
private static void SetFn<T>(ref T? target, IntPtr hModule, string procName)
where T : class
{
IntPtr procAddr = NativeMethods.GetProcAddress(hModule, procName);
if (procAddr == IntPtr.Zero)
throw new Win32Exception();
target = Marshal.GetDelegateForFunctionPointer(procAddr, typeof(T)) as T;
}
private static CascApi Load()
{
string myPath = Assembly.GetExecutingAssembly().Location;
string directory = Path.GetDirectoryName(myPath) ?? string.Empty;
string arch = IntPtr.Size == 8 ? "x64" : "x86";
#if DEBUG
string build = "dbg";
#else
string build = "fre";
#endif
#if NET20 || NET35
string mainPath = Path.Combine(directory, Path.Combine("CascLib", Path.Combine(build, Path.Combine(arch, "CascLib.dll"))));
#else
string mainPath = Path.Combine(directory, "CascLib", build, arch, "CascLib.dll");
#endif
if (File.Exists(mainPath))
return FromFile(mainPath);
#if NET20 || NET35
string alternatePath = Path.Combine(directory, Path.Combine("CascLib", Path.Combine(arch, "CascLib.dll")));
#else
string alternatePath = Path.Combine(directory, "CascLib", arch, "CascLib.dll");
#endif
if (File.Exists(mainPath))
return FromFile(alternatePath);
string localPath = Path.Combine(directory, "CascLib.dll");
if (File.Exists(localPath))
return FromFile(localPath);
throw new FileNotFoundException(string.Format("Could not locate a copy of CascLib.dll to load. The following paths were tried:\n\t{0}\n\t{1}\n\t{2}\n\nEnsure that an architecture-appropriate copy of CascLib.dll is included in your project.", mainPath, alternatePath, localPath));
}
#if NET20 || NET35
private static CascApi? _sharedInstance = null;
#else
private static readonly Lazy<CascApi> _sharedInstance = new(Load);
#endif
public static CascApi Instance
{
get
{
#if NET20 || NET35
if (_sharedInstance == null)
_sharedInstance = Load();
return _sharedInstance;
#else
return _sharedInstance.Value;
#endif
}
}
public static CascApi FromFile(string filePath)
{
if (!File.Exists(filePath))
throw new FileNotFoundException("The CascLib.dll library could not be loaded at the specified path.", filePath);
IntPtr hMod = NativeMethods.LoadLibraryEx(filePath, IntPtr.Zero, 0);
if (hMod == IntPtr.Zero)
throw new Win32Exception();
return new CascApi(hMod);
}
}
}

View File

@@ -1,51 +0,0 @@
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace CascLibSharp
{
/// <summary>
/// Represents an exception raised as part of a CASC operation.
/// </summary>
public class CascException : Win32Exception
{
/// <summary>
/// Creates a new CascException based on the last Win32 error.
/// </summary>
public CascException()
: base(Marshal.GetLastWin32Error())
{
}
/// <summary>
/// Creates a new CascException based on the specified Win32 error code.
/// </summary>
/// <param name="errorCode">A Win32 error code.</param>
public CascException(int errorCode)
: base(errorCode)
{
}
/// <summary>
/// Creates a new CascException based on the specified message.
/// </summary>
/// <param name="message">The error message.</param>
public CascException(string message)
: base(message)
{
}
/// <summary>
/// Creates a new CascException based on the specified Win32 error code and custom error message.
/// </summary>
/// <param name="errorCode">A Win32 error code.</param>
/// <param name="message">The error message.</param>
public CascException(int errorCode, string message)
: base(errorCode, message)
{
}
}
}

View File

@@ -1,164 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using CascLibSharp.Native;
namespace CascLibSharp
{
/// <summary>
/// Provides a view of the data in a file contained within CASC storage.
/// </summary>
public class CascFileStream : Stream
{
private readonly CascStorageFileSafeHandle _handle;
private readonly CascApi _api;
internal CascFileStream(CascStorageFileSafeHandle? handle, CascApi? api)
{
Debug.Assert(handle != null);
Debug.Assert(!handle!.IsInvalid);
Debug.Assert(api != null);
_api = api!;
_handle = handle;
}
private void AssertValidHandle()
{
if (_handle == null || _handle.IsInvalid)
throw new ObjectDisposedException("CascFileStream");
}
/// <summary>
/// Gets whether this Stream may be read. Always returns true as long as the Stream has not been disposed.
/// </summary>
public override bool CanRead
{
get { return _handle != null && !_handle.IsInvalid; }
}
/// <summary>
/// Gets whether this Stream may seek. Always returns true as long as the Stream has not been disposed.
/// </summary>
public override bool CanSeek
{
get { return _handle != null && !_handle.IsInvalid; }
}
/// <summary>
/// Gets whether this Stream may write. Always returns false.
/// </summary>
public override bool CanWrite
{
get { return false; }
}
/// <summary>
/// Flushes writes to the backing store. This method always throws because writing is not supported.
/// </summary>
/// <exception cref="NotSupportedException">Always thrown.</exception>
public override void Flush()
{
throw new NotSupportedException();
}
/// <summary>
/// Gets the length of the Stream.
/// </summary>
/// <exception cref="ObjectDisposedException">Thrown if the Stream has been disposed.</exception>
public override long Length
{
get
{
AssertValidHandle();
return _api!.CascGetFileSize(_handle);
}
}
/// <summary>
/// Gets or sets the current position within the Stream.
/// </summary>
/// <exception cref="ObjectDisposedException">Thrown if the Stream has been disposed.</exception>
/// <exception cref="CascException">Thrown if the attempt to seek fails.</exception>
public override long Position
{
get
{
AssertValidHandle();
return _api!.CascSetFilePointer(_handle, 0, SeekOrigin.Current);
}
set
{
AssertValidHandle();
long result = _api!.CascSetFilePointer(_handle, value, SeekOrigin.Begin);
if (result != value)
throw new CascException();
}
}
/// <summary>
/// Reads data into the buffer.
/// </summary>
/// <param name="buffer">The destination into which the data should be read.</param>
/// <param name="offset">The offset into the buffer to read from the current position of the Stream.</param>
/// <param name="count">The number of bytes to read.</param>
/// <returns>The number of bytes actually read.</returns>
/// <exception cref="ObjectDisposedException">Thrown if the Stream has been disposed.</exception>
/// <exception cref="CascException">Thrown if the CASC provider fails to read.</exception>
public override unsafe int Read(byte[] buffer, int offset, int count)
{
AssertValidHandle();
long len = Length;
if (offset < 0 || offset > len)
throw new ArgumentException(nameof(offset));
if (count < 0)
throw new ArgumentException(nameof(count));
if (offset + count > len)
throw new ArgumentException(nameof(count));
uint read = 0;
fixed (byte* pBuffer = &buffer[0])
{
if (!_api.CascReadFile!(_handle, new IntPtr((void*)pBuffer), unchecked((uint)count), out read))
throw new CascException();
}
return unchecked((int)read);
}
/// <summary>
/// Seeks to a specific position within the Stream.
/// </summary>
/// <param name="offset">The offset from the specified origin to seek.</param>
/// <param name="origin">The relative location (beginning, current, or end) from which to seek.</param>
/// <returns>The new position in the stream.</returns>
/// <exception cref="ObjectDisposedException">Thrown if the Stream has been disposed.</exception>
public override long Seek(long offset, SeekOrigin origin)
{
AssertValidHandle();
return _api.CascSetFilePointer(_handle, offset, origin);
}
/// <summary>
/// Sets the length of the Stream. Always throws because writing is not supported.
/// </summary>
/// <param name="value">The new length of the Stream.</param>
/// <exception cref="NotSupportedException">Always thrown.</exception>
public override void SetLength(long value)
{
throw new NotSupportedException();
}
/// <summary>
/// Writes the specified data to the Stream. Always throws because writing is not supported.
/// </summary>
/// <param name="buffer">The data to write.</param>
/// <param name="offset">The starting position in the buffer.</param>
/// <param name="count">The number of bytes.</param>
/// <exception cref="NotSupportedException">Always thrown.</exception>
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
}
}

View File

@@ -1,75 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace CascLibSharp
{
/// <summary>
/// Represents a file found in a CASC container search.
/// </summary>
public class CascFoundFile
{
#if NET20 || NET35 || NET40
private readonly WeakReference _ownerContext;
#else
private readonly WeakReference<CascStorageContext> _ownerContext;
#endif
internal CascFoundFile(string? fileName, IntPtr plainName, byte[] encodingKey, CascLocales locales, long fileSize, CascStorageContext ownerContext)
{
FileName = fileName;
PlainFileName = Marshal.PtrToStringAnsi(plainName);
EncodingKey = encodingKey;
Locales = locales;
FileSize = fileSize;
#if NET20 || NET35 || NET40
_ownerContext = new WeakReference(ownerContext);
#else
_ownerContext = new WeakReference<CascStorageContext>(ownerContext);
#endif
}
/// <summary>
/// Gets the full path to this file.
/// </summary>
public string? FileName { get; private set; }
/// <summary>
/// Gets the plain (no directory-qualified) file name of this file.
/// </summary>
public string? PlainFileName { get; private set; }
/// <summary>
/// Gets the CASC encoding key for this file.
/// </summary>
public byte[] EncodingKey { get; private set; }
/// <summary>
/// Gets the locales supported by this resource.
/// </summary>
public CascLocales Locales { get; private set; }
/// <summary>
/// Gets the length of the file in bytes.
/// </summary>
public long FileSize { get; private set; }
/// <summary>
/// Opens the found file for reading.
/// </summary>
/// <returns>A CascFileStream, which acts as a Stream for a CASC stored file.</returns>
public CascFileStream Open()
{
#if NET20 || NET35 || NET40
CascStorageContext? context = _ownerContext.Target as CascStorageContext;
if (context == null)
throw new ObjectDisposedException("The owning context has been closed.");
#else
if (!_ownerContext.TryGetTarget(out CascStorageContext? context) || context == null)
throw new ObjectDisposedException("The owning context has been closed.");
#endif
return context.OpenFileByEncodingKey(EncodingKey);
}
}
}

View File

@@ -1,453 +0,0 @@
using System;
using System.Collections.Generic;
using CascLibSharp.Native;
namespace CascLibSharp
{
/// <summary>
/// Represents a CASC storage directory.
/// </summary>
public class CascStorageContext : IDisposable
{
private readonly CascApi _api;
private CascStorageSafeHandle? _handle;
#if NET20 || NET35
private bool? _hasListfile = null;
private CascKnownClient? _clientType = null;
private long? _fileCount = null;
private int? _gameBuild = null;
#else
private readonly Lazy<bool> _hasListfile;
private readonly Lazy<CascKnownClient> _clientType;
private readonly Lazy<long> _fileCount;
private readonly Lazy<int> _gameBuild;
#endif
/// <summary>
/// Creates a new CascStorageContext for the specified path.
/// </summary>
/// <param name="dataPath">The path to a game's data directory.</param>
/// <example>An example directory is <c>c:\Program Files (x86)\Heroes of the Storm\HeroesData</c>.</example>
public CascStorageContext(string dataPath)
{
_api = CascApi.Instance;
if (!_api.CascOpenStorage!(dataPath, 0, out _handle) || _handle.IsInvalid)
throw new CascException();
_handle.Api = _api;
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
_hasListfile = new Lazy<bool>(CheckHasListfile);
_clientType = new Lazy<CascKnownClient>(GetClient);
_fileCount = new Lazy<long>(GetFileCount);
_gameBuild = new Lazy<int>(GetGameBuild);
#endif
}
/// <summary>
/// Opens a file by its fully-qualified name.
/// </summary>
/// <param name="fileName">The name of the file.</param>
/// <param name="locale">The file's locale (defaults to English-United States).</param>
/// <returns>A CascFileStream, which implements a Stream.</returns>
/// <exception cref="ObjectDisposedException">Thrown if the CascStorageContext has been disposed.</exception>
/// <exception cref="System.IO.FileNotFoundException">Thrown if the file does not exist within the CASC storage container.</exception>
public CascFileStream OpenFile(string fileName, CascLocales locale = CascLocales.EnUs)
{
if (_handle == null || _handle.IsInvalid)
throw new ObjectDisposedException("CascStorageContext");
CascStorageFileSafeHandle hFile;
if (!_api.CascOpenFile!(_handle, fileName, (uint)locale, 0, out hFile))
throw new CascException();
hFile.Api = _api;
return new CascFileStream(hFile, _api);
}
/// <summary>
/// Opens a file by its index key.
/// </summary>
/// <remarks>
/// An index key is a binary representation of a file. I do not know what it comes from; I know that it's used to identify files inside of CASC,
/// but I don't know how someone obtains the index key. They are not produced in the public API of CascLib.
/// </remarks>
/// <param name="indexKey">The index key to search.</param>
/// <returns>A CascFileStream, which implements a Stream.</returns>
/// <exception cref="ObjectDisposedException">Thrown if the CascStorageContext has been disposed.</exception>
/// <exception cref="System.IO.FileNotFoundException">Thrown if the file does not exist within the CASC storage container.</exception>
public CascFileStream OpenFileByIndexKey(byte[] indexKey)
{
if (_handle == null || _handle.IsInvalid)
throw new ObjectDisposedException("CascStorageContext");
using CoTaskMem mem = CoTaskMem.FromBytes(indexKey);
var qk = new QueryKey();
qk.cbData = unchecked((uint)indexKey.Length);
qk.pbData = mem.Pointer;
CascStorageFileSafeHandle hFile;
if (!_api.CascOpenFileByIndexKey!(_handle, ref qk, 0, out hFile))
throw new CascException();
hFile.Api = _api;
return new CascFileStream(hFile, _api);
}
/// <summary>
/// Opens a file by its encoding key.
/// </summary>
/// <param name="encodingKey">A 16-byte key representing the file. Encoding keys may be obtained via <see cref="SearchFiles(string, string)"/>.</param>
/// <returns>A CascFileStream, which implements a Stream.</returns>
/// <exception cref="ObjectDisposedException">Thrown if the CascStorageContext has been disposed.</exception>
/// <exception cref="System.IO.FileNotFoundException">Thrown if the file does not exist within the CASC storage container.</exception>
public CascFileStream OpenFileByEncodingKey(byte[] encodingKey)
{
if (_handle == null || _handle.IsInvalid)
throw new ObjectDisposedException("CascStorageContext");
using CoTaskMem mem = CoTaskMem.FromBytes(encodingKey);
var qk = new QueryKey();
qk.cbData = unchecked((uint)encodingKey.Length);
qk.pbData = mem.Pointer;
CascStorageFileSafeHandle hFile;
if (!_api.CascOpenFileByEncodingKey!(_handle, ref qk, 0, out hFile))
throw new CascException();
hFile.Api = _api;
return new CascFileStream(hFile, _api);
}
/// <summary>
/// Searches the files in the CASC container for files that match the specified pattern.
/// </summary>
/// <param name="mask">The mask to search. * and ? are valid tokens for substitution.</param>
/// <param name="listFilePath">A path to a listfile. Required if the CASC container is for World of Warcraft.</param>
/// <returns>An enumeration of matching file references in the CASC container.</returns>
public IEnumerable<CascFoundFile> SearchFiles(string mask, string? listFilePath = null)
{
if (_handle == null || _handle.IsInvalid)
throw new ObjectDisposedException("CascStorageContext");
#if NET20 || NET35
if (GameClient == CascKnownClient.WorldOfWarcraft && string.IsNullOrEmpty(listFilePath))
#else
if (GameClient == CascKnownClient.WorldOfWarcraft && string.IsNullOrWhiteSpace(listFilePath))
#endif
throw new ArgumentNullException("listFilePath");
var cfd = new CascFindData();
using var handle = _api.CascFindFirstFile!(_handle, mask, ref cfd, listFilePath);
if (handle.IsInvalid)
yield break;
handle.Api = _api;
yield return cfd.ToFoundFile(this);
while (_api.CascFindNextFile!(handle, ref cfd))
{
yield return cfd.ToFoundFile(this);
}
}
private bool CheckHasListfile()
{
if (_handle == null || _handle.IsInvalid)
throw new ObjectDisposedException("CascStorageContext");
uint storageInfo = 0, lengthNeeded = 4;
if (!_api.CascGetStorageInfo!(_handle, CascStorageInfoClass.Features, ref storageInfo, new IntPtr(4), ref lengthNeeded))
throw new CascException();
CascStorageFeatures features = (CascStorageFeatures)storageInfo;
#if NET20 || NET35
if ((features & CascStorageFeatures.HasListfile) != 0)
#else
if (features.HasFlag(CascStorageFeatures.HasListfile))
#endif
return true;
return false;
}
private CascKnownClient GetClient()
{
if (_handle == null || _handle.IsInvalid)
throw new ObjectDisposedException("CascStorageContext");
uint storageInfo = 0, lengthNeeded = 4;
if (!_api.CascGetStorageInfo!(_handle, CascStorageInfoClass.GameInfo, ref storageInfo, new IntPtr(4), ref lengthNeeded))
throw new CascException();
CascGameId gameId = (CascGameId)storageInfo;
return gameId.ToKnownClient();
}
private long GetFileCount()
{
if (_handle == null || _handle.IsInvalid)
throw new ObjectDisposedException("CascStorageContext");
uint storageInfo = 0, lengthNeeded = 4;
if (!_api.CascGetStorageInfo!(_handle, CascStorageInfoClass.Features, ref storageInfo, new IntPtr(4), ref lengthNeeded))
throw new CascException();
return storageInfo;
}
private int GetGameBuild()
{
if (_handle == null || _handle.IsInvalid)
throw new ObjectDisposedException("CascStorageContext");
uint storageInfo = 0, lengthNeeded = 4;
if (!_api.CascGetStorageInfo!(_handle, CascStorageInfoClass.Features, ref storageInfo, new IntPtr(4), ref lengthNeeded))
throw new CascException();
return unchecked((int)storageInfo);
}
/// <summary>
/// Gets whether the CASC container has a listfile or if one must be supplied while searching.
/// </summary>
public bool HasListfile
{
get
{
#if NET20 || NET35
if (_hasListfile == null)
_hasListfile = CheckHasListfile();
#endif
return _hasListfile.Value;
}
}
/// <summary>
/// Gets the number of files in the CASC container.
/// </summary>
public long FileCount
{
get
{
#if NET20 || NET35
if (_fileCount == null)
_fileCount = GetFileCount();
#endif
return _fileCount.Value;
}
}
/// <summary>
/// Gets the build number of the game.
/// </summary>
public int GameBuild
{
get
{
#if NET20 || NET35
if (_gameBuild == null)
_gameBuild = GetGameBuild();
#endif
return _gameBuild.Value;
}
}
/// <summary>
/// Gets the game client of the container, if it can be determined.
/// </summary>
public CascKnownClient GameClient
{
get
{
#if NET20 || NET35
if (_clientType == null)
_clientType = GetClient();
#endif
return _clientType.Value;
}
}
#region IDisposable implementation
/// <summary>
/// Finalizes the storage context.
/// </summary>
~CascStorageContext()
{
Dispose(false);
}
/// <summary>
/// Disposes the object.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes the object, cleaning up the unmanaged objects.
/// </summary>
/// <param name="disposing">True if this is being called via the Dispose() method; false if it's being called by the finalizer.</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_handle != null && !_handle.IsInvalid)
{
_handle.Close();
_handle = null;
}
}
}
#endregion
}
/// <summary>
/// Known locales supported by CASC.
/// </summary>
[Flags]
public enum CascLocales
{
/// <summary>
/// All available locales.
/// </summary>
All = -1,
/// <summary>
/// No locales.
/// </summary>
///
None = 0,
/// <summary>
/// Unknown
/// </summary>
Unknown1 = 1,
/// <summary>
/// English, United States
/// </summary>
EnUs = 2,
/// <summary>
/// Korean, South Korea
/// </summary>
KoKr = 4,
/// <summary>
/// Reserved (unknown)
/// </summary>
Reserved = 8,
/// <summary>
/// French, France
/// </summary>
FrFr = 0x10,
/// <summary>
/// German, Germany
/// </summary>
DeDe = 0x20,
/// <summary>
/// Chinese, China
/// </summary>
ZhCn = 0x40,
/// <summary>
/// Spanish, Spain
/// </summary>
EsEs = 0x80,
/// <summary>
/// Chinese, Taiwan
/// </summary>
ZhTw = 0x100,
/// <summary>
/// English, Great Britain
/// </summary>
EnGb = 0x200,
/// <summary>
/// English, China
/// </summary>
EnCn = 0x400,
/// <summary>
/// English, Taiwan
/// </summary>
EnTw = 0x800,
/// <summary>
/// Spanish, Mexico
/// </summary>
EsMx = 0x1000,
/// <summary>
/// Russian, Russia
/// </summary>
RuRu = 0x2000,
/// <summary>
/// Portuguese, Brazil
/// </summary>
PtBr = 0x4000,
/// <summary>
/// Italian, Italy
/// </summary>
ItIt = 0x8000,
/// <summary>
/// Portuguese, Portugal
/// </summary>
PtPt = 0x10000,
}
/// <summary>
/// Known clients supporting CASC
/// </summary>
public enum CascKnownClient
{
/// <summary>
/// The game client was unrecognized.
/// </summary>
Unknown = -1,
/// <summary>
/// Heroes of the Storm
/// </summary>
HeroesOfTheStorm = 0,
/// <summary>
/// Diablo 3
/// </summary>
Diablo3 = 1,
/// <summary>
/// World of Warcraft
/// </summary>
WorldOfWarcraft = 2,
/// <summary>
/// Overwatch
/// </summary>
Overwatch = 3,
/// <summary>
/// Starcraft 2
/// </summary>
Starcraft2 = 4,
}
}

View File

@@ -1,29 +0,0 @@
using System;
using Microsoft.Win32.SafeHandles;
namespace CascLibSharp.Native
{
internal class CascFileEnumerationSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public CascFileEnumerationSafeHandle()
: base(true) { }
public CascFileEnumerationSafeHandle(IntPtr handle)
: this()
{
SetHandle(handle);
}
protected override bool ReleaseHandle()
{
var api = Api ?? CascApi.Instance;
return api.CascFindClose!(DangerousGetHandle());
}
internal CascApi? Api
{
get;
set;
}
}
}

View File

@@ -1,32 +0,0 @@
using System;
using Microsoft.Win32.SafeHandles;
namespace CascLibSharp.Native
{
internal class CascStorageFileSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public CascStorageFileSafeHandle()
: base(true)
{
}
public CascStorageFileSafeHandle(IntPtr handle)
: this()
{
SetHandle(handle);
}
protected override bool ReleaseHandle()
{
var api = Api ?? CascApi.Instance;
return api.CascCloseFile!(DangerousGetHandle());
}
internal CascApi? Api
{
get;
set;
}
}
}

View File

@@ -1,32 +0,0 @@
using System;
using Microsoft.Win32.SafeHandles;
namespace CascLibSharp.Native
{
internal class CascStorageSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public CascStorageSafeHandle()
: base(true)
{
}
public CascStorageSafeHandle(IntPtr handle)
: this()
{
SetHandle(handle);
}
protected override bool ReleaseHandle()
{
var api = Api ?? CascApi.Instance;
return api.CascCloseStorage!(DangerousGetHandle());
}
internal CascApi? Api
{
get;
set;
}
}
}

View File

@@ -1,60 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace CascLibSharp.Native
{
internal sealed class CoTaskMem : IDisposable
{
private IntPtr _mem;
private int _size;
public CoTaskMem(int size)
{
_mem = Marshal.AllocCoTaskMem(size);
if (_mem == IntPtr.Zero)
throw new OutOfMemoryException();
_size = size;
}
public static CoTaskMem FromBytes(byte[] b)
{
var result = new CoTaskMem(b.Length);
Marshal.Copy(b, 0, result._mem, b.Length);
return result;
}
public IntPtr Pointer
{
get
{
if (_mem == IntPtr.Zero)
throw new ObjectDisposedException("CoTaskMem");
return _mem;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (_mem != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(_mem);
_mem = IntPtr.Zero;
_size = 0;
}
}
~CoTaskMem()
{
Dispose(false);
}
}
}

View File

@@ -1,116 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace CascLibSharp.Native
{
internal static class NativeMethods
{
[DllImport("kernel32", SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32", SetLastError = true)]
public static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, uint dwFlags);
}
internal enum CascStorageInfoClass
{
FileCount,
Features,
GameInfo,
GameBuild,
}
internal enum CascStorageFeatures
{
None = 0x0,
HasListfile = 0x1,
}
/*
* #define CASC_GAME_HOTS 0x00010000 // Heroes of the Storm
#define CASC_GAME_WOW6 0x00020000 // World of Warcraft - Warlords of Draenor
#define CASC_GAME_DIABLO3 0x00030000 // Diablo 3 since PTR 2.2.0
#define CASC_GAME_OVERWATCH 0x00040000 // Overwatch since PTR 24919*
*/
internal enum CascGameId
{
Hots = 0x00010000,
Wow6 = 0x00020000,
Diablo3 = 0x00030000,
Overwatch = 0x00040000,
Starcraft2 = 0x00050000,
}
internal static class GameConverterExtensions
{
private static readonly Dictionary<CascGameId, CascKnownClient> GameClientMap = new()
{
{ CascGameId.Hots, CascKnownClient.HeroesOfTheStorm },
{ CascGameId.Wow6, CascKnownClient.WorldOfWarcraft },
{ CascGameId.Diablo3, CascKnownClient.Diablo3 },
{ CascGameId.Overwatch, CascKnownClient.Overwatch },
{ CascGameId.Starcraft2, CascKnownClient.Starcraft2 },
};
private static readonly Dictionary<CascKnownClient, CascGameId> ClientGameMap = new()
{
{ CascKnownClient.HeroesOfTheStorm, CascGameId.Hots },
{ CascKnownClient.WorldOfWarcraft, CascGameId.Wow6 },
{ CascKnownClient.Diablo3, CascGameId.Diablo3 },
{ CascKnownClient.Overwatch, CascGameId.Overwatch },
{ CascKnownClient.Starcraft2, CascGameId.Starcraft2 },
};
public static CascKnownClient ToKnownClient(this CascGameId gameId)
{
if (!GameClientMap.TryGetValue(gameId, out CascKnownClient result))
result = CascKnownClient.Unknown;
return result;
}
public static CascGameId ToGameId(this CascKnownClient knownClient)
{
if (!ClientGameMap.TryGetValue(knownClient, out CascGameId result))
throw new ArgumentException("Invalid client.");
return result;
}
}
internal struct QueryKey
{
public IntPtr pbData;
public uint cbData;
}
internal unsafe struct CascFindData
{
const int MAX_PATH = 260;
public fixed byte szFileName[MAX_PATH];
public IntPtr szPlainName;
//public ulong FileNameHash;
public fixed byte EncodingKey[16];
//public uint dwPackageIndex;
public uint dwLocaleFlags;
public uint dwFileSize;
public unsafe CascFoundFile ToFoundFile(CascStorageContext ownerContext)
{
string? fileName = null;
fixed (void* pFileName = szFileName)
{
fileName = Marshal.PtrToStringAnsi(new IntPtr(pFileName));
}
byte[] encodingKey = new byte[16];
fixed (void* pEncodingKey = EncodingKey)
{
Marshal.Copy(new IntPtr(pEncodingKey), encodingKey, 0, 16);
}
return new CascFoundFile(fileName, szPlainName, encodingKey, (CascLocales)dwLocaleFlags, dwFileSize, ownerContext);
}
}
}

View File

@@ -1,10 +1,9 @@
# External Library Notes
This directory contains multiple external libraries. Here is the status of each:
This directory contains external libraries.
| Directory | Library | Status |
| --------- | ------- | ------ |
| CascLibSharp | [stormlibsharp](https://github.com/robpaveza/stormlibsharp) | Direct code duplicate |
| StormLibSharp | [stormlibsharp](https://github.com/robpaveza/stormlibsharp) | Direct code duplicate |
**CascLibSharp** and **StormLibSharp** have multiple changes made to accomodate modern .NET versions. The original code is MIT licensed.
**StormLibSharp** has multiple changes made to accomodate modern .NET versions. The original code is MIT licensed.