mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-07-09 02:16:55 +00:00
Merge pull request #39 from SabreTools/storm
Directly include StormLibSharp
This commit is contained in:
1
.github/workflows/build_and_test.yml
vendored
1
.github/workflows/build_and_test.yml
vendored
@@ -12,7 +12,6 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
|
||||
2
.github/workflows/check_pr.yml
vendored
2
.github/workflows/check_pr.yml
vendored
@@ -7,8 +7,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "SabreTools.Serialization/_EXTERNAL/stormlibsharp"]
|
||||
path = SabreTools.Serialization/_EXTERNAL/stormlibsharp
|
||||
url = https://github.com/robpaveza/stormlibsharp.git
|
||||
|
||||
@@ -9,7 +9,7 @@ Find the link to the Nuget package [here](https://www.nuget.org/packages/SabreTo
|
||||
The following non-project libraries (or ports thereof) are used for file handling:
|
||||
|
||||
- [GrindCore.SharpCompress](https://github.com/Nanook/GrindCore.SharpCompress) - Common archive format extraction
|
||||
- [StormLibSharp](https://github.com/robpaveza/stormlibsharp) - MoPaQ extraction [Unused in .NET Framework 2.0/3.5/4.0 and non-Windows builds due to Windows-specific libraries]
|
||||
- [StormLibSharp](https://github.com/robpaveza/stormlibsharp) - MoPaQ extraction [Unused in non-Windows builds due to Windows-specific libraries]
|
||||
|
||||
The following projects have influenced this library:
|
||||
|
||||
|
||||
@@ -16,6 +16,6 @@ namespace SabreTools.Data.Models.CDROM
|
||||
/// <summary>
|
||||
/// ISO9660 volume within the data track
|
||||
/// </summary>
|
||||
public Volume Volume { get; set; }
|
||||
public Volume Volume { get; set; } = new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,5 +79,29 @@ namespace SabreTools.Data.Models.MoPaQ
|
||||
public const uint MPQ_KEY_HASH_TABLE = 0xC3AF3770;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Special Entry Names
|
||||
|
||||
/// <summary>
|
||||
/// Name of internal listfile
|
||||
/// </summary>
|
||||
public const string LISTFILE_NAME = "(listfile)";
|
||||
|
||||
/// <summary>
|
||||
/// Name of internal signature
|
||||
/// </summary>
|
||||
public const string SIGNATURE_NAME = "(signature)";
|
||||
|
||||
/// <summary>
|
||||
/// Name of internal attributes file
|
||||
/// </summary>
|
||||
public const string ATTRIBUTES_NAME = "(attributes)";
|
||||
|
||||
/// <summary>
|
||||
/// Patch metadata
|
||||
/// </summary>
|
||||
public const string PATCH_METADATA_NAME = "(patch_metadata)";
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace SabreTools.Serialization.Readers
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled byte[] on success, null on error</returns>
|
||||
public static byte[]? ParseCDROMSystemArea(Stream data)
|
||||
public static byte[] ParseCDROMSystemArea(Stream data)
|
||||
{
|
||||
var systemArea = new byte[Constants.SystemAreaSectors * Constants.MinimumSectorSize];
|
||||
// Process in sectors
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<!-- Added due to StormLibSharp -->
|
||||
<NoWarn>CS8600;CS8601;CS8603;CS8604;CS8618;CS8625;CS8634;IL3000</NoWarn>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
@@ -43,25 +41,6 @@
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Exclude certain parts of external modules for by default -->
|
||||
<PropertyGroup>
|
||||
<DefaultItemExcludes>
|
||||
$(DefaultItemExcludes);
|
||||
**\AssemblyInfo.cs;
|
||||
_EXTERNAL\stormlibsharp\lib\**;
|
||||
_EXTERNAL\stormlibsharp\src\CascLibSharp\**;
|
||||
_EXTERNAL\stormlibsharp\src\TestConsole\**
|
||||
</DefaultItemExcludes>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Exclude all external modules for .NET Framework 2.0, .NET Framework 3.5, or non-Windows builds -->
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`))">
|
||||
<DefaultItemExcludes>
|
||||
$(DefaultItemExcludes);
|
||||
_EXTERNAL\**
|
||||
</DefaultItemExcludes>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- These are needed for dealing with native Windows DLLs -->
|
||||
<ItemGroup>
|
||||
<Content Include="runtimes\**\*">
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using System;
|
||||
#if NET452_OR_GREATER || NETCOREAPP
|
||||
using System.IO;
|
||||
using StormLibSharp;
|
||||
#endif
|
||||
using static SabreTools.Data.Models.MoPaQ.Constants;
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
{
|
||||
@@ -11,7 +10,6 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// <inheritdoc/>
|
||||
public bool Extract(string outputDirectory, bool includeDebug)
|
||||
{
|
||||
#if NET452_OR_GREATER || NETCOREAPP
|
||||
try
|
||||
{
|
||||
// Limit use to Windows only
|
||||
@@ -27,7 +25,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
// Try to open the archive and listfile
|
||||
var mpqArchive = new MpqArchive(Filename, FileAccess.Read);
|
||||
string? listfile = null;
|
||||
MpqFileStream listStream = mpqArchive.OpenFile("(listfile)");
|
||||
MpqFileStream listStream = mpqArchive.OpenFile(LISTFILE_NAME);
|
||||
|
||||
// If we can't read the listfile, we just return
|
||||
if (!listStream.CanRead)
|
||||
@@ -62,24 +60,19 @@ namespace SabreTools.Serialization.Wrappers
|
||||
{
|
||||
mpqArchive.ExtractFile(sub, filename);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (includeDebug) System.Console.WriteLine(ex);
|
||||
if (includeDebug) Console.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (includeDebug) System.Console.WriteLine(ex);
|
||||
if (includeDebug) Console.WriteLine(ex);
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
Console.WriteLine("Extraction is not supported for this framework!");
|
||||
Console.WriteLine();
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
239
SabreTools.Serialization/_EXTERNAL/CascLibSharp/CascApi.cs
Normal file
239
SabreTools.Serialization/_EXTERNAL/CascLibSharp/CascApi.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
393
SabreTools.Serialization/_EXTERNAL/StormLibSharp/MpqArchive.cs
Normal file
393
SabreTools.Serialization/_EXTERNAL/StormLibSharp/MpqArchive.cs
Normal file
@@ -0,0 +1,393 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using StormLibSharp.Native;
|
||||
|
||||
namespace StormLibSharp
|
||||
{
|
||||
public class MpqArchive : IDisposable
|
||||
{
|
||||
private MpqArchiveSafeHandle? _handle;
|
||||
private readonly List<MpqFileStream> _openFiles = [];
|
||||
private readonly FileAccess _accessType;
|
||||
private readonly List<MpqArchiveCompactingEventHandler> _compactCallbacks = [];
|
||||
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
|
||||
}
|
||||
|
||||
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(nameof(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)
|
||||
{
|
||||
var 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();
|
||||
|
||||
if (!NativeMethods.SFileOpenFileEx(_handle, fileName, 0, out MpqFileSafeHandle fileHandle))
|
||||
throw new Win32Exception();
|
||||
|
||||
var 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.
|
||||
foreach (var file in _openFiles)
|
||||
{
|
||||
file.Dispose();
|
||||
}
|
||||
|
||||
_openFiles.Clear();
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using StormLibSharp.Native;
|
||||
|
||||
namespace StormLibSharp
|
||||
{
|
||||
public class MpqFileStream : Stream
|
||||
{
|
||||
private MpqFileSafeHandle? _handle;
|
||||
private readonly 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(nameof(buffer));
|
||||
if (offset > buffer.Length || (offset + count) > buffer.Length)
|
||||
throw new ArgumentException();
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(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 = unchecked((uint)(offset & 0xffffffffu));
|
||||
uint 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(nameof(buffer));
|
||||
if (offset > buffer.Length || (offset + count) > buffer.Length)
|
||||
throw new ArgumentException();
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(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;
|
||||
}
|
||||
|
||||
_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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace StormLibSharp.Native
|
||||
{
|
||||
internal sealed class MpqArchiveSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
public MpqArchiveSafeHandle(IntPtr handle)
|
||||
: base(true)
|
||||
{
|
||||
SetHandle(handle);
|
||||
}
|
||||
|
||||
public MpqArchiveSafeHandle()
|
||||
: base(true) { }
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
return NativeMethods.SFileCloseArchive(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace StormLibSharp.Native
|
||||
{
|
||||
internal sealed class MpqFileSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
public MpqFileSafeHandle(IntPtr handle)
|
||||
: base(true)
|
||||
{
|
||||
SetHandle(handle);
|
||||
}
|
||||
|
||||
public MpqFileSafeHandle()
|
||||
: base(true)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
return NativeMethods.SFileCloseFile(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,520 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
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 == null || 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 CS0169
|
||||
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;
|
||||
}
|
||||
#pragma warning restore CS0169
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace StormLibSharp.Native
|
||||
{
|
||||
internal static class Win32Methods
|
||||
{
|
||||
[DllImport("kernel32", SetLastError = false, ExactSpelling = false)]
|
||||
public static extern int GetLastError();
|
||||
}
|
||||
}
|
||||
Submodule SabreTools.Serialization/_EXTERNAL/stormlibsharp deleted from 3bf1375317
Reference in New Issue
Block a user