forked from julianperrott/WowClassicGrindBot
-
-
Notifications
You must be signed in to change notification settings - Fork 129
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #535 from Xian55/refactor/ppather-memory-stream-ov…
…er-extract PPather: `MpqFileStream` over `SFileExtractFile`
- Loading branch information
Showing
10 changed files
with
445 additions
and
271 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
|
||
namespace StormDll; | ||
|
||
internal sealed class Archive | ||
{ | ||
public const uint SFILE_INVALID_SIZE = 0xFFFFFFFF; | ||
|
||
private readonly IntPtr handle; | ||
|
||
private readonly HashSet<string> fileList = new(StringComparer.InvariantCultureIgnoreCase); | ||
|
||
private static readonly bool Is64Bit = Environment.Is64BitProcess; | ||
|
||
public Archive(string file, out bool open, uint prio, OpenArchive flags) | ||
{ | ||
open = Is64Bit | ||
? StormDllx64.SFileOpenArchive(file, prio, flags, out handle) | ||
: StormDllx86.SFileOpenArchive(file, prio, flags, out handle); | ||
|
||
if (!open) | ||
return; | ||
|
||
using MpqFileStream mpq = GetStream("(listfile)"); | ||
using MemoryStream ms = new(mpq.ReadAllBytes()); | ||
using StreamReader stream = new(ms); | ||
|
||
while (!stream.EndOfStream) | ||
{ | ||
fileList.Add(stream.ReadLine()!); | ||
} | ||
|
||
if (fileList.Count == 0) | ||
throw new InvalidOperationException($"{nameof(fileList)} contains no elements!"); | ||
} | ||
|
||
public bool IsOpen() | ||
{ | ||
return handle != IntPtr.Zero; | ||
} | ||
|
||
public bool HasFile(string name) | ||
{ | ||
return fileList.Contains(name); | ||
} | ||
|
||
public bool SFileCloseArchive() | ||
{ | ||
fileList.Clear(); | ||
return Is64Bit | ||
? StormDllx64.SFileCloseArchive(handle) | ||
: StormDllx86.SFileCloseArchive(handle); | ||
} | ||
|
||
public MpqFileStream GetStream(string fileName) | ||
{ | ||
return !SFileOpenFileEx(handle, fileName, OpenFile.SFILE_OPEN_FROM_MPQ, out IntPtr fileHandle) | ||
? throw new IOException("SFileOpenFileEx failed") | ||
: new MpqFileStream(fileHandle); | ||
} | ||
|
||
public static bool SFileReadFile(IntPtr fileHandle, Span<byte> buffer, long toRead, out long read) | ||
{ | ||
return Is64Bit | ||
? StormDllx64.SFileReadFile(fileHandle, buffer, toRead, out read) | ||
: StormDllx86.SFileReadFile(fileHandle, buffer, toRead, out read); | ||
} | ||
|
||
public static bool SFileCloseFile(IntPtr fileHandle) | ||
{ | ||
return Is64Bit | ||
? StormDllx64.SFileCloseFile(fileHandle) | ||
: StormDllx86.SFileCloseFile(fileHandle); | ||
} | ||
|
||
public static long SFileGetFileSize(IntPtr fileHandle, out long fileSizeHigh) | ||
{ | ||
return Is64Bit | ||
? StormDllx64.SFileGetFileSize(fileHandle, out fileSizeHigh) | ||
: StormDllx86.SFileGetFileSize(fileHandle, out fileSizeHigh); | ||
} | ||
|
||
public static uint SFileSetFilePointer(IntPtr fileHandle, | ||
long filePos, | ||
ref uint plFilePosHigh, | ||
SeekOrigin origin) | ||
{ | ||
return Is64Bit | ||
? StormDllx64.SFileSetFilePointer(fileHandle, filePos, ref plFilePosHigh, origin) | ||
: StormDllx86.SFileSetFilePointer(fileHandle, filePos, ref plFilePosHigh, origin); | ||
} | ||
|
||
public static bool SFileOpenFileEx( | ||
IntPtr archiveHandle, | ||
string fileName, | ||
OpenFile searchScope, | ||
out IntPtr fileHandle) | ||
{ | ||
return Is64Bit | ||
? StormDllx64.SFileOpenFileEx(archiveHandle, fileName, searchScope, out fileHandle) | ||
: StormDllx86.SFileOpenFileEx(archiveHandle, fileName, searchScope, out fileHandle); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
using System.IO; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace StormDll; | ||
|
||
public sealed class ArchiveSet | ||
{ | ||
private readonly Archive[] archives; | ||
|
||
public ArchiveSet(ILogger logger, string[] files) | ||
{ | ||
archives = new Archive[files.Length]; | ||
|
||
for (int i = 0; i < files.Length; i++) | ||
{ | ||
Archive a = new(files[i], out bool open, 0, | ||
OpenArchive.MPQ_OPEN_NO_LISTFILE | | ||
OpenArchive.MPQ_OPEN_NO_ATTRIBUTES | | ||
OpenArchive.MPQ_OPEN_NO_HEADER_SEARCH | | ||
OpenArchive.MPQ_OPEN_READ_ONLY); | ||
|
||
if (open && a.IsOpen()) | ||
{ | ||
archives[i] = a; | ||
|
||
if (logger.IsEnabled(LogLevel.Trace)) | ||
logger.LogTrace($"Archive[{i}] open {files[i]}"); | ||
} | ||
else if (logger.IsEnabled(LogLevel.Trace)) | ||
logger.LogTrace($"Archive[{i}] openfail {files[i]}"); | ||
} | ||
} | ||
|
||
public MpqFileStream GetStream(string fileName) | ||
{ | ||
for (int i = 0; i < archives.Length; i++) | ||
{ | ||
Archive a = archives[i]; | ||
if (a.HasFile(fileName)) | ||
return a.GetStream(fileName); | ||
} | ||
|
||
throw new FileNotFoundException(nameof(fileName)); | ||
} | ||
|
||
public void Close() | ||
{ | ||
for (int i = 0; i < archives.Length; i++) | ||
archives[i].SFileCloseArchive(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
using System; | ||
using System.ComponentModel; | ||
using System.IO; | ||
using System.Runtime.InteropServices; | ||
|
||
namespace StormDll; | ||
|
||
public sealed class MpqFileStream : Stream | ||
{ | ||
private const int ERROR_HANDLE_EOF = 38; | ||
|
||
private readonly long length; | ||
|
||
private nint fileHandle; | ||
private long position; | ||
|
||
public MpqFileStream(nint fileHandle) | ||
{ | ||
this.fileHandle = fileHandle; | ||
|
||
long low = Archive.SFileGetFileSize(fileHandle, out long high); | ||
length = (high << 32) | low; | ||
} | ||
|
||
public sealed override bool CanRead => true; | ||
public sealed override bool CanSeek => true; | ||
public sealed override bool CanWrite => false; | ||
|
||
public sealed override long Length => length; | ||
|
||
public sealed override long Position | ||
{ | ||
get => position; | ||
set => Seek(value, SeekOrigin.Begin); | ||
} | ||
|
||
public sealed override void Flush() { } | ||
|
||
public sealed override int Read(byte[] buffer, int offset, int count) | ||
{ | ||
if (offset > buffer.Length || (offset + count) > buffer.Length) | ||
throw new ArgumentException(); | ||
|
||
if (count < 0) | ||
throw new ArgumentOutOfRangeException(nameof(count)); | ||
|
||
Span<byte> bufferSpan = buffer.AsSpan(offset); | ||
bool success = Archive.SFileReadFile(fileHandle, bufferSpan, count, out long bytesRead); | ||
position += bytesRead; | ||
|
||
if (!success) | ||
{ | ||
int lastError = Marshal.GetLastWin32Error(); | ||
if (lastError != ERROR_HANDLE_EOF) | ||
throw new Win32Exception(lastError); | ||
} | ||
|
||
return unchecked((int)bytesRead); | ||
} | ||
|
||
public sealed override long Seek(long offset, SeekOrigin origin) | ||
{ | ||
if (origin == SeekOrigin.Current && offset < 0) | ||
{ | ||
offset = Position + offset; | ||
origin = SeekOrigin.Begin; | ||
} | ||
|
||
uint low, high; | ||
low = unchecked((uint)(offset & 0xffffffffu)); | ||
high = unchecked((uint)(offset >> 32)); | ||
|
||
uint result = Archive.SFileSetFilePointer(fileHandle, low, ref high, origin); | ||
if (result == Archive.SFILE_INVALID_SIZE) | ||
throw new IOException("SFileSetFilePointer failed"); | ||
|
||
return position = result; | ||
} | ||
|
||
public override void SetLength(long value) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
public override void Write(byte[] buffer, int offset, int count) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
public sealed override void Close() | ||
{ | ||
base.Close(); | ||
|
||
if (fileHandle == nint.Zero) | ||
return; | ||
|
||
Archive.SFileCloseFile(fileHandle); | ||
fileHandle = nint.Zero; | ||
} | ||
|
||
public byte[] ReadAllBytes() | ||
{ | ||
byte[] bytes = new byte[Length]; | ||
Read(bytes, 0, (int)Length); | ||
|
||
return bytes; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
using System; | ||
|
||
namespace StormDll; | ||
|
||
[Flags] | ||
internal enum OpenArchive : uint | ||
{ | ||
BASE_PROVIDER_FILE = 0x00000000, // Base data source is a file | ||
BASE_PROVIDER_MAP = 0x00000001, // Base data source is memory-mapped file | ||
BASE_PROVIDER_HTTP = 0x00000002, // Base data source is a file on web server | ||
BASE_PROVIDER_MASK = 0x0000000F, // Mask for base provider value | ||
STREAM_PROVIDER_FLAT = 0x00000000, // Stream is linear with no offset mapping | ||
STREAM_PROVIDER_PARTIAL = 0x00000010, // Stream is partial file (.part) | ||
STREAM_PROVIDER_MPQE = 0x00000020, // Stream is an encrypted MPQ | ||
STREAM_PROVIDER_BLOCK4 = 0x00000030, // = 0x4000 per block, text MD5 after each block, max = 0x2000 blocks per file | ||
STREAM_PROVIDER_MASK = 0x000000F0, // Mask for stream provider value | ||
STREAM_FLAG_READ_ONLY = 0x00000100, // Stream is read only | ||
STREAM_FLAG_WRITE_SHARE = 0x00000200, // Allow write sharing when open for write | ||
STREAM_FLAG_USE_BITMAP = 0x00000400, // If the file has a file bitmap, load it and use it | ||
STREAM_OPTIONS_MASK = 0x0000FF00, // Mask for stream options | ||
STREAM_PROVIDERS_MASK = 0x000000FF, // Mask to get stream providers | ||
STREAM_FLAGS_MASK = 0x0000FFFF, // Mask for all stream flags (providers+options) | ||
MPQ_OPEN_NO_LISTFILE = 0x00010000, // Don't load the internal listfile | ||
MPQ_OPEN_NO_ATTRIBUTES = 0x00020000, // Don't open the attributes | ||
MPQ_OPEN_NO_HEADER_SEARCH = 0x00040000, // Don't search for the MPQ header past the begin of the file | ||
MPQ_OPEN_FORCE_MPQ_V1 = 0x00080000, // Always open the archive as MPQ v 1.00, ignore the "wFormatVersion" variable in the header | ||
MPQ_OPEN_CHECK_SECTOR_CRC = 0x00100000, // On files with MPQ_FILE_SECTOR_CRC, the CRC will be checked when reading file | ||
MPQ_OPEN_FORCE_LISTFILE = 0x00400000, // Force add listfile even if there is none at the moment of opening | ||
MPQ_OPEN_READ_ONLY = STREAM_FLAG_READ_ONLY | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace StormDll; | ||
|
||
internal enum OpenFile : uint | ||
{ | ||
SFILE_OPEN_FROM_MPQ = 0x00000000, // Open the file from the MPQ archive | ||
SFILE_OPEN_CHECK_EXISTS = 0xFFFFFFFC, // Only check whether the file exists | ||
SFILE_OPEN_LOCAL_FILE = 0xFFFFFFFF // Open a local file | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
using System; | ||
using System.IO; | ||
using System.Runtime.InteropServices; | ||
|
||
namespace StormDll; | ||
|
||
internal sealed partial class StormDllx64 | ||
{ | ||
[LibraryImport("MPQ\\StormLib_x64.dll")] | ||
[return: MarshalAs(UnmanagedType.Bool)] | ||
public static partial bool SFileOpenArchive( | ||
[MarshalAs(UnmanagedType.LPWStr)] string szMpqName, | ||
uint dwPriority, | ||
OpenArchive dwFlags, | ||
out nint phMpq); | ||
|
||
[LibraryImport("MPQ\\StormLib_x64.dll")] | ||
[return: MarshalAs(UnmanagedType.Bool)] | ||
public static partial bool SFileCloseArchive(nint hMpq); | ||
|
||
[LibraryImport("MPQ\\StormLib_x64.dll", SetLastError = true)] | ||
[return: MarshalAs(UnmanagedType.Bool)] | ||
public static partial bool SFileReadFile( | ||
nint fileHandle, | ||
Span<byte> buffer, | ||
[MarshalAs(UnmanagedType.I8)] long toRead, | ||
out long read); | ||
|
||
[LibraryImport("MPQ\\StormLib_x64.dll")] | ||
[return: MarshalAs(UnmanagedType.Bool)] | ||
public static partial bool SFileCloseFile( | ||
nint fileHandle); | ||
|
||
[LibraryImport("MPQ\\StormLib_x64.dll")] | ||
public static partial uint SFileGetFileSize( | ||
nint fileHandle, | ||
out long fileSizeHigh); | ||
|
||
[LibraryImport("MPQ\\StormLib_x64.dll")] | ||
public static partial uint SFileSetFilePointer( | ||
nint fileHandle, | ||
long filePos, | ||
ref uint plFilePosHigh, | ||
SeekOrigin origin); | ||
|
||
[LibraryImport("MPQ\\StormLib_x64.dll")] | ||
[return: MarshalAs(UnmanagedType.Bool)] | ||
public static partial bool SFileOpenFileEx( | ||
nint archiveHandle, | ||
[MarshalAs(UnmanagedType.LPStr)] string fileName, | ||
OpenFile searchScope, | ||
out nint fileHandle); | ||
} |
Oops, something went wrong.