Files
SabreTools.IO/SabreTools.IO.Extensions/StreamExtensions.cs
Matt Nadareski 6ba25e0fbf Fix build
2026-03-21 22:00:37 -04:00

157 lines
6.0 KiB
C#

using System.Collections.Generic;
using System.IO;
using SabreTools.Numerics.Extensions;
using SabreTools.Text.Extensions;
namespace SabreTools.IO.Extensions
{
public static class StreamExtensions
{
/// <summary>
/// Align the stream position to a byte-size boundary
/// </summary>
/// <param name="input">Input stream to try aligning</param>
/// <param name="alignment">Number of bytes to align on</param>
/// <returns>True if the stream could be aligned, false otherwise</returns>
public static bool AlignToBoundary(this Stream? input, int alignment)
{
// If the stream is invalid
if (input is null || input.Length == 0 || !input.CanRead)
return false;
// If already at the end of the stream
if (input.Position >= input.Length)
return false;
// Align the stream position
while (input.Position % alignment != 0 && input.Position < input.Length)
{
_ = input.ReadByteValue();
}
// Return if the alignment completed
return input.Position % alignment == 0;
}
/// <summary>
/// Read a number of bytes from an offset in a stream, if possible
/// </summary>
/// <param name="input">Input stream to read from</param>
/// <param name="offset">Offset within the stream to start reading</param>
/// <param name="length">Number of bytes to read from the offset</param>
/// <param name="retainPosition">Indicates if the original position of the stream should be retained after reading</param>
/// <returns>Filled byte array on success, null on error</returns>
/// <remarks>
/// This method will return a null array if the length is greater than what is left
/// in the stream. This is different behavior than a normal stream read that would
/// attempt to read as much as possible, returning the amount of bytes read.
/// </remarks>
public static byte[]? ReadFrom(this Stream? input, long offset, int length, bool retainPosition)
{
if (input is null || !input.CanRead || !input.CanSeek)
return null;
if (offset < 0 || offset >= input.Length)
return null;
if (length < 0 || offset + length > input.Length)
return null;
// Cache the current location
long currentLocation = input.Position;
// Seek to the requested offset
long newPosition = input.SeekIfPossible(offset);
if (newPosition != offset)
return null;
// Read from the position
byte[] data = input.ReadBytes(length);
// Seek back if requested
if (retainPosition)
_ = input.SeekIfPossible(currentLocation);
// Return the read data
return data;
}
/// <summary>
/// Read string data from a Stream
/// </summary>
/// <param name="charLimit">Number of characters needed to be a valid string, default 5</param>
/// <param name="position">Position in the source to read from</param>
/// <param name="length">Length of the requested data</param>
/// <returns>String list containing the requested data, null on error</returns>
#if NET5_0_OR_GREATER
/// <remarks>This reads both Latin1 and UTF-16 strings from the input data</remarks>
#else
/// <remarks>This reads both ASCII and UTF-16 strings from the input data</remarks>
#endif
public static List<string>? ReadStringsFrom(this Stream? input, int position, int length, int charLimit = 5)
{
// Read the data as a byte array first
byte[]? data = input.ReadFrom(position, length, retainPosition: true);
if (data is null)
return null;
return data.ReadStringsFrom(charLimit);
}
/// <summary>
/// Seek to a specific point in the stream, if possible
/// </summary>
/// <param name="input">Input stream to try seeking on</param>
/// <param name="offset">Optional offset to seek to</param>
public static long SeekIfPossible(this Stream input, long offset = 0)
=> input.SeekIfPossible(offset, offset < 0 ? SeekOrigin.End : SeekOrigin.Begin);
/// <summary>
/// Seek to a specific point in the stream, if possible
/// </summary>
/// <param name="input">Input stream to try seeking on</param>
/// <param name="offset">Optional offset to seek to</param>
public static long SeekIfPossible(this Stream input, long offset, SeekOrigin origin)
{
// If the input is not seekable, just return the current position
if (!input.CanSeek)
{
try
{
return input.Position;
}
catch
{
return -1;
}
}
// Attempt to seek to the offset
try
{
return input.Seek(offset, origin);
}
catch
{
return -1;
}
}
/// <summary>
/// Check if a segment is valid in the stream
/// </summary>
/// <param name="input">Input stream to validate</param>
/// <param name="offset">Position in the source</param>
/// <param name="count">Length of the data to check</param>
/// <returns>True if segment could be read fully, false otherwise</returns>
public static bool SegmentValid(this Stream? input, long offset, long count)
{
if (input is null)
return false;
if (offset < 0 || offset > input.Length)
return false;
if (count < 0 || offset + count > input.Length)
return false;
return true;
}
}
}