Directly include StormLibSharp code

This commit is contained in:
Matt Nadareski
2025-11-02 21:11:39 -05:00
parent 6b48164b97
commit 1378b87ea6
21 changed files with 2567 additions and 1 deletions

View File

@@ -0,0 +1,211 @@
using CascLibSharp.Native;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
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 high;
uint low = CascGetFileSizeBase(hFile, out 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 arch = IntPtr.Size == 8 ? "x64" : "x86";
#if DEBUG
string build = "dbg";
#else
string build = "fre";
#endif
string mainPath = Path.Combine(directory, "CascLib", build, arch, "CascLib.dll");
if (File.Exists(mainPath))
return FromFile(mainPath);
string alternatePath = Path.Combine(directory, "CascLib", arch, "CascLib.dll");
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));
}
private static Lazy<CascApi> _sharedInstance = new Lazy<CascApi>(Load);
public static CascApi Instance
{
get
{
return _sharedInstance.Value;
}
}
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

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
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

@@ -0,0 +1,168 @@
using CascLibSharp.Native;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CascLibSharp
{
/// <summary>
/// Provides a view of the data in a file contained within CASC storage.
/// </summary>
public class CascFileStream : Stream
{
private CascStorageFileSafeHandle _handle;
private 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("offset");
if (count < 0)
throw new ArgumentException("count");
if (offset + count > len)
throw new ArgumentException("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

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace CascLibSharp
{
/// <summary>
/// Represents a file found in a CASC container search.
/// </summary>
public class CascFoundFile
{
private WeakReference<CascStorageContext> _ownerContext;
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;
_ownerContext = new WeakReference<CascStorageContext>(ownerContext);
}
/// <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()
{
CascStorageContext context;
if (!_ownerContext.TryGetTarget(out context))
throw new ObjectDisposedException("The owning context has been closed.");
return context.OpenFileByEncodingKey(EncodingKey);
}
}
}

View File

@@ -0,0 +1,403 @@
using CascLibSharp.Native;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace CascLibSharp
{
/// <summary>
/// Represents a CASC storage directory.
/// </summary>
public class CascStorageContext : IDisposable
{
private CascApi _api;
private CascStorageSafeHandle _handle;
private Lazy<bool> _hasListfile;
private Lazy<CascKnownClient> _clientType;
private Lazy<long> _fileCount;
private Lazy<int> _gameBuild;
/// <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;
_hasListfile = new Lazy<bool>(CheckHasListfile);
_clientType = new Lazy<CascKnownClient>(GetClient);
_fileCount = new Lazy<long>(GetFileCount);
_gameBuild = new Lazy<int>(GetGameBuild);
}
/// <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))
{
QueryKey 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))
{
QueryKey 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 (this.GameClient == CascKnownClient.WorldOfWarcraft && string.IsNullOrWhiteSpace(listFilePath))
throw new ArgumentNullException("listFilePath");
CascFindData 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 (features.HasFlag(CascStorageFeatures.HasListfile))
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
{
return _hasListfile.Value;
}
}
/// <summary>
/// Gets the number of files in the CASC container.
/// </summary>
public long FileCount
{
get
{
return _fileCount.Value;
}
}
/// <summary>
/// Gets the build number of the game.
/// </summary>
public int GameBuild
{
get
{
return _gameBuild.Value;
}
}
/// <summary>
/// Gets the game client of the container, if it can be determined.
/// </summary>
public CascKnownClient GameClient
{
get
{
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.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

@@ -0,0 +1,32 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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

@@ -0,0 +1,36 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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

@@ -0,0 +1,36 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
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)
{
CoTaskMem 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

@@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
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 Dictionary<CascGameId, CascKnownClient> GameClientMap = new Dictionary<CascGameId, CascKnownClient>
{
{ CascGameId.Hots, CascKnownClient.HeroesOfTheStorm },
{ CascGameId.Wow6, CascKnownClient.WorldOfWarcraft },
{ CascGameId.Diablo3, CascKnownClient.Diablo3 },
{ CascGameId.Overwatch, CascKnownClient.Overwatch },
{ CascGameId.Starcraft2, CascKnownClient.Starcraft2 },
};
private static Dictionary<CascKnownClient, CascGameId> ClientGameMap = new Dictionary<CascKnownClient, CascGameId>()
{
{ 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)
{
CascKnownClient result;
if (!GameClientMap.TryGetValue(gameId, out result))
result = CascKnownClient.Unknown;
return result;
}
public static CascGameId ToGameId(this CascKnownClient knownClient)
{
CascGameId result;
if (!ClientGameMap.TryGetValue(knownClient, out result))
throw new ArgumentException("Invalid client.");
return result;
}
}
#pragma warning disable 649
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);
}
}
#pragma warning restore 649
}

View File

