Create a new DataSource helper class

This commit is contained in:
Matt Nadareski
2025-08-19 20:49:47 -04:00
parent b70c510734
commit d26ad35573
9 changed files with 228 additions and 163 deletions

View File

@@ -132,7 +132,7 @@ namespace SabreTools.Serialization.Wrappers
int compressedSize = file.CompressedSize;
// Some files can lack the length prefix
if (compressedSize > GetEndOfFile())
if (compressedSize > GetEndOffset())
{
offset -= 4;
compressedSize = file.UncompressedSize;

View File

@@ -314,7 +314,7 @@ namespace SabreTools.Serialization.Wrappers
{
// Try to get the sector data offset
int sectorDataOffset = (int)FATSectorToFileOffset(sectorChain[i]);
if (sectorDataOffset < 0 || sectorDataOffset >= GetEndOfFile())
if (sectorDataOffset < 0 || sectorDataOffset >= GetEndOffset())
return null;
// Try to read the sector data

View File

@@ -0,0 +1,203 @@
using System;
using System.IO;
using SabreTools.IO.Extensions;
namespace SabreTools.Serialization.Wrappers
{
/// <summary>
/// Represents the data source backing the wrapper
/// </summary>
public class DataSource
{
#region Instance Variables
/// <summary>
/// Source of the original data
/// </summary>
private readonly DataSourceType _dataSourceType = DataSourceType.UNKNOWN;
/// <summary>
/// Lock object for reading from the source
/// </summary>
private readonly object _streamDataLock = new();
/// <summary>
/// Initial position of the data source
/// </summary>
/// <remarks>Populated for both <see cref="DataSourceType.ByteArray"/> and <see cref="DataSourceType.Stream"/></remarks>
protected long _initialPosition = 0;
/// <summary>
/// Source byte array data
/// </summary>
/// <remarks>This is only populated if <see cref="_dataSourceType"/> is <see cref="DataSourceType.ByteArray"/></remarks>
protected byte[]? _byteArrayData = null;
/// <summary>
/// Source Stream data
/// </summary>
/// <remarks>This is only populated if <see cref="_dataSourceType"/> is <see cref="DataSourceType.Stream"/></remarks>
protected Stream? _streamData = null;
#endregion
#region Constructors
/// <summary>
/// Construct a new DataSource from a Stream
/// </summary>
/// <param name="data"></param>
public DataSource(Stream data)
{
_dataSourceType = DataSourceType.Stream;
_initialPosition = data.Position;
_streamData = data;
}
/// <summary>
/// Construct a new DataSource from a byte array
/// </summary>
/// <param name="data"></param>
/// <param name="offset"></param>
public DataSource(byte[] data, int offset)
{
_dataSourceType = DataSourceType.ByteArray;
_initialPosition = offset;
_byteArrayData = data;
}
#endregion
#region Data
/// <summary>
/// Get the ending offset of the source
/// </summary>
/// <returns>Value greater than 0 for a valid end of file, -1 on error</returns>
public long GetEndOffset()
{
// Validate the data souece
if (!IsValid())
return -1;
// Return the effective endpoint
return _dataSourceType switch
{
DataSourceType.ByteArray => _byteArrayData!.Length - _initialPosition,
DataSourceType.Stream => _streamData!.Length - _initialPosition,
_ => -1,
};
}
/// <summary>
/// Get the usable length of the underlying data
/// </summary>
/// <returns>The usable length on success, -1 on error</returns>
public long GetLength()
{
return _dataSourceType switch
{
DataSourceType.ByteArray => _byteArrayData!.Length - _initialPosition,
DataSourceType.Stream => _streamData!.Length - _initialPosition,
// Everything else is invalid
_ => -1,
};
}
/// <summary>
/// Validate the backing data source
/// </summary>
/// <returns>True if the data source is valid, false otherwise</returns>
public bool IsValid()
{
return _dataSourceType switch
{
// Byte array data requires both a valid array and offset
DataSourceType.ByteArray => _byteArrayData != null && _initialPosition >= 0,
// Stream data requires both a valid stream
DataSourceType.Stream => _streamData != null && _initialPosition >= 0 && _streamData.CanRead && _streamData.CanSeek,
// Everything else is invalid
_ => false,
};
}
/// <summary>
/// Check if a data segment is valid in the data source
/// </summary>
/// <param name="position">Position in the source</param>
/// <param name="length">Length of the data to check</param>
/// <returns>True if the positional data is valid, false otherwise</returns>
public bool SegmentValid(int position, int length)
{
// Validate the data souece
if (!IsValid())
return false;
// If we have an invalid position
if (position < 0 || position >= GetEndOffset())
return false;
return _dataSourceType switch
{
DataSourceType.ByteArray => _initialPosition + position + length <= _byteArrayData!.Length,
DataSourceType.Stream => _initialPosition + position + length <= _streamData!.Length,
// Everything else is invalid
_ => false,
};
}
/// <summary>
/// Read data from the source
/// </summary>
/// <param name="position">Position in the source to read from</param>
/// <param name="length">Length of the requested data</param>
/// <returns>Byte array containing the requested data, null on error</returns>
public byte[]? Read(int position, int length)
{
// Validate the data source
if (!IsValid())
return null;
// Validate the requested segment
if (!SegmentValid(position, length))
return null;
try
{
// Read and return the data
byte[]? sectionData = null;
switch (_dataSourceType)
{
case DataSourceType.ByteArray:
sectionData = new byte[length];
Array.Copy(_byteArrayData!, _initialPosition + position, sectionData, 0, length);
break;
case DataSourceType.Stream:
lock (_streamDataLock)
{
long currentLocation = _streamData!.Position;
_streamData.Seek(_initialPosition + position, SeekOrigin.Begin);
sectionData = _streamData.ReadBytes(length);
_streamData.Seek(currentLocation, SeekOrigin.Begin);
break;
}
}
return sectionData;
}
catch
{
// Absorb the error
return null;
}
}
#endregion
}
}

View File

@@ -3,7 +3,7 @@ namespace SabreTools.Serialization.Wrappers
/// <summary>
/// Location that the data originated from
/// </summary>
public enum DataSource
public enum DataSourceType
{
/// <summary>
/// Unknown origin / testing

View File

@@ -166,7 +166,7 @@ namespace SabreTools.Serialization.Wrappers
return -1;
// Get the end of the file, if possible
long endOfFile = GetEndOfFile();
long endOfFile = GetEndOffset();
if (endOfFile == -1)
return -1;
@@ -268,7 +268,7 @@ namespace SabreTools.Serialization.Wrappers
return -1;
// Get the end of the file, if possible
long endOfFile = GetEndOfFile();
long endOfFile = GetEndOffset();
if (endOfFile == -1)
return -1;
@@ -309,7 +309,7 @@ namespace SabreTools.Serialization.Wrappers
// If we have an unset length, read the whole source
if (length == -1)
length = GetEndOfFile();
length = GetEndOffset();
return ReadFromDataSource(rangeStart, (int)length);
}

View File

@@ -40,7 +40,7 @@ namespace SabreTools.Serialization.Wrappers
return _overlayAddress.Value;
// Get the end of the file, if possible
long endOfFile = GetEndOfFile();
long endOfFile = GetEndOffset();
if (endOfFile == -1)
return -1;
@@ -121,7 +121,7 @@ namespace SabreTools.Serialization.Wrappers
return _overlayData;
// Get the end of the file, if possible
long endOfFile = GetEndOfFile();
long endOfFile = GetEndOffset();
if (endOfFile == -1)
return null;
@@ -190,7 +190,7 @@ namespace SabreTools.Serialization.Wrappers
return _overlayStrings;
// Get the end of the file, if possible
long endOfFile = GetEndOfFile();
long endOfFile = GetEndOffset();
if (endOfFile == -1)
return null;
@@ -396,7 +396,7 @@ namespace SabreTools.Serialization.Wrappers
return null;
// Get the end of the file, if possible
long endOfFile = GetEndOfFile();
long endOfFile = GetEndOffset();
if (endOfFile == -1)
return null;
@@ -474,7 +474,7 @@ namespace SabreTools.Serialization.Wrappers
public int GetResourceOffset(int id)
{
// Get the end of the file, if possible
long endOfFile = GetEndOfFile();
long endOfFile = GetEndOffset();
if (endOfFile == -1)
return -1;
@@ -570,7 +570,7 @@ namespace SabreTools.Serialization.Wrappers
return -1;
// Get the end of the file, if possible
long endOfFile = GetEndOfFile();
long endOfFile = GetEndOffset();
if (endOfFile == -1)
return -1;
@@ -607,7 +607,7 @@ namespace SabreTools.Serialization.Wrappers
// If we have an unset length, read the whole source
if (length == -1)
length = GetEndOfFile();
length = GetEndOffset();
return ReadFromDataSource(rangeStart, (int)length);
}

View File

@@ -208,7 +208,7 @@ namespace SabreTools.Serialization.Wrappers
return _overlayAddress.Value;
// Get the end of the file, if possible
long endOfFile = GetEndOfFile();
long endOfFile = GetEndOffset();
if (endOfFile == -1)
return -1;
@@ -279,7 +279,7 @@ namespace SabreTools.Serialization.Wrappers
return _overlayData;
// Get the end of the file, if possible
long endOfFile = GetEndOfFile();
long endOfFile = GetEndOffset();
if (endOfFile == -1)
return null;
@@ -357,7 +357,7 @@ namespace SabreTools.Serialization.Wrappers
return _overlayStrings;
// Get the end of the file, if possible
long endOfFile = GetEndOfFile();
long endOfFile = GetEndOffset();
if (endOfFile == -1)
return null;

View File

@@ -135,7 +135,7 @@ namespace SabreTools.Serialization.Wrappers
// Read the entire compressed data
int compressedDataOffset = (int)CompressedDataOffset;
long compressedDataLength = GetEndOfFile() - compressedDataOffset;
long compressedDataLength = GetEndOffset() - compressedDataOffset;
var compressedData = ReadFromDataSource(compressedDataOffset, (int)compressedDataLength);
// Print a debug reminder

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using SabreTools.IO.Extensions;
using SabreTools.Serialization.Interfaces;
namespace SabreTools.Serialization.Wrappers
@@ -22,20 +21,7 @@ namespace SabreTools.Serialization.Wrappers
/// <summary>
/// Length of the underlying data
/// </summary>
public long Length
{
get
{
return _dataSource switch
{
DataSource.ByteArray => _byteArrayData!.Length - _initialPosition,
DataSource.Stream => _streamData!.Length - _initialPosition,
// Everything else is invalid
_ => -1,
};
}
}
public long Length => _dataSource.GetLength();
#endregion
@@ -44,30 +30,7 @@ namespace SabreTools.Serialization.Wrappers
/// <summary>
/// Source of the original data
/// </summary>
protected DataSource _dataSource = DataSource.UNKNOWN;
/// <summary>
/// Lock object for reading from the source
/// </summary>
private readonly object _streamDataLock = new();
/// <summary>
/// Initial position of the data source
/// </summary>
/// <remarks>Populated for both <see cref="DataSource.ByteArray"/> and <see cref="DataSource.Stream"/></remarks>
protected long _initialPosition = 0;
/// <summary>
/// Source byte array data
/// </summary>
/// <remarks>This is only populated if <see cref="_dataSource"/> is <see cref="DataSource.ByteArray"/></remarks>
protected byte[]? _byteArrayData = null;
/// <summary>
/// Source Stream data
/// </summary>
/// <remarks>This is only populated if <see cref="_dataSource"/> is <see cref="DataSource.Stream"/></remarks>
protected Stream? _streamData = null;
private readonly DataSource _dataSource;
#if NETCOREAPP
/// <summary>
@@ -107,9 +70,7 @@ namespace SabreTools.Serialization.Wrappers
throw new ArgumentOutOfRangeException(nameof(offset));
Model = model;
_dataSource = DataSource.ByteArray;
_initialPosition = offset;
_byteArrayData = data;
_dataSource = new DataSource(data, offset);
}
/// <summary>
@@ -125,9 +86,7 @@ namespace SabreTools.Serialization.Wrappers
throw new ArgumentOutOfRangeException(nameof(data));
Model = model;
_dataSource = DataSource.Stream;
_initialPosition = data.Position;
_streamData = data;
_dataSource = new DataSource(data);
}
#endregion
@@ -135,49 +94,10 @@ namespace SabreTools.Serialization.Wrappers
#region Data
/// <summary>
/// Validate the backing data source
/// Get the ending offset of the source
/// </summary>
/// <returns>True if the data source is valid, false otherwise</returns>
public bool DataSourceIsValid()
{
return _dataSource switch
{
// Byte array data requires both a valid array and offset
DataSource.ByteArray => _byteArrayData != null && _initialPosition >= 0,
// Stream data requires both a valid stream
DataSource.Stream => _streamData != null && _initialPosition >= 0 && _streamData.CanRead && _streamData.CanSeek,
// Everything else is invalid
_ => false,
};
}
/// <summary>
/// Check if a data segment is valid in the data source
/// </summary>
/// <param name="position">Position in the source</param>
/// <param name="length">Length of the data to check</param>
/// <returns>True if the positional data is valid, false otherwise</returns>
public bool SegmentValid(int position, int length)
{
// Validate the data souece
if (!DataSourceIsValid())
return false;
// If we have an invalid position
if (position < 0 || position >= GetEndOfFile())
return false;
return _dataSource switch
{
DataSource.ByteArray => _initialPosition + position + length <= _byteArrayData!.Length,
DataSource.Stream => _initialPosition + position + length <= _streamData!.Length,
// Everything else is invalid
_ => false,
};
}
/// <returns>Value greater than 0 for a valid end of file, -1 on error</returns>
public long GetEndOffset() => _dataSource.GetEndOffset();
/// <summary>
/// Read data from the source
@@ -186,46 +106,7 @@ namespace SabreTools.Serialization.Wrappers
/// <param name="length">Length of the requested data</param>
/// <returns>Byte array containing the requested data, null on error</returns>
public byte[]? ReadFromDataSource(int position, int length)
{
// Validate the data source
if (!DataSourceIsValid())
return null;
// Validate the requested segment
if (!SegmentValid(position, length))
return null;
try
{
// Read and return the data
byte[]? sectionData = null;
switch (_dataSource)
{
case DataSource.ByteArray:
sectionData = new byte[length];
Array.Copy(_byteArrayData!, _initialPosition + position, sectionData, 0, length);
break;
case DataSource.Stream:
lock (_streamDataLock)
{
long currentLocation = _streamData!.Position;
_streamData.Seek(_initialPosition + position, SeekOrigin.Begin);
sectionData = _streamData.ReadBytes(length);
_streamData.Seek(currentLocation, SeekOrigin.Begin);
break;
}
}
return sectionData;
}
catch
{
// Absorb the error
return null;
}
}
=> _dataSource.Read(position, length);
/// <summary>
/// Read string data from the source
@@ -260,25 +141,6 @@ namespace SabreTools.Serialization.Wrappers
return sourceStrings;
}
/// <summary>
/// Get the ending offset of the source
/// </summary>
/// <returns>Value greater than 0 for a valid end of file, -1 on error</returns>
public long GetEndOfFile()
{
// Validate the data souece
if (!DataSourceIsValid())
return -1;
// Return the effective endpoint
return _dataSource switch
{
DataSource.ByteArray => _byteArrayData!.Length - _initialPosition,
DataSource.Stream => _streamData!.Length - _initialPosition,
_ => -1,
};
}
/// <summary>
/// Read string data from the source with an encoding
/// </summary>