Skip to content

Commit

Permalink
Throw instead of returning null for most MPQ operations.
Browse files Browse the repository at this point in the history
  • Loading branch information
Nihlus committed Oct 16, 2017
1 parent 7d9c91f commit 33a2ea0
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 102 deletions.
3 changes: 2 additions & 1 deletion libwarcraft/Containers/Terrain/GameWorld.cs
Expand Up @@ -20,6 +20,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using Warcraft.WDT;
using Warcraft.ADT;
Expand Down Expand Up @@ -73,7 +74,7 @@ public TerrainTile LoadTile(uint tileXPosition, uint tileYPosition)

if (!this.Package.ContainsFile(tilePath))
{
return null;
throw new ArgumentException("No tile found for the given coordinates.");
}

byte[] terrainTileData = this.Package.ExtractFile(tilePath);
Expand Down
51 changes: 51 additions & 0 deletions libwarcraft/Core/Exceptions.cs
Expand Up @@ -24,6 +24,57 @@

namespace Warcraft.Core
{
public class FileDeletedException : Exception
{
public string FilePath { get; }

/// <summary>
/// Creates a new instance of the <see cref="FileDeletedException"/> class, along with a specified
/// message.
/// </summary>
/// <param name="message">The message included in the exception.</param>
public FileDeletedException(string message)
: base(message)
{
}

/// <summary>
/// Creates a new instance of the <see cref="FileDeletedException"/> class, along with a specified
/// message and file path.
/// </summary>
/// <param name="message"></param>
/// <param name="filePath"></param>
public FileDeletedException(string message, string filePath)
: base(message)
{
this.FilePath = filePath;
}

/// <summary>
/// Creates a new instance of the <see cref="FileDeletedException"/> class, along with a specified
/// message and inner exception which caused this exception.
/// </summary>
/// <param name="message">The message included in the exception.</param>
/// <param name="innerException">The exception which caused this exception.</param>
public FileDeletedException(string message, Exception innerException)
: base(message, innerException)
{
}

/// <summary>
/// Creates a new instance of the <see cref="FileDeletedException"/> class, along with a specified
/// message, file path, and inner exception which caused this exception.
/// </summary>
/// <param name="message"></param>
/// <param name="filePath"></param>
/// <param name="innerException"></param>
public FileDeletedException(string message, string filePath, Exception innerException)
: base(message, innerException)
{
this.FilePath = filePath;
}
}

/// <summary>
/// This exception thrown when an invalid or unknown chunk signature is found during parsing of binary data which
/// is expected to be in valid RIFF format.
Expand Down
158 changes: 60 additions & 98 deletions libwarcraft/MPQ/MPQ.cs
Expand Up @@ -211,10 +211,7 @@ private uint PeekHeaderSize()
/// <returns><c>true</c> if this archive has file attributes; otherwise, <c>false</c>.</returns>
public bool HasFileAttributes()
{
if (this.IsDisposed)
{
throw new ObjectDisposedException(ToString(), "Cannot use a disposed archive.");
}
ThrowIfDisposed();

return ContainsFile(ExtendedAttributes.InternalFileName);
}
Expand All @@ -224,6 +221,8 @@ public bool HasFileAttributes()
/// </summary>
public ExtendedAttributes GetFileAttributes()
{
ThrowIfDisposed();

if (this.FileAttributes != null)
{
return this.FileAttributes;
Expand All @@ -244,14 +243,9 @@ public ExtendedAttributes GetFileAttributes()
/// <returns>The weak signature.</returns>
public WeakPackageSignature GetWeakSignature()
{
if (ContainsFile(WeakPackageSignature.InternalFilename))
{
return new WeakPackageSignature(ExtractFile(WeakPackageSignature.InternalFilename));
}
else
{
return null;
}
ThrowIfDisposed();

return new WeakPackageSignature(ExtractFile(WeakPackageSignature.InternalFilename));
}

/// <summary>
Expand All @@ -260,10 +254,7 @@ public WeakPackageSignature GetWeakSignature()
/// <returns><c>true</c> if this archive has a listfile; otherwise, <c>false</c>.</returns>
public bool HasFileList()
{
if (this.IsDisposed)
{
throw new ObjectDisposedException(ToString(), "Cannot use a disposed archive.");
}
ThrowIfDisposed();

return ContainsFile("(listfile)") || this.ExternalListfile.Count > 0;
}
Expand All @@ -275,39 +266,31 @@ public bool HasFileList()
/// <returns>The listfile.</returns>
public IEnumerable<string> GetFileList()
{
if (this.IsDisposed)
{
throw new ObjectDisposedException(ToString(), "Cannot use a disposed archive.");
}
ThrowIfDisposed();

if (this.ExternalListfile.Count > 0)
{
return GetExternalFileList();
}
else
{
return GetInternalFileList();
}

return GetInternalFileList();
}

/// <summary>
/// Gets the internal file list. If no listfile is stored in the archive, this may
/// return null.
/// Gets the internal file list. If no listfile is stored in the archive, this may not return anything.
/// </summary>
/// <returns>The internal file list.</returns>
public IEnumerable<string> GetInternalFileList()
{
if (this.IsDisposed)
{
throw new ObjectDisposedException(ToString(), "Cannot use a disposed archive.");
}
ThrowIfDisposed();

byte[] listfileBytes = ExtractFile("(listfile)");
if (listfileBytes == null)
if (!ContainsFile("(listfile)"))
{
yield break;
}

byte[] listfileBytes = ExtractFile("(listfile)");

using (MemoryStream listfileStream = new MemoryStream(listfileBytes))
{
using (TextReader tr = new StreamReader(listfileStream))
Expand All @@ -328,10 +311,7 @@ public IEnumerable<string> GetInternalFileList()
/// <returns>The external file list.</returns>
public IEnumerable<string> GetExternalFileList()
{
if (this.IsDisposed)
{
throw new ObjectDisposedException(ToString(), "Cannot use a disposed archive.");
}
ThrowIfDisposed();

return this.ExternalListfile;
}
Expand All @@ -342,10 +322,7 @@ public IEnumerable<string> GetExternalFileList()
/// <param name="inExternalListfile">In external listfile.</param>
public void SetFileList(List<string> inExternalListfile)
{
if (this.IsDisposed)
{
throw new ObjectDisposedException(ToString(), "Cannot use a disposed archive.");
}
ThrowIfDisposed();

this.ExternalListfile = inExternalListfile;
}
Expand All @@ -356,90 +333,73 @@ public void SetFileList(List<string> inExternalListfile)
/// </summary>
public void ResetExternalFileList()
{
if (this.IsDisposed)
{
throw new ObjectDisposedException(ToString(), "Cannot use a disposed archive.");
}
ThrowIfDisposed();

this.ExternalListfile.Clear();
}

/// <summary>
/// Checks if the specified file path exists in the archive.
/// </summary>
/// <returns><c>true</c>, if the file exists, <c>false</c> otherwise.</returns>
/// <param name="filePath">File path.</param>
/// <inheritdoc />
public bool ContainsFile(string filePath)
{
if (this.IsDisposed)
ThrowIfDisposed();

try
{
throw new ObjectDisposedException(ToString(), "Cannot use a disposed archive.");
this.ArchiveHashTable.FindEntry(filePath.ToUpperInvariant());
}
catch (FileNotFoundException)
{
return false;
}

HashTableEntry fileHashEntry = this.ArchiveHashTable.FindEntry(filePath.ToUpperInvariant());
return fileHashEntry != null;
return true;
}

/// <summary>
/// Gets the file info of the provided path.
/// </summary>
/// <returns>The file info, or null if the file doesn't exist in the archive.</returns>
/// <param name="filePath">File path.</param>
/// <inheritdoc />
public MPQFileInfo GetFileInfo(string filePath)
{
if (this.IsDisposed)
ThrowIfDisposed();

if (!ContainsFile(filePath))
{
throw new ObjectDisposedException(ToString(), "Cannot use a disposed archive.");
throw new FileNotFoundException("The given file was not present in the archive.", filePath);
}

if (ContainsFile(filePath))
{
HashTableEntry hashEntry = this.ArchiveHashTable.FindEntry(filePath);
BlockTableEntry blockEntry = this.ArchiveBlockTable.GetEntry((int)hashEntry.GetBlockEntryIndex());
HashTableEntry hashEntry = this.ArchiveHashTable.FindEntry(filePath);
BlockTableEntry blockEntry = this.ArchiveBlockTable.GetEntry((int)hashEntry.GetBlockEntryIndex());

if (HasFileAttributes())
{
return new MPQFileInfo(filePath, hashEntry, blockEntry);
}
else
{
return new MPQFileInfo(filePath, hashEntry, blockEntry, this.FileAttributes.FileAttributes[(int)hashEntry.GetBlockEntryIndex()]);
}
}
else
if (HasFileAttributes())
{
return null;
return new MPQFileInfo(filePath, hashEntry, blockEntry);
}

return new MPQFileInfo(filePath, hashEntry, blockEntry, this.FileAttributes.FileAttributes[(int)hashEntry.GetBlockEntryIndex()]);
}

// TODO: Filter files based on language and platform
/// <summary>
/// Extract the file at <paramref name="filePath"/> from the archive.
/// </summary>
/// <returns>The file as a byte array, or null if the file could not be found.</returns>
/// <param name="filePath">Path to the file in the archive.</param>
/// <inheritdoc />
public byte[] ExtractFile(string filePath)
{
if (this.IsDisposed)
{
throw new ObjectDisposedException(ToString(), "Cannot use a disposed archive.");
}
ThrowIfDisposed();

// Reset all positions to be safe
this.ArchiveReader.BaseStream.Position = 0;

HashTableEntry fileHashEntry = this.ArchiveHashTable.FindEntry(filePath);
if (fileHashEntry == null)
HashTableEntry fileHashEntry;
try
{
fileHashEntry = this.ArchiveHashTable.FindEntry(filePath);
}
catch (FileNotFoundException fex)
{
return null;
throw new FileNotFoundException("No file found at the given path.", filePath, fex);
}

BlockTableEntry fileBlockEntry = this.ArchiveBlockTable.GetEntry((int)fileHashEntry.GetBlockEntryIndex());

// Drop out if the file is not actually a file
if (!fileBlockEntry.HasData())
// Drop out if the file has been deleted
if (fileBlockEntry.IsDeleted())
{
return null;
throw new FileDeletedException("The given file is deleted.", filePath);
}

// Seek to the beginning of the file's sectors
Expand Down Expand Up @@ -766,13 +726,15 @@ private uint GetMaxSectorSize()
return (uint)(512 * Math.Pow(2, this.Header.GetSectorSizeExponent()));
}

/// <summary>
/// Releases all resource used by the <see cref="Warcraft.MPQ.MPQ"/> object.
/// </summary>
/// <remarks>Call <see cref="Dispose"/> when you are finished using the <see cref="Warcraft.MPQ.MPQ"/>. The <see cref="Dispose"/>
/// method leaves the <see cref="Warcraft.MPQ.MPQ"/> in an unusable state. After calling <see cref="Dispose"/>, you must
/// release all references to the <see cref="Warcraft.MPQ.MPQ"/> so the garbage collector can reclaim the memory that
/// the <see cref="Warcraft.MPQ.MPQ"/> was occupying.</remarks>
private void ThrowIfDisposed()
{
if (this.IsDisposed)
{
throw new ObjectDisposedException(ToString(), "Cannot use a disposed archive.");
}
}

/// <inheritdoc />
public void Dispose()
{
this.Header = null;
Expand Down
9 changes: 9 additions & 0 deletions libwarcraft/MPQ/Tables/Block/BlockTableEntry.cs
Expand Up @@ -179,6 +179,15 @@ public bool HasData()
return this.Flags.HasFlag(BlockFlags.Exists) && !this.Flags.HasFlag(BlockFlags.IsDeletionMarker);
}

/// <summary>
/// Determines whether or not this block entry is pointing to a deleted file.
/// </summary>
/// <returns></returns>
public bool IsDeleted()
{
return this.Flags.HasFlag(BlockFlags.IsDeletionMarker);
}

/// <summary>
/// Determines whether or not the file data is stored as a single unit. If not, it's in sectors.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion libwarcraft/MPQ/Tables/Hash/HashTable.cs
Expand Up @@ -104,7 +104,7 @@ public HashTableEntry FindEntry(uint hashA, uint hashB, uint entryHomeIndex)
}
else
{
return null;
throw new FileNotFoundException($"No file has ever existed at the home index {entryHomeIndex}.");
}

// If that file doesn't match (but has existed, or is occupied, let's keep looking down the table.
Expand Down
5 changes: 3 additions & 2 deletions libwarcraft/WMO/RootFile/Chunks/ModelGroupNames.cs
Expand Up @@ -20,6 +20,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand Down Expand Up @@ -76,7 +77,7 @@ public string GetInternalGroupName(ModelGroup modelGroup)
return this.GroupNames[internalNameOffset];
}

return null;
throw new ArgumentException("Group name not found.", nameof(modelGroup));
}

public string GetInternalDescriptiveGroupName(ModelGroup modelGroup)
Expand All @@ -87,7 +88,7 @@ public string GetInternalDescriptiveGroupName(ModelGroup modelGroup)
return this.GroupNames[internalDescriptiveNameOffset];
}

return null;
throw new ArgumentException("Descriptive group name not found.", nameof(modelGroup));
}

public byte[] Serialize()
Expand Down

0 comments on commit 33a2ea0

Please sign in to comment.