@@ -4,4 +4,7 @@ This directory contains multiple external libraries. Here is the status of each:
| Directory | Library | Status |
| --------- | ------- | ------ |
| stormlibsharp | [stormlibsharp](https://github.com/robpaveza/stormlibsharp) | External submodule |
| 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.

View File

@@ -0,0 +1,414 @@
using StormLibSharp.Native;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace StormLibSharp
{
public class MpqArchive : IDisposable
{
private MpqArchiveSafeHandle _handle;
private List<MpqFileStream> _openFiles = new List<MpqFileStream>();
private FileAccess _accessType;
private List<MpqArchiveCompactingEventHandler> _compactCallbacks = new List<MpqArchiveCompactingEventHandler>();
private SFILE_COMPACT_CALLBACK _compactCallback;
#region Constructors / Factories
public MpqArchive(string filePath, FileAccess accessType)
{
_accessType = accessType;
SFileOpenArchiveFlags flags = SFileOpenArchiveFlags.TypeIsFile;
if (accessType == FileAccess.Read)
flags |= SFileOpenArchiveFlags.AccessReadOnly;
else
flags |= SFileOpenArchiveFlags.AccessReadWriteShare;
// constant 2 = SFILE_OPEN_HARD_DISK_FILE
if (!NativeMethods.SFileOpenArchive(filePath, 2, flags, out _handle))
throw new Win32Exception(); // Implicitly calls GetLastError
}
public MpqArchive(MemoryMappedFile file, FileAccess accessType)
{
_accessType = accessType;
string fileName = Win32Methods.GetFileNameOfMemoryMappedFile(file);
if (fileName == null)
throw new ArgumentException("Could not retrieve the name of the file to initialize.");
SFileOpenArchiveFlags flags = SFileOpenArchiveFlags.TypeIsMemoryMapped;
if (accessType == FileAccess.Read)
flags |= SFileOpenArchiveFlags.AccessReadOnly;
else
flags |= SFileOpenArchiveFlags.AccessReadWriteShare;
// constant 2 = SFILE_OPEN_HARD_DISK_FILE
if (!NativeMethods.SFileOpenArchive(fileName, 2, flags, out _handle))
throw new Win32Exception(); // Implicitly calls GetLastError
}
private MpqArchive(string filePath, MpqArchiveVersion version, MpqFileStreamAttributes listfileAttributes, MpqFileStreamAttributes attributesFileAttributes, int maxFileCount)
{
if (maxFileCount < 0)
throw new ArgumentException("maxFileCount");
SFileOpenArchiveFlags flags = SFileOpenArchiveFlags.TypeIsFile | SFileOpenArchiveFlags.AccessReadWriteShare;
flags |= (SFileOpenArchiveFlags)version;
//SFILE_CREATE_MPQ create = new SFILE_CREATE_MPQ()
//{
// cbSize = unchecked((uint)Marshal.SizeOf(typeof(SFILE_CREATE_MPQ))),
// dwMaxFileCount = unchecked((uint)maxFileCount),
// dwMpqVersion = (uint)version,
// dwFileFlags1 = (uint)listfileAttributes,
// dwFileFlags2 = (uint)attributesFileAttributes,
// dwStreamFlags = (uint)flags,
//};
//if (!NativeMethods.SFileCreateArchive2(filePath, ref create, out _handle))
// throw new Win32Exception();
if (!NativeMethods.SFileCreateArchive(filePath, (uint)flags, int.MaxValue, out _handle))
throw new Win32Exception();
}
public static MpqArchive CreateNew(string mpqPath, MpqArchiveVersion version)
{
return CreateNew(mpqPath, version, MpqFileStreamAttributes.None, MpqFileStreamAttributes.None, int.MaxValue);
}
public static MpqArchive CreateNew(string mpqPath, MpqArchiveVersion version, MpqFileStreamAttributes listfileAttributes,
MpqFileStreamAttributes attributesFileAttributes, int maxFileCount)
{
return new MpqArchive(mpqPath, version, listfileAttributes, attributesFileAttributes, maxFileCount);
}
#endregion
#region Properties
// TODO: Move to common location.
// This is a global setting, not per-archive setting.
//public int Locale
//{
// get
// {
// throw new NotImplementedException();
// }
// set
// {
// throw new NotImplementedException();
// }
//}
public long MaxFileCount
{
get
{
VerifyHandle();
return NativeMethods.SFileGetMaxFileCount(_handle);
}
set
{
if (value < 0 || value > uint.MaxValue)
throw new ArgumentException("value");
VerifyHandle();
if (!NativeMethods.SFileSetMaxFileCount(_handle, unchecked((uint)value)))
throw new Win32Exception();
}
}
private void VerifyHandle()
{
if (_handle == null || _handle.IsInvalid)
throw new ObjectDisposedException("MpqArchive");
}
public bool IsPatchedArchive
{
get
{
VerifyHandle();
return NativeMethods.SFileIsPatchedArchive(_handle);
}
}
#endregion
public void Flush()
{
VerifyHandle();
if (!NativeMethods.SFileFlushArchive(_handle))
throw new Win32Exception();
}
public int AddListFile(string listfileContents)
{
VerifyHandle();
return NativeMethods.SFileAddListFile(_handle, listfileContents);
}
public void AddFileFromDisk(string filePath, string archiveName)
{
VerifyHandle();
if (!NativeMethods.SFileAddFile(_handle, filePath, archiveName, 0))
throw new Win32Exception();
}
public void Compact(string listfile)
{
VerifyHandle();
if (!NativeMethods.SFileCompactArchive(_handle, listfile, false))
throw new Win32Exception();
}
private void _OnCompact(IntPtr pvUserData, uint dwWorkType, ulong bytesProcessed, ulong totalBytes)
{
MpqArchiveCompactingEventArgs args = new MpqArchiveCompactingEventArgs(dwWorkType, bytesProcessed, totalBytes);
OnCompacting(args);
}
protected virtual void OnCompacting(MpqArchiveCompactingEventArgs e)
{
foreach (var cb in _compactCallbacks)
{
cb(this, e);
}
}
public event MpqArchiveCompactingEventHandler Compacting
{
add
{
VerifyHandle();
_compactCallback = _OnCompact;
if (!NativeMethods.SFileSetCompactCallback(_handle, _compactCallback, IntPtr.Zero))
throw new Win32Exception();
_compactCallbacks.Add(value);
}
remove
{
_compactCallbacks.Remove(value);
VerifyHandle();
if (_compactCallbacks.Count == 0)
{
if (!NativeMethods.SFileSetCompactCallback(_handle, null, IntPtr.Zero))
{
// Don't do anything here. Remove shouldn't fail hard.
}
}
}
}
// TODO: Determine if SFileGetAttributes/SFileSetAttributes/SFileUpdateFileAttributes deserves a projection.
// It's unclear - these seem to affect the (attributes) file but I can't figure out exactly what that means.
public void AddPatchArchive(string patchPath)
{
VerifyHandle();
if (!NativeMethods.SFileOpenPatchArchive(_handle, patchPath, null, 0))
throw new Win32Exception();
}
public void AddPatchArchives(IEnumerable<string> patchPaths)
{
if (patchPaths == null)
throw new ArgumentNullException("patchPaths");
VerifyHandle();
foreach (string path in patchPaths)
{
// Don't sublet to AddPatchArchive to avoid having to repeatedly call VerifyHandle()
if (!NativeMethods.SFileOpenPatchArchive(_handle, path, null, 0))
throw new Win32Exception();
}
}
public bool HasFile(string fileToFind)
{
VerifyHandle();
return NativeMethods.SFileHasFile(_handle, fileToFind);
}
public MpqFileStream OpenFile(string fileName)
{
VerifyHandle();
MpqFileSafeHandle fileHandle;
if (!NativeMethods.SFileOpenFileEx(_handle, fileName, 0, out fileHandle))
throw new Win32Exception();
MpqFileStream fs = new MpqFileStream(fileHandle, _accessType, this);
_openFiles.Add(fs);
return fs;
}
public void ExtractFile(string fileToExtract, string destinationPath)
{
VerifyHandle();
if (!NativeMethods.SFileExtractFile(_handle, fileToExtract, destinationPath, 0))
throw new Win32Exception();
}
public MpqFileVerificationResults VerifyFile(string fileToVerify)
{
VerifyHandle();
return (MpqFileVerificationResults)NativeMethods.SFileVerifyFile(_handle, fileToVerify, 0);
}
// TODO: Consider SFileVerifyRawData
public MpqArchiveVerificationResult VerifyArchive()
{
VerifyHandle();
return (MpqArchiveVerificationResult)NativeMethods.SFileVerifyArchive(_handle);
}
#region IDisposable implementation
public void Dispose()
{
Dispose(true);
}
~MpqArchive()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Release owned files first.
if (_openFiles != null)
{
foreach (var file in _openFiles)
{
file.Dispose();
}
_openFiles.Clear();
_openFiles = null;
}
// Release
if (_handle != null && !_handle.IsInvalid)
{
_handle.Close();
_handle = null;
}
}
}
internal void RemoveOwnedFile(MpqFileStream file)
{
_openFiles.Remove(file);
}
#endregion
}
public enum MpqArchiveVersion
{
Version1 = 0,
Version2 = 0x01000000,
Version3 = 0x02000000,
Version4 = 0x03000000,
}
[Flags]
public enum MpqFileStreamAttributes
{
None = 0x0,
}
[Flags]
public enum MpqFileVerificationResults
{
/// <summary>
/// There were no errors with the file.
/// </summary>
Verified = 0,
/// <summary>
/// Failed to open the file
/// </summary>
Error = 0x1,
/// <summary>
/// Failed to read all data from the file
/// </summary>
ReadError = 0x2,
/// <summary>
/// File has sector CRC
/// </summary>
HasSectorCrc = 0x4,
/// <summary>
/// Sector CRC check failed
/// </summary>
SectorCrcError = 0x8,
/// <summary>
/// File has CRC32
/// </summary>
HasChecksum = 0x10,
/// <summary>
/// CRC32 check failed
/// </summary>
ChecksumError = 0x20,
/// <summary>
/// File has data MD5
/// </summary>
HasMd5 = 0x40,
/// <summary>
/// MD5 check failed
/// </summary>
Md5Error = 0x80,
/// <summary>
/// File has raw data MD5
/// </summary>
HasRawMd5 = 0x100,
/// <summary>
/// Raw MD5 check failed
/// </summary>
RawMd5Error = 0x200,
}
public enum MpqArchiveVerificationResult
{
/// <summary>
/// There is no signature in the MPQ
/// </summary>
NoSignature = 0,
/// <summary>
/// There was an error during verifying signature (like no memory)
/// </summary>
VerificationFailed = 1,
/// <summary>
/// There is a weak signature and sign check passed
/// </summary>
WeakSignatureVerified = 2,
/// <summary>
/// There is a weak signature but sign check failed
/// </summary>
WeakSignatureFailed = 3,
/// <summary>
/// There is a strong signature and sign check passed
/// </summary>
StrongSignatureVerified = 4,
/// <summary>
/// There is a strong signature but sign check failed
/// </summary>
StrongSignatureFailed = 5,
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StormLibSharp
{
public delegate void MpqArchiveCompactingEventHandler(MpqArchive sender, MpqArchiveCompactingEventArgs e);
public class MpqArchiveCompactingEventArgs : EventArgs
{
internal MpqArchiveCompactingEventArgs(uint dwWorkType, ulong processed, ulong total)
{
unchecked
{
WorkType = (MpqCompactingWorkType)dwWorkType;
BytesProcessed = (long)processed;
TotalBytes = (long)total;
}
}
public MpqCompactingWorkType WorkType
{
get;
private set;
}
public long BytesProcessed
{
get;
private set;
}
public long TotalBytes
{
get;
private set;
}
}
public enum MpqCompactingWorkType
{
CheckingFiles = 1,
CheckingHashTable = 2,
CopyingNonMpqData = 3,
CompactingFiles = 4,
ClosingArchive = 5,
}
}

View File

@@ -0,0 +1,185 @@
using StormLibSharp.Native;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
namespace StormLibSharp
{
public class MpqFileStream : Stream
{
private MpqFileSafeHandle _handle;
private FileAccess _accessType;
private MpqArchive _owner;
internal MpqFileStream(MpqFileSafeHandle handle, FileAccess accessType, MpqArchive owner)
{
_handle = handle;
_accessType = accessType;
_owner = owner;
}
private void VerifyHandle()
{
if (_handle == null || _handle.IsInvalid || _handle.IsClosed)
throw new ObjectDisposedException("MpqFileStream");
}
public override bool CanRead
{
get { VerifyHandle(); return true; }
}
public override bool CanSeek
{
get { VerifyHandle(); return true; }
}
public override bool CanWrite
{
get { VerifyHandle(); return _accessType != FileAccess.Read; }
}
public override void Flush()
{
VerifyHandle();
_owner.Flush();
}
public override long Length
{
get
{
VerifyHandle();
uint high = 0;
uint low = NativeMethods.SFileGetFileSize(_handle, ref high);
ulong val = (high << 32) | low;
return unchecked((long)val);
}
}
public override long Position
{
get
{
VerifyHandle();
return NativeMethods.SFileGetFilePointer(_handle);
}
set
{
Seek(value, SeekOrigin.Begin);
}
}
public override unsafe int Read(byte[] buffer, int offset, int count)
{
if (buffer == null)
throw new ArgumentNullException("buffer");
if (offset > buffer.Length || (offset + count) > buffer.Length)
throw new ArgumentException();
if (count < 0)
throw new ArgumentOutOfRangeException("count");
VerifyHandle();
bool success;
uint read;
fixed (byte* pb = &buffer[offset])
{
NativeOverlapped overlapped = default(NativeOverlapped);
success = NativeMethods.SFileReadFile(_handle, new IntPtr(pb), unchecked((uint)count), out read, ref overlapped);
}
if (!success)
{
int lastError = Win32Methods.GetLastError();
if (lastError != 38) // EOF
throw new Win32Exception(lastError);
}
return unchecked((int)read);
}
public override long Seek(long offset, SeekOrigin origin)
{
VerifyHandle();
uint low, high;
low = unchecked((uint)(offset & 0xffffffffu));
high = unchecked((uint)(offset >> 32));
return NativeMethods.SFileSetFilePointer(_handle, low, ref high, (uint)origin);
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override unsafe void Write(byte[] buffer, int offset, int count)
{
VerifyHandle();
if (buffer == null)
throw new ArgumentNullException("buffer");
if (offset > buffer.Length || (offset + count) > buffer.Length)
throw new ArgumentException();
if (count < 0)
throw new ArgumentOutOfRangeException("count");
VerifyHandle();
bool success;
fixed (byte* pb = &buffer[offset])
{
success = NativeMethods.SFileWriteFile(_handle, new IntPtr(pb), unchecked((uint)count), 0u);
}
if (!success)
throw new Win32Exception();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
if (_handle != null && !_handle.IsInvalid)
{
_handle.Close();
_handle = null;
}
if (_owner != null)
{
_owner.RemoveOwnedFile(this);
_owner = null;
}
}
}
// TODO: Seems like the right place for SFileGetFileInfo, but will need to determine
// what value add these features have except for sophisticated debugging purposes
// (like in Ladis' MPQ Editor app).
public int ChecksumCrc32
{
get
{
throw new NotImplementedException();
}
}
public byte[] GetMd5Hash()
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace StormLibSharp.Native
{
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate void SFILE_DOWNLOAD_CALLBACK(IntPtr pvUserData, ulong byteOffset, uint dwTotalBytes);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate void SFILE_COMPACT_CALLBACK(IntPtr pvUserData, uint dwWorkType, ulong bytesProcessed, ulong totalBytes);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
internal delegate void SFILE_ADDFILE_CALLBACK(IntPtr pvUserData, uint dwBytesWritte, uint dwTotalBytes, bool bFinalCall);
}

View File

@@ -0,0 +1,26 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace StormLibSharp.Native
{
internal sealed class MpqArchiveSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public MpqArchiveSafeHandle(IntPtr handle)
: base(true)
{
this.SetHandle(handle);
}
public MpqArchiveSafeHandle()
: base(true) { }
protected override bool ReleaseHandle()
{
return NativeMethods.SFileCloseArchive(this.handle);
}
}
}

View File

@@ -0,0 +1,27 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StormLibSharp.Native
{
internal sealed class MpqFileSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public MpqFileSafeHandle(IntPtr handle)
: base(true)
{
this.SetHandle(handle);
}
public MpqFileSafeHandle()
: base(true)
{
}
protected override bool ReleaseHandle()
{
return NativeMethods.SFileCloseFile(this.handle);
}
}
}

View File

@@ -0,0 +1,497 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace StormLibSharp.Native
{
internal static class NativeMethods
{
private const string STORMLIB = "stormlib.dll";
#region Functions for manipulation with StormLib global flags
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern uint SFileGetLocale();
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern uint SFileSetLocale(uint lcNewLocale);
#endregion
#region Functions for archive manipulation
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileOpenArchive(
[MarshalAs(UnmanagedType.LPTStr)] string szMpqName,
uint dwPriority,
SFileOpenArchiveFlags dwFlags,
out MpqArchiveSafeHandle phMpq
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileCreateArchive(
[MarshalAs(UnmanagedType.LPTStr)] string szMpqName,
uint dwCreateFlags,
uint dwMaxFileCount,
out MpqArchiveSafeHandle phMpq
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileCreateArchive2(
[MarshalAs(UnmanagedType.LPTStr)] string szMpqName,
ref SFILE_CREATE_MPQ pCreateInfo,
out MpqArchiveSafeHandle phMpq
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileSetDownloadCallback(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.FunctionPtr)] SFILE_DOWNLOAD_CALLBACK pfnCallback,
IntPtr pvUserData
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileFlushArchive(MpqArchiveSafeHandle hMpq);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileCloseArchive(IntPtr hMpq);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileCloseArchive(MpqArchiveSafeHandle hMpq);
#endregion
#region Adds another listfile into MPQ.
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern int SFileAddListFile(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPStr)] string szListFile
);
#endregion
#region Archive compacting
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileSetCompactCallback(
MpqArchiveSafeHandle hMpq,
SFILE_COMPACT_CALLBACK compactCB,
IntPtr pvUserData
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileCompactArchive(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPStr)] string szListFile,
bool bReserved
);
#endregion
#region Maximum file count
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern uint SFileGetMaxFileCount(MpqArchiveSafeHandle hMpq);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileSetMaxFileCount(MpqArchiveSafeHandle hMpq, uint dwMaxFileCount);
#endregion
#region Changing (attributes) file
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern uint SFileGetAttributes(MpqArchiveSafeHandle hMpq);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileSetAttributes(MpqArchiveSafeHandle hMpq, uint dwFlags);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileUpdateFileAttributes(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPStr)] string szFileName
);
#endregion
#region Functions for manipulation with patch archives
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileOpenPatchArchive(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPTStr)] string szPatchMpqName,
[MarshalAs(UnmanagedType.LPStr)] string szPatchPathPrefix,
uint dwFlags
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileIsPatchedArchive(MpqArchiveSafeHandle hMpq);
#endregion
#region Functions for file manipulation
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileHasFile(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPStr)] string szFileName
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileOpenFileEx(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPStr)] string szFileName,
uint dwSearchScope,
out MpqFileSafeHandle phFile
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern uint SFileGetFileSize(MpqFileSafeHandle hFile, ref uint pdwFileSizeHigh);
public static unsafe uint SFileGetFilePointer(
MpqFileSafeHandle hFile
)
{
if (hFile.IsInvalid || hFile.IsClosed)
throw new InvalidOperationException();
IntPtr handle = hFile.DangerousGetHandle();
_TMPQFileHeader* header = (_TMPQFileHeader*)handle.ToPointer();
return header->dwFilePos;
}
//public static unsafe uint SFileGetFileSize(
// MpqFileSafeHandle hFile
// )
//{
// if (hFile.IsInvalid || hFile.IsClosed)
// throw new InvalidOperationException();
// IntPtr handle = hFile.DangerousGetHandle();
// _TMPQFileHeader* header = (_TMPQFileHeader*)handle.ToPointer();
// return header->pFileEntry->dwFileSize;
//}
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern uint SFileSetFilePointer(
MpqFileSafeHandle hFile,
uint lFilePos,
ref uint plFilePosHigh,
uint dwMoveMethod
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileReadFile(
MpqFileSafeHandle hFile,
IntPtr lpBuffer,
uint dwToRead,
out uint pdwRead,
ref System.Threading.NativeOverlapped lpOverlapped
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileCloseFile(IntPtr hFile);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileCloseFile(MpqFileSafeHandle hFile);
#region Retrieving info about a file in the archive
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileGetFileInfo(
IntPtr hMpqOrFile,
SFileInfoClass InfoClass,
IntPtr pvFileInfo,
uint cbFileInfoSize,
out uint pcbLengthNeeded
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileGetFileInfo(
MpqArchiveSafeHandle hMpqOrFile,
SFileInfoClass InfoClass,
IntPtr pvFileInfo,
uint cbFileInfoSize,
out uint pcbLengthNeeded
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileGetFileInfo(
MpqFileSafeHandle hMpqOrFile,
SFileInfoClass InfoClass,
IntPtr pvFileInfo,
uint cbFileInfoSize,
out uint pcbLengthNeeded
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileGetFileName(
MpqFileSafeHandle hFile,
[MarshalAs(UnmanagedType.LPStr)] out string szFileName
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileFreeFileInfo(
IntPtr pvFileInfo,
SFileInfoClass infoClass
);
#endregion
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileExtractFile(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPStr)] string szToExtract,
[MarshalAs(UnmanagedType.LPTStr)] string szExtracted,
uint dwSearchScope
);
#endregion
#region Functions for file and archive verification
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileGetFileChecksums(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPStr)] string szFileName,
out uint pdwCrc32,
IntPtr pMD5
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern uint SFileVerifyFile(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPStr)] string szFileName,
uint dwFlags
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern int SFileVerifyRawData(
MpqArchiveSafeHandle hMpq,
uint dwWhatToVerify,
[MarshalAs(UnmanagedType.LPStr)] string szFileName
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern uint SFileVerifyArchive(MpqArchiveSafeHandle hMpq);
#endregion
#region Functions for file searching
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern IntPtr SFileFindFirstFile(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPStr)] string szMask,
out _SFILE_FIND_DATA lpFindFileData,
[MarshalAs(UnmanagedType.LPStr)] string szListFile
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileFindNextFile(
IntPtr hFind,
[In, Out] ref _SFILE_FIND_DATA lpFindFileData
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileFindClose(IntPtr hFind);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern IntPtr SListFileFindFirstFile(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPStr)] string szListFile,
[MarshalAs(UnmanagedType.LPStr)] string szMask,
[In, Out] ref _SFILE_FIND_DATA lpFindFileData
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SListFileFindNextFile(
IntPtr hFind,
[In, Out] ref _SFILE_FIND_DATA lpFindFileData
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SListFileFindClose(IntPtr hFind);
#endregion
#region Locale support
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern int SFileEnumLocales(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPStr)] string szFileName,
IntPtr plcLocales,
ref uint pdwMaxLocales,
uint dwSearchScope
);
#endregion
#region Support for adding files to the MPQ
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileCreateFile(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPStr)] string szArchiveName,
ulong fileTime,
uint dwFileSize,
uint lcLocale,
uint dwFlags,
out IntPtr phFile
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileWriteFile(
MpqFileSafeHandle hFile,
IntPtr pvData,
uint dwSize,
uint dwCompression
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileFinishFile(MpqFileSafeHandle hFile);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileAddFileEx(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPTStr)] string szFileName,
[MarshalAs(UnmanagedType.LPStr)] string szArchivedName,
uint dwFlags,
uint dwCompression,
uint dwCompressionNext
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileAddFile(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPTStr)] string szFileName,
[MarshalAs(UnmanagedType.LPStr)] string szArchivedName,
uint dwFlags
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileAddWave(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPTStr)] string szFileName,
[MarshalAs(UnmanagedType.LPStr)] string szArchivedName,
uint dwFlags,
uint dwQuality
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileRemoveFile(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPStr)] string szFileName,
uint dwSearchScope
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileRenameFile(
MpqArchiveSafeHandle hMpq,
[MarshalAs(UnmanagedType.LPStr)] string szOldFileName,
[MarshalAs(UnmanagedType.LPStr)] string szNewFileName
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileSetFileLocale(
MpqFileSafeHandle hFile,
uint lcNewLocale
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileSetDataCompression(uint DataCompression);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern bool SFileSetAddFileCallback(
MpqArchiveSafeHandle hMpq,
SFILE_ADDFILE_CALLBACK AddFileCB,
IntPtr pvUserData
);
#endregion
#region Compression and decompression
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern int SCompImplode(
IntPtr pvOutBuffer,
ref int pcbOutBuffer,
IntPtr pvInBuffer,
int cbInBuffer
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern int SCompExplode(
IntPtr pvOutBuffer,
ref int pcbOutBuffer,
IntPtr pvInBuffer,
int cbInBuffer
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern int SCompCompress(
IntPtr pvOutBuffer,
ref int pcbOutBuffer,
IntPtr pvInBuffer,
int cbInBuffer,
uint uCompressionMask,
int nCmpType,
int nCmpLevel
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern int SCompDecompress(
IntPtr pvOutBuffer,
ref int pcbOutBuffer,
IntPtr pvInBuffer,
int cbInBuffer
);
[DllImport(STORMLIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, PreserveSig = true, SetLastError = true, ThrowOnUnmappableChar = false)]
public static extern int SCompDecompress2(
IntPtr pvOutBuffer,
ref int pcbOutBuffer,
IntPtr pvInBuffer,
int cbInBuffer
);
#endregion
}
#pragma warning disable 0169,0649
internal struct SFILE_CREATE_MPQ
{
public uint cbSize;
public uint dwMpqVersion;
private IntPtr pvUserData;
private uint cbUserData;
public uint dwStreamFlags;
public uint dwFileFlags1;
public uint dwFileFlags2;
public uint dwAttrFlags;
public uint dwSectorSize;
public uint dwRawChunkSize;
public uint dwMaxFileCount;
}
internal unsafe struct _SFILE_FIND_DATA
{
public fixed char cFileName[260]; // Full name of the found file
public IntPtr szPlainName; // Plain name of the found file
public uint dwHashIndex; // Hash table index for the file
public uint dwBlockIndex; // Block table index for the file
public uint dwFileSize; // File size in bytes
public uint dwFileFlags; // MPQ file flags
public uint dwCompSize; // Compressed file size
public uint dwFileTimeLo; // Low 32-bits of the file time (0 if not present)
public uint dwFileTimeHi; // High 32-bits of the file time (0 if not present)
public uint lcLocale; // Locale version
}
internal unsafe struct _TFileEntry
{
public ulong FileNameHash;
public ulong ByteOffset;
public ulong FileTime;
public uint dwHashIndex;
public uint dwFileSize;
public uint dwCmpSize;
public uint dwFlags;
public ushort lcLocale;
public ushort wPlatform;
public uint dwCrc32;
public fixed byte md5[16];
public IntPtr szFileName;
}
// Provides enough of _TMPQFile to get to the file size and current position.
internal unsafe struct _TMPQFileHeader
{
public IntPtr pStream;
public IntPtr ha;
public _TFileEntry* pFileEntry;
public uint dwFileKey;
public uint dwFilePos;
}
#pragma warning restore 0169,0649
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StormLibSharp.Native
{
internal enum SFileInfoClass
{
// Info classes for archives
SFileMpqFileName, // Name of the archive file (TCHAR [])
SFileMpqStreamBitmap, // Array of bits, each bit means availability of one block (BYTE [])
SFileMpqUserDataOffset, // Offset of the user data header (ULONGLONG)
SFileMpqUserDataHeader, // Raw (unfixed) user data header (TMPQUserData)
SFileMpqUserData, // MPQ USer data, without the header (BYTE [])
SFileMpqHeaderOffset, // Offset of the MPQ header (ULONGLONG)
SFileMpqHeaderSize, // Fixed size of the MPQ header
SFileMpqHeader, // Raw (unfixed) archive header (TMPQHeader)
SFileMpqHetTableOffset, // Offset of the HET table, relative to MPQ header (ULONGLONG)
SFileMpqHetTableSize, // Compressed size of the HET table (ULONGLONG)
SFileMpqHetHeader, // HET table header (TMPQHetHeader)
SFileMpqHetTable, // HET table as pointer. Must be freed using SFileFreeFileInfo
SFileMpqBetTableOffset, // Offset of the BET table, relative to MPQ header (ULONGLONG)
SFileMpqBetTableSize, // Compressed size of the BET table (ULONGLONG)
SFileMpqBetHeader, // BET table header, followed by the flags (TMPQBetHeader + DWORD[])
SFileMpqBetTable, // BET table as pointer. Must be freed using SFileFreeFileInfo
SFileMpqHashTableOffset, // Hash table offset, relative to MPQ header (ULONGLONG)
SFileMpqHashTableSize64, // Compressed size of the hash table (ULONGLONG)
SFileMpqHashTableSize, // Size of the hash table, in entries (DWORD)
SFileMpqHashTable, // Raw (unfixed) hash table (TMPQBlock [])
SFileMpqBlockTableOffset, // Block table offset, relative to MPQ header (ULONGLONG)
SFileMpqBlockTableSize64, // Compressed size of the block table (ULONGLONG)
SFileMpqBlockTableSize, // Size of the block table, in entries (DWORD)
SFileMpqBlockTable, // Raw (unfixed) block table (TMPQBlock [])
SFileMpqHiBlockTableOffset, // Hi-block table offset, relative to MPQ header (ULONGLONG)
SFileMpqHiBlockTableSize64, // Compressed size of the hi-block table (ULONGLONG)
SFileMpqHiBlockTable, // The hi-block table (USHORT [])
SFileMpqSignatures, // Signatures present in the MPQ (DWORD)
SFileMpqStrongSignatureOffset, // Byte offset of the strong signature, relative to begin of the file (ULONGLONG)
SFileMpqStrongSignatureSize, // Size of the strong signature (DWORD)
SFileMpqStrongSignature, // The strong signature (BYTE [])
SFileMpqArchiveSize64, // Archive size from the header (ULONGLONG)
SFileMpqArchiveSize, // Archive size from the header (DWORD)
SFileMpqMaxFileCount, // Max number of files in the archive (DWORD)
SFileMpqFileTableSize, // Number of entries in the file table (DWORD)
SFileMpqSectorSize, // Sector size (DWORD)
SFileMpqNumberOfFiles, // Number of files (DWORD)
SFileMpqRawChunkSize, // Size of the raw data chunk for MD5
SFileMpqStreamFlags, // Stream flags (DWORD)
SFileMpqIsReadOnly, // Nonzero if the MPQ is read only (DWORD)
// Info classes for files
SFileInfoPatchChain, // Chain of patches where the file is (TCHAR [])
SFileInfoFileEntry, // The file entry for the file (TFileEntry)
SFileInfoHashEntry, // Hash table entry for the file (TMPQHash)
SFileInfoHashIndex, // Index of the hash table entry (DWORD)
SFileInfoNameHash1, // The first name hash in the hash table (DWORD)
SFileInfoNameHash2, // The second name hash in the hash table (DWORD)
SFileInfoNameHash3, // 64-bit file name hash for the HET/BET tables (ULONGLONG)
SFileInfoLocale, // File locale (DWORD)
SFileInfoFileIndex, // Block index (DWORD)
SFileInfoByteOffset, // File position in the archive (ULONGLONG)
SFileInfoFileTime, // File time (ULONGLONG)
SFileInfoFileSize, // Size of the file (DWORD)
SFileInfoCompressedSize, // Compressed file size (DWORD)
SFileInfoFlags, // File flags from (DWORD)
SFileInfoEncryptionKey, // File encryption key
SFileInfoEncryptionKeyRaw, // Unfixed value of the file key
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StormLibSharp.Native
{
[Flags]
internal enum SFileOpenArchiveFlags : uint
{
None = 0,
TypeIsFile = None,
TypeIsMemoryMapped = 1,
TypeIsHttp = 2,
AccessReadOnly = 0x100,
AccessReadWriteShare = 0x200,
AccessUseBitmap = 0x400,
DontOpenListfile = 0x10000,
DontOpenAttributes = 0x20000,
DontSearchHeader = 0x40000,
ForceVersion1 = 0x80000,
CheckSectorCRC = 0x100000,
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace StormLibSharp.Native
{
internal static class Win32Methods
{
[DllImport("kernel32", ExactSpelling = false, SetLastError = true)]
public static extern uint GetMappedFileName(
IntPtr hProcess,
IntPtr fileHandle,
IntPtr lpFilename,
uint nSize
);
[DllImport("kernel32", ExactSpelling = false, SetLastError = true)]
public static extern uint GetFinalPathNameByHandle(
IntPtr hFile,
IntPtr lpszFilePath,
uint cchFilePath,
uint dwFlags
);
[DllImport("kernel32", SetLastError = false, ExactSpelling = false)]
public static extern int GetLastError();
public static string GetFileNameOfMemoryMappedFile(MemoryMappedFile file)
{
const uint size = 522;
IntPtr path = Marshal.AllocCoTaskMem(unchecked((int)size)); // MAX_PATH + 1 char
string result = null;
try
{
// constant 0x2 = VOLUME_NAME_NT
uint test = GetFinalPathNameByHandle(file.SafeMemoryMappedFileHandle.DangerousGetHandle(), path, size, 0x2);
if (test != 0)
throw new Win32Exception();
result = Marshal.PtrToStringAuto(path);
}
catch
{
uint test = GetMappedFileName(Process.GetCurrentProcess().Handle, file.SafeMemoryMappedFileHandle.DangerousGetHandle(), path, size);
if (test != 0)
throw new Win32Exception();
result = Marshal.PtrToStringAuto(path);
}
return result;
}
}
}